import { z } from "@oh-my-pi/omptype/zod"; import type { ExtensionContext } from "@oh-my-pi/pi-coding-agent"; import fixture from "./inventory.json"; export const entryType = "seed-desk-state-v1"; export const changeChannel = "workbook:seed-desk:changed"; export const scope = "fictional-session-branch"; export const inventory = fixture; export interface Snapshot { version: 1; agentEnabled: boolean; quantities: Record; } const snapshotSchema = z.object({ version: z.literal(1), agentEnabled: z.boolean(), quantities: z.record(z.string(), z.number()), }); export interface DeskState extends Snapshot { revision: string; } export class DeskRefusal extends Error { constructor(public readonly code: string, message: string) { super(`${message} Nothing changed.`); } } export function inspect(id: string, state: DeskState) { const seed = inventory.find(item => item.id === id); if (!seed) throw new DeskRefusal("invalid-target", "Unknown seed ID; query valid IDs first."); const reserved = state.quantities[id] ?? 0; return { ...seed, reserved, available: seed.packets - reserved }; } export function query(state: DeskState, family?: string) { return inventory.filter(seed => !family || seed.family === family).map(seed => inspect(seed.id, state)); } // Only active-branch entries count. Unrelated conversation entries never change a state revision. export function reconstruct(ctx: ExtensionContext): DeskState { let state: DeskState = { version: 1, agentEnabled: false, quantities: {}, revision: `${ctx.sessionManager.getSessionId()}:root`, }; for (const entry of ctx.sessionManager.getBranch()) { if (entry.type !== "custom" || entry.customType !== entryType) continue; const parsed = snapshotSchema.safeParse(entry.data); if (!parsed.success) { throw new DeskRefusal("corrupt-state", "The branch contains incompatible seed-desk state; resume an earlier branch."); } const data = parsed.data; const quantities: Record = {}; for (const [id, count] of Object.entries(data.quantities)) { const seed = inventory.find(item => item.id === id); if (!seed || !Number.isSafeInteger(count) || count < 0 || count > seed.packets) { throw new DeskRefusal("corrupt-state", "The branch contains invalid reservation quantities; resume an earlier branch."); } quantities[id] = count; } state = { version: 1, agentEnabled: data.agentEnabled, quantities, revision: `${ctx.sessionManager.getSessionId()}:${entry.id}`, }; } return state; } export function changeReservation( state: DeskState, action: string, id: string, packets: number, expectedRevision: string, authority: "human" | "agent", ): Snapshot { if (expectedRevision !== state.revision) throw new DeskRefusal("stale-revision", "State changed; query again and use its revision."); if (authority === "agent" && !state.agentEnabled) throw new DeskRefusal("agent-disabled", "A human must enable session-local agent reservations with /seeds agent on."); const seed = inspect(id, state); if (!Number.isSafeInteger(packets) || packets < 1) throw new DeskRefusal("invalid-quantity", "Packets must be a positive integer."); if (action !== "reserve" && action !== "release") throw new DeskRefusal("invalid-action", "Use reserve or release."); if (action === "reserve" && packets > seed.available) throw new DeskRefusal("insufficient-availability", "The fictional fixture has insufficient available packets."); if (action === "release" && packets > seed.reserved) throw new DeskRefusal("insufficient-reservation", "This branch does not hold that many packets."); return { version: 1, agentEnabled: state.agentEnabled, quantities: { ...state.quantities, [id]: seed.reserved + (action === "reserve" ? packets : -packets) }, }; }