@byok-sdk/protocol 0.2.0 → 0.3.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.
@@ -34,6 +34,7 @@ export declare const RuntimeCapabilitiesSchema: z.ZodObject<{
34
34
  steer: z.ZodOptional<z.ZodBoolean>;
35
35
  resume: z.ZodOptional<z.ZodBoolean>;
36
36
  approvalInteractive: z.ZodOptional<z.ZodBoolean>;
37
+ mcpToolsets: z.ZodOptional<z.ZodBoolean>;
37
38
  permissionModes: z.ZodOptional<z.ZodArray<z.ZodString>>;
38
39
  }, z.core.$strip>;
39
40
  export type RuntimeCapabilities = z.infer<typeof RuntimeCapabilitiesSchema>;
@@ -54,6 +55,7 @@ export declare const RuntimeInfoSchema: z.ZodObject<{
54
55
  steer: z.ZodOptional<z.ZodBoolean>;
55
56
  resume: z.ZodOptional<z.ZodBoolean>;
56
57
  approvalInteractive: z.ZodOptional<z.ZodBoolean>;
58
+ mcpToolsets: z.ZodOptional<z.ZodBoolean>;
57
59
  permissionModes: z.ZodOptional<z.ZodArray<z.ZodString>>;
58
60
  }, z.core.$strip>>;
59
61
  }, z.core.$strip>;
@@ -76,6 +78,7 @@ export declare const ConnHelloPayloadSchema: z.ZodObject<{
76
78
  steer: z.ZodOptional<z.ZodBoolean>;
77
79
  resume: z.ZodOptional<z.ZodBoolean>;
78
80
  approvalInteractive: z.ZodOptional<z.ZodBoolean>;
81
+ mcpToolsets: z.ZodOptional<z.ZodBoolean>;
79
82
  permissionModes: z.ZodOptional<z.ZodArray<z.ZodString>>;
80
83
  }, z.core.$strip>>;
81
84
  }, z.core.$strip>>>;
@@ -89,6 +92,28 @@ export declare const ConnAckPayloadSchema: z.ZodObject<{
89
92
  serverTime: z.ZodISODateTime;
90
93
  }, z.core.$strip>;
91
94
  export type ConnAckPayload = z.infer<typeof ConnAckPayloadSchema>;
