diff --git a/apps/mcp-server/src/agent/agent.types.spec.ts b/apps/mcp-server/src/agent/agent.types.spec.ts new file mode 100644 index 0000000..72a6bcd --- /dev/null +++ b/apps/mcp-server/src/agent/agent.types.spec.ts @@ -0,0 +1,82 @@ +import { describe, it, expect } from 'vitest'; +import type { + TaskmaestroAssignment, + TaskmaestroDispatch, + DispatchAgentsInput, + DispatchResult, +} from './agent.types'; + +describe('agent.types - TaskMaestro types', () => { + describe('TaskmaestroAssignment', () => { + it('accepts object with name, displayName, prompt', () => { + const assignment: TaskmaestroAssignment = { + name: 'frontend-dev', + displayName: 'Frontend Developer', + prompt: 'Implement the UI component', + }; + expect(assignment.name).toBe('frontend-dev'); + expect(assignment.displayName).toBe('Frontend Developer'); + expect(assignment.prompt).toBe('Implement the UI component'); + }); + }); + + describe('TaskmaestroDispatch', () => { + it('accepts object with sessionName, paneCount, assignments', () => { + const dispatch: TaskmaestroDispatch = { + sessionName: 'workspace-1', + paneCount: 3, + assignments: [{ name: 'dev-1', displayName: 'Dev 1', prompt: 'Task 1' }], + }; + expect(dispatch.sessionName).toBe('workspace-1'); + expect(dispatch.paneCount).toBe(3); + expect(dispatch.assignments).toHaveLength(1); + }); + }); + + describe('DispatchAgentsInput - executionStrategy', () => { + it('accepts executionStrategy: subagent', () => { + const input: DispatchAgentsInput = { + mode: 'PLAN', + executionStrategy: 'subagent', + }; + expect(input.executionStrategy).toBe('subagent'); + }); + + it('accepts executionStrategy: taskmaestro', () => { + const input: DispatchAgentsInput = { + mode: 'ACT', + executionStrategy: 'taskmaestro', + }; + expect(input.executionStrategy).toBe('taskmaestro'); + }); + + it('executionStrategy is optional', () => { + const input: DispatchAgentsInput = { mode: 'PLAN' }; + expect(input.executionStrategy).toBeUndefined(); + }); + }); + + describe('DispatchResult - taskmaestro fields', () => { + it('accepts optional taskmaestro dispatch data', () => { + const result: DispatchResult = { + executionHint: 'Use taskmaestro for parallel execution', + taskmaestro: { + sessionName: 'ws-1', + paneCount: 2, + assignments: [], + }, + executionStrategy: 'taskmaestro', + }; + expect(result.taskmaestro?.sessionName).toBe('ws-1'); + expect(result.executionStrategy).toBe('taskmaestro'); + }); + + it('taskmaestro and executionStrategy are optional', () => { + const result: DispatchResult = { + executionHint: 'Use subagent', + }; + expect(result.taskmaestro).toBeUndefined(); + expect(result.executionStrategy).toBeUndefined(); + }); + }); +}); diff --git a/apps/mcp-server/src/agent/agent.types.ts b/apps/mcp-server/src/agent/agent.types.ts index 58c943f..c097bb1 100644 --- a/apps/mcp-server/src/agent/agent.types.ts +++ b/apps/mcp-server/src/agent/agent.types.ts @@ -73,6 +73,24 @@ export interface DispatchedAgent { dispatchParams: DispatchParams; } +/** + * A single TaskMaestro pane assignment with agent name and prompt + */ +export interface TaskmaestroAssignment { + name: string; + displayName: string; + prompt: string; +} + +/** + * TaskMaestro dispatch configuration for parallel tmux pane execution + */ +export interface TaskmaestroDispatch { + sessionName: string; + paneCount: number; + assignments: TaskmaestroAssignment[]; +} + /** * Result of dispatching agents for execution */ @@ -82,6 +100,10 @@ export interface DispatchResult { executionHint: string; /** Agents that failed to load */ failedAgents?: FailedAgent[]; + /** TaskMaestro dispatch data when executionStrategy is 'taskmaestro' */ + taskmaestro?: TaskmaestroDispatch; + /** Execution strategy used for this dispatch */ + executionStrategy?: string; } /** @@ -94,6 +116,8 @@ export interface DispatchAgentsInput { specialists?: string[]; includeParallel?: boolean; primaryAgent?: string; + /** Execution strategy: 'subagent' (default) or 'taskmaestro' */ + executionStrategy?: 'subagent' | 'taskmaestro'; } /** diff --git a/apps/mcp-server/src/keyword/keyword.types.ts b/apps/mcp-server/src/keyword/keyword.types.ts index c2e9eda..fd9d718 100644 --- a/apps/mcp-server/src/keyword/keyword.types.ts +++ b/apps/mcp-server/src/keyword/keyword.types.ts @@ -440,6 +440,10 @@ export interface ParseModeResult { * without needing to call dispatch_agents or prepare_parallel_agents. */ dispatchReady?: DispatchReady; + /** @apiProperty External API - do not rename. Available execution strategies (e.g. ['subagent', 'taskmaestro']) */ + availableStrategies?: string[]; + /** @apiProperty External API - do not rename. Hint for installing TaskMaestro when not available */ + taskmaestroInstallHint?: string; } /** diff --git a/apps/mcp-server/src/tui/components/FlowMap.spec.tsx b/apps/mcp-server/src/tui/components/FlowMap.spec.tsx index 8cab01c..25f5301 100644 --- a/apps/mcp-server/src/tui/components/FlowMap.spec.tsx +++ b/apps/mcp-server/src/tui/components/FlowMap.spec.tsx @@ -114,7 +114,15 @@ describe('tui/components/FlowMap', () => { }), ); const { lastFrame } = render( - , + , ); const frame = lastFrame() ?? ''; expect(frame).toContain(pulseIcon(0)); @@ -136,7 +144,15 @@ describe('tui/components/FlowMap', () => { }), ); const { lastFrame } = render( - , + , ); const frame = lastFrame() ?? ''; expect(frame).toContain(pulseIcon(2)); @@ -157,7 +173,15 @@ describe('tui/components/FlowMap', () => { }), ); const { lastFrame } = render( - , + , ); const frame = lastFrame() ?? ''; // idle agent should NOT have pulse icons @@ -190,10 +214,26 @@ describe('tui/components/FlowMap', () => { }), ); const { lastFrame: frame0 } = render( - , + , ); const { lastFrame: frame1 } = render( - , + , ); expect(frame0()).toContain('●'); expect(frame1()).toContain('◉'); diff --git a/apps/mcp-server/src/tui/components/FocusedAgentPanel.spec.tsx b/apps/mcp-server/src/tui/components/FocusedAgentPanel.spec.tsx index 2ef5e69..520f385 100644 --- a/apps/mcp-server/src/tui/components/FocusedAgentPanel.spec.tsx +++ b/apps/mcp-server/src/tui/components/FocusedAgentPanel.spec.tsx @@ -253,9 +253,7 @@ describe('tui/components/FocusedAgentPanel', () => { agent={mockAgent} activeSkills={[]} objectives={[]} - eventLog={[ - { timestamp: '10:12:01', message: 'ACT start', level: 'info' }, - ]} + eventLog={[{ timestamp: '10:12:01', message: 'ACT start', level: 'info' }]} />, ); const frame = lastFrame() ?? ''; diff --git a/apps/mcp-server/src/tui/components/FocusedAgentPanel.tsx b/apps/mcp-server/src/tui/components/FocusedAgentPanel.tsx index 92ffc87..0300b6a 100644 --- a/apps/mcp-server/src/tui/components/FocusedAgentPanel.tsx +++ b/apps/mcp-server/src/tui/components/FocusedAgentPanel.tsx @@ -115,9 +115,7 @@ export function FocusedAgentPanel({ )} {statusLabel} - {showElapsed && ( - {formatElapsed(agent.startedAt!, now!)} - )} + {showElapsed && {formatElapsed(agent.startedAt!, now!)}} {agent.stage} diff --git a/apps/mcp-server/src/tui/components/HeaderBar.tsx b/apps/mcp-server/src/tui/components/HeaderBar.tsx index 2a01f28..cc76c3f 100644 --- a/apps/mcp-server/src/tui/components/HeaderBar.tsx +++ b/apps/mcp-server/src/tui/components/HeaderBar.tsx @@ -97,9 +97,7 @@ export function HeaderBar({ - {now !== undefined && ( - {formatTimeWithSeconds(now)} - )} + {now !== undefined && {formatTimeWithSeconds(now)}} ); } @@ -122,9 +120,7 @@ export function HeaderBar({ - {now !== undefined && ( - {formatTimeWithSeconds(now)} - )} + {now !== undefined && {formatTimeWithSeconds(now)}}