akseljoonas HF Staff Claude Opus 4.6 commited on
Commit
1416398
·
1 Parent(s): 12b7c8f

fix: use backend is_processing as source of truth on refresh

Browse files

Replace 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>

Files changed (1) hide show
  1. frontend/src/hooks/useAgentChat.ts +16 -22
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
  }