@canonmsg/agent-sdk 13.0.3 → 13.2.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/README.md +11 -1
- package/dist/chunks/{chunk-ZPBOGOAD.js → chunk-5YKGDUJX.js} +271 -359
- package/dist/chunks/{chunk-XKUG2BQR.js → chunk-6FD6KFQN.js} +528 -167
- package/dist/cli.js +50 -6
- package/dist/index.d.ts +2 -1
- package/dist/index.js +6 -2
- package/dist/internal/core/client.d.ts +1 -215
- package/dist/internal/core/index.d.ts +0 -1
- package/dist/internal/endpoint/completions.d.ts +45 -0
- package/dist/internal/endpoint/engine.d.ts +15 -0
- package/dist/internal/endpoint/index.d.ts +1 -0
- package/dist/internal/endpoint/transient-publisher.d.ts +3 -2
- package/dist/media.js +1 -1
- package/dist/runner/errands.d.ts +2 -2
- package/dist/runner/runner.d.ts +34 -1
- package/dist/runner/wake.d.ts +16 -0
- package/package.json +1 -1
- package/dist/internal/core/completed-output-journal.d.ts +0 -34
|
@@ -111,9 +111,9 @@ import {
|
|
|
111
111
|
|
|
112
112
|
// src/media.ts
|
|
113
113
|
import { createWriteStream as createWriteStream2 } from "node:fs";
|
|
114
|
-
import { randomUUID as
|
|
114
|
+
import { randomUUID as randomUUID6 } from "node:crypto";
|
|
115
115
|
import { mkdir as mkdir2, open as open2, rename as rename2, stat as stat2, unlink as unlink2 } from "node:fs/promises";
|
|
116
|
-
import { basename, dirname as dirname2, extname, join as
|
|
116
|
+
import { basename, dirname as dirname2, extname, join as join4 } from "node:path";
|
|
117
117
|
import { Readable, Transform as Transform2 } from "node:stream";
|
|
118
118
|
import { pipeline as pipeline2 } from "node:stream/promises";
|
|
119
119
|
|
|
@@ -154,11 +154,11 @@ function createNodeEndpointMediaBytes(options) {
|
|
|
154
154
|
__name(digest, "digest");
|
|
155
155
|
return {
|
|
156
156
|
digest,
|
|
157
|
-
async retain(
|
|
157
|
+
async retain(key2, source) {
|
|
158
158
|
await initialized;
|
|
159
159
|
const expected = expectedDigests.get(source);
|
|
160
160
|
if (!expected) throw new Error("Canon media must be fingerprinted before retaining bytes");
|
|
161
|
-
const reference = `${createHash("sha256").update(
|
|
161
|
+
const reference = `${createHash("sha256").update(key2).digest("hex")}.bytes`;
|
|
162
162
|
const destination = referencePath(reference);
|
|
163
163
|
const temporary = `${destination}.${randomUUID()}.tmp`;
|
|
164
164
|
const sourceHandle = await open(source, "r");
|
|
@@ -522,8 +522,8 @@ async function readPreparationSnapshot(tx, scope5) {
|
|
|
522
522
|
__name(readPreparationSnapshot, "readPreparationSnapshot");
|
|
523
523
|
function assertPreparationAdmission(expected, current) {
|
|
524
524
|
if (!expected || !current.admission) return;
|
|
525
|
-
for (const
|
|
526
|
-
if (expected[
|
|
525
|
+
for (const key2 of ["conversationId", "membershipRevision", "admissionId"]) {
|
|
526
|
+
if (expected[key2] !== void 0 && current.admission[key2] !== void 0 && expected[key2] !== current.admission[key2]) {
|
|
527
527
|
throw new EndpointError("The authored admission changed before content could commit", { code: "PREPARATION_ADMISSION_CHANGED", retryable: false });
|
|
528
528
|
}
|
|
529
529
|
}
|
|
@@ -535,12 +535,12 @@ function assertGeneration(current, expected) {
|
|
|
535
535
|
__name(assertGeneration, "assertGeneration");
|
|
536
536
|
async function reservePreparedIdentityInTransaction(tx, input) {
|
|
537
537
|
assertGeneration(await tx.get(PREPARATION_GENERATION_KEY) ?? 0, input.installationGeneration);
|
|
538
|
-
const
|
|
539
|
-
const existing = await tx.get(
|
|
538
|
+
const key2 = `preparation/${input.operationId}`;
|
|
539
|
+
const existing = await tx.get(key2);
|
|
540
540
|
if (existing && (existing.action !== input.action || existing.fingerprint !== input.fingerprint || existing.installationGeneration !== input.installationGeneration)) {
|
|
541
541
|
throw new EndpointError("Operation ID was already reserved for different content", { status: 409, code: "OPERATION_ID_COLLISION", retryable: false, operationId: input.operationId });
|
|
542
542
|
}
|
|
543
|
-
if (!existing) await tx.put(
|
|
543
|
+
if (!existing) await tx.put(key2, input);
|
|
544
544
|
}
|
|
545
545
|
__name(reservePreparedIdentityInTransaction, "reservePreparedIdentityInTransaction");
|
|
546
546
|
async function reservePreparedIdentity(store, input) {
|
|
@@ -597,10 +597,10 @@ function endpointIntentOrdering(intent2) {
|
|
|
597
597
|
}
|
|
598
598
|
__name(endpointIntentOrdering, "endpointIntentOrdering");
|
|
599
599
|
async function nextQueueSequence(tx, name) {
|
|
600
|
-
const
|
|
601
|
-
const next = (await tx.get(
|
|
600
|
+
const key2 = `queue-sequence/${name}`;
|
|
601
|
+
const next = (await tx.get(key2) ?? 0) + 1;
|
|
602
602
|
if (!Number.isSafeInteger(next)) throw new Error("Endpoint queue sequence exhausted");
|
|
603
|
-
await tx.put(
|
|
603
|
+
await tx.put(key2, next);
|
|
604
604
|
return next;
|
|
605
605
|
}
|
|
606
606
|
__name(nextQueueSequence, "nextQueueSequence");
|
|
@@ -621,7 +621,7 @@ var outboundPrefix = /* @__PURE__ */ __name((operation) => operationOrderingKey(
|
|
|
621
621
|
var outboundKeys = /* @__PURE__ */ __name((operation) => [.../* @__PURE__ */ new Set([operationOrderingKey(operation), controlOrderingKey(operation)])].filter((prefix3) => Boolean(prefix3)).map((prefix3) => `${prefix3}${ordinal(operation.queueOrder)}/${operation.operationId}`), "outboundKeys");
|
|
622
622
|
async function trackOperationOrder(tx, operation) {
|
|
623
623
|
if (operation.queueOrder !== void 0 && !["succeeded", "rejected"].includes(operation.status)) {
|
|
624
|
-
for (const
|
|
624
|
+
for (const key2 of outboundKeys(operation)) await tx.put(key2, { operationId: operation.operationId });
|
|
625
625
|
}
|
|
626
626
|
}
|
|
627
627
|
__name(trackOperationOrder, "trackOperationOrder");
|
|
@@ -634,7 +634,7 @@ async function removeOperationOutbox(tx, operation) {
|
|
|
634
634
|
}
|
|
635
635
|
__name(removeOperationOutbox, "removeOperationOutbox");
|
|
636
636
|
async function settleOperationOrder(tx, operation) {
|
|
637
|
-
if (operation.queueOrder !== void 0) for (const
|
|
637
|
+
if (operation.queueOrder !== void 0) for (const key2 of outboundKeys(operation)) await tx.delete(key2);
|
|
638
638
|
}
|
|
639
639
|
__name(settleOperationOrder, "settleOperationOrder");
|
|
640
640
|
async function isFirstOrderedOperation(tx, operation) {
|
|
@@ -913,9 +913,9 @@ async function compactExpiredPayloads(store, clock) {
|
|
|
913
913
|
const rows = await tx.scan("payload-expiry/", { limit: 100 });
|
|
914
914
|
const expired = rows.filter((row) => row.value.expiresAt <= clock);
|
|
915
915
|
for (const row of expired) {
|
|
916
|
-
const
|
|
917
|
-
const value = await tx.get(
|
|
918
|
-
if (value && ["succeeded", "rejected", "uncertain"].includes(value.status)) await tx.put(
|
|
916
|
+
const key2 = `operation/${row.value.operationId}`;
|
|
917
|
+
const value = await tx.get(key2);
|
|
918
|
+
if (value && ["succeeded", "rejected", "uncertain"].includes(value.status)) await tx.put(key2, compactOperation(value));
|
|
919
919
|
await tx.delete(row.key);
|
|
920
920
|
}
|
|
921
921
|
return expired.length;
|
|
@@ -958,7 +958,7 @@ async function eraseEndpointPlaintext(store) {
|
|
|
958
958
|
after = rows[rows.length - 1].key;
|
|
959
959
|
}
|
|
960
960
|
}
|
|
961
|
-
for (const prefix3 of ["preparation/", "protocol-state/", "mls-principal", "mls-dev-device/", "mls-dev-key-package/", "mls-dev-group/", "deferred-opener/", "artifact-v2/", "artifact/", "artifact-intent/", "artifact-admission/", "artifact-key/", "conversation/", "inbound-ready/", "outbox/", "payload-expiry/", "media-flow/", "media-visible/", "media-pending/", "media-lease/", "media-prepared-upload/"]) {
|
|
961
|
+
for (const prefix3 of ["completion/", "preparation/", "protocol-state/", "mls-principal", "mls-dev-device/", "mls-dev-key-package/", "mls-dev-group/", "deferred-opener/", "artifact-v2/", "artifact/", "artifact-intent/", "artifact-admission/", "artifact-key/", "conversation/", "inbound-ready/", "outbox/", "payload-expiry/", "media-flow/", "media-visible/", "media-pending/", "media-lease/", "media-prepared-upload/"]) {
|
|
962
962
|
for (; ; ) {
|
|
963
963
|
const count = await store.transaction(async (tx) => {
|
|
964
964
|
const rows = await tx.scan(prefix3, { limit: 100 });
|
|
@@ -1052,11 +1052,11 @@ function createEndpointQueryObserver(options) {
|
|
|
1052
1052
|
return {
|
|
1053
1053
|
observe(query, parameters, listener, onError) {
|
|
1054
1054
|
if (closed) throw new Error("Endpoint query observer is closed");
|
|
1055
|
-
const
|
|
1056
|
-
let entry = entries2.get(
|
|
1055
|
+
const key2 = canonicalEndpointJson({ query, parameters });
|
|
1056
|
+
let entry = entries2.get(key2);
|
|
1057
1057
|
if (!entry) {
|
|
1058
1058
|
entry = { query, parameters, listeners: /* @__PURE__ */ new Set(), running: false, dirty: false, hasValue: false, hadError: false, retryAttempts: 0 };
|
|
1059
|
-
entries2.set(
|
|
1059
|
+
entries2.set(key2, entry);
|
|
1060
1060
|
}
|
|
1061
1061
|
const observer = { value: listener, error: onError };
|
|
1062
1062
|
entry.listeners.add(observer);
|
|
@@ -1068,7 +1068,7 @@ function createEndpointQueryObserver(options) {
|
|
|
1068
1068
|
ownedEntry.listeners.delete(observer);
|
|
1069
1069
|
if (ownedEntry.listeners.size === 0) {
|
|
1070
1070
|
if (ownedEntry.timer) clearTimeout(ownedEntry.timer);
|
|
1071
|
-
entries2.delete(
|
|
1071
|
+
entries2.delete(key2);
|
|
1072
1072
|
}
|
|
1073
1073
|
};
|
|
1074
1074
|
},
|
|
@@ -1224,7 +1224,7 @@ async function assertMlsDevPlaintextAllowed(tx, intent2, lane = "operation.reque
|
|
|
1224
1224
|
const privateLeaf = /* @__PURE__ */ __name((value, path) => {
|
|
1225
1225
|
if (value && typeof value === "object") {
|
|
1226
1226
|
const entries2 = Object.entries(value);
|
|
1227
|
-
if (entries2.length) return entries2.some(([
|
|
1227
|
+
if (entries2.length) return entries2.some(([key2, child]) => privateLeaf(child, [...path, key2]));
|
|
1228
1228
|
}
|
|
1229
1229
|
return boundary.classify(path) !== "public-metadata";
|
|
1230
1230
|
}, "privateLeaf");
|
|
@@ -1263,8 +1263,8 @@ function normalizeInboundDeletion(event) {
|
|
|
1263
1263
|
__name(normalizeInboundDeletion, "normalizeInboundDeletion");
|
|
1264
1264
|
async function recordInboundEvent(tx, incoming, timestamp) {
|
|
1265
1265
|
const event = normalizeInboundDeletion(incoming);
|
|
1266
|
-
const
|
|
1267
|
-
const existing = await tx.get(
|
|
1266
|
+
const key2 = `inbound/${event.id}`;
|
|
1267
|
+
const existing = await tx.get(key2);
|
|
1268
1268
|
if (event.conversationId && ["message.created", "message.snapshot", "message.updated"].includes(event.kind) && await isMlsDevRoomProtected(tx, event.conversationId)) {
|
|
1269
1269
|
if (existing) return false;
|
|
1270
1270
|
const raw = event.kind === "message.snapshot" ? event.data : event.data.message;
|
|
@@ -1278,7 +1278,7 @@ async function recordInboundEvent(tx, incoming, timestamp) {
|
|
|
1278
1278
|
durable: true,
|
|
1279
1279
|
data: notice ? { id: message.id, metadata, text: formatMembershipChangeNotice(metadata), contentType: "text" } : {}
|
|
1280
1280
|
};
|
|
1281
|
-
await tx.put(
|
|
1281
|
+
await tx.put(key2, {
|
|
1282
1282
|
event: sanitized,
|
|
1283
1283
|
state: "settled",
|
|
1284
1284
|
reason: notice ? "membership_control" : "mls_plaintext_withheld",
|
|
@@ -1312,12 +1312,12 @@ async function recordInboundEvent(tx, incoming, timestamp) {
|
|
|
1312
1312
|
};
|
|
1313
1313
|
delete next.lease;
|
|
1314
1314
|
if (existing) await removeInboundReady(tx, existing);
|
|
1315
|
-
await tx.put(
|
|
1315
|
+
await tx.put(key2, next);
|
|
1316
1316
|
return true;
|
|
1317
1317
|
}
|
|
1318
1318
|
if (existing) return false;
|
|
1319
1319
|
const record6 = await orderInbound(tx, { event: copyJson(event), state: "observed", createdAt: timestamp, updatedAt: timestamp });
|
|
1320
|
-
await tx.put(
|
|
1320
|
+
await tx.put(key2, record6);
|
|
1321
1321
|
await enqueueInbound(tx, record6);
|
|
1322
1322
|
return true;
|
|
1323
1323
|
}
|
|
@@ -1340,14 +1340,14 @@ function serializeInboundPreparation(store, scope5, prepare) {
|
|
|
1340
1340
|
}
|
|
1341
1341
|
__name(serializeInboundPreparation, "serializeInboundPreparation");
|
|
1342
1342
|
async function recordMlsInteractionProjection(tx, event, timestamp) {
|
|
1343
|
-
const
|
|
1343
|
+
const key2 = `inbound/${event.id}`, existing = await tx.get(key2);
|
|
1344
1344
|
if (existing && existing.reason !== "mls_plaintext_withheld") return;
|
|
1345
|
-
await tx.put(
|
|
1345
|
+
await tx.put(key2, { event: copyJson(event), state: "settled", reason: "mls_interaction", createdAt: timestamp, updatedAt: timestamp });
|
|
1346
1346
|
}
|
|
1347
1347
|
__name(recordMlsInteractionProjection, "recordMlsInteractionProjection");
|
|
1348
1348
|
async function recordVerifiedMlsInboundEvent(tx, event, feedOrdinal, timestamp, own = false) {
|
|
1349
1349
|
if (!event.conversationId || !Number.isSafeInteger(feedOrdinal) || feedOrdinal < 1) throw new Error("Invalid verified MLS input");
|
|
1350
|
-
const
|
|
1350
|
+
const key2 = `inbound/${event.id}`, existing = await tx.get(key2);
|
|
1351
1351
|
if (existing && existing.reason !== "mls_plaintext_withheld") return false;
|
|
1352
1352
|
const record6 = {
|
|
1353
1353
|
event: copyJson(event),
|
|
@@ -1356,7 +1356,7 @@ async function recordVerifiedMlsInboundEvent(tx, event, feedOrdinal, timestamp,
|
|
|
1356
1356
|
createdAt: timestamp,
|
|
1357
1357
|
updatedAt: timestamp
|
|
1358
1358
|
};
|
|
1359
|
-
await tx.put(
|
|
1359
|
+
await tx.put(key2, record6);
|
|
1360
1360
|
if (!own) await enqueueInbound(tx, record6);
|
|
1361
1361
|
return true;
|
|
1362
1362
|
}
|
|
@@ -1563,8 +1563,8 @@ function createConversationSynchronizer(input) {
|
|
|
1563
1563
|
if (protocolPage) return protocolPage;
|
|
1564
1564
|
const protectedMode = await input.prepareProtocol?.(conversationId) ?? false;
|
|
1565
1565
|
assertOpen();
|
|
1566
|
-
const
|
|
1567
|
-
const before = await input.store.transaction((tx) => tx.get(
|
|
1566
|
+
const key2 = `cursor/${conversationId}`;
|
|
1567
|
+
const before = await input.store.transaction((tx) => tx.get(key2));
|
|
1568
1568
|
const resuming = before?.paused !== void 0 && options.resumeAdmissionChange === "checkpoint";
|
|
1569
1569
|
if (before?.paused && !resuming) throw new EndpointError("Conversation admission changed; pickup is paused until an explicit new checkpoint is chosen", { code: "SYNC_ADMISSION_PAUSED", retryable: false });
|
|
1570
1570
|
if (!before && !options.bootstrap) throw new EndpointError("Choose a reconciled history baseline before receiving conversation work", { code: "SYNC_BASELINE_REQUIRED", retryable: false });
|
|
@@ -1608,11 +1608,11 @@ function createConversationSynchronizer(input) {
|
|
|
1608
1608
|
commit: /* @__PURE__ */ __name(async (tx, preparedChanges) => {
|
|
1609
1609
|
assertOpen();
|
|
1610
1610
|
if (await tx.get("migration/active")) throw new EndpointError("Migration started during synchronization", { code: "MIGRATION_IN_PROGRESS", retryable: false });
|
|
1611
|
-
const current = await tx.get(
|
|
1611
|
+
const current = await tx.get(key2);
|
|
1612
1612
|
if (canonicalEndpointJson(current ?? null) !== canonicalEndpointJson(before ?? null)) return false;
|
|
1613
1613
|
if (page.reset) {
|
|
1614
|
-
if (options.pauseOnAdmissionChange) await tx.put(
|
|
1615
|
-
else await tx.delete(
|
|
1614
|
+
if (options.pauseOnAdmissionChange) await tx.put(key2, { ...before ?? { cursor: null, admissionId: null, mode, bootstrapping: false }, paused: { admissionId: page.admissionId } });
|
|
1615
|
+
else await tx.delete(key2);
|
|
1616
1616
|
return true;
|
|
1617
1617
|
}
|
|
1618
1618
|
for (const event of preparedChanges) {
|
|
@@ -1620,7 +1620,7 @@ function createConversationSynchronizer(input) {
|
|
|
1620
1620
|
if (mode === "checkpoint") continue;
|
|
1621
1621
|
await recordInboundEvent(tx, event, new Date(input.clock()).toISOString());
|
|
1622
1622
|
}
|
|
1623
|
-
await tx.put(
|
|
1623
|
+
await tx.put(key2, { cursor: page.cursor, admissionId: page.admissionId, bootstrapping: page.bootstrap && page.hasMore, mode });
|
|
1624
1624
|
return true;
|
|
1625
1625
|
}, "commit")
|
|
1626
1626
|
}));
|
|
@@ -3184,6 +3184,90 @@ function createInboundHandoffs(input) {
|
|
|
3184
3184
|
}
|
|
3185
3185
|
__name(createInboundHandoffs, "createInboundHandoffs");
|
|
3186
3186
|
|
|
3187
|
+
// ../endpoint/src/completions.ts
|
|
3188
|
+
var PREFIX = "completion/";
|
|
3189
|
+
var key = /* @__PURE__ */ __name((id2) => `${PREFIX}${encodeURIComponent(id2)}`, "key");
|
|
3190
|
+
function createCompletions(input) {
|
|
3191
|
+
function validate(record6) {
|
|
3192
|
+
if (record6?.version !== 1 || typeof record6.id !== "string" || !record6.id || record6.id.length > 600 || typeof record6.conversationId !== "string" || !record6.conversationId || typeof record6.createdAt !== "string" || !Array.isArray(record6.inputIds) || record6.inputIds.some((id2) => typeof id2 !== "string" || !id2) || record6.output === void 0) {
|
|
3193
|
+
throw new EndpointError("Invalid completion", { code: "INVALID_COMPLETION", retryable: false });
|
|
3194
|
+
}
|
|
3195
|
+
}
|
|
3196
|
+
__name(validate, "validate");
|
|
3197
|
+
async function put(tx, record6) {
|
|
3198
|
+
const existing = await tx.get(key(record6.id));
|
|
3199
|
+
if (existing && canonicalEndpointJson(existing) !== canonicalEndpointJson(record6)) {
|
|
3200
|
+
throw new EndpointError("A completion cannot be replaced", { code: "COMPLETION_CONFLICT", retryable: false });
|
|
3201
|
+
}
|
|
3202
|
+
if (!existing) await tx.put(key(record6.id), record6);
|
|
3203
|
+
}
|
|
3204
|
+
__name(put, "put");
|
|
3205
|
+
async function all(tx) {
|
|
3206
|
+
const found = [];
|
|
3207
|
+
for (let after; ; ) {
|
|
3208
|
+
const batch = await tx.scan(PREFIX, { ...after ? { after } : {}, limit: 100 });
|
|
3209
|
+
found.push(...batch.map((entry) => entry.value));
|
|
3210
|
+
if (batch.length < 100) return found;
|
|
3211
|
+
after = batch.at(-1).key;
|
|
3212
|
+
}
|
|
3213
|
+
}
|
|
3214
|
+
__name(all, "all");
|
|
3215
|
+
const transaction = /* @__PURE__ */ __name(async (work) => {
|
|
3216
|
+
input.assertOpen();
|
|
3217
|
+
await input.ready();
|
|
3218
|
+
return input.store.transaction(async (tx) => {
|
|
3219
|
+
await input.assertCurrent(tx);
|
|
3220
|
+
return work(tx);
|
|
3221
|
+
});
|
|
3222
|
+
}, "transaction");
|
|
3223
|
+
return {
|
|
3224
|
+
async save(record6) {
|
|
3225
|
+
const frozen = copyJson(record6);
|
|
3226
|
+
validate(frozen);
|
|
3227
|
+
await transaction((tx) => put(tx, frozen));
|
|
3228
|
+
},
|
|
3229
|
+
async get(id2) {
|
|
3230
|
+
input.assertOpen();
|
|
3231
|
+
await input.ready();
|
|
3232
|
+
return input.store.transaction((tx) => tx.get(key(id2)));
|
|
3233
|
+
},
|
|
3234
|
+
/** Oldest first. */
|
|
3235
|
+
async list() {
|
|
3236
|
+
input.assertOpen();
|
|
3237
|
+
await input.ready();
|
|
3238
|
+
const found = await input.store.transaction(all);
|
|
3239
|
+
return found.sort((a, b) => a.createdAt.localeCompare(b.createdAt) || a.id.localeCompare(b.id));
|
|
3240
|
+
},
|
|
3241
|
+
/** One record for another, in one step: a Stop's cut keeps only what was already queued. */
|
|
3242
|
+
async replace(previousId, next) {
|
|
3243
|
+
const frozen = copyJson(next);
|
|
3244
|
+
validate(frozen);
|
|
3245
|
+
await transaction(async (tx) => {
|
|
3246
|
+
await put(tx, frozen);
|
|
3247
|
+
if (previousId !== frozen.id) await tx.delete(key(previousId));
|
|
3248
|
+
});
|
|
3249
|
+
},
|
|
3250
|
+
/**
|
|
3251
|
+
* The output is durable or withdrawn: its record goes, and in the same step `settle`'s inputs settle. An input already
|
|
3252
|
+
* settled, or no longer in the inbox, is left as it is.
|
|
3253
|
+
*/
|
|
3254
|
+
async finish(id2, settle) {
|
|
3255
|
+
await transaction(async (tx) => {
|
|
3256
|
+
for (const inputId of new Set(settle?.inputIds ?? [])) {
|
|
3257
|
+
const current = await tx.get(`inbound/${inputId}`);
|
|
3258
|
+
if (current && current.state !== "settled") await input.settleInbound(tx, inputId, settle.reason);
|
|
3259
|
+
}
|
|
3260
|
+
await tx.delete(key(id2));
|
|
3261
|
+
});
|
|
3262
|
+
},
|
|
3263
|
+
/** The input ids every completion names, read inside the caller's transaction. */
|
|
3264
|
+
async answered(tx) {
|
|
3265
|
+
return new Set((await all(tx)).flatMap((record6) => record6.inputIds));
|
|
3266
|
+
}
|
|
3267
|
+
};
|
|
3268
|
+
}
|
|
3269
|
+
__name(createCompletions, "createCompletions");
|
|
3270
|
+
|
|
3187
3271
|
// ../endpoint/src/external-inbound.ts
|
|
3188
3272
|
function createExternalInboundClaims(input) {
|
|
3189
3273
|
function validate(id2, requestId) {
|
|
@@ -3365,12 +3449,12 @@ function record(value, required, optional = []) {
|
|
|
3365
3449
|
if (!value || typeof value !== "object" || Array.isArray(value) || ![Object.prototype, null].includes(Object.getPrototypeOf(value))) fail();
|
|
3366
3450
|
const descriptors = Object.getOwnPropertyDescriptors(value);
|
|
3367
3451
|
const keys = Reflect.ownKeys(descriptors);
|
|
3368
|
-
if (keys.some((
|
|
3452
|
+
if (keys.some((key2) => typeof key2 !== "string" || ![...required, ...optional].includes(key2)) || required.some((key2) => !Object.hasOwn(descriptors, key2))) fail();
|
|
3369
3453
|
const result = {};
|
|
3370
|
-
for (const
|
|
3371
|
-
const descriptor = descriptors[
|
|
3454
|
+
for (const key2 of keys) {
|
|
3455
|
+
const descriptor = descriptors[key2];
|
|
3372
3456
|
if (!Object.hasOwn(descriptor, "value") || !descriptor.enumerable) fail();
|
|
3373
|
-
result[
|
|
3457
|
+
result[key2] = descriptor.value;
|
|
3374
3458
|
}
|
|
3375
3459
|
return result;
|
|
3376
3460
|
}
|
|
@@ -3409,8 +3493,8 @@ function createMlsDevDeviceCoordinator(input) {
|
|
|
3409
3493
|
installationId: binding.installationId,
|
|
3410
3494
|
...binding.principal.type === "ai_agent" ? { ownershipVersion: binding.principal.ownershipVersion } : {}
|
|
3411
3495
|
});
|
|
3412
|
-
function boundDevice(device,
|
|
3413
|
-
if (device.environmentId !== binding.environmentId || !same(device.principal, binding.principal) || device.installationId !== binding.installationId || device.signaturePublicKey !==
|
|
3496
|
+
function boundDevice(device, key2) {
|
|
3497
|
+
if (device.environmentId !== binding.environmentId || !same(device.principal, binding.principal) || device.installationId !== binding.installationId || device.signaturePublicKey !== key2) fail("MLS_DEV_DEVICE_BINDING_MISMATCH");
|
|
3414
3498
|
}
|
|
3415
3499
|
__name(boundDevice, "boundDevice");
|
|
3416
3500
|
async function load(tx, snapshot) {
|
|
@@ -3781,7 +3865,7 @@ __name(same2, "same");
|
|
|
3781
3865
|
function parseFlow(value) {
|
|
3782
3866
|
if (!value || typeof value !== "object" || Array.isArray(value)) fail2();
|
|
3783
3867
|
const raw = value;
|
|
3784
|
-
if (Object.keys(raw).some((
|
|
3868
|
+
if (Object.keys(raw).some((key2) => !["version", "id", "goal", "phase", "begin", "challenge"].includes(key2)) || raw.version !== 1 || typeof raw.id !== "string" || !/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/.test(raw.id)) fail2();
|
|
3785
3869
|
const head = { version: 1, id: raw.id };
|
|
3786
3870
|
if (raw.goal === "enroll" && raw.phase === "enrollment") return {
|
|
3787
3871
|
...head,
|
|
@@ -4217,8 +4301,8 @@ __name(fail3, "fail");
|
|
|
4217
4301
|
function record2(value, keys) {
|
|
4218
4302
|
if (!value || typeof value !== "object" || Array.isArray(value) || ![Object.prototype, null].includes(Object.getPrototypeOf(value))) fail3();
|
|
4219
4303
|
const fields = Object.getOwnPropertyDescriptors(value);
|
|
4220
|
-
if (Reflect.ownKeys(fields).length !== keys.length || keys.some((
|
|
4221
|
-
return Object.fromEntries(keys.map((
|
|
4304
|
+
if (Reflect.ownKeys(fields).length !== keys.length || keys.some((key2) => !Object.hasOwn(fields, key2) || !Object.hasOwn(fields[key2], "value") || !fields[key2].enumerable)) fail3();
|
|
4305
|
+
return Object.fromEntries(keys.map((key2) => [key2, fields[key2].value]));
|
|
4222
4306
|
}
|
|
4223
4307
|
__name(record2, "record");
|
|
4224
4308
|
function createMlsDevKeyPackageService(options, context, store, initialized, readPrepared) {
|
|
@@ -4242,9 +4326,9 @@ function createMlsDevKeyPackageService(options, context, store, initialized, rea
|
|
|
4242
4326
|
async function exchange(id2, online, op) {
|
|
4243
4327
|
const { binding, device } = await initialized();
|
|
4244
4328
|
if (online) network();
|
|
4245
|
-
const
|
|
4329
|
+
const key2 = op.prefix + id2;
|
|
4246
4330
|
const captured = await store.transaction(async (tx) => {
|
|
4247
|
-
const current = await device.captureKeyPackagePrerequisite(tx), raw = await tx.get(
|
|
4331
|
+
const current = await device.captureKeyPackagePrerequisite(tx), raw = await tx.get(key2);
|
|
4248
4332
|
if (!online && raw === void 0) return { current, raw, request: void 0, previous: void 0 };
|
|
4249
4333
|
const request = await op.make(tx, binding, current);
|
|
4250
4334
|
const previous = raw === void 0 ? void 0 : parseJournal(raw, binding, current, op);
|
|
@@ -4266,7 +4350,7 @@ function createMlsDevKeyPackageService(options, context, store, initialized, rea
|
|
|
4266
4350
|
if (current.fingerprint !== captured.current.fingerprint) fail3("MLS_DEV_KEY_PACKAGE_DEVICE_CHANGED", true);
|
|
4267
4351
|
const claims = parseMlsDevCredentialToken(current.credential.credential).unsigned.claims, now = context.clock();
|
|
4268
4352
|
if (!Number.isSafeInteger(now) || now < claims.issuedAtMs || now >= claims.expiresAtMs) fail3("MLS_DEV_KEY_PACKAGE_EXPIRED");
|
|
4269
|
-
if (!same3(await tx.get(
|
|
4353
|
+
if (!same3(await tx.get(key2) ?? null, captured.raw ?? null)) fail3("MLS_DEV_KEY_PACKAGE_STATE_CHANGED", true);
|
|
4270
4354
|
if (captured.raw === void 0 && (await tx.scan(op.prefix, { limit: 33 })).length >= 32) fail3("MLS_DEV_KEY_PACKAGE_INVENTORY_FULL");
|
|
4271
4355
|
const request = captured.request ?? fail3();
|
|
4272
4356
|
await op.validate(tx, binding, current, request, null);
|
|
@@ -4277,7 +4361,7 @@ function createMlsDevKeyPackageService(options, context, store, initialized, rea
|
|
|
4277
4361
|
request,
|
|
4278
4362
|
receipt: captured.previous?.receipt ?? null
|
|
4279
4363
|
};
|
|
4280
|
-
await tx.put(
|
|
4364
|
+
await tx.put(key2, value);
|
|
4281
4365
|
return value;
|
|
4282
4366
|
});
|
|
4283
4367
|
let receipt;
|
|
@@ -4287,7 +4371,7 @@ function createMlsDevKeyPackageService(options, context, store, initialized, rea
|
|
|
4287
4371
|
if (!(error instanceof EndpointError)) fail3("MLS_DEV_KEY_PACKAGE_REQUEST_FAILED", true);
|
|
4288
4372
|
if (op.forgetsRefusal && attempt.receipt === null && !error.retryable && error.status >= 400 && error.status < 500 && error.code === "HTTP_" + error.status) {
|
|
4289
4373
|
await store.transaction(async (tx) => {
|
|
4290
|
-
if (same3(await tx.get(
|
|
4374
|
+
if (same3(await tx.get(key2) ?? null, attempt)) await tx.delete(key2);
|
|
4291
4375
|
});
|
|
4292
4376
|
}
|
|
4293
4377
|
throw error;
|
|
@@ -4297,11 +4381,11 @@ function createMlsDevKeyPackageService(options, context, store, initialized, rea
|
|
|
4297
4381
|
return store.transaction(async (tx) => {
|
|
4298
4382
|
const current = await device.captureKeyPackagePrerequisite(tx);
|
|
4299
4383
|
if (current.fingerprint !== captured.current.fingerprint) fail3("MLS_DEV_KEY_PACKAGE_DEVICE_CHANGED", true);
|
|
4300
|
-
const stored = parseJournal(await tx.get(
|
|
4384
|
+
const stored = parseJournal(await tx.get(key2), binding, current, op);
|
|
4301
4385
|
if (!same3(stored.request, attempt.request)) fail3("MLS_DEV_KEY_PACKAGE_STATE_CHANGED", true);
|
|
4302
4386
|
await op.validate(tx, binding, current, attempt.request, receipt);
|
|
4303
4387
|
if (stored.receipt !== null && !same3(stored.receipt, receipt)) fail3("MLS_DEV_KEY_PACKAGE_RECEIPT_CONFLICT");
|
|
4304
|
-
await tx.put(
|
|
4388
|
+
await tx.put(key2, { ...stored, receipt });
|
|
4305
4389
|
return receipt;
|
|
4306
4390
|
});
|
|
4307
4391
|
}
|
|
@@ -4449,8 +4533,8 @@ var same4 = /* @__PURE__ */ __name((a, b) => canonicalEndpointJson(a) === canoni
|
|
|
4449
4533
|
function record3(value, keys) {
|
|
4450
4534
|
if (!value || typeof value !== "object" || Array.isArray(value) || ![Object.prototype, null].includes(Object.getPrototypeOf(value))) fail4();
|
|
4451
4535
|
const fields = Object.getOwnPropertyDescriptors(value);
|
|
4452
|
-
if (Reflect.ownKeys(fields).length !== keys.length || keys.some((
|
|
4453
|
-
return Object.fromEntries(keys.map((
|
|
4536
|
+
if (Reflect.ownKeys(fields).length !== keys.length || keys.some((key2) => !Object.hasOwn(fields, key2) || !Object.hasOwn(fields[key2], "value") || !fields[key2].enumerable)) fail4();
|
|
4537
|
+
return Object.fromEntries(keys.map((key2) => [key2, fields[key2].value]));
|
|
4454
4538
|
}
|
|
4455
4539
|
__name(record3, "record");
|
|
4456
4540
|
function boundPayload(command, payload) {
|
|
@@ -4948,28 +5032,28 @@ var erased = /* @__PURE__ */ __name((value) => {
|
|
|
4948
5032
|
async function deleteMlsDevTextMessage(tx, conversationId, messageId) {
|
|
4949
5033
|
const tombstone = mlsDevTextDeletedKey(conversationId, messageId), known = await tx.get(tombstone) !== void 0;
|
|
4950
5034
|
if (!known) await tx.put(tombstone, { version: 1 });
|
|
4951
|
-
const
|
|
5035
|
+
const key2 = mlsDevTextMessageKey(conversationId, messageId), value = await tx.get(key2);
|
|
4952
5036
|
if (value?.message.kind !== "text" && value?.message.kind !== "item" || value.deleted || value.feedOrdinal === null) return !known;
|
|
4953
|
-
await tx.put(
|
|
5037
|
+
await tx.put(key2, erased(value));
|
|
4954
5038
|
return true;
|
|
4955
5039
|
}
|
|
4956
5040
|
__name(deleteMlsDevTextMessage, "deleteMlsDevTextMessage");
|
|
4957
5041
|
async function setMlsDevTextReactions(tx, conversationId, messageId, reactions) {
|
|
4958
|
-
const
|
|
4959
|
-
const previous = await tx.get(
|
|
5042
|
+
const key2 = mlsDevTextReactionsKey(conversationId, messageId), next = reactions && Object.keys(reactions).length ? reactions : void 0;
|
|
5043
|
+
const previous = await tx.get(key2);
|
|
4960
5044
|
if (JSON.stringify(previous ?? null) === JSON.stringify(next ?? null)) return false;
|
|
4961
|
-
if (next) await tx.put(
|
|
4962
|
-
else await tx.delete(
|
|
5045
|
+
if (next) await tx.put(key2, next);
|
|
5046
|
+
else await tx.delete(key2);
|
|
4963
5047
|
return true;
|
|
4964
5048
|
}
|
|
4965
5049
|
__name(setMlsDevTextReactions, "setMlsDevTextReactions");
|
|
4966
5050
|
async function putMlsDevTextMessage(tx, value) {
|
|
4967
5051
|
if (await tx.get(mlsDevTextDeletedKey(value.message.conversationId, value.message.messageId)) !== void 0) value = erased(value);
|
|
4968
5052
|
const { conversationId, messageId } = value.message;
|
|
4969
|
-
const
|
|
4970
|
-
const old = await tx.get(
|
|
5053
|
+
const key2 = mlsDevTextMessageKey(conversationId, messageId);
|
|
5054
|
+
const old = await tx.get(key2);
|
|
4971
5055
|
if (old?.feedOrdinal !== null && old?.feedOrdinal !== void 0 && old.feedOrdinal !== value.feedOrdinal) fail5();
|
|
4972
|
-
await tx.put(
|
|
5056
|
+
await tx.put(key2, value);
|
|
4973
5057
|
if (value.message.kind !== "text" && value.message.kind !== "item" && value.message.kind !== "interaction.request") return;
|
|
4974
5058
|
if (value.feedOrdinal === null) await tx.put(`${pendingPrefix(conversationId)}${encoded2(messageId)}`, { messageId });
|
|
4975
5059
|
else {
|
|
@@ -5114,10 +5198,10 @@ async function queryMlsDevTextHistory(tx, conversationId, name, parameters, audi
|
|
|
5114
5198
|
__name(queryMlsDevTextHistory, "queryMlsDevTextHistory");
|
|
5115
5199
|
async function settleMlsDevTextOperation(tx, operation) {
|
|
5116
5200
|
if (operation.action !== "mls_application" || !operation.conversationId || !operation.messageId || !["rejected", "uncertain"].includes(operation.status)) return;
|
|
5117
|
-
const
|
|
5118
|
-
const value = await tx.get(
|
|
5201
|
+
const key2 = mlsDevTextMessageKey(operation.conversationId, operation.messageId);
|
|
5202
|
+
const value = await tx.get(key2);
|
|
5119
5203
|
if (value && value.message.operationId === operation.operationId && value.feedOrdinal === null) {
|
|
5120
|
-
await tx.put(
|
|
5204
|
+
await tx.put(key2, { ...value, status: operation.status });
|
|
5121
5205
|
}
|
|
5122
5206
|
}
|
|
5123
5207
|
__name(settleMlsDevTextOperation, "settleMlsDevTextOperation");
|
|
@@ -5135,13 +5219,13 @@ async function opensMlsAlias(environmentId, alias, opening, hash) {
|
|
|
5135
5219
|
return false;
|
|
5136
5220
|
}
|
|
5137
5221
|
if (!isMlsPrincipalAlias(alias)) return false;
|
|
5138
|
-
const
|
|
5139
|
-
let opens = checked.get(
|
|
5222
|
+
const key2 = checkKey(environmentId, alias, value);
|
|
5223
|
+
let opens = checked.get(key2);
|
|
5140
5224
|
if (opens === void 0) {
|
|
5141
5225
|
const digest = await hash(encodeMlsPrincipalAliasInput(environmentId, value));
|
|
5142
5226
|
opens = /^[0-9a-f]{64}$/.test(digest) && MLS_PRINCIPAL_ALIAS_PREFIX + encodeCanonicalMlsBase64Url(hexBytes(digest)) === alias;
|
|
5143
5227
|
if (checked.size >= 4096) checked.clear();
|
|
5144
|
-
checked.set(
|
|
5228
|
+
checked.set(key2, opens);
|
|
5145
5229
|
}
|
|
5146
5230
|
return opens;
|
|
5147
5231
|
}
|
|
@@ -5246,8 +5330,8 @@ __name(id, "id");
|
|
|
5246
5330
|
function record4(value, required, optional = []) {
|
|
5247
5331
|
if (!value || typeof value !== "object" || Array.isArray(value) || ![Object.prototype, null].includes(Object.getPrototypeOf(value))) fail6();
|
|
5248
5332
|
const descriptors = Object.getOwnPropertyDescriptors(value), fields = Reflect.ownKeys(descriptors);
|
|
5249
|
-
if (required.some((
|
|
5250
|
-
return Object.fromEntries(fields.map((
|
|
5333
|
+
if (required.some((key2) => !Object.hasOwn(descriptors, key2)) || fields.some((key2) => typeof key2 !== "string" || ![...required, ...optional].includes(key2) || !Object.hasOwn(descriptors[key2], "value") || !descriptors[key2].enumerable)) fail6();
|
|
5334
|
+
return Object.fromEntries(fields.map((key2) => [key2, descriptors[key2].value]));
|
|
5251
5335
|
}
|
|
5252
5336
|
__name(record4, "record");
|
|
5253
5337
|
function envelope(value, binding, conversationId) {
|
|
@@ -5386,7 +5470,7 @@ function createMlsDevGroups(options, context, keyPackages, bridge) {
|
|
|
5386
5470
|
"mlsRemoval",
|
|
5387
5471
|
"mlsIdentities",
|
|
5388
5472
|
"mlsInviteeId"
|
|
5389
|
-
].map((
|
|
5473
|
+
].map((key2) => [key2, conversation[key2] ?? null])) : null;
|
|
5390
5474
|
const cursor = await tx.get(`cursor/${conversationId}`);
|
|
5391
5475
|
const protocol = await tx.get(mlsDevTextProtocolKey(conversationId));
|
|
5392
5476
|
if (protocol !== void 0 && !isMlsDevTextProtocol(protocol) || current && current.protocol !== protocol) fail6("MLS_DEV_TEXT_PROTOCOL_UNSUPPORTED");
|
|
@@ -5782,13 +5866,13 @@ function createMlsDevGroups(options, context, keyPackages, bridge) {
|
|
|
5782
5866
|
const request = message.interaction, responder = request.responderIds[0];
|
|
5783
5867
|
if (sender.principal.type !== "ai_agent") return "MLS_DEV_INTERACTION_REQUESTER_INVALID";
|
|
5784
5868
|
if (responder === sender.principal.id || !current.envelope.roster.some((member) => member.device.principal.type === "human" && member.device.principal.id === responder)) return "MLS_DEV_INTERACTION_RESPONDER_UNAVAILABLE";
|
|
5785
|
-
const
|
|
5869
|
+
const key3 = mlsDevInteractionKey(cid, sender.principal.id, request.requestId), existing2 = await tx.get(key3);
|
|
5786
5870
|
if (existing2) {
|
|
5787
5871
|
const earlier = await tx.get(acceptedKey(cid, existing2.head.sequence));
|
|
5788
5872
|
const principals = /* @__PURE__ */ __name((roster) => JSON.stringify([...new Set(roster.map((member) => JSON.stringify([member.device.principal.type, member.device.principal.id, member.admissionId])))].sort()), "principals");
|
|
5789
5873
|
if (existing2.status !== "pending" || existing2.requesterDeviceId !== sender.deviceId || !same6(existing2.request, request) || existing2.membershipRevision !== wire.preconditions.membershipRevision || same6(existing2.head, wire.expectedHead) || earlier === void 0 || principals(parseMlsDevGroupAcceptedControl(earlier).control.roster) !== principals(current.envelope.roster)) return "MLS_DEV_INTERACTION_ID_CONFLICT";
|
|
5790
5874
|
}
|
|
5791
|
-
await tx.put(
|
|
5875
|
+
await tx.put(key3, {
|
|
5792
5876
|
version: 1,
|
|
5793
5877
|
conversationId: cid,
|
|
5794
5878
|
agentId: sender.principal.id,
|
|
@@ -5807,13 +5891,13 @@ function createMlsDevGroups(options, context, keyPackages, bridge) {
|
|
|
5807
5891
|
if (message.kind === "interaction.response") {
|
|
5808
5892
|
const response = message.interaction;
|
|
5809
5893
|
if (sender.principal.type !== "human") return "MLS_DEV_INTERACTION_RESPONDER_INVALID";
|
|
5810
|
-
const
|
|
5894
|
+
const key3 = mlsDevInteractionKey(cid, response.agentId, response.requestId), existing2 = await tx.get(key3);
|
|
5811
5895
|
if (!existing2) return "MLS_DEV_INTERACTION_UNKNOWN";
|
|
5812
5896
|
if (!existing2.request.responderIds.includes(sender.principal.id)) return "MLS_DEV_INTERACTION_RESPONDER_INVALID";
|
|
5813
5897
|
if (existing2.status !== "pending") return "MLS_DEV_INTERACTION_CLOSED";
|
|
5814
5898
|
if (existing2.requestOrdinal >= entry.ordinal || !same6(existing2.head, wire.expectedHead) || existing2.membershipRevision !== wire.preconditions.membershipRevision) return "MLS_DEV_INTERACTION_AUDIENCE_CHANGED";
|
|
5815
5899
|
if (!isMlsDevInteractionAnswerAllowed(existing2.request, response.answer)) return "MLS_DEV_INTERACTION_ANSWER_INVALID";
|
|
5816
|
-
await tx.put(
|
|
5900
|
+
await tx.put(key3, { ...existing2, status: "answered", response: {
|
|
5817
5901
|
responderId: sender.principal.id,
|
|
5818
5902
|
responderDeviceId: sender.deviceId,
|
|
5819
5903
|
messageId: wire.messageId,
|
|
@@ -5824,11 +5908,11 @@ function createMlsDevGroups(options, context, keyPackages, bridge) {
|
|
|
5824
5908
|
return void 0;
|
|
5825
5909
|
}
|
|
5826
5910
|
if (sender.principal.type !== "ai_agent") return "MLS_DEV_INTERACTION_REQUESTER_INVALID";
|
|
5827
|
-
const
|
|
5911
|
+
const key2 = mlsDevInteractionKey(cid, sender.principal.id, message.interaction.requestId), existing = await tx.get(key2);
|
|
5828
5912
|
if (!existing) return "MLS_DEV_INTERACTION_UNKNOWN";
|
|
5829
5913
|
if (existing.requesterDeviceId !== sender.deviceId) return "MLS_DEV_INTERACTION_REQUESTER_INVALID";
|
|
5830
5914
|
if (existing.status !== "pending") return "MLS_DEV_INTERACTION_CLOSED";
|
|
5831
|
-
await tx.put(
|
|
5915
|
+
await tx.put(key2, { ...existing, status: "withdrawn", withdrawal: {
|
|
5832
5916
|
reason: message.interaction.reason,
|
|
5833
5917
|
messageId: wire.messageId,
|
|
5834
5918
|
ordinal: entry.ordinal
|
|
@@ -5917,7 +6001,7 @@ function createMlsDevGroups(options, context, keyPackages, bridge) {
|
|
|
5917
6001
|
url: data.url,
|
|
5918
6002
|
mimeType: data.mimeType,
|
|
5919
6003
|
sizeBytes: data.sizeBytes,
|
|
5920
|
-
...Object.fromEntries(["fileName", "width", "height", "durationMs", "thumbnailUrl"].filter((
|
|
6004
|
+
...Object.fromEntries(["fileName", "width", "height", "durationMs", "thumbnailUrl"].filter((key2) => data[key2] !== void 0 && data[key2] !== null).map((key2) => [key2, data[key2]])),
|
|
5921
6005
|
...typeof caption === "string" && caption.trim() ? { caption } : {}
|
|
5922
6006
|
};
|
|
5923
6007
|
return validItem(raw);
|
|
@@ -5962,7 +6046,7 @@ function createMlsDevGroups(options, context, keyPackages, bridge) {
|
|
|
5962
6046
|
const attachments = data.attachments ?? [];
|
|
5963
6047
|
if (!Array.isArray(attachments) || attachments.length > 1) fail6(Array.isArray(attachments) ? "MLS_DEV_ONE_ITEM_PER_MESSAGE" : "MLS_DEV_TEXT_ONLY");
|
|
5964
6048
|
const item = attachments.length ? itemInput(attachments[0], data.text) : void 0;
|
|
5965
|
-
if (data.contentType !== void 0 && data.contentType !== (item ? item.type : "text") || !item && typeof data.text !== "string" || item && data.text !== void 0 && data.text !== null && typeof data.text !== "string" || data.mentions !== void 0 && (!Array.isArray(data.mentions) || data.mentions.some((mention) => typeof mention !== "string")) || data.replyTo !== void 0 && data.replyTo !== null && typeof data.replyTo !== "string" || ["replyToPosition", "metadata", "forwarded", "contactCardUserId"].some((
|
|
6049
|
+
if (data.contentType !== void 0 && data.contentType !== (item ? item.type : "text") || !item && typeof data.text !== "string" || item && data.text !== void 0 && data.text !== null && typeof data.text !== "string" || data.mentions !== void 0 && (!Array.isArray(data.mentions) || data.mentions.some((mention) => typeof mention !== "string")) || data.replyTo !== void 0 && data.replyTo !== null && typeof data.replyTo !== "string" || ["replyToPosition", "metadata", "forwarded", "contactCardUserId"].some((key2) => data[key2] !== void 0 && data[key2] !== null) || data.replyAuthority !== void 0 && (!!item || !data.replyAuthority || typeof data.replyAuthority !== "object" || Array.isArray(data.replyAuthority))) fail6("MLS_DEV_TEXT_ONLY");
|
|
5966
6050
|
const mentions = [...new Set(data.mentions ?? [])].map(id);
|
|
5967
6051
|
return {
|
|
5968
6052
|
conversationId: id(data.conversationId),
|
|
@@ -6150,14 +6234,14 @@ function createMlsDevGroups(options, context, keyPackages, bridge) {
|
|
|
6150
6234
|
...input.replyTo ? { replyTo: input.replyTo } : {},
|
|
6151
6235
|
...input.mentions ? { mentions: input.mentions } : {}
|
|
6152
6236
|
};
|
|
6153
|
-
const init = await initialized(),
|
|
6237
|
+
const init = await initialized(), key2 = mlsDevHeldTextKey(cid, messageId);
|
|
6154
6238
|
const kept = await store.transaction(async (tx) => {
|
|
6155
|
-
const existing = await tx.get(
|
|
6239
|
+
const existing = await tx.get(key2);
|
|
6156
6240
|
if (!existing) return await tx.get(`operation/${operationId}`) !== void 0 ? "made" : (await readMlsDevHeldTexts(tx, cid)).some((held) => !held.failed) ? "behind" : "none";
|
|
6157
6241
|
if (existing.operationId !== operationId || canonicalEndpointJson(existing.body) !== canonicalEndpointJson(body)) fail6("OPERATION_ID_COLLISION");
|
|
6158
6242
|
if (existing.failed) {
|
|
6159
6243
|
const { failed: _failed, ...again } = existing;
|
|
6160
|
-
await tx.put(
|
|
6244
|
+
await tx.put(key2, again);
|
|
6161
6245
|
}
|
|
6162
6246
|
return "kept";
|
|
6163
6247
|
});
|
|
@@ -6173,7 +6257,7 @@ function createMlsDevGroups(options, context, keyPackages, bridge) {
|
|
|
6173
6257
|
await store.transaction(async (tx) => {
|
|
6174
6258
|
const held = await readMlsDevHeldTexts(tx, cid);
|
|
6175
6259
|
if (held.length >= MLS_DEV_HELD_TEXT_MAX) fail6("MLS_DEV_HELD_TEXT_LIMIT");
|
|
6176
|
-
await tx.put(
|
|
6260
|
+
await tx.put(key2, {
|
|
6177
6261
|
version: 1,
|
|
6178
6262
|
body,
|
|
6179
6263
|
operationId,
|
|
@@ -6191,20 +6275,20 @@ function createMlsDevGroups(options, context, keyPackages, bridge) {
|
|
|
6191
6275
|
const sent = [], failed = [];
|
|
6192
6276
|
const kept = (await store.transaction((tx) => readMlsDevHeldTexts(tx, conversationId))).filter((held) => !held.failed);
|
|
6193
6277
|
for (const held of kept) {
|
|
6194
|
-
const
|
|
6278
|
+
const key2 = mlsDevHeldTextKey(conversationId, held.body.messageId);
|
|
6195
6279
|
try {
|
|
6196
6280
|
sent.push(await sendText(textInput(held.body), held.operationId));
|
|
6197
6281
|
} catch (error) {
|
|
6198
6282
|
if (error instanceof EndpointError && ["ENDPOINT_CLOSED", "PREPARATION_INSTALLATION_FENCED"].includes(error.code)) throw error;
|
|
6199
6283
|
if (!(error instanceof EndpointError) || error.retryable) return { sent, failed, waiting: true };
|
|
6200
6284
|
await store.transaction(async (tx) => {
|
|
6201
|
-
if (await tx.get(
|
|
6285
|
+
if (await tx.get(key2) !== void 0) await tx.put(key2, { ...held, failed: error.code });
|
|
6202
6286
|
});
|
|
6203
6287
|
failed.push({ operationId: held.operationId, error });
|
|
6204
6288
|
bridge.historyChanged?.(conversationId);
|
|
6205
6289
|
continue;
|
|
6206
6290
|
}
|
|
6207
|
-
await store.transaction((tx) => tx.delete(
|
|
6291
|
+
await store.transaction((tx) => tx.delete(key2));
|
|
6208
6292
|
}
|
|
6209
6293
|
return { sent, failed, waiting: false };
|
|
6210
6294
|
}
|
|
@@ -6452,7 +6536,7 @@ function createMlsDevGroups(options, context, keyPackages, bridge) {
|
|
|
6452
6536
|
}
|
|
6453
6537
|
__name(readInteraction, "readInteraction");
|
|
6454
6538
|
async function openSecretAnswer(conversationId, requestId) {
|
|
6455
|
-
const init = await initialized(),
|
|
6539
|
+
const init = await initialized(), key2 = mlsDevInteractionKey(conversationId, init.binding.principal.id, requestId);
|
|
6456
6540
|
if (principal.type !== "ai_agent") fail6("MLS_DEV_INTERACTION_REQUESTER_INVALID");
|
|
6457
6541
|
let captured, known, text;
|
|
6458
6542
|
await prepareAndCommitContent({
|
|
@@ -6463,7 +6547,7 @@ function createMlsDevGroups(options, context, keyPackages, bridge) {
|
|
|
6463
6547
|
const current = await capture(tx, state, init, conversationId), group = current.envelope;
|
|
6464
6548
|
if (!mlsDevProtocolCarriesCards(current.protocol) || !group?.head || group.removed) fail6("MLS_DEV_INTERACTION_PROTOCOL_REQUIRED");
|
|
6465
6549
|
if (group.pending) fail6("MLS_DEV_GROUP_PENDING_CONTROL", true);
|
|
6466
|
-
known = await tx.get(
|
|
6550
|
+
known = await tx.get(key2) ?? fail6("MLS_DEV_INTERACTION_UNKNOWN");
|
|
6467
6551
|
if (known.requesterDeviceId !== current.device.registration.device.deviceId || known.request.kind !== "secret") fail6("MLS_DEV_INTERACTION_REQUESTER_INVALID");
|
|
6468
6552
|
if (known.opened) fail6("MLS_DEV_INTERACTION_OPENED");
|
|
6469
6553
|
const view3 = viewMlsDevInteraction(known, context.clock(), void 0);
|
|
@@ -6486,8 +6570,8 @@ function createMlsDevGroups(options, context, keyPackages, bridge) {
|
|
|
6486
6570
|
}, "prepare"),
|
|
6487
6571
|
commit: /* @__PURE__ */ __name(async (tx, prepared, state) => {
|
|
6488
6572
|
const current = await capture(tx, state, init, conversationId);
|
|
6489
|
-
if (current.fingerprint !== prepared.fingerprint || !same6(await tx.get(
|
|
6490
|
-
await tx.put(
|
|
6573
|
+
if (current.fingerprint !== prepared.fingerprint || !same6(await tx.get(key2), prepared.opened)) fail6("MLS_DEV_GROUP_DEPENDENCY_CHANGED", true);
|
|
6574
|
+
await tx.put(key2, { ...prepared.opened, opened: true });
|
|
6491
6575
|
return true;
|
|
6492
6576
|
}, "commit")
|
|
6493
6577
|
});
|
|
@@ -7158,7 +7242,7 @@ function createEndpoint(options) {
|
|
|
7158
7242
|
let retryDue = Number.POSITIVE_INFINITY;
|
|
7159
7243
|
let negotiatedCapabilities = null;
|
|
7160
7244
|
const now = /* @__PURE__ */ __name(() => new Date(clock()).toISOString(), "now");
|
|
7161
|
-
const
|
|
7245
|
+
const key2 = /* @__PURE__ */ __name((operationId) => `operation/${operationId}`, "key");
|
|
7162
7246
|
function assertOpen() {
|
|
7163
7247
|
if (closed) throw new EndpointError("Endpoint is closed", { code: "ENDPOINT_CLOSED", retryable: false });
|
|
7164
7248
|
}
|
|
@@ -7736,9 +7820,9 @@ function createEndpoint(options) {
|
|
|
7736
7820
|
function rememberStubName(conversationId, stub) {
|
|
7737
7821
|
const name = typeof stub.senderName === "string" ? stub.senderName.trim() : "";
|
|
7738
7822
|
if (typeof stub.senderId !== "string" || !stub.senderId || !name || name === stub.senderId || name.length > 80) return;
|
|
7739
|
-
const
|
|
7740
|
-
stubNames.delete(
|
|
7741
|
-
stubNames.set(
|
|
7823
|
+
const key3 = stubKey(conversationId, stub.id);
|
|
7824
|
+
stubNames.delete(key3);
|
|
7825
|
+
stubNames.set(key3, { senderId: stub.senderId, name });
|
|
7742
7826
|
if (stubNames.size > 1e3) stubNames.delete(stubNames.keys().next().value);
|
|
7743
7827
|
}
|
|
7744
7828
|
__name(rememberStubName, "rememberStubName");
|
|
@@ -7752,13 +7836,13 @@ function createEndpoint(options) {
|
|
|
7752
7836
|
}
|
|
7753
7837
|
__name(withStubNames, "withStubNames");
|
|
7754
7838
|
function forgetStubReads(conversationId) {
|
|
7755
|
-
for (const
|
|
7839
|
+
for (const key3 of stubReads.keys()) if (key3.startsWith(`${conversationId}\0`)) stubReads.delete(key3);
|
|
7756
7840
|
}
|
|
7757
7841
|
__name(forgetStubReads, "forgetStubReads");
|
|
7758
7842
|
async function recoverMlsDevMessageActions(conversationId, messageIds) {
|
|
7759
7843
|
const at = clock(), due = [...new Set(messageIds)].filter((id2) => at >= (stubReads.get(stubKey(conversationId, id2)) ?? -Infinity) + 3e4).slice(0, 100);
|
|
7760
7844
|
if (!due.length) return false;
|
|
7761
|
-
for (const [
|
|
7845
|
+
for (const [key3, readAt] of stubReads) if (at >= readAt + 3e4) stubReads.delete(key3);
|
|
7762
7846
|
for (const id2 of due) stubReads.set(stubKey(conversationId, id2), at);
|
|
7763
7847
|
const beforeRead = liveActionCounter;
|
|
7764
7848
|
let stubs;
|
|
@@ -7929,6 +8013,13 @@ function createEndpoint(options) {
|
|
|
7929
8013
|
}, "assertReady")
|
|
7930
8014
|
});
|
|
7931
8015
|
const externalInbound = createExternalInboundClaims({ store, clock, hash, ownershipVersion: options.identity.ownershipVersion, ready: /* @__PURE__ */ __name(() => initialization, "ready"), assertOpen, assertCurrent: assertCurrentInstallation });
|
|
8016
|
+
const completions = createCompletions({
|
|
8017
|
+
store,
|
|
8018
|
+
ready: /* @__PURE__ */ __name(() => initialization, "ready"),
|
|
8019
|
+
assertOpen,
|
|
8020
|
+
assertCurrent: assertCurrentInstallation,
|
|
8021
|
+
settleInbound: /* @__PURE__ */ __name((tx, id2, reason) => writeInboundState(tx, id2, "settled", reason), "settleInbound")
|
|
8022
|
+
});
|
|
7932
8023
|
const messageGroups = createMessageGroupCoordinator({
|
|
7933
8024
|
store,
|
|
7934
8025
|
endpoint: /* @__PURE__ */ __name(() => endpoint, "endpoint"),
|
|
@@ -7939,6 +8030,26 @@ function createEndpoint(options) {
|
|
|
7939
8030
|
preparationGeneration: /* @__PURE__ */ __name(() => installationGeneration, "preparationGeneration"),
|
|
7940
8031
|
supportsPreparedContent: /* @__PURE__ */ __name(() => Promise.resolve(negotiatedCapabilities?.preparedContentVersion === 1), "supportsPreparedContent")
|
|
7941
8032
|
});
|
|
8033
|
+
async function writeInboundState(tx, id2, state, reason) {
|
|
8034
|
+
const recordKey = `inbound/${id2}`;
|
|
8035
|
+
const current = await tx.get(recordKey);
|
|
8036
|
+
if (!current) throw new EndpointError("Inbound operation was not durably received", { code: "INBOUND_NOT_FOUND", retryable: false });
|
|
8037
|
+
if (current.externalClaim) throw new EndpointError("Use the bound external claim to settle this input", { code: "EXTERNAL_INBOUND_CLAIM_REQUIRED", retryable: false });
|
|
8038
|
+
if (current.handoffGeneration && state !== "settled" && state !== "uncertain") throw new EndpointError("Use the input handoff to release or defer managed work", { code: "INBOUND_HANDOFF_REQUIRED", retryable: false });
|
|
8039
|
+
if (current.deleted && !["settled", "uncertain"].includes(state)) throw new EndpointError("Deleted input cannot be replayed", { code: "INBOUND_DELETED", retryable: false });
|
|
8040
|
+
if (current.state === "settled" && state !== "settled") throw new EndpointError("Settled input cannot be replayed", { code: "INBOUND_ALREADY_SETTLED", retryable: false });
|
|
8041
|
+
const next = { ...current, state, ...reason ? { reason } : {}, updatedAt: now() };
|
|
8042
|
+
delete next.lease;
|
|
8043
|
+
if (state === "settled") {
|
|
8044
|
+
delete next.handoffGeneration;
|
|
8045
|
+
delete next.handoffInstallationGeneration;
|
|
8046
|
+
}
|
|
8047
|
+
if (state === "settled") next.event = { ...next.event, data: {} };
|
|
8048
|
+
await tx.put(recordKey, next);
|
|
8049
|
+
if (state === "settled" || state === "uncertain" || current.deleted) await removeInboundReady(tx, current);
|
|
8050
|
+
else await enqueueInbound(tx, next);
|
|
8051
|
+
}
|
|
8052
|
+
__name(writeInboundState, "writeInboundState");
|
|
7942
8053
|
function deferredReceipt(operation, error) {
|
|
7943
8054
|
return operation.receipt ?? {
|
|
7944
8055
|
protocol: CANON_ENDPOINT_PROTOCOL,
|
|
@@ -7972,7 +8083,7 @@ function createEndpoint(options) {
|
|
|
7972
8083
|
const index = group?.children.findIndex((child) => child.operationId === next.operationId) ?? -1;
|
|
7973
8084
|
if (group && index >= 0 && !group.cancelled) {
|
|
7974
8085
|
for (const reference of group.children.slice(index + 1)) {
|
|
7975
|
-
const child = await tx.get(
|
|
8086
|
+
const child = await tx.get(key2(reference.operationId));
|
|
7976
8087
|
if (!child || messageGroupChildSucceeded(child)) continue;
|
|
7977
8088
|
if (messageGroupChildSucceeded(next)) {
|
|
7978
8089
|
if (!terminal(child.status) && child.status !== "uncertain") await enqueueOperation(tx, child);
|
|
@@ -7980,7 +8091,7 @@ function createEndpoint(options) {
|
|
|
7980
8091
|
}
|
|
7981
8092
|
if (!child.attempted && !terminal(child.status)) {
|
|
7982
8093
|
const { request: _plaintext, preparedContent: _sealedPlaintext, ...correlation } = child;
|
|
7983
|
-
await tx.put(
|
|
8094
|
+
await tx.put(key2(child.operationId), { ...correlation, status: "rejected", updatedAt: now(), receipt: {
|
|
7984
8095
|
protocol: CANON_ENDPOINT_PROTOCOL,
|
|
7985
8096
|
operationId: child.operationId,
|
|
7986
8097
|
installationId: child.installationId,
|
|
@@ -8000,7 +8111,7 @@ function createEndpoint(options) {
|
|
|
8000
8111
|
__name(advanceMessageGroup, "advanceMessageGroup");
|
|
8001
8112
|
async function commitOperationRecord(tx, created) {
|
|
8002
8113
|
const ordered = await orderOperation(tx, created);
|
|
8003
|
-
await tx.put(
|
|
8114
|
+
await tx.put(key2(created.operationId), ordered);
|
|
8004
8115
|
await trackOperationOrder(tx, ordered);
|
|
8005
8116
|
await enqueueOperation(tx, ordered);
|
|
8006
8117
|
if (!created.sensitive) await tx.delete(`preparation/${created.operationId}`);
|
|
@@ -8011,7 +8122,7 @@ function createEndpoint(options) {
|
|
|
8011
8122
|
if (receipt.operationId !== operation.operationId || receipt.installationId !== operation.installationId || receipt.action !== operation.action) {
|
|
8012
8123
|
throw new EndpointError("Operation receipt identity mismatch", { status: 502, code: "INVALID_RECEIPT" });
|
|
8013
8124
|
}
|
|
8014
|
-
const current = await tx.get(
|
|
8125
|
+
const current = await tx.get(key2(operation.operationId));
|
|
8015
8126
|
if (!current) throw new EndpointError("Pending operation was discarded", { code: "OPERATION_DISCARDED", retryable: false });
|
|
8016
8127
|
const recoveredGroupSuccess = acceptedEcho && ["mls_group_control", "mls_application"].includes(current.action) && receipt.status === "succeeded" && current.status === "rejected";
|
|
8017
8128
|
if (terminal(current.status) && !recoveredGroupSuccess) return current;
|
|
@@ -8026,7 +8137,7 @@ function createEndpoint(options) {
|
|
|
8026
8137
|
}
|
|
8027
8138
|
if (terminal(next.status)) delete next.nextAttemptAt;
|
|
8028
8139
|
else next.nextAttemptAt = clock() + (typeof receipt.error?.retryAfterMs === "number" && Number.isFinite(receipt.error.retryAfterMs) ? Math.max(1e3, Math.min(3e5, receipt.error.retryAfterMs)) : 1e3);
|
|
8029
|
-
await tx.put(
|
|
8140
|
+
await tx.put(key2(operation.operationId), next);
|
|
8030
8141
|
await settleMlsDevTextOperation(tx, next);
|
|
8031
8142
|
if (next.action === "delete_message" && next.status === "succeeded" && next.conversationId && next.messageId && await isMlsDevRoomProtected(tx, next.conversationId)) await applyMlsDevDeletion(tx, next.conversationId, next.messageId);
|
|
8032
8143
|
if (terminal(next.status) || next.status === "uncertain") {
|
|
@@ -8070,7 +8181,7 @@ function createEndpoint(options) {
|
|
|
8070
8181
|
assertOpen();
|
|
8071
8182
|
await initialization;
|
|
8072
8183
|
const { operation, acquired } = await store.transaction(async (tx) => {
|
|
8073
|
-
const current = await tx.get(
|
|
8184
|
+
const current = await tx.get(key2(operationId));
|
|
8074
8185
|
if (!current) throw new EndpointError("Unknown endpoint operation", { status: 404, code: "OPERATION_NOT_FOUND", retryable: false });
|
|
8075
8186
|
if (terminal(current.status)) return { operation: current, acquired: false };
|
|
8076
8187
|
await assertCurrentInstallation(tx);
|
|
@@ -8079,7 +8190,7 @@ function createEndpoint(options) {
|
|
|
8079
8190
|
if (!reconcileOnly && current.messageGroupId && current.status !== "uncertain") {
|
|
8080
8191
|
const group = await tx.get(`message-group/${current.messageGroupId}`);
|
|
8081
8192
|
const index = group?.children.findIndex((child) => child.operationId === operationId) ?? -1;
|
|
8082
|
-
const previous = group && index > 0 ? await tx.get(
|
|
8193
|
+
const previous = group && index > 0 ? await tx.get(key2(group.children[index - 1].operationId)) : void 0;
|
|
8083
8194
|
if (previous?.status === "rejected" || previous?.status === "succeeded" && !messageGroupChildSucceeded(previous)) {
|
|
8084
8195
|
const rejected = { ...current, status: "rejected", updatedAt: now(), receipt: {
|
|
8085
8196
|
protocol: CANON_ENDPOINT_PROTOCOL,
|
|
@@ -8093,7 +8204,7 @@ function createEndpoint(options) {
|
|
|
8093
8204
|
} };
|
|
8094
8205
|
delete rejected.request;
|
|
8095
8206
|
delete rejected.preparedContent;
|
|
8096
|
-
await tx.put(
|
|
8207
|
+
await tx.put(key2(operationId), rejected);
|
|
8097
8208
|
await removeOperationOutbox(tx, rejected);
|
|
8098
8209
|
await settleOperationOrder(tx, rejected);
|
|
8099
8210
|
return { operation: rejected, acquired: false };
|
|
@@ -8102,7 +8213,7 @@ function createEndpoint(options) {
|
|
|
8102
8213
|
}
|
|
8103
8214
|
if (current.lease && current.lease.owner !== owner && current.lease.expiresAt > clock()) return { operation: current, acquired: false };
|
|
8104
8215
|
const next = { ...current, status: reconcileOnly ? current.status : current.status === "uncertain" ? "uncertain" : "sending", lease: { owner, expiresAt: clock() + leaseMs }, attempts: current.attempts + 1, updatedAt: now() };
|
|
8105
|
-
await tx.put(
|
|
8216
|
+
await tx.put(key2(operationId), next);
|
|
8106
8217
|
return { operation: next, acquired: true };
|
|
8107
8218
|
});
|
|
8108
8219
|
if (terminal(operation.status) && operation.receipt) {
|
|
@@ -8120,8 +8231,8 @@ function createEndpoint(options) {
|
|
|
8120
8231
|
notify(operation);
|
|
8121
8232
|
const heartbeat = setInterval(() => {
|
|
8122
8233
|
void store.transaction(async (tx) => {
|
|
8123
|
-
const current = await tx.get(
|
|
8124
|
-
if (current?.lease?.owner === owner) await tx.put(
|
|
8234
|
+
const current = await tx.get(key2(operationId));
|
|
8235
|
+
if (current?.lease?.owner === owner) await tx.put(key2(operationId), { ...current, lease: { owner, expiresAt: clock() + leaseMs } });
|
|
8125
8236
|
}).catch(() => void 0);
|
|
8126
8237
|
}, Math.floor(leaseMs / 3));
|
|
8127
8238
|
heartbeat.unref?.();
|
|
@@ -8143,11 +8254,11 @@ function createEndpoint(options) {
|
|
|
8143
8254
|
const receipt2 = found?.status === "succeeded" || found?.status === "rejected" ? found : { ...deferredReceipt(operation), status: operation.status === "uncertain" ? "uncertain" : "pending" };
|
|
8144
8255
|
if (reconcileOnly && !terminal(receipt2.status)) {
|
|
8145
8256
|
await store.transaction(async (tx) => {
|
|
8146
|
-
const current = await tx.get(
|
|
8257
|
+
const current = await tx.get(key2(operationId));
|
|
8147
8258
|
if (current?.lease?.owner === owner) {
|
|
8148
8259
|
const next = { ...current };
|
|
8149
8260
|
delete next.lease;
|
|
8150
|
-
await tx.put(
|
|
8261
|
+
await tx.put(key2(operationId), next);
|
|
8151
8262
|
}
|
|
8152
8263
|
});
|
|
8153
8264
|
return receipt2;
|
|
@@ -8199,7 +8310,7 @@ function createEndpoint(options) {
|
|
|
8199
8310
|
});
|
|
8200
8311
|
checkingContentMode = false;
|
|
8201
8312
|
await store.transaction(async (tx) => {
|
|
8202
|
-
const current = await tx.get(
|
|
8313
|
+
const current = await tx.get(key2(operationId));
|
|
8203
8314
|
if (!current || current.lease?.owner !== owner || terminal(current.status)) throw new EndpointError("Delivery ownership changed", { code: "DELIVERY_FENCED" });
|
|
8204
8315
|
await assertCurrentInstallation(tx);
|
|
8205
8316
|
if (current.preparedContent && (await tx.get(PREPARATION_GENERATION_KEY) ?? 0) !== current.preparedContent.expected.installationGeneration) throw new EndpointError("This endpoint installation can no longer dispatch its prepared content", { code: "PREPARATION_INSTALLATION_FENCED", retryable: false });
|
|
@@ -8217,7 +8328,7 @@ function createEndpoint(options) {
|
|
|
8217
8328
|
const group = await tx.get(`message-group/${current.messageGroupId}`);
|
|
8218
8329
|
if (group?.cancelled || !messageGroups.allowed(current.messageGroupId)) throw new EndpointError("Message group was cancelled", { code: "OPERATION_DISCARDED", retryable: false });
|
|
8219
8330
|
}
|
|
8220
|
-
await tx.put(
|
|
8331
|
+
await tx.put(key2(operationId), { ...current, attempted: true });
|
|
8221
8332
|
});
|
|
8222
8333
|
const receipt = await transport.execute(request, controller.signal);
|
|
8223
8334
|
await persistReceipt(operation, receipt);
|
|
@@ -8226,17 +8337,17 @@ function createEndpoint(options) {
|
|
|
8226
8337
|
const failure = safeError(error);
|
|
8227
8338
|
if (reconcileOnly && failure.retryable) {
|
|
8228
8339
|
await store.transaction(async (tx) => {
|
|
8229
|
-
const current = await tx.get(
|
|
8340
|
+
const current = await tx.get(key2(operationId));
|
|
8230
8341
|
if (current?.lease?.owner === owner) {
|
|
8231
8342
|
const next = { ...current };
|
|
8232
8343
|
delete next.lease;
|
|
8233
|
-
await tx.put(
|
|
8344
|
+
await tx.put(key2(operationId), next);
|
|
8234
8345
|
}
|
|
8235
8346
|
});
|
|
8236
8347
|
return deferredReceipt(operation, failure);
|
|
8237
8348
|
}
|
|
8238
8349
|
const updated = await store.transaction(async (tx) => {
|
|
8239
|
-
const current = await tx.get(
|
|
8350
|
+
const current = await tx.get(key2(operationId));
|
|
8240
8351
|
if (!current) throw failure;
|
|
8241
8352
|
if (current.lease?.owner !== owner || terminal(current.status)) return current;
|
|
8242
8353
|
const lookupUncertain = resolvingReceipt && !failure.retryable;
|
|
@@ -8263,7 +8374,7 @@ function createEndpoint(options) {
|
|
|
8263
8374
|
next.receipt = { protocol: CANON_ENDPOINT_PROTOCOL, operationId: current.operationId, installationId: current.installationId, action: current.action, status: "rejected", error: { code: failure.code, message: current.sensitive ? "Sensitive operation could not complete" : failure.message, status: failure.status, retryable: false }, createdAt: current.createdAt, updatedAt: now() };
|
|
8264
8375
|
delete next.nextAttemptAt;
|
|
8265
8376
|
}
|
|
8266
|
-
await tx.put(
|
|
8377
|
+
await tx.put(key2(operationId), next);
|
|
8267
8378
|
await settleMlsDevTextOperation(tx, next);
|
|
8268
8379
|
if (terminal(next.status) || next.status === "uncertain") {
|
|
8269
8380
|
await removeOperationOutbox(tx, next);
|
|
@@ -8336,7 +8447,7 @@ function createEndpoint(options) {
|
|
|
8336
8447
|
const inputFingerprint = sensitive ? void 0 : await hash(canonicalEndpointJson({ ...authored, ...identity.ownershipVersion !== void 0 ? { ownershipVersion: identity.ownershipVersion } : {} }));
|
|
8337
8448
|
const previous = await store.transaction(async (tx) => {
|
|
8338
8449
|
await assertCurrentInstallation(tx);
|
|
8339
|
-
return tx.get(
|
|
8450
|
+
return tx.get(key2(operationId));
|
|
8340
8451
|
});
|
|
8341
8452
|
if (previous?.inputFingerprint !== void 0 && !sensitive) {
|
|
8342
8453
|
if (previous.inputFingerprint !== inputFingerprint || previous.guardId !== executeOptions.guardId) {
|
|
@@ -8401,7 +8512,7 @@ function createEndpoint(options) {
|
|
|
8401
8512
|
...normalized.preconditions ? { preconditions: normalized.preconditions } : {}
|
|
8402
8513
|
};
|
|
8403
8514
|
const existingOperation = /* @__PURE__ */ __name(async (tx) => {
|
|
8404
|
-
const existing = await tx.get(
|
|
8515
|
+
const existing = await tx.get(key2(operationId));
|
|
8405
8516
|
if (!existing) return void 0;
|
|
8406
8517
|
if (existing.fingerprint !== fingerprint || existing.sensitive !== sensitive || existing.guardId !== executeOptions.guardId) {
|
|
8407
8518
|
throw new EndpointError("Operation ID was already used for different content", { status: 409, code: "OPERATION_ID_COLLISION", retryable: false, operationId });
|
|
@@ -8500,7 +8611,7 @@ function createEndpoint(options) {
|
|
|
8500
8611
|
do {
|
|
8501
8612
|
const batch = await store.transaction(async (tx) => {
|
|
8502
8613
|
const entries2 = await tx.scan("outbox/", { after, limit: 100 });
|
|
8503
|
-
return Promise.all(entries2.map(async (entry) => ({ key: entry.key, value: await tx.get(
|
|
8614
|
+
return Promise.all(entries2.map(async (entry) => ({ key: entry.key, value: await tx.get(key2(entry.value.operationId)) })));
|
|
8504
8615
|
});
|
|
8505
8616
|
for (const entry of batch) {
|
|
8506
8617
|
after = entry.key;
|
|
@@ -8536,7 +8647,7 @@ function createEndpoint(options) {
|
|
|
8536
8647
|
},
|
|
8537
8648
|
async getOperation(operationId) {
|
|
8538
8649
|
await initialization;
|
|
8539
|
-
return store.transaction((tx) => tx.get(
|
|
8650
|
+
return store.transaction((tx) => tx.get(key2(operationId)));
|
|
8540
8651
|
},
|
|
8541
8652
|
waitForOperation(operationId, waitOptions = {}) {
|
|
8542
8653
|
return new Promise((resolve3, reject) => {
|
|
@@ -8714,7 +8825,7 @@ function createEndpoint(options) {
|
|
|
8714
8825
|
const recordKey = `${openerPrefix()}/${encodeURIComponent(input.messageId)}`;
|
|
8715
8826
|
await store.transaction(async (tx) => {
|
|
8716
8827
|
await assertCurrentInstallation(tx);
|
|
8717
|
-
const operation = await tx.get(
|
|
8828
|
+
const operation = await tx.get(key2(operationId));
|
|
8718
8829
|
if (operation) {
|
|
8719
8830
|
if (operation.openerFingerprint !== openerFingerprint)
|
|
8720
8831
|
throw new EndpointError("Opening line ID was already used for different content", { status: 409, code: "OPERATION_ID_COLLISION", retryable: false, operationId });
|
|
@@ -8735,7 +8846,7 @@ function createEndpoint(options) {
|
|
|
8735
8846
|
});
|
|
8736
8847
|
try {
|
|
8737
8848
|
const result = await endpoint.executeResult({ action: "start_conversation", body: { ...body } }, { operationId });
|
|
8738
|
-
if (result.status === "unavailable" && result.reason === "opening-text-required") {
|
|
8849
|
+
if (result.status === "unavailable" && (result.reason === "opening-text-required" || result.reason === "encrypted-room-required")) {
|
|
8739
8850
|
await store.transaction(async (tx) => {
|
|
8740
8851
|
await assertCurrentInstallation(tx);
|
|
8741
8852
|
await tx.delete(recordKey);
|
|
@@ -8906,29 +9017,27 @@ function createEndpoint(options) {
|
|
|
8906
9017
|
}
|
|
8907
9018
|
}
|
|
8908
9019
|
await initialization;
|
|
8909
|
-
await store.transaction(
|
|
8910
|
-
const recordKey = `inbound/${id2}`;
|
|
8911
|
-
const current = await tx.get(recordKey);
|
|
8912
|
-
if (!current) throw new EndpointError("Inbound operation was not durably received", { code: "INBOUND_NOT_FOUND", retryable: false });
|
|
8913
|
-
if (current.externalClaim) throw new EndpointError("Use the bound external claim to settle this input", { code: "EXTERNAL_INBOUND_CLAIM_REQUIRED", retryable: false });
|
|
8914
|
-
if (current.handoffGeneration && state !== "settled" && state !== "uncertain") throw new EndpointError("Use the input handoff to release or defer managed work", { code: "INBOUND_HANDOFF_REQUIRED", retryable: false });
|
|
8915
|
-
if (current.deleted && !["settled", "uncertain"].includes(state)) throw new EndpointError("Deleted input cannot be replayed", { code: "INBOUND_DELETED", retryable: false });
|
|
8916
|
-
if (current.state === "settled" && state !== "settled") throw new EndpointError("Settled input cannot be replayed", { code: "INBOUND_ALREADY_SETTLED", retryable: false });
|
|
8917
|
-
const next = { ...current, state, ...reason ? { reason } : {}, updatedAt: now() };
|
|
8918
|
-
delete next.lease;
|
|
8919
|
-
if (state === "settled") {
|
|
8920
|
-
delete next.handoffGeneration;
|
|
8921
|
-
delete next.handoffInstallationGeneration;
|
|
8922
|
-
}
|
|
8923
|
-
if (state === "settled") next.event = { ...next.event, data: {} };
|
|
8924
|
-
await tx.put(recordKey, next);
|
|
8925
|
-
if (state === "settled" || state === "uncertain" || current.deleted) await removeInboundReady(tx, current);
|
|
8926
|
-
else await enqueueInbound(tx, next);
|
|
8927
|
-
});
|
|
9020
|
+
await store.transaction((tx) => writeInboundState(tx, id2, state, reason));
|
|
8928
9021
|
},
|
|
8929
9022
|
async listInbound(listOptions = {}) {
|
|
8930
9023
|
await initialization;
|
|
8931
|
-
return store.transaction(
|
|
9024
|
+
return store.transaction(async (tx) => {
|
|
9025
|
+
const answered = listOptions.unanswered ? await completions.answered(tx) : void 0;
|
|
9026
|
+
return matchingRecords(tx, "inbound/", listOptions, (entry) => (!listOptions.states || listOptions.states.includes(entry.state)) && !answered?.has(entry.event.id));
|
|
9027
|
+
});
|
|
9028
|
+
},
|
|
9029
|
+
saveCompletion: completions.save,
|
|
9030
|
+
getCompletion: completions.get,
|
|
9031
|
+
listCompletions: completions.list,
|
|
9032
|
+
replaceCompletion: completions.replace,
|
|
9033
|
+
async finishCompletion(id2, settle) {
|
|
9034
|
+
for (const inputId of new Set(settle?.inputIds ?? [])) {
|
|
9035
|
+
const managed = handoffs.settle(inputId, settle.reason);
|
|
9036
|
+
if (managed && !await managed && (await endpoint.getInbound(inputId))?.state !== "settled") {
|
|
9037
|
+
throw new EndpointError("Input ownership changed before reconciliation", { code: "INBOUND_HANDOFF_LOST", retryable: false });
|
|
9038
|
+
}
|
|
9039
|
+
}
|
|
9040
|
+
await completions.finish(id2, settle);
|
|
8932
9041
|
},
|
|
8933
9042
|
async discardPending() {
|
|
8934
9043
|
await initialization;
|
|
@@ -9098,12 +9207,12 @@ async function acquireAgentEndpoint(input) {
|
|
|
9098
9207
|
${baseUrl}
|
|
9099
9208
|
${input.principalId}
|
|
9100
9209
|
${input.ownershipVersion}`).digest("hex");
|
|
9101
|
-
const
|
|
9210
|
+
const key2 = `${directory}
|
|
9102
9211
|
${namespace}
|
|
9103
9212
|
${input.options.autoDrain === false ? "operator" : "live"}`;
|
|
9104
|
-
let entry = endpoints.get(
|
|
9213
|
+
let entry = endpoints.get(key2);
|
|
9105
9214
|
if (entry && entry.installationId !== input.options.installationId) throw new Error("Canon endpoint is already open under another installation.");
|
|
9106
|
-
if (entry && (!!entry.mlsDev !== !!mls || mls && (entry.mlsDev?.fingerprint !== mls.fingerprint || entry.mlsDev.custody !== mls.custody || Object.keys(mls.callbacks).some((
|
|
9215
|
+
if (entry && (!!entry.mlsDev !== !!mls || mls && (entry.mlsDev?.fingerprint !== mls.fingerprint || entry.mlsDev.custody !== mls.custody || Object.keys(mls.callbacks).some((key3) => entry.mlsDev.callbacks[key3] !== mls.callbacks[key3])))) {
|
|
9107
9216
|
throw new Error("MLS_DEV_AGENT_SHARED_CONFIGURATION_MISMATCH");
|
|
9108
9217
|
}
|
|
9109
9218
|
if (!entry) {
|
|
@@ -9143,9 +9252,9 @@ ${input.options.autoDrain === false ? "operator" : "live"}`;
|
|
|
9143
9252
|
autoDrain: input.options.autoDrain
|
|
9144
9253
|
}));
|
|
9145
9254
|
entry = shared;
|
|
9146
|
-
endpoints.set(
|
|
9255
|
+
endpoints.set(key2, entry);
|
|
9147
9256
|
void entry.endpoint.catch(() => {
|
|
9148
|
-
if (endpoints.get(
|
|
9257
|
+
if (endpoints.get(key2) === entry) endpoints.delete(key2);
|
|
9149
9258
|
});
|
|
9150
9259
|
}
|
|
9151
9260
|
entry.token = input.apiKey;
|
|
@@ -9162,8 +9271,8 @@ ${input.options.autoDrain === false ? "operator" : "live"}`;
|
|
|
9162
9271
|
if (released) return;
|
|
9163
9272
|
released = true;
|
|
9164
9273
|
entry.references--;
|
|
9165
|
-
if (entry.references === 0 && endpoints.get(
|
|
9166
|
-
endpoints.delete(
|
|
9274
|
+
if (entry.references === 0 && endpoints.get(key2) === entry) {
|
|
9275
|
+
endpoints.delete(key2);
|
|
9167
9276
|
await endpoint.close();
|
|
9168
9277
|
}
|
|
9169
9278
|
} };
|
|
@@ -9395,42 +9504,6 @@ var CanonClient = class {
|
|
|
9395
9504
|
delivery?.operationId ?? (options?.messageId ? stableOperationId("send_message", conversationId, options.messageId) : void 0)
|
|
9396
9505
|
);
|
|
9397
9506
|
}
|
|
9398
|
-
/** Inspect a trusted reply's durable delivery without rebuilding its body. */
|
|
9399
|
-
async getMessageOperation(conversationId, messageId, delivery) {
|
|
9400
|
-
const endpoint = await this.getEndpoint();
|
|
9401
|
-
const group = await endpoint.getMessageGroup(delivery?.operationId ?? stableOperationId("send_group", conversationId, messageId));
|
|
9402
|
-
if (group) return group;
|
|
9403
|
-
const operation = await endpoint.getOperation(delivery?.operationId ?? stableOperationId("send_message", conversationId, messageId));
|
|
9404
|
-
if (operation) return operation;
|
|
9405
|
-
const firstPart = `${messageId.slice(0, 153)}-part-1`;
|
|
9406
|
-
if (await endpoint.getOperation(delivery?.operationId ? `${delivery.operationId.slice(0, 153)}-part-1` : stableOperationId("send_message", conversationId, firstPart))) {
|
|
9407
|
-
throw new CanonApiError(409, JSON.stringify({ code: "OPERATION_UNCERTAIN", message: "Existing message parts require aggregate delivery reconciliation" }));
|
|
9408
|
-
}
|
|
9409
|
-
return void 0;
|
|
9410
|
-
}
|
|
9411
|
-
/** Core's chunker records all ordered message intents before publishing any part. */
|
|
9412
|
-
async prepareMessageGroup(conversationId, messageId, parts, delivery) {
|
|
9413
|
-
const id2 = delivery?.operationId ?? stableOperationId("send_group", conversationId, messageId);
|
|
9414
|
-
try {
|
|
9415
|
-
const endpoint = await this.getEndpoint();
|
|
9416
|
-
const group = await endpoint.registerMessageGroup({ id: id2, ...delivery?.replacesOperationId ? { replacesOperationId: delivery.replacesOperationId } : {}, children: parts.map((part, index) => ({
|
|
9417
|
-
operationId: delivery?.operationId ? `${delivery.operationId.slice(0, 160 - `-part-${index + 1}`.length)}-part-${index + 1}` : stableOperationId("send_message", conversationId, part.options.messageId),
|
|
9418
|
-
intent: { action: "send_message", body: { conversationId, text: part.text, ...part.options } }
|
|
9419
|
-
})) }, { signal: delivery?.signal });
|
|
9420
|
-
return { id: id2, sendPart: /* @__PURE__ */ __name(async (index, options) => {
|
|
9421
|
-
const operationId = group.childOperationIds?.[index];
|
|
9422
|
-
if (!operationId) throw new Error("Unknown message group part");
|
|
9423
|
-
if (options?.signal?.aborted) throw new CanonApiError(409, JSON.stringify({ code: "OPERATION_PENDING", message: "Reply delivery was interrupted" }));
|
|
9424
|
-
await endpoint.retry(operationId);
|
|
9425
|
-
return this.waitForOperation(operationId, options);
|
|
9426
|
-
}, "sendPart") };
|
|
9427
|
-
} catch (error) {
|
|
9428
|
-
throw asCanonApiError(error);
|
|
9429
|
-
}
|
|
9430
|
-
}
|
|
9431
|
-
async cancelMessageGroup(id2) {
|
|
9432
|
-
await (await this.getEndpoint()).cancelMessageGroup(id2);
|
|
9433
|
-
}
|
|
9434
9507
|
/**
|
|
9435
9508
|
* Execute a canonical verb over the wire contract (canon.verb-wire.v1).
|
|
9436
9509
|
* Bindings build the request with projectVerbIntentToWire; the endpoint
|
|
@@ -9674,27 +9747,18 @@ var CanonClient = class {
|
|
|
9674
9747
|
async deleteMessage(conversationId, messageId) {
|
|
9675
9748
|
await this.endpointAction("delete_message", { conversationId, messageId });
|
|
9676
9749
|
}
|
|
9677
|
-
async updateMessageDisposition(conversationId, messageId, inboundDisposition) {
|
|
9678
|
-
await this.endpointAction("update_message_disposition", { conversationId, messageId, inboundDisposition });
|
|
9679
|
-
}
|
|
9680
9750
|
async markAsRead(conversationId) {
|
|
9681
9751
|
await this.endpointAction("mark_read", { conversationId });
|
|
9682
9752
|
}
|
|
9683
9753
|
async react(conversationId, messageId, emoji) {
|
|
9684
9754
|
await this.endpointAction("react", { conversationId, messageId, emoji });
|
|
9685
9755
|
}
|
|
9686
|
-
async setStreaming(options) {
|
|
9687
|
-
await this.endpointPublish("publish_streaming", { ...options });
|
|
9688
|
-
}
|
|
9689
9756
|
async clearStreaming(conversationId) {
|
|
9690
9757
|
await this.endpointPublish("publish_streaming", { conversationId, streaming: false });
|
|
9691
9758
|
}
|
|
9692
9759
|
async setRuntimeTurn(options) {
|
|
9693
9760
|
await this.endpointPublish("publish_turn", { ...options });
|
|
9694
9761
|
}
|
|
9695
|
-
async setTyping(conversationId, typing, status) {
|
|
9696
|
-
await this.endpointPublish("publish_typing", { conversationId, typing, ...status ? { status } : {} });
|
|
9697
|
-
}
|
|
9698
9762
|
async peekRuntimeSignal(conversationId) {
|
|
9699
9763
|
return this.endpointQuery("peek_runtime_signal", { conversationId });
|
|
9700
9764
|
}
|
|
@@ -9736,38 +9800,6 @@ var CanonClient = class {
|
|
|
9736
9800
|
async patchRuntimeStreaming(conversationId, patch) {
|
|
9737
9801
|
await this.endpointPublish("publish_streaming", { ...patch, conversationId });
|
|
9738
9802
|
}
|
|
9739
|
-
async createRuntimeInputRequest(options) {
|
|
9740
|
-
return this.endpointAction("create_interaction", { ...options, interactionKind: "input" });
|
|
9741
|
-
}
|
|
9742
|
-
async consumeRuntimeInputResponse(options) {
|
|
9743
|
-
return this.endpointAction("consume_interaction", { ...options, interactionKind: "input" });
|
|
9744
|
-
}
|
|
9745
|
-
async createRuntimeApprovalRequest(options) {
|
|
9746
|
-
return this.endpointAction("create_interaction", { ...options, interactionKind: "approval" });
|
|
9747
|
-
}
|
|
9748
|
-
async consumeRuntimeApprovalResponse(options) {
|
|
9749
|
-
return this.endpointAction("consume_interaction", { ...options, interactionKind: "approval" });
|
|
9750
|
-
}
|
|
9751
|
-
async createRuntimeCardRequest(options) {
|
|
9752
|
-
return this.endpointAction("create_interaction", { ...options, interactionKind: "card" });
|
|
9753
|
-
}
|
|
9754
|
-
async consumeRuntimeCardResponse(options) {
|
|
9755
|
-
return this.endpointAction("consume_interaction", { ...options, interactionKind: "card" });
|
|
9756
|
-
}
|
|
9757
|
-
/**
|
|
9758
|
-
* Create a server-owned plan-approval interaction.
|
|
9759
|
-
*
|
|
9760
|
-
* Seeds the pending node + attention + runtime-card state and authors the
|
|
9761
|
-
* `plan_approval` card message, mirroring the other `createRuntime*Request`
|
|
9762
|
-
* spine methods. Hosts resolve it through `consumeRuntimePlanResponse()` so
|
|
9763
|
-
* the canonical pending state, rather than a timeline receipt, is authoritative.
|
|
9764
|
-
*/
|
|
9765
|
-
async createRuntimePlanRequest(options) {
|
|
9766
|
-
return this.endpointAction("create_interaction", { ...options, interactionKind: "plan" });
|
|
9767
|
-
}
|
|
9768
|
-
async consumeRuntimePlanResponse(options) {
|
|
9769
|
-
return this.endpointAction("consume_interaction", { ...options, interactionKind: "plan" });
|
|
9770
|
-
}
|
|
9771
9803
|
async updateRuntimeStatus(options) {
|
|
9772
9804
|
await this.endpointPublish("publish_runtime_status", { ...options });
|
|
9773
9805
|
}
|
|
@@ -10027,7 +10059,7 @@ async function resumeRegistration(options) {
|
|
|
10027
10059
|
};
|
|
10028
10060
|
let session = await store.load();
|
|
10029
10061
|
if (session) {
|
|
10030
|
-
if (Object.entries(binding).some(([
|
|
10062
|
+
if (Object.entries(binding).some(([key2, value]) => session[key2] !== value)) {
|
|
10031
10063
|
throw new Error("Pending registration belongs to another Canon environment or endpoint set");
|
|
10032
10064
|
}
|
|
10033
10065
|
if (session.ownerPhone && session.ownerPhone !== input.ownerPhone || input.requestedAgentId && input.requestedAgentId !== session.requestedAgentId || input.localRegistrationId && input.localRegistrationId !== session.localRegistrationId) {
|
|
@@ -10440,7 +10472,7 @@ function normalizePendingRegistration(name, value) {
|
|
|
10440
10472
|
"createdAt",
|
|
10441
10473
|
"updatedAt"
|
|
10442
10474
|
];
|
|
10443
|
-
if (requiredStrings.some((field) => typeof pending[field] !== "string" || !pending[field]) || pending.profile !== name || pending.requestId !== void 0 && typeof pending.requestId !== "string" || pending.pollToken !== void 0 && typeof pending.pollToken !== "string" || pending.clientType !== void 0 && typeof pending.clientType !== "string" || ["ownerPhone", "requestedAgentId", "credentialAgentId"].some((
|
|
10475
|
+
if (requiredStrings.some((field) => typeof pending[field] !== "string" || !pending[field]) || pending.profile !== name || pending.requestId !== void 0 && typeof pending.requestId !== "string" || pending.pollToken !== void 0 && typeof pending.pollToken !== "string" || pending.clientType !== void 0 && typeof pending.clientType !== "string" || ["ownerPhone", "requestedAgentId", "credentialAgentId"].some((key2) => pending[key2] !== void 0 && typeof pending[key2] !== "string")) {
|
|
10444
10476
|
throw new Error(
|
|
10445
10477
|
`${PENDING_REGISTRATIONS_PATH} entry "${name}" must include a complete environment binding`
|
|
10446
10478
|
);
|
|
@@ -10521,7 +10553,7 @@ async function ensureAgentProfile(options) {
|
|
|
10521
10553
|
const existing = loadProfiles()[profile];
|
|
10522
10554
|
const pending = loadPendingRegistrations()[profile];
|
|
10523
10555
|
if (!pending && existing && !options.reconnect) {
|
|
10524
|
-
if (Object.entries(binding).some(([
|
|
10556
|
+
if (Object.entries(binding).some(([key2, value]) => existing[key2] !== value) || input.requestedAgentId && input.requestedAgentId !== existing.agentId) {
|
|
10525
10557
|
throw new Error("Saved Canon profile belongs to another environment or agent identity");
|
|
10526
10558
|
}
|
|
10527
10559
|
await verifyCanonRuntimeConnection(connection);
|
|
@@ -10547,7 +10579,7 @@ async function ensureAgentProfile(options) {
|
|
|
10547
10579
|
}, "save"),
|
|
10548
10580
|
loadCredentials: /* @__PURE__ */ __name(() => {
|
|
10549
10581
|
const saved = loadProfiles()[profile];
|
|
10550
|
-
if (saved && Object.entries(binding).some(([
|
|
10582
|
+
if (saved && Object.entries(binding).some(([key2, value]) => saved[key2] !== value)) {
|
|
10551
10583
|
throw new Error("Saved registration credential belongs to another Canon environment or endpoint set");
|
|
10552
10584
|
}
|
|
10553
10585
|
return saved;
|
|
@@ -10701,7 +10733,7 @@ function createRTDBClientHandle(client) {
|
|
|
10701
10733
|
if (target.family === "streaming") {
|
|
10702
10734
|
if (clear) return client.clearStreaming(target.conversationId);
|
|
10703
10735
|
const { updatedAt: _updatedAt, startedAt: _startedAt, ...patch2 } = record5(data);
|
|
10704
|
-
if (Object.keys(patch2).some((
|
|
10736
|
+
if (Object.keys(patch2).some((key2) => !["text", "status", "messageId", "turnId", "blocks"].includes(key2))) throw new Error("Unsupported streaming projection field");
|
|
10705
10737
|
return client.patchRuntimeStreaming(target.conversationId, patch2);
|
|
10706
10738
|
}
|
|
10707
10739
|
if (target.family === "turn-state") {
|
|
@@ -11206,125 +11238,6 @@ function runCli(moduleUrl, main, onError, metadata) {
|
|
|
11206
11238
|
}
|
|
11207
11239
|
__name(runCli, "runCli");
|
|
11208
11240
|
|
|
11209
|
-
// ../core/src/completed-output-journal.ts
|
|
11210
|
-
import { createHash as createHash4, randomUUID as randomUUID6 } from "node:crypto";
|
|
11211
|
-
import {
|
|
11212
|
-
closeSync as closeSync3,
|
|
11213
|
-
existsSync as existsSync2,
|
|
11214
|
-
fsyncSync,
|
|
11215
|
-
mkdirSync as mkdirSync2,
|
|
11216
|
-
openSync as openSync3,
|
|
11217
|
-
readFileSync as readFileSync4,
|
|
11218
|
-
readdirSync,
|
|
11219
|
-
renameSync as renameSync2,
|
|
11220
|
-
unlinkSync as unlinkSync3,
|
|
11221
|
-
writeFileSync as writeFileSync3
|
|
11222
|
-
} from "node:fs";
|
|
11223
|
-
import { join as join4 } from "node:path";
|
|
11224
|
-
function createCompletedOutputJournal(directory, scope5, options) {
|
|
11225
|
-
const label = options.label;
|
|
11226
|
-
const folder = join4(directory, createHash4("sha256").update(JSON.stringify(scope5)).digest("hex"));
|
|
11227
|
-
const filename = /* @__PURE__ */ __name((turnId) => join4(folder, `${createHash4("sha256").update(turnId).digest("hex")}.json`), "filename");
|
|
11228
|
-
function syncDirectory() {
|
|
11229
|
-
if (process.platform === "win32") return;
|
|
11230
|
-
const fd = openSync3(folder, "r");
|
|
11231
|
-
try {
|
|
11232
|
-
fsyncSync(fd);
|
|
11233
|
-
} finally {
|
|
11234
|
-
closeSync3(fd);
|
|
11235
|
-
}
|
|
11236
|
-
}
|
|
11237
|
-
__name(syncDirectory, "syncDirectory");
|
|
11238
|
-
function validate(record6) {
|
|
11239
|
-
if (record6?.version !== 1 || typeof record6.turnId !== "string" || !record6.turnId || typeof record6.conversationId !== "string" || !record6.conversationId || typeof record6.createdAt !== "string" || options.validate && !options.validate(record6)) {
|
|
11240
|
-
throw new Error(`Invalid completed ${label} output record; operator reconciliation is required`);
|
|
11241
|
-
}
|
|
11242
|
-
}
|
|
11243
|
-
__name(validate, "validate");
|
|
11244
|
-
function recoverTemporaryFiles() {
|
|
11245
|
-
for (const name of readdirSync(folder)) {
|
|
11246
|
-
const match = /^([a-f0-9]{64})\.(\d+)\.[a-f0-9-]+\.tmp$/.exec(name);
|
|
11247
|
-
if (!match) continue;
|
|
11248
|
-
try {
|
|
11249
|
-
process.kill(Number(match[2]), 0);
|
|
11250
|
-
continue;
|
|
11251
|
-
} catch (error) {
|
|
11252
|
-
if (error.code !== "ESRCH") continue;
|
|
11253
|
-
}
|
|
11254
|
-
const path = join4(folder, name);
|
|
11255
|
-
let record6;
|
|
11256
|
-
try {
|
|
11257
|
-
record6 = JSON.parse(readFileSync4(path, "utf8"));
|
|
11258
|
-
validate(record6);
|
|
11259
|
-
} catch {
|
|
11260
|
-
unlinkSync3(path);
|
|
11261
|
-
throw new Error(`Incomplete ${label} output write removed; reconcile its native transcript`);
|
|
11262
|
-
}
|
|
11263
|
-
const destination = filename(record6.turnId);
|
|
11264
|
-
if (destination !== join4(folder, `${match[1]}.json`)) {
|
|
11265
|
-
throw new Error(`Completed ${label} output temporary file identity mismatch`);
|
|
11266
|
-
}
|
|
11267
|
-
if (existsSync2(destination)) {
|
|
11268
|
-
if (readFileSync4(destination, "utf8") !== JSON.stringify(record6)) {
|
|
11269
|
-
throw new Error(`Conflicting completed ${label} outputs require reconciliation`);
|
|
11270
|
-
}
|
|
11271
|
-
unlinkSync3(path);
|
|
11272
|
-
} else renameSync2(path, destination);
|
|
11273
|
-
syncDirectory();
|
|
11274
|
-
}
|
|
11275
|
-
}
|
|
11276
|
-
__name(recoverTemporaryFiles, "recoverTemporaryFiles");
|
|
11277
|
-
return {
|
|
11278
|
-
save(record6) {
|
|
11279
|
-
validate(record6);
|
|
11280
|
-
mkdirSync2(folder, { recursive: true, mode: 448 });
|
|
11281
|
-
const path = filename(record6.turnId);
|
|
11282
|
-
const serialized = JSON.stringify(record6);
|
|
11283
|
-
if (existsSync2(path)) {
|
|
11284
|
-
if (readFileSync4(path, "utf8") !== serialized) throw new Error(`Completed ${label} output cannot be replaced`);
|
|
11285
|
-
return;
|
|
11286
|
-
}
|
|
11287
|
-
const temporary = `${path.slice(0, -5)}.${process.pid}.${randomUUID6()}.tmp`;
|
|
11288
|
-
try {
|
|
11289
|
-
const fd = openSync3(temporary, "wx", 384);
|
|
11290
|
-
try {
|
|
11291
|
-
writeFileSync3(fd, serialized);
|
|
11292
|
-
fsyncSync(fd);
|
|
11293
|
-
} finally {
|
|
11294
|
-
closeSync3(fd);
|
|
11295
|
-
}
|
|
11296
|
-
renameSync2(temporary, path);
|
|
11297
|
-
syncDirectory();
|
|
11298
|
-
} catch (error) {
|
|
11299
|
-
try {
|
|
11300
|
-
unlinkSync3(temporary);
|
|
11301
|
-
} catch {
|
|
11302
|
-
}
|
|
11303
|
-
throw error;
|
|
11304
|
-
}
|
|
11305
|
-
},
|
|
11306
|
-
list() {
|
|
11307
|
-
if (!existsSync2(folder)) return [];
|
|
11308
|
-
recoverTemporaryFiles();
|
|
11309
|
-
return readdirSync(folder).filter((name) => name.endsWith(".json")).map((name) => {
|
|
11310
|
-
const record6 = JSON.parse(readFileSync4(join4(folder, name), "utf8"));
|
|
11311
|
-
validate(record6);
|
|
11312
|
-
if (filename(record6.turnId) !== join4(folder, name)) throw new Error(`Completed ${label} output identity mismatch`);
|
|
11313
|
-
return record6;
|
|
11314
|
-
}).sort((a, b) => a.createdAt.localeCompare(b.createdAt));
|
|
11315
|
-
},
|
|
11316
|
-
remove(turnId) {
|
|
11317
|
-
try {
|
|
11318
|
-
unlinkSync3(filename(turnId));
|
|
11319
|
-
syncDirectory();
|
|
11320
|
-
} catch (error) {
|
|
11321
|
-
if (error.code !== "ENOENT") throw error;
|
|
11322
|
-
}
|
|
11323
|
-
}
|
|
11324
|
-
};
|
|
11325
|
-
}
|
|
11326
|
-
__name(createCompletedOutputJournal, "createCompletedOutputJournal");
|
|
11327
|
-
|
|
11328
11241
|
// src/bounded-file-read.ts
|
|
11329
11242
|
var BoundedFileReadError = class extends Error {
|
|
11330
11243
|
constructor(failure) {
|
|
@@ -11368,7 +11281,7 @@ var ANTHROPIC_IMAGE_MIME_TYPES = /* @__PURE__ */ new Set([
|
|
|
11368
11281
|
"image/gif",
|
|
11369
11282
|
"image/webp"
|
|
11370
11283
|
]);
|
|
11371
|
-
var DEFAULT_MEDIA_CACHE_DIR =
|
|
11284
|
+
var DEFAULT_MEDIA_CACHE_DIR = join4(CANON_DIR, "media-cache");
|
|
11372
11285
|
var DEFAULT_MEDIA_MATERIALIZATION_BYTES = 10 * 1024 * 1024;
|
|
11373
11286
|
var MAX_CANON_MEDIA_BYTES = 100 * 1024 * 1024;
|
|
11374
11287
|
var MAX_ANTHROPIC_IMAGE_RAW_BYTES = 7 * 1024 * 1024;
|
|
@@ -11436,7 +11349,7 @@ function inferExtension(input) {
|
|
|
11436
11349
|
}
|
|
11437
11350
|
__name(inferExtension, "inferExtension");
|
|
11438
11351
|
function conversationMediaCacheDir(input) {
|
|
11439
|
-
return
|
|
11352
|
+
return join4(
|
|
11440
11353
|
input.rootDir ?? DEFAULT_MEDIA_CACHE_DIR,
|
|
11441
11354
|
sanitizeSegment(input.agentId, "agent"),
|
|
11442
11355
|
sanitizeSegment(input.conversationId, "conversation")
|
|
@@ -11452,7 +11365,7 @@ function buildCachePath(input) {
|
|
|
11452
11365
|
const fallbackName = `${input.attachment.kind}-${input.index}${extension}`;
|
|
11453
11366
|
const preferredName = input.attachment.fileName ? sanitizeDownloadFileName(input.attachment.fileName, fallbackName) : fallbackName;
|
|
11454
11367
|
const fileName = extname(preferredName) ? preferredName : `${preferredName}${extension}`;
|
|
11455
|
-
return
|
|
11368
|
+
return join4(
|
|
11456
11369
|
conversationMediaCacheDir({ agentId: input.agentId, conversationId: input.conversationId, rootDir }),
|
|
11457
11370
|
sanitizeSegment(input.messageId, "message"),
|
|
11458
11371
|
`${String(input.index).padStart(2, "0")}-${fileName}`
|
|
@@ -11602,7 +11515,7 @@ async function materializeAttachment(attachment, options) {
|
|
|
11602
11515
|
if (!response.body) {
|
|
11603
11516
|
throw new Error("Canon media response had no body");
|
|
11604
11517
|
}
|
|
11605
|
-
const tempPath = `${path}.${process.pid}.${
|
|
11518
|
+
const tempPath = `${path}.${process.pid}.${randomUUID6()}.part`;
|
|
11606
11519
|
const limiter = byteLimitTransform(maxBytes);
|
|
11607
11520
|
try {
|
|
11608
11521
|
await pipeline2(
|
|
@@ -11698,7 +11611,7 @@ async function uploadMediaFile(client, conversationId, filePath, options) {
|
|
|
11698
11611
|
const media = await client.getFileMediaPipeline(options?.fetchImpl);
|
|
11699
11612
|
const [attachment] = await media.prepare({
|
|
11700
11613
|
conversationId,
|
|
11701
|
-
attachmentId: options?.attachmentId ??
|
|
11614
|
+
attachmentId: options?.attachmentId ?? randomUUID6(),
|
|
11702
11615
|
files: [{ source: filePath, descriptor: {
|
|
11703
11616
|
mimeType,
|
|
11704
11617
|
sizeBytes: fileStat.size,
|
|
@@ -11727,7 +11640,7 @@ async function sendMediaFileMessage(client, conversationId, filePath, text = "",
|
|
|
11727
11640
|
const current = await client.getConversation(conversationId);
|
|
11728
11641
|
return !!current && originalMembers.size > 0 && current.memberIds.every((id2) => originalMembers.has(id2)) && canPublish();
|
|
11729
11642
|
} : void 0;
|
|
11730
|
-
const messageId = sendOptions.messageId ??
|
|
11643
|
+
const messageId = sendOptions.messageId ?? randomUUID6();
|
|
11731
11644
|
const pipeline3 = await client.getFileMediaPipeline(fetchImpl);
|
|
11732
11645
|
const delivered = await pipeline3.send({
|
|
11733
11646
|
conversationId,
|
|
@@ -11909,7 +11822,6 @@ export {
|
|
|
11909
11822
|
splitTextWithinBytes,
|
|
11910
11823
|
readCliPackageVersion,
|
|
11911
11824
|
runCli,
|
|
11912
|
-
createCompletedOutputJournal,
|
|
11913
11825
|
BoundedFileReadError,
|
|
11914
11826
|
readBoundedRegularFileHandle,
|
|
11915
11827
|
DEFAULT_MEDIA_CACHE_DIR,
|