Skip to content

Commit 34a2281

Browse files
committed
fix(chat): restore thinking indicator after tools finish
1 parent 573b5e2 commit 34a2281

3 files changed

Lines changed: 55 additions & 18 deletions

File tree

‎apps/sim/app/workspace/[workspaceId]/home/components/message-content/message-content-thinking.test.tsx‎

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,53 @@ describe('MessageContent shared thinking indicator', () => {
6565
const thinking = () => container.querySelectorAll('[aria-hidden="false"] svg')
6666
const groups = () => container.querySelectorAll('[data-agent-group]')
6767

68+
it.each([undefined, 'main'])(
69+
'shows thinking after main tools finish and yields to the next tool (spanId=%s)',
70+
(spanId) => {
71+
const mainCall = (id: string, status: ToolCallStatus): ContentBlock => ({
72+
type: 'tool_call',
73+
spanId,
74+
toolCall: {
75+
id,
76+
name: 'sim_cli',
77+
status,
78+
displayTitle: 'List tables in Alfred',
79+
params: {
80+
args: ['tables', 'list'],
81+
activity: {
82+
id: 'inspect',
83+
title: 'Inspecting workspace resources',
84+
completedTitle: 'Inspected workspace resources',
85+
},
86+
},
87+
},
88+
})
89+
render([mainCall('first', 'executing')])
90+
act(() => vi.advanceTimersByTime(1_500))
91+
expect(thinking()).toHaveLength(0)
92+
expect(container.querySelector('[class*="shimmer"]')).not.toBeNull()
93+
94+
const completed = [mainCall('first', 'success')]
95+
render(completed)
96+
expect(thinking()).toHaveLength(0)
97+
act(() => vi.advanceTimersByTime(1_500))
98+
expect(thinking()).toHaveLength(1)
99+
expect(container.querySelector('[aria-hidden="false"]')?.textContent).toContain('Thinking')
100+
101+
render([...completed, { type: 'thinking', content: 'Checking the result.', timestamp: 3 }])
102+
expect(thinking()).toHaveLength(1)
103+
render([...completed, mainCall('next', 'executing')])
104+
expect(thinking()).toHaveLength(0)
105+
106+
const finished = [...completed, mainCall('next', 'success')]
107+
render(finished)
108+
act(() => vi.advanceTimersByTime(1_500))
109+
expect(thinking()).toHaveLength(1)
110+
render(finished, false)
111+
expect(thinking()).toHaveLength(0)
112+
}
113+
)
114+
68115
it('shares one indicator across parallel and nested empty agents', () => {
69116
render([start('workflow'), start('browser'), start('deploy', 'workflow')])
70117
expect(thinking()).toHaveLength(1)

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1023,9 +1023,9 @@ describe('parseBlocks legacy — thinking between top-level tools', () => {
10231023
})
10241024

10251025
describe('assistantMessageHasVisibleActivity', () => {
1026-
it('keeps the main tail active between calls but closes it when narration follows', () => {
1026+
it('leaves the gap after a completed main tool to the turn indicator', () => {
10271027
const blocks = [mainToolCall('finished', 'read')]
1028-
expect(assistantMessageHasVisibleActivity(parseBlocks(blocks), true)).toBe(true)
1028+
expect(assistantMessageHasVisibleActivity(parseBlocks(blocks), true)).toBe(false)
10291029
expect(assistantMessageHasVisibleActivity(parseBlocks(blocks), false)).toBe(false)
10301030
expect(
10311031
assistantMessageHasVisibleActivity(parseBlocks([...blocks, mainText('Done.')]), true)

‎apps/sim/app/workspace/[workspaceId]/home/components/message-content/message-content.tsx‎

Lines changed: 6 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ import type { TaskBlockInfo } from '@/lib/mothership/request/types'
1717
import { isToolHiddenInUi } from '@/lib/mothership/tools/client/hidden-tools'
1818
import { resolveToolDisplay } from '@/lib/mothership/tools/client/store-utils'
1919
import { ClientToolCallState } from '@/lib/mothership/tools/client/tool-call-state'
20-
import { RETIRED_BROWSER_REQUEST_TAKEOVER_ID } from '@/lib/mothership/tools/retired-tools'
2120
import { readToolActivity } from '@/lib/mothership/tools/tool-activity'
2221
import {
2322
getToolDisplayTitle,
@@ -889,30 +888,21 @@ export function assistantMessageHasRenderableContent(
889888
)
890889
}
891890

892-
/** The transcript already owns an activity indicator, including gaps between calls. */
891+
/** Only suppress the turn indicator when a tool or agent row is visibly active. */
893892
export function assistantMessageHasVisibleActivity(
894893
segments: MessageSegment[],
895894
isStreaming = false
896895
): boolean {
897-
return segments.some((segment, index) => {
896+
return segments.some((segment) => {
898897
if (segment.type !== 'agent_group' || !segment.items.some(hasAgentGroupItemContent)) {
899898
return false
900899
}
901900
const tools = collectGroupTools(segment.items)
902901
if (tools.some((tool) => tool.status === 'executing')) return true
903-
if (!isStreaming) return false
904-
if (segment.agentName !== 'mothership') {
905-
const statusTool = getActivityStatusTool(tools)
906-
return (
907-
(segment.isOpen || segment.isDelegating) && (!statusTool || statusTool.status === 'success')
908-
)
909-
}
910-
const lastItem = segment.items.at(-1)
902+
if (!isStreaming || segment.agentName === 'mothership') return false
903+
const statusTool = getActivityStatusTool(tools)
911904
return (
912-
index === segments.length - 1 &&
913-
lastItem?.type === 'tool' &&
914-
lastItem.data.status === 'success' &&
915-
lastItem.data.toolName !== RETIRED_BROWSER_REQUEST_TAKEOVER_ID
905+
(segment.isOpen || segment.isDelegating) && (!statusTool || statusTool.status === 'success')
916906
)
917907
})
918908
}
@@ -1094,7 +1084,7 @@ function MessageContentInner({
10941084

10951085
if (segments.length === 0 && !isLast) return null
10961086

1097-
/** Open activity groups own the shimmer through gaps between tool calls. */
1087+
/** Active tool and agent rows own the shimmer until the turn is waiting again. */
10981088
// A mid-stream special tag renders nothing until complete, so its bytes are a
10991089
// wait, not output — the shimmer bridges it without the quiet-period delay.
11001090
const thinkingLabel = deriveThinkingLabel(blocks)

0 commit comments

Comments
 (0)