@cryptiklemur/lattice 1.43.5 → 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/client/src/components/chat/ChatView.tsx +11 -1
- package/client/src/hooks/useSession.ts +28 -0
- package/client/src/stores/session.ts +6 -0
- package/package.json +1 -1
- package/server/src/handlers/session.ts +49 -39
- package/server/src/project/session.ts +107 -5
- package/shared/src/messages.ts +18 -0
|
@@ -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.
|
|
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>",
|
|
@@ -20,11 +20,12 @@ import {
|
|
|
20
20
|
getSessionUsage,
|
|
21
21
|
listSessions,
|
|
22
22
|
invalidateSessionCache,
|
|
23
|
+
getSessionHistoryPage,
|
|
23
24
|
loadSessionHistory,
|
|
24
25
|
renameSession,
|
|
25
26
|
} from "../project/session";
|
|
26
27
|
import { getContextBreakdown } from "../project/context-breakdown";
|
|
27
|
-
import { setActiveSession } from "./chat";
|
|
28
|
+
import { setActiveSession, getActiveSession } from "./chat";
|
|
28
29
|
import { setActiveProject } from "./fs";
|
|
29
30
|
import { wasSessionInterrupted, clearInterruptedFlag, isSessionBusy, watchSessionLock, stopExternalSession, getBusyOwner } from "../project/sdk-bridge";
|
|
30
31
|
import { log } from "../logger";
|
|
@@ -79,6 +80,20 @@ 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 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
|
+
});
|
|
93
|
+
});
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
|
|
82
97
|
if (message.type === "session:create") {
|
|
83
98
|
var createMsg = message as SessionCreateMessage;
|
|
84
99
|
var session = createSession(createMsg.projectSlug);
|
|
@@ -91,47 +106,42 @@ registerHandler("session", function (clientId: string, message: ClientMessage) {
|
|
|
91
106
|
setActiveSession(clientId, activateMsg.projectSlug, activateMsg.sessionId);
|
|
92
107
|
setActiveProject(clientId, activateMsg.projectSlug);
|
|
93
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
|
+
|
|
94
135
|
void Promise.all([
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
}),
|
|
99
|
-
getSessionTitle(activateMsg.projectSlug, activateMsg.sessionId).catch(function (err) {
|
|
100
|
-
log.session("Failed to load session title: %O", err);
|
|
101
|
-
return null;
|
|
102
|
-
}),
|
|
103
|
-
getSessionUsage(activateMsg.projectSlug, activateMsg.sessionId).catch(function (err) {
|
|
104
|
-
log.session("Failed to load session usage: %O", err);
|
|
105
|
-
return null;
|
|
106
|
-
}),
|
|
107
|
-
getContextBreakdown(activateMsg.projectSlug, activateMsg.sessionId).catch(function (err) {
|
|
108
|
-
log.session("Failed to load context breakdown: %O", err);
|
|
109
|
-
return null;
|
|
110
|
-
}),
|
|
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; }),
|
|
111
139
|
]).then(function (results) {
|
|
112
140
|
try {
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
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 });
|
|
116
143
|
}
|
|
117
|
-
var
|
|
118
|
-
var busyOwner = busy ? getBusyOwner(activateMsg.sessionId) : undefined;
|
|
119
|
-
sendTo(clientId, {
|
|
120
|
-
type: "session:history",
|
|
121
|
-
projectSlug: activateMsg.projectSlug,
|
|
122
|
-
sessionId: activateMsg.sessionId,
|
|
123
|
-
messages: results[0] || [],
|
|
124
|
-
title: results[1],
|
|
125
|
-
interrupted: interrupted || undefined,
|
|
126
|
-
busy: busy || undefined,
|
|
127
|
-
busyOwner: busyOwner,
|
|
128
|
-
});
|
|
129
|
-
} catch (err) {
|
|
130
|
-
log.session("Error sending session history: %O", err);
|
|
131
|
-
sendTo(clientId, { type: "chat:error", message: "Failed to load session history" });
|
|
132
|
-
}
|
|
133
|
-
try {
|
|
134
|
-
var usage = results[2];
|
|
144
|
+
var usage = results[1];
|
|
135
145
|
if (usage) {
|
|
136
146
|
sendTo(clientId, {
|
|
137
147
|
type: "chat:context_usage",
|
|
@@ -146,7 +156,7 @@ registerHandler("session", function (clientId: string, message: ClientMessage) {
|
|
|
146
156
|
log.session("Error sending context usage: %O", err);
|
|
147
157
|
}
|
|
148
158
|
try {
|
|
149
|
-
var breakdown = results[
|
|
159
|
+
var breakdown = results[2];
|
|
150
160
|
if (breakdown) {
|
|
151
161
|
sendTo(clientId, {
|
|
152
162
|
type: "chat:context_breakdown",
|
|
@@ -457,19 +457,121 @@ export async function getSessionTitle(projectSlug: string, sessionId: string): P
|
|
|
457
457
|
return "Untitled";
|
|
458
458
|
}
|
|
459
459
|
|
|
460
|
-
|
|
460
|
+
var historyCache = new Map<string, { messages: HistoryMessage[]; time: number }>();
|
|
461
|
+
var HISTORY_CACHE_TTL = 60000;
|
|
462
|
+
var INITIAL_MESSAGE_COUNT = 100;
|
|
463
|
+
var TAIL_READ_BYTES = 2 * 1024 * 1024;
|
|
464
|
+
|
|
465
|
+
function getSessionFilePath(projectSlug: string, sessionId: string): string | null {
|
|
461
466
|
var projectPath = getProjectPath(projectSlug);
|
|
462
|
-
|
|
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
|
+
}
|
|
463
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 }> {
|
|
464
504
|
try {
|
|
465
|
-
var
|
|
466
|
-
|
|
505
|
+
var t0 = Date.now();
|
|
506
|
+
var cached = historyCache.get(sessionId);
|
|
507
|
+
if (cached && Date.now() - cached.time < HISTORY_CACHE_TTL) {
|
|
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 };
|
|
513
|
+
}
|
|
514
|
+
|
|
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
|
+
}
|
|
534
|
+
|
|
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
|
|
542
|
+
? allMessages.slice(allMessages.length - INITIAL_MESSAGE_COUNT)
|
|
543
|
+
: allMessages;
|
|
544
|
+
return { messages: tailSlice, totalMessages: allMessages.length, hasMore: allMessages.length > INITIAL_MESSAGE_COUNT };
|
|
467
545
|
} catch (err) {
|
|
468
546
|
log.session("Failed to load session history: %O", err);
|
|
469
|
-
return [];
|
|
547
|
+
return { messages: [], totalMessages: 0, hasMore: false };
|
|
470
548
|
}
|
|
471
549
|
}
|
|
472
550
|
|
|
551
|
+
export async function getSessionHistoryPage(sessionId: string, beforeIndex: number, limit: number, projectSlug?: string): Promise<{ messages: HistoryMessage[]; hasMore: boolean }> {
|
|
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
|
+
}
|
|
566
|
+
if (!cached) return { messages: [], hasMore: false };
|
|
567
|
+
|
|
568
|
+
var endIdx = Math.max(0, beforeIndex);
|
|
569
|
+
var startIdx = Math.max(0, endIdx - limit);
|
|
570
|
+
var page = cached.messages.slice(startIdx, endIdx);
|
|
571
|
+
|
|
572
|
+
return { messages: page, hasMore: startIdx > 0 };
|
|
573
|
+
}
|
|
574
|
+
|
|
473
575
|
export function createSession(projectSlug: string): SessionSummary {
|
|
474
576
|
var sessionId = randomUUID();
|
|
475
577
|
var now = Date.now();
|
package/shared/src/messages.ts
CHANGED
|
@@ -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
|