letagents 0.12.11 → 0.12.12

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.
@@ -1,33 +1,303 @@
1
- import { randomUUID } from "node:crypto";
1
+ import { createHash, randomUUID } from "node:crypto";
2
+ import { lstat, readFile, realpath } from "node:fs/promises";
2
3
  import { createConnection } from "node:net";
4
+ import { homedir } from "node:os";
5
+ import { join } from "node:path";
6
+ import { getCurrentSupervisedRoomAuthority } from "./supervised-room-authority.js";
3
7
  const NEGOTIATION_PROTOCOL_VERSION = 1;
4
8
  const SUPPORTED_SUPERVISOR_PROTOCOL_VERSIONS = new Set([1, 2]);
5
9
  const DEFAULT_REQUEST_TIMEOUT_MS = 5_000;
10
+ const CONFIRMED_BINDING_VERIFY_TIMEOUT_MS = 250;
11
+ const SUPERVISOR_CONTEXT_FILE = ".letagents-supervisor-context.json";
12
+ const WORK_ATTEMPT_MARKER_FILE = ".letagents-work-attempt.json";
13
+ const MAX_SUPERVISOR_CONTEXT_BYTES = 4 * 1024;
14
+ const confirmedBindingsBySession = new Map();
15
+ const confirmedRequestsBySession = new Map();
16
+ const confirmedProtocolsBySession = new Map();
17
+ const pendingCursorCheckpoints = new Map();
18
+ const activeCursorCheckpointDrains = new Set();
19
+ const cursorCheckpointRetryTimers = new Map();
20
+ const CURSOR_CHECKPOINT_RETRY_DELAYS_MS = [250, 1_000, 3_000];
21
+ export async function prepareCurrentSupervisedEffect(input, env = process.env, options = {}) {
22
+ const coordinates = await requireCurrentSupervisedCoordinates(env, options);
23
+ const timeoutMs = options.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
24
+ const negotiated = await negotiateSupervisor(coordinates.socketPath, timeoutMs);
25
+ if (negotiated.generation === null)
26
+ throw new Error("The supervised daemon generation is unavailable.");
27
+ const response = await supervisorRequest(coordinates.socketPath, {
28
+ version: negotiated.protocolVersion,
29
+ id: randomUUID(),
30
+ method: "supervisor.prepare_bounded_effect",
31
+ params: {
32
+ entry_id: coordinates.entryId,
33
+ work_attempt_id: coordinates.workAttemptId,
34
+ execution_generation_id: coordinates.executionGenerationId,
35
+ ...(coordinates.providerTurnId ? { provider_turn_id: coordinates.providerTurnId } : {}),
36
+ daemon_generation: negotiated.generation,
37
+ mcp_request_id: input.mcpRequestId,
38
+ tool_name: input.toolName,
39
+ input: input.input,
40
+ mutation: input.mutation,
41
+ },
42
+ }, timeoutMs);
43
+ if (!response.ok)
44
+ throw new Error(response.error || "The supervised effect was rejected.");
45
+ const result = response.result && typeof response.result === "object" ? response.result : {};
46
+ const roomId = typeof result.room_id === "string" ? result.room_id.trim() : "";
47
+ if (!roomId || roomId.length > 1_024 || /[\u0000-\u001f\u007f]/.test(roomId)) {
48
+ throw new Error("The supervised daemon did not return valid exact room authority.");
49
+ }
50
+ if (result.state === "completed")
51
+ return { state: "completed", roomId, result: result.result };
52
+ const effectId = typeof result.effect_id === "string" ? result.effect_id : "";
53
+ if (!effectId)
54
+ throw new Error("The supervised effect journal did not return an effect id.");
55
+ if (result.state === "uncertain") {
56
+ const error = typeof result.error === "string" && result.error.trim()
57
+ ? result.error
58
+ : "The mutating tool outcome is uncertain.";
59
+ return { state: "uncertain", roomId, effectId, error };
60
+ }
61
+ if (result.state !== "prepared") {
62
+ throw new Error("The supervised effect journal returned an unsupported state.");
63
+ }
64
+ if (result.action === "execute") {
65
+ return { state: "prepared", roomId, effectId, action: "execute" };
66
+ }
67
+ if (result.action === "use_final_answer" && typeof result.source_message_id === "string" && result.source_message_id.trim()) {
68
+ return { state: "prepared", roomId, effectId, action: "use_final_answer", sourceMessageId: result.source_message_id };
69
+ }
70
+ if (result.action === "room_move_prepared" && typeof result.destination_room === "string" && result.destination_room.trim()) {
71
+ return { state: "prepared", roomId, effectId, action: "room_move_prepared", destinationRoom: result.destination_room };
72
+ }
73
+ throw new Error("The supervised effect journal returned an unsupported action.");
74
+ }
75
+ export async function completeCurrentSupervisedEffect(input, env = process.env, options = {}) {
76
+ const coordinates = await requireCurrentSupervisedCoordinates(env, options);
77
+ const timeoutMs = options.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
78
+ const deadline = Date.now() + timeoutMs;
79
+ let retryAttempt = 0;
80
+ while (true) {
81
+ let response;
82
+ try {
83
+ // Completion is idempotent by effect id. A daemon handoff can race the
84
+ // first socket request after the external callback already succeeded,
85
+ // so keep renegotiating within one bounded request budget instead of
86
+ // stranding an executing journal or tempting the caller to repeat the
87
+ // side effect. The successor's unlink-to-listen gap can span multiple
88
+ // failed socket attempts during process startup.
89
+ const negotiated = await negotiateSupervisor(coordinates.socketPath, remainingRequestTimeout(deadline));
90
+ if (negotiated.generation === null)
91
+ throw new Error("The supervised daemon generation is unavailable.");
92
+ response = await supervisorRequest(coordinates.socketPath, {
93
+ version: negotiated.protocolVersion,
94
+ id: randomUUID(),
95
+ method: "supervisor.complete_bounded_effect",
96
+ params: {
97
+ entry_id: coordinates.entryId,
98
+ work_attempt_id: coordinates.workAttemptId,
99
+ execution_generation_id: coordinates.executionGenerationId,
100
+ ...(coordinates.providerTurnId ? { provider_turn_id: coordinates.providerTurnId } : {}),
101
+ daemon_generation: negotiated.generation,
102
+ effect_id: input.effectId,
103
+ result: input.result,
104
+ error: input.error,
105
+ },
106
+ }, remainingRequestTimeout(deadline));
107
+ }
108
+ catch (error) {
109
+ const failure = error instanceof Error ? error : new Error(String(error));
110
+ if (!completionMayHaveRacedHandoff(failure)
111
+ || !await waitForCompletionHandoffRetry(deadline, retryAttempt++))
112
+ throw error;
113
+ continue;
114
+ }
115
+ if (response.ok)
116
+ return;
117
+ const rejection = new Error(response.error || "The supervised effect completion was rejected.");
118
+ if (!completionMayHaveRacedHandoff(rejection)
119
+ || !await waitForCompletionHandoffRetry(deadline, retryAttempt++))
120
+ throw rejection;
121
+ }
122
+ }
123
+ function completionMayHaveRacedHandoff(error) {
124
+ return isRetryableSupervisorBridgeError(error)
125
+ || /stale.*(?:daemon|supervisor).*generation|handoff/i.test(error.message);
126
+ }
127
+ async function waitForCompletionHandoffRetry(deadline, attempt) {
128
+ const remaining = deadline - Date.now();
129
+ if (remaining <= 0)
130
+ return false;
131
+ const delay = Math.min(25 * (2 ** Math.min(attempt, 4)), 250, remaining);
132
+ await new Promise((resolve) => setTimeout(resolve, delay));
133
+ return Date.now() < deadline;
134
+ }
135
+ async function requireCurrentSupervisedCoordinates(env, options) {
136
+ if (env.LETAGENTS_EXECUTION_PROFILE?.trim() !== "supervised_room_turn") {
137
+ throw new Error("Supervised effects require the supervised_room_turn execution profile.");
138
+ }
139
+ const coordinates = await resolveSupervisorCoordinates(supervisedContextSession(env), env, options);
140
+ if (!coordinates)
141
+ throw new Error("The exact supervised daemon coordinates are unavailable.");
142
+ return coordinates;
143
+ }
144
+ /**
145
+ * Resolve and borrow for the MCP process itself. Unlike registration, this
146
+ * never reads local agent state: the daemon-owned launch context is the sole
147
+ * authority for the exact worker session identity.
148
+ */
149
+ export async function borrowCurrentSupervisedWorkerCredential(env = process.env, options = {}) {
150
+ if (env.LETAGENTS_SUPERVISED_BOUNDED_TURNS?.trim() !== "1") {
151
+ return { state: "not_supervised" };
152
+ }
153
+ const seed = supervisedContextSession(env);
154
+ const coordinates = await resolveSupervisorCoordinates(seed, env, options);
155
+ const roomId = getCurrentSupervisedRoomAuthority();
156
+ if (!coordinates?.agentSessionId || !roomId) {
157
+ return { state: "stale", code: "SUPERVISED_CREDENTIAL_STALE" };
158
+ }
159
+ return borrowSupervisedWorkerCredential({
160
+ ...seed,
161
+ session_id: coordinates.agentSessionId,
162
+ room_id: roomId,
163
+ }, env, { ...options, resolvedCoordinates: coordinates });
164
+ }
165
+ /** A public, non-secret session-shaped marker for supervised MCP tools. */
166
+ export async function resolveCurrentSupervisedWorkerSession(roomId, env = process.env, options = {}) {
167
+ const seed = supervisedContextSession(env);
168
+ const coordinates = await resolveSupervisorCoordinates(seed, env, options);
169
+ const boundRoomId = getCurrentSupervisedRoomAuthority();
170
+ if (!coordinates?.agentSessionId || !boundRoomId) {
171
+ throw new Error("Daemon-supervised bounded turn is missing its exact worker session context.");
172
+ }
173
+ if (roomId && roomId !== boundRoomId) {
174
+ throw new Error(`Daemon-supervised worker session is registered for ${boundRoomId}, not ${roomId}.`);
175
+ }
176
+ const displayName = coordinates.agentDisplayName || "Daemon-supervised worker";
177
+ return {
178
+ ...seed,
179
+ session_id: coordinates.agentSessionId,
180
+ room_id: boundRoomId,
181
+ agent_key: coordinates.agentSessionId,
182
+ actor_label: displayName,
183
+ display_name: displayName,
184
+ };
185
+ }
186
+ function supervisedContextSession(env = process.env) {
187
+ const now = new Date(0).toISOString();
188
+ // Codex is the only provider that can recover supervisor coordinates from
189
+ // the worktree context file. Other supervised providers pass their exact
190
+ // coordinates and provider identity through the daemon-created MCP
191
+ // environment, so the fallback must remain Codex for existing context-file
192
+ // sessions rather than inventing a provider that cannot own the file.
193
+ const provider = env.LETAGENTS_SUPERVISOR_PROVIDER?.trim() || "codex";
194
+ const label = provider === "open-model"
195
+ ? "Open Model"
196
+ : provider === "claude-code"
197
+ ? "Claude Code"
198
+ : provider === "codex"
199
+ ? "Codex"
200
+ : "Supervised agent";
201
+ return {
202
+ session_id: "", session_token: "", room_id: "", session_kind: "worker", runtime: provider,
203
+ actor_label: "Daemon-supervised worker", agent_key: "daemon-supervised-worker",
204
+ display_name: "Daemon-supervised worker", owner_label: "", ide_label: label,
205
+ created_at: now, updated_at: now, last_seen_at: now,
206
+ };
207
+ }
208
+ /**
209
+ * Borrow an in-memory, exact-generation worker bearer. This intentionally has
210
+ * no fallback to the owner/session token: a daemon restarted without Electron
211
+ * handoff must retain its durable inbox and wait.
212
+ */
213
+ export async function borrowSupervisedWorkerCredential(session, env = process.env, options = {}) {
214
+ const coordinates = options.resolvedCoordinates ?? await resolveSupervisorCoordinates(session, env, options);
215
+ if (!coordinates)
216
+ return { state: "not_supervised" };
217
+ if (session.session_kind !== "worker")
218
+ return { state: "stale", code: "SUPERVISED_CREDENTIAL_STALE" };
219
+ const timeoutMs = options.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
220
+ const apiUrl = normalizedWorkerApiOrigin(env);
221
+ const negotiated = await negotiateSupervisor(coordinates.socketPath, timeoutMs);
222
+ if (negotiated.generation === null)
223
+ return { state: "stale", code: "SUPERVISED_CREDENTIAL_STALE" };
224
+ const response = await supervisorRequest(coordinates.socketPath, {
225
+ version: negotiated.protocolVersion,
226
+ id: randomUUID(),
227
+ method: "supervisor.borrow_worker_credential",
228
+ params: {
229
+ entry_id: coordinates.entryId, room_id: session.room_id, work_attempt_id: coordinates.workAttemptId,
230
+ execution_generation_id: coordinates.executionGenerationId, agent_session_id: session.session_id,
231
+ ...(coordinates.providerTurnId ? { provider_turn_id: coordinates.providerTurnId } : {}),
232
+ daemon_generation: negotiated.generation, api_url: apiUrl,
233
+ },
234
+ }, timeoutMs);
235
+ if (!response.ok || response.version !== negotiated.protocolVersion)
236
+ return { state: "stale", code: "SUPERVISED_CREDENTIAL_STALE" };
237
+ const result = response.result && typeof response.result === "object" ? response.result : {};
238
+ if (result.status === "deferred")
239
+ return { state: "deferred", code: "SUPERVISED_CREDENTIAL_UNAVAILABLE" };
240
+ if (result.status !== "available" || typeof result.credential !== "string" || !result.credential.trim()) {
241
+ return { state: "stale", code: "SUPERVISED_CREDENTIAL_STALE" };
242
+ }
243
+ return { state: "available", credential: result.credential };
244
+ }
245
+ function normalizedWorkerApiOrigin(env) {
246
+ const apiUrl = env.LETAGENTS_API_URL?.trim() || "https://letagents.chat";
247
+ let parsed;
248
+ try {
249
+ parsed = new URL(apiUrl);
250
+ }
251
+ catch {
252
+ throw new Error("Daemon-supervised worker requires a valid LETAGENTS_API_URL.");
253
+ }
254
+ const loopbackHosts = new Set(["localhost", "127.0.0.1", "::1", "[::1]"]);
255
+ if (parsed.protocol !== "https:" && !(parsed.protocol === "http:" && loopbackHosts.has(parsed.hostname.toLowerCase()))) {
256
+ throw new Error("Daemon-supervised worker requires HTTPS unless LETAGENTS_API_URL uses an exact loopback host.");
257
+ }
258
+ return parsed.origin;
259
+ }
6
260
  /** Bind the exact worker credential minted by registration to its daemon lane. */
