@nelsonlongxiang/dsh-open-a2a-net 0.5.14 → 0.6.24

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 (86) 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/group-store.js +6 -0
  19. package/lib/idempotency-store.js +291 -0
  20. package/lib/index.js +4217 -259
  21. package/lib/joined-store.js +11 -0
  22. package/lib/layout-store.js +155 -0
  23. package/lib/peer-store.js +98 -13
  24. package/lib/receipt-ladder.js +35 -0
  25. package/lib/receipt.js +102 -0
  26. package/lib/self-suppress.js +62 -0
  27. package/lib/session-transcript.js +150 -0
  28. package/lib/skill/SKILL.md +77 -0
  29. package/lib/skill/references/discovery.md +38 -0
  30. package/lib/skill/references/membership.md +50 -0
  31. package/lib/skill/references/pitfalls.md +42 -0
  32. package/lib/skill/references/routing.md +43 -0
  33. package/lib/skill-registration.js +52 -0
  34. package/lib/stage-mount.js +32 -0
  35. package/lib/task-ledger.js +540 -0
  36. package/lib/team-store.js +122 -0
  37. package/lib/teams-bridge.js +18 -0
  38. package/lib/transport-caps.js +31 -0
  39. package/lib/types/a2a-client.d.ts +72 -1
  40. package/lib/types/canvas-store.d.ts +102 -0
  41. package/lib/types/client/A2aControl.d.ts +18 -28
  42. package/lib/types/client/locales.d.ts +1 -1
  43. package/lib/types/group-store.d.ts +6 -0
  44. package/lib/types/idempotency-store.d.ts +164 -0
  45. package/lib/types/index.d.ts +210 -6
  46. package/lib/types/joined-store.d.ts +9 -0
  47. package/lib/types/layout-store.d.ts +63 -0
  48. package/lib/types/peer-store.d.ts +45 -2
  49. package/lib/types/receipt-ladder.d.ts +25 -0
  50. package/lib/types/receipt.d.ts +51 -0
  51. package/lib/types/self-suppress.d.ts +41 -0
  52. package/lib/types/session-transcript.d.ts +37 -0
  53. package/lib/types/skill-registration.d.ts +3 -0
  54. package/lib/types/stage-mount.d.ts +29 -0
  55. package/lib/types/task-ledger.d.ts +308 -0
  56. package/lib/types/team-store.d.ts +55 -0
  57. package/lib/types/teams-bridge.d.ts +112 -0
  58. package/lib/types/transport-caps.d.ts +29 -0
  59. package/lib/types/types.d.ts +43 -0
  60. package/nexus-stage/package-lock.json +2262 -0
  61. package/nexus-stage/package.json +21 -0
  62. package/nexus-stage/scripts/__pycache__/mockhost.cpython-312.pyc +0 -0
  63. package/nexus-stage/scripts/mockhost.py +253 -0
  64. package/nexus-stage/src/canvas-ops.ts +175 -0
  65. package/nexus-stage/src/canvas-wire.ts +156 -0
  66. package/nexus-stage/src/edges.ts +72 -0
  67. package/nexus-stage/src/fault.ts +56 -0
  68. package/nexus-stage/src/federation.ts +234 -0
  69. package/nexus-stage/src/layout-doc.ts +143 -0
  70. package/nexus-stage/src/layout-wire.ts +158 -0
  71. package/nexus-stage/src/main.ts +361 -0
  72. package/nexus-stage/src/planning-menus.ts +352 -0
  73. package/nexus-stage/src/planning-nodelist.ts +294 -0
  74. package/nexus-stage/src/planning-types.ts +111 -0
  75. package/nexus-stage/src/planning-view.ts +1313 -0
  76. package/nexus-stage/src/planning.css +280 -0
  77. package/nexus-stage/src/seat.ts +35 -0
  78. package/nexus-stage/src/stage-name.ts +13 -0
  79. package/nexus-stage/src/tokens.ts +32 -0
  80. package/nexus-stage/src/viewport.ts +83 -0
  81. package/nexus-stage/src/workbench.css +267 -0
  82. package/nexus-stage/src/workbench.ts +1530 -0
  83. package/nexus-stage/src/world.ts +509 -0
  84. package/nexus-stage/tsconfig.json +25 -0
  85. package/nexus-stage/vite.config.ts +16 -0
  86. package/package.json +83 -22
@@ -5,11 +5,45 @@
5
5
  * inbound direct routes into live agent sessions.
6
6
  * @module @nelsonlongxiang/dsh-open-a2a-net
7
7
  */
