@cryptiklemur/lattice 1.43.6 → 1.43.7

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.6",
3
+ "version": "1.43.7",
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 page = getSessionHistoryPage(pageMsg.sessionId, pageMsg.before, pageMsg.limit);
86
- sendTo(clientId, {
87
- type: "session:history_page_result",
88
- sessionId: pageMsg.sessionId,
89
- messages: page.messages,
90
- hasMore: page.hasMore,
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,42 @@ 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();
110
+ void loadSessionHistory(activateMsg.projectSlug, activateMsg.sessionId).then(function (historyResult) {
111
+ log.session("session:activate total history load: %dms", Date.now() - activateT0);
112
+ var interrupted = wasSessionInterrupted(activateMsg.sessionId);
113
+ if (interrupted) {
114
+ clearInterruptedFlag(activateMsg.sessionId);
115
+ }
116
+ var busy = isSessionBusy(activateMsg.sessionId);
117
+ var busyOwner = busy ? getBusyOwner(activateMsg.sessionId) : undefined;
118
+ sendTo(clientId, {
119
+ type: "session:history",
120
+ projectSlug: activateMsg.projectSlug,
121
+ sessionId: activateMsg.sessionId,
122
+ messages: historyResult.messages,
123
+ title: null,
124
+ interrupted: interrupted || undefined,
125
+ busy: busy || undefined,
126
+ busyOwner: busyOwner,
127
+ totalMessages: historyResult.totalMessages,
128
+ hasMore: historyResult.hasMore,
129
+ });
130
+ }).catch(function (err) {
131
+ log.session("Error sending session history: %O", err);
132
+ sendTo(clientId, { type: "chat:error", message: "Failed to load session history" });
133
+ });
134
+
107
135
  void Promise.all([
108
- loadSessionHistory(activateMsg.projectSlug, activateMsg.sessionId).catch(function (err) {
109
- log.session("Failed to load session history: %O", err);
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
- }),
136
+ getSessionTitle(activateMsg.projectSlug, activateMsg.sessionId).catch(function () { return null; }),
137
+ getSessionUsage(activateMsg.projectSlug, activateMsg.sessionId).catch(function () { return null; }),
138
+ getContextBreakdown(activateMsg.projectSlug, activateMsg.sessionId).catch(function () { return null; }),
124
139
  ]).then(function (results) {
125
140
  try {
126
- var interrupted = wasSessionInterrupted(activateMsg.sessionId);
127
- if (interrupted) {
128
- clearInterruptedFlag(activateMsg.sessionId);
141
+ if (results[0]) {
142
+ sendTo(clientId, { type: "session:history", projectSlug: activateMsg.projectSlug, sessionId: activateMsg.sessionId, messages: [], title: results[0] as string });
129
143
  }
130
- var busy = isSessionBusy(activateMsg.sessionId);
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" });
148
- }
149
- try {
150
- var usage = results[2];
144
+ var usage = results[1];
151
145
  if (usage) {
152
146
  sendTo(clientId, {
153
147
  type: "chat:context_usage",
@@ -162,7 +156,7 @@ registerHandler("session", function (clientId: string, message: ClientMessage) {
162
156
  log.session("Error sending context usage: %O", err);
163
157
  }
164
158
  try {
165
- var breakdown = results[3];
159
+ var breakdown = results[2];
166
160
  if (breakdown) {
167
161
  sendTo(clientId, {
168
162
  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 = 30000;
461
+ var HISTORY_CACHE_TTL = 60000;
462
462
  var INITIAL_MESSAGE_COUNT = 100;
463
+ var TAIL_READ_BYTES = 2 * 1024 * 1024;
463
464
 
464
- export async function loadSessionHistory(projectSlug: string, sessionId: string): Promise<{ messages: HistoryMessage[]; totalMessages: number; hasMore: boolean }> {
465
+ function getSessionFilePath(projectSlug: string, sessionId: string): string | null {
465
466
  var projectPath = getProjectPath(projectSlug);
466
- var options = projectPath ? { dir: projectPath } : undefined;
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
- allMessages = cached.messages;
475
- } else {
476
- var rawMessages = await getSessionMessages(sessionId, options);
477
- allMessages = convertSessionMessages(rawMessages);
478
- historyCache.set(sessionId, { messages: allMessages, time: Date.now() });
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
- log.session("loadSessionHistory %s: %dms, %d total messages", sessionId.slice(0, 8), Date.now() - t0, allMessages.length);
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 tail = allMessages.length > INITIAL_MESSAGE_COUNT
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);