diff --git a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/composite-webview/bash-with-permission-chromium-linux.png b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/composite-webview/bash-with-permission-chromium-linux.png index cb4ea8c6ef7..ba3d7a96c7d 100644 --- a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/composite-webview/bash-with-permission-chromium-linux.png +++ b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/composite-webview/bash-with-permission-chromium-linux.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b6cbba3354994879f21489539615bbeeb71cb944980c642d502fdab3c4500e73 -size 11000 +oid sha256:59b9045133b989b85e3748e4fe509b317e21761280f26b3ee46f837bccf3a1ad +size 14489 diff --git a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/composite-webview/glob-with-permission-chromium-linux.png b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/composite-webview/glob-with-permission-chromium-linux.png index eb3edfc7392..d59de67ff10 100644 --- a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/composite-webview/glob-with-permission-chromium-linux.png +++ b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/composite-webview/glob-with-permission-chromium-linux.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bcb19f99cbd4cbaf403e2224139ff47318c2618457975d4f957b481962031836 -size 9735 +oid sha256:ce43eb9d6415fdb082ff03ab15080219c6275f7ae1bd09689f7d020a5511347c +size 15274 diff --git a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/composite-webview/permission-dock-chromium-linux.png b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/composite-webview/permission-dock-chromium-linux.png index 2cc64b57e70..6555c17a5a4 100644 --- a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/composite-webview/permission-dock-chromium-linux.png +++ b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/composite-webview/permission-dock-chromium-linux.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2a225a66c7981745089a46f20841d5f214d23d905b8b31194cfba9f278a3763d -size 10965 +oid sha256:cae28aa54297014cb01b26120197b3bcab0b0a250c21d90c1050de3e09ee622b +size 10743 diff --git a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/composite-webview/permission-dock-todo-chromium-linux.png b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/composite-webview/permission-dock-todo-chromium-linux.png new file mode 100644 index 00000000000..bce78534484 --- /dev/null +++ b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/composite-webview/permission-dock-todo-chromium-linux.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f6fcd2e69976f2c9a02b24e721e96638ec20a6711d3853d0795e2913315ca0b5 +size 14341 diff --git a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/composite-webview/permission-dock-write-chromium-linux.png b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/composite-webview/permission-dock-write-chromium-linux.png new file mode 100644 index 00000000000..83a8e0c1fe7 --- /dev/null +++ b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/composite-webview/permission-dock-write-chromium-linux.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8ed8885168e17a44fee3cd34e32143a6514fce09c3cd143dcf3be3088164d696 +size 16827 diff --git a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/composite-webview/question-above-chatbox-chromium-linux.png b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/composite-webview/question-above-chatbox-chromium-linux.png new file mode 100644 index 00000000000..f83f906ecac --- /dev/null +++ b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/composite-webview/question-above-chatbox-chromium-linux.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:83076ce468f046d5a884d8abf38189718641803cc966cae255e75e238894ae72 +size 30673 diff --git a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/composite-webview/todo-write-with-permission-chromium-linux.png b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/composite-webview/todo-write-with-permission-chromium-linux.png index 16210893824..9c5e298920f 100644 --- a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/composite-webview/todo-write-with-permission-chromium-linux.png +++ b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/composite-webview/todo-write-with-permission-chromium-linux.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9777ee1cb00c3632e71c64d2518f86aab1e4199d52e95b36c0d6344ac042994a -size 14844 +oid sha256:26b18e59c4eccd851e4cb11f62f4b7b09e0fce56e9922b1ec51a94a4d9aeae67 +size 17664 diff --git a/packages/kilo-vscode/webview-ui/src/components/chat/AssistantMessage.tsx b/packages/kilo-vscode/webview-ui/src/components/chat/AssistantMessage.tsx index c4a9fcc9766..a948caf68dd 100644 --- a/packages/kilo-vscode/webview-ui/src/components/chat/AssistantMessage.tsx +++ b/packages/kilo-vscode/webview-ui/src/components/chat/AssistantMessage.tsx @@ -4,14 +4,13 @@ * Unlike the upstream AssistantParts, this renders each read/glob/grep/list tool * individually for maximum verbosity in the VS Code sidebar context. * - * Permissions and questions with a tool context are rendered inline with their - * tool call rather than in the bottom dock. + * Questions with a tool context are rendered inline with their tool call. + * Permissions are rendered in the bottom dock (PermissionDock). */ import { Component, For, Show, createMemo } from "solid-js" import { Dynamic } from "solid-js/web" import { Part, PART_MAPPING, ToolRegistry } from "@kilocode/kilo-ui/message-part" -import { Button } from "@kilocode/kilo-ui/button" import type { AssistantMessage as SDKAssistantMessage, Part as SDKPart, @@ -20,24 +19,20 @@ import type { } from "@kilocode/sdk/v2" import { useData } from "@kilocode/kilo-ui/context/data" import { useSession } from "../../context/session" -import { useLanguage } from "../../context/language" import { QuestionDock } from "./QuestionDock" // Tools that the upstream message-part renderer suppresses (returns null for). -// We render these ourselves via ToolRegistry when they have a pending permission -// or when they complete, so the user can see what the AI set up. -// We also use this set in ChatView to know NOT to block the prompt input for -// these tool permissions (since they're shown inline in the message stream). +// We render these ourselves via ToolRegistry when they complete, +// so the user can see what the AI set up. export const UPSTREAM_SUPPRESSED_TOOLS = new Set(["todowrite", "todoread"]) -function isRenderable(part: SDKPart, pendingPermissionCallIDs: Set): boolean { +function isRenderable(part: SDKPart): boolean { if (part.type === "tool") { const tool = (part as SDKPart & { tool: string }).tool const state = (part as SDKPart & { state: { status: string } }).state if (UPSTREAM_SUPPRESSED_TOOLS.has(tool)) { - const callID = (part as SDKPart & { callID: string }).callID - // Show todo parts when waiting for permission (inline) or when completed (to show what happened) - return pendingPermissionCallIDs.has(callID) || state.status === "completed" + // Show todo parts only when completed (permissions are now in the dock) + return state.status === "completed" } if (tool === "question" && (state.status === "pending" || state.status === "running")) return false return true @@ -76,52 +71,29 @@ function TodoToolCard(props: { part: ToolPart }) { export const AssistantMessage: Component = (props) => { const data = useData() const session = useSession() - const language = useLanguage() - const id = () => session.currentSessionID() - const permissions = () => session.permissions().filter((p) => p.sessionID === id() && p.tool) - const questions = () => session.questions().filter((q) => q.sessionID === id() && q.tool) - - const pendingPermissionCallIDs = createMemo(() => { - const ids = new Set() - for (const p of permissions()) { - if (p.tool?.messageID === props.message.id) ids.add(p.tool.callID) - } - return ids - }) + const questions = () => session.questions().filter((q) => q.sessionID === session.currentSessionID() && q.tool) const parts = createMemo(() => { const stored = data.store.part?.[props.message.id] if (!stored) return [] - return (stored as SDKPart[]).filter((part) => isRenderable(part, pendingPermissionCallIDs())) + return (stored as SDKPart[]).filter((part) => isRenderable(part)) }) - const permissionForPart = (part: SDKPart) => { - if (part.type !== "tool") return undefined - const callID = (part as SDKPart & { callID: string }).callID - return permissions().find((p) => p.tool!.callID === callID && p.tool!.messageID === props.message.id) - } - // Questions linked to this message (rendered after the last part) const questionForMessage = () => questions().find((q) => q.tool!.messageID === props.message.id) - const decide = (permissionId: string, response: "once" | "always" | "reject") => { - if (session.respondingPermissions().has(permissionId)) return - session.respondToPermission(permissionId, response) - } - return ( <> {(part) => { - const perm = () => permissionForPart(part) // Upstream PART_MAPPING["tool"] returns null for todowrite/todoread, // so we detect them here and render via ToolRegistry directly. const isUpstreamSuppressed = part.type === "tool" && UPSTREAM_SUPPRESSED_TOOLS.has((part as SDKPart & { tool: string }).tool) return ( -
+
= (props) => { > - - {(p) => { - const isTodoPerm = UPSTREAM_SUPPRESSED_TOOLS.has(p.toolName) - // For todo tools: show the friendly operation description instead of raw patterns like '*' - // For other tools: show patterns only if they're meaningful (not just '*') - const meaningfulPatterns = p.patterns.filter((pat) => pat !== "*") - return ( -
e.stopPropagation()}> - 0}> -
- - {(pattern) => {pattern}} - -
-
- -

- {p.toolName === "todowrite" - ? language.t("settings.permissions.tool.todowrite.description") - : language.t("settings.permissions.tool.todoread.description")} -

-
-
- - - -
-

{language.t("ui.permission.sessionHint")}

-
- ) - }} -
) diff --git a/packages/kilo-vscode/webview-ui/src/components/chat/ChatView.tsx b/packages/kilo-vscode/webview-ui/src/components/chat/ChatView.tsx index 75db890f034..740d5683b65 100644 --- a/packages/kilo-vscode/webview-ui/src/components/chat/ChatView.tsx +++ b/packages/kilo-vscode/webview-ui/src/components/chat/ChatView.tsx @@ -3,20 +3,18 @@ * Main chat container that combines all chat components */ -import { Component, For, Show, createEffect, on, onCleanup, onMount } from "solid-js" +import { Component, Show, createEffect, on, onCleanup, onMount } from "solid-js" import { Button } from "@kilocode/kilo-ui/button" import { Icon } from "@kilocode/kilo-ui/icon" -import { BasicTool } from "@kilocode/kilo-ui/basic-tool" import { TaskHeader } from "./TaskHeader" import { MessageList } from "./MessageList" import { PromptInput } from "./PromptInput" import { QuestionDock } from "./QuestionDock" -import { UPSTREAM_SUPPRESSED_TOOLS } from "./AssistantMessage" +import { PermissionDock } from "./PermissionDock" import { useSession } from "../../context/session" import { useVSCode } from "../../context/vscode" import { useLanguage } from "../../context/language" import { useWorktreeMode } from "../../context/worktree-mode" -import type { PermissionRequest } from "../../types/messages" interface ChatViewProps { onSelectSession?: (id: string) => void @@ -41,16 +39,14 @@ export const ChatView: Component = (props) => { const allPermissions = () => session.permissions() const allQuestions = () => session.questions() - // Bottom-dock permission: prefer current-session non-tool permissions, + // Bottom-dock permission: prefer current-session permissions, // then fall back to any pending permission (including child sessions). const questionRequest = () => - allQuestions().find((q) => q.sessionID === id() && !q.tool) ?? allQuestions().find((q) => !q.tool) ?? allQuestions()[0] - const permissionRequest = () => - allPermissions().find((p) => p.sessionID === id() && !p.tool) ?? allPermissions().find((p) => !p.tool) ?? allPermissions()[0] - // Only block the prompt when there's a non-inline permission or any question pending - // (todo permissions are shown inline, not in the bottom dock) - const isInlinePermission = (p: PermissionRequest) => p.tool && UPSTREAM_SUPPRESSED_TOOLS.has(p.toolName) - const blocked = () => allPermissions().some((p) => !isInlinePermission(p)) || allQuestions().length > 0 + allQuestions().find((q) => q.sessionID === id() && !q.tool) ?? + allQuestions().find((q) => !q.tool) ?? + allQuestions()[0] + const permissionRequest = () => allPermissions().find((p) => p.sessionID === id()) ?? allPermissions()[0] + const blocked = () => allPermissions().length > 0 || allQuestions().length > 0 // When a bottom-dock permission/question disappears while the session is busy, // the scroll container grows taller. Dispatch a custom event so MessageList can @@ -95,60 +91,13 @@ export const ChatView: Component = (props) => { {(req) => } - {(perm) => { - const fromChild = () => perm.sessionID !== id() - const subtitle = () => fromChild() ? `${perm.toolName} (subagent)` : perm.toolName - return ( -
- - 0}> -
- - {(pattern) => {pattern}} - -
-
-
-
-
- - - -
-

{language.t("ui.permission.sessionHint")}

-
-
- ) - }} + {(perm) => ( + + )}
diff --git a/packages/kilo-vscode/webview-ui/src/components/chat/PermissionDock.tsx b/packages/kilo-vscode/webview-ui/src/components/chat/PermissionDock.tsx new file mode 100644 index 00000000000..39b42e1c9c2 --- /dev/null +++ b/packages/kilo-vscode/webview-ui/src/components/chat/PermissionDock.tsx @@ -0,0 +1,90 @@ +/** + * PermissionDock component + * Displays permission requests from the AI assistant in the dock above the prompt input. + * Uses kilo-ui's DockPrompt component for proper surface styling. + * Modeled after the app's SessionPermissionDock. + */ + +import { Component, For, Show } from "solid-js" +import { Button } from "@kilocode/kilo-ui/button" +import { DockPrompt } from "@kilocode/kilo-ui/dock-prompt" +import { Icon } from "@kilocode/kilo-ui/icon" +import { useSession } from "../../context/session" +import { useLanguage } from "../../context/language" +import type { PermissionRequest } from "../../types/messages" + +export const PermissionDock: Component<{ + request: PermissionRequest + responding: boolean + onDecide: (response: "once" | "always" | "reject") => void +}> = (props) => { + const session = useSession() + const language = useLanguage() + + const fromChild = () => props.request.sessionID !== session.currentSessionID() + + const toolDescription = () => { + const key = `settings.permissions.tool.${props.request.toolName}.description` + const value = language.t(key as Parameters[0]) + if (value === key) return "" + return value + } + + const subtitle = () => (fromChild() ? `${props.request.toolName} (subagent)` : props.request.toolName) + + return ( + + + + +
+ {language.t("notification.permission.title")} + {subtitle()} +
+
+ } + footer={ + <> +
+
+ + + +
+ + } + > + +
+
+
+ + 0}> +
+
+
+ + ) +} diff --git a/packages/kilo-vscode/webview-ui/src/components/chat/TaskToolExpanded.tsx b/packages/kilo-vscode/webview-ui/src/components/chat/TaskToolExpanded.tsx index 11109816071..f5d60efcbd1 100644 --- a/packages/kilo-vscode/webview-ui/src/components/chat/TaskToolExpanded.tsx +++ b/packages/kilo-vscode/webview-ui/src/components/chat/TaskToolExpanded.tsx @@ -7,10 +7,9 @@ * Call registerExpandedTaskTool() once at app startup to activate. */ -import { Component, createEffect, createMemo, For, Match, Show, Switch } from "solid-js" +import { Component, createEffect, createMemo, For, Show } from "solid-js" import { ToolRegistry, ToolProps, getToolInfo } from "@kilocode/kilo-ui/message-part" import { BasicTool } from "@kilocode/kilo-ui/basic-tool" -import { Button } from "@kilocode/kilo-ui/button" import { Icon } from "@kilocode/kilo-ui/icon" import { useData } from "@kilocode/kilo-ui/context/data" import { useI18n } from "@kilocode/kilo-ui/context/i18n" @@ -64,36 +63,6 @@ const TaskToolRenderer: Component = (props) => { return getSessionToolParts(data.store, id) }) - // Permission from child session - const childPermission = createMemo(() => { - const id = childSessionId() - if (!id) return undefined - const perms = data.store.permission?.[id] as unknown[] - return (perms as PermissionRequest[] | undefined)?.[0] - }) - - const childToolPart = createMemo(() => { - const perm = childPermission() - if (!perm || !perm.tool) return undefined - const id = childSessionId() - if (!id) return undefined - const messages = (data.store.message?.[id] as SDKMessage[] | undefined) ?? [] - const msg = [...messages].reverse().find((m: SDKMessage) => m.id === perm.tool!.messageID) - if (!msg) return undefined - const parts = (data.store.part?.[msg.id] as ToolPart[] | undefined) ?? [] - return parts.find((p) => p.type === "tool" && p.callID === perm.tool!.callID) as ToolPart | undefined - }) - - const respond = (response: "once" | "always" | "reject") => { - const perm = childPermission() - if (!perm || !data.respondToPermission) return - data.respondToPermission({ - sessionID: perm.sessionID, - permissionID: perm.id, - response, - }) - } - const autoScroll = createAutoScroll({ working: running, overflowAnchor: "auto", @@ -112,100 +81,40 @@ const TaskToolRenderer: Component = (props) => {
) - // Render the child tool card that triggered a permission (shown when permission is pending) - const renderChildToolCard = () => { - const part = childToolPart() - if (!part) return null - const render = ToolRegistry.render(part.tool) - if (!render) return null - const metadata = (part.state as { metadata?: Record })?.metadata ?? {} - const input = part.state?.input ?? {} - const output = (part.state as { output?: string })?.output - return render({ - input: input as Record, - tool: part.tool, - metadata: metadata as Record, - output, - status: part.state.status, - defaultOpen: true, - }) - } - return ( -
- - {/* Branch 1: pending permission from child session */} - - <> - }> - {renderChildToolCard()} - -
-
- - - -
-

