@byok-sdk/protocol 0.31.0 → 0.33.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,5 @@
1
+ import { z } from 'zod';
2
+ /** Any well-formed open-vocabulary token, without a membership rule. */
3
+ export declare function openVocabularyToken(): z.ZodString;
4
+ /** A well-formed token that is none of `known`: the raw value a receiver keeps as `reported<Field>`. */
5
+ export declare function unrecognizedToken(known: readonly string[]): z.ZodString;
@@ -1,5 +1,6 @@
1
1
  /**
2
- * Sealed payload v1 on the hosted wire (#654 design §8.1, §8.3, §8.6).
2
+ * Sealed payload on the hosted wire: v1 (#654 design §8.1, §8.3, §8.6) and
3
+ * the v2 categories (#1045).
3
4
  *
4
5
  * Sealed is a property of the payload, not a message type: each sealable
5
6
  * type's payload is a plaintext branch or a sealed branch. The sealed branch is
@@ -19,20 +20,17 @@
19
20
  * length function; a cross-package test keeps the two equal.
20
21
  */
21
22
  import { z } from 'zod';
22
- export { SEALED_SPLIT_TABLE, isSealableMessageType, isSealedPayload } from './sealed-split-table';
23
+ export { SEALED_SPLIT_TABLE, isSealableMessageType, isSealedPayload, sealedMovedFields } from './sealed-split-table';
23
24
  export type { SealableMessageType, SealedDigestRule, SealedSplitEntry } from './sealed-split-table';
24
25
  export declare const SEALED_PAYLOAD_VERSION = 1;
25
26
  /** The only suite v1 admits. A new suite is a new constant and a new capability, never a parser branch. */
26
27
  export declare const SEALED_PAYLOAD_SUITE = "byok-sealed-v1.hkdf-sha256.aes-256-gcm";
27
- /** C1–C4 in canonical order. A task lists its sealing categories in this order. */
28
- export declare const SEALED_PAYLOAD_CATEGORIES: readonly ['task-input', 'agent-message', 'task-output', 'memory-projection'];
29
- export type SealedPayloadCategory = (typeof SEALED_PAYLOAD_CATEGORIES)[number];
30
28
  /**
31
- * The categories a v1 task may seal (D17): task input and task output. Agent
32
- * messages, memory projections, input preparation and drafts come later.
29
+ * C1–C4 in canonical order. A task lists its sealing categories in this order.
30
+ * Input preparation and drafts have no category; they stay plaintext.
33
31
  */
34
- export declare const SEALED_PAYLOAD_V1_CATEGORIES: readonly ['task-input', 'task-output'];
35
- export type SealedPayloadV1Category = (typeof SEALED_PAYLOAD_V1_CATEGORIES)[number];
32
+ export declare const SEALED_PAYLOAD_CATEGORIES: readonly ['task-input', 'agent-message', 'task-output', 'memory-projection'];
33
+ export type SealedPayloadCategory = (typeof SEALED_PAYLOAD_CATEGORIES)[number];
36
34
  /**
37
35
  * Closed `task.decline` codes of a receiver that refuses sealed task input
38
36
  * (§8.3). They are plaintext: a device without the scope key cannot seal a
@@ -64,23 +62,34 @@ export declare const SEALED_TASK_PLAIN_CODES: Readonly<{
64
62
  }>;
65
63
  /**
66
64
  * True when `payload` is a legal plaintext `type` message of a task whose
67
- * output is sealed: each field that the `task-output` sealed branch of `type`
68
- * moves or drops is absent or holds one of {@link SEALED_TASK_PLAIN_CODES}.
69
- * A type that has a sealed output branch and is not listed there is never
70
- * legal. A plaintext decline also names its code as `code`. A type with no
71
- * sealed output branch is always legal.
65
+ * output is sealed under `categories`: each field that the `task-output`
66
+ * sealed branch of `type` moves (with the `movedWith` fields of `categories`)
67
+ * or drops is absent or holds one of {@link SEALED_TASK_PLAIN_CODES}. A type
68
+ * that has a sealed output branch and is not listed there is never legal. A
69
+ * plaintext decline also names its code as `code`. A type with no sealed
70
+ * output branch is always legal.
72
71
  */
