Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 106 additions & 0 deletions .github/workflows/showcase-acp-sdk-v2-to-v3-migration.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
name: showcase acp-sdk-v2-to-v3-migration

on:
pull_request:
paths:
- 'showcase/acp-sdk-v2-to-v3-migration/**'
- '.github/workflows/showcase-acp-sdk-v2-to-v3-migration.yml'
push:
branches:
- main
- 'feat/acp-sdk-v2-to-v3-migration'
paths:
- 'showcase/acp-sdk-v2-to-v3-migration/**'
workflow_dispatch:

permissions:
contents: read

concurrency:
group: showcase-acp-sdk-v2-to-v3-migration-${{ github.ref }}
cancel-in-progress: true

jobs:
package-check:
name: package self-check
runs-on: ubuntu-latest

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: '22'

- name: Report contributor association
env:
GH_ASSOCIATION: ${{ github.event.pull_request.author_association || 'N/A' }}
DECLARED_ASSOCIATION: champion
shell: bash
run: |
set -euo pipefail
echo "github author_association : ${GH_ASSOCIATION}"
echo "declared association : ${DECLARED_ASSOCIATION}"
{
echo "### Contributor association"
echo ""
echo "| field | value |"
echo "| --- | --- |"
echo "| github \`author_association\` | \`${GH_ASSOCIATION}\` |"
echo "| declared (manifest \`builder.association\`) | \`${DECLARED_ASSOCIATION}\` |"
echo ""
echo "\`author_association\` is derived by GitHub from repository permissions and"
echo "cannot be set by a contributor. \`champion\` is a manifest-level declaration only."
} >> "$GITHUB_STEP_SUMMARY"

- name: Assert declared association is champion
shell: bash
run: |
set -euo pipefail
manifest=showcase/acp-sdk-v2-to-v3-migration/showcase.json
declared="$(node -e "const m=require('./'+process.argv[1]);process.stdout.write(String(m.builder&&m.builder.association))" "$manifest")"
echo "builder.association = ${declared}"
test "${declared}" = "champion"

- name: Validate showcase manifests
run: node scripts/validate-showcase.mjs

- name: Syntax-check package sources
working-directory: showcase/acp-sdk-v2-to-v3-migration
run: |
set -euo pipefail
node --check examples/v3-provider.mjs
node --check examples/v3-client.mjs
node --check examples/phase-event-map.mjs
node --check examples/v2-provider.legacy.mjs

- name: Run package offline self-check
working-directory: showcase/acp-sdk-v2-to-v3-migration
run: npm run check

- name: Assert skill is installable
shell: bash
run: |
set -euo pipefail
skill=showcase/acp-sdk-v2-to-v3-migration/skills/acp-sdk-v2-to-v3-migration
test -f "${skill}/SKILL.md"
tmp_home="$(mktemp -d)"
mkdir -p "${tmp_home}/.agents/skills" "${tmp_home}/.claude/skills"
cp -R "${skill}" "${tmp_home}/.agents/skills/"
cp -R "${skill}" "${tmp_home}/.claude/skills/"
test -f "${tmp_home}/.agents/skills/acp-sdk-v2-to-v3-migration/SKILL.md"
test -f "${tmp_home}/.claude/skills/acp-sdk-v2-to-v3-migration/SKILL.md"
rm -rf "${tmp_home}"

- name: Assert proof artifacts present
shell: bash
run: |
set -euo pipefail
cd showcase/acp-sdk-v2-to-v3-migration
test -f proof/jk-drq-piano-spaces.md
test -f proof/offline-validation.md
test -f proof/telegram-qchaingoldbot.md
test -f assets/poster.png
test -f soul.md
4 changes: 4 additions & 0 deletions showcase/acp-sdk-v2-to-v3-migration/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.env
node_modules/
*.log
.DS_Store
123 changes: 123 additions & 0 deletions showcase/acp-sdk-v2-to-v3-migration/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
# ACP SDK v2 → v3 Migration

Public, dry-runnable migration kit for moving an ACP Node integration from the
**v2 two-callback `AcpClient` model** to the **v3 `AcpAgent` entry-event model**.

> Package name stays `@virtuals-protocol/acp-node-v2`. "v3" here means the
> `AcpAgent.create` / `agent.on("entry")` / `AssetToken` / hooks API surface.

## Why this exists

Virtuals published an SDK migration guide covering:

- multi-chain sessions
- non-custodial agent wallets (keys not held in app memory at rest)
- hook-based protocol (memos removed)
- unified event model shared with `acp-cli`

This showcase turns that guide into:

1. side-by-side **before/after code**
2. a **canonical phase → event map**
3. **offline self-checks** (no credentials, no network)
4. a reusable **agent skill** other builders can install

## Quick start

