@cryptiklemur/lattice 1.43.1 → 1.43.2
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cryptiklemur/lattice",
|
|
3
|
-
"version": "1.43.
|
|
3
|
+
"version": "1.43.2",
|
|
4
4
|
"description": "Multi-machine agentic dashboard for Claude Code. Monitor sessions, manage MCP servers and skills, orchestrate across mesh-networked nodes.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Aaron Scherer <me@aaronscherer.me>",
|
|
@@ -221,32 +221,11 @@ export function getActiveSessionCountForProject(projectPath: string): number {
|
|
|
221
221
|
if (existsSync(join(dir, sessionId + ".jsonl"))) count++;
|
|
222
222
|
}
|
|
223
223
|
|
|
224
|
-
|
|
225
|
-
void sessionId2;
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
if (isClaudeCliRunningInProject(projectPath)) count++;
|
|
224
|
+
if (cliSessionsByProject.get(projectPath) !== null && cliSessionsByProject.get(projectPath) !== undefined) count++;
|
|
229
225
|
|
|
230
226
|
return count;
|
|
231
227
|
}
|
|
232
228
|
|
|
233
|
-
function isClaudeCliRunningInProject(projectPath: string): boolean {
|
|
234
|
-
try {
|
|
235
|
-
var result = Bun.spawnSync(["pgrep", "-x", "claude"], { stderr: "ignore" });
|
|
236
|
-
if (result.exitCode !== 0) return false;
|
|
237
|
-
var pids = result.stdout.toString().trim().split("\n");
|
|
238
|
-
for (var i = 0; i < pids.length; i++) {
|
|
239
|
-
var pid = parseInt(pids[i], 10);
|
|
240
|
-
if (isNaN(pid) || pid === process.pid) continue;
|
|
241
|
-
try {
|
|
242
|
-
var cwd = readlinkSync("/proc/" + pid + "/cwd");
|
|
243
|
-
if (cwd === projectPath) return true;
|
|
244
|
-
} catch {}
|
|
245
|
-
}
|
|
246
|
-
} catch {}
|
|
247
|
-
return false;
|
|
248
|
-
}
|
|
249
|
-
|
|
250
229
|
/**
|
|
251
230
|
* Check if a session is controlled by an external process (not Lattice).
|
|
252
231
|
* Lattice's own active streams are handled by isProcessing on the client,
|
|
@@ -262,28 +241,42 @@ export function isSessionBusy(sessionId: string): boolean {
|
|
|
262
241
|
* The SDK spawns child processes (e.g. claude-agent-sdk/cli.js) that hold
|
|
263
242
|
* lock files — those are NOT external.
|
|
264
243
|
*/
|
|
265
|
-
|
|
244
|
+
var cliSessionsByProject = new Map<string, string | null>();
|
|
245
|
+
var sessionNameCache = new Map<string, string>();
|
|
246
|
+
|
|
247
|
+
function refreshCliDetection(): void {
|
|
266
248
|
var config = loadConfig();
|
|
249
|
+
var cliPids = getClaudeCliPidsAsync();
|
|
267
250
|
for (var i = 0; i < config.projects.length; i++) {
|
|
268
|
-
var
|
|
269
|
-
var
|
|
270
|
-
|
|
251
|
+
var projectPath = config.projects[i].path;
|
|
252
|
+
var found: string | null = null;
|
|
253
|
+
for (var j = 0; j < cliPids.length; j++) {
|
|
254
|
+
if (cliPids[j].cwd !== projectPath) continue;
|
|
255
|
+
var cmdline = cliPids[j].cmdline;
|
|
256
|
+
var resumeIdx = cmdline.indexOf("--resume");
|
|
257
|
+
if (resumeIdx !== -1 && resumeIdx + 1 < cmdline.length) {
|
|
258
|
+
found = resolveSessionName(projectPath, cmdline[resumeIdx + 1]);
|
|
259
|
+
} else {
|
|
260
|
+
found = findMostRecentSession(projectPath);
|
|
261
|
+
}
|
|
262
|
+
break;
|
|
263
|
+
}
|
|
264
|
+
cliSessionsByProject.set(projectPath, found);
|
|
271
265
|
}
|
|
272
|
-
return null;
|
|
273
266
|
}
|
|
274
267
|
|
|
275
|
-
function
|
|
268
|
+
function getClaudeCliPidsAsync(): Array<{ pid: number; cwd: string; cmdline: string[] }> {
|
|
276
269
|
var results: Array<{ pid: number; cwd: string; cmdline: string[] }> = [];
|
|
277
270
|
try {
|
|
278
|
-
var
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
var pid = parseInt(pidStrs[i], 10);
|
|
283
|
-
if (isNaN(pid) || pid === process.pid) continue;
|
|
271
|
+
var procEntries = readdirSync("/proc").filter(function (e) { return /^\d+$/.test(e); });
|
|
272
|
+
for (var i = 0; i < procEntries.length; i++) {
|
|
273
|
+
var pid = parseInt(procEntries[i], 10);
|
|
274
|
+
if (pid === process.pid) continue;
|
|
284
275
|
try {
|
|
285
|
-
var cwd = readlinkSync("/proc/" + pid + "/cwd");
|
|
286
276
|
var cmdline = readFileSync("/proc/" + pid + "/cmdline", "utf-8").split("\0");
|
|
277
|
+
var exe = cmdline[0] || "";
|
|
278
|
+
if (!exe.endsWith("/claude") && exe !== "claude") continue;
|
|
279
|
+
var cwd = readlinkSync("/proc/" + pid + "/cwd");
|
|
287
280
|
results.push({ pid, cwd, cmdline });
|
|
288
281
|
} catch {}
|
|
289
282
|
}
|
|
@@ -291,25 +284,50 @@ function getClaudeCliPids(): Array<{ pid: number; cwd: string; cmdline: string[]
|
|
|
291
284
|
return results;
|
|
292
285
|
}
|
|
293
286
|
|
|
287
|
+
setInterval(refreshCliDetection, 5000);
|
|
288
|
+
setTimeout(refreshCliDetection, 1000);
|
|
289
|
+
|
|
290
|
+
function getProjectPathForSession(sessionId: string): string | null {
|
|
291
|
+
var config = loadConfig();
|
|
292
|
+
for (var i = 0; i < config.projects.length; i++) {
|
|
293
|
+
var hash = config.projects[i].path.replace(/\//g, "-");
|
|
294
|
+
var jsonlPath = join(homedir(), ".claude", "projects", hash, sessionId + ".jsonl");
|
|
295
|
+
if (existsSync(jsonlPath)) return config.projects[i].path;
|
|
296
|
+
}
|
|
297
|
+
return null;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
|
|
294
301
|
function resolveSessionName(projectPath: string, name: string): string | null {
|
|
302
|
+
var cacheKey = projectPath + ":" + name;
|
|
303
|
+
var cached = sessionNameCache.get(cacheKey);
|
|
304
|
+
if (cached) return cached;
|
|
305
|
+
|
|
295
306
|
var hash = projectPath.replace(/\//g, "-");
|
|
296
307
|
var dir = join(homedir(), ".claude", "projects", hash);
|
|
297
308
|
if (!existsSync(dir)) return null;
|
|
298
309
|
|
|
299
310
|
if (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/.test(name)) {
|
|
300
|
-
if (existsSync(join(dir, name + ".jsonl")))
|
|
311
|
+
if (existsSync(join(dir, name + ".jsonl"))) {
|
|
312
|
+
sessionNameCache.set(cacheKey, name);
|
|
313
|
+
return name;
|
|
314
|
+
}
|
|
301
315
|
}
|
|
302
316
|
|
|
303
317
|
var entries = readdirSync(dir).filter(function (f) { return f.endsWith(".jsonl"); });
|
|
304
318
|
for (var e = 0; e < entries.length; e++) {
|
|
305
319
|
try {
|
|
306
|
-
var
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
320
|
+
var content = readFileSync(join(dir, entries[e]), "utf-8");
|
|
321
|
+
var titleIdx = content.indexOf('"custom-title"');
|
|
322
|
+
if (titleIdx === -1) continue;
|
|
323
|
+
var lineStart = content.lastIndexOf("\n", titleIdx) + 1;
|
|
324
|
+
var lineEnd = content.indexOf("\n", titleIdx);
|
|
325
|
+
var line = content.slice(lineStart, lineEnd === -1 ? undefined : lineEnd);
|
|
310
326
|
var parsed = JSON.parse(line);
|
|
311
327
|
if (parsed.type === "custom-title" && parsed.customTitle === name) {
|
|
312
|
-
|
|
328
|
+
var id = entries[e].replace(".jsonl", "");
|
|
329
|
+
sessionNameCache.set(cacheKey, id);
|
|
330
|
+
return id;
|
|
313
331
|
}
|
|
314
332
|
} catch {}
|
|
315
333
|
}
|
|
@@ -335,39 +353,22 @@ function findMostRecentSession(projectPath: string): string | null {
|
|
|
335
353
|
return null;
|
|
336
354
|
}
|
|
337
355
|
|
|
338
|
-
function getCliSessionIdForProject(projectPath: string): string | null {
|
|
339
|
-
var cliProcesses = getClaudeCliPids();
|
|
340
|
-
for (var i = 0; i < cliProcesses.length; i++) {
|
|
341
|
-
if (cliProcesses[i].cwd !== projectPath) continue;
|
|
342
|
-
|
|
343
|
-
var cmdline = cliProcesses[i].cmdline;
|
|
344
|
-
var resumeIdx = cmdline.indexOf("--resume");
|
|
345
|
-
if (resumeIdx !== -1 && resumeIdx + 1 < cmdline.length) {
|
|
346
|
-
var sessionName = cmdline[resumeIdx + 1];
|
|
347
|
-
return resolveSessionName(projectPath, sessionName);
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
return findMostRecentSession(projectPath);
|
|
351
|
-
}
|
|
352
|
-
return null;
|
|
353
|
-
}
|
|
354
356
|
|
|
355
357
|
function isSessionLockedByExternal(sessionId: string): boolean {
|
|
356
358
|
if (activeStreams.has(sessionId)) return false;
|
|
357
359
|
var projectPath = getProjectPathForSession(sessionId);
|
|
358
360
|
if (!projectPath) return false;
|
|
359
|
-
|
|
360
|
-
return cliSessionId === sessionId;
|
|
361
|
+
return cliSessionsByProject.get(projectPath) === sessionId;
|
|
361
362
|
}
|
|
362
363
|
|
|
363
364
|
export function stopExternalSession(sessionId: string): boolean {
|
|
364
365
|
var projectPath = getProjectPathForSession(sessionId);
|
|
365
366
|
if (!projectPath) return false;
|
|
366
|
-
var
|
|
367
|
-
for (var i = 0; i <
|
|
368
|
-
if (
|
|
367
|
+
var pids = getClaudeCliPidsAsync();
|
|
368
|
+
for (var i = 0; i < pids.length; i++) {
|
|
369
|
+
if (pids[i].cwd === projectPath) {
|
|
369
370
|
try {
|
|
370
|
-
process.kill(
|
|
371
|
+
process.kill(pids[i].pid, "SIGINT");
|
|
371
372
|
return true;
|
|
372
373
|
} catch {}
|
|
373
374
|
}
|
|
@@ -422,7 +422,9 @@ export async function listSessions(projectSlug: string, options?: { offset?: num
|
|
|
422
422
|
}
|
|
423
423
|
|
|
424
424
|
try {
|
|
425
|
+
var sdkT0 = Date.now();
|
|
425
426
|
var sdkSessions = await sdkListSessions({ dir: projectPath });
|
|
427
|
+
log.session("sdkListSessions for %s: %dms (%d sessions)", projectSlug, Date.now() - sdkT0, sdkSessions.length);
|
|
426
428
|
var summaries = sdkSessions.map(function (s) {
|
|
427
429
|
return mapSDKSession(s, projectSlug);
|
|
428
430
|
});
|