diff --git a/packages/chat-core/src/workbench-message-reducer.test.ts b/packages/chat-core/src/workbench-message-reducer.test.ts index a367804894..1ea9be98a1 100644 --- a/packages/chat-core/src/workbench-message-reducer.test.ts +++ b/packages/chat-core/src/workbench-message-reducer.test.ts @@ -75,6 +75,43 @@ describe('reduceWorkbenchMessages', () => { ]) }) + test('finalizes incoming processing blocks on done', () => { + const state = reduceWorkbenchMessages([], { + type: 'assistant_started', + taskId: 1, + subtaskId: 9, + }) + + const done = reduceWorkbenchMessages(state, { + type: 'assistant_done', + subtaskId: 9, + content: 'Final', + blocks: [ + { + id: 'thinking-real', + subtaskId: 9, + type: 'thinking', + content: 'Drafting', + status: 'streaming', + createdAt: 1770000000000, + }, + { + id: 'call_1', + subtaskId: 9, + type: 'tool', + toolName: 'bash', + status: 'pending', + createdAt: 1770000001000, + }, + ], + }) + + expect(done[0].blocks).toMatchObject([ + { id: 'thinking-real', type: 'thinking', status: 'done' }, + { id: 'call_1', type: 'tool', status: 'done' }, + ]) + }) + test('preserves state for unknown runtime actions', () => { const state: WorkbenchMessage[] = [ { @@ -102,7 +139,11 @@ describe('normalizeWorkbenchBlockStatus', () => { expect(normalizeWorkbenchBlockStatus('streaming')).toBe('streaming') expect(normalizeWorkbenchBlockStatus('done')).toBe('done') expect(normalizeWorkbenchBlockStatus('error')).toBe('error') + expect(normalizeWorkbenchBlockStatus('completed')).toBe('done') + expect(normalizeWorkbenchBlockStatus('succeeded')).toBe('done') + expect(normalizeWorkbenchBlockStatus('failed')).toBe('error') expect(normalizeWorkbenchBlockStatus('running')).toBe('pending') + expect(normalizeWorkbenchBlockStatus('inProgress')).toBe('pending') expect(normalizeWorkbenchBlockStatus('unsupported')).toBe('pending') expect(normalizeWorkbenchBlockStatus()).toBe('pending') }) diff --git a/packages/chat-core/src/workbench-message-reducer.ts b/packages/chat-core/src/workbench-message-reducer.ts index 31ce1fc44a..e36a22447b 100644 --- a/packages/chat-core/src/workbench-message-reducer.ts +++ b/packages/chat-core/src/workbench-message-reducer.ts @@ -163,7 +163,7 @@ export function reduceWorkbenchMessages - block.type === 'thinking' && block.status === 'streaming' - ? { ...block, status: 'done' as const } - : block - ) + return finalizeBlocks(blocks) +} + +function finalizeBlocks( + blocks: WorkbenchProcessingBlock[] | undefined, + finalStatus?: Extract +): WorkbenchProcessingBlock[] { + return (blocks ?? []).map(block => { + if (block.type === 'thinking' && block.status === 'streaming') { + return { ...block, status: 'done' as const } + } + + if (!finalStatus || block.status === 'done' || block.status === 'error') { + return block + } + + return { ...block, status: finalStatus } as WorkbenchProcessingBlock + }) } function finalizeProcessingBlocks( - blocks: WorkbenchProcessingBlock[] | undefined + blocks: WorkbenchProcessingBlock[] | undefined, + finalStatus: Extract = 'done' ): WorkbenchProcessingBlock[] | undefined { if (!blocks) return undefined - return finalizeThinkingBlocks(blocks) + return finalizeBlocks(blocks, finalStatus) } function mergeProcessingBlock( diff --git a/wework/.prettierrc.json b/wework/.prettierrc.json new file mode 100644 index 0000000000..23c45a9a25 --- /dev/null +++ b/wework/.prettierrc.json @@ -0,0 +1,10 @@ +{ + "semi": false, + "trailingComma": "es5", + "singleQuote": true, + "printWidth": 100, + "tabWidth": 2, + "useTabs": false, + "arrowParens": "avoid", + "endOfLine": "lf" +} diff --git a/wework/src/features/workbench/WorkbenchProvider.test.tsx b/wework/src/features/workbench/WorkbenchProvider.test.tsx index 17eaa1912b..d8cb918df0 100644 --- a/wework/src/features/workbench/WorkbenchProvider.test.tsx +++ b/wework/src/features/workbench/WorkbenchProvider.test.tsx @@ -7,11 +7,7 @@ import type { Attachment, SkillRef, UnifiedModel } from '@/types/api' function Probe() { const { state } = useWorkbench() - return ( -
- {state.isBootstrapping ? 'loading' : state.user?.user_name} -
- ) + return
{state.isBootstrapping ? 'loading' : state.user?.user_name}
} function ProjectChatProbe() { @@ -42,14 +38,10 @@ function ProjectChatProbe() {
{workbench.messages - .flatMap(message => - (message.attachments ?? []).map(attachment => attachment.filename) - ) + .flatMap(message => (message.attachments ?? []).map(attachment => attachment.filename)) .join(',')} - - {workbench.projectExecutionMode} - + {workbench.projectExecutionMode} {workbench.state.input} {workbench.state.error ?? ''} -
{workbench.projectChat.models - .map( - model => - `${model.name}:${model.compatibilityDisabledReason ?? 'enabled'}` - ) + .map(model => `${model.name}:${model.compatibilityDisabledReason ?? 'enabled'}`) .join('|')}
@@ -143,9 +129,7 @@ function ProjectTaskSendProbe() {
{workbench.state.input} {workbench.state.error ?? ''} - - {workbench.state.currentTask?.id ?? 'no-task'} - + {workbench.state.currentTask?.id ?? 'no-task'} {workbench.state.projects.length} @@ -2471,9 +2431,7 @@ describe('WorkbenchProvider', () => { await userEvent.click(screen.getByText('set first input')) await userEvent.click(screen.getByText('send')) - await waitFor(() => - expect(screen.getByTestId('current-task-id')).toHaveTextContent('99') - ) + await waitFor(() => expect(screen.getByTestId('current-task-id')).toHaveTextContent('99')) await userEvent.click(screen.getByText('set second input')) await userEvent.click(screen.getByText('send')) @@ -2491,11 +2449,7 @@ describe('WorkbenchProvider', () => { test('keeps later guidance queued while one guidance send is in progress', async () => { let streamHandlers: { - onChatStart?: (payload: { - task_id: number - subtask_id: number - shell_type?: string - }) => void + onChatStart?: (payload: { task_id: number; subtask_id: number; shell_type?: string }) => void } = {} let resolveCancel: ((value: { success: boolean }) => void) | undefined const cancelStream = vi.fn().mockImplementation( @@ -2605,11 +2559,7 @@ describe('WorkbenchProvider', () => { test('does not drain remaining queued messages before the guided response starts', async () => { let streamHandlers: { - onChatStart?: (payload: { - task_id: number - subtask_id: number - shell_type?: string - }) => void + onChatStart?: (payload: { task_id: number; subtask_id: number; shell_type?: string }) => void } = {} const cancelStream = vi.fn().mockResolvedValue({ success: true }) const sendMessage = vi.fn().mockResolvedValue({ success: true, task_id: 8 }) @@ -2704,11 +2654,7 @@ describe('WorkbenchProvider', () => { test('retries a failed assistant message using the previous user message', async () => { type StreamHandlers = { - onChatStart?: (payload: { - task_id: number - subtask_id: number - shell_type?: string - }) => void + onChatStart?: (payload: { task_id: number; subtask_id: number; shell_type?: string }) => void onChatError?: (payload: { task_id?: number subtask_id: number @@ -2794,9 +2740,7 @@ describe('WorkbenchProvider', () => { }) await waitFor(() => - expect(screen.getByTestId('retry-message-states')).toHaveTextContent( - 'assistant::failed', - ) + expect(screen.getByTestId('retry-message-states')).toHaveTextContent('assistant::failed') ) await userEvent.click(screen.getByText('retry failed message')) @@ -2806,7 +2750,7 @@ describe('WorkbenchProvider', () => { expect.objectContaining({ task_id: 8, message: 'hi', - }), + }) ) }) @@ -2932,9 +2876,7 @@ describe('WorkbenchProvider', () => { 'user:我叫胡云鹏assistant:你好,胡云鹏!' ) ) - expect(screen.getByTestId('history-attachment-filenames')).toHaveTextContent( - 'diagram.png' - ) + expect(screen.getByTestId('history-attachment-filenames')).toHaveTextContent('diagram.png') }) test('restores persisted tool blocks when opening task history', async () => { @@ -2952,7 +2894,7 @@ describe('WorkbenchProvider', () => { ? [
  • {block.toolName}:{String(block.toolInput?.command)}: - {String(block.toolOutput)} + {String(block.toolOutput)}:{block.status}
  • , ] : [ @@ -3015,20 +2957,21 @@ describe('WorkbenchProvider', () => { tool_name: 'exec', tool_input: { command: 'pwd' }, tool_output: '/Users/yunpeng7/AIGCWorkSpace', - status: 'done', - timestamp: 1770000000000, + status: 'completed', + timestamp: 1770000000, }, { id: 'thinking_1', type: 'thinking', content: 'I will inspect the workspace', - status: 'done', + status: 'streaming', timestamp: 1770000000100, }, ], }, status: 'COMPLETED', created_at: '2026-05-27T00:02:00.000Z', + completed_at: '2026-05-27T00:03:00.000Z', }, ], }), @@ -3063,7 +3006,7 @@ describe('WorkbenchProvider', () => { await waitFor(() => expect(screen.getByTestId('tool-blocks')).toHaveTextContent( - 'exec:pwd:/Users/yunpeng7/AIGCWorkSpace' + 'exec:pwd:/Users/yunpeng7/AIGCWorkSpace:done' ) ) expect(screen.getByTestId('tool-blocks')).toHaveTextContent( @@ -3237,7 +3180,10 @@ describe('WorkbenchProvider', () => { {workbench.state.currentTask?.title ?? ''} -