{i18n.t("ui.permission.sessionHint")}

-
- -
- - {/* Branch 2: normal — compact list of child tool calls */} - - -
-
- - {(item) => { - const info = createMemo(() => getToolInfo(item.tool, item.state?.input)) - const subtitle = createMemo(() => { - if (info().subtitle) return info().subtitle - const state = item.state as { status: string; title?: string } - if (state.status === "completed" || state.status === "running") { - return state.title - } - return undefined - }) - return ( -
- - {info().title} - - {subtitle()} - -
- ) - }} -
-
-
-
-
-
+
+ +
+
+ + {(item) => { + const info = createMemo(() => getToolInfo(item.tool, item.state?.input)) + const subtitle = createMemo(() => { + if (info().subtitle) return info().subtitle + const state = item.state as { status: string; title?: string } + if (state.status === "completed" || state.status === "running") { + return state.title + } + return undefined + }) + return ( +
+ + {info().title} + + {subtitle()} + +
+ ) + }} +
+
+
+
) } -// Minimal PermissionRequest shape we need -interface PermissionRequest { - id: string - sessionID: string - tool?: { messageID: string; callID: string } - metadata?: Record -} - /** * Override the upstream "task" tool registration with the v1.0.25-style renderer. * Must be called once at app startup. diff --git a/packages/kilo-vscode/webview-ui/src/stories/composite.stories.tsx b/packages/kilo-vscode/webview-ui/src/stories/composite.stories.tsx index ab71bd221d8..e2b6d265090 100644 --- a/packages/kilo-vscode/webview-ui/src/stories/composite.stories.tsx +++ b/packages/kilo-vscode/webview-ui/src/stories/composite.stories.tsx @@ -8,18 +8,10 @@ */ import type { Meta, StoryObj } from "storybook-solidjs-vite" -import { For, Show } from "solid-js" -import { Part } from "@kilocode/kilo-ui/message-part" -import { BasicTool } from "@kilocode/kilo-ui/basic-tool" -import { Button } from "@kilocode/kilo-ui/button" -import type { - AssistantMessage as SDKAssistantMessage, - TextPart, - ToolPart, - Message as SDKMessage, -} from "@kilocode/sdk/v2" +import type { AssistantMessage as SDKAssistantMessage, TextPart, ToolPart } from "@kilocode/sdk/v2" import { StoryProviders, defaultMockData, mockSessionValue } from "./StoryProviders" import { AssistantMessage } from "../components/chat/AssistantMessage" +import { ChatView } from "../components/chat/ChatView" import { registerVscodeToolOverrides } from "../components/chat/VscodeToolOverrides" import { SessionContext } from "../context/session" import type { PermissionRequest, QuestionRequest } from "../types/messages" @@ -336,80 +328,120 @@ export default meta type Story = StoryObj // --------------------------------------------------------------------------- -// 1. Tool with inline permission (glob) +// 1. Permission dock — glob (above chatbox) // --------------------------------------------------------------------------- export const GlobWithPermission: Story = { - name: "Glob + Inline Permission", + name: "Permission Dock — glob above chatbox", render: () => { const perms = [globPermission] - const data = dataWith([globPending], perms) + const session = { + ...mockSessionValue({ id: SESSION_ID, status: "busy", permissions: perms }), + messages: () => [{ id: "msg-001" }] as any[], + } return ( - - + + +
+ +
+
) }, } // --------------------------------------------------------------------------- -// 2. Tool with inline permission (bash) +// 2. Permission dock — bash (above chatbox) // --------------------------------------------------------------------------- export const BashWithPermission: Story = { - name: "Bash + Inline Permission", + name: "Permission Dock — bash above chatbox", render: () => { const perms = [bashPermission] - const data = dataWith([bashPending], perms) + const session = { + ...mockSessionValue({ id: SESSION_ID, status: "busy", permissions: perms }), + messages: () => [{ id: "msg-001" }] as any[], + } return ( - - + + +
+ +
+
) }, } // --------------------------------------------------------------------------- -// 3. Permission dock (non-tool bottom prompt) +// 3. Permission dock — write with file patterns (above chatbox) // --------------------------------------------------------------------------- -export const PermissionDock: Story = { - name: "Permission Dock", +export const PermissionDockWrite: Story = { + name: "Permission Dock — write with patterns", render: () => { - const perm = dockPermission + const perms = [dockPermission] + const session = { + ...mockSessionValue({ id: SESSION_ID, status: "busy", permissions: perms }), + messages: () => [{ id: "msg-001" }] as any[], + } return ( - -
- - 0}> -
- {(pattern) => {pattern}} -
-
-
-
-
- - - -
-

