@canonmsg/agent-sdk 12.0.0 → 12.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. package/README.md +97 -7
  2. package/dist/chunks/{chunk-FPQFTTND.js → chunk-7N6OPPW4.js} +1616 -1085
  3. package/dist/chunks/chunk-N722GWCP.js +4897 -0
  4. package/dist/chunks/{chunk-OLJ7H4E2.js → chunk-QZ4PPRQW.js} +6462 -6920
  5. package/dist/chunks/{mls-crypto-NZDNSPAO.js → mls-crypto-LMNQZOS6.js} +3 -3
  6. package/dist/cli.js +403 -29
  7. package/dist/drivers/claude.d.ts +8 -2
  8. package/dist/drivers/codex.d.ts +11 -2
  9. package/dist/drivers/connector.d.ts +34 -0
  10. package/dist/index.d.ts +10 -2
  11. package/dist/index.js +26 -4
  12. package/dist/internal/backend-contracts/artifactActions.d.ts +0 -151
  13. package/dist/internal/backend-contracts/clientFloor.d.ts +41 -0
  14. package/dist/internal/backend-contracts/communication.d.ts +15 -0
  15. package/dist/internal/backend-contracts/connectorRelay.d.ts +179 -0
  16. package/dist/internal/backend-contracts/endpoint.d.ts +17 -0
  17. package/dist/internal/backend-contracts/endpointContentPolicy.d.ts +15 -4
  18. package/dist/internal/backend-contracts/index.d.ts +7 -1
  19. package/dist/internal/backend-contracts/media-url.d.ts +14 -0
  20. package/dist/internal/backend-contracts/message.d.ts +1 -1
  21. package/dist/internal/backend-contracts/mlsDevApplication.d.ts +90 -2
  22. package/dist/internal/backend-contracts/mlsDevCredential.d.ts +5 -2
  23. package/dist/internal/backend-contracts/mlsDevDeviceCustody.d.ts +2 -1
  24. package/dist/internal/backend-contracts/mlsDevEnrollment.d.ts +3 -2
  25. package/dist/internal/backend-contracts/mlsDevFeedReceipt.d.ts +43 -0
  26. package/dist/internal/backend-contracts/mlsDevGroup.d.ts +129 -5
  27. package/dist/internal/backend-contracts/mlsDevGroupFeed.d.ts +6 -2
  28. package/dist/internal/backend-contracts/mlsDevInteraction.d.ts +159 -0
  29. package/dist/internal/backend-contracts/mlsEnvironment.d.ts +35 -0
  30. package/dist/internal/backend-contracts/mlsPrincipalAlias.d.ts +23 -0
  31. package/dist/internal/backend-contracts/runtimeCardFields.d.ts +2 -2
  32. package/dist/internal/backend-contracts/upload-mime-types.d.ts +17 -0
  33. package/dist/internal/backend-contracts/verbContract.d.ts +15 -7
  34. package/dist/internal/backend-contracts/verbSchemas.d.ts +35 -25
  35. package/dist/internal/core/agent-profiles.d.ts +0 -4
  36. package/dist/internal/core/approval-format.d.ts +1 -5
  37. package/dist/internal/core/approval-types.d.ts +1 -2
  38. package/dist/internal/core/client.d.ts +2 -56
  39. package/dist/internal/core/constants.d.ts +0 -3
  40. package/dist/internal/core/endpoint-client.d.ts +14 -1
  41. package/dist/internal/core/execution-environment-mode.d.ts +0 -2
  42. package/dist/internal/core/gifs.d.ts +0 -6
  43. package/dist/internal/core/host-runtime.d.ts +2 -206
  44. package/dist/internal/core/index.d.ts +21 -60
  45. package/dist/internal/core/registration.d.ts +1 -17
  46. package/dist/internal/core/retry.d.ts +0 -9
  47. package/dist/internal/core/rtdb-rest.d.ts +0 -4
  48. package/dist/internal/core/runtime-cards.d.ts +0 -45
  49. package/dist/internal/core/runtime-descriptor.d.ts +1 -60
  50. package/dist/internal/core/runtime-presentation.d.ts +1 -27
  51. package/dist/internal/core/runtime-request-manager.d.ts +0 -1
  52. package/dist/internal/core/self-context.d.ts +0 -39
  53. package/dist/internal/core/silent-turn.d.ts +1 -38
  54. package/dist/internal/core/stream.d.ts +2 -1
  55. package/dist/internal/core/turn-protocol.d.ts +0 -5
  56. package/dist/internal/core/turn-verbosity.d.ts +0 -12
  57. package/dist/internal/core/types.d.ts +0 -111
  58. package/dist/internal/endpoint/engine.d.ts +31 -1
  59. package/dist/internal/endpoint/index.d.ts +1 -1
  60. package/dist/internal/endpoint/media.d.ts +5 -0
  61. package/dist/internal/endpoint/mls-crypto.d.ts +12 -5
  62. package/dist/internal/endpoint/mls-dev-client.d.ts +9 -1
  63. package/dist/internal/endpoint/mls-dev-device.d.ts +2 -0
  64. package/dist/internal/endpoint/mls-dev-groups.d.ts +65 -5
  65. package/dist/internal/endpoint/mls-dev-interactions.d.ts +99 -0
  66. package/dist/internal/endpoint/mls-dev-key-package-service.d.ts +13 -0
  67. package/dist/internal/endpoint/mls-dev-key-packages.d.ts +23 -3
  68. package/dist/internal/endpoint/mls-dev-text.d.ts +67 -0
  69. package/dist/internal/endpoint/mls-dev-transport.d.ts +2 -1
  70. package/dist/internal/endpoint/node-mls-custody-protocol.d.ts +5 -0
  71. package/dist/internal/endpoint/transport.d.ts +6 -1
  72. package/dist/media.js +2 -2
  73. package/dist/runner/errands.d.ts +118 -0
  74. package/dist/runner/mls.d.ts +97 -0
  75. package/dist/runner/relay.d.ts +87 -0
  76. package/dist/runner/runner.d.ts +61 -0
  77. package/dist/runner/types.d.ts +51 -4
  78. package/package.json +1 -1
  79. package/dist/chunks/chunk-6VK4UZHJ.js +0 -2806
  80. package/dist/internal/backend-contracts/workSessions.d.ts +0 -205
  81. package/dist/internal/core/agent-resolver.d.ts +0 -49
  82. package/dist/internal/core/agent-session.d.ts +0 -12
  83. package/dist/internal/core/artifacts.d.ts +0 -262
  84. package/dist/internal/core/attached-native-session.d.ts +0 -174
  85. package/dist/internal/core/attached-session-file-store.d.ts +0 -3
  86. package/dist/internal/core/control-poller.d.ts +0 -221
  87. package/dist/internal/core/exclusive-json-file-store.d.ts +0 -7
  88. package/dist/internal/core/execution-environment.d.ts +0 -55
  89. package/dist/internal/core/group-context.d.ts +0 -25
  90. package/dist/internal/core/inbound-frame.d.ts +0 -64
  91. package/dist/internal/core/local-runtime-catalog.d.ts +0 -131
  92. package/dist/internal/core/message-format.d.ts +0 -13
  93. package/dist/internal/core/native-session-interaction.d.ts +0 -44
  94. package/dist/internal/core/provenance.d.ts +0 -31
  95. package/dist/internal/core/registration-cli.d.ts +0 -40
  96. package/dist/internal/core/turn-output-controller.d.ts +0 -69
  97. package/dist/internal/core/typing-status-publisher.d.ts +0 -14
  98. package/dist/internal/core/work-session-provider.d.ts +0 -29
  99. package/dist/internal/core/workspace-discovery.d.ts +0 -17
