// Purpose: Register the oracle extension, wire commands/tools/workers, and manage per-session background maintenance. // Responsibilities: Bootstrap oracle commands and tools, start or stop polling, and surface startup/config availability in the pi session UI. // Scope: Extension entrypoint only; lifecycle mutation lives in lib modules and browser execution lives in worker scripts. // Usage: Loaded by pi as the extension module declared in package.json. // Invariants/Assumptions: Oracle only runs against persisted sessions, and startup maintenance should be best-effort without breaking session initialization. import { fileURLToPath } from "node:url"; import { dirname, join } from "node:path"; import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent"; import { loadOracleConfig } from "./lib/config.js"; import { registerOracleCommands } from "./lib/commands.js"; import { getSessionFile, pruneTerminalOracleJobs, reconcileStaleOracleJobs } from "./lib/jobs.js"; import { isLockTimeoutError, withGlobalReconcileLock } from "./lib/locks.js"; import { refreshOracleStatus, startPoller, stopPoller } from "./lib/poller.js"; import { promoteQueuedJobs } from "./lib/queue.js"; import { hasPersistedSessionFile } from "./lib/runtime.js"; import { registerOracleTools } from "./lib/tools.js"; export default function oracleExtension(pi: ExtensionAPI) { const extensionDir = dirname(fileURLToPath(import.meta.url)); const workerPath = join(extensionDir, "worker", "run-job.mjs"); const authWorkerPath = join(extensionDir, "worker", "auth-bootstrap.mjs"); registerOracleCommands(pi, authWorkerPath, workerPath); registerOracleTools(pi, workerPath, authWorkerPath); async function runStartupMaintenance(ctx: ExtensionContext): Promise { try { await withGlobalReconcileLock({ processPid: process.pid, source: "oracle_session_start", cwd: ctx.cwd }, async () => { await reconcileStaleOracleJobs(); await pruneTerminalOracleJobs(); }, { timeoutMs: 250 }); } catch (error) { if (!isLockTimeoutError(error, "reconcile", "global")) throw error; } await promoteQueuedJobs({ workerPath, source: "oracle_session_start" }); } function startPollerForContext(ctx: ExtensionContext) { try { const sessionFile = getSessionFile(ctx); if (!hasPersistedSessionFile(sessionFile)) { stopPoller(ctx); ctx.ui.setStatus("oracle", ctx.ui.theme.fg("accent", "oracle: unavailable")); return; } const config = loadOracleConfig(ctx.cwd); void runStartupMaintenance(ctx).catch((error) => { const message = `Oracle startup maintenance failed: ${error instanceof Error ? error.message : String(error)}`; console.error(message); ctx.ui.notify(message, "warning"); }); startPoller(pi, ctx, config.poller.intervalMs, workerPath); refreshOracleStatus(ctx); } catch (error) { const message = error instanceof Error ? error.message : String(error); stopPoller(ctx); ctx.ui.setStatus("oracle", ctx.ui.theme.fg("error", "oracle: config error")); ctx.ui.notify(message, "warning"); } } pi.on("session_start", async (_event, ctx) => { startPollerForContext(ctx); }); pi.on("session_shutdown", async (_event, ctx) => { stopPoller(ctx); }); }