Approval applies only to the current session

+ + +
+
-
+ + + ) + }, +} + +// --------------------------------------------------------------------------- +// 3b. Permission dock — todowrite (above chatbox) +// --------------------------------------------------------------------------- + +export const PermissionDockTodo: Story = { + name: "Permission Dock — todowrite above chatbox", + render: () => { + const perms = [todoWritePermission] + const session = { + ...mockSessionValue({ id: SESSION_ID, status: "busy", permissions: perms }), + messages: () => [{ id: "msg-001" }] as any[], + } + return ( + + +
+ +
+
+
+ ) + }, +} + +// --------------------------------------------------------------------------- +// 3c. Question dock above chatbox +// --------------------------------------------------------------------------- + +export const QuestionAboveChatbox: Story = { + name: "Question Dock — above chatbox", + render: () => { + const qs = [questionRequest] + const session = { + ...mockSessionValue({ id: SESSION_ID, status: "busy", questions: qs }), + messages: () => [{ id: "msg-001" }] as any[], + } + return ( + + +
+ +
+
) }, @@ -550,17 +582,25 @@ export const QuestionDismissed: Story = { } // --------------------------------------------------------------------------- -// 10. TodoWrite with inline permission (pending — shows todo list + permission prompt) +// 10. TodoWrite with permission in dock (pending — permission shown above chatbox) // --------------------------------------------------------------------------- export const TodoWriteWithPermission: Story = { - name: "TodoWrite + Inline Permission", + name: "TodoWrite + Permission in Dock", render: () => { const perms = [todoWritePermission] const data = dataWith([todoWritePending], perms) + const session = { + ...mockSessionValue({ id: SESSION_ID, status: "busy", permissions: perms }), + messages: () => [{ id: "msg-001" }] as any[], + } return ( - - + + +
+ +
+
) }, diff --git a/packages/kilo-vscode/webview-ui/src/styles/chat.css b/packages/kilo-vscode/webview-ui/src/styles/chat.css index dc9c2cde563..bd82cea41e5 100644 --- a/packages/kilo-vscode/webview-ui/src/styles/chat.css +++ b/packages/kilo-vscode/webview-ui/src/styles/chat.css @@ -1094,56 +1094,64 @@ } /* ============================================ - Permission Dock (patterns inside BasicTool) + Permission Dock (VS Code overrides) ============================================ */ -.permission-dock-patterns { - display: flex; - flex-direction: column; - gap: 4px; - max-height: 120px; - overflow-y: auto; - padding: 8px 12px; -} - -.permission-dock-pattern { - font-size: 12px; - font-family: var(--vscode-editor-font-family, monospace); - color: var(--vscode-foreground); - word-break: break-all; -} - -/* Permission Prompt (inline with tool cards) */ -[data-component="tool-part-wrapper"][data-permission="true"] { - /* When wrapping a Part + permission, the outer wrapper provides the card border. - Remove the inner Part's tool-part-wrapper border so we don't get double borders. */ - > [data-component="tool-part-wrapper"] { - border: none; - border-radius: 0; +[data-component="dock-prompt"][data-kind="permission"] { + [data-slot="permission-body"] { + padding: 10px 12px 0; + gap: 8px; } -} -[data-component="permission-prompt"] { - border-top: 1px solid var(--border-weak-base, var(--vscode-panel-border)); - padding: 8px 12px; -} + [data-slot="permission-header-title"] { + font-size: 13px; + display: flex; + align-items: baseline; + gap: 8px; + } -[data-component="permission-prompt"] [data-slot="permission-description"] { - font-size: 12px; - color: var(--text-weak, var(--vscode-descriptionForeground)); - margin: 0 0 8px; -} + [data-slot="permission-header-subtitle"] { + font-size: 11px; + font-weight: var(--font-weight-regular, 400); + color: var(--text-weak, var(--vscode-descriptionForeground)); + } -[data-component="permission-prompt"] [data-slot="permission-actions"] { - display: flex; - align-items: center; - gap: 6px; -} + [data-slot="permission-hint"] { + font-size: 12px; + } -[data-component="permission-prompt"] [data-slot="permission-hint"] { - font-size: 11px; - color: var(--text-weak, var(--vscode-descriptionForeground)); - margin: 4px 0 0; + [data-slot="permission-patterns"] { + margin-top: 0; + margin-bottom: 0; + max-height: 120px; + + code { + font-size: 12px; + font-family: var(--vscode-editor-font-family, monospace); + color: var(--vscode-foreground); + word-break: break-all; + } + } + + [data-slot="permission-footer"] { + padding: 8px; + margin-top: 0; + } + + [data-slot="permission-footer-actions"] > [data-component="button"][data-variant="primary"] { + color: var(--vscode-button-foreground); + background-color: var(--vscode-button-background); + border: 1px solid var(--vscode-button-border, transparent); + + &:hover:not(:disabled) { + background-color: var(--vscode-button-hoverBackground); + } + } + + [data-slot="permission-footer-actions"] > [data-component="button"][data-variant="ghost"] { + border: none; + box-shadow: none; + } } /* Dialog Confirmation */