95
+ /**
96
+ * The web-selected runtime/model target carried end to end with a task.
97
+ *
98
+ * This is an additive v1 field. The discriminated union makes lane ownership
99
+ * fail closed at decode time: subscription credentials can only belong to the
100
+ * vendor CLIs, while a BYOK provider can only be executed through Pi.
101
+ */
102
+ export declare const DispatchSelectionSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
103
+ lane: z.ZodLiteral<"subscription">;
104
+ runtimeId: z.ZodEnum<{
105
+ claude: "claude";
106
+ codex: "codex";
107
+ }>;
108
+ providerId: z.ZodNull;
109
+ modelId: z.ZodString;
110
+ }, z.core.$strict>, z.ZodObject<{
111
+ lane: z.ZodLiteral<"byok">;
112
+ runtimeId: z.ZodLiteral<"pi">;
113
+ providerId: z.ZodString;
114
+ modelId: z.ZodString;
115
+ }, z.core.$strict>], "lane">;
116
+ export type DispatchSelection = z.infer<typeof DispatchSelectionSchema>;
92
117
  /**
93
118
  * server -> daemon: offer a task for a device to claim.
94
119
  *
@@ -122,6 +147,20 @@ export declare const TaskOfferPayloadSchema: z.ZodObject<{
122
147
  codex: "codex";
123
148
  pi: "pi";
124
149
  }>>;
150
+ dispatchSelection: z.ZodOptional<z.ZodDiscriminatedUnion<[z.ZodObject<{
151
+ lane: z.ZodLiteral<"subscription">;
152
+ runtimeId: z.ZodEnum<{
153
+ claude: "claude";
154
+ codex: "codex";
155
+ }>;
156
+ providerId: z.ZodNull;
157
+ modelId: z.ZodString;
158
+ }, z.core.$strict>, z.ZodObject<{
159
+ lane: z.ZodLiteral<"byok">;
160
+ runtimeId: z.ZodLiteral<"pi">;
161
+ providerId: z.ZodString;
162
+ modelId: z.ZodString;
163
+ }, z.core.$strict>], "lane">>;
125
164
  sessionRef: z.ZodOptional<z.ZodString>;
126
165
  workspaceHint: z.ZodOptional<z.ZodString>;
127
166
  limits: z.ZodOptional<z.ZodObject<{
@@ -130,6 +169,73 @@ export declare const TaskOfferPayloadSchema: z.ZodObject<{
130
169
  }, z.core.$strip>>;
131
170
  }, z.core.$strip>;
132
171
  export type TaskOfferPayload = z.infer<typeof TaskOfferPayloadSchema>;
172
+ /**
173
+ * Logical, host-owned MCP toolset identifier. A task may request this name,
174
+ * but the executable/server definition behind it exists only in the daemon's
175
+ * local configuration and never crosses the SaaS wire.
176
+ */
177
+ export declare const ToolsetIdSchema: z.ZodString;
178
+ export type ToolsetId = z.infer<typeof ToolsetIdSchema>;
179
+ /** Every named toolset is required; duplicates are rejected instead of silently de-duplicated. */
180
+ export declare const RequiredToolsetsSchema: z.ZodArray<z.ZodString>;
181
+ /**
182
+ * Additive v1 offer variant for tasks whose semantics require local MCP
183
+ * tools. This is a distinct message type rather than an optional field on
184
+ * `task.offer`: an older v1 daemon skips an unknown message type, whereas it
185
+ * would legally strip an unknown optional control field and run the task
186
+ * without its required tools. The whole payload is strict because every
187
+ * field here affects execution authority.
188
+ */
189
+ export declare const TaskOfferWithToolsetsPayloadSchema: z.ZodObject<{
190
+ instruction: z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
191
+ blobRef: z.ZodObject<{
192
+ blobId: z.ZodString;
193
+ contentHash: z.ZodString;
194
+ size: z.ZodNumber;
195
+ contentType: z.ZodString;
196
+ url: z.ZodOptional<z.ZodString>;
197
+ }, z.core.$strip>;
198
+ }, z.core.$strict>]>;
199
+ policy: z.ZodObject<{
200
+ mode: z.ZodEnum<{
201
+ auto: "auto";
202
+ confirm: "confirm";
203
+ plan: "plan";
204
+ readonly: "readonly";
205
+ }>;
206
+ allowTools: z.ZodOptional<z.ZodArray<z.ZodString>>;
207
+ denyTools: z.ZodOptional<z.ZodArray<z.ZodString>>;
208
+ workspaceRoot: z.ZodOptional<z.ZodString>;
209
+ network: z.ZodOptional<z.ZodBoolean>;
210
+ }, z.core.$strict>;
211
+ runtime: z.ZodOptional<z.ZodEnum<{
212
+ claude: "claude";
213
+ codex: "codex";
214
+ pi: "pi";
215
+ }>>;
216
+ dispatchSelection: z.ZodOptional<z.ZodDiscriminatedUnion<[z.ZodObject<{
217
+ lane: z.ZodLiteral<"subscription">;
218
+ runtimeId: z.ZodEnum<{
219
+ claude: "claude";
220
+ codex: "codex";
221
+ }>;
222
+ providerId: z.ZodNull;
223
+ modelId: z.ZodString;
224
+ }, z.core.$strict>, z.ZodObject<{
225
+ lane: z.ZodLiteral<"byok">;
226
+ runtimeId: z.ZodLiteral<"pi">;
227
+ providerId: z.ZodString;
228
+ modelId: z.ZodString;
229
+ }, z.core.$strict>], "lane">>;
230
+ sessionRef: z.ZodOptional<z.ZodString>;
231
+ workspaceHint: z.ZodOptional<z.ZodString>;
232
+ limits: z.ZodOptional<z.ZodObject<{
233
+ maxDurationMs: z.ZodOptional<z.ZodNumber>;
234
+ maxTokens: z.ZodOptional<z.ZodNumber>;
235
+ }, z.core.$strip>>;
236
+ requiredToolsets: z.ZodArray<z.ZodString>;
237
+ }, z.core.$strict>;
238
+ export type TaskOfferWithToolsetsPayload = z.infer<typeof TaskOfferWithToolsetsPayloadSchema>;
133
239
  /**
134
240
  * server -> daemon: approve a pending `task.await_approval` request.
135
241
  *
@@ -241,6 +347,7 @@ export declare const TaskClaimPayloadSchema: z.ZodObject<{
241
347
  steer: z.ZodOptional<z.ZodBoolean>;
242
348
  resume: z.ZodOptional<z.ZodBoolean>;
243
349
  approvalInteractive: z.ZodOptional<z.ZodBoolean>;
350
+ mcpToolsets: z.ZodOptional<z.ZodBoolean>;
244
351
  permissionModes: z.ZodOptional<z.ZodArray<z.ZodString>>;
245
352
  }, z.core.$strip>>;
246
353
  }, z.core.$strip>;
@@ -356,7 +463,125 @@ export declare const TaskAwaitApprovalPayloadSchema: z.ZodObject<{
356
463
  approvalId: z.ZodOptional<z.ZodString>;
357
464
  }, z.core.$strip>;
358
465
  export type TaskAwaitApprovalPayload = z.infer<typeof TaskAwaitApprovalPayloadSchema>;
359
- /** daemon -> server: task finished successfully. */
466
+ /**
467
+ * Hard cap (1 MiB) on a single `task.complete.document` (see
468
+ * {@link TaskCompletePayloadSchema}), measured as the UTF-8 byte length of
469
+ * its canonical JSON encoding — NOT as a node/key count, since a document is
470
+ * schema-neutral and its object shape is unbounded by design.
471
+ *
472
+ * The cap is a REJECT-AT-BOUNDARY limit on both sides: a daemon that
473
+ * produces an over-cap document reports `task.fail` instead of sending it,
474
+ * and a server rejects an over-cap document at schema validation. It is
475
+ * never truncated — a truncated JSON document is not valid JSON, so
476
+ * "shrinking to fit" can only hand the consumer garbage.
477
+ *
478
+ * 1 MiB is the conservative ceiling declared by the first real consumer
479
+ * (`docs/researches/2026-08-12-salesko-consumption-evidence.md` §1/§2:
480
+ * smallest real frame 8.4 KiB, typical 48-96 KiB, 512 KiB comfortable).
481
+ * Producers should stay at or under ~512 KiB (docs/protocol.md); the extra
482
+ * headroom exists because raising a protocol cap later is additive while
483
+ * lowering one is breaking. A result too big for this channel belongs in
484
+ * `artifactRefs` (the multi-file/binary/oversized channel), not here.
485
+ */
486
+ export declare const RESULT_DOCUMENT_MAX_BYTES = 1048576;
487
+ /**
488
+ * Outcome of {@link checkResultDocument}.
489
+ *
490
+ * `bytes` is the measured canonical JSON UTF-8 byte length, present on both
491
+ * the accept and the over-cap rejection so a caller can name the actual size
492
+ * in a failure reason. `canonical` is the CANONICAL SNAPSHOT — the value a
493
+ * sender must actually put on the wire; see {@link checkResultDocument}.
494
+ */
495
+ export type ResultDocumentCheck = {
496
+ readonly ok: true;
497
+ readonly bytes: number;
498
+ readonly canonical: unknown;
499
+ } | {
500
+ readonly ok: false;
501
+ readonly reason: 'not-serializable';
502
+ } | {
503
+ readonly ok: false;
504
+ readonly reason: 'over-cap';
505
+ readonly bytes: number;
506
+ } | {
507
+ readonly ok: false;
508
+ readonly reason: 'not-plain-json';
509
+ };
510
+ /**
511
+ * THE single authority for "is this a legal `task.complete.document`", and
512
+ * the one place its canonical form is produced. `TaskCompletePayloadSchema`'s
513
+ * own refinement calls it, and the daemon-side pre-send gate
514
+ * (`packages/client`'s `task-runner.ts`) imports and calls the exact same
515
+ * function rather than re-deriving any part of it: a daemon that measured or
516
+ * judged a document even slightly differently from the server that validates
517
+ * it would either reject documents the wire would have accepted, or hand the
518
+ * server a payload it is about to reject after the runtime session already
519
+ * ended.
520
+ *
521
+ * **The contract is: a document must be PLAIN JSON DATA.** Not "an object
522
+ * that happens to survive `JSON.stringify`" — that bar is far too low, and
523
+ * two concrete attacks/mistakes live under it:
524
+ *
525
+ * 1. `JSON.stringify` succeeding does not mean the value was preserved. An
526
+ * `undefined`-valued key, a `NaN`, a function-valued property, or a
527
+ * `Date` all serialize "successfully" while silently becoming something
528
+ * else (dropped, `null`, or a string). The result is a well-formed,
529
+ * under-cap document that is not what the producer had — a confidently
530
+ * wrong terminal result, the worst outcome this channel has.
531
+ * 2. `toJSON(key)` receives the property key it is being serialized under,
532
+ * so an object can legally answer one way at the root (`key === ''`,
533
+ * where this function measures it) and a completely different way when
534
+ * nested inside the envelope payload (`key === 'document'`, where the
535
+ * codec actually serializes it). A root-only measurement is therefore
536
+ * not a bound on what goes on the wire at all. The same hole exists for
537
+ * any getter that answers differently on a second read.
538
+ *
539
+ * Both die together via the same mechanism. The steps:
540
+ *
541
+ * 1. `JSON.stringify` must succeed and not return `undefined`.
542
+ * 2. Its UTF-8 byte length must be within {@link RESULT_DOCUMENT_MAX_BYTES}.
543
+ * 3. `JSON.parse` that string — the CANONICAL SNAPSHOT. It is pure data:
544
+ * no `toJSON`, no getters, no prototype, nothing left that can answer
545
+ * differently a second time.
546
+ * 4. The original must be structurally equal to the snapshot
547
+ * ({@link isSameJsonData}). Any mismatch means the value was not plain
548
+ * JSON data, and it is rejected rather than silently transformed.
549
+ *
550
+ * On success the snapshot is returned as `canonical`, and **every sender
551
+ * must put THAT on the wire, never the original reference** — which is what
552
+ * closes the contextual-`toJSON`/unstable-getter hole for good: pure data
553
+ * serializes identically at the root and nested, so what was measured is
554
+ * necessarily what is sent. The check is idempotent on pure data, so the
555
+ * server re-running it on an already-parsed payload is a no-op that always
556
+ * agrees.
557
+ */
558
+ export declare function checkResultDocument(document: unknown): ResultDocumentCheck;
559
+ /**
560
+ * daemon -> server: task finished successfully.
561
+ *
562
+ * `document` (additive-minor, docs/protocol.md "Freeze rule"): the OPTIONAL
563
+ * structured terminal result of the task — one JSON value the product on the
564
+ * other side consumes as the task's actual output, as opposed to `summary`
565
+ * (human-readable prose) or `artifactRefs` (files). Deliberately
566
+ * `z.unknown()`: this SDK never understands, validates, or transforms the
567
+ * product's own document schema — that validation belongs to the consumer.
568
+ * The only constraints the wire imposes are the ones
569
+ * {@link checkResultDocument} enforces: it must be PLAIN JSON DATA (equal to
570
+ * its own JSON round trip — see that function for why "stringify succeeded"
571
+ * is not enough), and its canonical JSON UTF-8 encoding must be at most
572
+ * {@link RESULT_DOCUMENT_MAX_BYTES}. An over-cap document is REJECTED here,
573
+ * never truncated (see that constant's own doc comment). A sender puts the
574
+ * check's `canonical` snapshot on the wire, never the original object.
575
+ *
576
+ * Unlike `approvalId` on `task.await_approval` above, emitting this field IS
577
+ * gated on a capability flag (`result-document`, `version.ts`): a pre-
578
+ * `result-document` server strips it silently as an unknown key (the
579
+ * tolerant `z.object()` behavior §1 mandates), and silently losing the
580
+ * task's primary structured result is not a tolerable degradation the way
581
+ * losing an observability hint is. So a daemon sends `document` only to a
582
+ * server that advertised the flag, and fails the task loudly otherwise —
583
+ * see `packages/client`'s `task-runner.ts`.
584
+ */
360
585
  export declare const TaskCompletePayloadSchema: z.ZodObject<{
361
586
  summary: z.ZodString;
362
587
  sessionRef: z.ZodString;
@@ -367,6 +592,7 @@ export declare const TaskCompletePayloadSchema: z.ZodObject<{
367
592
  contentType: z.ZodString;
368
593
  url: z.ZodOptional<z.ZodString>;
369
594
  }, z.core.$strip>>>;
595
+ document: z.ZodOptional<z.ZodUnknown>;
370
596
  }, z.core.$strip>;
