@nelsonlongxiang/dsh-open-a2a-net 0.6.43 → 0.8.1-preview.1

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 (166) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +109 -94
  3. package/README_zh.md +64 -66
  4. package/THIRD_PARTY_NOTICES.md +12 -0
  5. package/assets/nexusDist/assets/index-BuidhKVr.css +1 -0
  6. package/assets/nexusDist/assets/index-YtYrkDJi.js +9 -0
  7. package/assets/nexusDist/index.html +2 -2
  8. package/cli/a2a.mjs +367 -22
  9. package/cordis.patch.yml +167 -0
  10. package/docs/licenses/dsh-superagent-MIT.txt +21 -0
  11. package/lib/a2a-client.js +21 -5
  12. package/lib/admitted-bookkeeping.js +7 -0
  13. package/lib/canvas-discovery.js +11 -1
  14. package/lib/canvas-member.js +43 -0
  15. package/lib/canvas-store.js +70 -4
  16. package/lib/card.js +2 -0
  17. package/lib/cli-deposit.js +75 -4
  18. package/lib/config.js +1 -2
  19. package/lib/direct-forward.js +31 -7
  20. package/lib/faces/admin.js +81 -55
  21. package/lib/faces/files.js +2 -1
  22. package/lib/faces/wire.js +605 -468
  23. package/lib/followup-admission.js +47 -0
  24. package/lib/followup-delivery.js +106 -0
  25. package/lib/idempotency-store.js +8 -0
  26. package/lib/inbound-verification.js +23 -0
  27. package/lib/index.js +1069 -518
  28. package/lib/node-self.js +1 -1
  29. package/lib/nudge-observability.js +34 -0
  30. package/lib/origin-claim.js +36 -9
  31. package/lib/outbound-scope.js +14 -2
  32. package/lib/peer-directory.js +17 -5
  33. package/lib/peer-store.js +31 -17
  34. package/lib/queue-guard.js +5 -4
  35. package/lib/receipt-ladder.js +1 -1
  36. package/lib/receipt-retry.js +58 -0
  37. package/lib/receipt.js +24 -2
  38. package/lib/reconciliation-reminders.js +148 -0
  39. package/lib/reconciliation-state.js +27 -0
  40. package/lib/session-transcript.js +46 -18
  41. package/lib/skill/SKILL.md +6 -0
  42. package/lib/skill/references/cli.md +197 -22
  43. package/lib/skill/references/collaboration.md +3 -2
  44. package/lib/skill/references/routing.md +31 -1
  45. package/lib/skill/references/superagent.md +106 -0
  46. package/lib/superagent/a2a-watch.js +587 -0
  47. package/lib/superagent/contract.js +1 -0
  48. package/lib/superagent/host/a2a-source.js +251 -0
  49. package/lib/superagent/host/executor.js +520 -0
  50. package/lib/superagent/host/index.js +1107 -0
  51. package/lib/superagent/host/policy.js +308 -0
  52. package/lib/superagent/host/pr-follow.js +158 -0
  53. package/lib/superagent/host/review-requests.js +77 -0
  54. package/lib/superagent/host/role-tools.js +137 -0
  55. package/lib/superagent/host/store.js +136 -0
  56. package/lib/superagent/host/tools.js +297 -0
  57. package/lib/superagent/source-types.js +22 -0
  58. package/lib/superagent/upstream/dsh-superagent/src/core/assignments.js +160 -0
  59. package/lib/superagent/upstream/dsh-superagent/src/core/engine.js +244 -0
  60. package/lib/superagent/upstream/dsh-superagent/src/core/errors.js +63 -0
  61. package/lib/superagent/upstream/dsh-superagent/src/core/evals.js +381 -0
  62. package/lib/superagent/upstream/dsh-superagent/src/core/ids.js +5 -0
  63. package/lib/superagent/upstream/dsh-superagent/src/core/index.js +9 -0
  64. package/lib/superagent/upstream/dsh-superagent/src/core/intake.js +68 -0
  65. package/lib/superagent/upstream/dsh-superagent/src/core/memory.js +51 -0
  66. package/lib/superagent/upstream/dsh-superagent/src/core/model.js +79 -0
  67. package/lib/superagent/upstream/dsh-superagent/src/core/part.js +71 -0
  68. package/lib/superagent/upstream/dsh-superagent/src/core/ports.js +1 -0
  69. package/lib/superagent/upstream/dsh-superagent/src/core/reasons.js +35 -0
  70. package/lib/superagent/upstream/dsh-superagent/src/core/replies.js +94 -0
  71. package/lib/superagent/upstream/dsh-superagent/src/core/stage.js +27 -0
  72. package/lib/superagent/upstream/dsh-superagent/src/core/tickets.js +487 -0
  73. package/lib/superagent/upstream/snapshot-8c32a9d.js +62 -0
  74. package/lib/task-auth.js +24 -3
  75. package/lib/task-delivery.js +23 -0
  76. package/lib/task-ledger-persistence.js +51 -0
  77. package/lib/task-ledger-rows.js +9 -0
  78. package/lib/task-ledger.js +287 -22
  79. package/lib/tools/info.js +47 -11
  80. package/lib/types/a2a-client.d.ts +1 -8
  81. package/lib/types/admitted-bookkeeping.d.ts +5 -0
  82. package/lib/types/canvas-member.d.ts +10 -0
  83. package/lib/types/canvas-store.d.ts +8 -0
  84. package/lib/types/cli-deposit.d.ts +65 -1
  85. package/lib/types/config.d.ts +6 -20
  86. package/lib/types/direct-forward.d.ts +14 -0
  87. package/lib/types/faces/admin.d.ts +24 -0
  88. package/lib/types/faces/files.d.ts +1 -1
  89. package/lib/types/faces/wire.d.ts +39 -69
  90. package/lib/types/followup-admission.d.ts +13 -0
  91. package/lib/types/followup-delivery.d.ts +25 -0
  92. package/lib/types/idempotency-store.d.ts +2 -0
  93. package/lib/types/inbound-verification.d.ts +14 -0
  94. package/lib/types/index.d.ts +5 -2
  95. package/lib/types/nudge-observability.d.ts +17 -0
  96. package/lib/types/origin-claim.d.ts +7 -1
  97. package/lib/types/outbound-scope.d.ts +2 -0
  98. package/lib/types/peer-store.d.ts +4 -0
  99. package/lib/types/queue-guard.d.ts +11 -2
  100. package/lib/types/receipt-ladder.d.ts +2 -0
  101. package/lib/types/receipt-retry.d.ts +22 -0
  102. package/lib/types/receipt.d.ts +21 -1
  103. package/lib/types/reconciliation-reminders.d.ts +41 -0
  104. package/lib/types/reconciliation-state.d.ts +32 -0
  105. package/lib/types/session-transcript.d.ts +6 -1
  106. package/lib/types/superagent/a2a-watch.d.ts +227 -0
  107. package/lib/types/superagent/contract.d.ts +36 -0
  108. package/lib/types/superagent/host/a2a-source.d.ts +90 -0
  109. package/lib/types/superagent/host/executor.d.ts +132 -0
  110. package/lib/types/superagent/host/index.d.ts +340 -0
  111. package/lib/types/superagent/host/policy.d.ts +128 -0
  112. package/lib/types/superagent/host/pr-follow.d.ts +51 -0
  113. package/lib/types/superagent/host/review-requests.d.ts +36 -0
  114. package/lib/types/superagent/host/role-tools.d.ts +85 -0
  115. package/lib/types/superagent/host/store.d.ts +61 -0
  116. package/lib/types/superagent/host/tools.d.ts +32 -0
  117. package/lib/types/superagent/source-types.d.ts +218 -0
  118. package/lib/types/superagent/upstream/dsh-superagent/src/core/assignments.d.ts +53 -0
  119. package/lib/types/superagent/upstream/dsh-superagent/src/core/engine.d.ts +191 -0
  120. package/lib/types/superagent/upstream/dsh-superagent/src/core/errors.d.ts +58 -0
  121. package/lib/types/superagent/upstream/dsh-superagent/src/core/evals.d.ts +99 -0
  122. package/lib/types/superagent/upstream/dsh-superagent/src/core/ids.d.ts +1 -0
  123. package/lib/types/superagent/upstream/dsh-superagent/src/core/index.d.ts +9 -0
  124. package/lib/types/superagent/upstream/dsh-superagent/src/core/intake.d.ts +15 -0
  125. package/lib/types/superagent/upstream/dsh-superagent/src/core/memory.d.ts +40 -0
  126. package/lib/types/superagent/upstream/dsh-superagent/src/core/model.d.ts +432 -0
  127. package/lib/types/superagent/upstream/dsh-superagent/src/core/part.d.ts +49 -0
  128. package/lib/types/superagent/upstream/dsh-superagent/src/core/ports.d.ts +217 -0
  129. package/lib/types/superagent/upstream/dsh-superagent/src/core/reasons.d.ts +36 -0
  130. package/lib/types/superagent/upstream/dsh-superagent/src/core/replies.d.ts +55 -0
  131. package/lib/types/superagent/upstream/dsh-superagent/src/core/stage.d.ts +18 -0
  132. package/lib/types/superagent/upstream/dsh-superagent/src/core/tickets.d.ts +151 -0
  133. package/lib/types/superagent/upstream/snapshot-8c32a9d.d.ts +317 -0
  134. package/lib/types/task-auth.d.ts +16 -0
  135. package/lib/types/task-delivery.d.ts +17 -0
  136. package/lib/types/task-ledger-persistence.d.ts +3 -0
  137. package/lib/types/task-ledger-rows.d.ts +39 -5
  138. package/lib/types/task-ledger.d.ts +57 -1
  139. package/lib/types/tools/info.d.ts +3 -0
  140. package/lib/types/types.d.ts +10 -4
  141. package/nexus-stage/scripts/mockhost.py +2 -2
  142. package/nexus-stage/src/federation.ts +1 -0
  143. package/nexus-stage/src/main.ts +37 -2
  144. package/nexus-stage/src/member-provenance-picker.ts +13 -0
  145. package/nexus-stage/src/planning-card-text.ts +1 -1
  146. package/nexus-stage/src/planning-member-handles.ts +4 -1
  147. package/nexus-stage/src/planning-team-roles.ts +80 -0
  148. package/nexus-stage/src/planning-types.ts +2 -0
  149. package/nexus-stage/src/planning-view.ts +4 -3
  150. package/nexus-stage/src/planning.css +29 -9
  151. package/nexus-stage/src/wb-superagent.ts +518 -0
  152. package/nexus-stage/src/workbench.css +32 -0
  153. package/nexus-stage/src/workbench.ts +53 -9
  154. package/nexus-stage/src/world.ts +2 -1
  155. package/package.json +10 -3
  156. package/assets/nexusDist/assets/index-C7HagaGy.css +0 -1
  157. package/assets/nexusDist/assets/index-CaA76j0F.js +0 -9
  158. package/assets/stageDist/assets/index-Bj7S2pA1.css +0 -1
  159. package/assets/stageDist/assets/index-C-28k_o5.js +0 -974
  160. package/assets/stageDist/assets/interiors-CQGBjffT.png +0 -0
  161. package/assets/stageDist/assets/office-tileset-CHQT-uYL.png +0 -0
  162. package/assets/stageDist/index.html +0 -13
  163. package/lib/legacy/teams-bridge-inbound.js +0 -156
  164. package/lib/teams-bridge.js +0 -18
  165. package/lib/types/legacy/teams-bridge-inbound.d.ts +0 -94
  166. package/lib/types/teams-bridge.d.ts +0 -112
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 NelsonLongxiang and the dsh-open-a2a-net contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -25,48 +25,66 @@ dsh plugin --profile web add @nelsonlongxiang/dsh-open-a2a-net
25
25
 
