refactor(01-03): wire TypedEventBus into AutoModeService

- Import TypedEventBus into AutoModeService
- Add eventBus property initialized via constructor injection
- Remove private emitAutoModeEvent method (now in TypedEventBus)
- Update all 66 emitAutoModeEvent calls to use this.eventBus
- Constructor accepts optional TypedEventBus for testing
This commit is contained in:
Shirone
2026-01-27 14:49:44 +01:00
parent 2a77407aaa
commit 93a6c32c32

View File

@@ -68,6 +68,7 @@ import {
type RunningFeature, type RunningFeature,
type GetCurrentBranchFn, type GetCurrentBranchFn,
} from './concurrency-manager.js'; } from './concurrency-manager.js';
import { TypedEventBus } from './typed-event-bus.js';
import type { SettingsService } from './settings-service.js'; import type { SettingsService } from './settings-service.js';
import { pipelineService, PipelineService } from './pipeline-service.js'; import { pipelineService, PipelineService } from './pipeline-service.js';
import { import {
@@ -421,6 +422,7 @@ const FAILURE_WINDOW_MS = 60000; // Failures within 1 minute count as consecutiv
export class AutoModeService { export class AutoModeService {
private events: EventEmitter; private events: EventEmitter;
private eventBus: TypedEventBus;
private concurrencyManager: ConcurrencyManager; private concurrencyManager: ConcurrencyManager;
private autoLoop: AutoLoopState | null = null; private autoLoop: AutoLoopState | null = null;
private featureLoader = new FeatureLoader(); private featureLoader = new FeatureLoader();
@@ -441,9 +443,11 @@ export class AutoModeService {
constructor( constructor(
events: EventEmitter, events: EventEmitter,
settingsService?: SettingsService, settingsService?: SettingsService,
concurrencyManager?: ConcurrencyManager concurrencyManager?: ConcurrencyManager,
eventBus?: TypedEventBus
) { ) {
this.events = events; this.events = events;
this.eventBus = eventBus ?? new TypedEventBus(events);
this.settingsService = settingsService ?? null; this.settingsService = settingsService ?? null;
// Pass the getCurrentBranch function to ConcurrencyManager for worktree counting // Pass the getCurrentBranch function to ConcurrencyManager for worktree counting
this.concurrencyManager = concurrencyManager ?? new ConcurrencyManager(getCurrentBranch); this.concurrencyManager = concurrencyManager ?? new ConcurrencyManager(getCurrentBranch);
@@ -653,7 +657,7 @@ export class AutoModeService {
); );
// Emit event to notify UI // Emit event to notify UI
this.emitAutoModeEvent('auto_mode_paused_failures', { this.eventBus.emitAutoModeEvent('auto_mode_paused_failures', {
message: message:
failureCount >= CONSECUTIVE_FAILURE_THRESHOLD failureCount >= CONSECUTIVE_FAILURE_THRESHOLD
? `Auto Mode paused: ${failureCount} consecutive failures detected. This may indicate a quota limit or API issue. Please check your usage and try again.` ? `Auto Mode paused: ${failureCount} consecutive failures detected. This may indicate a quota limit or API issue. Please check your usage and try again.`
@@ -683,7 +687,7 @@ export class AutoModeService {
); );
// Emit event to notify UI // Emit event to notify UI
this.emitAutoModeEvent('auto_mode_paused_failures', { this.eventBus.emitAutoModeEvent('auto_mode_paused_failures', {
message: message:
failureCount >= CONSECUTIVE_FAILURE_THRESHOLD failureCount >= CONSECUTIVE_FAILURE_THRESHOLD
? `Auto Mode paused: ${failureCount} consecutive failures detected. This may indicate a quota limit or API issue. Please check your usage and try again.` ? `Auto Mode paused: ${failureCount} consecutive failures detected. This may indicate a quota limit or API issue. Please check your usage and try again.`
@@ -839,7 +843,7 @@ export class AutoModeService {
// Don't fail startup due to reset errors // Don't fail startup due to reset errors
} }
this.emitAutoModeEvent('auto_mode_started', { this.eventBus.emitAutoModeEvent('auto_mode_started', {
message: `Auto mode started with max ${resolvedMaxConcurrency} concurrent features`, message: `Auto mode started with max ${resolvedMaxConcurrency} concurrent features`,
projectPath, projectPath,
branchName, branchName,
@@ -854,7 +858,7 @@ export class AutoModeService {
const worktreeDescErr = branchName ? `worktree ${branchName}` : 'main worktree'; const worktreeDescErr = branchName ? `worktree ${branchName}` : 'main worktree';
logger.error(`Loop error for ${worktreeDescErr} in ${projectPath}:`, error); logger.error(`Loop error for ${worktreeDescErr} in ${projectPath}:`, error);
const errorInfo = classifyError(error); const errorInfo = classifyError(error);
this.emitAutoModeEvent('auto_mode_error', { this.eventBus.emitAutoModeEvent('auto_mode_error', {
error: errorInfo.message, error: errorInfo.message,
errorType: errorInfo.type, errorType: errorInfo.type,
projectPath, projectPath,
@@ -909,7 +913,7 @@ export class AutoModeService {
if (pendingFeatures.length === 0) { if (pendingFeatures.length === 0) {
// Emit idle event only once when backlog is empty AND no features are running // Emit idle event only once when backlog is empty AND no features are running
if (projectRunningCount === 0 && !projectState.hasEmittedIdleEvent) { if (projectRunningCount === 0 && !projectState.hasEmittedIdleEvent) {
this.emitAutoModeEvent('auto_mode_idle', { this.eventBus.emitAutoModeEvent('auto_mode_idle', {
message: 'No pending features - auto mode idle', message: 'No pending features - auto mode idle',
projectPath, projectPath,
branchName, branchName,
@@ -1012,7 +1016,7 @@ export class AutoModeService {
// Emit stop event // Emit stop event
if (wasRunning) { if (wasRunning) {
this.emitAutoModeEvent('auto_mode_stopped', { this.eventBus.emitAutoModeEvent('auto_mode_stopped', {
message: 'Auto mode stopped', message: 'Auto mode stopped',
projectPath, projectPath,
branchName, branchName,
@@ -1115,7 +1119,7 @@ export class AutoModeService {
branchName: null, branchName: null,
}; };
this.emitAutoModeEvent('auto_mode_started', { this.eventBus.emitAutoModeEvent('auto_mode_started', {
message: `Auto mode started with max ${maxConcurrency} concurrent features`, message: `Auto mode started with max ${maxConcurrency} concurrent features`,
projectPath, projectPath,
}); });
@@ -1129,7 +1133,7 @@ export class AutoModeService {
this.runAutoLoop().catch((error) => { this.runAutoLoop().catch((error) => {
logger.error('Loop error:', error); logger.error('Loop error:', error);
const errorInfo = classifyError(error); const errorInfo = classifyError(error);
this.emitAutoModeEvent('auto_mode_error', { this.eventBus.emitAutoModeEvent('auto_mode_error', {
error: errorInfo.message, error: errorInfo.message,
errorType: errorInfo.type, errorType: errorInfo.type,
projectPath, projectPath,
@@ -1161,7 +1165,7 @@ export class AutoModeService {
// Emit idle event only once when backlog is empty AND no features are running // Emit idle event only once when backlog is empty AND no features are running
const runningCount = this.concurrencyManager.getAllRunning().length; const runningCount = this.concurrencyManager.getAllRunning().length;
if (runningCount === 0 && !this.hasEmittedIdleEvent) { if (runningCount === 0 && !this.hasEmittedIdleEvent) {
this.emitAutoModeEvent('auto_mode_idle', { this.eventBus.emitAutoModeEvent('auto_mode_idle', {
message: 'No pending features - auto mode idle', message: 'No pending features - auto mode idle',
projectPath: this.config!.projectPath, projectPath: this.config!.projectPath,
}); });
@@ -1225,7 +1229,7 @@ export class AutoModeService {
// Emit stop event immediately when user explicitly stops // Emit stop event immediately when user explicitly stops
if (wasRunning) { if (wasRunning) {
this.emitAutoModeEvent('auto_mode_stopped', { this.eventBus.emitAutoModeEvent('auto_mode_stopped', {
message: 'Auto mode stopped', message: 'Auto mode stopped',
projectPath, projectPath,
}); });
@@ -1390,7 +1394,7 @@ export class AutoModeService {
await this.updateFeatureStatus(projectPath, featureId, 'in_progress'); await this.updateFeatureStatus(projectPath, featureId, 'in_progress');
// Emit feature start event AFTER status update so frontend sees correct status // Emit feature start event AFTER status update so frontend sees correct status
this.emitAutoModeEvent('auto_mode_feature_start', { this.eventBus.emitAutoModeEvent('auto_mode_feature_start', {
featureId, featureId,
projectPath, projectPath,
branchName: feature.branchName ?? null, branchName: feature.branchName ?? null,
@@ -1442,7 +1446,7 @@ export class AutoModeService {
// Emit planning mode info // Emit planning mode info
if (feature.planningMode && feature.planningMode !== 'skip') { if (feature.planningMode && feature.planningMode !== 'skip') {
this.emitAutoModeEvent('planning_started', { this.eventBus.emitAutoModeEvent('planning_started', {
featureId: feature.id, featureId: feature.id,
mode: feature.planningMode, mode: feature.planningMode,
message: `Starting ${feature.planningMode} planning phase`, message: `Starting ${feature.planningMode} planning phase`,
@@ -1556,7 +1560,7 @@ export class AutoModeService {
console.warn('[AutoMode] Failed to record learnings:', learningError); console.warn('[AutoMode] Failed to record learnings:', learningError);
} }
this.emitAutoModeEvent('auto_mode_feature_complete', { this.eventBus.emitAutoModeEvent('auto_mode_feature_complete', {
featureId, featureId,
featureName: feature.title, featureName: feature.title,
branchName: feature.branchName ?? null, branchName: feature.branchName ?? null,
@@ -1572,7 +1576,7 @@ export class AutoModeService {
const errorInfo = classifyError(error); const errorInfo = classifyError(error);
if (errorInfo.isAbort) { if (errorInfo.isAbort) {
this.emitAutoModeEvent('auto_mode_feature_complete', { this.eventBus.emitAutoModeEvent('auto_mode_feature_complete', {
featureId, featureId,
featureName: feature?.title, featureName: feature?.title,
branchName: feature?.branchName ?? null, branchName: feature?.branchName ?? null,
@@ -1583,7 +1587,7 @@ export class AutoModeService {
} else { } else {
logger.error(`Feature ${featureId} failed:`, error); logger.error(`Feature ${featureId} failed:`, error);
await this.updateFeatureStatus(projectPath, featureId, 'backlog'); await this.updateFeatureStatus(projectPath, featureId, 'backlog');
this.emitAutoModeEvent('auto_mode_error', { this.eventBus.emitAutoModeEvent('auto_mode_error', {
featureId, featureId,
featureName: feature?.title, featureName: feature?.title,
branchName: feature?.branchName ?? null, branchName: feature?.branchName ?? null,
@@ -1666,14 +1670,14 @@ export class AutoModeService {
// Update feature status to current pipeline step // Update feature status to current pipeline step
await this.updateFeatureStatus(projectPath, featureId, pipelineStatus); await this.updateFeatureStatus(projectPath, featureId, pipelineStatus);
this.emitAutoModeEvent('auto_mode_progress', { this.eventBus.emitAutoModeEvent('auto_mode_progress', {
featureId, featureId,
branchName: feature.branchName ?? null, branchName: feature.branchName ?? null,
content: `Starting pipeline step ${i + 1}/${steps.length}: ${step.name}`, content: `Starting pipeline step ${i + 1}/${steps.length}: ${step.name}`,
projectPath, projectPath,
}); });
this.emitAutoModeEvent('pipeline_step_started', { this.eventBus.emitAutoModeEvent('pipeline_step_started', {
featureId, featureId,
stepId: step.id, stepId: step.id,
stepName: step.name, stepName: step.name,
@@ -1720,7 +1724,7 @@ export class AutoModeService {
// No context update // No context update
} }
this.emitAutoModeEvent('pipeline_step_complete', { this.eventBus.emitAutoModeEvent('pipeline_step_complete', {
featureId, featureId,
stepId: step.id, stepId: step.id,
stepName: step.name, stepName: step.name,
@@ -1882,7 +1886,7 @@ Complete the pipeline step instructions above. Review the previous work and appl
); );
// Emit event for UI notification // Emit event for UI notification
this.emitAutoModeEvent('auto_mode_feature_resuming', { this.eventBus.emitAutoModeEvent('auto_mode_feature_resuming', {
featureId, featureId,
featureName: feature.title, featureName: feature.title,
projectPath, projectPath,
@@ -1900,7 +1904,7 @@ Complete the pipeline step instructions above. Review the previous work and appl
); );
// Emit event for UI notification // Emit event for UI notification
this.emitAutoModeEvent('auto_mode_feature_resuming', { this.eventBus.emitAutoModeEvent('auto_mode_feature_resuming', {
featureId, featureId,
featureName: feature.title, featureName: feature.title,
projectPath, projectPath,
@@ -1978,7 +1982,7 @@ Complete the pipeline step instructions above. Review the previous work and appl
await this.updateFeatureStatus(projectPath, featureId, finalStatus); await this.updateFeatureStatus(projectPath, featureId, finalStatus);
this.emitAutoModeEvent('auto_mode_feature_complete', { this.eventBus.emitAutoModeEvent('auto_mode_feature_complete', {
featureId, featureId,
featureName: feature.title, featureName: feature.title,
branchName: feature.branchName ?? null, branchName: feature.branchName ?? null,
@@ -2064,7 +2068,7 @@ Complete the pipeline step instructions above. Review the previous work and appl
// If next status is not a pipeline step, feature is done // If next status is not a pipeline step, feature is done
if (!pipelineService.isPipelineStatus(nextStatus)) { if (!pipelineService.isPipelineStatus(nextStatus)) {
await this.updateFeatureStatus(projectPath, featureId, nextStatus); await this.updateFeatureStatus(projectPath, featureId, nextStatus);
this.emitAutoModeEvent('auto_mode_feature_complete', { this.eventBus.emitAutoModeEvent('auto_mode_feature_complete', {
featureId, featureId,
featureName: feature.title, featureName: feature.title,
branchName: feature.branchName ?? null, branchName: feature.branchName ?? null,
@@ -2093,7 +2097,7 @@ Complete the pipeline step instructions above. Review the previous work and appl
if (stepsToExecute.length === 0) { if (stepsToExecute.length === 0) {
const finalStatus = feature.skipTests ? 'waiting_approval' : 'verified'; const finalStatus = feature.skipTests ? 'waiting_approval' : 'verified';
await this.updateFeatureStatus(projectPath, featureId, finalStatus); await this.updateFeatureStatus(projectPath, featureId, finalStatus);
this.emitAutoModeEvent('auto_mode_feature_complete', { this.eventBus.emitAutoModeEvent('auto_mode_feature_complete', {
featureId, featureId,
featureName: feature.title, featureName: feature.title,
branchName: feature.branchName ?? null, branchName: feature.branchName ?? null,
@@ -2145,7 +2149,7 @@ Complete the pipeline step instructions above. Review the previous work and appl
runningEntry.branchName = branchName ?? null; runningEntry.branchName = branchName ?? null;
// Emit resume event // Emit resume event
this.emitAutoModeEvent('auto_mode_feature_start', { this.eventBus.emitAutoModeEvent('auto_mode_feature_start', {
featureId, featureId,
projectPath, projectPath,
branchName: branchName ?? null, branchName: branchName ?? null,
@@ -2156,7 +2160,7 @@ Complete the pipeline step instructions above. Review the previous work and appl
}, },
}); });
this.emitAutoModeEvent('auto_mode_progress', { this.eventBus.emitAutoModeEvent('auto_mode_progress', {
featureId, featureId,
projectPath, projectPath,
branchName: branchName ?? null, branchName: branchName ?? null,
@@ -2187,7 +2191,7 @@ Complete the pipeline step instructions above. Review the previous work and appl
logger.info(`Pipeline resume completed successfully for feature ${featureId}`); logger.info(`Pipeline resume completed successfully for feature ${featureId}`);
this.emitAutoModeEvent('auto_mode_feature_complete', { this.eventBus.emitAutoModeEvent('auto_mode_feature_complete', {
featureId, featureId,
featureName: feature.title, featureName: feature.title,
branchName: feature.branchName ?? null, branchName: feature.branchName ?? null,
@@ -2199,7 +2203,7 @@ Complete the pipeline step instructions above. Review the previous work and appl
const errorInfo = classifyError(error); const errorInfo = classifyError(error);
if (errorInfo.isAbort) { if (errorInfo.isAbort) {
this.emitAutoModeEvent('auto_mode_feature_complete', { this.eventBus.emitAutoModeEvent('auto_mode_feature_complete', {
featureId, featureId,
featureName: feature.title, featureName: feature.title,
branchName: feature.branchName ?? null, branchName: feature.branchName ?? null,
@@ -2210,7 +2214,7 @@ Complete the pipeline step instructions above. Review the previous work and appl
} else { } else {
logger.error(`Pipeline resume failed for feature ${featureId}:`, error); logger.error(`Pipeline resume failed for feature ${featureId}:`, error);
await this.updateFeatureStatus(projectPath, featureId, 'backlog'); await this.updateFeatureStatus(projectPath, featureId, 'backlog');
this.emitAutoModeEvent('auto_mode_error', { this.eventBus.emitAutoModeEvent('auto_mode_error', {
featureId, featureId,
featureName: feature.title, featureName: feature.title,
branchName: feature.branchName ?? null, branchName: feature.branchName ?? null,
@@ -2335,7 +2339,7 @@ Address the follow-up instructions above. Review the previous work and make the
await this.updateFeatureStatus(projectPath, featureId, 'in_progress'); await this.updateFeatureStatus(projectPath, featureId, 'in_progress');
// Emit feature start event AFTER status update so frontend sees correct status // Emit feature start event AFTER status update so frontend sees correct status
this.emitAutoModeEvent('auto_mode_feature_start', { this.eventBus.emitAutoModeEvent('auto_mode_feature_start', {
featureId, featureId,
projectPath, projectPath,
branchName, branchName,
@@ -2439,7 +2443,7 @@ Address the follow-up instructions above. Review the previous work and make the
// Record success to reset consecutive failure tracking // Record success to reset consecutive failure tracking
this.recordSuccess(); this.recordSuccess();
this.emitAutoModeEvent('auto_mode_feature_complete', { this.eventBus.emitAutoModeEvent('auto_mode_feature_complete', {
featureId, featureId,
featureName: feature?.title, featureName: feature?.title,
branchName: branchName ?? null, branchName: branchName ?? null,
@@ -2452,7 +2456,7 @@ Address the follow-up instructions above. Review the previous work and make the
} catch (error) { } catch (error) {
const errorInfo = classifyError(error); const errorInfo = classifyError(error);
if (!errorInfo.isCancellation) { if (!errorInfo.isCancellation) {
this.emitAutoModeEvent('auto_mode_error', { this.eventBus.emitAutoModeEvent('auto_mode_error', {
featureId, featureId,
featureName: feature?.title, featureName: feature?.title,
branchName: branchName ?? null, branchName: branchName ?? null,
@@ -2532,7 +2536,7 @@ Address the follow-up instructions above. Review the previous work and make the
} }
} }
this.emitAutoModeEvent('auto_mode_feature_complete', { this.eventBus.emitAutoModeEvent('auto_mode_feature_complete', {
featureId, featureId,
featureName: feature?.title, featureName: feature?.title,
branchName: feature?.branchName ?? null, branchName: feature?.branchName ?? null,
@@ -2612,7 +2616,7 @@ Address the follow-up instructions above. Review the previous work and make the
cwd: workDir, cwd: workDir,
}); });
this.emitAutoModeEvent('auto_mode_feature_complete', { this.eventBus.emitAutoModeEvent('auto_mode_feature_complete', {
featureId, featureId,
featureName: feature?.title, featureName: feature?.title,
branchName: feature?.branchName ?? null, branchName: feature?.branchName ?? null,
@@ -2651,7 +2655,7 @@ Address the follow-up instructions above. Review the previous work and make the
const abortController = new AbortController(); const abortController = new AbortController();
const analysisFeatureId = `analysis-${Date.now()}`; const analysisFeatureId = `analysis-${Date.now()}`;
this.emitAutoModeEvent('auto_mode_feature_start', { this.eventBus.emitAutoModeEvent('auto_mode_feature_start', {
featureId: analysisFeatureId, featureId: analysisFeatureId,
projectPath, projectPath,
branchName: null, // Project analysis is not worktree-specific branchName: null, // Project analysis is not worktree-specific
@@ -2732,7 +2736,7 @@ Format your response as a structured markdown document.`;
for (const block of msg.message.content) { for (const block of msg.message.content) {
if (block.type === 'text') { if (block.type === 'text') {
analysisResult = block.text || ''; analysisResult = block.text || '';
this.emitAutoModeEvent('auto_mode_progress', { this.eventBus.emitAutoModeEvent('auto_mode_progress', {
featureId: analysisFeatureId, featureId: analysisFeatureId,
content: block.text, content: block.text,
projectPath, projectPath,
@@ -2750,7 +2754,7 @@ Format your response as a structured markdown document.`;
await secureFs.mkdir(automakerDir, { recursive: true }); await secureFs.mkdir(automakerDir, { recursive: true });
await secureFs.writeFile(analysisPath, analysisResult); await secureFs.writeFile(analysisPath, analysisResult);
this.emitAutoModeEvent('auto_mode_feature_complete', { this.eventBus.emitAutoModeEvent('auto_mode_feature_complete', {
featureId: analysisFeatureId, featureId: analysisFeatureId,
featureName: 'Project Analysis', featureName: 'Project Analysis',
branchName: null, // Project analysis is not worktree-specific branchName: null, // Project analysis is not worktree-specific
@@ -2760,7 +2764,7 @@ Format your response as a structured markdown document.`;
}); });
} catch (error) { } catch (error) {
const errorInfo = classifyError(error); const errorInfo = classifyError(error);
this.emitAutoModeEvent('auto_mode_error', { this.eventBus.emitAutoModeEvent('auto_mode_error', {
featureId: analysisFeatureId, featureId: analysisFeatureId,
featureName: 'Project Analysis', featureName: 'Project Analysis',
branchName: null, // Project analysis is not worktree-specific branchName: null, // Project analysis is not worktree-specific
@@ -3021,7 +3025,7 @@ Format your response as a structured markdown document.`;
await this.updateFeatureStatus(projectPathFromClient, featureId, 'backlog'); await this.updateFeatureStatus(projectPathFromClient, featureId, 'backlog');
this.emitAutoModeEvent('plan_rejected', { this.eventBus.emitAutoModeEvent('plan_rejected', {
featureId, featureId,
projectPath: projectPathFromClient, projectPath: projectPathFromClient,
feedback, feedback,
@@ -3055,7 +3059,7 @@ Format your response as a structured markdown document.`;
// If rejected with feedback, we can store it for the user to see // If rejected with feedback, we can store it for the user to see
if (!approved && feedback) { if (!approved && feedback) {
// Emit event so client knows the rejection reason // Emit event so client knows the rejection reason
this.emitAutoModeEvent('plan_rejected', { this.eventBus.emitAutoModeEvent('plan_rejected', {
featureId, featureId,
projectPath, projectPath,
feedback, feedback,
@@ -3435,7 +3439,7 @@ Format your response as a structured markdown document.`;
await atomicWriteJson(featurePath, feature, { backupCount: DEFAULT_BACKUP_COUNT }); await atomicWriteJson(featurePath, feature, { backupCount: DEFAULT_BACKUP_COUNT });
this.emitAutoModeEvent('auto_mode_summary', { this.eventBus.emitAutoModeEvent('auto_mode_summary', {
featureId, featureId,
projectPath, projectPath,
summary, summary,
@@ -3483,7 +3487,7 @@ Format your response as a structured markdown document.`;
await atomicWriteJson(featurePath, feature, { backupCount: DEFAULT_BACKUP_COUNT }); await atomicWriteJson(featurePath, feature, { backupCount: DEFAULT_BACKUP_COUNT });
// Emit event for UI update // Emit event for UI update
this.emitAutoModeEvent('auto_mode_task_status', { this.eventBus.emitAutoModeEvent('auto_mode_task_status', {
featureId, featureId,
projectPath, projectPath,
taskId, taskId,
@@ -3938,14 +3942,14 @@ You can use the Read tool to view these images at any time during implementation
await this.sleep(500); await this.sleep(500);
// Emit mock progress events to simulate agent activity // Emit mock progress events to simulate agent activity
this.emitAutoModeEvent('auto_mode_progress', { this.eventBus.emitAutoModeEvent('auto_mode_progress', {
featureId, featureId,
content: 'Mock agent: Analyzing the codebase...', content: 'Mock agent: Analyzing the codebase...',
}); });
await this.sleep(300); await this.sleep(300);
this.emitAutoModeEvent('auto_mode_progress', { this.eventBus.emitAutoModeEvent('auto_mode_progress', {
featureId, featureId,
content: 'Mock agent: Implementing the feature...', content: 'Mock agent: Implementing the feature...',
}); });
@@ -3956,7 +3960,7 @@ You can use the Read tool to view these images at any time during implementation
const mockFilePath = path.join(workDir, 'yellow.txt'); const mockFilePath = path.join(workDir, 'yellow.txt');
await secureFs.writeFile(mockFilePath, 'yellow'); await secureFs.writeFile(mockFilePath, 'yellow');
this.emitAutoModeEvent('auto_mode_progress', { this.eventBus.emitAutoModeEvent('auto_mode_progress', {
featureId, featureId,
content: "Mock agent: Created yellow.txt file with content 'yellow'", content: "Mock agent: Created yellow.txt file with content 'yellow'",
}); });
@@ -4208,7 +4212,7 @@ This mock response was generated because AUTOMAKER_MOCK_AGENT=true was set.
// Emit task started // Emit task started
logger.info(`Starting task ${task.id}: ${task.description}`); logger.info(`Starting task ${task.id}: ${task.description}`);
this.emitAutoModeEvent('auto_mode_task_started', { this.eventBus.emitAutoModeEvent('auto_mode_task_started', {
featureId, featureId,
projectPath, projectPath,
branchName, branchName,
@@ -4257,7 +4261,7 @@ This mock response was generated because AUTOMAKER_MOCK_AGENT=true was set.
const text = block.text || ''; const text = block.text || '';
taskOutput += text; taskOutput += text;
responseText += text; responseText += text;
this.emitAutoModeEvent('auto_mode_progress', { this.eventBus.emitAutoModeEvent('auto_mode_progress', {
featureId, featureId,
branchName, branchName,
content: text, content: text,
@@ -4279,7 +4283,7 @@ This mock response was generated because AUTOMAKER_MOCK_AGENT=true was set.
} }
} }
} else if (block.type === 'tool_use') { } else if (block.type === 'tool_use') {
this.emitAutoModeEvent('auto_mode_tool', { this.eventBus.emitAutoModeEvent('auto_mode_tool', {
featureId, featureId,
branchName, branchName,
tool: block.name, tool: block.name,
@@ -4302,7 +4306,7 @@ This mock response was generated because AUTOMAKER_MOCK_AGENT=true was set.
// Emit task completed // Emit task completed
logger.info(`Task ${task.id} completed for feature ${featureId}`); logger.info(`Task ${task.id} completed for feature ${featureId}`);
this.emitAutoModeEvent('auto_mode_task_complete', { this.eventBus.emitAutoModeEvent('auto_mode_task_complete', {
featureId, featureId,
projectPath, projectPath,
branchName, branchName,
@@ -4466,7 +4470,7 @@ This mock response was generated because AUTOMAKER_MOCK_AGENT=true was set.
const approvalPromise = this.waitForPlanApproval(featureId, projectPath); const approvalPromise = this.waitForPlanApproval(featureId, projectPath);
// Emit plan_approval_required event // Emit plan_approval_required event
this.emitAutoModeEvent('plan_approval_required', { this.eventBus.emitAutoModeEvent('plan_approval_required', {
featureId, featureId,
projectPath, projectPath,
branchName, branchName,
@@ -4498,7 +4502,7 @@ This mock response was generated because AUTOMAKER_MOCK_AGENT=true was set.
userFeedback = approvalResult.feedback; userFeedback = approvalResult.feedback;
// Emit approval event // Emit approval event
this.emitAutoModeEvent('plan_approved', { this.eventBus.emitAutoModeEvent('plan_approved', {
featureId, featureId,
projectPath, projectPath,
branchName, branchName,
@@ -4527,7 +4531,7 @@ This mock response was generated because AUTOMAKER_MOCK_AGENT=true was set.
planVersion++; planVersion++;
// Emit revision event // Emit revision event
this.emitAutoModeEvent('plan_revision_requested', { this.eventBus.emitAutoModeEvent('plan_revision_requested', {
featureId, featureId,
projectPath, projectPath,
branchName, branchName,
@@ -4610,7 +4614,7 @@ This mock response was generated because AUTOMAKER_MOCK_AGENT=true was set.
for (const block of msg.message.content) { for (const block of msg.message.content) {
if (block.type === 'text') { if (block.type === 'text') {
revisionText += block.text || ''; revisionText += block.text || '';
this.emitAutoModeEvent('auto_mode_progress', { this.eventBus.emitAutoModeEvent('auto_mode_progress', {
featureId, featureId,
content: block.text, content: block.text,
}); });
@@ -4645,7 +4649,7 @@ This mock response was generated because AUTOMAKER_MOCK_AGENT=true was set.
`This will cause fallback to single-agent execution. ` + `This will cause fallback to single-agent execution. ` +
`The AI may have omitted the required \`\`\`tasks block.` `The AI may have omitted the required \`\`\`tasks block.`
); );
this.emitAutoModeEvent('plan_revision_warning', { this.eventBus.emitAutoModeEvent('plan_revision_warning', {
featureId, featureId,
projectPath, projectPath,
branchName, branchName,
@@ -4684,7 +4688,7 @@ This mock response was generated because AUTOMAKER_MOCK_AGENT=true was set.
); );
// Emit info event for frontend // Emit info event for frontend
this.emitAutoModeEvent('plan_auto_approved', { this.eventBus.emitAutoModeEvent('plan_auto_approved', {
featureId, featureId,
projectPath, projectPath,
branchName, branchName,
@@ -4744,7 +4748,7 @@ This mock response was generated because AUTOMAKER_MOCK_AGENT=true was set.
// Emit task started // Emit task started
logger.info(`Starting task ${task.id}: ${task.description}`); logger.info(`Starting task ${task.id}: ${task.description}`);
this.emitAutoModeEvent('auto_mode_task_started', { this.eventBus.emitAutoModeEvent('auto_mode_task_started', {
featureId, featureId,
projectPath, projectPath,
branchName, branchName,
@@ -4794,7 +4798,7 @@ This mock response was generated because AUTOMAKER_MOCK_AGENT=true was set.
const text = block.text || ''; const text = block.text || '';
taskOutput += text; taskOutput += text;
responseText += text; responseText += text;
this.emitAutoModeEvent('auto_mode_progress', { this.eventBus.emitAutoModeEvent('auto_mode_progress', {
featureId, featureId,
branchName, branchName,
content: text, content: text,
@@ -4813,7 +4817,7 @@ This mock response was generated because AUTOMAKER_MOCK_AGENT=true was set.
startTaskId, startTaskId,
'in_progress' 'in_progress'
); );
this.emitAutoModeEvent('auto_mode_task_started', { this.eventBus.emitAutoModeEvent('auto_mode_task_started', {
featureId, featureId,
projectPath, projectPath,
branchName, branchName,
@@ -4845,7 +4849,7 @@ This mock response was generated because AUTOMAKER_MOCK_AGENT=true was set.
const phaseNumber = detectPhaseCompleteMarker(text); const phaseNumber = detectPhaseCompleteMarker(text);
if (phaseNumber !== null) { if (phaseNumber !== null) {
logger.info(`[PHASE_COMPLETE] detected for Phase ${phaseNumber}`); logger.info(`[PHASE_COMPLETE] detected for Phase ${phaseNumber}`);
this.emitAutoModeEvent('auto_mode_phase_complete', { this.eventBus.emitAutoModeEvent('auto_mode_phase_complete', {
featureId, featureId,
projectPath, projectPath,
branchName, branchName,
@@ -4853,7 +4857,7 @@ This mock response was generated because AUTOMAKER_MOCK_AGENT=true was set.
}); });
} }
} else if (block.type === 'tool_use') { } else if (block.type === 'tool_use') {
this.emitAutoModeEvent('auto_mode_tool', { this.eventBus.emitAutoModeEvent('auto_mode_tool', {
featureId, featureId,
branchName, branchName,
tool: block.name, tool: block.name,
@@ -4877,7 +4881,7 @@ This mock response was generated because AUTOMAKER_MOCK_AGENT=true was set.
// Emit task completed // Emit task completed
logger.info(`Task ${task.id} completed for feature ${featureId}`); logger.info(`Task ${task.id} completed for feature ${featureId}`);
this.emitAutoModeEvent('auto_mode_task_complete', { this.eventBus.emitAutoModeEvent('auto_mode_task_complete', {
featureId, featureId,
projectPath, projectPath,
branchName, branchName,
@@ -4898,7 +4902,7 @@ This mock response was generated because AUTOMAKER_MOCK_AGENT=true was set.
// Phase changed, emit phase complete // Phase changed, emit phase complete
const phaseMatch = task.phase.match(/Phase\s*(\d+)/i); const phaseMatch = task.phase.match(/Phase\s*(\d+)/i);
if (phaseMatch) { if (phaseMatch) {
this.emitAutoModeEvent('auto_mode_phase_complete', { this.eventBus.emitAutoModeEvent('auto_mode_phase_complete', {
featureId, featureId,
projectPath, projectPath,
branchName, branchName,
@@ -4949,13 +4953,13 @@ This mock response was generated because AUTOMAKER_MOCK_AGENT=true was set.
for (const block of msg.message.content) { for (const block of msg.message.content) {
if (block.type === 'text') { if (block.type === 'text') {
responseText += block.text || ''; responseText += block.text || '';
this.emitAutoModeEvent('auto_mode_progress', { this.eventBus.emitAutoModeEvent('auto_mode_progress', {
featureId, featureId,
branchName, branchName,
content: block.text, content: block.text,
}); });
} else if (block.type === 'tool_use') { } else if (block.type === 'tool_use') {
this.emitAutoModeEvent('auto_mode_tool', { this.eventBus.emitAutoModeEvent('auto_mode_tool', {
featureId, featureId,
branchName, branchName,
tool: block.name, tool: block.name,
@@ -4988,7 +4992,7 @@ This mock response was generated because AUTOMAKER_MOCK_AGENT=true was set.
logger.info( logger.info(
`Emitting progress event for ${featureId}, content length: ${block.text?.length || 0}` `Emitting progress event for ${featureId}, content length: ${block.text?.length || 0}`
); );
this.emitAutoModeEvent('auto_mode_progress', { this.eventBus.emitAutoModeEvent('auto_mode_progress', {
featureId, featureId,
branchName, branchName,
content: block.text, content: block.text,
@@ -4996,7 +5000,7 @@ This mock response was generated because AUTOMAKER_MOCK_AGENT=true was set.
} }
} else if (block.type === 'tool_use') { } else if (block.type === 'tool_use') {
// Emit event for real-time UI // Emit event for real-time UI
this.emitAutoModeEvent('auto_mode_tool', { this.eventBus.emitAutoModeEvent('auto_mode_tool', {
featureId, featureId,
branchName, branchName,
tool: block.name, tool: block.name,
@@ -5234,19 +5238,6 @@ This mock response was generated because AUTOMAKER_MOCK_AGENT=true was set.
return prompt; return prompt;
} }
/**
* Emit an auto-mode event wrapped in the correct format for the client.
* All auto-mode events are sent as type "auto-mode:event" with the actual
* event type and data in the payload.
*/
private emitAutoModeEvent(eventType: string, data: Record<string, unknown>): void {
// Wrap the event in auto-mode:event format expected by the client
this.events.emit('auto-mode:event', {
type: eventType,
...data,
});
}
private sleep(ms: number, signal?: AbortSignal): Promise<void> { private sleep(ms: number, signal?: AbortSignal): Promise<void> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const timeout = setTimeout(resolve, ms); const timeout = setTimeout(resolve, ms);
@@ -5410,7 +5401,7 @@ This mock response was generated because AUTOMAKER_MOCK_AGENT=true was set.
); );
// Emit event to notify UI with context information // Emit event to notify UI with context information
this.emitAutoModeEvent('auto_mode_resuming_features', { this.eventBus.emitAutoModeEvent('auto_mode_resuming_features', {
message: `Resuming ${allInterruptedFeatures.length} interrupted feature(s) after server restart`, message: `Resuming ${allInterruptedFeatures.length} interrupted feature(s) after server restart`,
projectPath, projectPath,
featureIds: allInterruptedFeatures.map((f) => f.id), featureIds: allInterruptedFeatures.map((f) => f.id),