7
261
  export async function bindSupervisedWorkerSession(session, env = process.env, options = {}) {
8
- const entryId = env.LETAGENTS_SUPERVISOR_ENTRY_ID?.trim();
9
- const socketPath = env.LETAGENTS_SUPERVISOR_DAEMON_SOCKET?.trim();
10
- const workAttemptId = env.LETAGENTS_SUPERVISOR_WORK_ATTEMPT_ID?.trim();
11
- const executionGenerationId = env.LETAGENTS_SUPERVISOR_EXECUTION_GENERATION_ID?.trim();
12
- if (!entryId && !socketPath && !workAttemptId && !executionGenerationId)
13
- return false;
14
- if (!entryId || !socketPath || !workAttemptId || !executionGenerationId)
15
- throw new Error("Supervised worker bridge environment is incomplete.");
262
+ return (await bindSupervisedWorkerSessionWithContext(session, env, options)).bound;
263
+ }
264
+ /**
265
+ * Bind and return the validated file-backed route, when one was used.
266
+ * Registration persists this route so later wait/checkpoint calls do not fall
267
+ * back to an unrelated long-lived MCP process cwd.
268
+ */
269
+ export async function bindSupervisedWorkerSessionWithContext(session, env = process.env, options = {}) {
270
+ const coordinates = options.resolvedCoordinates ?? await resolveSupervisorCoordinates(session, env, options);
271
+ if (!coordinates)
272
+ return { bound: false, supervisorContextCwd: null };
273
+ const { entryId, socketPath, workAttemptId, executionGenerationId } = coordinates;
16
274
  if (session.session_kind !== "worker")
17
275
  throw new Error("A supervised provider must register a worker session.");
18
276
  const timeoutMs = options.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
19
277
  if (!Number.isSafeInteger(timeoutMs) || timeoutMs < 1)
20
278
  throw new Error("Supervisor bridge timeout must be a positive integer.");
21
- const negotiation = await supervisorRequest(socketPath, {
22
- version: NEGOTIATION_PROTOCOL_VERSION,
23
- id: randomUUID(),
24
- method: "daemon.negotiate",
25
- }, timeoutMs);
26
- if (!negotiation.ok)
27
- throw new Error(negotiation.error || "Supervisor protocol negotiation failed.");
28
- const protocolVersion = negotiationProtocolVersion(negotiation.result);
29
- if (negotiation.version !== protocolVersion)
30
- throw new Error("Supervisor negotiation response version does not match its negotiated protocol.");
279
+ const requestKey = bindingRequestKey(session, coordinates, env);
280
+ if (options.allowConfirmedFastPath && confirmedRequestsBySession.get(session.session_id) === requestKey) {
281
+ const confirmedProtocol = confirmedProtocolsBySession.get(session.session_id);
282
+ if (!confirmedProtocol)
283
+ throw new Error("The supervised worker binding protocol is not confirmed.");
284
+ try {
285
+ await verifyConfirmedBinding(session, coordinates, env, confirmedProtocol, Math.min(timeoutMs, CONFIRMED_BINDING_VERIFY_TIMEOUT_MS));
286
+ }
287
+ catch (error) {
288
+ clearBindingConfirmationIfCurrent(session.session_id, requestKey);
289
+ throw error;
290
+ }
291
+ return { bound: true, supervisorContextCwd: coordinates.supervisorContextCwd };
292
+ }
293
+ const deadline = Date.now() + timeoutMs;
294
+ const { protocolVersion, daemonIdentity } = await negotiateSupervisor(socketPath, timeoutMs);
295
+ const bindingKey = daemonIdentity ? `${requestKey}\u0000${daemonIdentity}` : null;
296
+ if (bindingKey && confirmedBindingsBySession.get(session.session_id) === bindingKey) {
297
+ confirmedRequestsBySession.set(session.session_id, requestKey);
298
+ confirmedProtocolsBySession.set(session.session_id, protocolVersion);
299
+ return { bound: true, supervisorContextCwd: coordinates.supervisorContextCwd };
300
+ }
31
301
  const response = await supervisorRequest(socketPath, {
32
302
  version: protocolVersion,
33
303
  id: randomUUID(),
@@ -41,13 +311,352 @@ export async function bindSupervisedWorkerSession(session, env = process.env, op
41
311
  agent_session_token: session.session_token,
42
312
  api_url: env.LETAGENTS_API_URL?.trim() || "https://letagents.chat",
43
313
  },
44
- }, timeoutMs);
314
+ }, remainingRequestTimeout(deadline));
45
315
  if (!response.ok)
