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)}}