@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.
Files changed (33) hide show
  1. package/dist/chunks/{chunk-5YKGDUJX.js → chunk-AVR7QCHK.js} +369 -416
  2. package/dist/chunks/{chunk-DTAWEQCM.js → chunk-BQGXOAIW.js} +2 -2
  3. package/dist/chunks/{chunk-5WPAB5MB.js → chunk-CUPSYLBV.js} +2041 -2049
  4. package/dist/chunks/{chunk-YJUPKU47.js → chunk-HFT75BNB.js} +2 -2
  5. package/dist/chunks/{chunk-YHDDOADK.js → chunk-NIORQKUI.js} +1 -1
  6. package/dist/chunks/{chunk-6FD6KFQN.js → chunk-ZT3TJZAV.js} +400 -270
  7. package/dist/chunks/{mls-crypto-NZVMSUFU.js → mls-crypto-AW7TY744.js} +2 -2
  8. package/dist/cli.js +141 -26
  9. package/dist/index.js +6 -6
  10. package/dist/internal/agent-tools/communication-tool.d.ts +80 -0
  11. package/dist/internal/agent-tools/index.d.ts +1 -0
  12. package/dist/internal/backend-contracts/endpoint.d.ts +25 -0
  13. package/dist/internal/backend-contracts/mlsDevKeyPackages.d.ts +4 -0
  14. package/dist/internal/core/client.d.ts +0 -4
  15. package/dist/internal/core/endpoint-client.d.ts +8 -0
  16. package/dist/internal/core/index.d.ts +1 -1
  17. package/dist/internal/core/registration-profile.d.ts +4 -1
  18. package/dist/internal/core/types.d.ts +3 -1
  19. package/dist/internal/endpoint/engine.d.ts +34 -9
  20. package/dist/internal/endpoint/events.d.ts +1 -2
  21. package/dist/internal/endpoint/index.d.ts +0 -5
  22. package/dist/internal/endpoint/media.d.ts +4 -63
  23. package/dist/internal/endpoint/mls-dev-groups.d.ts +9 -1
  24. package/dist/internal/endpoint/queue.d.ts +1 -3
  25. package/dist/media.js +4 -4
  26. package/dist/mls-wasm-host.js +3 -3
  27. package/dist/runner/mls.d.ts +12 -2
  28. package/dist/runner/runner.d.ts +5 -2
  29. package/package.json +2 -3
  30. package/dist/internal/endpoint/artifact-images.d.ts +0 -60
  31. package/dist/internal/endpoint/artifact-reducer.d.ts +0 -35
  32. package/dist/internal/endpoint/artifact-views.d.ts +0 -113
  33. 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-YJUPKU47.js";
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-5WPAB5MB.js";
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) && !ARTIFACT_QUERIES.has(query) && !INBOX_QUERIES.has(query) && !CONTACT_QUERIES.has(query)) return true;
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", "append_artifact_command", "retain_artifact_asset"].includes(String(action))) return intent2;
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 = ["leave_conversation", "append_artifact_command", "retain_artifact_asset"].includes(String(action));
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, background } = options;
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 canSendInBackground(options2) {
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 === "cancelled") throw new EndpointError("Attachment was cancelled", { code: "MEDIA_FLOW_CANCELLED", retryable: false });
2309
- const snapshots = await background.inspect();
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
- if (sendOptions?.signal?.aborted) onAbort();
2412
- return await resumeBackground(await read(id2), sendOptions?.onProgress);
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
- sendOptions?.signal?.removeEventListener("abort", onAbort);
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(stageBackground, "stageBackground");
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, deliveryMode: "legacy", status: "pending" }, owner);
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 id2 = flowId(input.conversationId, input.messageId);
2498
- const prior = await read(id2);
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 resumeBackground(persisted, resumeOptions?.onProgress);
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 backgroundFlow = await read(id2);
2797
- if (backgroundFlow?.deliveryMode === "background") {
2798
- if (!background || backgroundFlow.status === "succeeded" || backgroundFlow.status === "cancelled") return;
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
- const joining = !!room && !!room.mls && (!group || group.removed);
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), ...missed });
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
- if (await tx.get(appMessageKey(cid, messageId)) !== void 0 || await tx.get(mlsDevTextMessageKey(cid, messageId)) !== void 0) fail6("MLS_DEV_APPLICATION_ID_CONFLICT");
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 (input.replyTo) {
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 createdAtMs = context.clock(), timestamp = new Date(createdAtMs).toISOString();
6183
- await tx.put(appIntentKey(operationId), { version: 1, authoredHash, envelope: prepared.wire, envelopeHash: prepared.envelopeHash });
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 false;
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 due = announced !== announcementsSeen.get(conversationId) ? -Infinity : (admissionRuns.get(conversationId) ?? -Infinity) + (missing ? delay : ADMISSION_IDLE_MS);
7539
- if (clock() < due) return missing ? scheduleAdmission(conversationId, due - clock()) : void 0;
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 (missing || unfinished) {
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) await refreshMlsRoomFromHint(id2);
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
- await refreshMlsRoomFromHint(id2);
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) await refreshMlsRoomFromHint(decodeURIComponent(item.key.slice("mls-dev-group/mode/".length)));
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
- await sendDeferredOpeners();
7971
- await discoverInviteeRooms().catch(() => void 0);
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 drain(drainOptions = {}) {
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", "artifact", "conversation"]) {
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(operationId, waitOptions = {}) {
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
- const operation = await endpoint.getOperation(operationId);
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, mediaOptions) {
8663
+ createMediaPipeline(bytes) {
8786
8664
  assertOpen();
8787
- const pipeline3 = createEndpointMediaPipeline({ endpoint, store, bytes, hash, ids, clock, background: mediaOptions?.background });
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
- const status = entry.value.attempted ? "uncertain" : "rejected";
9051
- const next = { ...entry.value, status, updatedAt: now(), receipt: {
9052
- ...deferredReceipt(entry.value),
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-NZVMSUFU.js");
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 && !await lease.endpoint.getRecoveryBaseline()) {
9393
- const baseline = await lease.endpoint.executeResult(
9394
- { action: "initialize_endpoint", body: {} },
9395
- { operationId: "initialize-endpoint" }
9396
- );
9397
- await lease.endpoint.configureRecoveryBaseline(baseline);
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,