@adhdev/mesh-shared 1.0.60-rc.9 → 1.0.61-rc.1

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 (67) hide show
  1. package/dist/brain-routing.d.ts +6 -8
  2. package/dist/git-diff.d.ts +21 -0
  3. package/dist/index.d.ts +12 -1
  4. package/dist/index.js +2876 -77
  5. package/dist/index.js.map +1 -1
  6. package/dist/index.mjs +2625 -72
  7. package/dist/index.mjs.map +1 -1
  8. package/dist/interpolation.d.ts +0 -9
  9. package/dist/json.d.ts +4 -0
  10. package/dist/keyed-doc-delta.d.ts +105 -0
  11. package/dist/mesh-tool-names.d.ts +31 -2
  12. package/dist/mesh-vocabulary.d.ts +66 -0
  13. package/dist/mission-brief.d.ts +62 -0
  14. package/dist/outbound-message.d.ts +201 -0
  15. package/dist/path-ownership.d.ts +101 -0
  16. package/dist/protocol/daemon-server.d.ts +356 -0
  17. package/dist/protocol/dashboard-daemon-p2p.d.ts +213 -0
  18. package/dist/protocol/dashboard-server.d.ts +240 -0
  19. package/dist/protocol/dashboard-wire-version.d.ts +21 -0
  20. package/dist/protocol/envelope.d.ts +55 -0
  21. package/dist/protocol/index.d.ts +16 -0
  22. package/dist/session-launch.d.ts +105 -0
  23. package/dist/session-status.d.ts +75 -0
  24. package/dist/slot-proposal.d.ts +2 -28
  25. package/dist/turn-evidence.d.ts +434 -0
  26. package/dist/turn-ipc-guards.d.ts +18 -0
  27. package/dist/turn-ipc-mission.d.ts +250 -0
  28. package/dist/turn-ipc-queue.d.ts +196 -0
  29. package/dist/turn-ipc-stats.d.ts +72 -0
  30. package/dist/turn-ipc-turn.d.ts +176 -0
  31. package/dist/turn-ipc.d.ts +90 -0
  32. package/dist/types.d.ts +11 -0
  33. package/dist/worker-protocol.d.ts +69 -0
  34. package/dist/ws-protocol.d.ts +69 -41
  35. package/package.json +1 -1
  36. package/src/brain-routing.ts +9 -11
  37. package/src/git-diff.ts +48 -0
  38. package/src/index.ts +12 -1
  39. package/src/interpolation.ts +0 -18
  40. package/src/json.ts +10 -0
  41. package/src/keyed-doc-delta.ts +413 -0
  42. package/src/mesh-tool-names.ts +66 -25
  43. package/src/mesh-vocabulary.ts +94 -0
  44. package/src/mission-brief.ts +153 -0
  45. package/src/outbound-message.ts +319 -0
  46. package/src/path-ownership.ts +271 -0
  47. package/src/protocol/daemon-server.ts +510 -0
  48. package/src/protocol/dashboard-daemon-p2p.ts +273 -0
  49. package/src/protocol/dashboard-server.ts +340 -0
  50. package/src/protocol/dashboard-wire-version.ts +27 -0
  51. package/src/protocol/envelope.ts +75 -0
  52. package/src/protocol/index.ts +17 -0
  53. package/src/session-launch.ts +245 -0
  54. package/src/session-status.ts +164 -0
  55. package/src/slot-proposal.ts +2 -48
  56. package/src/turn-evidence.ts +596 -0
  57. package/src/turn-ipc-guards.ts +61 -0
  58. package/src/turn-ipc-mission.ts +647 -0
  59. package/src/turn-ipc-queue.ts +493 -0
  60. package/src/turn-ipc-stats.ts +193 -0
  61. package/src/turn-ipc-turn.ts +453 -0
  62. package/src/turn-ipc.ts +137 -0
  63. package/src/types.ts +8 -0
  64. package/src/worker-protocol.ts +164 -0
  65. package/src/ws-protocol.ts +98 -93
  66. package/dist/magi.d.ts +0 -281
  67. package/src/magi.ts +0 -310
