diff --git a/apps/server/src/provider/Layers/ProviderHealth.ts b/apps/server/src/provider/Layers/ProviderHealth.ts index 59f41edf81e8006371038949419b3296ab90ed69..41fc382a6ed6d5f1f3ed76b74e6552af7ce47daa 100644 --- a/apps/server/src/provider/Layers/ProviderHealth.ts +++ b/apps/server/src/provider/Layers/ProviderHealth.ts @@ -312,9 +312,56 @@ export const checkCodexProviderStatus: Effect.Effect< export const ProviderHealthLive = Layer.effect( ProviderHealth, Effect.gen(function* () { - const codexStatus = yield* checkCodexProviderStatus; + const spawner = yield* ChildProcessSpawner.ChildProcessSpawner; + let cachedStatuses: ReadonlyArray = [ + { + provider: CODEX_PROVIDER, + status: "warning", + available: false, + authStatus: "unknown", + checkedAt: new Date().toISOString(), + message: "Checking Codex CLI availability...", + }, + ]; + + let readyListeners: Array<(statuses: ReadonlyArray) => void> = []; + let resolved = false; + + const notifyReady = (statuses: ReadonlyArray) => { + resolved = true; + cachedStatuses = statuses; + for (const cb of readyListeners) cb(statuses); + readyListeners = []; + }; + + // Run health checks in the background so they don't block server startup. + checkCodexProviderStatus.pipe( + Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, spawner), + Effect.runPromise, + ).then((status) => { + notifyReady([status]); + }).catch(() => { + notifyReady([ + { + provider: CODEX_PROVIDER, + status: "error", + available: false, + authStatus: "unknown", + checkedAt: new Date().toISOString(), + message: "Failed to check Codex CLI status.", + }, + ]); + }); + return { - getStatuses: Effect.succeed([codexStatus]), + getStatuses: Effect.sync(() => cachedStatuses), + onReady: (cb) => { + if (resolved) { + cb(cachedStatuses); + } else { + readyListeners.push(cb); + } + }, } satisfies ProviderHealthShape; }), ); diff --git a/apps/server/src/provider/Services/ProviderHealth.ts b/apps/server/src/provider/Services/ProviderHealth.ts index 318d7e18d0ad57109a021af7329e21118c926103..3cc804a0c0e8bdc0e61756a362939257415ef13b 100644 --- a/apps/server/src/provider/Services/ProviderHealth.ts +++ b/apps/server/src/provider/Services/ProviderHealth.ts @@ -15,6 +15,10 @@ export interface ProviderHealthShape { * Read provider health statuses computed at server startup. */ readonly getStatuses: Effect.Effect>; + /** + * Register a callback invoked once when background health checks complete. + */ + readonly onReady: (cb: (statuses: ReadonlyArray) => void) => void; } export class ProviderHealth extends ServiceMap.Service()( diff --git a/apps/server/src/wsServer.ts b/apps/server/src/wsServer.ts index d8859c2fa5b71a0f7c0e50b5070b19a3ee68f636..2f7ed0bc452fbfccb75ffc07169331d9e0797e9c 100644 --- a/apps/server/src/wsServer.ts +++ b/apps/server/src/wsServer.ts @@ -268,7 +268,8 @@ export const createServer = Effect.fn(function* (): Effect.fn.Return< ), ); - const providerStatuses = yield* providerHealth.getStatuses; + // Read provider statuses lazily so background health checks are reflected. + const getProviderStatuses = () => Effect.runSync(providerHealth.getStatuses); const clients = yield* Ref.make(new Set()); const logger = createLogger("ws"); @@ -631,11 +632,23 @@ export const createServer = Effect.fn(function* (): Effect.fn.Return< channel: WS_CHANNELS.serverConfigUpdated, data: { issues: event.issues, - providers: providerStatuses, + providers: getProviderStatuses(), }, }), ).pipe(Effect.forkIn(subscriptionsScope)); + // Push updated provider statuses to connected clients once background health checks finish. + providerHealth.onReady((statuses) => { + broadcastPush({ + type: "push", + channel: WS_CHANNELS.serverConfigUpdated, + data: { + issues: [], + providers: statuses, + }, + }).pipe(Effect.runPromise).catch(() => {}); + }); + yield* Scope.provide(orchestrationReactor.start, subscriptionsScope); let welcomeBootstrapProjectId: ProjectId | undefined; @@ -883,7 +896,7 @@ export const createServer = Effect.fn(function* (): Effect.fn.Return< keybindingsConfigPath, keybindings: keybindingsConfig.keybindings, issues: keybindingsConfig.issues, - providers: providerStatuses, + providers: getProviderStatuses(), availableEditors, };