@canonmsg/agent-sdk 12.6.1 → 13.0.3

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 (30) hide show
  1. package/README.md +23 -21
  2. package/dist/chunks/{chunk-S7BV74DN.js → chunk-5WPAB5MB.js} +293 -8
  3. package/dist/chunks/{chunk-HH7KR22P.js → chunk-DTAWEQCM.js} +2 -2
  4. package/dist/chunks/{chunk-DAJ7L7TN.js → chunk-XKUG2BQR.js} +378 -663
  5. package/dist/chunks/{chunk-QK37HAHC.js → chunk-YHDDOADK.js} +1 -1
  6. package/dist/chunks/{chunk-FVYKLH6I.js → chunk-YJUPKU47.js} +20 -5
  7. package/dist/chunks/{chunk-5J4YPLNW.js → chunk-ZPBOGOAD.js} +267 -1217
  8. package/dist/chunks/{mls-crypto-4DZT2UFH.js → mls-crypto-NZVMSUFU.js} +2 -2
  9. package/dist/cli.js +32 -36
  10. package/dist/drivers/subprocess.d.ts +4 -1
  11. package/dist/index.d.ts +1 -1
  12. package/dist/index.js +6 -6
  13. package/dist/internal/backend-contracts/mlsDevGroup.d.ts +17 -0
  14. package/dist/internal/core/completed-output-journal.d.ts +0 -5
  15. package/dist/internal/core/index.d.ts +3 -9
  16. package/dist/internal/core/retry.d.ts +2 -89
  17. package/dist/internal/core/silent-turn.d.ts +4 -33
  18. package/dist/internal/endpoint/engine.d.ts +3 -0
  19. package/dist/internal/endpoint/mls-dev-groups.d.ts +12 -0
  20. package/dist/internal/endpoint/mls-dev-text.d.ts +29 -0
  21. package/dist/internal/endpoint/transport.d.ts +1 -1
  22. package/dist/media.js +4 -4
  23. package/dist/mls-wasm-host.js +3 -3
  24. package/dist/runner/mls.d.ts +4 -7
  25. package/dist/runner/runner.d.ts +9 -12
  26. package/dist/runner/types.d.ts +31 -0
  27. package/package.json +1 -1
  28. package/dist/internal/core/approval-manager.d.ts +0 -43
  29. package/dist/internal/core/runtime-request-manager.d.ts +0 -283
  30. package/dist/internal/core/turn-verbosity.d.ts +0 -79
@@ -5,7 +5,7 @@ import {
5
5
  createHttpEndpointTransport,
6
6
  createNodeEndpointStore,
7
7
  validateEndpointContentBoundary
8
- } from "./chunk-FVYKLH6I.js";
8
+ } from "./chunk-YJUPKU47.js";
9
9
  import {
10
10
  CANON_ENDPOINT_PROTOCOL,
11
11
  CANON_VERB_WIRE_SCHEMA_VERSION,
@@ -42,6 +42,7 @@ import {
42
42
  isMlsPrincipalAlias,
43
43
  logicalMediaBlobId,
44
44
  mergeDeliveryObservation,
45
+ mlsAdmissionWait,
45
46
  mlsDevProtocolCarriesCards,
46
47
  normalizePublicRuntimeDescriptor,
47
48
  normalizePublicRuntimeFacts,
@@ -106,11 +107,11 @@ import {
106
107
  readMembershipChangeMetadata,
107
108
  resolveAllowedUploadMimeType,
108
109
  sanitizeDownloadFileName
109
- } from "./chunk-S7BV74DN.js";
110
+ } from "./chunk-5WPAB5MB.js";
110
111
 
111
112
  // src/media.ts
112
113
  import { createWriteStream as createWriteStream2 } from "node:fs";
113
- import { randomUUID as randomUUID8 } from "node:crypto";
114
+ import { randomUUID as randomUUID7 } from "node:crypto";
114
115
  import { mkdir as mkdir2, open as open2, rename as rename2, stat as stat2, unlink as unlink2 } from "node:fs/promises";
115
116
  import { basename, dirname as dirname2, extname, join as join5 } from "node:path";
116
117
  import { Readable, Transform as Transform2 } from "node:stream";
@@ -4904,6 +4905,38 @@ async function isMlsDevOwnAnswerSending(tx, conversationId, agentId, requestId)
4904
4905
  return !!authored && authored.feedOrdinal === null && (authored.status === "queued" || authored.status === "uncertain");
4905
4906
  }
4906
4907
  __name(isMlsDevOwnAnswerSending, "isMlsDevOwnAnswerSending");
4908
+ var mlsDevHeldTextPrefix = /* @__PURE__ */ __name((conversationId) => `mls-dev-group/held-text/${conversationId === void 0 ? "" : `${encoded2(conversationId)}/`}`, "mlsDevHeldTextPrefix");
4909
+ var mlsDevHeldTextKey = /* @__PURE__ */ __name((conversationId, messageId) => `${mlsDevHeldTextPrefix(conversationId)}${encoded2(messageId)}`, "mlsDevHeldTextKey");
4910
+ async function readMlsDevHeldTexts(tx, conversationId) {
4911
+ const rows = await tx.scan(mlsDevHeldTextPrefix(conversationId), { limit: MLS_DEV_HELD_TEXT_MAX });
4912
+ return rows.map((row) => row.value).sort((a, b) => a.sequence - b.sequence);
4913
+ }
4914
+ __name(readMlsDevHeldTexts, "readMlsDevHeldTexts");
4915
+ var MLS_DEV_HELD_TEXT_MAX = 100;
4916
+ async function projectMlsDevHeldText(tx, value, account) {
4917
+ const { body } = value;
4918
+ const quote = body.replyTo ? mlsDevReplyQuote(
4919
+ await tx.get(mlsDevTextMessageKey(body.conversationId, body.replyTo)),
4920
+ MLS_DEV_REPLY_QUOTE_MAX_CHARACTERS
4921
+ ) : void 0;
4922
+ return {
4923
+ id: body.messageId,
4924
+ conversationId: body.conversationId,
4925
+ senderId: account({ type: value.senderType, id: value.senderId }),
4926
+ senderType: value.senderType,
4927
+ contentType: "text",
4928
+ text: body.text,
4929
+ mentions: body.mentions ?? [],
4930
+ attachments: [],
4931
+ replyTo: body.replyTo ?? null,
4932
+ ...quote ? { replyQuote: quote } : {},
4933
+ status: value.failed ? "failed" : "sending",
4934
+ deleted: false,
4935
+ createdAt: new Date(value.createdAtMs).toISOString(),
4936
+ mlsFeedOrdinal: null
4937
+ };
4938
+ }
4939
+ __name(projectMlsDevHeldText, "projectMlsDevHeldText");
4907
4940
  var mlsDevTextDeletedKey = /* @__PURE__ */ __name((conversationId, messageId) => `mls-dev-group/message-deleted/${encoded2(conversationId)}/${encoded2(messageId)}`, "mlsDevTextDeletedKey");
4908
4941
  var mlsDevTextReactionsKey = /* @__PURE__ */ __name((conversationId, messageId) => `mls-dev-group/message-reactions/${encoded2(conversationId)}/${encoded2(messageId)}`, "mlsDevTextReactionsKey");