@@ -1,10 +1,10 @@
1
1
  import {
2
- MLS_DEV_DEVICE_HTTP_CONFIGURATION,
3
2
  createMlsDevCredentialVerifier,
3
+ mlsDevDeviceHttpConfiguration,
4
4
  parseMlsDevClientConfiguration
5
- } from "./chunk-FPQFTTND.js";
5
+ } from "./chunk-7N6OPPW4.js";
6
6
  export {
7
- MLS_DEV_DEVICE_HTTP_CONFIGURATION,
8
7
  createMlsDevCredentialVerifier,
8
+ mlsDevDeviceHttpConfiguration,
9
9
  parseMlsDevClientConfiguration
10
10
  };
package/dist/cli.js CHANGED
@@ -1,20 +1,43 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
+ MLS_SETUP_ACTIONS,
3
4
  assertPrivateHome,
5
+ awaitErrand,
4
6
  checkCodex,
5
7
  claudeDriver,
6
8
  codeDriver,
7
9
  codexDriver,
10
+ connectorDriver,
8
11
  createRunner,
9
- loadClaudeQuery
10
- } from "./chunks/chunk-6VK4UZHJ.js";
12
+ errandJournal,
13
+ errandsDirectory,
14
+ loadClaudeQuery,
15
+ loadInstructions,
16
+ loadMlsDevConfiguration,
17
+ loadRelayConfiguration,
18
+ queueErrand,
19
+ readRelayCredential,
20
+ relayRunnerHome,
21
+ runMlsDevSetup,
22
+ runnerLockName,
23
+ startRelayChannel,
24
+ writeRelayCredential
25
+ } from "./chunks/chunk-N722GWCP.js";
11
26
  import {
27
+ CanonApiError,
28
+ CanonClient,
29
+ acquireLock,
30
+ isProfileLocked,
12
31
  loadProfiles,
13
- runCli
14
- } from "./chunks/chunk-OLJ7H4E2.js";
32
+ readCliPackageVersion,
33
+ resolveCanonRuntimeConnection,
34
+ runCli,
35
+ verifyCanonRuntimeConnection
36
+ } from "./chunks/chunk-QZ4PPRQW.js";
15
37
  import {
16
- __name
17
- } from "./chunks/chunk-FPQFTTND.js";
38
+ __name,
39
+ isCanonClientVersion
40
+ } from "./chunks/chunk-7N6OPPW4.js";
18
41
 
19
42
  // src/cli.ts
20
43
  import { realpathSync } from "node:fs";
@@ -22,6 +45,18 @@ import { homedir, tmpdir } from "node:os";
22
45
  import { dirname, join, resolve } from "node:path";
23
46
  import { pathToFileURL } from "node:url";
24
47
  import { parseArgs } from "node:util";
