Skip to content

Commit 6c8776d

Browse files
Apply PR #12633: feat(tui): add auto-accept mode for permission requests
2 parents d752f0a + 5792a80 commit 6c8776d

8 files changed

Lines changed: 52 additions & 8 deletions

File tree

packages/opencode/src/agent/agent.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ export const layer = Layer.effect(
8686
const defaults = Permission.fromConfig({
8787
"*": "allow",
8888
doom_loop: "ask",
89+
edit: "ask",
8990
external_directory: {
9091
"*": "ask",
9192
...Object.fromEntries(whitelistedDirs.map((dir) => [dir, "allow"])),

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -367,6 +367,11 @@ export const RunCommand = cmd({
367367
action: "deny",
368368
pattern: "*",
369369
},
370+
{
371+
permission: "edit",
372+
action: "allow",
373+
pattern: "*",
374+
},
370375
]
371376

372377
function title() {

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -505,6 +505,7 @@ function App(props: { onSnapshot?: () => Promise<string[]> }) {
505505
{
506506
title: "Toggle MCPs",
507507
value: "mcp.list",
508+
search: "toggle mcps",
508509
category: "Agent",
509510
slash: {
510511
name: "mcps",
@@ -611,6 +612,7 @@ function App(props: { onSnapshot?: () => Promise<string[]> }) {
611612
{
612613
title: mode() === "dark" ? "Switch to light mode" : "Switch to dark mode",
613614
value: "theme.switch_mode",
615+
search: "toggle appearance",
614616
onSelect: (dialog) => {
615617
setMode(mode() === "dark" ? "light" : "dark")
616618
dialog.clear()
@@ -659,6 +661,7 @@ function App(props: { onSnapshot?: () => Promise<string[]> }) {
659661
},
660662
{
661663
title: "Toggle debug panel",
664+
search: "toggle debug",
662665
category: "System",
663666
value: "app.debug",
664667
onSelect: (dialog) => {
@@ -668,6 +671,7 @@ function App(props: { onSnapshot?: () => Promise<string[]> }) {
668671
},
669672
{
670673
title: "Toggle console",
674+
search: "toggle console",
671675
category: "System",
672676
value: "app.console",
673677
onSelect: (dialog) => {
@@ -709,6 +713,7 @@ function App(props: { onSnapshot?: () => Promise<string[]> }) {
709713
{
710714
title: terminalTitleEnabled() ? "Disable terminal title" : "Enable terminal title",
711715
value: "terminal.title.toggle",
716+
search: "toggle terminal title",
712717
keybind: "terminal_title_toggle",
713718
category: "System",
714719
onSelect: (dialog) => {
@@ -724,6 +729,7 @@ function App(props: { onSnapshot?: () => Promise<string[]> }) {
724729
{
725730
title: kv.get("animations_enabled", true) ? "Disable animations" : "Enable animations",
726731
value: "app.toggle.animations",
732+
search: "toggle animations",
727733
category: "System",
728734
onSelect: (dialog) => {
729735
kv.set("animations_enabled", !kv.get("animations_enabled", true))
@@ -767,6 +773,7 @@ function App(props: { onSnapshot?: () => Promise<string[]> }) {
767773
{
768774
title: kv.get("diff_wrap_mode", "word") === "word" ? "Disable diff wrapping" : "Enable diff wrapping",
769775
value: "app.toggle.diffwrap",
776+
search: "toggle diff wrapping",
770777
category: "System",
771778
onSelect: (dialog) => {
772779
const current = kv.get("diff_wrap_mode", "word")

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

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@ export function Prompt(props: PromptProps) {
138138
const [auto, setAuto] = createSignal<AutocompleteRef>()
139139
const currentProviderLabel = createMemo(() => local.model.parsed().provider)
140140
const hasRightContent = createMemo(() => Boolean(props.right))
141+
const [autoaccept, setAutoaccept] = kv.signal<"none" | "edit">("permission_auto_accept", "edit")
141142

142143
function promptModelWarning() {
143144
toast.show({
@@ -254,6 +255,17 @@ export function Prompt(props: PromptProps) {
254255

255256
command.register(() => {
256257
return [
258+
{
259+
title: autoaccept() === "none" ? "Enable autoedit" : "Disable autoedit",
260+
value: "permission.auto_accept.toggle",
261+
search: "toggle permissions",
262+
keybind: "permission_auto_accept_toggle",
263+
category: "Agent",
264+
onSelect: (dialog) => {
265+
setAutoaccept(() => (autoaccept() === "none" ? "edit" : "none"))
266+
dialog.clear()
267+
},
268+
},
257269
{
258270
title: "Clear prompt",
259271
value: "prompt.clear",
@@ -1280,9 +1292,14 @@ export function Prompt(props: PromptProps) {
12801292
)}
12811293
</Show>
12821294
</box>
1283-
<Show when={hasRightContent()}>
1295+
<Show when={hasRightContent() || autoaccept() === "edit"}>
12841296
<box flexDirection="row" gap={1} alignItems="center">
12851297
{props.right}
1298+
<Show when={autoaccept() === "edit"}>
1299+
<text>
1300+
<span style={{ fg: theme.warning }}>autoedit</span>
1301+
</text>
1302+
</Show>
12861303
</box>
12871304
</Show>
12881305
</box>

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

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,11 @@ import { createSimpleContext } from "./helper"
2727
import type { Snapshot } from "@/snapshot"
2828
import { useExit } from "./exit"
2929
import { useArgs } from "./args"
30+
import { useKV } from "./kv"
3031
import { batch, onMount } from "solid-js"
3132
import * as Log from "@opencode-ai/core/util/log"
3233
import { emptyConsoleState, type ConsoleState } from "@/config/console-state"
3334
import path from "path"
34-
import { useKV } from "./kv"
3535

3636
export const { use: useSync, provider: SyncProvider } = createSimpleContext({
3737
name: "Sync",
@@ -110,6 +110,7 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
110110
const project = useProject()
111111
const sdk = useSDK()
112112
const kv = useKV()
113+
const [autoaccept] = kv.signal<"none" | "edit">("permission_auto_accept", "edit")
113114

114115
const fullSyncedSessions = new Set<string>()
115116
let syncedWorkspace = project.workspace.current()
@@ -152,6 +153,13 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
152153

153154
case "permission.asked": {
154155
const request = event.properties
156+
if (autoaccept() === "edit" && request.permission === "edit") {
157+
sdk.client.permission.reply({
158+
reply: "once",
159+
requestID: request.id,
160+
})
161+
break
162+
}
155163
const requests = store.permission[request.sessionID]
156164
if (!requests) {
157165
setStore("permission", request.sessionID, [request])

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

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -605,6 +605,7 @@ export function Session() {
605605
{
606606
title: sidebarVisible() ? "Hide sidebar" : "Show sidebar",
607607
value: "session.sidebar.toggle",
608+
search: "toggle sidebar",
608609
keybind: "sidebar_toggle",
609610
category: "Session",
610611
onSelect: (dialog) => {
@@ -629,6 +630,7 @@ export function Session() {
629630
{
630631
title: showTimestamps() ? "Hide timestamps" : "Show timestamps",
631632
value: "session.toggle.timestamps",
633+
search: "toggle timestamps",
632634
category: "Session",
633635
slash: {
634636
name: "timestamps",
@@ -642,6 +644,7 @@ export function Session() {
642644
{
643645
title: showThinking() ? "Hide thinking" : "Show thinking",
644646
value: "session.toggle.thinking",
647+
search: "toggle thinking",
645648
keybind: "display_thinking",
646649
category: "Session",
647650
slash: {
@@ -656,6 +659,7 @@ export function Session() {
656659
{
657660
title: showDetails() ? "Hide tool details" : "Show tool details",
658661
value: "session.toggle.actions",
662+
search: "toggle tool details",
659663
keybind: "tool_details",
660664
category: "Session",
661665
onSelect: (dialog) => {
@@ -664,8 +668,9 @@ export function Session() {
664668
},
665669
},
666670
{
667-
title: "Toggle session scrollbar",
671+
title: showScrollbar() ? "Hide session scrollbar" : "Show session scrollbar",
668672
value: "session.toggle.scrollbar",
673+
search: "toggle session scrollbar",
669674
keybind: "scrollbar_toggle",
670675
category: "Session",
671676
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/test/agent/agent.test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ test("build agent has correct default properties", async () => {
4747
expect(build).toBeDefined()
4848
expect(build?.mode).toBe("primary")
4949
expect(build?.native).toBe(true)
50-
expect(evalPerm(build, "edit")).toBe("allow")
50+
expect(evalPerm(build, "edit")).toBe("ask")
5151
expect(evalPerm(build, "bash")).toBe("allow")
5252
},
5353
})
@@ -224,8 +224,8 @@ test("agent permission config merges with defaults", async () => {
224224
expect(build).toBeDefined()
225225
// Specific pattern is denied
226226
expect(Permission.evaluate("bash", "rm -rf *", build!.permission).action).toBe("deny")
227-
// Edit still allowed
228-
expect(evalPerm(build, "edit")).toBe("allow")
227+
// Edit still asks (default behavior)
228+
expect(evalPerm(build, "edit")).toBe("ask")
229229
},
230230
})
231231
})

0 commit comments

Comments
 (0)