import { writeFileSync } from "node:fs"; import { basename, join } from "node:path"; import { tmpdir } from "node:os"; import type { Platform, PlatformContext } from "../platform/types.js"; import { systemPromptText } from "../platform/system-prompt.js"; import { parseSystemPrompt, buildBreakdownItems, formatSectionReport, formatToolsReport, } from "../context/analyzer.js"; import type { ContextUsage } from "../context/analyzer.js"; import { buildSavingsLinesFromStore, formatSavingsReportFromStore, getFirstRunNotice, } from "../context/savings.js"; import { getMetricsStore, getSessionId } from "../context-mode/hooks.js"; import { getProjectStateDir, getProjectStatePath } from "../workspace/state-paths.js"; import { openInEditor } from "../utils/editor.js"; const REPORT_FILE = ".omp-context-breakdown.md"; export function handleContext(platform: Platform, ctx: PlatformContext): void { void (async () => { if (!ctx.hasUI) return; // Gather data from OMP runtime let usage: ContextUsage | null = null; try { const raw = (ctx as any).getContextUsage?.(); if (raw && typeof raw === "object") { usage = { tokens: typeof raw.tokens === "number" ? raw.tokens : null, contextWindow: typeof raw.contextWindow === "number" ? raw.contextWindow : null, percent: typeof raw.percent === "number" ? raw.percent : null, }; } } catch { // getContextUsage not available — continue without } let systemPrompt = ""; try { systemPrompt = systemPromptText((ctx as any).getSystemPrompt?.()); } catch { // getSystemPrompt not available — continue without } // L1 metrics surfaces const store = getMetricsStore(); const sessionId = getSessionId(); const projectSlug = basename(getProjectStateDir(platform.paths, ctx.cwd)); const dbAbsPath = getProjectStatePath(platform.paths, ctx.cwd, "sessions", "metrics.db"); const sessionStartedAtMs = (() => { try { return store?.getSessionMeta(sessionId)?.started_at ?? null; } catch { return null; } })(); // Bail only when *nothing* is available: no usage, no system prompt, no metrics store. if (!usage && !systemPrompt && !store) { ctx.ui.notify("Context data unavailable", "warning"); return; } // Parse system prompt (may be empty) const sections = systemPrompt ? parseSystemPrompt(systemPrompt) : []; const activeTools = platform.getActiveTools(); const baseItems = buildBreakdownItems(usage, sections, activeTools, !systemPrompt); // Build the savings panel + first-run notice + footer const noticeLine = getFirstRunNotice(store, projectSlug, dbAbsPath); const savingsLines = buildSavingsLinesFromStore( store, sessionId, sessionStartedAtMs, dbAbsPath, ); const footerLine = `Metrics DB: ${dbAbsPath}`; const drillableSavings = new Set(savingsLines); const items: typeof baseItems = []; if (noticeLine) items.push({ line: noticeLine }); for (const line of savingsLines) items.push({ line }); items.push({ line: footerLine }); items.push(...baseItems); const lines = items.map(i => i.line); while (true) { const choice = await ctx.ui.select("Context Breakdown", lines, { helpText: "Select to inspect, Esc to close", }); if (!choice || choice.trim() === "Close") break; let report: string | null = null; if (drillableSavings.has(choice)) { report = formatSavingsReportFromStore(store, sessionId, sessionStartedAtMs); } else { const item = items.find(i => i.line === choice); if (!item || (!item.section && !item.toolNames)) continue; if (item.section) { report = formatSectionReport(item.section); } else if (item.toolNames) { report = formatToolsReport(item.toolNames); } } if (report) { const filePath = writeReport(ctx.cwd, report); await openInEditor(platform, filePath); ctx.ui.notify(`Wrote ${REPORT_FILE}`, "info"); } } })().catch((err) => { ctx.ui.notify(`Context error: ${(err as Error).message}`, "error"); }); } export function registerContextCommand(platform: Platform): void { platform.registerCommand("supi:context", { description: "Show context window breakdown — what's consuming tokens", async handler(_args: string | undefined, ctx: any) { handleContext(platform, ctx); }, }); } /** Write report to project root, falling back to tmpdir on failure */ function writeReport(cwd: string, content: string): string { const primary = join(cwd, REPORT_FILE); try { writeFileSync(primary, content, "utf-8"); return primary; } catch { const fallback = join(tmpdir(), REPORT_FILE); writeFileSync(fallback, content, "utf-8"); return fallback; } }