/** * Slash commands for the jobs extension. * * One `/jobs` command opens an interactive list (select, view logs, kill, * clear) in the TUI, and prints a text summary everywhere else. */ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent"; import { formatJobList } from "./format.ts"; import type { JobsRuntime } from "./runtime.ts"; /** How often the open log pane re-reads the log file. */ const LOG_POLL_MS = 500; export function registerCommands(pi: ExtensionAPI, runtime: JobsRuntime): void { pi.registerCommand("jobs", { description: "Show and manage background jobs", handler: async (_args, ctx) => { // The session's registry load is started, not awaited, so the first read waits for it: // a `/jobs` typed right after a reload would otherwise list an unmerged registry. await runtime.ready(); if (ctx.mode !== "tui") { ctx.ui.notify(formatJobList(runtime.list()), "info"); return; } // The screen and the dock it mounts are the TUI's, not the listener's: the text // listing above and registration load neither. const { withPolledScreen } = await import("../../lib/screen-session.ts"); const { JobListComponent } = await import("./view/jobs-screen.ts"); await withPolledScreen( pi, ctx, (screen) => new JobListComponent( () => runtime.list(), screen.theme, { sessionId: () => runtime.sessionId, logs: (id) => runtime.logs(id, 200), kill: (id) => { runtime.kill(id); }, clear: () => { const result = runtime.clear(undefined, false); if (result.cleared > 0) { ctx.ui.notify(`Cleared ${result.cleared} job${result.cleared === 1 ? "" : "s"}.`, "info"); } }, }, screen.close, screen.requestRender, screen.viewportRows, ), { pollMs: LOG_POLL_MS, // Only the open log pane is polled; the runtime's own clock repaints the list. poll: (component) => { const id = component.currentLogId(); if (id) component.refreshLogs(id); }, }, ); }, }); }