import { describe, expect, it } from 'vitest'; import { deriveTaskSteps, parseSubagentCompletionInfo } from '@/pages/Chat/task-visualization'; import type { RawMessage, ToolStatus } from '@/stores/chat'; describe('deriveTaskSteps', () => { it('builds running steps from streaming thinking and tool status', () => { const streamingTools: ToolStatus[] = [ { name: 'web_search', status: 'running', updatedAt: Date.now(), summary: 'Searching docs', }, ]; const steps = deriveTaskSteps({ messages: [], streamingMessage: { role: 'assistant', content: [ { type: 'thinking', thinking: 'Compare a few approaches before coding.' }, { type: 'tool_use', id: 'tool-1', name: 'web_search', input: { query: 'openclaw task list' } }, ], }, streamingTools, sending: true, pendingFinal: false, showThinking: true, }); expect(steps).toEqual([ expect.objectContaining({ id: 'stream-thinking', label: 'Thinking', status: 'running', kind: 'thinking', }), expect.objectContaining({ label: 'web_search', status: 'running', kind: 'tool', }), ]); }); it('keeps recent completed steps from assistant history', () => { const messages: RawMessage[] = [ { role: 'assistant', id: 'assistant-1', content: [ { type: 'thinking', thinking: 'Reviewing the code path.' }, { type: 'tool_use', id: 'tool-2', name: 'read_file', input: { path: 'src/App.tsx' } }, ], }, ]; const steps = deriveTaskSteps({ messages, streamingMessage: null, streamingTools: [], sending: false, pendingFinal: false, showThinking: true, }); expect(steps).toEqual([ expect.objectContaining({ id: 'history-thinking-assistant-1', label: 'Thinking', status: 'completed', kind: 'thinking', depth: 1, }), expect.objectContaining({ id: 'tool-2', label: 'read_file', status: 'completed', kind: 'tool', depth: 1, }), ]); }); it('builds a branch for spawned subagents', () => { const messages: RawMessage[] = [ { role: 'assistant', id: 'assistant-2', content: [ { type: 'tool_use', id: 'spawn-1', name: 'sessions_spawn', input: { agentId: 'coder', task: 'inspect repo' }, }, { type: 'tool_use', id: 'yield-1', name: 'sessions_yield', input: { message: 'wait coder finishes' }, }, ], }, ]; const steps = deriveTaskSteps({ messages, streamingMessage: null, streamingTools: [], sending: false, pendingFinal: false, showThinking: true, }); expect(steps).toEqual([ expect.objectContaining({ id: 'spawn-1', label: 'sessions_spawn', depth: 1, }), expect.objectContaining({ id: 'spawn-1:branch', label: 'coder run', depth: 2, parentId: 'spawn-1', }), expect.objectContaining({ id: 'yield-1', label: 'sessions_yield', depth: 3, parentId: 'spawn-1:branch', }), ]); }); it('parses internal subagent completion events from injected user messages', () => { const info = parseSubagentCompletionInfo({ role: 'user', content: [{ type: 'text', text: `[Internal task completion event] source: subagent session_key: agent:coder:subagent:child-123 session_id: child-session-id status: completed successfully`, }], } as RawMessage); expect(info).toEqual({ sessionKey: 'agent:coder:subagent:child-123', sessionId: 'child-session-id', agentId: 'coder', }); }); });