letagents 0.12.11 → 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 (49) hide show
  1. package/dist/mcp/git-remote.js +7 -7
  2. package/dist/mcp/local-state/agent-sessions.js +78 -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 +25 -12
  7. package/dist/mcp/server/runtime/agent-sessions.js +58 -9
  8. package/dist/mcp/server/runtime/api.js +40 -4
  9. package/dist/mcp/server/runtime/daemon-tool-context.js +11 -0
  10. package/dist/mcp/server/runtime/execution-profile.js +19 -0
  11. package/dist/mcp/server/runtime/identity/directory.js +2 -2
  12. package/dist/mcp/server/runtime/messages.js +55 -0
  13. package/dist/mcp/server/runtime/presence.js +4 -2
  14. package/dist/mcp/server/runtime/room-api.js +24 -7
  15. package/dist/mcp/server/runtime/room-state.js +59 -3
  16. package/dist/mcp/server/runtime/rooms.js +67 -32
  17. package/dist/mcp/server/runtime/supervised-room-authority.js +8 -0
  18. package/dist/mcp/server/runtime/supervisor-bridge.js +702 -24
  19. package/dist/mcp/server/runtime/tool-surface-policy.js +26 -0
  20. package/dist/mcp/server/runtime/worker-bearer.js +44 -6
  21. package/dist/mcp/server/runtime-contract.js +27 -0
  22. package/dist/mcp/server/runtime.js +15 -4
  23. package/dist/mcp/server/supervised-tool-facade.js +134 -0
  24. package/dist/mcp/server/tools/agent-sessions.js +74 -7
  25. package/dist/mcp/server/tools/messages/index.js +3 -2
  26. package/dist/mcp/server/tools/messages/read-tool.js +54 -97
  27. package/dist/mcp/server/tools/messages/reasoning-tool.js +2 -0
  28. package/dist/mcp/server/tools/messages/send-tool.js +5 -0
  29. package/dist/mcp/server/tools/messages/status-tool.js +2 -0
  30. package/dist/mcp/server/tools/messages/wait-tool.js +344 -71
  31. package/dist/mcp/server/tools/onboarding/status-tool.js +9 -8
  32. package/dist/mcp/server/tools/rooms/inspection-tools.js +40 -22
  33. package/dist/mcp/server/tools/rooms/repo-initialization-tool.js +2 -1
  34. package/dist/mcp/server/tools/supervised-room-turn.js +42 -0
  35. package/dist/mcp/server/tools/tasks/board-tools.js +34 -2
  36. package/dist/mcp/server.js +14 -7
  37. package/dist/mcp/sse-client.js +163 -20
  38. package/dist/shared/activation-routing.js +187 -20
  39. package/dist/shared/agent-presence.js +6 -0
  40. package/dist/shared/desktop-release-manifest.js +63 -0
  41. package/dist/shared/desktop-release.js +60 -0
  42. package/dist/shared/scoped-ids.js +6 -0
  43. package/package.json +11 -3
  44. package/shared/message-contracts.d.mts +32 -0
  45. package/shared/message-contracts.mjs +109 -0
  46. package/shared/routing-aliases.d.mts +18 -0
  47. package/shared/routing-aliases.mjs +66 -0
  48. package/shared/sqlite-thread-routing.d.mts +72 -0
  49. package/shared/sqlite-thread-routing.mjs +1038 -0
@@ -0,0 +1,26 @@
1
+ const TOOL_SURFACE_BY_PROFILE = {
2
+ supervised_room_turn: {
3
+ agentSessionLifecycle: false,
4
+ deliveryLoop: false,
5
+ onboarding: false,
6
+ rental: false,
7
+ roomResume: false,
8
+ },
9
+ autonomous_mcp_worker: {
10
+ agentSessionLifecycle: true,
11
+ deliveryLoop: true,
12
+ onboarding: true,
13
+ rental: true,
14
+ roomResume: true,
15
+ },
16
+ interactive_desktop: {
17
+ agentSessionLifecycle: true,
18
+ deliveryLoop: true,
19
+ onboarding: true,
20
+ rental: true,
21
+ roomResume: true,
22
+ },
23
+ };
24
+ export function toolSurfaceForExecutionProfile(profile) {
25
+ return TOOL_SURFACE_BY_PROFILE[profile];
26
+ }
@@ -1,4 +1,6 @@
1
+ import { getDaemonToolExecutionContext } from "./daemon-tool-context.js";
1
2
  export const LETAGENTS_AGENT_SESSION_BEARER_ENV = "LETAGENTS_AGENT_SESSION_BEARER";
