@byok-sdk/cloud 0.12.0 → 0.14.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";
@@ -321,6 +383,15 @@ var CLOUD_ERROR_CODES = {
321
383
  activity_batch_too_large: "activity_batch_too_large",
322
384
  /** Host control-plane task lookup is tenant-closed and found no task. */
323
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",
324
395
  /** Durable target-device capability is absent, revoked, or unavailable. */
325
396
  agent_capability_missing: "agent_capability_missing",
326
397
  /** Inbound Agent identity did not exactly match the offered identity. */
@@ -387,6 +458,12 @@ var TimelineEventSchema = z.object({
387
458
  sourceEnvelopeId: SourceEnvelopeIdSchema,
388
459
  batchSeq: z.number().int().nonnegative(),
389
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(),
390
467
  receivedAt: z.iso.datetime(),
391
468
  event: AgentEventOrUnknownSchema
392
469
  });
@@ -420,11 +497,24 @@ function projectTimelineEvents(input, receivedAt) {
420
497
  sourceEnvelopeId: input.sourceEnvelopeId,
421
498
  batchSeq: input.batchSeq,
422
499
  eventIndex,
500
+ sourceDropped: input.dropped,
423
501
  receivedAt,
424
502
  event
425
503
  })
426
504
  );
427
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
+ }
428
518
  function parseTimelineEvents(value) {
429
519
  return z.array(TimelineEventSchema).parse(value);
430
520
  }
@@ -469,6 +559,14 @@ function assertBoardLabels(channel, title, limits) {
469
559
  );
470
560
  }
