@finchip.ai/foga 0.6.0-alpha.5 → 0.6.0-alpha.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +41 -15
- package/dist/a2a/card.js +54 -0
- package/dist/a2a/server.js +276 -0
- package/dist/a2a/types.js +20 -0
- package/dist/build/bundle.js +2 -1
- package/dist/cli-cell.js +147 -16
- package/dist/cli.js +192 -90
- package/dist/demo/anvil.js +129 -0
- package/dist/demo/birth.js +71 -0
- package/dist/demo/loop-d.js +227 -111
- package/dist/demo/quickstart.js +365 -0
- package/dist/doctor/loop-d.js +29 -7
- package/dist/genesis/operator-scope.js +1 -0
- package/dist/http/access.js +105 -0
- package/dist/http/server.js +65 -43
- package/dist/http/signed.js +136 -0
- package/dist/mcp/server.js +27 -1
- package/dist/node/config.js +34 -1
- package/dist/node/documents.js +298 -0
- package/dist/node/preflight.js +208 -0
- package/dist/node/wallet.js +118 -0
- package/dist/tools/documents.js +74 -0
- package/dist/tools/index.js +2 -1
- package/dist/world/deploy.js +13 -12
- package/dist/world/sourcify.js +165 -0
- package/docs/a2a.md +31 -0
- package/docs/design-memo.md +18 -5
- package/docs/documents.md +25 -0
- package/docs/examples/README.md +5 -1
- package/docs/loop-d.md +22 -4
- package/docs/mcp.md +18 -13
- package/docs/node.md +28 -10
- package/docs/quickstart.md +87 -0
- package/docs/wallet.md +60 -0
- package/docs/world.md +12 -0
- package/package.json +3 -1
- package/scripts/prepare.mjs +21 -0
- package/server.json +2 -2
package/README.md
CHANGED
|
@@ -19,7 +19,11 @@ ERC-8004 (Trustless Agents) is a required dependency: ERC-8419, ERC-8434 and ERC
|
|
|
19
19
|
|
|
20
20
|
The project delivers three things from one kernel: the **Finch OG Agent** (the first running instance), the **FOGA node software** anyone can run with their own identity, model and wallet, and a **public access layer** (MCP, A2A, HTTP/CLI). It is built in phases; this repository has completed **Phase 2c: the genesis on a public chain**, on top of the Phase 2 genesis (T0–T9) and the Phase 1 world layer.
|
|
21
21
|
|
|
22
|
-
## Status: Phase 3c done — Loop D, the unscripted run
|
|
22
|
+
## Status: Phase 3d in progress — 3d-2 done (the wallet, signed callers, the documents on chain, preflight, A2A, Sourcify), 3d-1 done (quickstart, an outside agent's Loop D); Phase 3c done — Loop D, the unscripted run; Phase 3b done — the node served over MCP and HTTP; Phase 3a done — the kernel hardened and turned into a node with a capability catalog; Phase 2c done
|
|
23
|
+
|
|
24
|
+
**3d-2 (0.6.0-alpha.7, 2026-11-03).** The second slice of 3d is what a cell needs to stand on its own among other agents, under one principle (docs/design-memo.md, section 10): the wallet is the first step, made by default and never a reason to stop; identity is one's own; only the genome replicates. `foga wallet new` (`src/node/wallet.ts`, docs/wallet.md) makes an agent's keys on its own machine — owner, operator, guardian in 0600 files under `~/.foga/wallet`, never overwritten, no network — and `foga quickstart` makes the default wallet on its first run and births every later cell with the same keys; `--wallet <dir>` on every `foga cell` and `foga node` command takes a role's key from there. A wallet is also a credential: `Authorization: FOGA-EIP712 <envelope>` (`src/http/signed.ts`, `src/http/access.ts`) is an EIP-712 signature in the cell's own domain — bound to one request and good once, or a session for a client with static headers — and the caller is the signing address at the level `access.signers` grants it (the cell's own operator or owner, an allow-list by address or ERC-8004 agent id, a KYA standing threshold, a default); `/api`, `/mcp` and `/a2a` take it, `foga wallet sign` prints it, and every cell's MCP server carries the prompt `become-a-cell` and the resource `foga://docs/quickstart` for an agent that meets it. `foga cell docs build | publish | status` (`src/node/documents.ts`, docs/documents.md) generates the cell's documents from one configuration — the ERC-8004 registration, the ERC-8434 AID document, the ERC-8444 descriptor, the ERC-8454 unit, the A2A agent card, `server.json`, a manifest with content hashes — serves them at `/documents/*` and `/.well-known/*`, and commits them on chain as the operator's keyed job (`setAgentURI`, `setDocumentURI`, `updateDescriptor`, `publishVersion`; only what differs is sent), read back and cross-checked; `docs_status` and `docs_publish` are the catalog's 30th and 31st capabilities. `foga node preflight` (`src/node/preflight.ts`) is the readiness reconciliation — the world, the account and its allow-list, the budget, the keys, the standing, the skills run in the worker, the documents against the chain, the public URL, the model — advisory, `--strict` for CI. `POST /a2a` (`src/a2a/`, docs/a2a.md) is the A2A 1.0 JSON-RPC binding with the card at `/.well-known/agent-card.json`: a quote for anyone, a tender taken for an act-level caller as one task over the catalog's jobs, `GetTask`, `ListTasks`, `CancelTask`, errors by the specification. `foga world verify --sourcify` (`src/world/sourcify.ts`, docs/world.md) submits every FOGA-deployed contract of a world with the standard JSON input of its own compiler run (`forge build` now leaves the build-info beside the artifacts), matched by runtime code; a local chain is a dry run, which CI runs over all 47. The OG instance's default is host mode (`foga node serve`: every entrance, the watchers, no model) with embedded mode (`foga node run`, the runner with a model) on demand. Seven new tests on anvil and offline (`wallet`, `signed`, `documents`, `a2a`, `preflight`, `sourcify`, and `quickstart` extended) are the gate; `foga quickstart` now ends with a cell whose documents are on chain, whose card answers, and whose A2A entrance quotes. Next, 3d-3: the OG deployment runbook, `foga.finchip.ai`, the registry listing, the Sepolia Sourcify run, the OG cell's migration to revision 2 and an outside agent's Loop D on Sepolia.
|
|
25
|
+
|
|
26
|
+
**3d-1 (0.6.0-alpha.6, 2026-11-02).** The package is on npm as `@finchip.ai/foga` (0.6.0-alpha.4 and alpha.5, the latter with the launcher fixed for Windows), so the first slice of 3d is the two things that make it usable from nothing. `foga quickstart` (`src/demo/quickstart.ts`, docs/quickstart.md) is one command to a live cell: anvil, the local world, a cell born with keys nobody held and its two skills, two tenders minted for it, the node served (watchers, HTTP API, MCP over Streamable HTTP), both entrances checked (the handshake and tool list over HTTP with an act-level key, and over stdio with the operator key), and the MCP client configurations — Claude Desktop, Claude Code, Cursor, and the HTTP form with URL and Bearer key — printed and written with every path filled in, under `~/.foga/quickstart`. Loop D gains `--model external` (docs/loop-d.md): the node is served instead of run, an act-level key is minted for whoever proposes, the orchestrator prints a brief (the entrance, the five steps of a tender, what the run does around the agent — nothing about the cases) and waits for the agent — any model, any runtime, a person with `curl` — with the same recovery drill, the same entrance checks and the same acceptance; the evidence names the agent and carries no runner ticks, the doctor marks a case the agent left alone `NotTested` rather than `Fail`, and `--compare` skips what one run did not reach. `test/loop-d-external.test.ts` drives it with a scripted agent through the MCP entrance and every verdict passes, the model-agnostic row included against the kept stub run. Smaller: `.gitattributes` keeps every text file LF (the launcher's shebang included), a `prepare` script compiles a checkout installed straight from git, the shared cell birth (`src/demo/birth.ts`) and anvil helpers (`src/demo/anvil.ts`) serve the demos, and the docs carry the PowerShell and Windows notes. Next in 3d: `foga node preflight`, `docs build/publish` with `setAgentURI`, the A2A card and inbound mapping, Sourcify, then the OG deployment runbook, the documentation site and the registry listing.
|
|
23
27
|
|
|
24
28
|
**3c (0.6.0-alpha.4, 2026-11-02).** A model in the loop, and the acceptance that looks at records rather than words. `src/node/runner.ts` is the node's loop with a model: each tick perceives through the read capabilities, hands the situation and the catalog as tools to the model, carries out what it proposes — reads at once, acts through `Catalog.invoke` where the three checks decide and a refusal goes back so the model can revise once — and records the tick by hash (the decision records carry the model and the hashes of its request and answer; the journal the hash of the whole transcript; the text never). Limits bound a tick (12 tool calls, 2 acts) and a day (the model's calls, counted in the store). Two adapters on one interface: `anthropic` (the Messages API over `fetch`, the key in the environment, the model name in the configuration) and `stub` (a deterministic decision table that also proposes, once, a tender above the budget so the evidence shows the gate closing). `foga node run` is the embedded node — server, watchers, runner — on the operator key alone. `foga demo --loop-d` (`src/demo/loop-d.ts`) is the unscripted run: a new cell with fresh keys, an independent demander process minting cases a–f in a seeded order (two tenders to take, four to refuse at `skill`, `policy`, `verifier`, and one met during the guardian's suspension), the node killed after its first submission and restarted, the HTTP entrance used beside the runner (the same request answered once, the same job under another request a conflict, three mints at once of which the third is refused at `budget` before anything is sent), and an evidence file with every record, the cases' outcomes and a scan of every transaction the operator key signed. `foga doctor --loop-d` judges it beside layer 5: every operation under an allowed decision, refusals that broadcast nothing, the four budget books, the operator's transactions all at the account and allow-listed, the cases as expected, the recovery, the entrances, and — given two runs — the same outcomes from the stub and a real model. The slice also closed a race the run exposed: the catalog's decision section now runs one act at a time per node and a job reserves the value it will move from its decision. `test/loop-d.test.ts` (anvil, under a minute) and `test/model.test.ts` are the gate; CI runs Loop D on the world the genesis ran on. docs/loop-d.md.
|
|
25
29
|
|
|
@@ -70,11 +74,27 @@ From Phase 1, unchanged in substance:
|
|
|
70
74
|
- **The doctor.** `foga doctor` reports five layers per contract — chain and code, interfaces, version match, configuration and permissions, semantic validation — with the verdicts Pass / Fail / Unknown / Not tested / Not applicable. Layers 1–4 probe the chain; layer 5 reads the genesis evidence. A doctor run can be exported as a JSON-RPC probe plan and replayed from results collected elsewhere, so a report never depends on which machine can reach which RPC.
|
|
71
75
|
- **Sepolia address book.** `worlds/sepolia.json` records every upstream reference deployment at its deployed revision, including the three seams the inventory found (docs/version-lock.md); the completed world and the doctor's report on it are the 2c files above.
|
|
72
76
|
|
|
73
|
-
What is next: 3d closes Phase 3 —
|
|
77
|
+
What is next: 3d-3 closes Phase 3 — the OG instance's move to a revision-2 account with public endpoints (`foga.finchip.ai`), its documents and sources verified on Sepolia, the registry listing and an outside agent's Loop D run on Sepolia; Phase 4 the hosted node and payments. The design is in docs/design-memo.md.
|
|
74
78
|
|
|
75
79
|
## Quick start
|
|
76
80
|
|
|
77
|
-
|
|
81
|
+
With Node 22.13+ and Foundry's `anvil` installed, one command makes your wallet (on the first run; `~/.foga/wallet`, reused after), births a cell with it on a local chain, publishes the cell's documents and prints the MCP client configuration to connect to it (docs/quickstart.md; in PowerShell quote the name: `npx -y '@finchip.ai/foga' quickstart`):
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
npx -y @finchip.ai/foga quickstart
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
The wallet alone, for an agent that will only call cells (docs/wallet.md), and a cell on a public chain born with it:
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
npx -y @finchip.ai/foga wallet new # ~/.foga/wallet: owner, operator, guardian keys; prints the addresses to fund
|
|
91
|
+
foga cell new --wallet ~/.foga/wallet --name mine --world deployments/sepolia.json --store mine.sqlite --out mine.json
|
|
92
|
+
foga cell init --config mine.json --wallet ~/.foga/wallet
|
|
93
|
+
foga node preflight --config mine.json --wallet ~/.foga/wallet
|
|
94
|
+
foga node serve --config mine.json --wallet ~/.foga/wallet
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
From a checkout, three ways in, from most to least demanding:
|
|
78
98
|
|
|
79
99
|
```bash
|
|
80
100
|
# 1. Full reproduction (needs git, Node >= 22 and Foundry):
|
|
@@ -156,18 +176,18 @@ deployments/ world files, doctor reports, genesis evidence (local output
|
|
|
156
176
|
prover/ the ZK proving sub-package: run.cjs bridge (credential and predicate proofs), build-predicate.mjs (the development ceremony), its own dependencies
|
|
157
177
|
zk/ circuits/ (kya_predicate_count_v1.circom) and build/ (verification_key.json committed; the proving key is rebuilt locally)
|
|
158
178
|
docker/, docker-compose.yml
|
|
159
|
-
docs/ design memo, compatibility matrix, version lock, build, world, doctor, cell account, kernel, genesis, zk, node, mcp, loop-d; examples/ (client configurations)
|
|
179
|
+
docs/ design memo, compatibility matrix, version lock, build, world, doctor, cell account, kernel, genesis, zk, node, mcp, loop-d, quickstart, wallet, documents, a2a; examples/ (client configurations)
|
|
160
180
|
scenarios/skills/ the two ERC-8338 skill packages of the genesis: foga-probe, finch-extract-verify
|
|
161
181
|
scenarios/fixtures/ the pinned document finch-extract-verify works on in T5 and T7
|
|
162
182
|
scripts/ install-solc.sh, install-foundry.sh, relay.mjs, pack-smoke.mjs (the npm tarball run from a clean install)
|
|
163
183
|
src/ TypeScript: build/ (vendor + bundle), world/ (plan + deploy), doctor/ (probe + evaluate),
|
|
164
184
|
core/ (foga-core: one module per ERC incl. acdf, adrf and swarm, measure, canonicalization, DAG-CBOR packager),
|
|
165
185
|
kernel/ (store, transaction manager, cell, kernel), genesis/ (roles, operator scope, probe, extract, skill worker, zk, scenario T0-T3, t3-zk, t4..t9, runner),
|
|
166
|
-
tools/ (the capability catalog: types, catalog, read, tenders, identity, admin), node/ (config, keys, node, policy, init, migrate, watchers),
|
|
167
|
-
mcp/ (the MCP server built from the catalog), http/ (the HTTP API, MCP over Streamable HTTP, API keys),
|
|
168
|
-
node/runner.ts and node/model/ (the runner, the stub and anthropic adapters), demo/ (the Loop D demander, orchestration and evidence),
|
|
186
|
+
tools/ (the capability catalog: types, catalog, read, tenders, identity, documents, admin), node/ (config, keys, wallet, node, policy, init, migrate, watchers, documents, preflight),
|
|
187
|
+
mcp/ (the MCP server built from the catalog), http/ (the HTTP API, MCP over Streamable HTTP, API keys, signed callers), a2a/ (the A2A 1.0 binding and card),
|
|
188
|
+
node/runner.ts and node/model/ (the runner, the stub and anthropic adapters), demo/ (the Loop D demander, orchestration and evidence, quickstart), world/sourcify.ts,
|
|
169
189
|
cli.ts, cli-cell.ts (foga cell / foga node)
|
|
170
|
-
test/ unit tests: bytecode and interface ids, skill packages, canonicalization, operation store, transaction semantics against an in-process chain (mock-chain.ts), keys, policy gates, catalog, cell config, swarm/ACDF vectors, extractor, measurement resolver, doctor layer 5; node-cli.test.ts (the Loop D cases through the CLI on anvil), mcp.test.ts (the entrances over MCP and HTTP on anvil; helpers/anvil-cell.ts births the cell), model.test.ts (the stub's table, the Anthropic adapter against a stand-in API, the Loop D verdicts), loop-d.test.ts (the unscripted run with the stub on anvil)
|
|
190
|
+
test/ unit tests: bytecode and interface ids, skill packages, canonicalization, operation store, transaction semantics against an in-process chain (mock-chain.ts), keys, policy gates, catalog, cell config, swarm/ACDF vectors, extractor, measurement resolver, doctor layer 5, sourcify; node-cli.test.ts (the Loop D cases through the CLI on anvil), mcp.test.ts (the entrances over MCP and HTTP on anvil; helpers/anvil-cell.ts births the cell), model.test.ts (the stub's table, the Anthropic adapter against a stand-in API, the Loop D verdicts), loop-d.test.ts (the unscripted run with the stub on anvil), loop-d-external.test.ts, quickstart.test.ts, wallet.test.ts, signed.test.ts, documents.test.ts, a2a.test.ts, preflight.test.ts (3d, on anvil)
|
|
171
191
|
vendor/ LOCK.json (sources are materialized here and gitignored)
|
|
172
192
|
worlds/ world configs: local.json (deploy), sepolia.json (attach)
|
|
173
193
|
```
|
|
@@ -180,26 +200,32 @@ foga build [--verify] [--solc-dir <dir>] [--only <repo,repo>]
|
|
|
180
200
|
foga bundle info
|
|
181
201
|
foga world deploy --world <config> [--rpc <url>] [--out <file>] [--resume] [--gas-multiplier <x>] [--allow-chain-mismatch]
|
|
182
202
|
foga world attach --world <config> [--out <file>]
|
|
203
|
+
foga world verify --world <world.json> --sourcify [--dry-run] [--only <id,id>] [--server <url>] [--out <dir>] [--build-info <dir>] source verification from the bundle's own compiler runs
|
|
183
204
|
foga doctor --world <world|config> [--attach] [--rpc <url>] [--out <report>] [--export-probe <file>] [--import-results <file>] [--genesis <evidence>]
|
|
184
205
|
foga probe relay-script
|
|
185
206
|
foga genesis run --world <world> [--rpc <url>] [--until T0..T9] [--store <file>] [--out <evidence>] [--zk auto|require|skip] [--crash-after <key>] [--crash-before <key>]
|
|
186
207
|
foga genesis status --world <world> [--store <file>]
|
|
187
208
|
foga keys new [--out <file>] | show | export --role <role> [--out <file>] | check
|
|
209
|
+
foga wallet new [--name <n> | --dir <dir>] [--single] | show [--rpc <url>] | sign --config <cell.config.json> [--role] [--ttl <s>] [--agent-id <id>] [--method --path [--body]]
|
|
210
|
+
an agent's own keys (the first step), and a signed Authorization header for a cell
|
|
188
211
|
foga demo [--port <n>] [--keep] [--genesis <T0..T9>]
|
|
189
212
|
foga cell list [--public] | schema <capability>
|
|
190
|
-
foga cell new --name <cell> --world <world.json> --store <ops.sqlite> --
|
|
191
|
-
foga cell init --config <cell.config.json> [--capital-wei <n>] (owner key)
|
|
213
|
+
foga cell new --name <cell> --world <world.json> --store <ops.sqlite> --out <cell.config.json> (--wallet <dir> | --owner <addr> --operator <addr> --guardian <addr>)
|
|
214
|
+
foga cell init --config <cell.config.json> [--wallet <dir>] [--capital-wei <n>] (owner key)
|
|
192
215
|
foga cell migrate --config <cell.config.json> --to <new.config.json> [--operator <addr>] (owner key)
|
|
193
|
-
foga cell
|
|
216
|
+
foga cell docs build | publish | status --config <cell.config.json> [--wallet <dir>] the cell's documents, served and committed on chain (publish: operator key)
|
|
217
|
+
foga cell <capability> --config <cell.config.json> [--wallet <dir>] [--mode simulate|build|act] [--<field> <value> ...] [--json '<input>']
|
|
218
|
+
foga node preflight --config <cell.config.json> [--wallet <dir>] [--no-executors] [--no-network] [--out <report.json>] [--strict] the readiness reconciliation; sends nothing
|
|
194
219
|
foga node tick | watch --config <cell.config.json> [--interval <s>] (operator key alone)
|
|
195
|
-
foga node mcp --config <cell.config.json> MCP over stdio (= foga mcp; the operator with FOGA_OPERATOR_KEY[_FILE], reads and builds without)
|
|
196
|
-
foga node serve --config <cell.config.json> [--listen <host:port>] [--no-watch] the HTTP API, /mcp
|
|
220
|
+
foga node mcp --config <cell.config.json> MCP over stdio (= foga mcp; the operator with FOGA_OPERATOR_KEY[_FILE] or --wallet, reads and builds without)
|
|
221
|
+
foga node serve --config <cell.config.json> [--listen <host:port>] [--no-watch] host mode: the HTTP API, /mcp, /a2a, the documents, /healthz, the watchers; no model
|
|
197
222
|
foga node key new --config <cell.config.json> --id <id> [--level read|build|act] [--visibility public|caller|operator] [--note <text>]
|
|
198
223
|
foga node run --config <cell.config.json> [--model stub|anthropic] [--listen <host:port> | --no-http] [--interval <s>] [--once | --ticks <n>] [--transcripts <dir>]
|
|
199
224
|
the embedded node: server, watchers and the runner (operator key alone)
|
|
200
|
-
foga demo --loop-d [--model stub|anthropic] [--model-name <name>] [--seed <n>] [--no-recovery] anvil, the world, then Loop D judged by the doctor
|
|
201
|
-
foga loop-d run --world <world.json> --rpc <url> [--dir <dir>] [--out <evidence.json>] [--model] [--model-name] [--seed] [--no-recovery]
|
|
225
|
+
foga demo --loop-d [--model stub|anthropic|external] [--model-name <name>] [--seed <n>] [--no-recovery] anvil, the world, then Loop D judged by the doctor
|
|
226
|
+
foga loop-d run --world <world.json> --rpc <url> [--dir <dir>] [--out <evidence.json>] [--model] [--model-name] [--seed] [--no-recovery] [--listen] [--wait <s>] [--idle <s>]
|
|
202
227
|
foga doctor [--world <world.json>] --loop-d <evidence.json> [--compare <evidence2.json>] [--out <report.json>]
|
|
228
|
+
foga quickstart [--dir <dir>] [--wallet <dir>] [--port <n>] [--listen <host:port> | --no-serve] [--name <cell>] [--no-tenders] [--exit] one command to a live cell (the wallet first) and its client configuration
|
|
203
229
|
```
|
|
204
230
|
|
|
205
231
|
During development the CLI runs from source with `npm run foga -- <args>`; `npm run build` compiles it to `dist/` for the `foga` binary.
|
package/dist/a2a/card.js
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { SIGNED_SCHEME } from "../http/signed.js";
|
|
2
|
+
import { A2A_PROTOCOL_VERSION } from "./types.js";
|
|
3
|
+
/** The public endpoints of a cell: the configuration's, or derived from runtime.http.publicUrl. */
|
|
4
|
+
export function endpointsOf(node) {
|
|
5
|
+
const cfg = node.config;
|
|
6
|
+
const base = cfg.runtime.http.publicUrl?.replace(/\/+$/, "") ?? null;
|
|
7
|
+
return {
|
|
8
|
+
a2a: cfg.presence.endpoints.a2a ?? (base ? `${base}/a2a` : null),
|
|
9
|
+
mcp: cfg.presence.endpoints.mcp ?? (base ? `${base}/mcp` : null),
|
|
10
|
+
web: cfg.presence.endpoints.web ?? cfg.presence.homepage ?? base,
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
export async function buildAgentCard(node, o) {
|
|
14
|
+
const cfg = node.config;
|
|
15
|
+
const endpoints = endpointsOf(node);
|
|
16
|
+
const held = node.cell ? await node.heldSkills() : [];
|
|
17
|
+
const skills = held
|
|
18
|
+
.filter((h) => cfg.skills.offered.includes(h.name))
|
|
19
|
+
.map((h) => {
|
|
20
|
+
const m = h.pkg.manifest;
|
|
21
|
+
const version = m.skillVersion ?? String(h.pkg.version);
|
|
22
|
+
return {
|
|
23
|
+
id: `${h.name}@${version}`,
|
|
24
|
+
name: h.name,
|
|
25
|
+
description: m.summary ?? `The ERC-8338 skill ${h.name} (package ${h.packageHash})`,
|
|
26
|
+
tags: ["erc8338", "foga", h.name, ...(m.skillType ? [m.skillType] : [])],
|
|
27
|
+
examples: [
|
|
28
|
+
`{"foga":{"mode":"quote","skill":"${h.name}","input":{}}}`,
|
|
29
|
+
`{"foga":{"mode":"tender","tokenId":"<an ERC-8414 tender for ${h.name}, funded, with the hashlock committed>"}}`,
|
|
30
|
+
],
|
|
31
|
+
inputModes: ["application/json", "text/plain"],
|
|
32
|
+
outputModes: ["application/json"],
|
|
33
|
+
};
|
|
34
|
+
});
|
|
35
|
+
const card = {
|
|
36
|
+
name: cfg.presence.name,
|
|
37
|
+
description: cfg.presence.description || `FOGA cell ${cfg.name}`,
|
|
38
|
+
supportedInterfaces: endpoints.a2a ? [{ url: endpoints.a2a, protocolBinding: "JSONRPC", protocolVersion: A2A_PROTOCOL_VERSION }] : [],
|
|
39
|
+
...(cfg.presence.provider ? { provider: { organization: cfg.presence.provider.organization, url: cfg.presence.provider.url } } : {}),
|
|
40
|
+
version: o.version,
|
|
41
|
+
documentationUrl: "https://github.com/garyyang-finchip/foga#readme",
|
|
42
|
+
capabilities: { streaming: false, pushNotifications: false, extendedAgentCard: false },
|
|
43
|
+
securitySchemes: {
|
|
44
|
+
"api-key": { httpAuthSecurityScheme: { scheme: "Bearer", description: "An API key the cell's operator issued (foga node key new)." } },
|
|
45
|
+
"wallet-signature": { httpAuthSecurityScheme: { scheme: SIGNED_SCHEME, description: "An EIP-712 envelope signed by the caller's own wallet for this cell (docs/mcp.md, signed calls)." } },
|
|
46
|
+
},
|
|
47
|
+
securityRequirements: [],
|
|
48
|
+
defaultInputModes: ["text/plain", "application/json"],
|
|
49
|
+
defaultOutputModes: ["application/json", "text/plain"],
|
|
50
|
+
skills,
|
|
51
|
+
...(cfg.presence.image ? { iconUrl: cfg.presence.image } : {}),
|
|
52
|
+
};
|
|
53
|
+
return card;
|
|
54
|
+
}
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { ACTION_RANK } from "../tools/index.js";
|
|
3
|
+
import { A2A_ERRORS, A2A_PROTOCOL_VERSION, TERMINAL_STATES } from "./types.js";
|
|
4
|
+
const TASK_PREFIX = "a2a.task.";
|
|
5
|
+
const MSG_PREFIX = "a2a.msg.";
|
|
6
|
+
const QUOTE_SKILL_LIMIT = 50;
|
|
7
|
+
const now = () => new Date().toISOString();
|
|
8
|
+
function errorInfo(reason, metadata = {}) {
|
|
9
|
+
return { "@type": "type.googleapis.com/google.rpc.ErrorInfo", reason, domain: "a2a-protocol.org", metadata: { ...metadata, timestamp: now() } };
|
|
10
|
+
}
|
|
11
|
+
export class A2AError extends Error {
|
|
12
|
+
code;
|
|
13
|
+
data;
|
|
14
|
+
constructor(code, message, data) {
|
|
15
|
+
super(message);
|
|
16
|
+
this.code = code;
|
|
17
|
+
this.data = data;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
function agentMessage(text, data, taskId, contextId) {
|
|
21
|
+
const parts = [{ text }];
|
|
22
|
+
if (data !== undefined)
|
|
23
|
+
parts.push({ data, mediaType: "application/json" });
|
|
24
|
+
return { messageId: randomUUID(), role: "ROLE_AGENT", parts, ...(taskId ? { taskId } : {}), ...(contextId ? { contextId } : {}) };
|
|
25
|
+
}
|
|
26
|
+
/** The `foga` request in a message: from a data part with a `foga` object, or a text part (a question in words → quote). */
|
|
27
|
+
export function fogaRequestOf(message) {
|
|
28
|
+
for (const p of message.parts) {
|
|
29
|
+
const d = p.data;
|
|
30
|
+
if (d && typeof d === "object" && d.foga && typeof d.foga === "object") {
|
|
31
|
+
const f = d.foga;
|
|
32
|
+
const mode = f.mode === "tender" ? "tender" : "quote";
|
|
33
|
+
return { mode, skill: typeof f.skill === "string" ? f.skill : undefined, input: f.input, tokenId: f.tokenId !== undefined ? String(f.tokenId) : undefined };
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
const text = message.parts.map((p) => p.text).filter((t) => typeof t === "string").join("\n").trim();
|
|
37
|
+
return { mode: "quote", text: text || undefined };
|
|
38
|
+
}
|
|
39
|
+
export class A2AServer {
|
|
40
|
+
node;
|
|
41
|
+
catalog;
|
|
42
|
+
log;
|
|
43
|
+
constructor(node, catalog, log = () => { }) {
|
|
44
|
+
this.node = node;
|
|
45
|
+
this.catalog = catalog;
|
|
46
|
+
this.log = log;
|
|
47
|
+
}
|
|
48
|
+
get store() {
|
|
49
|
+
return this.node.store;
|
|
50
|
+
}
|
|
51
|
+
load(id) {
|
|
52
|
+
return this.store.getFact(TASK_PREFIX + id);
|
|
53
|
+
}
|
|
54
|
+
save(t) {
|
|
55
|
+
this.store.setFact(TASK_PREFIX + t.task.id, t);
|
|
56
|
+
}
|
|
57
|
+
visible(t, caller) {
|
|
58
|
+
return caller.visibility === "operator" || t.caller === caller.id;
|
|
59
|
+
}
|
|
60
|
+
setState(t, state, message) {
|
|
61
|
+
t.task.status = { state, timestamp: now(), ...(message ? { message } : {}) };
|
|
62
|
+
if (message)
|
|
63
|
+
t.task.history = [...(t.task.history ?? []), message];
|
|
64
|
+
this.save(t);
|
|
65
|
+
}
|
|
66
|
+
/** The JSON-RPC dispatcher: one request in, one response out; the HTTP layer checks the version header and the media type. */
|
|
67
|
+
async handle(request, caller, version) {
|
|
68
|
+
const id = request?.id ?? null;
|
|
69
|
+
try {
|
|
70
|
+
if (!request || typeof request !== "object" || request.jsonrpc !== "2.0" || typeof request.method !== "string")
|
|
71
|
+
throw new A2AError(A2A_ERRORS.InvalidRequestError, "Request payload validation error");
|
|
72
|
+
const v = (version ?? "").trim() || "0.3";
|
|
73
|
+
if (v !== A2A_PROTOCOL_VERSION)
|
|
74
|
+
throw new A2AError(A2A_ERRORS.VersionNotSupportedError, `A2A version ${v} is not supported; this agent speaks ${A2A_PROTOCOL_VERSION} (send A2A-Version: ${A2A_PROTOCOL_VERSION})`, [errorInfo("VERSION_NOT_SUPPORTED", { requested: v, supported: [A2A_PROTOCOL_VERSION] })]);
|
|
75
|
+
const { method, params } = request;
|
|
76
|
+
let result;
|
|
77
|
+
switch (method) {
|
|
78
|
+
case "SendMessage":
|
|
79
|
+
result = await this.sendMessage(params, caller);
|
|
80
|
+
break;
|
|
81
|
+
case "GetTask":
|
|
82
|
+
result = this.getTask(params, caller);
|
|
83
|
+
break;
|
|
84
|
+
case "ListTasks":
|
|
85
|
+
result = this.listTasks((params ?? {}), caller);
|
|
86
|
+
break;
|
|
87
|
+
case "CancelTask":
|
|
88
|
+
result = await this.cancelTask(params, caller);
|
|
89
|
+
break;
|
|
90
|
+
case "SendStreamingMessage":
|
|
91
|
+
case "SubscribeToTask":
|
|
92
|
+
throw new A2AError(A2A_ERRORS.UnsupportedOperationError, "streaming is not supported by this agent (capabilities.streaming is false)");
|
|
93
|
+
case "GetExtendedAgentCard":
|
|
94
|
+
throw new A2AError(A2A_ERRORS.UnsupportedOperationError, "no extended agent card (capabilities.extendedAgentCard is false)");
|
|
95
|
+
case "CreateTaskPushNotificationConfig":
|
|
96
|
+
case "GetTaskPushNotificationConfig":
|
|
97
|
+
case "ListTaskPushNotificationConfigs":
|
|
98
|
+
case "DeleteTaskPushNotificationConfig":
|
|
99
|
+
throw new A2AError(A2A_ERRORS.PushNotificationNotSupportedError, "push notifications are not supported by this agent (capabilities.pushNotifications is false)");
|
|
100
|
+
default:
|
|
101
|
+
throw new A2AError(A2A_ERRORS.MethodNotFoundError, "Method not found");
|
|
102
|
+
}
|
|
103
|
+
return { jsonrpc: "2.0", id, result };
|
|
104
|
+
}
|
|
105
|
+
catch (e) {
|
|
106
|
+
if (e instanceof A2AError)
|
|
107
|
+
return { jsonrpc: "2.0", id, error: { code: e.code, message: e.message, ...(e.data ? { data: e.data } : {}) } };
|
|
108
|
+
this.log(`a2a: ${e.message}`);
|
|
109
|
+
return { jsonrpc: "2.0", id, error: { code: A2A_ERRORS.InternalError, message: "Internal error" } };
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
// ---------------------------------------------------------------------------- SendMessage
|
|
113
|
+
async sendMessage(params, caller) {
|
|
114
|
+
const m = params?.message;
|
|
115
|
+
if (!m || typeof m !== "object" || typeof m.messageId !== "string" || !Array.isArray(m.parts) || m.parts.length === 0)
|
|
116
|
+
throw new A2AError(A2A_ERRORS.InvalidParamsError, "Invalid parameters", [{ "@type": "type.googleapis.com/google.rpc.BadRequest", fieldViolations: [{ field: "message", description: "messageId and at least one part are required" }] }]);
|
|
117
|
+
for (const p of m.parts) {
|
|
118
|
+
if (p.raw !== undefined || p.url !== undefined)
|
|
119
|
+
throw new A2AError(A2A_ERRORS.ContentTypeNotSupportedError, "file parts are not accepted; send text/plain or an application/json data part");
|
|
120
|
+
if (p.mediaType && !/^(text\/plain|application\/json)\b/.test(p.mediaType))
|
|
121
|
+
throw new A2AError(A2A_ERRORS.ContentTypeNotSupportedError, `media type ${p.mediaType} is not accepted; text/plain or application/json`);
|
|
122
|
+
}
|
|
123
|
+
// the same message from the same caller is the same task (3.3.1: messageId detects duplicates)
|
|
124
|
+
const dedupeKey = `${MSG_PREFIX}${caller.id}:${m.messageId}`;
|
|
125
|
+
const known = this.store.getFact(dedupeKey);
|
|
126
|
+
if (known) {
|
|
127
|
+
const t = this.load(known);
|
|
128
|
+
if (t)
|
|
129
|
+
return { task: this.publicTask(t, params.configuration?.historyLength) };
|
|
130
|
+
}
|
|
131
|
+
// a message to an existing task: input for one that asked, nothing for a terminal one
|
|
132
|
+
if (m.taskId) {
|
|
133
|
+
const t = this.load(m.taskId);
|
|
134
|
+
if (!t || !this.visible(t, caller))
|
|
135
|
+
throw new A2AError(A2A_ERRORS.TaskNotFoundError, "Task not found", [errorInfo("TASK_NOT_FOUND", { taskId: m.taskId })]);
|
|
136
|
+
if (TERMINAL_STATES.includes(t.task.status.state))
|
|
137
|
+
throw new A2AError(A2A_ERRORS.UnsupportedOperationError, `task ${m.taskId} is ${t.task.status.state} and accepts no further message`);
|
|
138
|
+
t.messageIds.push(m.messageId);
|
|
139
|
+
t.task.history = [...(t.task.history ?? []), m];
|
|
140
|
+
this.store.setFact(dedupeKey, t.task.id);
|
|
141
|
+
await this.work(t, m, caller);
|
|
142
|
+
return { task: this.publicTask(t, params.configuration?.historyLength) };
|
|
143
|
+
}
|
|
144
|
+
const task = { task: { id: randomUUID(), contextId: m.contextId ?? randomUUID(), status: { state: "TASK_STATE_SUBMITTED", timestamp: now() }, history: [m], artifacts: [], metadata: { caller: caller.id, cell: this.node.config.name } }, caller: caller.id, messageIds: [m.messageId], createdAt: now(), jobs: {} };
|
|
145
|
+
this.save(task);
|
|
146
|
+
this.store.setFact(dedupeKey, task.task.id);
|
|
147
|
+
await this.work(task, m, caller);
|
|
148
|
+
return { task: this.publicTask(task, params.configuration?.historyLength) };
|
|
149
|
+
}
|
|
150
|
+
/** Decide what a message asks for and do it: a quote at once, a tender's work started. */
|
|
151
|
+
async work(t, m, caller) {
|
|
152
|
+
const req = fogaRequestOf(m);
|
|
153
|
+
if (req.mode === "quote") {
|
|
154
|
+
await this.quote(t, req);
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
const tokenId = req.tokenId ?? t.tokenId;
|
|
158
|
+
if (!tokenId) {
|
|
159
|
+
this.setState(t, "TASK_STATE_INPUT_REQUIRED", agentMessage("Which tender? Send a data part {\"foga\":{\"mode\":\"tender\",\"tokenId\":\"<id>\"}} naming an ERC-8414 tender that exists, is funded, and has its answer committed to the hashlock verifier.", { foga: { need: ["tokenId"] } }, t.task.id, t.task.contextId));
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
if (ACTION_RANK[caller.level] < ACTION_RANK.act) {
|
|
163
|
+
this.setState(t, "TASK_STATE_REJECTED", agentMessage("This caller may not ask the cell to act: the tender mode needs an act-level credential (an API key the operator issued, or a wallet signature the cell's policy grants). A quote is open to everyone.", { foga: { gate: "permission", callerLevel: caller.level } }, t.task.id, t.task.contextId));
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
t.tokenId = tokenId;
|
|
167
|
+
this.setState(t, "TASK_STATE_WORKING", agentMessage(`Working tender ${tokenId}: pre-check, run, submit. Settlement is the node's own duty afterwards.`, undefined, t.task.id, t.task.contextId));
|
|
168
|
+
// the work continues after the response (3.3.3); GetTask reads the result
|
|
169
|
+
void this.takeTender(t, tokenId, caller).catch((e) => {
|
|
170
|
+
this.log(`a2a: task ${t.task.id}: ${e.message}`);
|
|
171
|
+
const fresh = this.load(t.task.id) ?? t;
|
|
172
|
+
this.setState(fresh, "TASK_STATE_FAILED", agentMessage(`The work failed: ${e.message}`, undefined, t.task.id, t.task.contextId));
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
async quote(t, req) {
|
|
176
|
+
const cfg = this.node.config;
|
|
177
|
+
const held = this.node.cell ? await this.node.heldSkills() : [];
|
|
178
|
+
const offered = held.filter((h) => cfg.skills.offered.includes(h.name)).slice(0, QUOTE_SKILL_LIMIT);
|
|
179
|
+
const terms = { acceptance: "hashlock", verifier: this.node.acceptance.hashlock, asset: "native", minRewardWei: cfg.policy.tenders.minRewardWei, minSubmitWindowSeconds: cfg.policy.tenders.minSubmitWindowSeconds, minSettleWindowSeconds: cfg.policy.tenders.minSettleWindowSeconds, counterpartyMinLevel: cfg.policy.tenders.counterpartyMinLevel };
|
|
180
|
+
const how = "Mint an ERC-8414 tender for the skill with these terms (tender_mint_build gives the calls for your own account), fund it, commit the answer hash to the hashlock verifier, then send {\"foga\":{\"mode\":\"tender\",\"tokenId\":\"<id>\"}} with an act-level credential.";
|
|
181
|
+
if (req.skill && !offered.some((h) => h.name === req.skill)) {
|
|
182
|
+
this.setState(t, "TASK_STATE_REJECTED", agentMessage(`The cell does not offer a skill named ${req.skill}. Offered: ${offered.map((h) => h.name).join(", ") || "none"}.`, { foga: { quote: { skill: req.skill, offered: false, available: offered.map((h) => h.name) } } }, t.task.id, t.task.contextId));
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
const chosen = req.skill ? offered.filter((h) => h.name === req.skill) : offered;
|
|
186
|
+
const quote = { cell: cfg.name, account: this.node.cell?.address ?? null, skills: chosen.map((h) => ({ name: h.name, version: String(h.pkg.manifest.skillVersion ?? h.pkg.version), packageHash: h.packageHash, tokenId: h.tokenId.toString(), summary: h.pkg.manifest.summary ?? null })), terms, ...(req.input !== undefined ? { inputEchoed: req.input } : {}), how };
|
|
187
|
+
const artifact = { artifactId: randomUUID(), name: "quote", description: "What the cell can do and on what terms", parts: [{ data: { foga: { quote } }, mediaType: "application/json" }] };
|
|
188
|
+
t.task.artifacts = [artifact];
|
|
189
|
+
const text = req.text ? `You asked: ${req.text.slice(0, 200)}. ` : "";
|
|
190
|
+
this.setState(t, "TASK_STATE_COMPLETED", agentMessage(`${text}The cell offers ${chosen.length} skill(s)${chosen.length ? `: ${chosen.map((h) => h.name).join(", ")}` : ""}; the terms are in the quote artifact. Nothing ran and nothing touched the chain.`, undefined, t.task.id, t.task.contextId));
|
|
191
|
+
}
|
|
192
|
+
/** The tender road: the catalog decides every step as the caller; the task records the decisions. */
|
|
193
|
+
async takeTender(t, tokenId, caller) {
|
|
194
|
+
const reject = (text, data) => this.setState(this.load(t.task.id) ?? t, "TASK_STATE_REJECTED", agentMessage(text, data, t.task.id, t.task.contextId));
|
|
195
|
+
const cancelled = () => Boolean(this.load(t.task.id)?.cancelRequested);
|
|
196
|
+
const pre = await this.catalog.invoke(this.node, "tender_precheck", { tokenId }, caller, { mode: "act" });
|
|
197
|
+
if (!pre.ok)
|
|
198
|
+
return reject(`Tender ${tokenId} refused at ${pre.gate}: ${pre.reason}`, { foga: { gate: pre.gate, reason: pre.reason, decisionId: pre.decisionId ?? null } });
|
|
199
|
+
const view = pre.output.tender;
|
|
200
|
+
if (!view.task)
|
|
201
|
+
return reject(`Tender ${tokenId} has no readable task document`, { foga: { gate: "input" } });
|
|
202
|
+
if (cancelled())
|
|
203
|
+
return this.setState(this.load(t.task.id), "TASK_STATE_CANCELED", agentMessage("Cancelled before the run.", undefined, t.task.id, t.task.contextId));
|
|
204
|
+
const run = await this.catalog.invoke(this.node, "skill_run", { skill: view.task.skill, input: view.task.input, tokenId }, caller, { mode: "act" });
|
|
205
|
+
if (!run.ok)
|
|
206
|
+
return reject(`The run for tender ${tokenId} was refused at ${run.gate}: ${run.reason}`, { foga: { gate: run.gate, reason: run.reason, decisionId: run.decisionId ?? null } });
|
|
207
|
+
const stored = this.load(t.task.id) ?? t;
|
|
208
|
+
stored.jobs.run = run.jobId;
|
|
209
|
+
this.save(stored);
|
|
210
|
+
if (cancelled())
|
|
211
|
+
return this.setState(this.load(t.task.id), "TASK_STATE_CANCELED", agentMessage("Cancelled before the submission.", undefined, t.task.id, t.task.contextId));
|
|
212
|
+
const out = run.output;
|
|
213
|
+
const sub = await this.catalog.invoke(this.node, "tender_submit", { tokenId, resultHash: out.resultHash, runReceipt: out.runReceipt }, caller, { mode: "act" });
|
|
214
|
+
const latest = this.load(t.task.id) ?? t;
|
|
215
|
+
if (!sub.ok) {
|
|
216
|
+
latest.jobs.submit = sub.jobId;
|
|
217
|
+
this.save(latest);
|
|
218
|
+
return reject(`The submission for tender ${tokenId} was refused at ${sub.gate}: ${sub.reason}`, { foga: { gate: sub.gate, reason: sub.reason, decisionId: sub.decisionId ?? null, jobId: sub.jobId ?? null } });
|
|
219
|
+
}
|
|
220
|
+
latest.jobs.submit = sub.jobId;
|
|
221
|
+
const result = sub.output;
|
|
222
|
+
latest.task.artifacts = [{ artifactId: randomUUID(), name: "submission", description: "The cell's submission on the tender", parts: [{ data: { foga: { tokenId, resultHash: out.resultHash, answerHash: out.answerHash, submissionId: result.submissionId ?? null, txHash: result.txHash ?? null, jobId: sub.jobId, decisionId: sub.decisionId ?? null, operations: sub.operations ?? [] } }, mediaType: "application/json" }] }];
|
|
223
|
+
this.setState(latest, "TASK_STATE_COMPLETED", agentMessage(`Tender ${tokenId} submitted (job ${sub.jobId}); the node settles it once the chain allows.`, undefined, t.task.id, t.task.contextId));
|
|
224
|
+
}
|
|
225
|
+
// ---------------------------------------------------------------------------- GetTask, ListTasks, CancelTask
|
|
226
|
+
getTask(params, caller) {
|
|
227
|
+
if (!params || typeof params.id !== "string")
|
|
228
|
+
throw new A2AError(A2A_ERRORS.InvalidParamsError, "Invalid parameters", [{ "@type": "type.googleapis.com/google.rpc.BadRequest", fieldViolations: [{ field: "id", description: "the task id is required" }] }]);
|
|
229
|
+
const t = this.load(params.id);
|
|
230
|
+
if (!t || !this.visible(t, caller))
|
|
231
|
+
throw new A2AError(A2A_ERRORS.TaskNotFoundError, "Task not found", [errorInfo("TASK_NOT_FOUND", { taskId: params.id })]);
|
|
232
|
+
return this.publicTask(t, params.historyLength);
|
|
233
|
+
}
|
|
234
|
+
listTasks(params, caller) {
|
|
235
|
+
const all = Object.values(this.store.facts(TASK_PREFIX));
|
|
236
|
+
let tasks = all.filter((t) => this.visible(t, caller));
|
|
237
|
+
if (params.contextId)
|
|
238
|
+
tasks = tasks.filter((t) => t.task.contextId === params.contextId);
|
|
239
|
+
if (params.status)
|
|
240
|
+
tasks = tasks.filter((t) => t.task.status.state === params.status);
|
|
241
|
+
if (params.statusTimestampAfter)
|
|
242
|
+
tasks = tasks.filter((t) => (t.task.status.timestamp ?? "") > params.statusTimestampAfter);
|
|
243
|
+
tasks.sort((a, b) => (a.createdAt < b.createdAt ? 1 : -1));
|
|
244
|
+
const pageSize = Math.min(100, Math.max(1, params.pageSize ?? 50));
|
|
245
|
+
const offset = params.pageToken ? Number(params.pageToken) || 0 : 0;
|
|
246
|
+
const page = tasks.slice(offset, offset + pageSize);
|
|
247
|
+
return { tasks: page.map((t) => this.publicTask(t, params.historyLength, params.includeArtifacts ?? false)), nextPageToken: offset + pageSize < tasks.length ? String(offset + pageSize) : "", pageSize, totalSize: tasks.length };
|
|
248
|
+
}
|
|
249
|
+
async cancelTask(params, caller) {
|
|
250
|
+
if (!params || typeof params.id !== "string")
|
|
251
|
+
throw new A2AError(A2A_ERRORS.InvalidParamsError, "Invalid parameters");
|
|
252
|
+
const t = this.load(params.id);
|
|
253
|
+
if (!t || !this.visible(t, caller))
|
|
254
|
+
throw new A2AError(A2A_ERRORS.TaskNotFoundError, "Task not found", [errorInfo("TASK_NOT_FOUND", { taskId: params.id })]);
|
|
255
|
+
if (TERMINAL_STATES.includes(t.task.status.state))
|
|
256
|
+
throw new A2AError(A2A_ERRORS.TaskNotCancelableError, `task ${params.id} is ${t.task.status.state}`, [errorInfo("TASK_NOT_CANCELABLE", { taskId: params.id, state: t.task.status.state })]);
|
|
257
|
+
// a submission on its way to the chain is not undone by a cancellation: what was broadcast stays
|
|
258
|
+
if (t.jobs.submit)
|
|
259
|
+
throw new A2AError(A2A_ERRORS.TaskNotCancelableError, `task ${params.id} has submitted on chain (job ${t.jobs.submit}); a broadcast is not undone`, [errorInfo("TASK_NOT_CANCELABLE", { taskId: params.id, jobId: t.jobs.submit })]);
|
|
260
|
+
if (t.task.status.state === "TASK_STATE_WORKING") {
|
|
261
|
+
t.cancelRequested = true;
|
|
262
|
+
this.save(t);
|
|
263
|
+
return this.publicTask(t);
|
|
264
|
+
}
|
|
265
|
+
this.setState(t, "TASK_STATE_CANCELED", agentMessage("Cancelled.", undefined, t.task.id, t.task.contextId));
|
|
266
|
+
return this.publicTask(t);
|
|
267
|
+
}
|
|
268
|
+
publicTask(t, historyLength, artifacts = true) {
|
|
269
|
+
const task = { ...t.task };
|
|
270
|
+
if (historyLength !== undefined && task.history)
|
|
271
|
+
task.history = historyLength <= 0 ? [] : task.history.slice(-historyLength);
|
|
272
|
+
if (!artifacts)
|
|
273
|
+
delete task.artifacts;
|
|
274
|
+
return task;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export const TERMINAL_STATES = ["TASK_STATE_COMPLETED", "TASK_STATE_FAILED", "TASK_STATE_CANCELED", "TASK_STATE_REJECTED"];
|
|
2
|
+
/** JSON-RPC error codes of the binding (specification section 5.4 and 9.5). */
|
|
3
|
+
export const A2A_ERRORS = {
|
|
4
|
+
JSONParseError: -32700,
|
|
5
|
+
InvalidRequestError: -32600,
|
|
6
|
+
MethodNotFoundError: -32601,
|
|
7
|
+
InvalidParamsError: -32602,
|
|
8
|
+
InternalError: -32603,
|
|
9
|
+
TaskNotFoundError: -32001,
|
|
10
|
+
TaskNotCancelableError: -32002,
|
|
11
|
+
PushNotificationNotSupportedError: -32003,
|
|
12
|
+
UnsupportedOperationError: -32004,
|
|
13
|
+
ContentTypeNotSupportedError: -32005,
|
|
14
|
+
InvalidAgentResponseError: -32006,
|
|
15
|
+
ExtendedAgentCardNotConfiguredError: -32007,
|
|
16
|
+
ExtensionSupportRequiredError: -32008,
|
|
17
|
+
VersionNotSupportedError: -32009,
|
|
18
|
+
};
|
|
19
|
+
export const A2A_PROTOCOL_VERSION = "1.0";
|
|
20
|
+
export const A2A_WELL_KNOWN = "/.well-known/agent-card.json";
|
package/dist/build/bundle.js
CHANGED
|
@@ -35,7 +35,8 @@ function solcBinary(profiles, version, solcDir) {
|
|
|
35
35
|
function forgeBuild(projectDir, outDir, skip, solcPath, extraArgs = []) {
|
|
36
36
|
rmrf(outDir);
|
|
37
37
|
ensureDir(outDir);
|
|
38
|
-
|
|
38
|
+
// the build-info files (the standard JSON input and output of each compiler run) stay beside the artifacts: `foga world verify --sourcify` submits them
|
|
39
|
+
const args = ["build", "--ast", "--no-cache", "--out", outDir, "--build-info", "--build-info-path", join(outDir, "build-info"), ...extraArgs];
|
|
39
40
|
for (const s of skip)
|
|
40
41
|
args.push("--skip", s);
|
|
41
42
|
if (solcPath)
|