@canonmsg/agent-sdk 13.2.0 → 13.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -5,7 +5,7 @@ import {
5
5
  createHttpEndpointTransport,
6
6
  createNodeEndpointStore,
7
7
  validateEndpointContentBoundary
8
- } from "./chunk-YJUPKU47.js";
8
+ } from "./chunk-BPSFHEAA.js";
9
9
  import {
10
10
  CANON_ENDPOINT_PROTOCOL,
11
11
  CANON_VERB_WIRE_SCHEMA_VERSION,
@@ -40,8 +40,6 @@ import {
40
40
  isMlsDevTextProtocol,
41
41
  isMlsEnvironmentId,
42
42
  isMlsPrincipalAlias,
43
- logicalMediaBlobId,
44
- mergeDeliveryObservation,
45
43
  mlsAdmissionWait,
46
44
  mlsDevProtocolCarriesCards,
47
45
  normalizePublicRuntimeDescriptor,
@@ -102,12 +100,11 @@ import {
102
100
  parseMlsPrincipalOpening,
103
101
  parseMlsRoomIdentities,
104
102
  prepareJsonContent,
105
- projectDeliveryStatus,
106
103
  projectVerbIntentToWire,
107
104
  readMembershipChangeMetadata,
108
105
  resolveAllowedUploadMimeType,
109
106
  sanitizeDownloadFileName
110
- } from "./chunk-5WPAB5MB.js";
107
+ } from "./chunk-VIS5WXNH.js";
111
108
 
112
109
  // src/media.ts
113
110
  import { createWriteStream as createWriteStream2 } from "node:fs";
@@ -587,7 +584,6 @@ function endpointIntentOrdering(intent2) {
587
584
  const body = { ...intent2.body, ...intent2.envelope };
588
585
  if (intent2.action === "consume_runtime_signal") return "independent";
589
586
  if (intent2.action === "generate_image") return "provider";
590
- if (["append_artifact_command", "retain_artifact_asset"].includes(intent2.action) && typeof body.artifactId === "string") return "artifact";
591
587
  if (intent2.action === "session_control" && !body.primitive && typeof body.agentId === "string") {
592
588
  const type = body.signal?.type;
593
589
  if (type === "interrupt" || type === "stop_and_drop") return "signal";
@@ -609,12 +605,10 @@ async function orderOperation(tx, operation) {
609
605
  }
610
606
  __name(orderOperation, "orderOperation");
611
607
  var outboxKey = /* @__PURE__ */ __name((operation) => operation.queueOrder === void 0 ? `outbox/${operation.operationId}` : `outbox/${ordinal(operation.queueOrder)}/${operation.operationId}`, "outboxKey");
612
- var artifactOrderingPrefix = /* @__PURE__ */ __name((conversationId, artifactId) => `outbound-artifact-order/${scope(conversationId)}/${scope(artifactId)}/`, "artifactOrderingPrefix");
613
608
  var controlOrderingKey = /* @__PURE__ */ __name((operation) => operation.action === "session_control" && operation.conversationId && operation.agentId ? `outbound-control-order/${scope(operation.conversationId)}/${scope(operation.agentId)}/` : void 0, "controlOrderingKey");
614
609
  var operationOrderingKey = /* @__PURE__ */ __name((operation) => {
615
610
  if (!operation.conversationId || operation.ordering === "independent" || operation.ordering === "provider") return void 0;
616
611
  if (operation.ordering === "signal") return controlOrderingKey(operation);
617
- if (operation.ordering === "artifact") return artifactOrderingPrefix(operation.conversationId, operation.artifactId ?? "~");
618
612
  return operation.ordering === "message" ? `outbound-ack-order/${scope(operation.conversationId)}/${scope(operation.messageId)}/` : `outbound-order/${scope(operation.conversationId)}/`;
619
613
  }, "operationOrderingKey");
620
614
  var outboundPrefix = /* @__PURE__ */ __name((operation) => operationOrderingKey(operation), "outboundPrefix");
@@ -1106,42 +1100,18 @@ var ROOM_QUERIES = /* @__PURE__ */ new Set([
1106
1100
  "list_voice_sessions",
1107
1101
  "get_voice_session"
1108
1102
  ]);
1109
- var ARTIFACT_QUERIES = /* @__PURE__ */ new Set(["list_artifacts", "get_artifact_log", "get_artifact_asset"]);
1110
1103
  var INBOX_QUERIES = /* @__PURE__ */ new Set(["list_conversations", "get_unread_summary"]);
1111
1104
  var CONTACT_QUERIES = /* @__PURE__ */ new Set(["list_contacts", "get_contact", "list_contact_requests"]);
1112
1105
  var ROOM_OPERATIONS = /* @__PURE__ */ new Set(["send_message", "mls_application", "mls_group_control", "react", "delete_message", "mark_read"]);
1113
- var ARTIFACT_OPERATIONS = /* @__PURE__ */ new Set(["append_artifact_command", "retain_artifact_asset"]);
1114
1106
  function queryAffectedByOperation(operation, query, parameters) {
1115
- if (ARTIFACT_OPERATIONS.has(operation.action)) {
1116
- if (operation.status !== "succeeded" || !ARTIFACT_QUERIES.has(query)) return false;
1117
- return typeof parameters.conversationId !== "string" || !operation.conversationId || parameters.conversationId === operation.conversationId;
1118
- }
1119
- if (ARTIFACT_QUERIES.has(query)) return false;
1120
1107
  if (!ROOM_OPERATIONS.has(operation.action)) return true;
1121
1108
  if (operation.status !== "succeeded" && !(operation.action === "mls_application" && ["rejected", "uncertain"].includes(operation.status))) return false;
1122
1109
  return queryAffectedByEvent({ kind: "conversation.changed", data: { conversationId: operation.conversationId } }, query, parameters);
1123
1110
  }
1124
1111
  __name(queryAffectedByOperation, "queryAffectedByOperation");
1125
- function artifactQueryAffected(event, query, parameters) {
1126
- const conversationId = parameters.conversationId;
1127
- const sameRoom = typeof conversationId !== "string" || typeof event.data.conversationId !== "string" || event.data.conversationId === conversationId;
1128
- if (event.kind === "artifact.changed") {
1129
- if (!sameRoom) return false;
1130
- return query === "list_artifacts" || typeof parameters.artifactId !== "string" || typeof event.data.artifactId !== "string" || parameters.artifactId === event.data.artifactId;
1131
- }
1132
- if (event.kind === "conversation.removed") return sameRoom;
1133
- if (event.kind === "conversations.changed") {
1134
- const removed = event.data.removedConversationIds;
1135
- return Array.isArray(removed) && (typeof conversationId !== "string" || removed.includes(conversationId));
1136
- }
1137
- return false;
1138
- }
1139
- __name(artifactQueryAffected, "artifactQueryAffected");
1140
1112
  function queryAffectedByEvent(event, query, parameters) {
1141
- if (!ROOM_QUERIES.has(query) && !ARTIFACT_QUERIES.has(query) && !INBOX_QUERIES.has(query) && !CONTACT_QUERIES.has(query)) return true;
1113
+ if (!ROOM_QUERIES.has(query) && !INBOX_QUERIES.has(query) && !CONTACT_QUERIES.has(query)) return true;
1142
1114
  if (event.kind === "agent.context" || event.kind.startsWith("replay.")) return true;
1143
- if (ARTIFACT_QUERIES.has(query)) return artifactQueryAffected(event, query, parameters);
1144
- if (event.kind === "artifact.changed") return false;
1145
1115
  if (/^contacts?\./.test(event.kind)) return CONTACT_QUERIES.has(query);
1146
1116
  if (!/^(message\.|conversations?\.|voice\.session\.)/.test(event.kind)) return true;
1147
1117
  if (CONTACT_QUERIES.has(query)) return false;
@@ -1172,10 +1142,10 @@ async function prepareEndpointIntent(input) {
1172
1142
  const { intent: intent2 } = input;
1173
1143
  const nested = intent2.action === "verb_execute";
1174
1144
  const action = nested ? intent2.body.verb : intent2.action;
1175
- if (!["remove_member", "update_member_role", "leave_conversation", "append_artifact_command", "retain_artifact_asset"].includes(String(action))) return intent2;
1145
+ if (!["remove_member", "update_member_role", "leave_conversation"].includes(String(action))) return intent2;
1176
1146
  const body = intentBody(intent2);
1177
1147
  const conversationId = body.conversationId ?? body.targetConversationId;
1178
- const ownAdmission = ["leave_conversation", "append_artifact_command", "retain_artifact_asset"].includes(String(action));
1148
+ const ownAdmission = action === "leave_conversation";
1179
1149
  const targetUserId = body.targetUserId ?? body.userId ?? body.agentId;
1180
1150
  if (typeof conversationId !== "string" || !ownAdmission && typeof targetUserId !== "string") {
1181
1151
  throw new EndpointError("Membership control requires a conversation and target", { status: 400, code: "INVALID_MEMBERSHIP_CONTROL", retryable: false });
@@ -2035,7 +2005,7 @@ __name(createEndpointMigration, "createEndpointMigration");
2035
2005
 
2036
2006
  // ../endpoint/src/media.ts
2037
2007
  function createEndpointMediaPipeline(options) {
2038
- const { endpoint, store, bytes, background } = options;
2008
+ const { endpoint, store, bytes } = options;
2039
2009
  const clock = options.clock ?? Date.now;
2040
2010
  const ids = options.ids ?? (() => crypto.randomUUID());
2041
2011
  const leaseMs = Math.max(1e3, options.leaseMs ?? 3e4);
@@ -2090,14 +2060,6 @@ function createEndpointMediaPipeline(options) {
2090
2060
  const listeners = /* @__PURE__ */ new Set();
2091
2061
  let closed = false;
2092
2062
  let foreground = true;
2093
- let restoredNativeObservations = false;
2094
- const reconciliation = /* @__PURE__ */ new Map();
2095
- const observations = /* @__PURE__ */ new Map();
2096
- let backgroundCapability;
2097
- let lastServerBackgroundCapability;
2098
- let serverPreparedContentVersion;
2099
- const preparationGeneration = background ? endpoint.ready().then(() => readPreparationGeneration(store)) : Promise.resolve(0);
2100
- void preparationGeneration.catch(() => void 0);
2101
2063
  let timer;
2102
2064
  const prefix3 = "media-flow/";
2103
2065
  const attachmentFor = /* @__PURE__ */ __name((file, value, resultUploadId) => {
@@ -2117,7 +2079,6 @@ function createEndpointMediaPipeline(options) {
2117
2079
  for (const file of previous?.files ?? []) if (file.uploadId && !currentIds.has(file.uploadId)) await tx.delete(`media-prepared-upload/${file.uploadId}`);
2118
2080
  for (const uploadId of currentIds) await tx.put(`media-prepared-upload/${uploadId}`, { id: flow.id });
2119
2081
  }
2120
- if (flow.status === "succeeded" || flow.status === "cancelled") delete flow.preparedContent;
2121
2082
  await tx.put(`${prefix3}${flow.id}`, flow);
2122
2083
  if (flow.status === "pending" || flow.status === "sending") await tx.put(`media-pending/${flow.id}`, { id: flow.id });
2123
2084
  else await tx.delete(`media-pending/${flow.id}`);
@@ -2158,10 +2119,6 @@ function createEndpointMediaPipeline(options) {
2158
2119
  timer.unref?.();
2159
2120
  }
2160
2121
  __name(schedule, "schedule");
2161
- const stopConnections = background ? endpoint.observeEvents?.(() => void 0, { families: ["messages"], onConnected: /* @__PURE__ */ __name(() => {
2162
- for (const state of reconciliation.values()) state.nextAt = Math.min(state.nextAt, clock() + 5e3);
2163
- schedule();
2164
- }, "onConnected") }) : void 0;
2165
2122
  const unsubscribe = endpoint.subscribe((operation) => {
2166
2123
  if (operation.status === "succeeded" && operation.action.endsWith("_upload")) schedule();
2167
2124
  });
@@ -2170,265 +2127,21 @@ function createEndpointMediaPipeline(options) {
2170
2127
  await Promise.allSettled(flow.files.filter((file) => file.reference).map((file) => bytes.release(file.reference)));
2171
2128
  }
2172
2129
  __name(release, "release");
2173
- function probeBackgroundCapability() {
2174
- if (!background) return Promise.resolve(false);
2175
- if (!backgroundCapability) {
2176
- const check = Promise.resolve().then(() => Promise.all([background.available(), endpoint.capabilities()])).then(([nativeAvailable, capabilities]) => {
2177
- if (capabilities) serverPreparedContentVersion = capabilities.preparedMediaContentVersion;
2178
- if (capabilities) lastServerBackgroundCapability = capabilities.actions.includes("stage_media_message") && (background.observationVersion !== 2 || capabilities.mediaDeliveryObservationVersion === 2);
2179
- return nativeAvailable && lastServerBackgroundCapability === true;
2180
- }).catch(() => false).then((enabled) => {
2181
- if (!enabled && backgroundCapability === check) backgroundCapability = void 0;
2182
- return enabled;
2183
- });
2184
- backgroundCapability = check;
2185
- }
2186
- return backgroundCapability;
2187
- }
2188
- __name(probeBackgroundCapability, "probeBackgroundCapability");
2189
2130
  const encryptedRoom = /* @__PURE__ */ __name(async (conversationId) => await endpoint.getConversationContentMode(conversationId) !== "plaintext", "encryptedRoom");
2190
- async function 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 });
2131
+ async function retireBackground(flow) {
2307
2132
  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 });
2133
+ if (flow.status !== "cancelled") {
2134
+ const owner = await acquire(flow.id);
2410
2135
  try {
2411
- if (sendOptions?.signal?.aborted) onAbort();
2412
- return await resumeBackground(await read(id2), sendOptions?.onProgress);
2136
+ const { id: id2, conversationId, messageId, createdAt, deliveryMode } = flow;
2137
+ await write({ id: id2, conversationId, messageId, createdAt, deliveryMode, message: {}, files: [], status: "cancelled" }, owner);
2413
2138
  } finally {
2414
- sendOptions?.signal?.removeEventListener("abort", onAbort);
2139
+ await unlock(flow.id, owner);
2415
2140
  }
2416
- })();
2417
- staging.set(id2, staged);
2418
- try {
2419
- return await staged;
2420
- } finally {
2421
- if (staging.get(id2) === staged) staging.delete(id2);
2422
2141
  }
2142
+ throw new EndpointError("Attachment was cancelled", { code: "MEDIA_FLOW_CANCELLED", retryable: false });
2423
2143
  }
2424
- __name(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
- });
2144
+ __name(retireBackground, "retireBackground");
2432
2145
  async function stage(input, sendOptions, prepareOnly = false) {
2433
2146
  if (closed) throw new Error("Endpoint media pipeline is closed");
2434
2147
  const id2 = flowId(input.conversationId, input.messageId);
@@ -2470,7 +2183,7 @@ function createEndpointMediaPipeline(options) {
2470
2183
  files.push({ descriptor: copyJson(file.descriptor), reference: await bytes.retain(`${id2}:${fingerprint}:${index}`, file.source) });
2471
2184
  }
2472
2185
  checkOpen();
2473
- await write({ id: id2, ...prepareOnly ? { purpose: "prepare" } : {}, conversationId: input.conversationId, messageId: input.messageId, fingerprint, createdAt: new Date(clock()).toISOString(), ...sendOptions?.beforePublish ? { requiresPublicationApproval: true } : {}, message: copyJson(input.message), files, deliveryMode: "legacy", status: "pending" }, owner);
2186
+ await write({ id: id2, ...prepareOnly ? { purpose: "prepare" } : {}, conversationId: input.conversationId, messageId: input.messageId, fingerprint, createdAt: new Date(clock()).toISOString(), ...sendOptions?.beforePublish ? { requiresPublicationApproval: true } : {}, message: copyJson(input.message), files, status: "pending" }, owner);
2474
2187
  }
2475
2188
  } catch (error) {
2476
2189
  await Promise.allSettled(files.map((file) => bytes.release(file.reference)));
@@ -2492,18 +2205,9 @@ function createEndpointMediaPipeline(options) {
2492
2205
  }
2493
2206
  __name(stage, "stage");
2494
2207
  const pipeline3 = {
2495
- canSendInBackground,
2496
2208
  async send(input, sendOptions) {
2497
- const 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
- }
2209
+ const prior = await read(flowId(input.conversationId, input.messageId));
2210
+ if (prior?.deliveryMode === "background") return retireBackground(prior);
2507
2211
  return stage(input, sendOptions);
2508
2212
  },
2509
2213
  async prepare(input, prepareOptions) {
@@ -2515,7 +2219,7 @@ function createEndpointMediaPipeline(options) {
2515
2219
  },
2516
2220
  async resume(id2, resumeOptions) {
2517
2221
  const persisted = await read(id2);
2518
- if (persisted?.deliveryMode === "background") return resumeBackground(persisted, resumeOptions?.onProgress);
2222
+ if (persisted?.deliveryMode === "background") return retireBackground(persisted);
2519
2223
  installGuard(id2, resumeOptions?.beforePublish);
2520
2224
  const existing = running.get(id2);
2521
2225
  if (existing) return existing;
@@ -2771,15 +2475,6 @@ function createEndpointMediaPipeline(options) {
2771
2475
  },
2772
2476
  async resumePending() {
2773
2477
  if (closed) return;
2774
- if (background?.reconcile && !restoredNativeObservations) {
2775
- restoredNativeObservations = true;
2776
- try {
2777
- for (const snapshot of await background.inspect()) await applyBackgroundSnapshot(snapshot);
2778
- } catch {
2779
- restoredNativeObservations = false;
2780
- schedule();
2781
- }
2782
- }
2783
2478
  let after;
2784
2479
  do {
2785
2480
  const records = await store.transaction((tx) => tx.scan("media-pending/", { after, limit: 100 }));
@@ -2793,20 +2488,9 @@ function createEndpointMediaPipeline(options) {
2793
2488
  if (saved) await pipeline3.cancel(saved.id);
2794
2489
  },
2795
2490
  async cancel(id2) {
2796
- const 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();
2491
+ const retired = await read(id2);
2492
+ if (retired?.deliveryMode === "background") {
2493
+ await retireBackground(retired).catch(() => void 0);
2810
2494
  return;
2811
2495
  }
2812
2496
  if (staging.has(id2)) cancelledStages.add(id2);
@@ -2829,15 +2513,12 @@ function createEndpointMediaPipeline(options) {
2829
2513
  async close(closeOptions) {
2830
2514
  closed = true;
2831
2515
  unsubscribe();
2832
- stopConnections?.();
2833
- stopBackground?.();
2834
2516
  listeners.clear();
2835
2517
  for (const guard of publicationGuards.values()) guard.unregister();
2836
2518
  publicationGuards.clear();
2837
2519
  if (timer) clearTimeout(timer);
2838
2520
  for (const controller of controllers.values()) controller.abort();
2839
2521
  await Promise.allSettled([...staging.values(), ...running.values()]);
2840
- if (closeOptions?.discard) await background?.cleanup();
2841
2522
  if (closeOptions?.discard) {
2842
2523
  let after;
2843
2524
  do {
@@ -3412,13 +3093,6 @@ function createExternalInboundClaims(input) {
3412
3093
  }
3413
3094
  __name(createExternalInboundClaims, "createExternalInboundClaims");
3414
3095
 
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
3096
  // ../endpoint/src/receipts.ts
3423
3097
  function endpointReceiptResult(receipt) {
3424
3098
  if (receipt.resultError) throw new EndpointError(receipt.resultError.message, { status: receipt.resultError.status, code: receipt.resultError.code, retryable: receipt.resultError.retryable, operationId: receipt.operationId });
@@ -7931,6 +7605,51 @@ function createEndpoint(options) {
7931
7605
  }
7932
7606
  }
7933
7607
  __name(refreshMlsRoomFromHint, "refreshMlsRoomFromHint");
7608
+ const roomHints = /* @__PURE__ */ new Map();
7609
+ let roomHintWorker;
7610
+ function queueRoomHint(key3, run, last = false) {
7611
+ if (closed) return Promise.resolve();
7612
+ let hint = roomHints.get(key3);
7613
+ if (hint && last) roomHints.delete(key3);
7614
+ if (!hint) {
7615
+ let settle;
7616
+ const done = new Promise((resolve3, reject) => {
7617
+ settle = /* @__PURE__ */ __name((error) => error === void 0 ? resolve3() : reject(error), "settle");
7618
+ });
7619
+ done.catch(() => void 0);
7620
+ hint = { run, done, settle };
7621
+ }
7622
+ roomHints.set(key3, hint);
7623
+ drainRoomHints();
7624
+ return hint.done;
7625
+ }
7626
+ __name(queueRoomHint, "queueRoomHint");
7627
+ function drainRoomHints() {
7628
+ if (roomHintWorker) return;
7629
+ roomHintWorker = (async () => {
7630
+ for (const [key3, hint] of roomHints) {
7631
+ roomHints.delete(key3);
7632
+ if (closed) {
7633
+ hint.settle();
7634
+ continue;
7635
+ }
7636
+ try {
7637
+ await hint.run();
7638
+ hint.settle();
7639
+ } catch (error) {
7640
+ hint.settle(error);
7641
+ }
7642
+ }
7643
+ })().finally(() => {
7644
+ roomHintWorker = void 0;
7645
+ if (roomHints.size) drainRoomHints();
7646
+ });
7647
+ }
7648
+ __name(drainRoomHints, "drainRoomHints");
7649
+ const hintRoom = /* @__PURE__ */ __name((conversationId) => queueRoomHint(`room/${conversationId}`, async () => {
7650
+ await refreshMlsRoomFromHint(conversationId);
7651
+ await sendDeferredOpeners(conversationId);
7652
+ }), "hintRoom");
7934
7653
  const queryObserver = createEndpointQueryObserver({ query: /* @__PURE__ */ __name((name, parameters) => endpoint.query(name, parameters), "query"), getPollInterval: /* @__PURE__ */ __name(() => events.isConnected() ? Math.max(options.queryIntervalMs ?? 0, 6e4) : options.queryIntervalMs ?? 3e3, "getPollInterval") });
7935
7654
  const invalidateEvent = /* @__PURE__ */ __name((event) => queryObserver.invalidate(event ? (query, parameters) => queryAffectedByEvent(event, query, parameters) : void 0), "invalidateEvent");
7936
7655
  const syncConversation = createConversationSynchronizer({ store, query: /* @__PURE__ */ __name((name, parameters) => endpoint.query(name, parameters), "query"), ready: /* @__PURE__ */ __name(() => initialization, "ready"), clock, invalidate: /* @__PURE__ */ __name((conversationId) => invalidateEvent({ kind: "conversation.changed", data: { conversationId } }), "invalidate"), admitRecoveredMessages: options.admitRecoveredMessages, preparationGeneration: /* @__PURE__ */ __name(() => installationGeneration, "preparationGeneration"), prepareProtocol: /* @__PURE__ */ __name((id2) => refreshMlsRoom(id2, true), "prepareProtocol"), syncProtocol: /* @__PURE__ */ __name(async (id2, opts) => {
@@ -7939,19 +7658,20 @@ function createEndpoint(options) {
7939
7658
  return mlsDevGroups.syncText(id2, opts);
7940
7659
  }, "syncProtocol") });
7941
7660
  const events = createEndpointEventHub({ transport, invalidate: invalidateEvent, onDisconnected: /* @__PURE__ */ __name(() => queryObserver.reschedule(), "onDisconnected"), onEvent: /* @__PURE__ */ __name(async (event) => {
7661
+ const hinted = [];
7942
7662
  if (mlsDevGroups && ["conversation.changed", "conversation.updated", "conversation.removed"].includes(event.kind)) {
7943
7663
  const id2 = event.data.conversationId ?? event.data.id;
7944
7664
  if (typeof id2 === "string" && ENDPOINT_ID_PATTERN.test(id2)) {
7945
7665
  if (event.kind === "conversation.changed" && event.data.projection === void 0) forgetStubReads(id2);
7946
7666
  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);
7667
+ if (!unseenRemoval) hinted.push(hintRoom(id2));
7668
+ else await sendDeferredOpeners(id2);
7949
7669
  }
7950
7670
  }
7951
7671
  if (mlsDevGroups && event.kind === "conversations.changed" && Array.isArray(event.data.conversationIds)) {
7952
7672
  for (const id2 of event.data.conversationIds) {
7953
7673
  if (typeof id2 === "string" && ENDPOINT_ID_PATTERN.test(id2) && await store.transaction((tx) => isMlsDevRoomProtected(tx, id2))) {
7954
- await refreshMlsRoomFromHint(id2);
7674
+ hinted.push(hintRoom(id2));
7955
7675
  }
7956
7676
  }
7957
7677
  }
@@ -7963,13 +7683,16 @@ function createEndpoint(options) {
7963
7683
  if (event.kind === "connected") serverRoomMode = void 0;
7964
7684
  for (let after; ; ) {
7965
7685
  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)));
7686
+ for (const item of page) hinted.push(hintRoom(decodeURIComponent(item.key.slice("mls-dev-group/mode/".length))));
7967
7687
  if (page.length < 100) break;
7968
7688
  after = page[page.length - 1].key;
7969
7689
  }
7970
- await sendDeferredOpeners();
7971
- await discoverInviteeRooms().catch(() => void 0);
7690
+ hinted.push(queueRoomHint("after/reconnect", async () => {
7691
+ await sendDeferredOpeners();
7692
+ await discoverInviteeRooms().catch(() => void 0);
7693
+ }, true));
7972
7694
  }
7695
+ if (event.id) await Promise.all(hinted);
7973
7696
  if ((event.kind === "message.updated" || event.kind === "message.deleted") && typeof event.data.conversationId === "string" && typeof event.data.messageId === "string" && ENDPOINT_ID_PATTERN.test(event.data.conversationId)) {
7974
7697
  const room = event.data.conversationId, message = event.data.messageId;
7975
7698
  const applied = await store.transaction(async (tx) => {
@@ -8540,7 +8263,6 @@ function createEndpoint(options) {
8540
8263
  ...endpointIntentConversationId(normalized) ? { conversationId: endpointIntentConversationId(normalized) } : {},
8541
8264
  ...typeof routedBody.messageId === "string" ? { messageId: routedBody.messageId } : {},
8542
8265
  ...normalized.action === "session_control" && typeof routedBody.agentId === "string" ? { agentId: routedBody.agentId } : {},
8543
- ...endpointIntentOrdering(normalized) === "artifact" && typeof routedBody.artifactId === "string" ? { artifactId: routedBody.artifactId } : {},
8544
8266
  sensitive,
8545
8267
  status: "queued",
8546
8268
  attempts: 0,
@@ -8606,7 +8328,7 @@ function createEndpoint(options) {
8606
8328
  let dispatched = 0;
8607
8329
  let hasDeferred = false;
8608
8330
  const blockedKeys = /* @__PURE__ */ new Set();
8609
- for (const ordering of ["independent", "signal", "message", "provider", "artifact", "conversation"]) {
8331
+ for (const ordering of ["independent", "signal", "message", "provider", "conversation"]) {
8610
8332
  let after;
8611
8333
  do {
8612
8334
  const batch = await store.transaction(async (tx) => {
@@ -8782,9 +8504,9 @@ function createEndpoint(options) {
8782
8504
  },
8783
8505
  refreshQueries: /* @__PURE__ */ __name(() => queryObserver.invalidate(), "refreshQueries"),
8784
8506
  observeEvents: events.observe,
8785
- createMediaPipeline(bytes, mediaOptions) {
8507
+ createMediaPipeline(bytes) {
8786
8508
  assertOpen();
8787
- const pipeline3 = createEndpointMediaPipeline({ endpoint, store, bytes, hash, ids, clock, background: mediaOptions?.background });
8509
+ const pipeline3 = createEndpointMediaPipeline({ endpoint, store, bytes, hash, ids, clock });
8788
8510
  mediaPipelines2.add(pipeline3);
8789
8511
  return pipeline3;
8790
8512
  },
@@ -9088,7 +8810,7 @@ function createEndpoint(options) {
9088
8810
  listeners.clear();
9089
8811
  closing = (async () => {
9090
8812
  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]);
8813
+ await Promise.allSettled([...running.values(), ...reads.values(), ...deviceRuns, initialization, roomHintWorker]);
9092
8814
  await endpoint.discardPending();
9093
8815
  await eraseEndpointPlaintext(store);
9094
8816
  await store.close();
@@ -9111,11 +8833,10 @@ function createEndpoint(options) {
9111
8833
  sensitiveRequests.clear();
9112
8834
  sensitiveFingerprints.clear();
9113
8835
  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());
8836
+ closing = Promise.allSettled([handoffs.close(), recovery.close(), syncConversation.close(), events.close(), ...[...consumers].map((consumer) => consumer.close()), ...[...mediaPipelines2].map((pipeline3) => pipeline3.close())]).then(() => Promise.allSettled([...running.values(), ...reads.values(), ...deviceRuns, initialization, roomHintWorker])).then(() => store.close());
9115
8837
  return closing;
9116
8838
  }
9117
8839
  };
9118
- registerArtifactEndpoint(endpoint, { store, closed: /* @__PURE__ */ __name(() => closed, "closed"), assertCurrent: assertCurrentInstallation });
9119
8840
  void initialization.then(() => scheduleRetry(0)).catch(() => void 0);
9120
8841
  void initialization.then(() => sendDeferredOpeners()).catch(() => void 0);
9121
8842
  if (mlsDevGroups) void initialization.then(() => resumeHeldTexts()).catch(() => void 0);
@@ -9173,7 +8894,7 @@ async function acquireAgentEndpoint(input) {
9173
8894
  }
9174
8895
  const requestedMls = input.options.mlsDev;
9175
8896
  const mls = requestedMls ? await (async () => {
9176
- const crypto2 = await import("./mls-crypto-NZVMSUFU.js");
8897
+ const crypto2 = await import("./mls-crypto-ZBEC26B5.js");
9177
8898
  const configuration = crypto2.parseMlsDevClientConfiguration(requestedMls.configuration);
9178
8899
  if (configuration.environmentId !== environmentId || baseUrl !== CANON_RUNTIME_ENDPOINT_DEFAULTS[configuration.environmentId]?.apiBaseUrl || configuration.principalIds && !configuration.principalIds.includes(input.principalId) || !Number.isSafeInteger(input.ownershipVersion) || input.ownershipVersion < 0) throw new Error("MLS_DEV_AGENT_CONFIGURATION_BINDING_MISMATCH");
9179
8900
  const custody = requestedMls.custody;