Skip to content

Commit 4eb9002

Browse files
Apply PR #12633: feat(tui): add auto-accept mode for permission requests
2 parents 49dc23b + e31f00a commit 4eb9002

File tree

11 files changed

+84
-24
lines changed

11 files changed

+84
-24
lines changed

packages/opencode/src/agent/agent.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ export namespace Agent {
6363
question: "deny",
6464
plan_enter: "deny",
6565
plan_exit: "deny",
66+
edit: "ask",
6667
// mirrors github.com/github/gitignore Node.gitignore pattern for .env files
6768
read: {
6869
"*": "allow",

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -365,6 +365,11 @@ export const RunCommand = cmd({
365365
action: "deny",
366366
pattern: "*",
367367
},
368+
{
369+
permission: "edit",
370+
action: "allow",
371+
pattern: "*",
372+
},
368373
]
369374

370375
function title() {

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

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -462,6 +462,7 @@ function App() {
462462
{
463463
title: "Toggle MCPs",
464464
value: "mcp.list",
465+
search: "toggle mcps",
465466
category: "Agent",
466467
slash: {
467468
name: "mcps",
@@ -537,8 +538,9 @@ function App() {
537538
category: "System",
538539
},
539540
{
540-
title: "Toggle appearance",
541+
title: mode() === "dark" ? "Light mode" : "Dark mode",
541542
value: "theme.switch_mode",
543+
search: "toggle appearance",
542544
onSelect: (dialog) => {
543545
setMode(mode() === "dark" ? "light" : "dark")
544546
dialog.clear()
@@ -577,6 +579,7 @@ function App() {
577579
},
578580
{
579581
title: "Toggle debug panel",
582+
search: "toggle debug",
580583
category: "System",
581584
value: "app.debug",
582585
onSelect: (dialog) => {
@@ -586,6 +589,7 @@ function App() {
586589
},
587590
{
588591
title: "Toggle console",
592+
search: "toggle console",
589593
category: "System",
590594
value: "app.console",
591595
onSelect: (dialog) => {
@@ -626,6 +630,7 @@ function App() {
626630
{
627631
title: terminalTitleEnabled() ? "Disable terminal title" : "Enable terminal title",
628632
value: "terminal.title.toggle",
633+
search: "toggle terminal title",
629634
keybind: "terminal_title_toggle",
630635
category: "System",
631636
onSelect: (dialog) => {
@@ -641,6 +646,7 @@ function App() {
641646
{
642647
title: kv.get("animations_enabled", true) ? "Disable animations" : "Enable animations",
643648
value: "app.toggle.animations",
649+
search: "toggle animations",
644650
category: "System",
645651
onSelect: (dialog) => {
646652
kv.set("animations_enabled", !kv.get("animations_enabled", true))
@@ -650,6 +656,7 @@ function App() {
650656
{
651657
title: kv.get("diff_wrap_mode", "word") === "word" ? "Disable diff wrapping" : "Enable diff wrapping",
652658
value: "app.toggle.diffwrap",
659+
search: "toggle diff wrapping",
653660
category: "System",
654661
onSelect: (dialog) => {
655662
const current = kv.get("diff_wrap_mode", "word")

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

Lines changed: 35 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ export function Prompt(props: PromptProps) {
7777
const renderer = useRenderer()
7878
const { theme, syntax } = useTheme()
7979
const kv = useKV()
80+
const [autoaccept, setAutoaccept] = kv.signal<"none" | "edit">("permission_auto_accept", "edit")
8081

8182
function promptModelWarning() {
8283
toast.show({
@@ -170,6 +171,17 @@ export function Prompt(props: PromptProps) {
170171

171172
command.register(() => {
172173
return [
174+
{
175+
title: autoaccept() === "none" ? "Enable autoedit" : "Disable autoedit",
176+
value: "permission.auto_accept.toggle",
177+
search: "toggle permissions",
178+
keybind: "permission_auto_accept_toggle",
179+
category: "Agent",
180+
onSelect: (dialog) => {
181+
setAutoaccept(() => (autoaccept() === "none" ? "edit" : "none"))
182+
dialog.clear()
183+
},
184+
},
173185
{
174186
title: "Clear prompt",
175187
value: "prompt.clear",
@@ -996,23 +1008,30 @@ export function Prompt(props: PromptProps) {
9961008
cursorColor={theme.text}
9971009
syntaxStyle={syntax()}
9981010
/>
999-
<box flexDirection="row" flexShrink={0} paddingTop={1} gap={1}>
1000-
<text fg={highlight()}>
1001-
{store.mode === "shell" ? "Shell" : Locale.titlecase(local.agent.current().name)}{" "}
1002-
</text>
1003-
<Show when={store.mode === "normal"}>
1004-
<box flexDirection="row" gap={1}>
1005-
<text flexShrink={0} fg={keybind.leader ? theme.textMuted : theme.text}>
1006-
{local.model.parsed().model}
1007-
</text>
1008-
<text fg={theme.textMuted}>{local.model.parsed().provider}</text>
1009-
<Show when={showVariant()}>
1010-
<text fg={theme.textMuted}>·</text>
1011-
<text>
1012-
<span style={{ fg: theme.warning, bold: true }}>{local.model.variant.current()}</span>
1011+
<box flexDirection="row" flexShrink={0} paddingTop={1} gap={1} justifyContent="space-between">
1012+
<box flexDirection="row" gap={1}>
1013+
<text fg={highlight()}>
1014+
{store.mode === "shell" ? "Shell" : Locale.titlecase(local.agent.current().name)}{" "}
1015+
</text>
1016+
<Show when={store.mode === "normal"}>
1017+
<box flexDirection="row" gap={1}>
1018+
<text flexShrink={0} fg={keybind.leader ? theme.textMuted : theme.text}>
1019+
{local.model.parsed().model}
10131020
</text>
1014-
</Show>
1015-
</box>
1021+
<text fg={theme.textMuted}>{local.model.parsed().provider}</text>
1022+
<Show when={showVariant()}>
1023+
<text fg={theme.textMuted}>·</text>
1024+
<text>
1025+
<span style={{ fg: theme.warning, bold: true }}>{local.model.variant.current()}</span>
1026+
</text>
1027+
</Show>
1028+
</box>
1029+
</Show>
1030+
</box>
1031+
<Show when={autoaccept() === "edit"}>
1032+
<text>
1033+
<span style={{ fg: theme.warning }}>autoedit</span>
1034+
</text>
10161035
</Show>
10171036
</box>
10181037
</box>

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import { createSimpleContext } from "./helper"
2525
import type { Snapshot } from "@/snapshot"
2626
import { useExit } from "./exit"
2727
import { useArgs } from "./args"
28+
import { useKV } from "./kv"
2829
import { batch, onMount } from "solid-js"
2930
import { Log } from "@/util/log"
3031
import type { Path } from "@opencode-ai/sdk"
@@ -103,6 +104,8 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
103104
})
104105

105106
const sdk = useSDK()
107+
const kv = useKV()
108+
const [autoaccept] = kv.signal<"none" | "edit">("permission_auto_accept", "edit")
106109

107110
sdk.event.listen((e) => {
108111
const event = e.details
@@ -127,6 +130,13 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
127130

128131
case "permission.asked": {
129132
const request = event.properties
133+
if (autoaccept() === "edit" && request.permission === "edit") {
134+
sdk.client.permission.reply({
135+
reply: "once",
136+
requestID: request.id,
137+
})
138+
break
139+
}
130140
const requests = store.permission[request.sessionID]
131141
if (!requests) {
132142
setStore("permission", request.sessionID, [request])
@@ -441,6 +451,7 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
441451
get ready() {
442452
return store.status !== "loading"
443453
},
454+
444455
session: {
445456
get(sessionID: string) {
446457
const match = Binary.search(store.session, sessionID, (s) => s.id)

packages/opencode/src/cli/cmd/tui/routes/home.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ export function Home() {
4646
{
4747
title: tipsHidden() ? "Show tips" : "Hide tips",
4848
value: "tips.toggle",
49+
search: "toggle tips",
4950
keybind: "tips_toggle",
5051
category: "System",
5152
onSelect: (dialog) => {

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

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -552,6 +552,7 @@ export function Session() {
552552
{
553553
title: sidebarVisible() ? "Hide sidebar" : "Show sidebar",
554554
value: "session.sidebar.toggle",
555+
search: "toggle sidebar",
555556
keybind: "sidebar_toggle",
556557
category: "Session",
557558
onSelect: (dialog) => {
@@ -566,6 +567,7 @@ export function Session() {
566567
{
567568
title: conceal() ? "Disable code concealment" : "Enable code concealment",
568569
value: "session.toggle.conceal",
570+
search: "toggle code concealment",
569571
keybind: "messages_toggle_conceal" as any,
570572
category: "Session",
571573
onSelect: (dialog) => {
@@ -576,6 +578,7 @@ export function Session() {
576578
{
577579
title: showTimestamps() ? "Hide timestamps" : "Show timestamps",
578580
value: "session.toggle.timestamps",
581+
search: "toggle timestamps",
579582
category: "Session",
580583
slash: {
581584
name: "timestamps",
@@ -589,6 +592,7 @@ export function Session() {
589592
{
590593
title: showThinking() ? "Hide thinking" : "Show thinking",
591594
value: "session.toggle.thinking",
595+
search: "toggle thinking",
592596
keybind: "display_thinking",
593597
category: "Session",
594598
slash: {
@@ -603,6 +607,7 @@ export function Session() {
603607
{
604608
title: showDetails() ? "Hide tool details" : "Show tool details",
605609
value: "session.toggle.actions",
610+
search: "toggle tool details",
606611
keybind: "tool_details",
607612
category: "Session",
608613
onSelect: (dialog) => {
@@ -611,8 +616,9 @@ export function Session() {
611616
},
612617
},
613618
{
614-
title: "Toggle session scrollbar",
619+
title: showScrollbar() ? "Hide session scrollbar" : "Show session scrollbar",
615620
value: "session.toggle.scrollbar",
621+
search: "toggle session scrollbar",
616622
keybind: "scrollbar_toggle",
617623
category: "Session",
618624
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
@@ -34,6 +34,7 @@ export interface DialogSelectOption<T = any> {
3434
title: string
3535
value: T
3636
description?: string
37+
search?: string
3738
footer?: JSX.Element | string
3839
category?: string
3940
disabled?: boolean
@@ -85,8 +86,8 @@ export function DialogSelect<T>(props: DialogSelectProps<T>) {
8586
// users typically search by the item name, and not its category.
8687
const result = fuzzysort
8788
.go(needle, options, {
88-
keys: ["title", "category"],
89-
scoreFn: (r) => r[0].score * 2 + r[1].score,
89+
keys: ["title", "category", "search"],
90+
scoreFn: (r) => r[0].score * 2 + r[1].score + r[2].score,
9091
})
9192
.map((x) => x.obj)
9293

packages/opencode/src/config/config.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -812,7 +812,12 @@ export namespace Config {
812812
command_list: z.string().optional().default("ctrl+p").describe("List available commands"),
813813
agent_list: z.string().optional().default("<leader>a").describe("List agents"),
814814
agent_cycle: z.string().optional().default("tab").describe("Next agent"),
815-
agent_cycle_reverse: z.string().optional().default("shift+tab").describe("Previous agent"),
815+
agent_cycle_reverse: z.string().optional().default("none").describe("Previous agent"),
816+
permission_auto_accept_toggle: z
817+
.string()
818+
.optional()
819+
.default("shift+tab")
820+
.describe("Toggle auto-accept mode for permissions"),
816821
variant_cycle: z.string().optional().default("ctrl+t").describe("Cycle model variants"),
817822
input_clear: z.string().optional().default("ctrl+c").describe("Clear input field"),
818823
input_paste: z.string().optional().default("ctrl+v").describe("Paste from clipboard"),

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ test("build agent has correct default properties", async () => {
3838
expect(build).toBeDefined()
3939
expect(build?.mode).toBe("primary")
4040
expect(build?.native).toBe(true)
41-
expect(evalPerm(build, "edit")).toBe("allow")
41+
expect(evalPerm(build, "edit")).toBe("ask")
4242
expect(evalPerm(build, "bash")).toBe("allow")
4343
},
4444
})
@@ -217,8 +217,8 @@ test("agent permission config merges with defaults", async () => {
217217
expect(build).toBeDefined()
218218
// Specific pattern is denied
219219
expect(PermissionNext.evaluate("bash", "rm -rf *", build!.permission).action).toBe("deny")
220-
// Edit still allowed
221-
expect(evalPerm(build, "edit")).toBe("allow")
220+
// Edit still asks (default behavior)
221+
expect(evalPerm(build, "edit")).toBe("ask")
222222
},
223223
})
224224
})

0 commit comments

Comments
 (0)