@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.
@@ -3,6 +3,7 @@ import {
3
3
  BoundedFileReadError,
4
4
  CanonClient,
5
5
  MAX_CANON_MEDIA_BYTES,
6
+ NodeMlsDevCustodyError,
6
7
  RUNTIME_NEW_SESSION_ACTION,
7
8
  RUNTIME_STOP_ACTION,
8
9
  RUNTIME_STOP_AND_DROP_ACTION,
@@ -29,7 +30,7 @@ import {
29
30
  sendMessageWithRetryChunked,
30
31
  splitTextByUtf8Bytes,
31
32
  verifyCanonRuntimeConnection
32
- } from "./chunk-RFNYPZXD.js";
33
+ } from "./chunk-TFSURFF6.js";
33
34
  import {
34
35
  CONNECTOR_RELAY_ANSWER_WITHIN_MS,
35
36
  CONNECTOR_RELAY_INSTALLATION_PATTERN,
@@ -58,7 +59,7 @@ import {
58
59
  parseMlsDevGroupHead,
59
60
  parseMlsDevGroupRoster,
60
61
  resolveAllowedUploadMimeType
61
- } from "./chunk-5MI6Q3VS.js";
62
+ } from "./chunk-C2WJT3VX.js";
62
63
 
63
64
  // src/runner/types.ts
64
65
  var DriverSessionError = class extends Error {
@@ -464,6 +465,8 @@ var GUEST_ITEMS = /* @__PURE__ */ new Set([
464
465
  "subAgentActivity",
465
466
  "sleep"
466
467
  ]);
468
+ var OWN_LISTINGS = /* @__PURE__ */ new Set(["list_mcp_resources", "list_mcp_resource_templates"]);
469
+ var GUEST = "Only your owner can let you use tools in this conversation, and this turn holds a message from someone else: you cannot read files, run commands or use any tool but the canon MCP server's. Answer from the conversation and what you already know, and say so when something needs your owner.";
467
470
  var RISKY = /\brm\s+-[a-z]*[rf]|\bgit\s+(reset\s+--hard|push\s+(-f|--force))|\bdrop\s+(table|database)\b|\bmkfs\b|\bdd\s+if=/i;
468
471
  var IMAGE = /\.(png|jpe?g|gif|webp)$/i;
469
472
  var isRecord2 = /* @__PURE__ */ __name((value) => typeof value === "object" && value !== null && !Array.isArray(value), "isRecord");
@@ -750,10 +753,11 @@ function codexDriver(options) {
750
753
  settle(new Error(`Codex ended the turn ${ended.status === "interrupted" ? "interrupted" : `with an error${detail ? `: ${oneLine2(detail, 300)}` : ""}`}`));
751
754
  }, "conclude");
752
755
  const guard = /* @__PURE__ */ __name((item) => {
753
- if (!guest || GUEST_ITEMS.has(text(item.type)) || item.type === "mcpToolCall" && item.server === CANON) return;
756
+ if (!guest || GUEST_ITEMS.has(text(item.type)) || item.type === "mcpToolCall" && (item.server === CANON || item.server === "codex" && OWN_LISTINGS.has(text(item.tool)))) return;
754
757
  interrupt();
755
758
  entry.close();
756
- settle(new DriverSessionError(`Codex started ${text(item.type) || "an unknown item"} in a turn holding someone other than the owner's message`));
759
+ const what = item.type === "mcpToolCall" ? `mcpToolCall (${describe(item)})` : text(item.type) || "an unknown item";
760
+ settle(new DriverSessionError(`Codex started ${what} in a turn holding someone other than the owner's message`));
757
761
  }, "guard");
