From d90978d16beb7279dc46419b5b0e1e8cb185582d Mon Sep 17 00:00:00 2001 From: JeremyDev87 Date: Thu, 19 Mar 2026 17:20:41 +0900 Subject: [PATCH 1/3] feat(mcp): add TaskMaestro dispatch types and availableStrategies - Add TaskmaestroAssignment and TaskmaestroDispatch interfaces - Extend DispatchAgentsInput with executionStrategy field - Extend DispatchResult with taskmaestro and executionStrategy fields - Extend ParseModeResult with availableStrategies and taskmaestroInstallHint - Add type tests for new interfaces Closes #706 --- apps/mcp-server/src/agent/agent.types.spec.ts | 84 +++++++++++++++++++ apps/mcp-server/src/agent/agent.types.ts | 24 ++++++ apps/mcp-server/src/keyword/keyword.types.ts | 4 + 3 files changed, 112 insertions(+) create mode 100644 apps/mcp-server/src/agent/agent.types.spec.ts 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 00000000..0341d3b9 --- /dev/null +++ b/apps/mcp-server/src/agent/agent.types.spec.ts @@ -0,0 +1,84 @@ +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 58c943ff..c097bb1c 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 c2e9eda8..fd9d7184 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; } /** From 0b83122537cd2123eb3777c1e006ec018266baa7 Mon Sep 17 00:00:00 2001 From: JeremyDev87 Date: Fri, 20 Mar 2026 17:00:34 +0900 Subject: [PATCH 2/3] style(mcp): fix prettier formatting in agent.types.spec.ts --- apps/mcp-server/src/agent/agent.types.spec.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/apps/mcp-server/src/agent/agent.types.spec.ts b/apps/mcp-server/src/agent/agent.types.spec.ts index 0341d3b9..72a6bcd0 100644 --- a/apps/mcp-server/src/agent/agent.types.spec.ts +++ b/apps/mcp-server/src/agent/agent.types.spec.ts @@ -25,9 +25,7 @@ describe('agent.types - TaskMaestro types', () => { const dispatch: TaskmaestroDispatch = { sessionName: 'workspace-1', paneCount: 3, - assignments: [ - { name: 'dev-1', displayName: 'Dev 1', prompt: 'Task 1' }, - ], + assignments: [{ name: 'dev-1', displayName: 'Dev 1', prompt: 'Task 1' }], }; expect(dispatch.sessionName).toBe('workspace-1'); expect(dispatch.paneCount).toBe(3); From 472eb1bacb7ae33139832882b0d278edcdc1e02c Mon Sep 17 00:00:00 2001 From: JeremyDev87 Date: Fri, 20 Mar 2026 17:13:39 +0900 Subject: [PATCH 3/3] fix: prettier formatting --- .../src/tui/components/FlowMap.spec.tsx | 50 +++++++++++++++++-- .../tui/components/FocusedAgentPanel.spec.tsx | 4 +- .../src/tui/components/FocusedAgentPanel.tsx | 4 +- .../src/tui/components/HeaderBar.tsx | 8 +-- 4 files changed, 49 insertions(+), 17 deletions(-) diff --git a/apps/mcp-server/src/tui/components/FlowMap.spec.tsx b/apps/mcp-server/src/tui/components/FlowMap.spec.tsx index 8cab01c2..25f5301d 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 2ef5e694..520f3850 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 92ffc873..0300b6ae 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 2a01f288..cc76c3f5 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)}}