import type { ExtensionAPI, ExtensionContext } from "@oh-my-pi/pi-coding-agent"; import { join } from "node:path"; import { addBlocker, bumpPatchVersion, gotoStage, passStage, recordEvidence, resolveBlocker, startStage, summarizeState, } from "./state"; import { stage, type ArtifactRecord, type PomState, type ValidationScope } from "./domain"; import { appendMarkdownLog, recordHiveEvent, registerArtifact, syncKnowledgeBase, writeHiveMission, } from "./persistence"; import { validatePom } from "./validators"; import { getPrompt, listPrompts, renderStagePrompt } from "./prompts"; import { buildHiveMission, missionToTaskBatch } from "./studio"; import { exportProject } from "./exporter"; import { loadPomConfig } from "./config"; import { routeStage } from "./router"; import { checkpointStage, enterStageBranch } from "./git"; import { toolCallComponent, toolResultComponent, type PomTheme } from "./visual"; import type { PomRuntime } from "./runtime"; import { updateChrome } from "./ui"; function visual(runtime: PomRuntime, label: string) { return { renderCall(args: Record, options: { spinnerFrame?: number }, theme: PomTheme) { const action = [args.action, args.op, args.scope, args.kind, args.path].find((value) => typeof value === "string") as string | undefined; return toolCallComponent(label, action, theme, runtime.get().motion, options.spinnerFrame); }, renderResult( result: { content: Array<{ type: string; text?: string }>; details?: unknown; isError?: boolean }, options: { expanded: boolean; isPartial: boolean }, theme: PomTheme, ) { return toolResultComponent(result, options.expanded, options.isPartial, theme, runtime.get().view); }, }; } function textResult(text: string, details?: unknown, isError = false) { return { content: [{ type: "text" as const, text }], details, isError }; } async function refresh(_pi: ExtensionAPI, runtime: PomRuntime, ctx: ExtensionContext): Promise { const state = runtime.get(); const config = await loadPomConfig(state.projectRoot); updateChrome(ctx, state, config, runtime); } export function registerPomTools(pi: ExtensionAPI, runtime: PomRuntime): void { const { z } = pi.zod; // @ts-expect-error OMP 16.4.x + Zod v4 can exceed TypeScript instantiation depth while contextualizing this complete tool schema. pi.registerTool({ ...visual(runtime, "State"), name: "pom_state", label: "POM State", description: "Read or update canonical POM configuration, decisions, assumptions, and warnings.", defaultInactive: true, approval: "write", parameters: z.object({ action: z.enum(["get", "configure", "decision", "assumption", "warning"]), value: z.string().optional(), mode: z.enum(["autopilot", "collaborative", "strict-autopilot"]).optional(), view: z.enum(["cinematic", "rich", "compact", "minimal"]).optional(), motion: z.enum(["off", "subtle", "full"]).optional(), }), async execute(_id, params, signal, _onUpdate, ctx) { if (signal?.aborted) return textResult("Cancelled", undefined, true); const state = structuredClone(runtime.get()); if (params.action === "configure") { state.mode = params.mode ?? state.mode; state.view = params.view ?? state.view; state.motion = params.motion ?? state.motion; } if (params.action === "decision" && params.value?.trim() && !state.decisions.includes(params.value.trim())) state.decisions.push(params.value.trim()); if (params.action === "assumption" && params.value?.trim() && !state.assumptions.includes(params.value.trim())) state.assumptions.push(params.value.trim()); if (params.action === "warning" && params.value?.trim() && !state.warnings.includes(params.value.trim())) state.warnings.push(params.value.trim()); if (params.action !== "get") await runtime.set(state); await refresh(pi, runtime, ctx); return textResult(summarizeState(runtime.get()), runtime.get()); }, }); pi.registerTool({ ...visual(runtime, "Stage"), name: "pom_stage", label: "POM Stage", description: "Start, inspect, evidence, block, resolve, pass, or navigate the authoritative stage machine.", defaultInactive: true, approval: "write", parameters: z.object({ action: z.enum(["inspect", "start", "evidence", "block", "resolve", "pass", "goto"]), stage: z.number().int().min(0).max(8).optional(), criterionId: z.string().optional(), summary: z.string().optional(), paths: z.array(z.string()).optional(), agent: z.string().optional(), reason: z.string().optional(), owner: z.string().optional(), blockerId: z.string().optional(), resolution: z.string().optional(), allStages: z.boolean().optional(), validationScope: z.enum(["quick", "stage", "canon", "continuity", "knowledge", "files", "delivery", "all"]).optional(), }), async execute(_id, params, signal, onUpdate, ctx) { if (signal?.aborted) return textResult("Cancelled", undefined, true); let state = runtime.get(); if (params.action === "start") { state = startStage(state, params.stage ?? state.currentStage, params.allStages ?? false); await runtime.set(state); const config = await loadPomConfig(state.projectRoot); await enterStageBranch(state, config); await routeStage(pi, ctx, state); } else if (params.action === "goto") { if (params.stage === undefined) throw new Error("stage is required"); state = gotoStage(state, params.stage); await runtime.set(state); } else if (params.action === "evidence") { if (!params.criterionId || !params.summary) throw new Error("criterionId and summary are required"); state = recordEvidence(state, params.criterionId, params.summary, params.paths ?? [], params.agent); await runtime.set(state); } else if (params.action === "block") { state = addBlocker(state, params.reason ?? "Unspecified blocker", params.owner); await runtime.set(state); } else if (params.action === "resolve") { if (!params.blockerId) throw new Error("blockerId is required"); state = resolveBlocker(state, params.blockerId, params.resolution ?? "Resolved"); await runtime.set(state); } else if (params.action === "pass") { onUpdate?.({ content: [{ type: "text", text: "Running deterministic stage gate…" }] }); const wasAllStages = state.activeRun?.allStages ?? false; const scope = (params.validationScope ?? (state.currentStage === 8 ? "delivery" : "stage")) as ValidationScope; const report = await validatePom(state, scope); runtime.telemetry.validationFailures = report.checks.filter((item) => item.status === "fail").length; pi.sendMessage({ customType: "pom:validation", content: `${report.passed ? "PASS" : "FAIL"} ${scope}`, display: true, details: report }, { triggerTurn: false }); if (!report.passed || !report.reportPath) return textResult(`Stage gate FAIL · ${runtime.telemetry.validationFailures} failures`, report, true); const passedStage = state.currentStage; state = passStage(state, report.reportPath); await runtime.set(state); const config = await loadPomConfig(state.projectRoot); const commit = await checkpointStage(state, config, `pom: complete stage ${passedStage} ${stage(passedStage).slug}`); pi.sendMessage({ customType: "pom:milestone", content: `Stage ${passedStage} complete`, display: true, details: { stage: passedStage, status: "PASS", path: report.reportPath, commit } }, { triggerTurn: false }); if (wasAllStages && passedStage < 8) { state = startStage(state, state.currentStage, true); await runtime.set(state); await enterStageBranch(state, config); await routeStage(pi, ctx, state); pi.sendUserMessage(await renderStagePrompt(state, true), { deliverAs: "followUp" }); } } await syncKnowledgeBase(runtime.get()); await refresh(pi, runtime, ctx); return textResult(summarizeState(runtime.get()), runtime.get()); }, }); pi.registerTool({ ...visual(runtime, "Artifact"), name: "pom_artifact", label: "POM Artifact", description: "Register a non-empty canonical artifact with measured size, SHA-256, status, source, and rights metadata.", defaultInactive: true, approval: "write", parameters: z.object({ path: z.string(), category: z.string().default("other"), status: z.enum(["draft", "reviewed", "approved", "superseded"]).default("draft"), rightsStatus: z.enum(["original", "licensed", "public-domain", "reference-only", "unknown"]).default("unknown"), source: z.string().optional(), notes: z.string().optional(), }), async execute(_id, params, signal) { if (signal?.aborted) return textResult("Cancelled", undefined, true); const state = runtime.get(); const artifactVersion = bumpPatchVersion(state.artifactVersion); const record = await registerArtifact(state, params.path, { category: params.category, status: params.status as ArtifactRecord["status"], rightsStatus: params.rightsStatus as ArtifactRecord["rightsStatus"], source: params.source, notes: params.notes, version: artifactVersion, }); const next = structuredClone(state); next.artifactVersion = artifactVersion; if (/canon|bible|continuity|timeline|world|character/i.test(params.category)) { next.canonVersion = bumpPatchVersion(next.canonVersion); } await runtime.set(next); return textResult(`Registered ${record.path} · v${record.version} · ${record.bytes} bytes · ${record.sha256.slice(0, 12)}`, record); }, }); pi.registerTool({ ...visual(runtime, "Validate"), name: "pom_validate", label: "POM Validate", description: "Run deterministic state, artifact, evidence, manifest, knowledge, continuity, and delivery checks.", defaultInactive: true, approval: "read", parameters: z.object({ scope: z.enum(["quick", "stage", "canon", "continuity", "knowledge", "files", "delivery", "all"]).default("stage"), }), async execute(_id, params, signal, onUpdate) { if (signal?.aborted) return textResult("Cancelled", undefined, true); onUpdate?.({ content: [{ type: "text", text: `Validating ${params.scope}…` }] }); const report = await validatePom(runtime.get(), params.scope as ValidationScope); const failures = report.checks.filter((item) => item.status === "fail").length; runtime.telemetry.validationFailures = failures; pi.sendMessage({ customType: "pom:validation", content: `${report.passed ? "PASS" : "FAIL"} ${params.scope}`, display: true, details: report }, { triggerTurn: false }); return textResult(`${report.passed ? "PASS" : "FAIL"} · ${failures} failures · ${report.checks.length} checks`, report, !report.passed); }, }); pi.registerTool({ ...visual(runtime, "Prompt"), name: "pom_prompt", label: "POM Prompt", description: "List or retrieve versioned POM prompt fragments, including project-local overrides and provenance hashes.", defaultInactive: true, approval: "read", parameters: z.object({ action: z.enum(["list", "get"]), id: z.string().optional() }), async execute(_id, params) { const state = runtime.get().initialized ? runtime.get() : undefined; if (params.action === "list") { const prompts = await listPrompts(state); return textResult(prompts.map((item) => `${item.id}\t${item.source}\t${item.sha256.slice(0, 12)}`).join("\n"), prompts); } if (!params.id) throw new Error("id is required"); const prompt = await getPrompt(state, params.id); return textResult(prompt.content, prompt); }, }); pi.registerTool({ ...visual(runtime, "Hive"), name: "pom_hive", label: "POM Hive", description: "Plan native task batches, persist inter-agent signals, and record final synthesis without replacing OMP task or IRC.", defaultInactive: true, approval: "write", parameters: z.object({ op: z.enum(["plan", "signal", "synthesize", "inspect"]), preset: z.enum(["lean", "stage", "audit"]).optional(), missionId: z.string().optional(), signal: z.enum(["CLAIM", "QUERY", "ANSWER", "ALERT", "HANDOFF", "DONE"]).optional(), from: z.string().optional(), to: z.string().optional(), message: z.string().optional(), paths: z.array(z.string()).optional(), status: z.enum(["complete", "blocked"]).optional(), synthesis: z.string().optional(), }), async execute(_id, params, signal, _onUpdate, ctx) { if (signal?.aborted) return textResult("Cancelled", undefined, true); let state = structuredClone(runtime.get()); if (params.op === "inspect") return textResult(JSON.stringify(state.hive, null, 2), state.hive); if (params.op === "plan") { const config = await loadPomConfig(state.projectRoot); const mission = buildHiveMission(state, config, params.preset ?? "stage"); const batch = missionToTaskBatch(state, mission); state.hive.activeMissionId = mission.id; state.hive.missions[mission.id] = mission; state.stageRuns[String(state.currentStage)].hiveMissionIds.push(mission.id); await runtime.set(state); const path = await writeHiveMission(state, mission); await recordHiveEvent(state, { type: "mission.planned", mission, path }); pi.sendMessage({ customType: "pom:hive", content: mission.title, display: true, details: mission }, { triggerTurn: false }); await refresh(pi, runtime, ctx); return textResult(`Call native task exactly once with this batch:\n${JSON.stringify(batch, null, 2)}`, { mission, batch, path }); } const missionId = params.missionId ?? state.hive.activeMissionId; if (!missionId || !state.hive.missions[missionId]) throw new Error("No matching hive mission"); const mission = state.hive.missions[missionId]; if (params.op === "signal") { if (!params.signal || !params.message) throw new Error("signal and message are required"); state.hive.signalsLogged += 1; runtime.telemetry.lastIrcSignal = `${params.signal} ${params.from ?? "agent"}: ${params.message}`; await runtime.set(state); await recordHiveEvent(state, { type: "hive.signal", missionId, signal: params.signal, from: params.from, to: params.to, message: params.message, paths: params.paths ?? [] }); return textResult(`${params.signal} recorded for ${missionId}`, { missionId, ...params }); } mission.status = params.status === "blocked" ? "blocked" : "complete"; mission.synthesis = params.synthesis?.trim() || "Synthesis recorded without narrative."; mission.updatedAt = new Date().toISOString(); if (state.hive.activeMissionId === missionId) delete state.hive.activeMissionId; await runtime.set(state); const path = await writeHiveMission(state, mission); await recordHiveEvent(state, { type: "mission.synthesized", missionId, status: mission.status, synthesis: mission.synthesis, path }); pi.sendMessage({ customType: "pom:hive", content: `${mission.title} ${mission.status}`, display: true, details: mission }, { triggerTurn: false }); return textResult(`Hive ${missionId} ${mission.status}`, { mission, path }); }, }); pi.registerTool({ ...visual(runtime, "Export"), name: "pom_export", label: "POM Export", description: "Create a fresh delivery archive, parse its central directory, decompress every member, verify sizes and CRC-32, and hash the archive.", defaultInactive: true, approval: "write", parameters: z.object({ kind: z.enum(["checkpoint", "final"]).default("checkpoint") }), async execute(_id, params, signal, onUpdate, ctx) { if (signal?.aborted) return textResult("Cancelled", undefined, true); onUpdate?.({ content: [{ type: "text", text: "Building and verifying archive…" }] }); const result = await exportProject(runtime.get(), params.kind); await appendMarkdownLog(runtime.get().projectRoot, "delivery_ledger.md", "Archive verified", [ `Archive: ${result.zipPath}`, `SHA-256: ${result.verification.sha256}`, `Members: ${result.verification.members.length}`, `Verification: ${result.verificationPath}`, ].join("\n\n")); pi.sendMessage({ customType: "pom:milestone", content: "Archive verified", display: true, details: { stage: runtime.get().currentStage, status: "ARCHIVE PASS", path: result.verificationPath } }, { triggerTurn: false }); await refresh(pi, runtime, ctx); return textResult(`PASS · ${result.zipPath} · ${result.verification.members.length} members`, result); }, }); }