diff --git a/frontend/src/app/pages/AgentChat/AgentChat.tsx b/frontend/src/app/pages/AgentChat/AgentChat.tsx index a1651382..f354ff7f 100644 --- a/frontend/src/app/pages/AgentChat/AgentChat.tsx +++ b/frontend/src/app/pages/AgentChat/AgentChat.tsx @@ -383,6 +383,13 @@ const AgentChat: React.FC = ({ sessionId: sessionIdProp, onClose const [activateError, setActivateError] = useState(null); // Holds the last non-empty suggestions so the docked banner's exit fade renders them instead of going blank the instant the array is cleared. const mcpSnapshotRef = useRef>([]); + // The connect nudge is ephemeral (ENG-350): auto-clears locally after 12s; a real dismiss (the x) also persists. + const mcpSuggestionCount = session?.mcp_suggestions?.length ?? 0; + useEffect(() => { + if (!id || mcpSuggestionCount === 0) return; + const t = window.setTimeout(() => dispatch(clearMcpSuggestions({ sessionId: id })), 12000); + return () => window.clearTimeout(t); + }, [id, mcpSuggestionCount, dispatch]); const [mode, setMode] = useState('agent'); const [model, setModel] = useState('opus-5'); // Workflow build chat only: brief "this model now runs the workflow" notice when the user switches models, so the run-model change isn't silent. @@ -989,6 +996,8 @@ const AgentChat: React.FC = ({ sessionId: sessionIdProp, onClose const handleApprove = (requestId: string, updatedInput?: Record, trustPattern?: boolean, alwaysAllow?: boolean) => { dispatch(handleApproval({ requestId, behavior: 'allow', updatedInput, trustPattern, setAlwaysAllow: alwaysAllow })); + // Answering pins to bottom like a composer send, or the agent's next message lands below the fold (ENG-345). + scrollToBottom(); }; const handleDeny = (requestId: string, message?: string) => { @@ -1863,7 +1872,7 @@ const AgentChat: React.FC = ({ sessionId: sessionIdProp, onClose } return ( - + {compactionChip} ); @@ -2015,84 +2024,6 @@ const AgentChat: React.FC = ({ sessionId: sessionIdProp, onClose /> )} - {/* Connect offer sits BELOW the latest reply (where the eye is), not at the top of the - transcript where the auto-scroll-to-bottom buries it. Suggest-only; activation is the - user's click through the gated MCPActivate endpoint. */} - {(session.mcp_suggestions && session.mcp_suggestions.length > 0) && ( - - {session.mcp_suggestions.map((s) => ( - - - Connect{' '} - {s.title} - {' '}so the agent can do this - - { - if (activatingMcp) return; - setActivateError(null); - setActivatingMcp(s.id); - try { - const headers: Record = { 'Content-Type': 'application/json' }; - const tok = (() => { try { return getAuthToken(); } catch { return ''; } })(); - if (tok) headers['Authorization'] = `Bearer ${tok}`; - const r = await fetch(`${API_BASE}/mcp-meta/activate`, { - method: 'POST', - headers, - body: JSON.stringify({ - server_name: s.id.toLowerCase().replace(/\s+/g, '-'), - reason: s.reason || 'preflight suggestion', - parent_session_id: session.id, - }), - }); - const body = await r.json().catch(() => ({} as any)); - if (!r.ok) { - setActivateError(`Activation failed (${r.status})`); - } else if (body?.status === 'unknown_server') { - // Not yet connected; jump to Actions so the user can finish OAuth. - openMarketplace('my-connectors'); - } else if (id) { - dispatch(clearMcpSuggestions({ sessionId: id })); - } - } catch (e: any) { - setActivateError(e?.message || 'Activation failed'); - } finally { - setActivatingMcp(null); - } - }} - sx={{ - border: 'none', - background: 'none', - p: 0, - color: c.accent.primary, - cursor: activatingMcp === s.id ? 'wait' : 'pointer', - opacity: activatingMcp === s.id ? 0.5 : 1, - '&:hover': { textDecoration: activatingMcp ? 'none' : 'underline' }, - flexShrink: 0, - }} - > - {activatingMcp === s.id ? 'Connecting…' : 'Connect'} - - - ))} - {activateError && ( - - {activateError} - - )} - id && dispatch(clearMcpSuggestions({ sessionId: id }))} - sx={{ alignSelf: 'flex-start', color: c.text.muted, cursor: 'pointer', fontSize: '0.75rem', '&:hover': { color: c.text.secondary } }} - > - Dismiss - - - )} {/* The surfaces this agent is driving, live inside the chat: browser snapshots + built apps, each one click from popping out onto the canvas. */} {!isDraft && id && } @@ -2460,7 +2391,8 @@ const AgentChat: React.FC = ({ sessionId: sessionIdProp, onClose = ({ sessionId: sessionIdProp, onClose > × - - Looks like this might need an integration - - - Activating one of these will let the agent answer in a single round-trip. - {display.map((s) => ( - - {s.title} + + Connect {s.title} - {s.reason && ( - - {s.reason} - - )}