@canonmsg/agent-sdk 12.4.1 → 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 +3 -3
- package/dist/chunks/{chunk-NXL4C2R6.js → chunk-3KFVBOVI.js} +207 -62
- package/dist/chunks/{chunk-GWQF4WFT.js → chunk-C2WJT3VX.js} +16 -0
- package/dist/chunks/{chunk-6PKXASSK.js → chunk-TFSURFF6.js} +49 -7
- package/dist/chunks/{mls-crypto-LGPCXM3K.js → mls-crypto-Y44BOSEM.js} +1 -1
- package/dist/cli.js +6 -10
- package/dist/drivers/connector.d.ts +5 -4
- package/dist/index.js +3 -3
- package/dist/internal/backend-contracts/connectorRelay.d.ts +34 -0
- package/dist/internal/backend-contracts/endpoint.d.ts +1 -1
- 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/runner/mls-managed.d.ts +6 -1
- package/dist/runner/mls.d.ts +7 -1
- 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 +1 -1
package/README.md
CHANGED
|
@@ -192,7 +192,7 @@ Inbound attachments are downloaded before the turn starts, up to 10 MiB each, an
|
|
|
192
192
|
| `askExpiryMs` | 1 h | How long an `ask` waits for its answer |
|
|
193
193
|
| `shutdownGraceMs` | 10 s | How long running turns may finish after `stop()` |
|
|
194
194
|
| `log` | stderr | Receives operational lines, never prompt or reply text |
|
|
195
|
-
| `mls` |
|
|
195
|
+
| `mls` | managed custody, where this package ships a helper for the host | The agent's MLS device for encrypted rooms, or `false` for none; see [Encrypted rooms](#encrypted-rooms) |
|
|
196
196
|
| `relay`, `installationId` | off | Canon's class-C relay; see [The class-C relay](#the-class-c-relay-canon-operated) |
|
|
197
197
|
| `trustedAgents` | none | Agent ids whose messages count as the owner's for tool use while Canon confirms each is an active agent of the same owner; see [Team rooms](#team-rooms) |
|
|
198
198
|
| `agentTurnCap` | 6 | Encrypted rooms: how many agent @mentions in a row wake the agent in a room before a person writes there again |
|
|
@@ -213,7 +213,7 @@ The CLI is `canon-agent run --driver code --module <path> [--profile <name>] [--
|
|
|
213
213
|
|
|
214
214
|
Dev and Prod require every new room to be end-to-end encrypted (MLS). Without an MLS device an agent takes part in no encrypted room. An agent whose outbound policy is closed cannot send there. The runner holds the agent's MLS device; drivers need nothing for it.
|
|
215
215
|
|
|
216
|
-
On macOS (Apple silicon) and Linux (x64 and arm64, glibc 2.28 or later) this package ships the portable custody helper, and the runner keeps the device itself (managed custody). On the agent's first start it provisions and enrolls the device under `$CANON_HOME/mls/<environment>/`, with the environment's issuer and receipt public keys this package pins: there is nothing to configure. Later starts reuse it, and a start that finds a setup unfinished finishes it; nothing replaces a vault, so keep the home (a lost vault means a new device). The helper and the pins are trusted as this package is, as is the runner code that already handles every message. `canon-agent mls --action setup` does the same without starting a model
|
|
216
|
+
On macOS (Apple silicon) and Linux (x64 and arm64, glibc 2.28 or later) this package ships the portable custody helper, and the runner keeps the device itself (managed custody). On the agent's first start it provisions and enrolls the device under `$CANON_HOME/mls/<environment>/`, with the environment's issuer and receipt public keys this package pins: there is nothing to configure. Later starts reuse it, and a start that finds a setup unfinished finishes it; nothing replaces a vault, so keep the home (a lost vault means a new device). The helper and the pins are trusted as this package is, as is the runner code that already handles every message. `canon-agent mls --action setup` does the same without starting a model. `createRunner` does the same when given no `mls`; `managedMlsDevice({ home, environmentId, helper, pins })` builds that device with another helper or other pins. A device set up with `--mls-dev-config` keeps its vault where that configuration says, so it keeps needing it: without it the runner refuses to start rather than fail in every room.
|
|
217
217
|
|
|
218
218
|
Elsewhere (an Intel Mac or musl; Windows is not supported), or to keep the device in custody of your own, give the runner an operator's MLS configuration with `--mls-dev-config`: a private JSON file with the custody helper, built from `mls/native` in the Canon repository, and the environment's issuer and receipt public keys, never private keys or credentials. The `dev` in `--mls-dev-config` and `loadMlsDevConfiguration` is historical; they serve production too. `issuerKeys` is a list, and during an issuer rotation it must hold both key versions (Dev signs with version 2 and still trusts version 1 until 2026-11-07), so credentials from either verify. Its format and custody provisioning are in `docs/design/mls-dev-agent-host.md` in the Canon repository:
|
|
219
219
|
|
|
@@ -236,7 +236,7 @@ In an encrypted room:
|
|
|
236
236
|
- A reply is journaled before it is encrypted. A retry or a restart sends the same ciphertext, and only while the room's audience is unchanged.
|
|
237
237
|
- The ids Canon sees for what the runner sends there (each message's id, and a card's request id) are keyed with a secret the runner creates once in `$CANON_HOME/runner/<environment>/<agent>/mls-ids/` (0600; it belongs with the rest of the home). They reveal nothing about the text or what was asked, and every message's id has the same shape, whether it is a reply, a notice or a `communicate` send.
|
|
238
238
|
|
|
239
|
-
Without a device (no helper for the host and no `--mls-dev-config`, or
|
|
239
|
+
Without a device (no helper for the host and no `--mls-dev-config`, or `mls: false` for `createRunner`), the runner refuses each encrypted room, logs it once, and runs nothing for it. Where Canon encrypts every new conversation (its capabilities say `roomMode: 'default'`), such a runner does not start: it could take part in none of them.
|
|
240
240
|
|
|
241
241
|
### The class-C relay (Canon-operated)
|
|
242
242
|
|
|
@@ -30,7 +30,7 @@ import {
|
|
|
30
30
|
sendMessageWithRetryChunked,
|
|
31
31
|
splitTextByUtf8Bytes,
|
|
32
32
|
verifyCanonRuntimeConnection
|
|
33
|
-
} from "./chunk-
|
|
33
|
+
} from "./chunk-TFSURFF6.js";
|
|
34
34
|
import {
|
|
35
35
|
CONNECTOR_RELAY_ANSWER_WITHIN_MS,
|
|
36
36
|
CONNECTOR_RELAY_INSTALLATION_PATTERN,
|
|
@@ -59,7 +59,7 @@ import {
|
|
|
59
59
|
parseMlsDevGroupHead,
|
|
60
60
|
parseMlsDevGroupRoster,
|
|
61
61
|
resolveAllowedUploadMimeType
|
|
62
|
-
} from "./chunk-
|
|
62
|
+
} from "./chunk-C2WJT3VX.js";
|
|
63
63
|
|
|
64
64
|
// src/runner/types.ts
|
|
65
65
|
var DriverSessionError = class extends Error {
|
|
@@ -913,31 +913,43 @@ function connectorDriver() {
|
|
|
913
913
|
},
|
|
914
914
|
async claim(inputId, requestId, options = {}) {
|
|
915
915
|
const entry = find(inputId);
|
|
916
|
-
if (entry.state === "claiming") {
|
|
917
|
-
|
|
918
|
-
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");
|
|
919
918
|
}
|
|
920
|
-
if (entry.state !== "pending") {
|
|
919
|
+
if (entry.state !== "pending" && entry.state !== "claiming") {
|
|
921
920
|
if (entry.requestId !== void 0 && entry.requestId !== requestId) {
|
|
922
921
|
throw new ConnectorRelayError(409, "CONNECTOR_CLAIM_CONFLICT", "This input belongs to another claim");
|
|
923
922
|
}
|
|
924
923
|
return { inputId, requestId, runAllowed: false, state: entry.state };
|
|
925
924
|
}
|
|
926
|
-
entry.
|
|
927
|
-
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;
|
|
928
940
|
const deadline = options.deadline ?? Date.now() + CONNECTOR_RELAY_ANSWER_WITHIN_MS;
|
|
929
941
|
let timer;
|
|
930
942
|
const late = new Promise((resolve3) => {
|
|
931
943
|
timer = setTimeout(resolve3, Math.max(0, deadline - Date.now()), "late");
|
|
932
944
|
});
|
|
933
|
-
const confirmed = await Promise.race([
|
|
934
|
-
|
|
945
|
+
const confirmed = await Promise.race([confirmation, late]).finally(() => clearTimeout(timer));
|
|
946
|
+
const state = entry.state;
|
|
947
|
+
if (state === "claiming" && confirmed === "ok") {
|
|
935
948
|
entry.state = "claimed";
|
|
936
949
|
return { inputId, requestId, runAllowed: true, state: "claimed", input: entry.input };
|
|
937
950
|
}
|
|
938
|
-
if (
|
|
939
|
-
|
|
940
|
-
entry.requestId = void 0;
|
|
951
|
+
if (state === "claimed") return { inputId, requestId, runAllowed: false, state: "claimed" };
|
|
952
|
+
if (state === "withdrawn" || state === "answered") throw withdrawn();
|
|
941
953
|
throw new ConnectorRelayError(503, "CONNECTOR_RELAY_BUSY", "Canon could not confirm this claim now; try again");
|
|
942
954
|
},
|
|
943
955
|
reply: /* @__PURE__ */ __name((inputId, requestId, text3) => answer(inputId, requestId, { outcome: "reply", text: text3 }), "reply"),
|
|
@@ -952,7 +964,10 @@ function connectorDriver() {
|
|
|
952
964
|
activity: false,
|
|
953
965
|
tools: false,
|
|
954
966
|
outboundFiles: false,
|
|
955
|
-
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
|
|
956
971
|
},
|
|
957
972
|
inbox,
|
|
958
973
|
runTurn(turn) {
|
|
@@ -1503,11 +1518,18 @@ function mlsDevRoomPackageId(identity, room) {
|
|
|
1503
1518
|
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-4${hex.slice(13, 16)}-8${hex.slice(17, 20)}-${hex.slice(20, 32)}`;
|
|
1504
1519
|
}
|
|
1505
1520
|
__name(mlsDevRoomPackageId, "mlsDevRoomPackageId");
|
|
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");
|
|
1506
1527
|
async function runMlsDevSetup(endpoint, action, room, provision, provisioned) {
|
|
1507
1528
|
const identity = await endpoint.ready();
|
|
1508
1529
|
const { environmentId } = identity, devices = endpoint.mlsDevDevice;
|
|
1509
1530
|
if (!isMlsEnvironmentId(environmentId) || !devices || !endpoint.mlsDevKeyPackages || !endpoint.mlsDevGroups) throw new Error("MLS_DEV_SETUP_UNAVAILABLE");
|
|
1510
1531
|
if ((action === "publish" || action === "refresh") !== !!room) throw new Error("MLS_DEV_SETUP_ARGUMENTS_INVALID");
|
|
1532
|
+
if (provisioned && action !== "status") await assertMlsVaultInHome(endpoint, provisioned);
|
|
1511
1533
|
const enroll = /* @__PURE__ */ __name(async () => {
|
|
1512
1534
|
await devices.enroll();
|
|
1513
1535
|
if ((await devices.read()).status !== "credential_committed") await devices.renew();
|
|
@@ -1689,6 +1711,12 @@ import { createHash as createHash6 } from "node:crypto";
|
|
|
1689
1711
|
import { chmodSync, copyFileSync, existsSync as existsSync2, mkdirSync as mkdirSync3, readFileSync as readFileSync3, renameSync as renameSync2 } from "node:fs";
|
|
1690
1712
|
import { dirname as dirname5, join as join3 } from "node:path";
|
|
1691
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");
|
|
1692
1720
|
function managedMlsDevice(options) {
|
|
1693
1721
|
const { environmentId } = options;
|
|
1694
1722
|
const helper = options.helper ?? bundledMlsCustodyHelper();
|
|
@@ -1849,6 +1877,8 @@ async function startRelayChannel(options) {
|
|
|
1849
1877
|
return served.runner.relayRead(request.args.conversationId, { limit: request.args.limit, before: request.args.before, deadline });
|
|
1850
1878
|
case "send":
|
|
1851
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 });
|
|
1852
1882
|
}
|
|
1853
1883
|
}
|
|
1854
1884
|
__name(dispatch, "dispatch");
|
|
@@ -3214,6 +3244,7 @@ __name(runtimeDescriptor, "runtimeDescriptor");
|
|
|
3214
3244
|
async function createRunner(options) {
|
|
3215
3245
|
const { driver, home } = options;
|
|
3216
3246
|
await assertPrivateHome(home);
|
|
3247
|
+
const mls = options.mls === false ? void 0 : options.mls ?? (options.endpoint ? void 0 : defaultMlsDevice(home, options.environmentId));
|
|
3217
3248
|
const log = options.log ?? ((line) => console.error(`[canon-runner] ${line}`));
|
|
3218
3249
|
const warn = /* @__PURE__ */ __name((what) => (error) => log(`${what}: ${error instanceof Error ? error.message : String(error)}`), "warn");
|
|
3219
3250
|
const abortGraceMs = options.abortGraceMs ?? 1e4;
|
|
@@ -3385,6 +3416,7 @@ ${error instanceof Error ? error.message : String(error)}`;
|
|
|
3385
3416
|
if (input.mls) target.mls = true;
|
|
3386
3417
|
target.queue.push(input);
|
|
3387
3418
|
target.turn?.live.refresh();
|
|
3419
|
+
requeue(target);
|
|
3388
3420
|
pump();
|
|
3389
3421
|
}
|
|
3390
3422
|
__name(offer, "offer");
|
|
@@ -3492,7 +3524,7 @@ ${error instanceof Error ? error.message : String(error)}`;
|
|
|
3492
3524
|
const inputs = candidate.queue.splice(0, Math.min(MAX_INPUTS_PER_TURN, other < 0 ? MAX_INPUTS_PER_TURN : other));
|
|
3493
3525
|
const turn = newTurn(inputs, turnId(candidate.id, inputs), candidate.id);
|
|
3494
3526
|
candidate.turn = turn;
|
|
3495
|
-
(turn.live = livePublisher(candidate, turn, encrypted ? "presence" : "status")).open();
|
|
3527
|
+
(turn.live = driver.capabilities.remote ? SILENT : livePublisher(candidate, turn, encrypted ? "presence" : "status")).open();
|
|
3496
3528
|
const done = execute(candidate, turn).catch((error) => {
|
|
3497
3529
|
warn("turn failed")(error);
|
|
3498
3530
|
return dispose(queued(turn), "release", "turn-failed");
|
|
@@ -3522,14 +3554,28 @@ ${error instanceof Error ? error.message : String(error)}`;
|
|
|
3522
3554
|
if (turn.inputs[0].mls && !await beforeTurn(current, turn)) return;
|
|
3523
3555
|
turn.trusted = await confirmTrusted(turn, conversation.memberIds);
|
|
3524
3556
|
turn.notes = pendingNotes(current.id);
|
|
3525
|
-
turn.contexts = await Promise.all(openErrands(current.id).map(async (record) => ({ record, encryptedOrigin: !!record.origin && await endpoint.getConversationContentMode(record.origin).then((
|
|
3526
|
-
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);
|
|
3527
3560
|
const history = driver.capabilities.sessionMemory === "none" ? await recentHistory(current, turn) : [];
|
|
3528
3561
|
await materialize(current, turn);
|
|
3529
3562
|
const bound = bindTools(current, turn);
|
|
3530
3563
|
try {
|
|
3531
3564
|
let work;
|
|
3532
|
-
|
|
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; ) {
|
|
3533
3579
|
turn.inputs = live;
|
|
3534
3580
|
turn.id = turnId(current.id, live);
|
|
3535
3581
|
let driverTurn = buildTurn(current.id, turn, kind, history, bound.tools);
|
|
@@ -3552,15 +3598,17 @@ ${error instanceof Error ? error.message : String(error)}`;
|
|
|
3552
3598
|
live = next;
|
|
3553
3599
|
}
|
|
3554
3600
|
if (!work) return;
|
|
3555
|
-
|
|
3556
|
-
progress(turn);
|
|
3557
|
-
if (!turn.mls) void client.markAsRead(current.id).catch(warn("mark read"));
|
|
3558
|
-
for (const { payload } of turn.mls ? [] : turn.inputs) {
|
|
3559
|
-
if (payload.message.metadata?.inboundDisposition === "queued") {
|
|
3560
|
-
void client.updateMessageDisposition(current.id, payload.message.id, "accepted_now").catch(warn("accepted_now"));
|
|
3561
|
-
}
|
|
3562
|
-
}
|
|
3601
|
+
if (!driver.capabilities.remote) started(current, turn);
|
|
3563
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
|
+
}
|
|
3564
3612
|
turn.open = false;
|
|
3565
3613
|
turn.ended.abort();
|
|
3566
3614
|
bound.release();
|
|
@@ -3573,6 +3621,46 @@ ${error instanceof Error ? error.message : String(error)}`;
|
|
|
3573
3621
|
}
|
|
3574
3622
|
}
|
|
3575
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");
|
|
3576
3664
|
async function confirmTrusted(turn, memberIds) {
|
|
3577
3665
|
if (!trustedAgents.size || driver.capabilities.remote || options.relay) return /* @__PURE__ */ new Set();
|
|
3578
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));
|
|
@@ -3805,7 +3893,7 @@ ${JSON.stringify(result, null, 2)}`);
|
|
|
3805
3893
|
let sending = turn.said.get(messageId);
|
|
3806
3894
|
if (!sending) {
|
|
3807
3895
|
turn.said.set(messageId, sending = (async () => {
|
|
3808
|
-
const
|
|
3896
|
+
const mls2 = audience ?? (await currentAudience(roomId)).audience;
|
|
3809
3897
|
const record = {
|
|
3810
3898
|
version: 1,
|
|
3811
3899
|
turnId: messageId,
|
|
@@ -3816,7 +3904,7 @@ ${JSON.stringify(result, null, 2)}`);
|
|
|
3816
3904
|
text: text3,
|
|
3817
3905
|
options: { messageId },
|
|
3818
3906
|
notice: false,
|
|
3819
|
-
mls
|
|
3907
|
+
mls: mls2
|
|
3820
3908
|
};
|
|
3821
3909
|
journal.save(record);
|
|
3822
3910
|
const sent2 = await publish(record);
|
|
@@ -4052,21 +4140,21 @@ ${JSON.stringify(result, null, 2)}`);
|
|
|
4052
4140
|
return await send(record.conversationId, STOPS_SHORT_NOTICE, notice, retire) === "pending" ? "pending" : "refused";
|
|
4053
4141
|
}
|
|
4054
4142
|
__name(publish, "publish");
|
|
4055
|
-
async function send(conversationId, text3, options2, retire, record,
|
|
4143
|
+
async function send(conversationId, text3, options2, retire, record, mls2 = record?.mls) {
|
|
4056
4144
|
const sent = await (async () => {
|
|
4057
4145
|
for (let attempt = 0; ; attempt += 1) {
|
|
4058
4146
|
const files = record?.attachments?.length ?? 0;
|
|
4059
|
-
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));
|
|
4060
4148
|
if (result === "delivered" || result === "pending") return result;
|
|
4061
4149
|
if (retire?.aborted || isPendingCanonOperation(result)) return "pending";
|
|
4062
|
-
if (
|
|
4150
|
+
if (mls2 ? result.retryable === false : !isRetryableCanonDeliveryError(result)) {
|
|
4063
4151
|
warn("Canon refused a reply")(result);
|
|
4064
4152
|
return "refused";
|
|
4065
4153
|
}
|
|
4066
4154
|
if (await untilHalt(pause2(Math.min(3e4, 1e3 * 2 ** attempt))) === "pending") return "pending";
|
|
4067
4155
|
}
|
|
4068
4156
|
})();
|
|
4069
|
-
if (
|
|
4157
|
+
if (mls2) forgetGuard(mls2, options2.messageId, sent);
|
|
4070
4158
|
return sent;
|
|
4071
4159
|
}
|
|
4072
4160
|
__name(send, "send");
|
|
@@ -4200,21 +4288,28 @@ ${JSON.stringify(result, null, 2)}`);
|
|
|
4200
4288
|
__name(buildTurn, "buildTurn");
|
|
4201
4289
|
async function confirmPickup(roomId, turn, open4) {
|
|
4202
4290
|
if (!open4()) throw turnEnded2();
|
|
4203
|
-
|
|
4204
|
-
|
|
4205
|
-
|
|
4206
|
-
|
|
4207
|
-
|
|
4208
|
-
|
|
4209
|
-
|
|
4210
|
-
if (!turn.controller.signal.aborted) {
|
|
4211
|
-
void dispose(queued(turn), "settle", "mls-obsolete-audience");
|
|
4212
|
-
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");
|
|
4213
4298
|
}
|
|
4214
|
-
|
|
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");
|
|
4305
|
+
}
|
|
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));
|
|
4215
4312
|
}
|
|
4216
|
-
if (!open4()) throw turnEnded2();
|
|
4217
|
-
progress(turn);
|
|
4218
4313
|
}
|
|
4219
4314
|
__name(confirmPickup, "confirmPickup");
|
|
4220
4315
|
function ask(roomId, turn, sender, request) {
|
|
@@ -4396,11 +4491,11 @@ ${JSON.stringify(result, null, 2)}`);
|
|
|
4396
4491
|
const mode = await endpoint.getConversationContentMode(roomId);
|
|
4397
4492
|
if (mode !== "plaintext") target.mls = true;
|
|
4398
4493
|
if (mode !== "plaintext" && (!mlsOn || mode !== "mls-dev-text")) refuse(roomId, mlsOn ? "protocol" : "no-device");
|
|
4399
|
-
const
|
|
4494
|
+
const mls2 = mode === "mls-dev-text" && mlsOn ? (await currentAudience(roomId)).audience : void 0;
|
|
4400
4495
|
const legacy = `${id}:restart`;
|
|
4401
|
-
const messageId =
|
|
4402
|
-
if (mode !== "plaintext" && !
|
|
4403
|
-
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") {
|
|
4404
4499
|
await settle(inputs.map(({ event }) => event.id), "interrupted-by-restart");
|
|
4405
4500
|
}
|
|
4406
4501
|
}
|
|
@@ -4933,7 +5028,7 @@ ${JSON.stringify(result, null, 2)}`);
|
|
|
4933
5028
|
// Below Canon's client floor nothing can work, wherever the refusal surfaces: a query, an operation or the stream.
|
|
4934
5029
|
...options.onFatal ? { onClientUpdateRequired: /* @__PURE__ */ __name((error) => options.onFatal(error), "onClientUpdateRequired") } : {},
|
|
4935
5030
|
...options.installationId ? { installationId: options.installationId } : {},
|
|
4936
|
-
...
|
|
5031
|
+
...mls ? { mlsDev: { configuration: mls.configuration, custody: mls.custody } } : {},
|
|
4937
5032
|
...options.endpoint ? { endpoint: options.endpoint } : {},
|
|
4938
5033
|
...options.fetch ? { fetch: options.fetch } : {}
|
|
4939
5034
|
});
|
|
@@ -4949,14 +5044,17 @@ ${JSON.stringify(result, null, 2)}`);
|
|
|
4949
5044
|
if (options.wake !== void 0) await client.configureWake(options.wake);
|
|
4950
5045
|
mlsOn = !!(endpoint.mlsDevDevice && endpoint.mlsDevGroups);
|
|
4951
5046
|
if (!mlsOn && (await endpoint.capabilities().catch(() => null))?.mls?.roomMode === "default") {
|
|
4952
|
-
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 })).`);
|
|
4953
5048
|
}
|
|
4954
|
-
if (mlsOn &&
|
|
4955
|
-
|
|
4956
|
-
|
|
4957
|
-
|
|
4958
|
-
|
|
4959
|
-
|
|
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
|
+
}
|
|
4960
5058
|
}
|
|
4961
5059
|
if (mlsOn) await endpoint.mlsDevDevice.ensureReady().catch(warn("MLS device not ready; encrypted rooms wait for it"));
|
|
4962
5060
|
const keepPool = /* @__PURE__ */ __name(() => endpoint.mlsDevDevice.read().then(async (view) => {
|
|
@@ -5011,10 +5109,8 @@ ${JSON.stringify(result, null, 2)}`);
|
|
|
5011
5109
|
roots = outbound ? (await Promise.all(outbound.roots.map((root) => realpath5(root).catch(() => "")))).filter(Boolean) : [];
|
|
5012
5110
|
if (driver.capabilities.tools) tools = await startToolServer();
|
|
5013
5111
|
await recover();
|
|
5014
|
-
|
|
5015
|
-
|
|
5016
|
-
heartbeat = createRuntimeHeartbeat({ publisher, getRuntime: /* @__PURE__ */ __name(() => ({ runtimeDescriptor: descriptor }), "getRuntime"), onError: warn("runtime heartbeat") });
|
|
5017
|
-
}
|
|
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") });
|
|
5018
5114
|
inbound = endpoint.acceptInbound({ kind: "message.created", offer, onError: warn("inbox"), ...mlsOn ? { intervalMs: 1e3 } : {} });
|
|
5019
5115
|
inbound.start();
|
|
5020
5116
|
let reconnect = false;
|
|
@@ -5079,7 +5175,7 @@ ${JSON.stringify(result, null, 2)}`);
|
|
|
5079
5175
|
for (const unregister of guards.values()) unregister();
|
|
5080
5176
|
guards.clear();
|
|
5081
5177
|
await client?.close();
|
|
5082
|
-
await
|
|
5178
|
+
await mls?.close?.().catch(warn("MLS custody close"));
|
|
5083
5179
|
})().finally(() => lock?.release());
|
|
5084
5180
|
return stopping;
|
|
5085
5181
|
},
|
|
@@ -5118,6 +5214,54 @@ ${JSON.stringify(result, null, 2)}`);
|
|
|
5118
5214
|
const sent = await within(entry.sent, Math.max(0, deadline - Date.now()));
|
|
5119
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");
|
|
5120
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
|
+
};
|
|
5121
5265
|
}
|
|
5122
5266
|
};
|
|
5123
5267
|
}
|
|
@@ -5157,6 +5301,7 @@ export {
|
|
|
5157
5301
|
runMlsDevSetup,
|
|
5158
5302
|
MLS_ENVIRONMENT_PINS,
|
|
5159
5303
|
bundledMlsCustodyHelper,
|
|
5304
|
+
defaultMlsDevice,
|
|
5160
5305
|
managedMlsDevice,
|
|
5161
5306
|
startRelayChannel,
|
|
5162
5307
|
loadRelayConfiguration,
|
|
@@ -4654,6 +4654,13 @@ function text4(value) {
|
|
|
4654
4654
|
return value;
|
|
4655
4655
|
}
|
|
4656
4656
|
__name(text4, "text");
|
|
4657
|
+
function selection(value) {
|
|
4658
|
+
const args = record3(value, ["mode"], ["conversationId"]);
|
|
4659
|
+
if ((args.mode === "new" || args.mode === "latest_or_new") && args.conversationId === void 0) return { mode: args.mode };
|
|
4660
|
+
if (args.mode === "specific") return { mode: "specific", conversationId: room(args.conversationId) };
|
|
4661
|
+
return invalid11("selection");
|
|
4662
|
+
}
|
|
4663
|
+
__name(selection, "selection");
|
|
4657
4664
|
function limit(value) {
|
|
4658
4665
|
if (value === void 0) return void 0;
|
|
4659
4666
|
if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 1 || value > 100) invalid11("limit");
|
|
@@ -4703,6 +4710,15 @@ function parseConnectorRelayRequest(value) {
|
|
|
4703
4710
|
const args = record3(body.args, ["conversationId", "messageId", "text"]);
|
|
4704
4711
|
return { ...head, op: "send", args: { conversationId: room(args.conversationId), messageId: id6(args.messageId, "messageId"), text: text4(args.text) } };
|
|
4705
4712
|
}
|
|
4713
|
+
case "start": {
|
|
4714
|
+
const args = record3(body.args, ["targetUserId", "messageId", "text"], ["selection"]);
|
|
4715
|
+
return { ...head, op: "start", args: {
|
|
4716
|
+
targetUserId: doc(args.targetUserId, "targetUserId"),
|
|
4717
|
+
messageId: id6(args.messageId, "messageId"),
|
|
4718
|
+
text: text4(args.text),
|
|
4719
|
+
...args.selection === void 0 ? {} : { selection: selection(args.selection) }
|
|
4720
|
+
} };
|
|
4721
|
+
}
|
|
4706
4722
|
case "credential": {
|
|
4707
4723
|
const args = record3(body.args, ["installationId", "generation", "credential"]);
|
|
4708
4724
|
if (!Number.isSafeInteger(args.generation) || args.generation < 1) invalid11("generation");
|
|
@@ -110,7 +110,7 @@ import {
|
|
|
110
110
|
readMembershipChangeMetadata,
|
|
111
111
|
resolveAllowedUploadMimeType,
|
|
112
112
|
sanitizeDownloadFileName
|
|
113
|
-
} from "./chunk-
|
|
113
|
+
} from "./chunk-C2WJT3VX.js";
|
|
114
114
|
|
|
115
115
|
// src/media.ts
|
|
116
116
|
import { createWriteStream as createWriteStream2 } from "node:fs";
|
|
@@ -5219,7 +5219,7 @@ function createMlsDevDeviceClient(options, context) {
|
|
|
5219
5219
|
const store = guardedStore();
|
|
5220
5220
|
const fence = /* @__PURE__ */ __name(() => store.transaction(async () => {
|
|
5221
5221
|
}), "fence");
|
|
5222
|
-
async function initialized() {
|
|
5222
|
+
async function initialized(storedPrincipalId) {
|
|
5223
5223
|
context.assertOpen();
|
|
5224
5224
|
const { identity, installationGeneration } = await context.ready();
|
|
5225
5225
|
await fence();
|
|
@@ -5230,7 +5230,7 @@ function createMlsDevDeviceClient(options, context) {
|
|
|
5230
5230
|
const binding = parseMlsDevDeviceBinding({
|
|
5231
5231
|
version: 1,
|
|
5232
5232
|
environmentId: identity.environmentId,
|
|
5233
|
-
principal: { ...principal, id: await context.mlsPrincipalId() },
|
|
5233
|
+
principal: { ...principal, id: storedPrincipalId ?? await context.mlsPrincipalId() },
|
|
5234
5234
|
installationId: identity.installationId
|
|
5235
5235
|
});
|
|
5236
5236
|
const coordinator = /* @__PURE__ */ __name((journal) => createMlsDevDeviceCoordinator({
|
|
@@ -5517,6 +5517,14 @@ function createMlsDevDeviceClient(options, context) {
|
|
|
5517
5517
|
}
|
|
5518
5518
|
}
|
|
5519
5519
|
__name(ensureReady, "ensureReady");
|
|
5520
|
+
async function revoke() {
|
|
5521
|
+
const { coordinator, transport: http } = await initialized();
|
|
5522
|
+
const device = coordinator(store), view3 = await device.read();
|
|
5523
|
+
if (view3.status === "revoked" || !view3.registration) return view3;
|
|
5524
|
+
const receipt = await http.revokeCredential({ version: 1, deviceId: view3.registration.device.deviceId }, context.signal);
|
|
5525
|
+
return device.acceptRevocation(receipt);
|
|
5526
|
+
}
|
|
5527
|
+
__name(revoke, "revoke");
|
|
5520
5528
|
function run(goal) {
|
|
5521
5529
|
context.assertOpen();
|
|
5522
5530
|
const preceding = running;
|
|
@@ -5543,12 +5551,26 @@ function createMlsDevDeviceClient(options, context) {
|
|
|
5543
5551
|
return promise;
|
|
5544
5552
|
}
|
|
5545
5553
|
__name(run, "run");
|
|
5554
|
+
function read(options2) {
|
|
5555
|
+
return context.run(async () => {
|
|
5556
|
+
let storedPrincipalId;
|
|
5557
|
+
if (options2?.localOnly) {
|
|
5558
|
+
await context.ready();
|
|
5559
|
+
const saved = await store.transaction((tx) => tx.get("mls-dev-device/identity"));
|
|
5560
|
+
if (!saved) return void 0;
|
|
5561
|
+
storedPrincipalId = parseMlsDevDeviceBinding(saved.binding).principal.id;
|
|
5562
|
+
}
|
|
5563
|
+
return (await initialized(storedPrincipalId)).coordinator(store).read();
|
|
5564
|
+
});
|
|
5565
|
+
}
|
|
5566
|
+
__name(read, "read");
|
|
5546
5567
|
return Object.freeze({
|
|
5547
|
-
read
|
|
5568
|
+
read,
|
|
5548
5569
|
enroll: /* @__PURE__ */ __name(() => run("enroll"), "enroll"),
|
|
5549
5570
|
renew: /* @__PURE__ */ __name(() => run("renew"), "renew"),
|
|
5550
5571
|
reconcile: /* @__PURE__ */ __name(() => run("reconcile"), "reconcile"),
|
|
5551
|
-
ensureReady: /* @__PURE__ */ __name(() => run("ready"), "ensureReady")
|
|
5572
|
+
ensureReady: /* @__PURE__ */ __name(() => run("ready"), "ensureReady"),
|
|
5573
|
+
revoke: /* @__PURE__ */ __name(() => context.run(revoke), "revoke")
|
|
5552
5574
|
});
|
|
5553
5575
|
}
|
|
5554
5576
|
__name(createMlsDevDeviceClient, "createMlsDevDeviceClient");
|
|
@@ -7242,7 +7264,11 @@ function createMlsDevGroups(options, context, keyPackages, bridge) {
|
|
|
7242
7264
|
const group = envelope((await readPreparationSnapshot(tx, scope4(conversationId))).protocolState, init.binding, conversationId);
|
|
7243
7265
|
const accounts = mlsRoomAccounts(room, init.binding.environmentId);
|
|
7244
7266
|
const admitted = new Set((group && !group.removed ? group.roster : []).map((entry) => mlsAccountOf(accounts, entry.device.principal)));
|
|
7245
|
-
const waiting = [
|
|
7267
|
+
const waiting = [.../* @__PURE__ */ new Set([
|
|
7268
|
+
...Array.isArray(room.memberIds) ? room.memberIds : [],
|
|
7269
|
+
...typeof room.mlsInviteeId === "string" ? [room.mlsInviteeId] : [],
|
|
7270
|
+
...Array.isArray(room.mlsPendingFounderIds) ? room.mlsPendingFounderIds : []
|
|
7271
|
+
])].filter((memberId) => typeof memberId === "string" && !admitted.has(memberId));
|
|
7246
7272
|
return Object.freeze({ held: waiting.length > 0, waitingPrincipalIds: Object.freeze(waiting) });
|
|
7247
7273
|
});
|
|
7248
7274
|
}
|
|
@@ -9514,6 +9540,13 @@ function createEndpoint(options) {
|
|
|
9514
9540
|
createdAt: timestamp,
|
|
9515
9541
|
updatedAt: timestamp
|
|
9516
9542
|
};
|
|
9543
|
+
if (normalized.action === "start_conversation") {
|
|
9544
|
+
const opener = normalized.body.opener;
|
|
9545
|
+
if (opener?.withheld && typeof opener.messageId === "string") {
|
|
9546
|
+
const pending = await tx.get(`${openerPrefix()}/${encodeURIComponent(opener.messageId)}`);
|
|
9547
|
+
if (pending?.start?.operationId === operationId) created.openerFingerprint = pending.openerFingerprint;
|
|
9548
|
+
}
|
|
9549
|
+
}
|
|
9517
9550
|
return commitOperationRecord(tx, created);
|
|
9518
9551
|
}, "commitOperation");
|
|
9519
9552
|
const operation = sensitive ? await store.transaction(async (tx) => {
|
|
@@ -9766,6 +9799,7 @@ function createEndpoint(options) {
|
|
|
9766
9799
|
},
|
|
9767
9800
|
async startWithheldOpener(input) {
|
|
9768
9801
|
assertOpen();
|
|
9802
|
+
input = copyJson(input);
|
|
9769
9803
|
await initialization;
|
|
9770
9804
|
if (typeof input.messageId !== "string" || !input.messageId || typeof input.text !== "string")
|
|
9771
9805
|
throw new EndpointError("Invalid deferred opening line", { status: 400, code: "INVALID_DEFERRED_OPENER", retryable: false });
|
|
@@ -9776,9 +9810,16 @@ function createEndpoint(options) {
|
|
|
9776
9810
|
opener: { withheld: true, messageId: input.messageId },
|
|
9777
9811
|
...input.selection ? { selection: copyJson(input.selection) } : {}
|
|
9778
9812
|
};
|
|
9813
|
+
const openerFingerprint = await hash3(canonicalEndpointJson([body, input.text]));
|
|
9779
9814
|
const recordKey = `${openerPrefix()}/${encodeURIComponent(input.messageId)}`;
|
|
9780
9815
|
await store.transaction(async (tx) => {
|
|
9781
9816
|
await assertCurrentInstallation(tx);
|
|
9817
|
+
const operation = await tx.get(key(operationId));
|
|
9818
|
+
if (operation) {
|
|
9819
|
+
if (operation.openerFingerprint !== openerFingerprint)
|
|
9820
|
+
throw new EndpointError("Opening line ID was already used for different content", { status: 409, code: "OPERATION_ID_COLLISION", retryable: false, operationId });
|
|
9821
|
+
return;
|
|
9822
|
+
}
|
|
9782
9823
|
const existing = await tx.get(recordKey);
|
|
9783
9824
|
if (existing && (existing.text !== input.text || canonicalEndpointJson(existing.start?.body) !== canonicalEndpointJson(body)))
|
|
9784
9825
|
throw new EndpointError("Opening line ID was already used for different content", { status: 409, code: "OPERATION_ID_COLLISION", retryable: false, operationId });
|
|
@@ -9787,6 +9828,7 @@ function createEndpoint(options) {
|
|
|
9787
9828
|
messageId: input.messageId,
|
|
9788
9829
|
text: input.text,
|
|
9789
9830
|
requestId: null,
|
|
9831
|
+
openerFingerprint,
|
|
9790
9832
|
operationId: `send-opener-${digest.slice(0, 48)}`,
|
|
9791
9833
|
start: { operationId, body }
|
|
9792
9834
|
});
|
|
@@ -10119,7 +10161,7 @@ async function acquireAgentEndpoint(input) {
|
|
|
10119
10161
|
}
|
|
10120
10162
|
const requestedMls = input.options.mlsDev;
|
|
10121
10163
|
const mls = requestedMls ? await (async () => {
|
|
10122
|
-
const crypto2 = await import("./mls-crypto-
|
|
10164
|
+
const crypto2 = await import("./mls-crypto-Y44BOSEM.js");
|
|
10123
10165
|
const configuration = crypto2.parseMlsDevClientConfiguration(requestedMls.configuration);
|
|
10124
10166
|
if (configuration.environmentId !== environmentId || baseUrl !== CANON_RUNTIME_ENDPOINT_DEFAULTS[configuration.environmentId]?.apiBaseUrl || configuration.principalIds && !configuration.principalIds.includes(input.principalId) || !Number.isSafeInteger(input.ownershipVersion) || input.ownershipVersion < 0) throw new Error("MLS_DEV_AGENT_CONFIGURATION_BINDING_MISMATCH");
|
|
10125
10167
|
const custody = requestedMls.custody;
|
package/dist/cli.js
CHANGED
|
@@ -3,20 +3,19 @@ import {
|
|
|
3
3
|
MLS_SETUP_ACTIONS,
|
|
4
4
|
assertPrivateHome,
|
|
5
5
|
awaitErrand,
|
|
6
|
-
bundledMlsCustodyHelper,
|
|
7
6
|
checkCodex,
|
|
8
7
|
claudeDriver,
|
|
9
8
|
codeDriver,
|
|
10
9
|
codexDriver,
|
|
11
10
|
connectorDriver,
|
|
12
11
|
createRunner,
|
|
12
|
+
defaultMlsDevice,
|
|
13
13
|
errandJournal,
|
|
14
14
|
errandsDirectory,
|
|
15
15
|
loadClaudeQuery,
|
|
16
16
|
loadInstructions,
|
|
17
17
|
loadMlsDevConfiguration,
|
|
18
18
|
loadRelayConfiguration,
|
|
19
|
-
managedMlsDevice,
|
|
20
19
|
queueErrand,
|
|
21
20
|
readRelayCredential,
|
|
22
21
|
relayRunnerHome,
|
|
@@ -24,7 +23,7 @@ import {
|
|
|
24
23
|
runnerLockName,
|
|
25
24
|
startRelayChannel,
|
|
26
25
|
writeRelayCredential
|
|
27
|
-
} from "./chunks/chunk-
|
|
26
|
+
} from "./chunks/chunk-3KFVBOVI.js";
|
|
28
27
|
import {
|
|
29
28
|
CanonApiError,
|
|
30
29
|
CanonClient,
|
|
@@ -35,12 +34,11 @@ import {
|
|
|
35
34
|
resolveCanonRuntimeConnection,
|
|
36
35
|
runCli,
|
|
37
36
|
verifyCanonRuntimeConnection
|
|
38
|
-
} from "./chunks/chunk-
|
|
37
|
+
} from "./chunks/chunk-TFSURFF6.js";
|
|
39
38
|
import {
|
|
40
39
|
__name,
|
|
41
|
-
isCanonClientVersion
|
|
42
|
-
|
|
43
|
-
} from "./chunks/chunk-GWQF4WFT.js";
|
|
40
|
+
isCanonClientVersion
|
|
41
|
+
} from "./chunks/chunk-C2WJT3VX.js";
|
|
44
42
|
|
|
45
43
|
// src/cli.ts
|
|
46
44
|
import { realpathSync } from "node:fs";
|
|
@@ -464,9 +462,7 @@ async function settings(values, { key = true } = {}) {
|
|
|
464
462
|
__name(settings, "settings");
|
|
465
463
|
async function mlsDevice(values, environmentId, home) {
|
|
466
464
|
if (values["mls-dev-config"]) return loadMlsDevConfiguration(values["mls-dev-config"], { environmentId });
|
|
467
|
-
|
|
468
|
-
const helper = bundledMlsCustodyHelper();
|
|
469
|
-
return helper ? managedMlsDevice({ home, environmentId, helper }) : void 0;
|
|
465
|
+
return defaultMlsDevice(home, environmentId);
|
|
470
466
|
}
|
|
471
467
|
__name(mlsDevice, "mlsDevice");
|
|
472
468
|
async function mlsSetup(values) {
|
|
@@ -10,7 +10,7 @@ export interface ConnectorInbox {
|
|
|
10
10
|
/**
|
|
11
11
|
* The first claim gets `runAllowed` and the turn's input, once Canon confirms the agent may still act on it (its
|
|
12
12
|
* membership and the room's audience, read now, never from a hint); a repeat gets its state, never the input again.
|
|
13
|
-
* A claim
|
|
13
|
+
* A claim not confirmed by `deadline` (epoch ms) is busy; retry the same request id to finish that confirmation.
|
|
14
14
|
*/
|
|
15
15
|
claim(inputId: string, requestId: string, options?: {
|
|
16
16
|
deadline?: number;
|
|
@@ -27,8 +27,9 @@ export interface ConnectorDriver extends RuntimeDriver {
|
|
|
27
27
|
/**
|
|
28
28
|
* The `connector` driver: the runtime is an assistant its provider runs (ChatGPT, Claude.ai and the like), reached
|
|
29
29
|
* through Canon's remote MCP connector, so a turn waits, as one pending input, for the assistant to claim it and answer
|
|
30
|
-
* through the connector's pickup tools (spec §11.3).
|
|
31
|
-
*
|
|
32
|
-
*
|
|
30
|
+
* through the connector's pickup tools (spec §11.3). Such an assistant acts only when its user prompts it, so an input
|
|
31
|
+
* waits as long as it takes: the runner submits it, and starts its no-progress deadline, only at the claim. The driver
|
|
32
|
+
* keeps no timer. It never calls Canon itself; the relay channel calls its inbox, and the runner confirms (and submits)
|
|
33
|
+
* each claim with Canon.
|
|
33
34
|
*/
|
|
34
35
|
export declare function connectorDriver(): ConnectorDriver;
|
package/dist/index.js
CHANGED
|
@@ -21,7 +21,7 @@ import {
|
|
|
21
21
|
runMlsDevSetup,
|
|
22
22
|
runtimeDescriptor,
|
|
23
23
|
startRelayChannel
|
|
24
|
-
} from "./chunks/chunk-
|
|
24
|
+
} from "./chunks/chunk-3KFVBOVI.js";
|
|
25
25
|
import {
|
|
26
26
|
CanonApiError,
|
|
27
27
|
DEFAULT_ANTHROPIC_REQUEST_HEADROOM_BYTES,
|
|
@@ -46,8 +46,8 @@ import {
|
|
|
46
46
|
toAnthropicImageBlock,
|
|
47
47
|
toAnthropicImageBlocksWithinBudget,
|
|
48
48
|
uploadMediaFile
|
|
49
|
-
} from "./chunks/chunk-
|
|
50
|
-
import "./chunks/chunk-
|
|
49
|
+
} from "./chunks/chunk-TFSURFF6.js";
|
|
50
|
+
import "./chunks/chunk-C2WJT3VX.js";
|
|
51
51
|
export {
|
|
52
52
|
CanonApiError,
|
|
53
53
|
ConnectorRelayError,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { DirectConversationSelection } from './communication.js';
|
|
1
2
|
/**
|
|
2
3
|
* The class-C relay (docs/design/agent-runner.md §11): a Canon-operated process that relays a connector agent's
|
|
3
4
|
* encrypted rooms, holding an MLS device for the agent and decrypting and re-encrypting messages for the assistant (which
|
|
@@ -108,6 +109,14 @@ export type ConnectorRelayRequest = {
|
|
|
108
109
|
messageId: string;
|
|
109
110
|
text: string;
|
|
110
111
|
};
|
|
112
|
+
} | {
|
|
113
|
+
op: 'start';
|
|
114
|
+
args: {
|
|
115
|
+
targetUserId: string;
|
|
116
|
+
messageId: string;
|
|
117
|
+
text: string;
|
|
118
|
+
selection?: DirectConversationSelection;
|
|
119
|
+
};
|
|
111
120
|
} | {
|
|
112
121
|
op: 'credential';
|
|
113
122
|
args: ConnectorRelayCredentialDelivery;
|
|
@@ -172,6 +181,31 @@ export interface ConnectorRelaySent {
|
|
|
172
181
|
historyMessageId: string;
|
|
173
182
|
status: 'sent' | 'pending';
|
|
174
183
|
}
|
|
184
|
+
/**
|
|
185
|
+
* A start (§11.5): the relay's device founds (or finds) the encrypted room and sends the opening line itself, as any
|
|
186
|
+
* device does with a withheld opener. `messageId` is the start's identity, as for a send; `historyMessageId` is the
|
|
187
|
+
* opening line's id in the room. `starting`: the outcome is unresolved, including whether local recording completed;
|
|
188
|
+
* retry with the same `messageId` until a terminal answer. Canon's answers otherwise: `messaged` (the line is going into `conversationId`),
|
|
189
|
+
* `requested` or `pending` (it goes once the request is approved), `blocked` or `unavailable`.
|
|
190
|
+
*/
|
|
191
|
+
export type ConnectorRelayStarted = {
|
|
192
|
+
messageId: string;
|
|
193
|
+
historyMessageId: string;
|
|
194
|
+
} & ({
|
|
195
|
+
status: 'messaged';
|
|
196
|
+
conversationId: string;
|
|
197
|
+
created?: boolean;
|
|
198
|
+
reused?: boolean;
|
|
199
|
+
} | {
|
|
200
|
+
status: 'requested' | 'pending';
|
|
201
|
+
requestId: string;
|
|
202
|
+
conversationId?: string;
|
|
203
|
+
} | {
|
|
204
|
+
status: 'starting';
|
|
205
|
+
} | {
|
|
206
|
+
status: 'blocked' | 'unavailable';
|
|
207
|
+
reason: string;
|
|
208
|
+
});
|
|
175
209
|
export declare class ConnectorRelayRequestError extends Error {
|
|
176
210
|
name: string;
|
|
177
211
|
}
|
|
@@ -97,7 +97,7 @@ export interface EndpointCapabilities {
|
|
|
97
97
|
actions: string[];
|
|
98
98
|
queries: string[];
|
|
99
99
|
maintenance: boolean;
|
|
100
|
-
/** Only
|
|
100
|
+
/** Only while MLS is on here, where every valid principal is eligible: how new rooms are born (functions `devActivation.ts`). */
|
|
101
101
|
mls?: {
|
|
102
102
|
eligible: true;
|
|
103
103
|
roomMode: 'default';
|
|
@@ -67,6 +67,8 @@ export interface EndpointOperation {
|
|
|
67
67
|
preparedContent?: PreparedContent;
|
|
68
68
|
/** Authored-input collision check, never recorded for memory-only answers. */
|
|
69
69
|
inputFingerprint?: string;
|
|
70
|
+
/** Local-only binding of a withheld start's text; retained after the opener is sent or its payload is erased. */
|
|
71
|
+
openerFingerprint?: string;
|
|
70
72
|
receipt?: EndpointOperationReceipt;
|
|
71
73
|
attempts: number;
|
|
72
74
|
attempted: boolean;
|
|
@@ -196,7 +198,9 @@ export interface Endpoint {
|
|
|
196
198
|
text: string;
|
|
197
199
|
requestId?: string | null;
|
|
198
200
|
}): Promise<void>;
|
|
199
|
-
/**
|
|
201
|
+
/**
|
|
202
|
+
* Persist the opening line before starting a room; recover the start through its durable receipt.
|
|
203
|
+
*/
|
|
200
204
|
startWithheldOpener(input: Omit<StartDirectInput, 'opener'> & {
|
|
201
205
|
messageId: string;
|
|
202
206
|
text: string;
|
|
@@ -17,6 +17,11 @@ export interface MlsDevDeviceClientOptions {
|
|
|
17
17
|
/** Explicit synthetic Dev operations. No timers, eager enrollment or room authority. */
|
|
18
18
|
export interface MlsDevDeviceClient {
|
|
19
19
|
read(): Promise<MlsDevDeviceView>;
|
|
20
|
+
/** Read only committed local device state, without discovering or waiting for its remote alias.
|
|
21
|
+
* Returns undefined when no local device exists. Used to capture the device before logout erases it. */
|
|
22
|
+
read(options: {
|
|
23
|
+
localOnly: true;
|
|
24
|
+
}): Promise<MlsDevDeviceView | undefined>;
|
|
20
25
|
/** Ensure local registration; an existing view is not an online status check. */
|
|
21
26
|
enroll(): Promise<MlsDevDeviceView>;
|
|
22
27
|
renew(): Promise<MlsDevDeviceView>;
|
|
@@ -28,6 +33,10 @@ export interface MlsDevDeviceClient {
|
|
|
28
33
|
* current credential is valid, a failed renewal returns it and is retried
|
|
29
34
|
* after a minute. Never creates an unrequested enrollment. */
|
|
30
35
|
ensureReady(): Promise<MlsDevDeviceView>;
|
|
36
|
+
/** Retire this registered device at Canon for good (`POST /revoke`): it can no longer authenticate, renew or be offered
|
|
37
|
+
* to a room, and readiness refuses it. A device Canon never registered has
|
|
38
|
+
* nothing to revoke; a revoked one is returned as it is. Rooms keep its leaf until Canon can remove devices (step 5c). */
|
|
39
|
+
revoke(): Promise<MlsDevDeviceView>;
|
|
31
40
|
}
|
|
32
41
|
/** Whether readiness would renew this registered device: it has no credential, or less than half its lifetime remains. */
|
|
33
42
|
export declare function isMlsDevCredentialRenewalDue(view: MlsDevDeviceView, now: number): boolean;
|
package/dist/media.js
CHANGED
|
@@ -18,8 +18,8 @@ import {
|
|
|
18
18
|
toAnthropicImageBlock,
|
|
19
19
|
toAnthropicImageBlocksWithinBudget,
|
|
20
20
|
uploadMediaFile
|
|
21
|
-
} from "./chunks/chunk-
|
|
22
|
-
import "./chunks/chunk-
|
|
21
|
+
} from "./chunks/chunk-TFSURFF6.js";
|
|
22
|
+
import "./chunks/chunk-C2WJT3VX.js";
|
|
23
23
|
export {
|
|
24
24
|
DEFAULT_ANTHROPIC_REQUEST_HEADROOM_BYTES,
|
|
25
25
|
DEFAULT_MEDIA_CACHE_DIR,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type MlsDevDeviceContext, type MlsEnvironmentId } from '../internal/backend-contracts/index.js';
|
|
2
2
|
import type { RunnerMlsOptions } from './mls.js';
|
|
3
3
|
export interface ManagedMlsDeviceOptions {
|
|
4
4
|
/** The runner's home (CANON_HOME, or a relay installation's home). */
|
|
@@ -28,4 +28,9 @@ export type ManagedMlsDevice = RunnerMlsOptions & {
|
|
|
28
28
|
provisioned(context: MlsDevDeviceContext): boolean;
|
|
29
29
|
close(): Promise<void>;
|
|
30
30
|
};
|
|
31
|
+
/**
|
|
32
|
+
* A runner's device where it is given none (`createRunner` and the CLI alike): managed custody with this package's helper
|
|
33
|
+
* for the host, or undefined where it ships none or the environment has no MLS.
|
|
34
|
+
*/
|
|
35
|
+
export declare function defaultMlsDevice(home: string, environmentId: string): ManagedMlsDevice | undefined;
|
|
31
36
|
export declare function managedMlsDevice(options: ManagedMlsDeviceOptions): ManagedMlsDevice;
|
package/dist/runner/mls.d.ts
CHANGED
|
@@ -84,11 +84,17 @@ export declare function mlsDevRoomPackageId(identity: {
|
|
|
84
84
|
installationId: string;
|
|
85
85
|
ownershipVersion?: number;
|
|
86
86
|
}, room: string): string;
|
|
87
|
+
/**
|
|
88
|
+
* Managed custody opens only the vaults it made in its home. A device the engine store holds with no vault there (set up
|
|
89
|
+
* with an operator's configuration, or its vault lost) is refused once, here, rather than failing in every room.
|
|
90
|
+
*/
|
|
91
|
+
export declare function assertMlsVaultInHome(endpoint: CanonEndpoint, provisioned: (context: MlsDevDeviceContext) => boolean): Promise<void>;
|
|
87
92
|
/**
|
|
88
93
|
* One operator step for the agent's device (`canon-agent mls`), on the same engine store the runner uses. It never
|
|
89
94
|
* starts a model, and prints only public device state: no credentials, proofs, envelopes or history. `setup` does what
|
|
90
95
|
* is left of provision then enroll, so it also finishes a setup that stopped part way; it never replaces a vault, and
|
|
91
|
-
* with `provisioned` (managed custody) it provisions only where this installation has none
|
|
96
|
+
* with `provisioned` (managed custody) it provisions only where this installation has none, and refuses a device whose
|
|
97
|
+
* vault is elsewhere.
|
|
92
98
|
*/
|
|
93
99
|
export declare function runMlsDevSetup(endpoint: CanonEndpoint, action: MlsSetupAction, room?: string, provision?: (context: MlsDevDeviceContext) => Promise<void>, provisioned?: (context: MlsDevDeviceContext) => boolean): Promise<{
|
|
94
100
|
conversationId?: string | undefined;
|
package/dist/runner/relay.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { type ConnectorRelayCredentialDelivery } from '../internal/backend-contracts/index.js';
|
|
2
2
|
import type { ConnectorInbox } from '../drivers/connector.js';
|
|
3
3
|
import type { Runner } from './runner.js';
|
|
4
|
-
/** What the channel serves: the relay's runner (history and
|
|
4
|
+
/** What the channel serves: the relay's runner (history, sends and starts) and its connector driver's inbox (pickup). */
|
|
5
5
|
export interface RelayTarget {
|
|
6
|
-
runner: Pick<Runner, 'relayRead' | 'relaySend'>;
|
|
6
|
+
runner: Pick<Runner, 'relayRead' | 'relaySend' | 'relayStart'>;
|
|
7
7
|
inbox: ConnectorInbox;
|
|
8
8
|
}
|
|
9
9
|
/** Canon's credential for this relay, as delivered: the connection it serves is the delivering request's. */
|
|
@@ -12,7 +12,7 @@ export type RelayCredentialDelivery = ConnectorRelayCredentialDelivery & {
|
|
|
12
12
|
};
|
|
13
13
|
export interface RelayChannelOptions {
|
|
14
14
|
/** The runner and inbox to serve from the start; `serve` sets or replaces them. Until then only `credential` is answered. */
|
|
15
|
-
runner?: Pick<Runner, 'relayRead' | 'relaySend'>;
|
|
15
|
+
runner?: Pick<Runner, 'relayRead' | 'relaySend' | 'relayStart'>;
|
|
16
16
|
inbox?: ConnectorInbox;
|
|
17
17
|
/** The relayed agent: requests naming another agent are refused. */
|
|
18
18
|
agentId: string;
|
package/dist/runner/runner.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { AgentWakeConfiguration } from '../internal/backend-contracts/index.js';
|
|
2
|
-
import { type ConnectorRelayPage, type ConnectorRelaySent } from '../internal/backend-contracts/index.js';
|
|
2
|
+
import { type ConnectorRelayPage, type ConnectorRelaySent, type ConnectorRelayStarted, type DirectConversationSelection } from '../internal/backend-contracts/index.js';
|
|
3
3
|
import { type CanonEndpoint, type CanonRuntimeDescriptor, type CommunicateInput, type CommunicateResult } from '../internal/core/index.js';
|
|
4
4
|
import { type RunnerMlsOptions } from './mls.js';
|
|
5
5
|
import { type RuntimeDriver } from './types.js';
|
|
@@ -26,16 +26,18 @@ export interface RunnerOptions {
|
|
|
26
26
|
baseUrl?: string;
|
|
27
27
|
streamUrl?: string;
|
|
28
28
|
/**
|
|
29
|
-
* The agent's MLS device (
|
|
30
|
-
*
|
|
31
|
-
*
|
|
29
|
+
* The agent's MLS device (spec §6): issuer pins and custody, from `loadMlsDevConfiguration` or `managedMlsDevice`. Left
|
|
30
|
+
* out, it is managed custody in `home` where this package ships a custody helper for the host, as the CLI's; `false` is
|
|
31
|
+
* none. With a device the runner takes part in encrypted rooms; without one each encrypted room is refused, once, in the
|
|
32
|
+
* log. An injected `endpoint` brings its own device, if any. `close` runs last on shutdown.
|
|
32
33
|
*/
|
|
33
|
-
mls?: RunnerMlsOptions;
|
|
34
|
+
mls?: RunnerMlsOptions | false;
|
|
34
35
|
/**
|
|
35
36
|
* Relay mode, the class-C relay (spec §11.4): only encrypted rooms are served, which is what lets a `remote` driver
|
|
36
37
|
* (the connector) into them. Plaintext input is cancelled with no turn (the connector's own installation serves it),
|
|
37
|
-
* control hints are read only for encrypted rooms,
|
|
38
|
-
*
|
|
38
|
+
* control hints are read only for encrypted rooms, and `relayRead` and `relaySend` serve the relay channel. It publishes
|
|
39
|
+
* the runtime descriptor and heartbeat, so the apps show a relayed turn working and offer Stop. It needs the agent's MLS
|
|
40
|
+
* device.
|
|
39
41
|
*/
|
|
40
42
|
relay?: boolean;
|
|
41
43
|
/** The engine's installation: the relay's reserved one, from its configuration. A store opened under another refuses to start. */
|
|
@@ -95,6 +97,15 @@ export interface Runner {
|
|
|
95
97
|
relaySend(conversationId: string, messageId: string, text: string, options?: {
|
|
96
98
|
deadline?: number;
|
|
97
99
|
}): Promise<ConnectorRelaySent>;
|
|
100
|
+
/**
|
|
101
|
+
* Relay mode (§11.5): a conversation started with a person or agent, its opening line withheld from Canon and kept
|
|
102
|
+
* here first; this device founds (or finds) the encrypted room and sends the line. The same `messageId` is the same
|
|
103
|
+
* start. It answers by `deadline`: `starting` means unresolved, even if recording has not finished; retry the same
|
|
104
|
+
* identity until another answer arrives. Only a persisted start resumes automatically after a restart.
|
|
105
|
+
*/
|
|
106
|
+
relayStart(targetUserId: string, messageId: string, text: string, selection?: DirectConversationSelection, options?: {
|
|
107
|
+
deadline?: number;
|
|
108
|
+
}): Promise<ConnectorRelayStarted>;
|
|
98
109
|
}
|
|
99
110
|
/** RUN-27: generated once from the driver's declarations; no static capability table. */
|
|
100
111
|
export declare function runtimeDescriptor(driver: RuntimeDriver): CanonRuntimeDescriptor;
|
package/dist/runner/types.d.ts
CHANGED
|
@@ -36,8 +36,9 @@ export interface DriverTurn {
|
|
|
36
36
|
tools: TurnTools;
|
|
37
37
|
/**
|
|
38
38
|
* For a `remote` runtime, before its input first leaves this host (the connector's claim): the runner reads the
|
|
39
|
-
* agent's membership, and an encrypted room's audience, from Canon now
|
|
40
|
-
*
|
|
39
|
+
* agent's membership, and an encrypted room's audience, from Canon now, submits the input on the first confirmed
|
|
40
|
+
* pickup (until then it is only offered, with no deadline) and counts each pickup as progress (RUN-12). It rejects,
|
|
41
|
+
* and ends the turn, when the agent may no longer act on this input; a transient failure only rejects.
|
|
41
42
|
*/
|
|
42
43
|
confirmPickup(): Promise<void>;
|
|
43
44
|
}
|
|
@@ -65,7 +66,9 @@ export interface DriverCapabilities {
|
|
|
65
66
|
/**
|
|
66
67
|
* The runtime runs off the runner's host (the class-C connector, spec §11.3): it gets no local files (inbound
|
|
67
68
|
* attachments are named in the prompt, never materialized), and since its plaintext leaves this host, the runner
|
|
68
|
-
* takes it into encrypted rooms only in relay mode, whose installation discloses that.
|
|
69
|
+
* takes it into encrypted rooms only in relay mode, whose installation discloses that. Its input is offered first and
|
|
70
|
+
* submitted only when the runtime picks it up (`confirmPickup`); newer messages in the room join an input not yet
|
|
71
|
+
* picked up.
|
|
69
72
|
*/
|
|
70
73
|
remote?: boolean;
|
|
71
74
|
/** Across rooms; runner default 8. */
|