@byok-sdk/cloud 0.17.0 → 0.19.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/auth/device-assertion.d.ts +41 -1
- package/dist/capabilities.d.ts +43 -0
- package/dist/cloud.d.ts +62 -2
- package/dist/errors.d.ts +10 -0
- package/dist/handlers/input-preparations.d.ts +17 -0
- package/dist/inbound.d.ts +1 -0
- package/dist/index.d.ts +8 -3
- package/dist/index.js +1986 -1561
- package/dist/index.js.map +1 -1
- package/dist/input-preparations.d.ts +42 -0
- package/dist/recurring.d.ts +121 -0
- package/dist/stores/in-memory/task-attempts.d.ts +4 -0
- package/dist/stores/ports.d.ts +5 -0
- package/dist/task-agent-message.d.ts +11 -0
- package/dist/tenant-stores.d.ts +4 -0
- package/dist/terminal-result.d.ts +9 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
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';
|
|
1
|
+
import { BOARD_STATUSES, CapabilityDeclarationSchema, NONCE_SIGNING_DOMAIN, hasCapability, isTenantId, tenantId, principalTenant, parseDeviceProofEnvelope, deviceProofSigningInput, contentHash, parseCapabilityDeclaration, ByokCoreError, tenantKey, PRESENCE_LEVELS, createInMemoryCoreStores, authenticateDeviceAssertion, authenticateTaskAssertion, 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, isTaskOfferType, MESSAGE_PAYLOAD_SCHEMAS, 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
3
|
import { z } from 'zod';
|
|
4
|
+
import { AgentEventOrUnknownSchema, AgentMessageServerContextSchema, AgentMessageEgressRequirementSchema, AgentRefSchema, TaskOfferForAgentWithEgressFreshPayloadSchema, PROTOCOL_VERSION, DAEMON_TO_SERVER_TYPES, AGENT_EGRESS_RELIABLE_ACK_CAPABILITY, encodeEnvelope, decodeEnvelope, AgentHomeProjectionCompletionRequestSchema, AgentInputPreparationPayloadSchema, InputPreparationCompletionRequestSchema, 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_INPUT_PREPARATION_COMPLETION_ROUTE, BYOK_INPUT_PREPARATION_STATUS_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, AgentMessagePublishPayloadSchema, isTaskOfferType, MESSAGE_PAYLOAD_SCHEMAS, createEnvelope, AgentHomeProjectionPayloadSchema, AgentContentReadPayloadSchema, AGENT_EGRESS_POLICY_CAPABILITY, AGENT_MESSAGE_EGRESS_CAPABILITY, TERMINAL_PROJECTION_SELECTION_CAPABILITY, TaskOfferForAgentWithEgressPayloadSchema, AGENT_INPUT_PREPARATION_CAPABILITY, TaskOfferPreparedPayloadSchema, 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, AGENT_EGRESS_FRESH_SESSION_CAPABILITY, PairRequestSchema, PairResponseSchema, ChallengeRequestSchema, TokenRequestSchema, AgentMemoryProjectionCommitRequestSchema, agentMemoryProjectionBase64UrlByteLength, MAILBOX_READ_AHEAD_MAX_SEQS, MAILBOX_READ_AHEAD_CAPABILITY, MessagesSendRequestSchema, InputPreparationStatusQuerySchema, PresencePublishRequestSchema, CreateBlobRequestSchema, byokBlobContentPath, AgentMessageDispositionPayloadSchema, AgentEgressAckPayloadSchema, AgentContentReceiptPayloadSchema } from '@byok-sdk/protocol';
|
|
5
5
|
import { Hono } from 'hono';
|
|
6
6
|
|
|
7
7
|
// src/index.ts
|
|
@@ -25,1682 +25,1778 @@ async function hashOfferBytes(bytes) {
|
|
|
25
25
|
return `sha256:${Array.from(hash, (byte) => byte.toString(16).padStart(2, "0")).join("")}`;
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
// src/
|
|
29
|
-
var
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
28
|
+
// src/errors.ts
|
|
29
|
+
var CLOUD_ERROR_CODES = {
|
|
30
|
+
/** A pairing code that is unknown, expired, or already redeemed (§6.1). */
|
|
31
|
+
pairing_code_invalid: "pairing_code_invalid",
|
|
32
|
+
/** The composition handed a device row whose tenant is not a mintable `TenantId`. */
|
|
33
|
+
device_tenant_invalid: "device_tenant_invalid",
|
|
34
|
+
/**
|
|
35
|
+
* The mailbox committed a row `seq` that disagrees with the delivery `seq`
|
|
36
|
+
* its body factory baked into the envelope. Loud rather than silent: those
|
|
37
|
+
* two numbers ARE the daemon's redelivery cursor.
|
|
38
|
+
*/
|
|
39
|
+
mailbox_seq_mismatch: "mailbox_seq_mismatch",
|
|
40
|
+
/** A capability declaration the host supplied that core refused. */
|
|
41
|
+
capability_declaration_invalid: "capability_declaration_invalid",
|
|
42
|
+
/**
|
|
43
|
+
* The declaration names a capability this composition cannot serve, so the
|
|
44
|
+
* deployment would publish a surface it does not have (ADR-010).
|
|
45
|
+
*
|
|
46
|
+
* Construction-time and fatal. A client learns what a deployment supports by
|
|
47
|
+
* READING the declaration and is entitled to act on it without probing, so a
|
|
48
|
+
* declaration that over-states is not a degraded deployment — it is a
|
|
49
|
+
* deployment whose one honest interface lies.
|
|
50
|
+
*/
|
|
51
|
+
capability_over_declared: "capability_over_declared",
|
|
52
|
+
/** Host-supplied board labels or coordination input exceeded the explicit contract. */
|
|
53
|
+
coordination_input_invalid: "coordination_input_invalid",
|
|
54
|
+
/**
|
|
55
|
+
* A terminal receipt whose stored body is not a terminal envelope — either
|
|
56
|
+
* undecodable or a non-terminal type. Whatever wrote that row broke the
|
|
57
|
+
* receipt-store contract, so the typed read model fails closed instead of
|
|
58
|
+
* projecting a best-effort shape.
|
|
59
|
+
*/
|
|
60
|
+
terminal_receipt_unreadable: "terminal_receipt_unreadable",
|
|
61
|
+
/** A progress/activity batch exceeded the configured event or byte ceiling. */
|
|
62
|
+
activity_batch_too_large: "activity_batch_too_large",
|
|
63
|
+
/** Host control-plane task lookup is tenant-closed and found no task. */
|
|
64
|
+
task_not_found: "task_not_found",
|
|
65
|
+
/**
|
|
66
|
+
* A host `approveTask`/`rejectTask` names a task with no approval to
|
|
67
|
+
* resolve: the durable timeline holds no unresolved `approval_requested`
|
|
68
|
+
* (including a tail that has expired or was never written), the attempt has
|
|
69
|
+
* already reached a terminal status, or no device ever claimed it so there
|
|
70
|
+
* is nothing to notify. Fail-closed by construction — cloud never invents a
|
|
71
|
+
* pending approval to make the call succeed.
|
|
72
|
+
*/
|
|
73
|
+
task_not_awaiting_approval: "task_not_awaiting_approval",
|
|
74
|
+
/** Durable target-device capability is absent, revoked, or unavailable. */
|
|
75
|
+
agent_capability_missing: "agent_capability_missing",
|
|
76
|
+
/** Inbound Agent identity did not exactly match the offered identity. */
|
|
77
|
+
agent_ref_mismatch: "agent_ref_mismatch",
|
|
78
|
+
/** A strict Agent task id already names a durable attempt and cannot be re-enqueued. */
|
|
79
|
+
agent_task_already_exists: "agent_task_already_exists",
|
|
80
|
+
/** A first-write-wins Agent control record was replayed with a different body. */
|
|
81
|
+
agent_content_request_mismatch: "agent_content_request_mismatch",
|
|
82
|
+
/** A durable mailbox receipt id resolved to an envelope other than its exact acknowledgement. */
|
|
83
|
+
mailbox_receipt_mismatch: "mailbox_receipt_mismatch",
|
|
84
|
+
/** A task-free Agent-home request id already names a different immutable desired projection. */
|
|
85
|
+
agent_home_projection_request_conflict: "agent_home_projection_request_conflict",
|
|
86
|
+
/** A direct Agent-home completion did not identify a stored desired request for this exact device. */
|
|
87
|
+
agent_home_projection_request_not_found: "agent_home_projection_request_not_found",
|
|
88
|
+
/** A direct Agent-home completion changed the first durable terminal outcome. */
|
|
89
|
+
agent_home_projection_completion_conflict: "agent_home_projection_completion_conflict",
|
|
90
|
+
/** A task-free completion did not exactly echo its immutable desired projection binding. */
|
|
91
|
+
agent_home_projection_receipt_mismatch: "agent_home_projection_receipt_mismatch",
|
|
92
|
+
/** A receipt-store row at the projection namespace violated the frozen projection schema. */
|
|
93
|
+
agent_home_projection_receipt_invalid: "agent_home_projection_receipt_invalid",
|
|
94
|
+
/** A remote input-preparation request id already names a different immutable desired request. */
|
|
95
|
+
input_preparation_request_conflict: "input_preparation_request_conflict",
|
|
96
|
+
/** A device completion did not identify a stored desired request for this exact device. */
|
|
97
|
+
input_preparation_request_not_found: "input_preparation_request_not_found",
|
|
98
|
+
/** A device completion changed the first durable terminal outcome. */
|
|
99
|
+
input_preparation_completion_conflict: "input_preparation_completion_conflict",
|
|
100
|
+
/** A completion did not exactly echo its immutable desired request/Agent/policy binding. */
|
|
101
|
+
input_preparation_receipt_mismatch: "input_preparation_receipt_mismatch",
|
|
102
|
+
/** A receipt-store row at the preparation namespace violated the frozen schema. */
|
|
103
|
+
input_preparation_receipt_invalid: "input_preparation_receipt_invalid",
|
|
104
|
+
/** A hosted Agent-memory mutation did not match the durable task/device/AgentRef binding. */
|
|
105
|
+
agent_memory_projection_task_mismatch: "agent_memory_projection_task_mismatch",
|
|
106
|
+
/** The embedder-owned grant/consent authority denied a hosted memory mutation. */
|
|
107
|
+
agent_memory_projection_authorization_denied: "agent_memory_projection_authorization_denied",
|
|
108
|
+
/** A redacted snapshot's decoded bytes did not match its declared hash or byte count. */
|
|
109
|
+
agent_memory_projection_hash_mismatch: "agent_memory_projection_hash_mismatch",
|
|
110
|
+
/** A writer epoch/source sequence already names a different immutable projection mutation. */
|
|
111
|
+
agent_memory_projection_replay_mismatch: "agent_memory_projection_replay_mismatch",
|
|
112
|
+
/** A hosted memory mutation came from an older writer epoch. */
|
|
113
|
+
agent_memory_projection_stale_epoch: "agent_memory_projection_stale_epoch",
|
|
114
|
+
/** A server-side erase fence prevents a deleted writer epoch from re-entering. */
|
|
115
|
+
agent_memory_projection_erased_epoch: "agent_memory_projection_erased_epoch",
|
|
116
|
+
/** A prior erase reached the protocol writer-epoch ceiling and cannot mint a later writer. */
|
|
117
|
+
agent_memory_projection_epoch_exhausted: "agent_memory_projection_epoch_exhausted",
|
|
118
|
+
/** A hosted memory mutation skipped or reset a required source sequence. */
|
|
119
|
+
agent_memory_projection_sequence_gap: "agent_memory_projection_sequence_gap"
|
|
42
120
|
};
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
pending = {
|
|
50
|
-
sourceEnvelopeId: entry.sourceEnvelopeId,
|
|
51
|
-
revision: entry.revision,
|
|
52
|
-
...event.approvalId === void 0 ? {} : { approvalId: event.approvalId }
|
|
53
|
-
};
|
|
54
|
-
continue;
|
|
55
|
-
}
|
|
56
|
-
if (pending === void 0) continue;
|
|
57
|
-
if (pending.approvalId === void 0 || pending.approvalId === event.approvalId) {
|
|
58
|
-
pending = void 0;
|
|
59
|
-
}
|
|
121
|
+
var ByokCloudError = class extends Error {
|
|
122
|
+
code;
|
|
123
|
+
constructor(code, message, options) {
|
|
124
|
+
super(message, options);
|
|
125
|
+
this.name = "ByokCloudError";
|
|
126
|
+
this.code = code;
|
|
60
127
|
}
|
|
61
|
-
|
|
128
|
+
};
|
|
129
|
+
function isCloudError(value, code) {
|
|
130
|
+
if (!(value instanceof ByokCloudError)) return false;
|
|
131
|
+
return code === void 0 || value.code === code;
|
|
62
132
|
}
|
|
63
133
|
|
|
64
|
-
// src/
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
134
|
+
// src/activity.ts
|
|
135
|
+
var DEFAULT_ACTIVITY_CAPACITY = 50;
|
|
136
|
+
var ActivityTaskIdSchema = z.string().min(1).max(200);
|
|
137
|
+
var SourceEnvelopeIdSchema = z.string().max(200).regex(/\S/, "sourceEnvelopeId must not be blank");
|
|
138
|
+
var ActivityAppendRequestSchema = z.object({
|
|
139
|
+
taskId: ActivityTaskIdSchema,
|
|
140
|
+
sourceEnvelopeId: SourceEnvelopeIdSchema,
|
|
141
|
+
batchSeq: z.number().int().nonnegative(),
|
|
142
|
+
events: z.array(AgentEventOrUnknownSchema).min(1),
|
|
143
|
+
dropped: z.number().int().nonnegative()
|
|
144
|
+
});
|
|
145
|
+
var TimelineEventSchema = z.object({
|
|
146
|
+
taskId: ActivityTaskIdSchema,
|
|
147
|
+
sourceEnvelopeId: SourceEnvelopeIdSchema,
|
|
148
|
+
batchSeq: z.number().int().nonnegative(),
|
|
149
|
+
eventIndex: z.number().int().nonnegative(),
|
|
150
|
+
/**
|
|
151
|
+
* The producer-declared loss attached to this source batch. It travels with
|
|
152
|
+
* every retained event so a replay includes it in the canonical batch
|
|
153
|
+
* binding rather than treating the aggregate as an incidental counter.
|
|
154
|
+
*/
|
|
155
|
+
sourceDropped: z.number().int().nonnegative().optional(),
|
|
156
|
+
receivedAt: z.iso.datetime(),
|
|
157
|
+
event: AgentEventOrUnknownSchema
|
|
158
|
+
});
|
|
159
|
+
function validateActivityAppend(input) {
|
|
160
|
+
if (!Number.isFinite(input.ttlMs) || input.ttlMs <= 0) {
|
|
161
|
+
throw new ByokCloudError(
|
|
162
|
+
"coordination_input_invalid",
|
|
163
|
+
`Activity ttl must be a positive number of milliseconds, received ${String(input.ttlMs)}.`
|
|
164
|
+
);
|
|
73
165
|
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
this.status = status;
|
|
81
|
-
this.runtime = runtime;
|
|
82
|
-
this.name = "SteerRejectedError";
|
|
166
|
+
const capacity = input.capacity ?? DEFAULT_ACTIVITY_CAPACITY;
|
|
167
|
+
if (!Number.isSafeInteger(capacity) || capacity <= 0) {
|
|
168
|
+
throw new ByokCloudError(
|
|
169
|
+
"coordination_input_invalid",
|
|
170
|
+
`Activity capacity must be a positive integer, received ${String(capacity)}.`
|
|
171
|
+
);
|
|
83
172
|
}
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
// src/crypto/web-crypto.ts
|
|
91
|
-
var PAIRING_CODE_ALPHABET = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789";
|
|
92
|
-
var HEX_DIGITS = "0123456789abcdef";
|
|
93
|
-
function base64UrlEncode(bytes) {
|
|
94
|
-
let binary = "";
|
|
95
|
-
for (const byte of bytes) binary += String.fromCharCode(byte);
|
|
96
|
-
return btoa(binary).replaceAll("+", "-").replaceAll("/", "_").replaceAll("=", "");
|
|
97
|
-
}
|
|
98
|
-
function base64UrlDecode(value) {
|
|
99
|
-
const padded = value.replaceAll("-", "+").replaceAll("_", "/");
|
|
100
|
-
try {
|
|
101
|
-
const binary = atob(padded);
|
|
102
|
-
const bytes = new Uint8Array(binary.length);
|
|
103
|
-
for (let index = 0; index < binary.length; index += 1) bytes[index] = binary.charCodeAt(index);
|
|
104
|
-
return bytes;
|
|
105
|
-
} catch {
|
|
106
|
-
return void 0;
|
|
173
|
+
const parsed = ActivityAppendRequestSchema.safeParse(input);
|
|
174
|
+
if (!parsed.success) {
|
|
175
|
+
throw new ByokCloudError(
|
|
176
|
+
"coordination_input_invalid",
|
|
177
|
+
"Activity batches require stable source identity, order, at least one valid event, and a non-negative dropped count."
|
|
178
|
+
);
|
|
107
179
|
}
|
|
180
|
+
return capacity;
|
|
108
181
|
}
|
|
109
|
-
function
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
182
|
+
function projectTimelineEvents(input, receivedAt) {
|
|
183
|
+
return input.events.map(
|
|
184
|
+
(event, eventIndex) => TimelineEventSchema.parse({
|
|
185
|
+
taskId: input.taskId,
|
|
186
|
+
sourceEnvelopeId: input.sourceEnvelopeId,
|
|
187
|
+
batchSeq: input.batchSeq,
|
|
188
|
+
eventIndex,
|
|
189
|
+
sourceDropped: input.dropped,
|
|
190
|
+
receivedAt,
|
|
191
|
+
event
|
|
192
|
+
})
|
|
193
|
+
);
|
|
116
194
|
}
|
|
117
|
-
function
|
|
118
|
-
const
|
|
119
|
-
if (
|
|
120
|
-
|
|
195
|
+
function activitySourceBatchState(entries, input) {
|
|
196
|
+
const existing = entries.filter((entry) => entry.sourceEnvelopeId === input.sourceEnvelopeId).sort((left, right) => left.eventIndex - right.eventIndex);
|
|
197
|
+
if (existing.length === 0) return "absent";
|
|
198
|
+
if (existing.length !== input.events.length) return "conflict";
|
|
199
|
+
for (const [eventIndex, event] of input.events.entries()) {
|
|
200
|
+
const stored = existing[eventIndex];
|
|
201
|
+
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)) {
|
|
202
|
+
return "conflict";
|
|
203
|
+
}
|
|
121
204
|
}
|
|
122
|
-
return
|
|
205
|
+
return "same";
|
|
123
206
|
}
|
|
124
|
-
function
|
|
125
|
-
return
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
out += PAIRING_CODE_ALPHABET[byte % PAIRING_CODE_ALPHABET.length];
|
|
137
|
-
}
|
|
138
|
-
return out;
|
|
139
|
-
},
|
|
140
|
-
async verifyEd25519(publicKeyBase64Url, message, signature) {
|
|
141
|
-
const signatureBytes = base64UrlDecode(signature);
|
|
142
|
-
if (signatureBytes === void 0) return false;
|
|
143
|
-
try {
|
|
144
|
-
const key = await subtle().importKey(
|
|
145
|
-
"jwk",
|
|
146
|
-
{ kty: "OKP", crv: "Ed25519", x: publicKeyBase64Url },
|
|
147
|
-
{ name: "Ed25519" },
|
|
148
|
-
false,
|
|
149
|
-
["verify"]
|
|
150
|
-
);
|
|
151
|
-
const messageBytes = typeof message === "string" ? new TextEncoder().encode(message) : message;
|
|
152
|
-
return await subtle().verify({ name: "Ed25519" }, key, signatureBytes, messageBytes);
|
|
153
|
-
} catch {
|
|
154
|
-
return false;
|
|
155
|
-
}
|
|
156
|
-
},
|
|
157
|
-
async hmacSha256(secret, message) {
|
|
158
|
-
const key = await subtle().importKey("raw", secret, { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
|
|
159
|
-
const signature = await subtle().sign("HMAC", key, new TextEncoder().encode(message));
|
|
160
|
-
return base64UrlEncode(new Uint8Array(signature));
|
|
161
|
-
},
|
|
162
|
-
async sha256(data) {
|
|
163
|
-
const digest = await subtle().digest("SHA-256", data);
|
|
164
|
-
return `sha256:${toHex(new Uint8Array(digest))}`;
|
|
165
|
-
},
|
|
166
|
-
timingSafeEqual(left, right) {
|
|
167
|
-
const leftBytes = base64UrlDecode(left);
|
|
168
|
-
const rightBytes = base64UrlDecode(right);
|
|
169
|
-
if (leftBytes === void 0 || rightBytes === void 0) return false;
|
|
170
|
-
if (leftBytes.length !== rightBytes.length) return false;
|
|
171
|
-
let diff = 0;
|
|
172
|
-
for (let index = 0; index < leftBytes.length; index += 1) {
|
|
173
|
-
diff |= (leftBytes[index] ?? 0) ^ (rightBytes[index] ?? 0);
|
|
174
|
-
}
|
|
175
|
-
return diff === 0;
|
|
176
|
-
}
|
|
177
|
-
};
|
|
207
|
+
function parseTimelineEvents(value) {
|
|
208
|
+
return z.array(TimelineEventSchema).parse(value);
|
|
209
|
+
}
|
|
210
|
+
function compareTimelineEvents(left, right) {
|
|
211
|
+
return left.batchSeq - right.batchSeq || left.eventIndex - right.eventIndex;
|
|
212
|
+
}
|
|
213
|
+
function activityCursor(entries) {
|
|
214
|
+
const last = entries.at(-1);
|
|
215
|
+
return last === void 0 ? void 0 : { batchSeq: last.batchSeq, eventIndex: last.eventIndex };
|
|
216
|
+
}
|
|
217
|
+
function activityTailKey(tenant, taskId) {
|
|
218
|
+
return tenantKey(tenant, taskId);
|
|
178
219
|
}
|
|
179
220
|
|
|
180
|
-
// src/
|
|
181
|
-
var
|
|
182
|
-
|
|
183
|
-
|
|
221
|
+
// src/coordination.ts
|
|
222
|
+
var DEFAULT_BOARD_CHANNEL_MAX_BYTES = 128;
|
|
223
|
+
var DEFAULT_BOARD_TITLE_MAX_BYTES = 512;
|
|
224
|
+
var DEFAULT_ACTIVITY_MAX_EVENTS = 50;
|
|
225
|
+
var DEFAULT_ACTIVITY_MAX_BYTES = 64 * 1024;
|
|
226
|
+
var DEFAULT_ACTIVITY_TTL_MS = 10 * 6e4;
|
|
227
|
+
var DEFAULT_PRESENCE_TTL_MS = 9e4;
|
|
228
|
+
var DEFAULT_PRESENCE_MINIMUM_INTERVAL_MS = 5e3;
|
|
229
|
+
var DEFAULT_PRESENCE_DETAIL_MAX_BYTES = 512;
|
|
230
|
+
var encoder = new TextEncoder();
|
|
231
|
+
var DEFAULT_ACTIVITY_BOUNDS = {
|
|
232
|
+
maxEvents: DEFAULT_ACTIVITY_MAX_EVENTS,
|
|
233
|
+
maxBytes: DEFAULT_ACTIVITY_MAX_BYTES,
|
|
234
|
+
capacity: DEFAULT_ACTIVITY_CAPACITY,
|
|
235
|
+
ttlMs: DEFAULT_ACTIVITY_TTL_MS
|
|
236
|
+
};
|
|
237
|
+
function assertBoardLabels(channel, title, limits) {
|
|
238
|
+
if (channel.length === 0 || encoder.encode(channel).length > limits.channelMaxBytes) {
|
|
239
|
+
throw new ByokCloudError(
|
|
240
|
+
"coordination_input_invalid",
|
|
241
|
+
`Board channel must contain 1..${limits.channelMaxBytes} UTF-8 bytes.`
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
if (title.length === 0 || encoder.encode(title).length > limits.titleMaxBytes) {
|
|
245
|
+
throw new ByokCloudError(
|
|
246
|
+
"coordination_input_invalid",
|
|
247
|
+
`Board title must contain 1..${limits.titleMaxBytes} UTF-8 bytes.`
|
|
248
|
+
);
|
|
249
|
+
}
|
|
184
250
|
}
|
|
185
|
-
function
|
|
186
|
-
|
|
187
|
-
|
|
251
|
+
function assertTaskAttemptListLimit(limit) {
|
|
252
|
+
if (!Number.isSafeInteger(limit) || limit <= 0) {
|
|
253
|
+
throw new ByokCloudError(
|
|
254
|
+
"coordination_input_invalid",
|
|
255
|
+
`Task attempt list limit must be a positive integer; received ${String(limit)}.`
|
|
256
|
+
);
|
|
188
257
|
}
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
258
|
+
}
|
|
259
|
+
function validateActivityEvents(events, dropped, bounds) {
|
|
260
|
+
if (!Number.isSafeInteger(dropped) || dropped < 0) {
|
|
261
|
+
throw new ByokCloudError(
|
|
262
|
+
"coordination_input_invalid",
|
|
263
|
+
"Activity dropped must be a non-negative integer."
|
|
264
|
+
);
|
|
265
|
+
}
|
|
266
|
+
if (events.length === 0 || events.length > bounds.maxEvents) {
|
|
267
|
+
throw new ByokCloudError(
|
|
268
|
+
"activity_batch_too_large",
|
|
269
|
+
`Activity batch must contain 1..${bounds.maxEvents} events.`
|
|
270
|
+
);
|
|
271
|
+
}
|
|
272
|
+
const encoded = JSON.stringify(events);
|
|
273
|
+
if (encoder.encode(encoded).length > bounds.maxBytes) {
|
|
274
|
+
throw new ByokCloudError(
|
|
275
|
+
"activity_batch_too_large",
|
|
276
|
+
`Activity batch exceeds ${bounds.maxBytes} UTF-8 bytes.`
|
|
277
|
+
);
|
|
193
278
|
}
|
|
194
|
-
return {
|
|
195
|
-
async sign(claims, expiresInSeconds) {
|
|
196
|
-
const issuedAt = Math.floor(clock.now().getTime() / 1e3);
|
|
197
|
-
const header = encodeSegment({ alg: "HS256", typ: "JWT" });
|
|
198
|
-
const payload = encodeSegment({
|
|
199
|
-
deviceId: claims.deviceId,
|
|
200
|
-
tenantId: claims.tenantId,
|
|
201
|
-
productId: claims.productId,
|
|
202
|
-
iat: issuedAt,
|
|
203
|
-
exp: issuedAt + expiresInSeconds
|
|
204
|
-
});
|
|
205
|
-
const signingInput = `${header}.${payload}`;
|
|
206
|
-
return `${signingInput}.${await signature(signingInput)}`;
|
|
207
|
-
},
|
|
208
|
-
async verify(token) {
|
|
209
|
-
const parts = token.split(".");
|
|
210
|
-
if (parts.length !== 3) return void 0;
|
|
211
|
-
const [header, payload, provided] = parts;
|
|
212
|
-
const key = await signingKey("verify");
|
|
213
|
-
const providedBytes = base64UrlDecode(provided);
|
|
214
|
-
if (providedBytes === void 0) return void 0;
|
|
215
|
-
const ok = await globalThis.crypto.subtle.verify(
|
|
216
|
-
"HMAC",
|
|
217
|
-
key,
|
|
218
|
-
providedBytes,
|
|
219
|
-
new TextEncoder().encode(`${header}.${payload}`)
|
|
220
|
-
);
|
|
221
|
-
if (!ok) return void 0;
|
|
222
|
-
const headerBytes = base64UrlDecode(header);
|
|
223
|
-
const payloadBytes = base64UrlDecode(payload);
|
|
224
|
-
if (headerBytes === void 0 || payloadBytes === void 0) return void 0;
|
|
225
|
-
let decodedHeader;
|
|
226
|
-
let decodedPayload;
|
|
227
|
-
try {
|
|
228
|
-
decodedHeader = JSON.parse(new TextDecoder().decode(headerBytes));
|
|
229
|
-
decodedPayload = JSON.parse(new TextDecoder().decode(payloadBytes));
|
|
230
|
-
} catch {
|
|
231
|
-
return void 0;
|
|
232
|
-
}
|
|
233
|
-
const alg = decodedHeader?.alg;
|
|
234
|
-
if (alg !== "HS256") return void 0;
|
|
235
|
-
const { deviceId, tenantId: tenantId4, productId, exp } = decodedPayload;
|
|
236
|
-
if (typeof deviceId !== "string" || typeof tenantId4 !== "string" || typeof productId !== "string") {
|
|
237
|
-
return void 0;
|
|
238
|
-
}
|
|
239
|
-
if (typeof exp !== "number" || clock.now().getTime() / 1e3 >= exp) return void 0;
|
|
240
|
-
return { deviceId, tenantId: tenantId4, productId };
|
|
241
|
-
}
|
|
242
|
-
};
|
|
243
279
|
}
|
|
244
|
-
function
|
|
245
|
-
|
|
280
|
+
function validateActivityBatch(input, bounds) {
|
|
281
|
+
validateActivityEvents(input.events, input.dropped, bounds);
|
|
282
|
+
validateActivityAppend({ ...input, ttlMs: bounds.ttlMs, capacity: bounds.capacity });
|
|
246
283
|
}
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
const expiresAt = new Date(clock.now().getTime() + (input.ttlMs ?? PAIRING_CODE_TTL_MS)).toISOString();
|
|
259
|
-
return stores.pairingCodes.issue(tenant, {
|
|
260
|
-
code: crypto2.randomPairingCode(PAIRING_CODE_LENGTH),
|
|
261
|
-
productId: input.productId,
|
|
262
|
-
expiresAt
|
|
263
|
-
});
|
|
264
|
-
},
|
|
265
|
-
async redeemAndRegister(input) {
|
|
266
|
-
return stores.pairing.redeemAndRegister({
|
|
267
|
-
pairingCode: input.pairingCode,
|
|
268
|
-
deviceId: `dev_${crypto2.randomUuid()}`,
|
|
269
|
-
deviceName: input.deviceName,
|
|
270
|
-
devicePublicKey: input.devicePublicKey,
|
|
271
|
-
proofKeyId: DEVICE_IDENTITY_PROOF_KEY_ID,
|
|
272
|
-
proofKeyEpoch: DEVICE_IDENTITY_PROOF_KEY_EPOCH,
|
|
273
|
-
...input.machineId === void 0 ? {} : { machineId: input.machineId }
|
|
274
|
-
});
|
|
275
|
-
},
|
|
276
|
-
async resolveDevice(deviceId) {
|
|
277
|
-
const device = await stores.devices.resolveByDeviceId(deviceId);
|
|
278
|
-
if (device === void 0 || device.revoked) return void 0;
|
|
279
|
-
return device;
|
|
280
|
-
},
|
|
281
|
-
issueNonce(device) {
|
|
282
|
-
return stores.nonces.issue(device.tenantId, device.deviceId);
|
|
283
|
-
},
|
|
284
|
-
consumeNonceIfValid(device, nonce) {
|
|
285
|
-
return stores.nonces.consumeIfValid(device.tenantId, device.deviceId, nonce);
|
|
286
|
-
},
|
|
287
|
-
verifySignature(device, nonce, signature) {
|
|
288
|
-
return verifyNonceSignature(crypto2, device.devicePublicKey, nonce, signature);
|
|
289
|
-
},
|
|
290
|
-
async mintAccessToken(device) {
|
|
291
|
-
const accessToken = await tokenSigner.sign(
|
|
292
|
-
{ deviceId: device.deviceId, tenantId: device.tenantId, productId: device.productId },
|
|
293
|
-
ttlSeconds
|
|
294
|
-
);
|
|
295
|
-
return {
|
|
296
|
-
accessToken,
|
|
297
|
-
expiresAt: new Date(clock.now().getTime() + ttlSeconds * 1e3).toISOString()
|
|
298
|
-
};
|
|
299
|
-
}
|
|
300
|
-
};
|
|
284
|
+
async function appendActivityEvents(activity, input, bounds) {
|
|
285
|
+
validateActivityBatch(input, bounds);
|
|
286
|
+
return activity.append({
|
|
287
|
+
taskId: input.taskId,
|
|
288
|
+
sourceEnvelopeId: input.sourceEnvelopeId,
|
|
289
|
+
batchSeq: input.batchSeq,
|
|
290
|
+
events: input.events,
|
|
291
|
+
dropped: input.dropped,
|
|
292
|
+
ttlMs: bounds.ttlMs,
|
|
293
|
+
capacity: bounds.capacity
|
|
294
|
+
});
|
|
301
295
|
}
|
|
302
|
-
var
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
return {
|
|
352
|
-
schema: "byok-capabilities-v1",
|
|
353
|
-
version,
|
|
354
|
-
capabilities: Object.values(CLOUD_CAPABILITIES).filter((capability) => {
|
|
355
|
-
if (capability === CLOUD_CAPABILITIES.truthRecords) return options.includeTruthRecords === true;
|
|
356
|
-
if (capability === CLOUD_CAPABILITIES.skillPacks) return options.includeSkillPacks === true;
|
|
357
|
-
if (capability === CLOUD_CAPABILITIES.agentMemoryProjection) {
|
|
358
|
-
return options.includeAgentMemoryProjection === true;
|
|
359
|
-
}
|
|
360
|
-
return true;
|
|
361
|
-
})
|
|
362
|
-
};
|
|
363
|
-
}
|
|
364
|
-
function declares(declaration, capability) {
|
|
365
|
-
return hasCapability(declaration, capability);
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
// src/errors.ts
|
|
369
|
-
var CLOUD_ERROR_CODES = {
|
|
370
|
-
/** A pairing code that is unknown, expired, or already redeemed (§6.1). */
|
|
371
|
-
pairing_code_invalid: "pairing_code_invalid",
|
|
372
|
-
/** The composition handed a device row whose tenant is not a mintable `TenantId`. */
|
|
373
|
-
device_tenant_invalid: "device_tenant_invalid",
|
|
374
|
-
/**
|
|
375
|
-
* The mailbox committed a row `seq` that disagrees with the delivery `seq`
|
|
376
|
-
* its body factory baked into the envelope. Loud rather than silent: those
|
|
377
|
-
* two numbers ARE the daemon's redelivery cursor.
|
|
378
|
-
*/
|
|
379
|
-
mailbox_seq_mismatch: "mailbox_seq_mismatch",
|
|
380
|
-
/** A capability declaration the host supplied that core refused. */
|
|
381
|
-
capability_declaration_invalid: "capability_declaration_invalid",
|
|
382
|
-
/**
|
|
383
|
-
* The declaration names a capability this composition cannot serve, so the
|
|
384
|
-
* deployment would publish a surface it does not have (ADR-010).
|
|
385
|
-
*
|
|
386
|
-
* Construction-time and fatal. A client learns what a deployment supports by
|
|
387
|
-
* READING the declaration and is entitled to act on it without probing, so a
|
|
388
|
-
* declaration that over-states is not a degraded deployment — it is a
|
|
389
|
-
* deployment whose one honest interface lies.
|
|
390
|
-
*/
|
|
391
|
-
capability_over_declared: "capability_over_declared",
|
|
392
|
-
/** Host-supplied board labels or coordination input exceeded the explicit contract. */
|
|
393
|
-
coordination_input_invalid: "coordination_input_invalid",
|
|
394
|
-
/**
|
|
395
|
-
* A terminal receipt whose stored body is not a terminal envelope — either
|
|
396
|
-
* undecodable or a non-terminal type. Whatever wrote that row broke the
|
|
397
|
-
* receipt-store contract, so the typed read model fails closed instead of
|
|
398
|
-
* projecting a best-effort shape.
|
|
399
|
-
*/
|
|
400
|
-
terminal_receipt_unreadable: "terminal_receipt_unreadable",
|
|
401
|
-
/** A progress/activity batch exceeded the configured event or byte ceiling. */
|
|
402
|
-
activity_batch_too_large: "activity_batch_too_large",
|
|
403
|
-
/** Host control-plane task lookup is tenant-closed and found no task. */
|
|
404
|
-
task_not_found: "task_not_found",
|
|
405
|
-
/**
|
|
406
|
-
* A host `approveTask`/`rejectTask` names a task with no approval to
|
|
407
|
-
* resolve: the durable timeline holds no unresolved `approval_requested`
|
|
408
|
-
* (including a tail that has expired or was never written), the attempt has
|
|
409
|
-
* already reached a terminal status, or no device ever claimed it so there
|
|
410
|
-
* is nothing to notify. Fail-closed by construction — cloud never invents a
|
|
411
|
-
* pending approval to make the call succeed.
|
|
412
|
-
*/
|
|
413
|
-
task_not_awaiting_approval: "task_not_awaiting_approval",
|
|
414
|
-
/** Durable target-device capability is absent, revoked, or unavailable. */
|
|
415
|
-
agent_capability_missing: "agent_capability_missing",
|
|
416
|
-
/** Inbound Agent identity did not exactly match the offered identity. */
|
|
417
|
-
agent_ref_mismatch: "agent_ref_mismatch",
|
|
418
|
-
/** A strict Agent task id already names a durable attempt and cannot be re-enqueued. */
|
|
419
|
-
agent_task_already_exists: "agent_task_already_exists",
|
|
420
|
-
/** A first-write-wins Agent control record was replayed with a different body. */
|
|
421
|
-
agent_content_request_mismatch: "agent_content_request_mismatch",
|
|
422
|
-
/** A durable mailbox receipt id resolved to an envelope other than its exact acknowledgement. */
|
|
423
|
-
mailbox_receipt_mismatch: "mailbox_receipt_mismatch",
|
|
424
|
-
/** A task-free Agent-home request id already names a different immutable desired projection. */
|
|
425
|
-
agent_home_projection_request_conflict: "agent_home_projection_request_conflict",
|
|
426
|
-
/** A direct Agent-home completion did not identify a stored desired request for this exact device. */
|
|
427
|
-
agent_home_projection_request_not_found: "agent_home_projection_request_not_found",
|
|
428
|
-
/** A direct Agent-home completion changed the first durable terminal outcome. */
|
|
429
|
-
agent_home_projection_completion_conflict: "agent_home_projection_completion_conflict",
|
|
430
|
-
/** A task-free completion did not exactly echo its immutable desired projection binding. */
|
|
431
|
-
agent_home_projection_receipt_mismatch: "agent_home_projection_receipt_mismatch",
|
|
432
|
-
/** A receipt-store row at the projection namespace violated the frozen projection schema. */
|
|
433
|
-
agent_home_projection_receipt_invalid: "agent_home_projection_receipt_invalid",
|
|
434
|
-
/** A hosted Agent-memory mutation did not match the durable task/device/AgentRef binding. */
|
|
435
|
-
agent_memory_projection_task_mismatch: "agent_memory_projection_task_mismatch",
|
|
436
|
-
/** The embedder-owned grant/consent authority denied a hosted memory mutation. */
|
|
437
|
-
agent_memory_projection_authorization_denied: "agent_memory_projection_authorization_denied",
|
|
438
|
-
/** A redacted snapshot's decoded bytes did not match its declared hash or byte count. */
|
|
439
|
-
agent_memory_projection_hash_mismatch: "agent_memory_projection_hash_mismatch",
|
|
440
|
-
/** A writer epoch/source sequence already names a different immutable projection mutation. */
|
|
441
|
-
agent_memory_projection_replay_mismatch: "agent_memory_projection_replay_mismatch",
|
|
442
|
-
/** A hosted memory mutation came from an older writer epoch. */
|
|
443
|
-
agent_memory_projection_stale_epoch: "agent_memory_projection_stale_epoch",
|
|
444
|
-
/** A server-side erase fence prevents a deleted writer epoch from re-entering. */
|
|
445
|
-
agent_memory_projection_erased_epoch: "agent_memory_projection_erased_epoch",
|
|
446
|
-
/** A prior erase reached the protocol writer-epoch ceiling and cannot mint a later writer. */
|
|
447
|
-
agent_memory_projection_epoch_exhausted: "agent_memory_projection_epoch_exhausted",
|
|
448
|
-
/** A hosted memory mutation skipped or reset a required source sequence. */
|
|
449
|
-
agent_memory_projection_sequence_gap: "agent_memory_projection_sequence_gap"
|
|
450
|
-
};
|
|
451
|
-
var ByokCloudError = class extends Error {
|
|
452
|
-
code;
|
|
453
|
-
constructor(code, message, options) {
|
|
454
|
-
super(message, options);
|
|
455
|
-
this.name = "ByokCloudError";
|
|
456
|
-
this.code = code;
|
|
457
|
-
}
|
|
458
|
-
};
|
|
459
|
-
function isCloudError(value, code) {
|
|
460
|
-
if (!(value instanceof ByokCloudError)) return false;
|
|
461
|
-
return code === void 0 || value.code === code;
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
// src/activity.ts
|
|
465
|
-
var DEFAULT_ACTIVITY_CAPACITY = 50;
|
|
466
|
-
var ActivityTaskIdSchema = z.string().min(1).max(200);
|
|
467
|
-
var SourceEnvelopeIdSchema = z.string().max(200).regex(/\S/, "sourceEnvelopeId must not be blank");
|
|
468
|
-
var ActivityAppendRequestSchema = z.object({
|
|
469
|
-
taskId: ActivityTaskIdSchema,
|
|
470
|
-
sourceEnvelopeId: SourceEnvelopeIdSchema,
|
|
471
|
-
batchSeq: z.number().int().nonnegative(),
|
|
472
|
-
events: z.array(AgentEventOrUnknownSchema).min(1),
|
|
473
|
-
dropped: z.number().int().nonnegative()
|
|
474
|
-
});
|
|
475
|
-
var TimelineEventSchema = z.object({
|
|
476
|
-
taskId: ActivityTaskIdSchema,
|
|
477
|
-
sourceEnvelopeId: SourceEnvelopeIdSchema,
|
|
478
|
-
batchSeq: z.number().int().nonnegative(),
|
|
479
|
-
eventIndex: z.number().int().nonnegative(),
|
|
480
|
-
/**
|
|
481
|
-
* The producer-declared loss attached to this source batch. It travels with
|
|
482
|
-
* every retained event so a replay includes it in the canonical batch
|
|
483
|
-
* binding rather than treating the aggregate as an incidental counter.
|
|
484
|
-
*/
|
|
485
|
-
sourceDropped: z.number().int().nonnegative().optional(),
|
|
296
|
+
var DEFAULT_APPROVAL_TIMELINE_CAPACITY = 50;
|
|
297
|
+
var DEFAULT_APPROVAL_TIMELINE_TTL_MS = 10 * 60 * 1e3;
|
|
298
|
+
var APPROVAL_SUMMARY_MAX_BYTES = 16 * 1024;
|
|
299
|
+
var NonBlankIdSchema = z.string().max(200).regex(/\S/, "value must not be blank");
|
|
300
|
+
var TaskIdSchema = z.string().min(1).max(200);
|
|
301
|
+
var ApprovalRequestedEventSchema = z.object({
|
|
302
|
+
type: z.literal("approval_requested"),
|
|
303
|
+
summary: z.string(),
|
|
304
|
+
approvalId: NonBlankIdSchema.optional()
|
|
305
|
+
}).strict();
|
|
306
|
+
var LocalApprovalResolutionEventSchema = z.object({
|
|
307
|
+
type: z.literal("approval_resolved"),
|
|
308
|
+
// Absent only when a daemon resolved the single outstanding request without
|
|
309
|
+
// native approval identity. No id is synthesized.
|
|
310
|
+
approvalId: NonBlankIdSchema.optional(),
|
|
311
|
+
decision: z.enum(["approve", "reject"]),
|
|
312
|
+
// A daemon's `task.approval_resolved` is pure wire observation; it cannot
|
|
313
|
+
// declare a host command payload.
|
|
314
|
+
resolvedBy: z.literal("local"),
|
|
315
|
+
at: z.iso.datetime({ offset: true })
|
|
316
|
+
}).strict();
|
|
317
|
+
var HostApprovalResolutionEventSchema = z.object({
|
|
318
|
+
type: z.literal("approval_resolved"),
|
|
319
|
+
approvalId: NonBlankIdSchema.optional(),
|
|
320
|
+
decision: z.literal("approve"),
|
|
321
|
+
resolvedBy: z.literal("host"),
|
|
322
|
+
at: z.iso.datetime({ offset: true })
|
|
323
|
+
}).strict();
|
|
324
|
+
var HostRejectResolutionEventSchema = z.object({
|
|
325
|
+
type: z.literal("approval_resolved"),
|
|
326
|
+
approvalId: NonBlankIdSchema.optional(),
|
|
327
|
+
decision: z.literal("reject"),
|
|
328
|
+
resolvedBy: z.literal("host"),
|
|
329
|
+
// Required even when the host deliberately omitted a wire reason. `null`
|
|
330
|
+
// records that complete payload, so a historical host reject with no field
|
|
331
|
+
// cannot be mistaken for a recoverable executable command.
|
|
332
|
+
reason: z.string().nullable(),
|
|
333
|
+
at: z.iso.datetime({ offset: true })
|
|
334
|
+
}).strict();
|
|
335
|
+
var ApprovalTimelineEventSchema = z.union([
|
|
336
|
+
ApprovalRequestedEventSchema,
|
|
337
|
+
LocalApprovalResolutionEventSchema,
|
|
338
|
+
HostApprovalResolutionEventSchema,
|
|
339
|
+
HostRejectResolutionEventSchema
|
|
340
|
+
]);
|
|
341
|
+
var ApprovalObservationSchema = z.object({
|
|
342
|
+
taskId: TaskIdSchema,
|
|
343
|
+
sourceEnvelopeId: NonBlankIdSchema,
|
|
344
|
+
revision: z.number().int().positive(),
|
|
486
345
|
receivedAt: z.iso.datetime(),
|
|
487
|
-
event:
|
|
346
|
+
event: ApprovalTimelineEventSchema
|
|
488
347
|
});
|
|
489
|
-
function
|
|
490
|
-
|
|
348
|
+
function validateApprovalTimelineAppend(input) {
|
|
349
|
+
const capacity = input.capacity ?? DEFAULT_APPROVAL_TIMELINE_CAPACITY;
|
|
350
|
+
const ttlMs = input.ttlMs ?? DEFAULT_APPROVAL_TIMELINE_TTL_MS;
|
|
351
|
+
if (!Number.isSafeInteger(capacity) || capacity <= 0) {
|
|
491
352
|
throw new ByokCloudError(
|
|
492
353
|
"coordination_input_invalid",
|
|
493
|
-
`
|
|
354
|
+
`Approval timeline capacity must be a positive integer, received ${String(capacity)}.`
|
|
494
355
|
);
|
|
495
356
|
}
|
|
496
|
-
|
|
497
|
-
if (!Number.isSafeInteger(capacity) || capacity <= 0) {
|
|
357
|
+
if (!Number.isFinite(ttlMs) || ttlMs <= 0) {
|
|
498
358
|
throw new ByokCloudError(
|
|
499
359
|
"coordination_input_invalid",
|
|
500
|
-
`
|
|
360
|
+
`Approval timeline ttl must be a positive number of milliseconds, received ${String(ttlMs)}.`
|
|
501
361
|
);
|
|
502
362
|
}
|
|
503
|
-
const parsed =
|
|
363
|
+
const parsed = z.object({
|
|
364
|
+
taskId: TaskIdSchema,
|
|
365
|
+
sourceEnvelopeId: NonBlankIdSchema,
|
|
366
|
+
event: ApprovalTimelineEventSchema
|
|
367
|
+
}).safeParse(input);
|
|
504
368
|
if (!parsed.success) {
|
|
505
369
|
throw new ByokCloudError(
|
|
506
370
|
"coordination_input_invalid",
|
|
507
|
-
"
|
|
371
|
+
"Approval observations require stable source identity and a valid native lifecycle event."
|
|
508
372
|
);
|
|
509
373
|
}
|
|
510
|
-
|
|
374
|
+
if (parsed.data.event.type === "approval_requested" && new TextEncoder().encode(parsed.data.event.summary).byteLength > APPROVAL_SUMMARY_MAX_BYTES) {
|
|
375
|
+
throw new ByokCloudError(
|
|
376
|
+
"coordination_input_invalid",
|
|
377
|
+
`Approval summary exceeds ${APPROVAL_SUMMARY_MAX_BYTES} UTF-8 bytes.`
|
|
378
|
+
);
|
|
379
|
+
}
|
|
380
|
+
return { capacity, ttlMs, event: parsed.data.event };
|
|
511
381
|
}
|
|
512
|
-
function
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
event
|
|
522
|
-
|
|
523
|
-
);
|
|
524
|
-
}
|
|
525
|
-
function activitySourceBatchState(entries, input) {
|
|
526
|
-
const existing = entries.filter((entry) => entry.sourceEnvelopeId === input.sourceEnvelopeId).sort((left, right) => left.eventIndex - right.eventIndex);
|
|
527
|
-
if (existing.length === 0) return "absent";
|
|
528
|
-
if (existing.length !== input.events.length) return "conflict";
|
|
529
|
-
for (const [eventIndex, event] of input.events.entries()) {
|
|
530
|
-
const stored = existing[eventIndex];
|
|
531
|
-
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)) {
|
|
532
|
-
return "conflict";
|
|
533
|
-
}
|
|
382
|
+
function validateApprovalTimelineResolve(input) {
|
|
383
|
+
const validated = validateApprovalTimelineAppend(input);
|
|
384
|
+
const expected = z.object({
|
|
385
|
+
expectedSourceEnvelopeId: NonBlankIdSchema,
|
|
386
|
+
expectedRevision: z.number().int().positive()
|
|
387
|
+
}).safeParse(input);
|
|
388
|
+
if (!expected.success || validated.event.type !== "approval_resolved" || validated.event.resolvedBy !== "host") {
|
|
389
|
+
throw new ByokCloudError(
|
|
390
|
+
"coordination_input_invalid",
|
|
391
|
+
"Host approval resolution requires an exact pending source/revision and a host resolution event."
|
|
392
|
+
);
|
|
534
393
|
}
|
|
535
|
-
return
|
|
394
|
+
return validated;
|
|
536
395
|
}
|
|
537
|
-
function
|
|
538
|
-
return
|
|
396
|
+
function approvalTimelineKey(tenant, taskId) {
|
|
397
|
+
return tenantKey(tenant, taskId);
|
|
539
398
|
}
|
|
540
|
-
function
|
|
541
|
-
return
|
|
399
|
+
function parseApprovalObservations(value) {
|
|
400
|
+
return z.array(ApprovalObservationSchema).parse(value);
|
|
542
401
|
}
|
|
543
|
-
function
|
|
544
|
-
|
|
545
|
-
return last === void 0 ? void 0 : { batchSeq: last.batchSeq, eventIndex: last.eventIndex };
|
|
402
|
+
function approvalTimelineCursor(entries) {
|
|
403
|
+
return entries.at(-1)?.revision;
|
|
546
404
|
}
|
|
547
|
-
function
|
|
548
|
-
|
|
405
|
+
async function projectTerminalToReview(board, taskId) {
|
|
406
|
+
const item = await board.get(taskId);
|
|
407
|
+
if (item === void 0 || item.status !== "in_progress") return;
|
|
408
|
+
try {
|
|
409
|
+
await board.updateStatus({
|
|
410
|
+
itemId: taskId,
|
|
411
|
+
expectedStatus: "in_progress",
|
|
412
|
+
status: "in_review"
|
|
413
|
+
});
|
|
414
|
+
} catch (caught) {
|
|
415
|
+
if (isCoreConflictError(caught)) return;
|
|
416
|
+
throw caught;
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
function agentReliabilityKey(domain, deviceId, agentRef, id) {
|
|
420
|
+
const exact = AgentRefSchema.parse(agentRef);
|
|
421
|
+
return `${domain}:${JSON.stringify([deviceId, exact.agentId, exact.profileRevision, id])}`;
|
|
549
422
|
}
|
|
550
423
|
|
|
551
|
-
// src/
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
maxEvents: DEFAULT_ACTIVITY_MAX_EVENTS,
|
|
563
|
-
maxBytes: DEFAULT_ACTIVITY_MAX_BYTES,
|
|
564
|
-
capacity: DEFAULT_ACTIVITY_CAPACITY,
|
|
565
|
-
ttlMs: DEFAULT_ACTIVITY_TTL_MS
|
|
566
|
-
};
|
|
567
|
-
function assertBoardLabels(channel, title, limits) {
|
|
568
|
-
if (channel.length === 0 || encoder.encode(channel).length > limits.channelMaxBytes) {
|
|
569
|
-
throw new ByokCloudError(
|
|
570
|
-
"coordination_input_invalid",
|
|
571
|
-
`Board channel must contain 1..${limits.channelMaxBytes} UTF-8 bytes.`
|
|
572
|
-
);
|
|
424
|
+
// src/inbound.ts
|
|
425
|
+
async function handleAgentMessagePublish(stores, deviceId, taskId, payload, consume, alreadyDeduplicated) {
|
|
426
|
+
const device = await stores.devices.get(deviceId);
|
|
427
|
+
const attempt = await stores.tasks.get(taskId);
|
|
428
|
+
const binding = await stores.receipts.get(`agent-message-offer:${deviceId}:${taskId}`);
|
|
429
|
+
if (device === void 0 || device.revoked || !hasCapabilities(device.capabilities, [AGENT_MESSAGE_EGRESS_CAPABILITY]) || attempt === void 0 || attempt.deviceId !== deviceId || binding === void 0 || attempt.agentRef === void 0 || !sameAgentRef(attempt.agentRef, payload.agentRef)) return { outcome: "rejected" };
|
|
430
|
+
let frozen;
|
|
431
|
+
try {
|
|
432
|
+
frozen = JSON.parse(binding.body);
|
|
433
|
+
} catch {
|
|
434
|
+
return { outcome: "rejected" };
|
|
573
435
|
}
|
|
574
|
-
if (
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
436
|
+
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" };
|
|
437
|
+
const priorDisposition = await readAgentMessageDisposition(stores, deviceId, taskId, payload);
|
|
438
|
+
if (priorDisposition !== void 0) return { outcome: "duplicate", disposition: priorDisposition };
|
|
439
|
+
const payloadBody = JSON.stringify(payload);
|
|
440
|
+
const reservation = await stores.tasks.reserveAgentMessage({
|
|
441
|
+
taskId,
|
|
442
|
+
deviceId,
|
|
443
|
+
messageId: payload.messageId,
|
|
444
|
+
payloadBody
|
|
445
|
+
});
|
|
446
|
+
if (reservation !== "reserved" && reservation !== "pending") return { outcome: "rejected" };
|
|
447
|
+
let decision;
|
|
448
|
+
if (consume === void 0) {
|
|
449
|
+
if (reservation === "pending") {
|
|
450
|
+
throw new Error("Agent message consumer is unavailable while reconciling an exact pending admission.");
|
|
451
|
+
}
|
|
452
|
+
decision = { outcome: "held", reasonCode: "consumer_unavailable" };
|
|
453
|
+
} else {
|
|
454
|
+
decision = await consume({ tenant: stores.tenant, deviceId, taskId, context: frozen.context, payload });
|
|
579
455
|
}
|
|
456
|
+
const disposition = agentMessageDisposition(payload, decision);
|
|
457
|
+
const admission = await stores.tasks.finalizeAgentMessage({
|
|
458
|
+
taskId,
|
|
459
|
+
deviceId,
|
|
460
|
+
messageId: payload.messageId,
|
|
461
|
+
payloadBody,
|
|
462
|
+
terminalBody: JSON.stringify({ payload, disposition })
|
|
463
|
+
});
|
|
464
|
+
const terminal = admission?.terminalBody === void 0 ? void 0 : parseAgentMessageDisposition(payload, admission.terminalBody);
|
|
465
|
+
return terminal === void 0 ? { outcome: "rejected" } : { outcome: "accepted", disposition: terminal };
|
|
580
466
|
}
|
|
581
|
-
function
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
467
|
+
function agentMessageDisposition(payload, decision) {
|
|
468
|
+
return {
|
|
469
|
+
agentRef: payload.agentRef,
|
|
470
|
+
sessionRef: payload.sessionRef,
|
|
471
|
+
contract: payload.contract,
|
|
472
|
+
messageId: payload.messageId,
|
|
473
|
+
cursor: payload.cursor,
|
|
474
|
+
contentHash: payload.contentHash,
|
|
475
|
+
outcome: decision.outcome,
|
|
476
|
+
receiptId: crypto.randomUUID(),
|
|
477
|
+
...decision.reasonCode === void 0 ? {} : { reasonCode: decision.reasonCode }
|
|
478
|
+
};
|
|
588
479
|
}
|
|
589
|
-
function
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
)
|
|
595
|
-
}
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
480
|
+
async function readAgentMessageDisposition(stores, deviceId, taskId, payload) {
|
|
481
|
+
const admission = await stores.tasks.readAgentMessage({
|
|
482
|
+
taskId,
|
|
483
|
+
deviceId,
|
|
484
|
+
messageId: payload.messageId,
|
|
485
|
+
payloadBody: JSON.stringify(payload)
|
|
486
|
+
});
|
|
487
|
+
return admission?.terminalBody === void 0 ? void 0 : parseAgentMessageDisposition(payload, admission.terminalBody);
|
|
488
|
+
}
|
|
489
|
+
function parseAgentMessageDisposition(payload, terminalBody) {
|
|
490
|
+
const invalid = () => new Error("Invalid persisted Agent message disposition.");
|
|
491
|
+
let decoded;
|
|
492
|
+
try {
|
|
493
|
+
decoded = JSON.parse(terminalBody);
|
|
494
|
+
} catch {
|
|
495
|
+
throw invalid();
|
|
601
496
|
}
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
497
|
+
if (decoded === null || typeof decoded !== "object" || Array.isArray(decoded)) throw invalid();
|
|
498
|
+
const record = decoded;
|
|
499
|
+
const storedPayload = AgentMessagePublishPayloadSchema.safeParse(record.payload);
|
|
500
|
+
const storedDisposition = AgentMessageDispositionPayloadSchema.safeParse(record.disposition);
|
|
501
|
+
if (!storedPayload.success || !storedDisposition.success || JSON.stringify(record.payload) !== JSON.stringify(payload)) throw invalid();
|
|
502
|
+
const disposition = storedDisposition.data;
|
|
503
|
+
if (!sameAgentRef(payload.agentRef, disposition.agentRef) || disposition.sessionRef !== payload.sessionRef || disposition.contract !== payload.contract || disposition.messageId !== payload.messageId || disposition.cursor !== payload.cursor || disposition.contentHash !== payload.contentHash) throw invalid();
|
|
504
|
+
return disposition;
|
|
505
|
+
}
|
|
506
|
+
function terminalReceiptKey(taskId) {
|
|
507
|
+
return `task:${taskId}:terminal`;
|
|
508
|
+
}
|
|
509
|
+
function sameAgentRef(expected, actual) {
|
|
510
|
+
return actual?.agentId === expected.agentId && actual.profileRevision === expected.profileRevision;
|
|
511
|
+
}
|
|
512
|
+
function sameReliableEgress(expected, actual) {
|
|
513
|
+
return sameAgentRef(expected.agentRef, actual.agentRef) && expected.sessionRef === actual.sessionRef && expected.policyRevision === actual.policyRevision && expected.eventId === actual.eventId && expected.cursor === actual.cursor && expected.contentHash === actual.contentHash && expected.byteCount === actual.byteCount && JSON.stringify(expected.payload) === JSON.stringify(actual.payload);
|
|
514
|
+
}
|
|
515
|
+
function contentReadCapability(surface) {
|
|
516
|
+
switch (surface) {
|
|
517
|
+
case "workspace":
|
|
518
|
+
return AGENT_CONTENT_WORKSPACE_READ_CAPABILITY;
|
|
519
|
+
case "transcript":
|
|
520
|
+
return AGENT_CONTENT_TRANSCRIPT_READ_CAPABILITY;
|
|
521
|
+
case "artifact":
|
|
522
|
+
return AGENT_CONTENT_ARTIFACT_READ_CAPABILITY;
|
|
608
523
|
}
|
|
609
524
|
}
|
|
610
|
-
function
|
|
611
|
-
|
|
612
|
-
validateActivityAppend({ ...input, ttlMs: bounds.ttlMs, capacity: bounds.capacity });
|
|
525
|
+
function hasCapabilities(capabilities, required) {
|
|
526
|
+
return capabilities !== void 0 && required.every((capability) => capabilities.includes(capability));
|
|
613
527
|
}
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
return activity.append({
|
|
617
|
-
taskId: input.taskId,
|
|
618
|
-
sourceEnvelopeId: input.sourceEnvelopeId,
|
|
619
|
-
batchSeq: input.batchSeq,
|
|
620
|
-
events: input.events,
|
|
621
|
-
dropped: input.dropped,
|
|
622
|
-
ttlMs: bounds.ttlMs,
|
|
623
|
-
capacity: bounds.capacity
|
|
624
|
-
});
|
|
528
|
+
function contentRequestReceiptKey(deviceId, agentRef, requestId) {
|
|
529
|
+
return agentReliabilityKey("agent-content-request", deviceId, agentRef, requestId);
|
|
625
530
|
}
|
|
626
|
-
function
|
|
627
|
-
|
|
628
|
-
const match = /^Bearer\s+(.+)$/i.exec(header);
|
|
629
|
-
return match?.[1];
|
|
531
|
+
function contentReceiptKey(deviceId, agentRef, requestId) {
|
|
532
|
+
return agentReliabilityKey("agent-content-receipt", deviceId, agentRef, requestId);
|
|
630
533
|
}
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
if (
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
if (deps.instanceProductId !== void 0 && claims.productId !== deps.instanceProductId) {
|
|
534
|
+
function matchesContentReadReceipt(request, receipt) {
|
|
535
|
+
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;
|
|
536
|
+
}
|
|
537
|
+
async function readContentRequest(stores, deviceId, agentRef, requestId) {
|
|
538
|
+
const stored = await stores.receipts.get(contentRequestReceiptKey(deviceId, agentRef, requestId));
|
|
539
|
+
if (stored === void 0) return void 0;
|
|
540
|
+
try {
|
|
541
|
+
return AgentContentReadPayloadSchema.parse(JSON.parse(stored.body));
|
|
542
|
+
} catch {
|
|
641
543
|
return void 0;
|
|
642
544
|
}
|
|
643
|
-
return {
|
|
644
|
-
kind: "device",
|
|
645
|
-
tenantId: device.tenantId,
|
|
646
|
-
productId: device.productId,
|
|
647
|
-
deviceId: device.deviceId
|
|
648
|
-
};
|
|
649
545
|
}
|
|
650
|
-
function
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
advanceCursor: (input) => core.mailbox.advanceCursor(tenant, input),
|
|
661
|
-
readCursor: (deviceId) => core.mailbox.readCursor(tenant, deviceId)
|
|
662
|
-
},
|
|
663
|
-
board: {
|
|
664
|
-
create: (input) => core.board.create(tenant, input),
|
|
665
|
-
get: (itemId) => core.board.get(tenant, itemId),
|
|
666
|
-
list: (query) => core.board.list(tenant, query),
|
|
667
|
-
claim: (input) => core.board.claim(tenant, input),
|
|
668
|
-
unclaim: (input) => core.board.unclaim(tenant, input),
|
|
669
|
-
updateStatus: (input) => core.board.updateStatus(tenant, input)
|
|
670
|
-
},
|
|
671
|
-
presence: {
|
|
672
|
-
publish: (input) => core.presence.publish(tenant, input),
|
|
673
|
-
read: (deviceId) => core.presence.read(tenant, deviceId),
|
|
674
|
-
list: () => core.presence.list(tenant)
|
|
675
|
-
},
|
|
676
|
-
activity: {
|
|
677
|
-
append: (input) => cloud.activity.append(tenant, input),
|
|
678
|
-
read: (taskId) => cloud.activity.read(tenant, taskId)
|
|
679
|
-
},
|
|
680
|
-
approvals: {
|
|
681
|
-
append: (input) => cloud.approvals.append(tenant, input),
|
|
682
|
-
read: (taskId) => cloud.approvals.read(tenant, taskId)
|
|
683
|
-
},
|
|
684
|
-
devices: {
|
|
685
|
-
get: (deviceId) => cloud.devices.get(tenant, deviceId),
|
|
686
|
-
list: () => cloud.devices.list(tenant),
|
|
687
|
-
revoke: (deviceId) => cloud.devices.revoke(tenant, deviceId),
|
|
688
|
-
recordCapabilities: (input) => principal.kind === "device" ? cloud.devices.recordCapabilities(tenant, {
|
|
689
|
-
deviceId: principal.deviceId,
|
|
690
|
-
capabilities: input.capabilities,
|
|
691
|
-
...input.harnesses === void 0 ? {} : { harnesses: input.harnesses }
|
|
692
|
-
}) : Promise.resolve(void 0),
|
|
693
|
-
readiness: () => cloud.devices.readiness(tenant, core.presence)
|
|
694
|
-
},
|
|
695
|
-
tasks: {
|
|
696
|
-
open: (input) => cloud.tasks.open(tenant, input),
|
|
697
|
-
reserveAgentOffer: (input) => cloud.tasks.reserveAgentOffer(tenant, input),
|
|
698
|
-
reserveAgentMessage: (input) => cloud.tasks.reserveAgentMessage(tenant, input),
|
|
699
|
-
readAgentMessage: (input) => cloud.tasks.readAgentMessage(tenant, input),
|
|
700
|
-
finalizeAgentMessage: (input) => cloud.tasks.finalizeAgentMessage(tenant, input),
|
|
701
|
-
get: (taskId) => cloud.tasks.get(tenant, taskId),
|
|
702
|
-
getMany: (taskIds) => cloud.tasks.getMany(tenant, taskIds),
|
|
703
|
-
list: (query) => cloud.tasks.list(tenant, query),
|
|
704
|
-
claim: (input) => cloud.tasks.claim(tenant, input),
|
|
705
|
-
recordStatus: (input) => cloud.tasks.recordStatus(tenant, input)
|
|
706
|
-
},
|
|
707
|
-
cancellations: {
|
|
708
|
-
request: (input) => cloud.cancellations.request(tenant, input)
|
|
709
|
-
},
|
|
710
|
-
dedup: {
|
|
711
|
-
checkAndRecord: (deviceId, envelopeId) => cloud.dedup.checkAndRecord(tenant, deviceId, envelopeId),
|
|
712
|
-
checkAndRecordAgent: (deviceId, agentRef, envelopeId) => cloud.dedup.checkAndRecordAgent(tenant, deviceId, agentRef, envelopeId)
|
|
713
|
-
},
|
|
714
|
-
receipts: {
|
|
715
|
-
record: (input) => cloud.receipts.record(tenant, input),
|
|
716
|
-
get: (key) => cloud.receipts.get(tenant, key)
|
|
717
|
-
},
|
|
718
|
-
egress: {
|
|
719
|
-
record: (input) => cloud.egress.record(tenant, input),
|
|
720
|
-
get: (deviceId, agentRef, eventId) => cloud.egress.get(tenant, deviceId, agentRef, eventId)
|
|
721
|
-
},
|
|
722
|
-
blobs: {
|
|
723
|
-
createUpload: (reservation) => cloud.blobs.createUpload(tenant, reservation),
|
|
724
|
-
observeUpload: (blobId, reservation) => cloud.blobs.observeUpload(tenant, blobId, reservation),
|
|
725
|
-
getDownloadUrl: (blobId) => cloud.blobs.getDownloadUrl(tenant, blobId)
|
|
726
|
-
},
|
|
727
|
-
quota: {
|
|
728
|
-
readReservation: (reservationId) => core.quota.readReservation(tenant, reservationId),
|
|
729
|
-
reserve: (input) => core.quota.reserve(tenant, input),
|
|
730
|
-
finalizeReservation: (input) => core.quota.finalizeReservation(tenant, input),
|
|
731
|
-
abortReservation: (reservationId) => core.quota.abortReservation(tenant, reservationId)
|
|
732
|
-
},
|
|
733
|
-
rateLimiter: {
|
|
734
|
-
consume: (deviceId) => cloud.rateLimiter.consume(tenant, deviceId)
|
|
735
|
-
}
|
|
736
|
-
};
|
|
737
|
-
}
|
|
738
|
-
|
|
739
|
-
// src/handlers/shared.ts
|
|
740
|
-
async function readJsonBody(c) {
|
|
741
|
-
try {
|
|
742
|
-
return await c.req.json();
|
|
743
|
-
} catch {
|
|
744
|
-
return void 0;
|
|
546
|
+
function inboundAgentRef(envelope) {
|
|
547
|
+
switch (envelope.type) {
|
|
548
|
+
case "task.claim":
|
|
549
|
+
case "task.decline":
|
|
550
|
+
case "task.complete":
|
|
551
|
+
case "task.fail":
|
|
552
|
+
case "task.cancelled":
|
|
553
|
+
return envelope.payload.agentRef;
|
|
554
|
+
default:
|
|
555
|
+
return void 0;
|
|
745
556
|
}
|
|
746
557
|
}
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
558
|
+
function agentRefEchoRequired(envelope) {
|
|
559
|
+
return envelope.type === "task.claim" || envelope.type === "task.decline" || envelope.type === "task.complete" || envelope.type === "task.fail" || envelope.type === "task.cancelled";
|
|
560
|
+
}
|
|
561
|
+
async function handleInboundEnvelope(stores, deviceId, envelope, activityBounds = DEFAULT_ACTIVITY_BOUNDS, agentMessageConsume, observer) {
|
|
562
|
+
if (envelope.v !== PROTOCOL_VERSION) return "rejected";
|
|
563
|
+
const outcome = await applyInboundGate(
|
|
564
|
+
stores,
|
|
565
|
+
deviceId,
|
|
566
|
+
envelope,
|
|
567
|
+
activityBounds,
|
|
568
|
+
agentMessageConsume
|
|
569
|
+
);
|
|
570
|
+
if (outcome === "accepted" && observer !== void 0) {
|
|
571
|
+
try {
|
|
572
|
+
observer.onInboundCommitted({ tenantId: stores.tenant, deviceId, envelope, outcome });
|
|
573
|
+
} catch {
|
|
754
574
|
}
|
|
755
575
|
}
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
return { body: void 0, tooLarge: true };
|
|
768
|
-
}
|
|
769
|
-
if (value.byteLength === 0) continue;
|
|
770
|
-
chunks.push(value);
|
|
771
|
-
total += value.byteLength;
|
|
576
|
+
return outcome;
|
|
577
|
+
}
|
|
578
|
+
async function applyInboundGate(stores, deviceId, envelope, activityBounds, agentMessageConsume) {
|
|
579
|
+
if (!await stores.rateLimiter.consume(deviceId)) return "rate_limited";
|
|
580
|
+
if (envelope.type === "conn.hello") {
|
|
581
|
+
if (envelope.payload.deviceId !== deviceId) return "rejected";
|
|
582
|
+
if (envelope.payload.harnesses !== void 0 && !envelope.payload.capabilities.includes("custom-harness")) return "rejected";
|
|
583
|
+
if (!envelope.payload.protocolVersions.includes(PROTOCOL_VERSION)) return "rejected";
|
|
584
|
+
const device = await stores.devices.get(deviceId);
|
|
585
|
+
if (device === void 0 || device.revoked || device.productId !== envelope.payload.productId) {
|
|
586
|
+
return "rejected";
|
|
772
587
|
}
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
588
|
+
if (await stores.devices.recordCapabilities({ capabilities: envelope.payload.capabilities, ...envelope.payload.harnesses === void 0 ? {} : { harnesses: envelope.payload.harnesses } }) === void 0) {
|
|
589
|
+
return "rejected";
|
|
590
|
+
}
|
|
591
|
+
return completeInboundEnvelope(stores, deviceId, envelope.id, void 0);
|
|
777
592
|
}
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
593
|
+
if (!DAEMON_TO_SERVER_TYPES.includes(envelope.type)) return "rejected";
|
|
594
|
+
if (envelope.type === "agent.message.publish") {
|
|
595
|
+
const message = await handleAgentMessagePublish(
|
|
596
|
+
stores,
|
|
597
|
+
deviceId,
|
|
598
|
+
envelope.task_id,
|
|
599
|
+
envelope.payload,
|
|
600
|
+
agentMessageConsume);
|
|
601
|
+
return message.outcome === "rejected" ? "rejected" : completeInboundEnvelope(stores, deviceId, envelope.id, envelope.payload.agentRef);
|
|
783
602
|
}
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
603
|
+
if (envelope.type === "agent.egress.reliable") {
|
|
604
|
+
const device = await stores.devices.get(deviceId);
|
|
605
|
+
if (device === void 0 || device.revoked || !hasCapabilities(device.capabilities, [AGENT_EGRESS_POLICY_CAPABILITY, AGENT_EGRESS_RELIABLE_ACK_CAPABILITY])) {
|
|
606
|
+
return "rejected";
|
|
607
|
+
}
|
|
608
|
+
const outcome = await stores.egress.record({
|
|
609
|
+
deviceId,
|
|
610
|
+
payload: envelope.payload,
|
|
611
|
+
receiptId: crypto.randomUUID()
|
|
790
612
|
});
|
|
791
|
-
|
|
613
|
+
if (!outcome.created && !sameReliableEgress(outcome.record.payload, envelope.payload)) return "rejected";
|
|
614
|
+
return completeInboundEnvelope(stores, deviceId, envelope.id, envelope.payload.agentRef);
|
|
792
615
|
}
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
616
|
+
if (envelope.type === "agent.content.receipt") {
|
|
617
|
+
const device = await stores.devices.get(deviceId);
|
|
618
|
+
if (device === void 0 || device.revoked || envelope.payload.eventId !== envelope.payload.requestId || !hasCapabilities(device.capabilities, [contentReadCapability(envelope.payload.surface), AGENT_EGRESS_RELIABLE_ACK_CAPABILITY])) {
|
|
619
|
+
return "rejected";
|
|
620
|
+
}
|
|
621
|
+
const request = await readContentRequest(stores, deviceId, envelope.payload.agentRef, envelope.payload.requestId);
|
|
622
|
+
if (request === void 0 || !matchesContentReadReceipt(request, envelope.payload)) return "rejected";
|
|
623
|
+
const key = contentReceiptKey(deviceId, envelope.payload.agentRef, envelope.payload.requestId);
|
|
624
|
+
const body = JSON.stringify(envelope.payload);
|
|
625
|
+
const receipt = await stores.receipts.record({ key, body });
|
|
626
|
+
if (!receipt.created && receipt.receipt.body !== body) return "rejected";
|
|
627
|
+
return completeInboundEnvelope(stores, deviceId, envelope.id, envelope.payload.agentRef);
|
|
801
628
|
}
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
629
|
+
const taskId = envelope.task_id;
|
|
630
|
+
if (taskId === void 0) return "rejected";
|
|
631
|
+
const attempt = await stores.tasks.get(taskId);
|
|
632
|
+
if (attempt === void 0 || attempt.deviceId !== deviceId) return "rejected";
|
|
633
|
+
if (attempt?.ownerDeviceId !== void 0 && attempt.ownerDeviceId !== deviceId) return "rejected";
|
|
634
|
+
if (envelope.type === "task.claim" && envelope.payload.deviceId !== deviceId) return "rejected";
|
|
635
|
+
if (envelope.type === "task.claim") {
|
|
636
|
+
const harnessId = envelope.payload.harnessId;
|
|
637
|
+
if (harnessId !== void 0 && envelope.payload.runtime !== void 0) return "rejected";
|
|
638
|
+
if (attempt.ownerDeviceId !== void 0 && (harnessId !== void 0 || attempt.claimedHarnessId !== void 0) && harnessId !== attempt.claimedHarnessId) return "rejected";
|
|
639
|
+
const device = await stores.devices.get(deviceId);
|
|
640
|
+
if (harnessId !== void 0 && (!device?.capabilities?.includes("custom-harness") || !device.harnesses?.some((harness) => harness.id === harnessId))) return "rejected";
|
|
641
|
+
const messageId = await taskOfferMessageId(stores.tenant, taskId, deviceId, attempt.agentRef, hashOfferBytes);
|
|
642
|
+
const offer = await stores.receipts.get(`task-offer:${messageId}`);
|
|
643
|
+
if (offer !== void 0) {
|
|
644
|
+
const sealed = JSON.parse(offer.body);
|
|
645
|
+
if (harnessId !== void 0 && (sealed.payload.runtime !== void 0 || sealed.payload.dispatchSelection !== void 0)) return "rejected";
|
|
646
|
+
if (sealed.payload.harnessId !== void 0 && sealed.payload.harnessId !== harnessId) return "rejected";
|
|
647
|
+
} else if (harnessId !== void 0) return "rejected";
|
|
808
648
|
}
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
}
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
}
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
if (!parsed.success) return c.json({ error: "size, contentType, and contentHash are required" }, 400);
|
|
828
|
-
if (parsed.data.size > deps.maxBlobSizeBytes) {
|
|
829
|
-
return c.json({ error: `blob exceeds max size of ${deps.maxBlobSizeBytes} bytes` }, 413);
|
|
830
|
-
}
|
|
831
|
-
const reservationId = idempotencyKey(c);
|
|
832
|
-
if (reservationId === void 0) {
|
|
833
|
-
return c.json({ error: "Idempotency-Key header is required" }, 400);
|
|
834
|
-
}
|
|
835
|
-
try {
|
|
836
|
-
const reservation = await authenticated.stores.quota.reserve({
|
|
837
|
-
reservationId,
|
|
838
|
-
kind: "object",
|
|
839
|
-
expectedBytes: BigInt(parsed.data.size),
|
|
840
|
-
contentHash: contentHash(parsed.data.contentHash),
|
|
841
|
-
contentType: parsed.data.contentType,
|
|
842
|
-
ttlMs: BLOB_RESERVATION_TTL_MS
|
|
843
|
-
});
|
|
844
|
-
try {
|
|
845
|
-
const { blobId, uploadUrl } = await authenticated.stores.blobs.createUpload(reservation);
|
|
846
|
-
const response = { blobId, uploadUrl };
|
|
847
|
-
return c.json(response, 200);
|
|
848
|
-
} catch (error) {
|
|
849
|
-
await authenticated.stores.quota.abortReservation(reservationId).catch(() => void 0);
|
|
850
|
-
const rendered = renderStorageError(c, error);
|
|
851
|
-
if (rendered !== void 0) return rendered;
|
|
852
|
-
throw error;
|
|
853
|
-
}
|
|
854
|
-
} catch (error) {
|
|
855
|
-
const rendered = renderStorageError(c, error);
|
|
856
|
-
if (rendered !== void 0) return rendered;
|
|
857
|
-
throw error;
|
|
858
|
-
}
|
|
859
|
-
};
|
|
860
|
-
}
|
|
861
|
-
function finalizeBlobHandler(deps) {
|
|
862
|
-
return async (c) => {
|
|
863
|
-
const authenticated = await authenticateDevice(c, deps);
|
|
864
|
-
if (authenticated === void 0) return c.json({ error: "unauthorized" }, 401);
|
|
865
|
-
const reservationId = idempotencyKey(c);
|
|
866
|
-
if (reservationId === void 0) {
|
|
867
|
-
return c.json({ error: "Idempotency-Key header is required" }, 400);
|
|
868
|
-
}
|
|
649
|
+
let unclaimedInterruption = false;
|
|
650
|
+
if (envelope.type === "task.fail" && envelope.payload.recovery !== void 0) {
|
|
651
|
+
if (envelope.payload.reason !== "daemon_interrupted" || envelope.payload.retryable !== false) return "rejected";
|
|
652
|
+
const messageId = await taskOfferMessageId(stores.tenant, taskId, deviceId, attempt.agentRef, hashOfferBytes);
|
|
653
|
+
if (envelope.payload.recovery.offerId !== messageId) return "rejected";
|
|
654
|
+
const offer = await stores.receipts.get(`task-offer:${messageId}`);
|
|
655
|
+
if (offer === void 0) return "rejected";
|
|
656
|
+
const sealed = JSON.parse(offer.body);
|
|
657
|
+
const harnessId = envelope.payload.harnessId;
|
|
658
|
+
if (sealed.payload.harnessId !== void 0 && sealed.payload.harnessId !== harnessId) return "rejected";
|
|
659
|
+
if (harnessId !== void 0 && (sealed.payload.runtime !== void 0 || sealed.payload.dispatchSelection !== void 0)) return "rejected";
|
|
660
|
+
unclaimedInterruption = attempt.ownerDeviceId === void 0 && attempt.claimedHarnessId === void 0;
|
|
661
|
+
}
|
|
662
|
+
if ((envelope.type === "task.complete" || envelope.type === "task.fail" || envelope.type === "task.cancelled") && !unclaimedInterruption && envelope.payload.harnessId !== attempt.claimedHarnessId) return "rejected";
|
|
663
|
+
if (attempt?.agentRef !== void 0 && agentRefEchoRequired(envelope) && !sameAgentRef(attempt.agentRef, inboundAgentRef(envelope))) {
|
|
664
|
+
return "rejected";
|
|
665
|
+
}
|
|
666
|
+
if (envelope.type === "task.progress" && envelope.payload.events.length > 0) {
|
|
869
667
|
try {
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
668
|
+
validateActivityBatch(
|
|
669
|
+
{
|
|
670
|
+
taskId,
|
|
671
|
+
sourceEnvelopeId: envelope.id,
|
|
672
|
+
batchSeq: envelope.payload.seq,
|
|
673
|
+
events: envelope.payload.events,
|
|
674
|
+
dropped: 0
|
|
675
|
+
},
|
|
676
|
+
activityBounds
|
|
878
677
|
);
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
return
|
|
678
|
+
} catch (caught) {
|
|
679
|
+
if (isCloudError(caught, "activity_batch_too_large") || isCloudError(caught, "coordination_input_invalid")) {
|
|
680
|
+
return "rejected";
|
|
882
681
|
}
|
|
883
|
-
|
|
884
|
-
reservationId,
|
|
885
|
-
...observed
|
|
886
|
-
});
|
|
887
|
-
return c.body(null, 204);
|
|
888
|
-
} catch (error) {
|
|
889
|
-
const rendered = renderStorageError(c, error);
|
|
890
|
-
if (rendered !== void 0) return rendered;
|
|
891
|
-
throw error;
|
|
682
|
+
throw caught;
|
|
892
683
|
}
|
|
893
|
-
}
|
|
684
|
+
}
|
|
685
|
+
const approvalInput = approvalTimelineAppendInput(taskId, envelope);
|
|
686
|
+
if (approvalInput !== void 0) {
|
|
687
|
+
try {
|
|
688
|
+
validateApprovalTimelineAppend(approvalInput);
|
|
689
|
+
} catch (caught) {
|
|
690
|
+
if (isCloudError(caught, "coordination_input_invalid")) return "rejected";
|
|
691
|
+
throw caught;
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
const lifecycleOutcome = await applyLifecycle(stores, deviceId, taskId, envelope, activityBounds, attempt?.agentRef);
|
|
695
|
+
if (lifecycleOutcome === "rejected") return "rejected";
|
|
696
|
+
return completeInboundEnvelope(stores, deviceId, envelope.id, attempt?.agentRef);
|
|
894
697
|
}
|
|
895
|
-
function
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
if (authenticated === void 0) return c.json({ error: "unauthorized" }, 401);
|
|
899
|
-
const downloadUrl = await authenticated.stores.blobs.getDownloadUrl(c.req.param("id") ?? "");
|
|
900
|
-
if (downloadUrl === void 0) return c.json({ error: "blob not found" }, 404);
|
|
901
|
-
const response = { downloadUrl };
|
|
902
|
-
return c.json(response, 200);
|
|
903
|
-
};
|
|
698
|
+
async function completeInboundEnvelope(stores, deviceId, envelopeId, agentRef) {
|
|
699
|
+
const duplicate = agentRef === void 0 ? await stores.dedup.checkAndRecord(deviceId, envelopeId) : await stores.dedup.checkAndRecordAgent(deviceId, agentRef, envelopeId);
|
|
700
|
+
return duplicate ? "duplicate" : "accepted";
|
|
904
701
|
}
|
|
905
|
-
function
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
if (contentLength !== void 0 && /^\d+$/u.test(contentLength) && BigInt(contentLength) > expectedBytes) {
|
|
918
|
-
return c.json({ error: "upload exceeds declared size" }, 413);
|
|
702
|
+
async function applyLifecycle(stores, deviceId, taskId, envelope, activityBounds, persistedAgentRef) {
|
|
703
|
+
switch (envelope.type) {
|
|
704
|
+
case "task.claim": {
|
|
705
|
+
const claimed = await stores.tasks.claim({
|
|
706
|
+
taskId,
|
|
707
|
+
deviceId,
|
|
708
|
+
...envelope.payload.runtime === void 0 ? {} : { runtime: envelope.payload.runtime },
|
|
709
|
+
...envelope.payload.harnessId === void 0 ? {} : { harnessId: envelope.payload.harnessId },
|
|
710
|
+
...envelope.payload.capabilities === void 0 ? {} : { capabilities: envelope.payload.capabilities }
|
|
711
|
+
});
|
|
712
|
+
if (claimed === void 0 || claimed.ownerDeviceId !== deviceId || claimed.claimedRuntime !== envelope.payload.runtime || claimed.claimedHarnessId !== envelope.payload.harnessId) return "rejected";
|
|
713
|
+
return;
|
|
919
714
|
}
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
715
|
+
case "task.started":
|
|
716
|
+
await stores.tasks.recordStatus({
|
|
717
|
+
taskId,
|
|
718
|
+
status: "running",
|
|
719
|
+
...persistedAgentRef === void 0 ? {} : { agentRef: persistedAgentRef }
|
|
720
|
+
});
|
|
721
|
+
return;
|
|
722
|
+
case "task.decline":
|
|
723
|
+
await recordTerminal(stores, taskId, envelope);
|
|
724
|
+
return;
|
|
725
|
+
case "task.progress":
|
|
726
|
+
if (envelope.payload.events.length > 0) {
|
|
727
|
+
await appendActivityEvents(
|
|
728
|
+
stores.activity,
|
|
729
|
+
{
|
|
730
|
+
taskId,
|
|
731
|
+
sourceEnvelopeId: envelope.id,
|
|
732
|
+
batchSeq: envelope.payload.seq,
|
|
733
|
+
events: envelope.payload.events,
|
|
734
|
+
dropped: 0
|
|
735
|
+
},
|
|
736
|
+
activityBounds
|
|
737
|
+
);
|
|
940
738
|
}
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
739
|
+
return;
|
|
740
|
+
case "task.await_approval":
|
|
741
|
+
await stores.approvals.append(approvalTimelineAppendInput(taskId, envelope));
|
|
742
|
+
return;
|
|
743
|
+
case "task.approval_resolved":
|
|
744
|
+
await stores.approvals.append(approvalTimelineAppendInput(taskId, envelope));
|
|
745
|
+
return;
|
|
746
|
+
case "task.complete":
|
|
747
|
+
await recordTerminal(stores, taskId, envelope);
|
|
748
|
+
return;
|
|
749
|
+
case "task.fail":
|
|
750
|
+
await recordTerminal(stores, taskId, envelope);
|
|
751
|
+
return;
|
|
752
|
+
case "task.cancelled":
|
|
753
|
+
await recordTerminal(stores, taskId, envelope);
|
|
754
|
+
return;
|
|
755
|
+
default:
|
|
756
|
+
return;
|
|
946
757
|
}
|
|
947
|
-
return { tooLarge: false, data: data.subarray(0, offset) };
|
|
948
|
-
}
|
|
949
|
-
function blobDownloadContentHandler(deps) {
|
|
950
|
-
return async (c) => {
|
|
951
|
-
const blobId = c.req.param("id") ?? "";
|
|
952
|
-
const query = SignedUrlQuerySchema.safeParse({ sig: c.req.query("sig"), exp: c.req.query("exp") });
|
|
953
|
-
if (!query.success || !await deps.contentProxy.verifySignedUrl(blobId, "get", query.data.sig, query.data.exp)) {
|
|
954
|
-
return c.json({ error: "invalid or expired signature" }, 401);
|
|
955
|
-
}
|
|
956
|
-
const result = await deps.contentProxy.readContent(blobId);
|
|
957
|
-
if (result === void 0) return c.json({ error: "blob not found" }, 404);
|
|
958
|
-
if (!result.ok) return c.json({ error: result.code }, BLOB_READ_ERROR_HTTP_STATUS[result.code]);
|
|
959
|
-
return c.body(new Uint8Array(result.content.data), 200, { "content-type": result.content.contentType });
|
|
960
|
-
};
|
|
961
|
-
}
|
|
962
|
-
var BLOB_READ_ERROR_HTTP_STATUS = {
|
|
963
|
-
blob_upstream_unavailable: 502,
|
|
964
|
-
blob_upstream_stream_interrupted: 502
|
|
965
|
-
};
|
|
966
|
-
function idempotencyKey(c) {
|
|
967
|
-
const value = c.req.header("Idempotency-Key");
|
|
968
|
-
if (value === void 0 || value.length === 0 || value.length > 200) return void 0;
|
|
969
|
-
return value;
|
|
970
758
|
}
|
|
971
|
-
function
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
759
|
+
function approvalTimelineAppendInput(taskId, envelope) {
|
|
760
|
+
switch (envelope.type) {
|
|
761
|
+
case "task.await_approval":
|
|
762
|
+
return {
|
|
763
|
+
taskId,
|
|
764
|
+
sourceEnvelopeId: envelope.id,
|
|
765
|
+
event: {
|
|
766
|
+
type: "approval_requested",
|
|
767
|
+
summary: envelope.payload.summary,
|
|
768
|
+
...envelope.payload.approvalId === void 0 ? {} : { approvalId: envelope.payload.approvalId }
|
|
769
|
+
}
|
|
770
|
+
};
|
|
771
|
+
case "task.approval_resolved":
|
|
772
|
+
return {
|
|
773
|
+
taskId,
|
|
774
|
+
sourceEnvelopeId: envelope.id,
|
|
775
|
+
event: {
|
|
776
|
+
type: "approval_resolved",
|
|
777
|
+
approvalId: envelope.payload.approvalId,
|
|
778
|
+
decision: envelope.payload.decision,
|
|
779
|
+
resolvedBy: envelope.payload.resolvedBy,
|
|
780
|
+
at: envelope.payload.at
|
|
781
|
+
}
|
|
782
|
+
};
|
|
783
|
+
default:
|
|
784
|
+
return void 0;
|
|
982
785
|
}
|
|
983
|
-
|
|
984
|
-
|
|
786
|
+
}
|
|
787
|
+
async function recordTerminal(stores, taskId, envelope) {
|
|
788
|
+
const receipt = await stores.receipts.record({
|
|
789
|
+
key: terminalReceiptKey(taskId),
|
|
790
|
+
body: encodeEnvelope(envelope)
|
|
791
|
+
});
|
|
792
|
+
const winner = terminalReceiptBinding(taskId, receipt.receipt.body);
|
|
793
|
+
if (winner === void 0) {
|
|
794
|
+
throw new Error(`terminal receipt for ${taskId} is not a canonical terminal binding`);
|
|
985
795
|
}
|
|
986
|
-
|
|
796
|
+
const attempt = await stores.tasks.get(taskId);
|
|
797
|
+
if (attempt?.cancellation !== void 0) {
|
|
798
|
+
await stores.tasks.recordStatus({
|
|
799
|
+
taskId,
|
|
800
|
+
status: "cancelled",
|
|
801
|
+
...attempt.agentRef === void 0 ? {} : { agentRef: attempt.agentRef }
|
|
802
|
+
});
|
|
803
|
+
return;
|
|
804
|
+
}
|
|
805
|
+
const recordedAttempt = await stores.tasks.recordStatus({
|
|
806
|
+
taskId,
|
|
807
|
+
status: winner.status,
|
|
808
|
+
...attempt?.agentRef === void 0 ? {} : { agentRef: attempt.agentRef },
|
|
809
|
+
...winner.cause === void 0 ? {} : { terminalCause: winner.cause }
|
|
810
|
+
});
|
|
811
|
+
if (recordedAttempt?.cancellation !== void 0 || recordedAttempt?.status !== winner.status) {
|
|
812
|
+
if (recordedAttempt?.cancellation !== void 0) {
|
|
813
|
+
await stores.tasks.recordStatus({
|
|
814
|
+
taskId,
|
|
815
|
+
status: "cancelled",
|
|
816
|
+
...recordedAttempt.agentRef === void 0 ? {} : { agentRef: recordedAttempt.agentRef }
|
|
817
|
+
});
|
|
818
|
+
}
|
|
819
|
+
return;
|
|
820
|
+
}
|
|
821
|
+
await projectTerminalToReview(stores.board, taskId);
|
|
987
822
|
}
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
823
|
+
function terminalStatus(envelope) {
|
|
824
|
+
switch (envelope.type) {
|
|
825
|
+
case "task.complete":
|
|
826
|
+
return "complete";
|
|
827
|
+
case "task.fail":
|
|
828
|
+
case "task.decline":
|
|
829
|
+
return "failed";
|
|
830
|
+
case "task.cancelled":
|
|
831
|
+
return "cancelled";
|
|
832
|
+
default:
|
|
833
|
+
return void 0;
|
|
834
|
+
}
|
|
992
835
|
}
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
400
|
|
1005
|
-
);
|
|
1006
|
-
}
|
|
1007
|
-
const device = await deps.auth.redeemAndRegister(parsed.data);
|
|
1008
|
-
if (device === void 0) return c.json({ error: "invalid pairing code" }, 401);
|
|
1009
|
-
const { accessToken, expiresAt } = await deps.auth.mintAccessToken(device);
|
|
1010
|
-
const response = PairResponseSchema.parse({
|
|
1011
|
-
deviceId: device.deviceId,
|
|
1012
|
-
accessToken,
|
|
1013
|
-
refreshHint: expiresAt,
|
|
1014
|
-
tenantId: device.tenantId
|
|
1015
|
-
});
|
|
1016
|
-
return c.json(response, 200);
|
|
1017
|
-
};
|
|
836
|
+
function terminalReceiptBinding(taskId, body) {
|
|
837
|
+
let envelope;
|
|
838
|
+
try {
|
|
839
|
+
envelope = decodeEnvelope(body);
|
|
840
|
+
} catch {
|
|
841
|
+
return void 0;
|
|
842
|
+
}
|
|
843
|
+
const status = terminalStatus(envelope);
|
|
844
|
+
if (status === void 0 || envelope.task_id !== taskId || encodeEnvelope(envelope) !== body) return void 0;
|
|
845
|
+
const cause = envelope.type === "task.fail" || envelope.type === "task.cancelled" || envelope.type === "task.decline" ? envelope.payload.reason : void 0;
|
|
846
|
+
return { status, ...cause === void 0 ? {} : { cause } };
|
|
1018
847
|
}
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
};
|
|
848
|
+
|
|
849
|
+
// src/task-agent-message.ts
|
|
850
|
+
var FrozenMessageBinding = z.object({
|
|
851
|
+
agentRef: AgentRefSchema,
|
|
852
|
+
sessionRef: z.string().min(1).optional(),
|
|
853
|
+
requirement: AgentMessageEgressRequirementSchema,
|
|
854
|
+
context: AgentMessageServerContextSchema
|
|
855
|
+
}).strict();
|
|
856
|
+
function sameRef(a, b) {
|
|
857
|
+
return a.agentId === b.agentId && a.profileRevision === b.profileRevision;
|
|
1030
858
|
}
|
|
1031
|
-
function
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
859
|
+
async function readTaskAgentMessage(stores, deviceId, taskId, expectedRef) {
|
|
860
|
+
const agentRef = AgentRefSchema.parse(expectedRef);
|
|
861
|
+
const attempt = await stores.tasks.get(taskId);
|
|
862
|
+
if (!attempt || attempt.tenantId !== stores.tenant || attempt.taskId !== taskId || attempt.deviceId !== deviceId || !attempt.agentRef || !sameRef(attempt.agentRef, agentRef)) return void 0;
|
|
863
|
+
const admission = await stores.tasks.readTaskAgentMessage({ taskId, deviceId });
|
|
864
|
+
if (!admission) return void 0;
|
|
865
|
+
const invalid = () => new Error("Invalid persisted task Agent message.");
|
|
866
|
+
const binding = await stores.receipts.get(`agent-message-offer:${deviceId}:${taskId}`);
|
|
867
|
+
if (!binding) throw invalid();
|
|
868
|
+
let body, frozenBody;
|
|
869
|
+
try {
|
|
870
|
+
body = JSON.parse(admission.payloadBody);
|
|
871
|
+
frozenBody = JSON.parse(binding.body);
|
|
872
|
+
} catch {
|
|
873
|
+
throw invalid();
|
|
874
|
+
}
|
|
875
|
+
const parsed = AgentMessagePublishPayloadSchema.safeParse(body);
|
|
876
|
+
const frozen = FrozenMessageBinding.safeParse(frozenBody);
|
|
877
|
+
if (!parsed.success || !frozen.success) throw invalid();
|
|
878
|
+
const payload = parsed.data;
|
|
879
|
+
if (admission.messageId !== payload.messageId || JSON.stringify(payload) !== admission.payloadBody || !sameRef(payload.agentRef, agentRef) || !sameRef(frozen.data.agentRef, agentRef) || payload.contract !== frozen.data.requirement.contract || payload.contentType !== frozen.data.requirement.contentType || frozen.data.sessionRef !== void 0 && payload.sessionRef !== frozen.data.sessionRef || payload.byteCount > frozen.data.requirement.maxBytes) throw invalid();
|
|
880
|
+
const disposition = admission.terminalBody === void 0 ? void 0 : parseAgentMessageDisposition(payload, admission.terminalBody);
|
|
881
|
+
return { payload, context: frozen.data.context, ...disposition === void 0 ? {} : { disposition } };
|
|
882
|
+
}
|
|
883
|
+
var RecurringExecutionInputSchema = z.object({
|
|
884
|
+
taskId: z.string().min(1),
|
|
885
|
+
deviceId: z.string().min(1),
|
|
886
|
+
payload: TaskOfferForAgentWithEgressFreshPayloadSchema.required({ runtime: true, messageEgress: true, terminalProjection: true }),
|
|
887
|
+
agentMessageContext: AgentMessageServerContextSchema
|
|
888
|
+
}).strict();
|
|
889
|
+
|
|
890
|
+
// src/approval-control.ts
|
|
891
|
+
var StaleApprovalError = class extends Error {
|
|
892
|
+
constructor(taskId, requestedApprovalId, currentApprovalId) {
|
|
893
|
+
super(
|
|
894
|
+
`cannot resolve approval ${requestedApprovalId} for task ${taskId}: the currently pending approval is ${currentApprovalId ?? "(none recorded)"}`
|
|
895
|
+
);
|
|
896
|
+
this.taskId = taskId;
|
|
897
|
+
this.requestedApprovalId = requestedApprovalId;
|
|
898
|
+
this.currentApprovalId = currentApprovalId;
|
|
899
|
+
this.name = "StaleApprovalError";
|
|
900
|
+
}
|
|
901
|
+
taskId;
|
|
902
|
+
requestedApprovalId;
|
|
903
|
+
currentApprovalId;
|
|
904
|
+
};
|
|
905
|
+
function pendingApproval(tail) {
|
|
906
|
+
if (tail === void 0) return void 0;
|
|
907
|
+
let pending;
|
|
908
|
+
for (const entry of tail.entries) {
|
|
909
|
+
const event = entry.event;
|
|
910
|
+
if (event.type === "approval_requested") {
|
|
911
|
+
pending = {
|
|
912
|
+
sourceEnvelopeId: entry.sourceEnvelopeId,
|
|
913
|
+
revision: entry.revision,
|
|
914
|
+
...event.approvalId === void 0 ? {} : { approvalId: event.approvalId }
|
|
915
|
+
};
|
|
916
|
+
continue;
|
|
1042
917
|
}
|
|
1043
|
-
if (
|
|
1044
|
-
|
|
918
|
+
if (pending === void 0) continue;
|
|
919
|
+
if (pending.approvalId === void 0 || pending.approvalId === event.approvalId) {
|
|
920
|
+
pending = void 0;
|
|
1045
921
|
}
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
return c.json(response, 200);
|
|
1049
|
-
};
|
|
922
|
+
}
|
|
923
|
+
return pending;
|
|
1050
924
|
}
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
"agent-egress-policy",
|
|
1063
|
-
"agent-egress-reliable-ack",
|
|
1064
|
-
"agent-egress-fresh-session",
|
|
1065
|
-
// `handleAgentMessagePublish` accepts `agent.message_egress` gated on exactly
|
|
1066
|
-
// this capability (`packages/cloud/src/inbound.ts:124`) — the same inbound
|
|
1067
|
-
// family as the three egress flags above, omitted by the same drift.
|
|
1068
|
-
"agent-message-egress",
|
|
1069
|
-
"agent-content-workspace-read",
|
|
1070
|
-
"agent-content-transcript-read",
|
|
1071
|
-
"agent-content-artifact-read"
|
|
1072
|
-
];
|
|
1073
|
-
function sleep(ms) {
|
|
1074
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
925
|
+
|
|
926
|
+
// src/steer-control.ts
|
|
927
|
+
function steerRejectionMessage(taskId, code, status, runtime) {
|
|
928
|
+
switch (code) {
|
|
929
|
+
case "task_terminal":
|
|
930
|
+
return `cannot steer task ${taskId}: task is already terminal (status ${status})`;
|
|
931
|
+
case "task_not_running":
|
|
932
|
+
return `cannot steer task ${taskId}: not running (status ${status})`;
|
|
933
|
+
case "steer_unsupported_runtime":
|
|
934
|
+
return `cannot steer task ${taskId}: claimed runtime ${runtime ?? "(unknown)"} does not support steering`;
|
|
935
|
+
}
|
|
1075
936
|
}
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
const response2 = {
|
|
1139
|
-
events,
|
|
1140
|
-
cursor: page.nextSeq,
|
|
1141
|
-
capabilities: CLOUD_PROTOCOL_CAPABILITIES
|
|
1142
|
-
};
|
|
1143
|
-
await stores.mailbox.recordDelivery({
|
|
1144
|
-
deviceId: device.deviceId,
|
|
1145
|
-
deliveredSeq: response2.cursor
|
|
1146
|
-
});
|
|
1147
|
-
return c.json(response2, 200);
|
|
937
|
+
var SteerRejectedError = class extends Error {
|
|
938
|
+
constructor(taskId, code, status, runtime) {
|
|
939
|
+
super(steerRejectionMessage(taskId, code, status, runtime));
|
|
940
|
+
this.taskId = taskId;
|
|
941
|
+
this.code = code;
|
|
942
|
+
this.status = status;
|
|
943
|
+
this.runtime = runtime;
|
|
944
|
+
this.name = "SteerRejectedError";
|
|
945
|
+
}
|
|
946
|
+
taskId;
|
|
947
|
+
code;
|
|
948
|
+
status;
|
|
949
|
+
runtime;
|
|
950
|
+
};
|
|
951
|
+
|
|
952
|
+
// src/crypto/web-crypto.ts
|
|
953
|
+
var PAIRING_CODE_ALPHABET = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789";
|
|
954
|
+
var HEX_DIGITS = "0123456789abcdef";
|
|
955
|
+
function base64UrlEncode(bytes) {
|
|
956
|
+
let binary = "";
|
|
957
|
+
for (const byte of bytes) binary += String.fromCharCode(byte);
|
|
958
|
+
return btoa(binary).replaceAll("+", "-").replaceAll("/", "_").replaceAll("=", "");
|
|
959
|
+
}
|
|
960
|
+
function base64UrlDecode(value) {
|
|
961
|
+
const padded = value.replaceAll("-", "+").replaceAll("_", "/");
|
|
962
|
+
try {
|
|
963
|
+
const binary = atob(padded);
|
|
964
|
+
const bytes = new Uint8Array(binary.length);
|
|
965
|
+
for (let index = 0; index < binary.length; index += 1) bytes[index] = binary.charCodeAt(index);
|
|
966
|
+
return bytes;
|
|
967
|
+
} catch {
|
|
968
|
+
return void 0;
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
function toHex(bytes) {
|
|
972
|
+
let out = "";
|
|
973
|
+
for (const byte of bytes) {
|
|
974
|
+
out += HEX_DIGITS[byte >> 4 & 15];
|
|
975
|
+
out += HEX_DIGITS[byte & 15];
|
|
976
|
+
}
|
|
977
|
+
return out;
|
|
978
|
+
}
|
|
979
|
+
function subtle() {
|
|
980
|
+
const webCrypto = globalThis.crypto;
|
|
981
|
+
if (webCrypto?.subtle === void 0) {
|
|
982
|
+
throw new Error("@byok-sdk/cloud requires a WebCrypto implementation on globalThis.crypto");
|
|
983
|
+
}
|
|
984
|
+
return webCrypto.subtle;
|
|
985
|
+
}
|
|
986
|
+
function createWebCrypto() {
|
|
987
|
+
return {
|
|
988
|
+
randomUuid() {
|
|
989
|
+
return globalThis.crypto.randomUUID();
|
|
990
|
+
},
|
|
991
|
+
randomToken(byteLength) {
|
|
992
|
+
return base64UrlEncode(globalThis.crypto.getRandomValues(new Uint8Array(byteLength)));
|
|
993
|
+
},
|
|
994
|
+
randomPairingCode(length) {
|
|
995
|
+
const bytes = globalThis.crypto.getRandomValues(new Uint8Array(length));
|
|
996
|
+
let out = "";
|
|
997
|
+
for (const byte of bytes) {
|
|
998
|
+
out += PAIRING_CODE_ALPHABET[byte % PAIRING_CODE_ALPHABET.length];
|
|
1148
999
|
}
|
|
1149
|
-
|
|
1000
|
+
return out;
|
|
1001
|
+
},
|
|
1002
|
+
async verifyEd25519(publicKeyBase64Url, message, signature) {
|
|
1003
|
+
const signatureBytes = base64UrlDecode(signature);
|
|
1004
|
+
if (signatureBytes === void 0) return false;
|
|
1005
|
+
try {
|
|
1006
|
+
const key = await subtle().importKey(
|
|
1007
|
+
"jwk",
|
|
1008
|
+
{ kty: "OKP", crv: "Ed25519", x: publicKeyBase64Url },
|
|
1009
|
+
{ name: "Ed25519" },
|
|
1010
|
+
false,
|
|
1011
|
+
["verify"]
|
|
1012
|
+
);
|
|
1013
|
+
const messageBytes = typeof message === "string" ? new TextEncoder().encode(message) : message;
|
|
1014
|
+
return await subtle().verify({ name: "Ed25519" }, key, signatureBytes, messageBytes);
|
|
1015
|
+
} catch {
|
|
1016
|
+
return false;
|
|
1017
|
+
}
|
|
1018
|
+
},
|
|
1019
|
+
async hmacSha256(secret, message) {
|
|
1020
|
+
const key = await subtle().importKey("raw", secret, { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
|
|
1021
|
+
const signature = await subtle().sign("HMAC", key, new TextEncoder().encode(message));
|
|
1022
|
+
return base64UrlEncode(new Uint8Array(signature));
|
|
1023
|
+
},
|
|
1024
|
+
async sha256(data) {
|
|
1025
|
+
const digest = await subtle().digest("SHA-256", data);
|
|
1026
|
+
return `sha256:${toHex(new Uint8Array(digest))}`;
|
|
1027
|
+
},
|
|
1028
|
+
timingSafeEqual(left, right) {
|
|
1029
|
+
const leftBytes = base64UrlDecode(left);
|
|
1030
|
+
const rightBytes = base64UrlDecode(right);
|
|
1031
|
+
if (leftBytes === void 0 || rightBytes === void 0) return false;
|
|
1032
|
+
if (leftBytes.length !== rightBytes.length) return false;
|
|
1033
|
+
let diff = 0;
|
|
1034
|
+
for (let index = 0; index < leftBytes.length; index += 1) {
|
|
1035
|
+
diff |= (leftBytes[index] ?? 0) ^ (rightBytes[index] ?? 0);
|
|
1036
|
+
}
|
|
1037
|
+
return diff === 0;
|
|
1150
1038
|
}
|
|
1151
|
-
const response = {
|
|
1152
|
-
events: [],
|
|
1153
|
-
cursor: afterSeq,
|
|
1154
|
-
capabilities: CLOUD_PROTOCOL_CAPABILITIES
|
|
1155
|
-
};
|
|
1156
|
-
return c.json(response, 200);
|
|
1157
1039
|
};
|
|
1158
1040
|
}
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
var
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
}).strict();
|
|
1169
|
-
var LocalApprovalResolutionEventSchema = z.object({
|
|
1170
|
-
type: z.literal("approval_resolved"),
|
|
1171
|
-
// Absent only when a daemon resolved the single outstanding request without
|
|
1172
|
-
// native approval identity. No id is synthesized.
|
|
1173
|
-
approvalId: NonBlankIdSchema.optional(),
|
|
1174
|
-
decision: z.enum(["approve", "reject"]),
|
|
1175
|
-
// A daemon's `task.approval_resolved` is pure wire observation; it cannot
|
|
1176
|
-
// declare a host command payload.
|
|
1177
|
-
resolvedBy: z.literal("local"),
|
|
1178
|
-
at: z.iso.datetime({ offset: true })
|
|
1179
|
-
}).strict();
|
|
1180
|
-
var HostApprovalResolutionEventSchema = z.object({
|
|
1181
|
-
type: z.literal("approval_resolved"),
|
|
1182
|
-
approvalId: NonBlankIdSchema.optional(),
|
|
1183
|
-
decision: z.literal("approve"),
|
|
1184
|
-
resolvedBy: z.literal("host"),
|
|
1185
|
-
at: z.iso.datetime({ offset: true })
|
|
1186
|
-
}).strict();
|
|
1187
|
-
var HostRejectResolutionEventSchema = z.object({
|
|
1188
|
-
type: z.literal("approval_resolved"),
|
|
1189
|
-
approvalId: NonBlankIdSchema.optional(),
|
|
1190
|
-
decision: z.literal("reject"),
|
|
1191
|
-
resolvedBy: z.literal("host"),
|
|
1192
|
-
// Required even when the host deliberately omitted a wire reason. `null`
|
|
1193
|
-
// records that complete payload, so a historical host reject with no field
|
|
1194
|
-
// cannot be mistaken for a recoverable executable command.
|
|
1195
|
-
reason: z.string().nullable(),
|
|
1196
|
-
at: z.iso.datetime({ offset: true })
|
|
1197
|
-
}).strict();
|
|
1198
|
-
var ApprovalTimelineEventSchema = z.union([
|
|
1199
|
-
ApprovalRequestedEventSchema,
|
|
1200
|
-
LocalApprovalResolutionEventSchema,
|
|
1201
|
-
HostApprovalResolutionEventSchema,
|
|
1202
|
-
HostRejectResolutionEventSchema
|
|
1203
|
-
]);
|
|
1204
|
-
var ApprovalObservationSchema = z.object({
|
|
1205
|
-
taskId: TaskIdSchema,
|
|
1206
|
-
sourceEnvelopeId: NonBlankIdSchema,
|
|
1207
|
-
revision: z.number().int().positive(),
|
|
1208
|
-
receivedAt: z.iso.datetime(),
|
|
1209
|
-
event: ApprovalTimelineEventSchema
|
|
1210
|
-
});
|
|
1211
|
-
function validateApprovalTimelineAppend(input) {
|
|
1212
|
-
const capacity = input.capacity ?? DEFAULT_APPROVAL_TIMELINE_CAPACITY;
|
|
1213
|
-
const ttlMs = input.ttlMs ?? DEFAULT_APPROVAL_TIMELINE_TTL_MS;
|
|
1214
|
-
if (!Number.isSafeInteger(capacity) || capacity <= 0) {
|
|
1215
|
-
throw new ByokCloudError(
|
|
1216
|
-
"coordination_input_invalid",
|
|
1217
|
-
`Approval timeline capacity must be a positive integer, received ${String(capacity)}.`
|
|
1218
|
-
);
|
|
1219
|
-
}
|
|
1220
|
-
if (!Number.isFinite(ttlMs) || ttlMs <= 0) {
|
|
1221
|
-
throw new ByokCloudError(
|
|
1222
|
-
"coordination_input_invalid",
|
|
1223
|
-
`Approval timeline ttl must be a positive number of milliseconds, received ${String(ttlMs)}.`
|
|
1224
|
-
);
|
|
1225
|
-
}
|
|
1226
|
-
const parsed = z.object({
|
|
1227
|
-
taskId: TaskIdSchema,
|
|
1228
|
-
sourceEnvelopeId: NonBlankIdSchema,
|
|
1229
|
-
event: ApprovalTimelineEventSchema
|
|
1230
|
-
}).safeParse(input);
|
|
1231
|
-
if (!parsed.success) {
|
|
1232
|
-
throw new ByokCloudError(
|
|
1233
|
-
"coordination_input_invalid",
|
|
1234
|
-
"Approval observations require stable source identity and a valid native lifecycle event."
|
|
1235
|
-
);
|
|
1041
|
+
|
|
1042
|
+
// src/auth/tokens.ts
|
|
1043
|
+
var ACCESS_TOKEN_TTL_SECONDS = 60 * 60;
|
|
1044
|
+
function encodeSegment(value) {
|
|
1045
|
+
return base64UrlEncode(new TextEncoder().encode(JSON.stringify(value)));
|
|
1046
|
+
}
|
|
1047
|
+
function createHmacTokenSigner(secret, clock) {
|
|
1048
|
+
async function signingKey(usage) {
|
|
1049
|
+
return globalThis.crypto.subtle.importKey("raw", secret, { name: "HMAC", hash: "SHA-256" }, false, [usage]);
|
|
1236
1050
|
}
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
);
|
|
1051
|
+
async function signature(signingInput) {
|
|
1052
|
+
const key = await signingKey("sign");
|
|
1053
|
+
const bytes = await globalThis.crypto.subtle.sign("HMAC", key, new TextEncoder().encode(signingInput));
|
|
1054
|
+
return base64UrlEncode(new Uint8Array(bytes));
|
|
1242
1055
|
}
|
|
1243
|
-
return {
|
|
1056
|
+
return {
|
|
1057
|
+
async sign(claims, expiresInSeconds) {
|
|
1058
|
+
const issuedAt = Math.floor(clock.now().getTime() / 1e3);
|
|
1059
|
+
const header = encodeSegment({ alg: "HS256", typ: "JWT" });
|
|
1060
|
+
const payload = encodeSegment({
|
|
1061
|
+
deviceId: claims.deviceId,
|
|
1062
|
+
tenantId: claims.tenantId,
|
|
1063
|
+
productId: claims.productId,
|
|
1064
|
+
iat: issuedAt,
|
|
1065
|
+
exp: issuedAt + expiresInSeconds
|
|
1066
|
+
});
|
|
1067
|
+
const signingInput = `${header}.${payload}`;
|
|
1068
|
+
return `${signingInput}.${await signature(signingInput)}`;
|
|
1069
|
+
},
|
|
1070
|
+
async verify(token) {
|
|
1071
|
+
const parts = token.split(".");
|
|
1072
|
+
if (parts.length !== 3) return void 0;
|
|
1073
|
+
const [header, payload, provided] = parts;
|
|
1074
|
+
const key = await signingKey("verify");
|
|
1075
|
+
const providedBytes = base64UrlDecode(provided);
|
|
1076
|
+
if (providedBytes === void 0) return void 0;
|
|
1077
|
+
const ok = await globalThis.crypto.subtle.verify(
|
|
1078
|
+
"HMAC",
|
|
1079
|
+
key,
|
|
1080
|
+
providedBytes,
|
|
1081
|
+
new TextEncoder().encode(`${header}.${payload}`)
|
|
1082
|
+
);
|
|
1083
|
+
if (!ok) return void 0;
|
|
1084
|
+
const headerBytes = base64UrlDecode(header);
|
|
1085
|
+
const payloadBytes = base64UrlDecode(payload);
|
|
1086
|
+
if (headerBytes === void 0 || payloadBytes === void 0) return void 0;
|
|
1087
|
+
let decodedHeader;
|
|
1088
|
+
let decodedPayload;
|
|
1089
|
+
try {
|
|
1090
|
+
decodedHeader = JSON.parse(new TextDecoder().decode(headerBytes));
|
|
1091
|
+
decodedPayload = JSON.parse(new TextDecoder().decode(payloadBytes));
|
|
1092
|
+
} catch {
|
|
1093
|
+
return void 0;
|
|
1094
|
+
}
|
|
1095
|
+
const alg = decodedHeader?.alg;
|
|
1096
|
+
if (alg !== "HS256") return void 0;
|
|
1097
|
+
const { deviceId, tenantId: tenantId4, productId, exp } = decodedPayload;
|
|
1098
|
+
if (typeof deviceId !== "string" || typeof tenantId4 !== "string" || typeof productId !== "string") {
|
|
1099
|
+
return void 0;
|
|
1100
|
+
}
|
|
1101
|
+
if (typeof exp !== "number" || clock.now().getTime() / 1e3 >= exp) return void 0;
|
|
1102
|
+
return { deviceId, tenantId: tenantId4, productId };
|
|
1103
|
+
}
|
|
1104
|
+
};
|
|
1244
1105
|
}
|
|
1245
|
-
function
|
|
1246
|
-
|
|
1247
|
-
const expected = z.object({
|
|
1248
|
-
expectedSourceEnvelopeId: NonBlankIdSchema,
|
|
1249
|
-
expectedRevision: z.number().int().positive()
|
|
1250
|
-
}).safeParse(input);
|
|
1251
|
-
if (!expected.success || validated.event.type !== "approval_resolved" || validated.event.resolvedBy !== "host") {
|
|
1252
|
-
throw new ByokCloudError(
|
|
1253
|
-
"coordination_input_invalid",
|
|
1254
|
-
"Host approval resolution requires an exact pending source/revision and a host resolution event."
|
|
1255
|
-
);
|
|
1256
|
-
}
|
|
1257
|
-
return validated;
|
|
1106
|
+
function verifyNonceSignature(crypto2, devicePublicKey, nonce, signature) {
|
|
1107
|
+
return crypto2.verifyEd25519(devicePublicKey, NONCE_SIGNING_DOMAIN + nonce, signature);
|
|
1258
1108
|
}
|
|
1259
|
-
|
|
1260
|
-
|
|
1109
|
+
|
|
1110
|
+
// src/auth/plane.ts
|
|
1111
|
+
var PAIRING_CODE_TTL_MS = 10 * 60 * 1e3;
|
|
1112
|
+
var PAIRING_CODE_LENGTH = 8;
|
|
1113
|
+
var DEVICE_IDENTITY_PROOF_KEY_ID = "identity";
|
|
1114
|
+
var DEVICE_IDENTITY_PROOF_KEY_EPOCH = 0;
|
|
1115
|
+
function createAuthPlane(deps) {
|
|
1116
|
+
const { stores, crypto: crypto2, clock, tokenSigner } = deps;
|
|
1117
|
+
const ttlSeconds = deps.accessTokenTtlSeconds ?? ACCESS_TOKEN_TTL_SECONDS;
|
|
1118
|
+
return {
|
|
1119
|
+
async createPairingCode(tenant, input) {
|
|
1120
|
+
const expiresAt = new Date(clock.now().getTime() + (input.ttlMs ?? PAIRING_CODE_TTL_MS)).toISOString();
|
|
1121
|
+
return stores.pairingCodes.issue(tenant, {
|
|
1122
|
+
code: crypto2.randomPairingCode(PAIRING_CODE_LENGTH),
|
|
1123
|
+
productId: input.productId,
|
|
1124
|
+
expiresAt
|
|
1125
|
+
});
|
|
1126
|
+
},
|
|
1127
|
+
async redeemAndRegister(input) {
|
|
1128
|
+
return stores.pairing.redeemAndRegister({
|
|
1129
|
+
pairingCode: input.pairingCode,
|
|
1130
|
+
deviceId: `dev_${crypto2.randomUuid()}`,
|
|
1131
|
+
deviceName: input.deviceName,
|
|
1132
|
+
devicePublicKey: input.devicePublicKey,
|
|
1133
|
+
proofKeyId: DEVICE_IDENTITY_PROOF_KEY_ID,
|
|
1134
|
+
proofKeyEpoch: DEVICE_IDENTITY_PROOF_KEY_EPOCH,
|
|
1135
|
+
...input.machineId === void 0 ? {} : { machineId: input.machineId }
|
|
1136
|
+
});
|
|
1137
|
+
},
|
|
1138
|
+
async resolveDevice(deviceId) {
|
|
1139
|
+
const device = await stores.devices.resolveByDeviceId(deviceId);
|
|
1140
|
+
if (device === void 0 || device.revoked) return void 0;
|
|
1141
|
+
return device;
|
|
1142
|
+
},
|
|
1143
|
+
issueNonce(device) {
|
|
1144
|
+
return stores.nonces.issue(device.tenantId, device.deviceId);
|
|
1145
|
+
},
|
|
1146
|
+
consumeNonceIfValid(device, nonce) {
|
|
1147
|
+
return stores.nonces.consumeIfValid(device.tenantId, device.deviceId, nonce);
|
|
1148
|
+
},
|
|
1149
|
+
verifySignature(device, nonce, signature) {
|
|
1150
|
+
return verifyNonceSignature(crypto2, device.devicePublicKey, nonce, signature);
|
|
1151
|
+
},
|
|
1152
|
+
async mintAccessToken(device) {
|
|
1153
|
+
const accessToken = await tokenSigner.sign(
|
|
1154
|
+
{ deviceId: device.deviceId, tenantId: device.tenantId, productId: device.productId },
|
|
1155
|
+
ttlSeconds
|
|
1156
|
+
);
|
|
1157
|
+
return {
|
|
1158
|
+
accessToken,
|
|
1159
|
+
expiresAt: new Date(clock.now().getTime() + ttlSeconds * 1e3).toISOString()
|
|
1160
|
+
};
|
|
1161
|
+
}
|
|
1162
|
+
};
|
|
1261
1163
|
}
|
|
1262
|
-
|
|
1263
|
-
|
|
1164
|
+
var CLOUD_CAPABILITIES = {
|
|
1165
|
+
/** `GET /byok/events` long-poll receive (§8). */
|
|
1166
|
+
eventsLongPoll: "events.longpoll",
|
|
1167
|
+
/** `POST /byok/messages` batched send (§8.2). */
|
|
1168
|
+
messagesBatch: "messages.batch",
|
|
1169
|
+
/** The three bearer-authed blob routes: reserve/grant, explicit finalize, committed-only download (§7). */
|
|
1170
|
+
blobsPresigned: "blobs.presigned",
|
|
1171
|
+
/**
|
|
1172
|
+
* The two presigned `/byok/blobs/:id/content` routes — cloud carrying the
|
|
1173
|
+
* bytes itself.
|
|
1174
|
+
*
|
|
1175
|
+
* Split out of `blobs.presigned` because it was one capability describing two
|
|
1176
|
+
* separable facts. A composition whose bytes live in object storage mints
|
|
1177
|
+
* grants (`blobs.presigned`) but has no byte-proxy path at all, and saying so
|
|
1178
|
+
* by declaration is ADR-010's whole posture: a client reads what a deployment
|
|
1179
|
+
* serves, it never probes a `/content` route and interprets the status code.
|
|
1180
|
+
*
|
|
1181
|
+
* Spelled all-lowercase because core's declaration schema pins capability
|
|
1182
|
+
* names to `/^[a-z0-9]+(?:[._-][a-z0-9]+)*$/` — the same reason the sibling
|
|
1183
|
+
* above reads `events.longpoll` and not `events.longPoll`.
|
|
1184
|
+
*/
|
|
1185
|
+
blobsContentProxy: "blobs.contentproxy",
|
|
1186
|
+
/** Board list/claim/unclaim/status routes. Polling is first-class under this declaration. */
|
|
1187
|
+
boardCoordination: "board.coordination",
|
|
1188
|
+
/** Additional SSE transport for the same board read model. */
|
|
1189
|
+
boardSse: "board.sse",
|
|
1190
|
+
/** Device-scoped five-level presence publication. */
|
|
1191
|
+
presenceHints: "presence.hints",
|
|
1192
|
+
/** Bounded task activity batch publication. */
|
|
1193
|
+
activityTail: "activity.tail",
|
|
1194
|
+
/** Request-bound device proof record manifest/read/write surface (S6). */
|
|
1195
|
+
truthRecords: "truth.records",
|
|
1196
|
+
/**
|
|
1197
|
+
* Tenant-scoped skill pack distribution: the manifest list plus the per-file
|
|
1198
|
+
* content route a paired device installs from.
|
|
1199
|
+
*
|
|
1200
|
+
* Hosted HTTP on purpose. A pack is DECLARATIVE CONTENT a device pulls after
|
|
1201
|
+
* reading this declaration — not a message — so `@byok-sdk/protocol` gains
|
|
1202
|
+
* nothing from it and the frozen v1 envelope stays untouched. Composition-
|
|
1203
|
+
* bound like `truth.records`: a deployment that declares it without supplying
|
|
1204
|
+
* a `SkillPackStore` is refused at construction rather than publishing two
|
|
1205
|
+
* routes it would then 404.
|
|
1206
|
+
*/
|
|
1207
|
+
skillPacks: "skills.pack",
|
|
1208
|
+
/** Optional, one-way redacted Agent-memory snapshot mutation route. */
|
|
1209
|
+
agentMemoryProjection: "agent.memory.projection",
|
|
1210
|
+
/**
|
|
1211
|
+
* Remote authenticated runtime input preparation (C07 G4): the host control
|
|
1212
|
+
* plane enqueues `agent.input.preparation` and the device discharges it
|
|
1213
|
+
* through the completion/status pair.
|
|
1214
|
+
*
|
|
1215
|
+
* Informational like `host-mcp-task-context`, not route-gating. Both routes
|
|
1216
|
+
* mount unconditionally (`cloud.ts`) because the completion route is the
|
|
1217
|
+
* DEVICE's only way to discharge a mailbox row this same deployment handed
|
|
1218
|
+
* it — withholding it would strand the device's redelivery cursor rather
|
|
1219
|
+
* than degrade a feature. For the same reason the completion route asserts
|
|
1220
|
+
* no DEVICE capability either: the device-level `agent-input-preparation`
|
|
1221
|
+
* flag gates ADMISSION (`enqueueInputPreparation`) and nothing else, so an
|
|
1222
|
+
* unconfigured device is refused a row instead of being handed one whose
|
|
1223
|
+
* only honest answer — `input_preparation_unconfigured` — it could not then
|
|
1224
|
+
* record. What declaring THIS capability promises is that this deployment's
|
|
1225
|
+
* host side actually drives the lane, which is what a Host reads before
|
|
1226
|
+
* calling `enqueueInputPreparation` instead of probing.
|
|
1227
|
+
*/
|
|
1228
|
+
inputPreparation: "agent.input.preparation",
|
|
1229
|
+
/**
|
|
1230
|
+
* Contract §8.1's capability gate for task-scoped tool authority
|
|
1231
|
+
* (`byok-task-assertion-v1`), deployment-level channel.
|
|
1232
|
+
*
|
|
1233
|
+
* The SAME literal name `@byok-sdk/protocol`'s
|
|
1234
|
+
* `HOST_MCP_TASK_CONTEXT_CAPABILITY` carries on the device-level channel —
|
|
1235
|
+
* §8.1 requires both to be in place, and a name spelled differently in the
|
|
1236
|
+
* two would make "both" unverifiable.
|
|
1237
|
+
*
|
|
1238
|
+
* Unlike its siblings above this capability mounts no route in this package:
|
|
1239
|
+
* what a deployment promises by declaring it is that its host side actually
|
|
1240
|
+
* VERIFIES task assertions (`authenticateHostedTaskAssertion` plus the frozen
|
|
1241
|
+
* offer check that only the host can make). That is why it is default-off
|
|
1242
|
+
* below and not derivable from anything this package can inspect — §8.2(1):
|
|
1243
|
+
* the capability is declared only once it is completely implemented.
|
|
1244
|
+
*/
|
|
1245
|
+
hostMcpTaskContext: "host-mcp-task-context"
|
|
1246
|
+
};
|
|
1247
|
+
var CapabilitiesResponseSchema = CapabilityDeclarationSchema;
|
|
1248
|
+
function fullCapabilityDeclaration(version = 1, options = {}) {
|
|
1249
|
+
return {
|
|
1250
|
+
schema: "byok-capabilities-v1",
|
|
1251
|
+
version,
|
|
1252
|
+
capabilities: Object.values(CLOUD_CAPABILITIES).filter((capability) => {
|
|
1253
|
+
if (capability === CLOUD_CAPABILITIES.truthRecords) return options.includeTruthRecords === true;
|
|
1254
|
+
if (capability === CLOUD_CAPABILITIES.skillPacks) return options.includeSkillPacks === true;
|
|
1255
|
+
if (capability === CLOUD_CAPABILITIES.agentMemoryProjection) {
|
|
1256
|
+
return options.includeAgentMemoryProjection === true;
|
|
1257
|
+
}
|
|
1258
|
+
if (capability === CLOUD_CAPABILITIES.hostMcpTaskContext) {
|
|
1259
|
+
return options.includeHostMcpTaskContext === true;
|
|
1260
|
+
}
|
|
1261
|
+
return true;
|
|
1262
|
+
})
|
|
1263
|
+
};
|
|
1264
1264
|
}
|
|
1265
|
-
function
|
|
1266
|
-
return
|
|
1265
|
+
function declares(declaration, capability) {
|
|
1266
|
+
return hasCapability(declaration, capability);
|
|
1267
|
+
}
|
|
1268
|
+
function extractBearerToken(header) {
|
|
1269
|
+
if (header === void 0) return void 0;
|
|
1270
|
+
const match = /^Bearer\s+(.+)$/i.exec(header);
|
|
1271
|
+
return match?.[1];
|
|
1272
|
+
}
|
|
1273
|
+
async function authenticateBearer(header, deps) {
|
|
1274
|
+
const token = extractBearerToken(header);
|
|
1275
|
+
if (token === void 0) return void 0;
|
|
1276
|
+
const claims = await deps.tokenSigner.verify(token);
|
|
1277
|
+
if (claims === void 0) return void 0;
|
|
1278
|
+
if (!isTenantId(claims.tenantId)) return void 0;
|
|
1279
|
+
const device = await deps.devices.get(tenantId(claims.tenantId), claims.deviceId);
|
|
1280
|
+
if (device === void 0 || device.revoked) return void 0;
|
|
1281
|
+
if (device.productId !== claims.productId) return void 0;
|
|
1282
|
+
if (deps.instanceProductId !== void 0 && claims.productId !== deps.instanceProductId) {
|
|
1283
|
+
return void 0;
|
|
1284
|
+
}
|
|
1285
|
+
return {
|
|
1286
|
+
kind: "device",
|
|
1287
|
+
tenantId: device.tenantId,
|
|
1288
|
+
productId: device.productId,
|
|
1289
|
+
deviceId: device.deviceId
|
|
1290
|
+
};
|
|
1291
|
+
}
|
|
1292
|
+
function tenantStoresFor(principal, root) {
|
|
1293
|
+
const tenant = principalTenant(principal);
|
|
1294
|
+
const { core, cloud } = root;
|
|
1295
|
+
return {
|
|
1296
|
+
tenant,
|
|
1297
|
+
principal,
|
|
1298
|
+
mailbox: {
|
|
1299
|
+
append: (input) => core.mailbox.append(tenant, input),
|
|
1300
|
+
readAfter: (query) => core.mailbox.readAfter(tenant, query),
|
|
1301
|
+
recordDelivery: (input) => core.mailbox.recordDelivery(tenant, input),
|
|
1302
|
+
advanceCursor: (input) => core.mailbox.advanceCursor(tenant, input),
|
|
1303
|
+
readCursor: (deviceId) => core.mailbox.readCursor(tenant, deviceId)
|
|
1304
|
+
},
|
|
1305
|
+
board: {
|
|
1306
|
+
create: (input) => core.board.create(tenant, input),
|
|
1307
|
+
get: (itemId) => core.board.get(tenant, itemId),
|
|
1308
|
+
list: (query) => core.board.list(tenant, query),
|
|
1309
|
+
claim: (input) => core.board.claim(tenant, input),
|
|
1310
|
+
unclaim: (input) => core.board.unclaim(tenant, input),
|
|
1311
|
+
updateStatus: (input) => core.board.updateStatus(tenant, input)
|
|
1312
|
+
},
|
|
1313
|
+
presence: {
|
|
1314
|
+
publish: (input) => core.presence.publish(tenant, input),
|
|
1315
|
+
read: (deviceId) => core.presence.read(tenant, deviceId),
|
|
1316
|
+
list: () => core.presence.list(tenant)
|
|
1317
|
+
},
|
|
1318
|
+
activity: {
|
|
1319
|
+
append: (input) => cloud.activity.append(tenant, input),
|
|
1320
|
+
read: (taskId) => cloud.activity.read(tenant, taskId)
|
|
1321
|
+
},
|
|
1322
|
+
approvals: {
|
|
1323
|
+
append: (input) => cloud.approvals.append(tenant, input),
|
|
1324
|
+
read: (taskId) => cloud.approvals.read(tenant, taskId)
|
|
1325
|
+
},
|
|
1326
|
+
devices: {
|
|
1327
|
+
get: (deviceId) => cloud.devices.get(tenant, deviceId),
|
|
1328
|
+
list: () => cloud.devices.list(tenant),
|
|
1329
|
+
revoke: (deviceId) => cloud.devices.revoke(tenant, deviceId),
|
|
1330
|
+
recordCapabilities: (input) => principal.kind === "device" ? cloud.devices.recordCapabilities(tenant, {
|
|
1331
|
+
deviceId: principal.deviceId,
|
|
1332
|
+
capabilities: input.capabilities,
|
|
1333
|
+
...input.harnesses === void 0 ? {} : { harnesses: input.harnesses }
|
|
1334
|
+
}) : Promise.resolve(void 0),
|
|
1335
|
+
readiness: () => cloud.devices.readiness(tenant, core.presence)
|
|
1336
|
+
},
|
|
1337
|
+
tasks: {
|
|
1338
|
+
open: (input) => cloud.tasks.open(tenant, input),
|
|
1339
|
+
reserveAgentOffer: (input) => cloud.tasks.reserveAgentOffer(tenant, input),
|
|
1340
|
+
reserveAgentMessage: (input) => cloud.tasks.reserveAgentMessage(tenant, input),
|
|
1341
|
+
readTaskAgentMessage: (input) => cloud.tasks.readTaskAgentMessage(tenant, input),
|
|
1342
|
+
readAgentMessage: (input) => cloud.tasks.readAgentMessage(tenant, input),
|
|
1343
|
+
finalizeAgentMessage: (input) => cloud.tasks.finalizeAgentMessage(tenant, input),
|
|
1344
|
+
get: (taskId) => cloud.tasks.get(tenant, taskId),
|
|
1345
|
+
getMany: (taskIds) => cloud.tasks.getMany(tenant, taskIds),
|
|
1346
|
+
list: (query) => cloud.tasks.list(tenant, query),
|
|
1347
|
+
claim: (input) => cloud.tasks.claim(tenant, input),
|
|
1348
|
+
recordStatus: (input) => cloud.tasks.recordStatus(tenant, input)
|
|
1349
|
+
},
|
|
1350
|
+
cancellations: {
|
|
1351
|
+
request: (input) => cloud.cancellations.request(tenant, input)
|
|
1352
|
+
},
|
|
1353
|
+
dedup: {
|
|
1354
|
+
checkAndRecord: (deviceId, envelopeId) => cloud.dedup.checkAndRecord(tenant, deviceId, envelopeId),
|
|
1355
|
+
checkAndRecordAgent: (deviceId, agentRef, envelopeId) => cloud.dedup.checkAndRecordAgent(tenant, deviceId, agentRef, envelopeId)
|
|
1356
|
+
},
|
|
1357
|
+
receipts: {
|
|
1358
|
+
record: (input) => cloud.receipts.record(tenant, input),
|
|
1359
|
+
get: (key) => cloud.receipts.get(tenant, key)
|
|
1360
|
+
},
|
|
1361
|
+
egress: {
|
|
1362
|
+
record: (input) => cloud.egress.record(tenant, input),
|
|
1363
|
+
get: (deviceId, agentRef, eventId) => cloud.egress.get(tenant, deviceId, agentRef, eventId)
|
|
1364
|
+
},
|
|
1365
|
+
blobs: {
|
|
1366
|
+
createUpload: (reservation) => cloud.blobs.createUpload(tenant, reservation),
|
|
1367
|
+
observeUpload: (blobId, reservation) => cloud.blobs.observeUpload(tenant, blobId, reservation),
|
|
1368
|
+
getDownloadUrl: (blobId) => cloud.blobs.getDownloadUrl(tenant, blobId)
|
|
1369
|
+
},
|
|
1370
|
+
quota: {
|
|
1371
|
+
readReservation: (reservationId) => core.quota.readReservation(tenant, reservationId),
|
|
1372
|
+
reserve: (input) => core.quota.reserve(tenant, input),
|
|
1373
|
+
finalizeReservation: (input) => core.quota.finalizeReservation(tenant, input),
|
|
1374
|
+
abortReservation: (reservationId) => core.quota.abortReservation(tenant, reservationId)
|
|
1375
|
+
},
|
|
1376
|
+
rateLimiter: {
|
|
1377
|
+
consume: (deviceId) => cloud.rateLimiter.consume(tenant, deviceId)
|
|
1378
|
+
}
|
|
1379
|
+
};
|
|
1267
1380
|
}
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1381
|
+
|
|
1382
|
+
// src/handlers/shared.ts
|
|
1383
|
+
async function readJsonBody(c) {
|
|
1271
1384
|
try {
|
|
1272
|
-
await
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
status: "in_review"
|
|
1276
|
-
});
|
|
1277
|
-
} catch (caught) {
|
|
1278
|
-
if (isCoreConflictError(caught)) return;
|
|
1279
|
-
throw caught;
|
|
1385
|
+
return await c.req.json();
|
|
1386
|
+
} catch {
|
|
1387
|
+
return void 0;
|
|
1280
1388
|
}
|
|
1281
1389
|
}
|
|
1282
|
-
function
|
|
1283
|
-
const
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
const
|
|
1292
|
-
if (
|
|
1293
|
-
|
|
1390
|
+
async function readBoundedJsonBody(c, maximum) {
|
|
1391
|
+
const declaredLength = c.req.header("content-length");
|
|
1392
|
+
if (declaredLength !== void 0) {
|
|
1393
|
+
const parsedLength = parseContentLength(declaredLength);
|
|
1394
|
+
if (parsedLength !== void 0 && parsedLength > BigInt(maximum)) {
|
|
1395
|
+
cancelBody(c.req.raw.body);
|
|
1396
|
+
return { body: void 0, tooLarge: true };
|
|
1397
|
+
}
|
|
1398
|
+
}
|
|
1399
|
+
const stream = c.req.raw.body;
|
|
1400
|
+
if (stream === null) return { body: void 0, tooLarge: false };
|
|
1401
|
+
const reader = stream.getReader();
|
|
1402
|
+
const chunks = [];
|
|
1403
|
+
let total = 0;
|
|
1294
1404
|
try {
|
|
1295
|
-
|
|
1405
|
+
while (true) {
|
|
1406
|
+
const { done, value } = await reader.read();
|
|
1407
|
+
if (done) break;
|
|
1408
|
+
if (value.byteLength > maximum - total) {
|
|
1409
|
+
cancelBody(reader);
|
|
1410
|
+
return { body: void 0, tooLarge: true };
|
|
1411
|
+
}
|
|
1412
|
+
if (value.byteLength === 0) continue;
|
|
1413
|
+
chunks.push(value);
|
|
1414
|
+
total += value.byteLength;
|
|
1415
|
+
}
|
|
1296
1416
|
} catch {
|
|
1297
|
-
return {
|
|
1417
|
+
return { body: void 0, tooLarge: false };
|
|
1418
|
+
} finally {
|
|
1419
|
+
reader.releaseLock();
|
|
1298
1420
|
}
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
taskId,
|
|
1305
|
-
deviceId,
|
|
1306
|
-
messageId: payload.messageId,
|
|
1307
|
-
payloadBody
|
|
1308
|
-
});
|
|
1309
|
-
if (reservation !== "reserved" && reservation !== "pending") return { outcome: "rejected" };
|
|
1310
|
-
let decision;
|
|
1311
|
-
if (consume === void 0) {
|
|
1312
|
-
if (reservation === "pending") {
|
|
1313
|
-
throw new Error("Agent message consumer is unavailable while reconciling an exact pending admission.");
|
|
1314
|
-
}
|
|
1315
|
-
decision = { outcome: "held", reasonCode: "consumer_unavailable" };
|
|
1316
|
-
} else {
|
|
1317
|
-
decision = await consume({ tenant: stores.tenant, deviceId, taskId, context: frozen.context, payload });
|
|
1421
|
+
const bytes = new Uint8Array(total);
|
|
1422
|
+
let offset = 0;
|
|
1423
|
+
for (const chunk of chunks) {
|
|
1424
|
+
bytes.set(chunk, offset);
|
|
1425
|
+
offset += chunk.byteLength;
|
|
1318
1426
|
}
|
|
1319
|
-
|
|
1320
|
-
const admission = await stores.tasks.finalizeAgentMessage({
|
|
1321
|
-
taskId,
|
|
1322
|
-
deviceId,
|
|
1323
|
-
messageId: payload.messageId,
|
|
1324
|
-
payloadBody,
|
|
1325
|
-
terminalBody: JSON.stringify({ payload, disposition })
|
|
1326
|
-
});
|
|
1327
|
-
const terminal = admission?.terminalBody === void 0 ? void 0 : parseAgentMessageDisposition(payload, admission.terminalBody);
|
|
1328
|
-
return terminal === void 0 ? { outcome: "rejected" } : { outcome: "accepted", disposition: terminal };
|
|
1329
|
-
}
|
|
1330
|
-
function agentMessageDisposition(payload, decision) {
|
|
1331
|
-
return {
|
|
1332
|
-
agentRef: payload.agentRef,
|
|
1333
|
-
sessionRef: payload.sessionRef,
|
|
1334
|
-
contract: payload.contract,
|
|
1335
|
-
messageId: payload.messageId,
|
|
1336
|
-
cursor: payload.cursor,
|
|
1337
|
-
contentHash: payload.contentHash,
|
|
1338
|
-
outcome: decision.outcome,
|
|
1339
|
-
receiptId: crypto.randomUUID(),
|
|
1340
|
-
...decision.reasonCode === void 0 ? {} : { reasonCode: decision.reasonCode }
|
|
1341
|
-
};
|
|
1342
|
-
}
|
|
1343
|
-
async function readAgentMessageDisposition(stores, deviceId, taskId, payload) {
|
|
1344
|
-
const admission = await stores.tasks.readAgentMessage({
|
|
1345
|
-
taskId,
|
|
1346
|
-
deviceId,
|
|
1347
|
-
messageId: payload.messageId,
|
|
1348
|
-
payloadBody: JSON.stringify(payload)
|
|
1349
|
-
});
|
|
1350
|
-
return admission?.terminalBody === void 0 ? void 0 : parseAgentMessageDisposition(payload, admission.terminalBody);
|
|
1427
|
+
return { body: parseJsonBytes(bytes), tooLarge: false };
|
|
1351
1428
|
}
|
|
1352
|
-
function
|
|
1429
|
+
function cancelBody(body) {
|
|
1430
|
+
if (body === null) return;
|
|
1353
1431
|
try {
|
|
1354
|
-
|
|
1355
|
-
|
|
1432
|
+
void Promise.resolve(body.cancel()).catch(() => {
|
|
1433
|
+
});
|
|
1356
1434
|
} catch {
|
|
1357
|
-
return void 0;
|
|
1358
1435
|
}
|
|
1359
1436
|
}
|
|
1360
|
-
function
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
}
|
|
1366
|
-
|
|
1367
|
-
return sameAgentRef(expected.agentRef, actual.agentRef) && expected.sessionRef === actual.sessionRef && expected.policyRevision === actual.policyRevision && expected.eventId === actual.eventId && expected.cursor === actual.cursor && expected.contentHash === actual.contentHash && expected.byteCount === actual.byteCount && JSON.stringify(expected.payload) === JSON.stringify(actual.payload);
|
|
1368
|
-
}
|
|
1369
|
-
function contentReadCapability(surface) {
|
|
1370
|
-
switch (surface) {
|
|
1371
|
-
case "workspace":
|
|
1372
|
-
return AGENT_CONTENT_WORKSPACE_READ_CAPABILITY;
|
|
1373
|
-
case "transcript":
|
|
1374
|
-
return AGENT_CONTENT_TRANSCRIPT_READ_CAPABILITY;
|
|
1375
|
-
case "artifact":
|
|
1376
|
-
return AGENT_CONTENT_ARTIFACT_READ_CAPABILITY;
|
|
1437
|
+
function parseContentLength(value) {
|
|
1438
|
+
const normalized = value.trim();
|
|
1439
|
+
if (!/^\d+$/u.test(normalized)) return void 0;
|
|
1440
|
+
try {
|
|
1441
|
+
return BigInt(normalized);
|
|
1442
|
+
} catch {
|
|
1443
|
+
return void 0;
|
|
1377
1444
|
}
|
|
1378
1445
|
}
|
|
1379
|
-
function
|
|
1380
|
-
return capabilities !== void 0 && required.every((capability) => capabilities.includes(capability));
|
|
1381
|
-
}
|
|
1382
|
-
function contentRequestReceiptKey(deviceId, agentRef, requestId) {
|
|
1383
|
-
return agentReliabilityKey("agent-content-request", deviceId, agentRef, requestId);
|
|
1384
|
-
}
|
|
1385
|
-
function contentReceiptKey(deviceId, agentRef, requestId) {
|
|
1386
|
-
return agentReliabilityKey("agent-content-receipt", deviceId, agentRef, requestId);
|
|
1387
|
-
}
|
|
1388
|
-
function matchesContentReadReceipt(request, receipt) {
|
|
1389
|
-
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;
|
|
1390
|
-
}
|
|
1391
|
-
async function readContentRequest(stores, deviceId, agentRef, requestId) {
|
|
1392
|
-
const stored = await stores.receipts.get(contentRequestReceiptKey(deviceId, agentRef, requestId));
|
|
1393
|
-
if (stored === void 0) return void 0;
|
|
1446
|
+
function parseJsonBytes(bytes) {
|
|
1394
1447
|
try {
|
|
1395
|
-
return
|
|
1448
|
+
return JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
|
|
1396
1449
|
} catch {
|
|
1397
1450
|
return void 0;
|
|
1398
1451
|
}
|
|
1399
1452
|
}
|
|
1400
|
-
function
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
case "task.complete":
|
|
1405
|
-
case "task.fail":
|
|
1406
|
-
case "task.cancelled":
|
|
1407
|
-
return envelope.payload.agentRef;
|
|
1408
|
-
default:
|
|
1409
|
-
return void 0;
|
|
1410
|
-
}
|
|
1453
|
+
async function authenticateDevice(c, deps) {
|
|
1454
|
+
const device = await authenticateBearer(c.req.header("authorization"), deps.bearer);
|
|
1455
|
+
if (device === void 0) return void 0;
|
|
1456
|
+
return { device, stores: tenantStoresFor(device, deps.root) };
|
|
1411
1457
|
}
|
|
1412
|
-
|
|
1413
|
-
|
|
1458
|
+
|
|
1459
|
+
// src/handlers/blobs.ts
|
|
1460
|
+
var BLOB_RESERVATION_TTL_MS = 15 * 60 * 1e3;
|
|
1461
|
+
var SignedUrlQuerySchema = z.object({
|
|
1462
|
+
sig: z.string().min(1),
|
|
1463
|
+
exp: z.coerce.number().finite()
|
|
1464
|
+
});
|
|
1465
|
+
function createBlobHandler(deps) {
|
|
1466
|
+
return async (c) => {
|
|
1467
|
+
const authenticated = await authenticateDevice(c, deps);
|
|
1468
|
+
if (authenticated === void 0) return c.json({ error: "unauthorized" }, 401);
|
|
1469
|
+
const parsed = CreateBlobRequestSchema.safeParse(await readJsonBody(c));
|
|
1470
|
+
if (!parsed.success) return c.json({ error: "size, contentType, and contentHash are required" }, 400);
|
|
1471
|
+
if (parsed.data.size > deps.maxBlobSizeBytes) {
|
|
1472
|
+
return c.json({ error: `blob exceeds max size of ${deps.maxBlobSizeBytes} bytes` }, 413);
|
|
1473
|
+
}
|
|
1474
|
+
const reservationId = idempotencyKey(c);
|
|
1475
|
+
if (reservationId === void 0) {
|
|
1476
|
+
return c.json({ error: "Idempotency-Key header is required" }, 400);
|
|
1477
|
+
}
|
|
1478
|
+
try {
|
|
1479
|
+
const reservation = await authenticated.stores.quota.reserve({
|
|
1480
|
+
reservationId,
|
|
1481
|
+
kind: "object",
|
|
1482
|
+
expectedBytes: BigInt(parsed.data.size),
|
|
1483
|
+
contentHash: contentHash(parsed.data.contentHash),
|
|
1484
|
+
contentType: parsed.data.contentType,
|
|
1485
|
+
ttlMs: BLOB_RESERVATION_TTL_MS
|
|
1486
|
+
});
|
|
1487
|
+
try {
|
|
1488
|
+
const { blobId, uploadUrl } = await authenticated.stores.blobs.createUpload(reservation);
|
|
1489
|
+
const response = { blobId, uploadUrl };
|
|
1490
|
+
return c.json(response, 200);
|
|
1491
|
+
} catch (error) {
|
|
1492
|
+
await authenticated.stores.quota.abortReservation(reservationId).catch(() => void 0);
|
|
1493
|
+
const rendered = renderStorageError(c, error);
|
|
1494
|
+
if (rendered !== void 0) return rendered;
|
|
1495
|
+
throw error;
|
|
1496
|
+
}
|
|
1497
|
+
} catch (error) {
|
|
1498
|
+
const rendered = renderStorageError(c, error);
|
|
1499
|
+
if (rendered !== void 0) return rendered;
|
|
1500
|
+
throw error;
|
|
1501
|
+
}
|
|
1502
|
+
};
|
|
1414
1503
|
}
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
);
|
|
1424
|
-
if (outcome === "accepted" && observer !== void 0) {
|
|
1504
|
+
function finalizeBlobHandler(deps) {
|
|
1505
|
+
return async (c) => {
|
|
1506
|
+
const authenticated = await authenticateDevice(c, deps);
|
|
1507
|
+
if (authenticated === void 0) return c.json({ error: "unauthorized" }, 401);
|
|
1508
|
+
const reservationId = idempotencyKey(c);
|
|
1509
|
+
if (reservationId === void 0) {
|
|
1510
|
+
return c.json({ error: "Idempotency-Key header is required" }, 400);
|
|
1511
|
+
}
|
|
1425
1512
|
try {
|
|
1426
|
-
|
|
1427
|
-
|
|
1513
|
+
const reservation = await authenticated.stores.quota.readReservation(reservationId);
|
|
1514
|
+
if (reservation === void 0) {
|
|
1515
|
+
return c.json({ error: "storage_reservation_not_found" }, 404);
|
|
1516
|
+
}
|
|
1517
|
+
const blobId = c.req.param("id") ?? "";
|
|
1518
|
+
const observed = await authenticated.stores.blobs.observeUpload(
|
|
1519
|
+
blobId,
|
|
1520
|
+
reservation
|
|
1521
|
+
);
|
|
1522
|
+
if (observed === void 0) {
|
|
1523
|
+
await authenticated.stores.quota.abortReservation(reservationId);
|
|
1524
|
+
return c.json({ error: "storage_integrity_mismatch" }, 422);
|
|
1525
|
+
}
|
|
1526
|
+
await authenticated.stores.quota.finalizeReservation({
|
|
1527
|
+
reservationId,
|
|
1528
|
+
...observed
|
|
1529
|
+
});
|
|
1530
|
+
return c.body(null, 204);
|
|
1531
|
+
} catch (error) {
|
|
1532
|
+
const rendered = renderStorageError(c, error);
|
|
1533
|
+
if (rendered !== void 0) return rendered;
|
|
1534
|
+
throw error;
|
|
1428
1535
|
}
|
|
1429
|
-
}
|
|
1430
|
-
return outcome;
|
|
1536
|
+
};
|
|
1431
1537
|
}
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
if (
|
|
1436
|
-
|
|
1437
|
-
if (
|
|
1438
|
-
const
|
|
1439
|
-
|
|
1440
|
-
|
|
1538
|
+
function blobDownloadUrlHandler(deps) {
|
|
1539
|
+
return async (c) => {
|
|
1540
|
+
const authenticated = await authenticateDevice(c, deps);
|
|
1541
|
+
if (authenticated === void 0) return c.json({ error: "unauthorized" }, 401);
|
|
1542
|
+
const downloadUrl = await authenticated.stores.blobs.getDownloadUrl(c.req.param("id") ?? "");
|
|
1543
|
+
if (downloadUrl === void 0) return c.json({ error: "blob not found" }, 404);
|
|
1544
|
+
const response = { downloadUrl };
|
|
1545
|
+
return c.json(response, 200);
|
|
1546
|
+
};
|
|
1547
|
+
}
|
|
1548
|
+
function blobUploadContentHandler(deps) {
|
|
1549
|
+
return async (c) => {
|
|
1550
|
+
const blobId = c.req.param("id") ?? "";
|
|
1551
|
+
const query = SignedUrlQuerySchema.safeParse({ sig: c.req.query("sig"), exp: c.req.query("exp") });
|
|
1552
|
+
if (!query.success || !await deps.contentProxy.verifySignedUrl(blobId, "put", query.data.sig, query.data.exp)) {
|
|
1553
|
+
return c.json({ error: "invalid or expired signature" }, 401);
|
|
1441
1554
|
}
|
|
1442
|
-
|
|
1443
|
-
|
|
1555
|
+
const expectedBytes = await deps.contentProxy.expectedUploadBytes(blobId);
|
|
1556
|
+
if (expectedBytes === void 0) return c.json({ error: "blob not found" }, 404);
|
|
1557
|
+
const absoluteCeiling = BigInt(deps.maxUploadBytes);
|
|
1558
|
+
if (expectedBytes > absoluteCeiling) return c.json({ error: "upload exceeds size limit" }, 413);
|
|
1559
|
+
const contentLength = c.req.header("content-length");
|
|
1560
|
+
if (contentLength !== void 0 && /^\d+$/u.test(contentLength) && BigInt(contentLength) > expectedBytes) {
|
|
1561
|
+
return c.json({ error: "upload exceeds declared size" }, 413);
|
|
1444
1562
|
}
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1563
|
+
const body = await readBoundedBody(c.req.raw.body, Number(expectedBytes));
|
|
1564
|
+
if (body.tooLarge) return c.json({ error: "upload exceeds declared size" }, 413);
|
|
1565
|
+
const result = await deps.contentProxy.writeContent(blobId, body.data);
|
|
1566
|
+
if (!result.ok) return c.json({ error: result.reason }, 422);
|
|
1567
|
+
return c.body(null, 204);
|
|
1568
|
+
};
|
|
1569
|
+
}
|
|
1570
|
+
async function readBoundedBody(body, ceiling) {
|
|
1571
|
+
if (body === null) return { tooLarge: false, data: new Uint8Array() };
|
|
1572
|
+
const reader = body.getReader();
|
|
1573
|
+
const data = new Uint8Array(ceiling);
|
|
1574
|
+
let offset = 0;
|
|
1575
|
+
try {
|
|
1576
|
+
for (; ; ) {
|
|
1577
|
+
const next = await reader.read();
|
|
1578
|
+
if (next.done) break;
|
|
1579
|
+
const chunk = next.value;
|
|
1580
|
+
if (chunk.byteLength > ceiling - offset) {
|
|
1581
|
+
void reader.cancel().catch(() => void 0);
|
|
1582
|
+
return { tooLarge: true };
|
|
1583
|
+
}
|
|
1584
|
+
data.set(chunk, offset);
|
|
1585
|
+
offset += chunk.byteLength;
|
|
1461
1586
|
}
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
payload: envelope.payload,
|
|
1465
|
-
receiptId: crypto.randomUUID()
|
|
1466
|
-
});
|
|
1467
|
-
if (!outcome.created && !sameReliableEgress(outcome.record.payload, envelope.payload)) return "rejected";
|
|
1468
|
-
return completeInboundEnvelope(stores, deviceId, envelope.id, envelope.payload.agentRef);
|
|
1587
|
+
} finally {
|
|
1588
|
+
reader.releaseLock();
|
|
1469
1589
|
}
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1590
|
+
return { tooLarge: false, data: data.subarray(0, offset) };
|
|
1591
|
+
}
|
|
1592
|
+
function blobDownloadContentHandler(deps) {
|
|
1593
|
+
return async (c) => {
|
|
1594
|
+
const blobId = c.req.param("id") ?? "";
|
|
1595
|
+
const query = SignedUrlQuerySchema.safeParse({ sig: c.req.query("sig"), exp: c.req.query("exp") });
|
|
1596
|
+
if (!query.success || !await deps.contentProxy.verifySignedUrl(blobId, "get", query.data.sig, query.data.exp)) {
|
|
1597
|
+
return c.json({ error: "invalid or expired signature" }, 401);
|
|
1474
1598
|
}
|
|
1475
|
-
const
|
|
1476
|
-
if (
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1599
|
+
const result = await deps.contentProxy.readContent(blobId);
|
|
1600
|
+
if (result === void 0) return c.json({ error: "blob not found" }, 404);
|
|
1601
|
+
if (!result.ok) return c.json({ error: result.code }, BLOB_READ_ERROR_HTTP_STATUS[result.code]);
|
|
1602
|
+
return c.body(new Uint8Array(result.content.data), 200, { "content-type": result.content.contentType });
|
|
1603
|
+
};
|
|
1604
|
+
}
|
|
1605
|
+
var BLOB_READ_ERROR_HTTP_STATUS = {
|
|
1606
|
+
blob_upstream_unavailable: 502,
|
|
1607
|
+
blob_upstream_stream_interrupted: 502
|
|
1608
|
+
};
|
|
1609
|
+
function idempotencyKey(c) {
|
|
1610
|
+
const value = c.req.header("Idempotency-Key");
|
|
1611
|
+
if (value === void 0 || value.length === 0 || value.length > 200) return void 0;
|
|
1612
|
+
return value;
|
|
1613
|
+
}
|
|
1614
|
+
function renderStorageError(c, error) {
|
|
1615
|
+
if (!isCoreError(error)) return void 0;
|
|
1616
|
+
if (STORAGE_ERROR_CODES.includes(error.code)) {
|
|
1617
|
+
const code = error.code;
|
|
1618
|
+
return c.json({ error: code }, STORAGE_ERROR_HTTP_STATUS[code]);
|
|
1482
1619
|
}
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
const attempt = await stores.tasks.get(taskId);
|
|
1486
|
-
if (attempt === void 0 || attempt.deviceId !== deviceId) return "rejected";
|
|
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";
|
|
1620
|
+
if (error.code === "storage_reservation_not_found" || error.code === "object_not_found") {
|
|
1621
|
+
return c.json({ error: error.code }, 404);
|
|
1502
1622
|
}
|
|
1503
|
-
|
|
1504
|
-
|
|
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;
|
|
1623
|
+
if (error.code === "storage_entitlement_missing" || error.code === "object_state_invalid") {
|
|
1624
|
+
return c.json({ error: error.code }, 409);
|
|
1515
1625
|
}
|
|
1516
|
-
if (
|
|
1517
|
-
|
|
1518
|
-
return "rejected";
|
|
1626
|
+
if (error.code === "content_hash_invalid") {
|
|
1627
|
+
return c.json({ error: error.code }, 400);
|
|
1519
1628
|
}
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1629
|
+
return void 0;
|
|
1630
|
+
}
|
|
1631
|
+
|
|
1632
|
+
// src/handlers/capabilities.ts
|
|
1633
|
+
function capabilitiesHandler(deps) {
|
|
1634
|
+
return async (c) => c.json(deps.declaration, 200);
|
|
1635
|
+
}
|
|
1636
|
+
var AUTH_JSON_BODY_MAX_BYTES = 16 * 1024;
|
|
1637
|
+
function pairHandler(deps) {
|
|
1638
|
+
return async (c) => {
|
|
1639
|
+
const body = await readBoundedJsonBody(c, AUTH_JSON_BODY_MAX_BYTES);
|
|
1640
|
+
if (body.tooLarge) return c.json({ error: "request body too large" }, 413);
|
|
1641
|
+
const parsed = PairRequestSchema.safeParse(body.body);
|
|
1642
|
+
if (!parsed.success) {
|
|
1643
|
+
return c.json(
|
|
1644
|
+
{
|
|
1645
|
+
error: "pairingCode, deviceName, and devicePublicKey are required strings; optional machineId must be 64 lowercase hex characters"
|
|
1529
1646
|
},
|
|
1530
|
-
|
|
1647
|
+
400
|
|
1531
1648
|
);
|
|
1532
|
-
} catch (caught) {
|
|
1533
|
-
if (isCloudError(caught, "activity_batch_too_large") || isCloudError(caught, "coordination_input_invalid")) {
|
|
1534
|
-
return "rejected";
|
|
1535
|
-
}
|
|
1536
|
-
throw caught;
|
|
1537
1649
|
}
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1650
|
+
const device = await deps.auth.redeemAndRegister(parsed.data);
|
|
1651
|
+
if (device === void 0) return c.json({ error: "invalid pairing code" }, 401);
|
|
1652
|
+
const { accessToken, expiresAt } = await deps.auth.mintAccessToken(device);
|
|
1653
|
+
const response = PairResponseSchema.parse({
|
|
1654
|
+
deviceId: device.deviceId,
|
|
1655
|
+
accessToken,
|
|
1656
|
+
refreshHint: expiresAt,
|
|
1657
|
+
tenantId: device.tenantId
|
|
1658
|
+
});
|
|
1659
|
+
return c.json(response, 200);
|
|
1660
|
+
};
|
|
1661
|
+
}
|
|
1662
|
+
function challengeHandler(deps) {
|
|
1663
|
+
return async (c) => {
|
|
1664
|
+
const body = await readBoundedJsonBody(c, AUTH_JSON_BODY_MAX_BYTES);
|
|
1665
|
+
if (body.tooLarge) return c.json({ error: "request body too large" }, 413);
|
|
1666
|
+
const parsed = ChallengeRequestSchema.safeParse(body.body);
|
|
1667
|
+
if (!parsed.success) return c.json({ error: "deviceId is required" }, 400);
|
|
1668
|
+
const device = await deps.auth.resolveDevice(parsed.data.deviceId);
|
|
1669
|
+
if (device === void 0) return c.json({ error: "unknown or revoked device" }, 401);
|
|
1670
|
+
const response = { nonce: await deps.auth.issueNonce(device) };
|
|
1671
|
+
return c.json(response, 200);
|
|
1672
|
+
};
|
|
1673
|
+
}
|
|
1674
|
+
function tokenHandler(deps) {
|
|
1675
|
+
return async (c) => {
|
|
1676
|
+
const body = await readBoundedJsonBody(c, AUTH_JSON_BODY_MAX_BYTES);
|
|
1677
|
+
if (body.tooLarge) return c.json({ error: "request body too large" }, 413);
|
|
1678
|
+
const parsed = TokenRequestSchema.safeParse(body.body);
|
|
1679
|
+
if (!parsed.success) return c.json({ error: "deviceId, nonce, and signature are required" }, 400);
|
|
1680
|
+
const { deviceId, nonce, signature } = parsed.data;
|
|
1681
|
+
const device = await deps.auth.resolveDevice(deviceId);
|
|
1682
|
+
if (device === void 0) return c.json({ error: "unknown or revoked device" }, 401);
|
|
1683
|
+
if (!await deps.auth.verifySignature(device, nonce, signature)) {
|
|
1684
|
+
return c.json({ error: "invalid signature" }, 401);
|
|
1546
1685
|
}
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1686
|
+
if (!await deps.auth.consumeNonceIfValid(device, nonce)) {
|
|
1687
|
+
return c.json({ error: "invalid, expired, or already-used nonce" }, 401);
|
|
1688
|
+
}
|
|
1689
|
+
const { accessToken, expiresAt } = await deps.auth.mintAccessToken(device);
|
|
1690
|
+
const response = { accessToken, expiresAt };
|
|
1691
|
+
return c.json(response, 200);
|
|
1692
|
+
};
|
|
1551
1693
|
}
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1694
|
+
var CLOUD_PROTOCOL_CAPABILITIES = [
|
|
1695
|
+
"custom-harness",
|
|
1696
|
+
MAILBOX_READ_AHEAD_CAPABILITY,
|
|
1697
|
+
"result-document",
|
|
1698
|
+
// The kernel's inbound handles `task.approval_resolved` on both the plain
|
|
1699
|
+
// and the approval-timeline paths (`packages/cloud/src/inbound.ts:576,610`).
|
|
1700
|
+
// Without this flag `TaskRunner.sendApprovalResolved` returns early and a
|
|
1701
|
+
// locally-resolved approval is never reported on the wire at all.
|
|
1702
|
+
"approval_resolved",
|
|
1703
|
+
"agent-home-contract",
|
|
1704
|
+
"agent-home-projection",
|
|
1705
|
+
"agent-egress-policy",
|
|
1706
|
+
"agent-egress-reliable-ack",
|
|
1707
|
+
"agent-egress-fresh-session",
|
|
1708
|
+
// `handleAgentMessagePublish` accepts `agent.message_egress` gated on exactly
|
|
1709
|
+
// this capability (`packages/cloud/src/inbound.ts:124`) — the same inbound
|
|
1710
|
+
// family as the three egress flags above, omitted by the same drift.
|
|
1711
|
+
"agent-message-egress",
|
|
1712
|
+
"agent-content-workspace-read",
|
|
1713
|
+
"agent-content-transcript-read",
|
|
1714
|
+
"agent-content-artifact-read"
|
|
1715
|
+
];
|
|
1716
|
+
function sleep(ms) {
|
|
1717
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
1555
1718
|
}
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1719
|
+
function eventsHandler(deps) {
|
|
1720
|
+
return async (c) => {
|
|
1721
|
+
const authenticated = await authenticateDevice(c, deps);
|
|
1722
|
+
if (authenticated === void 0) return c.json({ error: "unauthorized" }, 401);
|
|
1723
|
+
const { device, stores } = authenticated;
|
|
1724
|
+
const cursorRaw = c.req.query("cursor");
|
|
1725
|
+
let cursor = 0;
|
|
1726
|
+
if (cursorRaw !== void 0) {
|
|
1727
|
+
const parsedCursor = Number(cursorRaw);
|
|
1728
|
+
if (!Number.isSafeInteger(parsedCursor) || parsedCursor < 0) {
|
|
1729
|
+
return c.json({ error: "invalid cursor" }, 400);
|
|
1730
|
+
}
|
|
1731
|
+
cursor = parsedCursor;
|
|
1568
1732
|
}
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
taskId,
|
|
1585
|
-
sourceEnvelopeId: envelope.id,
|
|
1586
|
-
batchSeq: envelope.payload.seq,
|
|
1587
|
-
events: envelope.payload.events,
|
|
1588
|
-
dropped: 0
|
|
1589
|
-
},
|
|
1590
|
-
activityBounds
|
|
1591
|
-
);
|
|
1733
|
+
const afterRaw = c.req.query("afterSeq");
|
|
1734
|
+
const afterSeq = afterRaw === void 0 ? cursor : Number(afterRaw);
|
|
1735
|
+
if (!Number.isSafeInteger(afterSeq) || afterSeq < cursor || afterSeq - cursor > MAILBOX_READ_AHEAD_MAX_SEQS) {
|
|
1736
|
+
return c.json({ error: "invalid read-ahead offset" }, 400);
|
|
1737
|
+
}
|
|
1738
|
+
const acked = await stores.mailbox.readCursor(device.deviceId);
|
|
1739
|
+
if (afterRaw !== void 0 && afterSeq > acked.deliveredSeq) return c.json({ error: "read offset exceeds delivered watermark" }, 409);
|
|
1740
|
+
if (cursor > acked.ackedSeq) {
|
|
1741
|
+
try {
|
|
1742
|
+
await stores.mailbox.advanceCursor({ deviceId: device.deviceId, ackedSeq: cursor });
|
|
1743
|
+
} catch (caught) {
|
|
1744
|
+
if (isCoreConflictError(caught, "mailbox_cursor_ahead_of_delivery")) {
|
|
1745
|
+
return c.json({ error: "cursor exceeds delivered watermark" }, 409);
|
|
1746
|
+
}
|
|
1747
|
+
throw caught;
|
|
1592
1748
|
}
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
return;
|
|
1606
|
-
case "task.cancelled":
|
|
1607
|
-
await recordTerminal(stores, taskId, envelope);
|
|
1608
|
-
return;
|
|
1609
|
-
default:
|
|
1610
|
-
return;
|
|
1611
|
-
}
|
|
1612
|
-
}
|
|
1613
|
-
function approvalTimelineAppendInput(taskId, envelope) {
|
|
1614
|
-
switch (envelope.type) {
|
|
1615
|
-
case "task.await_approval":
|
|
1616
|
-
return {
|
|
1617
|
-
taskId,
|
|
1618
|
-
sourceEnvelopeId: envelope.id,
|
|
1619
|
-
event: {
|
|
1620
|
-
type: "approval_requested",
|
|
1621
|
-
summary: envelope.payload.summary,
|
|
1622
|
-
...envelope.payload.approvalId === void 0 ? {} : { approvalId: envelope.payload.approvalId }
|
|
1749
|
+
}
|
|
1750
|
+
const attempts = Math.max(1, Math.ceil(deps.longPollHoldMs / deps.longPollIntervalMs));
|
|
1751
|
+
for (let attempt = 0; attempt < attempts; attempt += 1) {
|
|
1752
|
+
let scanCursor = afterSeq;
|
|
1753
|
+
while (true) {
|
|
1754
|
+
const page = await stores.mailbox.readAfter({
|
|
1755
|
+
deviceId: device.deviceId,
|
|
1756
|
+
afterSeq: scanCursor,
|
|
1757
|
+
limit: deps.pageLimit
|
|
1758
|
+
});
|
|
1759
|
+
if (cursor < page.recoverableFrom - 1) {
|
|
1760
|
+
return c.json({ error: "cursor_too_old", recoverableFrom: page.recoverableFrom }, 409);
|
|
1623
1761
|
}
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1762
|
+
if (page.messages.length === 0) break;
|
|
1763
|
+
const decoded = page.messages.map((message) => decodeEnvelope(message.body));
|
|
1764
|
+
const offeredTaskIds = decoded.flatMap(
|
|
1765
|
+
(event) => isTaskOfferType(event.type) && event.task_id !== void 0 ? [event.task_id] : []
|
|
1766
|
+
);
|
|
1767
|
+
const attemptsByTaskId = Object.fromEntries(
|
|
1768
|
+
(await stores.tasks.getMany(offeredTaskIds)).map((attempt2) => [attempt2.taskId, attempt2])
|
|
1769
|
+
);
|
|
1770
|
+
const events = decoded.filter((event) => {
|
|
1771
|
+
if (!isTaskOfferType(event.type)) return true;
|
|
1772
|
+
return event.task_id === void 0 || attemptsByTaskId[event.task_id]?.cancellation === void 0;
|
|
1773
|
+
});
|
|
1774
|
+
if (events.length === 0 && page.hasMore) {
|
|
1775
|
+
scanCursor = page.nextSeq;
|
|
1776
|
+
continue;
|
|
1635
1777
|
}
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
}
|
|
1650
|
-
const attempt = await stores.tasks.get(taskId);
|
|
1651
|
-
if (attempt?.cancellation !== void 0) {
|
|
1652
|
-
await stores.tasks.recordStatus({
|
|
1653
|
-
taskId,
|
|
1654
|
-
status: "cancelled",
|
|
1655
|
-
...attempt.agentRef === void 0 ? {} : { agentRef: attempt.agentRef }
|
|
1656
|
-
});
|
|
1657
|
-
return;
|
|
1658
|
-
}
|
|
1659
|
-
const recordedAttempt = await stores.tasks.recordStatus({
|
|
1660
|
-
taskId,
|
|
1661
|
-
status: winner.status,
|
|
1662
|
-
...attempt?.agentRef === void 0 ? {} : { agentRef: attempt.agentRef },
|
|
1663
|
-
...winner.cause === void 0 ? {} : { terminalCause: winner.cause }
|
|
1664
|
-
});
|
|
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
|
-
});
|
|
1778
|
+
if (events.length === 0) break;
|
|
1779
|
+
const response2 = {
|
|
1780
|
+
events,
|
|
1781
|
+
cursor: page.nextSeq,
|
|
1782
|
+
capabilities: CLOUD_PROTOCOL_CAPABILITIES
|
|
1783
|
+
};
|
|
1784
|
+
await stores.mailbox.recordDelivery({
|
|
1785
|
+
deviceId: device.deviceId,
|
|
1786
|
+
deliveredSeq: response2.cursor
|
|
1787
|
+
});
|
|
1788
|
+
return c.json(response2, 200);
|
|
1789
|
+
}
|
|
1790
|
+
if (attempt < attempts - 1) await sleep(deps.longPollIntervalMs);
|
|
1672
1791
|
}
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
return "complete";
|
|
1681
|
-
case "task.fail":
|
|
1682
|
-
case "task.decline":
|
|
1683
|
-
return "failed";
|
|
1684
|
-
case "task.cancelled":
|
|
1685
|
-
return "cancelled";
|
|
1686
|
-
default:
|
|
1687
|
-
return void 0;
|
|
1688
|
-
}
|
|
1689
|
-
}
|
|
1690
|
-
function terminalReceiptBinding(taskId, body) {
|
|
1691
|
-
let envelope;
|
|
1692
|
-
try {
|
|
1693
|
-
envelope = decodeEnvelope(body);
|
|
1694
|
-
} catch {
|
|
1695
|
-
return void 0;
|
|
1696
|
-
}
|
|
1697
|
-
const status = terminalStatus(envelope);
|
|
1698
|
-
if (status === void 0 || envelope.task_id !== taskId || encodeEnvelope(envelope) !== body) return void 0;
|
|
1699
|
-
const cause = envelope.type === "task.fail" || envelope.type === "task.cancelled" || envelope.type === "task.decline" ? envelope.payload.reason : void 0;
|
|
1700
|
-
return { status, ...cause === void 0 ? {} : { cause } };
|
|
1792
|
+
const response = {
|
|
1793
|
+
events: [],
|
|
1794
|
+
cursor: afterSeq,
|
|
1795
|
+
capabilities: CLOUD_PROTOCOL_CAPABILITIES
|
|
1796
|
+
};
|
|
1797
|
+
return c.json(response, 200);
|
|
1798
|
+
};
|
|
1701
1799
|
}
|
|
1702
|
-
|
|
1703
|
-
// src/handlers/messages.ts
|
|
1704
1800
|
var MESSAGES_JSON_BODY_MAX_BYTES = 2 * 1024 * 1024;
|
|
1705
1801
|
function messagesHandler(deps) {
|
|
1706
1802
|
return async (c) => {
|
|
@@ -1775,6 +1871,63 @@ function agentHomeProjectionCompletionHandler(deps) {
|
|
|
1775
1871
|
}
|
|
1776
1872
|
};
|
|
1777
1873
|
}
|
|
1874
|
+
function mapCloudError(c, error) {
|
|
1875
|
+
if (isCloudError(error, "input_preparation_request_not_found")) {
|
|
1876
|
+
return c.json({ error: error.code }, 404);
|
|
1877
|
+
}
|
|
1878
|
+
if (isCloudError(error, "input_preparation_receipt_mismatch") || isCloudError(error, "input_preparation_receipt_invalid")) {
|
|
1879
|
+
return c.json({ error: error.code }, 422);
|
|
1880
|
+
}
|
|
1881
|
+
if (isCloudError(error, "input_preparation_request_conflict") || isCloudError(error, "input_preparation_completion_conflict")) {
|
|
1882
|
+
return c.json({ error: error.code }, 409);
|
|
1883
|
+
}
|
|
1884
|
+
throw error;
|
|
1885
|
+
}
|
|
1886
|
+
function inputPreparationCompletionHandler(deps) {
|
|
1887
|
+
return async (c) => {
|
|
1888
|
+
const authenticated = await authenticateDevice(c, deps);
|
|
1889
|
+
if (authenticated === void 0) return c.json({ error: "unauthorized" }, 401);
|
|
1890
|
+
const parsed = InputPreparationCompletionRequestSchema.safeParse(await readJsonBody(c));
|
|
1891
|
+
if (!parsed.success) return c.json({ error: "invalid input preparation completion" }, 422);
|
|
1892
|
+
const requestId = c.req.param("requestId");
|
|
1893
|
+
if (requestId === void 0 || requestId !== parsed.data.requestId) {
|
|
1894
|
+
return c.json({ error: "input_preparation_receipt_mismatch" }, 422);
|
|
1895
|
+
}
|
|
1896
|
+
try {
|
|
1897
|
+
return c.json(
|
|
1898
|
+
await deps.complete(authenticated.stores, authenticated.device.deviceId, parsed.data),
|
|
1899
|
+
200
|
|
1900
|
+
);
|
|
1901
|
+
} catch (error) {
|
|
1902
|
+
return mapCloudError(c, error);
|
|
1903
|
+
}
|
|
1904
|
+
};
|
|
1905
|
+
}
|
|
1906
|
+
function inputPreparationStatusHandler(deps) {
|
|
1907
|
+
return async (c) => {
|
|
1908
|
+
const authenticated = await authenticateDevice(c, deps);
|
|
1909
|
+
if (authenticated === void 0) return c.json({ error: "unauthorized" }, 401);
|
|
1910
|
+
const requestId = c.req.param("requestId");
|
|
1911
|
+
if (requestId === void 0 || requestId.length === 0) {
|
|
1912
|
+
return c.json({ error: "invalid input preparation request id" }, 422);
|
|
1913
|
+
}
|
|
1914
|
+
const query = InputPreparationStatusQuerySchema.safeParse({
|
|
1915
|
+
agentId: c.req.query("agentId"),
|
|
1916
|
+
profileRevision: c.req.query("profileRevision")
|
|
1917
|
+
});
|
|
1918
|
+
if (!query.success) return c.json({ error: "invalid input preparation status query" }, 422);
|
|
1919
|
+
try {
|
|
1920
|
+
const readback = await deps.status(authenticated.stores, authenticated.device.deviceId, {
|
|
1921
|
+
requestId,
|
|
1922
|
+
agentRef: { agentId: query.data.agentId, profileRevision: query.data.profileRevision }
|
|
1923
|
+
});
|
|
1924
|
+
if (readback === void 0) return c.json({ error: "input_preparation_request_not_found" }, 404);
|
|
1925
|
+
return c.json(readback, 200);
|
|
1926
|
+
} catch (error) {
|
|
1927
|
+
return mapCloudError(c, error);
|
|
1928
|
+
}
|
|
1929
|
+
};
|
|
1930
|
+
}
|
|
1778
1931
|
var DEFAULT_MAX_AGENT_MEMORY_PROJECTION_REQUEST_BYTES = 1 * 1024 * 1024;
|
|
1779
1932
|
function agentMemoryProjectionHandler(deps) {
|
|
1780
1933
|
return async (c) => {
|
|
@@ -2661,6 +2814,135 @@ async function recordAgentHomeProjectionCompletion(receipts, tenant, deviceId, r
|
|
|
2661
2814
|
completedAt: storedCompletion.receipt.recordedAt
|
|
2662
2815
|
};
|
|
2663
2816
|
}
|
|
2817
|
+
function inputPreparationRequestKey(deviceId, agentRef, requestId) {
|
|
2818
|
+
return agentReliabilityKey("agent-input-preparation-request", deviceId, agentRef, requestId);
|
|
2819
|
+
}
|
|
2820
|
+
function inputPreparationCompletionKey(deviceId, agentRef, requestId) {
|
|
2821
|
+
return agentReliabilityKey("agent-input-preparation-completion", deviceId, agentRef, requestId);
|
|
2822
|
+
}
|
|
2823
|
+
function sameInputPreparationRequest(expected, actual) {
|
|
2824
|
+
return canonicalRequestBody(expected) === canonicalRequestBody(actual);
|
|
2825
|
+
}
|
|
2826
|
+
function canonicalRequestBody(payload) {
|
|
2827
|
+
return JSON.stringify(AgentInputPreparationPayloadSchema.parse(payload));
|
|
2828
|
+
}
|
|
2829
|
+
function receiptMatchesInputPreparation(request, receipt) {
|
|
2830
|
+
return receipt.requestId === request.requestId && receipt.agentRef.agentId === request.agentRef.agentId && receipt.agentRef.profileRevision === request.agentRef.profileRevision && receipt.profileId === request.profileId && receipt.policyRevision === request.policyRevision;
|
|
2831
|
+
}
|
|
2832
|
+
function statusInputMatchesInputPreparation(request, input) {
|
|
2833
|
+
return input.requestId === request.requestId && input.agentRef.agentId === request.agentRef.agentId && input.agentRef.profileRevision === request.agentRef.profileRevision;
|
|
2834
|
+
}
|
|
2835
|
+
function parseRequestBody2(body) {
|
|
2836
|
+
try {
|
|
2837
|
+
return AgentInputPreparationPayloadSchema.parse(JSON.parse(body));
|
|
2838
|
+
} catch (error) {
|
|
2839
|
+
throw new ByokCloudError(
|
|
2840
|
+
"input_preparation_receipt_invalid",
|
|
2841
|
+
"Stored input-preparation request is not a valid immutable desired fact.",
|
|
2842
|
+
{ cause: error }
|
|
2843
|
+
);
|
|
2844
|
+
}
|
|
2845
|
+
}
|
|
2846
|
+
function parseCompletionBody2(body) {
|
|
2847
|
+
try {
|
|
2848
|
+
return InputPreparationCompletionRequestSchema.parse(JSON.parse(body));
|
|
2849
|
+
} catch (error) {
|
|
2850
|
+
throw new ByokCloudError(
|
|
2851
|
+
"input_preparation_receipt_invalid",
|
|
2852
|
+
"Stored input-preparation completion is not a valid immutable receipt fact.",
|
|
2853
|
+
{ cause: error }
|
|
2854
|
+
);
|
|
2855
|
+
}
|
|
2856
|
+
}
|
|
2857
|
+
function terminalReadback(tenant, deviceId, request, completion, completedAt) {
|
|
2858
|
+
const common = {
|
|
2859
|
+
tenantId: tenant,
|
|
2860
|
+
deviceId,
|
|
2861
|
+
requestId: request.requestId,
|
|
2862
|
+
agentRef: request.agentRef,
|
|
2863
|
+
profileId: request.profileId,
|
|
2864
|
+
policyRevision: request.policyRevision,
|
|
2865
|
+
completedAt
|
|
2866
|
+
};
|
|
2867
|
+
return completion.outcome === "prepared" ? { ...common, status: "prepared", receipt: completion.receipt } : { ...common, status: "rejected", reason: completion.reason };
|
|
2868
|
+
}
|
|
2869
|
+
async function readInputPreparationStatus(receipts, tenant, deviceId, input) {
|
|
2870
|
+
const storedRequest = await receipts.get(inputPreparationRequestKey(deviceId, input.agentRef, input.requestId));
|
|
2871
|
+
if (storedRequest === void 0) return void 0;
|
|
2872
|
+
const request = parseRequestBody2(storedRequest.body);
|
|
2873
|
+
if (!statusInputMatchesInputPreparation(request, input)) {
|
|
2874
|
+
throw new ByokCloudError(
|
|
2875
|
+
"input_preparation_request_conflict",
|
|
2876
|
+
`Input-preparation request ${input.requestId} does not match its immutable desired fact.`
|
|
2877
|
+
);
|
|
2878
|
+
}
|
|
2879
|
+
const storedCompletion = await receipts.get(
|
|
2880
|
+
inputPreparationCompletionKey(deviceId, request.agentRef, input.requestId)
|
|
2881
|
+
);
|
|
2882
|
+
if (storedCompletion === void 0) {
|
|
2883
|
+
return {
|
|
2884
|
+
tenantId: tenant,
|
|
2885
|
+
deviceId,
|
|
2886
|
+
requestId: request.requestId,
|
|
2887
|
+
agentRef: request.agentRef,
|
|
2888
|
+
profileId: request.profileId,
|
|
2889
|
+
policyRevision: request.policyRevision,
|
|
2890
|
+
status: "pending"
|
|
2891
|
+
};
|
|
2892
|
+
}
|
|
2893
|
+
const completion = parseCompletionBody2(storedCompletion.body);
|
|
2894
|
+
if (!receiptMatchesInputPreparation(request, completion)) {
|
|
2895
|
+
throw new ByokCloudError(
|
|
2896
|
+
"input_preparation_receipt_mismatch",
|
|
2897
|
+
`Input-preparation completion ${input.requestId} does not match its immutable desired fact.`
|
|
2898
|
+
);
|
|
2899
|
+
}
|
|
2900
|
+
return terminalReadback(tenant, deviceId, request, completion, storedCompletion.recordedAt);
|
|
2901
|
+
}
|
|
2902
|
+
async function recordInputPreparationCompletion(receipts, tenant, deviceId, receiptInput) {
|
|
2903
|
+
const receipt = InputPreparationCompletionRequestSchema.parse(receiptInput);
|
|
2904
|
+
const storedRequest = await receipts.get(
|
|
2905
|
+
inputPreparationRequestKey(deviceId, receipt.agentRef, receipt.requestId)
|
|
2906
|
+
);
|
|
2907
|
+
if (storedRequest === void 0) {
|
|
2908
|
+
throw new ByokCloudError(
|
|
2909
|
+
"input_preparation_request_not_found",
|
|
2910
|
+
`Input-preparation request ${receipt.requestId} was not found for this device.`
|
|
2911
|
+
);
|
|
2912
|
+
}
|
|
2913
|
+
const request = parseRequestBody2(storedRequest.body);
|
|
2914
|
+
if (!receiptMatchesInputPreparation(request, receipt)) {
|
|
2915
|
+
throw new ByokCloudError(
|
|
2916
|
+
"input_preparation_receipt_mismatch",
|
|
2917
|
+
`Input-preparation completion ${receipt.requestId} does not exactly match its desired fact.`
|
|
2918
|
+
);
|
|
2919
|
+
}
|
|
2920
|
+
const body = JSON.stringify(receipt);
|
|
2921
|
+
const storedCompletion = await receipts.record({
|
|
2922
|
+
key: inputPreparationCompletionKey(deviceId, request.agentRef, receipt.requestId),
|
|
2923
|
+
body
|
|
2924
|
+
});
|
|
2925
|
+
if (!storedCompletion.created && storedCompletion.receipt.body !== body) {
|
|
2926
|
+
throw new ByokCloudError(
|
|
2927
|
+
"input_preparation_completion_conflict",
|
|
2928
|
+
`Input-preparation request ${receipt.requestId} already has a different terminal completion.`
|
|
2929
|
+
);
|
|
2930
|
+
}
|
|
2931
|
+
return terminalReadback(
|
|
2932
|
+
tenant,
|
|
2933
|
+
deviceId,
|
|
2934
|
+
request,
|
|
2935
|
+
parseCompletionBody2(storedCompletion.receipt.body),
|
|
2936
|
+
storedCompletion.receipt.recordedAt
|
|
2937
|
+
);
|
|
2938
|
+
}
|
|
2939
|
+
function readDeviceTerminalReceipt(taskId, receipt) {
|
|
2940
|
+
const envelope = decodeEnvelope(receipt.body);
|
|
2941
|
+
if (envelope.task_id !== taskId || envelope.type !== "task.complete" && envelope.type !== "task.fail" && envelope.type !== "task.decline" && envelope.type !== "task.cancelled") {
|
|
2942
|
+
throw new ByokCloudError("terminal_receipt_unreadable", "Device terminal receipt identity or type is invalid.");
|
|
2943
|
+
}
|
|
2944
|
+
return { envelope, recordedAt: receipt.recordedAt };
|
|
2945
|
+
}
|
|
2664
2946
|
function projectTerminalResult(taskId, receipt) {
|
|
2665
2947
|
let envelope;
|
|
2666
2948
|
try {
|
|
@@ -2830,6 +3112,19 @@ function createByokCloud(options) {
|
|
|
2830
3112
|
complete: (stores, deviceId, receipt) => completeAgentHomeProjectionFromStores(stores, deviceId, receipt)
|
|
2831
3113
|
})
|
|
2832
3114
|
);
|
|
3115
|
+
const inputPreparationRouteDeps = {
|
|
3116
|
+
...deviceRouteDeps,
|
|
3117
|
+
complete: (stores, deviceId, receipt) => completeInputPreparationFromStores(stores, deviceId, receipt),
|
|
3118
|
+
status: (stores, deviceId, input) => readInputPreparationStatus(stores.receipts, stores.tenant, deviceId, input)
|
|
3119
|
+
};
|
|
3120
|
+
registry.register(
|
|
3121
|
+
{ method: "PUT", path: BYOK_INPUT_PREPARATION_COMPLETION_ROUTE, class: "device" },
|
|
3122
|
+
inputPreparationCompletionHandler(inputPreparationRouteDeps)
|
|
3123
|
+
);
|
|
3124
|
+
registry.register(
|
|
3125
|
+
{ method: "GET", path: BYOK_INPUT_PREPARATION_STATUS_ROUTE, class: "device" },
|
|
3126
|
+
inputPreparationStatusHandler(inputPreparationRouteDeps)
|
|
3127
|
+
);
|
|
2833
3128
|
if (declares(declaration, CLOUD_CAPABILITIES.boardCoordination)) {
|
|
2834
3129
|
const boardDeps = {
|
|
2835
3130
|
...deviceRouteDeps,
|
|
@@ -3340,6 +3635,25 @@ function createByokCloud(options) {
|
|
|
3340
3635
|
receipt
|
|
3341
3636
|
);
|
|
3342
3637
|
}
|
|
3638
|
+
async function getInputPreparationStatus(tenant, deviceId, input) {
|
|
3639
|
+
return readInputPreparationStatus(
|
|
3640
|
+
tenantStoresFor(controlPlane(tenant), root).receipts,
|
|
3641
|
+
tenant,
|
|
3642
|
+
deviceId,
|
|
3643
|
+
input
|
|
3644
|
+
);
|
|
3645
|
+
}
|
|
3646
|
+
async function completeInputPreparation(tenant, deviceId, receiptInput) {
|
|
3647
|
+
return completeInputPreparationFromStores(
|
|
3648
|
+
tenantStoresFor(controlPlane(tenant), root),
|
|
3649
|
+
deviceId,
|
|
3650
|
+
receiptInput
|
|
3651
|
+
);
|
|
3652
|
+
}
|
|
3653
|
+
async function completeInputPreparationFromStores(stores, deviceId, receiptInput) {
|
|
3654
|
+
const receipt = InputPreparationCompletionRequestSchema.parse(receiptInput);
|
|
3655
|
+
return recordInputPreparationCompletion(stores.receipts, stores.tenant, deviceId, receipt);
|
|
3656
|
+
}
|
|
3343
3657
|
async function commitAgentMemoryProjectionFromStores(stores, deviceId, mutationInput, redactedBytes) {
|
|
3344
3658
|
const mutation = AgentMemoryProjectionCommitRequestSchema.parse(mutationInput);
|
|
3345
3659
|
const attempt = await stores.tasks.get(mutation.taskId);
|
|
@@ -3474,6 +3788,36 @@ function createByokCloud(options) {
|
|
|
3474
3788
|
throw new ByokCloudError("mailbox_receipt_mismatch", "Agent message disposition mailbox readback mismatched.");
|
|
3475
3789
|
}
|
|
3476
3790
|
}
|
|
3791
|
+
async function enqueueFreshAgentEgressOffer(tenant, deviceId, input) {
|
|
3792
|
+
await assertAgentCapabilities(tenant, deviceId, [
|
|
3793
|
+
AGENT_HOME_CONTRACT_CAPABILITY,
|
|
3794
|
+
AGENT_EGRESS_POLICY_CAPABILITY,
|
|
3795
|
+
AGENT_EGRESS_RELIABLE_ACK_CAPABILITY,
|
|
3796
|
+
AGENT_EGRESS_FRESH_SESSION_CAPABILITY,
|
|
3797
|
+
...input.payload.messageEgress === void 0 ? [] : [AGENT_MESSAGE_EGRESS_CAPABILITY],
|
|
3798
|
+
...input.payload.terminalProjection === void 0 ? [] : [TERMINAL_PROJECTION_SELECTION_CAPABILITY]
|
|
3799
|
+
]);
|
|
3800
|
+
const payload = TaskOfferForAgentWithEgressFreshPayloadSchema.parse(input.payload);
|
|
3801
|
+
if (payload.messageEgress === void 0 && input.agentMessageContext !== void 0) {
|
|
3802
|
+
throw new Error("agentMessageContext requires messageEgress");
|
|
3803
|
+
}
|
|
3804
|
+
const messageContext = payload.messageEgress === void 0 ? void 0 : AgentMessageServerContextSchema.parse(input.agentMessageContext);
|
|
3805
|
+
const enqueued = await enqueueTaskEnvelope(
|
|
3806
|
+
tenant,
|
|
3807
|
+
deviceId,
|
|
3808
|
+
input.taskId,
|
|
3809
|
+
payload.agentRef,
|
|
3810
|
+
(taskId, seq, messageId) => createEnvelope("task.offer_for_agent_with_egress_fresh", payload, { id: messageId, taskId, seq }),
|
|
3811
|
+
payload.messageEgress === void 0 ? void 0 : async (stores, taskId) => {
|
|
3812
|
+
const body = JSON.stringify({ agentRef: payload.agentRef, requirement: payload.messageEgress, context: messageContext });
|
|
3813
|
+
const recorded = await stores.receipts.record({ key: `agent-message-offer:${deviceId}:${taskId}`, body });
|
|
3814
|
+
if (!recorded.created && recorded.receipt.body !== body) {
|
|
3815
|
+
throw new ByokCloudError("agent_content_request_mismatch", `Task ${taskId} already has a different Agent message context.`);
|
|
3816
|
+
}
|
|
3817
|
+
}
|
|
3818
|
+
);
|
|
3819
|
+
return enqueued;
|
|
3820
|
+
}
|
|
3477
3821
|
return {
|
|
3478
3822
|
fetch: registry.fetch,
|
|
3479
3823
|
routes: registry.routes,
|
|
@@ -3516,45 +3860,30 @@ function createByokCloud(options) {
|
|
|
3516
3860
|
(taskId, seq, messageId) => createEnvelope("task.offer_for_agent", payload, { id: messageId, taskId, seq })
|
|
3517
3861
|
);
|
|
3518
3862
|
},
|
|
3519
|
-
async
|
|
3863
|
+
async enqueuePreparedOffer(tenant, deviceId, input) {
|
|
3520
3864
|
await assertAgentCapabilities(tenant, deviceId, [
|
|
3521
3865
|
AGENT_HOME_CONTRACT_CAPABILITY,
|
|
3522
|
-
|
|
3523
|
-
AGENT_EGRESS_RELIABLE_ACK_CAPABILITY,
|
|
3524
|
-
...input.payload.messageEgress === void 0 ? [] : [AGENT_MESSAGE_EGRESS_CAPABILITY],
|
|
3866
|
+
AGENT_INPUT_PREPARATION_CAPABILITY,
|
|
3525
3867
|
...input.payload.terminalProjection === void 0 ? [] : [TERMINAL_PROJECTION_SELECTION_CAPABILITY]
|
|
3526
3868
|
]);
|
|
3527
|
-
const payload =
|
|
3528
|
-
|
|
3529
|
-
throw new Error("agentMessageContext requires messageEgress");
|
|
3530
|
-
}
|
|
3531
|
-
const messageContext = payload.messageEgress === void 0 ? void 0 : AgentMessageServerContextSchema.parse(input.agentMessageContext);
|
|
3532
|
-
const enqueued = await enqueueTaskEnvelope(
|
|
3869
|
+
const payload = TaskOfferPreparedPayloadSchema.parse(input.payload);
|
|
3870
|
+
return enqueueTaskEnvelope(
|
|
3533
3871
|
tenant,
|
|
3534
3872
|
deviceId,
|
|
3535
3873
|
input.taskId,
|
|
3536
3874
|
payload.agentRef,
|
|
3537
|
-
(taskId, seq, messageId) => createEnvelope("task.
|
|
3538
|
-
payload.messageEgress === void 0 ? void 0 : async (stores, taskId) => {
|
|
3539
|
-
const body = JSON.stringify({ agentRef: payload.agentRef, sessionRef: payload.sessionRef, requirement: payload.messageEgress, context: messageContext });
|
|
3540
|
-
const recorded = await stores.receipts.record({ key: `agent-message-offer:${deviceId}:${taskId}`, body });
|
|
3541
|
-
if (!recorded.created && recorded.receipt.body !== body) {
|
|
3542
|
-
throw new ByokCloudError("agent_content_request_mismatch", `Task ${taskId} already has a different Agent message context.`);
|
|
3543
|
-
}
|
|
3544
|
-
}
|
|
3875
|
+
(taskId, seq, messageId) => createEnvelope("task.offer_prepared", payload, { id: messageId, taskId, seq })
|
|
3545
3876
|
);
|
|
3546
|
-
return enqueued;
|
|
3547
3877
|
},
|
|
3548
|
-
async
|
|
3878
|
+
async enqueueAgentEgressOffer(tenant, deviceId, input) {
|
|
3549
3879
|
await assertAgentCapabilities(tenant, deviceId, [
|
|
3550
3880
|
AGENT_HOME_CONTRACT_CAPABILITY,
|
|
3551
3881
|
AGENT_EGRESS_POLICY_CAPABILITY,
|
|
3552
3882
|
AGENT_EGRESS_RELIABLE_ACK_CAPABILITY,
|
|
3553
|
-
AGENT_EGRESS_FRESH_SESSION_CAPABILITY,
|
|
3554
3883
|
...input.payload.messageEgress === void 0 ? [] : [AGENT_MESSAGE_EGRESS_CAPABILITY],
|
|
3555
3884
|
...input.payload.terminalProjection === void 0 ? [] : [TERMINAL_PROJECTION_SELECTION_CAPABILITY]
|
|
3556
3885
|
]);
|
|
3557
|
-
const payload =
|
|
3886
|
+
const payload = TaskOfferForAgentWithEgressPayloadSchema.parse(input.payload);
|
|
3558
3887
|
if (payload.messageEgress === void 0 && input.agentMessageContext !== void 0) {
|
|
3559
3888
|
throw new Error("agentMessageContext requires messageEgress");
|
|
3560
3889
|
}
|
|
@@ -3564,9 +3893,9 @@ function createByokCloud(options) {
|
|
|
3564
3893
|
deviceId,
|
|
3565
3894
|
input.taskId,
|
|
3566
3895
|
payload.agentRef,
|
|
3567
|
-
(taskId, seq, messageId) => createEnvelope("task.
|
|
3896
|
+
(taskId, seq, messageId) => createEnvelope("task.offer_for_agent_with_egress", payload, { id: messageId, taskId, seq }),
|
|
3568
3897
|
payload.messageEgress === void 0 ? void 0 : async (stores, taskId) => {
|
|
3569
|
-
const body = JSON.stringify({ agentRef: payload.agentRef, requirement: payload.messageEgress, context: messageContext });
|
|
3898
|
+
const body = JSON.stringify({ agentRef: payload.agentRef, sessionRef: payload.sessionRef, requirement: payload.messageEgress, context: messageContext });
|
|
3570
3899
|
const recorded = await stores.receipts.record({ key: `agent-message-offer:${deviceId}:${taskId}`, body });
|
|
3571
3900
|
if (!recorded.created && recorded.receipt.body !== body) {
|
|
3572
3901
|
throw new ByokCloudError("agent_content_request_mismatch", `Task ${taskId} already has a different Agent message context.`);
|
|
@@ -3575,6 +3904,12 @@ function createByokCloud(options) {
|
|
|
3575
3904
|
);
|
|
3576
3905
|
return enqueued;
|
|
3577
3906
|
},
|
|
3907
|
+
enqueueFreshAgentEgressOffer,
|
|
3908
|
+
async submitRecurringExecution(tenant, input) {
|
|
3909
|
+
const validated = RecurringExecutionInputSchema.parse(input);
|
|
3910
|
+
if (typeof options.agentMessage?.consume !== "function") throw new Error("Recurring execution requires a registered Agent message consumer.");
|
|
3911
|
+
return enqueueFreshAgentEgressOffer(tenant, validated.deviceId, validated);
|
|
3912
|
+
},
|
|
3578
3913
|
async enqueueAgentContentRead(tenant, deviceId, input) {
|
|
3579
3914
|
const payload = AgentContentReadPayloadSchema.parse(input.payload);
|
|
3580
3915
|
await assertAgentCapabilities(tenant, deviceId, [
|
|
@@ -3668,6 +4003,58 @@ function createByokCloud(options) {
|
|
|
3668
4003
|
},
|
|
3669
4004
|
getAgentHomeProjectionStatus,
|
|
3670
4005
|
completeAgentHomeProjection,
|
|
4006
|
+
async enqueueInputPreparation(tenant, deviceId, input) {
|
|
4007
|
+
const payload = AgentInputPreparationPayloadSchema.parse(input);
|
|
4008
|
+
await assertAgentCapabilities(tenant, deviceId, [
|
|
4009
|
+
AGENT_HOME_CONTRACT_CAPABILITY,
|
|
4010
|
+
AGENT_INPUT_PREPARATION_CAPABILITY
|
|
4011
|
+
]);
|
|
4012
|
+
const stores = tenantStoresFor(controlPlane(tenant), root);
|
|
4013
|
+
const requestBody = JSON.stringify(payload);
|
|
4014
|
+
const persisted = await stores.receipts.record({
|
|
4015
|
+
key: inputPreparationRequestKey(deviceId, payload.agentRef, payload.requestId),
|
|
4016
|
+
body: requestBody
|
|
4017
|
+
});
|
|
4018
|
+
const persistedPayload = AgentInputPreparationPayloadSchema.parse(JSON.parse(persisted.receipt.body));
|
|
4019
|
+
if (!sameInputPreparationRequest(payload, persistedPayload)) {
|
|
4020
|
+
throw new ByokCloudError(
|
|
4021
|
+
"input_preparation_request_conflict",
|
|
4022
|
+
`Input-preparation request ${payload.requestId} already exists with a different immutable desired body.`
|
|
4023
|
+
);
|
|
4024
|
+
}
|
|
4025
|
+
const messageId = await agentControlEnvelopeId(
|
|
4026
|
+
"byok:agent-input-preparation",
|
|
4027
|
+
tenant,
|
|
4028
|
+
deviceId,
|
|
4029
|
+
payload.agentRef,
|
|
4030
|
+
payload.requestId
|
|
4031
|
+
);
|
|
4032
|
+
const control = await enqueueAgentControlEnvelope(
|
|
4033
|
+
tenant,
|
|
4034
|
+
deviceId,
|
|
4035
|
+
messageId,
|
|
4036
|
+
(seq) => createEnvelope("agent.input.preparation", payload, { id: messageId, seq })
|
|
4037
|
+
);
|
|
4038
|
+
if (control.envelope.type !== "agent.input.preparation" || !sameInputPreparationRequest(payload, control.envelope.payload)) {
|
|
4039
|
+
throw new ByokCloudError(
|
|
4040
|
+
"input_preparation_request_conflict",
|
|
4041
|
+
`Mailbox request ${payload.requestId} does not match its immutable input-preparation fact.`
|
|
4042
|
+
);
|
|
4043
|
+
}
|
|
4044
|
+
const status = await getInputPreparationStatus(tenant, deviceId, {
|
|
4045
|
+
requestId: payload.requestId,
|
|
4046
|
+
agentRef: payload.agentRef
|
|
4047
|
+
});
|
|
4048
|
+
if (status === void 0) {
|
|
4049
|
+
throw new ByokCloudError(
|
|
4050
|
+
"input_preparation_receipt_invalid",
|
|
4051
|
+
`Input-preparation request ${payload.requestId} disappeared after durable allocation.`
|
|
4052
|
+
);
|
|
4053
|
+
}
|
|
4054
|
+
return { ...control, status };
|
|
4055
|
+
},
|
|
4056
|
+
getInputPreparationStatus,
|
|
4057
|
+
completeInputPreparation,
|
|
3671
4058
|
eraseAgentMemoryProjection,
|
|
3672
4059
|
approveTask(tenant, taskId, opts) {
|
|
3673
4060
|
return resolveApproval(tenant, taskId, "approve", opts);
|
|
@@ -3752,6 +4139,17 @@ function createByokCloud(options) {
|
|
|
3752
4139
|
readTerminalReceipt(tenant, taskId) {
|
|
3753
4140
|
return tenantStoresFor(controlPlane(tenant), root).receipts.get(terminalReceiptKey(taskId));
|
|
3754
4141
|
},
|
|
4142
|
+
async readDeviceTerminal(tenant, taskId) {
|
|
4143
|
+
const receipt = await tenantStoresFor(controlPlane(tenant), root).receipts.get(terminalReceiptKey(taskId));
|
|
4144
|
+
return receipt === void 0 ? void 0 : readDeviceTerminalReceipt(taskId, receipt);
|
|
4145
|
+
},
|
|
4146
|
+
readTaskAgentMessage(tenant, deviceId, taskId, agentRef) {
|
|
4147
|
+
return readTaskAgentMessage(tenantStoresFor(controlPlane(tenant), root), deviceId, taskId, agentRef);
|
|
4148
|
+
},
|
|
4149
|
+
readAgentMessageDisposition(tenant, deviceId, taskId, payload) {
|
|
4150
|
+
const parsed = AgentMessagePublishPayloadSchema.parse(payload);
|
|
4151
|
+
return readAgentMessageDisposition(tenantStoresFor(controlPlane(tenant), root), deviceId, taskId, parsed);
|
|
4152
|
+
},
|
|
3755
4153
|
readAgentEgress(tenant, deviceId, agentRef, eventId) {
|
|
3756
4154
|
return tenantStoresFor(controlPlane(tenant), root).egress.get(deviceId, agentRef, eventId);
|
|
3757
4155
|
},
|
|
@@ -4406,6 +4804,10 @@ var InMemoryTaskAttemptStore = class {
|
|
|
4406
4804
|
return "reserved";
|
|
4407
4805
|
});
|
|
4408
4806
|
}
|
|
4807
|
+
async readTaskAgentMessage(tenant, input) {
|
|
4808
|
+
const admission = this.#state.messageAdmissions.get(tenantKey(tenant, input.taskId));
|
|
4809
|
+
return admission?.deviceId === input.deviceId ? admission : void 0;
|
|
4810
|
+
}
|
|
4409
4811
|
async readAgentMessage(tenant, input) {
|
|
4410
4812
|
const admission = this.#state.messageAdmissions.get(tenantKey(tenant, input.taskId));
|
|
4411
4813
|
return admission !== void 0 && admission.deviceId === input.deviceId && admission.messageId === input.messageId && admission.payloadBody === input.payloadBody ? admission : void 0;
|
|
@@ -5065,6 +5467,28 @@ function authenticateHostedDeviceAssertion(input, deps) {
|
|
|
5065
5467
|
...deps.maxLifetimeMs === void 0 ? {} : { maxLifetimeMs: deps.maxLifetimeMs }
|
|
5066
5468
|
});
|
|
5067
5469
|
}
|
|
5470
|
+
function authenticateHostedTaskAssertion(input, deps) {
|
|
5471
|
+
return authenticateTaskAssertion(input, {
|
|
5472
|
+
verifier: {
|
|
5473
|
+
verify: ({ publicKey, signingInput, signature }) => deps.crypto.verifyEd25519(publicKey, signingInput, signature)
|
|
5474
|
+
},
|
|
5475
|
+
lookupDevice: async (deviceId) => {
|
|
5476
|
+
const row = await deps.devices.resolveByDeviceId(deviceId);
|
|
5477
|
+
if (row === void 0) return void 0;
|
|
5478
|
+
return {
|
|
5479
|
+
tenantId: row.tenantId,
|
|
5480
|
+
productId: row.productId,
|
|
5481
|
+
deviceId: row.deviceId,
|
|
5482
|
+
publicKeyJwkX: row.devicePublicKey,
|
|
5483
|
+
revoked: row.revoked
|
|
5484
|
+
};
|
|
5485
|
+
},
|
|
5486
|
+
replay: deps.replay,
|
|
5487
|
+
expected: deps.expected,
|
|
5488
|
+
now: deps.clock.now(),
|
|
5489
|
+
...deps.maxLifetimeMs === void 0 ? {} : { maxLifetimeMs: deps.maxLifetimeMs }
|
|
5490
|
+
});
|
|
5491
|
+
}
|
|
5068
5492
|
var BoardFeedItemSchema = z.object({
|
|
5069
5493
|
tenantId: z.string(),
|
|
5070
5494
|
itemId: z.string(),
|
|
@@ -5213,6 +5637,7 @@ var CLOUD_PORT_METHODS = {
|
|
|
5213
5637
|
"reserveAgentOffer",
|
|
5214
5638
|
"reserveAgentMessage",
|
|
5215
5639
|
"readAgentMessage",
|
|
5640
|
+
"readTaskAgentMessage",
|
|
5216
5641
|
"finalizeAgentMessage",
|
|
5217
5642
|
"get",
|
|
5218
5643
|
"getMany",
|
|
@@ -5247,6 +5672,6 @@ var CLOUD_PORT_INTERFACES = {
|
|
|
5247
5672
|
rateLimiter: "InboundRateLimiter"
|
|
5248
5673
|
};
|
|
5249
5674
|
|
|
5250
|
-
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 };
|
|
5675
|
+
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, RecurringExecutionInputSchema, 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, authenticateHostedTaskAssertion, createAuthPlane, createByokCloud, createHmacTokenSigner, createInMemoryBlobs, createInMemoryByokCloud, createInMemoryCloudStores, createWebCrypto, declares, extractBearerToken, fullCapabilityDeclaration, handleInboundEnvelope, inputPreparationCompletionKey, inputPreparationRequestKey, isCloudError, isTruthCommitError, parseApprovalObservations, parseTimelineEvents, pendingApproval, projectTerminalResult, projectTimelineEvents, readAgentHomeProjectionStatus, readInputPreparationStatus, recordAgentHomeProjectionCompletion, recordInputPreparationCompletion, routeKey, sameInputPreparationRequest, tenantStoresFor, terminalReceiptKey, truthManifestMetadata, truthRecordMetadata, validateActivityAppend, validateApprovalTimelineAppend, validateApprovalTimelineResolve, verifyNonceSignature };
|
|
5251
5676
|
//# sourceMappingURL=index.js.map
|
|
5252
5677
|
//# sourceMappingURL=index.js.map
|