```bash
# from repo root
node showcase/acp-sdk-v2-to-v3-migration/scripts/self-check.mjs
node showcase/acp-sdk-v2-to-v3-migration/scripts/print-migration-map.mjs
node showcase/acp-sdk-v2-to-v3-migration/examples/v3-provider.mjs
node showcase/acp-sdk-v2-to-v3-migration/examples/v3-client.mjs
```

## Layout

```
showcase/acp-sdk-v2-to-v3-migration/
showcase.json
README.md
soul.md
assets/poster.png
examples/
v2-provider.legacy.mjs # retired shape (documentation)
v3-provider.mjs # provider skeleton + dry-run
v3-client.mjs # client skeleton + exact fund guard
phase-event-map.mjs # tables
prompt.md
result-redacted.md
proof/offline-validation.md
scripts/self-check.mjs
scripts/print-migration-map.mjs
skills/acp-sdk-v2-to-v3-migration/SKILL.md
```

## Cheat sheet

| Concern | v2 | v3 |
| --- | --- | --- |
| Construct | `new AcpClient({ onNewTask, onEvaluate })` | `await AcpAgent.create(...)` + `agent.on("entry")` + `start()` |
| Price | `job.accept` + `createRequirement` | `session.setBudget(AssetToken.usdc(a, chainId))` |
| Fund | `job.payAndAcceptRequirement` | `session.fund(AssetToken.usdc(a, chainId))` |
| Deliver | `job.deliver({type,value})` | `session.submit(deliverable)` |
| Approve / reject | `job.evaluate(true\|false)` | `session.complete` / `session.reject` |
| Create job | `offering.initiateJob` | `agent.createJobFromOffering` |
| Tokens | `Fare` / `FareAmount` | `AssetToken.usdc` |

### Phase → event

| v2 phase | v3 event | Next actor |
| --- | --- | --- |
| REQUEST | `job.created` | Provider |
| NEGOTIATION | `budget.set` | Client |
| TRANSACTION | `job.funded` | Provider |
| EVALUATION | `job.submitted` | Evaluator / Client |
| COMPLETED | `job.completed` | — |
| REJECTED | `job.rejected` | — |

## Platform step

