import type { ExtensionAPI, ExtensionCommandContext, ExtensionContext, } from "@earendil-works/pi-coding-agent"; import { loadMidrunCompactConfig, resolveConfigPaths, type LoadConfigOptions, } from "./config.ts"; import { CONTINUATION_PROMPT } from "./continuation.ts"; import { blockRepeatedFailure, clearRuntimeFailure, createRuntimeState, invalidateRuntime, isCompactionBusy, resetRuntime, safeSessionId, startCompaction, } from "./runtime.ts"; import { evaluateThreshold, buildFailureKey, normalizeContextUsage } from "./threshold.ts"; import { endsWithCompleteToolResultBatch } from "./tool-batches.ts"; import { COMMAND_NAME, DEFAULT_CONFIG, EXTENSION_ID, type LoadedConfig, type MidrunCompactConfig, type RuntimeState, type TriggerSnapshot, } from "./types.ts"; export interface MidrunCompactExtensionDependencies { loadConfig?: (options: LoadConfigOptions) => LoadedConfig; continuationPrompt?: string; } function errorText(error: unknown): string { return error instanceof Error ? error.message : String(error); } function fallbackInvalidConfig(ctx: ExtensionContext, error: unknown): LoadedConfig { let globalPath = ""; let projectPath = ""; try { const paths = resolveConfigPaths(ctx.cwd); globalPath = paths.globalPath; projectPath = paths.projectPath; } catch { // Preserve the original loader error below. } let projectConfigSkippedAsUntrusted = true; try { projectConfigSkippedAsUntrusted = !ctx.isProjectTrusted(); } catch { // A broken/stale trust context is itself a reason to skip project config. } return { config: { ...DEFAULT_CONFIG }, valid: false, automaticEnabled: false, globalPath, projectPath, loadedSources: [], errors: [`Configuration loading failed: ${errorText(error)}`], warnings: [], projectConfigSkippedAsUntrusted, }; } function loadConfigSafely( ctx: ExtensionContext, loader: (options: LoadConfigOptions) => LoadedConfig, ): LoadedConfig { try { return loader({ cwd: ctx.cwd, projectTrusted: ctx.isProjectTrusted(), }); } catch (error) { return fallbackInvalidConfig(ctx, error); } } function notifyDirect( ctx: ExtensionContext, message: string, type: "info" | "warning" | "error", ): void { if (!ctx.hasUI) return; ctx.ui.notify(`${EXTENSION_ID}: ${message}`, type); } function reportConfigDiagnostics(ctx: ExtensionContext, loaded: LoadedConfig): void { if (loaded.errors.length > 0) { notifyDirect( ctx, `automatic triggering is disabled because configuration is invalid:\n${loaded.errors.map((item) => `- ${item}`).join("\n")}`, "error", ); } if (loaded.config.notify && loaded.warnings.length > 0) { notifyDirect(ctx, loaded.warnings.map((item) => `- ${item}`).join("\n"), "warning"); } } function mergeInstructions(base: string, extra: string | undefined): string | undefined { const parts = [base.trim(), extra?.trim()] .filter((part): part is string => Boolean(part)); return parts.length > 0 ? parts.join("\n\n") : undefined; } function currentTriggerSnapshot(ctx: ExtensionContext, config: MidrunCompactConfig): TriggerSnapshot | undefined { const usage = normalizeContextUsage(ctx.getContextUsage()); if (!usage) return undefined; return { tokens: usage.tokens, contextWindow: usage.contextWindow, percent: usage.percent, thresholdPercent: config.thresholdPercent, }; } function formatStatus(runtime: RuntimeState, loaded: LoadedConfig, ctx: ExtensionContext): string { const lines = [ `phase: ${runtime.phase}`, `automatic trigger: ${loaded.automaticEnabled ? "enabled" : "disabled"}`, `configured enabled: ${loaded.config.enabled}`, `configuration valid: ${loaded.valid}`, `threshold: ${loaded.config.thresholdPercent}%`, `auto resume: ${loaded.config.autoResume}`, `notifications: ${loaded.config.notify}`, `session: ${safeSessionId(ctx) ?? "unknown"}`, ]; if (runtime.triggerTokens !== undefined) { lines.push( `last trigger: ${runtime.triggerSource ?? "unknown"} at ${runtime.triggerTokens.toLocaleString()} tokens (${runtime.triggerPercent?.toFixed(1) ?? "?"}%)`, ); } if (runtime.lastError) lines.push(`last error: ${runtime.lastError}`); if (runtime.lastFailureKey) lines.push(`failure guard: ${runtime.lastFailureKey}`); lines.push(`global config: ${loaded.globalPath}`); if (loaded.projectPath) { lines.push( `project config: ${loaded.projectPath}${loaded.projectConfigSkippedAsUntrusted ? " (not read: project untrusted)" : ""}`, ); } lines.push(`loaded config files: ${loaded.loadedSources.length > 0 ? loaded.loadedSources.join(", ") : "none"}`); if (loaded.errors.length > 0) lines.push(`config errors: ${loaded.errors.join(" | ")}`); if (loaded.warnings.length > 0) lines.push(`config warnings: ${loaded.warnings.join(" | ")}`); return lines.join("\n"); } function parseCommand(args: string): { action: string; instructions?: string } { const trimmed = args.trim(); if (!trimmed) return { action: "status" }; const whitespace = trimmed.search(/\s/); if (whitespace === -1) return { action: trimmed.toLowerCase() }; return { action: trimmed.slice(0, whitespace).toLowerCase(), instructions: trimmed.slice(whitespace + 1).trim() || undefined, }; } function registerCommand( pi: ExtensionAPI, runtime: RuntimeState, getLoadedConfig: (ctx: ExtensionContext) => LoadedConfig, continuationPrompt: string, ): void { pi.registerCommand(COMMAND_NAME, { description: "Inspect, trigger, or retry safe mid-run Pi compaction (status | now | retry)", handler: async (args: string, ctx: ExtensionCommandContext) => { const loaded = getLoadedConfig(ctx); const request = parseCommand(args); if (request.action === "status") { notifyDirect(ctx, formatStatus(runtime, loaded, ctx), loaded.valid ? "info" : "error"); return; } if (request.action !== "now" && request.action !== "retry") { notifyDirect(ctx, "usage: /midrun-compact status | now [instructions] | retry [instructions]", "warning"); return; } if (isCompactionBusy(runtime)) { notifyDirect(ctx, "a compaction or continuation dispatch is already in progress.", "warning"); return; } if (request.action === "retry" && !runtime.lastError) { notifyDirect(ctx, "there is no failed compaction to retry. Use /midrun-compact now to compact explicitly.", "warning"); return; } const previousFailureKey = runtime.lastFailureKey; clearRuntimeFailure(runtime); startCompaction(pi, ctx, runtime, { source: request.action, config: loaded.config, customInstructions: mergeInstructions( loaded.config.customCompactionInstructions, request.instructions, ), trigger: currentTriggerSnapshot(ctx, loaded.config), failureKey: request.action === "retry" ? previousFailureKey : undefined, continuationPrompt, }); }, }); } export function createMidrunCompactExtension( dependencies: MidrunCompactExtensionDependencies = {}, ): (pi: ExtensionAPI) => void { const configLoader = dependencies.loadConfig ?? loadMidrunCompactConfig; const continuationPrompt = dependencies.continuationPrompt ?? CONTINUATION_PROMPT; return function midrunCompactExtension(pi: ExtensionAPI): void { const runtime = createRuntimeState(); let loadedConfig: LoadedConfig | undefined; const getLoadedConfig = (ctx: ExtensionContext): LoadedConfig => { loadedConfig ??= loadConfigSafely(ctx, configLoader); return loadedConfig; }; pi.on("session_start", (_event, ctx) => { resetRuntime(runtime); loadedConfig = loadConfigSafely(ctx, configLoader); reportConfigDiagnostics(ctx, loadedConfig); }); pi.on("context", (event, ctx) => { const loaded = getLoadedConfig(ctx); if (!loaded.automaticEnabled || !ctx.model) return; if (!endsWithCompleteToolResultBatch(event.messages)) return; if (isCompactionBusy(runtime)) return; const evaluation = evaluateThreshold( ctx.getContextUsage(), loaded.config.thresholdPercent, ); if (!evaluation) return; const sessionId = safeSessionId(ctx); if (!sessionId) return; const failureKey = buildFailureKey(sessionId, evaluation.usage); if (blockRepeatedFailure(ctx, runtime, loaded.config, failureKey)) return; startCompaction(pi, ctx, runtime, { source: "threshold", config: loaded.config, customInstructions: mergeInstructions( loaded.config.customCompactionInstructions, undefined, ), trigger: evaluation.trigger, failureKey, continuationPrompt, }); }); pi.on("session_shutdown", (_event, ctx) => { invalidateRuntime(runtime); if (ctx.hasUI) { try { ctx.ui.setStatus(EXTENSION_ID, undefined); } catch { // Runtime invalidation is authoritative; UI cleanup is best-effort. } } }); registerCommand(pi, runtime, getLoadedConfig, continuationPrompt); }; } export default createMidrunCompactExtension();