@@ -12,6 +12,7 @@ import {
1212import { ChevronDown , cn , Expandable , ExpandableContent , OverflowText } from '@sim/emcn'
1313import { ShimmerText } from '@/components/ui'
1414import { isBrowserAgentAvailable } from '@/lib/browser-agent/transport'
15+ import { Terminal as TerminalTool } from '@/lib/copilot/generated/tool-catalog-v1'
1516import { RETIRED_BROWSER_REQUEST_TAKEOVER_ID } from '@/lib/copilot/tools/retired-tools'
1617import { renderInlineMarkdown } from '@/app/workspace/[workspaceId]/home/components/message-content/components/agent-group/inline-markdown'
1718import type { ToolCallItemProps } from '@/app/workspace/[workspaceId]/home/components/message-content/components/agent-group/tool-call-item'
@@ -47,11 +48,9 @@ export interface AgentGroupProps {
4748 items : AgentGroupItem [ ]
4849 isDelegating ?: boolean
4950 isStreaming ?: boolean
50- /** This group is the latest section in its parent sequence (drives collapse). */
51- isCurrentSection ?: boolean
5251 /** The subagent lane is still open (no subagent_end yet) — i.e. actively running. */
5352 isLaneOpen ?: boolean
54- /** Opens the group on first render without changing production's automatic collapse rules . */
53+ /** Opens a subagent group on first render. */
5554 defaultExpanded ?: boolean
5655 /** Keeps the activity viewport anchored at the top while new rows stream in. */
5756 autoScrollActivity ?: boolean
@@ -151,7 +150,6 @@ export function AgentGroupView({
151150 items,
152151 isDelegating = false ,
153152 isStreaming = false ,
154- isCurrentSection = false ,
155153 isLaneOpen = false ,
156154 defaultExpanded = false ,
157155 autoScrollActivity = true ,
@@ -192,26 +190,15 @@ export function AgentGroupView({
192190 const isWorking =
193191 ! activeBrowserTakeover && ( ( isDelegating && ! resolved ) || ( isStreaming && isLaneOpen ) )
194192
195- // SUBAGENT groups never auto-expand: the collapsed row IS the live view —
196- // label plus latest running tool title. Expanding is a deliberate user
197- // action; only a pending permission prompt or a browser hand-back forces
198- // one open. The MAIN lane ("Sim") is not a delegation card: its narration
199- // and tool calls are the turn itself, so it keeps the original live-expand
200- // behavior (open while streaming/current, settles when superseded).
201- const autoExpanded = isMainAgent && isStreaming && ( isCurrentSection || isLaneOpen || ! resolved )
202- const [ manualExpanded , setManualExpanded ] = useState < boolean | null > (
203- defaultExpanded ? true : null
204- )
193+ const [ manualExpanded , setManualExpanded ] = useState ( defaultExpanded )
205194 const [ expandedTakeoverId , setExpandedTakeoverId ] = useState < string | null > ( null )
206195 // An outstanding permission prompt overrides a manual collapse: the turn
207196 // cannot proceed until it is answered, so hiding it would deadlock the chat
208197 // with nothing on screen to explain why.
209198 const expanded =
210199 hasAwaitingApproval ( items ) ||
211200 nestedBrowserTakeover ||
212- ( activeBrowserTakeover
213- ? expandedTakeoverId === activeBrowserTakeover . id
214- : ( manualExpanded ?? autoExpanded ) )
201+ ( activeBrowserTakeover ? expandedTakeoverId === activeBrowserTakeover . id : manualExpanded )
215202
216203 const toggleExpanded = ( ) => {
217204 if ( activeBrowserTakeover ) {
@@ -221,9 +208,74 @@ export function AgentGroupView({
221208 setManualExpanded ( ! expanded )
222209 }
223210
211+ let latestTool : AgentGroupItem | undefined
212+ if ( isMainAgent ) {
213+ for ( let index = items . length - 1 ; index >= 0 ; index -- ) {
214+ if ( items [ index ] . type === 'tool' ) {
215+ latestTool = items [ index ]
216+ break
217+ }
218+ }
219+ }
220+ /** Keep blocking controls visible even when a newer tool replaces the activity text. */
221+ const visibleItems = isMainAgent
222+ ? items . filter (
223+ ( item ) =>
224+ item . type !== 'tool' ||
225+ item === latestTool ||
226+ item . data . status === ToolCallStatus . awaiting_approval ||
227+ ( item . data . status === ToolCallStatus . executing &&
228+ item . data . toolName === TerminalTool . id &&
229+ item . data . params ?. operation === 'handoff' )
230+ )
231+ : items
232+ const activity = (
233+ < div className = { cn ( 'flex min-w-0 flex-col gap-1.5' , ! isMainAgent && 'py-0.5 pl-6' ) } >
234+ { visibleItems . map ( ( item , idx ) => {
235+ if ( item . type === 'tool' ) {
236+ return (
237+ < ToolCallComponent
238+ key = { item . data . id }
239+ toolCallId = { item . data . id }
240+ toolName = { item . data . toolName }
241+ displayTitle = { item . data . displayTitle }
242+ status = { item . data . status }
243+ params = { item . data . params }
244+ result = { item . data . result }
245+ streamingArgs = { item . data . streamingArgs }
246+ startedAt = { item . data . startedAt }
247+ />
248+ )
249+ }
250+ if ( item . type === 'agent_group' ) {
251+ return (
252+ < AgentGroupView
253+ key = { item . group . id }
254+ ToolCallComponent = { ToolCallComponent }
255+ renderBrowserTakeover = { renderBrowserTakeover }
256+ agentName = { item . group . agentName }
257+ agentLabel = { item . group . agentLabel }
258+ items = { item . group . items }
259+ isDelegating = { item . group . isDelegating }
260+ isStreaming = { isStreaming }
261+ isLaneOpen = { item . group . isOpen }
262+ />
263+ )
264+ }
265+ return (
266+ < NarrationText
267+ key = { `text-${ idx } ` }
268+ content = { item . content }
269+ isStreaming = { isStreaming && idx === visibleItems . length - 1 }
270+ />
271+ )
272+ } ) }
273+ </ div >
274+ )
275+
224276 return (
225277 < div className = 'flex flex-col gap-1.5' >
226- { hasItems ? (
278+ { isMainAgent ? null : hasItems ? (
227279 < button
228280 type = 'button'
229281 onClick = { toggleExpanded }
@@ -256,60 +308,20 @@ export function AgentGroupView({
256308 ) }
257309 </ div >
258310 ) }
259- { hasItems && (
311+ { isMainAgent ? (
312+ activity
313+ ) : hasItems ? (
260314 < Expandable expanded = { expanded } >
261315 < ExpandableContent >
262316 < BoundedViewport
263317 isStreaming = { isStreaming && autoScrollActivity }
264318 unbounded = { nestedBrowserTakeover }
265319 >
266- < div className = 'flex flex-col gap-1.5 py-0.5' >
267- { items . map ( ( item , idx ) => {
268- if ( item . type === 'tool' ) {
269- return (
270- < ToolCallComponent
271- key = { item . data . id }
272- toolCallId = { item . data . id }
273- toolName = { item . data . toolName }
274- displayTitle = { item . data . displayTitle }
275- status = { item . data . status }
276- params = { item . data . params }
277- result = { item . data . result }
278- streamingArgs = { item . data . streamingArgs }
279- startedAt = { item . data . startedAt }
280- />
281- )
282- }
283- if ( item . type === 'agent_group' ) {
284- return (
285- < div key = { item . group . id } className = 'pl-6' >
286- < AgentGroupView
287- ToolCallComponent = { ToolCallComponent }
288- renderBrowserTakeover = { renderBrowserTakeover }
289- agentName = { item . group . agentName }
290- agentLabel = { item . group . agentLabel }
291- items = { item . group . items }
292- isDelegating = { item . group . isDelegating }
293- isStreaming = { isStreaming }
294- isCurrentSection = { idx === items . length - 1 }
295- isLaneOpen = { item . group . isOpen }
296- />
297- </ div >
298- )
299- }
300- return (
301- < NarrationText
302- key = { `text-${ idx } ` }
303- content = { item . content }
304- isStreaming = { isStreaming && idx === items . length - 1 }
305- />
306- )
307- } ) }
308- </ div >
320+ { activity }
309321 </ BoundedViewport >
310322 </ ExpandableContent >
311323 </ Expandable >
312- ) }
324+ ) : null }
313325 { activeBrowserTakeover && (
314326 < div key = { activeBrowserTakeover . id } className = 'animate-stream-fade-in' >
315327 { renderBrowserTakeover ?.( activeBrowserTakeover . reason ) }
@@ -334,7 +346,7 @@ function NarrationText({ content, isStreaming }: NarrationTextProps) {
334346 const revealed = useSmoothText ( content , isStreaming )
335347
336348 return (
337- < span className = 'pl-6 text-[13px] text-[var(--text-muted)] leading-[18px]' >
349+ < span className = 'text-[13px] text-[var(--text-muted)] leading-[18px]' >
338350 { renderInlineMarkdown ( revealed . trim ( ) ) }
339351 </ span >
340352 )
0 commit comments