Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,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 command = useCommandDialog()
const renderer = useRenderer()
Expand Down Expand Up @@ -210,7 +215,7 @@ export function Prompt(props: PromptProps) {

setTimeout(() => {
setStore("interrupt", 0)
}, 5000)
}, sessionInterruptTimeout())

if (store.interrupt >= 2) {
sdk.client.session.abort({
Expand Down
9 changes: 9 additions & 0 deletions packages/opencode/src/config/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,9 +140,11 @@ export namespace Config {
}

if (!result.keybinds) result.keybinds = Info.shape.keybinds.parse({})
result.tui = Info.shape.tui.parse(result.tui ?? {})

return {
config: result,

directories,
}
})
Expand Down Expand Up @@ -580,6 +582,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 Layout = z.enum(["auto", "stretch"]).meta({
Expand Down
56 changes: 56 additions & 0 deletions packages/opencode/test/config/config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,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) => {
Expand Down
1 change: 1 addition & 0 deletions packages/web/src/content/docs/config.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,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`.
- `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).

Expand Down