letagents 0.8.0 → 0.8.1
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.js +1 -0
- package/dist/mcp/server.js +150 -40
- package/package.json +1 -1
package/dist/mcp/local-state.js
CHANGED
|
@@ -103,6 +103,7 @@ export function saveRoomSession(input) {
|
|
|
103
103
|
room_id: input.room_id,
|
|
104
104
|
project_id: input.project_id ?? existing?.project_id ?? null,
|
|
105
105
|
code: input.code ?? existing?.code ?? null,
|
|
106
|
+
display_name: input.display_name ?? existing?.display_name ?? null,
|
|
106
107
|
joined_via: input.joined_via,
|
|
107
108
|
joined_at: existing?.joined_at ?? now,
|
|
108
109
|
last_seen_at: now,
|
package/dist/mcp/server.js
CHANGED
|
@@ -64,6 +64,16 @@ class ApiError extends Error {
|
|
|
64
64
|
this.body = body;
|
|
65
65
|
}
|
|
66
66
|
}
|
|
67
|
+
class RepoRoomAuthRequiredError extends Error {
|
|
68
|
+
roomId;
|
|
69
|
+
pendingAuth;
|
|
70
|
+
constructor(roomId, pendingAuth) {
|
|
71
|
+
super(`Repo room '${roomId}' requires authentication. Device flow started: open ${pendingAuth.verification_uri} and enter code ${pendingAuth.user_code}, then run poll_device_auth.`);
|
|
72
|
+
this.name = "RepoRoomAuthRequiredError";
|
|
73
|
+
this.roomId = roomId;
|
|
74
|
+
this.pendingAuth = pendingAuth;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
67
77
|
function getLetagentsToken() {
|
|
68
78
|
return process.env.LETAGENTS_TOKEN || getStoredAuth()?.token || "";
|
|
69
79
|
}
|
|
@@ -79,6 +89,34 @@ function isMissingRouteError(error) {
|
|
|
79
89
|
(error.status === 404 || error.status === 405) &&
|
|
80
90
|
/Cannot (GET|POST|PATCH)|Not Found|Cannot GET \/rooms|Cannot POST \/rooms/i.test(error.body));
|
|
81
91
|
}
|
|
92
|
+
function parseApiErrorPayload(error) {
|
|
93
|
+
if (!(error instanceof ApiError)) {
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
try {
|
|
97
|
+
const parsed = JSON.parse(error.body);
|
|
98
|
+
return parsed && typeof parsed === "object" ? parsed : null;
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
function resolveApiPath(urlOrPath) {
|
|
105
|
+
if (!urlOrPath) {
|
|
106
|
+
return "/auth/device/start";
|
|
107
|
+
}
|
|
108
|
+
try {
|
|
109
|
+
const parsed = new URL(urlOrPath, `${API_URL}/`);
|
|
110
|
+
const apiBase = new URL(`${API_URL}/`);
|
|
111
|
+
if (parsed.origin !== apiBase.origin) {
|
|
112
|
+
return "/auth/device/start";
|
|
113
|
+
}
|
|
114
|
+
return `${parsed.pathname}${parsed.search}`;
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
return "/auth/device/start";
|
|
118
|
+
}
|
|
119
|
+
}
|
|
82
120
|
async function apiCall(path, options) {
|
|
83
121
|
const headers = {
|
|
84
122
|
"Content-Type": "application/json",
|
|
@@ -107,11 +145,48 @@ async function apiCall(path, options) {
|
|
|
107
145
|
}
|
|
108
146
|
return JSON.parse(body);
|
|
109
147
|
}
|
|
148
|
+
async function startPendingDeviceAuth(roomId, deviceFlowUrl) {
|
|
149
|
+
const existing = getPendingDeviceAuth();
|
|
150
|
+
if (existing?.suggested_room_id === roomId) {
|
|
151
|
+
return existing;
|
|
152
|
+
}
|
|
153
|
+
const response = await apiCall(resolveApiPath(deviceFlowUrl), {
|
|
154
|
+
method: "POST",
|
|
155
|
+
});
|
|
156
|
+
return setPendingDeviceAuth({
|
|
157
|
+
request_id: response.request_id,
|
|
158
|
+
user_code: response.user_code,
|
|
159
|
+
verification_uri: response.verification_uri,
|
|
160
|
+
interval_seconds: response.interval,
|
|
161
|
+
expires_at: new Date(Date.now() + response.expires_in * 1000).toISOString(),
|
|
162
|
+
started_at: new Date().toISOString(),
|
|
163
|
+
suggested_room_id: roomId,
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
async function maybeHandleRepoRoomAuthRequired(error, roomId) {
|
|
167
|
+
const payload = parseApiErrorPayload(error);
|
|
168
|
+
if (!(error instanceof ApiError) || error.status !== 401 || payload?.error !== "auth_required") {
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
const pendingAuth = await startPendingDeviceAuth(roomId, typeof payload.device_flow_url === "string" ? payload.device_flow_url : undefined);
|
|
172
|
+
throw new RepoRoomAuthRequiredError(roomId, pendingAuth);
|
|
173
|
+
}
|
|
174
|
+
function toRepoRoomAuthRequiredResult(error) {
|
|
175
|
+
return {
|
|
176
|
+
success: false,
|
|
177
|
+
error: "auth_required",
|
|
178
|
+
room_id: error.roomId,
|
|
179
|
+
next_step: "poll_device_auth",
|
|
180
|
+
pending_device_auth: error.pendingAuth,
|
|
181
|
+
message: error.message,
|
|
182
|
+
};
|
|
183
|
+
}
|
|
110
184
|
function toRoomState(input) {
|
|
111
185
|
return {
|
|
112
186
|
room_id: input.room_id,
|
|
113
187
|
project_id: input.project_id ?? null,
|
|
114
188
|
code: input.code ?? null,
|
|
189
|
+
display_name: input.display_name ?? null,
|
|
115
190
|
joined_via: input.joined_via,
|
|
116
191
|
};
|
|
117
192
|
}
|
|
@@ -122,6 +197,7 @@ function toPublicRoomState(state) {
|
|
|
122
197
|
return {
|
|
123
198
|
room_id: state.room_id,
|
|
124
199
|
code: state.code ?? null,
|
|
200
|
+
display_name: state.display_name ?? null,
|
|
125
201
|
joined_via: state.joined_via,
|
|
126
202
|
};
|
|
127
203
|
}
|
|
@@ -132,6 +208,7 @@ function toPublicStoredRoomSession(session) {
|
|
|
132
208
|
return {
|
|
133
209
|
room_id: session.room_id,
|
|
134
210
|
code: session.code ?? null,
|
|
211
|
+
display_name: session.display_name ?? null,
|
|
135
212
|
joined_via: session.joined_via,
|
|
136
213
|
joined_at: session.joined_at,
|
|
137
214
|
last_seen_at: session.last_seen_at,
|
|
@@ -151,6 +228,7 @@ function rememberRoom(state, lastMessageId) {
|
|
|
151
228
|
room_id: state.room_id,
|
|
152
229
|
project_id: state.project_id ?? null,
|
|
153
230
|
code: state.code ?? null,
|
|
231
|
+
display_name: state.display_name ?? null,
|
|
154
232
|
joined_via: state.joined_via,
|
|
155
233
|
last_message_id: lastMessageId,
|
|
156
234
|
});
|
|
@@ -192,6 +270,7 @@ async function roomScopedApiCall(input) {
|
|
|
192
270
|
return result;
|
|
193
271
|
}
|
|
194
272
|
catch (error) {
|
|
273
|
+
await maybeHandleRepoRoomAuthRequired(error, input.room_id);
|
|
195
274
|
if (!input.project_id || !isMissingRouteError(error)) {
|
|
196
275
|
throw error;
|
|
197
276
|
}
|
|
@@ -221,6 +300,7 @@ async function joinRoomIdentifier(identifier, joinedVia) {
|
|
|
221
300
|
: looksLikeInviteCode(joinedRoomId)
|
|
222
301
|
? joinedRoomId
|
|
223
302
|
: null,
|
|
303
|
+
display_name: typeof response.display_name === "string" ? response.display_name : null,
|
|
224
304
|
joined_via: joinedVia,
|
|
225
305
|
}));
|
|
226
306
|
return {
|
|
@@ -229,6 +309,7 @@ async function joinRoomIdentifier(identifier, joinedVia) {
|
|
|
229
309
|
};
|
|
230
310
|
}
|
|
231
311
|
catch (error) {
|
|
312
|
+
await maybeHandleRepoRoomAuthRequired(error, roomId);
|
|
232
313
|
if (!isMissingRouteError(error)) {
|
|
233
314
|
throw error;
|
|
234
315
|
}
|
|
@@ -242,6 +323,7 @@ async function joinRoomIdentifier(identifier, joinedVia) {
|
|
|
242
323
|
room_id: legacyRoomId,
|
|
243
324
|
project_id: typeof project.id === "string" ? project.id : null,
|
|
244
325
|
code: typeof project.code === "string" ? project.code : legacyRoomId,
|
|
326
|
+
display_name: typeof project.display_name === "string" ? project.display_name : null,
|
|
245
327
|
joined_via: joinedVia,
|
|
246
328
|
}));
|
|
247
329
|
return {
|
|
@@ -267,6 +349,7 @@ async function joinRoomIdentifier(identifier, joinedVia) {
|
|
|
267
349
|
: looksLikeInviteCode(legacyRoomId)
|
|
268
350
|
? legacyRoomId
|
|
269
351
|
: null,
|
|
352
|
+
display_name: typeof project.display_name === "string" ? project.display_name : null,
|
|
270
353
|
joined_via: joinedVia,
|
|
271
354
|
}));
|
|
272
355
|
return {
|
|
@@ -289,6 +372,7 @@ async function createInviteRoom() {
|
|
|
289
372
|
room_id: roomId,
|
|
290
373
|
project_id: typeof project.id === "string" ? project.id : null,
|
|
291
374
|
code: typeof project.code === "string" ? project.code : roomId,
|
|
375
|
+
display_name: typeof project.display_name === "string" ? project.display_name : null,
|
|
292
376
|
joined_via: "join_code",
|
|
293
377
|
}));
|
|
294
378
|
await autoRegisterAgentIdentity();
|
|
@@ -418,42 +502,49 @@ server.tool("join_project", "Legacy alias for join_code. Join an existing room u
|
|
|
418
502
|
server.tool("join_room", "Join a named room on Let Agents Chat. Creates the room if it doesn't exist. Use this for repo-based room joining.", {
|
|
419
503
|
name: z.string().describe("The room name to join (e.g. 'github.com/owner/repo')"),
|
|
420
504
|
}, async ({ name }) => {
|
|
421
|
-
|
|
422
|
-
content: [
|
|
423
|
-
{
|
|
424
|
-
type: "text",
|
|
425
|
-
text: JSON.stringify(await joinNamedRoom(name), null, 2),
|
|
426
|
-
},
|
|
427
|
-
],
|
|
428
|
-
};
|
|
429
|
-
});
|
|
430
|
-
// -- get_current_room -------------------------------------------------------
|
|
431
|
-
server.tool("get_current_room", "Get information about the currently joined room, including how it was joined.", {}, async () => {
|
|
432
|
-
if (!currentRoom) {
|
|
505
|
+
try {
|
|
433
506
|
return {
|
|
434
507
|
content: [
|
|
435
508
|
{
|
|
436
509
|
type: "text",
|
|
437
|
-
text: JSON.stringify(
|
|
510
|
+
text: JSON.stringify(await joinNamedRoom(name), null, 2),
|
|
438
511
|
},
|
|
439
512
|
],
|
|
440
513
|
};
|
|
441
514
|
}
|
|
515
|
+
catch (error) {
|
|
516
|
+
if (error instanceof RepoRoomAuthRequiredError) {
|
|
517
|
+
return {
|
|
518
|
+
content: [
|
|
519
|
+
{
|
|
520
|
+
type: "text",
|
|
521
|
+
text: JSON.stringify(toRepoRoomAuthRequiredResult(error), null, 2),
|
|
522
|
+
},
|
|
523
|
+
],
|
|
524
|
+
};
|
|
525
|
+
}
|
|
526
|
+
throw error;
|
|
527
|
+
}
|
|
528
|
+
});
|
|
529
|
+
// -- get_current_room -------------------------------------------------------
|
|
530
|
+
server.tool("get_current_room", "Get information about the currently joined room, including how it was joined.", {}, async () => {
|
|
442
531
|
return {
|
|
443
532
|
content: [
|
|
444
533
|
{
|
|
445
534
|
type: "text",
|
|
446
|
-
text: JSON.stringify(
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
535
|
+
text: JSON.stringify(currentRoom
|
|
536
|
+
? {
|
|
537
|
+
connected: true,
|
|
538
|
+
...toPublicRoomState(currentRoom),
|
|
539
|
+
auth: getStoredAuth()
|
|
540
|
+
? {
|
|
541
|
+
source: process.env.LETAGENTS_TOKEN ? "env" : "local_state",
|
|
542
|
+
expires_at: getStoredAuth()?.expires_at ?? null,
|
|
543
|
+
account: getStoredAuth()?.account ?? null,
|
|
544
|
+
}
|
|
545
|
+
: null,
|
|
546
|
+
}
|
|
547
|
+
: { connected: false, message: "Not currently in any room" }, null, 2),
|
|
457
548
|
},
|
|
458
549
|
],
|
|
459
550
|
};
|
|
@@ -1211,22 +1302,37 @@ server.tool("resume_room_session", "Rejoin the last locally saved room context,
|
|
|
1211
1302
|
],
|
|
1212
1303
|
};
|
|
1213
1304
|
}
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1305
|
+
try {
|
|
1306
|
+
const joined = await joinRoomIdentifier(savedRoom.room_id, savedRoom.joined_via);
|
|
1307
|
+
await autoRegisterAgentIdentity();
|
|
1308
|
+
return {
|
|
1309
|
+
content: [
|
|
1310
|
+
{
|
|
1311
|
+
type: "text",
|
|
1312
|
+
text: JSON.stringify({
|
|
1313
|
+
success: true,
|
|
1314
|
+
rejoined_from_local_state: true,
|
|
1315
|
+
server_session_resumed: false,
|
|
1316
|
+
last_message_id_before_restart: savedRoom.last_message_id ?? null,
|
|
1317
|
+
room: joined.room,
|
|
1318
|
+
}, null, 2),
|
|
1319
|
+
},
|
|
1320
|
+
],
|
|
1321
|
+
};
|
|
1322
|
+
}
|
|
1323
|
+
catch (error) {
|
|
1324
|
+
if (error instanceof RepoRoomAuthRequiredError) {
|
|
1325
|
+
return {
|
|
1326
|
+
content: [
|
|
1327
|
+
{
|
|
1328
|
+
type: "text",
|
|
1329
|
+
text: JSON.stringify(toRepoRoomAuthRequiredResult(error), null, 2),
|
|
1330
|
+
},
|
|
1331
|
+
],
|
|
1332
|
+
};
|
|
1333
|
+
}
|
|
1334
|
+
throw error;
|
|
1335
|
+
}
|
|
1230
1336
|
});
|
|
1231
1337
|
// -- check_repo_visibility --------------------------------------------------
|
|
1232
1338
|
server.tool("check_repo_visibility", "Auto-detect the current repo's git remote and check if it's public or private. Returns the canonical key, provider, visibility, and suggested room type (discoverable for public, invite for private/unknown). Useful for deciding whether to auto-join a discoverable room or create an invite room.", {
|
|
@@ -1293,6 +1399,10 @@ async function main() {
|
|
|
1293
1399
|
console.error("ℹ️ No .letagents.json, git remote, or saved room found — use create_room, join_code, or join_room to connect.");
|
|
1294
1400
|
}
|
|
1295
1401
|
catch (err) {
|
|
1402
|
+
if (err instanceof RepoRoomAuthRequiredError) {
|
|
1403
|
+
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.`);
|
|
1404
|
+
return;
|
|
1405
|
+
}
|
|
1296
1406
|
// Auto-join failure should never block the MCP server
|
|
1297
1407
|
console.error("⚠️ Auto-join failed (server still running):", err instanceof Error ? err.message : err);
|
|
1298
1408
|
}
|