8
+ /**
9
+ * Fix 1 (能发即能收): resolve the caller's routable label for one routed
10
+ * dispatch. Priority: the joined session's node team > the exempt initiator
11
+ * face (the NODE team — bare, team-name routable) > the session label (not
12
+ * routable; the dispatch registers receiptless per fix 2).
13
+ */
14
+ export declare function resolveCallerLabel(input: {
15
+ joined: boolean;
16
+ initiatorFace: boolean;
17
+ nodeTeam: string;
18
+ joinedTeam?: string;
19
+ sessionLabel?: string;
20
+ }): string | undefined;
21
+ import { type ReceiptResolvedInfo } from './task-ledger.ts';
8
22
  import type { Context } from '@deepseek-ai/cordis';
9
23
  import s from '@deepseek-ai/schemastery';
24
+ declare module '@deepseek-ai/dsh-llm' {
25
+ interface MessageSourceMap {
26
+ 'a2a-relay': {
27
+ kind: 'a2a-relay';
28
+ } & import('@deepseek-ai/dsh-llm').ContextFormed & {
29
+ /** The sending peer's routable label (team or session), when known.
30
+ * ui-chat's relay context body renders "来自 X" + the full text from
31
+ * this field; without it the row falls back to an opaque body. */
32
+ senderSessionId?: string;
33
+ };
34
+ }
35
+ }
10
36
  export type * from './types.ts';
11
37
  export { A2aClient } from './a2a-client.ts';
12
38
  export type { A2aClientOptions, A2aFetch, A2aSchedule } from './a2a-client.ts';
39
+ export { NATIVE_TEAMS_A2A_FACE_KEY } from './teams-bridge.ts';
40
+ export type { NativeTeamsBridgeFace, TeamsBridgeResolveInfo, TeamsBridgeSubmitOutcome, TeamsBridgeSubmitRequest } from './teams-bridge.ts';
41
+ export type { ReceiptResolvedInfo } from './task-ledger.ts';
42
+ declare module '@deepseek-ai/cordis' {
43
+ interface Events {
44
+ 'a2a/receipt-resolved'(payload: ReceiptResolvedInfo): void;
45
+ }
46
+ }
13
47
  export declare const name = "a2a";
14
48
  export declare const inject: string[];
15
49
  /** One signed zone delegation published on this node's agent card. */
