import React from 'react'; import { RiCheckLine, RiCloseLine, RiFileEditLine, RiGlobalLine, RiPencilAiLine, RiQuestionLine, RiTerminalBoxLine, RiTimeLine, RiToolsLine } from '@remixicon/react'; import { cn } from '@/lib/utils'; import type { PermissionRequest, PermissionResponse } from '@/types/permission'; import { useSessionUIStore } from '@/sync/session-ui-store'; import { useSessions } from '@/sync/sync-context'; import * as sessionActions from '@/sync/session-actions'; import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'; import { useThemeSystem } from '@/contexts/useThemeSystem'; import { generateSyntaxTheme } from '@/lib/theme/syntaxThemeGenerator'; import { ScrollableOverlay } from '@/components/ui/ScrollableOverlay'; import { DiffPreview, WritePreview } from './DiffPreview'; interface PermissionCardProps { permission: PermissionRequest; onResponse?: (response: 'once' | 'always' | 'reject') => void; } const getToolIcon = (toolName: string) => { const iconClass = "h-3 w-3"; const tool = toolName.toLowerCase(); if (tool === 'edit' || tool === 'multiedit' || tool === 'str_replace' || tool === 'str_replace_based_edit_tool') { return ; } if (tool === 'write' || tool === 'create' || tool === 'file_write') { return ; } if (tool === 'bash' || tool === 'shell' || tool === 'cmd' || tool === 'terminal' || tool === 'shell_command') { return ; } if (tool === 'webfetch' || tool === 'fetch' || tool === 'curl' || tool === 'wget') { return ; } return ; }; const getToolDisplayName = (toolName: string): string => { const tool = toolName.toLowerCase(); if (tool === 'edit' || tool === 'multiedit' || tool === 'str_replace' || tool === 'str_replace_based_edit_tool') { return 'edit'; } if (tool === 'write' || tool === 'create' || tool === 'file_write') { return 'write'; } if (tool === 'bash' || tool === 'shell' || tool === 'cmd' || tool === 'terminal' || tool === 'shell_command') { return 'bash'; } if (tool === 'webfetch' || tool === 'fetch' || tool === 'curl' || tool === 'wget') { return 'webfetch'; } return toolName; }; export const PermissionCard: React.FC = ({ permission, onResponse }) => { const [isResponding, setIsResponding] = React.useState(false); const [hasResponded, setHasResponded] = React.useState(false); const respondToPermission = sessionActions.respondToPermission;; const sessions = useSessions(); const currentSessionId = useSessionUIStore((state) => state.currentSessionId); const isFromSubagent = React.useMemo(() => { if (!currentSessionId || permission.sessionID === currentSessionId) return false; const sourceSession = sessions.find((session) => session.id === permission.sessionID); return Boolean(sourceSession?.parentID && sourceSession.parentID === currentSessionId); }, [permission.sessionID, currentSessionId, sessions]); const { currentTheme } = useThemeSystem(); const syntaxTheme = React.useMemo(() => generateSyntaxTheme(currentTheme), [currentTheme]); const handleResponse = async (response: PermissionResponse) => { setIsResponding(true); try { await respondToPermission(permission.sessionID, permission.id, response); setHasResponded(true); onResponse?.(response); } catch { /* ignored */ } finally { setIsResponding(false); } }; if (hasResponded) { return null; } const toolName = permission.permission || 'unknown'; const tool = toolName.toLowerCase(); const getMeta = (key: string, fallback: string = ''): string => { const val = permission.metadata[key]; return typeof val === 'string' ? val : (typeof val === 'number' ? String(val) : fallback); }; const getMetaNum = (key: string): number | undefined => { const val = permission.metadata[key]; return typeof val === 'number' ? val : undefined; }; const getMetaBool = (key: string): boolean => { const val = permission.metadata[key]; return Boolean(val); }; const displayToolName = getToolDisplayName(toolName); const renderToolContent = () => { if (tool === 'bash' || tool === 'shell' || tool === 'shell_command') { const command = getMeta('command') || getMeta('cmd') || getMeta('script'); const description = getMeta('description'); const workingDir = getMeta('cwd') || getMeta('working_directory') || getMeta('directory') || getMeta('path'); const timeout = getMetaNum('timeout'); return ( <> {description && (
{description}
)} {workingDir && (
Working Directory: {workingDir}
)} {timeout && (
Timeout: {timeout}ms
)} {} {command && (
{command}
)} ); } if (tool === 'edit' || tool === 'multiedit' || tool === 'str_replace' || tool === 'str_replace_based_edit_tool') { const filePath = getMeta('path') || getMeta('file_path') || getMeta('filename') || getMeta('filePath'); const changes = getMeta('changes') || getMeta('diff'); const replaceAll = getMetaBool('replace_all') || getMetaBool('replaceAll'); return ( <> {replaceAll && (
⚠️ Replace All Occurrences
)} {changes && ( )} ); } if (tool === 'write' || tool === 'create' || tool === 'file_write') { const filePath = getMeta('path') || getMeta('file_path') || getMeta('filename') || getMeta('filePath'); const content = getMeta('content') || getMeta('text') || getMeta('data'); if (content) { return ( ); } return null; } if (tool === 'webfetch' || tool === 'fetch' || tool === 'curl' || tool === 'wget') { const url = getMeta('url') || getMeta('uri') || getMeta('endpoint'); const method = getMeta('method') || 'GET'; const headers = permission.metadata.headers && typeof permission.metadata.headers === 'object' ? (permission.metadata.headers as Record) : undefined; const body = getMeta('body') || getMeta('data') || getMeta('payload'); const timeout = getMetaNum('timeout'); const format = getMeta('format') || getMeta('responseType'); return ( <> {url && (
Request:
{method} {url}
)} {headers && Object.keys(headers).length > 0 && (
Headers:
{JSON.stringify(headers, null, 2)}
)} {body && (
Body:
{typeof body === 'object' ? JSON.stringify(body, null, 2) : String(body)}
)} {(timeout || format) && (
{timeout && Timeout: {timeout}ms} {timeout && format && } {format && Response format: {format}}
)} ); } const genericContent = getMeta('command') || getMeta('content') || getMeta('action') || getMeta('operation'); const description = getMeta('description'); return ( <> {description && (
{description}
)} {genericContent && (
Action:
                {String(genericContent)}
              
)} {} {Object.keys(permission.metadata).length > 0 && !genericContent && !description && (
Details:
                {JSON.stringify(permission.metadata, null, 2)}
              
)} ); }; return (
{}
Permission Required {isFromSubagent ? ( From subagent ) : null}
{getToolIcon(toolName)} {displayToolName}
{}
{permission.patterns.length > 0 && (
Patterns:
{permission.patterns.join(", ")}
)} {renderToolContent()}
{}
{permission.always.length > 0 ? ( ) : ( )} {isResponding && (
)}
); };