@canonmsg/agent-sdk 12.3.2 → 12.5.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.
@@ -4065,6 +4065,9 @@ __name(normalizePublicRuntimeFacts, "normalizePublicRuntimeFacts");
4065
4065
 
4066
4066
  // ../backend-contracts/src/endpointContentPolicy.ts
4067
4067
  var MLS_DEV_UNSUPPORTED_CONTENT_ACTIONS = ["forward_message"];
4068
+ var MLS_DEV_DISCLOSED_PROCESSING_FIELDS = {
4069
+ generate_image: ["conversationId", "requestId", "prompt", "sourceUploadId"]
4070
+ };
4068
4071
  var ENDPOINT_CONTENT_ACTIONS = [
4069
4072
  "mls_application",
4070
4073
  "mls_group_control",
@@ -4651,6 +4654,13 @@ function text4(value) {
4651
4654
  return value;
4652
4655
  }
4653
4656
  __name(text4, "text");
4657
+ function selection(value) {
4658
+ const args = record3(value, ["mode"], ["conversationId"]);
4659
+ if ((args.mode === "new" || args.mode === "latest_or_new") && args.conversationId === void 0) return { mode: args.mode };
4660
+ if (args.mode === "specific") return { mode: "specific", conversationId: room(args.conversationId) };
4661
+ return invalid11("selection");
4662
+ }
4663
+ __name(selection, "selection");
4654
4664
  function limit(value) {
4655
4665
  if (value === void 0) return void 0;
4656
4666
  if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 1 || value > 100) invalid11("limit");
@@ -4700,6 +4710,15 @@ function parseConnectorRelayRequest(value) {
4700
4710
  const args = record3(body.args, ["conversationId", "messageId", "text"]);
4701
4711
  return { ...head, op: "send", args: { conversationId: room(args.conversationId), messageId: id6(args.messageId, "messageId"), text: text4(args.text) } };
4702
4712
  }
4713
+ case "start": {
4714
+ const args = record3(body.args, ["targetUserId", "messageId", "text"], ["selection"]);
4715
+ return { ...head, op: "start", args: {
4716
+ targetUserId: doc(args.targetUserId, "targetUserId"),
4717
+ messageId: id6(args.messageId, "messageId"),
4718
+ text: text4(args.text),
4719
+ ...args.selection === void 0 ? {} : { selection: selection(args.selection) }
4720
+ } };
4721
+ }
4703
4722
  case "credential": {
4704
4723
  const args = record3(body.args, ["installationId", "generation", "credential"]);
4705
4724
  if (!Number.isSafeInteger(args.generation) || args.generation < 1) invalid11("generation");
@@ -5381,6 +5400,7 @@ export {
5381
5400
  normalizePublicRuntimeDescriptor,
5382
5401
  normalizePublicRuntimeFacts,
5383
5402
  MLS_DEV_UNSUPPORTED_CONTENT_ACTIONS,
5403
+ MLS_DEV_DISCLOSED_PROCESSING_FIELDS,
5384
5404
  endpointOperationRequestRetention,
5385
5405
  endpointContentBoundary,
5386
5406
  canonicalEndpointJson,
@@ -8,6 +8,7 @@ import {
8
8
  MAX_PUBLIC_RUNTIME_COMMANDS,
9
9
  MLS_DEV_CREDENTIAL_MAX_BYTES,
10
10
  MLS_DEV_CREDENTIAL_RECONCILIATION_MAX_BYTES,
11
+ MLS_DEV_DISCLOSED_PROCESSING_FIELDS,
11
12
  MLS_DEV_ENROLLMENT_MAX_BYTES,
12
13
  MLS_DEV_GROUP_RECEIPT_CLOCK_SKEW,
13
14
  MLS_DEV_INTERACTION_LIMITS,
@@ -109,7 +110,7 @@ import {
109
110
  readMembershipChangeMetadata,
110
111
  resolveAllowedUploadMimeType,
111
112
  sanitizeDownloadFileName
112
- } from "./chunk-5MI6Q3VS.js";
113
+ } from "./chunk-C2WJT3VX.js";
113
114
 
114
115
  // src/media.ts
115
116
  import { createWriteStream as createWriteStream2 } from "node:fs";
@@ -2602,6 +2603,8 @@ async function assertMlsDevPlaintextAllowed(tx, intent2, lane = "operation.reque
2602
2603
  }), "failure");
2603
2604
  const action = intent2.action === "verb_execute" ? intent2.body.verb : intent2.action;
2604
2605
  if (typeof action === "string" && MLS_DEV_UNSUPPORTED_CONTENT_ACTIONS.some((unsupported) => unsupported === action)) throw failure2();
2606
+ const disclosed = lane === "operation.request" && Object.hasOwn(MLS_DEV_DISCLOSED_PROCESSING_FIELDS, intent2.action) ? MLS_DEV_DISCLOSED_PROCESSING_FIELDS[intent2.action] : void 0;
2607
+ if (disclosed && !intent2.envelope && Object.keys(intent2.body).every((field) => disclosed.includes(field))) return;
2605
2608
  const payload = lane === "operation.request" ? { body: { encoding: "json", value: intent2.body }, ...intent2.envelope ? { envelope: intent2.envelope } : {} } : intent2.body;
2606
2609
  let boundary;
2607
2610
  try {
@@ -5216,7 +5219,7 @@ function createMlsDevDeviceClient(options, context) {
5216
5219
  const store = guardedStore();
5217
5220
  const fence = /* @__PURE__ */ __name(() => store.transaction(async () => {
5218
5221
  }), "fence");
5219
- async function initialized() {
5222
+ async function initialized(storedPrincipalId) {
5220
5223
  context.assertOpen();
5221
5224
  const { identity, installationGeneration } = await context.ready();
5222
5225
  await fence();
@@ -5227,7 +5230,7 @@ function createMlsDevDeviceClient(options, context) {
5227
5230
  const binding = parseMlsDevDeviceBinding({
5228
5231
  version: 1,
5229
5232
  environmentId: identity.environmentId,
5230
- principal: { ...principal, id: await context.mlsPrincipalId() },
5233
+ principal: { ...principal, id: storedPrincipalId ?? await context.mlsPrincipalId() },
5231
5234
  installationId: identity.installationId
5232
5235
  });
5233
5236
  const coordinator = /* @__PURE__ */ __name((journal) => createMlsDevDeviceCoordinator({
@@ -5514,6 +5517,14 @@ function createMlsDevDeviceClient(options, context) {
5514
5517
  }
5515
5518
  }
5516
5519
  __name(ensureReady, "ensureReady");
5520
+ async function revoke() {
5521
+ const { coordinator, transport: http } = await initialized();
5522
+ const device = coordinator(store), view3 = await device.read();
5523
+ if (view3.status === "revoked" || !view3.registration) return view3;
5524
+ const receipt = await http.revokeCredential({ version: 1, deviceId: view3.registration.device.deviceId }, context.signal);
5525
+ return device.acceptRevocation(receipt);
5526
+ }
5527
+ __name(revoke, "revoke");
5517
5528
  function run(goal) {
5518
5529
  context.assertOpen();
5519
5530
  const preceding = running;
@@ -5540,12 +5551,26 @@ function createMlsDevDeviceClient(options, context) {
5540
5551
  return promise;
5541
5552
  }
5542
5553
  __name(run, "run");
5554
+ function read(options2) {
5555
+ return context.run(async () => {
5556
+ let storedPrincipalId;
5557
+ if (options2?.localOnly) {
5558
+ await context.ready();
5559
+ const saved = await store.transaction((tx) => tx.get("mls-dev-device/identity"));
5560
+ if (!saved) return void 0;
5561
+ storedPrincipalId = parseMlsDevDeviceBinding(saved.binding).principal.id;
5562
+ }
5563
+ return (await initialized(storedPrincipalId)).coordinator(store).read();
5564
+ });
5565
+ }
5566
+ __name(read, "read");
5543
5567
  return Object.freeze({
5544
- read: /* @__PURE__ */ __name(() => context.run(async () => (await initialized()).coordinator(store).read()), "read"),
5568
+ read,
5545
5569
  enroll: /* @__PURE__ */ __name(() => run("enroll"), "enroll"),
5546
5570
  renew: /* @__PURE__ */ __name(() => run("renew"), "renew"),
5547
5571
  reconcile: /* @__PURE__ */ __name(() => run("reconcile"), "reconcile"),
5548
- ensureReady: /* @__PURE__ */ __name(() => run("ready"), "ensureReady")
5572
+ ensureReady: /* @__PURE__ */ __name(() => run("ready"), "ensureReady"),
5573
+ revoke: /* @__PURE__ */ __name(() => context.run(revoke), "revoke")
5549
5574
  });
5550
5575
  }
5551
5576
  __name(createMlsDevDeviceClient, "createMlsDevDeviceClient");
@@ -7239,7 +7264,11 @@ function createMlsDevGroups(options, context, keyPackages, bridge) {
7239
7264
  const group = envelope((await readPreparationSnapshot(tx, scope4(conversationId))).protocolState, init.binding, conversationId);
7240
7265
  const accounts = mlsRoomAccounts(room, init.binding.environmentId);
7241
7266
  const admitted = new Set((group && !group.removed ? group.roster : []).map((entry) => mlsAccountOf(accounts, entry.device.principal)));
7242
- const waiting = [...Array.isArray(room.memberIds) ? room.memberIds : [], ...typeof room.mlsInviteeId === "string" ? [room.mlsInviteeId] : []].filter((memberId) => typeof memberId === "string" && !admitted.has(memberId));
7267
+ const waiting = [.../* @__PURE__ */ new Set([
7268
+ ...Array.isArray(room.memberIds) ? room.memberIds : [],
7269
+ ...typeof room.mlsInviteeId === "string" ? [room.mlsInviteeId] : [],
7270
+ ...Array.isArray(room.mlsPendingFounderIds) ? room.mlsPendingFounderIds : []
7271
+ ])].filter((memberId) => typeof memberId === "string" && !admitted.has(memberId));
7243
7272
  return Object.freeze({ held: waiting.length > 0, waitingPrincipalIds: Object.freeze(waiting) });
7244
7273
  });
7245
7274
  }
@@ -8803,9 +8832,28 @@ function createEndpoint(options) {
8803
8832
  }
8804
8833
  __name(sendDeferredOpeners, "sendDeferredOpeners");
8805
8834
  const stubReads = /* @__PURE__ */ new Map();
8835
+ const stubNames = /* @__PURE__ */ new Map();
8806
8836
  let liveActionCounter = 0;
8807
8837
  const liveActions = /* @__PURE__ */ new Map();
8808
8838
  const stubKey = /* @__PURE__ */ __name((conversationId, messageId) => `${conversationId}\0${messageId}`, "stubKey");
8839
+ function rememberStubName(conversationId, stub) {
8840
+ const name = typeof stub.senderName === "string" ? stub.senderName.trim() : "";
8841
+ if (typeof stub.senderId !== "string" || !stub.senderId || !name || name === stub.senderId || name.length > 80) return;
8842
+ const key2 = stubKey(conversationId, stub.id);
8843
+ stubNames.delete(key2);
8844
+ stubNames.set(key2, { senderId: stub.senderId, name });
8845
+ if (stubNames.size > 1e3) stubNames.delete(stubNames.keys().next().value);
8846
+ }
8847
+ __name(rememberStubName, "rememberStubName");
8848
+ function withStubNames(conversationId, history) {
8849
+ const messages = history.messages;
8850
+ if (!Array.isArray(messages)) return history;
8851
+ return { ...history, messages: messages.map((message) => {
8852
+ const named = typeof message.id === "string" ? stubNames.get(stubKey(conversationId, message.id)) : void 0;
8853
+ return named && named.senderId === message.senderId ? { ...message, senderName: named.name } : message;
8854
+ }) };
8855
+ }
8856
+ __name(withStubNames, "withStubNames");
8809
8857
  function forgetStubReads(conversationId) {
8810
8858
  for (const key2 of stubReads.keys()) if (key2.startsWith(`${conversationId}\0`)) stubReads.delete(key2);
8811
8859
  }
@@ -8823,6 +8871,7 @@ function createEndpoint(options) {
8823
8871
  } catch {
8824
8872
  return false;
8825
8873
  }
8874
+ for (const stub of stubs) if (typeof stub.id === "string" && due.includes(stub.id)) rememberStubName(conversationId, { ...stub, id: stub.id });
8826
8875
  return store.transaction(async (tx) => {
8827
8876
  let changed = false;
8828
8877
  for (const stub of stubs) {
@@ -9491,6 +9540,13 @@ function createEndpoint(options) {
9491
9540
  createdAt: timestamp,
9492
9541
  updatedAt: timestamp
9493
9542
  };
9543
+ if (normalized.action === "start_conversation") {
9544
+ const opener = normalized.body.opener;
9545
+ if (opener?.withheld && typeof opener.messageId === "string") {
9546
+ const pending = await tx.get(`${openerPrefix()}/${encodeURIComponent(opener.messageId)}`);
9547
+ if (pending?.start?.operationId === operationId) created.openerFingerprint = pending.openerFingerprint;
9548
+ }
9549
+ }
9494
9550
  return commitOperationRecord(tx, created);
9495
9551
  }, "commitOperation");
9496
9552
  const operation = sensitive ? await store.transaction(async (tx) => {
@@ -9667,7 +9723,7 @@ function createEndpoint(options) {
9667
9723
  const room = normalized.conversationId, read = /* @__PURE__ */ __name(() => mlsDevGroups.queryTextHistory(room, name, normalized), "read");
9668
9724
  const history = await read();
9669
9725
  const shown = (history.messages ?? []).map((entry) => entry.id).filter((id2) => typeof id2 === "string");
9670
- return await recoverMlsDevMessageActions(room, shown) ? await read() : history;
9726
+ return withStubNames(room, await recoverMlsDevMessageActions(room, shown) ? await read() : history);
9671
9727
  }
9672
9728
  }
9673
9729
  if (name === "get_runtime_card_state" && await mlsDevInteractionRoom(normalized.conversationId, false)) {
@@ -9743,6 +9799,7 @@ function createEndpoint(options) {
9743
9799
  },
9744
9800
  async startWithheldOpener(input) {
9745
9801
  assertOpen();
9802
+ input = copyJson(input);
9746
9803
  await initialization;
9747
9804
  if (typeof input.messageId !== "string" || !input.messageId || typeof input.text !== "string")
9748
9805
  throw new EndpointError("Invalid deferred opening line", { status: 400, code: "INVALID_DEFERRED_OPENER", retryable: false });
@@ -9753,9 +9810,16 @@ function createEndpoint(options) {
9753
9810
  opener: { withheld: true, messageId: input.messageId },
9754
9811
  ...input.selection ? { selection: copyJson(input.selection) } : {}
9755
9812
  };
9813
+ const openerFingerprint = await hash3(canonicalEndpointJson([body, input.text]));
9756
9814
  const recordKey = `${openerPrefix()}/${encodeURIComponent(input.messageId)}`;
9757
9815
  await store.transaction(async (tx) => {
9758
9816
  await assertCurrentInstallation(tx);
9817
+ const operation = await tx.get(key(operationId));
9818
+ if (operation) {
9819
+ if (operation.openerFingerprint !== openerFingerprint)
9820
+ throw new EndpointError("Opening line ID was already used for different content", { status: 409, code: "OPERATION_ID_COLLISION", retryable: false, operationId });
9821
+ return;
9822
+ }
9759
9823
  const existing = await tx.get(recordKey);
9760
9824
  if (existing && (existing.text !== input.text || canonicalEndpointJson(existing.start?.body) !== canonicalEndpointJson(body)))
9761
9825
  throw new EndpointError("Opening line ID was already used for different content", { status: 409, code: "OPERATION_ID_COLLISION", retryable: false, operationId });
@@ -9764,6 +9828,7 @@ function createEndpoint(options) {
9764
9828
  messageId: input.messageId,
9765
9829
  text: input.text,
9766
9830
  requestId: null,
9831
+ openerFingerprint,
9767
9832
  operationId: `send-opener-${digest.slice(0, 48)}`,
9768
9833
  start: { operationId, body }
9769
9834
  });
@@ -10096,7 +10161,7 @@ async function acquireAgentEndpoint(input) {
10096
10161
  }
10097
10162
  const requestedMls = input.options.mlsDev;
10098
10163
  const mls = requestedMls ? await (async () => {
10099
- const crypto2 = await import("./mls-crypto-D5YAGYJ3.js");
10164
+ const crypto2 = await import("./mls-crypto-Y44BOSEM.js");
10100
10165
  const configuration = crypto2.parseMlsDevClientConfiguration(requestedMls.configuration);
10101
10166
  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");
10102
10167
  const custody = requestedMls.custody;
@@ -14097,6 +14162,7 @@ function getCodexImagePath(attachment) {
14097
14162
  __name(getCodexImagePath, "getCodexImagePath");
14098
14163
 
14099
14164
  export {
14165
+ NodeMlsDevCustodyError,
14100
14166
  createNodeMlsDevCustody,
14101
14167
  createPortableNodeMlsDevCustody,
14102
14168
  resolveCanonRuntimeConnection,
@@ -2,7 +2,7 @@ import {
2
2
  createMlsDevCredentialVerifier,
3
3
  mlsDevDeviceHttpConfiguration,
4
4
  parseMlsDevClientConfiguration
5
- } from "./chunk-5MI6Q3VS.js";
5
+ } from "./chunk-C2WJT3VX.js";
6
6
  export {
7
7
  createMlsDevCredentialVerifier,
8
8
  mlsDevDeviceHttpConfiguration,
package/dist/cli.js CHANGED
@@ -9,6 +9,7 @@ import {
9
9
  codexDriver,
10
10
  connectorDriver,
11
11
  createRunner,
12
+ defaultMlsDevice,
12
13
  errandJournal,
13
14
  errandsDirectory,
14
15
  loadClaudeQuery,
@@ -22,7 +23,7 @@ import {
22
23
  runnerLockName,
23
24
  startRelayChannel,
24
25
  writeRelayCredential
25
- } from "./chunks/chunk-JCBQCO3F.js";
26
+ } from "./chunks/chunk-3KFVBOVI.js";
26
27
  import {
27
28
  CanonApiError,
28
29
  CanonClient,
@@ -33,11 +34,11 @@ import {
33
34
  resolveCanonRuntimeConnection,
34
35
  runCli,
35
36
  verifyCanonRuntimeConnection
36
- } from "./chunks/chunk-RFNYPZXD.js";
37
+ } from "./chunks/chunk-TFSURFF6.js";
37
38
  import {
38
39
  __name,
39
40
  isCanonClientVersion
40
- } from "./chunks/chunk-5MI6Q3VS.js";
41
+ } from "./chunks/chunk-C2WJT3VX.js";
41
42
 
42
43
  // src/cli.ts
43
44
  import { realpathSync } from "node:fs";
@@ -66,9 +67,9 @@ Usage:
66
67
  canon-agent run --driver codex [--cwd <dir>] [--model <id>] [--sandbox <mode>]
67
68
  [--approval-policy <policy>] [--codex-path <path>] [--profile <name>]
68
69
  [--environment <id>]
69
- canon-agent run --driver connector --relay-config <path> --mls-dev-config <path>
70
+ canon-agent run --driver connector --relay-config <path> [--mls-dev-config <path>]
70
71
  [--profile <name>] [--environment <id>]
71
- canon-agent mls --action <action> --mls-dev-config <path> [--room <id>]
72
+ canon-agent mls --action <action> [--mls-dev-config <path>] [--room <id>]
72
73
  [--relay-config <path>] [--profile <name>] [--environment <id>]
73
74
  canon-agent tell --room <id> --text <text> (--agent <id> | --profile <name>)
74
75
  [--environment <id>] [--wait]
@@ -80,10 +81,12 @@ Usage:
80
81
  canon-agent errands [--follow] [--cancel <errand id>] (--agent <id> | --profile <name>)
81
82
  [--environment <id>]
82
83
 
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.
84
+ Encrypted rooms need the agent's MLS device. On macOS (Apple silicon) and Linux
85
+ (x64, arm64) the runner keeps it in $CANON_HOME with the custody helper this package
86
+ ships, and sets it up on the agent's first start: nothing to configure. Every run
87
+ command also takes --mls-dev-config <path>, an operator's own custody instead. With
88
+ neither, the agent refuses encrypted rooms, and where Canon encrypts every new
89
+ conversation it does not start.
87
90
 
88
91
  Team rooms (an owner's agents working together): claude and codex also take
89
92
  [--instructions <file>] [--trust-agent <agentId>]...; every run command takes
@@ -146,20 +149,22 @@ Flags:
146
149
  marks destructive (Claude Code's default-to-no prompts,
147
150
  Codex's risky commands and deletions) for the rest of the
148
151
  session. Off by default: each such action asks.
149
- --relay-config <path> connector (Dev only): run as Canon's class-C relay for one agent
152
+ --relay-config <path> connector: run as Canon's class-C relay for one agent
150
153
  connected through Canon's MCP connector. A private JSON file
151
154
  naming the agent, where the channel to Canon's Functions
152
155
  listens, and the path of the channel secret. Canon delivers
153
156
  the relay's own credential over that channel when the owner
154
157
  enables the relay (no API key is used); it is kept in
155
158
  $CANON_HOME/relay. The relay serves only encrypted rooms,
156
- which are then not end-to-end encrypted to that agent. With
157
- "mls", it sets up the relay's device with that credential.
158
- --mls-dev-config <p> The operator's MLS configuration: an absolute path to a private
159
- JSON file with the environment's issuer pins and the custody
160
- helper (no keys or credentials).
161
- --action <action> mls: status (public device state), provision (create the
162
- portable vault, once), enroll, renew, publish (this device's
159
+ which are then not end-to-end encrypted to that agent. It sets
160
+ up the relay's device with that credential.
161
+ --mls-dev-config <p> An operator's own MLS configuration, instead of the device the
162
+ runner keeps itself: an absolute path to a private JSON file
163
+ with the environment's issuer pins and the custody helper (no
164
+ keys or credentials).
165
+ --action <action> mls: status (public device state), setup (whatever is left of
166
+ provision then enroll), provision (create the portable vault,
167
+ once), enroll, renew, publish (this device's
163
168
  KeyPackage for --room) or refresh (catch up --room). It never
164
169
  starts a model, and uses the same home as "run": stop the
165
170
  agent first.
@@ -232,7 +237,6 @@ async function main() {
232
237
  }
233
238
  if (values.driver === "code" && !values.module) throw new UsageError("--driver code needs --module <path>.");
234
239
  if (values.driver === "connector" !== !!values["relay-config"]) throw new UsageError("--relay-config goes with --driver connector, which needs it.");
235
- if (values.driver === "connector" && !values["mls-dev-config"]) throw new UsageError("--driver connector needs --mls-dev-config <path>: the relay holds the agent's MLS device.");
236
240
  if (!["claude", "codex"].includes(values.driver) && (values.instructions !== void 0 || values["trust-agent"]?.length)) {
237
241
  throw new UsageError("--instructions and --trust-agent go with --driver claude or codex.");
238
242
  }
@@ -254,7 +258,7 @@ async function main() {
254
258
  if (trustedAgents.some((id) => !/^[\w.:-]{1,200}$/.test(id))) throw new UsageError("--trust-agent takes an agent id.");
255
259
  const instructions = values.instructions !== void 0 ? await loadInstructions(resolve(values.instructions)) : void 0;
256
260
  const { environmentId, home, profile, apiKey } = await settings(values);
257
- const mls = values["mls-dev-config"] ? await loadMlsDevConfiguration(values["mls-dev-config"], { environmentId }) : void 0;
261
+ const mls = await mlsDevice(values, environmentId, home);
258
262
  const baseUrl = process.env.CANON_BASE_URL || profile?.baseUrl;
259
263
  const streamUrl = process.env.CANON_STREAM_URL || profile?.streamUrl;
260
264
  let driver;
@@ -352,7 +356,8 @@ async function runRelay(values, team) {
352
356
  channel.serve(void 0);
353
357
  await current?.stop();
354
358
  current = void 0;
355
- const mls = await loadMlsDevConfiguration(values["mls-dev-config"], { environmentId });
359
+ const mls = await mlsDevice(values, environmentId, relayRunnerHome(home, stored2.installationId));
360
+ if (!mls) throw new Error(`this host has no MLS custody helper (${process.platform}-${process.arch}): give the relay --mls-dev-config <path>`);
356
361
  const driver = connectorDriver();
357
362
  const runner = await createRunner({
358
363
  apiKey: stored2.credential,
@@ -455,11 +460,15 @@ async function settings(values, { key = true } = {}) {
455
460
  return { environmentId, home, profile, apiKey: process.env.CANON_API_KEY || profile?.apiKey || "" };
456
461
  }
457
462
  __name(settings, "settings");
463
+ async function mlsDevice(values, environmentId, home) {
464
+ if (values["mls-dev-config"]) return loadMlsDevConfiguration(values["mls-dev-config"], { environmentId });
465
+ return defaultMlsDevice(home, environmentId);
466
+ }
467
+ __name(mlsDevice, "mlsDevice");
458
468
  async function mlsSetup(values) {
459
469
  const action = values.action;
460
470
  if (!action || !MLS_SETUP_ACTIONS.includes(action)) throw new UsageError(`--action must be one of ${MLS_SETUP_ACTIONS.join(", ")}.`);
461
471
  if ((action === "publish" || action === "refresh") !== !!values.room) throw new UsageError("--room is needed for publish and refresh, and only for them.");
462
- if (!values["mls-dev-config"]) throw new UsageError("--mls-dev-config <path> is required.");
463
472
  const settled = await settings(values, { key: !values["relay-config"] });
464
473
  const { environmentId, profile } = settled;
465
474
  const relay = values["relay-config"] ? await loadRelayConfiguration(values["relay-config"], { environmentId }) : void 0;
@@ -469,7 +478,8 @@ async function mlsSetup(values) {
469
478
  }
470
479
  const apiKey = stored ? stored.credential : settled.apiKey;
471
480
  const home = stored ? relayRunnerHome(settled.home, stored.installationId) : settled.home;
472
- const config = await loadMlsDevConfiguration(values["mls-dev-config"], { environmentId });
481
+ const config = await mlsDevice(values, environmentId, home);
482
+ if (!config) throw new Error(`@canonmsg/agent-sdk ships no MLS custody helper for ${process.platform}-${process.arch}: give --mls-dev-config <path>.`);
473
483
  let client;
474
484
  let lock;
475
485
  try {
@@ -489,7 +499,7 @@ async function mlsSetup(values) {
489
499
  });
490
500
  const me = await client.getAgentMe();
491
501
  lock = acquireLock(runnerLockName(environmentId, me.agentId), home);
492
- console.log(JSON.stringify(await runMlsDevSetup(await client.getEndpoint(), action, values.room, config.provision)));
502
+ console.log(JSON.stringify(await runMlsDevSetup(await client.getEndpoint(), action, values.room, config.provision, config.provisioned)));
493
503
  } finally {
494
504
  try {
495
505
  await client?.close();
@@ -10,7 +10,7 @@ export interface ConnectorInbox {
10
10
  /**
11
11
  * The first claim gets `runAllowed` and the turn's input, once Canon confirms the agent may still act on it (its
12
12
  * membership and the room's audience, read now, never from a hint); a repeat gets its state, never the input again.
13
- * A claim that cannot be confirmed by `deadline` (epoch ms) is refused as busy and the input stays pending.
13
+ * A claim not confirmed by `deadline` (epoch ms) is busy; retry the same request id to finish that confirmation.
14
14
  */
15
15
  claim(inputId: string, requestId: string, options?: {
16
16
  deadline?: number;
@@ -27,8 +27,9 @@ export interface ConnectorDriver extends RuntimeDriver {
27
27
  /**
28
28
  * The `connector` driver: the runtime is an assistant its provider runs (ChatGPT, Claude.ai and the like), reached
29
29
  * through Canon's remote MCP connector, so a turn waits, as one pending input, for the assistant to claim it and answer
30
- * through the connector's pickup tools (spec §11.3). It keeps no timer: an assistant that never answers is the runner's
31
- * no-progress deadline, which a claim restarts. It never calls Canon itself; the relay channel calls its inbox, and the
32
- * runner confirms each claim with Canon.
30
+ * through the connector's pickup tools (spec §11.3). Such an assistant acts only when its user prompts it, so an input
31
+ * waits as long as it takes: the runner submits it, and starts its no-progress deadline, only at the claim. The driver
32
+ * keeps no timer. It never calls Canon itself; the relay channel calls its inbox, and the runner confirms (and submits)
33
+ * each claim with Canon.
33
34
  */
34
35
  export declare function connectorDriver(): ConnectorDriver;
package/dist/index.d.ts CHANGED
@@ -3,6 +3,8 @@ export type { Runner, RunnerOptions } from './runner/runner.js';
3
3
  export { ConnectorRelayError, DriverSessionError } from './runner/types.js';
4
4
  export { loadMlsDevConfiguration, MLS_SETUP_ACTIONS, mlsDevRoomPackageId, runMlsDevSetup } from './runner/mls.js';
5
5
  export type { MlsSetupAction, RunnerMlsOptions } from './runner/mls.js';
6
+ export { bundledMlsCustodyHelper, MLS_ENVIRONMENT_PINS } from './runner/mls-bundled.js';
7
+ export { managedMlsDevice, type ManagedMlsDevice, type ManagedMlsDeviceOptions } from './runner/mls-managed.js';
6
8
  export type { ActivityEvent, AskAnswer, AskRequest, DriverCapabilities, DriverResult, DriverState, DriverTurn, ErrandFacts, RuntimeDriver, ToolDefinition, ToolResult, TurnInput, TurnInputMessage, TurnTools, } from './runner/types.js';
7
9
  export { errandJournal, errandsDirectory, queueErrand } from './runner/errands.js';
8
10
  export type { ErrandCommand, ErrandInput, ErrandKind, ErrandRecord, ErrandStatus } from './runner/errands.js';
package/dist/index.js CHANGED
@@ -1,7 +1,9 @@
1
1
  import {
2
2
  ConnectorRelayError,
3
3
  DriverSessionError,
4
+ MLS_ENVIRONMENT_PINS,
4
5
  MLS_SETUP_ACTIONS,
6
+ bundledMlsCustodyHelper,
5
7
  checkCodex,
6
8
  claudeDriver,
7
9
  codeDriver,
@@ -13,12 +15,13 @@ import {
13
15
  loadClaudeQuery,
14
16
  loadMlsDevConfiguration,
15
17
  loadRelayConfiguration,
18
+ managedMlsDevice,
16
19
  mlsDevRoomPackageId,
17
20
  queueErrand,
18
21
  runMlsDevSetup,
19
22
  runtimeDescriptor,
20
23
  startRelayChannel
21
- } from "./chunks/chunk-JCBQCO3F.js";
24
+ } from "./chunks/chunk-3KFVBOVI.js";
22
25
  import {
23
26
  CanonApiError,
24
27
  DEFAULT_ANTHROPIC_REQUEST_HEADROOM_BYTES,
@@ -43,8 +46,8 @@ import {
43
46
  toAnthropicImageBlock,
44
47
  toAnthropicImageBlocksWithinBudget,
45
48
  uploadMediaFile
46
- } from "./chunks/chunk-RFNYPZXD.js";
47
- import "./chunks/chunk-5MI6Q3VS.js";
49
+ } from "./chunks/chunk-TFSURFF6.js";
50
+ import "./chunks/chunk-C2WJT3VX.js";
48
51
  export {
49
52
  CanonApiError,
50
53
  ConnectorRelayError,
@@ -55,7 +58,9 @@ export {
55
58
  MAX_ANTHROPIC_IMAGE_RAW_BYTES,
56
59
  MAX_ANTHROPIC_REQUEST_BYTES,
57
60
  MAX_CANON_MEDIA_BYTES,
61
+ MLS_ENVIRONMENT_PINS,
58
62
  MLS_SETUP_ACTIONS,
63
+ bundledMlsCustodyHelper,
59
64
  checkCodex,
60
65
  claudeDriver,
61
66
  clearPendingRegistration,
@@ -74,6 +79,7 @@ export {
74
79
  loadClaudeQuery,
75
80
  loadMlsDevConfiguration,
76
81
  loadRelayConfiguration,
82
+ managedMlsDevice,
77
83
  materializeAttachment,
78
84
  materializeMessageMedia,
79
85
  materializeReplyContextMedia,
@@ -1,3 +1,4 @@
1
+ import type { DirectConversationSelection } from './communication.js';
1
2
  /**
2
3
  * The class-C relay (docs/design/agent-runner.md §11): a Canon-operated process that relays a connector agent's
3
4
  * encrypted rooms, holding an MLS device for the agent and decrypting and re-encrypting messages for the assistant (which
@@ -108,6 +109,14 @@ export type ConnectorRelayRequest = {
108
109
  messageId: string;
109
110
  text: string;
110
111
  };
112
+ } | {
113
+ op: 'start';
114
+ args: {
115
+ targetUserId: string;
116
+ messageId: string;
117
+ text: string;
118
+ selection?: DirectConversationSelection;
119
+ };
111
120
  } | {
112
121
  op: 'credential';
113
122
  args: ConnectorRelayCredentialDelivery;
@@ -172,6 +181,31 @@ export interface ConnectorRelaySent {
172
181
  historyMessageId: string;
173
182
  status: 'sent' | 'pending';
174
183
  }
184
+ /**
185
+ * A start (§11.5): the relay's device founds (or finds) the encrypted room and sends the opening line itself, as any
186
+ * device does with a withheld opener. `messageId` is the start's identity, as for a send; `historyMessageId` is the
187
+ * opening line's id in the room. `starting`: the outcome is unresolved, including whether local recording completed;
188
+ * retry with the same `messageId` until a terminal answer. Canon's answers otherwise: `messaged` (the line is going into `conversationId`),
189
+ * `requested` or `pending` (it goes once the request is approved), `blocked` or `unavailable`.
190
+ */
191
+ export type ConnectorRelayStarted = {
192
+ messageId: string;
193
+ historyMessageId: string;
194
+ } & ({
195
+ status: 'messaged';
196
+ conversationId: string;
197
+ created?: boolean;
198
+ reused?: boolean;
199
+ } | {
200
+ status: 'requested' | 'pending';
201
+ requestId: string;
202
+ conversationId?: string;
203
+ } | {
204
+ status: 'starting';
205
+ } | {
206
+ status: 'blocked' | 'unavailable';
207
+ reason: string;
208
+ });
175
209
  export declare class ConnectorRelayRequestError extends Error {
176
210
  name: string;
177
211
  }
@@ -97,7 +97,7 @@ export interface EndpointCapabilities {
97
97
  actions: string[];
98
98
  queries: string[];
99
99
  maintenance: boolean;
100
- /** Only for a principal eligible for MLS here: how new rooms are born (functions `rollout.ts`, `CANON_MLS_DEV_ROOMS`). */
100
+ /** Only while MLS is on here, where every valid principal is eligible: how new rooms are born (functions `devActivation.ts`). */
101
101
  mls?: {
102
102
  eligible: true;
103
103
  roomMode: 'default';
@@ -8,6 +8,11 @@ export type EndpointContentLane = 'operation.request' | 'operation.receipt' | 'q
8
8
  * authenticated encrypted protocol before they can operate on a protected room.
9
9
  * Their routing fields remain public metadata; classification is not authority. */
10
10
  export declare const MLS_DEV_UNSUPPORTED_CONTENT_ACTIONS: readonly ["forward_message"];
11
+ /** Operations that send their private content to a named processor in an encrypted room too, disclosed where the
12
+ * app offers them, with the fields reviewed for that: an image's description and source photo go through Canon to
13
+ * Google (image-generation.md). The fields stay classified as private; only the encrypted room's plaintext fence lets
14
+ * them through, and any other field still keeps the operation out. */
15
+ export declare const MLS_DEV_DISCLOSED_PROCESSING_FIELDS: Readonly<Record<string, readonly string[]>>;
11
16
  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
17
  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
18
  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"];
@@ -67,6 +67,8 @@ export interface EndpointOperation {
67
67
  preparedContent?: PreparedContent;
68
68
  /** Authored-input collision check, never recorded for memory-only answers. */
69
69
  inputFingerprint?: string;
70
+ /** Local-only binding of a withheld start's text; retained after the opener is sent or its payload is erased. */
71
+ openerFingerprint?: string;
70
72
  receipt?: EndpointOperationReceipt;
71
73
  attempts: number;
72
74
  attempted: boolean;
@@ -196,7 +198,9 @@ export interface Endpoint {
196
198
  text: string;
197
199
  requestId?: string | null;
198
200
  }): Promise<void>;
199
- /** Persist the opening line before starting a room; recover the start through its durable receipt. */
201
+ /**
202
+ * Persist the opening line before starting a room; recover the start through its durable receipt.
203
+ */
200
204
  startWithheldOpener(input: Omit<StartDirectInput, 'opener'> & {
201
205
  messageId: string;
202
206
  text: string;
@@ -17,6 +17,11 @@ export interface MlsDevDeviceClientOptions {
17
17
  /** Explicit synthetic Dev operations. No timers, eager enrollment or room authority. */
18
18
  export interface MlsDevDeviceClient {
19
19
  read(): Promise<MlsDevDeviceView>;
20
+ /** Read only committed local device state, without discovering or waiting for its remote alias.
21
+ * Returns undefined when no local device exists. Used to capture the device before logout erases it. */
22
+ read(options: {
23
+ localOnly: true;
24
+ }): Promise<MlsDevDeviceView | undefined>;
20
25
  /** Ensure local registration; an existing view is not an online status check. */
21
26
  enroll(): Promise<MlsDevDeviceView>;
22
27
  renew(): Promise<MlsDevDeviceView>;
@@ -28,6 +33,10 @@ export interface MlsDevDeviceClient {
28
33
  * current credential is valid, a failed renewal returns it and is retried
29
34
  * after a minute. Never creates an unrequested enrollment. */
30
35
  ensureReady(): Promise<MlsDevDeviceView>;
36
+ /** Retire this registered device at Canon for good (`POST /revoke`): it can no longer authenticate, renew or be offered
37
+ * to a room, and readiness refuses it. A device Canon never registered has
38
+ * nothing to revoke; a revoked one is returned as it is. Rooms keep its leaf until Canon can remove devices (step 5c). */
39
+ revoke(): Promise<MlsDevDeviceView>;
31
40
  }
32
41
  /** Whether readiness would renew this registered device: it has no credential, or less than half its lifetime remains. */
33
42
  export declare function isMlsDevCredentialRenewalDue(view: MlsDevDeviceView, now: number): boolean;
package/dist/media.js CHANGED
@@ -18,8 +18,8 @@ import {
18
18
  toAnthropicImageBlock,
19
19
  toAnthropicImageBlocksWithinBudget,
20
20
  uploadMediaFile
21
- } from "./chunks/chunk-RFNYPZXD.js";
22
- import "./chunks/chunk-5MI6Q3VS.js";
21
+ } from "./chunks/chunk-TFSURFF6.js";
22
+ import "./chunks/chunk-C2WJT3VX.js";
23
23
  export {
24
24
  DEFAULT_ANTHROPIC_REQUEST_HEADROOM_BYTES,
25
25
  DEFAULT_MEDIA_CACHE_DIR,