diff --git a/apps/sim/app/(landing)/components/hero/components/hero-chat-loop/hero-tool-call-item.tsx b/apps/sim/app/(landing)/components/hero/components/hero-chat-loop/hero-tool-call-item.tsx
index b659eae8b45..f32b4385062 100644
--- a/apps/sim/app/(landing)/components/hero/components/hero-chat-loop/hero-tool-call-item.tsx
+++ b/apps/sim/app/(landing)/components/hero/components/hero-chat-loop/hero-tool-call-item.tsx
@@ -11,6 +11,7 @@ export function HeroToolCallItem({
renderStatus,
toolName,
displayTitle,
+ activityDescription,
status,
}: ToolCallItemProps) {
const Icon =
@@ -21,7 +22,7 @@ export function HeroToolCallItem({
: getToolIcon(toolName)
const activity = (
}
/>
diff --git a/apps/sim/app/workspace/[workspaceId]/home/components/message-content/components/agent-group/agent-group-view.tsx b/apps/sim/app/workspace/[workspaceId]/home/components/message-content/components/agent-group/agent-group-view.tsx
index c9733459807..32183aec931 100644
--- a/apps/sim/app/workspace/[workspaceId]/home/components/message-content/components/agent-group/agent-group-view.tsx
+++ b/apps/sim/app/workspace/[workspaceId]/home/components/message-content/components/agent-group/agent-group-view.tsx
@@ -4,6 +4,7 @@ import { type ComponentType, type ReactNode, useMemo, useState } from 'react'
import { ActivityStatus } from '@/components/ui/activity-status'
import { isBrowserAgentAvailable } from '@/lib/browser-agent/transport'
import { RETIRED_BROWSER_REQUEST_TAKEOVER_ID } from '@/lib/copilot/tools/retired-tools'
+import { getToolStatusDisplayTitle } from '@/lib/copilot/tools/tool-display'
import { ActivityDisclosure } from '@/app/workspace/[workspaceId]/home/components/message-content/components/agent-group/activity-disclosure'
import { BrowserAgentIcon } from '@/app/workspace/[workspaceId]/home/components/message-content/components/agent-group/browser-agent-icon'
import { renderInlineMarkdown } from '@/app/workspace/[workspaceId]/home/components/message-content/components/agent-group/inline-markdown'
@@ -52,7 +53,12 @@ export interface AgentGroupProps {
}
function toolStatusTitle(tool: ToolCallData): string {
- return tool.displayTitle || String(tool.toolName ?? '')
+ return getToolStatusDisplayTitle(
+ tool.displayTitle || String(tool.toolName ?? ''),
+ tool.status,
+ tool.toolName,
+ tool.activityDescription
+ )
}
/**
@@ -213,6 +219,7 @@ export function AgentGroupView({
toolCallId={item.data.id}
toolName={item.data.toolName}
displayTitle={item.data.displayTitle}
+ activityDescription={item.data.activityDescription}
status={item.data.status}
params={item.data.params}
result={item.data.result}
diff --git a/apps/sim/app/workspace/[workspaceId]/home/components/message-content/components/agent-group/agent-group.test.ts b/apps/sim/app/workspace/[workspaceId]/home/components/message-content/components/agent-group/agent-group.test.ts
index 8dd4c725517..1e0b1923cdc 100644
--- a/apps/sim/app/workspace/[workspaceId]/home/components/message-content/components/agent-group/agent-group.test.ts
+++ b/apps/sim/app/workspace/[workspaceId]/home/components/message-content/components/agent-group/agent-group.test.ts
@@ -120,6 +120,43 @@ describe('AgentGroup inline main activity', () => {
container.remove()
})
+ it.each(['mothership', 'workflow', 'browser'])(
+ 'uses the same model description in the %s live header and expanded row',
+ (agentName) => {
+ act(() =>
+ root.render(
+ createElement(AgentGroup, {
+ agentName,
+ agentLabel: agentName,
+ defaultExpanded: true,
+ isLaneOpen: true,
+ isStreaming: true,
+ items: [
+ {
+ type: 'tool',
+ data: {
+ id: 'described-read',
+ toolName: 'read',
+ displayTitle: 'Reading files',
+ activityDescription: 'Checking the project timeline',
+ status: 'executing',
+ },
+ },
+ ],
+ })
+ )
+ )
+
+ const statuses = [...container.querySelectorAll('[role="status"]')]
+ expect(statuses).toHaveLength(agentName === 'mothership' ? 1 : 2)
+ for (const status of statuses) {
+ expect(status.textContent).toContain('Checking the project timeline')
+ }
+ expect(container.textContent).not.toContain('Reading files')
+ expect(container.querySelector('[class*="shimmer"]')).not.toBeNull()
+ }
+ )
+
it.each([
['executing', 'Reading notes'],
['success', 'Read notes'],
@@ -733,6 +770,6 @@ describe('AgentGroup nested status line', () => {
namedTool('Reading workflow', 'success' as ToolCallStatus, 1),
group([namedTool('Deploying Invoice Sync as API', 'success' as ToolCallStatus, 2)]),
])
- expect(header).toContain('Workflow Agent โ Deploying Invoice Sync as API')
+ expect(header).toContain('Workflow Agent โ Deployed Invoice Sync as API')
})
})
diff --git a/apps/sim/app/workspace/[workspaceId]/home/components/message-content/components/agent-group/tool-activity-group.tsx b/apps/sim/app/workspace/[workspaceId]/home/components/message-content/components/agent-group/tool-activity-group.tsx
index d426fe587da..d86c2fbd3af 100644
--- a/apps/sim/app/workspace/[workspaceId]/home/components/message-content/components/agent-group/tool-activity-group.tsx
+++ b/apps/sim/app/workspace/[workspaceId]/home/components/message-content/components/agent-group/tool-activity-group.tsx
@@ -15,7 +15,12 @@ const MAX_SUMMARY_ACTIONS = 2
export function getToolActivitySummary(tools: ToolCallData[]): string {
if (tools.length === 1) {
const tool = tools[0]
- return getToolStatusDisplayTitle(tool.displayTitle, tool.status, tool.toolName)
+ return getToolStatusDisplayTitle(
+ tool.displayTitle,
+ tool.status,
+ tool.toolName,
+ tool.activityDescription
+ )
}
const labels = new Set()
let failed = 0
diff --git a/apps/sim/app/workspace/[workspaceId]/home/components/message-content/components/agent-group/tool-call-item.test.tsx b/apps/sim/app/workspace/[workspaceId]/home/components/message-content/components/agent-group/tool-call-item.test.tsx
index b52d7b1b0e6..52e9489811d 100644
--- a/apps/sim/app/workspace/[workspaceId]/home/components/message-content/components/agent-group/tool-call-item.test.tsx
+++ b/apps/sim/app/workspace/[workspaceId]/home/components/message-content/components/agent-group/tool-call-item.test.tsx
@@ -48,6 +48,90 @@ describe('ToolCallItem', () => {
expect(markup).not.toContain('Writing brief.md')
})
+ it.each([
+ ['executing', 'Checking the invoice totals'],
+ ['success', 'Checked the invoice totals'],
+ ['error', 'Failed checking the invoice totals'],
+ ['cancelled', 'Stopped checking the invoice totals'],
+ ['rejected', 'Failed checking the invoice totals'],
+ ['skipped', 'Skipped checking the invoice totals'],
+ ] as const)(
+ 'projects %s from the actual tool status onto the model description',
+ (status, title) => {
+ const markup = renderToStaticMarkup(
+
+ )
+
+ expect(markup).toContain(title)
+ expect(markup).not.toContain('report.md')
+ }
+ )
+
+ it.each([' ', 'a'.repeat(161)])(
+ 'uses the existing title for an invalid description',
+ (activityDescription) => {
+ const markup = renderToStaticMarkup(
+
+ )
+
+ expect(markup).toContain('Searching files')
+ }
+ )
+
+ it('keeps an executing wait countdown in place of the model phrase', () => {
+ const markup = renderToStaticMarkup(
+
+ )
+
+ expect(markup).toContain('10s')
+ expect(markup).not.toContain('Waiting for the export')
+ })
+
+ it('renders model descriptions as text, without interpreting markup', () => {
+ const markup = renderToStaticMarkup(
+
+ )
+
+ expect(markup).toContain('<script>')
+ expect(markup).not.toContain('