46
316
  throw new Error(response.error || "Supervisor rejected the worker session binding.");
47
317
  if (response.version !== protocolVersion)
48
318
  throw new Error("Supervisor binding response used an unexpected protocol version.");
319
+ if (bindingKey) {
320
+ confirmedRequestsBySession.set(session.session_id, requestKey);
321
+ confirmedBindingsBySession.set(session.session_id, bindingKey);
322
+ confirmedProtocolsBySession.set(session.session_id, protocolVersion);
323
+ }
324
+ else {
325
+ // Without a stable daemon identity we cannot prove that a later process at
326
+ // the same socket owns this confirmation, so every wait binds strictly.
327
+ confirmedRequestsBySession.delete(session.session_id);
328
+ confirmedBindingsBySession.delete(session.session_id);
329
+ confirmedProtocolsBySession.delete(session.session_id);
330
+ }
331
+ return { bound: true, supervisorContextCwd: coordinates.supervisorContextCwd };
332
+ }
333
+ function bindingRequestKey(session, coordinates, env) {
334
+ const tokenDigest = createHash("sha256").update(session.session_token).digest("hex");
335
+ return [
336
+ coordinates.socketPath,
337
+ coordinates.entryId,
338
+ coordinates.workAttemptId,
339
+ coordinates.executionGenerationId,
340
+ session.session_id,
341
+ session.room_id,
342
+ tokenDigest,
343
+ new URL(env.LETAGENTS_API_URL?.trim() || "https://letagents.chat").origin,
344
+ ].join("\u0000");
345
+ }
346
+ async function verifyConfirmedBinding(session, coordinates, env, protocolVersion, timeoutMs) {
347
+ const response = await supervisorRequest(coordinates.socketPath, {
348
+ version: protocolVersion,
349
+ id: randomUUID(),
350
+ method: "supervisor.verify_worker_session",
351
+ params: {
352
+ entry_id: coordinates.entryId,
353
+ room_id: session.room_id,
354
+ work_attempt_id: coordinates.workAttemptId,
355
+ execution_generation_id: coordinates.executionGenerationId,
356
+ agent_session_id: session.session_id,
357
+ agent_session_token: session.session_token,
358
+ api_url: env.LETAGENTS_API_URL?.trim() || "https://letagents.chat",
359
+ },
360
+ }, timeoutMs);
361
+ if (!response.ok)
362
+ throw new Error(response.error || "Supervisor rejected the worker session verification.");
363
+ if (response.version !== protocolVersion)
364
+ throw new Error("Supervisor verification response used an unexpected protocol version.");
365
+ }
366
+ function clearBindingConfirmationIfCurrent(sessionId, requestKey) {
367
+ if (confirmedRequestsBySession.get(sessionId) !== requestKey)
368
+ return;
369
+ confirmedRequestsBySession.delete(sessionId);
370
+ confirmedBindingsBySession.delete(sessionId);
371
+ confirmedProtocolsBySession.delete(sessionId);
372
+ }
373
+ /** Persist the room-delivery cursor beside the daemon-private exact worker credential. */
374
+ export async function checkpointSupervisedWorkerCursor(session, roomCursor, env = process.env, options = {}) {
375
+ const coordinates = options.resolvedCoordinates ?? await resolveSupervisorCoordinates(session, env, options);
376
+ if (!coordinates)
377
+ return false;
378
+ const { entryId, socketPath, workAttemptId, executionGenerationId } = coordinates;
379
+ if (session.session_kind !== "worker")
380
+ throw new Error("A supervised provider must use a worker session.");
381
+ if (!roomCursor.trim())
382
+ throw new Error("Supervised worker cursor is required.");
383
+ const timeoutMs = options.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
384
+ const deadline = Date.now() + timeoutMs;
385
+ const { protocolVersion } = await negotiateSupervisor(socketPath, timeoutMs);
386
+ const response = await supervisorRequest(socketPath, {
387
+ version: protocolVersion,
388
+ id: randomUUID(),
389
+ method: "supervisor.checkpoint_worker_cursor",
390
+ params: {
391
+ entry_id: entryId,
392
+ work_attempt_id: workAttemptId,
393
+ execution_generation_id: executionGenerationId,
394
+ agent_session_id: session.session_id,
395
+ room_cursor: roomCursor,
396
+ },
397
+ }, remainingRequestTimeout(deadline));
398
+ if (!response.ok)
399
+ throw new Error(response.error || "Supervisor rejected the worker cursor checkpoint.");
400
+ if (response.version !== protocolVersion)
401
+ throw new Error("Supervisor cursor checkpoint response used an unexpected protocol version.");
402
+ return true;
403
+ }
404
+ /**
405
+ * Coalesce an already-acknowledged cursor outside the MCP response path. The
406
+ * caller acknowledges a prior delivery by passing it as `after_message_id` on
407
+ * the next wait; newly observed output must never be checkpointed here.
408
+ */
409
+ export function scheduleSupervisedWorkerCursorCheckpoint(session, roomCursor, env = process.env, options = {}) {
410
+ void enqueueSupervisedWorkerCursorCheckpoint(session, roomCursor, env, options).catch((error) => {
411
+ console.error("[letagents] Supervised cursor checkpoint could not be scheduled:", error instanceof Error ? error.message : "unknown supervisor error");
412
+ });
413
+ }
414
+ async function enqueueSupervisedWorkerCursorCheckpoint(session, roomCursor, env, options) {
415
+ const coordinates = await resolveSupervisorCoordinates(session, env, options);
416
+ if (!coordinates)
417
+ return;
418
+ const key = [
419
+ session.session_id,
420
+ session.room_id,
421
+ coordinates.socketPath,
422
+ coordinates.entryId,
423
+ coordinates.workAttemptId,
424
+ coordinates.executionGenerationId,
425
+ ].join("\u0000");
426
+ const existing = pendingCursorCheckpoints.get(key);
427
+ if (existing && !isNewerRoomCursor(roomCursor, existing.roomCursor))
428
+ return;
429
+ pendingCursorCheckpoints.set(key, {
430
+ session,
431
+ roomCursor,
432
+ env,
433
+ options: { ...options, resolvedCoordinates: coordinates },
434
+ retryAttempt: 0,
435
+ });
436
+ const retryTimer = cursorCheckpointRetryTimers.get(key);
437
+ if (retryTimer) {
438
+ clearTimeout(retryTimer);
439
+ cursorCheckpointRetryTimers.delete(key);
440
+ }
441
+ startCursorCheckpointDrain(key);
442
+ }
443
+ async function drainCursorCheckpoints(key) {
444
+ try {
445
+ while (true) {
446
+ const pending = pendingCursorCheckpoints.get(key);
447
+ if (!pending)
448
+ return;
449
+ pendingCursorCheckpoints.delete(key);
450
+ try {
451
+ await checkpointSupervisedWorkerCursor(pending.session, pending.roomCursor, pending.env, pending.options);
452
+ }
453
+ catch (error) {
454
+ const queued = pendingCursorCheckpoints.get(key);
455
+ if (queued && isNewerRoomCursor(queued.roomCursor, pending.roomCursor))
456
+ continue;
457
+ if (isRetryableSupervisorBridgeError(error)
458
+ && pending.retryAttempt < CURSOR_CHECKPOINT_RETRY_DELAYS_MS.length) {
459
+ // A failed newer acknowledgement must not be replaced by an older
460
+ // concurrent wait that happened to enqueue while I/O was in flight.
461
+ if (queued)
462
+ pendingCursorCheckpoints.delete(key);
463
+ const delayMs = CURSOR_CHECKPOINT_RETRY_DELAYS_MS[pending.retryAttempt];
464
+ pendingCursorCheckpoints.set(key, { ...pending, retryAttempt: pending.retryAttempt + 1 });
465
+ const timer = setTimeout(() => {
466
+ cursorCheckpointRetryTimers.delete(key);
467
+ startCursorCheckpointDrain(key);
468
+ }, delayMs);
469
+ timer.unref?.();
470
+ cursorCheckpointRetryTimers.set(key, timer);
471
+ console.warn("[letagents] Supervised cursor checkpoint is pending:", error instanceof Error ? error.message : "unknown supervisor error");
472
+ return;
473
+ }
474
+ // An authority/generation rejection is not a harmless transport blip.
475
+ // Make the next wait prove the exact binding again before it can read.
476
+ await clearCheckpointBindingConfirmationIfCurrent(pending);
477
+ pendingCursorCheckpoints.delete(key);
478
+ console.error("[letagents] Supervised cursor checkpoint was rejected:", error instanceof Error ? error.message : "unknown supervisor error");
479
+ }
480
+ }
481
+ }
482
+ finally {
483
+ activeCursorCheckpointDrains.delete(key);
484
+ if (pendingCursorCheckpoints.has(key) && !cursorCheckpointRetryTimers.has(key)) {
485
+ startCursorCheckpointDrain(key);
486
+ }
487
+ }
488
+ }
489
+ async function clearCheckpointBindingConfirmationIfCurrent(pending) {
490
+ try {
491
+ const coordinates = pending.options.resolvedCoordinates
492
+ ?? await resolveSupervisorCoordinates(pending.session, pending.env, pending.options);
493
+ if (!coordinates)
494
+ return;
495
+ clearBindingConfirmationIfCurrent(pending.session.session_id, bindingRequestKey(pending.session, coordinates, pending.env));
496
+ }
497
+ catch {
498
+ // Missing or invalid context already fails the next wait closed.
499
+ }
500
+ }
501
+ function startCursorCheckpointDrain(key) {
502
+ if (activeCursorCheckpointDrains.has(key) || cursorCheckpointRetryTimers.has(key))
503
+ return;
504
+ activeCursorCheckpointDrains.add(key);
505
+ setImmediate(() => { void drainCursorCheckpoints(key); });
506
+ }
507
+ function isNewerRoomCursor(candidate, current) {
508
+ if (candidate === current)
509
+ return false;
510
+ const candidateNumber = parseRoomMessageNumber(candidate);
511
+ const currentNumber = parseRoomMessageNumber(current);
512
+ if (candidateNumber !== null && currentNumber !== null)
513
+ return candidateNumber > currentNumber;
49
514
  return true;
50
515
  }