73
- export declare function isSealedTaskPlainOutput(type: string, payload: object): boolean;
72
+ export declare function isSealedTaskPlainOutput(type: string, payload: object, categories: readonly SealedPayloadCategory[]): boolean;
74
73
  /** Padded frames above this length travel as a ciphertext blob (§8.6). */
75
74
  export declare const SEALED_INLINE_MAX_PADDED_LENGTH: number;
76
- /** Plaintext limit of every v1 category, in UTF-8 bytes of canonical JSON (D8). */
75
+ /** Plaintext limit of every category, in UTF-8 bytes of canonical JSON (D8). */
77
76
  export declare const SEALED_PAYLOAD_MAX_CONTENT_LENGTH: number;
77
+ /**
78
+ * The largest opened content of a sealed `agent.message.publish` whose body
79
+ * is at most `maxBytes` UTF-8 bytes (#1045 O2): `6 × maxBytes + 11`. At
80
+ * `AGENT_MESSAGE_MAX_BYTES` it is 1,572,875, below
81
+ * {@link SEALED_PAYLOAD_MAX_CONTENT_LENGTH}, so every legal body seals. The
82
+ * device seals within it, and the Host bounds a sealed message's
83
+ * `paddedLength` by `sealedPayloadPaddedLength(4 + this)` for the task's
84
+ * frozen `maxBytes`.
85
+ */
86
+ export declare function sealedAgentMessageMaxContentLength(maxBytes: number): number;
78
87
  /**
79
88
  * The one legal padded length of a frame of `frameLength` bytes
80
89
  * (`4 + contentLength`): the next multiple of 256 up to 16 KiB, Padmé above.
81
90
  */
82
91
  export declare function sealedPayloadPaddedLength(frameLength: number): number;
83
- /** The largest padded length a v1 content may have. */
92
+ /** The largest padded length a sealed content may have. */
84
93
  export declare const SEALED_PAYLOAD_MAX_PADDED_LENGTH: number;
85
94
  /**
86
95
  * The byte length of canonical unpadded base64url, or `undefined`. Canonical
@@ -88,11 +97,13 @@ export declare const SEALED_PAYLOAD_MAX_PADDED_LENGTH: number;
88
97
  * character, so one byte string has exactly one encoding.
89
98
  */
90
99
  export declare function sealedBase64UrlByteLength(value: string): number | undefined;
91
- /** A sealed member of any v1 category. */
100
+ /** A sealed member of any category. */
92
101
  export declare const SealedPayloadSchema: z.ZodObject<{
93
102
  v: z.ZodLiteral<1>;
94
103
  suite: z.ZodLiteral<"byok-sealed-v1.hkdf-sha256.aes-256-gcm">;
95
104
  category: z.ZodEnum<{
105
+ "agent-message": "agent-message";
106
+ "memory-projection": "memory-projection";
96
107
  "task-input": "task-input";
97
108
  "task-output": "task-output";
98
109
  }>;
@@ -173,15 +184,76 @@ export declare const SealedTaskOutputSchema: z.ZodObject<{
173
184
  contentType: z.ZodString;
174
185
  }, z.core.$strict>>;
175
186
  }, z.core.$strict>;
