import React from '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 { WorkerHighlightedCode } from '@/components/code/WorkerHighlightedCode'; import { ScrollableOverlay } from '@/components/ui/ScrollableOverlay'; import { Icon } from "@/components/icon/Icon"; import { DiffPreview, WritePreview } from './DiffPreview'; import { useI18n } from '@/lib/i18n'; import { getVisiblePermissionPatterns } from './permissionCardPatterns'; const PERMISSION_BASH_CUSTOM_STYLE: React.CSSProperties = { margin: 0, padding: '0.5rem', fontSize: 'var(--text-meta)', lineHeight: '1.25rem', background: 'rgb(var(--muted) / 0.3)', borderRadius: '0.25rem', whiteSpace: 'pre-wrap', wordBreak: 'break-word', overflowWrap: 'break-word', overflow: 'visible', }; const PERMISSION_BASH_CODE_TAG_PROPS = { style: { whiteSpace: 'pre-wrap', wordBreak: 'break-word', overflowWrap: 'break-word', } as React.CSSProperties, }; const PERMISSION_JSON_CUSTOM_STYLE: React.CSSProperties = { margin: 0, padding: '0.5rem', fontSize: 'var(--text-meta)', lineHeight: '1.25rem', background: 'rgb(var(--muted) / 0.3)', borderRadius: '0.25rem', }; 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 ; } if (tool === 'linear' || tool.startsWith('linear_')) { return ; } if (tool === 'cloudflare' || tool.startsWith('cloudflare_') || tool === 'claudflare' || tool.startsWith('claudflare_')) { 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 { t } = useI18n(); 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 handleResponse = async (response: PermissionResponse) => { setIsResponding(true); try { await respondToPermission(permission.sessionID, permission.id, response); setHasResponded(true); onResponse?.(response); } catch (error) { console.error('[PermissionCard] Failed to respond to permission:', error); } finally { setIsResponding(false); } }; if (hasResponded) { return null; } const toolName = permission.permission || 'unknown'; const tool = toolName.toLowerCase(); const isBashTool = tool === 'bash' || tool === 'shell' || tool === 'shell_command'; 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 bashCommand = isBashTool ? getMeta('command') || getMeta('cmd') || getMeta('script') : ''; const visiblePatterns = getVisiblePermissionPatterns(permission.patterns, bashCommand); const renderToolContent = () => { if (isBashTool) { const description = getMeta('description'); const workingDir = getMeta('cwd') || getMeta('working_directory') || getMeta('directory') || getMeta('path'); const timeout = getMetaNum('timeout'); return ( <> {description && (
{description}
)} {workingDir && (
{t('chat.permissionCard.workingDirectory')} {workingDir}
)} {timeout && (
{t('chat.permissionCard.timeout')} {timeout}ms
)} {} {bashCommand && (
)} ); } 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 && (
{t('chat.permissionCard.request')}
{method} {url}
)} {headers && Object.keys(headers).length > 0 && (
{t('chat.permissionCard.headers')}
)} {body && (
{t('chat.permissionCard.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 && (
{t('chat.permissionCard.action')}
                {String(genericContent)}
              
)} {} {Object.keys(permission.metadata).length > 0 && !genericContent && !description && (
{t('chat.permissionCard.details')}
                {JSON.stringify(permission.metadata, null, 2)}
              
)} ); }; return (
{}
Permission Required {isFromSubagent ? ( From subagent ) : null}
{getToolIcon(toolName)} {displayToolName}
{}
{visiblePatterns.length > 0 && (
{t('chat.permissionCard.patterns')}
{visiblePatterns.join(", ")}
)} {renderToolContent()}
{}
{permission.always.length > 0 ? ( ) : ( )} {isResponding && (
)}
); };