Compare commits
10 Commits
dev
...
feat/auto-
| Author | SHA1 | Date |
|---|---|---|
|
|
5792a80a8c | |
|
|
db039db7f5 | |
|
|
c1a3936b61 | |
|
|
a531f3f36d | |
|
|
bb3382311d | |
|
|
ad545d0cc9 | |
|
|
ac244b1458 | |
|
|
f202536b65 | |
|
|
405cc3f610 | |
|
|
878c1b8c2d |
|
|
@ -64,6 +64,7 @@ export namespace Agent {
|
||||||
question: "deny",
|
question: "deny",
|
||||||
plan_enter: "deny",
|
plan_enter: "deny",
|
||||||
plan_exit: "deny",
|
plan_exit: "deny",
|
||||||
|
edit: "ask",
|
||||||
// mirrors github.com/github/gitignore Node.gitignore pattern for .env files
|
// mirrors github.com/github/gitignore Node.gitignore pattern for .env files
|
||||||
read: {
|
read: {
|
||||||
"*": "allow",
|
"*": "allow",
|
||||||
|
|
|
||||||
|
|
@ -370,6 +370,11 @@ export const RunCommand = cmd({
|
||||||
action: "deny",
|
action: "deny",
|
||||||
pattern: "*",
|
pattern: "*",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
permission: "edit",
|
||||||
|
action: "allow",
|
||||||
|
pattern: "*",
|
||||||
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
function title() {
|
function title() {
|
||||||
|
|
|
||||||
|
|
@ -480,6 +480,7 @@ function App() {
|
||||||
{
|
{
|
||||||
title: "Toggle MCPs",
|
title: "Toggle MCPs",
|
||||||
value: "mcp.list",
|
value: "mcp.list",
|
||||||
|
search: "toggle mcps",
|
||||||
category: "Agent",
|
category: "Agent",
|
||||||
slash: {
|
slash: {
|
||||||
name: "mcps",
|
name: "mcps",
|
||||||
|
|
@ -555,8 +556,9 @@ function App() {
|
||||||
category: "System",
|
category: "System",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Toggle appearance",
|
title: mode() === "dark" ? "Light mode" : "Dark mode",
|
||||||
value: "theme.switch_mode",
|
value: "theme.switch_mode",
|
||||||
|
search: "toggle appearance",
|
||||||
onSelect: (dialog) => {
|
onSelect: (dialog) => {
|
||||||
setMode(mode() === "dark" ? "light" : "dark")
|
setMode(mode() === "dark" ? "light" : "dark")
|
||||||
dialog.clear()
|
dialog.clear()
|
||||||
|
|
@ -595,6 +597,7 @@ function App() {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Toggle debug panel",
|
title: "Toggle debug panel",
|
||||||
|
search: "toggle debug",
|
||||||
category: "System",
|
category: "System",
|
||||||
value: "app.debug",
|
value: "app.debug",
|
||||||
onSelect: (dialog) => {
|
onSelect: (dialog) => {
|
||||||
|
|
@ -604,6 +607,7 @@ function App() {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Toggle console",
|
title: "Toggle console",
|
||||||
|
search: "toggle console",
|
||||||
category: "System",
|
category: "System",
|
||||||
value: "app.console",
|
value: "app.console",
|
||||||
onSelect: (dialog) => {
|
onSelect: (dialog) => {
|
||||||
|
|
@ -644,6 +648,7 @@ function App() {
|
||||||
{
|
{
|
||||||
title: terminalTitleEnabled() ? "Disable terminal title" : "Enable terminal title",
|
title: terminalTitleEnabled() ? "Disable terminal title" : "Enable terminal title",
|
||||||
value: "terminal.title.toggle",
|
value: "terminal.title.toggle",
|
||||||
|
search: "toggle terminal title",
|
||||||
keybind: "terminal_title_toggle",
|
keybind: "terminal_title_toggle",
|
||||||
category: "System",
|
category: "System",
|
||||||
onSelect: (dialog) => {
|
onSelect: (dialog) => {
|
||||||
|
|
@ -659,6 +664,7 @@ function App() {
|
||||||
{
|
{
|
||||||
title: kv.get("animations_enabled", true) ? "Disable animations" : "Enable animations",
|
title: kv.get("animations_enabled", true) ? "Disable animations" : "Enable animations",
|
||||||
value: "app.toggle.animations",
|
value: "app.toggle.animations",
|
||||||
|
search: "toggle animations",
|
||||||
category: "System",
|
category: "System",
|
||||||
onSelect: (dialog) => {
|
onSelect: (dialog) => {
|
||||||
kv.set("animations_enabled", !kv.get("animations_enabled", true))
|
kv.set("animations_enabled", !kv.get("animations_enabled", true))
|
||||||
|
|
@ -668,6 +674,7 @@ function App() {
|
||||||
{
|
{
|
||||||
title: kv.get("diff_wrap_mode", "word") === "word" ? "Disable diff wrapping" : "Enable diff wrapping",
|
title: kv.get("diff_wrap_mode", "word") === "word" ? "Disable diff wrapping" : "Enable diff wrapping",
|
||||||
value: "app.toggle.diffwrap",
|
value: "app.toggle.diffwrap",
|
||||||
|
search: "toggle diff wrapping",
|
||||||
category: "System",
|
category: "System",
|
||||||
onSelect: (dialog) => {
|
onSelect: (dialog) => {
|
||||||
const current = kv.get("diff_wrap_mode", "word")
|
const current = kv.get("diff_wrap_mode", "word")
|
||||||
|
|
|
||||||
|
|
@ -79,6 +79,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<"none" | "edit">("permission_auto_accept", "edit")
|
||||||
|
|
||||||
function promptModelWarning() {
|
function promptModelWarning() {
|
||||||
toast.show({
|
toast.show({
|
||||||
|
|
@ -172,6 +173,17 @@ export function Prompt(props: PromptProps) {
|
||||||
|
|
||||||
command.register(() => {
|
command.register(() => {
|
||||||
return [
|
return [
|
||||||
|
{
|
||||||
|
title: autoaccept() === "none" ? "Enable autoedit" : "Disable autoedit",
|
||||||
|
value: "permission.auto_accept.toggle",
|
||||||
|
search: "toggle permissions",
|
||||||
|
keybind: "permission_auto_accept_toggle",
|
||||||
|
category: "Agent",
|
||||||
|
onSelect: (dialog) => {
|
||||||
|
setAutoaccept(() => (autoaccept() === "none" ? "edit" : "none"))
|
||||||
|
dialog.clear()
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: "Clear prompt",
|
title: "Clear prompt",
|
||||||
value: "prompt.clear",
|
value: "prompt.clear",
|
||||||
|
|
@ -1010,23 +1022,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() === "edit"}>
|
||||||
|
<text>
|
||||||
|
<span style={{ fg: theme.warning }}>autoedit</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"
|
||||||
|
|
@ -106,6 +107,8 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
|
||||||
})
|
})
|
||||||
|
|
||||||
const sdk = useSDK()
|
const sdk = useSDK()
|
||||||
|
const kv = useKV()
|
||||||
|
const [autoaccept] = kv.signal<"none" | "edit">("permission_auto_accept", "edit")
|
||||||
|
|
||||||
async function syncWorkspaces() {
|
async function syncWorkspaces() {
|
||||||
const result = await sdk.client.experimental.workspace.list().catch(() => undefined)
|
const result = await sdk.client.experimental.workspace.list().catch(() => undefined)
|
||||||
|
|
@ -136,6 +139,13 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
|
||||||
|
|
||||||
case "permission.asked": {
|
case "permission.asked": {
|
||||||
const request = event.properties
|
const request = event.properties
|
||||||
|
if (autoaccept() === "edit" && request.permission === "edit") {
|
||||||
|
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])
|
||||||
|
|
@ -451,6 +461,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)
|
||||||
|
|
|
||||||
|
|
@ -47,6 +47,7 @@ export function Home() {
|
||||||
{
|
{
|
||||||
title: tipsHidden() ? "Show tips" : "Hide tips",
|
title: tipsHidden() ? "Show tips" : "Hide tips",
|
||||||
value: "tips.toggle",
|
value: "tips.toggle",
|
||||||
|
search: "toggle tips",
|
||||||
keybind: "tips_toggle",
|
keybind: "tips_toggle",
|
||||||
category: "System",
|
category: "System",
|
||||||
onSelect: (dialog) => {
|
onSelect: (dialog) => {
|
||||||
|
|
|
||||||
|
|
@ -568,6 +568,7 @@ export function Session() {
|
||||||
{
|
{
|
||||||
title: sidebarVisible() ? "Hide sidebar" : "Show sidebar",
|
title: sidebarVisible() ? "Hide sidebar" : "Show sidebar",
|
||||||
value: "session.sidebar.toggle",
|
value: "session.sidebar.toggle",
|
||||||
|
search: "toggle sidebar",
|
||||||
keybind: "sidebar_toggle",
|
keybind: "sidebar_toggle",
|
||||||
category: "Session",
|
category: "Session",
|
||||||
onSelect: (dialog) => {
|
onSelect: (dialog) => {
|
||||||
|
|
@ -582,6 +583,7 @@ export function Session() {
|
||||||
{
|
{
|
||||||
title: conceal() ? "Disable code concealment" : "Enable code concealment",
|
title: conceal() ? "Disable code concealment" : "Enable code concealment",
|
||||||
value: "session.toggle.conceal",
|
value: "session.toggle.conceal",
|
||||||
|
search: "toggle code concealment",
|
||||||
keybind: "messages_toggle_conceal" as any,
|
keybind: "messages_toggle_conceal" as any,
|
||||||
category: "Session",
|
category: "Session",
|
||||||
onSelect: (dialog) => {
|
onSelect: (dialog) => {
|
||||||
|
|
@ -592,6 +594,7 @@ export function Session() {
|
||||||
{
|
{
|
||||||
title: showTimestamps() ? "Hide timestamps" : "Show timestamps",
|
title: showTimestamps() ? "Hide timestamps" : "Show timestamps",
|
||||||
value: "session.toggle.timestamps",
|
value: "session.toggle.timestamps",
|
||||||
|
search: "toggle timestamps",
|
||||||
category: "Session",
|
category: "Session",
|
||||||
slash: {
|
slash: {
|
||||||
name: "timestamps",
|
name: "timestamps",
|
||||||
|
|
@ -605,6 +608,7 @@ export function Session() {
|
||||||
{
|
{
|
||||||
title: showThinking() ? "Hide thinking" : "Show thinking",
|
title: showThinking() ? "Hide thinking" : "Show thinking",
|
||||||
value: "session.toggle.thinking",
|
value: "session.toggle.thinking",
|
||||||
|
search: "toggle thinking",
|
||||||
keybind: "display_thinking",
|
keybind: "display_thinking",
|
||||||
category: "Session",
|
category: "Session",
|
||||||
slash: {
|
slash: {
|
||||||
|
|
@ -619,6 +623,7 @@ export function Session() {
|
||||||
{
|
{
|
||||||
title: showDetails() ? "Hide tool details" : "Show tool details",
|
title: showDetails() ? "Hide tool details" : "Show tool details",
|
||||||
value: "session.toggle.actions",
|
value: "session.toggle.actions",
|
||||||
|
search: "toggle tool details",
|
||||||
keybind: "tool_details",
|
keybind: "tool_details",
|
||||||
category: "Session",
|
category: "Session",
|
||||||
onSelect: (dialog) => {
|
onSelect: (dialog) => {
|
||||||
|
|
@ -627,8 +632,9 @@ export function Session() {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Toggle session scrollbar",
|
title: showScrollbar() ? "Hide session scrollbar" : "Show session scrollbar",
|
||||||
value: "session.toggle.scrollbar",
|
value: "session.toggle.scrollbar",
|
||||||
|
search: "toggle session scrollbar",
|
||||||
keybind: "scrollbar_toggle",
|
keybind: "scrollbar_toggle",
|
||||||
category: "Session",
|
category: "Session",
|
||||||
onSelect: (dialog) => {
|
onSelect: (dialog) => {
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@ export interface DialogSelectOption<T = any> {
|
||||||
title: string
|
title: string
|
||||||
value: T
|
value: T
|
||||||
description?: string
|
description?: string
|
||||||
|
search?: string
|
||||||
footer?: JSX.Element | string
|
footer?: JSX.Element | string
|
||||||
category?: string
|
category?: string
|
||||||
disabled?: boolean
|
disabled?: boolean
|
||||||
|
|
@ -85,8 +86,8 @@ export function DialogSelect<T>(props: DialogSelectProps<T>) {
|
||||||
// users typically search by the item name, and not its category.
|
// users typically search by the item name, and not its category.
|
||||||
const result = fuzzysort
|
const result = fuzzysort
|
||||||
.go(needle, options, {
|
.go(needle, options, {
|
||||||
keys: ["title", "category"],
|
keys: ["title", "category", "search"],
|
||||||
scoreFn: (r) => r[0].score * 2 + r[1].score,
|
scoreFn: (r) => r[0].score * 2 + r[1].score + r[2].score,
|
||||||
})
|
})
|
||||||
.map((x) => x.obj)
|
.map((x) => x.obj)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -858,7 +858,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"),
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,7 @@ test("build agent has correct default properties", async () => {
|
||||||
expect(build).toBeDefined()
|
expect(build).toBeDefined()
|
||||||
expect(build?.mode).toBe("primary")
|
expect(build?.mode).toBe("primary")
|
||||||
expect(build?.native).toBe(true)
|
expect(build?.native).toBe(true)
|
||||||
expect(evalPerm(build, "edit")).toBe("allow")
|
expect(evalPerm(build, "edit")).toBe("ask")
|
||||||
expect(evalPerm(build, "bash")).toBe("allow")
|
expect(evalPerm(build, "bash")).toBe("allow")
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
@ -217,8 +217,8 @@ test("agent permission config merges with defaults", async () => {
|
||||||
expect(build).toBeDefined()
|
expect(build).toBeDefined()
|
||||||
// Specific pattern is denied
|
// Specific pattern is denied
|
||||||
expect(PermissionNext.evaluate("bash", "rm -rf *", build!.permission).action).toBe("deny")
|
expect(PermissionNext.evaluate("bash", "rm -rf *", build!.permission).action).toBe("deny")
|
||||||
// Edit still allowed
|
// Edit still asks (default behavior)
|
||||||
expect(evalPerm(build, "edit")).toBe("allow")
|
expect(evalPerm(build, "edit")).toBe("ask")
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue