import { SessionManager } from "@earendil-works/pi-coding-agent"; import { stat } from "node:fs/promises"; import { resolve } from "node:path"; export interface CloneActiveSessionInput { parentSessionFile: string; sessionDir: string; cwd: string; leafId: string | null; } export interface ClonedSession { parentSessionFile: string; parentSessionId: string; childSessionFile: string; childSessionId: string; leafId: string; } async function requireFile(path: string, label: string): Promise { let details; try { details = await stat(path); } catch (error) { throw new Error(`${label} does not exist: ${path}`, { cause: error }); } if (!details.isFile()) throw new Error(`${label} is not a file: ${path}`); } /** Clone one persisted Pi branch without mutating the live SessionManager. */ export async function cloneActiveSession(input: CloneActiveSessionInput): Promise { const leafId = input.leafId?.trim(); if (!leafId) throw new Error("The current Pi session has no saved leaf to clone. Continue the conversation first."); if (!input.parentSessionFile) throw new Error("The current Pi session is not persisted. Start Pi without --no-session and try again."); await requireFile(input.parentSessionFile, "Parent Pi session"); const detached = SessionManager.open(input.parentSessionFile, input.sessionDir, input.cwd); if (!detached.getEntry(leafId)) { throw new Error(`The selected Pi session leaf is no longer present: ${leafId}`); } const branch = detached.getBranch(leafId); const hasAssistant = branch.some((entry) => entry.type === "message" && entry.message.role === "assistant"); if (!hasAssistant) { throw new Error( "The selected branch has no completed assistant response. Continue on that branch, wait for an assistant response, then retry /sidetrack.", ); } const parentSessionId = detached.getSessionId(); const childSessionFile = detached.createBranchedSession(leafId); if (!childSessionFile) throw new Error("Pi did not create a persisted child session."); if (resolve(childSessionFile) === resolve(input.parentSessionFile)) { throw new Error("Pi returned the parent session path instead of a separate child session."); } await requireFile(childSessionFile, "Cloned Pi session"); return { parentSessionFile: input.parentSessionFile, parentSessionId, childSessionFile, childSessionId: detached.getSessionId(), leafId, }; }