letagents 0.12.12 → 0.12.14
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/dist/mcp/git-remote.js +7 -7
- package/dist/mcp/local-state/agent-sessions.js +63 -5
- package/dist/mcp/local-state/local-chat.js +189 -48
- package/dist/mcp/local-state/storage.js +16 -9
- package/dist/mcp/server/daemon-tool-executor.js +92 -0
- package/dist/mcp/server/register-tools.js +4 -2
- package/dist/mcp/server/runtime/agent-sessions.js +16 -7
- package/dist/mcp/server/runtime/api.js +11 -3
- package/dist/mcp/server/runtime/daemon-tool-context.js +11 -0
- package/dist/mcp/server/runtime/execution-profile.js +1 -0
- package/dist/mcp/server/runtime/messages.js +55 -0
- package/dist/mcp/server/runtime/presence.js +3 -3
- package/dist/mcp/server/runtime/room-api.js +2 -2
- package/dist/mcp/server/runtime/room-state.js +19 -9
- package/dist/mcp/server/runtime/rooms.js +54 -33
- package/dist/mcp/server/runtime/supervisor-bridge.js +129 -11
- package/dist/mcp/server/runtime/tool-surface-policy.js +7 -0
- package/dist/mcp/server/runtime/worker-bearer.js +17 -2
- package/dist/mcp/server/runtime-contract.js +1 -0
- package/dist/mcp/server/runtime.js +6 -6
- package/dist/mcp/server/supervised-tool-facade.js +107 -6
- package/dist/mcp/server/tools/messages/read-tool.js +54 -97
- package/dist/mcp/server/tools/messages/reasoning-tool.js +2 -0
- package/dist/mcp/server/tools/messages/send-tool.js +2 -0
- package/dist/mcp/server/tools/messages/status-tool.js +2 -0
- package/dist/mcp/server/tools/messages/wait-tool.js +303 -71
- package/dist/mcp/server/tools/onboarding/status-tool.js +4 -4
- package/dist/mcp/server/tools/rooms/inspection-tools.js +15 -10
- package/dist/mcp/server/tools/rooms/repo-initialization-tool.js +2 -1
- package/dist/mcp/server/tools/tasks/board-tools.js +34 -2
- package/dist/mcp/sse-client.js +163 -20
- package/dist/shared/activation-routing.js +163 -23
- package/dist/shared/agent-presence.js +6 -0
- package/dist/shared/desktop-release-manifest.js +63 -0
- package/dist/shared/desktop-release.js +60 -0
- package/dist/shared/scoped-ids.js +6 -0
- package/package.json +7 -3
- package/shared/execution-approval-projection.d.mts +32 -0
- package/shared/execution-approval-projection.mjs +107 -0
- package/shared/execution-approval-publication-item.d.mts +20 -0
- package/shared/execution-approval-publication-item.mjs +61 -0
- package/shared/execution-approval-publication.d.mts +53 -0
- package/shared/execution-approval-publication.mjs +136 -0
- package/shared/execution-delegation-decision.d.mts +37 -0
- package/shared/execution-delegation-decision.mjs +73 -0
- package/shared/message-contracts.d.mts +32 -0
- package/shared/message-contracts.mjs +109 -0
- package/shared/room-agent-work.d.mts +31 -0
- package/shared/room-agent-work.mjs +44 -0
- package/shared/room-resource-invalidation.d.mts +38 -0
- package/shared/room-resource-invalidation.mjs +50 -0
- package/shared/routing-aliases.d.mts +18 -0
- package/shared/routing-aliases.mjs +66 -0
- package/shared/sqlite-thread-routing.d.mts +72 -0
- package/shared/sqlite-thread-routing.mjs +1038 -0
|
@@ -3,14 +3,89 @@ import { lstat, readFile, realpath } from "node:fs/promises";
|
|
|
3
3
|
import { createConnection } from "node:net";
|
|
4
4
|
import { homedir } from "node:os";
|
|
5
5
|
import { join } from "node:path";
|
|
6
|
+
import { parsePositivePgIntegerScopedId } from "../../../../shared/message-contracts.mjs";
|
|
6
7
|
import { getCurrentSupervisedRoomAuthority } from "./supervised-room-authority.js";
|
|
7
8
|
const NEGOTIATION_PROTOCOL_VERSION = 1;
|
|
8
|
-
const SUPPORTED_SUPERVISOR_PROTOCOL_VERSIONS = new Set([1, 2]);
|
|
9
|
+
const SUPPORTED_SUPERVISOR_PROTOCOL_VERSIONS = new Set([1, 2, 3]);
|
|
9
10
|
const DEFAULT_REQUEST_TIMEOUT_MS = 5_000;
|
|
10
11
|
const CONFIRMED_BINDING_VERIFY_TIMEOUT_MS = 250;
|
|
11
12
|
const SUPERVISOR_CONTEXT_FILE = ".letagents-supervisor-context.json";
|
|
12
13
|
const WORK_ATTEMPT_MARKER_FILE = ".letagents-work-attempt.json";
|
|
13
14
|
const MAX_SUPERVISOR_CONTEXT_BYTES = 4 * 1024;
|
|
15
|
+
// An SDK request id is unique only within this MCP process lifetime.
|
|
16
|
+
const CUSTODIAL_POLLING_PROCESS_INCARNATION_ID = randomUUID();
|
|
17
|
+
/** Release reuses BEFORE's exact generation; it never authorizes against a successor. */
|
|
18
|
+
export async function authorizeCustodialPolling(toolName, prior, env = process.env, options = {}, waitRequest) {
|
|
19
|
+
const wait = waitRequest ? { ...waitRequest } : undefined;
|
|
20
|
+
if (toolName === "wait_for_messages") {
|
|
21
|
+
if (!wait || !(typeof wait.mcpRequestId === "string" || Number.isSafeInteger(wait.mcpRequestId))) {
|
|
22
|
+
throw new Error("Custodial wait is missing its exact MCP request id.");
|
|
23
|
+
}
|
|
24
|
+
if (!(wait.roomCursor === null || (typeof wait.roomCursor === "string" && parseRoomMessageNumber(wait.roomCursor) !== null))
|
|
25
|
+
|| (prior && (!prior.wait || prior.wait.processIncarnationId !== CUSTODIAL_POLLING_PROCESS_INCARNATION_ID
|
|
26
|
+
|| prior.wait.mcpRequestId !== wait.mcpRequestId || typeof wait.offeredFrontier !== "string"
|
|
27
|
+
|| parseRoomMessageNumber(wait.offeredFrontier) === null || prior.roomCursor === null
|
|
28
|
+
|| parseRoomMessageNumber(wait.offeredFrontier) < parseRoomMessageNumber(prior.roomCursor)))) {
|
|
29
|
+
throw new Error("Custodial wait receipt does not match its original invocation and cursor.");
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
else if (wait || prior?.wait)
|
|
33
|
+
throw new Error("Only custodial wait may acknowledge or offer a cursor.");
|
|
34
|
+
if (env.LETAGENTS_EXECUTION_PROFILE?.trim() !== "supervised_mcp_polling")
|
|
35
|
+
throw new Error("Custodial polling profile required.");
|
|
36
|
+
if (env.LETAGENTS_SUPERVISED_BOUNDED_TURNS?.trim() === "1"
|
|
37
|
+
|| env.LETAGENTS_TOKEN?.trim() || env.LETAGENTS_AGENT_SESSION_BEARER?.trim()) {
|
|
38
|
+
throw new Error("Custodial polling refuses bounded flags and environment credentials.");
|
|
39
|
+
}
|
|
40
|
+
const coordinates = prior?.coordinates ?? await resolveSupervisorCoordinates(supervisedContextSession(env), env, options);
|
|
41
|
+
if (!coordinates?.roomId || !coordinates.agentSessionId)
|
|
42
|
+
throw new Error("Custodial polling lacks exact worker coordinates.");
|
|
43
|
+
if ((wait?.requestedRoomId && wait.requestedRoomId !== coordinates.roomId)
|
|
44
|
+
|| (wait?.requestedAgentSessionId && wait.requestedAgentSessionId !== coordinates.agentSessionId)) {
|
|
45
|
+
throw new Error("Custodial wait room or worker identity does not match its exact authority.");
|
|
46
|
+
}
|
|
47
|
+
const timeoutMs = options.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
|
|
48
|
+
const negotiated = prior?.negotiated ?? await negotiateSupervisor(coordinates.socketPath, timeoutMs);
|
|
49
|
+
if (!negotiated.custodialPollingV1 || negotiated.generation === null)
|
|
50
|
+
throw new Error("Daemon does not support custodial_polling_v1.");
|
|
51
|
+
if (wait && !negotiated.custodialPollingOffersV1)
|
|
52
|
+
throw new Error("Daemon does not support custodialPollingOffersV1; refusing an unjournaled wait.");
|
|
53
|
+
const apiUrl = prior?.apiUrl ?? env.LETAGENTS_API_URL?.trim();
|
|
54
|
+
if (!apiUrl)
|
|
55
|
+
throw new Error("Custodial polling requires an explicit API URL.");
|
|
56
|
+
const response = await supervisorRequest(coordinates.socketPath, {
|
|
57
|
+
version: negotiated.protocolVersion, id: randomUUID(), method: "supervisor.authorize_custodial_polling",
|
|
58
|
+
params: {
|
|
59
|
+
entry_id: coordinates.entryId, room_id: coordinates.roomId, work_attempt_id: coordinates.workAttemptId,
|
|
60
|
+
execution_generation_id: coordinates.executionGenerationId, agent_session_id: coordinates.agentSessionId,
|
|
61
|
+
daemon_generation: negotiated.generation, api_url: apiUrl, contract: "custodial_polling_v1",
|
|
62
|
+
phase: prior ? "release" : "before", tool_name: toolName,
|
|
63
|
+
...(prior ? { expected_configuration_revision: prior.configurationRevision } : {}),
|
|
64
|
+
...(wait ? {
|
|
65
|
+
process_incarnation_id: CUSTODIAL_POLLING_PROCESS_INCARNATION_ID, mcp_request_id: wait.mcpRequestId,
|
|
66
|
+
...(prior ? { expected_activation_id: prior.wait.activationId, expected_binding_epoch: prior.wait.bindingEpoch,
|
|
67
|
+
input_cursor: prior.roomCursor, offered_frontier: wait.offeredFrontier } : { room_cursor: wait.roomCursor }),
|
|
68
|
+
} : {}),
|
|
69
|
+
},
|
|
70
|
+
}, timeoutMs);
|
|
71
|
+
const result = response.result;
|
|
72
|
+
if (!response.ok || response.version !== negotiated.protocolVersion || !result || result.status !== "authorized" || result.contract !== "custodial_polling_v1"
|
|
73
|
+
|| result.room_id !== coordinates.roomId || result.agent_session_id !== coordinates.agentSessionId
|
|
74
|
+
|| !Number.isSafeInteger(result.configuration_revision) || Number(result.configuration_revision) < 1
|
|
75
|
+
|| (prior && result.configuration_revision !== prior.configurationRevision)
|
|
76
|
+
|| (wait && (typeof result.activation_id !== "string" || !result.activation_id.trim()
|
|
77
|
+
|| !Number.isSafeInteger(result.binding_epoch) || Number(result.binding_epoch) < 1
|
|
78
|
+
|| typeof result.room_cursor !== "string"
|
|
79
|
+
|| (prior && (result.activation_id !== prior.wait.activationId || result.binding_epoch !== prior.wait.bindingEpoch
|
|
80
|
+
|| result.room_cursor !== prior.roomCursor))))
|
|
81
|
+
|| !(result.room_cursor === null || (typeof result.room_cursor === "string" && parseRoomMessageNumber(result.room_cursor) !== null))) {
|
|
82
|
+
throw new Error("Custodial polling authority was rejected or became stale.");
|
|
83
|
+
}
|
|
84
|
+
return { coordinates, negotiated, apiUrl, roomId: coordinates.roomId, agentSessionId: coordinates.agentSessionId,
|
|
85
|
+
roomCursor: result.room_cursor, configurationRevision: Number(result.configuration_revision),
|
|
86
|
+
...(wait ? { wait: { processIncarnationId: CUSTODIAL_POLLING_PROCESS_INCARNATION_ID, mcpRequestId: wait.mcpRequestId,
|
|
87
|
+
activationId: String(result.activation_id), bindingEpoch: Number(result.binding_epoch) } } : {}) };
|
|
88
|
+
}
|
|
14
89
|
const confirmedBindingsBySession = new Map();
|
|
15
90
|
const confirmedRequestsBySession = new Map();
|
|
16
91
|
const confirmedProtocolsBySession = new Map();
|
|
@@ -18,6 +93,42 @@ const pendingCursorCheckpoints = new Map();
|
|
|
18
93
|
const activeCursorCheckpointDrains = new Set();
|
|
19
94
|
const cursorCheckpointRetryTimers = new Map();
|
|
20
95
|
const CURSOR_CHECKPOINT_RETRY_DELAYS_MS = [250, 1_000, 3_000];
|
|
96
|
+
export async function executeCurrentSupervisedTool(input, env = process.env, options = {}) {
|
|
97
|
+
const coordinates = await requireCurrentSupervisedCoordinates(env, options);
|
|
98
|
+
const timeoutMs = options.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
|
|
99
|
+
const negotiated = await negotiateSupervisor(coordinates.socketPath, timeoutMs);
|
|
100
|
+
if (negotiated.generation === null)
|
|
101
|
+
throw new Error("The supervised daemon generation is unavailable.");
|
|
102
|
+
const response = await supervisorRequest(coordinates.socketPath, {
|
|
103
|
+
version: negotiated.protocolVersion,
|
|
104
|
+
id: randomUUID(),
|
|
105
|
+
method: "supervisor.execute_bounded_tool",
|
|
106
|
+
params: {
|
|
107
|
+
entry_id: coordinates.entryId,
|
|
108
|
+
work_attempt_id: coordinates.workAttemptId,
|
|
109
|
+
execution_generation_id: coordinates.executionGenerationId,
|
|
110
|
+
...(coordinates.providerTurnId ? { provider_turn_id: coordinates.providerTurnId } : {}),
|
|
111
|
+
daemon_generation: negotiated.generation,
|
|
112
|
+
mcp_request_id: input.mcpRequestId,
|
|
113
|
+
tool_name: input.toolName,
|
|
114
|
+
input: input.input,
|
|
115
|
+
},
|
|
116
|
+
}, null);
|
|
117
|
+
if (!response.ok) {
|
|
118
|
+
if (/Unsupported daemon method:\s*supervisor\.execute_bounded_tool/i.test(response.error ?? "")) {
|
|
119
|
+
return { state: "unsupported" };
|
|
120
|
+
}
|
|
121
|
+
throw new Error(response.error || "The daemon-owned supervised tool was rejected.");
|
|
122
|
+
}
|
|
123
|
+
const result = response.result && typeof response.result === "object"
|
|
124
|
+
? response.result
|
|
125
|
+
: {};
|
|
126
|
+
const roomId = typeof result.room_id === "string" ? result.room_id.trim() : "";
|
|
127
|
+
if (!roomId || roomId.length > 1_024 || /[\u0000-\u001f\u007f]/.test(roomId)) {
|
|
128
|
+
throw new Error("The supervised daemon did not return valid exact room authority.");
|
|
129
|
+
}
|
|
130
|
+
return { state: "completed", roomId, result: result.result };
|
|
131
|
+
}
|
|
21
132
|
export async function prepareCurrentSupervisedEffect(input, env = process.env, options = {}) {
|
|
22
133
|
const coordinates = await requireCurrentSupervisedCoordinates(env, options);
|
|
23
134
|
const timeoutMs = options.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
|
|
@@ -147,7 +258,8 @@ async function requireCurrentSupervisedCoordinates(env, options) {
|
|
|
147
258
|
* authority for the exact worker session identity.
|
|
148
259
|
*/
|
|
149
260
|
export async function borrowCurrentSupervisedWorkerCredential(env = process.env, options = {}) {
|
|
150
|
-
if (env.LETAGENTS_SUPERVISED_BOUNDED_TURNS?.trim() !== "1"
|
|
261
|
+
if (env.LETAGENTS_SUPERVISED_BOUNDED_TURNS?.trim() !== "1"
|
|
262
|
+
&& env.LETAGENTS_EXECUTION_PROFILE?.trim() !== "supervised_mcp_polling") {
|
|
151
263
|
return { state: "not_supervised" };
|
|
152
264
|
}
|
|
153
265
|
const seed = supervisedContextSession(env);
|
|
@@ -412,6 +524,8 @@ export function scheduleSupervisedWorkerCursorCheckpoint(session, roomCursor, en
|
|
|
412
524
|
});
|
|
413
525
|
}
|
|
414
526
|
async function enqueueSupervisedWorkerCursorCheckpoint(session, roomCursor, env, options) {
|
|
527
|
+
if (parseRoomMessageNumber(roomCursor) === null)
|
|
528
|
+
return;
|
|
415
529
|
const coordinates = await resolveSupervisorCoordinates(session, env, options);
|
|
416
530
|
if (!coordinates)
|
|
417
531
|
return;
|
|
@@ -509,13 +623,14 @@ function isNewerRoomCursor(candidate, current) {
|
|
|
509
623
|
return false;
|
|
510
624
|
const candidateNumber = parseRoomMessageNumber(candidate);
|
|
511
625
|
const currentNumber = parseRoomMessageNumber(current);
|
|
512
|
-
if (candidateNumber
|
|
513
|
-
return
|
|
514
|
-
|
|
626
|
+
if (candidateNumber === null)
|
|
627
|
+
return false;
|
|
628
|
+
if (currentNumber === null)
|
|
629
|
+
return true;
|
|
630
|
+
return candidateNumber > currentNumber;
|
|
515
631
|
}
|
|
516
632
|
function parseRoomMessageNumber(cursor) {
|
|
517
|
-
|
|
518
|
-
return match ? BigInt(match[1]) : null;
|
|
633
|
+
return parsePositivePgIntegerScopedId(cursor, "msg");
|
|
519
634
|
}
|
|
520
635
|
/** Transport failures are retryable bookkeeping failures, not worker failures. */
|
|
521
636
|
export function isRetryableSupervisorBridgeError(error) {
|
|
@@ -688,23 +803,26 @@ async function negotiateSupervisor(socketPath, timeoutMs) {
|
|
|
688
803
|
const daemonIdentity = hasCompleteIdentity
|
|
689
804
|
? [result.generation, result.pid, result.started_at].join(":")
|
|
690
805
|
: null;
|
|
691
|
-
return { protocolVersion, daemonIdentity, generation: hasCompleteIdentity ? Number(result.generation) : null
|
|
806
|
+
return { protocolVersion, daemonIdentity, generation: hasCompleteIdentity ? Number(result.generation) : null,
|
|
807
|
+
custodialPollingV1: result.capabilities?.custodialPollingV1 === true,
|
|
808
|
+
custodialPollingOffersV1: result.capabilities?.custodialPollingOffersV1 === true };
|
|
692
809
|
}
|
|
693
810
|
function supervisorRequest(socketPath, request, timeoutMs) {
|
|
694
811
|
return new Promise((resolve, reject) => {
|
|
695
812
|
const socket = createConnection(socketPath);
|
|
696
813
|
let buffer = "";
|
|
697
814
|
let finished = false;
|
|
698
|
-
const timer = setTimeout(() => {
|
|
815
|
+
const timer = timeoutMs === null ? null : setTimeout(() => {
|
|
699
816
|
socket.destroy();
|
|
700
817
|
finish(() => reject(new Error("Timed out communicating with the supervisor daemon.")));
|
|
701
818
|
}, timeoutMs);
|
|
702
|
-
timer
|
|
819
|
+
timer?.unref();
|
|
703
820
|
const finish = (operation) => {
|
|
704
821
|
if (finished)
|
|
705
822
|
return;
|
|
706
823
|
finished = true;
|
|
707
|
-
|
|
824
|
+
if (timer)
|
|
825
|
+
clearTimeout(timer);
|
|
708
826
|
operation();
|
|
709
827
|
};
|
|
710
828
|
socket.setEncoding("utf8");
|
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
const TOOL_SURFACE_BY_PROFILE = {
|
|
2
|
+
supervised_mcp_polling: {
|
|
3
|
+
agentSessionLifecycle: false,
|
|
4
|
+
deliveryLoop: true,
|
|
5
|
+
onboarding: false,
|
|
6
|
+
rental: false,
|
|
7
|
+
roomResume: false,
|
|
8
|
+
},
|
|
2
9
|
supervised_room_turn: {
|
|
3
10
|
agentSessionLifecycle: false,
|
|
4
11
|
deliveryLoop: false,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { getDaemonToolExecutionContext } from "./daemon-tool-context.js";
|
|
1
2
|
export const LETAGENTS_AGENT_SESSION_BEARER_ENV = "LETAGENTS_AGENT_SESSION_BEARER";
|
|
2
3
|
export const LETAGENTS_SUPERVISED_BOUNDED_TURNS_ENV = "LETAGENTS_SUPERVISED_BOUNDED_TURNS";
|
|
3
4
|
export class WorkerBearerRuntimeConfigurationError extends Error {
|
|
@@ -7,16 +8,22 @@ export class WorkerBearerRuntimeConfigurationError extends Error {
|
|
|
7
8
|
}
|
|
8
9
|
}
|
|
9
10
|
export function getWorkerBearerRuntime() {
|
|
11
|
+
if (getDaemonToolExecutionContext())
|
|
12
|
+
return { mode: "supervised" };
|
|
10
13
|
const bearer = process.env.LETAGENTS_AGENT_SESSION_BEARER?.trim();
|
|
11
14
|
const supervised = process.env.LETAGENTS_SUPERVISED_BOUNDED_TURNS?.trim() === "1";
|
|
12
15
|
const profile = process.env.LETAGENTS_EXECUTION_PROFILE?.trim();
|
|
16
|
+
const polling = profile === "supervised_mcp_polling";
|
|
13
17
|
if (supervised !== (profile === "supervised_room_turn")) {
|
|
14
18
|
return {
|
|
15
19
|
mode: "invalid",
|
|
16
20
|
error: "LETAGENTS_EXECUTION_PROFILE=supervised_room_turn and LETAGENTS_SUPERVISED_BOUNDED_TURNS=1 must be configured together.",
|
|
17
21
|
};
|
|
18
22
|
}
|
|
19
|
-
if (
|
|
23
|
+
if (polling && (bearer || process.env.LETAGENTS_TOKEN?.trim())) {
|
|
24
|
+
return { mode: "invalid", error: "Custodial polling refuses environment credentials; borrow exact daemon worker authority." };
|
|
25
|
+
}
|
|
26
|
+
if (!bearer && !supervised && !polling)
|
|
20
27
|
return { mode: "owner" };
|
|
21
28
|
if (bearer && supervised) {
|
|
22
29
|
return {
|
|
@@ -66,6 +73,13 @@ export function requireValidWorkerBearerRuntime() {
|
|
|
66
73
|
return runtime;
|
|
67
74
|
}
|
|
68
75
|
export function isSupervisedBoundedTurn() {
|
|
76
|
+
return hasSupervisedWorkerAuthority() && !isCustodialPolling();
|
|
77
|
+
}
|
|
78
|
+
export function isCustodialPolling() {
|
|
79
|
+
return process.env.LETAGENTS_EXECUTION_PROFILE?.trim() === "supervised_mcp_polling";
|
|
80
|
+
}
|
|
81
|
+
/** Credential custody is independent of who owns room delivery. */
|
|
82
|
+
export function hasSupervisedWorkerAuthority() {
|
|
69
83
|
return requireValidWorkerBearerRuntime().mode === "supervised";
|
|
70
84
|
}
|
|
71
85
|
export function workerModeDisabledToolResult(toolDescription = "This owner-auth onboarding tool") {
|
|
@@ -90,7 +104,8 @@ export function workerModeDisabledToolResult(toolDescription = "This owner-auth
|
|
|
90
104
|
* asks it to do so.
|
|
91
105
|
*/
|
|
92
106
|
export function supervisedBoundedDeliveryDisabledToolResult(toolName = "wait_for_messages") {
|
|
93
|
-
if (
|
|
107
|
+
if (!getDaemonToolExecutionContext()
|
|
108
|
+
&& process.env.LETAGENTS_SUPERVISED_BOUNDED_TURNS?.trim() !== "1") {
|
|
94
109
|
return null;
|
|
95
110
|
}
|
|
96
111
|
return {
|
|
@@ -19,6 +19,7 @@ export function letAgentsRuntimeContract() {
|
|
|
19
19
|
return {
|
|
20
20
|
format: 1,
|
|
21
21
|
profiles: {
|
|
22
|
+
supervised_mcp_polling: { contract: "custodial_polling_v1", tools: registeredToolNames("supervised_mcp_polling", "codex") },
|
|
22
23
|
cursor_supervised_room_turn: {
|
|
23
24
|
tools: registeredToolNames("supervised_room_turn", "cursor"),
|
|
24
25
|
},
|
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
// in src/mcp/server/runtime/* so tool modules can import focused responsibilities
|
|
3
3
|
// without turning this file back into the runtime god module.
|
|
4
4
|
import { isLocalRoomStorageEnabled as isStoredLocalRoomStorageEnabled, touchRoomSession as touchStoredRoomSession, } from "../local-state.js";
|
|
5
|
-
import {
|
|
5
|
+
import { hasSupervisedWorkerAuthority } from "./runtime/worker-bearer.js";
|
|
6
6
|
/** A daemon-supervised turn must always use its exact cloud worker route. */
|
|
7
7
|
export async function isLocalRoomStorageEnabled(roomId) {
|
|
8
|
-
return !
|
|
8
|
+
return !hasSupervisedWorkerAuthority() && isStoredLocalRoomStorageEnabled(roomId);
|
|
9
9
|
}
|
|
10
10
|
export function touchRoomSession(roomId, lastMessageId) {
|
|
11
|
-
if (!
|
|
11
|
+
if (!hasSupervisedWorkerAuthority())
|
|
12
12
|
touchStoredRoomSession(roomId, lastMessageId);
|
|
13
13
|
}
|
|
14
14
|
export { API_URL, ApiError, apiCall, getAuthorizationHeader, getLetagentsToken, isMissingRouteError, parseApiErrorPayload, resolveApiPath, } from "./runtime/api.js";
|
|
@@ -16,10 +16,10 @@ export { clearAuthenticatedAccountCache, getAuthenticatedAccountCache, setAuthen
|
|
|
16
16
|
export { RepoRoomAuthRequiredError, maybeHandleRepoRoomAuthRequired, startPendingDeviceAuth, toRepoRoomAuthRequiredResult, } from "./runtime/device-auth.js";
|
|
17
17
|
export { AGENT_INSTANCE_UUID, currentAgentIdentity, currentAgentIdentityKey, detectAgentIdeLabel, detectAgentRuntimeLabel, ensureAgentIdentity, getConversationIdentity, getSessionLivenessRegistration, resolveOwnerContext, setConversationIdentity, storeCurrentAgentIdentity, toPublicAgentIdentity, withAgentIdentity, } from "./runtime/identity.js";
|
|
18
18
|
export { agentSessionCredentials, buildAgentDeliveryHeaders, ensureLocalWorkerAgentSession, getAgentSessionRepoBranch, identityFromAgentSession, requireWorkerAgentSession, resolveAgentSession, resolveClientRequestedBase, resolveWorkerToolIdentity, toPublicAgentSession, WORKER_BEARER_AGENT_SESSION_ID, } from "./runtime/agent-sessions.js";
|
|
19
|
-
export { appendIncludePromptOnly, getLastMessageId, normalizeOptionalToolString, toAgentReadableMessages, withJoinRoomAgentPrompt, } from "./runtime/messages.js";
|
|
20
|
-
export { currentRoom, attachMcpServer, getCurrentSupervisedRoomAuthority, getFallbackProjectId, getTargetRoomId, rememberRoom, runWithCurrentSupervisedRoom, shutdownRuntime, toPublicRoomResponse, toPublicCurrentRoomState, toPublicRoomState, toPublicStoredRoomSession, toRoomState, touchCurrentRoom, withCanonicalRoomLink, } from "./runtime/room-state.js";
|
|
19
|
+
export { appendIncludePromptOnly, AGENT_MESSAGE_BODY_MAX_BYTES, AGENT_MESSAGE_OUTPUT_MAX_BYTES, boundAgentMessageOutput, getLastMessageId, normalizeOptionalToolString, toAgentReadableMessages, withJoinRoomAgentPrompt, } from "./runtime/messages.js";
|
|
20
|
+
export { currentRoom, currentRoomMatchesLocator, attachMcpServer, getCurrentSupervisedRoomAuthority, getFallbackProjectId, getTargetRoomId, rememberRoom, runWithCurrentSupervisedRoom, shutdownRuntime, toPublicRoomResponse, toPublicCurrentRoomState, toPublicRoomState, toPublicStoredRoomSession, toRoomState, touchCurrentRoom, withCanonicalRoomLink, } from "./runtime/room-state.js";
|
|
21
21
|
export { getRememberedRoomPresence, heartbeatRoomPresence, syncRoomPresence, } from "./runtime/presence.js";
|
|
22
22
|
export { roomScopedApiCall } from "./runtime/room-api.js";
|
|
23
23
|
export { borrowSupervisedWorkerCredential, borrowCurrentSupervisedWorkerCredential, bindSupervisedWorkerSession, checkpointSupervisedWorkerCursor, isRetryableSupervisorBridgeError, scheduleSupervisedWorkerCursorCheckpoint, resolveCurrentSupervisedWorkerSession, } from "./runtime/supervisor-bridge.js";
|
|
24
24
|
export { autoJoinFromContext, buildJoinResponse, createInviteRoom, getCurrentLiveSessionPayload, joinInviteCode, joinNamedRoom, joinRoomIdentifier, joinRoomIdentifierWithoutImplicitGitRefCreate, normalizeJoinSessionMode, } from "./runtime/rooms.js";
|
|
25
|
-
export { clearPendingDeviceAuth, clearStoredAuth, clearStoredAuth as clearStoredAuthorization, endStoredAgentSession, getCurrentAgentSession, getLocalStatePath, getPendingDeviceAuth, getStoredAgentIdentity, getStoredAgentSession, getStoredAgentSessionsForRoomIdentity, getStoredAuth, getStoredCurrentRoom, getStoredRoomSession, listStoredCodexLiveSessions, saveAgentSession, setPendingDeviceAuth, setStoredAuth, setStoredAgentIdentity, addLocalChatMessage, addLocalTask, claimLocalTaskReviewLease, getLatestLocalChatMessages, getLocalChatMessages, getLocalTask, listLocalTasks, isLocalChatStorageEnabled, resolveLocalRoomStorageIdentifiers, releaseLocalTaskReviewLease, updateLocalTask, waitForLocalChatMessages, } from "../local-state.js";
|
|
25
|
+
export { clearPendingDeviceAuth, clearStoredAuth, clearStoredAuth as clearStoredAuthorization, endStoredAgentSession, getCurrentAgentSession, getLocalStatePath, getPendingDeviceAuth, getStoredAgentIdentity, getStoredAgentSession, getCurrentAgentSessionSnapshot, getStoredAgentSessionsForRoomIdentity, getStoredActiveAgentSessionsForRoom, getStoredAgentRoutingStateSnapshot, getStoredAuth, getStoredCurrentRoom, getStoredRoomSession, listStoredCodexLiveSessions, saveAgentSession, setPendingDeviceAuth, setStoredAuth, setStoredAgentIdentity, addLocalChatMessage, addLocalTask, claimLocalTaskReviewLease, getLatestLocalChatMessages, getLocalImportedRoutingAuthority, getLocalChatMessages, getLocalChatThreadRoutingMembership, getLocalTask, listLocalActiveTaskOwnerLeases, listLocalTasks, isLocalChatStorageEnabled, resolveLocalRoomStorageIdentifiers, releaseLocalTaskReviewLease, updateLocalTask, waitForLocalChatMessages, } from "../local-state.js";
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { parsePositivePgIntegerScopedId } from "../../../shared/message-contracts.mjs";
|
|
1
2
|
import { runWithCurrentSupervisedRoom } from "./runtime/room-state.js";
|
|
2
|
-
import { completeCurrentSupervisedEffect, prepareCurrentSupervisedEffect, } from "./runtime/supervisor-bridge.js";
|
|
3
|
+
import { completeCurrentSupervisedEffect, authorizeCustodialPolling, executeCurrentSupervisedTool, prepareCurrentSupervisedEffect, } from "./runtime/supervisor-bridge.js";
|
|
3
4
|
const READ_TOOLS = new Set([
|
|
4
5
|
"get_current_room",
|
|
5
6
|
"check_repo",
|
|
@@ -15,6 +16,47 @@ const READ_TOOLS = new Set([
|
|
|
15
16
|
"status_local_codex_session",
|
|
16
17
|
"rental_list_requests",
|
|
17
18
|
]);
|
|
19
|
+
export function supervisedToolIsMutation(toolName) {
|
|
20
|
+
return !READ_TOOLS.has(toolName);
|
|
21
|
+
}
|
|
22
|
+
// The desktop daemon's local control protocol intentionally uses small bounded
|
|
23
|
+
// frames. A read result can be returned live to the provider without copying
|
|
24
|
+
// the entire payload into the durable effect journal.
|
|
25
|
+
const MAX_DURABLE_READ_RESULT_BYTES = 16 * 1024;
|
|
26
|
+
/** Receipt only the bounded page actually returned by wait, never an API tail
|
|
27
|
+
* beyond that page or a cursor inferred from its last visible message. */
|
|
28
|
+
function custodialWaitFrontier(result, inputCursor, roomId) {
|
|
29
|
+
const content = result.content;
|
|
30
|
+
if (result.isError || content.length !== 1 || content[0]?.type !== "text")
|
|
31
|
+
throw new Error("Custodial wait returned no valid bounded page.");
|
|
32
|
+
let output;
|
|
33
|
+
try {
|
|
34
|
+
output = JSON.parse(content[0].text);
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
throw new Error("Custodial wait returned no valid bounded page.");
|
|
38
|
+
}
|
|
39
|
+
if (!output || Array.isArray(output) || !Array.isArray(output.messages)
|
|
40
|
+
|| (output.room_id !== undefined && output.room_id !== roomId))
|
|
41
|
+
throw new Error("Custodial wait returned no valid bounded page.");
|
|
42
|
+
const noProgress = output.messages.length === 0 && (output.truncated === undefined || output.truncated === false)
|
|
43
|
+
&& (output.omitted_message_count === undefined || output.omitted_message_count === 0)
|
|
44
|
+
&& (output.skipped_message_count === undefined || output.skipped_message_count === 0)
|
|
45
|
+
&& (output.skipped_message_ids === undefined || (Array.isArray(output.skipped_message_ids) && output.skipped_message_ids.length === 0));
|
|
46
|
+
const frontier = output.last_observed_message_id;
|
|
47
|
+
if (frontier === undefined || frontier === null) {
|
|
48
|
+
if (!noProgress) {
|
|
49
|
+
throw new Error("Custodial wait is missing its bounded observed frontier.");
|
|
50
|
+
}
|
|
51
|
+
return inputCursor;
|
|
52
|
+
}
|
|
53
|
+
const number = parsePositivePgIntegerScopedId(frontier, "msg");
|
|
54
|
+
const inputNumber = parsePositivePgIntegerScopedId(inputCursor, "msg");
|
|
55
|
+
if (number === null || inputNumber === null || number < inputNumber || (number === inputNumber && !noProgress)) {
|
|
56
|
+
throw new Error("Custodial wait returned an invalid observed frontier.");
|
|
57
|
+
}
|
|
58
|
+
return String(frontier);
|
|
59
|
+
}
|
|
18
60
|
function instruction(text, data = {}) {
|
|
19
61
|
const payload = { ...data, instruction: text };
|
|
20
62
|
return {
|
|
@@ -22,13 +64,25 @@ function instruction(text, data = {}) {
|
|
|
22
64
|
structuredContent: payload,
|
|
23
65
|
};
|
|
24
66
|
}
|
|
67
|
+
export function durableCompletionResult(result, mutation) {
|
|
68
|
+
if (mutation)
|
|
69
|
+
return result;
|
|
70
|
+
const serializedBytes = Buffer.byteLength(JSON.stringify(result), "utf8");
|
|
71
|
+
if (serializedBytes <= MAX_DURABLE_READ_RESULT_BYTES)
|
|
72
|
+
return result;
|
|
73
|
+
return instruction("The read completed, but its large result was returned live instead of being copied into the durable journal. Issue a fresh read request if this exact request is replayed after a restart.", {
|
|
74
|
+
code: "SUPERVISED_READ_RESULT_NOT_RETAINED",
|
|
75
|
+
serialized_bytes: serializedBytes,
|
|
76
|
+
});
|
|
77
|
+
}
|
|
25
78
|
const productionDependencies = {
|
|
79
|
+
executeTool: executeCurrentSupervisedTool,
|
|
26
80
|
prepareEffect: prepareCurrentSupervisedEffect,
|
|
27
81
|
completeEffect: completeCurrentSupervisedEffect,
|
|
28
82
|
withRoom: runWithCurrentSupervisedRoom,
|
|
29
83
|
};
|
|
30
84
|
export function profileAwareToolServer(server, profile, dependencies = productionDependencies, supervisedProvider = process.env.LETAGENTS_SUPERVISOR_PROVIDER?.trim() || null) {
|
|
31
|
-
if (profile !== "supervised_room_turn")
|
|
85
|
+
if (profile !== "supervised_room_turn" && profile !== "supervised_mcp_polling")
|
|
32
86
|
return server;
|
|
33
87
|
return new Proxy(server, {
|
|
34
88
|
get(target, property, receiver) {
|
|
@@ -37,21 +91,65 @@ export function profileAwareToolServer(server, profile, dependencies = productio
|
|
|
37
91
|
return typeof value === "function" ? value.bind(target) : value;
|
|
38
92
|
}
|
|
39
93
|
return (name, ...registration) => {
|
|
94
|
+
const mutation = supervisedToolIsMutation(name);
|
|
40
95
|
const callback = registration.at(-1);
|
|
41
96
|
if (typeof callback !== "function")
|
|
42
97
|
throw new Error(`Tool ${name} has no callback.`);
|
|
43
98
|
const wrapped = async (...call) => {
|
|
99
|
+
if (profile === "supervised_mcp_polling") {
|
|
100
|
+
const authorize = dependencies.authorizePolling ?? ((toolName, prior, wait) => authorizeCustodialPolling(toolName, prior, process.env, {}, wait));
|
|
101
|
+
const input = call[0];
|
|
102
|
+
const extra = (call.length > 1 ? call.at(-1) : undefined);
|
|
103
|
+
let wait;
|
|
104
|
+
if (name === "wait_for_messages") {
|
|
105
|
+
const requestId = extra?.requestId;
|
|
106
|
+
if (!(typeof requestId === "string" || (typeof requestId === "number" && Number.isSafeInteger(requestId)))) {
|
|
107
|
+
throw new Error("Custodial wait is missing its exact MCP request id.");
|
|
108
|
+
}
|
|
109
|
+
if (input?.after_message_id != null && typeof input.after_message_id !== "string")
|
|
110
|
+
throw new Error("Custodial wait requires a valid requested cursor.");
|
|
111
|
+
if ((input?.room_id != null && typeof input.room_id !== "string")
|
|
112
|
+
|| (input?.agent_session_id != null && typeof input.agent_session_id !== "string"))
|
|
113
|
+
throw new Error("Custodial wait requires valid requested identity.");
|
|
114
|
+
wait = { mcpRequestId: requestId, roomCursor: input?.after_message_id ?? null,
|
|
115
|
+
...(typeof input?.room_id === "string" ? { requestedRoomId: input.room_id } : {}),
|
|
116
|
+
...(typeof input?.agent_session_id === "string" ? { requestedAgentSessionId: input.agent_session_id } : {}) };
|
|
117
|
+
}
|
|
118
|
+
const authority = await authorize(name, undefined, wait);
|
|
119
|
+
return dependencies.withRoom(authority.roomId, async () => {
|
|
120
|
+
if (input?.room_id && input.room_id !== authority.roomId)
|
|
121
|
+
throw new Error("Custodial tool room does not match its exact authority.");
|
|
122
|
+
if (name === "wait_for_messages") {
|
|
123
|
+
if (!authority.roomCursor)
|
|
124
|
+
throw new Error("Custodial polling has no durable cursor; refusing a tail fallback.");
|
|
125
|
+
call[0] = { ...input, after_message_id: authority.roomCursor };
|
|
126
|
+
}
|
|
127
|
+
const result = await callback(...call);
|
|
128
|
+
if (wait)
|
|
129
|
+
await authorize(name, authority, { ...wait,
|
|
130
|
+
offeredFrontier: custodialWaitFrontier(result, authority.roomCursor, authority.roomId) });
|
|
131
|
+
else if (name === "read_messages")
|
|
132
|
+
await authorize(name, authority);
|
|
133
|
+
return result;
|
|
134
|
+
});
|
|
135
|
+
}
|
|
44
136
|
const extra = (call.at(-1) ?? {});
|
|
45
137
|
const input = call.length > 1 ? call[0] : {};
|
|
46
138
|
if (extra.requestId === undefined || extra.requestId === null || String(extra.requestId).trim() === "") {
|
|
47
139
|
throw new Error(`Supervised tool ${name} is missing its MCP request id; refusing an effect that cannot be deduplicated safely.`);
|
|
48
140
|
}
|
|
49
|
-
const
|
|
141
|
+
const executionRequest = {
|
|
50
142
|
toolName: name,
|
|
51
143
|
input,
|
|
52
144
|
mcpRequestId: String(extra.requestId),
|
|
53
|
-
|
|
54
|
-
|
|
145
|
+
};
|
|
146
|
+
if (dependencies.executeTool) {
|
|
147
|
+
const executed = await dependencies.executeTool(executionRequest);
|
|
148
|
+
if (executed.state === "completed") {
|
|
149
|
+
return dependencies.withRoom(executed.roomId, () => executed.result);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
const prepared = await dependencies.prepareEffect({ ...executionRequest, mutation });
|
|
55
153
|
return dependencies.withRoom(prepared.roomId, async () => {
|
|
56
154
|
if (prepared.state === "completed")
|
|
57
155
|
return prepared.result;
|
|
@@ -94,7 +192,10 @@ export function profileAwareToolServer(server, profile, dependencies = productio
|
|
|
94
192
|
}
|
|
95
193
|
// Completion transport is deliberately outside the callback catch.
|
|
96
194
|
// A reporting failure must never relabel a successful action failed.
|
|
97
|
-
await dependencies.completeEffect({
|
|
195
|
+
await dependencies.completeEffect({
|
|
196
|
+
effectId: prepared.effectId,
|
|
197
|
+
result: durableCompletionResult(result, mutation),
|
|
198
|
+
});
|
|
98
199
|
return result;
|
|
99
200
|
});
|
|
100
201
|
};
|