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

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 (100) hide show
  1. package/dist/bin.js +367 -485
  2. package/dist/child-process-worker.js +172 -290
  3. package/dist/client/.vite/manifest.json +266 -266
  4. package/dist/client/assets/{AgentDetailView-CTdyQL11.js → AgentDetailView-DC3B06sE.js} +1 -1
  5. package/dist/client/assets/{AgentPermissionPolicyEditor-DBa7nIQV.js → AgentPermissionPolicyEditor-BtKWfG2h.js} +1 -1
  6. package/dist/client/assets/{AgentsView-B5tss2oK.js → AgentsView-4DiJ2Htx.js} +3 -3
  7. package/dist/client/assets/{ChatView-DAKt1a2L.js → ChatView-1nqZiDRk.js} +1 -1
  8. package/dist/client/assets/{CommandCenter-yPvRo9F6.js → CommandCenter-BHZN2tf0.js} +1 -1
  9. package/dist/client/assets/{DevServerView-BTyS2owH.js → DevServerView-BwPjQxR_.js} +1 -1
  10. package/dist/client/assets/{DirectoryPicker-BjGIupNU.js → DirectoryPicker-40vH29Sy.js} +1 -1
  11. package/dist/client/assets/{DocumentsView-CuxfbpSZ.js → DocumentsView-DI2mQkGF.js} +1 -1
  12. package/dist/client/assets/{EvalsView-DGuy8SHR.js → EvalsView-CTSt1-CF.js} +1 -1
  13. package/dist/client/assets/{ExperimentalAgentOnboardingModal-lK407ZAq.js → ExperimentalAgentOnboardingModal-Z1DJ2xFw.js} +1 -1
  14. package/dist/client/assets/{GoalsView-DAVN0ObR.js → GoalsView-BNyoKq27.js} +1 -1
  15. package/dist/client/assets/{InsightsView-BXQKcNof.js → InsightsView-zSwsbTfk.js} +1 -1
  16. package/dist/client/assets/{MemoryView-D7RKEtV7.js → MemoryView-e7R4rcQY.js} +1 -1
  17. package/dist/client/assets/{PiExtensionsManager-CrOJ57R2.js → PiExtensionsManager-DuZzMGuy.js} +1 -1
  18. package/dist/client/assets/{PluginManager-D7_evt4j.js → PluginManager--lgPTCei.js} +1 -1
  19. package/dist/client/assets/{PullRequestView-C0lRUbth.js → PullRequestView-C9KOlfV-.js} +1 -1
  20. package/dist/client/assets/ReportModal-BiXAfHbg.js +21 -0
  21. package/dist/client/assets/ReportModal-M3uh98sc.css +1 -0
  22. package/dist/client/assets/{ResearchView-e8st2Sxu.js → ResearchView-N4N7-_7C.js} +1 -1
  23. package/dist/client/assets/{SecretsView-BLzckrzb.js → SecretsView-GPt8Mw9d.js} +1 -1
  24. package/dist/client/assets/{SessionTerminal-F_GIj4gh.js → SessionTerminal-D2bnTrLK.js} +1 -1
  25. package/dist/client/assets/SettingsModal-B30rPfYC.css +1 -0
  26. package/dist/client/assets/{SettingsModal-eQbxbkLU.js → SettingsModal-D42Cph4Z.js} +4 -4
  27. package/dist/client/assets/{SettingsModal-XAddgWkF.js → SettingsModal-DLKu3CYU.js} +1 -1
  28. package/dist/client/assets/{SettingsTextareaRow-DVjUHNQp.js → SettingsTextareaRow-terwvp1e.js} +1 -1
  29. package/dist/client/assets/{SetupWizardModal-xID9yIff.js → SetupWizardModal-C-ApHZiL.js} +2 -2
  30. package/dist/client/assets/{SkillsView-BNdMGu6E.js → SkillsView-GYBeq0B-.js} +1 -1
  31. package/dist/client/assets/{TodoView-xcOjHZm1.js → TodoView-_1AmNwaZ.js} +1 -1
  32. package/dist/client/assets/{WorkflowNodeEditor-DZ2WZ-ve.js → WorkflowNodeEditor-CYf2R0ap.js} +1 -1
  33. package/dist/client/assets/{agent-import-generation-0tkW9WMt.js → agent-import-generation-BZPb1nmV.js} +1 -1
  34. package/dist/client/assets/{architectureDiagram-3BPJPVTR-DP9_UvPn.js → architectureDiagram-3BPJPVTR-C_wwNBnV.js} +1 -1
  35. package/dist/client/assets/{blockDiagram-GPEHLZMM-EgKwYMgQ.js → blockDiagram-GPEHLZMM-C7URdbgi.js} +1 -1
  36. package/dist/client/assets/{c4Diagram-AAUBKEIU-Dzv0-_xt.js → c4Diagram-AAUBKEIU-BBc8CXBz.js} +1 -1
  37. package/dist/client/assets/channel-M95yBImf.js +1 -0
  38. package/dist/client/assets/{chunk-2J33WTMH-CNmFmJlQ.js → chunk-2J33WTMH-KfVf5U4u.js} +1 -1
  39. package/dist/client/assets/{chunk-4BX2VUAB-DwtF2sl5.js → chunk-4BX2VUAB-BYzFvVsE.js} +1 -1
  40. package/dist/client/assets/{chunk-55IACEB6-DoDurT6F.js → chunk-55IACEB6-B-sxSeny.js} +1 -1
  41. package/dist/client/assets/{chunk-727SXJPM-DB87A9uH.js → chunk-727SXJPM-B0xJsLsd.js} +1 -1
  42. package/dist/client/assets/{chunk-AQP2D5EJ-ClTDkMp1.js → chunk-AQP2D5EJ-BQ9rJQCA.js} +1 -1
  43. package/dist/client/assets/{chunk-FMBD7UC4-BhY2wlYr.js → chunk-FMBD7UC4-DR-KABew.js} +1 -1
  44. package/dist/client/assets/{chunk-ND2GUHAM-BB8mzlmk.js → chunk-ND2GUHAM-C6ZnAzm4.js} +1 -1
  45. package/dist/client/assets/{chunk-QZHKN3VN-D81qQMGf.js → chunk-QZHKN3VN-zs4tZGnv.js} +1 -1
  46. package/dist/client/assets/classDiagram-4FO5ZUOK-DU8poSst.js +1 -0
  47. package/dist/client/assets/classDiagram-v2-Q7XG4LA2-DU8poSst.js +1 -0
  48. package/dist/client/assets/{cose-bilkent-S5V4N54A-CCFUHBJX.js → cose-bilkent-S5V4N54A-DnljjIm-.js} +1 -1
  49. package/dist/client/assets/{dagre-BM42HDAG-xr9MLhRA.js → dagre-BM42HDAG-CEX8DHCo.js} +1 -1
  50. package/dist/client/assets/{dashboard-view-RXKFv3QV.js → dashboard-view-Chxgvuq4.js} +1 -1
  51. package/dist/client/assets/{dashboard-view-lqwQ-pMo.js → dashboard-view-DPCI6MD3.js} +1 -1
  52. package/dist/client/assets/{dashboard-view-oTFt91I3.js → dashboard-view-D_xtIayp.js} +1 -1
  53. package/dist/client/assets/{diagram-2AECGRRQ-mN_tYnBs.js → diagram-2AECGRRQ-Dl9VFNxu.js} +1 -1
  54. package/dist/client/assets/{diagram-5GNKFQAL-xznlq-5C.js → diagram-5GNKFQAL-BPvDRIKP.js} +1 -1
  55. package/dist/client/assets/{diagram-KO2AKTUF-ClRlj1ZA.js → diagram-KO2AKTUF-c1v9YI9G.js} +1 -1
  56. package/dist/client/assets/{diagram-LMA3HP47-CNiPIQqT.js → diagram-LMA3HP47-ChVDgN1K.js} +1 -1
  57. package/dist/client/assets/{diagram-OG6HWLK6-DwQD-W0P.js → diagram-OG6HWLK6-Cns2RBoA.js} +1 -1
  58. package/dist/client/assets/{erDiagram-TEJ5UH35-ZXkiCm8S.js → erDiagram-TEJ5UH35-D33nn5Lx.js} +1 -1
  59. package/dist/client/assets/{flowDiagram-I6XJVG4X-DI5haEH9.js → flowDiagram-I6XJVG4X-BluS50at.js} +1 -1
  60. package/dist/client/assets/{folder-open-NenyCkrb.js → folder-open-oeYf8Ijc.js} +1 -1
  61. package/dist/client/assets/{ganttDiagram-6RSMTGT7-BRHJpfOZ.js → ganttDiagram-6RSMTGT7-D78LznXX.js} +1 -1
  62. package/dist/client/assets/{gitGraphDiagram-PVQCEYII-iXT_YBAZ.js → gitGraphDiagram-PVQCEYII-Cz-VVeuo.js} +1 -1
  63. package/dist/client/assets/index-Clr90mLV.js +2675 -0
  64. package/dist/client/assets/index-DRZtPb3i.css +1 -0
  65. package/dist/client/assets/{infoDiagram-5YYISTIA-BzkuQtqZ.js → infoDiagram-5YYISTIA-DW64QGM7.js} +1 -1
  66. package/dist/client/assets/{ishikawaDiagram-YF4QCWOH-5hnPG_98.js → ishikawaDiagram-YF4QCWOH-zB3QANvt.js} +1 -1
  67. package/dist/client/assets/{journeyDiagram-JHISSGLW-Co3fnxbr.js → journeyDiagram-JHISSGLW-nVRvkAiF.js} +1 -1
  68. package/dist/client/assets/{kanban-definition-UN3LZRKU-PZFl_y-h.js → kanban-definition-UN3LZRKU-DIsRSNcB.js} +1 -1
  69. package/dist/client/assets/{mermaid.core-CJRKeU9s.js → mermaid.core-ByWbtMdM.js} +4 -4
  70. package/dist/client/assets/{mindmap-definition-RKZ34NQL-B15e2rkx.js → mindmap-definition-RKZ34NQL-BbZZGyk5.js} +1 -1
  71. package/dist/client/assets/{pieDiagram-4H26LBE5-kGF592D_.js → pieDiagram-4H26LBE5-xEuGzir-.js} +1 -1
  72. package/dist/client/assets/{puzzle-Dgc4yzjO.js → puzzle-DfKZ6p_i.js} +1 -1
  73. package/dist/client/assets/{quadrantDiagram-W4KKPZXB-CUShcj70.js → quadrantDiagram-W4KKPZXB-CXU0exeD.js} +1 -1
  74. package/dist/client/assets/{requirementDiagram-4Y6WPE33-D2k4nLqQ.js → requirementDiagram-4Y6WPE33-xrqjvySl.js} +1 -1
  75. package/dist/client/assets/{sankeyDiagram-5OEKKPKP-CA03BxhT.js → sankeyDiagram-5OEKKPKP-B_XL-dlB.js} +1 -1
  76. package/dist/client/assets/{sequenceDiagram-3UESZ5HK-DDuFN2Pi.js → sequenceDiagram-3UESZ5HK-yPNpJSyq.js} +1 -1
  77. package/dist/client/assets/{shield-alert-BlYWqolM.js → shield-alert-C0mvrTVY.js} +1 -1
  78. package/dist/client/assets/{standing-instructions-template-ujl7UARV.js → standing-instructions-template-BA-Y897i.js} +1 -1
  79. package/dist/client/assets/{stateDiagram-AJRCARHV-yJOkHK4Q.js → stateDiagram-AJRCARHV-B3WacriB.js} +1 -1
  80. package/dist/client/assets/{stateDiagram-v2-BHNVJYJU-BgErHdAN.js → stateDiagram-v2-BHNVJYJU-C2J3lqDw.js} +1 -1
  81. package/dist/client/assets/{timeline-definition-PNZ67QCA-BUiDQw21.js → timeline-definition-PNZ67QCA-BMDzGW-H.js} +1 -1
  82. package/dist/client/assets/{upload-DmlZvpMp.js → upload-BcE2JLKs.js} +1 -1
  83. package/dist/client/assets/{users-Cx6ACt-W.js → users-CG9A3bIX.js} +1 -1
  84. package/dist/client/assets/{vennDiagram-CIIHVFJN-DqyIjBY8.js → vennDiagram-CIIHVFJN-C7ZUUi6G.js} +1 -1
  85. package/dist/client/assets/{wardley-L42UT6IY-C93tXAKH.js → wardley-L42UT6IY-zpz98dmH.js} +1 -1
  86. package/dist/client/assets/{wardleyDiagram-YWT4CUSO-DmH6-GJV.js → wardleyDiagram-YWT4CUSO-D4TOrUW4.js} +1 -1
  87. package/dist/client/assets/{xychartDiagram-2RQKCTM6-BPwjE4Su.js → xychartDiagram-2RQKCTM6-CDfiiyNj.js} +1 -1
  88. package/dist/client/index.html +4 -3
  89. package/dist/client/theme-data.css +85 -1
  90. package/dist/client/version.json +1 -1
  91. package/dist/extension.js +244 -362
  92. package/package.json +1 -1
  93. package/dist/client/assets/ReportModal-Bg-XvWay.css +0 -1
  94. package/dist/client/assets/ReportModal-Cy5fRNDK.js +0 -21
  95. package/dist/client/assets/SettingsModal-KnUd0942.css +0 -1
  96. package/dist/client/assets/channel-BdTAZBkX.js +0 -1
  97. package/dist/client/assets/classDiagram-4FO5ZUOK-CuoGzmwo.js +0 -1
  98. package/dist/client/assets/classDiagram-v2-Q7XG4LA2-CuoGzmwo.js +0 -1
  99. package/dist/client/assets/index-CITV-wDq.css +0 -1
  100. package/dist/client/assets/index-CrAJA2v0.js +0 -2675