187
+ /** The sealed member of an `agent.message.publish` (#1045 C2). */
188
+ export declare const SealedAgentMessageSchema: z.ZodObject<{
189
+ v: z.ZodLiteral<1>;
190
+ suite: z.ZodLiteral<"byok-sealed-v1.hkdf-sha256.aes-256-gcm">;
191
+ category: z.ZodLiteral<"agent-message">;
192
+ scope: z.ZodObject<{
193
+ id: z.ZodString;
194
+ epoch: z.ZodNumber;
195
+ keyId: z.ZodString;
196
+ }, z.core.$strict>;
197
+ chainHead: z.ZodObject<{
198
+ seq: z.ZodNumber;
199
+ hash: z.ZodString;
200
+ }, z.core.$strict>;
201
+ sender: z.ZodString;
202
+ salt: z.ZodString;
203
+ nonce: z.ZodString;
204
+ paddedLength: z.ZodNumber;
205
+ contentMac: z.ZodString;
206
+ ciphertext: z.ZodOptional<z.ZodString>;
207
+ ciphertextRef: z.ZodOptional<z.ZodObject<{
208
+ blobId: z.ZodString;
209
+ contentHash: z.ZodString;
210
+ size: z.ZodNumber;
211
+ contentType: z.ZodString;
212
+ }, z.core.$strict>>;
213
+ }, z.core.$strict>;
214
+ /**
215
+ * The sealed member of a memory projection (#1045 C4). It is inline only
216
+ * (owner decision O2): the Host keeps one latest snapshot per Agent and holds
217
+ * no ciphertext blob for it.
218
+ */
219
+ export declare const SealedMemoryProjectionSchema: z.ZodObject<{
220
+ v: z.ZodLiteral<1>;
221
+ suite: z.ZodLiteral<"byok-sealed-v1.hkdf-sha256.aes-256-gcm">;
222
+ category: z.ZodLiteral<"memory-projection">;
223
+ scope: z.ZodObject<{
224
+ id: z.ZodString;
225
+ epoch: z.ZodNumber;
226
+ keyId: z.ZodString;
227
+ }, z.core.$strict>;
228
+ chainHead: z.ZodObject<{
229
+ seq: z.ZodNumber;
230
+ hash: z.ZodString;
231
+ }, z.core.$strict>;
232
+ sender: z.ZodString;
233
+ salt: z.ZodString;
234
+ nonce: z.ZodString;
235
+ paddedLength: z.ZodNumber;
236
+ contentMac: z.ZodString;
237
+ ciphertext: z.ZodOptional<z.ZodString>;
238
+ ciphertextRef: z.ZodOptional<z.ZodObject<{
239
+ blobId: z.ZodString;
240
+ contentHash: z.ZodString;
241
+ size: z.ZodNumber;
242
+ contentType: z.ZodString;
243
+ }, z.core.$strict>>;
244
+ }, z.core.$strict>;
176
245
  /**
177
246
  * A task's frozen sealing policy (§6.2). The offer carries it in plaintext.
178
247
  * `epoch` is the epoch of the offer's task-input content, and the lowest epoch
179
- * the task accepts.
248
+ * the task accepts. `memory-projection` needs `task-output`: a terminal seals
249
+ * the task's memory-change evidence inside its task-output ciphertext.
180
250
  */
