@byok-sdk/cloud 0.16.0 → 0.18.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.
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { BOARD_STATUSES, CapabilityDeclarationSchema, NONCE_SIGNING_DOMAIN, hasCapability, isTenantId, tenantId, principalTenant, parseDeviceProofEnvelope, deviceProofSigningInput, contentHash, parseCapabilityDeclaration, ByokCoreError, tenantKey, PRESENCE_LEVELS, createInMemoryCoreStores, authenticateDeviceAssertion, assertCapability, isCoreConflictError, isCoreError, DEVICE_PROOF_HEADER, TRUTH_RECORD_KINDS, STORAGE_ERROR_CODES, STORAGE_ERROR_HTTP_STATUS } from '@byok-sdk/core';
2
2
  export { DEVICE_PROOF_HEADER, NONCE_SIGNING_DOMAIN, isTenantId, tenantId } from '@byok-sdk/core';
3
- import { AgentEventOrUnknownSchema, PROTOCOL_VERSION, DAEMON_TO_SERVER_TYPES, AGENT_EGRESS_RELIABLE_ACK_CAPABILITY, encodeEnvelope, decodeEnvelope, AgentHomeProjectionCompletionRequestSchema, BYOK_PAIR_PATH, BYOK_CHALLENGE_PATH, BYOK_TOKEN_PATH, BYOK_CAPABILITIES_PATH, BYOK_AGENT_MEMORY_PROJECTIONS_PATH, BYOK_EVENTS_PATH, BYOK_MESSAGES_PATH, BYOK_AGENT_HOME_PROJECTION_COMPLETION_ROUTE, BYOK_BOARD_PATH, BYOK_BOARD_CLAIM_ROUTE, BYOK_BOARD_UNCLAIM_ROUTE, BYOK_BOARD_STATUS_ROUTE, BYOK_BOARD_STREAM_PATH, BYOK_PRESENCE_PATH, BYOK_ACTIVITY_PATH, BYOK_RECORDS_PATH, BYOK_RECORD_ROUTE, BYOK_SKILL_PACKS_PATH, BYOK_SKILL_PACK_FILE_ROUTE, BYOK_BLOBS_PATH, BYOK_BLOB_FINALIZE_ROUTE, BYOK_BLOB_URL_ROUTE, BYOK_BLOB_CONTENT_ROUTE, createEnvelope, AgentHomeProjectionPayloadSchema, AgentContentReadPayloadSchema, AGENT_EGRESS_POLICY_CAPABILITY, AGENT_EGRESS_FRESH_SESSION_CAPABILITY, AGENT_MESSAGE_EGRESS_CAPABILITY, TERMINAL_PROJECTION_SELECTION_CAPABILITY, TaskOfferForAgentWithEgressFreshPayloadSchema, AgentMessageServerContextSchema, TaskOfferForAgentWithEgressPayloadSchema, TaskOfferForAgentPayloadSchema, AgentMemoryProjectionMutationSchema, AgentMemoryProjectionReceiptSchema, AGENT_MEMORY_PROJECTION_MAX_ORDERING_VALUE, AgentMemoryProjectionEraseResultSchema, HarnessIdSchema, STRICT_AGENT_ONLY_CAPABILITY, AGENT_CONTENT_ARTIFACT_READ_CAPABILITY, AGENT_CONTENT_TRANSCRIPT_READ_CAPABILITY, AGENT_CONTENT_WORKSPACE_READ_CAPABILITY, AGENT_HOME_PROJECTION_CAPABILITY, AgentRefSchema, PairRequestSchema, PairResponseSchema, ChallengeRequestSchema, TokenRequestSchema, AgentMemoryProjectionCommitRequestSchema, agentMemoryProjectionBase64UrlByteLength, MAILBOX_READ_AHEAD_MAX_SEQS, MAILBOX_READ_AHEAD_CAPABILITY, MessagesSendRequestSchema, PresencePublishRequestSchema, CreateBlobRequestSchema, byokBlobContentPath, AgentEgressAckPayloadSchema, AgentContentReceiptPayloadSchema } from '@byok-sdk/protocol';
4
3
  import { z } from 'zod';
4
+ import { AgentEventOrUnknownSchema, AgentMessageServerContextSchema, AgentMessageEgressRequirementSchema, AgentRefSchema, TaskOfferForAgentWithEgressFreshPayloadSchema, PROTOCOL_VERSION, DAEMON_TO_SERVER_TYPES, AGENT_EGRESS_RELIABLE_ACK_CAPABILITY, encodeEnvelope, decodeEnvelope, AgentHomeProjectionCompletionRequestSchema, BYOK_PAIR_PATH, BYOK_CHALLENGE_PATH, BYOK_TOKEN_PATH, BYOK_CAPABILITIES_PATH, BYOK_AGENT_MEMORY_PROJECTIONS_PATH, BYOK_EVENTS_PATH, BYOK_MESSAGES_PATH, BYOK_AGENT_HOME_PROJECTION_COMPLETION_ROUTE, BYOK_BOARD_PATH, BYOK_BOARD_CLAIM_ROUTE, BYOK_BOARD_UNCLAIM_ROUTE, BYOK_BOARD_STATUS_ROUTE, BYOK_BOARD_STREAM_PATH, BYOK_PRESENCE_PATH, BYOK_ACTIVITY_PATH, BYOK_RECORDS_PATH, BYOK_RECORD_ROUTE, BYOK_SKILL_PACKS_PATH, BYOK_SKILL_PACK_FILE_ROUTE, BYOK_BLOBS_PATH, BYOK_BLOB_FINALIZE_ROUTE, BYOK_BLOB_URL_ROUTE, BYOK_BLOB_CONTENT_ROUTE, AgentMessagePublishPayloadSchema, isTaskOfferType, MESSAGE_PAYLOAD_SCHEMAS, createEnvelope, AgentHomeProjectionPayloadSchema, AgentContentReadPayloadSchema, AGENT_EGRESS_POLICY_CAPABILITY, AGENT_MESSAGE_EGRESS_CAPABILITY, TERMINAL_PROJECTION_SELECTION_CAPABILITY, TaskOfferForAgentWithEgressPayloadSchema, TaskOfferForAgentPayloadSchema, AgentMemoryProjectionMutationSchema, AgentMemoryProjectionReceiptSchema, AGENT_MEMORY_PROJECTION_MAX_ORDERING_VALUE, AgentMemoryProjectionEraseResultSchema, HarnessIdSchema, STRICT_AGENT_ONLY_CAPABILITY, AGENT_CONTENT_ARTIFACT_READ_CAPABILITY, AGENT_CONTENT_TRANSCRIPT_READ_CAPABILITY, AGENT_CONTENT_WORKSPACE_READ_CAPABILITY, AGENT_HOME_PROJECTION_CAPABILITY, AGENT_EGRESS_FRESH_SESSION_CAPABILITY, PairRequestSchema, PairResponseSchema, ChallengeRequestSchema, TokenRequestSchema, AgentMemoryProjectionCommitRequestSchema, agentMemoryProjectionBase64UrlByteLength, MAILBOX_READ_AHEAD_MAX_SEQS, MAILBOX_READ_AHEAD_CAPABILITY, MessagesSendRequestSchema, PresencePublishRequestSchema, CreateBlobRequestSchema, byokBlobContentPath, AgentMessageDispositionPayloadSchema, AgentEgressAckPayloadSchema, AgentContentReceiptPayloadSchema } from '@byok-sdk/protocol';
5
5
  import { Hono } from 'hono';
6
6
 
7
7
  // src/index.ts
@@ -25,1682 +25,1731 @@ async function hashOfferBytes(bytes) {
25
25
  return `sha256:${Array.from(hash, (byte) => byte.toString(16).padStart(2, "0")).join("")}`;
26
26
  }
27
27
 
