letagents 0.12.12 → 0.12.13

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.
Files changed (38) hide show
  1. package/dist/mcp/git-remote.js +7 -7
  2. package/dist/mcp/local-state/agent-sessions.js +63 -5
  3. package/dist/mcp/local-state/local-chat.js +189 -48
  4. package/dist/mcp/local-state/storage.js +16 -9
  5. package/dist/mcp/server/daemon-tool-executor.js +92 -0
  6. package/dist/mcp/server/register-tools.js +4 -2
  7. package/dist/mcp/server/runtime/agent-sessions.js +14 -5
  8. package/dist/mcp/server/runtime/api.js +11 -3
  9. package/dist/mcp/server/runtime/daemon-tool-context.js +11 -0
  10. package/dist/mcp/server/runtime/messages.js +55 -0
  11. package/dist/mcp/server/runtime/room-state.js +13 -3
  12. package/dist/mcp/server/runtime/rooms.js +51 -30
  13. package/dist/mcp/server/runtime/supervisor-bridge.js +49 -8
  14. package/dist/mcp/server/runtime/worker-bearer.js +5 -1
  15. package/dist/mcp/server/runtime.js +3 -3
  16. package/dist/mcp/server/supervised-tool-facade.js +34 -5
  17. package/dist/mcp/server/tools/messages/read-tool.js +54 -97
  18. package/dist/mcp/server/tools/messages/reasoning-tool.js +2 -0
  19. package/dist/mcp/server/tools/messages/send-tool.js +2 -0
  20. package/dist/mcp/server/tools/messages/status-tool.js +2 -0
  21. package/dist/mcp/server/tools/messages/wait-tool.js +290 -68
  22. package/dist/mcp/server/tools/onboarding/status-tool.js +4 -4
  23. package/dist/mcp/server/tools/rooms/inspection-tools.js +15 -10
  24. package/dist/mcp/server/tools/rooms/repo-initialization-tool.js +2 -1
  25. package/dist/mcp/server/tools/tasks/board-tools.js +34 -2
  26. package/dist/mcp/sse-client.js +163 -20
  27. package/dist/shared/activation-routing.js +146 -23
  28. package/dist/shared/agent-presence.js +6 -0
  29. package/dist/shared/desktop-release-manifest.js +63 -0
  30. package/dist/shared/desktop-release.js +60 -0
  31. package/dist/shared/scoped-ids.js +6 -0
  32. package/package.json +6 -2
  33. package/shared/message-contracts.d.mts +32 -0
  34. package/shared/message-contracts.mjs +109 -0
  35. package/shared/routing-aliases.d.mts +18 -0
  36. package/shared/routing-aliases.mjs +66 -0
  37. package/shared/sqlite-thread-routing.d.mts +72 -0
  38. package/shared/sqlite-thread-routing.mjs +1038 -0
@@ -1,4 +1,4 @@
1
- import { getStoredAgentSession, isLocalRoomStorageEnabled, saveAgentSession, } from "../../local-state.js";
1
+ import { getStoredAgentSession, isLocalRoomStorageEnabled, replaceLocalWorkerAgentSession, saveAgentSession, } from "../../local-state.js";
2
2
  import { getGitCurrentBranch } from "../../git-remote.js";
3
3
  import { randomUUID } from "node:crypto";
4
4
  import { normalizeAgentBaseName } from "../../../shared/codenames.js";