@@ -0,0 +1,510 @@
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
+ limits: AuthOkPlanLimits
199
+ }
200
+
201
+ export type AuthErrorWirePayload = {
202
+ reason: string
203
+ message: string
204
+ limit?: number
205
+ current?: number
206
+ }
207
+
208
+ /** `machine_evicted` / `force_disconnect` / `token_revoked` — a reason token plus human text, then the socket closes. */
209
+ export type DisconnectNoticeWirePayload = {
210
+ reason: string
211
+ message: string
212
+ }
213
+
214
+ export type VersionMismatchWirePayload = {
215
+ current: string
216
+ latest: string
217
+ required: false
218
+ reason: VersionUpdateReasonWire
219
+ channel: ReleaseChannelWire
220
+ npmTag: NpmUpdateTagWire
221
+ updatePolicy: VersionUpdatePolicyWire
222
+ updateCommand: string
223
+ }
224
+
225
+ export type ForceUpdateRequiredWirePayload = {
226
+ current: string
227
+ latest: string
228
+ minVersion: string
229
+ required: true
230
+ reason: VersionUpdateReasonWire
231
+ channel: ReleaseChannelWire
232
+ npmTag: NpmUpdateTagWire
233
+ updatePolicy: VersionUpdatePolicyWire
234
+ updateCommand: string
235
+ forceUpdateBelow?: string
236
+ message: string
237
+ }
238
+
239
+ // ─── Commands and results ──────────────────────────────────────────────────
240
+
241
+ export type CommandResultWirePayload = {
242
+ requestId: MessageId
243
+ success: boolean
244
+ source?: string
245
+ } & Record<string, unknown>
246
+
247
+ export type DaemonErrorWirePayload = {
248
+ message?: string
249
+ detail?: unknown
250
+ }
251
+
252
+ // ─── P2P signaling (dashboard ↔ daemon through the server) ─────────────────
253
+
254
+ export const P2P_SIGNAL_TYPES = ['p2p_ready', 'p2p_offer', 'p2p_answer', 'p2p_ice'] as const
255
+ export type P2PSignalType = typeof P2P_SIGNAL_TYPES[number]
256
+ export const isP2PSignalType = makeTypeGuard(P2P_SIGNAL_TYPES)
257
+
258
+ /** Signals the daemon originates (it is the WebRTC offerer). */
259
+ export const DAEMON_P2P_SIGNAL_TYPES = ['p2p_offer', 'p2p_ice'] as const
260
+ export type DaemonP2PSignalType = typeof DAEMON_P2P_SIGNAL_TYPES[number]
261
+ export const isDaemonP2PSignalType = makeTypeGuard(DAEMON_P2P_SIGNAL_TYPES)
262
+
263
+ export type P2POfferWirePayload = { sdp: string; type: string; peerId: string }
264
+ export type P2PIceWirePayload = { candidate: string; mid: string; peerId: string }
265
+
266
+ /**
267
+ * A dashboard-originated signal as relayed to the daemon. `peerId` is stamped
268
+ * by UserSession/SharedSession, `sharePermission` and `shareTargetSessionId`
269
+ * (the one session a share link may see) only by SharedSession (server
270
+ * authority, never the client). Field names follow the browser/node-datachannel
271
+ * spellings both ends already read.
272
+ */
273
+ export type RelayedP2PSignalWirePayload = {
274
+ peerId?: string
275
+ daemonId?: string
276
+ sharePermission?: string
277
+ shareTargetSessionId?: string
278
+ sdp?: string
279
+ type?: string
280
+ candidate?: string
281
+ mid?: string
282
+ sdpMid?: string | null
283
+ sdpMLineIndex?: number | null
284
+ }
285
+
286
+ export const MESH_P2P_SIGNAL_TYPES = ['mesh_p2p_ready', 'mesh_p2p_offer', 'mesh_p2p_answer', 'mesh_p2p_ice'] as const
287
+ export type MeshP2PSignalType = typeof MESH_P2P_SIGNAL_TYPES[number]
288
+ export const isMeshP2PSignalType = makeTypeGuard(MESH_P2P_SIGNAL_TYPES)
289
+
290
+ /** Daemon→daemon signal as delivered to the target daemon: the sender's args plus the server-stamped canonical sender id. */
291
+ export type MeshP2PSignalWirePayload = { senderDaemonId: string } & Record<string, unknown>
292
+
293
+ // ─── Daemon → Server ───────────────────────────────────────────────────────
294
+
295
+ export const DAEMON_TO_SERVER_TYPES = [
296
+ 'auth',
297
+ 'status_report',
298
+ 'status_heartbeat',
299
+ 'status_event',
300
+ 'command_result',
301
+ 'error',
302
+ 'agent_event',
303
+ 'daemon_mesh_command',
304
+ 'p2p_offer',
305
+ 'p2p_ice',
306
+ ] as const
307
+ export type DaemonToServerType = typeof DAEMON_TO_SERVER_TYPES[number]
308
+ export const isDaemonToServerType = makeTypeGuard(DAEMON_TO_SERVER_TYPES)
309
+
310
+ export type DaemonAuthMsg = Envelope<'auth', DaemonAuthWirePayload>
311
+ export type StatusReportMsg = Envelope<'status_report', CloudStatusReportWirePayload>
312
+ export type StatusHeartbeatMsg = Envelope<'status_heartbeat', StatusHeartbeatWirePayload>
313
+ export type DaemonStatusEventMsg = Envelope<'status_event', DaemonStatusEventWirePayload>
314
+ export type CommandResultMsg = Envelope<'command_result', CommandResultWirePayload>
315
+ export type DaemonErrorMsg = Envelope<'error', DaemonErrorWirePayload>
316
+ /** Agent-level event forwarded verbatim to the `agent:status` webhook (throttled server-side). Dynamic by design. */
317
+ export type AgentEventMsg = Envelope<'agent_event', CommandPayload>
318
+ export type DaemonP2POfferMsg = Envelope<'p2p_offer', P2POfferWirePayload>
319
+ export type DaemonP2PIceMsg = Envelope<'p2p_ice', P2PIceWirePayload>
320
+ /**
321
+ * Daemon→daemon signaling relay request. Fields are TOP-LEVEL (no `payload`):
322
+ * this is the wire shape the live fleet speaks, kept as-is so mixed-version
323
+ * daemons and servers keep interoperating during a rollout.
324
+ */
325
+ export type DaemonMeshCommandMsg = {
326
+ type: 'daemon_mesh_command'
327
+ requestId: string
328
+ targetDaemonId: string
329
+ command: MeshP2PSignalType
330
+ args: Record<string, unknown>
331
+ timestamp: number
332
+ }
333
+
334
+ export type DaemonToServerMsg =
335
+ | DaemonAuthMsg
336
+ | StatusReportMsg
337
+ | StatusHeartbeatMsg
338
+ | DaemonStatusEventMsg
339
+ | CommandResultMsg
340
+ | DaemonErrorMsg
341
+ | AgentEventMsg
342
+ | DaemonMeshCommandMsg
343
+ | DaemonP2POfferMsg
344
+ | DaemonP2PIceMsg
345
+
346
+ const _daemonToServerNamesCoverUnion: AssertSameMembers<typeof DAEMON_TO_SERVER_TYPES, DaemonToServerMsg['type']> = true
347
+ void _daemonToServerNamesCoverUnion
348
+
349
+ /** Members that carry a `payload` object (everything except `daemon_mesh_command`). */
350
+ export type DaemonToServerPayloadType = Exclude<DaemonToServerMsg, DaemonMeshCommandMsg>['type']
351
+ export type DaemonToServerPayloadOf<T extends DaemonToServerPayloadType> = Extract<DaemonToServerMsg, { type: T }>['payload']
352
+
353
+ /**
354
+ * Parse boundary for the server: is `raw` a frame this link accepts from a daemon?
355
+ * Checks membership and the minimal envelope shape; `auth` additionally needs a
356
+ * token string because nothing downstream can proceed without one.
357
+ */
358
+ export function decodeDaemonToServer(raw: unknown): DaemonToServerMsg | null {
359
+ if (!isRecord(raw) || !isDaemonToServerType(raw.type)) return null
360
+ if (raw.type === 'daemon_mesh_command') {
361
+ // Handler-side validation replies with a correlated `daemon_mesh_result`
362
+ // error, so only the discriminant is checked here.
363
+ return {
364
+ type: 'daemon_mesh_command',
365
+ requestId: typeof raw.requestId === 'string' ? raw.requestId : String(raw.requestId ?? ''),
366
+ targetDaemonId: typeof raw.targetDaemonId === 'string' ? raw.targetDaemonId : '',
367
+ command: raw.command as MeshP2PSignalType,
368
+ args: isRecord(raw.args) ? raw.args : {},
369
+ timestamp: typeof raw.timestamp === 'number' ? raw.timestamp : Date.now(),
370
+ }
371
+ }
372
+ if (!isRecord(raw.payload)) return null
373
+ if (raw.type === 'auth' && !isNonEmptyString(raw.payload.token)) return null
374
+ return { type: raw.type, payload: raw.payload, ...readEnvelopeFields(raw) } as DaemonToServerMsg
375
+ }
376
+
377
+ // ─── Server → Daemon ───────────────────────────────────────────────────────
378
+
379
+ export const SERVER_TO_DAEMON_CONTROL_TYPES = [
380
+ 'auth_ok',
381
+ 'auth_error',
382
+ 'machine_evicted',
383
+ 'force_disconnect',
384
+ 'token_revoked',
385
+ 'version_mismatch',
386
+ 'force_update_required',
387
+ 'command',
388
+ 'agent_command',
389
+ 'resolve_action',
390
+ 'daemon_mesh_result',
391
+ 'p2p_ready',
392
+ 'p2p_offer',
393
+ 'p2p_answer',
394
+ 'p2p_ice',
395
+ 'mesh_p2p_ready',
396
+ 'mesh_p2p_offer',
397
+ 'mesh_p2p_answer',
398
+ 'mesh_p2p_ice',
399
+ ] as const
400
+ export type ServerToDaemonControlType = typeof SERVER_TO_DAEMON_CONTROL_TYPES[number]
401
+ export const isServerToDaemonControlType = makeTypeGuard(SERVER_TO_DAEMON_CONTROL_TYPES)
402
+
403
+ /** Optional routing fields the server stamps on relayed commands. */
404
+ type ServerRelayFields = { id?: MessageId; source?: string; timestamp?: number }
405
+
406
+ export type AuthOkMsg = Envelope<'auth_ok', AuthOkWirePayload>
407
+ export type AuthErrorMsg = Envelope<'auth_error', AuthErrorWirePayload>
408
+ export type MachineEvictedMsg = Envelope<'machine_evicted', DisconnectNoticeWirePayload>
409
+ export type ForceDisconnectMsg = Envelope<'force_disconnect', DisconnectNoticeWirePayload>
410
+ export type TokenRevokedMsg = Envelope<'token_revoked', DisconnectNoticeWirePayload>
411
+ export type VersionMismatchMsg = Envelope<'version_mismatch', VersionMismatchWirePayload>
412
+ export type ForceUpdateRequiredMsg = Envelope<'force_update_required', ForceUpdateRequiredWirePayload>
413
+ /** Wrapped command: `payload.command` names the command, `payload.args` carries it. */
414
+ export type ServerCommandMsg = Envelope<'command', { command: string; args?: CommandPayload } & Record<string, unknown>> & ServerRelayFields
415
+ export type ServerAgentCommandMsg = Envelope<'agent_command', { action: string } & Record<string, unknown>> & ServerRelayFields
416
+ export type ServerResolveActionMsg = Envelope<'resolve_action', CommandPayload> & ServerRelayFields
417
+ export type ServerP2PSignalMsg = Envelope<P2PSignalType, RelayedP2PSignalWirePayload> & ServerRelayFields
418
+ export type ServerMeshP2PSignalMsg = Envelope<MeshP2PSignalType, MeshP2PSignalWirePayload> & ServerRelayFields
419
+ /** Correlated ack/nack of a `daemon_mesh_command` (top-level, no payload). */
420
+ export type DaemonMeshResultMsg = {
421
+ type: 'daemon_mesh_result'
422
+ requestId: string
423
+ success: boolean
424
+ result?: unknown
425
+ error?: string
426
+ }
427
+
428
+ export type ServerToDaemonControlMsg =
429
+ | AuthOkMsg
430
+ | AuthErrorMsg
431
+ | MachineEvictedMsg
432
+ | ForceDisconnectMsg
433
+ | TokenRevokedMsg
434
+ | VersionMismatchMsg
435
+ | ForceUpdateRequiredMsg
436
+ | ServerCommandMsg
437
+ | ServerAgentCommandMsg
438
+ | ServerResolveActionMsg
439
+ | DaemonMeshResultMsg
440
+ | ServerP2PSignalMsg
441
+ | ServerMeshP2PSignalMsg
442
+
443
+ const _serverToDaemonControlNamesCoverUnion: AssertSameMembers<typeof SERVER_TO_DAEMON_CONTROL_TYPES, ServerToDaemonControlMsg['type']> = true
444
+ void _serverToDaemonControlNamesCoverUnion
445
+
446
+ declare const directCommandBrand: unique symbol
447
+ /**
448
+ * A command name relayed as the frame `type` — the OPEN namespace the REST API
449
+ * and `shortcuts` routes feed (`read_chat`, `send_chat`, `stop_cli`, `git_*`,
450
+ * `clone_mesh_node`, `terminal_exec`, ...). The daemon routes it by name
451
+ * through its command router. Branded so it cannot be confused with a control
452
+ * member at the type level; `decodeServerToDaemon` is the only producer.
453
+ */
454
+ export type DaemonDirectCommandType = string & { readonly [directCommandBrand]?: true }
455
+
456
+ export type ServerDirectCommandMsg = {
457
+ type: DaemonDirectCommandType
458
+ payload: CommandPayload
459
+ id?: MessageId
460
+ source?: string
461
+ timestamp?: number
462
+ }
463
+
464
+ export type ServerToDaemonMsg = ServerToDaemonControlMsg | ServerDirectCommandMsg
465
+ export type ServerToDaemonType = ServerToDaemonMsg['type']
466
+
467
+ export function isServerDirectCommandMsg(msg: ServerToDaemonMsg): msg is ServerDirectCommandMsg {
468
+ return !isServerToDaemonControlType(msg.type)
469
+ }
470
+
471
+ /** Members of the control set that carry a `payload` object. */
472
+ export type ServerToDaemonPayloadType = Exclude<ServerToDaemonControlMsg, DaemonMeshResultMsg>['type']
473
+ export type ServerToDaemonPayloadOf<T extends ServerToDaemonPayloadType> = Extract<ServerToDaemonControlMsg, { type: T }>['payload']
474
+
475
+ /** Command types a daemon must never receive as a direct command: they are daemon→server frames or reserved envelope words. */
476
+ const DIRECT_COMMAND_TYPE_PATTERN = /^[a-z][a-z0-9_:.-]{0,63}$/
477
+
478
+ export function isDaemonDirectCommandType(value: unknown): value is DaemonDirectCommandType {
479
+ return typeof value === 'string' && DIRECT_COMMAND_TYPE_PATTERN.test(value) && !isServerToDaemonControlType(value)
480
+ }
481
+
482
+ /**
483
+ * Parse boundary for the daemon: is `raw` a frame this link accepts from the server?
484
+ * Control members are checked for their discriminant and envelope shape; the
485
+ * payload-less `daemon_mesh_result` reply needs its `requestId`; any other well-formed
486
+ * command name becomes a `ServerDirectCommandMsg`.
487
+ */
488
+ export function decodeServerToDaemon(raw: unknown): ServerToDaemonMsg | null {
489
+ if (!isRecord(raw) || typeof raw.type !== 'string') return null
490
+ const relay = {
491
+ ...readEnvelopeFields(raw),
492
+ ...(isNonEmptyString(raw.source) ? { source: raw.source } : {}),
493
+ }
494
+ if (raw.type === 'daemon_mesh_result') {
495
+ if (!isNonEmptyString(raw.requestId)) return null
496
+ const base = { requestId: raw.requestId, success: raw.success === true }
497
+ return {
498
+ type: 'daemon_mesh_result',
499
+ ...base,
500
+ ...('result' in raw ? { result: raw.result } : {}),
501
+ ...(typeof raw.error === 'string' ? { error: raw.error } : {}),
502
+ }
503
+ }
504
+ if (!isRecord(raw.payload)) return null
505
+ if (isServerToDaemonControlType(raw.type)) {
506
+ return { type: raw.type, payload: raw.payload, ...relay } as ServerToDaemonControlMsg
507
+ }
508
+ if (!isDaemonDirectCommandType(raw.type)) return null
509
+ return { type: raw.type, payload: raw.payload, ...relay }
510
+ }