import { type Config, DEFAULTS, loadConfig } from "./config.js"; export type ResolveResult = | { ok: true; model: unknown; apiKey: string; headers?: Record } | { ok: false; reason: string }; type NotifyLevel = "warning" | "info" | "error"; type Notify = (message: string, type?: NotifyLevel) => void; export type ConsolidationPhase = "observer" | "reflector" | "dropper"; export interface ResolveCtx { model: unknown; modelRegistry: any; hasUI: boolean; ui?: { notify: Notify }; } export interface LaunchCtx { hasUI: boolean; ui?: { notify: Notify }; } export class Runtime { config: Config = { ...DEFAULTS }; configLoaded = false; consolidationInFlight = false; consolidationPromise: Promise | null = null; consolidationPhase: ConsolidationPhase | undefined; compactInFlight = false; compactHookInFlight = false; resolveFailureNotified = false; lastObserverError: string | undefined; lastReflectorError: string | undefined; lastDropperError: string | undefined; ensureConfig(cwd: string): void { if (this.configLoaded) return; this.config = loadConfig(cwd); this.configLoaded = true; } async resolveModel(ctx: ResolveCtx): Promise { let model = ctx.model; if (this.config.model) { const configured = ctx.modelRegistry.find(this.config.model.provider, this.config.model.id); if (configured) { model = configured; } else if (ctx.hasUI && ctx.ui) { ctx.ui.notify( `Observational memory: configured model ${this.config.model.provider}/${this.config.model.id} not found, using session model`, "warning", ); } } if (!model) return { ok: false, reason: "no model available (session has no model and no observational-memory model configured)" }; const auth = await ctx.modelRegistry.getApiKeyAndHeaders(model); if (!auth.ok || !auth.apiKey) { const provider = (model as { provider?: string }).provider ?? "unknown"; return { ok: false, reason: `no API key for provider "${provider}"` }; } return { ok: true, model, apiKey: auth.apiKey as string, headers: auth.headers as Record | undefined }; } launchConsolidationTask(ctx: LaunchCtx, work: () => Promise): Promise { this.consolidationInFlight = true; this.consolidationPhase = undefined; this.lastObserverError = undefined; this.lastReflectorError = undefined; this.lastDropperError = undefined; const promise = this.launchTrackedTask(ctx, "consolidation", work, () => { this.consolidationInFlight = false; this.consolidationPhase = undefined; if (this.consolidationPromise === promise) this.consolidationPromise = null; }); this.consolidationPromise = promise; return promise; } recordConsolidationStageError(ctx: LaunchCtx, phase: ConsolidationPhase, error: unknown): string { const message = error instanceof Error ? error.message : String(error); if (phase === "observer") this.lastObserverError = message; if (phase === "reflector") this.lastReflectorError = message; if (phase === "dropper") this.lastDropperError = message; if (ctx.hasUI && ctx.ui) ctx.ui.notify(`Observational memory: ${phase} failed: ${message}`, "warning"); return message; } private launchTrackedTask( ctx: LaunchCtx, label: string, work: () => Promise, onFinally: (error: string | undefined) => void, ): Promise { const hasUI = ctx.hasUI; const ui = ctx.ui; return (async () => { let errorMessage: string | undefined; try { await work(); } catch (error) { errorMessage = error instanceof Error ? error.message : String(error); if (hasUI && ui) ui.notify(`Observational memory: ${label} failed: ${errorMessage}`, "warning"); } finally { onFinally(errorMessage); } })(); } }