diff --git a/src/resources/extensions/sf/autonomous-solver.js b/src/resources/extensions/sf/autonomous-solver.js index 9f4a51714..66bc33706 100644 --- a/src/resources/extensions/sf/autonomous-solver.js +++ b/src/resources/extensions/sf/autonomous-solver.js @@ -1104,6 +1104,32 @@ export function assessAutonomousSolverTurn( checkpoint, }; } + if ( + (checkpoint.outcome === "continue" || checkpoint.outcome === "decide") && + (checkpoint.remainingItems?.length ?? 0) === 0 + ) { + const repairAttempts = getMissingCheckpointRepairAttempts(state).filter( + (attempt) => Number(attempt.iteration) === Number(state.iteration), + ).length; + if (repairAttempts >= DEFAULT_MISSING_CHECKPOINT_REPAIR_ATTEMPTS) { + return { + action: "pause", + reason: "solver-empty-continue", + state, + repairAttempts, + maxRepairAttempts: DEFAULT_MISSING_CHECKPOINT_REPAIR_ATTEMPTS, + checkpoint, + }; + } + return { + action: "missing-checkpoint-retry", + reason: "solver-empty-continue", + state, + repairAttempt: repairAttempts + 1, + maxRepairAttempts: DEFAULT_MISSING_CHECKPOINT_REPAIR_ATTEMPTS, + checkpoint, + }; + } // No-op detection: a continue with zero work is not real progress if ( (checkpoint.outcome === "continue" || checkpoint.outcome === "decide") && diff --git a/src/resources/extensions/sf/tests/autonomous-solver.test.mjs b/src/resources/extensions/sf/tests/autonomous-solver.test.mjs index 409b0f63d..f991ae70c 100644 --- a/src/resources/extensions/sf/tests/autonomous-solver.test.mjs +++ b/src/resources/extensions/sf/tests/autonomous-solver.test.mjs @@ -324,7 +324,7 @@ describe("autonomous solver", () => { outcome: "decide", summary: "Low confidence — reconstructed best-effort.", completedItems: ["Analysis done"], - remainingItems: [], + remainingItems: ["Continue from reconstructed plan"], verificationEvidence: ["artifacts match expectations"], pdd: pdd(), }); @@ -338,6 +338,30 @@ describe("autonomous solver", () => { expect(result.action).toBe("continue"); }); + test("assessAutonomousSolverTurn_empty_continue_requires_repair", () => { + const project = makeProject(); + beginAutonomousSolverIteration(project, "execute-task", "M001/S01/T01"); + appendAutonomousSolverCheckpoint(project, { + unitType: "execute-task", + unitId: "M001/S01/T01", + outcome: "continue", + summary: "Continue, but nothing remains.", + completedItems: ["Read files"], + remainingItems: [], + verificationEvidence: ["ls"], + pdd: pdd(), + }); + + const result = assessAutonomousSolverTurn( + project, + "execute-task", + "M001/S01/T01", + ); + + expect(result.action).toBe("missing-checkpoint-retry"); + expect(result.reason).toBe("solver-empty-continue"); + }); + test("assessAutonomousSolverTurn_max_iterations_pauses_before_unbounded_retry", () => { const project = makeProject(); beginAutonomousSolverIteration(project, "execute-task", "M001/S01/T01", {