fix(coding-agent): rechain fork paths without labels

closes #5669
This commit is contained in:
Armin Ronacher
2026-06-12 18:49:49 +02:00
parent 1b2c32c653
commit adf567c1c6
3 changed files with 24 additions and 2 deletions

View File

@@ -9,6 +9,7 @@
### Fixed
- Fixed `--model` resolution for authenticated custom model IDs whose slash prefix matches an unauthenticated built-in provider ([#5643](https://github.com/earendil-works/pi/issues/5643)).
- Fixed `/fork` to keep session parent chains connected when the forked path contains labels ([#5669](https://github.com/earendil-works/pi/issues/5669)).
- Fixed `/share` and `/export` HTML exports to use the active fallback theme when the configured custom theme no longer exists ([#5596](https://github.com/earendil-works/pi/issues/5596)).
- Fixed custom fallback model IDs with `:<thinking>` suffixes to preserve the requested thinking level when the provider template model does not advertise reasoning ([#5552](https://github.com/earendil-works/pi/issues/5552)).

View File

@@ -1290,8 +1290,16 @@ export class SessionManager {
throw new Error(`Entry ${leafId} not found`);
}
// Filter out LabelEntry from path - we'll recreate them from the resolved map
const pathWithoutLabels = path.filter((e) => e.type !== "label");
// Filter out LabelEntry from path - we'll recreate them from the resolved map.
// Because labels are real tree entries, later entries can be children of labels;
// removing labels requires re-chaining the retained path to avoid orphaned subtrees.
const pathWithoutLabels: SessionEntry[] = [];
let pathParentId: string | null = null;
for (const entry of path) {
if (entry.type === "label") continue;
pathWithoutLabels.push({ ...entry, parentId: pathParentId });
pathParentId = entry.id;
}
const newSessionId = createSessionId();
const timestamp = new Date().toISOString();

View File

@@ -142,6 +142,19 @@ describe("SessionManager labels", () => {
expect(msg2Node?.labelTimestamp).toBe(msg2LabelEntry.timestamp);
});
it("rewires children of removed labels when forking", () => {
const session = SessionManager.inMemory();
const msg1Id = session.appendMessage({ role: "user", content: "hello", timestamp: 1 });
session.appendLabelChange(msg1Id, "checkpoint");
const modelChangeId = session.appendModelChange("anthropic", "claude-test");
const msg2Id = session.appendMessage({ role: "user", content: "followup", timestamp: 2 });
session.createBranchedSession(msg2Id);
expect(session.getEntry(modelChangeId)?.parentId).toBe(msg1Id);
});
it("labels not on path are not preserved in createBranchedSession", () => {
const session = SessionManager.inMemory();