758
762
  const link = rpc(child, {
759
763
  notify(method, params) {
@@ -813,6 +817,7 @@ function codexDriver(options) {
813
817
  }
814
818
  if (turn.signal.aborted) return finish();
815
819
  const started = await link.call("turn/start", { threadId, input: [
820
+ ...guest ? [{ type: "text", text: GUEST, text_elements: [] }] : [],
816
821
  { type: "text", text: turn.prompt, text_elements: [] },
817
822
  // Images go to the model as images; every attachment's path is also in the prompt for Codex to read.
818
823
  ...turn.files.filter((file) => IMAGE.test(file)).map((path) => ({ type: "localImage", path }))
@@ -908,31 +913,43 @@ function connectorDriver() {
908
913
  },
909
914
  async claim(inputId, requestId, options = {}) {
910
915
  const entry = find(inputId);
911
- if (entry.state === "claiming") {
912
- if (entry.requestId !== requestId) throw new ConnectorRelayError(409, "CONNECTOR_CLAIM_CONFLICT", "This input belongs to another claim");
913
- throw new ConnectorRelayError(503, "CONNECTOR_RELAY_BUSY", "This claim is still being confirmed; try again");
916
+ if (entry.state === "claiming" && entry.requestId !== requestId) {
917
+ throw new ConnectorRelayError(409, "CONNECTOR_CLAIM_CONFLICT", "This input belongs to another claim");
914
918
  }
915
- if (entry.state !== "pending") {
919
+ if (entry.state !== "pending" && entry.state !== "claiming") {
916
920
  if (entry.requestId !== void 0 && entry.requestId !== requestId) {
917
921
  throw new ConnectorRelayError(409, "CONNECTOR_CLAIM_CONFLICT", "This input belongs to another claim");
918
922
  }
919
923
  return { inputId, requestId, runAllowed: false, state: entry.state };
920
924
  }
921
- entry.state = "claiming";
922
- entry.requestId = requestId;
925
+ if (entry.confirmation) throw new ConnectorRelayError(503, "CONNECTOR_RELAY_BUSY", "This claim is still being confirmed; retry the same request id");
926
+ if (entry.state === "pending") {
927
+ entry.state = "claiming";
928
+ entry.requestId = requestId;
929
+ }
930
+ const confirmation = entry.confirm().then(() => "ok", () => {
931
+ if (entry.state === "claiming") {
932
+ entry.state = "pending";
933
+ entry.requestId = void 0;
934
+ }
935
+ return "refused";
936
+ }).finally(() => {
937
+ entry.confirmation = void 0;
938
+ });
939
+ entry.confirmation = confirmation;
923
940
  const deadline = options.deadline ?? Date.now() + CONNECTOR_RELAY_ANSWER_WITHIN_MS;
924
941
  let timer;
925
942
  const late = new Promise((resolve3) => {
926
943
  timer = setTimeout(resolve3, Math.max(0, deadline - Date.now()), "late");
927
944
  });
928
- const confirmed = await Promise.race([entry.confirm().then(() => "ok", () => "refused"), late]).finally(() => clearTimeout(timer));
929
- if (entry.state === "claiming" && confirmed === "ok") {
945
+ const confirmed = await Promise.race([confirmation, late]).finally(() => clearTimeout(timer));
946
+ const state = entry.state;
947
+ if (state === "claiming" && confirmed === "ok") {
930
948
  entry.state = "claimed";
931
949
  return { inputId, requestId, runAllowed: true, state: "claimed", input: entry.input };
932
950
  }
933
- if (entry.state !== "claiming") throw withdrawn();
934
- entry.state = "pending";
935
- entry.requestId = void 0;
951
+ if (state === "claimed") return { inputId, requestId, runAllowed: false, state: "claimed" };
952
+ if (state === "withdrawn" || state === "answered") throw withdrawn();
936
953
  throw new ConnectorRelayError(503, "CONNECTOR_RELAY_BUSY", "Canon could not confirm this claim now; try again");
937
954
  },
938
955
  reply: /* @__PURE__ */ __name((inputId, requestId, text3) => answer(inputId, requestId, { outcome: "reply", text: text3 }), "reply"),
@@ -947,7 +964,10 @@ function connectorDriver() {
947
964
  activity: false,
948
965
  tools: false,
949
966
  outboundFiles: false,
950
- remote: true
967
+ remote: true,
968
+ // A turn here is an input waiting for (or with) the assistant, not work on this host: one per room, so a room
969
+ // whose assistant is away never keeps another room's input from being offered.
970
+ maxConcurrentTurns: Number.POSITIVE_INFINITY
951
971
  },
952
972
  inbox,
953
973
  runTurn(turn) {
@@ -1485,7 +1505,7 @@ async function loadMlsDevConfiguration(filePath, input) {
1485
1505
  return { configuration, custody: custody2, close: /* @__PURE__ */ __name(() => custody2.close(), "close") };
1486
1506
  }
1487
1507
  __name(loadMlsDevConfiguration, "loadMlsDevConfiguration");
1488
- var MLS_SETUP_ACTIONS = ["status", "provision", "enroll", "renew", "publish", "refresh"];
1508
+ var MLS_SETUP_ACTIONS = ["status", "setup", "provision", "enroll", "renew", "publish", "refresh"];
1489
1509
  function mlsDevRoomPackageId(identity, room) {
1490
1510
  const hex = createHash4("sha256").update(JSON.stringify([
1491
1511
  "canon.mls.dev-agent-room-package.v1",
@@ -1498,25 +1518,44 @@ function mlsDevRoomPackageId(identity, room) {
1498
1518
  return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-4${hex.slice(13, 16)}-8${hex.slice(17, 20)}-${hex.slice(20, 32)}`;
1499
1519
  }
1500
1520
  __name(mlsDevRoomPackageId, "mlsDevRoomPackageId");
1501
- async function runMlsDevSetup(endpoint, action, room, provision) {
1521
+ async function assertMlsVaultInHome(endpoint, provisioned) {
1522
+ const { status, binding } = await endpoint.mlsDevDevice.read();
1523
+ if (status === "absent" || status === "revoked" || provisioned({ ...binding, revision: 0 })) return;
1524
+ throw new Error("This agent's MLS device has no vault in this home. A device set up with an operator's configuration keeps needing it: run the agent with that --mls-dev-config <path> again (in createRunner, mls: await loadMlsDevConfiguration(path, { environmentId })). A vault that was lost cannot be recovered.");
1525
+ }
1526
+ __name(assertMlsVaultInHome, "assertMlsVaultInHome");
1527
+ async function runMlsDevSetup(endpoint, action, room, provision, provisioned) {
1502
1528
  const identity = await endpoint.ready();
1503
- if (!isMlsEnvironmentId(identity.environmentId) || !endpoint.mlsDevDevice || !endpoint.mlsDevKeyPackages || !endpoint.mlsDevGroups) throw new Error("MLS_DEV_SETUP_UNAVAILABLE");
1529
+ const { environmentId } = identity, devices = endpoint.mlsDevDevice;
1530
+ if (!isMlsEnvironmentId(environmentId) || !devices || !endpoint.mlsDevKeyPackages || !endpoint.mlsDevGroups) throw new Error("MLS_DEV_SETUP_UNAVAILABLE");
1504
1531
  if ((action === "publish" || action === "refresh") !== !!room) throw new Error("MLS_DEV_SETUP_ARGUMENTS_INVALID");
1505
- if (action === "provision") {
1532
+ if (provisioned && action !== "status") await assertMlsVaultInHome(endpoint, provisioned);
1533
+ const enroll = /* @__PURE__ */ __name(async () => {
1534
+ await devices.enroll();
1535
+ if ((await devices.read()).status !== "credential_committed") await devices.renew();
1536
+ }, "enroll");
1537
+ const provisionDevice = /* @__PURE__ */ __name(async (unlessProvisioned) => {
1506
1538
  if (!provision || identity.ownershipVersion === void 0) throw new Error("MLS_DEV_SETUP_UNAVAILABLE");
1507
- const device2 = await endpoint.mlsDevDevice.read();
1539
+ const device2 = await devices.read();
1508
1540
  if (device2.registration || device2.status !== "absent") throw new Error("MLS_DEV_SETUP_ALREADY_PROVISIONED");
1509
- await provision({
1541
+ const context = {
1510
1542
  version: 1,
1511
- environmentId: identity.environmentId,
1543
+ environmentId,
1512
1544
  installationId: identity.installationId,
1513
1545
  principal: { type: "ai_agent", id: await endpoint.mlsPrincipalId(), ownershipVersion: identity.ownershipVersion },
1514
1546
  revision: 0
1515
- });
1516
- } else if (action === "enroll") {
1517
- await endpoint.mlsDevDevice.enroll();
1518
- if ((await endpoint.mlsDevDevice.read()).status !== "credential_committed") await endpoint.mlsDevDevice.renew();
1519
- } else if (action === "renew") await endpoint.mlsDevDevice.renew();
1547
+ };
1548
+ if (!unlessProvisioned || !provisioned?.(context)) await provision(context);
1549
+ }, "provisionDevice");
1550
+ if (action === "setup") {
1551
+ const { status } = await devices.read();
1552
+ if (status === "revoked") throw new Error("MLS_DEV_SETUP_REVOKED");
1553
+ if (status === "absent") await provisionDevice(true);
1554
+ if (status === "registered") await devices.ensureReady();
1555
+ else if (status !== "credential_committed") await enroll();
1556
+ } else if (action === "provision") await provisionDevice(false);
1557
+ else if (action === "enroll") await enroll();
1558
+ else if (action === "renew") await devices.renew();
1520
1559
  else if (action === "publish") {
1521
1560
  const keyPackageId = mlsDevRoomPackageId(identity, room);
1522
1561
  await endpoint.mlsDevKeyPackages.prepare({ keyPackageId });
@@ -1525,7 +1564,7 @@ async function runMlsDevSetup(endpoint, action, room, provision) {
1525
1564
  const group = await endpoint.mlsDevGroups.refresh(room);
1526
1565
  if (group?.status !== "active" || await endpoint.mlsDevGroups.readRecoveryFailure(room)) throw new Error("MLS_DEV_SETUP_ROOM_NOT_READY");
1527
1566
  }
1528
- const device = await endpoint.mlsDevDevice.read();
1567
+ const device = await devices.read();
1529
1568
  return {
1530
1569
  version: 1,
1531
1570
  action,
@@ -1541,12 +1580,244 @@ async function runMlsDevSetup(endpoint, action, room, provision) {
1541
1580
  }
1542
1581
  __name(runMlsDevSetup, "runMlsDevSetup");
1543
1582
 
1583
+ // src/runner/mls-bundled.ts
1584
+ import { createHash as createHash5 } from "node:crypto";
1585
+ import { existsSync, readFileSync as readFileSync2 } from "node:fs";
1586
+ import { dirname as dirname4, join as join2 } from "node:path";
1587
+ import { fileURLToPath } from "node:url";
1588
+
1589
+ // mls-custody.json
1590
+ var mls_custody_default = {
1591
+ schemaVersion: 1,
1592
+ release: "mls-custody-cadb937436ce",
1593
+ protocolVersion: 1,
1594
+ rustVersion: "rustc 1.91.0 (f8297e351 2025-10-28)",
1595
+ sourceHashes: {
1596
+ "mls/native/Cargo.toml": "f93b48329951d5a11ef6d5830ff7c375cb847543bdb1c98a6fd8a8d02026db89",
1597
+ "mls/native/Cargo.lock": "9c2e9a3bb0f79c14540942c091124cba4aa262ee14cef2cdd5490653cd4656f3",
1598
+ "mls/native/src/lib.rs": "8e586bfaedc5fe376febbda78c32f8383aaa2ac92868000dd4c84ca083731571",
1599
+ "mls/native/src/bin/canon-mls-custody.rs": "3ab54102bfe315e425b6c3cf0aa7feb66e44d047e135336b0291305507062459",
1600
+ "mls/native/src/portable/mod.rs": "3005b60f7777a841347421ad96eb631b72063e85cebe96afd1c11fef095b0e34",
1601
+ "mls/native/src/portable/json.rs": "d4e68556a051ffe9008686036597b1fd8239ae66fad67bd848c3f0e16bf8ccaf",
1602
+ "mls/native/src/portable/vault.rs": "14b9ff1e10ec4058e39a46511a489c7e82a9836e4175a768ac5d4b69c1dba8e5",
1603
+ "mls/native/src/portable/tests.rs": "7eadba2a49ed044470bb567d9b93fba44b64dc117b1da50a22187cff3dccb64e",
1604
+ "mls/native/portable/build.mjs": "84c422a8a8aa6a7ec112a358bdeef080201aa4ea9a91f6d5726b05f46fa2a10c",
1605
+ "mls/core/open.rs": "8cdc03882f3b84dca72ab06afd437f162de01a0fe21327b019f17867dd59f5d0",
1606
+ "mls/core/protocol.rs": "7f3e021d6e4f1cc130b5dd3c98e0b0fa23edf813c72802c3fa3f8fb9e431c947",
1607
+ "mls/core/fault_storage.rs": "fdd6672a21f6ffee657142755637824541a5090b4f8a5fb4902f482556fb2793",
1608
+ "mls/core/mls_environment.rs": "390d11ee82ef8b108a7fffc7bb193ac7f3e470aa14e9643d029007aaaa29c4e4",
1609
+ "mls/core/dev_identity.rs": "97df6bdb9daab66f0e3d4ca3b49bf9954fa06ac032c927d9ae6c83509b0166bd",
1610
+ "mls/core/dev_key_package.rs": "3e7011fe8ab3044d80d84d04944068dbcf683141fcaba8a7a00bd2cfd103de72",
1611
+ "mls/core/dev_application.rs": "fafe245ace38e384da265bab2c12fef6b77c86b2c20477d00563ab891a54f94b",
1612
+ "mls/core/dev_group.rs": "1d519a5d15394bbc163aef00e0ddacb9bb87631beaf8a13e52c76d628749152b",
1613
+ "packages/test-fixtures/src/mlsDevEnrollmentProofV1.json": "50d7896e0a3a25b420412e84484e64f1e49480a3f298ee2d125f2f9188382810",
1614
+ "packages/test-fixtures/src/mlsDevCredentialV1.json": "24cd23af794b8a6e35caf050995020c6584b806de060969db4536f44b1188780",
1615
+ "packages/test-fixtures/src/mlsDevCredentialReconciliationV1.json": "bd4bbb3a2e5dc7921867a2e5936d1da77d536740b4360012c1f741f9caed2826",
1616
+ "packages/test-fixtures/src/mlsDevGroupV1.json": "10b7c0da00cfd3ecfb3c9e7f0118bc574be613cc82d97268766ba271c464ca9d",
1617
+ "packages/test-fixtures/src/mlsDevApplicationV1.json": "77c05e728acb7793a70c03a6857bc8b0e522e586b117241432460eec05ae3673",
1618
+ "packages/test-fixtures/src/mlsDevInteractionV1.json": "045ee34e1b5e126b67da94705b3f03a1d265774d7889ec3f4f272dd06ecef676"
1619
+ },
1620
+ platforms: {
1621
+ "darwin-arm64": {
1622
+ target: "aarch64-apple-darwin",
1623
+ sha256: "918ae350f1d7bd339c4eee548f6668943217f5fa66022bb93f41a02992dd296c",
1624
+ bytes: 2932976,
1625
+ unitTestsPassed: 95,
1626
+ commit: "cdc6ce8fd9d7a47bc5c796d19a72828e4cc9db06",
1627
+ builder: {
1628
+ macos: "26.3.1",
1629
+ sdk: "26.4",
1630
+ linker: "@(#)PROGRAM:ld PROJECT:ld-1266.8"
1631
+ }
1632
+ },
1633
+ "linux-x64": {
1634
+ target: "x86_64-unknown-linux-gnu",
1635
+ sha256: "1c5a12d0213c53bdf59b5f36a7390a4b4c17f8a53c95e760ad8cb4905297d256",
1636
+ bytes: 3389816,
1637
+ unitTestsPassed: 95,
1638
+ commit: "cdc6ce8fd9d7a47bc5c796d19a72828e4cc9db06",
1639
+ builder: {
1640
+ image: "quay.io/pypa/manylinux_2_28_x86_64@sha256:e0adf18521ad35192df733a6c62d35df7801957d24e0470238aacecfab2188a3",
1641
+ dockerfileSha256: "fe8d4895b8bd304954441fd27c6273c8c00baa10a1ad912d3c3851b193b8ad75"
1642
+ }
1643
+ },
1644
+ "linux-arm64": {
1645
+ target: "aarch64-unknown-linux-gnu",
1646
+ sha256: "371c5754cf0c437f561e8c49b3ea654c037311b951e11b1c5a29fb6b91656619",
1647
+ bytes: 3234232,
1648
+ unitTestsPassed: 95,
1649
+ commit: "cdc6ce8fd9d7a47bc5c796d19a72828e4cc9db06",
1650
+ builder: {
1651
+ image: "quay.io/pypa/manylinux_2_28_aarch64@sha256:a3e02308c822fb6ce0d12248b0c941817ba5e1a1bc9e7bb5929f29c50eaa40a9",
1652
+ dockerfileSha256: "fe8d4895b8bd304954441fd27c6273c8c00baa10a1ad912d3c3851b193b8ad75"
1653
+ }
1654
+ }
1655
+ }
1656
+ };
1657
+
1658
+ // src/runner/mls-bundled.ts
1659
+ var MLS_ENVIRONMENT_PINS = {
1660
+ "canon-prod-v1": {
1661
+ issuerKeys: [{
1662
+ keyVersion: "projects/canonmail-prod/locations/europe-west1/keyRings/canon-mls/cryptoKeys/device-issuer/cryptoKeyVersions/1",
1663
+ signaturePublicKey: "AYLEqY7gKOhQvRALb-31lz82XinehW2_gl0jKC-G8Pk"
1664
+ }],
1665
+ receiptKeys: [{
1666
+ keyVersion: "projects/canonmail-prod/locations/europe-west1/keyRings/canon-mls/cryptoKeys/feed-receipt/cryptoKeyVersions/1",
1667
+ signaturePublicKey: "sRi4mvMbFTL-LKzXilIJedefSqtqrF8Q-deM2AUSt40"
1668
+ }]
1669
+ },
1670
+ "canon-dev-v1": {
1671
+ // Dev signs with version 2 and still trusts version 1 until 2026-11-07.
1672
+ issuerKeys: [
1673
+ {
1674
+ keyVersion: "projects/canonmail-dev/locations/europe-west1/keyRings/canon-mls/cryptoKeys/device-issuer/cryptoKeyVersions/1",
1675
+ signaturePublicKey: "Hm1ULJGsF9MIQbHYIWmpkK3j2dPcFzeJ71xbYVsPZNs"
1676
+ },
1677
+ {
1678
+ keyVersion: "projects/canonmail-dev/locations/europe-west1/keyRings/canon-mls/cryptoKeys/device-issuer/cryptoKeyVersions/2",
1679
+ signaturePublicKey: "G2fwJdPQXP6Ar68LJveXQ_WAOtdtcsiyKdeEVb-9Gcw"
1680
+ }
1681
+ ],
1682
+ receiptKeys: [{
1683
+ keyVersion: "projects/canonmail-dev/locations/europe-west1/keyRings/canon-mls/cryptoKeys/feed-receipt/cryptoKeyVersions/1",
1684
+ signaturePublicKey: "LSRMjRHH73Q4ExPFSTaU_KTvr8wQ4Cp9vtaBBrVZvig"
1685
+ }]
1686
+ }
1687
+ };
1688
+ function packageRoot() {
1689
+ for (let directory = dirname4(fileURLToPath(import.meta.url)); ; directory = dirname4(directory)) {
1690
+ const manifest = join2(directory, "package.json");
1691
+ if (existsSync(manifest) && JSON.parse(readFileSync2(manifest, "utf8")).name === "@canonmsg/agent-sdk") return directory;
1692
+ if (dirname4(directory) === directory) return void 0;
1693
+ }
1694
+ }
1695
+ __name(packageRoot, "packageRoot");
1696
+ function bundledMlsCustodyHelper() {
1697
+ const platform = `${process.platform}-${process.arch}`;
1698
+ const pin = mls_custody_default.platforms[platform];
1699
+ const root = pin && packageRoot();
1700
+ const path = root && join2(root, "dist", "mls", platform, "canon-mls-custody");
1701
+ if (!pin || !path || !existsSync(path)) return void 0;
1702
+ if (createHash5("sha256").update(readFileSync2(path)).digest("hex") !== pin.sha256) {
1703
+ throw new Error(`${path} is not the custody helper this package pins; reinstall @canonmsg/agent-sdk.`);
1704
+ }
1705
+ return { path, sha256: pin.sha256 };
1706
+ }
1707
+ __name(bundledMlsCustodyHelper, "bundledMlsCustodyHelper");
1708
+
1709
+ // src/runner/mls-managed.ts
1710
+ import { createHash as createHash6 } from "node:crypto";
1711
+ import { chmodSync, copyFileSync, existsSync as existsSync2, mkdirSync as mkdirSync3, readFileSync as readFileSync3, renameSync as renameSync2 } from "node:fs";
1712
+ import { dirname as dirname5, join as join3 } from "node:path";
1713
+ var sha256 = /* @__PURE__ */ __name((bytes) => createHash6("sha256").update(bytes).digest("hex"), "sha256");
1714
+ function defaultMlsDevice(home, environmentId) {
1715
+ if (!isMlsEnvironmentId(environmentId)) return void 0;
1716
+ const helper = bundledMlsCustodyHelper();
1717
+ return helper ? managedMlsDevice({ home, environmentId, helper }) : void 0;
1718
+ }
1719
+ __name(defaultMlsDevice, "defaultMlsDevice");
1720
+ function managedMlsDevice(options) {
1721
+ const { environmentId } = options;
1722
+ const helper = options.helper ?? bundledMlsCustodyHelper();
1723
+ if (!helper) {
1724
+ throw new Error(`@canonmsg/agent-sdk ships no MLS custody helper for ${process.platform}-${process.arch}: build one from mls/native in the Canon repository and give the runner --mls-dev-config <path> (docs/design/mls-dev-agent-host.md).`);
1725
+ }
1726
+ const pins = options.pins ?? MLS_ENVIRONMENT_PINS[environmentId];
1727
+ const configuration = parseMlsDevClientConfiguration({
1728
+ version: 1,
1729
+ environmentId,
1730
+ enabled: true,
1731
+ issuerKeys: pins.issuerKeys,
1732
+ receiptKeys: pins.receiptKeys
1733
+ });
1734
+ const root = join3(options.home, "mls", environmentId);
1735
+ const directory = /* @__PURE__ */ __name((context) => join3(root, sha256(Buffer.from(JSON.stringify([
1736
+ context.installationId,
1737
+ context.principal.type,
1738
+ context.principal.id,
1739
+ "ownershipVersion" in context.principal ? context.principal.ownershipVersion : null
1740
+ ]))).slice(0, 32)), "directory");
1741
+ const vault = /* @__PURE__ */ __name((context) => join3(directory(context), "vault", "vault.bin"), "vault");
1742
+ let executable;
1743
+ function install() {
1744
+ const target = join3(root, "bin", helper.sha256, "canon-mls-custody");
1745
+ if (existsSync2(target) && sha256(readFileSync3(target)) === helper.sha256) return target;
1746
+ mkdirSync3(dirname5(target), { recursive: true, mode: 448 });
1747
+ const temporary = `${target}.${process.pid}.tmp`;
1748
+ copyFileSync(helper.path, temporary);
1749
+ chmodSync(temporary, 448);
1750
+ if (sha256(readFileSync3(temporary)) !== helper.sha256) throw new Error(`${helper.path} is not the pinned custody helper`);
1751
+ renameSync2(temporary, target);
1752
+ return target;
1753
+ }
1754
+ __name(install, "install");
1755
+ const devices = /* @__PURE__ */ new Map();
1756
+ function device(context, provisioning) {
1757
+ const key = directory(context);
1758
+ let found = devices.get(key);
1759
+ if (!found) {
1760
+ found = (async () => {
1761
+ if (!provisioning && !existsSync2(vault(context))) throw new NodeMlsDevCustodyError();
1762
+ mkdirSync3(dirname5(vault(context)), { recursive: true, mode: 448 });
1763
+ const unlock = join3(key, "unlock");
1764
+ if (provisioning) privateKey(unlock, 32);
1765
+ const text3 = `${JSON.stringify({
1766
+ version: 1,
1767
+ environmentId,
1768
+ context: {
1769
+ version: 1,
1770
+ environmentId: context.environmentId,
1771
+ principal: context.principal,
1772
+ installationId: context.installationId
1773
+ },
1774
+ vaultPath: vault(context),
1775
+ issuerKeys: configuration.issuerKeys,
1776
+ receiptKeys: configuration.receiptKeys
1777
+ }, null, 2)}
1778
+ `;
1779
+ const file = join3(key, "native.json");
1780
+ if (!existsSync2(file) || readFileSync3(file, "utf8") !== text3) writeDurable(file, text3);
1781
+ return createPortableNodeMlsDevCustody({
1782
+ executablePath: executable ??= install(),
1783
+ executableSha256: helper.sha256,
1784
+ configurationPath: file,
1785
+ configurationSha256: sha256(Buffer.from(text3)),
1786
+ secretSource: { kind: "managed-file", path: unlock },
1787
+ ...options.timeoutMs === void 0 ? {} : { timeoutMs: options.timeoutMs }
1788
+ });
1789
+ })();
1790
+ devices.set(key, found);
1791
+ found.catch(() => {
1792
+ if (devices.get(key) === found) devices.delete(key);
1793
+ });
1794
+ }
1795
+ return found;
1796
+ }
1797
+ __name(device, "device");
1798
+ const custody2 = {
1799
+ prepareProtected: /* @__PURE__ */ __name((context, ...rest) => device(context, false).then((open4) => open4.prepareProtected(context, ...rest)), "prepareProtected"),
1800
+ prepareProtectedKeyPackage: /* @__PURE__ */ __name((context, ...rest) => device(context, false).then((open4) => open4.prepareProtectedKeyPackage(context, ...rest)), "prepareProtectedKeyPackage"),
1801
+ prepareProtectedGroup: /* @__PURE__ */ __name((identity, ...rest) => device(identity, false).then((open4) => open4.prepareProtectedGroup(identity, ...rest)), "prepareProtectedGroup")
1802
+ };
1803
+ return {
1804
+ configuration,
1805
+ custody: custody2,
1806
+ provision: /* @__PURE__ */ __name((context) => device(context, true).then((open4) => open4.provision(context)), "provision"),
1807
+ provisioned: /* @__PURE__ */ __name((context) => existsSync2(vault(context)), "provisioned"),
1808
+ async close() {
1809
+ await Promise.allSettled([...devices.values()].map((open4) => open4.then((custody3) => custody3.close())));
1810
+ }
1811
+ };
1812
+ }
1813
+ __name(managedMlsDevice, "managedMlsDevice");
1814
+
1544
1815
  // src/runner/relay.ts
1545
1816
  import { createHmac as createHmac2, timingSafeEqual } from "node:crypto";
1546
- import { closeSync as closeSync3, constants as constants5, fsyncSync as fsyncSync2, mkdirSync as mkdirSync3, openSync as openSync3, renameSync as renameSync2, writeSync as writeSync2 } from "node:fs";
1817
+ import { closeSync as closeSync3, constants as constants5, fsyncSync as fsyncSync2, mkdirSync as mkdirSync4, openSync as openSync3, renameSync as renameSync3, writeSync as writeSync2 } from "node:fs";
1547
1818
  import { lstat as lstat2, open as open3, realpath as realpath4 } from "node:fs/promises";
1548
1819
  import { createServer } from "node:http";
1549
- import { isAbsolute as isAbsolute2, join as join2, resolve as resolve2 } from "node:path";
1820
+ import { isAbsolute as isAbsolute2, join as join4, resolve as resolve2 } from "node:path";
1550
1821
  var TOLERANCE_S = 300;
1551
1822
  function relaySecretKey(secret) {
1552
1823
  if (typeof secret !== "string" || !/^whsec_[A-Za-z0-9+/]+={0,2}$/.test(secret)) throw new Error("The relay channel secret must be a whsec_ value");
@@ -1606,6 +1877,8 @@ async function startRelayChannel(options) {
1606
1877
  return served.runner.relayRead(request.args.conversationId, { limit: request.args.limit, before: request.args.before, deadline });
1607
1878
  case "send":
1608
1879
  return served.runner.relaySend(request.args.conversationId, request.args.messageId, request.args.text, { deadline });
1880
+ case "start":
1881
+ return served.runner.relayStart(request.args.targetUserId, request.args.messageId, request.args.text, request.args.selection, { deadline });
1609
1882
  }
1610
1883
  }
1611
1884
  __name(dispatch, "dispatch");
@@ -1715,8 +1988,8 @@ async function loadRelayConfiguration(path, input) {
1715
1988
  return { environmentId: value.environmentId, agentId: value.agentId, listen: { host: listen.host, port: listen.port }, secret };
1716
1989
  }
1717
1990
  __name(loadRelayConfiguration, "loadRelayConfiguration");
1718
- var relayDirectory = /* @__PURE__ */ __name((home) => join2(home, "relay"), "relayDirectory");
1719
- var credentialFile = /* @__PURE__ */ __name((home) => join2(relayDirectory(home), "credential.json"), "credentialFile");
1991
+ var relayDirectory = /* @__PURE__ */ __name((home) => join4(home, "relay"), "relayDirectory");
1992
+ var credentialFile = /* @__PURE__ */ __name((home) => join4(relayDirectory(home), "credential.json"), "credentialFile");
1720
1993
  async function readRelayCredential(home, relay) {
1721
1994
  const path = credentialFile(home);
1722
1995
  if (!await lstat2(path).then(() => true, () => false)) return void 0;
@@ -1742,7 +2015,7 @@ function writeRelayCredential(home, relay, delivery) {
1742
2015
  generation: delivery.generation,
1743
2016
  credential: delivery.credential
1744
2017
  };
1745
- mkdirSync3(relayDirectory(home), { recursive: true, mode: 448 });
2018
+ mkdirSync4(relayDirectory(home), { recursive: true, mode: 448 });
1746
2019
  const file = credentialFile(home);
1747
2020
  const temporary = `${file}.${process.pid}.tmp`;
1748
2021
  const fd = openSync3(temporary, "w", 384);
@@ -1752,23 +2025,23 @@ function writeRelayCredential(home, relay, delivery) {
1752
2025
  } finally {
1753
2026
  closeSync3(fd);
1754
2027
  }
1755
- renameSync2(temporary, file);
2028
+ renameSync3(temporary, file);
1756
2029
  return value;
1757
2030
  }
1758
2031
  __name(writeRelayCredential, "writeRelayCredential");
1759
2032
  function relayRunnerHome(home, installationId) {
1760
2033
  if (!CONNECTOR_RELAY_INSTALLATION_PATTERN.test(installationId)) throw new Error("Invalid relay installation");
1761
- const directory = join2(relayDirectory(home), installationId.slice(CONNECTOR_RELAY_INSTALLATION_PREFIX.length));
1762
- mkdirSync3(directory, { recursive: true, mode: 448 });
2034
+ const directory = join4(relayDirectory(home), installationId.slice(CONNECTOR_RELAY_INSTALLATION_PREFIX.length));
2035
+ mkdirSync4(directory, { recursive: true, mode: 448 });
1763
2036
  return directory;
1764
2037
  }
1765
2038
  __name(relayRunnerHome, "relayRunnerHome");
1766
2039
 
1767
2040
  // src/runner/runner.ts
1768
- import { createHash as createHash6 } from "node:crypto";
1769
- import { readFileSync as readFileSync3, rmSync as rmSync3 } from "node:fs";
2041
+ import { createHash as createHash8 } from "node:crypto";
2042
+ import { readFileSync as readFileSync5, rmSync as rmSync3 } from "node:fs";
1770
2043
  import { realpath as realpath5, stat as stat2 } from "node:fs/promises";
1771
- import { basename as basename3, isAbsolute as isAbsolute3, join as join4, sep } from "node:path";
2044
+ import { basename as basename3, isAbsolute as isAbsolute3, join as join6, sep } from "node:path";
1772
2045
 
1773
2046
  // ../agent-tools/src/communication-tool.ts
1774
2047
  var CANON_COMMUNICATE_TOOL_NAME = "communicate";
@@ -2262,10 +2535,10 @@ function createLivePublisher(options) {
2262
2535
  __name(createLivePublisher, "createLivePublisher");
2263
2536
 
2264
2537
  // src/runner/mls-ask.ts
2265
- import { createHash as createHash5 } from "node:crypto";
2266
- import { readdirSync as readdirSync2, readFileSync as readFileSync2, rmSync as rmSync2 } from "node:fs";
2267
- import { join as join3 } from "node:path";
2268
- var sha256 = /* @__PURE__ */ __name((value) => createHash5("sha256").update(value).digest("hex"), "sha256");
2538
+ import { createHash as createHash7 } from "node:crypto";
2539
+ import { readdirSync as readdirSync2, readFileSync as readFileSync4, rmSync as rmSync2 } from "node:fs";
2540
+ import { join as join5 } from "node:path";
2541
+ var sha2562 = /* @__PURE__ */ __name((value) => createHash7("sha256").update(value).digest("hex"), "sha256");
2269
2542
  var pause = /* @__PURE__ */ __name((ms) => new Promise((resolve3) => {
2270
2543
  setTimeout(resolve3, ms).unref?.();
2271
2544
  }), "pause");
@@ -2319,10 +2592,10 @@ function createMlsAsks(options, approvalScope2) {
2319
2592
  });
2320
2593
  }
2321
2594
  });
2322
- const file = /* @__PURE__ */ __name((conversationId, requestId) => join3(options.directory, `${sha256(JSON.stringify([conversationId, requestId])).slice(0, 32)}.json`), "file");
2595
+ const file = /* @__PURE__ */ __name((conversationId, requestId) => join5(options.directory, `${sha2562(JSON.stringify([conversationId, requestId])).slice(0, 32)}.json`), "file");
2323
2596
  const save = /* @__PURE__ */ __name((journal) => writeDurable(file(journal.conversationId, journal.requestId), JSON.stringify(journal)), "save");
2324
2597
  const forget = /* @__PURE__ */ __name((journal) => rmSync2(file(journal.conversationId, journal.requestId), { force: true }), "forget");
2325
- const operationOf = /* @__PURE__ */ __name((conversationId, messageId) => `runner-mls-ask:${sha256(JSON.stringify([conversationId, messageId])).slice(0, 48)}`, "operationOf");
2598
+ const operationOf = /* @__PURE__ */ __name((conversationId, messageId) => `runner-mls-ask:${sha2562(JSON.stringify([conversationId, messageId])).slice(0, 48)}`, "operationOf");
2326
2599
  const withdrawalOf = /* @__PURE__ */ __name((journal) => operationOf(journal.conversationId, `${journal.requestId}:withdraw`), "withdrawalOf");
2327
2600
  const audienceChanged = /* @__PURE__ */ __name((error) => codeOf(error) === "MLS_DEV_AUDIENCE_CHANGED", "audienceChanged");
2328
2601
  const sameHead = /* @__PURE__ */ __name((a, b) => JSON.stringify(a.head) === JSON.stringify(b.head), "sameHead");
@@ -2663,11 +2936,11 @@ function createMlsAsks(options, approvalScope2) {
2663
2936
  for (const name of names) {
2664
2937
  let journal;
2665
2938
  try {
2666
- journal = JSON.parse(readFileSync2(join3(options.directory, name), "utf8"));
2939
+ journal = JSON.parse(readFileSync4(join5(options.directory, name), "utf8"));
2667
2940
  if (journal.version !== 1 || typeof journal.conversationId !== "string" || typeof journal.requestId !== "string" || !Array.isArray(journal.sends) || journal.sends.some((sent) => typeof sent?.operationId !== "string" || typeof sent.messageId !== "string")) throw new Error();
2668
2941
  for (const sent of journal.sends) sent.audience = snapshotMlsAudience(sent.audience);
2669
2942
  } catch {
2670
- rmSync2(join3(options.directory, name), { force: true });
2943
+ rmSync2(join5(options.directory, name), { force: true });
2671
2944
  continue;
2672
2945
  }
2673
2946
  try {
@@ -2825,7 +3098,7 @@ function answerText(answer) {
2825
3098
  return answer.text?.trim() ? answer.text : "They answered with an empty message.";
2826
3099
  }
2827
3100
  __name(answerText, "answerText");
2828
- var sha2562 = /* @__PURE__ */ __name((value) => createHash6("sha256").update(value).digest("hex"), "sha256");
3101
+ var sha2563 = /* @__PURE__ */ __name((value) => createHash8("sha256").update(value).digest("hex"), "sha256");
2829
3102
  var toolText = /* @__PURE__ */ __name((text3, isError = false) => ({ content: [{ type: "text", text: text3 }], ...isError ? { isError } : {} }), "toolText");
2830
3103
  var turnEnded2 = /* @__PURE__ */ __name(() => new DOMException("This turn has ended", "AbortError"), "turnEnded");
2831
3104
  function within(promise, ms) {
@@ -2841,9 +3114,10 @@ var pause2 = /* @__PURE__ */ __name((ms) => new Promise((resolve3) => {
2841
3114
  }), "pause");
2842
3115
  var audienceOf = /* @__PURE__ */ __name((room) => room?.memberIds ? { memberIds: [...room.memberIds], ...typeof room.membershipRevision === "number" ? { membershipRevision: room.membershipRevision } : {} } : null, "audienceOf");
2843
3116
  var inert = /* @__PURE__ */ __name((value) => value.replace(/\s+/g, " ").trim(), "inert");
3117
+ var OWNER_MARK = " [your owner]";
2844
3118
  var TRUSTED_MARK = " [trusted teammate agent]";
2845
3119
  var speaker = /* @__PURE__ */ __name((name) => inert(name).replace(/\[/g, "(").replace(/\]/g, ")"), "speaker");
2846
- var defused = /* @__PURE__ */ __name((text3) => text3.replace(/\[(\s*trusted\s+teammate\s+agent\s*)\]/gi, "($1)").replace(/\[(\s*(?:end\s+of\s+)?owner['\u2019]s\s+(?:private\s+note|errand)\s*)\]/gi, "($1)"), "defused");
3120
+ var defused = /* @__PURE__ */ __name((text3) => text3.replace(/\[(\s*(?:trusted\s+teammate\s+agent|your\s+owner)\s*)\]/gi, "($1)").replace(/\[(\s*(?:end\s+of\s+)?owner['\u2019]s\s+(?:private\s+note|errand)\s*)\]/gi, "($1)"), "defused");
2847
3121
  var NOTE_OPEN = "[owner's private note] Your owner left this for you on the machine you run on. It is not a message in this conversation: no one in it sees it, and it was not posted.";
2848
3122
  var NOTE_CLOSE = "[end of owner's private note]";
2849
3123
  var ERRAND_OPEN = "[owner's errand]";
@@ -2858,8 +3132,8 @@ var errandCommunicate = /* @__PURE__ */ __name((contact) => ({
2858
3132
  properties: { action: { const: "start_conversation" }, principalId: { type: "string", minLength: 1 }, text: { type: "string", minLength: 1 } }
2859
3133
  }] : []] }
2860
3134
  }), "errandCommunicate");
2861
- var promptLine = /* @__PURE__ */ __name((message2, name, media, trusted = false) => [
2862
- `${speaker(name)}${trusted ? TRUSTED_MARK : ""}: ${defused(renderCanonHostInboundContent(message2, media?.files))}`,
3135
+ var promptLine = /* @__PURE__ */ __name((message2, name, media, mark = "") => [
3136
+ `${speaker(name)}${mark}: ${defused(renderCanonHostInboundContent(message2, media?.files))}`,
2863
3137
  ...(media?.failed ?? []).map((label) => `[Attachment "${defused(inert(label))}" could not be downloaded]`)
2864
3138
  ].join("\n"), "promptLine");
2865
3139
  var inputMessage = /* @__PURE__ */ __name((message2, trusted = false) => Object.freeze({
@@ -2896,7 +3170,7 @@ function approvalScope(request) {
2896
3170
  if (request.scopeKey !== void 0 && /^[A-Za-z0-9_:-]{1,64}$/.test(request.scopeKey)) return request.scopeKey;
2897
3171
  const key = request.scopeKey ?? request.title;
2898
3172
  const slug = key.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "").slice(0, 40) || "approval";
2899
- return `${slug}-${sha2562(JSON.stringify([request.scopeKey === void 0 ? "title" : "scope", key])).slice(0, 16)}`;
3173
+ return `${slug}-${sha2563(JSON.stringify([request.scopeKey === void 0 ? "title" : "scope", key])).slice(0, 16)}`;
2900
3174
  }
2901
3175
  __name(approvalScope, "approvalScope");
2902
3176
  var MAX_GROUP_PARTS = 100;
@@ -2970,6 +3244,7 @@ __name(runtimeDescriptor, "runtimeDescriptor");
2970
3244
  async function createRunner(options) {
2971
3245
  const { driver, home } = options;
2972
3246
  await assertPrivateHome(home);
3247
+ const mls = options.mls === false ? void 0 : options.mls ?? (options.endpoint ? void 0 : defaultMlsDevice(home, options.environmentId));
2973
3248
  const log = options.log ?? ((line) => console.error(`[canon-runner] ${line}`));
2974
3249
  const warn = /* @__PURE__ */ __name((what) => (error) => log(`${what}: ${error instanceof Error ? error.message : String(error)}`), "warn");
2975
3250
  const abortGraceMs = options.abortGraceMs ?? 1e4;
@@ -3053,7 +3328,7 @@ async function createRunner(options) {
3053
3328
  const dispose = /* @__PURE__ */ __name((inputs, kind, reason) => Promise.allSettled(inputs.map((input) => input.handle[kind](reason))).then(() => void 0), "dispose");
3054
3329
  const settle = /* @__PURE__ */ __name(async (ids, reason) => (await Promise.allSettled(ids.map((id) => endpoint.setInboundState(id, "settled", reason)))).every(({ status }) => status === "fulfilled"), "settle");
3055
3330
  const queued = /* @__PURE__ */ __name((turn) => turn.inputs.filter((input) => input.handle.phase === "queued"), "queued");
3056
- const turnId = /* @__PURE__ */ __name((roomId, inputs) => `turn-${sha2562(JSON.stringify([roomId, ...inputs.map((input) => [input.id, input.generation])])).slice(0, 32)}`, "turnId");
3331
+ const turnId = /* @__PURE__ */ __name((roomId, inputs) => `turn-${sha2563(JSON.stringify([roomId, ...inputs.map((input) => [input.id, input.generation])])).slice(0, 32)}`, "turnId");
3057
3332
  const livePublisher = /* @__PURE__ */ __name((current, turn, mode) => createLivePublisher({
3058
3333
  client,
3059
3334
  conversationId: current.id,
@@ -3069,7 +3344,7 @@ async function createRunner(options) {
3069
3344
  current.lane = current.lane.then(publish2).catch(() => void 0);
3070
3345
  }, "enqueue")
3071
3346
  }), "livePublisher");
3072
- const stateFile = /* @__PURE__ */ __name((session) => join4(home, "runner", options.environmentId, agentId, "state", driver.name, `${sha2562(JSON.stringify([scope, session]))}.json`), "stateFile");
3347
+ const stateFile = /* @__PURE__ */ __name((session) => join6(home, "runner", options.environmentId, agentId, "state", driver.name, `${sha2563(JSON.stringify([scope, session]))}.json`), "stateFile");
3073
3348
  const resetSession = /* @__PURE__ */ __name(async (session) => {
3074
3349
  rmSync3(stateFile(session), { force: true });
3075
3350
  mlsAsks?.endSession(session);
@@ -3141,6 +3416,7 @@ ${error instanceof Error ? error.message : String(error)}`;
3141
3416
  if (input.mls) target.mls = true;
3142
3417
  target.queue.push(input);
3143
3418
  target.turn?.live.refresh();
3419
+ requeue(target);
3144
3420
  pump();
3145
3421
  }
3146
3422
  __name(offer, "offer");
@@ -3189,6 +3465,7 @@ ${error instanceof Error ? error.message : String(error)}`;
3189
3465
  const verified = {
3190
3466
  id: message2.id,
3191
3467
  senderId: message2.senderId,
3468
+ ...message2.senderName ? { senderName: message2.senderName } : {},
3192
3469
  senderType: fromAgent ? "ai_agent" : "human",
3193
3470
  isOwner: !fromAgent && message2.senderId === ownerId,
3194
3471
  text: message2.text ?? void 0,
@@ -3247,7 +3524,7 @@ ${error instanceof Error ? error.message : String(error)}`;
3247
3524
  const inputs = candidate.queue.splice(0, Math.min(MAX_INPUTS_PER_TURN, other < 0 ? MAX_INPUTS_PER_TURN : other));
3248
3525
  const turn = newTurn(inputs, turnId(candidate.id, inputs), candidate.id);
3249
3526
  candidate.turn = turn;
3250
- (turn.live = livePublisher(candidate, turn, encrypted ? "presence" : "status")).open();
3527
+ (turn.live = driver.capabilities.remote ? SILENT : livePublisher(candidate, turn, encrypted ? "presence" : "status")).open();
3251
3528
  const done = execute(candidate, turn).catch((error) => {
3252
3529
  warn("turn failed")(error);
3253
3530
  return dispose(queued(turn), "release", "turn-failed");
@@ -3277,14 +3554,28 @@ ${error instanceof Error ? error.message : String(error)}`;
3277
3554
  if (turn.inputs[0].mls && !await beforeTurn(current, turn)) return;
3278
3555
  turn.trusted = await confirmTrusted(turn, conversation.memberIds);
3279
3556
  turn.notes = pendingNotes(current.id);
3280
- turn.contexts = await Promise.all(openErrands(current.id).map(async (record) => ({ record, encryptedOrigin: !!record.origin && await endpoint.getConversationContentMode(record.origin).then((mode) => mode === "mls-dev-text", () => false) })));
3281
- turn.live.setMode(turn.mls ? "presence" : driver.capabilities.streaming && resolveTurnVerbosity({ conversationType: kind }) === "verbose" ? "full" : "status");
3557
+ turn.contexts = await Promise.all(openErrands(current.id).map(async (record) => ({ record, encryptedOrigin: !!record.origin && await endpoint.getConversationContentMode(record.origin).then((mode2) => mode2 === "mls-dev-text", () => false) })));
3558
+ const mode = turn.mls ? "presence" : driver.capabilities.streaming && resolveTurnVerbosity({ conversationType: kind }) === "verbose" ? "full" : "status";
3559
+ turn.live.setMode(mode);
3282
3560
  const history = driver.capabilities.sessionMemory === "none" ? await recentHistory(current, turn) : [];
3283
3561
  await materialize(current, turn);
3284
3562
  const bound = bindTools(current, turn);
3285
3563
  try {
3286
3564
  let work;
3287
- for (let live = queued(turn), waited = 0; live.length && !turn.controller.signal.aborted; ) {
3565
+ if (driver.capabilities.remote) {
3566
+ const live = queued(turn);
3567
+ if (live.length && !turn.controller.signal.aborted) {
3568
+ turn.inputs = live;
3569
+ turn.id = turnId(current.id, live);
3570
+ turn.pickUp = () => pickUp(current, turn, mode);
3571
+ try {
3572
+ work = Promise.resolve(driver.runTurn(buildTurn(current.id, turn, kind, history, bound.tools)));
3573
+ } catch (error) {
3574
+ work = Promise.reject(error);
3575
+ }
3576
+ requeue(current);
3577
+ }
3578
+ } else for (let live = queued(turn), waited = 0; live.length && !turn.controller.signal.aborted; ) {
3288
3579
  turn.inputs = live;
3289
3580
  turn.id = turnId(current.id, live);
3290
3581
  let driverTurn = buildTurn(current.id, turn, kind, history, bound.tools);
@@ -3307,15 +3598,17 @@ ${error instanceof Error ? error.message : String(error)}`;
3307
3598
  live = next;
3308
3599
  }
3309
3600
  if (!work) return;
3310
- turn.submitted = true;
3311
- progress(turn);
3312
- if (!turn.mls) void client.markAsRead(current.id).catch(warn("mark read"));
3313
- for (const { payload } of turn.mls ? [] : turn.inputs) {
3314
- if (payload.message.metadata?.inboundDisposition === "queued") {
3315
- void client.updateMessageDisposition(current.id, payload.message.id, "accepted_now").catch(warn("accepted_now"));
3316
- }
3317
- }
3601
+ if (!driver.capabilities.remote) started(current, turn);
3318
3602
  const outcome = await outcomeOf(turn, work);
3603
+ if (!turn.submitted) await turn.claiming?.catch(() => void 0);
3604
+ if (!turn.submitted) {
3605
+ turn.pickUp = void 0;
3606
+ if (turn.reason === "requeue") return void current.queue.unshift(...queued(turn));
3607
+ if (turn.reason) return;
3608
+ const claim = await endpoint.startInbound(queued(turn).map((input) => input.handle), () => void 0);
3609
+ if (claim.status !== "submitted") return await dispose(queued(turn), "release", "claim-deferred");
3610
+ started(current, turn);
3611
+ }
3319
3612
  turn.open = false;
3320
3613
  turn.ended.abort();
3321
3614
  bound.release();
@@ -3328,6 +3621,46 @@ ${error instanceof Error ? error.message : String(error)}`;
3328
3621
  }
3329
3622
  }
3330
3623
  __name(execute, "execute");
3624
+ function started(current, turn) {
3625
+ turn.submitted = true;
3626
+ progress(turn);
3627
+ if (!turn.mls) void client.markAsRead(current.id).catch(warn("mark read"));
3628
+ for (const { payload } of turn.mls ? [] : turn.inputs) {
3629
+ if (payload.message.metadata?.inboundDisposition === "queued") {
3630
+ void client.updateMessageDisposition(current.id, payload.message.id, "accepted_now").catch(warn("accepted_now"));
3631
+ }
3632
+ }
3633
+ }
3634
+ __name(started, "started");
3635
+ async function pickUp(current, turn, mode) {
3636
+ const live = queued(turn);
3637
+ if (live.length !== turn.inputs.length) {
3638
+ abort(turn, "requeue");
3639
+ throw turnEnded2();
3640
+ }
3641
+ const claiming = endpoint.startInbound(live.map((input) => input.handle), () => void 0).then((result) => {
3642
+ if (result.status === "submitted") {
3643
+ turn.pickUp = void 0;
3644
+ started(current, turn);
3645
+ if (!turn.controller.signal.aborted) (turn.live = livePublisher(current, turn, mode)).open();
3646
+ }
3647
+ return result;
3648
+ });
3649
+ turn.claiming = claiming;
3650
+ const claim = await claiming;
3651
+ if (claim.status !== "submitted") {
3652
+ if (claim.status !== "deferred") abort(turn, "requeue");
3653
+ throw new Error("The input could not be claimed now");
3654
+ }
3655
+ }
3656
+ __name(pickUp, "pickUp");
3657
+ function requeue(current) {
3658
+ const turn = current.turn, next = current.queue[0];
3659
+ if (!turn?.pickUp || turn.pickingUp || turn.controller.signal.aborted || !next) return;
3660
+ if (!!next.mls !== !!turn.inputs[0].mls || turn.inputs.length >= MAX_INPUTS_PER_TURN) return;
3661
+ abort(turn, "requeue");
3662
+ }
3663
+ __name(requeue, "requeue");
3331
3664
  async function confirmTrusted(turn, memberIds) {
3332
3665
  if (!trustedAgents.size || driver.capabilities.remote || options.relay) return /* @__PURE__ */ new Set();
3333
3666
  const candidates = turn.inputs.map(({ payload }) => payload.message).filter((message2) => message2.senderType === "ai_agent" && message2.senderId !== agentId && trustedAgents.has(message2.senderId) && (turn.mls ? true : message2.metadata?.[ACTED_FOR_OWNER] === true && message2.metadata.turnSemantics === "turn_complete" && message2.forwarded !== true));
@@ -3399,7 +3732,7 @@ ${error instanceof Error ? error.message : String(error)}`;
3399
3732
  return check;
3400
3733
  }
3401
3734
  __name(reaudience, "reaudience");
3402
- const inboundMedia = /* @__PURE__ */ __name((conversationId) => ({ agentId: "inbound", conversationId, rootDir: join4(home, "runner", options.environmentId, agentId) }), "inboundMedia");
3735
+ const inboundMedia = /* @__PURE__ */ __name((conversationId) => ({ agentId: "inbound", conversationId, rootDir: join6(home, "runner", options.environmentId, agentId) }), "inboundMedia");
3403
3736
  async function materialize(current, turn) {
3404
3737
  if (driver.capabilities.remote) return;
3405
3738
  for (const { payload: { message: message2 } } of turn.inputs) {
@@ -3450,7 +3783,7 @@ ${error instanceof Error ? error.message : String(error)}`;
3450
3783
  ask_question: /* @__PURE__ */ __name(async (args) => {
3451
3784
  const question = questionOf(args);
3452
3785
  if (typeof question === "string") return { result: toolText(question, true) };
3453
- const key = sha2562(JSON.stringify(question));
3786
+ const key = sha2563(JSON.stringify(question));
3454
3787
  const waiting = questions.get(key);
3455
3788
  const entry = waiting?.waiting ? waiting : { id: `canon:ask_question:${asked += 1}`, waiting: true };
3456
3789
  questions.set(key, entry);
@@ -3560,7 +3893,7 @@ ${JSON.stringify(result, null, 2)}`);
3560
3893
  let sending = turn.said.get(messageId);
3561
3894
  if (!sending) {
3562
3895
  turn.said.set(messageId, sending = (async () => {
3563
- const mls = audience ?? (await currentAudience(roomId)).audience;
3896
+ const mls2 = audience ?? (await currentAudience(roomId)).audience;
3564
3897
  const record = {
3565
3898
  version: 1,
3566
3899
  turnId: messageId,
@@ -3571,7 +3904,7 @@ ${JSON.stringify(result, null, 2)}`);
3571
3904
  text: text3,
3572
3905
  options: { messageId },
3573
3906
  notice: false,
3574
- mls
3907
+ mls: mls2
3575
3908
  };
3576
3909
  journal.save(record);
3577
3910
  const sent2 = await publish(record);
@@ -3768,7 +4101,7 @@ ${JSON.stringify(result, null, 2)}`);
3768
4101
  messageId: `${root}-part-${index + 1}`,
3769
4102
  metadata: buildChunkedMessagePartMetadata({ groupId: root, metadata: options2.metadata, index: index + 1, count: bodies.length })
3770
4103
  } }));
3771
- return client.prepareMessageGroup(record.conversationId, root, parts, { signal, ...replaces ? { operationId: sha2562(JSON.stringify([root, ...attachments.map((attachment) => attachment.uploadId)])), replacesOperationId: replaces } : {} }).then((group2) => ({ ...group2, count: parts.length }));
4104
+ return client.prepareMessageGroup(record.conversationId, root, parts, { signal, ...replaces ? { operationId: sha2563(JSON.stringify([root, ...attachments.map((attachment) => attachment.uploadId)])), replacesOperationId: replaces } : {} }).then((group2) => ({ ...group2, count: parts.length }));
3772
4105
  }, "register");
3773
4106
  const original = await register(record.attachments);
3774
4107
  let group = original;
@@ -3807,21 +4140,21 @@ ${JSON.stringify(result, null, 2)}`);
3807
4140
  return await send(record.conversationId, STOPS_SHORT_NOTICE, notice, retire) === "pending" ? "pending" : "refused";
3808
4141
  }
3809
4142
  __name(publish, "publish");
3810
- async function send(conversationId, text3, options2, retire, record, mls = record?.mls) {
4143
+ async function send(conversationId, text3, options2, retire, record, mls2 = record?.mls) {
3811
4144
  const sent = await (async () => {
3812
4145
  for (let attempt = 0; ; attempt += 1) {
3813
4146
  const files = record?.attachments?.length ?? 0;
3814
- const result = await untilHalt((mls ? sendEncryptedItems(mls, record?.items ?? [], retire).then(() => sendEncrypted(mls, options2.messageId, text3, retire)) : files ? sendWithFiles(record, fitGroup(text3, files), options2, retire) : sendMessageWithRetryChunked(client, conversationId, fitGroup(text3, 0), options2, { signal: retire })).then(() => "delivered", (error) => error));
4147
+ const result = await untilHalt((mls2 ? sendEncryptedItems(mls2, record?.items ?? [], retire).then(() => sendEncrypted(mls2, options2.messageId, text3, retire)) : files ? sendWithFiles(record, fitGroup(text3, files), options2, retire) : sendMessageWithRetryChunked(client, conversationId, fitGroup(text3, 0), options2, { signal: retire })).then(() => "delivered", (error) => error));
3815
4148
  if (result === "delivered" || result === "pending") return result;
3816
4149
  if (retire?.aborted || isPendingCanonOperation(result)) return "pending";
3817
- if (mls ? result.retryable === false : !isRetryableCanonDeliveryError(result)) {
4150
+ if (mls2 ? result.retryable === false : !isRetryableCanonDeliveryError(result)) {
3818
4151
  warn("Canon refused a reply")(result);
3819
4152
  return "refused";
3820
4153
  }
3821
4154
  if (await untilHalt(pause2(Math.min(3e4, 1e3 * 2 ** attempt))) === "pending") return "pending";
3822
4155
  }
3823
4156
  })();
3824
- if (mls) forgetGuard(mls, options2.messageId, sent);
4157
+ if (mls2) forgetGuard(mls2, options2.messageId, sent);
3825
4158
  return sent;
3826
4159
  }
3827
4160
  __name(send, "send");
@@ -3891,7 +4224,7 @@ ${JSON.stringify(result, null, 2)}`);
3891
4224
  return { items, refused: refused2 };
3892
4225
  }
3893
4226
  __name(admitItems, "admitItems");
3894
- const encryptedOperation = /* @__PURE__ */ __name((audience, messageId) => `runner-mls:${sha2562(JSON.stringify([audience.conversationId, messageId])).slice(0, 48)}`, "encryptedOperation");
4227
+ const encryptedOperation = /* @__PURE__ */ __name((audience, messageId) => `runner-mls:${sha2563(JSON.stringify([audience.conversationId, messageId])).slice(0, 48)}`, "encryptedOperation");
3895
4228
  function forgetGuard(audience, messageId, sent) {
3896
4229
  if (sent === "pending") return;
3897
4230
  const operationId = encryptedOperation(audience, messageId);
@@ -3919,7 +4252,7 @@ ${JSON.stringify(result, null, 2)}`);
3919
4252
  ...turn.contexts.length ? { errands: Object.freeze(turn.contexts.map(({ record }) => errandFacts(record))) } : {},
3920
4253
  ...turn.errand ? { errand: errandFacts(turn.errand.record) } : {}
3921
4254
  });
3922
- const line = /* @__PURE__ */ __name((message2, trusted = false) => promptLine(message2, message2.senderId === agentId ? "You" : message2.senderName ?? message2.senderId, turn.media.get(message2.id), trusted), "line");
4255
+ const line = /* @__PURE__ */ __name((message2, mark = "") => promptLine(message2, message2.senderId === agentId ? "You" : message2.senderName ?? message2.senderId, turn.media.get(message2.id), mark), "line");
3923
4256
  const open4 = /* @__PURE__ */ __name(() => turn.open && !turn.controller.signal.aborted, "open");
3924
4257
  const live = /* @__PURE__ */ __name(() => open4() && !turn.silenced, "live");
3925
4258
  return {
@@ -3929,7 +4262,7 @@ ${JSON.stringify(result, null, 2)}`);
3929
4262
  ...turn.notes.flatMap((note) => [NOTE_OPEN, defused(note.text), NOTE_CLOSE, ""]),
3930
4263
  ...turn.contexts.flatMap((context) => [...contextPrompt(context, !!turn.mls), ""]),
3931
4264
  ...history.length ? ["Earlier in this conversation (context, not new requests):", ...history.map((message2) => line(message2)), "", "New messages:"] : turn.notes.length || turn.contexts.length ? ["New messages:"] : [],
3932
- ...turn.inputs.map(({ payload: { message: message2 } }) => line(message2, trustedSender(message2)))
4265
+ ...turn.inputs.map(({ payload: { message: message2 } }) => line(message2, message2.isOwner === true ? OWNER_MARK : trustedSender(message2) ? TRUSTED_MARK : ""))
3933
4266
  ].join("\n"),
3934
4267
  input,
3935
4268
  files: turn.inputs.flatMap(({ payload: { message: message2 } }) => turn.media.get(message2.id)?.files.map((file) => file.path) ?? []),
@@ -3955,21 +4288,28 @@ ${JSON.stringify(result, null, 2)}`);
3955
4288
  __name(buildTurn, "buildTurn");
3956
4289
  async function confirmPickup(roomId, turn, open4) {
3957
4290
  if (!open4()) throw turnEnded2();
3958
- const current = await client.getConversation(roomId);
3959
- if (!open4()) throw turnEnded2();
3960
- if (!current?.memberIds?.includes(agentId)) {
3961
- loseMembership(room(roomId));
3962
- throw new Error("The agent is no longer a member of this conversation");
3963
- }
3964
- if (turn.mls && await readAgain(() => reaudience(roomId, turn), Date.now() + CONNECTOR_RELAY_ANSWER_WITHIN_MS) === "changed") {
3965
- if (!turn.controller.signal.aborted) {
3966
- void dispose(queued(turn), "settle", "mls-obsolete-audience");
3967
- abort(turn, "audience");
4291
+ turn.pickingUp = true;
4292
+ try {
4293
+ const current = await client.getConversation(roomId);
4294
+ if (!open4()) throw turnEnded2();
4295
+ if (!current?.memberIds?.includes(agentId)) {
4296
+ loseMembership(room(roomId));
4297
+ throw new Error("The agent is no longer a member of this conversation");
4298
+ }
4299
+ if (turn.mls && await readAgain(() => reaudience(roomId, turn), Date.now() + CONNECTOR_RELAY_ANSWER_WITHIN_MS) === "changed") {
4300
+ if (!turn.controller.signal.aborted) {
4301
+ void dispose(queued(turn), "settle", "mls-obsolete-audience");
4302
+ abort(turn, "audience");
4303
+ }
4304
+ throw new Error("Who can read this encrypted conversation changed");
3968
4305
  }
3969
- throw new Error("Who can read this encrypted conversation changed");
4306
+ if (!open4()) throw turnEnded2();
4307
+ if (turn.pickUp) await turn.pickUp();
4308
+ progress(turn);
4309
+ } finally {
4310
+ turn.pickingUp = false;
4311
+ requeue(room(roomId));
3970
4312
  }
3971
- if (!open4()) throw turnEnded2();
3972
- progress(turn);
3973
4313
  }
3974
4314
  __name(confirmPickup, "confirmPickup");
3975
4315
  function ask(roomId, turn, sender, request) {
@@ -3989,7 +4329,7 @@ ${JSON.stringify(result, null, 2)}`);
3989
4329
  }));
3990
4330
  const secret = request.kind === "text" && request.secret === true;
3991
4331
  const responder = secret || sender.type !== "human" ? ownerId : sender.id;
3992
- const requestId = turn.mls ? keyed().request(roomId, turn.id, request.id) : `ask-${sha2562(JSON.stringify([turn.id, request.id])).slice(0, 32)}`;
4332
+ const requestId = turn.mls ? keyed().request(roomId, turn.id, request.id) : `ask-${sha2563(JSON.stringify([turn.id, request.id])).slice(0, 32)}`;
3993
4333
  const expiresAt = Date.now() + askExpiryMs;
3994
4334
  if (turn.mls) {
3995
4335
  return await mlsAsks.ask({
@@ -4061,7 +4401,7 @@ ${JSON.stringify(result, null, 2)}`);
4061
4401
  const file = stateFile(session);
4062
4402
  let data = {};
4063
4403
  try {
4064
- data = JSON.parse(readFileSync3(file, "utf8"));
4404
+ data = JSON.parse(readFileSync5(file, "utf8"));
4065
4405
  } catch {
4066
4406
  }
4067
4407
  const save = /* @__PURE__ */ __name((next) => {
@@ -4151,11 +4491,11 @@ ${JSON.stringify(result, null, 2)}`);
4151
4491
  const mode = await endpoint.getConversationContentMode(roomId);
4152
4492
  if (mode !== "plaintext") target.mls = true;
4153
4493
  if (mode !== "plaintext" && (!mlsOn || mode !== "mls-dev-text")) refuse(roomId, mlsOn ? "protocol" : "no-device");
4154
- const mls = mode === "mls-dev-text" && mlsOn ? (await currentAudience(roomId)).audience : void 0;
4494
+ const mls2 = mode === "mls-dev-text" && mlsOn ? (await currentAudience(roomId)).audience : void 0;
4155
4495
  const legacy = `${id}:restart`;
4156
- const messageId = mls && !await endpoint.getOperation(encryptedOperation(mls, legacy)) ? keyed().text("restart", roomId, id) : legacy;
4157
- if (mode !== "plaintext" && !mls) await settle(inputs.map(({ event }) => event.id), "interrupted-by-restart");
4158
- else if (await send(roomId, RESTART_NOTICE, outputOptions(id, trigger, "restart", false, messageId), void 0, void 0, mls) !== "pending") {
4496
+ const messageId = mls2 && !await endpoint.getOperation(encryptedOperation(mls2, legacy)) ? keyed().text("restart", roomId, id) : legacy;
4497
+ if (mode !== "plaintext" && !mls2) await settle(inputs.map(({ event }) => event.id), "interrupted-by-restart");
4498
+ else if (await send(roomId, RESTART_NOTICE, outputOptions(id, trigger, "restart", false, messageId), void 0, void 0, mls2) !== "pending") {
4159
4499
  await settle(inputs.map(({ event }) => event.id), "interrupted-by-restart");
4160
4500
  }
4161
4501
  }
@@ -4278,7 +4618,7 @@ ${JSON.stringify(result, null, 2)}`);
4278
4618
  log(`Errand ${id} (tell) kept for the next turn in ${conversationId}.`);
4279
4619
  return true;
4280
4620
  }
4281
- const turnId2 = `turn-${sha2562(JSON.stringify(["canon.runner.errand", id])).slice(0, 32)}`;
4621
+ const turnId2 = `turn-${sha2563(JSON.stringify(["canon.runner.errand", id])).slice(0, 32)}`;
4282
4622
  const record = {
4283
4623
  version: 1,
4284
4624
  turnId: turnId2,
@@ -4347,7 +4687,7 @@ ${JSON.stringify(result, null, 2)}`);
4347
4687
  pump();
4348
4688
  }
4349
4689
  __name(enqueueErrand, "enqueueErrand");
4350
- const errandTurnId = /* @__PURE__ */ __name((id) => `turn-${sha2562(JSON.stringify(["canon.runner.errand-turn", id])).slice(0, 32)}`, "errandTurnId");
4690
+ const errandTurnId = /* @__PURE__ */ __name((id) => `turn-${sha2563(JSON.stringify(["canon.runner.errand-turn", id])).slice(0, 32)}`, "errandTurnId");
4351
4691
  const errandFacts = /* @__PURE__ */ __name((record) => Object.freeze({
4352
4692
  id: record.id,
4353
4693
  why: record.why,
@@ -4683,12 +5023,12 @@ ${JSON.stringify(result, null, 2)}`);
4683
5023
  client = new CanonClient(options.apiKey, connection.apiBaseUrl, {
4684
5024
  environmentId: connection.environmentId,
4685
5025
  streamUrl: connection.streamUrl,
4686
- stateDirectory: join4(home, "endpoint"),
5026
+ stateDirectory: join6(home, "endpoint"),
4687
5027
  ...options.client ? { client: options.client } : {},
4688
5028
  // Below Canon's client floor nothing can work, wherever the refusal surfaces: a query, an operation or the stream.
4689
5029
  ...options.onFatal ? { onClientUpdateRequired: /* @__PURE__ */ __name((error) => options.onFatal(error), "onClientUpdateRequired") } : {},
4690
5030
  ...options.installationId ? { installationId: options.installationId } : {},
4691
- ...options.mls ? { mlsDev: { configuration: options.mls.configuration, custody: options.mls.custody } } : {},
5031
+ ...mls ? { mlsDev: { configuration: mls.configuration, custody: mls.custody } } : {},
4692
5032
  ...options.endpoint ? { endpoint: options.endpoint } : {},
4693
5033
  ...options.fetch ? { fetch: options.fetch } : {}
4694
5034
  });
@@ -4704,7 +5044,17 @@ ${JSON.stringify(result, null, 2)}`);
4704
5044
  if (options.wake !== void 0) await client.configureWake(options.wake);
4705
5045
  mlsOn = !!(endpoint.mlsDevDevice && endpoint.mlsDevGroups);
4706
5046
  if (!mlsOn && (await endpoint.capabilities().catch(() => null))?.mls?.roomMode === "default") {
4707
- throw new Error("Canon encrypts every new conversation here, and this runner has no MLS device: run it with --mls-dev-config <path> (set one up with canon-agent mls).");
5047
+ throw new Error(`Canon encrypts every new conversation here, and this runner has no MLS device${options.mls === void 0 && !options.endpoint ? ` (@canonmsg/agent-sdk ships no custody helper for ${process.platform}-${process.arch})` : ""}: give it an operator's configuration (--mls-dev-config <path>, or mls: await loadMlsDevConfiguration(path, { environmentId })).`);
5048
+ }
5049
+ if (mlsOn && mls?.provisioned) {
5050
+ await assertMlsVaultInHome(endpoint, mls.provisioned);
5051
+ if ((await endpoint.mlsDevDevice.read()).status !== "credential_committed") {
5052
+ log("Setting up this agent's MLS device");
5053
+ const device = await runMlsDevSetup(endpoint, "setup", void 0, mls.provision, mls.provisioned).catch((error) => {
5054
+ throw new Error(`Could not set up this agent's MLS device: ${error instanceof Error ? error.message : String(error)}`);
5055
+ });
5056
+ log(`MLS device ${device.deviceId} is ${device.status}`);
5057
+ }
4708
5058
  }
4709
5059
  if (mlsOn) await endpoint.mlsDevDevice.ensureReady().catch(warn("MLS device not ready; encrypted rooms wait for it"));
4710
5060
  const keepPool = /* @__PURE__ */ __name(() => endpoint.mlsDevDevice.read().then(async (view) => {
@@ -4721,23 +5071,23 @@ ${JSON.stringify(result, null, 2)}`);
4721
5071
  }
4722
5072
  scope = JSON.stringify([identity.environmentId, agentId, identity.ownershipVersion ?? 0, identity.installationId]);
4723
5073
  if (mlsOn) {
4724
- mlsIds = createMlsIds(privateKey(join4(home, "runner", options.environmentId, agentId, "mls-ids", `${sha2562(scope).slice(0, 32)}.key`), MLS_ID_KEY_BYTES));
5074
+ mlsIds = createMlsIds(privateKey(join6(home, "runner", options.environmentId, agentId, "mls-ids", `${sha2563(scope).slice(0, 32)}.key`), MLS_ID_KEY_BYTES));
4725
5075
  mlsAsks = createMlsAsks({
4726
5076
  endpoint,
4727
5077
  agentId,
4728
- directory: join4(home, "runner", options.environmentId, agentId, "asks", sha2562(scope).slice(0, 32)),
5078
+ directory: join6(home, "runner", options.environmentId, agentId, "asks", sha2563(scope).slice(0, 32)),
4729
5079
  assertAudience,
4730
5080
  sessionForDestructive: options.sessionApprovalForDestructive === true,
4731
5081
  log
4732
5082
  }, approvalScope);
4733
5083
  }
4734
- journal = createCompletedOutputJournal(join4(home, "runner", options.environmentId, agentId, "finals"), scope, {
5084
+ journal = createCompletedOutputJournal(join6(home, "runner", options.environmentId, agentId, "finals"), scope, {
4735
5085
  label: "agent runner",
4736
5086
  validate: /* @__PURE__ */ __name((record) => typeof record.text === "string" && typeof record.options?.messageId === "string" && Array.isArray(record.inputs) && typeof record.notice === "boolean" && (record.attachments === void 0 || Array.isArray(record.attachments)) && (record.flows === void 0 || Array.isArray(record.flows) && record.flows.length === record.attachments?.length) && (record.mls === void 0 || !!record.mls && typeof record.mls === "object") && (record.errand === void 0 || typeof record.errand === "string"), "validate")
4737
5087
  });
4738
5088
  if (!options.relay && !driver.capabilities.remote) {
4739
5089
  errands = errandJournal(errandsDirectory(home, options.environmentId, agentId));
4740
- errandScope = sha2562(scope).slice(0, 32);
5090
+ errandScope = sha2563(scope).slice(0, 32);
4741
5091
  const pending = errands.list().filter((record) => record.kind === "tell" && record.status === "pending").sort((a, b) => (a.createdAt ?? "").localeCompare(b.createdAt ?? "") || a.id.localeCompare(b.id));
4742
5092
  for (const note of pending) {
4743
5093
  if (note.scope === errandScope) notes.set(note.conversationId, [...notes.get(note.conversationId) ?? [], note]);
@@ -4759,10 +5109,8 @@ ${JSON.stringify(result, null, 2)}`);
4759
5109
  roots = outbound ? (await Promise.all(outbound.roots.map((root) => realpath5(root).catch(() => "")))).filter(Boolean) : [];
4760
5110
  if (driver.capabilities.tools) tools = await startToolServer();
4761
5111
  await recover();
4762
- if (!options.relay) {
4763
- const publisher = createRuntimeStatePublisher({ agentId, clientType: CLIENT_TYPES[driver.name], hostMode: true, rtdb: initRTDBAuth(client) });
4764
- heartbeat = createRuntimeHeartbeat({ publisher, getRuntime: /* @__PURE__ */ __name(() => ({ runtimeDescriptor: descriptor }), "getRuntime"), onError: warn("runtime heartbeat") });
4765
- }
5112
+ const publisher = createRuntimeStatePublisher({ agentId, clientType: CLIENT_TYPES[driver.name], hostMode: true, rtdb: initRTDBAuth(client) });
5113
+ heartbeat = createRuntimeHeartbeat({ publisher, getRuntime: /* @__PURE__ */ __name(() => ({ runtimeDescriptor: descriptor }), "getRuntime"), onError: warn("runtime heartbeat") });
4766
5114
  inbound = endpoint.acceptInbound({ kind: "message.created", offer, onError: warn("inbox"), ...mlsOn ? { intervalMs: 1e3 } : {} });
4767
5115
  inbound.start();
4768
5116
  let reconnect = false;
@@ -4827,7 +5175,7 @@ ${JSON.stringify(result, null, 2)}`);
4827
5175
  for (const unregister of guards.values()) unregister();
4828
5176
  guards.clear();
4829
5177
  await client?.close();
4830
- await options.mls?.close?.().catch(warn("MLS custody close"));
5178
+ await mls?.close?.().catch(warn("MLS custody close"));
4831
5179
  })().finally(() => lock?.release());
4832
5180
  return stopping;
4833
5181
  },
@@ -4858,7 +5206,7 @@ ${JSON.stringify(result, null, 2)}`);
4858
5206
  throw new ConnectorRelayError(400, "CONNECTOR_RELAY_INVALID", "A send needs a messageId and 1 byte to 32 KiB of text");
4859
5207
  }
4860
5208
  const deadline = options2.deadline ?? Date.now() + CONNECTOR_RELAY_ANSWER_WITHIN_MS;
4861
- const id = `relay-send-${sha2562(JSON.stringify([conversationId, messageId])).slice(0, 32)}`;
5209
+ const id = `relay-send-${sha2563(JSON.stringify([conversationId, messageId])).slice(0, 32)}`;
4862
5210
  let entry = relaySends.get(id);
4863
5211
  if (!entry) relaySends.set(id, entry = prepareRelaySend(conversationId, id, text3, deadline));
4864
5212
  const prepared = await beforeDeadline(entry.ready, deadline);
@@ -4866,6 +5214,54 @@ ${JSON.stringify(result, null, 2)}`);
4866
5214
  const sent = await within(entry.sent, Math.max(0, deadline - Date.now()));
4867
5215
  if (sent === "refused") throw new ConnectorRelayError(409, "CONNECTOR_RELAY_SEND_REFUSED", "Canon did not accept the message; the conversation's members may have changed");
4868
5216
  return { conversationId, messageId, historyMessageId: id, status: sent === "delivered" ? "sent" : "pending" };
5217
+ },
5218
+ async relayStart(targetUserId, messageId, text3, selection, startOptions = {}) {
5219
+ if (typeof targetUserId !== "string" || !targetUserId || targetUserId.length > 200 || targetUserId.includes("/") || typeof messageId !== "string" || !ENDPOINT_ID_PATTERN.test(messageId) || messageId.length > 120 || typeof text3 !== "string" || !text3.trim() || Buffer.byteLength(text3) > MLS_DEV_TEXT_MAX_BYTES) {
5220
+ throw new ConnectorRelayError(400, "CONNECTOR_RELAY_INVALID", "A start needs a person or agent, a messageId and 1 byte to 32 KiB of text");
5221
+ }
5222
+ if (!options.relay || !client) throw new ConnectorRelayError(403, "CONNECTOR_RELAY_UNAVAILABLE", "This runner is not a relay");
5223
+ if (!mlsOn) throw new ConnectorRelayError(503, "CONNECTOR_RELAY_NO_DEVICE", "The relay has no MLS device");
5224
+ const deadline = startOptions.deadline ?? Date.now() + CONNECTOR_RELAY_ANSWER_WITHIN_MS;
5225
+ const identity = { messageId, historyMessageId: `relay-start-${sha2563(messageId).slice(0, 32)}` };
5226
+ const answer = await within(
5227
+ endpoint.startWithheldOpener({
5228
+ targetUserId,
5229
+ text: text3,
5230
+ messageId: identity.historyMessageId,
5231
+ ...selection ? { selection } : {}
5232
+ }).then((result2) => ({ result: result2 }), (error) => ({ error })),
5233
+ Math.max(0, deadline - Date.now())
5234
+ );
5235
+ if (!answer || "error" in answer && answer.error?.retryable === true) {
5236
+ return { ...identity, status: "starting" };
5237
+ }
5238
+ if ("error" in answer) {
5239
+ const { status, code, message: message2 } = answer.error;
5240
+ if (code === "OPERATION_ID_COLLISION") {
5241
+ throw new ConnectorRelayError(409, "CONNECTOR_RELAY_MESSAGE_CONFLICT", "This messageId already started a conversation with other text or another person");
5242
+ }
5243
+ if ((status === 400 || status === 403 || status === 404 || status === 409) && typeof code === "string") {
5244
+ throw new ConnectorRelayError(status, code, typeof message2 === "string" ? message2 : "Canon refused the start");
5245
+ }
5246
+ throw answer.error;
5247
+ }
5248
+ const result = answer.result;
5249
+ if ("reason" in result) {
5250
+ if (result.reason === "opening-text-required") {
5251
+ throw new ConnectorRelayError(403, "CONNECTOR_RELAY_NOT_ENCRYPTED", "The relay serves only encrypted conversations its agent is in");
5252
+ }
5253
+ return { ...identity, status: result.status, reason: result.reason };
5254
+ }
5255
+ if ("requestId" in result) {
5256
+ return { ...identity, status: result.status, requestId: result.requestId, ...result.conversationId ? { conversationId: result.conversationId } : {} };
5257
+ }
5258
+ return {
5259
+ ...identity,
5260
+ status: "messaged",
5261
+ conversationId: result.conversationId,
5262
+ ...result.created !== void 0 ? { created: result.created } : {},
5263
+ ...result.reused !== void 0 ? { reused: result.reused } : {}
5264
+ };
4869
5265
  }
4870
5266
  };
4871
5267
  }
@@ -4903,6 +5299,10 @@ export {
4903
5299
  MLS_SETUP_ACTIONS,
4904
5300
  mlsDevRoomPackageId,
4905
5301
  runMlsDevSetup,
5302
+ MLS_ENVIRONMENT_PINS,
5303
+ bundledMlsCustodyHelper,
5304
+ defaultMlsDevice,
5305
+ managedMlsDevice,
4906
5306
  startRelayChannel,
4907
5307
  loadRelayConfiguration,
4908
5308
  readRelayCredential,