@@ -34,6 +68,8 @@ export interface Config {
34
68
  readonly session: string;
35
69
  /** Team name this node exposes for direct routing. */
36
70
  readonly team: string;
71
+ /** S4 fix 3: audit (default) or refuse host-side (agent-less) routing calls. */
72
+ readonly gateAgentLessCalls: 'audit' | 'refuse';
37
73
  /**
38
74
  * Publish this node's `/.well-known/agent-card.json` on the shared web
39
75
  * server (peer discovery). Requires the web server in the composition;
@@ -58,6 +94,30 @@ export interface Config {
58
94
  * fail closed).
59
95
  */
60
96
  readonly delegates: DelegateConfig[];
97
+ /**
98
+ * gh#9+gh#10 OriginClaim window (docs/protocol/delivery-origin-auth.md §4):
99
+ * while true (default), a delivery/log request with NO envelope still
100
+ * walks N3 admission and increments the legacyInbound warn metric; when
101
+ * the operator closes it (post fleet flip), an absent envelope rejects
102
+ * `untrusted-origin` — same code as a failed signature (M1: a
103
+ * self-asserted already-linked name must not pass on shape alone).
104
+ */
105
+ readonly originClaimWindow: boolean;
106
+ /**
107
+ * Override for the OriginClaim callerUrl anchor. Loopback-bound hosts
108
+ * behind a pocket tunnel must name the card URL peers can actually
109
+ * fetch (e.g. http://192.168.3.157:13080); empty uses the web server's
110
+ * own address.
111
+ */
112
+ readonly originClaimUrl: string;
113
+ /**
114
+ * gh#8 (N9) escape hatch: when true, team-name resolution also considers
115
+ * the referral-learned peer tail (the legacy whole-store basis) instead
116
+ * of the seeded/delegate subset only. Default false — a team name binds
117
+ * to hosts the operator linked; fleets relying on referral discovery for
118
+ * routing migrate by adding explicit seeds, then leave this off.
119
+ */
120
+ readonly referralTeamResolution: boolean;
61
121
  /**
62
122
  * Harness home slice for the node keypair and peer store; `''` (the
63
123
  * default) uses the process DSH home. The Ed25519 key lives at
@@ -81,14 +141,103 @@ export interface Config {
81
141
  */
82
142
  readonly sessionNodes: boolean;
83
143
  /**
84
- * Wake every cold joined session's agent when the plugin mounts (dev boxes
85
- * and always-on collaboration hosts that restart often: the network state
86
- * returns without opening each session). Each wake materializes a full
87
- * agent — log replay plus composed preset world — so the default stays
88
- * off for deployments that would rather pay on demand (wake-on-route and
89
- * the sidebar's wake button remain available).
144
+ * Prewarm every cold joined session's agent after the plugin mounts (dev
145
+ * boxes and always-on collaboration hosts that restart often: the network
146
+ * state returns without opening each session). Each wake materializes a
147
+ * full agent — a main-thread log replay — so the prewarm is deferred,
148
+ * foreground-yielding, and cancellable (see `wakePrewarmDelayMs` /
149
+ * `wakePrewarmQuietMs`); the default stays off for deployments that would
150
+ * rather pay on demand (wake-on-route and the sidebar's wake button remain
151
+ * available either way).
90
152
  */
91
153
  readonly wakeJoinedOnBoot: boolean;
154
+ /**
155
+ * Idle delay between loader settlement and the first boot prewarm wake:
156
+ * boot traffic gets a clear window before any log replay starts. `0`
157
+ * restores the old fire-at-settle behavior.
158
+ */
159
+ readonly wakePrewarmDelayMs: number;
160
+ /**
161
+ * Foreground quiet window: a wake/route demand inside this window (or any
162
+ * outbound route still in flight) postpones the next prewarm step. Panel
163
+ * polls never count. `0` disables the yield.
164
+ */
165
+ readonly wakePrewarmQuietMs: number;
166
+ /**
167
+ * Pause between consecutive boot prewarm wakes. Each wake replays a full
168
+ * session log — the zstd decode yields the event loop only every 500ms —
169
+ * so unbounded concurrent wakes of several huge logs starve every request
170
+ * for tens of seconds after a restart. The serial queue with this pause
171
+ * keeps the preheat while capping decode saturation, and wake-on-route
172
+ * never waits on this queue.
173
+ */
174
+ readonly wakeBootStaggerMs: number;
175
+ /**
176
+ * F8: continuous desired-state reconciliation. The boot prewarm is a
177
+ * one-shot queue; the reconciler re-derives the due set every tick and
178
+ * wakes one cold intent per pass — covering post-boot drift, failed
179
+ * wakes, and gateways that mounted late. Default on.
180
+ */
181
+ readonly wakeReconcile: boolean;
182
+ /** Cadence between reconciliation ticks (floored at 50ms). */
183
+ readonly wakeReconcileIntervalMs: number;
184
+ /** First-retry delay after a failed reconcile wake; doubles per attempt. */
185
+ readonly wakeReconcileBackoffBaseMs: number;
186
+ /** Backoff ceiling; corrupt-log rows park as needs-repair and never retry. */
187
+ readonly wakeReconcileMaxBackoffMs: number;
188
+ /**
189
+ * S4: joinable-team patterns for a2a_team_join — exact names or
190
+ * trailing-`*` prefixes. Empty list denies every join (default).
191
+ */
192
+ readonly teamJoinAllowlist: string[];
193
+ /** S3 (queued): team-scoped routing admission. Enforcement off by default. */
194
+ readonly teamScopeRouting: boolean;
195
+ /**
196
+ * S5 (owner directive 2026-10-20): information-plane scoping — the
197
+ * read-only fleet tools (a2a_teams / a2a_status / a2a_probe) disclose
198
+ * only teams joined by this host's nodes (the union of local roster
199
+ * declarations). [this host] rows stay visible; a2a_tasks (this host's
200
+ * own ledger) stays unscoped. Set false for the open plane.
201
+ */
202
+ readonly teamScopeInfo: boolean;
203
+ /**
204
+ * Peer boundary model (docs/design/peer-boundary-model.md) — connection
205
+ * layer: an empty list keeps the historical open-referral posture; a
206
+ * non-empty list admits a referral only when its host matches an entry
207
+ * (exact host[:port], bare domain suffix, or full URL). Seeds from
208
+ * `peers` are owner-configured and unaffected.
209
+ */
210
+ readonly peerAllowlist: string[];
211
+ /**
212
+ * Peer boundary model — connection layer: when false, referral URLs are
213
+ * neither learned nor offered; the tracked set stays the configured seed
214
+ * set for the lifetime of the config that named them.
215
+ */
216
+ readonly referralLearning: boolean;
217
+ /**
218
+ * v0.5.23 (async-stall): see the schema comment on asyncNudgeDelayMs.
219
+ */
220
+ readonly asyncNudgeDelayMs: number;
221
+ /**
222
+ * How long the state route serves the cold-row id set from cache before
223
+ * re-enumerating the persistence layer. The panel polls every 2s; the
224
+ * default keeps one full enumeration per 5s instead of one per poll.
225
+ */
226
+ readonly stateColdRowsTtlMs: number;
227
+ /**
228
+ * Serve window for a verified peer card from the shared cache. Cards are
229
+ * signed for days and re-signed at TTL/4, so 60s is semantically safe; a
230
+ * peer that restarts with a fresh card is picked up on the next window.
231
+ */
232
+ readonly cardCacheTtlMs: number;
233
+ /** Shorter window for unreachable/invalid cards, so a revived peer reappears quickly. */
234
+ readonly cardCacheNegativeTtlMs: number;
235
+ /**
236
+ * Serve window for the panel\x27s remote rows. The refresh itself rides the
237
+ * shared card cache, so this is pure sweep cadence: how often real
238
+ * network activity happens while the panel polls every 2s.
239
+ */
240
+ readonly remoteRowsTtlMs: number;
92
241
  /**
93
242
  * `final` reply budget: how long an inbound direct route waits for the
94
243
  * session's next assistant message before answering with a timeout
@@ -101,6 +250,32 @@ export interface Config {
101
250
  * remote session's reply (default 30 minutes).
102
251
  */
103
252
  readonly routeTimeoutMs: number;
253
+ /**
254
+ * Age at which an unsettled async task auto-dead-letters (exits the owed
255
+ * view, disarms the nudge, still settles on a late receipt). Default
256
+ * 24h; the mesh anomaly suite injects sub-second windows to exercise the
257
+ * dead-letter → revival lifecycle without wall-clock waits.
258
+ */
259
+ readonly taskStaleTtlMs: number;
260
+ /**
261
+ * Opt-in native-teams inbound bridge: when composed with
262
+ * `@nelsonlongxiang/dsh-native-teams`, a routed team name that its
263
+ * registry classifies as an unambiguous local claim dispatches through
264
+ * its authoritative routing seam (`/a2a/direct`, the outbound A2A tools,
265
+ * and the directory listing). Off by default — exposing every registered
266
+ * team to the network is an operator decision (exposure-grants
267
+ * governance: grants are deliberate, never ambient). Dispatcher-level
268
+ * only: inbound callers address the team, never its individual members
269
+ * (members stay visible-not-addressable).
270
+ */
271
+ readonly nativeTeamsInbound?: boolean;
272
+ /**
273
+ * Reply-wait budget for one native-teams round (the bridge's answer
274
+ * deadline, mirroring the steer path's 180s deadline). A round still
275
+ * running past it answers the honest delivered-unsettled shape instead
276
+ * of parking the caller; the round itself keeps going.
277
+ */
278
+ readonly nativeRoundWaitMs?: number;
104
279
  }
