@byok-sdk/cloud 0.13.0 → 0.15.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 +68 -0
- package/dist/inbound.d.ts +5 -6
- package/dist/index.js +114 -49
- package/dist/index.js.map +1 -1
- package/dist/offer-identity.d.ts +4 -0
- package/dist/stores/in-memory/device-directory.d.ts +2 -0
- package/dist/stores/in-memory/task-attempts.d.ts +1 -0
- package/dist/stores/ports.d.ts +5 -0
- package/dist/tenant-stores.d.ts +3 -0
- package/dist/terminal-result.d.ts +4 -1
- package/package.json +3 -3
package/dist/activity.d.ts
CHANGED
|
@@ -14,12 +14,46 @@ export declare const ActivityAppendRequestSchema: z.ZodObject<{
|
|
|
14
14
|
tool: z.ZodString;
|
|
15
15
|
input: z.ZodOptional<z.ZodUnknown>;
|
|
16
16
|
toolCallId: z.ZodOptional<z.ZodString>;
|
|
17
|
+
spill: z.ZodOptional<z.ZodObject<{
|
|
18
|
+
field: z.ZodEnum<{
|
|
19
|
+
input: "input";
|
|
20
|
+
output: "output";
|
|
21
|
+
}>;
|
|
22
|
+
totalBytes: z.ZodNumber;
|
|
23
|
+
omittedBytes: z.ZodNumber;
|
|
24
|
+
contentType: z.ZodLiteral<"application/json">;
|
|
25
|
+
blob: z.ZodOptional<z.ZodObject<{
|
|
26
|
+
blobId: z.ZodString;
|
|
27
|
+
contentHash: z.ZodString;
|
|
28
|
+
size: z.ZodNumber;
|
|
29
|
+
contentType: z.ZodString;
|
|
30
|
+
url: z.ZodOptional<z.ZodString>;
|
|
31
|
+
}, z.core.$strip>>;
|
|
32
|
+
unstoredReason: z.ZodOptional<z.ZodString>;
|
|
33
|
+
}, z.core.$strict>>;
|
|
17
34
|
}, z.core.$strip>, z.ZodObject<{
|
|
18
35
|
type: z.ZodLiteral<"tool_result">;
|
|
19
36
|
tool: z.ZodString;
|
|
20
37
|
output: z.ZodOptional<z.ZodUnknown>;
|
|
21
38
|
toolCallId: z.ZodOptional<z.ZodString>;
|
|
22
39
|
isError: z.ZodOptional<z.ZodBoolean>;
|
|
40
|
+
spill: z.ZodOptional<z.ZodObject<{
|
|
41
|
+
field: z.ZodEnum<{
|
|
42
|
+
input: "input";
|
|
43
|
+
output: "output";
|
|
44
|
+
}>;
|
|
45
|
+
totalBytes: z.ZodNumber;
|
|
46
|
+
omittedBytes: z.ZodNumber;
|
|
47
|
+
contentType: z.ZodLiteral<"application/json">;
|
|
48
|
+
blob: z.ZodOptional<z.ZodObject<{
|
|
49
|
+
blobId: z.ZodString;
|
|
50
|
+
contentHash: z.ZodString;
|
|
51
|
+
size: z.ZodNumber;
|
|
52
|
+
contentType: z.ZodString;
|
|
53
|
+
url: z.ZodOptional<z.ZodString>;
|
|
54
|
+
}, z.core.$strip>>;
|
|
55
|
+
unstoredReason: z.ZodOptional<z.ZodString>;
|
|
56
|
+
}, z.core.$strict>>;
|
|
23
57
|
}, z.core.$strip>, z.ZodObject<{
|
|
24
58
|
type: z.ZodLiteral<"artifact">;
|
|
25
59
|
name: z.ZodString;
|
|
@@ -59,12 +93,46 @@ export declare const TimelineEventSchema: z.ZodObject<{
|
|
|
59
93
|
tool: z.ZodString;
|
|
60
94
|
input: z.ZodOptional<z.ZodUnknown>;
|
|
61
95
|
toolCallId: z.ZodOptional<z.ZodString>;
|
|
96
|
+
spill: z.ZodOptional<z.ZodObject<{
|
|
97
|
+
field: z.ZodEnum<{
|
|
98
|
+
input: "input";
|
|
99
|
+
output: "output";
|
|
100
|
+
}>;
|
|
101
|
+
totalBytes: z.ZodNumber;
|
|
102
|
+
omittedBytes: z.ZodNumber;
|
|
103
|
+
contentType: z.ZodLiteral<"application/json">;
|
|
104
|
+
blob: z.ZodOptional<z.ZodObject<{
|
|
105
|
+
blobId: z.ZodString;
|
|
106
|
+
contentHash: z.ZodString;
|
|
107
|
+
size: z.ZodNumber;
|
|
108
|
+
contentType: z.ZodString;
|
|
109
|
+
url: z.ZodOptional<z.ZodString>;
|
|
110
|
+
}, z.core.$strip>>;
|
|
111
|
+
unstoredReason: z.ZodOptional<z.ZodString>;
|
|
112
|
+
}, z.core.$strict>>;
|
|
62
113
|
}, z.core.$strip>, z.ZodObject<{
|
|
63
114
|
type: z.ZodLiteral<"tool_result">;
|
|
64
115
|
tool: z.ZodString;
|
|
65
116
|
output: z.ZodOptional<z.ZodUnknown>;
|
|
66
117
|
toolCallId: z.ZodOptional<z.ZodString>;
|
|
67
118
|
isError: z.ZodOptional<z.ZodBoolean>;
|
|
119
|
+
spill: z.ZodOptional<z.ZodObject<{
|
|
120
|
+
field: z.ZodEnum<{
|
|
121
|
+
input: "input";
|
|
122
|
+
output: "output";
|
|
123
|
+
}>;
|
|
124
|
+
totalBytes: z.ZodNumber;
|
|
125
|
+
omittedBytes: z.ZodNumber;
|
|
126
|
+
contentType: z.ZodLiteral<"application/json">;
|
|
127
|
+
blob: z.ZodOptional<z.ZodObject<{
|
|
128
|
+
blobId: z.ZodString;
|
|
129
|
+
contentHash: z.ZodString;
|
|
130
|
+
size: z.ZodNumber;
|
|
131
|
+
contentType: z.ZodString;
|
|
132
|
+
url: z.ZodOptional<z.ZodString>;
|
|
133
|
+
}, z.core.$strip>>;
|
|
134
|
+
unstoredReason: z.ZodOptional<z.ZodString>;
|
|
135
|
+
}, z.core.$strict>>;
|
|
68
136
|
}, z.core.$strip>, z.ZodObject<{
|
|
69
137
|
type: z.ZodLiteral<"artifact">;
|
|
70
138
|
name: z.ZodString;
|
package/dist/inbound.d.ts
CHANGED
|
@@ -13,13 +13,12 @@
|
|
|
13
13
|
* authenticated long-poll `conn.hello` capability snapshot handled below.
|
|
14
14
|
* A server -> daemon type arriving inbound, or anything unrecognized, is
|
|
15
15
|
* rejected before it is dispatched or counted accepted.
|
|
16
|
-
* 2. **ownership** — an envelope
|
|
17
|
-
* device
|
|
16
|
+
* 2. **ownership** — an envelope must name an existing task targeted at this
|
|
17
|
+
* exact device, and a task already owned by a DIFFERENT device is dropped,
|
|
18
|
+
* never force-failed: force-failing on an authz mismatch
|
|
18
19
|
* would let an attacker who merely guesses a `taskId` kill the real owner's
|
|
19
|
-
* task.
|
|
20
|
-
*
|
|
21
|
-
* the latter, and it covers it per tenant, so a guessed id from another
|
|
22
|
-
* tenant writes nothing anywhere.
|
|
20
|
+
* task. Unknown task ids are rejected before any task-scoped projection or
|
|
21
|
+
* dedup fact can be written.
|
|
23
22
|
* 3. **apply/recover** — every lifecycle side effect is idempotent under the
|
|
24
23
|
* envelope's own durable identity. A retry after a partial failure resumes
|
|
25
24
|
* this step rather than being hidden by transport admission.
|
package/dist/index.js
CHANGED
|
@@ -1,11 +1,30 @@
|
|
|
1
1
|
import { BOARD_STATUSES, CapabilityDeclarationSchema, NONCE_SIGNING_DOMAIN, hasCapability, isTenantId, tenantId, principalTenant, parseDeviceProofEnvelope, deviceProofSigningInput, contentHash, parseCapabilityDeclaration, ByokCoreError, tenantKey, PRESENCE_LEVELS, createInMemoryCoreStores, authenticateDeviceAssertion, assertCapability, isCoreConflictError, isCoreError, DEVICE_PROOF_HEADER, TRUTH_RECORD_KINDS, STORAGE_ERROR_CODES, STORAGE_ERROR_HTTP_STATUS } from '@byok-sdk/core';
|
|
2
2
|
export { DEVICE_PROOF_HEADER, NONCE_SIGNING_DOMAIN, isTenantId, tenantId } from '@byok-sdk/core';
|
|
3
|
-
import { AgentEventOrUnknownSchema, PROTOCOL_VERSION, DAEMON_TO_SERVER_TYPES, AGENT_EGRESS_RELIABLE_ACK_CAPABILITY, encodeEnvelope, decodeEnvelope, AgentHomeProjectionCompletionRequestSchema, BYOK_PAIR_PATH, BYOK_CHALLENGE_PATH, BYOK_TOKEN_PATH, BYOK_CAPABILITIES_PATH, BYOK_AGENT_MEMORY_PROJECTIONS_PATH, BYOK_EVENTS_PATH, BYOK_MESSAGES_PATH, BYOK_AGENT_HOME_PROJECTION_COMPLETION_ROUTE, BYOK_BOARD_PATH, BYOK_BOARD_CLAIM_ROUTE, BYOK_BOARD_UNCLAIM_ROUTE, BYOK_BOARD_STATUS_ROUTE, BYOK_BOARD_STREAM_PATH, BYOK_PRESENCE_PATH, BYOK_ACTIVITY_PATH, BYOK_RECORDS_PATH, BYOK_RECORD_ROUTE, BYOK_SKILL_PACKS_PATH, BYOK_SKILL_PACK_FILE_ROUTE, BYOK_BLOBS_PATH, BYOK_BLOB_FINALIZE_ROUTE, BYOK_BLOB_URL_ROUTE, BYOK_BLOB_CONTENT_ROUTE, createEnvelope, AgentHomeProjectionPayloadSchema, AgentContentReadPayloadSchema, AGENT_EGRESS_POLICY_CAPABILITY, AGENT_EGRESS_FRESH_SESSION_CAPABILITY, AGENT_MESSAGE_EGRESS_CAPABILITY, TERMINAL_PROJECTION_SELECTION_CAPABILITY, TaskOfferForAgentWithEgressFreshPayloadSchema, AgentMessageServerContextSchema, TaskOfferForAgentWithEgressPayloadSchema, TaskOfferForAgentPayloadSchema, AgentMemoryProjectionMutationSchema, AgentMemoryProjectionReceiptSchema, AGENT_MEMORY_PROJECTION_MAX_ORDERING_VALUE, AgentMemoryProjectionEraseResultSchema, 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';
|
|
3
|
+
import { AgentEventOrUnknownSchema, PROTOCOL_VERSION, DAEMON_TO_SERVER_TYPES, AGENT_EGRESS_RELIABLE_ACK_CAPABILITY, encodeEnvelope, decodeEnvelope, AgentHomeProjectionCompletionRequestSchema, BYOK_PAIR_PATH, BYOK_CHALLENGE_PATH, BYOK_TOKEN_PATH, BYOK_CAPABILITIES_PATH, BYOK_AGENT_MEMORY_PROJECTIONS_PATH, BYOK_EVENTS_PATH, BYOK_MESSAGES_PATH, BYOK_AGENT_HOME_PROJECTION_COMPLETION_ROUTE, BYOK_BOARD_PATH, BYOK_BOARD_CLAIM_ROUTE, BYOK_BOARD_UNCLAIM_ROUTE, BYOK_BOARD_STATUS_ROUTE, BYOK_BOARD_STREAM_PATH, BYOK_PRESENCE_PATH, BYOK_ACTIVITY_PATH, BYOK_RECORDS_PATH, BYOK_RECORD_ROUTE, BYOK_SKILL_PACKS_PATH, BYOK_SKILL_PACK_FILE_ROUTE, BYOK_BLOBS_PATH, BYOK_BLOB_FINALIZE_ROUTE, BYOK_BLOB_URL_ROUTE, BYOK_BLOB_CONTENT_ROUTE, createEnvelope, AgentHomeProjectionPayloadSchema, AgentContentReadPayloadSchema, AGENT_EGRESS_POLICY_CAPABILITY, AGENT_EGRESS_FRESH_SESSION_CAPABILITY, AGENT_MESSAGE_EGRESS_CAPABILITY, TERMINAL_PROJECTION_SELECTION_CAPABILITY, TaskOfferForAgentWithEgressFreshPayloadSchema, AgentMessageServerContextSchema, TaskOfferForAgentWithEgressPayloadSchema, TaskOfferForAgentPayloadSchema, AgentMemoryProjectionMutationSchema, AgentMemoryProjectionReceiptSchema, AGENT_MEMORY_PROJECTION_MAX_ORDERING_VALUE, AgentMemoryProjectionEraseResultSchema, HarnessIdSchema, STRICT_AGENT_ONLY_CAPABILITY, AGENT_CONTENT_ARTIFACT_READ_CAPABILITY, AGENT_CONTENT_TRANSCRIPT_READ_CAPABILITY, AGENT_CONTENT_WORKSPACE_READ_CAPABILITY, AGENT_HOME_PROJECTION_CAPABILITY, AgentRefSchema, PairRequestSchema, PairResponseSchema, ChallengeRequestSchema, TokenRequestSchema, AgentMemoryProjectionCommitRequestSchema, agentMemoryProjectionBase64UrlByteLength, MAILBOX_READ_AHEAD_MAX_SEQS, MAILBOX_READ_AHEAD_CAPABILITY, MessagesSendRequestSchema, PresencePublishRequestSchema, CreateBlobRequestSchema, byokBlobContentPath, AgentEgressAckPayloadSchema, AgentContentReceiptPayloadSchema } from '@byok-sdk/protocol';
|
|
4
4
|
import { z } from 'zod';
|
|
5
5
|
import { Hono } from 'hono';
|
|
6
6
|
|
|
7
7
|
// src/index.ts
|
|
8
8
|
|
|
9
|
+
// src/offer-identity.ts
|
|
10
|
+
function uuidFromSha256(value) {
|
|
11
|
+
const match = /^sha256:([0-9a-f]{64})$/.exec(value);
|
|
12
|
+
if (match === null) throw new Error("CloudCrypto.sha256 must return canonical sha256 hex");
|
|
13
|
+
const digest = match[1];
|
|
14
|
+
if (digest === void 0) throw new Error("CloudCrypto.sha256 must return canonical sha256 hex");
|
|
15
|
+
const hex = digest.slice(0, 32).split("");
|
|
16
|
+
hex[12] = "8";
|
|
17
|
+
hex[16] = ["8", "9", "a", "b"][Number.parseInt(hex[16], 16) & 3];
|
|
18
|
+
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("")}`;
|
|
19
|
+
}
|
|
20
|
+
async function taskOfferMessageId(tenant, taskId, deviceId, agentRef, sha256) {
|
|
21
|
+
return uuidFromSha256(await sha256(new TextEncoder().encode(JSON.stringify({ domain: "byok:task-offer", tenant, taskId, deviceId, agentRef }))));
|
|
22
|
+
}
|
|
23
|
+
async function hashOfferBytes(bytes) {
|
|
24
|
+
const hash = new Uint8Array(await crypto.subtle.digest("SHA-256", new Uint8Array(bytes)));
|
|
25
|
+
return `sha256:${Array.from(hash, (byte) => byte.toString(16).padStart(2, "0")).join("")}`;
|
|
26
|
+
}
|
|
27
|
+
|
|
9
28
|
// src/approval-control.ts
|
|
10
29
|
var StaleApprovalError = class extends Error {
|
|
11
30
|
constructor(taskId, requestedApprovalId, currentApprovalId) {
|
|
@@ -668,7 +687,8 @@ function tenantStoresFor(principal, root) {
|
|
|
668
687
|
revoke: (deviceId) => cloud.devices.revoke(tenant, deviceId),
|
|
669
688
|
recordCapabilities: (input) => principal.kind === "device" ? cloud.devices.recordCapabilities(tenant, {
|
|
670
689
|
deviceId: principal.deviceId,
|
|
671
|
-
capabilities: input.capabilities
|
|
690
|
+
capabilities: input.capabilities,
|
|
691
|
+
...input.harnesses === void 0 ? {} : { harnesses: input.harnesses }
|
|
672
692
|
}) : Promise.resolve(void 0),
|
|
673
693
|
readiness: () => cloud.devices.readiness(tenant, core.presence)
|
|
674
694
|
},
|
|
@@ -1029,6 +1049,8 @@ function tokenHandler(deps) {
|
|
|
1029
1049
|
};
|
|
1030
1050
|
}
|
|
1031
1051
|
var CLOUD_PROTOCOL_CAPABILITIES = [
|
|
1052
|
+
"custom-harness",
|
|
1053
|
+
MAILBOX_READ_AHEAD_CAPABILITY,
|
|
1032
1054
|
"result-document",
|
|
1033
1055
|
// The kernel's inbound handles `task.approval_resolved` on both the plain
|
|
1034
1056
|
// and the approval-timeline paths (`packages/cloud/src/inbound.ts:576,610`).
|
|
@@ -1065,7 +1087,13 @@ function eventsHandler(deps) {
|
|
|
1065
1087
|
}
|
|
1066
1088
|
cursor = parsedCursor;
|
|
1067
1089
|
}
|
|
1090
|
+
const afterRaw = c.req.query("afterSeq");
|
|
1091
|
+
const afterSeq = afterRaw === void 0 ? cursor : Number(afterRaw);
|
|
1092
|
+
if (!Number.isSafeInteger(afterSeq) || afterSeq < cursor || afterSeq - cursor > MAILBOX_READ_AHEAD_MAX_SEQS) {
|
|
1093
|
+
return c.json({ error: "invalid read-ahead offset" }, 400);
|
|
1094
|
+
}
|
|
1068
1095
|
const acked = await stores.mailbox.readCursor(device.deviceId);
|
|
1096
|
+
if (afterRaw !== void 0 && afterSeq > acked.deliveredSeq) return c.json({ error: "read offset exceeds delivered watermark" }, 409);
|
|
1069
1097
|
if (cursor > acked.ackedSeq) {
|
|
1070
1098
|
try {
|
|
1071
1099
|
await stores.mailbox.advanceCursor({ deviceId: device.deviceId, ackedSeq: cursor });
|
|
@@ -1078,7 +1106,7 @@ function eventsHandler(deps) {
|
|
|
1078
1106
|
}
|
|
1079
1107
|
const attempts = Math.max(1, Math.ceil(deps.longPollHoldMs / deps.longPollIntervalMs));
|
|
1080
1108
|
for (let attempt = 0; attempt < attempts; attempt += 1) {
|
|
1081
|
-
let scanCursor =
|
|
1109
|
+
let scanCursor = afterSeq;
|
|
1082
1110
|
while (true) {
|
|
1083
1111
|
const page = await stores.mailbox.readAfter({
|
|
1084
1112
|
deviceId: device.deviceId,
|
|
@@ -1122,7 +1150,7 @@ function eventsHandler(deps) {
|
|
|
1122
1150
|
}
|
|
1123
1151
|
const response = {
|
|
1124
1152
|
events: [],
|
|
1125
|
-
cursor,
|
|
1153
|
+
cursor: afterSeq,
|
|
1126
1154
|
capabilities: CLOUD_PROTOCOL_CAPABILITIES
|
|
1127
1155
|
};
|
|
1128
1156
|
return c.json(response, 200);
|
|
@@ -1385,6 +1413,7 @@ function agentRefEchoRequired(envelope) {
|
|
|
1385
1413
|
return envelope.type === "task.claim" || envelope.type === "task.decline" || envelope.type === "task.complete" || envelope.type === "task.fail" || envelope.type === "task.cancelled";
|
|
1386
1414
|
}
|
|
1387
1415
|
async function handleInboundEnvelope(stores, deviceId, envelope, activityBounds = DEFAULT_ACTIVITY_BOUNDS, agentMessageConsume, observer) {
|
|
1416
|
+
if (envelope.v !== PROTOCOL_VERSION) return "rejected";
|
|
1388
1417
|
const outcome = await applyInboundGate(
|
|
1389
1418
|
stores,
|
|
1390
1419
|
deviceId,
|
|
@@ -1404,12 +1433,13 @@ async function applyInboundGate(stores, deviceId, envelope, activityBounds, agen
|
|
|
1404
1433
|
if (!await stores.rateLimiter.consume(deviceId)) return "rate_limited";
|
|
1405
1434
|
if (envelope.type === "conn.hello") {
|
|
1406
1435
|
if (envelope.payload.deviceId !== deviceId) return "rejected";
|
|
1436
|
+
if (envelope.payload.harnesses !== void 0 && !envelope.payload.capabilities.includes("custom-harness")) return "rejected";
|
|
1407
1437
|
if (!envelope.payload.protocolVersions.includes(PROTOCOL_VERSION)) return "rejected";
|
|
1408
1438
|
const device = await stores.devices.get(deviceId);
|
|
1409
1439
|
if (device === void 0 || device.revoked || device.productId !== envelope.payload.productId) {
|
|
1410
1440
|
return "rejected";
|
|
1411
1441
|
}
|
|
1412
|
-
if (await stores.devices.recordCapabilities({ capabilities: envelope.payload.capabilities }) === void 0) {
|
|
1442
|
+
if (await stores.devices.recordCapabilities({ capabilities: envelope.payload.capabilities, ...envelope.payload.harnesses === void 0 ? {} : { harnesses: envelope.payload.harnesses } }) === void 0) {
|
|
1413
1443
|
return "rejected";
|
|
1414
1444
|
}
|
|
1415
1445
|
return completeInboundEnvelope(stores, deviceId, envelope.id, void 0);
|
|
@@ -1453,12 +1483,40 @@ async function applyInboundGate(stores, deviceId, envelope, activityBounds, agen
|
|
|
1453
1483
|
const taskId = envelope.task_id;
|
|
1454
1484
|
if (taskId === void 0) return "rejected";
|
|
1455
1485
|
const attempt = await stores.tasks.get(taskId);
|
|
1456
|
-
if (attempt
|
|
1486
|
+
if (attempt === void 0 || attempt.deviceId !== deviceId) return "rejected";
|
|
1457
1487
|
if (attempt?.ownerDeviceId !== void 0 && attempt.ownerDeviceId !== deviceId) return "rejected";
|
|
1488
|
+
if (envelope.type === "task.claim" && envelope.payload.deviceId !== deviceId) return "rejected";
|
|
1489
|
+
if (envelope.type === "task.claim") {
|
|
1490
|
+
const harnessId = envelope.payload.harnessId;
|
|
1491
|
+
if (harnessId !== void 0 && envelope.payload.runtime !== void 0) return "rejected";
|
|
1492
|
+
if (attempt.ownerDeviceId !== void 0 && (harnessId !== void 0 || attempt.claimedHarnessId !== void 0) && harnessId !== attempt.claimedHarnessId) return "rejected";
|
|
1493
|
+
const device = await stores.devices.get(deviceId);
|
|
1494
|
+
if (harnessId !== void 0 && (!device?.capabilities?.includes("custom-harness") || !device.harnesses?.some((harness) => harness.id === harnessId))) return "rejected";
|
|
1495
|
+
const messageId = await taskOfferMessageId(stores.tenant, taskId, deviceId, attempt.agentRef, hashOfferBytes);
|
|
1496
|
+
const offer = await stores.receipts.get(`task-offer:${messageId}`);
|
|
1497
|
+
if (offer !== void 0) {
|
|
1498
|
+
const sealed = JSON.parse(offer.body);
|
|
1499
|
+
if (harnessId !== void 0 && (sealed.payload.runtime !== void 0 || sealed.payload.dispatchSelection !== void 0)) return "rejected";
|
|
1500
|
+
if (sealed.payload.harnessId !== void 0 && sealed.payload.harnessId !== harnessId) return "rejected";
|
|
1501
|
+
} else if (harnessId !== void 0) return "rejected";
|
|
1502
|
+
}
|
|
1503
|
+
let unclaimedInterruption = false;
|
|
1504
|
+
if (envelope.type === "task.fail" && envelope.payload.recovery !== void 0) {
|
|
1505
|
+
if (envelope.payload.reason !== "daemon_interrupted" || envelope.payload.retryable !== false) return "rejected";
|
|
1506
|
+
const messageId = await taskOfferMessageId(stores.tenant, taskId, deviceId, attempt.agentRef, hashOfferBytes);
|
|
1507
|
+
if (envelope.payload.recovery.offerId !== messageId) return "rejected";
|
|
1508
|
+
const offer = await stores.receipts.get(`task-offer:${messageId}`);
|
|
1509
|
+
if (offer === void 0) return "rejected";
|
|
1510
|
+
const sealed = JSON.parse(offer.body);
|
|
1511
|
+
const harnessId = envelope.payload.harnessId;
|
|
1512
|
+
if (sealed.payload.harnessId !== void 0 && sealed.payload.harnessId !== harnessId) return "rejected";
|
|
1513
|
+
if (harnessId !== void 0 && (sealed.payload.runtime !== void 0 || sealed.payload.dispatchSelection !== void 0)) return "rejected";
|
|
1514
|
+
unclaimedInterruption = attempt.ownerDeviceId === void 0 && attempt.claimedHarnessId === void 0;
|
|
1515
|
+
}
|
|
1516
|
+
if ((envelope.type === "task.complete" || envelope.type === "task.fail" || envelope.type === "task.cancelled") && !unclaimedInterruption && envelope.payload.harnessId !== attempt.claimedHarnessId) return "rejected";
|
|
1458
1517
|
if (attempt?.agentRef !== void 0 && agentRefEchoRequired(envelope) && !sameAgentRef(attempt.agentRef, inboundAgentRef(envelope))) {
|
|
1459
1518
|
return "rejected";
|
|
1460
1519
|
}
|
|
1461
|
-
if (terminalStatus(envelope) !== void 0 && attempt === void 0) return "rejected";
|
|
1462
1520
|
if (envelope.type === "task.progress" && envelope.payload.events.length > 0) {
|
|
1463
1521
|
try {
|
|
1464
1522
|
validateActivityBatch(
|
|
@@ -1487,7 +1545,8 @@ async function applyInboundGate(stores, deviceId, envelope, activityBounds, agen
|
|
|
1487
1545
|
throw caught;
|
|
1488
1546
|
}
|
|
1489
1547
|
}
|
|
1490
|
-
await applyLifecycle(stores, deviceId, taskId, envelope, activityBounds, attempt?.agentRef);
|
|
1548
|
+
const lifecycleOutcome = await applyLifecycle(stores, deviceId, taskId, envelope, activityBounds, attempt?.agentRef);
|
|
1549
|
+
if (lifecycleOutcome === "rejected") return "rejected";
|
|
1491
1550
|
return completeInboundEnvelope(stores, deviceId, envelope.id, attempt?.agentRef);
|
|
1492
1551
|
}
|
|
1493
1552
|
async function completeInboundEnvelope(stores, deviceId, envelopeId, agentRef) {
|
|
@@ -1496,14 +1555,17 @@ async function completeInboundEnvelope(stores, deviceId, envelopeId, agentRef) {
|
|
|
1496
1555
|
}
|
|
1497
1556
|
async function applyLifecycle(stores, deviceId, taskId, envelope, activityBounds, persistedAgentRef) {
|
|
1498
1557
|
switch (envelope.type) {
|
|
1499
|
-
case "task.claim":
|
|
1500
|
-
await stores.tasks.claim({
|
|
1558
|
+
case "task.claim": {
|
|
1559
|
+
const claimed = await stores.tasks.claim({
|
|
1501
1560
|
taskId,
|
|
1502
1561
|
deviceId,
|
|
1503
1562
|
...envelope.payload.runtime === void 0 ? {} : { runtime: envelope.payload.runtime },
|
|
1563
|
+
...envelope.payload.harnessId === void 0 ? {} : { harnessId: envelope.payload.harnessId },
|
|
1504
1564
|
...envelope.payload.capabilities === void 0 ? {} : { capabilities: envelope.payload.capabilities }
|
|
1505
1565
|
});
|
|
1566
|
+
if (claimed === void 0 || claimed.ownerDeviceId !== deviceId || claimed.claimedRuntime !== envelope.payload.runtime || claimed.claimedHarnessId !== envelope.payload.harnessId) return "rejected";
|
|
1506
1567
|
return;
|
|
1568
|
+
}
|
|
1507
1569
|
case "task.started":
|
|
1508
1570
|
await stores.tasks.recordStatus({
|
|
1509
1571
|
taskId,
|
|
@@ -1587,13 +1649,11 @@ async function recordTerminal(stores, taskId, envelope) {
|
|
|
1587
1649
|
}
|
|
1588
1650
|
const attempt = await stores.tasks.get(taskId);
|
|
1589
1651
|
if (attempt?.cancellation !== void 0) {
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
});
|
|
1596
|
-
}
|
|
1652
|
+
await stores.tasks.recordStatus({
|
|
1653
|
+
taskId,
|
|
1654
|
+
status: "cancelled",
|
|
1655
|
+
...attempt.agentRef === void 0 ? {} : { agentRef: attempt.agentRef }
|
|
1656
|
+
});
|
|
1597
1657
|
return;
|
|
1598
1658
|
}
|
|
1599
1659
|
const recordedAttempt = await stores.tasks.recordStatus({
|
|
@@ -1602,7 +1662,16 @@ async function recordTerminal(stores, taskId, envelope) {
|
|
|
1602
1662
|
...attempt?.agentRef === void 0 ? {} : { agentRef: attempt.agentRef },
|
|
1603
1663
|
...winner.cause === void 0 ? {} : { terminalCause: winner.cause }
|
|
1604
1664
|
});
|
|
1605
|
-
if (recordedAttempt?.
|
|
1665
|
+
if (recordedAttempt?.cancellation !== void 0 || recordedAttempt?.status !== winner.status) {
|
|
1666
|
+
if (recordedAttempt?.cancellation !== void 0) {
|
|
1667
|
+
await stores.tasks.recordStatus({
|
|
1668
|
+
taskId,
|
|
1669
|
+
status: "cancelled",
|
|
1670
|
+
...recordedAttempt.agentRef === void 0 ? {} : { agentRef: recordedAttempt.agentRef }
|
|
1671
|
+
});
|
|
1672
|
+
}
|
|
1673
|
+
return;
|
|
1674
|
+
}
|
|
1606
1675
|
await projectTerminalToReview(stores.board, taskId);
|
|
1607
1676
|
}
|
|
1608
1677
|
function terminalStatus(envelope) {
|
|
@@ -2613,6 +2682,7 @@ function projectTerminalResult(taskId, receipt) {
|
|
|
2613
2682
|
sessionRef: envelope.payload.sessionRef,
|
|
2614
2683
|
...envelope.payload.artifactRefs !== void 0 ? { artifactRefs: envelope.payload.artifactRefs } : {},
|
|
2615
2684
|
...envelope.payload.document !== void 0 ? { document: envelope.payload.document } : {},
|
|
2685
|
+
...envelope.payload.harnessId === void 0 ? {} : { harnessId: envelope.payload.harnessId },
|
|
2616
2686
|
...envelope.payload.usage !== void 0 ? { usage: envelope.payload.usage } : {},
|
|
2617
2687
|
recordedAt: receipt.recordedAt
|
|
2618
2688
|
};
|
|
@@ -2621,9 +2691,11 @@ function projectTerminalResult(taskId, receipt) {
|
|
|
2621
2691
|
taskId,
|
|
2622
2692
|
state: "failed",
|
|
2623
2693
|
...envelope.payload.agentRef === void 0 ? {} : { agentRef: envelope.payload.agentRef },
|
|
2694
|
+
...envelope.payload.recovery === void 0 ? {} : { recovery: envelope.payload.recovery },
|
|
2624
2695
|
reason: envelope.payload.reason,
|
|
2625
2696
|
terminalCause: envelope.payload.reason,
|
|
2626
2697
|
...envelope.payload.retryable !== void 0 ? { retryable: envelope.payload.retryable } : {},
|
|
2698
|
+
...envelope.payload.harnessId === void 0 ? {} : { harnessId: envelope.payload.harnessId },
|
|
2627
2699
|
...envelope.payload.usage !== void 0 ? { usage: envelope.payload.usage } : {},
|
|
2628
2700
|
recordedAt: receipt.recordedAt
|
|
2629
2701
|
};
|
|
@@ -2649,6 +2721,7 @@ function projectTerminalResult(taskId, receipt) {
|
|
|
2649
2721
|
...envelope.payload.agentRef === void 0 ? {} : { agentRef: envelope.payload.agentRef },
|
|
2650
2722
|
...envelope.payload.reason !== void 0 ? { reason: envelope.payload.reason } : {},
|
|
2651
2723
|
...envelope.payload.reason === void 0 ? {} : { terminalCause: envelope.payload.reason },
|
|
2724
|
+
...envelope.payload.harnessId === void 0 ? {} : { harnessId: envelope.payload.harnessId },
|
|
2652
2725
|
...envelope.payload.usage !== void 0 ? { usage: envelope.payload.usage } : {},
|
|
2653
2726
|
recordedAt: receipt.recordedAt
|
|
2654
2727
|
};
|
|
@@ -2672,16 +2745,6 @@ function isTerminalAttemptStatus(status) {
|
|
|
2672
2745
|
function sameAgentRef2(expected, actual) {
|
|
2673
2746
|
return actual?.agentId === expected.agentId && actual.profileRevision === expected.profileRevision;
|
|
2674
2747
|
}
|
|
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
2748
|
function sameOfferEnvelope(expected, actual) {
|
|
2686
2749
|
return expected.id === actual.id && expected.type === actual.type && expected.task_id === actual.task_id && JSON.stringify(expected.payload) === JSON.stringify(actual.payload);
|
|
2687
2750
|
}
|
|
@@ -2888,13 +2951,16 @@ function createByokCloud(options) {
|
|
|
2888
2951
|
async function enqueueTaskEnvelope(tenant, deviceId, requestedTaskId, agentRef, buildEnvelope, beforeMailbox) {
|
|
2889
2952
|
const stores = tenantStoresFor(controlPlane(tenant), root);
|
|
2890
2953
|
const taskId = requestedTaskId ?? `task_${options.crypto.randomUuid()}`;
|
|
2891
|
-
const messageId =
|
|
2892
|
-
|
|
2893
|
-
|
|
2894
|
-
|
|
2895
|
-
|
|
2896
|
-
)
|
|
2897
|
-
|
|
2954
|
+
const messageId = await taskOfferMessageId(tenant, taskId, deviceId, agentRef, (bytes) => options.crypto.sha256(bytes));
|
|
2955
|
+
const proposed = buildEnvelope(taskId, 0, messageId);
|
|
2956
|
+
const selection = proposed.payload;
|
|
2957
|
+
if (selection.harnessId !== void 0) {
|
|
2958
|
+
HarnessIdSchema.parse(selection.harnessId);
|
|
2959
|
+
if (selection.runtime !== void 0 || selection.dispatchSelection !== void 0) throw new ByokCloudError("coordination_input_invalid", "harnessId conflicts with built-in runtime selection");
|
|
2960
|
+
await assertAgentCapabilities(tenant, deviceId, ["custom-harness"]);
|
|
2961
|
+
const device = await options.cloud.devices.get(tenant, deviceId);
|
|
2962
|
+
if (!device?.harnesses?.some((harness) => harness.id === selection.harnessId)) throw new ByokCloudError("coordination_input_invalid", "requested custom harness is not advertised by this device");
|
|
2963
|
+
}
|
|
2898
2964
|
const reservation = agentRef === void 0 ? void 0 : await stores.tasks.reserveAgentOffer({ taskId, deviceId, agentRef });
|
|
2899
2965
|
const openedBeforeAppend = reservation?.attempt ?? (agentRef === void 0 ? await stores.tasks.open({ taskId, deviceId }) : void 0);
|
|
2900
2966
|
if (openedBeforeAppend === void 0 || openedBeforeAppend.deviceId !== deviceId || (agentRef === void 0 ? openedBeforeAppend.agentRef !== void 0 : openedBeforeAppend.agentRef === void 0 || !sameAgentRef2(agentRef, openedBeforeAppend.agentRef))) {
|
|
@@ -2905,13 +2971,12 @@ function createByokCloud(options) {
|
|
|
2905
2971
|
}
|
|
2906
2972
|
const offerReceiptKey = `task-offer:${messageId}`;
|
|
2907
2973
|
const deliveryReceiptKey = `task-offer-delivered:${messageId}`;
|
|
2908
|
-
if (
|
|
2974
|
+
if (await stores.receipts.get(deliveryReceiptKey) !== void 0) {
|
|
2909
2975
|
throw new ByokCloudError(
|
|
2910
|
-
"agent_task_already_exists",
|
|
2911
|
-
`Task ${taskId} already has a durable
|
|
2976
|
+
agentRef === void 0 ? "coordination_input_invalid" : "agent_task_already_exists",
|
|
2977
|
+
`Task ${taskId} already has a durable delivered attempt and cannot be enqueued again.`
|
|
2912
2978
|
);
|
|
2913
2979
|
}
|
|
2914
|
-
const proposed = buildEnvelope(taskId, 0, messageId);
|
|
2915
2980
|
const proposedBody = JSON.stringify({
|
|
2916
2981
|
deviceId,
|
|
2917
2982
|
type: proposed.type,
|
|
@@ -2955,14 +3020,12 @@ function createByokCloud(options) {
|
|
|
2955
3020
|
`Task ${taskId} already has a different executable offer body.`
|
|
2956
3021
|
);
|
|
2957
3022
|
}
|
|
2958
|
-
|
|
2959
|
-
|
|
2960
|
-
|
|
2961
|
-
|
|
2962
|
-
|
|
2963
|
-
|
|
2964
|
-
);
|
|
2965
|
-
}
|
|
3023
|
+
const delivered = await stores.receipts.record({ key: deliveryReceiptKey, body: messageId });
|
|
3024
|
+
if (agentRef !== void 0 && reservation?.created === false && !delivered.created) {
|
|
3025
|
+
throw new ByokCloudError(
|
|
3026
|
+
"agent_task_already_exists",
|
|
3027
|
+
`Task ${taskId} already has a durable Agent attempt and cannot be enqueued again.`
|
|
3028
|
+
);
|
|
2966
3029
|
}
|
|
2967
3030
|
return { taskId, seq: message.seq, envelope, attempt: openedBeforeAppend };
|
|
2968
3031
|
}
|
|
@@ -3978,7 +4041,8 @@ var InMemoryDeviceDirectory = class {
|
|
|
3978
4041
|
if (record === void 0 || record.revoked) return void 0;
|
|
3979
4042
|
const updated = {
|
|
3980
4043
|
...record,
|
|
3981
|
-
capabilities: Object.freeze([...input.capabilities])
|
|
4044
|
+
capabilities: Object.freeze([...input.capabilities]),
|
|
4045
|
+
harnesses: Object.freeze((input.harnesses ?? []).map((value) => Object.freeze({ ...value })))
|
|
3982
4046
|
};
|
|
3983
4047
|
this.#byTenant.set(key, updated);
|
|
3984
4048
|
return updated;
|
|
@@ -4355,11 +4419,12 @@ var InMemoryTaskAttemptStore = class {
|
|
|
4355
4419
|
return this.#state.mutate(key, () => {
|
|
4356
4420
|
const existing = this.#state.attempts.get(key);
|
|
4357
4421
|
if (existing === void 0) return void 0;
|
|
4358
|
-
if (existing.ownerDeviceId !== void 0 || existing.cancellation !== void 0 || existing.status !== "offered") return existing;
|
|
4422
|
+
if (existing.deviceId !== input.deviceId || existing.ownerDeviceId !== void 0 || existing.cancellation !== void 0 || existing.status !== "offered") return existing;
|
|
4359
4423
|
const claimed = {
|
|
4360
4424
|
...existing,
|
|
4361
4425
|
ownerDeviceId: input.deviceId,
|
|
4362
4426
|
...input.runtime === void 0 ? {} : { claimedRuntime: input.runtime },
|
|
4427
|
+
...input.harnessId === void 0 ? {} : { claimedHarnessId: input.harnessId },
|
|
4363
4428
|
...input.capabilities === void 0 ? {} : { claimedRuntimeCapabilities: { ...input.capabilities } },
|
|
4364
4429
|
status: "claimed",
|
|
4365
4430
|
updatedAt: this.#now()
|