@adhdev/mesh-shared 1.0.60-rc.6 → 1.0.60-rc.61

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 (47) hide show
  1. package/dist/brain-routing.d.ts +2 -2
  2. package/dist/index.d.ts +10 -0
  3. package/dist/index.js +2862 -35
  4. package/dist/index.js.map +1 -1
  5. package/dist/index.mjs +2536 -35
  6. package/dist/index.mjs.map +1 -1
  7. package/dist/magi.d.ts +1 -1
  8. package/dist/mesh-tool-names.d.ts +27 -2
  9. package/dist/mesh-vocabulary.d.ts +70 -0
  10. package/dist/mission-brief.d.ts +62 -0
  11. package/dist/outbound-message.d.ts +202 -0
  12. package/dist/path-ownership.d.ts +101 -0
  13. package/dist/protocol/daemon-server.d.ts +388 -0
  14. package/dist/protocol/dashboard-daemon-p2p.d.ts +212 -0
  15. package/dist/protocol/dashboard-server.d.ts +240 -0
  16. package/dist/protocol/envelope.d.ts +55 -0
  17. package/dist/protocol/index.d.ts +15 -0
  18. package/dist/session-launch.d.ts +113 -0
  19. package/dist/session-status.d.ts +61 -0
  20. package/dist/slot-proposal.d.ts +1 -1
  21. package/dist/turn-evidence.d.ts +438 -0
  22. package/dist/turn-ipc.d.ts +1040 -0
  23. package/dist/types.d.ts +11 -0
  24. package/dist/worker-protocol.d.ts +69 -0
  25. package/dist/ws-protocol.d.ts +71 -41
  26. package/package.json +1 -1
  27. package/src/brain-routing.ts +3 -3
  28. package/src/index.ts +10 -0
  29. package/src/magi.ts +1 -1
  30. package/src/mesh-tool-names.ts +65 -19
  31. package/src/mesh-vocabulary.ts +98 -0
  32. package/src/mission-brief.ts +153 -0
  33. package/src/outbound-message.ts +320 -0
  34. package/src/path-ownership.ts +271 -0
  35. package/src/protocol/daemon-server.ts +552 -0
  36. package/src/protocol/dashboard-daemon-p2p.ts +272 -0
  37. package/src/protocol/dashboard-server.ts +340 -0
  38. package/src/protocol/envelope.ts +75 -0
  39. package/src/protocol/index.ts +16 -0
  40. package/src/session-launch.ts +247 -0
  41. package/src/session-status.ts +140 -0
  42. package/src/slot-proposal.ts +1 -1
  43. package/src/turn-evidence.ts +602 -0
  44. package/src/turn-ipc.ts +2331 -0
  45. package/src/types.ts +8 -0
  46. package/src/worker-protocol.ts +164 -0
  47. package/src/ws-protocol.ts +102 -93
