super-dsh 0.1.6 → 0.1.9

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 (87) hide show
  1. package/client/index.js +741 -256
  2. package/cordis.patch.yml +16 -0
  3. package/package.json +7 -2
  4. package/superd-hub/dist/a2a-delivery.d.ts +189 -0
  5. package/superd-hub/dist/a2a-delivery.js +240 -0
  6. package/superd-hub/dist/a2a-host.d.ts +76 -0
  7. package/superd-hub/dist/a2a-host.js +145 -0
  8. package/superd-hub/dist/a2a-ingest.d.ts +31 -0
  9. package/superd-hub/dist/a2a-ingest.js +636 -0
  10. package/superd-hub/dist/a2a-log.d.ts +17 -0
  11. package/superd-hub/dist/a2a-log.js +42 -0
  12. package/superd-hub/dist/a2a-row.d.ts +65 -0
  13. package/superd-hub/dist/a2a-row.js +162 -0
  14. package/superd-hub/dist/a2a-service.d.ts +357 -0
  15. package/superd-hub/dist/a2a-service.js +980 -0
  16. package/superd-hub/dist/a2a-wire.d.ts +220 -0
  17. package/superd-hub/dist/a2a-wire.js +305 -0
  18. package/superd-hub/dist/agent-ids.d.ts +10 -0
  19. package/superd-hub/dist/agent-ids.js +68 -0
  20. package/superd-hub/dist/agent-roster.d.ts +29 -18
  21. package/superd-hub/dist/agent-roster.js +54 -15
  22. package/superd-hub/dist/agent-status.d.ts +119 -0
  23. package/superd-hub/dist/agent-status.js +386 -0
  24. package/superd-hub/dist/client/mesh-api.d.ts +134 -0
  25. package/superd-hub/dist/client/mesh-api.js +219 -0
  26. package/superd-hub/dist/client/mesh-groups.d.ts +63 -0
  27. package/superd-hub/dist/client/mesh-groups.js +66 -0
  28. package/superd-hub/dist/client/roster.d.ts +48 -0
  29. package/superd-hub/dist/client/roster.js +96 -0
  30. package/superd-hub/dist/exec-pty-channel.d.ts +58 -0
  31. package/superd-hub/dist/exec-pty-channel.js +162 -0
  32. package/superd-hub/dist/exec-pty-framing.d.ts +32 -0
  33. package/superd-hub/dist/exec-pty-framing.js +81 -0
  34. package/superd-hub/dist/exec-pty-rpc.d.ts +21 -0
  35. package/superd-hub/dist/exec-pty-rpc.js +128 -0
  36. package/superd-hub/dist/exec-pty-session.d.ts +76 -0
  37. package/superd-hub/dist/exec-pty-session.js +234 -0
  38. package/superd-hub/dist/exec.d.ts +56 -0
  39. package/superd-hub/dist/exec.js +225 -0
  40. package/superd-hub/dist/gateway.d.ts +39 -2
  41. package/superd-hub/dist/gateway.js +62 -3
  42. package/superd-hub/dist/hub-tool.d.ts +174 -0
  43. package/superd-hub/dist/hub-tool.js +464 -0
  44. package/superd-hub/dist/index.d.ts +15 -1
  45. package/superd-hub/dist/index.js +16 -0
  46. package/superd-hub/dist/machines.d.ts +45 -0
  47. package/superd-hub/dist/machines.js +125 -0
  48. package/superd-hub/dist/mesh-federate.d.ts +138 -0
  49. package/superd-hub/dist/mesh-federate.js +223 -0
  50. package/superd-hub/dist/mesh-gateway.d.ts +67 -0
  51. package/superd-hub/dist/mesh-gateway.js +408 -0
  52. package/superd-hub/dist/mesh-link.d.ts +274 -0
  53. package/superd-hub/dist/mesh-link.js +441 -0
  54. package/superd-hub/dist/mesh-rpc.d.ts +123 -0
  55. package/superd-hub/dist/mesh-rpc.js +250 -0
  56. package/superd-hub/dist/mux-client.d.ts +71 -0
  57. package/superd-hub/dist/mux-client.js +281 -0
  58. package/superd-hub/dist/pack-version.d.ts +2 -0
  59. package/superd-hub/dist/pack-version.js +30 -0
  60. package/superd-hub/dist/roster.d.ts +6 -0
  61. package/superd-hub/dist/rpc.d.ts +18 -2
  62. package/superd-hub/dist/rpc.js +34 -4
  63. package/superd-hub/dist/spawn-world.js +3 -1
  64. package/superd-hub/dist/switch-core.d.ts +62 -25
  65. package/superd-hub/dist/switch-core.js +53 -30
  66. package/superd-hub/dist/system-head.d.ts +22 -12
  67. package/superd-hub/dist/system-head.js +100 -84
  68. package/superd-hub/dist/wire-endpoints.d.ts +264 -0
  69. package/superd-hub/dist/wire-endpoints.js +180 -0
  70. package/superd-hub/lib/client/index.js +741 -256
  71. package/superd-hub/package.json +11 -2
  72. package/agent-agy/dist/agent-preset-projection.d.ts +0 -31
  73. package/agent-agy/dist/agent-preset-projection.js +0 -33
  74. package/agent-claude/dist/agent-preset-projection.js +0 -30
  75. package/agent-codex/dist/agent-preset-projection.js +0 -37
  76. package/agent-codex/dist/app-home.js +0 -65
  77. package/agent-codex/dist/spike.js +0 -46
  78. package/agent-hermes/dist/agent-preset-projection.js +0 -34
  79. package/agent-omp/dist/agent-preset-projection.js +0 -36
  80. package/agent-omp/dist/app-home.js +0 -86
  81. package/agent-pi/dist/agent-preset-projection.js +0 -29
  82. package/superd-hub/dist/client-shim.d.ts +0 -28
  83. package/superd-hub/dist/client-shim.js +0 -251
  84. package/superd-hub/dist/index-pass.d.ts +0 -22
  85. package/superd-hub/dist/index-pass.js +0 -47
  86. package/superd-hub/dist/labels.d.ts +0 -45
  87. package/superd-hub/dist/labels.js +0 -83
