Skip to content

Commit 7cc013e

Browse files
committed
subagent thinking text
1 parent 9953fda commit 7cc013e

File tree

8 files changed

+107
-50
lines changed

8 files changed

+107
-50
lines changed

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

Lines changed: 53 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,14 @@ import type { ToolCallData } from '../../../../types'
77
import { getAgentIcon } from '../../utils'
88
import { ToolCallItem } from './tool-call-item'
99

10+
export type AgentGroupItem =
11+
| { type: 'text'; content: string }
12+
| { type: 'tool'; data: ToolCallData }
13+
1014
interface AgentGroupProps {
1115
agentName: string
1216
agentLabel: string
13-
tools: ToolCallData[]
17+
items: AgentGroupItem[]
1418
autoCollapse?: boolean
1519
}
1620

@@ -19,14 +23,20 @@ const FADE_MS = 300
1923
export function AgentGroup({
2024
agentName,
2125
agentLabel,
22-
tools,
26+
items,
2327
autoCollapse = false,
2428
}: AgentGroupProps) {
2529
const AgentIcon = getAgentIcon(agentName)
26-
const hasTools = tools.length > 0
30+
const hasItems = items.length > 0
31+
const toolItems = items.filter(
32+
(item): item is Extract<AgentGroupItem, { type: 'tool' }> => item.type === 'tool'
33+
)
2734
const allDone =
28-
hasTools &&
29-
tools.every((t) => t.status === 'success' || t.status === 'error' || t.status === 'cancelled')
35+
toolItems.length > 0 &&
36+
toolItems.every(
37+
(t) =>
38+
t.data.status === 'success' || t.data.status === 'error' || t.data.status === 'cancelled'
39+
)
3040

3141
const [expanded, setExpanded] = useState(!allDone)
3242
const [mounted, setMounted] = useState(!allDone)
@@ -49,39 +59,55 @@ export function AgentGroup({
4959

5060
return (
5161
<div className='flex flex-col gap-1.5'>
52-
<button
53-
type='button'
54-
onClick={hasTools ? () => setExpanded((prev) => !prev) : undefined}
55-
className={cn('flex items-center gap-[8px]', hasTools && 'cursor-pointer')}
56-
>
57-
<div className='flex h-[16px] w-[16px] flex-shrink-0 items-center justify-center'>
58-
<AgentIcon className='h-[16px] w-[16px] text-[var(--text-icon)]' />
59-
</div>
60-
<span className='font-base text-[14px] text-[var(--text-body)]'>{agentLabel}</span>
61-
{hasTools && (
62+
{hasItems ? (
63+
<button
64+
type='button'
65+
onClick={() => setExpanded((prev) => !prev)}
66+
className='flex cursor-pointer items-center gap-[8px]'
67+
>
68+
<div className='flex h-[16px] w-[16px] flex-shrink-0 items-center justify-center'>
69+
<AgentIcon className='h-[16px] w-[16px] text-[var(--text-icon)]' />
70+
</div>
71+
<span className='font-base text-[14px] text-[var(--text-body)]'>{agentLabel}</span>
6272
<ChevronDown
6373
className={cn(
6474
'h-[7px] w-[9px] text-[var(--text-icon)] transition-transform duration-150',
6575
!expanded && '-rotate-90'
6676
)}
6777
/>
68-
)}
69-
</button>
70-
{hasTools && mounted && (
78+
</button>
79+
) : (
80+
<div className='flex items-center gap-[8px]'>
81+
<div className='flex h-[16px] w-[16px] flex-shrink-0 items-center justify-center'>
82+
<AgentIcon className='h-[16px] w-[16px] text-[var(--text-icon)]' />
83+
</div>
84+
<span className='font-base text-[14px] text-[var(--text-body)]'>{agentLabel}</span>
85+
</div>
86+
)}
87+
{hasItems && mounted && (
7188
<div
7289
className={cn(
73-
'flex flex-col gap-1.5 transition-opacity duration-300 ease-out',
90+
'flex flex-col gap-3 transition-opacity duration-300 ease-out',
7491
expanded ? 'opacity-100' : 'opacity-0'
7592
)}
7693
>
77-
{tools.map((tool) => (
78-
<ToolCallItem
79-
key={tool.id}
80-
toolName={tool.toolName}
81-
displayTitle={tool.displayTitle}
82-
status={tool.status}
83-
/>
84-
))}
94+
{items.map((item, idx) =>
95+
item.type === 'tool' ? (
96+
<ToolCallItem
97+
key={item.data.id}
98+
toolName={item.data.toolName}
99+
displayTitle={item.data.displayTitle}
100+
status={item.data.status}
101+
/>
102+
) : (
103+
<p
104+
key={`text-${idx}`}
105+
className='whitespace-pre-wrap pl-[24px] font-base text-[13px] text-[var(--text-secondary)]'
106+
>
107+
{item.content.trim()}
108+
</p>
109+
)
110+
)}
85111
</div>
86112
)}
87113
</div>
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
1+
export type { AgentGroupItem } from './agent-group'
12
export { AgentGroup } from './agent-group'
23
export { CircleStop } from './tool-call-item'

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

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -53,16 +53,16 @@ export function ToolCallItem({ toolName, displayTitle, status }: ToolCallItemPro
5353
<div className='flex items-center gap-[8px] pl-[24px]'>
5454
<div className='flex h-[16px] w-[16px] flex-shrink-0 items-center justify-center'>
5555
{status === 'executing' ? (
56-
<Loader className='h-[16px] w-[16px] text-[var(--text-icon)]' animate />
56+
<Loader className='h-[15px] w-[15px] text-[var(--text-tertiary)]' animate />
5757
) : status === 'cancelled' ? (
58-
<CircleStop className='h-[16px] w-[16px] text-[var(--text-icon)]' />
58+
<CircleStop className='h-[15px] w-[15px] text-[var(--text-tertiary)]' />
5959
) : Icon ? (
60-
<Icon className='h-[16px] w-[16px] text-[var(--text-icon)]' />
60+
<Icon className='h-[15px] w-[15px] text-[var(--text-tertiary)]' />
6161
) : (
62-
<CircleCheck className='h-[16px] w-[16px] text-[var(--text-icon)]' />
62+
<CircleCheck className='h-[15px] w-[15px] text-[var(--text-tertiary)]' />
6363
)}
6464
</div>
65-
<span className='font-base text-[14px] text-[var(--text-body)]'>{displayTitle}</span>
65+
<span className='font-base text-[13px] text-[var(--text-secondary)]'>{displayTitle}</span>
6666
</div>
6767
)
6868
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
export type { AgentGroupItem } from './agent-group'
12
export { AgentGroup, CircleStop } from './agent-group'
23
export { ChatContent } from './chat-content'
34
export { Options } from './options'

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

Lines changed: 29 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import type { ContentBlock, OptionItem, SubagentName, ToolCallData } from '../../types'
44
import { SUBAGENT_LABELS } from '../../types'
5+
import type { AgentGroupItem } from './components'
56
import { AgentGroup, ChatContent, CircleStop, Options } from './components'
67

78
interface TextSegment {
@@ -14,7 +15,7 @@ interface AgentGroupSegment {
1415
id: string
1516
agentName: string
1617
agentLabel: string
17-
tools: ToolCallData[]
18+
items: AgentGroupItem[]
1819
}
1920

2021
interface OptionsSegment {
@@ -64,7 +65,18 @@ function parseBlocks(blocks: ContentBlock[]): MessageSegment[] {
6465
for (let i = 0; i < blocks.length; i++) {
6566
const block = blocks[i]
6667

67-
if (block.type === 'text' || block.type === 'subagent_text') {
68+
if (block.type === 'subagent_text') {
69+
if (!block.content || !group) continue
70+
const lastItem = group.items[group.items.length - 1]
71+
if (lastItem?.type === 'text') {
72+
lastItem.content += block.content
73+
} else {
74+
group.items.push({ type: 'text', content: block.content })
75+
}
76+
continue
77+
}
78+
79+
if (block.type === 'text') {
6880
if (!block.content?.trim()) continue
6981
if (group) {
7082
segments.push(group)
@@ -92,7 +104,7 @@ function parseBlocks(blocks: ContentBlock[]): MessageSegment[] {
92104
id: `agent-${key}-${i}`,
93105
agentName: key,
94106
agentLabel: resolveAgentLabel(key),
95-
tools: [],
107+
items: [],
96108
}
97109
continue
98110
}
@@ -113,7 +125,7 @@ function parseBlocks(blocks: ContentBlock[]): MessageSegment[] {
113125
id: `agent-${tc.name}-${i}`,
114126
agentName: tc.name,
115127
agentLabel: resolveAgentLabel(tc.name),
116-
tools: [],
128+
items: [],
117129
}
118130
}
119131
continue
@@ -122,7 +134,7 @@ function parseBlocks(blocks: ContentBlock[]): MessageSegment[] {
122134
const tool = toToolData(tc)
123135

124136
if (tc.calledBy && group && group.agentName === tc.calledBy) {
125-
group.tools.push(tool)
137+
group.items.push({ type: 'tool', data: tool })
126138
} else if (tc.calledBy) {
127139
if (group) {
128140
segments.push(group)
@@ -133,11 +145,11 @@ function parseBlocks(blocks: ContentBlock[]): MessageSegment[] {
133145
id: `agent-${tc.calledBy}-${i}`,
134146
agentName: tc.calledBy,
135147
agentLabel: resolveAgentLabel(tc.calledBy),
136-
tools: [tool],
148+
items: [{ type: 'tool', data: tool }],
137149
}
138150
} else {
139151
if (group && group.agentName === 'mothership') {
140-
group.tools.push(tool)
152+
group.items.push({ type: 'tool', data: tool })
141153
} else {
142154
if (group) {
143155
segments.push(group)
@@ -148,7 +160,7 @@ function parseBlocks(blocks: ContentBlock[]): MessageSegment[] {
148160
id: `agent-mothership-${i}`,
149161
agentName: 'mothership',
150162
agentLabel: 'Mothership',
151-
tools: [tool],
163+
items: [{ type: 'tool', data: tool }],
152164
}
153165
}
154166
}
@@ -216,18 +228,23 @@ export function MessageContent({
216228
/>
217229
)
218230
case 'agent_group': {
231+
const toolItems = segment.items.filter((item) => item.type === 'tool')
219232
const allToolsDone =
220-
segment.tools.length > 0 &&
221-
segment.tools.every(
222-
(t) => t.status === 'success' || t.status === 'error' || t.status === 'cancelled'
233+
toolItems.length === 0 ||
234+
toolItems.every(
235+
(t) =>
236+
t.type === 'tool' &&
237+
(t.data.status === 'success' ||
238+
t.data.status === 'error' ||
239+
t.data.status === 'cancelled')
223240
)
224241
const hasFollowingText = segments.slice(i + 1).some((s) => s.type === 'text')
225242
return (
226243
<div key={segment.id} className={isStreaming ? 'animate-stream-fade-in' : undefined}>
227244
<AgentGroup
228245
agentName={segment.agentName}
229246
agentLabel={segment.agentLabel}
230-
tools={segment.tools}
247+
items={segment.items}
231248
autoCollapse={allToolsDone && hasFollowingText}
232249
/>
233250
</div>

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,12 @@ import {
66
Bug,
77
Calendar,
88
ClipboardList,
9-
Connections,
109
Database,
1110
File,
1211
FolderCode,
1312
Hammer,
1413
Integration,
14+
Layout,
1515
Library,
1616
Pencil,
1717
PlayOutline,
@@ -42,7 +42,7 @@ const TOOL_ICONS: Record<MothershipToolName | SubagentName | 'mothership', IconC
4242
superagent: Blimp,
4343
user_table: TableIcon,
4444
workspace_file: File,
45-
create_workflow: Connections,
45+
create_workflow: Layout,
4646
edit_workflow: Pencil,
4747
build: Hammer,
4848
run: PlayOutline,
@@ -58,6 +58,7 @@ const TOOL_ICONS: Record<MothershipToolName | SubagentName | 'mothership', IconC
5858
plan: ClipboardList,
5959
debug: Bug,
6060
edit: Pencil,
61+
fast_edit: Pencil,
6162
}
6263

6364
export function getAgentIcon(name: string): IconComponent {

apps/sim/app/workspace/[workspaceId]/home/hooks/use-chat.ts

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -383,6 +383,14 @@ export function useChat(
383383
return b
384384
}
385385

386+
const ensureSubagentTextBlock = (): ContentBlock => {
387+
const last = blocks[blocks.length - 1]
388+
if (last?.type === 'subagent_text') return last
389+
const b: ContentBlock = { type: 'subagent_text', content: '' }
390+
blocks.push(b)
391+
return b
392+
}
393+
386394
const flush = () => {
387395
streamingBlocksRef.current = [...blocks]
388396
setMessages((prev) =>
@@ -450,10 +458,9 @@ export function useChat(
450458
lastContentSource !== contentSource &&
451459
runningText.length > 0 &&
452460
!runningText.endsWith('\n')
453-
const tb = ensureTextBlock()
454-
const normalizedChunk = needsBoundaryNewline ? `\n${chunk}` : chunk
455-
tb.content = (tb.content ?? '') + normalizedChunk
456-
runningText += normalizedChunk
461+
const tb = activeSubagent ? ensureSubagentTextBlock() : ensureTextBlock()
462+
tb.content = (tb.content ?? '') + chunk
463+
runningText += needsBoundaryNewline ? `\n${chunk}` : chunk
457464
lastContentSource = contentSource
458465
streamingContentRef.current = runningText
459466
flush()

apps/sim/app/workspace/[workspaceId]/home/types.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ export type MothershipToolName =
7272
| 'plan'
7373
| 'debug'
7474
| 'edit'
75+
| 'fast_edit'
7576

7677
/**
7778
* Subagent identifiers dispatched via `subagent_start` SSE events.
@@ -93,6 +94,7 @@ export type SubagentName =
9394
| 'plan'
9495
| 'debug'
9596
| 'edit'
97+
| 'fast_edit'
9698

9799
export type ToolPhase =
98100
| 'workspace'
@@ -179,6 +181,7 @@ export const SUBAGENT_LABELS: Record<SubagentName, string> = {
179181
plan: 'Plan agent',
180182
debug: 'Debug agent',
181183
edit: 'Edit agent',
184+
fast_edit: 'Edit agent',
182185
} as const
183186

184187
export interface ToolUIMetadata {
@@ -223,6 +226,7 @@ export const TOOL_UI_METADATA: Partial<Record<MothershipToolName, ToolUIMetadata
223226
plan: { title: 'Planning', phaseLabel: 'Plan', phase: 'subagent' },
224227
debug: { title: 'Debugging', phaseLabel: 'Debug', phase: 'subagent' },
225228
edit: { title: 'Editing workflow', phaseLabel: 'Edit', phase: 'subagent' },
229+
fast_edit: { title: 'Editing workflow', phaseLabel: 'Edit', phase: 'subagent' },
226230
}
227231

228232
export interface SSEPayloadUI {

0 commit comments

Comments
 (0)