feat: add ignore option for permission requests (UI-only dismissal)
This commit is contained in:
@@ -9,7 +9,8 @@ const {
|
|||||||
groupedBySession,
|
groupedBySession,
|
||||||
modalVisible,
|
modalVisible,
|
||||||
respondPermission,
|
respondPermission,
|
||||||
respondPlan
|
respondPlan,
|
||||||
|
ignoreApproval
|
||||||
} = useGlobalApproval()
|
} = useGlobalApproval()
|
||||||
|
|
||||||
function truncateId(id: string): string {
|
function truncateId(id: string): string {
|
||||||
@@ -68,6 +69,7 @@ watch(totalPending, (val) => {
|
|||||||
:key="perm.requestId"
|
:key="perm.requestId"
|
||||||
:request="perm"
|
:request="perm"
|
||||||
@respond="(id, decision, reason) => respondPermission(id, decision, reason)"
|
@respond="(id, decision, reason) => respondPermission(id, decision, reason)"
|
||||||
|
@ignore="(id) => ignoreApproval(id)"
|
||||||
/>
|
/>
|
||||||
<PlanApproval
|
<PlanApproval
|
||||||
v-for="plan in group.plans"
|
v-for="plan in group.plans"
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ const props = defineProps<{
|
|||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
respond: [requestId: string, decision: string, reason?: string]
|
respond: [requestId: string, decision: string, reason?: string]
|
||||||
|
ignore: [requestId: string]
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
// ── Mode detection ──
|
// ── Mode detection ──
|
||||||
@@ -190,6 +191,14 @@ function elapsed(): string {
|
|||||||
</svg>
|
</svg>
|
||||||
Reject
|
Reject
|
||||||
</button>
|
</button>
|
||||||
|
<button class="btn btn-ignore" @click="emit('ignore', request.requestId)" title="Remove from UI without responding">
|
||||||
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||||
|
<path d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94"/>
|
||||||
|
<path d="M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19"/>
|
||||||
|
<line x1="1" y1="1" x2="23" y2="23"/>
|
||||||
|
</svg>
|
||||||
|
Ignore
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -258,6 +267,14 @@ function elapsed(): string {
|
|||||||
</svg>
|
</svg>
|
||||||
Dismiss
|
Dismiss
|
||||||
</button>
|
</button>
|
||||||
|
<button class="btn btn-ignore" @click="emit('ignore', request.requestId)" title="Remove from UI without responding">
|
||||||
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||||
|
<path d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94"/>
|
||||||
|
<path d="M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19"/>
|
||||||
|
<line x1="1" y1="1" x2="23" y2="23"/>
|
||||||
|
</svg>
|
||||||
|
Ignore
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -316,6 +333,14 @@ function elapsed(): string {
|
|||||||
</svg>
|
</svg>
|
||||||
Deny
|
Deny
|
||||||
</button>
|
</button>
|
||||||
|
<button class="btn btn-ignore" @click="emit('ignore', request.requestId)" title="Remove from UI without responding">
|
||||||
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||||
|
<path d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94"/>
|
||||||
|
<path d="M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19"/>
|
||||||
|
<line x1="1" y1="1" x2="23" y2="23"/>
|
||||||
|
</svg>
|
||||||
|
Ignore
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -518,6 +543,9 @@ function elapsed(): string {
|
|||||||
.btn-reject { background: #ef4444; color: white; }
|
.btn-reject { background: #ef4444; color: white; }
|
||||||
.btn-reject:hover { background: #dc2626; }
|
.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 ── */
|
/* ── Transitions ── */
|
||||||
.expand-enter-active, .expand-leave-active { transition: all 0.2s ease; overflow: hidden; }
|
.expand-enter-active, .expand-leave-active { transition: all 0.2s ease; overflow: hidden; }
|
||||||
.expand-enter-from, .expand-leave-to { opacity: 0; max-height: 0; }
|
.expand-enter-from, .expand-leave-to { opacity: 0; max-height: 0; }
|
||||||
|
|||||||
@@ -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 ──
|
// ── connect/disconnect/fetchPending kept as no-ops for backward compat ──
|
||||||
// Session state WS (initialized in App.vue) handles everything now.
|
// Session state WS (initialized in App.vue) handles everything now.
|
||||||
|
|
||||||
@@ -120,6 +133,7 @@ export function useGlobalApproval() {
|
|||||||
disconnect,
|
disconnect,
|
||||||
fetchPending,
|
fetchPending,
|
||||||
respondPermission,
|
respondPermission,
|
||||||
respondPlan
|
respondPlan,
|
||||||
|
ignoreApproval
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -348,6 +348,31 @@ export async function handleHooksApprovalRespondPlan(req: Request): Promise<Resp
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Ignore (remove from UI state only, don't respond to Claude Code) ──
|
||||||
|
|
||||||
|
export async function handleHooksApprovalIgnore(req: Request): Promise<Response | null> {
|
||||||
|
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) ──
|
// ── List pending (for state recovery) ──
|
||||||
|
|
||||||
export async function handleHooksApprovalList(req: Request): Promise<Response | null> {
|
export async function handleHooksApprovalList(req: Request): Promise<Response | null> {
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ import { handleTranscriptDebugSessions, handleTranscriptDebugRaw, handleTranscri
|
|||||||
import {
|
import {
|
||||||
handleHooksApprovalPermission, handleHooksApprovalPlan,
|
handleHooksApprovalPermission, handleHooksApprovalPlan,
|
||||||
handleHooksApprovalRespond, handleHooksApprovalRespondPlan,
|
handleHooksApprovalRespond, handleHooksApprovalRespondPlan,
|
||||||
handleHooksApprovalList
|
handleHooksApprovalIgnore, handleHooksApprovalList
|
||||||
} from './hooks-approval'
|
} from './hooks-approval'
|
||||||
|
|
||||||
export async function handleRequest(req: Request): Promise<Response> {
|
export async function handleRequest(req: Request): Promise<Response> {
|
||||||
@@ -371,6 +371,11 @@ export async function handleRequest(req: Request): Promise<Response> {
|
|||||||
if (res) return res
|
if (res) return res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (path === '/api/hooks-approval/ignore' && req.method === 'POST') {
|
||||||
|
const res = await handleHooksApprovalIgnore(req)
|
||||||
|
if (res) return res
|
||||||
|
}
|
||||||
|
|
||||||
// Agents
|
// Agents
|
||||||
if (path === '/api/agents' && req.method === 'GET') {
|
if (path === '/api/agents' && req.method === 'GET') {
|
||||||
return handleAgents(req)
|
return handleAgents(req)
|
||||||
|
|||||||
Reference in New Issue
Block a user