@@ -0,0 +1,42 @@
1
+ // Operational journal for the A2A/mesh surface (0.1.7-b headliner; the
2
+ // 2026-09-29 first-person field pass found the hub emits ZERO journal lines
3
+ // — every field question forced registry forensics). One line per machine
4
+ // event, greppable `a2a.<event>` vocabulary, no secrets (messageIds and
5
+ // session/conversation ids only — never envelope bodies, never cookies).
6
+ //
7
+ // The pure modules (a2a-service/a2a-ingest) stay runtime-agnostic: they take
8
+ // an optional A2aLogFn and only FORMAT lines; the cordis host (a2a-row.ts)
9
+ // wires it onto ctx.logger so lines land in the unit journal (4999 rig /
10
+ // dev3 dsh.service). Tests inject a collector.
11
+ /**
12
+ * Render one journal-friendly line: `superd-hub: a2a.<event> k=v k=v`.
13
+ * Values are quoted (JSON) only when they contain whitespace, so the common
14
+ * case stays grep-friendly: `a2a.completion.transcript conversation=abc123 bytes=42`.
15
+ */
16
+ export function formatA2aLogLine(event, fields = {}) {
17
+ const parts = [`superd-hub: a2a.${event}`];
18
+ for (const [key, value] of Object.entries(fields)) {
19
+ if (value === undefined)
20
+ continue;
21
+ const text = String(value);
22
+ parts.push(`${key}=${/\s/.test(text) ? JSON.stringify(text) : text}`);
23
+ }
24
+ return parts.join(' ');
25
+ }
26
+ /**
27
+ * The host wiring both rows share: level → process console. NOT ctx.logger —
28
+ * the pack composes no cordis logger-console exporter, so LoggerService
29
+ * records go nowhere (verified live 2026-09-29); console matches the
30
+ * world-join / world-provision precedent and lands in every unit log.
31
+ */
32
+ export function consoleA2aLog() {
33
+ return (level, event, fields) => {
34
+ const line = formatA2aLogLine(event, fields);
35
+ if (level === 'warn')
36
+ console.warn(line);
37
+ else if (level === 'error')
38
+ console.error(line);
39
+ else
40
+ console.log(line);
41
+ };
42
+ }
@@ -0,0 +1,65 @@
1
+ /**
2
+ * `super-dsh/a2a` — the SuperD Hub TOOL row (mesh P3 Task 4, spec §9):
3
+ * composes the single model-facing `superd-hub` tool onto ctx0's `tools`
4
+ * service and closes T3's `nativeGateway` seam with the hub process's OWN
5
+ * in-process session-plane gateway.
6
+ *
7
+ * WHY A SUBPATH ROW (documented ruling, not riding the bare `superd-hub`
8
+ * row): world trees compose the `superd-hub` row too (world-mount.ts
9
+ * `WORLD_HUB_ROW`) and they DO provide a `tools` service (the base web-app
10
+ * composition's `tools` row is not in the wave-1/wave-2 disable lists) — so
11
+ * riding gateway.ts's apply would register a mis-wired A2A tool inside every
12
+ * world (its `nativeGateway` would be THAT WORLD's typertGateway, while the
13
+ * 'native' a6 belongs to ctx0's agent). The pack's precedent for an
14
+ * explicitly-composed capability is the subpath row (web-trust,
15
+ * fusion-transport — "composing this row IS the switch"): this row lives
16
+ * only in the pack's own cordis.patch.yml, which world profiles never
17
+ * compose (world-provision.ts `WORLD_BASE_BUNDLES`), so it mounts in ctx0
18
+ * only — exactly the P3 initiator surface (native DSH sessions; foreign
19
+ * runtimes get their own injection in P5, spec §10). Individually
20
+ * toggleable per the pack's row discipline.
21
+ *
22
+ * Dormancy (better-dash plugin.ts discipline): zero static injects; the
23
+ * dynamic `ctx.inject(['tools', 'typertGateway'])` keeps the row dormant —
24
+ * and side-effect-free — until BOTH the tool registry and ctx0's session
25
+ * plane exist. `tools.register` scopes the registration to the calling
26
+ * fiber, so the tool unregisters with the row.
27
+ *
28
+ * The stream-face bridge: T2's `A2aGateway` declares a 3-arg
29
+ * `openWireStream(endpoint, payload, signal)`, but a RAW typertGateway
30
+ * instance speaks the upstream canonical 6-arg order
31
+ * `openWireStream(endpoint, payload, uplink, peer, signal, control)`
32
+ * (dsh-api-gateway/src/index.ts:434; the 0.1.3-j lesson — positional
33
+ * misbinding strands streams forever). This row hands the RAW ctx0
34
+ * instance to `createA2aHost`. NOTE: T3's resolver hands LOCAL world rows
35
+ * from the shared target registry RAW (world-join.ts registers the instance
36
+ * verbatim) — fixed in round 1 (2026-09-28 review): the resolver now bridges
37
+ * ALL local rows + the native seam through the shared `bridgeTypertGateway`
38
+ * (a2a-host.ts), and FEDERATED wire adapters pass through unwrapped.
39
+ */
40
+ import type { Context } from '@deepseek-ai/cordis';
41
+ /** Stable Cordis plugin (row) name. */
42
+ export declare const name = "superd-a2a";
43
+ /** Zero static injects — the tools/typertGateway legs are dynamic (dormancy). */
44
+ export declare const inject: string[];
45
+ /** Injection seam for tests: a tmpdir home instead of the ambient/prod DSH_HOME (rpc.ts discipline). */
46
+ export interface HubToolRowOptions {
47
+ /** Hub home backing the registries; defaults to `resolveDshHome()` at wiring time. */
48
+ home?: string;
49
+ }
50
+ /**
51
+ * Wire the tool row: build T2+T3's service with ctx0's own gateway injected
52
+ * at the native seam, then register the `superd-hub` tool once the `tools`
53
+ * service and the session plane are both live.
54
+ * @param ctx - the hub host root context (ctx0).
55
+ * @param opts - optional home override (tests inject a tmpdir).
56
+ */
57
+ export declare function wireHubToolRow(ctx: Context, opts?: HubToolRowOptions): void;
58
+ /** MUST stay an arrow (RM0/better-dash note: function declarations get `new`-called, disposers lost). */
59
+ export declare const apply: (ctx: Context) => void;
60
+ declare const _default: {
61
+ name: string;
62
+ inject: string[];
63
+ apply: (ctx: Context) => void;
64
+ };
65
+ export default _default;
@@ -0,0 +1,162 @@
1
+ import { resolveDshHome } from '@deepseek-ai/dsh-home-paths';
2
+ import { createSuperdHubTool } from './hub-tool.js';
3
+ import { createA2aIngest } from './a2a-ingest.js';
4
+ import { statusSweepLegs, loadAgentStatus } from './agent-status.js';
5
+ import { setSweepCollectors } from './mesh-link.js';
6
+ import { consoleA2aLog } from './a2a-log.js';
7
+ import { createA2aService } from './a2a-service.js';
8
+ import { resolveA2aGatewayFor, localA2aInitiator } from './a2a-host.js';
9
+ import { registerA2aRpc, registerExecRpc } from './mesh-rpc.js';
10
+ import { runOneShotExec, postOneShotExec } from './exec.js';
11
+ import { ensureSelf, listRemotes } from './machines.js';
12
+ import { cookieOf } from './mesh-link.js';
13
+ import { PtyPool } from './exec-pty-session.js';
14
+ import { LocalScriptChannel, WsPtyChannel } from './exec-pty-channel.js';
15
+ import { PTY_EXEC_PATH } from './exec-pty-rpc.js';
16
+ import { agentLinks } from './agent-roster.js';
17
+ /** Stable Cordis plugin (row) name. */
18
+ export const name = 'superd-a2a';
19
+ /** Zero static injects — the tools/typertGateway legs are dynamic (dormancy). */
20
+ export const inject = [];
21
+ /**
22
+ * Resolve one initiator's live agent handle. Read LAZILY at delivery time
23
+ * (`ctx.get`, no declaration): the registry's availability must not gate the
24
+ * row, and the runtime Agent object carries followup/steer/inject even though
25
+ * its public type surface says `{id}` (dsh-agent runtime-types.ts interface
26
+ * merge — compile-time only, one object at runtime; T3's A2aAgentHandle is
27
+ * its structural subset).
28
+ */
29
+ function liveAgentHandle(ctx, sessionId) {
30
+ const registry = ctx.get('agents');
31
+ const agent = registry?.get(sessionId);
32
+ return agent === undefined || agent === null ? undefined : agent;
33
+ }
34
+ /** The default roster view over the hub home (the /api/agent-runtime facts). */
35
+ function rosterViewOf(home) {
36
+ return {
37
+ self: ensureSelf(home, 'local'),
38
+ remotes: listRemotes(home).map((row) => ({ id: row.id, label: row.label !== '' ? row.label : row.name })),
39
+ agents: agentLinks(home),
40
+ };
41
+ }
42
+ /**
43
+ * Wire the tool row: build T2+T3's service with ctx0's own gateway injected
44
+ * at the native seam, then register the `superd-hub` tool once the `tools`
45
+ * service and the session plane are both live.
46
+ * @param ctx - the hub host root context (ctx0).
47
+ * @param opts - optional home override (tests inject a tmpdir).
48
+ */
49
+ export function wireHubToolRow(ctx, opts = {}) {
50
+ const home = opts.home ?? resolveDshHome();
51
+ ctx.inject(['tools', 'typertGateway'], (t) => {
52
+ // RAW instance in, bridged at the resolver (fix round 1: one shared
53
+ // bridge in a2a-host.ts covers the native seam AND all local world rows).
54
+ const raw = t.get('typertGateway');
55
+ // postoffice R4: ONE resolver, ONE ingest, TWO consumers — the initiator
56
+ // service's self-routing leg calls the very same ingest handler the HTTP
57
+ // endpoint calls (protocol-identical by construction). The service takes
58
+ // a LAZY provider: the ingest's delivery face buffers through the
59
+ // service's own buffer leg, so it can only be built after the service.
60
+ const gatewayFor = resolveA2aGatewayFor({ home, nativeGateway: raw });
61
+ // Operational journal: the pure modules only format lines; this is
62
+ // where they meet the process console and land in the unit log (4999 rig
63
+ // / dev3 dsh.service journal). NOT ctx.logger: the pack composes no
64
+ // cordis logger-console exporter, so ctx.logger records go nowhere
65
+ // (verified live 2026-09-29 — zero a2a lines through a full envelope
66
+ // flow). Console matches the world-join/world-provision precedent for
67
+ // operational lines. One line per machine event, no secrets.
68
+ const alog = consoleA2aLog();
69
+ let ingestRef;
70
+ const service = createA2aService({ home, gatewayFor, ingest: () => ingestRef, log: alog });
71
+ const delivery = {
72
+ agents: { get: (sessionId) => liveAgentHandle(ctx, sessionId) },
73
+ buffer: (initiatorSessionId, message) => { service.buffer(initiatorSessionId, message); },
74
+ };
75
+ ingestRef = createA2aIngest({ home, gatewayFor, delivery, log: alog });
76
+ // postoffice R5: the sweep's dual-responsibility legs (outbox pull +
77
+ // session-list collection) over THIS assembly — registered process-level;
78
+ // gateway.ts's existing sweep timer picks them up (no second timer).
79
+ setSweepCollectors(statusSweepLegs({ home, gatewayFor, ingest: ingestRef }));
80
+ // the a2a endpoints (POST /api/superd/a2a + GET outbox) ride the shared
81
+ // /api fence like the machines route; registered on the same inject scope
82
+ // (they live and die with the typertGateway seam).
83
+ registerA2aRpc(t, ingestRef);
84
+ // P4: the exec endpoint rides the same shared /api fence (spec §8.1).
85
+ registerExecRpc(t, alog);
86
+ // P4 exec face: the local executing side + the wire leg over the pairing
87
+ // cookie. The initiator-side per-machine knob (execEnabled) is honored
88
+ // here, before any bytes leave (spec §5).
89
+ const execFace = {
90
+ selfM4: () => ensureSelf(home, 'local').id,
91
+ run: (input) => runOneShotExec(input, alog),
92
+ post: async (machineId, input) => {
93
+ const row = listRemotes(home).find((r) => r.id === machineId);
94
+ if (row === undefined)
95
+ throw new Error(`unknown machine "${machineId}" — call agents for the roster`);
96
+ if (row.execEnabled === false)
97
+ throw new Error(`exec is disabled for machine "${machineId}" on this registry row (execEnabled=false)`);
98
+ alog?.('info', 'exec.remote', { machine: machineId, cmd: input.cmd, timeoutSec: input.timeoutSec ?? 120 });
99
+ const result = await postOneShotExec(row.origin, input, { cookie: cookieOf(row.id, home) });
100
+ // F6 (first-person 0.1.7-d finding): the initiator side needs its own
101
+ // completion line — without it a remote result lives only in the tool
102
+ // response and the initiator's journal stays blind to the outcome.
103
+ alog?.('info', 'exec.remote.done', {
104
+ machine: machineId,
105
+ exit: result.exit ?? 'none',
106
+ durationMs: result.durationMs,
107
+ ...(result.outcome === 'exited' ? {} : { outcome: result.outcome }),
108
+ ...(result.truncated ? { truncated: true } : {}),
109
+ });
110
+ return result;
111
+ },
112
+ };
113
+ // P4 §8.2 initiator half: the PTY pool. Session key = (calling DSH
114
+ // session, machine) — per-agent direct, no central allocation (D12).
115
+ const ptyPool = new PtyPool({ idleTtlSec: 600 });
116
+ ctx.effect(() => () => { void ptyPool.disposeAll(); }, 'superd-a2a: pty pool teardown');
117
+ const execPty = async (sessionKey, machine, cmd, opts = {}) => {
118
+ const channelOf = () => {
119
+ if (machine === ensureSelf(home, 'local').id)
120
+ return new LocalScriptChannel();
121
+ const row = listRemotes(home).find((r) => r.id === machine);
122
+ if (row === undefined)
123
+ throw new Error(`unknown machine "${machine}" — call agents for the roster`);
124
+ if (row.execEnabled === false)
125
+ throw new Error(`exec is disabled for machine "${machine}" on this registry row (execEnabled=false)`);
126
+ alog?.('info', 'exec.pty.dial', { machine, session: sessionKey });
127
+ return new WsPtyChannel(`${row.origin.replace(/^http/, 'ws')}${PTY_EXEC_PATH}`, { cookie: cookieOf(row.id, home) });
128
+ };
129
+ const session = ptyPool.getOrCreate(`${sessionKey}|m:${machine}`, channelOf);
130
+ const result = await session.dispatch(cmd, { ...(opts.timeoutSec === undefined ? {} : { timeoutSec: opts.timeoutSec }) });
131
+ alog?.('info', 'exec.pty.run', {
132
+ machine,
133
+ cmd: cmd.length > 120 ? `${cmd.slice(0, 117)}...` : cmd,
134
+ exit: result.exit ?? 'none',
135
+ ...(result.cwd !== null ? { cwd: result.cwd } : {}),
136
+ durationMs: result.durationMs,
137
+ ...(result.reconnected ? { reconnected: true } : {}),
138
+ ...(result.timedOut ? { timedOut: true } : {}),
139
+ });
140
+ return result;
141
+ };
142
+ const execFaceFull = { ...execFace, pty: execPty };
143
+ // the a2a endpoints (POST /api/superd/a2a + GET /api/superd/a2a/outbox) ride the shared
144
+ t.tools.register(createSuperdHubTool({
145
+ service,
146
+ delivery,
147
+ exec: execFaceFull,
148
+ // R6: the agent_status leg reads the R5 cache (the sweep's legs fill it)
149
+ status: () => loadAgentStatus(home),
150
+ roster: () => rosterViewOf(home),
151
+ // This row mounts in ctx0 only (header ruling): every caller is a
152
+ // native DSH session, so the initiator's a6 is the 'native' row's.
153
+ initiatorOf: (sessionId) => localA2aInitiator(home, 'native', sessionId),
154
+ onWatch: (dispose) => { ctx.effect(() => dispose, 'superd-a2a: a2a completion watch'); },
155
+ }));
156
+ });
157
+ }
158
+ /** MUST stay an arrow (RM0/better-dash note: function declarations get `new`-called, disposers lost). */
159
+ export const apply = (ctx) => {
160
+ wireHubToolRow(ctx);
161
+ };
162
+ export default { name, inject, apply };
@@ -0,0 +1,357 @@
1
+ import { A2aAddressError } from './a2a-wire.js';
2
+ import type { A2aIngest } from './a2a-ingest.js';
3
+ import type { A2aLogFn } from './a2a-log.js';
4
+ export { A2aAddressError };
5
+ /** One tracked conversation = one target-side DSH session (spec §2). */
6
+ export interface ConversationRow {
7
+ /** Display/tool alias — first 6 chars of the session id's uuid part (the constant `session-` prefix stripped), collision-lengthened 6→8→10; legacy rows keep older spellings verbatim. */
8
+ shortId: string;
9
+ /** Machine id (m4) the conversation lives on. */
10
+ machine: string;
11
+ /** Agent id (a6) the conversation belongs to. */
12
+ agent: string;
13
+ /** Full DSH session id (the wire identity). */
14
+ sessionId: string;
15
+ /** Operator-facing title (optional; never an identity). */
16
+ title?: string;
17
+ /**
18
+ * R4 amendment (2026-09-28 review): present ONLY on MIRROR rows (the peer
19
+ * hosted the conversation here) = the LOCAL a6 hosting the session.
20
+ * Presence identifies the row as locally-hosted and routes continuations
21
+ * to our own ingest. Absent = initiator-row semantics (the session lives
22
+ * at {machine, agent}). Additive optional; no migration.
23
+ */
24
+ hostAgent?: string;
25
+ /**
26
+ * The initiator identity's full session id — the peer→initiator asynchronous
27
+ * delivery address. R4: persisted at create/first-send on INITIATOR rows.
28
+ * 0.1.7-i: MIRROR rows persist it at adoption too (the opener's session
29
+ * from the envelope) — the send-failed notice must address the original
30
+ * initiator even when the write-back dies before the initiator-side ingest
31
+ * ever learned the conversation. Additive; absent = pre-R4 rows / not yet
32
+ * sent.
33
+ */
34
+ initiatorSession?: string;
35
+ /**
36
+ * D20 (2026-09-29): present ONLY on MIRROR rows = the hosted peer has sent
37
+ * through the channel at least once in this conversation. Monotonic per
38
+ * conversation — once set, the completion watch NEVER transcribes again
39
+ * (the channel reply IS the reply; unsent turn-end output is the peer's
40
+ * own business). Additive optional; no migration.
41
+ */
42
+ channelUsed?: true;
43
+ /** D20: epoch ms of the peer's LAST channel send on this row — the watermark the completion watch compares against its arm time to detect "sent during this turn's window" (suppresses the artifact when the send already was it). */
44
+ lastSentAt?: number;
45
+ /**
46
+ * 0.1.7-h R2: epoch ms when a stop envelope cancelled a LIVE turn here.
47
+ * The completion watch compares it against its arm watermark — a turn that
48
+ * ends because the INITIATOR stopped it must surface as
49
+ * reason=stopped-by-initiator, not as the peer going silent. Additive
50
+ * optional; no migration.
51
+ */
52
+ stoppedAt?: number;
53
+ /** Epoch ms when the row was created. */
54
+ createdAt: number;
55
+ /** Epoch ms of the last successful send. */
56
+ lastActivityAt: number;
57
+ /**
58
+ * D30 (2026-09-30): the peer's last output delivered into this conversation
59
+ * (a channel reply or a turn-end transcript), recorded when this hub
60
+ * RECEIVES it. Queryable through agent_status by the conversation's
61
+ * INITIATOR only (the ownership gate). Additive optional; no migration.
62
+ */
63
+ lastOutput?: string;
64
+ /** Epoch ms of lastOutput. */
65
+ lastOutputAt?: number;
66
+ }
67
+ /** The whole registry file. */
68
+ export interface ConversationsFile {
69
+ conversations: ConversationRow[];
70
+ }
71
+ /** Read the registry; a missing or unreadable/corrupt file degrades to empty (never throws). */
72
+ export declare function loadConversations(home: string): ConversationsFile;
73
+ /** Persist the registry: recursive mkdir, tmp write, chmod 0600, atomic rename (machines.ts discipline). */
74
+ export declare function saveConversations(home: string, file: ConversationsFile): void;
75
+ /**
76
+ * shortId mint (D2 fix): DSH session ids are `session-<uuid>`, so slicing the
77
+ * raw id only ever captures the constant prefix (the live registry degenerated
78
+ * to "session-", "session-3", …). Strip the prefix, mint the first 6 chars of
79
+ * the uuid part, collision-lengthen 6→8→10… (step 2, spec §11). Ids without the
80
+ * prefix (older/foreign spellings) mint from the whole id the same way. A
81
+ * re-seen sessionId keeps its row's shortId (stable across restarts); stored
82
+ * shortIds are reserved as taken — DEGENERATE ones are re-minted once by
83
+ * {@link migrateConversations}, all others stay verbatim forever.
84
+ */
85
+ export declare function shortIdFor(sessionId: string, rows: readonly ConversationRow[]): string;
86
+ /**
87
+ * Degenerate-pattern predicate (D2 migration): a stored shortId that still
88
+ * carries the `session-` prefix AND whose remainder is shorter than the new
89
+ * mint length — exactly the ids the old prefix-slicing bug produced. Such ids
90
+ * carry (almost) no information and teach peers to echo garbage.
91
+ */
92
+ export declare function isDegenerateShortId(shortId: string): boolean;
93
+ /**
94
+ * One-time registry migration (D2 amended, review 2026-09-28): rows matching
95
+ * the degenerate pattern are re-minted with the current algorithm and written
96
+ * back; non-degenerate rows are never touched. Peers still holding an old
97
+ * degenerate id in their context is accepted collateral — the registry holds
98
+ * test data today, which is the cheapest migration window there will ever be.
99
+ * @returns the re-minted rows (empty when nothing needed migrating).
100
+ */
101
+ export declare function migrateConversations(home: string): ConversationRow[];
102
+ /**
103
+ * THE teaching envelope — one template constant (D20 v3, 2026-09-29: the
104
+ * SINGLE merged text, no variants). Filled with the INITIATOR's identity
105
+ * (machine-agent address = the write-back target), the conversation title,
106
+ * the body, and the shortId a superd-hub tool addresses. Tail = the user's
107
+ * 2026-09-29 redline, four short paragraphs, each carrying one load:
108
+ * provenance + channel-only principle / heterogeneity disclosure (tool
109
+ * availability genuinely differs per runtime — this is what stops a
110
+ * tool-less model from hunting for one) / how+when to send (sending does
111
+ * not end the turn) / the fallback, phrased from the RECEIVER's operational
112
+ * truth ("the hub receive no hub message" — intentionally or prevented;
113
+ * wording per user ruling 0.1.7-h: the receiver is the hub, the message
114
+ * class is a hub message). The "if available" conditional is evaluated by
115
+ * the MODEL against its own tool
116
+ * list; never pre-select variants on unknowable runtime state. Never a
117
+ * separate user prompt (the OMP 20-tool-call incident).
118
+ */
119
+ export declare const A2A_MESSAGE_TEMPLATE = "A2A message from {machine}-{agent} (conversation \"{title}\"):\n\n{message}\n\nThis conversation is initiated through superd-hub by the sender. The sender sees only what arrives through this conversation.\n\nAs superd-hub hosts various kinds of agent runtimes, integration into each runtime and superd-hub tool availability in each runtime session might differ.\n\nTo reply, send a message back: with a superd-hub tool, if available, {action:\"send\", agent:\"{machine}-{agent}\", conversationId:\"{shortId}\", message:\"\u2026\"}. You can send at any point, more than once \u2014 sending does not end your turn.\n\nIf the hub receive no hub message from you (intentionally or whatever situation prevents so), your last output at this turn's end is delivered to the sender as your reply.";
120
+ /** Title shown when the conversation has none. */
121
+ export declare const A2A_UNTITLED = "(untitled)";
122
+ /**
123
+ * Render the teaching envelope for one send — a SINGLE pass over the template
124
+ * (injection fix, 2026-09-28): each placeholder is expanded exactly once from
125
+ * the fields, and user-supplied values (title, message) are never re-scanned,
126
+ * so a title containing the literal `{shortId}` or `{message}` lands verbatim
127
+ * instead of being expanded by a later replaceAll round.
128
+ */
129
+ export declare function renderA2aEnvelope(fields: {
130
+ machine: string;
131
+ agent: string;
132
+ title?: string;
133
+ shortId: string;
134
+ message: string;
135
+ }): string;
136
+ /** Bounded unread buffer size per initiator session. */
137
+ export declare const A2A_BUFFER_MAX = 32;
138
+ /** D28: the sender's synchronous receipt window. If the end-to-end wire has not settled by then the hub answers `accepted` and lets the SAME in-flight request finish in the background. */
139
+ export declare const A2A_SYNC_RECEIPT_MS = 10000;
140
+ /** D28: the TOTAL wire budget for one send (120s). The POST timeout is set from the request start, so after the 10s synchronous window the SAME in-flight request still has ~110s left to settle; the peer may spend it bringing the runtime up (spawn OR resume — indistinguishable at the process level). */
141
+ export declare const A2A_WIRE_TOTAL_BUDGET_MS = 120000;
142
+ /** stop('session') degradation notice — the session plane has no close RPC upstream. */
143
+ export declare const A2A_NO_SESSION_CLOSE_REASON = "no session-close endpoint upstream";
144
+ /** Target address: machine id (m4) + agent id (a6) — the `gatewayFor` key. */
145
+ export interface A2aAddress {
146
+ machine: string;
147
+ id: string;
148
+ }
149
+ /**
150
+ * Initiator identity envelope (spec §7.3), opaque in T2 — Task 3 gives it its
151
+ * real shape. T2 uses `machine`/`agent` for the teaching template and
152
+ * `sessionId` as the unread-buffer key.
153
+ */
154
+ export interface A2aInitiator {
155
+ machine: string;
156
+ agent: string;
157
+ sessionId: string;
158
+ }
159
+ /** Prompt delivery mode (upstream `SessionPromptRequest.mode`); default 'queue' — peer semantics, never interrupting. */
160
+ export type A2aDelivery = 'queue' | 'steer';
161
+ /**
162
+ * Receipt taxonomy (D28): 'delivered' on every successful prompt submission
163
+ * (end-to-end delivery complete); 'failed' carries an executable error body.
164
+ * ('accepted' / 'spawn-failed' land with the 0.1.8 poll mechanism, T5.)
165
+ */
166
+ export type A2aReceiptOutcome = 'delivered' | 'accepted' | 'failed';
167
+ /** One failed-send error body, verbatim from the gateway (wire error shape). */
168
+ export interface A2aReceiptError {
169
+ code: string;
170
+ message: string;
171
+ details?: unknown;
172
+ }
173
+ /** send()'s immediate return (spec §2: inbox-acceptance receipt). */
174
+ export interface A2aReceipt {
175
+ outcome: A2aReceiptOutcome;
176
+ /** The client-minted requestId — the durable identity of the injected user message. */
177
+ messageId: string;
178
+ /** Conversation shortId (absent when no conversation resolved). */
179
+ conversationId?: string;
180
+ /** Target session ('' when the failure predates session resolution). */
181
+ sessionId: string;
182
+ /** Present iff outcome === 'failed'. */
183
+ error?: A2aReceiptError;
184
+ /** Model-facing guidance riding a non-terminal receipt (D28: an `accepted` receipt explains that the same in-flight request keeps running). */
185
+ note?: string;
186
+ }
187
+ /** One send request (spec §7.2): no conversationId ≡ new conversation. */
188
+ export interface A2aSendRequest {
189
+ /**
190
+ * Target agent. REQUIRED to open a new conversation (no conversationId);
191
+ * on a continuation it may be omitted — the hub derives the route from the
192
+ * registry row + the caller's session (D24/T3). When supplied on a
193
+ * continuation it is a checked assertion: a mismatch is refused.
194
+ */
195
+ to?: A2aAddress;
196
+ conversationId?: string;
197
+ title?: string;
198
+ message: string;
199
+ delivery?: A2aDelivery;
200
+ }
201
+ /** Conversation reference: address + shortId. */
202
+ export interface A2aConversationRef {
203
+ to: A2aAddress;
204
+ conversationId: string;
205
+ }
206
+ /** One stop request (D24/T3): `to` is optional — the hub derives the session owner from the row. */
207
+ export interface A2aStopRequest {
208
+ conversationId: string;
209
+ /** The session owner's address; optional (derived from the row), checked when supplied. */
210
+ to?: A2aAddress;
211
+ }
212
+ /** One captured turn completion. */
213
+ export interface A2aCompletion {
214
+ conversationId: string;
215
+ sessionId: string;
216
+ turn: number;
217
+ /** `turn/end.data.reason.kind` (TurnEndReason). */
218
+ reason: string;
219
+ /** Text blocks of the turn's LAST non-interrupted assistant/message, joined with '\n\n'. */
220
+ text: string;
221
+ }
222
+ export type A2aCompletionCb = (completion: A2aCompletion) => void;
223
+ export type A2aErrorCb = (error: {
224
+ code: string;
225
+ message: string;
226
+ }) => void;
227
+ export interface A2aSubscribeOptions {
228
+ /**
229
+ * Correlate one send: the requestId used on `session/prompt`. The cb fires
230
+ * exactly once — for the turn that admitted this request's user message
231
+ * (matched by `data.source.rpcId`) — then the subscription auto-disposes.
232
+ * Omit to observe every turn completion while subscribed (one cb per turn).
233
+ */
234
+ requestId?: string;
235
+ /** Death/failure notice path (missing conversation/gateway, second stream death). */
236
+ onError?: A2aErrorCb;
237
+ }
238
+ export type A2aStopScope = 'turn' | 'session';
239
+ /** stop() idle note — the honest answer when the pre-cancel liveness snapshot showed no live turn (D5). */
240
+ export declare const A2A_NO_TURN_ACTIVE_NOTE = "no turn was active";
241
+ /** stop() result: 'turn' answers a turn-scope stop; 'turn-only' reports the session-scope degradation.
242
+ * `cancelled:false` is the honest idle answer (D5): the cancel RPC still fired (today's wire behavior
243
+ * is unchanged), but the pre-cancel liveness read showed no turn to stop — the note says so. */
244
+ export type A2aStopResult = {
245
+ scope: 'turn';
246
+ cancelled: true;
247
+ } | {
248
+ scope: 'turn';
249
+ cancelled: false;
250
+ note: typeof A2A_NO_TURN_ACTIVE_NOTE;
251
+ } | {
252
+ scope: 'turn-only';
253
+ cancelled: true;
254
+ reason: typeof A2A_NO_SESSION_CLOSE_REASON;
255
+ } | {
256
+ scope: 'turn-only';
257
+ cancelled: false;
258
+ note: typeof A2A_NO_TURN_ACTIVE_NOTE;
259
+ reason: typeof A2A_NO_SESSION_CLOSE_REASON;
260
+ };
261
+ /** One buffered (undelivered) inbound message for an initiator session. */
262
+ export interface A2aBufferedMessage {
263
+ messageId: string;
264
+ initiatorSessionId: string;
265
+ from: A2aAddress;
266
+ conversationId: string;
267
+ text: string;
268
+ bufferedAt: number;
269
+ }
270
+ /** Input half of a buffer push (Task 3's delivery layer calls this on reply-delivery failure). */
271
+ export type A2aBufferInput = Omit<A2aBufferedMessage, 'initiatorSessionId' | 'bufferedAt'>;
272
+ /** The gateway face this service consumes — structural subset of `GatewayFace` (targets.ts). */
273
+ export interface A2aGateway {
274
+ dispatchRpc(endpoint: string, payload: unknown, signal: AbortSignal): Promise<unknown>;
275
+ openWireStream(endpoint: string, payload: unknown, signal: AbortSignal): AsyncIterable<unknown> | Promise<AsyncIterable<unknown>>;
276
+ }
277
+ /**
278
+ * Typed address-resolution failure (D7) — the class lives in a2a-wire.ts
279
+ * since postoffice R1 (the wire helpers classify transport failures with
280
+ * it); re-exported above for this module's existing consumers.
281
+ */
282
+ export interface A2aServiceDeps {
283
+ home: string;
284
+ /** Still the LOCAL leg's resolver (subscribeCompletion + the ingest assembly source); the send/stop paths never resolve a REMOTE gateway since R4 — remote routing is envelope-wire only. */
285
+ gatewayFor(addr: A2aAddress): A2aGateway;
286
+ /**
287
+ * R4: the ingest handler shared with the HTTP endpoint — the self-routing
288
+ * leg. LAZY (resolved at send time, never construction time: the ingest's
289
+ * delivery face buffers through THIS service's buffer leg, so the instance
290
+ * can only exist after the service does).
291
+ */
292
+ ingest?(): A2aIngest | undefined;
293
+ /** R4: fetch override for the remote wire leg (tests inject fixtures). */
294
+ fetchImpl?: typeof fetch;
295
+ /** Operational journal (0.1.7-b): one greppable line per machine event; the cordis host wires it onto ctx.logger, tests inject a collector. */
296
+ log?: A2aLogFn;
297
+ now?(): number;
298
+ /** D28: override the synchronous receipt window (tests run it small; production uses A2A_SYNC_RECEIPT_MS). */
299
+ syncReceiptMs?: number;
300
+ }
301
+ export interface A2aService {
302
+ /** Create-or-resume + inject one user turn; receipt semantics (never throws — failures are 'failed' receipts). */
303
+ send(req: A2aSendRequest, initiator: A2aInitiator): Promise<A2aReceipt>;
304
+ /** Subscribe turn completions of one conversation over session/follow; returns the dispose fn. */
305
+ subscribeCompletion(conv: A2aConversationRef, cb: A2aCompletionCb, opts?: A2aSubscribeOptions): () => void;
306
+ /** Cancel the active turn; 'session' scope degrades to turn-only with the typed reason. cancelled:false + note is the honest idle answer (D5 — the cancel still fires; a pre-cancel snapshot read proved no turn was live). */
307
+ stop(req: A2aStopRequest, scope: A2aStopScope): Promise<A2aStopResult>;
308
+ /** Buffer one undelivered message for an initiator (bounded A2A_BUFFER_MAX, oldest dropped). */
309
+ buffer(initiatorSessionId: string, message: A2aBufferInput): void;
310
+ /** Sweep the unread buffer — one initiator, or all; swept entries are returned and cleared. */
311
+ read(initiatorSessionId?: string): A2aBufferedMessage[];
312
+ /** Snapshot of the registry rows. */
313
+ listConversations(): ConversationRow[];
314
+ }
315
+ /** Map one typed address failure to its wire error body (D7): the error's own
316
+ * message; the code keeps 'a2a/gateway-unavailable' for the unreachable branch
317
+ * (today's code) and names the kind otherwise. */
318
+ export declare function addressErrorBody(error: A2aAddressError): {
319
+ code: string;
320
+ message: string;
321
+ };
322
+ /**
323
+ * D5 turn-liveness read: open the follow stream, replay the FIRST snapshot
324
+ * frame's records, and report whether a turn is still open (a turn/start not
325
+ * followed by its turn/end). Read manually (no for-await) and aborted BEFORE
326
+ * returning — closing an async generator suspended at an await must not gate
327
+ * the answer (the close promise can reject on abort and would eat the result).
328
+ * @returns true (a turn is live), false (idle), or undefined when the plane
329
+ * could not answer (stream death, stream end, no snapshot, read timeout) —
330
+ * callers treat undefined conservatively: cancel anyway, keep today's shape.
331
+ */
332
+ export declare function turnLivenessOf(gateway: A2aGateway, sessionId: string): Promise<boolean | undefined>;
333
+ /**
334
+ * One session-plane RPC through the gateway: payload via `wireArgsOf`,
335
+ * `{ok:false,error}` rethrown as a typed EnvelopeError (error body verbatim),
336
+ * transport throws pass through untouched (they are already gateway/* typed).
337
+ */
338
+ export declare function sessionRpc<T>(gateway: A2aGateway, endpoint: string, request: unknown): Promise<T>;
339
+ /** A directly-addressed session to watch — the ingest leg (postoffice R3)
340
+ * subscribes the LOCAL gateway without the initiator-side registry checks. */
341
+ export interface A2aSessionSource {
342
+ /** The session-plane gateway owning the session (local leg on the ingest side). */
343
+ gateway: A2aGateway;
344
+ /** The session to follow. */
345
+ sessionId: string;
346
+ /** Conversation id reported in fired completions (the registry shortId). */
347
+ conversationId: string;
348
+ }
349
+ /**
350
+ * The completion watcher behind `subscribeCompletion` (extracted verbatim in
351
+ * postoffice R3): one follow stream over the addressed session, turn-text
352
+ * reduction, requestId arming, one resubscribe, snapshot-history fire.
353
+ * Same contract as the service method — every fired completion is one cb,
354
+ * armed subscriptions auto-dispose after their turn fires.
355
+ */
356
+ export declare function subscribeSessionCompletion(source: A2aSessionSource, cb: A2aCompletionCb, opts?: A2aSubscribeOptions): () => void;
357
+ export declare function createA2aService(deps: A2aServiceDeps): A2aService;