diff --git a/frontend/src/app/pages/AgentChat/tool-bubbles/CompactMcpBubble.tsx b/frontend/src/app/pages/AgentChat/tool-bubbles/CompactMcpBubble.tsx index 9755ea88..d57c1d57 100644 --- a/frontend/src/app/pages/AgentChat/tool-bubbles/CompactMcpBubble.tsx +++ b/frontend/src/app/pages/AgentChat/tool-bubbles/CompactMcpBubble.tsx @@ -12,7 +12,6 @@ import SearchIcon from '@mui/icons-material/Search'; import { AgentMessage } from '@/shared/state/agentsSlice'; import { useClaudeTokens } from '@/shared/styles/ThemeContext'; import { getToolLabel } from '../parsing/toolLabels'; -import BrowserAgentInlineFeed from '../shell/BrowserAgentInlineFeed'; import { GoogleServiceIcon } from '../mcp-cards/GoogleServiceIcon'; import { ElapsedTimer, formatElapsed } from '../parsing/toolBubbleChrome'; import { useTermColors } from '../parsing/toolColorize'; @@ -192,12 +191,6 @@ export const CompactMcpBubble: React.FC = ({ '&::-webkit-scrollbar-thumb': { background: tc.SCROLLBAR_THUMB, borderRadius: 3 }, }} > - {isBrowserAgent && sessionId && ( - - )} {parsedResult && parsedResult.type === 'mcp' ? ( ) : parsedResult ? ( diff --git a/frontend/src/app/pages/AgentChat/tool-bubbles/DefaultToolBubble.tsx b/frontend/src/app/pages/AgentChat/tool-bubbles/DefaultToolBubble.tsx index b6a57533..bfcc11ac 100644 --- a/frontend/src/app/pages/AgentChat/tool-bubbles/DefaultToolBubble.tsx +++ b/frontend/src/app/pages/AgentChat/tool-bubbles/DefaultToolBubble.tsx @@ -13,7 +13,6 @@ import { AgentMessage } from '@/shared/state/agentsSlice'; import { useClaudeTokens } from '@/shared/styles/ThemeContext'; import { useMountReveal } from './useMountReveal'; import { getToolLabelWithInput } from '../parsing/toolLabels'; -import BrowserAgentInlineFeed from '../shell/BrowserAgentInlineFeed'; import { GoogleServiceIcon } from '../mcp-cards/GoogleServiceIcon'; import { ElapsedTimer, formatElapsed } from '../parsing/toolBubbleChrome'; import { useTermColors, colorizeInput, colorizeOutput } from '../parsing/toolColorize'; @@ -222,9 +221,9 @@ export const DefaultToolBubble: React.FC = ({ ) : ( = ({ )} - {isBrowserAgent && sessionId && ( - - )} - {parsedResult && parsedResult.type === 'mcp' ? ( ) : parsedResult ? ( diff --git a/frontend/src/app/pages/Dashboard/cards/BrowserAgentOverlay.tsx b/frontend/src/app/pages/Dashboard/cards/BrowserAgentOverlay.tsx index fbe45070..161bd98b 100644 --- a/frontend/src/app/pages/Dashboard/cards/BrowserAgentOverlay.tsx +++ b/frontend/src/app/pages/Dashboard/cards/BrowserAgentOverlay.tsx @@ -9,12 +9,9 @@ import SendIcon from '@mui/icons-material/Send'; import SmartToyOutlinedIcon from '@mui/icons-material/SmartToyOutlined'; import PanToolOutlinedIcon from '@mui/icons-material/PanToolOutlined'; import StopIcon from '@mui/icons-material/Stop'; -import OpenInFullIcon from '@mui/icons-material/OpenInFull'; -import CloseFullscreenIcon from '@mui/icons-material/CloseFullscreen'; import CheckCircleOutlineIcon from '@mui/icons-material/CheckCircleOutline'; import ErrorOutlineIcon from '@mui/icons-material/ErrorOutline'; -import { AgentSession, AgentMessage, stopAgent, handleApproval } from '@/shared/state/agentsSlice'; -import { useStreamingMessage } from '@/shared/state/streamingSlice'; +import { AgentSession, stopAgent, handleApproval } from '@/shared/state/agentsSlice'; import { useAppDispatch, useAppSelector } from '@/shared/hooks'; import { useClaudeTokens } from '@/shared/styles/ThemeContext'; @@ -24,58 +21,12 @@ interface Props { browserHeight: number; } -// Same red as this card's own error icon, so a failed step and a failed run read as one thing. -const P_FAILED_COLOR = '#f87171'; - -interface LogEntry { - type: 'thought' | 'action' | 'result' | 'skip'; - text: string; - /** Wall-clock of the message, so a stalled run shows WHERE it stalled. */ - at?: string; - /** Only on a result: did the tool that just ran actually work? */ - ok?: boolean; -} - -function summarizeMessage(msg: AgentMessage): LogEntry { - if (msg.role === 'assistant' && typeof msg.content === 'string') { - const trimmed = msg.content.trim(); - if (!trimmed) return { type: 'skip', text: '' }; - return { type: 'thought', text: trimmed, at: msg.timestamp }; - } - - if (msg.role === 'tool_call') { - const content = typeof msg.content === 'string' ? (() => { try { return JSON.parse(msg.content); } catch { return {}; } })() : msg.content; - const tool = content?.tool || content?.name || '?'; - const input = content?.input || {}; - let brief = ''; - switch (tool) { - case 'BrowserNavigate': brief = `Navigate → ${input.url || '...'}`; break; - case 'BrowserClick': brief = `Click ${input.selector || '...'}`; break; - case 'BrowserType': brief = `Type "${(input.text || '').slice(0, 30)}${(input.text || '').length > 30 ? '…' : ''}" into ${input.selector || '...'}`; break; - case 'BrowserScreenshot': brief = 'Screenshot'; break; - case 'BrowserGetText': brief = 'Read page text'; break; - case 'BrowserGetElements': brief = `Inspect elements${input.selector ? ` (${input.selector})` : ''}`; break; - case 'BrowserEvaluate': brief = `Evaluate JS`; break; - default: brief = tool; - } - return { type: 'action', text: brief, at: msg.timestamp }; - } - - if (msg.role === 'tool_result') { - const content = typeof msg.content === 'string' ? (() => { try { return JSON.parse(msg.content); } catch { return {}; } })() : msg.content; - // Older results predate the `ok` flag; absent means "no reason to think it failed", which keeps - // a resumed session from repainting its whole history red. - return { type: 'result', text: '', ok: content?.ok !== false }; - } - - return { type: 'skip', text: '' }; -} - +// The action log this overlay used to carry is gone on purpose (ENG-201, Eric's call): the live +// page IS the progress display, so the overlay is now just a slim status pill with a Stop button, +// or the full intervention card when the model itself asked for help. const BrowserAgentOverlay: React.FC = ({ session, browserWidth, browserHeight }) => { const c = useClaudeTokens(); const dispatch = useAppDispatch(); - const scrollRef = useRef(null); - const [expanded, setExpanded] = useState(false); const [confirmStop, setConfirmStop] = useState(false); const [fadeOut, setFadeOut] = useState(false); const [hidden, setHidden] = useState(false); @@ -94,7 +45,6 @@ const BrowserAgentOverlay: React.FC = ({ session, browserWidth, browserHe const isRunning = session.status === 'running' || session.status === 'waiting_approval'; const browserDone = session.status === 'completed' || session.status === 'error' || session.status === 'stopped'; - const streamingMessage = useStreamingMessage(session.id); // Only fade+hide when parent is finished too; otherwise show a "waiting" state between sub-tasks. const isDone = browserDone && !parentStillActive; @@ -137,12 +87,6 @@ const BrowserAgentOverlay: React.FC = ({ session, browserWidth, browserHe return () => { if (hideTimer.current) clearTimeout(hideTimer.current); }; }, [fadeOut]); - useEffect(() => { - if (scrollRef.current) { - scrollRef.current.scrollTop = scrollRef.current.scrollHeight; - } - }, [session.messages.length, streamingMessage]); - const handleStop = useCallback(() => { if (!confirmStop) { setConfirmStop(true); @@ -160,38 +104,6 @@ const BrowserAgentOverlay: React.FC = ({ session, browserWidth, browserHe const accentColor = c.accent.primary; - // A result carries no text of its own; its job is to say whether the action just above it - // worked. Fold it back onto that action so a failed click reads as failed instead of vanishing, - // which is what happened before: the overlay dropped results entirely and drew every action the - // same whether it succeeded or errored. - const entries: LogEntry[] = []; - for (const e of session.messages.map(summarizeMessage)) { - if (e.type === 'skip') continue; - if (e.type === 'result') { - for (let i = entries.length - 1; i >= 0; i--) { - if (entries[i].type === 'action') { - if (e.ok === false) entries[i] = { ...entries[i], ok: false }; - break; - } - } - continue; - } - entries.push(e); - } - - if (streamingMessage && streamingMessage.role === 'assistant' && streamingMessage.content) { - entries.push({ type: 'thought', text: streamingMessage.content }); - } - - const collapsedW = Math.min(300, browserWidth - 24); - const collapsedH = Math.min(200, browserHeight - 24); - const expandedW = Math.min(Math.floor(browserWidth * 0.55), browserWidth - 24); - const expandedH = Math.min(Math.floor(browserHeight * 0.6), browserHeight - 24); - - const panelW = intervention ? Math.min(340, browserWidth - 24) : expanded ? expandedW : collapsedW; - // Intervention auto-sizes to fit content; fixed height would clip the Done button. - const panelH = intervention ? undefined : expanded ? expandedH : collapsedH; - if (hidden) return null; return ( @@ -202,10 +114,11 @@ const BrowserAgentOverlay: React.FC = ({ session, browserWidth, browserHe position: 'absolute', bottom: 12, right: 12, - width: panelW, - ...(panelH != null ? { height: panelH } : { maxHeight: Math.min(320, browserHeight - 24) }), + width: intervention ? Math.min(340, browserWidth - 24) : 'auto', + maxWidth: browserWidth - 24, + ...(intervention ? { maxHeight: Math.min(320, browserHeight - 24) } : {}), zIndex: 18, - borderRadius: '12px', + borderRadius: intervention ? '12px' : '999px', bgcolor: 'rgba(15, 15, 15, 0.88)', backdropFilter: 'blur(16px)', border: `1px solid ${intervention ? 'rgba(245,158,11,0.4)' : `${accentColor}30`}`, @@ -213,7 +126,7 @@ const BrowserAgentOverlay: React.FC = ({ session, browserWidth, browserHe display: 'flex', flexDirection: 'column', overflow: 'hidden', - transition: 'width 0.25s ease, height 0.25s ease, opacity 0.4s ease', + transition: 'width 0.25s ease, opacity 0.4s ease', opacity: fadeOut ? 0 : isDone ? 0.7 : 1, animation: 'overlay-enter 0.3s ease-out', '@keyframes overlay-enter': { @@ -222,7 +135,7 @@ const BrowserAgentOverlay: React.FC = ({ session, browserWidth, browserHe }, }} > - {/* Header */} + {/* Header (the whole pill, when there is no intervention) */} = ({ session, browserWidth, browserHe gap: 0.75, px: 1.25, py: 0.75, - borderBottom: `1px solid rgba(255,255,255,0.08)`, + borderBottom: intervention ? `1px solid rgba(255,255,255,0.08)` : 'none', flexShrink: 0, }} > @@ -281,23 +194,6 @@ const BrowserAgentOverlay: React.FC = ({ session, browserWidth, browserHe : 'Browser Agent'} - - setExpanded((e) => !e)} - sx={{ - color: 'rgba(255,255,255,0.5)', - p: 0.3, - '&:hover': { color: 'rgba(255,255,255,0.8)' }, - }} - > - {expanded - ? - : - } - - - {isRunning && ( = ({ session, browserWidth, browserHe )} - {/* Body: intervention prompt OR scrollable action log */} - {intervention ? ( + {/* Body: only the intervention prompt ever needs one */} + {intervention && ( - {/* The run is stopped dead until somebody reads this, so it reads like a headline, not a footnote. The line that used to sit under it ("resolve it above, then click Done") was 10px at 30% opacity, the faintest thing on the card, and it only restated the amber button two rows down. */} + {/* The run is stopped dead until somebody reads this, so it reads like a headline, not a footnote. */} {interventionProblem} @@ -407,110 +303,6 @@ const BrowserAgentOverlay: React.FC = ({ session, browserWidth, browserHe )} - ) : ( - - {entries.length === 0 && isRunning && ( - - Starting... - - )} - - {entries.map((entry, i) => ( - - {entry.type === 'thought' ? ( - <> - - - {entry.text} - - - ) : ( - <> - - - {entry.ok === false ? `${entry.text} — failed` : entry.text} - - - )} - {expanded && entry.at && ( - - {new Date(entry.at).toLocaleTimeString([], { hour12: false })} - - )} - - ))} - )} ); diff --git a/frontend/src/shared/state/agentsSlice.ts b/frontend/src/shared/state/agentsSlice.ts index ea1f240f..3e834af2 100644 --- a/frontend/src/shared/state/agentsSlice.ts +++ b/frontend/src/shared/state/agentsSlice.ts @@ -587,15 +587,6 @@ export const resumeSession = createAsyncThunk( } ); -export const fetchBrowserAgentChildren = createAsyncThunk( - 'agents/fetchBrowserAgentChildren', - async (parentSessionId: string) => { - const res = await fetch(`${AGENTS_API}/sessions/${parentSessionId}/browser-agents`); - const data = await res.json(); - return data.sessions as AgentSession[]; - } -); - const agentsSlice = createSlice({ name: 'agents', initialState, @@ -1447,22 +1438,6 @@ const agentsSlice = createSlice({ state.trackedNotificationIds = state.trackedNotificationIds.filter((id) => id !== sessionId); } }) - .addCase(fetchBrowserAgentChildren.fulfilled, (state, action) => { - for (const session of action.payload) { - const existing = state.sessions[session.id]; - if (!existing) { - state.sessions[session.id] = { - ...session, - name: normalizeSessionName(session.name), - tool_group_meta: session.tool_group_meta ?? {}, - pending_approvals: session.pending_approvals ?? [], - }; - } else if (existing.messages.length === 0 && session.messages.length > 0) { - // Hydrate a child the trimmed session-list poll left message-less; don't touch one mid-stream (already has messages). - existing.messages = session.messages; - } - } - }) .addCase(searchHistory.pending, (state) => { state.historySearch.loading = true; })