On [app.virtuals.io](https://app.virtuals.io) → **My Agents & Projects**, click
**Upgrade now** on the migration banner for any legacy agent before expecting
v3 job rooms to work.

CLI equivalent for legacy agents:

```bash
acp agent migrate --agent-id <id> --json
acp agent migrate --agent-id <id> --complete --json
```

## Safety

- Examples default to **offline dry-run**. No private keys are embedded.
- `LIVE=1` is opt-in and requires you to supply a provider factory module.
- Client funding demos enforce **exact amount match** against `budget.set`.
- Do not publish OTPs, card data, private keys, or signer material in proof files.

## Install the skill

```bash
cp -R showcase/acp-sdk-v2-to-v3-migration/skills/acp-sdk-v2-to-v3-migration ~/.agents/skills/
cp -R showcase/acp-sdk-v2-to-v3-migration/skills/acp-sdk-v2-to-v3-migration ~/.claude/skills/
```

## Proof

See [`proof/offline-validation.md`](proof/offline-validation.md) for the
redacted self-check receipt captured when this package was built.

## Public video proof

Builder X identity: [@jk_drq](https://x.com/jk_drq)

Primary piano Space used as the showcase watch link:

- **Distorted Face Piano** — [https://x.com/i/spaces/1dKrPPWnNDzJX](https://x.com/i/spaces/1dKrPPWnNDzJX)

Spaces are linked via `links.video` (public X page). There is no stable public `video.twimg.com` mp4 for Spaces replays, so `visual.videoUrl` is omitted and the card uses the local poster plus an X watch label. Details: [`proof/jk-drq-piano-spaces.md`](proof/jk-drq-piano-spaces.md).

## Telegram

Public desk bot: [https://t.me/Qchaingoldbot](https://t.me/Qchaingoldbot) (`@Qchaingoldbot`).
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
55 changes: 55 additions & 0 deletions showcase/acp-sdk-v2-to-v3-migration/assets/poster.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
49 changes: 49 additions & 0 deletions showcase/acp-sdk-v2-to-v3-migration/examples/phase-event-map.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/**
* Canonical v2 phase → v3 event mapping (from Virtuals ACP SDK migration guide).
*/

export const PHASE_TO_EVENT = [
{ v2Phase: "REQUEST", v3Event: "job.created", nextActor: "Provider" },
{ v2Phase: "NEGOTIATION", v3Event: "budget.set", nextActor: "Client" },
{ v2Phase: "TRANSACTION", v3Event: "job.funded", nextActor: "Provider" },
{ v2Phase: "EVALUATION", v3Event: "job.submitted", nextActor: "Evaluator / Client" },
{ v2Phase: "COMPLETED", v3Event: "job.completed", nextActor: "—" },
{ v2Phase: "REJECTED", v3Event: "job.rejected", nextActor: "—" },
];

export const ACTION_TABLE = [
{ action: "Propose price", v2: "job.accept() + job.createRequirement()", v3: "session.setBudget(AssetToken.usdc(amount, chainId))" },
{ action: "Pay / fund", v2: "job.payAndAcceptRequirement()", v3: "session.fund(AssetToken.usdc(amount, chainId))" },
{ action: "Submit deliverable", v2: "job.deliver({ type, value })", v3: "session.submit(deliverable)" },
{ action: "Approve", v2: 'job.evaluate(true, "reason")', v3: 'session.complete("reason")' },
{ action: "Reject", v2: "job.evaluate(false)", v3: 'session.reject("reason")' },
];

export const INIT_TABLE = [
{ concern: "Construct agent", v2: "new AcpClient({ acpContractClient, onNewTask, onEvaluate })", v3: "await AcpAgent.create({ provider / evmProvider, ... }); agent.on('entry', handler); await agent.start()" },
{ concern: "Contract client", v2: "AcpContractClientV2.build(PRIVATE_KEY, ENTITY_ID, WALLET, config)", v3: "Provider adapters (Privy/Alchemy) — keys not held in app memory at rest" },
{ concern: "Tokens", v2: "Fare / FareAmount", v3: "AssetToken.usdc(amount, chainId)" },
{ concern: "Create job", v2: "offering.initiateJob(req, evaluator)", v3: "agent.createJobFromOffering(chainId, offering, provider, req, { evaluatorAddress })" },
{ concern: "Lifecycle control", v2: "acpClient.init()", v3: "agent.start() / agent.stop()" },
];

export function lookupPhase(v2Phase) {
const row = PHASE_TO_EVENT.find((r) => r.v2Phase === String(v2Phase).toUpperCase());
if (!row) throw new Error(`Unknown v2 phase: ${v2Phase}`);
return row;
}

async function main() {
console.log(JSON.stringify({ PHASE_TO_EVENT, ACTION_TABLE, INIT_TABLE }, null, 2));
}

const isDirect =
process.argv[1] &&
import.meta.url === new URL(process.argv[1], "file://").href;

if (isDirect) {
main().catch((err) => {
console.error(err);
process.exit(1);
});
}
14 changes: 14 additions & 0 deletions showcase/acp-sdk-v2-to-v3-migration/examples/prompt.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Demo prompt

```
Migrate my ACP Node provider from the v2 AcpClient onNewTask/onEvaluate
callbacks to the v3 AcpAgent entry-event model.

Constraints:
- package name stays @virtuals-protocol/acp-node-v2
- show phase → event map
- replace FareAmount with AssetToken.usdc
- replace job.deliver/evaluate with session.submit/complete
- run the offline self-check and print the migration map
- do not use real keys or mainnet funds
```
35 changes: 35 additions & 0 deletions showcase/acp-sdk-v2-to-v3-migration/examples/result-redacted.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Redacted result

## What ran

```bash
node showcase/acp-sdk-v2-to-v3-migration/scripts/self-check.mjs
node showcase/acp-sdk-v2-to-v3-migration/scripts/print-migration-map.mjs
node showcase/acp-sdk-v2-to-v3-migration/examples/v3-provider.mjs
node showcase/acp-sdk-v2-to-v3-migration/examples/v3-client.mjs
node scripts/validate-showcase.mjs
```

## Outcome (redacted)

- Offline self-check: **PASS** (all helper assertions green)
- Provider dry-run: emitted `setBudget` plan for `job.created` and `submit` for `job.funded`
- Client dry-run: emitted exact-amount `fund` instruction on `budget.set` and review actions on `job.submitted`
- Showcase validator: **PASS** for slug `acp-sdk-v2-to-v3-migration`
- Secrets printed: **none**
- On-chain txs: **none** (offline by design)
- Public video proof: [@jk_drq Distorted Face Piano Space](https://x.com/i/spaces/1dKrPPWnNDzJX)

## Migration deltas demonstrated

| Before | After |
| --- | --- |
| `onNewTask` / `onEvaluate` | `agent.on("entry", ...)` |
| `AcpJobPhases.*` | `entry.event.type` strings |
| `FareAmount` | `AssetToken.usdc(amount, chainId)` |
| `job.deliver` / `job.evaluate` | `session.submit` / `session.complete` |
| `offering.initiateJob` | `agent.createJobFromOffering` |

## Public PR

https://github.com/Virtual-Protocol/acp-cli-demos/pull/94
Loading
Loading