import type { ExtensionAPI, ExtensionCommandContext, ExtensionContext } from "@earendil-works/pi-coding-agent"; import { getConfigReport } from "./lib/config.ts"; import { registerGrounding } from "./lib/context.ts"; import { sendPromote } from "./lib/promote.ts"; import { openSettings } from "./lib/settings-view.ts"; import { createThreadStore, type ThreadStats } from "./lib/threads.ts"; import { SummaryCoordinator } from "./lib/summary-coordinator.ts"; import { openHistory, openOverlay } from "./lib/ui.ts"; export type IndexDependencies = { createThreadStore?: typeof createThreadStore; createSummaryCoordinator?: (threads: ReturnType) => SummaryCoordinator; /** Test seams; omitted in production so Pi always uses the real entry points. */ getConfigReport?: typeof getConfigReport; openSettings?: typeof openSettings; openHistory?: typeof openHistory; openOverlay?: typeof openOverlay; }; export function formatStats(stats: ThreadStats): string { const meter = stats.meter; const usage = meter.usage; const normal = (value: number) => String(value); const fixed = (value: number) => value.toFixed(6); const lines = [ `btw stats${stats.active ? " (active thread lineage only)" : ""}`, `thread record count: ${stats.storedThreads}`, `status counts: questions ${stats.questions}, attempts ${stats.attempts}, successful ${stats.successful}, errored ${stats.errored}, promoted ${stats.promoted}`, `summary requests: ${meter.requests}; committed ${meter.committed}, stale ${meter.stale}, failed ${meter.failed}, aborted ${meter.aborted}`, `current summary: ${stats.summary.present ? "present" : "absent"}, covers ${stats.summary.present ? stats.summary.covered : 0}/${stats.questions}`, `known/unknown usage: known ${meter.usageKnownRuns}, unknown ${meter.requests - meter.usageKnownRuns}`, `tokens: input ${normal(usage.input)}, output ${normal(usage.output)}, cacheRead ${normal(usage.cacheRead)}, cacheWrite ${normal(usage.cacheWrite)}, total ${normal(usage.totalTokens)}`, `costs: input ${fixed(usage.cost.input)}, output ${fixed(usage.cost.output)}, cacheRead ${fixed(usage.cost.cacheRead)}, cacheWrite ${fixed(usage.cost.cacheWrite)}, total ${fixed(usage.cost.total)}`, ]; if (!stats.active) lines.splice(2, 0, "no active thread"); if (usage.reasoning !== undefined) lines.push(`reasoning: ${normal(usage.reasoning)}`); if (usage.cacheWrite1h !== undefined) lines.push(`cacheWrite1h: ${normal(usage.cacheWrite1h)}`); return lines.join("\n"); } /** Optional factories keep the public one-argument Pi entry point unchanged. */ export default function (pi: ExtensionAPI, dependencies: IndexDependencies = {}) { // Registration is synchronous and exactly once. Invalid sources are already // diagnosed/fallen back by getConfigReport, so Pi receives a safe KeyId. const startupConfig = (dependencies.getConfigReport ?? getConfigReport)(); const settingsView = dependencies.openSettings ?? openSettings; const historyView = dependencies.openHistory ?? openHistory; const overlayView = dependencies.openOverlay ?? openOverlay; const grounding = registerGrounding(pi); const threads = (dependencies.createThreadStore ?? createThreadStore)(); const summaries = dependencies.createSummaryCoordinator?.(threads) ?? new SummaryCoordinator(threads); const onPromote = (note: string) => sendPromote(pi, note); let diagnosticsReported = false; pi.on("session_start", async (event, ctx) => { summaries.resetScope(); await threads.startSession(ctx, event.reason); if (!diagnosticsReported && ctx.hasUI && startupConfig.diagnostics.length) { diagnosticsReported = true; ctx.ui.notify(`btw settings: ${startupConfig.diagnostics.join("; ")}`, "warning"); } }); pi.on("session_tree", async (event, ctx) => { if (event.oldLeafId !== event.newLeafId) summaries.resetScope(); await threads.tree(ctx, event.oldLeafId, event.newLeafId); }); pi.on("session_shutdown", async () => { await summaries.shutdown(); await threads.shutdown(); }); pi.registerCommand("btw", { description: "Ask a side question grounded in the current session (auto-investigates with read-only tools when needed; does not write to the main transcript unless you confirm a share)", handler: async (args, ctx) => { const trimmed = args.trim(); if (trimmed === "--settings") { await settingsView(ctx as ExtensionCommandContext); return; } if (trimmed === "--stats") { if (!ctx.hasUI) { ctx.ui.notify("/btw --stats requires interactive UI", "error"); return; } ctx.ui.notify(formatStats(threads.stats?.() ?? { storedThreads: 0, active: false, questions: 0, attempts: 0, successful: 0, errored: 0, promoted: 0, summary: { present: false, covered: 0 }, meter: { runIds: [], requests: 0, committed: 0, stale: 0, failed: 0, aborted: 0, usageKnownRuns: 0, usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, totalTokens: 0, cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 } } } }), "info"); return; } if (trimmed === "--history") { await historyView(ctx, threads, grounding, onPromote, undefined, summaries); return; } const historyQuery = trimmed.match(/^--history\s+(\S(?:.*\S)?)$/)?.[1]; if (historyQuery) { await historyView(ctx, threads, grounding, onPromote, undefined, summaries, historyQuery); return; } await overlayView(ctx, threads, grounding, args, onPromote, undefined, summaries); }, }); pi.registerShortcut((startupConfig.settings.shortcut ?? "ctrl+alt+b") as any, { description: "btw: open the side-question overlay", handler: async (ctx: ExtensionContext) => { await overlayView(ctx, threads, grounding, undefined, onPromote, undefined, summaries); }, }); }