3
+ export const LETAGENTS_SUPERVISED_BOUNDED_TURNS_ENV = "LETAGENTS_SUPERVISED_BOUNDED_TURNS";
2
4
  export class WorkerBearerRuntimeConfigurationError extends Error {
3
5
  constructor(message) {
4
6
  super(message);
@@ -6,9 +8,24 @@ export class WorkerBearerRuntimeConfigurationError extends Error {
6
8
  }
7
9
  }
8
10
  export function getWorkerBearerRuntime() {
11
+ if (getDaemonToolExecutionContext())
12
+ return { mode: "supervised" };
9
13
  const bearer = process.env.LETAGENTS_AGENT_SESSION_BEARER?.trim();
10
- if (!bearer) {
14
+ const supervised = process.env.LETAGENTS_SUPERVISED_BOUNDED_TURNS?.trim() === "1";
15
+ const profile = process.env.LETAGENTS_EXECUTION_PROFILE?.trim();
16
+ if (supervised !== (profile === "supervised_room_turn")) {
17
+ return {
18
+ mode: "invalid",
19
+ error: "LETAGENTS_EXECUTION_PROFILE=supervised_room_turn and LETAGENTS_SUPERVISED_BOUNDED_TURNS=1 must be configured together.",
20
+ };
21
+ }
22
+ if (!bearer && !supervised)
11
23
  return { mode: "owner" };
24
+ if (bearer && supervised) {
25
+ return {
26
+ mode: "invalid",
27
+ error: "Daemon-supervised bounded turns refuse LETAGENTS_AGENT_SESSION_BEARER; credentials must be borrowed from the exact supervisor generation.",
28
+ };
12
29
  }
13
30
  const apiUrl = process.env.LETAGENTS_API_URL?.trim();
14
31
  if (!apiUrl) {
@@ -36,13 +53,13 @@ export function getWorkerBearerRuntime() {
36
53
  error: "Worker bearer mode requires LETAGENTS_API_URL to be a valid HTTP(S) URL.",
37
54
  };
38
55
  }
39
- if (process.env.LETAGENTS_TOKEN?.trim()) {
56
+ if (bearer && process.env.LETAGENTS_TOKEN?.trim()) {
40
57
  return {
41
58
  mode: "invalid",
42
59
  error: "Worker bearer mode refuses LETAGENTS_TOKEN. Remove the owner token from this process before starting the worker.",
43
60
  };
44
61
  }
45
- return { mode: "worker", bearer };
62
+ return bearer ? { mode: "worker", bearer } : { mode: "supervised" };
46
63
  }
47
64
  export function requireValidWorkerBearerRuntime() {
48
65
  const runtime = getWorkerBearerRuntime();
@@ -51,17 +68,38 @@ export function requireValidWorkerBearerRuntime() {
51
68
  }
52
69
  return runtime;
53
70
  }
71
+ export function isSupervisedBoundedTurn() {
72
+ return requireValidWorkerBearerRuntime().mode === "supervised";
73
+ }
54
74
  export function workerModeDisabledToolResult(toolDescription = "This owner-auth onboarding tool") {
55
75
  const runtime = getWorkerBearerRuntime();
56
76
  if (runtime.mode === "invalid") {
57
77
  return { success: false, error: "worker_bearer_configuration_invalid", message: runtime.error };
58
78
  }
59
- if (runtime.mode === "worker") {
79
+ if (runtime.mode === "worker" || runtime.mode === "supervised") {
60
80
  return {
61
81
  success: false,
62
- error: "worker_bearer_mode",
63
- message: `${toolDescription} is disabled while LETAGENTS_AGENT_SESSION_BEARER is configured.`,
82
+ error: runtime.mode === "worker" ? "worker_bearer_mode" : "supervised_bounded_mode",
83
+ message: runtime.mode === "worker"
84
+ ? `${toolDescription} is disabled while LETAGENTS_AGENT_SESSION_BEARER is configured.`
85
+ : `${toolDescription} is disabled during a daemon-supervised bounded turn.`,
64
86
  };
65
87
  }
66
88
  return null;
67
89
  }
90
+ /**
91
+ * Supervised room delivery belongs to the desktop daemon. A bounded provider
92
+ * turn must never recreate the permanent MCP polling loop, even if its prompt
93
+ * asks it to do so.
94
+ */
95
+ export function supervisedBoundedDeliveryDisabledToolResult(toolName = "wait_for_messages") {
96
+ if (!getDaemonToolExecutionContext()
97
+ && process.env.LETAGENTS_SUPERVISED_BOUNDED_TURNS?.trim() !== "1") {
98
+ return null;
99
+ }
100
+ return {
101
+ success: false,
102
+ error: "supervised_bounded_delivery",
103
+ message: `${toolName} is disabled because supervised room delivery is owned by the desktop daemon.`,
104
+ };
105
+ }
@@ -0,0 +1,27 @@
1
+ import { registerTools } from "./register-tools.js";
2
+ export const LETAGENTS_RUNTIME_CONTRACT_ARG = "--letagents-runtime-contract";
3
+ /**
4
+ * Discover through the production registration path rather than maintaining a
5
+ * second capability list that can drift from the MCP server.
6
+ */
7
+ export function registeredToolNames(profile, supervisedProvider = null) {
8
+ const names = new Set();
9
+ const recorder = {
10
+ tool(name) {
11
+ names.add(name);
12
+ return {};
13
+ },
14
+ };
15
+ registerTools(recorder, profile, supervisedProvider);
16
+ return [...names].sort();
17
+ }
18
+ export function letAgentsRuntimeContract() {
19
+ return {
20
+ format: 1,
21
+ profiles: {
22
+ cursor_supervised_room_turn: {
23
+ tools: registeredToolNames("supervised_room_turn", "cursor"),
24
+ },
25
+ },
26
+ };
27
+ }
@@ -1,14 +1,25 @@
1
1
  // Compatibility facade for MCP server runtime helpers. The implementation lives
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
+ import { isLocalRoomStorageEnabled as isStoredLocalRoomStorageEnabled, touchRoomSession as touchStoredRoomSession, } from "../local-state.js";
5
+ import { isSupervisedBoundedTurn } from "./runtime/worker-bearer.js";
6
+ /** A daemon-supervised turn must always use its exact cloud worker route. */
7
+ export async function isLocalRoomStorageEnabled(roomId) {
8
+ return !isSupervisedBoundedTurn() && isStoredLocalRoomStorageEnabled(roomId);
9
+ }
10
+ export function touchRoomSession(roomId, lastMessageId) {
11
+ if (!isSupervisedBoundedTurn())
12
+ touchStoredRoomSession(roomId, lastMessageId);
13
+ }
4
14
  export { API_URL, ApiError, apiCall, getAuthorizationHeader, getLetagentsToken, isMissingRouteError, parseApiErrorPayload, resolveApiPath, } from "./runtime/api.js";
5
15
  export { clearAuthenticatedAccountCache, getAuthenticatedAccountCache, setAuthenticatedAccountCache, } from "./runtime/auth-cache.js";
6
16
  export { RepoRoomAuthRequiredError, maybeHandleRepoRoomAuthRequired, startPendingDeviceAuth, toRepoRoomAuthRequiredResult, } from "./runtime/device-auth.js";
7
17
  export { AGENT_INSTANCE_UUID, currentAgentIdentity, currentAgentIdentityKey, detectAgentIdeLabel, detectAgentRuntimeLabel, ensureAgentIdentity, getConversationIdentity, getSessionLivenessRegistration, resolveOwnerContext, setConversationIdentity, storeCurrentAgentIdentity, toPublicAgentIdentity, withAgentIdentity, } from "./runtime/identity.js";
8
- export { agentSessionCredentials, buildAgentDeliveryHeaders, ensureLocalWorkerAgentSession, getAgentSessionRepoBranch, identityFromAgentSession, requireWorkerAgentSession, resolveAgentSession, resolveWorkerToolIdentity, toPublicAgentSession, WORKER_BEARER_AGENT_SESSION_ID, } from "./runtime/agent-sessions.js";
9
- export { appendIncludePromptOnly, getLastMessageId, normalizeOptionalToolString, toAgentReadableMessages, withJoinRoomAgentPrompt, } from "./runtime/messages.js";
10
- export { currentRoom, attachMcpServer, getFallbackProjectId, getTargetRoomId, rememberRoom, shutdownRuntime, toPublicRoomResponse, toPublicRoomState, toPublicStoredRoomSession, toRoomState, touchCurrentRoom, withCanonicalRoomLink, } from "./runtime/room-state.js";
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, 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";
11
21
  export { getRememberedRoomPresence, heartbeatRoomPresence, syncRoomPresence, } from "./runtime/presence.js";
12
22
  export { roomScopedApiCall } from "./runtime/room-api.js";
23
+ export { borrowSupervisedWorkerCredential, borrowCurrentSupervisedWorkerCredential, bindSupervisedWorkerSession, checkpointSupervisedWorkerCursor, isRetryableSupervisorBridgeError, scheduleSupervisedWorkerCursorCheckpoint, resolveCurrentSupervisedWorkerSession, } from "./runtime/supervisor-bridge.js";
13
24
  export { autoJoinFromContext, buildJoinResponse, createInviteRoom, getCurrentLiveSessionPayload, joinInviteCode, joinNamedRoom, joinRoomIdentifier, joinRoomIdentifierWithoutImplicitGitRefCreate, normalizeJoinSessionMode, } from "./runtime/rooms.js";
14
- export { clearPendingDeviceAuth, clearStoredAuth, clearStoredAuth as clearStoredAuthorization, endStoredAgentSession, getCurrentAgentSession, getLocalStatePath, getPendingDeviceAuth, getStoredAgentIdentity, getStoredAgentSession, getStoredAuth, getStoredCurrentRoom, getStoredRoomSession, listStoredCodexLiveSessions, saveAgentSession, setPendingDeviceAuth, setStoredAuth, setStoredAgentIdentity, touchRoomSession, addLocalChatMessage, addLocalTask, claimLocalTaskReviewLease, getLatestLocalChatMessages, getLocalChatMessages, getLocalTask, listLocalTasks, isLocalChatStorageEnabled, isLocalRoomStorageEnabled, 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";
@@ -0,0 +1,134 @@
1
+ import { runWithCurrentSupervisedRoom } from "./runtime/room-state.js";
2
+ import { completeCurrentSupervisedEffect, executeCurrentSupervisedTool, prepareCurrentSupervisedEffect, } from "./runtime/supervisor-bridge.js";
3
+ const READ_TOOLS = new Set([
4
+ "get_current_room",
5
+ "check_repo",
6
+ "check_repo_visibility",
7
+ "read_messages",
8
+ "wait_for_messages",
9
+ "get_board",
10
+ "get_board_settings",
11
+ "get_room_artifacts",
12
+ "get_room_events",
13
+ "list_board_intents",
14
+ "get_onboarding_status",
15
+ "status_local_codex_session",
16
+ "rental_list_requests",
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;
25
+ function instruction(text, data = {}) {
26
+ const payload = { ...data, instruction: text };
27
+ return {
28
+ content: [{ type: "text", text: JSON.stringify(payload, null, 2) }],
29
+ structuredContent: payload,
30
+ };
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
+ }
43
+ const productionDependencies = {
44
+ executeTool: executeCurrentSupervisedTool,
45
+ prepareEffect: prepareCurrentSupervisedEffect,
46
+ completeEffect: completeCurrentSupervisedEffect,
47
+ withRoom: runWithCurrentSupervisedRoom,
48
+ };
49
+ export function profileAwareToolServer(server, profile, dependencies = productionDependencies, supervisedProvider = process.env.LETAGENTS_SUPERVISOR_PROVIDER?.trim() || null) {
50
+ if (profile !== "supervised_room_turn")
51
+ return server;
52
+ return new Proxy(server, {
53
+ get(target, property, receiver) {
54
+ if (property !== "tool") {
55
+ const value = Reflect.get(target, property, receiver);
56
+ return typeof value === "function" ? value.bind(target) : value;
57
+ }
58
+ return (name, ...registration) => {
59
+ const mutation = supervisedToolIsMutation(name);
60
+ const callback = registration.at(-1);
61
+ if (typeof callback !== "function")
62
+ throw new Error(`Tool ${name} has no callback.`);
63
+ const wrapped = async (...call) => {
64
+ const extra = (call.at(-1) ?? {});
65
+ const input = call.length > 1 ? call[0] : {};
66
+ if (extra.requestId === undefined || extra.requestId === null || String(extra.requestId).trim() === "") {
67
+ throw new Error(`Supervised tool ${name} is missing its MCP request id; refusing an effect that cannot be deduplicated safely.`);
68
+ }
69
+ const executionRequest = {
70
+ toolName: name,
71
+ input,
72
+ mcpRequestId: String(extra.requestId),
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 });
81
+ return dependencies.withRoom(prepared.roomId, async () => {
82
+ if (prepared.state === "completed")
83
+ return prepared.result;
84
+ if (prepared.state === "uncertain") {
85
+ return instruction("This mutating tool may already have completed, but its result was not durably checkpointed. Verify the external state before issuing a new request; this exact request will not be repeated automatically.", {
86
+ code: "SUPERVISED_EFFECT_OUTCOME_UNCERTAIN",
87
+ effect_id: prepared.effectId,
88
+ detail: prepared.error,
89
+ });
90
+ }
91
+ if (prepared.action === "use_final_answer") {
92
+ return instruction(supervisedProvider === "cursor"
93
+ ? "Do not send the activating room reply with a message tool. Keep working, then record the one public answer with complete_room_turn; Cursor's aggregate final text is live evidence only."
94
+ : "Do not send the activating room reply with a message tool. Return it as your final answer; the daemon will publish it exactly once.", {
95
+ code: "USE_FINAL_ANSWER",
96
+ source_message_id: prepared.sourceMessageId,
97
+ });
98
+ }
99
+ if (prepared.action === "room_move_prepared") {
100
+ return instruction(supervisedProvider === "cursor"
101
+ ? "The room move is prepared. Finish the work, then call complete_room_turn with the public response; the daemon will publish that proposal and then move the agent."
102
+ : "The room move is prepared. Finish this turn normally; the daemon will publish the activating response and then move the agent.", {
103
+ code: "ROOM_MOVE_PREPARED",
104
+ destination_room: prepared.destinationRoom,
105
+ });
106
+ }
107
+ let result;
108
+ try {
109
+ result = await callback(...call);
110
+ }
111
+ catch (error) {
112
+ try {
113
+ await dependencies.completeEffect({ effectId: prepared.effectId, error: error instanceof Error ? error.message : String(error) });
114
+ }
115
+ catch {
116
+ // Preserve the callback error. An unacknowledged journal entry
117
+ // remains executing, which is safer than repeating the effect.
118
+ }
119
+ throw error;
120
+ }
121
+ // Completion transport is deliberately outside the callback catch.
122
+ // A reporting failure must never relabel a successful action failed.
123
+ await dependencies.completeEffect({
124
+ effectId: prepared.effectId,
125
+ result: durableCompletionResult(result, mutation),
126
+ });
127
+ return result;
128
+ });
129
+ };
130
+ return target.tool.call(target, name, ...registration.slice(0, -1), wrapped);
131
+ };
132
+ },
133
+ });
134
+ }
@@ -2,9 +2,9 @@ import { z } from "zod";
2
2
  import { scheduleCodexRuntimeStreamBridgeBind, } from "../../codex-session.js";
3
3
  import { getManagedAgentProvider, toManagedAgentStartResponse, } from "../../managed-agent-providers.js";
4
4
  import { encodeRoomIdPath, looksLikeInviteCode, normalizeInviteCode } from "../../room-id.js";
5
- import { AGENT_INSTANCE_UUID, RepoRoomAuthRequiredError, apiCall, agentSessionCredentials, currentRoom, detectAgentIdeLabel, detectAgentRuntimeLabel, endStoredAgentSession, ensureAgentIdentity, getSessionLivenessRegistration, getAgentSessionRepoBranch, getStoredAgentSession, getTargetRoomId, ensureLocalWorkerAgentSession, isLocalRoomStorageEnabled, joinRoomIdentifier, resolveLocalRoomStorageIdentifiers, saveAgentSession, toPublicAgentSession, toPublicRoomState, toRepoRoomAuthRequiredResult, withAgentIdentity, resolveWorkerToolIdentity, } from "../runtime.js";
5
+ import { AGENT_INSTANCE_UUID, RepoRoomAuthRequiredError, apiCall, agentSessionCredentials, currentRoom, detectAgentIdeLabel, detectAgentRuntimeLabel, endStoredAgentSession, ensureAgentIdentity, getSessionLivenessRegistration, getAgentSessionRepoBranch, getStoredAgentSession, getStoredAgentSessionsForRoomIdentity, getTargetRoomId, ensureLocalWorkerAgentSession, isLocalRoomStorageEnabled, joinRoomIdentifier, resolveLocalRoomStorageIdentifiers, resolveClientRequestedBase, saveAgentSession, toPublicAgentSession, toPublicRoomState, toRepoRoomAuthRequiredResult, withAgentIdentity, resolveWorkerToolIdentity, } from "../runtime.js";
6
6
  import { requireValidWorkerBearerRuntime, workerModeDisabledToolResult, } from "../runtime/worker-bearer.js";
7
- import { bindSupervisedWorkerSession } from "../runtime/supervisor-bridge.js";
7
+ import { bindSupervisedWorkerSessionWithContext } from "../runtime/supervisor-bridge.js";
8
8
  export function registerAgentSessionTools(server) {
9
9
  // -- register_agent_session -------------------------------------------------
10
10
  server.tool("register_agent_session", "Register this MCP client as an explicit room agent session. Unregistered MCP traffic is treated as controller traffic and stays out of the connected-agent roster.", {
@@ -27,8 +27,29 @@ export function registerAgentSessionTools(server) {
27
27
  cwd: z
28
28
  .string()
29
29
  .optional()
30
- .describe("Working directory used to detect the worker's active git branch. Defaults to the MCP server's working directory."),
30
+ .describe("Worker working directory used for branch detection and exact supervised Codex binding. Defaults to the MCP server's working directory."),
31
31
  }, async ({ room_id, session_kind, runtime, display_name, cwd }) => {
32
+ const workerRuntime = requireValidWorkerBearerRuntime();
33
+ if (workerRuntime.mode === "supervised") {
34
+ // Resolve before currentRoom, config, branch, or local storage. The
35
+ // daemon context is the only authority for a bounded worker's room.
36
+ const { agentSession } = await resolveWorkerToolIdentity({ roomId: room_id?.trim() || null });
37
+ return {
38
+ content: [
39
+ {
40
+ type: "text",
41
+ text: JSON.stringify({
42
+ success: true,
43
+ worker_bearer_mode: false,
44
+ supervised_bounded_mode: true,
45
+ agent_session: toPublicAgentSession(agentSession),
46
+ agent_session_id: agentSession.session_id,
47
+ use_agent_session_id: "This exact daemon-supervised agent_session_id may be passed to room tools. The credential remains daemon-private.",
48
+ }, null, 2),
49
+ },
50
+ ],
51
+ };
52
+ }
32
53
  const targetRoomId = getTargetRoomId(room_id);
33
54
  if (!targetRoomId) {
34
55
  return {
@@ -70,7 +91,7 @@ export function registerAgentSessionTools(server) {
70
91
  }
71
92
  const { cloudRoomId } = await resolveLocalRoomStorageIdentifiers(targetRoomId);
72
93
  const apiRoomId = cloudRoomId || targetRoomId;
73
- if (requireValidWorkerBearerRuntime().mode === "worker") {
94
+ if (workerRuntime.mode === "worker") {
74
95
  // The supplied bearer is issued for an existing server-side agent
75
96
  // session. Do not call the owner-only registration endpoint or write
76
97
  // any session credential to local storage.
@@ -81,7 +102,8 @@ export function registerAgentSessionTools(server) {
81
102
  type: "text",
82
103
  text: JSON.stringify({
83
104
  success: true,
84
- worker_bearer_mode: true,
105
+ worker_bearer_mode: workerRuntime.mode === "worker",
106
+ supervised_bounded_mode: false,
85
107
  agent_session: toPublicAgentSession(agentSession),
86
108
  agent_session_id: agentSession.session_id,
87
109
  use_agent_session_id: "This local worker-bearer session marker may be passed to room tools. The supplied bearer remains the only server credential.",
@@ -104,6 +126,22 @@ export function registerAgentSessionTools(server) {
104
126
  ],
105
127
  };
106
128
  }
129
+ // Stable-base signal (task_66): declare the intent behind display_name
130
+ // so the server can converge a replayed decorated label without ever
131
+ // guessing from numeric shape.
132
+ const priorRoomSessions = getStoredAgentSessionsForRoomIdentity(apiRoomId, identity.canonical_key);
133
+ const requestedSessionKind = session_kind ?? "worker";
134
+ const requestedBaseDisplayName = resolveClientRequestedBase({
135
+ explicitDisplayName: display_name,
136
+ identityDisplayName: identity.display_name,
137
+ priorSessions: priorRoomSessions,
138
+ });
139
+ const replacementSession = requestedSessionKind === "worker"
140
+ ? priorRoomSessions.find((session) => !session.ended_at
141
+ && session.session_kind === "worker"
142
+ && session.agent_instance_id === AGENT_INSTANCE_UUID
143
+ && Boolean(session.session_token)) ?? null
144
+ : null;
107
145
  const created = await apiCall(`/rooms/${encodeRoomIdPath(apiRoomId)}/agent-sessions`, {
108
146
  method: "POST",
109
147
  body: JSON.stringify({
@@ -112,10 +150,13 @@ export function registerAgentSessionTools(server) {
112
150
  ide_label: identity.ide_label ?? detectAgentIdeLabel(),
113
151
  agent_instance_id: AGENT_INSTANCE_UUID,
114
152
  display_name: display_name?.trim() || identity.display_name,
153
+ requested_base_display_name: requestedBaseDisplayName,
115
154
  session_kind: session_kind ?? "worker",
116
155
  runtime: requestedRuntime,
117
156
  repo_branch: repoBranch,
118
157
  registration_liveness: getSessionLivenessRegistration(requestedRuntime),
158
+ replace_agent_session_id: replacementSession?.session_id ?? null,
159
+ replace_agent_session_token: replacementSession?.session_token ?? null,
119
160
  }),
120
161
  });
121
162
  const sessionId = typeof created.session_id === "string" ? created.session_id : "";
@@ -123,7 +164,10 @@ export function registerAgentSessionTools(server) {
123
164
  if (!sessionId || !sessionToken) {
124
165
  throw new Error("Agent session registration response was missing session credentials.");
125
166
  }
126
- const session = saveAgentSession({
167
+ if (replacementSession) {
168
+ endStoredAgentSession(replacementSession.session_id, typeof created.created_at === "string" ? created.created_at : new Date().toISOString());
169
+ }
170
+ let session = saveAgentSession({
127
171
  session_id: sessionId,
128
172
  session_token: sessionToken,
129
173
  room_id: typeof created.room_id === "string" ? created.room_id : apiRoomId,
@@ -138,6 +182,11 @@ export function registerAgentSessionTools(server) {
138
182
  agent_key: typeof created.agent_key === "string" ? created.agent_key : identity.canonical_key,
139
183
  agent_instance_id: typeof created.agent_instance_id === "string" ? created.agent_instance_id : AGENT_INSTANCE_UUID,
140
184
  display_name: typeof created.display_name === "string" ? created.display_name : identity.display_name,
185
+ // Prefer the server-recorded allocation base; fall back to the base we
186
+ // declared so a later resume still replays a stable signal.
187
+ requested_base_display_name: (typeof created.assigned_base_display_name === "string" && created.assigned_base_display_name.trim())
188
+ || requestedBaseDisplayName
189
+ || null,
141
190
  owner_label: typeof created.owner_label === "string" ? created.owner_label : identity.owner_label,
142
191
  ide_label: typeof created.ide_label === "string" ? created.ide_label : identity.ide_label ?? detectAgentIdeLabel(),
143
192
  repo_branch: typeof created.repo_branch === "string" ? created.repo_branch : repoBranch,
@@ -146,7 +195,13 @@ export function registerAgentSessionTools(server) {
146
195
  last_seen_at: typeof created.last_seen_at === "string" ? created.last_seen_at : new Date().toISOString(),
147
196
  ended_at: typeof created.ended_at === "string" ? created.ended_at : null,
148
197
  });
149
- await bindSupervisedWorkerSession(session);
198
+ const supervisorBinding = await bindSupervisedWorkerSessionWithContext(session, process.env, { cwd: cwd?.trim() || process.cwd() });
199
+ if (supervisorBinding.supervisorContextCwd) {
200
+ session = saveAgentSession({
201
+ ...session,
202
+ supervisor_context_cwd: supervisorBinding.supervisorContextCwd,
203
+ });
204
+ }
150
205
  scheduleCodexRuntimeStreamBridgeBind(session);
151
206
  return {
152
207
  content: [
@@ -167,6 +222,18 @@ export function registerAgentSessionTools(server) {
167
222
  room_id: z.string().optional().describe("Canonical room ID. Defaults to the current room or the stored session room."),
168
223
  agent_session_id: z.string().optional().describe("Registered agent session to disconnect."),
169
224
  }, async ({ room_id, agent_session_id }) => {
225
+ if (requireValidWorkerBearerRuntime().mode === "supervised") {
226
+ return {
227
+ content: [{
228
+ type: "text",
229
+ text: JSON.stringify({
230
+ success: false,
231
+ error: "supervised_bounded_mode",
232
+ message: "Agent-session disconnection is owned by the desktop supervisor during a bounded turn.",
233
+ }, null, 2),
234
+ }],
235
+ };
236
+ }
170
237
  let targetRoomId = getTargetRoomId(room_id);
171
238
  const localSession = agent_session_id
172
239
  ? getStoredAgentSession(agent_session_id)
@@ -7,8 +7,9 @@ export function registerStatusTools(server) {
7
7
  registerPostStatusTool(server);
8
8
  registerPostReasoningTool(server);
9
9
  }
10
- export function registerMessageTools(server) {
10
+ export function registerMessageTools(server, options = {}) {
11
11
  registerSendMessageTool(server);
12
12
  registerReadMessagesTool(server);
13
- registerWaitForMessagesTool(server);
13
+ if (options.includeDeliveryLoop !== false)
14
+ registerWaitForMessagesTool(server);
14
15
  }