371
597
  export type TaskCompletePayload = z.infer<typeof TaskCompletePayloadSchema>;
372
598
  /** daemon -> server: task failed. */
@@ -464,6 +690,7 @@ export declare const MESSAGE_PAYLOAD_SCHEMAS: {
464
690
  steer: z.ZodOptional<z.ZodBoolean>;
465
691
  resume: z.ZodOptional<z.ZodBoolean>;
466
692
  approvalInteractive: z.ZodOptional<z.ZodBoolean>;
693
+ mcpToolsets: z.ZodOptional<z.ZodBoolean>;
467
694
  permissionModes: z.ZodOptional<z.ZodArray<z.ZodString>>;
468
695
  }, z.core.$strip>>;
469
696
  }, z.core.$strip>>>;
@@ -501,6 +728,20 @@ export declare const MESSAGE_PAYLOAD_SCHEMAS: {
501
728
  codex: "codex";
502
729
  pi: "pi";
503
730
  }>>;
731
+ dispatchSelection: z.ZodOptional<z.ZodDiscriminatedUnion<[z.ZodObject<{
732
+ lane: z.ZodLiteral<"subscription">;
733
+ runtimeId: z.ZodEnum<{
734
+ claude: "claude";
735
+ codex: "codex";
736
+ }>;
737
+ providerId: z.ZodNull;
738
+ modelId: z.ZodString;
739
+ }, z.core.$strict>, z.ZodObject<{
740
+ lane: z.ZodLiteral<"byok">;
741
+ runtimeId: z.ZodLiteral<"pi">;
742
+ providerId: z.ZodString;
743
+ modelId: z.ZodString;
744
+ }, z.core.$strict>], "lane">>;
504
745
  sessionRef: z.ZodOptional<z.ZodString>;