28
- // src/approval-control.ts
29
- var StaleApprovalError = class extends Error {
30
- constructor(taskId, requestedApprovalId, currentApprovalId) {
31
- super(
32
- `cannot resolve approval ${requestedApprovalId} for task ${taskId}: the currently pending approval is ${currentApprovalId ?? "(none recorded)"}`
33
- );
34
- this.taskId = taskId;
35
- this.requestedApprovalId = requestedApprovalId;
36
- this.currentApprovalId = currentApprovalId;
37
- this.name = "StaleApprovalError";
38
- }
39
- taskId;
40
- requestedApprovalId;
41
- currentApprovalId;
28
+ // src/errors.ts
29
+ var CLOUD_ERROR_CODES = {
30
+ /** A pairing code that is unknown, expired, or already redeemed (§6.1). */
31
+ pairing_code_invalid: "pairing_code_invalid",
32
+ /** The composition handed a device row whose tenant is not a mintable `TenantId`. */
33
+ device_tenant_invalid: "device_tenant_invalid",
34
+ /**
35
+ * The mailbox committed a row `seq` that disagrees with the delivery `seq`
36
+ * its body factory baked into the envelope. Loud rather than silent: those
37
+ * two numbers ARE the daemon's redelivery cursor.
38
+ */
39
+ mailbox_seq_mismatch: "mailbox_seq_mismatch",
40
+ /** A capability declaration the host supplied that core refused. */
41
+ capability_declaration_invalid: "capability_declaration_invalid",
42
+ /**
43
+ * The declaration names a capability this composition cannot serve, so the
44
+ * deployment would publish a surface it does not have (ADR-010).
45
+ *
46
+ * Construction-time and fatal. A client learns what a deployment supports by
47
+ * READING the declaration and is entitled to act on it without probing, so a
48
+ * declaration that over-states is not a degraded deployment — it is a
49
+ * deployment whose one honest interface lies.
50
+ */
51
+ capability_over_declared: "capability_over_declared",
52
+ /** Host-supplied board labels or coordination input exceeded the explicit contract. */
53
+ coordination_input_invalid: "coordination_input_invalid",
54
+ /**
55
+ * A terminal receipt whose stored body is not a terminal envelope — either
56
+ * undecodable or a non-terminal type. Whatever wrote that row broke the
57
+ * receipt-store contract, so the typed read model fails closed instead of
58
+ * projecting a best-effort shape.
59
+ */
60
+ terminal_receipt_unreadable: "terminal_receipt_unreadable",
61
+ /** A progress/activity batch exceeded the configured event or byte ceiling. */
62
+ activity_batch_too_large: "activity_batch_too_large",
63
+ /** Host control-plane task lookup is tenant-closed and found no task. */
64
+ task_not_found: "task_not_found",
65
+ /**
66
+ * A host `approveTask`/`rejectTask` names a task with no approval to
67
+ * resolve: the durable timeline holds no unresolved `approval_requested`
68
+ * (including a tail that has expired or was never written), the attempt has
69
+ * already reached a terminal status, or no device ever claimed it so there
70
+ * is nothing to notify. Fail-closed by construction — cloud never invents a
71
+ * pending approval to make the call succeed.
72
+ */
73
+ task_not_awaiting_approval: "task_not_awaiting_approval",
74
+ /** Durable target-device capability is absent, revoked, or unavailable. */
75
+ agent_capability_missing: "agent_capability_missing",
76
+ /** Inbound Agent identity did not exactly match the offered identity. */
77
+ agent_ref_mismatch: "agent_ref_mismatch",
78
+ /** A strict Agent task id already names a durable attempt and cannot be re-enqueued. */
79
+ agent_task_already_exists: "agent_task_already_exists",
80
+ /** A first-write-wins Agent control record was replayed with a different body. */
81
+ agent_content_request_mismatch: "agent_content_request_mismatch",
82
+ /** A durable mailbox receipt id resolved to an envelope other than its exact acknowledgement. */
83
+ mailbox_receipt_mismatch: "mailbox_receipt_mismatch",
84
+ /** A task-free Agent-home request id already names a different immutable desired projection. */
85
+ agent_home_projection_request_conflict: "agent_home_projection_request_conflict",
86
+ /** A direct Agent-home completion did not identify a stored desired request for this exact device. */
87
+ agent_home_projection_request_not_found: "agent_home_projection_request_not_found",
88
+ /** A direct Agent-home completion changed the first durable terminal outcome. */
89
+ agent_home_projection_completion_conflict: "agent_home_projection_completion_conflict",
90
+ /** A task-free completion did not exactly echo its immutable desired projection binding. */
91
+ agent_home_projection_receipt_mismatch: "agent_home_projection_receipt_mismatch",
92
+ /** A receipt-store row at the projection namespace violated the frozen projection schema. */
93
+ agent_home_projection_receipt_invalid: "agent_home_projection_receipt_invalid",
94
+ /** A hosted Agent-memory mutation did not match the durable task/device/AgentRef binding. */
95
+ agent_memory_projection_task_mismatch: "agent_memory_projection_task_mismatch",
96
+ /** The embedder-owned grant/consent authority denied a hosted memory mutation. */
97
+ agent_memory_projection_authorization_denied: "agent_memory_projection_authorization_denied",
98
+ /** A redacted snapshot's decoded bytes did not match its declared hash or byte count. */
99
+ agent_memory_projection_hash_mismatch: "agent_memory_projection_hash_mismatch",
100
+ /** A writer epoch/source sequence already names a different immutable projection mutation. */
101
+ agent_memory_projection_replay_mismatch: "agent_memory_projection_replay_mismatch",
102
+ /** A hosted memory mutation came from an older writer epoch. */
103
+ agent_memory_projection_stale_epoch: "agent_memory_projection_stale_epoch",
104
+ /** A server-side erase fence prevents a deleted writer epoch from re-entering. */
105
+ agent_memory_projection_erased_epoch: "agent_memory_projection_erased_epoch",
106
+ /** A prior erase reached the protocol writer-epoch ceiling and cannot mint a later writer. */
107
+ agent_memory_projection_epoch_exhausted: "agent_memory_projection_epoch_exhausted",
108
+ /** A hosted memory mutation skipped or reset a required source sequence. */
109
+ agent_memory_projection_sequence_gap: "agent_memory_projection_sequence_gap"
42
110
  };
43
- function pendingApproval(tail) {
44
- if (tail === void 0) return void 0;
45
- let pending;
46
- for (const entry of tail.entries) {
47
- const event = entry.event;
48
- if (event.type === "approval_requested") {
49
- pending = {
50
- sourceEnvelopeId: entry.sourceEnvelopeId,
51
- revision: entry.revision,
52
- ...event.approvalId === void 0 ? {} : { approvalId: event.approvalId }
53
- };
54
- continue;
55
- }
56
- if (pending === void 0) continue;
57
- if (pending.approvalId === void 0 || pending.approvalId === event.approvalId) {
58
- pending = void 0;
59
- }
111
+ var ByokCloudError = class extends Error {
112
+ code;
113
+ constructor(code, message, options) {
114
+ super(message, options);
115
+ this.name = "ByokCloudError";
116
+ this.code = code;
60
117
  }
61
- return pending;
118
+ };
119
+ function isCloudError(value, code) {
120
+ if (!(value instanceof ByokCloudError)) return false;
121
+ return code === void 0 || value.code === code;
62
122
  }
63
123
 
64
- // src/steer-control.ts
65
- function steerRejectionMessage(taskId, code, status, runtime) {
66
- switch (code) {
67
- case "task_terminal":
68
- return `cannot steer task ${taskId}: task is already terminal (status ${status})`;
69
- case "task_not_running":
70
- return `cannot steer task ${taskId}: not running (status ${status})`;
71
- case "steer_unsupported_runtime":
72
- return `cannot steer task ${taskId}: claimed runtime ${runtime ?? "(unknown)"} does not support steering`;
124
+ // src/activity.ts
125
+ var DEFAULT_ACTIVITY_CAPACITY = 50;
126
+ var ActivityTaskIdSchema = z.string().min(1).max(200);
127
+ var SourceEnvelopeIdSchema = z.string().max(200).regex(/\S/, "sourceEnvelopeId must not be blank");
128
+ var ActivityAppendRequestSchema = z.object({
129
+ taskId: ActivityTaskIdSchema,
130
+ sourceEnvelopeId: SourceEnvelopeIdSchema,
131
+ batchSeq: z.number().int().nonnegative(),
132
+ events: z.array(AgentEventOrUnknownSchema).min(1),
133
+ dropped: z.number().int().nonnegative()
134
+ });
135
+ var TimelineEventSchema = z.object({
136
+ taskId: ActivityTaskIdSchema,
137
+ sourceEnvelopeId: SourceEnvelopeIdSchema,
138
+ batchSeq: z.number().int().nonnegative(),
139
+ eventIndex: z.number().int().nonnegative(),
140
+ /**
141
+ * The producer-declared loss attached to this source batch. It travels with
142
+ * every retained event so a replay includes it in the canonical batch
143
+ * binding rather than treating the aggregate as an incidental counter.
144
+ */
145
+ sourceDropped: z.number().int().nonnegative().optional(),
146
+ receivedAt: z.iso.datetime(),
147
+ event: AgentEventOrUnknownSchema
148
+ });
149
+ function validateActivityAppend(input) {
150
+ if (!Number.isFinite(input.ttlMs) || input.ttlMs <= 0) {
151
+ throw new ByokCloudError(
152
+ "coordination_input_invalid",
153
+ `Activity ttl must be a positive number of milliseconds, received ${String(input.ttlMs)}.`
154
+ );
73
155
  }
74
- }
75
- var SteerRejectedError = class extends Error {
76
- constructor(taskId, code, status, runtime) {
77
- super(steerRejectionMessage(taskId, code, status, runtime));
78
- this.taskId = taskId;
79
- this.code = code;
80
- this.status = status;
81
- this.runtime = runtime;
82
- this.name = "SteerRejectedError";
156
+ const capacity = input.capacity ?? DEFAULT_ACTIVITY_CAPACITY;
157
+ if (!Number.isSafeInteger(capacity) || capacity <= 0) {
158
+ throw new ByokCloudError(
159
+ "coordination_input_invalid",
160
+ `Activity capacity must be a positive integer, received ${String(capacity)}.`
161
+ );
83
162
  }
84
- taskId;
85
- code;
86
- status;
87
- runtime;
88
- };
89
-
90
- // src/crypto/web-crypto.ts
91
- var PAIRING_CODE_ALPHABET = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789";
92
- var HEX_DIGITS = "0123456789abcdef";
93
- function base64UrlEncode(bytes) {
94
- let binary = "";
95
- for (const byte of bytes) binary += String.fromCharCode(byte);
96
- return btoa(binary).replaceAll("+", "-").replaceAll("/", "_").replaceAll("=", "");
97
- }
98
- function base64UrlDecode(value) {
99
- const padded = value.replaceAll("-", "+").replaceAll("_", "/");
100
- try {
101
- const binary = atob(padded);
102
- const bytes = new Uint8Array(binary.length);
103
- for (let index = 0; index < binary.length; index += 1) bytes[index] = binary.charCodeAt(index);
104
- return bytes;
105
- } catch {
106
- return void 0;
163
+ const parsed = ActivityAppendRequestSchema.safeParse(input);
164
+ if (!parsed.success) {
165
+ throw new ByokCloudError(
166
+ "coordination_input_invalid",
167
+ "Activity batches require stable source identity, order, at least one valid event, and a non-negative dropped count."
168
+ );
107
169
  }
170
+ return capacity;
108
171
  }
109
- function toHex(bytes) {
110
- let out = "";
111
- for (const byte of bytes) {
112
- out += HEX_DIGITS[byte >> 4 & 15];
113
- out += HEX_DIGITS[byte & 15];
114
- }
115
- return out;
172
+ function projectTimelineEvents(input, receivedAt) {
173
+ return input.events.map(
174
+ (event, eventIndex) => TimelineEventSchema.parse({
175
+ taskId: input.taskId,
176
+ sourceEnvelopeId: input.sourceEnvelopeId,
177
+ batchSeq: input.batchSeq,
178
+ eventIndex,
179
+ sourceDropped: input.dropped,
180
+ receivedAt,
181
+ event
182
+ })
183
+ );
116
184
  }
117
- function subtle() {
118
- const webCrypto = globalThis.crypto;
119
- if (webCrypto?.subtle === void 0) {
120
- throw new Error("@byok-sdk/cloud requires a WebCrypto implementation on globalThis.crypto");
185
+ function activitySourceBatchState(entries, input) {
186
+ const existing = entries.filter((entry) => entry.sourceEnvelopeId === input.sourceEnvelopeId).sort((left, right) => left.eventIndex - right.eventIndex);
187
+ if (existing.length === 0) return "absent";
188
+ if (existing.length !== input.events.length) return "conflict";
189
+ for (const [eventIndex, event] of input.events.entries()) {
190
+ const stored = existing[eventIndex];
191
+ if (stored === void 0 || stored.taskId !== input.taskId || stored.batchSeq !== input.batchSeq || stored.eventIndex !== eventIndex || stored.sourceDropped !== input.dropped || JSON.stringify(stored.event) !== JSON.stringify(event)) {
192
+ return "conflict";
193
+ }
121
194
  }
122
- return webCrypto.subtle;
195
+ return "same";
123
196
  }
124
- function createWebCrypto() {
125
- return {
126
- randomUuid() {
127
- return globalThis.crypto.randomUUID();
128
- },
129
- randomToken(byteLength) {
130
- return base64UrlEncode(globalThis.crypto.getRandomValues(new Uint8Array(byteLength)));
131
- },
132
- randomPairingCode(length) {
133
- const bytes = globalThis.crypto.getRandomValues(new Uint8Array(length));
134
- let out = "";
135
- for (const byte of bytes) {
136
- out += PAIRING_CODE_ALPHABET[byte % PAIRING_CODE_ALPHABET.length];
137
- }
138
- return out;
139
- },
140
- async verifyEd25519(publicKeyBase64Url, message, signature) {
141
- const signatureBytes = base64UrlDecode(signature);
142
- if (signatureBytes === void 0) return false;
143
- try {
144
- const key = await subtle().importKey(
145
- "jwk",
146
- { kty: "OKP", crv: "Ed25519", x: publicKeyBase64Url },
147
- { name: "Ed25519" },
148
- false,
149
- ["verify"]
150
- );
151
- const messageBytes = typeof message === "string" ? new TextEncoder().encode(message) : message;
152
- return await subtle().verify({ name: "Ed25519" }, key, signatureBytes, messageBytes);
153
- } catch {
154
- return false;
155
- }
156
- },
157
- async hmacSha256(secret, message) {
158
- const key = await subtle().importKey("raw", secret, { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
159
- const signature = await subtle().sign("HMAC", key, new TextEncoder().encode(message));
160
- return base64UrlEncode(new Uint8Array(signature));
161
- },
162
- async sha256(data) {
163
- const digest = await subtle().digest("SHA-256", data);
164
- return `sha256:${toHex(new Uint8Array(digest))}`;
165
- },
166
- timingSafeEqual(left, right) {
167
- const leftBytes = base64UrlDecode(left);
168
- const rightBytes = base64UrlDecode(right);
169
- if (leftBytes === void 0 || rightBytes === void 0) return false;
170
- if (leftBytes.length !== rightBytes.length) return false;
171
- let diff = 0;
172
- for (let index = 0; index < leftBytes.length; index += 1) {
173
- diff |= (leftBytes[index] ?? 0) ^ (rightBytes[index] ?? 0);
174
- }
175
- return diff === 0;
176
- }
177
- };
197
+ function parseTimelineEvents(value) {
198
+ return z.array(TimelineEventSchema).parse(value);
199
+ }
200
+ function compareTimelineEvents(left, right) {
201
+ return left.batchSeq - right.batchSeq || left.eventIndex - right.eventIndex;
202
+ }
203
+ function activityCursor(entries) {
204
+ const last = entries.at(-1);
205
+ return last === void 0 ? void 0 : { batchSeq: last.batchSeq, eventIndex: last.eventIndex };
206
+ }
207
+ function activityTailKey(tenant, taskId) {
208
+ return tenantKey(tenant, taskId);
178
209
  }
179
210
 
180
- // src/auth/tokens.ts
181
- var ACCESS_TOKEN_TTL_SECONDS = 60 * 60;
182
- function encodeSegment(value) {
183
- return base64UrlEncode(new TextEncoder().encode(JSON.stringify(value)));
211
+ // src/coordination.ts
212
+ var DEFAULT_BOARD_CHANNEL_MAX_BYTES = 128;
213
+ var DEFAULT_BOARD_TITLE_MAX_BYTES = 512;
214
+ var DEFAULT_ACTIVITY_MAX_EVENTS = 50;
215
+ var DEFAULT_ACTIVITY_MAX_BYTES = 64 * 1024;
216
+ var DEFAULT_ACTIVITY_TTL_MS = 10 * 6e4;
217
+ var DEFAULT_PRESENCE_TTL_MS = 9e4;
218
+ var DEFAULT_PRESENCE_MINIMUM_INTERVAL_MS = 5e3;
219
+ var DEFAULT_PRESENCE_DETAIL_MAX_BYTES = 512;
220
+ var encoder = new TextEncoder();
221
+ var DEFAULT_ACTIVITY_BOUNDS = {
222
+ maxEvents: DEFAULT_ACTIVITY_MAX_EVENTS,
223
+ maxBytes: DEFAULT_ACTIVITY_MAX_BYTES,
224
+ capacity: DEFAULT_ACTIVITY_CAPACITY,
225
+ ttlMs: DEFAULT_ACTIVITY_TTL_MS
226
+ };
227
+ function assertBoardLabels(channel, title, limits) {
228
+ if (channel.length === 0 || encoder.encode(channel).length > limits.channelMaxBytes) {
229
+ throw new ByokCloudError(
230
+ "coordination_input_invalid",
231
+ `Board channel must contain 1..${limits.channelMaxBytes} UTF-8 bytes.`
232
+ );
233
+ }
234
+ if (title.length === 0 || encoder.encode(title).length > limits.titleMaxBytes) {
235
+ throw new ByokCloudError(
236
+ "coordination_input_invalid",
237
+ `Board title must contain 1..${limits.titleMaxBytes} UTF-8 bytes.`
238
+ );
239
+ }
184
240
  }
185
- function createHmacTokenSigner(secret, clock) {
186
- async function signingKey(usage) {
187
- return globalThis.crypto.subtle.importKey("raw", secret, { name: "HMAC", hash: "SHA-256" }, false, [usage]);
241
+ function assertTaskAttemptListLimit(limit) {
242
+ if (!Number.isSafeInteger(limit) || limit <= 0) {
243
+ throw new ByokCloudError(
244
+ "coordination_input_invalid",
245
+ `Task attempt list limit must be a positive integer; received ${String(limit)}.`
246
+ );
188
247
  }
189
- async function signature(signingInput) {
190
- const key = await signingKey("sign");
191
- const bytes = await globalThis.crypto.subtle.sign("HMAC", key, new TextEncoder().encode(signingInput));
192
- return base64UrlEncode(new Uint8Array(bytes));
248
+ }
249
+ function validateActivityEvents(events, dropped, bounds) {
250
+ if (!Number.isSafeInteger(dropped) || dropped < 0) {
251
+ throw new ByokCloudError(
252
+ "coordination_input_invalid",
253
+ "Activity dropped must be a non-negative integer."
254
+ );
255
+ }
256
+ if (events.length === 0 || events.length > bounds.maxEvents) {
257
+ throw new ByokCloudError(
258
+ "activity_batch_too_large",
259
+ `Activity batch must contain 1..${bounds.maxEvents} events.`
260
+ );
261
+ }
262
+ const encoded = JSON.stringify(events);
263
+ if (encoder.encode(encoded).length > bounds.maxBytes) {
264
+ throw new ByokCloudError(
265
+ "activity_batch_too_large",
266
+ `Activity batch exceeds ${bounds.maxBytes} UTF-8 bytes.`
267
+ );
193
268
  }
194
- return {
195
- async sign(claims, expiresInSeconds) {
196
- const issuedAt = Math.floor(clock.now().getTime() / 1e3);
197
- const header = encodeSegment({ alg: "HS256", typ: "JWT" });
198
- const payload = encodeSegment({
199
- deviceId: claims.deviceId,
200
- tenantId: claims.tenantId,
201
- productId: claims.productId,
202
- iat: issuedAt,
203
- exp: issuedAt + expiresInSeconds
204
- });
205
- const signingInput = `${header}.${payload}`;
206
- return `${signingInput}.${await signature(signingInput)}`;
207
- },
208
- async verify(token) {
209
- const parts = token.split(".");
210
- if (parts.length !== 3) return void 0;
211
- const [header, payload, provided] = parts;
212
- const key = await signingKey("verify");
213
- const providedBytes = base64UrlDecode(provided);
214
- if (providedBytes === void 0) return void 0;
215
- const ok = await globalThis.crypto.subtle.verify(
216
- "HMAC",
217
- key,
218
- providedBytes,
219
- new TextEncoder().encode(`${header}.${payload}`)
220
- );
221
- if (!ok) return void 0;
222
- const headerBytes = base64UrlDecode(header);
223
- const payloadBytes = base64UrlDecode(payload);
224
- if (headerBytes === void 0 || payloadBytes === void 0) return void 0;
225
- let decodedHeader;
226
- let decodedPayload;
227
- try {
228
- decodedHeader = JSON.parse(new TextDecoder().decode(headerBytes));
229
- decodedPayload = JSON.parse(new TextDecoder().decode(payloadBytes));
230
- } catch {
231
- return void 0;
232
- }
233
- const alg = decodedHeader?.alg;
234
- if (alg !== "HS256") return void 0;
235
- const { deviceId, tenantId: tenantId4, productId, exp } = decodedPayload;
236
- if (typeof deviceId !== "string" || typeof tenantId4 !== "string" || typeof productId !== "string") {
237
- return void 0;
238
- }
239
- if (typeof exp !== "number" || clock.now().getTime() / 1e3 >= exp) return void 0;
240
- return { deviceId, tenantId: tenantId4, productId };
241
- }
242
- };
243
269
  }
244
- function verifyNonceSignature(crypto2, devicePublicKey, nonce, signature) {
245
- return crypto2.verifyEd25519(devicePublicKey, NONCE_SIGNING_DOMAIN + nonce, signature);
270
+ function validateActivityBatch(input, bounds) {
271
+ validateActivityEvents(input.events, input.dropped, bounds);
272
+ validateActivityAppend({ ...input, ttlMs: bounds.ttlMs, capacity: bounds.capacity });
246
273
  }
247
-
248
- // src/auth/plane.ts
249
- var PAIRING_CODE_TTL_MS = 10 * 60 * 1e3;
250
- var PAIRING_CODE_LENGTH = 8;
251
- var DEVICE_IDENTITY_PROOF_KEY_ID = "identity";
252
- var DEVICE_IDENTITY_PROOF_KEY_EPOCH = 0;
253
- function createAuthPlane(deps) {
254
- const { stores, crypto: crypto2, clock, tokenSigner } = deps;
255
- const ttlSeconds = deps.accessTokenTtlSeconds ?? ACCESS_TOKEN_TTL_SECONDS;
256
- return {
257
- async createPairingCode(tenant, input) {
258
- const expiresAt = new Date(clock.now().getTime() + (input.ttlMs ?? PAIRING_CODE_TTL_MS)).toISOString();
259
- return stores.pairingCodes.issue(tenant, {
260
- code: crypto2.randomPairingCode(PAIRING_CODE_LENGTH),
261
- productId: input.productId,
262
- expiresAt
263
- });
264
- },
265
- async redeemAndRegister(input) {
266
- return stores.pairing.redeemAndRegister({
267
- pairingCode: input.pairingCode,
268
- deviceId: `dev_${crypto2.randomUuid()}`,
269
- deviceName: input.deviceName,
270
- devicePublicKey: input.devicePublicKey,
271
- proofKeyId: DEVICE_IDENTITY_PROOF_KEY_ID,
272
- proofKeyEpoch: DEVICE_IDENTITY_PROOF_KEY_EPOCH,
273
- ...input.machineId === void 0 ? {} : { machineId: input.machineId }
274
- });
275
- },
276
- async resolveDevice(deviceId) {
277
- const device = await stores.devices.resolveByDeviceId(deviceId);
278
- if (device === void 0 || device.revoked) return void 0;
279
- return device;
280
- },
281
- issueNonce(device) {
282
- return stores.nonces.issue(device.tenantId, device.deviceId);
283
- },
284
- consumeNonceIfValid(device, nonce) {
285
- return stores.nonces.consumeIfValid(device.tenantId, device.deviceId, nonce);
286
- },
287
- verifySignature(device, nonce, signature) {
288
- return verifyNonceSignature(crypto2, device.devicePublicKey, nonce, signature);
289
- },
290
- async mintAccessToken(device) {
291
- const accessToken = await tokenSigner.sign(
292
- { deviceId: device.deviceId, tenantId: device.tenantId, productId: device.productId },
293
- ttlSeconds
294
- );
295
- return {
296
- accessToken,
297
- expiresAt: new Date(clock.now().getTime() + ttlSeconds * 1e3).toISOString()
298
- };
299
- }
300
- };
274
+ async function appendActivityEvents(activity, input, bounds) {
275
+ validateActivityBatch(input, bounds);
276
+ return activity.append({
277
+ taskId: input.taskId,
278
+ sourceEnvelopeId: input.sourceEnvelopeId,
279
+ batchSeq: input.batchSeq,
280
+ events: input.events,
281
+ dropped: input.dropped,
282
+ ttlMs: bounds.ttlMs,
283
+ capacity: bounds.capacity
284
+ });
301
285
  }
302
- var CLOUD_CAPABILITIES = {
303
- /** `GET /byok/events` long-poll receive (§8). */
304
- eventsLongPoll: "events.longpoll",
305
- /** `POST /byok/messages` batched send (§8.2). */
306
- messagesBatch: "messages.batch",
307
- /** The three bearer-authed blob routes: reserve/grant, explicit finalize, committed-only download (§7). */
308
- blobsPresigned: "blobs.presigned",
309
- /**
310
- * The two presigned `/byok/blobs/:id/content` routes — cloud carrying the
311
- * bytes itself.
312
- *
313
- * Split out of `blobs.presigned` because it was one capability describing two
314
- * separable facts. A composition whose bytes live in object storage mints
315
- * grants (`blobs.presigned`) but has no byte-proxy path at all, and saying so
316
- * by declaration is ADR-010's whole posture: a client reads what a deployment
317
- * serves, it never probes a `/content` route and interprets the status code.
318
- *
319
- * Spelled all-lowercase because core's declaration schema pins capability
320
- * names to `/^[a-z0-9]+(?:[._-][a-z0-9]+)*$/` — the same reason the sibling
321
- * above reads `events.longpoll` and not `events.longPoll`.
322
- */
323
- blobsContentProxy: "blobs.contentproxy",
324
- /** Board list/claim/unclaim/status routes. Polling is first-class under this declaration. */
325
- boardCoordination: "board.coordination",
326
- /** Additional SSE transport for the same board read model. */
327
- boardSse: "board.sse",
328
- /** Device-scoped five-level presence publication. */
329
- presenceHints: "presence.hints",
330
- /** Bounded task activity batch publication. */
331
- activityTail: "activity.tail",
332
- /** Request-bound device proof record manifest/read/write surface (S6). */
333
- truthRecords: "truth.records",
334
- /**
335
- * Tenant-scoped skill pack distribution: the manifest list plus the per-file
336
- * content route a paired device installs from.
337
- *
338
- * Hosted HTTP on purpose. A pack is DECLARATIVE CONTENT a device pulls after
339
- * reading this declaration — not a message — so `@byok-sdk/protocol` gains
340
- * nothing from it and the frozen v1 envelope stays untouched. Composition-
341
- * bound like `truth.records`: a deployment that declares it without supplying
342
- * a `SkillPackStore` is refused at construction rather than publishing two
343
- * routes it would then 404.
344
- */
345
- skillPacks: "skills.pack",
346
- /** Optional, one-way redacted Agent-memory snapshot mutation route. */
347
- agentMemoryProjection: "agent.memory.projection"
348
- };
349
- var CapabilitiesResponseSchema = CapabilityDeclarationSchema;
350
- function fullCapabilityDeclaration(version = 1, options = {}) {
351
- return {
352
- schema: "byok-capabilities-v1",
353
- version,
354
- capabilities: Object.values(CLOUD_CAPABILITIES).filter((capability) => {
355
- if (capability === CLOUD_CAPABILITIES.truthRecords) return options.includeTruthRecords === true;
356
- if (capability === CLOUD_CAPABILITIES.skillPacks) return options.includeSkillPacks === true;
357
- if (capability === CLOUD_CAPABILITIES.agentMemoryProjection) {
358
- return options.includeAgentMemoryProjection === true;
359
- }
360
- return true;
361
- })
362
- };
363
- }
364
- function declares(declaration, capability) {
365
- return hasCapability(declaration, capability);
366
- }
367
-
368
- // src/errors.ts
369
- var CLOUD_ERROR_CODES = {
370
- /** A pairing code that is unknown, expired, or already redeemed (§6.1). */
371
- pairing_code_invalid: "pairing_code_invalid",
372
- /** The composition handed a device row whose tenant is not a mintable `TenantId`. */
373
- device_tenant_invalid: "device_tenant_invalid",
374
- /**
375
- * The mailbox committed a row `seq` that disagrees with the delivery `seq`
376
- * its body factory baked into the envelope. Loud rather than silent: those
377
- * two numbers ARE the daemon's redelivery cursor.
378
- */
379
- mailbox_seq_mismatch: "mailbox_seq_mismatch",
380
- /** A capability declaration the host supplied that core refused. */
381
- capability_declaration_invalid: "capability_declaration_invalid",
382
- /**
383
- * The declaration names a capability this composition cannot serve, so the
384
- * deployment would publish a surface it does not have (ADR-010).
385
- *
386
- * Construction-time and fatal. A client learns what a deployment supports by
387
- * READING the declaration and is entitled to act on it without probing, so a
388
- * declaration that over-states is not a degraded deployment — it is a
389
- * deployment whose one honest interface lies.
390
- */
391
- capability_over_declared: "capability_over_declared",
392
- /** Host-supplied board labels or coordination input exceeded the explicit contract. */
393
- coordination_input_invalid: "coordination_input_invalid",
394
- /**
395
- * A terminal receipt whose stored body is not a terminal envelope — either
396
- * undecodable or a non-terminal type. Whatever wrote that row broke the
397
- * receipt-store contract, so the typed read model fails closed instead of
398
- * projecting a best-effort shape.
399
- */
400
- terminal_receipt_unreadable: "terminal_receipt_unreadable",
401
- /** A progress/activity batch exceeded the configured event or byte ceiling. */
402
- activity_batch_too_large: "activity_batch_too_large",
403
- /** Host control-plane task lookup is tenant-closed and found no task. */
404
- task_not_found: "task_not_found",
405
- /**
406
- * A host `approveTask`/`rejectTask` names a task with no approval to
407
- * resolve: the durable timeline holds no unresolved `approval_requested`
408
- * (including a tail that has expired or was never written), the attempt has
409
- * already reached a terminal status, or no device ever claimed it so there
410
- * is nothing to notify. Fail-closed by construction — cloud never invents a
411
- * pending approval to make the call succeed.
412
- */
413
- task_not_awaiting_approval: "task_not_awaiting_approval",
414
- /** Durable target-device capability is absent, revoked, or unavailable. */
415
- agent_capability_missing: "agent_capability_missing",
416
- /** Inbound Agent identity did not exactly match the offered identity. */
417
- agent_ref_mismatch: "agent_ref_mismatch",
418
- /** A strict Agent task id already names a durable attempt and cannot be re-enqueued. */
419
- agent_task_already_exists: "agent_task_already_exists",
420
- /** A first-write-wins Agent control record was replayed with a different body. */
421
- agent_content_request_mismatch: "agent_content_request_mismatch",
422
- /** A durable mailbox receipt id resolved to an envelope other than its exact acknowledgement. */
423
- mailbox_receipt_mismatch: "mailbox_receipt_mismatch",
424
- /** A task-free Agent-home request id already names a different immutable desired projection. */
425
- agent_home_projection_request_conflict: "agent_home_projection_request_conflict",
426
- /** A direct Agent-home completion did not identify a stored desired request for this exact device. */
427
- agent_home_projection_request_not_found: "agent_home_projection_request_not_found",
428
- /** A direct Agent-home completion changed the first durable terminal outcome. */
429
- agent_home_projection_completion_conflict: "agent_home_projection_completion_conflict",
430
- /** A task-free completion did not exactly echo its immutable desired projection binding. */
431
- agent_home_projection_receipt_mismatch: "agent_home_projection_receipt_mismatch",
432
- /** A receipt-store row at the projection namespace violated the frozen projection schema. */
433
- agent_home_projection_receipt_invalid: "agent_home_projection_receipt_invalid",
434
- /** A hosted Agent-memory mutation did not match the durable task/device/AgentRef binding. */
435
- agent_memory_projection_task_mismatch: "agent_memory_projection_task_mismatch",
436
- /** The embedder-owned grant/consent authority denied a hosted memory mutation. */
437
- agent_memory_projection_authorization_denied: "agent_memory_projection_authorization_denied",
438
- /** A redacted snapshot's decoded bytes did not match its declared hash or byte count. */
439
- agent_memory_projection_hash_mismatch: "agent_memory_projection_hash_mismatch",
440
- /** A writer epoch/source sequence already names a different immutable projection mutation. */
441
- agent_memory_projection_replay_mismatch: "agent_memory_projection_replay_mismatch",
442
- /** A hosted memory mutation came from an older writer epoch. */
443
- agent_memory_projection_stale_epoch: "agent_memory_projection_stale_epoch",
444
- /** A server-side erase fence prevents a deleted writer epoch from re-entering. */
445
- agent_memory_projection_erased_epoch: "agent_memory_projection_erased_epoch",
446
- /** A prior erase reached the protocol writer-epoch ceiling and cannot mint a later writer. */
447
- agent_memory_projection_epoch_exhausted: "agent_memory_projection_epoch_exhausted",
448
- /** A hosted memory mutation skipped or reset a required source sequence. */
449
- agent_memory_projection_sequence_gap: "agent_memory_projection_sequence_gap"
450
- };
451
- var ByokCloudError = class extends Error {
452
- code;
453
- constructor(code, message, options) {
454
- super(message, options);
455
- this.name = "ByokCloudError";
456
- this.code = code;
457
- }
458
- };
459
- function isCloudError(value, code) {
460
- if (!(value instanceof ByokCloudError)) return false;
461
- return code === void 0 || value.code === code;
462
- }
463
-
464
- // src/activity.ts
465
- var DEFAULT_ACTIVITY_CAPACITY = 50;
466
- var ActivityTaskIdSchema = z.string().min(1).max(200);
467
- var SourceEnvelopeIdSchema = z.string().max(200).regex(/\S/, "sourceEnvelopeId must not be blank");
468
- var ActivityAppendRequestSchema = z.object({
469
- taskId: ActivityTaskIdSchema,
470
- sourceEnvelopeId: SourceEnvelopeIdSchema,
471
- batchSeq: z.number().int().nonnegative(),
472
- events: z.array(AgentEventOrUnknownSchema).min(1),
473
- dropped: z.number().int().nonnegative()
474
- });
475
- var TimelineEventSchema = z.object({
476
- taskId: ActivityTaskIdSchema,
477
- sourceEnvelopeId: SourceEnvelopeIdSchema,
478
- batchSeq: z.number().int().nonnegative(),
479
- eventIndex: z.number().int().nonnegative(),
480
- /**
481
- * The producer-declared loss attached to this source batch. It travels with
482
- * every retained event so a replay includes it in the canonical batch
483
- * binding rather than treating the aggregate as an incidental counter.
484
- */
485
- sourceDropped: z.number().int().nonnegative().optional(),
286
+ var DEFAULT_APPROVAL_TIMELINE_CAPACITY = 50;
287
+ var DEFAULT_APPROVAL_TIMELINE_TTL_MS = 10 * 60 * 1e3;
288
+ var APPROVAL_SUMMARY_MAX_BYTES = 16 * 1024;
289
+ var NonBlankIdSchema = z.string().max(200).regex(/\S/, "value must not be blank");
290
+ var TaskIdSchema = z.string().min(1).max(200);
291
+ var ApprovalRequestedEventSchema = z.object({
292
+ type: z.literal("approval_requested"),
293
+ summary: z.string(),
294
+ approvalId: NonBlankIdSchema.optional()
295
+ }).strict();
296
+ var LocalApprovalResolutionEventSchema = z.object({
297
+ type: z.literal("approval_resolved"),
298
+ // Absent only when a daemon resolved the single outstanding request without
299
+ // native approval identity. No id is synthesized.
300
+ approvalId: NonBlankIdSchema.optional(),
301
+ decision: z.enum(["approve", "reject"]),
302
+ // A daemon's `task.approval_resolved` is pure wire observation; it cannot
303
+ // declare a host command payload.
304
+ resolvedBy: z.literal("local"),
305
+ at: z.iso.datetime({ offset: true })
306
+ }).strict();
307
+ var HostApprovalResolutionEventSchema = z.object({
308
+ type: z.literal("approval_resolved"),
309
+ approvalId: NonBlankIdSchema.optional(),
310
+ decision: z.literal("approve"),
311
+ resolvedBy: z.literal("host"),
312
+ at: z.iso.datetime({ offset: true })
313
+ }).strict();
314
+ var HostRejectResolutionEventSchema = z.object({
315
+ type: z.literal("approval_resolved"),
316
+ approvalId: NonBlankIdSchema.optional(),
317
+ decision: z.literal("reject"),
318
+ resolvedBy: z.literal("host"),
319
+ // Required even when the host deliberately omitted a wire reason. `null`
320
+ // records that complete payload, so a historical host reject with no field
321
+ // cannot be mistaken for a recoverable executable command.
322
+ reason: z.string().nullable(),
323
+ at: z.iso.datetime({ offset: true })
324
+ }).strict();
325
+ var ApprovalTimelineEventSchema = z.union([
326
+ ApprovalRequestedEventSchema,
327
+ LocalApprovalResolutionEventSchema,
328
+ HostApprovalResolutionEventSchema,
329
+ HostRejectResolutionEventSchema
330
+ ]);
331
+ var ApprovalObservationSchema = z.object({
332
+ taskId: TaskIdSchema,
333
+ sourceEnvelopeId: NonBlankIdSchema,
334
+ revision: z.number().int().positive(),
486
335
  receivedAt: z.iso.datetime(),
487
- event: AgentEventOrUnknownSchema
336
+ event: ApprovalTimelineEventSchema
488
337
  });
489
- function validateActivityAppend(input) {
490
- if (!Number.isFinite(input.ttlMs) || input.ttlMs <= 0) {
338
+ function validateApprovalTimelineAppend(input) {
339
+ const capacity = input.capacity ?? DEFAULT_APPROVAL_TIMELINE_CAPACITY;
340
+ const ttlMs = input.ttlMs ?? DEFAULT_APPROVAL_TIMELINE_TTL_MS;
341
+ if (!Number.isSafeInteger(capacity) || capacity <= 0) {
491
342
  throw new ByokCloudError(
492
343
  "coordination_input_invalid",
493
- `Activity ttl must be a positive number of milliseconds, received ${String(input.ttlMs)}.`
344
+ `Approval timeline capacity must be a positive integer, received ${String(capacity)}.`
494
345
  );
495
346
  }
496
- const capacity = input.capacity ?? DEFAULT_ACTIVITY_CAPACITY;
497
- if (!Number.isSafeInteger(capacity) || capacity <= 0) {
347
+ if (!Number.isFinite(ttlMs) || ttlMs <= 0) {
498
348
  throw new ByokCloudError(
499
349
  "coordination_input_invalid",
500
- `Activity capacity must be a positive integer, received ${String(capacity)}.`
350
+ `Approval timeline ttl must be a positive number of milliseconds, received ${String(ttlMs)}.`
501
351
  );
502
352
  }
503
- const parsed = ActivityAppendRequestSchema.safeParse(input);
353
+ const parsed = z.object({
354
+ taskId: TaskIdSchema,
355
+ sourceEnvelopeId: NonBlankIdSchema,
356
+ event: ApprovalTimelineEventSchema
357
+ }).safeParse(input);
504
358
  if (!parsed.success) {
505
359
  throw new ByokCloudError(
506
360
  "coordination_input_invalid",
507
- "Activity batches require stable source identity, order, at least one valid event, and a non-negative dropped count."
361
+ "Approval observations require stable source identity and a valid native lifecycle event."
508
362
  );
509
363
  }
510
- return capacity;
364
+ if (parsed.data.event.type === "approval_requested" && new TextEncoder().encode(parsed.data.event.summary).byteLength > APPROVAL_SUMMARY_MAX_BYTES) {
365
+ throw new ByokCloudError(
366
+ "coordination_input_invalid",
367
+ `Approval summary exceeds ${APPROVAL_SUMMARY_MAX_BYTES} UTF-8 bytes.`
368
+ );
369
+ }
370
+ return { capacity, ttlMs, event: parsed.data.event };
511
371
  }
512
- function projectTimelineEvents(input, receivedAt) {
513
- return input.events.map(
514
- (event, eventIndex) => TimelineEventSchema.parse({
515
- taskId: input.taskId,
516
- sourceEnvelopeId: input.sourceEnvelopeId,
517
- batchSeq: input.batchSeq,
518
- eventIndex,
519
- sourceDropped: input.dropped,
520
- receivedAt,
521
- event
522
- })
523
- );
524
- }
525
- function activitySourceBatchState(entries, input) {
526
- const existing = entries.filter((entry) => entry.sourceEnvelopeId === input.sourceEnvelopeId).sort((left, right) => left.eventIndex - right.eventIndex);
527
- if (existing.length === 0) return "absent";
528
- if (existing.length !== input.events.length) return "conflict";
529
- for (const [eventIndex, event] of input.events.entries()) {
530
- const stored = existing[eventIndex];
531
- if (stored === void 0 || stored.taskId !== input.taskId || stored.batchSeq !== input.batchSeq || stored.eventIndex !== eventIndex || stored.sourceDropped !== input.dropped || JSON.stringify(stored.event) !== JSON.stringify(event)) {
532
- return "conflict";
533
- }
372
+ function validateApprovalTimelineResolve(input) {
373
+ const validated = validateApprovalTimelineAppend(input);
374
+ const expected = z.object({
375
+ expectedSourceEnvelopeId: NonBlankIdSchema,
376
+ expectedRevision: z.number().int().positive()
377
+ }).safeParse(input);
378
+ if (!expected.success || validated.event.type !== "approval_resolved" || validated.event.resolvedBy !== "host") {
379
+ throw new ByokCloudError(
380
+ "coordination_input_invalid",
381
+ "Host approval resolution requires an exact pending source/revision and a host resolution event."
382
+ );
534
383
  }
535
- return "same";
384
+ return validated;
536
385
  }
537
- function parseTimelineEvents(value) {
538
- return z.array(TimelineEventSchema).parse(value);
386
+ function approvalTimelineKey(tenant, taskId) {
387
+ return tenantKey(tenant, taskId);
539
388
  }
540
- function compareTimelineEvents(left, right) {
541
- return left.batchSeq - right.batchSeq || left.eventIndex - right.eventIndex;
389
+ function parseApprovalObservations(value) {
390
+ return z.array(ApprovalObservationSchema).parse(value);
542
391
  }
543
- function activityCursor(entries) {
544
- const last = entries.at(-1);
545
- return last === void 0 ? void 0 : { batchSeq: last.batchSeq, eventIndex: last.eventIndex };
392
+ function approvalTimelineCursor(entries) {
393
+ return entries.at(-1)?.revision;
546
394
  }
547
- function activityTailKey(tenant, taskId) {
548
- return tenantKey(tenant, taskId);
395
+ async function projectTerminalToReview(board, taskId) {
396
+ const item = await board.get(taskId);
397
+ if (item === void 0 || item.status !== "in_progress") return;
398
+ try {
399
+ await board.updateStatus({
400
+ itemId: taskId,
401
+ expectedStatus: "in_progress",
402
+ status: "in_review"
403
+ });
404
+ } catch (caught) {
405
+ if (isCoreConflictError(caught)) return;
406
+ throw caught;
407
+ }
408
+ }
409
+ function agentReliabilityKey(domain, deviceId, agentRef, id) {
410
+ const exact = AgentRefSchema.parse(agentRef);
411
+ return `${domain}:${JSON.stringify([deviceId, exact.agentId, exact.profileRevision, id])}`;
549
412
  }
550
413
 
551
- // src/coordination.ts
552
- var DEFAULT_BOARD_CHANNEL_MAX_BYTES = 128;
553
- var DEFAULT_BOARD_TITLE_MAX_BYTES = 512;
554
- var DEFAULT_ACTIVITY_MAX_EVENTS = 50;
555
- var DEFAULT_ACTIVITY_MAX_BYTES = 64 * 1024;
556
- var DEFAULT_ACTIVITY_TTL_MS = 10 * 6e4;
557
- var DEFAULT_PRESENCE_TTL_MS = 9e4;
558
- var DEFAULT_PRESENCE_MINIMUM_INTERVAL_MS = 5e3;
559
- var DEFAULT_PRESENCE_DETAIL_MAX_BYTES = 512;
560
- var encoder = new TextEncoder();
561
- var DEFAULT_ACTIVITY_BOUNDS = {
562
- maxEvents: DEFAULT_ACTIVITY_MAX_EVENTS,
563
- maxBytes: DEFAULT_ACTIVITY_MAX_BYTES,
564
- capacity: DEFAULT_ACTIVITY_CAPACITY,
565
- ttlMs: DEFAULT_ACTIVITY_TTL_MS
566
- };
567
- function assertBoardLabels(channel, title, limits) {
568
- if (channel.length === 0 || encoder.encode(channel).length > limits.channelMaxBytes) {
569
- throw new ByokCloudError(
570
- "coordination_input_invalid",
571
- `Board channel must contain 1..${limits.channelMaxBytes} UTF-8 bytes.`
572
- );
414
+ // src/inbound.ts
415
+ async function handleAgentMessagePublish(stores, deviceId, taskId, payload, consume, alreadyDeduplicated) {
416
+ const device = await stores.devices.get(deviceId);
417
+ const attempt = await stores.tasks.get(taskId);
418
+ const binding = await stores.receipts.get(`agent-message-offer:${deviceId}:${taskId}`);
419
+ if (device === void 0 || device.revoked || !hasCapabilities(device.capabilities, [AGENT_MESSAGE_EGRESS_CAPABILITY]) || attempt === void 0 || attempt.deviceId !== deviceId || binding === void 0 || attempt.agentRef === void 0 || !sameAgentRef(attempt.agentRef, payload.agentRef)) return { outcome: "rejected" };
420
+ let frozen;
421
+ try {
422
+ frozen = JSON.parse(binding.body);
423
+ } catch {
424
+ return { outcome: "rejected" };
573
425
  }
574
- if (title.length === 0 || encoder.encode(title).length > limits.titleMaxBytes) {
575
- throw new ByokCloudError(
576
- "coordination_input_invalid",
577
- `Board title must contain 1..${limits.titleMaxBytes} UTF-8 bytes.`
578
- );
426
+ if (!sameAgentRef(frozen.agentRef, payload.agentRef) || frozen.context === void 0 || frozen.requirement?.contract !== payload.contract || frozen.sessionRef !== void 0 && frozen.sessionRef !== payload.sessionRef || frozen.requirement.contentType !== payload.contentType || typeof frozen.requirement.maxBytes !== "number" || payload.byteCount > frozen.requirement.maxBytes) return { outcome: "rejected" };
427
+ const priorDisposition = await readAgentMessageDisposition(stores, deviceId, taskId, payload);
428
+ if (priorDisposition !== void 0) return { outcome: "duplicate", disposition: priorDisposition };
429
+ const payloadBody = JSON.stringify(payload);
430
+ const reservation = await stores.tasks.reserveAgentMessage({
431
+ taskId,
432
+ deviceId,
433
+ messageId: payload.messageId,
434
+ payloadBody
435
+ });
436
+ if (reservation !== "reserved" && reservation !== "pending") return { outcome: "rejected" };
437
+ let decision;
438
+ if (consume === void 0) {
439
+ if (reservation === "pending") {
440
+ throw new Error("Agent message consumer is unavailable while reconciling an exact pending admission.");
441
+ }
442
+ decision = { outcome: "held", reasonCode: "consumer_unavailable" };
443
+ } else {
444
+ decision = await consume({ tenant: stores.tenant, deviceId, taskId, context: frozen.context, payload });
579
445
  }
446
+ const disposition = agentMessageDisposition(payload, decision);
447
+ const admission = await stores.tasks.finalizeAgentMessage({
448
+ taskId,
449
+ deviceId,
450
+ messageId: payload.messageId,
451
+ payloadBody,
452
+ terminalBody: JSON.stringify({ payload, disposition })
453
+ });
454
+ const terminal = admission?.terminalBody === void 0 ? void 0 : parseAgentMessageDisposition(payload, admission.terminalBody);
455
+ return terminal === void 0 ? { outcome: "rejected" } : { outcome: "accepted", disposition: terminal };
580
456
  }
581
- function assertTaskAttemptListLimit(limit) {
582
- if (!Number.isSafeInteger(limit) || limit <= 0) {
583
- throw new ByokCloudError(
584
- "coordination_input_invalid",
585
- `Task attempt list limit must be a positive integer; received ${String(limit)}.`
586
- );
587
- }
457
+ function agentMessageDisposition(payload, decision) {
458
+ return {
459
+ agentRef: payload.agentRef,
460
+ sessionRef: payload.sessionRef,
461
+ contract: payload.contract,
462
+ messageId: payload.messageId,
463
+ cursor: payload.cursor,
464
+ contentHash: payload.contentHash,
465
+ outcome: decision.outcome,
466
+ receiptId: crypto.randomUUID(),
467
+ ...decision.reasonCode === void 0 ? {} : { reasonCode: decision.reasonCode }
468
+ };
588
469
  }
589
- function validateActivityEvents(events, dropped, bounds) {
590
- if (!Number.isSafeInteger(dropped) || dropped < 0) {
591
- throw new ByokCloudError(
592
- "coordination_input_invalid",
593
- "Activity dropped must be a non-negative integer."
594
- );
595
- }
596
- if (events.length === 0 || events.length > bounds.maxEvents) {
597
- throw new ByokCloudError(
598
- "activity_batch_too_large",
599
- `Activity batch must contain 1..${bounds.maxEvents} events.`
600
- );
470
+ async function readAgentMessageDisposition(stores, deviceId, taskId, payload) {
471
+ const admission = await stores.tasks.readAgentMessage({
472
+ taskId,
473
+ deviceId,
474
+ messageId: payload.messageId,
475
+ payloadBody: JSON.stringify(payload)
476
+ });
477
+ return admission?.terminalBody === void 0 ? void 0 : parseAgentMessageDisposition(payload, admission.terminalBody);
478
+ }
479
+ function parseAgentMessageDisposition(payload, terminalBody) {
480
+ const invalid = () => new Error("Invalid persisted Agent message disposition.");
481
+ let decoded;
482
+ try {
483
+ decoded = JSON.parse(terminalBody);
484
+ } catch {
485
+ throw invalid();
601
486
  }
602
- const encoded = JSON.stringify(events);
603
- if (encoder.encode(encoded).length > bounds.maxBytes) {
604
- throw new ByokCloudError(
605
- "activity_batch_too_large",
606
- `Activity batch exceeds ${bounds.maxBytes} UTF-8 bytes.`
607
- );
487
+ if (decoded === null || typeof decoded !== "object" || Array.isArray(decoded)) throw invalid();
488
+ const record = decoded;
489
+ const storedPayload = AgentMessagePublishPayloadSchema.safeParse(record.payload);
490
+ const storedDisposition = AgentMessageDispositionPayloadSchema.safeParse(record.disposition);
491
+ if (!storedPayload.success || !storedDisposition.success || JSON.stringify(record.payload) !== JSON.stringify(payload)) throw invalid();
492
+ const disposition = storedDisposition.data;
493
+ if (!sameAgentRef(payload.agentRef, disposition.agentRef) || disposition.sessionRef !== payload.sessionRef || disposition.contract !== payload.contract || disposition.messageId !== payload.messageId || disposition.cursor !== payload.cursor || disposition.contentHash !== payload.contentHash) throw invalid();
494
+ return disposition;
495
+ }
496
+ function terminalReceiptKey(taskId) {
497
+ return `task:${taskId}:terminal`;
498
+ }
499
+ function sameAgentRef(expected, actual) {
500
+ return actual?.agentId === expected.agentId && actual.profileRevision === expected.profileRevision;
501
+ }
502
+ function sameReliableEgress(expected, actual) {
503
+ return sameAgentRef(expected.agentRef, actual.agentRef) && expected.sessionRef === actual.sessionRef && expected.policyRevision === actual.policyRevision && expected.eventId === actual.eventId && expected.cursor === actual.cursor && expected.contentHash === actual.contentHash && expected.byteCount === actual.byteCount && JSON.stringify(expected.payload) === JSON.stringify(actual.payload);
504
+ }
505
+ function contentReadCapability(surface) {
506
+ switch (surface) {
507
+ case "workspace":
508
+ return AGENT_CONTENT_WORKSPACE_READ_CAPABILITY;
509
+ case "transcript":
510
+ return AGENT_CONTENT_TRANSCRIPT_READ_CAPABILITY;
511
+ case "artifact":
512
+ return AGENT_CONTENT_ARTIFACT_READ_CAPABILITY;
608
513
  }
609
514
  }
610
- function validateActivityBatch(input, bounds) {
611
- validateActivityEvents(input.events, input.dropped, bounds);
612
- validateActivityAppend({ ...input, ttlMs: bounds.ttlMs, capacity: bounds.capacity });
515
+ function hasCapabilities(capabilities, required) {
516
+ return capabilities !== void 0 && required.every((capability) => capabilities.includes(capability));
613
517
  }
614
- async function appendActivityEvents(activity, input, bounds) {
615
- validateActivityBatch(input, bounds);
616
- return activity.append({
617
- taskId: input.taskId,
618
- sourceEnvelopeId: input.sourceEnvelopeId,
619
- batchSeq: input.batchSeq,
620
- events: input.events,
621
- dropped: input.dropped,
622
- ttlMs: bounds.ttlMs,
623
- capacity: bounds.capacity
624
- });
518
+ function contentRequestReceiptKey(deviceId, agentRef, requestId) {
519
+ return agentReliabilityKey("agent-content-request", deviceId, agentRef, requestId);
625
520
  }
626
- function extractBearerToken(header) {
627
- if (header === void 0) return void 0;
628
- const match = /^Bearer\s+(.+)$/i.exec(header);
629
- return match?.[1];
521
+ function contentReceiptKey(deviceId, agentRef, requestId) {
522
+ return agentReliabilityKey("agent-content-receipt", deviceId, agentRef, requestId);
630
523
  }
631
- async function authenticateBearer(header, deps) {
632
- const token = extractBearerToken(header);
633
- if (token === void 0) return void 0;
634
- const claims = await deps.tokenSigner.verify(token);
635
- if (claims === void 0) return void 0;
636
- if (!isTenantId(claims.tenantId)) return void 0;
637
- const device = await deps.devices.get(tenantId(claims.tenantId), claims.deviceId);
638
- if (device === void 0 || device.revoked) return void 0;
639
- if (device.productId !== claims.productId) return void 0;
640
- if (deps.instanceProductId !== void 0 && claims.productId !== deps.instanceProductId) {
524
+ function matchesContentReadReceipt(request, receipt) {
525
+ return request.requestId === receipt.requestId && request.surface === receipt.surface && request.actor.kind === receipt.actor.kind && request.actor.id === receipt.actor.id && sameAgentRef(request.agentRef, receipt.agentRef) && request.sessionRef === receipt.sessionRef && request.runtime === receipt.runtime && request.cwd === receipt.cwd && request.policyRevision === receipt.policyRevision && request.target === receipt.target && request.mimeType === receipt.mimeType && request.decodeAs === receipt.decodeAs;
526
+ }
527
+ async function readContentRequest(stores, deviceId, agentRef, requestId) {
528
+ const stored = await stores.receipts.get(contentRequestReceiptKey(deviceId, agentRef, requestId));
529
+ if (stored === void 0) return void 0;
530
+ try {
531
+ return AgentContentReadPayloadSchema.parse(JSON.parse(stored.body));
532
+ } catch {
641
533
  return void 0;
642
534
  }
643
- return {
644
- kind: "device",
645
- tenantId: device.tenantId,
646
- productId: device.productId,
647
- deviceId: device.deviceId
648
- };
649
535
  }
650
- function tenantStoresFor(principal, root) {
651
- const tenant = principalTenant(principal);
652
- const { core, cloud } = root;
653
- return {
654
- tenant,
655
- principal,
656
- mailbox: {
657
- append: (input) => core.mailbox.append(tenant, input),
658
- readAfter: (query) => core.mailbox.readAfter(tenant, query),
659
- recordDelivery: (input) => core.mailbox.recordDelivery(tenant, input),
660
- advanceCursor: (input) => core.mailbox.advanceCursor(tenant, input),
661
- readCursor: (deviceId) => core.mailbox.readCursor(tenant, deviceId)
662
- },
663
- board: {
664
- create: (input) => core.board.create(tenant, input),
665
- get: (itemId) => core.board.get(tenant, itemId),
666
- list: (query) => core.board.list(tenant, query),
667
- claim: (input) => core.board.claim(tenant, input),
668
- unclaim: (input) => core.board.unclaim(tenant, input),
669
- updateStatus: (input) => core.board.updateStatus(tenant, input)
670
- },
671
- presence: {
672
- publish: (input) => core.presence.publish(tenant, input),
673
- read: (deviceId) => core.presence.read(tenant, deviceId),
674
- list: () => core.presence.list(tenant)
675
- },
676
- activity: {
677
- append: (input) => cloud.activity.append(tenant, input),
678
- read: (taskId) => cloud.activity.read(tenant, taskId)
679
- },
680
- approvals: {
681
- append: (input) => cloud.approvals.append(tenant, input),
682
- read: (taskId) => cloud.approvals.read(tenant, taskId)
683
- },
684
- devices: {
685
- get: (deviceId) => cloud.devices.get(tenant, deviceId),
686
- list: () => cloud.devices.list(tenant),
687
- revoke: (deviceId) => cloud.devices.revoke(tenant, deviceId),
688
- recordCapabilities: (input) => principal.kind === "device" ? cloud.devices.recordCapabilities(tenant, {
689
- deviceId: principal.deviceId,
690
- capabilities: input.capabilities,
691
- ...input.harnesses === void 0 ? {} : { harnesses: input.harnesses }
692
- }) : Promise.resolve(void 0),
693
- readiness: () => cloud.devices.readiness(tenant, core.presence)
694
- },
695
- tasks: {
696
- open: (input) => cloud.tasks.open(tenant, input),
697
- reserveAgentOffer: (input) => cloud.tasks.reserveAgentOffer(tenant, input),
698
- reserveAgentMessage: (input) => cloud.tasks.reserveAgentMessage(tenant, input),
699
- readAgentMessage: (input) => cloud.tasks.readAgentMessage(tenant, input),
700
- finalizeAgentMessage: (input) => cloud.tasks.finalizeAgentMessage(tenant, input),
701
- get: (taskId) => cloud.tasks.get(tenant, taskId),
702
- getMany: (taskIds) => cloud.tasks.getMany(tenant, taskIds),
703
- list: (query) => cloud.tasks.list(tenant, query),
704
- claim: (input) => cloud.tasks.claim(tenant, input),
705
- recordStatus: (input) => cloud.tasks.recordStatus(tenant, input)
706
- },
707
- cancellations: {
708
- request: (input) => cloud.cancellations.request(tenant, input)
709
- },
710
- dedup: {
711
- checkAndRecord: (deviceId, envelopeId) => cloud.dedup.checkAndRecord(tenant, deviceId, envelopeId),
712
- checkAndRecordAgent: (deviceId, agentRef, envelopeId) => cloud.dedup.checkAndRecordAgent(tenant, deviceId, agentRef, envelopeId)
713
- },
714
- receipts: {
715
- record: (input) => cloud.receipts.record(tenant, input),
716
- get: (key) => cloud.receipts.get(tenant, key)
717
- },
718
- egress: {
719
- record: (input) => cloud.egress.record(tenant, input),
720
- get: (deviceId, agentRef, eventId) => cloud.egress.get(tenant, deviceId, agentRef, eventId)
721
- },
722
- blobs: {
723
- createUpload: (reservation) => cloud.blobs.createUpload(tenant, reservation),
724
- observeUpload: (blobId, reservation) => cloud.blobs.observeUpload(tenant, blobId, reservation),
725
- getDownloadUrl: (blobId) => cloud.blobs.getDownloadUrl(tenant, blobId)
726
- },
727
- quota: {
728
- readReservation: (reservationId) => core.quota.readReservation(tenant, reservationId),
729
- reserve: (input) => core.quota.reserve(tenant, input),
730
- finalizeReservation: (input) => core.quota.finalizeReservation(tenant, input),
731
- abortReservation: (reservationId) => core.quota.abortReservation(tenant, reservationId)
732
- },
733
- rateLimiter: {
734
- consume: (deviceId) => cloud.rateLimiter.consume(tenant, deviceId)
735
- }
736
- };
737
- }
738
-
739
- // src/handlers/shared.ts
740
- async function readJsonBody(c) {
741
- try {
742
- return await c.req.json();
743
- } catch {
744
- return void 0;
536
+ function inboundAgentRef(envelope) {
537
+ switch (envelope.type) {
538
+ case "task.claim":
539
+ case "task.decline":
540
+ case "task.complete":
541
+ case "task.fail":
542
+ case "task.cancelled":
543
+ return envelope.payload.agentRef;
544
+ default:
545
+ return void 0;
745
546
  }
746
547
  }
747
- async function readBoundedJsonBody(c, maximum) {
748
- const declaredLength = c.req.header("content-length");
749
- if (declaredLength !== void 0) {
750
- const parsedLength = parseContentLength(declaredLength);
751
- if (parsedLength !== void 0 && parsedLength > BigInt(maximum)) {
752
- cancelBody(c.req.raw.body);
753
- return { body: void 0, tooLarge: true };
548
+ function agentRefEchoRequired(envelope) {
549
+ return envelope.type === "task.claim" || envelope.type === "task.decline" || envelope.type === "task.complete" || envelope.type === "task.fail" || envelope.type === "task.cancelled";
550
+ }
551
+ async function handleInboundEnvelope(stores, deviceId, envelope, activityBounds = DEFAULT_ACTIVITY_BOUNDS, agentMessageConsume, observer) {
552
+ if (envelope.v !== PROTOCOL_VERSION) return "rejected";
553
+ const outcome = await applyInboundGate(
554
+ stores,
555
+ deviceId,
556
+ envelope,
557
+ activityBounds,
558
+ agentMessageConsume
559
+ );
560
+ if (outcome === "accepted" && observer !== void 0) {
561
+ try {
562
+ observer.onInboundCommitted({ tenantId: stores.tenant, deviceId, envelope, outcome });
563
+ } catch {
754
564
  }
755
565
  }
756
- const stream = c.req.raw.body;
757
- if (stream === null) return { body: void 0, tooLarge: false };
758
- const reader = stream.getReader();
759
- const chunks = [];
760
- let total = 0;
761
- try {
762
- while (true) {
763
- const { done, value } = await reader.read();
764
- if (done) break;
765
- if (value.byteLength > maximum - total) {
766
- cancelBody(reader);
767
- return { body: void 0, tooLarge: true };
768
- }
769
- if (value.byteLength === 0) continue;
770
- chunks.push(value);
771
- total += value.byteLength;
566
+ return outcome;
567
+ }
568
+ async function applyInboundGate(stores, deviceId, envelope, activityBounds, agentMessageConsume) {
569
+ if (!await stores.rateLimiter.consume(deviceId)) return "rate_limited";
570
+ if (envelope.type === "conn.hello") {
571
+ if (envelope.payload.deviceId !== deviceId) return "rejected";
572
+ if (envelope.payload.harnesses !== void 0 && !envelope.payload.capabilities.includes("custom-harness")) return "rejected";
573
+ if (!envelope.payload.protocolVersions.includes(PROTOCOL_VERSION)) return "rejected";
574
+ const device = await stores.devices.get(deviceId);
575
+ if (device === void 0 || device.revoked || device.productId !== envelope.payload.productId) {
576
+ return "rejected";
772
577
  }
773
- } catch {
774
- return { body: void 0, tooLarge: false };
775
- } finally {
776
- reader.releaseLock();
578
+ if (await stores.devices.recordCapabilities({ capabilities: envelope.payload.capabilities, ...envelope.payload.harnesses === void 0 ? {} : { harnesses: envelope.payload.harnesses } }) === void 0) {
579
+ return "rejected";
580
+ }
581
+ return completeInboundEnvelope(stores, deviceId, envelope.id, void 0);
777
582
  }
778
- const bytes = new Uint8Array(total);
779
- let offset = 0;
780
- for (const chunk of chunks) {
781
- bytes.set(chunk, offset);
782
- offset += chunk.byteLength;
583
+ if (!DAEMON_TO_SERVER_TYPES.includes(envelope.type)) return "rejected";
584
+ if (envelope.type === "agent.message.publish") {
585
+ const message = await handleAgentMessagePublish(
586
+ stores,
587
+ deviceId,
588
+ envelope.task_id,
589
+ envelope.payload,
590
+ agentMessageConsume);
591
+ return message.outcome === "rejected" ? "rejected" : completeInboundEnvelope(stores, deviceId, envelope.id, envelope.payload.agentRef);
783
592
  }
784
- return { body: parseJsonBytes(bytes), tooLarge: false };
785
- }
786
- function cancelBody(body) {
787
- if (body === null) return;
788
- try {
789
- void Promise.resolve(body.cancel()).catch(() => {
593
+ if (envelope.type === "agent.egress.reliable") {
594
+ const device = await stores.devices.get(deviceId);
595
+ if (device === void 0 || device.revoked || !hasCapabilities(device.capabilities, [AGENT_EGRESS_POLICY_CAPABILITY, AGENT_EGRESS_RELIABLE_ACK_CAPABILITY])) {
596
+ return "rejected";
597
+ }
598
+ const outcome = await stores.egress.record({
599
+ deviceId,
600
+ payload: envelope.payload,
601
+ receiptId: crypto.randomUUID()
790
602
  });
791
- } catch {
603
+ if (!outcome.created && !sameReliableEgress(outcome.record.payload, envelope.payload)) return "rejected";
604
+ return completeInboundEnvelope(stores, deviceId, envelope.id, envelope.payload.agentRef);
792
605
  }
793
- }
794
- function parseContentLength(value) {
795
- const normalized = value.trim();
796
- if (!/^\d+$/u.test(normalized)) return void 0;
797
- try {
798
- return BigInt(normalized);
799
- } catch {
800
- return void 0;
606
+ if (envelope.type === "agent.content.receipt") {
607
+ const device = await stores.devices.get(deviceId);
608
+ if (device === void 0 || device.revoked || envelope.payload.eventId !== envelope.payload.requestId || !hasCapabilities(device.capabilities, [contentReadCapability(envelope.payload.surface), AGENT_EGRESS_RELIABLE_ACK_CAPABILITY])) {
609
+ return "rejected";
610
+ }
611
+ const request = await readContentRequest(stores, deviceId, envelope.payload.agentRef, envelope.payload.requestId);
612
+ if (request === void 0 || !matchesContentReadReceipt(request, envelope.payload)) return "rejected";
613
+ const key = contentReceiptKey(deviceId, envelope.payload.agentRef, envelope.payload.requestId);
614
+ const body = JSON.stringify(envelope.payload);
615
+ const receipt = await stores.receipts.record({ key, body });
616
+ if (!receipt.created && receipt.receipt.body !== body) return "rejected";
617
+ return completeInboundEnvelope(stores, deviceId, envelope.id, envelope.payload.agentRef);
801
618
  }
802
- }
803
- function parseJsonBytes(bytes) {
804
- try {
805
- return JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
806
- } catch {
807
- return void 0;
619
+ const taskId = envelope.task_id;
620
+ if (taskId === void 0) return "rejected";
621
+ const attempt = await stores.tasks.get(taskId);
622
+ if (attempt === void 0 || attempt.deviceId !== deviceId) return "rejected";
623
+ if (attempt?.ownerDeviceId !== void 0 && attempt.ownerDeviceId !== deviceId) return "rejected";
624
+ if (envelope.type === "task.claim" && envelope.payload.deviceId !== deviceId) return "rejected";
625
+ if (envelope.type === "task.claim") {
626
+ const harnessId = envelope.payload.harnessId;
627
+ if (harnessId !== void 0 && envelope.payload.runtime !== void 0) return "rejected";
628
+ if (attempt.ownerDeviceId !== void 0 && (harnessId !== void 0 || attempt.claimedHarnessId !== void 0) && harnessId !== attempt.claimedHarnessId) return "rejected";
629
+ const device = await stores.devices.get(deviceId);
630
+ if (harnessId !== void 0 && (!device?.capabilities?.includes("custom-harness") || !device.harnesses?.some((harness) => harness.id === harnessId))) return "rejected";
631
+ const messageId = await taskOfferMessageId(stores.tenant, taskId, deviceId, attempt.agentRef, hashOfferBytes);
632
+ const offer = await stores.receipts.get(`task-offer:${messageId}`);
633
+ if (offer !== void 0) {
634
+ const sealed = JSON.parse(offer.body);
635
+ if (harnessId !== void 0 && (sealed.payload.runtime !== void 0 || sealed.payload.dispatchSelection !== void 0)) return "rejected";
636
+ if (sealed.payload.harnessId !== void 0 && sealed.payload.harnessId !== harnessId) return "rejected";
637
+ } else if (harnessId !== void 0) return "rejected";
808
638
  }
809
- }
810
- async function authenticateDevice(c, deps) {
811
- const device = await authenticateBearer(c.req.header("authorization"), deps.bearer);
812
- if (device === void 0) return void 0;
813
- return { device, stores: tenantStoresFor(device, deps.root) };
814
- }
815
-
816
- // src/handlers/blobs.ts
817
- var BLOB_RESERVATION_TTL_MS = 15 * 60 * 1e3;
818
- var SignedUrlQuerySchema = z.object({
819
- sig: z.string().min(1),
820
- exp: z.coerce.number().finite()
821
- });
822
- function createBlobHandler(deps) {
823
- return async (c) => {
824
- const authenticated = await authenticateDevice(c, deps);
825
- if (authenticated === void 0) return c.json({ error: "unauthorized" }, 401);
826
- const parsed = CreateBlobRequestSchema.safeParse(await readJsonBody(c));
827
- if (!parsed.success) return c.json({ error: "size, contentType, and contentHash are required" }, 400);
828
- if (parsed.data.size > deps.maxBlobSizeBytes) {
829
- return c.json({ error: `blob exceeds max size of ${deps.maxBlobSizeBytes} bytes` }, 413);
830
- }
831
- const reservationId = idempotencyKey(c);
832
- if (reservationId === void 0) {
833
- return c.json({ error: "Idempotency-Key header is required" }, 400);
834
- }
835
- try {
836
- const reservation = await authenticated.stores.quota.reserve({
837
- reservationId,
838
- kind: "object",
839
- expectedBytes: BigInt(parsed.data.size),
840
- contentHash: contentHash(parsed.data.contentHash),
841
- contentType: parsed.data.contentType,
842
- ttlMs: BLOB_RESERVATION_TTL_MS
843
- });
844
- try {
845
- const { blobId, uploadUrl } = await authenticated.stores.blobs.createUpload(reservation);
846
- const response = { blobId, uploadUrl };
847
- return c.json(response, 200);
848
- } catch (error) {
849
- await authenticated.stores.quota.abortReservation(reservationId).catch(() => void 0);
850
- const rendered = renderStorageError(c, error);
851
- if (rendered !== void 0) return rendered;
852
- throw error;
853
- }
854
- } catch (error) {
855
- const rendered = renderStorageError(c, error);
856
- if (rendered !== void 0) return rendered;
857
- throw error;
858
- }
859
- };
860
- }
861
- function finalizeBlobHandler(deps) {
862
- return async (c) => {
863
- const authenticated = await authenticateDevice(c, deps);
864
- if (authenticated === void 0) return c.json({ error: "unauthorized" }, 401);
865
- const reservationId = idempotencyKey(c);
866
- if (reservationId === void 0) {
867
- return c.json({ error: "Idempotency-Key header is required" }, 400);
868
- }
639
+ let unclaimedInterruption = false;
640
+ if (envelope.type === "task.fail" && envelope.payload.recovery !== void 0) {
641
+ if (envelope.payload.reason !== "daemon_interrupted" || envelope.payload.retryable !== false) return "rejected";
642
+ const messageId = await taskOfferMessageId(stores.tenant, taskId, deviceId, attempt.agentRef, hashOfferBytes);
643
+ if (envelope.payload.recovery.offerId !== messageId) return "rejected";
644
+ const offer = await stores.receipts.get(`task-offer:${messageId}`);
645
+ if (offer === void 0) return "rejected";
646
+ const sealed = JSON.parse(offer.body);
647
+ const harnessId = envelope.payload.harnessId;
648
+ if (sealed.payload.harnessId !== void 0 && sealed.payload.harnessId !== harnessId) return "rejected";
649
+ if (harnessId !== void 0 && (sealed.payload.runtime !== void 0 || sealed.payload.dispatchSelection !== void 0)) return "rejected";
650
+ unclaimedInterruption = attempt.ownerDeviceId === void 0 && attempt.claimedHarnessId === void 0;
651
+ }
652
+ if ((envelope.type === "task.complete" || envelope.type === "task.fail" || envelope.type === "task.cancelled") && !unclaimedInterruption && envelope.payload.harnessId !== attempt.claimedHarnessId) return "rejected";
653
+ if (attempt?.agentRef !== void 0 && agentRefEchoRequired(envelope) && !sameAgentRef(attempt.agentRef, inboundAgentRef(envelope))) {
654
+ return "rejected";
655
+ }
656
+ if (envelope.type === "task.progress" && envelope.payload.events.length > 0) {
869
657
  try {
870
- const reservation = await authenticated.stores.quota.readReservation(reservationId);
871
- if (reservation === void 0) {
872
- return c.json({ error: "storage_reservation_not_found" }, 404);
873
- }
874
- const blobId = c.req.param("id") ?? "";
875
- const observed = await authenticated.stores.blobs.observeUpload(
876
- blobId,
877
- reservation
658
+ validateActivityBatch(
659
+ {
660
+ taskId,
661
+ sourceEnvelopeId: envelope.id,
662
+ batchSeq: envelope.payload.seq,
663
+ events: envelope.payload.events,
664
+ dropped: 0
665
+ },
666
+ activityBounds
878
667
  );
879
- if (observed === void 0) {
880
- await authenticated.stores.quota.abortReservation(reservationId);
881
- return c.json({ error: "storage_integrity_mismatch" }, 422);
668
+ } catch (caught) {
669
+ if (isCloudError(caught, "activity_batch_too_large") || isCloudError(caught, "coordination_input_invalid")) {
670
+ return "rejected";
882
671
  }
883
- await authenticated.stores.quota.finalizeReservation({
884
- reservationId,
885
- ...observed
886
- });
887
- return c.body(null, 204);
888
- } catch (error) {
889
- const rendered = renderStorageError(c, error);
890
- if (rendered !== void 0) return rendered;
891
- throw error;
672
+ throw caught;
892
673
  }
893
- };
674
+ }
675
+ const approvalInput = approvalTimelineAppendInput(taskId, envelope);
676
+ if (approvalInput !== void 0) {
677
+ try {
678
+ validateApprovalTimelineAppend(approvalInput);
679
+ } catch (caught) {
680
+ if (isCloudError(caught, "coordination_input_invalid")) return "rejected";
681
+ throw caught;
682
+ }
683
+ }
684
+ const lifecycleOutcome = await applyLifecycle(stores, deviceId, taskId, envelope, activityBounds, attempt?.agentRef);
685
+ if (lifecycleOutcome === "rejected") return "rejected";
686
+ return completeInboundEnvelope(stores, deviceId, envelope.id, attempt?.agentRef);
894
687
  }
895
- function blobDownloadUrlHandler(deps) {
896
- return async (c) => {
897
- const authenticated = await authenticateDevice(c, deps);
898
- if (authenticated === void 0) return c.json({ error: "unauthorized" }, 401);
899
- const downloadUrl = await authenticated.stores.blobs.getDownloadUrl(c.req.param("id") ?? "");
900
- if (downloadUrl === void 0) return c.json({ error: "blob not found" }, 404);
901
- const response = { downloadUrl };
902
- return c.json(response, 200);
903
- };
688
+ async function completeInboundEnvelope(stores, deviceId, envelopeId, agentRef) {
689
+ const duplicate = agentRef === void 0 ? await stores.dedup.checkAndRecord(deviceId, envelopeId) : await stores.dedup.checkAndRecordAgent(deviceId, agentRef, envelopeId);
690
+ return duplicate ? "duplicate" : "accepted";
904
691
  }
905
- function blobUploadContentHandler(deps) {
906
- return async (c) => {
907
- const blobId = c.req.param("id") ?? "";
908
- const query = SignedUrlQuerySchema.safeParse({ sig: c.req.query("sig"), exp: c.req.query("exp") });
909
- if (!query.success || !await deps.contentProxy.verifySignedUrl(blobId, "put", query.data.sig, query.data.exp)) {
910
- return c.json({ error: "invalid or expired signature" }, 401);
911
- }
912
- const expectedBytes = await deps.contentProxy.expectedUploadBytes(blobId);
913
- if (expectedBytes === void 0) return c.json({ error: "blob not found" }, 404);
914
- const absoluteCeiling = BigInt(deps.maxUploadBytes);
915
- if (expectedBytes > absoluteCeiling) return c.json({ error: "upload exceeds size limit" }, 413);
916
- const contentLength = c.req.header("content-length");
917
- if (contentLength !== void 0 && /^\d+$/u.test(contentLength) && BigInt(contentLength) > expectedBytes) {
918
- return c.json({ error: "upload exceeds declared size" }, 413);
692
+ async function applyLifecycle(stores, deviceId, taskId, envelope, activityBounds, persistedAgentRef) {
693
+ switch (envelope.type) {
694
+ case "task.claim": {
695
+ const claimed = await stores.tasks.claim({
696
+ taskId,
697
+ deviceId,
698
+ ...envelope.payload.runtime === void 0 ? {} : { runtime: envelope.payload.runtime },
699
+ ...envelope.payload.harnessId === void 0 ? {} : { harnessId: envelope.payload.harnessId },
700
+ ...envelope.payload.capabilities === void 0 ? {} : { capabilities: envelope.payload.capabilities }
701
+ });
702
+ if (claimed === void 0 || claimed.ownerDeviceId !== deviceId || claimed.claimedRuntime !== envelope.payload.runtime || claimed.claimedHarnessId !== envelope.payload.harnessId) return "rejected";
703
+ return;
919
704
  }
920
- const body = await readBoundedBody(c.req.raw.body, Number(expectedBytes));
921
- if (body.tooLarge) return c.json({ error: "upload exceeds declared size" }, 413);
922
- const result = await deps.contentProxy.writeContent(blobId, body.data);
923
- if (!result.ok) return c.json({ error: result.reason }, 422);
924
- return c.body(null, 204);
925
- };
926
- }
927
- async function readBoundedBody(body, ceiling) {
928
- if (body === null) return { tooLarge: false, data: new Uint8Array() };
929
- const reader = body.getReader();
930
- const data = new Uint8Array(ceiling);
931
- let offset = 0;
932
- try {
933
- for (; ; ) {
934
- const next = await reader.read();
935
- if (next.done) break;
936
- const chunk = next.value;
937
- if (chunk.byteLength > ceiling - offset) {
938
- void reader.cancel().catch(() => void 0);
939
- return { tooLarge: true };
705
+ case "task.started":
706
+ await stores.tasks.recordStatus({
707
+ taskId,
708
+ status: "running",
709
+ ...persistedAgentRef === void 0 ? {} : { agentRef: persistedAgentRef }
710
+ });
711
+ return;
712
+ case "task.decline":
713
+ await recordTerminal(stores, taskId, envelope);
714
+ return;
715
+ case "task.progress":
716
+ if (envelope.payload.events.length > 0) {
717
+ await appendActivityEvents(
718
+ stores.activity,
719
+ {
720
+ taskId,
721
+ sourceEnvelopeId: envelope.id,
722
+ batchSeq: envelope.payload.seq,
723
+ events: envelope.payload.events,
724
+ dropped: 0
725
+ },
726
+ activityBounds
727
+ );
940
728
  }
941
- data.set(chunk, offset);
942
- offset += chunk.byteLength;
943
- }
944
- } finally {
945
- reader.releaseLock();
729
+ return;
730
+ case "task.await_approval":
731
+ await stores.approvals.append(approvalTimelineAppendInput(taskId, envelope));
732
+ return;
733
+ case "task.approval_resolved":
734
+ await stores.approvals.append(approvalTimelineAppendInput(taskId, envelope));
735
+ return;
736
+ case "task.complete":
737
+ await recordTerminal(stores, taskId, envelope);
738
+ return;
739
+ case "task.fail":
740
+ await recordTerminal(stores, taskId, envelope);
741
+ return;
742
+ case "task.cancelled":
743
+ await recordTerminal(stores, taskId, envelope);
744
+ return;
745
+ default:
746
+ return;
946
747
  }
947
- return { tooLarge: false, data: data.subarray(0, offset) };
948
- }
949
- function blobDownloadContentHandler(deps) {
950
- return async (c) => {
951
- const blobId = c.req.param("id") ?? "";
952
- const query = SignedUrlQuerySchema.safeParse({ sig: c.req.query("sig"), exp: c.req.query("exp") });
953
- if (!query.success || !await deps.contentProxy.verifySignedUrl(blobId, "get", query.data.sig, query.data.exp)) {
954
- return c.json({ error: "invalid or expired signature" }, 401);
955
- }
956
- const result = await deps.contentProxy.readContent(blobId);
957
- if (result === void 0) return c.json({ error: "blob not found" }, 404);
958
- if (!result.ok) return c.json({ error: result.code }, BLOB_READ_ERROR_HTTP_STATUS[result.code]);
959
- return c.body(new Uint8Array(result.content.data), 200, { "content-type": result.content.contentType });
960
- };
961
- }
962
- var BLOB_READ_ERROR_HTTP_STATUS = {
963
- blob_upstream_unavailable: 502,
964
- blob_upstream_stream_interrupted: 502
965
- };
966
- function idempotencyKey(c) {
967
- const value = c.req.header("Idempotency-Key");
968
- if (value === void 0 || value.length === 0 || value.length > 200) return void 0;
969
- return value;
970
748
  }
971
- function renderStorageError(c, error) {
972
- if (!isCoreError(error)) return void 0;
973
- if (STORAGE_ERROR_CODES.includes(error.code)) {
974
- const code = error.code;
975
- return c.json({ error: code }, STORAGE_ERROR_HTTP_STATUS[code]);
976
- }
977
- if (error.code === "storage_reservation_not_found" || error.code === "object_not_found") {
978
- return c.json({ error: error.code }, 404);
749
+ function approvalTimelineAppendInput(taskId, envelope) {
750
+ switch (envelope.type) {
751
+ case "task.await_approval":
752
+ return {
753
+ taskId,
754
+ sourceEnvelopeId: envelope.id,
755
+ event: {
756
+ type: "approval_requested",
757
+ summary: envelope.payload.summary,
758
+ ...envelope.payload.approvalId === void 0 ? {} : { approvalId: envelope.payload.approvalId }
759
+ }
760
+ };
761
+ case "task.approval_resolved":
762
+ return {
763
+ taskId,
764
+ sourceEnvelopeId: envelope.id,
765
+ event: {
766
+ type: "approval_resolved",
767
+ approvalId: envelope.payload.approvalId,
768
+ decision: envelope.payload.decision,
769
+ resolvedBy: envelope.payload.resolvedBy,
770
+ at: envelope.payload.at
771
+ }
772
+ };
773
+ default:
774
+ return void 0;
979
775
  }
980
- if (error.code === "storage_entitlement_missing" || error.code === "object_state_invalid") {
981
- return c.json({ error: error.code }, 409);
776
+ }
777
+ async function recordTerminal(stores, taskId, envelope) {
778
+ const receipt = await stores.receipts.record({
779
+ key: terminalReceiptKey(taskId),
780
+ body: encodeEnvelope(envelope)
781
+ });
782
+ const winner = terminalReceiptBinding(taskId, receipt.receipt.body);
783
+ if (winner === void 0) {
784
+ throw new Error(`terminal receipt for ${taskId} is not a canonical terminal binding`);
982
785
  }
983
- if (error.code === "content_hash_invalid") {
984
- return c.json({ error: error.code }, 400);
786
+ const attempt = await stores.tasks.get(taskId);
787
+ if (attempt?.cancellation !== void 0) {
788
+ await stores.tasks.recordStatus({
789
+ taskId,
790
+ status: "cancelled",
791
+ ...attempt.agentRef === void 0 ? {} : { agentRef: attempt.agentRef }
792
+ });
793
+ return;
985
794
  }
986
- return void 0;
795
+ const recordedAttempt = await stores.tasks.recordStatus({
796
+ taskId,
797
+ status: winner.status,
798
+ ...attempt?.agentRef === void 0 ? {} : { agentRef: attempt.agentRef },
799
+ ...winner.cause === void 0 ? {} : { terminalCause: winner.cause }
800
+ });
801
+ if (recordedAttempt?.cancellation !== void 0 || recordedAttempt?.status !== winner.status) {
802
+ if (recordedAttempt?.cancellation !== void 0) {
803
+ await stores.tasks.recordStatus({
804
+ taskId,
805
+ status: "cancelled",
806
+ ...recordedAttempt.agentRef === void 0 ? {} : { agentRef: recordedAttempt.agentRef }
807
+ });
808
+ }
809
+ return;
810
+ }
811
+ await projectTerminalToReview(stores.board, taskId);
987
812
  }
988
-
989
- // src/handlers/capabilities.ts
990
- function capabilitiesHandler(deps) {
991
- return async (c) => c.json(deps.declaration, 200);
813
+ function terminalStatus(envelope) {
814
+ switch (envelope.type) {
815
+ case "task.complete":
816
+ return "complete";
817
+ case "task.fail":
818
+ case "task.decline":
819
+ return "failed";
820
+ case "task.cancelled":
821
+ return "cancelled";
822
+ default:
823
+ return void 0;
824
+ }
992
825
  }
993
- var AUTH_JSON_BODY_MAX_BYTES = 16 * 1024;
994
- function pairHandler(deps) {
995
- return async (c) => {
996
- const body = await readBoundedJsonBody(c, AUTH_JSON_BODY_MAX_BYTES);
997
- if (body.tooLarge) return c.json({ error: "request body too large" }, 413);
998
- const parsed = PairRequestSchema.safeParse(body.body);
999
- if (!parsed.success) {
1000
- return c.json(
1001
- {
1002
- error: "pairingCode, deviceName, and devicePublicKey are required strings; optional machineId must be 64 lowercase hex characters"
1003
- },
1004
- 400
1005
- );
1006
- }
1007
- const device = await deps.auth.redeemAndRegister(parsed.data);
1008
- if (device === void 0) return c.json({ error: "invalid pairing code" }, 401);
1009
- const { accessToken, expiresAt } = await deps.auth.mintAccessToken(device);
1010
- const response = PairResponseSchema.parse({
1011
- deviceId: device.deviceId,
1012
- accessToken,
1013
- refreshHint: expiresAt,
1014
- tenantId: device.tenantId
1015
- });
1016
- return c.json(response, 200);
1017
- };
826
+ function terminalReceiptBinding(taskId, body) {
827
+ let envelope;
828
+ try {
829
+ envelope = decodeEnvelope(body);
830
+ } catch {
831
+ return void 0;
832
+ }
833
+ const status = terminalStatus(envelope);
834
+ if (status === void 0 || envelope.task_id !== taskId || encodeEnvelope(envelope) !== body) return void 0;
835
+ const cause = envelope.type === "task.fail" || envelope.type === "task.cancelled" || envelope.type === "task.decline" ? envelope.payload.reason : void 0;
836
+ return { status, ...cause === void 0 ? {} : { cause } };
1018
837
  }
1019
- function challengeHandler(deps) {
1020
- return async (c) => {
1021
- const body = await readBoundedJsonBody(c, AUTH_JSON_BODY_MAX_BYTES);
1022
- if (body.tooLarge) return c.json({ error: "request body too large" }, 413);
1023
- const parsed = ChallengeRequestSchema.safeParse(body.body);
1024
- if (!parsed.success) return c.json({ error: "deviceId is required" }, 400);
1025
- const device = await deps.auth.resolveDevice(parsed.data.deviceId);
1026
- if (device === void 0) return c.json({ error: "unknown or revoked device" }, 401);
1027
- const response = { nonce: await deps.auth.issueNonce(device) };
1028
- return c.json(response, 200);
1029
- };
838
+
839
+ // src/task-agent-message.ts
840
+ var FrozenMessageBinding = z.object({
841
+ agentRef: AgentRefSchema,
842
+ sessionRef: z.string().min(1).optional(),
843
+ requirement: AgentMessageEgressRequirementSchema,
844
+ context: AgentMessageServerContextSchema
845
+ }).strict();
846
+ function sameRef(a, b) {
847
+ return a.agentId === b.agentId && a.profileRevision === b.profileRevision;
1030
848
  }
1031
- function tokenHandler(deps) {
1032
- return async (c) => {
1033
- const body = await readBoundedJsonBody(c, AUTH_JSON_BODY_MAX_BYTES);
1034
- if (body.tooLarge) return c.json({ error: "request body too large" }, 413);
1035
- const parsed = TokenRequestSchema.safeParse(body.body);
1036
- if (!parsed.success) return c.json({ error: "deviceId, nonce, and signature are required" }, 400);
1037
- const { deviceId, nonce, signature } = parsed.data;
1038
- const device = await deps.auth.resolveDevice(deviceId);
1039
- if (device === void 0) return c.json({ error: "unknown or revoked device" }, 401);
1040
- if (!await deps.auth.verifySignature(device, nonce, signature)) {
1041
- return c.json({ error: "invalid signature" }, 401);
849
+ async function readTaskAgentMessage(stores, deviceId, taskId, expectedRef) {
850
+ const agentRef = AgentRefSchema.parse(expectedRef);
851
+ const attempt = await stores.tasks.get(taskId);
852
+ if (!attempt || attempt.tenantId !== stores.tenant || attempt.taskId !== taskId || attempt.deviceId !== deviceId || !attempt.agentRef || !sameRef(attempt.agentRef, agentRef)) return void 0;
853
+ const admission = await stores.tasks.readTaskAgentMessage({ taskId, deviceId });
854
+ if (!admission) return void 0;
855
+ const invalid = () => new Error("Invalid persisted task Agent message.");
856
+ const binding = await stores.receipts.get(`agent-message-offer:${deviceId}:${taskId}`);
857
+ if (!binding) throw invalid();
858
+ let body, frozenBody;
859
+ try {
860
+ body = JSON.parse(admission.payloadBody);
861
+ frozenBody = JSON.parse(binding.body);
862
+ } catch {
863
+ throw invalid();
864
+ }
865
+ const parsed = AgentMessagePublishPayloadSchema.safeParse(body);
866
+ const frozen = FrozenMessageBinding.safeParse(frozenBody);
867
+ if (!parsed.success || !frozen.success) throw invalid();
868
+ const payload = parsed.data;
869
+ if (admission.messageId !== payload.messageId || JSON.stringify(payload) !== admission.payloadBody || !sameRef(payload.agentRef, agentRef) || !sameRef(frozen.data.agentRef, agentRef) || payload.contract !== frozen.data.requirement.contract || payload.contentType !== frozen.data.requirement.contentType || frozen.data.sessionRef !== void 0 && payload.sessionRef !== frozen.data.sessionRef || payload.byteCount > frozen.data.requirement.maxBytes) throw invalid();
870
+ const disposition = admission.terminalBody === void 0 ? void 0 : parseAgentMessageDisposition(payload, admission.terminalBody);
871
+ return { payload, context: frozen.data.context, ...disposition === void 0 ? {} : { disposition } };
872
+ }
873
+ var RecurringExecutionInputSchema = z.object({
874
+ taskId: z.string().min(1),
875
+ deviceId: z.string().min(1),
876
+ payload: TaskOfferForAgentWithEgressFreshPayloadSchema.required({ runtime: true, messageEgress: true, terminalProjection: true }),
877
+ agentMessageContext: AgentMessageServerContextSchema
878
+ }).strict();
879
+
880
+ // src/approval-control.ts
881
+ var StaleApprovalError = class extends Error {
882
+ constructor(taskId, requestedApprovalId, currentApprovalId) {
883
+ super(
884
+ `cannot resolve approval ${requestedApprovalId} for task ${taskId}: the currently pending approval is ${currentApprovalId ?? "(none recorded)"}`
885
+ );
886
+ this.taskId = taskId;
887
+ this.requestedApprovalId = requestedApprovalId;
888
+ this.currentApprovalId = currentApprovalId;
889
+ this.name = "StaleApprovalError";
890
+ }
891
+ taskId;
892
+ requestedApprovalId;
893
+ currentApprovalId;
894
+ };
895
+ function pendingApproval(tail) {
896
+ if (tail === void 0) return void 0;
897
+ let pending;
898
+ for (const entry of tail.entries) {
899
+ const event = entry.event;
900
+ if (event.type === "approval_requested") {
901
+ pending = {
902
+ sourceEnvelopeId: entry.sourceEnvelopeId,
903
+ revision: entry.revision,
904
+ ...event.approvalId === void 0 ? {} : { approvalId: event.approvalId }
905
+ };
906
+ continue;
1042
907
  }
1043
- if (!await deps.auth.consumeNonceIfValid(device, nonce)) {
1044
- return c.json({ error: "invalid, expired, or already-used nonce" }, 401);
908
+ if (pending === void 0) continue;
909
+ if (pending.approvalId === void 0 || pending.approvalId === event.approvalId) {
910
+ pending = void 0;
1045
911
  }
1046
- const { accessToken, expiresAt } = await deps.auth.mintAccessToken(device);
1047
- const response = { accessToken, expiresAt };
1048
- return c.json(response, 200);
1049
- };
912
+ }
913
+ return pending;
1050
914
  }
1051
- var CLOUD_PROTOCOL_CAPABILITIES = [
1052
- "custom-harness",
1053
- MAILBOX_READ_AHEAD_CAPABILITY,
1054
- "result-document",
1055
- // The kernel's inbound handles `task.approval_resolved` on both the plain
1056
- // and the approval-timeline paths (`packages/cloud/src/inbound.ts:576,610`).
1057
- // Without this flag `TaskRunner.sendApprovalResolved` returns early and a
1058
- // locally-resolved approval is never reported on the wire at all.
1059
- "approval_resolved",
1060
- "agent-home-contract",
1061
- "agent-home-projection",
1062
- "agent-egress-policy",
1063
- "agent-egress-reliable-ack",
1064
- "agent-egress-fresh-session",
1065
- // `handleAgentMessagePublish` accepts `agent.message_egress` gated on exactly
1066
- // this capability (`packages/cloud/src/inbound.ts:124`) — the same inbound
1067
- // family as the three egress flags above, omitted by the same drift.
1068
- "agent-message-egress",
1069
- "agent-content-workspace-read",
1070
- "agent-content-transcript-read",
1071
- "agent-content-artifact-read"
1072
- ];
1073
- function sleep(ms) {
1074
- return new Promise((resolve) => setTimeout(resolve, ms));
915
+
916
+ // src/steer-control.ts
917
+ function steerRejectionMessage(taskId, code, status, runtime) {
918
+ switch (code) {
919
+ case "task_terminal":
920
+ return `cannot steer task ${taskId}: task is already terminal (status ${status})`;
921
+ case "task_not_running":
922
+ return `cannot steer task ${taskId}: not running (status ${status})`;
923
+ case "steer_unsupported_runtime":
924
+ return `cannot steer task ${taskId}: claimed runtime ${runtime ?? "(unknown)"} does not support steering`;
925
+ }
1075
926
  }
1076
- function eventsHandler(deps) {
1077
- return async (c) => {
1078
- const authenticated = await authenticateDevice(c, deps);
1079
- if (authenticated === void 0) return c.json({ error: "unauthorized" }, 401);
1080
- const { device, stores } = authenticated;
1081
- const cursorRaw = c.req.query("cursor");
1082
- let cursor = 0;
1083
- if (cursorRaw !== void 0) {
1084
- const parsedCursor = Number(cursorRaw);
1085
- if (!Number.isSafeInteger(parsedCursor) || parsedCursor < 0) {
1086
- return c.json({ error: "invalid cursor" }, 400);
1087
- }
1088
- cursor = parsedCursor;
1089
- }
1090
- const afterRaw = c.req.query("afterSeq");
1091
- const afterSeq = afterRaw === void 0 ? cursor : Number(afterRaw);
1092
- if (!Number.isSafeInteger(afterSeq) || afterSeq < cursor || afterSeq - cursor > MAILBOX_READ_AHEAD_MAX_SEQS) {
1093
- return c.json({ error: "invalid read-ahead offset" }, 400);
1094
- }
1095
- const acked = await stores.mailbox.readCursor(device.deviceId);
1096
- if (afterRaw !== void 0 && afterSeq > acked.deliveredSeq) return c.json({ error: "read offset exceeds delivered watermark" }, 409);
1097
- if (cursor > acked.ackedSeq) {
1098
- try {
1099
- await stores.mailbox.advanceCursor({ deviceId: device.deviceId, ackedSeq: cursor });
1100
- } catch (caught) {
1101
- if (isCoreConflictError(caught, "mailbox_cursor_ahead_of_delivery")) {
1102
- return c.json({ error: "cursor exceeds delivered watermark" }, 409);
1103
- }
1104
- throw caught;
1105
- }
1106
- }
1107
- const attempts = Math.max(1, Math.ceil(deps.longPollHoldMs / deps.longPollIntervalMs));
1108
- for (let attempt = 0; attempt < attempts; attempt += 1) {
1109
- let scanCursor = afterSeq;
1110
- while (true) {
1111
- const page = await stores.mailbox.readAfter({
1112
- deviceId: device.deviceId,
1113
- afterSeq: scanCursor,
1114
- limit: deps.pageLimit
1115
- });
1116
- if (cursor < page.recoverableFrom - 1) {
1117
- return c.json({ error: "cursor_too_old", recoverableFrom: page.recoverableFrom }, 409);
1118
- }
1119
- if (page.messages.length === 0) break;
1120
- const decoded = page.messages.map((message) => decodeEnvelope(message.body));
1121
- const offeredTaskIds = decoded.flatMap(
1122
- (event) => (event.type === "task.offer" || event.type === "task.offer_with_toolsets" || event.type === "task.offer_for_agent" || event.type === "task.offer_for_agent_with_egress" || event.type === "task.offer_for_agent_with_egress_fresh") && event.task_id !== void 0 ? [event.task_id] : []
1123
- );
1124
- const attemptsByTaskId = Object.fromEntries(
1125
- (await stores.tasks.getMany(offeredTaskIds)).map((attempt2) => [attempt2.taskId, attempt2])
1126
- );
1127
- const events = decoded.filter((event) => {
1128
- if (event.type !== "task.offer" && event.type !== "task.offer_with_toolsets" && event.type !== "task.offer_for_agent" && event.type !== "task.offer_for_agent_with_egress" && event.type !== "task.offer_for_agent_with_egress_fresh") {
1129
- return true;
1130
- }
1131
- return event.task_id === void 0 || attemptsByTaskId[event.task_id]?.cancellation === void 0;
1132
- });
1133
- if (events.length === 0 && page.hasMore) {
1134
- scanCursor = page.nextSeq;
1135
- continue;
1136
- }
1137
- if (events.length === 0) break;
1138
- const response2 = {
1139
- events,
1140
- cursor: page.nextSeq,
1141
- capabilities: CLOUD_PROTOCOL_CAPABILITIES
1142
- };
1143
- await stores.mailbox.recordDelivery({
1144
- deviceId: device.deviceId,
1145
- deliveredSeq: response2.cursor
1146
- });
1147
- return c.json(response2, 200);
1148
- }
1149
- if (attempt < attempts - 1) await sleep(deps.longPollIntervalMs);
1150
- }
1151
- const response = {
1152
- events: [],
1153
- cursor: afterSeq,
1154
- capabilities: CLOUD_PROTOCOL_CAPABILITIES
1155
- };
1156
- return c.json(response, 200);
1157
- };
1158
- }
1159
- var DEFAULT_APPROVAL_TIMELINE_CAPACITY = 50;
1160
- var DEFAULT_APPROVAL_TIMELINE_TTL_MS = 10 * 60 * 1e3;
1161
- var APPROVAL_SUMMARY_MAX_BYTES = 16 * 1024;
1162
- var NonBlankIdSchema = z.string().max(200).regex(/\S/, "value must not be blank");
1163
- var TaskIdSchema = z.string().min(1).max(200);
1164
- var ApprovalRequestedEventSchema = z.object({
1165
- type: z.literal("approval_requested"),
1166
- summary: z.string(),
1167
- approvalId: NonBlankIdSchema.optional()
1168
- }).strict();
1169
- var LocalApprovalResolutionEventSchema = z.object({
1170
- type: z.literal("approval_resolved"),
1171
- // Absent only when a daemon resolved the single outstanding request without
1172
- // native approval identity. No id is synthesized.
1173
- approvalId: NonBlankIdSchema.optional(),
1174
- decision: z.enum(["approve", "reject"]),
1175
- // A daemon's `task.approval_resolved` is pure wire observation; it cannot
1176
- // declare a host command payload.
1177
- resolvedBy: z.literal("local"),
1178
- at: z.iso.datetime({ offset: true })
1179
- }).strict();
1180
- var HostApprovalResolutionEventSchema = z.object({
1181
- type: z.literal("approval_resolved"),
1182
- approvalId: NonBlankIdSchema.optional(),
1183
- decision: z.literal("approve"),
1184
- resolvedBy: z.literal("host"),
1185
- at: z.iso.datetime({ offset: true })
1186
- }).strict();
1187
- var HostRejectResolutionEventSchema = z.object({
1188
- type: z.literal("approval_resolved"),
1189
- approvalId: NonBlankIdSchema.optional(),
1190
- decision: z.literal("reject"),
1191
- resolvedBy: z.literal("host"),
1192
- // Required even when the host deliberately omitted a wire reason. `null`
1193
- // records that complete payload, so a historical host reject with no field
1194
- // cannot be mistaken for a recoverable executable command.
1195
- reason: z.string().nullable(),
1196
- at: z.iso.datetime({ offset: true })
1197
- }).strict();
1198
- var ApprovalTimelineEventSchema = z.union([
1199
- ApprovalRequestedEventSchema,
1200
- LocalApprovalResolutionEventSchema,
1201
- HostApprovalResolutionEventSchema,
1202
- HostRejectResolutionEventSchema
1203
- ]);
1204
- var ApprovalObservationSchema = z.object({
1205
- taskId: TaskIdSchema,
1206
- sourceEnvelopeId: NonBlankIdSchema,
1207
- revision: z.number().int().positive(),
1208
- receivedAt: z.iso.datetime(),
1209
- event: ApprovalTimelineEventSchema
1210
- });
1211
- function validateApprovalTimelineAppend(input) {
1212
- const capacity = input.capacity ?? DEFAULT_APPROVAL_TIMELINE_CAPACITY;
1213
- const ttlMs = input.ttlMs ?? DEFAULT_APPROVAL_TIMELINE_TTL_MS;
1214
- if (!Number.isSafeInteger(capacity) || capacity <= 0) {
1215
- throw new ByokCloudError(
1216
- "coordination_input_invalid",
1217
- `Approval timeline capacity must be a positive integer, received ${String(capacity)}.`
1218
- );
1219
- }
1220
- if (!Number.isFinite(ttlMs) || ttlMs <= 0) {
1221
- throw new ByokCloudError(
1222
- "coordination_input_invalid",
1223
- `Approval timeline ttl must be a positive number of milliseconds, received ${String(ttlMs)}.`
1224
- );
1225
- }
1226
- const parsed = z.object({
1227
- taskId: TaskIdSchema,
1228
- sourceEnvelopeId: NonBlankIdSchema,
1229
- event: ApprovalTimelineEventSchema
1230
- }).safeParse(input);
1231
- if (!parsed.success) {
1232
- throw new ByokCloudError(
1233
- "coordination_input_invalid",
1234
- "Approval observations require stable source identity and a valid native lifecycle event."
1235
- );
1236
- }
1237
- if (parsed.data.event.type === "approval_requested" && new TextEncoder().encode(parsed.data.event.summary).byteLength > APPROVAL_SUMMARY_MAX_BYTES) {
1238
- throw new ByokCloudError(
1239
- "coordination_input_invalid",
1240
- `Approval summary exceeds ${APPROVAL_SUMMARY_MAX_BYTES} UTF-8 bytes.`
1241
- );
927
+ var SteerRejectedError = class extends Error {
928
+ constructor(taskId, code, status, runtime) {
929
+ super(steerRejectionMessage(taskId, code, status, runtime));
930
+ this.taskId = taskId;
931
+ this.code = code;
932
+ this.status = status;
933
+ this.runtime = runtime;
934
+ this.name = "SteerRejectedError";
1242
935
  }
1243
- return { capacity, ttlMs, event: parsed.data.event };
936
+ taskId;
937
+ code;
938
+ status;
939
+ runtime;
940
+ };
941
+
942
+ // src/crypto/web-crypto.ts
943
+ var PAIRING_CODE_ALPHABET = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789";
944
+ var HEX_DIGITS = "0123456789abcdef";
945
+ function base64UrlEncode(bytes) {
946
+ let binary = "";
947
+ for (const byte of bytes) binary += String.fromCharCode(byte);
948
+ return btoa(binary).replaceAll("+", "-").replaceAll("/", "_").replaceAll("=", "");
1244
949
  }
1245
- function validateApprovalTimelineResolve(input) {
1246
- const validated = validateApprovalTimelineAppend(input);
1247
- const expected = z.object({
1248
- expectedSourceEnvelopeId: NonBlankIdSchema,
1249
- expectedRevision: z.number().int().positive()
1250
- }).safeParse(input);
1251
- if (!expected.success || validated.event.type !== "approval_resolved" || validated.event.resolvedBy !== "host") {
1252
- throw new ByokCloudError(
1253
- "coordination_input_invalid",
1254
- "Host approval resolution requires an exact pending source/revision and a host resolution event."
1255
- );
950
+ function base64UrlDecode(value) {
951
+ const padded = value.replaceAll("-", "+").replaceAll("_", "/");
952
+ try {
953
+ const binary = atob(padded);
954
+ const bytes = new Uint8Array(binary.length);
955
+ for (let index = 0; index < binary.length; index += 1) bytes[index] = binary.charCodeAt(index);
956
+ return bytes;
957
+ } catch {
958
+ return void 0;
1256
959
  }
1257
- return validated;
1258
- }
1259
- function approvalTimelineKey(tenant, taskId) {
1260
- return tenantKey(tenant, taskId);
1261
960
  }
1262
- function parseApprovalObservations(value) {
1263
- return z.array(ApprovalObservationSchema).parse(value);
1264
- }
1265
- function approvalTimelineCursor(entries) {
1266
- return entries.at(-1)?.revision;
961
+ function toHex(bytes) {
962
+ let out = "";
963
+ for (const byte of bytes) {
964
+ out += HEX_DIGITS[byte >> 4 & 15];
965
+ out += HEX_DIGITS[byte & 15];
966
+ }
967
+ return out;
1267
968
  }
1268
- async function projectTerminalToReview(board, taskId) {
1269
- const item = await board.get(taskId);
1270
- if (item === void 0 || item.status !== "in_progress") return;
1271
- try {
1272
- await board.updateStatus({
1273
- itemId: taskId,
1274
- expectedStatus: "in_progress",
1275
- status: "in_review"
1276
- });
1277
- } catch (caught) {
1278
- if (isCoreConflictError(caught)) return;
1279
- throw caught;
969
+ function subtle() {
970
+ const webCrypto = globalThis.crypto;
971
+ if (webCrypto?.subtle === void 0) {
972
+ throw new Error("@byok-sdk/cloud requires a WebCrypto implementation on globalThis.crypto");
1280
973
  }
974
+ return webCrypto.subtle;
1281
975
  }
1282
- function agentReliabilityKey(domain, deviceId, agentRef, id) {
1283
- const exact = AgentRefSchema.parse(agentRef);
1284
- return `${domain}:${JSON.stringify([deviceId, exact.agentId, exact.profileRevision, id])}`;
976
+ function createWebCrypto() {
977
+ return {
978
+ randomUuid() {
979
+ return globalThis.crypto.randomUUID();
980
+ },
981
+ randomToken(byteLength) {
982
+ return base64UrlEncode(globalThis.crypto.getRandomValues(new Uint8Array(byteLength)));
983
+ },
984
+ randomPairingCode(length) {
985
+ const bytes = globalThis.crypto.getRandomValues(new Uint8Array(length));
986
+ let out = "";
987
+ for (const byte of bytes) {
988
+ out += PAIRING_CODE_ALPHABET[byte % PAIRING_CODE_ALPHABET.length];
989
+ }
990
+ return out;
991
+ },
992
+ async verifyEd25519(publicKeyBase64Url, message, signature) {
993
+ const signatureBytes = base64UrlDecode(signature);
994
+ if (signatureBytes === void 0) return false;
995
+ try {
996
+ const key = await subtle().importKey(
997
+ "jwk",
998
+ { kty: "OKP", crv: "Ed25519", x: publicKeyBase64Url },
999
+ { name: "Ed25519" },
1000
+ false,
1001
+ ["verify"]
1002
+ );
1003
+ const messageBytes = typeof message === "string" ? new TextEncoder().encode(message) : message;
1004
+ return await subtle().verify({ name: "Ed25519" }, key, signatureBytes, messageBytes);
1005
+ } catch {
1006
+ return false;
1007
+ }
1008
+ },
1009
+ async hmacSha256(secret, message) {
1010
+ const key = await subtle().importKey("raw", secret, { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
1011
+ const signature = await subtle().sign("HMAC", key, new TextEncoder().encode(message));
1012
+ return base64UrlEncode(new Uint8Array(signature));
1013
+ },
1014
+ async sha256(data) {
1015
+ const digest = await subtle().digest("SHA-256", data);
1016
+ return `sha256:${toHex(new Uint8Array(digest))}`;
1017
+ },
1018
+ timingSafeEqual(left, right) {
1019
+ const leftBytes = base64UrlDecode(left);
1020
+ const rightBytes = base64UrlDecode(right);
1021
+ if (leftBytes === void 0 || rightBytes === void 0) return false;
1022
+ if (leftBytes.length !== rightBytes.length) return false;
1023
+ let diff = 0;
1024
+ for (let index = 0; index < leftBytes.length; index += 1) {
1025
+ diff |= (leftBytes[index] ?? 0) ^ (rightBytes[index] ?? 0);
1026
+ }
1027
+ return diff === 0;
1028
+ }
1029
+ };
1285
1030
  }
1286
1031
 
1287
- // src/inbound.ts
1288
- async function handleAgentMessagePublish(stores, deviceId, taskId, payload, consume, alreadyDeduplicated) {
1289
- const device = await stores.devices.get(deviceId);
1290
- const attempt = await stores.tasks.get(taskId);
1291
- const binding = await stores.receipts.get(`agent-message-offer:${deviceId}:${taskId}`);
1292
- if (device === void 0 || device.revoked || !hasCapabilities(device.capabilities, [AGENT_MESSAGE_EGRESS_CAPABILITY]) || attempt === void 0 || attempt.deviceId !== deviceId || binding === void 0 || attempt.agentRef === void 0 || !sameAgentRef(attempt.agentRef, payload.agentRef)) return { outcome: "rejected" };
1293
- let frozen;
1294
- try {
1295
- frozen = JSON.parse(binding.body);
1296
- } catch {
1297
- return { outcome: "rejected" };
1032
+ // src/auth/tokens.ts
1033
+ var ACCESS_TOKEN_TTL_SECONDS = 60 * 60;
1034
+ function encodeSegment(value) {
1035
+ return base64UrlEncode(new TextEncoder().encode(JSON.stringify(value)));
1036
+ }
1037
+ function createHmacTokenSigner(secret, clock) {
1038
+ async function signingKey(usage) {
1039
+ return globalThis.crypto.subtle.importKey("raw", secret, { name: "HMAC", hash: "SHA-256" }, false, [usage]);
1298
1040
  }
1299
- if (!sameAgentRef(frozen.agentRef, payload.agentRef) || frozen.context === void 0 || frozen.requirement?.contract !== payload.contract || frozen.sessionRef !== void 0 && frozen.sessionRef !== payload.sessionRef || frozen.requirement.contentType !== payload.contentType || typeof frozen.requirement.maxBytes !== "number" || payload.byteCount > frozen.requirement.maxBytes) return { outcome: "rejected" };
1300
- const priorDisposition = await readAgentMessageDisposition(stores, deviceId, taskId, payload);
1301
- if (priorDisposition !== void 0) return { outcome: "duplicate", disposition: priorDisposition };
1302
- const payloadBody = JSON.stringify(payload);
1303
- const reservation = await stores.tasks.reserveAgentMessage({
1304
- taskId,
1305
- deviceId,
1306
- messageId: payload.messageId,
1307
- payloadBody
1308
- });
1309
- if (reservation !== "reserved" && reservation !== "pending") return { outcome: "rejected" };
1310
- let decision;
1311
- if (consume === void 0) {
1312
- if (reservation === "pending") {
1313
- throw new Error("Agent message consumer is unavailable while reconciling an exact pending admission.");
1041
+ async function signature(signingInput) {
1042
+ const key = await signingKey("sign");
1043
+ const bytes = await globalThis.crypto.subtle.sign("HMAC", key, new TextEncoder().encode(signingInput));
1044
+ return base64UrlEncode(new Uint8Array(bytes));
1045
+ }
1046
+ return {
1047
+ async sign(claims, expiresInSeconds) {
1048
+ const issuedAt = Math.floor(clock.now().getTime() / 1e3);
1049
+ const header = encodeSegment({ alg: "HS256", typ: "JWT" });
1050
+ const payload = encodeSegment({
1051
+ deviceId: claims.deviceId,
1052
+ tenantId: claims.tenantId,
1053
+ productId: claims.productId,
1054
+ iat: issuedAt,
1055
+ exp: issuedAt + expiresInSeconds
1056
+ });
1057
+ const signingInput = `${header}.${payload}`;
1058
+ return `${signingInput}.${await signature(signingInput)}`;
1059
+ },
1060
+ async verify(token) {
1061
+ const parts = token.split(".");
1062
+ if (parts.length !== 3) return void 0;
1063
+ const [header, payload, provided] = parts;
1064
+ const key = await signingKey("verify");
1065
+ const providedBytes = base64UrlDecode(provided);
1066
+ if (providedBytes === void 0) return void 0;
1067
+ const ok = await globalThis.crypto.subtle.verify(
1068
+ "HMAC",
1069
+ key,
1070
+ providedBytes,
1071
+ new TextEncoder().encode(`${header}.${payload}`)
1072
+ );
1073
+ if (!ok) return void 0;
1074
+ const headerBytes = base64UrlDecode(header);
1075
+ const payloadBytes = base64UrlDecode(payload);
1076
+ if (headerBytes === void 0 || payloadBytes === void 0) return void 0;
1077
+ let decodedHeader;
1078
+ let decodedPayload;
1079
+ try {
1080
+ decodedHeader = JSON.parse(new TextDecoder().decode(headerBytes));
1081
+ decodedPayload = JSON.parse(new TextDecoder().decode(payloadBytes));
1082
+ } catch {
1083
+ return void 0;
1084
+ }
1085
+ const alg = decodedHeader?.alg;
1086
+ if (alg !== "HS256") return void 0;
1087
+ const { deviceId, tenantId: tenantId4, productId, exp } = decodedPayload;
1088
+ if (typeof deviceId !== "string" || typeof tenantId4 !== "string" || typeof productId !== "string") {
1089
+ return void 0;
1090
+ }
1091
+ if (typeof exp !== "number" || clock.now().getTime() / 1e3 >= exp) return void 0;
1092
+ return { deviceId, tenantId: tenantId4, productId };
1314
1093
  }
1315
- decision = { outcome: "held", reasonCode: "consumer_unavailable" };
1316
- } else {
1317
- decision = await consume({ tenant: stores.tenant, deviceId, taskId, context: frozen.context, payload });
1094
+ };
1095
+ }
1096
+ function verifyNonceSignature(crypto2, devicePublicKey, nonce, signature) {
1097
+ return crypto2.verifyEd25519(devicePublicKey, NONCE_SIGNING_DOMAIN + nonce, signature);
1098
+ }
1099
+
1100
+ // src/auth/plane.ts
1101
+ var PAIRING_CODE_TTL_MS = 10 * 60 * 1e3;
1102
+ var PAIRING_CODE_LENGTH = 8;
1103
+ var DEVICE_IDENTITY_PROOF_KEY_ID = "identity";
1104
+ var DEVICE_IDENTITY_PROOF_KEY_EPOCH = 0;
1105
+ function createAuthPlane(deps) {
1106
+ const { stores, crypto: crypto2, clock, tokenSigner } = deps;
1107
+ const ttlSeconds = deps.accessTokenTtlSeconds ?? ACCESS_TOKEN_TTL_SECONDS;
1108
+ return {
1109
+ async createPairingCode(tenant, input) {
1110
+ const expiresAt = new Date(clock.now().getTime() + (input.ttlMs ?? PAIRING_CODE_TTL_MS)).toISOString();
1111
+ return stores.pairingCodes.issue(tenant, {
1112
+ code: crypto2.randomPairingCode(PAIRING_CODE_LENGTH),
1113
+ productId: input.productId,
1114
+ expiresAt
1115
+ });
1116
+ },
1117
+ async redeemAndRegister(input) {
1118
+ return stores.pairing.redeemAndRegister({
1119
+ pairingCode: input.pairingCode,
1120
+ deviceId: `dev_${crypto2.randomUuid()}`,
1121
+ deviceName: input.deviceName,
1122
+ devicePublicKey: input.devicePublicKey,
1123
+ proofKeyId: DEVICE_IDENTITY_PROOF_KEY_ID,
1124
+ proofKeyEpoch: DEVICE_IDENTITY_PROOF_KEY_EPOCH,
1125
+ ...input.machineId === void 0 ? {} : { machineId: input.machineId }
1126
+ });
1127
+ },
1128
+ async resolveDevice(deviceId) {
1129
+ const device = await stores.devices.resolveByDeviceId(deviceId);
1130
+ if (device === void 0 || device.revoked) return void 0;
1131
+ return device;
1132
+ },
1133
+ issueNonce(device) {
1134
+ return stores.nonces.issue(device.tenantId, device.deviceId);
1135
+ },
1136
+ consumeNonceIfValid(device, nonce) {
1137
+ return stores.nonces.consumeIfValid(device.tenantId, device.deviceId, nonce);
1138
+ },
1139
+ verifySignature(device, nonce, signature) {
1140
+ return verifyNonceSignature(crypto2, device.devicePublicKey, nonce, signature);
1141
+ },
1142
+ async mintAccessToken(device) {
1143
+ const accessToken = await tokenSigner.sign(
1144
+ { deviceId: device.deviceId, tenantId: device.tenantId, productId: device.productId },
1145
+ ttlSeconds
1146
+ );
1147
+ return {
1148
+ accessToken,
1149
+ expiresAt: new Date(clock.now().getTime() + ttlSeconds * 1e3).toISOString()
1150
+ };
1151
+ }
1152
+ };
1153
+ }
1154
+ var CLOUD_CAPABILITIES = {
1155
+ /** `GET /byok/events` long-poll receive (§8). */
1156
+ eventsLongPoll: "events.longpoll",
1157
+ /** `POST /byok/messages` batched send (§8.2). */
1158
+ messagesBatch: "messages.batch",
1159
+ /** The three bearer-authed blob routes: reserve/grant, explicit finalize, committed-only download (§7). */
1160
+ blobsPresigned: "blobs.presigned",
1161
+ /**
1162
+ * The two presigned `/byok/blobs/:id/content` routes — cloud carrying the
1163
+ * bytes itself.
1164
+ *
1165
+ * Split out of `blobs.presigned` because it was one capability describing two
1166
+ * separable facts. A composition whose bytes live in object storage mints
1167
+ * grants (`blobs.presigned`) but has no byte-proxy path at all, and saying so
1168
+ * by declaration is ADR-010's whole posture: a client reads what a deployment
1169
+ * serves, it never probes a `/content` route and interprets the status code.
1170
+ *
1171
+ * Spelled all-lowercase because core's declaration schema pins capability
1172
+ * names to `/^[a-z0-9]+(?:[._-][a-z0-9]+)*$/` — the same reason the sibling
1173
+ * above reads `events.longpoll` and not `events.longPoll`.
1174
+ */
1175
+ blobsContentProxy: "blobs.contentproxy",
1176
+ /** Board list/claim/unclaim/status routes. Polling is first-class under this declaration. */
1177
+ boardCoordination: "board.coordination",
1178
+ /** Additional SSE transport for the same board read model. */
1179
+ boardSse: "board.sse",
1180
+ /** Device-scoped five-level presence publication. */
1181
+ presenceHints: "presence.hints",
1182
+ /** Bounded task activity batch publication. */
1183
+ activityTail: "activity.tail",
1184
+ /** Request-bound device proof record manifest/read/write surface (S6). */
1185
+ truthRecords: "truth.records",
1186
+ /**
1187
+ * Tenant-scoped skill pack distribution: the manifest list plus the per-file
1188
+ * content route a paired device installs from.
1189
+ *
1190
+ * Hosted HTTP on purpose. A pack is DECLARATIVE CONTENT a device pulls after
1191
+ * reading this declaration — not a message — so `@byok-sdk/protocol` gains
1192
+ * nothing from it and the frozen v1 envelope stays untouched. Composition-
1193
+ * bound like `truth.records`: a deployment that declares it without supplying
1194
+ * a `SkillPackStore` is refused at construction rather than publishing two
1195
+ * routes it would then 404.
1196
+ */
1197
+ skillPacks: "skills.pack",
1198
+ /** Optional, one-way redacted Agent-memory snapshot mutation route. */
1199
+ agentMemoryProjection: "agent.memory.projection"
1200
+ };
1201
+ var CapabilitiesResponseSchema = CapabilityDeclarationSchema;
1202
+ function fullCapabilityDeclaration(version = 1, options = {}) {
1203
+ return {
1204
+ schema: "byok-capabilities-v1",
1205
+ version,
1206
+ capabilities: Object.values(CLOUD_CAPABILITIES).filter((capability) => {
1207
+ if (capability === CLOUD_CAPABILITIES.truthRecords) return options.includeTruthRecords === true;
1208
+ if (capability === CLOUD_CAPABILITIES.skillPacks) return options.includeSkillPacks === true;
1209
+ if (capability === CLOUD_CAPABILITIES.agentMemoryProjection) {
1210
+ return options.includeAgentMemoryProjection === true;
1211
+ }
1212
+ return true;
1213
+ })
1214
+ };
1215
+ }
1216
+ function declares(declaration, capability) {
1217
+ return hasCapability(declaration, capability);
1218
+ }
1219
+ function extractBearerToken(header) {
1220
+ if (header === void 0) return void 0;
1221
+ const match = /^Bearer\s+(.+)$/i.exec(header);
1222
+ return match?.[1];
1223
+ }
1224
+ async function authenticateBearer(header, deps) {
1225
+ const token = extractBearerToken(header);
1226
+ if (token === void 0) return void 0;
1227
+ const claims = await deps.tokenSigner.verify(token);
1228
+ if (claims === void 0) return void 0;
1229
+ if (!isTenantId(claims.tenantId)) return void 0;
1230
+ const device = await deps.devices.get(tenantId(claims.tenantId), claims.deviceId);
1231
+ if (device === void 0 || device.revoked) return void 0;
1232
+ if (device.productId !== claims.productId) return void 0;
1233
+ if (deps.instanceProductId !== void 0 && claims.productId !== deps.instanceProductId) {
1234
+ return void 0;
1318
1235
  }
1319
- const disposition = agentMessageDisposition(payload, decision);
1320
- const admission = await stores.tasks.finalizeAgentMessage({
1321
- taskId,
1322
- deviceId,
1323
- messageId: payload.messageId,
1324
- payloadBody,
1325
- terminalBody: JSON.stringify({ payload, disposition })
1326
- });
1327
- const terminal = admission?.terminalBody === void 0 ? void 0 : parseAgentMessageDisposition(payload, admission.terminalBody);
1328
- return terminal === void 0 ? { outcome: "rejected" } : { outcome: "accepted", disposition: terminal };
1236
+ return {
1237
+ kind: "device",
1238
+ tenantId: device.tenantId,
1239
+ productId: device.productId,
1240
+ deviceId: device.deviceId
1241
+ };
1329
1242
  }
1330
- function agentMessageDisposition(payload, decision) {
1243
+ function tenantStoresFor(principal, root) {
1244
+ const tenant = principalTenant(principal);
1245
+ const { core, cloud } = root;
1331
1246
  return {
1332
- agentRef: payload.agentRef,
1333
- sessionRef: payload.sessionRef,
1334
- contract: payload.contract,
1335
- messageId: payload.messageId,
1336
- cursor: payload.cursor,
1337
- contentHash: payload.contentHash,
1338
- outcome: decision.outcome,
1339
- receiptId: crypto.randomUUID(),
1340
- ...decision.reasonCode === void 0 ? {} : { reasonCode: decision.reasonCode }
1247
+ tenant,
1248
+ principal,
1249
+ mailbox: {
1250
+ append: (input) => core.mailbox.append(tenant, input),
1251
+ readAfter: (query) => core.mailbox.readAfter(tenant, query),
1252
+ recordDelivery: (input) => core.mailbox.recordDelivery(tenant, input),
1253
+ advanceCursor: (input) => core.mailbox.advanceCursor(tenant, input),
1254
+ readCursor: (deviceId) => core.mailbox.readCursor(tenant, deviceId)
1255
+ },
1256
+ board: {
1257
+ create: (input) => core.board.create(tenant, input),
1258
+ get: (itemId) => core.board.get(tenant, itemId),
1259
+ list: (query) => core.board.list(tenant, query),
1260
+ claim: (input) => core.board.claim(tenant, input),
1261
+ unclaim: (input) => core.board.unclaim(tenant, input),
1262
+ updateStatus: (input) => core.board.updateStatus(tenant, input)
1263
+ },
1264
+ presence: {
1265
+ publish: (input) => core.presence.publish(tenant, input),
1266
+ read: (deviceId) => core.presence.read(tenant, deviceId),
1267
+ list: () => core.presence.list(tenant)
1268
+ },
1269
+ activity: {
1270
+ append: (input) => cloud.activity.append(tenant, input),
1271
+ read: (taskId) => cloud.activity.read(tenant, taskId)
1272
+ },
1273
+ approvals: {
1274
+ append: (input) => cloud.approvals.append(tenant, input),
1275
+ read: (taskId) => cloud.approvals.read(tenant, taskId)
1276
+ },
1277
+ devices: {
1278
+ get: (deviceId) => cloud.devices.get(tenant, deviceId),
1279
+ list: () => cloud.devices.list(tenant),
1280
+ revoke: (deviceId) => cloud.devices.revoke(tenant, deviceId),
1281
+ recordCapabilities: (input) => principal.kind === "device" ? cloud.devices.recordCapabilities(tenant, {
1282
+ deviceId: principal.deviceId,
1283
+ capabilities: input.capabilities,
1284
+ ...input.harnesses === void 0 ? {} : { harnesses: input.harnesses }
1285
+ }) : Promise.resolve(void 0),
1286
+ readiness: () => cloud.devices.readiness(tenant, core.presence)
1287
+ },
1288
+ tasks: {
1289
+ open: (input) => cloud.tasks.open(tenant, input),
1290
+ reserveAgentOffer: (input) => cloud.tasks.reserveAgentOffer(tenant, input),
1291
+ reserveAgentMessage: (input) => cloud.tasks.reserveAgentMessage(tenant, input),
1292
+ readTaskAgentMessage: (input) => cloud.tasks.readTaskAgentMessage(tenant, input),
1293
+ readAgentMessage: (input) => cloud.tasks.readAgentMessage(tenant, input),
1294
+ finalizeAgentMessage: (input) => cloud.tasks.finalizeAgentMessage(tenant, input),
1295
+ get: (taskId) => cloud.tasks.get(tenant, taskId),
1296
+ getMany: (taskIds) => cloud.tasks.getMany(tenant, taskIds),
1297
+ list: (query) => cloud.tasks.list(tenant, query),
1298
+ claim: (input) => cloud.tasks.claim(tenant, input),
1299
+ recordStatus: (input) => cloud.tasks.recordStatus(tenant, input)
1300
+ },
1301
+ cancellations: {
1302
+ request: (input) => cloud.cancellations.request(tenant, input)
1303
+ },
1304
+ dedup: {
1305
+ checkAndRecord: (deviceId, envelopeId) => cloud.dedup.checkAndRecord(tenant, deviceId, envelopeId),
1306
+ checkAndRecordAgent: (deviceId, agentRef, envelopeId) => cloud.dedup.checkAndRecordAgent(tenant, deviceId, agentRef, envelopeId)
1307
+ },
1308
+ receipts: {
1309
+ record: (input) => cloud.receipts.record(tenant, input),
1310
+ get: (key) => cloud.receipts.get(tenant, key)
1311
+ },
1312
+ egress: {
1313
+ record: (input) => cloud.egress.record(tenant, input),
1314
+ get: (deviceId, agentRef, eventId) => cloud.egress.get(tenant, deviceId, agentRef, eventId)
1315
+ },
1316
+ blobs: {
1317
+ createUpload: (reservation) => cloud.blobs.createUpload(tenant, reservation),
1318
+ observeUpload: (blobId, reservation) => cloud.blobs.observeUpload(tenant, blobId, reservation),
1319
+ getDownloadUrl: (blobId) => cloud.blobs.getDownloadUrl(tenant, blobId)
1320
+ },
1321
+ quota: {
1322
+ readReservation: (reservationId) => core.quota.readReservation(tenant, reservationId),
1323
+ reserve: (input) => core.quota.reserve(tenant, input),
1324
+ finalizeReservation: (input) => core.quota.finalizeReservation(tenant, input),
1325
+ abortReservation: (reservationId) => core.quota.abortReservation(tenant, reservationId)
1326
+ },
1327
+ rateLimiter: {
1328
+ consume: (deviceId) => cloud.rateLimiter.consume(tenant, deviceId)
1329
+ }
1341
1330
  };
1342
1331
  }
1343
- async function readAgentMessageDisposition(stores, deviceId, taskId, payload) {
1344
- const admission = await stores.tasks.readAgentMessage({
1345
- taskId,
1346
- deviceId,
1347
- messageId: payload.messageId,
1348
- payloadBody: JSON.stringify(payload)
1349
- });
1350
- return admission?.terminalBody === void 0 ? void 0 : parseAgentMessageDisposition(payload, admission.terminalBody);
1351
- }
1352
- function parseAgentMessageDisposition(payload, terminalBody) {
1332
+
1333
+ // src/handlers/shared.ts
1334
+ async function readJsonBody(c) {
1353
1335
  try {
1354
- const decoded = JSON.parse(terminalBody);
1355
- return decoded.disposition !== void 0 && JSON.stringify(decoded.payload) === JSON.stringify(payload) ? decoded.disposition : void 0;
1336
+ return await c.req.json();
1356
1337
  } catch {
1357
1338
  return void 0;
1358
1339
  }
1359
1340
  }
1360
- function terminalReceiptKey(taskId) {
1361
- return `task:${taskId}:terminal`;
1362
- }
1363
- function sameAgentRef(expected, actual) {
1364
- return actual?.agentId === expected.agentId && actual.profileRevision === expected.profileRevision;
1365
- }
1366
- function sameReliableEgress(expected, actual) {
1367
- return sameAgentRef(expected.agentRef, actual.agentRef) && expected.sessionRef === actual.sessionRef && expected.policyRevision === actual.policyRevision && expected.eventId === actual.eventId && expected.cursor === actual.cursor && expected.contentHash === actual.contentHash && expected.byteCount === actual.byteCount && JSON.stringify(expected.payload) === JSON.stringify(actual.payload);
1368
- }
1369
- function contentReadCapability(surface) {
1370
- switch (surface) {
1371
- case "workspace":
1372
- return AGENT_CONTENT_WORKSPACE_READ_CAPABILITY;
1373
- case "transcript":
1374
- return AGENT_CONTENT_TRANSCRIPT_READ_CAPABILITY;
1375
- case "artifact":
1376
- return AGENT_CONTENT_ARTIFACT_READ_CAPABILITY;
1341
+ async function readBoundedJsonBody(c, maximum) {
1342
+ const declaredLength = c.req.header("content-length");
1343
+ if (declaredLength !== void 0) {
1344
+ const parsedLength = parseContentLength(declaredLength);
1345
+ if (parsedLength !== void 0 && parsedLength > BigInt(maximum)) {
1346
+ cancelBody(c.req.raw.body);
1347
+ return { body: void 0, tooLarge: true };
1348
+ }
1377
1349
  }
1350
+ const stream = c.req.raw.body;
1351
+ if (stream === null) return { body: void 0, tooLarge: false };
1352
+ const reader = stream.getReader();
1353
+ const chunks = [];
1354
+ let total = 0;
1355
+ try {
1356
+ while (true) {
1357
+ const { done, value } = await reader.read();
1358
+ if (done) break;
1359
+ if (value.byteLength > maximum - total) {
1360
+ cancelBody(reader);
1361
+ return { body: void 0, tooLarge: true };
1362
+ }
1363
+ if (value.byteLength === 0) continue;
1364
+ chunks.push(value);
1365
+ total += value.byteLength;
1366
+ }
1367
+ } catch {
1368
+ return { body: void 0, tooLarge: false };
1369
+ } finally {
1370
+ reader.releaseLock();
1371
+ }
1372
+ const bytes = new Uint8Array(total);
1373
+ let offset = 0;
1374
+ for (const chunk of chunks) {
1375
+ bytes.set(chunk, offset);
1376
+ offset += chunk.byteLength;
1377
+ }
1378
+ return { body: parseJsonBytes(bytes), tooLarge: false };
1378
1379
  }
1379
- function hasCapabilities(capabilities, required) {
1380
- return capabilities !== void 0 && required.every((capability) => capabilities.includes(capability));
1381
- }
1382
- function contentRequestReceiptKey(deviceId, agentRef, requestId) {
1383
- return agentReliabilityKey("agent-content-request", deviceId, agentRef, requestId);
1384
- }
1385
- function contentReceiptKey(deviceId, agentRef, requestId) {
1386
- return agentReliabilityKey("agent-content-receipt", deviceId, agentRef, requestId);
1387
- }
1388
- function matchesContentReadReceipt(request, receipt) {
1389
- return request.requestId === receipt.requestId && request.surface === receipt.surface && request.actor.kind === receipt.actor.kind && request.actor.id === receipt.actor.id && sameAgentRef(request.agentRef, receipt.agentRef) && request.sessionRef === receipt.sessionRef && request.runtime === receipt.runtime && request.cwd === receipt.cwd && request.policyRevision === receipt.policyRevision && request.target === receipt.target && request.mimeType === receipt.mimeType && request.decodeAs === receipt.decodeAs;
1380
+ function cancelBody(body) {
1381
+ if (body === null) return;
1382
+ try {
1383
+ void Promise.resolve(body.cancel()).catch(() => {
1384
+ });
1385
+ } catch {
1386
+ }
1390
1387
  }
1391
- async function readContentRequest(stores, deviceId, agentRef, requestId) {
1392
- const stored = await stores.receipts.get(contentRequestReceiptKey(deviceId, agentRef, requestId));
1393
- if (stored === void 0) return void 0;
1388
+ function parseContentLength(value) {
1389
+ const normalized = value.trim();
1390
+ if (!/^\d+$/u.test(normalized)) return void 0;
1394
1391
  try {
1395
- return AgentContentReadPayloadSchema.parse(JSON.parse(stored.body));
1392
+ return BigInt(normalized);
1396
1393
  } catch {
1397
1394
  return void 0;
1398
1395
  }
1399
1396
  }
1400
- function inboundAgentRef(envelope) {
1401
- switch (envelope.type) {
1402
- case "task.claim":
1403
- case "task.decline":
1404
- case "task.complete":
1405
- case "task.fail":
1406
- case "task.cancelled":
1407
- return envelope.payload.agentRef;
1408
- default:
1409
- return void 0;
1397
+ function parseJsonBytes(bytes) {
1398
+ try {
1399
+ return JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
1400
+ } catch {
1401
+ return void 0;
1410
1402
  }
1411
1403
  }
1412
- function agentRefEchoRequired(envelope) {
1413
- return envelope.type === "task.claim" || envelope.type === "task.decline" || envelope.type === "task.complete" || envelope.type === "task.fail" || envelope.type === "task.cancelled";
1404
+ async function authenticateDevice(c, deps) {
1405
+ const device = await authenticateBearer(c.req.header("authorization"), deps.bearer);
1406
+ if (device === void 0) return void 0;
1407
+ return { device, stores: tenantStoresFor(device, deps.root) };
1414
1408
  }
1415
- async function handleInboundEnvelope(stores, deviceId, envelope, activityBounds = DEFAULT_ACTIVITY_BOUNDS, agentMessageConsume, observer) {
1416
- if (envelope.v !== PROTOCOL_VERSION) return "rejected";
1417
- const outcome = await applyInboundGate(
1418
- stores,
1419
- deviceId,
1420
- envelope,
1421
- activityBounds,
1422
- agentMessageConsume
1423
- );
1424
- if (outcome === "accepted" && observer !== void 0) {
1409
+
1410
+ // src/handlers/blobs.ts
1411
+ var BLOB_RESERVATION_TTL_MS = 15 * 60 * 1e3;
1412
+ var SignedUrlQuerySchema = z.object({
1413
+ sig: z.string().min(1),
1414
+ exp: z.coerce.number().finite()
1415
+ });
1416
+ function createBlobHandler(deps) {
1417
+ return async (c) => {
1418
+ const authenticated = await authenticateDevice(c, deps);
1419
+ if (authenticated === void 0) return c.json({ error: "unauthorized" }, 401);
1420
+ const parsed = CreateBlobRequestSchema.safeParse(await readJsonBody(c));
1421
+ if (!parsed.success) return c.json({ error: "size, contentType, and contentHash are required" }, 400);
1422
+ if (parsed.data.size > deps.maxBlobSizeBytes) {
1423
+ return c.json({ error: `blob exceeds max size of ${deps.maxBlobSizeBytes} bytes` }, 413);
1424
+ }
1425
+ const reservationId = idempotencyKey(c);
1426
+ if (reservationId === void 0) {
1427
+ return c.json({ error: "Idempotency-Key header is required" }, 400);
1428
+ }
1425
1429
  try {
1426
- observer.onInboundCommitted({ tenantId: stores.tenant, deviceId, envelope, outcome });
1427
- } catch {
1430
+ const reservation = await authenticated.stores.quota.reserve({
1431
+ reservationId,
1432
+ kind: "object",
1433
+ expectedBytes: BigInt(parsed.data.size),
1434
+ contentHash: contentHash(parsed.data.contentHash),
1435
+ contentType: parsed.data.contentType,
1436
+ ttlMs: BLOB_RESERVATION_TTL_MS
1437
+ });
1438
+ try {
1439
+ const { blobId, uploadUrl } = await authenticated.stores.blobs.createUpload(reservation);
1440
+ const response = { blobId, uploadUrl };
1441
+ return c.json(response, 200);
1442
+ } catch (error) {
1443
+ await authenticated.stores.quota.abortReservation(reservationId).catch(() => void 0);
1444
+ const rendered = renderStorageError(c, error);
1445
+ if (rendered !== void 0) return rendered;
1446
+ throw error;
1447
+ }
1448
+ } catch (error) {
1449
+ const rendered = renderStorageError(c, error);
1450
+ if (rendered !== void 0) return rendered;
1451
+ throw error;
1428
1452
  }
1429
- }
1430
- return outcome;
1453
+ };
1431
1454
  }
1432
- async function applyInboundGate(stores, deviceId, envelope, activityBounds, agentMessageConsume) {
1433
- if (!await stores.rateLimiter.consume(deviceId)) return "rate_limited";
1434
- if (envelope.type === "conn.hello") {
1435
- if (envelope.payload.deviceId !== deviceId) return "rejected";
1436
- if (envelope.payload.harnesses !== void 0 && !envelope.payload.capabilities.includes("custom-harness")) return "rejected";
1437
- if (!envelope.payload.protocolVersions.includes(PROTOCOL_VERSION)) return "rejected";
1438
- const device = await stores.devices.get(deviceId);
1439
- if (device === void 0 || device.revoked || device.productId !== envelope.payload.productId) {
1440
- return "rejected";
1455
+ function finalizeBlobHandler(deps) {
1456
+ return async (c) => {
1457
+ const authenticated = await authenticateDevice(c, deps);
1458
+ if (authenticated === void 0) return c.json({ error: "unauthorized" }, 401);
1459
+ const reservationId = idempotencyKey(c);
1460
+ if (reservationId === void 0) {
1461
+ return c.json({ error: "Idempotency-Key header is required" }, 400);
1441
1462
  }
1442
- if (await stores.devices.recordCapabilities({ capabilities: envelope.payload.capabilities, ...envelope.payload.harnesses === void 0 ? {} : { harnesses: envelope.payload.harnesses } }) === void 0) {
1443
- return "rejected";
1463
+ try {
1464
+ const reservation = await authenticated.stores.quota.readReservation(reservationId);
1465
+ if (reservation === void 0) {
1466
+ return c.json({ error: "storage_reservation_not_found" }, 404);
1467
+ }
1468
+ const blobId = c.req.param("id") ?? "";
1469
+ const observed = await authenticated.stores.blobs.observeUpload(
1470
+ blobId,
1471
+ reservation
1472
+ );
1473
+ if (observed === void 0) {
1474
+ await authenticated.stores.quota.abortReservation(reservationId);
1475
+ return c.json({ error: "storage_integrity_mismatch" }, 422);
1476
+ }
1477
+ await authenticated.stores.quota.finalizeReservation({
1478
+ reservationId,
1479
+ ...observed
1480
+ });
1481
+ return c.body(null, 204);
1482
+ } catch (error) {
1483
+ const rendered = renderStorageError(c, error);
1484
+ if (rendered !== void 0) return rendered;
1485
+ throw error;
1486
+ }
1487
+ };
1488
+ }
1489
+ function blobDownloadUrlHandler(deps) {
1490
+ return async (c) => {
1491
+ const authenticated = await authenticateDevice(c, deps);
1492
+ if (authenticated === void 0) return c.json({ error: "unauthorized" }, 401);
1493
+ const downloadUrl = await authenticated.stores.blobs.getDownloadUrl(c.req.param("id") ?? "");
1494
+ if (downloadUrl === void 0) return c.json({ error: "blob not found" }, 404);
1495
+ const response = { downloadUrl };
1496
+ return c.json(response, 200);
1497
+ };
1498
+ }
1499
+ function blobUploadContentHandler(deps) {
1500
+ return async (c) => {
1501
+ const blobId = c.req.param("id") ?? "";
1502
+ const query = SignedUrlQuerySchema.safeParse({ sig: c.req.query("sig"), exp: c.req.query("exp") });
1503
+ if (!query.success || !await deps.contentProxy.verifySignedUrl(blobId, "put", query.data.sig, query.data.exp)) {
1504
+ return c.json({ error: "invalid or expired signature" }, 401);
1505
+ }
1506
+ const expectedBytes = await deps.contentProxy.expectedUploadBytes(blobId);
1507
+ if (expectedBytes === void 0) return c.json({ error: "blob not found" }, 404);
1508
+ const absoluteCeiling = BigInt(deps.maxUploadBytes);
1509
+ if (expectedBytes > absoluteCeiling) return c.json({ error: "upload exceeds size limit" }, 413);
1510
+ const contentLength = c.req.header("content-length");
1511
+ if (contentLength !== void 0 && /^\d+$/u.test(contentLength) && BigInt(contentLength) > expectedBytes) {
1512
+ return c.json({ error: "upload exceeds declared size" }, 413);
1444
1513
  }
1445
- return completeInboundEnvelope(stores, deviceId, envelope.id, void 0);
1446
- }
1447
- if (!DAEMON_TO_SERVER_TYPES.includes(envelope.type)) return "rejected";
1448
- if (envelope.type === "agent.message.publish") {
1449
- const message = await handleAgentMessagePublish(
1450
- stores,
1451
- deviceId,
1452
- envelope.task_id,
1453
- envelope.payload,
1454
- agentMessageConsume);
1455
- return message.outcome === "rejected" ? "rejected" : completeInboundEnvelope(stores, deviceId, envelope.id, envelope.payload.agentRef);
1456
- }
1457
- if (envelope.type === "agent.egress.reliable") {
1458
- const device = await stores.devices.get(deviceId);
1459
- if (device === void 0 || device.revoked || !hasCapabilities(device.capabilities, [AGENT_EGRESS_POLICY_CAPABILITY, AGENT_EGRESS_RELIABLE_ACK_CAPABILITY])) {
1460
- return "rejected";
1514
+ const body = await readBoundedBody(c.req.raw.body, Number(expectedBytes));
1515
+ if (body.tooLarge) return c.json({ error: "upload exceeds declared size" }, 413);
1516
+ const result = await deps.contentProxy.writeContent(blobId, body.data);
1517
+ if (!result.ok) return c.json({ error: result.reason }, 422);
1518
+ return c.body(null, 204);
1519
+ };
1520
+ }
1521
+ async function readBoundedBody(body, ceiling) {
1522
+ if (body === null) return { tooLarge: false, data: new Uint8Array() };
1523
+ const reader = body.getReader();
1524
+ const data = new Uint8Array(ceiling);
1525
+ let offset = 0;
1526
+ try {
1527
+ for (; ; ) {
1528
+ const next = await reader.read();
1529
+ if (next.done) break;
1530
+ const chunk = next.value;
1531
+ if (chunk.byteLength > ceiling - offset) {
1532
+ void reader.cancel().catch(() => void 0);
1533
+ return { tooLarge: true };
1534
+ }
1535
+ data.set(chunk, offset);
1536
+ offset += chunk.byteLength;
1461
1537
  }
1462
- const outcome = await stores.egress.record({
1463
- deviceId,
1464
- payload: envelope.payload,
1465
- receiptId: crypto.randomUUID()
1466
- });
1467
- if (!outcome.created && !sameReliableEgress(outcome.record.payload, envelope.payload)) return "rejected";
1468
- return completeInboundEnvelope(stores, deviceId, envelope.id, envelope.payload.agentRef);
1538
+ } finally {
1539
+ reader.releaseLock();
1469
1540
  }
1470
- if (envelope.type === "agent.content.receipt") {
1471
- const device = await stores.devices.get(deviceId);
1472
- if (device === void 0 || device.revoked || envelope.payload.eventId !== envelope.payload.requestId || !hasCapabilities(device.capabilities, [contentReadCapability(envelope.payload.surface), AGENT_EGRESS_RELIABLE_ACK_CAPABILITY])) {
1473
- return "rejected";
1541
+ return { tooLarge: false, data: data.subarray(0, offset) };
1542
+ }
1543
+ function blobDownloadContentHandler(deps) {
1544
+ return async (c) => {
1545
+ const blobId = c.req.param("id") ?? "";
1546
+ const query = SignedUrlQuerySchema.safeParse({ sig: c.req.query("sig"), exp: c.req.query("exp") });
1547
+ if (!query.success || !await deps.contentProxy.verifySignedUrl(blobId, "get", query.data.sig, query.data.exp)) {
1548
+ return c.json({ error: "invalid or expired signature" }, 401);
1474
1549
  }
1475
- const request = await readContentRequest(stores, deviceId, envelope.payload.agentRef, envelope.payload.requestId);
1476
- if (request === void 0 || !matchesContentReadReceipt(request, envelope.payload)) return "rejected";
1477
- const key = contentReceiptKey(deviceId, envelope.payload.agentRef, envelope.payload.requestId);
1478
- const body = JSON.stringify(envelope.payload);
1479
- const receipt = await stores.receipts.record({ key, body });
1480
- if (!receipt.created && receipt.receipt.body !== body) return "rejected";
1481
- return completeInboundEnvelope(stores, deviceId, envelope.id, envelope.payload.agentRef);
1550
+ const result = await deps.contentProxy.readContent(blobId);
1551
+ if (result === void 0) return c.json({ error: "blob not found" }, 404);
1552
+ if (!result.ok) return c.json({ error: result.code }, BLOB_READ_ERROR_HTTP_STATUS[result.code]);
1553
+ return c.body(new Uint8Array(result.content.data), 200, { "content-type": result.content.contentType });
1554
+ };
1555
+ }
1556
+ var BLOB_READ_ERROR_HTTP_STATUS = {
1557
+ blob_upstream_unavailable: 502,
1558
+ blob_upstream_stream_interrupted: 502
1559
+ };
1560
+ function idempotencyKey(c) {
1561
+ const value = c.req.header("Idempotency-Key");
1562
+ if (value === void 0 || value.length === 0 || value.length > 200) return void 0;
1563
+ return value;
1564
+ }
1565
+ function renderStorageError(c, error) {
1566
+ if (!isCoreError(error)) return void 0;
1567
+ if (STORAGE_ERROR_CODES.includes(error.code)) {
1568
+ const code = error.code;
1569
+ return c.json({ error: code }, STORAGE_ERROR_HTTP_STATUS[code]);
1482
1570
  }
1483
- const taskId = envelope.task_id;
1484
- if (taskId === void 0) return "rejected";
1485
- const attempt = await stores.tasks.get(taskId);
1486
- if (attempt === void 0 || attempt.deviceId !== deviceId) return "rejected";
1487
- if (attempt?.ownerDeviceId !== void 0 && attempt.ownerDeviceId !== deviceId) return "rejected";
1488
- if (envelope.type === "task.claim" && envelope.payload.deviceId !== deviceId) return "rejected";
1489
- if (envelope.type === "task.claim") {
1490
- const harnessId = envelope.payload.harnessId;
1491
- if (harnessId !== void 0 && envelope.payload.runtime !== void 0) return "rejected";
1492
- if (attempt.ownerDeviceId !== void 0 && (harnessId !== void 0 || attempt.claimedHarnessId !== void 0) && harnessId !== attempt.claimedHarnessId) return "rejected";
1493
- const device = await stores.devices.get(deviceId);
1494
- if (harnessId !== void 0 && (!device?.capabilities?.includes("custom-harness") || !device.harnesses?.some((harness) => harness.id === harnessId))) return "rejected";
1495
- const messageId = await taskOfferMessageId(stores.tenant, taskId, deviceId, attempt.agentRef, hashOfferBytes);
1496
- const offer = await stores.receipts.get(`task-offer:${messageId}`);
1497
- if (offer !== void 0) {
1498
- const sealed = JSON.parse(offer.body);
1499
- if (harnessId !== void 0 && (sealed.payload.runtime !== void 0 || sealed.payload.dispatchSelection !== void 0)) return "rejected";
1500
- if (sealed.payload.harnessId !== void 0 && sealed.payload.harnessId !== harnessId) return "rejected";
1501
- } else if (harnessId !== void 0) return "rejected";
1571
+ if (error.code === "storage_reservation_not_found" || error.code === "object_not_found") {
1572
+ return c.json({ error: error.code }, 404);
1502
1573
  }
1503
- let unclaimedInterruption = false;
1504
- if (envelope.type === "task.fail" && envelope.payload.recovery !== void 0) {
1505
- if (envelope.payload.reason !== "daemon_interrupted" || envelope.payload.retryable !== false) return "rejected";
1506
- const messageId = await taskOfferMessageId(stores.tenant, taskId, deviceId, attempt.agentRef, hashOfferBytes);
1507
- if (envelope.payload.recovery.offerId !== messageId) return "rejected";
1508
- const offer = await stores.receipts.get(`task-offer:${messageId}`);
1509
- if (offer === void 0) return "rejected";
1510
- const sealed = JSON.parse(offer.body);
1511
- const harnessId = envelope.payload.harnessId;
1512
- if (sealed.payload.harnessId !== void 0 && sealed.payload.harnessId !== harnessId) return "rejected";
1513
- if (harnessId !== void 0 && (sealed.payload.runtime !== void 0 || sealed.payload.dispatchSelection !== void 0)) return "rejected";
1514
- unclaimedInterruption = attempt.ownerDeviceId === void 0 && attempt.claimedHarnessId === void 0;
1574
+ if (error.code === "storage_entitlement_missing" || error.code === "object_state_invalid") {
1575
+ return c.json({ error: error.code }, 409);
1515
1576
  }
1516
- if ((envelope.type === "task.complete" || envelope.type === "task.fail" || envelope.type === "task.cancelled") && !unclaimedInterruption && envelope.payload.harnessId !== attempt.claimedHarnessId) return "rejected";
1517
- if (attempt?.agentRef !== void 0 && agentRefEchoRequired(envelope) && !sameAgentRef(attempt.agentRef, inboundAgentRef(envelope))) {
1518
- return "rejected";
1577
+ if (error.code === "content_hash_invalid") {
1578
+ return c.json({ error: error.code }, 400);
1519
1579
  }
1520
- if (envelope.type === "task.progress" && envelope.payload.events.length > 0) {
1521
- try {
1522
- validateActivityBatch(
1580
+ return void 0;
1581
+ }
1582
+
1583
+ // src/handlers/capabilities.ts
1584
+ function capabilitiesHandler(deps) {
1585
+ return async (c) => c.json(deps.declaration, 200);
1586
+ }
1587
+ var AUTH_JSON_BODY_MAX_BYTES = 16 * 1024;
1588
+ function pairHandler(deps) {
1589
+ return async (c) => {
1590
+ const body = await readBoundedJsonBody(c, AUTH_JSON_BODY_MAX_BYTES);
1591
+ if (body.tooLarge) return c.json({ error: "request body too large" }, 413);
1592
+ const parsed = PairRequestSchema.safeParse(body.body);
1593
+ if (!parsed.success) {
1594
+ return c.json(
1523
1595
  {
1524
- taskId,
1525
- sourceEnvelopeId: envelope.id,
1526
- batchSeq: envelope.payload.seq,
1527
- events: envelope.payload.events,
1528
- dropped: 0
1596
+ error: "pairingCode, deviceName, and devicePublicKey are required strings; optional machineId must be 64 lowercase hex characters"
1529
1597
  },
1530
- activityBounds
1598
+ 400
1531
1599
  );
1532
- } catch (caught) {
1533
- if (isCloudError(caught, "activity_batch_too_large") || isCloudError(caught, "coordination_input_invalid")) {
1534
- return "rejected";
1535
- }
1536
- throw caught;
1537
1600
  }
1538
- }
1539
- const approvalInput = approvalTimelineAppendInput(taskId, envelope);
1540
- if (approvalInput !== void 0) {
1541
- try {
1542
- validateApprovalTimelineAppend(approvalInput);
1543
- } catch (caught) {
1544
- if (isCloudError(caught, "coordination_input_invalid")) return "rejected";
1545
- throw caught;
1601
+ const device = await deps.auth.redeemAndRegister(parsed.data);
1602
+ if (device === void 0) return c.json({ error: "invalid pairing code" }, 401);
1603
+ const { accessToken, expiresAt } = await deps.auth.mintAccessToken(device);
1604
+ const response = PairResponseSchema.parse({
1605
+ deviceId: device.deviceId,
1606
+ accessToken,
1607
+ refreshHint: expiresAt,
1608
+ tenantId: device.tenantId
1609
+ });
1610
+ return c.json(response, 200);
1611
+ };
1612
+ }
1613
+ function challengeHandler(deps) {
1614
+ return async (c) => {
1615
+ const body = await readBoundedJsonBody(c, AUTH_JSON_BODY_MAX_BYTES);
1616
+ if (body.tooLarge) return c.json({ error: "request body too large" }, 413);
1617
+ const parsed = ChallengeRequestSchema.safeParse(body.body);
1618
+ if (!parsed.success) return c.json({ error: "deviceId is required" }, 400);
1619
+ const device = await deps.auth.resolveDevice(parsed.data.deviceId);
1620
+ if (device === void 0) return c.json({ error: "unknown or revoked device" }, 401);
1621
+ const response = { nonce: await deps.auth.issueNonce(device) };
1622
+ return c.json(response, 200);
1623
+ };
1624
+ }
1625
+ function tokenHandler(deps) {
1626
+ return async (c) => {
1627
+ const body = await readBoundedJsonBody(c, AUTH_JSON_BODY_MAX_BYTES);
1628
+ if (body.tooLarge) return c.json({ error: "request body too large" }, 413);
1629
+ const parsed = TokenRequestSchema.safeParse(body.body);
1630
+ if (!parsed.success) return c.json({ error: "deviceId, nonce, and signature are required" }, 400);
1631
+ const { deviceId, nonce, signature } = parsed.data;
1632
+ const device = await deps.auth.resolveDevice(deviceId);
1633
+ if (device === void 0) return c.json({ error: "unknown or revoked device" }, 401);
1634
+ if (!await deps.auth.verifySignature(device, nonce, signature)) {
1635
+ return c.json({ error: "invalid signature" }, 401);
1546
1636
  }
1547
- }
1548
- const lifecycleOutcome = await applyLifecycle(stores, deviceId, taskId, envelope, activityBounds, attempt?.agentRef);
1549
- if (lifecycleOutcome === "rejected") return "rejected";
1550
- return completeInboundEnvelope(stores, deviceId, envelope.id, attempt?.agentRef);
1637
+ if (!await deps.auth.consumeNonceIfValid(device, nonce)) {
1638
+ return c.json({ error: "invalid, expired, or already-used nonce" }, 401);
1639
+ }
1640
+ const { accessToken, expiresAt } = await deps.auth.mintAccessToken(device);
1641
+ const response = { accessToken, expiresAt };
1642
+ return c.json(response, 200);
1643
+ };
1551
1644
  }
1552
- async function completeInboundEnvelope(stores, deviceId, envelopeId, agentRef) {
1553
- const duplicate = agentRef === void 0 ? await stores.dedup.checkAndRecord(deviceId, envelopeId) : await stores.dedup.checkAndRecordAgent(deviceId, agentRef, envelopeId);
1554
- return duplicate ? "duplicate" : "accepted";
1645
+ var CLOUD_PROTOCOL_CAPABILITIES = [
1646
+ "custom-harness",
1647
+ MAILBOX_READ_AHEAD_CAPABILITY,
1648
+ "result-document",
1649
+ // The kernel's inbound handles `task.approval_resolved` on both the plain
1650
+ // and the approval-timeline paths (`packages/cloud/src/inbound.ts:576,610`).
1651
+ // Without this flag `TaskRunner.sendApprovalResolved` returns early and a
1652
+ // locally-resolved approval is never reported on the wire at all.
1653
+ "approval_resolved",
1654
+ "agent-home-contract",
1655
+ "agent-home-projection",
1656
+ "agent-egress-policy",
1657
+ "agent-egress-reliable-ack",
1658
+ "agent-egress-fresh-session",
1659
+ // `handleAgentMessagePublish` accepts `agent.message_egress` gated on exactly
1660
+ // this capability (`packages/cloud/src/inbound.ts:124`) — the same inbound
1661
+ // family as the three egress flags above, omitted by the same drift.
1662
+ "agent-message-egress",
1663
+ "agent-content-workspace-read",
1664
+ "agent-content-transcript-read",
1665
+ "agent-content-artifact-read"
1666
+ ];
1667
+ function sleep(ms) {
1668
+ return new Promise((resolve) => setTimeout(resolve, ms));
1555
1669
  }
1556
- async function applyLifecycle(stores, deviceId, taskId, envelope, activityBounds, persistedAgentRef) {
1557
- switch (envelope.type) {
1558
- case "task.claim": {
1559
- const claimed = await stores.tasks.claim({
1560
- taskId,
1561
- deviceId,
1562
- ...envelope.payload.runtime === void 0 ? {} : { runtime: envelope.payload.runtime },
1563
- ...envelope.payload.harnessId === void 0 ? {} : { harnessId: envelope.payload.harnessId },
1564
- ...envelope.payload.capabilities === void 0 ? {} : { capabilities: envelope.payload.capabilities }
1565
- });
1566
- if (claimed === void 0 || claimed.ownerDeviceId !== deviceId || claimed.claimedRuntime !== envelope.payload.runtime || claimed.claimedHarnessId !== envelope.payload.harnessId) return "rejected";
1567
- return;
1670
+ function eventsHandler(deps) {
1671
+ return async (c) => {
1672
+ const authenticated = await authenticateDevice(c, deps);
1673
+ if (authenticated === void 0) return c.json({ error: "unauthorized" }, 401);
1674
+ const { device, stores } = authenticated;
1675
+ const cursorRaw = c.req.query("cursor");
1676
+ let cursor = 0;
1677
+ if (cursorRaw !== void 0) {
1678
+ const parsedCursor = Number(cursorRaw);
1679
+ if (!Number.isSafeInteger(parsedCursor) || parsedCursor < 0) {
1680
+ return c.json({ error: "invalid cursor" }, 400);
1681
+ }
1682
+ cursor = parsedCursor;
1568
1683
  }
1569
- case "task.started":
1570
- await stores.tasks.recordStatus({
1571
- taskId,
1572
- status: "running",
1573
- ...persistedAgentRef === void 0 ? {} : { agentRef: persistedAgentRef }
1574
- });
1575
- return;
1576
- case "task.decline":
1577
- await recordTerminal(stores, taskId, envelope);
1578
- return;
1579
- case "task.progress":
1580
- if (envelope.payload.events.length > 0) {
1581
- await appendActivityEvents(
1582
- stores.activity,
1583
- {
1584
- taskId,
1585
- sourceEnvelopeId: envelope.id,
1586
- batchSeq: envelope.payload.seq,
1587
- events: envelope.payload.events,
1588
- dropped: 0
1589
- },
1590
- activityBounds
1591
- );
1684
+ const afterRaw = c.req.query("afterSeq");
1685
+ const afterSeq = afterRaw === void 0 ? cursor : Number(afterRaw);
1686
+ if (!Number.isSafeInteger(afterSeq) || afterSeq < cursor || afterSeq - cursor > MAILBOX_READ_AHEAD_MAX_SEQS) {
1687
+ return c.json({ error: "invalid read-ahead offset" }, 400);
1688
+ }
1689
+ const acked = await stores.mailbox.readCursor(device.deviceId);
1690
+ if (afterRaw !== void 0 && afterSeq > acked.deliveredSeq) return c.json({ error: "read offset exceeds delivered watermark" }, 409);
1691
+ if (cursor > acked.ackedSeq) {
1692
+ try {
1693
+ await stores.mailbox.advanceCursor({ deviceId: device.deviceId, ackedSeq: cursor });
1694
+ } catch (caught) {
1695
+ if (isCoreConflictError(caught, "mailbox_cursor_ahead_of_delivery")) {
1696
+ return c.json({ error: "cursor exceeds delivered watermark" }, 409);
1697
+ }
1698
+ throw caught;
1592
1699
  }
1593
- return;
1594
- case "task.await_approval":
1595
- await stores.approvals.append(approvalTimelineAppendInput(taskId, envelope));
1596
- return;
1597
- case "task.approval_resolved":
1598
- await stores.approvals.append(approvalTimelineAppendInput(taskId, envelope));
1599
- return;
1600
- case "task.complete":
1601
- await recordTerminal(stores, taskId, envelope);
1602
- return;
1603
- case "task.fail":
1604
- await recordTerminal(stores, taskId, envelope);
1605
- return;
1606
- case "task.cancelled":
1607
- await recordTerminal(stores, taskId, envelope);
1608
- return;
1609
- default:
1610
- return;
1611
- }
1612
- }
1613
- function approvalTimelineAppendInput(taskId, envelope) {
1614
- switch (envelope.type) {
1615
- case "task.await_approval":
1616
- return {
1617
- taskId,
1618
- sourceEnvelopeId: envelope.id,
1619
- event: {
1620
- type: "approval_requested",
1621
- summary: envelope.payload.summary,
1622
- ...envelope.payload.approvalId === void 0 ? {} : { approvalId: envelope.payload.approvalId }
1700
+ }
1701
+ const attempts = Math.max(1, Math.ceil(deps.longPollHoldMs / deps.longPollIntervalMs));
1702
+ for (let attempt = 0; attempt < attempts; attempt += 1) {
1703
+ let scanCursor = afterSeq;
1704
+ while (true) {
1705
+ const page = await stores.mailbox.readAfter({
1706
+ deviceId: device.deviceId,
1707
+ afterSeq: scanCursor,
1708
+ limit: deps.pageLimit
1709
+ });
1710
+ if (cursor < page.recoverableFrom - 1) {
1711
+ return c.json({ error: "cursor_too_old", recoverableFrom: page.recoverableFrom }, 409);
1623
1712
  }
1624
- };
1625
- case "task.approval_resolved":
1626
- return {
1627
- taskId,
1628
- sourceEnvelopeId: envelope.id,
1629
- event: {
1630
- type: "approval_resolved",
1631
- approvalId: envelope.payload.approvalId,
1632
- decision: envelope.payload.decision,
1633
- resolvedBy: envelope.payload.resolvedBy,
1634
- at: envelope.payload.at
1713
+ if (page.messages.length === 0) break;
1714
+ const decoded = page.messages.map((message) => decodeEnvelope(message.body));
1715
+ const offeredTaskIds = decoded.flatMap(
1716
+ (event) => (event.type === "task.offer" || event.type === "task.offer_with_toolsets" || event.type === "task.offer_for_agent" || event.type === "task.offer_for_agent_with_egress" || event.type === "task.offer_for_agent_with_egress_fresh") && event.task_id !== void 0 ? [event.task_id] : []
1717
+ );
1718
+ const attemptsByTaskId = Object.fromEntries(
1719
+ (await stores.tasks.getMany(offeredTaskIds)).map((attempt2) => [attempt2.taskId, attempt2])
1720
+ );
1721
+ const events = decoded.filter((event) => {
1722
+ if (event.type !== "task.offer" && event.type !== "task.offer_with_toolsets" && event.type !== "task.offer_for_agent" && event.type !== "task.offer_for_agent_with_egress" && event.type !== "task.offer_for_agent_with_egress_fresh") {
1723
+ return true;
1724
+ }
1725
+ return event.task_id === void 0 || attemptsByTaskId[event.task_id]?.cancellation === void 0;
1726
+ });
1727
+ if (events.length === 0 && page.hasMore) {
1728
+ scanCursor = page.nextSeq;
1729
+ continue;
1635
1730
  }
1636
- };
1637
- default:
1638
- return void 0;
1639
- }
1640
- }
1641
- async function recordTerminal(stores, taskId, envelope) {
1642
- const receipt = await stores.receipts.record({
1643
- key: terminalReceiptKey(taskId),
1644
- body: encodeEnvelope(envelope)
1645
- });
1646
- const winner = terminalReceiptBinding(taskId, receipt.receipt.body);
1647
- if (winner === void 0) {
1648
- throw new Error(`terminal receipt for ${taskId} is not a canonical terminal binding`);
1649
- }
1650
- const attempt = await stores.tasks.get(taskId);
1651
- if (attempt?.cancellation !== void 0) {
1652
- await stores.tasks.recordStatus({
1653
- taskId,
1654
- status: "cancelled",
1655
- ...attempt.agentRef === void 0 ? {} : { agentRef: attempt.agentRef }
1656
- });
1657
- return;
1658
- }
1659
- const recordedAttempt = await stores.tasks.recordStatus({
1660
- taskId,
1661
- status: winner.status,
1662
- ...attempt?.agentRef === void 0 ? {} : { agentRef: attempt.agentRef },
1663
- ...winner.cause === void 0 ? {} : { terminalCause: winner.cause }
1664
- });
1665
- if (recordedAttempt?.cancellation !== void 0 || recordedAttempt?.status !== winner.status) {
1666
- if (recordedAttempt?.cancellation !== void 0) {
1667
- await stores.tasks.recordStatus({
1668
- taskId,
1669
- status: "cancelled",
1670
- ...recordedAttempt.agentRef === void 0 ? {} : { agentRef: recordedAttempt.agentRef }
1671
- });
1731
+ if (events.length === 0) break;
1732
+ const response2 = {
1733
+ events,
1734
+ cursor: page.nextSeq,
1735
+ capabilities: CLOUD_PROTOCOL_CAPABILITIES
1736
+ };
1737
+ await stores.mailbox.recordDelivery({
1738
+ deviceId: device.deviceId,
1739
+ deliveredSeq: response2.cursor
1740
+ });
1741
+ return c.json(response2, 200);
1742
+ }
1743
+ if (attempt < attempts - 1) await sleep(deps.longPollIntervalMs);
1672
1744
  }
1673
- return;
1674
- }
1675
- await projectTerminalToReview(stores.board, taskId);
1676
- }
1677
- function terminalStatus(envelope) {
1678
- switch (envelope.type) {
1679
- case "task.complete":
1680
- return "complete";
1681
- case "task.fail":
1682
- case "task.decline":
1683
- return "failed";
1684
- case "task.cancelled":
1685
- return "cancelled";
1686
- default:
1687
- return void 0;
1688
- }
1689
- }
1690
- function terminalReceiptBinding(taskId, body) {
1691
- let envelope;
1692
- try {
1693
- envelope = decodeEnvelope(body);
1694
- } catch {
1695
- return void 0;
1696
- }
1697
- const status = terminalStatus(envelope);
1698
- if (status === void 0 || envelope.task_id !== taskId || encodeEnvelope(envelope) !== body) return void 0;
1699
- const cause = envelope.type === "task.fail" || envelope.type === "task.cancelled" || envelope.type === "task.decline" ? envelope.payload.reason : void 0;
1700
- return { status, ...cause === void 0 ? {} : { cause } };
1745
+ const response = {
1746
+ events: [],
1747
+ cursor: afterSeq,
1748
+ capabilities: CLOUD_PROTOCOL_CAPABILITIES
1749
+ };
1750
+ return c.json(response, 200);
1751
+ };
1701
1752
  }
1702
-
1703
- // src/handlers/messages.ts
1704
1753
  var MESSAGES_JSON_BODY_MAX_BYTES = 2 * 1024 * 1024;
1705
1754
  function messagesHandler(deps) {
1706
1755
  return async (c) => {
@@ -2661,6 +2710,13 @@ async function recordAgentHomeProjectionCompletion(receipts, tenant, deviceId, r
2661
2710
  completedAt: storedCompletion.receipt.recordedAt
2662
2711
  };
2663
2712
  }
2713
+ function readDeviceTerminalReceipt(taskId, receipt) {
2714
+ const envelope = decodeEnvelope(receipt.body);
2715
+ if (envelope.task_id !== taskId || envelope.type !== "task.complete" && envelope.type !== "task.fail" && envelope.type !== "task.decline" && envelope.type !== "task.cancelled") {
2716
+ throw new ByokCloudError("terminal_receipt_unreadable", "Device terminal receipt identity or type is invalid.");
2717
+ }
2718
+ return { envelope, recordedAt: receipt.recordedAt };
2719
+ }
2664
2720
  function projectTerminalResult(taskId, receipt) {
2665
2721
  let envelope;
2666
2722
  try {
@@ -3474,6 +3530,36 @@ function createByokCloud(options) {
3474
3530
  throw new ByokCloudError("mailbox_receipt_mismatch", "Agent message disposition mailbox readback mismatched.");
3475
3531
  }
3476
3532
  }
3533
+ async function enqueueFreshAgentEgressOffer(tenant, deviceId, input) {
3534
+ await assertAgentCapabilities(tenant, deviceId, [
3535
+ AGENT_HOME_CONTRACT_CAPABILITY,
3536
+ AGENT_EGRESS_POLICY_CAPABILITY,
3537
+ AGENT_EGRESS_RELIABLE_ACK_CAPABILITY,
3538
+ AGENT_EGRESS_FRESH_SESSION_CAPABILITY,
3539
+ ...input.payload.messageEgress === void 0 ? [] : [AGENT_MESSAGE_EGRESS_CAPABILITY],
3540
+ ...input.payload.terminalProjection === void 0 ? [] : [TERMINAL_PROJECTION_SELECTION_CAPABILITY]
3541
+ ]);
3542
+ const payload = TaskOfferForAgentWithEgressFreshPayloadSchema.parse(input.payload);
3543
+ if (payload.messageEgress === void 0 && input.agentMessageContext !== void 0) {
3544
+ throw new Error("agentMessageContext requires messageEgress");
3545
+ }
3546
+ const messageContext = payload.messageEgress === void 0 ? void 0 : AgentMessageServerContextSchema.parse(input.agentMessageContext);
3547
+ const enqueued = await enqueueTaskEnvelope(
3548
+ tenant,
3549
+ deviceId,
3550
+ input.taskId,
3551
+ payload.agentRef,
3552
+ (taskId, seq, messageId) => createEnvelope("task.offer_for_agent_with_egress_fresh", payload, { id: messageId, taskId, seq }),
3553
+ payload.messageEgress === void 0 ? void 0 : async (stores, taskId) => {
3554
+ const body = JSON.stringify({ agentRef: payload.agentRef, requirement: payload.messageEgress, context: messageContext });
3555
+ const recorded = await stores.receipts.record({ key: `agent-message-offer:${deviceId}:${taskId}`, body });
3556
+ if (!recorded.created && recorded.receipt.body !== body) {
3557
+ throw new ByokCloudError("agent_content_request_mismatch", `Task ${taskId} already has a different Agent message context.`);
3558
+ }
3559
+ }
3560
+ );
3561
+ return enqueued;
3562
+ }
3477
3563
  return {
3478
3564
  fetch: registry.fetch,
3479
3565
  routes: registry.routes,
@@ -3545,35 +3631,11 @@ function createByokCloud(options) {
3545
3631
  );
3546
3632
  return enqueued;
3547
3633
  },
3548
- async enqueueFreshAgentEgressOffer(tenant, deviceId, input) {
3549
- await assertAgentCapabilities(tenant, deviceId, [
3550
- AGENT_HOME_CONTRACT_CAPABILITY,
3551
- AGENT_EGRESS_POLICY_CAPABILITY,
3552
- AGENT_EGRESS_RELIABLE_ACK_CAPABILITY,
3553
- AGENT_EGRESS_FRESH_SESSION_CAPABILITY,
3554
- ...input.payload.messageEgress === void 0 ? [] : [AGENT_MESSAGE_EGRESS_CAPABILITY],
3555
- ...input.payload.terminalProjection === void 0 ? [] : [TERMINAL_PROJECTION_SELECTION_CAPABILITY]
3556
- ]);
3557
- const payload = TaskOfferForAgentWithEgressFreshPayloadSchema.parse(input.payload);
3558
- if (payload.messageEgress === void 0 && input.agentMessageContext !== void 0) {
3559
- throw new Error("agentMessageContext requires messageEgress");
3560
- }
3561
- const messageContext = payload.messageEgress === void 0 ? void 0 : AgentMessageServerContextSchema.parse(input.agentMessageContext);
3562
- const enqueued = await enqueueTaskEnvelope(
3563
- tenant,
3564
- deviceId,
3565
- input.taskId,
3566
- payload.agentRef,
3567
- (taskId, seq, messageId) => createEnvelope("task.offer_for_agent_with_egress_fresh", payload, { id: messageId, taskId, seq }),
3568
- payload.messageEgress === void 0 ? void 0 : async (stores, taskId) => {
3569
- const body = JSON.stringify({ agentRef: payload.agentRef, requirement: payload.messageEgress, context: messageContext });
3570
- const recorded = await stores.receipts.record({ key: `agent-message-offer:${deviceId}:${taskId}`, body });
3571
- if (!recorded.created && recorded.receipt.body !== body) {
3572
- throw new ByokCloudError("agent_content_request_mismatch", `Task ${taskId} already has a different Agent message context.`);
3573
- }
3574
- }
3575
- );
3576
- return enqueued;
3634
+ enqueueFreshAgentEgressOffer,
3635
+ async submitRecurringExecution(tenant, input) {
3636
+ const validated = RecurringExecutionInputSchema.parse(input);
3637
+ if (typeof options.agentMessage?.consume !== "function") throw new Error("Recurring execution requires a registered Agent message consumer.");
3638
+ return enqueueFreshAgentEgressOffer(tenant, validated.deviceId, validated);
3577
3639
  },
3578
3640
  async enqueueAgentContentRead(tenant, deviceId, input) {
3579
3641
  const payload = AgentContentReadPayloadSchema.parse(input.payload);
@@ -3712,6 +3774,37 @@ function createByokCloud(options) {
3712
3774
  }
3713
3775
  return mutation.attempt;
3714
3776
  },
3777
+ async readTaskOffer(tenant, taskId) {
3778
+ const stores = tenantStoresFor(controlPlane(tenant), root);
3779
+ const attempt = await stores.tasks.get(taskId);
3780
+ if (attempt === void 0) return void 0;
3781
+ const messageId = await taskOfferMessageId(tenant, taskId, attempt.deviceId, attempt.agentRef, (bytes) => options.crypto.sha256(bytes));
3782
+ const receipt = await stores.receipts.get(`task-offer:${messageId}`);
3783
+ if (receipt === void 0) return void 0;
3784
+ const invalid = () => new ByokCloudError("coordination_input_invalid", `Task ${taskId} has an unreadable or mismatched immutable offer receipt.`);
3785
+ let stored;
3786
+ try {
3787
+ stored = JSON.parse(receipt.body);
3788
+ } catch {
3789
+ throw invalid();
3790
+ }
3791
+ if (stored === null || typeof stored !== "object" || stored.deviceId !== attempt.deviceId || typeof stored.type !== "string" || !isTaskOfferType(stored.type)) throw invalid();
3792
+ const parsed = MESSAGE_PAYLOAD_SCHEMAS[stored.type].safeParse(stored.payload);
3793
+ if (!parsed.success || JSON.stringify(parsed.data) !== JSON.stringify(stored.payload)) throw invalid();
3794
+ const agentRef = "agentRef" in parsed.data ? parsed.data.agentRef : void 0;
3795
+ if (agentRef === void 0 ? attempt.agentRef !== void 0 : attempt.agentRef === void 0 || !sameAgentRef2(agentRef, attempt.agentRef)) throw invalid();
3796
+ const delivered = await stores.receipts.get(`task-offer-delivered:${messageId}`);
3797
+ if (delivered !== void 0 && delivered.body !== messageId) throw invalid();
3798
+ return {
3799
+ taskId,
3800
+ deviceId: attempt.deviceId,
3801
+ messageId,
3802
+ type: stored.type,
3803
+ payload: parsed.data,
3804
+ recordedAt: receipt.recordedAt,
3805
+ delivered: delivered !== void 0
3806
+ };
3807
+ },
3715
3808
  readTaskAttempt(tenant, taskId) {
3716
3809
  return tenantStoresFor(controlPlane(tenant), root).tasks.get(taskId);
3717
3810
  },
@@ -3721,6 +3814,17 @@ function createByokCloud(options) {
3721
3814
  readTerminalReceipt(tenant, taskId) {
3722
3815
  return tenantStoresFor(controlPlane(tenant), root).receipts.get(terminalReceiptKey(taskId));
3723
3816
  },
3817
+ async readDeviceTerminal(tenant, taskId) {
3818
+ const receipt = await tenantStoresFor(controlPlane(tenant), root).receipts.get(terminalReceiptKey(taskId));
3819
+ return receipt === void 0 ? void 0 : readDeviceTerminalReceipt(taskId, receipt);
3820
+ },
3821
+ readTaskAgentMessage(tenant, deviceId, taskId, agentRef) {
3822
+ return readTaskAgentMessage(tenantStoresFor(controlPlane(tenant), root), deviceId, taskId, agentRef);
3823
+ },
3824
+ readAgentMessageDisposition(tenant, deviceId, taskId, payload) {
3825
+ const parsed = AgentMessagePublishPayloadSchema.parse(payload);
3826
+ return readAgentMessageDisposition(tenantStoresFor(controlPlane(tenant), root), deviceId, taskId, parsed);
3827
+ },
3724
3828
  readAgentEgress(tenant, deviceId, agentRef, eventId) {
3725
3829
  return tenantStoresFor(controlPlane(tenant), root).egress.get(deviceId, agentRef, eventId);
3726
3830
  },
@@ -4375,6 +4479,10 @@ var InMemoryTaskAttemptStore = class {
4375
4479
  return "reserved";
4376
4480
  });
4377
4481
  }
4482
+ async readTaskAgentMessage(tenant, input) {
4483
+ const admission = this.#state.messageAdmissions.get(tenantKey(tenant, input.taskId));
4484
+ return admission?.deviceId === input.deviceId ? admission : void 0;
4485
+ }
4378
4486
  async readAgentMessage(tenant, input) {
4379
4487
  const admission = this.#state.messageAdmissions.get(tenantKey(tenant, input.taskId));
4380
4488
  return admission !== void 0 && admission.deviceId === input.deviceId && admission.messageId === input.messageId && admission.payloadBody === input.payloadBody ? admission : void 0;
@@ -5182,6 +5290,7 @@ var CLOUD_PORT_METHODS = {
5182
5290
  "reserveAgentOffer",
5183
5291
  "reserveAgentMessage",
5184
5292
  "readAgentMessage",
5293
+ "readTaskAgentMessage",
5185
5294
  "finalizeAgentMessage",
5186
5295
  "get",
5187
5296
  "getMany",
@@ -5216,6 +5325,6 @@ var CLOUD_PORT_INTERFACES = {
5216
5325
  rateLimiter: "InboundRateLimiter"
5217
5326
  };
5218
5327
 
5219
- export { ACCESS_TOKEN_TTL_SECONDS, AGENT_HOME_CONTRACT_CAPABILITY, APPROVAL_SUMMARY_MAX_BYTES, ActivityAppendRequestSchema, AllowAllRateLimiter, ApprovalObservationSchema, ApprovalTimelineEventSchema, BLOB_READ_ERROR_CODES, BLOB_URL_TTL_MS, BoardFeedClient, BoardFeedRetryableError, BoardFeedStoppedError, ByokCloudError, CLOUD_CAPABILITIES, CLOUD_ERROR_CODES, CLOUD_PORT_INTERFACES, CLOUD_PORT_METHODS, CLOUD_STORE_NAMES, CapabilitiesResponseSchema, CloudRouteRegistry, DEDUP_RING_CAPACITY, DEFAULT_ACTIVITY_BOUNDS, DEFAULT_ACTIVITY_CAPACITY, DEFAULT_ACTIVITY_MAX_BYTES, DEFAULT_ACTIVITY_MAX_EVENTS, DEFAULT_ACTIVITY_TTL_MS, DEFAULT_APPROVAL_TIMELINE_CAPACITY, DEFAULT_APPROVAL_TIMELINE_TTL_MS, DEFAULT_BOARD_CHANNEL_MAX_BYTES, DEFAULT_BOARD_PAGE_LIMIT, DEFAULT_BOARD_STREAM_HEARTBEAT_INTERVAL_MS, DEFAULT_BOARD_STREAM_QUERY_INTERVAL_MS, DEFAULT_BOARD_STREAM_RECONCILIATION_INTERVAL_MS, DEFAULT_BOARD_TITLE_MAX_BYTES, DEFAULT_DEVICE_PROOF_CLOCK_SKEW_MS, DEFAULT_DEVICE_PROOF_MAX_LIFETIME_MS, DEFAULT_EVENTS_PAGE_LIMIT, DEFAULT_LONG_POLL_HOLD_MS, DEFAULT_LONG_POLL_INTERVAL_MS, DEFAULT_MAX_BLOB_SIZE_BYTES, DEFAULT_MAX_TRUTH_REQUEST_BYTES, DEFAULT_PRESENCE_DETAIL_MAX_BYTES, DEFAULT_PRESENCE_MINIMUM_INTERVAL_MS, DEFAULT_PRESENCE_TTL_MS, DEFAULT_SKILL_PACK_PAGE_LIMIT, DEVICE_IDENTITY_PROOF_KEY_EPOCH, DEVICE_IDENTITY_PROOF_KEY_ID, InMemoryActivityStore, InMemoryAgentEgressStore, InMemoryAgentMemoryProjectionAuthorizer, InMemoryAgentMemoryProjectionStore, InMemoryBlobContentProxy, InMemoryCloudBlobStore, InMemoryDeviceDirectory, InMemoryInboundDedupStore, InMemoryNonceStore, InMemoryPairingCodeStore, InMemoryProofRequestReceiptStore, InMemoryRequestReceiptStore, InMemoryTaskAttemptStore, InMemoryTaskCancellationStore, MAX_DEVICE_PROOF_CLOCK_SKEW_MS, MAX_DEVICE_PROOF_HEADER_BYTES, MAX_DEVICE_PROOF_MAX_LIFETIME_MS, NONCE_TTL_MS, PAIRING_CODE_TTL_MS, ROUTE_CLASSES, ROUTE_METHODS, StaleApprovalError, SteerRejectedError, TASK_ATTEMPT_STATUSES, TRUTH_BATCH_MAX_RECORDS, TRUTH_INLINE_CONTENT_TYPE, TRUTH_LABEL_MAX_LENGTH, TRUTH_MANIFEST_MAX_LIMIT, TRUTH_RECORD_CAPABILITY, TRUTH_RECORD_KEY_MAX_LENGTH, TRUTH_REQUEST_ID_MAX_LENGTH, TimelineEventSchema, TruthBodyInputSchema, TruthCommitError, TruthCommitResponseSchema, TruthRecordKeySchema, TruthRecordMetadataSchema, TruthWriteRequestSchema, activityCursor, activitySourceBatchState, agentHomeProjectionCompletionKey, agentHomeProjectionRequestKey, approvalTimelineCursor, assertTaskAttemptListLimit, authenticateBearer, authenticateDeviceProof, authenticateHostedDeviceAssertion, createAuthPlane, createByokCloud, createHmacTokenSigner, createInMemoryBlobs, createInMemoryByokCloud, createInMemoryCloudStores, createWebCrypto, declares, extractBearerToken, fullCapabilityDeclaration, handleInboundEnvelope, isCloudError, isTruthCommitError, parseApprovalObservations, parseTimelineEvents, pendingApproval, projectTerminalResult, projectTimelineEvents, readAgentHomeProjectionStatus, recordAgentHomeProjectionCompletion, routeKey, tenantStoresFor, terminalReceiptKey, truthManifestMetadata, truthRecordMetadata, validateActivityAppend, validateApprovalTimelineAppend, validateApprovalTimelineResolve, verifyNonceSignature };
5328
+ export { ACCESS_TOKEN_TTL_SECONDS, AGENT_HOME_CONTRACT_CAPABILITY, APPROVAL_SUMMARY_MAX_BYTES, ActivityAppendRequestSchema, AllowAllRateLimiter, ApprovalObservationSchema, ApprovalTimelineEventSchema, BLOB_READ_ERROR_CODES, BLOB_URL_TTL_MS, BoardFeedClient, BoardFeedRetryableError, BoardFeedStoppedError, ByokCloudError, CLOUD_CAPABILITIES, CLOUD_ERROR_CODES, CLOUD_PORT_INTERFACES, CLOUD_PORT_METHODS, CLOUD_STORE_NAMES, CapabilitiesResponseSchema, CloudRouteRegistry, DEDUP_RING_CAPACITY, DEFAULT_ACTIVITY_BOUNDS, DEFAULT_ACTIVITY_CAPACITY, DEFAULT_ACTIVITY_MAX_BYTES, DEFAULT_ACTIVITY_MAX_EVENTS, DEFAULT_ACTIVITY_TTL_MS, DEFAULT_APPROVAL_TIMELINE_CAPACITY, DEFAULT_APPROVAL_TIMELINE_TTL_MS, DEFAULT_BOARD_CHANNEL_MAX_BYTES, DEFAULT_BOARD_PAGE_LIMIT, DEFAULT_BOARD_STREAM_HEARTBEAT_INTERVAL_MS, DEFAULT_BOARD_STREAM_QUERY_INTERVAL_MS, DEFAULT_BOARD_STREAM_RECONCILIATION_INTERVAL_MS, DEFAULT_BOARD_TITLE_MAX_BYTES, DEFAULT_DEVICE_PROOF_CLOCK_SKEW_MS, DEFAULT_DEVICE_PROOF_MAX_LIFETIME_MS, DEFAULT_EVENTS_PAGE_LIMIT, DEFAULT_LONG_POLL_HOLD_MS, DEFAULT_LONG_POLL_INTERVAL_MS, DEFAULT_MAX_BLOB_SIZE_BYTES, DEFAULT_MAX_TRUTH_REQUEST_BYTES, DEFAULT_PRESENCE_DETAIL_MAX_BYTES, DEFAULT_PRESENCE_MINIMUM_INTERVAL_MS, DEFAULT_PRESENCE_TTL_MS, DEFAULT_SKILL_PACK_PAGE_LIMIT, DEVICE_IDENTITY_PROOF_KEY_EPOCH, DEVICE_IDENTITY_PROOF_KEY_ID, InMemoryActivityStore, InMemoryAgentEgressStore, InMemoryAgentMemoryProjectionAuthorizer, InMemoryAgentMemoryProjectionStore, InMemoryBlobContentProxy, InMemoryCloudBlobStore, InMemoryDeviceDirectory, InMemoryInboundDedupStore, InMemoryNonceStore, InMemoryPairingCodeStore, InMemoryProofRequestReceiptStore, InMemoryRequestReceiptStore, InMemoryTaskAttemptStore, InMemoryTaskCancellationStore, MAX_DEVICE_PROOF_CLOCK_SKEW_MS, MAX_DEVICE_PROOF_HEADER_BYTES, MAX_DEVICE_PROOF_MAX_LIFETIME_MS, NONCE_TTL_MS, PAIRING_CODE_TTL_MS, ROUTE_CLASSES, ROUTE_METHODS, RecurringExecutionInputSchema, StaleApprovalError, SteerRejectedError, TASK_ATTEMPT_STATUSES, TRUTH_BATCH_MAX_RECORDS, TRUTH_INLINE_CONTENT_TYPE, TRUTH_LABEL_MAX_LENGTH, TRUTH_MANIFEST_MAX_LIMIT, TRUTH_RECORD_CAPABILITY, TRUTH_RECORD_KEY_MAX_LENGTH, TRUTH_REQUEST_ID_MAX_LENGTH, TimelineEventSchema, TruthBodyInputSchema, TruthCommitError, TruthCommitResponseSchema, TruthRecordKeySchema, TruthRecordMetadataSchema, TruthWriteRequestSchema, activityCursor, activitySourceBatchState, agentHomeProjectionCompletionKey, agentHomeProjectionRequestKey, approvalTimelineCursor, assertTaskAttemptListLimit, authenticateBearer, authenticateDeviceProof, authenticateHostedDeviceAssertion, createAuthPlane, createByokCloud, createHmacTokenSigner, createInMemoryBlobs, createInMemoryByokCloud, createInMemoryCloudStores, createWebCrypto, declares, extractBearerToken, fullCapabilityDeclaration, handleInboundEnvelope, isCloudError, isTruthCommitError, parseApprovalObservations, parseTimelineEvents, pendingApproval, projectTerminalResult, projectTimelineEvents, readAgentHomeProjectionStatus, recordAgentHomeProjectionCompletion, routeKey, tenantStoresFor, terminalReceiptKey, truthManifestMetadata, truthRecordMetadata, validateActivityAppend, validateApprovalTimelineAppend, validateApprovalTimelineResolve, verifyNonceSignature };
5220
5329
  //# sourceMappingURL=index.js.map
5221
5330
  //# sourceMappingURL=index.js.map