

















@@ -84,9 +84,11 @@ function isSessionFileEntry(value: unknown): value is FileEntry {
8484function parseSessionEntries(content: string): {
8585entries: FileEntry[];
8686warnings: JsonlParseWarning[];
87+rowByEntry: Map<FileEntry, number>;
8788} {
8889const entries: FileEntry[] = [];
8990const warnings: JsonlParseWarning[] = [];
91+const rowByEntry = new Map<FileEntry, number>();
9092const rows = content.split(/\r?\n/u);
9193for (const [index, rawLine] of rows.entries()) {
9294const line = rawLine.trim();
@@ -105,6 +107,7 @@ function parseSessionEntries(content: string): {
105107continue;
106108}
107109entries.push(parsed);
110+rowByEntry.set(parsed, index + 1);
108111} catch {
109112warnings.push({
110113source: "session",
@@ -114,7 +117,7 @@ function parseSessionEntries(content: string): {
114117});
115118}
116119}
117-return { entries, warnings };
120+return { entries, warnings, rowByEntry };
118121}
119122120123function migrateLegacySessionEntries(entries: FileEntry[]): void {
@@ -166,9 +169,11 @@ async function readSessionBranch(filePath: string): Promise<{
166169branchEntries: SessionEntry[];
167170warnings: JsonlParseWarning[];
168171}> {
169-const { entries: fileEntries, warnings } = parseSessionEntries(
170-await fsp.readFile(filePath, "utf8"),
171-);
172+const {
173+entries: fileEntries,
174+ warnings,
175+ rowByEntry,
176+} = parseSessionEntries(await fsp.readFile(filePath, "utf8"));
172177migrateLegacySessionEntries(fileEntries);
173178const header =
174179fileEntries.find((entry): entry is SessionHeader => entry.type === "session") ?? null;
@@ -182,10 +187,42 @@ async function readSessionBranch(filePath: string): Promise<{
182187const byId = new Map(entries.map((entry) => [entry.id, entry]));
183188const leafId = entries.at(-1)?.id ?? null;
184189const branchEntries: SessionEntry[] = [];
185-let current = leafId ? byId.get(leafId) : undefined;
186-while (current) {
190+const seen = new Set<string>();
191+let currentId = leafId;
192+while (currentId) {
193+if (seen.has(currentId)) {
194+const cycleEntry = byId.get(currentId);
195+warnings.push({
196+source: "session",
197+code: "cyclic-session-branch",
198+row: cycleEntry ? (rowByEntry.get(cycleEntry) ?? 0) : 0,
199+message: "Stopped trajectory session branch export at a cyclic parent link.",
200+});
201+break;
202+}
203+seen.add(currentId);
204+const current = byId.get(currentId);
205+if (!current) {
206+warnings.push({
207+source: "session",
208+code: "incomplete-session-branch",
209+row: 0,
210+message: "Exported the reachable session branch suffix after a missing parent link.",
211+});
212+break;
213+}
187214branchEntries.unshift(current);
188-current = current.parentId ? byId.get(current.parentId) : undefined;
215+const parentId = typeof current.parentId === "string" ? current.parentId : null;
216+if (parentId && !byId.has(parentId)) {
217+warnings.push({
218+source: "session",
219+code: "incomplete-session-branch",
220+row: rowByEntry.get(current) ?? 0,
221+message: "Exported the reachable session branch suffix after a missing parent link.",
222+});
223+break;
224+}
225+currentId = parentId;
189226}
190227return { header, leafId, branchEntries, warnings };
191228}
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。