Skip to content

[Feature]: Expose more actions as bindable keybinding commands #4641

Description

@achtan

Before submitting

  • I searched existing issues and did not find a duplicate.
  • I am describing a concrete problem or use case, not just a vague idea.

Area

packages/contracts or packages/shared

Problem or use case

The keybindings system itself is in good shape — file-backed config, hot reload, when expressions, a settings UI. The limitation is that KeybindingCommand is a closed set (packages/contracts/src/keybindings.ts:50-71), and several everyday actions aren't in it. Each one is already implemented and dispatchable; it just has no command ID, so it stays mouse-only with no way to rebind it.

Six that I hit daily:

1. Interrupt a running turn — no keyboard path at all

interruptThreadTurn (packages/client-runtime/src/operations/commands.ts:245) is reachable only through the stop button's onInterrupt (ChatComposer.tsx:2737).

I grepped every Escape handler in apps/web/src — 13 of them, and all are dialogs, pickers, inline rename, or selection-clear. None interrupt a run. So stopping an agent requires the mouse, and there's no workaround.

This is the one that hurts most, and it's arguably a gap rather than a feature request. Escape-to-interrupt is close to universal in agent UIs, and reaching for the mouse to stop a run that's heading the wrong way is exactly the moment you don't want to.

2. Rename thread

{ id: "rename", label: "Rename thread" } (SidebarV2.tsx:2018). The inline editor already handles Enter and Escape (SidebarV2.tsx:579-594) — only the entry point is missing, and it's the right-click menu.

3. Settle / un-settle thread

SidebarV2.tsx:1996-2001, gated on the threadSettlement capability (state/entities.ts:200). Context menu only.

4. Focus terminal

There's no terminal.focus. Focus is coupled to visibility: opening the drawer bumps terminalFocusRequestId (ChatView.tsx:4218). If the drawer is already open and focus sits in the composer, there's no keyboard route into the terminal.

5. Focus composer

The mirror image. Composer focus fires on drawer close (ChatView.tsx:4222-4227) and on thread switch. Type-to-focus (ChatView.tsx:428) bails whenever focus is inside an input or textarea (ChatView.tsx:383-389) — xterm renders a textarea, so typing in the terminal never brings you back.

6. Environment / checkout picker

The "Current checkout" control (resolveEnvModeLabel, BranchToolbar.logic.ts:58) is a mouse-only menu (BranchToolbar.tsx:184-203). Neither BranchToolbar.tsx nor BranchToolbarBranchSelector.tsx has any keyboard handling. Choosing between current checkout and a new worktree is a decision made on nearly every new thread, and today it needs a pointer.

Proposed solution

Extend STATIC_KEYBINDING_COMMANDS and wire the dispatch, following existing naming:

Command Action
chat.interrupt interrupt the running turn
thread.rename enter inline rename on the active thread
thread.settle toggle settled, as the context menu does
terminal.focus move focus into the terminal, opening it if closed
chat.focusComposer move focus to the composer, leaving the terminal open
env.togglePicker open the environment / checkout picker

For chat.interrupt a default of escape with something like when: "!terminalFocus" seems right, since mod+K is already hardcoded as terminal-clear and commandPalette.toggle works around it the same way. Happy to ship it with no default binding if Escape feels too overloaded.

Why this matters

The keybinding system is already good enough that the closed command set is what's holding it back — the gap isn't infrastructure, it's coverage. Every action above is one an agent-driven workflow touches many times an hour, and each one currently breaks flow by forcing a reach for the mouse.

chat.interrupt in particular is a correctness-of-experience issue rather than a nicety: there is currently no way to stop a running agent from the keyboard.

Smallest useful scope

Just chat.interrupt. It's the only one with no workaround at all, and it's a small change — the command already exists, it needs a command ID and a dispatch branch.

Items 2, 3 and 6 are similarly mechanical: existing menu handlers, new command IDs.

Items 4 and 5 are the only ones with real design in them, since focus is currently coupled to panel visibility and would need decoupling. Fine to split those into a follow-up.

Alternatives considered

For focus terminal / focus composer there's a partial workaround: mod+J twice. terminal.toggle calls toggleTerminalVisibility() (ChatView.tsx:4267), which is visibility-only rather than lifecycle, so the terminal session survives the round trip. It works, but toggling a panel to move focus isn't a great story.

The other four have no workaround.

I also looked at whether any of this is reachable through the command palette instead — the thread actions are not; they live only in the sidebar context menu.

Risks or tradeoffs

  • Escape is heavily overloaded. Binding chat.interrupt to bare escape by default needs a when clause at minimum, and it may be safer to ship the command with no default and let users opt in.
  • thread.rename / thread.settle need a target. The context-menu versions act on the right-clicked thread; a keybinding would need to act on the active thread, which is a small semantic difference worth being deliberate about.
  • Decoupling focus from visibility (items 4 and 5) touches existing effects in ChatView.tsx that several other behaviors depend on. This is the main reason to treat it as a separate change.
  • thread.settle is capability-gated, so the command needs to no-op cleanly against pre-settlement servers.

Examples or references

Code references are against 23ea08d on main; the behavior was observed on the nightly build 0.0.29-nightly.20260727.915.

Adjacent open issues, none of which overlap with the actions above:

Contribution

  • I would be open to helping implement this.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions