@cryptiklemur/lattice 1.41.2 → 1.41.3

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.41.2",
3
+ "version": "1.41.3",
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>",
@@ -4,7 +4,7 @@ import type { SDKMessage, SDKPartialAssistantMessage, SDKResultMessage, SDKUserM
4
4
  import type { CanUseTool, PermissionMode, PermissionResult, PermissionUpdate } from "@anthropic-ai/claude-agent-sdk";
5
5
  type MessageParam = SDKUserMessage["message"];
6
6
  import type { Attachment } from "@lattice/shared";
7
- import { existsSync, readFileSync, writeFileSync, mkdirSync, statSync } from "node:fs";
7
+ import { existsSync, readFileSync, writeFileSync, mkdirSync, statSync, readdirSync, readlinkSync } from "node:fs";
8
8
  import { join, resolve } from "node:path";
9
9
  import { homedir } from "node:os";
10
10
  import { sendTo, broadcast } from "../ws/broadcast";
@@ -227,47 +227,116 @@ export function isSessionBusy(sessionId: string): boolean {
227
227
  * The SDK spawns child processes (e.g. claude-agent-sdk/cli.js) that hold
228
228
  * lock files — those are NOT external.
229
229
  */
230
- function isSessionLockedByExternal(sessionId: string): boolean {
231
- if (activeStreams.has(sessionId)) return false;
232
-
230
+ function getProjectPathForSession(sessionId: string): string | null {
233
231
  var config = loadConfig();
234
232
  for (var i = 0; i < config.projects.length; i++) {
235
- var projectPath = config.projects[i].path;
236
- var hash = projectPath.replace(/\//g, "-");
233
+ var hash = config.projects[i].path.replace(/\//g, "-");
237
234
  var jsonlPath = join(homedir(), ".claude", "projects", hash, sessionId + ".jsonl");
238
- if (existsSync(jsonlPath)) {
235
+ if (existsSync(jsonlPath)) return config.projects[i].path;
236
+ }
237
+ return null;
238
+ }
239
+
240
+ function getClaudeCliPids(): Array<{ pid: number; cwd: string; cmdline: string[] }> {
241
+ var results: Array<{ pid: number; cwd: string; cmdline: string[] }> = [];
242
+ try {
243
+ var result = Bun.spawnSync(["pgrep", "-x", "claude"], { stderr: "ignore" });
244
+ if (result.exitCode !== 0) return results;
245
+ var pidStrs = result.stdout.toString().trim().split("\n");
246
+ for (var i = 0; i < pidStrs.length; i++) {
247
+ var pid = parseInt(pidStrs[i], 10);
248
+ if (isNaN(pid) || pid === process.pid) continue;
239
249
  try {
240
- var stat = statSync(jsonlPath);
241
- var mtime = stat.mtimeMs;
242
- if (Date.now() - mtime < 10000) {
243
- return true;
244
- }
250
+ var cwd = readlinkSync("/proc/" + pid + "/cwd");
251
+ var cmdline = readFileSync("/proc/" + pid + "/cmdline", "utf-8").split("\0");
252
+ results.push({ pid, cwd, cmdline });
245
253
  } catch {}
246
254
  }
255
+ } catch {}
256
+ return results;
257
+ }
258
+
259
+ function resolveSessionName(projectPath: string, name: string): string | null {
260
+ var hash = projectPath.replace(/\//g, "-");
261
+ var dir = join(homedir(), ".claude", "projects", hash);
262
+ if (!existsSync(dir)) return null;
263
+
264
+ if (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/.test(name)) {
265
+ if (existsSync(join(dir, name + ".jsonl"))) return name;
247
266
  }
248
267
 
249
- return false;
268
+ var entries = readdirSync(dir).filter(function (f) { return f.endsWith(".jsonl"); });
269
+ for (var e = 0; e < entries.length; e++) {
270
+ try {
271
+ var result = Bun.spawnSync(["grep", "-m", "1", "custom-title", join(dir, entries[e])], { stdout: "pipe", stderr: "ignore" });
272
+ if (result.exitCode !== 0) continue;
273
+ var line = result.stdout.toString().trim();
274
+ if (!line) continue;
275
+ var parsed = JSON.parse(line);
276
+ if (parsed.type === "custom-title" && parsed.customTitle === name) {
277
+ return entries[e].replace(".jsonl", "");
278
+ }
279
+ } catch {}
280
+ }
281
+ return null;
250
282
  }
251
283
 
252
- export function stopExternalSession(sessionId: string): boolean {
253
- try {
254
- var config = loadConfig();
255
- for (var i = 0; i < config.projects.length; i++) {
256
- var hash = config.projects[i].path.replace(/\//g, "-");
257
- var jsonlPath = join(homedir(), ".claude", "projects", hash, sessionId + ".jsonl");
258
- if (existsSync(jsonlPath)) {
259
- var result = Bun.spawnSync(["fuser", jsonlPath], { stderr: "ignore" });
260
- if (result.exitCode === 0) {
261
- var output = result.stdout.toString().trim();
262
- var pids = output.split(/\s+/).map(Number).filter(function (p) { return !isNaN(p) && p !== process.pid; });
263
- if (pids.length > 0) {
264
- process.kill(pids[0], "SIGINT");
265
- return true;
266
- }
267
- }
284
+ function findMostRecentSession(projectPath: string): string | null {
285
+ var hash = projectPath.replace(/\//g, "-");
286
+ var dir = join(homedir(), ".claude", "projects", hash);
287
+ if (!existsSync(dir)) return null;
288
+
289
+ var entries = readdirSync(dir).filter(function (f) { return f.endsWith(".jsonl"); });
290
+ var latest: { id: string; mtime: number } | null = null;
291
+ for (var e = 0; e < entries.length; e++) {
292
+ try {
293
+ var s = statSync(join(dir, entries[e]));
294
+ if (!latest || s.mtimeMs > latest.mtime) {
295
+ latest = { id: entries[e].replace(".jsonl", ""), mtime: s.mtimeMs };
268
296
  }
297
+ } catch {}
298
+ }
299
+ if (latest && Date.now() - latest.mtime < 60000) return latest.id;
300
+ return null;
301
+ }
302
+
303
+ function getCliSessionIdForProject(projectPath: string): string | null {
304
+ var cliProcesses = getClaudeCliPids();
305
+ for (var i = 0; i < cliProcesses.length; i++) {
306
+ if (cliProcesses[i].cwd !== projectPath) continue;
307
+
308
+ var cmdline = cliProcesses[i].cmdline;
309
+ var resumeIdx = cmdline.indexOf("--resume");
310
+ if (resumeIdx !== -1 && resumeIdx + 1 < cmdline.length) {
311
+ var sessionName = cmdline[resumeIdx + 1];
312
+ return resolveSessionName(projectPath, sessionName);
269
313
  }
270
- } catch {}
314
+
315
+ return findMostRecentSession(projectPath);
316
+ }
317
+ return null;
318
+ }
319
+
320
+ function isSessionLockedByExternal(sessionId: string): boolean {
321
+ if (activeStreams.has(sessionId)) return false;
322
+ var projectPath = getProjectPathForSession(sessionId);
323
+ if (!projectPath) return false;
324
+ var cliSessionId = getCliSessionIdForProject(projectPath);
325
+ return cliSessionId === sessionId;
326
+ }
327
+
328
+ export function stopExternalSession(sessionId: string): boolean {
329
+ var projectPath = getProjectPathForSession(sessionId);
330
+ if (!projectPath) return false;
331
+ var cliProcesses = getClaudeCliPids();
332
+ for (var i = 0; i < cliProcesses.length; i++) {
333
+ if (cliProcesses[i].cwd === projectPath) {
334
+ try {
335
+ process.kill(cliProcesses[i].pid, "SIGINT");
336
+ return true;
337
+ } catch {}
338
+ }
339
+ }
271
340
  return false;
272
341
  }
273
342