@@ -0,0 +1,388 @@
1
+ /**
2
+ * protocol/daemon-server — the daemon ↔ Workers-server WebSocket link.
3
+ *
4
+ * Wiring-unification Phase A2 (docs/design/2026-09-23-wiring-unification.md §3 A2).
5
+ *
6
+ * Before this file the link shared NAME lists only (`ws-protocol.ts`), payloads
7
+ * were `any` on both ends, `daemon_mesh_command` was sent by a raw `ws.send`
8
+ * outside the typed funnel, and the P2P signaling names in the shared list
9
+ * (`offer|answer|ice`) had drifted from the wire (`p2p_offer|p2p_answer|p2p_ice`)
10
+ * without anything noticing, because nothing imported them.
11
+ *
12
+ * Two directions:
13
+ * - `DaemonToServerMsg` — every frame the daemon writes to the server WS.
14
+ * - `ServerToDaemonMsg` — every frame the server writes to a daemon WS. This
15
+ * is a CLOSED control set plus one OPEN namespace: the public REST API
16
+ * (`POST /api/v1/daemons/:id/command`, `daemonCommandSchema` = any string)
17
+ * relays a caller-chosen command name as the frame `type`, so that member is
18
+ * typed with a branded string rather than a literal list. The brand is what
19
+ * keeps `msg.type === 'auth_ok'` narrowing to the control member instead of
20
+ * collapsing into "some string".
21
+ *
22
+ * Payloads are structural mirrors of the daemon-core types the two ends assign
23
+ * into them (see ./envelope.ts for why mesh-shared cannot import daemon-core).
24
+ */
25
+ import type { AuthOkPlanLimits } from '../ws-protocol';
26
+ import type { SessionStatus } from '../session-status';
27
+ import { type CommandPayload, type Envelope, type MessageId } from './envelope';
28
+ /**
29
+ * Daemon-originated status events. The daemon relays these to the server
30
+ * (push notifications, webhooks) AND over P2P (dashboard). Mirror of
31
+ * daemon-core `DaemonStatusEventName`; the server pins the two equal at compile
32
+ * time (UserSession.ts), so adding a member here without adding it there — or
33
+ * vice versa — fails `tsc`.
34
+ */
35
+ export declare const DAEMON_STATUS_EVENT_NAMES: readonly ["agent:generating_started", "agent:waiting_approval", "agent:waiting_choice", "agent:generating_completed", "agent:stopped", "monitor:no_progress", "monitor:long_generating"];
36
+ export type DaemonStatusEventName = typeof DAEMON_STATUS_EVENT_NAMES[number];
37
+ export declare const isDaemonStatusEventName: (value: unknown) => value is "agent:generating_started" | "agent:waiting_approval" | "agent:waiting_choice" | "agent:generating_completed" | "agent:stopped" | "monitor:no_progress" | "monitor:long_generating";
38
+ /**
39
+ * Server-bound status event. Content boundary: identifiers, enums, counters
40
+ * and booleans only — plus the approved approval-modal exception
41
+ * (`modalMessage`/`modalButtons`, CLAUDE.md "Push notification exception").
42
+ * Mirror of daemon-core `DaemonStatusEventPayload`.
43
+ */
44
+ export type DaemonStatusEventWirePayload = {
45
+ event: DaemonStatusEventName;
46
+ timestamp: number;
47
+ targetSessionId?: string;
48
+ providerType?: string;
49
+ providerSessionId?: string;
50
+ workspaceName?: string;
51
+ duration?: number;
52
+ elapsedSec?: number;
53
+ modalMessage?: string;
54
+ modalButtons?: string[];
55
+ surfaceHidden?: boolean;
56
+ muted?: boolean;
57
+ };
58
+ /** Routing-only session entry the daemon reports over the server WS. Mirror of daemon-core `RoutingSessionEntry`. */
59
+ export type RoutingSessionWireEntry = {
60
+ id: string;
61
+ parentId: string | null;
62
+ providerType: string;
63
+ providerName: string;
64
+ kind: string;
65
+ transport: string;
66
+ status: SessionStatus;
67
+ workspace: string | null;
68
+ cdpConnected?: boolean;
69
+ surfaceHidden?: boolean;
70
+ muted?: boolean;
71
+ };
72
+ /** Counters and enums only — never a peer id or address. Mirror of daemon-core `P2PStatusSummary`. */
73
+ export type P2PStatusWireSummary = {
74
+ available: boolean;
75
+ state: string;
76
+ peers: number;
77
+ screenshotActive?: boolean;
78
+ direct?: number;
79
+ relay?: number;
80
+ unknownTransport?: number;
81
+ directTotal?: number;
82
+ relayTotal?: number;
83
+ };
84
+ /**
85
+ * seqscribe replication health — pre-bucketed counters and booleans only,
86
+ * never a topic name or peer id. Mirror of daemon-core `SeqscribeStatusSummary`
87
+ * (which should derive from this once shared-types is on the protocol).
88
+ */
89
+ export type SeqscribeStatusWireSummary = {
90
+ topics: number;
91
+ peers: number;
92
+ peersReady: number;
93
+ pendingBucket: number;
94
+ consumerLagBucket: number;
95
+ queueBucket: number;
96
+ fgenAgeBucket: number;
97
+ quarantined: boolean;
98
+ authority: boolean;
99
+ dualWrite?: boolean;
100
+ dualWriteFailedBucket?: number;
101
+ dualWriteDroppedBucket?: number;
102
+ dualWriteBackfilledBucket?: number;
103
+ parityMismatchBucket?: number;
104
+ parityRan?: boolean;
105
+ parityMissingInShadowBucket?: number;
106
+ parityExtraInShadowBucket?: number;
107
+ parityFieldMismatchBucket?: number;
108
+ transcriptPublish?: boolean;
109
+ transcriptPublishedBucket?: number;
110
+ transcriptPublishFailedBucket?: number;
111
+ transcriptDedupedBucket?: number;
112
+ transcriptOversizedBucket?: number;
113
+ transcriptDroppedBucket?: number;
114
+ transcriptParityRan?: boolean;
115
+ transcriptParityMismatchBucket?: number;
116
+ };
117
+ export type CloudStatusReportWirePayload = {
118
+ sessions: RoutingSessionWireEntry[];
119
+ p2p?: P2PStatusWireSummary;
120
+ seqscribe?: SeqscribeStatusWireSummary;
121
+ timestamp: number;
122
+ };
123
+ export declare const RELEASE_CHANNELS: readonly ["stable", "preview"];
124
+ export type ReleaseChannelWire = typeof RELEASE_CHANNELS[number];
125
+ export declare const NPM_UPDATE_TAGS: readonly ["latest", "next"];
126
+ export type NpmUpdateTagWire = typeof NPM_UPDATE_TAGS[number];
127
+ export declare const VERSION_UPDATE_REASONS: readonly ["force_update_below", "major_minor_mismatch", "patch_mismatch", "daemon_ahead"];
128
+ export type VersionUpdateReasonWire = typeof VERSION_UPDATE_REASONS[number];
129
+ /** Mirror of daemon-core `VersionUpdatePolicy`. */
130
+ export type VersionUpdatePolicyWire = {
131
+ channel: ReleaseChannelWire;
132
+ npmTag: NpmUpdateTagWire;
133
+ targetVersion: string;
134
+ minVersion?: string;
135
+ updateCommand: string;
136
+ };
137
+ export type StatusHeartbeatWirePayload = {
138
+ timestamp?: number;
139
+ p2pPeers?: number;
140
+ };
141
+ /** What the daemon says about itself at auth time (`ServerConnectionOptions.cliInfo`). */
142
+ export type DaemonAuthIdentity = {
143
+ type: string;
144
+ version: string;
145
+ platform: string;
146
+ hostname?: string;
147
+ machineId?: string;
148
+ /** Canonical mesh identity `daemon_<machineId>`; the server keys its DO and status by it. */
149
+ instanceId: string;
150
+ };
151
+ export type DaemonAuthWirePayload = {
152
+ token: string;
153
+ daemon: DaemonAuthIdentity;
154
+ };
155
+ export type IceServerWireEntry = {
156
+ urls: string | string[];
157
+ username?: string;
158
+ credential?: string;
159
+ };
160
+ export type AuthOkWirePayload = {
161
+ message?: string;
162
+ plan: string;
163
+ iceServers: IceServerWireEntry[];
164
+ turnenabled: boolean;
165
+ serverVersion: string;
166
+ releaseChannel?: ReleaseChannelWire;
167
+ updatePolicy?: VersionUpdatePolicyWire;
168
+ machineNickname: string | null;
169
+ seqscribeFleetSecret?: string;
170
+ seqscribeFleetSecretVersion?: number;
171
+ /** Beacon board seed for cold start (advisory, may be absent). */
172
+ beaconSeed?: {
173
+ reports: unknown[];
174
+ truncated: number;
175
+ };
176
+ limits: AuthOkPlanLimits;
177
+ };
178
+ export type AuthErrorWirePayload = {
179
+ reason: string;
180
+ message: string;
181
+ limit?: number;
182
+ current?: number;
183
+ };
184
+ /** `machine_evicted` / `force_disconnect` / `token_revoked` — a reason token plus human text, then the socket closes. */
185
+ export type DisconnectNoticeWirePayload = {
186
+ reason: string;
187
+ message: string;
188
+ };
189
+ export type VersionMismatchWirePayload = {
190
+ current: string;
191
+ latest: string;
192
+ required: false;
193
+ reason: VersionUpdateReasonWire;
194
+ channel: ReleaseChannelWire;
195
+ npmTag: NpmUpdateTagWire;
196
+ updatePolicy: VersionUpdatePolicyWire;
197
+ updateCommand: string;
198
+ };
199
+ export type ForceUpdateRequiredWirePayload = {
200
+ current: string;
201
+ latest: string;
202
+ minVersion: string;
203
+ required: true;
204
+ reason: VersionUpdateReasonWire;
205
+ channel: ReleaseChannelWire;
206
+ npmTag: NpmUpdateTagWire;
207
+ updatePolicy: VersionUpdatePolicyWire;
208
+ updateCommand: string;
209
+ forceUpdateBelow?: string;
210
+ message: string;
211
+ };
212
+ export type CommandResultWirePayload = {
213
+ requestId: MessageId;
214
+ success: boolean;
215
+ source?: string;
216
+ } & Record<string, unknown>;
217
+ export type DaemonErrorWirePayload = {
218
+ message?: string;
219
+ detail?: unknown;
220
+ };
221
+ export type BeaconVectorsWirePayload = {
222
+ op: 'put';
223
+ report: unknown;
224
+ } | {
225
+ op: 'get';
226
+ topics: string[];
227
+ requestId: string;
228
+ };
229
+ export declare const P2P_SIGNAL_TYPES: readonly ["p2p_ready", "p2p_offer", "p2p_answer", "p2p_ice"];
230
+ export type P2PSignalType = typeof P2P_SIGNAL_TYPES[number];
231
+ export declare const isP2PSignalType: (value: unknown) => value is "p2p_ready" | "p2p_offer" | "p2p_answer" | "p2p_ice";
232
+ /** Signals the daemon originates (it is the WebRTC offerer). */
233
+ export declare const DAEMON_P2P_SIGNAL_TYPES: readonly ["p2p_offer", "p2p_ice"];
234
+ export type DaemonP2PSignalType = typeof DAEMON_P2P_SIGNAL_TYPES[number];
235
+ export declare const isDaemonP2PSignalType: (value: unknown) => value is "p2p_offer" | "p2p_ice";
236
+ export type P2POfferWirePayload = {
237
+ sdp: string;
238
+ type: string;
239
+ peerId: string;
240
+ };
241
+ export type P2PIceWirePayload = {
242
+ candidate: string;
243
+ mid: string;
244
+ peerId: string;
245
+ };
246
+ /**
247
+ * A dashboard-originated signal as relayed to the daemon. `peerId` is stamped
248
+ * by UserSession/SharedSession, `sharePermission` only by SharedSession (server
249
+ * authority, never the client). Field names follow the browser/node-datachannel
250
+ * spellings both ends already read.
251
+ */
252
+ export type RelayedP2PSignalWirePayload = {
253
+ peerId?: string;
254
+ daemonId?: string;
255
+ sharePermission?: string;
256
+ sdp?: string;
257
+ type?: string;
258
+ candidate?: string;
259
+ mid?: string;
260
+ sdpMid?: string | null;
261
+ sdpMLineIndex?: number | null;
262
+ };
263
+ export declare const MESH_P2P_SIGNAL_TYPES: readonly ["mesh_p2p_ready", "mesh_p2p_offer", "mesh_p2p_answer", "mesh_p2p_ice"];
264
+ export type MeshP2PSignalType = typeof MESH_P2P_SIGNAL_TYPES[number];
265
+ export declare const isMeshP2PSignalType: (value: unknown) => value is "mesh_p2p_ready" | "mesh_p2p_offer" | "mesh_p2p_answer" | "mesh_p2p_ice";
266
+ /** Daemon→daemon signal as delivered to the target daemon: the sender's args plus the server-stamped canonical sender id. */
267
+ export type MeshP2PSignalWirePayload = {
268
+ senderDaemonId: string;
269
+ } & Record<string, unknown>;
270
+ export declare const DAEMON_TO_SERVER_TYPES: readonly ["auth", "status_report", "status_heartbeat", "status_event", "command_result", "error", "agent_event", "beacon_vectors", "daemon_mesh_command", "p2p_offer", "p2p_ice"];
271
+ export type DaemonToServerType = typeof DAEMON_TO_SERVER_TYPES[number];
272
+ export declare const isDaemonToServerType: (value: unknown) => value is "error" | "p2p_offer" | "p2p_ice" | "auth" | "status_report" | "status_heartbeat" | "status_event" | "command_result" | "agent_event" | "beacon_vectors" | "daemon_mesh_command";
273
+ export type DaemonAuthMsg = Envelope<'auth', DaemonAuthWirePayload>;
274
+ export type StatusReportMsg = Envelope<'status_report', CloudStatusReportWirePayload>;
275
+ export type StatusHeartbeatMsg = Envelope<'status_heartbeat', StatusHeartbeatWirePayload>;
276
+ export type DaemonStatusEventMsg = Envelope<'status_event', DaemonStatusEventWirePayload>;
277
+ export type CommandResultMsg = Envelope<'command_result', CommandResultWirePayload>;
278
+ export type DaemonErrorMsg = Envelope<'error', DaemonErrorWirePayload>;
279
+ /** Agent-level event forwarded verbatim to the `agent:status` webhook (throttled server-side). Dynamic by design. */
280
+ export type AgentEventMsg = Envelope<'agent_event', CommandPayload>;
281
+ export type BeaconVectorsMsg = Envelope<'beacon_vectors', BeaconVectorsWirePayload>;
282
+ export type DaemonP2POfferMsg = Envelope<'p2p_offer', P2POfferWirePayload>;
283
+ export type DaemonP2PIceMsg = Envelope<'p2p_ice', P2PIceWirePayload>;
284
+ /**
285
+ * Daemon→daemon signaling relay request. Fields are TOP-LEVEL (no `payload`):
286
+ * this is the wire shape the live fleet speaks, kept as-is so mixed-version
287
+ * daemons and servers keep interoperating during a rollout.
288
+ */
289
+ export type DaemonMeshCommandMsg = {
290
+ type: 'daemon_mesh_command';
291
+ requestId: string;
292
+ targetDaemonId: string;
293
+ command: MeshP2PSignalType;
294
+ args: Record<string, unknown>;
295
+ timestamp: number;
296
+ };
297
+ export type DaemonToServerMsg = DaemonAuthMsg | StatusReportMsg | StatusHeartbeatMsg | DaemonStatusEventMsg | CommandResultMsg | DaemonErrorMsg | AgentEventMsg | BeaconVectorsMsg | DaemonMeshCommandMsg | DaemonP2POfferMsg | DaemonP2PIceMsg;
298
+ /** Members that carry a `payload` object (everything except `daemon_mesh_command`). */
299
+ export type DaemonToServerPayloadType = Exclude<DaemonToServerMsg, DaemonMeshCommandMsg>['type'];
300
+ export type DaemonToServerPayloadOf<T extends DaemonToServerPayloadType> = Extract<DaemonToServerMsg, {
301
+ type: T;
302
+ }>['payload'];
303
+ /**
304
+ * Parse boundary for the server: is `raw` a frame this link accepts from a daemon?
305
+ * Checks membership and the minimal envelope shape; `auth` additionally needs a
306
+ * token string because nothing downstream can proceed without one.
307
+ */
308
+ export declare function decodeDaemonToServer(raw: unknown): DaemonToServerMsg | null;
309
+ export declare const SERVER_TO_DAEMON_CONTROL_TYPES: readonly ["auth_ok", "auth_error", "machine_evicted", "force_disconnect", "token_revoked", "version_mismatch", "force_update_required", "command", "agent_command", "resolve_action", "beacon_vectors_result", "daemon_mesh_result", "p2p_ready", "p2p_offer", "p2p_answer", "p2p_ice", "mesh_p2p_ready", "mesh_p2p_offer", "mesh_p2p_answer", "mesh_p2p_ice"];
310
+ export type ServerToDaemonControlType = typeof SERVER_TO_DAEMON_CONTROL_TYPES[number];
311
+ export declare const isServerToDaemonControlType: (value: unknown) => value is "p2p_ready" | "p2p_offer" | "p2p_answer" | "p2p_ice" | "mesh_p2p_ready" | "mesh_p2p_offer" | "mesh_p2p_answer" | "mesh_p2p_ice" | "command" | "auth_ok" | "auth_error" | "machine_evicted" | "force_disconnect" | "token_revoked" | "version_mismatch" | "force_update_required" | "agent_command" | "resolve_action" | "beacon_vectors_result" | "daemon_mesh_result";
312
+ /** Optional routing fields the server stamps on relayed commands. */
313
+ type ServerRelayFields = {
314
+ id?: MessageId;
315
+ source?: string;
316
+ timestamp?: number;
317
+ };
318
+ export type AuthOkMsg = Envelope<'auth_ok', AuthOkWirePayload>;
319
+ export type AuthErrorMsg = Envelope<'auth_error', AuthErrorWirePayload>;
320
+ export type MachineEvictedMsg = Envelope<'machine_evicted', DisconnectNoticeWirePayload>;
321
+ export type ForceDisconnectMsg = Envelope<'force_disconnect', DisconnectNoticeWirePayload>;
322
+ export type TokenRevokedMsg = Envelope<'token_revoked', DisconnectNoticeWirePayload>;
323
+ export type VersionMismatchMsg = Envelope<'version_mismatch', VersionMismatchWirePayload>;
324
+ export type ForceUpdateRequiredMsg = Envelope<'force_update_required', ForceUpdateRequiredWirePayload>;
325
+ /** Wrapped command: `payload.command` names the command, `payload.args` carries it. */
326
+ export type ServerCommandMsg = Envelope<'command', {
327
+ command: string;
328
+ args?: CommandPayload;
329
+ } & Record<string, unknown>> & ServerRelayFields;
330
+ export type ServerAgentCommandMsg = Envelope<'agent_command', {
331
+ action: string;
332
+ } & Record<string, unknown>> & ServerRelayFields;
333
+ export type ServerResolveActionMsg = Envelope<'resolve_action', CommandPayload> & ServerRelayFields;
334
+ export type ServerP2PSignalMsg = Envelope<P2PSignalType, RelayedP2PSignalWirePayload> & ServerRelayFields;
335
+ export type ServerMeshP2PSignalMsg = Envelope<MeshP2PSignalType, MeshP2PSignalWirePayload> & ServerRelayFields;
336
+ /** Reply to a `beacon_vectors` GET, correlated by `requestId` (top-level, no payload). A PUT gets no reply. */
337
+ export type BeaconVectorsResultMsg = {
338
+ type: 'beacon_vectors_result';
339
+ requestId: string;
340
+ success: boolean;
341
+ reports?: unknown[];
342
+ truncated?: number;
343
+ error?: string;
344
+ };
345
+ /** Correlated ack/nack of a `daemon_mesh_command` (top-level, no payload). */
346
+ export type DaemonMeshResultMsg = {
347
+ type: 'daemon_mesh_result';
348
+ requestId: string;
349
+ success: boolean;
350
+ result?: unknown;
351
+ error?: string;
352
+ };
353
+ export type ServerToDaemonControlMsg = AuthOkMsg | AuthErrorMsg | MachineEvictedMsg | ForceDisconnectMsg | TokenRevokedMsg | VersionMismatchMsg | ForceUpdateRequiredMsg | ServerCommandMsg | ServerAgentCommandMsg | ServerResolveActionMsg | BeaconVectorsResultMsg | DaemonMeshResultMsg | ServerP2PSignalMsg | ServerMeshP2PSignalMsg;
354
+ declare const directCommandBrand: unique symbol;
355
+ /**
356
+ * A command name relayed as the frame `type` — the OPEN namespace the REST API
357
+ * and `shortcuts` routes feed (`read_chat`, `send_chat`, `stop_cli`, `git_*`,
358
+ * `clone_mesh_node`, `terminal_exec`, ...). The daemon routes it by name
359
+ * through its command router. Branded so it cannot be confused with a control
360
+ * member at the type level; `decodeServerToDaemon` is the only producer.
361
+ */
362
+ export type DaemonDirectCommandType = string & {
363
+ readonly [directCommandBrand]?: true;
364
+ };
365
+ export type ServerDirectCommandMsg = {
366
+ type: DaemonDirectCommandType;
367
+ payload: CommandPayload;
368
+ id?: MessageId;
369
+ source?: string;
370
+ timestamp?: number;
371
+ };
372
+ export type ServerToDaemonMsg = ServerToDaemonControlMsg | ServerDirectCommandMsg;
373
+ export type ServerToDaemonType = ServerToDaemonMsg['type'];
374
+ export declare function isServerDirectCommandMsg(msg: ServerToDaemonMsg): msg is ServerDirectCommandMsg;
375
+ /** Members of the control set that carry a `payload` object. */
376
+ export type ServerToDaemonPayloadType = Exclude<ServerToDaemonControlMsg, BeaconVectorsResultMsg | DaemonMeshResultMsg>['type'];
377
+ export type ServerToDaemonPayloadOf<T extends ServerToDaemonPayloadType> = Extract<ServerToDaemonControlMsg, {
378
+ type: T;
379
+ }>['payload'];
380
+ export declare function isDaemonDirectCommandType(value: unknown): value is DaemonDirectCommandType;
381
+ /**
382
+ * Parse boundary for the daemon: is `raw` a frame this link accepts from the server?
383
+ * Control members are checked for their discriminant and envelope shape; the
384
+ * two payload-less replies need their `requestId`; any other well-formed
385
+ * command name becomes a `ServerDirectCommandMsg`.
386
+ */
387
+ export declare function decodeServerToDaemon(raw: unknown): ServerToDaemonMsg | null;
388
+ export {};
@@ -0,0 +1,212 @@
1
+ /**
2
+ * protocol/dashboard-daemon-p2p — the dashboard ↔ daemon WebRTC DataChannel
3
+ * link (`data` channel, JSON frames; screenshots ride the same channel as
4
+ * binary and are not frames).
5
+ *
6
+ * Wiring-unification Phase A2 (docs/design/2026-09-23-wiring-unification.md §3 A2).
7
+ *
8
+ * Before this file ~20 kinds were matched as bare string literals on both ends
9
+ * (web-cloud `p2p.ts`, daemon-cloud `data-channel-router.ts`), the shared
10
+ * `DashboardP2PMessageKind` listed 12 of them and was imported by nothing, and
11
+ * the daemon's file-operation replies carried no `type` at all — the browser
12
+ * matched them by `id` as a fallthrough. They are now `response` frames; the
13
+ * browser decoder still accepts the typeless legacy shape from older daemons.
14
+ *
15
+ * Chunking: any request frame may be split by the sender into
16
+ * `<kind>_chunk` frames (`id`, `chunkId`, `index`, `total`, `data`) when it
17
+ * exceeds `MESH_MAX_INLINE_FRAME_BYTES` (../rpc-chunking.ts). Each direction
18
+ * reassembles BEFORE decoding, so the decoders see whole frames; the chunk
19
+ * envelope is typed here so the splitter and the reassembler agree.
20
+ *
21
+ * Content boundary: this is the P2P plane — rich session data (chat, prompts,
22
+ * transcripts, PTY output) is CORRECT here and must not be reduced.
23
+ */
24
+ import { type CommandPayload, type MessageId } from './envelope';
25
+ import type { DaemonStatusEventWirePayload } from './daemon-server';
26
+ export type ChunkFrame<T extends string> = {
27
+ type: T;
28
+ /** Request id of the frame being split, when it has one. */
29
+ id?: MessageId;
30
+ chunkId: string;
31
+ index: number;
32
+ total: number;
33
+ data: string;
34
+ };
35
+ export declare function isChunkType(value: unknown): value is `${string}_chunk`;
36
+ export declare const DASHBOARD_TO_DAEMON_P2P_TYPES: readonly ["ping", "pong", "command", "input", "pty_input", "pty_resize", "screenshot_start", "screenshot_stop", "subscribe", "unsubscribe", "seqscribe_session_interest", "read", "write", "list"];
37
+ export type DashboardToDaemonP2PType = typeof DASHBOARD_TO_DAEMON_P2P_TYPES[number];
38
+ export declare const isDashboardToDaemonP2PType: (value: unknown) => value is "list" | "command" | "ping" | "pong" | "input" | "pty_input" | "pty_resize" | "screenshot_start" | "screenshot_stop" | "subscribe" | "unsubscribe" | "seqscribe_session_interest" | "read" | "write";
39
+ export type DashboardToDaemonChunkType = `${DashboardToDaemonP2PType}_chunk`;
40
+ export declare function isDashboardToDaemonChunkType(value: unknown): value is DashboardToDaemonChunkType;
41
+ export type P2PPingMsg = {
42
+ type: 'ping';
43
+ ts: number;
44
+ };
45
+ export type P2PPongMsg = {
46
+ type: 'pong';
47
+ ts: number;
48
+ };
49
+ /** A command routed through the daemon's command router; `commandType`/`data` are dynamic by design. */
50
+ export type P2PCommandMsg = {
51
+ type: 'command';
52
+ id: MessageId;
53
+ commandType: string;
54
+ data: CommandPayload;
55
+ };
56
+ /** Remote-desktop input (click/type/key) for the target session's IDE surface. */
57
+ export type P2PInputMsg = {
58
+ type: 'input';
59
+ id: MessageId;
60
+ action: string;
61
+ params: CommandPayload;
62
+ targetSessionId?: string;
63
+ };
64
+ /** Raw PTY input. `sessionId` is canonical; `targetSessionId` is the accepted alias. */
65
+ export type P2PPtyInputMsg = {
66
+ type: 'pty_input';
67
+ sessionId?: string;
68
+ targetSessionId?: string;
69
+ data: string;
70
+ };
71
+ export type P2PPtyResizeMsg = {
72
+ type: 'pty_resize';
73
+ sessionId: string;
74
+ cols: number;
75
+ rows: number;
76
+ };
77
+ export type P2PScreenshotStartMsg = {
78
+ type: 'screenshot_start';
79
+ targetSessionId: string;
80
+ };
81
+ export type P2PScreenshotStopMsg = {
82
+ type: 'screenshot_stop';
83
+ targetSessionId?: string;
84
+ };
85
+ /** Topic subscription. `params` is topic-specific (daemon-core `SubscribeRequestMap`); `object` keeps every topic's interface assignable. */
86
+ export type P2PSubscribeMsg = {
87
+ type: 'subscribe';
88
+ topic: string;
89
+ key: string;
90
+ params: object;
91
+ };
92
+ export type P2PUnsubscribeMsg = {
93
+ type: 'unsubscribe';
94
+ topic: string;
95
+ key: string;
96
+ };
97
+ /** Which sessions this peer wants replicated over the seqscribe lane; also the peer's responder proof. */
98
+ export type P2PSessionInterestMsg = {
99
+ type: 'seqscribe_session_interest';
100
+ sessionIds: readonly string[];
101
+ };
102
+ export type P2PFileReadMsg = {
103
+ type: 'read';
104
+ id: MessageId;
105
+ path: string;
106
+ };
107
+ export type P2PFileWriteMsg = {
108
+ type: 'write';
109
+ id: MessageId;
110
+ path: string;
111
+ content: string;
112
+ };
113
+ export type P2PFileListMsg = {
114
+ type: 'list';
115
+ id: MessageId;
116
+ path: string;
117
+ };
118
+ export type DashboardToDaemonChunkMsg = ChunkFrame<DashboardToDaemonChunkType>;
119
+ export type DashboardToDaemonP2PMsg = P2PPingMsg | P2PPongMsg | P2PCommandMsg | P2PInputMsg | P2PPtyInputMsg | P2PPtyResizeMsg | P2PScreenshotStartMsg | P2PScreenshotStopMsg | P2PSubscribeMsg | P2PUnsubscribeMsg | P2PSessionInterestMsg | P2PFileReadMsg | P2PFileWriteMsg | P2PFileListMsg;
120
+ /** Parse boundary for the daemon's chunk reassembler: one `<kind>_chunk` piece of a split request. */
121
+ export declare function decodeDashboardToDaemonP2PChunk(raw: unknown): DashboardToDaemonChunkMsg | null;
122
+ /**
123
+ * Parse boundary for the daemon (after chunk reassembly): a whole request.
124
+ * Chunk pieces are not requests and decode to null here; see
125
+ * `decodeDashboardToDaemonP2PChunk`.
126
+ */
127
+ export declare function decodeDashboardToDaemonP2P(raw: unknown): DashboardToDaemonP2PMsg | null;
128
+ export declare const DAEMON_TO_DASHBOARD_P2P_TYPES: readonly ["ping", "pong", "p2p_evicted", "status_report", "status_event", "topic_update", "topic_update_chunk", "session_output", "session_output_chunk", "session_io_error", "command_result", "command_result_chunk", "response"];
129
+ export type DaemonToDashboardP2PType = typeof DAEMON_TO_DASHBOARD_P2P_TYPES[number];
130
+ export declare const isDaemonToDashboardP2PType: (value: unknown) => value is "status_report" | "status_event" | "command_result" | "ping" | "pong" | "p2p_evicted" | "topic_update" | "topic_update_chunk" | "session_output" | "session_output_chunk" | "session_io_error" | "command_result_chunk" | "response";
131
+ /** Rich P2P status snapshot (daemon-core `StatusReportPayload`, or a `_delta` partial of it). Dynamic by design on this plane. */
132
+ export type P2PStatusReportWirePayload = Record<string, unknown>;
133
+ /** The server-bound event plus the P2P-only structured prompt (daemon-core `P2PStatusEventPayload`). */
134
+ export type P2PStatusEventWirePayload = DaemonStatusEventWirePayload & {
135
+ interactivePrompt?: unknown;
136
+ promptId?: string;
137
+ multiSelect?: boolean;
138
+ };
139
+ /** One topic update (daemon-core `TopicUpdateEnvelope`); consumers narrow by `topic`. */
140
+ export type TopicUpdateWireEnvelope = {
141
+ topic: string;
142
+ key: string;
143
+ };
144
+ export type P2PFileEntry = {
145
+ name: string;
146
+ type: string;
147
+ size?: number;
148
+ };
149
+ export type P2PEvictedMsg = {
150
+ type: 'p2p_evicted';
151
+ reason: string;
152
+ maxconnections: number;
153
+ };
154
+ export type P2PStatusReportMsg = {
155
+ type: 'status_report';
156
+ payload: P2PStatusReportWirePayload;
157
+ timestamp: number;
158
+ };
159
+ export type P2PStatusEventMsg = {
160
+ type: 'status_event';
161
+ payload: P2PStatusEventWirePayload;
162
+ timestamp: number;
163
+ };
164
+ export type P2PTopicUpdateMsg = {
165
+ type: 'topic_update';
166
+ update: TopicUpdateWireEnvelope;
167
+ };
168
+ export type P2PTopicUpdateChunkMsg = ChunkFrame<'topic_update_chunk'>;
169
+ export type P2PSessionOutputMsg = {
170
+ type: 'session_output';
171
+ sessionId: string;
172
+ data: string;
173
+ };
174
+ export type P2PSessionOutputChunkMsg = ChunkFrame<'session_output_chunk'> & {
175
+ sessionId: string;
176
+ };
177
+ export type P2PSessionIoErrorMsg = {
178
+ type: 'session_io_error';
179
+ sessionId: string;
180
+ reason: string;
181
+ error?: string;
182
+ };
183
+ /** Result of a `command` frame. The router spreads the handler result in; `resultId` preserves a result-side `id`. */
184
+ export type P2PCommandResultMsg = {
185
+ type: 'command_result';
186
+ id: MessageId;
187
+ success: boolean;
188
+ error?: string;
189
+ resultId?: unknown;
190
+ } & Record<string, unknown>;
191
+ export type P2PCommandResultChunkMsg = ChunkFrame<'command_result_chunk'>;
192
+ /** Result of an `input` or file (`read`/`write`/`list`) frame. */
193
+ export type P2PResponseMsg = {
194
+ type: 'response';
195
+ id: MessageId;
196
+ success: boolean;
197
+ result?: unknown;
198
+ error?: string;
199
+ content?: string;
200
+ entries?: P2PFileEntry[];
201
+ };
202
+ export type DaemonToDashboardP2PMsg = P2PPingMsg | P2PPongMsg | P2PEvictedMsg | P2PStatusReportMsg | P2PStatusEventMsg | P2PTopicUpdateMsg | P2PTopicUpdateChunkMsg | P2PSessionOutputMsg | P2PSessionOutputChunkMsg | P2PSessionIoErrorMsg | P2PCommandResultMsg | P2PCommandResultChunkMsg | P2PResponseMsg;
203
+ /**
204
+ * Parse boundary for the dashboard (after chunk reassembly).
205
+ *
206
+ * A typeless `{ id, success }` frame is a file/input reply from a daemon that
207
+ * predates the `response` kind; it is promoted here so the union stays
208
+ * exhaustive while the fleet rolls forward.
209
+ */
210
+ export declare function decodeDaemonToDashboardP2P(raw: unknown): DaemonToDashboardP2PMsg | null;
211
+ /** Both directions, for the legacy `DashboardP2PMessageKind` name and the drift gate. */
212
+ export declare const DASHBOARD_P2P_MESSAGE_KINDS: ReadonlyArray<DashboardToDaemonP2PType | DaemonToDashboardP2PType>;