Add tool confirmation UI with Accept/Decline for write operations

Before executing any write tool (create/update/delete), the backend now
pauses with an asyncio.Future and emits a tool_pending SSE event. The
frontend displays a ToolConfirmCard with Accept and Decline buttons.
Clicking Accept resolves the Future and proceeds; Decline records a
declined tool_call chip and falls through to regular streaming. Typing
single-word yes/no responses (e.g. "yes", "cancel") also works as
confirmation. 120s timeout auto-declines if no response.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-18 20:43:47 -05:00
parent 1b63371bb3
commit 815eed2574
8 changed files with 304 additions and 43 deletions
+13 -2
View File
@@ -11,6 +11,7 @@ const appliedTags = ref<Set<string>>(new Set());
const applyingTag = ref<string | null>(null);
const label = computed(() => {
if (props.toolCall.status === "declined") return "Declined";
if (!props.toolCall.result.success) return "Error";
switch (props.toolCall.result.type) {
case "task":
@@ -180,9 +181,12 @@ async function applyTag(tag: string) {
</script>
<template>
<div class="tool-call-card" :class="{ error: toolCall.status === 'error' }">
<div class="tool-call-card" :class="{ error: toolCall.status === 'error', declined: toolCall.status === 'declined' }">
<span class="tool-label">{{ label }}</span>
<template v-if="toolCall.status === 'error'">
<template v-if="toolCall.status === 'declined'">
<span class="tool-declined-name">{{ toolCall.arguments.title ?? toolCall.arguments.summary ?? toolCall.arguments.query ?? toolCall.function }}</span>
</template>
<template v-else-if="toolCall.status === 'error'">
<span class="tool-error">{{ toolCall.result.error }}</span>
</template>
<template v-else-if="searchResults">
@@ -292,6 +296,13 @@ async function applyTag(tag: string) {
.tool-call-card.error {
border-color: var(--color-danger, #e74c3c);
}
.tool-call-card.declined {
opacity: 0.55;
}
.tool-declined-name {
text-decoration: line-through;
color: var(--color-text-muted);
}
.tool-label {
font-weight: 600;
color: var(--color-text-muted);