import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent"; import { type ManagedRun, WorkflowManager } from "../src/manager.js"; import { createWorkflowTool } from "../src/tool.js"; /** * pi-better-workflows — a faithful port of Claude Code's built-in Workflow * tool. Registers as the `workflow` tool (1:1 swap with * @quintinshaw/pi-dynamic-workflows; enable only one at a time to A/B). */ export default function extension(pi: ExtensionAPI): void { const cwd = process.cwd(); const manager = new WorkflowManager({ cwd }); const tool = createWorkflowTool({ cwd, manager }); pi.registerTool(tool); registerWorkflowsCommand(pi, manager); installResultDelivery(pi, manager); pi.on("session_start", (_event: unknown, ctx: ExtensionContext) => { const active = pi.getActiveTools(); if (!active.includes(tool.name)) pi.setActiveTools([...active, tool.name]); // Subagents inherit the session's main model unless an agent() overrides it. manager.setMainModel(ctx.model ? `${ctx.model.provider}/${ctx.model.id}` : undefined); }); } /** * When a background run finishes (or fails), deliver its result back into the * conversation and continue the turn — faithful to CC's . * Idempotent across re-installs. */ function installResultDelivery(pi: ExtensionAPI, manager: WorkflowManager): void { const guard = manager as unknown as { __delivery?: boolean }; if (guard.__delivery) return; guard.__delivery = true; const deliver = (content: string) => { try { const ret = pi.sendMessage( { customType: "workflow-result", content, display: true }, { triggerTurn: true, deliverAs: "followUp" }, ); void Promise.resolve(ret).catch(() => {}); } catch { // stale ctx after /reload — result still visible via /workflows } }; manager.on("complete", ({ runId }: { runId: string }) => { const run = manager.getRun(runId); if (run?.background) deliver(deliverText(run)); }); manager.on("error", ({ runId, error }: { runId: string; error?: { message?: string } }) => { const run = manager.getRun(runId); if (run?.background) deliver(`✗ Background workflow ${runId} failed: ${error?.message ?? "unknown error"}`); }); } function deliverText(run: ManagedRun): string { const r = run.result; const agents = r?.agentCount ?? run.snapshot.agentCount; const tokens = r?.tokensTotal ? ` · ${r.tokensTotal.toLocaleString()} tokens` : ""; const cost = r?.cost ? ` · $${r.cost.toFixed(4)}` : ""; const duration = r?.durationMs ? ` · ${(r.durationMs / 1000).toFixed(1)}s` : ""; return [ `✓ Background workflow "${run.snapshot.name}" finished (${agents} agents${tokens}${cost}${duration}).`, `Run ID: ${run.runId}`, "", summarize(r?.result), ].join("\n"); } function summarize(result: unknown): string { if (typeof result === "string") return result; if (result == null) return "null"; if (typeof result === "object") { for (const key of ["verdict", "report", "summary"] as const) { const v = (result as Record)[key]; if (typeof v === "string" && v.trim()) return v; } } const json = JSON.stringify(result, null, 2); return json.length > 1200 ? `${json.slice(0, 1200)}\n…(truncated — full result via /workflows)` : json; } /** `/workflows [status | stop ]` — list runs, inspect one, or cancel one. */ function registerWorkflowsCommand(pi: ExtensionAPI, manager: WorkflowManager): void { pi.registerCommand("workflows", { description: "List, inspect, or stop better-workflows runs", // biome-ignore lint/suspicious/noExplicitAny: command ctx shape varies by host version handler: async (args: string, _ctx: any) => { const [sub, id] = args.trim().split(/\s+/); let content: string; if (sub === "stop" && id) { content = manager.stop(id) ? `Stopped ${id}.` : `No run ${id}.`; } else if (sub === "status" && id) { const run = manager.getRun(id); content = run ? renderRun(run) : `No run ${id}.`; } else { const runs = manager.listRuns(); content = runs.length ? `Workflow runs:\n${runs.map((r) => ` ${statusIcon(r.status)} ${r.runId} ${r.snapshot.name} ${doneCount(r)}/${r.snapshot.agents.length} agents · ${r.status}`).join("\n")}` : "No workflow runs this session."; } try { await pi.sendMessage({ customType: "workflow-status", content, display: true }); } catch { // no-op } }, }); } function renderRun(run: ManagedRun): string { const lines = [`${statusIcon(run.status)} ${run.snapshot.name} (${run.runId}) — ${run.status}`]; if (run.snapshot.currentPhase) lines.push(`phase: ${run.snapshot.currentPhase}`); for (const a of run.snapshot.agents) { lines.push(` ${statusIcon(a.status)} [${a.phase ?? "-"}] ${a.label}${a.error ? ` — ${a.error}` : ""}`); } if (run.error) lines.push(`error: ${run.error.message}`); return lines.join("\n"); } function doneCount(run: ManagedRun): number { return run.snapshot.agents.filter((a) => a.status === "done").length; } function statusIcon(status: string): string { return status === "complete" || status === "done" ? "✓" : status === "error" ? "✗" : status === "stopped" ? "⏹" : "◆"; }