@byok-sdk/cloud 0.11.0 → 0.13.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,11 +1,73 @@
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, AgentHomeProjectionCompletionRequestSchema, decodeEnvelope, 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, STRICT_AGENT_ONLY_CAPABILITY, AGENT_CONTENT_ARTIFACT_READ_CAPABILITY, AGENT_CONTENT_TRANSCRIPT_READ_CAPABILITY, AGENT_CONTENT_WORKSPACE_READ_CAPABILITY, AGENT_HOME_PROJECTION_CAPABILITY, PairRequestSchema, PairResponseSchema, ChallengeRequestSchema, TokenRequestSchema, AgentMemoryProjectionCommitRequestSchema, agentMemoryProjectionBase64UrlByteLength, MessagesSendRequestSchema, PresencePublishRequestSchema, CreateBlobRequestSchema, byokBlobContentPath, AgentEgressAckPayloadSchema, AgentContentReceiptPayloadSchema } from '@byok-sdk/protocol';
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, 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, MessagesSendRequestSchema, PresencePublishRequestSchema, CreateBlobRequestSchema, byokBlobContentPath, AgentEgressAckPayloadSchema, AgentContentReceiptPayloadSchema } from '@byok-sdk/protocol';
4
4
  import { z } from 'zod';
5
5
  import { Hono } from 'hono';
6
6
 
7
7
  // src/index.ts
8
8
 
9
+ // src/approval-control.ts
10
+ var StaleApprovalError = class extends Error {
11
+ constructor(taskId, requestedApprovalId, currentApprovalId) {
12
+ super(
13
+ `cannot resolve approval ${requestedApprovalId} for task ${taskId}: the currently pending approval is ${currentApprovalId ?? "(none recorded)"}`
14
+ );
15
+ this.taskId = taskId;
16
+ this.requestedApprovalId = requestedApprovalId;
17
+ this.currentApprovalId = currentApprovalId;
18
+ this.name = "StaleApprovalError";
19
+ }
20
+ taskId;
21
+ requestedApprovalId;
22
+ currentApprovalId;
23
+ };
24
+ function pendingApproval(tail) {
25
+ if (tail === void 0) return void 0;
26
+ let pending;
27
+ for (const entry of tail.entries) {
28
+ const event = entry.event;
29
+ if (event.type === "approval_requested") {
30
+ pending = {
31
+ sourceEnvelopeId: entry.sourceEnvelopeId,
32
+ revision: entry.revision,
33
+ ...event.approvalId === void 0 ? {} : { approvalId: event.approvalId }
34
+ };
35
+ continue;
36
+ }
37
+ if (pending === void 0) continue;
38
+ if (pending.approvalId === void 0 || pending.approvalId === event.approvalId) {
39
+ pending = void 0;
40
+ }
41
+ }
42
+ return pending;
43
+ }
44
+
45
+ // src/steer-control.ts
46
+ function steerRejectionMessage(taskId, code, status, runtime) {
47
+ switch (code) {
48
+ case "task_terminal":
49
+ return `cannot steer task ${taskId}: task is already terminal (status ${status})`;
50
+ case "task_not_running":
51
+ return `cannot steer task ${taskId}: not running (status ${status})`;
52
+ case "steer_unsupported_runtime":
53
+ return `cannot steer task ${taskId}: claimed runtime ${runtime ?? "(unknown)"} does not support steering`;
54
+ }
55
+ }
56
+ var SteerRejectedError = class extends Error {
57
+ constructor(taskId, code, status, runtime) {
58
+ super(steerRejectionMessage(taskId, code, status, runtime));
59
+ this.taskId = taskId;
60
+ this.code = code;
61
+ this.status = status;
62
+ this.runtime = runtime;
63
+ this.name = "SteerRejectedError";
64
+ }
65
+ taskId;
66
+ code;
67
+ status;
68
+ runtime;
69
+ };
70
+
9
71
  // src/crypto/web-crypto.ts
10
72
  var PAIRING_CODE_ALPHABET = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789";
11
73
  var HEX_DIGITS = "0123456789abcdef";
@@ -182,10 +244,8 @@ function createAuthPlane(deps) {
182
244
  });
183
245
  },
