Skip to content

Commit 721fceb

Browse files
Apply PR #12633: feat(tui): add auto-accept mode for permission requests
2 parents 5c5dd67 + 5792a80 commit 721fceb

8 files changed

Lines changed: 66 additions & 14 deletions

File tree

packages/opencode/src/cli/cmd/run.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -375,6 +375,11 @@ export const RunCommand = cmd({
375375
action: "deny",
376376
pattern: "*",
377377
},
378+
{
379+
permission: "edit",
380+
action: "allow",
381+
pattern: "*",
382+
},
378383
]
379384

380385
function title() {

packages/opencode/src/cli/cmd/tui/app.tsx

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -553,6 +553,7 @@ function App(props: { onSnapshot?: () => Promise<string[]> }) {
553553
{
554554
title: "Toggle MCPs",
555555
value: "mcp.list",
556+
search: "toggle mcps",
556557
category: "Agent",
557558
slash: {
558559
name: "mcps",
@@ -657,8 +658,9 @@ function App(props: { onSnapshot?: () => Promise<string[]> }) {
657658
category: "System",
658659
},
659660
{
660-
title: "Toggle theme mode",
661+
title: mode() === "dark" ? "Light mode" : "Dark mode",
661662
value: "theme.switch_mode",
663+
search: "toggle appearance",
662664
onSelect: (dialog) => {
663665
setMode(mode() === "dark" ? "light" : "dark")
664666
dialog.clear()
@@ -707,6 +709,7 @@ function App(props: { onSnapshot?: () => Promise<string[]> }) {
707709
},
708710
{
709711
title: "Toggle debug panel",
712+
search: "toggle debug",
710713
category: "System",
711714
value: "app.debug",
712715
onSelect: (dialog) => {
@@ -716,6 +719,7 @@ function App(props: { onSnapshot?: () => Promise<string[]> }) {
716719
},
717720
{
718721
title: "Toggle console",
722+
search: "toggle console",
719723
category: "System",
720724
value: "app.console",
721725
onSelect: (dialog) => {
@@ -757,6 +761,7 @@ function App(props: { onSnapshot?: () => Promise<string[]> }) {
757761
{
758762
title: terminalTitleEnabled() ? "Disable terminal title" : "Enable terminal title",
759763
value: "terminal.title.toggle",
764+
search: "toggle terminal title",
760765
keybind: "terminal_title_toggle",
761766
category: "System",
762767
onSelect: (dialog) => {
@@ -772,6 +777,7 @@ function App(props: { onSnapshot?: () => Promise<string[]> }) {
772777
{
773778
title: kv.get("animations_enabled", true) ? "Disable animations" : "Enable animations",
774779
value: "app.toggle.animations",
780+
search: "toggle animations",
775781
category: "System",
776782
onSelect: (dialog) => {
777783
kv.set("animations_enabled", !kv.get("animations_enabled", true))
@@ -781,6 +787,7 @@ function App(props: { onSnapshot?: () => Promise<string[]> }) {
781787
{
782788
title: kv.get("diff_wrap_mode", "word") === "word" ? "Disable diff wrapping" : "Enable diff wrapping",
783789
value: "app.toggle.diffwrap",
790+
search: "toggle diff wrapping",
784791
category: "System",
785792
onSelect: (dialog) => {
786793
const current = kv.get("diff_wrap_mode", "word")
@@ -789,7 +796,9 @@ function App(props: { onSnapshot?: () => Promise<string[]> }) {
789796
},
790797
},
791798
{
792-
title: kv.get("clear_prompt_save_history", false) ? "Don't include cleared prompts in history" : "Include cleared prompts in history",
799+
title: kv.get("clear_prompt_save_history", false)
800+
? "Don't include cleared prompts in history"
801+
: "Include cleared prompts in history",
793802
value: "app.toggle.clear_prompt_history",
794803
category: "System",
795804
onSelect: (dialog) => {

packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ export function Prompt(props: PromptProps) {
9898
const [auto, setAuto] = createSignal<AutocompleteRef>()
9999
const currentProviderLabel = createMemo(() => local.model.parsed().provider)
100100
const hasRightContent = createMemo(() => Boolean(props.right))
101+
const [autoaccept, setAutoaccept] = kv.signal<"none" | "edit">("permission_auto_accept", "edit")
101102

102103
function promptModelWarning() {
103104
toast.show({
@@ -213,6 +214,17 @@ export function Prompt(props: PromptProps) {
213214

214215
command.register(() => {
215216
return [
217+
{
218+
title: autoaccept() === "none" ? "Enable autoedit" : "Disable autoedit",
219+
value: "permission.auto_accept.toggle",
220+
search: "toggle permissions",
221+
keybind: "permission_auto_accept_toggle",
222+
category: "Agent",
223+
onSelect: (dialog) => {
224+
setAutoaccept(() => (autoaccept() === "none" ? "edit" : "none"))
225+
dialog.clear()
226+
},
227+
},
216228
{
217229
title: "Clear prompt",
218230
value: "prompt.clear",
@@ -1108,6 +1120,7 @@ export function Prompt(props: PromptProps) {
11081120
{local.model.parsed().model}
11091121
</text>
11101122
<text fg={theme.textMuted}>{currentProviderLabel()}</text>
1123+
<text fg={theme.textMuted}>{currentProviderLabel()}</text>
11111124
<Show when={showVariant()}>
11121125
<text fg={theme.textMuted}>·</text>
11131126
<text>
@@ -1117,11 +1130,14 @@ export function Prompt(props: PromptProps) {
11171130
</box>
11181131
</Show>
11191132
</box>
1120-
<Show when={hasRightContent()}>
1121-
<box flexDirection="row" gap={1} alignItems="center">
1122-
{props.right}
1123-
</box>
1124-
</Show>
1133+
<box flexDirection="row" gap={1} alignItems="center">
1134+
<Show when={autoaccept() === "edit"}>
1135+
<text>
1136+
<span style={{ fg: theme.warning }}>autoedit</span>
1137+
</text>
1138+
</Show>
1139+
<Show when={hasRightContent()}>{props.right}</Show>
1140+
</box>
11251141
</box>
11261142
</box>
11271143
</box>

packages/opencode/src/cli/cmd/tui/context/sync.tsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import type { Snapshot } from "@/snapshot"
2828
import { useExit } from "./exit"
2929
import { useArgs } from "./args"
3030
import { batch, createEffect, on } from "solid-js"
31+
import { useKV } from "./kv"
3132
import { Log } from "@/util/log"
3233
import { ConsoleState, emptyConsoleState, type ConsoleState as ConsoleStateType } from "@/config/console-state"
3334

@@ -107,6 +108,8 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
107108
const event = useEvent()
108109
const project = useProject()
109110
const sdk = useSDK()
111+
const kv = useKV()
112+
const [autoaccept] = kv.signal<"none" | "edit">("permission_auto_accept", "edit")
110113

111114
event.subscribe((event) => {
112115
switch (event.type) {
@@ -130,6 +133,13 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
130133

131134
case "permission.asked": {
132135
const request = event.properties
136+
if (autoaccept() === "edit" && request.permission === "edit") {
137+
sdk.client.permission.reply({
138+
reply: "once",
139+
requestID: request.id,
140+
})
141+
break
142+
}
133143
const requests = store.permission[request.sessionID]
134144
if (!requests) {
135145
setStore("permission", request.sessionID, [request])

packages/opencode/src/cli/cmd/tui/routes/session/index.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -584,6 +584,7 @@ export function Session() {
584584
{
585585
title: sidebarVisible() ? "Hide sidebar" : "Show sidebar",
586586
value: "session.sidebar.toggle",
587+
search: "toggle sidebar",
587588
keybind: "sidebar_toggle",
588589
category: "Session",
589590
onSelect: (dialog) => {
@@ -598,6 +599,7 @@ export function Session() {
598599
{
599600
title: conceal() ? "Disable code concealment" : "Enable code concealment",
600601
value: "session.toggle.conceal",
602+
search: "toggle code concealment",
601603
keybind: "messages_toggle_conceal" as any,
602604
category: "Session",
603605
onSelect: (dialog) => {
@@ -608,6 +610,7 @@ export function Session() {
608610
{
609611
title: showTimestamps() ? "Hide timestamps" : "Show timestamps",
610612
value: "session.toggle.timestamps",
613+
search: "toggle timestamps",
611614
category: "Session",
612615
slash: {
613616
name: "timestamps",
@@ -621,6 +624,7 @@ export function Session() {
621624
{
622625
title: showThinking() ? "Hide thinking" : "Show thinking",
623626
value: "session.toggle.thinking",
627+
search: "toggle thinking",
624628
keybind: "display_thinking",
625629
category: "Session",
626630
slash: {
@@ -635,6 +639,7 @@ export function Session() {
635639
{
636640
title: showDetails() ? "Hide tool details" : "Show tool details",
637641
value: "session.toggle.actions",
642+
search: "toggle tool details",
638643
keybind: "tool_details",
639644
category: "Session",
640645
onSelect: (dialog) => {
@@ -643,8 +648,9 @@ export function Session() {
643648
},
644649
},
645650
{
646-
title: "Toggle session scrollbar",
651+
title: showScrollbar() ? "Hide session scrollbar" : "Show session scrollbar",
647652
value: "session.toggle.scrollbar",
653+
search: "toggle session scrollbar",
648654
keybind: "scrollbar_toggle",
649655
category: "Session",
650656
onSelect: (dialog) => {

packages/opencode/src/cli/cmd/tui/ui/dialog-select.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ export interface DialogSelectOption<T = any> {
3737
title: string
3838
value: T
3939
description?: string
40+
search?: string
4041
footer?: JSX.Element | string
4142
category?: string
4243
categoryView?: JSX.Element
@@ -93,8 +94,8 @@ export function DialogSelect<T>(props: DialogSelectProps<T>) {
9394
// users typically search by the item name, and not its category.
9495
const result = fuzzysort
9596
.go(needle, options, {
96-
keys: ["title", "category"],
97-
scoreFn: (r) => r[0].score * 2 + r[1].score,
97+
keys: ["title", "category", "search"],
98+
scoreFn: (r) => r[0].score * 2 + r[1].score + r[2].score,
9899
})
99100
.map((x) => x.obj)
100101

packages/opencode/src/config/config.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -672,7 +672,12 @@ export namespace Config {
672672
command_list: z.string().optional().default("ctrl+p").describe("List available commands"),
673673
agent_list: z.string().optional().default("<leader>a").describe("List agents"),
674674
agent_cycle: z.string().optional().default("tab").describe("Next agent"),
675-
agent_cycle_reverse: z.string().optional().default("shift+tab").describe("Previous agent"),
675+
agent_cycle_reverse: z.string().optional().default("none").describe("Previous agent"),
676+
permission_auto_accept_toggle: z
677+
.string()
678+
.optional()
679+
.default("shift+tab")
680+
.describe("Toggle auto-accept mode for permissions"),
676681
variant_cycle: z.string().optional().default("ctrl+t").describe("Cycle model variants"),
677682
variant_list: z.string().optional().default("none").describe("List model variants"),
678683
input_clear: z.string().optional().default("ctrl+c").describe("Clear input field"),

packages/opencode/test/agent/agent.test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ test("build agent has correct default properties", async () => {
4242
expect(build).toBeDefined()
4343
expect(build?.mode).toBe("primary")
4444
expect(build?.native).toBe(true)
45-
expect(evalPerm(build, "edit")).toBe("allow")
45+
expect(evalPerm(build, "edit")).toBe("ask")
4646
expect(evalPerm(build, "bash")).toBe("allow")
4747
},
4848
})
@@ -219,8 +219,8 @@ test("agent permission config merges with defaults", async () => {
219219
expect(build).toBeDefined()
220220
// Specific pattern is denied
221221
expect(Permission.evaluate("bash", "rm -rf *", build!.permission).action).toBe("deny")
222-
// Edit still allowed
223-
expect(evalPerm(build, "edit")).toBe("allow")
222+
// Edit still asks (default behavior)
223+
expect(evalPerm(build, "edit")).toBe("ask")
224224
},
225225
})
226226
})

0 commit comments

Comments
 (0)