import { BorderedLoader, type ExtensionCommandContext } from "@earendil-works/pi-coding-agent"; export const SYMPHONY_PROGRESS_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]; export interface SymphonyProgressOptions { message: string; restoreStatus: (ctx: ExtensionCommandContext) => void; } export async function withSymphonyProgress( ctx: ExtensionCommandContext, options: SymphonyProgressOptions, fn: (signal: AbortSignal) => T | Promise, ): Promise { const controller = new AbortController(); ctx.ui.setWorkingIndicator({ frames: SYMPHONY_PROGRESS_FRAMES, intervalMs: 120 }); ctx.ui.setWorkingMessage(options.message); ctx.ui.setStatus("symphony", options.message); try { return await fn(controller.signal); } finally { ctx.ui.setWorkingIndicator(); ctx.ui.setWorkingMessage(); options.restoreStatus(ctx); } } export async function withSymphonyLoader( ctx: ExtensionCommandContext, options: SymphonyProgressOptions, fn: (signal: AbortSignal) => T | Promise, ): Promise { ctx.ui.setStatus("symphony", options.message); try { if (!ctx.hasUI) { const controller = new AbortController(); return await fn(controller.signal); } let operationFailed = false; let operationError: unknown; const result = await ctx.ui.custom((tui, theme, _keybindings, done) => { const loader = new BorderedLoader(tui, theme, options.message); let completed = false; const complete = (value: T | undefined) => { if (completed) return; completed = true; done(value); }; loader.onAbort = () => complete(undefined); void Promise.resolve() .then(() => fn(loader.signal)) .then( (value) => complete(value), (error: unknown) => { if (completed) return; operationFailed = true; operationError = error; complete(undefined); }, ); return loader; }); if (operationFailed) throw operationError; return result; } finally { options.restoreStatus(ctx); } }