@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.
- package/README.md +14 -10
- package/dist/chunks/{chunk-JCBQCO3F.js → chunk-3KFVBOVI.js} +513 -113
- package/dist/chunks/{chunk-5MI6Q3VS.js → chunk-C2WJT3VX.js} +20 -0
- package/dist/chunks/{chunk-RFNYPZXD.js → chunk-TFSURFF6.js} +74 -8
- package/dist/chunks/{mls-crypto-D5YAGYJ3.js → mls-crypto-Y44BOSEM.js} +1 -1
- package/dist/cli.js +33 -23
- package/dist/drivers/connector.d.ts +5 -4
- package/dist/index.d.ts +2 -0
- package/dist/index.js +9 -3
- package/dist/internal/backend-contracts/connectorRelay.d.ts +34 -0
- package/dist/internal/backend-contracts/endpoint.d.ts +1 -1
- package/dist/internal/backend-contracts/endpointContentPolicy.d.ts +5 -0
- package/dist/internal/endpoint/engine.d.ts +5 -1
- package/dist/internal/endpoint/mls-dev-client.d.ts +9 -0
- package/dist/media.js +2 -2
- package/dist/mls/darwin-arm64/canon-mls-custody +0 -0
- package/dist/mls/linux-arm64/canon-mls-custody +0 -0
- package/dist/mls/linux-x64/canon-mls-custody +0 -0
- package/dist/runner/mls-bundled.d.ts +25 -0
- package/dist/runner/mls-managed.d.ts +36 -0
- package/dist/runner/mls.d.ts +19 -6
- package/dist/runner/relay.d.ts +3 -3
- package/dist/runner/runner.d.ts +18 -7
- package/dist/runner/types.d.ts +6 -3
- package/package.json +2 -2
|
@@ -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-
|
|
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-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
922
|
-
entry.
|
|
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([
|
|
929
|
-
|
|
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 (
|
|
934
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
1539
|
+
const device2 = await devices.read();
|
|
1508
1540
|
if (device2.registration || device2.status !== "absent") throw new Error("MLS_DEV_SETUP_ALREADY_PROVISIONED");
|
|
1509
|
-
|
|
1541
|
+
const context = {
|
|
1510
1542
|
version: 1,
|
|
1511
|
-
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
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
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
|
|
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
|
|
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
|
|
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) =>
|
|
1719
|
-
var credentialFile = /* @__PURE__ */ __name((home) =>
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
1762
|
-
|
|
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
|
|
1769
|
-
import { readFileSync as
|
|
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
|
|
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
|
|
2266
|
-
import { readdirSync as readdirSync2, readFileSync as
|
|
2267
|
-
import { join as
|
|
2268
|
-
var
|
|
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) =>
|
|
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:${
|
|
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(
|
|
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(
|
|
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
|
|
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,
|
|
2862
|
-
`${speaker(name)}${
|
|
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}-${
|
|
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-${
|
|
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) =>
|
|
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((
|
|
3281
|
-
turn.
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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 =
|
|
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
|
|
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:
|
|
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,
|
|
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((
|
|
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 (
|
|
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 (
|
|
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:${
|
|
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,
|
|
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
|
-
|
|
3959
|
-
|
|
3960
|
-
|
|
3961
|
-
|
|
3962
|
-
|
|
3963
|
-
|
|
3964
|
-
|
|
3965
|
-
|
|
3966
|
-
|
|
3967
|
-
|
|
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
|
-
|
|
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-${
|
|
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(
|
|
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
|
|
4494
|
+
const mls2 = mode === "mls-dev-text" && mlsOn ? (await currentAudience(roomId)).audience : void 0;
|
|
4155
4495
|
const legacy = `${id}:restart`;
|
|
4156
|
-
const messageId =
|
|
4157
|
-
if (mode !== "plaintext" && !
|
|
4158
|
-
else if (await send(roomId, RESTART_NOTICE, outputOptions(id, trigger, "restart", false, messageId), void 0, void 0,
|
|
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-${
|
|
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-${
|
|
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:
|
|
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
|
-
...
|
|
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(
|
|
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(
|
|
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:
|
|
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(
|
|
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 =
|
|
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
|
-
|
|
4763
|
-
|
|
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
|
|
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-${
|
|
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,
|