4909
4942
  var erased = /* @__PURE__ */ __name((value) => {
@@ -5036,7 +5069,8 @@ async function queryMlsDevTextHistory(tx, conversationId, name, parameters, audi
5036
5069
  const messages2 = [], missingMessageIds = [];
5037
5070
  for (const id2 of parameters.messageIds) {
5038
5071
  const value = await tx.get(mlsDevTextMessageKey(conversationId, id2));
5039
- const projected = value && await projectLocal(tx, value, audience);
5072
+ const held = value ? void 0 : await tx.get(mlsDevHeldTextKey(conversationId, id2));
5073
+ const projected = value ? await projectLocal(tx, value, audience) : held && await projectMlsDevHeldText(tx, held, audience.account);
5040
5074
  if (projected) messages2.push(projected);
5041
5075
  else missingMessageIds.push(id2);
5042
5076
  }
@@ -5060,10 +5094,15 @@ async function queryMlsDevTextHistory(tx, conversationId, name, parameters, audi
5060
5094
  return { ...row.value, ordinal: position };
5061
5095
  });
5062
5096
  const messages = [];
5063
- if (parameters.before === void 0) for (const row of await tx.scan(pendingPrefix(conversationId), { limit: 100 })) {
5064
- const value = await tx.get(mlsDevTextMessageKey(conversationId, row.value.messageId));
5065
- const projected = value && await projectLocal(tx, value, audience);
5066
- if (projected) messages.push(projected);
5097
+ if (parameters.before === void 0) {
5098
+ for (const held of (await readMlsDevHeldTexts(tx, conversationId)).reverse()) {
5099
+ if (await tx.get(mlsDevTextMessageKey(conversationId, held.body.messageId)) === void 0) messages.push(await projectMlsDevHeldText(tx, held, audience.account));
5100
+ }
5101
+ for (const row of await tx.scan(pendingPrefix(conversationId), { limit: 100 })) {
5102
+ const value = await tx.get(mlsDevTextMessageKey(conversationId, row.value.messageId));
5103
+ const projected = value && await projectLocal(tx, value, audience);
5104
+ if (projected) messages.push(projected);
5105
+ }
5067
5106
  }
5068
5107
  for (const item of selected) {
5069
5108
  const value = await tx.get(mlsDevTextMessageKey(conversationId, item.messageId));
@@ -5166,6 +5205,7 @@ var recoveryFailureKey = /* @__PURE__ */ __name((id2) => `mls-dev-group/recovery
5166
5205
  var authorityKey = /* @__PURE__ */ __name((id2) => `mls-dev-group/authority/${encodeURIComponent(id2)}`, "authorityKey");
5167
5206
  var acceptedKey = /* @__PURE__ */ __name((id2, sequence) => `mls-dev-group/accepted/${encodeURIComponent(id2)}/${sequence}`, "acceptedKey");
5168
5207
  var feedKey = /* @__PURE__ */ __name((id2) => `mls-dev-group/feed/${encodeURIComponent(id2)}`, "feedKey");
5208
+ var missedEarlierKey = /* @__PURE__ */ __name((id2) => `mls-dev-group/missed-earlier/${encodeURIComponent(id2)}`, "missedEarlierKey");
5169
5209
  var feedEntryKey = /* @__PURE__ */ __name((id2, ordinal3) => `mls-dev-group/feed-entry/${encodeURIComponent(id2)}/${ordinal3}`, "feedEntryKey");
5170
5210
  var appIntentKey = /* @__PURE__ */ __name((operationId) => `mls-dev-group/application/${encodeURIComponent(operationId)}`, "appIntentKey");
5171
5211
  var appOperationKey = /* @__PURE__ */ __name((deviceId, operationId) => `mls-dev-group/application-operation/${encodeURIComponent(deviceId)}/${encodeURIComponent(operationId)}`, "appOperationKey");
@@ -5433,6 +5473,7 @@ function createMlsDevGroups(options, context, keyPackages, bridge) {
5433
5473
  return { changed: false, operation: void 0 };
5434
5474
  }
5435
5475
  if (feed) await checkFeedEntry(tx, current, conversationId, feed);
5476
+ if (!current.envelope?.head && current.envelope?.pending?.control.kind === "create" && current.envelope.pending.operationId !== accepted.operationId) fail6("MLS_DEV_GROUP_PENDING_CONTROL", true);
5436
5477
  const rejoining = !!current.envelope && accepted.control.kind === "add" && accepted.control.keyPackageClaim?.recipientDeviceId === current.device.registration.device.deviceId && accepted.head.sequence > (current.envelope.head?.sequence ?? 0);
5437
5478
  if (current.envelope?.removed && !rejoining) fail6("MLS_DEV_GROUP_REMOVED");
5438
5479
  if (current.envelope?.head && !rejoining && (!same6(accepted.control.expectedHead, current.envelope.head) || accepted.head.sequence !== current.envelope.head.sequence + 1)) fail6("MLS_DEV_GROUP_CONTROL_GAP", true);
@@ -5840,6 +5881,8 @@ function createMlsDevGroups(options, context, keyPackages, bridge) {
5840
5881
  observedSequence: current.envelope?.head?.sequence ?? 0
5841
5882
  });
5842
5883
  await rememberMlsDevRoomMode(tx, cid, response.marker);
5884
+ if (initialWelcome && captured.feedOrdinal === 0 && first.kind === "control" && first.ordinal > first.accepted.head.sequence)
5885
+ await tx.put(missedEarlierKey(cid), true);
5843
5886
  });
5844
5887
  for (const entry of response.entries) {
5845
5888
  if (entry.kind === "control") {
@@ -5897,17 +5940,35 @@ function createMlsDevGroups(options, context, keyPackages, bridge) {
5897
5940
  }
5898
5941
  }
5899
5942
  __name(validItem, "validItem");
5943
+ function validText(text) {
5944
+ try {
5945
+ parseMlsDevTextPayload({
5946
+ version: 1,
5947
+ kind: "text",
5948
+ conversationId: "validation",
5949
+ groupId: "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
5950
+ messageId: "validation",
5951
+ operationId: "validation",
5952
+ senderDeviceId: "00000000-0000-4000-8000-000000000000",
5953
+ text
5954
+ });
5955
+ } catch {
5956
+ fail6("MLS_DEV_TEXT_INVALID");
5957
+ }
5958
+ }
5959
+ __name(validText, "validText");
5900
5960
  function textInput(value) {
5901
- const data = record4(value, ["conversationId"], ["text", "messageId", "contentType", "mentions", "attachments", "replyTo", "replyToPosition", "metadata", "forwarded", "contactCardUserId"]);
5961
+ const data = record4(value, ["conversationId"], ["text", "messageId", "contentType", "mentions", "attachments", "replyTo", "replyToPosition", "metadata", "forwarded", "contactCardUserId", "replyAuthority"]);
5902
5962
  const attachments = data.attachments ?? [];
5903
5963
  if (!Array.isArray(attachments) || attachments.length > 1) fail6(Array.isArray(attachments) ? "MLS_DEV_ONE_ITEM_PER_MESSAGE" : "MLS_DEV_TEXT_ONLY");
5904
5964
  const item = attachments.length ? itemInput(attachments[0], data.text) : void 0;
5905
- if (data.contentType !== void 0 && data.contentType !== (item ? item.type : "text") || !item && typeof data.text !== "string" || item && data.text !== void 0 && data.text !== null && typeof data.text !== "string" || data.mentions !== void 0 && (!Array.isArray(data.mentions) || data.mentions.some((mention) => typeof mention !== "string")) || data.replyTo !== void 0 && data.replyTo !== null && typeof data.replyTo !== "string" || ["replyToPosition", "metadata", "forwarded", "contactCardUserId"].some((key) => data[key] !== void 0 && data[key] !== null)) fail6("MLS_DEV_TEXT_ONLY");
5965
+ if (data.contentType !== void 0 && data.contentType !== (item ? item.type : "text") || !item && typeof data.text !== "string" || item && data.text !== void 0 && data.text !== null && typeof data.text !== "string" || data.mentions !== void 0 && (!Array.isArray(data.mentions) || data.mentions.some((mention) => typeof mention !== "string")) || data.replyTo !== void 0 && data.replyTo !== null && typeof data.replyTo !== "string" || ["replyToPosition", "metadata", "forwarded", "contactCardUserId"].some((key) => data[key] !== void 0 && data[key] !== null) || data.replyAuthority !== void 0 && (!!item || !data.replyAuthority || typeof data.replyAuthority !== "object" || Array.isArray(data.replyAuthority))) fail6("MLS_DEV_TEXT_ONLY");
5906
5966
  const mentions = [...new Set(data.mentions ?? [])].map(id);
5907
5967
  return {
5908
5968
  conversationId: id(data.conversationId),
5909
5969
  ...item ? { item } : { text: data.text },
5910
5970
  ...data.messageId === void 0 ? {} : { messageId: id(data.messageId) },
5971
+ ...data.replyAuthority === void 0 ? {} : { replyAuthority: data.replyAuthority },
5911
5972
  ...typeof data.replyTo === "string" ? { replyTo: id(data.replyTo) } : {},
5912
5973
  ...mentions.length ? { mentions } : {}
5913
5974
  };
@@ -5919,18 +5980,23 @@ function createMlsDevGroups(options, context, keyPackages, bridge) {
5919
5980
  return store.transaction(async (tx) => {
5920
5981
  const room = await tx.get(`conversation/${conversationId}`);
5921
5982
  const group = envelope((await readPreparationSnapshot(tx, scope4(conversationId))).protocolState, init.binding, conversationId);
5983
+ const missed = await tx.get(missedEarlierKey(conversationId)) === true ? { missedEarlier: true } : {};
5922
5984
  if (room?.mlsAdmissionHold !== true) {
5923
5985
  const joining = !!room && !!room.mls && (!group || group.removed);
5924
- return Object.freeze({ held: joining, waitingPrincipalIds: Object.freeze(joining ? [init.identity.principalId] : []) });
5986
+ return Object.freeze({ held: joining, waitingPrincipalIds: Object.freeze(joining ? [init.identity.principalId] : []), ...missed });
5925
5987
  }
5926
5988
  const accounts = mlsRoomAccounts(room, init.binding.environmentId);
5927
5989
  const admitted = new Set((group && !group.removed ? group.roster : []).map((entry) => mlsAccountOf(accounts, entry.device.principal)));
5928
- const waiting = [.../* @__PURE__ */ new Set([
5929
- ...Array.isArray(room.memberIds) ? room.memberIds : [],
5930
- ...typeof room.mlsInviteeId === "string" ? [room.mlsInviteeId] : [],
5931
- ...Array.isArray(room.mlsPendingFounderIds) ? room.mlsPendingFounderIds : []
5932
- ])].filter((memberId) => typeof memberId === "string" && !admitted.has(memberId));
5933
- return Object.freeze({ held: waiting.length > 0, waitingPrincipalIds: Object.freeze(waiting) });
5990
+ const ids = /* @__PURE__ */ __name((value) => Array.isArray(value) ? value.filter((entry) => typeof entry === "string") : [], "ids");
5991
+ const waiting = mlsAdmissionWait({
5992
+ senderId: init.identity.principalId,
5993
+ memberIds: ids(room.memberIds),
5994
+ admitted,
5995
+ unacceptedIds: [...ids([room.mlsInviteeId]), ...ids(room.mlsPendingFounderIds)],
5996
+ joinedAtMs: Number(room.mlsMemberJoinedAtMs),
5997
+ nowMs: context.clock()
5998
+ });
5999
+ return Object.freeze({ held: waiting.length > 0, waitingPrincipalIds: Object.freeze(waiting), ...missed });
5934
6000
  });
5935
6001
  }
5936
6002
  __name(admissionHold, "admissionHold");
@@ -5965,6 +6031,10 @@ function createMlsDevGroups(options, context, keyPackages, bridge) {
5965
6031
  const current = await capture(tx, state, init, cid), group = current.envelope;
5966
6032
  if (!isMlsDevTextProtocol(current.protocol)) fail6("MLS_DEV_TEXT_PROTOCOL_UNSUPPORTED");
5967
6033
  if ((input.replyTo || input.mentions || input.item) && current.protocol !== MLS_DEV_TEXT_PROTOCOL_V3) fail6("MLS_DEV_TEXT_ONLY");
6034
+ if (input.mentions) {
6035
+ const members = (await tx.get(`conversation/${cid}`))?.memberIds;
6036
+ if (!Array.isArray(members) || input.mentions.some((mention) => !members.includes(mention))) fail6("MLS_DEV_MENTION_NOT_MEMBER");
6037
+ }
5968
6038
  if (input.item && !canonItemReference(input.item, cid, init.binding.environmentId)) fail6("MLS_DEV_ITEM_INVALID");
5969
6039
  if (input.replyTo) {
5970
6040
  const quote = mlsDevReplyQuote(await tx.get(mlsDevTextMessageKey(cid, input.replyTo)), MLS_DEV_REPLY_QUOTE_MAX_CHARACTERS);
@@ -6038,6 +6108,7 @@ function createMlsDevGroups(options, context, keyPackages, bridge) {
6038
6108
  feedOrdinal: null
6039
6109
  });
6040
6110
  const media = prepared.text.kind === "item" ? { mediaUploadIds: [prepared.text.item.uploadId] } : {};
6111
+ const reply = input.replyAuthority ? { replyAuthority: input.replyAuthority } : {};
6041
6112
  return bridge.queue(tx, {
6042
6113
  operationId,
6043
6114
  installationId: init.identity.installationId,
@@ -6058,7 +6129,7 @@ function createMlsDevGroups(options, context, keyPackages, bridge) {
6058
6129
  operationId,
6059
6130
  installationId: init.identity.installationId,
6060
6131
  action: "mls_application",
6061
- body: { encoding: "json", value: { ...prepared.wire, ...media } },
6132
+ body: { encoding: "json", value: { ...prepared.wire, ...media, ...reply } },
6062
6133
  ...init.identity.ownershipVersion === void 0 ? {} : { ownershipVersion: init.identity.ownershipVersion }
6063
6134
  }
6064
6135
  });
@@ -6068,6 +6139,76 @@ function createMlsDevGroups(options, context, keyPackages, bridge) {
6068
6139
  return operation;
6069
6140
  }
6070
6141
  __name(sendText, "sendText");
6142
+ async function holdText(input, operationId) {
6143
+ const cid = input.conversationId;
6144
+ if (input.item || input.replyAuthority || typeof input.text !== "string") return sendText(input, operationId);
6145
+ const messageId = input.messageId ?? await bridge.hash(canonicalEndpointJson(["canon.held-text-message.v1", operationId]));
6146
+ const body = {
6147
+ conversationId: cid,
6148
+ messageId,
6149
+ text: input.text,
6150
+ ...input.replyTo ? { replyTo: input.replyTo } : {},
6151
+ ...input.mentions ? { mentions: input.mentions } : {}
6152
+ };
6153
+ const init = await initialized(), key = mlsDevHeldTextKey(cid, messageId);
6154
+ const kept = await store.transaction(async (tx) => {
6155
+ const existing = await tx.get(key);
6156
+ if (!existing) return await tx.get(`operation/${operationId}`) !== void 0 ? "made" : (await readMlsDevHeldTexts(tx, cid)).some((held) => !held.failed) ? "behind" : "none";
6157
+ if (existing.operationId !== operationId || canonicalEndpointJson(existing.body) !== canonicalEndpointJson(body)) fail6("OPERATION_ID_COLLISION");
6158
+ if (existing.failed) {
6159
+ const { failed: _failed, ...again } = existing;
6160
+ await tx.put(key, again);
6161
+ }
6162
+ return "kept";
6163
+ });
6164
+ if (kept === "kept") return void 0;
6165
+ if (kept !== "behind") {
6166
+ try {
6167
+ return await sendText({ ...input, messageId }, operationId);
6168
+ } catch (error) {
6169
+ if (kept === "made" || !(error instanceof EndpointError) || error.code !== "MLS_DEV_GROUP_ADMISSION_PENDING") throw error;
6170
+ }
6171
+ }
6172
+ validText(body.text);
6173
+ await store.transaction(async (tx) => {
6174
+ const held = await readMlsDevHeldTexts(tx, cid);
6175
+ if (held.length >= MLS_DEV_HELD_TEXT_MAX) fail6("MLS_DEV_HELD_TEXT_LIMIT");
6176
+ await tx.put(key, {
6177
+ version: 1,
6178
+ body,
6179
+ operationId,
6180
+ senderId: init.binding.principal.id,
6181
+ senderType: principal.type,
6182
+ createdAtMs: context.clock(),
6183
+ sequence: (held.at(-1)?.sequence ?? 0) + 1
6184
+ });
6185
+ });
6186
+ bridge.historyChanged?.(cid);
6187
+ return void 0;
6188
+ }
6189
+ __name(holdText, "holdText");
6190
+ async function sendHeldTexts(conversationId) {
6191
+ const sent = [], failed = [];
6192
+ const kept = (await store.transaction((tx) => readMlsDevHeldTexts(tx, conversationId))).filter((held) => !held.failed);
6193
+ for (const held of kept) {
6194
+ const key = mlsDevHeldTextKey(conversationId, held.body.messageId);
6195
+ try {
6196
+ sent.push(await sendText(textInput(held.body), held.operationId));
6197
+ } catch (error) {
6198
+ if (error instanceof EndpointError && ["ENDPOINT_CLOSED", "PREPARATION_INSTALLATION_FENCED"].includes(error.code)) throw error;
6199
+ if (!(error instanceof EndpointError) || error.retryable) return { sent, failed, waiting: true };
6200
+ await store.transaction(async (tx) => {
6201
+ if (await tx.get(key) !== void 0) await tx.put(key, { ...held, failed: error.code });
6202
+ });
6203
+ failed.push({ operationId: held.operationId, error });
6204
+ bridge.historyChanged?.(conversationId);
6205
+ continue;
6206
+ }
6207
+ await store.transaction((tx) => tx.delete(key));
6208
+ }
6209
+ return { sent, failed, waiting: false };
6210
+ }
6211
+ __name(sendHeldTexts, "sendHeldTexts");
6071
6212
  async function roomAccounts(tx, conversationId, init) {
6072
6213
  return mlsRoomAccounts(await tx.get(`conversation/${conversationId}`), init.binding.environmentId);
6073
6214
  }
@@ -6844,6 +6985,15 @@ function createMlsDevGroups(options, context, keyPackages, bridge) {
6844
6985
  const input = textInput(value), selectedId = id(operationId), selectedGuard = guardId === void 0 ? void 0 : id(guardId);
6845
6986
  return run(() => serializeInboundPreparation(store, input.conversationId, () => sendText(input, selectedId, selectedGuard)));
6846
6987
  },
6988
+ /** `sendText` for a person's composer: in a held room the text is kept, not refused (undefined), until `sendHeldTexts`. */
6989
+ holdText(value, operationId) {
6990
+ const input = textInput(value), selectedId = id(operationId);
6991
+ return run(() => serializeInboundPreparation(store, input.conversationId, () => holdText(input, selectedId)));
6992
+ },
6993
+ sendHeldTexts(conversationId) {
6994
+ const cid = id(conversationId);
6995
+ return run(() => serializeInboundPreparation(store, cid, () => sendHeldTexts(cid)));
6996
+ },
6847
6997
  /** A room this device holds caught up is read as it holds it: what fetches the room's new entries says so
6848
6998
  * (bridge.historyChanged). Otherwise a read fetches the feed first, which the polling fallback and the connected
6849
6999
  * stream's minute-long safety poll rely on. */
@@ -7127,6 +7277,7 @@ function createEndpoint(options) {
7127
7277
  clearTimeout(admissionTimers.get(conversationId));
7128
7278
  admissionTimers.delete(conversationId);
7129
7279
  scheduleAdmission(conversationId, 0);
7280
+ void releaseHeldTexts(conversationId);
7130
7281
  }, "controlSettled"),
7131
7282
  // Whatever fetched them (a hint, the reconnect sweep, a send, recovery), the room's local reads show them now.
7132
7283
  historyChanged: /* @__PURE__ */ __name((conversationId) => queryObserver.invalidate((query, parameters) => options.localInboxPreviews && query === "list_conversations" || parameters.conversationId === conversationId && ["list_messages", "get_messages", "get_runtime_card_state"].includes(query)), "historyChanged"),
@@ -7497,6 +7648,86 @@ function createEndpoint(options) {
7497
7648
  }
7498
7649
  }
7499
7650
  __name(sendDeferredOpeners, "sendDeferredOpeners");
7651
+ const heldWaiters = /* @__PURE__ */ new Map();
7652
+ const heldRooms = /* @__PURE__ */ new Set(), heldTimers = /* @__PURE__ */ new Map(), heldDelays = /* @__PURE__ */ new Map();
7653
+ const releasingHeld = /* @__PURE__ */ new Set(), releaseAgain = /* @__PURE__ */ new Set();
7654
+ function settleHeldWaiters(operationId, settle) {
7655
+ const waiters = heldWaiters.get(operationId) ?? [];
7656
+ heldWaiters.delete(operationId);
7657
+ for (const waiter of waiters) settle(waiter);
7658
+ }
7659
+ __name(settleHeldWaiters, "settleHeldWaiters");
7660
+ function scheduleHeldTexts(conversationId) {
7661
+ if (closed || heldTimers.has(conversationId)) return;
7662
+ const delay = heldDelays.get(conversationId) ?? 3e3;
7663
+ heldDelays.set(conversationId, Math.min(delay * 2, 3e4));
7664
+ const timer = setTimeout(() => {
7665
+ heldTimers.delete(conversationId);
7666
+ void releaseHeldTexts(conversationId);
7667
+ }, delay);
7668
+ timer.unref?.();
7669
+ heldTimers.set(conversationId, timer);
7670
+ }
7671
+ __name(scheduleHeldTexts, "scheduleHeldTexts");
7672
+ async function releaseHeldTexts(conversationId) {
7673
+ if (!mlsDevGroups || closed || !heldRooms.has(conversationId)) return;
7674
+ if (releasingHeld.has(conversationId)) {
7675
+ releaseAgain.add(conversationId);
7676
+ return;
7677
+ }
7678
+ releasingHeld.add(conversationId);
7679
+ try {
7680
+ let waiting;
7681
+ do {
7682
+ releaseAgain.delete(conversationId);
7683
+ const result = await mlsDevGroups.sendHeldTexts(conversationId);
7684
+ for (const operation of result.sent) settleHeldWaiters(operation.operationId, (waiter) => waiter.resolve(operation));
7685
+ for (const { operationId, error } of result.failed) settleHeldWaiters(operationId, (waiter) => waiter.reject(error));
7686
+ waiting = result.waiting;
7687
+ } while (releaseAgain.has(conversationId));
7688
+ if (waiting) scheduleHeldTexts(conversationId);
7689
+ else {
7690
+ heldRooms.delete(conversationId);
7691
+ heldDelays.delete(conversationId);
7692
+ clearTimeout(heldTimers.get(conversationId));
7693
+ heldTimers.delete(conversationId);
7694
+ }
7695
+ } catch (error) {
7696
+ if (!(error instanceof EndpointError) || !["ENDPOINT_CLOSED", "PREPARATION_INSTALLATION_FENCED"].includes(error.code)) scheduleHeldTexts(conversationId);
7697
+ } finally {
7698
+ releasingHeld.delete(conversationId);
7699
+ }
7700
+ }
7701
+ __name(releaseHeldTexts, "releaseHeldTexts");
7702
+ function closeHeldTexts() {
7703
+ for (const timer of heldTimers.values()) clearTimeout(timer);
7704
+ heldTimers.clear();
7705
+ const closedError = new EndpointError("Endpoint is closed", { code: "ENDPOINT_CLOSED", retryable: false });
7706
+ for (const operationId of [...heldWaiters.keys()]) settleHeldWaiters(operationId, (waiter) => waiter.reject(closedError));
7707
+ }
7708
+ __name(closeHeldTexts, "closeHeldTexts");
7709
+ async function sendTextWhenReady(body, operationId) {
7710
+ const queued = await mlsDevGroups.holdText(body, operationId);
7711
+ if (queued) return queued;
7712
+ const conversationId = body.conversationId;
7713
+ const sent = new Promise((resolve3, reject) => {
7714
+ heldWaiters.set(operationId, [...heldWaiters.get(operationId) ?? [], { resolve: resolve3, reject }]);
7715
+ });
7716
+ heldRooms.add(conversationId);
7717
+ void releaseHeldTexts(conversationId);
7718
+ return sent;
7719
+ }
7720
+ __name(sendTextWhenReady, "sendTextWhenReady");
7721
+ async function resumeHeldTexts() {
7722
+ for (let after; ; ) {
7723
+ const page = await store.transaction((tx) => tx.scan(mlsDevHeldTextPrefix(), { limit: 100, ...after ? { after } : {} }));
7724
+ for (const row of page) if (!row.value.failed) heldRooms.add(row.value.body.conversationId);
7725
+ if (page.length < 100) break;
7726
+ after = page[page.length - 1].key;
7727
+ }
7728
+ for (const conversationId of heldRooms) void releaseHeldTexts(conversationId);
7729
+ }
7730
+ __name(resumeHeldTexts, "resumeHeldTexts");
7500
7731
  const stubReads = /* @__PURE__ */ new Map();
7501
7732
  const stubNames = /* @__PURE__ */ new Map();
7502
7733
  let liveActionCounter = 0;
@@ -7601,6 +7832,7 @@ function createEndpoint(options) {
7601
7832
  await mlsDevGroups.commitHeldRemoval(conversationId).catch((error) => {
7602
7833
  if (!(error instanceof EndpointError) || ["ENDPOINT_CLOSED", "PREPARATION_INSTALLATION_FENCED"].includes(error.code)) throw error;
7603
7834
  });
7835
+ void releaseHeldTexts(conversationId);
7604
7836
  }
7605
7837
  return true;
7606
7838
  }
@@ -8117,6 +8349,7 @@ function createEndpoint(options) {
8117
8349
  if (mode.required) {
8118
8350
  if (!isMlsDevTextProtocol(mode.protocol) || !mlsDevGroups) throw new EndpointError("Encrypted text is unavailable for this room", { code: "MLS_DEV_ENCRYPTED_CONTENT_REQUIRED", retryable: false });
8119
8351
  if (executeOptions.sensitive || authored.envelope || authored.preconditions) throw new EndpointError("Unsupported encrypted text fields", { code: "INVALID_MLS_DEV_TEXT_INPUT", retryable: false });
8352
+ if (executeOptions.waitWhileHeld && !executeOptions.guardId) return sendTextWhenReady(authored.body, operationId);
8120
8353
  return mlsDevGroups.sendText(authored.body, operationId, executeOptions.guardId);
8121
8354
  }
8122
8355
  }
@@ -8741,6 +8974,7 @@ function createEndpoint(options) {
8741
8974
  admissionTimers.clear();
8742
8975
  for (const timer of openerTimers.values()) clearTimeout(timer);
8743
8976
  openerTimers.clear();
8977
+ closeHeldTexts();
8744
8978
  queryObserver.close();
8745
8979
  listeners.clear();
8746
8980
  closing = (async () => {
@@ -8763,6 +8997,7 @@ function createEndpoint(options) {
8763
8997
  admissionTimers.clear();
8764
8998
  for (const timer of openerTimers.values()) clearTimeout(timer);
8765
8999
  openerTimers.clear();
9000
+ closeHeldTexts();
8766
9001
  queryObserver.close();
8767
9002
  sensitiveRequests.clear();
8768
9003
  sensitiveFingerprints.clear();
@@ -8774,6 +9009,7 @@ function createEndpoint(options) {
8774
9009
  registerArtifactEndpoint(endpoint, { store, closed: /* @__PURE__ */ __name(() => closed, "closed"), assertCurrent: assertCurrentInstallation });
8775
9010
  void initialization.then(() => scheduleRetry(0)).catch(() => void 0);
8776
9011
  void initialization.then(() => sendDeferredOpeners()).catch(() => void 0);
9012
+ if (mlsDevGroups) void initialization.then(() => resumeHeldTexts()).catch(() => void 0);
8777
9013
  return endpoint;
8778
9014
  }
8779
9015
  __name(createEndpoint, "createEndpoint");
@@ -8828,7 +9064,7 @@ async function acquireAgentEndpoint(input) {
8828
9064
  }
8829
9065
  const requestedMls = input.options.mlsDev;
8830
9066
  const mls = requestedMls ? await (async () => {
8831
- const crypto2 = await import("./mls-crypto-4DZT2UFH.js");
9067
+ const crypto2 = await import("./mls-crypto-NZVMSUFU.js");
8832
9068
  const configuration = crypto2.parseMlsDevClientConfiguration(requestedMls.configuration);
8833
9069
  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");
8834
9070
  const custody = requestedMls.custody;
@@ -9433,7 +9669,7 @@ var CanonClient = class {
9433
9669
  return result.status === "unavailable" && result.reason === "opening-text-required" ? void 0 : result;
9434
9670
  }
9435
9671
  async blockUser(userId) {
9436
- await this.endpointAction("set_block", { userId, blocked: true });
9672
+ await this.endpointAction("set_block", { targetUserId: userId, blocked: true });
9437
9673
  }
9438
9674
  async deleteMessage(conversationId, messageId) {
9439
9675
  await this.endpointAction("delete_message", { conversationId, messageId });
@@ -10330,1028 +10566,14 @@ async function ensureAgentProfile(options) {
10330
10566
  }
10331
10567
  __name(ensureAgentProfile, "ensureAgentProfile");
10332
10568
 
10333
- // ../core/src/runtime-cards.ts
10334
- var FIELD_TYPE_SET = new Set(RUNTIME_CARD_FIELD_TYPES);
10335
- var LINE_ITEM_COLUMN_TYPE_SET = new Set(RUNTIME_CARD_LINE_ITEM_COLUMN_TYPES);
10336
- function buildRuntimeInputOutcome(inputId, status, details) {
10337
- return {
10338
- text: status === "timeout" ? "Input request expired" : status === "cancelled" ? "Input request cancelled" : "Input request resolved",
10339
- metadata: {
10340
- type: "runtime_input_outcome",
10341
- inputId,
10342
- ...details?.kind ? { kind: details.kind } : {},
10343
- status,
10344
- ...details?.reason ? { reason: details.reason } : {}
10345
- }
10346
- };
10347
- }
10348
- __name(buildRuntimeInputOutcome, "buildRuntimeInputOutcome");
10349
- function buildRuntimeCardOutcome(cardId, status, details) {
10350
- return {
10351
- text: status === "timeout" ? "Card request expired" : status === "cancelled" ? "Card request cancelled" : "Card request resolved",
10352
- metadata: {
10353
- type: "runtime_card_outcome",
10354
- cardId,
10355
- status,
10356
- ...details?.reason ? { reason: details.reason } : {},
10357
- turnSemantics: "control",
10358
- replyBehavior: "suppress_auto_reply"
10359
- }
10360
- };
10361
- }
10362
- __name(buildRuntimeCardOutcome, "buildRuntimeCardOutcome");
10363
-
10364
- // ../core/src/runtime-request-manager.ts
10365
- var RuntimeRequestCancelledError = class extends Error {
10366
- static {
10367
- __name(this, "RuntimeRequestCancelledError");
10368
- }
10369
- constructor(message = "Runtime request cancelled") {
10370
- super(message);
10371
- this.name = "RuntimeRequestCancelledError";
10372
- }
10373
- };
10374
- var DEFAULT_TIMEOUT_MS = 3e5;
10375
- var DEFAULT_POLL_MS = 1e3;
10376
- function pendingKey(kind, conversationId, requestId) {
10377
- return `${kind}::${conversationId}::${requestId}`;
10378
- }
10379
- __name(pendingKey, "pendingKey");
10380
- var RuntimeRequestManager = class {
10381
- static {
10382
- __name(this, "RuntimeRequestManager");
10383
- }
10384
- client;
10385
- ctx;
10386
- descriptors = /* @__PURE__ */ new Map();
10387
- pending = /* @__PURE__ */ new Map();
10388
- rules = [];
10389
- constructor(client, ctx) {
10390
- this.client = client;
10391
- this.ctx = ctx;
10392
- this.registerBuiltins();
10393
- }
10394
- /** Register (or override) the descriptor for a kind. */
10395
- register(kind, descriptor) {
10396
- this.descriptors.set(kind, descriptor);
10397
- }
10398
- /** Built-in input, card, and plan descriptors. Subclasses add approval policy. */
10399
- registerBuiltins() {
10400
- this.register("input", runtimeInputDescriptor);
10401
- this.register("card", runtimeCardDescriptor);
10402
- this.register("plan", runtimePlanDescriptor);
10403
- }
10404
- get pendingCount() {
10405
- return this.pending.size;
10406
- }
10407
- /**
10408
- * Run one interactive runtime request end to end:
10409
- * preflight → create → onCreated → register pending → poll (racing push wakeup
10410
- * and abort) → post-deadline consume → finalize → onOutcome → resolve.
10411
- */
10412
- async request(kind, conversationId, payload, options = {}) {
10413
- const descriptor = this.descriptors.get(kind);
10414
- if (!descriptor) {
10415
- throw new Error(`No runtime-request descriptor registered for kind '${kind}'`);
10416
- }
10417
- if (options.signal?.aborted) {
10418
- throw new RuntimeRequestCancelledError();
10419
- }
10420
- const pre = descriptor.preflight?.({
10421
- client: this.client,
10422
- ctx: this.ctx,
10423
- conversationId,
10424
- payload,
10425
- options
10426
- });
10427
- if (pre != null) return pre;
10428
- const requestId = options.requestId ?? descriptor.generateId(payload);
10429
- const expiresAt = descriptor.resolveExpiry?.({ payload, options }) ?? options.expiresAt ?? Date.now() + (options.timeoutMs ?? DEFAULT_TIMEOUT_MS);
10430
- const pollMs = descriptor.pollMs?.(payload) ?? options.pollMs ?? DEFAULT_POLL_MS;
10431
- const created = await descriptor.create({
10432
- client: this.client,
10433
- ctx: this.ctx,
10434
- conversationId,
10435
- requestId,
10436
- payload,
10437
- expiresAt,
10438
- state: void 0,
10439
- options
10440
- });
10441
- const effectiveId = created.requestId || requestId;
10442
- const effectiveExpiry = created.expiresAt ?? expiresAt;
10443
- const state = created.state;
10444
- const base = {
10445
- client: this.client,
10446
- ctx: this.ctx,
10447
- conversationId,
10448
- requestId: effectiveId,
10449
- payload,
10450
- expiresAt: effectiveExpiry,
10451
- state
10452
- };
10453
- if (options.signal?.aborted) {
10454
- await descriptor.cancel?.(base).catch(() => {
10455
- });
10456
- throw signalReason(options.signal) ?? new RuntimeRequestCancelledError();
10457
- }
10458
- if (options.onCreated) {
10459
- await options.onCreated({
10460
- requestId: effectiveId,
10461
- expiresAt: effectiveExpiry,
10462
- ...created.messageId !== void 0 ? { messageId: created.messageId } : {},
10463
- ...created.interactive !== void 0 ? { interactive: created.interactive } : {}
10464
- });
10465
- }
10466
- if (options.signal?.aborted) {
10467
- await descriptor.cancel?.(base).catch(() => {
10468
- });
10469
- throw signalReason(options.signal) ?? new RuntimeRequestCancelledError();
10470
- }
10471
- const key = pendingKey(kind, conversationId, effectiveId);
10472
- const signal = options.signal;
10473
- return await new Promise((resolve3, reject) => {
10474
- let settled = false;
10475
- const finishCommon = /* @__PURE__ */ __name(() => {
10476
- settled = true;
10477
- entry.wake();
10478
- clearTimeout(entry.timer);
10479
- this.pending.delete(key);
10480
- if (signal) signal.removeEventListener("abort", onAbort);
10481
- }, "finishCommon");
10482
- const settle = /* @__PURE__ */ __name((result, reason) => {
10483
- if (settled) return;
10484
- finishCommon();
10485
- void (async () => {
10486
- let finalResult = result;
10487
- try {
10488
- const override = descriptor.finalize?.({ ...base, result, reason });
10489
- if (override !== void 0) finalResult = override;
10490
- } catch {
10491
- }
10492
- if (options.onOutcome && descriptor.buildOutcome) {
10493
- try {
10494
- const env = descriptor.buildOutcome({ ...base, result: finalResult, reason });
10495
- if (env) await options.onOutcome(env);
10496
- } catch {
10497
- }
10498
- }
10499
- resolve3(finalResult);
10500
- })();
10501
- }, "settle");
10502
- const abort = /* @__PURE__ */ __name((error) => {
10503
- if (settled) return;
10504
- finishCommon();
10505
- void descriptor.cancel?.(base).catch(() => {
10506
- });
10507
- reject(error);
10508
- }, "abort");
10509
- const onAbort = /* @__PURE__ */ __name(() => abort(signalReason(signal) ?? new RuntimeRequestCancelledError()), "onAbort");
10510
- const entry = {
10511
- kind,
10512
- conversationId,
10513
- requestId: effectiveId,
10514
- timer: setTimeout(
10515
- () => settle(descriptor.timeoutResult(base), "timeout"),
10516
- Math.max(0, effectiveExpiry - Date.now())
10517
- ),
10518
- wake: /* @__PURE__ */ __name(() => {
10519
- }, "wake"),
10520
- wakeRequested: false,
10521
- settle: /* @__PURE__ */ __name((result, reason) => settle(result, reason), "settle"),
10522
- abort
10523
- };
10524
- this.pending.set(key, entry);
10525
- if (signal) signal.addEventListener("abort", onAbort, { once: true });
10526
- const pollOnce = /* @__PURE__ */ __name(async () => {
10527
- try {
10528
- const outcome = await descriptor.consume(base);
10529
- if (settled) return true;
10530
- if (outcome.state === "resolved") {
10531
- settle(outcome.result, "replied");
10532
- return true;
10533
- }
10534
- if (outcome.state === "timeout") {
10535
- settle(descriptor.timeoutResult(base), "timeout");
10536
- return true;
10537
- }
10538
- } catch {
10539
- }
10540
- return false;
10541
- }, "pollOnce");
10542
- const poll = /* @__PURE__ */ __name(async () => {
10543
- while (!settled && Date.now() < effectiveExpiry) {
10544
- if (await pollOnce()) return;
10545
- if (settled) return;
10546
- await this.sleep(key, Math.min(pollMs, Math.max(1, effectiveExpiry - Date.now())));
10547
- }
10548
- if (settled) return;
10549
- if (await pollOnce()) return;
10550
- if (!settled) settle(descriptor.timeoutResult(base), "timeout");
10551
- }, "poll");
10552
- void poll();
10553
- });
10554
- }
10555
- /** Push short-circuit: wake the pending request's poll to consume immediately. */
10556
- notify(kind, conversationId, requestId) {
10557
- const entry = this.pending.get(pendingKey(kind, conversationId, requestId));
10558
- if (!entry) return;
10559
- entry.wakeRequested = true;
10560
- entry.wake();
10561
- }
10562
- /**
10563
- * Feed an inbound message to the manager. Returns true when a matching pending
10564
- * request was woken to consume canonical state. Receipt metadata never settles
10565
- * the request directly. Fails closed on unknown / mismatched ids.
10566
- */
10567
- handleMessage(conversationId, message) {
10568
- if (this.pending.size === 0) return false;
10569
- for (const descriptor of this.descriptors.values()) {
10570
- const match = descriptor.matchPush?.({ conversationId, message, ctx: this.ctx });
10571
- if (!match) continue;
10572
- const entry = this.pending.get(pendingKey(descriptor.kind, conversationId, match.requestId));
10573
- if (!entry) return false;
10574
- entry.wakeRequested = true;
10575
- entry.wake();
10576
- return true;
10577
- }
10578
- return false;
10579
- }
10580
- /** Cancel a specific pending request (rejects its promise, best-effort server cancel). */
10581
- async cancel(kind, conversationId, requestId) {
10582
- this.pending.get(pendingKey(kind, conversationId, requestId))?.abort(new RuntimeRequestCancelledError());
10583
- }
10584
- // ── Session rules (approval descriptor uses these) ─────────────────
10585
- checkSessionRules(toolName, conversationId) {
10586
- this.pruneExpiredRules();
10587
- for (const stored of this.rules) {
10588
- if (conversationId && stored.conversationId !== conversationId) continue;
10589
- const { rule } = stored;
10590
- if (rule.type === "approve-all") return rule;
10591
- if ((rule.type === "approve-tool" || rule.type === "deny-tool") && rule.toolPattern) {
10592
- try {
10593
- const re = new RegExp(`^${rule.toolPattern}$`, "i");
10594
- if (re.test(toolName)) return rule;
10595
- } catch {
10596
- continue;
10597
- }
10598
- }
10599
- }
10600
- return null;
10601
- }
10602
- addSessionRule(conversationId, rule) {
10603
- this.rules.push({ conversationId, rule });
10604
- }
10605
- getSessionRules() {
10606
- this.pruneExpiredRules();
10607
- return this.rules.map((stored) => stored.rule);
10608
- }
10609
- clearSessionRules() {
10610
- this.rules = [];
10611
- }
10612
- dispose() {
10613
- for (const entry of this.pending.values()) {
10614
- clearTimeout(entry.timer);
10615
- }
10616
- this.pending.clear();
10617
- this.rules = [];
10618
- }
10619
- pruneExpiredRules() {
10620
- const now = Date.now();
10621
- this.rules = this.rules.filter(({ rule }) => {
10622
- if (!rule.expiresAt) return true;
10623
- return new Date(rule.expiresAt).getTime() > now;
10624
- });
10625
- }
10626
- /** Sleep that resolves on timeout, on `notify()` wakeup, or when the request settles. */
10627
- sleep(key, ms) {
10628
- const pending = this.pending.get(key);
10629
- if (pending?.wakeRequested) {
10630
- pending.wakeRequested = false;
10631
- return Promise.resolve();
10632
- }
10633
- return new Promise((resolve3) => {
10634
- let done = false;
10635
- const finish = /* @__PURE__ */ __name(() => {
10636
- if (done) return;
10637
- done = true;
10638
- clearTimeout(timer);
10639
- const current = this.pending.get(key);
10640
- if (current) {
10641
- current.wakeRequested = false;
10642
- current.wake = () => {
10643
- };
10644
- }
10645
- resolve3();
10646
- }, "finish");
10647
- const timer = setTimeout(finish, ms);
10648
- const entry = this.pending.get(key);
10649
- if (entry) entry.wake = finish;
10650
- });
10651
- }
10652
- };
10653
- function signalReason(signal) {
10654
- const reason = signal?.reason;
10655
- return reason instanceof Error ? reason : void 0;
10656
- }
10657
- __name(signalReason, "signalReason");
10658
- function resolveResponder(explicit, ownerId, policy) {
10659
- if (explicit) return explicit;
10660
- return policy === "owner" ? ownerId || void 0 : void 0;
10661
- }
10662
- __name(resolveResponder, "resolveResponder");
10663
- var inputSeq = 0;
10664
- var cardSeq = 0;
10665
- var planSeq = 0;
10666
- var runtimeInputDescriptor = {
10667
- kind: "input",
10668
- generateId(payload) {
10669
- return `${payload.kind}_${Date.now().toString(36)}${(++inputSeq).toString(36)}`;
10670
- },
10671
- async create({ client, ctx, conversationId, requestId, payload, expiresAt, options }) {
10672
- const responder = resolveResponder(payload.responseUserId, ctx.ownerId, options.responderPolicy ?? "owner");
10673
- const created = await client.createRuntimeInputRequest({
10674
- conversationId,
10675
- inputId: requestId,
10676
- kind: payload.kind,
10677
- expiresAt,
10678
- ...responder !== void 0 ? { responseUserId: responder } : {},
10679
- ...payload.title !== void 0 ? { title: payload.title } : {},
10680
- ...payload.prompt !== void 0 ? { prompt: payload.prompt } : {},
10681
- ...payload.choices ? { choices: payload.choices } : {},
10682
- ...payload.questions ? { questions: payload.questions } : {},
10683
- ...payload.secretName ? { secretName: payload.secretName } : {},
10684
- ...payload.native ? { native: payload.native } : {},
10685
- ...payload.sensitive !== void 0 ? { sensitive: payload.sensitive } : {},
10686
- ...payload.turnId ? { turnId: payload.turnId } : {}
10687
- });
10688
- return { requestId, ...created.messageId !== void 0 ? { messageId: created.messageId } : {} };
10689
- },
10690
- async consume({ client, conversationId, requestId }) {
10691
- const response = await client.consumeRuntimeInputResponse({ conversationId, inputId: requestId });
10692
- if (response.status === "submitted") {
10693
- return {
10694
- state: "resolved",
10695
- result: {
10696
- status: "submitted",
10697
- inputId: requestId,
10698
- value: response.value,
10699
- ...response.answers ? { answers: response.answers } : {}
10700
- }
10701
- };
10702
- }
10703
- if (response.status === "cancelled") {
10704
- return { state: "resolved", result: { status: "cancelled", inputId: requestId } };
10705
- }
10706
- if (response.status === "timeout") return { state: "timeout" };
10707
- return { state: "pending" };
10708
- },
10709
- async cancel({ client, conversationId, requestId }) {
10710
- await client.consumeRuntimeInputResponse({ conversationId, inputId: requestId, cancel: true });
10711
- },
10712
- timeoutResult({ requestId }) {
10713
- return { status: "timeout", inputId: requestId };
10714
- },
10715
- buildOutcome({ payload, result }) {
10716
- const outcome = buildRuntimeInputOutcome(result.inputId, result.status, {
10717
- kind: payload.kind,
10718
- reason: result.status
10719
- });
10720
- return { text: outcome.text, metadata: outcome.metadata };
10721
- }
10722
- };
10723
- var runtimeCardDescriptor = {
10724
- kind: "card",
10725
- generateId(payload) {
10726
- return payload.card.cardId ?? `card_${Date.now().toString(36)}${(++cardSeq).toString(36)}`;
10727
- },
10728
- async create({ client, ctx, conversationId, requestId, payload, expiresAt, options }) {
10729
- const responder = resolveResponder(payload.responseUserId, ctx.ownerId, options.responderPolicy ?? "infer");
10730
- const created = await client.createRuntimeCardRequest({
10731
- conversationId,
10732
- // Embed cardId into the card body (as hosts do) so the wire shape is
10733
- // byte-identical whether a host folds onto this built-in or not.
10734
- card: { ...payload.card, cardId: requestId },
10735
- cardId: requestId,
10736
- expiresAt,
10737
- ...responder !== void 0 ? { responseUserId: responder } : {},
10738
- ...payload.runtimeId ? { runtimeId: payload.runtimeId } : {},
10739
- ...payload.turnId ? { turnId: payload.turnId } : {},
10740
- ...payload.native ? { native: payload.native } : {}
10741
- });
10742
- return {
10743
- requestId: created.cardId ?? requestId,
10744
- interactive: created.interactive,
10745
- ...created.expiresAt ? { expiresAt: created.expiresAt } : {},
10746
- ...created.messageId !== void 0 ? { messageId: created.messageId } : {}
10747
- };
10748
- },
10749
- async consume({ client, conversationId, requestId }) {
10750
- const response = await client.consumeRuntimeCardResponse({ conversationId, cardId: requestId });
10751
- if (response.status === "submitted") {
10752
- return {
10753
- state: "resolved",
10754
- result: {
10755
- status: "submitted",
10756
- cardId: requestId,
10757
- ...response.actionId ? { actionId: response.actionId } : {},
10758
- ...response.values ? { values: response.values } : {},
10759
- ...response.respondedBy ? { respondedBy: response.respondedBy } : {}
10760
- }
10761
- };
10762
- }
10763
- if (response.status === "cancelled") {
10764
- return {
10765
- state: "resolved",
10766
- result: {
10767
- status: "cancelled",
10768
- cardId: requestId,
10769
- ...response.respondedBy ? { respondedBy: response.respondedBy } : {}
10770
- }
10771
- };
10772
- }
10773
- if (response.status === "timeout") return { state: "timeout" };
10774
- return { state: "pending" };
10775
- },
10776
- async cancel({ client, conversationId, requestId }) {
10777
- await client.consumeRuntimeCardResponse({ conversationId, cardId: requestId, cancel: true });
10778
- },
10779
- timeoutResult({ requestId }) {
10780
- return { status: "timeout", cardId: requestId };
10781
- },
10782
- buildOutcome({ result }) {
10783
- const status = result.status;
10784
- const outcome = buildRuntimeCardOutcome(result.cardId, status, { reason: status });
10785
- return { text: outcome.text, metadata: outcome.metadata };
10786
- }
10787
- };
10788
- var runtimePlanDescriptor = {
10789
- kind: "plan",
10790
- generateId() {
10791
- return `plan_${Date.now().toString(36)}${(++planSeq).toString(36)}`;
10792
- },
10793
- async create({ client, ctx, conversationId, requestId, payload, expiresAt, options }) {
10794
- const responder = resolveResponder(payload.responseUserId, ctx.ownerId, options.responderPolicy ?? "infer");
10795
- const created = await client.createRuntimePlanRequest({
10796
- conversationId,
10797
- planId: requestId,
10798
- expiresAt,
10799
- ...responder !== void 0 ? { responseUserId: responder } : {},
10800
- ...payload.title !== void 0 ? { title: payload.title } : {},
10801
- ...payload.summary !== void 0 ? { summary: payload.summary } : {},
10802
- ...payload.body !== void 0 ? { body: payload.body } : {},
10803
- ...payload.allowedPrompts ? { allowedPrompts: payload.allowedPrompts } : {},
10804
- ...payload.turnId ? { turnId: payload.turnId } : {}
10805
- });
10806
- return {
10807
- requestId: created.planId ?? requestId,
10808
- expiresAt: created.expiresAt,
10809
- ...created.messageId !== void 0 ? { messageId: created.messageId } : {}
10810
- };
10811
- },
10812
- async consume({ client, conversationId, requestId }) {
10813
- const response = await client.consumeRuntimePlanResponse({ conversationId, planId: requestId });
10814
- if (response.status === "approve" || response.status === "revise" || response.status === "reject") {
10815
- return {
10816
- state: "resolved",
10817
- result: {
10818
- status: response.status,
10819
- planId: requestId,
10820
- ...response.feedback ? { feedback: response.feedback } : {},
10821
- ...response.grantedPrompts ? { grantedPrompts: response.grantedPrompts } : {},
10822
- ...response.receiptId ? { receiptId: response.receiptId } : {}
10823
- }
10824
- };
10825
- }
10826
- if (response.status === "cancelled") {
10827
- return { state: "resolved", result: { status: "cancelled", planId: requestId } };
10828
- }
10829
- if (response.status === "timeout") return { state: "timeout" };
10830
- return { state: "pending" };
10831
- },
10832
- async cancel({ client, conversationId, requestId }) {
10833
- await client.consumeRuntimePlanResponse({ conversationId, planId: requestId, cancel: true });
10834
- },
10835
- timeoutResult({ requestId }) {
10836
- return { status: "timeout", planId: requestId };
10837
- },
10838
- matchPush({ message }) {
10839
- if (message.metadata?.type !== "plan_approval_reply") return null;
10840
- const requestId = message.metadata.planId;
10841
- return typeof requestId === "string" ? { requestId } : null;
10842
- }
10843
- };
10844
-
10845
- // ../core/src/approval-types.ts
10846
- var DEFAULT_APPROVAL_CONFIG = {
10847
- enabled: true,
10848
- timeoutSeconds: 300,
10849
- defaultOnTimeout: "deny",
10850
- // Applied to every host's approval previews via ApprovalManager. Covers both
10851
- // key=value assignments and common bare-credential shapes (bearer headers,
10852
- // provider keys, cloud keys, PEM blocks, credentialed URLs) so a redacted
10853
- // command preview does not leak secrets that the narrow key=value rule misses.
10854
- // Order matters: redactSecrets applies patterns sequentially, so specific
10855
- // credential shapes run BEFORE the broad key=value rule — otherwise the kv
10856
- // rule would consume "Authorization: Bearer" and leave the opaque token.
10857
- // Bare-credential shapes carry a left boundary so ordinary tokens whose tail
10858
- // happens to embed a prefix (risk-assessment…, disk-cleanup…) are not mangled
10859
- // in the command preview the owner must read before approving.
10860
- redactPatterns: [
10861
- "(?<![A-Za-z0-9])bearer\\s+[A-Za-z0-9._~+/=-]{12,}",
10862
- "(?<![A-Za-z0-9])sk-[A-Za-z0-9_-]{16,}",
10863
- "(?<![A-Za-z0-9])AKIA[0-9A-Z]{16}",
10864
- "(?<![A-Za-z0-9])gh[posru]_[A-Za-z0-9]{20,}",
10865
- "(?<![A-Za-z0-9])xox[baprs]-[A-Za-z0-9-]{10,}",
10866
- "(?<![A-Za-z0-9])eyJ[A-Za-z0-9_-]{8,}\\.[A-Za-z0-9_-]{8,}\\.[A-Za-z0-9_-]{8,}",
10867
- "-----BEGIN [A-Z ]*PRIVATE KEY-----[\\s\\S]*?-----END [A-Z ]*PRIVATE KEY-----",
10868
- "://[^\\s:@/]+:[^\\s:@/]+@",
10869
- "(?:api[_-]?key|access[_-]?token|client[_-]?secret|secret|token|password|passwd|pwd|authorization|auth[_-]?token|private[_-]?key)\\s*[:=]\\s*\\S+"
10870
- ]
10871
- };
10872
- function isRecord2(value) {
10873
- return Boolean(value && typeof value === "object" && !Array.isArray(value));
10874
- }
10875
- __name(isRecord2, "isRecord");
10876
- function normalizeString(value, maxLength) {
10877
- if (typeof value !== "string") return null;
10878
- const trimmed = value.trim();
10879
- if (!trimmed || trimmed.length > maxLength) return null;
10880
- return trimmed;
10881
- }
10882
- __name(normalizeString, "normalizeString");
10883
- function parseSessionRule(value) {
10884
- if (!isRecord2(value)) return null;
10885
- const type = value.type;
10886
- if (type !== "approve-all" && type !== "approve-tool" && type !== "deny-tool") {
10887
- return null;
10888
- }
10889
- let expiresAt;
10890
- if (value.expiresAt !== void 0) {
10891
- if (value.expiresAt === null) {
10892
- expiresAt = null;
10893
- } else {
10894
- const normalizedExpiresAt = normalizeString(value.expiresAt, 128);
10895
- if (!normalizedExpiresAt) return null;
10896
- const expiresMs = Date.parse(normalizedExpiresAt);
10897
- if (!Number.isFinite(expiresMs) || expiresMs <= Date.now()) return null;
10898
- expiresAt = new Date(expiresMs).toISOString();
10899
- }
10900
- }
10901
- if (type === "approve-all") {
10902
- return {
10903
- type,
10904
- ...expiresAt !== void 0 ? { expiresAt } : {}
10905
- };
10906
- }
10907
- const toolPattern = normalizeString(value.toolPattern, 128);
10908
- if (!toolPattern || !/^[\w.*:-]+$/.test(toolPattern)) {
10909
- return null;
10910
- }
10911
- return {
10912
- type,
10913
- toolPattern,
10914
- ...expiresAt !== void 0 ? { expiresAt } : {}
10915
- };
10916
- }
10917
- __name(parseSessionRule, "parseSessionRule");
10918
- function parseApprovalReplyMetadata(value) {
10919
- if (!isRecord2(value) || value.type !== "approval_reply") return null;
10920
- const approvalId = normalizeString(value.approvalId, 128);
10921
- if (!approvalId) return null;
10922
- const decision = value.decision;
10923
- if (decision !== "allow" && decision !== "deny") return null;
10924
- let sessionRule;
10925
- if (value.sessionRule !== void 0) {
10926
- const parsed = parseSessionRule(value.sessionRule);
10927
- if (!parsed) return null;
10928
- sessionRule = parsed;
10929
- }
10930
- return {
10931
- type: "approval_reply",
10932
- approvalId,
10933
- decision,
10934
- ...sessionRule ? { sessionRule } : {}
10935
- };
10936
- }
10937
- __name(parseApprovalReplyMetadata, "parseApprovalReplyMetadata");
10938
-
10939
10569
  // ../core/src/unified-diff.ts
10940
10570
  var UNIFIED_DIFF_MAX_TOTAL_BYTES = 96 * 1024;
10941
10571
  var UNIFIED_DIFF_MAX_FILE_BYTES = 24 * 1024;
10942
- var UNIFIED_DIFF_MAX_FILES = 100;
10943
10572
  var textEncoder = new TextEncoder();
10944
- function byteLength(text) {
10945
- return textEncoder.encode(text).length;
10946
- }
10947
- __name(byteLength, "byteLength");
10948
- function clipDiffText(text, maxBytes) {
10949
- if (maxBytes <= 0) return { text: "", clipped: true };
10950
- if (byteLength(text) <= maxBytes) return { text, clipped: false };
10951
- const lines = text.split("\n");
10952
- const kept = [];
10953
- let used = 0;
10954
- for (const line of lines) {
10955
- const cost = byteLength(line) + (kept.length > 0 ? 1 : 0);
10956
- if (used + cost > maxBytes) break;
10957
- kept.push(line);
10958
- used += cost;
10959
- }
10960
- return { text: kept.join("\n"), clipped: true };
10961
- }
10962
- __name(clipDiffText, "clipDiffText");
10963
- function truncateUnifiedDiff(diff, options) {
10964
- const maxTotalBytes = options?.maxTotalBytes ?? UNIFIED_DIFF_MAX_TOTAL_BYTES;
10965
- const maxFileBytes = options?.maxFileBytes ?? UNIFIED_DIFF_MAX_FILE_BYTES;
10966
- const maxFiles = options?.maxFiles ?? UNIFIED_DIFF_MAX_FILES;
10967
- let totalBytes = 0;
10968
- let anyClipped = false;
10969
- const files = diff.files.slice(0, maxFiles).map((file) => {
10970
- if (typeof file.diff !== "string" || file.diff.length === 0) {
10971
- const { diff: _omitted, ...rest } = file;
10972
- return rest;
10973
- }
10974
- let truncated2 = file.truncated === true;
10975
- let text = file.diff;
10976
- const perFile = clipDiffText(text, maxFileBytes);
10977
- if (perFile.clipped) {
10978
- text = perFile.text;
10979
- truncated2 = true;
10980
- }
10981
- const remaining = maxTotalBytes - totalBytes;
10982
- const withinTotal = clipDiffText(text, remaining);
10983
- if (withinTotal.clipped) {
10984
- text = withinTotal.text;
10985
- truncated2 = true;
10986
- }
10987
- if (truncated2) anyClipped = true;
10988
- if (text.length === 0) {
10989
- const { diff: _dropped, ...rest } = file;
10990
- return { ...rest, truncated: true };
10991
- }
10992
- totalBytes += byteLength(text);
10993
- return { ...file, diff: text, ...truncated2 ? { truncated: true } : {} };
10994
- });
10995
- const droppedFiles = diff.files.length > files.length;
10996
- const truncated = diff.truncated === true || droppedFiles || anyClipped;
10997
- return { files, ...truncated ? { truncated: true } : {} };
10998
- }
10999
- __name(truncateUnifiedDiff, "truncateUnifiedDiff");
11000
-
11001
- // ../core/src/approval-format.ts
11002
- var seq = 0;
11003
- function generateApprovalId() {
11004
- const ts = Date.now().toString(36);
11005
- const r = Math.random().toString(36).slice(2, 6);
11006
- return `apr_${ts}${r}${++seq}`;
11007
- }
11008
- __name(generateApprovalId, "generateApprovalId");
11009
- function summarizeToolInput(toolName, toolInput) {
11010
- if (toolName === "Bash" && typeof toolInput.command === "string") {
11011
- return toolInput.command;
11012
- }
11013
- if ((toolName === "Edit" || toolName === "Write") && typeof toolInput.file_path === "string") {
11014
- return toolInput.file_path;
11015
- }
11016
- const json = JSON.stringify(toolInput);
11017
- return json.length > 200 ? json.slice(0, 200) + "..." : json;
11018
- }
11019
- __name(summarizeToolInput, "summarizeToolInput");
11020
- function redactSecrets(text, patterns) {
11021
- let result = text;
11022
- for (const pat of patterns) {
11023
- result = result.replace(new RegExp(pat, "gi"), "[REDACTED]");
11024
- }
11025
- return result;
11026
- }
11027
- __name(redactSecrets, "redactSecrets");
11028
- function redactApprovalDetails(details, patterns) {
11029
- if (!details?.length) return void 0;
11030
- return details.slice(0, 8).map((detail) => ({
11031
- ...detail,
11032
- label: detail.label.slice(0, 80),
11033
- value: patterns?.length ? redactSecrets(detail.value.slice(0, 500), patterns) : detail.value.slice(0, 500)
11034
- }));
11035
- }
11036
- __name(redactApprovalDetails, "redactApprovalDetails");
11037
- function buildApprovalRequest(approvalId, toolName, toolInput, opts) {
11038
- let summary = opts.toolSummary ?? summarizeToolInput(toolName, toolInput);
11039
- if (opts.redactPatterns?.length) {
11040
- summary = redactSecrets(summary, opts.redactPatterns);
11041
- }
11042
- summary = summary.slice(0, 1e3);
11043
- const risk = opts.risk ?? opts.riskLevel ?? "normal";
11044
- const riskLevel = risk === "destructive" ? "destructive" : opts.riskLevel ?? "normal";
11045
- const details = redactApprovalDetails(opts.details, opts.redactPatterns);
11046
- const diff = opts.diff ? truncateUnifiedDiff(opts.diff) : void 0;
11047
- const timeoutMin = Math.round(
11048
- (new Date(opts.expiresAt).getTime() - Date.now()) / 6e4
11049
- );
11050
- const lines = [
11051
- `Action Approval Required [${approvalId}]`,
11052
- "",
11053
- `Tool: ${toolName}`,
11054
- summary,
11055
- ""
11056
- ];
11057
- if (risk === "high" || risk === "destructive") {
11058
- lines.push(risk === "destructive" ? "This is a destructive operation" : "This is a high-risk operation");
11059
- lines.push("");
11060
- }
11061
- lines.push(`Reply "approve" or "deny" (expires in ${timeoutMin}m)`);
11062
- const metadata = {
11063
- type: "approval_request",
11064
- approvalId,
11065
- ...opts.responseUserId ? { responseUserId: opts.responseUserId.slice(0, 128) } : {},
11066
- toolName,
11067
- toolSummary: summary,
11068
- ...opts.category ? { category: opts.category } : {},
11069
- risk,
11070
- riskLevel,
11071
- ...opts.runtimeId ? { runtimeId: opts.runtimeId } : {},
11072
- ...opts.turnId ? { turnId: opts.turnId } : {},
11073
- ...opts.native ? { native: opts.native } : {},
11074
- ...details ? { details } : {},
11075
- ...diff ? { diff } : {},
11076
- expiresAt: opts.expiresAt
11077
- };
11078
- return { text: lines.join("\n"), metadata };
11079
- }
11080
- __name(buildApprovalRequest, "buildApprovalRequest");
11081
- function buildApprovalOutcome(approvalId, toolName, toolSummary, decision, reason) {
11082
- const icon = decision === "allow" ? "Approved" : "Denied";
11083
- const short = toolSummary.length > 60 ? toolSummary.slice(0, 60) + "..." : toolSummary;
11084
- if (reason === "timeout") {
11085
- return `Expired [${approvalId}] -- ${toolName}: ${short} (auto-denied)`;
11086
- }
11087
- if (reason === "session-rule") {
11088
- const label = decision === "allow" ? "Auto-approved" : "Auto-denied";
11089
- return `${label} (session rule) -- ${toolName}: ${short}`;
11090
- }
11091
- return `${icon} [${approvalId}] -- ${toolName}: ${short}`;
11092
- }
11093
- __name(buildApprovalOutcome, "buildApprovalOutcome");
11094
-
11095
- // ../core/src/approval-manager.ts
11096
- var ApprovalManager = class extends RuntimeRequestManager {
11097
- static {
11098
- __name(this, "ApprovalManager");
11099
- }
11100
- config;
11101
- constructor(client, agentId, ownerId, config) {
11102
- super(client, { agentId, ownerId });
11103
- this.config = { ...DEFAULT_APPROVAL_CONFIG, ...config };
11104
- this.register("approval", this.buildApprovalDescriptor());
11105
- }
11106
- /**
11107
- * Request approval for a tool call.
11108
- *
11109
- * 1. Checks session rules — may resolve immediately.
11110
- * 2. Sends a message with approval card metadata.
11111
- * 3. Waits for the owner's reply (or timeout).
11112
- */
11113
- requestApproval(conversationId, toolName, toolInput, opts, runtimeOptions) {
11114
- return this.request(
11115
- "approval",
11116
- conversationId,
11117
- { toolName, toolInput, opts },
11118
- runtimeOptions
11119
- );
11120
- }
11121
- buildApprovalDescriptor() {
11122
- return {
11123
- kind: "approval",
11124
- generateId: /* @__PURE__ */ __name(() => generateApprovalId(), "generateId"),
11125
- resolveExpiry: /* @__PURE__ */ __name(({ options }) => options.expiresAt ?? Date.now() + this.config.timeoutSeconds * 1e3, "resolveExpiry"),
11126
- pollMs: /* @__PURE__ */ __name(() => 1e3, "pollMs"),
11127
- preflight: /* @__PURE__ */ __name(({ conversationId, payload }) => {
11128
- const { toolName, toolInput, opts } = payload;
11129
- const nonOwnerResponder = Boolean(
11130
- opts?.responseUserId && opts.responseUserId !== this.ctx.ownerId
11131
- );
11132
- const matchingRule = opts?.ignoreSessionRules || nonOwnerResponder ? null : this.checkSessionRules(toolName, conversationId);
11133
- if (!matchingRule) return null;
11134
- const decision = matchingRule.type === "deny-tool" ? "deny" : "allow";
11135
- const rawSummary = opts?.toolSummary ?? summarizeTool(toolName, toolInput);
11136
- const summary = this.config.redactPatterns.length ? redactSecrets(rawSummary, this.config.redactPatterns) : rawSummary;
11137
- const logMsg = buildApprovalOutcome("", toolName, summary, decision, "session-rule");
11138
- this.client.sendMessage(conversationId, logMsg, {
11139
- metadata: { type: "approval_outcome", decision, reason: "session-rule" }
11140
- }).catch(() => {
11141
- });
11142
- return { decision };
11143
- }, "preflight"),
11144
- create: /* @__PURE__ */ __name(async ({ conversationId, requestId, payload, expiresAt }) => {
11145
- const { toolName, toolInput, opts } = payload;
11146
- const approvalId = requestId;
11147
- const expiresAtIso = new Date(expiresAt).toISOString();
11148
- const requestedResponseUserId = opts?.responseUserId || this.ctx.ownerId || void 0;
11149
- const requestedAllowSessionRule = requestedResponseUserId === this.ctx.ownerId && opts?.allowSessionRule !== false;
11150
- const { metadata } = buildApprovalRequest(approvalId, toolName, toolInput, {
11151
- riskLevel: opts?.riskLevel,
11152
- risk: opts?.risk,
11153
- category: opts?.category,
11154
- responseUserId: requestedResponseUserId,
11155
- runtimeId: opts?.runtimeId,
11156
- turnId: opts?.turnId,
11157
- native: opts?.native,
11158
- toolSummary: opts?.toolSummary,
11159
- details: opts?.details,
11160
- diff: opts?.diff,
11161
- expiresAt: expiresAtIso,
11162
- redactPatterns: this.config.redactPatterns
11163
- });
11164
- const created = await this.client.createRuntimeApprovalRequest({
11165
- conversationId,
11166
- approvalId,
11167
- toolName,
11168
- toolSummary: metadata.toolSummary,
11169
- expiresAt: Date.parse(expiresAtIso),
11170
- ...requestedResponseUserId ? { responseUserId: requestedResponseUserId } : {},
11171
- ...opts?.riskLevel ? { riskLevel: opts.riskLevel } : {},
11172
- ...opts?.risk ? { risk: opts.risk } : {},
11173
- ...opts?.category ? { category: opts.category } : {},
11174
- ...opts?.runtimeId ? { runtimeId: opts.runtimeId } : {},
11175
- ...opts?.turnId ? { turnId: opts.turnId } : {},
11176
- ...metadata.native ? { native: metadata.native } : {},
11177
- ...metadata.details ? { details: metadata.details } : {},
11178
- ...metadata.diff ? { diff: metadata.diff } : {},
11179
- ...!requestedAllowSessionRule ? { allowSessionRule: false } : {}
11180
- });
11181
- return {
11182
- requestId: approvalId,
11183
- expiresAt,
11184
- state: {
11185
- toolName,
11186
- toolSummary: metadata.toolSummary,
11187
- allowSessionRule: created.allowSessionRule ?? requestedAllowSessionRule
11188
- }
11189
- };
11190
- }, "create"),
11191
- consume: /* @__PURE__ */ __name(async ({ conversationId, requestId }) => {
11192
- const response = await this.client.consumeRuntimeApprovalResponse({
11193
- conversationId,
11194
- approvalId: requestId
11195
- });
11196
- if (response.status === "allow" || response.status === "deny") {
11197
- return {
11198
- state: "resolved",
11199
- result: {
11200
- decision: response.status,
11201
- ...response.sessionRule ? { sessionRule: response.sessionRule } : {},
11202
- ...response.respondedBy ? { respondedBy: response.respondedBy } : {}
11203
- }
11204
- };
11205
- }
11206
- if (response.status === "timeout") return { state: "timeout" };
11207
- return { state: "pending" };
11208
- }, "consume"),
11209
- cancel: /* @__PURE__ */ __name(async ({ conversationId, requestId }) => {
11210
- await this.client.consumeRuntimeApprovalResponse({
11211
- conversationId,
11212
- approvalId: requestId,
11213
- cancel: true
11214
- });
11215
- }, "cancel"),
11216
- timeoutResult: /* @__PURE__ */ __name(() => ({ decision: "deny" }), "timeoutResult"),
11217
- finalize: /* @__PURE__ */ __name(({ conversationId, requestId, state, result, reason }) => {
11218
- const toolName = state?.toolName ?? "";
11219
- const toolSummary = state?.toolSummary ?? "";
11220
- if (reason === "timeout") {
11221
- const msg = buildApprovalOutcome(requestId, toolName, toolSummary, "deny", "timeout");
11222
- this.client.sendMessage(conversationId, msg, {
11223
- metadata: {
11224
- type: "approval_outcome",
11225
- approvalId: requestId,
11226
- decision: "deny",
11227
- reason: "timeout"
11228
- }
11229
- }).catch(() => {
11230
- });
11231
- return { decision: "deny" };
11232
- }
11233
- const acceptedSessionRule = state?.allowSessionRule ? result.sessionRule : void 0;
11234
- if (acceptedSessionRule) {
11235
- this.addSessionRule(conversationId, acceptedSessionRule);
11236
- }
11237
- const outcomeMsg = buildApprovalOutcome(
11238
- requestId,
11239
- toolName,
11240
- toolSummary,
11241
- result.decision,
11242
- "replied"
11243
- );
11244
- this.client.sendMessage(conversationId, outcomeMsg, {
11245
- metadata: {
11246
- type: "approval_outcome",
11247
- approvalId: requestId,
11248
- decision: result.decision,
11249
- reason: "replied"
11250
- }
11251
- }).catch(() => {
11252
- });
11253
- if (acceptedSessionRule) {
11254
- const ruleDesc = describeRule(acceptedSessionRule);
11255
- this.client.sendMessage(conversationId, `Session rule set: ${ruleDesc}`, {
11256
- metadata: { type: "approval_outcome", decision: result.decision, reason: "session-rule" }
11257
- }).catch(() => {
11258
- });
11259
- }
11260
- return {
11261
- decision: result.decision,
11262
- ...acceptedSessionRule ? { sessionRule: acceptedSessionRule } : {},
11263
- ...result.respondedBy ? { respondedBy: result.respondedBy } : {}
11264
- };
11265
- }, "finalize"),
11266
- matchPush: /* @__PURE__ */ __name(({ conversationId, message }) => {
11267
- void conversationId;
11268
- if (message.metadata?.type !== "approval_reply") return null;
11269
- const parsed = parseApprovalReplyMetadata(message.metadata);
11270
- if (!parsed) return null;
11271
- return { requestId: parsed.approvalId };
11272
- }, "matchPush")
11273
- };
11274
- }
11275
- };
11276
- function summarizeTool(toolName, toolInput) {
11277
- if (toolName === "Bash" && typeof toolInput.command === "string") {
11278
- return toolInput.command;
11279
- }
11280
- if ((toolName === "Edit" || toolName === "Write") && typeof toolInput.file_path === "string") {
11281
- return toolInput.file_path;
11282
- }
11283
- const json = JSON.stringify(toolInput);
11284
- return json.length > 100 ? json.slice(0, 100) + "..." : json;
11285
- }
11286
- __name(summarizeTool, "summarizeTool");
11287
- function describeRule(rule) {
11288
- if (rule.type === "approve-all") {
11289
- if (rule.expiresAt) {
11290
- const min = Math.round((new Date(rule.expiresAt).getTime() - Date.now()) / 6e4);
11291
- return `auto-approving all tools for ${min}m`;
11292
- }
11293
- return "auto-approving all tools for this session";
11294
- }
11295
- if (rule.type === "approve-tool") {
11296
- return `auto-approving all ${rule.toolPattern} commands`;
11297
- }
11298
- if (rule.type === "deny-tool") {
11299
- return `auto-denying all ${rule.toolPattern} commands`;
11300
- }
11301
- return "unknown rule";
11302
- }
11303
- __name(describeRule, "describeRule");
11304
-
11305
- // ../core/src/silent-turn.ts
11306
- var NO_REPLY_REASON_SENTINEL = "given";
11307
- function reportNoReplyOutcome(client, params) {
11308
- try {
11309
- const hasReason = Boolean(params.reason?.trim());
11310
- const wire = projectVerbIntentToWire(
11311
- "no_reply",
11312
- {
11313
- conversationId: params.conversationId,
11314
- ...params.messageId ? { messageId: params.messageId } : {},
11315
- ...hasReason ? { reason: NO_REPLY_REASON_SENTINEL } : {}
11316
- },
11317
- { now: params.now ?? Date.now() }
11318
- );
11319
- void client.executeVerbWire(wire).catch(() => {
11320
- });
11321
- } catch {
11322
- }
11323
- }
11324
- __name(reportNoReplyOutcome, "reportNoReplyOutcome");
11325
10573
 
11326
- // ../core/src/turn-verbosity.ts
11327
- var DEFAULT_TURN_VERBOSITY_BY_CONVERSATION_TYPE = {
11328
- direct: "verbose",
11329
- group: "quiet",
11330
- // Fail OPEN to today's behavior: 'unknown' is reachable whenever the
11331
- // conversation fetch fails, and silence-by-accident is worse than noise.
11332
- unknown: "verbose"
11333
- };
11334
- function normalizeTurnVerbosityConversationType(value) {
11335
- const normalized = typeof value === "string" ? value.trim().toLowerCase() : "";
11336
- if (normalized === "direct") return "direct";
11337
- if (normalized === "group") return "group";
11338
- return "unknown";
11339
- }
11340
- __name(normalizeTurnVerbosityConversationType, "normalizeTurnVerbosityConversationType");
11341
- function resolveTurnVerbosity(input) {
11342
- const configured = parseTurnVerbosityConfig(input.configured);
11343
- if (configured === "verbose" || configured === "quiet") return configured;
11344
- return DEFAULT_TURN_VERBOSITY_BY_CONVERSATION_TYPE[normalizeTurnVerbosityConversationType(input.conversationType)];
11345
- }
11346
- __name(resolveTurnVerbosity, "resolveTurnVerbosity");
11347
- function parseTurnVerbosityConfig(raw) {
11348
- const normalized = typeof raw === "string" ? raw.trim().toLowerCase() : "";
11349
- if (normalized === "verbose" || normalized === "quiet" || normalized === "auto") {
11350
- return normalized;
11351
- }
11352
- return null;
11353
- }
11354
- __name(parseTurnVerbosityConfig, "parseTurnVerbosityConfig");
10574
+ // ../core/src/runtime-cards.ts
10575
+ var FIELD_TYPE_SET = new Set(RUNTIME_CARD_FIELD_TYPES);
10576
+ var LINE_ITEM_COLUMN_TYPE_SET = new Set(RUNTIME_CARD_LINE_ITEM_COLUMN_TYPES);
11355
10577
 
11356
10578
  // ../core/src/rtdb-rest.ts
11357
10579
  var CLEARED_LOCAL_RUNTIME_FIELDS = {
@@ -11792,62 +11014,12 @@ function createRuntimeStatePublisher(options) {
11792
11014
  __name(createRuntimeStatePublisher, "createRuntimeStatePublisher");
11793
11015
 
11794
11016
  // ../core/src/retry.ts
11795
- import { randomUUID as randomUUID6 } from "node:crypto";
11796
11017
  var CANON_MESSAGE_TEXT_MAX_BYTES = 4 * 1024;
11797
- var DEFAULT_CHUNKED_MESSAGE_TEXT_MAX_BYTES = 3800;
11798
11018
  function isPendingCanonOperation(error) {
11799
- if (isChunkedSendMessageError(error)) return isPendingCanonOperation(error.cause);
11800
11019
  return !!error && typeof error === "object" && "code" in error && ["OPERATION_PENDING", "OPERATION_UNCERTAIN", "ENDPOINT_CLOSED"].includes(String(error.code));
11801
11020
  }
11802
11021
  __name(isPendingCanonOperation, "isPendingCanonOperation");
11803
- var ChunkedSendMessageError = class extends Error {
11804
- static {
11805
- __name(this, "ChunkedSendMessageError");
11806
- }
11807
- /** Ids of the parts that are durable in Canon, in part order. */
11808
- deliveredMessageIds;
11809
- /** The part the send stopped on (1-based). */
11810
- failedPart;
11811
- /** How many parts the text splits into in total. */
11812
- totalParts;
11813
- /** The underlying send failure — what callers should classify. */
11814
- cause;
11815
- constructor(input) {
11816
- const detail = input.cause instanceof Error ? input.cause.message : String(input.cause);
11817
- super(`Chunked send failed at part ${input.failedPart} of ${input.totalParts}: ${detail}`);
11818
- this.name = "ChunkedSendMessageError";
11819
- this.deliveredMessageIds = [...input.deliveredMessageIds];
11820
- this.failedPart = input.failedPart;
11821
- this.totalParts = input.totalParts;
11822
- this.cause = input.cause;
11823
- }
11824
- /** How many parts are durable — a resume starts at the next one. */
11825
- get deliveredParts() {
11826
- return this.deliveredMessageIds.length;
11827
- }
11828
- };
11829
- function isChunkedSendMessageError(error) {
11830
- if (error instanceof ChunkedSendMessageError) return true;
11831
- return error instanceof Error && error.name === "ChunkedSendMessageError" && Array.isArray(error.deliveredMessageIds);
11832
- }
11833
- __name(isChunkedSendMessageError, "isChunkedSendMessageError");
11834
11022
  var textEncoder2 = new TextEncoder();
11835
- function isRetryableCanonDeliveryError(error) {
11836
- if (error instanceof CanonApiError) {
11837
- return error.status === 429 || error.status >= 500;
11838
- }
11839
- return error instanceof Error;
11840
- }
11841
- __name(isRetryableCanonDeliveryError, "isRetryableCanonDeliveryError");
11842
- async function sendMessageWithRetry(client, conversationId, text, options, retryOptions = {}) {
11843
- try {
11844
- return await (retryOptions.operationId ? client.sendMessage(conversationId, text, options, { operationId: retryOptions.operationId }) : client.sendMessage(conversationId, text, options));
11845
- } catch (error) {
11846
- if (!(error instanceof CanonApiError) || !error.operationId) throw error;
11847
- return client.waitForOperation(error.operationId, { signal: retryOptions.signal, timeoutMs: retryOptions.timeoutMs });
11848
- }
11849
- }
11850
- __name(sendMessageWithRetry, "sendMessageWithRetry");
11851
11023
  function utf8ByteLength(value) {
11852
11024
  return textEncoder2.encode(value).byteLength;
11853
11025
  }
@@ -11946,8 +11118,7 @@ ${renderedFence.marker}` : "";
11946
11118
  };
11947
11119
  }
11948
11120
  __name(takeChunk, "takeChunk");
11949
- function splitTextByUtf8Bytes(text, maxBytes = DEFAULT_CHUNKED_MESSAGE_TEXT_MAX_BYTES) {
11950
- const byteLimit = Math.min(maxBytes, CANON_MESSAGE_TEXT_MAX_BYTES);
11121
+ function splitTextWithinBytes(text, byteLimit) {
11951
11122
  if (!Number.isFinite(byteLimit) || byteLimit < 4) {
11952
11123
  throw new Error("maxBytes must be at least 4");
11953
11124
  }
@@ -11972,115 +11143,7 @@ function splitTextByUtf8Bytes(text, maxBytes = DEFAULT_CHUNKED_MESSAGE_TEXT_MAX_
11972
11143
  }
11973
11144
  return chunks;
11974
11145
  }
11975
- __name(splitTextByUtf8Bytes, "splitTextByUtf8Bytes");
11976
- function chunkMessageId(baseMessageId, index) {
11977
- if (!baseMessageId) return void 0;
11978
- const suffix = `-part-${index}`;
11979
- const maxBaseLength = 160 - suffix.length;
11980
- return `${baseMessageId.slice(0, Math.max(0, maxBaseLength))}${suffix}`;
11981
- }
11982
- __name(chunkMessageId, "chunkMessageId");
11983
- function stripTrailTextBlocks(trail) {
11984
- if (!Array.isArray(trail)) return null;
11985
- return trail.filter((block) => {
11986
- if (!block || typeof block !== "object") return true;
11987
- return block.kind !== "text";
11988
- });
11989
- }
11990
- __name(stripTrailTextBlocks, "stripTrailTextBlocks");
11991
- function buildChunkedMessagePartMetadata(input) {
11992
- if (input.count <= 1) return { ...input.metadata ?? {} };
11993
- const chunk = {
11994
- type: "text",
11995
- index: input.index,
11996
- count: input.count,
11997
- ...input.groupId ? { groupId: input.groupId } : {}
11998
- };
11999
- if (input.index >= input.count) {
12000
- const metadata = { ...input.metadata ?? {} };
12001
- const trail = stripTrailTextBlocks(metadata.turnTrail);
12002
- if (trail) {
12003
- if (trail.length > 0) metadata.turnTrail = trail;
12004
- else delete metadata.turnTrail;
12005
- }
12006
- return {
12007
- ...metadata,
12008
- messageChunk: chunk
12009
- };
12010
- }
12011
- const {
12012
- turnSemantics: _turnSemantics,
12013
- turnTrail: _turnTrail,
12014
- ...rest
12015
- } = input.metadata ?? {};
12016
- return {
12017
- ...rest,
12018
- turnSemantics: "progress",
12019
- replyBehavior: "suppress_auto_reply",
12020
- messageChunk: chunk
12021
- };
12022
- }
12023
- __name(buildChunkedMessagePartMetadata, "buildChunkedMessagePartMetadata");
12024
- async function sendMessageWithRetryChunked(client, conversationId, text, options, retryOptions = {}, chunkingOptions = {}) {
12025
- const chunks = splitTextByUtf8Bytes(
12026
- text,
12027
- chunkingOptions.maxTextBytes ?? DEFAULT_CHUNKED_MESSAGE_TEXT_MAX_BYTES
12028
- );
12029
- const alreadyDelivered = chunkingOptions.deliveredMessageIds ?? [];
12030
- const resumable = chunkingOptions.resumable === true || alreadyDelivered.length > 0;
12031
- if (chunks.length === 1) {
12032
- if (alreadyDelivered.length > 0) {
12033
- throw new ChunkedSendMessageError({
12034
- cause: new Error(
12035
- `Resume cursor holds ${alreadyDelivered.length} part(s) but the text now fits in one message`
12036
- ),
12037
- deliveredMessageIds: alreadyDelivered,
12038
- failedPart: 1,
12039
- totalParts: 1
12040
- });
12041
- }
12042
- const result = await sendMessageWithRetry(client, conversationId, text, options, retryOptions);
12043
- return { messageIds: [result.messageId] };
12044
- }
12045
- const rootMessageId = options?.messageId ?? randomUUID6();
12046
- const parts = chunks.map((text2, index) => ({ text: text2, options: {
12047
- ...options ?? {},
12048
- messageId: chunkMessageId(rootMessageId, index + 1),
12049
- metadata: buildChunkedMessagePartMetadata({ groupId: rootMessageId, metadata: options?.metadata, index: index + 1, count: chunks.length })
12050
- } }));
12051
- const group = await client.prepareMessageGroup(conversationId, rootMessageId, parts, retryOptions);
12052
- const groupId = group.id;
12053
- const cancel = /* @__PURE__ */ __name(() => {
12054
- void client.cancelMessageGroup(groupId).catch(() => {
12055
- });
12056
- }, "cancel");
12057
- retryOptions.signal?.addEventListener("abort", cancel, { once: true });
12058
- try {
12059
- const startIndex = Math.min(alreadyDelivered.length, chunks.length);
12060
- const messageIds = [...alreadyDelivered.slice(0, startIndex)];
12061
- for (let index = startIndex; index < chunks.length; index += 1) {
12062
- const part = index + 1;
12063
- try {
12064
- if (retryOptions.signal?.aborted) throw new CanonApiError(409, JSON.stringify({ code: "OPERATION_PENDING", message: "Reply delivery was interrupted" }));
12065
- const result = await group.sendPart(index, retryOptions);
12066
- messageIds.push(result.messageId);
12067
- } catch (error) {
12068
- if (!resumable) throw error;
12069
- throw new ChunkedSendMessageError({
12070
- cause: error,
12071
- deliveredMessageIds: messageIds,
12072
- failedPart: part,
12073
- totalParts: chunks.length
12074
- });
12075
- }
12076
- }
12077
- return { messageIds };
12078
- } finally {
12079
- retryOptions.signal?.removeEventListener("abort", cancel);
12080
- if (retryOptions.signal?.aborted) await client.cancelMessageGroup(groupId);
12081
- }
12082
- }
12083
- __name(sendMessageWithRetryChunked, "sendMessageWithRetryChunked");
11146
+ __name(splitTextWithinBytes, "splitTextWithinBytes");
12084
11147
 
12085
11148
  // ../core/src/cli-metadata.ts
12086
11149
  import { readFileSync as readFileSync3, realpathSync } from "node:fs";
@@ -12144,7 +11207,7 @@ function runCli(moduleUrl, main, onError, metadata) {
12144
11207
  __name(runCli, "runCli");
12145
11208
 
12146
11209
  // ../core/src/completed-output-journal.ts
12147
- import { createHash as createHash4, randomUUID as randomUUID7 } from "node:crypto";
11210
+ import { createHash as createHash4, randomUUID as randomUUID6 } from "node:crypto";
12148
11211
  import {
12149
11212
  closeSync as closeSync3,
12150
11213
  existsSync as existsSync2,
@@ -12221,7 +11284,7 @@ function createCompletedOutputJournal(directory, scope5, options) {
12221
11284
  if (readFileSync4(path, "utf8") !== serialized) throw new Error(`Completed ${label} output cannot be replaced`);
12222
11285
  return;
12223
11286
  }
12224
- const temporary = `${path.slice(0, -5)}.${process.pid}.${randomUUID7()}.tmp`;
11287
+ const temporary = `${path.slice(0, -5)}.${process.pid}.${randomUUID6()}.tmp`;
12225
11288
  try {
12226
11289
  const fd = openSync3(temporary, "wx", 384);
12227
11290
  try {
@@ -12261,12 +11324,6 @@ function createCompletedOutputJournal(directory, scope5, options) {
12261
11324
  };
12262
11325
  }
12263
11326
  __name(createCompletedOutputJournal, "createCompletedOutputJournal");
12264
- function assertCompletedOutputAudience(record6, current, label) {
12265
- if (!record6.audience || !current || record6.audience.membershipRevision !== void 0 && record6.audience.membershipRevision !== current.membershipRevision || JSON.stringify([...record6.audience.memberIds].sort()) !== JSON.stringify([...current.memberIds].sort())) {
12266
- throw new Error(`Completed ${label} output audience changed; operator reconciliation is required`);
12267
- }
12268
- }
12269
- __name(assertCompletedOutputAudience, "assertCompletedOutputAudience");
12270
11327
 
12271
11328
  // src/bounded-file-read.ts
12272
11329
  var BoundedFileReadError = class extends Error {
@@ -12545,7 +11602,7 @@ async function materializeAttachment(attachment, options) {
12545
11602
  if (!response.body) {
12546
11603
  throw new Error("Canon media response had no body");
12547
11604
  }
12548
- const tempPath = `${path}.${process.pid}.${randomUUID8()}.part`;
11605
+ const tempPath = `${path}.${process.pid}.${randomUUID7()}.part`;
12549
11606
  const limiter = byteLimitTransform(maxBytes);
12550
11607
  try {
12551
11608
  await pipeline2(
@@ -12641,7 +11698,7 @@ async function uploadMediaFile(client, conversationId, filePath, options) {
12641
11698
  const media = await client.getFileMediaPipeline(options?.fetchImpl);
12642
11699
  const [attachment] = await media.prepare({
12643
11700
  conversationId,
12644
- attachmentId: options?.attachmentId ?? randomUUID8(),
11701
+ attachmentId: options?.attachmentId ?? randomUUID7(),
12645
11702
  files: [{ source: filePath, descriptor: {
12646
11703
  mimeType,
12647
11704
  sizeBytes: fileStat.size,
@@ -12670,7 +11727,7 @@ async function sendMediaFileMessage(client, conversationId, filePath, text = "",
12670
11727
  const current = await client.getConversation(conversationId);
12671
11728
  return !!current && originalMembers.size > 0 && current.memberIds.every((id2) => originalMembers.has(id2)) && canPublish();
12672
11729
  } : void 0;
12673
- const messageId = sendOptions.messageId ?? randomUUID8();
11730
+ const messageId = sendOptions.messageId ?? randomUUID7();
12674
11731
  const pipeline3 = await client.getFileMediaPipeline(fetchImpl);
12675
11732
  const delivered = await pipeline3.send({
12676
11733
  conversationId,
@@ -12845,21 +11902,14 @@ export {
12845
11902
  acquireLock,
12846
11903
  clearPendingRegistration,
12847
11904
  ensureAgentProfile,
12848
- ApprovalManager,
12849
- reportNoReplyOutcome,
12850
- resolveTurnVerbosity,
12851
11905
  initRTDBAuth,
12852
11906
  renderCanonHostInboundContent,
12853
11907
  createRuntimeStatePublisher,
12854
11908
  isPendingCanonOperation,
12855
- isRetryableCanonDeliveryError,
12856
- splitTextByUtf8Bytes,
12857
- buildChunkedMessagePartMetadata,
12858
- sendMessageWithRetryChunked,
11909
+ splitTextWithinBytes,
12859
11910
  readCliPackageVersion,
12860
11911
  runCli,
12861
11912
  createCompletedOutputJournal,
12862
- assertCompletedOutputAudience,
12863
11913
  BoundedFileReadError,
12864
11914
  readBoundedRegularFileHandle,
12865
11915
  DEFAULT_MEDIA_CACHE_DIR,