import type { OpencodeStatus } from './manager'; type ProjectConfigReadResult = { status: 'valid' | 'missing' | 'invalid'; config?: { initialized?: boolean }; }; export type OpencodeStartupWarmupSkipReason = | 'no-session' | 'runtime-active' | 'no-active-project' | 'project-not-ready' | 'no-provider' | 'eligibility-check-failed' | 'start-failed'; export type OpencodeStartupWarmupResult = | { started: true; status: OpencodeStatus } | { started: false; reason: OpencodeStartupWarmupSkipReason; error?: unknown }; export interface OpencodeStartupWarmupDependencies { hasAuthenticatedSession: () => boolean; getStatus: () => OpencodeStatus; getActiveProject: () => Promise<{ path: string } | null>; readProjectConfig: (projectPath: string) => Promise; getConfiguredProviderCount: () => Promise; start: () => Promise; onError?: (error: unknown, phase: 'eligibility' | 'start') => void; } function reportError( dependencies: OpencodeStartupWarmupDependencies, error: unknown, phase: 'eligibility' | 'start', ): void { try { dependencies.onError?.(error, phase); } catch { // Logging must never turn a background warmup into an unhandled rejection. } } /** * Starts the local runtime in the background when the persisted app state is * ready for a Code session. This deliberately does not throw: startup * warmup is an optimization and the normal Chat-page lazy start remains the * recovery path when it is unavailable. */ export async function warmupOpencodeRuntime( dependencies: OpencodeStartupWarmupDependencies, ): Promise { if (!dependencies.hasAuthenticatedSession()) { return { started: false, reason: 'no-session' }; } const status = dependencies.getStatus(); if (status.state !== 'stopped') { return { started: false, reason: 'runtime-active' }; } let project: { path: string } | null; let projectConfig: ProjectConfigReadResult; let providerCount: number; try { project = await dependencies.getActiveProject(); if (!project) { return { started: false, reason: 'no-active-project' }; } projectConfig = await dependencies.readProjectConfig(project.path); if (projectConfig.status !== 'valid' || projectConfig.config?.initialized !== true) { return { started: false, reason: 'project-not-ready' }; } providerCount = await dependencies.getConfiguredProviderCount(); } catch (error) { reportError(dependencies, error, 'eligibility'); return { started: false, reason: 'eligibility-check-failed', error }; } if (!Number.isFinite(providerCount) || providerCount <= 0) { return { started: false, reason: 'no-provider' }; } try { const startedStatus = await dependencies.start(); return { started: true, status: startedStatus }; } catch (error) { reportError(dependencies, error, 'start'); return { started: false, reason: 'start-failed', error }; } }