Skip to content

Commit e4f4641

Browse files
committed
fix(chat): keep single tool calls inline
1 parent f45cab0 commit e4f4641

2 files changed

Lines changed: 103 additions & 38 deletions

File tree

apps/sim/app/workspace/[workspaceId]/home/components/message-content/components/agent-group/agent-group.test.ts

Lines changed: 70 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,42 @@ describe('AgentGroup inline main activity', () => {
120120
container.remove()
121121
})
122122

123+
it.each([
124+
['executing', 'Reading notes'],
125+
['success', 'Read notes'],
126+
['error', 'Failed reading notes'],
127+
['cancelled', 'Stopped reading notes'],
128+
['skipped', 'Skipped reading notes'],
129+
['rejected', 'Failed reading notes'],
130+
['interrupted', 'Stopped reading notes'],
131+
] as const)('renders a single %s tool once without a disclosure', (status, expected) => {
132+
act(() =>
133+
root.render(
134+
createElement(AgentGroup, {
135+
agentName: 'mothership',
136+
agentLabel: 'Sim',
137+
items: [
138+
{
139+
type: 'tool',
140+
data: {
141+
id: 'read',
142+
toolName: 'read',
143+
displayTitle: 'Reading notes',
144+
status,
145+
},
146+
},
147+
],
148+
isStreaming: status === 'executing',
149+
})
150+
)
151+
)
152+
expect(container.textContent).toBe(expected)
153+
expect(container.querySelectorAll('[role="status"]')).toHaveLength(1)
154+
expect(container.querySelector('button')).toBeNull()
155+
expect(container.querySelector('[data-state]')).toBeNull()
156+
expect(Boolean(container.querySelector('[class*="shimmer"]'))).toBe(status === 'executing')
157+
})
158+
123159
it('replaces the active status in place and expands the full completed history', () => {
124160
const first: AgentGroupItem = {
125161
type: 'tool',
@@ -144,6 +180,7 @@ describe('AgentGroup inline main activity', () => {
144180

145181
render([first])
146182
expect(container.textContent).toBe('Searching files')
183+
expect(container.querySelector('button')).toBeNull()
147184
const activity = container.firstElementChild
148185

149186
render([first, next])
@@ -179,6 +216,15 @@ describe('AgentGroup inline main activity', () => {
179216
type: 'tool',
180217
data: { id: 'first', toolName: 'read', displayTitle: 'Reading notes', status: 'success' },
181218
}
219+
const second: AgentGroupItem = {
220+
type: 'tool',
221+
data: {
222+
id: 'second',
223+
toolName: 'read',
224+
displayTitle: 'Reading more notes',
225+
status: 'success',
226+
},
227+
}
182228
const render = (items: AgentGroupItem[]) =>
183229
act(() =>
184230
root.render(
@@ -190,14 +236,15 @@ describe('AgentGroup inline main activity', () => {
190236
})
191237
)
192238
)
193-
render([first])
239+
render([first, second])
194240
act(() => container.querySelector('button')?.click())
195241
render([
196242
first,
243+
second,
197244
{
198245
type: 'tool',
199246
data: {
200-
id: 'second',
247+
id: 'third',
201248
toolName: 'terminal_run',
202249
displayTitle: 'Running checks',
203250
status: 'executing',
@@ -206,7 +253,7 @@ describe('AgentGroup inline main activity', () => {
206253
])
207254
expect(container.querySelector('button')?.getAttribute('aria-expanded')).toBe('true')
208255
expect(container.querySelector('[data-state="open"]')?.textContent).toBe(
209-
'Read notesRunning checks'
256+
'Read notesRead more notesRunning checks'
210257
)
211258
})
212259

@@ -225,6 +272,10 @@ describe('AgentGroup inline main activity', () => {
225272
params: { seconds: 3 },
226273
},
227274
}
275+
const read: AgentGroupItem = {
276+
type: 'tool',
277+
data: { id: 'read', toolName: 'read', displayTitle: 'Reading notes', status: 'success' },
278+
}
228279
const render = (items: AgentGroupItem[]) =>
229280
act(() =>
230281
root.render(
@@ -239,31 +290,43 @@ describe('AgentGroup inline main activity', () => {
239290
render([wait])
240291
act(() => vi.advanceTimersByTime(2000))
241292
expect(container.textContent).toBe('Waiting 1s')
293+
expect(container.querySelector('button')).toBeNull()
294+
render([wait, read])
295+
expect(container.textContent).toBe('Waiting 1s')
296+
expect(setIntervalSpy).toHaveBeenCalledTimes(1)
242297
const header = container.querySelector('button')
243298
act(() => header?.click())
244299
expect(header?.hasAttribute('aria-label')).toBe(false)
245300
expect(header?.textContent).toBe('Waiting 1s')
246301
expect(header).toHaveAccessibleName('Waiting 1s')
247-
expect(container.querySelector('[data-state="open"]')?.textContent).toBe('Waiting 1s')
302+
expect(container.querySelector('[data-state="open"]')?.textContent).toBe(
303+
'Waiting 1sRead notes'
304+
)
248305
expect(setIntervalSpy).toHaveBeenCalledTimes(1)
249306
act(() => header?.click())
250307
act(() => header?.click())
251-
expect(container.querySelector('[data-state="open"]')?.textContent).toBe('Waiting 1s')
308+
expect(container.querySelector('[data-state="open"]')?.textContent).toBe(
309+
'Waiting 1sRead notes'
310+
)
252311
const viewport = container.querySelector('.overflow-y-auto')
253312
render([
254313
{ ...wait, data: { ...wait.data, status: 'success' } },
314+
read,
255315
{ ...wait, data: { ...wait.data, id: 'wait-second' } },
256316
])
257317
expect(header?.textContent).toBe('Waiting 3s')
258318
expect(header).toHaveAccessibleName('Waiting 3s')
259319
expect(container.querySelector('.overflow-y-auto')).toBe(viewport)
260-
expect(container.querySelector('[data-state="open"]')?.textContent).toBe('WaitedWaiting 3s')
320+
expect(container.querySelector('[data-state="open"]')?.textContent).toBe(
321+
'WaitedRead notesWaiting 3s'
322+
)
261323
expect(setIntervalSpy).toHaveBeenCalledTimes(2)
262324
render([
263325
{ ...wait, data: { ...wait.data, status: 'success' } },
326+
read,
264327
{ ...wait, data: { ...wait.data, id: 'wait-second', status: 'success' } },
265328
])
266-
expect(header?.textContent).toBe('Waited')
329+
expect(header?.textContent).toBe('Waited, read files')
267330
expect(container.querySelector('.overflow-y-auto')).toBe(viewport)
268331
expect(clearIntervalSpy).toHaveBeenCalledTimes(2)
269332
} finally {

apps/sim/app/workspace/[workspaceId]/home/components/message-content/components/agent-group/tool-activity-group.tsx

Lines changed: 33 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -64,43 +64,45 @@ export function ToolActivityGroup({
6464
}
6565
}
6666
const statusTool = activeTool ?? tools[tools.length - 1]
67-
const showToolHeader = Boolean(activeTool) || tools.length === 1
6867
const SummaryIcon = getToolIcon(tools[0].toolName)
6968

7069
return (
7170
<ToolCallComponent
7271
{...statusTool}
7372
toolCallId={statusTool.id}
74-
renderStatus={(status) => (
75-
<ActivityDisclosure
76-
header={
77-
showToolHeader ? (
78-
status
79-
) : (
80-
<ActivityStatus
81-
label={getToolActivitySummary(tools)}
82-
isActive={false}
83-
icon={<SummaryIcon className='size-full' />}
84-
/>
85-
)
86-
}
87-
expanded={expanded}
88-
onToggle={() => setExpanded(!expanded)}
89-
isStreaming={Boolean(activeTool) && autoScrollActivity}
90-
>
91-
<div className='flex min-w-0 flex-col gap-1.5 py-0.5 pl-6'>
92-
{tools.map((tool) => (
93-
<Fragment key={tool.id}>
94-
{tool.id === statusTool.id ? (
95-
status
96-
) : (
97-
<ToolCallComponent {...tool} toolCallId={tool.id} />
98-
)}
99-
</Fragment>
100-
))}
101-
</div>
102-
</ActivityDisclosure>
103-
)}
73+
renderStatus={(status) => {
74+
if (tools.length === 1) return status
75+
return (
76+
<ActivityDisclosure
77+
header={
78+
activeTool ? (
79+
status
80+
) : (
81+
<ActivityStatus
82+
label={getToolActivitySummary(tools)}
83+
isActive={false}
84+
icon={<SummaryIcon className='size-full' />}
85+
/>
86+
)
87+
}
88+
expanded={expanded}
89+
onToggle={() => setExpanded(!expanded)}
90+
isStreaming={Boolean(activeTool) && autoScrollActivity}
91+
>
92+
<div className='flex min-w-0 flex-col gap-1.5 py-0.5 pl-6'>
93+
{tools.map((tool) => (
94+
<Fragment key={tool.id}>
95+
{tool.id === statusTool.id ? (
96+
status
97+
) : (
98+
<ToolCallComponent {...tool} toolCallId={tool.id} />
99+
)}
100+
</Fragment>
101+
))}
102+
</div>
103+
</ActivityDisclosure>
104+
)
105+
}}
104106
/>
105107
)
106108
}

0 commit comments

Comments
 (0)