516
+ function parseRoomMessageNumber(cursor) {
517
+ const match = /^msg_(\d+)$/.exec(cursor.trim());
518
+ return match ? BigInt(match[1]) : null;
519
+ }
520
+ /** Transport failures are retryable bookkeeping failures, not worker failures. */
521
+ export function isRetryableSupervisorBridgeError(error) {
522
+ const code = error?.code;
523
+ if (code && ["ECONNREFUSED", "ECONNRESET", "EPIPE", "ENOENT", "ETIMEDOUT"].includes(code))
524
+ return true;
525
+ const message = error instanceof Error ? error.message : String(error ?? "");
526
+ return /timed out communicating|socket hang up|connection (?:closed|refused|reset)|broken pipe/i.test(message);
527
+ }
528
+ function remainingRequestTimeout(deadline) {
529
+ const remaining = deadline - Date.now();
530
+ if (remaining < 1)
531
+ throw new Error("Timed out communicating with the supervisor daemon.");
532
+ return remaining;
533
+ }
534
+ async function resolveSupervisorCoordinates(session, env, options) {
535
+ const environmentCoordinates = {
536
+ entryId: env.LETAGENTS_SUPERVISOR_ENTRY_ID?.trim() ?? "",
537
+ socketPath: env.LETAGENTS_SUPERVISOR_DAEMON_SOCKET?.trim() ?? "",
538
+ workAttemptId: env.LETAGENTS_SUPERVISOR_WORK_ATTEMPT_ID?.trim() ?? "",
539
+ executionGenerationId: env.LETAGENTS_SUPERVISOR_EXECUTION_GENERATION_ID?.trim() ?? "",
540
+ providerTurnId: env.LETAGENTS_SUPERVISOR_PROVIDER_TURN_ID?.trim() || undefined,
541
+ agentSessionId: env.LETAGENTS_SUPERVISOR_AGENT_SESSION_ID?.trim() || undefined,
542
+ roomId: env.LETAGENTS_SUPERVISOR_ROOM_ID?.trim() || undefined,
543
+ agentDisplayName: env.LETAGENTS_SUPERVISOR_AGENT_DISPLAY_NAME?.trim() || undefined,
544
+ };
545
+ const values = [
546
+ environmentCoordinates.entryId,
547
+ environmentCoordinates.socketPath,
548
+ environmentCoordinates.workAttemptId,
549
+ environmentCoordinates.executionGenerationId,
550
+ ];
551
+ const hasEnvironmentCoordinates = values.some((value) => Boolean(value));
552
+ if (hasEnvironmentCoordinates && values.some((value) => !value)) {
553
+ throw new Error("Supervised worker bridge environment is incomplete.");
554
+ }
555
+ if (hasEnvironmentCoordinates) {
556
+ return { ...environmentCoordinates, supervisorContextCwd: null };
557
+ }
558
+ const context = await readCodexSupervisorContext(options.cwd ?? session.supervisor_context_cwd ?? process.cwd());
559
+ if (!context) {
560
+ if (session.supervisor_context_cwd?.trim()) {
561
+ throw new Error("The persisted supervised worker context is missing.");
562
+ }
563
+ return null;
564
+ }
565
+ if (!/^codex(?::|$)/i.test(session.runtime.trim())) {
566
+ throw new Error("Codex supervisor bridge context cannot bind a non-Codex worker session.");
567
+ }
568
+ if (session.room_id && context.roomId !== session.room_id) {
569
+ throw new Error("Codex supervisor bridge context does not match the worker room.");
570
+ }
571
+ return {
572
+ ...context,
573
+ socketPath: options.trustedDaemonSocketPath ?? join(homedir(), ".letagents", "daemon.sock"),
574
+ };
575
+ }
576
+ async function readCodexSupervisorContext(cwd) {
577
+ const path = join(cwd, SUPERVISOR_CONTEXT_FILE);
578
+ let encoded;
579
+ try {
580
+ const info = await lstat(path);
581
+ if (!info.isFile() || info.isSymbolicLink() || info.size > MAX_SUPERVISOR_CONTEXT_BYTES) {
582
+ throw new Error("Codex supervisor bridge context must be a small regular file.");
583
+ }
584
+ encoded = await readFile(path, "utf8");
585
+ }
586
+ catch (error) {
587
+ if (error.code === "ENOENT")
588
+ return null;
589
+ throw error;
590
+ }
591
+ let value;
592
+ try {
593
+ value = JSON.parse(encoded);
594
+ }
595
+ catch {
596
+ throw new Error("Codex supervisor bridge context is not valid JSON.");
597
+ }
598
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
599
+ throw new Error("Codex supervisor bridge context is malformed.");
600
+ }
601
+ const record = value;
602
+ if (record.version !== 1 || record.provider !== "codex") {
603
+ throw new Error("Codex supervisor bridge context has an unsupported identity.");
604
+ }
605
+ const required = {
606
+ entryId: record.entry_id,
607
+ roomId: record.room_id,
608
+ workAttemptId: record.work_attempt_id,
609
+ executionGenerationId: record.execution_generation_id,
610
+ };
611
+ for (const [field, candidate] of Object.entries(required)) {
612
+ if (typeof candidate !== "string" || !candidate.trim()) {
613
+ throw new Error(`Codex supervisor bridge context ${field} is required.`);
614
+ }
615
+ }
616
+ const coordinates = Object.fromEntries(Object.entries(required).map(([field, candidate]) => [field, candidate.trim()]));
617
+ const agentSessionId = typeof record.agent_session_id === "string" && record.agent_session_id.trim()
618
+ ? record.agent_session_id.trim()
619
+ : undefined;
620
+ const agentDisplayName = typeof record.agent_display_name === "string" && record.agent_display_name.trim()
621
+ ? record.agent_display_name.trim()
622
+ : undefined;
623
+ const marker = await readWorkAttemptMarker(cwd);
624
+ if (marker.workAttemptId !== coordinates.workAttemptId) {
625
+ throw new Error("Codex supervisor bridge context does not match the daemon-owned worktree.");
626
+ }
627
+ return { ...coordinates, agentSessionId, agentDisplayName, supervisorContextCwd: await realpath(cwd) };
628
+ }
629
+ async function readWorkAttemptMarker(cwd) {
630
+ const path = join(cwd, WORK_ATTEMPT_MARKER_FILE);
631
+ let encoded;
632
+ try {
633
+ const info = await lstat(path);
634
+ if (!info.isFile() || info.isSymbolicLink() || info.size > MAX_SUPERVISOR_CONTEXT_BYTES) {
635
+ throw new Error("Daemon work-attempt marker must be a small regular file.");
636
+ }
637
+ encoded = await readFile(path, "utf8");
638
+ }
639
+ catch (error) {
640
+ if (error.code === "ENOENT") {
641
+ throw new Error("Codex supervisor bridge context is outside a daemon-owned worktree.");
642
+ }
643
+ throw error;
644
+ }
645
+ let value;
646
+ try {
647
+ value = JSON.parse(encoded);
648
+ }
649
+ catch {
650
+ throw new Error("Daemon work-attempt marker is not valid JSON.");
651
+ }
652
+ const workAttemptId = value && typeof value === "object" && !Array.isArray(value)
653
+ ? value.work_attempt_id
654
+ : null;
655
+ if (value?.version !== 1 || typeof workAttemptId !== "string" || !workAttemptId.trim()) {
656
+ throw new Error("Daemon work-attempt marker is malformed.");
657
+ }
658
+ return { workAttemptId: workAttemptId.trim() };
659
+ }
51
660
  function negotiationProtocolVersion(result) {
52
661
  if (!result || typeof result !== "object")
53
662
  throw new Error("Supervisor protocol negotiation returned a malformed result.");
@@ -58,21 +667,49 @@ function negotiationProtocolVersion(result) {
58
667
  }
59
668
  return protocolVersion;
60
669
  }
