From 899d32b950569fe333dbe77ac876b03a1f737d01 Mon Sep 17 00:00:00 2001 From: marius-kilocode Date: Thu, 30 Jul 2026 14:11:41 +0200 Subject: [PATCH 1/2] fix(agent-manager): align Cmd+/ fallback with platform binding and one-shot echo --- ...gent-manager-terminal-shortcut-platform.md | 5 ++ .../unit/agent-manager-terminal-side.test.ts | 64 +++++++++++++------ .../webview-ui/agent-manager/terminal/side.ts | 31 +++++++-- 3 files changed, 76 insertions(+), 24 deletions(-) create mode 100644 .changeset/agent-manager-terminal-shortcut-platform.md diff --git a/.changeset/agent-manager-terminal-shortcut-platform.md b/.changeset/agent-manager-terminal-shortcut-platform.md new file mode 100644 index 00000000000..dbd2a126cb2 --- /dev/null +++ b/.changeset/agent-manager-terminal-shortcut-platform.md @@ -0,0 +1,5 @@ +--- +"kilo-code": patch +--- + +Match the Agent Manager terminal shortcut fallback to the platform modifier (Cmd on macOS, Ctrl elsewhere) and consume the extension echo once per keypress so unrelated invocations are no longer swallowed. diff --git a/packages/kilo-vscode/tests/unit/agent-manager-terminal-side.test.ts b/packages/kilo-vscode/tests/unit/agent-manager-terminal-side.test.ts index 1d60d6ecd2b..ec8e2ca6937 100644 --- a/packages/kilo-vscode/tests/unit/agent-manager-terminal-side.test.ts +++ b/packages/kilo-vscode/tests/unit/agent-manager-terminal-side.test.ts @@ -12,6 +12,7 @@ function scene( saved?: "vscode" | "agentManager" visible?: boolean focusedId?: string + mac?: boolean } = {}, ) { const calls = { @@ -50,6 +51,7 @@ function scene( openVscode: () => calls.openVscode++, saved: opts.saved, save: (destination) => calls.persisted.push(destination), + mac: opts.mac, }) if (opts.destination) ctl.syncDefault(opts.destination) return { ctl, calls } @@ -99,33 +101,57 @@ describe("Agent Manager side terminal controller", () => { expect(panelFirst.calls.openVscode).toBe(0) }) - it("handles Cmd/Ctrl+/ presses locally and dedupes the extension echo", () => { - const press = (key: string, opts: Partial = {}) => - ({ key, metaKey: true, ctrlKey: false, shiftKey: false, altKey: false, ...opts }) as KeyboardEvent + it("handles the platform terminal shortcut locally and dedupes the extension echo", () => { + const press = (opts: Partial = {}) => + ({ key: "/", metaKey: false, ctrlKey: false, shiftKey: false, altKey: false, ...opts }) as KeyboardEvent - const item = scene({ destination: "agentManager" }) - expect(item.ctl.press(press("/"))).toBe(true) - expect(item.calls.requestSide).toBe(1) - // The extension echoes the same keypress back as an action message; - // it must be ignored so the panel does not toggle twice. - expect(item.ctl.echo()).toBe(true) + // macOS: the workbench binding is Cmd+/, so only Cmd is accepted. + const mac = scene({ destination: "agentManager", mac: true }) + expect(mac.ctl.press(press({ metaKey: true }))).toBe(true) + expect(mac.calls.requestSide).toBe(1) + expect(mac.ctl.press(press({ ctrlKey: true }))).toBe(false) + expect(mac.ctl.press(press({ metaKey: true, ctrlKey: true }))).toBe(false) + expect(mac.calls.requestSide).toBe(1) + + // Windows/Linux: the workbench binding is Ctrl+/, so only Ctrl is accepted. + const win = scene({ destination: "agentManager", mac: false }) + expect(win.ctl.press(press({ ctrlKey: true }))).toBe(true) + expect(win.calls.requestSide).toBe(1) + expect(win.ctl.press(press({ metaKey: true }))).toBe(false) + expect(win.calls.requestSide).toBe(1) // Unrelated keys and modifier combinations are not the shortcut. - const other = scene({ destination: "agentManager" }) - expect(other.ctl.press(press("?"))).toBe(false) - expect(other.ctl.press(press("/", { shiftKey: true }))).toBe(false) - expect(other.ctl.press(press("/", { altKey: true }))).toBe(false) - expect(other.ctl.press(press("/", { metaKey: false }))).toBe(false) - expect(other.ctl.press(press("/", { metaKey: false, ctrlKey: true }))).toBe(true) - expect(other.calls.requestSide).toBe(1) + expect(win.ctl.press(press({ key: "?" }))).toBe(false) + expect(win.ctl.press(press({ ctrlKey: true, shiftKey: true }))).toBe(false) + expect(win.ctl.press(press({ ctrlKey: true, altKey: true }))).toBe(false) + expect(win.calls.requestSide).toBe(1) + + // The extension echoes each locally handled keypress back as an action + // message; one echo is consumed per press, then invocations run again. + expect(mac.ctl.echo()).toBe(true) + expect(mac.ctl.echo()).toBe(false) }) - it("stops deduping after the echo window passes", async () => { - const item = scene({ destination: "agentManager" }) - item.ctl.press({ key: "/", metaKey: true } as KeyboardEvent) + it("consumes one echo per press, even for rapid repeated presses", () => { + const item = scene({ destination: "agentManager", mac: true }) + const press = () => item.ctl.press({ key: "/", metaKey: true } as KeyboardEvent) + press() + press() + // Two presses toggled the panel open and closed again; both echoes + // must still be consumed so neither press toggles a third time. + expect(item.calls.requestSide).toBe(1) + expect(item.calls.hide).toBe(1) expect(item.ctl.echo()).toBe(true) + expect(item.ctl.echo()).toBe(true) + expect(item.ctl.echo()).toBe(false) + }) + + it("drops a never-arriving echo after the timeout safety valve", async () => { + const item = scene({ destination: "agentManager", mac: true }) + item.ctl.press({ key: "/", metaKey: true } as KeyboardEvent) await new Promise((resolve) => setTimeout(resolve, 550)) expect(item.ctl.echo()).toBe(false) + expect(item.ctl.echo()).toBe(false) }) it("persists the picked destination with a section-relative settings key", () => { diff --git a/packages/kilo-vscode/webview-ui/agent-manager/terminal/side.ts b/packages/kilo-vscode/webview-ui/agent-manager/terminal/side.ts index d4d9c80cff9..f26c1a7f5c0 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/terminal/side.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/terminal/side.ts @@ -83,6 +83,9 @@ export interface SideTerminalDeps { saved: TerminalDestination | undefined /** Persist the panel-local choice so it survives webview reloads. */ save: (destination: TerminalDestination) => void + /** Platform override for tests; the workbench keybinding is Cmd on + * macOS and Ctrl elsewhere, and the local fallback must match it. */ + mac?: boolean } export function createSideTerminal(deps: SideTerminalDeps) { @@ -162,23 +165,41 @@ export function createSideTerminal(deps: SideTerminalDeps) { /** * Cmd/Ctrl+/ pressed while the webview holds DOM focus. VS Code normally * forwards the keybinding to the workbench too, and the extension echoes - * it back as a showTerminal action message; `echo()` lets the action - * handler skip that duplicate so one keypress never toggles twice. + * it back as a showTerminal action message; `echo()` consumes one pending + * echo per press so one keypress never toggles twice while an unrelated + * invocation (command palette, terminal-focused press) still runs. * Handling the key locally keeps the shortcut working when the * forwarding path drops it (e.g. the chat prompt input is focused). + * The modifier matches the declared keybinding: Cmd on macOS, Ctrl + * elsewhere — accepting both would hijack the other platform's combo + * (and any user keybinding on it) without a matching workbench binding. */ + const mac = deps.mac ?? (typeof navigator !== "undefined" && /Mac|iPhone|iPad/.test(navigator.userAgent)) + let pending = 0 let lastPress = 0 const ECHO_MS = 500 const press = (e: KeyboardEvent): boolean => { - if (e.key !== "/" || !(e.metaKey || e.ctrlKey) || e.shiftKey || e.altKey) return false + const mod = mac ? e.metaKey && !e.ctrlKey : e.ctrlKey && !e.metaKey + if (e.key !== "/" || e.shiftKey || e.altKey || !mod) return false + pending++ lastPress = Date.now() openPreferred("keyboard_shortcut") return true } - /** True while an incoming showTerminal action is the echo of `press`. */ - const echo = () => Date.now() - lastPress < ECHO_MS + /** Consumes one pending extension echo of a local `press`. The timeout is + * only a safety valve for an echo that never arrives; an echo that + * outlasts it is indistinguishable from a real invocation and must run. */ + const echo = () => { + if (pending === 0) return false + if (Date.now() - lastPress > ECHO_MS) { + pending = 0 + return false + } + pending-- + return true + } return { destination, syncDefault, toggle, close, openPreferred, choose, press, echo } } From 3f70657d4c178d9cb3a13aa734d92792eee8f9c4 Mon Sep 17 00:00:00 2001 From: marius-kilocode Date: Thu, 30 Jul 2026 14:32:11 +0200 Subject: [PATCH 2/2] fix(agent-manager): expire dropped terminal-shortcut echoes at press time --- .../tests/unit/agent-manager-terminal-side.test.ts | 11 +++++++++++ .../webview-ui/agent-manager/terminal/side.ts | 3 +++ 2 files changed, 14 insertions(+) diff --git a/packages/kilo-vscode/tests/unit/agent-manager-terminal-side.test.ts b/packages/kilo-vscode/tests/unit/agent-manager-terminal-side.test.ts index ec8e2ca6937..65e06fe8f51 100644 --- a/packages/kilo-vscode/tests/unit/agent-manager-terminal-side.test.ts +++ b/packages/kilo-vscode/tests/unit/agent-manager-terminal-side.test.ts @@ -154,6 +154,17 @@ describe("Agent Manager side terminal controller", () => { expect(item.ctl.echo()).toBe(false) }) + it("expires a dropped echo's backlog at the next spaced press", async () => { + const item = scene({ destination: "agentManager", mac: true }) + // First press's echo never arrives (dropped forwarding); its backlog + // must not outlive the echo window into the next press. + item.ctl.press({ key: "/", metaKey: true } as KeyboardEvent) + await new Promise((resolve) => setTimeout(resolve, 550)) + item.ctl.press({ key: "/", metaKey: true } as KeyboardEvent) + expect(item.ctl.echo()).toBe(true) + expect(item.ctl.echo()).toBe(false) + }) + it("persists the picked destination with a section-relative settings key", () => { const item = scene() item.ctl.choose("agentManager") diff --git a/packages/kilo-vscode/webview-ui/agent-manager/terminal/side.ts b/packages/kilo-vscode/webview-ui/agent-manager/terminal/side.ts index f26c1a7f5c0..1f8a59a1053 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/terminal/side.ts +++ b/packages/kilo-vscode/webview-ui/agent-manager/terminal/side.ts @@ -182,6 +182,9 @@ export function createSideTerminal(deps: SideTerminalDeps) { const press = (e: KeyboardEvent): boolean => { const mod = mac ? e.metaKey && !e.ctrlKey : e.ctrlKey && !e.metaKey if (e.key !== "/" || e.shiftKey || e.altKey || !mod) return false + // An echo either arrives promptly or never; drop the backlog of a + // dropped echo so it cannot swallow a later unrelated invocation. + if (Date.now() - lastPress > ECHO_MS) pending = 0 pending++ lastPress = Date.now() openPreferred("keyboard_shortcut")