From 75ddd158b4caa6d4b86910fceb3cb32e53c7cd3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=A8=8B=E5=BA=8F=E5=91=98=E9=98=BF=E6=B1=9F=28Relakkes?= =?UTF-8?q?=29?= Date: Mon, 6 Jul 2026 21:12:00 +0800 Subject: [PATCH] fix(desktop): improve activity panel interactions Tested: cd desktop && bun run test -- src/components/activity/SessionActivityPanel.test.tsx src/pages/ActiveSession.test.tsx src/pages/SubagentRunPage.test.tsx --run Tested: bun run check:desktop Tested: browser smoke for activity panel auto-open and SubAgent run rendering Not-tested: full bun run verify was not run for this scoped desktop handoff Confidence: high Scope-risk: narrow --- .../activity/SessionActivityPanel.test.tsx | 19 +-- .../activity/SessionActivityPanel.tsx | 41 +++++-- desktop/src/pages/ActiveSession.test.tsx | 77 ++++++++++++ desktop/src/pages/ActiveSession.tsx | 27 +++++ desktop/src/pages/SubagentRunPage.test.tsx | 44 +++++-- desktop/src/pages/SubagentRunPage.tsx | 111 +++++++++--------- 6 files changed, 237 insertions(+), 82 deletions(-) diff --git a/desktop/src/components/activity/SessionActivityPanel.test.tsx b/desktop/src/components/activity/SessionActivityPanel.test.tsx index 83cb2eed..edc1c52e 100644 --- a/desktop/src/components/activity/SessionActivityPanel.test.tsx +++ b/desktop/src/components/activity/SessionActivityPanel.test.tsx @@ -160,6 +160,7 @@ describe('SessionActivityPanel', () => { expect(screen.getByLabelText('Task completed')).toBeInTheDocument() expect(screen.getByLabelText('Task in progress')).toBeInTheDocument() expect(screen.getByLabelText('Task pending')).toBeInTheDocument() + expect(screen.getByText('Active task').closest('button,div')).toHaveClass('py-3') expect(screen.queryByText('Completed')).not.toBeInTheDocument() expect(screen.queryByText('Pending')).not.toBeInTheDocument() }) @@ -392,17 +393,17 @@ describe('SessionActivityPanel', () => { expect(screen.getByTestId('session-activity-panel')).toHaveAttribute('data-placement', 'rail') expect(screen.getByTestId('session-activity-panel')).toHaveClass('my-4') expect(screen.getByTestId('session-activity-panel')).toHaveClass('mr-4') - expect(screen.getByTestId('session-activity-panel')).toHaveClass('w-[340px]') + expect(screen.getByTestId('session-activity-panel')).toHaveClass('w-[360px]') expect(screen.getByTestId('session-activity-panel')).toHaveClass('rounded-[24px]') expect(screen.getByTestId('session-activity-panel')).toHaveClass('self-start') - expect(screen.getByTestId('session-activity-panel')).toHaveClass('max-h-[min(480px,calc(100vh-96px))]') + expect(screen.getByTestId('session-activity-panel')).toHaveClass('max-h-[min(620px,calc(100vh-72px))]') expect(screen.getByTestId('session-activity-panel')).not.toHaveClass('h-[calc(100%-24px)]') fireEvent.pointerDown(screen.getByRole('button', { name: 'Outside' })) expect(onClose).not.toHaveBeenCalled() }) - it('caps each populated activity section independently', () => { + it('uses one polished scroll owner instead of nested section scrollbars', () => { const taskRows = Array.from({ length: 12 }, (_, index) => ({ id: `task-${index + 1}`, section: 'tasks' as const, @@ -455,15 +456,19 @@ describe('SessionActivityPanel', () => { />, ) + const scrollOwner = screen.getByTestId('session-activity-scroll') const tasksSection = document.querySelector('section[aria-label="Tasks"]') const teamSection = document.querySelector('section[aria-label="Team"]') const backgroundSection = document.querySelector('section[aria-label="Background Tasks"]') const subagentsSection = document.querySelector('section[aria-label="SubAgents"]') - expect(tasksSection?.querySelector('.max-h-44.overflow-y-auto')).toBeInTheDocument() - expect(teamSection?.querySelector('.max-h-36.overflow-y-auto')).toBeInTheDocument() - expect(backgroundSection?.querySelector('.max-h-40.overflow-y-auto')).toBeInTheDocument() - expect(subagentsSection?.querySelector('.max-h-40.overflow-y-auto')).toBeInTheDocument() + expect(scrollOwner).toHaveClass('overflow-y-auto') + expect(scrollOwner).toHaveClass('[scrollbar-width:auto]') + expect(scrollOwner).toHaveClass('[&::-webkit-scrollbar]:w-2.5') + expect(tasksSection?.querySelector('.overflow-y-auto')).not.toBeInTheDocument() + expect(teamSection?.querySelector('.overflow-y-auto')).not.toBeInTheDocument() + expect(backgroundSection?.querySelector('.overflow-y-auto')).not.toBeInTheDocument() + expect(subagentsSection?.querySelector('.overflow-y-auto')).not.toBeInTheDocument() }) it('opens a SubAgent row when the row is openable', () => { diff --git a/desktop/src/components/activity/SessionActivityPanel.tsx b/desktop/src/components/activity/SessionActivityPanel.tsx index 4e761c02..12e6b313 100644 --- a/desktop/src/components/activity/SessionActivityPanel.tsx +++ b/desktop/src/components/activity/SessionActivityPanel.tsx @@ -16,6 +16,20 @@ type SessionActivityPanelPlacement = 'overlay' | 'rail' type TranslationFn = ReturnType +const ACTIVITY_SCROLLBAR_CLASS = [ + '[scrollbar-width:auto]', + '[scrollbar-color:color-mix(in_srgb,var(--color-outline)_62%,transparent)_transparent]', + '[&::-webkit-scrollbar]:w-2.5', + '[&::-webkit-scrollbar-track]:bg-transparent', + '[&::-webkit-scrollbar-thumb]:rounded-full', + '[&::-webkit-scrollbar-thumb]:border-[3px]', + '[&::-webkit-scrollbar-thumb]:border-transparent', + '[&::-webkit-scrollbar-thumb]:bg-[color-mix(in_srgb,var(--color-outline)_68%,transparent)]', + '[&::-webkit-scrollbar-thumb]:bg-clip-content', + '[&::-webkit-scrollbar-thumb:hover]:border-2', + '[&::-webkit-scrollbar-thumb:hover]:bg-[color-mix(in_srgb,var(--color-outline)_84%,transparent)]', +].join(' ') + function fallbackStatusLabel(status: ActivityRow['status']): string { const label = String(status).replace(/[_-]/g, ' ').replace(/\s+/g, ' ').trim() if (!label) return '' @@ -63,22 +77,22 @@ function getSectionTitle(sectionId: ActivitySectionId, t: TranslationFn): string } function getSectionRowsClassName(sectionId: ActivitySectionId, rowCount: number): string { - const base = 'space-y-1' + const base = 'space-y-1.5' if (rowCount === 0) return base switch (sectionId) { case 'tasks': - return `${base} max-h-44 overflow-y-auto overscroll-contain pr-1` + return base case 'team': - return `${base} max-h-36 overflow-y-auto overscroll-contain pr-1` + return base case 'backgroundTasks': - return `${base} max-h-40 overflow-y-auto overscroll-contain pr-1` + return base case 'subagents': - return `${base} max-h-40 overflow-y-auto overscroll-contain pr-1` + return base case 'sources': - return `${base} max-h-28 overflow-y-auto overscroll-contain pr-1` + return base case 'output': - return `${base} max-h-28 overflow-y-auto overscroll-contain pr-1` + return base } } @@ -263,7 +277,7 @@ function ActivityRowView({ ) const interactiveRowClassName = - 'flex w-full items-center gap-2.5 rounded-xl px-3 py-2.5 text-left transition-[background-color,transform] duration-150 ease-out hover:bg-[var(--color-surface-hover)] active:translate-y-px focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-border-focus)]' + 'flex w-full items-center gap-3 rounded-xl px-3 py-3 text-left transition-[background-color,transform] duration-150 ease-out hover:bg-[var(--color-surface-hover)] active:translate-y-px focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-border-focus)]' if (row.section === 'team' && row.member && onOpenMember) { return ( @@ -306,7 +320,7 @@ function ActivityRowView({ } return ( -
+
{content}
) @@ -427,8 +441,8 @@ export function SessionActivityPanel({ if (!open) return null const className = placement === 'rail' - ? 'my-4 ml-3 mr-4 flex max-h-[min(480px,calc(100vh-96px))] w-[340px] shrink-0 self-start flex-col overflow-hidden rounded-[24px] border border-[var(--color-border)] bg-[var(--color-surface)] shadow-[0_26px_80px_-48px_rgba(15,23,42,0.58),0_12px_30px_-22px_rgba(15,23,42,0.34),inset_0_1px_0_rgba(255,255,255,0.82)]' - : 'absolute right-4 top-4 z-40 flex max-h-[calc(100%-112px)] w-[min(340px,calc(100%-32px))] flex-col overflow-hidden rounded-[24px] border border-[var(--color-border)] bg-[var(--color-surface)] shadow-[0_26px_80px_-48px_rgba(15,23,42,0.58),0_12px_30px_-22px_rgba(15,23,42,0.34),inset_0_1px_0_rgba(255,255,255,0.82)]' + ? 'my-4 ml-3 mr-4 flex max-h-[min(620px,calc(100vh-72px))] w-[360px] shrink-0 self-start flex-col overflow-hidden rounded-[24px] border border-[var(--color-border)] bg-[var(--color-surface)] shadow-[0_26px_80px_-48px_rgba(15,23,42,0.58),0_12px_30px_-22px_rgba(15,23,42,0.34),inset_0_1px_0_rgba(255,255,255,0.82)]' + : 'absolute right-4 top-4 z-40 flex max-h-[calc(100%-80px)] w-[min(360px,calc(100%-32px))] flex-col overflow-hidden rounded-[24px] border border-[var(--color-border)] bg-[var(--color-surface)] shadow-[0_26px_80px_-48px_rgba(15,23,42,0.58),0_12px_30px_-22px_rgba(15,23,42,0.34),inset_0_1px_0_rgba(255,255,255,0.82)]' return (
-
+
{visibleSections.map((section, index) => { const sectionTitle = getSectionTitle(section.id, t) diff --git a/desktop/src/pages/ActiveSession.test.tsx b/desktop/src/pages/ActiveSession.test.tsx index 2d1ab8f4..95bdbff2 100644 --- a/desktop/src/pages/ActiveSession.test.tsx +++ b/desktop/src/pages/ActiveSession.test.tsx @@ -635,6 +635,83 @@ describe('ActiveSession task polling', () => { }) }) + it('auto-opens the activity panel when the current session first produces activity', async () => { + const sessionId = 'activity-auto-open-session' + const fetchSessionTasks = vi.fn().mockResolvedValue(undefined) + + useCLITaskStore.setState({ fetchSessionTasks }) + useSessionStore.setState({ + sessions: [{ + id: sessionId, + title: 'Auto Open Activity Session', + createdAt: '2026-05-07T00:00:00.000Z', + modifiedAt: '2026-05-07T00:00:00.000Z', + messageCount: 0, + projectPath: '/workspace/project', + workDir: '/workspace/project', + workDirExists: true, + }], + activeSessionId: sessionId, + isLoading: false, + error: null, + }) + useTabStore.setState({ + tabs: [{ sessionId, title: 'Auto Open Activity Session', type: 'session', status: 'idle' }], + activeTabId: sessionId, + }) + useChatStore.setState({ + sessions: { + [sessionId]: { + messages: [], + chatState: 'idle', + connectionState: 'connected', + streamingText: '', + streamingToolInput: '', + activeToolUseId: null, + activeToolName: null, + activeThinkingId: null, + pendingPermission: null, + pendingComputerUsePermission: null, + tokenUsage: { input_tokens: 0, output_tokens: 0 }, + streamingResponseChars: 0, + elapsedSeconds: 0, + statusVerb: '', + slashCommands: [], + backgroundAgentTasks: {}, + agentTaskNotifications: {}, + elapsedTimer: null, + }, + }, + }) + + render() + + expect(screen.queryByTestId('session-activity-panel')).not.toBeInTheDocument() + expect(useActivityPanelStore.getState().isOpen(sessionId)).toBe(false) + + act(() => { + useCLITaskStore.setState({ + sessionId, + tasks: [{ + id: 'task-1', + subject: 'Draft implementation plan', + description: 'Create the first activity row', + status: 'in_progress', + blocks: [], + blockedBy: [], + taskListId: sessionId, + }], + completedAndDismissed: false, + }) + }) + + await waitFor(() => { + expect(useActivityPanelStore.getState().isOpen(sessionId)).toBe(true) + }) + expect(screen.getByTestId('session-activity-panel')).toHaveAttribute('data-placement', 'rail') + expect(screen.getByText('Draft implementation plan')).toBeInTheDocument() + }) + it('renders completed historical TodoWrite activity in the rail', () => { const sessionId = 'activity-todowrite-history-session' diff --git a/desktop/src/pages/ActiveSession.tsx b/desktop/src/pages/ActiveSession.tsx index 593461a9..7c84e1b3 100644 --- a/desktop/src/pages/ActiveSession.tsx +++ b/desktop/src/pages/ActiveSession.tsx @@ -302,6 +302,7 @@ export function ActiveSession() { const hasIncompleteTasks = cliTasks.some((task) => task.status !== 'completed') const hasRunningTasks = cliTasks.some((task) => task.status === 'in_progress') const isActivityPanelOpen = useActivityPanelStore((state) => activeTabId ? state.isOpen(activeTabId) : false) + const openActivityPanel = useActivityPanelStore((state) => state.open) const closeActivityPanel = useActivityPanelStore((state) => state.close) const dismissBackgroundTaskKeys = useActivityPanelStore((state) => state.dismissBackgroundTaskKeys) const pruneDismissedBackgroundTaskKeys = useActivityPanelStore((state) => state.pruneDismissedBackgroundTaskKeys) @@ -336,6 +337,7 @@ export function ActiveSession() { : undefined, ) const terminalPanelHeight = useTerminalPanelStore((state) => state.height) + const activityVisibilityBySessionRef = useRef>({}) useEffect(() => { if (activeTabId && !isMemberSession) { @@ -442,6 +444,31 @@ export function ActiveSession() { trackedTaskSessionId, ]) const hasVisibleActivity = activityModel ? hasVisibleSessionActivity(activityModel) : false + const hasAutoOpenActivity = activityModel ? activityModel.badgeCount > 0 : false + + useEffect(() => { + if (!activeTabId || isMemberSession || !isSessionTabState(activeTabId, activeTabType)) return + + const state = activityVisibilityBySessionRef.current[activeTabId] + if (!state) { + activityVisibilityBySessionRef.current[activeTabId] = { + hadAutoOpenActivity: hasAutoOpenActivity, + } + return + } + + if (!state.hadAutoOpenActivity && hasAutoOpenActivity && !isActivityPanelOpen) { + openActivityPanel(activeTabId) + } + state.hadAutoOpenActivity = hasAutoOpenActivity + }, [ + activeTabId, + activeTabType, + hasAutoOpenActivity, + isActivityPanelOpen, + isMemberSession, + openActivityPanel, + ]) useEffect(() => { if (!activeTabId || !isActivityPanelOpen || hasVisibleActivity) return diff --git a/desktop/src/pages/SubagentRunPage.test.tsx b/desktop/src/pages/SubagentRunPage.test.tsx index e6b2ec50..439d3c5f 100644 --- a/desktop/src/pages/SubagentRunPage.test.tsx +++ b/desktop/src/pages/SubagentRunPage.test.tsx @@ -61,6 +61,7 @@ describe('SubagentRunPage', () => { afterEach(() => { cleanup() + vi.useRealTimers() vi.mocked(subagentsApi.getRunByTool).mockReset() }) @@ -72,16 +73,15 @@ describe('SubagentRunPage', () => { render() expect(await screen.findByText('Kuhn')).toBeInTheDocument() - expect(screen.getAllByText('Agent').length).toBeGreaterThan(0) + expect(screen.getByText('Agent: abc123')).toBeInTheDocument() expect(screen.getAllByText('Explore repo').length).toBeGreaterThan(0) - expect(screen.getAllByText('Found layout seam').length).toBeGreaterThan(0) expect(screen.getByText('Output: /tmp/result.md')).toBeInTheDocument() - expect(screen.getByText('Parent Agent Tool Call')).toBeInTheDocument() - expect(document.body).toHaveTextContent('"prompt": "Read files"') + expect(screen.queryByText('Parent Agent Tool Call')).not.toBeInTheDocument() + expect(document.body).not.toHaveTextContent('"prompt": "Read files"') expect(screen.queryByText(/Dispatched an agent|派遣了一个代理/)).not.toBeInTheDocument() expect(screen.queryByRole('button', { name: /Open run/ })).not.toBeInTheDocument() - const transcript = screen.getByTestId('subagent-transcript') + const transcript = screen.getByTestId('subagent-conversation') expect(transcript).toHaveTextContent('Read files') expect(transcript).toHaveTextContent('Finding') expect(transcript).not.toHaveTextContent('assistant_text') @@ -107,8 +107,33 @@ describe('SubagentRunPage', () => { render() - expect(await screen.findByText('No local transcript messages captured for this SubAgent.')).toBeInTheDocument() - expect(screen.getAllByText('Only summary available').length).toBeGreaterThan(0) + const conversation = await screen.findByTestId('subagent-conversation') + expect(conversation).toHaveTextContent('Only summary available') + expect(screen.queryByText('No local transcript messages captured for this SubAgent.')).not.toBeInTheDocument() + }) + + it('refreshes running SubAgent runs while the detail tab is open', async () => { + vi.mocked(subagentsApi.getRunByTool) + .mockResolvedValueOnce(subagentRun({ + status: 'running', + messages: [], + prompt: 'Review streaming changes', + })) + .mockResolvedValueOnce(subagentRun({ + status: 'completed', + messages: [], + prompt: 'Review streaming changes', + result: 'Streaming review complete', + })) + + render() + + expect(await screen.findByText('Running')).toBeInTheDocument() + expect(screen.getByTestId('subagent-conversation')).toHaveTextContent('Review streaming changes') + + await waitFor(() => expect(subagentsApi.getRunByTool).toHaveBeenCalledTimes(2), { timeout: 2500 }) + expect(await screen.findByText('Completed')).toBeInTheDocument() + expect(screen.getByTestId('subagent-conversation')).toHaveTextContent('Streaming review complete') }) it('keeps the tab open on API errors', async () => { @@ -145,7 +170,6 @@ describe('SubagentRunPage', () => { await second.promise }) - expect((await screen.findAllByText('Second result')).length).toBeGreaterThan(0) expect(screen.getByText(/Second finding/)).toBeInTheDocument() await act(async () => { @@ -163,14 +187,14 @@ describe('SubagentRunPage', () => { await first.promise }) - expect(screen.getAllByText('Second result').length).toBeGreaterThan(0) + expect(screen.getByText(/Second finding/)).toBeInTheDocument() expect(screen.queryByText('Stale first result')).not.toBeInTheDocument() expect(screen.queryByText(/Stale finding/)).not.toBeInTheDocument() }) it('keeps existing details visible when refresh fails', async () => { vi.mocked(subagentsApi.getRunByTool) - .mockResolvedValueOnce(subagentRun({ summary: 'Initial result' })) + .mockResolvedValueOnce(subagentRun({ messages: [], summary: 'Initial result' })) .mockRejectedValueOnce(new Error('refresh failed')) render() diff --git a/desktop/src/pages/SubagentRunPage.tsx b/desktop/src/pages/SubagentRunPage.tsx index 3699bd1a..c990ab97 100644 --- a/desktop/src/pages/SubagentRunPage.tsx +++ b/desktop/src/pages/SubagentRunPage.tsx @@ -6,15 +6,13 @@ import { type SubagentRunStatus, } from '../api/subagents' import { buildRenderModel, MessageBlock } from '../components/chat/MessageList' -import { ToolCallBlock } from '../components/chat/ToolCallBlock' import { ToolCallGroup } from '../components/chat/ToolCallGroup' import { useTranslation } from '../i18n' import { mapHistoryMessagesToUiMessages } from '../stores/chatStore' import type { AgentTaskNotification, UIMessage } from '../types/chat' -type ToolCall = Extract -type ToolResult = Extract type TranslationFn = ReturnType +const LIVE_RUN_REFRESH_MS = 2000 export function SubagentRunPage({ sourceSessionId, @@ -54,6 +52,16 @@ export function SubagentRunPage({ void load({ resetData: true }) }, [load]) + useEffect(() => { + if (data?.status !== 'running' || loading) return + + const timer = window.setTimeout(() => { + void load() + }, LIVE_RUN_REFRESH_MS) + + return () => window.clearTimeout(timer) + }, [data?.status, load, loading]) + return (
@@ -96,8 +104,6 @@ export function SubagentRunPage({ function SubagentRunDetails({ data }: { data: SubagentRunResponse }) { const t = useTranslation() - const agentToolCall = useMemo(() => buildAgentToolCall(data), [data]) - const agentToolResult = useMemo(() => buildAgentToolResult(data, t), [data, t]) return (
@@ -105,6 +111,12 @@ function SubagentRunDetails({ data }: { data: SubagentRunResponse }) { {t('subagentRun.source')}: {sourceLabel(data.source, t)} {t('subagentRun.agent')}: {data.agentId ?? t('subagentRun.unknown')} + {data.description ? ( + <> + + {data.description} + + ) : null} {data.taskId ? ( <> @@ -127,30 +139,17 @@ function SubagentRunDetails({ data }: { data: SubagentRunResponse }) { ) : null}
-
-

{t('subagentRun.parentToolCall')}

- -
- - +
) } const EMPTY_AGENT_TASK_NOTIFICATIONS: Record = {} -function TranscriptSection({ data }: { data: SubagentRunResponse }) { +function ConversationSection({ data }: { data: SubagentRunResponse }) { const t = useTranslation() - const transcriptMessages = useMemo(() => ( - mapHistoryMessagesToUiMessages(data.messages, { includeTeammateMessages: true }) - ), [data.messages]) - const renderModel = useMemo(() => buildRenderModel(transcriptMessages), [transcriptMessages]) + const conversationMessages = useMemo(() => buildSubagentConversationMessages(data), [data]) + const renderModel = useMemo(() => buildRenderModel(conversationMessages), [conversationMessages]) if (renderModel.renderItems.length === 0) { return ( @@ -171,7 +170,7 @@ function TranscriptSection({ data }: { data: SubagentRunResponse }) { {t('subagentRun.truncated')} ) : null}
-
+
{renderModel.renderItems.map((item) => { if (item.kind === 'tool_group') { return ( @@ -265,38 +264,44 @@ function timestampMs(value: string | undefined) { return Number.isFinite(time) ? time : Date.now() } -function buildAgentToolCall(data: SubagentRunResponse): ToolCall { - return { - id: `subagent-tool-${data.toolUseId}`, - type: 'tool_use', - toolName: 'Agent', - toolUseId: data.toolUseId, - input: { - ...(data.description ? { description: data.description } : {}), - ...(data.prompt ? { prompt: data.prompt } : {}), - }, - timestamp: timestampMs(data.updatedAt), +function normalizedText(value: string | undefined) { + return (value ?? '').replace(/\s+/g, ' ').trim() +} + +function hasPromptMessage(messages: UIMessage[], prompt: string) { + const normalizedPrompt = normalizedText(prompt) + if (!normalizedPrompt) return false + + return messages.some((message) => ( + message.type === 'user_text' && + normalizedText(message.content) === normalizedPrompt + )) +} + +function buildSubagentConversationMessages(data: SubagentRunResponse): UIMessage[] { + const transcriptMessages = mapHistoryMessagesToUiMessages(data.messages, { includeTeammateMessages: true }) + const messages = [...transcriptMessages] + const prompt = data.prompt?.trim() + const baseTimestamp = timestampMs(data.updatedAt) + + if (prompt && !hasPromptMessage(transcriptMessages, prompt)) { + messages.unshift({ + id: `subagent-prompt-${data.toolUseId}`, + type: 'user_text', + content: prompt, + timestamp: baseTimestamp - 1, + }) } -} -function buildAgentToolResult(data: SubagentRunResponse, t: TranslationFn): ToolResult | null { - const resultText = data.result || data.summary - if (!resultText && data.status === 'running') return null - if (!resultText && data.status === 'unknown') return null - - return { - id: `subagent-result-${data.toolUseId}`, - type: 'tool_result', - toolUseId: data.toolUseId, - content: resultText || statusFallbackResult(data.status, t), - isError: data.status === 'failed' || data.status === 'stopped', - timestamp: timestampMs(data.updatedAt), + const resultText = (data.result || data.summary)?.trim() + if (transcriptMessages.length === 0 && resultText) { + messages.push({ + id: `subagent-result-message-${data.toolUseId}`, + type: 'assistant_text', + content: resultText, + timestamp: baseTimestamp, + }) } -} -function statusFallbackResult(status: SubagentRunStatus, t: TranslationFn) { - if (status === 'completed') return t('subagentRun.result.completedFallback') - if (status === 'failed') return t('subagentRun.result.failedFallback') - if (status === 'stopped') return t('subagentRun.result.stoppedFallback') - return t('subagentRun.result.noneFallback') + return messages }