@nelsonlongxiang/dsh-open-a2a-net 0.5.15 → 0.6.25

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 (89) hide show
  1. package/README.md +243 -121
  2. package/README_zh.md +144 -84
  3. package/assets/nexusDist/assets/index-034h1cNc.js +3 -0
  4. package/assets/nexusDist/assets/index-PG9EieGX.css +1 -0
  5. package/assets/nexusDist/index.html +26 -0
  6. package/assets/stageDist/assets/index-Bj7S2pA1.css +1 -0
  7. package/assets/stageDist/assets/index-C-28k_o5.js +974 -0
  8. package/assets/stageDist/assets/interiors-CQGBjffT.png +0 -0
  9. package/assets/stageDist/assets/office-tileset-CHQT-uYL.png +0 -0
  10. package/assets/stageDist/index.html +13 -0
  11. package/lib/a2a-client.js +190 -13
  12. package/lib/canvas-store.js +218 -0
  13. package/lib/card.js +25 -0
  14. package/lib/client/A2aControl.js +45 -137
  15. package/lib/client/locales.js +16 -40
  16. package/lib/client.js +164 -467
  17. package/lib/client.js.map +1 -1
  18. package/lib/exposure-store.js +82 -0
  19. package/lib/group-store.js +6 -0
  20. package/lib/idempotency-store.js +291 -0
  21. package/lib/index.js +4446 -271
  22. package/lib/joined-store.js +11 -0
  23. package/lib/layout-store.js +155 -0
  24. package/lib/peer-store.js +98 -13
  25. package/lib/receipt-ladder.js +35 -0
  26. package/lib/receipt.js +102 -0
  27. package/lib/self-suppress.js +62 -0
  28. package/lib/session-transcript.js +150 -0
  29. package/lib/skill/SKILL.md +77 -0
  30. package/lib/skill/references/discovery.md +38 -0
  31. package/lib/skill/references/membership.md +50 -0
  32. package/lib/skill/references/pitfalls.md +42 -0
  33. package/lib/skill/references/routing.md +43 -0
  34. package/lib/skill-registration.js +52 -0
  35. package/lib/stage-mount.js +32 -0
  36. package/lib/task-ledger.js +540 -0
  37. package/lib/team-store.js +122 -0
  38. package/lib/teams-bridge.js +18 -0
  39. package/lib/transport-caps.js +31 -0
  40. package/lib/types/a2a-client.d.ts +72 -1
  41. package/lib/types/canvas-store.d.ts +102 -0
  42. package/lib/types/client/A2aControl.d.ts +18 -28
  43. package/lib/types/client/locales.d.ts +1 -1
  44. package/lib/types/exposure-store.d.ts +45 -0
  45. package/lib/types/group-store.d.ts +6 -0
  46. package/lib/types/idempotency-store.d.ts +164 -0
  47. package/lib/types/index.d.ts +211 -11
  48. package/lib/types/joined-store.d.ts +9 -0
  49. package/lib/types/layout-store.d.ts +63 -0
  50. package/lib/types/peer-store.d.ts +45 -2
  51. package/lib/types/receipt-ladder.d.ts +25 -0
  52. package/lib/types/receipt.d.ts +51 -0
  53. package/lib/types/self-suppress.d.ts +41 -0
  54. package/lib/types/session-transcript.d.ts +37 -0
  55. package/lib/types/skill-registration.d.ts +3 -0
  56. package/lib/types/stage-mount.d.ts +29 -0
  57. package/lib/types/task-ledger.d.ts +308 -0
  58. package/lib/types/team-store.d.ts +55 -0
  59. package/lib/types/teams-bridge.d.ts +112 -0
  60. package/lib/types/transport-caps.d.ts +29 -0
  61. package/lib/types/types.d.ts +43 -0
  62. package/nexus-stage/package-lock.json +2262 -0
  63. package/nexus-stage/package.json +21 -0
  64. package/nexus-stage/scripts/__pycache__/mockhost.cpython-312.pyc +0 -0
  65. package/nexus-stage/scripts/mockhost.py +253 -0
  66. package/nexus-stage/src/canvas-ops.ts +175 -0
  67. package/nexus-stage/src/canvas-wire.ts +156 -0
  68. package/nexus-stage/src/edges.ts +72 -0
  69. package/nexus-stage/src/fault.ts +56 -0
  70. package/nexus-stage/src/federation.ts +234 -0
  71. package/nexus-stage/src/layout-doc.ts +143 -0
  72. package/nexus-stage/src/layout-wire.ts +158 -0
  73. package/nexus-stage/src/main.ts +361 -0
  74. package/nexus-stage/src/planning-menus.ts +352 -0
  75. package/nexus-stage/src/planning-nodelist.ts +294 -0
  76. package/nexus-stage/src/planning-types.ts +111 -0
  77. package/nexus-stage/src/planning-view.ts +1313 -0
  78. package/nexus-stage/src/planning.css +280 -0
  79. package/nexus-stage/src/seat.ts +35 -0
  80. package/nexus-stage/src/stage-name.ts +13 -0
  81. package/nexus-stage/src/tokens.ts +32 -0
  82. package/nexus-stage/src/viewport.ts +83 -0
  83. package/nexus-stage/src/workbench.css +267 -0
  84. package/nexus-stage/src/workbench.ts +1530 -0
  85. package/nexus-stage/src/world.ts +509 -0
  86. package/nexus-stage/tsconfig.json +25 -0
  87. package/nexus-stage/vite.config.ts +16 -0
  88. package/package.json +218 -157
  89. package/lib/types/client.d.ts +0 -73
