@cryptiklemur/lattice 1.43.2 → 1.43.4
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.
|
@@ -175,10 +175,25 @@ function PreviewPopover(props: { preview: SessionPreview | null; anchorRect: DOM
|
|
|
175
175
|
return createPortal(content, document.body);
|
|
176
176
|
}
|
|
177
177
|
|
|
178
|
+
function loadCachedSessions(slug: string | null): SessionSummary[] {
|
|
179
|
+
if (!slug) return [];
|
|
180
|
+
try {
|
|
181
|
+
var raw = localStorage.getItem("lattice:sessions:" + slug);
|
|
182
|
+
if (raw) return JSON.parse(raw);
|
|
183
|
+
} catch {}
|
|
184
|
+
return [];
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function cacheSessions(slug: string, sessions: SessionSummary[]): void {
|
|
188
|
+
try {
|
|
189
|
+
localStorage.setItem("lattice:sessions:" + slug, JSON.stringify(sessions.slice(0, 100)));
|
|
190
|
+
} catch {}
|
|
191
|
+
}
|
|
192
|
+
|
|
178
193
|
export function SessionList(props: SessionListProps) {
|
|
179
194
|
useTimeTick();
|
|
180
195
|
var ws = useWebSocket();
|
|
181
|
-
var [sessions, setSessions] = useState<SessionSummary[]>(
|
|
196
|
+
var [sessions, setSessions] = useState<SessionSummary[]>(function () { return loadCachedSessions(props.projectSlug); });
|
|
182
197
|
var [loading, setLoading] = useState<boolean>(false);
|
|
183
198
|
var [loadingMore, setLoadingMore] = useState<boolean>(false);
|
|
184
199
|
var [totalCount, setTotalCount] = useState<number>(0);
|
|
@@ -240,6 +255,9 @@ export function SessionList(props: SessionListProps) {
|
|
|
240
255
|
|
|
241
256
|
setTotalCount(listTotal);
|
|
242
257
|
offsetRef.current = listOffset + incoming.length;
|
|
258
|
+
if (props.projectSlug && listOffset === 0) {
|
|
259
|
+
cacheSessions(props.projectSlug, incoming);
|
|
260
|
+
}
|
|
243
261
|
hasMoreRef.current = listOffset + incoming.length < listTotal;
|
|
244
262
|
}
|
|
245
263
|
} else if (msg.type === "session:created") {
|
|
@@ -286,8 +304,9 @@ export function SessionList(props: SessionListProps) {
|
|
|
286
304
|
|
|
287
305
|
useEffect(function () {
|
|
288
306
|
if (props.projectSlug && ws.status === "connected") {
|
|
289
|
-
|
|
290
|
-
|
|
307
|
+
var cached = loadCachedSessions(props.projectSlug);
|
|
308
|
+
setSessions(cached);
|
|
309
|
+
setLoading(cached.length === 0);
|
|
291
310
|
offsetRef.current = 0;
|
|
292
311
|
hasMoreRef.current = true;
|
|
293
312
|
sendRef.current({ type: "session:list_request", projectSlug: props.projectSlug, offset: 0, limit: PAGE_SIZE });
|
|
@@ -12,9 +12,23 @@ export interface UseProjectsResult {
|
|
|
12
12
|
setActiveProject: (project: ProjectInfo | null) => void;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
+
function loadCachedProjects(): ProjectInfo[] {
|
|
16
|
+
try {
|
|
17
|
+
var raw = localStorage.getItem("lattice:projects");
|
|
18
|
+
if (raw) return JSON.parse(raw);
|
|
19
|
+
} catch {}
|
|
20
|
+
return [];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function cacheProjects(projects: ProjectInfo[]): void {
|
|
24
|
+
try {
|
|
25
|
+
localStorage.setItem("lattice:projects", JSON.stringify(projects));
|
|
26
|
+
} catch {}
|
|
27
|
+
}
|
|
28
|
+
|
|
15
29
|
export function useProjects(): UseProjectsResult {
|
|
16
30
|
var ws = useWebSocket();
|
|
17
|
-
var [projects, setProjects] = useState<ProjectInfo[]>(
|
|
31
|
+
var [projects, setProjects] = useState<ProjectInfo[]>(loadCachedProjects);
|
|
18
32
|
var activeProjectSlug = useStore(getSidebarStore(), function (state) { return state.activeProjectSlug; });
|
|
19
33
|
|
|
20
34
|
var handleRef = useRef<(msg: ServerMessage) => void>(function () {});
|
|
@@ -32,7 +46,9 @@ export function useProjects(): UseProjectsResult {
|
|
|
32
46
|
for (var i = 0; i < kept.length; i++) {
|
|
33
47
|
(kept[i] as any).online = false;
|
|
34
48
|
}
|
|
35
|
-
|
|
49
|
+
var merged = incoming.concat(kept);
|
|
50
|
+
cacheProjects(merged);
|
|
51
|
+
return merged;
|
|
36
52
|
});
|
|
37
53
|
var storeState = getSidebarStore().state;
|
|
38
54
|
var currentSlug = storeState.activeProjectSlug;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cryptiklemur/lattice",
|
|
3
|
-
"version": "1.43.
|
|
3
|
+
"version": "1.43.4",
|
|
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>",
|
|
@@ -34,7 +34,9 @@ registerHandler("session", function (clientId: string, message: ClientMessage) {
|
|
|
34
34
|
var listReqMsg = message as SessionListRequestMessage;
|
|
35
35
|
var offset = listReqMsg.offset || 0;
|
|
36
36
|
var limit = listReqMsg.limit || 0;
|
|
37
|
+
var t0 = Date.now();
|
|
37
38
|
void listSessions(listReqMsg.projectSlug, { offset, limit }).then(function (result) {
|
|
39
|
+
log.session("session:list_request for %s took %dms (%d sessions)", listReqMsg.projectSlug, Date.now() - t0, result.sessions.length);
|
|
38
40
|
sendTo(clientId, {
|
|
39
41
|
type: "session:list",
|
|
40
42
|
projectSlug: listReqMsg.projectSlug,
|
|
@@ -221,7 +221,7 @@ export function getActiveSessionCountForProject(projectPath: string): number {
|
|
|
221
221
|
if (existsSync(join(dir, sessionId + ".jsonl"))) count++;
|
|
222
222
|
}
|
|
223
223
|
|
|
224
|
-
if (
|
|
224
|
+
if (cliActiveProjects.has(projectPath)) count++;
|
|
225
225
|
|
|
226
226
|
return count;
|
|
227
227
|
}
|
|
@@ -241,28 +241,15 @@ export function isSessionBusy(sessionId: string): boolean {
|
|
|
241
241
|
* The SDK spawns child processes (e.g. claude-agent-sdk/cli.js) that hold
|
|
242
242
|
* lock files — those are NOT external.
|
|
243
243
|
*/
|
|
244
|
-
var
|
|
245
|
-
var sessionNameCache = new Map<string, string>();
|
|
244
|
+
var cliActiveProjects = new Set<string>();
|
|
246
245
|
|
|
247
246
|
function refreshCliDetection(): void {
|
|
248
|
-
var
|
|
247
|
+
var newActive = new Set<string>();
|
|
249
248
|
var cliPids = getClaudeCliPidsAsync();
|
|
250
|
-
for (var i = 0; i <
|
|
251
|
-
|
|
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);
|
|
249
|
+
for (var i = 0; i < cliPids.length; i++) {
|
|
250
|
+
newActive.add(cliPids[i].cwd);
|
|
265
251
|
}
|
|
252
|
+
cliActiveProjects = newActive;
|
|
266
253
|
}
|
|
267
254
|
|
|
268
255
|
function getClaudeCliPidsAsync(): Array<{ pid: number; cwd: string; cmdline: string[] }> {
|
|
@@ -284,8 +271,7 @@ function getClaudeCliPidsAsync(): Array<{ pid: number; cwd: string; cmdline: str
|
|
|
284
271
|
return results;
|
|
285
272
|
}
|
|
286
273
|
|
|
287
|
-
setInterval(refreshCliDetection,
|
|
288
|
-
setTimeout(refreshCliDetection, 1000);
|
|
274
|
+
setInterval(refreshCliDetection, 10000);
|
|
289
275
|
|
|
290
276
|
function getProjectPathForSession(sessionId: string): string | null {
|
|
291
277
|
var config = loadConfig();
|
|
@@ -298,67 +284,11 @@ function getProjectPathForSession(sessionId: string): string | null {
|
|
|
298
284
|
}
|
|
299
285
|
|
|
300
286
|
|
|
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
|
-
|
|
306
|
-
var hash = projectPath.replace(/\//g, "-");
|
|
307
|
-
var dir = join(homedir(), ".claude", "projects", hash);
|
|
308
|
-
if (!existsSync(dir)) return null;
|
|
309
|
-
|
|
310
|
-
if (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/.test(name)) {
|
|
311
|
-
if (existsSync(join(dir, name + ".jsonl"))) {
|
|
312
|
-
sessionNameCache.set(cacheKey, name);
|
|
313
|
-
return name;
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
var entries = readdirSync(dir).filter(function (f) { return f.endsWith(".jsonl"); });
|
|
318
|
-
for (var e = 0; e < entries.length; e++) {
|
|
319
|
-
try {
|
|
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);
|
|
326
|
-
var parsed = JSON.parse(line);
|
|
327
|
-
if (parsed.type === "custom-title" && parsed.customTitle === name) {
|
|
328
|
-
var id = entries[e].replace(".jsonl", "");
|
|
329
|
-
sessionNameCache.set(cacheKey, id);
|
|
330
|
-
return id;
|
|
331
|
-
}
|
|
332
|
-
} catch {}
|
|
333
|
-
}
|
|
334
|
-
return null;
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
function findMostRecentSession(projectPath: string): string | null {
|
|
338
|
-
var hash = projectPath.replace(/\//g, "-");
|
|
339
|
-
var dir = join(homedir(), ".claude", "projects", hash);
|
|
340
|
-
if (!existsSync(dir)) return null;
|
|
341
|
-
|
|
342
|
-
var entries = readdirSync(dir).filter(function (f) { return f.endsWith(".jsonl"); });
|
|
343
|
-
var latest: { id: string; mtime: number } | null = null;
|
|
344
|
-
for (var e = 0; e < entries.length; e++) {
|
|
345
|
-
try {
|
|
346
|
-
var s = statSync(join(dir, entries[e]));
|
|
347
|
-
if (!latest || s.mtimeMs > latest.mtime) {
|
|
348
|
-
latest = { id: entries[e].replace(".jsonl", ""), mtime: s.mtimeMs };
|
|
349
|
-
}
|
|
350
|
-
} catch {}
|
|
351
|
-
}
|
|
352
|
-
if (latest && Date.now() - latest.mtime < 60000) return latest.id;
|
|
353
|
-
return null;
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
|
|
357
287
|
function isSessionLockedByExternal(sessionId: string): boolean {
|
|
358
288
|
if (activeStreams.has(sessionId)) return false;
|
|
359
289
|
var projectPath = getProjectPathForSession(sessionId);
|
|
360
290
|
if (!projectPath) return false;
|
|
361
|
-
return
|
|
291
|
+
return cliActiveProjects.has(projectPath);
|
|
362
292
|
}
|
|
363
293
|
|
|
364
294
|
export function stopExternalSession(sessionId: string): boolean {
|