fix: keep stuck detector active by default

This commit is contained in:
Berlin Luk
2026-05-10 23:55:36 +08:00
parent 62e8f6cf1f
commit 4205309114
3 changed files with 46 additions and 8 deletions

View File

@@ -718,21 +718,52 @@ describe("StuckTaskDetector", () => {
vi.useRealTimers();
});
it("does nothing when timeout is disabled", async () => {
it("falls back to workflow step timeout when stuck timeout is unset", async () => {
store = createMockStore({
getSettings: vi.fn().mockResolvedValue({ taskStuckTimeoutMs: undefined }),
getSettings: vi.fn().mockResolvedValue({
taskStuckTimeoutMs: undefined,
workflowStepTimeoutMs: 60_000,
}),
});
const customDetector = new StuckTaskDetector(store);
const onStuck = vi.fn();
const customDetector = new StuckTaskDetector(store, { onStuck });
const session = createMockSession();
customDetector.trackTask("FN-001", session);
vi.useFakeTimers({ shouldAdvanceTime: true });
vi.advanceTimersByTime(61000);
vi.advanceTimersByTime(61_000);
await customDetector.checkNow();
expect(store.moveTask).not.toHaveBeenCalled();
expect(onStuck).toHaveBeenCalledWith(
expect.objectContaining({ taskId: "FN-001", reason: "inactivity" }),
);
expect(session.dispose).toHaveBeenCalled();
vi.useRealTimers();
});
it("does nothing when both stuck and workflow timeouts are disabled", async () => {
store = createMockStore({
getSettings: vi.fn().mockResolvedValue({
taskStuckTimeoutMs: undefined,
workflowStepTimeoutMs: undefined,
}),
});
const onStuck = vi.fn();
const customDetector = new StuckTaskDetector(store, { onStuck });
const session = createMockSession();
customDetector.trackTask("FN-001", session);
vi.useFakeTimers({ shouldAdvanceTime: true });
vi.advanceTimersByTime(61_000);
await customDetector.checkNow();
expect(onStuck).not.toHaveBeenCalled();
expect(session.dispose).not.toHaveBeenCalled();
vi.useRealTimers();
});

View File

@@ -11,7 +11,9 @@
* - `recordProgress(taskId)` — step transitions (in-progress, done, skipped); resets counters
*
* The detector polls at a configurable interval and compares timestamps against
* `taskStuckTimeoutMs` from settings.
* `taskStuckTimeoutMs` from settings. When that explicit override is unset, the
* detector falls back to `workflowStepTimeoutMs` so in-flight tool calls cannot
* leave an in-progress task unmonitored by default.
*/
import type { TaskStore, Settings } from "@fusion/core";
@@ -470,8 +472,8 @@ export class StuckTaskDetector {
// Defensive fallback for pause windows where lifecycle hooks haven't run yet.
if (settings.globalPause || settings.enginePaused) return;
const timeoutMs = settings.taskStuckTimeoutMs;
if (!timeoutMs || timeoutMs <= 0) return; // Disabled
const timeoutMs = settings.taskStuckTimeoutMs ?? settings.workflowStepTimeoutMs;
if (!timeoutMs || timeoutMs <= 0) return; // Disabled only when both stuck and workflow timeouts are unset/disabled
const stuckTasks: string[] = [];