@runfusion/fusion 0.74.0-beta.7 → 0.74.0-beta.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. package/dist/bin.js +339 -473
  2. package/dist/child-process-worker.js +144 -278
  3. package/dist/client/.vite/manifest.json +254 -254
  4. package/dist/client/assets/{AgentDetailView-CTdyQL11.js → AgentDetailView-CnzGIxqj.js} +1 -1
  5. package/dist/client/assets/{AgentPermissionPolicyEditor-DBa7nIQV.js → AgentPermissionPolicyEditor-DMnyR96M.js} +1 -1
  6. package/dist/client/assets/{AgentsView-B5tss2oK.js → AgentsView-CzFtICwM.js} +3 -3
  7. package/dist/client/assets/{ChatView-DAKt1a2L.js → ChatView-Cs6B3fNf.js} +1 -1
  8. package/dist/client/assets/{CommandCenter-yPvRo9F6.js → CommandCenter-DADvho3z.js} +1 -1
  9. package/dist/client/assets/{DevServerView-BTyS2owH.js → DevServerView-Bzrzfs8Z.js} +1 -1
  10. package/dist/client/assets/{DirectoryPicker-BjGIupNU.js → DirectoryPicker-rmip5GcI.js} +1 -1
  11. package/dist/client/assets/{DocumentsView-CuxfbpSZ.js → DocumentsView-BEM4nmJT.js} +1 -1
  12. package/dist/client/assets/{EvalsView-DGuy8SHR.js → EvalsView-Du7NaYMJ.js} +1 -1
  13. package/dist/client/assets/{ExperimentalAgentOnboardingModal-lK407ZAq.js → ExperimentalAgentOnboardingModal-BpIMiZOi.js} +1 -1
  14. package/dist/client/assets/{GoalsView-DAVN0ObR.js → GoalsView-BxKRyXM3.js} +1 -1
  15. package/dist/client/assets/{InsightsView-BXQKcNof.js → InsightsView-Dauq6b3M.js} +1 -1
  16. package/dist/client/assets/{MemoryView-D7RKEtV7.js → MemoryView-DRD9Xvwx.js} +1 -1
  17. package/dist/client/assets/{PiExtensionsManager-CrOJ57R2.js → PiExtensionsManager-DJKE1iAX.js} +1 -1
  18. package/dist/client/assets/{PluginManager-D7_evt4j.js → PluginManager-CScwY-Ib.js} +1 -1
  19. package/dist/client/assets/{PullRequestView-C0lRUbth.js → PullRequestView-uAu5_4__.js} +1 -1
  20. package/dist/client/assets/{ReportModal-Cy5fRNDK.js → ReportModal-nNUcGxYj.js} +1 -1
  21. package/dist/client/assets/{ResearchView-e8st2Sxu.js → ResearchView-BX1kGbWP.js} +1 -1
  22. package/dist/client/assets/{SecretsView-BLzckrzb.js → SecretsView-U_itETHx.js} +1 -1
  23. package/dist/client/assets/{SessionTerminal-F_GIj4gh.js → SessionTerminal-_b15vcd9.js} +1 -1
  24. package/dist/client/assets/SettingsModal-B30rPfYC.css +1 -0
  25. package/dist/client/assets/{SettingsModal-XAddgWkF.js → SettingsModal-DkpUKf4k.js} +1 -1
  26. package/dist/client/assets/{SettingsModal-eQbxbkLU.js → SettingsModal-DpbYR3Co.js} +4 -4
  27. package/dist/client/assets/{SettingsTextareaRow-DVjUHNQp.js → SettingsTextareaRow-Dbv-oqsF.js} +1 -1
  28. package/dist/client/assets/{SetupWizardModal-xID9yIff.js → SetupWizardModal-n_kcJfIP.js} +2 -2
  29. package/dist/client/assets/{SkillsView-BNdMGu6E.js → SkillsView-Bv2uqBre.js} +1 -1
  30. package/dist/client/assets/{TodoView-xcOjHZm1.js → TodoView-y11oUvjw.js} +1 -1
  31. package/dist/client/assets/{WorkflowNodeEditor-DZ2WZ-ve.js → WorkflowNodeEditor-DwY4XzNB.js} +1 -1
  32. package/dist/client/assets/{agent-import-generation-0tkW9WMt.js → agent-import-generation-B_UNOAWY.js} +1 -1
  33. package/dist/client/assets/{architectureDiagram-3BPJPVTR-DP9_UvPn.js → architectureDiagram-3BPJPVTR-BpA9_eoq.js} +1 -1
  34. package/dist/client/assets/{blockDiagram-GPEHLZMM-EgKwYMgQ.js → blockDiagram-GPEHLZMM-DFFA8tCG.js} +1 -1
  35. package/dist/client/assets/{c4Diagram-AAUBKEIU-Dzv0-_xt.js → c4Diagram-AAUBKEIU-Fzhn0dG7.js} +1 -1
  36. package/dist/client/assets/channel-C9C9Rxgn.js +1 -0
  37. package/dist/client/assets/{chunk-2J33WTMH-CNmFmJlQ.js → chunk-2J33WTMH-Bi3oPWBJ.js} +1 -1
  38. package/dist/client/assets/{chunk-4BX2VUAB-DwtF2sl5.js → chunk-4BX2VUAB-UmeI_To2.js} +1 -1
  39. package/dist/client/assets/{chunk-55IACEB6-DoDurT6F.js → chunk-55IACEB6-BvNWeThd.js} +1 -1
  40. package/dist/client/assets/{chunk-727SXJPM-DB87A9uH.js → chunk-727SXJPM-OHmjSQwz.js} +1 -1
  41. package/dist/client/assets/{chunk-AQP2D5EJ-ClTDkMp1.js → chunk-AQP2D5EJ-DCi9eQaE.js} +1 -1
  42. package/dist/client/assets/{chunk-FMBD7UC4-BhY2wlYr.js → chunk-FMBD7UC4-CelGyE3R.js} +1 -1
  43. package/dist/client/assets/{chunk-ND2GUHAM-BB8mzlmk.js → chunk-ND2GUHAM-DespM-6E.js} +1 -1
  44. package/dist/client/assets/{chunk-QZHKN3VN-D81qQMGf.js → chunk-QZHKN3VN-DLkr_Pz0.js} +1 -1
  45. package/dist/client/assets/classDiagram-4FO5ZUOK-BGVE_u03.js +1 -0
  46. package/dist/client/assets/classDiagram-v2-Q7XG4LA2-BGVE_u03.js +1 -0
  47. package/dist/client/assets/{cose-bilkent-S5V4N54A-CCFUHBJX.js → cose-bilkent-S5V4N54A-I7MlbwDq.js} +1 -1
  48. package/dist/client/assets/{dagre-BM42HDAG-xr9MLhRA.js → dagre-BM42HDAG-DRwLRj5m.js} +1 -1
  49. package/dist/client/assets/{dashboard-view-oTFt91I3.js → dashboard-view-CIwO3ODB.js} +1 -1
  50. package/dist/client/assets/{dashboard-view-RXKFv3QV.js → dashboard-view-CSxVzmpI.js} +1 -1
  51. package/dist/client/assets/{dashboard-view-lqwQ-pMo.js → dashboard-view-Gb436HIO.js} +1 -1
  52. package/dist/client/assets/{diagram-2AECGRRQ-mN_tYnBs.js → diagram-2AECGRRQ-BuWGDbh_.js} +1 -1
  53. package/dist/client/assets/{diagram-5GNKFQAL-xznlq-5C.js → diagram-5GNKFQAL-hG6N6ArX.js} +1 -1
  54. package/dist/client/assets/{diagram-KO2AKTUF-ClRlj1ZA.js → diagram-KO2AKTUF-BSXKqQJA.js} +1 -1
  55. package/dist/client/assets/{diagram-LMA3HP47-CNiPIQqT.js → diagram-LMA3HP47-wNefeM2W.js} +1 -1
  56. package/dist/client/assets/{diagram-OG6HWLK6-DwQD-W0P.js → diagram-OG6HWLK6-nA8v_k_H.js} +1 -1
  57. package/dist/client/assets/{erDiagram-TEJ5UH35-ZXkiCm8S.js → erDiagram-TEJ5UH35-Dwnvvp2x.js} +1 -1
  58. package/dist/client/assets/{flowDiagram-I6XJVG4X-DI5haEH9.js → flowDiagram-I6XJVG4X-FS4W3U0O.js} +1 -1
  59. package/dist/client/assets/{folder-open-NenyCkrb.js → folder-open-C3GXrzUo.js} +1 -1
  60. package/dist/client/assets/{ganttDiagram-6RSMTGT7-BRHJpfOZ.js → ganttDiagram-6RSMTGT7-D95bgKFE.js} +1 -1
  61. package/dist/client/assets/{gitGraphDiagram-PVQCEYII-iXT_YBAZ.js → gitGraphDiagram-PVQCEYII-zqFbK6on.js} +1 -1
  62. package/dist/client/assets/{index-CrAJA2v0.js → index-BWRNl9tu.js} +274 -274
  63. package/dist/client/assets/index-DRZtPb3i.css +1 -0
  64. package/dist/client/assets/{infoDiagram-5YYISTIA-BzkuQtqZ.js → infoDiagram-5YYISTIA-BBELQBNE.js} +1 -1
  65. package/dist/client/assets/{ishikawaDiagram-YF4QCWOH-5hnPG_98.js → ishikawaDiagram-YF4QCWOH-bSZkIXd-.js} +1 -1
  66. package/dist/client/assets/{journeyDiagram-JHISSGLW-Co3fnxbr.js → journeyDiagram-JHISSGLW-8wIimXRT.js} +1 -1
  67. package/dist/client/assets/{kanban-definition-UN3LZRKU-PZFl_y-h.js → kanban-definition-UN3LZRKU-DMj6kFc5.js} +1 -1
  68. package/dist/client/assets/{mermaid.core-CJRKeU9s.js → mermaid.core-UDKENpfo.js} +4 -4
  69. package/dist/client/assets/{mindmap-definition-RKZ34NQL-B15e2rkx.js → mindmap-definition-RKZ34NQL-CJGH0VYQ.js} +1 -1
  70. package/dist/client/assets/{pieDiagram-4H26LBE5-kGF592D_.js → pieDiagram-4H26LBE5-B3cZLcOe.js} +1 -1
  71. package/dist/client/assets/{puzzle-Dgc4yzjO.js → puzzle-D85LM19g.js} +1 -1
  72. package/dist/client/assets/{quadrantDiagram-W4KKPZXB-CUShcj70.js → quadrantDiagram-W4KKPZXB-bR1CBrVi.js} +1 -1
  73. package/dist/client/assets/{requirementDiagram-4Y6WPE33-D2k4nLqQ.js → requirementDiagram-4Y6WPE33-DnyJ74cE.js} +1 -1
  74. package/dist/client/assets/{sankeyDiagram-5OEKKPKP-CA03BxhT.js → sankeyDiagram-5OEKKPKP-ttv4kq0z.js} +1 -1
  75. package/dist/client/assets/{sequenceDiagram-3UESZ5HK-DDuFN2Pi.js → sequenceDiagram-3UESZ5HK-FiUBFoWw.js} +1 -1
  76. package/dist/client/assets/{shield-alert-BlYWqolM.js → shield-alert-D3IVOJV1.js} +1 -1
  77. package/dist/client/assets/{standing-instructions-template-ujl7UARV.js → standing-instructions-template-CWY_pFh6.js} +1 -1
  78. package/dist/client/assets/{stateDiagram-AJRCARHV-yJOkHK4Q.js → stateDiagram-AJRCARHV-BcsYRB77.js} +1 -1
  79. package/dist/client/assets/{stateDiagram-v2-BHNVJYJU-BgErHdAN.js → stateDiagram-v2-BHNVJYJU-DsBPFwnG.js} +1 -1
  80. package/dist/client/assets/{timeline-definition-PNZ67QCA-BUiDQw21.js → timeline-definition-PNZ67QCA-B8rOOxEj.js} +1 -1
  81. package/dist/client/assets/{upload-DmlZvpMp.js → upload-WqSaqC__.js} +1 -1
  82. package/dist/client/assets/{users-Cx6ACt-W.js → users-CPP-3wxQ.js} +1 -1
  83. package/dist/client/assets/{vennDiagram-CIIHVFJN-DqyIjBY8.js → vennDiagram-CIIHVFJN-CO0LSFj2.js} +1 -1
  84. package/dist/client/assets/{wardley-L42UT6IY-C93tXAKH.js → wardley-L42UT6IY-QHbclXGi.js} +1 -1
  85. package/dist/client/assets/{wardleyDiagram-YWT4CUSO-DmH6-GJV.js → wardleyDiagram-YWT4CUSO-DAsnTuM9.js} +1 -1
  86. package/dist/client/assets/{xychartDiagram-2RQKCTM6-BPwjE4Su.js → xychartDiagram-2RQKCTM6-BmG5EEoO.js} +1 -1
  87. package/dist/client/index.html +2 -2
  88. package/dist/client/version.json +1 -1
  89. package/dist/extension.js +216 -350
  90. package/package.json +1 -1
  91. package/dist/client/assets/SettingsModal-KnUd0942.css +0 -1
  92. package/dist/client/assets/channel-BdTAZBkX.js +0 -1
  93. package/dist/client/assets/classDiagram-4FO5ZUOK-CuoGzmwo.js +0 -1
  94. package/dist/client/assets/classDiagram-v2-Q7XG4LA2-CuoGzmwo.js +0 -1
  95. package/dist/client/assets/index-CITV-wDq.css +0 -1
