@canonmsg/agent-sdk 13.2.0 → 13.3.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 (33) hide show
  1. package/dist/chunks/{chunk-5YKGDUJX.js → chunk-AVR7QCHK.js} +369 -416
  2. package/dist/chunks/{chunk-DTAWEQCM.js → chunk-BQGXOAIW.js} +2 -2
  3. package/dist/chunks/{chunk-5WPAB5MB.js → chunk-CUPSYLBV.js} +2041 -2049
  4. package/dist/chunks/{chunk-YJUPKU47.js → chunk-HFT75BNB.js} +2 -2
  5. package/dist/chunks/{chunk-YHDDOADK.js → chunk-NIORQKUI.js} +1 -1
  6. package/dist/chunks/{chunk-6FD6KFQN.js → chunk-ZT3TJZAV.js} +400 -270
  7. package/dist/chunks/{mls-crypto-NZVMSUFU.js → mls-crypto-AW7TY744.js} +2 -2
  8. package/dist/cli.js +141 -26
  9. package/dist/index.js +6 -6
  10. package/dist/internal/agent-tools/communication-tool.d.ts +80 -0
  11. package/dist/internal/agent-tools/index.d.ts +1 -0
  12. package/dist/internal/backend-contracts/endpoint.d.ts +25 -0
  13. package/dist/internal/backend-contracts/mlsDevKeyPackages.d.ts +4 -0
  14. package/dist/internal/core/client.d.ts +0 -4
  15. package/dist/internal/core/endpoint-client.d.ts +8 -0
  16. package/dist/internal/core/index.d.ts +1 -1
  17. package/dist/internal/core/registration-profile.d.ts +4 -1
  18. package/dist/internal/core/types.d.ts +3 -1
  19. package/dist/internal/endpoint/engine.d.ts +34 -9
  20. package/dist/internal/endpoint/events.d.ts +1 -2
  21. package/dist/internal/endpoint/index.d.ts +0 -5
  22. package/dist/internal/endpoint/media.d.ts +4 -63
  23. package/dist/internal/endpoint/mls-dev-groups.d.ts +9 -1
  24. package/dist/internal/endpoint/queue.d.ts +1 -3
  25. package/dist/media.js +4 -4
  26. package/dist/mls-wasm-host.js +3 -3
  27. package/dist/runner/mls.d.ts +12 -2
  28. package/dist/runner/runner.d.ts +5 -2
  29. package/package.json +2 -3
  30. package/dist/internal/endpoint/artifact-images.d.ts +0 -60
  31. package/dist/internal/endpoint/artifact-reducer.d.ts +0 -35
  32. package/dist/internal/endpoint/artifact-views.d.ts +0 -113
  33. package/dist/internal/endpoint/artifacts.d.ts +0 -75
@@ -2,8 +2,8 @@ import {
2
2
  createMlsDevCredentialVerifier,
3
3
  mlsDevDeviceHttpConfiguration,
4
4
  parseMlsDevClientConfiguration
5
- } from "./chunk-YHDDOADK.js";
6
- import "./chunk-5WPAB5MB.js";
5
+ } from "./chunk-NIORQKUI.js";
6
+ import "./chunk-CUPSYLBV.js";
7
7
  export {
8
8
  createMlsDevCredentialVerifier,
9
9
  mlsDevDeviceHttpConfiguration,
package/dist/cli.js CHANGED
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  MLS_SETUP_ACTIONS,
4
+ RUNNER_CLIENT,
4
5
  acpDriver,
5
6
  awaitErrand,
6
7
  checkAcp,
@@ -25,28 +26,30 @@ import {
25
26
  runMlsDevSetup,
26
27
  startRelayChannel,
27
28
  writeRelayCredential
28
- } from "./chunks/chunk-6FD6KFQN.js";
29
+ } from "./chunks/chunk-ZT3TJZAV.js";
29
30
  import {
30
31
  assertPrivateHome,
31
32
  runnerLockName
32
- } from "./chunks/chunk-DTAWEQCM.js";
33
+ } from "./chunks/chunk-BQGXOAIW.js";
33
34
  import {
34
35
  CanonApiError,
35
36
  CanonClient,
36
37
  acquireLock,
38
+ clearPendingRegistration,
39
+ ensureAgentProfile,
37
40
  isProfileLocked,
41
+ loadPendingRegistrations,
38
42
  loadProfiles,
39
- readCliPackageVersion,
40
43
  resolveCanonRuntimeConnection,
41
44
  runCli,
42
45
  verifyCanonRuntimeConnection
43
- } from "./chunks/chunk-5YKGDUJX.js";
44
- import "./chunks/chunk-YJUPKU47.js";
45
- import "./chunks/chunk-YHDDOADK.js";
46
+ } from "./chunks/chunk-AVR7QCHK.js";
47
+ import "./chunks/chunk-HFT75BNB.js";
48
+ import "./chunks/chunk-NIORQKUI.js";
46
49
  import {
47
- __name,
48
- isCanonClientVersion
49
- } from "./chunks/chunk-5WPAB5MB.js";
50
+ AGENT_HOME_ELSEWHERE,
51
+ __name
52
+ } from "./chunks/chunk-CUPSYLBV.js";
50
53
 
51
54
  // src/cli.ts
52
55
  import { realpathSync } from "node:fs";
@@ -54,8 +57,23 @@ import { homedir, tmpdir } from "node:os";
54
57
  import { dirname, join, resolve } from "node:path";
55
58
  import { pathToFileURL } from "node:url";
56
59
  import { parseArgs } from "node:util";
