@canonmsg/agent-sdk 12.2.2 → 12.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -232,7 +232,7 @@ In an encrypted room:
232
232
  - A reply is journaled before it is encrypted. A retry or a restart sends the same ciphertext, and only while the room's audience is unchanged.
233
233
  - The ids Canon sees for what the runner sends there (each message's id, and a card's request id) are keyed with a secret the runner creates once in `$CANON_HOME/runner/<environment>/<agent>/mls-ids/` (0600; it belongs with the rest of the home). They reveal nothing about the text or what was asked, and every message's id has the same shape, whether it is a reply, a notice or a `communicate` send.
234
234
 
235
- Without `--mls-dev-config` (or `mls`), the runner refuses each encrypted room, logs it once, and runs nothing for it.
235
+ Without `--mls-dev-config` (or `mls`), the runner refuses each encrypted room, logs it once, and runs nothing for it. Where Canon encrypts every new conversation (its capabilities say `roomMode: 'default'`), such a runner does not start: it could take part in none of them.
236
236
 
237
237
  ### The class-C relay (Dev, Canon-operated)
238
238
 
@@ -4159,6 +4159,7 @@ var ENDPOINT_CONTENT_FUNCTION_QUERIES = [
4159
4159
  ];
4160
4160
  var ENDPOINT_CONTENT_QUERIES = [...ENDPOINT_CONTENT_FUNCTION_QUERIES, "admit_messages"];
