@cryptiklemur/lattice 1.43.5 → 1.43.6

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.
@@ -23,7 +23,7 @@ import { useBookmarks } from "../../hooks/useBookmarks";
23
23
  import { formatSessionTitle } from "../../utils/formatSessionTitle";
24
24
 
25
25
  export function ChatView({ sessionId: tabSessionId, projectSlug: tabProjectSlug }: { sessionId?: string; projectSlug?: string } = {}) {
26
- var { messages, isProcessing, sendMessage, activeSessionId, activeSessionTitle, currentStatus, contextUsage, contextBreakdown, lastResponseCost, lastResponseDuration, historyLoading, wasInterrupted, promptSuggestion, failedInput, clearFailedInput, messageQueue, enqueueMessage, removeQueuedMessage, updateQueuedMessage, isBusy, busyOwner, isPlanMode, pendingPrefill, activateSession, budgetStatus, budgetExceeded, sendBudgetOverride, dismissBudgetExceeded } = useSession();
26
+ var { messages, isProcessing, sendMessage, activeSessionId, activeSessionTitle, currentStatus, contextUsage, contextBreakdown, lastResponseCost, lastResponseDuration, historyLoading, historyHasMore, loadMoreHistory, wasInterrupted, promptSuggestion, failedInput, clearFailedInput, messageQueue, enqueueMessage, removeQueuedMessage, updateQueuedMessage, isBusy, busyOwner, isPlanMode, pendingPrefill, activateSession, budgetStatus, budgetExceeded, sendBudgetOverride, dismissBudgetExceeded } = useSession();
27
27
  var { activeProject } = useProjects();
28
28
  var { toggleDrawer } = useSidebar();
29
29
 
@@ -735,6 +735,16 @@ export function ChatView({ sessionId: tabSessionId, projectSlug: tabProjectSlug
735
735
  aria-relevant="additions"
736
736
  style={{ WebkitOverflowScrolling: "touch", touchAction: "pan-y" }}
737
737
  >
738
+ {historyHasMore && messages.length > 0 && (
739
+ <div className="flex justify-center py-3">
740
+ <button
741
+ onClick={loadMoreHistory}
742
+ className="text-[11px] text-base-content/30 hover:text-base-content/50 font-mono transition-colors"
743
+ >
744
+ Load older messages
745
+ </button>
746
+ </div>
747
+ )}
738
748
  {messages.length === 0 && historyLoading ? (
739
749
  <div className="flex items-center justify-center h-full">
740
750
  <div className="flex flex-col items-center gap-3">
@@ -78,6 +78,8 @@ export interface UseSessionReturn extends SessionState {
78
78
  clearMessageQueue: () => void;
79
79
  sendBudgetOverride: () => void;
80
80
  dismissBudgetExceeded: () => void;
81
+ loadMoreHistory: () => void;
82
+ historyHasMore: boolean;
81
83
  }
82
84
 
83
85
  export function useSession(): UseSessionReturn {
@@ -276,6 +278,19 @@ export function useSession(): UseSessionReturn {
276
278
  updatePermissionStatus(m.requestId, m.status);
277
279
  }
278
280
 
281
+ function handleHistoryPage(msg: ServerMessage) {
282
+ var m = msg as { type: string; sessionId: string; messages: HistoryMessage[]; hasMore: boolean };
283
+ var state = getSessionStore().state;
284
+ if (m.sessionId !== state.activeSessionId) return;
285
+ getSessionStore().setState(function (s) {
286
+ return {
287
+ ...s,
288
+ messages: mergeToolResults(m.messages).concat(s.messages),
289
+ historyHasMore: m.hasMore,
290
+ };
291
+ });
292
+ }
293
+
279
294
  function handleHistory(msg: ServerMessage) {
280
295
  var m = msg as SessionHistoryMessage;
281
296
  setCurrentAssistantUuid(null);
@@ -303,6 +318,8 @@ export function useSession(): UseSessionReturn {
303
318
  lastResponseDuration: null,
304
319
  lastReadIndex: null,
305
320
  historyLoading: false,
321
+ historyHasMore: m.hasMore || false,
322
+ historyTotalMessages: m.totalMessages || m.messages.length,
306
323
  wasInterrupted: m.interrupted || false,
307
324
  isBusy: m.busy || false,
308
325
  busyOwner: m.busyOwner ?? null,
@@ -390,6 +407,7 @@ export function useSession(): UseSessionReturn {
390
407
  subscribe("chat:context_usage", handleContextUsage);
391
408
  subscribe("chat:context_breakdown", handleContextBreakdown);
392
409
  subscribe("session:history", handleHistory);
410
+ subscribe("session:history_page_result", handleHistoryPage);
393
411
  subscribe("chat:prompt_suggestion", handlePromptSuggestion);
394
412
  subscribe("session:busy", handleSessionBusy);
395
413
  subscribe("chat:prompt_request", handlePromptRequest);
@@ -413,6 +431,7 @@ export function useSession(): UseSessionReturn {
413
431
  unsubscribe("chat:context_usage", handleContextUsage);
414
432
  unsubscribe("chat:context_breakdown", handleContextBreakdown);
415
433
  unsubscribe("session:history", handleHistory);
434
+ unsubscribe("session:history_page_result", handleHistoryPage);
416
435
  unsubscribe("chat:prompt_suggestion", handlePromptSuggestion);
417
436
  unsubscribe("session:busy", handleSessionBusy);
418
437
  unsubscribe("chat:prompt_request", handlePromptRequest);
@@ -441,6 +460,15 @@ export function useSession(): UseSessionReturn {
441
460
  lastResponseDuration: state.lastResponseDuration,
442
461
  lastReadIndex: state.lastReadIndex,
443
462
  historyLoading: state.historyLoading,
463
+ historyHasMore: state.historyHasMore,
464
+ historyTotalMessages: state.historyTotalMessages,
465
+ loadMoreHistory: function () {
466
+ if (!state.historyHasMore || !state.activeSessionId) return;
467
+ var totalMessages = state.historyTotalMessages;
468
+ var loadedCount = state.messages.length;
469
+ var beforeIndex = totalMessages - loadedCount;
470
+ sendRef.current({ type: "session:history_page", sessionId: state.activeSessionId, before: beforeIndex, limit: 100 } as any);
471
+ },
444
472
  wasInterrupted: state.wasInterrupted,
445
473
  promptSuggestion: state.promptSuggestion,
446
474
  failedInput: state.failedInput,
@@ -42,6 +42,8 @@ export interface SessionState {
42
42
  lastResponseDuration: number | null;
43
43
  lastReadIndex: number | null;
44
44
  historyLoading: boolean;
45
+ historyHasMore: boolean;
46
+ historyTotalMessages: number;
45
47
  wasInterrupted: boolean;
46
48
  promptSuggestion: string | null;
47
49
  failedInput: string | null;
@@ -68,6 +70,8 @@ var sessionStore = new Store<SessionState>({
68
70
  lastResponseDuration: null,
69
71
  lastReadIndex: null,
70
72
  historyLoading: false,
73
+ historyHasMore: false,
74
+ historyTotalMessages: 0,
71
75
  wasInterrupted: false,
72
76
  promptSuggestion: null,
73
77
  failedInput: null,
@@ -293,6 +297,8 @@ export function clearSession(): void {
293
297
  lastResponseDuration: null,
294
298
  lastReadIndex: null,
295
299
  historyLoading: false,
300
+ historyHasMore: false,
301
+ historyTotalMessages: 0,
296
302
  wasInterrupted: false,
297
303
  promptSuggestion: null,
298
304
  failedInput: null,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cryptiklemur/lattice",
3
- "version": "1.43.5",
3
+ "version": "1.43.6",
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>",
@@ -20,6 +20,7 @@ import {
20
20
  getSessionUsage,
21
21
  listSessions,
22
22
  invalidateSessionCache,
23
+ getSessionHistoryPage,
23
24
  loadSessionHistory,
24
25
  renameSession,
25
26
  } from "../project/session";
@@ -79,6 +80,18 @@ registerHandler("session", function (clientId: string, message: ClientMessage) {
79
80
  return;
80
81
  }
81
82
 
83
+ if (message.type === "session:history_page") {
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,
91
+ });
92
+ return;
93
+ }
94
+
82
95
  if (message.type === "session:create") {
83
96
  var createMsg = message as SessionCreateMessage;
84
97
  var session = createSession(createMsg.projectSlug);
@@ -116,15 +129,18 @@ registerHandler("session", function (clientId: string, message: ClientMessage) {
116
129
  }
117
130
  var busy = isSessionBusy(activateMsg.sessionId);
118
131
  var busyOwner = busy ? getBusyOwner(activateMsg.sessionId) : undefined;
132
+ var historyResult = results[0] || { messages: [], totalMessages: 0, hasMore: false };
119
133
  sendTo(clientId, {
120
134
  type: "session:history",
121
135
  projectSlug: activateMsg.projectSlug,
122
136
  sessionId: activateMsg.sessionId,
123
- messages: results[0] || [],
137
+ messages: historyResult.messages,
124
138
  title: results[1],
125
139
  interrupted: interrupted || undefined,
126
140
  busy: busy || undefined,
127
141
  busyOwner: busyOwner,
142
+ totalMessages: historyResult.totalMessages,
143
+ hasMore: historyResult.hasMore,
128
144
  });
129
145
  } catch (err) {
130
146
  log.session("Error sending session history: %O", err);
@@ -457,19 +457,55 @@ export async function getSessionTitle(projectSlug: string, sessionId: string): P
457
457
  return "Untitled";
458
458
  }
459
459
 
460
- export async function loadSessionHistory(projectSlug: string, sessionId: string): Promise<HistoryMessage[]> {
460
+ var historyCache = new Map<string, { messages: HistoryMessage[]; time: number }>();
461
+ var HISTORY_CACHE_TTL = 30000;
462
+ var INITIAL_MESSAGE_COUNT = 100;
463
+
464
+ export async function loadSessionHistory(projectSlug: string, sessionId: string): Promise<{ messages: HistoryMessage[]; totalMessages: number; hasMore: boolean }> {
461
465
  var projectPath = getProjectPath(projectSlug);
462
466
  var options = projectPath ? { dir: projectPath } : undefined;
463
467
 
464
468
  try {
465
- var messages = await getSessionMessages(sessionId, options);
466
- return convertSessionMessages(messages);
469
+ var t0 = Date.now();
470
+ var cached = historyCache.get(sessionId);
471
+ var allMessages: HistoryMessage[];
472
+
473
+ 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() });
479
+ }
480
+
481
+ log.session("loadSessionHistory %s: %dms, %d total messages", sessionId.slice(0, 8), Date.now() - t0, allMessages.length);
482
+
483
+ var tail = allMessages.length > INITIAL_MESSAGE_COUNT
484
+ ? allMessages.slice(allMessages.length - INITIAL_MESSAGE_COUNT)
485
+ : allMessages;
486
+
487
+ return {
488
+ messages: tail,
489
+ totalMessages: allMessages.length,
490
+ hasMore: allMessages.length > INITIAL_MESSAGE_COUNT,
491
+ };
467
492
  } catch (err) {
468
493
  log.session("Failed to load session history: %O", err);
469
- return [];
494
+ return { messages: [], totalMessages: 0, hasMore: false };
470
495
  }
471
496
  }
472
497
 
498
+ export function getSessionHistoryPage(sessionId: string, beforeIndex: number, limit: number): { messages: HistoryMessage[]; hasMore: boolean } {
499
+ var cached = historyCache.get(sessionId);
500
+ if (!cached) return { messages: [], hasMore: false };
501
+
502
+ var endIdx = Math.max(0, beforeIndex);
503
+ var startIdx = Math.max(0, endIdx - limit);
504
+ var page = cached.messages.slice(startIdx, endIdx);
505
+
506
+ return { messages: page, hasMore: startIdx > 0 };
507
+ }
508
+
473
509
  export function createSession(projectSlug: string): SessionSummary {
474
510
  var sessionId = randomUUID();
475
511
  var now = Date.now();
@@ -50,6 +50,20 @@ export interface SessionListRequestMessage {
50
50
  limit?: number;
51
51
  }
52
52
 
53
+ export interface SessionHistoryPageMessage {
54
+ type: "session:history_page";
55
+ sessionId: string;
56
+ before: number;
57
+ limit: number;
58
+ }
59
+
60
+ export interface SessionHistoryPageResultMessage {
61
+ type: "session:history_page_result";
62
+ sessionId: string;
63
+ messages: HistoryMessage[];
64
+ hasMore: boolean;
65
+ }
66
+
53
67
  export interface SessionPreviewRequestMessage {
54
68
  type: "session:preview_request";
55
69
  projectSlug: string;
@@ -578,6 +592,7 @@ export type ClientMessage =
578
592
  | EditorEnsureProjectMessage
579
593
  | ChatPromptResponseMessage
580
594
  | AnalyticsRequestMessage
595
+ | SessionHistoryPageMessage
581
596
  | SessionPreviewRequestMessage
582
597
  | BookmarkListMessage
583
598
  | BookmarkAddMessage
@@ -623,6 +638,8 @@ export interface SessionHistoryMessage {
623
638
  interrupted?: boolean;
624
639
  busy?: boolean;
625
640
  busyOwner?: "cli" | "lattice";
641
+ totalMessages?: number;
642
+ hasMore?: boolean;
626
643
  }
627
644
 
628
645
  export interface SessionBusyMessage {
@@ -1076,6 +1093,7 @@ export type ServerMessage =
1076
1093
  | AnalyticsDataMessage
1077
1094
  | AnalyticsErrorMessage
1078
1095
  | SessionPreviewMessage
1096
+ | SessionHistoryPageResultMessage
1079
1097
  | BookmarkListResultMessage
1080
1098
  | BudgetStatusMessage
1081
1099
  | BudgetExceededMessage