105
280
  /** Schemastery configuration for the A2A client plugin row. */
106
281
  export declare const Config: s<Config>;
@@ -110,4 +285,33 @@ export declare const Config: s<Config>;
110
285
  * @param ctx - registrant context carrying the tool registry and timer.
111
286
  * @param config - deployment's A2A node facts.
112
287
  */
288
+ /**
289
+ * gh#9 control-face leniency audit (docs/protocol/delivery-origin-auth.md
290
+ * §10): a keyless host that runs session nodes rides loopback leniency for
291
+ * every control write — on a host that grants sessions a shell that is a
292
+ * speed bump, not authentication. Pure logic so the boot warning is
293
+ * testable without a mount.
294
+ * @param apiKey - the configured API key ('' disables header auth).
295
+ * @param sessionNodes - whether session nodes are enabled.
296
+ * @returns the warning to log, or undefined.
297
+ */
298
+ export declare function controlFaceLeniencyWarning(apiKey: string, sessionNodes: boolean): string | undefined;
299
+ /**
300
+ * Exposure audit for unauthenticated direct deliveries (defect card F4, the
301
+ * zero-risk slice). `/a2a/direct` carries no per-call caller identity yet
302
+ * (docs/protocol/delivery-origin-auth.md stays best-effort until the
303
+ * OriginClaim envelope lands), so with an empty `apiKey` every process that
304
+ * can reach this host's ports can steer every joined session. A node that
305
+ * only talks to loopback peers (same-host collaboration) is not exposed;
306
+ * one seeded with non-loopback peers is, and the operator should either set
307
+ * `apiKey` or accept the exposure deliberately.
308
+ * @param peers - the configured seed peer URLs.
309
+ * @param apiKey - the configured API key ('' disables header auth).
310
+ * @returns the non-loopback seed peers and, when they coincide with an
311
+ * empty key, the boot warning to log.
312
+ */
313
+ export declare function directDeliveryExposure(peers: readonly string[], apiKey: string): {
314
+ nonLoopbackPeers: string[];
315
+ warning: string | undefined;
316
+ };
113
317
  export declare function apply(ctx: Context, config: Config): void;
