letagents 0.12.9 → 0.12.11
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/README.md +18 -15
- package/dist/api/board-intent-payloads.js +25 -0
- package/dist/mcp/agent-prompt-delivery.js +20 -0
- package/dist/mcp/codex-session/app-server.js +89 -0
- package/dist/mcp/codex-session/index.js +10 -0
- package/dist/mcp/codex-session/rpc-client.js +85 -0
- package/dist/mcp/codex-session/runtime-bridge.js +271 -0
- package/dist/mcp/codex-session/runtime-summary.js +220 -0
- package/dist/mcp/codex-session/session-inspection.js +100 -0
- package/dist/mcp/codex-session/session-mapper.js +51 -0
- package/dist/mcp/codex-session/session-start.js +182 -0
- package/dist/mcp/codex-session/session-status.js +77 -0
- package/dist/mcp/codex-session/session-stop.js +43 -0
- package/dist/mcp/codex-session/session-supervisor.js +111 -0
- package/dist/mcp/codex-session/start-prompt.js +49 -0
- package/dist/mcp/codex-session/types.js +1 -0
- package/dist/mcp/codex-session.js +1 -672
- package/dist/mcp/config-reader.js +10 -2
- package/dist/mcp/git-remote.js +90 -14
- package/dist/mcp/local-state/agent-identity.js +26 -0
- package/dist/mcp/local-state/agent-sessions.js +66 -0
- package/dist/mcp/local-state/auth.js +57 -0
- package/dist/mcp/local-state/codex-live-sessions.js +59 -0
- package/dist/mcp/local-state/index.js +8 -0
- package/dist/mcp/local-state/local-chat.js +808 -0
- package/dist/mcp/local-state/room-sessions.js +51 -0
- package/dist/mcp/local-state/storage.js +95 -0
- package/dist/mcp/local-state/types.js +1 -0
- package/dist/mcp/local-state.js +1 -351
- package/dist/mcp/managed-agent-providers.js +65 -0
- package/dist/mcp/rental-tools/activity.js +37 -0
- package/dist/mcp/rental-tools/budget.js +36 -0
- package/dist/mcp/rental-tools/context.js +92 -0
- package/dist/mcp/rental-tools/lifecycle.js +47 -0
- package/dist/mcp/rental-tools/metering.js +73 -0
- package/dist/mcp/rental-tools/patches.js +100 -0
- package/dist/mcp/rental-tools/provider.js +117 -0
- package/dist/mcp/rental-tools/shared.js +19 -0
- package/dist/mcp/rental-tools/types.js +1 -0
- package/dist/mcp/rental-tools.js +7 -0
- package/dist/mcp/repo-visibility.js +8 -6
- package/dist/mcp/server/register-tools.js +19 -0
- package/dist/mcp/server/repo-context.js +28 -0
- package/dist/mcp/server/resources.js +77 -0
- package/dist/mcp/server/runtime/agent-sessions.js +173 -0
- package/dist/mcp/server/runtime/api.js +104 -0
- package/dist/mcp/server/runtime/auth-cache.js +20 -0
- package/dist/mcp/server/runtime/device-auth.js +48 -0
- package/dist/mcp/server/runtime/identity/config.js +58 -0
- package/dist/mcp/server/runtime/identity/directory.js +94 -0
- package/dist/mcp/server/runtime/identity/liveness.js +32 -0
- package/dist/mcp/server/runtime/identity/names.js +74 -0
- package/dist/mcp/server/runtime/identity/public.js +19 -0
- package/dist/mcp/server/runtime/identity/state.js +32 -0
- package/dist/mcp/server/runtime/identity.js +97 -0
- package/dist/mcp/server/runtime/messages.js +160 -0
- package/dist/mcp/server/runtime/presence.js +52 -0
- package/dist/mcp/server/runtime/room-api.js +42 -0
- package/dist/mcp/server/runtime/room-state.js +122 -0
- package/dist/mcp/server/runtime/rooms.js +374 -0
- package/dist/mcp/server/runtime/supervisor-bridge.js +94 -0
- package/dist/mcp/server/runtime/worker-bearer.js +67 -0
- package/dist/mcp/server/runtime.js +14 -0
- package/dist/mcp/server/tools/agent-sessions.js +415 -0
- package/dist/mcp/server/tools/messages/index.js +14 -0
- package/dist/mcp/server/tools/messages/message-lookup.js +75 -0
- package/dist/mcp/server/tools/messages/read-tool.js +176 -0
- package/dist/mcp/server/tools/messages/reasoning-tool.js +179 -0
- package/dist/mcp/server/tools/messages/response.js +17 -0
- package/dist/mcp/server/tools/messages/send-tool.js +160 -0
- package/dist/mcp/server/tools/messages/status-tool.js +87 -0
- package/dist/mcp/server/tools/messages/wait-tool.js +403 -0
- package/dist/mcp/server/tools/messages.js +1 -0
- package/dist/mcp/server/tools/onboarding/device-auth-tools.js +158 -0
- package/dist/mcp/server/tools/onboarding/name-tool.js +93 -0
- package/dist/mcp/server/tools/onboarding/responses.js +10 -0
- package/dist/mcp/server/tools/onboarding/status-tool.js +125 -0
- package/dist/mcp/server/tools/onboarding.js +8 -0
- package/dist/mcp/server/tools/rental/activity-tools.js +43 -0
- package/dist/mcp/server/tools/rental/context-tools.js +43 -0
- package/dist/mcp/server/tools/rental/index.js +17 -0
- package/dist/mcp/server/tools/rental/metering-tools.js +39 -0
- package/dist/mcp/server/tools/rental/patch-tools.js +53 -0
- package/dist/mcp/server/tools/rental/provider-tools.js +44 -0
- package/dist/mcp/server/tools/rental/response.js +10 -0
- package/dist/mcp/server/tools/rental/types.js +1 -0
- package/dist/mcp/server/tools/rental.js +1 -0
- package/dist/mcp/server/tools/rooms/index.js +5 -0
- package/dist/mcp/server/tools/rooms/inspection-tools.js +140 -0
- package/dist/mcp/server/tools/rooms/join-tools.js +37 -0
- package/dist/mcp/server/tools/rooms/repo-initialization-tool.js +113 -0
- package/dist/mcp/server/tools/rooms/repo-visibility-tool.js +20 -0
- package/dist/mcp/server/tools/rooms/response.js +10 -0
- package/dist/mcp/server/tools/rooms/resume-tool.js +48 -0
- package/dist/mcp/server/tools/rooms.js +1 -0
- package/dist/mcp/server/tools/tasks/api.js +222 -0
- package/dist/mcp/server/tools/tasks/artifact-tools.js +75 -0
- package/dist/mcp/server/tools/tasks/board-intent-tools.js +326 -0
- package/dist/mcp/server/tools/tasks/board-tools.js +73 -0
- package/dist/mcp/server/tools/tasks/context.js +35 -0
- package/dist/mcp/server/tools/tasks/event-tools.js +40 -0
- package/dist/mcp/server/tools/tasks/index.js +16 -0
- package/dist/mcp/server/tools/tasks/lease-tools.js +145 -0
- package/dist/mcp/server/tools/tasks/mutation-tools.js +121 -0
- package/dist/mcp/server/tools/tasks/response.js +13 -0
- package/dist/mcp/server/tools/tasks/schemas.js +68 -0
- package/dist/mcp/server/tools/tasks/verdict-tools.js +51 -0
- package/dist/mcp/server/tools/tasks.js +1 -0
- package/dist/mcp/server.js +12 -3157
- package/dist/mcp/sse-client.js +9 -17
- package/dist/shared/activation-routing.js +272 -0
- package/dist/shared/agent-identity.js +21 -5
- package/dist/shared/agent-presence.js +5 -3
- package/dist/shared/agent-session-bearer.js +28 -0
- package/dist/shared/board-manager-failover.js +16 -0
- package/dist/shared/rental/lrt.js +214 -0
- package/dist/shared/rental/meter-types.js +14 -0
- package/dist/shared/room-agent-prompts.js +14 -1
- package/package.json +31 -16
- package/dist/mcp/__tests__/config-reader.test.js +0 -93
- package/dist/mcp/__tests__/git-remote.test.js +0 -37
- package/dist/mcp/__tests__/server-helpers.test.js +0 -121
- package/dist/shared/agent-reasoning.js +0 -49
- /package/dist/{mcp → shared}/codenames.js +0 -0
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
import { getRoomFromConfig } from "../../config-reader.js";
|
|
2
|
+
import { buildActiveGitRoomContext, getGitCurrentBranch, getGitDefaultBranch, getGitRoomContext, } from "../../git-remote.js";
|
|
3
|
+
import { encodeRoomIdPath, looksLikeInviteCode, normalizeInviteCode, } from "../../room-id.js";
|
|
4
|
+
import { isLocalRoomStorageEnabled, getStoredCurrentRoom, getStoredRoomSession, } from "../../local-state.js";
|
|
5
|
+
import { getDefaultManagedAgentProvider, toManagedAgentStartResponse, } from "../../managed-agent-providers.js";
|
|
6
|
+
import { ApiError, apiCall, isMissingRouteError, } from "./api.js";
|
|
7
|
+
import { RepoRoomAuthRequiredError, maybeHandleRepoRoomAuthRequired, } from "./device-auth.js";
|
|
8
|
+
import { ensureAgentIdentity, toPublicAgentIdentity, withAgentIdentity, } from "./identity.js";
|
|
9
|
+
import { withJoinRoomAgentPrompt } from "./messages.js";
|
|
10
|
+
import { syncRoomPresence } from "./presence.js";
|
|
11
|
+
import { rememberRoom, toPublicRoomResponse, toRoomState, } from "./room-state.js";
|
|
12
|
+
import { requireValidWorkerBearerRuntime } from "./worker-bearer.js";
|
|
13
|
+
export function normalizeJoinSessionMode(value) {
|
|
14
|
+
return String(value || "").trim().toLowerCase() === "live" ? "live" : "current";
|
|
15
|
+
}
|
|
16
|
+
export function getCurrentLiveSessionPayload(roomId) {
|
|
17
|
+
return getDefaultManagedAgentProvider().getCurrentLiveSessionPayload(roomId);
|
|
18
|
+
}
|
|
19
|
+
function isNotFoundApiError(error) {
|
|
20
|
+
return error instanceof ApiError && error.status === 404;
|
|
21
|
+
}
|
|
22
|
+
function parseGeneratedGitRefRoomIdentifier(identifier) {
|
|
23
|
+
const match = /^git-room:github\.com:([^/:\s]+\/[^/:\s]+):(branch|tag):[A-Za-z0-9_-]+$/.exec(identifier.trim());
|
|
24
|
+
if (!match) {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
return {
|
|
28
|
+
repoRoom: `github.com/${match[1].toLowerCase()}`,
|
|
29
|
+
refType: match[2],
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
export async function joinRoomIdentifier(identifier, joinedVia, options = {}) {
|
|
33
|
+
const roomId = joinedVia === "join_code" ? normalizeInviteCode(identifier) : identifier.trim();
|
|
34
|
+
if (joinedVia !== "join_code" && await isLocalRoomStorageEnabled(roomId)) {
|
|
35
|
+
if (options.allowCreate === false && !getStoredRoomSession(roomId)) {
|
|
36
|
+
throw new ApiError(404, JSON.stringify({ error: "Room not found", room_id: roomId }));
|
|
37
|
+
}
|
|
38
|
+
const agentIdentity = await ensureAgentIdentity();
|
|
39
|
+
const room = rememberRoom(toRoomState({
|
|
40
|
+
room_id: roomId,
|
|
41
|
+
project_id: null,
|
|
42
|
+
code: null,
|
|
43
|
+
display_name: roomId,
|
|
44
|
+
joined_via: joinedVia,
|
|
45
|
+
is_local: true,
|
|
46
|
+
}));
|
|
47
|
+
return {
|
|
48
|
+
room,
|
|
49
|
+
response: {
|
|
50
|
+
room_id: roomId,
|
|
51
|
+
display_name: roomId,
|
|
52
|
+
joined_via: joinedVia,
|
|
53
|
+
is_local: true,
|
|
54
|
+
agent_identity: toPublicAgentIdentity(agentIdentity),
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
try {
|
|
59
|
+
const createQuery = options.allowCreate === false ? "?create=false" : "";
|
|
60
|
+
const response = await apiCall(`/rooms/${encodeRoomIdPath(roomId)}/join${createQuery}`, { method: "POST" });
|
|
61
|
+
const joinedRoomId = typeof response.room_id === "string"
|
|
62
|
+
? response.room_id
|
|
63
|
+
: roomId;
|
|
64
|
+
const agentIdentity = await ensureAgentIdentity();
|
|
65
|
+
const room = rememberRoom(toRoomState({
|
|
66
|
+
room_id: joinedRoomId,
|
|
67
|
+
project_id: typeof response.project_id === "string" ? response.project_id : null,
|
|
68
|
+
code: typeof response.code === "string"
|
|
69
|
+
? response.code
|
|
70
|
+
: looksLikeInviteCode(joinedRoomId)
|
|
71
|
+
? joinedRoomId
|
|
72
|
+
: null,
|
|
73
|
+
display_name: typeof response.display_name === "string" ? response.display_name : null,
|
|
74
|
+
git_room: response.git_room ?? null,
|
|
75
|
+
joined_via: joinedVia,
|
|
76
|
+
}));
|
|
77
|
+
await syncRoomPresence(room.room_id, agentIdentity, {
|
|
78
|
+
status: "idle",
|
|
79
|
+
status_text: "available in room",
|
|
80
|
+
});
|
|
81
|
+
return {
|
|
82
|
+
room,
|
|
83
|
+
response: {
|
|
84
|
+
...response,
|
|
85
|
+
room_id: joinedRoomId,
|
|
86
|
+
agent_identity: toPublicAgentIdentity(agentIdentity),
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
catch (error) {
|
|
91
|
+
await maybeHandleRepoRoomAuthRequired(error, roomId);
|
|
92
|
+
if (options.allowCreate === false) {
|
|
93
|
+
throw error;
|
|
94
|
+
}
|
|
95
|
+
if (!isMissingRouteError(error)) {
|
|
96
|
+
throw error;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
if (joinedVia === "join_code") {
|
|
100
|
+
const project = await apiCall(`/projects/join/${encodeURIComponent(roomId)}`);
|
|
101
|
+
const legacyRoomId = typeof project.code === "string"
|
|
102
|
+
? project.code
|
|
103
|
+
: roomId;
|
|
104
|
+
const agentIdentity = await ensureAgentIdentity();
|
|
105
|
+
const room = rememberRoom(toRoomState({
|
|
106
|
+
room_id: legacyRoomId,
|
|
107
|
+
project_id: typeof project.id === "string" ? project.id : null,
|
|
108
|
+
code: typeof project.code === "string" ? project.code : legacyRoomId,
|
|
109
|
+
display_name: typeof project.display_name === "string" ? project.display_name : null,
|
|
110
|
+
git_room: project.git_room ?? null,
|
|
111
|
+
joined_via: joinedVia,
|
|
112
|
+
}));
|
|
113
|
+
await syncRoomPresence(room.room_id, agentIdentity, {
|
|
114
|
+
status: "idle",
|
|
115
|
+
status_text: "available in room",
|
|
116
|
+
});
|
|
117
|
+
return {
|
|
118
|
+
room,
|
|
119
|
+
response: {
|
|
120
|
+
...project,
|
|
121
|
+
room_id: legacyRoomId,
|
|
122
|
+
project_id: typeof project.id === "string" ? project.id : null,
|
|
123
|
+
agent_identity: toPublicAgentIdentity(agentIdentity),
|
|
124
|
+
},
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
const project = await apiCall(`/projects/room/${encodeURIComponent(roomId)}`, { method: "POST" });
|
|
128
|
+
const legacyRoomId = typeof project.name === "string" && project.name.trim()
|
|
129
|
+
? project.name
|
|
130
|
+
: typeof project.code === "string" && project.code.trim()
|
|
131
|
+
? project.code
|
|
132
|
+
: roomId;
|
|
133
|
+
const agentIdentity = await ensureAgentIdentity();
|
|
134
|
+
const room = rememberRoom(toRoomState({
|
|
135
|
+
room_id: legacyRoomId,
|
|
136
|
+
project_id: typeof project.id === "string" ? project.id : null,
|
|
137
|
+
code: typeof project.code === "string"
|
|
138
|
+
? project.code
|
|
139
|
+
: looksLikeInviteCode(legacyRoomId)
|
|
140
|
+
? legacyRoomId
|
|
141
|
+
: null,
|
|
142
|
+
display_name: typeof project.display_name === "string" ? project.display_name : null,
|
|
143
|
+
git_room: project.git_room ?? null,
|
|
144
|
+
joined_via: joinedVia,
|
|
145
|
+
}));
|
|
146
|
+
await syncRoomPresence(room.room_id, agentIdentity, {
|
|
147
|
+
status: "idle",
|
|
148
|
+
status_text: "available in room",
|
|
149
|
+
});
|
|
150
|
+
return {
|
|
151
|
+
room,
|
|
152
|
+
response: {
|
|
153
|
+
...project,
|
|
154
|
+
room_id: legacyRoomId,
|
|
155
|
+
project_id: typeof project.id === "string" ? project.id : null,
|
|
156
|
+
agent_identity: toPublicAgentIdentity(agentIdentity),
|
|
157
|
+
},
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
async function joinExistingRoomIdentifier(identifier, joinedVia) {
|
|
161
|
+
try {
|
|
162
|
+
return await joinRoomIdentifier(identifier, joinedVia, { allowCreate: false });
|
|
163
|
+
}
|
|
164
|
+
catch (error) {
|
|
165
|
+
if (isNotFoundApiError(error)) {
|
|
166
|
+
return null;
|
|
167
|
+
}
|
|
168
|
+
throw error;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
export async function joinRoomIdentifierWithoutImplicitGitRefCreate(identifier, joinedVia, options = {}) {
|
|
172
|
+
const gitRefRoom = parseGeneratedGitRefRoomIdentifier(identifier);
|
|
173
|
+
if (!gitRefRoom) {
|
|
174
|
+
return joinRoomIdentifier(identifier, joinedVia);
|
|
175
|
+
}
|
|
176
|
+
const joined = await joinExistingRoomIdentifier(identifier, joinedVia);
|
|
177
|
+
if (joined) {
|
|
178
|
+
return joined;
|
|
179
|
+
}
|
|
180
|
+
if (options.fallbackToRepo) {
|
|
181
|
+
return joinRoomIdentifier(gitRefRoom.repoRoom, joinedVia);
|
|
182
|
+
}
|
|
183
|
+
throw new ApiError(404, JSON.stringify({
|
|
184
|
+
error: "Room not found",
|
|
185
|
+
code: "ROOM_NOT_FOUND",
|
|
186
|
+
room_id: identifier.trim(),
|
|
187
|
+
}));
|
|
188
|
+
}
|
|
189
|
+
export async function createInviteRoom() {
|
|
190
|
+
const project = await apiCall("/projects", { method: "POST" });
|
|
191
|
+
const roomId = typeof project.code === "string"
|
|
192
|
+
? project.code
|
|
193
|
+
: typeof project.id === "string"
|
|
194
|
+
? project.id
|
|
195
|
+
: "unknown-room";
|
|
196
|
+
const room = rememberRoom(toRoomState({
|
|
197
|
+
room_id: roomId,
|
|
198
|
+
project_id: typeof project.id === "string" ? project.id : null,
|
|
199
|
+
code: typeof project.code === "string" ? project.code : roomId,
|
|
200
|
+
display_name: typeof project.display_name === "string" ? project.display_name : null,
|
|
201
|
+
joined_via: "join_code",
|
|
202
|
+
}));
|
|
203
|
+
const agentIdentity = await ensureAgentIdentity();
|
|
204
|
+
await syncRoomPresence(room.room_id, agentIdentity, {
|
|
205
|
+
status: "idle",
|
|
206
|
+
status_text: "available in room",
|
|
207
|
+
});
|
|
208
|
+
return {
|
|
209
|
+
room,
|
|
210
|
+
response: {
|
|
211
|
+
...toPublicRoomResponse(project, roomId),
|
|
212
|
+
agent_identity: toPublicAgentIdentity(agentIdentity),
|
|
213
|
+
},
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
export async function buildJoinResponse(input) {
|
|
217
|
+
const basePayload = await withAgentIdentity({
|
|
218
|
+
...toPublicRoomResponse(input.joined.response, input.joined.room.room_id),
|
|
219
|
+
joined_via: input.joined_via,
|
|
220
|
+
session_mode: input.session_mode,
|
|
221
|
+
});
|
|
222
|
+
if (input.session_mode === "current") {
|
|
223
|
+
return withJoinRoomAgentPrompt(basePayload);
|
|
224
|
+
}
|
|
225
|
+
const provider = getDefaultManagedAgentProvider();
|
|
226
|
+
const liveSession = await provider.startLocalSession({
|
|
227
|
+
room_id: input.joined.room.room_id,
|
|
228
|
+
room_identifier: input.room_identifier,
|
|
229
|
+
room_code: input.joined.room.code ?? null,
|
|
230
|
+
room_display_name: input.joined.room.display_name ?? null,
|
|
231
|
+
joined_via: input.joined_via,
|
|
232
|
+
cwd: process.cwd(),
|
|
233
|
+
});
|
|
234
|
+
return withJoinRoomAgentPrompt({
|
|
235
|
+
...basePayload,
|
|
236
|
+
...toManagedAgentStartResponse(provider, liveSession),
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
export async function joinInviteCode(code, sessionMode) {
|
|
240
|
+
const joined = await joinRoomIdentifier(code, "join_code");
|
|
241
|
+
return buildJoinResponse({
|
|
242
|
+
joined,
|
|
243
|
+
room_identifier: normalizeInviteCode(code),
|
|
244
|
+
joined_via: "join_code",
|
|
245
|
+
session_mode: sessionMode,
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
export async function joinNamedRoom(name, sessionMode) {
|
|
249
|
+
const joined = await joinRoomIdentifier(name, "join_room");
|
|
250
|
+
return buildJoinResponse({
|
|
251
|
+
joined,
|
|
252
|
+
room_identifier: name.trim(),
|
|
253
|
+
joined_via: "join_room",
|
|
254
|
+
session_mode: sessionMode,
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
function bindWorkerRoomFromContext() {
|
|
258
|
+
const configRoom = getRoomFromConfig();
|
|
259
|
+
if (configRoom) {
|
|
260
|
+
const gitContext = buildActiveGitRoomContext({
|
|
261
|
+
repoRoom: configRoom,
|
|
262
|
+
currentBranch: getGitCurrentBranch(),
|
|
263
|
+
defaultBranch: getGitDefaultBranch(),
|
|
264
|
+
});
|
|
265
|
+
const roomId = gitContext.activeRoom ?? configRoom;
|
|
266
|
+
return {
|
|
267
|
+
room: rememberRoom(toRoomState({
|
|
268
|
+
room_id: roomId,
|
|
269
|
+
display_name: roomId,
|
|
270
|
+
joined_via: "config",
|
|
271
|
+
})),
|
|
272
|
+
source: ".letagents.json",
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
const gitContext = getGitRoomContext();
|
|
276
|
+
if (gitContext.activeRoom) {
|
|
277
|
+
return {
|
|
278
|
+
room: rememberRoom(toRoomState({
|
|
279
|
+
room_id: gitContext.activeRoom,
|
|
280
|
+
display_name: gitContext.activeRoom,
|
|
281
|
+
joined_via: "git-remote",
|
|
282
|
+
})),
|
|
283
|
+
source: "git remote",
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
const savedCurrentRoom = getStoredCurrentRoom();
|
|
287
|
+
if (!savedCurrentRoom) {
|
|
288
|
+
return null;
|
|
289
|
+
}
|
|
290
|
+
return {
|
|
291
|
+
room: rememberRoom(toRoomState({
|
|
292
|
+
room_id: savedCurrentRoom.room_id,
|
|
293
|
+
project_id: savedCurrentRoom.project_id,
|
|
294
|
+
code: savedCurrentRoom.code,
|
|
295
|
+
display_name: savedCurrentRoom.display_name,
|
|
296
|
+
git_room: savedCurrentRoom.git_room,
|
|
297
|
+
joined_via: savedCurrentRoom.joined_via,
|
|
298
|
+
})),
|
|
299
|
+
source: "saved local room state",
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
export async function autoJoinFromContext() {
|
|
303
|
+
try {
|
|
304
|
+
if (requireValidWorkerBearerRuntime().mode === "worker") {
|
|
305
|
+
const bound = bindWorkerRoomFromContext();
|
|
306
|
+
if (bound) {
|
|
307
|
+
console.error(`🏠 Bound worker bearer to room '${bound.room.room_id}' (from ${bound.source}; no join/create request).`);
|
|
308
|
+
}
|
|
309
|
+
else {
|
|
310
|
+
console.error("ℹ️ Worker bearer has no .letagents.json, git remote, or saved room to bind locally.");
|
|
311
|
+
}
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
const configRoom = getRoomFromConfig();
|
|
315
|
+
if (configRoom) {
|
|
316
|
+
const gitContext = buildActiveGitRoomContext({
|
|
317
|
+
repoRoom: configRoom,
|
|
318
|
+
currentBranch: getGitCurrentBranch(),
|
|
319
|
+
defaultBranch: getGitDefaultBranch(),
|
|
320
|
+
});
|
|
321
|
+
if (gitContext.activeRefRoom && gitContext.currentBranch) {
|
|
322
|
+
const joinedBranchRoom = await joinExistingRoomIdentifier(gitContext.activeRefRoom, "config");
|
|
323
|
+
if (joinedBranchRoom) {
|
|
324
|
+
await ensureAgentIdentity();
|
|
325
|
+
console.error(`🏠 Auto-joined existing branch room '${gitContext.activeRefRoom}' (from .letagents.json + branch '${gitContext.currentBranch}')`);
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
await joinRoomIdentifier(configRoom, "config");
|
|
330
|
+
await ensureAgentIdentity();
|
|
331
|
+
const branchNote = gitContext.activeRefRoom && gitContext.currentBranch
|
|
332
|
+
? `; branch '${gitContext.currentBranch}' has no existing Git Room`
|
|
333
|
+
: "";
|
|
334
|
+
console.error(`🏠 Auto-joined room '${configRoom}' (from .letagents.json${branchNote})`);
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
const gitContext = getGitRoomContext();
|
|
338
|
+
if (gitContext.repoRoom) {
|
|
339
|
+
if (gitContext.activeRefRoom && gitContext.currentBranch) {
|
|
340
|
+
const joinedBranchRoom = await joinExistingRoomIdentifier(gitContext.activeRefRoom, "git-remote");
|
|
341
|
+
if (joinedBranchRoom) {
|
|
342
|
+
await ensureAgentIdentity();
|
|
343
|
+
console.error(`🏠 Auto-joined existing branch room '${gitContext.activeRefRoom}' (inferred from git remote and branch '${gitContext.currentBranch}' — consider adding a .letagents.json)`);
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
await joinRoomIdentifier(gitContext.repoRoom, "git-remote");
|
|
348
|
+
await ensureAgentIdentity();
|
|
349
|
+
const branchNote = gitContext.activeRefRoom && gitContext.currentBranch
|
|
350
|
+
? `; branch '${gitContext.currentBranch}' has no existing Git Room`
|
|
351
|
+
: "";
|
|
352
|
+
console.error(`🏠 Auto-joined room '${gitContext.repoRoom}' (inferred from git remote${branchNote} — consider adding a .letagents.json)`);
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
const savedCurrentRoom = getStoredCurrentRoom();
|
|
356
|
+
if (savedCurrentRoom) {
|
|
357
|
+
const joined = await joinRoomIdentifierWithoutImplicitGitRefCreate(savedCurrentRoom.room_id, savedCurrentRoom.joined_via, { fallbackToRepo: true });
|
|
358
|
+
await ensureAgentIdentity();
|
|
359
|
+
const fallbackNote = joined.room.room_id !== savedCurrentRoom.room_id
|
|
360
|
+
? `; saved Git ref room '${savedCurrentRoom.room_id}' was missing`
|
|
361
|
+
: "";
|
|
362
|
+
console.error(`🏠 Rejoined saved room '${joined.room.room_id}' (from local state${fallbackNote})`);
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
console.error("ℹ️ No .letagents.json, git remote, or saved room found — use create_room, join_code, or join_room to connect.");
|
|
366
|
+
}
|
|
367
|
+
catch (err) {
|
|
368
|
+
if (err instanceof RepoRoomAuthRequiredError) {
|
|
369
|
+
console.error(`🔐 Repo room auth required for '${err.roomId}'. Open ${err.pendingAuth.verification_uri} and enter code ${err.pendingAuth.user_code}, then run poll_device_auth.`);
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
console.error("⚠️ Auto-join failed (server still running):", err instanceof Error ? err.message : err);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { createConnection } from "node:net";
|
|
3
|
+
const NEGOTIATION_PROTOCOL_VERSION = 1;
|
|
4
|
+
const SUPPORTED_SUPERVISOR_PROTOCOL_VERSIONS = new Set([1, 2]);
|
|
5
|
+
const DEFAULT_REQUEST_TIMEOUT_MS = 5_000;
|
|
6
|
+
/** Bind the exact worker credential minted by registration to its daemon lane. */
|
|
7
|
+
export async function bindSupervisedWorkerSession(session, env = process.env, options = {}) {
|
|
8
|
+
const entryId = env.LETAGENTS_SUPERVISOR_ENTRY_ID?.trim();
|
|
9
|
+
const socketPath = env.LETAGENTS_SUPERVISOR_DAEMON_SOCKET?.trim();
|
|
10
|
+
const workAttemptId = env.LETAGENTS_SUPERVISOR_WORK_ATTEMPT_ID?.trim();
|
|
11
|
+
const executionGenerationId = env.LETAGENTS_SUPERVISOR_EXECUTION_GENERATION_ID?.trim();
|
|
12
|
+
if (!entryId && !socketPath && !workAttemptId && !executionGenerationId)
|
|
13
|
+
return false;
|
|
14
|
+
if (!entryId || !socketPath || !workAttemptId || !executionGenerationId)
|
|
15
|
+
throw new Error("Supervised worker bridge environment is incomplete.");
|
|
16
|
+
if (session.session_kind !== "worker")
|
|
17
|
+
throw new Error("A supervised provider must register a worker session.");
|
|
18
|
+
const timeoutMs = options.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
|
|
19
|
+
if (!Number.isSafeInteger(timeoutMs) || timeoutMs < 1)
|
|
20
|
+
throw new Error("Supervisor bridge timeout must be a positive integer.");
|
|
21
|
+
const negotiation = await supervisorRequest(socketPath, {
|
|
22
|
+
version: NEGOTIATION_PROTOCOL_VERSION,
|
|
23
|
+
id: randomUUID(),
|
|
24
|
+
method: "daemon.negotiate",
|
|
25
|
+
}, timeoutMs);
|
|
26
|
+
if (!negotiation.ok)
|
|
27
|
+
throw new Error(negotiation.error || "Supervisor protocol negotiation failed.");
|
|
28
|
+
const protocolVersion = negotiationProtocolVersion(negotiation.result);
|
|
29
|
+
if (negotiation.version !== protocolVersion)
|
|
30
|
+
throw new Error("Supervisor negotiation response version does not match its negotiated protocol.");
|
|
31
|
+
const response = await supervisorRequest(socketPath, {
|
|
32
|
+
version: protocolVersion,
|
|
33
|
+
id: randomUUID(),
|
|
34
|
+
method: "supervisor.bind_worker_session",
|
|
35
|
+
params: {
|
|
36
|
+
entry_id: entryId,
|
|
37
|
+
room_id: session.room_id,
|
|
38
|
+
work_attempt_id: workAttemptId,
|
|
39
|
+
execution_generation_id: executionGenerationId,
|
|
40
|
+
agent_session_id: session.session_id,
|
|
41
|
+
agent_session_token: session.session_token,
|
|
42
|
+
api_url: env.LETAGENTS_API_URL?.trim() || "https://letagents.chat",
|
|
43
|
+
},
|
|
44
|
+
}, timeoutMs);
|
|
45
|
+
if (!response.ok)
|
|
46
|
+
throw new Error(response.error || "Supervisor rejected the worker session binding.");
|
|
47
|
+
if (response.version !== protocolVersion)
|
|
48
|
+
throw new Error("Supervisor binding response used an unexpected protocol version.");
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
function negotiationProtocolVersion(result) {
|
|
52
|
+
if (!result || typeof result !== "object")
|
|
53
|
+
throw new Error("Supervisor protocol negotiation returned a malformed result.");
|
|
54
|
+
const protocolVersion = result.protocol_version;
|
|
55
|
+
if (typeof protocolVersion !== "number" || !Number.isSafeInteger(protocolVersion)
|
|
56
|
+
|| !SUPPORTED_SUPERVISOR_PROTOCOL_VERSIONS.has(protocolVersion)) {
|
|
57
|
+
throw new Error(`Supervisor protocol negotiation returned unsupported version ${String(protocolVersion)}.`);
|
|
58
|
+
}
|
|
59
|
+
return protocolVersion;
|
|
60
|
+
}
|
|
61
|
+
function supervisorRequest(socketPath, request, timeoutMs) {
|
|
62
|
+
return new Promise((resolve, reject) => {
|
|
63
|
+
const socket = createConnection(socketPath);
|
|
64
|
+
let buffer = "";
|
|
65
|
+
const timer = setTimeout(() => {
|
|
66
|
+
socket.destroy();
|
|
67
|
+
reject(new Error("Timed out communicating with the supervisor daemon."));
|
|
68
|
+
}, timeoutMs);
|
|
69
|
+
timer.unref();
|
|
70
|
+
const finish = (operation) => {
|
|
71
|
+
clearTimeout(timer);
|
|
72
|
+
operation();
|
|
73
|
+
};
|
|
74
|
+
socket.setEncoding("utf8");
|
|
75
|
+
socket.once("error", (error) => finish(() => reject(error)));
|
|
76
|
+
socket.on("data", (chunk) => {
|
|
77
|
+
buffer += chunk;
|
|
78
|
+
const newline = buffer.indexOf("\n");
|
|
79
|
+
if (newline < 0)
|
|
80
|
+
return;
|
|
81
|
+
socket.end();
|
|
82
|
+
try {
|
|
83
|
+
const response = JSON.parse(buffer.slice(0, newline));
|
|
84
|
+
finish(() => response.id === request.id
|
|
85
|
+
? resolve(response)
|
|
86
|
+
: reject(new Error("Supervisor response id does not match its request.")));
|
|
87
|
+
}
|
|
88
|
+
catch (error) {
|
|
89
|
+
finish(() => reject(error));
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
socket.once("connect", () => socket.write(`${JSON.stringify(request)}\n`));
|
|
93
|
+
});
|
|
94
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
export const LETAGENTS_AGENT_SESSION_BEARER_ENV = "LETAGENTS_AGENT_SESSION_BEARER";
|
|
2
|
+
export class WorkerBearerRuntimeConfigurationError extends Error {
|
|
3
|
+
constructor(message) {
|
|
4
|
+
super(message);
|
|
5
|
+
this.name = "WorkerBearerRuntimeConfigurationError";
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
export function getWorkerBearerRuntime() {
|
|
9
|
+
const bearer = process.env.LETAGENTS_AGENT_SESSION_BEARER?.trim();
|
|
10
|
+
if (!bearer) {
|
|
11
|
+
return { mode: "owner" };
|
|
12
|
+
}
|
|
13
|
+
const apiUrl = process.env.LETAGENTS_API_URL?.trim();
|
|
14
|
+
if (!apiUrl) {
|
|
15
|
+
return {
|
|
16
|
+
mode: "invalid",
|
|
17
|
+
error: "Worker bearer mode requires an explicit LETAGENTS_API_URL.",
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
try {
|
|
21
|
+
const parsed = new URL(apiUrl);
|
|
22
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
23
|
+
throw new Error("unsupported protocol");
|
|
24
|
+
}
|
|
25
|
+
const loopbackHosts = new Set(["localhost", "127.0.0.1", "::1", "[::1]"]);
|
|
26
|
+
if (parsed.protocol === "http:" && !loopbackHosts.has(parsed.hostname.toLowerCase())) {
|
|
27
|
+
return {
|
|
28
|
+
mode: "invalid",
|
|
29
|
+
error: "Worker bearer mode requires HTTPS unless LETAGENTS_API_URL uses an exact loopback host.",
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return {
|
|
35
|
+
mode: "invalid",
|
|
36
|
+
error: "Worker bearer mode requires LETAGENTS_API_URL to be a valid HTTP(S) URL.",
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
if (process.env.LETAGENTS_TOKEN?.trim()) {
|
|
40
|
+
return {
|
|
41
|
+
mode: "invalid",
|
|
42
|
+
error: "Worker bearer mode refuses LETAGENTS_TOKEN. Remove the owner token from this process before starting the worker.",
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
return { mode: "worker", bearer };
|
|
46
|
+
}
|
|
47
|
+
export function requireValidWorkerBearerRuntime() {
|
|
48
|
+
const runtime = getWorkerBearerRuntime();
|
|
49
|
+
if (runtime.mode === "invalid") {
|
|
50
|
+
throw new WorkerBearerRuntimeConfigurationError(runtime.error);
|
|
51
|
+
}
|
|
52
|
+
return runtime;
|
|
53
|
+
}
|
|
54
|
+
export function workerModeDisabledToolResult(toolDescription = "This owner-auth onboarding tool") {
|
|
55
|
+
const runtime = getWorkerBearerRuntime();
|
|
56
|
+
if (runtime.mode === "invalid") {
|
|
57
|
+
return { success: false, error: "worker_bearer_configuration_invalid", message: runtime.error };
|
|
58
|
+
}
|
|
59
|
+
if (runtime.mode === "worker") {
|
|
60
|
+
return {
|
|
61
|
+
success: false,
|
|
62
|
+
error: "worker_bearer_mode",
|
|
63
|
+
message: `${toolDescription} is disabled while LETAGENTS_AGENT_SESSION_BEARER is configured.`,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// Compatibility facade for MCP server runtime helpers. The implementation lives
|
|
2
|
+
// in src/mcp/server/runtime/* so tool modules can import focused responsibilities
|
|
3
|
+
// without turning this file back into the runtime god module.
|
|
4
|
+
export { API_URL, ApiError, apiCall, getAuthorizationHeader, getLetagentsToken, isMissingRouteError, parseApiErrorPayload, resolveApiPath, } from "./runtime/api.js";
|
|
5
|
+
export { clearAuthenticatedAccountCache, getAuthenticatedAccountCache, setAuthenticatedAccountCache, } from "./runtime/auth-cache.js";
|
|
6
|
+
export { RepoRoomAuthRequiredError, maybeHandleRepoRoomAuthRequired, startPendingDeviceAuth, toRepoRoomAuthRequiredResult, } from "./runtime/device-auth.js";
|
|
7
|
+
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";
|
|
11
|
+
export { getRememberedRoomPresence, heartbeatRoomPresence, syncRoomPresence, } from "./runtime/presence.js";
|
|
12
|
+
export { roomScopedApiCall } from "./runtime/room-api.js";
|
|
13
|
+
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";
|