@@ -159300,7 +159300,11 @@ async function createFnAgent2(options) {
159300
159300
  }
159301
159301
  });
159302
159302
  attachSessionIdentity(promptableSession);
159303
- return { session: promptableSession, sessionFile: promptableSession.sessionFile };
159303
+ return {
159304
+ session: promptableSession,
159305
+ sessionFile: promptableSession.sessionFile,
159306
+ settleFallbackDispatch: async () => void 0
159307
+ };
159304
159308
  }
159305
159309
  var execAsync10, RTK_ACCEPTED_REWRITE_EXIT_CODES, RTK_EXPECTED_PASSTHROUGH_EXIT_CODES, RTK_EXPECTED_FAIL_OPEN_ERROR_CODES, RTK_REWRITE_MAX_BUFFER_BYTES, hostExtensionPaths, FN_MEMORY_APPEND_TOOL_NAME, FUSION_SHUTDOWN_WRAP_FLAG, promptWithFallbackInFlight, COMPACTION_FALLBACK_INSTRUCTIONS, MAX_COMPACTED_PROMPT_MEMORY_CHARS, MAX_COMPACTED_SUBTASK_GUIDANCE_CHARS, MAX_COMPACTED_ATTACHMENTS_CHARS, MAX_COMPACTED_EXISTING_SPEC_CHARS, MAX_COMPACTED_TASK_PROMPT_CHARS, MAX_COMPACTED_USER_COMMENTS_CHARS, ModelFallbackExhaustedError, GATE_BYPASS_TOOL_NAMES, TOOL_OUTPUT_BUDGET_OVERRIDES;