505
746
  workspaceHint: z.ZodOptional<z.ZodString>;
506
747
  limits: z.ZodOptional<z.ZodObject<{
@@ -508,6 +749,55 @@ export declare const MESSAGE_PAYLOAD_SCHEMAS: {
508
749
  maxTokens: z.ZodOptional<z.ZodNumber>;
509
750
  }, z.core.$strip>>;
510
751
  }, z.core.$strip>;
752
+ readonly 'task.offer_with_toolsets': z.ZodObject<{
753
+ instruction: z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
754
+ blobRef: z.ZodObject<{
755
+ blobId: z.ZodString;
756
+ contentHash: z.ZodString;
757
+ size: z.ZodNumber;
758
+ contentType: z.ZodString;
759
+ url: z.ZodOptional<z.ZodString>;
760
+ }, z.core.$strip>;
761
+ }, z.core.$strict>]>;
762
+ policy: z.ZodObject<{
763
+ mode: z.ZodEnum<{
764
+ auto: "auto";
765
+ confirm: "confirm";
766
+ plan: "plan";
767
+ readonly: "readonly";
768
+ }>;
769
+ allowTools: z.ZodOptional<z.ZodArray<z.ZodString>>;
770
+ denyTools: z.ZodOptional<z.ZodArray<z.ZodString>>;
771
+ workspaceRoot: z.ZodOptional<z.ZodString>;
772
+ network: z.ZodOptional<z.ZodBoolean>;
773
+ }, z.core.$strict>;
774
+ runtime: z.ZodOptional<z.ZodEnum<{
775
+ claude: "claude";
776
+ codex: "codex";
777
+ pi: "pi";
778
+ }>>;
779
+ dispatchSelection: z.ZodOptional<z.ZodDiscriminatedUnion<[z.ZodObject<{
780
+ lane: z.ZodLiteral<"subscription">;
781
+ runtimeId: z.ZodEnum<{
782
+ claude: "claude";
783
+ codex: "codex";
784
+ }>;
785
+ providerId: z.ZodNull;
786
+ modelId: z.ZodString;
787
+ }, z.core.$strict>, z.ZodObject<{
788
+ lane: z.ZodLiteral<"byok">;
789
+ runtimeId: z.ZodLiteral<"pi">;
790
+ providerId: z.ZodString;
791
+ modelId: z.ZodString;
792
+ }, z.core.$strict>], "lane">>;
793
+ sessionRef: z.ZodOptional<z.ZodString>;
794
+ workspaceHint: z.ZodOptional<z.ZodString>;
795
+ limits: z.ZodOptional<z.ZodObject<{
796
+ maxDurationMs: z.ZodOptional<z.ZodNumber>;
797
+ maxTokens: z.ZodOptional<z.ZodNumber>;
798
+ }, z.core.$strip>>;
799
+ requiredToolsets: z.ZodArray<z.ZodString>;
800
+ }, z.core.$strict>;
511
801
  readonly 'task.approve': z.ZodObject<{
512
802
  approvalId: z.ZodOptional<z.ZodString>;
513
803
  }, z.core.$strip>;
