@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.1",
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
- for (var [sessionId2] of streamMetadata) {
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
- function getProjectPathForSession(sessionId: string): string | null {
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 hash = config.projects[i].path.replace(/\//g, "-");
269
- var jsonlPath = join(homedir(), ".claude", "projects", hash, sessionId + ".jsonl");
270
- if (existsSync(jsonlPath)) return config.projects[i].path;
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 getClaudeCliPids(): Array<{ pid: number; cwd: string; cmdline: string[] }> {
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 result = Bun.spawnSync(["pgrep", "-x", "claude"], { stderr: "ignore" });
279
- if (result.exitCode !== 0) return results;
280
- var pidStrs = result.stdout.toString().trim().split("\n");
281
- for (var i = 0; i < pidStrs.length; i++) {
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"))) return name;
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 result = Bun.spawnSync(["grep", "-m", "1", "custom-title", join(dir, entries[e])], { stdout: "pipe", stderr: "ignore" });
307
- if (result.exitCode !== 0) continue;
308
- var line = result.stdout.toString().trim();
309
- if (!line) continue;
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
- return entries[e].replace(".jsonl", "");
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
- var cliSessionId = getCliSessionIdForProject(projectPath);
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 cliProcesses = getClaudeCliPids();
367
- for (var i = 0; i < cliProcesses.length; i++) {
368
- if (cliProcesses[i].cwd === projectPath) {
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(cliProcesses[i].pid, "SIGINT");
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
  });