26
26
  Restart `dsh web`, then open the sidebar network panel (footer action).
27
27
 
28
- (As a package specifier, a local path, or a Git URL; the package runs `pnpm build` in `prepare`, so a source
29
- install builds on install. Note: a standalone Git install currently fails to build outside a DSH profile —
30
- the official `@deepseek-ai/*` npm packages lag the harness source this plugin compiles against, so install
31
- from the registry or inside a profile whose harness provides current `@deepseek-ai` peers.) The plugin composes
32
- beside the stock
33
- webserver row: it registers its card, state, and control routes on the shared listener and its model tools on the
34
- shared tool runtime.
28
+ (As a package name, a local path, or a Git URL; the argument is handed to pnpm unchanged. The package's `prepare`
29
+ script is `pnpm build`, but only a Git URL install triggers it, and pnpm 11 blocks a Git package's build script:
30
+ `dsh plugin` reports an error that prints the exact key — add it under `allowBuilds` in the profile's
31
+ `pnpm-workspace.yaml` and run it again. A package installed from the registry already ships its build output and
32
+ does not build; a local-path install does not build either, so run `pnpm build` in that directory first. Building
33
+ needs pnpm >=11.24 and <12 (`engines` in `package.json`). A standalone Git install outside a DSH profile can now
34
+ build (the `@deepseek-ai/*` peers come from npm; verified on 0.7.0), so it no longer has to be limited to a registry
35
+ or profile install.) The plugin composes beside the stock webserver row: it registers the state and control routes
36
+ (`/__dsh_a2a/*`, while `sessionNodes` is on — the default) on the shared listener and its model tools on the shared
37
+ tool runtime; the card route `/.well-known/agent-card.json` is registered only when `announce: true` (off by default).
35
38
 
36
39
  ## What you get
37
40
 
38
- - **Model tools** (see the table below) — `a2a_teams` lists the teams this node can see (its own rows
41
+ - **Model tools** (twelve, see the table below) — `a2a_teams` lists the teams this node can see (its own rows
39
42
  plus peer teams inside the joined-team scope); `a2a_route`
40
43
  sends one message to a team, reuses `context_id` to continue a remote conversation, and fails over across
41
44
  candidates when a peer is unreachable. `a2a_tasks` reconciles the receipt contract across three tiers: every route that leaves a
42
- task owed a reply (async delivery, a released wait) stays in the owed book as pending until its
45
+ task owed a reply (async delivery, or a synchronous wait released at its 180s deadline; a synchronous wait the caller aborts is not booked,
46
+ and a delivery with no routable callback address is only listed as a visible row with no receipt debt) stays in the owed book as pending until its
43
47
  `[A2A receipt] task <task_id>` message correlates — overdue rows auto-dead-letter past the stale TTL (a revived target can still
44
48
  settle with a late receipt), every settlement lands in the bounded archive instead of being silently evicted, and each row keeps
45
49
  its follow-up `context_id`, persisted across
46
50
  restarts. `a2a_probe` measures the tracked fleet's reachability and round-trip latency (one verified-card
47
51
  fetch per peer, optionally narrowed to a single url) for pre-dispatch health checks, naming each miss's
48
52
  stage — `unreachable` (transport/HTTP) vs. `rejected` (distrusted card).