@@ -533,6 +823,7 @@ export declare const MESSAGE_PAYLOAD_SCHEMAS: {
533
823
  steer: z.ZodOptional<z.ZodBoolean>;
534
824
  resume: z.ZodOptional<z.ZodBoolean>;
535
825
  approvalInteractive: z.ZodOptional<z.ZodBoolean>;
826
+ mcpToolsets: z.ZodOptional<z.ZodBoolean>;
536
827
  permissionModes: z.ZodOptional<z.ZodArray<z.ZodString>>;
537
828
  }, z.core.$strip>>;
538
829
  }, z.core.$strip>;
@@ -603,6 +894,7 @@ export declare const MESSAGE_PAYLOAD_SCHEMAS: {
603
894
  contentType: z.ZodString;
604
895
  url: z.ZodOptional<z.ZodString>;
605
896
  }, z.core.$strip>>>;
897
+ document: z.ZodOptional<z.ZodUnknown>;
606
898
  }, z.core.$strip>;
607
899
  readonly 'task.fail': z.ZodObject<{
608
900
  reason: z.ZodString;
@@ -630,7 +922,7 @@ export declare const MESSAGE_TYPES: MessageType[];
630
922
  * (`envelope.ts`) to decide which branches require envelope `seq` (M1
631
923
  * redelivery cursor).
632
924
  */
633
- export declare const SERVER_TO_DAEMON_TYPES: readonly ["conn.ack", "task.offer", "task.approve", "task.reject", "task.cancel", "task.steer"];
925
+ export declare const SERVER_TO_DAEMON_TYPES: readonly ["conn.ack", "task.offer", "task.offer_with_toolsets", "task.approve", "task.reject", "task.cancel", "task.steer"];
634
926
  /**
635
927
  * Message types the daemon sends to the server — the flip side of
636
928
  * {@link SERVER_TO_DAEMON_TYPES}. `conn.hello` is deliberately excluded: it's
package/dist/version.d.ts CHANGED
@@ -66,5 +66,39 @@ export declare const PROTOCOL_VERSION = 1;
66
66
  * participate in targeting at all -- the same N/N-1-safe shape as every
67
67
  * other flag here, just consumed for observability instead of gating.
68
68
  */
69
- export declare const CAPABILITY_FLAGS: readonly ['steer', 'blob-upload', 'interactive-approval', 'approval_resolved', 'approval-targeting'];
69
+ /**
70
+ * `result-document` (additive-minor): a SERVER-advertised flag meaning "I
71
+ * understand the optional `task.complete.document` field" (`messages.ts`).
72
+ * Functionally gating, like `approval_resolved` and unlike
73
+ * `approval-targeting`.
74
+ *
75
+ * This is the N/N-1 answer for that new daemon -> server FIELD. An old
76
+ * server's `CAPABILITY_FLAGS`/`conn.ack.capabilities` never includes it, and
77
+ * its `TaskCompletePayloadSchema` is a tolerant (non-`.strict()`)
78
+ * `z.object()`, so a `document` sent to it would be silently STRIPPED on
79
+ * parse and vanish without a trace. That is exactly why emission is gated
80
+ * here rather than sent unconditionally the way `approvalId` is: `document`
81
+ * carries the task's primary structured RESULT, so losing it silently is
82
+ * data loss, not a missed observability hint. A new daemon talking to an old
83
+ * server therefore never sends `document` at all, and — if its configured
84
+ * extractor did produce one — reports `task.fail` (retryable: false; the
85
+ * same server will strip it on every retry too) instead of completing the
86
+ * task with its main result quietly deleted (`packages/client`'s
87
+ * `task-runner.ts`). A new server talking to an old daemon is unaffected:
88
+ * the field is optional, and an old daemon simply never sets it.
89
+ *
90
+ * `dispatch-selection` (additive-minor) is a correctness gate for the
91
+ * optional `task.offer.dispatchSelection` control field. An older v1 daemon
92
+ * legally strips unknown optional fields, so a server must never send an
93
+ * authoritative provider/model selection unless the target connection
94
+ * advertises this flag. Absence means reject before task creation, not send
95
+ * a legacy runtime-only offer that could reach a different provider.
96
+ *
97
+ * `toolset-selection` (additive-minor) means the daemon understands
98
+ * `task.offer_with_toolsets` and can resolve its logical ids against local
99
+ * MCP configuration. The distinct message type is also the N/N-1 safety
100
+ * boundary for long-poll: an older daemon skips it as unknown and therefore
101
+ * cannot accidentally execute the instruction without the required tools.
102
+ */
103
+ export declare const CAPABILITY_FLAGS: readonly ['steer', 'blob-upload', 'interactive-approval', 'approval_resolved', 'approval-targeting', 'result-document', 'dispatch-selection', 'toolset-selection'];
70
104
  export type CapabilityFlag = (typeof CAPABILITY_FLAGS)[number];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@byok-sdk/protocol",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "BYOK SDK wire protocol: envelope schema, message types, and codec helpers",
5
5
  "type": "module",
6
6
  "license": "MIT",