





























@@ -42,6 +42,7 @@ const transcriptMessageCountCache = new Map<
4242>();
4343const MAX_TRANSCRIPT_MESSAGE_COUNT_CACHE_ENTRIES = 5000;
4444const TRANSCRIPT_ASYNC_READ_CHUNK_BYTES = 64 * 1024;
45+type TranscriptFileHandle = Awaited<ReturnType<typeof fs.promises.open>>;
45464647function readSessionTitleFieldsCacheKey(
4748filePath: string,
@@ -813,43 +814,47 @@ export async function readSessionTitleFieldsFromTranscriptAsync(
813814if (cached) {
814815return cached;
815816}
816-const index = await readSessionTranscriptIndex(filePath);
817-if (!index) {
818-return { firstUserMessage: null, lastMessagePreview: null };
817+818+if (stat.size === 0) {
819+const empty = { firstUserMessage: null, lastMessagePreview: null };
820+setCachedSessionTitleFields(cacheKey, stat, empty);
821+return empty;
819822}
820823821-let firstUserMessage: string | null = null;
822-for (const entry of index.entries) {
823-const msg = entry.record.message as TranscriptMessage | undefined;
824-if (msg?.role !== "user") {
825-continue;
826-}
827-if (opts?.includeInterSession !== true && hasInterSessionUserProvenance(msg)) {
828-continue;
829-}
830-const text = extractTextFromContent(msg.content);
831-if (text) {
832-firstUserMessage = text;
833-break;
824+let handle: TranscriptFileHandle | null = null;
825+try {
826+handle = await fs.promises.open(filePath, "r");
827+828+let firstUserMessage: string | null = null;
829+try {
830+const chunk = await readTranscriptHeadChunkAsync(handle);
831+if (chunk) {
832+firstUserMessage = extractFirstUserMessageFromTranscriptChunk(chunk, opts);
833+}
834+} catch {
835+// ignore head read errors
834836}
835-}
836837837-let lastMessagePreview: string | null = null;
838-for (const entry of index.entries.toReversed()) {
839-const msg = entry.record.message as TranscriptMessage | undefined;
840-if (!msg || (msg.role !== "user" && msg.role !== "assistant")) {
841-continue;
838+let lastMessagePreview: string | null = null;
839+try {
840+lastMessagePreview = await readLastMessagePreviewFromOpenTranscriptAsync({
841+ handle,
842+size: stat.size,
843+});
844+} catch {
845+// ignore tail read errors
842846}
843-const text = extractTextFromContent(msg.content);
844-if (text) {
845-lastMessagePreview = text;
846-break;
847+848+const result = { firstUserMessage, lastMessagePreview };
849+setCachedSessionTitleFields(cacheKey, stat, result);
850+return result;
851+} catch {
852+return { firstUserMessage: null, lastMessagePreview: null };
853+} finally {
854+if (handle) {
855+await handle.close().catch(() => undefined);
847856}
848857}
849-850-const result = { firstUserMessage, lastMessagePreview };
851-setCachedSessionTitleFields(cacheKey, stat, result);
852-return result;
853858}
854859855860function extractTextFromContent(content: TranscriptMessage["content"]): string | null {
@@ -883,6 +888,18 @@ function readTranscriptHeadChunk(fd: number, maxBytes = 8192): string | null {
883888return buf.toString("utf-8", 0, bytesRead);
884889}
885890891+async function readTranscriptHeadChunkAsync(
892+handle: TranscriptFileHandle,
893+maxBytes = 8192,
894+): Promise<string | null> {
895+const buffer = Buffer.alloc(maxBytes);
896+const { bytesRead } = await handle.read(buffer, 0, buffer.length, 0);
897+if (bytesRead <= 0) {
898+return null;
899+}
900+return buffer.toString("utf-8", 0, bytesRead);
901+}
902+886903function extractFirstUserMessageFromTranscriptChunk(
887904chunk: string,
888905opts?: { includeInterSession?: boolean },
@@ -993,6 +1010,41 @@ function readLastMessagePreviewFromOpenTranscript(params: {
9931010return null;
9941011}
99510121013+async function readLastMessagePreviewFromOpenTranscriptAsync(params: {
1014+handle: TranscriptFileHandle;
1015+size: number;
1016+}): Promise<string | null> {
1017+const readStart = Math.max(0, params.size - LAST_MSG_MAX_BYTES);
1018+const readLen = Math.min(params.size, LAST_MSG_MAX_BYTES);
1019+const buffer = Buffer.alloc(readLen);
1020+const { bytesRead } = await params.handle.read(buffer, 0, readLen, readStart);
1021+if (bytesRead <= 0) {
1022+return null;
1023+}
1024+1025+const chunk = buffer.toString("utf-8", 0, bytesRead);
1026+const lines = chunk.split(/\r?\n/).filter((line) => line.trim());
1027+const tailLines = lines.slice(-LAST_MSG_MAX_LINES);
1028+1029+for (let i = tailLines.length - 1; i >= 0; i--) {
1030+const line = tailLines[i];
1031+try {
1032+const parsed = JSON.parse(line);
1033+const msg = parsed?.message as TranscriptMessage | undefined;
1034+if (msg?.role !== "user" && msg?.role !== "assistant") {
1035+continue;
1036+}
1037+const text = extractTextFromContent(msg.content);
1038+if (text) {
1039+return text;
1040+}
1041+} catch {
1042+// skip malformed
1043+}
1044+}
1045+return null;
1046+}
1047+9961048export function readLastMessagePreviewFromTranscript(
9971049sessionId: string,
9981050storePath: string | undefined,
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。