fix: recover completed tasks stuck in progress
This commit is contained in:
@@ -4,12 +4,16 @@ import { EventEmitter } from "node:events";
|
||||
// ── Capture instances & arguments ───────────────────────────────────
|
||||
|
||||
let capturedExecutorOpts: Record<string, unknown> | undefined;
|
||||
let capturedSelfHealingOpts: Record<string, unknown> | undefined;
|
||||
|
||||
const {
|
||||
mockAuthStorage,
|
||||
mockModelRegistry,
|
||||
mockDiscoverAndLoadExtensions,
|
||||
mockCreateExtensionRuntime,
|
||||
mockSelfHealingStart,
|
||||
mockSelfHealingStop,
|
||||
mockCheckStuckBudget,
|
||||
} = vi.hoisted(() => ({
|
||||
mockAuthStorage: { getAuth: vi.fn(), setAuth: vi.fn() },
|
||||
mockModelRegistry: {
|
||||
@@ -21,6 +25,9 @@ const {
|
||||
errors: [],
|
||||
}),
|
||||
mockCreateExtensionRuntime: vi.fn(),
|
||||
mockSelfHealingStart: vi.fn(),
|
||||
mockSelfHealingStop: vi.fn(),
|
||||
mockCheckStuckBudget: vi.fn().mockResolvedValue(true),
|
||||
}));
|
||||
|
||||
// Minimal mock store backed by EventEmitter so `store.on` works
|
||||
@@ -195,6 +202,14 @@ vi.mock("@fusion/engine", async (importOriginal) => {
|
||||
start: vi.fn(),
|
||||
stop: vi.fn(),
|
||||
})),
|
||||
SelfHealingManager: vi.fn().mockImplementation((_store: unknown, opts: unknown) => {
|
||||
capturedSelfHealingOpts = opts as Record<string, unknown>;
|
||||
return {
|
||||
start: mockSelfHealingStart,
|
||||
stop: mockSelfHealingStop,
|
||||
checkStuckBudget: mockCheckStuckBudget,
|
||||
};
|
||||
}),
|
||||
scanIdleWorktrees: vi.fn().mockResolvedValue([]),
|
||||
cleanupOrphanedWorktrees: vi.fn().mockResolvedValue(0),
|
||||
};
|
||||
@@ -574,6 +589,7 @@ describe("runDashboard — auto-merge pause exclusion", () => {
|
||||
|
||||
beforeEach(async () => {
|
||||
capturedExecutorOpts = undefined;
|
||||
capturedSelfHealingOpts = undefined;
|
||||
vi.clearAllMocks();
|
||||
resetGitHubMocks();
|
||||
mockStore = makeMockStore();
|
||||
@@ -759,6 +775,17 @@ describe("runDashboard — immediate resume on unpause", () => {
|
||||
expect(resumeOrphaned).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("passes executor recovery callbacks into SelfHealingManager", async () => {
|
||||
await runDashboard(0, { open: false });
|
||||
|
||||
expect(capturedSelfHealingOpts).toMatchObject({
|
||||
rootDir: process.cwd(),
|
||||
recoverCompletedTask: expect.any(Function),
|
||||
getExecutingTaskIds: expect.any(Function),
|
||||
});
|
||||
expect(mockSelfHealingStart).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("sweeps merge queue on unpause when autoMerge is enabled", async () => {
|
||||
// Set up settings to return autoMerge: true for the drain queue check
|
||||
mockStore.getSettings.mockResolvedValue({
|
||||
|
||||
@@ -568,7 +568,11 @@ export async function runDashboard(port: number, opts: { paused?: boolean; dev?:
|
||||
});
|
||||
|
||||
// ── Self-healing: auto-unpause, stuck kill budgets, maintenance ─────
|
||||
const selfHealing = new SelfHealingManager(store, { rootDir: cwd });
|
||||
const selfHealing = new SelfHealingManager(store, {
|
||||
rootDir: cwd,
|
||||
recoverCompletedTask: (task) => executorRef.current?.recoverCompletedTask(task) ?? Promise.resolve(false),
|
||||
getExecutingTaskIds: () => executorRef.current?.getExecutingTaskIds() ?? new Set(),
|
||||
});
|
||||
|
||||
// ── Stuck task detector: monitors agent sessions for stagnation ────
|
||||
// Created before the executor so it can be passed in options.
|
||||
|
||||
Reference in New Issue
Block a user