From cfeaa34c16230cd198fc009a01e43cb93f72f437 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Fri, 9 Jan 2026 21:28:14 +0100 Subject: [PATCH] refactor: centralize cli timeout parsing --- CHANGELOG.md | 2 +- docs/tui.md | 2 +- src/cli/nodes-run.ts | 17 +++-------------- src/cli/parse-timeout.ts | 14 ++++++++++++++ src/cli/program.test.ts | 13 +++++++++++++ src/cli/tui-cli.ts | 17 ++++++++--------- 6 files changed, 40 insertions(+), 25 deletions(-) create mode 100644 src/cli/parse-timeout.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index c61408bac..6f8a9f010 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -114,7 +114,7 @@ - Docs: expand parameter descriptions for agent/wake hooks. (#532) — thanks @mcinteerj - Docs: add community showcase entries from Discord. (#476) — thanks @gupsammy - TUI: refresh status bar after think/verbose/reasoning changes. (#519) — thanks @jdrhyne -- TUI: stop overriding agent timeout so config defaults apply. (#549) +- TUI: stop overriding agent timeout so config defaults apply; warn on invalid `--timeout-ms`. (#549) - Status: show Verbose/Elevated only when enabled. - Status: filter usage summary to the active model provider. - Status: map model providers to usage sources so unrelated usage doesn’t appear. diff --git a/docs/tui.md b/docs/tui.md index 6bb209406..6c059d0cb 100644 --- a/docs/tui.md +++ b/docs/tui.md @@ -109,7 +109,7 @@ Session lifecycle: - `--session `: Session key (default: `main`, or `global` when scope is global) - `--deliver`: Deliver assistant replies to the provider (default off) - `--thinking `: Override thinking level for sends -- `--timeout-ms `: Agent timeout (defaults to `agents.defaults.timeoutSeconds`) +- `--timeout-ms `: Agent timeout in ms (defaults to `agents.defaults.timeoutSeconds`) - `--history-limit `: History entries to load (default 200) ## Troubleshooting diff --git a/src/cli/nodes-run.ts b/src/cli/nodes-run.ts index 7d375e97f..d294d4360 100644 --- a/src/cli/nodes-run.ts +++ b/src/cli/nodes-run.ts @@ -1,3 +1,5 @@ +import { parseTimeoutMs } from "./parse-timeout.js"; + export function parseEnvPairs( pairs: unknown, ): Record | undefined { @@ -14,17 +16,4 @@ export function parseEnvPairs( return Object.keys(env).length > 0 ? env : undefined; } -export function parseTimeoutMs(raw: unknown): number | undefined { - if (raw === undefined || raw === null) return undefined; - let value = Number.NaN; - if (typeof raw === "number") { - value = raw; - } else if (typeof raw === "bigint") { - value = Number(raw); - } else if (typeof raw === "string") { - const trimmed = raw.trim(); - if (!trimmed) return undefined; - value = Number.parseInt(trimmed, 10); - } - return Number.isFinite(value) ? value : undefined; -} +export { parseTimeoutMs }; diff --git a/src/cli/parse-timeout.ts b/src/cli/parse-timeout.ts new file mode 100644 index 000000000..19b42e0b5 --- /dev/null +++ b/src/cli/parse-timeout.ts @@ -0,0 +1,14 @@ +export function parseTimeoutMs(raw: unknown): number | undefined { + if (raw === undefined || raw === null) return undefined; + let value = Number.NaN; + if (typeof raw === "number") { + value = raw; + } else if (typeof raw === "bigint") { + value = Number(raw); + } else if (typeof raw === "string") { + const trimmed = raw.trim(); + if (!trimmed) return undefined; + value = Number.parseInt(trimmed, 10); + } + return Number.isFinite(value) ? value : undefined; +} diff --git a/src/cli/program.test.ts b/src/cli/program.test.ts index b916e77d1..ad9fa2df1 100644 --- a/src/cli/program.test.ts +++ b/src/cli/program.test.ts @@ -98,6 +98,19 @@ describe("cli program", () => { ); }); + it("warns and ignores invalid tui timeout override", async () => { + const program = buildProgram(); + await program.parseAsync(["tui", "--timeout-ms", "nope"], { + from: "user", + }); + expect(runtime.error).toHaveBeenCalledWith( + 'warning: invalid --timeout-ms "nope"; ignoring', + ); + expect(runTui).toHaveBeenCalledWith( + expect.objectContaining({ timeoutMs: undefined }), + ); + }); + it("runs config alias as configure", async () => { const program = buildProgram(); await program.parseAsync(["config"], { from: "user" }); diff --git a/src/cli/tui-cli.ts b/src/cli/tui-cli.ts index 283d02adf..2d82d7a14 100644 --- a/src/cli/tui-cli.ts +++ b/src/cli/tui-cli.ts @@ -1,6 +1,7 @@ import type { Command } from "commander"; import { defaultRuntime } from "../runtime.js"; import { runTui } from "../tui/tui.js"; +import { parseTimeoutMs } from "./parse-timeout.js"; export function registerTuiCli(program: Command) { program @@ -26,14 +27,12 @@ export function registerTuiCli(program: Command) { .option("--history-limit ", "History entries to load", "200") .action(async (opts) => { try { - const timeoutMs = - typeof opts.timeoutMs === "undefined" - ? undefined - : Number.parseInt(String(opts.timeoutMs), 10); - const normalizedTimeoutMs = - typeof timeoutMs === "number" && Number.isFinite(timeoutMs) - ? timeoutMs - : undefined; + const timeoutMs = parseTimeoutMs(opts.timeoutMs); + if (opts.timeoutMs !== undefined && timeoutMs === undefined) { + defaultRuntime.error( + `warning: invalid --timeout-ms "${String(opts.timeoutMs)}"; ignoring`, + ); + } const historyLimit = Number.parseInt( String(opts.historyLimit ?? "200"), 10, @@ -46,7 +45,7 @@ export function registerTuiCli(program: Command) { deliver: Boolean(opts.deliver), thinking: opts.thinking as string | undefined, message: opts.message as string | undefined, - timeoutMs: normalizedTimeoutMs, + timeoutMs, historyLimit: Number.isNaN(historyLimit) ? undefined : historyLimit, }); } catch (err) {