49
- - **Sidebar control** — a footer action in the web sidebar listing this host's sessions as joinable network
50
- nodes: title, recent-activity excerpt, and team per row, join/leave in place, and a wake action for cold rows
51
- (persisted join intent whose session is not loaded yet — opening the session remounts the node). Session teams
52
- are `<team>/<id8>`; web sessions use their id's first 8 chars, imported sessions (`import-<uuid>`) keep the
53
- `import-` prefix plus the uuid's first 8 hex chars so imported ids cannot collapse into a handful of teams.
54
- The panel's activity section also lists owed receipts — async tasks delivered but not yet correlated by their
55
- `[A2A receipt]` message — beside the in-flight waits, so a cross-turn wait is visible without asking the model.
56
- - **Archive leaves the network** — archiving a session (workspace registry state) prunes its join
57
- intent and unmounts its node: at boot settlement before any wake, on every state read (a mid-session
58
- archive disappears within one panel poll), and as a route-time guard that never wakes an archived
59
- target.
60
- - **Honest waits** — a synchronous route whose target never answers releases the caller at a 180s
61
- reply-wait deadline with the delivered shape and the receipt contract (the target answers on its own
62
- cadence); `async: true` skips the wait entirely. The network panel dims an in-flight row past 120s as
63
- a stale wait instead of implying live progress, and its title carries the package version badge.
64
- - **Announce** — `announce: true` publishes this node's card (team, capabilities, referrals, joined session
65
- teams) so peers find it without any directory.
53
+ - **Sidebar panel** — the footer entry in the web sidebar, slimmed down to this host's recent network activity
54
+ (`docs/design/panel-activity-feed.md`): in-flight routes, owed receipts (only settleable debt is listed; the stale conversational tail is
55
+ only counted) and the routing activity ring side by side, so a cross-turn wait is visible without asking the model; an activity row for a
56
+ same-host session jumps to that session inside the GUI; an unread badge for inbound activity, the package version badge in the title, and an entry
57
+ to the workbench. Joining/leaving the network, canvas teams and member arrangement are no longer done in the sidebar — they moved to the
58
+ workbench planning board below. Session teams are `<team>/<id8>`; web sessions use their id's first 8 chars (after dropping the `session-`
59
+ prefix), imported sessions (`import-<uuid>`) keep the `import-` prefix plus the uuid's first 8 hex chars so imported ids cannot collapse into a
60
+ handful of teams.
61
+ - **Archive leaves the network** — archiving a session (workspace registry state) takes it off the node network: its node is unmounted,
62
+ its roster declarations withdrawn, it is hidden from listings and the card, and it is never woken — at boot settlement before any wake, on every
63
+ state read (a mid-session archive disappears within one state poll), and as a route-time guard. **The join intent is kept** in `joined.json`
64
+ (wiping it on a transient archived reading once left cold sessions that could never wake again); the prune is recorded as `archive-prune` in the
65
+ state face's `registry.teamAudit`.
66
+ - **Honest waits** — a synchronous route whose target never answers: for a same-host session target the caller is released at a 180s
67
+ reply-wait deadline with the delivered shape (`TASK_STATE_DELIVERED`) and the receipt contract (the target answers on its own cadence); a remote
68
+ peer reached over the direct route is bound by a 15s transport budget, then the host tries to recover the peer's settled outcome through the task
69
+ query (upgrading to the real result if it finished) and otherwise returns `-32005` "delivered, not settled" — reconcile by task id, do not
70
+ re-dispatch blindly. `async: true` skips the wait entirely. The network panel dims an in-flight row past 120s as a stale wait instead of
71
+ implying live progress.
72
+ - **A2A workbench** — `/__dsh_a2a_nexus/`, a full-page DOM workbench (it replaced the Three.js 3D view in 0.6.0; 0.6.2 merged the planning board
73
+ into it: the board is the main stage and Directory / Hosts / Ledger / Activity / Plan ride a collapsible sidebar, plus a Team page when the host
74
+ runs a superagent team; responsive — a wider sidebar from 1440px, a drawer sidebar and bottom-sheet detail up to 1023px, a wrapping header up to
75
+ 680px; the panels fall back to the main area when the canvas face goes down). Directory: local sessions (live / cold), canvas teams, remote nodes,
76
+ local logs. Hosts: tracked peers' health and link badges. Ledger: pending / dead letters with in-place clearing. Activity ring. Plan: a derived
77
+ action list plus nudge and dispatch drafting (both produce copyable text). The spatial planning board: build canvas teams, arrange members,
78
+ join/leave the network, with the layout persisted; `?mode=plan` is only an old-link compatibility form — with the canvas face up the board is
79
+ already the default.
80
+ - **Announce** — `announce: true` (default `false`; needs a web server in the composition) publishes this node's signed card so peers find it
81
+ without any directory. Referrals (`peers`) go only to a signature-verified caller that is a configured seed or already linked; joined session
82
+ teams (`sessionTeams`), rosters and the canvas org go only to a linked caller; every other caller gets the minimal discovery card (name, team,
83
+ capabilities, records, signature). Unjoined session nodes are not published.
66
84
 
67
85
  ## Model tools at a glance
68
86
 
69
- Ten tools. With `teamScopeInfo` on (the default), `a2a_teams`, `a2a_status` and `a2a_probe` answer from
87
+ Twelve tools. With `teamScopeInfo` on (the default), `a2a_teams`, `a2a_status` and `a2a_probe` answer from
70
88
  this host's joined-team scope: `[this host]` rows plus peers serving a team this host's nodes declared.
