feat(tui): add auto-accept mode for permission requests
Add a toggleable auto-accept mode that automatically accepts all incoming permission requests with a 'once' reply. This is useful for users who want to streamline their workflow when they trust the agent's actions. Changes: - Add permission_auto_accept keybind (default: shift+tab) to config - Remove default for agent_cycle_reverse (was shift+tab) - Add auto-accept logic in sync.tsx to auto-reply when enabled - Add command bar action to toggle auto-accept mode (copy: "Toggle autoaccept permissions") - Add visual indicator showing 'auto-accept' when active - Store auto-accept state in KV for persistence across sessionspull/12633/head
parent
4b7abc0a2c
commit
878c1b8c2d
|
|
@ -74,6 +74,7 @@ export function Prompt(props: PromptProps) {
|
||||||
const renderer = useRenderer()
|
const renderer = useRenderer()
|
||||||
const { theme, syntax } = useTheme()
|
const { theme, syntax } = useTheme()
|
||||||
const kv = useKV()
|
const kv = useKV()
|
||||||
|
const [autoaccept, setAutoaccept] = kv.signal("permission_auto_accept", false)
|
||||||
|
|
||||||
function promptModelWarning() {
|
function promptModelWarning() {
|
||||||
toast.show({
|
toast.show({
|
||||||
|
|
@ -157,6 +158,16 @@ export function Prompt(props: PromptProps) {
|
||||||
|
|
||||||
command.register(() => {
|
command.register(() => {
|
||||||
return [
|
return [
|
||||||
|
{
|
||||||
|
title: "Toggle autoaccept permissions",
|
||||||
|
value: "permission.auto_accept.toggle",
|
||||||
|
keybind: "permission_auto_accept_toggle",
|
||||||
|
category: "Permission",
|
||||||
|
onSelect: (dialog) => {
|
||||||
|
setAutoaccept(!autoaccept() as any)
|
||||||
|
dialog.clear()
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: "Clear prompt",
|
title: "Clear prompt",
|
||||||
value: "prompt.clear",
|
value: "prompt.clear",
|
||||||
|
|
@ -973,23 +984,30 @@ export function Prompt(props: PromptProps) {
|
||||||
cursorColor={theme.text}
|
cursorColor={theme.text}
|
||||||
syntaxStyle={syntax()}
|
syntaxStyle={syntax()}
|
||||||
/>
|
/>
|
||||||
<box flexDirection="row" flexShrink={0} paddingTop={1} gap={1}>
|
<box flexDirection="row" flexShrink={0} paddingTop={1} gap={1} justifyContent="space-between">
|
||||||
<text fg={highlight()}>
|
<box flexDirection="row" gap={1}>
|
||||||
{store.mode === "shell" ? "Shell" : Locale.titlecase(local.agent.current().name)}{" "}
|
<text fg={highlight()}>
|
||||||
</text>
|
{store.mode === "shell" ? "Shell" : Locale.titlecase(local.agent.current().name)}{" "}
|
||||||
<Show when={store.mode === "normal"}>
|
</text>
|
||||||
<box flexDirection="row" gap={1}>
|
<Show when={store.mode === "normal"}>
|
||||||
<text flexShrink={0} fg={keybind.leader ? theme.textMuted : theme.text}>
|
<box flexDirection="row" gap={1}>
|
||||||
{local.model.parsed().model}
|
<text flexShrink={0} fg={keybind.leader ? theme.textMuted : theme.text}>
|
||||||
</text>
|
{local.model.parsed().model}
|
||||||
<text fg={theme.textMuted}>{local.model.parsed().provider}</text>
|
|
||||||
<Show when={showVariant()}>
|
|
||||||
<text fg={theme.textMuted}>·</text>
|
|
||||||
<text>
|
|
||||||
<span style={{ fg: theme.warning, bold: true }}>{local.model.variant.current()}</span>
|
|
||||||
</text>
|
</text>
|
||||||
</Show>
|
<text fg={theme.textMuted}>{local.model.parsed().provider}</text>
|
||||||
</box>
|
<Show when={showVariant()}>
|
||||||
|
<text fg={theme.textMuted}>·</text>
|
||||||
|
<text>
|
||||||
|
<span style={{ fg: theme.warning, bold: true }}>{local.model.variant.current()}</span>
|
||||||
|
</text>
|
||||||
|
</Show>
|
||||||
|
</box>
|
||||||
|
</Show>
|
||||||
|
</box>
|
||||||
|
<Show when={autoaccept()}>
|
||||||
|
<text>
|
||||||
|
<span style={{ fg: theme.warning, bold: true }}>auto-accept</span>
|
||||||
|
</text>
|
||||||
</Show>
|
</Show>
|
||||||
</box>
|
</box>
|
||||||
</box>
|
</box>
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ import { createSimpleContext } from "./helper"
|
||||||
import type { Snapshot } from "@/snapshot"
|
import type { Snapshot } from "@/snapshot"
|
||||||
import { useExit } from "./exit"
|
import { useExit } from "./exit"
|
||||||
import { useArgs } from "./args"
|
import { useArgs } from "./args"
|
||||||
|
import { useKV } from "./kv"
|
||||||
import { batch, onMount } from "solid-js"
|
import { batch, onMount } from "solid-js"
|
||||||
import { Log } from "@/util/log"
|
import { Log } from "@/util/log"
|
||||||
import type { Path } from "@opencode-ai/sdk"
|
import type { Path } from "@opencode-ai/sdk"
|
||||||
|
|
@ -103,6 +104,8 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
|
||||||
})
|
})
|
||||||
|
|
||||||
const sdk = useSDK()
|
const sdk = useSDK()
|
||||||
|
const kv = useKV()
|
||||||
|
const [autoaccept] = kv.signal("permission_auto_accept", false)
|
||||||
|
|
||||||
sdk.event.listen((e) => {
|
sdk.event.listen((e) => {
|
||||||
const event = e.details
|
const event = e.details
|
||||||
|
|
@ -127,6 +130,13 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
|
||||||
|
|
||||||
case "permission.asked": {
|
case "permission.asked": {
|
||||||
const request = event.properties
|
const request = event.properties
|
||||||
|
if (autoaccept()) {
|
||||||
|
sdk.client.permission.reply({
|
||||||
|
reply: "once",
|
||||||
|
requestID: request.id,
|
||||||
|
})
|
||||||
|
break
|
||||||
|
}
|
||||||
const requests = store.permission[request.sessionID]
|
const requests = store.permission[request.sessionID]
|
||||||
if (!requests) {
|
if (!requests) {
|
||||||
setStore("permission", request.sessionID, [request])
|
setStore("permission", request.sessionID, [request])
|
||||||
|
|
@ -423,6 +433,7 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
|
||||||
get ready() {
|
get ready() {
|
||||||
return store.status !== "loading"
|
return store.status !== "loading"
|
||||||
},
|
},
|
||||||
|
|
||||||
session: {
|
session: {
|
||||||
get(sessionID: string) {
|
get(sessionID: string) {
|
||||||
const match = Binary.search(store.session, sessionID, (s) => s.id)
|
const match = Binary.search(store.session, sessionID, (s) => s.id)
|
||||||
|
|
|
||||||
|
|
@ -818,7 +818,12 @@ export namespace Config {
|
||||||
command_list: z.string().optional().default("ctrl+p").describe("List available commands"),
|
command_list: z.string().optional().default("ctrl+p").describe("List available commands"),
|
||||||
agent_list: z.string().optional().default("<leader>a").describe("List agents"),
|
agent_list: z.string().optional().default("<leader>a").describe("List agents"),
|
||||||
agent_cycle: z.string().optional().default("tab").describe("Next agent"),
|
agent_cycle: z.string().optional().default("tab").describe("Next agent"),
|
||||||
agent_cycle_reverse: z.string().optional().default("shift+tab").describe("Previous agent"),
|
agent_cycle_reverse: z.string().optional().default("none").describe("Previous agent"),
|
||||||
|
permission_auto_accept_toggle: z
|
||||||
|
.string()
|
||||||
|
.optional()
|
||||||
|
.default("shift+tab")
|
||||||
|
.describe("Toggle auto-accept mode for permissions"),
|
||||||
variant_cycle: z.string().optional().default("ctrl+t").describe("Cycle model variants"),
|
variant_cycle: z.string().optional().default("ctrl+t").describe("Cycle model variants"),
|
||||||
input_clear: z.string().optional().default("ctrl+c").describe("Clear input field"),
|
input_clear: z.string().optional().default("ctrl+c").describe("Clear input field"),
|
||||||
input_paste: z.string().optional().default("ctrl+v").describe("Paste from clipboard"),
|
input_paste: z.string().optional().default("ctrl+v").describe("Paste from clipboard"),
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue