letagents 0.8.1 → 0.10.0
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 +94 -11
- package/dist/mcp/room-id.js +3 -0
- package/dist/mcp/server.js +814 -79
- package/dist/shared/agent-identity.js +104 -0
- package/package.json +2 -1
package/dist/mcp/server.js
CHANGED
|
@@ -2,25 +2,593 @@
|
|
|
2
2
|
import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
3
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
4
|
import { z } from "zod";
|
|
5
|
+
import { createHash } from "crypto";
|
|
5
6
|
import { writeFileSync, existsSync } from "fs";
|
|
7
|
+
import { userInfo } from "os";
|
|
6
8
|
import { join, dirname } from "path";
|
|
7
9
|
import { execSync } from "child_process";
|
|
8
10
|
import { SseClient } from "./sse-client.js";
|
|
9
11
|
import { getRoomFromConfig } from "./config-reader.js";
|
|
10
12
|
import { getGitRemoteIdentity } from "./git-remote.js";
|
|
11
|
-
import { clearPendingDeviceAuth, clearStoredAuth, getLocalStatePath, getPendingDeviceAuth, getStoredAuth, getStoredCurrentRoom, getStoredRoomSession, saveRoomSession, setPendingDeviceAuth, setStoredAuth, touchRoomSession, } from "./local-state.js";
|
|
12
|
-
import { encodeRoomIdPath, looksLikeInviteCode, normalizeInviteCode, } from "./room-id.js";
|
|
13
|
+
import { clearPendingDeviceAuth, clearStoredAuth, getLocalStatePath, getPendingDeviceAuth, getStoredAgentIdentity, getStoredAuth, getStoredCurrentRoom, getStoredRoomSession, saveRoomSession, setStoredAgentIdentity, setPendingDeviceAuth, setStoredAuth, touchRoomSession, updateLocalState, } from "./local-state.js";
|
|
14
|
+
import { encodeRoomIdPath, getCanonicalRoomWebPath, looksLikeInviteCode, normalizeInviteCode, } from "./room-id.js";
|
|
15
|
+
import { buildAgentActorLabel, formatOwnerAttribution, inferAgentIdeLabel, toTitleCaseCodename, } from "../shared/agent-identity.js";
|
|
13
16
|
let currentRoom = null;
|
|
17
|
+
let currentAgentIdentityKey = "";
|
|
18
|
+
let currentAgentIdentity = null;
|
|
19
|
+
let currentAuthenticatedAccount = undefined;
|
|
20
|
+
let currentAuthenticatedAccountSource = null;
|
|
21
|
+
let currentAuthenticatedEnvToken = null;
|
|
14
22
|
// ---------------------------------------------------------------------------
|
|
15
23
|
// Config
|
|
16
24
|
// ---------------------------------------------------------------------------
|
|
17
25
|
const API_URL = (process.env.LETAGENTS_API_URL || "http://localhost:3001").replace(/\/+$/, "");
|
|
18
26
|
const AGENT_NAME = (process.env.LETAGENTS_AGENT_NAME || process.env.AGENT_NAME || "").trim();
|
|
19
27
|
const AGENT_DISPLAY_NAME = (process.env.LETAGENTS_AGENT_DISPLAY_NAME || "").trim();
|
|
28
|
+
const AGENT_IDE_LABEL = (process.env.LETAGENTS_AGENT_IDE || process.env.AGENT_IDE || "").trim();
|
|
20
29
|
const AGENT_OWNER_LABEL = (process.env.LETAGENTS_AGENT_OWNER_LABEL || "").trim();
|
|
30
|
+
const EXPLICIT_AGENT_IDENTITY_KEY = getExplicitAgentIdentityStorageKey();
|
|
31
|
+
currentAgentIdentityKey =
|
|
32
|
+
EXPLICIT_AGENT_IDENTITY_KEY ?? getFallbackAgentIdentityNamespaceKey(detectAgentIdeLabel());
|
|
33
|
+
currentAgentIdentity = getStoredAgentIdentity(currentAgentIdentityKey);
|
|
21
34
|
// ---------------------------------------------------------------------------
|
|
22
35
|
// Helpers
|
|
23
36
|
// ---------------------------------------------------------------------------
|
|
37
|
+
function readCommandOutput(command, cwd = process.cwd()) {
|
|
38
|
+
try {
|
|
39
|
+
const output = execSync(command, {
|
|
40
|
+
cwd,
|
|
41
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
42
|
+
encoding: "utf-8",
|
|
43
|
+
}).trim();
|
|
44
|
+
return output || null;
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
function normalizeSlugSegment(input, fallback) {
|
|
51
|
+
const normalized = input
|
|
52
|
+
.normalize("NFKD")
|
|
53
|
+
.replace(/[^\x00-\x7F]/g, "")
|
|
54
|
+
.toLowerCase()
|
|
55
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
56
|
+
.replace(/^-+|-+$/g, "")
|
|
57
|
+
.replace(/-{2,}/g, "-");
|
|
58
|
+
return normalized || fallback;
|
|
59
|
+
}
|
|
60
|
+
function normalizeAgentBaseName(input) {
|
|
61
|
+
return normalizeSlugSegment(input, "agent").replace(/-agent$/, "") || "agent";
|
|
62
|
+
}
|
|
63
|
+
function isCodexRuntime() {
|
|
64
|
+
return Boolean(process.env.CODEX_THREAD_ID ||
|
|
65
|
+
process.env.CODEX_SHELL ||
|
|
66
|
+
process.env.CODEX_CI ||
|
|
67
|
+
process.env.CODEX_INTERNAL_ORIGINATOR_OVERRIDE);
|
|
68
|
+
}
|
|
69
|
+
const AGENT_CODENAMES = [
|
|
70
|
+
"amber",
|
|
71
|
+
"anchor",
|
|
72
|
+
"autumn",
|
|
73
|
+
"badger",
|
|
74
|
+
"bay",
|
|
75
|
+
"bear",
|
|
76
|
+
"brook",
|
|
77
|
+
"calm",
|
|
78
|
+
"canyon",
|
|
79
|
+
"cedar",
|
|
80
|
+
"clear",
|
|
81
|
+
"cloud",
|
|
82
|
+
"comet",
|
|
83
|
+
"copper",
|
|
84
|
+
"creek",
|
|
85
|
+
"crisp",
|
|
86
|
+
"crest",
|
|
87
|
+
"dawn",
|
|
88
|
+
"delta",
|
|
89
|
+
"dune",
|
|
90
|
+
"ember",
|
|
91
|
+
"falcon",
|
|
92
|
+
"fern",
|
|
93
|
+
"field",
|
|
94
|
+
"firefly",
|
|
95
|
+
"fjord",
|
|
96
|
+
"forest",
|
|
97
|
+
"fox",
|
|
98
|
+
"garden",
|
|
99
|
+
"glade",
|
|
100
|
+
"golden",
|
|
101
|
+
"granite",
|
|
102
|
+
"grove",
|
|
103
|
+
"harbor",
|
|
104
|
+
"hawk",
|
|
105
|
+
"hollow",
|
|
106
|
+
"indigo",
|
|
107
|
+
"ivory",
|
|
108
|
+
"jade",
|
|
109
|
+
"juniper",
|
|
110
|
+
"lagoon",
|
|
111
|
+
"lake",
|
|
112
|
+
"lantern",
|
|
113
|
+
"leaf",
|
|
114
|
+
"lively",
|
|
115
|
+
"lunar",
|
|
116
|
+
"lynx",
|
|
117
|
+
"maple",
|
|
118
|
+
"marsh",
|
|
119
|
+
"meadow",
|
|
120
|
+
"mesa",
|
|
121
|
+
"misty",
|
|
122
|
+
"moon",
|
|
123
|
+
"morrow",
|
|
124
|
+
"moss",
|
|
125
|
+
"noble",
|
|
126
|
+
"oak",
|
|
127
|
+
"olive",
|
|
128
|
+
"opal",
|
|
129
|
+
"otter",
|
|
130
|
+
"owl",
|
|
131
|
+
"peak",
|
|
132
|
+
"pearl",
|
|
133
|
+
"pine",
|
|
134
|
+
"quiet",
|
|
135
|
+
"raven",
|
|
136
|
+
"reef",
|
|
137
|
+
"ridge",
|
|
138
|
+
"river",
|
|
139
|
+
"rook",
|
|
140
|
+
"sage",
|
|
141
|
+
"scarlet",
|
|
142
|
+
"shore",
|
|
143
|
+
"silver",
|
|
144
|
+
"sky",
|
|
145
|
+
"solar",
|
|
146
|
+
"sparrow",
|
|
147
|
+
"spring",
|
|
148
|
+
"star",
|
|
149
|
+
"stone",
|
|
150
|
+
"storm",
|
|
151
|
+
"summit",
|
|
152
|
+
"sun",
|
|
153
|
+
"sunlit",
|
|
154
|
+
"swift",
|
|
155
|
+
"thicket",
|
|
156
|
+
"tidal",
|
|
157
|
+
"timber",
|
|
158
|
+
"trail",
|
|
159
|
+
"valley",
|
|
160
|
+
"verdant",
|
|
161
|
+
"vista",
|
|
162
|
+
"warm",
|
|
163
|
+
"wave",
|
|
164
|
+
"west",
|
|
165
|
+
"wild",
|
|
166
|
+
"willow",
|
|
167
|
+
"wind",
|
|
168
|
+
"winter",
|
|
169
|
+
"wolf",
|
|
170
|
+
"wood",
|
|
171
|
+
"wren",
|
|
172
|
+
];
|
|
173
|
+
const AGENT_CODENAME_SPACE = AGENT_CODENAMES.length * AGENT_CODENAMES.length;
|
|
174
|
+
const AGENT_IDENTITY_SLOT_SUFFIX = ":slot:";
|
|
175
|
+
function getExplicitAgentIdentityStorageKey() {
|
|
176
|
+
const runtimeSignals = [
|
|
177
|
+
process.env.LETAGENTS_AGENT_INSTANCE_ID,
|
|
178
|
+
process.env.CODEX_THREAD_ID && `codex:${process.env.CODEX_THREAD_ID}`,
|
|
179
|
+
process.env.ANTIGRAVITY_THREAD_ID && `antigravity:${process.env.ANTIGRAVITY_THREAD_ID}`,
|
|
180
|
+
process.env.CLAUDECODE_SESSION_ID && `claude:${process.env.CLAUDECODE_SESSION_ID}`,
|
|
181
|
+
process.env.MCP_SESSION_ID && `mcp:${process.env.MCP_SESSION_ID}`,
|
|
182
|
+
].filter((value) => Boolean(value?.trim()));
|
|
183
|
+
if (runtimeSignals.length) {
|
|
184
|
+
return runtimeSignals[0];
|
|
185
|
+
}
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
188
|
+
function getFallbackAgentIdentityNamespaceKey(ideLabel) {
|
|
189
|
+
return `cwd:${process.cwd()}:ide:${normalizeAgentBaseName(ideLabel)}`;
|
|
190
|
+
}
|
|
191
|
+
function hashStringToIndex(value, modulo) {
|
|
192
|
+
const digest = createHash("sha256").update(value).digest();
|
|
193
|
+
return digest.readUInt16BE(0) % modulo;
|
|
194
|
+
}
|
|
195
|
+
function codenameFromIndex(index) {
|
|
196
|
+
const normalizedIndex = ((index % AGENT_CODENAME_SPACE) + AGENT_CODENAME_SPACE) % AGENT_CODENAME_SPACE;
|
|
197
|
+
const firstIndex = Math.floor(normalizedIndex / AGENT_CODENAMES.length);
|
|
198
|
+
const secondIndex = normalizedIndex % AGENT_CODENAMES.length;
|
|
199
|
+
const first = AGENT_CODENAMES[firstIndex];
|
|
200
|
+
const second = AGENT_CODENAMES[secondIndex];
|
|
201
|
+
return {
|
|
202
|
+
name: normalizeAgentBaseName(`${first}-${second}`),
|
|
203
|
+
display_name: `${toTitleCaseCodename(first)} ${toTitleCaseCodename(second)}`,
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
function detectAgentIdeLabel() {
|
|
207
|
+
if (AGENT_IDE_LABEL) {
|
|
208
|
+
return toTitleCaseCodename(AGENT_IDE_LABEL);
|
|
209
|
+
}
|
|
210
|
+
if (isCodexRuntime()) {
|
|
211
|
+
return "Codex";
|
|
212
|
+
}
|
|
213
|
+
const explicitName = normalizeAgentBaseName(AGENT_NAME || AGENT_DISPLAY_NAME);
|
|
214
|
+
const inferred = inferAgentIdeLabel(explicitName);
|
|
215
|
+
return inferred || "Agent";
|
|
216
|
+
}
|
|
217
|
+
function getFallbackSlotPrefix(namespaceKey) {
|
|
218
|
+
return `${namespaceKey}${AGENT_IDENTITY_SLOT_SUFFIX}`;
|
|
219
|
+
}
|
|
220
|
+
function getFallbackSlotKey(namespaceKey, slotIndex) {
|
|
221
|
+
return `${getFallbackSlotPrefix(namespaceKey)}${slotIndex}`;
|
|
222
|
+
}
|
|
223
|
+
function parseFallbackSlotIndex(identityKey, namespaceKey) {
|
|
224
|
+
const prefix = getFallbackSlotPrefix(namespaceKey);
|
|
225
|
+
if (!identityKey.startsWith(prefix)) {
|
|
226
|
+
return null;
|
|
227
|
+
}
|
|
228
|
+
const slotIndex = Number.parseInt(identityKey.slice(prefix.length), 10);
|
|
229
|
+
return Number.isInteger(slotIndex) && slotIndex >= 0 ? slotIndex : null;
|
|
230
|
+
}
|
|
231
|
+
function isProcessAlive(pid) {
|
|
232
|
+
if (!pid || !Number.isInteger(pid) || pid <= 0) {
|
|
233
|
+
return false;
|
|
234
|
+
}
|
|
235
|
+
try {
|
|
236
|
+
process.kill(pid, 0);
|
|
237
|
+
return true;
|
|
238
|
+
}
|
|
239
|
+
catch (error) {
|
|
240
|
+
const err = error;
|
|
241
|
+
return err.code === "EPERM";
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
function claimFallbackIdentityKey(namespaceKey) {
|
|
245
|
+
let claimedKey = getFallbackSlotKey(namespaceKey, 0);
|
|
246
|
+
let claimedIdentity = null;
|
|
247
|
+
const now = new Date().toISOString();
|
|
248
|
+
updateLocalState((state) => {
|
|
249
|
+
const identities = state.agent_identities ?? {};
|
|
250
|
+
const leases = state.agent_identity_leases ?? {};
|
|
251
|
+
const namespacePrefix = getFallbackSlotPrefix(namespaceKey);
|
|
252
|
+
for (const [identityKey, lease] of Object.entries(leases)) {
|
|
253
|
+
if (!identityKey.startsWith(namespacePrefix) || lease.namespace_key !== namespaceKey) {
|
|
254
|
+
continue;
|
|
255
|
+
}
|
|
256
|
+
if (!isProcessAlive(lease.pid)) {
|
|
257
|
+
delete leases[identityKey];
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
const activeLeaseForPid = Object.entries(leases).find(([identityKey, lease]) => identityKey.startsWith(namespacePrefix) &&
|
|
261
|
+
lease.namespace_key === namespaceKey &&
|
|
262
|
+
lease.pid === process.pid);
|
|
263
|
+
if (activeLeaseForPid) {
|
|
264
|
+
const [identityKey, lease] = activeLeaseForPid;
|
|
265
|
+
lease.updated_at = now;
|
|
266
|
+
claimedKey = identityKey;
|
|
267
|
+
claimedIdentity = identities[identityKey] ?? null;
|
|
268
|
+
state.agent_identity_leases = leases;
|
|
269
|
+
return state;
|
|
270
|
+
}
|
|
271
|
+
const slotKeys = new Set();
|
|
272
|
+
for (const identityKey of Object.keys(identities)) {
|
|
273
|
+
if (identityKey.startsWith(namespacePrefix)) {
|
|
274
|
+
slotKeys.add(identityKey);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
for (const identityKey of Object.keys(leases)) {
|
|
278
|
+
if (identityKey.startsWith(namespacePrefix)) {
|
|
279
|
+
slotKeys.add(identityKey);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
const sortedSlotKeys = [...slotKeys].sort((left, right) => {
|
|
283
|
+
const leftIndex = parseFallbackSlotIndex(left, namespaceKey) ?? Number.MAX_SAFE_INTEGER;
|
|
284
|
+
const rightIndex = parseFallbackSlotIndex(right, namespaceKey) ?? Number.MAX_SAFE_INTEGER;
|
|
285
|
+
return leftIndex - rightIndex;
|
|
286
|
+
});
|
|
287
|
+
for (const identityKey of sortedSlotKeys) {
|
|
288
|
+
if (leases[identityKey]) {
|
|
289
|
+
continue;
|
|
290
|
+
}
|
|
291
|
+
leases[identityKey] = {
|
|
292
|
+
namespace_key: namespaceKey,
|
|
293
|
+
pid: process.pid,
|
|
294
|
+
acquired_at: now,
|
|
295
|
+
updated_at: now,
|
|
296
|
+
};
|
|
297
|
+
claimedKey = identityKey;
|
|
298
|
+
claimedIdentity = identities[identityKey] ?? null;
|
|
299
|
+
state.agent_identity_leases = leases;
|
|
300
|
+
return state;
|
|
301
|
+
}
|
|
302
|
+
let nextSlotIndex = 0;
|
|
303
|
+
while (slotKeys.has(getFallbackSlotKey(namespaceKey, nextSlotIndex))) {
|
|
304
|
+
nextSlotIndex += 1;
|
|
305
|
+
}
|
|
306
|
+
claimedKey = getFallbackSlotKey(namespaceKey, nextSlotIndex);
|
|
307
|
+
leases[claimedKey] = {
|
|
308
|
+
namespace_key: namespaceKey,
|
|
309
|
+
pid: process.pid,
|
|
310
|
+
acquired_at: now,
|
|
311
|
+
updated_at: now,
|
|
312
|
+
};
|
|
313
|
+
claimedIdentity = identities[claimedKey] ?? null;
|
|
314
|
+
state.agent_identity_leases = leases;
|
|
315
|
+
return state;
|
|
316
|
+
});
|
|
317
|
+
return {
|
|
318
|
+
identityKey: claimedKey,
|
|
319
|
+
identity: claimedIdentity,
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
function ensureAgentIdentityKey(ideLabel) {
|
|
323
|
+
if (EXPLICIT_AGENT_IDENTITY_KEY) {
|
|
324
|
+
currentAgentIdentityKey = EXPLICIT_AGENT_IDENTITY_KEY;
|
|
325
|
+
currentAgentIdentity = getStoredAgentIdentity(currentAgentIdentityKey);
|
|
326
|
+
return currentAgentIdentityKey;
|
|
327
|
+
}
|
|
328
|
+
const claimed = claimFallbackIdentityKey(getFallbackAgentIdentityNamespaceKey(ideLabel));
|
|
329
|
+
currentAgentIdentityKey = claimed.identityKey;
|
|
330
|
+
currentAgentIdentity = claimed.identity;
|
|
331
|
+
return currentAgentIdentityKey;
|
|
332
|
+
}
|
|
333
|
+
async function getAuthenticatedAgentDirectory() {
|
|
334
|
+
try {
|
|
335
|
+
const result = await apiCall("/agents/me");
|
|
336
|
+
const account = result?.account;
|
|
337
|
+
if (!account?.login?.trim()) {
|
|
338
|
+
return null;
|
|
339
|
+
}
|
|
340
|
+
currentAuthenticatedAccount = account;
|
|
341
|
+
currentAuthenticatedAccountSource = process.env.LETAGENTS_TOKEN?.trim() ? "env" : "stored";
|
|
342
|
+
currentAuthenticatedEnvToken = process.env.LETAGENTS_TOKEN?.trim() || null;
|
|
343
|
+
return {
|
|
344
|
+
account,
|
|
345
|
+
agents: Array.isArray(result?.agents) ? result.agents : [],
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
catch {
|
|
349
|
+
return null;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
function shouldReuseStoredIdentity(identity, identityKey) {
|
|
353
|
+
return Boolean(identity &&
|
|
354
|
+
identity.runtime_key === identityKey &&
|
|
355
|
+
identity.display_name?.trim() &&
|
|
356
|
+
identity.ide_label?.trim() &&
|
|
357
|
+
identity.owner_attribution?.trim());
|
|
358
|
+
}
|
|
359
|
+
function resolveExplicitAgentIdentity() {
|
|
360
|
+
if (AGENT_NAME) {
|
|
361
|
+
const name = normalizeAgentBaseName(AGENT_NAME);
|
|
362
|
+
return {
|
|
363
|
+
name,
|
|
364
|
+
display_name: AGENT_DISPLAY_NAME || toTitleCaseCodename(AGENT_NAME),
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
if (AGENT_DISPLAY_NAME) {
|
|
368
|
+
return {
|
|
369
|
+
name: normalizeAgentBaseName(AGENT_DISPLAY_NAME),
|
|
370
|
+
display_name: AGENT_DISPLAY_NAME.trim(),
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
return null;
|
|
374
|
+
}
|
|
375
|
+
function pickLocalCodename(runtimeKey, offset = 0) {
|
|
376
|
+
const index = hashStringToIndex(runtimeKey, AGENT_CODENAME_SPACE) + offset;
|
|
377
|
+
return codenameFromIndex(index);
|
|
378
|
+
}
|
|
379
|
+
async function resolveAgentName(authAvailable, identityKey) {
|
|
380
|
+
const explicit = resolveExplicitAgentIdentity();
|
|
381
|
+
if (explicit) {
|
|
382
|
+
return explicit;
|
|
383
|
+
}
|
|
384
|
+
if (shouldReuseStoredIdentity(currentAgentIdentity, identityKey)) {
|
|
385
|
+
return {
|
|
386
|
+
name: currentAgentIdentity.name,
|
|
387
|
+
display_name: currentAgentIdentity.display_name,
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
if (!authAvailable) {
|
|
391
|
+
return pickLocalCodename(identityKey);
|
|
392
|
+
}
|
|
393
|
+
const directory = await getAuthenticatedAgentDirectory();
|
|
394
|
+
const existingNames = new Set((directory?.agents ?? [])
|
|
395
|
+
.map((agent) => normalizeAgentBaseName(agent.name || ""))
|
|
396
|
+
.filter(Boolean));
|
|
397
|
+
for (let offset = 0; offset < AGENT_CODENAME_SPACE; offset += 1) {
|
|
398
|
+
const candidate = pickLocalCodename(identityKey, offset);
|
|
399
|
+
if (!existingNames.has(candidate.name)) {
|
|
400
|
+
return candidate;
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
const fallbackHash = createHash("sha256")
|
|
404
|
+
.update(identityKey)
|
|
405
|
+
.digest("hex")
|
|
406
|
+
.slice(0, 4);
|
|
407
|
+
const fallback = pickLocalCodename(identityKey);
|
|
408
|
+
return {
|
|
409
|
+
name: `${fallback.name}-${fallbackHash}`,
|
|
410
|
+
display_name: `${fallback.display_name} ${fallbackHash.toUpperCase()}`,
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
async function getAuthenticatedAccountProfile() {
|
|
414
|
+
const envToken = (process.env.LETAGENTS_TOKEN || "").trim();
|
|
415
|
+
if (envToken) {
|
|
416
|
+
if (currentAuthenticatedAccountSource === "env" &&
|
|
417
|
+
currentAuthenticatedEnvToken === envToken &&
|
|
418
|
+
currentAuthenticatedAccount?.login?.trim()) {
|
|
419
|
+
return currentAuthenticatedAccount;
|
|
420
|
+
}
|
|
421
|
+
const directory = await getAuthenticatedAgentDirectory();
|
|
422
|
+
if (directory?.account?.login?.trim()) {
|
|
423
|
+
return directory.account;
|
|
424
|
+
}
|
|
425
|
+
return null;
|
|
426
|
+
}
|
|
427
|
+
const storedAccount = getStoredAuth()?.account;
|
|
428
|
+
if (storedAccount?.login?.trim()) {
|
|
429
|
+
currentAuthenticatedAccount = storedAccount;
|
|
430
|
+
currentAuthenticatedAccountSource = "stored";
|
|
431
|
+
currentAuthenticatedEnvToken = null;
|
|
432
|
+
return storedAccount;
|
|
433
|
+
}
|
|
434
|
+
if (!getLetagentsToken()) {
|
|
435
|
+
currentAuthenticatedAccount = undefined;
|
|
436
|
+
currentAuthenticatedAccountSource = null;
|
|
437
|
+
currentAuthenticatedEnvToken = null;
|
|
438
|
+
return null;
|
|
439
|
+
}
|
|
440
|
+
if (currentAuthenticatedAccountSource === "stored" &&
|
|
441
|
+
currentAuthenticatedAccount?.login?.trim()) {
|
|
442
|
+
return currentAuthenticatedAccount;
|
|
443
|
+
}
|
|
444
|
+
const directory = await getAuthenticatedAgentDirectory();
|
|
445
|
+
if (directory?.account?.login?.trim()) {
|
|
446
|
+
return directory.account;
|
|
447
|
+
}
|
|
448
|
+
return null;
|
|
449
|
+
}
|
|
450
|
+
async function resolveOwnerContext() {
|
|
451
|
+
const account = await getAuthenticatedAccountProfile();
|
|
452
|
+
const authLogin = account?.login?.trim() || null;
|
|
453
|
+
const authLabel = account?.display_name?.trim() || authLogin;
|
|
454
|
+
if (authLogin || authLabel || AGENT_OWNER_LABEL) {
|
|
455
|
+
const label = AGENT_OWNER_LABEL || authLabel || authLogin || "Owner";
|
|
456
|
+
const slug = normalizeSlugSegment(authLogin || label, "owner");
|
|
457
|
+
return { slug, label, login: authLogin };
|
|
458
|
+
}
|
|
459
|
+
const gitUserName = readCommandOutput("git config --get user.name");
|
|
460
|
+
const gitUserEmail = readCommandOutput("git config --get user.email");
|
|
461
|
+
const gitIdentity = gitUserName || gitUserEmail?.split("@")[0] || null;
|
|
462
|
+
if (gitIdentity) {
|
|
463
|
+
return {
|
|
464
|
+
slug: normalizeSlugSegment(gitIdentity, "owner"),
|
|
465
|
+
label: gitIdentity,
|
|
466
|
+
login: null,
|
|
467
|
+
};
|
|
468
|
+
}
|
|
469
|
+
const osIdentity = process.env.USER ||
|
|
470
|
+
process.env.LOGNAME ||
|
|
471
|
+
process.env.USERNAME ||
|
|
472
|
+
(() => {
|
|
473
|
+
try {
|
|
474
|
+
return userInfo().username;
|
|
475
|
+
}
|
|
476
|
+
catch {
|
|
477
|
+
return null;
|
|
478
|
+
}
|
|
479
|
+
})() ||
|
|
480
|
+
"owner";
|
|
481
|
+
return {
|
|
482
|
+
slug: normalizeSlugSegment(osIdentity, "owner"),
|
|
483
|
+
label: osIdentity,
|
|
484
|
+
login: null,
|
|
485
|
+
};
|
|
486
|
+
}
|
|
487
|
+
function sameAgentIdentity(left, right) {
|
|
488
|
+
return Boolean(left &&
|
|
489
|
+
left.name === right.name &&
|
|
490
|
+
left.display_name === right.display_name &&
|
|
491
|
+
left.owner_label === right.owner_label &&
|
|
492
|
+
left.owner_attribution === right.owner_attribution &&
|
|
493
|
+
left.ide_label === right.ide_label &&
|
|
494
|
+
left.actor_label === right.actor_label &&
|
|
495
|
+
left.canonical_key === right.canonical_key &&
|
|
496
|
+
left.runtime_key === right.runtime_key &&
|
|
497
|
+
left.source === right.source);
|
|
498
|
+
}
|
|
499
|
+
function toPublicAgentIdentity(identity) {
|
|
500
|
+
if (!identity) {
|
|
501
|
+
return null;
|
|
502
|
+
}
|
|
503
|
+
return {
|
|
504
|
+
name: identity.name,
|
|
505
|
+
display_name: identity.display_name,
|
|
506
|
+
owner_label: identity.owner_label,
|
|
507
|
+
owner_attribution: identity.owner_attribution ?? formatOwnerAttribution(identity.owner_label),
|
|
508
|
+
ide_label: identity.ide_label ?? inferAgentIdeLabel(identity.display_name) ?? "Agent",
|
|
509
|
+
actor_label: identity.actor_label,
|
|
510
|
+
canonical_key: identity.canonical_key ?? null,
|
|
511
|
+
runtime_key: identity.runtime_key ?? null,
|
|
512
|
+
source: identity.source,
|
|
513
|
+
};
|
|
514
|
+
}
|
|
515
|
+
async function ensureAgentIdentity() {
|
|
516
|
+
const owner = await resolveOwnerContext();
|
|
517
|
+
const authAvailable = Boolean(getLetagentsToken());
|
|
518
|
+
const ideLabel = detectAgentIdeLabel();
|
|
519
|
+
const identityKey = ensureAgentIdentityKey(ideLabel);
|
|
520
|
+
const ownerAttribution = formatOwnerAttribution(owner.label);
|
|
521
|
+
const { name, display_name: displayName } = await resolveAgentName(authAvailable, identityKey);
|
|
522
|
+
const actorLabel = buildAgentActorLabel({
|
|
523
|
+
display_name: displayName,
|
|
524
|
+
owner_label: owner.label,
|
|
525
|
+
ide_label: ideLabel,
|
|
526
|
+
});
|
|
527
|
+
let resolved = {
|
|
528
|
+
name,
|
|
529
|
+
display_name: displayName,
|
|
530
|
+
owner_label: owner.label,
|
|
531
|
+
owner_attribution: ownerAttribution,
|
|
532
|
+
ide_label: ideLabel,
|
|
533
|
+
actor_label: actorLabel,
|
|
534
|
+
canonical_key: owner.login ? `${owner.login}/${name}` : null,
|
|
535
|
+
runtime_key: identityKey,
|
|
536
|
+
source: "local",
|
|
537
|
+
resolved_at: new Date().toISOString(),
|
|
538
|
+
};
|
|
539
|
+
if (currentAgentIdentity &&
|
|
540
|
+
currentAgentIdentity.name === resolved.name &&
|
|
541
|
+
currentAgentIdentity.display_name === resolved.display_name &&
|
|
542
|
+
currentAgentIdentity.owner_label === resolved.owner_label &&
|
|
543
|
+
currentAgentIdentity.owner_attribution === resolved.owner_attribution &&
|
|
544
|
+
currentAgentIdentity.ide_label === resolved.ide_label &&
|
|
545
|
+
currentAgentIdentity.actor_label === resolved.actor_label &&
|
|
546
|
+
currentAgentIdentity.runtime_key === resolved.runtime_key &&
|
|
547
|
+
(!authAvailable || currentAgentIdentity.source === "api")) {
|
|
548
|
+
return currentAgentIdentity;
|
|
549
|
+
}
|
|
550
|
+
if (authAvailable) {
|
|
551
|
+
try {
|
|
552
|
+
const registered = await apiCall("/agents", {
|
|
553
|
+
method: "POST",
|
|
554
|
+
body: JSON.stringify({
|
|
555
|
+
name: resolved.name,
|
|
556
|
+
display_name: resolved.display_name,
|
|
557
|
+
owner_label: resolved.owner_label,
|
|
558
|
+
}),
|
|
559
|
+
});
|
|
560
|
+
resolved = {
|
|
561
|
+
...resolved,
|
|
562
|
+
canonical_key: typeof registered.canonical_key === "string"
|
|
563
|
+
? registered.canonical_key
|
|
564
|
+
: resolved.canonical_key,
|
|
565
|
+
display_name: typeof registered.display_name === "string"
|
|
566
|
+
? registered.display_name
|
|
567
|
+
: resolved.display_name,
|
|
568
|
+
owner_label: typeof registered.owner_label === "string"
|
|
569
|
+
? registered.owner_label
|
|
570
|
+
: resolved.owner_label,
|
|
571
|
+
source: "api",
|
|
572
|
+
};
|
|
573
|
+
resolved.owner_attribution = formatOwnerAttribution(resolved.owner_label);
|
|
574
|
+
resolved.actor_label = buildAgentActorLabel({
|
|
575
|
+
display_name: resolved.display_name,
|
|
576
|
+
owner_label: resolved.owner_label,
|
|
577
|
+
ide_label: resolved.ide_label,
|
|
578
|
+
});
|
|
579
|
+
}
|
|
580
|
+
catch (error) {
|
|
581
|
+
console.error("Agent identity registration failed:", error instanceof Error ? error.message : error);
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
if (!sameAgentIdentity(currentAgentIdentity, resolved)) {
|
|
585
|
+
currentAgentIdentity = setStoredAgentIdentity({
|
|
586
|
+
...resolved,
|
|
587
|
+
resolved_at: new Date().toISOString(),
|
|
588
|
+
}, identityKey);
|
|
589
|
+
}
|
|
590
|
+
return currentAgentIdentity ?? resolved;
|
|
591
|
+
}
|
|
24
592
|
/**
|
|
25
593
|
* Resolve the root of the git repository containing `dir`.
|
|
26
594
|
* Returns null if `dir` is not inside a git repo.
|
|
@@ -136,6 +704,9 @@ async function apiCall(path, options) {
|
|
|
136
704
|
// Only clear on 401 (invalid/expired credential), NOT on 403
|
|
137
705
|
// (valid credential but insufficient permissions, e.g., private repo access)
|
|
138
706
|
clearStoredAuth();
|
|
707
|
+
currentAuthenticatedAccount = undefined;
|
|
708
|
+
currentAuthenticatedAccountSource = null;
|
|
709
|
+
currentAuthenticatedEnvToken = null;
|
|
139
710
|
}
|
|
140
711
|
throw new ApiError(res.status, body);
|
|
141
712
|
}
|
|
@@ -190,22 +761,32 @@ function toRoomState(input) {
|
|
|
190
761
|
joined_via: input.joined_via,
|
|
191
762
|
};
|
|
192
763
|
}
|
|
764
|
+
function getCanonicalRoomWebUrl(roomId) {
|
|
765
|
+
return new URL(getCanonicalRoomWebPath(roomId), `${API_URL}/`).toString();
|
|
766
|
+
}
|
|
767
|
+
function withCanonicalRoomLink(roomId, payload) {
|
|
768
|
+
return {
|
|
769
|
+
...payload,
|
|
770
|
+
room_path: getCanonicalRoomWebPath(roomId),
|
|
771
|
+
room_url: getCanonicalRoomWebUrl(roomId),
|
|
772
|
+
};
|
|
773
|
+
}
|
|
193
774
|
function toPublicRoomState(state) {
|
|
194
775
|
if (!state) {
|
|
195
776
|
return null;
|
|
196
777
|
}
|
|
197
|
-
return {
|
|
778
|
+
return withCanonicalRoomLink(state.room_id, {
|
|
198
779
|
room_id: state.room_id,
|
|
199
780
|
code: state.code ?? null,
|
|
200
781
|
display_name: state.display_name ?? null,
|
|
201
782
|
joined_via: state.joined_via,
|
|
202
|
-
};
|
|
783
|
+
});
|
|
203
784
|
}
|
|
204
785
|
function toPublicStoredRoomSession(session) {
|
|
205
786
|
if (!session) {
|
|
206
787
|
return null;
|
|
207
788
|
}
|
|
208
|
-
return {
|
|
789
|
+
return withCanonicalRoomLink(session.room_id, {
|
|
209
790
|
room_id: session.room_id,
|
|
210
791
|
code: session.code ?? null,
|
|
211
792
|
display_name: session.display_name ?? null,
|
|
@@ -213,15 +794,21 @@ function toPublicStoredRoomSession(session) {
|
|
|
213
794
|
joined_at: session.joined_at,
|
|
214
795
|
last_seen_at: session.last_seen_at,
|
|
215
796
|
last_message_id: session.last_message_id ?? null,
|
|
216
|
-
};
|
|
797
|
+
});
|
|
217
798
|
}
|
|
218
799
|
function toPublicRoomResponse(response, fallbackRoomId) {
|
|
219
800
|
const { id: _legacyId, project_id: _legacyProjectId, ...rest } = response;
|
|
220
801
|
return {
|
|
221
|
-
...rest,
|
|
802
|
+
...withCanonicalRoomLink(typeof rest.room_id === "string" ? rest.room_id : fallbackRoomId, rest),
|
|
222
803
|
room_id: typeof rest.room_id === "string" ? rest.room_id : fallbackRoomId,
|
|
223
804
|
};
|
|
224
805
|
}
|
|
806
|
+
async function withAgentIdentity(payload) {
|
|
807
|
+
return {
|
|
808
|
+
...payload,
|
|
809
|
+
agent_identity: toPublicAgentIdentity(await ensureAgentIdentity()),
|
|
810
|
+
};
|
|
811
|
+
}
|
|
225
812
|
function rememberRoom(state, lastMessageId) {
|
|
226
813
|
currentRoom = state;
|
|
227
814
|
saveRoomSession({
|
|
@@ -303,9 +890,14 @@ async function joinRoomIdentifier(identifier, joinedVia) {
|
|
|
303
890
|
display_name: typeof response.display_name === "string" ? response.display_name : null,
|
|
304
891
|
joined_via: joinedVia,
|
|
305
892
|
}));
|
|
893
|
+
const agentIdentity = await ensureAgentIdentity();
|
|
306
894
|
return {
|
|
307
895
|
room,
|
|
308
|
-
response: {
|
|
896
|
+
response: {
|
|
897
|
+
...response,
|
|
898
|
+
room_id: joinedRoomId,
|
|
899
|
+
agent_identity: toPublicAgentIdentity(agentIdentity),
|
|
900
|
+
},
|
|
309
901
|
};
|
|
310
902
|
}
|
|
311
903
|
catch (error) {
|
|
@@ -326,12 +918,14 @@ async function joinRoomIdentifier(identifier, joinedVia) {
|
|
|
326
918
|
display_name: typeof project.display_name === "string" ? project.display_name : null,
|
|
327
919
|
joined_via: joinedVia,
|
|
328
920
|
}));
|
|
921
|
+
const agentIdentity = await ensureAgentIdentity();
|
|
329
922
|
return {
|
|
330
923
|
room,
|
|
331
924
|
response: {
|
|
332
925
|
...project,
|
|
333
926
|
room_id: legacyRoomId,
|
|
334
927
|
project_id: typeof project.id === "string" ? project.id : null,
|
|
928
|
+
agent_identity: toPublicAgentIdentity(agentIdentity),
|
|
335
929
|
},
|
|
336
930
|
};
|
|
337
931
|
}
|
|
@@ -352,12 +946,14 @@ async function joinRoomIdentifier(identifier, joinedVia) {
|
|
|
352
946
|
display_name: typeof project.display_name === "string" ? project.display_name : null,
|
|
353
947
|
joined_via: joinedVia,
|
|
354
948
|
}));
|
|
949
|
+
const agentIdentity = await ensureAgentIdentity();
|
|
355
950
|
return {
|
|
356
951
|
room,
|
|
357
952
|
response: {
|
|
358
953
|
...project,
|
|
359
954
|
room_id: legacyRoomId,
|
|
360
955
|
project_id: typeof project.id === "string" ? project.id : null,
|
|
956
|
+
agent_identity: toPublicAgentIdentity(agentIdentity),
|
|
361
957
|
},
|
|
362
958
|
};
|
|
363
959
|
}
|
|
@@ -375,45 +971,28 @@ async function createInviteRoom() {
|
|
|
375
971
|
display_name: typeof project.display_name === "string" ? project.display_name : null,
|
|
376
972
|
joined_via: "join_code",
|
|
377
973
|
}));
|
|
378
|
-
await
|
|
974
|
+
const agentIdentity = await ensureAgentIdentity();
|
|
379
975
|
return {
|
|
380
976
|
room,
|
|
381
|
-
response:
|
|
977
|
+
response: {
|
|
978
|
+
...toPublicRoomResponse(project, roomId),
|
|
979
|
+
agent_identity: toPublicAgentIdentity(agentIdentity),
|
|
980
|
+
},
|
|
382
981
|
};
|
|
383
982
|
}
|
|
384
983
|
async function joinInviteCode(code) {
|
|
385
984
|
const joined = await joinRoomIdentifier(code, "join_code");
|
|
386
|
-
|
|
387
|
-
return {
|
|
985
|
+
return withAgentIdentity({
|
|
388
986
|
...toPublicRoomResponse(joined.response, joined.room.room_id),
|
|
389
987
|
joined_via: "join_code",
|
|
390
|
-
};
|
|
988
|
+
});
|
|
391
989
|
}
|
|
392
990
|
async function joinNamedRoom(name) {
|
|
393
991
|
const joined = await joinRoomIdentifier(name, "join_room");
|
|
394
|
-
|
|
395
|
-
return {
|
|
992
|
+
return withAgentIdentity({
|
|
396
993
|
...toPublicRoomResponse(joined.response, joined.room.room_id),
|
|
397
994
|
joined_via: "join_room",
|
|
398
|
-
};
|
|
399
|
-
}
|
|
400
|
-
async function autoRegisterAgentIdentity() {
|
|
401
|
-
if (!AGENT_NAME || !currentRoom) {
|
|
402
|
-
return;
|
|
403
|
-
}
|
|
404
|
-
try {
|
|
405
|
-
await apiCall("/agents", {
|
|
406
|
-
method: "POST",
|
|
407
|
-
body: JSON.stringify({
|
|
408
|
-
name: AGENT_NAME,
|
|
409
|
-
display_name: AGENT_DISPLAY_NAME || AGENT_NAME,
|
|
410
|
-
owner_label: AGENT_OWNER_LABEL || undefined,
|
|
411
|
-
}),
|
|
412
|
-
});
|
|
413
|
-
}
|
|
414
|
-
catch (error) {
|
|
415
|
-
console.error("Agent identity registration failed:", error);
|
|
416
|
-
}
|
|
995
|
+
});
|
|
417
996
|
}
|
|
418
997
|
// ---------------------------------------------------------------------------
|
|
419
998
|
// MCP Server
|
|
@@ -432,18 +1011,35 @@ server.resource("room_messages", new ResourceTemplate("letagents://rooms/{room_i
|
|
|
432
1011
|
const normalizedRoomId = String(room_id);
|
|
433
1012
|
const storedSession = getStoredRoomSession(normalizedRoomId) ??
|
|
434
1013
|
(currentRoom?.room_id === normalizedRoomId ? getStoredCurrentRoom() : null);
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
1014
|
+
// Paginate through all pages to return full message history
|
|
1015
|
+
const allMessages = [];
|
|
1016
|
+
let afterCursor;
|
|
1017
|
+
for (;;) {
|
|
1018
|
+
const query = new URLSearchParams();
|
|
1019
|
+
if (afterCursor)
|
|
1020
|
+
query.set("after", afterCursor);
|
|
1021
|
+
const qs = query.toString();
|
|
1022
|
+
const result = await roomScopedApiCall({
|
|
1023
|
+
room_id: normalizedRoomId,
|
|
1024
|
+
project_id: storedSession?.project_id ?? null,
|
|
1025
|
+
room_path: (targetRoomId) => `/rooms/${encodeRoomIdPath(targetRoomId)}/messages${qs ? `?${qs}` : ""}`,
|
|
1026
|
+
project_path: (projectId) => `/projects/${encodeURIComponent(projectId)}/messages${qs ? `?${qs}` : ""}`,
|
|
1027
|
+
});
|
|
1028
|
+
const msgs = result.messages ?? [];
|
|
1029
|
+
allMessages.push(...msgs);
|
|
1030
|
+
if (!result.has_more || msgs.length === 0)
|
|
1031
|
+
break;
|
|
1032
|
+
const lastMsg = msgs[msgs.length - 1];
|
|
1033
|
+
if (!lastMsg?.id)
|
|
1034
|
+
break;
|
|
1035
|
+
afterCursor = lastMsg.id;
|
|
1036
|
+
}
|
|
441
1037
|
return {
|
|
442
1038
|
contents: [
|
|
443
1039
|
{
|
|
444
1040
|
uri: uri.href,
|
|
445
1041
|
mimeType: "application/json",
|
|
446
|
-
text: JSON.stringify(
|
|
1042
|
+
text: JSON.stringify({ messages: allMessages }, null, 2),
|
|
447
1043
|
},
|
|
448
1044
|
],
|
|
449
1045
|
};
|
|
@@ -536,6 +1132,7 @@ server.tool("get_current_room", "Get information about the currently joined room
|
|
|
536
1132
|
? {
|
|
537
1133
|
connected: true,
|
|
538
1134
|
...toPublicRoomState(currentRoom),
|
|
1135
|
+
agent_identity: toPublicAgentIdentity(currentAgentIdentity ?? getStoredAgentIdentity(currentAgentIdentityKey)),
|
|
539
1136
|
auth: getStoredAuth()
|
|
540
1137
|
? {
|
|
541
1138
|
source: process.env.LETAGENTS_TOKEN ? "env" : "local_state",
|
|
@@ -599,13 +1196,16 @@ server.tool("post_status", "Broadcast a lightweight status update to the current
|
|
|
599
1196
|
"Use this to let other agents and humans know what you are currently doing, " +
|
|
600
1197
|
"e.g. 'reviewing PR #2', 'waiting for tests', 'writing WISHLIST.md'. " +
|
|
601
1198
|
"Status updates are distinct from chat messages and can be filtered separately.", {
|
|
602
|
-
sender: z
|
|
1199
|
+
sender: z
|
|
1200
|
+
.string()
|
|
1201
|
+
.optional()
|
|
1202
|
+
.describe("Deprecated override. Agent identity is resolved automatically on room entry."),
|
|
603
1203
|
status: z.string().describe("Short status description (e.g. 'reviewing PR #2', 'idle', 'thinking...')"),
|
|
604
1204
|
room_id: z
|
|
605
1205
|
.string()
|
|
606
1206
|
.optional()
|
|
607
1207
|
.describe("Canonical room ID. Defaults to the current room."),
|
|
608
|
-
}, async ({ sender, status, room_id }) => {
|
|
1208
|
+
}, async ({ sender: _sender, status, room_id }) => {
|
|
609
1209
|
const targetRoomId = getTargetRoomId(room_id);
|
|
610
1210
|
const targetProjectId = getFallbackProjectId();
|
|
611
1211
|
if (!targetRoomId && !targetProjectId) {
|
|
@@ -624,6 +1224,8 @@ server.tool("post_status", "Broadcast a lightweight status update to the current
|
|
|
624
1224
|
}
|
|
625
1225
|
// Status messages use a reserved prefix so the UI (and agents) can distinguish
|
|
626
1226
|
// them from normal chat messages without changing the data model.
|
|
1227
|
+
const identity = await ensureAgentIdentity();
|
|
1228
|
+
const sender = identity.actor_label;
|
|
627
1229
|
const statusText = `[status] ${status}`;
|
|
628
1230
|
const message = await roomScopedApiCall({
|
|
629
1231
|
room_id: targetRoomId,
|
|
@@ -644,6 +1246,7 @@ server.tool("post_status", "Broadcast a lightweight status update to the current
|
|
|
644
1246
|
success: true,
|
|
645
1247
|
status_posted: status,
|
|
646
1248
|
sender,
|
|
1249
|
+
agent_identity: toPublicAgentIdentity(identity),
|
|
647
1250
|
message_id: typeof message.id === "string" ? message.id : null,
|
|
648
1251
|
timestamp: typeof message.timestamp === "string" ? message.timestamp : null,
|
|
649
1252
|
}, null, 2),
|
|
@@ -662,10 +1265,13 @@ server.tool("add_task", "Add a new task to the room board. Tasks normally start
|
|
|
662
1265
|
"work that needs to be done.", {
|
|
663
1266
|
title: z.string().describe("Short task title, e.g. 'Wire up Jest test runner'"),
|
|
664
1267
|
description: z.string().optional().describe("Longer description of what needs to be done"),
|
|
665
|
-
created_by: z
|
|
1268
|
+
created_by: z
|
|
1269
|
+
.string()
|
|
1270
|
+
.optional()
|
|
1271
|
+
.describe("Deprecated override. Agent identity is resolved automatically on room entry."),
|
|
666
1272
|
source_message_id: z.string().optional().describe("Optional message ID where task was agreed, e.g. 'msg_42'"),
|
|
667
1273
|
room_id: z.string().optional().describe("Canonical room ID. Defaults to current room."),
|
|
668
|
-
}, async ({ title, description, created_by, source_message_id, room_id }) => {
|
|
1274
|
+
}, async ({ title, description, created_by: _createdBy, source_message_id, room_id }) => {
|
|
669
1275
|
const targetRoomId = getTargetRoomId(room_id);
|
|
670
1276
|
const targetProjectId = getFallbackProjectId();
|
|
671
1277
|
if (!targetRoomId && !targetProjectId) {
|
|
@@ -673,6 +1279,7 @@ server.tool("add_task", "Add a new task to the room board. Tasks normally start
|
|
|
673
1279
|
content: [{ type: "text", text: JSON.stringify({ success: false, error: "Not in a room. Join one first." }) }],
|
|
674
1280
|
};
|
|
675
1281
|
}
|
|
1282
|
+
const identity = await ensureAgentIdentity();
|
|
676
1283
|
const task = await roomScopedApiCall({
|
|
677
1284
|
room_id: targetRoomId,
|
|
678
1285
|
project_id: targetProjectId,
|
|
@@ -680,11 +1287,21 @@ server.tool("add_task", "Add a new task to the room board. Tasks normally start
|
|
|
680
1287
|
project_path: (targetProjectId) => `/projects/${encodeURIComponent(targetProjectId)}/tasks`,
|
|
681
1288
|
options: {
|
|
682
1289
|
method: "POST",
|
|
683
|
-
body: JSON.stringify({
|
|
1290
|
+
body: JSON.stringify({
|
|
1291
|
+
title,
|
|
1292
|
+
description,
|
|
1293
|
+
created_by: identity.actor_label,
|
|
1294
|
+
source_message_id,
|
|
1295
|
+
}),
|
|
684
1296
|
},
|
|
685
1297
|
});
|
|
686
1298
|
return {
|
|
687
|
-
content: [
|
|
1299
|
+
content: [
|
|
1300
|
+
{
|
|
1301
|
+
type: "text",
|
|
1302
|
+
text: JSON.stringify({ success: true, task, agent_identity: toPublicAgentIdentity(identity) }, null, 2),
|
|
1303
|
+
},
|
|
1304
|
+
],
|
|
688
1305
|
};
|
|
689
1306
|
});
|
|
690
1307
|
server.tool("get_board", "Get the current task board for the room. By default shows only open tasks " +
|
|
@@ -706,24 +1323,43 @@ server.tool("get_board", "Get the current task board for the room. By default sh
|
|
|
706
1323
|
params.set("status", status);
|
|
707
1324
|
if (open_only !== false)
|
|
708
1325
|
params.set("open", "true");
|
|
709
|
-
|
|
710
|
-
const
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
1326
|
+
// Paginate through all pages to return the full board
|
|
1327
|
+
const allTasks = [];
|
|
1328
|
+
let afterCursor;
|
|
1329
|
+
for (;;) {
|
|
1330
|
+
const pageParams = new URLSearchParams(params);
|
|
1331
|
+
if (afterCursor)
|
|
1332
|
+
pageParams.set("after", afterCursor);
|
|
1333
|
+
const qs = pageParams.toString();
|
|
1334
|
+
const result = await roomScopedApiCall({
|
|
1335
|
+
room_id: targetRoomId,
|
|
1336
|
+
project_id: targetProjectId,
|
|
1337
|
+
room_path: (targetRoomId) => `/rooms/${encodeRoomIdPath(targetRoomId)}/tasks${qs ? `?${qs}` : ""}`,
|
|
1338
|
+
project_path: (targetProjectId) => `/projects/${encodeURIComponent(targetProjectId)}/tasks${qs ? `?${qs}` : ""}`,
|
|
1339
|
+
});
|
|
1340
|
+
const tasks = result.tasks ?? [];
|
|
1341
|
+
allTasks.push(...tasks);
|
|
1342
|
+
if (!result.has_more || tasks.length === 0)
|
|
1343
|
+
break;
|
|
1344
|
+
const lastTask = tasks[tasks.length - 1];
|
|
1345
|
+
if (!lastTask?.id)
|
|
1346
|
+
break;
|
|
1347
|
+
afterCursor = lastTask.id;
|
|
1348
|
+
}
|
|
716
1349
|
return {
|
|
717
|
-
content: [{ type: "text", text: JSON.stringify({ success: true,
|
|
1350
|
+
content: [{ type: "text", text: JSON.stringify({ success: true, tasks: allTasks }, null, 2) }],
|
|
718
1351
|
};
|
|
719
1352
|
});
|
|
720
1353
|
server.tool("claim_task", "Claim an accepted task. The task must be in 'accepted' " +
|
|
721
1354
|
"status. This sets the assignee to you and moves the status to 'assigned'. " +
|
|
722
1355
|
"Do NOT claim proposed tasks — they need to be accepted first.", {
|
|
723
1356
|
task_id: z.string().describe("The task ID to claim, e.g. 'task_1'"),
|
|
724
|
-
assignee: z
|
|
1357
|
+
assignee: z
|
|
1358
|
+
.string()
|
|
1359
|
+
.optional()
|
|
1360
|
+
.describe("Deprecated override. Agent identity is resolved automatically on room entry."),
|
|
725
1361
|
room_id: z.string().optional().describe("Canonical room ID. Defaults to current room."),
|
|
726
|
-
}, async ({ task_id, assignee, room_id }) => {
|
|
1362
|
+
}, async ({ task_id, assignee: _assignee, room_id }) => {
|
|
727
1363
|
const targetRoomId = getTargetRoomId(room_id);
|
|
728
1364
|
const targetProjectId = getFallbackProjectId();
|
|
729
1365
|
if (!targetRoomId && !targetProjectId) {
|
|
@@ -732,6 +1368,7 @@ server.tool("claim_task", "Claim an accepted task. The task must be in 'accepted
|
|
|
732
1368
|
};
|
|
733
1369
|
}
|
|
734
1370
|
try {
|
|
1371
|
+
const identity = await ensureAgentIdentity();
|
|
735
1372
|
const updated = await roomScopedApiCall({
|
|
736
1373
|
room_id: targetRoomId,
|
|
737
1374
|
project_id: targetProjectId,
|
|
@@ -739,11 +1376,16 @@ server.tool("claim_task", "Claim an accepted task. The task must be in 'accepted
|
|
|
739
1376
|
project_path: (targetProjectId) => `/projects/${encodeURIComponent(targetProjectId)}/tasks/${encodeURIComponent(task_id)}`,
|
|
740
1377
|
options: {
|
|
741
1378
|
method: "PATCH",
|
|
742
|
-
body: JSON.stringify({ status: "assigned", assignee }),
|
|
1379
|
+
body: JSON.stringify({ status: "assigned", assignee: identity.actor_label }),
|
|
743
1380
|
},
|
|
744
1381
|
});
|
|
745
1382
|
return {
|
|
746
|
-
content: [
|
|
1383
|
+
content: [
|
|
1384
|
+
{
|
|
1385
|
+
type: "text",
|
|
1386
|
+
text: JSON.stringify({ success: true, task: updated, agent_identity: toPublicAgentIdentity(identity) }, null, 2),
|
|
1387
|
+
},
|
|
1388
|
+
],
|
|
747
1389
|
};
|
|
748
1390
|
}
|
|
749
1391
|
catch (error) {
|
|
@@ -757,7 +1399,10 @@ server.tool("update_task", "Update a task's status or assignee. Status transitio
|
|
|
757
1399
|
"but NOT proposed → in_progress).", {
|
|
758
1400
|
task_id: z.string().describe("The task ID to update"),
|
|
759
1401
|
status: z.enum(TASK_STATUSES).optional().describe("New status for the task"),
|
|
760
|
-
assignee: z
|
|
1402
|
+
assignee: z
|
|
1403
|
+
.string()
|
|
1404
|
+
.optional()
|
|
1405
|
+
.describe("New assignee for the task. Defaults to the current agent when status=assigned."),
|
|
761
1406
|
pr_url: z.string().optional().describe("PR URL to link to the task"),
|
|
762
1407
|
room_id: z.string().optional().describe("Canonical room ID. Defaults to current room."),
|
|
763
1408
|
}, async ({ task_id, status, assignee, pr_url, room_id }) => {
|
|
@@ -769,6 +1414,9 @@ server.tool("update_task", "Update a task's status or assignee. Status transitio
|
|
|
769
1414
|
};
|
|
770
1415
|
}
|
|
771
1416
|
try {
|
|
1417
|
+
const identity = status === "assigned" && !assignee
|
|
1418
|
+
? await ensureAgentIdentity()
|
|
1419
|
+
: null;
|
|
772
1420
|
const updated = await roomScopedApiCall({
|
|
773
1421
|
room_id: targetRoomId,
|
|
774
1422
|
project_id: targetProjectId,
|
|
@@ -776,11 +1424,24 @@ server.tool("update_task", "Update a task's status or assignee. Status transitio
|
|
|
776
1424
|
project_path: (targetProjectId) => `/projects/${encodeURIComponent(targetProjectId)}/tasks/${encodeURIComponent(task_id)}`,
|
|
777
1425
|
options: {
|
|
778
1426
|
method: "PATCH",
|
|
779
|
-
body: JSON.stringify({
|
|
1427
|
+
body: JSON.stringify({
|
|
1428
|
+
status,
|
|
1429
|
+
assignee: assignee ?? identity?.actor_label,
|
|
1430
|
+
pr_url,
|
|
1431
|
+
}),
|
|
780
1432
|
},
|
|
781
1433
|
});
|
|
782
1434
|
return {
|
|
783
|
-
content: [
|
|
1435
|
+
content: [
|
|
1436
|
+
{
|
|
1437
|
+
type: "text",
|
|
1438
|
+
text: JSON.stringify({
|
|
1439
|
+
success: true,
|
|
1440
|
+
task: updated,
|
|
1441
|
+
agent_identity: identity ? toPublicAgentIdentity(identity) : null,
|
|
1442
|
+
}, null, 2),
|
|
1443
|
+
},
|
|
1444
|
+
],
|
|
784
1445
|
};
|
|
785
1446
|
}
|
|
786
1447
|
catch (error) {
|
|
@@ -971,14 +1632,18 @@ server.tool("initialize_repo", "Initialize the current repo for Let Agents Chat
|
|
|
971
1632
|
// -- send_message -----------------------------------------------------------
|
|
972
1633
|
server.tool("send_message", "Send a message to a Let Agents Chat room.", {
|
|
973
1634
|
room_id: z.string().optional().describe("Canonical room ID. Defaults to the current room."),
|
|
974
|
-
sender: z
|
|
1635
|
+
sender: z
|
|
1636
|
+
.string()
|
|
1637
|
+
.optional()
|
|
1638
|
+
.describe("Deprecated override. Agent identity is resolved automatically on room entry."),
|
|
975
1639
|
text: z.string().describe("The message text to send"),
|
|
976
|
-
}, async ({ room_id, sender, text }) => {
|
|
1640
|
+
}, async ({ room_id, sender: _sender, text }) => {
|
|
977
1641
|
const targetRoomId = getTargetRoomId(room_id);
|
|
978
1642
|
const targetProjectId = getFallbackProjectId();
|
|
979
1643
|
if (!targetRoomId && !targetProjectId) {
|
|
980
1644
|
throw new Error("No room is currently selected. Join a room first or pass room_id.");
|
|
981
1645
|
}
|
|
1646
|
+
const identity = await ensureAgentIdentity();
|
|
982
1647
|
const message = await roomScopedApiCall({
|
|
983
1648
|
room_id: targetRoomId,
|
|
984
1649
|
project_id: targetProjectId,
|
|
@@ -986,7 +1651,7 @@ server.tool("send_message", "Send a message to a Let Agents Chat room.", {
|
|
|
986
1651
|
project_path: (targetProjectId) => `/projects/${encodeURIComponent(targetProjectId)}/messages`,
|
|
987
1652
|
options: {
|
|
988
1653
|
method: "POST",
|
|
989
|
-
body: JSON.stringify({ sender, text }),
|
|
1654
|
+
body: JSON.stringify({ sender: identity.actor_label, text }),
|
|
990
1655
|
},
|
|
991
1656
|
});
|
|
992
1657
|
touchCurrentRoom(typeof message.id === "string" ? message.id : undefined);
|
|
@@ -994,7 +1659,10 @@ server.tool("send_message", "Send a message to a Let Agents Chat room.", {
|
|
|
994
1659
|
content: [
|
|
995
1660
|
{
|
|
996
1661
|
type: "text",
|
|
997
|
-
text: JSON.stringify(
|
|
1662
|
+
text: JSON.stringify({
|
|
1663
|
+
...message,
|
|
1664
|
+
agent_identity: toPublicAgentIdentity(identity),
|
|
1665
|
+
}, null, 2),
|
|
998
1666
|
},
|
|
999
1667
|
],
|
|
1000
1668
|
};
|
|
@@ -1005,17 +1673,41 @@ server.tool("read_messages", "Read all messages from a Let Agents Chat room.", {
|
|
|
1005
1673
|
}, async ({ room_id }) => {
|
|
1006
1674
|
const targetRoomId = getTargetRoomId(room_id);
|
|
1007
1675
|
const targetProjectId = getFallbackProjectId();
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1676
|
+
// Paginate through all pages to honor the "read all messages" contract
|
|
1677
|
+
const allMessages = [];
|
|
1678
|
+
let afterCursor;
|
|
1679
|
+
let roomIdFromResponse;
|
|
1680
|
+
for (;;) {
|
|
1681
|
+
const query = new URLSearchParams();
|
|
1682
|
+
if (afterCursor)
|
|
1683
|
+
query.set("after", afterCursor);
|
|
1684
|
+
const qs = query.toString();
|
|
1685
|
+
const result = await roomScopedApiCall({
|
|
1686
|
+
room_id: targetRoomId,
|
|
1687
|
+
project_id: targetProjectId,
|
|
1688
|
+
room_path: (targetRoomId) => `/rooms/${encodeRoomIdPath(targetRoomId)}/messages${qs ? `?${qs}` : ""}`,
|
|
1689
|
+
project_path: (targetProjectId) => `/projects/${encodeURIComponent(targetProjectId)}/messages${qs ? `?${qs}` : ""}`,
|
|
1690
|
+
});
|
|
1691
|
+
roomIdFromResponse = roomIdFromResponse || result.room_id || result.project_id;
|
|
1692
|
+
const msgs = result.messages ?? [];
|
|
1693
|
+
allMessages.push(...msgs);
|
|
1694
|
+
if (!result.has_more || msgs.length === 0)
|
|
1695
|
+
break;
|
|
1696
|
+
// Use the last message ID as the cursor for the next page
|
|
1697
|
+
const lastMsg = msgs[msgs.length - 1];
|
|
1698
|
+
if (!lastMsg?.id)
|
|
1699
|
+
break;
|
|
1700
|
+
afterCursor = lastMsg.id;
|
|
1701
|
+
}
|
|
1702
|
+
const output = { messages: allMessages };
|
|
1703
|
+
if (roomIdFromResponse) {
|
|
1704
|
+
output[targetRoomId ? "room_id" : "project_id"] = roomIdFromResponse;
|
|
1705
|
+
}
|
|
1014
1706
|
return {
|
|
1015
1707
|
content: [
|
|
1016
1708
|
{
|
|
1017
1709
|
type: "text",
|
|
1018
|
-
text: JSON.stringify(
|
|
1710
|
+
text: JSON.stringify(output, null, 2),
|
|
1019
1711
|
},
|
|
1020
1712
|
],
|
|
1021
1713
|
};
|
|
@@ -1043,21 +1735,49 @@ server.tool("wait_for_messages", "Wait for new messages in a Let Agents Chat roo
|
|
|
1043
1735
|
params.set("after", after_message_id);
|
|
1044
1736
|
params.set("timeout", String(serverTimeout));
|
|
1045
1737
|
const queryString = params.toString();
|
|
1046
|
-
const
|
|
1738
|
+
const firstResult = await roomScopedApiCall({
|
|
1047
1739
|
room_id: targetRoomId,
|
|
1048
1740
|
project_id: targetProjectId,
|
|
1049
1741
|
room_path: (targetRoomId) => `/rooms/${encodeRoomIdPath(targetRoomId)}/messages/poll?${queryString}`,
|
|
1050
1742
|
project_path: (targetProjectId) => `/projects/${encodeURIComponent(targetProjectId)}/messages/poll?${queryString}`,
|
|
1051
1743
|
options: { signal: AbortSignal.timeout(clientTimeout) },
|
|
1052
1744
|
});
|
|
1745
|
+
const allMessages = [...(firstResult.messages ?? [])];
|
|
1746
|
+
const roomIdFromResponse = firstResult.room_id || firstResult.project_id;
|
|
1747
|
+
// If the immediate response has more pages, paginate through them
|
|
1748
|
+
if (firstResult.has_more && allMessages.length > 0) {
|
|
1749
|
+
let afterCursor = allMessages[allMessages.length - 1]?.id;
|
|
1750
|
+
while (afterCursor) {
|
|
1751
|
+
const pageParams = new URLSearchParams();
|
|
1752
|
+
pageParams.set("after", afterCursor);
|
|
1753
|
+
const qs = pageParams.toString();
|
|
1754
|
+
const page = await roomScopedApiCall({
|
|
1755
|
+
room_id: targetRoomId,
|
|
1756
|
+
project_id: targetProjectId,
|
|
1757
|
+
room_path: (targetRoomId) => `/rooms/${encodeRoomIdPath(targetRoomId)}/messages?${qs}`,
|
|
1758
|
+
project_path: (targetProjectId) => `/projects/${encodeURIComponent(targetProjectId)}/messages?${qs}`,
|
|
1759
|
+
});
|
|
1760
|
+
const msgs = page.messages ?? [];
|
|
1761
|
+
allMessages.push(...msgs);
|
|
1762
|
+
if (!page.has_more || msgs.length === 0)
|
|
1763
|
+
break;
|
|
1764
|
+
afterCursor = msgs[msgs.length - 1]?.id;
|
|
1765
|
+
if (!afterCursor)
|
|
1766
|
+
break;
|
|
1767
|
+
}
|
|
1768
|
+
}
|
|
1769
|
+
const output = { messages: allMessages };
|
|
1770
|
+
if (roomIdFromResponse) {
|
|
1771
|
+
output[targetRoomId ? "room_id" : "project_id"] = roomIdFromResponse;
|
|
1772
|
+
}
|
|
1053
1773
|
if (targetRoomId) {
|
|
1054
|
-
touchRoomSession(targetRoomId, getLastMessageId(
|
|
1774
|
+
touchRoomSession(targetRoomId, getLastMessageId(output));
|
|
1055
1775
|
}
|
|
1056
1776
|
return {
|
|
1057
1777
|
content: [
|
|
1058
1778
|
{
|
|
1059
1779
|
type: "text",
|
|
1060
|
-
text: JSON.stringify(
|
|
1780
|
+
text: JSON.stringify(output, null, 2),
|
|
1061
1781
|
},
|
|
1062
1782
|
],
|
|
1063
1783
|
};
|
|
@@ -1100,6 +1820,7 @@ server.tool("get_onboarding_status", "Inspect local Let Agents MCP auth and room
|
|
|
1100
1820
|
account: storedAuth?.account ?? null,
|
|
1101
1821
|
token_expires_at: storedAuth?.expires_at ?? null,
|
|
1102
1822
|
pending_device_auth: pendingAuth,
|
|
1823
|
+
agent_identity: toPublicAgentIdentity(currentAgentIdentity ?? getStoredAgentIdentity(currentAgentIdentityKey)),
|
|
1103
1824
|
current_room: toPublicRoomState(currentRoom),
|
|
1104
1825
|
saved_current_room: toPublicStoredRoomSession(savedCurrentRoom),
|
|
1105
1826
|
detected_room_from_context: detectedRoom,
|
|
@@ -1222,6 +1943,9 @@ server.tool("poll_device_auth", "Poll a pending GitHub Device Flow request. On s
|
|
|
1222
1943
|
if (result.status === "denied" || result.status === "expired") {
|
|
1223
1944
|
clearPendingDeviceAuth();
|
|
1224
1945
|
clearStoredAuth();
|
|
1946
|
+
currentAuthenticatedAccount = undefined;
|
|
1947
|
+
currentAuthenticatedAccountSource = null;
|
|
1948
|
+
currentAuthenticatedEnvToken = null;
|
|
1225
1949
|
return {
|
|
1226
1950
|
content: [
|
|
1227
1951
|
{
|
|
@@ -1242,6 +1966,9 @@ server.tool("poll_device_auth", "Poll a pending GitHub Device Flow request. On s
|
|
|
1242
1966
|
stored_at: new Date().toISOString(),
|
|
1243
1967
|
source: "device_flow",
|
|
1244
1968
|
});
|
|
1969
|
+
currentAuthenticatedAccount = storedAuth.account ?? undefined;
|
|
1970
|
+
currentAuthenticatedAccountSource = storedAuth.account ? "stored" : null;
|
|
1971
|
+
currentAuthenticatedEnvToken = null;
|
|
1245
1972
|
let joinedRoom = null;
|
|
1246
1973
|
const roomToJoin = room_id ||
|
|
1247
1974
|
pendingAuth?.suggested_room_id ||
|
|
@@ -1252,8 +1979,8 @@ server.tool("poll_device_auth", "Poll a pending GitHub Device Flow request. On s
|
|
|
1252
1979
|
const joinedVia = looksLikeInviteCode(roomToJoin) ? "join_code" : "join_room";
|
|
1253
1980
|
const joined = await joinRoomIdentifier(roomToJoin, joinedVia);
|
|
1254
1981
|
joinedRoom = joined.room;
|
|
1255
|
-
await autoRegisterAgentIdentity();
|
|
1256
1982
|
}
|
|
1983
|
+
const agentIdentity = await ensureAgentIdentity();
|
|
1257
1984
|
return {
|
|
1258
1985
|
content: [
|
|
1259
1986
|
{
|
|
@@ -1264,6 +1991,7 @@ server.tool("poll_device_auth", "Poll a pending GitHub Device Flow request. On s
|
|
|
1264
1991
|
account: storedAuth.account ?? null,
|
|
1265
1992
|
expires_at: storedAuth.expires_at ?? null,
|
|
1266
1993
|
auto_joined_room: joinedRoom,
|
|
1994
|
+
agent_identity: toPublicAgentIdentity(agentIdentity),
|
|
1267
1995
|
}, null, 2),
|
|
1268
1996
|
},
|
|
1269
1997
|
],
|
|
@@ -1272,6 +2000,9 @@ server.tool("poll_device_auth", "Poll a pending GitHub Device Flow request. On s
|
|
|
1272
2000
|
server.tool("clear_saved_auth", "Clear any locally saved LetAgents auth token and pending device auth request.", {}, async () => {
|
|
1273
2001
|
clearPendingDeviceAuth();
|
|
1274
2002
|
clearStoredAuth();
|
|
2003
|
+
currentAuthenticatedAccount = undefined;
|
|
2004
|
+
currentAuthenticatedAccountSource = null;
|
|
2005
|
+
currentAuthenticatedEnvToken = null;
|
|
1275
2006
|
return {
|
|
1276
2007
|
content: [
|
|
1277
2008
|
{
|
|
@@ -1304,7 +2035,7 @@ server.tool("resume_room_session", "Rejoin the last locally saved room context,
|
|
|
1304
2035
|
}
|
|
1305
2036
|
try {
|
|
1306
2037
|
const joined = await joinRoomIdentifier(savedRoom.room_id, savedRoom.joined_via);
|
|
1307
|
-
await
|
|
2038
|
+
const agentIdentity = await ensureAgentIdentity();
|
|
1308
2039
|
return {
|
|
1309
2040
|
content: [
|
|
1310
2041
|
{
|
|
@@ -1314,7 +2045,8 @@ server.tool("resume_room_session", "Rejoin the last locally saved room context,
|
|
|
1314
2045
|
rejoined_from_local_state: true,
|
|
1315
2046
|
server_session_resumed: false,
|
|
1316
2047
|
last_message_id_before_restart: savedRoom.last_message_id ?? null,
|
|
1317
|
-
room: joined.room,
|
|
2048
|
+
room: toPublicRoomState(joined.room),
|
|
2049
|
+
agent_identity: toPublicAgentIdentity(agentIdentity),
|
|
1318
2050
|
}, null, 2),
|
|
1319
2051
|
},
|
|
1320
2052
|
],
|
|
@@ -1378,6 +2110,7 @@ async function main() {
|
|
|
1378
2110
|
const configRoom = getRoomFromConfig();
|
|
1379
2111
|
if (configRoom) {
|
|
1380
2112
|
await joinRoomIdentifier(configRoom, "config");
|
|
2113
|
+
await ensureAgentIdentity();
|
|
1381
2114
|
console.error(`🏠 Auto-joined room '${configRoom}' (from .letagents.json)`);
|
|
1382
2115
|
return;
|
|
1383
2116
|
}
|
|
@@ -1385,6 +2118,7 @@ async function main() {
|
|
|
1385
2118
|
const gitRoom = getGitRemoteIdentity();
|
|
1386
2119
|
if (gitRoom) {
|
|
1387
2120
|
await joinRoomIdentifier(gitRoom, "git-remote");
|
|
2121
|
+
await ensureAgentIdentity();
|
|
1388
2122
|
console.error(`🏠 Auto-joined room '${gitRoom}' (inferred from git remote — consider adding a .letagents.json)`);
|
|
1389
2123
|
return;
|
|
1390
2124
|
}
|
|
@@ -1392,6 +2126,7 @@ async function main() {
|
|
|
1392
2126
|
const savedCurrentRoom = getStoredCurrentRoom();
|
|
1393
2127
|
if (savedCurrentRoom) {
|
|
1394
2128
|
await joinRoomIdentifier(savedCurrentRoom.room_id, savedCurrentRoom.joined_via);
|
|
2129
|
+
await ensureAgentIdentity();
|
|
1395
2130
|
console.error(`🏠 Rejoined saved room '${savedCurrentRoom.room_id}' (from local state)`);
|
|
1396
2131
|
return;
|
|
1397
2132
|
}
|