181
251
  export declare const TaskSealingSchema: z.ZodObject<{
182
252
  scopeId: z.ZodString;
183
253
  epoch: z.ZodNumber;
184
254
  categories: z.ZodArray<z.ZodEnum<{
255
+ "agent-message": "agent-message";
256
+ "memory-projection": "memory-projection";
185
257
  "task-input": "task-input";
186
258
  "task-output": "task-output";
187
259
  }>>;
@@ -1,4 +1,4 @@
1
- import type { SealedPayloadV1Category } from './sealed-payload';
1
+ import type { SealedPayloadCategory } from './sealed-payload';
2
2
  /**
3
3
  * How the sealed branch of a type treats one digest field of its plaintext
4
4
  * branch (D16, design §9.3).
@@ -20,7 +20,7 @@ export interface SealedSplitEntry {
20
20
  * and the content MAC bind the wire type as the family.
21
21
  */
22
22
  readonly marker: 'sealing' | 'sealed';
23
- readonly category: SealedPayloadV1Category;
23
+ readonly category: SealedPayloadCategory;
24
24
  /**
25
25
  * Payload fields that, after the envelope `task_id`, name this write in the
26
26
  * AAD and the content MAC (§8.2, §9.1). The sealed branch requires them.
@@ -32,7 +32,17 @@ export interface SealedSplitEntry {
32
32
  * object back and validates the plaintext branch.
33
33
  */
34
34
  readonly moved: readonly string[];
35
- /** Top-level fields of the plaintext branch that the sealed branch drops in v1. */
35
+ /**
36
+ * Top-level fields that also move into this ciphertext when the task's
37
+ * frozen `sealing` names the category (#1045): on a task that seals
38
+ * `memory-projection`, a terminal seals its `agentHomeMemoryChange`. The
39
+ * sealed branch schema cannot see the task's sealing, so the Host and the
40
+ * device check these fields; read them with {@link sealedMovedFields}.
41
+ */
42
+ readonly movedWith?: {
43
+ readonly [category in SealedPayloadCategory]?: readonly string[];
44
+ };
45
+ /** Top-level fields of the plaintext branch that the sealed branch drops. */
36
46
  readonly forbidden: readonly string[];
37
47
  /**
38
48
  * Every path of the plaintext branch whose name contains `digest`, `hash` or
@@ -102,6 +112,9 @@ export declare const SEALED_SPLIT_TABLE: Readonly<{
102
112
  readonly category: 'task-output';
103
113
  readonly identity: readonly [];
104
114
  readonly moved: readonly ["summary", "finalMessage", "document"];
115
+ readonly movedWith: {
116
+ readonly 'memory-projection': readonly ['agentHomeMemoryChange'];
117
+ };
105
118
  readonly forbidden: readonly ["artifactRefs", "preparedObservation"];
106
119
  readonly digests: {
107
120
  readonly 'agentRef.profileRevision': 'plain';
@@ -116,6 +129,9 @@ export declare const SEALED_SPLIT_TABLE: Readonly<{
116
129
  readonly category: 'task-output';
117
130
  readonly identity: readonly [];
118
131
  readonly moved: readonly ["reason"];
132
+ readonly movedWith: {
133
+ readonly 'memory-projection': readonly ['agentHomeMemoryChange'];
134
+ };
119
135
  readonly forbidden: readonly ["preparedObservation"];
120
136
  readonly digests: {
121
137
  readonly 'agentRef.profileRevision': 'plain';
@@ -139,6 +155,9 @@ export declare const SEALED_SPLIT_TABLE: Readonly<{
139
155
  readonly category: 'task-output';
140
156
  readonly identity: readonly [];
141
157
  readonly moved: readonly ["reason"];
158
+ readonly movedWith: {
159
+ readonly 'memory-projection': readonly ['agentHomeMemoryChange'];
160
+ };
142
161
  readonly forbidden: readonly [];
143
162
  readonly digests: {
144
163
  readonly 'agentRef.profileRevision': 'plain';
@@ -179,7 +198,34 @@ export declare const SEALED_SPLIT_TABLE: Readonly<{
179
198
  readonly digests: {};
180
199
  readonly inlineOnly: true;
181
200
  };
201
+ readonly 'agent.message.publish': {
202
+ readonly marker: 'sealed';
203
+ readonly category: 'agent-message';
204
+ readonly identity: readonly ["messageId", "cursor"];
205
+ readonly moved: readonly ["body"];
206
+ readonly forbidden: readonly ["contentHash", "byteCount"];
207
+ readonly digests: {
208
+ readonly 'agentRef.profileRevision': 'plain';
209
+ readonly contentHash: 'forbidden';
210
+ };
211
+ };
212
+ }>;
213
+ /**
214
+ * The receiver capability each category's sealed branches need: the v1
215
+ * categories `sealed-payload-v1`, the #1045 categories `sealed-payload-v2`.
216
+ */
217
+ export declare const SEALED_CATEGORY_CAPABILITY: Readonly<{
218
+ readonly 'task-input': 'sealed-payload-v1';
219
+ readonly 'agent-message': 'sealed-payload-v2';
220
+ readonly 'task-output': 'sealed-payload-v1';
221
+ readonly 'memory-projection': 'sealed-payload-v2';
182
222
  }>;
223
+ /**
224
+ * The top-level fields a `type` message of a task whose frozen `sealing`
225
+ * lists `categories` moves into its ciphertext: the type's `moved` fields and
226
+ * the `movedWith` fields of each listed category.
227
+ */
228
+ export declare function sealedMovedFields(type: SealableMessageType, categories: readonly SealedPayloadCategory[]): readonly string[];
183
229
  /** A message type with a sealed branch. */
184
230
  export type SealableMessageType = keyof typeof SEALED_SPLIT_TABLE;
185
231
  export declare function isSealableMessageType(type: string): type is SealableMessageType;
package/dist/version.d.ts CHANGED
@@ -69,5 +69,5 @@ export declare const AGENT_HOME_READERS_CAPABILITY: 'agent-home-readers';
69
69
  * MCP configuration.
70
70
  */
71
71
  export declare const CUSTOM_HARNESS_CAPABILITY: 'custom-harness';
72
- export declare const CAPABILITY_FLAGS: readonly ['steer', 'blob-upload', 'dispatch-selection', "provider-profile-binding", 'toolset-selection', 'agent-home-contract', "agent-home-readers", "agent-egress-policy", "agent-egress-reliable-ack", "agent-message-egress", "agent-egress-fresh-session", "agent-content-workspace-read", "agent-content-transcript-read", "agent-content-artifact-read", "agent-home-projection", "agent-input-preparation-v9", "terminal-projection-selection", "host-mcp-task-context", "custom-harness", "mailbox-read-ahead", "provider-provisioning.v1", "agent-memory-intent.v1", "agent-memory-selection", "agent-memory-read-if-admitted", "agent-memory-read-write-if-admitted", 'agent-message-draft', "sealed-payload-v1", "sealed-output-ciphertext-ref", "runtime-failure-class"];
72
+ export declare const CAPABILITY_FLAGS: readonly ['steer', 'blob-upload', 'dispatch-selection', "provider-profile-binding", 'toolset-selection', 'agent-home-contract', "agent-home-readers", "agent-egress-policy", "agent-egress-reliable-ack", "agent-message-egress", "agent-egress-fresh-session", "agent-content-workspace-read", "agent-content-transcript-read", "agent-content-artifact-read", "agent-home-projection", "agent-input-preparation-v9", "terminal-projection-selection", "host-mcp-task-context", "custom-harness", "mailbox-read-ahead", "provider-provisioning.v1", "agent-memory-intent.v1", "agent-memory-selection", "agent-memory-read-if-admitted", "agent-memory-read-write-if-admitted", 'agent-message-draft', "sealed-payload-v1", "sealed-payload-v2", "sealed-output-ciphertext-ref", "runtime-failure-class", "runtime-quota-scope", "runtime-unavailable-cause"];
73
73
  export type CapabilityFlag = (typeof CAPABILITY_FLAGS)[number];
@@ -8,9 +8,10 @@
8
8
  *
9
9
  * - `gated`: the sender must not emit the surface unless the receiver
10
10
  * durably advertised `capability`. A field entry applies when the payload
11
- * carries the field (and, with `value`, when the field equals it); a
12
- * dotted field names a nested field, for example `sealed.ciphertextRef`. A type
13
- * entry applies to every message of that type. Server-to-daemon surfaces
11
+ * carries the field (and, with `value`, when the field equals it, or when
12
+ * the field is an array that includes it); a dotted field names a nested
13
+ * field, for example `sealed.ciphertextRef`. A type entry applies to every
14
+ * message of that type. Server-to-daemon surfaces
14
15
  * are gated by a device flag, daemon-to-server surfaces by a server flag.
15
16
  * - `strip-safe`: an optional field an older receiver drops without changing
16
17
  * what it executes or acknowledges.
@@ -36,7 +37,7 @@
36
37
  */
37
38
  import type { MessageType } from './messages';
38
39
  import type { CapabilityFlag } from './version';
39
- type HttpWireSurface = 'ChallengeRequest' | 'ChallengeResponse' | 'TokenRequest' | 'TokenResponse' | 'EventsPollQuery' | 'EventsPollResponse' | 'MessagesSendRequest' | 'MessagesSendResponse';
40
+ type HttpWireSurface = 'ChallengeRequest' | 'ChallengeResponse' | 'TokenRequest' | 'TokenResponse' | 'PairRequest' | 'PairResponse' | 'EventsPollQuery' | 'EventsPollResponse' | 'MessagesSendRequest' | 'MessagesSendResponse' | 'PresencePublishRequest';
40
41
  export type WireAddition = {
41
42
  readonly kind: 'envelope';
42
43
  readonly rule: 'gated';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@byok-sdk/protocol",
3
- "version": "0.31.0",
3
+ "version": "0.33.0",
4
4
  "description": "BYOK SDK wire protocol: envelope schema, message types, and codec helpers",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -29,6 +29,10 @@
29
29
  "./constants": {
30
30
  "types": "./dist/constants.d.ts",
31
31
  "import": "./dist/constants.js"
32
+ },
33
+ "./agent-memory": {
34
+ "types": "./dist/agent-memory.d.ts",
35
+ "import": "./dist/agent-memory.js"
32
36
  }
33
37
  },
34
38
  "files": [
@@ -49,7 +53,7 @@
49
53
  },
50
54
  "dependencies": {
51
55
  "@cfworker/json-schema": "4.1.1",
52
- "zod": "^4.4.3"
56
+ "zod": "^4.6.6"
53
57
  },
54
58
  "devDependencies": {
55
59
  "byok-protocol-previous": "https://registry.npmjs.org/@byok-sdk/protocol/-/protocol-0.30.2.tgz"
@@ -1,28 +0,0 @@
1
- /**
2
- * The device-level half of the task lane's capability gate (contract §8.1).
3
- *
4
- * Contract §8.1 fixes ONE name, `host-mcp-task-context`, carried on two
5
- * independent channels that must both be in place before a host may route a
6
- * tool invocation through the task lane:
7
- *
8
- * 1. **Deployment level** — the ADR-010 `CapabilityDeclarationSchema` a
9
- * deployment serves (`@byok-sdk/cloud`'s `CLOUD_CAPABILITIES`). §8.2(1)
10
- * governs it: a deployment declares the capability only once it is
11
- * completely implemented.
12
- * 2. **Device level** — this constant: a `conn.hello` capability string a host
13
- * consumes through `declaresCapabilities`. §8.3 governs it: a device whose
14
- * build cannot serve the lane simply does not advertise it, and the host
15
- * answers `unavailable` rather than reaching for the device assertion the
16
- * task lane does not accept.
17
- *
18
- * It lives in `@byok-sdk/protocol` for the same reason every other daemon
19
- * capability flag does: this package is the wire vocabulary both sides compile
20
- * against, and a name spelled twice is a name that can drift. It is registered
21
- * in `CAPABILITY_FLAGS` (`./version`), which is what makes it a value a daemon
22
- * can actually send.
23
- *
24
- * The spelling satisfies `@byok-sdk/core`'s `CAPABILITY_NAME_PATTERN`
25
- * (`/^[a-z0-9]+(?:[._-][a-z0-9]+)*$/`) on purpose: the deployment-level channel
26
- * carries this exact literal, so a name legal in only one of the two channels
27
- * would leave them naming different things.
28
- */