184
246
  async redeemAndRegister(input) {
185
- const claims = await stores.pairingCodes.redeem(input.pairingCode);
186
- if (claims === void 0) return void 0;
187
- return stores.devices.register(claims.tenantId, {
188
- productId: claims.productId,
247
+ return stores.pairing.redeemAndRegister({
248
+ pairingCode: input.pairingCode,
189
249
  deviceId: `dev_${crypto2.randomUuid()}`,
190
250
  deviceName: input.deviceName,
191
251
  devicePublicKey: input.devicePublicKey,
@@ -202,11 +262,8 @@ function createAuthPlane(deps) {
202
262
  issueNonce(device) {
203
263
  return stores.nonces.issue(device.tenantId, device.deviceId);
204
264
  },
205
- validateNonce(device, nonce) {
206
- return stores.nonces.validate(device.tenantId, device.deviceId, nonce);
207
- },
208
- consumeNonce(device, nonce) {
209
- return stores.nonces.markUsed(device.tenantId, nonce);
265
+ consumeNonceIfValid(device, nonce) {
266
+ return stores.nonces.consumeIfValid(device.tenantId, device.deviceId, nonce);
210
267
  },
211
268
  verifySignature(device, nonce, signature) {
212
269
  return verifyNonceSignature(crypto2, device.devicePublicKey, nonce, signature);
@@ -326,6 +383,15 @@ var CLOUD_ERROR_CODES = {
326
383
  activity_batch_too_large: "activity_batch_too_large",
327
384
  /** Host control-plane task lookup is tenant-closed and found no task. */
328
385
  task_not_found: "task_not_found",
386
+ /**
387
+ * A host `approveTask`/`rejectTask` names a task with no approval to
388
+ * resolve: the durable timeline holds no unresolved `approval_requested`
389
+ * (including a tail that has expired or was never written), the attempt has
390
+ * already reached a terminal status, or no device ever claimed it so there
391
+ * is nothing to notify. Fail-closed by construction — cloud never invents a
392
+ * pending approval to make the call succeed.
393
+ */
394
+ task_not_awaiting_approval: "task_not_awaiting_approval",
329
395
  /** Durable target-device capability is absent, revoked, or unavailable. */
330
396
  agent_capability_missing: "agent_capability_missing",
331
397
  /** Inbound Agent identity did not exactly match the offered identity. */
@@ -392,6 +458,12 @@ var TimelineEventSchema = z.object({
392
458
  sourceEnvelopeId: SourceEnvelopeIdSchema,
393
459
  batchSeq: z.number().int().nonnegative(),
394
460
  eventIndex: z.number().int().nonnegative(),
461
+ /**
462
+ * The producer-declared loss attached to this source batch. It travels with
463
+ * every retained event so a replay includes it in the canonical batch
464
+ * binding rather than treating the aggregate as an incidental counter.
465
+ */
466
+ sourceDropped: z.number().int().nonnegative().optional(),
395
467
  receivedAt: z.iso.datetime(),
396
468
  event: AgentEventOrUnknownSchema
397
469
  });
@@ -425,11 +497,24 @@ function projectTimelineEvents(input, receivedAt) {
425
497
  sourceEnvelopeId: input.sourceEnvelopeId,
426
498
  batchSeq: input.batchSeq,
427
499
  eventIndex,
500
+ sourceDropped: input.dropped,
428
501
  receivedAt,
429
502
  event
430
503
  })
431
504
  );
432
505
  }
506
+ function activitySourceBatchState(entries, input) {
507
+ const existing = entries.filter((entry) => entry.sourceEnvelopeId === input.sourceEnvelopeId).sort((left, right) => left.eventIndex - right.eventIndex);
508
+ if (existing.length === 0) return "absent";
509
+ if (existing.length !== input.events.length) return "conflict";
510
+ for (const [eventIndex, event] of input.events.entries()) {
511
+ const stored = existing[eventIndex];
512
+ 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)) {
513
+ return "conflict";
514
+ }
515
+ }
516
+ return "same";
517
+ }
433
518
  function parseTimelineEvents(value) {
434
519
  return z.array(TimelineEventSchema).parse(value);
435
520
  }
@@ -474,6 +559,14 @@ function assertBoardLabels(channel, title, limits) {
474
559
  );
475
560
  }
476
561
  }
562
+ function assertTaskAttemptListLimit(limit) {
563
+ if (!Number.isSafeInteger(limit) || limit <= 0) {
564
+ throw new ByokCloudError(
565
+ "coordination_input_invalid",
566
+ `Task attempt list limit must be a positive integer; received ${String(limit)}.`
567
+ );
568
+ }
569
+ }
477
570
  function validateActivityEvents(events, dropped, bounds) {
478
571
  if (!Number.isSafeInteger(dropped) || dropped < 0) {
479
572
  throw new ByokCloudError(
@@ -525,6 +618,9 @@ async function authenticateBearer(header, deps) {
525
618
  const device = await deps.devices.get(tenantId(claims.tenantId), claims.deviceId);
526
619
  if (device === void 0 || device.revoked) return void 0;
527
620
  if (device.productId !== claims.productId) return void 0;
621
+ if (deps.instanceProductId !== void 0 && claims.productId !== deps.instanceProductId) {
622
+ return void 0;
623
+ }
528
624
  return {
529
625
  kind: "device",
530
626
  tenantId: device.tenantId,
@@ -541,6 +637,7 @@ function tenantStoresFor(principal, root) {
541
637
  mailbox: {
542
638
  append: (input) => core.mailbox.append(tenant, input),
543
639
  readAfter: (query) => core.mailbox.readAfter(tenant, query),
640
+ recordDelivery: (input) => core.mailbox.recordDelivery(tenant, input),
544
641
  advanceCursor: (input) => core.mailbox.advanceCursor(tenant, input),
545
642
  readCursor: (deviceId) => core.mailbox.readCursor(tenant, deviceId)
546
643
  },
@@ -578,8 +675,12 @@ function tenantStoresFor(principal, root) {
578
675
  tasks: {
579
676
  open: (input) => cloud.tasks.open(tenant, input),
580
677
  reserveAgentOffer: (input) => cloud.tasks.reserveAgentOffer(tenant, input),
678
+ reserveAgentMessage: (input) => cloud.tasks.reserveAgentMessage(tenant, input),
679
+ readAgentMessage: (input) => cloud.tasks.readAgentMessage(tenant, input),
680
+ finalizeAgentMessage: (input) => cloud.tasks.finalizeAgentMessage(tenant, input),
581
681
  get: (taskId) => cloud.tasks.get(tenant, taskId),
582
682
  getMany: (taskIds) => cloud.tasks.getMany(tenant, taskIds),
683
+ list: (query) => cloud.tasks.list(tenant, query),
583
684
  claim: (input) => cloud.tasks.claim(tenant, input),
584
685
  recordStatus: (input) => cloud.tasks.recordStatus(tenant, input)
585
686
  },
@@ -587,7 +688,8 @@ function tenantStoresFor(principal, root) {
587
688
  request: (input) => cloud.cancellations.request(tenant, input)
588
689
  },
589
690
  dedup: {
590
- checkAndRecord: (deviceId, envelopeId) => cloud.dedup.checkAndRecord(tenant, deviceId, envelopeId)
691
+ checkAndRecord: (deviceId, envelopeId) => cloud.dedup.checkAndRecord(tenant, deviceId, envelopeId),
692
+ checkAndRecordAgent: (deviceId, agentRef, envelopeId) => cloud.dedup.checkAndRecordAgent(tenant, deviceId, agentRef, envelopeId)
591
693
  },
592
694
  receipts: {
593
695
  record: (input) => cloud.receipts.record(tenant, input),
@@ -595,7 +697,7 @@ function tenantStoresFor(principal, root) {
595
697
  },
596
698
  egress: {
597
699
  record: (input) => cloud.egress.record(tenant, input),
598
- get: (deviceId, eventId) => cloud.egress.get(tenant, deviceId, eventId)
700
+ get: (deviceId, agentRef, eventId) => cloud.egress.get(tenant, deviceId, agentRef, eventId)
599
701
  },
600
702
  blobs: {
601
703
  createUpload: (reservation) => cloud.blobs.createUpload(tenant, reservation),
@@ -627,6 +729,7 @@ async function readBoundedJsonBody(c, maximum) {
627
729
  if (declaredLength !== void 0) {
628
730
  const parsedLength = parseContentLength(declaredLength);
629
731
  if (parsedLength !== void 0 && parsedLength > BigInt(maximum)) {
732
+ cancelBody(c.req.raw.body);
630
733
  return { body: void 0, tooLarge: true };
631
734
  }
632
735
  }
@@ -640,10 +743,7 @@ async function readBoundedJsonBody(c, maximum) {
640
743
  const { done, value } = await reader.read();
641
744
  if (done) break;
642
745
  if (value.byteLength > maximum - total) {
643
- try {
644
- await reader.cancel();
645
- } catch {
646
- }
746
+ cancelBody(reader);
647
747
  return { body: void 0, tooLarge: true };
648
748
  }
649
749
  if (value.byteLength === 0) continue;
@@ -663,6 +763,14 @@ async function readBoundedJsonBody(c, maximum) {
663
763
  }
664
764
  return { body: parseJsonBytes(bytes), tooLarge: false };
665
765
  }
766
+ function cancelBody(body) {
767
+ if (body === null) return;
768
+ try {
769
+ void Promise.resolve(body.cancel()).catch(() => {
770
+ });
771
+ } catch {
772
+ }
773
+ }
666
774
  function parseContentLength(value) {
667
775
  const normalized = value.trim();
668
776
  if (!/^\d+$/u.test(normalized)) return void 0;
@@ -781,11 +889,43 @@ function blobUploadContentHandler(deps) {
781
889
  if (!query.success || !await deps.contentProxy.verifySignedUrl(blobId, "put", query.data.sig, query.data.exp)) {
782
890
  return c.json({ error: "invalid or expired signature" }, 401);
783
891
  }
784
- const result = await deps.contentProxy.writeContent(blobId, new Uint8Array(await c.req.arrayBuffer()));
892
+ const expectedBytes = await deps.contentProxy.expectedUploadBytes(blobId);
893
+ if (expectedBytes === void 0) return c.json({ error: "blob not found" }, 404);
894
+ const absoluteCeiling = BigInt(deps.maxUploadBytes);
895
+ if (expectedBytes > absoluteCeiling) return c.json({ error: "upload exceeds size limit" }, 413);
896
+ const contentLength = c.req.header("content-length");
897
+ if (contentLength !== void 0 && /^\d+$/u.test(contentLength) && BigInt(contentLength) > expectedBytes) {
898
+ return c.json({ error: "upload exceeds declared size" }, 413);
899
+ }
900
+ const body = await readBoundedBody(c.req.raw.body, Number(expectedBytes));
901
+ if (body.tooLarge) return c.json({ error: "upload exceeds declared size" }, 413);
902
+ const result = await deps.contentProxy.writeContent(blobId, body.data);
785
903
  if (!result.ok) return c.json({ error: result.reason }, 422);
786
904
  return c.body(null, 204);
787
905
  };
788
906
  }
907
+ async function readBoundedBody(body, ceiling) {
908
+ if (body === null) return { tooLarge: false, data: new Uint8Array() };
909
+ const reader = body.getReader();
910
+ const data = new Uint8Array(ceiling);
911
+ let offset = 0;
912
+ try {
913
+ for (; ; ) {
914
+ const next = await reader.read();
915
+ if (next.done) break;
916
+ const chunk = next.value;
917
+ if (chunk.byteLength > ceiling - offset) {
918
+ void reader.cancel().catch(() => void 0);
919
+ return { tooLarge: true };
920
+ }
921
+ data.set(chunk, offset);
922
+ offset += chunk.byteLength;
923
+ }
924
+ } finally {
925
+ reader.releaseLock();
926
+ }
927
+ return { tooLarge: false, data: data.subarray(0, offset) };
928
+ }
789
929
  function blobDownloadContentHandler(deps) {
790
930
  return async (c) => {
791
931
  const blobId = c.req.param("id") ?? "";
@@ -830,9 +970,12 @@ function renderStorageError(c, error) {
830
970
  function capabilitiesHandler(deps) {
831
971
  return async (c) => c.json(deps.declaration, 200);
832
972
  }
973
+ var AUTH_JSON_BODY_MAX_BYTES = 16 * 1024;
833
974
  function pairHandler(deps) {
834
975
  return async (c) => {
835
- const parsed = PairRequestSchema.safeParse(await readJsonBody(c));
976
+ const body = await readBoundedJsonBody(c, AUTH_JSON_BODY_MAX_BYTES);
977
+ if (body.tooLarge) return c.json({ error: "request body too large" }, 413);
978
+ const parsed = PairRequestSchema.safeParse(body.body);
836
979
  if (!parsed.success) {
837
980
  return c.json(
838
981
  {
@@ -855,7 +998,9 @@ function pairHandler(deps) {
855
998
  }
856
999
  function challengeHandler(deps) {
857
1000
  return async (c) => {
858
- const parsed = ChallengeRequestSchema.safeParse(await readJsonBody(c));
1001
+ const body = await readBoundedJsonBody(c, AUTH_JSON_BODY_MAX_BYTES);
1002
+ if (body.tooLarge) return c.json({ error: "request body too large" }, 413);
1003
+ const parsed = ChallengeRequestSchema.safeParse(body.body);
859
1004
  if (!parsed.success) return c.json({ error: "deviceId is required" }, 400);
860
1005
  const device = await deps.auth.resolveDevice(parsed.data.deviceId);
861
1006
  if (device === void 0) return c.json({ error: "unknown or revoked device" }, 401);
@@ -865,18 +1010,19 @@ function challengeHandler(deps) {
865
1010
  }
866
1011
  function tokenHandler(deps) {
867
1012
  return async (c) => {
868
- const parsed = TokenRequestSchema.safeParse(await readJsonBody(c));
1013
+ const body = await readBoundedJsonBody(c, AUTH_JSON_BODY_MAX_BYTES);
1014
+ if (body.tooLarge) return c.json({ error: "request body too large" }, 413);
1015
+ const parsed = TokenRequestSchema.safeParse(body.body);
869
1016
  if (!parsed.success) return c.json({ error: "deviceId, nonce, and signature are required" }, 400);
870
1017
  const { deviceId, nonce, signature } = parsed.data;
871
1018
  const device = await deps.auth.resolveDevice(deviceId);
872
1019
  if (device === void 0) return c.json({ error: "unknown or revoked device" }, 401);
873
- if (!await deps.auth.validateNonce(device, nonce)) {
874
- return c.json({ error: "invalid, expired, or already-used nonce" }, 401);
875
- }
876
1020
  if (!await deps.auth.verifySignature(device, nonce, signature)) {
877
1021
  return c.json({ error: "invalid signature" }, 401);
878
1022
  }
879
- await deps.auth.consumeNonce(device, nonce);
1023
+ if (!await deps.auth.consumeNonceIfValid(device, nonce)) {
1024
+ return c.json({ error: "invalid, expired, or already-used nonce" }, 401);
1025
+ }
880
1026
  const { accessToken, expiresAt } = await deps.auth.mintAccessToken(device);
881
1027
  const response = { accessToken, expiresAt };
882
1028
  return c.json(response, 200);
@@ -884,11 +1030,20 @@ function tokenHandler(deps) {
884
1030
  }
885
1031
  var CLOUD_PROTOCOL_CAPABILITIES = [
886
1032
  "result-document",
1033
+ // The kernel's inbound handles `task.approval_resolved` on both the plain
1034
+ // and the approval-timeline paths (`packages/cloud/src/inbound.ts:576,610`).
1035
+ // Without this flag `TaskRunner.sendApprovalResolved` returns early and a
1036
+ // locally-resolved approval is never reported on the wire at all.
1037
+ "approval_resolved",
887
1038
  "agent-home-contract",
888
1039
  "agent-home-projection",
889
1040
  "agent-egress-policy",
890
1041
  "agent-egress-reliable-ack",
891
1042
  "agent-egress-fresh-session",
1043
+ // `handleAgentMessagePublish` accepts `agent.message_egress` gated on exactly
1044
+ // this capability (`packages/cloud/src/inbound.ts:124`) — the same inbound
1045
+ // family as the three egress flags above, omitted by the same drift.
1046
+ "agent-message-egress",
892
1047
  "agent-content-workspace-read",
893
1048
  "agent-content-transcript-read",
894
1049
  "agent-content-artifact-read"
@@ -905,12 +1060,21 @@ function eventsHandler(deps) {
905
1060
  let cursor = 0;
906
1061
  if (cursorRaw !== void 0) {
907
1062
  const parsedCursor = Number(cursorRaw);
908
- if (!Number.isInteger(parsedCursor) || parsedCursor < 0) return c.json({ error: "invalid cursor" }, 400);
1063
+ if (!Number.isSafeInteger(parsedCursor) || parsedCursor < 0) {
1064
+ return c.json({ error: "invalid cursor" }, 400);
1065
+ }
909
1066
  cursor = parsedCursor;
910
1067
  }
911
1068
  const acked = await stores.mailbox.readCursor(device.deviceId);
912
1069
  if (cursor > acked.ackedSeq) {
913
- await stores.mailbox.advanceCursor({ deviceId: device.deviceId, ackedSeq: cursor });
1070
+ try {
1071
+ await stores.mailbox.advanceCursor({ deviceId: device.deviceId, ackedSeq: cursor });
1072
+ } catch (caught) {
1073
+ if (isCoreConflictError(caught, "mailbox_cursor_ahead_of_delivery")) {
1074
+ return c.json({ error: "cursor exceeds delivered watermark" }, 409);
1075
+ }
1076
+ throw caught;
1077
+ }
914
1078
  }
915
1079
  const attempts = Math.max(1, Math.ceil(deps.longPollHoldMs / deps.longPollIntervalMs));
916
1080
  for (let attempt = 0; attempt < attempts; attempt += 1) {
@@ -921,6 +1085,9 @@ function eventsHandler(deps) {
921
1085
  afterSeq: scanCursor,
922
1086
  limit: deps.pageLimit
923
1087
  });
1088
+ if (cursor < page.recoverableFrom - 1) {
1089
+ return c.json({ error: "cursor_too_old", recoverableFrom: page.recoverableFrom }, 409);
1090
+ }
924
1091
  if (page.messages.length === 0) break;
925
1092
  const decoded = page.messages.map((message) => decodeEnvelope(message.body));
926
1093
  const offeredTaskIds = decoded.flatMap(
@@ -945,6 +1112,10 @@ function eventsHandler(deps) {
945
1112
  cursor: page.nextSeq,
946
1113
  capabilities: CLOUD_PROTOCOL_CAPABILITIES
947
1114
  };
1115
+ await stores.mailbox.recordDelivery({
1116
+ deviceId: device.deviceId,
1117
+ deliveredSeq: response2.cursor
1118
+ });
948
1119
  return c.json(response2, 200);
949
1120
  }
950
1121
  if (attempt < attempts - 1) await sleep(deps.longPollIntervalMs);
@@ -962,19 +1133,45 @@ var DEFAULT_APPROVAL_TIMELINE_TTL_MS = 10 * 60 * 1e3;
962
1133
  var APPROVAL_SUMMARY_MAX_BYTES = 16 * 1024;
963
1134
  var NonBlankIdSchema = z.string().max(200).regex(/\S/, "value must not be blank");
964
1135
  var TaskIdSchema = z.string().min(1).max(200);
965
- var ApprovalTimelineEventSchema = z.discriminatedUnion("type", [
966
- z.object({
967
- type: z.literal("approval_requested"),
968
- summary: z.string(),
969
- approvalId: NonBlankIdSchema.optional()
970
- }),
971
- z.object({
972
- type: z.literal("approval_resolved"),
973
- approvalId: NonBlankIdSchema,
974
- decision: z.enum(["approve", "reject"]),
975
- resolvedBy: z.enum(["local"]),
976
- at: z.iso.datetime({ offset: true })
977
- })
1136
+ var ApprovalRequestedEventSchema = z.object({
1137
+ type: z.literal("approval_requested"),
1138
+ summary: z.string(),
1139
+ approvalId: NonBlankIdSchema.optional()
1140
+ }).strict();
1141
+ var LocalApprovalResolutionEventSchema = z.object({
1142
+ type: z.literal("approval_resolved"),
1143
+ // Absent only when a daemon resolved the single outstanding request without
1144
+ // native approval identity. No id is synthesized.
1145
+ approvalId: NonBlankIdSchema.optional(),
1146
+ decision: z.enum(["approve", "reject"]),
1147
+ // A daemon's `task.approval_resolved` is pure wire observation; it cannot
1148
+ // declare a host command payload.
1149
+ resolvedBy: z.literal("local"),
1150
+ at: z.iso.datetime({ offset: true })
1151
+ }).strict();
1152
+ var HostApprovalResolutionEventSchema = z.object({
1153
+ type: z.literal("approval_resolved"),
1154
+ approvalId: NonBlankIdSchema.optional(),
1155
+ decision: z.literal("approve"),
1156
+ resolvedBy: z.literal("host"),
1157
+ at: z.iso.datetime({ offset: true })
1158
+ }).strict();
1159
+ var HostRejectResolutionEventSchema = z.object({
1160
+ type: z.literal("approval_resolved"),
1161
+ approvalId: NonBlankIdSchema.optional(),
1162
+ decision: z.literal("reject"),
1163
+ resolvedBy: z.literal("host"),
1164
+ // Required even when the host deliberately omitted a wire reason. `null`
1165
+ // records that complete payload, so a historical host reject with no field
1166
+ // cannot be mistaken for a recoverable executable command.
1167
+ reason: z.string().nullable(),
1168
+ at: z.iso.datetime({ offset: true })
1169
+ }).strict();
1170
+ var ApprovalTimelineEventSchema = z.union([
1171
+ ApprovalRequestedEventSchema,
1172
+ LocalApprovalResolutionEventSchema,
1173
+ HostApprovalResolutionEventSchema,
1174
+ HostRejectResolutionEventSchema
978
1175
  ]);
979
1176
  var ApprovalObservationSchema = z.object({
980
1177
  taskId: TaskIdSchema,
@@ -1017,6 +1214,20 @@ function validateApprovalTimelineAppend(input) {
1017
1214
  }
1018
1215
  return { capacity, ttlMs, event: parsed.data.event };
1019
1216
  }
1217
+ function validateApprovalTimelineResolve(input) {
1218
+ const validated = validateApprovalTimelineAppend(input);
1219
+ const expected = z.object({
1220
+ expectedSourceEnvelopeId: NonBlankIdSchema,
1221
+ expectedRevision: z.number().int().positive()
1222
+ }).safeParse(input);
1223
+ if (!expected.success || validated.event.type !== "approval_resolved" || validated.event.resolvedBy !== "host") {
1224
+ throw new ByokCloudError(
1225
+ "coordination_input_invalid",
1226
+ "Host approval resolution requires an exact pending source/revision and a host resolution event."
1227
+ );
1228
+ }
1229
+ return validated;
1230
+ }
1020
1231
  function approvalTimelineKey(tenant, taskId) {
1021
1232
  return tenantKey(tenant, taskId);
1022
1233
  }
@@ -1040,13 +1251,17 @@ async function projectTerminalToReview(board, taskId) {
1040
1251
  throw caught;
1041
1252
  }
1042
1253
  }
1254
+ function agentReliabilityKey(domain, deviceId, agentRef, id) {
1255
+ const exact = AgentRefSchema.parse(agentRef);
1256
+ return `${domain}:${JSON.stringify([deviceId, exact.agentId, exact.profileRevision, id])}`;
1257
+ }
1043
1258
 
1044
1259
  // src/inbound.ts
1045
- async function handleAgentMessagePublish(stores, deviceId, taskId, payload, consume) {
1260
+ async function handleAgentMessagePublish(stores, deviceId, taskId, payload, consume, alreadyDeduplicated) {
1046
1261
  const device = await stores.devices.get(deviceId);
1047
1262
  const attempt = await stores.tasks.get(taskId);
1048
1263
  const binding = await stores.receipts.get(`agent-message-offer:${deviceId}:${taskId}`);
1049
- if (device === void 0 || device.revoked || !hasCapabilities(device.capabilities, [AGENT_MESSAGE_EGRESS_CAPABILITY]) || attempt === void 0 || attempt.deviceId !== deviceId || binding === void 0 || !sameAgentRef(attempt.agentRef, payload.agentRef)) return { outcome: "rejected" };
1264
+ 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" };
1050
1265
  let frozen;
1051
1266
  try {
1052
1267
  frozen = JSON.parse(binding.body);
@@ -1054,19 +1269,38 @@ async function handleAgentMessagePublish(stores, deviceId, taskId, payload, cons
1054
1269
  return { outcome: "rejected" };
1055
1270
  }
1056
1271
  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" };
1057
- const taskMessageKey = `agent-message-task:${deviceId}:${taskId}`;
1058
- const taskMessageBody = JSON.stringify(payload);
1059
- const lockedTaskMessage = await stores.receipts.record({ key: taskMessageKey, body: taskMessageBody });
1060
- if (!lockedTaskMessage.created && lockedTaskMessage.receipt.body !== taskMessageBody) return { outcome: "rejected" };
1061
- const receiptKey2 = `agent-message:${deviceId}:${payload.messageId}`;
1062
- const previous = await stores.receipts.get(receiptKey2);
1063
- if (previous !== void 0) {
1064
- const decoded = JSON.parse(previous.body);
1065
- if (JSON.stringify(decoded.payload) !== JSON.stringify(payload)) return { outcome: "rejected" };
1066
- return { outcome: "duplicate", disposition: decoded.disposition };
1067
- }
1068
- const decision = consume === void 0 ? { outcome: "held", reasonCode: "consumer_unavailable" } : await consume({ tenant: stores.tenant, deviceId, taskId, context: frozen.context, payload });
1069
- const disposition = {
1272
+ const priorDisposition = await readAgentMessageDisposition(stores, deviceId, taskId, payload);
1273
+ if (priorDisposition !== void 0) return { outcome: "duplicate", disposition: priorDisposition };
1274
+ const payloadBody = JSON.stringify(payload);
1275
+ const reservation = await stores.tasks.reserveAgentMessage({
1276
+ taskId,
1277
+ deviceId,
1278
+ messageId: payload.messageId,
1279
+ payloadBody
1280
+ });
1281
+ if (reservation !== "reserved" && reservation !== "pending") return { outcome: "rejected" };
1282
+ let decision;
1283
+ if (consume === void 0) {
1284
+ if (reservation === "pending") {
1285
+ throw new Error("Agent message consumer is unavailable while reconciling an exact pending admission.");
1286
+ }
1287
+ decision = { outcome: "held", reasonCode: "consumer_unavailable" };
1288
+ } else {
1289
+ decision = await consume({ tenant: stores.tenant, deviceId, taskId, context: frozen.context, payload });
1290
+ }
1291
+ const disposition = agentMessageDisposition(payload, decision);
1292
+ const admission = await stores.tasks.finalizeAgentMessage({
1293
+ taskId,
1294
+ deviceId,
1295
+ messageId: payload.messageId,
1296
+ payloadBody,
1297
+ terminalBody: JSON.stringify({ payload, disposition })
1298
+ });
1299
+ const terminal = admission?.terminalBody === void 0 ? void 0 : parseAgentMessageDisposition(payload, admission.terminalBody);
1300
+ return terminal === void 0 ? { outcome: "rejected" } : { outcome: "accepted", disposition: terminal };
1301
+ }
1302
+ function agentMessageDisposition(payload, decision) {
1303
+ return {
1070
1304
  agentRef: payload.agentRef,
1071
1305
  sessionRef: payload.sessionRef,
1072
1306
  contract: payload.contract,
@@ -1077,9 +1311,23 @@ async function handleAgentMessagePublish(stores, deviceId, taskId, payload, cons
1077
1311
  receiptId: crypto.randomUUID(),
1078
1312
  ...decision.reasonCode === void 0 ? {} : { reasonCode: decision.reasonCode }
1079
1313
  };
1080
- const recorded = await stores.receipts.record({ key: receiptKey2, body: JSON.stringify({ payload, disposition }) });
1081
- if (!recorded.created && recorded.receipt.body !== JSON.stringify({ payload, disposition })) return { outcome: "rejected" };
1082
- return { outcome: "accepted", disposition };
1314
+ }
1315
+ async function readAgentMessageDisposition(stores, deviceId, taskId, payload) {
1316
+ const admission = await stores.tasks.readAgentMessage({
1317
+ taskId,
1318
+ deviceId,
1319
+ messageId: payload.messageId,
1320
+ payloadBody: JSON.stringify(payload)
1321
+ });
1322
+ return admission?.terminalBody === void 0 ? void 0 : parseAgentMessageDisposition(payload, admission.terminalBody);
1323
+ }
1324
+ function parseAgentMessageDisposition(payload, terminalBody) {
1325
+ try {
1326
+ const decoded = JSON.parse(terminalBody);
1327
+ return decoded.disposition !== void 0 && JSON.stringify(decoded.payload) === JSON.stringify(payload) ? decoded.disposition : void 0;
1328
+ } catch {
1329
+ return void 0;
1330
+ }
1083
1331
  }
1084
1332
  function terminalReceiptKey(taskId) {
1085
1333
  return `task:${taskId}:terminal`;
@@ -1103,17 +1351,17 @@ function contentReadCapability(surface) {
1103
1351
  function hasCapabilities(capabilities, required) {
1104
1352
  return capabilities !== void 0 && required.every((capability) => capabilities.includes(capability));
1105
1353
  }
1106
- function contentRequestReceiptKey(deviceId, requestId) {
1107
- return `agent-content-request:${deviceId}:${requestId}`;
1354
+ function contentRequestReceiptKey(deviceId, agentRef, requestId) {
1355
+ return agentReliabilityKey("agent-content-request", deviceId, agentRef, requestId);
1108
1356
  }
1109
- function contentReceiptKey(deviceId, requestId) {
1110
- return `agent-content:${deviceId}:${requestId}`;
1357
+ function contentReceiptKey(deviceId, agentRef, requestId) {
1358
+ return agentReliabilityKey("agent-content-receipt", deviceId, agentRef, requestId);
1111
1359
  }
1112
1360
  function matchesContentReadReceipt(request, receipt) {
1113
1361
  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;
1114
1362
  }
1115
- async function readContentRequest(stores, deviceId, requestId) {
1116
- const stored = await stores.receipts.get(contentRequestReceiptKey(deviceId, requestId));
1363
+ async function readContentRequest(stores, deviceId, agentRef, requestId) {
1364
+ const stored = await stores.receipts.get(contentRequestReceiptKey(deviceId, agentRef, requestId));
1117
1365
  if (stored === void 0) return void 0;
1118
1366
  try {
1119
1367
  return AgentContentReadPayloadSchema.parse(JSON.parse(stored.body));
@@ -1136,7 +1384,23 @@ function inboundAgentRef(envelope) {
1136
1384
  function agentRefEchoRequired(envelope) {
1137
1385
  return envelope.type === "task.claim" || envelope.type === "task.decline" || envelope.type === "task.complete" || envelope.type === "task.fail" || envelope.type === "task.cancelled";
1138
1386
  }
1139
- async function handleInboundEnvelope(stores, deviceId, envelope, activityBounds = DEFAULT_ACTIVITY_BOUNDS) {
1387
+ async function handleInboundEnvelope(stores, deviceId, envelope, activityBounds = DEFAULT_ACTIVITY_BOUNDS, agentMessageConsume, observer) {
1388
+ const outcome = await applyInboundGate(
1389
+ stores,
1390
+ deviceId,
1391
+ envelope,
1392
+ activityBounds,
1393
+ agentMessageConsume
1394
+ );
1395
+ if (outcome === "accepted" && observer !== void 0) {
1396
+ try {
1397
+ observer.onInboundCommitted({ tenantId: stores.tenant, deviceId, envelope, outcome });
1398
+ } catch {
1399
+ }
1400
+ }
1401
+ return outcome;
1402
+ }
1403
+ async function applyInboundGate(stores, deviceId, envelope, activityBounds, agentMessageConsume) {
1140
1404
  if (!await stores.rateLimiter.consume(deviceId)) return "rate_limited";
1141
1405
  if (envelope.type === "conn.hello") {
1142
1406
  if (envelope.payload.deviceId !== deviceId) return "rejected";
@@ -1145,10 +1409,21 @@ async function handleInboundEnvelope(stores, deviceId, envelope, activityBounds
1145
1409
  if (device === void 0 || device.revoked || device.productId !== envelope.payload.productId) {
1146
1410
  return "rejected";
1147
1411
  }
1148
- if (await stores.dedup.checkAndRecord(deviceId, envelope.id)) return "duplicate";
1149
- return await stores.devices.recordCapabilities({ capabilities: envelope.payload.capabilities }) === void 0 ? "rejected" : "accepted";
1412
+ if (await stores.devices.recordCapabilities({ capabilities: envelope.payload.capabilities }) === void 0) {
1413
+ return "rejected";
1414
+ }
1415
+ return completeInboundEnvelope(stores, deviceId, envelope.id, void 0);
1150
1416
  }
1151
1417
  if (!DAEMON_TO_SERVER_TYPES.includes(envelope.type)) return "rejected";
1418
+ if (envelope.type === "agent.message.publish") {
1419
+ const message = await handleAgentMessagePublish(
1420
+ stores,
1421
+ deviceId,
1422
+ envelope.task_id,
1423
+ envelope.payload,
1424
+ agentMessageConsume);
1425
+ return message.outcome === "rejected" ? "rejected" : completeInboundEnvelope(stores, deviceId, envelope.id, envelope.payload.agentRef);
1426
+ }
1152
1427
  if (envelope.type === "agent.egress.reliable") {
1153
1428
  const device = await stores.devices.get(deviceId);
1154
1429
  if (device === void 0 || device.revoked || !hasCapabilities(device.capabilities, [AGENT_EGRESS_POLICY_CAPABILITY, AGENT_EGRESS_RELIABLE_ACK_CAPABILITY])) {
@@ -1159,19 +1434,21 @@ async function handleInboundEnvelope(stores, deviceId, envelope, activityBounds
1159
1434
  payload: envelope.payload,
1160
1435
  receiptId: crypto.randomUUID()
1161
1436
  });
1162
- return outcome.created || sameReliableEgress(outcome.record.payload, envelope.payload) ? outcome.created ? "accepted" : "duplicate" : "rejected";
1437
+ if (!outcome.created && !sameReliableEgress(outcome.record.payload, envelope.payload)) return "rejected";
1438
+ return completeInboundEnvelope(stores, deviceId, envelope.id, envelope.payload.agentRef);
1163
1439
  }
1164
1440
  if (envelope.type === "agent.content.receipt") {
1165
1441
  const device = await stores.devices.get(deviceId);
1166
1442
  if (device === void 0 || device.revoked || envelope.payload.eventId !== envelope.payload.requestId || !hasCapabilities(device.capabilities, [contentReadCapability(envelope.payload.surface), AGENT_EGRESS_RELIABLE_ACK_CAPABILITY])) {
1167
1443
  return "rejected";
1168
1444
  }
1169
- const request = await readContentRequest(stores, deviceId, envelope.payload.requestId);
1445
+ const request = await readContentRequest(stores, deviceId, envelope.payload.agentRef, envelope.payload.requestId);
1170
1446
  if (request === void 0 || !matchesContentReadReceipt(request, envelope.payload)) return "rejected";
1171
- const key = contentReceiptKey(deviceId, envelope.payload.requestId);
1447
+ const key = contentReceiptKey(deviceId, envelope.payload.agentRef, envelope.payload.requestId);
1172
1448
  const body = JSON.stringify(envelope.payload);
1173
1449
  const receipt = await stores.receipts.record({ key, body });
1174
- return receipt.created ? "accepted" : receipt.receipt.body === body ? "duplicate" : "rejected";
1450
+ if (!receipt.created && receipt.receipt.body !== body) return "rejected";
1451
+ return completeInboundEnvelope(stores, deviceId, envelope.id, envelope.payload.agentRef);
1175
1452
  }
1176
1453
  const taskId = envelope.task_id;
1177
1454
  if (taskId === void 0) return "rejected";
@@ -1181,6 +1458,7 @@ async function handleInboundEnvelope(stores, deviceId, envelope, activityBounds
1181
1458
  if (attempt?.agentRef !== void 0 && agentRefEchoRequired(envelope) && !sameAgentRef(attempt.agentRef, inboundAgentRef(envelope))) {
1182
1459
  return "rejected";
1183
1460
  }
1461
+ if (terminalStatus(envelope) !== void 0 && attempt === void 0) return "rejected";
1184
1462
  if (envelope.type === "task.progress" && envelope.payload.events.length > 0) {
1185
1463
  try {
1186
1464
  validateActivityBatch(
@@ -1209,14 +1487,22 @@ async function handleInboundEnvelope(stores, deviceId, envelope, activityBounds
1209
1487
  throw caught;
1210
1488
  }
1211
1489
  }
1212
- if (await stores.dedup.checkAndRecord(deviceId, envelope.id)) return "duplicate";
1213
1490
  await applyLifecycle(stores, deviceId, taskId, envelope, activityBounds, attempt?.agentRef);
1214
- return "accepted";
1491
+ return completeInboundEnvelope(stores, deviceId, envelope.id, attempt?.agentRef);
1492
+ }
1493
+ async function completeInboundEnvelope(stores, deviceId, envelopeId, agentRef) {
1494
+ const duplicate = agentRef === void 0 ? await stores.dedup.checkAndRecord(deviceId, envelopeId) : await stores.dedup.checkAndRecordAgent(deviceId, agentRef, envelopeId);
1495
+ return duplicate ? "duplicate" : "accepted";
1215
1496
  }
1216
1497
  async function applyLifecycle(stores, deviceId, taskId, envelope, activityBounds, persistedAgentRef) {
1217
1498
  switch (envelope.type) {
1218
1499
  case "task.claim":
1219
- await stores.tasks.claim({ taskId, deviceId });
1500
+ await stores.tasks.claim({
1501
+ taskId,
1502
+ deviceId,
1503
+ ...envelope.payload.runtime === void 0 ? {} : { runtime: envelope.payload.runtime },
1504
+ ...envelope.payload.capabilities === void 0 ? {} : { capabilities: envelope.payload.capabilities }
1505
+ });
1220
1506
  return;
1221
1507
  case "task.started":
1222
1508
  await stores.tasks.recordStatus({
@@ -1226,11 +1512,7 @@ async function applyLifecycle(stores, deviceId, taskId, envelope, activityBounds
1226
1512
  });
1227
1513
  return;
1228
1514
  case "task.decline":
1229
- await stores.tasks.recordStatus({
1230
- taskId,
1231
- status: "failed",
1232
- ...persistedAgentRef === void 0 ? {} : { agentRef: persistedAgentRef }
1233
- });
1515
+ await recordTerminal(stores, taskId, envelope);
1234
1516
  return;
1235
1517
  case "task.progress":
1236
1518
  if (envelope.payload.events.length > 0) {
@@ -1254,13 +1536,13 @@ async function applyLifecycle(stores, deviceId, taskId, envelope, activityBounds
1254
1536
  await stores.approvals.append(approvalTimelineAppendInput(taskId, envelope));
1255
1537
  return;
1256
1538
  case "task.complete":
1257
- await recordTerminal(stores, taskId, envelope, "complete");
1539
+ await recordTerminal(stores, taskId, envelope);
1258
1540
  return;
1259
1541
  case "task.fail":
1260
- await recordTerminal(stores, taskId, envelope, "failed");
1542
+ await recordTerminal(stores, taskId, envelope);
1261
1543
  return;
1262
1544
  case "task.cancelled":
1263
- await recordTerminal(stores, taskId, envelope, "cancelled");
1545
+ await recordTerminal(stores, taskId, envelope);
1264
1546
  return;
1265
1547
  default:
1266
1548
  return;
@@ -1294,14 +1576,18 @@ function approvalTimelineAppendInput(taskId, envelope) {
1294
1576
  return void 0;
1295
1577
  }
1296
1578
  }
1297
- async function recordTerminal(stores, taskId, envelope, status) {
1298
- const attempt = await stores.tasks.get(taskId);
1299
- const { created } = await stores.receipts.record({
1579
+ async function recordTerminal(stores, taskId, envelope) {
1580
+ const receipt = await stores.receipts.record({
1300
1581
  key: terminalReceiptKey(taskId),
1301
1582
  body: encodeEnvelope(envelope)
1302
1583
  });
1584
+ const winner = terminalReceiptBinding(taskId, receipt.receipt.body);
1585
+ if (winner === void 0) {
1586
+ throw new Error(`terminal receipt for ${taskId} is not a canonical terminal binding`);
1587
+ }
1588
+ const attempt = await stores.tasks.get(taskId);
1303
1589
  if (attempt?.cancellation !== void 0) {
1304
- if (status === "cancelled") {
1590
+ if (winner.status === "cancelled") {
1305
1591
  await stores.tasks.recordStatus({
1306
1592
  taskId,
1307
1593
  status: "cancelled",
@@ -1310,48 +1596,72 @@ async function recordTerminal(stores, taskId, envelope, status) {
1310
1596
  }
1311
1597
  return;
1312
1598
  }
1313
- if (!created) return;
1314
- const cause = envelope.type === "task.fail" || envelope.type === "task.cancelled" ? envelope.payload.reason : void 0;
1315
1599
  const recordedAttempt = await stores.tasks.recordStatus({
1316
1600
  taskId,
1317
- status,
1601
+ status: winner.status,
1318
1602
  ...attempt?.agentRef === void 0 ? {} : { agentRef: attempt.agentRef },
1319
- ...cause === void 0 ? {} : { terminalCause: cause }
1603
+ ...winner.cause === void 0 ? {} : { terminalCause: winner.cause }
1320
1604
  });
1321
- if (recordedAttempt?.status !== status || recordedAttempt.cancellation !== void 0) return;
1605
+ if (recordedAttempt?.status !== winner.status || recordedAttempt.cancellation !== void 0) return;
1322
1606
  await projectTerminalToReview(stores.board, taskId);
1323
1607
  }
1608
+ function terminalStatus(envelope) {
1609
+ switch (envelope.type) {
1610
+ case "task.complete":
1611
+ return "complete";
1612
+ case "task.fail":
1613
+ case "task.decline":
1614
+ return "failed";
1615
+ case "task.cancelled":
1616
+ return "cancelled";
1617
+ default:
1618
+ return void 0;
1619
+ }
1620
+ }
1621
+ function terminalReceiptBinding(taskId, body) {
1622
+ let envelope;
1623
+ try {
1624
+ envelope = decodeEnvelope(body);
1625
+ } catch {
1626
+ return void 0;
1627
+ }
1628
+ const status = terminalStatus(envelope);
1629
+ if (status === void 0 || envelope.task_id !== taskId || encodeEnvelope(envelope) !== body) return void 0;
1630
+ const cause = envelope.type === "task.fail" || envelope.type === "task.cancelled" || envelope.type === "task.decline" ? envelope.payload.reason : void 0;
1631
+ return { status, ...cause === void 0 ? {} : { cause } };
1632
+ }
1324
1633
 
1325
1634
  // src/handlers/messages.ts
1635
+ var MESSAGES_JSON_BODY_MAX_BYTES = 2 * 1024 * 1024;
1326
1636
  function messagesHandler(deps) {
1327
1637
  return async (c) => {
1328
1638
  const authenticated = await authenticateDevice(c, deps);
1329
1639
  if (authenticated === void 0) return c.json({ error: "unauthorized" }, 401);
1330
1640
  const { device, stores } = authenticated;
1331
- const parsed = MessagesSendRequestSchema.safeParse(await readJsonBody(c));
1641
+ const body = await readBoundedJsonBody(c, MESSAGES_JSON_BODY_MAX_BYTES);
1642
+ if (body.tooLarge) return c.json({ error: "request body too large" }, 413);
1643
+ const parsed = MessagesSendRequestSchema.safeParse(body.body);
1332
1644
  if (!parsed.success) return c.json({ error: "messages must be an array of envelopes" }, 400);
1333
1645
  let accepted = 0;
1334
1646
  let rejected = 0;
1335
1647
  for (const envelope of parsed.data.messages) {
1336
- const messageResult = envelope.type === "agent.message.publish" ? await handleAgentMessagePublish(
1337
- stores,
1338
- device.deviceId,
1339
- envelope.task_id,
1340
- envelope.payload,
1341
- deps.agentMessage?.consume
1342
- ) : void 0;
1343
- const outcome = messageResult?.outcome ?? await handleInboundEnvelope(
1648
+ const outcome = await handleInboundEnvelope(
1344
1649
  stores,
1345
1650
  device.deviceId,
1346
1651
  envelope,
1347
- deps.activityBounds
1652
+ deps.activityBounds,
1653
+ envelope.type === "agent.message.publish" ? deps.agentMessage?.consume : void 0,
1654
+ deps.observer
1348
1655
  );
1349
1656
  if (outcome === "rate_limited") return c.json({ error: "rate limit exceeded" }, 429);
1350
- if (envelope.type === "agent.message.publish" && messageResult?.disposition !== void 0) {
1351
- await deps.appendAgentMessageDisposition(stores, device.deviceId, envelope.task_id, messageResult.disposition);
1657
+ if (envelope.type === "agent.message.publish") {
1658
+ const disposition = await readAgentMessageDisposition(stores, device.deviceId, envelope.task_id, envelope.payload);
1659
+ if (disposition !== void 0) {
1660
+ await deps.appendAgentMessageDisposition(stores, device.deviceId, envelope.task_id, disposition);
1661
+ }
1352
1662
  }
1353
1663
  if (envelope.type === "agent.egress.reliable" && (outcome === "accepted" || outcome === "duplicate")) {
1354
- const record = await stores.egress.get(device.deviceId, envelope.payload.eventId);
1664
+ const record = await stores.egress.get(device.deviceId, envelope.payload.agentRef, envelope.payload.eventId);
1355
1665
  if (record === void 0) {
1356
1666
  throw new Error(`Accepted reliable egress ${envelope.payload.eventId} has no durable receipt record.`);
1357
1667
  }
@@ -1961,7 +2271,7 @@ function truthGetHandler(deps) {
1961
2271
  }
1962
2272
  function truthPutHandler(deps) {
1963
2273
  return async (c) => {
1964
- const body = await readBoundedBody(c, deps.maxRequestBytes);
2274
+ const body = await readBoundedBody2(c, deps.maxRequestBytes);
1965
2275
  if (body === "too_large") return c.json({ error: "truth request body too large" }, 413);
1966
2276
  if (body === void 0) return c.json({ error: "invalid truth request body" }, 400);
1967
2277
  const authenticated = await authenticateTruthRequest(c, deps, {
@@ -2093,7 +2403,7 @@ function decodeProofHeader(value) {
2093
2403
  return void 0;
2094
2404
  }
2095
2405
  }
2096
- async function readBoundedBody(c, maximum) {
2406
+ async function readBoundedBody2(c, maximum) {
2097
2407
  const length = c.req.header("content-length");
2098
2408
  if (length !== void 0) {
2099
2409
  const parsed = Number(length);
@@ -2170,11 +2480,11 @@ var CloudRouteRegistry = class {
2170
2480
  return this.#app.fetch;
2171
2481
  }
2172
2482
  };
2173
- function agentHomeProjectionRequestKey(deviceId, requestId) {
2174
- return `agent-home-projection:v1:${deviceId}:${requestId}:request`;
2483
+ function agentHomeProjectionRequestKey(deviceId, agentRef, requestId) {
2484
+ return agentReliabilityKey("agent-home-projection-request", deviceId, agentRef, requestId);
2175
2485
  }
2176
- function agentHomeProjectionCompletionKey(deviceId, requestId) {
2177
- return `agent-home-projection:v1:${deviceId}:${requestId}:completion`;
2486
+ function agentHomeProjectionCompletionKey(deviceId, agentRef, requestId) {
2487
+ return agentReliabilityKey("agent-home-projection-completion", deviceId, agentRef, requestId);
2178
2488
  }
2179
2489
  function sameAgentHomeProjectionRequest(expected, actual) {
2180
2490
  return expected.requestId === actual.requestId && expected.agentRef.agentId === actual.agentRef.agentId && expected.agentRef.profileRevision === actual.agentRef.profileRevision && expected.projectionHash === actual.projectionHash && JSON.stringify(expected.projection) === JSON.stringify(actual.projection);
@@ -2208,7 +2518,7 @@ function parseCompletionBody(body) {
2208
2518
  }
2209
2519
  }
2210
2520
  async function readAgentHomeProjectionStatus(receipts, tenant, deviceId, input) {
2211
- const storedRequest = await receipts.get(agentHomeProjectionRequestKey(deviceId, input.requestId));
2521
+ const storedRequest = await receipts.get(agentHomeProjectionRequestKey(deviceId, input.agentRef, input.requestId));
2212
2522
  if (storedRequest === void 0) return void 0;
2213
2523
  const request = parseRequestBody(storedRequest.body);
2214
2524
  if (!statusInputMatchesAgentHomeProjection(request, input)) {
@@ -2217,7 +2527,7 @@ async function readAgentHomeProjectionStatus(receipts, tenant, deviceId, input)
2217
2527
  `Agent-home projection request ${input.requestId} does not match its immutable desired fact.`
2218
2528
  );
2219
2529
  }
2220
- const storedCompletion = await receipts.get(agentHomeProjectionCompletionKey(deviceId, input.requestId));
2530
+ const storedCompletion = await receipts.get(agentHomeProjectionCompletionKey(deviceId, request.agentRef, input.requestId));
2221
2531
  if (storedCompletion === void 0) {
2222
2532
  return {
2223
2533
  tenantId: tenant,
@@ -2247,7 +2557,7 @@ async function readAgentHomeProjectionStatus(receipts, tenant, deviceId, input)
2247
2557
  }
2248
2558
  async function recordAgentHomeProjectionCompletion(receipts, tenant, deviceId, receiptInput) {
2249
2559
  const receipt = AgentHomeProjectionCompletionRequestSchema.parse(receiptInput);
2250
- const storedRequest = await receipts.get(agentHomeProjectionRequestKey(deviceId, receipt.requestId));
2560
+ const storedRequest = await receipts.get(agentHomeProjectionRequestKey(deviceId, receipt.agentRef, receipt.requestId));
2251
2561
  if (storedRequest === void 0) {
2252
2562
  throw new ByokCloudError(
2253
2563
  "agent_home_projection_request_not_found",
@@ -2263,7 +2573,7 @@ async function recordAgentHomeProjectionCompletion(receipts, tenant, deviceId, r
2263
2573
  }
2264
2574
  const body = JSON.stringify(receipt);
2265
2575
  const storedCompletion = await receipts.record({
2266
- key: agentHomeProjectionCompletionKey(deviceId, receipt.requestId),
2576
+ key: agentHomeProjectionCompletionKey(deviceId, request.agentRef, receipt.requestId),
2267
2577
  body
2268
2578
  });
2269
2579
  if (!storedCompletion.created && storedCompletion.receipt.body !== body) {
@@ -2317,6 +2627,21 @@ function projectTerminalResult(taskId, receipt) {
2317
2627
  ...envelope.payload.usage !== void 0 ? { usage: envelope.payload.usage } : {},
2318
2628
  recordedAt: receipt.recordedAt
2319
2629
  };
2630
+ // `task.decline` is the pre-claim terminal (§3.2, `Offered -> Failed`).
2631
+ // It projects onto the same `failed` state as `task.fail` — there is no
2632
+ // separate declined state — and carries the device's own `retryable`
2633
+ // verbatim, exactly like `task.fail` does; this projection never decides
2634
+ // that a decline is retryable on the device's behalf.
2635
+ case "task.decline":
2636
+ return {
2637
+ taskId,
2638
+ state: "failed",
2639
+ ...envelope.payload.agentRef === void 0 ? {} : { agentRef: envelope.payload.agentRef },
2640
+ reason: envelope.payload.reason,
2641
+ terminalCause: envelope.payload.reason,
2642
+ ...envelope.payload.retryable !== void 0 ? { retryable: envelope.payload.retryable } : {},
2643
+ recordedAt: receipt.recordedAt
2644
+ };
2320
2645
  case "task.cancelled":
2321
2646
  return {
2322
2647
  taskId,
@@ -2341,9 +2666,25 @@ var DEFAULT_LONG_POLL_HOLD_MS = 5e4;
2341
2666
  var DEFAULT_LONG_POLL_INTERVAL_MS = 250;
2342
2667
  var DEFAULT_EVENTS_PAGE_LIMIT = 50;
2343
2668
  var AGENT_HOME_CONTRACT_CAPABILITY = "agent-home-contract";
2669
+ function isTerminalAttemptStatus(status) {
2670
+ return status === "complete" || status === "failed" || status === "cancelled";
2671
+ }
2344
2672
  function sameAgentRef2(expected, actual) {
2345
2673
  return actual?.agentId === expected.agentId && actual.profileRevision === expected.profileRevision;
2346
2674
  }
2675
+ function uuidFromSha256(value) {
2676
+ const match = /^sha256:([0-9a-f]{64})$/.exec(value);
2677
+ if (match === null) throw new Error("CloudCrypto.sha256 must return canonical sha256 hex");
2678
+ const digest = match[1];
2679
+ if (digest === void 0) throw new Error("CloudCrypto.sha256 must return canonical sha256 hex");
2680
+ const hex = digest.slice(0, 32).split("");
2681
+ hex[12] = "8";
2682
+ hex[16] = ["8", "9", "a", "b"][Number.parseInt(hex[16], 16) & 3];
2683
+ return `${hex.slice(0, 8).join("")}-${hex.slice(8, 12).join("")}-${hex.slice(12, 16).join("")}-${hex.slice(16, 20).join("")}-${hex.slice(20).join("")}`;
2684
+ }
2685
+ function sameOfferEnvelope(expected, actual) {
2686
+ return expected.id === actual.id && expected.type === actual.type && expected.task_id === actual.task_id && JSON.stringify(expected.payload) === JSON.stringify(actual.payload);
2687
+ }
2347
2688
  function createByokCloud(options) {
2348
2689
  const declaration = parseDeclaration(options.capabilities);
2349
2690
  assertNoOverDeclaration(
@@ -2365,7 +2706,11 @@ function createByokCloud(options) {
2365
2706
  });
2366
2707
  const deviceRouteDeps = {
2367
2708
  root,
2368
- bearer: { tokenSigner: options.tokenSigner, devices: options.cloud.devices }
2709
+ bearer: {
2710
+ tokenSigner: options.tokenSigner,
2711
+ devices: options.cloud.devices,
2712
+ ...options.instanceProductId === void 0 ? {} : { instanceProductId: options.instanceProductId }
2713
+ }
2369
2714
  };
2370
2715
  const registry = new CloudRouteRegistry();
2371
2716
  const activityBounds = {
@@ -2410,7 +2755,8 @@ function createByokCloud(options) {
2410
2755
  appendReliableEgressAck: enqueueReliableEgressAck,
2411
2756
  appendContentReceiptAck: enqueueContentReceiptAck,
2412
2757
  agentMessage: options.agentMessage,
2413
- appendAgentMessageDisposition: enqueueAgentMessageDisposition
2758
+ appendAgentMessageDisposition: enqueueAgentMessageDisposition,
2759
+ observer: options.observer
2414
2760
  })
2415
2761
  );
2416
2762
  }
@@ -2522,7 +2868,10 @@ function createByokCloud(options) {
2522
2868
  }
2523
2869
  const contentProxy = options.blobContentProxy;
2524
2870
  if (contentProxy !== void 0 && declares(declaration, CLOUD_CAPABILITIES.blobsContentProxy)) {
2525
- const contentDeps = { contentProxy };
2871
+ const contentDeps = {
2872
+ contentProxy,
2873
+ maxUploadBytes: options.maxBlobSizeBytes ?? DEFAULT_MAX_BLOB_SIZE_BYTES
2874
+ };
2526
2875
  registry.register(
2527
2876
  { method: "PUT", path: BYOK_BLOB_CONTENT_ROUTE, class: "presigned" },
2528
2877
  blobUploadContentHandler(contentDeps)
@@ -2539,56 +2888,60 @@ function createByokCloud(options) {
2539
2888
  async function enqueueTaskEnvelope(tenant, deviceId, requestedTaskId, agentRef, buildEnvelope, beforeMailbox) {
2540
2889
  const stores = tenantStoresFor(controlPlane(tenant), root);
2541
2890
  const taskId = requestedTaskId ?? `task_${options.crypto.randomUuid()}`;
2542
- const messageId = options.crypto.randomUuid();
2891
+ const messageId = uuidFromSha256(
2892
+ await options.crypto.sha256(
2893
+ new TextEncoder().encode(
2894
+ JSON.stringify({ domain: "byok:task-offer", tenant, taskId, deviceId, agentRef })
2895
+ )
2896
+ )
2897
+ );
2543
2898
  const reservation = agentRef === void 0 ? void 0 : await stores.tasks.reserveAgentOffer({ taskId, deviceId, agentRef });
2544
- const openedBeforeAppend = reservation?.attempt;
2545
- if (agentRef !== void 0 && (openedBeforeAppend === void 0 || openedBeforeAppend.deviceId !== deviceId || openedBeforeAppend.agentRef === void 0 || !sameAgentRef2(agentRef, openedBeforeAppend.agentRef))) {
2899
+ const openedBeforeAppend = reservation?.attempt ?? (agentRef === void 0 ? await stores.tasks.open({ taskId, deviceId }) : void 0);
2900
+ if (openedBeforeAppend === void 0 || openedBeforeAppend.deviceId !== deviceId || (agentRef === void 0 ? openedBeforeAppend.agentRef !== void 0 : openedBeforeAppend.agentRef === void 0 || !sameAgentRef2(agentRef, openedBeforeAppend.agentRef))) {
2546
2901
  throw new ByokCloudError(
2547
- "agent_ref_mismatch",
2548
- `Task ${taskId} already has a different durable Agent identity or target device.`
2902
+ agentRef === void 0 ? "coordination_input_invalid" : "agent_ref_mismatch",
2903
+ `Task ${taskId} already has a different durable target or Agent identity.`
2549
2904
  );
2550
2905
  }
2551
- if (reservation !== void 0 && !reservation.created) {
2906
+ const offerReceiptKey = `task-offer:${messageId}`;
2907
+ const deliveryReceiptKey = `task-offer-delivered:${messageId}`;
2908
+ if (agentRef !== void 0 && reservation?.created === false && await stores.receipts.get(deliveryReceiptKey) !== void 0) {
2552
2909
  throw new ByokCloudError(
2553
2910
  "agent_task_already_exists",
2554
2911
  `Task ${taskId} already has a durable Agent attempt and cannot be enqueued again.`
2555
2912
  );
2556
2913
  }
2557
- let message;
2558
- try {
2559
- await beforeMailbox?.(stores, taskId);
2560
- message = await stores.mailbox.append({
2561
- deviceId,
2562
- messageId,
2563
- materialize: async (seq) => {
2564
- const envelope2 = buildEnvelope(taskId, seq, messageId);
2565
- const body = encodeEnvelope(envelope2);
2566
- const bytes = new TextEncoder().encode(body);
2567
- return {
2568
- body,
2569
- bodyHash: contentHash(await options.crypto.sha256(bytes)),
2570
- byteSize: BigInt(bytes.length)
2571
- };
2572
- }
2573
- });
2574
- } catch (error) {
2575
- if (openedBeforeAppend !== void 0) {
2576
- try {
2577
- await stores.tasks.recordStatus({
2578
- taskId,
2579
- status: "failed",
2580
- agentRef,
2581
- terminalCause: "mailbox append failed before Agent offer delivery"
2582
- });
2583
- } catch (cleanupError) {
2584
- throw new AggregateError(
2585
- [error, cleanupError],
2586
- `Agent task ${taskId} could not be delivered or durably closed`
2587
- );
2588
- }
2589
- }
2590
- throw error;
2914
+ const proposed = buildEnvelope(taskId, 0, messageId);
2915
+ const proposedBody = JSON.stringify({
2916
+ deviceId,
2917
+ type: proposed.type,
2918
+ payload: proposed.payload
2919
+ });
2920
+ const recorded = await stores.receipts.record({
2921
+ key: offerReceiptKey,
2922
+ body: proposedBody
2923
+ });
2924
+ if (!recorded.created && recorded.receipt.body !== proposedBody) {
2925
+ throw new ByokCloudError(
2926
+ "coordination_input_invalid",
2927
+ `Task ${taskId} already has a different executable offer body.`
2928
+ );
2591
2929
  }
2930
+ await beforeMailbox?.(stores, taskId);
2931
+ const message = await stores.mailbox.append({
2932
+ deviceId,
2933
+ messageId,
2934
+ materialize: async (seq) => {
2935
+ const envelope2 = buildEnvelope(taskId, seq, messageId);
2936
+ const body = encodeEnvelope(envelope2);
2937
+ const bytes = new TextEncoder().encode(body);
2938
+ return {
2939
+ body,
2940
+ bodyHash: contentHash(await options.crypto.sha256(bytes)),
2941
+ byteSize: BigInt(bytes.length)
2942
+ };
2943
+ }
2944
+ });
2592
2945
  const envelope = decodeEnvelope(message.body);
2593
2946
  if (envelope.seq !== message.seq) {
2594
2947
  throw new ByokCloudError(
@@ -2596,11 +2949,22 @@ function createByokCloud(options) {
2596
2949
  `Mailbox stored offer seq ${String(envelope.seq)} at row ${message.seq}; the daemon's redelivery cursor would be wrong.`
2597
2950
  );
2598
2951
  }
2599
- const attempt = openedBeforeAppend ?? await stores.tasks.open({
2600
- taskId,
2601
- deviceId
2602
- });
2603
- return { taskId, seq: message.seq, envelope, attempt };
2952
+ if (!sameOfferEnvelope(buildEnvelope(taskId, message.seq, messageId), envelope)) {
2953
+ throw new ByokCloudError(
2954
+ "coordination_input_invalid",
2955
+ `Task ${taskId} already has a different executable offer body.`
2956
+ );
2957
+ }
2958
+ if (agentRef !== void 0) {
2959
+ const delivered = await stores.receipts.record({ key: deliveryReceiptKey, body: messageId });
2960
+ if (reservation?.created === false && !delivered.created) {
2961
+ throw new ByokCloudError(
2962
+ "agent_task_already_exists",
2963
+ `Task ${taskId} already has a durable Agent attempt and cannot be enqueued again.`
2964
+ );
2965
+ }
2966
+ }
2967
+ return { taskId, seq: message.seq, envelope, attempt: openedBeforeAppend };
2604
2968
  }
2605
2969
  async function assertAgentCapabilities(tenant, deviceId, required) {
2606
2970
  const device = await options.cloud.devices.get(tenant, deviceId);
@@ -2655,6 +3019,236 @@ function createByokCloud(options) {
2655
3019
  }
2656
3020
  return { seq: message.seq, envelope };
2657
3021
  }
3022
+ async function agentControlEnvelopeId(domain, tenant, deviceId, agentRef, logicalRequestId) {
3023
+ return uuidFromSha256(
3024
+ await options.crypto.sha256(
3025
+ new TextEncoder().encode(
3026
+ JSON.stringify({ domain, tenant, deviceId, agentRef, logicalRequestId })
3027
+ )
3028
+ )
3029
+ );
3030
+ }
3031
+ async function approvalDecisionSource(tenant, taskId, attempt, pending, decision) {
3032
+ const agent = attempt.agentRef;
3033
+ return uuidFromSha256(
3034
+ await options.crypto.sha256(
3035
+ new TextEncoder().encode(
3036
+ JSON.stringify({
3037
+ domain: "byok:approval-decision",
3038
+ tenant,
3039
+ taskId,
3040
+ deviceId: attempt.deviceId,
3041
+ ownerDeviceId: attempt.ownerDeviceId,
3042
+ agentRef: agent,
3043
+ requestSourceEnvelopeId: pending.sourceEnvelopeId,
3044
+ requestRevision: pending.revision,
3045
+ decision
3046
+ })
3047
+ )
3048
+ )
3049
+ );
3050
+ }
3051
+ function latestApprovalRequest(tail) {
3052
+ if (tail === void 0) return void 0;
3053
+ for (let index = tail.entries.length - 1; index >= 0; index -= 1) {
3054
+ const entry = tail.entries[index];
3055
+ if (entry.event.type !== "approval_requested") continue;
3056
+ return {
3057
+ sourceEnvelopeId: entry.sourceEnvelopeId,
3058
+ revision: entry.revision,
3059
+ ...entry.event.approvalId === void 0 ? {} : { approvalId: entry.event.approvalId }
3060
+ };
3061
+ }
3062
+ return void 0;
3063
+ }
3064
+ async function recordedHostDecision(tenant, taskId, attempt, tail, request) {
3065
+ for (const decision of ["approve", "reject"]) {
3066
+ const sourceEnvelopeId = await approvalDecisionSource(tenant, taskId, attempt, request, decision);
3067
+ const index = tail?.entries.findIndex((candidate) => candidate.sourceEnvelopeId === sourceEnvelopeId) ?? -1;
3068
+ const entry = index < 0 ? void 0 : tail.entries[index];
3069
+ if (entry?.event.type !== "approval_resolved" || entry.event.resolvedBy !== "host") continue;
3070
+ const laterResolution = tail.entries.slice(index + 1).some(
3071
+ (candidate) => candidate.event.type === "approval_resolved" && (request.approvalId === void 0 || candidate.event.approvalId === request.approvalId)
3072
+ );
3073
+ if (!laterResolution) {
3074
+ if (entry.event.decision === "reject") {
3075
+ return {
3076
+ decision: "reject",
3077
+ ...entry.event.approvalId === void 0 ? {} : { approvalId: entry.event.approvalId },
3078
+ reason: entry.event.reason
3079
+ };
3080
+ }
3081
+ return {
3082
+ decision: "approve",
3083
+ ...entry.event.approvalId === void 0 ? {} : { approvalId: entry.event.approvalId }
3084
+ };
3085
+ }
3086
+ }
3087
+ return void 0;
3088
+ }
3089
+ async function resolveApproval(tenant, taskId, decision, opts) {
3090
+ const stores = tenantStoresFor(controlPlane(tenant), root);
3091
+ const attempt = await stores.tasks.get(taskId);
3092
+ if (attempt === void 0) {
3093
+ throw new ByokCloudError("task_not_found", `Task ${taskId} was not found for this tenant.`);
3094
+ }
3095
+ if (isTerminalAttemptStatus(attempt.status)) {
3096
+ throw new ByokCloudError(
3097
+ "task_not_awaiting_approval",
3098
+ `Task ${taskId} already reached ${attempt.status}; there is no approval left to ${decision}.`
3099
+ );
3100
+ }
3101
+ const tail = await stores.approvals.read(taskId);
3102
+ const pending = pendingApproval(tail);
3103
+ const latestRequest = latestApprovalRequest(tail);
3104
+ if (pending === void 0) {
3105
+ if (latestRequest !== void 0) {
3106
+ const recorded = await recordedHostDecision(tenant, taskId, attempt, tail, latestRequest);
3107
+ if (recorded !== void 0) {
3108
+ if (recorded.decision !== decision) {
3109
+ throw new ByokCloudError(
3110
+ "coordination_input_invalid",
3111
+ `Task ${taskId} already has the opposite host approval decision.`
3112
+ );
3113
+ }
3114
+ if (recorded.decision === "reject" && recorded.reason !== (opts?.reason ?? null)) {
3115
+ throw new ByokCloudError(
3116
+ "coordination_input_invalid",
3117
+ `Task ${taskId} already has a host rejection with a different immutable reason.`
3118
+ );
3119
+ }
3120
+ const ownerDeviceId2 = attempt.ownerDeviceId;
3121
+ if (ownerDeviceId2 === void 0) {
3122
+ throw new ByokCloudError(
3123
+ "task_not_awaiting_approval",
3124
+ `Task ${taskId} has no owning device to receive its recorded ${decision}.`
3125
+ );
3126
+ }
3127
+ const messageId2 = await approvalDecisionSource(tenant, taskId, attempt, latestRequest, decision);
3128
+ const targeting2 = recorded.approvalId === void 0 ? {} : { approvalId: recorded.approvalId };
3129
+ const recordedReason = recorded.decision === "reject" ? recorded.reason : void 0;
3130
+ return enqueueAgentControlEnvelope(
3131
+ tenant,
3132
+ ownerDeviceId2,
3133
+ messageId2,
3134
+ (seq) => decision === "approve" ? createEnvelope("task.approve", targeting2, { id: messageId2, taskId, seq }) : createEnvelope(
3135
+ "task.reject",
3136
+ { ...targeting2, ...recordedReason === null || recordedReason === void 0 ? {} : { reason: recordedReason } },
3137
+ { id: messageId2, taskId, seq }
3138
+ )
3139
+ );
3140
+ }
3141
+ }
3142
+ throw new ByokCloudError(
3143
+ "task_not_awaiting_approval",
3144
+ `Task ${taskId} has no pending approval to ${decision}.`
3145
+ );
3146
+ }
3147
+ if (opts?.approvalId !== void 0 && pending.approvalId !== void 0 && opts.approvalId !== pending.approvalId) {
3148
+ throw new StaleApprovalError(taskId, opts.approvalId, pending.approvalId);
3149
+ }
3150
+ const ownerDeviceId = attempt.ownerDeviceId;
3151
+ if (ownerDeviceId === void 0) {
3152
+ throw new ByokCloudError(
3153
+ "task_not_awaiting_approval",
3154
+ `Task ${taskId} has no owning device; no runtime is paused on an approval to ${decision}.`
3155
+ );
3156
+ }
3157
+ const approvalId = pending.approvalId;
3158
+ const messageId = await approvalDecisionSource(tenant, taskId, attempt, pending, decision);
3159
+ const event = decision === "reject" ? {
3160
+ type: "approval_resolved",
3161
+ ...approvalId === void 0 ? {} : { approvalId },
3162
+ decision: "reject",
3163
+ resolvedBy: "host",
3164
+ reason: opts?.reason ?? null,
3165
+ at: (/* @__PURE__ */ new Date()).toISOString()
3166
+ } : {
3167
+ type: "approval_resolved",
3168
+ ...approvalId === void 0 ? {} : { approvalId },
3169
+ decision: "approve",
3170
+ resolvedBy: "host",
3171
+ at: (/* @__PURE__ */ new Date()).toISOString()
3172
+ };
3173
+ const resolved = await root.cloud.approvals.resolvePending(tenant, {
3174
+ taskId,
3175
+ sourceEnvelopeId: messageId,
3176
+ expectedSourceEnvelopeId: pending.sourceEnvelopeId,
3177
+ expectedRevision: pending.revision,
3178
+ event
3179
+ });
3180
+ let deliveredApprovalId = approvalId;
3181
+ let deliveredReason = decision === "reject" ? opts?.reason ?? null : void 0;
3182
+ if (resolved.status === "conflict" || resolved.status === "superseded" || resolved.status === "absent") {
3183
+ const currentTail = resolved.tail ?? await stores.approvals.read(taskId);
3184
+ const currentPending = pendingApproval(currentTail);
3185
+ const recorded = await recordedHostDecision(tenant, taskId, attempt, currentTail, pending);
3186
+ if (recorded?.decision === decision) {
3187
+ deliveredApprovalId = recorded.approvalId;
3188
+ if (recorded.decision === "reject") {
3189
+ if (recorded.reason !== deliveredReason) {
3190
+ throw new ByokCloudError(
3191
+ "coordination_input_invalid",
3192
+ `Task ${taskId} already has a host rejection with a different immutable reason.`
3193
+ );
3194
+ }
3195
+ deliveredReason = recorded.reason;
3196
+ }
3197
+ } else if (recorded !== void 0) {
3198
+ throw new ByokCloudError(
3199
+ "coordination_input_invalid",
3200
+ `Task ${taskId} already has the opposite host approval decision.`
3201
+ );
3202
+ } else if (currentPending !== void 0 && opts?.approvalId !== void 0 && currentPending.approvalId !== void 0 && currentPending.approvalId !== opts.approvalId) {
3203
+ throw new StaleApprovalError(taskId, opts.approvalId, currentPending.approvalId);
3204
+ } else {
3205
+ throw new ByokCloudError(
3206
+ "task_not_awaiting_approval",
3207
+ `Task ${taskId} no longer has the pending approval to ${decision}.`
3208
+ );
3209
+ }
3210
+ }
3211
+ const targeting = deliveredApprovalId === void 0 ? {} : { approvalId: deliveredApprovalId };
3212
+ return enqueueAgentControlEnvelope(
3213
+ tenant,
3214
+ ownerDeviceId,
3215
+ messageId,
3216
+ (seq) => decision === "approve" ? createEnvelope("task.approve", targeting, { id: messageId, taskId, seq }) : createEnvelope(
3217
+ "task.reject",
3218
+ { ...targeting, ...deliveredReason === null || deliveredReason === void 0 ? {} : { reason: deliveredReason } },
3219
+ { id: messageId, taskId, seq }
3220
+ )
3221
+ );
3222
+ }
3223
+ async function steerTask(tenant, taskId, payload) {
3224
+ const stores = tenantStoresFor(controlPlane(tenant), root);
3225
+ const attempt = await stores.tasks.get(taskId);
3226
+ if (attempt === void 0) {
3227
+ throw new ByokCloudError("task_not_found", `Task ${taskId} was not found for this tenant.`);
3228
+ }
3229
+ if (isTerminalAttemptStatus(attempt.status)) {
3230
+ throw new SteerRejectedError(taskId, "task_terminal", attempt.status, attempt.claimedRuntime);
3231
+ }
3232
+ const ownerDeviceId = attempt.ownerDeviceId;
3233
+ if (attempt.status !== "running" || ownerDeviceId === void 0) {
3234
+ throw new SteerRejectedError(taskId, "task_not_running", attempt.status, attempt.claimedRuntime);
3235
+ }
3236
+ if (attempt.claimedRuntimeCapabilities?.steer !== true) {
3237
+ throw new SteerRejectedError(
3238
+ taskId,
3239
+ "steer_unsupported_runtime",
3240
+ attempt.status,
3241
+ attempt.claimedRuntime
3242
+ );
3243
+ }
3244
+ const messageId = options.crypto.randomUuid();
3245
+ return enqueueAgentControlEnvelope(
3246
+ tenant,
3247
+ ownerDeviceId,
3248
+ messageId,
3249
+ (seq) => createEnvelope("task.steer", { text: payload.text }, { id: messageId, taskId, seq })
3250
+ );
3251
+ }
2658
3252
  async function getAgentHomeProjectionStatus(tenant, deviceId, input) {
2659
3253
  return readAgentHomeProjectionStatus(
2660
3254
  tenantStoresFor(controlPlane(tenant), root).receipts,
@@ -2928,7 +3522,7 @@ function createByokCloud(options) {
2928
3522
  ]);
2929
3523
  const stores = tenantStoresFor(controlPlane(tenant), root);
2930
3524
  const persisted = await stores.receipts.record({
2931
- key: `agent-content-request:${deviceId}:${payload.requestId}`,
3525
+ key: agentReliabilityKey("agent-content-request", deviceId, payload.agentRef, payload.requestId),
2932
3526
  body: JSON.stringify(payload)
2933
3527
  });
2934
3528
  if (!persisted.created && persisted.receipt.body !== JSON.stringify(payload)) {
@@ -2937,11 +3531,18 @@ function createByokCloud(options) {
2937
3531
  `Request ${payload.requestId} already exists with a different immutable content-read body.`
2938
3532
  );
2939
3533
  }
3534
+ const messageId = await agentControlEnvelopeId(
3535
+ "byok:agent-content-read",
3536
+ tenant,
3537
+ deviceId,
3538
+ payload.agentRef,
3539
+ payload.requestId
3540
+ );
2940
3541
  const control = await enqueueAgentControlEnvelope(
2941
3542
  tenant,
2942
3543
  deviceId,
2943
- payload.requestId,
2944
- (seq) => createEnvelope("agent.content.read", payload, { id: payload.requestId, seq })
3544
+ messageId,
3545
+ (seq) => createEnvelope("agent.content.read", payload, { id: messageId, seq })
2945
3546
  );
2946
3547
  if (control.envelope.type !== "agent.content.read" || JSON.stringify(control.envelope.payload) !== JSON.stringify(payload)) {
2947
3548
  throw new ByokCloudError(
@@ -2960,7 +3561,7 @@ function createByokCloud(options) {
2960
3561
  const stores = tenantStoresFor(controlPlane(tenant), root);
2961
3562
  const requestBody = JSON.stringify(payload);
2962
3563
  const persisted = await stores.receipts.record({
2963
- key: agentHomeProjectionRequestKey(deviceId, payload.requestId),
3564
+ key: agentHomeProjectionRequestKey(deviceId, payload.agentRef, payload.requestId),
2964
3565
  body: requestBody
2965
3566
  });
2966
3567
  const persistedPayload = AgentHomeProjectionPayloadSchema.parse(JSON.parse(persisted.receipt.body));
@@ -2970,11 +3571,18 @@ function createByokCloud(options) {
2970
3571
  `Agent-home projection request ${payload.requestId} already exists with a different immutable desired body.`
2971
3572
  );
2972
3573
  }
3574
+ const messageId = await agentControlEnvelopeId(
3575
+ "byok:agent-home-projection",
3576
+ tenant,
3577
+ deviceId,
3578
+ payload.agentRef,
3579
+ payload.requestId
3580
+ );
2973
3581
  const control = await enqueueAgentControlEnvelope(
2974
3582
  tenant,
2975
3583
  deviceId,
2976
- payload.requestId,
2977
- (seq) => createEnvelope("agent.home.projection", payload, { id: payload.requestId, seq })
3584
+ messageId,
3585
+ (seq) => createEnvelope("agent.home.projection", payload, { id: messageId, seq })
2978
3586
  );
2979
3587
  if (control.envelope.type !== "agent.home.projection" || !sameAgentHomeProjectionRequest(payload, control.envelope.payload)) {
2980
3588
  throw new ByokCloudError(
@@ -2998,6 +3606,13 @@ function createByokCloud(options) {
2998
3606
  getAgentHomeProjectionStatus,
2999
3607
  completeAgentHomeProjection,
3000
3608
  eraseAgentMemoryProjection,
3609
+ approveTask(tenant, taskId, opts) {
3610
+ return resolveApproval(tenant, taskId, "approve", opts);
3611
+ },
3612
+ rejectTask(tenant, taskId, opts) {
3613
+ return resolveApproval(tenant, taskId, "reject", opts);
3614
+ },
3615
+ steerTask,
3001
3616
  async cancelTask(tenant, taskId, reason) {
3002
3617
  const stores = tenantStoresFor(controlPlane(tenant), root);
3003
3618
  const proposedMessageId = options.crypto.randomUuid();
@@ -3037,11 +3652,14 @@ function createByokCloud(options) {
3037
3652
  readTaskAttempt(tenant, taskId) {
3038
3653
  return tenantStoresFor(controlPlane(tenant), root).tasks.get(taskId);
3039
3654
  },
3655
+ listTaskAttempts(tenant, query) {
3656
+ return tenantStoresFor(controlPlane(tenant), root).tasks.list(query);
3657
+ },
3040
3658
  readTerminalReceipt(tenant, taskId) {
3041
3659
  return tenantStoresFor(controlPlane(tenant), root).receipts.get(terminalReceiptKey(taskId));
3042
3660
  },
3043
- readAgentEgress(tenant, deviceId, eventId) {
3044
- return tenantStoresFor(controlPlane(tenant), root).egress.get(deviceId, eventId);
3661
+ readAgentEgress(tenant, deviceId, agentRef, eventId) {
3662
+ return tenantStoresFor(controlPlane(tenant), root).egress.get(deviceId, agentRef, eventId);
3045
3663
  },
3046
3664
  async readTaskResult(tenant, taskId) {
3047
3665
  const stores = tenantStoresFor(controlPlane(tenant), root);
@@ -3256,6 +3874,9 @@ var InMemoryBlobContentProxy = class {
3256
3874
  const expected = await this.#registry.computeSig(blobId, action, exp);
3257
3875
  return this.#registry.crypto.timingSafeEqual(expected, sig);
3258
3876
  }
3877
+ async expectedUploadBytes(blobId) {
3878
+ return this.#registry.blobs.get(blobId)?.byteSize;
3879
+ }
3259
3880
  async writeContent(blobId, data) {
3260
3881
  const record = this.#registry.blobs.get(blobId);
3261
3882
  if (record === void 0) return { ok: false, reason: "unknown blobId" };
@@ -3425,7 +4046,15 @@ var InMemoryInboundDedupStore = class {
3425
4046
  this.#capacity = capacity;
3426
4047
  }
3427
4048
  async checkAndRecord(tenant, deviceId, envelopeId) {
3428
- const key = tenantKey(tenant, deviceId);
4049
+ return this.#checkAndRecord(tenantKey(tenant, deviceId), envelopeId);
4050
+ }
4051
+ async checkAndRecordAgent(tenant, deviceId, agentRef, envelopeId) {
4052
+ return this.#checkAndRecord(
4053
+ tenantKey(tenant, agentReliabilityKey("inbound-dedup", deviceId, agentRef, "")),
4054
+ envelopeId
4055
+ );
4056
+ }
4057
+ #checkAndRecord(key, envelopeId) {
3429
4058
  let ring = this.#rings.get(key);
3430
4059
  if (ring === void 0) {
3431
4060
  ring = /* @__PURE__ */ new Set();
@@ -3469,27 +4098,38 @@ var InMemoryNonceStore = class {
3469
4098
  });
3470
4099
  return nonce;
3471
4100
  }
3472
- async validate(tenant, deviceId, nonce) {
4101
+ async consumeIfValid(tenant, deviceId, nonce) {
3473
4102
  const record = this.#nonces.get(nonce);
3474
4103
  if (record === void 0) return false;
3475
4104
  if (record.used) return false;
3476
4105
  if (record.owner !== tenantKey(tenant, deviceId)) return false;
3477
- return this.#clock.now().getTime() <= record.expiresAtMs;
3478
- }
3479
- async markUsed(tenant, nonce) {
3480
- const record = this.#nonces.get(nonce);
3481
- if (record === void 0) return;
3482
- if (!record.owner.startsWith(tenantKey(tenant, ""))) return;
4106
+ if (this.#clock.now().getTime() > record.expiresAtMs) return false;
3483
4107
  record.used = true;
4108
+ return true;
3484
4109
  }
3485
4110
  };
3486
4111
 
3487
4112
  // src/stores/in-memory/pairing-codes.ts
4113
+ function bindingOf(input) {
4114
+ return {
4115
+ deviceName: input.deviceName,
4116
+ devicePublicKey: input.devicePublicKey,
4117
+ proofKeyId: input.proofKeyId,
4118
+ proofKeyEpoch: input.proofKeyEpoch,
4119
+ ...input.machineId === void 0 ? {} : { machineId: input.machineId }
4120
+ };
4121
+ }
4122
+ function sameBinding(left, right) {
4123
+ return left.deviceName === right.deviceName && left.devicePublicKey === right.devicePublicKey && left.proofKeyId === right.proofKeyId && left.proofKeyEpoch === right.proofKeyEpoch && left.machineId === right.machineId;
4124
+ }
3488
4125
  var InMemoryPairingCodeStore = class {
3489
4126
  #codes = /* @__PURE__ */ new Map();
4127
+ #enrollmentTails = /* @__PURE__ */ new Map();
3490
4128
  #clock;
3491
- constructor(clock) {
4129
+ #devices;
4130
+ constructor(clock, devices) {
3492
4131
  this.#clock = clock;
4132
+ this.#devices = devices;
3493
4133
  }
3494
4134
  async issue(tenant, input) {
3495
4135
  this.#codes.set(input.code, {
@@ -3499,13 +4139,42 @@ var InMemoryPairingCodeStore = class {
3499
4139
  });
3500
4140
  return { code: input.code, expiresAt: input.expiresAt };
3501
4141
  }
3502
- async redeem(code) {
3503
- const record = this.#codes.get(code);
3504
- if (record === void 0) return void 0;
3505
- if (record.used) return void 0;
3506
- if (this.#clock.now().getTime() > record.expiresAtMs) return void 0;
3507
- record.used = true;
3508
- return record.claims;
4142
+ async redeemAndRegister(input) {
4143
+ const previous = this.#enrollmentTails.get(input.pairingCode) ?? Promise.resolve();
4144
+ let release;
4145
+ const current = new Promise((resolve) => {
4146
+ release = resolve;
4147
+ });
4148
+ this.#enrollmentTails.set(input.pairingCode, current);
4149
+ await previous;
4150
+ try {
4151
+ const record = this.#codes.get(input.pairingCode);
4152
+ if (record === void 0) return void 0;
4153
+ if (record.used) {
4154
+ const completion = record.completion;
4155
+ if (completion === void 0 || !sameBinding(completion.binding, bindingOf(input))) return void 0;
4156
+ const active = await this.#devices.get(completion.device.tenantId, completion.device.deviceId);
4157
+ return active === void 0 ? void 0 : completion.device;
4158
+ }
4159
+ if (this.#clock.now().getTime() > record.expiresAtMs) return void 0;
4160
+ const device = await this.#devices.register(record.claims.tenantId, {
4161
+ productId: record.claims.productId,
4162
+ deviceId: input.deviceId,
4163
+ deviceName: input.deviceName,
4164
+ devicePublicKey: input.devicePublicKey,
4165
+ proofKeyId: input.proofKeyId,
4166
+ proofKeyEpoch: input.proofKeyEpoch,
4167
+ ...input.machineId === void 0 ? {} : { machineId: input.machineId }
4168
+ });
4169
+ record.used = true;
4170
+ record.completion = { device, binding: bindingOf(input) };
4171
+ return device;
4172
+ } finally {
4173
+ release();
4174
+ if (this.#enrollmentTails.get(input.pairingCode) === current) {
4175
+ this.#enrollmentTails.delete(input.pairingCode);
4176
+ }
4177
+ }
3509
4178
  }
3510
4179
  };
3511
4180
  var InMemoryRequestReceiptStore = class {
@@ -3553,6 +4222,36 @@ var InMemoryProofRequestReceiptStore = class {
3553
4222
  return this.#receipts.get(tenantKey(tenant, deviceId, requestId));
3554
4223
  }
3555
4224
  };
4225
+
4226
+ // src/stores/ports.ts
4227
+ var TASK_ATTEMPT_STATUSES = [
4228
+ "offered",
4229
+ "claimed",
4230
+ "running",
4231
+ "cancel_requested",
4232
+ "complete",
4233
+ "failed",
4234
+ "cancelled"
4235
+ ];
4236
+ var BLOB_READ_ERROR_CODES = ["blob_upstream_unavailable", "blob_upstream_stream_interrupted"];
4237
+ var CLOUD_STORE_NAMES = [
4238
+ "activity",
4239
+ "approvals",
4240
+ "devices",
4241
+ "pairingCodes",
4242
+ "pairing",
4243
+ "nonces",
4244
+ "dedup",
4245
+ "tasks",
4246
+ "cancellations",
4247
+ "receipts",
4248
+ "egress",
4249
+ "proofReceipts",
4250
+ "blobs",
4251
+ "rateLimiter"
4252
+ ];
4253
+
4254
+ // src/stores/in-memory/task-attempts.ts
3556
4255
  function sameAgentRef3(left, right) {
3557
4256
  return left?.agentId === right?.agentId && left?.profileRevision === right?.profileRevision;
3558
4257
  }
@@ -3595,6 +4294,41 @@ var InMemoryTaskAttemptStore = class {
3595
4294
  return { attempt, created: true };
3596
4295
  });
3597
4296
  }
4297
+ async reserveAgentMessage(tenant, input) {
4298
+ const key = tenantKey(tenant, input.taskId);
4299
+ return this.#state.mutate(key, () => {
4300
+ const existing = this.#state.messageAdmissions.get(key);
4301
+ if (existing !== void 0) {
4302
+ return existing.deviceId === input.deviceId && existing.messageId === input.messageId && existing.payloadBody === input.payloadBody ? "pending" : "rejected";
4303
+ }
4304
+ const attempt = this.#state.attempts.get(key);
4305
+ if (attempt === void 0 || attempt.deviceId !== input.deviceId || attempt.cancellation !== void 0 || !["offered", "claimed", "running"].includes(attempt.status)) return "rejected";
4306
+ this.#state.messageAdmissions.set(key, {
4307
+ deviceId: input.deviceId,
4308
+ messageId: input.messageId,
4309
+ payloadBody: input.payloadBody
4310
+ });
4311
+ return "reserved";
4312
+ });
4313
+ }
4314
+ async readAgentMessage(tenant, input) {
4315
+ const admission = this.#state.messageAdmissions.get(tenantKey(tenant, input.taskId));
4316
+ return admission !== void 0 && admission.deviceId === input.deviceId && admission.messageId === input.messageId && admission.payloadBody === input.payloadBody ? admission : void 0;
4317
+ }
4318
+ async finalizeAgentMessage(tenant, input) {
4319
+ const key = tenantKey(tenant, input.taskId);
4320
+ return this.#state.mutate(key, () => this.#terminalize(key, input));
4321
+ }
4322
+ #terminalize(key, input) {
4323
+ const admission = this.#state.messageAdmissions.get(key);
4324
+ if (admission === void 0 || admission.deviceId !== input.deviceId || admission.messageId !== input.messageId || admission.payloadBody !== input.payloadBody) {
4325
+ return void 0;
4326
+ }
4327
+ if (admission.terminalBody !== void 0) return admission;
4328
+ const terminal = { ...admission, terminalBody: input.terminalBody };
4329
+ this.#state.messageAdmissions.set(key, terminal);
4330
+ return terminal;
4331
+ }
3598
4332
  async get(tenant, taskId) {
3599
4333
  return this.#state.attempts.get(tenantKey(tenant, taskId));
3600
4334
  }
@@ -3604,6 +4338,18 @@ var InMemoryTaskAttemptStore = class {
3604
4338
  return attempt === void 0 ? [] : [attempt];
3605
4339
  });
3606
4340
  }
4341
+ async list(tenant, query) {
4342
+ assertTaskAttemptListLimit(query.limit);
4343
+ const cursor = query.cursor;
4344
+ const matches = [...this.#state.attempts.values()].filter((attempt) => attempt.tenantId === tenant).filter((attempt) => cursor === void 0 || attempt.taskId > cursor).sort((left, right) => left.taskId < right.taskId ? -1 : left.taskId > right.taskId ? 1 : 0);
4345
+ const attempts = matches.slice(0, query.limit);
4346
+ const hasMore = matches.length > attempts.length;
4347
+ const last = attempts.at(-1);
4348
+ return {
4349
+ attempts,
4350
+ ...hasMore && last !== void 0 ? { nextCursor: last.taskId } : {}
4351
+ };
4352
+ }
3607
4353
  async claim(tenant, input) {
3608
4354
  const key = tenantKey(tenant, input.taskId);
3609
4355
  return this.#state.mutate(key, () => {
@@ -3613,6 +4359,8 @@ var InMemoryTaskAttemptStore = class {
3613
4359
  const claimed = {
3614
4360
  ...existing,
3615
4361
  ownerDeviceId: input.deviceId,
4362
+ ...input.runtime === void 0 ? {} : { claimedRuntime: input.runtime },
4363
+ ...input.capabilities === void 0 ? {} : { claimedRuntimeCapabilities: { ...input.capabilities } },
3616
4364
  status: "claimed",
3617
4365
  updatedAt: this.#now()
3618
4366
  };
@@ -3647,6 +4395,7 @@ var InMemoryTaskAttemptStore = class {
3647
4395
  };
3648
4396
  var InMemoryTaskAttemptState = class {
3649
4397
  attempts = /* @__PURE__ */ new Map();
4398
+ messageAdmissions = /* @__PURE__ */ new Map();
3650
4399
  #clock;
3651
4400
  #mutationTails = /* @__PURE__ */ new Map();
3652
4401
  constructor(clock) {
@@ -3734,6 +4483,14 @@ var InMemoryActivityStore = class {
3734
4483
  const key = activityTailKey(tenant, input.taskId);
3735
4484
  const existing = this.#tails.get(key);
3736
4485
  const live = existing !== void 0 && receivedAt < existing.expiresAt ? existing : void 0;
4486
+ const sourceState = activitySourceBatchState(live?.entries ?? [], input);
4487
+ if (sourceState === "same") return live;
4488
+ if (sourceState === "conflict") {
4489
+ throw new ByokCloudError(
4490
+ "coordination_input_invalid",
4491
+ `Activity source envelope ${input.sourceEnvelopeId} already belongs to another canonical batch.`
4492
+ );
4493
+ }
3737
4494
  const incoming = projectTimelineEvents(input, receivedAt);
3738
4495
  for (const next of incoming) {
3739
4496
  const collision = live?.entries.find(
@@ -3825,6 +4582,46 @@ var InMemoryApprovalTimelineStore = class {
3825
4582
  this.#tails.set(key, tail);
3826
4583
  return tail;
3827
4584
  }
4585
+ async resolvePending(tenant, input) {
4586
+ const { capacity, ttlMs, event } = validateApprovalTimelineResolve(input);
4587
+ const now = this.clock.now();
4588
+ const receivedAt = now.toISOString();
4589
+ const key = approvalTimelineKey(tenant, input.taskId);
4590
+ const existing = this.#tails.get(key);
4591
+ const live = existing !== void 0 && receivedAt < existing.expiresAt ? existing : void 0;
4592
+ if (live === void 0) return { status: "absent" };
4593
+ const duplicate = live.entries.find((entry) => entry.sourceEnvelopeId === input.sourceEnvelopeId);
4594
+ if (duplicate !== void 0) {
4595
+ if (JSON.stringify(duplicate.event) !== JSON.stringify(event)) return { status: "conflict", tail: live };
4596
+ return { status: "replayed", tail: live };
4597
+ }
4598
+ const pending = pendingObservation(live.entries);
4599
+ if (pending === void 0) return { status: "absent", tail: live };
4600
+ if (pending.sourceEnvelopeId !== input.expectedSourceEnvelopeId || pending.revision !== input.expectedRevision) {
4601
+ return { status: "superseded", tail: live };
4602
+ }
4603
+ const revision = (live.cursor ?? 0) + 1;
4604
+ const observation = ApprovalObservationSchema.parse({
4605
+ taskId: input.taskId,
4606
+ sourceEnvelopeId: input.sourceEnvelopeId,
4607
+ revision,
4608
+ receivedAt,
4609
+ event
4610
+ });
4611
+ const allEntries = [...live.entries, observation];
4612
+ const evicted = Math.max(allEntries.length - capacity, 0);
4613
+ const tail = {
4614
+ tenantId: tenant,
4615
+ taskId: input.taskId,
4616
+ entries: allEntries.slice(evicted),
4617
+ cursor: revision,
4618
+ dropped: live.dropped + evicted,
4619
+ capacity,
4620
+ expiresAt: new Date(now.getTime() + ttlMs).toISOString()
4621
+ };
4622
+ this.#tails.set(key, tail);
4623
+ return { status: "applied", tail };
4624
+ }
3828
4625
  async read(tenant, taskId) {
3829
4626
  const key = approvalTimelineKey(tenant, taskId);
3830
4627
  const tail = this.#tails.get(key);
@@ -3836,6 +4633,19 @@ var InMemoryApprovalTimelineStore = class {
3836
4633
  return tail;
3837
4634
  }
3838
4635
  };
4636
+ function pendingObservation(entries) {
4637
+ let pending;
4638
+ for (const entry of entries) {
4639
+ if (entry.event.type === "approval_requested") {
4640
+ pending = entry;
4641
+ continue;
4642
+ }
4643
+ if (pending !== void 0 && (pending.event.approvalId === void 0 || pending.event.approvalId === entry.event.approvalId)) {
4644
+ pending = void 0;
4645
+ }
4646
+ }
4647
+ return pending;
4648
+ }
3839
4649
  var InMemoryAgentEgressStore = class {
3840
4650
  #records = /* @__PURE__ */ new Map();
3841
4651
  #clock;
@@ -3843,7 +4653,10 @@ var InMemoryAgentEgressStore = class {
3843
4653
  this.#clock = clock;
3844
4654
  }
3845
4655
  async record(tenant, input) {
3846
- const key = tenantKey(tenant, `${input.deviceId}\0${input.payload.eventId}`);
4656
+ const key = tenantKey(
4657
+ tenant,
4658
+ agentReliabilityKey("agent-egress", input.deviceId, input.payload.agentRef, input.payload.eventId)
4659
+ );
3847
4660
  const existing = this.#records.get(key);
3848
4661
  if (existing !== void 0) return { record: existing, created: false };
3849
4662
  const record = {
@@ -3856,8 +4669,8 @@ var InMemoryAgentEgressStore = class {
3856
4669
  this.#records.set(key, record);
3857
4670
  return { record, created: true };
3858
4671
  }
3859
- async get(tenant, deviceId, eventId) {
3860
- return this.#records.get(tenantKey(tenant, `${deviceId}\0${eventId}`));
4672
+ async get(tenant, deviceId, agentRef, eventId) {
4673
+ return this.#records.get(tenantKey(tenant, agentReliabilityKey("agent-egress", deviceId, agentRef, eventId)));
3861
4674
  }
3862
4675
  };
3863
4676
 
@@ -3866,12 +4679,17 @@ function createInMemoryCloudStores(clock, crypto2, objects, mailbox) {
3866
4679
  const blobs = createInMemoryBlobs(clock, crypto2, objects);
3867
4680
  const taskState = new InMemoryTaskAttemptState(clock);
3868
4681
  const tasks = new InMemoryTaskAttemptStore(clock, taskState);
4682
+ const devices = new InMemoryDeviceDirectory();
4683
+ const pairing = new InMemoryPairingCodeStore(clock, devices);
3869
4684
  return {
3870
4685
  stores: {
3871
4686
  activity: new InMemoryActivityStore(clock),
3872
4687
  approvals: new InMemoryApprovalTimelineStore(clock),
3873
- devices: new InMemoryDeviceDirectory(),
3874
- pairingCodes: new InMemoryPairingCodeStore(clock),
4688
+ devices,
4689
+ // One code/device authority, projected into issuance and consumption
4690
+ // ports so neither side exposes the other's methods.
4691
+ pairingCodes: { issue: pairing.issue.bind(pairing) },
4692
+ pairing: { redeemAndRegister: pairing.redeemAndRegister.bind(pairing) },
3875
4693
  nonces: new InMemoryNonceStore(clock, crypto2),
3876
4694
  dedup: new InMemoryInboundDedupStore(),
3877
4695
  tasks,
@@ -3919,7 +4737,9 @@ function createInMemoryByokCloud(options = {}) {
3919
4737
  ...options.truthCommitter === void 0 ? {} : { truthCommitter: options.truthCommitter },
3920
4738
  ...options.truthObjectDownloads === void 0 ? {} : { truthObjectDownloads: options.truthObjectDownloads },
3921
4739
  ...options.operatorId !== void 0 ? { operatorId: options.operatorId } : {},
4740
+ ...options.instanceProductId === void 0 ? {} : { instanceProductId: options.instanceProductId },
3922
4741
  ...options.agentMessage === void 0 ? {} : { agentMessage: options.agentMessage },
4742
+ ...options.observer === void 0 ? {} : { observer: options.observer },
3923
4743
  ...options.agentMemoryProjectionAuthorizer === void 0 ? {} : { agentMemoryProjectionAuthorizer: options.agentMemoryProjectionAuthorizer },
3924
4744
  ...options.agentMemoryProjectionStore === void 0 ? {} : { agentMemoryProjectionStore: options.agentMemoryProjectionStore },
3925
4745
  ...options.maxBlobSizeBytes !== void 0 ? { maxBlobSizeBytes: options.maxBlobSizeBytes } : {},
@@ -4283,42 +5103,27 @@ function parseSseFrame(frame) {
4283
5103
  return { type: "board", item: BoardFeedItemSchema.parse(JSON.parse(data.join("\n"))) };
4284
5104
  }
4285
5105
 
4286
- // src/stores/ports.ts
4287
- var TASK_ATTEMPT_STATUSES = [
4288
- "offered",
4289
- "claimed",
4290
- "running",
4291
- "cancel_requested",
4292
- "complete",
4293
- "failed",
4294
- "cancelled"
4295
- ];
4296
- var BLOB_READ_ERROR_CODES = ["blob_upstream_unavailable", "blob_upstream_stream_interrupted"];
4297
- var CLOUD_STORE_NAMES = [
4298
- "activity",
4299
- "approvals",
4300
- "devices",
4301
- "pairingCodes",
4302
- "nonces",
4303
- "dedup",
4304
- "tasks",
4305
- "cancellations",
4306
- "receipts",
4307
- "egress",
4308
- "proofReceipts",
4309
- "blobs",
4310
- "rateLimiter"
4311
- ];
4312
-
4313
5106
  // src/stores/ports-contract.ts
4314
5107
  var CLOUD_PORT_METHODS = {
4315
5108
  activity: ["append", "read"],
4316
- approvals: ["append", "read"],
5109
+ approvals: ["append", "read", "resolvePending"],
4317
5110
  devices: ["register", "get", "revoke", "list", "readiness", "recordCapabilities", "resolveByDeviceId"],
4318
- pairingCodes: ["issue", "redeem"],
4319
- nonces: ["issue", "validate", "markUsed"],
4320
- dedup: ["checkAndRecord"],
4321
- tasks: ["open", "reserveAgentOffer", "get", "getMany", "claim", "recordStatus"],
5111
+ pairingCodes: ["issue"],
5112
+ pairing: ["redeemAndRegister"],
5113
+ nonces: ["issue", "consumeIfValid"],
5114
+ dedup: ["checkAndRecord", "checkAndRecordAgent"],
5115
+ tasks: [
5116
+ "open",
5117
+ "reserveAgentOffer",
5118
+ "reserveAgentMessage",
5119
+ "readAgentMessage",
5120
+ "finalizeAgentMessage",
5121
+ "get",
5122
+ "getMany",
5123
+ "list",
5124
+ "claim",
5125
+ "recordStatus"
5126
+ ],
4322
5127
  cancellations: ["request"],
4323
5128
  receipts: ["record", "get"],
4324
5129
  egress: ["record", "get"],
@@ -4334,6 +5139,7 @@ var CLOUD_PORT_INTERFACES = {
4334
5139
  approvals: "ApprovalTimelineStore",
4335
5140
  devices: "DeviceDirectory",
4336
5141
  pairingCodes: "PairingCodeStore",
5142
+ pairing: "PairingEnrollment",
4337
5143
  nonces: "NonceStore",
4338
5144
  dedup: "InboundDedupStore",
4339
5145
  tasks: "TaskAttemptStore",
@@ -4345,6 +5151,6 @@ var CLOUD_PORT_INTERFACES = {
4345
5151
  rateLimiter: "InboundRateLimiter"
4346
5152
  };
4347
5153
 
4348
- 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, 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, agentHomeProjectionCompletionKey, agentHomeProjectionRequestKey, approvalTimelineCursor, authenticateBearer, authenticateDeviceProof, authenticateHostedDeviceAssertion, createAuthPlane, createByokCloud, createHmacTokenSigner, createInMemoryBlobs, createInMemoryByokCloud, createInMemoryCloudStores, createWebCrypto, declares, extractBearerToken, fullCapabilityDeclaration, handleInboundEnvelope, isCloudError, isTruthCommitError, parseApprovalObservations, parseTimelineEvents, projectTerminalResult, projectTimelineEvents, readAgentHomeProjectionStatus, recordAgentHomeProjectionCompletion, routeKey, tenantStoresFor, terminalReceiptKey, truthManifestMetadata, truthRecordMetadata, validateActivityAppend, validateApprovalTimelineAppend, verifyNonceSignature };
5154
+ 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 };
4349
5155
  //# sourceMappingURL=index.js.map
4350
5156
  //# sourceMappingURL=index.js.map