import { uuidv7 } from '../api'; const KEY = 'albedo.shell-attempts.v1'; export interface ShellAttempt { session: string; id: string; command: string; at: number } function read(): ShellAttempt[] { let raw: string | null; try { raw = localStorage.getItem(KEY); } catch (error) { throw new Error(`browser storage is unavailable: ${(error as Error).message ?? error}`); } if (raw === null) return []; let value: unknown; try { value = JSON.parse(raw); } catch { throw new Error('saved shell retry identity is corrupt; refusing to run a new command'); } if (!Array.isArray(value) || !value.every((item) => typeof item?.session === 'string' && typeof item?.id === 'string' && typeof item?.command === 'string' && Number.isFinite(item?.at))) { throw new Error('saved shell retry identity is invalid; refusing to run a new command'); } return value; } /** A pending shell intent is durable before its first PUT, so uncertain retries keep the same identity. */ export function pendingShell(session: string, command: string): ShellAttempt { const all = read(); const prior = all.find((item) => item.session === session && item.command === command); if (prior) return prior; if (all.length >= 32) throw new Error('too many unresolved shell retry identities; resolve one before running another'); const attempt = { session, id: uuidv7(), command, at: Date.now() }; try { localStorage.setItem(KEY, JSON.stringify([...all, attempt])); } catch (error) { throw new Error(`couldn't save shell retry identity: ${(error as Error).message ?? error}`); } return attempt; } /** Removes only after an authoritative server record was read or admitted. */ export function resolveShell(attempt: ShellAttempt): void { const rest = read().filter((item) => item.session !== attempt.session || item.id !== attempt.id); try { if (rest.length) localStorage.setItem(KEY, JSON.stringify(rest)); else localStorage.removeItem(KEY); } catch (error) { throw new Error(`couldn't clear resolved shell retry identity: ${(error as Error).message ?? error}`); } }