@byok-sdk/cloud 0.12.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/activity.d.ts +13 -0
- package/dist/agent-home-projections.d.ts +2 -2
- package/dist/agent-reliability.d.ts +9 -0
- package/dist/approval-control.d.ts +75 -0
- package/dist/approval-timeline.d.ts +60 -14
- package/dist/auth/bearer.d.ts +17 -0
- package/dist/cloud.d.ts +116 -5
- package/dist/composition/in-memory.d.ts +4 -0
- package/dist/coordination.d.ts +8 -0
- package/dist/errors.d.ts +9 -0
- package/dist/handlers/events.d.ts +7 -1
- package/dist/handlers/messages.d.ts +7 -0
- package/dist/inbound.d.ts +42 -4
- package/dist/index.d.ts +11 -7
- package/dist/index.js +747 -145
- package/dist/index.js.map +1 -1
- package/dist/steer-control.d.ts +81 -0
- package/dist/stores/in-memory/agent-egress.d.ts +1 -1
- package/dist/stores/in-memory/approval-timeline.d.ts +2 -1
- package/dist/stores/in-memory/dedup.d.ts +8 -5
- package/dist/stores/in-memory/task-attempts.d.ts +10 -2
- package/dist/stores/ports.d.ts +106 -9
- package/dist/tenant-stores.d.ts +8 -1
- package/package.json +3 -3
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,
|
|
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
|
|
1022
|
-
z.
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
z.
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
1354
|
+
function contentRequestReceiptKey(deviceId, agentRef, requestId) {
|
|
1355
|
+
return agentReliabilityKey("agent-content-request", deviceId, agentRef, requestId);
|
|
1200
1356
|
}
|
|
1201
|
-
function contentReceiptKey(deviceId, requestId) {
|
|
1202
|
-
return
|
|
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.
|
|
1241
|
-
|
|
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
|
|
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
|
-
|
|
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,19 +1434,21 @@ async function handleInboundEnvelope(stores, deviceId, envelope, activityBounds
|
|
|
1262
1434
|
payload: envelope.payload,
|
|
1263
1435
|
receiptId: crypto.randomUUID()
|
|
1264
1436
|
});
|
|
1265
|
-
|
|
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
|
-
|
|
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";
|
|
@@ -1284,6 +1458,7 @@ async function handleInboundEnvelope(stores, deviceId, envelope, activityBounds
|
|
|
1284
1458
|
if (attempt?.agentRef !== void 0 && agentRefEchoRequired(envelope) && !sameAgentRef(attempt.agentRef, inboundAgentRef(envelope))) {
|
|
1285
1459
|
return "rejected";
|
|
1286
1460
|
}
|
|
1461
|
+
if (terminalStatus(envelope) !== void 0 && attempt === void 0) return "rejected";
|
|
1287
1462
|
if (envelope.type === "task.progress" && envelope.payload.events.length > 0) {
|
|
1288
1463
|
try {
|
|
1289
1464
|
validateActivityBatch(
|
|
@@ -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
|
|
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({
|
|
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
|
|
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
|
|
1539
|
+
await recordTerminal(stores, taskId, envelope);
|
|
1361
1540
|
return;
|
|
1362
1541
|
case "task.fail":
|
|
1363
|
-
await recordTerminal(stores, taskId, envelope
|
|
1542
|
+
await recordTerminal(stores, taskId, envelope);
|
|
1364
1543
|
return;
|
|
1365
1544
|
case "task.cancelled":
|
|
1366
|
-
await recordTerminal(stores, taskId, envelope
|
|
1545
|
+
await recordTerminal(stores, taskId, envelope);
|
|
1367
1546
|
return;
|
|
1368
1547
|
default:
|
|
1369
1548
|
return;
|
|
@@ -1397,14 +1576,18 @@ function approvalTimelineAppendInput(taskId, envelope) {
|
|
|
1397
1576
|
return void 0;
|
|
1398
1577
|
}
|
|
1399
1578
|
}
|
|
1400
|
-
async function recordTerminal(stores, taskId, envelope
|
|
1401
|
-
const
|
|
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
|
+
if (winner.status === "cancelled") {
|
|
1408
1591
|
await stores.tasks.recordStatus({
|
|
1409
1592
|
taskId,
|
|
1410
1593
|
status: "cancelled",
|
|
@@ -1413,17 +1596,40 @@ async function recordTerminal(stores, taskId, envelope, status) {
|
|
|
1413
1596
|
}
|
|
1414
1597
|
return;
|
|
1415
1598
|
}
|
|
1416
|
-
if (!created) return;
|
|
1417
|
-
const cause = envelope.type === "task.fail" || envelope.type === "task.cancelled" ? envelope.payload.reason : void 0;
|
|
1418
1599
|
const recordedAttempt = await stores.tasks.recordStatus({
|
|
1419
1600
|
taskId,
|
|
1420
|
-
status,
|
|
1601
|
+
status: winner.status,
|
|
1421
1602
|
...attempt?.agentRef === void 0 ? {} : { agentRef: attempt.agentRef },
|
|
1422
|
-
...cause === void 0 ? {} : { terminalCause: cause }
|
|
1603
|
+
...winner.cause === void 0 ? {} : { terminalCause: winner.cause }
|
|
1423
1604
|
});
|
|
1424
|
-
if (recordedAttempt?.status !== status || recordedAttempt.cancellation !== void 0) return;
|
|
1605
|
+
if (recordedAttempt?.status !== winner.status || recordedAttempt.cancellation !== void 0) return;
|
|
1425
1606
|
await projectTerminalToReview(stores.board, taskId);
|
|
1426
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
|
+
}
|
|
1427
1633
|
|
|
1428
1634
|
// src/handlers/messages.ts
|
|
1429
1635
|
var MESSAGES_JSON_BODY_MAX_BYTES = 2 * 1024 * 1024;
|
|
@@ -1444,7 +1650,8 @@ function messagesHandler(deps) {
|
|
|
1444
1650
|
device.deviceId,
|
|
1445
1651
|
envelope,
|
|
1446
1652
|
deps.activityBounds,
|
|
1447
|
-
envelope.type === "agent.message.publish" ? deps.agentMessage?.consume : void 0
|
|
1653
|
+
envelope.type === "agent.message.publish" ? deps.agentMessage?.consume : void 0,
|
|
1654
|
+
deps.observer
|
|
1448
1655
|
);
|
|
1449
1656
|
if (outcome === "rate_limited") return c.json({ error: "rate limit exceeded" }, 429);
|
|
1450
1657
|
if (envelope.type === "agent.message.publish") {
|
|
@@ -1454,7 +1661,7 @@ function messagesHandler(deps) {
|
|
|
1454
1661
|
}
|
|
1455
1662
|
}
|
|
1456
1663
|
if (envelope.type === "agent.egress.reliable" && (outcome === "accepted" || outcome === "duplicate")) {
|
|
1457
|
-
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);
|
|
1458
1665
|
if (record === void 0) {
|
|
1459
1666
|
throw new Error(`Accepted reliable egress ${envelope.payload.eventId} has no durable receipt record.`);
|
|
1460
1667
|
}
|
|
@@ -2273,11 +2480,11 @@ var CloudRouteRegistry = class {
|
|
|
2273
2480
|
return this.#app.fetch;
|
|
2274
2481
|
}
|
|
2275
2482
|
};
|
|
2276
|
-
function agentHomeProjectionRequestKey(deviceId, requestId) {
|
|
2277
|
-
return
|
|
2483
|
+
function agentHomeProjectionRequestKey(deviceId, agentRef, requestId) {
|
|
2484
|
+
return agentReliabilityKey("agent-home-projection-request", deviceId, agentRef, requestId);
|
|
2278
2485
|
}
|
|
2279
|
-
function agentHomeProjectionCompletionKey(deviceId, requestId) {
|
|
2280
|
-
return
|
|
2486
|
+
function agentHomeProjectionCompletionKey(deviceId, agentRef, requestId) {
|
|
2487
|
+
return agentReliabilityKey("agent-home-projection-completion", deviceId, agentRef, requestId);
|
|
2281
2488
|
}
|
|
2282
2489
|
function sameAgentHomeProjectionRequest(expected, actual) {
|
|
2283
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);
|
|
@@ -2311,7 +2518,7 @@ function parseCompletionBody(body) {
|
|
|
2311
2518
|
}
|
|
2312
2519
|
}
|
|
2313
2520
|
async function readAgentHomeProjectionStatus(receipts, tenant, deviceId, input) {
|
|
2314
|
-
const storedRequest = await receipts.get(agentHomeProjectionRequestKey(deviceId, input.requestId));
|
|
2521
|
+
const storedRequest = await receipts.get(agentHomeProjectionRequestKey(deviceId, input.agentRef, input.requestId));
|
|
2315
2522
|
if (storedRequest === void 0) return void 0;
|
|
2316
2523
|
const request = parseRequestBody(storedRequest.body);
|
|
2317
2524
|
if (!statusInputMatchesAgentHomeProjection(request, input)) {
|
|
@@ -2320,7 +2527,7 @@ async function readAgentHomeProjectionStatus(receipts, tenant, deviceId, input)
|
|
|
2320
2527
|
`Agent-home projection request ${input.requestId} does not match its immutable desired fact.`
|
|
2321
2528
|
);
|
|
2322
2529
|
}
|
|
2323
|
-
const storedCompletion = await receipts.get(agentHomeProjectionCompletionKey(deviceId, input.requestId));
|
|
2530
|
+
const storedCompletion = await receipts.get(agentHomeProjectionCompletionKey(deviceId, request.agentRef, input.requestId));
|
|
2324
2531
|
if (storedCompletion === void 0) {
|
|
2325
2532
|
return {
|
|
2326
2533
|
tenantId: tenant,
|
|
@@ -2350,7 +2557,7 @@ async function readAgentHomeProjectionStatus(receipts, tenant, deviceId, input)
|
|
|
2350
2557
|
}
|
|
2351
2558
|
async function recordAgentHomeProjectionCompletion(receipts, tenant, deviceId, receiptInput) {
|
|
2352
2559
|
const receipt = AgentHomeProjectionCompletionRequestSchema.parse(receiptInput);
|
|
2353
|
-
const storedRequest = await receipts.get(agentHomeProjectionRequestKey(deviceId, receipt.requestId));
|
|
2560
|
+
const storedRequest = await receipts.get(agentHomeProjectionRequestKey(deviceId, receipt.agentRef, receipt.requestId));
|
|
2354
2561
|
if (storedRequest === void 0) {
|
|
2355
2562
|
throw new ByokCloudError(
|
|
2356
2563
|
"agent_home_projection_request_not_found",
|
|
@@ -2366,7 +2573,7 @@ async function recordAgentHomeProjectionCompletion(receipts, tenant, deviceId, r
|
|
|
2366
2573
|
}
|
|
2367
2574
|
const body = JSON.stringify(receipt);
|
|
2368
2575
|
const storedCompletion = await receipts.record({
|
|
2369
|
-
key: agentHomeProjectionCompletionKey(deviceId, receipt.requestId),
|
|
2576
|
+
key: agentHomeProjectionCompletionKey(deviceId, request.agentRef, receipt.requestId),
|
|
2370
2577
|
body
|
|
2371
2578
|
});
|
|
2372
2579
|
if (!storedCompletion.created && storedCompletion.receipt.body !== body) {
|
|
@@ -2420,6 +2627,21 @@ function projectTerminalResult(taskId, receipt) {
|
|
|
2420
2627
|
...envelope.payload.usage !== void 0 ? { usage: envelope.payload.usage } : {},
|
|
2421
2628
|
recordedAt: receipt.recordedAt
|
|
2422
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
|
+
};
|
|
2423
2645
|
case "task.cancelled":
|
|
2424
2646
|
return {
|
|
2425
2647
|
taskId,
|
|
@@ -2444,9 +2666,25 @@ var DEFAULT_LONG_POLL_HOLD_MS = 5e4;
|
|
|
2444
2666
|
var DEFAULT_LONG_POLL_INTERVAL_MS = 250;
|
|
2445
2667
|
var DEFAULT_EVENTS_PAGE_LIMIT = 50;
|
|
2446
2668
|
var AGENT_HOME_CONTRACT_CAPABILITY = "agent-home-contract";
|
|
2669
|
+
function isTerminalAttemptStatus(status) {
|
|
2670
|
+
return status === "complete" || status === "failed" || status === "cancelled";
|
|
2671
|
+
}
|
|
2447
2672
|
function sameAgentRef2(expected, actual) {
|
|
2448
2673
|
return actual?.agentId === expected.agentId && actual.profileRevision === expected.profileRevision;
|
|
2449
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
|
+
}
|
|
2450
2688
|
function createByokCloud(options) {
|
|
2451
2689
|
const declaration = parseDeclaration(options.capabilities);
|
|
2452
2690
|
assertNoOverDeclaration(
|
|
@@ -2468,7 +2706,11 @@ function createByokCloud(options) {
|
|
|
2468
2706
|
});
|
|
2469
2707
|
const deviceRouteDeps = {
|
|
2470
2708
|
root,
|
|
2471
|
-
bearer: {
|
|
2709
|
+
bearer: {
|
|
2710
|
+
tokenSigner: options.tokenSigner,
|
|
2711
|
+
devices: options.cloud.devices,
|
|
2712
|
+
...options.instanceProductId === void 0 ? {} : { instanceProductId: options.instanceProductId }
|
|
2713
|
+
}
|
|
2472
2714
|
};
|
|
2473
2715
|
const registry = new CloudRouteRegistry();
|
|
2474
2716
|
const activityBounds = {
|
|
@@ -2513,7 +2755,8 @@ function createByokCloud(options) {
|
|
|
2513
2755
|
appendReliableEgressAck: enqueueReliableEgressAck,
|
|
2514
2756
|
appendContentReceiptAck: enqueueContentReceiptAck,
|
|
2515
2757
|
agentMessage: options.agentMessage,
|
|
2516
|
-
appendAgentMessageDisposition: enqueueAgentMessageDisposition
|
|
2758
|
+
appendAgentMessageDisposition: enqueueAgentMessageDisposition,
|
|
2759
|
+
observer: options.observer
|
|
2517
2760
|
})
|
|
2518
2761
|
);
|
|
2519
2762
|
}
|
|
@@ -2645,56 +2888,60 @@ function createByokCloud(options) {
|
|
|
2645
2888
|
async function enqueueTaskEnvelope(tenant, deviceId, requestedTaskId, agentRef, buildEnvelope, beforeMailbox) {
|
|
2646
2889
|
const stores = tenantStoresFor(controlPlane(tenant), root);
|
|
2647
2890
|
const taskId = requestedTaskId ?? `task_${options.crypto.randomUuid()}`;
|
|
2648
|
-
const messageId =
|
|
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
|
+
);
|
|
2649
2898
|
const reservation = agentRef === void 0 ? void 0 : await stores.tasks.reserveAgentOffer({ taskId, deviceId, agentRef });
|
|
2650
|
-
const openedBeforeAppend = reservation?.attempt;
|
|
2651
|
-
if (
|
|
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))) {
|
|
2652
2901
|
throw new ByokCloudError(
|
|
2653
|
-
"agent_ref_mismatch",
|
|
2654
|
-
`Task ${taskId} already has a different durable
|
|
2902
|
+
agentRef === void 0 ? "coordination_input_invalid" : "agent_ref_mismatch",
|
|
2903
|
+
`Task ${taskId} already has a different durable target or Agent identity.`
|
|
2655
2904
|
);
|
|
2656
2905
|
}
|
|
2657
|
-
|
|
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) {
|
|
2658
2909
|
throw new ByokCloudError(
|
|
2659
2910
|
"agent_task_already_exists",
|
|
2660
2911
|
`Task ${taskId} already has a durable Agent attempt and cannot be enqueued again.`
|
|
2661
2912
|
);
|
|
2662
2913
|
}
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
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;
|
|
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
|
+
);
|
|
2697
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
|
+
});
|
|
2698
2945
|
const envelope = decodeEnvelope(message.body);
|
|
2699
2946
|
if (envelope.seq !== message.seq) {
|
|
2700
2947
|
throw new ByokCloudError(
|
|
@@ -2702,11 +2949,22 @@ function createByokCloud(options) {
|
|
|
2702
2949
|
`Mailbox stored offer seq ${String(envelope.seq)} at row ${message.seq}; the daemon's redelivery cursor would be wrong.`
|
|
2703
2950
|
);
|
|
2704
2951
|
}
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
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 };
|
|
2710
2968
|
}
|
|
2711
2969
|
async function assertAgentCapabilities(tenant, deviceId, required) {
|
|
2712
2970
|
const device = await options.cloud.devices.get(tenant, deviceId);
|
|
@@ -2761,6 +3019,236 @@ function createByokCloud(options) {
|
|
|
2761
3019
|
}
|
|
2762
3020
|
return { seq: message.seq, envelope };
|
|
2763
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
|
+
}
|
|
2764
3252
|
async function getAgentHomeProjectionStatus(tenant, deviceId, input) {
|
|
2765
3253
|
return readAgentHomeProjectionStatus(
|
|
2766
3254
|
tenantStoresFor(controlPlane(tenant), root).receipts,
|
|
@@ -3034,7 +3522,7 @@ function createByokCloud(options) {
|
|
|
3034
3522
|
]);
|
|
3035
3523
|
const stores = tenantStoresFor(controlPlane(tenant), root);
|
|
3036
3524
|
const persisted = await stores.receipts.record({
|
|
3037
|
-
key:
|
|
3525
|
+
key: agentReliabilityKey("agent-content-request", deviceId, payload.agentRef, payload.requestId),
|
|
3038
3526
|
body: JSON.stringify(payload)
|
|
3039
3527
|
});
|
|
3040
3528
|
if (!persisted.created && persisted.receipt.body !== JSON.stringify(payload)) {
|
|
@@ -3043,11 +3531,18 @@ function createByokCloud(options) {
|
|
|
3043
3531
|
`Request ${payload.requestId} already exists with a different immutable content-read body.`
|
|
3044
3532
|
);
|
|
3045
3533
|
}
|
|
3534
|
+
const messageId = await agentControlEnvelopeId(
|
|
3535
|
+
"byok:agent-content-read",
|
|
3536
|
+
tenant,
|
|
3537
|
+
deviceId,
|
|
3538
|
+
payload.agentRef,
|
|
3539
|
+
payload.requestId
|
|
3540
|
+
);
|
|
3046
3541
|
const control = await enqueueAgentControlEnvelope(
|
|
3047
3542
|
tenant,
|
|
3048
3543
|
deviceId,
|
|
3049
|
-
|
|
3050
|
-
(seq) => createEnvelope("agent.content.read", payload, { id:
|
|
3544
|
+
messageId,
|
|
3545
|
+
(seq) => createEnvelope("agent.content.read", payload, { id: messageId, seq })
|
|
3051
3546
|
);
|
|
3052
3547
|
if (control.envelope.type !== "agent.content.read" || JSON.stringify(control.envelope.payload) !== JSON.stringify(payload)) {
|
|
3053
3548
|
throw new ByokCloudError(
|
|
@@ -3066,7 +3561,7 @@ function createByokCloud(options) {
|
|
|
3066
3561
|
const stores = tenantStoresFor(controlPlane(tenant), root);
|
|
3067
3562
|
const requestBody = JSON.stringify(payload);
|
|
3068
3563
|
const persisted = await stores.receipts.record({
|
|
3069
|
-
key: agentHomeProjectionRequestKey(deviceId, payload.requestId),
|
|
3564
|
+
key: agentHomeProjectionRequestKey(deviceId, payload.agentRef, payload.requestId),
|
|
3070
3565
|
body: requestBody
|
|
3071
3566
|
});
|
|
3072
3567
|
const persistedPayload = AgentHomeProjectionPayloadSchema.parse(JSON.parse(persisted.receipt.body));
|
|
@@ -3076,11 +3571,18 @@ function createByokCloud(options) {
|
|
|
3076
3571
|
`Agent-home projection request ${payload.requestId} already exists with a different immutable desired body.`
|
|
3077
3572
|
);
|
|
3078
3573
|
}
|
|
3574
|
+
const messageId = await agentControlEnvelopeId(
|
|
3575
|
+
"byok:agent-home-projection",
|
|
3576
|
+
tenant,
|
|
3577
|
+
deviceId,
|
|
3578
|
+
payload.agentRef,
|
|
3579
|
+
payload.requestId
|
|
3580
|
+
);
|
|
3079
3581
|
const control = await enqueueAgentControlEnvelope(
|
|
3080
3582
|
tenant,
|
|
3081
3583
|
deviceId,
|
|
3082
|
-
|
|
3083
|
-
(seq) => createEnvelope("agent.home.projection", payload, { id:
|
|
3584
|
+
messageId,
|
|
3585
|
+
(seq) => createEnvelope("agent.home.projection", payload, { id: messageId, seq })
|
|
3084
3586
|
);
|
|
3085
3587
|
if (control.envelope.type !== "agent.home.projection" || !sameAgentHomeProjectionRequest(payload, control.envelope.payload)) {
|
|
3086
3588
|
throw new ByokCloudError(
|
|
@@ -3104,6 +3606,13 @@ function createByokCloud(options) {
|
|
|
3104
3606
|
getAgentHomeProjectionStatus,
|
|
3105
3607
|
completeAgentHomeProjection,
|
|
3106
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,
|
|
3107
3616
|
async cancelTask(tenant, taskId, reason) {
|
|
3108
3617
|
const stores = tenantStoresFor(controlPlane(tenant), root);
|
|
3109
3618
|
const proposedMessageId = options.crypto.randomUuid();
|
|
@@ -3143,11 +3652,14 @@ function createByokCloud(options) {
|
|
|
3143
3652
|
readTaskAttempt(tenant, taskId) {
|
|
3144
3653
|
return tenantStoresFor(controlPlane(tenant), root).tasks.get(taskId);
|
|
3145
3654
|
},
|
|
3655
|
+
listTaskAttempts(tenant, query) {
|
|
3656
|
+
return tenantStoresFor(controlPlane(tenant), root).tasks.list(query);
|
|
3657
|
+
},
|
|
3146
3658
|
readTerminalReceipt(tenant, taskId) {
|
|
3147
3659
|
return tenantStoresFor(controlPlane(tenant), root).receipts.get(terminalReceiptKey(taskId));
|
|
3148
3660
|
},
|
|
3149
|
-
readAgentEgress(tenant, deviceId, eventId) {
|
|
3150
|
-
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);
|
|
3151
3663
|
},
|
|
3152
3664
|
async readTaskResult(tenant, taskId) {
|
|
3153
3665
|
const stores = tenantStoresFor(controlPlane(tenant), root);
|
|
@@ -3534,7 +4046,15 @@ var InMemoryInboundDedupStore = class {
|
|
|
3534
4046
|
this.#capacity = capacity;
|
|
3535
4047
|
}
|
|
3536
4048
|
async checkAndRecord(tenant, deviceId, envelopeId) {
|
|
3537
|
-
|
|
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) {
|
|
3538
4058
|
let ring = this.#rings.get(key);
|
|
3539
4059
|
if (ring === void 0) {
|
|
3540
4060
|
ring = /* @__PURE__ */ new Set();
|
|
@@ -3777,22 +4297,23 @@ var InMemoryTaskAttemptStore = class {
|
|
|
3777
4297
|
async reserveAgentMessage(tenant, input) {
|
|
3778
4298
|
const key = tenantKey(tenant, input.taskId);
|
|
3779
4299
|
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
4300
|
const existing = this.#state.messageAdmissions.get(key);
|
|
3783
|
-
if (existing
|
|
3784
|
-
|
|
3785
|
-
messageId: input.messageId,
|
|
3786
|
-
payloadBody: input.payloadBody
|
|
3787
|
-
});
|
|
3788
|
-
return "reserved";
|
|
4301
|
+
if (existing !== void 0) {
|
|
4302
|
+
return existing.deviceId === input.deviceId && existing.messageId === input.messageId && existing.payloadBody === input.payloadBody ? "pending" : "rejected";
|
|
3789
4303
|
}
|
|
3790
|
-
|
|
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";
|
|
3791
4312
|
});
|
|
3792
4313
|
}
|
|
3793
4314
|
async readAgentMessage(tenant, input) {
|
|
3794
4315
|
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;
|
|
4316
|
+
return admission !== void 0 && admission.deviceId === input.deviceId && admission.messageId === input.messageId && admission.payloadBody === input.payloadBody ? admission : void 0;
|
|
3796
4317
|
}
|
|
3797
4318
|
async finalizeAgentMessage(tenant, input) {
|
|
3798
4319
|
const key = tenantKey(tenant, input.taskId);
|
|
@@ -3800,7 +4321,7 @@ var InMemoryTaskAttemptStore = class {
|
|
|
3800
4321
|
}
|
|
3801
4322
|
#terminalize(key, input) {
|
|
3802
4323
|
const admission = this.#state.messageAdmissions.get(key);
|
|
3803
|
-
if (admission === void 0 || admission.messageId !== input.messageId || admission.payloadBody !== input.payloadBody) {
|
|
4324
|
+
if (admission === void 0 || admission.deviceId !== input.deviceId || admission.messageId !== input.messageId || admission.payloadBody !== input.payloadBody) {
|
|
3804
4325
|
return void 0;
|
|
3805
4326
|
}
|
|
3806
4327
|
if (admission.terminalBody !== void 0) return admission;
|
|
@@ -3817,6 +4338,18 @@ var InMemoryTaskAttemptStore = class {
|
|
|
3817
4338
|
return attempt === void 0 ? [] : [attempt];
|
|
3818
4339
|
});
|
|
3819
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
|
+
}
|
|
3820
4353
|
async claim(tenant, input) {
|
|
3821
4354
|
const key = tenantKey(tenant, input.taskId);
|
|
3822
4355
|
return this.#state.mutate(key, () => {
|
|
@@ -3826,6 +4359,8 @@ var InMemoryTaskAttemptStore = class {
|
|
|
3826
4359
|
const claimed = {
|
|
3827
4360
|
...existing,
|
|
3828
4361
|
ownerDeviceId: input.deviceId,
|
|
4362
|
+
...input.runtime === void 0 ? {} : { claimedRuntime: input.runtime },
|
|
4363
|
+
...input.capabilities === void 0 ? {} : { claimedRuntimeCapabilities: { ...input.capabilities } },
|
|
3829
4364
|
status: "claimed",
|
|
3830
4365
|
updatedAt: this.#now()
|
|
3831
4366
|
};
|
|
@@ -3948,6 +4483,14 @@ var InMemoryActivityStore = class {
|
|
|
3948
4483
|
const key = activityTailKey(tenant, input.taskId);
|
|
3949
4484
|
const existing = this.#tails.get(key);
|
|
3950
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
|
+
}
|
|
3951
4494
|
const incoming = projectTimelineEvents(input, receivedAt);
|
|
3952
4495
|
for (const next of incoming) {
|
|
3953
4496
|
const collision = live?.entries.find(
|
|
@@ -4039,6 +4582,46 @@ var InMemoryApprovalTimelineStore = class {
|
|
|
4039
4582
|
this.#tails.set(key, tail);
|
|
4040
4583
|
return tail;
|
|
4041
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
|
+
}
|
|
4042
4625
|
async read(tenant, taskId) {
|
|
4043
4626
|
const key = approvalTimelineKey(tenant, taskId);
|
|
4044
4627
|
const tail = this.#tails.get(key);
|
|
@@ -4050,6 +4633,19 @@ var InMemoryApprovalTimelineStore = class {
|
|
|
4050
4633
|
return tail;
|
|
4051
4634
|
}
|
|
4052
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
|
+
}
|
|
4053
4649
|
var InMemoryAgentEgressStore = class {
|
|
4054
4650
|
#records = /* @__PURE__ */ new Map();
|
|
4055
4651
|
#clock;
|
|
@@ -4057,7 +4653,10 @@ var InMemoryAgentEgressStore = class {
|
|
|
4057
4653
|
this.#clock = clock;
|
|
4058
4654
|
}
|
|
4059
4655
|
async record(tenant, input) {
|
|
4060
|
-
const key = tenantKey(
|
|
4656
|
+
const key = tenantKey(
|
|
4657
|
+
tenant,
|
|
4658
|
+
agentReliabilityKey("agent-egress", input.deviceId, input.payload.agentRef, input.payload.eventId)
|
|
4659
|
+
);
|
|
4061
4660
|
const existing = this.#records.get(key);
|
|
4062
4661
|
if (existing !== void 0) return { record: existing, created: false };
|
|
4063
4662
|
const record = {
|
|
@@ -4070,8 +4669,8 @@ var InMemoryAgentEgressStore = class {
|
|
|
4070
4669
|
this.#records.set(key, record);
|
|
4071
4670
|
return { record, created: true };
|
|
4072
4671
|
}
|
|
4073
|
-
async get(tenant, deviceId, eventId) {
|
|
4074
|
-
return this.#records.get(tenantKey(tenant,
|
|
4672
|
+
async get(tenant, deviceId, agentRef, eventId) {
|
|
4673
|
+
return this.#records.get(tenantKey(tenant, agentReliabilityKey("agent-egress", deviceId, agentRef, eventId)));
|
|
4075
4674
|
}
|
|
4076
4675
|
};
|
|
4077
4676
|
|
|
@@ -4138,7 +4737,9 @@ function createInMemoryByokCloud(options = {}) {
|
|
|
4138
4737
|
...options.truthCommitter === void 0 ? {} : { truthCommitter: options.truthCommitter },
|
|
4139
4738
|
...options.truthObjectDownloads === void 0 ? {} : { truthObjectDownloads: options.truthObjectDownloads },
|
|
4140
4739
|
...options.operatorId !== void 0 ? { operatorId: options.operatorId } : {},
|
|
4740
|
+
...options.instanceProductId === void 0 ? {} : { instanceProductId: options.instanceProductId },
|
|
4141
4741
|
...options.agentMessage === void 0 ? {} : { agentMessage: options.agentMessage },
|
|
4742
|
+
...options.observer === void 0 ? {} : { observer: options.observer },
|
|
4142
4743
|
...options.agentMemoryProjectionAuthorizer === void 0 ? {} : { agentMemoryProjectionAuthorizer: options.agentMemoryProjectionAuthorizer },
|
|
4143
4744
|
...options.agentMemoryProjectionStore === void 0 ? {} : { agentMemoryProjectionStore: options.agentMemoryProjectionStore },
|
|
4144
4745
|
...options.maxBlobSizeBytes !== void 0 ? { maxBlobSizeBytes: options.maxBlobSizeBytes } : {},
|
|
@@ -4505,12 +5106,12 @@ function parseSseFrame(frame) {
|
|
|
4505
5106
|
// src/stores/ports-contract.ts
|
|
4506
5107
|
var CLOUD_PORT_METHODS = {
|
|
4507
5108
|
activity: ["append", "read"],
|
|
4508
|
-
approvals: ["append", "read"],
|
|
5109
|
+
approvals: ["append", "read", "resolvePending"],
|
|
4509
5110
|
devices: ["register", "get", "revoke", "list", "readiness", "recordCapabilities", "resolveByDeviceId"],
|
|
4510
5111
|
pairingCodes: ["issue"],
|
|
4511
5112
|
pairing: ["redeemAndRegister"],
|
|
4512
5113
|
nonces: ["issue", "consumeIfValid"],
|
|
4513
|
-
dedup: ["checkAndRecord"],
|
|
5114
|
+
dedup: ["checkAndRecord", "checkAndRecordAgent"],
|
|
4514
5115
|
tasks: [
|
|
4515
5116
|
"open",
|
|
4516
5117
|
"reserveAgentOffer",
|
|
@@ -4519,6 +5120,7 @@ var CLOUD_PORT_METHODS = {
|
|
|
4519
5120
|
"finalizeAgentMessage",
|
|
4520
5121
|
"get",
|
|
4521
5122
|
"getMany",
|
|
5123
|
+
"list",
|
|
4522
5124
|
"claim",
|
|
4523
5125
|
"recordStatus"
|
|
4524
5126
|
],
|
|
@@ -4549,6 +5151,6 @@ var CLOUD_PORT_INTERFACES = {
|
|
|
4549
5151
|
rateLimiter: "InboundRateLimiter"
|
|
4550
5152
|
};
|
|
4551
5153
|
|
|
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 };
|
|
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 };
|
|
4553
5155
|
//# sourceMappingURL=index.js.map
|
|
4554
5156
|
//# sourceMappingURL=index.js.map
|