@@ -9,6 +9,7 @@ import { useNotificationStore, ALWAYS_ALLOW_TOOLS_KEY } from '../stores/notifica
99import type { GaiaNotification } from '../types/agent' ;
1010import * as api from '../services/api' ;
1111import { log } from '../utils/logger' ;
12+ import ProgressStrip from './ProgressStrip' ;
1213import { getSessionHash } from '../utils/format' ;
1314import { bugReportUrl } from './UnsupportedFeature' ;
1415import type { Message , StreamEvent , AgentStep , Attachment , Session } from '../types' ;
@@ -121,8 +122,8 @@ function agentEventToStep(event: StreamEvent, stepIdRef: React.MutableRefObject<
121122 case 'tool_start' :
122123 return {
123124 id, type : 'tool' ,
124- // Label is determined by AgentActivity based on tool name
125- label : 'Using tool' ,
125+ // Prefer server-provided human-friendly label when available
126+ label : event . display_label || 'Using tool' ,
126127 tool : event . tool ,
127128 detail : event . detail ,
128129 active : true , timestamp : ts ,
@@ -175,6 +176,8 @@ export function ChatView({ sessionId, onCreateAgent, onAgentChange }: ChatViewPr
175176 agents, activeAgentId, setActiveAgentId,
176177 } = useChatStore ( ) ;
177178
179+ const surfacedCards = useChatStore ( ( s ) => s . surfacedCards ) ;
180+
178181 const { addNotification } = useNotificationStore ( ) ;
179182 const pendingPrompt = useChatStore ( ( s ) => s . pendingPrompt ) ;
180183
@@ -192,6 +195,8 @@ export function ChatView({ sessionId, onCreateAgent, onAgentChange }: ChatViewPr
192195 const [ attachments , setAttachments ] = useState < Attachment [ ] > ( [ ] ) ;
193196 const [ docsExpanded , setDocsExpanded ] = useState ( false ) ;
194197 const [ deletingMsgId , setDeletingMsgId ] = useState < number | null > ( null ) ;
198+ // Progress strip state for streaming tool progress
199+ const [ progress , setProgress ] = useState < { label ?: string ; detail ?: string ; latencyMs ?: number ; active ?: boolean } > ( { active : false } ) ;
195200 // Agent picker dropdown state
196201 const [ agentPickerOpen , setAgentPickerOpen ] = useState ( false ) ;
197202 const agentPickerRef = useRef < HTMLDivElement > ( null ) ;
@@ -838,6 +843,16 @@ export function ChatView({ sessionId, onCreateAgent, onAgentChange }: ChatViewPr
838843 } ;
839844 }
840845 updateLastToolStep ( updates ) ;
846+ // Stream incremental surfaced cards for retrieval/classify results
847+ if ( event . result_data ?. chunks && event . result_data . chunks . length > 0 ) {
848+ const addCard = useChatStore . getState ( ) . addSurfacedCard ;
849+ for ( const c of event . result_data . chunks ) {
850+ addCard ( { id : `${ Date . now ( ) } -${ Math . random ( ) . toString ( 36 ) . slice ( 2 , 8 ) } ` , preview : c . preview , content : c . content , source : c . source , score : c . score } ) ;
851+ }
852+ }
853+ if ( typeof event . latency_ms === 'number' ) {
854+ setProgress ( ( p ) => ( { ...p , latencyMs : event . latency_ms } ) ) ;
855+ }
841856 return ;
842857 }
843858 // Tool args update the last TOOL step with detail
@@ -911,6 +926,7 @@ export function ChatView({ sessionId, onCreateAgent, onAgentChange }: ChatViewPr
911926 addAgentStep ( step ) ;
912927 if ( event . type === 'tool_start' ) {
913928 toolOccurredRef . current = true ;
929+ setProgress ( { label : event . display_label || event . tool || 'Using tool' , detail : event . detail , active : true } ) ;
914930 }
915931 }
916932 } ,
@@ -954,6 +970,9 @@ export function ChatView({ sessionId, onCreateAgent, onAgentChange }: ChatViewPr
954970 clearStreamContent ( ) ;
955971 clearAgentSteps ( ) ;
956972
973+ // Clear progress strip
974+ setProgress ( ( p ) => ( { ...p , active : false } ) ) ;
975+
957976 // Refocus input so user can immediately type the next message
958977 if ( inputRef . current ) inputRef . current . focus ( ) ;
959978
@@ -1128,6 +1147,19 @@ export function ChatView({ sessionId, onCreateAgent, onAgentChange }: ChatViewPr
11281147 } ) . catch ( ( ) => {
11291148 log . ui . warn ( 'Clipboard write failed' ) ;
11301149 } ) ;
1150+ // Keep a reference so UI can cancel the stream
1151+ abortRef . current = controller ;
1152+
1153+ const handleCancel = ( ) => {
1154+ try {
1155+ if ( abortRef . current ) {
1156+ abortRef . current . abort ( ) ;
1157+ }
1158+ } catch ( err ) {
1159+ console . error ( 'Failed to abort stream' , err ) ;
1160+ }
1161+ setProgress ( ( p ) => ( { ...p , active : false } ) ) ;
1162+ } ;
11311163 } , [ sessionId ] ) ;
11321164
11331165 // Title editing
@@ -1297,6 +1329,28 @@ export function ChatView({ sessionId, onCreateAgent, onAgentChange }: ChatViewPr
12971329 </ div >
12981330 </ header >
12991331
1332+ { /* Progress strip (shows current tool/status during streaming) */ }
1333+ { progress ?. active && (
1334+ < div style = { { padding : '8px 12px' } } >
1335+ < ProgressStrip label = { progress . label } detail = { progress . detail } latencyMs = { progress . latencyMs } active = { progress . active } onCancel = { ( ) => { if ( abortRef . current ) abortRef . current . abort ( ) ; setProgress ( ( p ) => ( { ...p , active : false } ) ) ; } } />
1336+ </ div >
1337+ ) }
1338+
1339+ { /* Surfaced incremental cards (streamed from tool_result events) */ }
1340+ { surfacedCards && surfacedCards . length > 0 && (
1341+ < div className = "surfaced-panel" >
1342+ < div className = "surfaced-header" > Surfaced</ div >
1343+ < div className = "surfaced-cards" >
1344+ { surfacedCards . map ( ( c : any ) => (
1345+ < div key = { c . id } className = "surfaced-card" >
1346+ < div className = "surfaced-preview" > { c . preview } </ div >
1347+ < div className = "surfaced-source" > { c . source } </ div >
1348+ </ div >
1349+ ) ) }
1350+ </ div >
1351+ </ div >
1352+ ) }
1353+
13001354 { /* Indexed documents context bar — shows only docs attached to this session */ }
13011355 { sessionDocs . length > 0 && ( ( ) => {
13021356 // Sort by most recently accessed (last_accessed_at), falling back to indexed_at
0 commit comments