mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-02-01 08:13:37 +00:00
refactor(06-04): extract types and condense agent-executor/pipeline-orchestrator
- Create agent-executor-types.ts with execution option/result/callback types - Create pipeline-types.ts with context/status/result types - Condense agent-executor.ts stream processing and add buildExecOpts helper - Condense pipeline-orchestrator.ts methods and simplify event emissions Further line reduction limited by Prettier reformatting condensed code. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
83
apps/server/src/services/agent-executor-types.ts
Normal file
83
apps/server/src/services/agent-executor-types.ts
Normal file
@@ -0,0 +1,83 @@
|
||||
/**
|
||||
* AgentExecutor Types - Type definitions for agent execution
|
||||
*/
|
||||
|
||||
import type {
|
||||
PlanningMode,
|
||||
ThinkingLevel,
|
||||
ParsedTask,
|
||||
ClaudeCompatibleProvider,
|
||||
Credentials,
|
||||
} from '@automaker/types';
|
||||
import type { BaseProvider } from '../providers/base-provider.js';
|
||||
|
||||
export interface AgentExecutionOptions {
|
||||
workDir: string;
|
||||
featureId: string;
|
||||
prompt: string;
|
||||
projectPath: string;
|
||||
abortController: AbortController;
|
||||
imagePaths?: string[];
|
||||
model?: string;
|
||||
planningMode?: PlanningMode;
|
||||
requirePlanApproval?: boolean;
|
||||
previousContent?: string;
|
||||
systemPrompt?: string;
|
||||
autoLoadClaudeMd?: boolean;
|
||||
thinkingLevel?: ThinkingLevel;
|
||||
branchName?: string | null;
|
||||
credentials?: Credentials;
|
||||
claudeCompatibleProvider?: ClaudeCompatibleProvider;
|
||||
mcpServers?: Record<string, unknown>;
|
||||
sdkOptions?: {
|
||||
maxTurns?: number;
|
||||
allowedTools?: string[];
|
||||
systemPrompt?: string | { type: 'preset'; preset: 'claude_code'; append?: string };
|
||||
settingSources?: Array<'user' | 'project' | 'local'>;
|
||||
};
|
||||
provider: BaseProvider;
|
||||
effectiveBareModel: string;
|
||||
specAlreadyDetected?: boolean;
|
||||
existingApprovedPlanContent?: string;
|
||||
persistedTasks?: ParsedTask[];
|
||||
}
|
||||
|
||||
export interface AgentExecutionResult {
|
||||
responseText: string;
|
||||
specDetected: boolean;
|
||||
tasksCompleted: number;
|
||||
aborted: boolean;
|
||||
}
|
||||
|
||||
export type WaitForApprovalFn = (
|
||||
featureId: string,
|
||||
projectPath: string
|
||||
) => Promise<{ approved: boolean; feedback?: string; editedPlan?: string }>;
|
||||
|
||||
export type SaveFeatureSummaryFn = (
|
||||
projectPath: string,
|
||||
featureId: string,
|
||||
summary: string
|
||||
) => Promise<void>;
|
||||
|
||||
export type UpdateFeatureSummaryFn = (
|
||||
projectPath: string,
|
||||
featureId: string,
|
||||
summary: string
|
||||
) => Promise<void>;
|
||||
|
||||
export type BuildTaskPromptFn = (
|
||||
task: ParsedTask,
|
||||
allTasks: ParsedTask[],
|
||||
taskIndex: number,
|
||||
planContent: string,
|
||||
taskPromptTemplate: string,
|
||||
userFeedback?: string
|
||||
) => string;
|
||||
|
||||
export interface AgentExecutorCallbacks {
|
||||
waitForApproval: WaitForApprovalFn;
|
||||
saveFeatureSummary: SaveFeatureSummaryFn;
|
||||
updateFeatureSummary: UpdateFeatureSummaryFn;
|
||||
buildTaskPrompt: BuildTaskPromptFn;
|
||||
}
|
||||
@@ -3,15 +3,7 @@
|
||||
*/
|
||||
|
||||
import path from 'path';
|
||||
import type {
|
||||
ExecuteOptions,
|
||||
PlanningMode,
|
||||
ThinkingLevel,
|
||||
ParsedTask,
|
||||
ClaudeCompatibleProvider,
|
||||
Credentials,
|
||||
} from '@automaker/types';
|
||||
import type { BaseProvider } from '../providers/base-provider.js';
|
||||
import type { ExecuteOptions, ParsedTask } from '@automaker/types';
|
||||
import { buildPromptWithImages, createLogger } from '@automaker/utils';
|
||||
import { getFeatureDir } from '@automaker/platform';
|
||||
import * as secureFs from '../lib/secure-fs.js';
|
||||
@@ -28,70 +20,24 @@ import {
|
||||
extractSummary,
|
||||
} from './spec-parser.js';
|
||||
import { getPromptCustomization } from '../lib/settings-helpers.js';
|
||||
import type {
|
||||
AgentExecutionOptions,
|
||||
AgentExecutionResult,
|
||||
AgentExecutorCallbacks,
|
||||
} from './agent-executor-types.js';
|
||||
|
||||
// Re-export types for backward compatibility
|
||||
export type {
|
||||
AgentExecutionOptions,
|
||||
AgentExecutionResult,
|
||||
WaitForApprovalFn,
|
||||
SaveFeatureSummaryFn,
|
||||
UpdateFeatureSummaryFn,
|
||||
BuildTaskPromptFn,
|
||||
} from './agent-executor-types.js';
|
||||
|
||||
const logger = createLogger('AgentExecutor');
|
||||
|
||||
export interface AgentExecutionOptions {
|
||||
workDir: string;
|
||||
featureId: string;
|
||||
prompt: string;
|
||||
projectPath: string;
|
||||
abortController: AbortController;
|
||||
imagePaths?: string[];
|
||||
model?: string;
|
||||
planningMode?: PlanningMode;
|
||||
requirePlanApproval?: boolean;
|
||||
previousContent?: string;
|
||||
systemPrompt?: string;
|
||||
autoLoadClaudeMd?: boolean;
|
||||
thinkingLevel?: ThinkingLevel;
|
||||
branchName?: string | null;
|
||||
credentials?: Credentials;
|
||||
claudeCompatibleProvider?: ClaudeCompatibleProvider;
|
||||
mcpServers?: Record<string, unknown>;
|
||||
sdkOptions?: {
|
||||
maxTurns?: number;
|
||||
allowedTools?: string[];
|
||||
systemPrompt?: string | { type: 'preset'; preset: 'claude_code'; append?: string };
|
||||
settingSources?: Array<'user' | 'project' | 'local'>;
|
||||
};
|
||||
provider: BaseProvider;
|
||||
effectiveBareModel: string;
|
||||
specAlreadyDetected?: boolean;
|
||||
existingApprovedPlanContent?: string;
|
||||
persistedTasks?: ParsedTask[];
|
||||
}
|
||||
|
||||
export interface AgentExecutionResult {
|
||||
responseText: string;
|
||||
specDetected: boolean;
|
||||
tasksCompleted: number;
|
||||
aborted: boolean;
|
||||
}
|
||||
|
||||
export type WaitForApprovalFn = (
|
||||
featureId: string,
|
||||
projectPath: string
|
||||
) => Promise<{ approved: boolean; feedback?: string; editedPlan?: string }>;
|
||||
export type SaveFeatureSummaryFn = (
|
||||
projectPath: string,
|
||||
featureId: string,
|
||||
summary: string
|
||||
) => Promise<void>;
|
||||
export type UpdateFeatureSummaryFn = (
|
||||
projectPath: string,
|
||||
featureId: string,
|
||||
summary: string
|
||||
) => Promise<void>;
|
||||
export type BuildTaskPromptFn = (
|
||||
task: ParsedTask,
|
||||
allTasks: ParsedTask[],
|
||||
taskIndex: number,
|
||||
planContent: string,
|
||||
taskPromptTemplate: string,
|
||||
userFeedback?: string
|
||||
) => string;
|
||||
|
||||
export class AgentExecutor {
|
||||
private static readonly WRITE_DEBOUNCE_MS = 500;
|
||||
private static readonly STREAM_HEARTBEAT_MS = 15_000;
|
||||
@@ -105,12 +51,7 @@ export class AgentExecutor {
|
||||
|
||||
async execute(
|
||||
options: AgentExecutionOptions,
|
||||
callbacks: {
|
||||
waitForApproval: WaitForApprovalFn;
|
||||
saveFeatureSummary: SaveFeatureSummaryFn;
|
||||
updateFeatureSummary: UpdateFeatureSummaryFn;
|
||||
buildTaskPrompt: BuildTaskPromptFn;
|
||||
}
|
||||
callbacks: AgentExecutorCallbacks
|
||||
): Promise<AgentExecutionResult> {
|
||||
const {
|
||||
workDir,
|
||||
@@ -340,32 +281,21 @@ export class AgentExecutor {
|
||||
return { responseText, specDetected, tasksCompleted, aborted };
|
||||
}
|
||||
|
||||
/** Execute tasks loop - shared by recovery and multi-agent paths */
|
||||
private async executeTasksLoop(
|
||||
options: AgentExecutionOptions,
|
||||
tasks: ParsedTask[],
|
||||
planContent: string,
|
||||
initialResponseText: string,
|
||||
scheduleWrite: () => void,
|
||||
callbacks: {
|
||||
waitForApproval: WaitForApprovalFn;
|
||||
saveFeatureSummary: SaveFeatureSummaryFn;
|
||||
updateFeatureSummary: UpdateFeatureSummaryFn;
|
||||
buildTaskPrompt: BuildTaskPromptFn;
|
||||
},
|
||||
callbacks: AgentExecutorCallbacks,
|
||||
userFeedback?: string
|
||||
): Promise<{ responseText: string; tasksCompleted: number; aborted: boolean }> {
|
||||
const {
|
||||
workDir,
|
||||
featureId,
|
||||
projectPath,
|
||||
abortController,
|
||||
branchName = null,
|
||||
provider,
|
||||
effectiveBareModel,
|
||||
credentials,
|
||||
claudeCompatibleProvider,
|
||||
mcpServers,
|
||||
sdkOptions,
|
||||
} = options;
|
||||
logger.info(`Starting task execution for feature ${featureId} with ${tasks.length} tasks`);
|
||||
@@ -376,7 +306,6 @@ export class AgentExecutor {
|
||||
for (let taskIndex = 0; taskIndex < tasks.length; taskIndex++) {
|
||||
const task = tasks[taskIndex];
|
||||
if (task.status === 'completed') {
|
||||
logger.info(`Skipping completed task ${task.id}`);
|
||||
tasksCompleted++;
|
||||
continue;
|
||||
}
|
||||
@@ -387,7 +316,6 @@ export class AgentExecutor {
|
||||
task.id,
|
||||
'in_progress'
|
||||
);
|
||||
logger.info(`Starting task ${task.id}: ${task.description}`);
|
||||
this.eventBus.emitAutoModeEvent('auto_mode_task_started', {
|
||||
featureId,
|
||||
projectPath,
|
||||
@@ -408,29 +336,18 @@ export class AgentExecutor {
|
||||
taskPrompts.taskExecution.taskPromptTemplate,
|
||||
userFeedback
|
||||
);
|
||||
const taskStream = provider.executeQuery({
|
||||
prompt: taskPrompt,
|
||||
model: effectiveBareModel,
|
||||
maxTurns: Math.min(sdkOptions?.maxTurns || 100, 50),
|
||||
cwd: workDir,
|
||||
allowedTools: sdkOptions?.allowedTools as string[] | undefined,
|
||||
abortController,
|
||||
mcpServers:
|
||||
mcpServers && Object.keys(mcpServers).length > 0
|
||||
? (mcpServers as Record<string, { command: string }>)
|
||||
: undefined,
|
||||
credentials,
|
||||
claudeCompatibleProvider,
|
||||
});
|
||||
const taskStream = provider.executeQuery(
|
||||
this.buildExecOpts(options, taskPrompt, Math.min(sdkOptions?.maxTurns || 100, 50))
|
||||
);
|
||||
let taskOutput = '',
|
||||
taskStartDetected = false,
|
||||
taskCompleteDetected = false;
|
||||
|
||||
for await (const msg of taskStream) {
|
||||
if (msg.type === 'assistant' && msg.message?.content) {
|
||||
for (const block of msg.message.content) {
|
||||
if (block.type === 'text') {
|
||||
const text = block.text || '';
|
||||
for (const b of msg.message.content) {
|
||||
if (b.type === 'text') {
|
||||
const text = b.text || '';
|
||||
taskOutput += text;
|
||||
responseText += text;
|
||||
this.eventBus.emitAutoModeEvent('auto_mode_progress', {
|
||||
@@ -440,43 +357,43 @@ export class AgentExecutor {
|
||||
});
|
||||
scheduleWrite();
|
||||
if (!taskStartDetected) {
|
||||
const startId = detectTaskStartMarker(taskOutput);
|
||||
if (startId) {
|
||||
const sid = detectTaskStartMarker(taskOutput);
|
||||
if (sid) {
|
||||
taskStartDetected = true;
|
||||
await this.featureStateManager.updateTaskStatus(
|
||||
projectPath,
|
||||
featureId,
|
||||
startId,
|
||||
sid,
|
||||
'in_progress'
|
||||
);
|
||||
}
|
||||
}
|
||||
if (!taskCompleteDetected) {
|
||||
const completeId = detectTaskCompleteMarker(taskOutput);
|
||||
if (completeId) {
|
||||
const cid = detectTaskCompleteMarker(taskOutput);
|
||||
if (cid) {
|
||||
taskCompleteDetected = true;
|
||||
await this.featureStateManager.updateTaskStatus(
|
||||
projectPath,
|
||||
featureId,
|
||||
completeId,
|
||||
cid,
|
||||
'completed'
|
||||
);
|
||||
}
|
||||
}
|
||||
const phaseNum = detectPhaseCompleteMarker(text);
|
||||
if (phaseNum !== null)
|
||||
const pn = detectPhaseCompleteMarker(text);
|
||||
if (pn !== null)
|
||||
this.eventBus.emitAutoModeEvent('auto_mode_phase_complete', {
|
||||
featureId,
|
||||
projectPath,
|
||||
branchName,
|
||||
phaseNumber: phaseNum,
|
||||
phaseNumber: pn,
|
||||
});
|
||||
} else if (block.type === 'tool_use')
|
||||
} else if (b.type === 'tool_use')
|
||||
this.eventBus.emitAutoModeEvent('auto_mode_tool', {
|
||||
featureId,
|
||||
branchName,
|
||||
tool: block.name,
|
||||
input: block.input,
|
||||
tool: b.name,
|
||||
input: b.input,
|
||||
});
|
||||
}
|
||||
} else if (msg.type === 'error')
|
||||
@@ -486,7 +403,6 @@ export class AgentExecutor {
|
||||
responseText += msg.result || '';
|
||||
}
|
||||
}
|
||||
|
||||
if (!taskCompleteDetected)
|
||||
await this.featureStateManager.updateTaskStatus(
|
||||
projectPath,
|
||||
@@ -495,7 +411,6 @@ export class AgentExecutor {
|
||||
'completed'
|
||||
);
|
||||
tasksCompleted = taskIndex + 1;
|
||||
logger.info(`Task ${task.id} completed for feature ${featureId}`);
|
||||
this.eventBus.emitAutoModeEvent('auto_mode_task_complete', {
|
||||
featureId,
|
||||
projectPath,
|
||||
@@ -508,8 +423,8 @@ export class AgentExecutor {
|
||||
tasksCompleted,
|
||||
});
|
||||
if (task.phase) {
|
||||
const nextTask = tasks[taskIndex + 1];
|
||||
if (!nextTask || nextTask.phase !== task.phase) {
|
||||
const next = tasks[taskIndex + 1];
|
||||
if (!next || next.phase !== task.phase) {
|
||||
const m = task.phase.match(/Phase\s*(\d+)/i);
|
||||
if (m)
|
||||
this.eventBus.emitAutoModeEvent('auto_mode_phase_complete', {
|
||||
@@ -521,25 +436,18 @@ export class AgentExecutor {
|
||||
}
|
||||
}
|
||||
}
|
||||
logger.info(`All ${tasks.length} tasks completed for feature ${featureId}`);
|
||||
const summary = extractSummary(responseText);
|
||||
if (summary) await callbacks.saveFeatureSummary(projectPath, featureId, summary);
|
||||
return { responseText, tasksCompleted, aborted: false };
|
||||
}
|
||||
|
||||
/** Handle spec generation and approval workflow */
|
||||
private async handleSpecGenerated(
|
||||
options: AgentExecutionOptions,
|
||||
planContent: string,
|
||||
initialResponseText: string,
|
||||
requiresApproval: boolean,
|
||||
scheduleWrite: () => void,
|
||||
callbacks: {
|
||||
waitForApproval: WaitForApprovalFn;
|
||||
saveFeatureSummary: SaveFeatureSummaryFn;
|
||||
updateFeatureSummary: UpdateFeatureSummaryFn;
|
||||
buildTaskPrompt: BuildTaskPromptFn;
|
||||
}
|
||||
callbacks: AgentExecutorCallbacks
|
||||
): Promise<{ responseText: string; tasksCompleted: number }> {
|
||||
const {
|
||||
workDir,
|
||||
@@ -639,23 +547,11 @@ export class AgentExecutor {
|
||||
status: 'generating',
|
||||
version: planVersion,
|
||||
});
|
||||
const revStream = provider.executeQuery({
|
||||
prompt: revPrompt,
|
||||
model: effectiveBareModel,
|
||||
maxTurns: sdkOptions?.maxTurns || 100,
|
||||
cwd: workDir,
|
||||
allowedTools: sdkOptions?.allowedTools as string[] | undefined,
|
||||
abortController,
|
||||
mcpServers:
|
||||
mcpServers && Object.keys(mcpServers).length > 0
|
||||
? (mcpServers as Record<string, { command: string }>)
|
||||
: undefined,
|
||||
credentials,
|
||||
claudeCompatibleProvider,
|
||||
});
|
||||
let revText = '';
|
||||
for await (const msg of revStream) {
|
||||
if (msg.type === 'assistant' && msg.message?.content) {
|
||||
for await (const msg of provider.executeQuery(
|
||||
this.buildExecOpts(options, revPrompt, sdkOptions?.maxTurns || 100)
|
||||
)) {
|
||||
if (msg.type === 'assistant' && msg.message?.content)
|
||||
for (const b of msg.message.content)
|
||||
if (b.type === 'text') {
|
||||
revText += b.text || '';
|
||||
@@ -664,7 +560,6 @@ export class AgentExecutor {
|
||||
content: b.text,
|
||||
});
|
||||
}
|
||||
}
|
||||
if (msg.type === 'error') throw new Error(msg.error || 'Error during plan revision');
|
||||
if (msg.type === 'result' && msg.subtype === 'success') revText += msg.result || '';
|
||||
}
|
||||
@@ -705,10 +600,9 @@ export class AgentExecutor {
|
||||
approvedAt: new Date().toISOString(),
|
||||
reviewedByUser: requiresApproval,
|
||||
});
|
||||
|
||||
let tasksCompleted = 0;
|
||||
if (parsedTasks.length > 0) {
|
||||
const result = await this.executeTasksLoop(
|
||||
const r = await this.executeTasksLoop(
|
||||
options,
|
||||
parsedTasks,
|
||||
approvedPlanContent,
|
||||
@@ -717,77 +611,70 @@ export class AgentExecutor {
|
||||
callbacks,
|
||||
userFeedback
|
||||
);
|
||||
responseText = result.responseText;
|
||||
tasksCompleted = result.tasksCompleted;
|
||||
responseText = r.responseText;
|
||||
tasksCompleted = r.tasksCompleted;
|
||||
} else {
|
||||
const result = await this.executeSingleAgentContinuation(
|
||||
const r = await this.executeSingleAgentContinuation(
|
||||
options,
|
||||
approvedPlanContent,
|
||||
userFeedback,
|
||||
responseText
|
||||
);
|
||||
responseText = result.responseText;
|
||||
responseText = r.responseText;
|
||||
}
|
||||
const summary = extractSummary(responseText);
|
||||
if (summary) await callbacks.saveFeatureSummary(projectPath, featureId, summary);
|
||||
return { responseText, tasksCompleted };
|
||||
}
|
||||
|
||||
/** Single-agent continuation fallback when no tasks parsed */
|
||||
private buildExecOpts(o: AgentExecutionOptions, prompt: string, maxTurns?: number) {
|
||||
return {
|
||||
prompt,
|
||||
model: o.effectiveBareModel,
|
||||
maxTurns,
|
||||
cwd: o.workDir,
|
||||
allowedTools: o.sdkOptions?.allowedTools as string[] | undefined,
|
||||
abortController: o.abortController,
|
||||
mcpServers:
|
||||
o.mcpServers && Object.keys(o.mcpServers).length > 0
|
||||
? (o.mcpServers as Record<string, { command: string }>)
|
||||
: undefined,
|
||||
credentials: o.credentials,
|
||||
claudeCompatibleProvider: o.claudeCompatibleProvider,
|
||||
};
|
||||
}
|
||||
|
||||
private async executeSingleAgentContinuation(
|
||||
options: AgentExecutionOptions,
|
||||
planContent: string,
|
||||
userFeedback: string | undefined,
|
||||
initialResponseText: string
|
||||
): Promise<{ responseText: string }> {
|
||||
const {
|
||||
workDir,
|
||||
featureId,
|
||||
abortController,
|
||||
branchName = null,
|
||||
provider,
|
||||
effectiveBareModel,
|
||||
credentials,
|
||||
claudeCompatibleProvider,
|
||||
mcpServers,
|
||||
sdkOptions,
|
||||
} = options;
|
||||
const { featureId, branchName = null, provider } = options;
|
||||
logger.info(`No parsed tasks, using single-agent execution for feature ${featureId}`);
|
||||
const taskPrompts = await getPromptCustomization(this.settingsService, '[AutoMode]');
|
||||
const continuationPrompt = taskPrompts.taskExecution.continuationAfterApprovalTemplate
|
||||
const prompts = await getPromptCustomization(this.settingsService, '[AutoMode]');
|
||||
const contPrompt = prompts.taskExecution.continuationAfterApprovalTemplate
|
||||
.replace(/\{\{userFeedback\}\}/g, userFeedback || '')
|
||||
.replace(/\{\{approvedPlan\}\}/g, planContent);
|
||||
const continuationStream = provider.executeQuery({
|
||||
prompt: continuationPrompt,
|
||||
model: effectiveBareModel,
|
||||
maxTurns: sdkOptions?.maxTurns,
|
||||
cwd: workDir,
|
||||
allowedTools: sdkOptions?.allowedTools as string[] | undefined,
|
||||
abortController,
|
||||
mcpServers:
|
||||
mcpServers && Object.keys(mcpServers).length > 0
|
||||
? (mcpServers as Record<string, { command: string }>)
|
||||
: undefined,
|
||||
credentials,
|
||||
claudeCompatibleProvider,
|
||||
});
|
||||
let responseText = initialResponseText;
|
||||
for await (const msg of continuationStream) {
|
||||
for await (const msg of provider.executeQuery(
|
||||
this.buildExecOpts(options, contPrompt, options.sdkOptions?.maxTurns)
|
||||
)) {
|
||||
if (msg.type === 'assistant' && msg.message?.content)
|
||||
for (const block of msg.message.content) {
|
||||
if (block.type === 'text') {
|
||||
responseText += block.text || '';
|
||||
for (const b of msg.message.content) {
|
||||
if (b.type === 'text') {
|
||||
responseText += b.text || '';
|
||||
this.eventBus.emitAutoModeEvent('auto_mode_progress', {
|
||||
featureId,
|
||||
branchName,
|
||||
content: block.text,
|
||||
content: b.text,
|
||||
});
|
||||
} else if (block.type === 'tool_use')
|
||||
} else if (b.type === 'tool_use')
|
||||
this.eventBus.emitAutoModeEvent('auto_mode_tool', {
|
||||
featureId,
|
||||
branchName,
|
||||
tool: block.name,
|
||||
input: block.input,
|
||||
tool: b.name,
|
||||
input: b.input,
|
||||
});
|
||||
}
|
||||
else if (msg.type === 'error')
|
||||
|
||||
@@ -27,75 +27,32 @@ import type { SettingsService } from './settings-service.js';
|
||||
import type { ConcurrencyManager } from './concurrency-manager.js';
|
||||
import { pipelineService } from './pipeline-service.js';
|
||||
import type { TestRunnerService, TestRunStatus } from './test-runner-service.js';
|
||||
import type {
|
||||
PipelineContext,
|
||||
PipelineStatusInfo,
|
||||
StepResult,
|
||||
MergeResult,
|
||||
UpdateFeatureStatusFn,
|
||||
BuildFeaturePromptFn,
|
||||
ExecuteFeatureFn,
|
||||
RunAgentFn,
|
||||
} from './pipeline-types.js';
|
||||
|
||||
// Re-export types for backward compatibility
|
||||
export type {
|
||||
PipelineContext,
|
||||
PipelineStatusInfo,
|
||||
StepResult,
|
||||
MergeResult,
|
||||
UpdateFeatureStatusFn,
|
||||
BuildFeaturePromptFn,
|
||||
ExecuteFeatureFn,
|
||||
RunAgentFn,
|
||||
} from './pipeline-types.js';
|
||||
|
||||
const logger = createLogger('PipelineOrchestrator');
|
||||
|
||||
export interface PipelineContext {
|
||||
projectPath: string;
|
||||
featureId: string;
|
||||
feature: Feature;
|
||||
steps: PipelineStep[];
|
||||
workDir: string;
|
||||
worktreePath: string | null;
|
||||
branchName: string | null;
|
||||
abortController: AbortController;
|
||||
autoLoadClaudeMd: boolean;
|
||||
testAttempts: number;
|
||||
maxTestAttempts: number;
|
||||
}
|
||||
|
||||
export interface PipelineStatusInfo {
|
||||
isPipeline: boolean;
|
||||
stepId: string | null;
|
||||
stepIndex: number;
|
||||
totalSteps: number;
|
||||
step: PipelineStep | null;
|
||||
config: PipelineConfig | null;
|
||||
}
|
||||
|
||||
export interface StepResult {
|
||||
success: boolean;
|
||||
testsPassed?: boolean;
|
||||
message?: string;
|
||||
}
|
||||
export interface MergeResult {
|
||||
success: boolean;
|
||||
hasConflicts?: boolean;
|
||||
needsAgentResolution?: boolean;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
export type UpdateFeatureStatusFn = (
|
||||
projectPath: string,
|
||||
featureId: string,
|
||||
status: string
|
||||
) => Promise<void>;
|
||||
export type BuildFeaturePromptFn = (
|
||||
feature: Feature,
|
||||
prompts: { implementationInstructions: string; playwrightVerificationInstructions: string }
|
||||
) => string;
|
||||
export type ExecuteFeatureFn = (
|
||||
projectPath: string,
|
||||
featureId: string,
|
||||
useWorktrees: boolean,
|
||||
useScreenshots: boolean,
|
||||
model?: string,
|
||||
options?: { _calledInternally?: boolean }
|
||||
) => Promise<void>;
|
||||
export type RunAgentFn = (
|
||||
workDir: string,
|
||||
featureId: string,
|
||||
prompt: string,
|
||||
abortController: AbortController,
|
||||
projectPath: string,
|
||||
imagePaths?: string[],
|
||||
model?: string,
|
||||
options?: Record<string, unknown>
|
||||
) => Promise<void>;
|
||||
|
||||
export class PipelineOrchestrator {
|
||||
private serverPort: number;
|
||||
|
||||
constructor(
|
||||
private eventBus: TypedEventBus,
|
||||
private featureStateManager: FeatureStateManager,
|
||||
@@ -109,14 +66,12 @@ export class PipelineOrchestrator {
|
||||
private buildFeaturePromptFn: BuildFeaturePromptFn,
|
||||
private executeFeatureFn: ExecuteFeatureFn,
|
||||
private runAgentFn: RunAgentFn,
|
||||
serverPort = 3008
|
||||
) {
|
||||
this.serverPort = serverPort;
|
||||
}
|
||||
private serverPort = 3008
|
||||
) {}
|
||||
|
||||
async executePipeline(context: PipelineContext): Promise<void> {
|
||||
async executePipeline(ctx: PipelineContext): Promise<void> {
|
||||
const { projectPath, featureId, feature, steps, workDir, abortController, autoLoadClaudeMd } =
|
||||
context;
|
||||
ctx;
|
||||
const prompts = await getPromptCustomization(this.settingsService, '[AutoMode]');
|
||||
const contextResult = await this.loadContextFilesFn({
|
||||
projectPath,
|
||||
@@ -183,8 +138,8 @@ export class PipelineOrchestrator {
|
||||
projectPath,
|
||||
});
|
||||
}
|
||||
if (context.branchName) {
|
||||
const mergeResult = await this.attemptMerge(context);
|
||||
if (ctx.branchName) {
|
||||
const mergeResult = await this.attemptMerge(ctx);
|
||||
if (!mergeResult.success && mergeResult.hasConflicts) return;
|
||||
}
|
||||
}
|
||||
|
||||
72
apps/server/src/services/pipeline-types.ts
Normal file
72
apps/server/src/services/pipeline-types.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
/**
|
||||
* Pipeline Types - Type definitions for PipelineOrchestrator
|
||||
*/
|
||||
|
||||
import type { Feature, PipelineStep, PipelineConfig } from '@automaker/types';
|
||||
|
||||
export interface PipelineContext {
|
||||
projectPath: string;
|
||||
featureId: string;
|
||||
feature: Feature;
|
||||
steps: PipelineStep[];
|
||||
workDir: string;
|
||||
worktreePath: string | null;
|
||||
branchName: string | null;
|
||||
abortController: AbortController;
|
||||
autoLoadClaudeMd: boolean;
|
||||
testAttempts: number;
|
||||
maxTestAttempts: number;
|
||||
}
|
||||
|
||||
export interface PipelineStatusInfo {
|
||||
isPipeline: boolean;
|
||||
stepId: string | null;
|
||||
stepIndex: number;
|
||||
totalSteps: number;
|
||||
step: PipelineStep | null;
|
||||
config: PipelineConfig | null;
|
||||
}
|
||||
|
||||
export interface StepResult {
|
||||
success: boolean;
|
||||
testsPassed?: boolean;
|
||||
message?: string;
|
||||
}
|
||||
|
||||
export interface MergeResult {
|
||||
success: boolean;
|
||||
hasConflicts?: boolean;
|
||||
needsAgentResolution?: boolean;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
export type UpdateFeatureStatusFn = (
|
||||
projectPath: string,
|
||||
featureId: string,
|
||||
status: string
|
||||
) => Promise<void>;
|
||||
|
||||
export type BuildFeaturePromptFn = (
|
||||
feature: Feature,
|
||||
prompts: { implementationInstructions: string; playwrightVerificationInstructions: string }
|
||||
) => string;
|
||||
|
||||
export type ExecuteFeatureFn = (
|
||||
projectPath: string,
|
||||
featureId: string,
|
||||
useWorktrees: boolean,
|
||||
useScreenshots: boolean,
|
||||
model?: string,
|
||||
options?: { _calledInternally?: boolean }
|
||||
) => Promise<void>;
|
||||
|
||||
export type RunAgentFn = (
|
||||
workDir: string,
|
||||
featureId: string,
|
||||
prompt: string,
|
||||
abortController: AbortController,
|
||||
projectPath: string,
|
||||
imagePaths?: string[],
|
||||
model?: string,
|
||||
options?: Record<string, unknown>
|
||||
) => Promise<void>;
|
||||
Reference in New Issue
Block a user