@@ -865,6 +865,11 @@ var init_execution_and_ui = __esm({
865
865
  "dawn",
866
866
  // FNXC:DashboardTheming 2026-07-31-21:28: Sage must stay order-synchronized across core, selector metadata, and both first-paint validators so saved preferences survive pre-hydration validation.
867
867
  "sage",
868
+ /*
869
+ FNXC:MidnightTheme 2026-08-03-02:05:
870
+ Midnight is a persisted selectable dashboard theme. Keep its id and order synchronized with selector metadata and both web/Electron first-paint validators so saved choices survive before hydration, while shadcn-ember remains the unset default.
871
+ */
872
+ "midnight",
868
873
  // FNXC:DashboardTheming 2026-07-31-20:39: Factory Dark must stay order-synchronized across core, selector metadata, and both first-paint validators so saved preferences survive pre-hydration validation.
869
874
  "factory-dark",
870
875
  // FNXC:DashboardTheming 2026-07-31-23:51: Factory Light must stay order-synchronized across core, selector metadata, and both first-paint validators so saved preferences survive pre-hydration validation.
@@ -96696,15 +96701,20 @@ async function updateTaskUnlockedImpl(store, id, updates, runContext) {
96696
96701
  task.workspaceWorktrees = updates.workspaceWorktrees;
96697
96702
  }
96698
96703
  let movedToTriage = false;
96704
+ let respecifyFromColumn;
96705
+ let respecifyMoveLanes;
96699
96706
  if (updates.dependencies !== void 0) {
96700
96707
  const oldDeps = new Set((task.dependencies ?? []).map((dependency) => dependency.trim()).filter(Boolean));
96701
96708
  const normalizedDependencies = updates.dependencies.map((dependency) => dependency.trim()).filter(Boolean);
96702
96709
  const hasNewDeps = normalizedDependencies.some((d) => !oldDeps.has(d));
96703
96710
  task.dependencies = normalizedDependencies;
96704
- const depLanes = hasNewDeps ? await resolveTaskLifecycleColumns(store, id).catch(() => void 0) : void 0;
96711
+ const respecifyIr = hasNewDeps ? await resolveWorkflowIrForTask(store, id).catch(() => void 0) : void 0;
96712
+ respecifyMoveLanes = toTaskMoveLanes(respecifyIr);
96713
+ const depLanes = respecifyIr ? resolveLifecycleColumns(respecifyIr) : void 0;
96705
96714
  const holdLane = depLanes === void 0 ? "todo" : depLanes.hold;
96706
96715
  if (hasNewDeps && holdLane !== void 0 && task.column === holdLane) {
96707
96716
  const intakeLane = depLanes?.intake;
96717
+ respecifyFromColumn = task.column;
96708
96718
  const relocating = intakeLane !== void 0 && intakeLane !== task.column;
96709
96719
  if (relocating) {
96710
96720
  task.column = intakeLane;
@@ -97382,10 +97392,15 @@ async function updateTaskUnlockedImpl(store, id, updates, runContext) {
97382
97392
  storeLog.warn(`[task-detail] failed to sync PROMPT.md heading for ${task.id}: ${err instanceof Error ? err.message : String(err)}`);
97383
97393
  }
97384
97394
  }
97385
- if (movedToTriage) {
97386
- const lanes = toTaskMoveLanes(await resolveWorkflowIrForTask(store, id).catch(() => void 0));
97387
- store.laneCache.set(task.id, lanes);
97388
- store.emit("task:moved", { task, from: "todo", to: "triage", source: "engine", lanes });
97395
+ if (movedToTriage && respecifyFromColumn !== void 0 && respecifyFromColumn !== task.column && respecifyMoveLanes) {
97396
+ store.laneCache.set(task.id, respecifyMoveLanes);
97397
+ store.emit("task:moved", {
97398
+ task,
97399
+ from: respecifyFromColumn,
97400
+ to: task.column,
97401
+ source: "engine",
97402
+ lanes: respecifyMoveLanes
97403
+ });
97389
97404
  }
97390
97405
  store.emitTaskLifecycleEventSafely("task:updated", [task]);
97391
97406
  return task;
@@ -97397,7 +97412,6 @@ var init_task_update = __esm({
97397
97412
  init_store();
97398
97413
  init_workflow_lifecycle_traits();
97399
97414
  init_workflow_ir_resolver();
97400
- init_workflow_lifecycle_traits();
97401
97415
  init_errors3();
97402
97416
  init_task_fields();
97403
97417
  init_builtin_traits();
@@ -159300,7 +159314,11 @@ async function createFnAgent2(options) {
159300
159314
  }
159301
159315
  });
159302
159316
  attachSessionIdentity(promptableSession);
159303
- return { session: promptableSession, sessionFile: promptableSession.sessionFile };
159317
+ return {
159318
+ session: promptableSession,
159319
+ sessionFile: promptableSession.sessionFile,
159320
+ settleFallbackDispatch: async () => void 0
159321
+ };
159304
159322
  }
159305
159323
  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
159324
  var init_pi = __esm({
@@ -159424,7 +159442,8 @@ function wrapPluginRuntime(instance, runtimeId, runtimeName) {
159424
159442
  const result = await adapter.createSession(options);
159425
159443
  return {
159426
159444
  session: result.session ?? result,
159427
- sessionFile: result.sessionFile
159445
+ sessionFile: result.sessionFile,
159446
+ settleFallbackDispatch: result.settleFallbackDispatch
159428
159447
  };
159429
159448
  }
159430
159449
  throw new Error(`Plugin runtime "${runtimeId}" does not implement createSession`);
@@ -159533,11 +159552,15 @@ var init_runtime_resolution = __esm({
159533
159552
  name = "Default PI Runtime";
159534
159553
  async createSession(options) {
159535
159554
  const { toolOutputMaxChars, mcpServers, ...agentOptions } = options;
159536
- return createFnAgent2({
159555
+ const result = await createFnAgent2({
159537
159556
  ...agentOptions,
159538
159557
  toolOutputMaxChars,
159539
159558
  mcpServers: normalizeAgentRuntimeMcpServers(mcpServers)
159540
159559
  });
159560
+ return {
159561
+ ...result,
159562
+ settleFallbackDispatch: result.settleFallbackDispatch ?? (async () => void 0)
159563
+ };
159541
159564
  }
159542
159565
  async promptWithFallback(session, prompt, options) {
159543
159566
  return promptWithFallback(session, prompt, options);
@@ -160404,6 +160427,7 @@ async function createResolvedAgentSession(options) {
160404
160427
  return {
160405
160428
  session: result.session,
160406
160429
  sessionFile: result.sessionFile,
160430
+ settleFallbackDispatch: result.settleFallbackDispatch,
160407
160431
  runtimeId: resolved.runtimeId,
160408
160432
  wasConfigured: resolved.wasConfigured
160409
160433
  };
@@ -190840,6 +190864,25 @@ var Scheduler = class {
190840
190864
  });
190841
190865
  }
190842
190866
  }
190867
+ if (from === parked.wip && to2 === parked.hold) {
190868
+ if (source === "engine") {
190869
+ this.recentEngineTodoRequeues.set(task.id, task.columnMovedAt ?? (/* @__PURE__ */ new Date()).toISOString());
190870
+ } else {
190871
+ this.recentEngineTodoRequeues.delete(task.id);
190872
+ }
190873
+ } else if (to2 === parked.review || parked.terminal.has(to2)) {
190874
+ this.recentEngineTodoRequeues.delete(task.id);
190875
+ if (task.dispatchStormCount != null || task.lastDispatchAt != null || task.executeRequeueLoopCount != null || task.executeRequeueLoopSignature != null) {
190876
+ void this.store.updateTask(task.id, {
190877
+ dispatchStormCount: null,
190878
+ lastDispatchAt: null,
190879
+ executeRequeueLoopCount: null,
190880
+ executeRequeueLoopSignature: null
190881
+ }).catch((error2) => {
190882
+ schedulerLog.warn(`Failed to reset dispatch oscillation state for ${task.id} on move to ${to2}: ${error2 instanceof Error ? error2.message : String(error2)}`);
190883
+ });
190884
+ }
190885
+ }
190843
190886
  const resolvedParked = mergeParkedColumns(await resolveTaskParkedColumns(this.store, task.id), lanes);
190844
190887
  if (resolvedParked.terminal.has(to2)) {
190845
190888
  try {
@@ -190900,25 +190943,6 @@ var Scheduler = class {
190900
190943
  schedulerLog.error(`Failed event-driven blocker reconciliation for ${task.id}`, error2);
190901
190944
  }
190902
190945
  }
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
190946
  if (resolvedParked.terminal.has(to2) || to2 === resolvedParked.hold) {
190923
190947
  schedulerLog.log(`Task moved to ${to2} \u2014 triggering scheduling`);
190924
190948
  this.schedule();
@@ -192692,110 +192716,40 @@ init_src2();
192692
192716
 
192693
192717
  // ../engine/src/execution-block-classifier.ts
192694
192718
  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;
192719
+ var LEGACY_PR_REF_RE = /^(?:pr[:#-]|#|PR-)(\d+)$/i;
192698
192720
  function partitionBlockedByRefs(blockedBy) {
192699
192721
  const taskIds = [];
192700
- const prNumbers = [];
192701
192722
  for (const raw of blockedBy) {
192702
192723
  const id = raw.trim();
192703
192724
  if (!id) continue;
192704
- const prMatch = id.match(PR_REF_RE);
192705
- if (prMatch) {
192706
- prNumbers.push(Number(prMatch[1]));
192707
- continue;
192708
- }
192725
+ if (LEGACY_PR_REF_RE.test(id)) continue;
192709
192726
  if (TASK_ID_RE.test(id)) {
192710
192727
  const m2 = id.toUpperCase().match(/^([A-Z][A-Z0-9]*)-(\d+)$/);
192711
192728
  taskIds.push(m2 ? `${m2[1]}-${m2[2]}` : id.toUpperCase());
192712
- continue;
192713
192729
  }
192714
192730
  }
192715
- return {
192716
- taskIds: [...new Set(taskIds)],
192717
- prNumbers: [...new Set(prNumbers.filter((n) => Number.isFinite(n) && n > 0))]
192718
- };
192731
+ return { taskIds: [...new Set(taskIds)] };
192719
192732
  }
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) {
192770
- return {
192771
- allowAutoReplan: false,
192772
- class: "external",
192773
- externalBlockers: [],
192774
- thrashSignature: `tasks:${partitioned.taskIds.slice().sort().join(",")}`,
192775
- prNumbers
192776
- };
192777
- }
192778
- if (prNumbers.length > 0) {
192733
+ function classifyBlockedExit(_reason, blockedBy = []) {
192734
+ const { taskIds } = partitionBlockedByRefs(blockedBy);
192735
+ if (taskIds.length > 0) {
192779
192736
  return {
192780
192737
  allowAutoReplan: false,
192781
192738
  class: "external",
192782
- externalBlockers: prNumbers.map((number4) => ({ kind: "github-pr", number: number4 })),
192783
- thrashSignature: prNumbers.map((n) => `pr:${n}`).sort().join("|"),
192784
- prNumbers
192739
+ thrashSignature: `tasks:${taskIds.slice().sort().join(",")}`
192785
192740
  };
192786
192741
  }
192787
192742
  return {
192788
192743
  allowAutoReplan: true,
192789
192744
  class: "plan-defect",
192790
- externalBlockers: [],
192791
- thrashSignature: "plan-defect",
192792
- prNumbers: []
192745
+ thrashSignature: "plan-defect"
192793
192746
  };
192794
192747
  }
192795
192748
  var BLOCKED_THRASH_LIMIT = 3;
192796
192749
  var BLOCKED_THRASH_WINDOW_MS = 60 * 60 * 1e3;
192797
192750
  function countBlockedThrashHits(log41, signature, nowMs = Date.now(), windowMs = BLOCKED_THRASH_WINDOW_MS) {
192798
192751
  if (!log41?.length) return 0;
192752
+ if (signature === "plan-defect") return 0;
192799
192753
  const cutoff = nowMs - windowMs;
192800
192754
  let count2 = 0;
192801
192755
  for (const entry of log41) {
@@ -192807,18 +192761,7 @@ function countBlockedThrashHits(log41, signature, nowMs = Date.now(), windowMs =
192807
192761
  continue;
192808
192762
  }
192809
192763
  }
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
192764
  for (const part of signature.split("|")) {
192818
- if (part.startsWith("pr:") && action.includes(`PR #${part.slice(3)}`)) {
192819
- count2 += 1;
192820
- break;
192821
- }
192822
192765
  if (part.startsWith("tasks:") && part.slice(6).split(",").some((id) => id && action.includes(id))) {
192823
192766
  count2 += 1;
192824
192767
  break;
@@ -192827,28 +192770,18 @@ function countBlockedThrashHits(log41, signature, nowMs = Date.now(), windowMs =
192827
192770
  }
192828
192771
  return count2;
192829
192772
  }
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
192773
  function isDurableBlockedTask(task) {
192837
- if (task.status === "failed" && isDurableBlockedError(task.error)) return true;
192838
192774
  const meta2 = task.sourceMetadata;
192839
192775
  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";
192776
+ if (meta2.blockedClass !== "external") return false;
192777
+ return task.status === "failed" || task.status === "needs-replan";
192845
192778
  }
192846
192779
  function buildExternalBlockMetadataPatch(classification, thrashCount) {
192847
192780
  return {
192848
192781
  blockedClass: classification.class,
192849
192782
  blockedThrashSignature: classification.thrashSignature,
192850
192783
  blockedThrashCount: thrashCount,
192851
- externalBlockers: classification.externalBlockers
192784
+ externalBlockers: []
192852
192785
  };
192853
192786
  }
192854
192787
 
@@ -208426,7 +208359,6 @@ var SelfHealingManager = class _SelfHealingManager extends SelfHealingGitEvidenc
208426
208359
  { name: "reconcile-done-task-integrity", fn: () => this.reconcileDoneTaskIntegrity() },
208427
208360
  { name: "reconcile-stale-merger-status", fn: () => this.reconcileStaleMergerStatus() },
208428
208361
  { name: "reconcile-stale-duplicate-decision", fn: () => this.reconcileStaleDuplicateDecisionPause() },
208429
- { name: "reconcile-external-pr-blockers", fn: () => this.reconcileExternalPrBlockers() },
208430
208362
  // FNXC:OrphanedPendingSteps 2026-07-22-16:35 (FN-8492 review follow-up): also
208431
208363
  // steady-state — a step session can die without an engine restart, and startup-only
208432
208364
  // cadence left that case riding the 3×30-min stall escalator to a deadlock park.
@@ -209930,6 +209862,7 @@ var SelfHealingManager = class _SelfHealingManager extends SelfHealingGitEvidenc
209930
209862
  }
209931
209863
  async reclaimStaleActiveBranches() {
209932
209864
  const reclaimArchivedColumns = await resolveProjectColumnsForRoles(this.store, ["archived"]);
209865
+ const reclaimCompleteColumns = await resolveProjectColumnsForRoles(this.store, ["complete"]);
209933
209866
  try {
209934
209867
  const settings = await this.store.getSettings();
209935
209868
  if (settings.globalPause || settings.enginePaused) return 0;
@@ -210027,10 +209960,12 @@ var SelfHealingManager = class _SelfHealingManager extends SelfHealingGitEvidenc
210027
209960
  if (task.worktree && await isUsableTaskWorktree(this.options.rootDir, task.worktree)) continue;
210028
209961
  const inspection = await this.inspectOrphanedBranch(branch);
210029
209962
  if (!inspection) continue;
210030
- if (inspection.uniqueCommitCount > 0) {
209963
+ const isCompleteColumn2 = reclaimCompleteColumns.has(task.column);
209964
+ if (inspection.uniqueCommitCount > 0 && !isCompleteColumn2) {
210031
209965
  log34.warn(`[recovery] stale-active-branch-rescue-needed ${task.id} branch=${branch} unique=${inspection.uniqueCommitCount} tip=${inspection.tipSha.slice(0, 12)}`);
210032
209966
  continue;
210033
209967
  }
209968
+ const reclaimReason = inspection.uniqueCommitCount > 0 ? "complete-column-unique-commits-force" : "zero-unique-commits-no-worktree";
210034
209969
  await execAsync33(`git branch -D ${JSON.stringify(branch)}`, {
210035
209970
  cwd: this.options.rootDir,
210036
209971
  timeout: 12e4,
@@ -210048,7 +209983,7 @@ var SelfHealingManager = class _SelfHealingManager extends SelfHealingGitEvidenc
210048
209983
  });
210049
209984
  await this.store.logEntry(
210050
209985
  task.id,
210051
- `[recovery] stale-active-branch-reclaim ${task.id} branch=${branch} reason=zero-unique-commits-no-worktree`
209986
+ `[recovery] stale-active-branch-reclaim ${task.id} branch=${branch} reason=${reclaimReason}`
210052
209987
  );
210053
209988
  try {
210054
209989
  const auditor = createRunAuditor(this.store, {
@@ -210066,7 +210001,7 @@ var SelfHealingManager = class _SelfHealingManager extends SelfHealingGitEvidenc
210066
210001
  branch,
210067
210002
  tipSha: inspection.tipSha,
210068
210003
  uniqueCommitCount: inspection.uniqueCommitCount,
210069
- reason: "zero-unique-commits-no-worktree"
210004
+ reason: reclaimReason
210070
210005
  }
210071
210006
  });
210072
210007
  } catch (auditErr) {
@@ -210113,10 +210048,9 @@ var SelfHealingManager = class _SelfHealingManager extends SelfHealingGitEvidenc
210113
210048
  const baseBranch = task.baseBranch || await resolveIntegrationBranch(this.options.rootDir, void 0);
210114
210049
  const comparison = await listUniqueBranchCommits(this.options.rootDir, baseBranch, branchName);
210115
210050
  if (comparison.commits.length > 0) {
210116
- log34.warn(
210117
- `[self-healing] reconcileCompletedTask ${task.id}: branch ${branchName} has ${comparison.commits.length} unique commit(s) vs ${comparison.mainRef}; skip deletion`
210051
+ log34.log(
210052
+ `[self-healing] reconcileCompletedTask ${task.id}: branch ${branchName} has ${comparison.commits.length} unique commit(s) vs ${comparison.mainRef}; force-deleting post-completion (squash-safe)`
210118
210053
  );
210119
- return false;
210120
210054
  }
210121
210055
  try {
210122
210056
  await execAsync33(`git branch -D ${shellQuote2(branchName)}`, {
@@ -217323,71 +217257,12 @@ var SelfHealingManager = class _SelfHealingManager extends SelfHealingGitEvidenc
217323
217257
  }
217324
217258
  }
217325
217259
  /*
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.
217260
+ FNXC:HonestBlockedExit 2026-08-02-23:59 (operator decision — FN-8728 vs PR #2398):
217261
+ The FN-8700 `reconcile-external-pr-blockers` sweep (gh-backed clearing of PR-claim parks)
217262
+ is REMOVED with the whole PR/file-claim blocking mechanism. Open PRs are never blockers;
217263
+ file-scope conflicts are arbitrated only by Fusion's own board. Legacy PR-claim parks are
217264
+ no longer honored by isDurableBlockedTask, so normal recovery paths reclaim them.
217330
217265
  */
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
217266
  async resolveExplicitDuplicateMarkerTasks() {
217392
217267
  try {
217393
217268
  const settings = await this.store.getSettings();
@@ -219772,7 +219647,7 @@ If you have just finished a step's work, immediately call \`fn_task_update\` to
219772
219647
 
219773
219648
  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
219649
 
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.
219650
+ **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
219651
  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
219652
 
219778
219653
  ## How to work
@@ -227231,75 +227106,16 @@ Graph value: ${failureValue}` : ""}`, budget.key),
227231
227106
  );
227232
227107
  }
227233
227108
  }
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
227109
  async routeGraphFailureToExecutionResume(live, failedNode, failureValue, resumeLanesMemo) {
227291
227110
  if (live.deletedAt) return false;
227292
227111
  if (live.paused || live.userPaused === true) return false;
227293
227112
  if ((await resolveTerminalColumnsFor(this.store, live.id)).includes(live.column)) return false;
227294
- if (isDurableBlockedTask(live) || isDurableBlockedError(live.error)) {
227113
+ if (isDurableBlockedTask(live)) {
227295
227114
  executorLog.log(
227296
- `${live.id}: graph failure resume skipped \u2014 durable BLOCKED park honored (class from error/metadata)`
227115
+ `${live.id}: graph failure resume skipped \u2014 durable BLOCKED park honored (task-dependency block)`
227297
227116
  );
227298
227117
  return false;
227299
227118
  }
227300
- if (await this.parkDurableBlockedFromRecentLog(live)) {
227301
- return false;
227302
- }
227303
227119
  if (failedNode === COMPLETION_SUMMARY_NODE_ID) return false;
227304
227120
  const incompleteSteps = hasNonTerminalWorkflowSteps(live);
227305
227121
  const implementationIncompleteMergeFailure = this.isMergeGraphFailure(failedNode) && failureValue === "implementation-incomplete";
@@ -231046,10 +230862,10 @@ ${summary}`,
231046
230862
  { 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
230863
  )),
231048
230864
  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).'
230865
+ 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
230866
  })),
231051
230867
  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").'
230868
+ description: 'Required when outcome="blocked": concrete explanation of what is blocking the work and what is needed to unblock it.'
231053
230869
  }))
231054
230870
  }),
231055
230871
  execute: async (_id, params) => {
@@ -231108,7 +230924,7 @@ ${summary}`,
231108
230924
  }, this.getRunContextFor(taskId));
231109
230925
  await store.logEntry(
231110
230926
  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)`,
230927
+ 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
230928
  void 0,
231113
230929
  this.getRunContextFor(taskId)
231114
230930
  );
@@ -231127,18 +230943,17 @@ ${summary}`,
231127
230943
  parkedAs: autoReplanPark ? "auto-replan" : "failed",
231128
230944
  blockedClass: classification.class,
231129
230945
  thrashCount,
231130
- thrashExhausted,
231131
- prNumbers: classification.prNumbers
230946
+ thrashExhausted
231132
230947
  }
231133
230948
  });
231134
230949
  await this.persistTokenUsage(taskId);
231135
230950
  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"})`}`
230951
+ `\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
230952
  );
231138
230953
  return {
231139
230954
  content: [{
231140
230955
  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."
230956
+ 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
230957
  }],
231143
230958
  details: {}
231144
230959
  };
@@ -243299,6 +243114,7 @@ init_transient_error_detector();
243299
243114
  init_rate_limit_retry();
243300
243115
  init_agent_tools();
243301
243116
  import { exec as exec42 } from "node:child_process";
243117
+ import { existsSync as existsSync67 } from "node:fs";
243302
243118
  import { readFile as readFile40, writeFile as writeFile29, rm as rm12 } from "node:fs/promises";
243303
243119
  import { join as join78 } from "node:path";
243304
243120
  import { promisify as promisify57 } from "node:util";
@@ -243600,6 +243416,8 @@ var TriageProcessor = class _TriageProcessor {
243600
243416
  unregisterAdmissionProvider = null;
243601
243417
  /** Timestamps when tasks entered the `processing` set, for staleness detection. */
243602
243418
  processingSince = /* @__PURE__ */ new Map();
243419
+ /** Monotonic provenance token for attempt-local planner fallback observation. */
243420
+ planningAttemptSequence = 0;
243603
243421
  wasGlobalPaused = false;
243604
243422
  wasEnginePaused = false;
243605
243423
  idleSemaphoreLeakCandidateSince = null;
@@ -244895,7 +244713,28 @@ You are ${assignedAgent.name}${assignedAgent.title?.trim() ? `, ${assignedAgent.
244895
244713
  }
244896
244714
  await this.store.logEntry(task.id, `Planning session running in task worktree ${planningCwd}`).catch(() => void 0);
244897
244715
  }
244898
- const { session } = await createResolvedAgentSession({
244716
+ const authoritativePromptPath = join78(this.rootDir, promptPath);
244717
+ const planningAttemptBaseline = existsSync67(authoritativePromptPath) ? await readFile40(authoritativePromptPath, "utf-8").catch(() => void 0) : void 0;
244718
+ const planningAttempt = {
244719
+ id: `${task.id}:${++this.planningAttemptSequence}`,
244720
+ baseline: planningAttemptBaseline,
244721
+ fallbackEngaged: false,
244722
+ fallbackSettlements: []
244723
+ };
244724
+ const fallbackObserver = createFallbackModelObserver({
244725
+ agent: "triage",
244726
+ label: "triage",
244727
+ store: this.store,
244728
+ taskId: task.id,
244729
+ taskTitle: task.title
244730
+ });
244731
+ const onFallbackModelUsed = (payload) => {
244732
+ planningAttempt.fallbackEngaged = true;
244733
+ const settlement = Promise.resolve(fallbackObserver(payload));
244734
+ planningAttempt.fallbackSettlements.push(settlement);
244735
+ return settlement;
244736
+ };
244737
+ const { session, settleFallbackDispatch, runtimeId } = await createResolvedAgentSession({
244899
244738
  sessionPurpose: "triage",
244900
244739
  runtimeHint: triageRuntimeHint,
244901
244740
  pluginRunner: this.options.pluginRunner,
@@ -244934,13 +244773,7 @@ You are ${assignedAgent.name}${assignedAgent.title?.trim() ? `, ${assignedAgent.
244934
244773
  taskTitle: task.title,
244935
244774
  actionGateContext: this.buildActionGateContext(task.id, triageRunContext.runId, assignedAgent, settings.defaultAgentPermissionPolicy),
244936
244775
  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
- })
244776
+ onFallbackModelUsed
244944
244777
  });
244945
244778
  const modelDesc = formatModelMarkerDetails(describeModel(session), resolvePlanningThinkingLevel(settings, task.planningThinkingLevel ?? task.thinkingLevel));
244946
244779
  planLog.debug(`${task.id}: using model ${modelDesc}`);
@@ -245010,6 +244843,10 @@ You are ${assignedAgent.name}${assignedAgent.title?.trim() ? `, ${assignedAgent.
245010
244843
  agentPrompt,
245011
244844
  imageContents.length > 0 ? { images: imageContents } : void 0
245012
244845
  );
244846
+ const fallbackDispatchBoundaryMissing = typeof settleFallbackDispatch !== "function";
244847
+ if (!fallbackDispatchBoundaryMissing) {
244848
+ await settleFallbackDispatch();
244849
+ }
245013
244850
  checkSessionError(session);
245014
244851
  if (this.pauseAborted.has(task.id)) {
245015
244852
  this.pauseAborted.delete(task.id);
@@ -245102,12 +244939,42 @@ You are ${assignedAgent.name}${assignedAgent.title?.trim() ? `, ${assignedAgent.
245102
244939
  }
245103
244940
  }
245104
244941
  }
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);
244942
+ let settledFallbackCount = 0;
244943
+ while (settledFallbackCount < planningAttempt.fallbackSettlements.length) {
244944
+ const pendingSettlements = planningAttempt.fallbackSettlements.slice(settledFallbackCount);
244945
+ settledFallbackCount = planningAttempt.fallbackSettlements.length;
244946
+ await Promise.all(pendingSettlements);
244947
+ }
244948
+ const artifactChangedByAttempt = planningAttempt.baseline !== written;
244949
+ if (fallbackDispatchBoundaryMissing || planningAttempt.fallbackEngaged || !artifactChangedByAttempt) {
244950
+ 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}`;
244951
+ const decision = computeRecoveryDecision({
244952
+ recoveryRetryCount: task.recoveryRetryCount,
244953
+ nextRecoveryAt: task.nextRecoveryAt
244954
+ });
244955
+ if (decision.shouldRetry) {
244956
+ const retryMessage = `${failure} \u2014 retry ${decision.nextState.recoveryRetryCount}/${MAX_RECOVERY_RETRIES} in ${formatDelay(decision.delayMs)}.`;
244957
+ planLog.warn(`${task.id} ${retryMessage}`);
244958
+ await this.store.logEntry(task.id, retryMessage);
244959
+ await this.updatePlanningStateIfStillCurrent(task, {
244960
+ status: this.restoreStatusAfterInterruptedTriageWork(task),
244961
+ error: null,
244962
+ recoveryRetryCount: decision.nextState.recoveryRetryCount,
244963
+ nextRecoveryAt: decision.nextState.nextRecoveryAt
244964
+ });
244965
+ return;
244966
+ }
244967
+ const failureMessage = `${failure} after ${MAX_RECOVERY_RETRIES} retries. Retry after adjusting the task prompt or model.`;
244968
+ planLog.error(`${task.id} clean planning attempt retry budget exhausted`);
244969
+ await this.store.logEntry(task.id, failureMessage);
244970
+ if (await this.updatePlanningStateIfStillCurrent(task, {
244971
+ status: "failed",
244972
+ error: failureMessage,
244973
+ recoveryRetryCount: null,
244974
+ nextRecoveryAt: null
244975
+ })) {
244976
+ await this.backfillBlankTitleAfterTerminalTriageFailure(task);
244977
+ }
245111
244978
  return;
245112
244979
  }
245113
244980
  const deterministicSpecFailure = await this.validateGeneratedPrompt(task.id, written);
@@ -245149,6 +245016,21 @@ You are ${assignedAgent.name}${assignedAgent.title?.trim() ? `, ${assignedAgent.
245149
245016
  }
245150
245017
  return;
245151
245018
  }
245019
+ const duplicateReport = { outcome: "parked" };
245020
+ if (await this.tryFinalizeExplicitDuplicateMarker(task, written, settings, {
245021
+ isReplan,
245022
+ feedback
245023
+ }, duplicateReport)) {
245024
+ this.options.onSpecifyComplete?.(task, duplicateReport);
245025
+ return;
245026
+ }
245027
+ if (task.recoveryRetryCount != null || task.nextRecoveryAt != null) {
245028
+ await this.updatePlanningStateIfStillCurrent(task, {
245029
+ error: null,
245030
+ recoveryRetryCount: null,
245031
+ nextRecoveryAt: null
245032
+ });
245033
+ }
245152
245034
  const finalizeReport = await this.finalizeApprovedTask(task, written, settings, {
245153
245035
  isReplan,
245154
245036
  feedback
@@ -249882,14 +249764,14 @@ function extractAdvisorAssistantText(session) {
249882
249764
 
249883
249765
  // ../engine/src/overseer-watchdog.ts
249884
249766
  init_logger();
249885
- import { existsSync as existsSync67, readFileSync as readFileSync24 } from "node:fs";
249767
+ import { existsSync as existsSync68, readFileSync as readFileSync24 } from "node:fs";
249886
249768
  import { dirname as dirname34, join as join79, relative as relative17, resolve as resolve40, sep as sep11 } from "node:path";
249887
249769
  import { homedir as homedir12 } from "node:os";
249888
249770
  var log38 = createLogger("overseer-watchdog");
249889
249771
  var WATCHDOG_FILENAMES = ["OVERSEER.md", "WATCHDOG.md"];
249890
249772
  function defaultReadText(path5) {
249891
249773
  try {
249892
- if (!existsSync67(path5)) return null;
249774
+ if (!existsSync68(path5)) return null;
249893
249775
  return readFileSync24(path5, "utf8");
249894
249776
  } catch {
249895
249777
  return null;
@@ -251033,7 +250915,7 @@ init_worktree_paths();
251033
250915
  // ../engine/src/merger-ai-worktree.ts
251034
250916
  init_active_session_registry();
251035
250917
  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";
250918
+ 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
250919
  import { rm as rm13 } from "node:fs/promises";
251038
250920
  import { tmpdir as tmpdir10 } from "node:os";
251039
250921
  import { isAbsolute as isAbsolute21, join as join80, relative as relative18 } from "node:path";
@@ -251070,7 +250952,7 @@ function isBenignAbsentWorktreeError(err) {
251070
250952
  }
251071
250953
  function ensureAiMergeRootIgnored(projectRootDir, settings) {
251072
250954
  const excludePath = join80(projectRootDir, ".git", "info", "exclude");
251073
- if (!existsSync68(excludePath)) return;
250955
+ if (!existsSync69(excludePath)) return;
251074
250956
  try {
251075
250957
  const current = readFileSync25(excludePath, "utf-8");
251076
250958
  const legacyAiMergeRoot = resolveLegacyAiMergeRootPath(projectRootDir);
@@ -251201,7 +251083,7 @@ async function cleanupAiMergeWorktree(input) {
251201
251083
  const cleanupMetadata = { taskId, mergeRoot: canonicalRoot, requestedMergeRoot: mergeRoot };
251202
251084
  let alreadyAbsent = false;
251203
251085
  if (worktreeAdded) {
251204
- if (!existsSync68(canonicalRoot) && !existsSync68(mergeRoot)) {
251086
+ if (!existsSync69(canonicalRoot) && !existsSync69(mergeRoot)) {
251205
251087
  alreadyAbsent = true;
251206
251088
  await log41(`AI merge cleanup: worktree ${canonicalRoot} was already absent before git removal; treating cleanup as idempotent`);
251207
251089
  await audit.git({ type: "merge:ai-worktree-cleanup", target: canonicalRoot, metadata: { ...cleanupMetadata, phase: "git-remove", success: true, alreadyAbsent: true, idempotent: true, code: "ENOENT" } });