@@ -7,6 +7,7 @@ import { LETAGENTS_AGENT_SESSION_ID_HEADER, LETAGENTS_AGENT_SESSION_TOKEN_HEADER
7
7
  import { AGENT_INSTANCE_UUID, detectAgentIdeLabel, detectAgentRuntimeLabel, ensureAgentIdentity, } from "./identity.js";
8
8
  import { isSupervisedBoundedTurn, requireValidWorkerBearerRuntime } from "./worker-bearer.js";
9
9
  import { resolveCurrentSupervisedWorkerSession } from "./supervisor-bridge.js";
10
+ import { getDaemonToolExecutionContext, getRuntimeWorkingDirectory } from "./daemon-tool-context.js";
10
11
  // A worker bearer already represents a server-side worker session. This local
11
12
  // marker lets the MCP tool contract stay session-shaped without persisting or
12
13
  // transmitting a second set of credentials.
@@ -119,7 +120,12 @@ export function requireWorkerAgentSession(roomId, sessionId) {
119
120
  export async function resolveWorkerToolIdentity(input) {
120
121
  const runtimeMode = requireValidWorkerBearerRuntime().mode;
121
122
  if (runtimeMode === "supervised") {
122
- const agentSession = await resolveCurrentSupervisedWorkerSession(input.roomId);
123
+ const daemonContext = getDaemonToolExecutionContext();
124
+ const agentSession = daemonContext?.agentSession
125
+ ?? await resolveCurrentSupervisedWorkerSession(input.roomId);
126
+ if (input.roomId && input.roomId !== agentSession.room_id) {
127
+ throw new Error(`Daemon-supervised worker session is registered for ${agentSession.room_id}, not ${input.roomId}.`);
128
+ }
123
129
  if (input.agentSessionId
124
130
  && input.agentSessionId !== WORKER_BEARER_AGENT_SESSION_ID
125
131
  && input.agentSessionId !== agentSession.session_id) {
@@ -173,7 +179,7 @@ export async function ensureLocalWorkerAgentSession(roomId, input = {}) {
173
179
  const now = new Date().toISOString();
174
180
  const runtime = input.runtime?.trim() || detectAgentRuntimeLabel();
175
181
  const displayName = input.displayName?.trim() || identity.display_name;
176
- return saveAgentSession({
182
+ const session = {
177
183
  session_id: `local_${randomUUID()}`,
178
184
  session_token: `local_${randomUUID()}`,
179
185
  room_id: roomId,
@@ -195,10 +201,13 @@ export async function ensureLocalWorkerAgentSession(roomId, input = {}) {
195
201
  updated_at: now,
196
202
  last_seen_at: now,
197
203
  ended_at: null,
198
- });
204
+ };
205
+ return session.session_kind === "worker"
206
+ ? replaceLocalWorkerAgentSession(session)
207
+ : saveAgentSession(session);
199
208
  }
200
209
  export function getAgentSessionRepoBranch(cwd) {
201
- const workingDir = cwd?.trim() || process.cwd();
210
+ const workingDir = cwd?.trim() || getRuntimeWorkingDirectory();
202
211
  return getGitCurrentBranch(workingDir);
203
212
  }
204
213
  export function agentSessionCredentials(agentSession) {
@@ -1,4 +1,5 @@
1
1
  import { clearAuthenticatedAccountCache } from "./auth-cache.js";
2
+ import { getDaemonToolExecutionContext } from "./daemon-tool-context.js";
2
3
  import { requireValidWorkerBearerRuntime } from "./worker-bearer.js";
3
4
  import { borrowCurrentSupervisedWorkerCredential, } from "./supervisor-bridge.js";
4
5
  let ownerAuthStoreLoader = () => import("../../local-state.js");
@@ -20,12 +21,18 @@ export class SupervisedWorkerCredentialError extends Error {
20
21
  }
21
22
  }
22
23
  async function getSupervisedCredential() {
24
+ const daemonContext = getDaemonToolExecutionContext();
25
+ if (daemonContext)
26
+ return daemonContext.bearer;
23
27
  const result = await supervisedCredentialBorrower();
24
28
  if (result.state === "available")
25
29
  return result.credential;
26
30
  throw new SupervisedWorkerCredentialError(result.state === "deferred" ? "SUPERVISED_CREDENTIAL_UNAVAILABLE" : "SUPERVISED_CREDENTIAL_STALE");
27
31
  }
28
32
  export const API_URL = (process.env.LETAGENTS_API_URL || "http://localhost:3001").replace(/\/+$/, "");
33
+ export function getApiUrl() {
34
+ return getDaemonToolExecutionContext()?.apiUrl.replace(/\/+$/, "") ?? API_URL;
35
+ }
29
36
  export class ApiError extends Error {
30
37
  status;
31
38
  body;
@@ -76,8 +83,9 @@ export function resolveApiPath(urlOrPath) {
76
83
  return "/auth/device/start";
77
84
  }
78
85
  try {
79
- const parsed = new URL(urlOrPath, `${API_URL}/`);
80
- const apiBase = new URL(`${API_URL}/`);
86
+ const apiUrl = getApiUrl();
87
+ const parsed = new URL(urlOrPath, `${apiUrl}/`);
88
+ const apiBase = new URL(`${apiUrl}/`);
81
89
  if (parsed.origin !== apiBase.origin) {
82
90
  return "/auth/device/start";
83
91
  }
@@ -109,7 +117,7 @@ export async function apiCall(path, options) {
109
117
  headers.set("Authorization", authorizationHeader);
110
118
  }
111
119
  }
112
- const res = await fetch(`${API_URL}${path}`, {
120
+ const res = await fetch(`${getApiUrl()}${path}`, {
113
121
  ...options,
114
122
  headers,
115
123
  });
@@ -0,0 +1,11 @@
1
+ import { AsyncLocalStorage } from "node:async_hooks";
2
+ const daemonToolContext = new AsyncLocalStorage();
3
+ export function getDaemonToolExecutionContext() {
4
+ return daemonToolContext.getStore() ?? null;
5
+ }
6
+ export function runWithDaemonToolExecutionContext(context, callback) {
7
+ return daemonToolContext.run(context, callback);
8
+ }
9
+ export function getRuntimeWorkingDirectory() {
10
+ return getDaemonToolExecutionContext()?.cwd ?? process.cwd();
11
+ }
@@ -151,6 +151,61 @@ export function toAgentReadableMessages(messages, contextMessages = []) {
151
151
  const summaries = buildThreadSummaries(records, recordsById);
152
152
  return (messages ?? []).map((message) => toAgentReadableMessage(message, recordsById, summaries));
153
153
  }
154
+ export const AGENT_MESSAGE_OUTPUT_MAX_BYTES = 4 * 1024 * 1024;
155
+ export const AGENT_MESSAGE_BODY_MAX_BYTES = AGENT_MESSAGE_OUTPUT_MAX_BYTES / 2;
156
+ function jsonUtf8Bytes(value) {
157
+ const serialized = JSON.stringify(value);
158
+ return Buffer.byteLength(serialized === undefined ? "null" : serialized, "utf8");
159
+ }
160
+ function compactOversizedAgentMessage(message) {
161
+ if (!isRecord(message)) {
162
+ return { content_truncated: true, value_type: typeof message };
163
+ }
164
+ const text = typeof message.text === "string" ? message.text : "";
165
+ return {
166
+ ...(typeof message.id === "string" ? { id: message.id } : {}),
167
+ ...(typeof message.sender === "string" ? { sender: message.sender } : {}),
168
+ ...(typeof message.source === "string" ? { source: message.source } : {}),
169
+ ...(typeof message.timestamp === "string" ? { timestamp: message.timestamp } : {}),
170
+ ...(typeof message.thread_root_id === "string"
171
+ ? { thread_root_id: message.thread_root_id }
172
+ : {}),
173
+ ...(typeof message.thread_reply_to_id === "string"
174
+ ? { thread_reply_to_id: message.thread_reply_to_id }
175
+ : {}),
176
+ text: text.slice(0, 16 * 1024),
177
+ content_truncated: true,
178
+ original_utf8_bytes: jsonUtf8Bytes(message),
179
+ };
180
+ }
181
+ export function boundAgentMessageOutput(messages, options = {}) {
182
+ const direction = options.direction ?? "prefix";
183
+ const maxBytes = Math.max(1024, Math.floor(options.maxBytes ?? AGENT_MESSAGE_OUTPUT_MAX_BYTES));
184
+ const ordered = direction === "suffix" ? [...messages].reverse() : [...messages];
185
+ const selected = [];
186
+ let outputBytes = 2; // JSON array brackets.
187
+ for (const message of ordered) {
188
+ let candidate = message;
189
+ let candidateBytes = jsonUtf8Bytes(candidate);
190
+ if (candidateBytes + 2 > maxBytes) {
191
+ candidate = compactOversizedAgentMessage(candidate);
192
+ candidateBytes = jsonUtf8Bytes(candidate);
193
+ }
194
+ const separatorBytes = selected.length > 0 ? 1 : 0;
195
+ if (outputBytes + separatorBytes + candidateBytes > maxBytes)
196
+ break;
197
+ selected.push(candidate);
198
+ outputBytes += separatorBytes + candidateBytes;
199
+ }
200
+ if (direction === "suffix")
201
+ selected.reverse();
202
+ return {
203
+ messages: selected,
204
+ truncated: selected.length < messages.length,
205
+ omittedMessageCount: messages.length - selected.length,
206
+ outputBytes,
207
+ };
208
+ }
154
209
  export function appendIncludePromptOnly(path) {
155
210
  return `${path}${path.includes("?") ? "&" : "?"}include_prompt_only=1`;
156
211
  }
@@ -1,7 +1,7 @@
1
1
  import { SseClient } from "../../sse-client.js";
2
2
  import { getStoredAgentIdentity, saveRoomSession, touchRoomSession, } from "../../local-state.js";
3
3
  import { getCanonicalRoomWebPath, } from "../../room-id.js";
4
- import { API_URL, getLetagentsToken } from "./api.js";
4
+ import { getApiUrl, getLetagentsToken } from "./api.js";
5
5
  import { AGENT_INSTANCE_UUID, currentAgentIdentity, currentAgentIdentityKey, } from "./identity.js";
6
6
  import { isSupervisedBoundedTurn } from "./worker-bearer.js";
7
7
  import { getCurrentSupervisedRoomAuthority, runWithSupervisedRoomAuthority, } from "./supervised-room-authority.js";
@@ -15,7 +15,7 @@ export function shutdownRuntime() {
15
15
  sseClient?.unsubscribeAll();
16
16
  }
17
17
  function getSseClient() {
18
- sseClient ??= new SseClient(API_URL, () => getLetagentsToken());
18
+ sseClient ??= new SseClient(getApiUrl(), () => getLetagentsToken());
19
19
  return sseClient;
20
20
  }
21
21
  function getCurrentStreamAgentIdentity() {
@@ -32,6 +32,7 @@ function getCurrentStreamAgentIdentity() {
32
32
  export function toRoomState(input) {
33
33
  return {
34
34
  room_id: input.room_id,
35
+ navigation_locator: input.navigation_locator ?? null,
35
36
  project_id: input.project_id ?? null,
36
37
  code: input.code ?? null,
37
38
  display_name: input.display_name ?? null,
@@ -40,8 +41,12 @@ export function toRoomState(input) {
40
41
  is_local: input.is_local ?? false,
41
42
  };
42
43
  }
44
+ export function currentRoomMatchesLocator(locator) {
45
+ const value = locator?.trim();
46
+ return Boolean(value && currentRoom && (currentRoom.room_id === value || currentRoom.navigation_locator === value));
47
+ }
43
48
  function getCanonicalRoomWebUrl(roomId) {
44
- return new URL(getCanonicalRoomWebPath(roomId), `${API_URL}/`).toString();
49
+ return new URL(getCanonicalRoomWebPath(roomId), `${getApiUrl()}/`).toString();
45
50
  }
46
51
  export function withCanonicalRoomLink(roomId, payload) {
47
52
  return {
@@ -109,6 +114,11 @@ export function rememberRoom(state, lastMessageId) {
109
114
  }, (_message) => {
110
115
  touchRoomSession(state.room_id);
111
116
  mcpServer?.server.sendResourceListChanged();
117
+ }, () => {
118
+ // The SSE cursor crossed a broker/bridge loss boundary. MCP resources
119
+ // are pull-based, so invalidating the list is the full-state repair.
120
+ touchRoomSession(state.room_id);
121
+ mcpServer?.server.sendResourceListChanged();
112
122
  });
113
123
  return state;
114
124
  }
@@ -20,7 +20,7 @@ function isNotFoundApiError(error) {
20
20
  return error instanceof ApiError && error.status === 404;
21
21
  }
22
22
  function parseGeneratedGitRefRoomIdentifier(identifier) {
23
- const match = /^git-room:github\.com:([^/:\s]+\/[^/:\s]+):(branch|tag):[A-Za-z0-9_-]+$/.exec(identifier.trim());
23
+ const match = /^github\.com\/([^/\s]+\/[^/\s]+)\/focus\/git:(branch|tag):[A-Za-z0-9_-]+$/.exec(identifier.trim());
24
24
  if (!match) {
25
25
  return null;
26
26
  }
@@ -67,6 +67,7 @@ export async function joinRoomIdentifier(identifier, joinedVia, options = {}) {
67
67
  const agentIdentity = await ensureAgentIdentity();
68
68
  const room = rememberRoom(toRoomState({
69
69
  room_id: joinedRoomId,
70
+ navigation_locator: joinedRoomId === roomId ? null : roomId,
70
71
  project_id: typeof response.project_id === "string" ? response.project_id : null,
71
72
  code: typeof response.code === "string"
72
73
  ? response.code
@@ -260,7 +261,27 @@ export async function joinNamedRoom(name, sessionMode) {
260
261
  session_mode: sessionMode,
261
262
  });
262
263
  }
263
- function bindWorkerRoomFromContext() {
264
+ async function bindWorkerRoomLocator(roomLocator, source) {
265
+ const response = await apiCall(`/rooms/resolve/${encodeURIComponent(roomLocator)}`);
266
+ if (response.room_exists !== true) {
267
+ throw new ApiError(404, JSON.stringify({ error: "Room not found", code: "ROOM_NOT_FOUND" }));
268
+ }
269
+ const roomId = typeof response.canonical_room_id === "string"
270
+ ? response.canonical_room_id
271
+ : roomLocator;
272
+ return {
273
+ room: rememberRoom(toRoomState({
274
+ room_id: roomId,
275
+ navigation_locator: roomId === roomLocator ? null : roomLocator,
276
+ project_id: roomId,
277
+ display_name: roomId,
278
+ git_room: response.git_room ?? null,
279
+ joined_via: source === ".letagents.json" ? "config" : "git-remote",
280
+ })),
281
+ source,
282
+ };
283
+ }
284
+ async function bindWorkerRoomFromContext() {
264
285
  const configRoom = getRoomFromConfig();
265
286
  if (configRoom) {
266
287
  const gitContext = buildActiveGitRoomContext({
@@ -268,26 +289,26 @@ function bindWorkerRoomFromContext() {
268
289
  currentBranch: getGitCurrentBranch(),
269
290
  defaultBranch: getGitDefaultBranch(),
270
291
  });
271
- const roomId = gitContext.activeRoom ?? configRoom;
272
- return {
273
- room: rememberRoom(toRoomState({
274
- room_id: roomId,
275
- display_name: roomId,
276
- joined_via: "config",
277
- })),
278
- source: ".letagents.json",
279
- };
292
+ const roomId = gitContext.activeRoomLocator ?? configRoom;
293
+ try {
294
+ return await bindWorkerRoomLocator(roomId, ".letagents.json");
295
+ }
296
+ catch (error) {
297
+ if (roomId === configRoom || !isNotFoundApiError(error))
298
+ throw error;
299
+ return bindWorkerRoomLocator(configRoom, ".letagents.json");
300
+ }
280
301
  }
281
302
  const gitContext = getGitRoomContext();
282
- if (gitContext.activeRoom) {
283
- return {
284
- room: rememberRoom(toRoomState({
285
- room_id: gitContext.activeRoom,
286
- display_name: gitContext.activeRoom,
287
- joined_via: "git-remote",
288
- })),
289
- source: "git remote",
290
- };
303
+ if (gitContext.activeRoomLocator) {
304
+ try {
305
+ return await bindWorkerRoomLocator(gitContext.activeRoomLocator, "git remote");
306
+ }
307
+ catch (error) {
308
+ if (!gitContext.repoRoom || !isNotFoundApiError(error))
309
+ throw error;
310
+ return bindWorkerRoomLocator(gitContext.repoRoom, "git remote");
311
+ }
291
312
  }
292
313
  const savedCurrentRoom = getStoredCurrentRoom();
293
314
  if (!savedCurrentRoom) {
@@ -316,9 +337,9 @@ export async function autoJoinFromContext() {
316
337
  return;
317
338
  }
318
339
  if (workerRuntime.mode === "worker") {
319
- const bound = bindWorkerRoomFromContext();
340
+ const bound = await bindWorkerRoomFromContext();
320
341
  if (bound) {
321
- console.error(`🏠 Bound worker bearer to room '${bound.room.room_id}' (from ${bound.source}; no join/create request).`);
342
+ console.error(`🏠 Bound worker bearer to existing room '${bound.room.room_id}' (from ${bound.source}).`);
322
343
  }
323
344
  else {
324
345
  console.error("ℹ️ Worker bearer has no .letagents.json, git remote, or saved room to bind locally.");
@@ -332,17 +353,17 @@ export async function autoJoinFromContext() {
332
353
  currentBranch: getGitCurrentBranch(),
333
354
  defaultBranch: getGitDefaultBranch(),
334
355
  });
335
- if (gitContext.activeRefRoom && gitContext.currentBranch) {
336
- const joinedBranchRoom = await joinExistingRoomIdentifier(gitContext.activeRefRoom, "config");
356
+ if (gitContext.activeRefRoomLocator && gitContext.currentBranch) {
357
+ const joinedBranchRoom = await joinExistingRoomIdentifier(gitContext.activeRefRoomLocator, "config");
337
358
  if (joinedBranchRoom) {
338
359
  await ensureAgentIdentity();
339
- console.error(`🏠 Auto-joined existing branch room '${gitContext.activeRefRoom}' (from .letagents.json + branch '${gitContext.currentBranch}')`);
360
+ console.error(`🏠 Auto-joined existing branch room '${gitContext.activeRefRoomLocator}' (from .letagents.json + branch '${gitContext.currentBranch}')`);
340
361
  return;
341
362
  }
342
363
  }
343
364
  await joinRoomIdentifier(configRoom, "config");
344
365
  await ensureAgentIdentity();
345
- const branchNote = gitContext.activeRefRoom && gitContext.currentBranch
366
+ const branchNote = gitContext.activeRefRoomLocator && gitContext.currentBranch
346
367
  ? `; branch '${gitContext.currentBranch}' has no existing Git Room`
347
368
  : "";
348
369
  console.error(`🏠 Auto-joined room '${configRoom}' (from .letagents.json${branchNote})`);
@@ -350,17 +371,17 @@ export async function autoJoinFromContext() {
350
371
  }
351
372
  const gitContext = getGitRoomContext();
352
373
  if (gitContext.repoRoom) {
353
- if (gitContext.activeRefRoom && gitContext.currentBranch) {
354
- const joinedBranchRoom = await joinExistingRoomIdentifier(gitContext.activeRefRoom, "git-remote");
374
+ if (gitContext.activeRefRoomLocator && gitContext.currentBranch) {
375
+ const joinedBranchRoom = await joinExistingRoomIdentifier(gitContext.activeRefRoomLocator, "git-remote");
355
376
  if (joinedBranchRoom) {
356
377
  await ensureAgentIdentity();
357
- console.error(`🏠 Auto-joined existing branch room '${gitContext.activeRefRoom}' (inferred from git remote and branch '${gitContext.currentBranch}' — consider adding a .letagents.json)`);
378
+ console.error(`🏠 Auto-joined existing branch room '${gitContext.activeRefRoomLocator}' (inferred from git remote and branch '${gitContext.currentBranch}' — consider adding a .letagents.json)`);
358
379
  return;
359
380
  }
360
381
  }
361
382
  await joinRoomIdentifier(gitContext.repoRoom, "git-remote");
362
383
  await ensureAgentIdentity();
363
- const branchNote = gitContext.activeRefRoom && gitContext.currentBranch
384
+ const branchNote = gitContext.activeRefRoomLocator && gitContext.currentBranch
364
385
  ? `; branch '${gitContext.currentBranch}' has no existing Git Room`
365
386
  : "";
366
387
  console.error(`🏠 Auto-joined room '${gitContext.repoRoom}' (inferred from git remote${branchNote} — consider adding a .letagents.json)`);
@@ -3,6 +3,7 @@ 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
9
  const SUPPORTED_SUPERVISOR_PROTOCOL_VERSIONS = new Set([1, 2]);
@@ -18,6 +19,42 @@ const pendingCursorCheckpoints = new Map();
18
19
  const activeCursorCheckpointDrains = new Set();
19
20
  const cursorCheckpointRetryTimers = new Map();
20
21
  const CURSOR_CHECKPOINT_RETRY_DELAYS_MS = [250, 1_000, 3_000];
22
+ export async function executeCurrentSupervisedTool(input, env = process.env, options = {}) {
23
+ const coordinates = await requireCurrentSupervisedCoordinates(env, options);
24
+ const timeoutMs = options.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
25
+ const negotiated = await negotiateSupervisor(coordinates.socketPath, timeoutMs);
26
+ if (negotiated.generation === null)
27
+ throw new Error("The supervised daemon generation is unavailable.");
28
+ const response = await supervisorRequest(coordinates.socketPath, {
29
+ version: negotiated.protocolVersion,
30
+ id: randomUUID(),
31
+ method: "supervisor.execute_bounded_tool",
32
+ params: {
33
+ entry_id: coordinates.entryId,
34
+ work_attempt_id: coordinates.workAttemptId,
35
+ execution_generation_id: coordinates.executionGenerationId,
36
+ ...(coordinates.providerTurnId ? { provider_turn_id: coordinates.providerTurnId } : {}),
37
+ daemon_generation: negotiated.generation,
38
+ mcp_request_id: input.mcpRequestId,
39
+ tool_name: input.toolName,
40
+ input: input.input,
41
+ },
42
+ }, null);
43
+ if (!response.ok) {
44
+ if (/Unsupported daemon method:\s*supervisor\.execute_bounded_tool/i.test(response.error ?? "")) {
45
+ return { state: "unsupported" };
46
+ }
47
+ throw new Error(response.error || "The daemon-owned supervised tool was rejected.");
48
+ }
49
+ const result = response.result && typeof response.result === "object"
50
+ ? response.result
51
+ : {};
52
+ const roomId = typeof result.room_id === "string" ? result.room_id.trim() : "";
53
+ if (!roomId || roomId.length > 1_024 || /[\u0000-\u001f\u007f]/.test(roomId)) {
54
+ throw new Error("The supervised daemon did not return valid exact room authority.");
55
+ }
56
+ return { state: "completed", roomId, result: result.result };
57
+ }
21
58
  export async function prepareCurrentSupervisedEffect(input, env = process.env, options = {}) {
22
59
  const coordinates = await requireCurrentSupervisedCoordinates(env, options);
23
60
  const timeoutMs = options.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
@@ -412,6 +449,8 @@ export function scheduleSupervisedWorkerCursorCheckpoint(session, roomCursor, en
412
449
  });
413
450
  }
414
451
  async function enqueueSupervisedWorkerCursorCheckpoint(session, roomCursor, env, options) {
452
+ if (parseRoomMessageNumber(roomCursor) === null)
453
+ return;
415
454
  const coordinates = await resolveSupervisorCoordinates(session, env, options);
416
455
  if (!coordinates)
417
456
  return;
@@ -509,13 +548,14 @@ function isNewerRoomCursor(candidate, current) {
509
548
  return false;
510
549
  const candidateNumber = parseRoomMessageNumber(candidate);
511
550
  const currentNumber = parseRoomMessageNumber(current);
512
- if (candidateNumber !== null && currentNumber !== null)
513
- return candidateNumber > currentNumber;
514
- return true;
551
+ if (candidateNumber === null)
552
+ return false;
553
+ if (currentNumber === null)
554
+ return true;
555
+ return candidateNumber > currentNumber;
515
556
  }
516
557
  function parseRoomMessageNumber(cursor) {
517
- const match = /^msg_(\d+)$/.exec(cursor.trim());
518
- return match ? BigInt(match[1]) : null;
558
+ return parsePositivePgIntegerScopedId(cursor, "msg");
519
559
  }
520
560
  /** Transport failures are retryable bookkeeping failures, not worker failures. */
521
561
  export function isRetryableSupervisorBridgeError(error) {
@@ -695,16 +735,17 @@ function supervisorRequest(socketPath, request, timeoutMs) {
695
735
  const socket = createConnection(socketPath);
696
736
  let buffer = "";
697
737
  let finished = false;
698
- const timer = setTimeout(() => {
738
+ const timer = timeoutMs === null ? null : setTimeout(() => {
699
739
  socket.destroy();
700
740
  finish(() => reject(new Error("Timed out communicating with the supervisor daemon.")));
701
741
  }, timeoutMs);
702
- timer.unref();
742
+ timer?.unref();
703
743
  const finish = (operation) => {
704
744
  if (finished)
705
745
  return;
706
746
  finished = true;
707
- clearTimeout(timer);
747
+ if (timer)
748
+ clearTimeout(timer);
708
749
  operation();
709
750
  };
710
751
  socket.setEncoding("utf8");
@@ -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,6 +8,8 @@ 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();
@@ -90,7 +93,8 @@ export function workerModeDisabledToolResult(toolDescription = "This owner-auth
90
93
  * asks it to do so.
91
94
  */
92
95
  export function supervisedBoundedDeliveryDisabledToolResult(toolName = "wait_for_messages") {
93
- if (process.env.LETAGENTS_SUPERVISED_BOUNDED_TURNS?.trim() !== "1") {
96
+ if (!getDaemonToolExecutionContext()
97
+ && process.env.LETAGENTS_SUPERVISED_BOUNDED_TURNS?.trim() !== "1") {
94
98
  return null;
95
99
  }
96
100
  return {
@@ -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,5 @@
1
1
  import { runWithCurrentSupervisedRoom } from "./runtime/room-state.js";
2
- import { completeCurrentSupervisedEffect, prepareCurrentSupervisedEffect, } from "./runtime/supervisor-bridge.js";
2
+ import { completeCurrentSupervisedEffect, executeCurrentSupervisedTool, prepareCurrentSupervisedEffect, } from "./runtime/supervisor-bridge.js";
3
3
  const READ_TOOLS = new Set([
4
4
  "get_current_room",
5
5
  "check_repo",
@@ -15,6 +15,13 @@ const READ_TOOLS = new Set([
15
15
  "status_local_codex_session",
16
16
  "rental_list_requests",
17
17
  ]);
18
+ export function supervisedToolIsMutation(toolName) {
19
+ return !READ_TOOLS.has(toolName);
20
+ }
21
+ // The desktop daemon's local control protocol intentionally uses small bounded
22
+ // frames. A read result can be returned live to the provider without copying
23
+ // the entire payload into the durable effect journal.
24
+ const MAX_DURABLE_READ_RESULT_BYTES = 16 * 1024;
18
25
  function instruction(text, data = {}) {
19
26
  const payload = { ...data, instruction: text };
20
27
  return {
@@ -22,7 +29,19 @@ function instruction(text, data = {}) {
22
29
  structuredContent: payload,
23
30
  };
24
31
  }
32
+ export function durableCompletionResult(result, mutation) {
33
+ if (mutation)
34
+ return result;
35
+ const serializedBytes = Buffer.byteLength(JSON.stringify(result), "utf8");
36
+ if (serializedBytes <= MAX_DURABLE_READ_RESULT_BYTES)
37
+ return result;
38
+ 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.", {
39
+ code: "SUPERVISED_READ_RESULT_NOT_RETAINED",
40
+ serialized_bytes: serializedBytes,
41
+ });
42
+ }
25
43
  const productionDependencies = {
44
+ executeTool: executeCurrentSupervisedTool,
26
45
  prepareEffect: prepareCurrentSupervisedEffect,
27
46
  completeEffect: completeCurrentSupervisedEffect,
28
47
  withRoom: runWithCurrentSupervisedRoom,
@@ -37,6 +56,7 @@ export function profileAwareToolServer(server, profile, dependencies = productio
37
56
  return typeof value === "function" ? value.bind(target) : value;
38
57
  }
39
58
  return (name, ...registration) => {
59
+ const mutation = supervisedToolIsMutation(name);
40
60
  const callback = registration.at(-1);
41
61
  if (typeof callback !== "function")
42
62
  throw new Error(`Tool ${name} has no callback.`);
@@ -46,12 +66,18 @@ export function profileAwareToolServer(server, profile, dependencies = productio
46
66
  if (extra.requestId === undefined || extra.requestId === null || String(extra.requestId).trim() === "") {
47
67
  throw new Error(`Supervised tool ${name} is missing its MCP request id; refusing an effect that cannot be deduplicated safely.`);
48
68
  }
49
- const prepared = await dependencies.prepareEffect({
69
+ const executionRequest = {
50
70
  toolName: name,
51
71
  input,
52
72
  mcpRequestId: String(extra.requestId),
53
- mutation: !READ_TOOLS.has(name),
54
- });
73
+ };
74
+ if (dependencies.executeTool) {
75
+ const executed = await dependencies.executeTool(executionRequest);
76
+ if (executed.state === "completed") {
77
+ return dependencies.withRoom(executed.roomId, () => executed.result);
78
+ }
79
+ }
80
+ const prepared = await dependencies.prepareEffect({ ...executionRequest, mutation });
55
81
  return dependencies.withRoom(prepared.roomId, async () => {
56
82
  if (prepared.state === "completed")
57
83
  return prepared.result;
@@ -94,7 +120,10 @@ export function profileAwareToolServer(server, profile, dependencies = productio
94
120
  }
95
121
  // Completion transport is deliberately outside the callback catch.
96
122
  // A reporting failure must never relabel a successful action failed.
97
- await dependencies.completeEffect({ effectId: prepared.effectId, result });
123
+ await dependencies.completeEffect({
124
+ effectId: prepared.effectId,
125
+ result: durableCompletionResult(result, mutation),
126
+ });
98
127
  return result;
99
128
  });
100
129
  };