import { useMemo, useState } from 'react' import { useTranslation } from '../../i18n' import { computerUseApi } from '../../api/computerUse' import { useChatStore } from '../../stores/chatStore' import type { ComputerUsePermissionRequest, ComputerUsePermissionResponse, } from '../../types/chat' import { Button } from '../shared/Button' import { Modal } from '../shared/Modal' type Props = { sessionId: string request: ComputerUsePermissionRequest | null } const DEFAULT_GRANT_FLAGS = { clipboardRead: false, clipboardWrite: false, systemKeyCombos: false, } as const function denyAllResponse(): ComputerUsePermissionResponse { return { granted: [], denied: [], flags: { ...DEFAULT_GRANT_FLAGS }, userConsented: false, } } function buildAllowResponse( request: ComputerUsePermissionRequest, ): ComputerUsePermissionResponse { const now = Date.now() const granted = request.apps.flatMap((app) => { if (!app.resolved || app.alreadyGranted) return [] return [{ bundleId: app.resolved.bundleId, displayName: app.resolved.displayName, grantedAt: now, tier: app.proposedTier, }] }) const denied = request.apps.flatMap((app) => { if (app.resolved) return [] return [{ bundleId: app.requestedName, reason: 'not_installed' as const, }] }) const flags = { ...DEFAULT_GRANT_FLAGS, ...Object.fromEntries( Object.entries(request.requestedFlags).filter(([, value]) => value === true), ), } return { granted, denied, flags, userConsented: true, } } export function ComputerUsePermissionModal({ sessionId, request }: Props) { const t = useTranslation() const respondToComputerUsePermission = useChatStore( (s) => s.respondToComputerUsePermission, ) const [openingPane, setOpeningPane] = useState< 'Privacy_Accessibility' | 'Privacy_ScreenCapture' | null >(null) const requestedFlags = useMemo( () => request ? Object.entries(request.requestedFlags) .filter(([, enabled]) => enabled) .map(([flag]) => flag) : [], [request], ) if (!request) return null const handleDeny = () => { respondToComputerUsePermission( sessionId, request.requestId, denyAllResponse(), ) } const handleAllow = () => { respondToComputerUsePermission( sessionId, request.requestId, buildAllowResponse(request), ) } const openSettings = async ( pane: 'Privacy_Accessibility' | 'Privacy_ScreenCapture', ) => { setOpeningPane(pane) try { await computerUseApi.openSettings(pane) } finally { setOpeningPane(null) } } const tccState = request.tccState return ( {t('computerUseApproval.deny')} ) : ( <> ) } > {tccState ? (

{t('computerUseApproval.tccHint')}

openSettings('Privacy_Accessibility')} /> openSettings('Privacy_ScreenCapture')} />
{t('computerUseApproval.tryAgainHint')}
) : (
{request.reason ? (
{t('computerUseApproval.reason')}
{request.reason}
) : null}
{request.apps.map((app) => { const resolved = app.resolved return (
{resolved?.displayName ?? app.requestedName}
{resolved?.bundleId ?? t('computerUseApproval.notInstalled')}
{app.proposedTier}
{!resolved ? (

{t('computerUseApproval.notInstalled')}

) : null} {app.alreadyGranted ? (

{t('computerUseApproval.alreadyGranted')}

) : null} {app.isSentinel ? (

{t('computerUseApproval.sensitiveApp')}

) : null}
) })}
{requestedFlags.length > 0 ? (
{t('computerUseApproval.alsoRequested')}
{requestedFlags.map((flag) => ( {flag} ))}
) : null} {request.willHide && request.willHide.length > 0 ? (
{request.autoUnhideEnabled ? t('computerUseApproval.hideWhileWorkingRestore', { count: request.willHide.length, }) : t('computerUseApproval.hideWhileWorking', { count: request.willHide.length, })}
) : null}
)}
) } function PermissionRow({ label, granted, actionLabel, actionLoading, onAction, }: { label: string granted: boolean actionLabel: string actionLoading: boolean onAction: () => void }) { const t = useTranslation() return (
{label}
{granted ? t('computerUseApproval.granted') : t('computerUseApproval.notGranted')}
{!granted ? ( ) : null}
) }