@byok-sdk/protocol 0.1.0

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.
@@ -0,0 +1,646 @@
1
+ import { z } from 'zod';
2
+ export declare const RuntimeIdSchema: z.ZodEnum<{
3
+ claude: "claude";
4
+ codex: "codex";
5
+ pi: "pi";
6
+ }>;
7
+ export type RuntimeId = z.infer<typeof RuntimeIdSchema>;
8
+ /**
9
+ * Per-runtime feature flags reported in `conn.hello.runtimes[].capabilities`
10
+ * (pre-freeze addition). Distinct from the connection-level `CAPABILITY_FLAGS`
11
+ * (`version.ts`) / `conn.hello.capabilities` array: those are protocol-level
12
+ * flags negotiated for the whole connection, while this is what one specific
13
+ * detected runtime (pi/claude/codex) supports. The whole field is optional
14
+ * end-to-end — older daemons omit `capabilities` entirely — and every field
15
+ * inside it is itself optional, since detection can be partial.
16
+ *
17
+ * Per-tool allow/deny lists are deliberately NOT included here (noise).
18
+ * `permissionModes` mirrors `PERMISSION_MODES` (`permission.ts`) but is kept
19
+ * as a bare `string[]` rather than `z.enum(PERMISSION_MODES)`: this is a
20
+ * runtime's self-reported observability data, not a control/security field,
21
+ * so — per the freeze rule (tolerate unknown for observability, fail closed
22
+ * for control/security; see `agent-event.ts`'s unknown-variant tolerance for
23
+ * the same asymmetry applied to `task.progress` events) — it stays tolerant
24
+ * of a mode string a newer runtime might report that this schema doesn't
25
+ * enumerate yet, rather than rejecting the whole `conn.hello`.
26
+ *
27
+ * Unrecognized keys inside `capabilities` itself, by contrast, are silently
28
+ * stripped (zod's default object behavior — same as every other payload
29
+ * schema in this file) rather than passed through: this is a closed, typed
30
+ * shape consumers can rely on, and a genuinely new capability flag gets added
31
+ * here explicitly rather than round-tripped opaquely.
32
+ */
33
+ export declare const RuntimeCapabilitiesSchema: z.ZodObject<{
34
+ steer: z.ZodOptional<z.ZodBoolean>;
35
+ resume: z.ZodOptional<z.ZodBoolean>;
36
+ approvalInteractive: z.ZodOptional<z.ZodBoolean>;
37
+ permissionModes: z.ZodOptional<z.ZodArray<z.ZodString>>;
38
+ }, z.core.$strip>;
39
+ export type RuntimeCapabilities = z.infer<typeof RuntimeCapabilitiesSchema>;
40
+ /**
41
+ * Runtime detection info reported in `conn.hello`. Supersedes the M0
42
+ * `agents: unknown` field (M1 gap #4): typed, so the server no longer has to
43
+ * best-effort-normalize an untyped blob.
44
+ */
45
+ export declare const RuntimeInfoSchema: z.ZodObject<{
46
+ id: z.ZodEnum<{
47
+ claude: "claude";
48
+ codex: "codex";
49
+ pi: "pi";
50
+ }>;
51
+ version: z.ZodOptional<z.ZodString>;
52
+ authPresent: z.ZodOptional<z.ZodBoolean>;
53
+ capabilities: z.ZodOptional<z.ZodObject<{
54
+ steer: z.ZodOptional<z.ZodBoolean>;
55
+ resume: z.ZodOptional<z.ZodBoolean>;
56
+ approvalInteractive: z.ZodOptional<z.ZodBoolean>;
57
+ permissionModes: z.ZodOptional<z.ZodArray<z.ZodString>>;
58
+ }, z.core.$strip>>;
59
+ }, z.core.$strip>;
60
+ export type RuntimeInfo = z.infer<typeof RuntimeInfoSchema>;
61
+ /** daemon -> server: opening handshake. */
62
+ export declare const ConnHelloPayloadSchema: z.ZodObject<{
63
+ protocolVersions: z.ZodArray<z.ZodNumber>;
64
+ capabilities: z.ZodArray<z.ZodString>;
65
+ deviceId: z.ZodString;
66
+ productId: z.ZodString;
67
+ runtimes: z.ZodOptional<z.ZodArray<z.ZodObject<{
68
+ id: z.ZodEnum<{
69
+ claude: "claude";
70
+ codex: "codex";
71
+ pi: "pi";
72
+ }>;
73
+ version: z.ZodOptional<z.ZodString>;
74
+ authPresent: z.ZodOptional<z.ZodBoolean>;
75
+ capabilities: z.ZodOptional<z.ZodObject<{
76
+ steer: z.ZodOptional<z.ZodBoolean>;
77
+ resume: z.ZodOptional<z.ZodBoolean>;
78
+ approvalInteractive: z.ZodOptional<z.ZodBoolean>;
79
+ permissionModes: z.ZodOptional<z.ZodArray<z.ZodString>>;
80
+ }, z.core.$strip>>;
81
+ }, z.core.$strip>>>;
82
+ cursor: z.ZodOptional<z.ZodNumber>;
83
+ }, z.core.$strip>;
84
+ export type ConnHelloPayload = z.infer<typeof ConnHelloPayloadSchema>;
85
+ /** server -> daemon: handshake acknowledgement. */
86
+ export declare const ConnAckPayloadSchema: z.ZodObject<{
87
+ protocolVersion: z.ZodNumber;
88
+ capabilities: z.ZodArray<z.ZodString>;
89
+ serverTime: z.ZodISODateTime;
90
+ }, z.core.$strip>;
91
+ export type ConnAckPayload = z.infer<typeof ConnAckPayloadSchema>;
92
+ /**
93
+ * server -> daemon: offer a task for a device to claim.
94
+ *
95
+ * `taskId` used to be duplicated here; it is now carried only by the
96
+ * envelope's `task_id` (M1 gap #7 — single source of truth for routing).
97
+ */
98
+ export declare const TaskOfferPayloadSchema: z.ZodObject<{
99
+ instruction: z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
100
+ blobRef: z.ZodObject<{
101
+ blobId: z.ZodString;
102
+ contentHash: z.ZodString;
103
+ size: z.ZodNumber;
104
+ contentType: z.ZodString;
105
+ url: z.ZodOptional<z.ZodString>;
106
+ }, z.core.$strip>;
107
+ }, z.core.$strict>]>;
108
+ policy: z.ZodObject<{
109
+ mode: z.ZodEnum<{
110
+ auto: "auto";
111
+ confirm: "confirm";
112
+ plan: "plan";
113
+ readonly: "readonly";
114
+ }>;
115
+ allowTools: z.ZodOptional<z.ZodArray<z.ZodString>>;
116
+ denyTools: z.ZodOptional<z.ZodArray<z.ZodString>>;
117
+ workspaceRoot: z.ZodOptional<z.ZodString>;
118
+ network: z.ZodOptional<z.ZodBoolean>;
119
+ }, z.core.$strict>;
120
+ runtime: z.ZodOptional<z.ZodEnum<{
121
+ claude: "claude";
122
+ codex: "codex";
123
+ pi: "pi";
124
+ }>>;
125
+ sessionRef: z.ZodOptional<z.ZodString>;
126
+ workspaceHint: z.ZodOptional<z.ZodString>;
127
+ limits: z.ZodOptional<z.ZodObject<{
128
+ maxDurationMs: z.ZodOptional<z.ZodNumber>;
129
+ maxTokens: z.ZodOptional<z.ZodNumber>;
130
+ }, z.core.$strip>>;
131
+ }, z.core.$strip>;
132
+ export type TaskOfferPayload = z.infer<typeof TaskOfferPayloadSchema>;
133
+ /**
134
+ * server -> daemon: approve a pending `task.await_approval` request.
135
+ *
136
+ * Semantics (M1 gap #3): the server's own state is authoritative on its own
137
+ * action — calling the server-side `approve()` API moves the task record
138
+ * `AwaitApproval -> Running` immediately. This wire message is a best-effort
139
+ * *notification* telling the daemon to resume the paused runtime session; the
140
+ * daemon does not send a dedicated ack. Its outcome is observable through the
141
+ * task's existing message stream (e.g. `task.progress` resuming, or
142
+ * `task.fail`/`task.cancelled` if resuming turns out to be impossible) — no
143
+ * new ack message type is introduced. See docs/protocol.md "Approval flow".
144
+ *
145
+ * `approvalId` (M5, additive-minor — docs/protocol.md §5.3): OPTIONAL target
146
+ * identity for the SPECIFIC pending approval this decision resolves, rather
147
+ * than "whichever one is currently pending" (the pre-M5 behavior, and still
148
+ * what happens when this field is absent — a legacy server that never
149
+ * learned an id, or one talking to a legacy daemon). When present, the
150
+ * daemon compares it against its own currently-dispatched approval id
151
+ * (`ActiveTask.pendingApprovalId`, `packages/client`'s `task-runner.ts`) and
152
+ * treats a mismatch as a stale, audit-only no-op instead of resolving
153
+ * whatever happens to be pending right now — see `TaskRunner.handleApprove`.
154
+ */
155
+ export declare const TaskApprovePayloadSchema: z.ZodObject<{
156
+ approvalId: z.ZodOptional<z.ZodString>;
157
+ }, z.core.$strip>;
158
+ export type TaskApprovePayload = z.infer<typeof TaskApprovePayloadSchema>;
159
+ /**
160
+ * server -> daemon: reject a pending `task.await_approval` request.
161
+ *
162
+ * Same best-effort-notification semantics as `task.approve` (M1 gap #3): the
163
+ * server moves its own record `AwaitApproval -> Failed` immediately; this
164
+ * message just tells the daemon to stop, and the daemon reports the outcome
165
+ * via its existing `task.fail` terminal message.
166
+ *
167
+ * `approvalId` (M5, additive-minor — docs/protocol.md §5.3): same optional
168
+ * targeting semantics as `TaskApprovePayloadSchema.approvalId` above, applied
169
+ * to the reject path (`TaskRunner.handleReject`).
170
+ */
171
+ export declare const TaskRejectPayloadSchema: z.ZodObject<{
172
+ reason: z.ZodOptional<z.ZodString>;
173
+ approvalId: z.ZodOptional<z.ZodString>;
174
+ }, z.core.$strip>;
175
+ export type TaskRejectPayload = z.infer<typeof TaskRejectPayloadSchema>;
176
+ /**
177
+ * server -> daemon: cancel a task in any non-terminal state.
178
+ *
179
+ * Same best-effort-notification semantics (M1 gap #3): the server moves its
180
+ * own record to `Cancelled` immediately on its own action and does not wait
181
+ * for a daemon ack; this message just tells the daemon to stop local work.
182
+ * The daemon reports the outcome via the explicit `task.cancelled` terminal
183
+ * message (M1 gap #6) — not `task.fail`.
184
+ */
185
+ export declare const TaskCancelPayloadSchema: z.ZodObject<{
186
+ reason: z.ZodOptional<z.ZodString>;
187
+ }, z.core.$strip>;
188
+ export type TaskCancelPayload = z.infer<typeof TaskCancelPayloadSchema>;
189
+ /** server -> daemon: inject steering text into a running task. */
190
+ export declare const TaskSteerPayloadSchema: z.ZodObject<{
191
+ text: z.ZodString;
192
+ }, z.core.$strip>;
193
+ export type TaskSteerPayload = z.infer<typeof TaskSteerPayloadSchema>;
194
+ /**
195
+ * daemon -> server: claim an offered task (idempotent CAS on the server
196
+ * side). `taskId` used to be duplicated here; it is now carried only by the
197
+ * envelope's `task_id` (M1 gap #7).
198
+ *
199
+ * Claiming no longer implies the task is `Running` (M1 gap #2) — see
200
+ * `task.started`.
201
+ *
202
+ * `runtime` (M5, additive-minor — docs/protocol.md §3.1): the ACTUAL
203
+ * adapter this device selected for the task, distinct from `task.offer`'s
204
+ * own `runtime` (the merely REQUESTED one, `TaskOfferPayloadSchema.runtime`
205
+ * above). When an offer names no runtime the daemon auto-selects (pi-first —
206
+ * `TaskRunner.pickAdapter`, `packages/client`'s `task-runner.ts`), and before
207
+ * this field existed the server had no way to learn which adapter actually
208
+ * ran — `TaskSnapshot.runtime` (`packages/server`'s `types.ts`) only ever
209
+ * recorded what was requested. Plain optional property on this already-
210
+ * tolerant `z.object()`: an old server simply never reads it, so this needed
211
+ * no version bump and no emission gating (same shape as `approvalId` on
212
+ * `task.await_approval`/`task.approve`/`task.reject`, §5.3) — a new daemon
213
+ * sends it unconditionally, regardless of whether the connected server is
214
+ * new enough to store it.
215
+ *
216
+ * `capabilities` (S0/D-4, additive-minor — docs/protocol.md §2.4): the
217
+ * TASK-level capability authority — what the claiming adapter reported about
218
+ * itself at the moment it took this task, reusing `RuntimeCapabilitiesSchema`
219
+ * (above) rather than introducing a second capability shape. Same source of
220
+ * truth as `conn.hello.runtimes[].capabilities`, different scope: `conn.hello`
221
+ * is CONNECTION-level discovery ("what could this device run"), which no
222
+ * server-side control decision may read, because it is transport-shaped — a
223
+ * long-poll-only daemon never sends `conn.hello` at all — and it describes a
224
+ * device, not a task. This field is task-shaped: it shares a lifecycle with
225
+ * the task↔runtime binding the claim itself establishes, so a control gate
226
+ * (`steerTask()`, `packages/server`'s `hub.ts`) can key off it and stay
227
+ * correct across reconnects, adapter-set changes, and transports. Plain
228
+ * optional property on this already-tolerant `z.object()`, exactly like
229
+ * `runtime` above: an old daemon simply omits it, and a consumer that gates on
230
+ * it must fail closed on absence rather than assume a default.
231
+ */
232
+ export declare const TaskClaimPayloadSchema: z.ZodObject<{
233
+ deviceId: z.ZodString;
234
+ agentId: z.ZodOptional<z.ZodString>;
235
+ runtime: z.ZodOptional<z.ZodEnum<{
236
+ claude: "claude";
237
+ codex: "codex";
238
+ pi: "pi";
239
+ }>>;
240
+ capabilities: z.ZodOptional<z.ZodObject<{
241
+ steer: z.ZodOptional<z.ZodBoolean>;
242
+ resume: z.ZodOptional<z.ZodBoolean>;
243
+ approvalInteractive: z.ZodOptional<z.ZodBoolean>;
244
+ permissionModes: z.ZodOptional<z.ZodArray<z.ZodString>>;
245
+ }, z.core.$strip>>;
246
+ }, z.core.$strip>;
247
+ export type TaskClaimPayload = z.infer<typeof TaskClaimPayloadSchema>;
248
+ /**
249
+ * daemon -> server: explicit `Claimed -> Running` transition (M1 gap #2).
250
+ * A `task.claim` no longer implies the task started running; the daemon
251
+ * sends this once it has actually started the runtime session for the task.
252
+ */
253
+ export declare const TaskStartedPayloadSchema: z.ZodObject<{}, z.core.$strip>;
254
+ export type TaskStartedPayload = z.infer<typeof TaskStartedPayloadSchema>;
255
+ /**
256
+ * daemon -> server: decline an offer *before* claiming it (M1 gap #5) — e.g.
257
+ * no compatible/available runtime, or the offered policy exceeds this
258
+ * device's ceiling. Fail-closed rejections must use this instead of silently
259
+ * dropping the offer.
260
+ *
261
+ * Decision (see docs/protocol.md "Declined vs. Failed" for the full
262
+ * writeup): declining does *not* introduce a new `Declined` terminal state.
263
+ * It maps onto the existing `Failed` state via a new `Offered -> Failed`
264
+ * transition. `reason`/`retryable` intentionally mirror `TaskFailPayload`
265
+ * exactly, because a pre-claim decline and a post-claim failure are the same
266
+ * outcome from the dispatcher's point of view (this attempt produced no
267
+ * result; here's whether retrying — e.g. offering to a different device —
268
+ * makes sense), and keeping the state machine minimal avoids forking every
269
+ * terminal-state consumer into "Failed or Declined, handle both".
270
+ */
271
+ export declare const TaskDeclinePayloadSchema: z.ZodObject<{
272
+ reason: z.ZodString;
273
+ retryable: z.ZodOptional<z.ZodBoolean>;
274
+ }, z.core.$strip>;
275
+ export type TaskDeclinePayload = z.infer<typeof TaskDeclinePayloadSchema>;
276
+ /**
277
+ * daemon -> server: batch of normalized agent events.
278
+ *
279
+ * `events` elements are known-or-unknown (`AgentEventOrUnknownSchema` —
280
+ * `agent-event.ts`), not bare `AgentEventSchema`: pre-freeze, an unrecognized
281
+ * event `type` must not fail the whole batch, since a peer running a newer
282
+ * minor version may have emitted an additive event variant this schema
283
+ * doesn't know about yet. See `agent-event.ts` for the full rationale and
284
+ * `partitionAgentEvents`/`isKnownAgentEvent` for how consumers should skip
285
+ * unknowns instead of choking on them.
286
+ */
287
+ export declare const TaskProgressPayloadSchema: z.ZodObject<{
288
+ seq: z.ZodNumber;
289
+ events: z.ZodArray<z.ZodUnion<readonly [z.ZodDiscriminatedUnion<[z.ZodObject<{
290
+ type: z.ZodLiteral<"progress">;
291
+ text: z.ZodString;
292
+ }, z.core.$strip>, z.ZodObject<{
293
+ type: z.ZodLiteral<"tool_use">;
294
+ tool: z.ZodString;
295
+ input: z.ZodOptional<z.ZodUnknown>;
296
+ }, z.core.$strip>, z.ZodObject<{
297
+ type: z.ZodLiteral<"tool_result">;
298
+ tool: z.ZodString;
299
+ output: z.ZodOptional<z.ZodUnknown>;
300
+ }, z.core.$strip>, z.ZodObject<{
301
+ type: z.ZodLiteral<"artifact">;
302
+ name: z.ZodString;
303
+ contentType: z.ZodString;
304
+ }, z.core.$strip>, z.ZodObject<{
305
+ type: z.ZodLiteral<"needs_approval">;
306
+ summary: z.ZodString;
307
+ }, z.core.$strip>, z.ZodObject<{
308
+ type: z.ZodLiteral<"turn_end">;
309
+ }, z.core.$strip>, z.ZodObject<{
310
+ type: z.ZodLiteral<"error">;
311
+ message: z.ZodString;
312
+ }, z.core.$strip>, z.ZodObject<{
313
+ type: z.ZodLiteral<"usage">;
314
+ inputTokens: z.ZodOptional<z.ZodNumber>;
315
+ cachedInputTokens: z.ZodOptional<z.ZodNumber>;
316
+ outputTokens: z.ZodOptional<z.ZodNumber>;
317
+ reasoningTokens: z.ZodOptional<z.ZodNumber>;
318
+ totalTokens: z.ZodOptional<z.ZodNumber>;
319
+ }, z.core.$strip>], "type">, z.ZodObject<{
320
+ type: z.ZodString;
321
+ }, z.core.$loose>]>>;
322
+ }, z.core.$strip>;
323
+ export type TaskProgressPayload = z.infer<typeof TaskProgressPayloadSchema>;
324
+ /** daemon -> server: an artifact produced by the task, inline or by blob ref. */
325
+ export declare const TaskArtifactPayloadSchema: z.ZodObject<{
326
+ name: z.ZodString;
327
+ contentType: z.ZodString;
328
+ inline: z.ZodOptional<z.ZodString>;
329
+ blobRef: z.ZodOptional<z.ZodObject<{
330
+ blobId: z.ZodString;
331
+ contentHash: z.ZodString;
332
+ size: z.ZodNumber;
333
+ contentType: z.ZodString;
334
+ url: z.ZodOptional<z.ZodString>;
335
+ }, z.core.$strip>>;
336
+ }, z.core.$strip>;
337
+ export type TaskArtifactPayload = z.infer<typeof TaskArtifactPayloadSchema>;
338
+ /**
339
+ * daemon -> server: task is blocked on an out-of-band approval.
340
+ *
341
+ * `approvalId` (M5, additive-minor — docs/protocol.md §5.3): the daemon's own
342
+ * locally-generated identity for THIS SPECIFIC pending approval
343
+ * (`ApprovalRegistry`, `packages/client`'s `approvals.ts`) — included
344
+ * unconditionally by an M5+ daemon, regardless of whether the connected
345
+ * server has advertised the `approval-targeting` capability flag
346
+ * (`version.ts`; see that flag's own doc comment for why no emission gating
347
+ * is needed here — it's a tolerant `z.object()` field, so an older server
348
+ * simply ignores it). Optional purely for wire tolerance with a pre-M5
349
+ * daemon build that never set it at all: a server that never learns an id
350
+ * for a task's current approval can't target a later `approve`/`reject`
351
+ * decision and falls back to resolving "whichever approval is currently
352
+ * pending" — the same behavior every server had before this field existed.
353
+ */
354
+ export declare const TaskAwaitApprovalPayloadSchema: z.ZodObject<{
355
+ summary: z.ZodString;
356
+ approvalId: z.ZodOptional<z.ZodString>;
357
+ }, z.core.$strip>;
358
+ export type TaskAwaitApprovalPayload = z.infer<typeof TaskAwaitApprovalPayloadSchema>;
359
+ /** daemon -> server: task finished successfully. */
360
+ export declare const TaskCompletePayloadSchema: z.ZodObject<{
361
+ summary: z.ZodString;
362
+ sessionRef: z.ZodString;
363
+ artifactRefs: z.ZodOptional<z.ZodArray<z.ZodObject<{
364
+ blobId: z.ZodString;
365
+ contentHash: z.ZodString;
366
+ size: z.ZodNumber;
367
+ contentType: z.ZodString;
368
+ url: z.ZodOptional<z.ZodString>;
369
+ }, z.core.$strip>>>;
370
+ }, z.core.$strip>;
371
+ export type TaskCompletePayload = z.infer<typeof TaskCompletePayloadSchema>;
372
+ /** daemon -> server: task failed. */
373
+ export declare const TaskFailPayloadSchema: z.ZodObject<{
374
+ reason: z.ZodString;
375
+ retryable: z.ZodOptional<z.ZodBoolean>;
376
+ }, z.core.$strip>;
377
+ export type TaskFailPayload = z.infer<typeof TaskFailPayloadSchema>;
378
+ /**
379
+ * daemon -> server: task ended in the `Cancelled` state (M1 gap #6) — either
380
+ * in response to a server-sent `task.cancel`, or a cancellation the daemon
381
+ * observed/decided locally (e.g. a local stop action) that the server didn't
382
+ * initiate. This is the canonical way to report a `Cancelled` outcome; it
383
+ * supersedes the M0 convention of `task.fail({ reason: 'cancelled' })`.
384
+ *
385
+ * This is deliberately its own message rather than folded into `task.fail`
386
+ * (decision: prefer the explicit message — see docs/protocol.md) because
387
+ * `Cancelled` is semantically distinct from `Failed`: one is an intentional
388
+ * stop, the other an error. Overloading `task.fail` with a magic
389
+ * `reason: 'cancelled'` string convention hid that distinction on the wire.
390
+ *
391
+ * Dual-purpose on receipt: if the server already moved its own record to
392
+ * `Cancelled` (it initiated the cancel — M1 gap #3's "server state is
393
+ * authoritative" rule), this is an idempotent no-op ack. If the server
394
+ * hasn't yet (a locally-observed cancellation), this is the authoritative
395
+ * trigger that moves `Claimed`/`Running`/`AwaitApproval -> Cancelled`.
396
+ */
397
+ export declare const TaskCancelledPayloadSchema: z.ZodObject<{
398
+ reason: z.ZodOptional<z.ZodString>;
399
+ }, z.core.$strip>;
400
+ export type TaskCancelledPayload = z.infer<typeof TaskCancelledPayloadSchema>;
401
+ /**
402
+ * daemon -> server: a pending `task.await_approval` was resolved entirely
403
+ * LOCALLY on the device — the local control-socket `approvals.resolve` RPC,
404
+ * a fail-closed `requestApproval` timeout, or a fail-closed eviction/finish
405
+ * rejection (see `packages/client`'s `task-runner.ts`/`approvals.ts`) —
406
+ * *without* a wire `task.approve`/`task.reject` ever having been exchanged
407
+ * for it. This is the additive-minor answer to a gap the M4 Phase 3 approval
408
+ * work left open (see the "Deferred additive candidate" note this schema
409
+ * resolves, `docs/protocol.md`): today the server only learns of a local
410
+ * resolution IMPLICITLY, after the fact, once the daemon's next
411
+ * `task.progress`/`task.artifact`/`task.complete` proves the task already
412
+ * moved on (`ConnectionHub.resumeIfImplicitlyApproved`,
413
+ * `packages/server/src/hub.ts`) — a window in which a SaaS-side
414
+ * `TaskHandle.approve()`/`.reject()` can independently decide (and win) the
415
+ * server's own authoritative record before that evidence ever arrives. This
416
+ * message lets the daemon report the local resolution explicitly and
417
+ * immediately, narrowing that window from "until the next progress message"
418
+ * down to ordinary network latency; the implicit-inference path stays as-is,
419
+ * unconditionally, as the compatibility fallback for an old server that
420
+ * never advertises the `approval_resolved` capability flag (`version.ts`) or
421
+ * an old daemon that predates this message entirely.
422
+ *
423
+ * Observability-class tolerance applies (not control/security — see the
424
+ * freeze rule's asymmetry, `docs/protocol.md`): this is a daemon reporting
425
+ * what it already did locally, not a payload that grants/denies anything on
426
+ * its own — the receiving server's own state machine (`TASK_TRANSITIONS`,
427
+ * `task-state.ts`) is still what decides whether the reported resolution is
428
+ * legal to apply. Plain `z.object()` (not `.strict()`), same as every other
429
+ * non-control payload in this file.
430
+ *
431
+ * `resolvedBy` is a single-value enum (`'local'`) rather than a bare string:
432
+ * deliberately future-proof (a later wave could add e.g. `'operator-cli'` as
433
+ * a DISTINCT value without a version bump — a new enum member is additive,
434
+ * same as a new message type or capability flag), while still being a closed,
435
+ * typed shape today rather than an open string a typo could silently widen.
436
+ */
437
+ export declare const TaskApprovalResolvedPayloadSchema: z.ZodObject<{
438
+ approvalId: z.ZodString;
439
+ decision: z.ZodEnum<{
440
+ approve: "approve";
441
+ reject: "reject";
442
+ }>;
443
+ resolvedBy: z.ZodEnum<{
444
+ local: "local";
445
+ }>;
446
+ at: z.ZodISODateTime;
447
+ }, z.core.$strip>;
448
+ export type TaskApprovalResolvedPayload = z.infer<typeof TaskApprovalResolvedPayloadSchema>;
449
+ export declare const MESSAGE_PAYLOAD_SCHEMAS: {
450
+ readonly 'conn.hello': z.ZodObject<{
451
+ protocolVersions: z.ZodArray<z.ZodNumber>;
452
+ capabilities: z.ZodArray<z.ZodString>;
453
+ deviceId: z.ZodString;
454
+ productId: z.ZodString;
455
+ runtimes: z.ZodOptional<z.ZodArray<z.ZodObject<{
456
+ id: z.ZodEnum<{
457
+ claude: "claude";
458
+ codex: "codex";
459
+ pi: "pi";
460
+ }>;
461
+ version: z.ZodOptional<z.ZodString>;
462
+ authPresent: z.ZodOptional<z.ZodBoolean>;
463
+ capabilities: z.ZodOptional<z.ZodObject<{
464
+ steer: z.ZodOptional<z.ZodBoolean>;
465
+ resume: z.ZodOptional<z.ZodBoolean>;
466
+ approvalInteractive: z.ZodOptional<z.ZodBoolean>;
467
+ permissionModes: z.ZodOptional<z.ZodArray<z.ZodString>>;
468
+ }, z.core.$strip>>;
469
+ }, z.core.$strip>>>;
470
+ cursor: z.ZodOptional<z.ZodNumber>;
471
+ }, z.core.$strip>;
472
+ readonly 'conn.ack': z.ZodObject<{
473
+ protocolVersion: z.ZodNumber;
474
+ capabilities: z.ZodArray<z.ZodString>;
475
+ serverTime: z.ZodISODateTime;
476
+ }, z.core.$strip>;
477
+ readonly 'task.offer': z.ZodObject<{
478
+ instruction: z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
479
+ blobRef: z.ZodObject<{
480
+ blobId: z.ZodString;
481
+ contentHash: z.ZodString;
482
+ size: z.ZodNumber;
483
+ contentType: z.ZodString;
484
+ url: z.ZodOptional<z.ZodString>;
485
+ }, z.core.$strip>;
486
+ }, z.core.$strict>]>;
487
+ policy: z.ZodObject<{
488
+ mode: z.ZodEnum<{
489
+ auto: "auto";
490
+ confirm: "confirm";
491
+ plan: "plan";
492
+ readonly: "readonly";
493
+ }>;
494
+ allowTools: z.ZodOptional<z.ZodArray<z.ZodString>>;
495
+ denyTools: z.ZodOptional<z.ZodArray<z.ZodString>>;
496
+ workspaceRoot: z.ZodOptional<z.ZodString>;
497
+ network: z.ZodOptional<z.ZodBoolean>;
498
+ }, z.core.$strict>;
499
+ runtime: z.ZodOptional<z.ZodEnum<{
500
+ claude: "claude";
501
+ codex: "codex";
502
+ pi: "pi";
503
+ }>>;
504
+ sessionRef: z.ZodOptional<z.ZodString>;
505
+ workspaceHint: z.ZodOptional<z.ZodString>;
506
+ limits: z.ZodOptional<z.ZodObject<{
507
+ maxDurationMs: z.ZodOptional<z.ZodNumber>;
508
+ maxTokens: z.ZodOptional<z.ZodNumber>;
509
+ }, z.core.$strip>>;
510
+ }, z.core.$strip>;
511
+ readonly 'task.approve': z.ZodObject<{
512
+ approvalId: z.ZodOptional<z.ZodString>;
513
+ }, z.core.$strip>;
514
+ readonly 'task.reject': z.ZodObject<{
515
+ reason: z.ZodOptional<z.ZodString>;
516
+ approvalId: z.ZodOptional<z.ZodString>;
517
+ }, z.core.$strip>;
518
+ readonly 'task.cancel': z.ZodObject<{
519
+ reason: z.ZodOptional<z.ZodString>;
520
+ }, z.core.$strip>;
521
+ readonly 'task.steer': z.ZodObject<{
522
+ text: z.ZodString;
523
+ }, z.core.$strip>;
524
+ readonly 'task.claim': z.ZodObject<{
525
+ deviceId: z.ZodString;
526
+ agentId: z.ZodOptional<z.ZodString>;
527
+ runtime: z.ZodOptional<z.ZodEnum<{
528
+ claude: "claude";
529
+ codex: "codex";
530
+ pi: "pi";
531
+ }>>;
532
+ capabilities: z.ZodOptional<z.ZodObject<{
533
+ steer: z.ZodOptional<z.ZodBoolean>;
534
+ resume: z.ZodOptional<z.ZodBoolean>;
535
+ approvalInteractive: z.ZodOptional<z.ZodBoolean>;
536
+ permissionModes: z.ZodOptional<z.ZodArray<z.ZodString>>;
537
+ }, z.core.$strip>>;
538
+ }, z.core.$strip>;
539
+ readonly 'task.started': z.ZodObject<{}, z.core.$strip>;
540
+ readonly 'task.decline': z.ZodObject<{
541
+ reason: z.ZodString;
542
+ retryable: z.ZodOptional<z.ZodBoolean>;
543
+ }, z.core.$strip>;
544
+ readonly 'task.progress': z.ZodObject<{
545
+ seq: z.ZodNumber;
546
+ events: z.ZodArray<z.ZodUnion<readonly [z.ZodDiscriminatedUnion<[z.ZodObject<{
547
+ type: z.ZodLiteral<"progress">;
548
+ text: z.ZodString;
549
+ }, z.core.$strip>, z.ZodObject<{
550
+ type: z.ZodLiteral<"tool_use">;
551
+ tool: z.ZodString;
552
+ input: z.ZodOptional<z.ZodUnknown>;
553
+ }, z.core.$strip>, z.ZodObject<{
554
+ type: z.ZodLiteral<"tool_result">;
555
+ tool: z.ZodString;
556
+ output: z.ZodOptional<z.ZodUnknown>;
557
+ }, z.core.$strip>, z.ZodObject<{
558
+ type: z.ZodLiteral<"artifact">;
559
+ name: z.ZodString;
560
+ contentType: z.ZodString;
561
+ }, z.core.$strip>, z.ZodObject<{
562
+ type: z.ZodLiteral<"needs_approval">;
563
+ summary: z.ZodString;
564
+ }, z.core.$strip>, z.ZodObject<{
565
+ type: z.ZodLiteral<"turn_end">;
566
+ }, z.core.$strip>, z.ZodObject<{
567
+ type: z.ZodLiteral<"error">;
568
+ message: z.ZodString;
569
+ }, z.core.$strip>, z.ZodObject<{
570
+ type: z.ZodLiteral<"usage">;
571
+ inputTokens: z.ZodOptional<z.ZodNumber>;
572
+ cachedInputTokens: z.ZodOptional<z.ZodNumber>;
573
+ outputTokens: z.ZodOptional<z.ZodNumber>;
574
+ reasoningTokens: z.ZodOptional<z.ZodNumber>;
575
+ totalTokens: z.ZodOptional<z.ZodNumber>;
576
+ }, z.core.$strip>], "type">, z.ZodObject<{
577
+ type: z.ZodString;
578
+ }, z.core.$loose>]>>;
579
+ }, z.core.$strip>;
580
+ readonly 'task.artifact': z.ZodObject<{
581
+ name: z.ZodString;
582
+ contentType: z.ZodString;
583
+ inline: z.ZodOptional<z.ZodString>;
584
+ blobRef: z.ZodOptional<z.ZodObject<{
585
+ blobId: z.ZodString;
586
+ contentHash: z.ZodString;
587
+ size: z.ZodNumber;
588
+ contentType: z.ZodString;
589
+ url: z.ZodOptional<z.ZodString>;
590
+ }, z.core.$strip>>;
591
+ }, z.core.$strip>;
592
+ readonly 'task.await_approval': z.ZodObject<{
593
+ summary: z.ZodString;
594
+ approvalId: z.ZodOptional<z.ZodString>;
595
+ }, z.core.$strip>;
596
+ readonly 'task.complete': z.ZodObject<{
597
+ summary: z.ZodString;
598
+ sessionRef: z.ZodString;
599
+ artifactRefs: z.ZodOptional<z.ZodArray<z.ZodObject<{
600
+ blobId: z.ZodString;
601
+ contentHash: z.ZodString;
602
+ size: z.ZodNumber;
603
+ contentType: z.ZodString;
604
+ url: z.ZodOptional<z.ZodString>;
605
+ }, z.core.$strip>>>;
606
+ }, z.core.$strip>;
607
+ readonly 'task.fail': z.ZodObject<{
608
+ reason: z.ZodString;
609
+ retryable: z.ZodOptional<z.ZodBoolean>;
610
+ }, z.core.$strip>;
611
+ readonly 'task.cancelled': z.ZodObject<{
612
+ reason: z.ZodOptional<z.ZodString>;
613
+ }, z.core.$strip>;
614
+ readonly 'task.approval_resolved': z.ZodObject<{
615
+ approvalId: z.ZodString;
616
+ decision: z.ZodEnum<{
617
+ approve: "approve";
618
+ reject: "reject";
619
+ }>;
620
+ resolvedBy: z.ZodEnum<{
621
+ local: "local";
622
+ }>;
623
+ at: z.ZodISODateTime;
624
+ }, z.core.$strip>;
625
+ };
626
+ export type MessageType = keyof typeof MESSAGE_PAYLOAD_SCHEMAS;
627
+ export declare const MESSAGE_TYPES: MessageType[];
628
+ /**
629
+ * Message types the server sends to the daemon. Used by {@link EnvelopeSchema}
630
+ * (`envelope.ts`) to decide which branches require envelope `seq` (M1
631
+ * redelivery cursor).
632
+ */
633
+ export declare const SERVER_TO_DAEMON_TYPES: readonly ["conn.ack", "task.offer", "task.approve", "task.reject", "task.cancel", "task.steer"];
634
+ /**
635
+ * Message types the daemon sends to the server — the flip side of
636
+ * {@link SERVER_TO_DAEMON_TYPES}. `conn.hello` is deliberately excluded: it's
637
+ * only ever valid as the first frame of a WS handshake (`ws-server.ts`), not
638
+ * as ongoing inbound traffic through `ConnectionHub.handleInbound`.
639
+ *
640
+ * Used by `handleInbound` (`@byok-sdk/server`'s `hub.ts`) as the type-allow gate
641
+ * for every inbound envelope, WS and `POST /byok/messages` alike (finding
642
+ * P2): a `type` outside this set — a server -> daemon type arriving inbound,
643
+ * or anything unrecognized — is rejected before it's dispatched to any
644
+ * handler or counted `accepted` on the `/byok/messages` wire.
645
+ */
646
+ export declare const DAEMON_TO_SERVER_TYPES: readonly ["task.claim", "task.started", "task.decline", "task.progress", "task.artifact", "task.await_approval", "task.complete", "task.fail", "task.cancelled", "task.approval_resolved"];
@@ -0,0 +1,39 @@
1
+ import { z } from 'zod';
2
+ export declare const PERMISSION_MODES: readonly ['auto', 'confirm', 'readonly', 'plan'];
3
+ export type PermissionMode = (typeof PERMISSION_MODES)[number];
4
+ /**
5
+ * Policy the server proposes for a task. The daemon/runtime adapter maps this
6
+ * onto the concrete runtime's flags; anything that can't be expressed exactly
7
+ * must fail closed (deny) rather than silently widen the grant.
8
+ *
9
+ * `.strict()`: this is control/security data, so per the freeze rule's
10
+ * observability-vs-control asymmetry (docs/protocol.md "Freeze rule") an
11
+ * unrecognized field must be REJECTED, not silently stripped-and-ignored the
12
+ * way an ordinary payload's unknown field is (plain `z.object()`'s default
13
+ * behavior). Without `.strict()`, a policy carrying a future constraint this
14
+ * schema doesn't know about yet would parse successfully with that
15
+ * constraint silently discarded — exactly the silent-widening failure mode
16
+ * this type's own doc comment above warns against, since a stripped
17
+ * constraint is indistinguishable from a constraint that was never sent.
18
+ *
19
+ * Consequence: adding a new field to this schema post-freeze is therefore a
20
+ * BREAKING change requiring a `PROTOCOL_VERSION` bump — unlike the general
21
+ * "a new optional field on an existing payload is non-breaking" rule the
22
+ * freeze rule grants every other schema. That's intentional: a new
23
+ * security/control constraint must force a conscious version bump so an
24
+ * unupgraded peer can never silently ignore it, rather than being added the
25
+ * same low-friction way a harmless observability field would be.
26
+ */
27
+ export declare const PermissionPolicySchema: z.ZodObject<{
28
+ mode: z.ZodEnum<{
29
+ auto: "auto";
30
+ confirm: "confirm";
31
+ plan: "plan";
32
+ readonly: "readonly";
33
+ }>;
34
+ allowTools: z.ZodOptional<z.ZodArray<z.ZodString>>;
35
+ denyTools: z.ZodOptional<z.ZodArray<z.ZodString>>;
36
+ workspaceRoot: z.ZodOptional<z.ZodString>;
37
+ network: z.ZodOptional<z.ZodBoolean>;
38
+ }, z.core.$strict>;
39
+ export type PermissionPolicy = z.infer<typeof PermissionPolicySchema>;