159306
159310
  var init_pi = __esm({
@@ -159424,7 +159428,8 @@ function wrapPluginRuntime(instance, runtimeId, runtimeName) {
159424
159428
  const result = await adapter.createSession(options);
159425
159429
  return {
159426
159430
  session: result.session ?? result,
159427
- sessionFile: result.sessionFile
159431
+ sessionFile: result.sessionFile,
159432
+ settleFallbackDispatch: result.settleFallbackDispatch
159428
159433
  };
159429
159434
  }
159430
159435
  throw new Error(`Plugin runtime "${runtimeId}" does not implement createSession`);
@@ -159533,11 +159538,15 @@ var init_runtime_resolution = __esm({
159533
159538
  name = "Default PI Runtime";
159534
159539
  async createSession(options) {
159535
159540
  const { toolOutputMaxChars, mcpServers, ...agentOptions } = options;
159536
- return createFnAgent2({
159541
+ const result = await createFnAgent2({
159537
159542
  ...agentOptions,
159538
159543
  toolOutputMaxChars,
159539
159544
  mcpServers: normalizeAgentRuntimeMcpServers(mcpServers)
159540
159545
  });
159546
+ return {
159547
+ ...result,
159548
+ settleFallbackDispatch: result.settleFallbackDispatch ?? (async () => void 0)
159549
+ };
159541
159550
  }
159542
159551
  async promptWithFallback(session, prompt, options) {
159543
159552
  return promptWithFallback(session, prompt, options);
@@ -160404,6 +160413,7 @@ async function createResolvedAgentSession(options) {
160404
160413
  return {
160405
160414
  session: result.session,
160406
160415
  sessionFile: result.sessionFile,
160416
+ settleFallbackDispatch: result.settleFallbackDispatch,
160407
160417
  runtimeId: resolved.runtimeId,
160408
160418
  wasConfigured: resolved.wasConfigured
160409
160419
  };
@@ -190840,6 +190850,25 @@ var Scheduler = class {
190840
190850
  });
190841
190851
  }
190842
190852
  }
190853
+ if (from === parked.wip && to2 === parked.hold) {
190854
+ if (source === "engine") {
190855
+ this.recentEngineTodoRequeues.set(task.id, task.columnMovedAt ?? (/* @__PURE__ */ new Date()).toISOString());
190856
+ } else {
190857
+ this.recentEngineTodoRequeues.delete(task.id);
190858
+ }
190859
+ } else if (to2 === parked.review || parked.terminal.has(to2)) {
190860
+ this.recentEngineTodoRequeues.delete(task.id);
190861
+ if (task.dispatchStormCount != null || task.lastDispatchAt != null || task.executeRequeueLoopCount != null || task.executeRequeueLoopSignature != null) {
190862
+ void this.store.updateTask(task.id, {
190863
+ dispatchStormCount: null,
190864
+ lastDispatchAt: null,
190865
+ executeRequeueLoopCount: null,
190866
+ executeRequeueLoopSignature: null
190867
+ }).catch((error2) => {
190868
+ schedulerLog.warn(`Failed to reset dispatch oscillation state for ${task.id} on move to ${to2}: ${error2 instanceof Error ? error2.message : String(error2)}`);
190869
+ });
190870
+ }
190871
+ }
190843
190872
  const resolvedParked = mergeParkedColumns(await resolveTaskParkedColumns(this.store, task.id), lanes);
190844
190873
  if (resolvedParked.terminal.has(to2)) {
190845
190874
  try {
@@ -190900,25 +190929,6 @@ var Scheduler = class {
190900
190929
  schedulerLog.error(`Failed event-driven blocker reconciliation for ${task.id}`, error2);
190901
190930
  }
190902
190931
  }
190903
- if (from === resolvedParked.wip && to2 === resolvedParked.hold) {
190904
- if (source === "engine") {
190905
- this.recentEngineTodoRequeues.set(task.id, task.columnMovedAt ?? (/* @__PURE__ */ new Date()).toISOString());
190906
- } else {
190907
- this.recentEngineTodoRequeues.delete(task.id);
190908
- }
190909
- } else if (to2 === resolvedParked.review || resolvedParked.terminal.has(to2)) {
190910
- this.recentEngineTodoRequeues.delete(task.id);
190911
- if (task.dispatchStormCount != null || task.lastDispatchAt != null || task.executeRequeueLoopCount != null || task.executeRequeueLoopSignature != null) {
190912
- void this.store.updateTask(task.id, {
190913
- dispatchStormCount: null,
190914
- lastDispatchAt: null,
190915
- executeRequeueLoopCount: null,
190916
- executeRequeueLoopSignature: null
190917
- }).catch((error2) => {
190918
- schedulerLog.warn(`Failed to reset dispatch oscillation state for ${task.id} on move to ${to2}: ${error2 instanceof Error ? error2.message : String(error2)}`);
190919
- });
190920
- }
190921
- }
190922
190932
  if (resolvedParked.terminal.has(to2) || to2 === resolvedParked.hold) {
190923
190933
  schedulerLog.log(`Task moved to ${to2} \u2014 triggering scheduling`);
190924
190934
  this.schedule();
@@ -192692,110 +192702,40 @@ init_src2();
192692
192702
 
192693
192703
  // ../engine/src/execution-block-classifier.ts
192694
192704
  var TASK_ID_RE = /^[A-Z][A-Z0-9]*-\d+$/i;
192695
- var PR_REF_RE = /^(?:pr[:#-]|#|PR-)(\d+)$/i;
192696
- var PR_IN_TEXT_RE = /\bPR\s*#?\s*(\d+)\b/gi;
192697
- var CLAIM_REASON_RE = /check-file-claimed|actively claimed|file[- ]claim|claimed by (?:open )?pr|open pr\s*#?\s*\d+|collision policy|claimed paths?|file claims?/i;
192705
+ var LEGACY_PR_REF_RE = /^(?:pr[:#-]|#|PR-)(\d+)$/i;
192698
192706
  function partitionBlockedByRefs(blockedBy) {
192699
192707
  const taskIds = [];
192700
- const prNumbers = [];
192701
192708
  for (const raw of blockedBy) {
192702
192709
  const id = raw.trim();
192703
192710
  if (!id) continue;
192704
- const prMatch = id.match(PR_REF_RE);
192705
- if (prMatch) {
192706
- prNumbers.push(Number(prMatch[1]));
192707
- continue;
192708
- }
192711
+ if (LEGACY_PR_REF_RE.test(id)) continue;
192709
192712
  if (TASK_ID_RE.test(id)) {
192710
192713
  const m2 = id.toUpperCase().match(/^([A-Z][A-Z0-9]*)-(\d+)$/);
192711
192714
  taskIds.push(m2 ? `${m2[1]}-${m2[2]}` : id.toUpperCase());
192712
- continue;
192713
192715
  }
192714
192716
  }
192715
- return {
192716
- taskIds: [...new Set(taskIds)],
192717
- prNumbers: [...new Set(prNumbers.filter((n) => Number.isFinite(n) && n > 0))]
192718
- };
192717
+ return { taskIds: [...new Set(taskIds)] };
192719
192718
  }
192720
- function extractPrNumbersFromText(text2) {
192721
- const found = [];
192722
- for (const match of text2.matchAll(PR_IN_TEXT_RE)) {
192723
- const n = Number(match[1]);
192724
- if (Number.isFinite(n) && n > 0) found.push(n);
192725
- }
192726
- return [...new Set(found)];
192727
- }
192728
- function isFileClaimBlockedReason(reason) {
192729
- return CLAIM_REASON_RE.test(reason);
192730
- }
192731
- function classifyBlockedExit(reason, blockedBy = []) {
192732
- const trimmed = reason.trim();
192733
- const partitioned = partitionBlockedByRefs(blockedBy);
192734
- const prFromText = extractPrNumbersFromText(trimmed);
192735
- const prNumbers = [.../* @__PURE__ */ new Set([...partitioned.prNumbers, ...prFromText])];
192736
- const isClaim = isFileClaimBlockedReason(trimmed) || prNumbers.length > 0 && /claim/i.test(trimmed);
192737
- if (isClaim || prNumbers.length > 0 && isFileClaimBlockedReason(trimmed)) {
192738
- const externalBlockers = [
192739
- ...prNumbers.map((number4) => ({ kind: "github-pr", number: number4 }))
192740
- ];
192741
- if (externalBlockers.length === 0 && isClaim) {
192742
- externalBlockers.push({
192743
- kind: "file-claim",
192744
- prNumber: prNumbers[0]
192745
- });
192746
- } else if (isClaim) {
192747
- for (const n of prNumbers) {
192748
- if (!externalBlockers.some((b3) => b3.kind === "github-pr" && b3.number === n)) {
192749
- externalBlockers.push({ kind: "github-pr", number: n });
192750
- }
192751
- }
192752
- if (prNumbers.length === 0) {
192753
- externalBlockers.push({ kind: "file-claim" });
192754
- }
192755
- }
192756
- const thrashSignature = [
192757
- "file-claim",
192758
- ...prNumbers.map((n) => `pr:${n}`).sort(),
192759
- ...partitioned.taskIds.slice().sort()
192760
- ].join("|");
192761
- return {
192762
- allowAutoReplan: false,
192763
- class: "file-claim",
192764
- externalBlockers,
192765
- thrashSignature: thrashSignature || "file-claim",
192766
- prNumbers
192767
- };
192768
- }
192769
- if (partitioned.taskIds.length > 0) {
192719
+ function classifyBlockedExit(_reason, blockedBy = []) {
192720
+ const { taskIds } = partitionBlockedByRefs(blockedBy);
192721
+ if (taskIds.length > 0) {
192770
192722
  return {
192771
192723
  allowAutoReplan: false,
192772
192724
  class: "external",
192773
- externalBlockers: [],
192774
- thrashSignature: `tasks:${partitioned.taskIds.slice().sort().join(",")}`,
192775
- prNumbers
192776
- };
192777
- }
192778
- if (prNumbers.length > 0) {
192779
- return {
192780
- allowAutoReplan: false,
192781
- class: "external",
192782
- externalBlockers: prNumbers.map((number4) => ({ kind: "github-pr", number: number4 })),
192783
- thrashSignature: prNumbers.map((n) => `pr:${n}`).sort().join("|"),
192784
- prNumbers
192725
+ thrashSignature: `tasks:${taskIds.slice().sort().join(",")}`
192785
192726
  };
192786
192727
  }
192787
192728
  return {
192788
192729
  allowAutoReplan: true,
192789
192730
  class: "plan-defect",
192790
- externalBlockers: [],
192791
- thrashSignature: "plan-defect",
192792
- prNumbers: []
192731
+ thrashSignature: "plan-defect"
192793
192732
  };
192794
192733
  }
192795
192734
  var BLOCKED_THRASH_LIMIT = 3;
192796
192735
  var BLOCKED_THRASH_WINDOW_MS = 60 * 60 * 1e3;
192797
192736
  function countBlockedThrashHits(log41, signature, nowMs = Date.now(), windowMs = BLOCKED_THRASH_WINDOW_MS) {
192798
192737
  if (!log41?.length) return 0;
192738
+ if (signature === "plan-defect") return 0;
192799
192739
  const cutoff = nowMs - windowMs;
192800
192740
  let count2 = 0;
192801
192741
  for (const entry of log41) {
@@ -192807,18 +192747,7 @@ function countBlockedThrashHits(log41, signature, nowMs = Date.now(), windowMs =
192807
192747
  continue;
192808
192748
  }
192809
192749
  }
192810
- if (signature === "plan-defect") continue;
192811
- if (signature.includes("file-claim") || signature.startsWith("pr:") || signature.includes("|pr:")) {
192812
- if (isFileClaimBlockedReason(action) || /PR\s*#?\s*\d+/i.test(action) || action.includes("durable external block")) {
192813
- count2 += 1;
192814
- continue;
192815
- }
192816
- }
192817
192750
  for (const part of signature.split("|")) {
192818
- if (part.startsWith("pr:") && action.includes(`PR #${part.slice(3)}`)) {
192819
- count2 += 1;
192820
- break;
192821
- }
192822
192751
  if (part.startsWith("tasks:") && part.slice(6).split(",").some((id) => id && action.includes(id))) {
192823
192752
  count2 += 1;
192824
192753
  break;
@@ -192827,28 +192756,18 @@ function countBlockedThrashHits(log41, signature, nowMs = Date.now(), windowMs =
192827
192756
  }
192828
192757
  return count2;
192829
192758
  }
192830
- function isDurableBlockedError(error2) {
192831
- if (!error2?.startsWith("BLOCKED:")) return false;
192832
- const reason = error2.slice("BLOCKED:".length).trim();
192833
- const classification = classifyBlockedExit(reason, []);
192834
- return !classification.allowAutoReplan;
192835
- }
192836
192759
  function isDurableBlockedTask(task) {
192837
- if (task.status === "failed" && isDurableBlockedError(task.error)) return true;
192838
192760
  const meta2 = task.sourceMetadata;
192839
192761
  if (!meta2 || typeof meta2 !== "object") return false;
192840
- if (meta2.blockedClass === "file-claim" || meta2.blockedClass === "external") {
192841
- if (task.status === "failed" || task.status === "needs-replan") return true;
192842
- }
192843
- const blockers = meta2.externalBlockers;
192844
- return Array.isArray(blockers) && blockers.length > 0 && task.status === "failed";
192762
+ if (meta2.blockedClass !== "external") return false;
192763
+ return task.status === "failed" || task.status === "needs-replan";
192845
192764
  }
192846
192765
  function buildExternalBlockMetadataPatch(classification, thrashCount) {
192847
192766
  return {
192848
192767
  blockedClass: classification.class,
192849
192768
  blockedThrashSignature: classification.thrashSignature,
192850
192769
  blockedThrashCount: thrashCount,
192851
- externalBlockers: classification.externalBlockers
192770
+ externalBlockers: []
192852
192771
  };
192853
192772
  }
192854
192773
 
@@ -208426,7 +208345,6 @@ var SelfHealingManager = class _SelfHealingManager extends SelfHealingGitEvidenc
208426
208345
  { name: "reconcile-done-task-integrity", fn: () => this.reconcileDoneTaskIntegrity() },
208427
208346
  { name: "reconcile-stale-merger-status", fn: () => this.reconcileStaleMergerStatus() },
208428
208347
  { name: "reconcile-stale-duplicate-decision", fn: () => this.reconcileStaleDuplicateDecisionPause() },
208429
- { name: "reconcile-external-pr-blockers", fn: () => this.reconcileExternalPrBlockers() },
208430
208348
  // FNXC:OrphanedPendingSteps 2026-07-22-16:35 (FN-8492 review follow-up): also
208431
208349
  // steady-state — a step session can die without an engine restart, and startup-only
208432
208350
  // cadence left that case riding the 3×30-min stall escalator to a deadlock park.
@@ -217323,71 +217241,12 @@ var SelfHealingManager = class _SelfHealingManager extends SelfHealingGitEvidenc
217323
217241
  }
217324
217242
  }
217325
217243
  /*
217326
- FNXC:HonestBlockedExit 2026-08-02-01:30:
217327
- Durable parks store github-pr externalBlockers (FN-8700 file-claim). When every blocking
217328
- PR is MERGED or CLOSED, clear the failed park so the scheduler can re-dispatch. Fail-soft
217329
- when gh is unavailable leave the park for the operator.
217244
+ FNXC:HonestBlockedExit 2026-08-02-23:59 (operator decision — FN-8728 vs PR #2398):
217245
+ The FN-8700 `reconcile-external-pr-blockers` sweep (gh-backed clearing of PR-claim parks)
217246
+ is REMOVED with the whole PR/file-claim blocking mechanism. Open PRs are never blockers;
217247
+ file-scope conflicts are arbitrated only by Fusion's own board. Legacy PR-claim parks are
217248
+ no longer honored by isDurableBlockedTask, so normal recovery paths reclaim them.
217330
217249
  */
217331
- async reconcileExternalPrBlockers() {
217332
- try {
217333
- if (!await isGhAvailable()) {
217334
- log34.debug("reconcile-external-pr-blockers skipped \u2014 gh unavailable");
217335
- return 0;
217336
- }
217337
- const tasks2 = await this.store.listTasks({ slim: true, includeArchived: false, limit: 500 });
217338
- let cleared = 0;
217339
- for (const task of tasks2.slice(0, 80)) {
217340
- if (task.status !== "failed" || !task.error?.startsWith("BLOCKED:")) continue;
217341
- const meta2 = task.sourceMetadata;
217342
- const blockers = meta2?.externalBlockers;
217343
- if (!Array.isArray(blockers) || blockers.length === 0) continue;
217344
- const prNumbers = blockers.map((b3) => b3 && typeof b3 === "object" && b3.kind === "github-pr" ? Number(b3.number) : NaN).filter((n) => Number.isFinite(n) && n > 0);
217345
- if (prNumbers.length === 0) continue;
217346
- let allResolved = true;
217347
- for (const n of prNumbers) {
217348
- try {
217349
- const pr2 = await runGhJsonAsync(
217350
- ["pr", "view", String(n), "--json", "state,mergedAt"],
217351
- { timeoutMs: 15e3 }
217352
- );
217353
- const state = String(pr2?.state ?? "").toUpperCase();
217354
- const merged = Boolean(pr2?.mergedAt) || state === "MERGED";
217355
- const closed = state === "CLOSED" || state === "MERGED";
217356
- if (!merged && !closed) {
217357
- allResolved = false;
217358
- break;
217359
- }
217360
- } catch {
217361
- allResolved = false;
217362
- break;
217363
- }
217364
- }
217365
- if (!allResolved) continue;
217366
- await this.store.updateTask(task.id, {
217367
- status: null,
217368
- error: null,
217369
- sourceMetadataPatch: {
217370
- externalBlockers: [],
217371
- blockedClass: null,
217372
- blockedThrashSignature: null,
217373
- blockedThrashCount: null,
217374
- externalPrBlockersClearedAt: (/* @__PURE__ */ new Date()).toISOString(),
217375
- externalPrBlockersCleared: prNumbers
217376
- }
217377
- });
217378
- await this.store.logEntry(
217379
- task.id,
217380
- `Auto-recovered: external PR blocker(s) ${prNumbers.map((n) => `#${n}`).join(", ")} merged/closed \u2014 cleared durable BLOCKED park for re-dispatch`
217381
- );
217382
- log34.log(`Cleared durable PR block for ${task.id} (prs=${prNumbers.join(",")})`);
217383
- cleared += 1;
217384
- }
217385
- return cleared;
217386
- } catch (error2) {
217387
- log34.warn(`reconcile-external-pr-blockers failed: ${error2 instanceof Error ? error2.message : String(error2)}`);
217388
- return 0;
217389
- }
217390
- }
217391
217250
  async resolveExplicitDuplicateMarkerTasks() {
217392
217251
  try {
217393
217252
  const settings = await this.store.getSettings();
@@ -219772,7 +219631,7 @@ If you have just finished a step's work, immediately call \`fn_task_update\` to
219772
219631
 
219773
219632
  The user is not watching this conversation in real-time. They will read the final result. Asking permission wastes a full retry cycle and may orphan committed work.
219774
219633
 
219775
- **Cannot proceed \u2014 the honest blocked exit.** If the work genuinely cannot be finished (an upstream API break, a missing prerequisite task, an unresolvable external error, or a **file claim / open PR collision**), call \`fn_task_done(outcome="blocked", reason="<concrete blocker + what would unblock it>", blockedBy=["FN-XXXX"] or ["pr:2398"])\`. File-claim reasons must name the PR (e.g. "actively claimed by PR #2398" + \`blockedBy:["pr:2398"]\`). That parks durable failed WITHOUT auto-replan so the engine does not thrash; task IDs requeue when those tasks complete; PR refs clear when the PR merges/closes. Do NOT skip remaining steps to fake completion.
219634
+ **Cannot proceed \u2014 the honest blocked exit.** If the work genuinely cannot be finished (an upstream API break, a missing prerequisite task, or an unresolvable external error), call \`fn_task_done(outcome="blocked", reason="<concrete blocker + what would unblock it>", blockedBy=["FN-XXXX"])\`. That parks durable failed WITHOUT auto-replan so the engine does not thrash; task IDs requeue when those tasks complete. Blockers must be Fusion board tasks \u2014 do NOT treat open GitHub PRs touching the same files as blockers; other PRs are not claims on your file scope. Do NOT skip remaining steps to fake completion.
219776
219635
  This is THE correct action when you are stuck \u2014 do NOT instead mark the remaining steps \`skipped\` and call \`fn_task_done\` to make the task look finished. Skipping steps to escape a blocker launders a failure into \`done\` and is never the right move. (\`skipped\` remains valid only for the stale-premise path below, when the requested work is already present on HEAD.) Never write the blocker as plain prose.
219777
219636
 
219778
219637
  ## How to work
@@ -227231,75 +227090,16 @@ Graph value: ${failureValue}` : ""}`, budget.key),
227231
227090
  );
227232
227091
  }
227233
227092
  }
227234
- /*
227235
- FNXC:HonestBlockedExit 2026-08-02-01:30:
227236
- Agents often log `BLOCKED: … actively claimed by PR #N` without calling fn_task_done.
227237
- Without promotion, incomplete-step graph resume requeues the card and thrash restarts
227238
- (FN-8700). Promote claim/external blocks to a durable failed park here.
227239
- */
227240
- async parkDurableBlockedFromRecentLog(live) {
227241
- const recent = [...live.log ?? []].reverse().find((entry) => {
227242
- const action = entry.action ?? "";
227243
- return action.startsWith("BLOCKED:") || action.includes("durable external block");
227244
- });
227245
- if (!recent?.action) return false;
227246
- const reason = recent.action.startsWith("BLOCKED:") ? recent.action.slice("BLOCKED:".length).trim() : recent.action;
227247
- const classification = classifyBlockedExit(reason, []);
227248
- if (classification.allowAutoReplan) return false;
227249
- const thrashCount = countBlockedThrashHits(live.log, classification.thrashSignature) + 1;
227250
- const thrashExhausted = thrashCount >= BLOCKED_THRASH_LIMIT;
227251
- const parkError = thrashExhausted ? `BLOCKED: ${reason} [thrash-exhausted after ${thrashCount} identical durable blocks]` : `BLOCKED: ${reason}`;
227252
- const metaPatch = buildExternalBlockMetadataPatch(classification, thrashCount);
227253
- await this.store.updateTask(live.id, {
227254
- status: "failed",
227255
- error: parkError,
227256
- paused: false,
227257
- pausedByAgentId: null,
227258
- sourceMetadataPatch: metaPatch
227259
- }, this.getRunContextFor(live.id));
227260
- await this.store.logEntry(
227261
- live.id,
227262
- thrashExhausted ? `${parkError} \u2014 promoted from session log; thrash-exhausted, no auto-requeue` : `${parkError} \u2014 promoted from session log to durable external block park (class=${classification.class})`,
227263
- void 0,
227264
- this.getRunContextFor(live.id)
227265
- );
227266
- await this.store.recordRunAuditEvent?.({
227267
- taskId: live.id,
227268
- agentId: "executor",
227269
- runId: generateSyntheticRunId("execution-blocked-log-promote", live.id),
227270
- domain: "database",
227271
- mutationType: "task:execution-blocked-parked",
227272
- target: live.id,
227273
- metadata: {
227274
- taskId: live.id,
227275
- blockedBy: [],
227276
- hasReason: true,
227277
- parkedAs: "failed",
227278
- blockedClass: classification.class,
227279
- thrashCount,
227280
- thrashExhausted,
227281
- prNumbers: classification.prNumbers,
227282
- source: "session-log-promote"
227283
- }
227284
- });
227285
- executorLog.warn(
227286
- `${live.id}: promoted durable BLOCKED from session log (class=${classification.class}; thrash=${thrashCount})`
227287
- );
227288
- return true;
227289
- }
227290
227093
  async routeGraphFailureToExecutionResume(live, failedNode, failureValue, resumeLanesMemo) {
227291
227094
  if (live.deletedAt) return false;
227292
227095
  if (live.paused || live.userPaused === true) return false;
227293
227096
  if ((await resolveTerminalColumnsFor(this.store, live.id)).includes(live.column)) return false;
227294
- if (isDurableBlockedTask(live) || isDurableBlockedError(live.error)) {
227097
+ if (isDurableBlockedTask(live)) {
227295
227098
  executorLog.log(
227296
- `${live.id}: graph failure resume skipped \u2014 durable BLOCKED park honored (class from error/metadata)`
227099
+ `${live.id}: graph failure resume skipped \u2014 durable BLOCKED park honored (task-dependency block)`
227297
227100
  );
227298
227101
  return false;
227299
227102
  }
227300
- if (await this.parkDurableBlockedFromRecentLog(live)) {
227301
- return false;
227302
- }
227303
227103
  if (failedNode === COMPLETION_SUMMARY_NODE_ID) return false;
227304
227104
  const incompleteSteps = hasNonTerminalWorkflowSteps(live);
227305
227105
  const implementationIncompleteMergeFailure = this.isMergeGraphFailure(failedNode) && failureValue === "implementation-incomplete";
@@ -231046,10 +230846,10 @@ ${summary}`,
231046
230846
  { description: '"completed" (default) finishes the task; "blocked" honestly parks it as failed because the work cannot proceed. Use "blocked" instead of skipping steps + completing when you are stuck.' }
231047
230847
  )),
231048
230848
  blockedBy: Type6.Optional(Type6.Array(Type6.String(), {
231049
- description: 'When outcome="blocked": task IDs (e.g. ["FN-8145"]) and/or PR refs (e.g. ["pr:2398"]) that must clear before this task can proceed. Task IDs become real dependency edges; PR refs are stored as external blockers (durable park until the PR merges/closes).'
230849
+ description: 'When outcome="blocked": Fusion task IDs (e.g. ["FN-8145"]) that must complete before this task can proceed. Task IDs become real dependency edges. Open GitHub PRs are not valid blockers.'
231050
230850
  })),
231051
230851
  reason: Type6.Optional(Type6.String({
231052
- description: 'Required when outcome="blocked": concrete explanation of what is blocking the work and what is needed to unblock it. File-claim / open-PR collisions should name the PR (e.g. "actively claimed by PR #2398").'
230852
+ description: 'Required when outcome="blocked": concrete explanation of what is blocking the work and what is needed to unblock it.'
231053
230853
  }))
231054
230854
  }),
231055
230855
  execute: async (_id, params) => {
@@ -231108,7 +230908,7 @@ ${summary}`,
231108
230908
  }, this.getRunContextFor(taskId));
231109
230909
  await store.logEntry(
231110
230910
  taskId,
231111
- thrashExhausted ? `${parkError} \u2014 durable external block thrash-exhausted (signature=${classification.thrashSignature}); parked failed, no auto-requeue` : classification.externalBlockers.length > 0 ? `${parkError} \u2014 durable external block (${classification.class}; pr=${classification.prNumbers.join(",") || "none"}; tasks=${blockedByIds.join(",") || "none"}) \u2014 parked failed (honest blocked exit; steps preserved)` : `${parkError} \u2014 recorded dependencies: ${blockedByIds.join(", ")} \u2014 parked failed (honest blocked exit; steps preserved)`,
230911
+ thrashExhausted ? `${parkError} \u2014 durable external block thrash-exhausted (signature=${classification.thrashSignature}); parked failed, no auto-requeue` : `${parkError} \u2014 recorded dependencies: ${blockedByIds.join(", ")} \u2014 parked failed (honest blocked exit; steps preserved)`,
231112
230912
  void 0,
231113
230913
  this.getRunContextFor(taskId)
231114
230914
  );
@@ -231127,18 +230927,17 @@ ${summary}`,
231127
230927
  parkedAs: autoReplanPark ? "auto-replan" : "failed",
231128
230928
  blockedClass: classification.class,
231129
230929
  thrashCount,
231130
- thrashExhausted,
231131
- prNumbers: classification.prNumbers
230930
+ thrashExhausted
231132
230931
  }
231133
230932
  });
231134
230933
  await this.persistTokenUsage(taskId);
231135
230934
  executorLog.log(
231136
- `\u26D4 ${taskId} ${autoReplanPark ? "parked for automatic replan via blocked exit (plan defect, no dependencies)" : thrashExhausted ? `parked failed via blocked thrash-exhaustion (class=${classification.class})` : `parked failed via durable blocked exit (class=${classification.class}; blockedBy tasks: ${blockedByIds.join(", ") || "none"}; pr: ${classification.prNumbers.join(",") || "none"})`}`
230935
+ `\u26D4 ${taskId} ${autoReplanPark ? "parked for automatic replan via blocked exit (plan defect, no dependencies)" : thrashExhausted ? `parked failed via blocked thrash-exhaustion (class=${classification.class})` : `parked failed via durable blocked exit (class=${classification.class}; blockedBy tasks: ${blockedByIds.join(", ") || "none"})`}`
231137
230936
  );
231138
230937
  return {
231139
230938
  content: [{
231140
230939
  type: "text",
231141
- text: autoReplanPark ? "Task parked as blocked with no external/file-claim blocker \u2014 queued for automatic replan so the plan can resolve the conflict. Steps left in their true statuses; no completion recorded." : thrashExhausted ? "Task parked as blocked (failed) after repeated identical durable blocks \u2014 no further automatic retries. Resolve the external claim/PR or replan manually." : blockedByIds.length > 0 ? `Task parked as blocked (failed). Recorded ${blockedByIds.length} blocking task dependency(ies); it will requeue once they complete. Steps left in their true statuses; no completion recorded.` : classification.prNumbers.length > 0 ? `Task parked as blocked (failed) on open PR #${classification.prNumbers.join(", #")}. It will not auto-replan or re-execute until that claim clears or an operator retries. Steps preserved.` : "Task parked as blocked (failed) on a durable external/file-claim blocker. No automatic replan. Steps preserved."
230940
+ text: autoReplanPark ? "Task parked as blocked with no blocking task dependencies \u2014 queued for automatic replan so the plan can resolve the conflict. Steps left in their true statuses; no completion recorded." : thrashExhausted ? "Task parked as blocked (failed) after repeated identical durable blocks \u2014 no further automatic retries. Resolve the blocking tasks or replan manually." : `Task parked as blocked (failed). Recorded ${blockedByIds.length} blocking task dependency(ies); it will requeue once they complete. Steps left in their true statuses; no completion recorded.`
231142
230941
  }],
231143
230942
  details: {}
231144
230943
  };
@@ -243299,6 +243098,7 @@ init_transient_error_detector();
243299
243098
  init_rate_limit_retry();
243300
243099
  init_agent_tools();
243301
243100
  import { exec as exec42 } from "node:child_process";
243101
+ import { existsSync as existsSync67 } from "node:fs";
243302
243102
  import { readFile as readFile40, writeFile as writeFile29, rm as rm12 } from "node:fs/promises";
243303
243103
  import { join as join78 } from "node:path";
243304
243104
  import { promisify as promisify57 } from "node:util";
@@ -243600,6 +243400,8 @@ var TriageProcessor = class _TriageProcessor {
243600
243400
  unregisterAdmissionProvider = null;
243601
243401
  /** Timestamps when tasks entered the `processing` set, for staleness detection. */
243602
243402
  processingSince = /* @__PURE__ */ new Map();
243403
+ /** Monotonic provenance token for attempt-local planner fallback observation. */
243404
+ planningAttemptSequence = 0;
243603
243405
  wasGlobalPaused = false;
243604
243406
  wasEnginePaused = false;
243605
243407
  idleSemaphoreLeakCandidateSince = null;
@@ -244895,7 +244697,28 @@ You are ${assignedAgent.name}${assignedAgent.title?.trim() ? `, ${assignedAgent.
244895
244697
  }
244896
244698
  await this.store.logEntry(task.id, `Planning session running in task worktree ${planningCwd}`).catch(() => void 0);
244897
244699
  }
244898
- const { session } = await createResolvedAgentSession({
244700
+ const authoritativePromptPath = join78(this.rootDir, promptPath);
244701
+ const planningAttemptBaseline = existsSync67(authoritativePromptPath) ? await readFile40(authoritativePromptPath, "utf-8").catch(() => void 0) : void 0;
244702
+ const planningAttempt = {
244703
+ id: `${task.id}:${++this.planningAttemptSequence}`,
244704
+ baseline: planningAttemptBaseline,
244705
+ fallbackEngaged: false,
244706
+ fallbackSettlements: []
244707
+ };
244708
+ const fallbackObserver = createFallbackModelObserver({
244709
+ agent: "triage",
244710
+ label: "triage",
244711
+ store: this.store,
244712
+ taskId: task.id,
244713
+ taskTitle: task.title
244714
+ });
244715
+ const onFallbackModelUsed = (payload) => {
244716
+ planningAttempt.fallbackEngaged = true;
244717
+ const settlement = Promise.resolve(fallbackObserver(payload));
244718
+ planningAttempt.fallbackSettlements.push(settlement);
244719
+ return settlement;
244720
+ };
244721
+ const { session, settleFallbackDispatch, runtimeId } = await createResolvedAgentSession({
244899
244722
  sessionPurpose: "triage",
244900
244723
  runtimeHint: triageRuntimeHint,
244901
244724
  pluginRunner: this.options.pluginRunner,
@@ -244934,13 +244757,7 @@ You are ${assignedAgent.name}${assignedAgent.title?.trim() ? `, ${assignedAgent.
244934
244757
  taskTitle: task.title,
244935
244758
  actionGateContext: this.buildActionGateContext(task.id, triageRunContext.runId, assignedAgent, settings.defaultAgentPermissionPolicy),
244936
244759
  permanentAgentGating: this.buildPermanentAgentGatingContext(task.id, triageRunContext.runId, assignedAgent, settings.defaultAgentPermissionPolicy),
244937
- onFallbackModelUsed: createFallbackModelObserver({
244938
- agent: "triage",
244939
- label: "triage",
244940
- store: this.store,
244941
- taskId: task.id,
244942
- taskTitle: task.title
244943
- })
244760
+ onFallbackModelUsed
244944
244761
  });
244945
244762
  const modelDesc = formatModelMarkerDetails(describeModel(session), resolvePlanningThinkingLevel(settings, task.planningThinkingLevel ?? task.thinkingLevel));
244946
244763
  planLog.debug(`${task.id}: using model ${modelDesc}`);
@@ -245010,6 +244827,10 @@ You are ${assignedAgent.name}${assignedAgent.title?.trim() ? `, ${assignedAgent.
245010
244827
  agentPrompt,
245011
244828
  imageContents.length > 0 ? { images: imageContents } : void 0
245012
244829
  );
244830
+ const fallbackDispatchBoundaryMissing = typeof settleFallbackDispatch !== "function";
244831
+ if (!fallbackDispatchBoundaryMissing) {
244832
+ await settleFallbackDispatch();
244833
+ }
245013
244834
  checkSessionError(session);
245014
244835
  if (this.pauseAborted.has(task.id)) {
245015
244836
  this.pauseAborted.delete(task.id);
@@ -245102,12 +244923,42 @@ You are ${assignedAgent.name}${assignedAgent.title?.trim() ? `, ${assignedAgent.
245102
244923
  }
245103
244924
  }
245104
244925
  }
245105
- const duplicateReport = { outcome: "parked" };
245106
- if (await this.tryFinalizeExplicitDuplicateMarker(task, written, settings, {
245107
- isReplan,
245108
- feedback
245109
- }, duplicateReport)) {
245110
- this.options.onSpecifyComplete?.(task, duplicateReport);
244926
+ let settledFallbackCount = 0;
244927
+ while (settledFallbackCount < planningAttempt.fallbackSettlements.length) {
244928
+ const pendingSettlements = planningAttempt.fallbackSettlements.slice(settledFallbackCount);
244929
+ settledFallbackCount = planningAttempt.fallbackSettlements.length;
244930
+ await Promise.all(pendingSettlements);
244931
+ }
244932
+ const artifactChangedByAttempt = planningAttempt.baseline !== written;
244933
+ if (fallbackDispatchBoundaryMissing || planningAttempt.fallbackEngaged || !artifactChangedByAttempt) {
244934
+ const failure = fallbackDispatchBoundaryMissing ? `Planner runtime ${runtimeId} did not provide a fallback-dispatch settlement boundary for attempt ${planningAttempt.id}` : planningAttempt.fallbackEngaged ? `Planner fallback engaged during attempt ${planningAttempt.id}` : `Planner did not update the authoritative PROMPT.md during attempt ${planningAttempt.id}`;
244935
+ const decision = computeRecoveryDecision({
244936
+ recoveryRetryCount: task.recoveryRetryCount,
244937
+ nextRecoveryAt: task.nextRecoveryAt
244938
+ });
244939
+ if (decision.shouldRetry) {
244940
+ const retryMessage = `${failure} \u2014 retry ${decision.nextState.recoveryRetryCount}/${MAX_RECOVERY_RETRIES} in ${formatDelay(decision.delayMs)}.`;
244941
+ planLog.warn(`${task.id} ${retryMessage}`);
244942
+ await this.store.logEntry(task.id, retryMessage);
244943
+ await this.updatePlanningStateIfStillCurrent(task, {
244944
+ status: this.restoreStatusAfterInterruptedTriageWork(task),
244945
+ error: null,
244946
+ recoveryRetryCount: decision.nextState.recoveryRetryCount,
244947
+ nextRecoveryAt: decision.nextState.nextRecoveryAt
244948
+ });
244949
+ return;
244950
+ }
244951
+ const failureMessage = `${failure} after ${MAX_RECOVERY_RETRIES} retries. Retry after adjusting the task prompt or model.`;
244952
+ planLog.error(`${task.id} clean planning attempt retry budget exhausted`);
244953
+ await this.store.logEntry(task.id, failureMessage);
244954
+ if (await this.updatePlanningStateIfStillCurrent(task, {
244955
+ status: "failed",
244956
+ error: failureMessage,
244957
+ recoveryRetryCount: null,
244958
+ nextRecoveryAt: null
244959
+ })) {
244960
+ await this.backfillBlankTitleAfterTerminalTriageFailure(task);
244961
+ }
245111
244962
  return;
245112
244963
  }
245113
244964
  const deterministicSpecFailure = await this.validateGeneratedPrompt(task.id, written);
@@ -245149,6 +245000,21 @@ You are ${assignedAgent.name}${assignedAgent.title?.trim() ? `, ${assignedAgent.
245149
245000
  }
245150
245001
  return;
245151
245002
  }
245003
+ const duplicateReport = { outcome: "parked" };
245004
+ if (await this.tryFinalizeExplicitDuplicateMarker(task, written, settings, {
245005
+ isReplan,
245006
+ feedback
245007
+ }, duplicateReport)) {
245008
+ this.options.onSpecifyComplete?.(task, duplicateReport);
245009
+ return;
245010
+ }
245011
+ if (task.recoveryRetryCount != null || task.nextRecoveryAt != null) {
245012
+ await this.updatePlanningStateIfStillCurrent(task, {
245013
+ error: null,
245014
+ recoveryRetryCount: null,
245015
+ nextRecoveryAt: null
245016
+ });
245017
+ }
245152
245018
  const finalizeReport = await this.finalizeApprovedTask(task, written, settings, {
245153
245019
  isReplan,
245154
245020
  feedback
@@ -249882,14 +249748,14 @@ function extractAdvisorAssistantText(session) {
249882
249748
 
249883
249749
  // ../engine/src/overseer-watchdog.ts
249884
249750
  init_logger();
249885
- import { existsSync as existsSync67, readFileSync as readFileSync24 } from "node:fs";
249751
+ import { existsSync as existsSync68, readFileSync as readFileSync24 } from "node:fs";
249886
249752
  import { dirname as dirname34, join as join79, relative as relative17, resolve as resolve40, sep as sep11 } from "node:path";
249887
249753
  import { homedir as homedir12 } from "node:os";
249888
249754
  var log38 = createLogger("overseer-watchdog");
249889
249755
  var WATCHDOG_FILENAMES = ["OVERSEER.md", "WATCHDOG.md"];
249890
249756
  function defaultReadText(path5) {
249891
249757
  try {
249892
- if (!existsSync67(path5)) return null;
249758
+ if (!existsSync68(path5)) return null;
249893
249759
  return readFileSync24(path5, "utf8");
249894
249760
  } catch {
249895
249761
  return null;
@@ -251033,7 +250899,7 @@ init_worktree_paths();
251033
250899
  // ../engine/src/merger-ai-worktree.ts
251034
250900
  init_active_session_registry();
251035
250901
  import { execFile as execFile28 } from "node:child_process";
251036
- import { appendFileSync as appendFileSync2, existsSync as existsSync68, mkdirSync as mkdirSync11, readdirSync as readdirSync12, readFileSync as readFileSync25, realpathSync as realpathSync11, rmSync as rmSync9, statSync as statSync13 } from "node:fs";
250902
+ import { appendFileSync as appendFileSync2, existsSync as existsSync69, mkdirSync as mkdirSync11, readdirSync as readdirSync12, readFileSync as readFileSync25, realpathSync as realpathSync11, rmSync as rmSync9, statSync as statSync13 } from "node:fs";
251037
250903
  import { rm as rm13 } from "node:fs/promises";
251038
250904
  import { tmpdir as tmpdir10 } from "node:os";
251039
250905
  import { isAbsolute as isAbsolute21, join as join80, relative as relative18 } from "node:path";
@@ -251070,7 +250936,7 @@ function isBenignAbsentWorktreeError(err) {
251070
250936
  }
251071
250937
  function ensureAiMergeRootIgnored(projectRootDir, settings) {
251072
250938
  const excludePath = join80(projectRootDir, ".git", "info", "exclude");
251073
- if (!existsSync68(excludePath)) return;
250939
+ if (!existsSync69(excludePath)) return;
251074
250940
  try {
251075
250941
  const current = readFileSync25(excludePath, "utf-8");
251076
250942
  const legacyAiMergeRoot = resolveLegacyAiMergeRootPath(projectRootDir);
@@ -251201,7 +251067,7 @@ async function cleanupAiMergeWorktree(input) {
251201
251067
  const cleanupMetadata = { taskId, mergeRoot: canonicalRoot, requestedMergeRoot: mergeRoot };
251202
251068
  let alreadyAbsent = false;
251203
251069
  if (worktreeAdded) {
251204
- if (!existsSync68(canonicalRoot) && !existsSync68(mergeRoot)) {
251070
+ if (!existsSync69(canonicalRoot) && !existsSync69(mergeRoot)) {
251205
251071
  alreadyAbsent = true;
251206
251072
  await log41(`AI merge cleanup: worktree ${canonicalRoot} was already absent before git removal; treating cleanup as idempotent`);
251207
251073
  await audit.git({ type: "merge:ai-worktree-cleanup", target: canonicalRoot, metadata: { ...cleanupMetadata, phase: "git-remove", success: true, alreadyAbsent: true, idempotent: true, code: "ENOENT" } });