471
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
+ }
472
570
  function validateActivityEvents(events, dropped, bounds) {
473
571
  if (!Number.isSafeInteger(dropped) || dropped < 0) {
474
572
  throw new ByokCloudError(
@@ -520,6 +618,9 @@ async function authenticateBearer(header, deps) {
520
618
  const device = await deps.devices.get(tenantId(claims.tenantId), claims.deviceId);
521
619
  if (device === void 0 || device.revoked) return void 0;
522
620
  if (device.productId !== claims.productId) return void 0;
621
+ if (deps.instanceProductId !== void 0 && claims.productId !== deps.instanceProductId) {
622
+ return void 0;
623
+ }
523
624
  return {
524
625
  kind: "device",
525
626
  tenantId: device.tenantId,
@@ -579,6 +680,7 @@ function tenantStoresFor(principal, root) {
579
680
  finalizeAgentMessage: (input) => cloud.tasks.finalizeAgentMessage(tenant, input),
580
681
  get: (taskId) => cloud.tasks.get(tenant, taskId),
581
682
  getMany: (taskIds) => cloud.tasks.getMany(tenant, taskIds),
683
+ list: (query) => cloud.tasks.list(tenant, query),
582
684
  claim: (input) => cloud.tasks.claim(tenant, input),
583
685
  recordStatus: (input) => cloud.tasks.recordStatus(tenant, input)
584
686
  },
@@ -586,7 +688,8 @@ function tenantStoresFor(principal, root) {
586
688
  request: (input) => cloud.cancellations.request(tenant, input)
587
689
  },
588
690
  dedup: {
589
- 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)
590
693
  },
591
694
  receipts: {
592
695
  record: (input) => cloud.receipts.record(tenant, input),
@@ -594,7 +697,7 @@ function tenantStoresFor(principal, root) {
594
697
  },
595
698
  egress: {
596
699
  record: (input) => cloud.egress.record(tenant, input),
597
- get: (deviceId, eventId) => cloud.egress.get(tenant, deviceId, eventId)
700
+ get: (deviceId, agentRef, eventId) => cloud.egress.get(tenant, deviceId, agentRef, eventId)
598
701
  },
599
702
  blobs: {
600
703
  createUpload: (reservation) => cloud.blobs.createUpload(tenant, reservation),
@@ -927,11 +1030,20 @@ function tokenHandler(deps) {
927
1030
  }
928
1031
  var CLOUD_PROTOCOL_CAPABILITIES = [
929
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",
930
1038
  "agent-home-contract",
931
1039
  "agent-home-projection",
932
1040
  "agent-egress-policy",
933
1041
  "agent-egress-reliable-ack",
934
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",
935
1047
  "agent-content-workspace-read",
936
1048
  "agent-content-transcript-read",
937
1049
  "agent-content-artifact-read"
@@ -973,6 +1085,9 @@ function eventsHandler(deps) {
973
1085
  afterSeq: scanCursor,
974
1086
  limit: deps.pageLimit
975
1087
  });
1088
+ if (cursor < page.recoverableFrom - 1) {
1089
+ return c.json({ error: "cursor_too_old", recoverableFrom: page.recoverableFrom }, 409);
1090
+ }
976
1091
  if (page.messages.length === 0) break;
977
1092
  const decoded = page.messages.map((message) => decodeEnvelope(message.body));
978
1093
  const offeredTaskIds = decoded.flatMap(
@@ -1018,19 +1133,45 @@ var DEFAULT_APPROVAL_TIMELINE_TTL_MS = 10 * 60 * 1e3;
1018
1133
  var APPROVAL_SUMMARY_MAX_BYTES = 16 * 1024;
1019
1134
  var NonBlankIdSchema = z.string().max(200).regex(/\S/, "value must not be blank");
1020
1135
  var TaskIdSchema = z.string().min(1).max(200);
1021
- var ApprovalTimelineEventSchema = z.discriminatedUnion("type", [
1022
- z.object({
1023
- type: z.literal("approval_requested"),
1024
- summary: z.string(),
1025
- approvalId: NonBlankIdSchema.optional()
1026
- }),
1027
- z.object({
1028
- type: z.literal("approval_resolved"),
1029
- approvalId: NonBlankIdSchema,
1030
- decision: z.enum(["approve", "reject"]),
1031
- resolvedBy: z.enum(["local"]),
1032
- at: z.iso.datetime({ offset: true })
1033
- })
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
1034
1175
  ]);
1035
1176
  var ApprovalObservationSchema = z.object({
1036
1177
  taskId: TaskIdSchema,
@@ -1073,6 +1214,20 @@ function validateApprovalTimelineAppend(input) {
1073
1214
  }
1074
1215
  return { capacity, ttlMs, event: parsed.data.event };
1075
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
+ }
1076
1231
  function approvalTimelineKey(tenant, taskId) {
1077
1232
  return tenantKey(tenant, taskId);
1078
1233
  }
@@ -1096,6 +1251,10 @@ async function projectTerminalToReview(board, taskId) {
1096
1251
  throw caught;
1097
1252
  }
1098
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
+ }
1099
1258
 
1100
1259
  // src/inbound.ts
1101
1260
  async function handleAgentMessagePublish(stores, deviceId, taskId, payload, consume, alreadyDeduplicated) {
@@ -1112,7 +1271,6 @@ async function handleAgentMessagePublish(stores, deviceId, taskId, payload, cons
1112
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" };
1113
1272
  const priorDisposition = await readAgentMessageDisposition(stores, deviceId, taskId, payload);
1114
1273
  if (priorDisposition !== void 0) return { outcome: "duplicate", disposition: priorDisposition };
1115
- if (alreadyDeduplicated) return { outcome: "rejected" };
1116
1274
  const payloadBody = JSON.stringify(payload);
1117
1275
  const reservation = await stores.tasks.reserveAgentMessage({
1118
1276
  taskId,
@@ -1120,17 +1278,15 @@ async function handleAgentMessagePublish(stores, deviceId, taskId, payload, cons
1120
1278
  messageId: payload.messageId,
1121
1279
  payloadBody
1122
1280
  });
1123
- if (reservation === "pending") return { outcome: "rejected" };
1124
- if (reservation !== "reserved") return { outcome: "rejected" };
1281
+ if (reservation !== "reserved" && reservation !== "pending") return { outcome: "rejected" };
1125
1282
  let decision;
1126
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
+ }
1127
1287
  decision = { outcome: "held", reasonCode: "consumer_unavailable" };
1128
1288
  } else {
1129
- try {
1130
- decision = await consume({ tenant: stores.tenant, deviceId, taskId, context: frozen.context, payload });
1131
- } catch {
1132
- decision = { outcome: "held", reasonCode: "consumer_failed" };
1133
- }
1289
+ decision = await consume({ tenant: stores.tenant, deviceId, taskId, context: frozen.context, payload });
1134
1290
  }
1135
1291
  const disposition = agentMessageDisposition(payload, decision);
1136
1292
  const admission = await stores.tasks.finalizeAgentMessage({
@@ -1195,17 +1351,17 @@ function contentReadCapability(surface) {
1195
1351
  function hasCapabilities(capabilities, required) {
1196
1352
  return capabilities !== void 0 && required.every((capability) => capabilities.includes(capability));
1197
1353
  }
1198
- function contentRequestReceiptKey(deviceId, requestId) {
1199
- return `agent-content-request:${deviceId}:${requestId}`;
1354
+ function contentRequestReceiptKey(deviceId, agentRef, requestId) {
1355
+ return agentReliabilityKey("agent-content-request", deviceId, agentRef, requestId);
1200
1356
  }
1201
- function contentReceiptKey(deviceId, requestId) {
1202
- return `agent-content:${deviceId}:${requestId}`;
1357
+ function contentReceiptKey(deviceId, agentRef, requestId) {
1358
+ return agentReliabilityKey("agent-content-receipt", deviceId, agentRef, requestId);
1203
1359
  }
1204
1360
  function matchesContentReadReceipt(request, receipt) {
1205
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;
1206
1362
  }
1207
- async function readContentRequest(stores, deviceId, requestId) {
1208
- 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));
1209
1365
  if (stored === void 0) return void 0;
1210
1366
  try {
1211
1367
  return AgentContentReadPayloadSchema.parse(JSON.parse(stored.body));
@@ -1228,7 +1384,23 @@ function inboundAgentRef(envelope) {
1228
1384
  function agentRefEchoRequired(envelope) {
1229
1385
  return envelope.type === "task.claim" || envelope.type === "task.decline" || envelope.type === "task.complete" || envelope.type === "task.fail" || envelope.type === "task.cancelled";
1230
1386
  }
1231
- async function handleInboundEnvelope(stores, deviceId, envelope, activityBounds = DEFAULT_ACTIVITY_BOUNDS, agentMessageConsume) {
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) {
1232
1404
  if (!await stores.rateLimiter.consume(deviceId)) return "rate_limited";
1233
1405
  if (envelope.type === "conn.hello") {
1234
1406
  if (envelope.payload.deviceId !== deviceId) return "rejected";
@@ -1237,20 +1409,20 @@ async function handleInboundEnvelope(stores, deviceId, envelope, activityBounds
1237
1409
  if (device === void 0 || device.revoked || device.productId !== envelope.payload.productId) {
1238
1410
  return "rejected";
1239
1411
  }
1240
- if (await stores.dedup.checkAndRecord(deviceId, envelope.id)) return "duplicate";
1241
- 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);
1242
1416
  }
1243
1417
  if (!DAEMON_TO_SERVER_TYPES.includes(envelope.type)) return "rejected";
1244
1418
  if (envelope.type === "agent.message.publish") {
1245
- const alreadyDeduplicated = await stores.dedup.checkAndRecord(deviceId, envelope.id);
1246
- return (await handleAgentMessagePublish(
1419
+ const message = await handleAgentMessagePublish(
1247
1420
  stores,
1248
1421
  deviceId,
1249
1422
  envelope.task_id,
1250
1423
  envelope.payload,
1251
- agentMessageConsume,
1252
- alreadyDeduplicated
1253
- )).outcome;
1424
+ agentMessageConsume);
1425
+ return message.outcome === "rejected" ? "rejected" : completeInboundEnvelope(stores, deviceId, envelope.id, envelope.payload.agentRef);
1254
1426
  }
1255
1427
  if (envelope.type === "agent.egress.reliable") {
1256
1428
  const device = await stores.devices.get(deviceId);
@@ -1262,25 +1434,28 @@ async function handleInboundEnvelope(stores, deviceId, envelope, activityBounds
1262
1434
  payload: envelope.payload,
1263
1435
  receiptId: crypto.randomUUID()
1264
1436
  });
1265
- 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);
1266
1439
  }
1267
1440
  if (envelope.type === "agent.content.receipt") {
1268
1441
  const device = await stores.devices.get(deviceId);
1269
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])) {
1270
1443
  return "rejected";
1271
1444
  }
1272
- const request = await readContentRequest(stores, deviceId, envelope.payload.requestId);
1445
+ const request = await readContentRequest(stores, deviceId, envelope.payload.agentRef, envelope.payload.requestId);
1273
1446
  if (request === void 0 || !matchesContentReadReceipt(request, envelope.payload)) return "rejected";
1274
- const key = contentReceiptKey(deviceId, envelope.payload.requestId);
1447
+ const key = contentReceiptKey(deviceId, envelope.payload.agentRef, envelope.payload.requestId);
1275
1448
  const body = JSON.stringify(envelope.payload);
1276
1449
  const receipt = await stores.receipts.record({ key, body });
1277
- 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);
1278
1452
  }
1279
1453
  const taskId = envelope.task_id;
1280
1454
  if (taskId === void 0) return "rejected";
1281
1455
  const attempt = await stores.tasks.get(taskId);
1282
- if (attempt?.agentRef !== void 0 && attempt.deviceId !== deviceId) return "rejected";
1456
+ if (attempt === void 0 || attempt.deviceId !== deviceId) return "rejected";
1283
1457
  if (attempt?.ownerDeviceId !== void 0 && attempt.ownerDeviceId !== deviceId) return "rejected";
1458
+ if (envelope.type === "task.claim" && envelope.payload.deviceId !== deviceId) return "rejected";
1284
1459
  if (attempt?.agentRef !== void 0 && agentRefEchoRequired(envelope) && !sameAgentRef(attempt.agentRef, inboundAgentRef(envelope))) {
1285
1460
  return "rejected";
1286
1461
  }
@@ -1312,14 +1487,22 @@ async function handleInboundEnvelope(stores, deviceId, envelope, activityBounds
1312
1487
  throw caught;
1313
1488
  }
1314
1489
  }
1315
- if (await stores.dedup.checkAndRecord(deviceId, envelope.id)) return "duplicate";
1316
1490
  await applyLifecycle(stores, deviceId, taskId, envelope, activityBounds, attempt?.agentRef);
1317
- 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";
1318
1496
  }
1319
1497
  async function applyLifecycle(stores, deviceId, taskId, envelope, activityBounds, persistedAgentRef) {
1320
1498
  switch (envelope.type) {
1321
1499
  case "task.claim":
1322
- 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
+ });
1323
1506
  return;
1324
1507
  case "task.started":
1325
1508
  await stores.tasks.recordStatus({
@@ -1329,11 +1512,7 @@ async function applyLifecycle(stores, deviceId, taskId, envelope, activityBounds
1329
1512
  });
1330
1513
  return;
1331
1514
  case "task.decline":
1332
- await stores.tasks.recordStatus({
1333
- taskId,
1334
- status: "failed",
1335
- ...persistedAgentRef === void 0 ? {} : { agentRef: persistedAgentRef }
1336
- });
1515
+ await recordTerminal(stores, taskId, envelope);
1337
1516
  return;
1338
1517
  case "task.progress":
1339
1518
  if (envelope.payload.events.length > 0) {
@@ -1357,13 +1536,13 @@ async function applyLifecycle(stores, deviceId, taskId, envelope, activityBounds
1357
1536
  await stores.approvals.append(approvalTimelineAppendInput(taskId, envelope));
1358
1537
  return;
1359
1538
  case "task.complete":
1360
- await recordTerminal(stores, taskId, envelope, "complete");
1539
+ await recordTerminal(stores, taskId, envelope);
1361
1540
  return;
1362
1541
  case "task.fail":
1363
- await recordTerminal(stores, taskId, envelope, "failed");
1542
+ await recordTerminal(stores, taskId, envelope);
1364
1543
  return;
1365
1544
  case "task.cancelled":
1366
- await recordTerminal(stores, taskId, envelope, "cancelled");
1545
+ await recordTerminal(stores, taskId, envelope);
1367
1546
  return;
1368
1547
  default:
1369
1548
  return;
@@ -1397,33 +1576,67 @@ function approvalTimelineAppendInput(taskId, envelope) {
1397
1576
  return void 0;
1398
1577
  }
1399
1578
  }
1400
- async function recordTerminal(stores, taskId, envelope, status) {
1401
- const attempt = await stores.tasks.get(taskId);
1402
- const { created } = await stores.receipts.record({
1579
+ async function recordTerminal(stores, taskId, envelope) {
1580
+ const receipt = await stores.receipts.record({
1403
1581
  key: terminalReceiptKey(taskId),
1404
1582
  body: encodeEnvelope(envelope)
1405
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);
1406
1589
  if (attempt?.cancellation !== void 0) {
1407
- if (status === "cancelled") {
1590
+ await stores.tasks.recordStatus({
1591
+ taskId,
1592
+ status: "cancelled",
1593
+ ...attempt.agentRef === void 0 ? {} : { agentRef: attempt.agentRef }
1594
+ });
1595
+ return;
1596
+ }
1597
+ const recordedAttempt = await stores.tasks.recordStatus({
1598
+ taskId,
1599
+ status: winner.status,
1600
+ ...attempt?.agentRef === void 0 ? {} : { agentRef: attempt.agentRef },
1601
+ ...winner.cause === void 0 ? {} : { terminalCause: winner.cause }
1602
+ });
1603
+ if (recordedAttempt?.cancellation !== void 0 || recordedAttempt?.status !== winner.status) {
1604
+ if (recordedAttempt?.cancellation !== void 0) {
1408
1605
  await stores.tasks.recordStatus({
1409
1606
  taskId,
1410
1607
  status: "cancelled",
1411
- ...attempt.agentRef === void 0 ? {} : { agentRef: attempt.agentRef }
1608
+ ...recordedAttempt.agentRef === void 0 ? {} : { agentRef: recordedAttempt.agentRef }
1412
1609
  });
1413
1610
  }
1414
1611
  return;
1415
1612
  }
1416
- if (!created) return;
1417
- const cause = envelope.type === "task.fail" || envelope.type === "task.cancelled" ? envelope.payload.reason : void 0;
1418
- const recordedAttempt = await stores.tasks.recordStatus({
1419
- taskId,
1420
- status,
1421
- ...attempt?.agentRef === void 0 ? {} : { agentRef: attempt.agentRef },
1422
- ...cause === void 0 ? {} : { terminalCause: cause }
1423
- });
1424
- if (recordedAttempt?.status !== status || recordedAttempt.cancellation !== void 0) return;
1425
1613
  await projectTerminalToReview(stores.board, taskId);
1426
1614
  }
1615
+ function terminalStatus(envelope) {
1616
+ switch (envelope.type) {
1617
+ case "task.complete":
1618
+ return "complete";
1619
+ case "task.fail":
1620
+ case "task.decline":
1621
+ return "failed";
1622
+ case "task.cancelled":
1623
+ return "cancelled";
1624
+ default:
1625
+ return void 0;
1626
+ }
1627
+ }
1628
+ function terminalReceiptBinding(taskId, body) {
1629
+ let envelope;
1630
+ try {
1631
+ envelope = decodeEnvelope(body);
1632
+ } catch {
1633
+ return void 0;
1634
+ }
1635
+ const status = terminalStatus(envelope);
1636
+ if (status === void 0 || envelope.task_id !== taskId || encodeEnvelope(envelope) !== body) return void 0;
1637
+ const cause = envelope.type === "task.fail" || envelope.type === "task.cancelled" || envelope.type === "task.decline" ? envelope.payload.reason : void 0;
1638
+ return { status, ...cause === void 0 ? {} : { cause } };
1639
+ }
1427
1640
 
1428
1641
  // src/handlers/messages.ts
1429
1642
  var MESSAGES_JSON_BODY_MAX_BYTES = 2 * 1024 * 1024;
@@ -1444,7 +1657,8 @@ function messagesHandler(deps) {
1444
1657
  device.deviceId,
1445
1658
  envelope,
1446
1659
  deps.activityBounds,
1447
- envelope.type === "agent.message.publish" ? deps.agentMessage?.consume : void 0
1660
+ envelope.type === "agent.message.publish" ? deps.agentMessage?.consume : void 0,
1661
+ deps.observer
1448
1662
  );
1449
1663
  if (outcome === "rate_limited") return c.json({ error: "rate limit exceeded" }, 429);
1450
1664
  if (envelope.type === "agent.message.publish") {
@@ -1454,7 +1668,7 @@ function messagesHandler(deps) {
1454
1668
  }
1455
1669
  }
1456
1670
  if (envelope.type === "agent.egress.reliable" && (outcome === "accepted" || outcome === "duplicate")) {
1457
- const record = await stores.egress.get(device.deviceId, envelope.payload.eventId);
1671
+ const record = await stores.egress.get(device.deviceId, envelope.payload.agentRef, envelope.payload.eventId);
1458
1672
  if (record === void 0) {
1459
1673
  throw new Error(`Accepted reliable egress ${envelope.payload.eventId} has no durable receipt record.`);
1460
1674
  }
@@ -2273,11 +2487,11 @@ var CloudRouteRegistry = class {
2273
2487
  return this.#app.fetch;
2274
2488
  }
2275
2489
  };
2276
- function agentHomeProjectionRequestKey(deviceId, requestId) {
2277
- return `agent-home-projection:v1:${deviceId}:${requestId}:request`;
2490
+ function agentHomeProjectionRequestKey(deviceId, agentRef, requestId) {
2491
+ return agentReliabilityKey("agent-home-projection-request", deviceId, agentRef, requestId);
2278
2492
  }
2279
- function agentHomeProjectionCompletionKey(deviceId, requestId) {
2280
- return `agent-home-projection:v1:${deviceId}:${requestId}:completion`;
2493
+ function agentHomeProjectionCompletionKey(deviceId, agentRef, requestId) {
2494
+ return agentReliabilityKey("agent-home-projection-completion", deviceId, agentRef, requestId);
2281
2495
  }
2282
2496
  function sameAgentHomeProjectionRequest(expected, actual) {
2283
2497
  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);
@@ -2311,7 +2525,7 @@ function parseCompletionBody(body) {
2311
2525
  }
2312
2526
  }
2313
2527
  async function readAgentHomeProjectionStatus(receipts, tenant, deviceId, input) {
2314
- const storedRequest = await receipts.get(agentHomeProjectionRequestKey(deviceId, input.requestId));
2528
+ const storedRequest = await receipts.get(agentHomeProjectionRequestKey(deviceId, input.agentRef, input.requestId));
2315
2529
  if (storedRequest === void 0) return void 0;
2316
2530
  const request = parseRequestBody(storedRequest.body);
2317
2531
  if (!statusInputMatchesAgentHomeProjection(request, input)) {
@@ -2320,7 +2534,7 @@ async function readAgentHomeProjectionStatus(receipts, tenant, deviceId, input)
2320
2534
  `Agent-home projection request ${input.requestId} does not match its immutable desired fact.`
2321
2535
  );
2322
2536
  }
2323
- const storedCompletion = await receipts.get(agentHomeProjectionCompletionKey(deviceId, input.requestId));
2537
+ const storedCompletion = await receipts.get(agentHomeProjectionCompletionKey(deviceId, request.agentRef, input.requestId));
2324
2538
  if (storedCompletion === void 0) {
2325
2539
  return {
2326
2540
  tenantId: tenant,
@@ -2350,7 +2564,7 @@ async function readAgentHomeProjectionStatus(receipts, tenant, deviceId, input)
2350
2564
  }
2351
2565
  async function recordAgentHomeProjectionCompletion(receipts, tenant, deviceId, receiptInput) {
2352
2566
  const receipt = AgentHomeProjectionCompletionRequestSchema.parse(receiptInput);
2353
- const storedRequest = await receipts.get(agentHomeProjectionRequestKey(deviceId, receipt.requestId));
2567
+ const storedRequest = await receipts.get(agentHomeProjectionRequestKey(deviceId, receipt.agentRef, receipt.requestId));
2354
2568
  if (storedRequest === void 0) {
2355
2569
  throw new ByokCloudError(
2356
2570
  "agent_home_projection_request_not_found",
@@ -2366,7 +2580,7 @@ async function recordAgentHomeProjectionCompletion(receipts, tenant, deviceId, r
2366
2580
  }
2367
2581
  const body = JSON.stringify(receipt);
2368
2582
  const storedCompletion = await receipts.record({
2369
- key: agentHomeProjectionCompletionKey(deviceId, receipt.requestId),
2583
+ key: agentHomeProjectionCompletionKey(deviceId, request.agentRef, receipt.requestId),
2370
2584
  body
2371
2585
  });
2372
2586
  if (!storedCompletion.created && storedCompletion.receipt.body !== body) {
@@ -2420,6 +2634,21 @@ function projectTerminalResult(taskId, receipt) {
2420
2634
  ...envelope.payload.usage !== void 0 ? { usage: envelope.payload.usage } : {},
2421
2635
  recordedAt: receipt.recordedAt
2422
2636
  };
2637
+ // `task.decline` is the pre-claim terminal (§3.2, `Offered -> Failed`).
2638
+ // It projects onto the same `failed` state as `task.fail` — there is no
2639
+ // separate declined state — and carries the device's own `retryable`
2640
+ // verbatim, exactly like `task.fail` does; this projection never decides
2641
+ // that a decline is retryable on the device's behalf.
2642
+ case "task.decline":
2643
+ return {
2644
+ taskId,
2645
+ state: "failed",
2646
+ ...envelope.payload.agentRef === void 0 ? {} : { agentRef: envelope.payload.agentRef },
2647
+ reason: envelope.payload.reason,
2648
+ terminalCause: envelope.payload.reason,
2649
+ ...envelope.payload.retryable !== void 0 ? { retryable: envelope.payload.retryable } : {},
2650
+ recordedAt: receipt.recordedAt
2651
+ };
2423
2652
  case "task.cancelled":
2424
2653
  return {
2425
2654
  taskId,
@@ -2444,9 +2673,25 @@ var DEFAULT_LONG_POLL_HOLD_MS = 5e4;
2444
2673
  var DEFAULT_LONG_POLL_INTERVAL_MS = 250;
2445
2674
  var DEFAULT_EVENTS_PAGE_LIMIT = 50;
2446
2675
  var AGENT_HOME_CONTRACT_CAPABILITY = "agent-home-contract";
2676
+ function isTerminalAttemptStatus(status) {
2677
+ return status === "complete" || status === "failed" || status === "cancelled";
2678
+ }
2447
2679
  function sameAgentRef2(expected, actual) {
2448
2680
  return actual?.agentId === expected.agentId && actual.profileRevision === expected.profileRevision;
2449
2681
  }
2682
+ function uuidFromSha256(value) {
2683
+ const match = /^sha256:([0-9a-f]{64})$/.exec(value);
2684
+ if (match === null) throw new Error("CloudCrypto.sha256 must return canonical sha256 hex");
2685
+ const digest = match[1];
2686
+ if (digest === void 0) throw new Error("CloudCrypto.sha256 must return canonical sha256 hex");
2687
+ const hex = digest.slice(0, 32).split("");
2688
+ hex[12] = "8";
2689
+ hex[16] = ["8", "9", "a", "b"][Number.parseInt(hex[16], 16) & 3];
2690
+ 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("")}`;
2691
+ }
2692
+ function sameOfferEnvelope(expected, actual) {
2693
+ return expected.id === actual.id && expected.type === actual.type && expected.task_id === actual.task_id && JSON.stringify(expected.payload) === JSON.stringify(actual.payload);
2694
+ }
2450
2695
  function createByokCloud(options) {
2451
2696
  const declaration = parseDeclaration(options.capabilities);
2452
2697
  assertNoOverDeclaration(
@@ -2468,7 +2713,11 @@ function createByokCloud(options) {
2468
2713
  });
2469
2714
  const deviceRouteDeps = {
2470
2715
  root,
2471
- bearer: { tokenSigner: options.tokenSigner, devices: options.cloud.devices }
2716
+ bearer: {
2717
+ tokenSigner: options.tokenSigner,
2718
+ devices: options.cloud.devices,
2719
+ ...options.instanceProductId === void 0 ? {} : { instanceProductId: options.instanceProductId }
2720
+ }
2472
2721
  };
2473
2722
  const registry = new CloudRouteRegistry();
2474
2723
  const activityBounds = {
@@ -2513,7 +2762,8 @@ function createByokCloud(options) {
2513
2762
  appendReliableEgressAck: enqueueReliableEgressAck,
2514
2763
  appendContentReceiptAck: enqueueContentReceiptAck,
2515
2764
  agentMessage: options.agentMessage,
2516
- appendAgentMessageDisposition: enqueueAgentMessageDisposition
2765
+ appendAgentMessageDisposition: enqueueAgentMessageDisposition,
2766
+ observer: options.observer
2517
2767
  })
2518
2768
  );
2519
2769
  }
@@ -2645,56 +2895,60 @@ function createByokCloud(options) {
2645
2895
  async function enqueueTaskEnvelope(tenant, deviceId, requestedTaskId, agentRef, buildEnvelope, beforeMailbox) {
2646
2896
  const stores = tenantStoresFor(controlPlane(tenant), root);
2647
2897
  const taskId = requestedTaskId ?? `task_${options.crypto.randomUuid()}`;
2648
- const messageId = options.crypto.randomUuid();
2898
+ const messageId = uuidFromSha256(
2899
+ await options.crypto.sha256(
2900
+ new TextEncoder().encode(
2901
+ JSON.stringify({ domain: "byok:task-offer", tenant, taskId, deviceId, agentRef })
2902
+ )
2903
+ )
2904
+ );
2649
2905
  const reservation = agentRef === void 0 ? void 0 : await stores.tasks.reserveAgentOffer({ taskId, deviceId, agentRef });
2650
- const openedBeforeAppend = reservation?.attempt;
2651
- if (agentRef !== void 0 && (openedBeforeAppend === void 0 || openedBeforeAppend.deviceId !== deviceId || openedBeforeAppend.agentRef === void 0 || !sameAgentRef2(agentRef, openedBeforeAppend.agentRef))) {
2906
+ const openedBeforeAppend = reservation?.attempt ?? (agentRef === void 0 ? await stores.tasks.open({ taskId, deviceId }) : void 0);
2907
+ if (openedBeforeAppend === void 0 || openedBeforeAppend.deviceId !== deviceId || (agentRef === void 0 ? openedBeforeAppend.agentRef !== void 0 : openedBeforeAppend.agentRef === void 0 || !sameAgentRef2(agentRef, openedBeforeAppend.agentRef))) {
2652
2908
  throw new ByokCloudError(
2653
- "agent_ref_mismatch",
2654
- `Task ${taskId} already has a different durable Agent identity or target device.`
2909
+ agentRef === void 0 ? "coordination_input_invalid" : "agent_ref_mismatch",
2910
+ `Task ${taskId} already has a different durable target or Agent identity.`
2655
2911
  );
2656
2912
  }
2657
- if (reservation !== void 0 && !reservation.created) {
2913
+ const offerReceiptKey = `task-offer:${messageId}`;
2914
+ const deliveryReceiptKey = `task-offer-delivered:${messageId}`;
2915
+ if (await stores.receipts.get(deliveryReceiptKey) !== void 0) {
2658
2916
  throw new ByokCloudError(
2659
- "agent_task_already_exists",
2660
- `Task ${taskId} already has a durable Agent attempt and cannot be enqueued again.`
2917
+ agentRef === void 0 ? "coordination_input_invalid" : "agent_task_already_exists",
2918
+ `Task ${taskId} already has a durable delivered attempt and cannot be enqueued again.`
2661
2919
  );
2662
2920
  }
2663
- let message;
2664
- try {
2665
- await beforeMailbox?.(stores, taskId);
2666
- message = await stores.mailbox.append({
2667
- deviceId,
2668
- messageId,
2669
- materialize: async (seq) => {
2670
- const envelope2 = buildEnvelope(taskId, seq, messageId);
2671
- const body = encodeEnvelope(envelope2);
2672
- const bytes = new TextEncoder().encode(body);
2673
- return {
2674
- body,
2675
- bodyHash: contentHash(await options.crypto.sha256(bytes)),
2676
- byteSize: BigInt(bytes.length)
2677
- };
2678
- }
2679
- });
2680
- } catch (error) {
2681
- if (openedBeforeAppend !== void 0) {
2682
- try {
2683
- await stores.tasks.recordStatus({
2684
- taskId,
2685
- status: "failed",
2686
- agentRef,
2687
- terminalCause: "mailbox append failed before Agent offer delivery"
2688
- });
2689
- } catch (cleanupError) {
2690
- throw new AggregateError(
2691
- [error, cleanupError],
2692
- `Agent task ${taskId} could not be delivered or durably closed`
2693
- );
2694
- }
2695
- }
2696
- throw error;
2921
+ const proposed = buildEnvelope(taskId, 0, messageId);
2922
+ const proposedBody = JSON.stringify({
2923
+ deviceId,
2924
+ type: proposed.type,
2925
+ payload: proposed.payload
2926
+ });
2927
+ const recorded = await stores.receipts.record({
2928
+ key: offerReceiptKey,
2929
+ body: proposedBody
2930
+ });
2931
+ if (!recorded.created && recorded.receipt.body !== proposedBody) {
2932
+ throw new ByokCloudError(
2933
+ "coordination_input_invalid",
2934
+ `Task ${taskId} already has a different executable offer body.`
2935
+ );
2697
2936
  }
2937
+ await beforeMailbox?.(stores, taskId);
2938
+ const message = await stores.mailbox.append({
2939
+ deviceId,
2940
+ messageId,
2941
+ materialize: async (seq) => {
2942
+ const envelope2 = buildEnvelope(taskId, seq, messageId);
2943
+ const body = encodeEnvelope(envelope2);
2944
+ const bytes = new TextEncoder().encode(body);
2945
+ return {
2946
+ body,
2947
+ bodyHash: contentHash(await options.crypto.sha256(bytes)),
2948
+ byteSize: BigInt(bytes.length)
2949
+ };
2950
+ }
2951
+ });
2698
2952
  const envelope = decodeEnvelope(message.body);
2699
2953
  if (envelope.seq !== message.seq) {
2700
2954
  throw new ByokCloudError(
@@ -2702,11 +2956,20 @@ function createByokCloud(options) {
2702
2956
  `Mailbox stored offer seq ${String(envelope.seq)} at row ${message.seq}; the daemon's redelivery cursor would be wrong.`
2703
2957
  );
2704
2958
  }
2705
- const attempt = openedBeforeAppend ?? await stores.tasks.open({
2706
- taskId,
2707
- deviceId
2708
- });
2709
- return { taskId, seq: message.seq, envelope, attempt };
2959
+ if (!sameOfferEnvelope(buildEnvelope(taskId, message.seq, messageId), envelope)) {
2960
+ throw new ByokCloudError(
2961
+ "coordination_input_invalid",
2962
+ `Task ${taskId} already has a different executable offer body.`
2963
+ );
2964
+ }
2965
+ const delivered = await stores.receipts.record({ key: deliveryReceiptKey, body: messageId });
2966
+ if (agentRef !== void 0 && reservation?.created === false && !delivered.created) {
2967
+ throw new ByokCloudError(
2968
+ "agent_task_already_exists",
2969
+ `Task ${taskId} already has a durable Agent attempt and cannot be enqueued again.`
2970
+ );
2971
+ }
2972
+ return { taskId, seq: message.seq, envelope, attempt: openedBeforeAppend };
2710
2973
  }
2711
2974
  async function assertAgentCapabilities(tenant, deviceId, required) {
2712
2975
  const device = await options.cloud.devices.get(tenant, deviceId);
@@ -2761,6 +3024,236 @@ function createByokCloud(options) {
2761
3024
  }
2762
3025
  return { seq: message.seq, envelope };
2763
3026
  }
3027
+ async function agentControlEnvelopeId(domain, tenant, deviceId, agentRef, logicalRequestId) {
3028
+ return uuidFromSha256(
3029
+ await options.crypto.sha256(
3030
+ new TextEncoder().encode(
3031
+ JSON.stringify({ domain, tenant, deviceId, agentRef, logicalRequestId })
3032
+ )
3033
+ )
3034
+ );
3035
+ }
3036
+ async function approvalDecisionSource(tenant, taskId, attempt, pending, decision) {
3037
+ const agent = attempt.agentRef;
3038
+ return uuidFromSha256(
3039
+ await options.crypto.sha256(
3040
+ new TextEncoder().encode(
3041
+ JSON.stringify({
3042
+ domain: "byok:approval-decision",
3043
+ tenant,
3044
+ taskId,
3045
+ deviceId: attempt.deviceId,
3046
+ ownerDeviceId: attempt.ownerDeviceId,
3047
+ agentRef: agent,
3048
+ requestSourceEnvelopeId: pending.sourceEnvelopeId,
3049
+ requestRevision: pending.revision,
3050
+ decision
3051
+ })
3052
+ )
3053
+ )
3054
+ );
3055
+ }
3056
+ function latestApprovalRequest(tail) {
3057
+ if (tail === void 0) return void 0;
3058
+ for (let index = tail.entries.length - 1; index >= 0; index -= 1) {
3059
+ const entry = tail.entries[index];
3060
+ if (entry.event.type !== "approval_requested") continue;
3061
+ return {
3062
+ sourceEnvelopeId: entry.sourceEnvelopeId,
3063
+ revision: entry.revision,
3064
+ ...entry.event.approvalId === void 0 ? {} : { approvalId: entry.event.approvalId }
3065
+ };
3066
+ }
3067
+ return void 0;
3068
+ }
3069
+ async function recordedHostDecision(tenant, taskId, attempt, tail, request) {
3070
+ for (const decision of ["approve", "reject"]) {
3071
+ const sourceEnvelopeId = await approvalDecisionSource(tenant, taskId, attempt, request, decision);
3072
+ const index = tail?.entries.findIndex((candidate) => candidate.sourceEnvelopeId === sourceEnvelopeId) ?? -1;
3073
+ const entry = index < 0 ? void 0 : tail.entries[index];
3074
+ if (entry?.event.type !== "approval_resolved" || entry.event.resolvedBy !== "host") continue;
3075
+ const laterResolution = tail.entries.slice(index + 1).some(
3076
+ (candidate) => candidate.event.type === "approval_resolved" && (request.approvalId === void 0 || candidate.event.approvalId === request.approvalId)
3077
+ );
3078
+ if (!laterResolution) {
3079
+ if (entry.event.decision === "reject") {
3080
+ return {
3081
+ decision: "reject",
3082
+ ...entry.event.approvalId === void 0 ? {} : { approvalId: entry.event.approvalId },
3083
+ reason: entry.event.reason
3084
+ };
3085
+ }
3086
+ return {
3087
+ decision: "approve",
3088
+ ...entry.event.approvalId === void 0 ? {} : { approvalId: entry.event.approvalId }
3089
+ };
3090
+ }
3091
+ }
3092
+ return void 0;
3093
+ }
3094
+ async function resolveApproval(tenant, taskId, decision, opts) {
3095
+ const stores = tenantStoresFor(controlPlane(tenant), root);
3096
+ const attempt = await stores.tasks.get(taskId);
3097
+ if (attempt === void 0) {
3098
+ throw new ByokCloudError("task_not_found", `Task ${taskId} was not found for this tenant.`);
3099
+ }
3100
+ if (isTerminalAttemptStatus(attempt.status)) {
3101
+ throw new ByokCloudError(
3102
+ "task_not_awaiting_approval",
3103
+ `Task ${taskId} already reached ${attempt.status}; there is no approval left to ${decision}.`
3104
+ );
3105
+ }
3106
+ const tail = await stores.approvals.read(taskId);
3107
+ const pending = pendingApproval(tail);
3108
+ const latestRequest = latestApprovalRequest(tail);
3109
+ if (pending === void 0) {
3110
+ if (latestRequest !== void 0) {
3111
+ const recorded = await recordedHostDecision(tenant, taskId, attempt, tail, latestRequest);
3112
+ if (recorded !== void 0) {
3113
+ if (recorded.decision !== decision) {
3114
+ throw new ByokCloudError(
3115
+ "coordination_input_invalid",
3116
+ `Task ${taskId} already has the opposite host approval decision.`
3117
+ );
3118
+ }
3119
+ if (recorded.decision === "reject" && recorded.reason !== (opts?.reason ?? null)) {
3120
+ throw new ByokCloudError(
3121
+ "coordination_input_invalid",
3122
+ `Task ${taskId} already has a host rejection with a different immutable reason.`
3123
+ );
3124
+ }
3125
+ const ownerDeviceId2 = attempt.ownerDeviceId;
3126
+ if (ownerDeviceId2 === void 0) {
3127
+ throw new ByokCloudError(
3128
+ "task_not_awaiting_approval",
3129
+ `Task ${taskId} has no owning device to receive its recorded ${decision}.`
3130
+ );
3131
+ }
3132
+ const messageId2 = await approvalDecisionSource(tenant, taskId, attempt, latestRequest, decision);
3133
+ const targeting2 = recorded.approvalId === void 0 ? {} : { approvalId: recorded.approvalId };
3134
+ const recordedReason = recorded.decision === "reject" ? recorded.reason : void 0;
3135
+ return enqueueAgentControlEnvelope(
3136
+ tenant,
3137
+ ownerDeviceId2,
3138
+ messageId2,
3139
+ (seq) => decision === "approve" ? createEnvelope("task.approve", targeting2, { id: messageId2, taskId, seq }) : createEnvelope(
3140
+ "task.reject",
3141
+ { ...targeting2, ...recordedReason === null || recordedReason === void 0 ? {} : { reason: recordedReason } },
3142
+ { id: messageId2, taskId, seq }
3143
+ )
3144
+ );
3145
+ }
3146
+ }
3147
+ throw new ByokCloudError(
3148
+ "task_not_awaiting_approval",
3149
+ `Task ${taskId} has no pending approval to ${decision}.`
3150
+ );
3151
+ }
3152
+ if (opts?.approvalId !== void 0 && pending.approvalId !== void 0 && opts.approvalId !== pending.approvalId) {
3153
+ throw new StaleApprovalError(taskId, opts.approvalId, pending.approvalId);
3154
+ }
3155
+ const ownerDeviceId = attempt.ownerDeviceId;
3156
+ if (ownerDeviceId === void 0) {
3157
+ throw new ByokCloudError(
3158
+ "task_not_awaiting_approval",
3159
+ `Task ${taskId} has no owning device; no runtime is paused on an approval to ${decision}.`
3160
+ );
3161
+ }
3162
+ const approvalId = pending.approvalId;
3163
+ const messageId = await approvalDecisionSource(tenant, taskId, attempt, pending, decision);
3164
+ const event = decision === "reject" ? {
3165
+ type: "approval_resolved",
3166
+ ...approvalId === void 0 ? {} : { approvalId },
3167
+ decision: "reject",
3168
+ resolvedBy: "host",
3169
+ reason: opts?.reason ?? null,
3170
+ at: (/* @__PURE__ */ new Date()).toISOString()
3171
+ } : {
3172
+ type: "approval_resolved",
3173
+ ...approvalId === void 0 ? {} : { approvalId },
3174
+ decision: "approve",
3175
+ resolvedBy: "host",
3176
+ at: (/* @__PURE__ */ new Date()).toISOString()
3177
+ };
3178
+ const resolved = await root.cloud.approvals.resolvePending(tenant, {
3179
+ taskId,
3180
+ sourceEnvelopeId: messageId,
3181
+ expectedSourceEnvelopeId: pending.sourceEnvelopeId,
3182
+ expectedRevision: pending.revision,
3183
+ event
3184
+ });
3185
+ let deliveredApprovalId = approvalId;
3186
+ let deliveredReason = decision === "reject" ? opts?.reason ?? null : void 0;
3187
+ if (resolved.status === "conflict" || resolved.status === "superseded" || resolved.status === "absent") {
3188
+ const currentTail = resolved.tail ?? await stores.approvals.read(taskId);
3189
+ const currentPending = pendingApproval(currentTail);
3190
+ const recorded = await recordedHostDecision(tenant, taskId, attempt, currentTail, pending);
3191
+ if (recorded?.decision === decision) {
3192
+ deliveredApprovalId = recorded.approvalId;
3193
+ if (recorded.decision === "reject") {
3194
+ if (recorded.reason !== deliveredReason) {
3195
+ throw new ByokCloudError(
3196
+ "coordination_input_invalid",
3197
+ `Task ${taskId} already has a host rejection with a different immutable reason.`
3198
+ );
3199
+ }
3200
+ deliveredReason = recorded.reason;
3201
+ }
3202
+ } else if (recorded !== void 0) {
3203
+ throw new ByokCloudError(
3204
+ "coordination_input_invalid",
3205
+ `Task ${taskId} already has the opposite host approval decision.`
3206
+ );
3207
+ } else if (currentPending !== void 0 && opts?.approvalId !== void 0 && currentPending.approvalId !== void 0 && currentPending.approvalId !== opts.approvalId) {
3208
+ throw new StaleApprovalError(taskId, opts.approvalId, currentPending.approvalId);
3209
+ } else {
3210
+ throw new ByokCloudError(
3211
+ "task_not_awaiting_approval",
3212
+ `Task ${taskId} no longer has the pending approval to ${decision}.`
3213
+ );
3214
+ }
3215
+ }
3216
+ const targeting = deliveredApprovalId === void 0 ? {} : { approvalId: deliveredApprovalId };
3217
+ return enqueueAgentControlEnvelope(
3218
+ tenant,
3219
+ ownerDeviceId,
3220
+ messageId,
3221
+ (seq) => decision === "approve" ? createEnvelope("task.approve", targeting, { id: messageId, taskId, seq }) : createEnvelope(
3222
+ "task.reject",
3223
+ { ...targeting, ...deliveredReason === null || deliveredReason === void 0 ? {} : { reason: deliveredReason } },
3224
+ { id: messageId, taskId, seq }
3225
+ )
3226
+ );
3227
+ }
3228
+ async function steerTask(tenant, taskId, payload) {
3229
+ const stores = tenantStoresFor(controlPlane(tenant), root);
3230
+ const attempt = await stores.tasks.get(taskId);
3231
+ if (attempt === void 0) {
3232
+ throw new ByokCloudError("task_not_found", `Task ${taskId} was not found for this tenant.`);
3233
+ }
3234
+ if (isTerminalAttemptStatus(attempt.status)) {
3235
+ throw new SteerRejectedError(taskId, "task_terminal", attempt.status, attempt.claimedRuntime);
3236
+ }
3237
+ const ownerDeviceId = attempt.ownerDeviceId;
3238
+ if (attempt.status !== "running" || ownerDeviceId === void 0) {
3239
+ throw new SteerRejectedError(taskId, "task_not_running", attempt.status, attempt.claimedRuntime);
3240
+ }
3241
+ if (attempt.claimedRuntimeCapabilities?.steer !== true) {
3242
+ throw new SteerRejectedError(
3243
+ taskId,
3244
+ "steer_unsupported_runtime",
3245
+ attempt.status,
3246
+ attempt.claimedRuntime
3247
+ );
3248
+ }
3249
+ const messageId = options.crypto.randomUuid();
3250
+ return enqueueAgentControlEnvelope(
3251
+ tenant,
3252
+ ownerDeviceId,
3253
+ messageId,
3254
+ (seq) => createEnvelope("task.steer", { text: payload.text }, { id: messageId, taskId, seq })
3255
+ );
3256
+ }
2764
3257
  async function getAgentHomeProjectionStatus(tenant, deviceId, input) {
2765
3258
  return readAgentHomeProjectionStatus(
2766
3259
  tenantStoresFor(controlPlane(tenant), root).receipts,
@@ -3034,7 +3527,7 @@ function createByokCloud(options) {
3034
3527
  ]);
3035
3528
  const stores = tenantStoresFor(controlPlane(tenant), root);
3036
3529
  const persisted = await stores.receipts.record({
3037
- key: `agent-content-request:${deviceId}:${payload.requestId}`,
3530
+ key: agentReliabilityKey("agent-content-request", deviceId, payload.agentRef, payload.requestId),
3038
3531
  body: JSON.stringify(payload)
3039
3532
  });
3040
3533
  if (!persisted.created && persisted.receipt.body !== JSON.stringify(payload)) {
@@ -3043,11 +3536,18 @@ function createByokCloud(options) {
3043
3536
  `Request ${payload.requestId} already exists with a different immutable content-read body.`
3044
3537
  );
3045
3538
  }
3539
+ const messageId = await agentControlEnvelopeId(
3540
+ "byok:agent-content-read",
3541
+ tenant,
3542
+ deviceId,
3543
+ payload.agentRef,
3544
+ payload.requestId
3545
+ );
3046
3546
  const control = await enqueueAgentControlEnvelope(
3047
3547
  tenant,
3048
3548
  deviceId,
3049
- payload.requestId,
3050
- (seq) => createEnvelope("agent.content.read", payload, { id: payload.requestId, seq })
3549
+ messageId,
3550
+ (seq) => createEnvelope("agent.content.read", payload, { id: messageId, seq })
3051
3551
  );
3052
3552
  if (control.envelope.type !== "agent.content.read" || JSON.stringify(control.envelope.payload) !== JSON.stringify(payload)) {
3053
3553
  throw new ByokCloudError(
@@ -3066,7 +3566,7 @@ function createByokCloud(options) {
3066
3566
  const stores = tenantStoresFor(controlPlane(tenant), root);
3067
3567
  const requestBody = JSON.stringify(payload);
3068
3568
  const persisted = await stores.receipts.record({
3069
- key: agentHomeProjectionRequestKey(deviceId, payload.requestId),
3569
+ key: agentHomeProjectionRequestKey(deviceId, payload.agentRef, payload.requestId),
3070
3570
  body: requestBody
3071
3571
  });
3072
3572
  const persistedPayload = AgentHomeProjectionPayloadSchema.parse(JSON.parse(persisted.receipt.body));
@@ -3076,11 +3576,18 @@ function createByokCloud(options) {
3076
3576
  `Agent-home projection request ${payload.requestId} already exists with a different immutable desired body.`
3077
3577
  );
3078
3578
  }
3579
+ const messageId = await agentControlEnvelopeId(
3580
+ "byok:agent-home-projection",
3581
+ tenant,
3582
+ deviceId,
3583
+ payload.agentRef,
3584
+ payload.requestId
3585
+ );
3079
3586
  const control = await enqueueAgentControlEnvelope(
3080
3587
  tenant,
3081
3588
  deviceId,
3082
- payload.requestId,
3083
- (seq) => createEnvelope("agent.home.projection", payload, { id: payload.requestId, seq })
3589
+ messageId,
3590
+ (seq) => createEnvelope("agent.home.projection", payload, { id: messageId, seq })
3084
3591
  );
3085
3592
  if (control.envelope.type !== "agent.home.projection" || !sameAgentHomeProjectionRequest(payload, control.envelope.payload)) {
3086
3593
  throw new ByokCloudError(
@@ -3104,6 +3611,13 @@ function createByokCloud(options) {
3104
3611
  getAgentHomeProjectionStatus,
3105
3612
  completeAgentHomeProjection,
3106
3613
  eraseAgentMemoryProjection,
3614
+ approveTask(tenant, taskId, opts) {
3615
+ return resolveApproval(tenant, taskId, "approve", opts);
3616
+ },
3617
+ rejectTask(tenant, taskId, opts) {
3618
+ return resolveApproval(tenant, taskId, "reject", opts);
3619
+ },
3620
+ steerTask,
3107
3621
  async cancelTask(tenant, taskId, reason) {
3108
3622
  const stores = tenantStoresFor(controlPlane(tenant), root);
3109
3623
  const proposedMessageId = options.crypto.randomUuid();
@@ -3143,11 +3657,14 @@ function createByokCloud(options) {
3143
3657
  readTaskAttempt(tenant, taskId) {
3144
3658
  return tenantStoresFor(controlPlane(tenant), root).tasks.get(taskId);
3145
3659
  },
3660
+ listTaskAttempts(tenant, query) {
3661
+ return tenantStoresFor(controlPlane(tenant), root).tasks.list(query);
3662
+ },
3146
3663
  readTerminalReceipt(tenant, taskId) {
3147
3664
  return tenantStoresFor(controlPlane(tenant), root).receipts.get(terminalReceiptKey(taskId));
3148
3665
  },
3149
- readAgentEgress(tenant, deviceId, eventId) {
3150
- return tenantStoresFor(controlPlane(tenant), root).egress.get(deviceId, eventId);
3666
+ readAgentEgress(tenant, deviceId, agentRef, eventId) {
3667
+ return tenantStoresFor(controlPlane(tenant), root).egress.get(deviceId, agentRef, eventId);
3151
3668
  },
3152
3669
  async readTaskResult(tenant, taskId) {
3153
3670
  const stores = tenantStoresFor(controlPlane(tenant), root);
@@ -3534,7 +4051,15 @@ var InMemoryInboundDedupStore = class {
3534
4051
  this.#capacity = capacity;
3535
4052
  }
3536
4053
  async checkAndRecord(tenant, deviceId, envelopeId) {
3537
- const key = tenantKey(tenant, deviceId);
4054
+ return this.#checkAndRecord(tenantKey(tenant, deviceId), envelopeId);
4055
+ }
4056
+ async checkAndRecordAgent(tenant, deviceId, agentRef, envelopeId) {
4057
+ return this.#checkAndRecord(
4058
+ tenantKey(tenant, agentReliabilityKey("inbound-dedup", deviceId, agentRef, "")),
4059
+ envelopeId
4060
+ );
4061
+ }
4062
+ #checkAndRecord(key, envelopeId) {
3538
4063
  let ring = this.#rings.get(key);
3539
4064
  if (ring === void 0) {
3540
4065
  ring = /* @__PURE__ */ new Set();
@@ -3777,22 +4302,23 @@ var InMemoryTaskAttemptStore = class {
3777
4302
  async reserveAgentMessage(tenant, input) {
3778
4303
  const key = tenantKey(tenant, input.taskId);
3779
4304
  return this.#state.mutate(key, () => {
3780
- const attempt = this.#state.attempts.get(key);
3781
- if (attempt === void 0 || attempt.deviceId !== input.deviceId || attempt.cancellation !== void 0 || !["offered", "claimed", "running"].includes(attempt.status)) return "rejected";
3782
4305
  const existing = this.#state.messageAdmissions.get(key);
3783
- if (existing === void 0) {
3784
- this.#state.messageAdmissions.set(key, {
3785
- messageId: input.messageId,
3786
- payloadBody: input.payloadBody
3787
- });
3788
- return "reserved";
4306
+ if (existing !== void 0) {
4307
+ return existing.deviceId === input.deviceId && existing.messageId === input.messageId && existing.payloadBody === input.payloadBody ? "pending" : "rejected";
3789
4308
  }
3790
- return existing.messageId === input.messageId && existing.payloadBody === input.payloadBody ? "pending" : "rejected";
4309
+ const attempt = this.#state.attempts.get(key);
4310
+ if (attempt === void 0 || attempt.deviceId !== input.deviceId || attempt.cancellation !== void 0 || !["offered", "claimed", "running"].includes(attempt.status)) return "rejected";
4311
+ this.#state.messageAdmissions.set(key, {
4312
+ deviceId: input.deviceId,
4313
+ messageId: input.messageId,
4314
+ payloadBody: input.payloadBody
4315
+ });
4316
+ return "reserved";
3791
4317
  });
3792
4318
  }
3793
4319
  async readAgentMessage(tenant, input) {
3794
4320
  const admission = this.#state.messageAdmissions.get(tenantKey(tenant, input.taskId));
3795
- return admission !== void 0 && admission.messageId === input.messageId && admission.payloadBody === input.payloadBody ? admission : void 0;
4321
+ return admission !== void 0 && admission.deviceId === input.deviceId && admission.messageId === input.messageId && admission.payloadBody === input.payloadBody ? admission : void 0;
3796
4322
  }
3797
4323
  async finalizeAgentMessage(tenant, input) {
3798
4324
  const key = tenantKey(tenant, input.taskId);
@@ -3800,7 +4326,7 @@ var InMemoryTaskAttemptStore = class {
3800
4326
  }
3801
4327
  #terminalize(key, input) {
3802
4328
  const admission = this.#state.messageAdmissions.get(key);
3803
- if (admission === void 0 || admission.messageId !== input.messageId || admission.payloadBody !== input.payloadBody) {
4329
+ if (admission === void 0 || admission.deviceId !== input.deviceId || admission.messageId !== input.messageId || admission.payloadBody !== input.payloadBody) {
3804
4330
  return void 0;
3805
4331
  }
3806
4332
  if (admission.terminalBody !== void 0) return admission;
@@ -3817,15 +4343,29 @@ var InMemoryTaskAttemptStore = class {
3817
4343
  return attempt === void 0 ? [] : [attempt];
3818
4344
  });
3819
4345
  }
4346
+ async list(tenant, query) {
4347
+ assertTaskAttemptListLimit(query.limit);
4348
+ const cursor = query.cursor;
4349
+ 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);
4350
+ const attempts = matches.slice(0, query.limit);
4351
+ const hasMore = matches.length > attempts.length;
4352
+ const last = attempts.at(-1);
4353
+ return {
4354
+ attempts,
4355
+ ...hasMore && last !== void 0 ? { nextCursor: last.taskId } : {}
4356
+ };
4357
+ }
3820
4358
  async claim(tenant, input) {
3821
4359
  const key = tenantKey(tenant, input.taskId);
3822
4360
  return this.#state.mutate(key, () => {
3823
4361
  const existing = this.#state.attempts.get(key);
3824
4362
  if (existing === void 0) return void 0;
3825
- if (existing.ownerDeviceId !== void 0 || existing.cancellation !== void 0 || existing.status !== "offered") return existing;
4363
+ if (existing.deviceId !== input.deviceId || existing.ownerDeviceId !== void 0 || existing.cancellation !== void 0 || existing.status !== "offered") return existing;
3826
4364
  const claimed = {
3827
4365
  ...existing,
3828
4366
  ownerDeviceId: input.deviceId,
4367
+ ...input.runtime === void 0 ? {} : { claimedRuntime: input.runtime },
4368
+ ...input.capabilities === void 0 ? {} : { claimedRuntimeCapabilities: { ...input.capabilities } },
3829
4369
  status: "claimed",
3830
4370
  updatedAt: this.#now()
3831
4371
  };
@@ -3948,6 +4488,14 @@ var InMemoryActivityStore = class {
3948
4488
  const key = activityTailKey(tenant, input.taskId);
3949
4489
  const existing = this.#tails.get(key);
3950
4490
  const live = existing !== void 0 && receivedAt < existing.expiresAt ? existing : void 0;
4491
+ const sourceState = activitySourceBatchState(live?.entries ?? [], input);
4492
+ if (sourceState === "same") return live;
4493
+ if (sourceState === "conflict") {
4494
+ throw new ByokCloudError(
4495
+ "coordination_input_invalid",
4496
+ `Activity source envelope ${input.sourceEnvelopeId} already belongs to another canonical batch.`
4497
+ );
4498
+ }
3951
4499
  const incoming = projectTimelineEvents(input, receivedAt);
3952
4500
  for (const next of incoming) {
3953
4501
  const collision = live?.entries.find(
@@ -4039,6 +4587,46 @@ var InMemoryApprovalTimelineStore = class {
4039
4587
  this.#tails.set(key, tail);
4040
4588
  return tail;
4041
4589
  }
4590
+ async resolvePending(tenant, input) {
4591
+ const { capacity, ttlMs, event } = validateApprovalTimelineResolve(input);
4592
+ const now = this.clock.now();
4593
+ const receivedAt = now.toISOString();
4594
+ const key = approvalTimelineKey(tenant, input.taskId);
4595
+ const existing = this.#tails.get(key);
4596
+ const live = existing !== void 0 && receivedAt < existing.expiresAt ? existing : void 0;
4597
+ if (live === void 0) return { status: "absent" };
4598
+ const duplicate = live.entries.find((entry) => entry.sourceEnvelopeId === input.sourceEnvelopeId);
4599
+ if (duplicate !== void 0) {
4600
+ if (JSON.stringify(duplicate.event) !== JSON.stringify(event)) return { status: "conflict", tail: live };
4601
+ return { status: "replayed", tail: live };
4602
+ }
4603
+ const pending = pendingObservation(live.entries);
4604
+ if (pending === void 0) return { status: "absent", tail: live };
4605
+ if (pending.sourceEnvelopeId !== input.expectedSourceEnvelopeId || pending.revision !== input.expectedRevision) {
4606
+ return { status: "superseded", tail: live };
4607
+ }
4608
+ const revision = (live.cursor ?? 0) + 1;
4609
+ const observation = ApprovalObservationSchema.parse({
4610
+ taskId: input.taskId,
4611
+ sourceEnvelopeId: input.sourceEnvelopeId,
4612
+ revision,
4613
+ receivedAt,
4614
+ event
4615
+ });
4616
+ const allEntries = [...live.entries, observation];
4617
+ const evicted = Math.max(allEntries.length - capacity, 0);
4618
+ const tail = {
4619
+ tenantId: tenant,
4620
+ taskId: input.taskId,
4621
+ entries: allEntries.slice(evicted),
4622
+ cursor: revision,
4623
+ dropped: live.dropped + evicted,
4624
+ capacity,
4625
+ expiresAt: new Date(now.getTime() + ttlMs).toISOString()
4626
+ };
4627
+ this.#tails.set(key, tail);
4628
+ return { status: "applied", tail };
4629
+ }
4042
4630
  async read(tenant, taskId) {
4043
4631
  const key = approvalTimelineKey(tenant, taskId);
4044
4632
  const tail = this.#tails.get(key);
@@ -4050,6 +4638,19 @@ var InMemoryApprovalTimelineStore = class {
4050
4638
  return tail;
4051
4639
  }
4052
4640
  };
4641
+ function pendingObservation(entries) {
4642
+ let pending;
4643
+ for (const entry of entries) {
4644
+ if (entry.event.type === "approval_requested") {
4645
+ pending = entry;
4646
+ continue;
4647
+ }
4648
+ if (pending !== void 0 && (pending.event.approvalId === void 0 || pending.event.approvalId === entry.event.approvalId)) {
4649
+ pending = void 0;
4650
+ }
4651
+ }
4652
+ return pending;
4653
+ }
4053
4654
  var InMemoryAgentEgressStore = class {
4054
4655
  #records = /* @__PURE__ */ new Map();
4055
4656
  #clock;
@@ -4057,7 +4658,10 @@ var InMemoryAgentEgressStore = class {
4057
4658
  this.#clock = clock;
4058
4659
  }
4059
4660
  async record(tenant, input) {
4060
- const key = tenantKey(tenant, `${input.deviceId}\0${input.payload.eventId}`);
4661
+ const key = tenantKey(
4662
+ tenant,
4663
+ agentReliabilityKey("agent-egress", input.deviceId, input.payload.agentRef, input.payload.eventId)
4664
+ );
4061
4665
  const existing = this.#records.get(key);
4062
4666
  if (existing !== void 0) return { record: existing, created: false };
4063
4667
  const record = {
@@ -4070,8 +4674,8 @@ var InMemoryAgentEgressStore = class {
4070
4674
  this.#records.set(key, record);
4071
4675
  return { record, created: true };
4072
4676
  }
4073
- async get(tenant, deviceId, eventId) {
4074
- return this.#records.get(tenantKey(tenant, `${deviceId}\0${eventId}`));
4677
+ async get(tenant, deviceId, agentRef, eventId) {
4678
+ return this.#records.get(tenantKey(tenant, agentReliabilityKey("agent-egress", deviceId, agentRef, eventId)));
4075
4679
  }
4076
4680
  };
4077
4681
 
@@ -4138,7 +4742,9 @@ function createInMemoryByokCloud(options = {}) {
4138
4742
  ...options.truthCommitter === void 0 ? {} : { truthCommitter: options.truthCommitter },
4139
4743
  ...options.truthObjectDownloads === void 0 ? {} : { truthObjectDownloads: options.truthObjectDownloads },
4140
4744
  ...options.operatorId !== void 0 ? { operatorId: options.operatorId } : {},
4745
+ ...options.instanceProductId === void 0 ? {} : { instanceProductId: options.instanceProductId },
4141
4746
  ...options.agentMessage === void 0 ? {} : { agentMessage: options.agentMessage },
4747
+ ...options.observer === void 0 ? {} : { observer: options.observer },
4142
4748
  ...options.agentMemoryProjectionAuthorizer === void 0 ? {} : { agentMemoryProjectionAuthorizer: options.agentMemoryProjectionAuthorizer },
4143
4749
  ...options.agentMemoryProjectionStore === void 0 ? {} : { agentMemoryProjectionStore: options.agentMemoryProjectionStore },
4144
4750
  ...options.maxBlobSizeBytes !== void 0 ? { maxBlobSizeBytes: options.maxBlobSizeBytes } : {},
@@ -4505,12 +5111,12 @@ function parseSseFrame(frame) {
4505
5111
  // src/stores/ports-contract.ts
4506
5112
  var CLOUD_PORT_METHODS = {
4507
5113
  activity: ["append", "read"],
4508
- approvals: ["append", "read"],
5114
+ approvals: ["append", "read", "resolvePending"],
4509
5115
  devices: ["register", "get", "revoke", "list", "readiness", "recordCapabilities", "resolveByDeviceId"],
4510
5116
  pairingCodes: ["issue"],
4511
5117
  pairing: ["redeemAndRegister"],
4512
5118
  nonces: ["issue", "consumeIfValid"],
4513
- dedup: ["checkAndRecord"],
5119
+ dedup: ["checkAndRecord", "checkAndRecordAgent"],
4514
5120
  tasks: [
4515
5121
  "open",
4516
5122
  "reserveAgentOffer",
@@ -4519,6 +5125,7 @@ var CLOUD_PORT_METHODS = {
4519
5125
  "finalizeAgentMessage",
4520
5126
  "get",
4521
5127
  "getMany",
5128
+ "list",
4522
5129
  "claim",
4523
5130
  "recordStatus"
4524
5131
  ],
@@ -4549,6 +5156,6 @@ var CLOUD_PORT_INTERFACES = {
4549
5156
  rateLimiter: "InboundRateLimiter"
4550
5157
  };
4551
5158
 
4552
- 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 };
5159
+ 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 };
4553
5160
  //# sourceMappingURL=index.js.map
4554
5161
  //# sourceMappingURL=index.js.map