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.
@@ -26,6 +26,21 @@ export function getCurrentAgentSession(roomId) {
26
26
  }
27
27
  return best;
28
28
  }
29
+ /**
30
+ * Every stored session (active or ended) this identity had in the room,
31
+ * most recently updated first. Re-registration consults the FULL lineage so a
32
+ * replayed prior label reuses the base recorded when that exact label was
33
+ * allocated — a latest-only lookup would lose an older concurrent sibling's
34
+ * base and misread its restart as a deliberate rename.
35
+ */
36
+ export function getStoredAgentSessionsForRoomIdentity(roomId, agentKey) {
37
+ if (!roomId || !agentKey)
38
+ return [];
39
+ const state = readLocalState();
40
+ return Object.values(state.agent_sessions ?? {})
41
+ .filter((session) => session.room_id === roomId && session.agent_key === agentKey)
42
+ .sort((left, right) => right.updated_at.localeCompare(left.updated_at));
43
+ }
29
44
  export function saveAgentSession(session, makeCurrent = true) {
30
45
  updateLocalState((state) => {
31
46
  state.agent_sessions = state.agent_sessions ?? {};
@@ -4,16 +4,27 @@ import { registerOnboardingTools } from "./tools/onboarding.js";
4
4
  import { registerRentalTools } from "./tools/rental.js";
5
5
  import { registerRepoInitializationTool, registerRepoVisibilityTool, registerRoomInspectionTools, registerRoomJoinTools, registerRoomResumeTool, } from "./tools/rooms.js";
6
6
  import { registerTaskTools } from "./tools/tasks.js";
7
- export function registerTools(server) {
8
- registerRoomJoinTools(server);
9
- registerAgentSessionTools(server);
10
- registerRoomInspectionTools(server);
11
- registerStatusTools(server);
12
- registerTaskTools(server);
13
- registerRepoInitializationTool(server);
14
- registerMessageTools(server);
15
- registerOnboardingTools(server);
16
- registerRoomResumeTool(server);
17
- registerRentalTools(server);
18
- registerRepoVisibilityTool(server);
7
+ import { registerSupervisedRoomTurnTools } from "./tools/supervised-room-turn.js";
8
+ import { toolSurfaceForExecutionProfile } from "./runtime/tool-surface-policy.js";
9
+ import { profileAwareToolServer } from "./supervised-tool-facade.js";
10
+ export function registerTools(server, profile = "autonomous_mcp_worker", supervisedProvider = process.env.LETAGENTS_SUPERVISOR_PROVIDER?.trim() || null) {
11
+ const tools = profileAwareToolServer(server, profile, undefined, supervisedProvider);
12
+ const surface = toolSurfaceForExecutionProfile(profile);
13
+ registerRoomJoinTools(tools);
14
+ if (surface.agentSessionLifecycle)
15
+ registerAgentSessionTools(tools);
16
+ registerRoomInspectionTools(tools);
17
+ registerStatusTools(tools);
18
+ registerTaskTools(tools);
19
+ registerRepoInitializationTool(tools);
20
+ registerMessageTools(tools, { includeDeliveryLoop: surface.deliveryLoop });
21
+ if (profile === "supervised_room_turn" && supervisedProvider === "cursor")
22
+ registerSupervisedRoomTurnTools(tools);
23
+ if (surface.onboarding)
24
+ registerOnboardingTools(tools);
25
+ if (surface.roomResume)
26
+ registerRoomResumeTool(tools);
27
+ if (surface.rental)
28
+ registerRentalTools(tools);
29
+ registerRepoVisibilityTool(tools);
19
30
  }
@@ -5,13 +5,15 @@ import { normalizeAgentBaseName } from "../../../shared/codenames.js";
5
5
  import { formatOwnerAttribution } from "../../../shared/agent-identity.js";
6
6
  import { LETAGENTS_AGENT_SESSION_ID_HEADER, LETAGENTS_AGENT_SESSION_TOKEN_HEADER, } from "../../../shared/request-headers.js";
7
7
  import { AGENT_INSTANCE_UUID, detectAgentIdeLabel, detectAgentRuntimeLabel, ensureAgentIdentity, } from "./identity.js";
8
- import { requireValidWorkerBearerRuntime } from "./worker-bearer.js";
8
+ import { isSupervisedBoundedTurn, requireValidWorkerBearerRuntime } from "./worker-bearer.js";
9
+ import { resolveCurrentSupervisedWorkerSession } from "./supervisor-bridge.js";
9
10
  // A worker bearer already represents a server-side worker session. This local
10
11
  // marker lets the MCP tool contract stay session-shaped without persisting or
11
12
  // transmitting a second set of credentials.
12
13
  export const WORKER_BEARER_AGENT_SESSION_ID = "worker_bearer";
13
14
  export function buildAgentDeliveryHeaders(agentSession) {
14
- if (!agentSession || requireValidWorkerBearerRuntime().mode === "worker") {
15
+ const runtime = requireValidWorkerBearerRuntime();
16
+ if (!agentSession || runtime.mode === "worker" || runtime.mode === "supervised") {
15
17
  return {};
16
18
  }
17
19
  return {
@@ -62,6 +64,33 @@ export function resolveAgentSession(roomId, sessionId) {
62
64
  }
63
65
  return session;
64
66
  }
67
+ /**
68
+ * The stable base this client declares as `requested_base_display_name` when
69
+ * registering. Rules (task_66):
70
+ * - An explicit display_name that replays the EXACT label of any prior stored
71
+ * session for this room+identity is a resume, not a rename: reuse the base
72
+ * recorded when THAT label was allocated, so a server-decorated label
73
+ * converges. The whole lineage is consulted (most recent first) because a
74
+ * latest-only lookup would lose an older concurrent sibling's base and
75
+ * misread its restart as a deliberate rename.
76
+ * - Any other explicit display_name is deliberate intent and IS the base —
77
+ * a numeric-ending custom name ("Agent 47") is therefore never demoted.
78
+ * - With no explicit name, fall back to the most recent recorded base in the
79
+ * lineage, then to the durable identity's display name.
80
+ */
81
+ export function resolveClientRequestedBase(input) {
82
+ const explicit = input.explicitDisplayName?.trim() || "";
83
+ const lineage = input.priorSessions ?? [];
84
+ if (explicit) {
85
+ const replayed = lineage.find((session) => session.display_name?.trim() === explicit);
86
+ const replayedBase = replayed?.requested_base_display_name?.trim() || "";
87
+ return replayedBase || explicit;
88
+ }
89
+ const latestBase = lineage
90
+ .map((session) => session.requested_base_display_name?.trim() || "")
91
+ .find((base) => base.length > 0);
92
+ return latestBase || input.identityDisplayName.trim();
93
+ }
65
94
  export function identityFromAgentSession(session) {
66
95
  return {
67
96
  name: normalizeAgentBaseName(session.display_name),
@@ -88,7 +117,17 @@ export function requireWorkerAgentSession(roomId, sessionId) {
88
117
  return session;
89
118
  }
90
119
  export async function resolveWorkerToolIdentity(input) {
91
- if (requireValidWorkerBearerRuntime().mode === "worker" &&
120
+ const runtimeMode = requireValidWorkerBearerRuntime().mode;
121
+ if (runtimeMode === "supervised") {
122
+ const agentSession = await resolveCurrentSupervisedWorkerSession(input.roomId);
123
+ if (input.agentSessionId
124
+ && input.agentSessionId !== WORKER_BEARER_AGENT_SESSION_ID
125
+ && input.agentSessionId !== agentSession.session_id) {
126
+ throw new Error(`Daemon-supervised worker session is ${agentSession.session_id}, not ${input.agentSessionId}.`);
127
+ }
128
+ return { identity: identityFromAgentSession(agentSession), agentSession };
129
+ }
130
+ if (runtimeMode === "worker" &&
92
131
  (!input.agentSessionId || input.agentSessionId === WORKER_BEARER_AGENT_SESSION_ID)) {
93
132
  const identity = await ensureAgentIdentity();
94
133
  const now = new Date().toISOString();
@@ -121,7 +160,7 @@ export async function resolveWorkerToolIdentity(input) {
121
160
  }
122
161
  const agentSession = input.agentSessionId
123
162
  ? requireWorkerAgentSession(input.roomId, input.agentSessionId)
124
- : input.roomId && await isLocalRoomStorageEnabled(input.roomId)
163
+ : input.roomId && !isSupervisedBoundedTurn() && await isLocalRoomStorageEnabled(input.roomId)
125
164
  ? await ensureLocalWorkerAgentSession(input.roomId)
126
165
  : requireWorkerAgentSession(input.roomId, input.agentSessionId);
127
166
  return {
@@ -163,7 +202,8 @@ export function getAgentSessionRepoBranch(cwd) {
163
202
  return getGitCurrentBranch(workingDir);
164
203
  }
165
204
  export function agentSessionCredentials(agentSession) {
166
- if (requireValidWorkerBearerRuntime().mode === "worker") {
205
+ const runtime = requireValidWorkerBearerRuntime();
206
+ if (runtime.mode === "worker" || runtime.mode === "supervised") {
167
207
  return {};
168
208
  }
169
209
  return {
@@ -1,9 +1,30 @@
1
1
  import { clearAuthenticatedAccountCache } from "./auth-cache.js";
2
2
  import { requireValidWorkerBearerRuntime } from "./worker-bearer.js";
3
+ import { borrowCurrentSupervisedWorkerCredential, } from "./supervisor-bridge.js";
3
4
  let ownerAuthStoreLoader = () => import("../../local-state.js");
5
+ let supervisedCredentialBorrower = () => borrowCurrentSupervisedWorkerCredential();
4
6
  export function setOwnerAuthStoreLoaderForTest(loader) {
5
7
  ownerAuthStoreLoader = loader ?? (() => import("../../local-state.js"));
6
8
  }
9
+ export function setSupervisedCredentialBorrowerForTest(borrower) {
10
+ supervisedCredentialBorrower = borrower ?? (() => borrowCurrentSupervisedWorkerCredential());
11
+ }
12
+ export class SupervisedWorkerCredentialError extends Error {
13
+ code;
14
+ constructor(code) {
15
+ super(code === "SUPERVISED_CREDENTIAL_UNAVAILABLE"
16
+ ? "The daemon-supervised worker credential is not available yet."
17
+ : "The daemon-supervised worker credential is stale or missing its exact context.");
18
+ this.code = code;
19
+ this.name = "SupervisedWorkerCredentialError";
20
+ }
21
+ }
22
+ async function getSupervisedCredential() {
23
+ const result = await supervisedCredentialBorrower();
24
+ if (result.state === "available")
25
+ return result.credential;
26
+ throw new SupervisedWorkerCredentialError(result.state === "deferred" ? "SUPERVISED_CREDENTIAL_UNAVAILABLE" : "SUPERVISED_CREDENTIAL_STALE");
27
+ }
7
28
  export const API_URL = (process.env.LETAGENTS_API_URL || "http://localhost:3001").replace(/\/+$/, "");
8
29
  export class ApiError extends Error {
9
30
  status;
@@ -20,6 +41,8 @@ export async function getLetagentsToken() {
20
41
  if (runtime.mode === "worker") {
21
42
  return runtime.bearer;
22
43
  }
44
+ if (runtime.mode === "supervised")
45
+ return getSupervisedCredential();
23
46
  const envToken = process.env.LETAGENTS_TOKEN?.trim();
24
47
  if (envToken) {
25
48
  return envToken;
@@ -75,6 +98,11 @@ export async function apiCall(path, options) {
75
98
  // every caller spelling of Authorization is overwritten.
76
99
  headers.set("Authorization", `Bearer ${runtime.bearer}`);
77
100
  }
101
+ else if (runtime.mode === "supervised") {
102
+ // Resolve on every API request: the daemon may rotate the in-memory
103
+ // credential while Codex remains running.
104
+ headers.set("Authorization", `Bearer ${await getSupervisedCredential()}`);
105
+ }
78
106
  else {
79
107
  const authorizationHeader = await getAuthorizationHeader();
80
108
  if (authorizationHeader && !headers.has("Authorization")) {
@@ -87,7 +115,7 @@ export async function apiCall(path, options) {
87
115
  });
88
116
  if (!res.ok) {
89
117
  const body = await res.text();
90
- if (res.status === 401 && requireValidWorkerBearerRuntime().mode !== "worker") {
118
+ if (res.status === 401 && requireValidWorkerBearerRuntime().mode === "owner") {
91
119
  // Only clear on 401 (invalid/expired credential), NOT on 403
92
120
  // (valid credential but insufficient permissions, e.g., private repo access)
93
121
  const { clearStoredAuth } = await ownerAuthStoreLoader();
@@ -0,0 +1,19 @@
1
+ export const EXECUTION_PROFILES = [
2
+ "supervised_room_turn",
3
+ "autonomous_mcp_worker",
4
+ "interactive_desktop",
5
+ ];
6
+ export const LETAGENTS_EXECUTION_PROFILE_ENV = "LETAGENTS_EXECUTION_PROFILE";
7
+ export const LETAGENTS_SUPERVISED_BOUNDED_TURNS_ENV = "LETAGENTS_SUPERVISED_BOUNDED_TURNS";
8
+ export function executionProfile(env = process.env) {
9
+ const configured = env[LETAGENTS_EXECUTION_PROFILE_ENV]?.trim();
10
+ const bounded = env[LETAGENTS_SUPERVISED_BOUNDED_TURNS_ENV]?.trim() === "1";
11
+ if (configured && !EXECUTION_PROFILES.includes(configured)) {
12
+ throw new Error(`Invalid ${LETAGENTS_EXECUTION_PROFILE_ENV}: ${configured}`);
13
+ }
14
+ const profile = (configured || "autonomous_mcp_worker");
15
+ if (bounded !== (profile === "supervised_room_turn")) {
16
+ throw new Error(`${LETAGENTS_EXECUTION_PROFILE_ENV}=supervised_room_turn and ${LETAGENTS_SUPERVISED_BOUNDED_TURNS_ENV}=1 must be configured together.`);
17
+ }
18
+ return profile;
19
+ }
@@ -5,7 +5,7 @@ import { requireValidWorkerBearerRuntime } from "../worker-bearer.js";
5
5
  import { getAuthenticatedAccountCache, setAuthenticatedAccountCache, } from "../auth-cache.js";
6
6
  import { AGENT_OWNER_LABEL, readCommandOutput, } from "./config.js";
7
7
  export async function getAuthenticatedAgentDirectory() {
8
- if (requireValidWorkerBearerRuntime().mode === "worker") {
8
+ if (requireValidWorkerBearerRuntime().mode !== "owner") {
9
9
  return null;
10
10
  }
11
11
  try {
@@ -25,7 +25,7 @@ export async function getAuthenticatedAgentDirectory() {
25
25
  }
26
26
  }
27
27
  async function getAuthenticatedAccountProfile() {
28
- if (requireValidWorkerBearerRuntime().mode === "worker") {
28
+ if (requireValidWorkerBearerRuntime().mode !== "owner") {
29
29
  return null;
30
30
  }
31
31
  const envToken = (process.env.LETAGENTS_TOKEN || "").trim();
@@ -4,6 +4,7 @@ import { isLocalRoomStorageEnabled, resolveLocalRoomStorageIdentifiers, touchRoo
4
4
  import { apiCall, isMissingRouteError } from "./api.js";
5
5
  import { agentSessionCredentials, identityFromAgentSession } from "./agent-sessions.js";
6
6
  import { getSessionLivenessRegistration } from "./identity.js";
7
+ import { isSupervisedBoundedTurn } from "./worker-bearer.js";
7
8
  const roomPresenceByIdentity = new Map();
8
9
  export function getRememberedRoomPresence(roomId, identity) {
9
10
  if (!roomId || !identity) {
@@ -18,7 +19,7 @@ export async function syncRoomPresence(roomId, identity, presence, agentSession)
18
19
  }
19
20
  roomPresenceByIdentity.set(getRoomIdentityPresenceCacheKey(roomId, resolvedIdentity.actor_label), presence);
20
21
  const { localRoomId, cloudRoomId } = await resolveLocalRoomStorageIdentifiers(roomId);
21
- if (await isLocalRoomStorageEnabled(roomId)) {
22
+ if (!isSupervisedBoundedTurn() && await isLocalRoomStorageEnabled(roomId)) {
22
23
  touchRoomSession(localRoomId || roomId);
23
24
  return;
24
25
  }
@@ -38,7 +39,8 @@ export async function syncRoomPresence(roomId, identity, presence, agentSession)
38
39
  ...agentSessionCredentials(agentSession),
39
40
  }),
40
41
  });
41
- touchRoomSession(apiRoomId);
42
+ if (!isSupervisedBoundedTurn())
43
+ touchRoomSession(apiRoomId);
42
44
  }
43
45
  catch (error) {
44
46
  if (isMissingRouteError(error)) {
@@ -3,13 +3,24 @@ import { LETAGENTS_ORIGIN_ROOM_ID_HEADER } from "../../../shared/request-headers
3
3
  import { apiCall, isMissingRouteError, } from "./api.js";
4
4
  import { maybeHandleRepoRoomAuthRequired } from "./device-auth.js";
5
5
  import { getLastMessageId } from "./messages.js";
6
- import { currentRoom } from "./room-state.js";
6
+ import { currentRoom, getCurrentSupervisedRoomAuthority } from "./room-state.js";
7
+ import { isSupervisedBoundedTurn } from "./worker-bearer.js";
7
8
  export async function roomScopedApiCall(input) {
9
+ const supervised = isSupervisedBoundedTurn();
10
+ const exactRoomAuthority = supervised ? getCurrentSupervisedRoomAuthority() : null;
11
+ if (supervised && (!exactRoomAuthority || input.room_id !== exactRoomAuthority)) {
12
+ throw new Error("The daemon-supervised API request is missing its exact per-call room authority.");
13
+ }
8
14
  const headers = {
9
15
  ...input.options?.headers,
10
16
  };
11
- if (currentRoom?.room_id &&
12
- !Object.keys(headers).some((key) => key.toLowerCase() === LETAGENTS_ORIGIN_ROOM_ID_HEADER.toLowerCase())) {
17
+ const originHeaderKey = Object.keys(headers).find((key) => key.toLowerCase() === LETAGENTS_ORIGIN_ROOM_ID_HEADER.toLowerCase());
18
+ if (exactRoomAuthority) {
19
+ if (originHeaderKey)
20
+ delete headers[originHeaderKey];
21
+ headers[LETAGENTS_ORIGIN_ROOM_ID_HEADER] = exactRoomAuthority;
22
+ }
23
+ else if (currentRoom?.room_id && !originHeaderKey) {
13
24
  headers[LETAGENTS_ORIGIN_ROOM_ID_HEADER] = currentRoom.room_id;
14
25
  }
15
26
  const options = {
@@ -17,16 +28,20 @@ export async function roomScopedApiCall(input) {
17
28
  headers,
18
29
  };
19
30
  if (input.room_id) {
20
- const { cloudRoomId } = await resolveLocalRoomStorageIdentifiers(input.room_id);
31
+ const cloudRoomId = supervised
32
+ ? null
33
+ : (await resolveLocalRoomStorageIdentifiers(input.room_id)).cloudRoomId;
21
34
  const apiRoomId = cloudRoomId || input.room_id;
22
35
  try {
23
36
  const result = await apiCall(input.room_path(apiRoomId), options);
24
- touchRoomSession(input.room_id, input.preserve_session_cursor ? undefined : getLastMessageId(result));
37
+ if (!supervised) {
38
+ touchRoomSession(input.room_id, input.preserve_session_cursor ? undefined : getLastMessageId(result));
39
+ }
25
40
  return result;
26
41
  }
27
42
  catch (error) {
28
43
  await maybeHandleRepoRoomAuthRequired(error, apiRoomId);
29
- if (!input.project_id || !isMissingRouteError(error)) {
44
+ if (supervised || !input.project_id || !isMissingRouteError(error)) {
30
45
  throw error;
31
46
  }
32
47
  }
@@ -36,7 +51,9 @@ export async function roomScopedApiCall(input) {
36
51
  }
37
52
  const result = await apiCall(input.project_path(input.project_id), options);
38
53
  if (input.room_id) {
39
- touchRoomSession(input.room_id, input.preserve_session_cursor ? undefined : getLastMessageId(result));
54
+ if (!supervised) {
55
+ touchRoomSession(input.room_id, input.preserve_session_cursor ? undefined : getLastMessageId(result));
56
+ }
40
57
  }
41
58
  return result;
42
59
  }
@@ -3,6 +3,8 @@ import { getStoredAgentIdentity, saveRoomSession, touchRoomSession, } from "../.
3
3
  import { getCanonicalRoomWebPath, } from "../../room-id.js";
4
4
  import { API_URL, getLetagentsToken } from "./api.js";
5
5
  import { AGENT_INSTANCE_UUID, currentAgentIdentity, currentAgentIdentityKey, } from "./identity.js";
6
+ import { isSupervisedBoundedTurn } from "./worker-bearer.js";
7
+ import { getCurrentSupervisedRoomAuthority, runWithSupervisedRoomAuthority, } from "./supervised-room-authority.js";
6
8
  let mcpServer = null;
7
9
  let sseClient = null;
8
10
  export let currentRoom = null;
@@ -85,6 +87,8 @@ export function toPublicRoomResponse(response, fallbackRoomId) {
85
87
  }
86
88
  export function rememberRoom(state, lastMessageId) {
87
89
  currentRoom = state;
90
+ if (isSupervisedBoundedTurn())
91
+ return state;
88
92
  saveRoomSession({
89
93
  room_id: state.room_id,
90
94
  project_id: state.project_id ?? null,
@@ -109,14 +113,56 @@ export function rememberRoom(state, lastMessageId) {
109
113
  return state;
110
114
  }
111
115
  export function touchCurrentRoom(lastMessageId) {
116
+ if (isSupervisedBoundedTurn())
117
+ return;
112
118
  if (!currentRoom) {
113
119
  return;
114
120
  }
115
121
  touchRoomSession(currentRoom.room_id, lastMessageId);
116
122
  }
117
123
  export function getTargetRoomId(roomId) {
124
+ if (isSupervisedBoundedTurn()) {
125
+ const exactRoomAuthority = getCurrentSupervisedRoomAuthority();
126
+ if (!exactRoomAuthority) {
127
+ throw new Error("The daemon-supervised tool has not received its exact room authority.");
128
+ }
129
+ if (roomId && roomId !== exactRoomAuthority) {
130
+ throw new Error(`The daemon-supervised tool is authorized for ${exactRoomAuthority}, not ${roomId}.`);
131
+ }
132
+ return exactRoomAuthority;
133
+ }
118
134
  return roomId || currentRoom?.room_id || null;
119
135
  }
136
+ /** The last room authority returned by this process's exact daemon effect. */
137
+ export { getCurrentSupervisedRoomAuthority } from "./supervised-room-authority.js";
138
+ /** Public room metadata without inventing join provenance or locality. */
139
+ export function toPublicCurrentRoomState() {
140
+ const exactRoomAuthority = getCurrentSupervisedRoomAuthority();
141
+ if (!exactRoomAuthority)
142
+ return toPublicRoomState(currentRoom);
143
+ return {
144
+ ...toPublicRoomState(toRoomState({ room_id: exactRoomAuthority, joined_via: "join_room" })),
145
+ joined_via: null,
146
+ is_local: null,
147
+ };
148
+ }
149
+ /**
150
+ * Bind only the in-memory default used by one supervised MCP process. The
151
+ * daemon response is the authority; this performs no join, storage, SSE, or
152
+ * repository inspection and can safely rebind after a durable room move.
153
+ */
154
+ export function runWithCurrentSupervisedRoom(roomId, callback) {
155
+ if (!isSupervisedBoundedTurn()) {
156
+ throw new Error("Only a daemon-supervised bounded turn can bind supervisor room authority.");
157
+ }
158
+ const normalized = roomId.trim();
159
+ if (!normalized || normalized.length > 1_024 || /[\u0000-\u001f\u007f]/.test(normalized)) {
160
+ throw new Error("The daemon-supervised room authority is malformed.");
161
+ }
162
+ return runWithSupervisedRoomAuthority(normalized, callback);
163
+ }
120
164
  export function getFallbackProjectId() {
165
+ if (isSupervisedBoundedTurn())
166
+ return null;
121
167
  return currentRoom?.project_id ?? null;
122
168
  }
@@ -9,7 +9,7 @@ import { ensureAgentIdentity, toPublicAgentIdentity, withAgentIdentity, } from "
9
9
  import { withJoinRoomAgentPrompt } from "./messages.js";
10
10
  import { syncRoomPresence } from "./presence.js";
11
11
  import { rememberRoom, toPublicRoomResponse, toRoomState, } from "./room-state.js";
12
- import { requireValidWorkerBearerRuntime } from "./worker-bearer.js";
12
+ import { isSupervisedBoundedTurn, requireValidWorkerBearerRuntime } from "./worker-bearer.js";
13
13
  export function normalizeJoinSessionMode(value) {
14
14
  return String(value || "").trim().toLowerCase() === "live" ? "live" : "current";
15
15
  }
@@ -30,6 +30,9 @@ function parseGeneratedGitRefRoomIdentifier(identifier) {
30
30
  };
31
31
  }
32
32
  export async function joinRoomIdentifier(identifier, joinedVia, options = {}) {
33
+ if (isSupervisedBoundedTurn()) {
34
+ throw new Error("Room joins and creation are disabled during a daemon-supervised bounded turn.");
35
+ }
33
36
  const roomId = joinedVia === "join_code" ? normalizeInviteCode(identifier) : identifier.trim();
34
37
  if (joinedVia !== "join_code" && await isLocalRoomStorageEnabled(roomId)) {
35
38
  if (options.allowCreate === false && !getStoredRoomSession(roomId)) {
@@ -187,6 +190,9 @@ export async function joinRoomIdentifierWithoutImplicitGitRefCreate(identifier,
187
190
  }));
188
191
  }
189
192
  export async function createInviteRoom() {
193
+ if (isSupervisedBoundedTurn()) {
194
+ throw new Error("Room joins and creation are disabled during a daemon-supervised bounded turn.");
195
+ }
190
196
  const project = await apiCall("/projects", { method: "POST" });
191
197
  const roomId = typeof project.code === "string"
192
198
  ? project.code
@@ -301,7 +307,15 @@ function bindWorkerRoomFromContext() {
301
307
  }
302
308
  export async function autoJoinFromContext() {
303
309
  try {
304
- if (requireValidWorkerBearerRuntime().mode === "worker") {
310
+ const workerRuntime = requireValidWorkerBearerRuntime();
311
+ if (workerRuntime.mode === "supervised") {
312
+ // The exact room may change after a durable daemon-owned room move.
313
+ // Bind it per tool effect from the supervisor response, never from
314
+ // ambient repository, persisted state, or launch-time environment.
315
+ console.error("ℹ️ Daemon-supervised bounded turn leaves room selection to its exact supervisor context.");
316
+ return;
317
+ }
318
+ if (workerRuntime.mode === "worker") {
305
319
  const bound = bindWorkerRoomFromContext();
306
320
  if (bound) {
307
321
  console.error(`🏠 Bound worker bearer to room '${bound.room.room_id}' (from ${bound.source}; no join/create request).`);
@@ -0,0 +1,8 @@
1
+ import { AsyncLocalStorage } from "node:async_hooks";
2
+ const exactRoomAuthority = new AsyncLocalStorage();
3
+ export function getCurrentSupervisedRoomAuthority() {
4
+ return exactRoomAuthority.getStore() ?? null;
5
+ }
6
+ export function runWithSupervisedRoomAuthority(roomId, callback) {
7
+ return exactRoomAuthority.run(roomId, callback);
8
+ }