@@ -5172,6 +5172,7 @@ function createSessionQueryScanState(tokens, options = {}) {
51725172 count: 0,
51735173 snippets: [],
51745174 positions: [],
5175+ complete: true,
51755176 leadingSystem: roleFilter !== 'system'
51765177 };
51775178}
@@ -5210,11 +5211,13 @@ function consumeSessionQueryMessage(state, message) {
52105211 timestamp: typeof message.timestamp === 'string' ? message.timestamp : ''
52115212 });
52125213 }
5214+ if (state.count >= state.maxMatches) state.complete = false;
52135215 return state.count >= state.maxMatches;
52145216}
52155217
52165218function buildSessionQueryScanResult(state) {
52175219 return {
5220+ complete: !!(state && state.complete),
52185221 hit: !!(state && state.count > 0),
52195222 count: state && Number.isFinite(state.count) ? state.count : 0,
52205223 snippets: state && Array.isArray(state.snippets) ? state.snippets : [],
@@ -5259,6 +5262,7 @@ async function scanSessionContentForQuery(session, tokens, options = {}) {
52595262 const state = createSessionQueryScanState(tokens, options);
52605263 if (session.source === 'opencode') {
52615264 const extracted = queryOpencodeSessionMessages(session.sessionId || parseOpencodeVirtualSessionId(filePath), { maxMessages: Infinity });
5265+ state.complete = !!extracted && !extracted.truncated;
52625266 const messages = extracted && Array.isArray(extracted.messages) ? extracted.messages : [];
52635267 for (let i = 0; i < messages.length; i += 1) {
52645268 const message = { ...messages[i], recordLineIndex: i };
@@ -5268,6 +5272,7 @@ async function scanSessionContentForQuery(session, tokens, options = {}) {
52685272 }
52695273 if (session.source === 'zed') {
52705274 const zedExtracted = queryZedSessionMessages(session.sessionId || parseZedVirtualSessionId(filePath), { maxMessages: Infinity });
5275+ state.complete = !!zedExtracted && !zedExtracted.truncated;
52715276 const zedMessages = zedExtracted && Array.isArray(zedExtracted.messages) ? zedExtracted.messages : [];
52725277 for (let i = 0; i < zedMessages.length; i += 1) {
52735278 const message = { ...zedMessages[i], recordLineIndex: i };
@@ -5283,6 +5288,7 @@ async function scanSessionContentForQuery(session, tokens, options = {}) {
52835288 } catch (_) {
52845289 json = null;
52855290 }
5291+ state.complete = !!json;
52865292 const rawMessages = json && Array.isArray(json.messages) ? json.messages : [];
52875293 for (const entry of rawMessages) {
52885294 if (!entry || typeof entry !== 'object') continue;
@@ -5310,12 +5316,14 @@ async function scanSessionContentForQuery(session, tokens, options = {}) {
53105316 const buf = Buffer.alloc(targetBytes);
53115317 const bytes = fs.readSync(fd, buf, 0, targetBytes, 0);
53125318 fs.closeSync(fd);
5319+ state.complete = bytes >= stat.size;
53135320 text = bytes > 0 ? buf.slice(0, bytes).toString('utf-8') : '';
53145321 } catch (_) {
53155322 try {
53165323 text = fs.readFileSync(filePath, 'utf-8');
53175324 } catch (_) {
53185325 text = '';
5326+ state.complete = false;
53195327 }
53205328 }
53215329
@@ -5340,6 +5348,7 @@ async function scanSessionContentForQuery(session, tokens, options = {}) {
53405348 const currentLineIndex = lineIndex;
53415349 lineIndex += 1;
53425350 if (maxBytes > 0 && bytesRead >= maxBytes) {
5351+ state.complete = false;
53435352 break;
53445353 }
53455354
@@ -5369,6 +5378,7 @@ async function scanSessionContentForQuery(session, tokens, options = {}) {
53695378
53705379 return buildSessionQueryScanResult(state);
53715380 } catch (e) {
5381+ state.complete = false;
53725382 return scanSessionContentForQueryInRecords(readJsonlRecords(filePath), session.source, state);
53735383 } finally {
53745384 if (rl) {
@@ -5383,7 +5393,9 @@ async function scanSessionContentForQuery(session, tokens, options = {}) {
53835393async function applySessionQueryFilter(sessions, options = {}) {
53845394 const tokens = Array.isArray(options.tokens) ? options.tokens : [];
53855395 if (tokens.length === 0) {
5386- return sessions;
5396+ const result = sessions.slice();
5397+ Object.defineProperty(result, 'searchInfo', { value: null });
5398+ return result;
53875399 }
53885400
53895401 const mode = normalizeQueryMode(options.queryMode);
@@ -5397,10 +5409,12 @@ async function applySessionQueryFilter(sessions, options = {}) {
53975409 : 0;
53985410
53995411 let scanned = 0;
5412+ let complete = true;
54005413 const results = [];
54015414
54025415 for (const session of sessions) {
54035416 if (scope === 'content' && scanned >= contentScanLimit) {
5417+ complete = false;
54045418 break;
54055419 }
54065420
@@ -5420,6 +5434,10 @@ async function applySessionQueryFilter(sessions, options = {}) {
54205434 snippetLimit: 2
54215435 });
54225436 contentHit = contentInfo.hit;
5437+ // 命中已完成候选的存在性判定,无需穷尽匹配或消息。
5438+ if (!contentHit && !contentInfo.complete) complete = false;
5439+ } else if (shouldScanContent) {
5440+ complete = false;
54235441 }
54245442
54255443 const hit = scope === 'summary'
@@ -5441,6 +5459,9 @@ async function applySessionQueryFilter(sessions, options = {}) {
54415459 results.push(session);
54425460 }
54435461
5462+ Object.defineProperty(results, 'searchInfo', {
5463+ value: { scanned, candidates: sessions.length, complete }
5464+ });
54445465 return results;
54455466}
54465467function collectRecentJsonlFiles(rootDir, options = {}) {
@@ -7632,7 +7653,9 @@ async function listAllSessions(params = {}) {
76327653 contentScanBytes: params.contentScanBytes
76337654 });
76347655 }
7656+ const searchInfo = hasQuery ? result.searchInfo : null;
76357657 result = mergeAndLimitSessions(result, limit);
7658+ Object.defineProperty(result, 'searchInfo', { value: searchInfo });
76367659 setSessionListCache(cacheKey, result);
76377660 return result;
76387661}
@@ -7665,6 +7688,7 @@ async function listAllSessionsData(params = {}) {
76657688 delete normalized.__messageCountExact;
76667689 return normalized;
76677690 });
7691+ Object.defineProperty(result, 'searchInfo', { value: sessions.searchInfo || null });
76687692 if (!hasQuery) {
76697693 setSessionListCache(cacheKey, result);
76707694 }
@@ -7676,7 +7700,7 @@ async function listSessionBrowse(params = {}) {
76767700 ...params,
76777701 browseLightweight: true
76787702 });
7679- return Array.isArray(sessions)
7703+ const result = Array.isArray(sessions)
76807704 ? sessions.map((item) => {
76817705 if (!item || typeof item !== 'object' || Array.isArray(item)) {
76827706 return item;
@@ -7686,6 +7710,8 @@ async function listSessionBrowse(params = {}) {
76867710 return normalized;
76877711 })
76887712 : [];
7713+ Object.defineProperty(result, 'searchInfo', { value: sessions.searchInfo || null });
7714+ return result;
76897715}
76907716
76917717async function listSessionUsage(params = {}) {
@@ -10008,8 +10034,7 @@ async function exportSessionData(params = {}) {
1000810034 if (!extracted) {
1000910035 return { error: 'Failed to parse session file' };
1001010036 }
10011- }
10012- if (source === 'zed') {
10037+ } else if (source === 'zed') {
1001310038 const zedSessionId = params.sessionId || parseZedVirtualSessionId(filePath);
1001410039 extracted = queryZedSessionMessages(zedSessionId, { maxMessages });
1001510040 if (!extracted) {
@@ -14760,9 +14785,11 @@ function createWebServer({ htmlPath, assetsDir, webDir, host, port, openBrowser
1476014785 if (source && !isSessionSourceOrAll(source)) {
1476114786 result = { error: 'Invalid source. Must be codex, claude, gemini, codebuddy, workbuddy, pi, opencode, or all' };
1476214787 } else {
14788+ const sessions = await listSessionBrowse(params);
1476314789 result = {
14764- sessions: await listSessionBrowse(params),
14765- source: source || 'all'
14790+ sessions,
14791+ source: source || 'all',
14792+ searchInfo: sessions.searchInfo
1476614793 };
1476714794 }
1476814795 }
0 commit comments