@canonmsg/agent-sdk 12.6.1 → 13.0.2
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 +23 -21
- package/dist/chunks/{chunk-S7BV74DN.js → chunk-5WPAB5MB.js} +293 -8
- package/dist/chunks/{chunk-DAJ7L7TN.js → chunk-DNHJGAEL.js} +378 -663
- package/dist/chunks/{chunk-HH7KR22P.js → chunk-DTAWEQCM.js} +2 -2
- package/dist/chunks/{chunk-5J4YPLNW.js → chunk-TYZT346O.js} +266 -1217
- package/dist/chunks/{chunk-QK37HAHC.js → chunk-YHDDOADK.js} +1 -1
- package/dist/chunks/{chunk-FVYKLH6I.js → chunk-YJUPKU47.js} +20 -5
- package/dist/chunks/{mls-crypto-4DZT2UFH.js → mls-crypto-NZVMSUFU.js} +2 -2
- package/dist/cli.js +32 -36
- package/dist/drivers/subprocess.d.ts +4 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +6 -6
- package/dist/internal/backend-contracts/mlsDevGroup.d.ts +17 -0
- package/dist/internal/core/completed-output-journal.d.ts +0 -5
- package/dist/internal/core/index.d.ts +3 -9
- package/dist/internal/core/retry.d.ts +2 -89
- package/dist/internal/core/silent-turn.d.ts +4 -33
- package/dist/internal/endpoint/engine.d.ts +3 -0
- package/dist/internal/endpoint/mls-dev-groups.d.ts +12 -0
- package/dist/internal/endpoint/mls-dev-text.d.ts +29 -0
- package/dist/internal/endpoint/transport.d.ts +1 -1
- package/dist/media.js +4 -4
- package/dist/mls-wasm-host.js +3 -3
- package/dist/runner/mls.d.ts +4 -7
- package/dist/runner/runner.d.ts +9 -12
- package/dist/runner/types.d.ts +31 -0
- package/package.json +1 -1
- package/dist/internal/core/approval-manager.d.ts +0 -43
- package/dist/internal/core/runtime-request-manager.d.ts +0 -283
- package/dist/internal/core/turn-verbosity.d.ts +0 -79
|
@@ -8,9 +8,8 @@ import {
|
|
|
8
8
|
wasmMlsCustody,
|
|
9
9
|
windowsAclProblems,
|
|
10
10
|
writeDurable
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-DTAWEQCM.js";
|
|
12
12
|
import {
|
|
13
|
-
ApprovalManager,
|
|
14
13
|
BoundedFileReadError,
|
|
15
14
|
CanonClient,
|
|
16
15
|
MAX_CANON_MEDIA_BYTES,
|
|
@@ -18,8 +17,6 @@ import {
|
|
|
18
17
|
RUNTIME_STOP_ACTION,
|
|
19
18
|
RUNTIME_STOP_AND_DROP_ACTION,
|
|
20
19
|
acquireLock,
|
|
21
|
-
assertCompletedOutputAudience,
|
|
22
|
-
buildChunkedMessagePartMetadata,
|
|
23
20
|
conversationMediaCacheDir,
|
|
24
21
|
createCompletedOutputJournal,
|
|
25
22
|
createRuntimeHeartbeat,
|
|
@@ -27,26 +24,22 @@ import {
|
|
|
27
24
|
inferUploadMimeType,
|
|
28
25
|
initRTDBAuth,
|
|
29
26
|
isPendingCanonOperation,
|
|
30
|
-
isRetryableCanonDeliveryError,
|
|
31
27
|
materializeAttachment,
|
|
32
28
|
normalizeRuntimeCommandDescriptors,
|
|
33
29
|
readBoundedRegularFileHandle,
|
|
34
30
|
renderCanonHostInboundContent,
|
|
35
|
-
reportNoReplyOutcome,
|
|
36
31
|
resolveCanonRuntimeConnection,
|
|
37
|
-
|
|
38
|
-
sendMessageWithRetryChunked,
|
|
39
|
-
splitTextByUtf8Bytes,
|
|
32
|
+
splitTextWithinBytes,
|
|
40
33
|
verifyCanonRuntimeConnection
|
|
41
|
-
} from "./chunk-
|
|
34
|
+
} from "./chunk-TYZT346O.js";
|
|
42
35
|
import {
|
|
43
36
|
NodeMlsDevCustodyError,
|
|
44
37
|
createNodeMlsDevCustody,
|
|
45
38
|
createPortableNodeMlsDevCustody
|
|
46
|
-
} from "./chunk-
|
|
39
|
+
} from "./chunk-YJUPKU47.js";
|
|
47
40
|
import {
|
|
48
41
|
parseMlsDevClientConfiguration
|
|
49
|
-
} from "./chunk-
|
|
42
|
+
} from "./chunk-YHDDOADK.js";
|
|
50
43
|
import {
|
|
51
44
|
CONNECTOR_RELAY_ANSWER_WITHIN_MS,
|
|
52
45
|
CONNECTOR_RELAY_INSTALLATION_PATTERN,
|
|
@@ -60,9 +53,11 @@ import {
|
|
|
60
53
|
MLS_DEV_TEXT_MAX_BYTES,
|
|
61
54
|
MLS_DEV_TEXT_PROTOCOL_V3,
|
|
62
55
|
NO_REPLY_ACK_NOTE,
|
|
63
|
-
|
|
56
|
+
PARTICIPATION_HISTORY_FETCH_LIMIT,
|
|
64
57
|
__name,
|
|
58
|
+
buildParticipationHistorySnapshot,
|
|
65
59
|
connectorRelayResponseBytes,
|
|
60
|
+
evaluateParticipationPolicy,
|
|
66
61
|
isConnectorRelayCredential,
|
|
67
62
|
isConnectorRelayInstallation,
|
|
68
63
|
isMlsDevTextProtocol,
|
|
@@ -73,8 +68,9 @@ import {
|
|
|
73
68
|
parseMlsDevCredentialToken,
|
|
74
69
|
parseMlsDevGroupHead,
|
|
75
70
|
parseMlsDevGroupRoster,
|
|
76
|
-
resolveAllowedUploadMimeType
|
|
77
|
-
|
|
71
|
+
resolveAllowedUploadMimeType,
|
|
72
|
+
resolveConversationPolicyScope
|
|
73
|
+
} from "./chunk-5WPAB5MB.js";
|
|
78
74
|
|
|
79
75
|
// src/runner/types.ts
|
|
80
76
|
var DriverSessionError = class extends Error {
|
|
@@ -225,6 +221,7 @@ async function approve(turn, request) {
|
|
|
225
221
|
return answer.scope === "session" ? "session" : "once";
|
|
226
222
|
}
|
|
227
223
|
__name(approve, "approve");
|
|
224
|
+
var PIPE_GRACE_MS = 1e3;
|
|
228
225
|
function stdioRpc(child, handlers, { name, envelope }) {
|
|
229
226
|
let next = 0;
|
|
230
227
|
const pending = /* @__PURE__ */ new Map();
|
|
@@ -258,6 +255,12 @@ function stdioRpc(child, handlers, { name, envelope }) {
|
|
|
258
255
|
} else handlers.notify(message2.method, params);
|
|
259
256
|
});
|
|
260
257
|
child.stderr?.resume();
|
|
258
|
+
child.once("exit", () => {
|
|
259
|
+
setTimeout(() => {
|
|
260
|
+
child.stdout?.destroy();
|
|
261
|
+
child.stderr?.destroy();
|
|
262
|
+
}, PIPE_GRACE_MS).unref();
|
|
263
|
+
});
|
|
261
264
|
return {
|
|
262
265
|
call: /* @__PURE__ */ __name((method, params) => new Promise((resolve3, reject) => {
|
|
263
266
|
next += 1;
|
|
@@ -283,7 +286,6 @@ var AUTH_REQUIRED = -32e3;
|
|
|
283
286
|
var SYSTEM = "You are an agent in Canon, a messaging app where people and AI agents talk. Each prompt holds the new messages in one conversation. Your final message in a turn is posted there as your reply, so put your whole answer in it. Canon's tools are on the MCP server whose name starts with canon_. To ask the people in the conversation a question, call its ask_question tool and wait for the answer. When you have nothing to add, call its no_reply tool instead of answering.";
|
|
284
287
|
var GUEST = "Only your owner can let you use tools in this conversation, and this turn holds a message from someone else: do not read files, run commands or use any tool but those of the MCP server whose name starts with canon_. Answer from the conversation and what you already know, and say so when something needs your owner.";
|
|
285
288
|
var OWNER_ONLY = "This agent only takes messages from its owner.";
|
|
286
|
-
var PIPE_GRACE_MS = 1e3;
|
|
287
289
|
var KILL_AFTER_MS = 2e3;
|
|
288
290
|
var MACHINE_KINDS = /* @__PURE__ */ new Set(["read", "edit", "delete", "move", "search", "execute", "fetch"]);
|
|
289
291
|
var BRIDGE = `
|
|
@@ -537,10 +539,6 @@ ${text(item.newText)}`);
|
|
|
537
539
|
}, KILL_AFTER_MS);
|
|
538
540
|
});
|
|
539
541
|
}
|
|
540
|
-
setTimeout(() => {
|
|
541
|
-
child.stdout?.destroy();
|
|
542
|
-
child.stderr?.destroy();
|
|
543
|
-
}, PIPE_GRACE_MS).unref();
|
|
544
542
|
});
|
|
545
543
|
child.once("error", (error) => gone(new Error(`Could not run ${command}: ${error.message}`)));
|
|
546
544
|
child.once("close", () => gone(new Error(`${agent} exited before finishing the turn`)));
|
|
@@ -1701,7 +1699,7 @@ function createMlsIds(key) {
|
|
|
1701
1699
|
}
|
|
1702
1700
|
__name(createMlsIds, "createMlsIds");
|
|
1703
1701
|
function requireMlsSupport(input) {
|
|
1704
|
-
if (!isMlsEnvironmentId(input.environmentId)
|
|
1702
|
+
if (!isMlsEnvironmentId(input.environmentId)) fail();
|
|
1705
1703
|
}
|
|
1706
1704
|
__name(requireMlsSupport, "requireMlsSupport");
|
|
1707
1705
|
function mentionsAgent(text3, names) {
|
|
@@ -2421,153 +2419,7 @@ import { realpath as realpath4, stat as stat2 } from "node:fs/promises";
|
|
|
2421
2419
|
import { basename as basename4, isAbsolute as isAbsolute3, join as join7, sep } from "node:path";
|
|
2422
2420
|
|
|
2423
2421
|
// ../agent-tools/src/communication-tool.ts
|
|
2424
|
-
var CANON_COMMUNICATE_TOOL_NAME = "communicate";
|
|
2425
2422
|
var CANON_COMMUNICATE_DISCOVERY_MAX_RESULTS = 10;
|
|
2426
|
-
var NON_EMPTY_ID = { type: "string", minLength: 1, maxLength: 160 };
|
|
2427
|
-
var MESSAGE_TEXT = { type: "string", minLength: 1, maxLength: 4096 };
|
|
2428
|
-
var OPTIONAL_CAPTION = { type: "string", maxLength: 4096 };
|
|
2429
|
-
var DIRECT_SELECTION_SCHEMA = {
|
|
2430
|
-
oneOf: [
|
|
2431
|
-
{
|
|
2432
|
-
type: "object",
|
|
2433
|
-
required: ["mode"],
|
|
2434
|
-
additionalProperties: false,
|
|
2435
|
-
properties: { mode: { const: "latest_or_new" } }
|
|
2436
|
-
},
|
|
2437
|
-
{
|
|
2438
|
-
type: "object",
|
|
2439
|
-
required: ["mode"],
|
|
2440
|
-
additionalProperties: false,
|
|
2441
|
-
properties: { mode: { const: "new" } }
|
|
2442
|
-
},
|
|
2443
|
-
{
|
|
2444
|
-
type: "object",
|
|
2445
|
-
required: ["mode", "conversationId"],
|
|
2446
|
-
additionalProperties: false,
|
|
2447
|
-
properties: {
|
|
2448
|
-
mode: { const: "specific" },
|
|
2449
|
-
conversationId: NON_EMPTY_ID
|
|
2450
|
-
}
|
|
2451
|
-
}
|
|
2452
|
-
]
|
|
2453
|
-
};
|
|
2454
|
-
function canonCommunicateToolDefinition(name = CANON_COMMUNICATE_TOOL_NAME) {
|
|
2455
|
-
return {
|
|
2456
|
-
name,
|
|
2457
|
-
description: "Find agents, list current conversations with their participants, message an explicit conversation, start_conversation with a person or agent (new by default), create_conversation with an optional title, manage_participants, leave, forward a message or share a contact. Check the current participants before selecting an existing conversation. Canon enforces admission and operator permissions.",
|
|
2458
|
-
inputSchema: {
|
|
2459
|
-
type: "object",
|
|
2460
|
-
oneOf: [
|
|
2461
|
-
{ type: "object", required: ["action"], additionalProperties: false, properties: { action: { const: "list_conversations" }, limit: { type: "integer", minimum: 1, maximum: 100 } } },
|
|
2462
|
-
{ type: "object", required: ["action", "conversationId"], additionalProperties: false, properties: { action: { const: "leave_conversation" }, conversationId: NON_EMPTY_ID } },
|
|
2463
|
-
{
|
|
2464
|
-
type: "object",
|
|
2465
|
-
required: ["action"],
|
|
2466
|
-
additionalProperties: false,
|
|
2467
|
-
properties: {
|
|
2468
|
-
action: { const: "discover_agents" },
|
|
2469
|
-
query: {
|
|
2470
|
-
type: "string",
|
|
2471
|
-
maxLength: MAX_DISCOVER_AGENTS_QUERY_LENGTH,
|
|
2472
|
-
description: "Optional name or description prefix. Empty lists the directory."
|
|
2473
|
-
},
|
|
2474
|
-
limit: {
|
|
2475
|
-
type: "integer",
|
|
2476
|
-
minimum: 1,
|
|
2477
|
-
maximum: CANON_COMMUNICATE_DISCOVERY_MAX_RESULTS,
|
|
2478
|
-
default: CANON_COMMUNICATE_DISCOVERY_MAX_RESULTS
|
|
2479
|
-
},
|
|
2480
|
-
cursor: {
|
|
2481
|
-
type: "string",
|
|
2482
|
-
minLength: 1,
|
|
2483
|
-
description: "Opaque cursor from a prior discovery action with the same query."
|
|
2484
|
-
}
|
|
2485
|
-
}
|
|
2486
|
-
},
|
|
2487
|
-
{
|
|
2488
|
-
type: "object",
|
|
2489
|
-
required: ["action", "conversationId", "text"],
|
|
2490
|
-
additionalProperties: false,
|
|
2491
|
-
properties: {
|
|
2492
|
-
action: { const: "message_existing" },
|
|
2493
|
-
conversationId: NON_EMPTY_ID,
|
|
2494
|
-
text: MESSAGE_TEXT,
|
|
2495
|
-
messageId: NON_EMPTY_ID
|
|
2496
|
-
}
|
|
2497
|
-
},
|
|
2498
|
-
{
|
|
2499
|
-
type: "object",
|
|
2500
|
-
required: ["action", "principalId", "text"],
|
|
2501
|
-
additionalProperties: false,
|
|
2502
|
-
properties: {
|
|
2503
|
-
action: { enum: ["start_conversation", "start_direct"] },
|
|
2504
|
-
principalId: NON_EMPTY_ID,
|
|
2505
|
-
text: MESSAGE_TEXT,
|
|
2506
|
-
selection: DIRECT_SELECTION_SCHEMA,
|
|
2507
|
-
messageId: NON_EMPTY_ID
|
|
2508
|
-
}
|
|
2509
|
-
},
|
|
2510
|
-
{
|
|
2511
|
-
type: "object",
|
|
2512
|
-
required: ["action", "memberIds"],
|
|
2513
|
-
additionalProperties: false,
|
|
2514
|
-
properties: {
|
|
2515
|
-
action: { enum: ["create_conversation", "create_group"] },
|
|
2516
|
-
name: { type: "string", minLength: 1, maxLength: VERB_LIMITS.groupNameChars },
|
|
2517
|
-
memberIds: {
|
|
2518
|
-
type: "array",
|
|
2519
|
-
minItems: 1,
|
|
2520
|
-
maxItems: VERB_LIMITS.groupMembers - 1,
|
|
2521
|
-
uniqueItems: true,
|
|
2522
|
-
items: NON_EMPTY_ID
|
|
2523
|
-
}
|
|
2524
|
-
}
|
|
2525
|
-
},
|
|
2526
|
-
{
|
|
2527
|
-
type: "object",
|
|
2528
|
-
required: [
|
|
2529
|
-
"action",
|
|
2530
|
-
"sourceConversationId",
|
|
2531
|
-
"targetConversationId",
|
|
2532
|
-
"messageId"
|
|
2533
|
-
],
|
|
2534
|
-
additionalProperties: false,
|
|
2535
|
-
properties: {
|
|
2536
|
-
action: { const: "forward_message" },
|
|
2537
|
-
sourceConversationId: NON_EMPTY_ID,
|
|
2538
|
-
targetConversationId: NON_EMPTY_ID,
|
|
2539
|
-
messageId: NON_EMPTY_ID,
|
|
2540
|
-
text: OPTIONAL_CAPTION
|
|
2541
|
-
}
|
|
2542
|
-
},
|
|
2543
|
-
{
|
|
2544
|
-
type: "object",
|
|
2545
|
-
required: ["action", "conversationId", "contactUserId"],
|
|
2546
|
-
additionalProperties: false,
|
|
2547
|
-
properties: {
|
|
2548
|
-
action: { const: "share_contact" },
|
|
2549
|
-
conversationId: NON_EMPTY_ID,
|
|
2550
|
-
contactUserId: NON_EMPTY_ID,
|
|
2551
|
-
text: OPTIONAL_CAPTION,
|
|
2552
|
-
messageId: NON_EMPTY_ID
|
|
2553
|
-
}
|
|
2554
|
-
},
|
|
2555
|
-
{
|
|
2556
|
-
type: "object",
|
|
2557
|
-
required: ["action", "conversationId", "userId", "operation"],
|
|
2558
|
-
additionalProperties: false,
|
|
2559
|
-
properties: {
|
|
2560
|
-
action: { enum: ["manage_participants", "manage_group_members"] },
|
|
2561
|
-
conversationId: NON_EMPTY_ID,
|
|
2562
|
-
userId: NON_EMPTY_ID,
|
|
2563
|
-
operation: { enum: ["add", "remove"] }
|
|
2564
|
-
}
|
|
2565
|
-
}
|
|
2566
|
-
]
|
|
2567
|
-
}
|
|
2568
|
-
};
|
|
2569
|
-
}
|
|
2570
|
-
__name(canonCommunicateToolDefinition, "canonCommunicateToolDefinition");
|
|
2571
2423
|
function isRecord3(value) {
|
|
2572
2424
|
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
2573
2425
|
}
|
|
@@ -2788,124 +2640,41 @@ __name(executeCanonCommunicateTool, "executeCanonCommunicateTool");
|
|
|
2788
2640
|
|
|
2789
2641
|
// src/runner/live.ts
|
|
2790
2642
|
import { randomBytes as randomBytes3 } from "node:crypto";
|
|
2791
|
-
var SNAPSHOT_GAP_MS = 250;
|
|
2792
2643
|
var KEEPALIVE_MS = 3500;
|
|
2793
2644
|
var PROGRESS_REFRESH_MS = 6e4;
|
|
2794
|
-
var MAX_ROWS = 30;
|
|
2795
2645
|
function createLivePublisher(options) {
|
|
2796
2646
|
const { client, conversationId, enqueue } = options;
|
|
2797
|
-
const presence = options.mode === "presence";
|
|
2798
2647
|
const presenceId = `turn-${randomBytes3(16).toString("hex")}`;
|
|
2799
|
-
const publicTurnId = /* @__PURE__ */ __name(() =>
|
|
2648
|
+
const publicTurnId = /* @__PURE__ */ __name(() => options.turnId() !== null ? presenceId : null, "publicTurnId");
|
|
2800
2649
|
const openedAt = Date.now();
|
|
2801
|
-
const rows = /* @__PURE__ */ new Map();
|
|
2802
|
-
let mode = options.mode;
|
|
2803
|
-
let text3 = "";
|
|
2804
2650
|
let waiting = false;
|
|
2805
2651
|
let ended = false;
|
|
2806
|
-
let sequence = 0;
|
|
2807
|
-
let state;
|
|
2808
2652
|
let stateAt = 0;
|
|
2809
|
-
let snapshotAt = 0;
|
|
2810
|
-
let timer;
|
|
2811
2653
|
let keepalive;
|
|
2812
|
-
const current = /* @__PURE__ */ __name(() => waiting ? "waiting_input" : presence ? "thinking" : [...rows.values()].some((row) => row.status === "running") ? "tool" : text3 ? "streaming" : "thinking", "current");
|
|
2813
|
-
const typing = /* @__PURE__ */ __name((on) => enqueue(() => client.setTyping(conversationId, on, on ? "thinking" : void 0)), "typing");
|
|
2814
|
-
const node = /* @__PURE__ */ __name((blank = false) => enqueue(async () => {
|
|
2815
|
-
if (ended && !blank) return;
|
|
2816
|
-
const turnId = options.turnId();
|
|
2817
|
-
await client.setStreaming({
|
|
2818
|
-
conversationId,
|
|
2819
|
-
turnId,
|
|
2820
|
-
messageId: turnId ?? "",
|
|
2821
|
-
text: blank ? "" : text3,
|
|
2822
|
-
status: blank ? "thinking" : current(),
|
|
2823
|
-
blocks: blank ? [] : [...rows.values()]
|
|
2824
|
-
});
|
|
2825
|
-
}), "node");
|
|
2826
2654
|
const publishState = /* @__PURE__ */ __name(() => {
|
|
2827
|
-
const
|
|
2655
|
+
const state = waiting ? "waiting_input" : "thinking";
|
|
2828
2656
|
stateAt = Date.now();
|
|
2829
|
-
enqueue(() => client.setRuntimeTurn({
|
|
2830
|
-
conversationId,
|
|
2831
|
-
turnId: publicTurnId(),
|
|
2832
|
-
state: next,
|
|
2833
|
-
openedAt,
|
|
2834
|
-
turnUpdatedAt: Date.now(),
|
|
2835
|
-
...presence ? {} : options.context()
|
|
2836
|
-
}));
|
|
2657
|
+
enqueue(() => client.setRuntimeTurn({ conversationId, turnId: publicTurnId(), state, openedAt, turnUpdatedAt: Date.now() }));
|
|
2837
2658
|
}, "publishState");
|
|
2838
|
-
const progress = /* @__PURE__ */ __name(() => {
|
|
2839
|
-
if (current() !== state || Date.now() - stateAt > PROGRESS_REFRESH_MS) publishState();
|
|
2840
|
-
if (mode !== "full" || timer) return;
|
|
2841
|
-
timer = setTimeout(() => {
|
|
2842
|
-
timer = void 0;
|
|
2843
|
-
snapshotAt = Date.now();
|
|
2844
|
-
node();
|
|
2845
|
-
}, Math.max(0, snapshotAt + SNAPSHOT_GAP_MS - Date.now()));
|
|
2846
|
-
}, "progress");
|
|
2847
2659
|
return {
|
|
2848
2660
|
open() {
|
|
2849
2661
|
if (ended) return;
|
|
2850
|
-
if (!presence) typing(true);
|
|
2851
2662
|
publishState();
|
|
2852
|
-
if (mode === "full") node();
|
|
2853
2663
|
keepalive = setInterval(() => {
|
|
2854
|
-
if (ended)
|
|
2855
|
-
if (presence) {
|
|
2856
|
-
if (Date.now() - stateAt > PROGRESS_REFRESH_MS) publishState();
|
|
2857
|
-
return;
|
|
2858
|
-
}
|
|
2859
|
-
if (waiting) return;
|
|
2860
|
-
typing(true);
|
|
2861
|
-
if (mode === "full" && !text3 && !rows.size) node();
|
|
2664
|
+
if (!ended && Date.now() - stateAt > PROGRESS_REFRESH_MS) publishState();
|
|
2862
2665
|
}, KEEPALIVE_MS);
|
|
2863
2666
|
keepalive.unref?.();
|
|
2864
2667
|
},
|
|
2865
|
-
setMode(next) {
|
|
2866
|
-
if (ended || next === mode || presence || next === "presence") return;
|
|
2867
|
-
mode = next;
|
|
2868
|
-
if (mode === "full") node();
|
|
2869
|
-
},
|
|
2870
|
-
text(snapshot) {
|
|
2871
|
-
if (ended || presence || snapshot === text3) return;
|
|
2872
|
-
text3 = snapshot;
|
|
2873
|
-
progress();
|
|
2874
|
-
},
|
|
2875
|
-
activity(event) {
|
|
2876
|
-
if (ended || presence) return;
|
|
2877
|
-
rows.set(event.id, {
|
|
2878
|
-
id: event.id,
|
|
2879
|
-
turnId: options.turnId() ?? "",
|
|
2880
|
-
kind: event.kind,
|
|
2881
|
-
title: event.title,
|
|
2882
|
-
status: event.state === "done" ? "completed" : event.state,
|
|
2883
|
-
sequence: rows.get(event.id)?.sequence ?? ++sequence
|
|
2884
|
-
});
|
|
2885
|
-
for (const id of rows.keys()) if (rows.size > MAX_ROWS) rows.delete(id);
|
|
2886
|
-
progress();
|
|
2887
|
-
},
|
|
2888
2668
|
waiting(on) {
|
|
2889
2669
|
if (ended || waiting === on) return;
|
|
2890
2670
|
waiting = on;
|
|
2891
|
-
if (!presence) typing(!on);
|
|
2892
2671
|
publishState();
|
|
2893
|
-
if (mode === "full") node();
|
|
2894
|
-
},
|
|
2895
|
-
refresh() {
|
|
2896
|
-
if (!ended && state && !presence) publishState();
|
|
2897
2672
|
},
|
|
2898
2673
|
end(how) {
|
|
2899
2674
|
if (ended) return;
|
|
2900
2675
|
ended = true;
|
|
2901
|
-
clearTimeout(timer);
|
|
2902
2676
|
clearInterval(keepalive);
|
|
2903
|
-
|
|
2904
|
-
if (mode === "full") {
|
|
2905
|
-
if (how !== "answered") node(true);
|
|
2906
|
-
enqueue(() => client.clearStreaming(conversationId));
|
|
2907
|
-
}
|
|
2908
|
-
enqueue(() => client.setRuntimeTurn(how === "stopped" ? { conversationId, turnId: publicTurnId(), state: "interrupted", ...presence ? {} : options.context() } : { conversationId, turnId: null, state: "idle", ...presence ? {} : { queueDepth: 0 }, openedAt: null, turnUpdatedAt: null }));
|
|
2677
|
+
enqueue(() => client.setRuntimeTurn(how === "stopped" ? { conversationId, turnId: publicTurnId(), state: "interrupted" } : { conversationId, turnId: null, state: "idle", openedAt: null, turnUpdatedAt: null }));
|
|
2909
2678
|
}
|
|
2910
2679
|
};
|
|
2911
2680
|
}
|
|
@@ -3419,7 +3188,6 @@ var CLAIM_PATIENCE_MS = 2e4;
|
|
|
3419
3188
|
var HISTORY_WINDOW = 20;
|
|
3420
3189
|
var STATE_LIMIT_BYTES = 64 * 1024;
|
|
3421
3190
|
var FAILURE_NOTICE = "Sorry, something went wrong and I could not finish this reply.";
|
|
3422
|
-
var STOPS_SHORT_NOTICE = "Sorry, my reply could not be delivered in full.";
|
|
3423
3191
|
var RESTART_NOTICE = "Sorry, I was restarted before I could finish replying to this. Please send it again if you still need an answer.";
|
|
3424
3192
|
var CLIENT_TYPES = { code: "generic", claude: "claude-code", codex: "codex", acp: "generic", connector: "generic" };
|
|
3425
3193
|
var objectSchema = /* @__PURE__ */ __name((properties, required = []) => ({ type: "object", properties, required, additionalProperties: false }), "objectSchema");
|
|
@@ -3489,16 +3257,60 @@ __name(within, "within");
|
|
|
3489
3257
|
var pause2 = /* @__PURE__ */ __name((ms) => new Promise((resolve3) => {
|
|
3490
3258
|
setTimeout(resolve3, ms).unref?.();
|
|
3491
3259
|
}), "pause");
|
|
3492
|
-
|
|
3260
|
+
function agentRun(turns) {
|
|
3261
|
+
const history = turns.map(({ senderId, senderType }) => senderType === "ai_agent" ? { senderId, senderType, metadata: { turnSemantics: "turn_complete" } } : { senderId, senderType: "human" });
|
|
3262
|
+
return {
|
|
3263
|
+
consecutiveAgentTurns: buildParticipationHistorySnapshot(history.slice(1)).consecutiveAgentTurns,
|
|
3264
|
+
currentAgentStreakStartedByHuman: buildParticipationHistorySnapshot(history).currentAgentStreakStartedByHuman
|
|
3265
|
+
};
|
|
3266
|
+
}
|
|
3267
|
+
__name(agentRun, "agentRun");
|
|
3493
3268
|
var inert = /* @__PURE__ */ __name((value) => value.replace(/\s+/g, " ").trim(), "inert");
|
|
3494
3269
|
var OWNER_MARK = " [your owner]";
|
|
3495
3270
|
var TRUSTED_MARK = " [trusted teammate agent]";
|
|
3496
3271
|
var speaker = /* @__PURE__ */ __name((name) => inert(name).replace(/\[/g, "(").replace(/\]/g, ")"), "speaker");
|
|
3497
|
-
var defused = /* @__PURE__ */ __name((text3) => text3.replace(/\[(\s*(?:trusted\s+teammate\s+agent|your\s+owner)\s*)\]/gi, "($1)").replace(/\[(\s*(?:end\s+of\s+)?owner['\u2019]s\s+(?:private\s+note|errand)\s*)\]/gi, "($1)"), "defused");
|
|
3272
|
+
var defused = /* @__PURE__ */ __name((text3) => text3.replace(/\[(\s*(?:trusted\s+teammate\s+agent|your\s+owner)\s*)\]/gi, "($1)").replace(/\[(\s*(?:end\s+of\s+)?owner['\u2019]s\s+(?:private\s+note|errand)\s*)\]/gi, "($1)").replace(/\[(\s*(?:end\s+of\s+)?conversation\s+facts\s*)\]/gi, "($1)"), "defused");
|
|
3498
3273
|
var NOTE_OPEN = "[owner's private note] Your owner left this for you on the machine you run on. It is not a message in this conversation: no one in it sees it, and it was not posted.";
|
|
3499
3274
|
var NOTE_CLOSE = "[end of owner's private note]";
|
|
3500
3275
|
var ERRAND_OPEN = "[owner's errand]";
|
|
3501
3276
|
var ERRAND_CLOSE = "[end of owner's errand]";
|
|
3277
|
+
var FACTS_OPEN = "[conversation facts] Your runner added this from Canon's records. It is not a message, and no one in the conversation sees it.";
|
|
3278
|
+
var FACTS_CLOSE = "[end of conversation facts]";
|
|
3279
|
+
var MAX_FACT_MEMBERS = 50;
|
|
3280
|
+
var MAX_FACT_NAME = 200;
|
|
3281
|
+
var MAX_FACT_TOPIC = 500;
|
|
3282
|
+
var STYLE_RULES = {
|
|
3283
|
+
natural: void 0,
|
|
3284
|
+
collaborative: "Work openly with the people and agents here.",
|
|
3285
|
+
"mention-first": "Speak when you are clearly addressed; otherwise end your turn with no_reply.",
|
|
3286
|
+
"approval-gated": "Be cautious: join in only on a strong signal; otherwise end your turn with no_reply.",
|
|
3287
|
+
"handoff-only": "Act when someone explicitly hands you a task; otherwise end your turn with no_reply.",
|
|
3288
|
+
observer: "Stay quiet unless a reply is necessary, and end your turn with no_reply."
|
|
3289
|
+
};
|
|
3290
|
+
var factText = /* @__PURE__ */ __name((value, limit = MAX_FACT_NAME) => speaker(value).slice(0, limit).trimEnd(), "factText");
|
|
3291
|
+
function factsPrompt({ name, topic, owner, members, rules }, kind) {
|
|
3292
|
+
const shown = members.slice(0, MAX_FACT_MEMBERS).map((member) => member.isYou ? "you" : factText(member.name ?? member.id) + (member.isOwner ? OWNER_MARK : member.type === "ai_agent" ? " [agent]" : ""));
|
|
3293
|
+
const lines = !rules ? [] : [
|
|
3294
|
+
STYLE_RULES[rules.style],
|
|
3295
|
+
kind === "group" && rules.requireMentionForGroupReplies ? "Only messages that mention you reach you." : void 0,
|
|
3296
|
+
rules.allowAgentToAgent ? void 0 : "Messages from other agents do not reach you.",
|
|
3297
|
+
rules.allowLongRunningCollaboration ? void 0 : "After a person writes, another agent gets one exchange with you.",
|
|
3298
|
+
typeof rules.maxConsecutiveAgentTurns === "number" ? `Agents get at most ${rules.maxConsecutiveAgentTurns} turns in a row before a person writes.` : void 0
|
|
3299
|
+
].filter((line) => line !== void 0);
|
|
3300
|
+
return [
|
|
3301
|
+
FACTS_OPEN,
|
|
3302
|
+
`Your owner: ${factText(owner.name ?? owner.id)}`,
|
|
3303
|
+
...name ? [`Conversation: ${factText(name)}`] : [],
|
|
3304
|
+
...topic ? [`Topic: ${factText(topic, MAX_FACT_TOPIC)}`] : [],
|
|
3305
|
+
`Members: ${shown.join(", ")}${members.length > shown.length ? `, and ${members.length - shown.length} more` : ""}`,
|
|
3306
|
+
...lines.length ? [
|
|
3307
|
+
"This conversation's rules, which your owner or any of its admins can set. They say how you take part here, not what you may do:",
|
|
3308
|
+
...lines.map((line) => `- ${line}`)
|
|
3309
|
+
] : [],
|
|
3310
|
+
FACTS_CLOSE
|
|
3311
|
+
];
|
|
3312
|
+
}
|
|
3313
|
+
__name(factsPrompt, "factsPrompt");
|
|
3502
3314
|
var errandCommunicate = /* @__PURE__ */ __name((contact) => ({
|
|
3503
3315
|
name: "communicate",
|
|
3504
3316
|
description: "List Canon conversations or agents, or send text to your errand's recipient or to the conversation it came from" + (contact ? ". Start the conversation with the recipient first, with start_conversation." : "."),
|
|
@@ -3527,21 +3339,6 @@ var inputMessage = /* @__PURE__ */ __name((message2, trusted = false) => Object.
|
|
|
3527
3339
|
...message2.replyTo ? { replyTo: message2.replyTo } : {},
|
|
3528
3340
|
createdAt: message2.createdAt ?? ""
|
|
3529
3341
|
}), "inputMessage");
|
|
3530
|
-
function outputOptions(turnId, trigger, kind, actedForOwner = false, messageId = `${turnId}:${kind}`) {
|
|
3531
|
-
return {
|
|
3532
|
-
messageId,
|
|
3533
|
-
...trigger.replyAuthority ? { replyAuthority: trigger.replyAuthority } : {},
|
|
3534
|
-
...trigger.activeSelfContextId ? { selfContextId: trigger.activeSelfContextId } : {},
|
|
3535
|
-
metadata: {
|
|
3536
|
-
turnId,
|
|
3537
|
-
turnSemantics: "turn_complete",
|
|
3538
|
-
...kind === "final" ? {} : { replyBehavior: "suppress_auto_reply" },
|
|
3539
|
-
...kind === "final" && actedForOwner ? { [ACTED_FOR_OWNER]: true } : {}
|
|
3540
|
-
}
|
|
3541
|
-
};
|
|
3542
|
-
}
|
|
3543
|
-
__name(outputOptions, "outputOptions");
|
|
3544
|
-
var ACTED_FOR_OWNER = "actedForOwner";
|
|
3545
3342
|
var MAX_TEAM = 20;
|
|
3546
3343
|
function approvalScope(request) {
|
|
3547
3344
|
if (request.scopeKey !== void 0 && /^[A-Za-z0-9_:-]{1,64}$/.test(request.scopeKey)) return request.scopeKey;
|
|
@@ -3552,19 +3349,6 @@ function approvalScope(request) {
|
|
|
3552
3349
|
__name(approvalScope, "approvalScope");
|
|
3553
3350
|
var MAX_GROUP_PARTS = 100;
|
|
3554
3351
|
var CUT_SHORT_NOTE = "(This reply was too long to send in full and was cut short.)";
|
|
3555
|
-
var EXPIRED_UPLOAD = ["MEDIA_UPLOAD_TEMPORARY_EXPIRED", "MEDIA_UPLOAD_EXPIRED"];
|
|
3556
|
-
function fitGroup(text3, files) {
|
|
3557
|
-
const room = Math.max(1, MAX_GROUP_PARTS - files);
|
|
3558
|
-
const count = /* @__PURE__ */ __name((value) => value ? splitTextByUtf8Bytes(value).length : 0, "count");
|
|
3559
|
-
let fitted = text3;
|
|
3560
|
-
for (let end = Math.floor(text3.length * Math.min(1, room / Math.max(1, count(text3)))); count(fitted) > room; end = Math.floor(end * 0.95)) {
|
|
3561
|
-
fitted = `${text3.slice(0, end).replace(/[\uD800-\uDBFF]$/, "").trimEnd()}
|
|
3562
|
-
|
|
3563
|
-
${CUT_SHORT_NOTE}`;
|
|
3564
|
-
}
|
|
3565
|
-
return fitted;
|
|
3566
|
-
}
|
|
3567
|
-
__name(fitGroup, "fitGroup");
|
|
3568
3352
|
function fitEncrypted(text3) {
|
|
3569
3353
|
if (Buffer.byteLength(text3) <= MLS_DEV_TEXT_MAX_BYTES) return text3;
|
|
3570
3354
|
const room = MLS_DEV_TEXT_MAX_BYTES - Buffer.byteLength(`
|
|
@@ -3582,6 +3366,27 @@ ${CUT_SHORT_NOTE}`);
|
|
|
3582
3366
|
${CUT_SHORT_NOTE}`;
|
|
3583
3367
|
}
|
|
3584
3368
|
__name(fitEncrypted, "fitEncrypted");
|
|
3369
|
+
function encryptedParts(text3, limit) {
|
|
3370
|
+
if (Buffer.byteLength(text3) <= MLS_DEV_TEXT_MAX_BYTES) return [text3];
|
|
3371
|
+
const parts = splitTextWithinBytes(text3, MLS_DEV_TEXT_MAX_BYTES).filter((part) => part.trim());
|
|
3372
|
+
if (parts.length <= limit) return parts;
|
|
3373
|
+
const room = MLS_DEV_TEXT_MAX_BYTES - Buffer.byteLength(`
|
|
3374
|
+
|
|
3375
|
+
${CUT_SHORT_NOTE}`);
|
|
3376
|
+
let bytes = 0;
|
|
3377
|
+
let end = 0;
|
|
3378
|
+
for (const char of parts[limit - 1]) {
|
|
3379
|
+
bytes += Buffer.byteLength(char);
|
|
3380
|
+
if (bytes > room) break;
|
|
3381
|
+
end += char.length;
|
|
3382
|
+
}
|
|
3383
|
+
return [...parts.slice(0, limit - 1), `${parts[limit - 1].slice(0, end).trimEnd()}
|
|
3384
|
+
|
|
3385
|
+
${CUT_SHORT_NOTE}`];
|
|
3386
|
+
}
|
|
3387
|
+
__name(encryptedParts, "encryptedParts");
|
|
3388
|
+
var MAX_REPLY_PARTS = 32;
|
|
3389
|
+
var REPLY_GRANT_RETRIES = 6;
|
|
3585
3390
|
var ENCRYPTED_FILES = "Files cannot be sent in encrypted conversations yet";
|
|
3586
3391
|
var ERRAND_RETRY_MS = 5e3;
|
|
3587
3392
|
var STOPPED_ERRAND = {
|
|
@@ -3593,14 +3398,10 @@ var STOPPED_ERRAND = {
|
|
|
3593
3398
|
};
|
|
3594
3399
|
var SUBMITTED = { phase: "submitted", settle: /* @__PURE__ */ __name(async () => void 0, "settle"), release: /* @__PURE__ */ __name(async () => void 0, "release"), cancel: /* @__PURE__ */ __name(async () => void 0, "cancel") };
|
|
3595
3400
|
var SILENT = { open() {
|
|
3596
|
-
}, setMode() {
|
|
3597
|
-
}, text() {
|
|
3598
|
-
}, activity() {
|
|
3599
3401
|
}, waiting() {
|
|
3600
|
-
}, refresh() {
|
|
3601
3402
|
}, end() {
|
|
3602
3403
|
} };
|
|
3603
|
-
var UNAVAILABLE_ENCRYPTED = ["
|
|
3404
|
+
var UNAVAILABLE_ENCRYPTED = ["send_file", "ask_question"];
|
|
3604
3405
|
var carriesCards = /* @__PURE__ */ __name((audience) => mlsDevProtocolCarriesCards(audience?.protocol), "carriesCards");
|
|
3605
3406
|
var carriesItems = /* @__PURE__ */ __name((audience) => audience?.protocol === MLS_DEV_TEXT_PROTOCOL_V3, "carriesItems");
|
|
3606
3407
|
function runtimeDescriptor(driver) {
|
|
@@ -3641,14 +3442,6 @@ async function createRunner(options) {
|
|
|
3641
3442
|
const mentionAliases = names(options.mentionAliases, "mentionAliases").map((name) => name.replace(/^@+/, "")).filter(Boolean);
|
|
3642
3443
|
const descriptor = runtimeDescriptor(driver);
|
|
3643
3444
|
const offersQuestions = !driver.capabilities.nativeQuestions && ["choose", "text"].every((kind) => driver.capabilities.ask.includes(kind));
|
|
3644
|
-
const capabilities = {
|
|
3645
|
-
supportsInterrupt: true,
|
|
3646
|
-
supportsInputInterrupt: false,
|
|
3647
|
-
supportsQueue: true,
|
|
3648
|
-
supportsInterleave: false,
|
|
3649
|
-
supportsRequiresAction: driver.capabilities.ask.length > 0,
|
|
3650
|
-
supportsNonFinalPermanentMessages: false
|
|
3651
|
-
};
|
|
3652
3445
|
const rooms = /* @__PURE__ */ new Map();
|
|
3653
3446
|
const running = /* @__PURE__ */ new Set();
|
|
3654
3447
|
const detach = [];
|
|
@@ -3663,7 +3456,6 @@ async function createRunner(options) {
|
|
|
3663
3456
|
let tools;
|
|
3664
3457
|
let lock;
|
|
3665
3458
|
let owner;
|
|
3666
|
-
const approvals = /* @__PURE__ */ new Map();
|
|
3667
3459
|
let roots = [];
|
|
3668
3460
|
let mlsOn = false;
|
|
3669
3461
|
let mlsAsks;
|
|
@@ -3672,12 +3464,14 @@ async function createRunner(options) {
|
|
|
3672
3464
|
if (!mlsIds) throw new Error("MLS_AUTHORITY_REQUIRED");
|
|
3673
3465
|
return mlsIds;
|
|
3674
3466
|
}, "keyed");
|
|
3467
|
+
let outboundClosed = false;
|
|
3675
3468
|
let ownershipVersion;
|
|
3676
3469
|
let displayName;
|
|
3470
|
+
let ownerOf;
|
|
3677
3471
|
const refused = /* @__PURE__ */ new Set();
|
|
3678
3472
|
const deferredNotes = /* @__PURE__ */ new Set();
|
|
3473
|
+
const ruleNotes = /* @__PURE__ */ new Set();
|
|
3679
3474
|
const guards = /* @__PURE__ */ new Map();
|
|
3680
|
-
const relayed = /* @__PURE__ */ new Set();
|
|
3681
3475
|
const relaySends = /* @__PURE__ */ new Map();
|
|
3682
3476
|
let errands;
|
|
3683
3477
|
let errandInbox2;
|
|
@@ -3706,17 +3500,10 @@ async function createRunner(options) {
|
|
|
3706
3500
|
const settle = /* @__PURE__ */ __name(async (ids, reason) => (await Promise.allSettled(ids.map((id) => endpoint.setInboundState(id, "settled", reason)))).every(({ status }) => status === "fulfilled"), "settle");
|
|
3707
3501
|
const queued = /* @__PURE__ */ __name((turn) => turn.inputs.filter((input) => input.handle.phase === "queued"), "queued");
|
|
3708
3502
|
const turnId = /* @__PURE__ */ __name((roomId, inputs) => `turn-${sha2563(JSON.stringify([roomId, ...inputs.map((input) => [input.id, input.generation])])).slice(0, 32)}`, "turnId");
|
|
3709
|
-
const livePublisher = /* @__PURE__ */ __name((current, turn
|
|
3503
|
+
const livePublisher = /* @__PURE__ */ __name((current, turn) => createLivePublisher({
|
|
3710
3504
|
client,
|
|
3711
3505
|
conversationId: current.id,
|
|
3712
|
-
mode,
|
|
3713
3506
|
turnId: /* @__PURE__ */ __name(() => turn?.id ?? null, "turnId"),
|
|
3714
|
-
context: /* @__PURE__ */ __name(() => ({
|
|
3715
|
-
queueDepth: current.queue.length,
|
|
3716
|
-
currentSpeakerId: agentId,
|
|
3717
|
-
capabilities,
|
|
3718
|
-
activeMessageIds: turn?.inputs.map(({ payload }) => payload.message.id) ?? []
|
|
3719
|
-
}), "context"),
|
|
3720
3507
|
enqueue: /* @__PURE__ */ __name((publish2) => {
|
|
3721
3508
|
current.lane = current.lane.then(publish2).catch(() => void 0);
|
|
3722
3509
|
}, "enqueue")
|
|
@@ -3731,15 +3518,18 @@ async function createRunner(options) {
|
|
|
3731
3518
|
function refuse(roomId, why) {
|
|
3732
3519
|
if (refused.has(roomId)) return;
|
|
3733
3520
|
refused.add(roomId);
|
|
3734
|
-
log(`Refusing encrypted room ${roomId}: ${why === "no-device" ? "this runner has no MLS device (run it with --mls-dev-config)" : why === "remote" ? `the ${driver.name} driver runs off this host, which an encrypted room allows only in relay mode` : "its encryption protocol is not supported"}. Its messages are not delivered to this agent.`);
|
|
3521
|
+
log(why === "plaintext" ? `Refusing room ${roomId}: it is not encrypted, and this runner takes part only in encrypted rooms. Its messages are not delivered to this agent.` : `Refusing encrypted room ${roomId}: ${why === "no-device" ? "this runner has no MLS device (run it with --mls-dev-config)" : why === "remote" ? `the ${driver.name} driver runs off this host, which an encrypted room allows only in relay mode` : "its encryption protocol is not supported"}. Its messages are not delivered to this agent.`);
|
|
3735
3522
|
}
|
|
3736
3523
|
__name(refuse, "refuse");
|
|
3737
3524
|
async function authorize() {
|
|
3738
3525
|
await endpoint.mlsDevDevice.ensureReady();
|
|
3739
3526
|
const me = await client.getAgentMe();
|
|
3740
|
-
requireMlsSupport({ environmentId: options.environmentId
|
|
3527
|
+
requireMlsSupport({ environmentId: options.environmentId });
|
|
3741
3528
|
if (me.agentId !== agentId || me.ownershipVersion !== ownershipVersion) throw new Error("MLS_AUTHORITY_REQUIRED");
|
|
3529
|
+
outboundClosed = me.outboundPolicy === "closed";
|
|
3530
|
+
if (outboundClosed && options.relay) throw new Error("MLS_AUTHORITY_REQUIRED");
|
|
3742
3531
|
displayName = me.displayName;
|
|
3532
|
+
ownerOf = { id: me.ownerId, ...me.ownerName ? { name: me.ownerName } : {} };
|
|
3743
3533
|
return me.ownerId;
|
|
3744
3534
|
}
|
|
3745
3535
|
__name(authorize, "authorize");
|
|
@@ -3790,55 +3580,53 @@ ${error instanceof Error ? error.message : String(error)}`;
|
|
|
3790
3580
|
if (!input) return;
|
|
3791
3581
|
if (stopping) return void await handle.release("runner-stopping");
|
|
3792
3582
|
const target = room(input.payload.conversationId);
|
|
3793
|
-
if (input.mls) target.mls = true;
|
|
3794
3583
|
target.queue.push(input);
|
|
3795
|
-
target.turn?.live.refresh();
|
|
3796
3584
|
requeue(target);
|
|
3797
3585
|
pump();
|
|
3798
3586
|
}
|
|
3799
3587
|
__name(offer, "offer");
|
|
3800
3588
|
async function admitInput(event, handle) {
|
|
3801
3589
|
const payload = event.data;
|
|
3590
|
+
const { conversationId } = payload;
|
|
3802
3591
|
const generation = (await endpoint.getInbound(event.id))?.handoffGeneration ?? "";
|
|
3803
|
-
|
|
3804
|
-
|
|
3592
|
+
let mode;
|
|
3593
|
+
try {
|
|
3594
|
+
mode = await endpoint.getConversationContentMode(conversationId);
|
|
3595
|
+
} catch (error) {
|
|
3596
|
+
if (error.code !== "MLS_DEV_ROOM_MODE_UNAVAILABLE") throw error;
|
|
3597
|
+
if (!(await client.getConversation(conversationId))?.memberIds?.includes(agentId)) return void await handle.cancel("membership-lost");
|
|
3805
3598
|
throw error;
|
|
3806
|
-
});
|
|
3807
|
-
if (mode === "plaintext") {
|
|
3808
|
-
if (options.relay) {
|
|
3809
|
-
if (!relayed.has(payload.conversationId)) log(`Relay mode: leaving plaintext room ${payload.conversationId} to the connector.`);
|
|
3810
|
-
relayed.add(payload.conversationId);
|
|
3811
|
-
return void await handle.cancel("relay-plaintext-room");
|
|
3812
|
-
}
|
|
3813
|
-
if (payload.turnDispatch && payload.turnDispatch.kind !== "run_turn") return void await handle.cancel("observe-only");
|
|
3814
|
-
return { id: event.id, payload, handle, generation };
|
|
3815
3599
|
}
|
|
3816
3600
|
if (mode !== "mls-dev-text" || !mlsOn || driver.capabilities.remote && !options.relay) {
|
|
3817
|
-
refuse(
|
|
3818
|
-
return void await handle.cancel("encrypted-room-refused");
|
|
3601
|
+
refuse(conversationId, mode === "plaintext" ? "plaintext" : !mlsOn ? "no-device" : mode !== "mls-dev-text" ? "protocol" : "remote");
|
|
3602
|
+
return void await handle.cancel(mode === "plaintext" ? "plaintext-room-refused" : "encrypted-room-refused");
|
|
3819
3603
|
}
|
|
3820
|
-
const { conversationId } = payload;
|
|
3821
3604
|
const groups = endpoint.mlsDevGroups;
|
|
3822
3605
|
await authorize();
|
|
3823
3606
|
const binding = await groups.readVerifiedMessageBinding({ conversationId, messageId: payload.message.id });
|
|
3824
3607
|
if (!binding) throw new Error("MLS_INPUT_NOT_VERIFIED");
|
|
3825
3608
|
const { messages } = await endpoint.query("get_messages", { conversationId, messageIds: [binding.messageId] });
|
|
3826
3609
|
const [message2] = messages;
|
|
3827
|
-
if (messages.length !== 1 || message2.id !== binding.messageId || message2.senderId !== binding.senderId || message2.senderType !== binding.senderPrincipal.type || typeof message2.text !== "string" && !(message2.text === null && (message2.attachments?.length ?? 0) === 1)) throw new Error("MLS_INPUT_NOT_VERIFIED");
|
|
3610
|
+
if (messages.length !== 1 || message2.id !== binding.messageId || message2.senderId !== binding.senderId || message2.senderType !== binding.senderPrincipal.type || !Number.isSafeInteger(message2.mlsFeedOrdinal) || typeof message2.text !== "string" && !(message2.text === null && (message2.attachments?.length ?? 0) === 1)) throw new Error("MLS_INPUT_NOT_VERIFIED");
|
|
3828
3611
|
const { audience, ownerId } = await currentAudience(conversationId);
|
|
3829
3612
|
if (binding.groupId !== audience.groupId) throw new Error("MLS_INPUT_NOT_VERIFIED");
|
|
3830
3613
|
const obsolete = binding.head.sequence < audience.head.sequence;
|
|
3831
3614
|
if (!obsolete && JSON.stringify(binding.head) !== JSON.stringify(audience.head)) throw new Error("MLS_INPUT_AHEAD_OF_AUDIENCE");
|
|
3832
3615
|
if (obsolete) return void await handle.settle("mls-obsolete-audience");
|
|
3833
3616
|
const fromAgent = binding.senderPrincipal.type !== "human";
|
|
3834
|
-
|
|
3835
|
-
|
|
3836
|
-
|
|
3837
|
-
} else {
|
|
3617
|
+
const mentioned = (message2.mentions ?? []).includes(agentId) || mentionsAgent(message2.text ?? "", [displayName, agentId, ...mentionAliases]);
|
|
3618
|
+
if (fromAgent && (message2.senderId === agentId || !mentioned)) return void await handle.settle("mls-observe-only-sender");
|
|
3619
|
+
if (!fromAgent) {
|
|
3838
3620
|
const target = room(conversationId);
|
|
3839
3621
|
target.agentInputs = void 0;
|
|
3840
3622
|
target.capped = false;
|
|
3841
3623
|
}
|
|
3624
|
+
const rule = await withheld(conversationId, message2, fromAgent, mentioned, ownerId);
|
|
3625
|
+
if (rule) return void await handle.settle(rule);
|
|
3626
|
+
if (fromAgent) {
|
|
3627
|
+
const wake = agentWake(conversationId, event.id);
|
|
3628
|
+
if (wake !== "admit") return void await handle.settle(wake);
|
|
3629
|
+
}
|
|
3842
3630
|
const verified = {
|
|
3843
3631
|
id: message2.id,
|
|
3844
3632
|
senderId: message2.senderId,
|
|
@@ -3855,8 +3643,48 @@ ${error instanceof Error ? error.message : String(error)}`;
|
|
|
3855
3643
|
return { id: event.id, handle, generation, mls: audience, payload: { conversationId, message: verified } };
|
|
3856
3644
|
}
|
|
3857
3645
|
__name(admitInput, "admitInput");
|
|
3858
|
-
function
|
|
3859
|
-
|
|
3646
|
+
async function withheld(roomId, message2, fromAgent, mentioned, ownerId) {
|
|
3647
|
+
const current = await endpoint.query(
|
|
3648
|
+
"conversation",
|
|
3649
|
+
{ conversationId: roomId }
|
|
3650
|
+
);
|
|
3651
|
+
if (current?.blockedMemberIds?.includes(message2.senderId)) return "mls-blocked-sender";
|
|
3652
|
+
if (options.relay || !current?.behavior) return void 0;
|
|
3653
|
+
const { maxConsecutiveAgentTurns, allowLongRunningCollaboration } = current.behavior.participation;
|
|
3654
|
+
const decision = evaluateParticipationPolicy(current.behavior, {
|
|
3655
|
+
conversationType: resolveConversationPolicyScope(current.memberIds),
|
|
3656
|
+
senderType: fromAgent ? "ai_agent" : "human",
|
|
3657
|
+
isOwner: !fromAgent && message2.senderId === ownerId,
|
|
3658
|
+
mentionedAgent: mentioned,
|
|
3659
|
+
...fromAgent ? agentRun(await turnsBehind(roomId, message2, Math.max(maxConsecutiveAgentTurns ?? 0, allowLongRunningCollaboration ? 0 : 2))) : {}
|
|
3660
|
+
});
|
|
3661
|
+
if (decision.allow) return void 0;
|
|
3662
|
+
const note = `${roomId}
|
|
3663
|
+
${decision.reasonCode}`;
|
|
3664
|
+
if (!ruleNotes.has(note)) log(`The rules of ${roomId} keep messages from a turn: ${decision.reasonCode}.`);
|
|
3665
|
+
ruleNotes.add(note);
|
|
3666
|
+
return `mls-${decision.reasonCode}`;
|
|
3667
|
+
}
|
|
3668
|
+
__name(withheld, "withheld");
|
|
3669
|
+
async function turnsBehind(roomId, message2, limit) {
|
|
3670
|
+
const turns = [message2];
|
|
3671
|
+
let before = String(message2.mlsFeedOrdinal);
|
|
3672
|
+
while (before && turns.length <= limit) {
|
|
3673
|
+
const page = await endpoint.query(
|
|
3674
|
+
"list_messages",
|
|
3675
|
+
{ conversationId: roomId, limit: PARTICIPATION_HISTORY_FETCH_LIMIT, before }
|
|
3676
|
+
);
|
|
3677
|
+
for (const entry of page.messages) {
|
|
3678
|
+
if (entry.contentType === "interaction" || entry.senderId === turns.at(-1).senderId) continue;
|
|
3679
|
+
turns.push(entry);
|
|
3680
|
+
if (entry.senderType !== "ai_agent" || turns.length > limit) return turns;
|
|
3681
|
+
}
|
|
3682
|
+
before = page.nextBefore;
|
|
3683
|
+
}
|
|
3684
|
+
return turns;
|
|
3685
|
+
}
|
|
3686
|
+
__name(turnsBehind, "turnsBehind");
|
|
3687
|
+
function agentWake(roomId, inputId) {
|
|
3860
3688
|
const target = room(roomId);
|
|
3861
3689
|
const streak = target.agentInputs ??= /* @__PURE__ */ new Set();
|
|
3862
3690
|
if (!streak.has(inputId) && streak.size >= agentTurnCap) {
|
|
@@ -3896,12 +3724,10 @@ ${error instanceof Error ? error.message : String(error)}`;
|
|
|
3896
3724
|
continue;
|
|
3897
3725
|
}
|
|
3898
3726
|
if (!candidate.queue.length) continue;
|
|
3899
|
-
const
|
|
3900
|
-
const other = candidate.queue.findIndex((input) => !!input.mls !== encrypted);
|
|
3901
|
-
const inputs = candidate.queue.splice(0, Math.min(MAX_INPUTS_PER_TURN, other < 0 ? MAX_INPUTS_PER_TURN : other));
|
|
3727
|
+
const inputs = candidate.queue.splice(0, MAX_INPUTS_PER_TURN);
|
|
3902
3728
|
const turn = newTurn(inputs, turnId(candidate.id, inputs), candidate.id);
|
|
3903
3729
|
candidate.turn = turn;
|
|
3904
|
-
(turn.live = driver.capabilities.remote ? SILENT : livePublisher(candidate, turn
|
|
3730
|
+
(turn.live = driver.capabilities.remote ? SILENT : livePublisher(candidate, turn)).open();
|
|
3905
3731
|
const done = execute(candidate, turn).catch((error) => {
|
|
3906
3732
|
warn("turn failed")(error);
|
|
3907
3733
|
return dispose(queued(turn), "release", "turn-failed");
|
|
@@ -3918,6 +3744,32 @@ ${error instanceof Error ? error.message : String(error)}`;
|
|
|
3918
3744
|
while (localErrands.length && !stopping && running.size < maxTurns) startErrand(void 0, localErrands.shift());
|
|
3919
3745
|
}
|
|
3920
3746
|
__name(pump, "pump");
|
|
3747
|
+
async function roomFacts(conversation) {
|
|
3748
|
+
const { members } = await endpoint.query("get_members", { conversationId: conversation.id });
|
|
3749
|
+
const names2 = new Map(members.map(({ userId, displayName: name }) => [userId, name]));
|
|
3750
|
+
const owner2 = Object.freeze(ownerOf);
|
|
3751
|
+
const participation = conversation.behavior?.participation;
|
|
3752
|
+
return Object.freeze({
|
|
3753
|
+
...conversation.name ? { name: conversation.name } : {},
|
|
3754
|
+
...conversation.topic ? { topic: conversation.topic } : {},
|
|
3755
|
+
owner: owner2,
|
|
3756
|
+
members: Object.freeze(conversation.memberIds.map((id) => Object.freeze({
|
|
3757
|
+
id,
|
|
3758
|
+
...names2.get(id) ? { name: names2.get(id) } : {},
|
|
3759
|
+
type: conversation.participantTypes?.[id] === "ai_agent" || id === agentId ? "ai_agent" : "human",
|
|
3760
|
+
isOwner: id === owner2.id,
|
|
3761
|
+
isYou: id === agentId
|
|
3762
|
+
}))),
|
|
3763
|
+
...participation ? { rules: Object.freeze({
|
|
3764
|
+
style: participation.style,
|
|
3765
|
+
requireMentionForGroupReplies: participation.requireMentionForGroupReplies,
|
|
3766
|
+
allowAgentToAgent: participation.allowAgentToAgent,
|
|
3767
|
+
allowLongRunningCollaboration: participation.allowLongRunningCollaboration,
|
|
3768
|
+
maxConsecutiveAgentTurns: participation.maxConsecutiveAgentTurns ?? null
|
|
3769
|
+
}) } : {}
|
|
3770
|
+
});
|
|
3771
|
+
}
|
|
3772
|
+
__name(roomFacts, "roomFacts");
|
|
3921
3773
|
async function execute(current, turn) {
|
|
3922
3774
|
let conversation;
|
|
3923
3775
|
try {
|
|
@@ -3928,12 +3780,18 @@ ${error instanceof Error ? error.message : String(error)}`;
|
|
|
3928
3780
|
}
|
|
3929
3781
|
if (!conversation?.memberIds?.includes(agentId)) return loseMembership(current);
|
|
3930
3782
|
const kind = conversation.memberIds.length > 2 ? "group" : "direct";
|
|
3931
|
-
if (
|
|
3783
|
+
if (!await beforeTurn(current, turn)) return;
|
|
3784
|
+
if (!options.relay) {
|
|
3785
|
+
try {
|
|
3786
|
+
turn.facts = await roomFacts(conversation);
|
|
3787
|
+
} catch (error) {
|
|
3788
|
+
warn("room read failed; input returns to the inbox")(error);
|
|
3789
|
+
return dispose(queued(turn), "release", "room-unavailable");
|
|
3790
|
+
}
|
|
3791
|
+
}
|
|
3932
3792
|
turn.trusted = await confirmTrusted(turn, conversation.memberIds);
|
|
3933
3793
|
turn.notes = pendingNotes(current.id);
|
|
3934
|
-
turn.contexts =
|
|
3935
|
-
const mode = turn.mls ? "presence" : driver.capabilities.streaming && resolveTurnVerbosity({ conversationType: kind }) === "verbose" ? "full" : "status";
|
|
3936
|
-
turn.live.setMode(mode);
|
|
3794
|
+
turn.contexts = openErrands(current.id);
|
|
3937
3795
|
const history = driver.capabilities.sessionMemory === "none" ? await recentHistory(current, turn) : [];
|
|
3938
3796
|
await materialize(current, turn);
|
|
3939
3797
|
const bound = bindTools(current, turn);
|
|
@@ -3944,7 +3802,7 @@ ${error instanceof Error ? error.message : String(error)}`;
|
|
|
3944
3802
|
if (live.length && !turn.controller.signal.aborted) {
|
|
3945
3803
|
turn.inputs = live;
|
|
3946
3804
|
turn.id = turnId(current.id, live);
|
|
3947
|
-
turn.pickUp = () => pickUp(current, turn
|
|
3805
|
+
turn.pickUp = () => pickUp(current, turn);
|
|
3948
3806
|
try {
|
|
3949
3807
|
work = Promise.resolve(driver.runTurn(buildTurn(current.id, turn, kind, history, bound.tools)));
|
|
3950
3808
|
} catch (error) {
|
|
@@ -3990,7 +3848,7 @@ ${error instanceof Error ? error.message : String(error)}`;
|
|
|
3990
3848
|
turn.ended.abort();
|
|
3991
3849
|
bound.release();
|
|
3992
3850
|
clearTimeout(turn.timer);
|
|
3993
|
-
await deliver(current, turn, outcome
|
|
3851
|
+
await deliver(current, turn, outcome);
|
|
3994
3852
|
} finally {
|
|
3995
3853
|
turn.ended.abort();
|
|
3996
3854
|
bound.release();
|
|
@@ -4001,15 +3859,10 @@ ${error instanceof Error ? error.message : String(error)}`;
|
|
|
4001
3859
|
function started(current, turn) {
|
|
4002
3860
|
turn.submitted = true;
|
|
4003
3861
|
progress(turn);
|
|
4004
|
-
if (!
|
|
4005
|
-
for (const { payload } of turn.mls ? [] : turn.inputs) {
|
|
4006
|
-
if (payload.message.metadata?.inboundDisposition === "queued") {
|
|
4007
|
-
void client.updateMessageDisposition(current.id, payload.message.id, "accepted_now").catch(warn("accepted_now"));
|
|
4008
|
-
}
|
|
4009
|
-
}
|
|
3862
|
+
if (!options.relay) void client.markAsRead(current.id).catch(warn("mark read"));
|
|
4010
3863
|
}
|
|
4011
3864
|
__name(started, "started");
|
|
4012
|
-
async function pickUp(current, turn
|
|
3865
|
+
async function pickUp(current, turn) {
|
|
4013
3866
|
const live = queued(turn);
|
|
4014
3867
|
if (live.length !== turn.inputs.length) {
|
|
4015
3868
|
abort(turn, "requeue");
|
|
@@ -4019,7 +3872,7 @@ ${error instanceof Error ? error.message : String(error)}`;
|
|
|
4019
3872
|
if (result.status === "submitted") {
|
|
4020
3873
|
turn.pickUp = void 0;
|
|
4021
3874
|
started(current, turn);
|
|
4022
|
-
if (!turn.controller.signal.aborted) (turn.live = livePublisher(current, turn
|
|
3875
|
+
if (!turn.controller.signal.aborted) (turn.live = livePublisher(current, turn)).open();
|
|
4023
3876
|
}
|
|
4024
3877
|
return result;
|
|
4025
3878
|
});
|
|
@@ -4033,20 +3886,19 @@ ${error instanceof Error ? error.message : String(error)}`;
|
|
|
4033
3886
|
__name(pickUp, "pickUp");
|
|
4034
3887
|
function requeue(current) {
|
|
4035
3888
|
const turn = current.turn, next = current.queue[0];
|
|
4036
|
-
if (!turn?.pickUp || turn.pickingUp || turn.controller.signal.aborted || !next) return;
|
|
4037
|
-
if (!!next.mls !== !!turn.inputs[0].mls || turn.inputs.length >= MAX_INPUTS_PER_TURN) return;
|
|
3889
|
+
if (!turn?.pickUp || turn.pickingUp || turn.controller.signal.aborted || !next || turn.inputs.length >= MAX_INPUTS_PER_TURN) return;
|
|
4038
3890
|
abort(turn, "requeue");
|
|
4039
3891
|
}
|
|
4040
3892
|
__name(requeue, "requeue");
|
|
4041
3893
|
async function confirmTrusted(turn, memberIds) {
|
|
4042
|
-
|
|
4043
|
-
|
|
3894
|
+
const audience = turn.mls;
|
|
3895
|
+
if (!trustedAgents.size || driver.capabilities.remote || options.relay || !audience) return /* @__PURE__ */ new Set();
|
|
3896
|
+
const candidates = turn.inputs.map(({ payload }) => payload.message).filter((message2) => message2.senderType === "ai_agent" && message2.senderId !== agentId && trustedAgents.has(message2.senderId));
|
|
4044
3897
|
if (!candidates.length) return /* @__PURE__ */ new Set();
|
|
4045
|
-
|
|
4046
|
-
|
|
4047
|
-
const rosterIds = turn.mls ? await Promise.all(roster.map(async (entry) => await endpoint.mlsAccountOf(turn.mls.conversationId, entry.device.principal) ?? "")) : [];
|
|
3898
|
+
if (audience.roster.some((entry) => isConnectorRelayInstallation(entry.device.installationId))) return /* @__PURE__ */ new Set();
|
|
3899
|
+
const rosterIds = await Promise.all(audience.roster.map(async (entry) => await endpoint.mlsAccountOf(audience.conversationId, entry.device.principal) ?? ""));
|
|
4048
3900
|
if (rosterIds.includes("")) return /* @__PURE__ */ new Set();
|
|
4049
|
-
const member = /* @__PURE__ */ __name((id) => memberIds.includes(id) &&
|
|
3901
|
+
const member = /* @__PURE__ */ __name((id) => memberIds.includes(id) && rosterIds.includes(id), "member");
|
|
4050
3902
|
if (candidates.some((message2) => !member(message2.senderId))) return /* @__PURE__ */ new Set();
|
|
4051
3903
|
try {
|
|
4052
3904
|
const { ownerId } = await client.getAgentMe();
|
|
@@ -4174,35 +4026,30 @@ ${error instanceof Error ? error.message : String(error)}`;
|
|
|
4174
4026
|
};
|
|
4175
4027
|
const cards = carriesCards(turn.mls), items = carriesItems(turn.mls);
|
|
4176
4028
|
const errand = turn.errand;
|
|
4177
|
-
const sendsFiles = roots.length > 0 &&
|
|
4029
|
+
const sendsFiles = roots.length > 0 && forOwner2(turn);
|
|
4178
4030
|
const binding = {
|
|
4179
|
-
// §6:
|
|
4180
|
-
//
|
|
4181
|
-
// asks only in an origin its report
|
|
4031
|
+
// §6: a turn gets no_reply, a communicate that only lists and sends encrypted text to its own room, and react, which
|
|
4032
|
+
// a relay's credential cannot do; in a room created for cards, ask_question, and in one that carries items, send_file.
|
|
4033
|
+
// §12.6: an errand turn reaches its recipient and its origin, and asks only in an origin its report goes to.
|
|
4182
4034
|
definitions: errand ? [
|
|
4183
4035
|
TOOLS.no_reply,
|
|
4184
4036
|
errandCommunicate("contact" in errand.target),
|
|
4185
|
-
...errand.reports &&
|
|
4186
|
-
] :
|
|
4037
|
+
...errand.reports && cards && offersQuestions ? [TOOLS.ask_question] : []
|
|
4038
|
+
] : [
|
|
4187
4039
|
TOOLS.no_reply,
|
|
4188
4040
|
ENCRYPTED_COMMUNICATE,
|
|
4041
|
+
...options.relay ? [] : [TOOLS.react],
|
|
4189
4042
|
...items && sendsFiles ? [TOOLS.send_file] : [],
|
|
4190
4043
|
...cards && offersQuestions ? [TOOLS.ask_question] : []
|
|
4191
|
-
] : [
|
|
4192
|
-
TOOLS.no_reply,
|
|
4193
|
-
canonCommunicateToolDefinition(),
|
|
4194
|
-
TOOLS.react,
|
|
4195
|
-
...sendsFiles ? [TOOLS.send_file] : [],
|
|
4196
|
-
...offersQuestions ? [TOOLS.ask_question] : []
|
|
4197
4044
|
],
|
|
4198
4045
|
async call(name, args) {
|
|
4199
4046
|
if (!turn.open) throw turnEnded2();
|
|
4200
4047
|
if (name === "no_reply" && turn.silenced) return { result: toolText(NO_REPLY_ACK_NOTE) };
|
|
4201
4048
|
if (turn.controller.signal.aborted || turn.silenced) throw turnEnded2();
|
|
4202
|
-
if (
|
|
4049
|
+
if (!errand && UNAVAILABLE_ENCRYPTED.includes(name) && !(name === "ask_question" && cards) && !(name === "send_file" && items)) {
|
|
4203
4050
|
return { result: toolText(`${name} is not available in encrypted conversations yet.`, true) };
|
|
4204
4051
|
}
|
|
4205
|
-
if (name === "send_file" && !errand && roots.length && !
|
|
4052
|
+
if (name === "send_file" && !errand && roots.length && !forOwner2(turn)) {
|
|
4206
4053
|
return { result: toolText("send_file sends files only in a turn answering your owner.", true) };
|
|
4207
4054
|
}
|
|
4208
4055
|
const handler = binding.definitions.some((definition) => definition.name === name) ? handlers[name] : void 0;
|
|
@@ -4223,11 +4070,9 @@ ${error instanceof Error ? error.message : String(error)}`;
|
|
|
4223
4070
|
}, "call") } };
|
|
4224
4071
|
}
|
|
4225
4072
|
__name(bindTools, "bindTools");
|
|
4226
|
-
const sealed = /* @__PURE__ */ __name((turn) => turn.errand ? "room" in turn.errand.target && !!turn.errand.target.mls : !!turn.mls, "sealed");
|
|
4227
4073
|
async function communicate(current, turn, args) {
|
|
4228
4074
|
const errand = turn.errand;
|
|
4229
|
-
const origins = errand ? errand.origin ? [errand.origin.room] : [] : turn.contexts.flatMap((
|
|
4230
|
-
if (!errand && !sealed(turn) && !origins.length) return executeCanonCommunicateTool(client, args);
|
|
4075
|
+
const origins = errand ? errand.origin ? [errand.origin.room] : [] : turn.contexts.flatMap((record) => record.origin ? [record.origin] : []);
|
|
4231
4076
|
let command;
|
|
4232
4077
|
try {
|
|
4233
4078
|
command = parseCommunicateToolInput(args);
|
|
@@ -4239,19 +4084,14 @@ ${error instanceof Error ? error.message : String(error)}`;
|
|
|
4239
4084
|
}
|
|
4240
4085
|
if (command.action === "message_existing" && command.text.trim()) {
|
|
4241
4086
|
const to = command.conversationId;
|
|
4242
|
-
if (
|
|
4087
|
+
if (!errand && to === current.id) return sealedSend(turn, to, command.text, turn.mls);
|
|
4243
4088
|
if (errand && "room" in errand.target && to === errand.target.room) {
|
|
4244
4089
|
reach(turn, to);
|
|
4245
|
-
return
|
|
4090
|
+
return sealedSend(turn, to, command.text);
|
|
4246
4091
|
}
|
|
4247
4092
|
if (origins.includes(to)) {
|
|
4248
|
-
|
|
4249
|
-
|
|
4250
|
-
if (mode !== "plaintext") return toolText(`${to} is an encrypted conversation this runner cannot write to.`, true);
|
|
4251
|
-
if (sealed(turn)) {
|
|
4252
|
-
return toolText(errand ? `This errand's recipient is in an encrypted conversation, so nothing of it may go to ${to}, which is not encrypted. Your final answer stays on your owner's machine as your report.` : `Nothing from this encrypted conversation may go to ${to}: it is not encrypted, and Canon must never receive this conversation's content in plaintext. Your owner sees on their machine that the errand was answered.`, true);
|
|
4253
|
-
}
|
|
4254
|
-
return executeCanonCommunicateTool(client, args);
|
|
4093
|
+
if (await endpoint.getConversationContentMode(to) === "mls-dev-text" && mlsOn) return sealedSend(turn, to, command.text);
|
|
4094
|
+
return toolText(`${to} is not an encrypted conversation this runner can write to.`, true);
|
|
4255
4095
|
}
|
|
4256
4096
|
}
|
|
4257
4097
|
if (errand && "contact" in errand.target && (command.action === "start_conversation" || command.action === "start_direct") && command.principalId === errand.target.contact) {
|
|
@@ -4261,8 +4101,7 @@ ${error instanceof Error ? error.message : String(error)}`;
|
|
|
4261
4101
|
${JSON.stringify(result, null, 2)}`);
|
|
4262
4102
|
}
|
|
4263
4103
|
if (errand) return toolText("On an errand, communicate can only list conversations or agents, or send text to the errand's recipient or its origin.", true);
|
|
4264
|
-
|
|
4265
|
-
return executeCanonCommunicateTool(client, args);
|
|
4104
|
+
return toolText("In an encrypted conversation, communicate can only list conversations or agents, or send text to this conversation.", true);
|
|
4266
4105
|
}
|
|
4267
4106
|
__name(communicate, "communicate");
|
|
4268
4107
|
async function sealedSend(turn, roomId, text3, audience) {
|
|
@@ -4283,9 +4122,10 @@ ${JSON.stringify(result, null, 2)}`);
|
|
|
4283
4122
|
notice: false,
|
|
4284
4123
|
mls: mls2
|
|
4285
4124
|
};
|
|
4286
|
-
|
|
4287
|
-
const sent2 = await publish(record);
|
|
4125
|
+
saveOutput(record);
|
|
4126
|
+
const sent2 = await publish(record, turn.retire.signal);
|
|
4288
4127
|
if (sent2 !== "pending") journal.remove(messageId);
|
|
4128
|
+
else if (turn.retire.signal.aborted) await stopOutput(record);
|
|
4289
4129
|
return sent2;
|
|
4290
4130
|
})().catch((error) => {
|
|
4291
4131
|
turn.said.delete(messageId);
|
|
@@ -4309,60 +4149,6 @@ ${JSON.stringify(result, null, 2)}`);
|
|
|
4309
4149
|
}
|
|
4310
4150
|
}
|
|
4311
4151
|
__name(admitFile, "admitFile");
|
|
4312
|
-
async function attach(conversationId, turn, candidates) {
|
|
4313
|
-
const attachments = [];
|
|
4314
|
-
const flows = [];
|
|
4315
|
-
const refused2 = [];
|
|
4316
|
-
const seen = /* @__PURE__ */ new Set();
|
|
4317
|
-
for (const candidate of candidates) {
|
|
4318
|
-
const file = await admitFile(candidate);
|
|
4319
|
-
if (typeof file === "string") {
|
|
4320
|
-
refused2.push(typeof candidate === "string" ? basename4(candidate) : "a file");
|
|
4321
|
-
continue;
|
|
4322
|
-
}
|
|
4323
|
-
if (seen.has(file.path)) continue;
|
|
4324
|
-
seen.add(file.path);
|
|
4325
|
-
if (attachments.length >= MAX_GROUP_PARTS - 1) {
|
|
4326
|
-
refused2.push(basename4(file.path));
|
|
4327
|
-
continue;
|
|
4328
|
-
}
|
|
4329
|
-
try {
|
|
4330
|
-
const media = await client.getFileMediaPipeline(options.fetch);
|
|
4331
|
-
const attachmentId = `${turn.id}-${seen.size}`;
|
|
4332
|
-
const [prepared] = await media.prepare({ conversationId, attachmentId, files: [{
|
|
4333
|
-
source: file.path,
|
|
4334
|
-
descriptor: { mimeType: inferUploadMimeType(file.path), sizeBytes: file.size, fileName: basename4(file.path) }
|
|
4335
|
-
}] }, { signal: turn.retire.signal });
|
|
4336
|
-
attachments.push(prepared);
|
|
4337
|
-
flows.push(`${conversationId}:upload:${attachmentId}`);
|
|
4338
|
-
} catch (error) {
|
|
4339
|
-
if (turn.retire.signal.aborted) break;
|
|
4340
|
-
warn("file upload")(error);
|
|
4341
|
-
refused2.push(basename4(file.path));
|
|
4342
|
-
}
|
|
4343
|
-
}
|
|
4344
|
-
return { attachments, flows, refused: refused2 };
|
|
4345
|
-
}
|
|
4346
|
-
__name(attach, "attach");
|
|
4347
|
-
const releaseUploads = /* @__PURE__ */ __name((attachments = []) => void (attachments.length && client.getFileMediaPipeline(options.fetch).then((media) => Promise.all(attachments.map((attachment) => attachment.uploadId && media.releasePreparedUpload(attachment.uploadId)))).catch(warn("release upload"))), "releaseUploads");
|
|
4348
|
-
async function renew(flows, signal) {
|
|
4349
|
-
const media = await client.getFileMediaPipeline(options.fetch);
|
|
4350
|
-
const renewed = [];
|
|
4351
|
-
for (const id of flows) {
|
|
4352
|
-
let prepared;
|
|
4353
|
-
const off = media.subscribe((flow) => {
|
|
4354
|
-
if (flow.id === id && flow.status === "prepared") prepared = flow.files[0]?.attachment;
|
|
4355
|
-
});
|
|
4356
|
-
try {
|
|
4357
|
-
await media.resume(id, { signal });
|
|
4358
|
-
} finally {
|
|
4359
|
-
off();
|
|
4360
|
-
}
|
|
4361
|
-
renewed.push(prepared);
|
|
4362
|
-
}
|
|
4363
|
-
return renewed;
|
|
4364
|
-
}
|
|
4365
|
-
__name(renew, "renew");
|
|
4366
4152
|
async function recentHistory(current, turn) {
|
|
4367
4153
|
const first = turn.inputs[0].payload.message;
|
|
4368
4154
|
try {
|
|
@@ -4373,8 +4159,8 @@ ${JSON.stringify(result, null, 2)}`);
|
|
|
4373
4159
|
const ownerId = driver.capabilities.ownerHistoryOnly ? (await client.getAgentMe()).ownerId : void 0;
|
|
4374
4160
|
const allowed = /* @__PURE__ */ __name((message2) => ownerId === void 0 || message2.senderId === agentId || message2.senderId === ownerId, "allowed");
|
|
4375
4161
|
const window = recent.filter((message2, index) => before(message2, index) && !message2.deleted && !skip.has(message2.id) && allowed(message2)).slice(0, HISTORY_WINDOW);
|
|
4376
|
-
const sameAudience =
|
|
4377
|
-
return window.filter((_, index) =>
|
|
4162
|
+
const sameAudience = await Promise.all(window.map((message2) => endpoint.mlsDevGroups.readVerifiedMessageBinding({ conversationId: current.id, messageId: message2.id }).then((binding) => !!binding && JSON.stringify(binding.head) === JSON.stringify(turn.mls.head), () => false)));
|
|
4163
|
+
return window.filter((_, index) => sameAudience[index]).reverse();
|
|
4378
4164
|
} catch (error) {
|
|
4379
4165
|
warn("history read failed; the turn runs without it")(error);
|
|
4380
4166
|
return [];
|
|
@@ -4394,8 +4180,9 @@ ${JSON.stringify(result, null, 2)}`);
|
|
|
4394
4180
|
return void 0;
|
|
4395
4181
|
}
|
|
4396
4182
|
__name(outcomeOf, "outcomeOf");
|
|
4397
|
-
async function deliver(current, turn, outcome
|
|
4183
|
+
async function deliver(current, turn, outcome) {
|
|
4398
4184
|
if (turn.reason === "shutdown") return turn.live.end("stopped");
|
|
4185
|
+
const audience = turn.mls;
|
|
4399
4186
|
const result = outcome && "result" in outcome ? outcome.result : void 0;
|
|
4400
4187
|
const stopped = /* @__PURE__ */ __name(() => turn.retire.signal.aborted, "stopped");
|
|
4401
4188
|
const silent = !stopped() && (turn.silenced !== void 0 || !turn.reason && result?.reply === null);
|
|
@@ -4403,141 +4190,108 @@ ${JSON.stringify(result, null, 2)}`);
|
|
|
4403
4190
|
const failed = !stopped() && !silent && !answered2;
|
|
4404
4191
|
if (failed && outcome && "error" in outcome) warn("the runtime failed the turn")(outcome.error);
|
|
4405
4192
|
let text3 = answered2 ? result.reply : failed ? FAILURE_NOTICE : null;
|
|
4406
|
-
let attachments = [];
|
|
4407
|
-
let flows = [];
|
|
4408
4193
|
let items = [];
|
|
4409
4194
|
const resultFiles = answered2 && Array.isArray(result.files) ? result.files : [];
|
|
4410
|
-
if (answered2 &&
|
|
4195
|
+
if (answered2 && carriesItems(audience)) {
|
|
4411
4196
|
const admitted = await admitItems(current.id, turn, [...turn.outbox, ...resultFiles]);
|
|
4412
4197
|
items = admitted.items;
|
|
4413
4198
|
if (admitted.refused.length) text3 = `${text3}
|
|
4414
4199
|
|
|
4415
4200
|
(Could not attach ${admitted.refused.join(", ")}.)`;
|
|
4416
|
-
} else if (answered2 &&
|
|
4201
|
+
} else if (answered2 && resultFiles.length) {
|
|
4417
4202
|
text3 = `${text3}
|
|
4418
4203
|
|
|
4419
4204
|
(${ENCRYPTED_FILES}: ${resultFiles.map((file) => typeof file === "string" ? basename4(file) : "a file").join(", ")}.)`;
|
|
4420
|
-
} else if (answered2 && !turn.mls) {
|
|
4421
|
-
const files = await attach(current.id, turn, [...turn.outbox, ...resultFiles]);
|
|
4422
|
-
({ attachments, flows } = files);
|
|
4423
|
-
if (files.refused.length) text3 = `${text3}
|
|
4424
|
-
|
|
4425
|
-
(Could not attach ${files.refused.join(", ")}.)`;
|
|
4426
4205
|
}
|
|
4427
4206
|
let sent;
|
|
4428
4207
|
if (text3 !== null && !stopped()) {
|
|
4429
4208
|
const kind = failed ? "failed" : "final";
|
|
4209
|
+
const replyAuthority = await replyGrant(current.id, turn.inputs.at(-1).payload.message.id, stopped);
|
|
4430
4210
|
const record = {
|
|
4431
4211
|
version: 1,
|
|
4432
4212
|
turnId: turn.id,
|
|
4433
4213
|
conversationId: current.id,
|
|
4434
4214
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4435
|
-
audience,
|
|
4215
|
+
audience: null,
|
|
4436
4216
|
inputs: turn.inputs.map((input) => input.id),
|
|
4437
4217
|
text: text3,
|
|
4438
|
-
...attachments.length ? { attachments, flows } : {},
|
|
4439
4218
|
...items.length ? { items } : {},
|
|
4440
|
-
options:
|
|
4441
|
-
turn.id,
|
|
4442
|
-
turn.inputs.at(-1).payload,
|
|
4443
|
-
kind,
|
|
4444
|
-
!turn.mls && actedForOwner(turn),
|
|
4445
|
-
turn.mls ? keyed().text(kind, current.id, turn.id) : void 0
|
|
4446
|
-
),
|
|
4219
|
+
options: { messageId: keyed().text(kind, current.id, turn.id), ...replyAuthority ? { replyAuthority } : {} },
|
|
4447
4220
|
notice: failed,
|
|
4448
|
-
|
|
4221
|
+
mls: audience
|
|
4449
4222
|
};
|
|
4450
|
-
|
|
4223
|
+
saveOutput(record);
|
|
4451
4224
|
sent = await publish(record, turn.retire.signal);
|
|
4452
|
-
|
|
4453
|
-
if (sent === "pending"
|
|
4225
|
+
if (sent === "pending" && !stopped()) return turn.live.end(turn.reason && STOPS.includes(turn.reason) ? "stopped" : "ended");
|
|
4226
|
+
if (sent === "pending") await stopOutput(record);
|
|
4454
4227
|
}
|
|
4455
4228
|
turn.live.end(sent === "delivered" ? "answered" : stopped() ? "stopped" : "ended");
|
|
4456
|
-
releaseUploads(attachments);
|
|
4457
4229
|
if (failed && outcome && "error" in outcome && outcome.error instanceof DriverSessionError) {
|
|
4458
4230
|
await resetSession(turn.session).catch(warn("session reset"));
|
|
4459
4231
|
}
|
|
4460
|
-
if (turn.
|
|
4232
|
+
if (turn.reason === "audience" || turn.reason === "membership") {
|
|
4461
4233
|
await resetSession(turn.session).catch(warn("session reset"));
|
|
4462
4234
|
if (current.session === turn.session) current.session = void 0;
|
|
4463
4235
|
}
|
|
4464
|
-
if (silent && !turn.mls) reportNoReplyOutcome(client, { conversationId: current.id, messageId: turn.inputs.at(-1).payload.message.id, ...turn.silenced });
|
|
4465
4236
|
const reason = stopped() ? turn.reason : silent ? "silent" : answered2 ? "answered" : "failed";
|
|
4466
4237
|
if (await settle(turn.inputs.map((input) => input.id), reason) && sent !== void 0) journal.remove(turn.id);
|
|
4467
4238
|
}
|
|
4468
4239
|
__name(deliver, "deliver");
|
|
4469
|
-
const
|
|
4470
|
-
async function sendWithFiles(record, text3, options2, signal) {
|
|
4471
|
-
const root = options2.messageId;
|
|
4472
|
-
const register = /* @__PURE__ */ __name((attachments, replaces) => {
|
|
4473
|
-
const bodies = [
|
|
4474
|
-
...attachments.map((attachment) => ({ text: "", contentType: attachment.kind, attachments: [attachment] })),
|
|
4475
|
-
...(text3 ? splitTextByUtf8Bytes(text3) : []).map((part) => ({ text: part }))
|
|
4476
|
-
];
|
|
4477
|
-
const parts = bodies.map(({ text: partText, ...body }, index) => ({ text: partText, options: {
|
|
4478
|
-
...options2,
|
|
4479
|
-
...body,
|
|
4480
|
-
messageId: `${root}-part-${index + 1}`,
|
|
4481
|
-
metadata: buildChunkedMessagePartMetadata({ groupId: root, metadata: options2.metadata, index: index + 1, count: bodies.length })
|
|
4482
|
-
} }));
|
|
4483
|
-
return client.prepareMessageGroup(record.conversationId, root, parts, { signal, ...replaces ? { operationId: sha2563(JSON.stringify([root, ...attachments.map((attachment) => attachment.uploadId)])), replacesOperationId: replaces } : {} }).then((group2) => ({ ...group2, count: parts.length }));
|
|
4484
|
-
}, "register");
|
|
4485
|
-
const original = await register(record.attachments);
|
|
4486
|
-
let group = original;
|
|
4487
|
-
const cancel = /* @__PURE__ */ __name(() => {
|
|
4488
|
-
void client.cancelMessageGroup(group.id).catch(() => void 0);
|
|
4489
|
-
}, "cancel");
|
|
4490
|
-
signal?.addEventListener("abort", cancel, { once: true });
|
|
4491
|
-
try {
|
|
4492
|
-
for (; ; ) {
|
|
4493
|
-
try {
|
|
4494
|
-
for (let index = 0; index < group.count; index += 1) await group.sendPart(index, { signal });
|
|
4495
|
-
return;
|
|
4496
|
-
} catch (error) {
|
|
4497
|
-
if (!record.flows || !EXPIRED_UPLOAD.includes(error.code ?? "")) throw error;
|
|
4498
|
-
const renewed = await renew(record.flows, signal).catch((failure) => {
|
|
4499
|
-
throw failure.retryable === false ? error : failure;
|
|
4500
|
-
});
|
|
4501
|
-
if (renewed.some((attachment) => !attachment) || renewed.every((attachment, index) => attachment.uploadId === record.attachments[index].uploadId)) throw error;
|
|
4502
|
-
record.attachments = renewed;
|
|
4503
|
-
group = await register(record.attachments, original.id);
|
|
4504
|
-
}
|
|
4505
|
-
}
|
|
4506
|
-
} finally {
|
|
4507
|
-
signal?.removeEventListener("abort", cancel);
|
|
4508
|
-
}
|
|
4509
|
-
}
|
|
4510
|
-
__name(sendWithFiles, "sendWithFiles");
|
|
4240
|
+
const forOwner2 = /* @__PURE__ */ __name((turn) => turn.inputs.every(({ payload: { message: message2 } }) => message2.isOwner === true || message2.senderType === "ai_agent" && turn.trusted.has(message2.id)), "forOwner");
|
|
4511
4241
|
async function publish(record, retire) {
|
|
4512
|
-
|
|
4513
|
-
if (sent !== "refused" || record.notice || record.mls || record.errand) return sent;
|
|
4514
|
-
const notice = {
|
|
4515
|
-
...record.options,
|
|
4516
|
-
messageId: `${record.options.messageId}:short`,
|
|
4517
|
-
metadata: { ...record.options.metadata, replyBehavior: "suppress_auto_reply" }
|
|
4518
|
-
};
|
|
4519
|
-
return await send(record.conversationId, STOPS_SHORT_NOTICE, notice, retire) === "pending" ? "pending" : "refused";
|
|
4242
|
+
return send(record.text, record.options, record.mls, retire, record);
|
|
4520
4243
|
}
|
|
4521
4244
|
__name(publish, "publish");
|
|
4522
|
-
async function send(
|
|
4245
|
+
async function send(text3, options2, mls2, retire, record) {
|
|
4246
|
+
const grant = options2.replyAuthority;
|
|
4247
|
+
const parts = encryptedMessages(mls2, text3, options2, record);
|
|
4523
4248
|
const sent = await (async () => {
|
|
4524
4249
|
for (let attempt = 0; ; attempt += 1) {
|
|
4525
|
-
const
|
|
4526
|
-
|
|
4250
|
+
const result = await untilHalt(sendEncryptedItems(mls2, record?.items ?? [], retire).then(async () => {
|
|
4251
|
+
for (const part of parts) await sendEncrypted(mls2, part.messageId, part.text, retire, grant && { ...grant, turnId: options2.messageId, complete: part.last });
|
|
4252
|
+
}).then(() => "delivered", (error) => error));
|
|
4527
4253
|
if (result === "delivered" || result === "pending") return result;
|
|
4528
|
-
if (retire?.aborted || isPendingCanonOperation(result)) return "pending";
|
|
4529
|
-
if (
|
|
4254
|
+
if (retire?.aborted || isPendingCanonOperation(result) && parts.length < 2) return "pending";
|
|
4255
|
+
if (result.retryable === false) {
|
|
4530
4256
|
warn("Canon refused a reply")(result);
|
|
4531
4257
|
return "refused";
|
|
4532
4258
|
}
|
|
4533
4259
|
if (await untilHalt(pause2(Math.min(3e4, 1e3 * 2 ** attempt))) === "pending") return "pending";
|
|
4534
4260
|
}
|
|
4535
4261
|
})();
|
|
4536
|
-
|
|
4262
|
+
for (const part of parts) forgetGuard(mls2, part.messageId, sent);
|
|
4537
4263
|
return sent;
|
|
4538
4264
|
}
|
|
4539
4265
|
__name(send, "send");
|
|
4540
|
-
|
|
4266
|
+
const partsOf = /* @__PURE__ */ __name((text3, options2) => encryptedParts(text3, options2.replyAuthority ? MAX_REPLY_PARTS : MAX_GROUP_PARTS), "partsOf");
|
|
4267
|
+
function saveOutput(record) {
|
|
4268
|
+
record.parts = partsOf(record.text, record.options).length;
|
|
4269
|
+
journal.save(record);
|
|
4270
|
+
}
|
|
4271
|
+
__name(saveOutput, "saveOutput");
|
|
4272
|
+
function encryptedMessages(mls2, text3, options2, record) {
|
|
4273
|
+
const all = record && record.parts === void 0 ? [fitEncrypted(text3)] : partsOf(text3, options2);
|
|
4274
|
+
return all.slice(0, record?.parts).map((part, index) => ({
|
|
4275
|
+
text: part,
|
|
4276
|
+
last: index === all.length - 1,
|
|
4277
|
+
messageId: index ? keyed().text("part", mls2.conversationId, options2.messageId, String(index)) : options2.messageId
|
|
4278
|
+
}));
|
|
4279
|
+
}
|
|
4280
|
+
__name(encryptedMessages, "encryptedMessages");
|
|
4281
|
+
async function stopOutput(record) {
|
|
4282
|
+
let queued2 = 0;
|
|
4283
|
+
for (const [index, { messageId }] of encryptedMessages(record.mls, record.text, record.options, record).entries()) {
|
|
4284
|
+
const status = (await endpoint.getOperation(encryptedOperation(record.mls, messageId)))?.status;
|
|
4285
|
+
if (status && status !== "succeeded" && status !== "rejected") queued2 = index + 1;
|
|
4286
|
+
}
|
|
4287
|
+
if (queued2) {
|
|
4288
|
+
const { items: _items, errand: _errand, ...rest } = record;
|
|
4289
|
+
journal.save({ ...rest, turnId: `${record.turnId}:stopped`, inputs: [], parts: queued2 });
|
|
4290
|
+
}
|
|
4291
|
+
journal.remove(record.turnId);
|
|
4292
|
+
}
|
|
4293
|
+
__name(stopOutput, "stopOutput");
|
|
4294
|
+
async function sendEncrypted(audience, messageId, text3, signal, replyAuthority) {
|
|
4541
4295
|
const operationId = encryptedOperation(audience, messageId);
|
|
4542
4296
|
if (!guards.has(operationId)) {
|
|
4543
4297
|
guards.set(operationId, endpoint.registerDeliveryGuard(operationId, () => assertAudience(audience).then(() => true, (error) => {
|
|
@@ -4548,11 +4302,38 @@ ${JSON.stringify(result, null, 2)}`);
|
|
|
4548
4302
|
if (!await endpoint.getOperation(operationId)) await assertAudience(audience);
|
|
4549
4303
|
signal?.throwIfAborted();
|
|
4550
4304
|
await endpoint.executeResult(
|
|
4551
|
-
{ action: "send_message", body: {
|
|
4305
|
+
{ action: "send_message", body: {
|
|
4306
|
+
conversationId: audience.conversationId,
|
|
4307
|
+
messageId,
|
|
4308
|
+
text: text3,
|
|
4309
|
+
contentType: "text",
|
|
4310
|
+
...replyAuthority ? { replyAuthority } : {}
|
|
4311
|
+
} },
|
|
4552
4312
|
{ operationId, guardId: operationId }
|
|
4553
4313
|
);
|
|
4554
4314
|
}
|
|
4555
4315
|
__name(sendEncrypted, "sendEncrypted");
|
|
4316
|
+
async function replyGrant(roomId, source, stopped) {
|
|
4317
|
+
for (let attempt = 0; !options.relay; attempt += 1) {
|
|
4318
|
+
try {
|
|
4319
|
+
await authorize();
|
|
4320
|
+
if (!outboundClosed) return void 0;
|
|
4321
|
+
const { changes } = await endpoint.query(
|
|
4322
|
+
"admit_messages",
|
|
4323
|
+
{ conversationId: roomId, messageIds: [source] }
|
|
4324
|
+
);
|
|
4325
|
+
return changes[0]?.data?.replyAuthority;
|
|
4326
|
+
} catch (error) {
|
|
4327
|
+
if (attempt >= REPLY_GRANT_RETRIES || stopped() || halt.signal.aborted) {
|
|
4328
|
+
warn("no reply authority")(error);
|
|
4329
|
+
return void 0;
|
|
4330
|
+
}
|
|
4331
|
+
await untilHalt(pause2(1e3 * 2 ** attempt));
|
|
4332
|
+
}
|
|
4333
|
+
}
|
|
4334
|
+
return void 0;
|
|
4335
|
+
}
|
|
4336
|
+
__name(replyGrant, "replyGrant");
|
|
4556
4337
|
async function sendEncryptedItems(audience, items, signal) {
|
|
4557
4338
|
if (!items.length) return;
|
|
4558
4339
|
const media = await client.getFileMediaPipeline(options.fetch);
|
|
@@ -4628,8 +4409,9 @@ ${JSON.stringify(result, null, 2)}`);
|
|
|
4628
4409
|
messages: Object.freeze(messages),
|
|
4629
4410
|
history: Object.freeze(history.map((message2) => inputMessage(message2))),
|
|
4630
4411
|
...turn.notes.length ? { ownerNotes: Object.freeze(turn.notes.map((note) => note.text)) } : {},
|
|
4631
|
-
...turn.contexts.length ? { errands: Object.freeze(turn.contexts.map((
|
|
4632
|
-
...turn.errand ? { errand: errandFacts(turn.errand.record) } : {}
|
|
4412
|
+
...turn.contexts.length ? { errands: Object.freeze(turn.contexts.map((record) => errandFacts(record))) } : {},
|
|
4413
|
+
...turn.errand ? { errand: errandFacts(turn.errand.record) } : {},
|
|
4414
|
+
...turn.facts ? { roomFacts: turn.facts } : {}
|
|
4633
4415
|
});
|
|
4634
4416
|
const line = /* @__PURE__ */ __name((message2, mark = "") => promptLine(message2, message2.senderId === agentId ? "You" : message2.senderName ?? message2.senderId, turn.media.get(message2.id), mark), "line");
|
|
4635
4417
|
const open4 = /* @__PURE__ */ __name(() => turn.open && !turn.controller.signal.aborted, "open");
|
|
@@ -4638,25 +4420,21 @@ ${JSON.stringify(result, null, 2)}`);
|
|
|
4638
4420
|
session: turn.session,
|
|
4639
4421
|
// §12.6: an errand turn's prompt is its errand; the owner's message is only its input's record.
|
|
4640
4422
|
prompt: turn.errand ? errandPrompt(turn.errand) : [
|
|
4423
|
+
...turn.facts ? [...factsPrompt(turn.facts, kind), ""] : [],
|
|
4641
4424
|
...turn.notes.flatMap((note) => [NOTE_OPEN, defused(note.text), NOTE_CLOSE, ""]),
|
|
4642
|
-
...turn.contexts.flatMap((
|
|
4643
|
-
...history.length ? ["Earlier in this conversation (context, not new requests):", ...history.map((message2) => line(message2)), "", "New messages:"] : turn.notes.length || turn.contexts.length ? ["New messages:"] : [],
|
|
4425
|
+
...turn.contexts.flatMap((record) => [...contextPrompt(record), ""]),
|
|
4426
|
+
...history.length ? ["Earlier in this conversation (context, not new requests):", ...history.map((message2) => line(message2)), "", "New messages:"] : turn.facts || turn.notes.length || turn.contexts.length ? ["New messages:"] : [],
|
|
4644
4427
|
...turn.inputs.map(({ payload: { message: message2 } }) => line(message2, message2.isOwner === true ? OWNER_MARK : trustedSender(message2) ? TRUSTED_MARK : ""))
|
|
4645
4428
|
].join("\n"),
|
|
4646
4429
|
input,
|
|
4647
4430
|
files: turn.inputs.flatMap(({ payload: { message: message2 } }) => turn.media.get(message2.id)?.files.map((file) => file.path) ?? []),
|
|
4648
4431
|
signal: turn.controller.signal,
|
|
4649
|
-
|
|
4650
|
-
|
|
4651
|
-
|
|
4652
|
-
turn.live.text(snapshot);
|
|
4653
|
-
}
|
|
4432
|
+
// Text and activity are progress (RUN-12), and nothing of them is published (§6).
|
|
4433
|
+
onText: /* @__PURE__ */ __name(() => {
|
|
4434
|
+
if (live()) progress(turn);
|
|
4654
4435
|
}, "onText"),
|
|
4655
|
-
onActivity: /* @__PURE__ */ __name((
|
|
4656
|
-
if (live())
|
|
4657
|
-
progress(turn);
|
|
4658
|
-
turn.live.activity(event);
|
|
4659
|
-
}
|
|
4436
|
+
onActivity: /* @__PURE__ */ __name(() => {
|
|
4437
|
+
if (live()) progress(turn);
|
|
4660
4438
|
}, "onActivity"),
|
|
4661
4439
|
ask: /* @__PURE__ */ __name((request) => live() ? ask(roomId, turn, input.sender, request) : Promise.reject(turnEnded2()), "ask"),
|
|
4662
4440
|
state: driverState(turn.session, open4),
|
|
@@ -4675,7 +4453,7 @@ ${JSON.stringify(result, null, 2)}`);
|
|
|
4675
4453
|
loseMembership(room(roomId));
|
|
4676
4454
|
throw new Error("The agent is no longer a member of this conversation");
|
|
4677
4455
|
}
|
|
4678
|
-
if (
|
|
4456
|
+
if (await readAgain(() => reaudience(roomId, turn), Date.now() + CONNECTOR_RELAY_ANSWER_WITHIN_MS) === "changed") {
|
|
4679
4457
|
if (!turn.controller.signal.aborted) {
|
|
4680
4458
|
void dispose(queued(turn), "settle", "mls-obsolete-audience");
|
|
4681
4459
|
abort(turn, "audience");
|
|
@@ -4698,68 +4476,32 @@ ${JSON.stringify(result, null, 2)}`);
|
|
|
4698
4476
|
if (turn.errand && !turn.errand.reports) return Promise.resolve({ status: "cancelled" });
|
|
4699
4477
|
const signal = AbortSignal.any([turn.controller.signal, turn.ended.signal]);
|
|
4700
4478
|
turn.asking += 1;
|
|
4701
|
-
if (!turn.mls) turn.live.waiting(true);
|
|
4702
4479
|
const answer = (async () => {
|
|
4703
4480
|
try {
|
|
4704
|
-
if (turn.mls
|
|
4481
|
+
if (!turn.mls || !carriesCards(turn.mls) || !mlsAsks) throw new Error("approvals and questions need an encrypted conversation created for them");
|
|
4705
4482
|
const ownerId = await (owner ??= client.getAgentMe().then((me) => me.ownerId, (error) => {
|
|
4706
4483
|
owner = void 0;
|
|
4707
4484
|
throw error;
|
|
4708
4485
|
}));
|
|
4709
4486
|
const secret = request.kind === "text" && request.secret === true;
|
|
4710
4487
|
const responder = secret || sender.type !== "human" ? ownerId : sender.id;
|
|
4711
|
-
const requestId =
|
|
4712
|
-
|
|
4713
|
-
|
|
4714
|
-
|
|
4715
|
-
|
|
4716
|
-
|
|
4717
|
-
|
|
4718
|
-
|
|
4719
|
-
|
|
4720
|
-
|
|
4721
|
-
|
|
4722
|
-
|
|
4723
|
-
|
|
4724
|
-
|
|
4725
|
-
|
|
4726
|
-
|
|
4727
|
-
|
|
4728
|
-
ending: /* @__PURE__ */ __name(() => turn.reason === "shutdown" ? "keep" : turn.reason === "membership" ? "drop" : turn.reason && STOPS.includes(turn.reason) ? "stopped" : "turn_ended", "ending")
|
|
4729
|
-
});
|
|
4730
|
-
}
|
|
4731
|
-
let manager = approvals.get(roomId);
|
|
4732
|
-
if (!manager) approvals.set(roomId, manager = new ApprovalManager(client, agentId, ownerId));
|
|
4733
|
-
const options2 = { requestId, expiresAt, signal };
|
|
4734
|
-
if (request.kind === "approve") {
|
|
4735
|
-
let created = false;
|
|
4736
|
-
const result2 = await manager.requestApproval(roomId, approvalScope(request), {}, {
|
|
4737
|
-
toolSummary: request.title,
|
|
4738
|
-
responseUserId: responder,
|
|
4739
|
-
turnId: turn.id,
|
|
4740
|
-
...request.detail ? { details: [{ label: "Details", value: request.detail.slice(0, 500) }] } : {},
|
|
4741
|
-
...request.risk ? { riskLevel: request.risk, risk: request.risk } : {},
|
|
4742
|
-
ignoreSessionRules: request.allowSession !== true,
|
|
4743
|
-
allowSessionRule: request.allowSession === true
|
|
4744
|
-
}, { ...options2, onCreated: /* @__PURE__ */ __name(() => {
|
|
4745
|
-
created = true;
|
|
4746
|
-
}, "onCreated") });
|
|
4747
|
-
if (created && !result2.respondedBy) return { status: "expired" };
|
|
4748
|
-
return { status: "answered", by: result2.respondedBy ?? ownerId, allow: result2.decision === "allow", scope: !created || result2.sessionRule ? "session" : "once" };
|
|
4749
|
-
}
|
|
4750
|
-
const choices = request.kind === "choose" ? request.options.map(({ id, label }) => ({ label, value: id })) : void 0;
|
|
4751
|
-
const result = await manager.request("input", roomId, {
|
|
4752
|
-
kind: secret ? "secret" : "clarify",
|
|
4753
|
-
prompt: request.question,
|
|
4754
|
-
responseUserId: responder,
|
|
4755
|
-
turnId: turn.id,
|
|
4756
|
-
...secret ? { sensitive: true } : {},
|
|
4757
|
-
...request.kind === "choose" && request.multiple ? { questions: [{ id: "choice", question: request.question, choices, multiSelect: true }] } : choices ? { choices } : {}
|
|
4758
|
-
}, options2);
|
|
4759
|
-
if (result.status !== "submitted") return { status: result.status === "timeout" ? "expired" : "cancelled" };
|
|
4760
|
-
if (request.kind !== "choose") return { status: "answered", by: responder, text: result.value };
|
|
4761
|
-
const picked = result.answers ? Object.values(result.answers).flatMap((entry) => entry.answers) : [result.value];
|
|
4762
|
-
return { status: "answered", by: responder, choices: picked.filter((id) => request.options.some((option) => option.id === id)) };
|
|
4488
|
+
const requestId = keyed().request(roomId, turn.id, request.id);
|
|
4489
|
+
return await mlsAsks.ask({
|
|
4490
|
+
conversationId: roomId,
|
|
4491
|
+
session: turn.session,
|
|
4492
|
+
requestId,
|
|
4493
|
+
responder,
|
|
4494
|
+
ownerId,
|
|
4495
|
+
request,
|
|
4496
|
+
expiresAtMs: Date.now() + askExpiryMs,
|
|
4497
|
+
audience: /* @__PURE__ */ __name(() => turn.mls, "audience"),
|
|
4498
|
+
signal,
|
|
4499
|
+
recheck: /* @__PURE__ */ __name(() => checkAudience(room(roomId)), "recheck"),
|
|
4500
|
+
pending: /* @__PURE__ */ __name(() => turn.live.waiting(true), "pending"),
|
|
4501
|
+
// Stop, and a new member (whose card is void for every device), withdraw a card, and so does the turn's end;
|
|
4502
|
+
// shutdown leaves it to restart recovery, and an agent no longer in the room can send nothing.
|
|
4503
|
+
ending: /* @__PURE__ */ __name(() => turn.reason === "shutdown" ? "keep" : turn.reason === "membership" ? "drop" : turn.reason && STOPS.includes(turn.reason) ? "stopped" : "turn_ended", "ending")
|
|
4504
|
+
});
|
|
4763
4505
|
} catch (error) {
|
|
4764
4506
|
if (signal.aborted) throw turnEnded2();
|
|
4765
4507
|
warn("ask could not be put to anyone")(error);
|
|
@@ -4812,20 +4554,13 @@ ${JSON.stringify(result, null, 2)}`);
|
|
|
4812
4554
|
if (consumed.status !== "signal") return;
|
|
4813
4555
|
const signal = consumed.signal;
|
|
4814
4556
|
const turn = current.turn;
|
|
4815
|
-
if (signal === "stop_and_drop" || signal === "new_session")
|
|
4816
|
-
const dropped = current.queue.splice(0);
|
|
4817
|
-
for (const { payload } of current.mls ? [] : dropped) {
|
|
4818
|
-
void client.updateMessageDisposition(current.id, payload.message.id, "rejected").catch(warn("rejected disposition"));
|
|
4819
|
-
}
|
|
4820
|
-
void dispose(dropped, "cancel", "stop-and-clear");
|
|
4821
|
-
}
|
|
4557
|
+
if (signal === "stop_and_drop" || signal === "new_session") void dispose(current.queue.splice(0), "cancel", "stop-and-clear");
|
|
4822
4558
|
if (turn) {
|
|
4823
4559
|
void dispose(turn.inputs, "cancel", "stopped-before-submit");
|
|
4824
4560
|
abort(turn, signal === "interrupt" ? "stop" : signal === "stop_and_drop" ? "drop" : "new_session");
|
|
4825
4561
|
}
|
|
4826
|
-
(turn?.live ?? livePublisher(current, void 0
|
|
4562
|
+
(turn?.live ?? livePublisher(current, void 0)).end("stopped");
|
|
4827
4563
|
if (signal === "new_session") {
|
|
4828
|
-
approvals.delete(current.id);
|
|
4829
4564
|
const sessions = /* @__PURE__ */ new Set([
|
|
4830
4565
|
turn?.session ?? current.session ?? current.id,
|
|
4831
4566
|
current.session ?? (turn?.mls ? mlsSession(current.id, mlsAudienceEpoch(turn.mls)) : current.id)
|
|
@@ -4860,25 +4595,19 @@ ${JSON.stringify(result, null, 2)}`);
|
|
|
4860
4595
|
const target = room(roomId);
|
|
4861
4596
|
const work = (async () => {
|
|
4862
4597
|
for (const record of records.filter((candidate) => candidate.conversationId === roomId)) {
|
|
4863
|
-
if (record.mls) target.mls = true;
|
|
4864
4598
|
await redeliver(record).catch(warn("journaled reply kept for reconciliation"));
|
|
4865
4599
|
}
|
|
4866
4600
|
const inputs = orphans.filter(({ event }) => event.conversationId === roomId).sort((a, b) => a.createdAt.localeCompare(b.createdAt) || a.event.id.localeCompare(b.event.id));
|
|
4867
4601
|
if (inputs.length) {
|
|
4868
4602
|
const id = turnId(roomId, inputs.map(({ event, handoffGeneration }) => ({ id: event.id, generation: handoffGeneration ?? "" })));
|
|
4869
|
-
const trigger = inputs.at(-1).event.data;
|
|
4870
4603
|
const mode = await endpoint.getConversationContentMode(roomId);
|
|
4871
|
-
if (mode !== "
|
|
4872
|
-
if (mode !== "plaintext" && (!mlsOn || mode !== "mls-dev-text")) refuse(roomId, mlsOn ? "protocol" : "no-device");
|
|
4604
|
+
if (mode !== "mls-dev-text" || !mlsOn) refuse(roomId, mode === "plaintext" ? "plaintext" : mlsOn ? "protocol" : "no-device");
|
|
4873
4605
|
const mls2 = mode === "mls-dev-text" && mlsOn ? (await currentAudience(roomId)).audience : void 0;
|
|
4874
4606
|
const legacy = `${id}:restart`;
|
|
4875
4607
|
const messageId = mls2 && !await endpoint.getOperation(encryptedOperation(mls2, legacy)) ? keyed().text("restart", roomId, id) : legacy;
|
|
4876
|
-
if (
|
|
4877
|
-
else if (await send(roomId, RESTART_NOTICE, outputOptions(id, trigger, "restart", false, messageId), void 0, void 0, mls2) !== "pending") {
|
|
4878
|
-
await settle(inputs.map(({ event }) => event.id), "interrupted-by-restart");
|
|
4879
|
-
}
|
|
4608
|
+
if (!mls2 || await send(RESTART_NOTICE, { messageId }, mls2) !== "pending") await settle(inputs.map(({ event }) => event.id), "interrupted-by-restart");
|
|
4880
4609
|
}
|
|
4881
|
-
livePublisher(target, void 0
|
|
4610
|
+
livePublisher(target, void 0).end("ended");
|
|
4882
4611
|
})().catch(warn("restart recovery"));
|
|
4883
4612
|
target.recovering = work.finally(() => {
|
|
4884
4613
|
target.recovering = void 0;
|
|
@@ -4888,24 +4617,16 @@ ${JSON.stringify(result, null, 2)}`);
|
|
|
4888
4617
|
}
|
|
4889
4618
|
__name(recover, "recover");
|
|
4890
4619
|
async function redeliver(record) {
|
|
4891
|
-
if (record.mls) {
|
|
4892
|
-
if (
|
|
4893
|
-
|
|
4894
|
-
|
|
4895
|
-
try {
|
|
4896
|
-
assertCompletedOutputAudience(record, members, "agent runner");
|
|
4897
|
-
} catch (error) {
|
|
4898
|
-
if (!record.errand) throw error;
|
|
4899
|
-
return finishErrandOutput(record, "refused", "the conversation's members changed before it was sent");
|
|
4900
|
-
}
|
|
4620
|
+
if (!record.mls) {
|
|
4621
|
+
if (record.errand) return finishErrandOutput(record, "refused", "its conversation is not encrypted");
|
|
4622
|
+
if (await settle(record.inputs, "plaintext-output-dropped")) journal.remove(record.turnId);
|
|
4623
|
+
return;
|
|
4901
4624
|
}
|
|
4625
|
+
if (!mlsOn) return refuse(record.conversationId, "no-device");
|
|
4902
4626
|
const sent = await publish(record);
|
|
4903
4627
|
if (sent === "pending") return;
|
|
4904
4628
|
if (record.errand) return finishErrandOutput(record, sent);
|
|
4905
|
-
if (await settle(record.inputs, "recovered"))
|
|
4906
|
-
journal.remove(record.turnId);
|
|
4907
|
-
releaseUploads(record.attachments);
|
|
4908
|
-
}
|
|
4629
|
+
if (await settle(record.inputs, "recovered")) journal.remove(record.turnId);
|
|
4909
4630
|
}
|
|
4910
4631
|
__name(redeliver, "redeliver");
|
|
4911
4632
|
function pendingNotes(roomId) {
|
|
@@ -5003,15 +4724,15 @@ ${JSON.stringify(result, null, 2)}`);
|
|
|
5003
4724
|
turnId: turnId2,
|
|
5004
4725
|
conversationId,
|
|
5005
4726
|
createdAt,
|
|
5006
|
-
audience:
|
|
4727
|
+
audience: null,
|
|
5007
4728
|
inputs: [],
|
|
5008
4729
|
text: parsed.text,
|
|
5009
|
-
options:
|
|
4730
|
+
options: { messageId: keyed().text("final", conversationId, turnId2) },
|
|
5010
4731
|
notice: false,
|
|
5011
4732
|
errand: id,
|
|
5012
|
-
|
|
4733
|
+
mls: target.mls
|
|
5013
4734
|
};
|
|
5014
|
-
|
|
4735
|
+
saveOutput(record);
|
|
5015
4736
|
errands.put({ ...base, status: "sending", updatedAt });
|
|
5016
4737
|
const delivery = publish(record).then((sent) => {
|
|
5017
4738
|
if (sent !== "pending") finishErrandOutput(record, sent);
|
|
@@ -5030,9 +4751,9 @@ ${JSON.stringify(result, null, 2)}`);
|
|
|
5030
4751
|
if (!conversation) return "Canon does not show the agent that conversation";
|
|
5031
4752
|
if (!conversation.memberIds?.includes(agentId)) return "the agent is not a member of that conversation";
|
|
5032
4753
|
const mode = await endpoint.getConversationContentMode(roomId).catch(missing);
|
|
5033
|
-
if (mode === "plaintext") return
|
|
4754
|
+
if (mode === "plaintext") return "it is not encrypted, and the agent takes part only in encrypted conversations";
|
|
5034
4755
|
if (mode !== "mls-dev-text" || !mlsOn) return "it is an encrypted conversation this runner cannot take part in";
|
|
5035
|
-
return
|
|
4756
|
+
return kind === "post" ? { mls: (await currentAudience(roomId)).audience } : {};
|
|
5036
4757
|
}
|
|
5037
4758
|
__name(errandRoom, "errandRoom");
|
|
5038
4759
|
function finishErrandOutput(record, sent, reason = "Canon did not accept it") {
|
|
@@ -5075,8 +4796,8 @@ ${JSON.stringify(result, null, 2)}`);
|
|
|
5075
4796
|
...record.origin ? { origin: record.origin } : {}
|
|
5076
4797
|
}), "errandFacts");
|
|
5077
4798
|
function errandPrompt({ record, target, origin, reports }) {
|
|
5078
|
-
const recipient = "contact" in target ? `${target.contact}. Start a new conversation with them: communicate start_conversation.` : `the
|
|
5079
|
-
const report = reports ? `your final answer is your report to your owner, posted in the conversation ${origin.room}.` :
|
|
4799
|
+
const recipient = "contact" in target ? `${target.contact}. Start a new conversation with them: communicate start_conversation.` : `the encrypted conversation ${target.room}. Send to it with communicate message_existing.`;
|
|
4800
|
+
const report = reports ? `your final answer is your report to your owner, posted in the conversation ${origin.room}.` : "your final answer is your report to your owner, and it stays on their machine.";
|
|
5080
4801
|
return [
|
|
5081
4802
|
`${ERRAND_OPEN} Your owner sends you on an errand, from the machine you run on. It is not a message in any conversation, and no one else sees it.`,
|
|
5082
4803
|
`Why: ${defused(record.why)}`,
|
|
@@ -5088,8 +4809,8 @@ ${JSON.stringify(result, null, 2)}`);
|
|
|
5088
4809
|
].join("\n");
|
|
5089
4810
|
}
|
|
5090
4811
|
__name(errandPrompt, "errandPrompt");
|
|
5091
|
-
function contextPrompt(
|
|
5092
|
-
const report = !record.origin ? "Your owner follows this errand on their machine; it has no conversation to report to." :
|
|
4812
|
+
function contextPrompt(record) {
|
|
4813
|
+
const report = !record.origin ? "Your owner follows this errand on their machine; it has no conversation to report to." : `You may report to your owner in the encrypted conversation ${record.origin} with communicate.`;
|
|
5093
4814
|
return [
|
|
5094
4815
|
`${ERRAND_OPEN} Your owner sent you into this conversation on an errand, from the machine you run on. No one here sees this note.`,
|
|
5095
4816
|
`Why: ${defused(record.why)}`,
|
|
@@ -5107,7 +4828,7 @@ ${JSON.stringify(result, null, 2)}`);
|
|
|
5107
4828
|
}
|
|
5108
4829
|
__name(openErrands, "openErrands");
|
|
5109
4830
|
function replied(turn) {
|
|
5110
|
-
for (const
|
|
4831
|
+
for (const record of turn.contexts) {
|
|
5111
4832
|
const known = latest(record.id);
|
|
5112
4833
|
if (!known || !openErrand(known)) continue;
|
|
5113
4834
|
try {
|
|
@@ -5132,7 +4853,7 @@ ${JSON.stringify(result, null, 2)}`);
|
|
|
5132
4853
|
const turn = newTurn([], errandTurnId(record.id), `errand:${record.id}`);
|
|
5133
4854
|
holder.turn = turn;
|
|
5134
4855
|
errandTurns.set(record.id, turn);
|
|
5135
|
-
(turn.live = origin ? livePublisher(origin, turn
|
|
4856
|
+
(turn.live = origin ? livePublisher(origin, turn) : SILENT).open();
|
|
5136
4857
|
const done = executeErrand(holder, turn, record).catch(warn(`errand ${record.id}`)).finally(() => {
|
|
5137
4858
|
turn.live.end("ended");
|
|
5138
4859
|
if (holder.turn === turn) holder.turn = void 0;
|
|
@@ -5167,7 +4888,6 @@ ${JSON.stringify(result, null, 2)}`);
|
|
|
5167
4888
|
if (typeof setup === "string") return void saveErrand({ ...known, status: "refused", reason: setup });
|
|
5168
4889
|
turn.errand = setup;
|
|
5169
4890
|
turn.mls = setup.origin?.mls;
|
|
5170
|
-
if (turn.mls) current.mls = true;
|
|
5171
4891
|
const message2 = {
|
|
5172
4892
|
id: `${turn.id}-errand`,
|
|
5173
4893
|
senderId: setup.owner,
|
|
@@ -5210,16 +4930,15 @@ ${JSON.stringify(result, null, 2)}`);
|
|
|
5210
4930
|
if (record.target === "room") {
|
|
5211
4931
|
const found = await errandRoom(record.to, "post");
|
|
5212
4932
|
if (typeof found === "string") return `its recipient: ${found}`;
|
|
5213
|
-
target = { room: record.to
|
|
4933
|
+
target = { room: record.to };
|
|
5214
4934
|
}
|
|
5215
4935
|
let origin;
|
|
5216
4936
|
if (record.origin) {
|
|
5217
4937
|
const found = await errandRoom(record.origin, "post");
|
|
5218
4938
|
if (typeof found === "string") return `its origin: ${found}`;
|
|
5219
|
-
origin = { room: record.origin,
|
|
4939
|
+
origin = { room: record.origin, mls: found.mls };
|
|
5220
4940
|
}
|
|
5221
|
-
|
|
5222
|
-
return { record, owner: owner2, target, ...origin ? { origin } : {}, reports };
|
|
4941
|
+
return { record, owner: owner2, target, ...origin ? { origin } : {}, reports: !!origin };
|
|
5223
4942
|
}
|
|
5224
4943
|
__name(errandSetup, "errandSetup");
|
|
5225
4944
|
async function deliverReport(turn, outcome) {
|
|
@@ -5252,19 +4971,16 @@ ${JSON.stringify(result, null, 2)}`);
|
|
|
5252
4971
|
turnId: turn.id,
|
|
5253
4972
|
conversationId: origin.room,
|
|
5254
4973
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5255
|
-
audience:
|
|
4974
|
+
audience: null,
|
|
5256
4975
|
inputs: [],
|
|
5257
4976
|
text: text3,
|
|
5258
4977
|
notice: failed,
|
|
5259
4978
|
errand: record.id,
|
|
5260
|
-
|
|
5261
|
-
options:
|
|
5262
|
-
messageId: `${turn.id}:${kind}`,
|
|
5263
|
-
metadata: { turnId: turn.id, turnSemantics: "turn_complete", ...failed ? { replyBehavior: "suppress_auto_reply" } : {} }
|
|
5264
|
-
}
|
|
4979
|
+
mls: origin.mls,
|
|
4980
|
+
options: { messageId: keyed().text(kind, origin.room, turn.id) }
|
|
5265
4981
|
};
|
|
5266
4982
|
try {
|
|
5267
|
-
|
|
4983
|
+
saveOutput(out);
|
|
5268
4984
|
} catch (error) {
|
|
5269
4985
|
warn(`errand ${record.id}: its report could not be journaled; it stays in the record`)(error);
|
|
5270
4986
|
saveErrand({ ...closing, report: text3, reported: "refused", reason: "its report: it could not be saved for sending" });
|
|
@@ -5273,9 +4989,9 @@ ${JSON.stringify(result, null, 2)}`);
|
|
|
5273
4989
|
saveErrand({ ...closing, reported: "sending" });
|
|
5274
4990
|
const sent = await publish(out, turn.retire.signal);
|
|
5275
4991
|
if (sent === "pending" && stopped()) {
|
|
5276
|
-
|
|
5277
|
-
const { reported: _reported, ...
|
|
5278
|
-
saveErrand({ ...
|
|
4992
|
+
await stopOutput(out);
|
|
4993
|
+
const { reported: _reported, ...withheld2 } = latest(record.id) ?? closing;
|
|
4994
|
+
saveErrand({ ...withheld2, status: "cancelled", reason: STOPPED_ERRAND[turn.reason] ?? "stopped" });
|
|
5279
4995
|
} else if (sent !== "pending") finishErrandOutput(out, sent);
|
|
5280
4996
|
turn.live.end(sent === "delivered" ? "answered" : stopped() ? "stopped" : "ended");
|
|
5281
4997
|
}
|
|
@@ -5309,7 +5025,6 @@ ${JSON.stringify(result, null, 2)}`);
|
|
|
5309
5025
|
} else if (event.kind === "conversation.updated") {
|
|
5310
5026
|
void onRoomChanged(data).catch(warn("membership update"));
|
|
5311
5027
|
} else if (event.kind === "runtime.control" && data.kind === "signal" && typeof data.conversationId === "string") {
|
|
5312
|
-
if (options.relay && !rooms.get(data.conversationId)?.mls) return;
|
|
5313
5028
|
scheduleControl(room(data.conversationId));
|
|
5314
5029
|
}
|
|
5315
5030
|
}
|
|
@@ -5373,7 +5088,7 @@ ${JSON.stringify(result, null, 2)}`);
|
|
|
5373
5088
|
notice: false,
|
|
5374
5089
|
mls: audience
|
|
5375
5090
|
};
|
|
5376
|
-
|
|
5091
|
+
saveOutput(record);
|
|
5377
5092
|
return { text: text3, record };
|
|
5378
5093
|
})();
|
|
5379
5094
|
const sent = ready.then(({ record, settled }) => settled ?? publish(record).then((result) => {
|
|
@@ -5462,7 +5177,7 @@ ${JSON.stringify(result, null, 2)}`);
|
|
|
5462
5177
|
}
|
|
5463
5178
|
journal = createCompletedOutputJournal(join7(home, "runner", options.environmentId, agentId, "finals"), scope, {
|
|
5464
5179
|
label: "agent runner",
|
|
5465
|
-
validate: /* @__PURE__ */ __name((record) => typeof record.text === "string" && typeof record.options?.messageId === "string" && Array.isArray(record.inputs) && typeof record.notice === "boolean" && (record.
|
|
5180
|
+
validate: /* @__PURE__ */ __name((record) => typeof record.text === "string" && typeof record.options?.messageId === "string" && Array.isArray(record.inputs) && typeof record.notice === "boolean" && (record.mls === void 0 || !!record.mls && typeof record.mls === "object") && (record.parts === void 0 || Number.isSafeInteger(record.parts)) && (record.errand === void 0 || typeof record.errand === "string"), "validate")
|
|
5466
5181
|
});
|
|
5467
5182
|
if (!options.relay && !driver.capabilities.remote) {
|
|
5468
5183
|
errands = errandJournal(errandsDirectory(home, options.environmentId, agentId));
|
|
@@ -5480,7 +5195,7 @@ ${JSON.stringify(result, null, 2)}`);
|
|
|
5480
5195
|
else if (report) saveErrand({ ...record, status: ended(record, report.notice), reported: "sending" });
|
|
5481
5196
|
else {
|
|
5482
5197
|
saveErrand({ ...record, status: "interrupted", reason: "the runner stopped during its turn, which never runs again" });
|
|
5483
|
-
if (record.origin) livePublisher(room(record.origin), void 0
|
|
5198
|
+
if (record.origin) livePublisher(room(record.origin), void 0).end("stopped");
|
|
5484
5199
|
}
|
|
5485
5200
|
}
|
|
5486
5201
|
}
|