@@ -54,6 +54,15 @@ export declare class JoinedSessions {
54
54
  * @param id - the session id whose intent is removed.
55
55
  */
56
56
  remove(id: string): void;
57
+ /**
58
+ * F7 (wake-intent self-heal): re-read the persisted snapshot into memory.
59
+ * A boot-time transient (e.g. the workspace registry briefly reporting a
60
+ * session archived) can prune the in-memory intent while the file still
61
+ * holds it — the file is the durable source of the user's gesture, so the
62
+ * reload restores memory to match it. Callers use this as a one-shot
63
+ * fallback when a wake finds no in-memory match.
64
+ */
65
+ reload(): void;
57
66
  /** Load a persisted snapshot on construction, if present. */
58
67
  private restore;
59
68
  /** Persist the intent set (no-op when no path was configured). */
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Persisted spatial layout for the infinite-canvas page ('a2a/canvas-layout.json').
3
+ * Pure presentation state: node coordinates, team-frame rectangles, and the
4
+ * viewport transform. Membership truth stays in canvas.json - this store may
5
+ * be reset freely without touching who sits in which team.
6
+ *
7
+ * Unit semantics (contract note 6, 2026-08-28 ruling): {x,y} are 2D
8
+ * planning-canvas card centers in pixels. Other consumers (the 3D
9
+ * observation lens) must reproject into their own envelope, never consume
10
+ * the values as absolute scene coordinates.
11
+ * @module @nelsonlongxiang/dsh-open-a2a-net/layout-store
12
+ */
13
+ /** One draggable point in world space. */
14
+ export interface LayoutPoint {
15
+ readonly x: number;
16
+ readonly y: number;
17
+ }
18
+ /** One team-frame rectangle in world space. */
19
+ export interface LayoutRect extends LayoutPoint {
20
+ readonly w: number;
21
+ readonly h: number;
22
+ }
23
+ /** The viewport transform (world point at the top-left corner, plus zoom). */
24
+ export interface LayoutViewport extends LayoutPoint {
25
+ readonly scale: number;
26
+ }
27
+ /** The whole persisted document. */
28
+ export interface LayoutSnapshot {
29
+ readonly version: 1;
30
+ readonly viewport: LayoutViewport;
31
+ readonly nodes: Readonly<Record<string, LayoutPoint>>;
32
+ readonly frames: Readonly<Record<string, LayoutRect>>;
33
+ }
34
+ /** World-coordinate magnitude bound: values beyond are clamped, never trusted. */
35
+ export declare const COORD_LIMIT = 1000000;
36
+ /** Zoom bounds, matching the page's wheel clamps. */
37
+ export declare const SCALE_MIN = 0.25;
38
+ export declare const SCALE_MAX = 3;
39
+ /** Document shape caps: a layout taxonomy, not a database. */
40
+ export declare const LAYOUT_NODE_CAP = 256;
41
+ export declare const LAYOUT_FRAME_CAP = 96;
42
+ /**
43
+ * Whole-document layout store with last-write-wins persistence. Accepts
44
+ * anything the client sends and answers with the normalized truth: clients
45
+ * round-trip GET after save when they need canonical numbers.
46
+ */
47
+ export declare class LayoutStore {
48
+ private doc;
49
+ private readonly path;
50
+ /** @param path - the snapshot file; an empty path keeps memory-only. */
51
+ constructor(path: string);
52
+ private restore;
53
+ private persist;
54
+ /** The current snapshot, or null before the first save. */
55
+ get(): LayoutSnapshot | null;
56
+ /**
57
+ * Replace the document wholesale after normalization.
58
+ * @returns false when the payload is not a version-1 layout document.
59
+ */
60
+ save(raw: unknown): boolean;
61
+ /** Drop all persisted presentation state. */
62
+ reset(): void;
63
+ }
@@ -23,6 +23,8 @@ export interface PeerRecord {
23
23
  readonly url: string;
24
24
  score: number;
25
25
  readonly seed: boolean;
26
+ /** The signed-card session the peer served last, when a fetch revealed it. */
27
+ readonly session?: string;
26
28
  }
27
29
  /** The persisted store document (whole-file read/write, small by design). */
28
30
  export interface PeerStoreSnapshot {
@@ -37,19 +39,27 @@ export interface PeerStoreSnapshot {
37
39
  export declare class PeerStore {
38
40
  private readonly path;
39
41
  private readonly cap;
42
+ private readonly persistDebounceMs;
40
43
  private readonly peers;
41
44
  /**
42
45
  * @param seeds - the configured seed URLs; always retained (never evicted).
43
46
  * @param path - persistence file (`<dsh-home>/a2a/peers.json`); empty = no persistence.
44
47
  * @param cap - peer bound, seeds included (default {@link PEER_CAP}).
45
48
  */
46
- constructor(seeds: readonly string[], path: string, cap?: number);
49
+ constructor(seeds: readonly string[], path: string, cap?: number, persistDebounceMs?: number);
47
50
  /**
48
51
  * Every tracked peer URL: seeds first (in first-tracked order), then
49
52
  * offered peers by score descending (equal scores keep first-tracked order).
50
53
  * @returns the ordered peer URLs.
51
54
  */
52
55
  list(): readonly string[];
56
+ /**
57
+ * The operator-established subset (gh#8 N9): URLs that came from the
58
+ * config seed set — never the referral-learned tail. Team-name resolution
59
+ * rides this basis so a self-naming host nobody linked cannot capture
60
+ * routes; probing and visibility keep the full list.
61
+ */
62
+ listSeeded(): readonly string[];
53
63
  /** Whether the store has room for one more peer. */
54
64
  private hasRoom;
55
65
  /**
@@ -84,8 +94,41 @@ export declare class PeerStore {
84
94
  * @returns the score, or undefined when the peer is not tracked.
85
95
  */
86
96
  score(url: string): number | undefined;
97
+ /**
98
+ * Record the signed-card session a tracked peer served. The session is the
99
+ * node identity (the same fact the self-referral guard judges), so learning
100
+ * it per URL is what lets the caller collapse alias spellings of one node
101
+ * (loopback vs LAN IP) into a single tracked peer.
102
+ * @param url - the tracked peer the card came from.
103
+ * @param session - the verified card's session label; empty is ignored.
104
+ */
105
+ learnSession(url: string, session: string): void;
106
+ /**
107
+ * The strongest tracked URL known to serve one node identity — seeds beat
108
+ * referrals, then the higher score wins (ties keep first-tracked order).
109
+ * @param session - the signed-card session to look up.
110
+ * @returns the winning peer URL, or undefined when none is tracked.
111
+ */
112
+ bySession(session: string): string | undefined;
87
113
  /** Load a persisted snapshot on construction, if present. */
88
114
  private restore;
89
- /** Persist the current peer set (no-op when no path was configured). */
115
+ /**
116
+ * Persist the current peer set (no-op when no path was configured).
117
+ * Writes are debounced and asynchronous: a sweep that settles dozens of
118
+ * fetch outcomes must not fire one synchronous writeFileSync per change
119
+ * (each blocks the event loop for milliseconds on Windows). The dirty
120
+ * flag coalesces the whole window into one async write; `flush()` lets
121
+ * tests and teardown await the final state on disk. A crash inside the
122
+ * window loses at most `persistDebounceMs` of score deltas — seeds are
123
+ * re-seeded from config on every boot, and scores rebuild on contact.
124
+ */
125
+ private dirty;
126
+ private writeChain;
127
+ private pendingTimer;
90
128
  private persist;
129
+ /**
130
+ * Await the last scheduled write (tests and teardown). Resolves when the
131
+ * debounced write chain has settled with the current in-memory state.
132
+ */
133
+ flush(): Promise<void>;
91
134
  }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Receipt delivery three-tier hard order (work-order P2 / B1 residual):
3
+ * caller lane first; if it fails, escalate ONCE to the owner mailbox so the
4
+ * outcome still lands somewhere answerable on this host; total failure stays
5
+ * a breadcrumb (correlation & archive remain the authoritative truth).
6
+ *
7
+ * Extracted pure so the ladder is unit-drivable without live agents.
8
+ * @module @nelsonlongxiang/dsh-open-a2a-net/receipt-ladder
9
+ */
10
+ /** Everything the ladder needs from its host — all seams, zero globals. */
11
+ export interface ReceiptLadderDeps {
12
+ /** Try delivering the receipt text to one routable team. */
13
+ deliver: (team: string) => Promise<unknown>;
14
+ /** This host's process/team mailbox (tier two). */
15
+ ownerTeam: string;
16
+ /** Observability hook: `stage` distinguishes `to <team>` / `to owner <team>` / `both lanes`. */
17
+ log: (stage: string, error: unknown) => void;
18
+ }
19
+ /**
20
+ * Attempt delivery through the ladder.
21
+ * @param callerTeam - tier-one target (`callbackTarget`). When it already IS
22
+ * the owner team, a failure terminates after tier one — escalating to
23
+ * yourself would just double the noise.
24
+ */
25
+ export declare function runReceiptLadder(deps: ReceiptLadderDeps, callerTeam: string): Promise<void>;
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Receipt text codec — single home for the correlation pattern and the v2
3
+ * structured envelope (receipt-envelope-v2.md). The header line
4
+ * `[A2A receipt] task <id> …` stays byte-identical to the network's original
5
+ * contract; the machine payload rides EXACTLY ONE following line that must be
6
+ * a complete JSON object containing at least one known key. Everything after
7
+ * it is human continuation and every consumer ignores unknown keys.
8
+ *
9
+ * Discriminator over a bare `{`: an object qualifies as an envelope only when
10
+ * one of the known keys is present, so legacy prose summaries that happen to
11
+ * open with a brace can never be mistaken for machine payloads.
12
+ * @module @nelsonlongxiang/dsh-open-a2a-net/receipt
13
+ */
14
+ /** The network's fixed, case-sensitive correlation template. */
15
+ export declare const RECEIPT_PATTERN: RegExp;
16
+ /** Controlled outcome vocabulary — the ONLY branchable field of an envelope. */
17
+ export declare const RECEIPT_OUTCOMES: readonly ["completed", "failed", "abandoned", "expired", "rejected", "sync_completed", "unknown"];
18
+ export type ReceiptOutcomeV2 = (typeof RECEIPT_OUTCOMES)[number];
19
+ /** Envelope fields this codec extracts; anything else rides through ignored (append-only). */
20
+ export interface ReceiptEnvelopeV2Fields {
21
+ outcome?: ReceiptOutcomeV2;
22
+ idempotencyKey?: string;
23
+ sessionKey?: string;
24
+ elapsedMs?: number;
25
+ }
26
+ export interface ParsedReceipt {
27
+ /** Correlation key from the immutable header. */
28
+ taskId: string;
29
+ /**
30
+ * Raw post-id text. When a machine line was recognized this is ONLY the
31
+ * human summary line (continuation prose excluded); without one it is the
32
+ * legacy multi-line remainder, byte-for-byte.
33
+ */
34
+ summary: string;
35
+ /** Present when the second line carried a recognized envelope. */
36
+ envelope?: ReceiptEnvelopeV2Fields;
37
+ }
38
+ /**
39
+ * Parse one inbound message against the receipt contract.
40
+ * @returns `null` when the message is not a receipt at all; otherwise the
41
+ * correlation key with its summary and, when present, the v2 envelope.
42
+ */
43
+ export declare function parseReceipt(message: string): ParsedReceipt | null;
44
+ /**
45
+ * Compose a receipt text: the immutable header (+ human summary) followed,
46
+ * when any envelope field is supplied, by exactly one complete JSON line.
47
+ * @param taskId - correlation key echoed in the header.
48
+ * @param summary - human-readable outcome tail on the header line.
49
+ * @param fields - structured projection; omit entirely for a pure v1 receipt.
50
+ */
51
+ export declare function formatReceipt(taskId: string, summary: string, fields?: ReceiptEnvelopeV2Fields): string;
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Unmasked-identity suppression: decentralized gossip mirrors peer lists back
3
+ * and forth, so every alias of this node's own address comes home as an
4
+ * inbound referral within minutes of being dropped. Fetching one is the
5
+ * identity check that drops it (the fetched card carries our session id),
6
+ * but the next inbound referral re-offers the same URL seconds later, and
7
+ * the peer store flickers between present and dropped forever.
8
+ *
9
+ * The same flicker plagues alias spellings of OTHER tracked nodes: a peer
10
+ * reached as both loopback and LAN IP drops one spelling on the alias
11
+ * collapse, and the next mirrored referral re-offers it. This filter
12
+ * therefore remembers each URL whose fetch revealed a settled identity —
13
+ * self or an already-tracked peer — for a window far beyond any realistic
14
+ * offer cadence: offers inside the window are refused without a network
15
+ * round-trip. Shape-agnostic on purpose — alias spellings never seen before
16
+ * (localhost vs 127.0.0.1 vs a hostname) suppress naturally once their first
17
+ * fetch unmasks them.
18
+ * @module @nelsonlongxiang/dsh-open-a2a-net/self-suppress
19
+ */
20
+ /** How long an identity-revealing URL stays suppressed after its unmasking fetch. */
21
+ export declare const SELF_REFERRAL_SUPPRESS_MS: number;
22
+ /** Bounded memory of URLs whose recent fetch proved they serve a known identity. */
23
+ export declare class SelfReferralFilter {
24
+ private readonly remembered;
25
+ private readonly now;
26
+ /** @param now - injectable clock for deterministic tests. */
27
+ constructor(now?: () => number);
28
+ /**
29
+ * Record that fetching `url` served this node's own card. Expired entries
30
+ * prune first, so the map stays bounded by the live offer cadence.
31
+ */
32
+ remember(url: string): void;
33
+ /**
34
+ * Whether one inbound referral is still worth offering to the store.
35
+ * A URL unmasked as self inside the suppression window refuses without a
36
+ * fetch; every other URL passes through unchanged.
37
+ */
38
+ shouldOffer(url: string): boolean;
39
+ /** Drop entries past the window (called from the mutating entry points). */
40
+ private prune;
41
+ }
@@ -0,0 +1,37 @@
1
+ /** One human-readable transcript entry. */
2
+ export interface TranscriptEntry {
3
+ role: 'user' | 'assistant';
4
+ text: string;
5
+ time?: number;
6
+ }
7
+ /** Hard caps so a pathological log cannot balloon memory or the response. */
8
+ export declare const TRANSCRIPT_MAX_FILE_BYTES: number;
9
+ export declare const TRANSCRIPT_MAX_DECOMPRESSED_BYTES: number;
10
+ export declare const TRANSCRIPT_ENTRY_TEXT_CAP = 500;
11
+ export declare const TRANSCRIPT_DEFAULT_LIMIT = 20;
12
+ export declare const TRANSCRIPT_MAX_LIMIT = 100;
13
+ /** Byte range of one structurally complete zstd frame. */
14
+ interface FrameRange {
15
+ start: number;
16
+ end: number;
17
+ }
18
+ /**
19
+ * Walk concatenated zstd frames structurally (no block decoding) and return
20
+ * their byte ranges. Malformed structure rejects; EOF inside a final frame is
21
+ * dropped from the result (a torn tail frame decodes to nothing usable).
22
+ */
23
+ export declare function scanZstdFrames(buffer: Buffer): FrameRange[];
24
+ /**
25
+ * Decode a session log artifact to text. Plain .jsonl passes through; .zstd
26
+ * artifacts are split into frames and decoded frame by frame (node's public
27
+ * decoder is single-frame).
28
+ */
29
+ export declare function decodeSessionLog(buffer: Buffer): string;
30
+ /**
31
+ * Extract the last `limit` human-visible turns from a decoded session log.
32
+ * User lines come from user/message data.content[].text; assistant lines from
33
+ * assistant/message data.message.content[] segments of type "text" (reasoning
34
+ * and tool calls are working detail, not transcript).
35
+ */
36
+ export declare function extractTranscript(logText: string, limit?: number): TranscriptEntry[];
37
+ export {};
@@ -0,0 +1,3 @@
1
+ import type { Context } from '@deepseek-ai/cordis';
2
+ /** Register the a2a-network skill (runtime contribution). */
3
+ export declare function registerA2aNetworkSkill(ctx: Context): void;
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Stage-mount pathname resolution: both static stages (canvas office floor,
3
+ * Three.js nexus viewer) serve one built Vite tree behind a mount prefix,
4
+ * and both used to answer the bare `/mount` form directly with index.html.
5
+ * That shell then resolved its `./assets/…` script reference against `/`,
6
+ * fetched `/assets/index-*.js` from the web root — a 404 — and rendered a
7
+ * silent black shell (`main.ts` returns early when state fetches fail). The
8
+ * fix is the directory-classic one: a bare mount request redirects to
9
+ * `mount/`, every other relative resolution then lands inside the tree.
10
+ * @module @nelsonlongxiang/dsh-open-a2a-net/stage-mount
11
+ */
12
+ /** Outcome of resolving one request pathname against a stage mount. */
13
+ export interface StageResolution {
14
+ /** `301` target — present exactly when the client addressed the bare mount. */
15
+ readonly redirectTo?: string;
16
+ /**
17
+ * Mount-stripped file path, still percent-encoded and un-decoded, ready
18
+ * for the handler's traversal guard and decode. `'/index.html'` when the
19
+ * client addressed the mount itself.
20
+ */
21
+ readonly rel: string;
22
+ }
23
+ /**
24
+ * Resolve one request pathname against a stage mount prefix.
25
+ * @param pathname - `req.url` verbatim (query string allowed, split here).
26
+ * @param mount - the registered mount prefix, e.g. `/__dsh_a2a_nexus`.
27
+ * @returns the redirect target plus the within-tree relative path.
28
+ */
29
+ export declare function resolveStageMount(pathname: string, mount: string): StageResolution;