import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { buildOfficeTaskList, buildOfficeView } from '@/lib/office'; import type { ProcessArtifact, ProcessEvent, ProcessRun, Session } from '@/types'; describe('office view builders', () => { beforeEach(() => { vi.useFakeTimers(); vi.setSystemTime(new Date('2026-03-24T12:00:00.000Z')); }); afterEach(() => { vi.useRealTimers(); }); it('builds an office view from a root run tree', () => { const sessions: Session[] = [ { key: 'web:default', path: '需求讨论', created_at: '2026-03-24T09:55:00.000Z', updated_at: '2026-03-24T10:10:00.000Z', }, ]; const processRuns: ProcessRun[] = [ { run_id: 'run-root', parent_run_id: null, session_id: 'web:default', actor_type: 'agent', actor_id: 'main-agent', actor_name: '主 Agent', title: '整理竞品研究并给出结论', status: 'running', started_at: '2026-03-24T10:00:00.000Z', metadata: { stage_label: '分析结果', }, }, { run_id: 'run-sub-agent', parent_run_id: 'run-root', session_id: 'web:default', actor_type: 'agent', actor_id: 'research-agent', actor_name: 'Research Agent', title: '收集竞品资料', status: 'done', started_at: '2026-03-24T10:01:00.000Z', finished_at: '2026-03-24T10:04:00.000Z', summary: '已完成资料收集', }, { run_id: 'run-sub-mcp', parent_run_id: 'run-root', session_id: 'web:default', actor_type: 'mcp', actor_id: 'search-mcp', actor_name: 'Search MCP', title: '抓取公开资料', status: 'running', started_at: '2026-03-24T10:02:00.000Z', }, ]; const processEvents: ProcessEvent[] = [ { event_id: 'evt-1', run_id: 'run-root', parent_run_id: null, kind: 'run_progress', actor_type: 'agent', actor_id: 'main-agent', actor_name: '主 Agent', text: '开始归纳公开信息', created_at: '2026-03-24T10:03:00.000Z', metadata: { stage_label: '分析结果', }, }, { event_id: 'evt-2', run_id: 'run-sub-agent', parent_run_id: 'run-root', kind: 'run_finished', actor_type: 'agent', actor_id: 'research-agent', actor_name: 'Research Agent', text: '资料整理完成', status: 'done', created_at: '2026-03-24T10:04:00.000Z', }, { event_id: 'evt-3', run_id: 'run-sub-mcp', parent_run_id: 'run-root', kind: 'run_progress', actor_type: 'mcp', actor_id: 'search-mcp', actor_name: 'Search MCP', text: '正在搜索公开网页', created_at: '2026-03-24T10:05:00.000Z', }, ]; const processArtifacts: ProcessArtifact[] = [ { artifact_id: 'artifact-1', run_id: 'run-sub-agent', actor_type: 'agent', actor_id: 'research-agent', actor_name: 'Research Agent', title: '竞品清单', artifact_type: 'markdown', content: '- A\n- B', created_at: '2026-03-24T10:04:30.000Z', }, ]; const office = buildOfficeView('run-root', { sessions, processRuns, processEvents, processArtifacts, }); expect(office).not.toBeNull(); expect(office?.taskId).toBe('run-root'); expect(office?.title).toBe('整理竞品研究并给出结论'); expect(office?.sourceSessionLabel).toBe('需求讨论'); expect(office?.members).toHaveLength(3); expect(office?.tasks).toHaveLength(3); expect(office?.assignments).toHaveLength(1); expect(office?.progress.label).toBe('已完成子任务 1 / 3'); expect(office?.currentStageLabel).toBe('分析结果'); expect(office?.stats.artifactCount).toBe(1); expect(office?.zones.find((zone) => zone.id === 'workspace')?.memberIds).toContain('main-agent'); expect(office?.zones.find((zone) => zone.id === 'collab')?.memberIds).toContain('research-agent'); expect(office?.zones.find((zone) => zone.id === 'research')?.memberIds).toContain('search-mcp'); }); it('marks stale waiting tasks as blocked and emits alerts', () => { const processRuns: ProcessRun[] = [ { run_id: 'run-blocked', parent_run_id: null, session_id: 'web:default', actor_type: 'agent', actor_id: 'main-agent', actor_name: '主 Agent', title: '等待下游结果', status: 'waiting', started_at: '2026-03-24T09:00:00.000Z', }, ]; const office = buildOfficeView('run-blocked', { sessions: [], processRuns, processEvents: [], processArtifacts: [], }); expect(office?.status).toBe('blocked'); expect(office?.alerts).toHaveLength(1); expect(office?.alerts[0].level).toBe('warn'); expect(office?.members[0].zoneId).toBe('collab'); }); it('builds a filtered task list and sorts active tasks ahead of finished ones', () => { const sessions: Session[] = [ { key: 'web:alpha', path: 'Alpha Session' }, { key: 'web:beta', path: 'Beta Session' }, ]; const processRuns: ProcessRun[] = [ { run_id: 'run-active', parent_run_id: null, session_id: 'web:alpha', actor_type: 'agent', actor_id: 'agent-a', actor_name: 'Agent A', title: '执行活跃任务', status: 'running', started_at: '2026-03-24T11:20:00.000Z', }, { run_id: 'run-done', parent_run_id: null, session_id: 'web:alpha', actor_type: 'agent', actor_id: 'agent-b', actor_name: 'Agent B', title: '已结束任务', status: 'done', started_at: '2026-03-24T10:00:00.000Z', finished_at: '2026-03-24T10:08:00.000Z', }, { run_id: 'run-other-session', parent_run_id: null, session_id: 'web:beta', actor_type: 'agent', actor_id: 'agent-c', actor_name: 'Agent C', title: '其他会话任务', status: 'running', started_at: '2026-03-24T11:00:00.000Z', }, ]; const tasks = buildOfficeTaskList({ sessionId: 'web:alpha', sessions, processRuns, processEvents: [], processArtifacts: [], }); expect(tasks).toHaveLength(2); expect(tasks[0].taskId).toBe('run-active'); expect(tasks[1].taskId).toBe('run-done'); expect(tasks[0].sessionLabel).toBe('Alpha Session'); }); it('keeps office tasks visible when the root run inherits session from descendants', () => { const tasks = buildOfficeTaskList({ sessionId: 'web:alpha', sessions: [{ key: 'web:alpha', path: 'Alpha Session' }], processRuns: [ { run_id: 'run-root-no-session', parent_run_id: null, actor_type: 'agent', actor_id: 'agent-a', actor_name: 'Agent A', title: '根任务缺少会话字段', status: 'running', started_at: '2026-03-24T11:20:00.000Z', }, { run_id: 'run-child-with-session', parent_run_id: 'run-root-no-session', session_id: 'web:alpha', actor_type: 'agent', actor_id: 'agent-b', actor_name: 'Agent B', title: '子任务仍带着会话字段', status: 'running', started_at: '2026-03-24T11:21:00.000Z', }, ], processEvents: [], processArtifacts: [], }); expect(tasks).toHaveLength(1); expect(tasks[0].taskId).toBe('run-root-no-session'); expect(tasks[0].sessionId).toBe('web:alpha'); }); });