import type { SessionEntry, SessionStartEvent, } from "@earendil-works/pi-coding-agent"; import { createRuntimeState, type DamageControlRuntimeState, } from "ast-guard-core"; export const RUNTIME_STATE_CUSTOM_TYPE = "pi-damage-control.runtime-state"; interface PersistedRuntimeStateV1 { blockedRuleIds?: string[]; version: 1; yolo: boolean; } interface PersistedRuntimeStateV2 { enabledOverrides?: [string, boolean][]; version: 2; yolo: boolean; } interface PersistedRuntimeStateV3 { version: 3; yolo: boolean; } interface PersistedRuntimeStateV4 { dna: boolean; version: 4; } type PersistedRuntimeState = | PersistedRuntimeStateV1 | PersistedRuntimeStateV2 | PersistedRuntimeStateV3 | PersistedRuntimeStateV4; export type SessionStartReason = SessionStartEvent["reason"]; const RESTORE_RUNTIME_BY_SESSION_START_REASON = { fork: false, new: false, reload: true, resume: false, startup: false, } satisfies Record; function isRecord(value: unknown): value is Record { return typeof value === "object" && value !== null; } function isEnabledOverride(value: unknown): value is [string, boolean] { return ( Array.isArray(value) && value.length === 2 && typeof value[0] === "string" && typeof value[1] === "boolean" ); } /** * 解析历史版本条目,迁移至 v4(仅保留 dna)。 * v1(blockedRuleIds)与 v2(enabledOverrides)曾是 ag:block/ag:unblock 的 * 持久化覆盖;该机制已移除。v3 将 yolo 重命名为 dna。 */ function parsePersistedRuntimeState( value: unknown ): PersistedRuntimeState | undefined { if ( !isRecord(value) || (typeof value.yolo !== "boolean" && typeof value.dna !== "boolean") ) { return; } if (value.version === 1) { if (!Array.isArray(value.blockedRuleIds)) { return; } if (!value.blockedRuleIds.every((item) => typeof item === "string")) { return; } return { version: 1, yolo: value.yolo as boolean }; } if (value.version === 2) { if (!Array.isArray(value.enabledOverrides)) { return; } if (!value.enabledOverrides.every(isEnabledOverride)) { return; } return { version: 2, yolo: value.yolo as boolean }; } if (value.version === 3) { return { version: 3, yolo: value.yolo as boolean }; } if (value.version === 4) { return { version: 4, dna: value.dna as boolean }; } return; } function runtimeFromCustomEntry( entry: SessionEntry ): DamageControlRuntimeState | undefined { if ( entry.type !== "custom" || entry.customType !== RUNTIME_STATE_CUSTOM_TYPE ) { return; } const parsed = parsePersistedRuntimeState(entry.data); if (!parsed) { return; } // v1-v3 迁移:yolo → dna if (parsed.version === 1 || parsed.version === 2 || parsed.version === 3) { return createRuntimeState({ dna: parsed.yolo }); } return createRuntimeState({ dna: parsed.dna }); } export function snapshotRuntimeState( state: DamageControlRuntimeState ): PersistedRuntimeStateV4 { return { version: 4, dna: state.dna, }; } export function runtimeForSessionStart( reason: SessionStartReason, entries: readonly SessionEntry[] ): DamageControlRuntimeState { if (!RESTORE_RUNTIME_BY_SESSION_START_REASON[reason]) { return createRuntimeState(); } for (let index = entries.length - 1; index >= 0; index -= 1) { const entry = entries[index]; if (!entry) { continue; } const restored = runtimeFromCustomEntry(entry); if (restored) { return restored; } } return createRuntimeState(); }