48
+ var RUNNER_VERSION = readCliPackageVersion(import.meta.url);
49
+ var RUNNER_CLIENT = isCanonClientVersion(RUNNER_VERSION) ? { kind: "runner", version: RUNNER_VERSION } : void 0;
50
+ function once(handler) {
51
+ let called = false;
52
+ return (error) => {
53
+ if (!called) {
54
+ called = true;
55
+ handler(error);
56
+ }
57
+ };
58
+ }
59
+ __name(once, "once");
25
60
  var HELP = `canon-agent: run a Canon agent on the unified runner
26
61
 
27
62
  Usage:
@@ -31,9 +66,42 @@ Usage:
31
66
  canon-agent run --driver codex [--cwd <dir>] [--model <id>] [--sandbox <mode>]
32
67
  [--approval-policy <policy>] [--codex-path <path>] [--profile <name>]
33
68
  [--environment <id>]
69
+ canon-agent run --driver connector --relay-config <path> --mls-dev-config <path>
70
+ [--profile <name>] [--environment <id>]
71
+ canon-agent mls --action <action> --mls-dev-config <path> [--room <id>]
72
+ [--relay-config <path>] [--profile <name>] [--environment <id>]
73
+ canon-agent tell --room <id> --text <text> (--agent <id> | --profile <name>)
74
+ [--environment <id>] [--wait]
75
+ canon-agent post --room <id> --text <text> (--agent <id> | --profile <name>)
76
+ [--environment <id>] [--wait]
77
+ canon-agent errand --to <room or contact id> --why <why> --text <instruction>
78
+ [--origin <room id>] (--agent <id> | --profile <name>)
79
+ [--environment <id>] [--wait]
80
+ canon-agent errands [--follow] [--cancel <errand id>] (--agent <id> | --profile <name>)
81
+ [--environment <id>]
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.
86
+
87
+ Team rooms (an owner's agents working together): claude and codex also take
88
+ [--instructions <file>] [--trust-agent <agentId>]...; every run command takes
89
+ [--agent-turn-cap <n>] [--mention-alias <name>]..., which apply in encrypted rooms.
90
+ code, claude and codex take [--allow-session-for-destructive] for encrypted cards.
91
+
92
+ Errands: tell, post and errand direct a running agent from the machine it runs on:
93
+ whoever can write $CANON_HOME commands it, and the CLI itself uses no network. tell
94
+ leaves a private note for the agent's next turn in a room: never posted, it starts no
95
+ turn. post sends the text as the agent's own message, with no turn (encrypted in an
96
+ encrypted room). errand runs one owner-authorised turn that carries the instruction to
97
+ the recipient (a room the agent is in, or a new conversation with a person or agent),
98
+ reports to --origin, and tells later turns there why the agent came. What concerns an
99
+ encrypted room is reported only to an encrypted origin; otherwise the report stays
100
+ local (canon-agent errands). Canon's policies, blocks and rate limits apply as to any
101
+ message from the agent.
34
102
 
35
103
  Flags:
36
- --driver <name> The runtime driver: "code", "claude" or "codex".
104
+ --driver <name> The runtime driver: "code", "claude", "codex" or "connector".
37
105
  --module <path> code: an ES module whose default export answers a turn.
38
106
  Its directory is one the agent may send files from.
39
107
  --cwd <dir> claude, codex: the directory the runtime works in (default:
@@ -52,6 +120,64 @@ Flags:
52
120
  Turns holding anyone else's message are always read-only,
53
121
  approve nothing and get only Canon's tools.
54
122
  --codex-path <path> codex: the Codex CLI (default: codex on the PATH).
123
+ --instructions <file> claude, codex: standing instructions from the owner (a role,
124
+ say), appended to the runtime's system or developer
125
+ instructions. A UTF-8 file of at most 32 KiB, owned by you
126
+ and not writable by others, read once at start; never sent
127
+ to Canon.
128
+ --trust-agent <id> claude, codex: an agent of the same owner whose messages count
129
+ as the owner's for tool use (repeat for several), only in a
130
+ room whose other members are all listed agents that Canon
131
+ confirms at each turn as active agents of this agent's
132
+ owner (and, in plaintext, only for a final that agent's own
133
+ runner marked as answering its owner). Otherwise they are
134
+ guarded like any other agent's. Approvals still go to the
135
+ owner.
136
+ --agent-turn-cap <n> Encrypted rooms: another agent's message wakes this agent only
137
+ when it @mentions it (by display name, id or an alias), at
138
+ most n times in a row per room until a person writes there
139
+ (default 6; 0: agents never wake it). Plaintext rooms
140
+ follow Canon's group rules.
141
+ --mention-alias <name> Encrypted rooms: another name that @mentions this agent, such
142
+ as its role (repeat for several).
143
+ --allow-session-for-destructive
144
+ Encrypted rooms: let the owner allow an action the runtime
145
+ marks destructive (Claude Code's default-to-no prompts,
146
+ Codex's risky commands and deletions) for the rest of the
147
+ session. Off by default: each such action asks.
148
+ --relay-config <path> connector (Dev only): run as Canon's class-C relay for one agent
149
+ connected through Canon's MCP connector. A private JSON file
150
+ naming the agent, where the channel to Canon's Functions
151
+ listens, and the path of the channel secret. Canon delivers
152
+ the relay's own credential over that channel when the owner
153
+ enables the relay (no API key is used); it is kept in
154
+ $CANON_HOME/relay. The relay serves only encrypted rooms,
155
+ which are then not end-to-end encrypted to that agent. With
156
+ "mls", it sets up the relay's device with that credential.
157
+ --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.
160
+ --action <action> mls: status (public device state), provision (create the
161
+ portable vault, once), enroll, renew, publish (this device's
162
+ KeyPackage for --room) or refresh (catch up --room). It never
163
+ starts a model, and uses the same home as "run": stop the
164
+ agent first.
165
+ --room <id> mls: the encrypted room, for publish and refresh. tell, post:
166
+ the conversation.
167
+ --text <text> tell: the note. post: the message. errand: the instruction.
168
+ At most 32 KiB.
169
+ --to <id> errand: a conversation the agent is in, or else a person or
170
+ agent to start a new (plaintext) conversation with.
171
+ --why <why> errand: why the owner sends the agent (at most 4 KiB).
172
+ --origin <id> errand: the conversation the report goes to. A report about an
173
+ encrypted recipient goes only to an encrypted origin.
174
+ --follow errands: go on printing each change.
175
+ --cancel <id> errands: cancel that errand (Stop in its origin also stops it).
176
+ --agent <id> tell, post, errand, errands: the agent (or --profile).
177
+ --wait tell, post, errand: wait up to 30 s for the running agent to take
178
+ it (an errand: to finish its turn), print the outcome, and exit
179
+ 1 unless it was accepted (a note kept, a post sent, an errand's
180
+ turn ended and any report it sent to --origin accepted).
55
181
  --profile <name> A profile in $CANON_HOME/agents.json (key and endpoints).
56
182
  --environment <id> canon-prod-v1 or canon-dev-v1 (default: CANON_ENVIRONMENT_ID).
57
183
  --help, -h Show this help.
@@ -68,11 +194,19 @@ Environment:
68
194
 
69
195
  SIGTERM or Ctrl-C stops taking messages, lets running turns finish for 10 seconds,
70
196
  then stops them.`;
