fix(app): default auto-respond to false
parent
1cd77b1072
commit
78069369e2
|
|
@ -142,6 +142,17 @@ test("default dock shows prompt input", async ({ page, sdk, gotoSession }) => {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test("auto-accept toggle works before first submit", async ({ page, gotoSession }) => {
|
||||||
|
await gotoSession()
|
||||||
|
|
||||||
|
const button = page.locator('[data-action="prompt-permissions"]').first()
|
||||||
|
await expect(button).toBeVisible()
|
||||||
|
await expect(button).toHaveAttribute("aria-pressed", "false")
|
||||||
|
|
||||||
|
await setAutoAccept(page, true)
|
||||||
|
await setAutoAccept(page, false)
|
||||||
|
})
|
||||||
|
|
||||||
test("blocked question flow unblocks after submit", async ({ page, sdk, gotoSession }) => {
|
test("blocked question flow unblocks after submit", async ({ page, sdk, gotoSession }) => {
|
||||||
await withDockSession(sdk, "e2e composer dock question", async (session) => {
|
await withDockSession(sdk, "e2e composer dock question", async (session) => {
|
||||||
await withDockSeed(sdk, session.id, async () => {
|
await withDockSeed(sdk, session.id, async () => {
|
||||||
|
|
|
||||||
|
|
@ -243,6 +243,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||||
draggingType: "image" | "@mention" | null
|
draggingType: "image" | "@mention" | null
|
||||||
mode: "normal" | "shell"
|
mode: "normal" | "shell"
|
||||||
applyingHistory: boolean
|
applyingHistory: boolean
|
||||||
|
pendingAutoAccept: boolean
|
||||||
}>({
|
}>({
|
||||||
popover: null,
|
popover: null,
|
||||||
historyIndex: -1,
|
historyIndex: -1,
|
||||||
|
|
@ -251,6 +252,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||||
draggingType: null,
|
draggingType: null,
|
||||||
mode: "normal",
|
mode: "normal",
|
||||||
applyingHistory: false,
|
applyingHistory: false,
|
||||||
|
pendingAutoAccept: false,
|
||||||
})
|
})
|
||||||
|
|
||||||
const commentCount = createMemo(() => {
|
const commentCount = createMemo(() => {
|
||||||
|
|
@ -301,6 +303,12 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
createEffect(
|
||||||
|
on(sessionKey, () => {
|
||||||
|
setStore("pendingAutoAccept", false)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
const historyComments = () => {
|
const historyComments = () => {
|
||||||
const byID = new Map(comments.all().map((item) => [`${item.file}\n${item.id}`, item] as const))
|
const byID = new Map(comments.all().map((item) => [`${item.file}\n${item.id}`, item] as const))
|
||||||
return prompt.context.items().flatMap((item) => {
|
return prompt.context.items().flatMap((item) => {
|
||||||
|
|
@ -947,10 +955,18 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||||
readClipboardImage: platform.readClipboardImage,
|
readClipboardImage: platform.readClipboardImage,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const variants = createMemo(() => ["default", ...local.model.variant.list()])
|
||||||
|
const accepting = createMemo(() => {
|
||||||
|
const id = params.id
|
||||||
|
if (!id) return store.pendingAutoAccept
|
||||||
|
return permission.isAutoAccepting(id, sdk.directory)
|
||||||
|
})
|
||||||
|
|
||||||
const { abort, handleSubmit } = createPromptSubmit({
|
const { abort, handleSubmit } = createPromptSubmit({
|
||||||
info,
|
info,
|
||||||
imageAttachments,
|
imageAttachments,
|
||||||
commentCount,
|
commentCount,
|
||||||
|
autoAccept: () => accepting(),
|
||||||
mode: () => store.mode,
|
mode: () => store.mode,
|
||||||
working,
|
working,
|
||||||
editor: () => editorRef,
|
editor: () => editorRef,
|
||||||
|
|
@ -1115,13 +1131,6 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const variants = createMemo(() => ["default", ...local.model.variant.list()])
|
|
||||||
const accepting = createMemo(() => {
|
|
||||||
const id = params.id
|
|
||||||
if (!id) return false
|
|
||||||
return permission.isAutoAccepting(id, sdk.directory)
|
|
||||||
})
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class="relative size-full _max-h-[320px] flex flex-col gap-0">
|
<div class="relative size-full _max-h-[320px] flex flex-col gap-0">
|
||||||
<PromptPopover
|
<PromptPopover
|
||||||
|
|
@ -1313,9 +1322,11 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||||
<Button
|
<Button
|
||||||
data-action="prompt-permissions"
|
data-action="prompt-permissions"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
disabled={!params.id}
|
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (!params.id) return
|
if (!params.id) {
|
||||||
|
setStore("pendingAutoAccept", (value) => !value)
|
||||||
|
return
|
||||||
|
}
|
||||||
permission.toggleAutoAccept(params.id, sdk.directory)
|
permission.toggleAutoAccept(params.id, sdk.directory)
|
||||||
}}
|
}}
|
||||||
classList={{
|
classList={{
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ let createPromptSubmit: typeof import("./submit").createPromptSubmit
|
||||||
|
|
||||||
const createdClients: string[] = []
|
const createdClients: string[] = []
|
||||||
const createdSessions: string[] = []
|
const createdSessions: string[] = []
|
||||||
|
const enabledAutoAccept: Array<{ sessionID: string; directory: string }> = []
|
||||||
const sentShell: string[] = []
|
const sentShell: string[] = []
|
||||||
const syncedDirectories: string[] = []
|
const syncedDirectories: string[] = []
|
||||||
|
|
||||||
|
|
@ -69,6 +70,14 @@ beforeAll(async () => {
|
||||||
}),
|
}),
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
mock.module("@/context/permission", () => ({
|
||||||
|
usePermission: () => ({
|
||||||
|
enableAutoAccept(sessionID: string, directory: string) {
|
||||||
|
enabledAutoAccept.push({ sessionID, directory })
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
}))
|
||||||
|
|
||||||
mock.module("@/context/prompt", () => ({
|
mock.module("@/context/prompt", () => ({
|
||||||
usePrompt: () => ({
|
usePrompt: () => ({
|
||||||
current: () => promptValue,
|
current: () => promptValue,
|
||||||
|
|
@ -145,6 +154,7 @@ beforeAll(async () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
createdClients.length = 0
|
createdClients.length = 0
|
||||||
createdSessions.length = 0
|
createdSessions.length = 0
|
||||||
|
enabledAutoAccept.length = 0
|
||||||
sentShell.length = 0
|
sentShell.length = 0
|
||||||
syncedDirectories.length = 0
|
syncedDirectories.length = 0
|
||||||
selected = "/repo/worktree-a"
|
selected = "/repo/worktree-a"
|
||||||
|
|
@ -156,6 +166,7 @@ describe("prompt submit worktree selection", () => {
|
||||||
info: () => undefined,
|
info: () => undefined,
|
||||||
imageAttachments: () => [],
|
imageAttachments: () => [],
|
||||||
commentCount: () => 0,
|
commentCount: () => 0,
|
||||||
|
autoAccept: () => false,
|
||||||
mode: () => "shell",
|
mode: () => "shell",
|
||||||
working: () => false,
|
working: () => false,
|
||||||
editor: () => undefined,
|
editor: () => undefined,
|
||||||
|
|
@ -181,4 +192,31 @@ describe("prompt submit worktree selection", () => {
|
||||||
expect(sentShell).toEqual(["/repo/worktree-a", "/repo/worktree-b"])
|
expect(sentShell).toEqual(["/repo/worktree-a", "/repo/worktree-b"])
|
||||||
expect(syncedDirectories).toEqual(["/repo/worktree-a", "/repo/worktree-b"])
|
expect(syncedDirectories).toEqual(["/repo/worktree-a", "/repo/worktree-b"])
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test("applies auto-accept to newly created sessions", async () => {
|
||||||
|
const submit = createPromptSubmit({
|
||||||
|
info: () => undefined,
|
||||||
|
imageAttachments: () => [],
|
||||||
|
commentCount: () => 0,
|
||||||
|
autoAccept: () => true,
|
||||||
|
mode: () => "shell",
|
||||||
|
working: () => false,
|
||||||
|
editor: () => undefined,
|
||||||
|
queueScroll: () => undefined,
|
||||||
|
promptLength: (value) => value.reduce((sum, part) => sum + ("content" in part ? part.content.length : 0), 0),
|
||||||
|
addToHistory: () => undefined,
|
||||||
|
resetHistoryNavigation: () => undefined,
|
||||||
|
setMode: () => undefined,
|
||||||
|
setPopover: () => undefined,
|
||||||
|
newSessionWorktree: () => selected,
|
||||||
|
onNewSessionWorktreeReset: () => undefined,
|
||||||
|
onSubmit: () => undefined,
|
||||||
|
})
|
||||||
|
|
||||||
|
const event = { preventDefault: () => undefined } as unknown as Event
|
||||||
|
|
||||||
|
await submit.handleSubmit(event)
|
||||||
|
|
||||||
|
expect(enabledAutoAccept).toEqual([{ sessionID: "session-1", directory: "/repo/worktree-a" }])
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import { useGlobalSync } from "@/context/global-sync"
|
||||||
import { useLanguage } from "@/context/language"
|
import { useLanguage } from "@/context/language"
|
||||||
import { useLayout } from "@/context/layout"
|
import { useLayout } from "@/context/layout"
|
||||||
import { useLocal } from "@/context/local"
|
import { useLocal } from "@/context/local"
|
||||||
|
import { usePermission } from "@/context/permission"
|
||||||
import { type ImageAttachmentPart, type Prompt, usePrompt } from "@/context/prompt"
|
import { type ImageAttachmentPart, type Prompt, usePrompt } from "@/context/prompt"
|
||||||
import { useSDK } from "@/context/sdk"
|
import { useSDK } from "@/context/sdk"
|
||||||
import { useSync } from "@/context/sync"
|
import { useSync } from "@/context/sync"
|
||||||
|
|
@ -27,6 +28,7 @@ type PromptSubmitInput = {
|
||||||
info: Accessor<{ id: string } | undefined>
|
info: Accessor<{ id: string } | undefined>
|
||||||
imageAttachments: Accessor<ImageAttachmentPart[]>
|
imageAttachments: Accessor<ImageAttachmentPart[]>
|
||||||
commentCount: Accessor<number>
|
commentCount: Accessor<number>
|
||||||
|
autoAccept: Accessor<boolean>
|
||||||
mode: Accessor<"normal" | "shell">
|
mode: Accessor<"normal" | "shell">
|
||||||
working: Accessor<boolean>
|
working: Accessor<boolean>
|
||||||
editor: () => HTMLDivElement | undefined
|
editor: () => HTMLDivElement | undefined
|
||||||
|
|
@ -56,6 +58,7 @@ export function createPromptSubmit(input: PromptSubmitInput) {
|
||||||
const sync = useSync()
|
const sync = useSync()
|
||||||
const globalSync = useGlobalSync()
|
const globalSync = useGlobalSync()
|
||||||
const local = useLocal()
|
const local = useLocal()
|
||||||
|
const permission = usePermission()
|
||||||
const prompt = usePrompt()
|
const prompt = usePrompt()
|
||||||
const layout = useLayout()
|
const layout = useLayout()
|
||||||
const language = useLanguage()
|
const language = useLanguage()
|
||||||
|
|
@ -140,6 +143,7 @@ export function createPromptSubmit(input: PromptSubmitInput) {
|
||||||
|
|
||||||
const projectDirectory = sdk.directory
|
const projectDirectory = sdk.directory
|
||||||
const isNewSession = !params.id
|
const isNewSession = !params.id
|
||||||
|
const shouldAutoAccept = isNewSession && input.autoAccept()
|
||||||
const worktreeSelection = input.newSessionWorktree?.() || "main"
|
const worktreeSelection = input.newSessionWorktree?.() || "main"
|
||||||
|
|
||||||
let sessionDirectory = projectDirectory
|
let sessionDirectory = projectDirectory
|
||||||
|
|
@ -197,6 +201,7 @@ export function createPromptSubmit(input: PromptSubmitInput) {
|
||||||
return undefined
|
return undefined
|
||||||
})
|
})
|
||||||
if (session) {
|
if (session) {
|
||||||
|
if (shouldAutoAccept) permission.enableAutoAccept(session.id, sessionDirectory)
|
||||||
layout.handoff.setTabs(base64Encode(sessionDirectory), session.id)
|
layout.handoff.setTabs(base64Encode(sessionDirectory), session.id)
|
||||||
navigate(`/${base64Encode(sessionDirectory)}/session/${session.id}`)
|
navigate(`/${base64Encode(sessionDirectory)}/session/${session.id}`)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -31,13 +31,13 @@ describe("autoRespondsPermission", () => {
|
||||||
expect(autoRespondsPermission({ root: true }, sessions, permission("child"), "/tmp/project")).toBe(true)
|
expect(autoRespondsPermission({ root: true }, sessions, permission("child"), "/tmp/project")).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
test("defaults to auto-accept when no lineage override exists", () => {
|
test("defaults to requiring approval when no lineage override exists", () => {
|
||||||
const sessions = [session({ id: "root" }), session({ id: "child", parentID: "root" }), session({ id: "other" })]
|
const sessions = [session({ id: "root" }), session({ id: "child", parentID: "root" }), session({ id: "other" })]
|
||||||
const autoAccept = {
|
const autoAccept = {
|
||||||
other: true,
|
other: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
expect(autoRespondsPermission(autoAccept, sessions, permission("child"), "/tmp/project")).toBe(true)
|
expect(autoRespondsPermission(autoAccept, sessions, permission("child"), "/tmp/project")).toBe(false)
|
||||||
})
|
})
|
||||||
|
|
||||||
test("inherits a parent session's false override", () => {
|
test("inherits a parent session's false override", () => {
|
||||||
|
|
|
||||||
|
|
@ -37,5 +37,5 @@ export function autoRespondsPermission(
|
||||||
const value = sessionLineage(session, permission.sessionID)
|
const value = sessionLineage(session, permission.sessionID)
|
||||||
.map((id) => accepted(autoAccept, id, directory))
|
.map((id) => accepted(autoAccept, id, directory))
|
||||||
.find((item): item is boolean => item !== undefined)
|
.find((item): item is boolean => item !== undefined)
|
||||||
return value ?? true
|
return value ?? false
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue