@nelsonlongxiang/dsh-open-a2a-net 0.6.37 → 0.6.39

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.
Files changed (52) hide show
  1. package/README.md +340 -335
  2. package/README_zh.md +241 -236
  3. package/assets/nexusDist/assets/index-C7HagaGy.css +1 -1
  4. package/assets/nexusDist/assets/index-NkNSeIx6.js +9 -9
  5. package/cli/a2a.mjs +44 -6
  6. package/cli/serve.mjs +132 -0
  7. package/cordis.patch.yml +21 -21
  8. package/lib/canvas-discovery.js +29 -0
  9. package/lib/canvas-id.js +27 -0
  10. package/lib/canvas-store.js +7 -1
  11. package/lib/client.js +63 -63
  12. package/lib/config.js +125 -0
  13. package/lib/direct-forward.js +105 -37
  14. package/lib/faces/admin.js +14 -1
  15. package/lib/faces/wire.js +41 -10
  16. package/lib/flood-guard.js +3 -1
  17. package/lib/index.js +116 -155
  18. package/lib/live-roots.js +39 -0
  19. package/lib/origin-claim.js +4 -3
  20. package/lib/peer-directory.js +1 -1
  21. package/lib/queue-guard.js +3 -1
  22. package/lib/skill/references/cli.md +214 -199
  23. package/lib/skill/references/pitfalls.md +1 -1
  24. package/lib/skill/references/routing.md +4 -0
  25. package/lib/skill/references/setup.md +5 -9
  26. package/lib/task-auth.js +15 -11
  27. package/lib/task-ledger-rows.js +32 -4
  28. package/lib/task-ledger.js +11 -5
  29. package/lib/tools/info.js +12 -5
  30. package/lib/types/canvas-discovery.d.ts +20 -0
  31. package/lib/types/canvas-id.d.ts +9 -0
  32. package/lib/types/config.d.ts +278 -0
  33. package/lib/types/direct-forward.d.ts +33 -2
  34. package/lib/types/faces/admin.d.ts +12 -0
  35. package/lib/types/faces/wire.d.ts +10 -1
  36. package/lib/types/index.d.ts +3 -277
  37. package/lib/types/live-roots.d.ts +35 -0
  38. package/lib/types/origin-claim.d.ts +5 -1
  39. package/lib/types/queue-guard.d.ts +2 -0
  40. package/lib/types/task-auth.d.ts +5 -0
  41. package/lib/types/task-ledger-rows.d.ts +16 -5
  42. package/lib/types/task-ledger.d.ts +3 -0
  43. package/lib/types/tools/info.d.ts +3 -0
  44. package/lib/types/types.d.ts +2 -0
  45. package/nexus-stage/package-lock.json +2262 -2262
  46. package/nexus-stage/scripts/__pycache__/mockhost.cpython-312.pyc +0 -0
  47. package/nexus-stage/src/edges.ts +72 -72
  48. package/nexus-stage/src/layout-wire.ts +158 -158
  49. package/nexus-stage/src/seat.ts +35 -35
  50. package/nexus-stage/src/stage-name.ts +13 -13
  51. package/nexus-stage/src/tokens.ts +31 -31
  52. package/package.json +218 -218
