diff --git a/CHANGELOG.md b/CHANGELOG.md index 9f4574912e..832ed3b521 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## [2.3.11] - 2026-03-12 + +### Fixed + +- **KiloCode healthcheck**: Increase `healthcheckTimeoutMs` from 4000ms to 15000ms — kilocode renders an ASCII logo banner on startup causing false `healthcheck_failed` on slow/cold-start environments + ## [2.3.10] - 2026-03-12 ### Fixed diff --git a/package.json b/package.json index 22cfef982b..fed2652fd0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "omniroute", - "version": "2.3.10", + "version": "2.3.11", "description": "Smart AI Router with auto fallback — route to FREE & cheap models, zero downtime. Works with Cursor, Cline, Claude Desktop, Codex, and any OpenAI-compatible tool.", "type": "module", "bin": { diff --git a/src/shared/services/cliRuntime.ts b/src/shared/services/cliRuntime.ts index 57db407485..cc35745619 100644 --- a/src/shared/services/cliRuntime.ts +++ b/src/shared/services/cliRuntime.ts @@ -73,7 +73,10 @@ const CLI_TOOLS: Record = { defaultCommand: "kilocode", envBinKey: "CLI_KILO_BIN", requiresBinary: true, - healthcheckTimeoutMs: 4000, + // kilocode renders an ASCII logo banner on startup which can take >4s + // on cold-start or low-resource environments (VPS, CI). Increase timeout + // to avoid false healthcheck_failed results. + healthcheckTimeoutMs: 15000, paths: { auth: ".local/share/kilo/auth.json", }, @@ -95,7 +98,11 @@ const parseBoolean = (value: unknown, defaultValue = true) => { return !FALSE_VALUES.has(String(value).trim().toLowerCase()); }; -const runProcess = (command: string, args: string[], { env, timeoutMs = 3000 }: { env?: Record; timeoutMs?: number } = {}): Promise => +const runProcess = ( + command: string, + args: string[], + { env, timeoutMs = 3000 }: { env?: Record; timeoutMs?: number } = {} +): Promise => new Promise((resolve) => { let stdout = ""; let stderr = ""; @@ -231,7 +238,10 @@ const locateCommand = async (command: string, env: Record) => { +const locateCommandCandidate = async ( + commands: string[], + env: Record +) => { if (!Array.isArray(commands) || commands.length === 0) { return { command: null, installed: false, commandPath: null, reason: "missing_command" }; } @@ -246,7 +256,11 @@ const locateCommandCandidate = async (commands: string[], env: Record, timeoutMs = 4000) => { +const checkRunnable = async ( + commandPath: string, + env: Record, + timeoutMs = 4000 +) => { for (const args of [["--version"], ["-v"]]) { const result = await runProcess(commandPath, args, { env, timeoutMs }); if (result.ok) { @@ -272,7 +286,10 @@ export const getCliConfigPaths = (toolId: string) => { if (!tool) return null; const home = getCliConfigHome(); return Object.fromEntries( - Object.entries(tool.paths).map(([key, relativePath]) => [key, path.join(home, relativePath as string)]) + Object.entries(tool.paths).map(([key, relativePath]) => [ + key, + path.join(home, relativePath as string), + ]) ); };