fix: remap session JSONL chunk line numbers to original source positions (#12102)

* fix: remap session JSONL chunk line numbers to original source positions

buildSessionEntry() flattens JSONL messages into plain text before
chunkMarkdown() assigns line numbers. The stored startLine/endLine
values therefore reference positions in the flattened text, not the
original JSONL file.

- Add lineMap to SessionFileEntry tracking which JSONL line each
  extracted message came from
- Add remapChunkLines() to translate chunk positions back to original
  JSONL lines after chunking
- Guard remap with source === "sessions" to prevent misapplication
- Include lineMap in content hash so existing sessions get re-indexed

Fixes #12044

* memory: dedupe session JSONL parsing

---------

Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
This commit is contained in:
Marcus Castro
2026-02-10 21:09:24 -03:00
committed by GitHub
parent 424d2dddf5
commit 45488e4ec9
5 changed files with 197 additions and 122 deletions

View File

@@ -14,6 +14,8 @@ export type SessionFileEntry = {
size: number;
hash: string;
content: string;
/** Maps each content line (0-indexed) to its 1-indexed JSONL source line. */
lineMap: number[];
};
export async function listSessionFilesForAgent(agentId: string): Promise<string[]> {
@@ -75,7 +77,9 @@ export async function buildSessionEntry(absPath: string): Promise<SessionFileEnt
const raw = await fs.readFile(absPath, "utf-8");
const lines = raw.split("\n");
const collected: string[] = [];
for (const line of lines) {
const lineMap: number[] = [];
for (let jsonlIdx = 0; jsonlIdx < lines.length; jsonlIdx++) {
const line = lines[jsonlIdx];
if (!line.trim()) {
continue;
}
@@ -108,6 +112,7 @@ export async function buildSessionEntry(absPath: string): Promise<SessionFileEnt
const safe = redactSensitiveText(text, { mode: "tools" });
const label = message.role === "user" ? "User" : "Assistant";
collected.push(`${label}: ${safe}`);
lineMap.push(jsonlIdx + 1);
}
const content = collected.join("\n");
return {
@@ -115,8 +120,9 @@ export async function buildSessionEntry(absPath: string): Promise<SessionFileEnt
absPath,
mtimeMs: stat.mtimeMs,
size: stat.size,
hash: hashText(content),
hash: hashText(content + "\n" + lineMap.join(",")),
content,
lineMap,
};
} catch (err) {
log.debug(`Failed reading session file ${absPath}: ${String(err)}`);