diff --git a/frontend/src/components/HooksApprovalModal.vue b/frontend/src/components/HooksApprovalModal.vue index 6f6eaa4..b699380 100644 --- a/frontend/src/components/HooksApprovalModal.vue +++ b/frontend/src/components/HooksApprovalModal.vue @@ -9,7 +9,8 @@ const { groupedBySession, modalVisible, respondPermission, - respondPlan + respondPlan, + ignoreApproval } = useGlobalApproval() function truncateId(id: string): string { @@ -68,6 +69,7 @@ watch(totalPending, (val) => { :key="perm.requestId" :request="perm" @respond="(id, decision, reason) => respondPermission(id, decision, reason)" + @ignore="(id) => ignoreApproval(id)" /> () // ── Mode detection ── @@ -190,6 +191,14 @@ function elapsed(): string { Reject + @@ -258,6 +267,14 @@ function elapsed(): string { Dismiss + @@ -316,6 +333,14 @@ function elapsed(): string { Deny + @@ -518,6 +543,9 @@ function elapsed(): string { .btn-reject { background: #ef4444; color: white; } .btn-reject:hover { background: #dc2626; } +.btn-ignore { background: transparent; color: var(--text-muted); border: 1px solid var(--border-color); } +.btn-ignore:hover { background: var(--bg-hover); color: var(--text-secondary); } + /* ── Transitions ── */ .expand-enter-active, .expand-leave-active { transition: all 0.2s ease; overflow: hidden; } .expand-enter-from, .expand-leave-to { opacity: 0; max-height: 0; } diff --git a/frontend/src/composables/useGlobalApproval.ts b/frontend/src/composables/useGlobalApproval.ts index 8118840..e0dec1e 100644 --- a/frontend/src/composables/useGlobalApproval.ts +++ b/frontend/src/composables/useGlobalApproval.ts @@ -103,6 +103,19 @@ export function useGlobalApproval() { } } + async function ignoreApproval(requestId: string) { + console.log(`[GlobalApproval] Ignoring ${requestId} (UI-only removal)`) + try { + await fetch('/api/hooks-approval/ignore', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ requestId }) + }) + } catch (e) { + console.error('[GlobalApproval] Failed to ignore approval:', e) + } + } + // ── connect/disconnect/fetchPending kept as no-ops for backward compat ── // Session state WS (initialized in App.vue) handles everything now. @@ -120,6 +133,7 @@ export function useGlobalApproval() { disconnect, fetchPending, respondPermission, - respondPlan + respondPlan, + ignoreApproval } } diff --git a/server/routes/hooks-approval.ts b/server/routes/hooks-approval.ts index 711f969..950fbb1 100644 --- a/server/routes/hooks-approval.ts +++ b/server/routes/hooks-approval.ts @@ -348,6 +348,31 @@ export async function handleHooksApprovalRespondPlan(req: Request): Promise { + if (req.method !== 'POST') return null + + try { + const body = await req.json() as { requestId: string } + + if (!body.requestId) { + return errorResponse('Missing requestId', 400) + } + + console.log(`[HooksApproval] Ignoring ${body.requestId} (removing from UI state only)`) + + // Remove from session state → broadcasts patch to all clients + // The long-poll promise stays alive and will timeout naturally (120s), + // at which point Claude Code gets {} and handles it with default behavior. + notifyResolveApproval(body.requestId, 'ignore') + + return jsonResponse({ success: true, requestId: body.requestId }) + } catch (e) { + return errorResponse('Invalid JSON body', 400) + } +} + // ── List pending (for state recovery) ── export async function handleHooksApprovalList(req: Request): Promise { diff --git a/server/routes/index.ts b/server/routes/index.ts index 27c6d90..50a46f0 100644 --- a/server/routes/index.ts +++ b/server/routes/index.ts @@ -26,7 +26,7 @@ import { handleTranscriptDebugSessions, handleTranscriptDebugRaw, handleTranscri import { handleHooksApprovalPermission, handleHooksApprovalPlan, handleHooksApprovalRespond, handleHooksApprovalRespondPlan, - handleHooksApprovalList + handleHooksApprovalIgnore, handleHooksApprovalList } from './hooks-approval' export async function handleRequest(req: Request): Promise { @@ -371,6 +371,11 @@ export async function handleRequest(req: Request): Promise { if (res) return res } + if (path === '/api/hooks-approval/ignore' && req.method === 'POST') { + const res = await handleHooksApprovalIgnore(req) + if (res) return res + } + // Agents if (path === '/api/agents' && req.method === 'GET') { return handleAgents(req)