diff --git a/src/resources/extensions/sf/auto/run-unit.js b/src/resources/extensions/sf/auto/run-unit.js index f869469c1..57590fb17 100644 --- a/src/resources/extensions/sf/auto/run-unit.js +++ b/src/resources/extensions/sf/auto/run-unit.js @@ -562,6 +562,32 @@ async function runUnitViaSwarm(ctx, _pi, s, unitType, unitId, prompt, options) { input: toolCall.arguments ?? {}, }); + // #sf-mp94lth4-ew26om: Recognize unit-specific completion tools as + // implicit "complete" signals. Workers often call the unit's specific + // completion tool (reassess_roadmap with verdict, validate_milestone, + // complete_milestone, complete_slice, save_summary) and skip the + // generic checkpoint tool. Without this, the synthesized checkpoint + // falls back to outcome=continue and the solver iterates indefinitely + // despite the worker having genuinely finished (witnessed on + // reassess-roadmap M010/S03 — 5 iterations in 8min, all confirming + // completion in their text but never calling `checkpoint`). + const UNIT_COMPLETION_TOOLS = new Set([ + "reassess_roadmap", + "validate_milestone", + "complete_milestone", + "complete_slice", + "save_summary", + ]); + if ( + UNIT_COMPLETION_TOOLS.has(toolCall.name) && + workerSignaledOutcome !== "complete" && + workerSignaledOutcome !== "blocked" + ) { + // Mark as complete unless an earlier explicit checkpoint already + // said otherwise (don't override blocked / explicit progress). + workerSignaledOutcome = "complete"; + } + // Detect the canonical completion-signal tool: "checkpoint". // This is the same tool inspected by the legacy runUnit path's solver pass // (phases-unit.js line ~989: activeToolsAllowlist: ["checkpoint"]).