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.
- package/dist/mcp/local-state/agent-sessions.js +15 -0
- package/dist/mcp/server/register-tools.js +23 -12
- package/dist/mcp/server/runtime/agent-sessions.js +45 -5
- package/dist/mcp/server/runtime/api.js +29 -1
- package/dist/mcp/server/runtime/execution-profile.js +19 -0
- package/dist/mcp/server/runtime/identity/directory.js +2 -2
- package/dist/mcp/server/runtime/presence.js +4 -2
- package/dist/mcp/server/runtime/room-api.js +24 -7
- package/dist/mcp/server/runtime/room-state.js +46 -0
- package/dist/mcp/server/runtime/rooms.js +16 -2
- package/dist/mcp/server/runtime/supervised-room-authority.js +8 -0
- package/dist/mcp/server/runtime/supervisor-bridge.js +658 -21
- package/dist/mcp/server/runtime/tool-surface-policy.js +26 -0
- package/dist/mcp/server/runtime/worker-bearer.js +40 -6
- package/dist/mcp/server/runtime-contract.js +27 -0
- package/dist/mcp/server/runtime.js +14 -3
- package/dist/mcp/server/supervised-tool-facade.js +105 -0
- package/dist/mcp/server/tools/agent-sessions.js +74 -7
- package/dist/mcp/server/tools/messages/index.js +3 -2
- package/dist/mcp/server/tools/messages/send-tool.js +3 -0
- package/dist/mcp/server/tools/messages/wait-tool.js +56 -5
- package/dist/mcp/server/tools/onboarding/status-tool.js +5 -4
- package/dist/mcp/server/tools/rooms/inspection-tools.js +26 -13
- package/dist/mcp/server/tools/supervised-room-turn.js +42 -0
- package/dist/mcp/server.js +14 -7
- package/dist/shared/activation-routing.js +44 -0
- package/package.json +6 -2
|
@@ -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
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
12
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
+
}
|