diff --git a/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx b/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx index 4558914cb7e..ffd5f5acd1d 100644 --- a/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx @@ -66,6 +66,11 @@ export function Prompt(props: PromptProps) { const dialog = useDialog() const toast = useToast() const status = createMemo(() => sync.data.session_status?.[props.sessionID ?? ""] ?? { type: "idle" }) + const sessionInterruptTimeout = createMemo(() => { + const timeout = (sync.data.config as { tui?: { session_interrupt_timeout_ms?: number } }).tui + ?.session_interrupt_timeout_ms + return typeof timeout === "number" ? timeout : 5000 + }) const history = usePromptHistory() const stash = usePromptStash() const command = useCommandDialog() @@ -212,7 +217,7 @@ export function Prompt(props: PromptProps) { setTimeout(() => { setStore("interrupt", 0) - }, 5000) + }, sessionInterruptTimeout()) if (store.interrupt >= 2) { sdk.client.session.abort({ diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts index be234948424..b86ce18873f 100644 --- a/packages/opencode/src/config/config.ts +++ b/packages/opencode/src/config/config.ts @@ -169,6 +169,7 @@ export namespace Config { } if (!result.keybinds) result.keybinds = Info.shape.keybinds.parse({}) + result.tui = Info.shape.tui.parse(result.tui ?? {}) // Apply flag overrides for compaction settings if (Flag.OPENCODE_DISABLE_AUTOCOMPACT) { @@ -180,6 +181,7 @@ export namespace Config { return { config: result, + directories, } }) @@ -716,6 +718,13 @@ export namespace Config { .enum(["auto", "stacked"]) .optional() .describe("Control diff rendering style: 'auto' adapts to terminal width, 'stacked' always shows single column"), + session_interrupt_timeout_ms: z + .number() + .int() + .min(500) + .max(10000) + .default(5000) + .describe("Time window in milliseconds to detect double-Esc interrupts"), }) export const Server = z diff --git a/packages/opencode/test/config/config.test.ts b/packages/opencode/test/config/config.test.ts index b52f3ef7f77..5f38833e7b6 100644 --- a/packages/opencode/test/config/config.test.ts +++ b/packages/opencode/test/config/config.test.ts @@ -18,6 +18,62 @@ test("loads config with defaults when no files exist", async () => { }) }) +test("defaults session interrupt timeout when not configured", async () => { + await using tmp = await tmpdir() + await Instance.provide({ + directory: tmp.path, + fn: async () => { + const config = await Config.get() + expect(config.tui?.session_interrupt_timeout_ms).toBe(5000) + }, + }) +}) + +test("accepts in-range session interrupt timeout", async () => { + await using tmp = await tmpdir({ + init: async (dir) => { + await Bun.write( + path.join(dir, "opencode.json"), + JSON.stringify({ + $schema: "https://opencode.ai/config.json", + tui: { + session_interrupt_timeout_ms: 1200, + }, + }), + ) + }, + }) + await Instance.provide({ + directory: tmp.path, + fn: async () => { + const config = await Config.get() + expect(config.tui?.session_interrupt_timeout_ms).toBe(1200) + }, + }) +}) + +test("rejects out-of-range session interrupt timeout", async () => { + await using tmp = await tmpdir({ + init: async (dir) => { + await Bun.write( + path.join(dir, "opencode.json"), + JSON.stringify({ + $schema: "https://opencode.ai/config.json", + tui: { + session_interrupt_timeout_ms: 200, + }, + }), + ) + }, + }) + await Instance.provide({ + directory: tmp.path, + fn: async () => { + await expect(Config.get()).rejects.toThrow() + }, + }) +}) + test("loads JSON config file", async () => { await using tmp = await tmpdir({ init: async (dir) => { diff --git a/packages/web/src/content/docs/config.mdx b/packages/web/src/content/docs/config.mdx index a5931b6fcf5..f9a0594ebc8 100644 --- a/packages/web/src/content/docs/config.mdx +++ b/packages/web/src/content/docs/config.mdx @@ -170,6 +170,7 @@ Available options: - `scroll_acceleration.enabled` - Enable macOS-style scroll acceleration. **Takes precedence over `scroll_speed`.** - `scroll_speed` - Custom scroll speed multiplier (default: `1`, minimum: `1`). Ignored if `scroll_acceleration.enabled` is `true`. - `diff_style` - Control diff rendering. `"auto"` adapts to terminal width, `"stacked"` always shows single column. +- `session_interrupt_timeout_ms` - Time window for double-Esc session interrupts (default: `5000`, min: `500`, max: `10000`). Applies to TUI session interruption only. [Learn more about using the TUI here](/docs/tui).