@canonmsg/agent-sdk 13.1.0 → 13.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/chunks/{chunk-ZPBOGOAD.js → chunk-5YKGDUJX.js} +271 -359
- package/dist/chunks/{chunk-IEKVS7AN.js → chunk-6FD6KFQN.js} +257 -93
- package/dist/cli.js +2 -2
- package/dist/index.js +2 -2
- package/dist/internal/core/client.d.ts +1 -215
- package/dist/internal/core/index.d.ts +0 -1
- package/dist/internal/endpoint/completions.d.ts +45 -0
- package/dist/internal/endpoint/engine.d.ts +15 -0
- package/dist/internal/endpoint/index.d.ts +1 -0
- package/dist/media.js +1 -1
- package/dist/runner/errands.d.ts +2 -2
- package/dist/runner/runner.d.ts +6 -1
- package/package.json +1 -1
- package/dist/internal/core/completed-output-journal.d.ts +0 -34
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
} from "./chunk-DTAWEQCM.js";
|
|
12
12
|
import {
|
|
13
13
|
BoundedFileReadError,
|
|
14
|
+
CanonApiError,
|
|
14
15
|
CanonClient,
|
|
15
16
|
MAX_CANON_MEDIA_BYTES,
|
|
16
17
|
RUNTIME_NEW_SESSION_ACTION,
|
|
@@ -18,7 +19,6 @@ import {
|
|
|
18
19
|
RUNTIME_STOP_AND_DROP_ACTION,
|
|
19
20
|
acquireLock,
|
|
20
21
|
conversationMediaCacheDir,
|
|
21
|
-
createCompletedOutputJournal,
|
|
22
22
|
createRuntimeHeartbeat,
|
|
23
23
|
createRuntimeStatePublisher,
|
|
24
24
|
inferUploadMimeType,
|
|
@@ -31,7 +31,7 @@ import {
|
|
|
31
31
|
resolveCanonRuntimeConnection,
|
|
32
32
|
splitTextWithinBytes,
|
|
33
33
|
verifyCanonRuntimeConnection
|
|
34
|
-
} from "./chunk-
|
|
34
|
+
} from "./chunk-5YKGDUJX.js";
|
|
35
35
|
import {
|
|
36
36
|
NodeMlsDevCustodyError,
|
|
37
37
|
createNodeMlsDevCustody,
|
|
@@ -1585,7 +1585,7 @@ ${name}`, `Errands: ${name} in the inbox is not a command file; it is left alone
|
|
|
1585
1585
|
}
|
|
1586
1586
|
const path = join2(inbox, name);
|
|
1587
1587
|
try {
|
|
1588
|
-
if (handler.taken(id) || await handler.take(id, readCommand(path, id))) unlinkSync(path);
|
|
1588
|
+
if (await handler.taken(id) || await handler.take(id, readCommand(path, id))) unlinkSync(path);
|
|
1589
1589
|
} catch (error) {
|
|
1590
1590
|
if (error.code === "ENOENT") continue;
|
|
1591
1591
|
const reason = error instanceof Error ? error.message : String(error);
|
|
@@ -2413,10 +2413,10 @@ function relayRunnerHome(home, installationId) {
|
|
|
2413
2413
|
__name(relayRunnerHome, "relayRunnerHome");
|
|
2414
2414
|
|
|
2415
2415
|
// src/runner/runner.ts
|
|
2416
|
-
import { createHash as
|
|
2417
|
-
import { readFileSync as
|
|
2416
|
+
import { createHash as createHash9, randomUUID as randomUUID2 } from "node:crypto";
|
|
2417
|
+
import { readFileSync as readFileSync7, rmSync as rmSync2 } from "node:fs";
|
|
2418
2418
|
import { realpath as realpath4, stat as stat2 } from "node:fs/promises";
|
|
2419
|
-
import { basename as basename4, isAbsolute as isAbsolute3, join as
|
|
2419
|
+
import { basename as basename4, isAbsolute as isAbsolute3, join as join8, sep } from "node:path";
|
|
2420
2420
|
|
|
2421
2421
|
// ../agent-tools/src/communication-tool.ts
|
|
2422
2422
|
var CANON_COMMUNICATE_DISCOVERY_MAX_RESULTS = 10;
|
|
@@ -2638,6 +2638,54 @@ async function executeCanonCommunicateTool(client, args) {
|
|
|
2638
2638
|
}
|
|
2639
2639
|
__name(executeCanonCommunicateTool, "executeCanonCommunicateTool");
|
|
2640
2640
|
|
|
2641
|
+
// src/runner/finals.ts
|
|
2642
|
+
import { createHash as createHash7 } from "node:crypto";
|
|
2643
|
+
import { readdirSync as readdirSync3, readFileSync as readFileSync5, rmdirSync, unlinkSync as unlinkSync2 } from "node:fs";
|
|
2644
|
+
import { join as join6 } from "node:path";
|
|
2645
|
+
var sha2562 = /* @__PURE__ */ __name((value) => createHash7("sha256").update(value).digest("hex"), "sha256");
|
|
2646
|
+
async function importFinals(directory, scope, input) {
|
|
2647
|
+
const folder = join6(directory, sha2562(JSON.stringify(scope)));
|
|
2648
|
+
let names;
|
|
2649
|
+
try {
|
|
2650
|
+
names = readdirSync3(folder);
|
|
2651
|
+
} catch {
|
|
2652
|
+
return;
|
|
2653
|
+
}
|
|
2654
|
+
for (const name of names.sort()) {
|
|
2655
|
+
const match = /^([a-f0-9]{64})(\.\d+\.[a-f0-9-]+\.tmp|\.json)$/.exec(name);
|
|
2656
|
+
if (!match) continue;
|
|
2657
|
+
const path = join6(folder, name);
|
|
2658
|
+
const torn = match[2] !== ".json";
|
|
2659
|
+
let record;
|
|
2660
|
+
try {
|
|
2661
|
+
record = JSON.parse(readFileSync5(path, "utf8"));
|
|
2662
|
+
} catch {
|
|
2663
|
+
record = void 0;
|
|
2664
|
+
}
|
|
2665
|
+
if (!input.valid(record) || sha2562(record.turnId) !== match[1]) {
|
|
2666
|
+
if (torn) {
|
|
2667
|
+
try {
|
|
2668
|
+
unlinkSync2(path);
|
|
2669
|
+
} catch (error) {
|
|
2670
|
+
input.warn(`torn output write ${name}`)(error);
|
|
2671
|
+
}
|
|
2672
|
+
} else input.warn(`saved output ${name}`)(new Error("it is not a valid output; it stays for operator reconciliation"));
|
|
2673
|
+
continue;
|
|
2674
|
+
}
|
|
2675
|
+
try {
|
|
2676
|
+
await input.save(record);
|
|
2677
|
+
unlinkSync2(path);
|
|
2678
|
+
} catch (error) {
|
|
2679
|
+
input.warn(`saved output ${name} stays where it was`)(error);
|
|
2680
|
+
}
|
|
2681
|
+
}
|
|
2682
|
+
try {
|
|
2683
|
+
rmdirSync(folder);
|
|
2684
|
+
} catch {
|
|
2685
|
+
}
|
|
2686
|
+
}
|
|
2687
|
+
__name(importFinals, "importFinals");
|
|
2688
|
+
|
|
2641
2689
|
// src/runner/live.ts
|
|
2642
2690
|
import { randomBytes as randomBytes3 } from "node:crypto";
|
|
2643
2691
|
var KEEPALIVE_MS = 3500;
|
|
@@ -2681,10 +2729,10 @@ function createLivePublisher(options) {
|
|
|
2681
2729
|
__name(createLivePublisher, "createLivePublisher");
|
|
2682
2730
|
|
|
2683
2731
|
// src/runner/mls-ask.ts
|
|
2684
|
-
import { createHash as
|
|
2685
|
-
import { readdirSync as
|
|
2686
|
-
import { join as
|
|
2687
|
-
var
|
|
2732
|
+
import { createHash as createHash8 } from "node:crypto";
|
|
2733
|
+
import { readdirSync as readdirSync4, readFileSync as readFileSync6, rmSync } from "node:fs";
|
|
2734
|
+
import { join as join7 } from "node:path";
|
|
2735
|
+
var sha2563 = /* @__PURE__ */ __name((value) => createHash8("sha256").update(value).digest("hex"), "sha256");
|
|
2688
2736
|
var pause = /* @__PURE__ */ __name((ms) => new Promise((resolve4) => {
|
|
2689
2737
|
setTimeout(resolve4, ms).unref?.();
|
|
2690
2738
|
}), "pause");
|
|
@@ -2738,10 +2786,10 @@ function createMlsAsks(options, approvalScope2) {
|
|
|
2738
2786
|
});
|
|
2739
2787
|
}
|
|
2740
2788
|
});
|
|
2741
|
-
const file = /* @__PURE__ */ __name((conversationId, requestId) =>
|
|
2789
|
+
const file = /* @__PURE__ */ __name((conversationId, requestId) => join7(options.directory, `${sha2563(JSON.stringify([conversationId, requestId])).slice(0, 32)}.json`), "file");
|
|
2742
2790
|
const save = /* @__PURE__ */ __name((journal) => writeDurable(file(journal.conversationId, journal.requestId), JSON.stringify(journal)), "save");
|
|
2743
2791
|
const forget = /* @__PURE__ */ __name((journal) => rmSync(file(journal.conversationId, journal.requestId), { force: true }), "forget");
|
|
2744
|
-
const operationOf = /* @__PURE__ */ __name((conversationId, messageId) => `runner-mls-ask:${
|
|
2792
|
+
const operationOf = /* @__PURE__ */ __name((conversationId, messageId) => `runner-mls-ask:${sha2563(JSON.stringify([conversationId, messageId])).slice(0, 48)}`, "operationOf");
|
|
2745
2793
|
const withdrawalOf = /* @__PURE__ */ __name((journal) => operationOf(journal.conversationId, `${journal.requestId}:withdraw`), "withdrawalOf");
|
|
2746
2794
|
const audienceChanged = /* @__PURE__ */ __name((error) => codeOf(error) === "MLS_DEV_AUDIENCE_CHANGED", "audienceChanged");
|
|
2747
2795
|
const sameHead = /* @__PURE__ */ __name((a, b) => JSON.stringify(a.head) === JSON.stringify(b.head), "sameHead");
|
|
@@ -3075,18 +3123,18 @@ function createMlsAsks(options, approvalScope2) {
|
|
|
3075
3123
|
async recover() {
|
|
3076
3124
|
let names;
|
|
3077
3125
|
try {
|
|
3078
|
-
names =
|
|
3126
|
+
names = readdirSync4(options.directory).filter((name) => name.endsWith(".json"));
|
|
3079
3127
|
} catch {
|
|
3080
3128
|
return;
|
|
3081
3129
|
}
|
|
3082
3130
|
for (const name of names) {
|
|
3083
3131
|
let journal;
|
|
3084
3132
|
try {
|
|
3085
|
-
journal = JSON.parse(
|
|
3133
|
+
journal = JSON.parse(readFileSync6(join7(options.directory, name), "utf8"));
|
|
3086
3134
|
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();
|
|
3087
3135
|
for (const sent of journal.sends) sent.audience = snapshotMlsAudience(sent.audience);
|
|
3088
3136
|
} catch {
|
|
3089
|
-
rmSync(
|
|
3137
|
+
rmSync(join7(options.directory, name), { force: true });
|
|
3090
3138
|
continue;
|
|
3091
3139
|
}
|
|
3092
3140
|
try {
|
|
@@ -3182,6 +3230,38 @@ __name(startToolServer, "startToolServer");
|
|
|
3182
3230
|
|
|
3183
3231
|
// src/runner/runner.ts
|
|
3184
3232
|
var STOPS = ["stop", "drop", "new_session", "membership", "audience"];
|
|
3233
|
+
var toCompletion = /* @__PURE__ */ __name((record) => ({
|
|
3234
|
+
version: 1,
|
|
3235
|
+
id: record.turnId,
|
|
3236
|
+
conversationId: record.conversationId,
|
|
3237
|
+
createdAt: record.createdAt,
|
|
3238
|
+
inputIds: record.inputs,
|
|
3239
|
+
fence: record.mls ?? null,
|
|
3240
|
+
output: {
|
|
3241
|
+
text: record.text,
|
|
3242
|
+
options: record.options,
|
|
3243
|
+
notice: record.notice,
|
|
3244
|
+
...record.items ? { items: record.items } : {},
|
|
3245
|
+
...record.parts !== void 0 ? { parts: record.parts } : {},
|
|
3246
|
+
...record.errand ? { errand: record.errand } : {},
|
|
3247
|
+
...record.proactive ? { proactive: true } : {},
|
|
3248
|
+
...record.sent ? { sent: record.sent } : {}
|
|
3249
|
+
}
|
|
3250
|
+
}), "toCompletion");
|
|
3251
|
+
var fromCompletion = /* @__PURE__ */ __name(({ id, conversationId, createdAt, inputIds, fence, output }) => ({
|
|
3252
|
+
turnId: id,
|
|
3253
|
+
conversationId,
|
|
3254
|
+
createdAt,
|
|
3255
|
+
inputs: inputIds,
|
|
3256
|
+
...output,
|
|
3257
|
+
...fence ? { mls: fence } : {}
|
|
3258
|
+
}), "fromCompletion");
|
|
3259
|
+
var isOutputFile = /* @__PURE__ */ __name((record) => {
|
|
3260
|
+
const value = record;
|
|
3261
|
+
return !!value && value.version === 1 && typeof value.turnId === "string" && !!value.turnId && typeof value.conversationId === "string" && !!value.conversationId && typeof value.createdAt === "string" && typeof value.text === "string" && typeof value.options?.messageId === "string" && Array.isArray(value.inputs) && value.inputs.every((id) => typeof id === "string" && !!id) && typeof value.notice === "boolean" && (value.mls === void 0 || !!value.mls && typeof value.mls === "object") && (value.parts === void 0 || Number.isSafeInteger(value.parts)) && (value.errand === void 0 || typeof value.errand === "string") && (value.proactive === void 0 || value.proactive === true) && (value.sent === void 0 || typeof value.sent?.fingerprint === "string" && ["delivered", "refused"].includes(value.sent.outcome));
|
|
3262
|
+
}, "isOutputFile");
|
|
3263
|
+
var textFingerprint = /* @__PURE__ */ __name((text3) => createHash9("sha256").update(JSON.stringify(["canon.runner.proactive-text.v1", text3])).digest("hex"), "textFingerprint");
|
|
3264
|
+
var PLAINTEXT_START = ["opening-text-required", "encrypted-room-required"];
|
|
3185
3265
|
var MAX_INPUTS_PER_TURN = 20;
|
|
3186
3266
|
var OUTBOUND_UNANSWERED = ["queued", "sending", "deferred", "pending"];
|
|
3187
3267
|
var INBOUND_UNSETTLED = ["observed", "deferred", "offered"];
|
|
@@ -3246,7 +3326,7 @@ function answerText(answer) {
|
|
|
3246
3326
|
return answer.text?.trim() ? answer.text : "They answered with an empty message.";
|
|
3247
3327
|
}
|
|
3248
3328
|
__name(answerText, "answerText");
|
|
3249
|
-
var
|
|
3329
|
+
var sha2564 = /* @__PURE__ */ __name((value) => createHash9("sha256").update(value).digest("hex"), "sha256");
|
|
3250
3330
|
var toolText = /* @__PURE__ */ __name((text3, isError = false) => ({ content: [{ type: "text", text: text3 }], ...isError ? { isError } : {} }), "toolText");
|
|
3251
3331
|
var turnEnded2 = /* @__PURE__ */ __name(() => new DOMException("This turn has ended", "AbortError"), "turnEnded");
|
|
3252
3332
|
function within(promise, ms) {
|
|
@@ -3347,7 +3427,7 @@ function approvalScope(request) {
|
|
|
3347
3427
|
if (request.scopeKey !== void 0 && /^[A-Za-z0-9_:-]{1,64}$/.test(request.scopeKey)) return request.scopeKey;
|
|
3348
3428
|
const key = request.scopeKey ?? request.title;
|
|
3349
3429
|
const slug = key.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "").slice(0, 40) || "approval";
|
|
3350
|
-
return `${slug}-${
|
|
3430
|
+
return `${slug}-${sha2564(JSON.stringify([request.scopeKey === void 0 ? "title" : "scope", key])).slice(0, 16)}`;
|
|
3351
3431
|
}
|
|
3352
3432
|
__name(approvalScope, "approvalScope");
|
|
3353
3433
|
var MAX_GROUP_PARTS = 100;
|
|
@@ -3457,7 +3537,6 @@ async function createRunner(options) {
|
|
|
3457
3537
|
let endpoint;
|
|
3458
3538
|
let inbound;
|
|
3459
3539
|
let heartbeat;
|
|
3460
|
-
let journal;
|
|
3461
3540
|
let agentId = "";
|
|
3462
3541
|
let scope = "";
|
|
3463
3542
|
let stopping;
|
|
@@ -3481,6 +3560,7 @@ async function createRunner(options) {
|
|
|
3481
3560
|
const ruleNotes = /* @__PURE__ */ new Set();
|
|
3482
3561
|
const guards = /* @__PURE__ */ new Map();
|
|
3483
3562
|
const relaySends = /* @__PURE__ */ new Map();
|
|
3563
|
+
const proactive = /* @__PURE__ */ new Map();
|
|
3484
3564
|
let errands;
|
|
3485
3565
|
let errandInbox2;
|
|
3486
3566
|
let errandScope = "";
|
|
@@ -3584,7 +3664,7 @@ async function createRunner(options) {
|
|
|
3584
3664
|
const dispose = /* @__PURE__ */ __name((inputs, kind, reason) => Promise.allSettled(inputs.map((input) => input.handle[kind](reason))).then(() => void 0), "dispose");
|
|
3585
3665
|
const settle = /* @__PURE__ */ __name(async (ids, reason) => (await Promise.allSettled(ids.map((id) => endpoint.setInboundState(id, "settled", reason)))).every(({ status }) => status === "fulfilled"), "settle");
|
|
3586
3666
|
const queued = /* @__PURE__ */ __name((turn) => turn.inputs.filter((input) => input.handle.phase === "queued"), "queued");
|
|
3587
|
-
const turnId = /* @__PURE__ */ __name((roomId, inputs) => `turn-${
|
|
3667
|
+
const turnId = /* @__PURE__ */ __name((roomId, inputs) => `turn-${sha2564(JSON.stringify([roomId, ...inputs.map((input) => [input.id, input.generation])])).slice(0, 32)}`, "turnId");
|
|
3588
3668
|
const livePublisher = /* @__PURE__ */ __name((current, turn) => createLivePublisher({
|
|
3589
3669
|
client,
|
|
3590
3670
|
conversationId: current.id,
|
|
@@ -3593,7 +3673,7 @@ async function createRunner(options) {
|
|
|
3593
3673
|
current.lane = current.lane.then(publish2).catch(() => void 0);
|
|
3594
3674
|
}, "enqueue")
|
|
3595
3675
|
}), "livePublisher");
|
|
3596
|
-
const stateFile = /* @__PURE__ */ __name((session) =>
|
|
3676
|
+
const stateFile = /* @__PURE__ */ __name((session) => join8(home, "runner", options.environmentId, agentId, "state", driver.name, `${sha2564(JSON.stringify([scope, session]))}.json`), "stateFile");
|
|
3597
3677
|
const resetSession = /* @__PURE__ */ __name(async (session) => {
|
|
3598
3678
|
rmSync2(stateFile(session), { force: true });
|
|
3599
3679
|
mlsAsks?.endSession(session);
|
|
@@ -4061,7 +4141,7 @@ ${decision.reasonCode}`;
|
|
|
4061
4141
|
return check;
|
|
4062
4142
|
}
|
|
4063
4143
|
__name(reaudience, "reaudience");
|
|
4064
|
-
const inboundMedia = /* @__PURE__ */ __name((conversationId) => ({ agentId: "inbound", conversationId, rootDir:
|
|
4144
|
+
const inboundMedia = /* @__PURE__ */ __name((conversationId) => ({ agentId: "inbound", conversationId, rootDir: join8(home, "runner", options.environmentId, agentId) }), "inboundMedia");
|
|
4065
4145
|
async function materialize(current, turn) {
|
|
4066
4146
|
if (driver.capabilities.remote) return;
|
|
4067
4147
|
for (const { payload: { message: message2 } } of turn.inputs) {
|
|
@@ -4112,7 +4192,7 @@ ${decision.reasonCode}`;
|
|
|
4112
4192
|
ask_question: /* @__PURE__ */ __name(async (args) => {
|
|
4113
4193
|
const question = questionOf(args);
|
|
4114
4194
|
if (typeof question === "string") return { result: toolText(question, true) };
|
|
4115
|
-
const key =
|
|
4195
|
+
const key = sha2564(JSON.stringify(question));
|
|
4116
4196
|
const waiting = questions.get(key);
|
|
4117
4197
|
const entry = waiting?.waiting ? waiting : { id: `canon:ask_question:${asked += 1}`, waiting: true };
|
|
4118
4198
|
questions.set(key, entry);
|
|
@@ -4195,7 +4275,7 @@ ${decision.reasonCode}`;
|
|
|
4195
4275
|
}
|
|
4196
4276
|
}
|
|
4197
4277
|
if (errand && "contact" in errand.target && (command.action === "start_conversation" || command.action === "start_direct") && command.principalId === errand.target.contact) {
|
|
4198
|
-
const result = await
|
|
4278
|
+
const result = await startConversation({ ...command, action: "start_conversation", selection: { mode: "new" } });
|
|
4199
4279
|
if (result.status === "messaged") reach(turn, result.conversationId);
|
|
4200
4280
|
return toolText(`communicate: ${result.status}
|
|
4201
4281
|
${JSON.stringify(result, null, 2)}`);
|
|
@@ -4211,20 +4291,18 @@ ${JSON.stringify(result, null, 2)}`);
|
|
|
4211
4291
|
turn.said.set(messageId, sending = (async () => {
|
|
4212
4292
|
const mls2 = audience ?? (await currentAudience(roomId)).audience;
|
|
4213
4293
|
const record = {
|
|
4214
|
-
version: 1,
|
|
4215
4294
|
turnId: messageId,
|
|
4216
4295
|
conversationId: roomId,
|
|
4217
4296
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4218
|
-
audience: null,
|
|
4219
4297
|
inputs: [],
|
|
4220
4298
|
text: text3,
|
|
4221
4299
|
options: { messageId },
|
|
4222
4300
|
notice: false,
|
|
4223
4301
|
mls: mls2
|
|
4224
4302
|
};
|
|
4225
|
-
saveOutput(record);
|
|
4303
|
+
await saveOutput(record);
|
|
4226
4304
|
const sent2 = await publish(record, turn.retire.signal);
|
|
4227
|
-
if (sent2 !== "pending")
|
|
4305
|
+
if (sent2 !== "pending") await endpoint.finishCompletion(messageId);
|
|
4228
4306
|
else if (turn.retire.signal.aborted) await stopOutput(record);
|
|
4229
4307
|
return sent2;
|
|
4230
4308
|
})().catch((error) => {
|
|
@@ -4308,11 +4386,9 @@ ${JSON.stringify(result, null, 2)}`);
|
|
|
4308
4386
|
const kind = failed ? "failed" : "final";
|
|
4309
4387
|
const replyAuthority = await replyGrant(current.id, turn.inputs.at(-1).payload.message.id, stopped);
|
|
4310
4388
|
const record = {
|
|
4311
|
-
version: 1,
|
|
4312
4389
|
turnId: turn.id,
|
|
4313
4390
|
conversationId: current.id,
|
|
4314
4391
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4315
|
-
audience: null,
|
|
4316
4392
|
inputs: turn.inputs.map((input) => input.id),
|
|
4317
4393
|
text: text3,
|
|
4318
4394
|
...items.length ? { items } : {},
|
|
@@ -4320,7 +4396,7 @@ ${JSON.stringify(result, null, 2)}`);
|
|
|
4320
4396
|
notice: failed,
|
|
4321
4397
|
mls: audience
|
|
4322
4398
|
};
|
|
4323
|
-
saveOutput(record);
|
|
4399
|
+
await saveOutput(record);
|
|
4324
4400
|
sent = await publish(record, turn.retire.signal);
|
|
4325
4401
|
if (sent === "pending" && !stopped()) return turn.live.end(turn.reason && STOPS.includes(turn.reason) ? "stopped" : "ended");
|
|
4326
4402
|
if (sent === "pending") await stopOutput(record);
|
|
@@ -4334,7 +4410,7 @@ ${JSON.stringify(result, null, 2)}`);
|
|
|
4334
4410
|
if (current.session === turn.session) current.session = void 0;
|
|
4335
4411
|
}
|
|
4336
4412
|
const reason = stopped() ? turn.reason : silent ? "silent" : answered2 ? "answered" : "failed";
|
|
4337
|
-
|
|
4413
|
+
await endpoint.finishCompletion(turn.id, { inputIds: turn.inputs.map((input) => input.id), reason }).catch(warn("reply kept for restart recovery"));
|
|
4338
4414
|
}
|
|
4339
4415
|
__name(deliver, "deliver");
|
|
4340
4416
|
const forOwner2 = /* @__PURE__ */ __name((turn) => turn.inputs.every(({ payload: { message: message2 } }) => message2.isOwner === true || message2.senderType === "ai_agent" && turn.trusted.has(message2.id)), "forOwner");
|
|
@@ -4364,11 +4440,16 @@ ${JSON.stringify(result, null, 2)}`);
|
|
|
4364
4440
|
}
|
|
4365
4441
|
__name(send, "send");
|
|
4366
4442
|
const partsOf = /* @__PURE__ */ __name((text3, options2) => encryptedParts(text3, options2.replyAuthority ? MAX_REPLY_PARTS : MAX_GROUP_PARTS), "partsOf");
|
|
4367
|
-
function saveOutput(record) {
|
|
4443
|
+
async function saveOutput(record) {
|
|
4368
4444
|
record.parts = partsOf(record.text, record.options).length;
|
|
4369
|
-
|
|
4445
|
+
await endpoint.saveCompletion(toCompletion(record));
|
|
4370
4446
|
}
|
|
4371
4447
|
__name(saveOutput, "saveOutput");
|
|
4448
|
+
const outputs = /* @__PURE__ */ __name(async () => (await endpoint.listCompletions()).map(fromCompletion).filter((record) => !record.sent), "outputs");
|
|
4449
|
+
const savedOutput = /* @__PURE__ */ __name(async (turnId2) => {
|
|
4450
|
+
const found = await endpoint.getCompletion(turnId2);
|
|
4451
|
+
return found && fromCompletion(found);
|
|
4452
|
+
}, "savedOutput");
|
|
4372
4453
|
function encryptedMessages(mls2, text3, options2, record) {
|
|
4373
4454
|
const all = record && record.parts === void 0 ? [fitEncrypted(text3)] : partsOf(text3, options2);
|
|
4374
4455
|
return all.slice(0, record?.parts).map((part, index) => ({
|
|
@@ -4384,11 +4465,9 @@ ${JSON.stringify(result, null, 2)}`);
|
|
|
4384
4465
|
const status = (await endpoint.getOperation(encryptedOperation(record.mls, messageId)))?.status;
|
|
4385
4466
|
if (status && status !== "succeeded" && status !== "rejected") queued2 = index + 1;
|
|
4386
4467
|
}
|
|
4387
|
-
|
|
4388
|
-
|
|
4389
|
-
|
|
4390
|
-
}
|
|
4391
|
-
journal.remove(record.turnId);
|
|
4468
|
+
const { items: _items, errand: _errand, ...rest } = record;
|
|
4469
|
+
if (queued2) await endpoint.replaceCompletion(record.turnId, toCompletion({ ...rest, turnId: `${record.turnId}:stopped`, inputs: [], parts: queued2 }));
|
|
4470
|
+
else await endpoint.finishCompletion(record.turnId);
|
|
4392
4471
|
}
|
|
4393
4472
|
__name(stopOutput, "stopOutput");
|
|
4394
4473
|
async function sendEncrypted(audience, messageId, text3, signal, replyAuthority) {
|
|
@@ -4484,7 +4563,7 @@ ${JSON.stringify(result, null, 2)}`);
|
|
|
4484
4563
|
return { items, refused: refused2 };
|
|
4485
4564
|
}
|
|
4486
4565
|
__name(admitItems, "admitItems");
|
|
4487
|
-
const encryptedOperation = /* @__PURE__ */ __name((audience, messageId) => `runner-mls:${
|
|
4566
|
+
const encryptedOperation = /* @__PURE__ */ __name((audience, messageId) => `runner-mls:${sha2564(JSON.stringify([audience.conversationId, messageId])).slice(0, 48)}`, "encryptedOperation");
|
|
4488
4567
|
function forgetGuard(audience, messageId, sent) {
|
|
4489
4568
|
if (sent === "pending") return;
|
|
4490
4569
|
const operationId = encryptedOperation(audience, messageId);
|
|
@@ -4622,7 +4701,7 @@ ${JSON.stringify(result, null, 2)}`);
|
|
|
4622
4701
|
const file = stateFile(session);
|
|
4623
4702
|
let data = {};
|
|
4624
4703
|
try {
|
|
4625
|
-
data = JSON.parse(
|
|
4704
|
+
data = JSON.parse(readFileSync7(file, "utf8"));
|
|
4626
4705
|
} catch {
|
|
4627
4706
|
}
|
|
4628
4707
|
const save = /* @__PURE__ */ __name((next) => {
|
|
@@ -4693,9 +4772,8 @@ ${JSON.stringify(result, null, 2)}`);
|
|
|
4693
4772
|
__name(scheduleControl, "scheduleControl");
|
|
4694
4773
|
async function recover() {
|
|
4695
4774
|
await mlsAsks?.recover().catch(warn("pending encrypted cards"));
|
|
4696
|
-
const records =
|
|
4697
|
-
const
|
|
4698
|
-
const orphans = (await endpoint.listInbound({ states: ["uncertain"] })).filter(({ event }) => event.kind === "message.created" && event.conversationId && !answered2.has(event.id));
|
|
4775
|
+
const records = await outputs();
|
|
4776
|
+
const orphans = (await endpoint.listInbound({ states: ["uncertain"], unanswered: true })).filter(({ event }) => event.kind === "message.created" && event.conversationId);
|
|
4699
4777
|
for (const roomId of /* @__PURE__ */ new Set([...records.map((record) => record.conversationId), ...orphans.map(({ event }) => event.conversationId)])) {
|
|
4700
4778
|
const target = room(roomId);
|
|
4701
4779
|
const work = (async () => {
|
|
@@ -4725,14 +4803,14 @@ ${JSON.stringify(result, null, 2)}`);
|
|
|
4725
4803
|
async function redeliver(record) {
|
|
4726
4804
|
if (!record.mls) {
|
|
4727
4805
|
if (record.errand) return finishErrandOutput(record, "refused", "its conversation is not encrypted");
|
|
4728
|
-
|
|
4729
|
-
return;
|
|
4806
|
+
return endpoint.finishCompletion(record.turnId, { inputIds: record.inputs, reason: "plaintext-output-dropped" });
|
|
4730
4807
|
}
|
|
4731
4808
|
if (!mlsOn) return refuse(record.conversationId, "no-device");
|
|
4732
4809
|
const sent = await publish(record);
|
|
4733
4810
|
if (sent === "pending") return;
|
|
4734
4811
|
if (record.errand) return finishErrandOutput(record, sent);
|
|
4735
|
-
if (
|
|
4812
|
+
if (record.proactive) return finishProactive(record, sent);
|
|
4813
|
+
await endpoint.finishCompletion(record.turnId, { inputIds: record.inputs, reason: "recovered" });
|
|
4736
4814
|
}
|
|
4737
4815
|
__name(redeliver, "redeliver");
|
|
4738
4816
|
function pendingNotes(roomId) {
|
|
@@ -4824,25 +4902,12 @@ ${JSON.stringify(result, null, 2)}`);
|
|
|
4824
4902
|
log(`Errand ${id} (tell) kept for the next turn in ${conversationId}.`);
|
|
4825
4903
|
return true;
|
|
4826
4904
|
}
|
|
4827
|
-
const turnId2 = `turn-${
|
|
4828
|
-
const record = {
|
|
4829
|
-
version: 1,
|
|
4830
|
-
turnId: turnId2,
|
|
4831
|
-
conversationId,
|
|
4832
|
-
createdAt,
|
|
4833
|
-
audience: null,
|
|
4834
|
-
inputs: [],
|
|
4835
|
-
text: parsed.text,
|
|
4836
|
-
options: { messageId: keyed().text("final", conversationId, turnId2) },
|
|
4837
|
-
notice: false,
|
|
4838
|
-
errand: id,
|
|
4839
|
-
mls: target.mls
|
|
4840
|
-
};
|
|
4841
|
-
saveOutput(record);
|
|
4905
|
+
const turnId2 = `turn-${sha2564(JSON.stringify(["canon.runner.errand", id])).slice(0, 32)}`;
|
|
4906
|
+
const record = await outsideTurn({ conversationId, turnId: turnId2, text: parsed.text, mls: target.mls, createdAt, errand: id });
|
|
4842
4907
|
errands.put({ ...base, status: "sending", updatedAt });
|
|
4843
|
-
const delivery = publish(record).then((sent) => {
|
|
4844
|
-
if (sent !== "pending") finishErrandOutput(record, sent);
|
|
4845
|
-
}
|
|
4908
|
+
const delivery = publish(record).then(async (sent) => {
|
|
4909
|
+
if (sent !== "pending") await finishErrandOutput(record, sent);
|
|
4910
|
+
}).catch(warn(`errand ${id}`));
|
|
4846
4911
|
posting.add(delivery);
|
|
4847
4912
|
touch();
|
|
4848
4913
|
void delivery.finally(() => {
|
|
@@ -4863,10 +4928,12 @@ ${JSON.stringify(result, null, 2)}`);
|
|
|
4863
4928
|
const mode = await endpoint.getConversationContentMode(roomId).catch(missing);
|
|
4864
4929
|
if (mode === "plaintext") return "it is not encrypted, and the agent takes part only in encrypted conversations";
|
|
4865
4930
|
if (mode !== "mls-dev-text" || !mlsOn) return "it is an encrypted conversation this runner cannot take part in";
|
|
4866
|
-
|
|
4931
|
+
if (kind !== "post") return {};
|
|
4932
|
+
const { audience, ownerId } = await currentAudience(roomId);
|
|
4933
|
+
return { mls: audience, ownerRoom: conversation.memberIds.length === 2 && conversation.memberIds.includes(ownerId) };
|
|
4867
4934
|
}
|
|
4868
4935
|
__name(errandRoom, "errandRoom");
|
|
4869
|
-
function finishErrandOutput(record, sent, reason = "Canon did not accept it") {
|
|
4936
|
+
async function finishErrandOutput(record, sent, reason = "Canon did not accept it") {
|
|
4870
4937
|
const id = record.errand;
|
|
4871
4938
|
const known = latest(id) ?? { version: 1, id, kind: "post", conversationId: record.conversationId, createdAt: record.createdAt };
|
|
4872
4939
|
const ok = sent === "delivered";
|
|
@@ -4877,9 +4944,108 @@ ${JSON.stringify(result, null, 2)}`);
|
|
|
4877
4944
|
errands?.put({ ...known, status: ok ? "sent" : "refused", ...ok ? {} : { reason }, updatedAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
4878
4945
|
log(`Errand ${id} (post) ${ok ? "sent" : `refused: ${reason}`} in ${record.conversationId}.`);
|
|
4879
4946
|
}
|
|
4880
|
-
|
|
4947
|
+
await endpoint.finishCompletion(record.turnId);
|
|
4881
4948
|
}
|
|
4882
4949
|
__name(finishErrandOutput, "finishErrandOutput");
|
|
4950
|
+
async function outsideTurn(input) {
|
|
4951
|
+
const { conversationId, turnId: turnId2, text: text3, mls: mls2, errand } = input;
|
|
4952
|
+
const record = {
|
|
4953
|
+
turnId: turnId2,
|
|
4954
|
+
conversationId,
|
|
4955
|
+
createdAt: input.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
4956
|
+
inputs: [],
|
|
4957
|
+
text: text3,
|
|
4958
|
+
options: { messageId: keyed().text("final", conversationId, turnId2) },
|
|
4959
|
+
notice: false,
|
|
4960
|
+
...errand ? { errand } : {},
|
|
4961
|
+
...input.proactive ? { proactive: true } : {},
|
|
4962
|
+
mls: mls2
|
|
4963
|
+
};
|
|
4964
|
+
await saveOutput(record);
|
|
4965
|
+
return record;
|
|
4966
|
+
}
|
|
4967
|
+
__name(outsideTurn, "outsideTurn");
|
|
4968
|
+
const refusal = /* @__PURE__ */ __name((status, code, message2) => new CanonApiError(status, JSON.stringify({ code, message: message2 })), "refusal");
|
|
4969
|
+
function sendOutside(command) {
|
|
4970
|
+
const { conversationId, text: text3 } = command;
|
|
4971
|
+
if (command.privateContext !== void 0 || command.sourceLabel !== void 0) {
|
|
4972
|
+
return Promise.reject(refusal(400, "ENCRYPTED_TEXT_ONLY", "Encrypted text carries no private context or source label"));
|
|
4973
|
+
}
|
|
4974
|
+
const turnId2 = `turn-${sha2564(JSON.stringify(["canon.runner.communicate", conversationId, command.messageId ?? randomUUID2()])).slice(0, 32)}`;
|
|
4975
|
+
const conflict = /* @__PURE__ */ __name(() => refusal(409, "MESSAGE_ID_CONFLICT", "This messageId was already sent with other text"), "conflict");
|
|
4976
|
+
const known = proactive.get(turnId2);
|
|
4977
|
+
if (known) return known.text === text3 ? known.result : Promise.reject(conflict());
|
|
4978
|
+
const result = (async () => {
|
|
4979
|
+
let record = await savedOutput(turnId2);
|
|
4980
|
+
const done = record ? void 0 : await savedOutput(`${turnId2}:sent`);
|
|
4981
|
+
if (record ? record.text !== text3 : done && done.sent.fingerprint !== textFingerprint(text3)) throw conflict();
|
|
4982
|
+
if (done) return sendResult(done.sent.outcome, conversationId, done.options.messageId);
|
|
4983
|
+
if (!record) {
|
|
4984
|
+
const room2 = await errandRoom(conversationId, "post");
|
|
4985
|
+
if (typeof room2 === "string") throw refusal(403, "CONVERSATION_UNAVAILABLE", `This runner cannot send to ${conversationId}: ${room2}`);
|
|
4986
|
+
if (outboundClosed && !room2.ownerRoom) {
|
|
4987
|
+
throw refusal(403, "OUTBOUND_NOT_ALLOWED", "This agent's outbound policy is closed: it answers messages, and writes unprompted only to its owner");
|
|
4988
|
+
}
|
|
4989
|
+
record = await outsideTurn({ conversationId, turnId: turnId2, text: text3, mls: room2.mls, proactive: command.messageId !== void 0 });
|
|
4990
|
+
}
|
|
4991
|
+
const sent = await publish(record);
|
|
4992
|
+
if (sent !== "pending") await finishProactive(record, sent);
|
|
4993
|
+
return sendResult(sent, conversationId, record.options.messageId);
|
|
4994
|
+
})().finally(() => proactive.delete(turnId2));
|
|
4995
|
+
proactive.set(turnId2, { text: text3, result });
|
|
4996
|
+
return result;
|
|
4997
|
+
}
|
|
4998
|
+
__name(sendOutside, "sendOutside");
|
|
4999
|
+
async function finishProactive(record, outcome) {
|
|
5000
|
+
const id = `${record.turnId}:sent`;
|
|
5001
|
+
if (!record.proactive || await savedOutput(id)) return endpoint.finishCompletion(record.turnId);
|
|
5002
|
+
await endpoint.replaceCompletion(record.turnId, toCompletion({
|
|
5003
|
+
turnId: id,
|
|
5004
|
+
conversationId: record.conversationId,
|
|
5005
|
+
createdAt: record.createdAt,
|
|
5006
|
+
inputs: [],
|
|
5007
|
+
text: "",
|
|
5008
|
+
options: { messageId: record.options.messageId },
|
|
5009
|
+
notice: false,
|
|
5010
|
+
sent: { fingerprint: textFingerprint(record.text), outcome }
|
|
5011
|
+
}));
|
|
5012
|
+
}
|
|
5013
|
+
__name(finishProactive, "finishProactive");
|
|
5014
|
+
function sendResult(sent, conversationId, messageId) {
|
|
5015
|
+
if (sent === "delivered") return { status: "messaged", conversationId, messageId };
|
|
5016
|
+
if (sent === "refused") throw refusal(409, "MESSAGE_REFUSED", "Canon did not accept the message; the conversation's members may have changed");
|
|
5017
|
+
throw refusal(202, "OPERATION_PENDING", "The message is saved and will be sent when Canon is reachable; call again with the same messageId to learn how it went");
|
|
5018
|
+
}
|
|
5019
|
+
__name(sendResult, "sendResult");
|
|
5020
|
+
async function startConversation(command) {
|
|
5021
|
+
if (!mlsOn) throw refusal(403, "MLS_DEVICE_REQUIRED", "This runner has no MLS device, so it can start no conversation");
|
|
5022
|
+
const input = {
|
|
5023
|
+
targetUserId: command.principalId,
|
|
5024
|
+
text: command.text,
|
|
5025
|
+
messageId: command.messageId ?? randomUUID2(),
|
|
5026
|
+
selection: command.selection ?? (command.action === "start_conversation" ? { mode: "new" } : { mode: "latest_or_new" })
|
|
5027
|
+
};
|
|
5028
|
+
const result = await endpoint.startWithheldOpener(input).catch(async (error) => {
|
|
5029
|
+
const { code, operationId } = error;
|
|
5030
|
+
if (code !== "OPERATION_PENDING" || typeof operationId !== "string") throw error;
|
|
5031
|
+
const receipt = await endpoint.waitForOperation(operationId);
|
|
5032
|
+
if (receipt.status !== "succeeded" || !receipt.result) throw error;
|
|
5033
|
+
return receipt.result;
|
|
5034
|
+
});
|
|
5035
|
+
if ("reason" in result) {
|
|
5036
|
+
if (PLAINTEXT_START.includes(result.reason)) throw refusal(403, "ENCRYPTED_ROOM_REQUIRED", "Canon would make that conversation plaintext, so nothing was sent");
|
|
5037
|
+
return { status: result.status, reason: result.reason };
|
|
5038
|
+
}
|
|
5039
|
+
if ("requestId" in result) return { status: result.status, requestId: result.requestId };
|
|
5040
|
+
return {
|
|
5041
|
+
status: "messaged",
|
|
5042
|
+
conversationId: result.conversationId,
|
|
5043
|
+
messageId: result.messageId ?? input.messageId,
|
|
5044
|
+
...result.created !== void 0 ? { created: result.created } : {},
|
|
5045
|
+
...result.reused !== void 0 ? { reused: result.reused } : {}
|
|
5046
|
+
};
|
|
5047
|
+
}
|
|
5048
|
+
__name(startConversation, "startConversation");
|
|
4883
5049
|
const latest = /* @__PURE__ */ __name((id) => errandsById.get(id) ?? errands?.get(id), "latest");
|
|
4884
5050
|
const openErrand = /* @__PURE__ */ __name((record) => record.kind === "errand" && !!record.conversationId && !["pending", "cancelled", "expired", "refused", "rejected"].includes(record.status), "openErrand");
|
|
4885
5051
|
const aliveErrand = /* @__PURE__ */ __name((record) => record.kind === "errand" && (["pending", "running"].includes(record.status) || openErrand(record)), "aliveErrand");
|
|
@@ -4898,7 +5064,7 @@ ${JSON.stringify(result, null, 2)}`);
|
|
|
4898
5064
|
pump();
|
|
4899
5065
|
}
|
|
4900
5066
|
__name(enqueueErrand, "enqueueErrand");
|
|
4901
|
-
const errandTurnId = /* @__PURE__ */ __name((id) => `turn-${
|
|
5067
|
+
const errandTurnId = /* @__PURE__ */ __name((id) => `turn-${sha2564(JSON.stringify(["canon.runner.errand-turn", id])).slice(0, 32)}`, "errandTurnId");
|
|
4902
5068
|
const errandFacts = /* @__PURE__ */ __name((record) => Object.freeze({
|
|
4903
5069
|
id: record.id,
|
|
4904
5070
|
why: record.why,
|
|
@@ -5080,11 +5246,9 @@ ${JSON.stringify(result, null, 2)}`);
|
|
|
5080
5246
|
}
|
|
5081
5247
|
const kind = failed ? "failed" : "final";
|
|
5082
5248
|
const out = {
|
|
5083
|
-
version: 1,
|
|
5084
5249
|
turnId: turn.id,
|
|
5085
5250
|
conversationId: origin.room,
|
|
5086
5251
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5087
|
-
audience: null,
|
|
5088
5252
|
inputs: [],
|
|
5089
5253
|
text: text3,
|
|
5090
5254
|
notice: failed,
|
|
@@ -5093,7 +5257,7 @@ ${JSON.stringify(result, null, 2)}`);
|
|
|
5093
5257
|
options: { messageId: keyed().text(kind, origin.room, turn.id) }
|
|
5094
5258
|
};
|
|
5095
5259
|
try {
|
|
5096
|
-
saveOutput(out);
|
|
5260
|
+
await saveOutput(out);
|
|
5097
5261
|
} catch (error) {
|
|
5098
5262
|
warn(`errand ${record.id}: its report could not be journaled; it stays in the record`)(error);
|
|
5099
5263
|
saveErrand({ ...closing, report: text3, reported: "refused", reason: "its report: it could not be saved for sending" });
|
|
@@ -5105,7 +5269,7 @@ ${JSON.stringify(result, null, 2)}`);
|
|
|
5105
5269
|
await stopOutput(out);
|
|
5106
5270
|
const { reported: _reported, ...withheld2 } = latest(record.id) ?? closing;
|
|
5107
5271
|
saveErrand({ ...withheld2, status: "cancelled", reason: STOPPED_ERRAND[turn.reason] ?? "stopped" });
|
|
5108
|
-
} else if (sent !== "pending") finishErrandOutput(out, sent);
|
|
5272
|
+
} else if (sent !== "pending") await finishErrandOutput(out, sent);
|
|
5109
5273
|
turn.live.end(sent === "delivered" ? "answered" : stopped() ? "stopped" : "ended");
|
|
5110
5274
|
}
|
|
5111
5275
|
__name(deliverReport, "deliverReport");
|
|
@@ -5179,7 +5343,7 @@ ${JSON.stringify(result, null, 2)}`);
|
|
|
5179
5343
|
if (await endpoint.getConversationContentMode(conversationId).catch(() => "unavailable") !== "mls-dev-text") {
|
|
5180
5344
|
throw new ConnectorRelayError(403, "CONNECTOR_RELAY_NOT_ENCRYPTED", "The relay serves only encrypted conversations its agent is in");
|
|
5181
5345
|
}
|
|
5182
|
-
const journaled =
|
|
5346
|
+
const journaled = await savedOutput(id);
|
|
5183
5347
|
if (journaled) return { text: journaled.text, record: journaled };
|
|
5184
5348
|
const { messages } = await readAgain(() => endpoint.query(
|
|
5185
5349
|
"get_messages",
|
|
@@ -5190,22 +5354,20 @@ ${JSON.stringify(result, null, 2)}`);
|
|
|
5190
5354
|
const { audience } = await relayRoom(conversationId);
|
|
5191
5355
|
if (Date.now() >= deadline) throw relayBusy();
|
|
5192
5356
|
const record = {
|
|
5193
|
-
version: 1,
|
|
5194
5357
|
turnId: id,
|
|
5195
5358
|
conversationId,
|
|
5196
5359
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5197
|
-
audience: null,
|
|
5198
5360
|
inputs: [],
|
|
5199
5361
|
text: text3,
|
|
5200
5362
|
options: { messageId: id },
|
|
5201
5363
|
notice: false,
|
|
5202
5364
|
mls: audience
|
|
5203
5365
|
};
|
|
5204
|
-
saveOutput(record);
|
|
5366
|
+
await saveOutput(record);
|
|
5205
5367
|
return { text: text3, record };
|
|
5206
5368
|
})();
|
|
5207
|
-
const sent = ready.then(({ record, settled }) => settled ?? publish(record).then((result) => {
|
|
5208
|
-
if (result !== "pending")
|
|
5369
|
+
const sent = ready.then(({ record, settled }) => settled ?? publish(record).then(async (result) => {
|
|
5370
|
+
if (result !== "pending") await endpoint.finishCompletion(id);
|
|
5209
5371
|
return result;
|
|
5210
5372
|
}));
|
|
5211
5373
|
const entry = { ready, sent };
|
|
@@ -5230,7 +5392,7 @@ ${JSON.stringify(result, null, 2)}`);
|
|
|
5230
5392
|
client = new CanonClient(options.apiKey, connection.apiBaseUrl, {
|
|
5231
5393
|
environmentId: connection.environmentId,
|
|
5232
5394
|
streamUrl: connection.streamUrl,
|
|
5233
|
-
stateDirectory:
|
|
5395
|
+
stateDirectory: join8(home, "endpoint"),
|
|
5234
5396
|
...options.client ? { client: options.client } : {},
|
|
5235
5397
|
// Below Canon's client floor nothing can work, wherever the refusal surfaces: a query, an operation or the stream.
|
|
5236
5398
|
...options.onFatal ? { onClientUpdateRequired: /* @__PURE__ */ __name((error) => options.onFatal(error), "onClientUpdateRequired") } : {},
|
|
@@ -5278,29 +5440,30 @@ ${JSON.stringify(result, null, 2)}`);
|
|
|
5278
5440
|
}
|
|
5279
5441
|
scope = JSON.stringify([identity.environmentId, agentId, identity.ownershipVersion ?? 0, identity.installationId]);
|
|
5280
5442
|
if (mlsOn) {
|
|
5281
|
-
mlsIds = createMlsIds(privateKey(
|
|
5443
|
+
mlsIds = createMlsIds(privateKey(join8(home, "runner", options.environmentId, agentId, "mls-ids", `${sha2564(scope).slice(0, 32)}.key`), MLS_ID_KEY_BYTES));
|
|
5282
5444
|
mlsAsks = createMlsAsks({
|
|
5283
5445
|
endpoint,
|
|
5284
5446
|
agentId,
|
|
5285
|
-
directory:
|
|
5447
|
+
directory: join8(home, "runner", options.environmentId, agentId, "asks", sha2564(scope).slice(0, 32)),
|
|
5286
5448
|
assertAudience,
|
|
5287
5449
|
sessionForDestructive: options.sessionApprovalForDestructive === true,
|
|
5288
5450
|
log
|
|
5289
5451
|
}, approvalScope);
|
|
5290
5452
|
}
|
|
5291
|
-
|
|
5292
|
-
|
|
5293
|
-
|
|
5294
|
-
|
|
5453
|
+
await importFinals(
|
|
5454
|
+
join8(home, "runner", options.environmentId, agentId, "finals"),
|
|
5455
|
+
scope,
|
|
5456
|
+
{ valid: isOutputFile, save: /* @__PURE__ */ __name((record) => endpoint.saveCompletion(toCompletion(record)), "save"), warn }
|
|
5457
|
+
);
|
|
5295
5458
|
if (!options.relay && !driver.capabilities.remote) {
|
|
5296
5459
|
errands = errandJournal(errandsDirectory(home, options.environmentId, agentId));
|
|
5297
|
-
errandScope =
|
|
5460
|
+
errandScope = sha2564(scope).slice(0, 32);
|
|
5298
5461
|
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));
|
|
5299
5462
|
for (const note of pending) {
|
|
5300
5463
|
if (note.scope === errandScope) notes.set(note.conversationId, [...notes.get(note.conversationId) ?? [], note]);
|
|
5301
5464
|
else settleNote(note.conversationId, note, "expired", void 0, "it was left under another owner or installation");
|
|
5302
5465
|
}
|
|
5303
|
-
const reports = new Map(
|
|
5466
|
+
const reports = new Map((await outputs()).flatMap((output) => output.errand ? [[output.errand, output]] : []));
|
|
5304
5467
|
for (const record of errands.list().filter(aliveErrand)) {
|
|
5305
5468
|
const report = reports.get(record.id);
|
|
5306
5469
|
if (record.scope !== errandScope) saveErrand({ ...record, status: "expired", reason: "it was left under another owner or installation" });
|
|
@@ -5340,11 +5503,11 @@ ${JSON.stringify(result, null, 2)}`);
|
|
|
5340
5503
|
onError: warn("event stream")
|
|
5341
5504
|
}));
|
|
5342
5505
|
if (errands) {
|
|
5343
|
-
const journaled = /* @__PURE__ */ __name((id) =>
|
|
5506
|
+
const journaled = /* @__PURE__ */ __name(async (id) => (await outputs()).some((record) => record.errand === id), "journaled");
|
|
5344
5507
|
try {
|
|
5345
5508
|
errandInbox2 = watchErrandInbox(
|
|
5346
5509
|
errandsDirectory(home, options.environmentId, agentId),
|
|
5347
|
-
{ taken: /* @__PURE__ */ __name((id) => errands.has(id) || journaled(id), "taken"), take: takeErrand },
|
|
5510
|
+
{ taken: /* @__PURE__ */ __name(async (id) => errands.has(id) || journaled(id), "taken"), take: takeErrand },
|
|
5348
5511
|
{ log }
|
|
5349
5512
|
);
|
|
5350
5513
|
} catch (error) {
|
|
@@ -5391,7 +5554,8 @@ ${JSON.stringify(result, null, 2)}`);
|
|
|
5391
5554
|
},
|
|
5392
5555
|
async communicate(input) {
|
|
5393
5556
|
if (!client) throw new Error("Start the runner before sending");
|
|
5394
|
-
|
|
5557
|
+
const command = parseCommunicateToolInput(input);
|
|
5558
|
+
return activeCall(() => command.action === "message_existing" ? sendOutside(command) : command.action === "start_conversation" || command.action === "start_direct" ? startConversation(command) : client.communicate(command));
|
|
5395
5559
|
},
|
|
5396
5560
|
async relayRead(conversationId, page = {}) {
|
|
5397
5561
|
touch();
|
|
@@ -5418,7 +5582,7 @@ ${JSON.stringify(result, null, 2)}`);
|
|
|
5418
5582
|
throw new ConnectorRelayError(400, "CONNECTOR_RELAY_INVALID", "A send needs a messageId and 1 byte to 32 KiB of text");
|
|
5419
5583
|
}
|
|
5420
5584
|
const deadline = options2.deadline ?? Date.now() + CONNECTOR_RELAY_ANSWER_WITHIN_MS;
|
|
5421
|
-
const id = `relay-send-${
|
|
5585
|
+
const id = `relay-send-${sha2564(JSON.stringify([conversationId, messageId])).slice(0, 32)}`;
|
|
5422
5586
|
let entry = relaySends.get(id);
|
|
5423
5587
|
if (!entry) relaySends.set(id, entry = prepareRelaySend(conversationId, id, text3, deadline));
|
|
5424
5588
|
const prepared = await beforeDeadline(activeCall(() => entry.ready), deadline);
|
|
@@ -5435,7 +5599,7 @@ ${JSON.stringify(result, null, 2)}`);
|
|
|
5435
5599
|
if (!options.relay || !client) throw new ConnectorRelayError(403, "CONNECTOR_RELAY_UNAVAILABLE", "This runner is not a relay");
|
|
5436
5600
|
if (!mlsOn) throw new ConnectorRelayError(503, "CONNECTOR_RELAY_NO_DEVICE", "The relay has no MLS device");
|
|
5437
5601
|
const deadline = startOptions.deadline ?? Date.now() + CONNECTOR_RELAY_ANSWER_WITHIN_MS;
|
|
5438
|
-
const identity = { messageId, historyMessageId: `relay-start-${
|
|
5602
|
+
const identity = { messageId, historyMessageId: `relay-start-${sha2564(messageId).slice(0, 32)}` };
|
|
5439
5603
|
const answer = await within(
|
|
5440
5604
|
activeCall(() => endpoint.startWithheldOpener({
|
|
5441
5605
|
targetUserId,
|
|
@@ -5460,7 +5624,7 @@ ${JSON.stringify(result, null, 2)}`);
|
|
|
5460
5624
|
}
|
|
5461
5625
|
const result = answer.result;
|
|
5462
5626
|
if ("reason" in result) {
|
|
5463
|
-
if (result.reason
|
|
5627
|
+
if (PLAINTEXT_START.includes(result.reason)) {
|
|
5464
5628
|
throw new ConnectorRelayError(403, "CONNECTOR_RELAY_NOT_ENCRYPTED", "The relay serves only encrypted conversations its agent is in");
|
|
5465
5629
|
}
|
|
5466
5630
|
return { ...identity, status: result.status, reason: result.reason };
|