diff --git a/web/src/lib/components/InlineApproval.svelte b/web/src/lib/components/InlineApproval.svelte
index dc8fa8f..d750d6c 100644
--- a/web/src/lib/components/InlineApproval.svelte
+++ b/web/src/lib/components/InlineApproval.svelte
@@ -1,4 +1,5 @@
-{#if match && !done}
-
- {#if done === 'approved'}
+{#each approvals.filter(a => !done || a.executionId !== doneId) as approval (approval.executionId)}
+ {#if !done || approval.executionId !== doneId}
+
+
+
+ {approval.action} on {approval.target} requires approval
+
+ {#if pending && doneId === approval.executionId}
+
+ {:else}
+
+
+ {/if}
+
+ {:else if done === 'approved'}
+
Approved. The action is running.
- {:else}
+
+ {:else}
+
Denied.
- {/if}
-
-{/if}
+
+ {/if}
+{/each}
diff --git a/web/src/lib/components/ToolCallGroup.svelte b/web/src/lib/components/ToolCallGroup.svelte
index 8658be5..c4eea6f 100644
--- a/web/src/lib/components/ToolCallGroup.svelte
+++ b/web/src/lib/components/ToolCallGroup.svelte
@@ -10,7 +10,7 @@
let { tools, active = false }: { tools: ToolCallResult[]; active?: boolean } = $props()
let open = $state(false)
- let wasActive = active
+ let wasActive = $state(active)
$effect(() => {
if (active && !wasActive) {
diff --git a/web/src/lib/stores/chat.ts b/web/src/lib/stores/chat.ts
index 2a05f56..bc03c7a 100644
--- a/web/src/lib/stores/chat.ts
+++ b/web/src/lib/stores/chat.ts
@@ -2,11 +2,38 @@ import { writable, get } from 'svelte/store'
import { streamChat, fetchSessions, fetchMessages, deleteSession as apiDeleteSession } from '$lib/api'
import type { ChatEvent, Session, Message } from '$lib/api'
+export interface PendingApproval {
+ executionId: string
+ action: string
+ target: string
+}
+
export interface ChatMessage {
id: string
role: 'user' | 'assistant'
text: string
tools: ToolCallResult[]
+ pendingApprovals: PendingApproval[]
+}
+
+const APPROVAL_RE = /execution\s+([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})/i
+
+function extractApprovals(tools: ToolCallResult[]): PendingApproval[] {
+ const out: PendingApproval[] = []
+ for (const t of tools) {
+ if (t.name !== 'request_execution' || t.type !== 'tool_result') continue
+ const text = typeof t.result === 'string' ? t.result : JSON.stringify(t.result ?? '')
+ if (!text.includes('requires approval')) continue
+ const m = text.match(APPROVAL_RE)
+ if (m) {
+ out.push({
+ executionId: m[1],
+ action: t.args?.action ?? 'unknown',
+ target: t.args?.target ?? 'unknown'
+ })
+ }
+ }
+ return out
}
export interface ToolCallResult {
@@ -57,12 +84,16 @@ export async function loadSessionMessages(sessionId: string) {
currentSession.set(sessionId)
const msgs = await fetchMessages(sessionId)
sessionMessages.set(msgs)
- const chatMsgs: ChatMessage[] = msgs.map((m) => ({
- id: m.id,
- role: m.role as 'user' | 'assistant',
- text: m.content?.text ?? (typeof m.content === 'string' ? m.content : ''),
- tools: mergeToolCalls(m.content?.tool_calls)
- }))
+ const chatMsgs: ChatMessage[] = msgs.map((m) => {
+ const tools = mergeToolCalls(m.content?.tool_calls)
+ return {
+ id: m.id,
+ role: m.role as 'user' | 'assistant',
+ text: m.content?.text ?? (typeof m.content === 'string' ? m.content : ''),
+ tools,
+ pendingApprovals: extractApprovals(tools)
+ }
+ })
messages.set(chatMsgs)
}
@@ -74,7 +105,8 @@ export function sendMessage(text: string) {
id: mid(),
role: 'user',
text,
- tools: []
+ tools: [],
+ pendingApprovals: []
}
messages.update((ms) => [...ms, userMsg])
@@ -82,7 +114,8 @@ export function sendMessage(text: string) {
id: mid(),
role: 'assistant',
text: '',
- tools: []
+ tools: [],
+ pendingApprovals: []
}
messages.update((ms) => [...ms, assistantMsg])
@@ -147,6 +180,13 @@ export function sendMessage(text: string) {
return [...ms]
})
} else if (ev.type === 'done') {
+ messages.update((ms) => {
+ const last = ms[ms.length - 1]
+ if (last && last.role === 'assistant') {
+ last.pendingApprovals = extractApprovals(last.tools)
+ }
+ return [...ms]
+ })
currentSession.set(ev.data?.session_id ?? ev.session_id)
} else if (ev.type === 'error') {
error.set(ev.data)
diff --git a/web/src/pages/Chat.svelte b/web/src/pages/Chat.svelte
index c169352..ff859a3 100644
--- a/web/src/pages/Chat.svelte
+++ b/web/src/pages/Chat.svelte
@@ -114,7 +114,7 @@
{:else}