import type { ExtensionAPI, ExtensionCommandContext, ExtensionContext } from "@earendil-works/pi-coding-agent"; import { UpdateFailure, updatePiAndExtensions, type UpdateReporter, type UpdateResult } from "./updater.ts"; const STATUS_KEY = "pi-update"; function conciseError(error: unknown): string { const message = error instanceof Error ? error.message : String(error); const lines = message.split(/\r?\n/).filter(Boolean); return lines.slice(Math.max(0, lines.length - 12)).join("\n"); } function notify(ctx: ExtensionContext, message: string, level: "info" | "warning" | "error" = "info"): void { if (ctx.hasUI) { ctx.ui.notify(message, level); } else if (level === "error") { console.error(message); } else if (level === "warning") { console.warn(message); } else { console.log(message); } } function makeReporter(ctx: ExtensionContext): UpdateReporter { return { info(message) { ctx.ui.setStatus(STATUS_KEY, message); notify(ctx, message, "info"); }, retry(message) { ctx.ui.setStatus(STATUS_KEY, message); notify(ctx, message, "warning"); }, }; } function formatResult(result: UpdateResult): string { const versionChange = result.beforeVersion === result.afterVersion ? `Pi remains at v${result.afterVersion}` : `Pi updated: v${result.beforeVersion} → v${result.afterVersion}`; const extensionStatus = result.extensionsChecked ? "Configured Pi extension packages were checked successfully." : "Extension package checking did not complete."; return `${versionChange}\nInstall method: ${result.method}\n${extensionStatus}\nRestart Pi to load updated code.`; } export default function updateExtension(pi: ExtensionAPI): void { let activeUpdate: Promise | undefined; let updateFlagHandled = false; pi.registerFlag("update", { description: "Update Pi and all configured extension packages, then exit", type: "boolean", default: false, }); async function runUpdate(ctx: ExtensionContext): Promise { if (!activeUpdate) { ctx.ui.setStatus(STATUS_KEY, "Preparing Pi update"); activeUpdate = updatePiAndExtensions(pi, makeReporter(ctx), { projectTrusted: ctx.isProjectTrusted(), }).finally(() => { ctx.ui.setStatus(STATUS_KEY, undefined); activeUpdate = undefined; }); } return activeUpdate; } async function handleUpdate(ctx: ExtensionContext, exitAfterUpdate: boolean): Promise { try { const result = await runUpdate(ctx); notify(ctx, formatResult(result), "info"); if (exitAfterUpdate || result.requiresShutdown) ctx.shutdown(); } catch (error) { notify(ctx, `Update failed\n${conciseError(error)}`, "error"); if (exitAfterUpdate) process.exitCode = 1; if (exitAfterUpdate || (error instanceof UpdateFailure && error.requiresShutdown)) ctx.shutdown(); } } pi.registerCommand("update", { description: "Update Pi and all configured extension packages", handler: async (_args: string, ctx: ExtensionCommandContext) => { await ctx.waitForIdle(); await handleUpdate(ctx, false); }, }); pi.on("session_start", async (_event, ctx) => { if (updateFlagHandled || pi.getFlag("update") !== true) return; updateFlagHandled = true; await handleUpdate(ctx, true); }); }