@canonmsg/agent-sdk 13.2.0 → 13.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunks/{chunk-5YKGDUJX.js → chunk-AVR7QCHK.js} +369 -416
- package/dist/chunks/{chunk-DTAWEQCM.js → chunk-BQGXOAIW.js} +2 -2
- package/dist/chunks/{chunk-5WPAB5MB.js → chunk-CUPSYLBV.js} +2041 -2049
- package/dist/chunks/{chunk-YJUPKU47.js → chunk-HFT75BNB.js} +2 -2
- package/dist/chunks/{chunk-YHDDOADK.js → chunk-NIORQKUI.js} +1 -1
- package/dist/chunks/{chunk-6FD6KFQN.js → chunk-ZT3TJZAV.js} +400 -270
- package/dist/chunks/{mls-crypto-NZVMSUFU.js → mls-crypto-AW7TY744.js} +2 -2
- package/dist/cli.js +141 -26
- package/dist/index.js +6 -6
- package/dist/internal/agent-tools/communication-tool.d.ts +80 -0
- package/dist/internal/agent-tools/index.d.ts +1 -0
- package/dist/internal/backend-contracts/endpoint.d.ts +25 -0
- package/dist/internal/backend-contracts/mlsDevKeyPackages.d.ts +4 -0
- package/dist/internal/core/client.d.ts +0 -4
- package/dist/internal/core/endpoint-client.d.ts +8 -0
- package/dist/internal/core/index.d.ts +1 -1
- package/dist/internal/core/registration-profile.d.ts +4 -1
- package/dist/internal/core/types.d.ts +3 -1
- package/dist/internal/endpoint/engine.d.ts +34 -9
- package/dist/internal/endpoint/events.d.ts +1 -2
- package/dist/internal/endpoint/index.d.ts +0 -5
- package/dist/internal/endpoint/media.d.ts +4 -63
- package/dist/internal/endpoint/mls-dev-groups.d.ts +9 -1
- 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/mls.d.ts +12 -2
- package/dist/runner/runner.d.ts +5 -2
- package/package.json +2 -3
- 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,8 +5,9 @@ import {
|
|
|
5
5
|
createHttpEndpointTransport,
|
|
6
6
|
createNodeEndpointStore,
|
|
7
7
|
validateEndpointContentBoundary
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-HFT75BNB.js";
|
|
9
9
|
import {
|
|
10
|
+
AGENT_HOME_ELSEWHERE,
|
|
10
11
|
CANON_ENDPOINT_PROTOCOL,
|
|
11
12
|
CANON_VERB_WIRE_SCHEMA_VERSION,
|
|
12
13
|
ENDPOINT_ACTION_PATTERN,
|
|
@@ -15,6 +16,7 @@ import {
|
|
|
15
16
|
MLS_DEV_DISCLOSED_PROCESSING_FIELDS,
|
|
16
17
|
MLS_DEV_GROUP_RECEIPT_CLOCK_SKEW,
|
|
17
18
|
MLS_DEV_INTERACTION_LIMITS,
|
|
19
|
+
MLS_DEV_KEY_PACKAGE_CLAIM_LEASE_MS,
|
|
18
20
|
MLS_DEV_REPLY_QUOTE_MAX_CHARACTERS,
|
|
19
21
|
MLS_DEV_TEXT_PROTOCOL_V3,
|
|
20
22
|
MLS_DEV_UNSUPPORTED_CONTENT_ACTIONS,
|
|
@@ -22,6 +24,7 @@ import {
|
|
|
22
24
|
RUNTIME_CARD_FIELD_TYPES,
|
|
23
25
|
RUNTIME_CARD_LINE_ITEM_COLUMN_TYPES,
|
|
24
26
|
__name,
|
|
27
|
+
agentHomeElsewhereMessage,
|
|
25
28
|
assertMlsDevInteractionPayloadBinding,
|
|
26
29
|
canonicalEndpointJson,
|
|
27
30
|
createMlsDevCredentialSigningInput,
|
|
@@ -36,12 +39,11 @@ import {
|
|
|
36
39
|
getCanonEnvironmentContract,
|
|
37
40
|
isCanonMediaOrigin,
|
|
38
41
|
isCanonUploadUrl,
|
|
42
|
+
isConnectorRelayInstallation,
|
|
39
43
|
isMlsDevInteractionAnswerAllowed,
|
|
40
44
|
isMlsDevTextProtocol,
|
|
41
45
|
isMlsEnvironmentId,
|
|
42
46
|
isMlsPrincipalAlias,
|
|
43
|
-
logicalMediaBlobId,
|
|
44
|
-
mergeDeliveryObservation,
|
|
45
47
|
mlsAdmissionWait,
|
|
46
48
|
mlsDevProtocolCarriesCards,
|
|
47
49
|
normalizePublicRuntimeDescriptor,
|
|
@@ -102,12 +104,11 @@ import {
|
|
|
102
104
|
parseMlsPrincipalOpening,
|
|
103
105
|
parseMlsRoomIdentities,
|
|
104
106
|
prepareJsonContent,
|
|
105
|
-
projectDeliveryStatus,
|
|
106
107
|
projectVerbIntentToWire,
|
|
107
108
|
readMembershipChangeMetadata,
|
|
108
109
|
resolveAllowedUploadMimeType,
|
|
109
110
|
sanitizeDownloadFileName
|
|
110
|
-
} from "./chunk-
|
|
111
|
+
} from "./chunk-CUPSYLBV.js";
|
|
111
112
|
|
|
112
113
|
// src/media.ts
|
|
113
114
|
import { createWriteStream as createWriteStream2 } from "node:fs";
|
|
@@ -587,7 +588,6 @@ function endpointIntentOrdering(intent2) {
|
|
|
587
588
|
const body = { ...intent2.body, ...intent2.envelope };
|
|
588
589
|
if (intent2.action === "consume_runtime_signal") return "independent";
|
|
589
590
|
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
591
|
if (intent2.action === "session_control" && !body.primitive && typeof body.agentId === "string") {
|
|
592
592
|
const type = body.signal?.type;
|
|
593
593
|
if (type === "interrupt" || type === "stop_and_drop") return "signal";
|
|
@@ -609,12 +609,10 @@ async function orderOperation(tx, operation) {
|
|
|
609
609
|
}
|
|
610
610
|
__name(orderOperation, "orderOperation");
|
|
611
611
|
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
612
|
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
613
|
var operationOrderingKey = /* @__PURE__ */ __name((operation) => {
|
|
615
614
|
if (!operation.conversationId || operation.ordering === "independent" || operation.ordering === "provider") return void 0;
|
|
616
615
|
if (operation.ordering === "signal") return controlOrderingKey(operation);
|
|
617
|
-
if (operation.ordering === "artifact") return artifactOrderingPrefix(operation.conversationId, operation.artifactId ?? "~");
|
|
618
616
|
return operation.ordering === "message" ? `outbound-ack-order/${scope(operation.conversationId)}/${scope(operation.messageId)}/` : `outbound-order/${scope(operation.conversationId)}/`;
|
|
619
617
|
}, "operationOrderingKey");
|
|
620
618
|
var outboundPrefix = /* @__PURE__ */ __name((operation) => operationOrderingKey(operation), "outboundPrefix");
|
|
@@ -1106,42 +1104,18 @@ var ROOM_QUERIES = /* @__PURE__ */ new Set([
|
|
|
1106
1104
|
"list_voice_sessions",
|
|
1107
1105
|
"get_voice_session"
|
|
1108
1106
|
]);
|
|
1109
|
-
var ARTIFACT_QUERIES = /* @__PURE__ */ new Set(["list_artifacts", "get_artifact_log", "get_artifact_asset"]);
|
|
1110
1107
|
var INBOX_QUERIES = /* @__PURE__ */ new Set(["list_conversations", "get_unread_summary"]);
|
|
1111
1108
|
var CONTACT_QUERIES = /* @__PURE__ */ new Set(["list_contacts", "get_contact", "list_contact_requests"]);
|
|
1112
1109
|
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
1110
|
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
1111
|
if (!ROOM_OPERATIONS.has(operation.action)) return true;
|
|
1121
1112
|
if (operation.status !== "succeeded" && !(operation.action === "mls_application" && ["rejected", "uncertain"].includes(operation.status))) return false;
|
|
1122
1113
|
return queryAffectedByEvent({ kind: "conversation.changed", data: { conversationId: operation.conversationId } }, query, parameters);
|
|
1123
1114
|
}
|
|
1124
1115
|
__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
1116
|
function queryAffectedByEvent(event, query, parameters) {
|
|
1141
|
-
if (!ROOM_QUERIES.has(query) && !
|
|
1117
|
+
if (!ROOM_QUERIES.has(query) && !INBOX_QUERIES.has(query) && !CONTACT_QUERIES.has(query)) return true;
|
|
1142
1118
|
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
1119
|
if (/^contacts?\./.test(event.kind)) return CONTACT_QUERIES.has(query);
|
|
1146
1120
|
if (!/^(message\.|conversations?\.|voice\.session\.)/.test(event.kind)) return true;
|
|
1147
1121
|
if (CONTACT_QUERIES.has(query)) return false;
|
|
@@ -1172,10 +1146,10 @@ async function prepareEndpointIntent(input) {
|
|
|
1172
1146
|
const { intent: intent2 } = input;
|
|
1173
1147
|
const nested = intent2.action === "verb_execute";
|
|
1174
1148
|
const action = nested ? intent2.body.verb : intent2.action;
|
|
1175
|
-
if (!["remove_member", "update_member_role", "leave_conversation"
|
|
1149
|
+
if (!["remove_member", "update_member_role", "leave_conversation"].includes(String(action))) return intent2;
|
|
1176
1150
|
const body = intentBody(intent2);
|
|
1177
1151
|
const conversationId = body.conversationId ?? body.targetConversationId;
|
|
1178
|
-
const ownAdmission =
|
|
1152
|
+
const ownAdmission = action === "leave_conversation";
|
|
1179
1153
|
const targetUserId = body.targetUserId ?? body.userId ?? body.agentId;
|
|
1180
1154
|
if (typeof conversationId !== "string" || !ownAdmission && typeof targetUserId !== "string") {
|
|
1181
1155
|
throw new EndpointError("Membership control requires a conversation and target", { status: 400, code: "INVALID_MEMBERSHIP_CONTROL", retryable: false });
|
|
@@ -1345,19 +1319,22 @@ async function recordMlsInteractionProjection(tx, event, timestamp) {
|
|
|
1345
1319
|
await tx.put(key2, { event: copyJson(event), state: "settled", reason: "mls_interaction", createdAt: timestamp, updatedAt: timestamp });
|
|
1346
1320
|
}
|
|
1347
1321
|
__name(recordMlsInteractionProjection, "recordMlsInteractionProjection");
|
|
1322
|
+
var WORK_FLOOR_KEY = "recovery/work-floor";
|
|
1323
|
+
var beforeWorkFloor = /* @__PURE__ */ __name((floor, acceptedAt) => !!floor && Date.parse(acceptedAt) < Date.parse(floor.cutoverAt), "beforeWorkFloor");
|
|
1348
1324
|
async function recordVerifiedMlsInboundEvent(tx, event, feedOrdinal, timestamp, own = false) {
|
|
1349
1325
|
if (!event.conversationId || !Number.isSafeInteger(feedOrdinal) || feedOrdinal < 1) throw new Error("Invalid verified MLS input");
|
|
1350
1326
|
const key2 = `inbound/${event.id}`, existing = await tx.get(key2);
|
|
1351
1327
|
if (existing && existing.reason !== "mls_plaintext_withheld") return false;
|
|
1328
|
+
const history = !own && beforeWorkFloor(await tx.get(WORK_FLOOR_KEY), timestamp);
|
|
1352
1329
|
const record6 = {
|
|
1353
1330
|
event: copyJson(event),
|
|
1354
|
-
state: own ? "settled" : "observed",
|
|
1355
|
-
...own ? { reason: "own_mls_message" } : { queueKey: `inbound-ready/${encodeURIComponent(event.conversationId)}/mls/${String(feedOrdinal).padStart(16, "0")}/${encodeURIComponent(event.id)}` },
|
|
1331
|
+
state: own || history ? "settled" : "observed",
|
|
1332
|
+
...own ? { reason: "own_mls_message" } : history ? { reason: "before_take_over" } : { queueKey: `inbound-ready/${encodeURIComponent(event.conversationId)}/mls/${String(feedOrdinal).padStart(16, "0")}/${encodeURIComponent(event.id)}` },
|
|
1356
1333
|
createdAt: timestamp,
|
|
1357
1334
|
updatedAt: timestamp
|
|
1358
1335
|
};
|
|
1359
1336
|
await tx.put(key2, record6);
|
|
1360
|
-
if (!own) await enqueueInbound(tx, record6);
|
|
1337
|
+
if (!own && !history) await enqueueInbound(tx, record6);
|
|
1361
1338
|
return true;
|
|
1362
1339
|
}
|
|
1363
1340
|
__name(recordVerifiedMlsInboundEvent, "recordVerifiedMlsInboundEvent");
|
|
@@ -2035,7 +2012,7 @@ __name(createEndpointMigration, "createEndpointMigration");
|
|
|
2035
2012
|
|
|
2036
2013
|
// ../endpoint/src/media.ts
|
|
2037
2014
|
function createEndpointMediaPipeline(options) {
|
|
2038
|
-
const { endpoint, store, bytes
|
|
2015
|
+
const { endpoint, store, bytes } = options;
|
|
2039
2016
|
const clock = options.clock ?? Date.now;
|
|
2040
2017
|
const ids = options.ids ?? (() => crypto.randomUUID());
|
|
2041
2018
|
const leaseMs = Math.max(1e3, options.leaseMs ?? 3e4);
|
|
@@ -2090,14 +2067,6 @@ function createEndpointMediaPipeline(options) {
|
|
|
2090
2067
|
const listeners = /* @__PURE__ */ new Set();
|
|
2091
2068
|
let closed = false;
|
|
2092
2069
|
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
2070
|
let timer;
|
|
2102
2071
|
const prefix3 = "media-flow/";
|
|
2103
2072
|
const attachmentFor = /* @__PURE__ */ __name((file, value, resultUploadId) => {
|
|
@@ -2117,7 +2086,6 @@ function createEndpointMediaPipeline(options) {
|
|
|
2117
2086
|
for (const file of previous?.files ?? []) if (file.uploadId && !currentIds.has(file.uploadId)) await tx.delete(`media-prepared-upload/${file.uploadId}`);
|
|
2118
2087
|
for (const uploadId of currentIds) await tx.put(`media-prepared-upload/${uploadId}`, { id: flow.id });
|
|
2119
2088
|
}
|
|
2120
|
-
if (flow.status === "succeeded" || flow.status === "cancelled") delete flow.preparedContent;
|
|
2121
2089
|
await tx.put(`${prefix3}${flow.id}`, flow);
|
|
2122
2090
|
if (flow.status === "pending" || flow.status === "sending") await tx.put(`media-pending/${flow.id}`, { id: flow.id });
|
|
2123
2091
|
else await tx.delete(`media-pending/${flow.id}`);
|
|
@@ -2158,10 +2126,6 @@ function createEndpointMediaPipeline(options) {
|
|
|
2158
2126
|
timer.unref?.();
|
|
2159
2127
|
}
|
|
2160
2128
|
__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
2129
|
const unsubscribe = endpoint.subscribe((operation) => {
|
|
2166
2130
|
if (operation.status === "succeeded" && operation.action.endsWith("_upload")) schedule();
|
|
2167
2131
|
});
|
|
@@ -2170,265 +2134,21 @@ function createEndpointMediaPipeline(options) {
|
|
|
2170
2134
|
await Promise.allSettled(flow.files.filter((file) => file.reference).map((file) => bytes.release(file.reference)));
|
|
2171
2135
|
}
|
|
2172
2136
|
__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
2137
|
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 });
|
|
2138
|
+
async function retireBackground(flow) {
|
|
2307
2139
|
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);
|
|
2359
|
-
try {
|
|
2360
|
-
if (closed || sendOptions?.signal?.aborted) throw new DOMException("Attachment cancelled", "AbortError");
|
|
2361
|
-
const prior = await read(id2);
|
|
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
|
-
}
|
|
2403
|
-
} finally {
|
|
2404
|
-
await unlock(id2, owner);
|
|
2405
|
-
}
|
|
2406
|
-
const onAbort = /* @__PURE__ */ __name(() => {
|
|
2407
|
-
void pipeline3.cancel(id2).catch(() => void 0);
|
|
2408
|
-
}, "onAbort");
|
|
2409
|
-
sendOptions?.signal?.addEventListener("abort", onAbort, { once: true });
|
|
2140
|
+
if (flow.status !== "cancelled") {
|
|
2141
|
+
const owner = await acquire(flow.id);
|
|
2410
2142
|
try {
|
|
2411
|
-
|
|
2412
|
-
|
|
2143
|
+
const { id: id2, conversationId, messageId, createdAt, deliveryMode } = flow;
|
|
2144
|
+
await write({ id: id2, conversationId, messageId, createdAt, deliveryMode, message: {}, files: [], status: "cancelled" }, owner);
|
|
2413
2145
|
} finally {
|
|
2414
|
-
|
|
2146
|
+
await unlock(flow.id, owner);
|
|
2415
2147
|
}
|
|
2416
|
-
})();
|
|
2417
|
-
staging.set(id2, staged);
|
|
2418
|
-
try {
|
|
2419
|
-
return await staged;
|
|
2420
|
-
} finally {
|
|
2421
|
-
if (staging.get(id2) === staged) staging.delete(id2);
|
|
2422
2148
|
}
|
|
2149
|
+
throw new EndpointError("Attachment was cancelled", { code: "MEDIA_FLOW_CANCELLED", retryable: false });
|
|
2423
2150
|
}
|
|
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
|
-
});
|
|
2151
|
+
__name(retireBackground, "retireBackground");
|
|
2432
2152
|
async function stage(input, sendOptions, prepareOnly = false) {
|
|
2433
2153
|
if (closed) throw new Error("Endpoint media pipeline is closed");
|
|
2434
2154
|
const id2 = flowId(input.conversationId, input.messageId);
|
|
@@ -2470,7 +2190,7 @@ function createEndpointMediaPipeline(options) {
|
|
|
2470
2190
|
files.push({ descriptor: copyJson(file.descriptor), reference: await bytes.retain(`${id2}:${fingerprint}:${index}`, file.source) });
|
|
2471
2191
|
}
|
|
2472
2192
|
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,
|
|
2193
|
+
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
2194
|
}
|
|
2475
2195
|
} catch (error) {
|
|
2476
2196
|
await Promise.allSettled(files.map((file) => bytes.release(file.reference)));
|
|
@@ -2492,18 +2212,9 @@ function createEndpointMediaPipeline(options) {
|
|
|
2492
2212
|
}
|
|
2493
2213
|
__name(stage, "stage");
|
|
2494
2214
|
const pipeline3 = {
|
|
2495
|
-
canSendInBackground,
|
|
2496
2215
|
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
|
-
}
|
|
2216
|
+
const prior = await read(flowId(input.conversationId, input.messageId));
|
|
2217
|
+
if (prior?.deliveryMode === "background") return retireBackground(prior);
|
|
2507
2218
|
return stage(input, sendOptions);
|
|
2508
2219
|
},
|
|
2509
2220
|
async prepare(input, prepareOptions) {
|
|
@@ -2515,7 +2226,7 @@ function createEndpointMediaPipeline(options) {
|
|
|
2515
2226
|
},
|
|
2516
2227
|
async resume(id2, resumeOptions) {
|
|
2517
2228
|
const persisted = await read(id2);
|
|
2518
|
-
if (persisted?.deliveryMode === "background") return
|
|
2229
|
+
if (persisted?.deliveryMode === "background") return retireBackground(persisted);
|
|
2519
2230
|
installGuard(id2, resumeOptions?.beforePublish);
|
|
2520
2231
|
const existing = running.get(id2);
|
|
2521
2232
|
if (existing) return existing;
|
|
@@ -2557,7 +2268,7 @@ function createEndpointMediaPipeline(options) {
|
|
|
2557
2268
|
const progress = /* @__PURE__ */ __name((phase) => resumeOptions?.onProgress?.({ phase, totalBytes, uploadedBytes: offsets.reduce((sum, offset) => sum + offset, 0) }), "progress");
|
|
2558
2269
|
const publicationOperationId = /* @__PURE__ */ __name(() => `message:${flow.conversationId}:${flow.messageId}${flow.publicationGeneration ? `:g${flow.publicationGeneration}` : ""}`, "publicationOperationId");
|
|
2559
2270
|
const publish = /* @__PURE__ */ __name(async (reconciling = false) => {
|
|
2560
|
-
if (!reconciling && flow.requiresPublicationApproval && !await publicationGuards.get(id2)?.check()) {
|
|
2271
|
+
if (!reconciling && flow.requiresPublicationApproval && !await publicationGuards.get(id2)?.check({ automaticResend: false })) {
|
|
2561
2272
|
throw new EndpointError("Attachment publication requires a current audience approval", { code: "MEDIA_PUBLICATION_APPROVAL_REQUIRED", retryable: true });
|
|
2562
2273
|
}
|
|
2563
2274
|
flow.status = "sending";
|
|
@@ -2771,15 +2482,6 @@ function createEndpointMediaPipeline(options) {
|
|
|
2771
2482
|
},
|
|
2772
2483
|
async resumePending() {
|
|
2773
2484
|
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
2485
|
let after;
|
|
2784
2486
|
do {
|
|
2785
2487
|
const records = await store.transaction((tx) => tx.scan("media-pending/", { after, limit: 100 }));
|
|
@@ -2793,20 +2495,9 @@ function createEndpointMediaPipeline(options) {
|
|
|
2793
2495
|
if (saved) await pipeline3.cancel(saved.id);
|
|
2794
2496
|
},
|
|
2795
2497
|
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();
|
|
2498
|
+
const retired = await read(id2);
|
|
2499
|
+
if (retired?.deliveryMode === "background") {
|
|
2500
|
+
await retireBackground(retired).catch(() => void 0);
|
|
2810
2501
|
return;
|
|
2811
2502
|
}
|
|
2812
2503
|
if (staging.has(id2)) cancelledStages.add(id2);
|
|
@@ -2829,15 +2520,12 @@ function createEndpointMediaPipeline(options) {
|
|
|
2829
2520
|
async close(closeOptions) {
|
|
2830
2521
|
closed = true;
|
|
2831
2522
|
unsubscribe();
|
|
2832
|
-
stopConnections?.();
|
|
2833
|
-
stopBackground?.();
|
|
2834
2523
|
listeners.clear();
|
|
2835
2524
|
for (const guard of publicationGuards.values()) guard.unregister();
|
|
2836
2525
|
publicationGuards.clear();
|
|
2837
2526
|
if (timer) clearTimeout(timer);
|
|
2838
2527
|
for (const controller of controllers.values()) controller.abort();
|
|
2839
2528
|
await Promise.allSettled([...staging.values(), ...running.values()]);
|
|
2840
|
-
if (closeOptions?.discard) await background?.cleanup();
|
|
2841
2529
|
if (closeOptions?.discard) {
|
|
2842
2530
|
let after;
|
|
2843
2531
|
do {
|
|
@@ -3412,13 +3100,6 @@ function createExternalInboundClaims(input) {
|
|
|
3412
3100
|
}
|
|
3413
3101
|
__name(createExternalInboundClaims, "createExternalInboundClaims");
|
|
3414
3102
|
|
|
3415
|
-
// ../endpoint/src/artifact-context.ts
|
|
3416
|
-
var contexts = /* @__PURE__ */ new WeakMap();
|
|
3417
|
-
function registerArtifactEndpoint(endpoint, context) {
|
|
3418
|
-
contexts.set(endpoint, context);
|
|
3419
|
-
}
|
|
3420
|
-
__name(registerArtifactEndpoint, "registerArtifactEndpoint");
|
|
3421
|
-
|
|
3422
3103
|
// ../endpoint/src/receipts.ts
|
|
3423
3104
|
function endpointReceiptResult(receipt) {
|
|
3424
3105
|
if (receipt.resultError) throw new EndpointError(receipt.resultError.message, { status: receipt.resultError.status, code: receipt.resultError.code, retryable: receipt.resultError.retryable, operationId: receipt.operationId });
|
|
@@ -5295,6 +4976,12 @@ var appIntentKey = /* @__PURE__ */ __name((operationId) => `mls-dev-group/applic
|
|
|
5295
4976
|
var appOperationKey = /* @__PURE__ */ __name((deviceId, operationId) => `mls-dev-group/application-operation/${encodeURIComponent(deviceId)}/${encodeURIComponent(operationId)}`, "appOperationKey");
|
|
5296
4977
|
var acceptedReceiptKey = /* @__PURE__ */ __name((operationId) => `mls-dev-group/accepted-receipt/${encodeURIComponent(operationId)}`, "acceptedReceiptKey");
|
|
5297
4978
|
var appMessageKey = /* @__PURE__ */ __name((cid, messageId) => `mls-dev-group/application-id/${encodeURIComponent(cid)}/${encodeURIComponent(messageId)}`, "appMessageKey");
|
|
4979
|
+
var BEHIND_CODES = ["MLS_DEV_GROUP_HEAD_CHANGED", "MLS_DEV_GROUP_ADMISSION_CHANGED"];
|
|
4980
|
+
var MLS_DEV_RESEND_MAX = 3;
|
|
4981
|
+
var readersOf = /* @__PURE__ */ __name((roster) => [...new Set(roster.flatMap(({ device: { principal, installationId } }) => [
|
|
4982
|
+
`${principal.type}:${principal.id}`,
|
|
4983
|
+
...isConnectorRelayInstallation(installationId) ? [`relay:${installationId}`] : []
|
|
4984
|
+
]))].sort(), "readersOf");
|
|
5298
4985
|
var same6 = /* @__PURE__ */ __name((a, b) => canonicalEndpointJson(a) === canonicalEndpointJson(b), "same");
|
|
5299
4986
|
async function roomRemoval(tx, conversationId) {
|
|
5300
4987
|
const room = await tx.get(`conversation/${conversationId}`);
|
|
@@ -5322,6 +5009,10 @@ function fail6(code = "MLS_DEV_GROUP_STATE_INVALID", retryable = false) {
|
|
|
5322
5009
|
throw new EndpointError(code, { code, retryable });
|
|
5323
5010
|
}
|
|
5324
5011
|
__name(fail6, "fail");
|
|
5012
|
+
function admissionOf(operation) {
|
|
5013
|
+
return operation.request?.body.value?.preconditions?.admissionId;
|
|
5014
|
+
}
|
|
5015
|
+
__name(admissionOf, "admissionOf");
|
|
5325
5016
|
function id(value) {
|
|
5326
5017
|
if (typeof value !== "string" || !ENDPOINT_ID_PATTERN.test(value) || value === "." || value === "..") fail6("INVALID_MLS_DEV_GROUP_INPUT");
|
|
5327
5018
|
return value;
|
|
@@ -6065,9 +5756,10 @@ function createMlsDevGroups(options, context, keyPackages, bridge) {
|
|
|
6065
5756
|
const room = await tx.get(`conversation/${conversationId}`);
|
|
6066
5757
|
const group = envelope((await readPreparationSnapshot(tx, scope4(conversationId))).protocolState, init.binding, conversationId);
|
|
6067
5758
|
const missed = await tx.get(missedEarlierKey(conversationId)) === true ? { missedEarlier: true } : {};
|
|
5759
|
+
const outside = !!room && !!room.mls && (!group || group.removed);
|
|
5760
|
+
const noted = { ...missed, ...outside ? { outside: true } : {} };
|
|
6068
5761
|
if (room?.mlsAdmissionHold !== true) {
|
|
6069
|
-
|
|
6070
|
-
return Object.freeze({ held: joining, waitingPrincipalIds: Object.freeze(joining ? [init.identity.principalId] : []), ...missed });
|
|
5762
|
+
return Object.freeze({ held: outside, waitingPrincipalIds: Object.freeze(outside ? [init.identity.principalId] : []), ...noted });
|
|
6071
5763
|
}
|
|
6072
5764
|
const accounts = mlsRoomAccounts(room, init.binding.environmentId);
|
|
6073
5765
|
const admitted = new Set((group && !group.removed ? group.roster : []).map((entry) => mlsAccountOf(accounts, entry.device.principal)));
|
|
@@ -6080,7 +5772,7 @@ function createMlsDevGroups(options, context, keyPackages, bridge) {
|
|
|
6080
5772
|
joinedAtMs: Number(room.mlsMemberJoinedAtMs),
|
|
6081
5773
|
nowMs: context.clock()
|
|
6082
5774
|
});
|
|
6083
|
-
return Object.freeze({ held: waiting.length > 0, waitingPrincipalIds: Object.freeze(waiting), ...
|
|
5775
|
+
return Object.freeze({ held: waiting.length > 0, waitingPrincipalIds: Object.freeze(waiting), ...noted });
|
|
6084
5776
|
});
|
|
6085
5777
|
}
|
|
6086
5778
|
__name(admissionHold, "admissionHold");
|
|
@@ -6088,7 +5780,7 @@ function createMlsDevGroups(options, context, keyPackages, bridge) {
|
|
|
6088
5780
|
if ((await admissionHold(conversationId)).held) fail6("MLS_DEV_GROUP_ADMISSION_PENDING", true);
|
|
6089
5781
|
}
|
|
6090
5782
|
__name(assertNotHeld, "assertNotHeld");
|
|
6091
|
-
async function sendText(input, operationId, guardId) {
|
|
5783
|
+
async function sendText(input, operationId, guardId, resend) {
|
|
6092
5784
|
const cid = input.conversationId, init = await initialized();
|
|
6093
5785
|
const authoredHash = await bridge.hash(canonicalEndpointJson(guardId ? { ...input, guardId } : input));
|
|
6094
5786
|
async function existing(tx) {
|
|
@@ -6100,10 +5792,11 @@ function createMlsDevGroups(options, context, keyPackages, bridge) {
|
|
|
6100
5792
|
__name(existing, "existing");
|
|
6101
5793
|
const previous = await store.transaction(existing);
|
|
6102
5794
|
if (previous) return previous;
|
|
6103
|
-
if (!await caughtUp(cid)) await refresh(cid);
|
|
5795
|
+
if (resend || !await caughtUp(cid)) await refresh(cid);
|
|
6104
5796
|
await assertNotHeld(cid);
|
|
6105
5797
|
const messageId = input.messageId ?? id(context.ids());
|
|
6106
5798
|
let captured, replyTo;
|
|
5799
|
+
let replaced, written;
|
|
6107
5800
|
const operation = await prepareAndCommitContent({
|
|
6108
5801
|
store,
|
|
6109
5802
|
scope: scope4(cid),
|
|
@@ -6111,7 +5804,9 @@ function createMlsDevGroups(options, context, keyPackages, bridge) {
|
|
|
6111
5804
|
existing: /* @__PURE__ */ __name(async (tx, state) => {
|
|
6112
5805
|
const previous2 = await existing(tx);
|
|
6113
5806
|
if (previous2) return previous2;
|
|
6114
|
-
|
|
5807
|
+
const mapped = await tx.get(appMessageKey(cid, messageId)), local = await tx.get(mlsDevTextMessageKey(cid, messageId));
|
|
5808
|
+
if (resend ? mapped !== resend.previous.operationId || local?.message.operationId !== resend.previous.operationId || local.feedOrdinal !== null || local.deleted : mapped !== void 0 || local !== void 0) fail6("MLS_DEV_APPLICATION_ID_CONFLICT");
|
|
5809
|
+
written = local?.createdAtMs;
|
|
6115
5810
|
const current = await capture(tx, state, init, cid), group = current.envelope;
|
|
6116
5811
|
if (!isMlsDevTextProtocol(current.protocol)) fail6("MLS_DEV_TEXT_PROTOCOL_UNSUPPORTED");
|
|
6117
5812
|
if ((input.replyTo || input.mentions || input.item) && current.protocol !== MLS_DEV_TEXT_PROTOCOL_V3) fail6("MLS_DEV_TEXT_ONLY");
|
|
@@ -6120,7 +5815,8 @@ function createMlsDevGroups(options, context, keyPackages, bridge) {
|
|
|
6120
5815
|
if (!Array.isArray(members) || input.mentions.some((mention) => !members.includes(mention))) fail6("MLS_DEV_MENTION_NOT_MEMBER");
|
|
6121
5816
|
}
|
|
6122
5817
|
if (input.item && !canonItemReference(input.item, cid, init.binding.environmentId)) fail6("MLS_DEV_ITEM_INVALID");
|
|
6123
|
-
if (
|
|
5818
|
+
if (resend?.replyTo) replyTo = resend.replyTo;
|
|
5819
|
+
else if (input.replyTo) {
|
|
6124
5820
|
const quote = mlsDevReplyQuote(await tx.get(mlsDevTextMessageKey(cid, input.replyTo)), MLS_DEV_REPLY_QUOTE_MAX_CHARACTERS);
|
|
6125
5821
|
replyTo = { messageId: input.replyTo, quote: quote ?? fail6("MLS_DEV_REPLY_UNAVAILABLE") };
|
|
6126
5822
|
}
|
|
@@ -6128,6 +5824,7 @@ function createMlsDevGroups(options, context, keyPackages, bridge) {
|
|
|
6128
5824
|
await assertRoomOpen(tx, cid, group.roster);
|
|
6129
5825
|
if (group.pending) fail6("MLS_DEV_GROUP_PENDING_CONTROL", true);
|
|
6130
5826
|
if (!same6(group.head, current.authority?.marker?.head)) fail6("MLS_DEV_GROUP_HEAD_CHANGED", true);
|
|
5827
|
+
if (resend?.readers && (readersOf(group.roster).some((reader) => !resend.readers.includes(reader)) || current.authority?.admissionId !== admissionOf(resend.previous))) fail6("MLS_DEV_RESEND_AUDIENCE_CHANGED");
|
|
6131
5828
|
captured = current;
|
|
6132
5829
|
return void 0;
|
|
6133
5830
|
}, "existing"),
|
|
@@ -6179,8 +5876,24 @@ function createMlsDevGroups(options, context, keyPackages, bridge) {
|
|
|
6179
5876
|
commit: /* @__PURE__ */ __name(async (tx, prepared, state) => {
|
|
6180
5877
|
const current = await capture(tx, state, init, cid);
|
|
6181
5878
|
if (current.fingerprint !== prepared.fingerprint) fail6("MLS_DEV_GROUP_DEPENDENCY_CHANGED", true);
|
|
6182
|
-
const
|
|
6183
|
-
|
|
5879
|
+
const timestamp = new Date(context.clock()).toISOString();
|
|
5880
|
+
let queueOrder;
|
|
5881
|
+
if (resend) {
|
|
5882
|
+
const before = await tx.get(`operation/${resend.previous.operationId}`);
|
|
5883
|
+
const settled = before && resend.receipt ? await bridge.settle(tx, before, resend.receipt, false) : before;
|
|
5884
|
+
if (settled?.status !== "rejected" || settled.resentAs) fail6("MLS_DEV_RESEND_UNAVAILABLE");
|
|
5885
|
+
if (resend.receipt) queueOrder = settled.queueOrder;
|
|
5886
|
+
replaced = { ...settled, resentAs: operationId, updatedAt: timestamp };
|
|
5887
|
+
await tx.put(`operation/${settled.operationId}`, replaced);
|
|
5888
|
+
}
|
|
5889
|
+
await tx.put(appIntentKey(operationId), {
|
|
5890
|
+
version: 1,
|
|
5891
|
+
authoredHash,
|
|
5892
|
+
envelope: prepared.wire,
|
|
5893
|
+
envelopeHash: prepared.envelopeHash,
|
|
5894
|
+
readers: resend?.readers ?? readersOf(current.envelope.roster),
|
|
5895
|
+
resends: resend?.resends ?? 0
|
|
5896
|
+
});
|
|
6184
5897
|
await tx.put(appMessageKey(cid, messageId), operationId);
|
|
6185
5898
|
await putMlsDevTextMessage(tx, {
|
|
6186
5899
|
version: 1,
|
|
@@ -6188,7 +5901,7 @@ function createMlsDevGroups(options, context, keyPackages, bridge) {
|
|
|
6188
5901
|
senderId: init.binding.principal.id,
|
|
6189
5902
|
senderType: principal.type,
|
|
6190
5903
|
status: "queued",
|
|
6191
|
-
createdAtMs,
|
|
5904
|
+
createdAtMs: written ?? context.clock(),
|
|
6192
5905
|
feedOrdinal: null
|
|
6193
5906
|
});
|
|
6194
5907
|
const media = prepared.text.kind === "item" ? { mediaUploadIds: [prepared.text.item.uploadId] } : {};
|
|
@@ -6201,7 +5914,9 @@ function createMlsDevGroups(options, context, keyPackages, bridge) {
|
|
|
6201
5914
|
messageId,
|
|
6202
5915
|
fingerprint: prepared.envelopeHash,
|
|
6203
5916
|
ordering: "conversation",
|
|
5917
|
+
...queueOrder === void 0 ? {} : { queueOrder },
|
|
6204
5918
|
...guardId ? { guardId } : {},
|
|
5919
|
+
...resend?.receipt ? { automaticResend: true } : {},
|
|
6205
5920
|
sensitive: false,
|
|
6206
5921
|
status: "queued",
|
|
6207
5922
|
attempts: 0,
|
|
@@ -6219,10 +5934,45 @@ function createMlsDevGroups(options, context, keyPackages, bridge) {
|
|
|
6219
5934
|
});
|
|
6220
5935
|
}, "commit")
|
|
6221
5936
|
});
|
|
5937
|
+
if (replaced) await bridge.afterReceipt(replaced);
|
|
6222
5938
|
bridge.submitted(operation);
|
|
6223
5939
|
return operation;
|
|
6224
5940
|
}
|
|
6225
5941
|
__name(sendText, "sendText");
|
|
5942
|
+
async function resendText(operation, receipt) {
|
|
5943
|
+
const cid = operation.conversationId, messageId = operation.messageId;
|
|
5944
|
+
if (operation.action !== "mls_application" || !cid || !messageId) return void 0;
|
|
5945
|
+
if (receipt && (receipt.status !== "rejected" || !BEHIND_CODES.includes(receipt.error?.code ?? ""))) return void 0;
|
|
5946
|
+
const { intent: intent2, local } = await store.transaction(async (tx) => ({
|
|
5947
|
+
intent: await tx.get(appIntentKey(operation.operationId)),
|
|
5948
|
+
local: await tx.get(mlsDevTextMessageKey(cid, messageId))
|
|
5949
|
+
}));
|
|
5950
|
+
const message = local?.message;
|
|
5951
|
+
if (!intent2 || !local || local.deleted || local.feedOrdinal !== null || message?.operationId !== operation.operationId || message.kind !== "text" && message.kind !== "item") return void 0;
|
|
5952
|
+
if (receipt && (!intent2.readers || (intent2.resends ?? 0) >= MLS_DEV_RESEND_MAX)) return void 0;
|
|
5953
|
+
const { replyTo, mentions, ...item } = message.kind === "item" ? message.item : { replyTo: message.replyTo, mentions: message.mentions };
|
|
5954
|
+
const replyAuthority = operation.request?.body.value.replyAuthority;
|
|
5955
|
+
const input = {
|
|
5956
|
+
conversationId: cid,
|
|
5957
|
+
messageId,
|
|
5958
|
+
...message.kind === "item" ? { item } : { text: message.text },
|
|
5959
|
+
...replyTo ? { replyTo: replyTo.messageId } : {},
|
|
5960
|
+
...mentions?.length ? { mentions: [...mentions] } : {},
|
|
5961
|
+
...replyAuthority ? { replyAuthority } : {}
|
|
5962
|
+
};
|
|
5963
|
+
const successor = id(await bridge.hash(canonicalEndpointJson(["canon.mls.dev-resend.v1", operation.operationId])));
|
|
5964
|
+
try {
|
|
5965
|
+
return await sendText(input, successor, operation.guardId, {
|
|
5966
|
+
previous: operation,
|
|
5967
|
+
...replyTo ? { replyTo } : {},
|
|
5968
|
+
...receipt ? { receipt, readers: intent2.readers, resends: (intent2.resends ?? 0) + 1 } : { resends: 0 }
|
|
5969
|
+
});
|
|
5970
|
+
} catch (error) {
|
|
5971
|
+
if (!receipt || !(error instanceof EndpointError) || error.retryable || ["ENDPOINT_CLOSED", "PREPARATION_INSTALLATION_FENCED"].includes(error.code)) throw error;
|
|
5972
|
+
return void 0;
|
|
5973
|
+
}
|
|
5974
|
+
}
|
|
5975
|
+
__name(resendText, "resendText");
|
|
6226
5976
|
async function holdText(input, operationId) {
|
|
6227
5977
|
const cid = input.conversationId;
|
|
6228
5978
|
if (input.item || input.replyAuthority || typeof input.text !== "string") return sendText(input, operationId);
|
|
@@ -7069,6 +6819,11 @@ function createMlsDevGroups(options, context, keyPackages, bridge) {
|
|
|
7069
6819
|
const input = textInput(value), selectedId = id(operationId), selectedGuard = guardId === void 0 ? void 0 : id(guardId);
|
|
7070
6820
|
return run(() => serializeInboundPreparation(store, input.conversationId, () => sendText(input, selectedId, selectedGuard)));
|
|
7071
6821
|
},
|
|
6822
|
+
/** A person's Retry of a text or item Canon refused: encrypted again for the room as it is now (`resendText`). */
|
|
6823
|
+
resendText(operation) {
|
|
6824
|
+
const cid = id(operation.conversationId);
|
|
6825
|
+
return run(() => serializeInboundPreparation(store, cid, () => resendText(operation)));
|
|
6826
|
+
},
|
|
7072
6827
|
/** `sendText` for a person's composer: in a held room the text is kept, not refused (undefined), until `sendHeldTexts`. */
|
|
7073
6828
|
holdText(value, operationId) {
|
|
7074
6829
|
const input = textInput(value), selectedId = id(operationId);
|
|
@@ -7116,9 +6871,9 @@ function createMlsDevGroups(options, context, keyPackages, bridge) {
|
|
|
7116
6871
|
return receipt;
|
|
7117
6872
|
});
|
|
7118
6873
|
},
|
|
7119
|
-
async settleReceipt(operation, receipt) {
|
|
6874
|
+
async settleReceipt(operation, receipt, options2) {
|
|
7120
6875
|
if (!["mls_group_control", "mls_application"].includes(operation.action)) return false;
|
|
7121
|
-
if (operation.action === "mls_application" && receipt.status !== "succeeded") return
|
|
6876
|
+
if (operation.action === "mls_application" && receipt.status !== "succeeded") return !options2?.reconcileOnly && receipt.status === "rejected" && BEHIND_CODES.includes(receipt.error?.code ?? "") && !!operation.conversationId && await run(() => serializeInboundPreparation(store, operation.conversationId, () => resendText(operation, receipt))) !== void 0;
|
|
7122
6877
|
if (receipt.status === "succeeded" && isMlsDevTextProtocol(await bridge.resolveProtocol?.(operation.conversationId ?? fail6()))) {
|
|
7123
6878
|
const cid = operation.conversationId;
|
|
7124
6879
|
const accepted2 = operation.action === "mls_application" ? parseMlsDevApplicationReceipt(receipt.result) : parseMlsDevGroupAcceptedControl(receipt.result);
|
|
@@ -7204,6 +6959,8 @@ function terminal(status) {
|
|
|
7204
6959
|
return status === "succeeded" || status === "rejected";
|
|
7205
6960
|
}
|
|
7206
6961
|
__name(terminal, "terminal");
|
|
6962
|
+
var TAKE_OVER_OPERATION = "initialize-endpoint:take-over:";
|
|
6963
|
+
var TAKE_OVER_DISCARDED = "A take-over is not sent again by itself; start the runner with --take-over again";
|
|
7207
6964
|
function safeError(error) {
|
|
7208
6965
|
return error instanceof EndpointError ? error : new EndpointError("Canon delivery is deferred until the connection recovers");
|
|
7209
6966
|
}
|
|
@@ -7295,6 +7052,19 @@ function createEndpoint(options) {
|
|
|
7295
7052
|
return identity;
|
|
7296
7053
|
});
|
|
7297
7054
|
void initialization.catch(() => void 0);
|
|
7055
|
+
const opened = initialization.then(() => store.transaction(async (tx) => {
|
|
7056
|
+
for (let after; ; ) {
|
|
7057
|
+
const page = await tx.scan("outbox/", { ...after ? { after } : {}, limit: 100 });
|
|
7058
|
+
for (const entry of page) {
|
|
7059
|
+
if (!entry.value.operationId.startsWith(TAKE_OVER_OPERATION)) continue;
|
|
7060
|
+
const operation = await tx.get(key2(entry.value.operationId));
|
|
7061
|
+
if (operation && !terminal(operation.status)) await discardOperation(tx, operation, TAKE_OVER_DISCARDED);
|
|
7062
|
+
}
|
|
7063
|
+
if (page.length < 100) return;
|
|
7064
|
+
after = page.at(-1).key;
|
|
7065
|
+
}
|
|
7066
|
+
}));
|
|
7067
|
+
void opened.catch(() => void 0);
|
|
7298
7068
|
let mlsPrincipal;
|
|
7299
7069
|
function readMlsPrincipalId() {
|
|
7300
7070
|
mlsPrincipal ??= (async () => {
|
|
@@ -7506,6 +7276,7 @@ function createEndpoint(options) {
|
|
|
7506
7276
|
const admissionTimers = /* @__PURE__ */ new Map();
|
|
7507
7277
|
const ADMISSION_IDLE_MS = 10 * 6e4, CLAIM_JOURNAL_KEEP_MS = 60 * 6e4;
|
|
7508
7278
|
const admissionUnfinished = /* @__PURE__ */ new Set();
|
|
7279
|
+
const announcementLooks = /* @__PURE__ */ new Map();
|
|
7509
7280
|
const claimAdds = /* @__PURE__ */ new Map(), CLAIM_ADDS_MAX = 3;
|
|
7510
7281
|
const admissionRetry = { initial: options.mlsAdmissionRetryMs?.initial ?? 3e4, maximum: options.mlsAdmissionRetryMs?.maximum ?? 12e4 };
|
|
7511
7282
|
function scheduleAdmission(conversationId, delayMs, discover = false) {
|
|
@@ -7532,13 +7303,24 @@ function createEndpoint(options) {
|
|
|
7532
7303
|
const inRoster = new Set(group.roster.map((entry) => mlsAccountOf(accounts, entry.device.principal)));
|
|
7533
7304
|
const invitee = local?.mlsInviteeId;
|
|
7534
7305
|
const missing = [...local?.memberIds ?? [], ...typeof invitee === "string" ? [invitee] : []].some((memberId) => !inRoster.has(memberId)) || admissionUnfinished.has(conversationId);
|
|
7535
|
-
if (!missing) admissionDelays.delete(conversationId);
|
|
7536
|
-
const delay = admissionDelays.get(conversationId) ?? admissionRetry.initial;
|
|
7537
7306
|
const announced = local?.mlsDeviceAnnouncedAtMs;
|
|
7538
|
-
const
|
|
7539
|
-
if (
|
|
7307
|
+
const fresh = announced !== announcementsSeen.get(conversationId);
|
|
7308
|
+
if ((announcementLooks.get(conversationId)?.roster ?? Infinity) < group.roster.length) announcementLooks.delete(conversationId);
|
|
7309
|
+
const look = announcementLooks.get(conversationId), retrying = missing || !!look;
|
|
7310
|
+
if (!retrying) admissionDelays.delete(conversationId);
|
|
7311
|
+
const delay = admissionDelays.get(conversationId) ?? admissionRetry.initial;
|
|
7312
|
+
let due = fresh ? -Infinity : (admissionRuns.get(conversationId) ?? -Infinity) + (retrying ? delay : ADMISSION_IDLE_MS);
|
|
7313
|
+
if (look && !missing) due = Math.min(due, look.untilMs);
|
|
7314
|
+
if (clock() < due) return retrying ? scheduleAdmission(conversationId, due - clock()) : void 0;
|
|
7540
7315
|
admitting.add(conversationId);
|
|
7316
|
+
const watched = announcementsSeen.has(conversationId);
|
|
7541
7317
|
announcementsSeen.set(conversationId, announced);
|
|
7318
|
+
if (fresh && typeof announced === "number" && (watched || clock() < announced + MLS_DEV_KEY_PACKAGE_CLAIM_LEASE_MS)) {
|
|
7319
|
+
announcementLooks.set(conversationId, {
|
|
7320
|
+
untilMs: clock() + MLS_DEV_KEY_PACKAGE_CLAIM_LEASE_MS + admissionRetry.initial,
|
|
7321
|
+
roster: group.roster.length
|
|
7322
|
+
});
|
|
7323
|
+
}
|
|
7542
7324
|
try {
|
|
7543
7325
|
admissionRuns.set(conversationId, clock());
|
|
7544
7326
|
await mlsDevKeyPackages.pruneClaims(clock() - CLAIM_JOURNAL_KEEP_MS);
|
|
@@ -7596,13 +7378,16 @@ function createEndpoint(options) {
|
|
|
7596
7378
|
admissionRuns.delete(conversationId);
|
|
7597
7379
|
admissionDelays.delete(conversationId);
|
|
7598
7380
|
admissionUnfinished.delete(conversationId);
|
|
7381
|
+
announcementLooks.delete(conversationId);
|
|
7599
7382
|
return scheduleAdmission(conversationId, admissionRetry.initial);
|
|
7600
7383
|
}
|
|
7601
7384
|
if (unfinished) admissionUnfinished.add(conversationId);
|
|
7602
7385
|
else admissionUnfinished.delete(conversationId);
|
|
7603
|
-
if (
|
|
7386
|
+
if ((announcementLooks.get(conversationId)?.untilMs ?? -Infinity) <= clock()) announcementLooks.delete(conversationId);
|
|
7387
|
+
const looking = !missing && !unfinished ? announcementLooks.get(conversationId) : void 0;
|
|
7388
|
+
if (missing || unfinished || looking) {
|
|
7604
7389
|
admissionDelays.set(conversationId, Math.min(delay * 2, admissionRetry.maximum));
|
|
7605
|
-
scheduleAdmission(conversationId, delay);
|
|
7390
|
+
scheduleAdmission(conversationId, looking ? Math.min(delay, Math.max(0, looking.untilMs - clock())) : delay);
|
|
7606
7391
|
}
|
|
7607
7392
|
} finally {
|
|
7608
7393
|
admitting.delete(conversationId);
|
|
@@ -7931,6 +7716,51 @@ function createEndpoint(options) {
|
|
|
7931
7716
|
}
|
|
7932
7717
|
}
|
|
7933
7718
|
__name(refreshMlsRoomFromHint, "refreshMlsRoomFromHint");
|
|
7719
|
+
const roomHints = /* @__PURE__ */ new Map();
|
|
7720
|
+
let roomHintWorker;
|
|
7721
|
+
function queueRoomHint(key3, run, last = false) {
|
|
7722
|
+
if (closed) return Promise.resolve();
|
|
7723
|
+
let hint = roomHints.get(key3);
|
|
7724
|
+
if (hint && last) roomHints.delete(key3);
|
|
7725
|
+
if (!hint) {
|
|
7726
|
+
let settle;
|
|
7727
|
+
const done = new Promise((resolve3, reject) => {
|
|
7728
|
+
settle = /* @__PURE__ */ __name((error) => error === void 0 ? resolve3() : reject(error), "settle");
|
|
7729
|
+
});
|
|
7730
|
+
done.catch(() => void 0);
|
|
7731
|
+
hint = { run, done, settle };
|
|
7732
|
+
}
|
|
7733
|
+
roomHints.set(key3, hint);
|
|
7734
|
+
drainRoomHints();
|
|
7735
|
+
return hint.done;
|
|
7736
|
+
}
|
|
7737
|
+
__name(queueRoomHint, "queueRoomHint");
|
|
7738
|
+
function drainRoomHints() {
|
|
7739
|
+
if (roomHintWorker) return;
|
|
7740
|
+
roomHintWorker = (async () => {
|
|
7741
|
+
for (const [key3, hint] of roomHints) {
|
|
7742
|
+
roomHints.delete(key3);
|
|
7743
|
+
if (closed) {
|
|
7744
|
+
hint.settle();
|
|
7745
|
+
continue;
|
|
7746
|
+
}
|
|
7747
|
+
try {
|
|
7748
|
+
await hint.run();
|
|
7749
|
+
hint.settle();
|
|
7750
|
+
} catch (error) {
|
|
7751
|
+
hint.settle(error);
|
|
7752
|
+
}
|
|
7753
|
+
}
|
|
7754
|
+
})().finally(() => {
|
|
7755
|
+
roomHintWorker = void 0;
|
|
7756
|
+
if (roomHints.size) drainRoomHints();
|
|
7757
|
+
});
|
|
7758
|
+
}
|
|
7759
|
+
__name(drainRoomHints, "drainRoomHints");
|
|
7760
|
+
const hintRoom = /* @__PURE__ */ __name((conversationId) => queueRoomHint(`room/${conversationId}`, async () => {
|
|
7761
|
+
await refreshMlsRoomFromHint(conversationId);
|
|
7762
|
+
await sendDeferredOpeners(conversationId);
|
|
7763
|
+
}), "hintRoom");
|
|
7934
7764
|
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") });
|
|
7935
7765
|
const invalidateEvent = /* @__PURE__ */ __name((event) => queryObserver.invalidate(event ? (query, parameters) => queryAffectedByEvent(event, query, parameters) : void 0), "invalidateEvent");
|
|
7936
7766
|
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) => {
|
|
@@ -7939,19 +7769,20 @@ function createEndpoint(options) {
|
|
|
7939
7769
|
return mlsDevGroups.syncText(id2, opts);
|
|
7940
7770
|
}, "syncProtocol") });
|
|
7941
7771
|
const events = createEndpointEventHub({ transport, invalidate: invalidateEvent, onDisconnected: /* @__PURE__ */ __name(() => queryObserver.reschedule(), "onDisconnected"), onEvent: /* @__PURE__ */ __name(async (event) => {
|
|
7772
|
+
const hinted = [];
|
|
7942
7773
|
if (mlsDevGroups && ["conversation.changed", "conversation.updated", "conversation.removed"].includes(event.kind)) {
|
|
7943
7774
|
const id2 = event.data.conversationId ?? event.data.id;
|
|
7944
7775
|
if (typeof id2 === "string" && ENDPOINT_ID_PATTERN.test(id2)) {
|
|
7945
7776
|
if (event.kind === "conversation.changed" && event.data.projection === void 0) forgetStubReads(id2);
|
|
7946
7777
|
const unseenRemoval = event.kind === "conversation.removed" && await store.transaction(async (tx) => await tx.get(`conversation/${id2}`) === void 0 && !await isMlsDevRoomProtected(tx, id2));
|
|
7947
|
-
if (!unseenRemoval)
|
|
7948
|
-
await sendDeferredOpeners(id2);
|
|
7778
|
+
if (!unseenRemoval) hinted.push(hintRoom(id2));
|
|
7779
|
+
else await sendDeferredOpeners(id2);
|
|
7949
7780
|
}
|
|
7950
7781
|
}
|
|
7951
7782
|
if (mlsDevGroups && event.kind === "conversations.changed" && Array.isArray(event.data.conversationIds)) {
|
|
7952
7783
|
for (const id2 of event.data.conversationIds) {
|
|
7953
7784
|
if (typeof id2 === "string" && ENDPOINT_ID_PATTERN.test(id2) && await store.transaction((tx) => isMlsDevRoomProtected(tx, id2))) {
|
|
7954
|
-
|
|
7785
|
+
hinted.push(hintRoom(id2));
|
|
7955
7786
|
}
|
|
7956
7787
|
}
|
|
7957
7788
|
}
|
|
@@ -7963,13 +7794,16 @@ function createEndpoint(options) {
|
|
|
7963
7794
|
if (event.kind === "connected") serverRoomMode = void 0;
|
|
7964
7795
|
for (let after; ; ) {
|
|
7965
7796
|
const page = await store.transaction((tx) => tx.scan("mls-dev-group/mode/", { after, limit: 100 }));
|
|
7966
|
-
for (const item of page)
|
|
7797
|
+
for (const item of page) hinted.push(hintRoom(decodeURIComponent(item.key.slice("mls-dev-group/mode/".length))));
|
|
7967
7798
|
if (page.length < 100) break;
|
|
7968
7799
|
after = page[page.length - 1].key;
|
|
7969
7800
|
}
|
|
7970
|
-
|
|
7971
|
-
|
|
7801
|
+
hinted.push(queueRoomHint("after/reconnect", async () => {
|
|
7802
|
+
await sendDeferredOpeners();
|
|
7803
|
+
await discoverInviteeRooms().catch(() => void 0);
|
|
7804
|
+
}, true));
|
|
7972
7805
|
}
|
|
7806
|
+
if (event.id) await Promise.all(hinted);
|
|
7973
7807
|
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)) {
|
|
7974
7808
|
const room = event.data.conversationId, message = event.data.messageId;
|
|
7975
7809
|
const applied = await store.transaction(async (tx) => {
|
|
@@ -8050,6 +7884,24 @@ function createEndpoint(options) {
|
|
|
8050
7884
|
else await enqueueInbound(tx, next);
|
|
8051
7885
|
}
|
|
8052
7886
|
__name(writeInboundState, "writeInboundState");
|
|
7887
|
+
async function discardOperation(tx, operation, message) {
|
|
7888
|
+
const status = operation.attempted ? "uncertain" : "rejected";
|
|
7889
|
+
const next = { ...operation, status, updatedAt: now(), receipt: {
|
|
7890
|
+
...deferredReceipt(operation),
|
|
7891
|
+
status,
|
|
7892
|
+
updatedAt: now(),
|
|
7893
|
+
error: { code: "OPERATION_DISCARDED", message, retryable: false }
|
|
7894
|
+
} };
|
|
7895
|
+
delete next.request;
|
|
7896
|
+
delete next.preparedContent;
|
|
7897
|
+
delete next.lease;
|
|
7898
|
+
delete next.nextAttemptAt;
|
|
7899
|
+
await tx.put(key2(operation.operationId), next);
|
|
7900
|
+
await settleMlsDevTextOperation(tx, next);
|
|
7901
|
+
await removeOperationOutbox(tx, operation);
|
|
7902
|
+
if (!operation.attempted) await settleOperationOrder(tx, operation);
|
|
7903
|
+
}
|
|
7904
|
+
__name(discardOperation, "discardOperation");
|
|
8053
7905
|
function deferredReceipt(operation, error) {
|
|
8054
7906
|
return operation.receipt ?? {
|
|
8055
7907
|
protocol: CANON_ENDPOINT_PROTOCOL,
|
|
@@ -8171,12 +8023,18 @@ function createEndpoint(options) {
|
|
|
8171
8023
|
if (mlsDevGroups && typeof made === "string" && ENDPOINT_ID_PATTERN.test(made)) void refreshMlsRoom(made, true).catch(() => void 0);
|
|
8172
8024
|
}
|
|
8173
8025
|
__name(afterReceipt, "afterReceipt");
|
|
8174
|
-
async function persistReceipt(operation, receipt) {
|
|
8175
|
-
if (await mlsDevGroups?.settleReceipt(operation, receipt)) return;
|
|
8026
|
+
async function persistReceipt(operation, receipt, reconcileOnly = false) {
|
|
8027
|
+
if (await mlsDevGroups?.settleReceipt(operation, receipt, { reconcileOnly })) return;
|
|
8176
8028
|
const updated = await store.transaction((tx) => commitReceipt(tx, operation, receipt));
|
|
8177
8029
|
await afterReceipt(updated);
|
|
8178
8030
|
}
|
|
8179
8031
|
__name(persistReceipt, "persistReceipt");
|
|
8032
|
+
async function answerOf(operationId, receipt, reconcileOnly = false) {
|
|
8033
|
+
if (receipt.action !== "mls_application" || receipt.status !== "rejected") return receipt;
|
|
8034
|
+
const resentAs = (await store.transaction((tx) => tx.get(key2(operationId))))?.resentAs;
|
|
8035
|
+
return resentAs ? endpoint.retry(resentAs, { reconcileOnly }) : receipt;
|
|
8036
|
+
}
|
|
8037
|
+
__name(answerOf, "answerOf");
|
|
8180
8038
|
async function dispatch(operationId, reconcileOnly = false) {
|
|
8181
8039
|
assertOpen();
|
|
8182
8040
|
await initialization;
|
|
@@ -8216,12 +8074,17 @@ function createEndpoint(options) {
|
|
|
8216
8074
|
await tx.put(key2(operationId), next);
|
|
8217
8075
|
return { operation: next, acquired: true };
|
|
8218
8076
|
});
|
|
8077
|
+
if (operation.resentAs) return endpoint.retry(operation.resentAs, { reconcileOnly });
|
|
8219
8078
|
if (terminal(operation.status) && operation.receipt) {
|
|
8220
8079
|
if ((operation.receipt.resultReference || operation.resultEvicted) && operation.status === "succeeded") {
|
|
8221
8080
|
const hydrated = await transport.getOperation(operationId, operation.installationId, controller.signal);
|
|
8222
8081
|
if (!hydrated) throw new EndpointError("The private operation result is no longer available", { status: 410, code: "RESULT_EXPIRED", retryable: false, operationId });
|
|
8223
8082
|
return hydrated;
|
|
8224
8083
|
}
|
|
8084
|
+
if (!reconcileOnly && operation.action === "mls_application" && operation.status === "rejected") {
|
|
8085
|
+
await mlsDevGroups?.settleReceipt(operation, operation.receipt);
|
|
8086
|
+
return answerOf(operationId, operation.receipt);
|
|
8087
|
+
}
|
|
8225
8088
|
return operation.receipt;
|
|
8226
8089
|
}
|
|
8227
8090
|
if (!acquired) {
|
|
@@ -8263,8 +8126,8 @@ function createEndpoint(options) {
|
|
|
8263
8126
|
});
|
|
8264
8127
|
return receipt2;
|
|
8265
8128
|
}
|
|
8266
|
-
await persistReceipt(operation, receipt2);
|
|
8267
|
-
return receipt2;
|
|
8129
|
+
await persistReceipt(operation, receipt2, reconcileOnly);
|
|
8130
|
+
return answerOf(operationId, receipt2, reconcileOnly);
|
|
8268
8131
|
}
|
|
8269
8132
|
if (operation.attempted) {
|
|
8270
8133
|
resolvingReceipt = true;
|
|
@@ -8272,7 +8135,7 @@ function createEndpoint(options) {
|
|
|
8272
8135
|
resolvingReceipt = false;
|
|
8273
8136
|
if (receipt2 && (receipt2.status !== "pending" || !operation.request && !sensitiveRequests.has(operationId))) {
|
|
8274
8137
|
await persistReceipt(operation, receipt2);
|
|
8275
|
-
return receipt2;
|
|
8138
|
+
return answerOf(operationId, receipt2, reconcileOnly);
|
|
8276
8139
|
}
|
|
8277
8140
|
}
|
|
8278
8141
|
if (clock() - Date.parse(operation.createdAt) > retryHorizonMs) {
|
|
@@ -8297,7 +8160,7 @@ function createEndpoint(options) {
|
|
|
8297
8160
|
checkingDeliveryGuard = true;
|
|
8298
8161
|
const guard = deliveryGuards.get(operation.guardId);
|
|
8299
8162
|
if (!guard) throw new EndpointError("This publication requires its runtime to recheck the current audience", { code: "DELIVERY_GUARD_REQUIRED", retryable: true });
|
|
8300
|
-
if (!await guard()) throw new EndpointError("Publication is withheld by the runtime policy", { code: "DELIVERY_GUARD_WITHHELD", retryable: true });
|
|
8163
|
+
if (!await guard({ automaticResend: operation.automaticResend === true })) throw new EndpointError("Publication is withheld by the runtime policy", { code: "DELIVERY_GUARD_WITHHELD", retryable: true });
|
|
8301
8164
|
assertOpen();
|
|
8302
8165
|
checkingDeliveryGuard = false;
|
|
8303
8166
|
}
|
|
@@ -8332,7 +8195,7 @@ function createEndpoint(options) {
|
|
|
8332
8195
|
});
|
|
8333
8196
|
const receipt = await transport.execute(request, controller.signal);
|
|
8334
8197
|
await persistReceipt(operation, receipt);
|
|
8335
|
-
return receipt;
|
|
8198
|
+
return answerOf(operationId, receipt, reconcileOnly);
|
|
8336
8199
|
} catch (error) {
|
|
8337
8200
|
const failure = safeError(error);
|
|
8338
8201
|
if (reconcileOnly && failure.retryable) {
|
|
@@ -8540,7 +8403,6 @@ function createEndpoint(options) {
|
|
|
8540
8403
|
...endpointIntentConversationId(normalized) ? { conversationId: endpointIntentConversationId(normalized) } : {},
|
|
8541
8404
|
...typeof routedBody.messageId === "string" ? { messageId: routedBody.messageId } : {},
|
|
8542
8405
|
...normalized.action === "session_control" && typeof routedBody.agentId === "string" ? { agentId: routedBody.agentId } : {},
|
|
8543
|
-
...endpointIntentOrdering(normalized) === "artifact" && typeof routedBody.artifactId === "string" ? { artifactId: routedBody.artifactId } : {},
|
|
8544
8406
|
sensitive,
|
|
8545
8407
|
status: "queued",
|
|
8546
8408
|
attempts: 0,
|
|
@@ -8598,15 +8460,26 @@ function createEndpoint(options) {
|
|
|
8598
8460
|
running.set(operationId, work);
|
|
8599
8461
|
return work;
|
|
8600
8462
|
},
|
|
8601
|
-
async
|
|
8463
|
+
async resend(operationId) {
|
|
8602
8464
|
assertOpen();
|
|
8603
8465
|
await initialization;
|
|
8466
|
+
let operation = await endpoint.getOperation(operationId);
|
|
8467
|
+
while (operation?.resentAs) operation = await endpoint.getOperation(operation.resentAs);
|
|
8468
|
+
if (!operation) throw new EndpointError("Unknown endpoint operation", { status: 404, code: "OPERATION_NOT_FOUND", retryable: false, operationId });
|
|
8469
|
+
if (operation.status !== "rejected") return operation;
|
|
8470
|
+
const resent = operation.action === "mls_application" && mlsDevGroups ? await mlsDevGroups.resendText(operation) : void 0;
|
|
8471
|
+
if (!resent) throw new EndpointError("This message can no longer be sent again", { code: "OPERATION_NOT_RESENDABLE", retryable: false, operationId });
|
|
8472
|
+
return resent;
|
|
8473
|
+
},
|
|
8474
|
+
async drain(drainOptions = {}) {
|
|
8475
|
+
assertOpen();
|
|
8476
|
+
await opened;
|
|
8604
8477
|
await compactExpiredPayloads(store, clock());
|
|
8605
8478
|
await sendDeferredOpeners(void 0, true).catch(() => void 0);
|
|
8606
8479
|
let dispatched = 0;
|
|
8607
8480
|
let hasDeferred = false;
|
|
8608
8481
|
const blockedKeys = /* @__PURE__ */ new Set();
|
|
8609
|
-
for (const ordering of ["independent", "signal", "message", "provider", "
|
|
8482
|
+
for (const ordering of ["independent", "signal", "message", "provider", "conversation"]) {
|
|
8610
8483
|
let after;
|
|
8611
8484
|
do {
|
|
8612
8485
|
const batch = await store.transaction(async (tx) => {
|
|
@@ -8649,7 +8522,8 @@ function createEndpoint(options) {
|
|
|
8649
8522
|
await initialization;
|
|
8650
8523
|
return store.transaction((tx) => tx.get(key2(operationId)));
|
|
8651
8524
|
},
|
|
8652
|
-
waitForOperation(
|
|
8525
|
+
waitForOperation(waited, waitOptions = {}) {
|
|
8526
|
+
let operationId = waited;
|
|
8653
8527
|
return new Promise((resolve3, reject) => {
|
|
8654
8528
|
let finished = false;
|
|
8655
8529
|
let checking = false;
|
|
@@ -8675,7 +8549,11 @@ function createEndpoint(options) {
|
|
|
8675
8549
|
if (finished || checking) return;
|
|
8676
8550
|
checking = true;
|
|
8677
8551
|
try {
|
|
8678
|
-
|
|
8552
|
+
let operation = await endpoint.getOperation(operationId);
|
|
8553
|
+
while (operation?.resentAs) {
|
|
8554
|
+
operationId = operation.resentAs;
|
|
8555
|
+
operation = await endpoint.getOperation(operationId);
|
|
8556
|
+
}
|
|
8679
8557
|
if (!operation) return finish(void 0, new EndpointError("Unknown endpoint operation", { status: 404, code: "OPERATION_NOT_FOUND", retryable: false, operationId }));
|
|
8680
8558
|
if (operation.status === "uncertain") return finish(void 0, new EndpointError("Delivery outcome requires reconciliation", { code: "OPERATION_UNCERTAIN", retryable: false, operationId }));
|
|
8681
8559
|
if (terminal(operation.status) && operation.receipt) {
|
|
@@ -8782,9 +8660,9 @@ function createEndpoint(options) {
|
|
|
8782
8660
|
},
|
|
8783
8661
|
refreshQueries: /* @__PURE__ */ __name(() => queryObserver.invalidate(), "refreshQueries"),
|
|
8784
8662
|
observeEvents: events.observe,
|
|
8785
|
-
createMediaPipeline(bytes
|
|
8663
|
+
createMediaPipeline(bytes) {
|
|
8786
8664
|
assertOpen();
|
|
8787
|
-
const pipeline3 = createEndpointMediaPipeline({ endpoint, store, bytes, hash, ids, clock
|
|
8665
|
+
const pipeline3 = createEndpointMediaPipeline({ endpoint, store, bytes, hash, ids, clock });
|
|
8788
8666
|
mediaPipelines2.add(pipeline3);
|
|
8789
8667
|
return pipeline3;
|
|
8790
8668
|
},
|
|
@@ -8938,6 +8816,57 @@ function createEndpoint(options) {
|
|
|
8938
8816
|
startRecovery: recovery.start,
|
|
8939
8817
|
configureRecoveryBaseline: recovery.configureBaseline,
|
|
8940
8818
|
getRecoveryBaseline: recovery.getBaseline,
|
|
8819
|
+
async startWorkAt(cutoverAt) {
|
|
8820
|
+
assertOpen();
|
|
8821
|
+
await initialization;
|
|
8822
|
+
if (typeof cutoverAt !== "string" || !Number.isFinite(Date.parse(cutoverAt))) {
|
|
8823
|
+
throw new EndpointError("Invalid work floor", { code: "INVALID_RECOVERY_BASELINE", retryable: false });
|
|
8824
|
+
}
|
|
8825
|
+
const sweep = await store.transaction(async (tx) => {
|
|
8826
|
+
const floor = await tx.get(WORK_FLOOR_KEY);
|
|
8827
|
+
const at = floor ? Date.parse(floor.cutoverAt) : -Infinity, next = Date.parse(cutoverAt);
|
|
8828
|
+
if (at > next || at === next && floor?.swept) return false;
|
|
8829
|
+
if (at < next) await tx.put(WORK_FLOOR_KEY, { cutoverAt });
|
|
8830
|
+
return true;
|
|
8831
|
+
});
|
|
8832
|
+
if (!sweep) return;
|
|
8833
|
+
for (let after; ; ) {
|
|
8834
|
+
const page = await store.transaction(async (tx) => {
|
|
8835
|
+
const floor = await tx.get(WORK_FLOOR_KEY);
|
|
8836
|
+
const answered = await completions.answered(tx);
|
|
8837
|
+
const entries2 = await tx.scan("inbound/", { ...after ? { after } : {}, limit: 100 });
|
|
8838
|
+
for (const { key: recordKey, value } of entries2) {
|
|
8839
|
+
if (value.state === "settled" || value.externalClaim || answered.has(value.event.id) || !beforeWorkFloor(floor, value.createdAt)) continue;
|
|
8840
|
+
const next = { ...value, state: "settled", reason: "before_take_over", updatedAt: now() };
|
|
8841
|
+
delete next.lease;
|
|
8842
|
+
delete next.handoffGeneration;
|
|
8843
|
+
delete next.handoffInstallationGeneration;
|
|
8844
|
+
await removeInboundReady(tx, value);
|
|
8845
|
+
await tx.put(recordKey, next);
|
|
8846
|
+
}
|
|
8847
|
+
return entries2;
|
|
8848
|
+
});
|
|
8849
|
+
if (page.length < 100) break;
|
|
8850
|
+
after = page.at(-1).key;
|
|
8851
|
+
}
|
|
8852
|
+
await store.transaction(async (tx) => {
|
|
8853
|
+
const floor = await tx.get(WORK_FLOOR_KEY);
|
|
8854
|
+
if (floor && Date.parse(floor.cutoverAt) === Date.parse(cutoverAt)) await tx.put(WORK_FLOOR_KEY, { cutoverAt: floor.cutoverAt, swept: true });
|
|
8855
|
+
});
|
|
8856
|
+
},
|
|
8857
|
+
async takeOverHome() {
|
|
8858
|
+
assertOpen();
|
|
8859
|
+
await opened;
|
|
8860
|
+
const operationId = `${TAKE_OVER_OPERATION}${ids()}`;
|
|
8861
|
+
const { cutoverAt } = await endpoint.executeResult({ action: "initialize_endpoint", body: {} }, { operationId }).catch(async (error) => {
|
|
8862
|
+
await store.transaction(async (tx) => {
|
|
8863
|
+
const current = await tx.get(key2(operationId));
|
|
8864
|
+
if (current && !terminal(current.status)) await discardOperation(tx, current, TAKE_OVER_DISCARDED);
|
|
8865
|
+
});
|
|
8866
|
+
throw error;
|
|
8867
|
+
});
|
|
8868
|
+
await endpoint.startWorkAt(cutoverAt);
|
|
8869
|
+
},
|
|
8941
8870
|
refreshRecovery: recovery.refresh,
|
|
8942
8871
|
planRecoveryMigration(baseline, conversationIds) {
|
|
8943
8872
|
assertOpen();
|
|
@@ -9046,23 +8975,11 @@ function createEndpoint(options) {
|
|
|
9046
8975
|
const batch = await store.transaction(async (tx) => {
|
|
9047
8976
|
const entries2 = await tx.scan("operation/", { after, limit: 100 });
|
|
9048
8977
|
for (const entry of entries2) {
|
|
9049
|
-
if (!terminal(entry.value.status))
|
|
9050
|
-
|
|
9051
|
-
|
|
9052
|
-
|
|
9053
|
-
status,
|
|
9054
|
-
updatedAt: now(),
|
|
9055
|
-
error: { code: "OPERATION_DISCARDED", message: "Local delivery was cancelled", retryable: false }
|
|
9056
|
-
} };
|
|
9057
|
-
delete next.request;
|
|
9058
|
-
delete next.preparedContent;
|
|
9059
|
-
delete next.lease;
|
|
9060
|
-
delete next.nextAttemptAt;
|
|
9061
|
-
await tx.put(entry.key, next);
|
|
9062
|
-
await settleMlsDevTextOperation(tx, next);
|
|
8978
|
+
if (!terminal(entry.value.status)) await discardOperation(tx, entry.value, "Local delivery was cancelled");
|
|
8979
|
+
else {
|
|
8980
|
+
await removeOperationOutbox(tx, entry.value);
|
|
8981
|
+
await settleOperationOrder(tx, entry.value);
|
|
9063
8982
|
}
|
|
9064
|
-
await removeOperationOutbox(tx, entry.value);
|
|
9065
|
-
if (!entry.value.attempted || terminal(entry.value.status)) await settleOperationOrder(tx, entry.value);
|
|
9066
8983
|
}
|
|
9067
8984
|
return entries2;
|
|
9068
8985
|
});
|
|
@@ -9088,7 +9005,7 @@ function createEndpoint(options) {
|
|
|
9088
9005
|
listeners.clear();
|
|
9089
9006
|
closing = (async () => {
|
|
9090
9007
|
await Promise.allSettled([handoffs.close(), recovery.close(), syncConversation.close(), events.close(), ...[...consumers].map((consumer) => consumer.close()), ...[...mediaPipelines2].map((pipeline3) => pipeline3.close({ discard: true }))]);
|
|
9091
|
-
await Promise.allSettled([...running.values(), ...reads.values(), ...deviceRuns, initialization]);
|
|
9008
|
+
await Promise.allSettled([...running.values(), ...reads.values(), ...deviceRuns, initialization, roomHintWorker]);
|
|
9092
9009
|
await endpoint.discardPending();
|
|
9093
9010
|
await eraseEndpointPlaintext(store);
|
|
9094
9011
|
await store.close();
|
|
@@ -9111,11 +9028,10 @@ function createEndpoint(options) {
|
|
|
9111
9028
|
sensitiveRequests.clear();
|
|
9112
9029
|
sensitiveFingerprints.clear();
|
|
9113
9030
|
listeners.clear();
|
|
9114
|
-
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());
|
|
9031
|
+
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());
|
|
9115
9032
|
return closing;
|
|
9116
9033
|
}
|
|
9117
9034
|
};
|
|
9118
|
-
registerArtifactEndpoint(endpoint, { store, closed: /* @__PURE__ */ __name(() => closed, "closed"), assertCurrent: assertCurrentInstallation });
|
|
9119
9035
|
void initialization.then(() => scheduleRetry(0)).catch(() => void 0);
|
|
9120
9036
|
void initialization.then(() => sendDeferredOpeners()).catch(() => void 0);
|
|
9121
9037
|
if (mlsDevGroups) void initialization.then(() => resumeHeldTexts()).catch(() => void 0);
|
|
@@ -9173,7 +9089,7 @@ async function acquireAgentEndpoint(input) {
|
|
|
9173
9089
|
}
|
|
9174
9090
|
const requestedMls = input.options.mlsDev;
|
|
9175
9091
|
const mls = requestedMls ? await (async () => {
|
|
9176
|
-
const crypto2 = await import("./mls-crypto-
|
|
9092
|
+
const crypto2 = await import("./mls-crypto-AW7TY744.js");
|
|
9177
9093
|
const configuration = crypto2.parseMlsDevClientConfiguration(requestedMls.configuration);
|
|
9178
9094
|
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");
|
|
9179
9095
|
const custody = requestedMls.custody;
|
|
@@ -9358,6 +9274,17 @@ function isDiscoverAgentsResult(value) {
|
|
|
9358
9274
|
return isRecord(value) && Array.isArray(value.agents) && value.agents.every(isAgentDirectoryEntry) && (value.nextCursor === null || typeof value.nextCursor === "string");
|
|
9359
9275
|
}
|
|
9360
9276
|
__name(isDiscoverAgentsResult, "isDiscoverAgentsResult");
|
|
9277
|
+
function homeClaim(input) {
|
|
9278
|
+
const first = input.baseline ? void 0 : "initialize-endpoint";
|
|
9279
|
+
if (isConnectorRelayInstallation(input.installationId)) return first;
|
|
9280
|
+
if (input.home?.kind === "connector") return input.runner ? "refuse" : void 0;
|
|
9281
|
+
const holder = input.home?.installationId;
|
|
9282
|
+
if (holder === void 0 || holder === input.installationId) return first;
|
|
9283
|
+
if (!input.runner) return void 0;
|
|
9284
|
+
if (!input.baseline) return first;
|
|
9285
|
+
return input.takeOver ? "take-over" : "refuse";
|
|
9286
|
+
}
|
|
9287
|
+
__name(homeClaim, "homeClaim");
|
|
9361
9288
|
var CanonClient = class {
|
|
9362
9289
|
static {
|
|
9363
9290
|
__name(this, "CanonClient");
|
|
@@ -9389,12 +9316,29 @@ var CanonClient = class {
|
|
|
9389
9316
|
options: this.endpointOptions
|
|
9390
9317
|
});
|
|
9391
9318
|
try {
|
|
9392
|
-
if (context.endpointBootstrapEligible && this.endpointOptions.autoDrain !== false
|
|
9393
|
-
const baseline = await lease.endpoint.
|
|
9394
|
-
|
|
9395
|
-
|
|
9396
|
-
|
|
9397
|
-
|
|
9319
|
+
if (context.endpointBootstrapEligible && this.endpointOptions.autoDrain !== false) {
|
|
9320
|
+
const baseline = await lease.endpoint.getRecoveryBaseline();
|
|
9321
|
+
const { installationId } = await lease.endpoint.ready();
|
|
9322
|
+
const home = context.home;
|
|
9323
|
+
const claim = homeClaim({
|
|
9324
|
+
installationId,
|
|
9325
|
+
home,
|
|
9326
|
+
baseline: !!baseline,
|
|
9327
|
+
runner: !!this.endpointOptions.claimHome,
|
|
9328
|
+
takeOver: !!this.endpointOptions.takeOver
|
|
9329
|
+
});
|
|
9330
|
+
if (claim === "refuse") {
|
|
9331
|
+
throw new EndpointError(agentHomeElsewhereMessage(context.displayName, home), { status: 409, code: AGENT_HOME_ELSEWHERE, retryable: false });
|
|
9332
|
+
}
|
|
9333
|
+
if (claim === "take-over") await lease.endpoint.takeOverHome();
|
|
9334
|
+
else if (claim) {
|
|
9335
|
+
await lease.endpoint.configureRecoveryBaseline(await lease.endpoint.executeResult(
|
|
9336
|
+
{ action: "initialize_endpoint", body: {} },
|
|
9337
|
+
{ operationId: claim }
|
|
9338
|
+
));
|
|
9339
|
+
} else if (home?.kind === "runner" && home.installationId === installationId && home.since) {
|
|
9340
|
+
await lease.endpoint.startWorkAt(new Date(home.since).toISOString());
|
|
9341
|
+
}
|
|
9398
9342
|
}
|
|
9399
9343
|
return lease;
|
|
9400
9344
|
} catch (error) {
|
|
@@ -10559,8 +10503,16 @@ async function ensureAgentProfile(options) {
|
|
|
10559
10503
|
await verifyCanonRuntimeConnection(connection);
|
|
10560
10504
|
return { status: "approved", apiKey: existing.apiKey, agentId: existing.agentId, agentName: existing.agentName };
|
|
10561
10505
|
}
|
|
10506
|
+
const requestedAgentId = input.requestedAgentId ?? (options.reconnect && !pending ? existing?.agentId : void 0);
|
|
10507
|
+
if (options.reconnect && !pending && !requestedAgentId) {
|
|
10508
|
+
throw new Error(`Canon profile "${profile}" has no saved agent to reconnect: give input.requestedAgentId, or register without reconnect`);
|
|
10509
|
+
}
|
|
10510
|
+
if (options.reconnect && !pending && existing && requestedAgentId !== existing.agentId) {
|
|
10511
|
+
throw new Error("Saved Canon profile belongs to another agent identity");
|
|
10512
|
+
}
|
|
10562
10513
|
return resumeRegistration({
|
|
10563
10514
|
...options,
|
|
10515
|
+
input: { ...input, ...requestedAgentId ? { requestedAgentId } : {} },
|
|
10564
10516
|
store: {
|
|
10565
10517
|
load: /* @__PURE__ */ __name(() => loadPendingRegistrations()[profile], "load"),
|
|
10566
10518
|
save: /* @__PURE__ */ __name((session) => {
|
|
@@ -11813,14 +11765,15 @@ export {
|
|
|
11813
11765
|
loadProfiles,
|
|
11814
11766
|
isProfileLocked,
|
|
11815
11767
|
acquireLock,
|
|
11768
|
+
loadPendingRegistrations,
|
|
11816
11769
|
clearPendingRegistration,
|
|
11817
11770
|
ensureAgentProfile,
|
|
11818
11771
|
initRTDBAuth,
|
|
11819
11772
|
renderCanonHostInboundContent,
|
|
11820
11773
|
createRuntimeStatePublisher,
|
|
11821
11774
|
isPendingCanonOperation,
|
|
11775
|
+
utf8ByteLength,
|
|
11822
11776
|
splitTextWithinBytes,
|
|
11823
|
-
readCliPackageVersion,
|
|
11824
11777
|
runCli,
|
|
11825
11778
|
BoundedFileReadError,
|
|
11826
11779
|
readBoundedRegularFileHandle,
|