@adhdev/mesh-shared 1.0.60-rc.7 → 1.0.60-rc.71

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