4161
4161
  var ENDPOINT_CONTENT_EPHEMERALS = [
4162
+ "prewarm_agents",
4162
4163
  "create_memo_stream",
4163
4164
  "publish_presence",
4164
4165
  "publish_typing",
@@ -4364,6 +4365,7 @@ var turnPresence = {
4364
4365
  var transientRouting = {
4365
4366
  create_memo_stream: ["conversationId", "streamId"],
4366
4367
  append_memo_chunk: ["streamId"],
4368
+ prewarm_agents: ["conversationId"],
4367
4369
  publish_presence: [],
4368
4370
  publish_typing: ["conversationId"],
4369
4371
  publish_streaming: ["conversationId"],
@@ -29,7 +29,7 @@ import {
29
29
  sendMessageWithRetryChunked,
30
30
  splitTextByUtf8Bytes,
31
31
  verifyCanonRuntimeConnection
32
- } from "./chunk-FCG2Z57F.js";
32
+ } from "./chunk-SKFI2MJA.js";
33
33
  import {
34
34
  CONNECTOR_RELAY_ANSWER_WITHIN_MS,
35
35
  CONNECTOR_RELAY_INSTALLATION_PATTERN,
@@ -58,7 +58,7 @@ import {
58
58
  parseMlsDevGroupHead,
59
59
  parseMlsDevGroupRoster,
60
60
  resolveAllowedUploadMimeType
61
- } from "./chunk-J63YVAKA.js";
61
+ } from "./chunk-5MI6Q3VS.js";
62
62
 
63
63
  // src/runner/types.ts
64
64
  var DriverSessionError = class extends Error {
@@ -4701,7 +4701,11 @@ ${JSON.stringify(result, null, 2)}`);
4701
4701
  }
4702
4702
  agentId = identity.principalId;
4703
4703
  ownershipVersion = identity.ownershipVersion;
4704
+ if (options.wake !== void 0) await client.configureWake(options.wake);
4704
4705
  mlsOn = !!(endpoint.mlsDevDevice && endpoint.mlsDevGroups);
4706
+ if (!mlsOn && (await endpoint.capabilities().catch(() => null))?.mls?.roomMode === "default") {
4707
+ throw new Error("Canon encrypts every new conversation here, and this runner has no MLS device: run it with --mls-dev-config <path> (set one up with canon-agent mls).");
4708
+ }
4705
4709
  if (mlsOn) await endpoint.mlsDevDevice.ensureReady().catch(warn("MLS device not ready; encrypted rooms wait for it"));
4706
4710
  const keepPool = /* @__PURE__ */ __name(() => endpoint.mlsDevDevice.read().then(async (view) => {
4707
4711
  if (view.registration && view.status !== "revoked") {
@@ -109,7 +109,7 @@ import {
109
109
  readMembershipChangeMetadata,
110
110
  resolveAllowedUploadMimeType,
111
111
  sanitizeDownloadFileName
112
- } from "./chunk-J63YVAKA.js";
112
+ } from "./chunk-5MI6Q3VS.js";
113
113
 
114
114
  // src/media.ts
115
115
  import { createWriteStream as createWriteStream2 } from "node:fs";
@@ -194,6 +194,7 @@ function createEndpointEventHub(input) {
194
194
  let attempt = 0;
195
195
  while (!signal.aborted && observers.size) {
196
196
  let retryAfterMs = 0;
197
+ let handling = false;
197
198
  try {
198
199
  if (!input.transport.events) return;
199
200
  for await (const event of input.transport.events({ lastEventId, signal, families: families(), principalScopes: principalScopes(), onConnected: /* @__PURE__ */ __name(() => {
@@ -204,8 +205,10 @@ function createEndpointEventHub(input) {
204
205
  if (signal.aborted) break;
205
206
  validateEndpointContentBoundary("event", event.kind, event.data);
206
207
  if (event.kind === "error") throw decodeEndpointError(event.data, { stream: true });
207
- attempt = 0;
208
+ handling = true;
208
209
  await input.onEvent?.(event);
210
+ handling = false;
211
+ attempt = 0;
209
212
  if (event.kind === "replay.expired") lastEventId = void 0;
210
213
  else if (event.id) lastEventId = event.id;
211
214
  for (const observer of observers) safe(() => observer.listener(event));
@@ -213,7 +216,7 @@ function createEndpointEventHub(input) {
213
216
  }
214
217
  } catch (error) {
215
218
  if (!signal.aborted) {
216
- terminalError = error instanceof EndpointError && !error.retryable;
219
+ terminalError = !handling && error instanceof EndpointError && !error.retryable;
217
220
  if (error instanceof EndpointError && typeof error.retryAfterMs === "number" && Number.isFinite(error.retryAfterMs)) retryAfterMs = Math.max(0, Math.min(3e5, error.retryAfterMs));
218
221
  for (const { hooks } of observers) safe(() => hooks?.onError?.(error instanceof Error ? error : new EndpointError("Stream connection failed")));
219
222
  }
@@ -6609,6 +6612,7 @@ var view2 = /* @__PURE__ */ __name((value) => Object.freeze({
6609
6612
  roster: value.roster,
6610
6613
  pendingOperationId: value.pending?.operationId ?? null
6611
6614
  }), "view");
6615
+ var CATCH_UP_MAX_AGE_MS = 6e4;
6612
6616
  function createMlsDevGroups(options, context, keyPackages, bridge) {
6613
6617
  const principal = parseMlsDevEnrollmentPrincipal(options.principal);
6614
6618
  const prepareProtected = options.prepareProtectedGroup ?? fail6("MLS_DEV_GROUP_UNAVAILABLE");
@@ -6623,6 +6627,7 @@ function createMlsDevGroups(options, context, keyPackages, bridge) {
6623
6627
  }), "transaction") };
6624
6628
  const fence = /* @__PURE__ */ __name(() => store.transaction(async () => {
6625
6629
  }), "fence");
6630
+ const caughtUpAt = /* @__PURE__ */ new Map();
6626
6631
  async function initialized() {
6627
6632
  const ready = await context.ready();
6628
6633
  await fence();
@@ -6906,7 +6911,7 @@ function createMlsDevGroups(options, context, keyPackages, bridge) {
6906
6911
  if (!current.envelope?.head || current.envelope.removed || !same6(current.envelope.head, wire.expectedHead) || current.envelope.groupId !== wire.groupId) fail6("MLS_DEV_GROUP_HEAD_CHANGED", true);
6907
6912
  if (await tx.get(appMessageKey(cid, wire.messageId)) !== void 0 && await tx.get(appMessageKey(cid, wire.messageId)) !== receipt.operationId) fail6("MLS_DEV_APPLICATION_ID_CONFLICT");
6908
6913
  const existingMessage = await tx.get(mlsDevTextMessageKey(cid, wire.messageId));
6909
- if (existingMessage?.feedOrdinal !== null && existingMessage?.feedOrdinal !== void 0) fail6("MLS_DEV_APPLICATION_ID_CONFLICT");
6914
+ if (existingMessage?.feedOrdinal !== null && existingMessage?.feedOrdinal !== void 0 && (existingMessage.feedOrdinal !== entry.ordinal || sender.deviceId !== current.device.registration.device.deviceId)) fail6("MLS_DEV_APPLICATION_ID_CONFLICT");
6910
6915
  if (!current.envelope.roster.some((member) => same6(member.device, sender) && member.admissionId === wire.preconditions.admissionId)) fail6("MLS_DEV_GROUP_MEMBER_REQUIRED");
6911
6916
  ownIntent = sender.deviceId === current.device.registration.device.deviceId ? await tx.get(appIntentKey(receipt.operationId)) : void 0;
6912
6917
  if (ownIntent && (!same6(ownIntent.envelope, wire) || ownIntent.envelopeHash !== receipt.envelopeHash)) fail6("MLS_DEV_APPLICATION_ID_CONFLICT");
@@ -6919,7 +6924,7 @@ function createMlsDevGroups(options, context, keyPackages, bridge) {
6919
6924
  let message, integrity, wrappedState = group.wrappedState;
6920
6925
  if (ownIntent) {
6921
6926
  const local = await store.transaction((tx) => tx.get(mlsDevTextMessageKey(cid, wire.messageId))) ?? fail6();
6922
- message = local.message.kind === "text" ? parseMlsDevTextPayload(local.message) : parseMlsDevApplicationPayload(local.message);
6927
+ message = local.deleted ? local.message : local.message.kind === "text" ? parseMlsDevTextPayload(local.message) : parseMlsDevApplicationPayload(local.message);
6923
6928
  } else {
6924
6929
  const prepared = await native(init, current, state, cid, group.groupId, { op: "process_application", entry });
6925
6930
  if (prepared.payload.kind !== "text" && prepared.payload.kind !== "item" && prepared.payload.kind !== "interaction" && prepared.payload.kind !== "invalid" || prepared.payload.messageId !== wire.messageId || prepared.payload.operationId !== receipt.operationId || prepared.payload.senderDeviceId !== sender2.deviceId || prepared.payload.feedOrdinal !== entry.ordinal) fail6("MLS_DEV_GROUP_BINDING_MISMATCH");
@@ -7027,7 +7032,7 @@ function createMlsDevGroups(options, context, keyPackages, bridge) {
7027
7032
  let operation;
7028
7033
  if (prepared.own) {
7029
7034
  const own = await tx.get(`operation/${receipt.operationId}`) ?? fail6();
7030
- operation = await bridge.settle(tx, own, {
7035
+ if (own.status !== "succeeded") operation = await bridge.settle(tx, own, {
7031
7036
  protocol: CANON_ENDPOINT_PROTOCOL,
7032
7037
  operationId: own.operationId,
7033
7038
  installationId: own.installationId,
@@ -7108,6 +7113,17 @@ function createMlsDevGroups(options, context, keyPackages, bridge) {
7108
7113
  }
7109
7114
  __name(reduceInteraction, "reduceInteraction");
7110
7115
  async function refreshFeed(cid, init) {
7116
+ const applied = { entries: false };
7117
+ try {
7118
+ const view3 = await catchUpFeed(cid, init, applied);
7119
+ caughtUpAt.set(cid, context.clock());
7120
+ return view3;
7121
+ } finally {
7122
+ if (applied.entries) bridge.historyChanged?.(cid);
7123
+ }
7124
+ }
7125
+ __name(refreshFeed, "refreshFeed");
7126
+ async function catchUpFeed(cid, init, applied) {
7111
7127
  if (!bridge.queryFeed) fail6("MLS_DEV_GROUP_UNAVAILABLE");
7112
7128
  for (let page = 0; page < 8; page++) {
7113
7129
  const captured = await store.transaction(async (tx) => capture(tx, await readPreparationSnapshot(tx, scope4(cid)), init, cid));
@@ -7146,6 +7162,7 @@ function createMlsDevGroups(options, context, keyPackages, bridge) {
7146
7162
  if (entry.kind === "control") {
7147
7163
  if (!await applyAccepted(init, entry.accepted, void 0, entry)) fail6("MLS_DEV_GROUP_FEED_GAP", true);
7148
7164
  } else await applyApplication(init, entry);
7165
+ applied.entries = true;
7149
7166
  }
7150
7167
  if (!response.hasMore) {
7151
7168
  await store.transaction(async (tx) => {
@@ -7163,7 +7180,7 @@ function createMlsDevGroups(options, context, keyPackages, bridge) {
7163
7180
  }
7164
7181
  return fail6("MLS_DEV_GROUP_CATCHUP_REQUIRED", true);
7165
7182
  }
7166
- __name(refreshFeed, "refreshFeed");
7183
+ __name(catchUpFeed, "catchUpFeed");
7167
7184
  const canonItemReference = /* @__PURE__ */ __name((item, conversationId, environmentId) => isCanonUploadUrl(item.url, { environmentId, conversationId, uploadId: item.uploadId }) && (item.thumbnailUrl === void 0 || isCanonUploadUrl(item.thumbnailUrl, { environmentId, conversationId, uploadId: item.uploadId, poster: true })), "canonItemReference");
7168
7185
  function itemInput(attachment, caption) {
7169
7186
  const data = record5(attachment, ["kind", "url", "mimeType", "sizeBytes", "uploadId"], ["fileName", "width", "height", "durationMs", "thumbnailUrl", "processingStatus"]);
@@ -7243,7 +7260,7 @@ function createMlsDevGroups(options, context, keyPackages, bridge) {
7243
7260
  __name(existing, "existing");
7244
7261
  const previous = await store.transaction(existing);
7245
7262
  if (previous) return previous;
7246
- await refresh(cid);
7263
+ if (!await caughtUp(cid)) await refresh(cid);
7247
7264
  await assertNotHeld(cid);
7248
7265
  const messageId = input.messageId ?? id(context.ids());
7249
7266
  let captured, replyTo;
@@ -7768,6 +7785,28 @@ function createMlsDevGroups(options, context, keyPackages, bridge) {
7768
7785
  }
7769
7786
  }
7770
7787
  __name(refresh, "refresh");
7788
+ function textReady(conversationId) {
7789
+ return store.transaction(async (tx) => {
7790
+ const ready = await tx.get(`mls-dev-group/feed-ready/${encodeURIComponent(conversationId)}`);
7791
+ return !!ready?.admitted && !ready.terminal && ready.ordinal === await tx.get(feedKey(conversationId)) && await tx.get(recoveryFailureKey(conversationId)) === void 0;
7792
+ });
7793
+ }
7794
+ __name(textReady, "textReady");
7795
+ async function caughtUp(conversationId) {
7796
+ return !!bridge.streamLive?.() && context.clock() - (caughtUpAt.get(conversationId) ?? -Infinity) < CATCH_UP_MAX_AGE_MS && await textReady(conversationId);
7797
+ }
7798
+ __name(caughtUp, "caughtUp");
7799
+ async function settleOwnApplication(tx, operation, accepted, receipt) {
7800
+ if (operation.status === "succeeded" && same6(operation.receipt?.result, receipt.result)) return {};
7801
+ if (operation.status === "succeeded" || operation.status === "rejected") return void 0;
7802
+ const local = await tx.get(mlsDevTextMessageKey(accepted.conversationId, accepted.messageId));
7803
+ if (local?.message.kind !== "text" && local?.message.kind !== "item") return void 0;
7804
+ const intent2 = await tx.get(appIntentKey(operation.operationId));
7805
+ if (accepted.messageId !== operation.messageId || local.message.operationId !== operation.operationId || intent2?.envelopeHash !== accepted.envelopeHash || intent2.envelope.groupId !== accepted.groupId) fail6("MLS_DEV_GROUP_BINDING_MISMATCH");
7806
+ await putMlsDevTextMessage(tx, { ...local, status: "sent", createdAtMs: accepted.acceptedAtMs, feedOrdinal: accepted.feedOrdinal });
7807
+ return { operation: await bridge.settle(tx, operation, receipt, true) };
7808
+ }
7809
+ __name(settleOwnApplication, "settleOwnApplication");
7771
7810
  async function existingOperation(tx, operationId, authored) {
7772
7811
  const intent2 = await tx.get(`mls-dev-group/intent/${operationId}`), operation = await tx.get(`operation/${operationId}`);
7773
7812
  if (!intent2 && !operation) return void 0;
@@ -7900,10 +7939,12 @@ function createMlsDevGroups(options, context, keyPackages, bridge) {
7900
7939
  const verified = await store.transaction(async (tx) => {
7901
7940
  const local = await tx.get(mlsDevTextMessageKey(conversationId, messageId));
7902
7941
  if (!local || local.feedOrdinal === null || local.message.kind !== "text" && local.message.kind !== "item" || local.deleted) return void 0;
7942
+ const stored = await tx.get(feedEntryKey(conversationId, local.feedOrdinal));
7943
+ if (stored === void 0) return void 0;
7903
7944
  const text = local.message.kind === "item" ? parseMlsDevItemPayload(local.message) : parseMlsDevTextPayload(local.message);
7904
7945
  const protocol2 = await tx.get(mlsDevTextProtocolKey(conversationId));
7905
7946
  if (!isMlsDevTextProtocol(protocol2)) fail6("MLS_DEV_VERIFIED_MESSAGE_MISMATCH");
7906
- const entry = parseMlsDevGroupFeedEntry(await tx.get(feedEntryKey(conversationId, local.feedOrdinal)));
7947
+ const entry = parseMlsDevGroupFeedEntry(stored);
7907
7948
  if (entry.kind !== "application") fail6("MLS_DEV_VERIFIED_MESSAGE_MISMATCH");
7908
7949
  const { envelope: wire2, receipt: receipt2 } = entry;
7909
7950
  const sender2 = parseMlsDevCredentialToken(wire2.senderCredential).unsigned.claims.device;
@@ -8102,9 +8143,12 @@ function createMlsDevGroups(options, context, keyPackages, bridge) {
8102
8143
  const input = textInput(value), selectedId = id(operationId), selectedGuard = guardId === void 0 ? void 0 : id(guardId);
8103
8144
  return run(() => serializeInboundPreparation(store, input.conversationId, () => sendText(input, selectedId, selectedGuard)));
8104
8145
  },
8146
+ /** A room this device holds caught up is read as it holds it: what fetches the room's new entries says so
8147
+ * (bridge.historyChanged). Otherwise a read fetches the feed first, which the polling fallback and the connected
8148
+ * stream's minute-long safety poll rely on. */
8105
8149
  queryTextHistory(conversationId, name, parameters) {
8106
8150
  return run(() => serializeInboundPreparation(store, id(conversationId), async () => {
8107
- await refresh(conversationId);
8151
+ if (!await caughtUp(conversationId)) await refresh(conversationId);
8108
8152
  const init = await initialized();
8109
8153
  await primeIdentities(conversationId, init);
8110
8154
  return store.transaction(async (tx) => {
@@ -8128,12 +8172,7 @@ function createMlsDevGroups(options, context, keyPackages, bridge) {
8128
8172
  });
8129
8173
  }));
8130
8174
  },
8131
- async isTextReady(conversationId) {
8132
- return store.transaction(async (tx) => {
8133
- const ready = await tx.get(`mls-dev-group/feed-ready/${encodeURIComponent(conversationId)}`);
8134
- return !!ready?.admitted && !ready.terminal && ready.ordinal === await tx.get(feedKey(conversationId)) && await tx.get(recoveryFailureKey(conversationId)) === void 0;
8135
- });
8136
- },
8175
+ isTextReady: textReady,
8137
8176
  async readAcceptedReceipt(operation) {
8138
8177
  if (!["mls_group_control", "mls_application"].includes(operation.action)) return void 0;
8139
8178
  return store.transaction(async (tx) => {
@@ -8149,13 +8188,18 @@ function createMlsDevGroups(options, context, keyPackages, bridge) {
8149
8188
  const cid = operation.conversationId;
8150
8189
  const accepted2 = operation.action === "mls_application" ? parseMlsDevApplicationReceipt(receipt.result) : parseMlsDevGroupAcceptedControl(receipt.result);
8151
8190
  if (accepted2.operationId !== operation.operationId || ("control" in accepted2 ? accepted2.control.conversationId : accepted2.conversationId) !== cid) fail6("MLS_DEV_GROUP_BINDING_MISMATCH");
8152
- await store.transaction(async (tx) => {
8191
+ const own = await store.transaction(async (tx) => {
8153
8192
  const current = await tx.get(`operation/${operation.operationId}`) ?? fail6();
8154
8193
  if (receipt.installationId !== current.installationId || receipt.action !== current.action) fail6("MLS_DEV_GROUP_BINDING_MISMATCH");
8155
8194
  const previous = await tx.get(acceptedReceiptKey(operation.operationId));
8156
8195
  if (previous && !same6(previous, receipt)) fail6("MLS_DEV_GROUP_BINDING_MISMATCH");
8157
8196
  if (!previous) await tx.put(acceptedReceiptKey(operation.operationId), receipt);
8197
+ return "control" in accepted2 ? void 0 : settleOwnApplication(tx, current, accepted2, receipt);
8158
8198
  });
8199
+ if (own) {
8200
+ if (own.operation) await bridge.afterReceipt(own.operation);
8201
+ return true;
8202
+ }
8159
8203
  await run(() => serializeInboundPreparation(store, cid, async () => {
8160
8204
  try {
8161
8205
  await refresh(cid);
@@ -8382,7 +8426,10 @@ function createEndpoint(options) {
8382
8426
  clearTimeout(admissionTimers.get(conversationId));
8383
8427
  admissionTimers.delete(conversationId);
8384
8428
  scheduleAdmission(conversationId, 0);
8385
- }, "controlSettled")
8429
+ }, "controlSettled"),
8430
+ // Whatever fetched them (a hint, the reconnect sweep, a send, recovery), the room's local reads show them now.
8431
+ historyChanged: /* @__PURE__ */ __name((conversationId) => queryObserver.invalidate((query, parameters) => parameters.conversationId === conversationId && ["list_messages", "get_messages", "get_runtime_card_state"].includes(query)), "historyChanged"),
8432
+ streamLive: /* @__PURE__ */ __name(() => events.isConnected(), "streamLive")
8386
8433
  }) : void 0;
8387
8434
  async function inMlsEnvironment() {
8388
8435
  return isMlsEnvironmentId((await initialization).environmentId);
@@ -8834,7 +8881,8 @@ function createEndpoint(options) {
8834
8881
  await refreshMlsRoom(conversationId, true);
8835
8882
  } catch (error) {
8836
8883
  if (isMlsDevGroupRoomRecoveryFailure(error)) return;
8837
- throw error;
8884
+ if (error instanceof EndpointError && ["ENDPOINT_CLOSED", "PREPARATION_INSTALLATION_FENCED"].includes(error.code)) throw error;
8885
+ if (!(error instanceof EndpointError) || error.retryable) scheduleAdmission(conversationId, admissionRetry.initial, true);
8838
8886
  }
8839
8887
  }
8840
8888
  __name(refreshMlsRoomFromHint, "refreshMlsRoomFromHint");
@@ -9047,6 +9095,8 @@ function createEndpoint(options) {
9047
9095
  queryObserver.invalidate((query, parameters) => queryAffectedByOperation(updated, query, parameters));
9048
9096
  if (terminal(updated.status)) scheduleRetry(0);
9049
9097
  if (!terminal(updated.status) && updated.status !== "uncertain") scheduleRetry(Math.max(1e3, (updated.nextAttemptAt ?? clock()) - clock()));
9098
+ const made = updated.status === "succeeded" && ["start_conversation", "create_conversation"].includes(updated.action) ? updated.receipt?.result?.conversationId : void 0;
9099
+ if (mlsDevGroups && typeof made === "string" && ENDPOINT_ID_PATTERN.test(made)) void refreshMlsRoom(made, true).catch(() => void 0);
9050
9100
  }
9051
9101
  __name(afterReceipt, "afterReceipt");
9052
9102
  async function persistReceipt(operation, receipt) {
@@ -10032,7 +10082,7 @@ async function acquireAgentEndpoint(input) {
10032
10082
  }
10033
10083
  const requestedMls = input.options.mlsDev;
10034
10084
  const mls = requestedMls ? await (async () => {
10035
- const crypto2 = await import("./mls-crypto-ITVGUEP7.js");
10085
+ const crypto2 = await import("./mls-crypto-D5YAGYJ3.js");
10036
10086
  const configuration = crypto2.parseMlsDevClientConfiguration(requestedMls.configuration);
10037
10087
  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");
10038
10088
  const custody = requestedMls.custody;
@@ -10326,6 +10376,17 @@ var CanonClient = class {
10326
10376
  if (!res.ok) throw new CanonApiError(res.status, await res.text());
10327
10377
  return res.json();
10328
10378
  }
10379
+ /** Register (or explicitly remove) this installed agent's signed readiness callback. */
10380
+ async configureWake(configuration) {
10381
+ const response = await this.http(`${this.baseUrl}/agents/me/wake`, {
10382
+ method: configuration ? "POST" : "DELETE",
10383
+ headers: this.authHeaders(),
10384
+ signal: AbortSignal.timeout(15e3),
10385
+ ...configuration ? { body: JSON.stringify(configuration) } : {}
10386
+ });
10387
+ if (!response.ok) throw new CanonApiError(response.status, "Wake callback registration failed");
10388
+ return response.json();
10389
+ }
10329
10390
  async discoverAgents(input = {}) {
10330
10391
  const result = await this.endpointQuery("discover_agents", { ...input });
10331
10392
  if (!isDiscoverAgentsResult(result)) {
@@ -2,7 +2,7 @@ import {
2
2
  createMlsDevCredentialVerifier,
3
3
  mlsDevDeviceHttpConfiguration,
4
4
  parseMlsDevClientConfiguration
5
- } from "./chunk-J63YVAKA.js";
5
+ } from "./chunk-5MI6Q3VS.js";
6
6
  export {
7
7
  createMlsDevCredentialVerifier,
8
8
  mlsDevDeviceHttpConfiguration,
package/dist/cli.js CHANGED
@@ -22,7 +22,7 @@ import {
22
22
  runnerLockName,
23
23
  startRelayChannel,
24
24
  writeRelayCredential
25
- } from "./chunks/chunk-LFTYXZUY.js";
25
+ } from "./chunks/chunk-HI2SVCUL.js";
26
26
  import {
27
27
  CanonApiError,
28
28
  CanonClient,
@@ -33,11 +33,11 @@ import {
33
33
  resolveCanonRuntimeConnection,
34
34
  runCli,
35
35
  verifyCanonRuntimeConnection
36
- } from "./chunks/chunk-FCG2Z57F.js";
36
+ } from "./chunks/chunk-SKFI2MJA.js";
37
37
  import {
38
38
  __name,
39
39
  isCanonClientVersion
40
- } from "./chunks/chunk-J63YVAKA.js";
40
+ } from "./chunks/chunk-5MI6Q3VS.js";
41
41
 
42
42
  // src/cli.ts
43
43
  import { realpathSync } from "node:fs";
@@ -80,9 +80,10 @@ Usage:
80
80
  canon-agent errands [--follow] [--cancel <errand id>] (--agent <id> | --profile <name>)
81
81
  [--environment <id>]
82
82
 
83
- Every run command also takes --mls-dev-config <path> (Dev only), which gives the agent
84
- its MLS device so it can take part in encrypted rooms. Without it, the agent refuses
85
- encrypted rooms and logs each one once.
83
+ Every run command also takes --mls-dev-config <path>, which gives the agent its MLS
84
+ device so it can take part in encrypted rooms. Without it, the agent refuses encrypted
85
+ rooms and logs each one once; where Canon encrypts every new conversation, it does not
86
+ start.
86
87
 
87
88
  Team rooms (an owner's agents working together): claude and codex also take
88
89
  [--instructions <file>] [--trust-agent <agentId>]...; every run command takes
@@ -155,8 +156,8 @@ Flags:
155
156
  which are then not end-to-end encrypted to that agent. With
156
157
  "mls", it sets up the relay's device with that credential.
157
158
  --mls-dev-config <p> The operator's MLS configuration: an absolute path to a private
158
- JSON file with the Dev issuer pins and the custody helper (no
159
- keys or credentials). Dev only.
159
+ JSON file with the environment's issuer pins and the custody
160
+ helper (no keys or credentials).
160
161
  --action <action> mls: status (public device state), provision (create the
161
162
  portable vault, once), enroll, renew, publish (this device's
162
163
  KeyPackage for --room) or refresh (catch up --room). It never
package/dist/index.d.ts CHANGED
@@ -20,3 +20,4 @@ export { CanonApiError, clearPendingRegistration, ensureAgentProfile, resumeRegi
20
20
  export type { CommunicateInput, CommunicateResult, EnsureAgentProfileOptions, MediaAttachment, RegistrationCredentials, RegistrationProgress, RegistrationSession, RegistrationStatus, RegistrationStore, ResumableMediaUploadResult, ResumeRegistrationOptions, VideoProcessingErrorCode, VideoProcessingStatus, } from './internal/core/index.js';
21
21
  export { DEFAULT_ANTHROPIC_REQUEST_HEADROOM_BYTES, DEFAULT_MEDIA_CACHE_DIR, DEFAULT_MEDIA_MATERIALIZATION_BYTES, MAX_ANTHROPIC_IMAGE_RAW_BYTES, MAX_ANTHROPIC_REQUEST_BYTES, MAX_CANON_MEDIA_BYTES, getCodexImagePath, getMessageAttachments, inferUploadMimeType, isAnthropicImageAttachment, conversationMediaCacheDir, materializeAttachment, materializeMessageMedia, materializeReplyContextMedia, resolveAttachmentMimeType, sendMediaFileMessage, toAnthropicImageBlock, toAnthropicImageBlocksWithinBudget, uploadMediaFile, } from './media.js';
22
22
  export type { AnthropicImageBlock, AnthropicImageBudgetOptions, AnthropicImageMimeType, MaterializeMediaOptions, MaterializedCanonAttachment, MaterializedCanonReplyContext, ReplyWithFileOptions, UploadMediaFileOptions, } from './media.js';
23
+ export type { AgentWakeConfiguration } from './internal/backend-contracts/index.js';
package/dist/index.js CHANGED
@@ -18,7 +18,7 @@ import {
18
18
  runMlsDevSetup,
19
19
  runtimeDescriptor,
20
20
  startRelayChannel
21
- } from "./chunks/chunk-LFTYXZUY.js";
21
+ } from "./chunks/chunk-HI2SVCUL.js";
22
22
  import {
23
23
  CanonApiError,
24
24
  DEFAULT_ANTHROPIC_REQUEST_HEADROOM_BYTES,
@@ -43,8 +43,8 @@ import {
43
43
  toAnthropicImageBlock,
44
44
  toAnthropicImageBlocksWithinBudget,
45
45
  uploadMediaFile
46
- } from "./chunks/chunk-FCG2Z57F.js";
47
- import "./chunks/chunk-J63YVAKA.js";
46
+ } from "./chunks/chunk-SKFI2MJA.js";
47
+ import "./chunks/chunk-5MI6Q3VS.js";
48
48
  export {
49
49
  CanonApiError,
50
50
  ConnectorRelayError,
@@ -0,0 +1,11 @@
1
+ /** Operator-installed plugin callback. Signing material is restricted secret
2
+ * configuration and never part of conversations, catalogs or event payloads. */
3
+ export interface AgentWakeConfiguration {
4
+ callbackUrl: string;
5
+ secret: string;
6
+ conversationIds: string[];
7
+ }
8
+ export interface AgentWakeHint {
9
+ type: 'wake';
10
+ reason: 'typing' | 'event';
11
+ }
@@ -11,7 +11,7 @@ export declare const MLS_DEV_UNSUPPORTED_CONTENT_ACTIONS: readonly ["forward_mes
11
11
  export declare const ENDPOINT_CONTENT_ACTIONS: readonly ["mls_application", "mls_group_control", "initialize_endpoint", "append_artifact_command", "retain_artifact_asset", "sync_phone_contacts", "update_agent_behavior_policy", "update_conversation_agent_behavior_policy", "send_message", "delete_message", "forward_message", "react", "verb_execute", "send_contextual_message", "create_interaction", "consume_interaction", "start_conversation", "create_conversation", "add_member", "remove_member", "leave_conversation", "update_member_role", "update_conversation", "rename_conversation", "update_topic", "update_group_avatar", "mark_read", "cancel_queued_message", "set_conversation_preferences", "set_block", "save_contact", "remove_contact", "respond_interaction", "session_control", "approve_contact_request", "reject_contact_request", "cancel_direct_request", "update_message_disposition", "create_upload", "finalize_upload", "delete_upload", "retry_video_processing", "stage_media_message", "cancel_media_message", "renew_media_message_upload", "finalize_memo_stream", "abort_memo_stream", "create_voice_session", "join_voice_session", "decline_voice_session", "end_voice_session", "consume_runtime_signal", "consume_runtime_primitive", "generate_image"];
12
12
  export declare const ENDPOINT_CONTENT_FUNCTION_QUERIES: readonly ["mls_group_feed", "mls_group_controls", "mls_group_admissions", "mls_interactions", "list_artifacts", "get_artifact_log", "get_artifact_asset", "discover_agents", "get_unread_summary", "peek_runtime_signal", "list_runtime_primitives", "get_agent_behavior_policy", "get_conversation_agent_behavior_policy", "get_ephemeral", "list_voice_sessions", "list_contact_requests", "list_conversations", "list_messages", "get_messages", "admission_fences", "list_contacts", "get_contact", "get_members", "resolve_admission", "resolve_group_admission", "get_runtime_card_state", "conversation", "conversation_changes", "get_upload", "get_media_message", "get_voice_session", "get_image_generation", "get_image_generation_status", "same_owner_agents"];
13
13
  export declare const ENDPOINT_CONTENT_QUERIES: readonly ["mls_group_feed", "mls_group_controls", "mls_group_admissions", "mls_interactions", "list_artifacts", "get_artifact_log", "get_artifact_asset", "discover_agents", "get_unread_summary", "peek_runtime_signal", "list_runtime_primitives", "get_agent_behavior_policy", "get_conversation_agent_behavior_policy", "get_ephemeral", "list_voice_sessions", "list_contact_requests", "list_conversations", "list_messages", "get_messages", "admission_fences", "list_contacts", "get_contact", "get_members", "resolve_admission", "resolve_group_admission", "get_runtime_card_state", "conversation", "conversation_changes", "get_upload", "get_media_message", "get_voice_session", "get_image_generation", "get_image_generation_status", "same_owner_agents", "admit_messages"];
14
- export declare const ENDPOINT_CONTENT_EPHEMERALS: readonly ["create_memo_stream", "publish_presence", "publish_typing", "publish_streaming", "publish_turn", "publish_runtime_status", "publish_session_state", "publish_runtime_info", "publish_runtime_activity", "append_memo_chunk"];
14
+ export declare const ENDPOINT_CONTENT_EPHEMERALS: readonly ["prewarm_agents", "create_memo_stream", "publish_presence", "publish_typing", "publish_streaming", "publish_turn", "publish_runtime_status", "publish_session_state", "publish_runtime_info", "publish_runtime_activity", "append_memo_chunk"];
15
15
  export declare const ENDPOINT_CONTENT_EPHEMERAL_FAMILIES: readonly ["typing", "streaming", "turn-state", "agent-session", "runtime-card-state", "memo-streams", "runtime-info", "runtime-activity", "runtime-suppression", "runtime-silence", "runtime-attention", "call-attention", "agent-runtime", "presence"];
16
16
  export declare const ENDPOINT_CONTENT_EVENTS: readonly ["connected", "heartbeat", "agent.context", "replay.expired", "error", "participation.suppressed", "artifact.changed", "message.created", "message.snapshot", "message.updated", "message.deleted", "message.media_terminal", "conversation.updated", "conversation.changed", "conversation.removed", "conversations.changed", "contacts.changed", "contact.added", "contact.updated", "contact.removed", "presence", "typing", "runtime.updated", "runtime.control", "turn.updated", "voice.session.started", "voice.session.ended", ...string[]];
17
17
  export declare class EndpointContentPolicyError extends Error {
@@ -52,3 +52,4 @@ export * from './mlsDevGroupFeed.js';
52
52
  export * from './mlsDevFeedReceipt.js';
53
53
  export * from './mlsDevInteraction.js';
54
54
  export * from './mlsPrincipalAlias.js';
55
+ export type { AgentWakeConfiguration, AgentWakeHint } from './agentWake.js';
@@ -1,3 +1,4 @@
1
+ import type { AgentWakeConfiguration } from '../backend-contracts/index.js';
1
2
  import { type CanonMessage, type CanonConversation, type CanonUnifiedDiff, type CanonMessagesPage, type AgentContext, type MediaAttachment, type SendMessageOptions, type RegistrationStatus, type AgentOriginDeclaration, type SetRuntimeTurnOptions, type SetStreamingOptions } from './types.js';
2
3
  import type { RuntimeInputKind } from './runtime-cards.js';
3
4
  import type { ApprovalNativeRequestMetadata, ApprovalRequestCategory, ApprovalRequestDetail, ApprovalRisk, SessionRule } from './approval-types.js';
@@ -37,6 +38,11 @@ export declare class CanonClient {
37
38
  private endpointPublish;
38
39
  private authHeaders;
39
40
  getAgentMe(): Promise<AgentContext>;
41
+ /** Register (or explicitly remove) this installed agent's signed readiness callback. */
42
+ configureWake(configuration: AgentWakeConfiguration | null): Promise<{
43
+ revision?: string;
44
+ disabled?: true;
45
+ }>;
40
46
  discoverAgents(input?: DiscoverAgentsInput): Promise<DiscoverAgentsResult>;
41
47
  /** Targeted membership read; session hosts must not poll the entire account. */
42
48
  getConversation(conversationId: string): Promise<CanonConversation | null>;
@@ -149,6 +149,10 @@ export interface MlsDevGroupBridge {
149
149
  /** A control of this device's settled, accepted or lost to another device's: what waited on it can go on now. A hint
150
150
  * announcing the room's change may have come while it was pending, and none may follow (D2 holds the room's sends). */
151
151
  controlSettled?(conversationId: string): void;
152
+ /** A fetch applied entries of the room's feed: its encrypted history, read locally, has changed. */
153
+ historyChanged?(conversationId: string): void;
154
+ /** This device's event stream is connected, so each room's hints announce what Canon accepts into its feed. */
155
+ streamLive?(): boolean;
152
156
  /** Reads and applies the room's conversation snapshot from Canon, for openings of members admitted since. */
153
157
  readRoom?(conversationId: string): Promise<unknown>;
154
158
  }
@@ -161,13 +165,16 @@ export declare function createMlsDevGroups(options: MlsDevDeviceClientOptions, c
161
165
  * encrypted room (mls-payload-v3.md §4). Items and cards are not forwarded yet. */
162
166
  forwardText(value: unknown, operationId: string, guardId?: string): Promise<EndpointOperation>;
163
167
  sendText(value: unknown, operationId: string, guardId?: string): Promise<EndpointOperation>;
168
+ /** A room this device holds caught up is read as it holds it: what fetches the room's new entries says so
169
+ * (bridge.historyChanged). Otherwise a read fetches the feed first, which the polling fallback and the connected
170
+ * stream's minute-long safety poll rely on. */
164
171
  queryTextHistory(conversationId: string, name: string, parameters: Record<string, unknown>): Promise<{
165
172
  messages: Record<string, unknown>[];
166
173
  nextBefore?: string | null;
167
174
  missingMessageIds?: string[];
168
175
  }>;
169
176
  syncText(conversationId: string, options?: EndpointSyncOptions): Promise<EndpointSyncPage>;
170
- isTextReady(conversationId: string): Promise<boolean>;
177
+ isTextReady: (conversationId: string) => Promise<boolean>;
171
178
  readAcceptedReceipt(operation: EndpointOperation): Promise<EndpointOperationReceipt | undefined>;
172
179
  settleReceipt(operation: EndpointOperation, receipt: EndpointOperationReceipt): Promise<boolean>;
173
180
  };
@@ -29,8 +29,8 @@ export declare const mlsDevTextDeletedKey: (conversationId: string, messageId: s
29
29
  export declare const mlsDevTextReactionsKey: (conversationId: string, messageId: string) => string;
30
30
  /**
31
31
  * Its sender deleted a message for everyone. The decrypted text is erased now, and the tombstone keeps it erased if the
32
- * feed brings the message again, on catch-up or replay. An own send whose echo is not applied yet keeps its text until
33
- * the echo applies (the echo is checked against it); the history already shows it deleted. Cards are not deleted this
32
+ * feed brings the message again, on catch-up or replay. An own send Canon has not accepted yet keeps its text until its
33
+ * receipt or echo settles it; the history already shows it deleted. Cards are not deleted this
34
34
  * way: Canon refuses to delete an encrypted card, which is withdrawn instead. Returns whether anything changed.
35
35
  */
36
36
  export declare function deleteMlsDevTextMessage(tx: EndpointStoreTransaction, conversationId: string, messageId: string): Promise<boolean>;
package/dist/media.js CHANGED
@@ -18,8 +18,8 @@ import {
18
18
  toAnthropicImageBlock,
19
19
  toAnthropicImageBlocksWithinBudget,
20
20
  uploadMediaFile
21
- } from "./chunks/chunk-FCG2Z57F.js";
22
- import "./chunks/chunk-J63YVAKA.js";
21
+ } from "./chunks/chunk-SKFI2MJA.js";
22
+ import "./chunks/chunk-5MI6Q3VS.js";
23
23
  export {
24
24
  DEFAULT_ANTHROPIC_REQUEST_HEADROOM_BYTES,
25
25
  DEFAULT_MEDIA_CACHE_DIR,
@@ -1,3 +1,4 @@
1
+ import type { AgentWakeConfiguration } from '../internal/backend-contracts/index.js';
1
2
  import { type ConnectorRelayPage, type ConnectorRelaySent } from '../internal/backend-contracts/index.js';
2
3
  import { type CanonEndpoint, type CanonRuntimeDescriptor, type CommunicateInput, type CommunicateResult } from '../internal/core/index.js';
3
4
  import { type RunnerMlsOptions } from './mls.js';
@@ -11,6 +12,8 @@ export interface RunnerOptions {
11
12
  version: string;
12
13
  };
13
14
  apiKey: string;
15
+ /** Signed, content-free wake callback installed by the agent. Null explicitly disables it. */
16
+ wake?: AgentWakeConfiguration | null;
14
17
  environmentId: string;
15
18
  /** CANON_HOME: a private 0700 directory, checked before anything is opened. */
16
19
  home: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@canonmsg/agent-sdk",
3
- "version": "12.2.2",
3
+ "version": "12.3.1",
4
4
  "description": "Canon Agent SDK — build AI agents that participate in Canon conversations",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",