670
+ async function negotiateSupervisor(socketPath, timeoutMs) {
671
+ const negotiation = await supervisorRequest(socketPath, {
672
+ version: NEGOTIATION_PROTOCOL_VERSION,
673
+ id: randomUUID(),
674
+ method: "daemon.negotiate",
675
+ }, timeoutMs);
676
+ if (!negotiation.ok)
677
+ throw new Error(negotiation.error || "Supervisor protocol negotiation failed.");
678
+ const protocolVersion = negotiationProtocolVersion(negotiation.result);
679
+ if (negotiation.version !== protocolVersion)
680
+ throw new Error("Supervisor negotiation response version does not match its negotiated protocol.");
681
+ const result = negotiation.result;
682
+ const hasCompleteIdentity = typeof result.generation === "number"
683
+ && Number.isSafeInteger(result.generation)
684
+ && typeof result.pid === "number"
685
+ && Number.isSafeInteger(result.pid)
686
+ && typeof result.started_at === "string"
687
+ && Boolean(result.started_at.trim());
688
+ const daemonIdentity = hasCompleteIdentity
689
+ ? [result.generation, result.pid, result.started_at].join(":")
690
+ : null;
691
+ return { protocolVersion, daemonIdentity, generation: hasCompleteIdentity ? Number(result.generation) : null };
692
+ }
61
693
  function supervisorRequest(socketPath, request, timeoutMs) {
62
694
  return new Promise((resolve, reject) => {
63
695
  const socket = createConnection(socketPath);
64
696
  let buffer = "";
697
+ let finished = false;
65
698
  const timer = setTimeout(() => {
66
699
  socket.destroy();
67
- reject(new Error("Timed out communicating with the supervisor daemon."));
700
+ finish(() => reject(new Error("Timed out communicating with the supervisor daemon.")));
68
701
  }, timeoutMs);
69
702
  timer.unref();
70
703
  const finish = (operation) => {
704
+ if (finished)
705
+ return;
706
+ finished = true;
71
707
  clearTimeout(timer);
72
708
  operation();
73
709
  };
74
710
  socket.setEncoding("utf8");
75
711
  socket.once("error", (error) => finish(() => reject(error)));
712
+ socket.once("close", () => finish(() => reject(new Error("Supervisor connection closed before a response."))));
76
713
  socket.on("data", (chunk) => {
77
714
  buffer += chunk;
78
715
  const newline = buffer.indexOf("\n");