From e21b27607000fd50e4a49826de71bfbd463b82fe Mon Sep 17 00:00:00 2001 From: Sergio Marcelino Date: Mon, 18 May 2026 09:43:06 -0300 Subject: [PATCH] feat: web harness improvements --- console/web/src/components/chat/ChatView.tsx | 43 +++++- .../components/chat/FunctionCallMessage.tsx | 35 ++++- .../web/src/components/chat/MessageList.tsx | 19 ++- console/web/src/lib/backend/real.ts | 49 +++---- console/web/src/lib/backend/translate.ts | 11 +- harness-node/src/provider-anthropic/auth.ts | 24 ++-- harness-node/src/provider-anthropic/stream.ts | 125 +++++++++--------- .../src/turn-orchestrator/run-start.ts | 1 + .../src/turn-orchestrator/states/functions.ts | 6 +- .../turn-orchestrator/states/provisioning.ts | 9 +- .../src/turn-orchestrator/system-prompt.ts | 50 ++++--- .../src/turn-orchestrator/transitions.ts | 43 +++--- .../turn-orchestrator/system-prompt.test.ts | 65 +++++++++ 13 files changed, 319 insertions(+), 161 deletions(-) diff --git a/console/web/src/components/chat/ChatView.tsx b/console/web/src/components/chat/ChatView.tsx index 56570cf5b..9a34b835d 100644 --- a/console/web/src/components/chat/ChatView.tsx +++ b/console/web/src/components/chat/ChatView.tsx @@ -114,6 +114,17 @@ export function ChatView({ break } case 'fcall-start': { + /* Defensive turn boundary: if a provider emits fcall-start + before the assistant's message_end, finalize the in-flight + assistant message so the next turn's text doesn't merge + into it. */ + if (assistantId) { + onPatchMessage(conversationId, assistantId, { + streaming: false, + }) + assistantId = null + assistantBuffer = '' + } const msg: FunctionCallMessage = { id: uid(), role: 'function-call', @@ -163,6 +174,12 @@ export function ChatView({ case 'assistant-end': { if (!assistantId) break onPatchMessage(conversationId, assistantId, { streaming: false }) + /* Per-turn boundary: clear pointers so the next turn's first + token creates a brand-new assistant message instead of + appending to this one. translate.ts emits assistant-end on + every assistant message_end, not just the final agent_end. */ + assistantId = null + assistantBuffer = '' break } } @@ -204,6 +221,30 @@ export function ChatView({ abortRef.current?.abort() }, []) + /* "agent is thinking" indicator: stream is active but no streaming + thought/assistant message is currently visible. Covers two gaps: + (a) right after submit, before the first token/thought/fcall arrives; + (b) after each fcall-end, while the harness transitions through + function_finalize → steering_check → next turn's provider stream. + A streaming assistant/thought message renders its own "thinking…" + shimmer when content is empty, so we don't double-render those. */ + const isThinking = + isStreaming && + (() => { + const last = + conversation.messages[conversation.messages.length - 1] ?? null + if (!last) return true + if (last.role === 'user') return true + if ( + last.role === 'function-call' && + !last.running && + !last.pendingApproval + ) { + return true + } + return false + })() + return (
@@ -223,7 +264,7 @@ export function ChatView({
- +