From 6bb7b8648785340c2934d3cc3545b0f09cc4ef2d Mon Sep 17 00:00:00 2001 From: Shirone Date: Sat, 31 Jan 2026 13:13:47 +0100 Subject: [PATCH] fix(facade): wire runAgentFn to AgentExecutor.execute The facade had stubs for runAgentFn that threw errors, causing feature execution to fail with "runAgentFn not implemented in facade". This fix wires both ExecutionService and PipelineOrchestrator runAgentFn callbacks to properly call AgentExecutor.execute() with: - Provider from ProviderFactory.getProviderForModel() - Bare model from stripProviderPrefix() - Proper AgentExecutorCallbacks for waitForApproval, saveFeatureSummary, etc. Co-Authored-By: Claude Opus 4.5 --- apps/server/src/services/auto-mode/facade.ts | 125 +++++++++++++++++-- 1 file changed, 116 insertions(+), 9 deletions(-) diff --git a/apps/server/src/services/auto-mode/facade.ts b/apps/server/src/services/auto-mode/facade.ts index b29fc15f..1059731f 100644 --- a/apps/server/src/services/auto-mode/facade.ts +++ b/apps/server/src/services/auto-mode/facade.ts @@ -14,8 +14,8 @@ import path from 'path'; import { exec } from 'child_process'; import { promisify } from 'util'; -import type { Feature } from '@automaker/types'; -import { DEFAULT_MAX_CONCURRENCY } from '@automaker/types'; +import type { Feature, PlanningMode, ThinkingLevel } from '@automaker/types'; +import { DEFAULT_MAX_CONCURRENCY, stripProviderPrefix } from '@automaker/types'; import { createLogger, loadContextFiles, classifyError } from '@automaker/utils'; import { getFeatureDir } from '@automaker/platform'; import * as secureFs from '../../lib/secure-fs.js'; @@ -32,6 +32,7 @@ import { RecoveryService } from '../recovery-service.js'; import { PipelineOrchestrator } from '../pipeline-orchestrator.js'; import { AgentExecutor } from '../agent-executor.js'; import { TestRunnerService } from '../test-runner-service.js'; +import { ProviderFactory } from '../../providers/provider-factory.js'; import { FeatureLoader } from '../feature-loader.js'; import type { SettingsService } from '../settings-service.js'; import type { EventEmitter } from '../../lib/events.js'; @@ -153,9 +154,58 @@ export class AutoModeServiceFacade { buildFeaturePrompt, (pPath, featureId, useWorktrees, _isAutoMode, _model, opts) => facadeInstance!.executeFeature(featureId, useWorktrees, false, undefined, opts), - // runAgentFn stub - facade does not implement runAgent directly - async () => { - throw new Error('runAgentFn not implemented in facade'); + // runAgentFn - delegates to AgentExecutor + async ( + workDir: string, + featureId: string, + prompt: string, + abortController: AbortController, + pPath: string, + imagePaths?: string[], + model?: string, + opts?: Record + ) => { + const resolvedModel = model || 'claude-sonnet-4-20250514'; + const provider = ProviderFactory.getProviderForModel(resolvedModel); + const effectiveBareModel = stripProviderPrefix(resolvedModel); + + await agentExecutor.execute( + { + workDir, + featureId, + prompt, + projectPath: pPath, + abortController, + imagePaths, + model: resolvedModel, + planningMode: opts?.planningMode as PlanningMode | undefined, + requirePlanApproval: opts?.requirePlanApproval as boolean | undefined, + systemPrompt: opts?.systemPrompt as string | undefined, + autoLoadClaudeMd: opts?.autoLoadClaudeMd as boolean | undefined, + thinkingLevel: opts?.thinkingLevel as ThinkingLevel | undefined, + branchName: opts?.branchName as string | null | undefined, + provider, + effectiveBareModel, + }, + { + waitForApproval: (fId, projPath) => planApprovalService.waitForApproval(fId, projPath), + saveFeatureSummary: (projPath, fId, summary) => + featureStateManager.saveFeatureSummary(projPath, fId, summary), + updateFeatureSummary: (projPath, fId, summary) => + featureStateManager.saveFeatureSummary(projPath, fId, summary), + buildTaskPrompt: (task, allTasks, taskIndex, _planContent, template, feedback) => { + let taskPrompt = template + .replace(/\{\{taskName\}\}/g, task.description) + .replace(/\{\{taskIndex\}\}/g, String(taskIndex + 1)) + .replace(/\{\{totalTasks\}\}/g, String(allTasks.length)) + .replace(/\{\{taskDescription\}\}/g, task.description || task.description); + if (feedback) { + taskPrompt = taskPrompt.replace(/\{\{userFeedback\}\}/g, feedback); + } + return taskPrompt; + }, + } + ); } ); @@ -193,15 +243,72 @@ export class AutoModeServiceFacade { (featureId) => concurrencyManager.isRunning(featureId) ); - // ExecutionService - runAgentFn is a stub + // ExecutionService - runAgentFn calls AgentExecutor.execute const executionService = new ExecutionService( eventBus, concurrencyManager, worktreeResolver, settingsService, - // Callbacks - runAgentFn stub - async () => { - throw new Error('runAgentFn not implemented in facade'); + // runAgentFn - delegates to AgentExecutor + async ( + workDir: string, + featureId: string, + prompt: string, + abortController: AbortController, + pPath: string, + imagePaths?: string[], + model?: string, + opts?: { + projectPath?: string; + planningMode?: PlanningMode; + requirePlanApproval?: boolean; + systemPrompt?: string; + autoLoadClaudeMd?: boolean; + thinkingLevel?: ThinkingLevel; + branchName?: string | null; + } + ) => { + const resolvedModel = model || 'claude-sonnet-4-20250514'; + const provider = ProviderFactory.getProviderForModel(resolvedModel); + const effectiveBareModel = stripProviderPrefix(resolvedModel); + + await agentExecutor.execute( + { + workDir, + featureId, + prompt, + projectPath: pPath, + abortController, + imagePaths, + model: resolvedModel, + planningMode: opts?.planningMode, + requirePlanApproval: opts?.requirePlanApproval, + systemPrompt: opts?.systemPrompt, + autoLoadClaudeMd: opts?.autoLoadClaudeMd, + thinkingLevel: opts?.thinkingLevel, + branchName: opts?.branchName, + provider, + effectiveBareModel, + }, + { + waitForApproval: (fId, projPath) => planApprovalService.waitForApproval(fId, projPath), + saveFeatureSummary: (projPath, fId, summary) => + featureStateManager.saveFeatureSummary(projPath, fId, summary), + updateFeatureSummary: (projPath, fId, summary) => + featureStateManager.saveFeatureSummary(projPath, fId, summary), + buildTaskPrompt: (task, allTasks, taskIndex, planContent, template, feedback) => { + let taskPrompt = template + .replace(/\{\{taskName\}\}/g, task.description) + .replace(/\{\{taskIndex\}\}/g, String(taskIndex + 1)) + .replace(/\{\{totalTasks\}\}/g, String(allTasks.length)) + .replace(/\{\{taskDescription\}\}/g, task.description || task.description); + if (feedback) { + taskPrompt = taskPrompt.replace(/\{\{userFeedback\}\}/g, feedback); + } + return taskPrompt; + }, + } + ); }, (context) => pipelineOrchestrator.executePipeline(context), (pPath, featureId, status) =>