Files
OmniRoute/src/shared/services/cliInstallFallback.ts
Diego Rodrigues de Sa e Souza 4d92dfe0a2 fix: distinguish CLI-probe timeouts from not_found, resolve Hermes Agent keyId server-side (#10710+10711) (#10746)
#10710: locateCommand() in cliRuntime.ts collapsed a genuine probe timeout
(runProcess's timedOut flag) into the same reason:"not_found" as a truly
absent binary, on both the where.exe and `command -v` branches. Give
timeouts a distinct "timeout" reason, keep trying remaining command
candidates in locateCommandCandidate instead of treating a timeout as
terminal, and extend the settings-file fallback (cliInstallFallback.ts) to
also cover the new "timeout" reason, matching the scenario it already
existed for.

#10711: the Hermes Agent dashboard "Apply" flow only ever sends `keyId`
(never a raw `apiKey`), but the hermes-agent-settings POST handler never
resolved it, so generateHermesAgentConfig() always fell through to the
literal placeholder "YOUR_OMNIROUTE_API_KEY_HERE" for
providers.omniroute.api_key, delegation.api_key, and every
auxiliary.*.api_key. Resolve keyId server-side via getApiKeyById(), the
same precedented pattern already used by claude-settings/route.ts and
codex-settings/route.ts.

Bug 2 from #10710 (hermes tool-detector configPath) was already fixed by
commit 0a74bfbdea -- confirmed still intact,
no action needed.

Co-authored-by: Markus Hartung <mail@hartmark.se>
2026-08-19 11:08:34 -03:00

74 lines
2.9 KiB
TypeScript

import fsSync from "fs";
/**
* #6701 — 9router-parity fallback for CLI install detection.
*
* `getCliRuntimeStatus()` in `cliRuntime.ts` determines `installed` from
* binary resolution alone (known install paths + a `where`/`which` PATH
* search). If the binary is not currently resolvable — stale PATH inherited
* by a long-running/background OmniRoute process, the binary having moved,
* or an install method we don't enumerate yet — it used to unconditionally
* report `installed:false`, even when the tool's own settings/config file on
* disk proves it was installed and used before.
*
* Upstream 9router's equivalent route
* (`src/app/api/cli-tools/claude-settings/route.js::checkClaudeInstalled()`)
* has a second-chance fallback: when `where`/`which` fails, it still reports
* `installed:true` if the settings file exists. This restores that fallback
* for any CLI tool that declares a `settings` config path (currently
* `claude` and `droid` — see `CLI_TOOLS` in `cliRuntime.ts`).
*
* Only applies when the lookup's own reason is "not_found" or "timeout" —
* i.e. the binary genuinely couldn't be located on PATH/known install paths,
* or the probe never got a chance to answer (#10710: a probe timeout is one
* more variant of "not currently resolvable", the exact scenario this
* fallback exists for). Deliberate security rejections (unsafe/relative env
* override paths, symlink escapes, suspicious file sizes, etc.) must stay
* `installed:false` regardless of whether a settings file happens to exist.
*/
export interface NotInstalledResult {
installed: false;
runnable: boolean;
command: string | null;
commandPath: string | null;
reason: string;
runtimeMode: string;
requiresBinary: boolean;
}
export interface SettingsFallbackResult {
installed: true;
runnable: false;
command: string | null;
commandPath: null;
reason: "settings_found_binary_unresolved";
runtimeMode: string;
requiresBinary: boolean;
}
/**
* Given the resolved settings-file path for a tool (or undefined if the tool
* has none) and the "not installed" result the binary lookup already
* produced, return a settings-fallback result when the settings file exists
* on disk, or the original "not installed" result unchanged otherwise.
*/
export const withSettingsFallback = (
settingsPath: string | undefined,
notInstalledResult: NotInstalledResult
): NotInstalledResult | SettingsFallbackResult => {
if (notInstalledResult.reason !== "not_found" && notInstalledResult.reason !== "timeout") {
return notInstalledResult;
}
if (!settingsPath || !fsSync.existsSync(settingsPath)) return notInstalledResult;
return {
installed: true,
runnable: false,
command: notInstalledResult.command,
commandPath: null,
reason: "settings_found_binary_unresolved",
runtimeMode: notInstalledResult.runtimeMode,
requiresBinary: notInstalledResult.requiresBinary,
};
};