71
89
  `a2a_tasks` (this host's own dispatch ledger) stays unscoped.
72
90
 
@@ -77,13 +95,13 @@ this host's joined-team scope: `[this host]` rows plus peers serving a team this
77
95
  | `a2a_route` | Send a message to a team. | `context_id` continues a conversation; `async: true` delivers without waiting; fails over across candidates. |
78
96
  | `a2a_tasks` | Which async tasks still owe a receipt? | Three tiers: owed rows keep the follow-up `context_id`; past-TTL rows auto-dead-letter (still settleable); outcomes land in the bounded archive; persisted across restarts. |
79
97
  | `a2a_probe` | Is the fleet healthy? | One verified-card fetch per in-scope peer (or one given url); `N reachable, M down`; misses classified `unreachable` vs `rejected`. |
80
- | `a2a_team_join` | Declare one joined node a member of a team. | Allowlist-gated: only an **exact** name in `teamJoinAllowlist` authorizes a join (empty list denies every join). A wildcard/prefix entry is shown by `a2a_whoami` but authorizes nothing (`[PATTERN_NO_MINT]`); canvas teams are assigned in the workbench, never here (`[CANVAS_TEAM]`). Attempts land in the state face's `registry.teamAudit`. |
81
- | `a2a_team_leave` | Retract a team membership. | Always allowed — leaving only shrinks exposure. |
82
- | `a2a_exposure` | How visible is this session? | Sets the session's own tier: `linked` (the default floor) or `fleet` (a voluntary opt-up); a group's visibility follows its strictest member. Requires a joined session. |
83
- | `a2a_report` | Tell this host's supervision something while off the network. | The one ingress exempt from the join gate: one-way, lands in the state face's reports ring, grants no presence or routing. One report per 30s per session, ≤4000 chars, identical text 3× in 10 min is refused as a loop. |
98
+ | `a2a_team_join` | Declare one joined node a member of a team. | Allowlist-gated: only an **exact** name in `teamJoinAllowlist` authorizes a join (empty list denies every join). A wildcard/prefix entry is shown by `a2a_whoami` but authorizes nothing (`[PATTERN_NO_MINT]`); canvas teams are assigned in the workbench, never here (`[CANVAS_TEAM]`). Attempts land in the state face's `registry.teamAudit`. A model tool may only declare membership for the calling session's own node (else `[NOT_SELF]`), and that node must already be joined (else `[NOT_JOINED]`). |
99
+ | `a2a_team_leave` | Retract a team membership. | Not subject to the allowlist — leaving only shrinks exposure; it still can only retract a membership the calling session's own node declared (`[NOT_SELF]`, `[NOT_JOINED]`; nothing to retract if it never declared one). |
100
+ | `a2a_exposure` | How visible is this session? | Sets the session's own tier: `linked` (the default floor) or `fleet` (a voluntary opt-up), changeable at any time; requires a joined session. **Today the tier is only persisted (`a2a/exposure.json`) and shown in the `exposure` field of the state face's session rows; it does not yet change what the card or the directory exposes** — "a group's visibility follows its strictest member" is a written rule with neither a group-level API nor an enforcement point in v1 (owner ruling 2026-11-08, #449: documented as is). |
101
+ | `a2a_report` | Tell this host's supervision something while off the network. | The one write-ingress to this host that works while unjoined (the read-only `a2a_whoami` is exempt from the join gate too): one-way, lands in the state face's reports ring (newest 64 kept), grants no presence or routing. One report per 30s per session, ≤4000 chars; once the same session has reported the same text 3 times within 10 min, the 4th is refused as a loop. |
84
102
  | `a2a_status` | What is this node doing right now? | In-scope tracked peers with quality scores, in-flight routes, recent routing activity, and the idempotency window aggregate (occupancy + claim/replay/conflict counters). |
85
- | `a2a_file_send` | Hand files to a team without putting them in a message. | Stages each file (≤16 per call, ≤64 MiB each) on this host for that team and routes ONE message of `[A2A file]` reference lines; the bytes never ride the message (#91). |
86
- | `a2a_file_fetch` | Pull a file someone referenced. | Pulls one `[A2A file]` reference from the host that staged it, signed as this session; checks size and sha256 before saving it under `<cwd>/a2a-inbox/`. A grant names one session (or a whole host, or a canvas team's members); the fetch always marks the session as having read peer content. |
103
+ | `a2a_file_send` | Hand files to a team without putting them in a message. | Stages each file (≤16 per call, ≤64 MiB each) on this host, granted to that team for 24 hours, and routes ONE message of `[A2A file]` reference lines; the bytes never ride the message (#91). Needs this host to have published a card URL (`announce`), otherwise it refuses to send. |
104
+ | `a2a_file_fetch` | Pull a file someone referenced. | Pulls one `[A2A file]` reference from the host that staged it, carrying this session as `caller_session` and this host node's OriginClaim envelope (a reference naming this host reads the local store directly); checks size and sha256 before saving it under `<cwd>/a2a-inbox/`. A grant names one session (or a whole host, or a canvas team's members); the fetch always marks the session as having read peer content. |
87
105
 
88
106
  ## a2a-net CLI (callers outside a session)
89
107
 
@@ -100,7 +118,7 @@ a2a-net route dsh/1a2b3c4d "status?" # async by default; --s
100
118
  a2a-net inbox --new # unread mail; --wait blocks, --hook feeds a Claude Code hook
101
119
  ```
102
120
 
103
- From 0.6.38, `forwardDirect` defaults to `true`: after a local miss the home can forward an authenticated online CLI one hop to a linked peer target. Both operators must link: A links the receiving B member; B links the CLI handle and explicitly pins A as its home. For canvas targets A links only the owner host (#282 S3): receipts are admitted and settle only from receivers frozen from the owner's signed card, on that owner's key. With scope and settlement enforcement on, the member-host auto-receipt settles A’s forwarded row and lands in the CLI inbox; `--sync` returns B’s reply. Commands, flags, environment
121
+ From 0.6.38, `forwardDirect` defaults to `true`: after a local miss the home can forward an authenticated online CLI one hop to a linked peer target. Both operators must link: A links the receiving B member; B links the CLI handle and explicitly pins A as its home. For canvas targets A links only the owner host (#282 S3, same team authorizes; the owner host must run 0.6.37 or later): receipts are admitted and settle only from receivers frozen from the owner's signed card, on that owner's key. With scope and settlement enforcement on, the member-host auto-receipt settles A’s forwarded row and lands in the CLI inbox; `--sync` returns B’s reply, but on the forwarded path a synchronous wait has only a ~15s transport window and times out with `-32005` (delivered, not settled): the target still auto-receipts when it finishes, so reconcile by task id / `inbox` and do not resend blindly; for long work use the default async `route`, then `inbox --wait --renew`. Commands, flags, environment
104
122
  variables and exit codes: [`src/skill/references/cli.md`](src/skill/references/cli.md).
105
123
 
106
124
 
@@ -108,34 +126,21 @@ variables and exit codes: [`src/skill/references/cli.md`](src/skill/references/c
108
126
 
109
127
  Deployment guide (install, service files, linking DSH hosts, public-internet caveats; Chinese): [docs/guides/standalone-node.md](docs/guides/standalone-node.md).
110
128
 
111
- `a2a-net serve --config node.json` runs the network layer in its own Node process: cards and signatures, the peer directory, CLI deposits and mailboxes, one-hop forwarding, canvas ownership, the ledger, the state face and the workbench. It hosts no AI sessions; messages for a session are forwarded to a DSH host as usual. Run it on a stable machine as the CLIs' home so a DSH restart does not take their deposits, mailboxes and canvas directory down (they also survive the node's own restart). Install and config: the `a2a-net serve` section of [`src/skill/references/cli.md`](src/skill/references/cli.md).
129
+ `a2a-net serve --config node.json` runs the network layer in its own Node process: cards and signatures, the peer directory, CLI deposits and mailboxes, one-hop forwarding, canvas ownership, the ledger, the state face and the workbench. It hosts no AI sessions; a CLI's message for a session is handed to a linked DSH host by the one-hop forwarding rule above. "No DSH instance" means no running DSH host is needed; the runtime still needs the `@deepseek-ai/*` npm packages (when they are missing, `serve` prints the pinned set to install). Run it on a stable machine as the CLIs' home so a DSH restart does not take their deposits, mailboxes and canvas directory down (they also survive the node's own restart). Install and config: the `a2a-net serve` section of [`src/skill/references/cli.md`](src/skill/references/cli.md).
112
130
 
113
131
  ## Canvas teams (arbitrary grouping, v0.5.25)
114
132
 
115
133
  Peer/member badges are host-specific display facts: bilateral URL seeds or an exact, nonlocal canvas-member handle uniquely advertised by that host card. Shared zone names (such as `dsh`) never establish a badge link. A minimal card without session rows remains unconfirmed/stranger unless bilateral seeds establish the host; this conservative display rule does not change caller admission or CLI home validation.
116
134
 
117
- A canvas team is a user-composed, named, multi-member routing group: **one atomic session node may sit in many teams**, and a route to `<team>/canvas/<name>` resolves the **first live member** (member order is the routing priority) or wakes the **first cold joined member** (wake-on-route). The `canvas/` path segment can never collide with a node alias (`<team>/<id8>`), so both namespaces coexist untouched.
135
+ A canvas team is a user-composed, named, multi-member routing group: **one atomic session node may sit in many teams**, and a route to `<team>/canvas/<name>` resolves the **first live member** (member order is the routing priority) or wakes the **first cold joined member** (wake-on-route); when no local member is usable it falls through to the remote members in order (the directory resolves their handles; remote nodes are never woken from here). The dispatching session itself never occupies the two local tiers (the self-route rule, #41). The `canvas/` path segment can never collide with a node alias (`<team>/<id8>`), so both namespaces coexist untouched.
118
136
 
119
- - Storage: `<dsh-home>/a2a/canvas.json` (ordered team entries; caps: 64 teams × 32 members)
120
- - Control API: `POST /__dsh_a2a/canvas` with `action: create | remove | add-member | remove-member`; `cli-home-options` is a read-only owner action for an existing CLI member.
137
+ - Storage: `<dsh-home>/a2a/canvas.json` (ordered team entries; caps: 64 teams × 32 members; the file also carries a version, member owner anchors, suspension records and CLI home pins)
138
+ - Control API: `POST /__dsh_a2a/canvas` with `action: create | remove | add-member | remove-member`; `add-member` creates a team that does not exist yet. There are also owner actions for member provenance and suspension — `confirm-member`, `restore-suspended`, `delete-suspended` (#334/#335) — and `cli-home-options`, a read-only lookup for an existing CLI member. Team names are ≤40 characters, contain no `/`, are not all digits, do not end in `@<8 lowercase hex>`, and may not be roster-reserved (a `roster:` prefix, or equal to an allowlist/roster team name); a team holding suspended members cannot be `remove`d.
121
139
  - For a CLI member marked “home 未确定”, open its canvas team inspector and choose “指定 home”. The host freshly reads cards, lists only linked advertisers, and reports unreadable-card counts. No home is selected automatically; confirming calls the existing local `add-member` with explicit `home`, which freshly validates again. A locally held household (live or offline tombstone) instead shows “本机持有,无需指定”, returns `held: true` with no choices, and rejects explicit home writes until that household is revoked. Online local households receive receipts first. A successful local publish removes a conflicting older pin and leaves a durable inspector notice; expiry during local receipt delivery fails closed instead of resolving another advertiser. This does not weaken automatic pinning or admission.
122
- - Membership requires a joined session (live node or remembered intent) — no routing backdoor over unjoined sessions; leaving the network (leave/archive) drops every canvas membership
123
- - A member may also be a **remote** zone-qualified team handle (`zone/a1b2c3d4`) the operator adds by hand — the link that admits that remote caller here and entitles it to the full card
140
+ - Membership requires a joined session (live node or remembered intent) — no routing backdoor over unjoined sessions; leaving the network (leave/archive) takes it out of every canvas team (a legacy bare-handle slot is recorded as suspended instead: it no longer routes or counts as an effective member, and the owner restores or deletes it with `restore-suspended` / `delete-suspended`; see `docs/design/canvas-member-provenance.md`)
141
+ - A member may also be a **remote** zone-qualified team handle (`zone/a1b2c3d4`) the operator adds by hand — the link that admits that remote caller here and entitles it to the full card. On write the host freshly reads the verified cards of linked seed/delegate hosts and pins the handle as `zone/a1b2c3d4@<8-hex fingerprint>` carrying the owner's key fingerprint; with no unique verifiable owner the write is refused (#334). A legacy handle without a fingerprint is kept only for compatibility (while `canvasLegacyBareMembers` is `true`, its default, it still counts, authorizes and routes the old way) and needs the owner's `confirm-member`
124
142
  - `a2a_teams` lists local and in-scope remote canvas teams with owner URL and member count; CLI `teams` also includes local canvas and peerOrg, retaining same-name owners as separate rows. `a2a_whoami.teams.canvasDetails` and CLI `whoami --json` list local/remote memberships of this identity (legacy `teams.canvas` stays local-name-only). Owner URLs are discovery metadata, not a requirement to override `--home`. Failure guidance distinguishes visible-directory absence, known-owner missing prerequisites, and multiple owners (candidates, never guessed). Information scope, candidate selection and admission do not change; `/__dsh_a2a/state` serves per-member `joined`/`live` flags for local members and `{ id, team, remote: true }` for remote ones
125
143
 
126
- ## Native-teams bridge (node unification, P1) — deprecated
127
-
128
- > **Deprecated.** `@nelsonlongxiang/dsh-native-teams` is deprecated. The bridge code still ships
129
- > (`src/teams-bridge.ts`; `nativeTeamsInbound` defaults to `false`), kept for existing deployments;
130
- > new deployments should not rely on it. The section below describes it as it stands.
131
-
132
- Two half-bridges to `@nelsonlongxiang/dsh-native-teams` (structural contract mirror: `src/teams-bridge.ts`; the frozen contract lives in that package's `src/a2a-face.ts`):
133
-
134
- - **Outbound transport face** — this plugin mounts the `nativeTeamsA2a` service: `resolve` answers through the one directory walk `submit` uses (no drift-prone double matcher), `submit` rides the direct-route dispatcher (per-candidate async gate mirroring `a2a_route`; a peer's idempotency 409 replay is terminal per delivery (async → accepted, sync → throws with the `-32003` literal), never a failover duplicate; accepted submissions track in the owed ledger; when the submitting session is a joined node, its node team rides the wire as the receipt `callback`, so a peer's receipt wakes the session that dispatched the round — wake-on-route covers a cold one; without a joined parent no callback rides and receipts fall back to the caller label), `cancel` drives the cooperative `[A2A cancel]` stop-notice through the wire to the owning team (peers do not track inbound task ids, so a ledger route would always answer unknown) and clears the owed row. It exposes nothing new — only teams the peer network already publishes resolve.
135
- - **Inbound dispatch** (config `nativeTeamsInbound`, default `false`) — a routed team name the sibling registry classifies as an unambiguous local claim starts a routed round through its authoritative seam (`describeTarget`/`startRound`), parented by this node's live initiator, with the A2A envelope riding the round message. Registry presence alone is never exposure: the operator opts in. Dispatcher-level only — inbound callers address the team, never its members (members stay visible-not-addressable). Rounds are bounded like the steer path: a 180s reply-wait deadline (`nativeRoundWaitMs`) answers the honest delivered-unsettled shape while the round keeps going, and the caller's abort (where one exists) cancels the round through its own signal. `wait: false` fires the round detached after prepare-first checks (claim, seam, initiator — a phantom dispatch never answers success); native-teams rounds do not emit A2A receipts in this slice, so their results carry the `bridge` marker and are never booked as receipt-owed (the receipt backflow to `callbackTarget` is the P2 slice).
136
-
137
- See `docs/native-teams-bridge.md` for the full mapping and scope notes.
138
-
139
144
  ## Configuration
140
145
 
141
146
  Row config overlays in the profile's patch layers; every key has a schema default.
@@ -145,21 +150,20 @@ Row config overlays in the profile's patch layers; every key has a schema defaul
145
150
  | `announce` | `false` | Publish this node's agent card for peer discovery. |
146
151
  | `isolated` | `false` | Send nothing to any peer: card reads, directory sweeps, routes, remote log reads, canvas pings and fleet canvas-op relays are all refused (gh#193). `a2a/peers.json` is left untouched and refused fetches do not score peers; inbound faces keep answering; the state face shows `isolated: true`. For test and acceptance hosts. |
147
152
  | `forwardDirect` | `true` | From 0.6.38, one-hop forwarding is enabled by default for an authenticated online local CLI after a local target miss; sender scope and the hop guard remain enforced. Link B receiving members at A, and link/pin the CLI home A at B; no second forwarding hop. Set `forwardDirect: false` to restore local-only CLI delivery. |
153
+ | `superagent` | `false` | superagent (design v4, team model v5): run superagent's own domain engine (vendored upstream `src/core`) in this host. The team is a canvas team: the owner composes it on the canvas (router and members, local or on other hosts) and binds it at `/__dsh_a2a/superagent` with the router and the allowed senders; its A2A address is the canvas address `<zone>/canvas/<name>`. The router and members run the shipped `superagent-router` / `superagent-member` agent presets; see [`superagent.md`](src/skill/references/superagent.md). |
148
154
  | `peers` | `[]` | Seed base URLs; referred peers are learned from their cards. |
149
155
  | `delegates` | `[]` | Zone delegations: `{ name, url, publicKey }` published on the card. |
150
156
  | `team` | `'dsh'` | Team name this node exposes for direct routing. |
151
157
  | `session` | `''` | Caller label; `''` derives a stable `dsh-host-<8hex>` per home. |
152
158
  | `agentName` | `'DeepSeek Harness A2A node'` | Human-facing name on the card. |
153
- | `apiKey` | `''` | `X-API-Key` sent on peer requests; non-empty also gates the control routes. |
159
+ | `apiKey` | `''` | `X-API-Key` sent on peer requests (a peer with a `linkKeys` entry gets its link key instead); non-empty also gates the control routes and this host's own peer faces (`/a2a/direct`, `/a2a/log`, `/a2a/blob`; see `linkKeys` for when a link key is accepted). Empty leaves the peer faces open, admitting by caller identity. |
154
160
  | `sessionNodes` | `true` | Expose main sessions as joinable network nodes. |
155
- | `nativeTeamsInbound` | `false` | Dispatch inbound direct routes (and the outbound A2A tools' local candidates) to native-teams registry teams through its routing seam; needs the sibling plugin composed. |
156
- | `nativeRoundWaitMs` | `180000` | Reply-wait budget for one native-teams round (the bridge's deadline, mirroring the steer path's 180s). A round still running past it answers the honest delivered-unsettled shape and keeps going. |
157
- | `wakeJoinedOnBoot` | `false` | Prewarm cold joined sessions' agents after mount (needs the session controller wake face; wake-on-route and the sidebar wake button stay available without it). The prewarm is deferred, foreground-yielding, and cancellable — it never blocks the boot window (see the two knobs below). |
161
+ | `wakeJoinedOnBoot` | `false` | Prewarm cold joined sessions' agents after mount (needs the session controller wake face — without it the prewarm is skipped and the state face records `skipped:no-wake-face`; with the flag off, cold sessions are still woken on route (wake-on-route) and by the `wakeReconcile` loop, both of which also need the wake face). The prewarm is deferred, foreground-yielding, and cancellable — it never blocks the boot window (see the two knobs below). |
158
162
  | `wakePrewarmDelayMs` | `10000` | Idle delay between loader settlement and the first prewarm wake; `0` restores fire-at-settle. |
159
- | `wakePrewarmQuietMs` | `5000` | Foreground quiet window: a wake/route demand (or any outbound route in flight) inside this window postpones the next prewarm step; `0` disables the yield. |
163
+ | `wakePrewarmQuietMs` | `5000` | Foreground quiet window: a wake/route demand (or any outbound route in flight) inside this window postpones the next prewarm step; `0` disables the time window, but an outbound route in flight still yields. The reconcile loop (`wakeReconcile`) yields to the same window, at most 5 times per pass. |
160
164
  | `wakeBootStaggerMs` | `3000` | Pause between serial boot-prewarm wakes (caps log-decode saturation after a restart; wake-on-route never waits on it). |
161
165
  | `wakeReconcile` | `true` | Continuous desired-state reconciliation: each tick re-derives the cold joined intents due and wakes one per pass (post-boot drift, failed wakes, late gateways). Tuned by `wakeReconcileIntervalMs` (`60000`), `wakeReconcileBackoffBaseMs` (`5000`) and `wakeReconcileMaxBackoffMs` (`600000`). |
162
- | `seatNames` | `{}` | Human-owned stable display labels keyed by full local session id; controls/format characters (Cc/Cf), line/paragraph separators, names without visible content, and address-shaped names (`dsh/66abc2cf`, `zone/canvas/x`) are rejected. Invalid boot entries are dropped individually with warnings; owner writes remain all-or-nothing. Set/clear through the workbench owner panel; labels hot-apply to cards, whoami, CLI teams and the board, without changing titles, handles or admission. A2A model-tool naming arguments are refused. |
166
+ | `seatNames` | `{}` | Human-owned stable display labels (≤64 characters, at most 1024 entries) keyed by full local session id (`session-…` form); controls/format characters (Cc/Cf), line/paragraph separators, names without visible content, and address-shaped names (`dsh/66abc2cf`, `zone/canvas/x`) are rejected. Invalid boot entries are dropped individually with warnings; owner writes remain all-or-nothing. Set/clear through the workbench owner panel; labels hot-apply to cards, whoami, CLI teams and the board, without changing titles, handles or admission. A2A model-tool naming arguments are refused. |
163
167
  | `teamJoinAllowlist` | `[]` | Teams `a2a_team_join` may declare: **exact names only** (a `*`/prefix entry authorizes nothing). Empty denies every join. The owner can also edit it from the workbench owner panel (`/__dsh_a2a/owner-settings`). |
164
168
  | `teamScopeRouting` | `true` | S3 admission: routing checks caller↔target shared team membership; a session with no declared team has no network. `false` restores the open outbound seam. |
165
169
  | `teamScopeInfo` | `true` | S5 information-plane scope for `a2a_teams` / `a2a_status` / `a2a_probe` (see the tools section). `false` restores fleet-wide disclosure. |
@@ -175,11 +179,11 @@ Row config overlays in the profile's patch layers; every key has a schema defaul
175
179
  | `linkKeys` | `{}` | Link keys (#317): `{ "<peer origin>": "<key>" }`. Requests to that peer carry this key instead of `apiKey`; this host's peer faces (`/a2a/direct`, `/a2a/log`, `/a2a/blob`) accept it only from a caller whose OriginClaim verifies and names that origin (a POST must be Sig3 body-bound); the control routes never accept it. Lets a public node link to the fleet without holding the fleet apiKey; both ends configure the same link key. |
176
180
  | `gateAgentLessCalls` | `'audit'` | Host-side (agent-less) routing calls: audit them, or `refuse`. |
177
181
  | `settlementAuth` | `'observe'` | Settlement authorization (#114): `off`, `observe` (record what would be refused, settle as before), or `enforce`. |
178
- | `asyncNudgeDelayMs` | `120000` | Delay before a delivered-but-unconsumed async route re-wakes its target with a one-line nudge; `0` disables. |
182
+ | `asyncNudgeDelayMs` | `120000` | Delay before an async task delivered through `/a2a/direct` but not yet consumed by its target re-wakes it with a one-line nudge (a busy target gets one round of grace, then the nudge escalates to a queued prompt); `0` disables. |
179
183
  | `targetQueueCap` | `3` | At most this many unanswered, receipt-owing tasks queue for one target on this host; the next delivery is refused visibly (`-32009 target-queue-full`, nothing delivered). A receipt for a task this host booked and CLI mailboxes are never refused; a canvas team counts as one target. A refused task id is recorded as failed — retry with a new id. A target-attributed final or its own receipt attempt records `answeredAt` and releases queue capacity even if receipt delivery fails; the row remains owed, with unchanged expiry, late-receipt and conservation semantics. State/board queue counts use this same rule. `0` disables (#224/#253). |
180
184
  | `taskStaleTtlMs` | `86400000` | Owed-task stale TTL (24h): past it a pending row auto-dead-letters (a late receipt can still settle it). |
181
185
  | `dshHome` | `''` | DSH home directory override for the plugin's state (`<home>/a2a/…`); `''` resolves the default home. |
182
- | `cardTtlMs` / `flushTimeoutMs` / `routeTimeoutMs` | see schema | Card lifetime (48h) and route timing budgets. |
186
+ | `cardTtlMs` / `flushTimeoutMs` / `routeTimeoutMs` | `172800000` / `300000` / `1800000` | Card lifetime (48h, re-signed every quarter of it); how long a session may go without a final reply before the reply waiter is released with a placeholder answer (5 min); per-call timeout of the `a2a_route` tool (30 min). |
183
187
  | `cardCacheTtlMs` / `cardCacheNegativeTtlMs` / `remoteRowsTtlMs` / `stateColdRowsTtlMs` | `60000` / `30000` / `15000` / `5000` | Peer card cache (hit / miss), remote session rows refresh window, and cold-row refresh window of the state face. |
184
188
 
185
189
  Example — one announcing node with a seed:
@@ -194,76 +198,77 @@ Example — one announcing node with a seed:
194
198
 
195
199
  ## Leave semantics (bounded staleness)
196
200
 
197
- `leave` is a per-session, per-host gesture: it removes the join intent locally and unmounts the node. There is **no network-wide revocation broadcast** — peers keep listing the team until their cached copy of this node's card refreshes (`cardCacheTtlMs`, 60s by default — `sessionTeams` is an unsigned serve-fresh field, so the card's own 48h TTL does not hold it), and inbound deliveries during that window fail honestly (the host no longer resolves the team). Remote targets that already implemented the receipt contract are unaffected. A formal revocation-broadcast protocol is tracked in upstream note `2026-08-27-a2a-leave-no-revocation-broadcast.md` (proposed).
201
+ `leave` is a per-session, per-host gesture (`POST /__dsh_a2a/leave`, a control route): it removes the join intent locally and unmounts the node, takes the session out of every canvas team (a legacy bare-handle slot is recorded as suspended) and withdraws its roster declarations; `sessionTeams` on the card this host publishes stops listing it at once. There is **no network-wide revocation broadcast** — peers keep listing the team until their cached copy of this node's card refreshes (the peer's own `cardCacheTtlMs`, 60s by default — `sessionTeams` is an unsigned serve-fresh field, so the card's own 48h TTL does not hold it), and inbound deliveries during that window fail honestly (the host no longer resolves the team). That bound holds only while this node stays reachable: if a peer can no longer read this node's card it keeps using the old card for the `cardCacheNegativeTtlMs` window (30s by default) and retries, for at most until the old card's own expiry (48h by default; the rows are marked unreachable). A formal revocation-broadcast protocol is tracked in upstream note `2026-08-27-a2a-leave-no-revocation-broadcast.md` (proposed).
198
202
 
199
203
  ## Trust model
200
204
 
201
- Cards are Ed25519-signed and expire; `peers` referrals, `sessionTeams` listings, `lanIp`, and `version` on a card
205
+ Cards are Ed25519-signed and expire; the signed core covers only `name`, `session`, `team`, `capabilities`, `expiresAt` and the zone-delegation `records`. `peers` referrals, `sessionTeams` listings, `teamMemberships`, `canvasOrg`, `lanIp`, and `version` on a card
202
206
  are unsigned and read fresh — the last two exist so operators can audit which build every fleet node actually runs
203
- without visiting each host. A referral URL whose own fetch just proved it serves this node's card is remembered,
204
- and re-offers of it within a suppression window are ignored instead of flickering in and out of the peer store.
205
- Every `/__dsh_a2a/*` control route — reads included — requires the configured `apiKey` (`X-API-Key`,
207
+ without visiting each host. A referral URL whose own fetch just proved it serves this node's card (or another spelling of an already tracked peer) is remembered for 6 hours,
208
+ and re-offers of it within that window are ignored instead of flickering in and out of the peer store.
209
+ Every `/__dsh_a2a/*` control route — reads included — requires the configured `apiKey` (the `X-API-Key` header, or an `api_key` query parameter for non-browser clients;
206
210
  constant-time compare) when one is set. With an empty key only a loopback socket is trusted; a
207
- `Sec-Fetch-Site` header can only refuse (a cross-site page is rejected even on loopback), never grant.
211
+ `Sec-Fetch-Site` header can only refuse (a cross-site page is rejected even on loopback), never grant, and a request carrying `X-Forwarded-For`, `Forwarded` or `X-Real-IP` gets no loopback trust either (a remote caller arriving through a local reverse proxy or tunnel cannot borrow it to skip the key).
208
212
  Set an `apiKey` before exposing the listener beyond loopback.
209
213
 
210
214
  | Control route | Purpose |
211
215
  | --- | --- |
212
216
  | `GET /__dsh_a2a/state` | The state face the panel and workbench poll (sessions, canvas, peers, remote rows, activity, ledger). |
213
217
  | `POST /__dsh_a2a/join` · `/leave` | Join or leave one session node. |
214
- | `/__dsh_a2a/canvas` · `/canvas-layout` | Canvas teams (create/remove/add-member/remove-member) and the board layout. |
215
- | `POST /__dsh_a2a/roster-op` | Owner-controlled roster team creation, member add/remove, dissolution, and exact allowlist maintenance. |
218
+ | `/__dsh_a2a/canvas` · `/canvas-layout` | Canvas teams (create/remove/add-member/remove-member, plus the owner's confirm-member / restore-suspended / delete-suspended / cli-home-options) and the board layout. |
219
+ | `POST /__dsh_a2a/roster-op` | Owner-controlled roster teams on the planning board (`op`: create — at least two joined local sessions — / add-member / remove-member / dissolve), with the exact names in `teamJoinAllowlist` maintained alongside; it cannot admit remote or unjoined sessions. |
216
220
  | `/__dsh_a2a/groups` | Manual session grouping — deprecated since 0.6.6 (canvas teams supersede it), kept for old panels. |
217
221
  | `POST /__dsh_a2a/dispatch` | Workbench quick dispatch into a team. |
218
- | `POST /__dsh_a2a/tasks/abandon` · `/tasks/cancel` | Settle an owed task from the caller side (cancel also steers a stop notice to a live local target); the row lands in `archived[]`. |
222
+ | `POST /__dsh_a2a/tasks/abandon` · `/tasks/cancel` | Settle an owed task from the caller side (cancel also makes a best-effort stop notice to the target: a steered notice for a local one, a wire delivery for a remote one — which also carries `cancel_task_id` when the peer's card declares `queuedTaskCancellation`, withdrawing the original task if it is still queued; the reply carries `steered` and `removed`); the row lands in `archived[]`. |
223
+ | `GET`/`POST /__dsh_a2a/superagent` | With `superagent: true`: the engine state (threads, decisions, replies, tickets with stage, the bound canvas and any binding problem), and the owner's actions `settings` (bind `canvas` and `router`) · `request` (the workbench Source) · `serve` (serve another host's team by its canvas id) · `stop` · `retry`. |
219
224
  | `/__dsh_a2a/session-log` · `/remote-session-log` | Session log reads for the workbench. |
220
- | `/__dsh_a2a/owner-settings` | The owner panel: read (GET) or write (POST) `teamJoinAllowlist`, `teamScopeInfo`, `seatNames` and `announce` into the home patch layer (all but `announce` apply immediately). `seatNames` is a validated full map; omit a session id to clear its label. This owner-authenticated control face is not a model naming tool; local shell/config writers remain inside the existing operator trust boundary. |
225
+ | `/__dsh_a2a/owner-settings` | The owner panel: read (GET) or write (POST) `teamJoinAllowlist`, `teamScopeInfo`, `seatNames` and `announce` into the home patch layer (all but `announce` apply immediately; `announce` needs a restart, and GET reports `announceRestartPending`). `seatNames` is a validated full map; omit a session id to clear its label. This owner-authenticated control face is not a model naming tool; local shell/config writers remain inside the existing operator trust boundary. |
221
226
  | `/__dsh_a2a/fleet-canvas-op` | Relay a canvas operation (`create`, `add-member`, `remove-member`, `remove`) to a peer's `/a2a/canvas-op`, stamped with this node's OriginClaim; the peer's link gate decides. |
222
- | `/__dsh_a2a/cli-deposit` | Deposit, list, revoke and mailbox reads for `a2a-net` CLI identities. |
223
- | `GET /__dsh_a2a/nodes` · `POST` | Node identities (docs/design/node-identity.md): GET reads the roster or one node (`?name=`); POST is an identity write and needs the apiKey even on loopback. |
227
+ | `/__dsh_a2a/cli-deposit` | Deposit, list, revoke, mailbox reads and admission queries (`admission`, which must be signed by the identity itself) for `a2a-net` CLI identities. |
228
+ | `GET /__dsh_a2a/nodes` · `POST` | Node identities (docs/design/node-identity.md): GET reads the roster or one node (`?name=`); POST is an identity write and needs the `X-API-Key` even on loopback; a host with no `apiKey` refuses every identity write. |
224
229
  | `POST /__dsh_a2a/blob` | Stage a file for a recipient (`?to=&name=`) — the file channel's write side; peers pull through `GET /a2a/blob`. |
225
230
 
226
231
  The state face's ledger: `tasks` (pending, each with `settleable`), `tasksStaleCount`, `tasksDead` (with
227
232
  `direction` when known), and `archived` — the newest 64 settled rows, newest first, each with `outcome`,
228
- `endedBy` (`receipt` | `echo` | `abandoned` | `cancelled`), `late: true` when a receipt arrived after the
233
+ `endedBy` (`receipt` | `echo` | `abandoned` | `cancelled` | `evicted`), `late: true` when a receipt arrived after the
229
234
  caller ended it, and `direction` when known — plus `archivedCount` and `archivedSplit` (count per outcome).
230
235
 
231
236
  ## Stages (visualization surfaces)
232
237
 
233
- Two full-page stages mount beside the model tools: `/__dsh_a2a_nexus` (the
238
+ A full-page workbench mounts beside the model tools: `/__dsh_a2a_nexus` (the
234
239
  A2A workbench — since 0.6.2 the 2D spatial planning board IS the stage and
235
- the directory/ledger/activity/plan panels ride a collapsible side rail in
240
+ the directory / hosts / ledger / activity / plan panels (plus a Team page when the host runs a superagent team) ride a collapsible side rail in
236
241
  one merged, responsive shell; while the canvas face is down the panels fall
237
242
  back to the main stack; the Three.js topology viewer was retired in 0.6.0;
238
- `?mode=plan` still converges on the board) and `/__dsh_a2a_canvas`. A bare
243
+ `?mode=plan` still converges on the board). A bare
239
244
  mount path
240
245
  redirects (301) onto its trailing-slash form, so both spellings render and
241
- the shell's relative assets always resolve inside the served tree. For
242
- offline development, `nexus-stage/scripts/mockhost.py [port]` serves the
243
- built stage with canned state; open `/?fault=500` on it to exercise the
244
- on-page fault badge. `pnpm verify` needs no manual bootstrap on a fresh
245
- checkout: its `verify:nexus` step installs the subpackage dependencies
246
- itself before building.
246
+ the shell's relative assets always resolve inside the served tree. The earlier office stage `/__dsh_a2a_canvas` has been removed. For
247
+ offline development, `nexus-stage/scripts/mockhost.py [port]` (default 8780) serves the
248
+ built stage (`assets/nexusDist`); its state comes from `LIVE_STATE` (default: the state face on local port 3080) and falls back to an empty fleet when that is unreachable. Open `/?fault=500` on it to exercise the
249
+ on-page fault badge, `/?demo=1` to merge a demo federation dataset (2 peers, 2 in-flight routes), and set `LAYOUT_FILE=<path>` to keep the layout across restarts
250
+ (in memory only by default). `pnpm verify` is `pnpm verify:core` wrapped in a "zero leftover temp directories" gate; its `verify:nexus` step runs `npm ci` for the
251
+ stage subpackage and then builds, so the subpackage needs no manual bootstrap (the repo root's own dependencies still need `pnpm install` first).
247
252
 
248
253
  ## Collaboration SOP (network practice, v0.1)
249
254
 
250
- > Current rules: the A2A collaboration norm ([`src/skill/references/collaboration.md`](src/skill/references/collaboration.md), in Chinese, shipped with the plugin to every node). The v0.1 SOP below is kept as history; where they conflict, the norm wins.
255
+ > Current rules: the A2A collaboration norm ([`src/skill/references/collaboration.md`](src/skill/references/collaboration.md), in Chinese, shipped with the plugin to every node). The v0.1 SOP below is kept as history; where they conflict, the norm wins. Each item below says whether the current norm (v1.18) has superseded it.
251
256
 
252
257
  Distilled from the 0.1.x–0.5.x delivery cycles by the network's research node; adopted by the maintainer.
253
258
 
254
- - **Roles** — one session, one office, no crossing workspace boundaries: a research node (protocol/design
259
+ - **Roles** (superseded: since norm v1.17 a project has at most two roles — the developer does, the reviewer reviews — and no separate research, maintainer, test or ops seats; see norm §1) — one session, one office, no crossing workspace boundaries: a research node (protocol/design
255
260
  review, verdict criteria, source-line references with must-fix/recommend labels), a maintainer node
256
261
  (version rulings, releases, adoption receipts), a test node (the single execution point for all live-host
257
262
  verification; production hosts are never used for ad-hoc testing), and an install/ops node (production
258
263
  profile deployment, single-owner, waits for the maintenance window).
259
- - **Dispatch discipline** — test tasks go to the test node with version + commit + decidable criteria +
264
+ - **Dispatch discipline** (partly kept: a review request still names the commit SHA, the completion criteria and the input as a path or link, never pasted content — norm §6; "test tasks go to the test node" and "review tasks go to the research node" are superseded, testing belongs to the reviewer) — test tasks go to the test node with version + commit + decidable criteria +
260
265
  receipt target; review tasks go to the research node with source pointers, not paraphrases.
261
266
  - **Receipt contract** — tasks longer than minutes route with `async: true` (wait:false); receipts read
262
267
  `[A2A receipt] task <task_id> <outcome summary>`; the task id is caller-born, request-carried,
263
- peer-echoed, and steered-header-transmitted — four-way agreement before a receipt correlates.
264
- - **Upgrade chain** — test node green → maintainer receipt → ops node requests the window → production
268
+ peer-echoed, and steered-header-transmitted; a receipt correlates to the owed row in the local ledger by that id (when an older peer does not echo it the caller falls back to the id it minted itself, so four-way agreement is not required), and then passes the settlement-authorization verdict.
269
+ - **Upgrade chain** (superseded: the current norm has no pre-authorization, maintenance window or network-wide notice; a high-tier change needs one non-self node's review PASS on the same commit SHA, after which the author merges and executes — norm §4, §7) — test node green → maintainer receipt → ops node requests the window → production
265
270
  upgrade → maintainer's browser-face final check → network-wide notice.
266
- - **Reuse red lines** — shared pure logic extracts to a library; shared runtime capability extracts to a
271
+ - **Reuse red lines** (partly enforced: "plugins never value-import each other" is enforced for the browser client bundle by the purity gate in `tsdown.config.ts`, which fails the build on any non-platform `@deepseek-ai/*` value import; "production hosts carry no verification duty" has no counterpart in the current norm and is only a historical statement) — shared pure logic extracts to a library; shared runtime capability extracts to a
267
272
  Service; plugins never value-import each other; production hosts carry no verification duty (no
268
273
  dev-who-self-tests).
269
274
 
@@ -273,20 +278,27 @@ Tests are layered by depth (contract: `docs/testing-layers.md`): each layer
273
278
  has its own directory and script, so iteration can target one depth.
274
279
 
275
280
  ```sh
276
- pnpm install # auto-install-peers pulls the DSH peer train from npm
277
- pnpm verify # typecheck + build + ALL vitest layers below + layout gate + nexus-stage suite
278
- pnpm test:unit # L0 — pure logic, no fiber, no sockets (stores, codecs, jsdom components)
281
+ pnpm install # auto-install-peers pulls the DSH peer train from npm; `prepare` also runs pnpm build once
282
+ pnpm verify # verify:core wrapped in a "zero leftover temp directories" gate: typecheck + verify:docs (doc drift)
283
+ # + build + pnpm test (ALL vitest layers L0-L3; L4 is skipped without its env) + layout gate + nexus-stage suite
284
+ pnpm test # ALL vitest layers L0-L3 (gates included)
285
+ pnpm test:unit # L0 — pure logic, usually no fiber and no sockets (stores, codecs, jsdom components); a few exceptions:
286
+ # cli-args / cli-receipt / cli-thread start a loopback HTTP server to test the real CLI,
287
+ # receipt-retry / superagent-role-tools-scope build a bare cordis Context
279
288
  pnpm test:wire # L1 — ONE real plugin instance over real HTTP against faked peers
280
289
  pnpm test:mesh # L2 — >=2 REAL nodes (own homes/keys/peers.json) over real HTTP: the
281
290
  # cross-machine equivalent, incl. fault injection (clock-skew cards,
282
291
  # tampered cards, partition/restart, alias collapse, zone delegation,
283
292
  # referral freeze) and receipt replay — see the fault matrix in
284
293
  # docs/testing-layers.md
294
+ pnpm test:build-layout # L3 — artifact contract (the build layout `exports` points at exists); not a behavior test
285
295
  pnpm test:live # L4 — REAL remote machines, env-gated (DSH_A2A_LIVE_MESH=...); skipped
286
296
  # by default, never in CI — runbook in docs/testing-layers.md
287
- pnpm build # host lib (including lib/a2a-client.js), client types, and browser bundle (lib/client.js)
297
+ pnpm build # host lib (including lib/a2a-client.js), client types, the browser bundle (lib/client.js) and the packaged skill (lib/skill/)
288
298
  ```
289
299
 
300
+ Note: the `paths` in `tsconfig.json` point first at a checkout of `../deepseek-harness` **beside** this repo (`vendor/`, `packages/`) for types; without that sibling checkout typecheck/build falls back to the DSH types on npm, which may lag the source this plugin compiles against (see the note in Install).
301
+
290
302
  ## Non-DSH interop (protocol modules)
291
303
 
292
304
  A node that is NOT a DeepSeek Harness host can still speak the protocol. The
@@ -341,4 +353,7 @@ subpaths import only each other and `node:crypto`.
341
353
 
342
354
  ## License
343
355
 
344
- MIT
356
+ MIT — see `LICENSE`. Third-party work is listed in `THIRD_PARTY_NOTICES.md`, with its licence texts in `docs/licenses/`;
357
+ all three ship in the npm package (#470).
358
+
359
+ The superagent engine is vendored unchanged from upstream (MIT, © DeepSeek Harness Team).