57
- var RUNNER_VERSION = readCliPackageVersion(import.meta.url);
58
- var RUNNER_CLIENT = isCanonClientVersion(RUNNER_VERSION) ? { kind: "runner", version: RUNNER_VERSION } : void 0;
60
+ function exitCodeFor(error) {
61
+ return error?.code === AGENT_HOME_ELSEWHERE ? 0 : 1;
62
+ }
63
+ __name(exitCodeFor, "exitCodeFor");
64
+ function stopThenExit(stop, code, exit = process.exit, boundMs = 15e3) {
65
+ let done = false;
66
+ const finish = /* @__PURE__ */ __name(() => {
67
+ if (!done) {
68
+ done = true;
69
+ clearTimeout(bound);
70
+ exit(code);
71
+ }
72
+ }, "finish");
73
+ const bound = setTimeout(finish, boundMs);
74
+ void Promise.resolve().then(stop).catch(() => void 0).finally(finish);
75
+ }
76
+ __name(stopThenExit, "stopThenExit");
59
77
  function once(handler) {
60
78
  let called = false;
61
79
  return (...args) => {
@@ -69,6 +87,8 @@ __name(once, "once");
69
87
  var HELP = `canon-agent: run a Canon agent on the unified runner
70
88
 
71
89
  Usage:
90
+ canon-agent run --driver <driver> --name <name> --owner-phone <+E.164> [--description <text>]
91
+ [--environment <id>] ... (the first run: registers the agent, then runs)
72
92
  canon-agent run --driver code --module <path> [--profile <name>] [--environment <id>]
73
93
  canon-agent run --driver claude [--cwd <dir>] [--model <id>] [--permission-mode <mode>]
74
94
  [--claude-path <path>] [--profile <name>] [--environment <id>]
@@ -99,6 +119,19 @@ command also takes --mls-dev-config <path>, an operator's own custody instead. W
99
119
  of these, the agent refuses every room, and where Canon encrypts every new conversation
100
120
  it does not start.
101
121
 
122
+ First run: with --name and --owner-phone, a run whose profile (--profile, default the
123
+ name) has no key asks the owner to approve the agent in Canon, waits up to 30 minutes,
124
+ saves the profile and runs. A run whose saved key Canon refuses asks the owner for a
125
+ new key for the same agent the same way; the agent keeps its conversations, devices and
126
+ connections. The request stays open while nobody answers: run the command again to keep
127
+ waiting. A declined request ends the run.
128
+
129
+ One home: an agent answers from one place at a time. A run on a new machine, or with an
130
+ empty $CANON_HOME, takes it over at start, and the run it replaced stops and exits 0, so
131
+ a supervisor that restarts only on failure leaves it stopped. That run does not take the
132
+ home back by itself; start it with --take-over to move the agent back there. While the
133
+ agent's owner has Canon's relay on for its MCP connection, no run starts (exit 0).
134
+
102
135
  Waking and sleeping: code, claude, codex and acp also take [--wake-config <path> |
103
136
  --wake-off] [--idle-timeout <duration>]. With --wake-config the agent registers, at
104
137
  each start, a public HTTPS callback for up to 32 rooms; Canon POSTs it a signed,
@@ -191,6 +224,8 @@ Flags:
191
224
  with a signed challenge first; the agent does not start if
192
225
  that fails. Without the flag a registration is left as it is.
193
226
  --wake-off Remove the agent's wake callback at start.
227
+ --take-over Move the agent's home back to this machine after a run
228
+ elsewhere took it over.
194
229
  --idle-timeout <d> Stop and exit 0 after this long with nothing to do: whole
195
230
  seconds, or 90s, 10m, 1h. Typing in a room does not reach the
196
231
  agent: a host that also receives typing hints decides itself
@@ -221,6 +256,9 @@ Flags:
221
256
  1 unless it was accepted (a note kept, a post sent, an errand's
222
257
  turn ended and any report it sent to --origin accepted).
223
258
  --profile <name> A profile in $CANON_HOME/agents.json (key and endpoints).
259
+ --name <name> run: the agent's name, to register it (with --owner-phone).
260
+ --owner-phone <phone> run: the owner's phone number in Canon, international format.
261
+ --description <text> run: what the agent does, shown to its owner (default: the name).
224
262
  --environment <id> canon-prod-v1 or canon-dev-v1 (default: CANON_ENVIRONMENT_ID).
225
263
  --help, -h Show this help.
226
264
  --version, -V Show the package version.
@@ -310,10 +348,11 @@ async function main() {
310
348
  if (trustedAgents.some((id) => !/^[\w.:-]{1,200}$/.test(id))) throw new UsageError("--trust-agent takes an agent id.");
311
349
  const instructions = values.instructions !== void 0 ? await loadInstructions(resolve(values.instructions)) : void 0;
312
350
  const wake = values["wake-off"] ? null : values["wake-config"] !== void 0 ? await loadWakeConfiguration(resolve(values["wake-config"])) : void 0;
351
+ const registered = await ensureRegistered(values, values.driver);
352
+ if (registered) values.profile = registered;
313
353
  const { environmentId, home, profile, apiKey } = await settings(values);
314
354
  const mls = await mlsDevice(values, environmentId, home);
315
- const baseUrl = process.env.CANON_BASE_URL || profile?.baseUrl;
316
- const streamUrl = process.env.CANON_STREAM_URL || profile?.streamUrl;
355
+ const { apiBaseUrl: baseUrl, streamUrl } = connectionFor(environmentId, profile);
317
356
  let driver;
318
357
  if (values.driver === "claude") {
319
358
  driver = claudeDriver({
@@ -366,8 +405,8 @@ async function main() {
366
405
  ...team,
367
406
  ...trustedAgents.length ? { trustedAgents } : {},
368
407
  ...values["allow-session-for-destructive"] ? { sessionApprovalForDestructive: true } : {},
369
- ...RUNNER_CLIENT ? { client: RUNNER_CLIENT } : {},
370
408
  ...wake !== void 0 ? { wake } : {},
409
+ ...values["take-over"] ? { takeOver: true } : {},
371
410
  // Idle: a clean stop and exit 0, so a supervisor that restarts only on failure leaves the agent stopped until it is woken.
372
411
  ...idleTimeoutMs !== void 0 ? { idleTimeoutMs, onIdle: once(() => {
373
412
  console.error(`canon-agent: nothing to do for ${values["idle-timeout"]}; stopping.`);
@@ -378,7 +417,7 @@ async function main() {
378
417
  }) } : {},
379
418
  onFatal: once((error) => {
380
419
  console.error(`canon-agent: ${error.message}`);
381
- void runner.stop().catch(() => void 0).finally(() => process.exit(1));
420
+ stopThenExit(() => runner.stop(), exitCodeFor(error));
382
421
  })
383
422
  });
384
423
  stopOnSignals(() => runner.stop());
@@ -394,8 +433,7 @@ __name(main, "main");
394
433
  async function runRelay(values, team) {
395
434
  const { environmentId, home, profile } = await settings(values, { key: false });
396
435
  const relay = await loadRelayConfiguration(values["relay-config"], { environmentId });
397
- const baseUrl = process.env.CANON_BASE_URL || profile?.baseUrl;
398
- const streamUrl = process.env.CANON_STREAM_URL || profile?.streamUrl;
436
+ const { apiBaseUrl: baseUrl, streamUrl } = connectionFor(environmentId, profile);
399
437
  let current;
400
438
  let latest;
401
439
  let switching = Promise.resolve();
@@ -446,10 +484,12 @@ async function runRelay(values, team) {
446
484
  relay: true,
447
485
  installationId: stored2.installationId,
448
486
  ...team,
449
- ...RUNNER_CLIENT ? { client: RUNNER_CLIENT } : {},
450
487
  onFatal: once((error) => {
451
488
  console.error(`canon-agent: ${error.message}`);
452
- void Promise.resolve().then(() => channel.close()).then(() => runner.stop()).catch(() => void 0).finally(() => process.exit(1));
489
+ stopThenExit(async () => {
490
+ await channel.close();
491
+ await runner.stop();
492
+ }, 1);
453
493
  })
454
494
  });
455
495
  try {
@@ -522,11 +562,85 @@ function parse() {
522
562
  cancel: { type: "string" },
523
563
  "wake-config": { type: "string" },
524
564
  "wake-off": { type: "boolean" },
525
- "idle-timeout": { type: "string" }
565
+ "idle-timeout": { type: "string" },
566
+ "take-over": { type: "boolean" },
567
+ name: { type: "string" },
568
+ "owner-phone": { type: "string" },
569
+ description: { type: "string" }
526
570
  }
527
571
  });
528
572
  }
529
573
  __name(parse, "parse");
574
+ var REGISTRATION_WAIT_MS = 30 * 6e4;
575
+ var REGISTRATION_CLIENT_TYPES = { claude: "claude-code", codex: "codex" };
576
+ async function ensureRegistered(values, driver) {
577
+ const name = values.name?.trim(), ownerPhone = values["owner-phone"]?.trim();
578
+ if (!name && !ownerPhone && values.description === void 0) return void 0;
579
+ if (!name || !ownerPhone) throw new UsageError("--name and --owner-phone go together (and --description with them).");
580
+ if (process.env.CANON_API_KEY) throw new UsageError("--name and --owner-phone keep the agent's key in a profile: unset CANON_API_KEY.");
581
+ const environmentId = values.environment || process.env.CANON_ENVIRONMENT_ID;
582
+ if (!environmentId) throw new Error("CANON_ENVIRONMENT_ID (or --environment) is required.");
583
+ const home = resolve(process.env.CANON_HOME || join(homedir(), ".canon"));
584
+ await assertPrivateHome(home);
585
+ const profile = values.profile || name;
586
+ const saved = loadProfiles(home)[profile];
587
+ if (saved && saved.environmentId !== environmentId) throw new Error(`Profile "${profile}" belongs to ${saved.environmentId}, not ${environmentId}.`);
588
+ const connection = connectionFor(environmentId, saved);
589
+ const agentName = saved?.agentName ?? name;
590
+ const register = /* @__PURE__ */ __name(async (reconnect) => {
591
+ const pending = loadPendingRegistrations()[profile];
592
+ const ask = reconnect || pending?.requestedAgentId ? `a new key for ${agentName}` : agentName;
593
+ const waiting = /* @__PURE__ */ __name(() => console.error(`canon-agent: ask the owner of ${ownerPhone} to approve ${ask} in Canon. Waiting up to 30 minutes; the request stays open until they answer, and running this command again keeps waiting.`), "waiting");
594
+ if (pending && !pending.credentialAgentId) waiting();
595
+ const asked = reconnect || !!pending || !saved;
596
+ const progress = await ensureAgentProfile({
597
+ profile,
598
+ reconnect,
599
+ waitMs: REGISTRATION_WAIT_MS,
600
+ onSubmitted: waiting,
601
+ connection,
602
+ input: {
603
+ name,
604
+ description: values.description?.trim() || name,
605
+ ownerPhone,
606
+ clientType: REGISTRATION_CLIENT_TYPES[driver] ?? "generic",
607
+ developerInfo: `canon-agent run --driver ${driver}`
608
+ }
609
+ });
610
+ switch (progress.status) {
611
+ case "approved":
612
+ if (asked) console.error(`canon-agent: ${progress.agentName} is approved; its key is saved in profile "${profile}".`);
613
+ return progress.apiKey;
614
+ case "pending":
615
+ throw new Error(`Nobody has answered the request for ${ask} yet. It stays open: run this command again to keep waiting.`);
616
+ case "rejected":
617
+ clearPendingRegistration(profile);
618
+ throw new Error(`The owner declined ${ask}. Nothing was saved; run this command again to ask again.`);
619
+ case "credential-expired":
620
+ clearPendingRegistration(profile);
621
+ throw new Error(`The owner approved ${ask}, but its key was not collected within 48 hours and Canon removed it. Run this command again to ask again.`);
622
+ case "credential-delivered":
623
+ clearPendingRegistration(profile);
624
+ throw new Error(`The key for ${ask} was already collected elsewhere, so this run cannot have it. Run this command again to ask again.`);
625
+ }
626
+ }, "register");
627
+ const apiKey = await register(false);
628
+ try {
629
+ await new CanonClient(apiKey, resolveCanonRuntimeConnection(connection).apiBaseUrl).getAgentMe();
630
+ return profile;
631
+ } catch (error) {
632
+ if (!(error instanceof CanonApiError) || error.status !== 401) throw error;
633
+ }
634
+ console.error(`canon-agent: Canon no longer accepts ${agentName}'s saved key.`);
635
+ await register(true);
636
+ return profile;
637
+ }
638
+ __name(ensureRegistered, "ensureRegistered");
639
+ function connectionFor(environmentId, profile) {
640
+ const apiBaseUrl = process.env.CANON_BASE_URL || profile?.baseUrl, streamUrl = process.env.CANON_STREAM_URL || profile?.streamUrl;
641
+ return { environmentId, ...apiBaseUrl ? { apiBaseUrl } : {}, ...streamUrl ? { streamUrl } : {} };
642
+ }
643
+ __name(connectionFor, "connectionFor");
530
644
  async function settings(values, { key = true } = {}) {
531
645
  const environmentId = values.environment || process.env.CANON_ENVIRONMENT_ID;
532
646
  if (!environmentId) throw new Error("CANON_ENVIRONMENT_ID (or --environment) is required.");
@@ -564,11 +678,7 @@ async function mlsSetup(values) {
564
678
  let client;
565
679
  let lock;
566
680
  try {
567
- const connection = resolveCanonRuntimeConnection({
568
- environmentId,
569
- apiBaseUrl: process.env.CANON_BASE_URL || profile?.baseUrl,
570
- streamUrl: process.env.CANON_STREAM_URL || profile?.streamUrl
571
- });
681
+ const connection = resolveCanonRuntimeConnection(connectionFor(environmentId, profile));
572
682
  await verifyCanonRuntimeConnection(connection);
573
683
  client = new CanonClient(apiKey, connection.apiBaseUrl, {
574
684
  environmentId,
@@ -660,5 +770,10 @@ __name(stopOnSignals, "stopOnSignals");
660
770
  runCli(import.meta.url, main, (error) => {
661
771
  console.error(`canon-agent: ${error instanceof Error ? error.message : String(error)}`);
662
772
  if (error instanceof UsageError) console.error(HELP.split("\n\nFlags:")[0]);
663
- process.exit(1);
773
+ process.exit(error instanceof UsageError ? 1 : exitCodeFor(error));
664
774
  }, { name: "canon-agent", help: HELP });
775
+ export {
776
+ ensureRegistered,
777
+ exitCodeFor,
778
+ stopThenExit
779
+ };
package/dist/index.js CHANGED
@@ -26,8 +26,8 @@ import {
26
26
  runMlsDevSetup,
27
27
  runtimeDescriptor,
28
28
  startRelayChannel
29
- } from "./chunks/chunk-6FD6KFQN.js";
30
- import "./chunks/chunk-DTAWEQCM.js";
29
+ } from "./chunks/chunk-ZT3TJZAV.js";
30
+ import "./chunks/chunk-BQGXOAIW.js";
31
31
  import {
32
32
  CanonApiError,
33
33
  DEFAULT_ANTHROPIC_REQUEST_HEADROOM_BYTES,
@@ -52,10 +52,10 @@ import {
52
52
  toAnthropicImageBlock,
53
53
  toAnthropicImageBlocksWithinBudget,
54
54
  uploadMediaFile
55
- } from "./chunks/chunk-5YKGDUJX.js";
56
- import "./chunks/chunk-YJUPKU47.js";
57
- import "./chunks/chunk-YHDDOADK.js";
58
- import "./chunks/chunk-5WPAB5MB.js";
55
+ } from "./chunks/chunk-AVR7QCHK.js";
56
+ import "./chunks/chunk-HFT75BNB.js";
57
+ import "./chunks/chunk-NIORQKUI.js";
58
+ import "./chunks/chunk-CUPSYLBV.js";
59
59
  export {
60
60
  CanonApiError,
61
61
  ConnectorRelayError,
@@ -0,0 +1,80 @@
1
+ import { type CanonClient, type CommunicateInput } from '../core/index.js';
2
+ export interface VerbToolDefinition {
3
+ name: string;
4
+ description: string;
5
+ inputSchema: Record<string, unknown>;
6
+ }
7
+ export interface VerbToolResult {
8
+ content: Array<{
9
+ type: 'text';
10
+ text: string;
11
+ }>;
12
+ isError?: boolean;
13
+ }
14
+ export declare const CANON_COMMUNICATE_TOOL_NAME = "communicate";
15
+ export declare const CANON_COMMUNICATE_DISCOVERY_MAX_RESULTS = 10;
16
+ /**
17
+ * What each action takes: the fields it needs, then those it may have. The legacy aliases (start_direct,
18
+ * create_group, manage_group_members) take their action's fields; the parser still accepts them, and no schema offers them.
19
+ */
20
+ declare const ACTION_FIELDS: {
21
+ readonly list_conversations: {
22
+ readonly required: readonly [];
23
+ readonly optional: readonly ["limit"];
24
+ };
25
+ readonly discover_agents: {
26
+ readonly required: readonly [];
27
+ readonly optional: readonly ["query", "limit", "cursor"];
28
+ };
29
+ readonly message_existing: {
30
+ readonly required: readonly ["conversationId", "text"];
31
+ readonly optional: readonly ["messageId"];
32
+ };
33
+ readonly start_conversation: {
34
+ readonly required: readonly ["principalId", "text"];
35
+ readonly optional: readonly ["selection", "messageId"];
36
+ };
37
+ readonly create_conversation: {
38
+ readonly required: readonly ["memberIds"];
39
+ readonly optional: readonly ["name"];
40
+ };
41
+ readonly manage_participants: {
42
+ readonly required: readonly ["conversationId", "userId", "operation"];
43
+ readonly optional: readonly [];
44
+ };
45
+ readonly leave_conversation: {
46
+ readonly required: readonly ["conversationId"];
47
+ readonly optional: readonly [];
48
+ };
49
+ readonly forward_message: {
50
+ readonly required: readonly ["sourceConversationId", "targetConversationId", "messageId"];
51
+ readonly optional: readonly ["text"];
52
+ };
53
+ readonly share_contact: {
54
+ readonly required: readonly ["conversationId", "contactUserId"];
55
+ readonly optional: readonly ["text", "messageId"];
56
+ };
57
+ };
58
+ export type CanonCommunicateAction = keyof typeof ACTION_FIELDS;
59
+ /** The actions a binding offers, each with the fields it shows for it (an action's required fields always). */
60
+ export type CommunicateShape = Readonly<Partial<Record<CanonCommunicateAction, readonly string[]>>>;
61
+ /** Every action with every field: the complete communication surface. */
62
+ export declare const CANON_COMMUNICATE_SHAPE: CommunicateShape;
63
+ /**
64
+ * One flat object schema for `communicate`: `action` names the action, and every other field is optional and says which
65
+ * actions take it. Claude reads a tool's schema as one object, so a top-level `oneOf` showed it only its first
66
+ * alternative (the driver trial on Dev, 2026-11-09); the parser checks each combination instead.
67
+ */
68
+ export declare function communicateInputSchema(shape: CommunicateShape, textMaxLength?: number | null): Record<string, unknown>;
69
+ /**
70
+ * The complete model-facing communication surface. It is exported separately
71
+ * from the in-conversation verb list so each runtime can opt into it.
72
+ */
73
+ export declare function canonCommunicateToolDefinition(name?: string): VerbToolDefinition;
74
+ /**
75
+ * Runtime validation for bindings that dispatch directly. The schema is flat, so this is where a combination is checked:
76
+ * an action the binding does not offer, a field the action needs, or one it does not take, is refused by name.
77
+ */
78
+ export declare function parseCommunicateToolInput(value: unknown, shape?: CommunicateShape): CommunicateInput;
79
+ export declare function executeCanonCommunicateTool(client: CanonClient, args: unknown, shape?: CommunicateShape): Promise<VerbToolResult>;
80
+ export {};
@@ -0,0 +1 @@
1
+ export { CANON_COMMUNICATE_DISCOVERY_MAX_RESULTS, CANON_COMMUNICATE_SHAPE, CANON_COMMUNICATE_TOOL_NAME, canonCommunicateToolDefinition, communicateInputSchema, executeCanonCommunicateTool, parseCommunicateToolInput, type CanonCommunicateAction, type CommunicateShape, type VerbToolDefinition, type VerbToolResult, } from './communication-tool.js';
@@ -118,3 +118,28 @@ export declare const ENDPOINT_ID_PATTERN: RegExp;
118
118
  export declare const ENDPOINT_ACTION_PATTERN: RegExp;
119
119
  export declare function isEndpointOperationRequest(value: unknown): value is EndpointOperationRequest;
120
120
  export declare function isEndpointOperationReceipt(value: unknown): value is EndpointOperationReceipt;
121
+ /**
122
+ * An agent answers from one home at a time: Canon's relay for its MCP connection while the owner has the relay on,
123
+ * otherwise its runner, the installation that started most recently (`initialize_endpoint` moves it there). Everything
124
+ * the other home sends is refused with `AGENT_HOME_ELSEWHERE`, so no message gets two answers. It is read from the
125
+ * agent's own document alone (`users/{agentId}`), so every hot path can check it without another read.
126
+ *
127
+ * `since` (ms, Canon's clock): this home answers for work from here, for its work floor and in the refusal's text. Nothing
128
+ * Canon accepted before it is that home's work, since the home before may have answered it. For the relay, its approval.
129
+ * For a runner, the later of `installedAt` and the relay's turn-off, after which the runner it replaced is the home again
130
+ * by itself. Despite its name, `installedAt` holds where its installation answers for work from (its baseline's cutover):
131
+ * a first installation's approval, a later one's take-over. Older markers hold a first installation's first start, and
132
+ * are read as they are.
133
+ */
134
+ export declare const AGENT_HOME_ELSEWHERE = "AGENT_HOME_ELSEWHERE";
135
+ export type AgentHome = {
136
+ kind: 'connector';
137
+ since?: number;
138
+ } | {
139
+ kind: 'runner';
140
+ installationId?: string;
141
+ since?: number;
142
+ };
143
+ export declare function readAgentHome(agentConfig: unknown): AgentHome;
144
+ /** The one refusal both homes give the other: what is true now, and what the owner or operator can do. */
145
+ export declare function agentHomeElsewhereMessage(agentName: string | undefined, home: AgentHome): string;
@@ -1,5 +1,9 @@
1
1
  export declare const MLS_DEV_KEY_PACKAGES_MAX_BYTES = 32768;
2
2
  export declare const MLS_DEV_KEY_PACKAGE_TLS_MAX_BYTES = 16384;
3
+ /** How long a claim holds its recipient's slot before another member's device may take it over (mls-rooms-by-default.md
4
+ * §2.4). On Dev from 2026-10-20 to 11-08, 113 Adds were accepted a median 2.4 s after their claim, 99% within 9.4 s, and
5
+ * the slowest in 61.5 s: two minutes is about twice the slowest, and a dead claimant holds a device off no longer. */
6
+ export declare const MLS_DEV_KEY_PACKAGE_CLAIM_LEASE_MS: number;
3
7
  export interface MlsDevKeyPackagePublishRequest {
4
8
  readonly version: 1;
5
9
  readonly requestId: string;
@@ -8,10 +8,6 @@ export interface ResumableMediaUploadResult {
8
8
  status?: 'processing' | 'ready' | 'failed';
9
9
  attachment: MediaAttachment;
10
10
  }
11
- /**
12
- * Agent-facing ergonomics over the shared endpoint engine. Authentication and
13
- * operator registration remain outside the conversation protocol.
14
- */
15
11
  export declare class CanonClient {
16
12
  private baseUrl;
17
13
  private apiKey;
@@ -30,6 +30,14 @@ export interface CanonClientEndpointOptions {
30
30
  mediaDirectory?: string;
31
31
  /** Operator inspection/import does not automatically publish queued operations. */
32
32
  autoDrain?: boolean;
33
+ /**
34
+ * This client is the agent's runner, and its installation is the agent's home: a new installation takes the home at
35
+ * start; one that lost it to another refuses to start (`AGENT_HOME_ELSEWHERE`) unless `takeOver` moves it back, so two
36
+ * supervised machines never take it from each other in turn. Other clients (a script, a CLI step) never move the home.
37
+ */
38
+ claimHome?: boolean;
39
+ /** With `claimHome`: move the home back to this installation after another one took it. */
40
+ takeOver?: boolean;
33
41
  /** Replicated cloud hosts supply operator-owned durable byte storage. */
34
42
  mediaBytes?: EndpointMediaBytes<string>;
35
43
  /** Test seam for the client's out-of-protocol HTTP (identity, profile); production leaves it unset. */
@@ -55,5 +55,5 @@ export { handleCliMetadataRequest, isDirectExecution, readCliPackageVersion, run
55
55
  export type { CliMetadata, RunCliMetadataOptions, } from './cli-metadata.js';
56
56
  export { CANON_VERB_NAMES, CANON_VERB_NAMESPACE, CANON_VERBS_SCHEMA_ID, CANON_VERBS_SCHEMA_VERSION, CANON_VERB_WIRE_SCHEMA_ID, CANON_VERB_WIRE_SCHEMA_VERSION, CANON_CARD_SCHEMA_ID, VERB_LIMITS, VERB_RATE_LIMITS, VERB_WIRE_ENVELOPE_FIELDS, canonVerbToolName, findVerbByteLimitViolations, findVerbWireEnvelopeViolations, getVerbResultSchema, mergeVerbWireToIntent, normalizeVerbNativeMetadata, projectVerbIntentToWire, type CanonVerbName, type CanonVerbWireEnvelope, type CanonVerbWireRequest, type CanonVerbWireResponse, type CanonVerbJsonSchema, type SendToInput, type SendToResult, type VerbMessageOptions, type VerbNativeMetadata, type VerbWireAccepted, type VerbWireBody, } from '../backend-contracts/index.js';
57
57
  export { DEFAULT_DISCOVER_AGENTS_LIMIT, MAX_DISCOVER_AGENTS_LIMIT, MAX_DISCOVER_AGENTS_QUERY_LENGTH, type CanonAgentDirectoryEntry, type DiscoverAgentsInput, type DiscoverAgentsResult, } from '../backend-contracts/index.js';
58
- export { parseDirectConversationSelection, readCommunicationRule, type CommunicateInput, type CommunicateResult, type CommunicationRule, type DirectConversationSelection, type StartDirectInput, type StartDirectResult, } from '../backend-contracts/index.js';
58
+ export { MLS_DEV_TEXT_MAX_BYTES, parseDirectConversationSelection, readCommunicationRule, type CommunicateInput, type CommunicateResult, type CommunicationRule, type DirectConversationSelection, type StartDirectInput, type StartDirectResult, } from '../backend-contracts/index.js';
59
59
  export { canonMediaDownloadUrl, sanitizeDownloadFileName, buildMediaContentDisposition, getStoredFileExtension } from '../backend-contracts/index.js';
@@ -3,7 +3,10 @@ export interface EnsureAgentProfileOptions extends Omit<ResumeRegistrationOption
3
3
  /** The local profile store owns its stable localRegistrationId. */
4
4
  input: Omit<ResumeRegistrationOptions['input'], 'localRegistrationId'>;
5
5
  profile: string;
6
- /** Explicitly request a new registration even if a usable local credential exists. */
6
+ /**
7
+ * Ask the owner for a new key for this profile's agent (its saved agent ID, or `input.requestedAgentId`), even if a
8
+ * usable local credential exists. Approval only replaces the key: the agent keeps its rooms, devices and connections.
9
+ */
7
10
  reconnect?: boolean;
8
11
  }
9
12
  /** Reuse an environment-bound local profile, or resume its owner-approved registration. */
@@ -1,4 +1,4 @@
1
- import type { AgentOrigin, AgentOriginDeclaration, ArtifactContext } from '../backend-contracts/index.js';
1
+ import type { AgentHome, AgentOrigin, AgentOriginDeclaration, ArtifactContext } from '../backend-contracts/index.js';
2
2
  import type { CanonControlValue, CanonRuntimeStreamingMode, CanonRuntimeDetailTier, CanonRuntimeVisibility, CanonRuntimeCommandDescriptor } from '../backend-contracts/index.js';
3
3
  export type { AgentOrigin, AgentOriginDeclaration, CanonControlValue, CanonRuntimeStreamingMode, CanonRuntimeDetailTier, CanonRuntimeVisibility, CanonRuntimeActionAvailability, CanonRuntimeActionPlacement, CanonRuntimeActionCategory, CanonRuntimePrimitiveId, CanonRuntimeCommandArgumentKind, CanonRuntimeCommandArgumentChoice, CanonRuntimeCommandArgumentDescriptor, CanonRuntimeActionDispatch, CanonRuntimeActionDescriptor, CanonRuntimeCommandDescriptor, } from '../backend-contracts/index.js';
4
4
  import type { ExecutionEnvironmentMode } from './execution-environment-mode.js';
@@ -479,6 +479,8 @@ export interface AgentContext {
479
479
  ownershipVersion: number;
480
480
  /** Server-created new-principal marker; initialization still binds the installation atomically. */
481
481
  endpointBootstrapEligible?: boolean;
482
+ /** Where the agent answers from: Canon's relay, or the runner installation that started last (absent from older Canon). */
483
+ home?: AgentHome;
482
484
  canonContactId?: string;
483
485
  displayName?: string;
484
486
  avatarUrl?: string | null;
@@ -7,7 +7,7 @@ import { type EndpointSyncOptions, type EndpointSyncPage } from './sync.js';
7
7
  import { type EndpointEventObserver, type EndpointTransportEvent } from './events.js';
8
8
  import { type EndpointRecoveryOptions, type EndpointRecoveryBaseline } from './recovery.js';
9
9
  import { type EndpointRecoveryMigration, type EndpointMigrationReceipt } from './migration.js';
10
- import { type EndpointBackgroundMediaAdapter, type EndpointMediaBytes, type EndpointMediaPipeline } from './media.js';
10
+ import { type EndpointMediaBytes, type EndpointMediaPipeline } from './media.js';
11
11
  import { type EndpointOrdering } from './queue.js';
12
12
  import { type EndpointInboundHandoff, type EndpointInboundStartResult } from './handoff.js';
13
13
  import { type EndpointCompletion } from './completions.js';
@@ -55,8 +55,6 @@ export interface EndpointOperation {
55
55
  conversationId?: string;
56
56
  messageId?: string;
57
57
  agentId?: string;
58
- /** Ordering scope for shared-work commands, retained after payload erasure. */
59
- artifactId?: string;
60
58
  status: EndpointLocalStatus;
61
59
  fingerprint: string;
62
60
  preconditions?: EndpointPreconditions;
@@ -74,6 +72,11 @@ export interface EndpointOperation {
74
72
  /** Local-only binding of a withheld start's text; retained after the opener is sent or its payload is erased. */
75
73
  openerFingerprint?: string;
76
74
  receipt?: EndpointOperationReceipt;
75
+ /** A refused encrypted send this device encrypted again: the operation that replaced it, which retries and waits
76
+ * follow (mls-dev-groups.ts, resendText). */
77
+ resentAs?: string;
78
+ /** Local successor made after Canon refused an encrypted send, with readers and sender admission rechecked. */
79
+ automaticResend?: boolean;
77
80
  attempts: number;
78
81
  attempted: boolean;
79
82
  nextAttemptAt?: number;
@@ -150,10 +153,15 @@ export interface Endpoint {
150
153
  submit(intent: EndpointIntent, options?: EndpointExecuteOptions): Promise<EndpointOperation>;
151
154
  execute<T = unknown>(intent: EndpointIntent, options?: EndpointExecuteOptions): Promise<EndpointOperationReceipt<T>>;
152
155
  executeResult<T = unknown>(intent: EndpointIntent, options?: EndpointExecuteOptions): Promise<T>;
153
- /** reconcileOnly reads the server receipt; it never dispatches an operation. */
156
+ /** reconcileOnly reads and records the server receipt; it never dispatches or encrypts again. An operation encrypted
157
+ * again (`resentAs`) answers with the one that replaced it. An ordinary retry of a send Canon refused one change behind
158
+ * encrypts it again, by the rule its first refusal had, when a reconcile only recorded the refusal. */
154
159
  retry<T = unknown>(operationId: string, options?: {
155
160
  reconcileOnly?: boolean;
156
161
  }): Promise<EndpointOperationReceipt<T>>;
162
+ /** A person's Retry of an encrypted message Canon refused: encrypts it again, from this device's copy, for its room as
163
+ * it is now, under a new operation that replaces the refused one. Returns the operation to retry. */
164
+ resend(operationId: string): Promise<EndpointOperation>;
157
165
  drain(options?: {
158
166
  limit?: number;
159
167
  }): Promise<void>;
@@ -185,9 +193,7 @@ export interface Endpoint {
185
193
  observeQuery<T>(query: string, parameters: Record<string, unknown>, listener: (value: T) => void, onError?: (error: unknown) => void): () => void;
186
194
  refreshQueries(): void;
187
195
  observeEvents(listener: (event: EndpointTransportEvent) => void, options?: EndpointEventObserver): () => void;
188
- createMediaPipeline<Source>(bytes: EndpointMediaBytes<Source>, options?: {
189
- background?: EndpointBackgroundMediaAdapter<Source>;
190
- }): EndpointMediaPipeline<Source>;
196
+ createMediaPipeline<Source>(bytes: EndpointMediaBytes<Source>): EndpointMediaPipeline<Source>;
191
197
  registerMessageGroup(input: EndpointMessageGroupInput, options?: {
192
198
  signal?: AbortSignal;
193
199
  }): Promise<EndpointOperation>;
@@ -196,8 +202,9 @@ export interface Endpoint {
196
202
  cancelMessageGroup(id: string): Promise<void>;
197
203
  /** Runtime-specific publication policy; closures are never serialized.
198
204
  * Return false to defer, or throw a nonretryable EndpointError to retire an
199
- * unsent operation. An attempted operation still needs its server receipt. */
200
- registerDeliveryGuard(id: string, allow: () => Promise<boolean>): () => void;
205
+ * unsent operation. An attempted operation still needs its server receipt.
206
+ * Automatic resends retain the policy; only an audience-specific check may use the revalidated resend context. */
207
+ registerDeliveryGuard(id: string, allow: EndpointDeliveryGuard): () => void;
201
208
  /** Keep a start's opening line that Canon did not store, and send it once the room and the request allow (§2a.13). */
202
209
  deferOpener(input: {
203
210
  conversationId: string;
@@ -220,6 +227,21 @@ export interface Endpoint {
220
227
  /** Immutable operator-reviewed or server-proven cutover for new rooms/admissions. */
221
228
  configureRecoveryBaseline(config: EndpointRecoveryBaseline): Promise<void>;
222
229
  getRecoveryBaseline(): Promise<EndpointRecoveryBaseline | undefined>;
230
+ /**
231
+ * Work here starts at `cutoverAt` (Canon's clock), where this installation's turn as its agent's home began: a
232
+ * take-over, or Canon's relay turned off. Every input Canon accepted before it is history, never work, whether it is
233
+ * already in the inbox or still to come off a room's feed, since the home before may have answered it. Only an input a
234
+ * journaled output answers keeps it. The floor only ever moves later.
235
+ */
236
+ startWorkAt(cutoverAt: string): Promise<void>;
237
+ /**
238
+ * A runner moving its agent's home here, under a new `initialize_endpoint` operation each time, starts work from
239
+ * Canon's answer (`startWorkAt`). Only this call sends it. One that fails, or that a process left unanswered when it
240
+ * died, is never sent again (the latter is settled when the store next opens, before anything drains), so no later
241
+ * start makes a take-over without its operator. If Canon took the home all the same, the next start reads that from the
242
+ * agent's home and starts work from there.
243
+ */
244
+ takeOverHome(): Promise<void>;
223
245
  refreshRecovery(): Promise<void>;
224
246
  planRecoveryMigration(baseline: EndpointRecoveryBaseline, conversationIds?: string[]): Promise<EndpointRecoveryMigration>;
225
247
  importRecoveryMigration(manifest: EndpointRecoveryMigration, reviewedSha256: string): Promise<EndpointMigrationReceipt>;
@@ -270,5 +292,8 @@ export interface Endpoint {
270
292
  logout(): Promise<void>;
271
293
  close(): Promise<void>;
272
294
  }
295
+ export type EndpointDeliveryGuard = (context: {
296
+ automaticResend: boolean;
297
+ }) => Promise<boolean>;
273
298
  export declare function mergeConversationSnapshot<T extends EndpointConversation>(current: T | undefined, incoming: T): T;
274
299
  export declare function createEndpoint(options: EndpointOptions): Endpoint;
@@ -4,8 +4,7 @@ export interface EndpointTransportEvent {
4
4
  kind: string;
5
5
  data: Record<string, unknown>;
6
6
  }
7
- /** `artifacts` streams `artifact.changed` hints for rooms the principal belongs to. */
8
- export type EndpointEventFamily = 'messages' | 'contacts' | 'typing_presence' | 'runtime_turn' | 'voice' | 'artifacts';
7
+ export type EndpointEventFamily = 'messages' | 'contacts' | 'typing_presence' | 'runtime_turn' | 'voice';
9
8
  export interface EndpointEventObserver {
10
9
  families?: EndpointEventFamily[];
11
10
  /** Explicit principal projections; room projections follow membership. */
@@ -12,12 +12,7 @@ export * from './media.js';
12
12
  export * from './history.js';
13
13
  export * from './ephemeral.js';
14
14
  export * from './transient-publisher.js';
15
- export { createArtifactClient, ArtifactRejectedError, type ArtifactClient, type ArtifactClientOptions, type ArtifactCreateOptions, type ArtifactLibrary, type ArtifactWriteResult, } from './artifacts.js';
16
- export { createArtifactImageClient, type ArtifactImageAddInput, type ArtifactImageClient, type ArtifactImageGenerateInput, type ArtifactImageGenerateResult, type ArtifactImageRegion, } from './artifact-images.js';
17
15
  export { isTerminalImageGenerationError } from './image-generation-errors.js';
18
- export { describeArtifact, summarizeArtifact, type ArtifactChessMoveView, type ArtifactChessView, type ArtifactFormChangeView, type ArtifactFormFieldView, type ArtifactFormView, type ArtifactImageVariantView, type ArtifactImageView, type ArtifactPrincipalView, type ArtifactView, type ArtifactViewOptions, } from './artifact-views.js';
19
- export { reduceArtifactRecord, replayArtifactPage, artifactCommandConflicts, artifactFormConflicts, type ArtifactConflict } from './artifact-reducer.js';
20
- export type { ArtifactAsset, ArtifactChessMove, ArtifactChessPlayers, ArtifactChessState, ArtifactCommand, ArtifactCommandBody, ArtifactCommandRecord, ArtifactContext, ArtifactDefinition, ArtifactDomainOutcome, ArtifactFormChange, ArtifactFormColumn, ArtifactFormField, ArtifactFormFieldRemoval, ArtifactFormRow, ArtifactFormState, ArtifactFormTable, ArtifactFormValue, ArtifactImageState, ArtifactImageVariant, ArtifactJson, ArtifactKind, ArtifactOutcomeCode, ArtifactReadResult, ArtifactReference, ArtifactSelection, ArtifactSnapshot, } from '../backend-contracts/index.js';
21
16
  export { eraseEndpointPlaintext } from './retention.js';
22
17
  export type { EndpointOperationRequest, EndpointOperationReceipt, EndpointOperationError, EndpointPreconditions, EndpointEvent, EndpointQueryRequest, EndpointQueryResponse, } from '../backend-contracts/index.js';
23
18
  export { CANON_ENDPOINT_PROTOCOL } from '../backend-contracts/index.js';