@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/activity.d.ts +81 -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 +47 -10
- package/dist/index.d.ts +11 -7
- package/dist/index.js +761 -154
- 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,25 +1434,28 @@ 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";
|
|
1281
1455
|
const attempt = await stores.tasks.get(taskId);
|
|
1282
|
-
if (attempt
|
|
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
|
|
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,33 +1576,67 @@ 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
|
-
|
|
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
|
-
...
|
|
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
|
|
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
|
|
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: {
|
|
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 =
|
|
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 (
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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;
|
|
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
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
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:
|
|
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
|
-
|
|
3050
|
-
(seq) => createEnvelope("agent.content.read", payload, { id:
|
|
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
|
-
|
|
3083
|
-
(seq) => createEnvelope("agent.home.projection", payload, { id:
|
|
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
|
-
|
|
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
|
|
3784
|
-
|
|
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
|
-
|
|
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(
|
|
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,
|
|
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
|