71
- var DRIVERS = ["code", "claude", "codex", "hermes"];
72
- var AVAILABLE = ["code", "claude", "codex"];
197
+ var DRIVERS = ["code", "claude", "codex", "hermes", "connector"];
198
+ var AVAILABLE = ["code", "claude", "codex", "connector"];
73
199
  var PERMISSION_MODES = ["default", "acceptEdits", "bypassPermissions"];
74
200
  var SANDBOXES = ["workspace-write", "read-only", "danger-full-access"];
75
201
  var APPROVAL_POLICIES = ["on-request", "untrusted", "never"];
202
+ function teamOptions(values) {
203
+ const cap = values["agent-turn-cap"];
204
+ if (cap !== void 0 && (!/^\d{1,4}$/.test(cap) || Number(cap) > 1e3)) throw new UsageError("--agent-turn-cap must be a whole number from 0 to 1000.");
205
+ const aliases = values["mention-alias"] ?? [];
206
+ if (aliases.some((alias) => !alias.replace(/^@+/, "").trim())) throw new UsageError("--mention-alias needs a name.");
207
+ return { ...cap !== void 0 ? { agentTurnCap: Number(cap) } : {}, ...aliases.length ? { mentionAliases: aliases } : {} };
208
+ }
209
+ __name(teamOptions, "teamOptions");
76
210
  var UsageError = class extends Error {
77
211
  static {
78
212
  __name(this, "UsageError");
@@ -86,12 +220,25 @@ async function main() {
86
220
  throw new UsageError(error instanceof Error ? error.message : String(error));
87
221
  }
88
222
  const { values, positionals } = parsed;
89
- if (positionals[0] !== "run" || positionals.length > 1) throw new UsageError('Expected the "run" command.');
223
+ if (!["run", "mls", "tell", "post", "errand", "errands"].includes(positionals[0] ?? "") || positionals.length > 1) {
224
+ throw new UsageError('Expected the "run", "mls", "tell", "post", "errand" or "errands" command.');
225
+ }
226
+ if (positionals[0] === "mls") return mlsSetup(values);
227
+ if (positionals[0] === "errands") return errands(values);
228
+ if (positionals[0] === "tell" || positionals[0] === "post" || positionals[0] === "errand") return errand(positionals[0], values);
90
229
  if (!values.driver) throw new UsageError("--driver is required.");
91
230
  if (!AVAILABLE.includes(values.driver)) {
92
231
  throw new Error(DRIVERS.includes(values.driver) ? `The "${values.driver}" driver is not yet available; ${AVAILABLE.join(", ")} run today.` : `Unknown driver "${values.driver}". Available: ${AVAILABLE.join(", ")}.`);
93
232
  }
94
233
  if (values.driver === "code" && !values.module) throw new UsageError("--driver code needs --module <path>.");
234
+ 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
+ if (!["claude", "codex"].includes(values.driver) && (values.instructions !== void 0 || values["trust-agent"]?.length)) {
237
+ throw new UsageError("--instructions and --trust-agent go with --driver claude or codex.");
238
+ }
239
+ const team = teamOptions(values);
240
+ if (values.driver === "connector" && values["allow-session-for-destructive"]) throw new UsageError("--allow-session-for-destructive goes with a driver that asks.");
241
+ if (values.driver === "connector") return runRelay(values, team);
95
242
  const permissionMode = values["permission-mode"];
96
243
  if (permissionMode !== void 0 && !PERMISSION_MODES.includes(permissionMode)) {
97
244
  throw new UsageError(`--permission-mode must be one of ${PERMISSION_MODES.join(", ")}.`);
@@ -103,17 +250,13 @@ async function main() {
103
250
  if (approvalPolicy !== void 0 && !APPROVAL_POLICIES.includes(approvalPolicy)) {
104
251
  throw new UsageError(`--approval-policy must be one of ${APPROVAL_POLICIES.join(", ")}.`);
105
252
  }
106
- const environmentId = values.environment || process.env.CANON_ENVIRONMENT_ID;
107
- if (!environmentId) throw new Error("CANON_ENVIRONMENT_ID (or --environment) is required.");
108
- if (!values.profile && !process.env.CANON_API_KEY) throw new Error("CANON_API_KEY (or --profile <name>) is required.");
109
- const home = resolve(process.env.CANON_HOME || join(homedir(), ".canon"));
110
- await assertPrivateHome(home);
111
- const profile = values.profile ? loadProfiles(home)[values.profile] : void 0;
112
- if (values.profile && !profile) throw new Error(`No profile "${values.profile}" in ${join(home, "agents.json")}.`);
113
- if (profile && profile.environmentId !== environmentId) {
114
- throw new Error(`Profile "${values.profile}" belongs to ${profile.environmentId}, not ${environmentId}.`);
115
- }
116
- const apiKey = process.env.CANON_API_KEY || profile.apiKey;
253
+ const trustedAgents = values["trust-agent"] ?? [];
254
+ if (trustedAgents.some((id) => !/^[\w.:-]{1,200}$/.test(id))) throw new UsageError("--trust-agent takes an agent id.");
255
+ const instructions = values.instructions !== void 0 ? await loadInstructions(resolve(values.instructions)) : void 0;
256
+ const { environmentId, home, profile, apiKey } = await settings(values);
257
+ const mls = values["mls-dev-config"] ? await loadMlsDevConfiguration(values["mls-dev-config"], { environmentId }) : void 0;
258
+ const baseUrl = process.env.CANON_BASE_URL || profile?.baseUrl;
259
+ const streamUrl = process.env.CANON_STREAM_URL || profile?.streamUrl;
117
260
  let driver;
118
261
  if (values.driver === "claude") {
119
262
  driver = claudeDriver({
@@ -121,7 +264,8 @@ async function main() {
121
264
  query: await loadClaudeQuery(),
122
265
  ...values.model ? { model: values.model } : {},
123
266
  ...permissionMode ? { permissionMode } : {},
124
- ...values["claude-path"] ? { claudePath: resolve(values["claude-path"]) } : {}
267
+ ...values["claude-path"] ? { claudePath: resolve(values["claude-path"]) } : {},
268
+ ...instructions ? { instructions } : {}
125
269
  });
126
270
  } else if (values.driver === "codex") {
127
271
  const codexPath = values["codex-path"] ? resolve(values["codex-path"]) : "codex";
@@ -131,7 +275,8 @@ async function main() {
131
275
  codexPath,
132
276
  ...values.model ? { model: values.model } : {},
133
277
  ...sandbox ? { sandbox } : {},
134
- ...approvalPolicy ? { approvalPolicy } : {}
278
+ ...approvalPolicy ? { approvalPolicy } : {},
279
+ ...instructions ? { instructions } : {}
135
280
  });
136
281
  } else {
137
282
  const modulePath = realpathSync(resolve(values.module));
@@ -144,10 +289,19 @@ async function main() {
144
289
  environmentId,
145
290
  home,
146
291
  driver,
147
- baseUrl: process.env.CANON_BASE_URL || profile?.baseUrl,
148
- streamUrl: process.env.CANON_STREAM_URL || profile?.streamUrl
292
+ ...mls ? { mls } : {},
293
+ baseUrl,
294
+ streamUrl,
295
+ ...team,
296
+ ...trustedAgents.length ? { trustedAgents } : {},
297
+ ...values["allow-session-for-destructive"] ? { sessionApprovalForDestructive: true } : {},
298
+ ...RUNNER_CLIENT ? { client: RUNNER_CLIENT } : {},
299
+ onFatal: once((error) => {
300
+ console.error(`canon-agent: ${error.message}`);
301
+ void runner.stop().catch(() => void 0).finally(() => process.exit(1));
302
+ })
149
303
  });
150
- stopOnSignals(runner);
304
+ stopOnSignals(() => runner.stop());
151
305
  try {
152
306
  await runner.start();
153
307
  } catch (error) {
@@ -157,6 +311,100 @@ async function main() {
157
311
  console.error(`canon-agent: running the ${values.driver} driver in ${environmentId}. Ctrl-C stops it.`);
158
312
  }
159
313
  __name(main, "main");
314
+ async function runRelay(values, team) {
315
+ const { environmentId, home, profile } = await settings(values, { key: false });
316
+ const relay = await loadRelayConfiguration(values["relay-config"], { environmentId });
317
+ const baseUrl = process.env.CANON_BASE_URL || profile?.baseUrl;
318
+ const streamUrl = process.env.CANON_STREAM_URL || profile?.streamUrl;
319
+ let current;
320
+ let latest;
321
+ let switching = Promise.resolve();
322
+ const switchTo = /* @__PURE__ */ __name((stored2, what) => {
323
+ latest = stored2;
324
+ switching = switching.then(() => runUnder(stored2)).catch((error) => {
325
+ console.error(`canon-agent: the relay could not start under ${what} (${error instanceof Error ? error.message : String(error)}); waiting for the owner to enable the relay again.`);
326
+ });
327
+ }, "switchTo");
328
+ const channel = await startRelayChannel({
329
+ agentId: relay.agentId,
330
+ secret: relay.secret,
331
+ host: relay.listen.host,
332
+ port: relay.listen.port,
333
+ // Durable before the channel confirms the delivery; the runner switches over once Canon has committed it.
334
+ onCredential: /* @__PURE__ */ __name((delivery) => switchTo(writeRelayCredential(home, relay, delivery), "its new credential"), "onCredential")
335
+ });
336
+ async function accepted(stored2) {
337
+ for (let attempt = 0; attempt < 60; attempt += 1) {
338
+ if (latest !== stored2) return false;
339
+ try {
340
+ await assertRelayAgent(relay, stored2, baseUrl, streamUrl);
341
+ return true;
342
+ } catch (error) {
343
+ if (!(error instanceof CanonApiError) || error.status !== 401) throw error;
344
+ }
345
+ await new Promise((resolve2) => setTimeout(resolve2, 1e3));
346
+ }
347
+ throw new Error("Canon did not accept the relay's credential");
348
+ }
349
+ __name(accepted, "accepted");
350
+ async function runUnder(stored2) {
351
+ if (!await accepted(stored2)) return;
352
+ channel.serve(void 0);
353
+ await current?.stop();
354
+ current = void 0;
355
+ const mls = await loadMlsDevConfiguration(values["mls-dev-config"], { environmentId });
356
+ const driver = connectorDriver();
357
+ const runner = await createRunner({
358
+ apiKey: stored2.credential,
359
+ environmentId,
360
+ home: relayRunnerHome(home, stored2.installationId),
361
+ driver,
362
+ mls,
363
+ baseUrl,
364
+ streamUrl,
365
+ relay: true,
366
+ installationId: stored2.installationId,
367
+ ...team,
368
+ ...RUNNER_CLIENT ? { client: RUNNER_CLIENT } : {},
369
+ onFatal: once((error) => {
370
+ console.error(`canon-agent: ${error.message}`);
371
+ void Promise.resolve().then(() => channel.close()).then(() => runner.stop()).catch(() => void 0).finally(() => process.exit(1));
372
+ })
373
+ });
374
+ try {
375
+ await runner.start();
376
+ } catch (error) {
377
+ await runner.stop().catch(() => void 0);
378
+ throw error;
379
+ }
380
+ current = runner;
381
+ channel.serve({ runner, inbox: driver.inbox }, stored2.grantId);
382
+ console.error(`canon-agent: relaying ${relay.agentId} under ${stored2.installationId} in ${environmentId}.`);
383
+ }
384
+ __name(runUnder, "runUnder");
385
+ stopOnSignals(async () => {
386
+ await channel.close();
387
+ latest = void 0;
388
+ await switching;
389
+ await current?.stop();
390
+ });
391
+ console.error(`canon-agent: relay channel for ${relay.agentId} listening on ${channel.url}. Ctrl-C stops it.`);
392
+ const stored = await readRelayCredential(home, relay);
393
+ if (stored) switchTo(stored, "its stored credential");
394
+ else console.error("canon-agent: waiting for Canon to deliver this relay's credential (the owner enables the relay for the connection).");
395
+ }
396
+ __name(runRelay, "runRelay");
397
+ async function assertRelayAgent(relay, stored, baseUrl, streamUrl) {
398
+ const connection = resolveCanonRuntimeConnection({ environmentId: relay.environmentId, apiBaseUrl: baseUrl, streamUrl });
399
+ const client = new CanonClient(stored.credential, connection.apiBaseUrl, { environmentId: relay.environmentId });
400
+ try {
401
+ const me = await client.getAgentMe();
402
+ if (me.agentId !== relay.agentId) throw new Error(`The relay credential names agent ${me.agentId}, but the relay configuration names ${relay.agentId}.`);
403
+ } finally {
404
+ await client.close();
405
+ }
406
+ }
407
+ __name(assertRelayAgent, "assertRelayAgent");
160
408
  function parse() {
161
409
  return parseArgs({
162
410
  allowPositionals: true,
@@ -171,18 +419,144 @@ function parse() {
171
419
  "claude-path": { type: "string" },
172
420
  sandbox: { type: "string" },
173
421
  "approval-policy": { type: "string" },
174
- "codex-path": { type: "string" }
422
+ "codex-path": { type: "string" },
423
+ "mls-dev-config": { type: "string" },
424
+ action: { type: "string" },
425
+ room: { type: "string" },
426
+ "relay-config": { type: "string" },
427
+ instructions: { type: "string" },
428
+ "trust-agent": { type: "string", multiple: true },
429
+ "agent-turn-cap": { type: "string" },
430
+ "mention-alias": { type: "string", multiple: true },
431
+ "allow-session-for-destructive": { type: "boolean" },
432
+ text: { type: "string" },
433
+ agent: { type: "string" },
434
+ wait: { type: "boolean" },
435
+ to: { type: "string" },
436
+ why: { type: "string" },
437
+ origin: { type: "string" },
438
+ follow: { type: "boolean" },
439
+ cancel: { type: "string" }
175
440
  }
176
441
  });
177
442
  }
178
443
  __name(parse, "parse");
179
- function stopOnSignals(runner) {
444
+ async function settings(values, { key = true } = {}) {
445
+ const environmentId = values.environment || process.env.CANON_ENVIRONMENT_ID;
446
+ if (!environmentId) throw new Error("CANON_ENVIRONMENT_ID (or --environment) is required.");
447
+ if (key && !values.profile && !process.env.CANON_API_KEY) throw new Error("CANON_API_KEY (or --profile <name>) is required.");
448
+ const home = resolve(process.env.CANON_HOME || join(homedir(), ".canon"));
449
+ await assertPrivateHome(home);
450
+ const profile = values.profile ? loadProfiles(home)[values.profile] : void 0;
451
+ if (values.profile && !profile) throw new Error(`No profile "${values.profile}" in ${join(home, "agents.json")}.`);
452
+ if (profile && profile.environmentId !== environmentId) {
453
+ throw new Error(`Profile "${values.profile}" belongs to ${profile.environmentId}, not ${environmentId}.`);
454
+ }
455
+ return { environmentId, home, profile, apiKey: process.env.CANON_API_KEY || profile?.apiKey || "" };
456
+ }
457
+ __name(settings, "settings");
458
+ async function mlsSetup(values) {
459
+ const action = values.action;
460
+ if (!action || !MLS_SETUP_ACTIONS.includes(action)) throw new UsageError(`--action must be one of ${MLS_SETUP_ACTIONS.join(", ")}.`);
461
+ 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
+ const settled = await settings(values, { key: !values["relay-config"] });
464
+ const { environmentId, profile } = settled;
465
+ const relay = values["relay-config"] ? await loadRelayConfiguration(values["relay-config"], { environmentId }) : void 0;
466
+ const stored = relay ? await readRelayCredential(settled.home, relay) : void 0;
467
+ if (relay && !stored) {
468
+ throw new Error("This relay has no credential yet: start it (canon-agent run --driver connector) and have the owner enable the relay.");
469
+ }
470
+ const apiKey = stored ? stored.credential : settled.apiKey;
471
+ const home = stored ? relayRunnerHome(settled.home, stored.installationId) : settled.home;
472
+ const config = await loadMlsDevConfiguration(values["mls-dev-config"], { environmentId });
473
+ let client;
474
+ let lock;
475
+ try {
476
+ const connection = resolveCanonRuntimeConnection({
477
+ environmentId,
478
+ apiBaseUrl: process.env.CANON_BASE_URL || profile?.baseUrl,
479
+ streamUrl: process.env.CANON_STREAM_URL || profile?.streamUrl
480
+ });
481
+ await verifyCanonRuntimeConnection(connection);
482
+ client = new CanonClient(apiKey, connection.apiBaseUrl, {
483
+ environmentId,
484
+ streamUrl: connection.streamUrl,
485
+ stateDirectory: join(home, "endpoint"),
486
+ mlsDev: { configuration: config.configuration, custody: config.custody },
487
+ ...stored ? { installationId: stored.installationId } : {},
488
+ ...RUNNER_CLIENT ? { client: RUNNER_CLIENT } : {}
489
+ });
490
+ const me = await client.getAgentMe();
491
+ lock = acquireLock(runnerLockName(environmentId, me.agentId), home);
492
+ console.log(JSON.stringify(await runMlsDevSetup(await client.getEndpoint(), action, values.room, config.provision)));
493
+ } finally {
494
+ try {
495
+ await client?.close();
496
+ } finally {
497
+ try {
498
+ await config.close();
499
+ } finally {
500
+ lock?.release();
501
+ }
502
+ }
503
+ }
504
+ }
505
+ __name(mlsSetup, "mlsSetup");
506
+ async function errandHome(values) {
507
+ if (!values.agent && !values.profile) throw new UsageError("--agent <id> or --profile <name> names the agent.");
508
+ if (values.agent !== void 0 && (!/^[\w.:-]{1,200}$/.test(values.agent) || /^\.+$/.test(values.agent))) throw new UsageError("--agent takes an agent id.");
509
+ const { environmentId, home, profile } = await settings(values, { key: false });
510
+ if (values.agent && profile && profile.agentId !== values.agent) throw new UsageError("--agent and --profile name different agents.");
511
+ const agentId = values.agent ?? profile.agentId;
512
+ return { agentId, directory: errandsDirectory(home, environmentId, agentId), running: isProfileLocked(runnerLockName(environmentId, agentId), home).locked };
513
+ }
514
+ __name(errandHome, "errandHome");
515
+ async function errand(kind, values) {
516
+ const input = kind === "errand" ? { kind, to: values.to ?? "", why: values.why ?? "", text: values.text ?? "", ...values.origin ? { origin: values.origin } : {} } : { kind, conversationId: values.room ?? "", text: values.text ?? "" };
517
+ if (kind === "errand" && (!values.to || values.why === void 0)) throw new UsageError("errand needs --to <room or contact id> and --why <why>.");
518
+ if (kind !== "errand" && !values.room) throw new UsageError("--room <conversation id> is required.");
519
+ if (values.text === void 0) throw new UsageError("--text <text> is required.");
520
+ const { agentId, directory, running } = await errandHome(values);
521
+ const command = queueErrand(directory, input);
522
+ if (!running) console.error(`canon-agent: no runner of ${agentId} is running in this home; it takes the ${kind} when it starts.`);
523
+ await report(directory, command, !!values.wait, running);
524
+ }
525
+ __name(errand, "errand");
526
+ async function report(directory, command, wait, running) {
527
+ if (!wait) return console.log(JSON.stringify({ id: command.id, kind: command.kind, record: join(directory, "journal", `${command.id}.json`) }));
528
+ const { record: found, accepted } = await awaitErrand(directory, command, running ? 3e4 : 0);
529
+ const { text: _text, ...outcome } = found ?? { version: 1, id: command.id, status: "queued", updatedAt: command.createdAt };
530
+ console.log(JSON.stringify({ ...outcome, kind: command.kind }));
531
+ if (!accepted) process.exitCode = 1;
532
+ }
533
+ __name(report, "report");
534
+ async function errands(values) {
535
+ const { directory, running } = await errandHome(values);
536
+ if (values.cancel !== void 0) {
537
+ if (!/^[0-9a-f]{32}$/.test(values.cancel)) throw new UsageError("--cancel takes an errand id.");
538
+ return report(directory, queueErrand(directory, { kind: "cancel", errand: values.cancel }), running, running);
539
+ }
540
+ const journal = errandJournal(directory);
541
+ const shown = /* @__PURE__ */ new Map();
542
+ const show = /* @__PURE__ */ __name(() => {
543
+ for (const record of journal.list().filter((candidate) => candidate.kind === "errand").sort((a, b) => (a.createdAt ?? "").localeCompare(b.createdAt ?? "") || a.id.localeCompare(b.id))) {
544
+ if (shown.get(record.id) === record.updatedAt) continue;
545
+ shown.set(record.id, record.updatedAt);
546
+ console.log(JSON.stringify(record));
547
+ }
548
+ }, "show");
549
+ show();
550
+ if (values.follow) setInterval(show, 500);
551
+ }
552
+ __name(errands, "errands");
553
+ function stopOnSignals(stop) {
180
554
  let stopping = false;
181
555
  const onSignal = /* @__PURE__ */ __name((signal) => {
182
556
  if (stopping) process.exit(1);
183
557
  stopping = true;
184
558
  console.error(`canon-agent: ${signal}, stopping.`);
185
- runner.stop().then(() => process.exit(0), (error) => {
559
+ stop().then(() => process.exit(0), (error) => {
186
560
  console.error(`canon-agent: ${error instanceof Error ? error.message : String(error)}`);
187
561
  process.exit(1);
188
562
  });
@@ -14,10 +14,16 @@ export interface ClaudeDriverOptions {
14
14
  model?: string;
15
15
  /**
16
16
  * `default` (the default) puts every tool call Claude Code would ask about to the owner as a Canon approval.
17
- * `acceptEdits` and `bypassPermissions` approve more on their own. In every mode, a turn holding anyone else's message
18
- * may use only Canon's tools, AskUserQuestion and ToolSearch.
17
+ * `acceptEdits` and `bypassPermissions` approve more on their own. In every mode, a turn holding a message from anyone
18
+ * but the owner or a confirmed trusted agent of the owner (`TurnInputMessage.trustedAgent`) may use only Canon's
19
+ * tools, AskUserQuestion and ToolSearch.
19
20
  */
20
21
  permissionMode?: 'default' | 'acceptEdits' | 'bypassPermissions';
22
+ /**
23
+ * The owner's standing instructions for this agent (its role, say), appended to Claude Code's system prompt after
24
+ * Canon's own framing. At most 32 KiB; the CLI reads them from `--instructions <file>`. Never sent to Canon.
25
+ */
26
+ instructions?: string;
21
27
  /** A Claude Code executable to run instead of the one bundled with the SDK. */
22
28
  claudePath?: string;
23
29
  /** How long an interrupted Claude Code may take to stop before its process is closed (default 5 s). */
@@ -8,9 +8,15 @@ export interface CodexDriverOptions {
8
8
  sandbox?: 'read-only' | 'workspace-write' | 'danger-full-access';
9
9
  /**
10
10
  * When Codex asks before acting in the owner's turns: `on-request` (the default), `untrusted` or `never`. Each request
11
- * becomes a Canon approval. A turn holding anyone else's message is always read-only and approves nothing.
11
+ * becomes a Canon approval. A turn holding a message from anyone but the owner or a confirmed trusted agent of the owner
12
+ * (`TurnInputMessage.trustedAgent`) is always read-only and approves nothing.
12
13
  */
13
14
  approvalPolicy?: 'untrusted' | 'on-request' | 'never';
15
+ /**
16
+ * The owner's standing instructions for this agent (its role, say), appended to the thread's developer instructions
17
+ * after Canon's own framing. At most 32 KiB; the CLI reads them from `--instructions <file>`. Never sent to Canon.
18
+ */
19
+ instructions?: string;
14
20
  /** The Codex CLI (default: `codex` on the PATH). */
15
21
  codexPath?: string;
16
22
  /** How long an interrupted Codex may take to stop before its process is closed (default 5 s). */
@@ -23,7 +29,10 @@ export declare function checkCodex(codexPath?: string): string;
23
29
  * Codex's requests to its client, from the turn's thread or a subagent's. Approvals are scoped to exactly what they
24
30
  * allow: a session approval covers the same command with the same directory, network destination and permissions, the
25
31
  * same set of files or the same MCP tool, never "commands". A request whose scope is not fully known is approved once.
26
- * `rpcId` is the request's own JSON-RPC id, the identity of a request that names no item.
32
+ * A command's, file change's or permission request's key is `exec`, `apply_patch` or `permissions` and a digest of what
33
+ * it covers (`callScope`), which the runner keeps as it is and the cards offer as one exact call; an MCP approval's key
34
+ * names its server and tool, all of whose calls it covers. `rpcId` is the request's own JSON-RPC id, the identity of a
35
+ * request that names no item.
27
36
  */
28
37
  export declare function answerCodexRequest(turn: DriverTurn, method: string, params: Json, items: Map<string, Json>, rpcId: unknown): Promise<unknown>;
29
38
  /**
@@ -0,0 +1,34 @@
1
+ import { type ConnectorRelayAck, type ConnectorRelayClaim, type ConnectorRelayInputRef, type ConnectorRelaySettleOutcome } from '../internal/backend-contracts/index.js';
2
+ import { type RuntimeDriver } from '../runner/types.js';
3
+ /**
4
+ * The driver boundary (spec §11.3): what the relay channel calls on behalf of the remote assistant, through the
5
+ * connector's pickup tools. Each running turn is one pending input until the assistant claims it.
6
+ */
7
+ export interface ConnectorInbox {
8
+ /** Unclaimed inputs in the room, oldest first. */
9
+ pending(conversationId: string, limit?: number): ConnectorRelayInputRef[];
10
+ /**
11
+ * The first claim gets `runAllowed` and the turn's input, once Canon confirms the agent may still act on it (its
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.
14
+ */
15
+ claim(inputId: string, requestId: string, options?: {
16
+ deadline?: number;
17
+ }): Promise<ConnectorRelayClaim>;
18
+ /** The turn's one final. */
19
+ reply(inputId: string, requestId: string, text: string): ConnectorRelayAck;
20
+ /** `completed` or `no_reply`: a silent turn; `failed`: the runner's failure notice. */
21
+ settle(inputId: string, requestId: string, outcome: ConnectorRelaySettleOutcome): ConnectorRelayAck;
22
+ }
23
+ export interface ConnectorDriver extends RuntimeDriver {
24
+ readonly name: 'connector';
25
+ readonly inbox: ConnectorInbox;
26
+ }
27
+ /**
28
+ * The `connector` driver: the runtime is an assistant its provider runs (ChatGPT, Claude.ai and the like), reached
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.
33
+ */
34
+ export declare function connectorDriver(): ConnectorDriver;