@cryptiklemur/lattice 1.43.6 → 1.43.8
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/handlers/session.ts +43 -48
- package/server/src/project/session.ts +85 -19
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cryptiklemur/lattice",
|
|
3
|
-
"version": "1.43.
|
|
3
|
+
"version": "1.43.8",
|
|
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>",
|
|
@@ -25,7 +25,7 @@ import {
|
|
|
25
25
|
renameSession,
|
|
26
26
|
} from "../project/session";
|
|
27
27
|
import { getContextBreakdown } from "../project/context-breakdown";
|
|
28
|
-
import { setActiveSession } from "./chat";
|
|
28
|
+
import { setActiveSession, getActiveSession } from "./chat";
|
|
29
29
|
import { setActiveProject } from "./fs";
|
|
30
30
|
import { wasSessionInterrupted, clearInterruptedFlag, isSessionBusy, watchSessionLock, stopExternalSession, getBusyOwner } from "../project/sdk-bridge";
|
|
31
31
|
import { log } from "../logger";
|
|
@@ -82,12 +82,14 @@ registerHandler("session", function (clientId: string, message: ClientMessage) {
|
|
|
82
82
|
|
|
83
83
|
if (message.type === "session:history_page") {
|
|
84
84
|
var pageMsg = message as { type: "session:history_page"; sessionId: string; before: number; limit: number };
|
|
85
|
-
var
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
85
|
+
var activeSession = getActiveSession(clientId);
|
|
86
|
+
void getSessionHistoryPage(pageMsg.sessionId, pageMsg.before, pageMsg.limit, activeSession?.projectSlug).then(function (page) {
|
|
87
|
+
sendTo(clientId, {
|
|
88
|
+
type: "session:history_page_result",
|
|
89
|
+
sessionId: pageMsg.sessionId,
|
|
90
|
+
messages: page.messages,
|
|
91
|
+
hasMore: page.hasMore,
|
|
92
|
+
});
|
|
91
93
|
});
|
|
92
94
|
return;
|
|
93
95
|
}
|
|
@@ -104,50 +106,43 @@ registerHandler("session", function (clientId: string, message: ClientMessage) {
|
|
|
104
106
|
setActiveSession(clientId, activateMsg.projectSlug, activateMsg.sessionId);
|
|
105
107
|
setActiveProject(clientId, activateMsg.projectSlug);
|
|
106
108
|
watchSessionLock(activateMsg.sessionId);
|
|
109
|
+
var activateT0 = Date.now();
|
|
107
110
|
void Promise.all([
|
|
108
|
-
loadSessionHistory(activateMsg.projectSlug, activateMsg.sessionId)
|
|
109
|
-
|
|
110
|
-
return null;
|
|
111
|
-
}),
|
|
112
|
-
getSessionTitle(activateMsg.projectSlug, activateMsg.sessionId).catch(function (err) {
|
|
113
|
-
log.session("Failed to load session title: %O", err);
|
|
114
|
-
return null;
|
|
115
|
-
}),
|
|
116
|
-
getSessionUsage(activateMsg.projectSlug, activateMsg.sessionId).catch(function (err) {
|
|
117
|
-
log.session("Failed to load session usage: %O", err);
|
|
118
|
-
return null;
|
|
119
|
-
}),
|
|
120
|
-
getContextBreakdown(activateMsg.projectSlug, activateMsg.sessionId).catch(function (err) {
|
|
121
|
-
log.session("Failed to load context breakdown: %O", err);
|
|
122
|
-
return null;
|
|
123
|
-
}),
|
|
111
|
+
loadSessionHistory(activateMsg.projectSlug, activateMsg.sessionId),
|
|
112
|
+
getSessionTitle(activateMsg.projectSlug, activateMsg.sessionId).catch(function () { return null; }),
|
|
124
113
|
]).then(function (results) {
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
var busyOwner = busy ? getBusyOwner(activateMsg.sessionId) : undefined;
|
|
132
|
-
var historyResult = results[0] || { messages: [], totalMessages: 0, hasMore: false };
|
|
133
|
-
sendTo(clientId, {
|
|
134
|
-
type: "session:history",
|
|
135
|
-
projectSlug: activateMsg.projectSlug,
|
|
136
|
-
sessionId: activateMsg.sessionId,
|
|
137
|
-
messages: historyResult.messages,
|
|
138
|
-
title: results[1],
|
|
139
|
-
interrupted: interrupted || undefined,
|
|
140
|
-
busy: busy || undefined,
|
|
141
|
-
busyOwner: busyOwner,
|
|
142
|
-
totalMessages: historyResult.totalMessages,
|
|
143
|
-
hasMore: historyResult.hasMore,
|
|
144
|
-
});
|
|
145
|
-
} catch (err) {
|
|
146
|
-
log.session("Error sending session history: %O", err);
|
|
147
|
-
sendTo(clientId, { type: "chat:error", message: "Failed to load session history" });
|
|
114
|
+
log.session("session:activate history+title: %dms", Date.now() - activateT0);
|
|
115
|
+
var historyResult = results[0];
|
|
116
|
+
var title = results[1];
|
|
117
|
+
var interrupted = wasSessionInterrupted(activateMsg.sessionId);
|
|
118
|
+
if (interrupted) {
|
|
119
|
+
clearInterruptedFlag(activateMsg.sessionId);
|
|
148
120
|
}
|
|
121
|
+
var busy = isSessionBusy(activateMsg.sessionId);
|
|
122
|
+
var busyOwner = busy ? getBusyOwner(activateMsg.sessionId) : undefined;
|
|
123
|
+
sendTo(clientId, {
|
|
124
|
+
type: "session:history",
|
|
125
|
+
projectSlug: activateMsg.projectSlug,
|
|
126
|
+
sessionId: activateMsg.sessionId,
|
|
127
|
+
messages: historyResult.messages,
|
|
128
|
+
title: title,
|
|
129
|
+
interrupted: interrupted || undefined,
|
|
130
|
+
busy: busy || undefined,
|
|
131
|
+
busyOwner: busyOwner,
|
|
132
|
+
totalMessages: historyResult.totalMessages,
|
|
133
|
+
hasMore: historyResult.hasMore,
|
|
134
|
+
});
|
|
135
|
+
}).catch(function (err) {
|
|
136
|
+
log.session("Error sending session history: %O", err);
|
|
137
|
+
sendTo(clientId, { type: "chat:error", message: "Failed to load session history" });
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
void Promise.all([
|
|
141
|
+
getSessionUsage(activateMsg.projectSlug, activateMsg.sessionId).catch(function () { return null; }),
|
|
142
|
+
getContextBreakdown(activateMsg.projectSlug, activateMsg.sessionId).catch(function () { return null; }),
|
|
143
|
+
]).then(function (results) {
|
|
149
144
|
try {
|
|
150
|
-
var usage = results[
|
|
145
|
+
var usage = results[0];
|
|
151
146
|
if (usage) {
|
|
152
147
|
sendTo(clientId, {
|
|
153
148
|
type: "chat:context_usage",
|
|
@@ -162,7 +157,7 @@ registerHandler("session", function (clientId: string, message: ClientMessage) {
|
|
|
162
157
|
log.session("Error sending context usage: %O", err);
|
|
163
158
|
}
|
|
164
159
|
try {
|
|
165
|
-
var breakdown = results[
|
|
160
|
+
var breakdown = results[1];
|
|
166
161
|
if (breakdown) {
|
|
167
162
|
sendTo(clientId, {
|
|
168
163
|
type: "chat:context_breakdown",
|
|
@@ -458,45 +458,111 @@ export async function getSessionTitle(projectSlug: string, sessionId: string): P
|
|
|
458
458
|
}
|
|
459
459
|
|
|
460
460
|
var historyCache = new Map<string, { messages: HistoryMessage[]; time: number }>();
|
|
461
|
-
var HISTORY_CACHE_TTL =
|
|
461
|
+
var HISTORY_CACHE_TTL = 60000;
|
|
462
462
|
var INITIAL_MESSAGE_COUNT = 100;
|
|
463
|
+
var TAIL_READ_BYTES = 2 * 1024 * 1024;
|
|
463
464
|
|
|
464
|
-
|
|
465
|
+
function getSessionFilePath(projectSlug: string, sessionId: string): string | null {
|
|
465
466
|
var projectPath = getProjectPath(projectSlug);
|
|
466
|
-
|
|
467
|
+
if (!projectPath) return null;
|
|
468
|
+
var hash = projectPathToHash(projectPath);
|
|
469
|
+
var filePath = join(homedir(), ".claude", "projects", hash, sessionId + ".jsonl");
|
|
470
|
+
return existsSync(filePath) ? filePath : null;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
function readTailLines(filePath: string, maxBytes: number): { lines: string[]; isPartial: boolean; fileSize: number } {
|
|
474
|
+
var { openSync, readSync, fstatSync, closeSync } = require("node:fs") as typeof import("node:fs");
|
|
475
|
+
var fd = openSync(filePath, "r");
|
|
476
|
+
var stat = fstatSync(fd);
|
|
477
|
+
var readStart = Math.max(0, stat.size - maxBytes);
|
|
478
|
+
var buf = Buffer.alloc(stat.size - readStart);
|
|
479
|
+
readSync(fd, buf, 0, buf.length, readStart);
|
|
480
|
+
closeSync(fd);
|
|
481
|
+
var text = buf.toString("utf-8");
|
|
482
|
+
if (readStart > 0) {
|
|
483
|
+
var firstNewline = text.indexOf("\n");
|
|
484
|
+
if (firstNewline >= 0) text = text.slice(firstNewline + 1);
|
|
485
|
+
}
|
|
486
|
+
var lines = text.split("\n").filter(function (l) { return l.length > 0; });
|
|
487
|
+
return { lines, isPartial: readStart > 0, fileSize: stat.size };
|
|
488
|
+
}
|
|
467
489
|
|
|
490
|
+
function parseJsonlLines(lines: string[]): SessionMessage[] {
|
|
491
|
+
var results: SessionMessage[] = [];
|
|
492
|
+
for (var i = 0; i < lines.length; i++) {
|
|
493
|
+
try {
|
|
494
|
+
var parsed = JSON.parse(lines[i]);
|
|
495
|
+
if (parsed.type === "user" || parsed.type === "assistant" || parsed.type === "system") {
|
|
496
|
+
results.push(parsed as SessionMessage);
|
|
497
|
+
}
|
|
498
|
+
} catch {}
|
|
499
|
+
}
|
|
500
|
+
return results;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
export async function loadSessionHistory(projectSlug: string, sessionId: string): Promise<{ messages: HistoryMessage[]; totalMessages: number; hasMore: boolean }> {
|
|
468
504
|
try {
|
|
469
505
|
var t0 = Date.now();
|
|
470
506
|
var cached = historyCache.get(sessionId);
|
|
471
|
-
var allMessages: HistoryMessage[];
|
|
472
|
-
|
|
473
507
|
if (cached && Date.now() - cached.time < HISTORY_CACHE_TTL) {
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
508
|
+
var tail = cached.messages.length > INITIAL_MESSAGE_COUNT
|
|
509
|
+
? cached.messages.slice(cached.messages.length - INITIAL_MESSAGE_COUNT)
|
|
510
|
+
: cached.messages;
|
|
511
|
+
log.session("loadSessionHistory %s: %dms (cached, %d total)", sessionId.slice(0, 8), Date.now() - t0, cached.messages.length);
|
|
512
|
+
return { messages: tail, totalMessages: cached.messages.length, hasMore: cached.messages.length > INITIAL_MESSAGE_COUNT };
|
|
479
513
|
}
|
|
480
514
|
|
|
481
|
-
|
|
515
|
+
var filePath = getSessionFilePath(projectSlug, sessionId);
|
|
516
|
+
if (filePath) {
|
|
517
|
+
var tailData = readTailLines(filePath, TAIL_READ_BYTES);
|
|
518
|
+
var tailRaw = parseJsonlLines(tailData.lines);
|
|
519
|
+
var tailMessages = convertSessionMessages(tailRaw);
|
|
520
|
+
var hasMore = tailData.isPartial;
|
|
521
|
+
|
|
522
|
+
log.session("loadSessionHistory %s: %dms (tail read, %d msgs, partial=%s)", sessionId.slice(0, 8), Date.now() - t0, tailMessages.length, hasMore);
|
|
523
|
+
|
|
524
|
+
if (!hasMore) {
|
|
525
|
+
historyCache.set(sessionId, { messages: tailMessages, time: Date.now() });
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
var initialSlice = tailMessages.length > INITIAL_MESSAGE_COUNT
|
|
529
|
+
? tailMessages.slice(tailMessages.length - INITIAL_MESSAGE_COUNT)
|
|
530
|
+
: tailMessages;
|
|
531
|
+
|
|
532
|
+
return { messages: initialSlice, totalMessages: tailMessages.length, hasMore: hasMore };
|
|
533
|
+
}
|
|
482
534
|
|
|
483
|
-
var
|
|
535
|
+
var projectPath = getProjectPath(projectSlug);
|
|
536
|
+
var options = projectPath ? { dir: projectPath } : undefined;
|
|
537
|
+
var rawMessages = await getSessionMessages(sessionId, options);
|
|
538
|
+
var allMessages = convertSessionMessages(rawMessages);
|
|
539
|
+
historyCache.set(sessionId, { messages: allMessages, time: Date.now() });
|
|
540
|
+
log.session("loadSessionHistory %s: %dms (full SDK, %d msgs)", sessionId.slice(0, 8), Date.now() - t0, allMessages.length);
|
|
541
|
+
var tailSlice = allMessages.length > INITIAL_MESSAGE_COUNT
|
|
484
542
|
? allMessages.slice(allMessages.length - INITIAL_MESSAGE_COUNT)
|
|
485
543
|
: allMessages;
|
|
486
|
-
|
|
487
|
-
return {
|
|
488
|
-
messages: tail,
|
|
489
|
-
totalMessages: allMessages.length,
|
|
490
|
-
hasMore: allMessages.length > INITIAL_MESSAGE_COUNT,
|
|
491
|
-
};
|
|
544
|
+
return { messages: tailSlice, totalMessages: allMessages.length, hasMore: allMessages.length > INITIAL_MESSAGE_COUNT };
|
|
492
545
|
} catch (err) {
|
|
493
546
|
log.session("Failed to load session history: %O", err);
|
|
494
547
|
return { messages: [], totalMessages: 0, hasMore: false };
|
|
495
548
|
}
|
|
496
549
|
}
|
|
497
550
|
|
|
498
|
-
export function getSessionHistoryPage(sessionId: string, beforeIndex: number, limit: number): { messages: HistoryMessage[]; hasMore: boolean } {
|
|
551
|
+
export async function getSessionHistoryPage(sessionId: string, beforeIndex: number, limit: number, projectSlug?: string): Promise<{ messages: HistoryMessage[]; hasMore: boolean }> {
|
|
499
552
|
var cached = historyCache.get(sessionId);
|
|
553
|
+
if (!cached && projectSlug) {
|
|
554
|
+
var projectPath = getProjectPath(projectSlug);
|
|
555
|
+
var options = projectPath ? { dir: projectPath } : undefined;
|
|
556
|
+
try {
|
|
557
|
+
var rawMessages = await getSessionMessages(sessionId, options);
|
|
558
|
+
var allMessages = convertSessionMessages(rawMessages);
|
|
559
|
+
historyCache.set(sessionId, { messages: allMessages, time: Date.now() });
|
|
560
|
+
cached = { messages: allMessages, time: Date.now() };
|
|
561
|
+
log.session("getSessionHistoryPage: full load for %s, %d messages", sessionId.slice(0, 8), allMessages.length);
|
|
562
|
+
} catch {
|
|
563
|
+
return { messages: [], hasMore: false };
|
|
564
|
+
}
|
|
565
|
+
}
|
|
500
566
|
if (!cached) return { messages: [], hasMore: false };
|
|
501
567
|
|
|
502
568
|
var endIdx = Math.max(0, beforeIndex);
|