@yeaft/webchat-agent 1.0.335 → 1.0.337
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/local-runtime/server/handlers/agent-output.js +15 -0
- package/local-runtime/server/handlers/client-conversation.js +13 -1
- package/local-runtime/version.json +1 -1
- package/local-runtime/web/app.bundle.js +93 -94
- package/local-runtime/web/app.bundle.js.gz +0 -0
- package/local-runtime/web/index.html +1 -1
- package/package.json +1 -1
- package/yeaft/conversation/history-index-state.js +294 -0
- package/yeaft/conversation/history-index-worker.js +574 -0
- package/yeaft/conversation/history-index.js +530 -0
- package/yeaft/conversation/persist.js +197 -94
- package/yeaft/conversation/visible-entry.js +143 -0
- package/yeaft/sessions/session-crud.js +65 -2
- package/yeaft/web-bridge.js +199 -23
|
@@ -57,6 +57,11 @@ import { seedDefaultSession, DEFAULT_SESSION_ID } from './seed-default.js';
|
|
|
57
57
|
import { nextSessionId, validateVpId, isReservedVpId } from './ids.js';
|
|
58
58
|
import { scanVpLibrary, DEFAULT_VP_LIB_DIR } from '../vp/vp-store.js';
|
|
59
59
|
import { seedSummaryIfMissingSync, removeScopeDirSync } from '../memory/store.js';
|
|
60
|
+
import {
|
|
61
|
+
markConversationDirty,
|
|
62
|
+
removeConversationIndexScope,
|
|
63
|
+
} from '../conversation/history-index-state.js';
|
|
64
|
+
import { retireConversationHistoryIndex } from '../conversation/history-index.js';
|
|
60
65
|
import { ensureSessionConfigFile, saveSessionConfig, loadSessionConfig } from './session-config.js';
|
|
61
66
|
import { repairSessionStore } from './recovery.js';
|
|
62
67
|
import {
|
|
@@ -75,6 +80,40 @@ import {
|
|
|
75
80
|
*/
|
|
76
81
|
const DEFAULT_MEMORY_ROOT = join(homedir(), '.yeaft', 'memory');
|
|
77
82
|
|
|
83
|
+
function markSessionConversationDirty(ownerRoot, sessionId, reason, paths = {}) {
|
|
84
|
+
try {
|
|
85
|
+
markConversationDirty({
|
|
86
|
+
ownerRoot,
|
|
87
|
+
scopeKind: 'session',
|
|
88
|
+
scopeId: sessionId,
|
|
89
|
+
reason,
|
|
90
|
+
...paths,
|
|
91
|
+
});
|
|
92
|
+
} catch (error) {
|
|
93
|
+
console.warn(`[session-crud] failed to mark conversation index dirty for ${sessionId}:`, error?.message || error);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function invalidateSessionConversationIndex(ownerRoots, sessionId, reason, paths = {}) {
|
|
98
|
+
for (const ownerRoot of new Set((ownerRoots || []).filter(Boolean))) {
|
|
99
|
+
retireConversationHistoryIndex(ownerRoot, sessionId).catch(error => {
|
|
100
|
+
console.warn(
|
|
101
|
+
`[session-crud] failed to retire conversation index for ${sessionId}:`,
|
|
102
|
+
error?.message || error,
|
|
103
|
+
);
|
|
104
|
+
});
|
|
105
|
+
try {
|
|
106
|
+
removeConversationIndexScope(ownerRoot, sessionId);
|
|
107
|
+
} catch (error) {
|
|
108
|
+
console.warn(
|
|
109
|
+
`[session-crud] failed to remove conversation index for ${sessionId}:`,
|
|
110
|
+
error?.message || error,
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
markSessionConversationDirty(ownerRoot, sessionId, reason, paths);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
78
117
|
/**
|
|
79
118
|
* Build the group seed summary body. Uses the group display name + roster
|
|
80
119
|
* so even an empty conversation has SOMETHING for engine.#prepareAms to
|
|
@@ -353,6 +392,12 @@ export function restoreSessionToRegistry(defaultYeaftDir, sessionId, workDir) {
|
|
|
353
392
|
handle.close();
|
|
354
393
|
}
|
|
355
394
|
copySessionExtras(projectYeaftDir, defaultYeaftDir, sessionId);
|
|
395
|
+
invalidateSessionConversationIndex(
|
|
396
|
+
[defaultYeaftDir],
|
|
397
|
+
sessionId,
|
|
398
|
+
'restore-session',
|
|
399
|
+
{ oldPath: sourceDir, newPath: destDir },
|
|
400
|
+
);
|
|
356
401
|
addOrUpdateManifestSession(defaultYeaftDir, importedMeta, destDir);
|
|
357
402
|
unregisterSessionWorkDir(defaultYeaftDir, sessionId);
|
|
358
403
|
return importedMeta;
|
|
@@ -598,9 +643,15 @@ export function archiveSession(yeaftDir, sessionId) {
|
|
|
598
643
|
const groupYeaftDir = resolveSessionYeaftDir(yeaftDir, sessionId);
|
|
599
644
|
const root = sessionsRoot(groupYeaftDir);
|
|
600
645
|
const srcDir = join(root, sessionId);
|
|
601
|
-
// Idempotent
|
|
602
|
-
//
|
|
646
|
+
// Idempotent source deletion must still invalidate every derived generation:
|
|
647
|
+
// an active query worker may otherwise serve history after the directory is gone.
|
|
603
648
|
if (!existsSync(srcDir) || !loadSessionMeta(srcDir)) {
|
|
649
|
+
invalidateSessionConversationIndex(
|
|
650
|
+
[groupYeaftDir, yeaftDir],
|
|
651
|
+
sessionId,
|
|
652
|
+
'archive-session',
|
|
653
|
+
{ oldPath: srcDir },
|
|
654
|
+
);
|
|
604
655
|
unregisterSessionWorkDir(yeaftDir, sessionId);
|
|
605
656
|
removeManifestSession(yeaftDir, sessionId);
|
|
606
657
|
return { sessionId, archivedAs: null, alreadyGone: true };
|
|
@@ -610,6 +661,12 @@ export function archiveSession(yeaftDir, sessionId) {
|
|
|
610
661
|
const suffix = randomBytes(2).toString('hex');
|
|
611
662
|
const dstDir = join(root, `.archived-${ts}-${suffix}-${sessionId}`);
|
|
612
663
|
renameSync(srcDir, dstDir);
|
|
664
|
+
invalidateSessionConversationIndex(
|
|
665
|
+
[groupYeaftDir, yeaftDir],
|
|
666
|
+
sessionId,
|
|
667
|
+
'archive-session',
|
|
668
|
+
{ oldPath: srcDir, newPath: dstDir },
|
|
669
|
+
);
|
|
613
670
|
unregisterSessionWorkDir(yeaftDir, sessionId);
|
|
614
671
|
removeManifestSession(yeaftDir, sessionId);
|
|
615
672
|
return { sessionId, archivedAs: dstDir, alreadyGone: false };
|
|
@@ -669,6 +726,12 @@ export function deleteSession(yeaftDir, sessionId, options = {}) {
|
|
|
669
726
|
console.warn(`[session-crud] failed to remove memory dir for ${sessionId}:`, err?.message || err);
|
|
670
727
|
}
|
|
671
728
|
|
|
729
|
+
invalidateSessionConversationIndex(
|
|
730
|
+
[groupYeaftDir, yeaftDir],
|
|
731
|
+
sessionId,
|
|
732
|
+
'delete-session',
|
|
733
|
+
{ oldPath: srcDir },
|
|
734
|
+
);
|
|
672
735
|
unregisterSessionWorkDir(yeaftDir, sessionId);
|
|
673
736
|
removeManifestSession(yeaftDir, sessionId);
|
|
674
737
|
return {
|
package/yeaft/web-bridge.js
CHANGED
|
@@ -63,6 +63,11 @@ import {
|
|
|
63
63
|
import { persistYeaftAttachments, attachmentsForPersistence, persistedAttachmentPreviewPayload } from './attachments.js';
|
|
64
64
|
import { normalizeSessionMessageQuote, sessionMessageQuotePrompt } from './session-message-quote.js';
|
|
65
65
|
import { ConversationStore, parseSeqFromId, projectVisibleSessionMessages } from './conversation/persist.js';
|
|
66
|
+
import {
|
|
67
|
+
loadConversationOutlineFromIndex,
|
|
68
|
+
readConversationIndexWindow,
|
|
69
|
+
searchConversationIndex,
|
|
70
|
+
} from './conversation/history-index.js';
|
|
66
71
|
import { isHiddenConversationRow, isVisibleConversationRow } from './conversation/internal-control.js';
|
|
67
72
|
import {
|
|
68
73
|
ProjectStoreError,
|
|
@@ -1319,16 +1324,17 @@ function hydrateHistoryAttachmentPreviews(attachments) {
|
|
|
1319
1324
|
});
|
|
1320
1325
|
}
|
|
1321
1326
|
|
|
1322
|
-
function loadVisibleGroupHistoryPage(store, sessionId, limit, beforeSeq = null) {
|
|
1327
|
+
function loadVisibleGroupHistoryPage(store, sessionId, limit, beforeSeq = null, opts = {}) {
|
|
1323
1328
|
if (!store || !sessionId || !(limit > 0)) return { messages: [], oldestSeq: null, hasMore: false };
|
|
1324
1329
|
|
|
1325
1330
|
let rows = [];
|
|
1326
1331
|
try {
|
|
1327
1332
|
if (typeof store.loadVisibleBySession === 'function') {
|
|
1328
|
-
const page = store.loadVisibleBySession(sessionId, beforeSeq, limit);
|
|
1333
|
+
const page = store.loadVisibleBySession(sessionId, beforeSeq, limit, opts);
|
|
1329
1334
|
return {
|
|
1330
1335
|
messages: (page.messages || []).map(projectPersistedToVisibleHistoryEntry).filter(Boolean),
|
|
1331
1336
|
oldestSeq: (typeof page.oldestSeq === 'number') ? page.oldestSeq : null,
|
|
1337
|
+
nextBeforeSeq: (typeof page.nextBeforeSeq === 'number') ? page.nextBeforeSeq : null,
|
|
1332
1338
|
hasMore: !!page.hasMore,
|
|
1333
1339
|
};
|
|
1334
1340
|
} else if (typeof store.loadOlderBySession === 'function') {
|
|
@@ -1364,6 +1370,7 @@ function loadVisibleGroupHistoryPage(store, sessionId, limit, beforeSeq = null)
|
|
|
1364
1370
|
return {
|
|
1365
1371
|
messages,
|
|
1366
1372
|
oldestSeq: Number.isFinite(oldestSeq) ? oldestSeq : null,
|
|
1373
|
+
nextBeforeSeq: Number.isFinite(oldestSeq) ? oldestSeq : null,
|
|
1367
1374
|
hasMore,
|
|
1368
1375
|
};
|
|
1369
1376
|
}
|
|
@@ -1382,6 +1389,7 @@ function projectVisibleHistoryChunkMessages(messages = []) {
|
|
|
1382
1389
|
.filter(Boolean)
|
|
1383
1390
|
.map(m => ({
|
|
1384
1391
|
...(m.id ? { id: m.id } : {}),
|
|
1392
|
+
...(Number.isFinite(m.seq) ? { seq: m.seq } : {}),
|
|
1385
1393
|
role: m.role,
|
|
1386
1394
|
content: m.content,
|
|
1387
1395
|
ts: m.ts || null,
|
|
@@ -1405,7 +1413,7 @@ function projectVisibleHistoryChunkMessages(messages = []) {
|
|
|
1405
1413
|
}));
|
|
1406
1414
|
}
|
|
1407
1415
|
|
|
1408
|
-
function emitHistoryChunk({ sessionId, messages, mode = 'older', oldestSeq = null, hasMore = false, latestSeq = null, afterSeq = null, turns = null, requestId = null, requestClientId = null, perfTraceId = null }) {
|
|
1416
|
+
function emitHistoryChunk({ sessionId, messages, mode = 'older', oldestSeq = null, nextBeforeSeq = null, hasMore = false, latestSeq = null, afterSeq = null, turns = null, pageKind = null, gapStopAtSeq = null, cacheEpoch = null, requestId = null, requestClientId = null, perfTraceId = null }) {
|
|
1409
1417
|
const projectedMessages = projectVisibleHistoryChunkMessages(messages);
|
|
1410
1418
|
// Empty deltas still carry the authoritative safe cursor and clear the
|
|
1411
1419
|
// browser's syncingAfterSeq fence. Dropping this envelope leaves Session
|
|
@@ -1420,10 +1428,14 @@ function emitHistoryChunk({ sessionId, messages, mode = 'older', oldestSeq = nul
|
|
|
1420
1428
|
mode,
|
|
1421
1429
|
messages: projectedMessages,
|
|
1422
1430
|
oldestSeq,
|
|
1431
|
+
nextBeforeSeq: Number.isFinite(nextBeforeSeq) ? nextBeforeSeq : oldestSeq,
|
|
1423
1432
|
hasMore: !!hasMore,
|
|
1424
1433
|
latestSeq,
|
|
1425
1434
|
afterSeq,
|
|
1426
1435
|
turns,
|
|
1436
|
+
...(pageKind ? { pageKind } : {}),
|
|
1437
|
+
...(Number.isFinite(gapStopAtSeq) ? { gapStopAtSeq } : {}),
|
|
1438
|
+
...(Number.isFinite(cacheEpoch) ? { cacheEpoch } : {}),
|
|
1427
1439
|
});
|
|
1428
1440
|
return projectedMessages;
|
|
1429
1441
|
}
|
|
@@ -1497,6 +1509,7 @@ function emitVisibleHistoryReplay({ store, sessionId, limit, beforeSeq = null, m
|
|
|
1497
1509
|
messages: replayEntries,
|
|
1498
1510
|
mode,
|
|
1499
1511
|
oldestSeq: visiblePage.oldestSeq,
|
|
1512
|
+
nextBeforeSeq: visiblePage.nextBeforeSeq,
|
|
1500
1513
|
hasMore: visiblePage.hasMore,
|
|
1501
1514
|
latestSeq: Number.isFinite(latestSeq) ? latestSeq : null,
|
|
1502
1515
|
turns: limit,
|
|
@@ -1512,6 +1525,7 @@ function emitVisibleHistoryReplay({ store, sessionId, limit, beforeSeq = null, m
|
|
|
1512
1525
|
requestId,
|
|
1513
1526
|
hasMore: visiblePage.hasMore,
|
|
1514
1527
|
oldestSeq: visiblePage.oldestSeq,
|
|
1528
|
+
nextBeforeSeq: visiblePage.nextBeforeSeq,
|
|
1515
1529
|
latestSeq: Number.isFinite(latestSeq) ? latestSeq : null,
|
|
1516
1530
|
}, { sessionId, requestId, requestClientId, perfTraceId });
|
|
1517
1531
|
return;
|
|
@@ -6800,6 +6814,7 @@ export async function handleYeaftLoadHistory(msg) {
|
|
|
6800
6814
|
messages: replayEntries,
|
|
6801
6815
|
mode: 'recent',
|
|
6802
6816
|
oldestSeq: visiblePage.oldestSeq,
|
|
6817
|
+
nextBeforeSeq: visiblePage.nextBeforeSeq,
|
|
6803
6818
|
hasMore: visiblePage.hasMore,
|
|
6804
6819
|
latestSeq,
|
|
6805
6820
|
turns: limit,
|
|
@@ -6842,6 +6857,7 @@ export async function handleYeaftLoadHistory(msg) {
|
|
|
6842
6857
|
requestId,
|
|
6843
6858
|
hasMore,
|
|
6844
6859
|
oldestSeq,
|
|
6860
|
+
nextBeforeSeq: visiblePage.nextBeforeSeq,
|
|
6845
6861
|
latestSeq,
|
|
6846
6862
|
}, { sessionId, requestId, requestClientId, perfTraceId });
|
|
6847
6863
|
};
|
|
@@ -6942,13 +6958,45 @@ export async function handleYeaftLoadHistory(msg) {
|
|
|
6942
6958
|
traceDuration('history.handler_total', perfStart, { ok: true });
|
|
6943
6959
|
}
|
|
6944
6960
|
|
|
6961
|
+
function traceHistoryScan({ perfTraceId, phase, sessionId, messageType, start, scanStats, detail = null }) {
|
|
6962
|
+
if (!perfTraceId) return;
|
|
6963
|
+
recordAgentPerfTrace(ctx.CONFIG, {
|
|
6964
|
+
traceId: perfTraceId,
|
|
6965
|
+
phase,
|
|
6966
|
+
sessionId,
|
|
6967
|
+
messageType,
|
|
6968
|
+
durationMs: perfNowMs() - start,
|
|
6969
|
+
bytes: Number(scanStats?.bytes) || 0,
|
|
6970
|
+
detail: {
|
|
6971
|
+
segments: Number(scanStats?.segments) || 0,
|
|
6972
|
+
rows: Number(scanStats?.rows) || 0,
|
|
6973
|
+
legacyFiles: Number(scanStats?.legacyFiles) || 0,
|
|
6974
|
+
...(detail || {}),
|
|
6975
|
+
},
|
|
6976
|
+
});
|
|
6977
|
+
}
|
|
6978
|
+
|
|
6979
|
+
function scheduleHistoryEventLoopTrace({ perfTraceId, phase, sessionId, messageType }) {
|
|
6980
|
+
if (!perfTraceId) return;
|
|
6981
|
+
const scheduledAt = perfNowMs();
|
|
6982
|
+
setImmediate(() => {
|
|
6983
|
+
recordAgentPerfTrace(ctx.CONFIG, {
|
|
6984
|
+
traceId: perfTraceId,
|
|
6985
|
+
phase,
|
|
6986
|
+
sessionId,
|
|
6987
|
+
messageType,
|
|
6988
|
+
durationMs: perfNowMs() - scheduledAt,
|
|
6989
|
+
});
|
|
6990
|
+
});
|
|
6991
|
+
}
|
|
6992
|
+
|
|
6945
6993
|
/**
|
|
6946
6994
|
* Load one lightweight Conversation Outline page. The response contains only
|
|
6947
6995
|
* visible user/assistant metadata and bounded snippets; full message bodies and
|
|
6948
6996
|
* tool payloads stay on the Agent. `beforeSeq` is an exclusive older-page
|
|
6949
|
-
* cursor, and
|
|
6997
|
+
* cursor, and total counting is explicit because it scans the full transcript.
|
|
6950
6998
|
*
|
|
6951
|
-
* @param {object} msg — { sessionId, beforeSeq, limit, includeTotal }
|
|
6999
|
+
* @param {object} msg — { sessionId, beforeSeq, limit, includeTotal, perfTraceId }
|
|
6952
7000
|
*/
|
|
6953
7001
|
export async function handleYeaftLoadHistoryOutline(msg) {
|
|
6954
7002
|
const sessionId = typeof msg?.sessionId === 'string' ? msg.sessionId.trim() : '';
|
|
@@ -6971,16 +7019,59 @@ export async function handleYeaftLoadHistoryOutline(msg) {
|
|
|
6971
7019
|
return;
|
|
6972
7020
|
}
|
|
6973
7021
|
|
|
7022
|
+
const perfTraceId = typeof msg?.perfTraceId === 'string' && msg.perfTraceId.trim() ? msg.perfTraceId.trim() : null;
|
|
7023
|
+
const scanStats = {};
|
|
7024
|
+
scheduleHistoryEventLoopTrace({
|
|
7025
|
+
perfTraceId,
|
|
7026
|
+
phase: 'history_outline.event_loop_delay',
|
|
7027
|
+
sessionId,
|
|
7028
|
+
messageType: msg?.type || 'yeaft_load_history_outline',
|
|
7029
|
+
});
|
|
7030
|
+
const scanStart = perfNowMs();
|
|
6974
7031
|
try {
|
|
6975
7032
|
const defaultYeaftDir = ctx.CONFIG?.yeaftDir || DEFAULT_YEAFT_DIR;
|
|
6976
7033
|
const storeDir = resolveSessionYeaftDir(defaultYeaftDir, sessionId);
|
|
6977
|
-
|
|
6978
|
-
|
|
6979
|
-
|
|
6980
|
-
|
|
6981
|
-
|
|
7034
|
+
let result;
|
|
7035
|
+
let indexFallback = false;
|
|
7036
|
+
try {
|
|
7037
|
+
result = await loadConversationOutlineFromIndex(storeDir, sessionId, {
|
|
7038
|
+
limit,
|
|
7039
|
+
beforeSeq,
|
|
7040
|
+
cursor: msg?.cursor || null,
|
|
7041
|
+
includeTotal: msg?.includeTotal === true,
|
|
7042
|
+
_waitForBuild: false,
|
|
7043
|
+
});
|
|
7044
|
+
} catch (indexError) {
|
|
7045
|
+
sendToServer({
|
|
7046
|
+
...response,
|
|
7047
|
+
error: indexError?.code === 'stale_result'
|
|
7048
|
+
? 'stale_result'
|
|
7049
|
+
: (indexError?.code === 'index_building' ? 'index_building' : 'index_unavailable'),
|
|
7050
|
+
...(perfTraceId ? { perfTraceId } : {}),
|
|
7051
|
+
});
|
|
7052
|
+
return;
|
|
7053
|
+
}
|
|
7054
|
+
if (!indexFallback) {
|
|
7055
|
+
scanStats.segments = Number(result.indexSourceFiles) || 0;
|
|
7056
|
+
scanStats.bytes = Number(result.indexSourceBytes) || 0;
|
|
7057
|
+
scanStats.rows = Number(result.indexEntryCount) || 0;
|
|
7058
|
+
}
|
|
7059
|
+
traceHistoryScan({
|
|
7060
|
+
perfTraceId,
|
|
7061
|
+
phase: 'history_outline.store_scan',
|
|
7062
|
+
sessionId,
|
|
7063
|
+
messageType: msg?.type || 'yeaft_load_history_outline',
|
|
7064
|
+
start: scanStart,
|
|
7065
|
+
scanStats,
|
|
7066
|
+
detail: {
|
|
7067
|
+
limit,
|
|
7068
|
+
includeTotal: msg?.includeTotal === true,
|
|
7069
|
+
resultCount: result.results.length,
|
|
7070
|
+
indexGeneration: result.indexGeneration || null,
|
|
7071
|
+
indexFallback,
|
|
7072
|
+
},
|
|
6982
7073
|
});
|
|
6983
|
-
sendToServer({ ...response, ...result });
|
|
7074
|
+
sendToServer({ ...response, ...result, ...(perfTraceId ? { perfTraceId } : {}) });
|
|
6984
7075
|
} catch (err) {
|
|
6985
7076
|
console.error('[Yeaft] Session history outline failed:', err?.message || err);
|
|
6986
7077
|
sendToServer({ ...response, error: 'outline_failed' });
|
|
@@ -7008,17 +7099,65 @@ export async function handleYeaftSearchHistory(msg) {
|
|
|
7008
7099
|
_requestClientId: msg?._requestClientId || null,
|
|
7009
7100
|
};
|
|
7010
7101
|
|
|
7011
|
-
if (!sessionId || (query.length <
|
|
7102
|
+
if (!sessionId || (Array.from(query).length < 1 && !senderKey)) {
|
|
7012
7103
|
sendToServer(response);
|
|
7013
7104
|
return;
|
|
7014
7105
|
}
|
|
7015
7106
|
|
|
7107
|
+
const perfTraceId = typeof msg?.perfTraceId === 'string' && msg.perfTraceId.trim() ? msg.perfTraceId.trim() : null;
|
|
7108
|
+
const scanStats = {};
|
|
7109
|
+
scheduleHistoryEventLoopTrace({
|
|
7110
|
+
perfTraceId,
|
|
7111
|
+
phase: 'history_search.event_loop_delay',
|
|
7112
|
+
sessionId,
|
|
7113
|
+
messageType: msg?.type || 'yeaft_search_history',
|
|
7114
|
+
});
|
|
7115
|
+
const scanStart = perfNowMs();
|
|
7016
7116
|
try {
|
|
7017
7117
|
const defaultYeaftDir = ctx.CONFIG?.yeaftDir || DEFAULT_YEAFT_DIR;
|
|
7018
7118
|
const storeDir = resolveSessionYeaftDir(defaultYeaftDir, sessionId);
|
|
7019
|
-
|
|
7020
|
-
|
|
7021
|
-
|
|
7119
|
+
let result;
|
|
7120
|
+
let indexFallback = false;
|
|
7121
|
+
try {
|
|
7122
|
+
result = await searchConversationIndex(storeDir, sessionId, query, {
|
|
7123
|
+
limit,
|
|
7124
|
+
beforeSeq,
|
|
7125
|
+
cursor: msg?.cursor || null,
|
|
7126
|
+
senderKey,
|
|
7127
|
+
_waitForBuild: false,
|
|
7128
|
+
});
|
|
7129
|
+
} catch (indexError) {
|
|
7130
|
+
sendToServer({
|
|
7131
|
+
...response,
|
|
7132
|
+
error: indexError?.code === 'stale_result'
|
|
7133
|
+
? 'stale_result'
|
|
7134
|
+
: (indexError?.code === 'index_building' ? 'index_building' : 'index_unavailable'),
|
|
7135
|
+
...(perfTraceId ? { perfTraceId } : {}),
|
|
7136
|
+
});
|
|
7137
|
+
return;
|
|
7138
|
+
}
|
|
7139
|
+
if (!indexFallback) {
|
|
7140
|
+
scanStats.segments = Number(result.indexSourceFiles) || 0;
|
|
7141
|
+
scanStats.bytes = Number(result.indexSourceBytes) || 0;
|
|
7142
|
+
scanStats.rows = Number(result.indexEntryCount) || 0;
|
|
7143
|
+
}
|
|
7144
|
+
traceHistoryScan({
|
|
7145
|
+
perfTraceId,
|
|
7146
|
+
phase: 'history_search.store_scan',
|
|
7147
|
+
sessionId,
|
|
7148
|
+
messageType: msg?.type || 'yeaft_search_history',
|
|
7149
|
+
start: scanStart,
|
|
7150
|
+
scanStats,
|
|
7151
|
+
detail: {
|
|
7152
|
+
limit,
|
|
7153
|
+
queryLength: Array.from(query).length,
|
|
7154
|
+
senderFilter: !!senderKey,
|
|
7155
|
+
resultCount: result.results.length,
|
|
7156
|
+
indexGeneration: result.indexGeneration || null,
|
|
7157
|
+
indexFallback,
|
|
7158
|
+
},
|
|
7159
|
+
});
|
|
7160
|
+
sendToServer({ ...response, ...result, ...(perfTraceId ? { perfTraceId } : {}) });
|
|
7022
7161
|
} catch (err) {
|
|
7023
7162
|
console.error('[Yeaft] Session history search failed:', err?.message || err);
|
|
7024
7163
|
sendToServer({ ...response, error: 'search_failed' });
|
|
@@ -7030,11 +7169,17 @@ export async function handleYeaftLoadHistoryWindow(msg) {
|
|
|
7030
7169
|
const requestId = typeof msg?.requestId === 'string' ? msg.requestId : null;
|
|
7031
7170
|
const anchorSeq = Number(msg?.anchorSeq);
|
|
7032
7171
|
const anchorMessageId = typeof msg?.anchorMessageId === 'string' ? msg.anchorMessageId : null;
|
|
7172
|
+
const entryId = typeof msg?.entryId === 'string' ? msg.entryId : null;
|
|
7173
|
+
const indexGeneration = Number(msg?.indexGeneration);
|
|
7174
|
+
const entryStartSeq = Number(msg?.entryStartSeq);
|
|
7033
7175
|
const response = {
|
|
7034
7176
|
type: 'yeaft_history_window',
|
|
7035
7177
|
requestId,
|
|
7036
7178
|
conversationId: ensureYeaftConversationId(),
|
|
7037
7179
|
sessionId: sessionId || null,
|
|
7180
|
+
entryId,
|
|
7181
|
+
indexGeneration: Number.isFinite(indexGeneration) ? indexGeneration : null,
|
|
7182
|
+
entryStartSeq: Number.isFinite(entryStartSeq) ? entryStartSeq : null,
|
|
7038
7183
|
anchorMessageId,
|
|
7039
7184
|
anchorSeq: Number.isFinite(anchorSeq) ? anchorSeq : null,
|
|
7040
7185
|
messages: [],
|
|
@@ -7043,7 +7188,10 @@ export async function handleYeaftLoadHistoryWindow(msg) {
|
|
|
7043
7188
|
_requestClientId: msg?._requestClientId || null,
|
|
7044
7189
|
};
|
|
7045
7190
|
|
|
7046
|
-
if (!sessionId || !
|
|
7191
|
+
if (!sessionId || !entryId || !anchorMessageId
|
|
7192
|
+
|| !Number.isFinite(indexGeneration)
|
|
7193
|
+
|| !Number.isFinite(entryStartSeq)
|
|
7194
|
+
|| !Number.isFinite(anchorSeq)) {
|
|
7047
7195
|
sendToServer({ ...response, error: 'invalid_anchor' });
|
|
7048
7196
|
return;
|
|
7049
7197
|
}
|
|
@@ -7051,19 +7199,34 @@ export async function handleYeaftLoadHistoryWindow(msg) {
|
|
|
7051
7199
|
try {
|
|
7052
7200
|
const defaultYeaftDir = ctx.CONFIG?.yeaftDir || DEFAULT_YEAFT_DIR;
|
|
7053
7201
|
const storeDir = resolveSessionYeaftDir(defaultYeaftDir, sessionId);
|
|
7054
|
-
const
|
|
7055
|
-
|
|
7202
|
+
const loaded = await readConversationIndexWindow(storeDir, sessionId, {
|
|
7203
|
+
indexGeneration,
|
|
7204
|
+
entryId,
|
|
7205
|
+
entryStartSeq,
|
|
7206
|
+
anchorMessageId,
|
|
7207
|
+
anchorSeq,
|
|
7056
7208
|
beforeTurns: msg?.beforeTurns,
|
|
7057
7209
|
afterTurns: msg?.afterTurns,
|
|
7210
|
+
maxRows: msg?.maxRows,
|
|
7211
|
+
maxBytes: msg?.maxBytes,
|
|
7058
7212
|
});
|
|
7213
|
+
if (!loaded?.ok) {
|
|
7214
|
+
sendToServer({ ...response, error: 'stale_result' });
|
|
7215
|
+
return;
|
|
7216
|
+
}
|
|
7059
7217
|
sendToServer({
|
|
7060
7218
|
...response,
|
|
7061
|
-
...window,
|
|
7062
|
-
|
|
7219
|
+
...loaded.window,
|
|
7220
|
+
entryEndSeq: loaded.entry.entryEndSeq,
|
|
7221
|
+
sourceMessageIds: loaded.entry.sourceMessageIds,
|
|
7222
|
+
messages: projectVisibleHistoryChunkMessages(loaded.window.messages),
|
|
7063
7223
|
});
|
|
7064
7224
|
} catch (err) {
|
|
7065
7225
|
console.error('[Yeaft] Session history anchor load failed:', err?.message || err);
|
|
7066
|
-
sendToServer({
|
|
7226
|
+
sendToServer({
|
|
7227
|
+
...response,
|
|
7228
|
+
error: err?.code === 'stale_result' ? 'stale_result' : 'window_load_failed',
|
|
7229
|
+
});
|
|
7067
7230
|
}
|
|
7068
7231
|
}
|
|
7069
7232
|
|
|
@@ -7086,6 +7249,11 @@ export async function handleYeaftLoadMoreHistory(msg) {
|
|
|
7086
7249
|
}
|
|
7087
7250
|
|
|
7088
7251
|
const beforeSeq = (typeof msg.beforeSeq === 'number') ? msg.beforeSeq : null;
|
|
7252
|
+
const pageKind = msg.pageKind === 'gap' ? 'gap' : 'server';
|
|
7253
|
+
const gapStopAtSeq = pageKind === 'gap' && Number.isFinite(msg.gapStopAtSeq)
|
|
7254
|
+
? msg.gapStopAtSeq
|
|
7255
|
+
: null;
|
|
7256
|
+
const cacheEpoch = Number.isFinite(msg.cacheEpoch) ? msg.cacheEpoch : 0;
|
|
7089
7257
|
const turns = Math.min(50, (typeof msg.turns === 'number' && msg.turns > 0) ? Math.floor(msg.turns) : 20);
|
|
7090
7258
|
|
|
7091
7259
|
let result;
|
|
@@ -7094,8 +7262,12 @@ export async function handleYeaftLoadMoreHistory(msg) {
|
|
|
7094
7262
|
const store = session?.conversationStore || new ConversationStore(
|
|
7095
7263
|
resolveSessionYeaftDir(ctx.CONFIG?.yeaftDir || DEFAULT_YEAFT_DIR, sessionId),
|
|
7096
7264
|
);
|
|
7097
|
-
result = loadVisibleGroupHistoryPage(store, sessionId, turns, beforeSeq
|
|
7098
|
-
|
|
7265
|
+
result = loadVisibleGroupHistoryPage(store, sessionId, turns, beforeSeq, {
|
|
7266
|
+
stopAtSeq: gapStopAtSeq,
|
|
7267
|
+
});
|
|
7268
|
+
traceDuration('history_more.store_load', loadStart, {
|
|
7269
|
+
detail: { count: result.messages?.length || 0, beforeSeq, turns, pageKind, gapStopAtSeq },
|
|
7270
|
+
});
|
|
7099
7271
|
} catch (err) {
|
|
7100
7272
|
console.error('[Yeaft] loadOlderBySession failed:', err.message);
|
|
7101
7273
|
result = { messages: [], oldestSeq: null, hasMore: false };
|
|
@@ -7112,8 +7284,12 @@ export async function handleYeaftLoadMoreHistory(msg) {
|
|
|
7112
7284
|
messages: result.messages || [],
|
|
7113
7285
|
mode: 'older',
|
|
7114
7286
|
oldestSeq: result.oldestSeq,
|
|
7287
|
+
nextBeforeSeq: result.nextBeforeSeq,
|
|
7115
7288
|
hasMore: !!result.hasMore,
|
|
7116
7289
|
turns,
|
|
7290
|
+
pageKind,
|
|
7291
|
+
gapStopAtSeq,
|
|
7292
|
+
cacheEpoch,
|
|
7117
7293
|
requestId,
|
|
7118
7294
|
requestClientId,
|
|
7119
7295
|
perfTraceId,
|