From 674b6f5f67ce4e6f160ba5cff292b16fd6167ef9 Mon Sep 17 00:00:00 2001 From: minhn4 Date: Wed, 29 Jul 2026 17:50:56 +0700 Subject: [PATCH 1/2] feat(tui): always show permission mode and cycle it with shift+tab The prompt footer badge next to the agent name now always renders in prompt mode: gray "manual" in normal mode, red "auto" (theme.error) when permissions are auto-approved (--auto). New permission_mode_cycle keybind (default shift+tab) toggles the mode via the existing permission.mode command; agent_cycle_reverse is now unbound by default (tab still cycles agents forward). Co-Authored-By: Claude Fable 5 --- CHANGELOG.md | 10 ++++++++++ DIVERGENCES.md | 1 + packages/tui/src/app.tsx | 1 + packages/tui/src/component/prompt/index.tsx | 11 +++++++++-- packages/tui/src/config/keybind.ts | 4 +++- 5 files changed, 24 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b2df7dd66..20edbfa76f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,16 @@ file is the human-readable summary of what changed and why. ## Unreleased +### TUI + +- **Permission mode is always shown, and `shift+tab` toggles it** — the prompt + footer now labels the mode next to the agent name: gray `manual` in normal + mode, red `auto` when permissions are auto-approved (`--auto`, previously a + gray `auto` shown only in that mode). `shift+tab` cycles between the two, + configurable as the new `permission_mode_cycle` keybind. Its former default, + previous-agent (`agent_cycle_reverse`), is now unbound by default — `tab` + still cycles agents forward. + ## 1.18.4-1 ### CodeGraph diff --git a/DIVERGENCES.md b/DIVERGENCES.md index bfb7bcaddf..43b72f05f0 100644 --- a/DIVERGENCES.md +++ b/DIVERGENCES.md @@ -161,6 +161,7 @@ from the squash message first, or use a plain merge. | `packages/app/src/context/language.tsx`, `packages/app/src/entry.tsx`, `packages/desktop/src/renderer/i18n/index.ts`, `packages/console/app/src/i18n/index.ts`, `packages/console/app/src/lib/language.ts`, `packages/console/app/src/component/footer.tsx`, `packages/stats/app/src/i18n.ts`, `packages/enterprise/src/app.tsx`, `packages/app/e2e/regression/session-timeline-locale-projection.spec.ts` | Locale registries trimmed to `en` + `es`: `Locale` types, LOCALES/LABEL/TAG/DOCS maps, dict loaders, accept-language matchers; enterprise/entry `zh` special cases now use `es`; console footer always links Discord (Feishu was zh-only); e2e locale-projection spec tests `es` ("Explorado") instead of de/ar | Fork ships English + Spanish only. | Keep our trimmed lists; take upstream's structural changes and re-trim any new locale entries. | | `packages/tui/src/component/prompt/autocomplete.tsx`, `packages/app/src/components/prompt-input.tsx`, `packages/opencode/src/cli/cmd/run/footer.prompt.tsx`, `packages/core/src/filesystem/search.ts`, `packages/tui/test/fixture/tui-sdk.ts`, `packages/opencode/test/cli/run/footer.view.test.tsx`, `packages/core/test/filesystem/search.test.ts` | `@` mention autocomplete lists file/directory results before references/agents/MCP resources; TUI and run-CLI menus cap the leading files so up to 3 non-file matches stay in the first visible page; web app group rank reordered to recent → file → reference → agent → resource (extracted into fork-only `prompt-input/at-options.ts`). TUI and run-CLI file resources track menu visibility so opening on an empty query fetches (previously nothing refetched until the query changed); core's ripgrep-fallback `find` returns its first entries for an empty query to mirror fff. Tests: tui-sdk fixture's `FetchHandler` gains an optional `method` arg, `renderFooter` accepts `findFiles`/`agents` overrides + a mention-menu ordering test, core search tests cover the fallback's empty query (fork-only test files: `packages/tui/test/prompt-autocomplete.test.tsx`, `packages/app/test-browser/prompt-at-options.test.ts`, `packages/app/src/components/prompt-input/at-options{,.test}.ts`) | Files are the dominant `@` use case; they should rank first and appear on a bare `@`. | Keep our ordering/reserve logic, resource source tracking, empty-query branch, fixture/test additions; take upstream's surrounding changes. | | `packages/slack/.gitignore`, `sdks/vscode/.gitignore` | Added the missing trailing newline (content unchanged) | Upstream's copies end without a newline; POSIX text files should end with one, and tools that concatenate or append to these files misbehave without it. | Whitespace-only: keep the trailing newline; take upstream's entries. | +| `packages/tui/src/config/keybind.ts`, `packages/tui/src/component/prompt/index.tsx`, `packages/tui/src/app.tsx` | Permission-mode indicator + shift+tab cycle. keybind.ts: added `permission_mode_cycle: keybind("shift+tab", ...)` + its `permission.mode` CommandMap entry; `agent_cycle_reverse` default `"shift+tab"` → `"none"` (tab still cycles agents forward; previous-agent stays rebindable). prompt/index.tsx: the footer badge next to the agent name always renders in prompt mode — gray `manual` (`theme.textMuted`) in normal mode, red `auto` (`theme.error`) in auto mode (upstream only showed gray `auto`). app.tsx: `"permission.mode"` added to `appBindingCommands` so the binding is gathered. | The permission mode is always visible (manual/auto) with auto in red as a danger signal, and shift+tab toggles it without opening the palette. | Keep the `permission_mode_cycle` definition + mapping, the `agent_cycle_reverse` `"none"` default, the badge `Show` block, and the `appBindingCommands` entry; take upstream's surrounding changes. If upstream reworks the auto badge or rebinds shift+tab, re-apply on top and re-audit the shift+tab conflict. | ## Known rebrand gaps diff --git a/packages/tui/src/app.tsx b/packages/tui/src/app.tsx index 0d919b93b3..9a32730b72 100644 --- a/packages/tui/src/app.tsx +++ b/packages/tui/src/app.tsx @@ -142,6 +142,7 @@ const appBindingCommands = [ "app.toggle.diffwrap", "app.toggle.paste_summary", "app.toggle.session_directory_filter", + "permission.mode", ] as const export type TuiInput = { diff --git a/packages/tui/src/component/prompt/index.tsx b/packages/tui/src/component/prompt/index.tsx index c97538af97..87b2ff2f9b 100644 --- a/packages/tui/src/component/prompt/index.tsx +++ b/packages/tui/src/component/prompt/index.tsx @@ -1435,8 +1435,15 @@ export function Prompt(props: PromptProps) { {store.mode === "shell" ? "Shell" : Locale.titlecase(agent().name)} - - auto + + + {local.permission.mode === "auto" ? "auto" : "manual"} + diff --git a/packages/tui/src/config/keybind.ts b/packages/tui/src/config/keybind.ts index e7e886b855..147274e6c7 100644 --- a/packages/tui/src/config/keybind.ts +++ b/packages/tui/src/config/keybind.ts @@ -130,7 +130,8 @@ export const Definitions = { console_org_switch: keybind("none", "Switch console organization"), agent_list: keybind("a", "List agents"), agent_cycle: keybind("tab", "Next agent"), - agent_cycle_reverse: keybind("shift+tab", "Previous agent"), + agent_cycle_reverse: keybind("none", "Previous agent"), + permission_mode_cycle: keybind("shift+tab", "Cycle permission mode (manual/auto)"), variant_cycle: keybind("ctrl+t", "Cycle model variants"), variant_list: keybind("none", "List model variants"), @@ -339,6 +340,7 @@ export const CommandMap = { agent_list: "agent.list", agent_cycle: "agent.cycle", agent_cycle_reverse: "agent.cycle.reverse", + permission_mode_cycle: "permission.mode", variant_cycle: "variant.cycle", variant_list: "variant.list", messages_page_up: "session.page.up", From 847cd56423d75ca649e85a698627c8cc8882f578 Mon Sep 17 00:00:00 2001 From: minhn4 Date: Wed, 29 Jul 2026 17:53:26 +0700 Subject: [PATCH 2/2] feat(tui): add shift+tab modes hint to the prompt footer Co-Authored-By: Claude Fable 5 --- CHANGELOG.md | 3 ++- DIVERGENCES.md | 2 +- packages/tui/src/component/prompt/index.tsx | 4 ++++ 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 20edbfa76f..8df584c207 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,7 +13,8 @@ file is the human-readable summary of what changed and why. footer now labels the mode next to the agent name: gray `manual` in normal mode, red `auto` when permissions are auto-approved (`--auto`, previously a gray `auto` shown only in that mode). `shift+tab` cycles between the two, - configurable as the new `permission_mode_cycle` keybind. Its former default, + configurable as the new `permission_mode_cycle` keybind, and the idle footer + advertises it with a `shift+tab modes` hint next to `tab agents`. Its former default, previous-agent (`agent_cycle_reverse`), is now unbound by default — `tab` still cycles agents forward. diff --git a/DIVERGENCES.md b/DIVERGENCES.md index 43b72f05f0..801100d274 100644 --- a/DIVERGENCES.md +++ b/DIVERGENCES.md @@ -161,7 +161,7 @@ from the squash message first, or use a plain merge. | `packages/app/src/context/language.tsx`, `packages/app/src/entry.tsx`, `packages/desktop/src/renderer/i18n/index.ts`, `packages/console/app/src/i18n/index.ts`, `packages/console/app/src/lib/language.ts`, `packages/console/app/src/component/footer.tsx`, `packages/stats/app/src/i18n.ts`, `packages/enterprise/src/app.tsx`, `packages/app/e2e/regression/session-timeline-locale-projection.spec.ts` | Locale registries trimmed to `en` + `es`: `Locale` types, LOCALES/LABEL/TAG/DOCS maps, dict loaders, accept-language matchers; enterprise/entry `zh` special cases now use `es`; console footer always links Discord (Feishu was zh-only); e2e locale-projection spec tests `es` ("Explorado") instead of de/ar | Fork ships English + Spanish only. | Keep our trimmed lists; take upstream's structural changes and re-trim any new locale entries. | | `packages/tui/src/component/prompt/autocomplete.tsx`, `packages/app/src/components/prompt-input.tsx`, `packages/opencode/src/cli/cmd/run/footer.prompt.tsx`, `packages/core/src/filesystem/search.ts`, `packages/tui/test/fixture/tui-sdk.ts`, `packages/opencode/test/cli/run/footer.view.test.tsx`, `packages/core/test/filesystem/search.test.ts` | `@` mention autocomplete lists file/directory results before references/agents/MCP resources; TUI and run-CLI menus cap the leading files so up to 3 non-file matches stay in the first visible page; web app group rank reordered to recent → file → reference → agent → resource (extracted into fork-only `prompt-input/at-options.ts`). TUI and run-CLI file resources track menu visibility so opening on an empty query fetches (previously nothing refetched until the query changed); core's ripgrep-fallback `find` returns its first entries for an empty query to mirror fff. Tests: tui-sdk fixture's `FetchHandler` gains an optional `method` arg, `renderFooter` accepts `findFiles`/`agents` overrides + a mention-menu ordering test, core search tests cover the fallback's empty query (fork-only test files: `packages/tui/test/prompt-autocomplete.test.tsx`, `packages/app/test-browser/prompt-at-options.test.ts`, `packages/app/src/components/prompt-input/at-options{,.test}.ts`) | Files are the dominant `@` use case; they should rank first and appear on a bare `@`. | Keep our ordering/reserve logic, resource source tracking, empty-query branch, fixture/test additions; take upstream's surrounding changes. | | `packages/slack/.gitignore`, `sdks/vscode/.gitignore` | Added the missing trailing newline (content unchanged) | Upstream's copies end without a newline; POSIX text files should end with one, and tools that concatenate or append to these files misbehave without it. | Whitespace-only: keep the trailing newline; take upstream's entries. | -| `packages/tui/src/config/keybind.ts`, `packages/tui/src/component/prompt/index.tsx`, `packages/tui/src/app.tsx` | Permission-mode indicator + shift+tab cycle. keybind.ts: added `permission_mode_cycle: keybind("shift+tab", ...)` + its `permission.mode` CommandMap entry; `agent_cycle_reverse` default `"shift+tab"` → `"none"` (tab still cycles agents forward; previous-agent stays rebindable). prompt/index.tsx: the footer badge next to the agent name always renders in prompt mode — gray `manual` (`theme.textMuted`) in normal mode, red `auto` (`theme.error`) in auto mode (upstream only showed gray `auto`). app.tsx: `"permission.mode"` added to `appBindingCommands` so the binding is gathered. | The permission mode is always visible (manual/auto) with auto in red as a danger signal, and shift+tab toggles it without opening the palette. | Keep the `permission_mode_cycle` definition + mapping, the `agent_cycle_reverse` `"none"` default, the badge `Show` block, and the `appBindingCommands` entry; take upstream's surrounding changes. If upstream reworks the auto badge or rebinds shift+tab, re-apply on top and re-audit the shift+tab conflict. | +| `packages/tui/src/config/keybind.ts`, `packages/tui/src/component/prompt/index.tsx`, `packages/tui/src/app.tsx` | Permission-mode indicator + shift+tab cycle. keybind.ts: added `permission_mode_cycle: keybind("shift+tab", ...)` + its `permission.mode` CommandMap entry; `agent_cycle_reverse` default `"shift+tab"` → `"none"` (tab still cycles agents forward; previous-agent stays rebindable). prompt/index.tsx: the footer badge next to the agent name always renders in prompt mode — gray `manual` (`theme.textMuted`) in normal mode, red `auto` (`theme.error`) in auto mode (upstream only showed gray `auto`); the idle footer's shortcut hints gain a `shift+tab modes` text (via a `modeShortcut` `useCommandShortcut("permission.mode")` memo) left of `tab agents`, in the same no-usage `Match` arm. app.tsx: `"permission.mode"` added to `appBindingCommands` so the binding is gathered. | The permission mode is always visible (manual/auto) with auto in red as a danger signal, and shift+tab toggles it without opening the palette. | Keep the `permission_mode_cycle` definition + mapping, the `agent_cycle_reverse` `"none"` default, the badge `Show` block, the `modeShortcut` memo + hint text, and the `appBindingCommands` entry; take upstream's surrounding changes. If upstream reworks the auto badge or rebinds shift+tab, re-apply on top and re-audit the shift+tab conflict. | ## Known rebrand gaps diff --git a/packages/tui/src/component/prompt/index.tsx b/packages/tui/src/component/prompt/index.tsx index 87b2ff2f9b..89bb5f9a74 100644 --- a/packages/tui/src/component/prompt/index.tsx +++ b/packages/tui/src/component/prompt/index.tsx @@ -166,6 +166,7 @@ export function Prompt(props: PromptProps) { const stash = usePromptStash() const keymap = useOpencodeKeymap() const agentShortcut = useCommandShortcut("agent.cycle") + const modeShortcut = useCommandShortcut("permission.mode") const paletteShortcut = useCommandShortcut("command.palette.show") const interruptSequence = useCommandShortcut("session.interrupt") const interruptNowSequence = useCommandShortcut("session.interrupt.now") @@ -1651,6 +1652,9 @@ export function Prompt(props: PromptProps) { )} + + {modeShortcut()} modes + {agentShortcut()} agents