@adhdev/mesh-shared 1.0.60-rc.4 → 1.0.60-rc.41

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 (39) hide show
  1. package/dist/index.d.ts +10 -0
  2. package/dist/index.js +2764 -17
  3. package/dist/index.js.map +1 -1
  4. package/dist/index.mjs +2441 -17
  5. package/dist/index.mjs.map +1 -1
  6. package/dist/mesh-tool-names.d.ts +2 -2
  7. package/dist/mesh-vocabulary.d.ts +70 -0
  8. package/dist/mission-brief.d.ts +62 -0
  9. package/dist/outbound-message.d.ts +202 -0
  10. package/dist/path-ownership.d.ts +101 -0
  11. package/dist/protocol/daemon-server.d.ts +388 -0
  12. package/dist/protocol/dashboard-daemon-p2p.d.ts +212 -0
  13. package/dist/protocol/dashboard-server.d.ts +240 -0
  14. package/dist/protocol/envelope.d.ts +55 -0
  15. package/dist/protocol/index.d.ts +15 -0
  16. package/dist/session-launch.d.ts +113 -0
  17. package/dist/session-status.d.ts +61 -0
  18. package/dist/turn-evidence.d.ts +407 -0
  19. package/dist/turn-ipc.d.ts +974 -0
  20. package/dist/worker-protocol.d.ts +69 -0
  21. package/dist/ws-protocol.d.ts +71 -41
  22. package/package.json +1 -1
  23. package/src/index.ts +10 -0
  24. package/src/mesh-tool-names.ts +0 -1
  25. package/src/mesh-vocabulary.ts +98 -0
  26. package/src/mission-brief.ts +153 -0
  27. package/src/outbound-message.ts +320 -0
  28. package/src/path-ownership.ts +271 -0
  29. package/src/protocol/daemon-server.ts +552 -0
  30. package/src/protocol/dashboard-daemon-p2p.ts +272 -0
  31. package/src/protocol/dashboard-server.ts +340 -0
  32. package/src/protocol/envelope.ts +75 -0
  33. package/src/protocol/index.ts +16 -0
  34. package/src/session-launch.ts +247 -0
  35. package/src/session-status.ts +140 -0
  36. package/src/turn-evidence.ts +562 -0
  37. package/src/turn-ipc.ts +2231 -0
  38. package/src/worker-protocol.ts +142 -0
  39. package/src/ws-protocol.ts +102 -93
@@ -0,0 +1,552 @@
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
+
26
+ import type { AuthOkPlanLimits } from '../ws-protocol'
27
+ import type { SessionStatus } from '../session-status'
28
+ import {
29
+ type CommandPayload,
30
+ type Envelope,
31
+ type MessageId,
32
+ type AssertSameMembers,
33
+ isRecord,
34
+ isNonEmptyString,
35
+ makeTypeGuard,
36
+ readEnvelopeFields,
37
+ } from './envelope'
38
+
39
+ // ─── Status events (mirror of daemon-core `DaemonStatusEventName`) ─────────
40
+
41
+ /**
42
+ * Daemon-originated status events. The daemon relays these to the server
43
+ * (push notifications, webhooks) AND over P2P (dashboard). Mirror of
44
+ * daemon-core `DaemonStatusEventName`; the server pins the two equal at compile
45
+ * time (UserSession.ts), so adding a member here without adding it there — or
46
+ * vice versa — fails `tsc`.
47
+ */
48
+ export const DAEMON_STATUS_EVENT_NAMES = [
49
+ 'agent:generating_started',
50
+ 'agent:waiting_approval',
51
+ 'agent:waiting_choice',
52
+ 'agent:generating_completed',
53
+ 'agent:stopped',
54
+ 'monitor:no_progress',
55
+ /** Legacy alias for `monitor:no_progress`, still emitted by older daemons. */
56
+ 'monitor:long_generating',
57
+ ] as const
58
+ export type DaemonStatusEventName = typeof DAEMON_STATUS_EVENT_NAMES[number]
59
+ export const isDaemonStatusEventName = makeTypeGuard(DAEMON_STATUS_EVENT_NAMES)
60
+
61
+ /**
62
+ * Server-bound status event. Content boundary: identifiers, enums, counters
63
+ * and booleans only — plus the approved approval-modal exception
64
+ * (`modalMessage`/`modalButtons`, CLAUDE.md "Push notification exception").
65
+ * Mirror of daemon-core `DaemonStatusEventPayload`.
66
+ */
67
+ export type DaemonStatusEventWirePayload = {
68
+ event: DaemonStatusEventName
69
+ timestamp: number
70
+ targetSessionId?: string
71
+ providerType?: string
72
+ providerSessionId?: string
73
+ workspaceName?: string
74
+ duration?: number
75
+ elapsedSec?: number
76
+ modalMessage?: string
77
+ modalButtons?: string[]
78
+ surfaceHidden?: boolean
79
+ muted?: boolean
80
+ }
81
+
82
+ // ─── Status report (mirror of daemon-core `CloudStatusReportPayload`) ──────
83
+
84
+ /** Routing-only session entry the daemon reports over the server WS. Mirror of daemon-core `RoutingSessionEntry`. */
85
+ export type RoutingSessionWireEntry = {
86
+ id: string
87
+ parentId: string | null
88
+ providerType: string
89
+ providerName: string
90
+ kind: string
91
+ transport: string
92
+ status: SessionStatus
93
+ workspace: string | null
94
+ cdpConnected?: boolean
95
+ surfaceHidden?: boolean
96
+ muted?: boolean
97
+ }
98
+
99
+ /** Counters and enums only — never a peer id or address. Mirror of daemon-core `P2PStatusSummary`. */
100
+ export type P2PStatusWireSummary = {
101
+ available: boolean
102
+ state: string
103
+ peers: number
104
+ screenshotActive?: boolean
105
+ direct?: number
106
+ relay?: number
107
+ unknownTransport?: number
108
+ directTotal?: number
109
+ relayTotal?: number
110
+ }
111
+
112
+ /**
113
+ * seqscribe replication health — pre-bucketed counters and booleans only,
114
+ * never a topic name or peer id. Mirror of daemon-core `SeqscribeStatusSummary`
115
+ * (which should derive from this once shared-types is on the protocol).
116
+ */
117
+ export type SeqscribeStatusWireSummary = {
118
+ topics: number
119
+ peers: number
120
+ peersReady: number
121
+ pendingBucket: number
122
+ consumerLagBucket: number
123
+ queueBucket: number
124
+ fgenAgeBucket: number
125
+ quarantined: boolean
126
+ authority: boolean
127
+ dualWrite?: boolean
128
+ dualWriteFailedBucket?: number
129
+ dualWriteDroppedBucket?: number
130
+ dualWriteBackfilledBucket?: number
131
+ parityMismatchBucket?: number
132
+ parityRan?: boolean
133
+ parityMissingInShadowBucket?: number
134
+ parityExtraInShadowBucket?: number
135
+ parityFieldMismatchBucket?: number
136
+ transcriptPublish?: boolean
137
+ transcriptPublishedBucket?: number
138
+ transcriptPublishFailedBucket?: number
139
+ transcriptDedupedBucket?: number
140
+ transcriptOversizedBucket?: number
141
+ transcriptDroppedBucket?: number
142
+ transcriptParityRan?: boolean
143
+ transcriptParityMismatchBucket?: number
144
+ }
145
+
146
+ export type CloudStatusReportWirePayload = {
147
+ sessions: RoutingSessionWireEntry[]
148
+ p2p?: P2PStatusWireSummary
149
+ seqscribe?: SeqscribeStatusWireSummary
150
+ timestamp: number
151
+ }
152
+
153
+ // ─── Release / update vocabulary (mirror of daemon-core shared-types) ──────
154
+
155
+ export const RELEASE_CHANNELS = ['stable', 'preview'] as const
156
+ export type ReleaseChannelWire = typeof RELEASE_CHANNELS[number]
157
+ export const NPM_UPDATE_TAGS = ['latest', 'next'] as const
158
+ export type NpmUpdateTagWire = typeof NPM_UPDATE_TAGS[number]
159
+ export const VERSION_UPDATE_REASONS = ['force_update_below', 'major_minor_mismatch', 'patch_mismatch', 'daemon_ahead'] as const
160
+ export type VersionUpdateReasonWire = typeof VERSION_UPDATE_REASONS[number]
161
+
162
+ /** Mirror of daemon-core `VersionUpdatePolicy`. */
163
+ export type VersionUpdatePolicyWire = {
164
+ channel: ReleaseChannelWire
165
+ npmTag: NpmUpdateTagWire
166
+ targetVersion: string
167
+ minVersion?: string
168
+ updateCommand: string
169
+ }
170
+
171
+ export type StatusHeartbeatWirePayload = {
172
+ timestamp?: number
173
+ p2pPeers?: number
174
+ }
175
+
176
+ // ─── Auth ──────────────────────────────────────────────────────────────────
177
+
178
+ /** What the daemon says about itself at auth time (`ServerConnectionOptions.cliInfo`). */
179
+ export type DaemonAuthIdentity = {
180
+ type: string
181
+ version: string
182
+ platform: string
183
+ hostname?: string
184
+ machineId?: string
185
+ /** Canonical mesh identity `daemon_<machineId>`; the server keys its DO and status by it. */
186
+ instanceId: string
187
+ }
188
+
189
+ export type DaemonAuthWirePayload = {
190
+ token: string
191
+ daemon: DaemonAuthIdentity
192
+ }
193
+
194
+ export type IceServerWireEntry = {
195
+ urls: string | string[]
196
+ username?: string
197
+ credential?: string
198
+ }
199
+
200
+ export type AuthOkWirePayload = {
201
+ message?: string
202
+ plan: string
203
+ iceServers: IceServerWireEntry[]
204
+ turnenabled: boolean
205
+ serverVersion: string
206
+ releaseChannel?: ReleaseChannelWire
207
+ updatePolicy?: VersionUpdatePolicyWire
208
+ machineNickname: string | null
209
+ seqscribeFleetSecret?: string
210
+ seqscribeFleetSecretVersion?: number
211
+ /** Beacon board seed for cold start (advisory, may be absent). */
212
+ beaconSeed?: { reports: unknown[]; truncated: number }
213
+ limits: AuthOkPlanLimits
214
+ }
215
+
216
+ export type AuthErrorWirePayload = {
217
+ reason: string
218
+ message: string
219
+ limit?: number
220
+ current?: number
221
+ }
222
+
223
+ /** `machine_evicted` / `force_disconnect` / `token_revoked` — a reason token plus human text, then the socket closes. */
224
+ export type DisconnectNoticeWirePayload = {
225
+ reason: string
226
+ message: string
227
+ }
228
+
229
+ export type VersionMismatchWirePayload = {
230
+ current: string
231
+ latest: string
232
+ required: false
233
+ reason: VersionUpdateReasonWire
234
+ channel: ReleaseChannelWire
235
+ npmTag: NpmUpdateTagWire
236
+ updatePolicy: VersionUpdatePolicyWire
237
+ updateCommand: string
238
+ }
239
+
240
+ export type ForceUpdateRequiredWirePayload = {
241
+ current: string
242
+ latest: string
243
+ minVersion: string
244
+ required: true
245
+ reason: VersionUpdateReasonWire
246
+ channel: ReleaseChannelWire
247
+ npmTag: NpmUpdateTagWire
248
+ updatePolicy: VersionUpdatePolicyWire
249
+ updateCommand: string
250
+ forceUpdateBelow?: string
251
+ message: string
252
+ }
253
+
254
+ // ─── Commands and results ──────────────────────────────────────────────────
255
+
256
+ export type CommandResultWirePayload = {
257
+ requestId: MessageId
258
+ success: boolean
259
+ source?: string
260
+ } & Record<string, unknown>
261
+
262
+ export type DaemonErrorWirePayload = {
263
+ message?: string
264
+ detail?: unknown
265
+ }
266
+
267
+ // ─── Beacon (seqscribe vector board, design §7.1) ──────────────────────────
268
+
269
+ export type BeaconVectorsWirePayload =
270
+ | { op: 'put'; report: unknown }
271
+ | { op: 'get'; topics: string[]; requestId: string }
272
+
273
+ // ─── P2P signaling (dashboard ↔ daemon through the server) ─────────────────
274
+
275
+ export const P2P_SIGNAL_TYPES = ['p2p_ready', 'p2p_offer', 'p2p_answer', 'p2p_ice'] as const
276
+ export type P2PSignalType = typeof P2P_SIGNAL_TYPES[number]
277
+ export const isP2PSignalType = makeTypeGuard(P2P_SIGNAL_TYPES)
278
+
279
+ /** Signals the daemon originates (it is the WebRTC offerer). */
280
+ export const DAEMON_P2P_SIGNAL_TYPES = ['p2p_offer', 'p2p_ice'] as const
281
+ export type DaemonP2PSignalType = typeof DAEMON_P2P_SIGNAL_TYPES[number]
282
+ export const isDaemonP2PSignalType = makeTypeGuard(DAEMON_P2P_SIGNAL_TYPES)
283
+
284
+ export type P2POfferWirePayload = { sdp: string; type: string; peerId: string }
285
+ export type P2PIceWirePayload = { candidate: string; mid: string; peerId: string }
286
+
287
+ /**
288
+ * A dashboard-originated signal as relayed to the daemon. `peerId` is stamped
289
+ * by UserSession/SharedSession, `sharePermission` only by SharedSession (server
290
+ * authority, never the client). Field names follow the browser/node-datachannel
291
+ * spellings both ends already read.
292
+ */
293
+ export type RelayedP2PSignalWirePayload = {
294
+ peerId?: string
295
+ daemonId?: string
296
+ sharePermission?: string
297
+ sdp?: string
298
+ type?: string
299
+ candidate?: string
300
+ mid?: string
301
+ sdpMid?: string | null
302
+ sdpMLineIndex?: number | null
303
+ }
304
+
305
+ export const MESH_P2P_SIGNAL_TYPES = ['mesh_p2p_ready', 'mesh_p2p_offer', 'mesh_p2p_answer', 'mesh_p2p_ice'] as const
306
+ export type MeshP2PSignalType = typeof MESH_P2P_SIGNAL_TYPES[number]
307
+ export const isMeshP2PSignalType = makeTypeGuard(MESH_P2P_SIGNAL_TYPES)
308
+
309
+ /** Daemon→daemon signal as delivered to the target daemon: the sender's args plus the server-stamped canonical sender id. */
310
+ export type MeshP2PSignalWirePayload = { senderDaemonId: string } & Record<string, unknown>
311
+
312
+ // ─── Daemon → Server ───────────────────────────────────────────────────────
313
+
314
+ export const DAEMON_TO_SERVER_TYPES = [
315
+ 'auth',
316
+ 'status_report',
317
+ 'status_heartbeat',
318
+ 'status_event',
319
+ 'command_result',
320
+ 'error',
321
+ 'agent_event',
322
+ 'beacon_vectors',
323
+ 'daemon_mesh_command',
324
+ 'p2p_offer',
325
+ 'p2p_ice',
326
+ ] as const
327
+ export type DaemonToServerType = typeof DAEMON_TO_SERVER_TYPES[number]
328
+ export const isDaemonToServerType = makeTypeGuard(DAEMON_TO_SERVER_TYPES)
329
+
330
+ export type DaemonAuthMsg = Envelope<'auth', DaemonAuthWirePayload>
331
+ export type StatusReportMsg = Envelope<'status_report', CloudStatusReportWirePayload>
332
+ export type StatusHeartbeatMsg = Envelope<'status_heartbeat', StatusHeartbeatWirePayload>
333
+ export type DaemonStatusEventMsg = Envelope<'status_event', DaemonStatusEventWirePayload>
334
+ export type CommandResultMsg = Envelope<'command_result', CommandResultWirePayload>
335
+ export type DaemonErrorMsg = Envelope<'error', DaemonErrorWirePayload>
336
+ /** Agent-level event forwarded verbatim to the `agent:status` webhook (throttled server-side). Dynamic by design. */
337
+ export type AgentEventMsg = Envelope<'agent_event', CommandPayload>
338
+ export type BeaconVectorsMsg = Envelope<'beacon_vectors', BeaconVectorsWirePayload>
339
+ export type DaemonP2POfferMsg = Envelope<'p2p_offer', P2POfferWirePayload>
340
+ export type DaemonP2PIceMsg = Envelope<'p2p_ice', P2PIceWirePayload>
341
+ /**
342
+ * Daemon→daemon signaling relay request. Fields are TOP-LEVEL (no `payload`):
343
+ * this is the wire shape the live fleet speaks, kept as-is so mixed-version
344
+ * daemons and servers keep interoperating during a rollout.
345
+ */
346
+ export type DaemonMeshCommandMsg = {
347
+ type: 'daemon_mesh_command'
348
+ requestId: string
349
+ targetDaemonId: string
350
+ command: MeshP2PSignalType
351
+ args: Record<string, unknown>
352
+ timestamp: number
353
+ }
354
+
355
+ export type DaemonToServerMsg =
356
+ | DaemonAuthMsg
357
+ | StatusReportMsg
358
+ | StatusHeartbeatMsg
359
+ | DaemonStatusEventMsg
360
+ | CommandResultMsg
361
+ | DaemonErrorMsg
362
+ | AgentEventMsg
363
+ | BeaconVectorsMsg
364
+ | DaemonMeshCommandMsg
365
+ | DaemonP2POfferMsg
366
+ | DaemonP2PIceMsg
367
+
368
+ const _daemonToServerNamesCoverUnion: AssertSameMembers<typeof DAEMON_TO_SERVER_TYPES, DaemonToServerMsg['type']> = true
369
+ void _daemonToServerNamesCoverUnion
370
+
371
+ /** Members that carry a `payload` object (everything except `daemon_mesh_command`). */
372
+ export type DaemonToServerPayloadType = Exclude<DaemonToServerMsg, DaemonMeshCommandMsg>['type']
373
+ export type DaemonToServerPayloadOf<T extends DaemonToServerPayloadType> = Extract<DaemonToServerMsg, { type: T }>['payload']
374
+
375
+ /**
376
+ * Parse boundary for the server: is `raw` a frame this link accepts from a daemon?
377
+ * Checks membership and the minimal envelope shape; `auth` additionally needs a
378
+ * token string because nothing downstream can proceed without one.
379
+ */
380
+ export function decodeDaemonToServer(raw: unknown): DaemonToServerMsg | null {
381
+ if (!isRecord(raw) || !isDaemonToServerType(raw.type)) return null
382
+ if (raw.type === 'daemon_mesh_command') {
383
+ // Handler-side validation replies with a correlated `daemon_mesh_result`
384
+ // error, so only the discriminant is checked here.
385
+ return {
386
+ type: 'daemon_mesh_command',
387
+ requestId: typeof raw.requestId === 'string' ? raw.requestId : String(raw.requestId ?? ''),
388
+ targetDaemonId: typeof raw.targetDaemonId === 'string' ? raw.targetDaemonId : '',
389
+ command: raw.command as MeshP2PSignalType,
390
+ args: isRecord(raw.args) ? raw.args : {},
391
+ timestamp: typeof raw.timestamp === 'number' ? raw.timestamp : Date.now(),
392
+ }
393
+ }
394
+ if (!isRecord(raw.payload)) return null
395
+ if (raw.type === 'auth' && !isNonEmptyString(raw.payload.token)) return null
396
+ return { type: raw.type, payload: raw.payload, ...readEnvelopeFields(raw) } as DaemonToServerMsg
397
+ }
398
+
399
+ // ─── Server → Daemon ───────────────────────────────────────────────────────
400
+
401
+ export const SERVER_TO_DAEMON_CONTROL_TYPES = [
402
+ 'auth_ok',
403
+ 'auth_error',
404
+ 'machine_evicted',
405
+ 'force_disconnect',
406
+ 'token_revoked',
407
+ 'version_mismatch',
408
+ 'force_update_required',
409
+ 'command',
410
+ 'agent_command',
411
+ 'resolve_action',
412
+ 'beacon_vectors_result',
413
+ 'daemon_mesh_result',
414
+ 'p2p_ready',
415
+ 'p2p_offer',
416
+ 'p2p_answer',
417
+ 'p2p_ice',
418
+ 'mesh_p2p_ready',
419
+ 'mesh_p2p_offer',
420
+ 'mesh_p2p_answer',
421
+ 'mesh_p2p_ice',
422
+ ] as const
423
+ export type ServerToDaemonControlType = typeof SERVER_TO_DAEMON_CONTROL_TYPES[number]
424
+ export const isServerToDaemonControlType = makeTypeGuard(SERVER_TO_DAEMON_CONTROL_TYPES)
425
+
426
+ /** Optional routing fields the server stamps on relayed commands. */
427
+ type ServerRelayFields = { id?: MessageId; source?: string; timestamp?: number }
428
+
429
+ export type AuthOkMsg = Envelope<'auth_ok', AuthOkWirePayload>
430
+ export type AuthErrorMsg = Envelope<'auth_error', AuthErrorWirePayload>
431
+ export type MachineEvictedMsg = Envelope<'machine_evicted', DisconnectNoticeWirePayload>
432
+ export type ForceDisconnectMsg = Envelope<'force_disconnect', DisconnectNoticeWirePayload>
433
+ export type TokenRevokedMsg = Envelope<'token_revoked', DisconnectNoticeWirePayload>
434
+ export type VersionMismatchMsg = Envelope<'version_mismatch', VersionMismatchWirePayload>
435
+ export type ForceUpdateRequiredMsg = Envelope<'force_update_required', ForceUpdateRequiredWirePayload>
436
+ /** Wrapped command: `payload.command` names the command, `payload.args` carries it. */
437
+ export type ServerCommandMsg = Envelope<'command', { command: string; args?: CommandPayload } & Record<string, unknown>> & ServerRelayFields
438
+ export type ServerAgentCommandMsg = Envelope<'agent_command', { action: string } & Record<string, unknown>> & ServerRelayFields
439
+ export type ServerResolveActionMsg = Envelope<'resolve_action', CommandPayload> & ServerRelayFields
440
+ export type ServerP2PSignalMsg = Envelope<P2PSignalType, RelayedP2PSignalWirePayload> & ServerRelayFields
441
+ export type ServerMeshP2PSignalMsg = Envelope<MeshP2PSignalType, MeshP2PSignalWirePayload> & ServerRelayFields
442
+ /** Reply to a `beacon_vectors` GET, correlated by `requestId` (top-level, no payload). A PUT gets no reply. */
443
+ export type BeaconVectorsResultMsg = {
444
+ type: 'beacon_vectors_result'
445
+ requestId: string
446
+ success: boolean
447
+ reports?: unknown[]
448
+ truncated?: number
449
+ error?: string
450
+ }
451
+ /** Correlated ack/nack of a `daemon_mesh_command` (top-level, no payload). */
452
+ export type DaemonMeshResultMsg = {
453
+ type: 'daemon_mesh_result'
454
+ requestId: string
455
+ success: boolean
456
+ result?: unknown
457
+ error?: string
458
+ }
459
+
460
+ export type ServerToDaemonControlMsg =
461
+ | AuthOkMsg
462
+ | AuthErrorMsg
463
+ | MachineEvictedMsg
464
+ | ForceDisconnectMsg
465
+ | TokenRevokedMsg
466
+ | VersionMismatchMsg
467
+ | ForceUpdateRequiredMsg
468
+ | ServerCommandMsg
469
+ | ServerAgentCommandMsg
470
+ | ServerResolveActionMsg
471
+ | BeaconVectorsResultMsg
472
+ | DaemonMeshResultMsg
473
+ | ServerP2PSignalMsg
474
+ | ServerMeshP2PSignalMsg
475
+
476
+ const _serverToDaemonControlNamesCoverUnion: AssertSameMembers<typeof SERVER_TO_DAEMON_CONTROL_TYPES, ServerToDaemonControlMsg['type']> = true
477
+ void _serverToDaemonControlNamesCoverUnion
478
+
479
+ declare const directCommandBrand: unique symbol
480
+ /**
481
+ * A command name relayed as the frame `type` — the OPEN namespace the REST API
482
+ * and `shortcuts` routes feed (`read_chat`, `send_chat`, `stop_cli`, `git_*`,
483
+ * `clone_mesh_node`, `terminal_exec`, ...). The daemon routes it by name
484
+ * through its command router. Branded so it cannot be confused with a control
485
+ * member at the type level; `decodeServerToDaemon` is the only producer.
486
+ */
487
+ export type DaemonDirectCommandType = string & { readonly [directCommandBrand]?: true }
488
+
489
+ export type ServerDirectCommandMsg = {
490
+ type: DaemonDirectCommandType
491
+ payload: CommandPayload
492
+ id?: MessageId
493
+ source?: string
494
+ timestamp?: number
495
+ }
496
+
497
+ export type ServerToDaemonMsg = ServerToDaemonControlMsg | ServerDirectCommandMsg
498
+ export type ServerToDaemonType = ServerToDaemonMsg['type']
499
+
500
+ export function isServerDirectCommandMsg(msg: ServerToDaemonMsg): msg is ServerDirectCommandMsg {
501
+ return !isServerToDaemonControlType(msg.type)
502
+ }
503
+
504
+ /** Members of the control set that carry a `payload` object. */
505
+ export type ServerToDaemonPayloadType = Exclude<ServerToDaemonControlMsg, BeaconVectorsResultMsg | DaemonMeshResultMsg>['type']
506
+ export type ServerToDaemonPayloadOf<T extends ServerToDaemonPayloadType> = Extract<ServerToDaemonControlMsg, { type: T }>['payload']
507
+
508
+ /** Command types a daemon must never receive as a direct command: they are daemon→server frames or reserved envelope words. */
509
+ const DIRECT_COMMAND_TYPE_PATTERN = /^[a-z][a-z0-9_:.-]{0,63}$/
510
+
511
+ export function isDaemonDirectCommandType(value: unknown): value is DaemonDirectCommandType {
512
+ return typeof value === 'string' && DIRECT_COMMAND_TYPE_PATTERN.test(value) && !isServerToDaemonControlType(value)
513
+ }
514
+
515
+ /**
516
+ * Parse boundary for the daemon: is `raw` a frame this link accepts from the server?
517
+ * Control members are checked for their discriminant and envelope shape; the
518
+ * two payload-less replies need their `requestId`; any other well-formed
519
+ * command name becomes a `ServerDirectCommandMsg`.
520
+ */
521
+ export function decodeServerToDaemon(raw: unknown): ServerToDaemonMsg | null {
522
+ if (!isRecord(raw) || typeof raw.type !== 'string') return null
523
+ const relay = {
524
+ ...readEnvelopeFields(raw),
525
+ ...(isNonEmptyString(raw.source) ? { source: raw.source } : {}),
526
+ }
527
+ if (raw.type === 'beacon_vectors_result' || raw.type === 'daemon_mesh_result') {
528
+ if (!isNonEmptyString(raw.requestId)) return null
529
+ const base = { requestId: raw.requestId, success: raw.success === true }
530
+ if (raw.type === 'beacon_vectors_result') {
531
+ return {
532
+ type: 'beacon_vectors_result',
533
+ ...base,
534
+ ...(Array.isArray(raw.reports) ? { reports: raw.reports } : {}),
535
+ ...(typeof raw.truncated === 'number' ? { truncated: raw.truncated } : {}),
536
+ ...(typeof raw.error === 'string' ? { error: raw.error } : {}),
537
+ }
538
+ }
539
+ return {
540
+ type: 'daemon_mesh_result',
541
+ ...base,
542
+ ...('result' in raw ? { result: raw.result } : {}),
543
+ ...(typeof raw.error === 'string' ? { error: raw.error } : {}),
544
+ }
545
+ }
546
+ if (!isRecord(raw.payload)) return null
547
+ if (isServerToDaemonControlType(raw.type)) {
548
+ return { type: raw.type, payload: raw.payload, ...relay } as ServerToDaemonControlMsg
549
+ }
550
+ if (!isDaemonDirectCommandType(raw.type)) return null
551
+ return { type: raw.type, payload: raw.payload, ...relay }
552
+ }