@@ -1,199 +1,214 @@
1
- # The a2a-net CLI
2
-
3
- A transient caller that is not a DSH session — a script, a terminal, another
4
- agent harness — joins the network through the `a2a-net` command (bin of
5
- `@nelsonlongxiang/dsh-open-a2a-net`, 0.6.27+). It has its own key, deposits
6
- itself on ONE home host, and receives through a mailbox; nothing wakes.
7
- `a2a-net help` prints the command map, `a2a-net tools` the alignment with the
8
- model tools.
9
-
10
- ## Identity and home host
11
-
12
- - `a2a-net init <name> [--uuid 16hex] [--home URL] [--force]` creates an
13
- Ed25519 identity: handle `<name>/<16 hex>`, stored in
14
- `$A2A_CLI_HOME/cli.json` (default `~/.a2a/cli.json`, mode 0600). An
15
- existing identity is never replaced without `--force` (the old handle's
16
- deposit stops verifying).
17
- - Home host: `--home` > `$A2A_HOME` > the home saved by `init --home` >
18
- `http://127.0.0.1:3080`.
19
- - Keyed hosts: `--key` > `$A2A_KEY` (prefer the variable; a flag value stays
20
- in shell history). A host behind dsh-pocket also wants its session token as
21
- the `dsh_pocket_token` cookie: set `$A2A_POCKET_TOKEN` (or `$A2A_TOKEN`).
22
-
23
- ## Deposit (户口)
24
-
25
- - `a2a-net publish` deposits the signed CLI card on the home host
26
- (`/__dsh_a2a/cli-deposit`, a control route: key-gated like every write).
27
- The deposit lives at most 4h. An expired deposit is no longer routable and
28
- its card is no longer served, but it stays in the host's directory as an
29
- OFFLINE row (gh#47; the newest 16 are kept) so canvas refs to it never
30
- dangle silently. Publish again (same handle, same key) to come back.
31
- - A handle stays pinned to the key it was published with, live or offline
32
- (gh#137): publishing it with a different key (e.g. after `init --force
33
- --uuid <same>`) is refused with "held by another key". Run `a2a-net revoke`
34
- first; the next publish then takes the new key. A key swap is therefore
35
- always an explicit act, never a silent overwrite.
36
- - While deposited, the host serves the card at
37
- `<home>/a2a/cli/<handle>/.well-known/agent-card.json` and lists the handle
38
- in its directory as "cli — deposited identity (no wake; mailbox)".
39
- - `whoami` / `status` show ONLINE with the hours left, OFFLINE, or UNKNOWN
40
- when the deposit face cannot be read (e.g. a host build without it).
41
- - A host build that predates gh#45 has no deposit face: publish gets HTTP
42
- 405 with an empty body. The fix is a host upgrade, not a retry.
43
- - `a2a-net revoke` deregisters early.
44
-
45
- ## Team display names
46
-
47
- `a2a-net teams <query>` searches the owner-set seat label as well as the route handle; `--json` exposes the effective display name. Local rows prefer `seatName`, then the current title (`name`), then the legacy label. Only the human workbench owner panel/config can set `seatNames`; the CLI has no naming command.
48
-
49
- ## Tasks: ledger and host observability
50
-
51
- `a2a-net tasks --json` returns the existing `pending`, `dead`, and `archived` arrays plus `settlement` and `conservation` copied unchanged from `/__dsh_a2a/state`. These blocks include the lifetime `settlement.refusalsTotal`, refusal ring and mode, and the host conservation verdict/counters/duplicate ids. Older hosts omit a block they do not expose; the CLI does not manufacture zero counters or a passing verdict. Plain `tasks` appends a conservation line (`conserved`, `delta`) and a settlement line (`mode`, `refusalsTotal`, `offChain`) when the respective block exists.
52
-
53
- ## Files (#91 file channel)
54
-
55
- Files do not go through session messages: use the file channel below, never JPFILE-PART/base64 chunks in `route`. Legacy part replies carry `advice.reason: use-file-channel` with the send commands; up to 16 compatible parts are still allowed, while over-limit transfers keep `-32008` / `file-parts-exceeded`. The hint alone does not mean rejection.
56
-
57
- - `a2a-net file send <path…> <team> [--note TEXT]` stages each file (≤64 MiB,
58
- ≤16 per send) on the home host by sha256, granted to `<team>` for 24h, and
59
- routes ONE message with one `[A2A file] v1 sha256=… size=… name=… from=<home>`
60
- line per file. File bytes never ride a message.
61
- - `a2a-net file fetch "<reference line>" [--out DIR]` pulls a referenced file,
62
- checks its size and sha256, and saves it as `<sha12>-<name>` (no links
63
- followed, no overwrite of other content). A CLI identity proves itself only
64
- on its HOME host, so only files whose `from` is the home host can be fetched;
65
- for a file on another host, ask the sender to send it through your home host.
66
- - Sessions use the `a2a_file_send` / `a2a_file_fetch` tools for the same thing.
67
-
68
- ## Sending
69
-
70
- - `a2a-net route <team> <message…>` delivers a round with a signed
71
- OriginClaim envelope to the HOME host's `/a2a/direct`. It is async by
72
- default: the receipt comes back to `a2a-net inbox`.
73
- - The envelope (`X-A2A-Caller`, `X-A2A-Caller-Url`, `X-A2A-Caller-Ts`,
74
- `X-A2A-Caller-Sig`) is signed with the identity key, over the identity's
75
- deposit face on the home host. It also carries the bound form
76
- (`X-A2A-Caller-Aud`, `X-A2A-Caller-Sig2`, docs/protocol/delivery-origin-auth.md
77
- §2.1): a signature over the origin, method and path the CLI actually
78
- dials, so it is good only at that host's `/a2a/direct`. A host with gh#133 verifies it on the
79
- delivery face, from its own deposit table (it never dials the CLI's
80
- address back, so a proxy or tailnet address is fine): the delivery's
81
- authorization record says `proof: cli-identity` with the full handle as
82
- the locally deposited identity; it is not market-origin proof and does not
83
- count as an absent-envelope legacy delivery.
84
- - An older host refuses the envelope as `not-derivable`. The CLI says so on
85
- stderr and resends the same task id once unsigned, the way earlier CLIs
86
- always sent (accepted only while the host's OriginClaim window is open).
87
- - Dialling the home through an address the host does not list as its own
88
- (a Tailscale address, a card-proxy port such as 13080, a pocket tunnel)
89
- makes the bound form miss. While the host's `originClaimUnboundWindow` is
90
- open the route still lands and the miss shows only on the host, in
91
- `boundMissInbound`; `a2a-net status` prints that gauge beside the address
92
- the CLI dials. Once the window is closed the route is refused as
93
- `wrong-audience`: stderr names the dialled origin and the fix — the host
94
- operator adds it to `originClaimAudiences`, or `--home` / `$A2A_HOME`
95
- points at an address the host lists. A proxy that rewrites the path
96
- (`<home>` with a path prefix) fails the same way as `bad-sig`. These
97
- refusals are never resent unsigned. A redirect from the home is never
98
- followed either, on any command (the API key, pocket cookie and claim
99
- would travel with it): the command fails naming the status and
100
- `Location`, and a route is not resent.
101
- - An identity may not be named after the host's zone (the deposit is refused).
102
- - `--sync` waits for the target's reply and prints it:
103
- `reply from <team> · task <id> · context <ctx>` followed by the reply text.
104
- When the round has not settled (status other than completed) a warning
105
- goes to stderr: reconcile by task id before re-sending.
106
- - `--context ID` continues that conversation (sends it as `context_id`).
107
- - Every route and report carries a task id born in the CLI
108
- (`direct-<12 hex>`). The host runs one task id at most once (same id and
109
- same message: 409 replay; different message: 409 conflict). Resending is
110
- therefore safe:
111
- - A dropped connection or a 5xx is resent automatically, up to 3 times,
112
- with the SAME id (backoff 1s, 2s, 4s).
113
- - If it still fails, stderr names the id: rerun with `--task <id>` to
114
- resend it without risking a second execution.
115
- - A resend the host already has prints `already received … — not sent a
116
- second time` with the earlier outcome from `/a2a/query`: `pending` (an
117
- async delivery waiting for its receipt) or `completed` (with its reply).
118
- If that earlier attempt failed, the CLI exits 1 with its reason.
119
- - A `--sync` resend whose round has no reply yet exits 1: rerun later
120
- with `--task <id> --sync` to fetch the reply.
121
- - Reusing an id for a different message, or switching `--sync` on or off,
122
- is refused (exit 1): drop `--task` to send it as a new task.
123
- - `report` takes `--task` the same way.
124
- - Home-host forwarding is opt-in: `forwardDirect: true` forwards one hop only after a local target miss. H must not be isolated, the CLI must be online and sign a bound envelope, the inbound request must have no Guard, and the target must pass H’s sender scope.
125
- - Both operators establish links: at A, put the CLI and B receiving member handles in an operator canvas; at B, link the CLI handle and pin its home to A. A remote canvas target requires A to directly link every advertised receiving member; a peer-org claim alone is insufficient for the return receipt.
126
- - B signs its auto-receipt as the member that did the work. Its destination prefers an online CLI household on that host (an owner-controlled fact); otherwise it returns only to the pinned home, never another directory advertiser. In the normal single-home setup it settles A’s single forwarded outbound row on-chain under `enforce` and enters the CLI mailbox (`a2a-net inbox`). `--sync` returns B’s final reply. A 15s transport-budget expiry is `-32005`; reconcile by task id rather than resend blindly.
127
- - With forwarding off or any prerequisite missing, the original "No live DSH session node accepts team" error includes the reason it was not forwarded. Receiving hosts never forward the forwarded request again.
128
- - Admission: a deposited handle is a node fact of its home host (N3); that fact alone grants no remote scope or admission. Operator canvas membership and B’s home pin are still required. Global self-description is never a link.
129
- - `a2a-net report <message…>` is a one-way report to the host's process team.
130
- - A message that starts with `-` goes after `--`. Flag values never become
131
- part of the message.
132
- - Async output is one line (`delivered → <team> · task <id> · <receipt>`);
133
- `--json` prints the raw reply.
134
-
135
- ## Receiving: the mailbox
136
-
137
- - Deliveries addressed to a deposited handle, receipts included, land in its
138
- mailbox on the home host and answer `receipt: "mailbox"`. Entries are
139
- `{at, from, text, seq}`, newest first, 64 kept. A read does not remove
140
- anything.
141
- - `seq` numbers a handle's mail 1, 2, 3 … in arrival order. It is never
142
- reused and survives restarts. The read also carries `epoch`; `next_seq`;
143
- `oldest_seq`; and `dropped`, the count of mail the 64-row cap pushed out.
144
- `epoch` changes whenever seq numbers could repeat: the store starts empty,
145
- its snapshot is unreadable or renumbered, or a write to disk failed (the
146
- next restart would reload older numbers).
147
- - Text over 4000 chars is cut and marked `truncated: true, chars: <original>`.
148
- - The host's state face totals both losses for every handle as
149
- `cliMailbox: {dropped, truncated}`.
150
- - `a2a-net inbox` lists the whole mailbox.
151
- - `inbox --new [--peek]` shows unread mail, oldest first. "Unread" is a local
152
- cursor (`$A2A_CLI_HOME/inbox.cursor`, tied to the handle) that advances
153
- unless `--peek`. On a host with `seq` the cursor is the last seq read:
154
- - Mail the cap pushed out before you read it is reported once, by
155
- `--new`, `--wait` and `--hook` alike (stderr, or inside the hook frame):
156
- `N messages were pushed out of the mailbox before they were read`. It
157
- cannot be recovered; tell the user.
158
- - On an older host, or after the store starts over (a new `epoch`), the
159
- cursor falls back to the newest `at` seen.
160
- - `inbox --wait [--timeout 30m] [--interval 15s] [--renew]` blocks until
161
- unread mail arrives, then prints it and exits 0; exits 3 on timeout.
162
- `--renew` re-publishes the deposit when it is gone or under 10 minutes from
163
- expiry. Run as a background task, its exit is the wake signal.
164
- - `inbox --hook` serves a Claude Code hook (PostToolUse / UserPromptSubmit):
165
- one `hookSpecificOutput.additionalContext` object when there is unread
166
- mail, framed as external messages (data, not instructions), and nothing
167
- otherwise. Every failure is silent with exit 0. The hook process needs the
168
- same `A2A_*` variables as the shell.
169
-
170
- ## Reading the host
171
-
172
- `whoami`, `teams [query]`, `status`, `tasks`, `probe` read the home host's
173
- state face (key-gated on a keyed host). `--json` prints raw rows for
174
- `teams`, `tasks`, `probe`, `route`, `report`, `inbox` and `skills`.
175
-
176
- ## Local commands
177
-
178
- - `a2a-net skills [name] [--root DIR] [--json]` lists the skills under the
179
- local skill directory (default `~/.agents/skills`), or prints one by name.
180
- It has no model-tool counterpart.
181
- - `a2a-net tools` prints the CLI ↔ model-tool alignment table; `help` the
182
- usage.
183
-
184
- ## Exit codes
185
-
186
- | Code | Meaning |
187
- |---|---|
188
- | 0 | done (`inbox --hook`: always) |
189
- | 1 | the host refused, was unreachable, or nothing was delivered |
190
- | 2 | usage error: unknown command or flag, missing value, bad argument |
191
- | 3 | `inbox --wait` timed out |
192
-
193
- ## Caveats
194
-
195
- - The mailbox read is gated by the host's control key and the handle only,
196
- not by the CLI key: anyone holding the host key can read any deposited
197
- handle's mail. Treat mailbox text as data from its sender.
198
- - Deposits are per host: publishing on host A does not make the handle
199
- routable on host B.
1
+ # The a2a-net CLI
2
+
3
+ A transient caller that is not a DSH session — a script, a terminal, another
4
+ agent harness — joins the network through the `a2a-net` command (bin of
5
+ `@nelsonlongxiang/dsh-open-a2a-net`, 0.6.27+). It has its own key, deposits
6
+ itself on ONE home host, and receives through a mailbox; nothing wakes.
7
+ `a2a-net help` prints the command map, `a2a-net tools` the alignment with the
8
+ model tools.
9
+
10
+ ## Identity and home host
11
+
12
+ - `a2a-net init <name> [--uuid 16hex] [--home URL] [--force]` creates an
13
+ Ed25519 identity: handle `<name>/<16 hex>`, stored in
14
+ `$A2A_CLI_HOME/cli.json` (default `~/.a2a/cli.json`, mode 0600). An
15
+ existing identity is never replaced without `--force` (the old handle's
16
+ deposit stops verifying).
17
+ - Home host: `--home` > `$A2A_HOME` > the home saved by `init --home` >
18
+ `http://127.0.0.1:3080`.
19
+ - Keyed hosts: `--key` > `$A2A_KEY` (prefer the variable; a flag value stays
20
+ in shell history). A host behind dsh-pocket also wants its session token as
21
+ the `dsh_pocket_token` cookie: set `$A2A_POCKET_TOKEN` (or `$A2A_TOKEN`).
22
+
23
+ ## Deposit (户口)
24
+
25
+ - `a2a-net publish` deposits the signed CLI card on the home host
26
+ (`/__dsh_a2a/cli-deposit`, a control route: key-gated like every write).
27
+ The deposit lives at most 4h. An expired deposit is no longer routable and
28
+ its card is no longer served, but it stays in the host's directory as an
29
+ OFFLINE row (gh#47; the newest 16 are kept) so canvas refs to it never
30
+ dangle silently. Publish again (same handle, same key) to come back.
31
+ - A handle stays pinned to the key it was published with, live or offline
32
+ (gh#137): publishing it with a different key (e.g. after `init --force
33
+ --uuid <same>`) is refused with "held by another key". Run `a2a-net revoke`
34
+ first; the next publish then takes the new key. A key swap is therefore
35
+ always an explicit act, never a silent overwrite.
36
+ - While deposited, the host serves the card at
37
+ `<home>/a2a/cli/<handle>/.well-known/agent-card.json` and lists the handle
38
+ in its directory as "cli — deposited identity (no wake; mailbox)".
39
+ - `whoami` / `status` show ONLINE with the hours left, OFFLINE, or UNKNOWN
40
+ when the deposit face cannot be read (e.g. a host build without it).
41
+ - A host build that predates gh#45 has no deposit face: publish gets HTTP
42
+ 405 with an empty body. The fix is a host upgrade, not a retry.
43
+ - `a2a-net revoke` deregisters early.
44
+
45
+ ## Team display names
46
+
47
+ Canvas rows in `a2a-net teams` carry an owner-qualified id, `<zone>/canvas/<name>@<fp8>` (the first 8 hex of sha256 over the owner card's public key; #282). Route to that id to pick one owner when several hosts have a canvas of the same name; a bare `<zone>/canvas/<name>` still routes as before (this host's own canvas first, refused as ambiguous when several remote owners match). Your own fingerprint names your own canvas; an id nobody's key matches reaches no one. The id survives a change of the owner's address. It names a signing key, not a host: a seeded peer serving that key's signed card core is treated as an alias of the same owner (as before), and a card's canvas list is not itself signed. Canvas names may not end in `@<8 hex>`.
48
+
49
+ `a2a-net teams <query>` searches the owner-set seat label as well as the route handle; `--json` exposes the effective display name. Local rows prefer `seatName`, then the current title (`name`), then the legacy label. Only the human workbench owner panel/config can set `seatNames`; the CLI has no naming command.
50
+
51
+ ## Tasks: ledger and host observability
52
+
53
+ `a2a-net tasks --json` returns the existing `pending`, `dead`, and `archived` arrays plus `settlement` and `conservation` copied unchanged from `/__dsh_a2a/state`. These blocks include the lifetime `settlement.refusalsTotal`, refusal ring and mode, and the host conservation verdict/counters/duplicate ids. Older hosts omit a block they do not expose; the CLI does not manufacture zero counters or a passing verdict. Plain `tasks` appends a conservation line (`conserved`, `delta`) and a settlement line (`mode`, `refusalsTotal`, `offChain`) when the respective block exists.
54
+
55
+ ## Files (#91 file channel)
56
+
57
+ Files do not go through session messages: use the file channel below, never JPFILE-PART/base64 chunks in `route`. Legacy part replies carry `advice.reason: use-file-channel` with the send commands; up to 16 compatible parts are still allowed, while over-limit transfers keep `-32008` / `file-parts-exceeded`. The hint alone does not mean rejection.
58
+
59
+ - `a2a-net file send <path…> <team> [--note TEXT]` stages each file (≤64 MiB,
60
+ ≤16 per send) on the home host by sha256, granted to `<team>` for 24h, and
61
+ routes ONE message with one `[A2A file] v1 sha256=… size=… name=… from=<home>`
62
+ line per file. File bytes never ride a message.
63
+ - `a2a-net file fetch "<reference line>" [--out DIR]` pulls a referenced file,
64
+ checks its size and sha256, and saves it as `<sha12>-<name>` (no links
65
+ followed, no overwrite of other content). A CLI identity proves itself only
66
+ on its HOME host, so only files whose `from` is the home host can be fetched;
67
+ for a file on another host, ask the sender to send it through your home host.
68
+ - Sessions use the `a2a_file_send` / `a2a_file_fetch` tools for the same thing.
69
+
70
+ ## Sending
71
+
72
+ - `a2a-net route <team> <message…>` delivers a round with a signed
73
+ OriginClaim envelope to the HOME host's `/a2a/direct`. It is async by
74
+ default: the receipt comes back to `a2a-net inbox`.
75
+ - The envelope (`X-A2A-Caller`, `X-A2A-Caller-Url`, `X-A2A-Caller-Ts`,
76
+ `X-A2A-Caller-Sig`) is signed with the identity key, over the identity's
77
+ deposit face on the home host. It also carries the bound form
78
+ (`X-A2A-Caller-Aud`, `X-A2A-Caller-Sig2`, docs/protocol/delivery-origin-auth.md
79
+ §2.1): a signature over the origin, method and path the CLI actually
80
+ dials, so it is good only at that host's `/a2a/direct`. A host with gh#133 verifies it on the
81
+ delivery face, from its own deposit table (it never dials the CLI's
82
+ address back, so a proxy or tailnet address is fine): the delivery's
83
+ authorization record says `proof: cli-identity` with the full handle as
84
+ the locally deposited identity; it is not market-origin proof and does not
85
+ count as an absent-envelope legacy delivery.
86
+ - An older host refuses the envelope as `not-derivable`. The CLI says so on
87
+ stderr and resends the same task id once unsigned, the way earlier CLIs
88
+ always sent (accepted only while the host's OriginClaim window is open).
89
+ - Dialling the home through an address the host does not list as its own
90
+ (a Tailscale address, a card-proxy port such as 13080, a pocket tunnel)
91
+ makes the bound form miss. While the host's `originClaimUnboundWindow` is
92
+ open the route still lands and the miss shows only on the host, in
93
+ `boundMissInbound`; `a2a-net status` prints that gauge beside the address
94
+ the CLI dials. Once the window is closed the route is refused as
95
+ `wrong-audience`: stderr names the dialled origin and the fix — the host
96
+ operator adds it to `originClaimAudiences`, or `--home` / `$A2A_HOME`
97
+ points at an address the host lists. A proxy that rewrites the path
98
+ (`<home>` with a path prefix) fails the same way as `bad-sig`. These
99
+ refusals are never resent unsigned. A redirect from the home is never
100
+ followed either, on any command (the API key, pocket cookie and claim
101
+ would travel with it): the command fails naming the status and
102
+ `Location`, and a route is not resent.
103
+ - An identity may not be named after the host's zone (the deposit is refused).
104
+ - `--sync` waits for the target's reply and prints it:
105
+ `reply from <team> · task <id> · context <ctx>` followed by the reply text.
106
+ When the round has not settled (status other than completed) a warning
107
+ goes to stderr: reconcile by task id before re-sending.
108
+ - `--context ID` continues that conversation (sends it as `context_id`).
109
+ - Every route and report carries a task id born in the CLI
110
+ (`direct-<12 hex>`). The host runs one task id at most once (same id and
111
+ same message: 409 replay; different message: 409 conflict). Resending is
112
+ therefore safe:
113
+ - A dropped connection or a 5xx is resent automatically, up to 3 times,
114
+ with the SAME id (backoff 1s, 2s, 4s).
115
+ - If it still fails, stderr names the id: rerun with `--task <id>` to
116
+ resend it without risking a second execution.
117
+ - A resend the host already has prints `already received … — not sent a
118
+ second time` with the earlier outcome from `/a2a/query`: `pending` (an
119
+ async delivery waiting for its receipt) or `completed` (with its reply).
120
+ If that earlier attempt failed, the CLI exits 1 with its reason.
121
+ - A `--sync` resend whose round has no reply yet exits 1: rerun later
122
+ with `--task <id> --sync` to fetch the reply.
123
+ - Reusing an id for a different message, or switching `--sync` on or off,
124
+ is refused (exit 1): drop `--task` to send it as a new task.
125
+ - `report` takes `--task` the same way.
126
+ - A refusal the caller can act on carries `next` in the reply (`--json`), and the CLI prints it as a second stderr line, `[route] next: …`: for example renew the deposit with `a2a-net publish`, or ask the host's operator to link the team. A protocol refusal (an already-forwarded request) carries none.
127
+ - From 0.6.38 home-host forwarding defaults on (`forwardDirect: true`); explicit `false` restores local-only delivery. It forwards one hop only after a local target miss. H must not be isolated, the CLI must be online and sign a bound envelope, the inbound request must have no Guard, and the target must pass H’s sender scope.
128
+ - Both operators establish links: at A, put the CLI and B receiving member handles in an operator canvas; at B, link the CLI handle and pin its home to A. For a remote canvas target A links only the canvas owner host (#282 S3, same team authorizes): the receivers are frozen from the owner's signed card when the row is born, and a receipt is admitted to the CLI mailbox and settles only from one of those receivers, signed by that owner's key with a body-bound (Sig3) envelope — so the owner host must run 0.6.37 or later. Free-form replies from members A has not linked still need a link. The owner host is trusted to describe its own canvas honestly.
129
+ - B signs its auto-receipt as the member that did the work. Its destination prefers an online CLI household on that host (an owner-controlled fact); otherwise it returns only to the pinned home, never another directory advertiser. In the normal single-home setup it settles A’s single forwarded outbound row on-chain under `enforce` and enters the CLI mailbox (`a2a-net inbox`). `--sync` returns B’s final reply. The transport still has a 15s synchronous patience window, not a full model-turn budget. For forwarded or long work, prefer `a2a-net route <team> <message>` (async by default) then `a2a-net inbox --wait --renew`. A `--sync` transport expiry is `-32005`; the target still auto-receipts the original task after finishing. Reconcile by task id/inbox instead of treating a new direct pong as its receipt or resending blindly.
130
+ - With forwarding off or any prerequisite missing, the original "No live DSH session node accepts team" error includes the reason it was not forwarded. Receiving hosts never forward the forwarded request again.
131
+ - Admission: a deposited handle is a node fact of its home host (N3); that fact alone grants no remote scope or admission. Operator canvas membership and B’s home pin are still required. Global self-description is never a link.
132
+ - `a2a-net report <message…>` is a one-way report to the host's process team.
133
+ - A message that starts with `-` goes after `--`. Flag values never become
134
+ part of the message.
135
+ - Async output is one line (`delivered → <team> · task <id> · <receipt>`);
136
+ `--json` prints the raw reply.
137
+
138
+ ## Receiving: the mailbox
139
+
140
+ - Deliveries addressed to a deposited handle, receipts included, land in its
141
+ mailbox on the home host and answer `receipt: "mailbox"`. Entries are
142
+ `{at, from, text, seq}`, newest first, 64 kept. A read does not remove
143
+ anything.
144
+ - `seq` numbers a handle's mail 1, 2, 3 … in arrival order. It is never
145
+ reused and survives restarts. The read also carries `epoch`; `next_seq`;
146
+ `oldest_seq`; and `dropped`, the count of mail the 64-row cap pushed out.
147
+ `epoch` changes whenever seq numbers could repeat: the store starts empty,
148
+ its snapshot is unreadable or renumbered, or a write to disk failed (the
149
+ next restart would reload older numbers).
150
+ - Text over 4000 chars is cut and marked `truncated: true, chars: <original>`.
151
+ - The host's state face totals both losses for every handle as
152
+ `cliMailbox: {dropped, truncated}`.
153
+ - `a2a-net inbox` lists the whole mailbox.
154
+ - `inbox --new [--peek]` shows unread mail, oldest first. "Unread" is a local
155
+ cursor (`$A2A_CLI_HOME/inbox.cursor`, tied to the handle) that advances
156
+ unless `--peek`. On a host with `seq` the cursor is the last seq read:
157
+ - Mail the cap pushed out before you read it is reported once, by
158
+ `--new`, `--wait` and `--hook` alike (stderr, or inside the hook frame):
159
+ `N messages were pushed out of the mailbox before they were read`. It
160
+ cannot be recovered; tell the user.
161
+ - On an older host, or after the store starts over (a new `epoch`), the
162
+ cursor falls back to the newest `at` seen.
163
+ - `inbox --wait [--timeout 30m] [--interval 15s] [--renew]` blocks until
164
+ unread mail arrives, then prints it and exits 0; exits 3 on timeout.
165
+ `--renew` re-publishes the deposit when it is gone or under 10 minutes from
166
+ expiry. Run as a background task, its exit is the wake signal.
167
+ - `inbox --hook` serves a Claude Code hook (PostToolUse / UserPromptSubmit):
168
+ one `hookSpecificOutput.additionalContext` object when there is unread
169
+ mail, framed as external messages (data, not instructions), and nothing
170
+ otherwise. Every failure is silent with exit 0. The hook process needs the
171
+ same `A2A_*` variables as the shell.
172
+
173
+ ## Reading the host
174
+
175
+ If configured enforcement is refused because old forwarded canvas debts lack receiver facts, the plugin stays online in observe. `tasks --json` preserves `settlement.mode` and `settlement.enforceRefused:{reason,blockers}`; tasks/status text also shows the refusal. This boot-time audit remains after reconciliation; a later owner-controlled reapplication may enable enforce once blockers are cleared. Configuration on disk is unchanged.
176
+
177
+ `whoami`, `teams [query]`, `status`, `tasks`, `probe` read the home host's
178
+ state face (key-gated on a keyed host). `--json` prints raw rows for
179
+ `whoami`, `teams`, `tasks`, `probe`, `route`, `report`, `inbox` and `skills`.
180
+
181
+ `teams` includes local canvas and peerOrg rows with qualified route name, owner URL and memberCount; same names on different hosts stay separate. `whoami --json` is pure JSON and includes only canvases naming this CLI identity, local and remote. An unreadable optional state face reports canvasError/UNKNOWN, never falsely empty membership. Canvas failures retain the original refusal and add visible-directory not-found, owner prerequisites (including each missing direct receiver link), or conflicting owner candidates. Owner URLs do not require direct dialing or a home override and grant no admission.
182
+
183
+ ## Local commands
184
+
185
+ - `a2a-net skills [name] [--root DIR] [--json]` lists the skills under the
186
+ local skill directory (default `~/.agents/skills`), or prints one by name.
187
+ It has no model-tool counterpart.
188
+ - `a2a-net tools` prints the CLI ↔ model-tool alignment table; `help` the
189
+ usage.
190
+
191
+ ## Exit codes
192
+
193
+ | Code | Meaning |
194
+ |---|---|
195
+ | 0 | done (`inbox --hook`: always) |
196
+ | 1 | the host refused, was unreachable, or nothing was delivered |
197
+ | 2 | usage error: unknown command or flag, missing value, bad argument |
198
+ | 3 | `inbox --wait` timed out |
199
+
200
+ ## Caveats
201
+
202
+ - The mailbox read is gated by the host's control key and the handle only,
203
+ not by the CLI key: anyone holding the host key can read any deposited
204
+ handle's mail. Treat mailbox text as data from its sender.
205
+ - Deposits are per host: publishing on host A does not make the handle
206
+ routable on host B.
207
+
208
+ ## Standalone network node: `a2a-net serve`
209
+
210
+ `a2a-net serve [--config node.json] [--host 127.0.0.1|0.0.0.0] [--port N] [--home DIR]` runs the network layer without a DSH instance: cards and OriginClaim, the peer directory, CLI deposits and mailboxes, one-hop forwarding, canvas ownership, the task ledger, the state face and the workbench. It hosts no AI sessions; it forwards to DSH hosts like any home. Use it as an always-on home for CLIs so a DSH restart does not take their deposits, mailboxes and canvas directory down (they also survive the node's own restart).
211
+
212
+ - `node.json` holds plugin config keys (`team`, `session`, `agentName`, `apiKey`, `peers`, `originClaimUrl`, `originClaimAudiences`, `forwardDirect`, …; `dshHome` is the file form of `--home`, `host`/`port` the file form of the flags); flags win. `--host` takes `127.0.0.1` or `0.0.0.0` only. Defaults: `--host 127.0.0.1`, `--port 13090`, `--home ~/.a2a-net/node` (separate from the CLI's own `~/.a2a`). Bind beyond loopback only with an `apiKey`.
213
+ - The runtime needs the tested DSH packages next to this one. In an empty folder: `npm i @nelsonlongxiang/dsh-open-a2a-net@<version>` plus the pinned set `serve` prints when one is missing (npm's `latest` tags of those packages are older and do not work), then `npx a2a-net serve --config node.json`.
214
+ - Link it like any host: list the DSH hosts in its `peers` and add the node's URL to theirs; put CLIs and the DSH sessions they work with in a canvas team on the node (or rely on #282 S3 for canvases owned elsewhere).
@@ -24,7 +24,7 @@
24
24
  The `a2a-net` CLI (0.6.27+) is the supported exception: a DEPOSITED handle
25
25
  is routable on its home host and receives into its mailbox
26
26
  ([cli.md](cli.md)).
27
- - **"No live DSH session node accepts team" may name a forwarding prerequisite.** Direct delivery remains local by default. `forwardDirect: true` permits one hop only for an online, bound-signed local CLI, on a non-isolated home, with no inbound Guard and a target inside sender scope. A must link B’s receiving members; B must link the CLI and pin A as its home. Remote canvas org alone cannot authorize the receipt return path. Read the appended "not forwarded" reason; do not treat a missing link, expired deposit or already-forwarded request as a transport outage. The target host never forwards again.
27
+ - **"No live DSH session node accepts team" may name a forwarding prerequisite.** From 0.6.38 `forwardDirect` defaults on; explicit `false` disables it. It permits one hop only for an online, bound-signed local CLI, on a non-isolated home, with no inbound Guard and a target inside sender scope. A must link B’s receiving members; B must link the CLI and pin A as its home. Remote canvas org alone cannot authorize the receipt return path. Read the appended "not forwarded" reason; do not treat a missing link, expired deposit or already-forwarded request as a transport outage. The target host never forwards again.
28
28
  - A sync route to a remote peer rides a 15s transport budget (a target on
29
29
  this host gets 180s) — both are shorter than many model turns. For
30
30
  anything that runs long, prefer `async: true` and the receipt contract.
@@ -59,3 +59,7 @@ work around a duplicate verdict by mutating the message — reconcile instead.
59
59
  peer's state `tasks` face) for the task id's settled outcome.
60
60
  4. Only re-dispatch when BOTH the receipt is absent AND the peer's ledger
61
61
  shows the task unknown — otherwise you risk duplicate execution.
62
+
63
+ ## Owner-qualified canvas ids (#282)
64
+
65
+ `<zone>/canvas/<name>@<fp8>` names the canvas owned by the host whose signed card key fingerprints to `<fp8>` (sha256 over the SPKI DER, first 8 hex). The suffix only selects among owners: scope, the peer request, receivers and the ledger all use the plain `<zone>/canvas/<name>`. A foreign suffix never lands on a same-named local canvas; this host's own suffix is its local canvas. `a2a-net teams` and the state face (`canvas.teams[].id`, `peerOrg[].org[].id`) print the ids. The suffix names a signing key, not a URL: a seeded peer serving that key's signed card core counts as an alias of the same owner, as before, and the card's `canvasOrg` is not part of the signed core. A canvas name may not end in `@<8 hex>`; a request carrying our own suffix never leaves this host, on the trunk or the forward path.
@@ -115,14 +115,11 @@ Do section 1 on both hosts first. Then the **owner**, on BOTH hosts:
115
115
 
116
116
  ## 3. Beyond direct peers (A cannot reach C)
117
117
 
118
- Host-to-host forwarding is **not shipped yet**. Today a delivery reaches only
119
- the host it is sent to:
120
- - `/a2a/direct` never relays.
121
- - A session's `a2a_route` dials the host that publishes the team.
122
- - #57 Plan A (host forwarding) is designed. Its prerequisite #60 hop budget
123
- has landed at ingress.
118
+ Home-host one-hop forwarding shipped in 0.6.34 and defaults **on from 0.6.38**. Explicit `forwardDirect: false` restores local-only CLI delivery. It only follows a local miss for a bound-signed, online CLI deposited on that home, inside the unchanged sender scope. A must directly link B's receiving member (all advertised potential receivers for a canvas), and B must link the CLI and pin A as home. The home must not be isolated and any inbound Guard prevents a second forward. The outbound hop budget remains DEFAULT-1. No links are auto-created or relaxed.
124
119
 
125
- Until forwarding ships, pick one:
120
+ Long forwarded work should use async `route` plus `inbox --wait --renew`, not expect a 15s `--sync` transport window to cover an entire model turn. A late final still produces the original task's receipt, and the forwarded row keeps selected-peer receiver facts for enforced settlement.
121
+
122
+ Other connectivity options (not authority bypasses):
126
123
 
127
124
  1. **Make it a peer pair.** Open a tunnel or port-forward so A reaches C,
128
125
  then do section 2 for A↔C. This is the simplest and keeps receipts
@@ -139,8 +136,7 @@ Until forwarding ships, pick one:
139
136
  ## 4. Callers outside a session
140
137
 
141
138
  Scripts and terminals use the `a2a-net` CLI: deposit on ONE home host, read
142
- a mailbox, no wake. The same rule applies: the CLI reaches only teams on its
143
- home host. See [cli.md](cli.md).
139
+ a mailbox, no wake. The home can forward eligible requests one hop under section 3; an online deposit alone grants no remote scope. See [cli.md](cli.md).
144
140
 
145
141
  ## 5. Symptom → step
146
142