@canonmsg/agent-sdk 13.1.0 → 13.2.1
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 +1 -1
- package/dist/chunks/{chunk-YHDDOADK.js → chunk-2NVLVPSG.js} +1 -1
- package/dist/chunks/{chunk-YJUPKU47.js → chunk-BPSFHEAA.js} +2 -2
- package/dist/chunks/{chunk-ZPBOGOAD.js → chunk-HTLKCUQG.js} +353 -720
- package/dist/chunks/{chunk-DTAWEQCM.js → chunk-POBMFS2K.js} +2 -2
- package/dist/chunks/{chunk-IEKVS7AN.js → chunk-TJBG6NKF.js} +261 -97
- package/dist/chunks/{chunk-5WPAB5MB.js → chunk-VIS5WXNH.js} +0 -24
- package/dist/chunks/{mls-crypto-NZVMSUFU.js → mls-crypto-ZBEC26B5.js} +2 -2
- package/dist/cli.js +6 -6
- package/dist/index.js +6 -6
- 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 +17 -6
- package/dist/internal/endpoint/events.d.ts +1 -2
- package/dist/internal/endpoint/index.d.ts +1 -5
- package/dist/internal/endpoint/media.d.ts +2 -61
- package/dist/internal/endpoint/queue.d.ts +1 -3
- package/dist/media.js +4 -4
- package/dist/mls-wasm-host.js +3 -3
- package/dist/runner/errands.d.ts +2 -2
- package/dist/runner/runner.d.ts +6 -1
- package/package.json +2 -3
- package/dist/internal/core/completed-output-journal.d.ts +0 -34
- package/dist/internal/endpoint/artifact-images.d.ts +0 -60
- package/dist/internal/endpoint/artifact-reducer.d.ts +0 -35
- package/dist/internal/endpoint/artifact-views.d.ts +0 -113
- package/dist/internal/endpoint/artifacts.d.ts +0 -75
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
createHttpEndpointTransport,
|
|
6
6
|
createNodeEndpointStore,
|
|
7
7
|
validateEndpointContentBoundary
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-BPSFHEAA.js";
|
|
9
9
|
import {
|
|
10
10
|
CANON_ENDPOINT_PROTOCOL,
|
|
11
11
|
CANON_VERB_WIRE_SCHEMA_VERSION,
|
|
@@ -40,8 +40,6 @@ import {
|
|
|
40
40
|
isMlsDevTextProtocol,
|
|
41
41
|
isMlsEnvironmentId,
|
|
42
42
|
isMlsPrincipalAlias,
|
|
43
|
-
logicalMediaBlobId,
|
|
44
|
-
mergeDeliveryObservation,
|
|
45
43
|
mlsAdmissionWait,
|
|
46
44
|
mlsDevProtocolCarriesCards,
|
|
47
45
|
normalizePublicRuntimeDescriptor,
|
|
@@ -102,18 +100,17 @@ import {
|
|
|
102
100
|
parseMlsPrincipalOpening,
|
|
103
101
|
parseMlsRoomIdentities,
|
|
104
102
|
prepareJsonContent,
|
|
105
|
-
projectDeliveryStatus,
|
|
106
103
|
projectVerbIntentToWire,
|
|
107
104
|
readMembershipChangeMetadata,
|
|
108
105
|
resolveAllowedUploadMimeType,
|
|
109
106
|
sanitizeDownloadFileName
|
|
110
|
-
} from "./chunk-
|
|
107
|
+
} from "./chunk-VIS5WXNH.js";
|
|
111
108
|
|
|
112
109
|
// src/media.ts
|
|
113
110
|
import { createWriteStream as createWriteStream2 } from "node:fs";
|
|
114
|
-
import { randomUUID as
|
|
111
|
+
import { randomUUID as randomUUID6 } from "node:crypto";
|
|
115
112
|
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
|
|
113
|
+
import { basename, dirname as dirname2, extname, join as join4 } from "node:path";
|
|
117
114
|
import { Readable, Transform as Transform2 } from "node:stream";
|
|
118
115
|
import { pipeline as pipeline2 } from "node:stream/promises";
|
|
119
116
|
|
|
@@ -154,11 +151,11 @@ function createNodeEndpointMediaBytes(options) {
|
|
|
154
151
|
__name(digest, "digest");
|
|
155
152
|
return {
|
|
156
153
|
digest,
|
|
157
|
-
async retain(
|
|
154
|
+
async retain(key2, source) {
|
|
158
155
|
await initialized;
|
|
159
156
|
const expected = expectedDigests.get(source);
|
|
160
157
|
if (!expected) throw new Error("Canon media must be fingerprinted before retaining bytes");
|
|
161
|
-
const reference = `${createHash("sha256").update(
|
|
158
|
+
const reference = `${createHash("sha256").update(key2).digest("hex")}.bytes`;
|
|
162
159
|
const destination = referencePath(reference);
|
|
163
160
|
const temporary = `${destination}.${randomUUID()}.tmp`;
|
|
164
161
|
const sourceHandle = await open(source, "r");
|
|
@@ -522,8 +519,8 @@ async function readPreparationSnapshot(tx, scope5) {
|
|
|
522
519
|
__name(readPreparationSnapshot, "readPreparationSnapshot");
|
|
523
520
|
function assertPreparationAdmission(expected, current) {
|
|
524
521
|
if (!expected || !current.admission) return;
|
|
525
|
-
for (const
|
|
526
|
-
if (expected[
|
|
522
|
+
for (const key2 of ["conversationId", "membershipRevision", "admissionId"]) {
|
|
523
|
+
if (expected[key2] !== void 0 && current.admission[key2] !== void 0 && expected[key2] !== current.admission[key2]) {
|
|
527
524
|
throw new EndpointError("The authored admission changed before content could commit", { code: "PREPARATION_ADMISSION_CHANGED", retryable: false });
|
|
528
525
|
}
|
|
529
526
|
}
|
|
@@ -535,12 +532,12 @@ function assertGeneration(current, expected) {
|
|
|
535
532
|
__name(assertGeneration, "assertGeneration");
|
|
536
533
|
async function reservePreparedIdentityInTransaction(tx, input) {
|
|
537
534
|
assertGeneration(await tx.get(PREPARATION_GENERATION_KEY) ?? 0, input.installationGeneration);
|
|
538
|
-
const
|
|
539
|
-
const existing = await tx.get(
|
|
535
|
+
const key2 = `preparation/${input.operationId}`;
|
|
536
|
+
const existing = await tx.get(key2);
|
|
540
537
|
if (existing && (existing.action !== input.action || existing.fingerprint !== input.fingerprint || existing.installationGeneration !== input.installationGeneration)) {
|
|
541
538
|
throw new EndpointError("Operation ID was already reserved for different content", { status: 409, code: "OPERATION_ID_COLLISION", retryable: false, operationId: input.operationId });
|
|
542
539
|
}
|
|
543
|
-
if (!existing) await tx.put(
|
|
540
|
+
if (!existing) await tx.put(key2, input);
|
|
544
541
|
}
|
|
545
542
|
__name(reservePreparedIdentityInTransaction, "reservePreparedIdentityInTransaction");
|
|
546
543
|
async function reservePreparedIdentity(store, input) {
|
|
@@ -587,7 +584,6 @@ function endpointIntentOrdering(intent2) {
|
|
|
587
584
|
const body = { ...intent2.body, ...intent2.envelope };
|
|
588
585
|
if (intent2.action === "consume_runtime_signal") return "independent";
|
|
589
586
|
if (intent2.action === "generate_image") return "provider";
|
|
590
|
-
if (["append_artifact_command", "retain_artifact_asset"].includes(intent2.action) && typeof body.artifactId === "string") return "artifact";
|
|
591
587
|
if (intent2.action === "session_control" && !body.primitive && typeof body.agentId === "string") {
|
|
592
588
|
const type = body.signal?.type;
|
|
593
589
|
if (type === "interrupt" || type === "stop_and_drop") return "signal";
|
|
@@ -597,10 +593,10 @@ function endpointIntentOrdering(intent2) {
|
|
|
597
593
|
}
|
|
598
594
|
__name(endpointIntentOrdering, "endpointIntentOrdering");
|
|
599
595
|
async function nextQueueSequence(tx, name) {
|
|
600
|
-
const
|
|
601
|
-
const next = (await tx.get(
|
|
596
|
+
const key2 = `queue-sequence/${name}`;
|
|
597
|
+
const next = (await tx.get(key2) ?? 0) + 1;
|
|
602
598
|
if (!Number.isSafeInteger(next)) throw new Error("Endpoint queue sequence exhausted");
|
|
603
|
-
await tx.put(
|
|
599
|
+
await tx.put(key2, next);
|
|
604
600
|
return next;
|
|
605
601
|
}
|
|
606
602
|
__name(nextQueueSequence, "nextQueueSequence");
|
|
@@ -609,19 +605,17 @@ async function orderOperation(tx, operation) {
|
|
|
609
605
|
}
|
|
610
606
|
__name(orderOperation, "orderOperation");
|
|
611
607
|
var outboxKey = /* @__PURE__ */ __name((operation) => operation.queueOrder === void 0 ? `outbox/${operation.operationId}` : `outbox/${ordinal(operation.queueOrder)}/${operation.operationId}`, "outboxKey");
|
|
612
|
-
var artifactOrderingPrefix = /* @__PURE__ */ __name((conversationId, artifactId) => `outbound-artifact-order/${scope(conversationId)}/${scope(artifactId)}/`, "artifactOrderingPrefix");
|
|
613
608
|
var controlOrderingKey = /* @__PURE__ */ __name((operation) => operation.action === "session_control" && operation.conversationId && operation.agentId ? `outbound-control-order/${scope(operation.conversationId)}/${scope(operation.agentId)}/` : void 0, "controlOrderingKey");
|
|
614
609
|
var operationOrderingKey = /* @__PURE__ */ __name((operation) => {
|
|
615
610
|
if (!operation.conversationId || operation.ordering === "independent" || operation.ordering === "provider") return void 0;
|
|
616
611
|
if (operation.ordering === "signal") return controlOrderingKey(operation);
|
|
617
|
-
if (operation.ordering === "artifact") return artifactOrderingPrefix(operation.conversationId, operation.artifactId ?? "~");
|
|
618
612
|
return operation.ordering === "message" ? `outbound-ack-order/${scope(operation.conversationId)}/${scope(operation.messageId)}/` : `outbound-order/${scope(operation.conversationId)}/`;
|
|
619
613
|
}, "operationOrderingKey");
|
|
620
614
|
var outboundPrefix = /* @__PURE__ */ __name((operation) => operationOrderingKey(operation), "outboundPrefix");
|
|
621
615
|
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
616
|
async function trackOperationOrder(tx, operation) {
|
|
623
617
|
if (operation.queueOrder !== void 0 && !["succeeded", "rejected"].includes(operation.status)) {
|
|
624
|
-
for (const
|
|
618
|
+
for (const key2 of outboundKeys(operation)) await tx.put(key2, { operationId: operation.operationId });
|
|
625
619
|
}
|
|
626
620
|
}
|
|
627
621
|
__name(trackOperationOrder, "trackOperationOrder");
|
|
@@ -634,7 +628,7 @@ async function removeOperationOutbox(tx, operation) {
|
|
|
634
628
|
}
|
|
635
629
|
__name(removeOperationOutbox, "removeOperationOutbox");
|
|
636
630
|
async function settleOperationOrder(tx, operation) {
|
|
637
|
-
if (operation.queueOrder !== void 0) for (const
|
|
631
|
+
if (operation.queueOrder !== void 0) for (const key2 of outboundKeys(operation)) await tx.delete(key2);
|
|
638
632
|
}
|
|
639
633
|
__name(settleOperationOrder, "settleOperationOrder");
|
|
640
634
|
async function isFirstOrderedOperation(tx, operation) {
|
|
@@ -913,9 +907,9 @@ async function compactExpiredPayloads(store, clock) {
|
|
|
913
907
|
const rows = await tx.scan("payload-expiry/", { limit: 100 });
|
|
914
908
|
const expired = rows.filter((row) => row.value.expiresAt <= clock);
|
|
915
909
|
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(
|
|
910
|
+
const key2 = `operation/${row.value.operationId}`;
|
|
911
|
+
const value = await tx.get(key2);
|
|
912
|
+
if (value && ["succeeded", "rejected", "uncertain"].includes(value.status)) await tx.put(key2, compactOperation(value));
|
|
919
913
|
await tx.delete(row.key);
|
|
920
914
|
}
|
|
921
915
|
return expired.length;
|
|
@@ -958,7 +952,7 @@ async function eraseEndpointPlaintext(store) {
|
|
|
958
952
|
after = rows[rows.length - 1].key;
|
|
959
953
|
}
|
|
960
954
|
}
|
|
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/"]) {
|
|
955
|
+
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
956
|
for (; ; ) {
|
|
963
957
|
const count = await store.transaction(async (tx) => {
|
|
964
958
|
const rows = await tx.scan(prefix3, { limit: 100 });
|
|
@@ -1052,11 +1046,11 @@ function createEndpointQueryObserver(options) {
|
|
|
1052
1046
|
return {
|
|
1053
1047
|
observe(query, parameters, listener, onError) {
|
|
1054
1048
|
if (closed) throw new Error("Endpoint query observer is closed");
|
|
1055
|
-
const
|
|
1056
|
-
let entry = entries2.get(
|
|
1049
|
+
const key2 = canonicalEndpointJson({ query, parameters });
|
|
1050
|
+
let entry = entries2.get(key2);
|
|
1057
1051
|
if (!entry) {
|
|
1058
1052
|
entry = { query, parameters, listeners: /* @__PURE__ */ new Set(), running: false, dirty: false, hasValue: false, hadError: false, retryAttempts: 0 };
|
|
1059
|
-
entries2.set(
|
|
1053
|
+
entries2.set(key2, entry);
|
|
1060
1054
|
}
|
|
1061
1055
|
const observer = { value: listener, error: onError };
|
|
1062
1056
|
entry.listeners.add(observer);
|
|
@@ -1068,7 +1062,7 @@ function createEndpointQueryObserver(options) {
|
|
|
1068
1062
|
ownedEntry.listeners.delete(observer);
|
|
1069
1063
|
if (ownedEntry.listeners.size === 0) {
|
|
1070
1064
|
if (ownedEntry.timer) clearTimeout(ownedEntry.timer);
|
|
1071
|
-
entries2.delete(
|
|
1065
|
+
entries2.delete(key2);
|
|
1072
1066
|
}
|
|
1073
1067
|
};
|
|
1074
1068
|
},
|
|
@@ -1106,42 +1100,18 @@ var ROOM_QUERIES = /* @__PURE__ */ new Set([
|
|
|
1106
1100
|
"list_voice_sessions",
|
|
1107
1101
|
"get_voice_session"
|
|
1108
1102
|
]);
|
|
1109
|
-
var ARTIFACT_QUERIES = /* @__PURE__ */ new Set(["list_artifacts", "get_artifact_log", "get_artifact_asset"]);
|
|
1110
1103
|
var INBOX_QUERIES = /* @__PURE__ */ new Set(["list_conversations", "get_unread_summary"]);
|
|
1111
1104
|
var CONTACT_QUERIES = /* @__PURE__ */ new Set(["list_contacts", "get_contact", "list_contact_requests"]);
|
|
1112
1105
|
var ROOM_OPERATIONS = /* @__PURE__ */ new Set(["send_message", "mls_application", "mls_group_control", "react", "delete_message", "mark_read"]);
|
|
1113
|
-
var ARTIFACT_OPERATIONS = /* @__PURE__ */ new Set(["append_artifact_command", "retain_artifact_asset"]);
|
|
1114
1106
|
function queryAffectedByOperation(operation, query, parameters) {
|
|
1115
|
-
if (ARTIFACT_OPERATIONS.has(operation.action)) {
|
|
1116
|
-
if (operation.status !== "succeeded" || !ARTIFACT_QUERIES.has(query)) return false;
|
|
1117
|
-
return typeof parameters.conversationId !== "string" || !operation.conversationId || parameters.conversationId === operation.conversationId;
|
|
1118
|
-
}
|
|
1119
|
-
if (ARTIFACT_QUERIES.has(query)) return false;
|
|
1120
1107
|
if (!ROOM_OPERATIONS.has(operation.action)) return true;
|
|
1121
1108
|
if (operation.status !== "succeeded" && !(operation.action === "mls_application" && ["rejected", "uncertain"].includes(operation.status))) return false;
|
|
1122
1109
|
return queryAffectedByEvent({ kind: "conversation.changed", data: { conversationId: operation.conversationId } }, query, parameters);
|
|
1123
1110
|
}
|
|
1124
1111
|
__name(queryAffectedByOperation, "queryAffectedByOperation");
|
|
1125
|
-
function artifactQueryAffected(event, query, parameters) {
|
|
1126
|
-
const conversationId = parameters.conversationId;
|
|
1127
|
-
const sameRoom = typeof conversationId !== "string" || typeof event.data.conversationId !== "string" || event.data.conversationId === conversationId;
|
|
1128
|
-
if (event.kind === "artifact.changed") {
|
|
1129
|
-
if (!sameRoom) return false;
|
|
1130
|
-
return query === "list_artifacts" || typeof parameters.artifactId !== "string" || typeof event.data.artifactId !== "string" || parameters.artifactId === event.data.artifactId;
|
|
1131
|
-
}
|
|
1132
|
-
if (event.kind === "conversation.removed") return sameRoom;
|
|
1133
|
-
if (event.kind === "conversations.changed") {
|
|
1134
|
-
const removed = event.data.removedConversationIds;
|
|
1135
|
-
return Array.isArray(removed) && (typeof conversationId !== "string" || removed.includes(conversationId));
|
|
1136
|
-
}
|
|
1137
|
-
return false;
|
|
1138
|
-
}
|
|
1139
|
-
__name(artifactQueryAffected, "artifactQueryAffected");
|
|
1140
1112
|
function queryAffectedByEvent(event, query, parameters) {
|
|
1141
|
-
if (!ROOM_QUERIES.has(query) && !
|
|
1113
|
+
if (!ROOM_QUERIES.has(query) && !INBOX_QUERIES.has(query) && !CONTACT_QUERIES.has(query)) return true;
|
|
1142
1114
|
if (event.kind === "agent.context" || event.kind.startsWith("replay.")) return true;
|
|
1143
|
-
if (ARTIFACT_QUERIES.has(query)) return artifactQueryAffected(event, query, parameters);
|
|
1144
|
-
if (event.kind === "artifact.changed") return false;
|
|
1145
1115
|
if (/^contacts?\./.test(event.kind)) return CONTACT_QUERIES.has(query);
|
|
1146
1116
|
if (!/^(message\.|conversations?\.|voice\.session\.)/.test(event.kind)) return true;
|
|
1147
1117
|
if (CONTACT_QUERIES.has(query)) return false;
|
|
@@ -1172,10 +1142,10 @@ async function prepareEndpointIntent(input) {
|
|
|
1172
1142
|
const { intent: intent2 } = input;
|
|
1173
1143
|
const nested = intent2.action === "verb_execute";
|
|
1174
1144
|
const action = nested ? intent2.body.verb : intent2.action;
|
|
1175
|
-
if (!["remove_member", "update_member_role", "leave_conversation"
|
|
1145
|
+
if (!["remove_member", "update_member_role", "leave_conversation"].includes(String(action))) return intent2;
|
|
1176
1146
|
const body = intentBody(intent2);
|
|
1177
1147
|
const conversationId = body.conversationId ?? body.targetConversationId;
|
|
1178
|
-
const ownAdmission =
|
|
1148
|
+
const ownAdmission = action === "leave_conversation";
|
|
1179
1149
|
const targetUserId = body.targetUserId ?? body.userId ?? body.agentId;
|
|
1180
1150
|
if (typeof conversationId !== "string" || !ownAdmission && typeof targetUserId !== "string") {
|
|
1181
1151
|
throw new EndpointError("Membership control requires a conversation and target", { status: 400, code: "INVALID_MEMBERSHIP_CONTROL", retryable: false });
|
|
@@ -1224,7 +1194,7 @@ async function assertMlsDevPlaintextAllowed(tx, intent2, lane = "operation.reque
|
|
|
1224
1194
|
const privateLeaf = /* @__PURE__ */ __name((value, path) => {
|
|
1225
1195
|
if (value && typeof value === "object") {
|
|
1226
1196
|
const entries2 = Object.entries(value);
|
|
1227
|
-
if (entries2.length) return entries2.some(([
|
|
1197
|
+
if (entries2.length) return entries2.some(([key2, child]) => privateLeaf(child, [...path, key2]));
|
|
1228
1198
|
}
|
|
1229
1199
|
return boundary.classify(path) !== "public-metadata";
|
|
1230
1200
|
}, "privateLeaf");
|
|
@@ -1263,8 +1233,8 @@ function normalizeInboundDeletion(event) {
|
|
|
1263
1233
|
__name(normalizeInboundDeletion, "normalizeInboundDeletion");
|
|
1264
1234
|
async function recordInboundEvent(tx, incoming, timestamp) {
|
|
1265
1235
|
const event = normalizeInboundDeletion(incoming);
|
|
1266
|
-
const
|
|
1267
|
-
const existing = await tx.get(
|
|
1236
|
+
const key2 = `inbound/${event.id}`;
|
|
1237
|
+
const existing = await tx.get(key2);
|
|
1268
1238
|
if (event.conversationId && ["message.created", "message.snapshot", "message.updated"].includes(event.kind) && await isMlsDevRoomProtected(tx, event.conversationId)) {
|
|
1269
1239
|
if (existing) return false;
|
|
1270
1240
|
const raw = event.kind === "message.snapshot" ? event.data : event.data.message;
|
|
@@ -1278,7 +1248,7 @@ async function recordInboundEvent(tx, incoming, timestamp) {
|
|
|
1278
1248
|
durable: true,
|
|
1279
1249
|
data: notice ? { id: message.id, metadata, text: formatMembershipChangeNotice(metadata), contentType: "text" } : {}
|
|
1280
1250
|
};
|
|
1281
|
-
await tx.put(
|
|
1251
|
+
await tx.put(key2, {
|
|
1282
1252
|
event: sanitized,
|
|
1283
1253
|
state: "settled",
|
|
1284
1254
|
reason: notice ? "membership_control" : "mls_plaintext_withheld",
|
|
@@ -1312,12 +1282,12 @@ async function recordInboundEvent(tx, incoming, timestamp) {
|
|
|
1312
1282
|
};
|
|
1313
1283
|
delete next.lease;
|
|
1314
1284
|
if (existing) await removeInboundReady(tx, existing);
|
|
1315
|
-
await tx.put(
|
|
1285
|
+
await tx.put(key2, next);
|
|
1316
1286
|
return true;
|
|
1317
1287
|
}
|
|
1318
1288
|
if (existing) return false;
|
|
1319
1289
|
const record6 = await orderInbound(tx, { event: copyJson(event), state: "observed", createdAt: timestamp, updatedAt: timestamp });
|
|
1320
|
-
await tx.put(
|
|
1290
|
+
await tx.put(key2, record6);
|
|
1321
1291
|
await enqueueInbound(tx, record6);
|
|
1322
1292
|
return true;
|
|
1323
1293
|
}
|
|
@@ -1340,14 +1310,14 @@ function serializeInboundPreparation(store, scope5, prepare) {
|
|
|
1340
1310
|
}
|
|
1341
1311
|
__name(serializeInboundPreparation, "serializeInboundPreparation");
|
|
1342
1312
|
async function recordMlsInteractionProjection(tx, event, timestamp) {
|
|
1343
|
-
const
|
|
1313
|
+
const key2 = `inbound/${event.id}`, existing = await tx.get(key2);
|
|
1344
1314
|
if (existing && existing.reason !== "mls_plaintext_withheld") return;
|
|
1345
|
-
await tx.put(
|
|
1315
|
+
await tx.put(key2, { event: copyJson(event), state: "settled", reason: "mls_interaction", createdAt: timestamp, updatedAt: timestamp });
|
|
1346
1316
|
}
|
|
1347
1317
|
__name(recordMlsInteractionProjection, "recordMlsInteractionProjection");
|
|
1348
1318
|
async function recordVerifiedMlsInboundEvent(tx, event, feedOrdinal, timestamp, own = false) {
|
|
1349
1319
|
if (!event.conversationId || !Number.isSafeInteger(feedOrdinal) || feedOrdinal < 1) throw new Error("Invalid verified MLS input");
|
|
1350
|
-
const
|
|
1320
|
+
const key2 = `inbound/${event.id}`, existing = await tx.get(key2);
|
|
1351
1321
|
if (existing && existing.reason !== "mls_plaintext_withheld") return false;
|
|
1352
1322
|
const record6 = {
|
|
1353
1323
|
event: copyJson(event),
|
|
@@ -1356,7 +1326,7 @@ async function recordVerifiedMlsInboundEvent(tx, event, feedOrdinal, timestamp,
|
|
|
1356
1326
|
createdAt: timestamp,
|
|
1357
1327
|
updatedAt: timestamp
|
|
1358
1328
|
};
|
|
1359
|
-
await tx.put(
|
|
1329
|
+
await tx.put(key2, record6);
|
|
1360
1330
|
if (!own) await enqueueInbound(tx, record6);
|
|
1361
1331
|
return true;
|
|
1362
1332
|
}
|
|
@@ -1563,8 +1533,8 @@ function createConversationSynchronizer(input) {
|
|
|
1563
1533
|
if (protocolPage) return protocolPage;
|
|
1564
1534
|
const protectedMode = await input.prepareProtocol?.(conversationId) ?? false;
|
|
1565
1535
|
assertOpen();
|
|
1566
|
-
const
|
|
1567
|
-
const before = await input.store.transaction((tx) => tx.get(
|
|
1536
|
+
const key2 = `cursor/${conversationId}`;
|
|
1537
|
+
const before = await input.store.transaction((tx) => tx.get(key2));
|
|
1568
1538
|
const resuming = before?.paused !== void 0 && options.resumeAdmissionChange === "checkpoint";
|
|
1569
1539
|
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
1540
|
if (!before && !options.bootstrap) throw new EndpointError("Choose a reconciled history baseline before receiving conversation work", { code: "SYNC_BASELINE_REQUIRED", retryable: false });
|
|
@@ -1608,11 +1578,11 @@ function createConversationSynchronizer(input) {
|
|
|
1608
1578
|
commit: /* @__PURE__ */ __name(async (tx, preparedChanges) => {
|
|
1609
1579
|
assertOpen();
|
|
1610
1580
|
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(
|
|
1581
|
+
const current = await tx.get(key2);
|
|
1612
1582
|
if (canonicalEndpointJson(current ?? null) !== canonicalEndpointJson(before ?? null)) return false;
|
|
1613
1583
|
if (page.reset) {
|
|
1614
|
-
if (options.pauseOnAdmissionChange) await tx.put(
|
|
1615
|
-
else await tx.delete(
|
|
1584
|
+
if (options.pauseOnAdmissionChange) await tx.put(key2, { ...before ?? { cursor: null, admissionId: null, mode, bootstrapping: false }, paused: { admissionId: page.admissionId } });
|
|
1585
|
+
else await tx.delete(key2);
|
|
1616
1586
|
return true;
|
|
1617
1587
|
}
|
|
1618
1588
|
for (const event of preparedChanges) {
|
|
@@ -1620,7 +1590,7 @@ function createConversationSynchronizer(input) {
|
|
|
1620
1590
|
if (mode === "checkpoint") continue;
|
|
1621
1591
|
await recordInboundEvent(tx, event, new Date(input.clock()).toISOString());
|
|
1622
1592
|
}
|
|
1623
|
-
await tx.put(
|
|
1593
|
+
await tx.put(key2, { cursor: page.cursor, admissionId: page.admissionId, bootstrapping: page.bootstrap && page.hasMore, mode });
|
|
1624
1594
|
return true;
|
|
1625
1595
|
}, "commit")
|
|
1626
1596
|
}));
|
|
@@ -2035,7 +2005,7 @@ __name(createEndpointMigration, "createEndpointMigration");
|
|
|
2035
2005
|
|
|
2036
2006
|
// ../endpoint/src/media.ts
|
|
2037
2007
|
function createEndpointMediaPipeline(options) {
|
|
2038
|
-
const { endpoint, store, bytes
|
|
2008
|
+
const { endpoint, store, bytes } = options;
|
|
2039
2009
|
const clock = options.clock ?? Date.now;
|
|
2040
2010
|
const ids = options.ids ?? (() => crypto.randomUUID());
|
|
2041
2011
|
const leaseMs = Math.max(1e3, options.leaseMs ?? 3e4);
|
|
@@ -2090,14 +2060,6 @@ function createEndpointMediaPipeline(options) {
|
|
|
2090
2060
|
const listeners = /* @__PURE__ */ new Set();
|
|
2091
2061
|
let closed = false;
|
|
2092
2062
|
let foreground = true;
|
|
2093
|
-
let restoredNativeObservations = false;
|
|
2094
|
-
const reconciliation = /* @__PURE__ */ new Map();
|
|
2095
|
-
const observations = /* @__PURE__ */ new Map();
|
|
2096
|
-
let backgroundCapability;
|
|
2097
|
-
let lastServerBackgroundCapability;
|
|
2098
|
-
let serverPreparedContentVersion;
|
|
2099
|
-
const preparationGeneration = background ? endpoint.ready().then(() => readPreparationGeneration(store)) : Promise.resolve(0);
|
|
2100
|
-
void preparationGeneration.catch(() => void 0);
|
|
2101
2063
|
let timer;
|
|
2102
2064
|
const prefix3 = "media-flow/";
|
|
2103
2065
|
const attachmentFor = /* @__PURE__ */ __name((file, value, resultUploadId) => {
|
|
@@ -2117,7 +2079,6 @@ function createEndpointMediaPipeline(options) {
|
|
|
2117
2079
|
for (const file of previous?.files ?? []) if (file.uploadId && !currentIds.has(file.uploadId)) await tx.delete(`media-prepared-upload/${file.uploadId}`);
|
|
2118
2080
|
for (const uploadId of currentIds) await tx.put(`media-prepared-upload/${uploadId}`, { id: flow.id });
|
|
2119
2081
|
}
|
|
2120
|
-
if (flow.status === "succeeded" || flow.status === "cancelled") delete flow.preparedContent;
|
|
2121
2082
|
await tx.put(`${prefix3}${flow.id}`, flow);
|
|
2122
2083
|
if (flow.status === "pending" || flow.status === "sending") await tx.put(`media-pending/${flow.id}`, { id: flow.id });
|
|
2123
2084
|
else await tx.delete(`media-pending/${flow.id}`);
|
|
@@ -2158,10 +2119,6 @@ function createEndpointMediaPipeline(options) {
|
|
|
2158
2119
|
timer.unref?.();
|
|
2159
2120
|
}
|
|
2160
2121
|
__name(schedule, "schedule");
|
|
2161
|
-
const stopConnections = background ? endpoint.observeEvents?.(() => void 0, { families: ["messages"], onConnected: /* @__PURE__ */ __name(() => {
|
|
2162
|
-
for (const state of reconciliation.values()) state.nextAt = Math.min(state.nextAt, clock() + 5e3);
|
|
2163
|
-
schedule();
|
|
2164
|
-
}, "onConnected") }) : void 0;
|
|
2165
2122
|
const unsubscribe = endpoint.subscribe((operation) => {
|
|
2166
2123
|
if (operation.status === "succeeded" && operation.action.endsWith("_upload")) schedule();
|
|
2167
2124
|
});
|
|
@@ -2170,265 +2127,21 @@ function createEndpointMediaPipeline(options) {
|
|
|
2170
2127
|
await Promise.allSettled(flow.files.filter((file) => file.reference).map((file) => bytes.release(file.reference)));
|
|
2171
2128
|
}
|
|
2172
2129
|
__name(release, "release");
|
|
2173
|
-
function probeBackgroundCapability() {
|
|
2174
|
-
if (!background) return Promise.resolve(false);
|
|
2175
|
-
if (!backgroundCapability) {
|
|
2176
|
-
const check = Promise.resolve().then(() => Promise.all([background.available(), endpoint.capabilities()])).then(([nativeAvailable, capabilities]) => {
|
|
2177
|
-
if (capabilities) serverPreparedContentVersion = capabilities.preparedMediaContentVersion;
|
|
2178
|
-
if (capabilities) lastServerBackgroundCapability = capabilities.actions.includes("stage_media_message") && (background.observationVersion !== 2 || capabilities.mediaDeliveryObservationVersion === 2);
|
|
2179
|
-
return nativeAvailable && lastServerBackgroundCapability === true;
|
|
2180
|
-
}).catch(() => false).then((enabled) => {
|
|
2181
|
-
if (!enabled && backgroundCapability === check) backgroundCapability = void 0;
|
|
2182
|
-
return enabled;
|
|
2183
|
-
});
|
|
2184
|
-
backgroundCapability = check;
|
|
2185
|
-
}
|
|
2186
|
-
return backgroundCapability;
|
|
2187
|
-
}
|
|
2188
|
-
__name(probeBackgroundCapability, "probeBackgroundCapability");
|
|
2189
2130
|
const encryptedRoom = /* @__PURE__ */ __name(async (conversationId) => await endpoint.getConversationContentMode(conversationId) !== "plaintext", "encryptedRoom");
|
|
2190
|
-
async function
|
|
2191
|
-
if (!background) return false;
|
|
2192
|
-
if (options2?.conversationId !== void 0 && await encryptedRoom(options2.conversationId).catch(() => true)) return false;
|
|
2193
|
-
if (options2?.maxProbeWaitMs === void 0) return probeBackgroundCapability();
|
|
2194
|
-
const nativeAvailable = await Promise.resolve().then(() => background.available()).catch(() => false);
|
|
2195
|
-
if (!nativeAvailable) return false;
|
|
2196
|
-
if (lastServerBackgroundCapability === false) {
|
|
2197
|
-
void probeBackgroundCapability();
|
|
2198
|
-
return false;
|
|
2199
|
-
}
|
|
2200
|
-
const check = probeBackgroundCapability();
|
|
2201
|
-
const waitMs = Math.max(0, options2.maxProbeWaitMs);
|
|
2202
|
-
if (waitMs === 0) return false;
|
|
2203
|
-
return new Promise((resolve3) => {
|
|
2204
|
-
const timer2 = setTimeout(() => resolve3(false), waitMs);
|
|
2205
|
-
void check.then((enabled) => {
|
|
2206
|
-
clearTimeout(timer2);
|
|
2207
|
-
resolve3(enabled);
|
|
2208
|
-
});
|
|
2209
|
-
});
|
|
2210
|
-
}
|
|
2211
|
-
__name(canSendInBackground, "canSendInBackground");
|
|
2212
|
-
async function applyBackgroundSnapshot(snapshot) {
|
|
2213
|
-
if (closed || !background) return;
|
|
2214
|
-
const flow = await read(snapshot.deliveryId);
|
|
2215
|
-
if (!flow || flow.deliveryMode !== "background") return;
|
|
2216
|
-
if (snapshot.schemaVersion === 2) {
|
|
2217
|
-
const identity = await endpoint.ready();
|
|
2218
|
-
if (!snapshot.identity || snapshot.identity.installationId !== identity.installationId || snapshot.identity.environmentId !== identity.environmentId || snapshot.identity.conversationId !== flow.conversationId || snapshot.identity.messageId !== flow.messageId) {
|
|
2219
|
-
schedule();
|
|
2220
|
-
return;
|
|
2221
|
-
}
|
|
2222
|
-
}
|
|
2223
|
-
const owner = await acquire(flow.id).catch(() => null);
|
|
2224
|
-
if (!owner) {
|
|
2225
|
-
schedule();
|
|
2226
|
-
return;
|
|
2227
|
-
}
|
|
2228
|
-
try {
|
|
2229
|
-
const current = await read(flow.id);
|
|
2230
|
-
if (!current || current.deliveryMode !== "background" || current.status === "succeeded" || current.status === "cancelled") return;
|
|
2231
|
-
if (snapshot.schemaVersion === 2) {
|
|
2232
|
-
const identity = await endpoint.ready();
|
|
2233
|
-
if (snapshot.observation) current.observation = mergeDeliveryObservation(current.observation, snapshot.observation, {
|
|
2234
|
-
deliveryId: flow.id,
|
|
2235
|
-
conversationId: flow.conversationId,
|
|
2236
|
-
messageId: flow.messageId,
|
|
2237
|
-
installationId: identity.installationId,
|
|
2238
|
-
environmentId: identity.environmentId
|
|
2239
|
-
});
|
|
2240
|
-
current.attention = snapshot.attention;
|
|
2241
|
-
const phase = ["published", "cancelled", "expired", "rejected"].includes(snapshot.status) ? "idle" : snapshot.status;
|
|
2242
|
-
current.status = projectDeliveryStatus(current.observation?.outcome ?? "unknown", phase);
|
|
2243
|
-
current.background = { phase: snapshot.status, uploadedBytes: Math.max(0, snapshot.uploadedBytes), totalBytes: Math.max(0, snapshot.totalBytes) };
|
|
2244
|
-
if (current.attention) current.error = { code: current.attention.code, message: snapshot.errorMessage ?? "Delivery needs attention. Its server outcome is being checked." };
|
|
2245
|
-
else delete current.error;
|
|
2246
|
-
} else {
|
|
2247
|
-
current.background = {
|
|
2248
|
-
phase: snapshot.status,
|
|
2249
|
-
uploadedBytes: Math.max(0, snapshot.uploadedBytes),
|
|
2250
|
-
totalBytes: Math.max(0, snapshot.totalBytes)
|
|
2251
|
-
};
|
|
2252
|
-
if (snapshot.status === "published") {
|
|
2253
|
-
current.status = "succeeded";
|
|
2254
|
-
delete current.error;
|
|
2255
|
-
} else if (snapshot.status === "cancelled") {
|
|
2256
|
-
current.status = "cancelled";
|
|
2257
|
-
delete current.error;
|
|
2258
|
-
} else if (snapshot.status === "expired" || snapshot.status === "rejected") {
|
|
2259
|
-
current.status = "rejected";
|
|
2260
|
-
current.error = { code: snapshot.errorCode ?? (snapshot.status === "expired" ? "MEDIA_BACKGROUND_EXPIRED" : "MEDIA_BACKGROUND_REJECTED"), message: snapshot.errorMessage ?? "Attachment delivery needs attention" };
|
|
2261
|
-
} else {
|
|
2262
|
-
current.status = snapshot.status === "sending" ? "sending" : "pending";
|
|
2263
|
-
delete current.error;
|
|
2264
|
-
}
|
|
2265
|
-
}
|
|
2266
|
-
await write(current, owner);
|
|
2267
|
-
if (current.status === "succeeded" || current.status === "cancelled") {
|
|
2268
|
-
delete current.attention;
|
|
2269
|
-
delete current.error;
|
|
2270
|
-
await Promise.allSettled(current.files.filter((file) => file.descriptor.cleanupAfterSend).map((file) => background.cleanupSource?.(file.reference)));
|
|
2271
|
-
await write({ ...current, message: {}, files: [] }, owner);
|
|
2272
|
-
}
|
|
2273
|
-
if (current.status === "pending" || current.status === "sending") schedule();
|
|
2274
|
-
} finally {
|
|
2275
|
-
await unlock(flow.id, owner);
|
|
2276
|
-
}
|
|
2277
|
-
}
|
|
2278
|
-
__name(applyBackgroundSnapshot, "applyBackgroundSnapshot");
|
|
2279
|
-
async function authoritativeSnapshot(id2) {
|
|
2280
|
-
if (!background?.reconcile || !foreground) return void 0;
|
|
2281
|
-
const state = reconciliation.get(id2) ?? { nextAt: 0, failures: 0 };
|
|
2282
|
-
if (state.pending) return state.pending;
|
|
2283
|
-
if (clock() < state.nextAt) {
|
|
2284
|
-
schedule();
|
|
2285
|
-
return void 0;
|
|
2286
|
-
}
|
|
2287
|
-
state.nextAt = clock() + 5e3;
|
|
2288
|
-
const pending = background.reconcile(id2).then((snapshot) => {
|
|
2289
|
-
state.failures = 0;
|
|
2290
|
-
state.nextAt = clock() + 5e3;
|
|
2291
|
-
return snapshot;
|
|
2292
|
-
}, (error) => {
|
|
2293
|
-
state.failures += 1;
|
|
2294
|
-
state.nextAt = clock() + Math.min(3e5, 5e3 * 2 ** Math.min(6, state.failures));
|
|
2295
|
-
schedule();
|
|
2296
|
-
throw error;
|
|
2297
|
-
}).finally(() => {
|
|
2298
|
-
delete state.pending;
|
|
2299
|
-
});
|
|
2300
|
-
state.pending = pending;
|
|
2301
|
-
reconciliation.set(id2, state);
|
|
2302
|
-
return pending;
|
|
2303
|
-
}
|
|
2304
|
-
__name(authoritativeSnapshot, "authoritativeSnapshot");
|
|
2305
|
-
async function resumeBackground(flow, onProgress) {
|
|
2306
|
-
if (!background) throw new EndpointError("This delivery needs a Canon version with background transfer support", { code: "MEDIA_BACKGROUND_MODULE_UNAVAILABLE", retryable: false });
|
|
2131
|
+
async function retireBackground(flow) {
|
|
2307
2132
|
if (flow.status === "succeeded") return flow.messageId;
|
|
2308
|
-
if (flow.status
|
|
2309
|
-
|
|
2310
|
-
let snapshot = snapshots.find((item) => item.deliveryId === flow.id);
|
|
2311
|
-
if (!snapshot && flow.background?.phase === "cancelling") {
|
|
2312
|
-
throw new EndpointError("Cancellation is being reconciled", { code: "MEDIA_CANCELLING", retryable: true });
|
|
2313
|
-
}
|
|
2314
|
-
if (!snapshot) {
|
|
2315
|
-
const identity = await endpoint.ready();
|
|
2316
|
-
snapshot = await background.enqueue({
|
|
2317
|
-
deliveryId: flow.id,
|
|
2318
|
-
installationId: identity.installationId,
|
|
2319
|
-
conversationId: flow.conversationId,
|
|
2320
|
-
messageId: flow.messageId,
|
|
2321
|
-
message: copyJson(flow.message),
|
|
2322
|
-
...flow.preparedContent ? { preparedContent: copyJson(flow.preparedContent) } : {},
|
|
2323
|
-
files: flow.files.map((file) => ({ source: file.reference, descriptor: copyJson(file.descriptor) }))
|
|
2324
|
-
});
|
|
2325
|
-
}
|
|
2326
|
-
await applyBackgroundSnapshot(snapshot);
|
|
2327
|
-
if (background.reconcile) {
|
|
2328
|
-
const authoritative = await authoritativeSnapshot(flow.id).catch(() => void 0);
|
|
2329
|
-
if (authoritative) {
|
|
2330
|
-
snapshot = authoritative;
|
|
2331
|
-
await applyBackgroundSnapshot(snapshot);
|
|
2332
|
-
}
|
|
2333
|
-
schedule();
|
|
2334
|
-
}
|
|
2335
|
-
if (snapshot.schemaVersion !== 2 && (snapshot.status === "expired" || snapshot.status === "rejected")) {
|
|
2336
|
-
throw new EndpointError(snapshot.errorMessage ?? "Attach this file again to send it", {
|
|
2337
|
-
code: snapshot.errorCode ?? (snapshot.status === "expired" ? "MEDIA_BACKGROUND_EXPIRED" : "MEDIA_BACKGROUND_REJECTED"),
|
|
2338
|
-
retryable: false
|
|
2339
|
-
});
|
|
2340
|
-
}
|
|
2341
|
-
const phase = snapshot.status === "waiting_for_connection" ? "waiting_for_connection" : snapshot.status === "cancelling" ? "cancelling" : snapshot.status === "sending" || snapshot.status === "published" ? "sending" : snapshot.status === "uploading" ? "uploading" : "preparing";
|
|
2342
|
-
onProgress?.({ phase, uploadedBytes: snapshot.uploadedBytes, totalBytes: snapshot.totalBytes });
|
|
2343
|
-
return flow.messageId;
|
|
2344
|
-
}
|
|
2345
|
-
__name(resumeBackground, "resumeBackground");
|
|
2346
|
-
async function stageBackground(input, sendOptions) {
|
|
2347
|
-
if (!background) throw new Error("Background media adapter is missing");
|
|
2348
|
-
const id2 = flowId(input.conversationId, input.messageId);
|
|
2349
|
-
const existing = staging.get(id2);
|
|
2350
|
-
if (existing) {
|
|
2351
|
-
await existing;
|
|
2352
|
-
return stageBackground(input, sendOptions);
|
|
2353
|
-
}
|
|
2354
|
-
const staged = (async () => {
|
|
2355
|
-
if (closed || sendOptions?.signal?.aborted) throw new DOMException("Attachment cancelled", "AbortError");
|
|
2356
|
-
const fingerprint = await hash(canonicalEndpointJson({ message: input.message, files: input.files.map((file) => ({ descriptor: file.descriptor, source: background.sourceIdentity(file.source) })) }));
|
|
2357
|
-
if (closed || sendOptions?.signal?.aborted) throw new DOMException("Attachment cancelled", "AbortError");
|
|
2358
|
-
const owner = await acquire(id2);
|
|
2133
|
+
if (flow.status !== "cancelled") {
|
|
2134
|
+
const owner = await acquire(flow.id);
|
|
2359
2135
|
try {
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
if (prior) {
|
|
2363
|
-
if (prior.deliveryMode !== "background" || prior.fingerprint !== fingerprint) throw new EndpointError("Message identity already belongs to another attachment", { status: 409, code: "MEDIA_FLOW_CONFLICT", retryable: false });
|
|
2364
|
-
} else {
|
|
2365
|
-
const flow = {
|
|
2366
|
-
id: id2,
|
|
2367
|
-
conversationId: input.conversationId,
|
|
2368
|
-
messageId: input.messageId,
|
|
2369
|
-
message: copyJson(input.message),
|
|
2370
|
-
fingerprint,
|
|
2371
|
-
createdAt: new Date(clock()).toISOString(),
|
|
2372
|
-
files: input.files.map((file) => ({ descriptor: copyJson(file.descriptor), reference: background.sourceIdentity(file.source) })),
|
|
2373
|
-
deliveryMode: "background",
|
|
2374
|
-
status: "pending",
|
|
2375
|
-
background: { phase: "preparing", uploadedBytes: 0, totalBytes: input.files.reduce((sum, file) => sum + file.descriptor.sizeBytes, 0) }
|
|
2376
|
-
};
|
|
2377
|
-
if (background.preparedContentVersion === 1 && serverPreparedContentVersion === 1) {
|
|
2378
|
-
const generation2 = await preparationGeneration;
|
|
2379
|
-
const operationId = `media:${id2}`;
|
|
2380
|
-
await reservePreparedIdentity(store, { operationId, action: "send_message", fingerprint, installationGeneration: generation2 });
|
|
2381
|
-
const committed = await prepareAndCommitContent({
|
|
2382
|
-
store,
|
|
2383
|
-
scope: input.conversationId,
|
|
2384
|
-
installationGeneration: generation2,
|
|
2385
|
-
existing: /* @__PURE__ */ __name((tx) => tx.get(`${prefix3}${id2}`), "existing"),
|
|
2386
|
-
prepare: /* @__PURE__ */ __name(async (snapshot) => ({ nextProtocolState: snapshot.protocolState, payload: prepareJsonContent({
|
|
2387
|
-
context: { action: "send_message", conversationId: input.conversationId, messageId: input.messageId },
|
|
2388
|
-
body: { ...input.message, conversationId: input.conversationId, messageId: input.messageId },
|
|
2389
|
-
blobs: input.files.map((_, index) => ({ id: logicalMediaBlobId(input.conversationId, input.messageId, index), index })),
|
|
2390
|
-
expected: { installationGeneration: generation2, protocolRevision: snapshot.protocolRevision, ...snapshot.admission ? { admission: snapshot.admission } : {} }
|
|
2391
|
-
}) }), "prepare"),
|
|
2392
|
-
commit: /* @__PURE__ */ __name(async (tx, preparedContent) => {
|
|
2393
|
-
if (closed || sendOptions?.signal?.aborted) throw new DOMException("Attachment cancelled", "AbortError");
|
|
2394
|
-
const sealed = { ...flow, preparedContent };
|
|
2395
|
-
await writeInTransaction(tx, sealed, owner);
|
|
2396
|
-
await tx.delete(`preparation/${operationId}`);
|
|
2397
|
-
return sealed;
|
|
2398
|
-
}, "commit")
|
|
2399
|
-
});
|
|
2400
|
-
notifyFlow(committed);
|
|
2401
|
-
} else await write(flow, owner);
|
|
2402
|
-
}
|
|
2136
|
+
const { id: id2, conversationId, messageId, createdAt, deliveryMode } = flow;
|
|
2137
|
+
await write({ id: id2, conversationId, messageId, createdAt, deliveryMode, message: {}, files: [], status: "cancelled" }, owner);
|
|
2403
2138
|
} finally {
|
|
2404
|
-
await unlock(
|
|
2405
|
-
}
|
|
2406
|
-
const onAbort = /* @__PURE__ */ __name(() => {
|
|
2407
|
-
void pipeline3.cancel(id2).catch(() => void 0);
|
|
2408
|
-
}, "onAbort");
|
|
2409
|
-
sendOptions?.signal?.addEventListener("abort", onAbort, { once: true });
|
|
2410
|
-
try {
|
|
2411
|
-
if (sendOptions?.signal?.aborted) onAbort();
|
|
2412
|
-
return await resumeBackground(await read(id2), sendOptions?.onProgress);
|
|
2413
|
-
} finally {
|
|
2414
|
-
sendOptions?.signal?.removeEventListener("abort", onAbort);
|
|
2139
|
+
await unlock(flow.id, owner);
|
|
2415
2140
|
}
|
|
2416
|
-
})();
|
|
2417
|
-
staging.set(id2, staged);
|
|
2418
|
-
try {
|
|
2419
|
-
return await staged;
|
|
2420
|
-
} finally {
|
|
2421
|
-
if (staging.get(id2) === staged) staging.delete(id2);
|
|
2422
2141
|
}
|
|
2142
|
+
throw new EndpointError("Attachment was cancelled", { code: "MEDIA_FLOW_CANCELLED", retryable: false });
|
|
2423
2143
|
}
|
|
2424
|
-
__name(
|
|
2425
|
-
const stopBackground = background?.subscribe((snapshot) => {
|
|
2426
|
-
const next = (observations.get(snapshot.deliveryId) ?? Promise.resolve()).then(() => applyBackgroundSnapshot(snapshot)).catch(() => schedule());
|
|
2427
|
-
observations.set(snapshot.deliveryId, next);
|
|
2428
|
-
void next.finally(() => {
|
|
2429
|
-
if (observations.get(snapshot.deliveryId) === next) observations.delete(snapshot.deliveryId);
|
|
2430
|
-
});
|
|
2431
|
-
});
|
|
2144
|
+
__name(retireBackground, "retireBackground");
|
|
2432
2145
|
async function stage(input, sendOptions, prepareOnly = false) {
|
|
2433
2146
|
if (closed) throw new Error("Endpoint media pipeline is closed");
|
|
2434
2147
|
const id2 = flowId(input.conversationId, input.messageId);
|
|
@@ -2470,7 +2183,7 @@ function createEndpointMediaPipeline(options) {
|
|
|
2470
2183
|
files.push({ descriptor: copyJson(file.descriptor), reference: await bytes.retain(`${id2}:${fingerprint}:${index}`, file.source) });
|
|
2471
2184
|
}
|
|
2472
2185
|
checkOpen();
|
|
2473
|
-
await write({ id: id2, ...prepareOnly ? { purpose: "prepare" } : {}, conversationId: input.conversationId, messageId: input.messageId, fingerprint, createdAt: new Date(clock()).toISOString(), ...sendOptions?.beforePublish ? { requiresPublicationApproval: true } : {}, message: copyJson(input.message), files,
|
|
2186
|
+
await write({ id: id2, ...prepareOnly ? { purpose: "prepare" } : {}, conversationId: input.conversationId, messageId: input.messageId, fingerprint, createdAt: new Date(clock()).toISOString(), ...sendOptions?.beforePublish ? { requiresPublicationApproval: true } : {}, message: copyJson(input.message), files, status: "pending" }, owner);
|
|
2474
2187
|
}
|
|
2475
2188
|
} catch (error) {
|
|
2476
2189
|
await Promise.allSettled(files.map((file) => bytes.release(file.reference)));
|
|
@@ -2492,18 +2205,9 @@ function createEndpointMediaPipeline(options) {
|
|
|
2492
2205
|
}
|
|
2493
2206
|
__name(stage, "stage");
|
|
2494
2207
|
const pipeline3 = {
|
|
2495
|
-
canSendInBackground,
|
|
2496
2208
|
async send(input, sendOptions) {
|
|
2497
|
-
const
|
|
2498
|
-
|
|
2499
|
-
if (prior?.deliveryMode === "background") return stageBackground(input, sendOptions);
|
|
2500
|
-
if (!prior && !sendOptions?.beforePublish && sendOptions?.handoffMode !== "legacy") {
|
|
2501
|
-
const encrypted = await encryptedRoom(input.conversationId);
|
|
2502
|
-
if (encrypted && sendOptions?.handoffMode === "background") {
|
|
2503
|
-
throw new EndpointError("An encrypted room's attachments are prepared on this device", { code: "MEDIA_BACKGROUND_UNAVAILABLE", retryable: false });
|
|
2504
|
-
}
|
|
2505
|
-
if (!encrypted && (sendOptions?.handoffMode === "background" || await canSendInBackground())) return stageBackground(input, sendOptions);
|
|
2506
|
-
}
|
|
2209
|
+
const prior = await read(flowId(input.conversationId, input.messageId));
|
|
2210
|
+
if (prior?.deliveryMode === "background") return retireBackground(prior);
|
|
2507
2211
|
return stage(input, sendOptions);
|
|
2508
2212
|
},
|
|
2509
2213
|
async prepare(input, prepareOptions) {
|
|
@@ -2515,7 +2219,7 @@ function createEndpointMediaPipeline(options) {
|
|
|
2515
2219
|
},
|
|
2516
2220
|
async resume(id2, resumeOptions) {
|
|
2517
2221
|
const persisted = await read(id2);
|
|
2518
|
-
if (persisted?.deliveryMode === "background") return
|
|
2222
|
+
if (persisted?.deliveryMode === "background") return retireBackground(persisted);
|
|
2519
2223
|
installGuard(id2, resumeOptions?.beforePublish);
|
|
2520
2224
|
const existing = running.get(id2);
|
|
2521
2225
|
if (existing) return existing;
|
|
@@ -2771,15 +2475,6 @@ function createEndpointMediaPipeline(options) {
|
|
|
2771
2475
|
},
|
|
2772
2476
|
async resumePending() {
|
|
2773
2477
|
if (closed) return;
|
|
2774
|
-
if (background?.reconcile && !restoredNativeObservations) {
|
|
2775
|
-
restoredNativeObservations = true;
|
|
2776
|
-
try {
|
|
2777
|
-
for (const snapshot of await background.inspect()) await applyBackgroundSnapshot(snapshot);
|
|
2778
|
-
} catch {
|
|
2779
|
-
restoredNativeObservations = false;
|
|
2780
|
-
schedule();
|
|
2781
|
-
}
|
|
2782
|
-
}
|
|
2783
2478
|
let after;
|
|
2784
2479
|
do {
|
|
2785
2480
|
const records = await store.transaction((tx) => tx.scan("media-pending/", { after, limit: 100 }));
|
|
@@ -2793,20 +2488,9 @@ function createEndpointMediaPipeline(options) {
|
|
|
2793
2488
|
if (saved) await pipeline3.cancel(saved.id);
|
|
2794
2489
|
},
|
|
2795
2490
|
async cancel(id2) {
|
|
2796
|
-
const
|
|
2797
|
-
if (
|
|
2798
|
-
|
|
2799
|
-
const owner2 = await acquire(id2);
|
|
2800
|
-
try {
|
|
2801
|
-
const current = await read(id2);
|
|
2802
|
-
if (!current || current.status === "succeeded" || current.status === "cancelled") return;
|
|
2803
|
-
current.background = { phase: "cancelling", uploadedBytes: current.background?.uploadedBytes ?? 0, totalBytes: current.background?.totalBytes ?? 0 };
|
|
2804
|
-
await write(current, owner2);
|
|
2805
|
-
} finally {
|
|
2806
|
-
await unlock(id2, owner2);
|
|
2807
|
-
}
|
|
2808
|
-
await background.cancel(id2);
|
|
2809
|
-
schedule();
|
|
2491
|
+
const retired = await read(id2);
|
|
2492
|
+
if (retired?.deliveryMode === "background") {
|
|
2493
|
+
await retireBackground(retired).catch(() => void 0);
|
|
2810
2494
|
return;
|
|
2811
2495
|
}
|
|
2812
2496
|
if (staging.has(id2)) cancelledStages.add(id2);
|
|
@@ -2829,15 +2513,12 @@ function createEndpointMediaPipeline(options) {
|
|
|
2829
2513
|
async close(closeOptions) {
|
|
2830
2514
|
closed = true;
|
|
2831
2515
|
unsubscribe();
|
|
2832
|
-
stopConnections?.();
|
|
2833
|
-
stopBackground?.();
|
|
2834
2516
|
listeners.clear();
|
|
2835
2517
|
for (const guard of publicationGuards.values()) guard.unregister();
|
|
2836
2518
|
publicationGuards.clear();
|
|
2837
2519
|
if (timer) clearTimeout(timer);
|
|
2838
2520
|
for (const controller of controllers.values()) controller.abort();
|
|
2839
2521
|
await Promise.allSettled([...staging.values(), ...running.values()]);
|
|
2840
|
-
if (closeOptions?.discard) await background?.cleanup();
|
|
2841
2522
|
if (closeOptions?.discard) {
|
|
2842
2523
|
let after;
|
|
2843
2524
|
do {
|
|
@@ -3184,6 +2865,90 @@ function createInboundHandoffs(input) {
|
|
|
3184
2865
|
}
|
|
3185
2866
|
__name(createInboundHandoffs, "createInboundHandoffs");
|
|
3186
2867
|
|
|
2868
|
+
// ../endpoint/src/completions.ts
|
|
2869
|
+
var PREFIX = "completion/";
|
|
2870
|
+
var key = /* @__PURE__ */ __name((id2) => `${PREFIX}${encodeURIComponent(id2)}`, "key");
|
|
2871
|
+
function createCompletions(input) {
|
|
2872
|
+
function validate(record6) {
|
|
2873
|
+
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) {
|
|
2874
|
+
throw new EndpointError("Invalid completion", { code: "INVALID_COMPLETION", retryable: false });
|
|
2875
|
+
}
|
|
2876
|
+
}
|
|
2877
|
+
__name(validate, "validate");
|
|
2878
|
+
async function put(tx, record6) {
|
|
2879
|
+
const existing = await tx.get(key(record6.id));
|
|
2880
|
+
if (existing && canonicalEndpointJson(existing) !== canonicalEndpointJson(record6)) {
|
|
2881
|
+
throw new EndpointError("A completion cannot be replaced", { code: "COMPLETION_CONFLICT", retryable: false });
|
|
2882
|
+
}
|
|
2883
|
+
if (!existing) await tx.put(key(record6.id), record6);
|
|
2884
|
+
}
|
|
2885
|
+
__name(put, "put");
|
|
2886
|
+
async function all(tx) {
|
|
2887
|
+
const found = [];
|
|
2888
|
+
for (let after; ; ) {
|
|
2889
|
+
const batch = await tx.scan(PREFIX, { ...after ? { after } : {}, limit: 100 });
|
|
2890
|
+
found.push(...batch.map((entry) => entry.value));
|
|
2891
|
+
if (batch.length < 100) return found;
|
|
2892
|
+
after = batch.at(-1).key;
|
|
2893
|
+
}
|
|
2894
|
+
}
|
|
2895
|
+
__name(all, "all");
|
|
2896
|
+
const transaction = /* @__PURE__ */ __name(async (work) => {
|
|
2897
|
+
input.assertOpen();
|
|
2898
|
+
await input.ready();
|
|
2899
|
+
return input.store.transaction(async (tx) => {
|
|
2900
|
+
await input.assertCurrent(tx);
|
|
2901
|
+
return work(tx);
|
|
2902
|
+
});
|
|
2903
|
+
}, "transaction");
|
|
2904
|
+
return {
|
|
2905
|
+
async save(record6) {
|
|
2906
|
+
const frozen = copyJson(record6);
|
|
2907
|
+
validate(frozen);
|
|
2908
|
+
await transaction((tx) => put(tx, frozen));
|
|
2909
|
+
},
|
|
2910
|
+
async get(id2) {
|
|
2911
|
+
input.assertOpen();
|
|
2912
|
+
await input.ready();
|
|
2913
|
+
return input.store.transaction((tx) => tx.get(key(id2)));
|
|
2914
|
+
},
|
|
2915
|
+
/** Oldest first. */
|
|
2916
|
+
async list() {
|
|
2917
|
+
input.assertOpen();
|
|
2918
|
+
await input.ready();
|
|
2919
|
+
const found = await input.store.transaction(all);
|
|
2920
|
+
return found.sort((a, b) => a.createdAt.localeCompare(b.createdAt) || a.id.localeCompare(b.id));
|
|
2921
|
+
},
|
|
2922
|
+
/** One record for another, in one step: a Stop's cut keeps only what was already queued. */
|
|
2923
|
+
async replace(previousId, next) {
|
|
2924
|
+
const frozen = copyJson(next);
|
|
2925
|
+
validate(frozen);
|
|
2926
|
+
await transaction(async (tx) => {
|
|
2927
|
+
await put(tx, frozen);
|
|
2928
|
+
if (previousId !== frozen.id) await tx.delete(key(previousId));
|
|
2929
|
+
});
|
|
2930
|
+
},
|
|
2931
|
+
/**
|
|
2932
|
+
* The output is durable or withdrawn: its record goes, and in the same step `settle`'s inputs settle. An input already
|
|
2933
|
+
* settled, or no longer in the inbox, is left as it is.
|
|
2934
|
+
*/
|
|
2935
|
+
async finish(id2, settle) {
|
|
2936
|
+
await transaction(async (tx) => {
|
|
2937
|
+
for (const inputId of new Set(settle?.inputIds ?? [])) {
|
|
2938
|
+
const current = await tx.get(`inbound/${inputId}`);
|
|
2939
|
+
if (current && current.state !== "settled") await input.settleInbound(tx, inputId, settle.reason);
|
|
2940
|
+
}
|
|
2941
|
+
await tx.delete(key(id2));
|
|
2942
|
+
});
|
|
2943
|
+
},
|
|
2944
|
+
/** The input ids every completion names, read inside the caller's transaction. */
|
|
2945
|
+
async answered(tx) {
|
|
2946
|
+
return new Set((await all(tx)).flatMap((record6) => record6.inputIds));
|
|
2947
|
+
}
|
|
2948
|
+
};
|
|
2949
|
+
}
|
|
2950
|
+
__name(createCompletions, "createCompletions");
|
|
2951
|
+
|
|
3187
2952
|
// ../endpoint/src/external-inbound.ts
|
|
3188
2953
|
function createExternalInboundClaims(input) {
|
|
3189
2954
|
function validate(id2, requestId) {
|
|
@@ -3328,13 +3093,6 @@ function createExternalInboundClaims(input) {
|
|
|
3328
3093
|
}
|
|
3329
3094
|
__name(createExternalInboundClaims, "createExternalInboundClaims");
|
|
3330
3095
|
|
|
3331
|
-
// ../endpoint/src/artifact-context.ts
|
|
3332
|
-
var contexts = /* @__PURE__ */ new WeakMap();
|
|
3333
|
-
function registerArtifactEndpoint(endpoint, context) {
|
|
3334
|
-
contexts.set(endpoint, context);
|
|
3335
|
-
}
|
|
3336
|
-
__name(registerArtifactEndpoint, "registerArtifactEndpoint");
|
|
3337
|
-
|
|
3338
3096
|
// ../endpoint/src/receipts.ts
|
|
3339
3097
|
function endpointReceiptResult(receipt) {
|
|
3340
3098
|
if (receipt.resultError) throw new EndpointError(receipt.resultError.message, { status: receipt.resultError.status, code: receipt.resultError.code, retryable: receipt.resultError.retryable, operationId: receipt.operationId });
|
|
@@ -3365,12 +3123,12 @@ function record(value, required, optional = []) {
|
|
|
3365
3123
|
if (!value || typeof value !== "object" || Array.isArray(value) || ![Object.prototype, null].includes(Object.getPrototypeOf(value))) fail();
|
|
3366
3124
|
const descriptors = Object.getOwnPropertyDescriptors(value);
|
|
3367
3125
|
const keys = Reflect.ownKeys(descriptors);
|
|
3368
|
-
if (keys.some((
|
|
3126
|
+
if (keys.some((key2) => typeof key2 !== "string" || ![...required, ...optional].includes(key2)) || required.some((key2) => !Object.hasOwn(descriptors, key2))) fail();
|
|
3369
3127
|
const result = {};
|
|
3370
|
-
for (const
|
|
3371
|
-
const descriptor = descriptors[
|
|
3128
|
+
for (const key2 of keys) {
|
|
3129
|
+
const descriptor = descriptors[key2];
|
|
3372
3130
|
if (!Object.hasOwn(descriptor, "value") || !descriptor.enumerable) fail();
|
|
3373
|
-
result[
|
|
3131
|
+
result[key2] = descriptor.value;
|
|
3374
3132
|
}
|
|
3375
3133
|
return result;
|
|
3376
3134
|
}
|
|
@@ -3409,8 +3167,8 @@ function createMlsDevDeviceCoordinator(input) {
|
|
|
3409
3167
|
installationId: binding.installationId,
|
|
3410
3168
|
...binding.principal.type === "ai_agent" ? { ownershipVersion: binding.principal.ownershipVersion } : {}
|
|
3411
3169
|
});
|
|
3412
|
-
function boundDevice(device,
|
|
3413
|
-
if (device.environmentId !== binding.environmentId || !same(device.principal, binding.principal) || device.installationId !== binding.installationId || device.signaturePublicKey !==
|
|
3170
|
+
function boundDevice(device, key2) {
|
|
3171
|
+
if (device.environmentId !== binding.environmentId || !same(device.principal, binding.principal) || device.installationId !== binding.installationId || device.signaturePublicKey !== key2) fail("MLS_DEV_DEVICE_BINDING_MISMATCH");
|
|
3414
3172
|
}
|
|
3415
3173
|
__name(boundDevice, "boundDevice");
|
|
3416
3174
|
async function load(tx, snapshot) {
|
|
@@ -3781,7 +3539,7 @@ __name(same2, "same");
|
|
|
3781
3539
|
function parseFlow(value) {
|
|
3782
3540
|
if (!value || typeof value !== "object" || Array.isArray(value)) fail2();
|
|
3783
3541
|
const raw = value;
|
|
3784
|
-
if (Object.keys(raw).some((
|
|
3542
|
+
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
3543
|
const head = { version: 1, id: raw.id };
|
|
3786
3544
|
if (raw.goal === "enroll" && raw.phase === "enrollment") return {
|
|
3787
3545
|
...head,
|
|
@@ -4217,8 +3975,8 @@ __name(fail3, "fail");
|
|
|
4217
3975
|
function record2(value, keys) {
|
|
4218
3976
|
if (!value || typeof value !== "object" || Array.isArray(value) || ![Object.prototype, null].includes(Object.getPrototypeOf(value))) fail3();
|
|
4219
3977
|
const fields = Object.getOwnPropertyDescriptors(value);
|
|
4220
|
-
if (Reflect.ownKeys(fields).length !== keys.length || keys.some((
|
|
4221
|
-
return Object.fromEntries(keys.map((
|
|
3978
|
+
if (Reflect.ownKeys(fields).length !== keys.length || keys.some((key2) => !Object.hasOwn(fields, key2) || !Object.hasOwn(fields[key2], "value") || !fields[key2].enumerable)) fail3();
|
|
3979
|
+
return Object.fromEntries(keys.map((key2) => [key2, fields[key2].value]));
|
|
4222
3980
|
}
|
|
4223
3981
|
__name(record2, "record");
|
|
4224
3982
|
function createMlsDevKeyPackageService(options, context, store, initialized, readPrepared) {
|
|
@@ -4242,9 +4000,9 @@ function createMlsDevKeyPackageService(options, context, store, initialized, rea
|
|
|
4242
4000
|
async function exchange(id2, online, op) {
|
|
4243
4001
|
const { binding, device } = await initialized();
|
|
4244
4002
|
if (online) network();
|
|
4245
|
-
const
|
|
4003
|
+
const key2 = op.prefix + id2;
|
|
4246
4004
|
const captured = await store.transaction(async (tx) => {
|
|
4247
|
-
const current = await device.captureKeyPackagePrerequisite(tx), raw = await tx.get(
|
|
4005
|
+
const current = await device.captureKeyPackagePrerequisite(tx), raw = await tx.get(key2);
|
|
4248
4006
|
if (!online && raw === void 0) return { current, raw, request: void 0, previous: void 0 };
|
|
4249
4007
|
const request = await op.make(tx, binding, current);
|
|
4250
4008
|
const previous = raw === void 0 ? void 0 : parseJournal(raw, binding, current, op);
|
|
@@ -4266,7 +4024,7 @@ function createMlsDevKeyPackageService(options, context, store, initialized, rea
|
|
|
4266
4024
|
if (current.fingerprint !== captured.current.fingerprint) fail3("MLS_DEV_KEY_PACKAGE_DEVICE_CHANGED", true);
|
|
4267
4025
|
const claims = parseMlsDevCredentialToken(current.credential.credential).unsigned.claims, now = context.clock();
|
|
4268
4026
|
if (!Number.isSafeInteger(now) || now < claims.issuedAtMs || now >= claims.expiresAtMs) fail3("MLS_DEV_KEY_PACKAGE_EXPIRED");
|
|
4269
|
-
if (!same3(await tx.get(
|
|
4027
|
+
if (!same3(await tx.get(key2) ?? null, captured.raw ?? null)) fail3("MLS_DEV_KEY_PACKAGE_STATE_CHANGED", true);
|
|
4270
4028
|
if (captured.raw === void 0 && (await tx.scan(op.prefix, { limit: 33 })).length >= 32) fail3("MLS_DEV_KEY_PACKAGE_INVENTORY_FULL");
|
|
4271
4029
|
const request = captured.request ?? fail3();
|
|
4272
4030
|
await op.validate(tx, binding, current, request, null);
|
|
@@ -4277,7 +4035,7 @@ function createMlsDevKeyPackageService(options, context, store, initialized, rea
|
|
|
4277
4035
|
request,
|
|
4278
4036
|
receipt: captured.previous?.receipt ?? null
|
|
4279
4037
|
};
|
|
4280
|
-
await tx.put(
|
|
4038
|
+
await tx.put(key2, value);
|
|
4281
4039
|
return value;
|
|
4282
4040
|
});
|
|
4283
4041
|
let receipt;
|
|
@@ -4287,7 +4045,7 @@ function createMlsDevKeyPackageService(options, context, store, initialized, rea
|
|
|
4287
4045
|
if (!(error instanceof EndpointError)) fail3("MLS_DEV_KEY_PACKAGE_REQUEST_FAILED", true);
|
|
4288
4046
|
if (op.forgetsRefusal && attempt.receipt === null && !error.retryable && error.status >= 400 && error.status < 500 && error.code === "HTTP_" + error.status) {
|
|
4289
4047
|
await store.transaction(async (tx) => {
|
|
4290
|
-
if (same3(await tx.get(
|
|
4048
|
+
if (same3(await tx.get(key2) ?? null, attempt)) await tx.delete(key2);
|
|
4291
4049
|
});
|
|
4292
4050
|
}
|
|
4293
4051
|
throw error;
|
|
@@ -4297,11 +4055,11 @@ function createMlsDevKeyPackageService(options, context, store, initialized, rea
|
|
|
4297
4055
|
return store.transaction(async (tx) => {
|
|
4298
4056
|
const current = await device.captureKeyPackagePrerequisite(tx);
|
|
4299
4057
|
if (current.fingerprint !== captured.current.fingerprint) fail3("MLS_DEV_KEY_PACKAGE_DEVICE_CHANGED", true);
|
|
4300
|
-
const stored = parseJournal(await tx.get(
|
|
4058
|
+
const stored = parseJournal(await tx.get(key2), binding, current, op);
|
|
4301
4059
|
if (!same3(stored.request, attempt.request)) fail3("MLS_DEV_KEY_PACKAGE_STATE_CHANGED", true);
|
|
4302
4060
|
await op.validate(tx, binding, current, attempt.request, receipt);
|
|
4303
4061
|
if (stored.receipt !== null && !same3(stored.receipt, receipt)) fail3("MLS_DEV_KEY_PACKAGE_RECEIPT_CONFLICT");
|
|
4304
|
-
await tx.put(
|
|
4062
|
+
await tx.put(key2, { ...stored, receipt });
|
|
4305
4063
|
return receipt;
|
|
4306
4064
|
});
|
|
4307
4065
|
}
|
|
@@ -4449,8 +4207,8 @@ var same4 = /* @__PURE__ */ __name((a, b) => canonicalEndpointJson(a) === canoni
|
|
|
4449
4207
|
function record3(value, keys) {
|
|
4450
4208
|
if (!value || typeof value !== "object" || Array.isArray(value) || ![Object.prototype, null].includes(Object.getPrototypeOf(value))) fail4();
|
|
4451
4209
|
const fields = Object.getOwnPropertyDescriptors(value);
|
|
4452
|
-
if (Reflect.ownKeys(fields).length !== keys.length || keys.some((
|
|
4453
|
-
return Object.fromEntries(keys.map((
|
|
4210
|
+
if (Reflect.ownKeys(fields).length !== keys.length || keys.some((key2) => !Object.hasOwn(fields, key2) || !Object.hasOwn(fields[key2], "value") || !fields[key2].enumerable)) fail4();
|
|
4211
|
+
return Object.fromEntries(keys.map((key2) => [key2, fields[key2].value]));
|
|
4454
4212
|
}
|
|
4455
4213
|
__name(record3, "record");
|
|
4456
4214
|
function boundPayload(command, payload) {
|
|
@@ -4948,28 +4706,28 @@ var erased = /* @__PURE__ */ __name((value) => {
|
|
|
4948
4706
|
async function deleteMlsDevTextMessage(tx, conversationId, messageId) {
|
|
4949
4707
|
const tombstone = mlsDevTextDeletedKey(conversationId, messageId), known = await tx.get(tombstone) !== void 0;
|
|
4950
4708
|
if (!known) await tx.put(tombstone, { version: 1 });
|
|
4951
|
-
const
|
|
4709
|
+
const key2 = mlsDevTextMessageKey(conversationId, messageId), value = await tx.get(key2);
|
|
4952
4710
|
if (value?.message.kind !== "text" && value?.message.kind !== "item" || value.deleted || value.feedOrdinal === null) return !known;
|
|
4953
|
-
await tx.put(
|
|
4711
|
+
await tx.put(key2, erased(value));
|
|
4954
4712
|
return true;
|
|
4955
4713
|
}
|
|
4956
4714
|
__name(deleteMlsDevTextMessage, "deleteMlsDevTextMessage");
|
|
4957
4715
|
async function setMlsDevTextReactions(tx, conversationId, messageId, reactions) {
|
|
4958
|
-
const
|
|
4959
|
-
const previous = await tx.get(
|
|
4716
|
+
const key2 = mlsDevTextReactionsKey(conversationId, messageId), next = reactions && Object.keys(reactions).length ? reactions : void 0;
|
|
4717
|
+
const previous = await tx.get(key2);
|
|
4960
4718
|
if (JSON.stringify(previous ?? null) === JSON.stringify(next ?? null)) return false;
|
|
4961
|
-
if (next) await tx.put(
|
|
4962
|
-
else await tx.delete(
|
|
4719
|
+
if (next) await tx.put(key2, next);
|
|
4720
|
+
else await tx.delete(key2);
|
|
4963
4721
|
return true;
|
|
4964
4722
|
}
|
|
4965
4723
|
__name(setMlsDevTextReactions, "setMlsDevTextReactions");
|
|
4966
4724
|
async function putMlsDevTextMessage(tx, value) {
|
|
4967
4725
|
if (await tx.get(mlsDevTextDeletedKey(value.message.conversationId, value.message.messageId)) !== void 0) value = erased(value);
|
|
4968
4726
|
const { conversationId, messageId } = value.message;
|
|
4969
|
-
const
|
|
4970
|
-
const old = await tx.get(
|
|
4727
|
+
const key2 = mlsDevTextMessageKey(conversationId, messageId);
|
|
4728
|
+
const old = await tx.get(key2);
|
|
4971
4729
|
if (old?.feedOrdinal !== null && old?.feedOrdinal !== void 0 && old.feedOrdinal !== value.feedOrdinal) fail5();
|
|
4972
|
-
await tx.put(
|
|
4730
|
+
await tx.put(key2, value);
|
|
4973
4731
|
if (value.message.kind !== "text" && value.message.kind !== "item" && value.message.kind !== "interaction.request") return;
|
|
4974
4732
|
if (value.feedOrdinal === null) await tx.put(`${pendingPrefix(conversationId)}${encoded2(messageId)}`, { messageId });
|
|
4975
4733
|
else {
|
|
@@ -5114,10 +4872,10 @@ async function queryMlsDevTextHistory(tx, conversationId, name, parameters, audi
|
|
|
5114
4872
|
__name(queryMlsDevTextHistory, "queryMlsDevTextHistory");
|
|
5115
4873
|
async function settleMlsDevTextOperation(tx, operation) {
|
|
5116
4874
|
if (operation.action !== "mls_application" || !operation.conversationId || !operation.messageId || !["rejected", "uncertain"].includes(operation.status)) return;
|
|
5117
|
-
const
|
|
5118
|
-
const value = await tx.get(
|
|
4875
|
+
const key2 = mlsDevTextMessageKey(operation.conversationId, operation.messageId);
|
|
4876
|
+
const value = await tx.get(key2);
|
|
5119
4877
|
if (value && value.message.operationId === operation.operationId && value.feedOrdinal === null) {
|
|
5120
|
-
await tx.put(
|
|
4878
|
+
await tx.put(key2, { ...value, status: operation.status });
|
|
5121
4879
|
}
|
|
5122
4880
|
}
|
|
5123
4881
|
__name(settleMlsDevTextOperation, "settleMlsDevTextOperation");
|
|
@@ -5135,13 +4893,13 @@ async function opensMlsAlias(environmentId, alias, opening, hash) {
|
|
|
5135
4893
|
return false;
|
|
5136
4894
|
}
|
|
5137
4895
|
if (!isMlsPrincipalAlias(alias)) return false;
|
|
5138
|
-
const
|
|
5139
|
-
let opens = checked.get(
|
|
4896
|
+
const key2 = checkKey(environmentId, alias, value);
|
|
4897
|
+
let opens = checked.get(key2);
|
|
5140
4898
|
if (opens === void 0) {
|
|
5141
4899
|
const digest = await hash(encodeMlsPrincipalAliasInput(environmentId, value));
|
|
5142
4900
|
opens = /^[0-9a-f]{64}$/.test(digest) && MLS_PRINCIPAL_ALIAS_PREFIX + encodeCanonicalMlsBase64Url(hexBytes(digest)) === alias;
|
|
5143
4901
|
if (checked.size >= 4096) checked.clear();
|
|
5144
|
-
checked.set(
|
|
4902
|
+
checked.set(key2, opens);
|
|
5145
4903
|
}
|
|
5146
4904
|
return opens;
|
|
5147
4905
|
}
|
|
@@ -5246,8 +5004,8 @@ __name(id, "id");
|
|
|
5246
5004
|
function record4(value, required, optional = []) {
|
|
5247
5005
|
if (!value || typeof value !== "object" || Array.isArray(value) || ![Object.prototype, null].includes(Object.getPrototypeOf(value))) fail6();
|
|
5248
5006
|
const descriptors = Object.getOwnPropertyDescriptors(value), fields = Reflect.ownKeys(descriptors);
|
|
5249
|
-
if (required.some((
|
|
5250
|
-
return Object.fromEntries(fields.map((
|
|
5007
|
+
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();
|
|
5008
|
+
return Object.fromEntries(fields.map((key2) => [key2, descriptors[key2].value]));
|
|
5251
5009
|
}
|
|
5252
5010
|
__name(record4, "record");
|
|
5253
5011
|
function envelope(value, binding, conversationId) {
|
|
@@ -5386,7 +5144,7 @@ function createMlsDevGroups(options, context, keyPackages, bridge) {
|
|
|
5386
5144
|
"mlsRemoval",
|
|
5387
5145
|
"mlsIdentities",
|
|
5388
5146
|
"mlsInviteeId"
|
|
5389
|
-
].map((
|
|
5147
|
+
].map((key2) => [key2, conversation[key2] ?? null])) : null;
|
|
5390
5148
|
const cursor = await tx.get(`cursor/${conversationId}`);
|
|
5391
5149
|
const protocol = await tx.get(mlsDevTextProtocolKey(conversationId));
|
|
5392
5150
|
if (protocol !== void 0 && !isMlsDevTextProtocol(protocol) || current && current.protocol !== protocol) fail6("MLS_DEV_TEXT_PROTOCOL_UNSUPPORTED");
|
|
@@ -5782,13 +5540,13 @@ function createMlsDevGroups(options, context, keyPackages, bridge) {
|
|
|
5782
5540
|
const request = message.interaction, responder = request.responderIds[0];
|
|
5783
5541
|
if (sender.principal.type !== "ai_agent") return "MLS_DEV_INTERACTION_REQUESTER_INVALID";
|
|
5784
5542
|
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
|
|
5543
|
+
const key3 = mlsDevInteractionKey(cid, sender.principal.id, request.requestId), existing2 = await tx.get(key3);
|
|
5786
5544
|
if (existing2) {
|
|
5787
5545
|
const earlier = await tx.get(acceptedKey(cid, existing2.head.sequence));
|
|
5788
5546
|
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
5547
|
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
5548
|
}
|
|
5791
|
-
await tx.put(
|
|
5549
|
+
await tx.put(key3, {
|
|
5792
5550
|
version: 1,
|
|
5793
5551
|
conversationId: cid,
|
|
5794
5552
|
agentId: sender.principal.id,
|
|
@@ -5807,13 +5565,13 @@ function createMlsDevGroups(options, context, keyPackages, bridge) {
|
|
|
5807
5565
|
if (message.kind === "interaction.response") {
|
|
5808
5566
|
const response = message.interaction;
|
|
5809
5567
|
if (sender.principal.type !== "human") return "MLS_DEV_INTERACTION_RESPONDER_INVALID";
|
|
5810
|
-
const
|
|
5568
|
+
const key3 = mlsDevInteractionKey(cid, response.agentId, response.requestId), existing2 = await tx.get(key3);
|
|
5811
5569
|
if (!existing2) return "MLS_DEV_INTERACTION_UNKNOWN";
|
|
5812
5570
|
if (!existing2.request.responderIds.includes(sender.principal.id)) return "MLS_DEV_INTERACTION_RESPONDER_INVALID";
|
|
5813
5571
|
if (existing2.status !== "pending") return "MLS_DEV_INTERACTION_CLOSED";
|
|
5814
5572
|
if (existing2.requestOrdinal >= entry.ordinal || !same6(existing2.head, wire.expectedHead) || existing2.membershipRevision !== wire.preconditions.membershipRevision) return "MLS_DEV_INTERACTION_AUDIENCE_CHANGED";
|
|
5815
5573
|
if (!isMlsDevInteractionAnswerAllowed(existing2.request, response.answer)) return "MLS_DEV_INTERACTION_ANSWER_INVALID";
|
|
5816
|
-
await tx.put(
|
|
5574
|
+
await tx.put(key3, { ...existing2, status: "answered", response: {
|
|
5817
5575
|
responderId: sender.principal.id,
|
|
5818
5576
|
responderDeviceId: sender.deviceId,
|
|
5819
5577
|
messageId: wire.messageId,
|
|
@@ -5824,11 +5582,11 @@ function createMlsDevGroups(options, context, keyPackages, bridge) {
|
|
|
5824
5582
|
return void 0;
|
|
5825
5583
|
}
|
|
5826
5584
|
if (sender.principal.type !== "ai_agent") return "MLS_DEV_INTERACTION_REQUESTER_INVALID";
|
|
5827
|
-
const
|
|
5585
|
+
const key2 = mlsDevInteractionKey(cid, sender.principal.id, message.interaction.requestId), existing = await tx.get(key2);
|
|
5828
5586
|
if (!existing) return "MLS_DEV_INTERACTION_UNKNOWN";
|
|
5829
5587
|
if (existing.requesterDeviceId !== sender.deviceId) return "MLS_DEV_INTERACTION_REQUESTER_INVALID";
|
|
5830
5588
|
if (existing.status !== "pending") return "MLS_DEV_INTERACTION_CLOSED";
|
|
5831
|
-
await tx.put(
|
|
5589
|
+
await tx.put(key2, { ...existing, status: "withdrawn", withdrawal: {
|
|
5832
5590
|
reason: message.interaction.reason,
|
|
5833
5591
|
messageId: wire.messageId,
|
|
5834
5592
|
ordinal: entry.ordinal
|
|
@@ -5917,7 +5675,7 @@ function createMlsDevGroups(options, context, keyPackages, bridge) {
|
|
|
5917
5675
|
url: data.url,
|
|
5918
5676
|
mimeType: data.mimeType,
|
|
5919
5677
|
sizeBytes: data.sizeBytes,
|
|
5920
|
-
...Object.fromEntries(["fileName", "width", "height", "durationMs", "thumbnailUrl"].filter((
|
|
5678
|
+
...Object.fromEntries(["fileName", "width", "height", "durationMs", "thumbnailUrl"].filter((key2) => data[key2] !== void 0 && data[key2] !== null).map((key2) => [key2, data[key2]])),
|
|
5921
5679
|
...typeof caption === "string" && caption.trim() ? { caption } : {}
|
|
5922
5680
|
};
|
|
5923
5681
|
return validItem(raw);
|
|
@@ -5962,7 +5720,7 @@ function createMlsDevGroups(options, context, keyPackages, bridge) {
|
|
|
5962
5720
|
const attachments = data.attachments ?? [];
|
|
5963
5721
|
if (!Array.isArray(attachments) || attachments.length > 1) fail6(Array.isArray(attachments) ? "MLS_DEV_ONE_ITEM_PER_MESSAGE" : "MLS_DEV_TEXT_ONLY");
|
|
5964
5722
|
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((
|
|
5723
|
+
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
5724
|
const mentions = [...new Set(data.mentions ?? [])].map(id);
|
|
5967
5725
|
return {
|
|
5968
5726
|
conversationId: id(data.conversationId),
|
|
@@ -6150,14 +5908,14 @@ function createMlsDevGroups(options, context, keyPackages, bridge) {
|
|
|
6150
5908
|
...input.replyTo ? { replyTo: input.replyTo } : {},
|
|
6151
5909
|
...input.mentions ? { mentions: input.mentions } : {}
|
|
6152
5910
|
};
|
|
6153
|
-
const init = await initialized(),
|
|
5911
|
+
const init = await initialized(), key2 = mlsDevHeldTextKey(cid, messageId);
|
|
6154
5912
|
const kept = await store.transaction(async (tx) => {
|
|
6155
|
-
const existing = await tx.get(
|
|
5913
|
+
const existing = await tx.get(key2);
|
|
6156
5914
|
if (!existing) return await tx.get(`operation/${operationId}`) !== void 0 ? "made" : (await readMlsDevHeldTexts(tx, cid)).some((held) => !held.failed) ? "behind" : "none";
|
|
6157
5915
|
if (existing.operationId !== operationId || canonicalEndpointJson(existing.body) !== canonicalEndpointJson(body)) fail6("OPERATION_ID_COLLISION");
|
|
6158
5916
|
if (existing.failed) {
|
|
6159
5917
|
const { failed: _failed, ...again } = existing;
|
|
6160
|
-
await tx.put(
|
|
5918
|
+
await tx.put(key2, again);
|
|
6161
5919
|
}
|
|
6162
5920
|
return "kept";
|
|
6163
5921
|
});
|
|
@@ -6173,7 +5931,7 @@ function createMlsDevGroups(options, context, keyPackages, bridge) {
|
|
|
6173
5931
|
await store.transaction(async (tx) => {
|
|
6174
5932
|
const held = await readMlsDevHeldTexts(tx, cid);
|
|
6175
5933
|
if (held.length >= MLS_DEV_HELD_TEXT_MAX) fail6("MLS_DEV_HELD_TEXT_LIMIT");
|
|
6176
|
-
await tx.put(
|
|
5934
|
+
await tx.put(key2, {
|
|
6177
5935
|
version: 1,
|
|
6178
5936
|
body,
|
|
6179
5937
|
operationId,
|
|
@@ -6191,20 +5949,20 @@ function createMlsDevGroups(options, context, keyPackages, bridge) {
|
|
|
6191
5949
|
const sent = [], failed = [];
|
|
6192
5950
|
const kept = (await store.transaction((tx) => readMlsDevHeldTexts(tx, conversationId))).filter((held) => !held.failed);
|
|
6193
5951
|
for (const held of kept) {
|
|
6194
|
-
const
|
|
5952
|
+
const key2 = mlsDevHeldTextKey(conversationId, held.body.messageId);
|
|
6195
5953
|
try {
|
|
6196
5954
|
sent.push(await sendText(textInput(held.body), held.operationId));
|
|
6197
5955
|
} catch (error) {
|
|
6198
5956
|
if (error instanceof EndpointError && ["ENDPOINT_CLOSED", "PREPARATION_INSTALLATION_FENCED"].includes(error.code)) throw error;
|
|
6199
5957
|
if (!(error instanceof EndpointError) || error.retryable) return { sent, failed, waiting: true };
|
|
6200
5958
|
await store.transaction(async (tx) => {
|
|
6201
|
-
if (await tx.get(
|
|
5959
|
+
if (await tx.get(key2) !== void 0) await tx.put(key2, { ...held, failed: error.code });
|
|
6202
5960
|
});
|
|
6203
5961
|
failed.push({ operationId: held.operationId, error });
|
|
6204
5962
|
bridge.historyChanged?.(conversationId);
|
|
6205
5963
|
continue;
|
|
6206
5964
|
}
|
|
6207
|
-
await store.transaction((tx) => tx.delete(
|
|
5965
|
+
await store.transaction((tx) => tx.delete(key2));
|
|
6208
5966
|
}
|
|
6209
5967
|
return { sent, failed, waiting: false };
|
|
6210
5968
|
}
|
|
@@ -6452,7 +6210,7 @@ function createMlsDevGroups(options, context, keyPackages, bridge) {
|
|
|
6452
6210
|
}
|
|
6453
6211
|
__name(readInteraction, "readInteraction");
|
|
6454
6212
|
async function openSecretAnswer(conversationId, requestId) {
|
|
6455
|
-
const init = await initialized(),
|
|
6213
|
+
const init = await initialized(), key2 = mlsDevInteractionKey(conversationId, init.binding.principal.id, requestId);
|
|
6456
6214
|
if (principal.type !== "ai_agent") fail6("MLS_DEV_INTERACTION_REQUESTER_INVALID");
|
|
6457
6215
|
let captured, known, text;
|
|
6458
6216
|
await prepareAndCommitContent({
|
|
@@ -6463,7 +6221,7 @@ function createMlsDevGroups(options, context, keyPackages, bridge) {
|
|
|
6463
6221
|
const current = await capture(tx, state, init, conversationId), group = current.envelope;
|
|
6464
6222
|
if (!mlsDevProtocolCarriesCards(current.protocol) || !group?.head || group.removed) fail6("MLS_DEV_INTERACTION_PROTOCOL_REQUIRED");
|
|
6465
6223
|
if (group.pending) fail6("MLS_DEV_GROUP_PENDING_CONTROL", true);
|
|
6466
|
-
known = await tx.get(
|
|
6224
|
+
known = await tx.get(key2) ?? fail6("MLS_DEV_INTERACTION_UNKNOWN");
|
|
6467
6225
|
if (known.requesterDeviceId !== current.device.registration.device.deviceId || known.request.kind !== "secret") fail6("MLS_DEV_INTERACTION_REQUESTER_INVALID");
|
|
6468
6226
|
if (known.opened) fail6("MLS_DEV_INTERACTION_OPENED");
|
|
6469
6227
|
const view3 = viewMlsDevInteraction(known, context.clock(), void 0);
|
|
@@ -6486,8 +6244,8 @@ function createMlsDevGroups(options, context, keyPackages, bridge) {
|
|
|
6486
6244
|
}, "prepare"),
|
|
6487
6245
|
commit: /* @__PURE__ */ __name(async (tx, prepared, state) => {
|
|
6488
6246
|
const current = await capture(tx, state, init, conversationId);
|
|
6489
|
-
if (current.fingerprint !== prepared.fingerprint || !same6(await tx.get(
|
|
6490
|
-
await tx.put(
|
|
6247
|
+
if (current.fingerprint !== prepared.fingerprint || !same6(await tx.get(key2), prepared.opened)) fail6("MLS_DEV_GROUP_DEPENDENCY_CHANGED", true);
|
|
6248
|
+
await tx.put(key2, { ...prepared.opened, opened: true });
|
|
6491
6249
|
return true;
|
|
6492
6250
|
}, "commit")
|
|
6493
6251
|
});
|
|
@@ -7158,7 +6916,7 @@ function createEndpoint(options) {
|
|
|
7158
6916
|
let retryDue = Number.POSITIVE_INFINITY;
|
|
7159
6917
|
let negotiatedCapabilities = null;
|
|
7160
6918
|
const now = /* @__PURE__ */ __name(() => new Date(clock()).toISOString(), "now");
|
|
7161
|
-
const
|
|
6919
|
+
const key2 = /* @__PURE__ */ __name((operationId) => `operation/${operationId}`, "key");
|
|
7162
6920
|
function assertOpen() {
|
|
7163
6921
|
if (closed) throw new EndpointError("Endpoint is closed", { code: "ENDPOINT_CLOSED", retryable: false });
|
|
7164
6922
|
}
|
|
@@ -7736,9 +7494,9 @@ function createEndpoint(options) {
|
|
|
7736
7494
|
function rememberStubName(conversationId, stub) {
|
|
7737
7495
|
const name = typeof stub.senderName === "string" ? stub.senderName.trim() : "";
|
|
7738
7496
|
if (typeof stub.senderId !== "string" || !stub.senderId || !name || name === stub.senderId || name.length > 80) return;
|
|
7739
|
-
const
|
|
7740
|
-
stubNames.delete(
|
|
7741
|
-
stubNames.set(
|
|
7497
|
+
const key3 = stubKey(conversationId, stub.id);
|
|
7498
|
+
stubNames.delete(key3);
|
|
7499
|
+
stubNames.set(key3, { senderId: stub.senderId, name });
|
|
7742
7500
|
if (stubNames.size > 1e3) stubNames.delete(stubNames.keys().next().value);
|
|
7743
7501
|
}
|
|
7744
7502
|
__name(rememberStubName, "rememberStubName");
|
|
@@ -7752,13 +7510,13 @@ function createEndpoint(options) {
|
|
|
7752
7510
|
}
|
|
7753
7511
|
__name(withStubNames, "withStubNames");
|
|
7754
7512
|
function forgetStubReads(conversationId) {
|
|
7755
|
-
for (const
|
|
7513
|
+
for (const key3 of stubReads.keys()) if (key3.startsWith(`${conversationId}\0`)) stubReads.delete(key3);
|
|
7756
7514
|
}
|
|
7757
7515
|
__name(forgetStubReads, "forgetStubReads");
|
|
7758
7516
|
async function recoverMlsDevMessageActions(conversationId, messageIds) {
|
|
7759
7517
|
const at = clock(), due = [...new Set(messageIds)].filter((id2) => at >= (stubReads.get(stubKey(conversationId, id2)) ?? -Infinity) + 3e4).slice(0, 100);
|
|
7760
7518
|
if (!due.length) return false;
|
|
7761
|
-
for (const [
|
|
7519
|
+
for (const [key3, readAt] of stubReads) if (at >= readAt + 3e4) stubReads.delete(key3);
|
|
7762
7520
|
for (const id2 of due) stubReads.set(stubKey(conversationId, id2), at);
|
|
7763
7521
|
const beforeRead = liveActionCounter;
|
|
7764
7522
|
let stubs;
|
|
@@ -7847,6 +7605,51 @@ function createEndpoint(options) {
|
|
|
7847
7605
|
}
|
|
7848
7606
|
}
|
|
7849
7607
|
__name(refreshMlsRoomFromHint, "refreshMlsRoomFromHint");
|
|
7608
|
+
const roomHints = /* @__PURE__ */ new Map();
|
|
7609
|
+
let roomHintWorker;
|
|
7610
|
+
function queueRoomHint(key3, run, last = false) {
|
|
7611
|
+
if (closed) return Promise.resolve();
|
|
7612
|
+
let hint = roomHints.get(key3);
|
|
7613
|
+
if (hint && last) roomHints.delete(key3);
|
|
7614
|
+
if (!hint) {
|
|
7615
|
+
let settle;
|
|
7616
|
+
const done = new Promise((resolve3, reject) => {
|
|
7617
|
+
settle = /* @__PURE__ */ __name((error) => error === void 0 ? resolve3() : reject(error), "settle");
|
|
7618
|
+
});
|
|
7619
|
+
done.catch(() => void 0);
|
|
7620
|
+
hint = { run, done, settle };
|
|
7621
|
+
}
|
|
7622
|
+
roomHints.set(key3, hint);
|
|
7623
|
+
drainRoomHints();
|
|
7624
|
+
return hint.done;
|
|
7625
|
+
}
|
|
7626
|
+
__name(queueRoomHint, "queueRoomHint");
|
|
7627
|
+
function drainRoomHints() {
|
|
7628
|
+
if (roomHintWorker) return;
|
|
7629
|
+
roomHintWorker = (async () => {
|
|
7630
|
+
for (const [key3, hint] of roomHints) {
|
|
7631
|
+
roomHints.delete(key3);
|
|
7632
|
+
if (closed) {
|
|
7633
|
+
hint.settle();
|
|
7634
|
+
continue;
|
|
7635
|
+
}
|
|
7636
|
+
try {
|
|
7637
|
+
await hint.run();
|
|
7638
|
+
hint.settle();
|
|
7639
|
+
} catch (error) {
|
|
7640
|
+
hint.settle(error);
|
|
7641
|
+
}
|
|
7642
|
+
}
|
|
7643
|
+
})().finally(() => {
|
|
7644
|
+
roomHintWorker = void 0;
|
|
7645
|
+
if (roomHints.size) drainRoomHints();
|
|
7646
|
+
});
|
|
7647
|
+
}
|
|
7648
|
+
__name(drainRoomHints, "drainRoomHints");
|
|
7649
|
+
const hintRoom = /* @__PURE__ */ __name((conversationId) => queueRoomHint(`room/${conversationId}`, async () => {
|
|
7650
|
+
await refreshMlsRoomFromHint(conversationId);
|
|
7651
|
+
await sendDeferredOpeners(conversationId);
|
|
7652
|
+
}), "hintRoom");
|
|
7850
7653
|
const queryObserver = createEndpointQueryObserver({ query: /* @__PURE__ */ __name((name, parameters) => endpoint.query(name, parameters), "query"), getPollInterval: /* @__PURE__ */ __name(() => events.isConnected() ? Math.max(options.queryIntervalMs ?? 0, 6e4) : options.queryIntervalMs ?? 3e3, "getPollInterval") });
|
|
7851
7654
|
const invalidateEvent = /* @__PURE__ */ __name((event) => queryObserver.invalidate(event ? (query, parameters) => queryAffectedByEvent(event, query, parameters) : void 0), "invalidateEvent");
|
|
7852
7655
|
const syncConversation = createConversationSynchronizer({ store, query: /* @__PURE__ */ __name((name, parameters) => endpoint.query(name, parameters), "query"), ready: /* @__PURE__ */ __name(() => initialization, "ready"), clock, invalidate: /* @__PURE__ */ __name((conversationId) => invalidateEvent({ kind: "conversation.changed", data: { conversationId } }), "invalidate"), admitRecoveredMessages: options.admitRecoveredMessages, preparationGeneration: /* @__PURE__ */ __name(() => installationGeneration, "preparationGeneration"), prepareProtocol: /* @__PURE__ */ __name((id2) => refreshMlsRoom(id2, true), "prepareProtocol"), syncProtocol: /* @__PURE__ */ __name(async (id2, opts) => {
|
|
@@ -7855,19 +7658,20 @@ function createEndpoint(options) {
|
|
|
7855
7658
|
return mlsDevGroups.syncText(id2, opts);
|
|
7856
7659
|
}, "syncProtocol") });
|
|
7857
7660
|
const events = createEndpointEventHub({ transport, invalidate: invalidateEvent, onDisconnected: /* @__PURE__ */ __name(() => queryObserver.reschedule(), "onDisconnected"), onEvent: /* @__PURE__ */ __name(async (event) => {
|
|
7661
|
+
const hinted = [];
|
|
7858
7662
|
if (mlsDevGroups && ["conversation.changed", "conversation.updated", "conversation.removed"].includes(event.kind)) {
|
|
7859
7663
|
const id2 = event.data.conversationId ?? event.data.id;
|
|
7860
7664
|
if (typeof id2 === "string" && ENDPOINT_ID_PATTERN.test(id2)) {
|
|
7861
7665
|
if (event.kind === "conversation.changed" && event.data.projection === void 0) forgetStubReads(id2);
|
|
7862
7666
|
const unseenRemoval = event.kind === "conversation.removed" && await store.transaction(async (tx) => await tx.get(`conversation/${id2}`) === void 0 && !await isMlsDevRoomProtected(tx, id2));
|
|
7863
|
-
if (!unseenRemoval)
|
|
7864
|
-
await sendDeferredOpeners(id2);
|
|
7667
|
+
if (!unseenRemoval) hinted.push(hintRoom(id2));
|
|
7668
|
+
else await sendDeferredOpeners(id2);
|
|
7865
7669
|
}
|
|
7866
7670
|
}
|
|
7867
7671
|
if (mlsDevGroups && event.kind === "conversations.changed" && Array.isArray(event.data.conversationIds)) {
|
|
7868
7672
|
for (const id2 of event.data.conversationIds) {
|
|
7869
7673
|
if (typeof id2 === "string" && ENDPOINT_ID_PATTERN.test(id2) && await store.transaction((tx) => isMlsDevRoomProtected(tx, id2))) {
|
|
7870
|
-
|
|
7674
|
+
hinted.push(hintRoom(id2));
|
|
7871
7675
|
}
|
|
7872
7676
|
}
|
|
7873
7677
|
}
|
|
@@ -7879,13 +7683,16 @@ function createEndpoint(options) {
|
|
|
7879
7683
|
if (event.kind === "connected") serverRoomMode = void 0;
|
|
7880
7684
|
for (let after; ; ) {
|
|
7881
7685
|
const page = await store.transaction((tx) => tx.scan("mls-dev-group/mode/", { after, limit: 100 }));
|
|
7882
|
-
for (const item of page)
|
|
7686
|
+
for (const item of page) hinted.push(hintRoom(decodeURIComponent(item.key.slice("mls-dev-group/mode/".length))));
|
|
7883
7687
|
if (page.length < 100) break;
|
|
7884
7688
|
after = page[page.length - 1].key;
|
|
7885
7689
|
}
|
|
7886
|
-
|
|
7887
|
-
|
|
7690
|
+
hinted.push(queueRoomHint("after/reconnect", async () => {
|
|
7691
|
+
await sendDeferredOpeners();
|
|
7692
|
+
await discoverInviteeRooms().catch(() => void 0);
|
|
7693
|
+
}, true));
|
|
7888
7694
|
}
|
|
7695
|
+
if (event.id) await Promise.all(hinted);
|
|
7889
7696
|
if ((event.kind === "message.updated" || event.kind === "message.deleted") && typeof event.data.conversationId === "string" && typeof event.data.messageId === "string" && ENDPOINT_ID_PATTERN.test(event.data.conversationId)) {
|
|
7890
7697
|
const room = event.data.conversationId, message = event.data.messageId;
|
|
7891
7698
|
const applied = await store.transaction(async (tx) => {
|
|
@@ -7929,6 +7736,13 @@ function createEndpoint(options) {
|
|
|
7929
7736
|
}, "assertReady")
|
|
7930
7737
|
});
|
|
7931
7738
|
const externalInbound = createExternalInboundClaims({ store, clock, hash, ownershipVersion: options.identity.ownershipVersion, ready: /* @__PURE__ */ __name(() => initialization, "ready"), assertOpen, assertCurrent: assertCurrentInstallation });
|
|
7739
|
+
const completions = createCompletions({
|
|
7740
|
+
store,
|
|
7741
|
+
ready: /* @__PURE__ */ __name(() => initialization, "ready"),
|
|
7742
|
+
assertOpen,
|
|
7743
|
+
assertCurrent: assertCurrentInstallation,
|
|
7744
|
+
settleInbound: /* @__PURE__ */ __name((tx, id2, reason) => writeInboundState(tx, id2, "settled", reason), "settleInbound")
|
|
7745
|
+
});
|
|
7932
7746
|
const messageGroups = createMessageGroupCoordinator({
|
|
7933
7747
|
store,
|
|
7934
7748
|
endpoint: /* @__PURE__ */ __name(() => endpoint, "endpoint"),
|
|
@@ -7939,6 +7753,26 @@ function createEndpoint(options) {
|
|
|
7939
7753
|
preparationGeneration: /* @__PURE__ */ __name(() => installationGeneration, "preparationGeneration"),
|
|
7940
7754
|
supportsPreparedContent: /* @__PURE__ */ __name(() => Promise.resolve(negotiatedCapabilities?.preparedContentVersion === 1), "supportsPreparedContent")
|
|
7941
7755
|
});
|
|
7756
|
+
async function writeInboundState(tx, id2, state, reason) {
|
|
7757
|
+
const recordKey = `inbound/${id2}`;
|
|
7758
|
+
const current = await tx.get(recordKey);
|
|
7759
|
+
if (!current) throw new EndpointError("Inbound operation was not durably received", { code: "INBOUND_NOT_FOUND", retryable: false });
|
|
7760
|
+
if (current.externalClaim) throw new EndpointError("Use the bound external claim to settle this input", { code: "EXTERNAL_INBOUND_CLAIM_REQUIRED", retryable: false });
|
|
7761
|
+
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 });
|
|
7762
|
+
if (current.deleted && !["settled", "uncertain"].includes(state)) throw new EndpointError("Deleted input cannot be replayed", { code: "INBOUND_DELETED", retryable: false });
|
|
7763
|
+
if (current.state === "settled" && state !== "settled") throw new EndpointError("Settled input cannot be replayed", { code: "INBOUND_ALREADY_SETTLED", retryable: false });
|
|
7764
|
+
const next = { ...current, state, ...reason ? { reason } : {}, updatedAt: now() };
|
|
7765
|
+
delete next.lease;
|
|
7766
|
+
if (state === "settled") {
|
|
7767
|
+
delete next.handoffGeneration;
|
|
7768
|
+
delete next.handoffInstallationGeneration;
|
|
7769
|
+
}
|
|
7770
|
+
if (state === "settled") next.event = { ...next.event, data: {} };
|
|
7771
|
+
await tx.put(recordKey, next);
|
|
7772
|
+
if (state === "settled" || state === "uncertain" || current.deleted) await removeInboundReady(tx, current);
|
|
7773
|
+
else await enqueueInbound(tx, next);
|
|
7774
|
+
}
|
|
7775
|
+
__name(writeInboundState, "writeInboundState");
|
|
7942
7776
|
function deferredReceipt(operation, error) {
|
|
7943
7777
|
return operation.receipt ?? {
|
|
7944
7778
|
protocol: CANON_ENDPOINT_PROTOCOL,
|
|
@@ -7972,7 +7806,7 @@ function createEndpoint(options) {
|
|
|
7972
7806
|
const index = group?.children.findIndex((child) => child.operationId === next.operationId) ?? -1;
|
|
7973
7807
|
if (group && index >= 0 && !group.cancelled) {
|
|
7974
7808
|
for (const reference of group.children.slice(index + 1)) {
|
|
7975
|
-
const child = await tx.get(
|
|
7809
|
+
const child = await tx.get(key2(reference.operationId));
|
|
7976
7810
|
if (!child || messageGroupChildSucceeded(child)) continue;
|
|
7977
7811
|
if (messageGroupChildSucceeded(next)) {
|
|
7978
7812
|
if (!terminal(child.status) && child.status !== "uncertain") await enqueueOperation(tx, child);
|
|
@@ -7980,7 +7814,7 @@ function createEndpoint(options) {
|
|
|
7980
7814
|
}
|
|
7981
7815
|
if (!child.attempted && !terminal(child.status)) {
|
|
7982
7816
|
const { request: _plaintext, preparedContent: _sealedPlaintext, ...correlation } = child;
|
|
7983
|
-
await tx.put(
|
|
7817
|
+
await tx.put(key2(child.operationId), { ...correlation, status: "rejected", updatedAt: now(), receipt: {
|
|
7984
7818
|
protocol: CANON_ENDPOINT_PROTOCOL,
|
|
7985
7819
|
operationId: child.operationId,
|
|
7986
7820
|
installationId: child.installationId,
|
|
@@ -8000,7 +7834,7 @@ function createEndpoint(options) {
|
|
|
8000
7834
|
__name(advanceMessageGroup, "advanceMessageGroup");
|
|
8001
7835
|
async function commitOperationRecord(tx, created) {
|
|
8002
7836
|
const ordered = await orderOperation(tx, created);
|
|
8003
|
-
await tx.put(
|
|
7837
|
+
await tx.put(key2(created.operationId), ordered);
|
|
8004
7838
|
await trackOperationOrder(tx, ordered);
|
|
8005
7839
|
await enqueueOperation(tx, ordered);
|
|
8006
7840
|
if (!created.sensitive) await tx.delete(`preparation/${created.operationId}`);
|
|
@@ -8011,7 +7845,7 @@ function createEndpoint(options) {
|
|
|
8011
7845
|
if (receipt.operationId !== operation.operationId || receipt.installationId !== operation.installationId || receipt.action !== operation.action) {
|
|
8012
7846
|
throw new EndpointError("Operation receipt identity mismatch", { status: 502, code: "INVALID_RECEIPT" });
|
|
8013
7847
|
}
|
|
8014
|
-
const current = await tx.get(
|
|
7848
|
+
const current = await tx.get(key2(operation.operationId));
|
|
8015
7849
|
if (!current) throw new EndpointError("Pending operation was discarded", { code: "OPERATION_DISCARDED", retryable: false });
|
|
8016
7850
|
const recoveredGroupSuccess = acceptedEcho && ["mls_group_control", "mls_application"].includes(current.action) && receipt.status === "succeeded" && current.status === "rejected";
|
|
8017
7851
|
if (terminal(current.status) && !recoveredGroupSuccess) return current;
|
|
@@ -8026,7 +7860,7 @@ function createEndpoint(options) {
|
|
|
8026
7860
|
}
|
|
8027
7861
|
if (terminal(next.status)) delete next.nextAttemptAt;
|
|
8028
7862
|
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(
|
|
7863
|
+
await tx.put(key2(operation.operationId), next);
|
|
8030
7864
|
await settleMlsDevTextOperation(tx, next);
|
|
8031
7865
|
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
7866
|
if (terminal(next.status) || next.status === "uncertain") {
|
|
@@ -8070,7 +7904,7 @@ function createEndpoint(options) {
|
|
|
8070
7904
|
assertOpen();
|
|
8071
7905
|
await initialization;
|
|
8072
7906
|
const { operation, acquired } = await store.transaction(async (tx) => {
|
|
8073
|
-
const current = await tx.get(
|
|
7907
|
+
const current = await tx.get(key2(operationId));
|
|
8074
7908
|
if (!current) throw new EndpointError("Unknown endpoint operation", { status: 404, code: "OPERATION_NOT_FOUND", retryable: false });
|
|
8075
7909
|
if (terminal(current.status)) return { operation: current, acquired: false };
|
|
8076
7910
|
await assertCurrentInstallation(tx);
|
|
@@ -8079,7 +7913,7 @@ function createEndpoint(options) {
|
|
|
8079
7913
|
if (!reconcileOnly && current.messageGroupId && current.status !== "uncertain") {
|
|
8080
7914
|
const group = await tx.get(`message-group/${current.messageGroupId}`);
|
|
8081
7915
|
const index = group?.children.findIndex((child) => child.operationId === operationId) ?? -1;
|
|
8082
|
-
const previous = group && index > 0 ? await tx.get(
|
|
7916
|
+
const previous = group && index > 0 ? await tx.get(key2(group.children[index - 1].operationId)) : void 0;
|
|
8083
7917
|
if (previous?.status === "rejected" || previous?.status === "succeeded" && !messageGroupChildSucceeded(previous)) {
|
|
8084
7918
|
const rejected = { ...current, status: "rejected", updatedAt: now(), receipt: {
|
|
8085
7919
|
protocol: CANON_ENDPOINT_PROTOCOL,
|
|
@@ -8093,7 +7927,7 @@ function createEndpoint(options) {
|
|
|
8093
7927
|
} };
|
|
8094
7928
|
delete rejected.request;
|
|
8095
7929
|
delete rejected.preparedContent;
|
|
8096
|
-
await tx.put(
|
|
7930
|
+
await tx.put(key2(operationId), rejected);
|
|
8097
7931
|
await removeOperationOutbox(tx, rejected);
|
|
8098
7932
|
await settleOperationOrder(tx, rejected);
|
|
8099
7933
|
return { operation: rejected, acquired: false };
|
|
@@ -8102,7 +7936,7 @@ function createEndpoint(options) {
|
|
|
8102
7936
|
}
|
|
8103
7937
|
if (current.lease && current.lease.owner !== owner && current.lease.expiresAt > clock()) return { operation: current, acquired: false };
|
|
8104
7938
|
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(
|
|
7939
|
+
await tx.put(key2(operationId), next);
|
|
8106
7940
|
return { operation: next, acquired: true };
|
|
8107
7941
|
});
|
|
8108
7942
|
if (terminal(operation.status) && operation.receipt) {
|
|
@@ -8120,8 +7954,8 @@ function createEndpoint(options) {
|
|
|
8120
7954
|
notify(operation);
|
|
8121
7955
|
const heartbeat = setInterval(() => {
|
|
8122
7956
|
void store.transaction(async (tx) => {
|
|
8123
|
-
const current = await tx.get(
|
|
8124
|
-
if (current?.lease?.owner === owner) await tx.put(
|
|
7957
|
+
const current = await tx.get(key2(operationId));
|
|
7958
|
+
if (current?.lease?.owner === owner) await tx.put(key2(operationId), { ...current, lease: { owner, expiresAt: clock() + leaseMs } });
|
|
8125
7959
|
}).catch(() => void 0);
|
|
8126
7960
|
}, Math.floor(leaseMs / 3));
|
|
8127
7961
|
heartbeat.unref?.();
|
|
@@ -8143,11 +7977,11 @@ function createEndpoint(options) {
|
|
|
8143
7977
|
const receipt2 = found?.status === "succeeded" || found?.status === "rejected" ? found : { ...deferredReceipt(operation), status: operation.status === "uncertain" ? "uncertain" : "pending" };
|
|
8144
7978
|
if (reconcileOnly && !terminal(receipt2.status)) {
|
|
8145
7979
|
await store.transaction(async (tx) => {
|
|
8146
|
-
const current = await tx.get(
|
|
7980
|
+
const current = await tx.get(key2(operationId));
|
|
8147
7981
|
if (current?.lease?.owner === owner) {
|
|
8148
7982
|
const next = { ...current };
|
|
8149
7983
|
delete next.lease;
|
|
8150
|
-
await tx.put(
|
|
7984
|
+
await tx.put(key2(operationId), next);
|
|
8151
7985
|
}
|
|
8152
7986
|
});
|
|
8153
7987
|
return receipt2;
|
|
@@ -8199,7 +8033,7 @@ function createEndpoint(options) {
|
|
|
8199
8033
|
});
|
|
8200
8034
|
checkingContentMode = false;
|
|
8201
8035
|
await store.transaction(async (tx) => {
|
|
8202
|
-
const current = await tx.get(
|
|
8036
|
+
const current = await tx.get(key2(operationId));
|
|
8203
8037
|
if (!current || current.lease?.owner !== owner || terminal(current.status)) throw new EndpointError("Delivery ownership changed", { code: "DELIVERY_FENCED" });
|
|
8204
8038
|
await assertCurrentInstallation(tx);
|
|
8205
8039
|
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 +8051,7 @@ function createEndpoint(options) {
|
|
|
8217
8051
|
const group = await tx.get(`message-group/${current.messageGroupId}`);
|
|
8218
8052
|
if (group?.cancelled || !messageGroups.allowed(current.messageGroupId)) throw new EndpointError("Message group was cancelled", { code: "OPERATION_DISCARDED", retryable: false });
|
|
8219
8053
|
}
|
|
8220
|
-
await tx.put(
|
|
8054
|
+
await tx.put(key2(operationId), { ...current, attempted: true });
|
|
8221
8055
|
});
|
|
8222
8056
|
const receipt = await transport.execute(request, controller.signal);
|
|
8223
8057
|
await persistReceipt(operation, receipt);
|
|
@@ -8226,17 +8060,17 @@ function createEndpoint(options) {
|
|
|
8226
8060
|
const failure = safeError(error);
|
|
8227
8061
|
if (reconcileOnly && failure.retryable) {
|
|
8228
8062
|
await store.transaction(async (tx) => {
|
|
8229
|
-
const current = await tx.get(
|
|
8063
|
+
const current = await tx.get(key2(operationId));
|
|
8230
8064
|
if (current?.lease?.owner === owner) {
|
|
8231
8065
|
const next = { ...current };
|
|
8232
8066
|
delete next.lease;
|
|
8233
|
-
await tx.put(
|
|
8067
|
+
await tx.put(key2(operationId), next);
|
|
8234
8068
|
}
|
|
8235
8069
|
});
|
|
8236
8070
|
return deferredReceipt(operation, failure);
|
|
8237
8071
|
}
|
|
8238
8072
|
const updated = await store.transaction(async (tx) => {
|
|
8239
|
-
const current = await tx.get(
|
|
8073
|
+
const current = await tx.get(key2(operationId));
|
|
8240
8074
|
if (!current) throw failure;
|
|
8241
8075
|
if (current.lease?.owner !== owner || terminal(current.status)) return current;
|
|
8242
8076
|
const lookupUncertain = resolvingReceipt && !failure.retryable;
|
|
@@ -8263,7 +8097,7 @@ function createEndpoint(options) {
|
|
|
8263
8097
|
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
8098
|
delete next.nextAttemptAt;
|
|
8265
8099
|
}
|
|
8266
|
-
await tx.put(
|
|
8100
|
+
await tx.put(key2(operationId), next);
|
|
8267
8101
|
await settleMlsDevTextOperation(tx, next);
|
|
8268
8102
|
if (terminal(next.status) || next.status === "uncertain") {
|
|
8269
8103
|
await removeOperationOutbox(tx, next);
|
|
@@ -8336,7 +8170,7 @@ function createEndpoint(options) {
|
|
|
8336
8170
|
const inputFingerprint = sensitive ? void 0 : await hash(canonicalEndpointJson({ ...authored, ...identity.ownershipVersion !== void 0 ? { ownershipVersion: identity.ownershipVersion } : {} }));
|
|
8337
8171
|
const previous = await store.transaction(async (tx) => {
|
|
8338
8172
|
await assertCurrentInstallation(tx);
|
|
8339
|
-
return tx.get(
|
|
8173
|
+
return tx.get(key2(operationId));
|
|
8340
8174
|
});
|
|
8341
8175
|
if (previous?.inputFingerprint !== void 0 && !sensitive) {
|
|
8342
8176
|
if (previous.inputFingerprint !== inputFingerprint || previous.guardId !== executeOptions.guardId) {
|
|
@@ -8401,7 +8235,7 @@ function createEndpoint(options) {
|
|
|
8401
8235
|
...normalized.preconditions ? { preconditions: normalized.preconditions } : {}
|
|
8402
8236
|
};
|
|
8403
8237
|
const existingOperation = /* @__PURE__ */ __name(async (tx) => {
|
|
8404
|
-
const existing = await tx.get(
|
|
8238
|
+
const existing = await tx.get(key2(operationId));
|
|
8405
8239
|
if (!existing) return void 0;
|
|
8406
8240
|
if (existing.fingerprint !== fingerprint || existing.sensitive !== sensitive || existing.guardId !== executeOptions.guardId) {
|
|
8407
8241
|
throw new EndpointError("Operation ID was already used for different content", { status: 409, code: "OPERATION_ID_COLLISION", retryable: false, operationId });
|
|
@@ -8429,7 +8263,6 @@ function createEndpoint(options) {
|
|
|
8429
8263
|
...endpointIntentConversationId(normalized) ? { conversationId: endpointIntentConversationId(normalized) } : {},
|
|
8430
8264
|
...typeof routedBody.messageId === "string" ? { messageId: routedBody.messageId } : {},
|
|
8431
8265
|
...normalized.action === "session_control" && typeof routedBody.agentId === "string" ? { agentId: routedBody.agentId } : {},
|
|
8432
|
-
...endpointIntentOrdering(normalized) === "artifact" && typeof routedBody.artifactId === "string" ? { artifactId: routedBody.artifactId } : {},
|
|
8433
8266
|
sensitive,
|
|
8434
8267
|
status: "queued",
|
|
8435
8268
|
attempts: 0,
|
|
@@ -8495,12 +8328,12 @@ function createEndpoint(options) {
|
|
|
8495
8328
|
let dispatched = 0;
|
|
8496
8329
|
let hasDeferred = false;
|
|
8497
8330
|
const blockedKeys = /* @__PURE__ */ new Set();
|
|
8498
|
-
for (const ordering of ["independent", "signal", "message", "provider", "
|
|
8331
|
+
for (const ordering of ["independent", "signal", "message", "provider", "conversation"]) {
|
|
8499
8332
|
let after;
|
|
8500
8333
|
do {
|
|
8501
8334
|
const batch = await store.transaction(async (tx) => {
|
|
8502
8335
|
const entries2 = await tx.scan("outbox/", { after, limit: 100 });
|
|
8503
|
-
return Promise.all(entries2.map(async (entry) => ({ key: entry.key, value: await tx.get(
|
|
8336
|
+
return Promise.all(entries2.map(async (entry) => ({ key: entry.key, value: await tx.get(key2(entry.value.operationId)) })));
|
|
8504
8337
|
});
|
|
8505
8338
|
for (const entry of batch) {
|
|
8506
8339
|
after = entry.key;
|
|
@@ -8536,7 +8369,7 @@ function createEndpoint(options) {
|
|
|
8536
8369
|
},
|
|
8537
8370
|
async getOperation(operationId) {
|
|
8538
8371
|
await initialization;
|
|
8539
|
-
return store.transaction((tx) => tx.get(
|
|
8372
|
+
return store.transaction((tx) => tx.get(key2(operationId)));
|
|
8540
8373
|
},
|
|
8541
8374
|
waitForOperation(operationId, waitOptions = {}) {
|
|
8542
8375
|
return new Promise((resolve3, reject) => {
|
|
@@ -8671,9 +8504,9 @@ function createEndpoint(options) {
|
|
|
8671
8504
|
},
|
|
8672
8505
|
refreshQueries: /* @__PURE__ */ __name(() => queryObserver.invalidate(), "refreshQueries"),
|
|
8673
8506
|
observeEvents: events.observe,
|
|
8674
|
-
createMediaPipeline(bytes
|
|
8507
|
+
createMediaPipeline(bytes) {
|
|
8675
8508
|
assertOpen();
|
|
8676
|
-
const pipeline3 = createEndpointMediaPipeline({ endpoint, store, bytes, hash, ids, clock
|
|
8509
|
+
const pipeline3 = createEndpointMediaPipeline({ endpoint, store, bytes, hash, ids, clock });
|
|
8677
8510
|
mediaPipelines2.add(pipeline3);
|
|
8678
8511
|
return pipeline3;
|
|
8679
8512
|
},
|
|
@@ -8714,7 +8547,7 @@ function createEndpoint(options) {
|
|
|
8714
8547
|
const recordKey = `${openerPrefix()}/${encodeURIComponent(input.messageId)}`;
|
|
8715
8548
|
await store.transaction(async (tx) => {
|
|
8716
8549
|
await assertCurrentInstallation(tx);
|
|
8717
|
-
const operation = await tx.get(
|
|
8550
|
+
const operation = await tx.get(key2(operationId));
|
|
8718
8551
|
if (operation) {
|
|
8719
8552
|
if (operation.openerFingerprint !== openerFingerprint)
|
|
8720
8553
|
throw new EndpointError("Opening line ID was already used for different content", { status: 409, code: "OPERATION_ID_COLLISION", retryable: false, operationId });
|
|
@@ -8735,7 +8568,7 @@ function createEndpoint(options) {
|
|
|
8735
8568
|
});
|
|
8736
8569
|
try {
|
|
8737
8570
|
const result = await endpoint.executeResult({ action: "start_conversation", body: { ...body } }, { operationId });
|
|
8738
|
-
if (result.status === "unavailable" && result.reason === "opening-text-required") {
|
|
8571
|
+
if (result.status === "unavailable" && (result.reason === "opening-text-required" || result.reason === "encrypted-room-required")) {
|
|
8739
8572
|
await store.transaction(async (tx) => {
|
|
8740
8573
|
await assertCurrentInstallation(tx);
|
|
8741
8574
|
await tx.delete(recordKey);
|
|
@@ -8906,29 +8739,27 @@ function createEndpoint(options) {
|
|
|
8906
8739
|
}
|
|
8907
8740
|
}
|
|
8908
8741
|
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
|
-
});
|
|
8742
|
+
await store.transaction((tx) => writeInboundState(tx, id2, state, reason));
|
|
8928
8743
|
},
|
|
8929
8744
|
async listInbound(listOptions = {}) {
|
|
8930
8745
|
await initialization;
|
|
8931
|
-
return store.transaction(
|
|
8746
|
+
return store.transaction(async (tx) => {
|
|
8747
|
+
const answered = listOptions.unanswered ? await completions.answered(tx) : void 0;
|
|
8748
|
+
return matchingRecords(tx, "inbound/", listOptions, (entry) => (!listOptions.states || listOptions.states.includes(entry.state)) && !answered?.has(entry.event.id));
|
|
8749
|
+
});
|
|
8750
|
+
},
|
|
8751
|
+
saveCompletion: completions.save,
|
|
8752
|
+
getCompletion: completions.get,
|
|
8753
|
+
listCompletions: completions.list,
|
|
8754
|
+
replaceCompletion: completions.replace,
|
|
8755
|
+
async finishCompletion(id2, settle) {
|
|
8756
|
+
for (const inputId of new Set(settle?.inputIds ?? [])) {
|
|
8757
|
+
const managed = handoffs.settle(inputId, settle.reason);
|
|
8758
|
+
if (managed && !await managed && (await endpoint.getInbound(inputId))?.state !== "settled") {
|
|
8759
|
+
throw new EndpointError("Input ownership changed before reconciliation", { code: "INBOUND_HANDOFF_LOST", retryable: false });
|
|
8760
|
+
}
|
|
8761
|
+
}
|
|
8762
|
+
await completions.finish(id2, settle);
|
|
8932
8763
|
},
|
|
8933
8764
|
async discardPending() {
|
|
8934
8765
|
await initialization;
|
|
@@ -8979,7 +8810,7 @@ function createEndpoint(options) {
|
|
|
8979
8810
|
listeners.clear();
|
|
8980
8811
|
closing = (async () => {
|
|
8981
8812
|
await Promise.allSettled([handoffs.close(), recovery.close(), syncConversation.close(), events.close(), ...[...consumers].map((consumer) => consumer.close()), ...[...mediaPipelines2].map((pipeline3) => pipeline3.close({ discard: true }))]);
|
|
8982
|
-
await Promise.allSettled([...running.values(), ...reads.values(), ...deviceRuns, initialization]);
|
|
8813
|
+
await Promise.allSettled([...running.values(), ...reads.values(), ...deviceRuns, initialization, roomHintWorker]);
|
|
8983
8814
|
await endpoint.discardPending();
|
|
8984
8815
|
await eraseEndpointPlaintext(store);
|
|
8985
8816
|
await store.close();
|
|
@@ -9002,11 +8833,10 @@ function createEndpoint(options) {
|
|
|
9002
8833
|
sensitiveRequests.clear();
|
|
9003
8834
|
sensitiveFingerprints.clear();
|
|
9004
8835
|
listeners.clear();
|
|
9005
|
-
closing = Promise.allSettled([handoffs.close(), recovery.close(), syncConversation.close(), events.close(), ...[...consumers].map((consumer) => consumer.close()), ...[...mediaPipelines2].map((pipeline3) => pipeline3.close())]).then(() => Promise.allSettled([...running.values(), ...reads.values(), ...deviceRuns, initialization])).then(() => store.close());
|
|
8836
|
+
closing = Promise.allSettled([handoffs.close(), recovery.close(), syncConversation.close(), events.close(), ...[...consumers].map((consumer) => consumer.close()), ...[...mediaPipelines2].map((pipeline3) => pipeline3.close())]).then(() => Promise.allSettled([...running.values(), ...reads.values(), ...deviceRuns, initialization, roomHintWorker])).then(() => store.close());
|
|
9006
8837
|
return closing;
|
|
9007
8838
|
}
|
|
9008
8839
|
};
|
|
9009
|
-
registerArtifactEndpoint(endpoint, { store, closed: /* @__PURE__ */ __name(() => closed, "closed"), assertCurrent: assertCurrentInstallation });
|
|
9010
8840
|
void initialization.then(() => scheduleRetry(0)).catch(() => void 0);
|
|
9011
8841
|
void initialization.then(() => sendDeferredOpeners()).catch(() => void 0);
|
|
9012
8842
|
if (mlsDevGroups) void initialization.then(() => resumeHeldTexts()).catch(() => void 0);
|
|
@@ -9064,7 +8894,7 @@ async function acquireAgentEndpoint(input) {
|
|
|
9064
8894
|
}
|
|
9065
8895
|
const requestedMls = input.options.mlsDev;
|
|
9066
8896
|
const mls = requestedMls ? await (async () => {
|
|
9067
|
-
const crypto2 = await import("./mls-crypto-
|
|
8897
|
+
const crypto2 = await import("./mls-crypto-ZBEC26B5.js");
|
|
9068
8898
|
const configuration = crypto2.parseMlsDevClientConfiguration(requestedMls.configuration);
|
|
9069
8899
|
if (configuration.environmentId !== environmentId || baseUrl !== CANON_RUNTIME_ENDPOINT_DEFAULTS[configuration.environmentId]?.apiBaseUrl || configuration.principalIds && !configuration.principalIds.includes(input.principalId) || !Number.isSafeInteger(input.ownershipVersion) || input.ownershipVersion < 0) throw new Error("MLS_DEV_AGENT_CONFIGURATION_BINDING_MISMATCH");
|
|
9070
8900
|
const custody = requestedMls.custody;
|
|
@@ -9098,12 +8928,12 @@ async function acquireAgentEndpoint(input) {
|
|
|
9098
8928
|
${baseUrl}
|
|
9099
8929
|
${input.principalId}
|
|
9100
8930
|
${input.ownershipVersion}`).digest("hex");
|
|
9101
|
-
const
|
|
8931
|
+
const key2 = `${directory}
|
|
9102
8932
|
${namespace}
|
|
9103
8933
|
${input.options.autoDrain === false ? "operator" : "live"}`;
|
|
9104
|
-
let entry = endpoints.get(
|
|
8934
|
+
let entry = endpoints.get(key2);
|
|
9105
8935
|
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((
|
|
8936
|
+
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
8937
|
throw new Error("MLS_DEV_AGENT_SHARED_CONFIGURATION_MISMATCH");
|
|
9108
8938
|
}
|
|
9109
8939
|
if (!entry) {
|
|
@@ -9143,9 +8973,9 @@ ${input.options.autoDrain === false ? "operator" : "live"}`;
|
|
|
9143
8973
|
autoDrain: input.options.autoDrain
|
|
9144
8974
|
}));
|
|
9145
8975
|
entry = shared;
|
|
9146
|
-
endpoints.set(
|
|
8976
|
+
endpoints.set(key2, entry);
|
|
9147
8977
|
void entry.endpoint.catch(() => {
|
|
9148
|
-
if (endpoints.get(
|
|
8978
|
+
if (endpoints.get(key2) === entry) endpoints.delete(key2);
|
|
9149
8979
|
});
|
|
9150
8980
|
}
|
|
9151
8981
|
entry.token = input.apiKey;
|
|
@@ -9162,8 +8992,8 @@ ${input.options.autoDrain === false ? "operator" : "live"}`;
|
|
|
9162
8992
|
if (released) return;
|
|
9163
8993
|
released = true;
|
|
9164
8994
|
entry.references--;
|
|
9165
|
-
if (entry.references === 0 && endpoints.get(
|
|
9166
|
-
endpoints.delete(
|
|
8995
|
+
if (entry.references === 0 && endpoints.get(key2) === entry) {
|
|
8996
|
+
endpoints.delete(key2);
|
|
9167
8997
|
await endpoint.close();
|
|
9168
8998
|
}
|
|
9169
8999
|
} };
|
|
@@ -9395,42 +9225,6 @@ var CanonClient = class {
|
|
|
9395
9225
|
delivery?.operationId ?? (options?.messageId ? stableOperationId("send_message", conversationId, options.messageId) : void 0)
|
|
9396
9226
|
);
|
|
9397
9227
|
}
|
|
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
9228
|
/**
|
|
9435
9229
|
* Execute a canonical verb over the wire contract (canon.verb-wire.v1).
|
|
9436
9230
|
* Bindings build the request with projectVerbIntentToWire; the endpoint
|
|
@@ -9674,27 +9468,18 @@ var CanonClient = class {
|
|
|
9674
9468
|
async deleteMessage(conversationId, messageId) {
|
|
9675
9469
|
await this.endpointAction("delete_message", { conversationId, messageId });
|
|
9676
9470
|
}
|
|
9677
|
-
async updateMessageDisposition(conversationId, messageId, inboundDisposition) {
|
|
9678
|
-
await this.endpointAction("update_message_disposition", { conversationId, messageId, inboundDisposition });
|
|
9679
|
-
}
|
|
9680
9471
|
async markAsRead(conversationId) {
|
|
9681
9472
|
await this.endpointAction("mark_read", { conversationId });
|
|
9682
9473
|
}
|
|
9683
9474
|
async react(conversationId, messageId, emoji) {
|
|
9684
9475
|
await this.endpointAction("react", { conversationId, messageId, emoji });
|
|
9685
9476
|
}
|
|
9686
|
-
async setStreaming(options) {
|
|
9687
|
-
await this.endpointPublish("publish_streaming", { ...options });
|
|
9688
|
-
}
|
|
9689
9477
|
async clearStreaming(conversationId) {
|
|
9690
9478
|
await this.endpointPublish("publish_streaming", { conversationId, streaming: false });
|
|
9691
9479
|
}
|
|
9692
9480
|
async setRuntimeTurn(options) {
|
|
9693
9481
|
await this.endpointPublish("publish_turn", { ...options });
|
|
9694
9482
|
}
|
|
9695
|
-
async setTyping(conversationId, typing, status) {
|
|
9696
|
-
await this.endpointPublish("publish_typing", { conversationId, typing, ...status ? { status } : {} });
|
|
9697
|
-
}
|
|
9698
9483
|
async peekRuntimeSignal(conversationId) {
|
|
9699
9484
|
return this.endpointQuery("peek_runtime_signal", { conversationId });
|
|
9700
9485
|
}
|
|
@@ -9736,38 +9521,6 @@ var CanonClient = class {
|
|
|
9736
9521
|
async patchRuntimeStreaming(conversationId, patch) {
|
|
9737
9522
|
await this.endpointPublish("publish_streaming", { ...patch, conversationId });
|
|
9738
9523
|
}
|
|
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
9524
|
async updateRuntimeStatus(options) {
|
|
9772
9525
|
await this.endpointPublish("publish_runtime_status", { ...options });
|
|
9773
9526
|
}
|
|
@@ -10027,7 +9780,7 @@ async function resumeRegistration(options) {
|
|
|
10027
9780
|
};
|
|
10028
9781
|
let session = await store.load();
|
|
10029
9782
|
if (session) {
|
|
10030
|
-
if (Object.entries(binding).some(([
|
|
9783
|
+
if (Object.entries(binding).some(([key2, value]) => session[key2] !== value)) {
|
|
10031
9784
|
throw new Error("Pending registration belongs to another Canon environment or endpoint set");
|
|
10032
9785
|
}
|
|
10033
9786
|
if (session.ownerPhone && session.ownerPhone !== input.ownerPhone || input.requestedAgentId && input.requestedAgentId !== session.requestedAgentId || input.localRegistrationId && input.localRegistrationId !== session.localRegistrationId) {
|
|
@@ -10440,7 +10193,7 @@ function normalizePendingRegistration(name, value) {
|
|
|
10440
10193
|
"createdAt",
|
|
10441
10194
|
"updatedAt"
|
|
10442
10195
|
];
|
|
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((
|
|
10196
|
+
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
10197
|
throw new Error(
|
|
10445
10198
|
`${PENDING_REGISTRATIONS_PATH} entry "${name}" must include a complete environment binding`
|
|
10446
10199
|
);
|
|
@@ -10521,7 +10274,7 @@ async function ensureAgentProfile(options) {
|
|
|
10521
10274
|
const existing = loadProfiles()[profile];
|
|
10522
10275
|
const pending = loadPendingRegistrations()[profile];
|
|
10523
10276
|
if (!pending && existing && !options.reconnect) {
|
|
10524
|
-
if (Object.entries(binding).some(([
|
|
10277
|
+
if (Object.entries(binding).some(([key2, value]) => existing[key2] !== value) || input.requestedAgentId && input.requestedAgentId !== existing.agentId) {
|
|
10525
10278
|
throw new Error("Saved Canon profile belongs to another environment or agent identity");
|
|
10526
10279
|
}
|
|
10527
10280
|
await verifyCanonRuntimeConnection(connection);
|
|
@@ -10547,7 +10300,7 @@ async function ensureAgentProfile(options) {
|
|
|
10547
10300
|
}, "save"),
|
|
10548
10301
|
loadCredentials: /* @__PURE__ */ __name(() => {
|
|
10549
10302
|
const saved = loadProfiles()[profile];
|
|
10550
|
-
if (saved && Object.entries(binding).some(([
|
|
10303
|
+
if (saved && Object.entries(binding).some(([key2, value]) => saved[key2] !== value)) {
|
|
10551
10304
|
throw new Error("Saved registration credential belongs to another Canon environment or endpoint set");
|
|
10552
10305
|
}
|
|
10553
10306
|
return saved;
|
|
@@ -10701,7 +10454,7 @@ function createRTDBClientHandle(client) {
|
|
|
10701
10454
|
if (target.family === "streaming") {
|
|
10702
10455
|
if (clear) return client.clearStreaming(target.conversationId);
|
|
10703
10456
|
const { updatedAt: _updatedAt, startedAt: _startedAt, ...patch2 } = record5(data);
|
|
10704
|
-
if (Object.keys(patch2).some((
|
|
10457
|
+
if (Object.keys(patch2).some((key2) => !["text", "status", "messageId", "turnId", "blocks"].includes(key2))) throw new Error("Unsupported streaming projection field");
|
|
10705
10458
|
return client.patchRuntimeStreaming(target.conversationId, patch2);
|
|
10706
10459
|
}
|
|
10707
10460
|
if (target.family === "turn-state") {
|
|
@@ -11206,125 +10959,6 @@ function runCli(moduleUrl, main, onError, metadata) {
|
|
|
11206
10959
|
}
|
|
11207
10960
|
__name(runCli, "runCli");
|
|
11208
10961
|
|
|
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
10962
|
// src/bounded-file-read.ts
|
|
11329
10963
|
var BoundedFileReadError = class extends Error {
|
|
11330
10964
|
constructor(failure) {
|
|
@@ -11368,7 +11002,7 @@ var ANTHROPIC_IMAGE_MIME_TYPES = /* @__PURE__ */ new Set([
|
|
|
11368
11002
|
"image/gif",
|
|
11369
11003
|
"image/webp"
|
|
11370
11004
|
]);
|
|
11371
|
-
var DEFAULT_MEDIA_CACHE_DIR =
|
|
11005
|
+
var DEFAULT_MEDIA_CACHE_DIR = join4(CANON_DIR, "media-cache");
|
|
11372
11006
|
var DEFAULT_MEDIA_MATERIALIZATION_BYTES = 10 * 1024 * 1024;
|
|
11373
11007
|
var MAX_CANON_MEDIA_BYTES = 100 * 1024 * 1024;
|
|
11374
11008
|
var MAX_ANTHROPIC_IMAGE_RAW_BYTES = 7 * 1024 * 1024;
|
|
@@ -11436,7 +11070,7 @@ function inferExtension(input) {
|
|
|
11436
11070
|
}
|
|
11437
11071
|
__name(inferExtension, "inferExtension");
|
|
11438
11072
|
function conversationMediaCacheDir(input) {
|
|
11439
|
-
return
|
|
11073
|
+
return join4(
|
|
11440
11074
|
input.rootDir ?? DEFAULT_MEDIA_CACHE_DIR,
|
|
11441
11075
|
sanitizeSegment(input.agentId, "agent"),
|
|
11442
11076
|
sanitizeSegment(input.conversationId, "conversation")
|
|
@@ -11452,7 +11086,7 @@ function buildCachePath(input) {
|
|
|
11452
11086
|
const fallbackName = `${input.attachment.kind}-${input.index}${extension}`;
|
|
11453
11087
|
const preferredName = input.attachment.fileName ? sanitizeDownloadFileName(input.attachment.fileName, fallbackName) : fallbackName;
|
|
11454
11088
|
const fileName = extname(preferredName) ? preferredName : `${preferredName}${extension}`;
|
|
11455
|
-
return
|
|
11089
|
+
return join4(
|
|
11456
11090
|
conversationMediaCacheDir({ agentId: input.agentId, conversationId: input.conversationId, rootDir }),
|
|
11457
11091
|
sanitizeSegment(input.messageId, "message"),
|
|
11458
11092
|
`${String(input.index).padStart(2, "0")}-${fileName}`
|
|
@@ -11602,7 +11236,7 @@ async function materializeAttachment(attachment, options) {
|
|
|
11602
11236
|
if (!response.body) {
|
|
11603
11237
|
throw new Error("Canon media response had no body");
|
|
11604
11238
|
}
|
|
11605
|
-
const tempPath = `${path}.${process.pid}.${
|
|
11239
|
+
const tempPath = `${path}.${process.pid}.${randomUUID6()}.part`;
|
|
11606
11240
|
const limiter = byteLimitTransform(maxBytes);
|
|
11607
11241
|
try {
|
|
11608
11242
|
await pipeline2(
|
|
@@ -11698,7 +11332,7 @@ async function uploadMediaFile(client, conversationId, filePath, options) {
|
|
|
11698
11332
|
const media = await client.getFileMediaPipeline(options?.fetchImpl);
|
|
11699
11333
|
const [attachment] = await media.prepare({
|
|
11700
11334
|
conversationId,
|
|
11701
|
-
attachmentId: options?.attachmentId ??
|
|
11335
|
+
attachmentId: options?.attachmentId ?? randomUUID6(),
|
|
11702
11336
|
files: [{ source: filePath, descriptor: {
|
|
11703
11337
|
mimeType,
|
|
11704
11338
|
sizeBytes: fileStat.size,
|
|
@@ -11727,7 +11361,7 @@ async function sendMediaFileMessage(client, conversationId, filePath, text = "",
|
|
|
11727
11361
|
const current = await client.getConversation(conversationId);
|
|
11728
11362
|
return !!current && originalMembers.size > 0 && current.memberIds.every((id2) => originalMembers.has(id2)) && canPublish();
|
|
11729
11363
|
} : void 0;
|
|
11730
|
-
const messageId = sendOptions.messageId ??
|
|
11364
|
+
const messageId = sendOptions.messageId ?? randomUUID6();
|
|
11731
11365
|
const pipeline3 = await client.getFileMediaPipeline(fetchImpl);
|
|
11732
11366
|
const delivered = await pipeline3.send({
|
|
11733
11367
|
conversationId,
|
|
@@ -11909,7 +11543,6 @@ export {
|
|
|
11909
11543
|
splitTextWithinBytes,
|
|
11910
11544
|
readCliPackageVersion,
|
|
11911
11545
|
runCli,
|
|
11912
|
-
createCompletedOutputJournal,
|
|
11913
11546
|
BoundedFileReadError,
|
|
11914
11547
|
readBoundedRegularFileHandle,
|
|
11915
11548
|
DEFAULT_MEDIA_CACHE_DIR,
|