import type { ExtensionAPI, ExtensionContext } from "@oh-my-pi/pi-coding-agent"; import copy from "./copy.json"; import { ENTRY_TYPE, isReviewState, MAX_TEXT_LENGTH, MESSAGE_TYPE, type ReviewAction, type ReviewState, transition } from "./domain"; import { BoundedText, ReviewPanel, safeText } from "./panel"; export default async function reviewDesk(pi: ExtensionAPI): Promise { const fixture = await Bun.file(`${import.meta.dir}/release-note.txt`).text(); let grant: { sessionId: string; revision: number } | undefined; let pending: AbortController | undefined; let generation = 0; function stateFor(ctx: ExtensionContext): ReviewState { let state: ReviewState = { revision: 0, status: "draft", text: fixture }; for (const entry of ctx.sessionManager.getBranch()) { if (entry.type === "custom" && entry.customType === ENTRY_TYPE && isReviewState(entry.data)) state = entry.data; } return state; } function granted(ctx: ExtensionContext, state: ReviewState): boolean { return grant?.sessionId === ctx.sessionManager.getSessionId() && grant.revision === state.revision; } function present(ctx: ExtensionContext, state = stateFor(ctx)): void { ctx.ui.setStatus("review-desk", `Review: ${state.status} · r${state.revision}`); ctx.ui.setWidget("review-desk", [ `Release note: ${state.status} (revision ${state.revision})`, granted(ctx, state) ? "Agent: one local change authorized" : "Agent: inspect only; /review-desk delegate to authorize", "Local only — nothing sent or published", ], { placement: "aboveEditor" }); } function record(ctx: ExtensionContext, next: ReviewState): void { grant = undefined; pi.appendEntry(ENTRY_TYPE, next); present(ctx, next); pi.sendMessage({ customType: MESSAGE_TYPE, content: `Release note ${next.status} locally (revision ${next.revision}). Nothing published.`, details: next, display: true }); } function invalidateAuthority(): void { generation++; grant = undefined; pending?.abort(); } pi.on("session_before_switch", invalidateAuthority); pi.on("session_before_branch", invalidateAuthority); pi.on("session_before_tree", invalidateAuthority); pi.on("session_start", (_event, ctx) => present(ctx)); pi.on("session_switch", (_event, ctx) => present(ctx)); pi.on("session_branch", (_event, ctx) => present(ctx)); pi.on("session_tree", (_event, ctx) => present(ctx)); pi.on("session_shutdown", (_event, ctx) => { invalidateAuthority(); ctx.ui.setStatus("review-desk", undefined); ctx.ui.setWidget("review-desk", undefined); }); pi.registerMessageRenderer(MESSAGE_TYPE, (message, options, theme) => { const heading = typeof message.content === "string" ? message.content : "Local review update"; return new BoundedText(options.expanded && isReviewState(message.details) ? `${heading}\n\n${message.details.text}` : heading, theme); }); pi.registerCommand("review-desk", { description: copy.command, async handler(args, ctx) { const verb = args.trim() || "show"; const state = stateFor(ctx); if (verb === "show") { present(ctx, state); pi.sendMessage({ customType: MESSAGE_TYPE, content: `Release note ${state.status} locally (revision ${state.revision}). Nothing published.`, details: state, display: true }); return; } if (verb === "revoke") { invalidateAuthority(); present(ctx, state); ctx.ui.notify("Agent authorization revoked. Draft unchanged."); return; } if (!["review", "overlay", "delegate"].includes(verb)) { ctx.ui.notify(copy.usage, "warning"); return; } // hasUI is true for this build's wired RPC adapter; mode guards native components. if (!ctx.hasUI || (ctx.mode !== "tui" && ctx.mode !== "rpc")) { pi.sendMessage({ customType: MESSAGE_TYPE, content: copy.noUi, display: true }); return; } if (verb === "overlay" && ctx.mode !== "tui") { ctx.ui.notify(copy.noOverlay, "warning"); return; } if (pending) { ctx.ui.notify("A review dialog is already open. Nothing changed.", "warning"); return; } const controller = new AbortController(); pending = controller; const epoch = generation; try { if (verb === "delegate") { const approved = await ctx.ui.confirm(copy.grantTitle, copy.grantMessage, { signal: controller.signal }); if (epoch !== generation) return; grant = approved ? { sessionId: ctx.sessionManager.getSessionId(), revision: state.revision } : undefined; present(ctx, state); ctx.ui.notify(approved ? `One agent change authorized for revision ${state.revision}.` : "No agent change authorized. Draft unchanged."); return; } let action: Exclude = "cancel"; let edited: string | undefined; if (verb === "overlay") { action = await ctx.ui.custom>( (tui, theme, _keys, done) => new ReviewPanel(state.text, theme, () => tui.requestRender(), done), { overlay: true, overlayOptions: { anchor: "center", width: "90%", maxHeight: "90%", margin: 1 }, signal: controller.signal }, ); } else { edited = await ctx.ui.editor(copy.editorTitle, safeText(state.text), { signal: controller.signal }); if (edited !== undefined) { const choice = await ctx.ui.select(copy.decisionTitle, copy.choices, { signal: controller.signal }); action = choice === "Accept locally" ? "accept" : choice === "Reject" ? "reject" : "cancel"; } } if (epoch !== generation) return; record(ctx, transition(state, action, action === "accept" ? edited : undefined)); } catch (error) { if (!controller.signal.aborted) throw error; } finally { if (pending === controller) pending = undefined; controller.abort(); } }, }); const z = pi.zod; const parameters = z.object({ op: z.enum(["discover", "inspect", "query", "act"]), action: z.enum(["revise", "accept", "reject", "cancel"]).optional(), expectedRevision: z.number().int().nonnegative().optional(), text: z.string().min(1).max(MAX_TEXT_LENGTH).optional(), }); pi.registerTool({ name: "review_desk", label: "Review desk", description: copy.tool, approval: "write", parameters, async execute(_id, args, signal, _update, ctx) { signal?.throwIfAborted(); const state = stateFor(ctx); let result: object; if (args.op === "discover") result = { operations: ["discover", "inspect", "query", "act"], actions: ["revise", "accept", "reject", "cancel"], authorization: "/review-desk delegate; one action on the inspected revision; never persisted", transport: "local domain state only", publish: false, }; else if (args.op === "inspect") result = { ...state, authorized: granted(ctx, state) }; else if (args.op === "query") result = { revision: state.revision, status: state.status, authorized: granted(ctx, state), busy: pending !== undefined, mode: ctx.mode }; else { if (pending) throw new Error("Review dialog open. Nothing changed."); if (!granted(ctx, state)) throw new Error("No human grant for this revision. Nothing changed."); if (args.expectedRevision !== state.revision) throw new Error("Revision mismatch. Inspect again; nothing changed."); if (!args.action) throw new Error("act requires action."); if (args.action !== "revise" && args.text !== undefined) throw new Error("Only revise accepts text; inspect before accepting."); const next = transition(state, args.action, args.text); record(ctx, next); result = { ...next, authorized: false, published: false }; } return { content: [{ type: "text", text: JSON.stringify(result) }], details: result }; }, }); }