package/README.md CHANGED
@@ -1,121 +1,243 @@
1
- # @nelsonlongxiang/dsh-open-a2a-net
2
-
3
- English | [中文](README_zh.md)
4
-
5
- [![npm](https://img.shields.io/npm/v/@nelsonlongxiang/dsh-open-a2a-net?label=npm)](https://www.npmjs.com/package/@nelsonlongxiang/dsh-open-a2a-net)
6
-
7
- Open A2A network plugin for [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) (DSH): turns a DSH
8
- deployment into one node of a decentralized agent network — no central server, no registry.
9
-
10
- Each node publishes a **signed, expiring agent card** at `/.well-known/agent-card.json`, discovers peers through
11
- **seed URLs and referrals** (each card lists the peers it knows), resolves names through **zone delegations**
12
- (GNUnet GNS-style: a card may delegate a name to another zone), and routes **directly** to a peer's team with
13
- failover across the reachable half of candidates. Every main session can additionally be exposed as a **session
14
- node** other hosts can discover and join from the web sidebar.
15
-
16
- | The sidebar network panel | Context message display |
17
- | --- | --- |
18
- | ![Sidebar network panel](images/image-01.png) | ![Context message display](images/image-02.png) |
19
-
20
- ## Install
21
-
22
- ```sh
23
- dsh plugin --profile web add @nelsonlongxiang/dsh-open-a2a-net
24
- ```
25
-
26
- Restart `dsh web`, then open the sidebar network panel (footer action).
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.
35
-
36
- ## What you get
37
-
38
- - **Model tools** — `a2a_teams` lists the teams this node can see (own, peers', joined sessions'); `a2a_route`
39
- sends one message to a team, reuses `context_id` to continue a remote conversation, and fails over across
40
- candidates when a peer is unreachable.
41
- - **Sidebar control** — a footer action in the web sidebar listing this host's sessions as joinable network
42
- nodes: title, recent-activity excerpt, and team per row, join/leave in place, and a wake action for cold rows
43
- (persisted join intent whose session is not loaded yet — opening the session remounts the node). Session teams
44
- are `<team>/<id8>`; web sessions use their id's first 8 chars, imported sessions (`import-<uuid>`) keep the
45
- `import-` prefix plus the uuid's first 8 hex chars so imported ids cannot collapse into a handful of teams.
46
- - **Archive leaves the network** — archiving a session (workspace registry state) prunes its join
47
- intent and unmounts its node: at boot settlement before any wake, on every state read (a mid-session
48
- archive disappears within one panel poll), and as a route-time guard that never wakes an archived
49
- target.
50
- - **Honest waits** — a synchronous route whose target never answers releases the caller at a 180s
51
- reply-wait deadline with the delivered shape and the receipt contract (the target answers on its own
52
- cadence); `async: true` skips the wait entirely. The network panel dims an in-flight row past 120s as
53
- a stale wait instead of implying live progress, and its title carries the package version badge.
54
- - **Announce** — `announce: true` publishes this node's card (team, capabilities, referrals, joined session
55
- teams) so peers find it without any directory.
56
-
57
- ## Configuration
58
-
59
- Row config overlays in the profile's patch layers; every key has a schema default.
60
-
61
- | Key | Default | Purpose |
62
- | --- | --- | --- |
63
- | `announce` | `false` | Publish this node's agent card for peer discovery. |
64
- | `peers` | `[]` | Seed base URLs; referred peers are learned from their cards. |
65
- | `delegates` | `[]` | Zone delegations: `{ name, url, publicKey }` published on the card. |
66
- | `team` | `'dsh'` | Team name this node exposes for direct routing. |
67
- | `session` | `''` | Caller label; `''` derives a stable `dsh-host-<8hex>` per home. |
68
- | `agentName` | `'DeepSeek Harness A2A node'` | Human-facing name on the card. |
69
- | `apiKey` | `''` | `X-API-Key` sent on peer requests; non-empty also gates the control routes. |
70
- | `sessionNodes` | `true` | Expose main sessions as joinable network nodes. |
71
- | `wakeJoinedOnBoot` | `false` | Materialize every cold joined session's agent on mount (needs the api gateway; wake-on-route and the sidebar wake button stay available without it). |
72
- | `cardTtlMs` / `flushTimeoutMs` / `routeTimeoutMs` | see schema | Card lifetime and route timing budgets. |
73
-
74
- Example — one announcing node with a seed:
75
-
76
- ```yaml
77
- - id: a2a
78
- name: '@nelsonlongxiang/dsh-open-a2a-net'
79
- config:
80
- announce: true
81
- peers: ['http://127.0.0.1:41243']
82
- ```
83
-
84
- ## Trust model
85
-
86
- Cards are Ed25519-signed and expire; `peers` referrals and `sessionTeams` listings on a card are unsigned and
87
- read fresh. The `/__dsh_a2a/join` and `/__dsh_a2a/leave` control routes require the configured `apiKey`
88
- (constant-time compare) when one is set; with an empty key they trust only same-origin browsers and loopback
89
- callers. Set an `apiKey` before exposing the listener beyond loopback.
90
-
91
- ## Collaboration SOP (network practice, v0.1)
92
-
93
- Distilled from the 0.1.x–0.5.x delivery cycles by the network's research node; adopted by the maintainer.
94
-
95
- - **Roles** — one session, one office, no crossing workspace boundaries: a research node (protocol/design
96
- review, verdict criteria, source-line references with must-fix/recommend labels), a maintainer node
97
- (version rulings, releases, adoption receipts), a test node (the single execution point for all live-host
98
- verification; production hosts are never used for ad-hoc testing), and an install/ops node (production
99
- profile deployment, single-owner, waits for the maintenance window).
100
- - **Dispatch discipline** — test tasks go to the test node with version + commit + decidable criteria +
101
- receipt target; review tasks go to the research node with source pointers, not paraphrases.
102
- - **Receipt contract** — tasks longer than minutes route with `async: true` (wait:false); receipts read
103
- `[A2A receipt] task <task_id> <outcome summary>`; the task id is caller-born, request-carried,
104
- peer-echoed, and steered-header-transmitted — four-way agreement before a receipt correlates.
105
- - **Upgrade chain** — test node green → maintainer receipt → ops node requests the window → production
106
- upgrade → maintainer's browser-face final check → network-wide notice.
107
- - **Reuse red lines** — shared pure logic extracts to a library; shared runtime capability extracts to a
108
- Service; plugins never value-import each other; production hosts carry no verification duty (no
109
- dev-who-self-tests).
110
-
111
- ## Verify
112
-
113
- ```sh
114
- pnpm install # auto-install-peers pulls the DSH peer train from npm
115
- pnpm verify # typecheck, build, source tests, and built-artifact layout guard
116
- pnpm build # host lib (including lib/a2a-client.js), client types, and browser bundle (lib/client.js)
117
- ```
118
-
119
- ## License
120
-
121
- MIT
1
+ # @nelsonlongxiang/dsh-open-a2a-net
2
+
3
+ English | [中文](README_zh.md)
4
+
5
+ [![npm](https://img.shields.io/npm/v/@nelsonlongxiang/dsh-open-a2a-net?label=npm)](https://www.npmjs.com/package/@nelsonlongxiang/dsh-open-a2a-net)
6
+
7
+ Open A2A network plugin for [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) (DSH): turns a DSH
8
+ deployment into one node of a decentralized agent network — no central server, no registry.
9
+
10
+ Each node publishes a **signed, expiring agent card** at `/.well-known/agent-card.json`, discovers peers through
11
+ **seed URLs and referrals** (each card lists the peers it knows), resolves names through **zone delegations**
12
+ (GNUnet GNS-style: a card may delegate a name to another zone), and routes **directly** to a peer's team with
13
+ failover across the reachable half of candidates. Every main session can additionally be exposed as a **session
14
+ node** other hosts can discover and join from the web sidebar.
15
+
16
+ | The sidebar network panel | Context message display |
17
+ | --- | --- |
18
+ | ![Sidebar network panel](images/image-01.png) | ![Context message display](images/image-02.png) |
19
+
20
+ ## Install
21
+
22
+ ```sh
23
+ dsh plugin --profile web add @nelsonlongxiang/dsh-open-a2a-net
24
+ ```
25
+
26
+ Restart `dsh web`, then open the sidebar network panel (footer action).
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.
35
+
36
+ ## What you get
37
+
38
+ - **Model tools** — `a2a_teams` lists the teams this node can see (own, peers', joined sessions'); `a2a_route`
39
+ sends one message to a team, reuses `context_id` to continue a remote conversation, and fails over across
40
+ candidates when a peer is unreachable. `a2a_tasks` reconciles the receipt contract across three tiers: every route that leaves a
41
+ task owed a reply (async delivery, a released wait) stays in the owed book as pending until its
42
+ `[A2A receipt] task <task_id>` message correlates — overdue rows auto-dead-letter past the stale TTL (a revived target can still
43
+ settle with a late receipt), every settlement lands in the bounded archive instead of being silently evicted, and each row keeps
44
+ its follow-up `context_id`, persisted across
45
+ restarts. `a2a_probe` measures the tracked fleet's reachability and round-trip latency (one verified-card
46
+ fetch per peer, optionally narrowed to a single url) for pre-dispatch health checks, naming each miss's
47
+ stage — `unreachable` (transport/HTTP) vs. `rejected` (distrusted card).
48
+ - **Sidebar control** — a footer action in the web sidebar listing this host's sessions as joinable network
49
+ nodes: title, recent-activity excerpt, and team per row, join/leave in place, and a wake action for cold rows
50
+ (persisted join intent whose session is not loaded yet — opening the session remounts the node). Session teams
51
+ are `<team>/<id8>`; web sessions use their id's first 8 chars, imported sessions (`import-<uuid>`) keep the
52
+ `import-` prefix plus the uuid's first 8 hex chars so imported ids cannot collapse into a handful of teams.
53
+ The panel's activity section also lists owed receipts — async tasks delivered but not yet correlated by their
54
+ `[A2A receipt]` message — beside the in-flight waits, so a cross-turn wait is visible without asking the model.
55
+ - **Archive leaves the network** — archiving a session (workspace registry state) prunes its join
56
+ intent and unmounts its node: at boot settlement before any wake, on every state read (a mid-session
57
+ archive disappears within one panel poll), and as a route-time guard that never wakes an archived
58
+ target.
59
+ - **Honest waits** — a synchronous route whose target never answers releases the caller at a 180s
60
+ reply-wait deadline with the delivered shape and the receipt contract (the target answers on its own
61
+ cadence); `async: true` skips the wait entirely. The network panel dims an in-flight row past 120s as
62
+ a stale wait instead of implying live progress, and its title carries the package version badge.
63
+ - **Announce** — `announce: true` publishes this node's card (team, capabilities, referrals, joined session
64
+ teams) so peers find it without any directory.
65
+
66
+ ## Model tools at a glance
67
+
68
+ | Tool | Answers | Notes |
69
+ | --- | --- | --- |
70
+ | `a2a_teams` | Which teams can I route to? | Own, peers', and joined sessions' teams, with activity excerpts. |
71
+ | `a2a_route` | Send a message to a team. | `context_id` continues a conversation; `async: true` delivers without waiting; fails over across candidates. |
72
+ | `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. |
73
+ | `a2a_probe` | Is the fleet healthy? | One verified-card fetch per peer (or one given url); `N reachable, M down`; misses classified `unreachable` vs `rejected`. |
74
+ | `a2a_team_join` | Declare one joined node a member of a team. | Allowlist-gated (`teamJoinAllowlist`: exact names or trailing-`*` prefixes; empty list denies every join); refusals carry guidance; attempts land in the state face's `registry.teamAudit`. |
75
+ | `a2a_team_leave` | Retract a team membership. | Always allowed — leaving only shrinks exposure. |
76
+ | `a2a_status` | What is this node doing right now? | Tracked peers with quality scores, in-flight routes, recent routing activity, and the idempotency window aggregate (occupancy + claim/replay/conflict counters). |
77
+
78
+ ## Canvas teams (arbitrary grouping, v0.5.25)
79
+
80
+ 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.
81
+
82
+ - Storage: `<dsh-home>/a2a/canvas.json` (ordered team entries; caps: 64 teams × 32 members)
83
+ - Control API: `POST /__dsh_a2a/canvas` with `action: create | remove | add-member | remove-member`
84
+ - 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
85
+ - `a2a_teams` lists canvas teams as local rows with member/live counts; `/__dsh_a2a/state` serves per-member `joined`/`live` flags
86
+
87
+ ## Native-teams bridge (node unification, P1)
88
+
89
+ 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`):
90
+
91
+ - **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.
92
+ - **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).
93
+
94
+ See `docs/native-teams-bridge.md` for the full mapping and scope notes.
95
+
96
+ ## Configuration
97
+
98
+ Row config overlays in the profile's patch layers; every key has a schema default.
99
+
100
+ | Key | Default | Purpose |
101
+ | --- | --- | --- |
102
+ | `announce` | `false` | Publish this node's agent card for peer discovery. |
103
+ | `peers` | `[]` | Seed base URLs; referred peers are learned from their cards. |
104
+ | `delegates` | `[]` | Zone delegations: `{ name, url, publicKey }` published on the card. |
105
+ | `team` | `'dsh'` | Team name this node exposes for direct routing. |
106
+ | `session` | `''` | Caller label; `''` derives a stable `dsh-host-<8hex>` per home. |
107
+ | `agentName` | `'DeepSeek Harness A2A node'` | Human-facing name on the card. |
108
+ | `apiKey` | `''` | `X-API-Key` sent on peer requests; non-empty also gates the control routes. |
109
+ | `sessionNodes` | `true` | Expose main sessions as joinable network nodes. |
110
+ | `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. |
111
+ | `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. |
112
+ | `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). |
113
+ | `wakePrewarmDelayMs` | `10000` | Idle delay between loader settlement and the first prewarm wake; `0` restores fire-at-settle. |
114
+ | `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. |
115
+ | `cardTtlMs` / `flushTimeoutMs` / `routeTimeoutMs` | see schema | Card lifetime and route timing budgets. |
116
+
117
+ Example — one announcing node with a seed:
118
+
119
+ ```yaml
120
+ - id: a2a
121
+ name: '@nelsonlongxiang/dsh-open-a2a-net'
122
+ config:
123
+ announce: true
124
+ peers: ['http://127.0.0.1:41243']
125
+ ```
126
+
127
+ ## Leave semantics (bounded staleness)
128
+
129
+ `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** — peer directories keep publishing the team until their card TTL expires, 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).
130
+
131
+ ## Trust model
132
+
133
+ Cards are Ed25519-signed and expire; `peers` referrals, `sessionTeams` listings, `lanIp`, and `version` on a card
134
+ are unsigned and read fresh — the last two exist so operators can audit which build every fleet node actually runs
135
+ without visiting each host. A referral URL whose own fetch just proved it serves this node's card is remembered,
136
+ and re-offers of it within a suppression window are ignored instead of flickering in and out of the peer store.
137
+ The `/__dsh_a2a/join` and `/__dsh_a2a/leave` control routes require the configured `apiKey`
138
+ (constant-time compare) when one is set; with an empty key they trust only same-origin browsers and loopback
139
+ callers. Set an `apiKey` before exposing the listener beyond loopback.
140
+
141
+ ## Stages (visualization surfaces)
142
+
143
+ Two full-page stages mount beside the model tools: `/__dsh_a2a_nexus` (the
144
+ A2A workbench — since 0.6.2 the 2D spatial planning board IS the stage and
145
+ the directory/ledger/activity/plan panels ride a collapsible side rail in
146
+ one merged, responsive shell; while the canvas face is down the panels fall
147
+ back to the main stack; the Three.js topology viewer was retired in 0.6.0;
148
+ `?mode=plan` still converges on the board) and `/__dsh_a2a_canvas`. A bare
149
+ mount path
150
+ redirects (301) onto its trailing-slash form, so both spellings render and
151
+ the shell's relative assets always resolve inside the served tree. For
152
+ offline development, `nexus-stage/scripts/mockhost.py [port]` serves the
153
+ built stage with canned state; open `/?fault=500` on it to exercise the
154
+ on-page fault badge. `pnpm verify` needs no manual bootstrap on a fresh
155
+ checkout: its `verify:nexus` step installs the subpackage dependencies
156
+ itself before building.
157
+
158
+ ## Collaboration SOP (network practice, v0.1)
159
+
160
+ Distilled from the 0.1.x–0.5.x delivery cycles by the network's research node; adopted by the maintainer.
161
+
162
+ - **Roles** — one session, one office, no crossing workspace boundaries: a research node (protocol/design
163
+ review, verdict criteria, source-line references with must-fix/recommend labels), a maintainer node
164
+ (version rulings, releases, adoption receipts), a test node (the single execution point for all live-host
165
+ verification; production hosts are never used for ad-hoc testing), and an install/ops node (production
166
+ profile deployment, single-owner, waits for the maintenance window).
167
+ - **Dispatch discipline** — test tasks go to the test node with version + commit + decidable criteria +
168
+ receipt target; review tasks go to the research node with source pointers, not paraphrases.
169
+ - **Receipt contract** — tasks longer than minutes route with `async: true` (wait:false); receipts read
170
+ `[A2A receipt] task <task_id> <outcome summary>`; the task id is caller-born, request-carried,
171
+ peer-echoed, and steered-header-transmitted — four-way agreement before a receipt correlates.
172
+ - **Upgrade chain** — test node green → maintainer receipt → ops node requests the window → production
173
+ upgrade → maintainer's browser-face final check → network-wide notice.
174
+ - **Reuse red lines** — shared pure logic extracts to a library; shared runtime capability extracts to a
175
+ Service; plugins never value-import each other; production hosts carry no verification duty (no
176
+ dev-who-self-tests).
177
+
178
+ ## Verify
179
+
180
+ Tests are layered by depth (contract: `docs/testing-layers.md`): each layer
181
+ has its own directory and script, so iteration can target one depth.
182
+
183
+ ```sh
184
+ pnpm install # auto-install-peers pulls the DSH peer train from npm
185
+ pnpm verify # typecheck + build + ALL vitest layers below + layout gate + nexus-stage suite
186
+ pnpm test:unit # L0 — pure logic, no fiber, no sockets (stores, codecs, jsdom components)
187
+ pnpm test:wire # L1 — ONE real plugin instance over real HTTP against faked peers
188
+ pnpm test:mesh # L2 — >=2 REAL nodes (own homes/keys/peers.json) over real HTTP: the
189
+ # cross-machine equivalent, incl. fault injection (clock-skew cards,
190
+ # tampered cards, partition/restart, alias collapse, zone delegation,
191
+ # referral freeze) and receipt replay — see the fault matrix in
192
+ # docs/testing-layers.md
193
+ pnpm test:live # L4 — REAL remote machines, env-gated (DSH_A2A_LIVE_MESH=...); skipped
194
+ # by default, never in CI — runbook in docs/testing-layers.md
195
+ pnpm build # host lib (including lib/a2a-client.js), client types, and browser bundle (lib/client.js)
196
+ ```
197
+
198
+ ## Non-DSH interop (protocol modules)
199
+
200
+ A node that is NOT a DeepSeek Harness host can still speak the protocol. The
201
+ four dependency-free protocol modules are exported subpaths (gh#13) — import
202
+ them without pulling the DSH peer train:
203
+
204
+ ```js
205
+ import { signCard, verifyCard } from '@nelsonlongxiang/dsh-open-a2a-net/card'
206
+ import { formatReceipt, parseReceipt } from '@nelsonlongxiang/dsh-open-a2a-net/receipt'
207
+ import { withinRouteBodyCap } from '@nelsonlongxiang/dsh-open-a2a-net/transport-caps'
208
+ import { A2aClient } from '@nelsonlongxiang/dsh-open-a2a-net/a2a-client'
209
+ ```
210
+
211
+ Implement your own signed card, `GET /.well-known/agent-card.json`, and
212
+ `POST /a2a/direct`. The wire shapes these modules encode are the contract.
213
+ Anonymous card fetches get the minimal discovery face. The full card needs a
214
+ verified OriginClaim envelope (`X-A2A-Caller` / `-Url` / `-Ts` / `-Sig`,
215
+ signed with your own card key); see `docs/protocol/delivery-origin-auth.md`
216
+ and the rest of `docs/protocol/` for the envelope details.
217
+
218
+ **Which artifact to build against.** The subpath exports landed in commit
219
+ `9cb3852`. Identify them by commit, not by version: 0.6.21 builds exist both
220
+ with and without them. The public npm package behind the badge above
221
+ (`latest`: 0.5.15) predates them, and it also predates the admission rules and
222
+ the OriginClaim envelope above. Importing these subpaths from it fails with
223
+ `ERR_PACKAGE_PATH_NOT_EXPORTED`. Stable releases go out through the
224
+ release gate (`docs/release-gate.md`), not to public npm. Until a release
225
+ carrying the subpaths reaches the registry you install from, build the
226
+ package from a pinned commit and depend on the tarball:
227
+
228
+ ```bash
229
+ git clone https://github.com/NelsonLongxiang/dsh-open-a2a-net && cd dsh-open-a2a-net
230
+ git checkout <commit> # pin one; any commit at or after 9cb3852 exports the subpaths
231
+ pnpm install --frozen-lockfile # pnpm version from package.json "packageManager"
232
+ pnpm build
233
+ npm pack # → nelsonlongxiang-dsh-open-a2a-net-<version>.tgz
234
+ # in your project:
235
+ npm install /path/to/nelsonlongxiang-dsh-open-a2a-net-<version>.tgz
236
+ ```
237
+
238
+ Installing that tarball pulls no `@deepseek-ai/*` package: the DSH peers are
239
+ optional and the four subpaths import only each other and `node:crypto`.
240
+
241
+ ## License
242
+
243
+ MIT