Spaces:
Running on CPU Upgrade
Running on CPU Upgrade
Commit ·
1416398
1
Parent(s): 12b7c8f
fix: use backend is_processing as source of truth on refresh
Browse filesReplace message-based state inference with the backend's is_processing
flag. Message inference is fundamentally broken — completed tool results
make tools look "done" even when the agent is mid-turn.
Also add resume: true to useChat so the SDK calls reconnectToStream()
on mount, re-subscribing to live events if the agent is still running.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
frontend/src/hooks/useAgentChat.ts
CHANGED
|
@@ -265,6 +265,10 @@ export function useAgentChat({ sessionId, isActive, onReady, onError, onSessionD
|
|
| 265 |
messages: initialMessages,
|
| 266 |
transport: transportRef.current!,
|
| 267 |
experimental_throttle: 80,
|
|
|
|
|
|
|
|
|
|
|
|
|
| 268 |
// After all approval responses are set, auto-send to continue the agent loop.
|
| 269 |
// Without this, addToolApprovalResponse only updates the UI — it won't trigger
|
| 270 |
// sendMessages on the transport.
|
|
@@ -294,8 +298,10 @@ export function useAgentChat({ sessionId, isActive, onReady, onError, onSessionD
|
|
| 294 |
if (cancelled) return;
|
| 295 |
|
| 296 |
let pendingIds: Set<string> | undefined;
|
|
|
|
| 297 |
if (infoRes.ok) {
|
| 298 |
const info = await infoRes.json();
|
|
|
|
| 299 |
if (info.pending_approval && Array.isArray(info.pending_approval)) {
|
| 300 |
pendingIds = new Set(
|
| 301 |
info.pending_approval.map((t: { tool_call_id: string }) => t.tool_call_id)
|
|
@@ -313,30 +319,18 @@ export function useAgentChat({ sessionId, isActive, onReady, onError, onSessionD
|
|
| 313 |
if (uiMsgs.length > 0) {
|
| 314 |
chat.setMessages(uiMsgs);
|
| 315 |
saveMessages(sessionId, uiMsgs);
|
| 316 |
-
|
| 317 |
-
// Derive processing state from hydrated messages so the input
|
| 318 |
-
// doesn't flash as enabled before the agent loop resumes.
|
| 319 |
-
const lastAssistant = [...uiMsgs].reverse().find(m => m.role === 'assistant');
|
| 320 |
-
if (lastAssistant) {
|
| 321 |
-
const hasPending = lastAssistant.parts.some(
|
| 322 |
-
p => p.type === 'dynamic-tool' && p.state === 'approval-requested',
|
| 323 |
-
);
|
| 324 |
-
const hasRunning = lastAssistant.parts.some(
|
| 325 |
-
p => p.type === 'dynamic-tool' && (p.state === 'input-available' || p.state === 'input-streaming'),
|
| 326 |
-
);
|
| 327 |
-
if (hasPending) {
|
| 328 |
-
updateSession(sessionId, { activityStatus: { type: 'waiting-approval' } });
|
| 329 |
-
} else if (hasRunning) {
|
| 330 |
-
// Extract the actual tool name from the last in-progress tool part
|
| 331 |
-
const runningPart = lastAssistant.parts.find(
|
| 332 |
-
p => p.type === 'dynamic-tool' && (p.state === 'input-available' || p.state === 'input-streaming'),
|
| 333 |
-
);
|
| 334 |
-
const runningToolName = (runningPart && 'toolName' in runningPart) ? runningPart.toolName : undefined;
|
| 335 |
-
updateSession(sessionId, { isProcessing: true, activityStatus: { type: 'tool', toolName: runningToolName || 'sandbox' } });
|
| 336 |
-
}
|
| 337 |
-
}
|
| 338 |
}
|
| 339 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 340 |
} catch {
|
| 341 |
/* backend unreachable -- localStorage fallback is fine */
|
| 342 |
}
|
|
|
|
| 265 |
messages: initialMessages,
|
| 266 |
transport: transportRef.current!,
|
| 267 |
experimental_throttle: 80,
|
| 268 |
+
// On mount, the SDK calls transport.reconnectToStream() which checks
|
| 269 |
+
// is_processing and subscribes to the live event stream if the agent
|
| 270 |
+
// is mid-turn. Without this, page refresh kills live updates.
|
| 271 |
+
resume: true,
|
| 272 |
// After all approval responses are set, auto-send to continue the agent loop.
|
| 273 |
// Without this, addToolApprovalResponse only updates the UI — it won't trigger
|
| 274 |
// sendMessages on the transport.
|
|
|
|
| 298 |
if (cancelled) return;
|
| 299 |
|
| 300 |
let pendingIds: Set<string> | undefined;
|
| 301 |
+
let backendIsProcessing = false;
|
| 302 |
if (infoRes.ok) {
|
| 303 |
const info = await infoRes.json();
|
| 304 |
+
backendIsProcessing = !!info.is_processing;
|
| 305 |
if (info.pending_approval && Array.isArray(info.pending_approval)) {
|
| 306 |
pendingIds = new Set(
|
| 307 |
info.pending_approval.map((t: { tool_call_id: string }) => t.tool_call_id)
|
|
|
|
| 319 |
if (uiMsgs.length > 0) {
|
| 320 |
chat.setMessages(uiMsgs);
|
| 321 |
saveMessages(sessionId, uiMsgs);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 322 |
}
|
| 323 |
}
|
| 324 |
+
|
| 325 |
+
// Use the backend's is_processing flag as the source of truth.
|
| 326 |
+
// Message-based inference doesn't work because completed tool
|
| 327 |
+
// results make tools look "done" even when the agent is still
|
| 328 |
+
// mid-turn and about to call more tools.
|
| 329 |
+
if (backendIsProcessing) {
|
| 330 |
+
updateSession(sessionId, { isProcessing: true, activityStatus: { type: 'thinking' } });
|
| 331 |
+
} else if (pendingIds && pendingIds.size > 0) {
|
| 332 |
+
updateSession(sessionId, { activityStatus: { type: 'waiting-approval' } });
|
| 333 |
+
}
|
| 334 |
} catch {
|
| 335 |
/* backend unreachable -- localStorage fallback is fine */
|
| 336 |
}
|