+ {/* Header */}
+
+
+
+ {comment.content || 'Permission Required'}
+
+ {getStatusBadge()}
+
+
+ {/* Approval buttons - only show when pending */}
+ {isPending && (
+
+
+ Approve this action?
+
+
+
+
+ )}
+
+ );
+}
+
diff --git a/apps/web/components/task/chat/messages/tool-call-message.tsx b/apps/web/components/task/chat/messages/tool-call-message.tsx
index 968fc013e9..5359721934 100644
--- a/apps/web/components/task/chat/messages/tool-call-message.tsx
+++ b/apps/web/components/task/chat/messages/tool-call-message.tsx
@@ -16,10 +16,28 @@ import {
} from '@tabler/icons-react';
import { Button } from '@kandev/ui/button';
import { cn } from '@/lib/utils';
+import { getWebSocketClient } from '@/lib/ws/connection';
import type { Message } from '@/lib/types/http';
import type { ToolCallMetadata } from '@/components/task/chat/types';
-import { useAppStore } from '@/components/state-provider';
-import { getWebSocketClient } from '@/lib/ws/connection';
+
+type PermissionOption = {
+ option_id: string;
+ name: string;
+ kind: string;
+};
+
+type PermissionRequestMetadata = {
+ pending_id: string;
+ tool_call_id: string;
+ options: PermissionOption[];
+ action_type: string;
+ action_details: {
+ command?: string;
+ path?: string;
+ cwd?: string;
+ };
+ status?: 'pending' | 'approved' | 'rejected';
+};
function getToolIcon(toolName: string | undefined, className: string) {
const name = toolName?.toLowerCase() ?? '';
@@ -41,7 +59,15 @@ function getToolIcon(toolName: string | undefined, className: string) {
return
- {/* Inline approval buttons when permission is pending */}
- {isPendingApproval && (
-
-
- Approve this action?
-
-
-
-
- )}
-
{isExpanded && hasDetails && (
{args && Object.entries(args).map(([key, value]) => {
@@ -243,6 +227,39 @@ export function ToolCallMessage({ comment, taskId }: ToolCallMessageProps) {
)}
)}
+
+ {/* Inline permission approval UI */}
+ {isPermissionPending && (
+
+
+ Approve this action?
+
+
+
+
+ )}
);
}
diff --git a/apps/web/components/task/command-approval-dialog.tsx b/apps/web/components/task/command-approval-dialog.tsx
deleted file mode 100644
index b3e2b06b2c..0000000000
--- a/apps/web/components/task/command-approval-dialog.tsx
+++ /dev/null
@@ -1,187 +0,0 @@
-'use client';
-
-import { useCallback, useMemo, useState } from 'react';
-import { IconTerminal2, IconAlertTriangle, IconLoader2 } from '@tabler/icons-react';
-import {
- AlertDialog,
- AlertDialogAction,
- AlertDialogCancel,
- AlertDialogContent,
- AlertDialogDescription,
- AlertDialogFooter,
- AlertDialogHeader,
- AlertDialogTitle,
-} from '@kandev/ui/alert-dialog';
-import { useAppStore } from '@/components/state-provider';
-import { getWebSocketClient } from '@/lib/ws/connection';
-import type { Message } from '@/lib/types/http';
-
-const EMPTY_MESSAGES: Message[] = [];
-
-type CommandApprovalDialogProps = {
- taskId: string;
- /** Only show permissions that don't have a matching tool call (standalone permissions like workspace indexing) */
- standaloneOnly?: boolean;
-};
-
-export function CommandApprovalDialog({ taskId, standaloneOnly = false }: CommandApprovalDialogProps) {
- const [isResponding, setIsResponding] = useState(false);
- const pendingPermissions = useAppStore((state) => state.permissions.pending);
- const removePendingPermission = useAppStore((state) => state.removePendingPermission);
- const activeSessionId = useAppStore((state) => state.tasks.activeSessionId);
- const messages = useAppStore((state) =>
- activeSessionId ? state.messages.bySession[activeSessionId] ?? EMPTY_MESSAGES : EMPTY_MESSAGES
- );
-
- // Get tool_call_ids from all tool call messages
- const toolCallIds = useMemo(() => {
- const ids = new Set