letagents 0.12.12 â 0.12.14
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/git-remote.js +7 -7
- package/dist/mcp/local-state/agent-sessions.js +63 -5
- package/dist/mcp/local-state/local-chat.js +189 -48
- package/dist/mcp/local-state/storage.js +16 -9
- package/dist/mcp/server/daemon-tool-executor.js +92 -0
- package/dist/mcp/server/register-tools.js +4 -2
- package/dist/mcp/server/runtime/agent-sessions.js +16 -7
- package/dist/mcp/server/runtime/api.js +11 -3
- package/dist/mcp/server/runtime/daemon-tool-context.js +11 -0
- package/dist/mcp/server/runtime/execution-profile.js +1 -0
- package/dist/mcp/server/runtime/messages.js +55 -0
- package/dist/mcp/server/runtime/presence.js +3 -3
- package/dist/mcp/server/runtime/room-api.js +2 -2
- package/dist/mcp/server/runtime/room-state.js +19 -9
- package/dist/mcp/server/runtime/rooms.js +54 -33
- package/dist/mcp/server/runtime/supervisor-bridge.js +129 -11
- package/dist/mcp/server/runtime/tool-surface-policy.js +7 -0
- package/dist/mcp/server/runtime/worker-bearer.js +17 -2
- package/dist/mcp/server/runtime-contract.js +1 -0
- package/dist/mcp/server/runtime.js +6 -6
- package/dist/mcp/server/supervised-tool-facade.js +107 -6
- package/dist/mcp/server/tools/messages/read-tool.js +54 -97
- package/dist/mcp/server/tools/messages/reasoning-tool.js +2 -0
- package/dist/mcp/server/tools/messages/send-tool.js +2 -0
- package/dist/mcp/server/tools/messages/status-tool.js +2 -0
- package/dist/mcp/server/tools/messages/wait-tool.js +303 -71
- package/dist/mcp/server/tools/onboarding/status-tool.js +4 -4
- package/dist/mcp/server/tools/rooms/inspection-tools.js +15 -10
- package/dist/mcp/server/tools/rooms/repo-initialization-tool.js +2 -1
- package/dist/mcp/server/tools/tasks/board-tools.js +34 -2
- package/dist/mcp/sse-client.js +163 -20
- package/dist/shared/activation-routing.js +163 -23
- package/dist/shared/agent-presence.js +6 -0
- package/dist/shared/desktop-release-manifest.js +63 -0
- package/dist/shared/desktop-release.js +60 -0
- package/dist/shared/scoped-ids.js +6 -0
- package/package.json +7 -3
- package/shared/execution-approval-projection.d.mts +32 -0
- package/shared/execution-approval-projection.mjs +107 -0
- package/shared/execution-approval-publication-item.d.mts +20 -0
- package/shared/execution-approval-publication-item.mjs +61 -0
- package/shared/execution-approval-publication.d.mts +53 -0
- package/shared/execution-approval-publication.mjs +136 -0
- package/shared/execution-delegation-decision.d.mts +37 -0
- package/shared/execution-delegation-decision.mjs +73 -0
- package/shared/message-contracts.d.mts +32 -0
- package/shared/message-contracts.mjs +109 -0
- package/shared/room-agent-work.d.mts +31 -0
- package/shared/room-agent-work.mjs +44 -0
- package/shared/room-resource-invalidation.d.mts +38 -0
- package/shared/room-resource-invalidation.mjs +50 -0
- package/shared/routing-aliases.d.mts +18 -0
- package/shared/routing-aliases.mjs +66 -0
- package/shared/sqlite-thread-routing.d.mts +72 -0
- package/shared/sqlite-thread-routing.mjs +1038 -0
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
import { getStoredAgentSession, isLocalRoomStorageEnabled, saveAgentSession, } from "../../local-state.js";
|
|
1
|
+
import { getStoredAgentSession, isLocalRoomStorageEnabled, replaceLocalWorkerAgentSession, saveAgentSession, } from "../../local-state.js";
|
|
2
2
|
import { getGitCurrentBranch } from "../../git-remote.js";
|
|
3
3
|
import { randomUUID } from "node:crypto";
|
|
4
4
|
import { normalizeAgentBaseName } from "../../../shared/codenames.js";
|
|
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 {
|
|
8
|
+
import { hasSupervisedWorkerAuthority, requireValidWorkerBearerRuntime } from "./worker-bearer.js";
|
|
9
9
|
import { resolveCurrentSupervisedWorkerSession } from "./supervisor-bridge.js";
|
|
10
|
+
import { getDaemonToolExecutionContext, getRuntimeWorkingDirectory } from "./daemon-tool-context.js";
|
|
10
11
|
// A worker bearer already represents a server-side worker session. This local
|
|
11
12
|
// marker lets the MCP tool contract stay session-shaped without persisting or
|
|
12
13
|
// transmitting a second set of credentials.
|
|
@@ -119,7 +120,12 @@ export function requireWorkerAgentSession(roomId, sessionId) {
|
|
|
119
120
|
export async function resolveWorkerToolIdentity(input) {
|
|
120
121
|
const runtimeMode = requireValidWorkerBearerRuntime().mode;
|
|
121
122
|
if (runtimeMode === "supervised") {
|
|
122
|
-
const
|
|
123
|
+
const daemonContext = getDaemonToolExecutionContext();
|
|
124
|
+
const agentSession = daemonContext?.agentSession
|
|
125
|
+
?? await resolveCurrentSupervisedWorkerSession(input.roomId);
|
|
126
|
+
if (input.roomId && input.roomId !== agentSession.room_id) {
|
|
127
|
+
throw new Error(`Daemon-supervised worker session is registered for ${agentSession.room_id}, not ${input.roomId}.`);
|
|
128
|
+
}
|
|
123
129
|
if (input.agentSessionId
|
|
124
130
|
&& input.agentSessionId !== WORKER_BEARER_AGENT_SESSION_ID
|
|
125
131
|
&& input.agentSessionId !== agentSession.session_id) {
|
|
@@ -160,7 +166,7 @@ export async function resolveWorkerToolIdentity(input) {
|
|
|
160
166
|
}
|
|
161
167
|
const agentSession = input.agentSessionId
|
|
162
168
|
? requireWorkerAgentSession(input.roomId, input.agentSessionId)
|
|
163
|
-
: input.roomId && !
|
|
169
|
+
: input.roomId && !hasSupervisedWorkerAuthority() && await isLocalRoomStorageEnabled(input.roomId)
|
|
164
170
|
? await ensureLocalWorkerAgentSession(input.roomId)
|
|
165
171
|
: requireWorkerAgentSession(input.roomId, input.agentSessionId);
|
|
166
172
|
return {
|
|
@@ -173,7 +179,7 @@ export async function ensureLocalWorkerAgentSession(roomId, input = {}) {
|
|
|
173
179
|
const now = new Date().toISOString();
|
|
174
180
|
const runtime = input.runtime?.trim() || detectAgentRuntimeLabel();
|
|
175
181
|
const displayName = input.displayName?.trim() || identity.display_name;
|
|
176
|
-
|
|
182
|
+
const session = {
|
|
177
183
|
session_id: `local_${randomUUID()}`,
|
|
178
184
|
session_token: `local_${randomUUID()}`,
|
|
179
185
|
room_id: roomId,
|
|
@@ -195,10 +201,13 @@ export async function ensureLocalWorkerAgentSession(roomId, input = {}) {
|
|
|
195
201
|
updated_at: now,
|
|
196
202
|
last_seen_at: now,
|
|
197
203
|
ended_at: null,
|
|
198
|
-
}
|
|
204
|
+
};
|
|
205
|
+
return session.session_kind === "worker"
|
|
206
|
+
? replaceLocalWorkerAgentSession(session)
|
|
207
|
+
: saveAgentSession(session);
|
|
199
208
|
}
|
|
200
209
|
export function getAgentSessionRepoBranch(cwd) {
|
|
201
|
-
const workingDir = cwd?.trim() ||
|
|
210
|
+
const workingDir = cwd?.trim() || getRuntimeWorkingDirectory();
|
|
202
211
|
return getGitCurrentBranch(workingDir);
|
|
203
212
|
}
|
|
204
213
|
export function agentSessionCredentials(agentSession) {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { clearAuthenticatedAccountCache } from "./auth-cache.js";
|
|
2
|
+
import { getDaemonToolExecutionContext } from "./daemon-tool-context.js";
|
|
2
3
|
import { requireValidWorkerBearerRuntime } from "./worker-bearer.js";
|
|
3
4
|
import { borrowCurrentSupervisedWorkerCredential, } from "./supervisor-bridge.js";
|
|
4
5
|
let ownerAuthStoreLoader = () => import("../../local-state.js");
|
|
@@ -20,12 +21,18 @@ export class SupervisedWorkerCredentialError extends Error {
|
|
|
20
21
|
}
|
|
21
22
|
}
|
|
22
23
|
async function getSupervisedCredential() {
|
|
24
|
+
const daemonContext = getDaemonToolExecutionContext();
|
|
25
|
+
if (daemonContext)
|
|
26
|
+
return daemonContext.bearer;
|
|
23
27
|
const result = await supervisedCredentialBorrower();
|
|
24
28
|
if (result.state === "available")
|
|
25
29
|
return result.credential;
|
|
26
30
|
throw new SupervisedWorkerCredentialError(result.state === "deferred" ? "SUPERVISED_CREDENTIAL_UNAVAILABLE" : "SUPERVISED_CREDENTIAL_STALE");
|
|
27
31
|
}
|
|
28
32
|
export const API_URL = (process.env.LETAGENTS_API_URL || "http://localhost:3001").replace(/\/+$/, "");
|
|
33
|
+
export function getApiUrl() {
|
|
34
|
+
return getDaemonToolExecutionContext()?.apiUrl.replace(/\/+$/, "") ?? API_URL;
|
|
35
|
+
}
|
|
29
36
|
export class ApiError extends Error {
|
|
30
37
|
status;
|
|
31
38
|
body;
|
|
@@ -76,8 +83,9 @@ export function resolveApiPath(urlOrPath) {
|
|
|
76
83
|
return "/auth/device/start";
|
|
77
84
|
}
|
|
78
85
|
try {
|
|
79
|
-
const
|
|
80
|
-
const
|
|
86
|
+
const apiUrl = getApiUrl();
|
|
87
|
+
const parsed = new URL(urlOrPath, `${apiUrl}/`);
|
|
88
|
+
const apiBase = new URL(`${apiUrl}/`);
|
|
81
89
|
if (parsed.origin !== apiBase.origin) {
|
|
82
90
|
return "/auth/device/start";
|
|
83
91
|
}
|
|
@@ -109,7 +117,7 @@ export async function apiCall(path, options) {
|
|
|
109
117
|
headers.set("Authorization", authorizationHeader);
|
|
110
118
|
}
|
|
111
119
|
}
|
|
112
|
-
const res = await fetch(`${
|
|
120
|
+
const res = await fetch(`${getApiUrl()}${path}`, {
|
|
113
121
|
...options,
|
|
114
122
|
headers,
|
|
115
123
|
});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
|
+
const daemonToolContext = new AsyncLocalStorage();
|
|
3
|
+
export function getDaemonToolExecutionContext() {
|
|
4
|
+
return daemonToolContext.getStore() ?? null;
|
|
5
|
+
}
|
|
6
|
+
export function runWithDaemonToolExecutionContext(context, callback) {
|
|
7
|
+
return daemonToolContext.run(context, callback);
|
|
8
|
+
}
|
|
9
|
+
export function getRuntimeWorkingDirectory() {
|
|
10
|
+
return getDaemonToolExecutionContext()?.cwd ?? process.cwd();
|
|
11
|
+
}
|
|
@@ -151,6 +151,61 @@ export function toAgentReadableMessages(messages, contextMessages = []) {
|
|
|
151
151
|
const summaries = buildThreadSummaries(records, recordsById);
|
|
152
152
|
return (messages ?? []).map((message) => toAgentReadableMessage(message, recordsById, summaries));
|
|
153
153
|
}
|
|
154
|
+
export const AGENT_MESSAGE_OUTPUT_MAX_BYTES = 4 * 1024 * 1024;
|
|
155
|
+
export const AGENT_MESSAGE_BODY_MAX_BYTES = AGENT_MESSAGE_OUTPUT_MAX_BYTES / 2;
|
|
156
|
+
function jsonUtf8Bytes(value) {
|
|
157
|
+
const serialized = JSON.stringify(value);
|
|
158
|
+
return Buffer.byteLength(serialized === undefined ? "null" : serialized, "utf8");
|
|
159
|
+
}
|
|
160
|
+
function compactOversizedAgentMessage(message) {
|
|
161
|
+
if (!isRecord(message)) {
|
|
162
|
+
return { content_truncated: true, value_type: typeof message };
|
|
163
|
+
}
|
|
164
|
+
const text = typeof message.text === "string" ? message.text : "";
|
|
165
|
+
return {
|
|
166
|
+
...(typeof message.id === "string" ? { id: message.id } : {}),
|
|
167
|
+
...(typeof message.sender === "string" ? { sender: message.sender } : {}),
|
|
168
|
+
...(typeof message.source === "string" ? { source: message.source } : {}),
|
|
169
|
+
...(typeof message.timestamp === "string" ? { timestamp: message.timestamp } : {}),
|
|
170
|
+
...(typeof message.thread_root_id === "string"
|
|
171
|
+
? { thread_root_id: message.thread_root_id }
|
|
172
|
+
: {}),
|
|
173
|
+
...(typeof message.thread_reply_to_id === "string"
|
|
174
|
+
? { thread_reply_to_id: message.thread_reply_to_id }
|
|
175
|
+
: {}),
|
|
176
|
+
text: text.slice(0, 16 * 1024),
|
|
177
|
+
content_truncated: true,
|
|
178
|
+
original_utf8_bytes: jsonUtf8Bytes(message),
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
export function boundAgentMessageOutput(messages, options = {}) {
|
|
182
|
+
const direction = options.direction ?? "prefix";
|
|
183
|
+
const maxBytes = Math.max(1024, Math.floor(options.maxBytes ?? AGENT_MESSAGE_OUTPUT_MAX_BYTES));
|
|
184
|
+
const ordered = direction === "suffix" ? [...messages].reverse() : [...messages];
|
|
185
|
+
const selected = [];
|
|
186
|
+
let outputBytes = 2; // JSON array brackets.
|
|
187
|
+
for (const message of ordered) {
|
|
188
|
+
let candidate = message;
|
|
189
|
+
let candidateBytes = jsonUtf8Bytes(candidate);
|
|
190
|
+
if (candidateBytes + 2 > maxBytes) {
|
|
191
|
+
candidate = compactOversizedAgentMessage(candidate);
|
|
192
|
+
candidateBytes = jsonUtf8Bytes(candidate);
|
|
193
|
+
}
|
|
194
|
+
const separatorBytes = selected.length > 0 ? 1 : 0;
|
|
195
|
+
if (outputBytes + separatorBytes + candidateBytes > maxBytes)
|
|
196
|
+
break;
|
|
197
|
+
selected.push(candidate);
|
|
198
|
+
outputBytes += separatorBytes + candidateBytes;
|
|
199
|
+
}
|
|
200
|
+
if (direction === "suffix")
|
|
201
|
+
selected.reverse();
|
|
202
|
+
return {
|
|
203
|
+
messages: selected,
|
|
204
|
+
truncated: selected.length < messages.length,
|
|
205
|
+
omittedMessageCount: messages.length - selected.length,
|
|
206
|
+
outputBytes,
|
|
207
|
+
};
|
|
208
|
+
}
|
|
154
209
|
export function appendIncludePromptOnly(path) {
|
|
155
210
|
return `${path}${path.includes("?") ? "&" : "?"}include_prompt_only=1`;
|
|
156
211
|
}
|
|
@@ -4,7 +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 {
|
|
7
|
+
import { hasSupervisedWorkerAuthority } from "./worker-bearer.js";
|
|
8
8
|
const roomPresenceByIdentity = new Map();
|
|
9
9
|
export function getRememberedRoomPresence(roomId, identity) {
|
|
10
10
|
if (!roomId || !identity) {
|
|
@@ -19,7 +19,7 @@ export async function syncRoomPresence(roomId, identity, presence, agentSession)
|
|
|
19
19
|
}
|
|
20
20
|
roomPresenceByIdentity.set(getRoomIdentityPresenceCacheKey(roomId, resolvedIdentity.actor_label), presence);
|
|
21
21
|
const { localRoomId, cloudRoomId } = await resolveLocalRoomStorageIdentifiers(roomId);
|
|
22
|
-
if (!
|
|
22
|
+
if (!hasSupervisedWorkerAuthority() && await isLocalRoomStorageEnabled(roomId)) {
|
|
23
23
|
touchRoomSession(localRoomId || roomId);
|
|
24
24
|
return;
|
|
25
25
|
}
|
|
@@ -39,7 +39,7 @@ export async function syncRoomPresence(roomId, identity, presence, agentSession)
|
|
|
39
39
|
...agentSessionCredentials(agentSession),
|
|
40
40
|
}),
|
|
41
41
|
});
|
|
42
|
-
if (!
|
|
42
|
+
if (!hasSupervisedWorkerAuthority())
|
|
43
43
|
touchRoomSession(apiRoomId);
|
|
44
44
|
}
|
|
45
45
|
catch (error) {
|
|
@@ -4,9 +4,9 @@ import { apiCall, isMissingRouteError, } from "./api.js";
|
|
|
4
4
|
import { maybeHandleRepoRoomAuthRequired } from "./device-auth.js";
|
|
5
5
|
import { getLastMessageId } from "./messages.js";
|
|
6
6
|
import { currentRoom, getCurrentSupervisedRoomAuthority } from "./room-state.js";
|
|
7
|
-
import {
|
|
7
|
+
import { hasSupervisedWorkerAuthority } from "./worker-bearer.js";
|
|
8
8
|
export async function roomScopedApiCall(input) {
|
|
9
|
-
const supervised =
|
|
9
|
+
const supervised = hasSupervisedWorkerAuthority();
|
|
10
10
|
const exactRoomAuthority = supervised ? getCurrentSupervisedRoomAuthority() : null;
|
|
11
11
|
if (supervised && (!exactRoomAuthority || input.room_id !== exactRoomAuthority)) {
|
|
12
12
|
throw new Error("The daemon-supervised API request is missing its exact per-call room authority.");
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { SseClient } from "../../sse-client.js";
|
|
2
2
|
import { getStoredAgentIdentity, saveRoomSession, touchRoomSession, } from "../../local-state.js";
|
|
3
3
|
import { getCanonicalRoomWebPath, } from "../../room-id.js";
|
|
4
|
-
import {
|
|
4
|
+
import { getApiUrl, getLetagentsToken } from "./api.js";
|
|
5
5
|
import { AGENT_INSTANCE_UUID, currentAgentIdentity, currentAgentIdentityKey, } from "./identity.js";
|
|
6
|
-
import {
|
|
6
|
+
import { hasSupervisedWorkerAuthority } from "./worker-bearer.js";
|
|
7
7
|
import { getCurrentSupervisedRoomAuthority, runWithSupervisedRoomAuthority, } from "./supervised-room-authority.js";
|
|
8
8
|
let mcpServer = null;
|
|
9
9
|
let sseClient = null;
|
|
@@ -15,7 +15,7 @@ export function shutdownRuntime() {
|
|
|
15
15
|
sseClient?.unsubscribeAll();
|
|
16
16
|
}
|
|
17
17
|
function getSseClient() {
|
|
18
|
-
sseClient ??= new SseClient(
|
|
18
|
+
sseClient ??= new SseClient(getApiUrl(), () => getLetagentsToken());
|
|
19
19
|
return sseClient;
|
|
20
20
|
}
|
|
21
21
|
function getCurrentStreamAgentIdentity() {
|
|
@@ -32,6 +32,7 @@ function getCurrentStreamAgentIdentity() {
|
|
|
32
32
|
export function toRoomState(input) {
|
|
33
33
|
return {
|
|
34
34
|
room_id: input.room_id,
|
|
35
|
+
navigation_locator: input.navigation_locator ?? null,
|
|
35
36
|
project_id: input.project_id ?? null,
|
|
36
37
|
code: input.code ?? null,
|
|
37
38
|
display_name: input.display_name ?? null,
|
|
@@ -40,8 +41,12 @@ export function toRoomState(input) {
|
|
|
40
41
|
is_local: input.is_local ?? false,
|
|
41
42
|
};
|
|
42
43
|
}
|
|
44
|
+
export function currentRoomMatchesLocator(locator) {
|
|
45
|
+
const value = locator?.trim();
|
|
46
|
+
return Boolean(value && currentRoom && (currentRoom.room_id === value || currentRoom.navigation_locator === value));
|
|
47
|
+
}
|
|
43
48
|
function getCanonicalRoomWebUrl(roomId) {
|
|
44
|
-
return new URL(getCanonicalRoomWebPath(roomId), `${
|
|
49
|
+
return new URL(getCanonicalRoomWebPath(roomId), `${getApiUrl()}/`).toString();
|
|
45
50
|
}
|
|
46
51
|
export function withCanonicalRoomLink(roomId, payload) {
|
|
47
52
|
return {
|
|
@@ -87,7 +92,7 @@ export function toPublicRoomResponse(response, fallbackRoomId) {
|
|
|
87
92
|
}
|
|
88
93
|
export function rememberRoom(state, lastMessageId) {
|
|
89
94
|
currentRoom = state;
|
|
90
|
-
if (
|
|
95
|
+
if (hasSupervisedWorkerAuthority())
|
|
91
96
|
return state;
|
|
92
97
|
saveRoomSession({
|
|
93
98
|
room_id: state.room_id,
|
|
@@ -109,11 +114,16 @@ export function rememberRoom(state, lastMessageId) {
|
|
|
109
114
|
}, (_message) => {
|
|
110
115
|
touchRoomSession(state.room_id);
|
|
111
116
|
mcpServer?.server.sendResourceListChanged();
|
|
117
|
+
}, () => {
|
|
118
|
+
// The SSE cursor crossed a broker/bridge loss boundary. MCP resources
|
|
119
|
+
// are pull-based, so invalidating the list is the full-state repair.
|
|
120
|
+
touchRoomSession(state.room_id);
|
|
121
|
+
mcpServer?.server.sendResourceListChanged();
|
|
112
122
|
});
|
|
113
123
|
return state;
|
|
114
124
|
}
|
|
115
125
|
export function touchCurrentRoom(lastMessageId) {
|
|
116
|
-
if (
|
|
126
|
+
if (hasSupervisedWorkerAuthority())
|
|
117
127
|
return;
|
|
118
128
|
if (!currentRoom) {
|
|
119
129
|
return;
|
|
@@ -121,7 +131,7 @@ export function touchCurrentRoom(lastMessageId) {
|
|
|
121
131
|
touchRoomSession(currentRoom.room_id, lastMessageId);
|
|
122
132
|
}
|
|
123
133
|
export function getTargetRoomId(roomId) {
|
|
124
|
-
if (
|
|
134
|
+
if (hasSupervisedWorkerAuthority()) {
|
|
125
135
|
const exactRoomAuthority = getCurrentSupervisedRoomAuthority();
|
|
126
136
|
if (!exactRoomAuthority) {
|
|
127
137
|
throw new Error("The daemon-supervised tool has not received its exact room authority.");
|
|
@@ -152,7 +162,7 @@ export function toPublicCurrentRoomState() {
|
|
|
152
162
|
* repository inspection and can safely rebind after a durable room move.
|
|
153
163
|
*/
|
|
154
164
|
export function runWithCurrentSupervisedRoom(roomId, callback) {
|
|
155
|
-
if (!
|
|
165
|
+
if (!hasSupervisedWorkerAuthority()) {
|
|
156
166
|
throw new Error("Only a daemon-supervised bounded turn can bind supervisor room authority.");
|
|
157
167
|
}
|
|
158
168
|
const normalized = roomId.trim();
|
|
@@ -162,7 +172,7 @@ export function runWithCurrentSupervisedRoom(roomId, callback) {
|
|
|
162
172
|
return runWithSupervisedRoomAuthority(normalized, callback);
|
|
163
173
|
}
|
|
164
174
|
export function getFallbackProjectId() {
|
|
165
|
-
if (
|
|
175
|
+
if (hasSupervisedWorkerAuthority())
|
|
166
176
|
return null;
|
|
167
177
|
return currentRoom?.project_id ?? null;
|
|
168
178
|
}
|
|
@@ -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 {
|
|
12
|
+
import { hasSupervisedWorkerAuthority, requireValidWorkerBearerRuntime } from "./worker-bearer.js";
|
|
13
13
|
export function normalizeJoinSessionMode(value) {
|
|
14
14
|
return String(value || "").trim().toLowerCase() === "live" ? "live" : "current";
|
|
15
15
|
}
|
|
@@ -20,7 +20,7 @@ function isNotFoundApiError(error) {
|
|
|
20
20
|
return error instanceof ApiError && error.status === 404;
|
|
21
21
|
}
|
|
22
22
|
function parseGeneratedGitRefRoomIdentifier(identifier) {
|
|
23
|
-
const match = /^
|
|
23
|
+
const match = /^github\.com\/([^/\s]+\/[^/\s]+)\/focus\/git:(branch|tag):[A-Za-z0-9_-]+$/.exec(identifier.trim());
|
|
24
24
|
if (!match) {
|
|
25
25
|
return null;
|
|
26
26
|
}
|
|
@@ -30,7 +30,7 @@ function parseGeneratedGitRefRoomIdentifier(identifier) {
|
|
|
30
30
|
};
|
|
31
31
|
}
|
|
32
32
|
export async function joinRoomIdentifier(identifier, joinedVia, options = {}) {
|
|
33
|
-
if (
|
|
33
|
+
if (hasSupervisedWorkerAuthority()) {
|
|
34
34
|
throw new Error("Room joins and creation are disabled during a daemon-supervised bounded turn.");
|
|
35
35
|
}
|
|
36
36
|
const roomId = joinedVia === "join_code" ? normalizeInviteCode(identifier) : identifier.trim();
|
|
@@ -67,6 +67,7 @@ export async function joinRoomIdentifier(identifier, joinedVia, options = {}) {
|
|
|
67
67
|
const agentIdentity = await ensureAgentIdentity();
|
|
68
68
|
const room = rememberRoom(toRoomState({
|
|
69
69
|
room_id: joinedRoomId,
|
|
70
|
+
navigation_locator: joinedRoomId === roomId ? null : roomId,
|
|
70
71
|
project_id: typeof response.project_id === "string" ? response.project_id : null,
|
|
71
72
|
code: typeof response.code === "string"
|
|
72
73
|
? response.code
|
|
@@ -190,7 +191,7 @@ export async function joinRoomIdentifierWithoutImplicitGitRefCreate(identifier,
|
|
|
190
191
|
}));
|
|
191
192
|
}
|
|
192
193
|
export async function createInviteRoom() {
|
|
193
|
-
if (
|
|
194
|
+
if (hasSupervisedWorkerAuthority()) {
|
|
194
195
|
throw new Error("Room joins and creation are disabled during a daemon-supervised bounded turn.");
|
|
195
196
|
}
|
|
196
197
|
const project = await apiCall("/projects", { method: "POST" });
|
|
@@ -260,7 +261,27 @@ export async function joinNamedRoom(name, sessionMode) {
|
|
|
260
261
|
session_mode: sessionMode,
|
|
261
262
|
});
|
|
262
263
|
}
|
|
263
|
-
function
|
|
264
|
+
async function bindWorkerRoomLocator(roomLocator, source) {
|
|
265
|
+
const response = await apiCall(`/rooms/resolve/${encodeURIComponent(roomLocator)}`);
|
|
266
|
+
if (response.room_exists !== true) {
|
|
267
|
+
throw new ApiError(404, JSON.stringify({ error: "Room not found", code: "ROOM_NOT_FOUND" }));
|
|
268
|
+
}
|
|
269
|
+
const roomId = typeof response.canonical_room_id === "string"
|
|
270
|
+
? response.canonical_room_id
|
|
271
|
+
: roomLocator;
|
|
272
|
+
return {
|
|
273
|
+
room: rememberRoom(toRoomState({
|
|
274
|
+
room_id: roomId,
|
|
275
|
+
navigation_locator: roomId === roomLocator ? null : roomLocator,
|
|
276
|
+
project_id: roomId,
|
|
277
|
+
display_name: roomId,
|
|
278
|
+
git_room: response.git_room ?? null,
|
|
279
|
+
joined_via: source === ".letagents.json" ? "config" : "git-remote",
|
|
280
|
+
})),
|
|
281
|
+
source,
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
async function bindWorkerRoomFromContext() {
|
|
264
285
|
const configRoom = getRoomFromConfig();
|
|
265
286
|
if (configRoom) {
|
|
266
287
|
const gitContext = buildActiveGitRoomContext({
|
|
@@ -268,26 +289,26 @@ function bindWorkerRoomFromContext() {
|
|
|
268
289
|
currentBranch: getGitCurrentBranch(),
|
|
269
290
|
defaultBranch: getGitDefaultBranch(),
|
|
270
291
|
});
|
|
271
|
-
const roomId = gitContext.
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
}
|
|
292
|
+
const roomId = gitContext.activeRoomLocator ?? configRoom;
|
|
293
|
+
try {
|
|
294
|
+
return await bindWorkerRoomLocator(roomId, ".letagents.json");
|
|
295
|
+
}
|
|
296
|
+
catch (error) {
|
|
297
|
+
if (roomId === configRoom || !isNotFoundApiError(error))
|
|
298
|
+
throw error;
|
|
299
|
+
return bindWorkerRoomLocator(configRoom, ".letagents.json");
|
|
300
|
+
}
|
|
280
301
|
}
|
|
281
302
|
const gitContext = getGitRoomContext();
|
|
282
|
-
if (gitContext.
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
}
|
|
303
|
+
if (gitContext.activeRoomLocator) {
|
|
304
|
+
try {
|
|
305
|
+
return await bindWorkerRoomLocator(gitContext.activeRoomLocator, "git remote");
|
|
306
|
+
}
|
|
307
|
+
catch (error) {
|
|
308
|
+
if (!gitContext.repoRoom || !isNotFoundApiError(error))
|
|
309
|
+
throw error;
|
|
310
|
+
return bindWorkerRoomLocator(gitContext.repoRoom, "git remote");
|
|
311
|
+
}
|
|
291
312
|
}
|
|
292
313
|
const savedCurrentRoom = getStoredCurrentRoom();
|
|
293
314
|
if (!savedCurrentRoom) {
|
|
@@ -316,9 +337,9 @@ export async function autoJoinFromContext() {
|
|
|
316
337
|
return;
|
|
317
338
|
}
|
|
318
339
|
if (workerRuntime.mode === "worker") {
|
|
319
|
-
const bound = bindWorkerRoomFromContext();
|
|
340
|
+
const bound = await bindWorkerRoomFromContext();
|
|
320
341
|
if (bound) {
|
|
321
|
-
console.error(`đ Bound worker bearer to room '${bound.room.room_id}' (from ${bound.source}
|
|
342
|
+
console.error(`đ Bound worker bearer to existing room '${bound.room.room_id}' (from ${bound.source}).`);
|
|
322
343
|
}
|
|
323
344
|
else {
|
|
324
345
|
console.error("âšī¸ Worker bearer has no .letagents.json, git remote, or saved room to bind locally.");
|
|
@@ -332,17 +353,17 @@ export async function autoJoinFromContext() {
|
|
|
332
353
|
currentBranch: getGitCurrentBranch(),
|
|
333
354
|
defaultBranch: getGitDefaultBranch(),
|
|
334
355
|
});
|
|
335
|
-
if (gitContext.
|
|
336
|
-
const joinedBranchRoom = await joinExistingRoomIdentifier(gitContext.
|
|
356
|
+
if (gitContext.activeRefRoomLocator && gitContext.currentBranch) {
|
|
357
|
+
const joinedBranchRoom = await joinExistingRoomIdentifier(gitContext.activeRefRoomLocator, "config");
|
|
337
358
|
if (joinedBranchRoom) {
|
|
338
359
|
await ensureAgentIdentity();
|
|
339
|
-
console.error(`đ Auto-joined existing branch room '${gitContext.
|
|
360
|
+
console.error(`đ Auto-joined existing branch room '${gitContext.activeRefRoomLocator}' (from .letagents.json + branch '${gitContext.currentBranch}')`);
|
|
340
361
|
return;
|
|
341
362
|
}
|
|
342
363
|
}
|
|
343
364
|
await joinRoomIdentifier(configRoom, "config");
|
|
344
365
|
await ensureAgentIdentity();
|
|
345
|
-
const branchNote = gitContext.
|
|
366
|
+
const branchNote = gitContext.activeRefRoomLocator && gitContext.currentBranch
|
|
346
367
|
? `; branch '${gitContext.currentBranch}' has no existing Git Room`
|
|
347
368
|
: "";
|
|
348
369
|
console.error(`đ Auto-joined room '${configRoom}' (from .letagents.json${branchNote})`);
|
|
@@ -350,17 +371,17 @@ export async function autoJoinFromContext() {
|
|
|
350
371
|
}
|
|
351
372
|
const gitContext = getGitRoomContext();
|
|
352
373
|
if (gitContext.repoRoom) {
|
|
353
|
-
if (gitContext.
|
|
354
|
-
const joinedBranchRoom = await joinExistingRoomIdentifier(gitContext.
|
|
374
|
+
if (gitContext.activeRefRoomLocator && gitContext.currentBranch) {
|
|
375
|
+
const joinedBranchRoom = await joinExistingRoomIdentifier(gitContext.activeRefRoomLocator, "git-remote");
|
|
355
376
|
if (joinedBranchRoom) {
|
|
356
377
|
await ensureAgentIdentity();
|
|
357
|
-
console.error(`đ Auto-joined existing branch room '${gitContext.
|
|
378
|
+
console.error(`đ Auto-joined existing branch room '${gitContext.activeRefRoomLocator}' (inferred from git remote and branch '${gitContext.currentBranch}' â consider adding a .letagents.json)`);
|
|
358
379
|
return;
|
|
359
380
|
}
|
|
360
381
|
}
|
|
361
382
|
await joinRoomIdentifier(gitContext.repoRoom, "git-remote");
|
|
362
383
|
await ensureAgentIdentity();
|
|
363
|
-
const branchNote = gitContext.
|
|
384
|
+
const branchNote = gitContext.activeRefRoomLocator && gitContext.currentBranch
|
|
364
385
|
? `; branch '${gitContext.currentBranch}' has no existing Git Room`
|
|
365
386
|
: "";
|
|
366
387
|
console.error(`đ Auto-joined room '${gitContext.repoRoom}' (inferred from git remote${branchNote} â consider adding a .letagents.json)`);
|