fix: structured approvals + ToolCallGroup reactivity
- Replace text-based regex parsing in InlineApproval with structured pendingApprovals extracted from request_execution tool results. The tool result text is deterministic (not LLM-generated), making UUID extraction reliable regardless of how the LLM rephrases the response. - Fix ToolCallGroup reactivity: wasActive = active captured initial value. Now uses (active) so re-runs on prop changes. - Extract approvals in both live streaming (done event) and history loading for consistent behavior on resumed sessions.
This commit is contained in:
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user