fix(app): handle non-tool call permissions
This commit is contained in:
@@ -17,6 +17,7 @@ import { Tabs } from "@opencode-ai/ui/tabs"
|
|||||||
import { useCodeComponent } from "@opencode-ai/ui/context/code"
|
import { useCodeComponent } from "@opencode-ai/ui/context/code"
|
||||||
import { LineComment as LineCommentView, LineCommentEditor } from "@opencode-ai/ui/line-comment"
|
import { LineComment as LineCommentView, LineCommentEditor } from "@opencode-ai/ui/line-comment"
|
||||||
import { SessionTurn } from "@opencode-ai/ui/session-turn"
|
import { SessionTurn } from "@opencode-ai/ui/session-turn"
|
||||||
|
import { BasicTool } from "@opencode-ai/ui/basic-tool"
|
||||||
import { createAutoScroll } from "@opencode-ai/ui/hooks"
|
import { createAutoScroll } from "@opencode-ai/ui/hooks"
|
||||||
import { SessionReview } from "@opencode-ai/ui/session-review"
|
import { SessionReview } from "@opencode-ai/ui/session-review"
|
||||||
import { Mark } from "@opencode-ai/ui/logo"
|
import { Mark } from "@opencode-ai/ui/logo"
|
||||||
@@ -184,6 +185,40 @@ export default function Page() {
|
|||||||
const prompt = usePrompt()
|
const prompt = usePrompt()
|
||||||
const comments = useComments()
|
const comments = useComments()
|
||||||
const permission = usePermission()
|
const permission = usePermission()
|
||||||
|
|
||||||
|
const request = createMemo(() => {
|
||||||
|
const sessionID = params.id
|
||||||
|
if (!sessionID) return
|
||||||
|
const next = sync.data.permission[sessionID]?.[0]
|
||||||
|
if (!next) return
|
||||||
|
if (next.tool) return
|
||||||
|
return next
|
||||||
|
})
|
||||||
|
|
||||||
|
const [responding, setResponding] = createSignal(false)
|
||||||
|
|
||||||
|
createEffect(
|
||||||
|
on(
|
||||||
|
() => request()?.id,
|
||||||
|
() => setResponding(false),
|
||||||
|
{ defer: true },
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
const decide = (response: "once" | "always" | "reject") => {
|
||||||
|
const perm = request()
|
||||||
|
if (!perm) return
|
||||||
|
if (responding()) return
|
||||||
|
|
||||||
|
setResponding(true)
|
||||||
|
sdk.client.permission
|
||||||
|
.respond({ sessionID: perm.sessionID, permissionID: perm.id, response })
|
||||||
|
.catch((err: unknown) => {
|
||||||
|
const message = err instanceof Error ? err.message : String(err)
|
||||||
|
showToast({ title: language.t("common.requestFailed"), description: message })
|
||||||
|
})
|
||||||
|
.finally(() => setResponding(false))
|
||||||
|
}
|
||||||
const [pendingMessage, setPendingMessage] = createSignal<string | undefined>(undefined)
|
const [pendingMessage, setPendingMessage] = createSignal<string | undefined>(undefined)
|
||||||
const sessionKey = createMemo(() => `${params.dir}${params.id ? "/" + params.id : ""}`)
|
const sessionKey = createMemo(() => `${params.dir}${params.id ? "/" + params.id : ""}`)
|
||||||
const tabs = createMemo(() => layout.tabs(sessionKey))
|
const tabs = createMemo(() => layout.tabs(sessionKey))
|
||||||
@@ -730,7 +765,7 @@ export default function Page() {
|
|||||||
const sessionID = params.id
|
const sessionID = params.id
|
||||||
if (!sessionID) return
|
if (!sessionID) return
|
||||||
if (status()?.type !== "idle") {
|
if (status()?.type !== "idle") {
|
||||||
await sdk.client.session.abort({ sessionID }).catch(() => {})
|
await sdk.client.session.abort({ sessionID }).catch(() => { })
|
||||||
}
|
}
|
||||||
const revert = info()?.revert?.messageID
|
const revert = info()?.revert?.messageID
|
||||||
// Find the last user message that's not already reverted
|
// Find the last user message that's not already reverted
|
||||||
@@ -813,69 +848,69 @@ export default function Page() {
|
|||||||
},
|
},
|
||||||
...(sync.data.config.share !== "disabled"
|
...(sync.data.config.share !== "disabled"
|
||||||
? [
|
? [
|
||||||
{
|
{
|
||||||
id: "session.share",
|
id: "session.share",
|
||||||
title: "Share session",
|
title: "Share session",
|
||||||
description: "Share this session and copy the URL to clipboard",
|
description: "Share this session and copy the URL to clipboard",
|
||||||
category: "Session",
|
category: "Session",
|
||||||
slash: "share",
|
slash: "share",
|
||||||
disabled: !params.id || !!info()?.share?.url,
|
disabled: !params.id || !!info()?.share?.url,
|
||||||
onSelect: async () => {
|
onSelect: async () => {
|
||||||
if (!params.id) return
|
if (!params.id) return
|
||||||
await sdk.client.session
|
await sdk.client.session
|
||||||
.share({ sessionID: params.id })
|
.share({ sessionID: params.id })
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
navigator.clipboard.writeText(res.data!.share!.url).catch(() =>
|
navigator.clipboard.writeText(res.data!.share!.url).catch(() =>
|
||||||
showToast({
|
|
||||||
title: "Failed to copy URL to clipboard",
|
|
||||||
variant: "error",
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.then(() =>
|
|
||||||
showToast({
|
showToast({
|
||||||
title: "Session shared",
|
title: "Failed to copy URL to clipboard",
|
||||||
description: "Share URL copied to clipboard!",
|
|
||||||
variant: "success",
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.catch(() =>
|
|
||||||
showToast({
|
|
||||||
title: "Failed to share session",
|
|
||||||
description: "An error occurred while sharing the session",
|
|
||||||
variant: "error",
|
variant: "error",
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
},
|
})
|
||||||
|
.then(() =>
|
||||||
|
showToast({
|
||||||
|
title: "Session shared",
|
||||||
|
description: "Share URL copied to clipboard!",
|
||||||
|
variant: "success",
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.catch(() =>
|
||||||
|
showToast({
|
||||||
|
title: "Failed to share session",
|
||||||
|
description: "An error occurred while sharing the session",
|
||||||
|
variant: "error",
|
||||||
|
}),
|
||||||
|
)
|
||||||
},
|
},
|
||||||
{
|
},
|
||||||
id: "session.unshare",
|
{
|
||||||
title: "Unshare session",
|
id: "session.unshare",
|
||||||
description: "Stop sharing this session",
|
title: "Unshare session",
|
||||||
category: "Session",
|
description: "Stop sharing this session",
|
||||||
slash: "unshare",
|
category: "Session",
|
||||||
disabled: !params.id || !info()?.share?.url,
|
slash: "unshare",
|
||||||
onSelect: async () => {
|
disabled: !params.id || !info()?.share?.url,
|
||||||
if (!params.id) return
|
onSelect: async () => {
|
||||||
await sdk.client.session
|
if (!params.id) return
|
||||||
.unshare({ sessionID: params.id })
|
await sdk.client.session
|
||||||
.then(() =>
|
.unshare({ sessionID: params.id })
|
||||||
showToast({
|
.then(() =>
|
||||||
title: "Session unshared",
|
showToast({
|
||||||
description: "Session unshared successfully!",
|
title: "Session unshared",
|
||||||
variant: "success",
|
description: "Session unshared successfully!",
|
||||||
}),
|
variant: "success",
|
||||||
)
|
}),
|
||||||
.catch(() =>
|
)
|
||||||
showToast({
|
.catch(() =>
|
||||||
title: "Failed to unshare session",
|
showToast({
|
||||||
description: "An error occurred while unsharing the session",
|
title: "Failed to unshare session",
|
||||||
variant: "error",
|
description: "An error occurred while unsharing the session",
|
||||||
}),
|
variant: "error",
|
||||||
)
|
}),
|
||||||
},
|
)
|
||||||
},
|
},
|
||||||
]
|
},
|
||||||
|
]
|
||||||
: []),
|
: []),
|
||||||
])
|
])
|
||||||
|
|
||||||
@@ -1690,6 +1725,56 @@ export default function Page() {
|
|||||||
"md:max-w-200": !showTabs(),
|
"md:max-w-200": !showTabs(),
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
<Show when={request()} keyed>
|
||||||
|
{(perm) => (
|
||||||
|
<div data-component="tool-part-wrapper" data-permission="true" class="mb-3">
|
||||||
|
<BasicTool
|
||||||
|
icon="checklist"
|
||||||
|
locked
|
||||||
|
defaultOpen
|
||||||
|
trigger={{
|
||||||
|
title: language.t("notification.permission.title"),
|
||||||
|
subtitle:
|
||||||
|
perm.permission === "doom_loop"
|
||||||
|
? language.t("settings.permissions.tool.doom_loop.title")
|
||||||
|
: perm.permission,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Show when={perm.patterns.length > 0}>
|
||||||
|
<div class="flex flex-col gap-1 py-2 px-3 max-h-40 overflow-y-auto no-scrollbar">
|
||||||
|
<For each={perm.patterns}>
|
||||||
|
{(pattern) => <code class="text-12-regular text-text-base break-all">{pattern}</code>}
|
||||||
|
</For>
|
||||||
|
</div>
|
||||||
|
</Show>
|
||||||
|
<Show when={perm.permission === "doom_loop"}>
|
||||||
|
<div class="text-12-regular text-text-weak pb-2 px-3">
|
||||||
|
{language.t("settings.permissions.tool.doom_loop.description")}
|
||||||
|
</div>
|
||||||
|
</Show>
|
||||||
|
</BasicTool>
|
||||||
|
<div data-component="permission-prompt">
|
||||||
|
<div data-slot="permission-actions">
|
||||||
|
<Button variant="ghost" size="small" onClick={() => decide("reject")} disabled={responding()}>
|
||||||
|
{language.t("ui.permission.deny")}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="secondary"
|
||||||
|
size="small"
|
||||||
|
onClick={() => decide("always")}
|
||||||
|
disabled={responding()}
|
||||||
|
>
|
||||||
|
{language.t("ui.permission.allowAlways")}
|
||||||
|
</Button>
|
||||||
|
<Button variant="primary" size="small" onClick={() => decide("once")} disabled={responding()}>
|
||||||
|
{language.t("ui.permission.allowOnce")}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Show>
|
||||||
|
|
||||||
<Show
|
<Show
|
||||||
when={prompt.ready()}
|
when={prompt.ready()}
|
||||||
fallback={
|
fallback={
|
||||||
|
|||||||
Reference in New Issue
Block a user