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

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