feat(FEAT-004): add MissionExecutionLoop class and wiring

- Create MissionExecutionLoop class for validation cycle orchestration
- Wire loop into dashboard.ts and serve.ts startup/shutdown alongside MissionAutopilot
- Integrate processTaskOutcome into scheduler's task:moved handler for mission-linked tasks
- Implement validation pass/fail/blocked/error handling with fix feature generation
- Implement retry budget exhaustion handling via createGeneratedFixFeature
- Implement recoverActiveMissions on startup to re-enqueue pending validations
- Wire loop into InProcessRuntime lifecycle
- Add AI session integration with createKbAgent and promptWithFallback
- Implement autopilot coordination via notifyValidationComplete callback
- Add error handling for AI/store failures with graceful loop continuation
- Extend SchedulerOptions with optional missionExecutionLoop field
- Export MissionExecutionLoop from engine barrel

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
This commit is contained in:
gsxdsm
2026-04-11 18:04:21 -07:00
parent 4929661674
commit 46fff0d33f
6 changed files with 725 additions and 8 deletions

View File

@@ -2,7 +2,7 @@ import type { AddressInfo } from "node:net";
import { TaskStore, AutomationStore, CentralCore, AgentStore, PluginStore, PluginLoader, getTaskMergeBlocker, syncInsightExtractionAutomation, INSIGHT_EXTRACTION_SCHEDULE_NAME, processAndAuditInsightExtraction } from "@fusion/core";
import type { Settings, ScheduledTask, AutomationRunResult } from "@fusion/core";
import { createServer, GitHubClient } from "@fusion/dashboard";
import { TriageProcessor, TaskExecutor, Scheduler, AgentSemaphore, WorktreePool, aiMergeTask, UsageLimitPauser, PRIORITY_MERGE, scanIdleWorktrees, cleanupOrphanedWorktrees, NtfyNotifier, PrMonitor, PrCommentHandler, CronRunner, StuckTaskDetector, SelfHealingManager, MissionAutopilot, createAiPromptExecutor, HeartbeatMonitor, HeartbeatTriggerScheduler, type WakeContext } from "@fusion/engine";
import { TriageProcessor, TaskExecutor, Scheduler, AgentSemaphore, WorktreePool, aiMergeTask, UsageLimitPauser, PRIORITY_MERGE, scanIdleWorktrees, cleanupOrphanedWorktrees, NtfyNotifier, PrMonitor, PrCommentHandler, CronRunner, StuckTaskDetector, SelfHealingManager, MissionAutopilot, MissionExecutionLoop, createAiPromptExecutor, HeartbeatMonitor, HeartbeatTriggerScheduler, type WakeContext } from "@fusion/engine";
import { AuthStorage, DefaultPackageManager, ModelRegistry, SettingsManager, discoverAndLoadExtensions, getAgentDir, createExtensionRuntime } from "@mariozechner/pi-coding-agent";
import {
getMergeStrategy,
@@ -711,6 +711,26 @@ export async function runDashboard(port: number, opts: { paused?: boolean; dev?:
//
const missionAutopilot = new MissionAutopilot(store, store.getMissionStore());
// ── MissionExecutionLoop: validation cycle orchestration ───────────
//
// Created alongside MissionAutopilot to handle the validation cycle
// (implement → validate → fix → pass). In dev mode the loop is created
// but not started.
//
const missionExecutionLoop = new MissionExecutionLoop({
taskStore: store,
missionStore: store.getMissionStore(),
missionAutopilot: {
notifyValidationComplete: async (featureId: string, status: "passed" | "failed" | "blocked" | "error") => {
// Delegate to autopilot after validation completes
if (missionAutopilot) {
await missionAutopilot.handleTaskCompletion(featureId);
}
},
},
rootDir: cwd,
});
const dashboardAuthStorage = wrapAuthStorageWithApiKeyProviders(authStorage, modelRegistry);
// Start the web server with AI merge, auth, model registry, and plugin wiring
@@ -829,6 +849,7 @@ export async function runDashboard(port: number, opts: { paused?: boolean; dev?:
prMonitor,
missionStore: store.getMissionStore(),
missionAutopilot,
missionExecutionLoop,
onSchedule: (t) => console.log(`[engine] Scheduled ${t.id}`),
onBlocked: (t, deps) => console.log(`[engine] ${t.id} blocked by ${deps.join(", ")}`),
onClosedPrFeedback: async (taskId, prInfo, comments) => {
@@ -922,9 +943,17 @@ export async function runDashboard(port: number, opts: { paused?: boolean; dev?:
triage.start();
scheduler.start();
missionAutopilot.start();
missionExecutionLoop.start();
stuckTaskDetector.start();
selfHealing.start();
// ── Startup: recover active missions for validation loop ─────────────
// Re-enqueue pending validations from any missions that were interrupted
// before the engine was stopped (e.g., features in validating/needs_fix state).
void missionExecutionLoop.recoverActiveMissions().catch((err) => {
console.error("[engine] Failed to recover active missions:", err);
});
// ── Startup sweep: resume orphaned in-progress tasks ──────────────
executor.resumeOrphaned().catch((err) =>
console.error("[engine] Failed to resume orphaned tasks:", err),
@@ -1079,6 +1108,7 @@ export async function runDashboard(port: number, opts: { paused?: boolean; dev?:
selfHealing.stop();
stuckTaskDetector.stop();
missionAutopilot.stop();
missionExecutionLoop.stop();
triage.stop();
scheduler.stop();
cronRunner.stop();

View File

@@ -42,6 +42,7 @@ import {
StuckTaskDetector,
SelfHealingManager,
MissionAutopilot,
MissionExecutionLoop,
createAiPromptExecutor,
HeartbeatMonitor,
HeartbeatTriggerScheduler,
@@ -583,6 +584,25 @@ export async function runServe(
const missionAutopilot = new MissionAutopilot(store, store.getMissionStore());
// ── MissionExecutionLoop: validation cycle orchestration ───────────
//
// Created alongside MissionAutopilot to handle the validation cycle
// (implement → validate → fix → pass).
//
const missionExecutionLoop = new MissionExecutionLoop({
taskStore: store,
missionStore: store.getMissionStore(),
missionAutopilot: {
notifyValidationComplete: async (featureId: string, status: "passed" | "failed" | "blocked" | "error") => {
// Delegate to autopilot after validation completes
if (missionAutopilot) {
await missionAutopilot.handleTaskCompletion(featureId);
}
},
},
rootDir: cwd,
});
const app = createServer(store, {
onMerge,
authStorage,
@@ -671,6 +691,7 @@ export async function runServe(
prMonitor,
missionStore: store.getMissionStore(),
missionAutopilot,
missionExecutionLoop,
onSchedule: (t) => console.log(`[engine] Scheduled ${t.id}`),
onBlocked: (t, deps) =>
console.log(`[engine] ${t.id} blocked by ${deps.join(", ")}`),
@@ -762,9 +783,17 @@ export async function runServe(
triage.start();
scheduler.start();
missionAutopilot.start();
missionExecutionLoop.start();
stuckTaskDetector.start();
selfHealing.start();
// ── Startup: recover active missions for validation loop ─────────────
// Re-enqueue pending validations from any missions that were interrupted
// before the engine was stopped (e.g., features in validating/needs_fix state).
void missionExecutionLoop.recoverActiveMissions().catch((err) => {
console.error("[engine] Failed to recover active missions:", err);
});
executor.resumeOrphaned().catch((err) =>
console.error("[engine] Failed to resume orphaned tasks:", err),
);
@@ -945,6 +974,7 @@ export async function runServe(
selfHealing.stop();
stuckTaskDetector.stop();
missionAutopilot.stop();
missionExecutionLoop.stop();
triage.stop();
scheduler.stop();
cronRunner.stop();