@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 +1 -1
- package/server/src/project/sdk-bridge.ts +99 -30
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cryptiklemur/lattice",
|
|
3
|
-
"version": "1.41.
|
|
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
|
|
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
|
|
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
|
|
241
|
-
var
|
|
242
|
-
|
|
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
|
|
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
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
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
|
-
|
|
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
|
|