import { jsonResponse, errorResponse } from '../utils/cors' import { PORT_TERMINAL } from '../config' interface PermissionPayload { hook_event_name?: string session_id?: string tool_name?: string tool_input?: unknown cwd?: string [key: string]: unknown } interface PendingPermission { resolve: (decision: string) => void timer: ReturnType payload: PermissionPayload } // Map of requestId -> pending permission promise resolver const pendingPermissions = new Map() const PERMISSION_TIMEOUT_MS = 60_000 export async function handleClaudePermission(req: Request): Promise { if (req.method !== 'POST') return null try { const body = await req.json() as PermissionPayload const requestId = `perm_${Date.now()}_${Math.random().toString(36).slice(2, 8)}` // Broadcast permission request to UI via WebSocket try { await fetch(`http://localhost:${PORT_TERMINAL}/claude-permission`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ requestId, ...body }) }) } catch (e) { console.error('[claude-permission] Failed to broadcast to terminal server:', e) } // Also broadcast claude-status for backward compat (animations) try { await fetch(`http://localhost:${PORT_TERMINAL}/claude-status`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ status: 'permissionRequest', tool: body.tool_name }) }) } catch (e) { console.error('[claude-permission] Failed to forward status:', e) } // Wait for UI decision (or timeout) const decision = await new Promise((resolve) => { const timer = setTimeout(() => { pendingPermissions.delete(requestId) resolve('ask') }, PERMISSION_TIMEOUT_MS) pendingPermissions.set(requestId, { resolve, timer, payload: body }) }) return jsonResponse({ decision, requestId }) } catch (e) { return errorResponse('Invalid JSON body', 400) } } export async function handleClaudePermissionRespond(req: Request): Promise { if (req.method !== 'POST') return null try { const body = await req.json() as { requestId: string, decision: 'allow' | 'deny' } if (!body.requestId || !body.decision) { return errorResponse('Missing requestId or decision', 400) } if (!['allow', 'deny'].includes(body.decision)) { return errorResponse('Decision must be "allow" or "deny"', 400) } const pending = pendingPermissions.get(body.requestId) if (!pending) { return errorResponse('Permission request not found or already expired', 404) } // Resolve the pending promise clearTimeout(pending.timer) pendingPermissions.delete(body.requestId) pending.resolve(body.decision) return jsonResponse({ success: true, requestId: body.requestId, decision: body.decision }) } catch (e) { return errorResponse('Invalid JSON body', 400) } } // List pending permissions (useful for UI to recover state) export async function handleClaudePermissionList(req: Request): Promise { if (req.method !== 'GET') return null const pending = Array.from(pendingPermissions.entries()).map(([id, p]) => ({ requestId: id, tool_name: p.payload.tool_name, tool_input: p.payload.tool_input, session_id: p.payload.session_id })) return jsonResponse({ pending }) }