diff --git a/src/state.ts b/src/state.ts index be97fa0..03597b6 100644 --- a/src/state.ts +++ b/src/state.ts @@ -1,4 +1,4 @@ -import type { ExtensionAPI } from "@earendil-works/pi-coding-agent"; +import type { ExtensionAPI, SessionEntry } from "@earendil-works/pi-coding-agent"; /** How a skill was first observed in the session (SPEC §6.2). */ export type SkillSource = "slash" | "skill-block" | "read"; @@ -39,6 +39,37 @@ export function saveState(pi: ExtensionAPI, state: ExtensionState): void { pi.appendEntry(STATE_ENTRY_TYPE, state); } +function isExtensionState(data: unknown): data is ExtensionState { + if (!data || typeof data !== "object") { + return false; + } + const candidate = data as ExtensionState; + return ( + candidate.version === 1 && + (candidate.sessionOverride === null || typeof candidate.sessionOverride === "boolean") && + Array.isArray(candidate.skills) && + (candidate.lastCompactionSource === null || + candidate.lastCompactionSource === "auto" || + candidate.lastCompactionSource === "manual") && + Array.isArray(candidate.pendingReinject) + ); +} + +/** Latest persisted state on the branch, or null if none (SPEC §6.3). */ +export function loadStateFromBranch(branch: SessionEntry[]): ExtensionState | null { + for (let i = branch.length - 1; i >= 0; i--) { + const entry = branch[i]; + if (entry.type !== "custom" || entry.customType !== STATE_ENTRY_TYPE || entry.data === undefined) { + continue; + } + if (!isExtensionState(entry.data)) { + continue; + } + return structuredClone(entry.data); + } + return null; +} + export function createInitialState(): ExtensionState { return { version: 1,