@runfusion/fusion 0.74.0-beta.1 → 0.74.0-beta.3

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 (112) hide show
  1. package/dist/bin.js +1246 -680
  2. package/dist/child-process-worker.js +810 -253
  3. package/dist/client/.vite/manifest.json +268 -268
  4. package/dist/client/assets/{AgentDetailView-Bopzxiqb.js → AgentDetailView-D0J_GDct.js} +1 -1
  5. package/dist/client/assets/{AgentPermissionPolicyEditor-i5N7rUlL.js → AgentPermissionPolicyEditor-BBAwprvO.js} +1 -1
  6. package/dist/client/assets/{AgentsView-M3GoJDHH.js → AgentsView-DTY5DIav.js} +3 -3
  7. package/dist/client/assets/{ChatView-CqiyA9Kt.js → ChatView-CRoW2tRJ.js} +1 -1
  8. package/dist/client/assets/{CommandCenter-CAvMGKhl.js → CommandCenter-BieyKvKb.js} +1 -1
  9. package/dist/client/assets/{DevServerView-wqM6h3nV.js → DevServerView-CLR1LmhK.js} +1 -1
  10. package/dist/client/assets/{DirectoryPicker-C8OyXCE-.js → DirectoryPicker-B-olNAvT.js} +1 -1
  11. package/dist/client/assets/{DocumentsView-CjNMZQi8.js → DocumentsView-CG_-pnXU.js} +1 -1
  12. package/dist/client/assets/{EvalsView-BVlbuB5t.js → EvalsView-Xp0zCUlr.js} +1 -1
  13. package/dist/client/assets/{ExperimentalAgentOnboardingModal-CIwCg8sq.js → ExperimentalAgentOnboardingModal-z8m5V5yv.js} +1 -1
  14. package/dist/client/assets/{GoalsView-Cnw2igil.js → GoalsView-Bd-4g949.js} +1 -1
  15. package/dist/client/assets/{InsightsView-D7GmD-Kx.js → InsightsView-tCgg66wJ.js} +1 -1
  16. package/dist/client/assets/{MemoryView-BlqkfIE5.js → MemoryView-Dqgsd_0E.js} +1 -1
  17. package/dist/client/assets/{PiExtensionsManager-EC94zLMq.js → PiExtensionsManager-GzGoWgb5.js} +1 -1
  18. package/dist/client/assets/{PluginManager-pYyCD3uh.js → PluginManager-BWdqEqr9.js} +1 -1
  19. package/dist/client/assets/{PullRequestView-BAb0vVhu.js → PullRequestView-CQq1G1kY.js} +1 -1
  20. package/dist/client/assets/{ReportModal-COmReivA.js → ReportModal-C-3rBFhw.js} +1 -1
  21. package/dist/client/assets/{ResearchView-DLMo4rFw.js → ResearchView-CS1HwDg_.js} +1 -1
  22. package/dist/client/assets/{SecretsView-w_O8NLfw.js → SecretsView-C6RP9Rkw.js} +1 -1
  23. package/dist/client/assets/{SessionTerminal-CSPi-qWE.js → SessionTerminal-C9nCSefk.js} +1 -1
  24. package/dist/client/assets/SettingsModal-C1oEklYy.css +1 -0
  25. package/dist/client/assets/{SettingsModal-wilrGXvE.js → SettingsModal-D3DCvCfP.js} +1 -1
  26. package/dist/client/assets/{SettingsModal-C9bVvYQq.js → SettingsModal-D4mjT8hw.js} +4 -4
  27. package/dist/client/assets/{SettingsTextareaRow-Bd7_SxVv.js → SettingsTextareaRow-zBVXQbcC.js} +1 -1
  28. package/dist/client/assets/{SetupWizardModal-sPs6Lw3a.js → SetupWizardModal-C9r1dd-h.js} +2 -2
  29. package/dist/client/assets/{SkillsView-B9PvYS8t.js → SkillsView-DFyUBWd8.js} +1 -1
  30. package/dist/client/assets/{TodoView-Df0SD_JT.js → TodoView-BC8f952m.js} +1 -1
  31. package/dist/client/assets/{WorkflowNodeEditor-B3kFD-S0.js → WorkflowNodeEditor-UTX757WH.js} +1 -1
  32. package/dist/client/assets/{agent-import-generation-OXfU1-D1.js → agent-import-generation-DN0q1xEo.js} +1 -1
  33. package/dist/client/assets/{app-DJIiVHdA.js → app-BP57l_5h.js} +1 -1
  34. package/dist/client/assets/{app-hQw_x2KJ.js → app-D4frL0Ka.js} +1 -1
  35. package/dist/client/assets/{app-DaikOPcV.js → app-niWDkeV8.js} +3 -3
  36. package/dist/client/assets/{app-Dn1u7zJ9.js → app-sLOwmHAE.js} +1 -1
  37. package/dist/client/assets/{app-DMZ_RusZ.js → app-vRWwM-aD.js} +1 -1
  38. package/dist/client/assets/{app-DxTbBEMH.js → app-zPyey_fg.js} +1 -1
  39. package/dist/client/assets/{architectureDiagram-3BPJPVTR-B_8zk4ag.js → architectureDiagram-3BPJPVTR-D-nqJ95-.js} +1 -1
  40. package/dist/client/assets/{blockDiagram-GPEHLZMM-CpDKBJkO.js → blockDiagram-GPEHLZMM--OlkXlyS.js} +1 -1
  41. package/dist/client/assets/{c4Diagram-AAUBKEIU-D3wtfGzI.js → c4Diagram-AAUBKEIU-CH7PaWgs.js} +1 -1
  42. package/dist/client/assets/channel-FJNMzlkv.js +1 -0
  43. package/dist/client/assets/{chunk-2J33WTMH-DS9sHIMv.js → chunk-2J33WTMH-CpHDwqg2.js} +1 -1
  44. package/dist/client/assets/{chunk-4BX2VUAB-B-xW6cfi.js → chunk-4BX2VUAB-BdW4tdnc.js} +1 -1
  45. package/dist/client/assets/{chunk-55IACEB6-Ddxf-sPU.js → chunk-55IACEB6-b3tBFiuH.js} +1 -1
  46. package/dist/client/assets/{chunk-727SXJPM-Bv_Z1w57.js → chunk-727SXJPM-DifknEtv.js} +1 -1
  47. package/dist/client/assets/{chunk-AQP2D5EJ-DYHYN43E.js → chunk-AQP2D5EJ-DHIZNZ4Z.js} +1 -1
  48. package/dist/client/assets/{chunk-FMBD7UC4-wuuFvvBk.js → chunk-FMBD7UC4-BLRLYW2y.js} +1 -1
  49. package/dist/client/assets/{chunk-ND2GUHAM-DfVRkVZo.js → chunk-ND2GUHAM-Dh8f06Ld.js} +1 -1
  50. package/dist/client/assets/{chunk-QZHKN3VN-D0t6ryuh.js → chunk-QZHKN3VN-CpzUiemt.js} +1 -1
  51. package/dist/client/assets/classDiagram-4FO5ZUOK-BOenhjVP.js +1 -0
  52. package/dist/client/assets/classDiagram-v2-Q7XG4LA2-BOenhjVP.js +1 -0
  53. package/dist/client/assets/common-13IRj_PO.js +1 -0
  54. package/dist/client/assets/common-BBynATth.js +1 -0
  55. package/dist/client/assets/common-BG-vw0tL.js +1 -0
  56. package/dist/client/assets/common-DSo46rnH.js +1 -0
  57. package/dist/client/assets/common-DbOCE-0i.js +1 -0
  58. package/dist/client/assets/common-DnXF4Jmj.js +1 -0
  59. package/dist/client/assets/{cose-bilkent-S5V4N54A-DVlKqNFJ.js → cose-bilkent-S5V4N54A-CE2RJ_k0.js} +1 -1
  60. package/dist/client/assets/{dagre-BM42HDAG-DTtiGxz-.js → dagre-BM42HDAG-CCs_xlYw.js} +1 -1
  61. package/dist/client/assets/{dashboard-view-CLjYqvE9.js → dashboard-view-D5qv61fP.js} +1 -1
  62. package/dist/client/assets/{dashboard-view-D-nDgSmP.js → dashboard-view-Eg7Jyvnm.js} +1 -1
  63. package/dist/client/assets/{dashboard-view-DH99T6VJ.js → dashboard-view-Z6H-k0uR.js} +1 -1
  64. package/dist/client/assets/{diagram-2AECGRRQ-Tv-YxCWk.js → diagram-2AECGRRQ-CF60Q-de.js} +1 -1
  65. package/dist/client/assets/{diagram-5GNKFQAL-DlAAPkGu.js → diagram-5GNKFQAL-DDcGMlua.js} +1 -1
  66. package/dist/client/assets/{diagram-KO2AKTUF-DisatTBB.js → diagram-KO2AKTUF-YgvJ4DQf.js} +1 -1
  67. package/dist/client/assets/{diagram-LMA3HP47-CDrUxjzW.js → diagram-LMA3HP47-Bi-j9nGw.js} +1 -1
  68. package/dist/client/assets/{diagram-OG6HWLK6-BQ34a4Ej.js → diagram-OG6HWLK6-DEmMqnwc.js} +1 -1
  69. package/dist/client/assets/{erDiagram-TEJ5UH35-s-YHwAEU.js → erDiagram-TEJ5UH35-DwmZwhiW.js} +1 -1
  70. package/dist/client/assets/{flowDiagram-I6XJVG4X-EpXzmG2d.js → flowDiagram-I6XJVG4X-B5ITa_HJ.js} +1 -1
  71. package/dist/client/assets/{folder-open-DN86xUly.js → folder-open-BVxA7YtM.js} +1 -1
  72. package/dist/client/assets/{ganttDiagram-6RSMTGT7-Dk2WaoX8.js → ganttDiagram-6RSMTGT7-BgAPwh9w.js} +1 -1
  73. package/dist/client/assets/{gitGraphDiagram-PVQCEYII-CjchnKW0.js → gitGraphDiagram-PVQCEYII-DTtGzZFj.js} +1 -1
  74. package/dist/client/assets/{index-aGZ3DHQw.js → index-idkERMsE.js} +289 -289
  75. package/dist/client/assets/{infoDiagram-5YYISTIA-CiU-twoj.js → infoDiagram-5YYISTIA-CkSnTje1.js} +1 -1
  76. package/dist/client/assets/{ishikawaDiagram-YF4QCWOH-BvylyUKK.js → ishikawaDiagram-YF4QCWOH-jn5VNpMh.js} +1 -1
  77. package/dist/client/assets/{journeyDiagram-JHISSGLW-Ca6z84KB.js → journeyDiagram-JHISSGLW-BpaqoXaI.js} +1 -1
  78. package/dist/client/assets/{kanban-definition-UN3LZRKU-DXbw2-s-.js → kanban-definition-UN3LZRKU-CpfjJoBN.js} +1 -1
  79. package/dist/client/assets/{mermaid.core-DkS33D12.js → mermaid.core-DZNGPQzF.js} +4 -4
  80. package/dist/client/assets/{mindmap-definition-RKZ34NQL-BgXuWPDD.js → mindmap-definition-RKZ34NQL-BdbX1EJa.js} +1 -1
  81. package/dist/client/assets/{pieDiagram-4H26LBE5-CnOzTN6U.js → pieDiagram-4H26LBE5-Xn0322tJ.js} +1 -1
  82. package/dist/client/assets/{puzzle-Dxl1kcLF.js → puzzle-CPx1RP2g.js} +1 -1
  83. package/dist/client/assets/{quadrantDiagram-W4KKPZXB-DI3s1u95.js → quadrantDiagram-W4KKPZXB-OOHqqw5y.js} +1 -1
  84. package/dist/client/assets/{requirementDiagram-4Y6WPE33-Ck97mo-W.js → requirementDiagram-4Y6WPE33-Dvj6zvaB.js} +1 -1
  85. package/dist/client/assets/{sankeyDiagram-5OEKKPKP-Z3w8NJac.js → sankeyDiagram-5OEKKPKP-DEkEqthK.js} +1 -1
  86. package/dist/client/assets/{sequenceDiagram-3UESZ5HK-DLGtWgfy.js → sequenceDiagram-3UESZ5HK-BPc9ncPj.js} +1 -1
  87. package/dist/client/assets/{shield-alert-BCGxgBpM.js → shield-alert-baoKRyU6.js} +1 -1
  88. package/dist/client/assets/{standing-instructions-template-CUE6ItG6.js → standing-instructions-template-CkqsGC1L.js} +1 -1
  89. package/dist/client/assets/{stateDiagram-AJRCARHV-C6Zl2EJW.js → stateDiagram-AJRCARHV-DywhjHIx.js} +1 -1
  90. package/dist/client/assets/{stateDiagram-v2-BHNVJYJU-BA77mDAW.js → stateDiagram-v2-BHNVJYJU-D7t7x4uT.js} +1 -1
  91. package/dist/client/assets/{timeline-definition-PNZ67QCA-D9Cp15Hm.js → timeline-definition-PNZ67QCA-BH3CKgrg.js} +1 -1
  92. package/dist/client/assets/{upload-2jCicylS.js → upload-DtIvOGwM.js} +1 -1
  93. package/dist/client/assets/{users-C1TTGAfX.js → users-DcxsxPlj.js} +1 -1
  94. package/dist/client/assets/{vennDiagram-CIIHVFJN-BwFSh6Tr.js → vennDiagram-CIIHVFJN-CCDllBgJ.js} +1 -1
  95. package/dist/client/assets/{wardley-L42UT6IY-CQknAo24.js → wardley-L42UT6IY-C43hFLXh.js} +1 -1
  96. package/dist/client/assets/{wardleyDiagram-YWT4CUSO-Dwc8f8bd.js → wardleyDiagram-YWT4CUSO-__f9rI8w.js} +1 -1
  97. package/dist/client/assets/{xychartDiagram-2RQKCTM6-CPkeHv8g.js → xychartDiagram-2RQKCTM6-DY-fpQ_x.js} +1 -1
  98. package/dist/client/index.html +1 -1
  99. package/dist/client/version.json +1 -1
  100. package/dist/extension.js +950 -393
  101. package/package.json +1 -1
  102. package/skill/fusion/references/engine-tools.md +1 -1
  103. package/dist/client/assets/SettingsModal-CMLHZBhX.css +0 -1
  104. package/dist/client/assets/channel-BLzjKIVz.js +0 -1
  105. package/dist/client/assets/classDiagram-4FO5ZUOK-DkwPy8u9.js +0 -1
  106. package/dist/client/assets/classDiagram-v2-Q7XG4LA2-DkwPy8u9.js +0 -1
  107. package/dist/client/assets/common-BDp2y6CF.js +0 -1
  108. package/dist/client/assets/common-BnEo5fLN.js +0 -1
  109. package/dist/client/assets/common-Bzr_Y8h4.js +0 -1
  110. package/dist/client/assets/common-CvwyFzfv.js +0 -1
  111. package/dist/client/assets/common-CztCXRkz.js +0 -1
  112. package/dist/client/assets/common-DPe2mbb7.js +0 -1
package/dist/extension.js CHANGED
@@ -5525,7 +5525,7 @@ var init_sql = __esm({
5525
5525
  return new SQL([new StringChunk(str)]);
5526
5526
  }
5527
5527
  sql2.raw = raw;
5528
- function join108(chunks, separator) {
5528
+ function join109(chunks, separator) {
5529
5529
  const result2 = [];
5530
5530
  for (const [i, chunk] of chunks.entries()) {
5531
5531
  if (i > 0 && separator !== void 0) {
@@ -5535,7 +5535,7 @@ var init_sql = __esm({
5535
5535
  }
5536
5536
  return new SQL(result2);
5537
5537
  }
5538
- sql2.join = join108;
5538
+ sql2.join = join109;
5539
5539
  function identifier(value) {
5540
5540
  return new Name(value);
5541
5541
  }
@@ -10183,7 +10183,7 @@ var init_select2 = __esm({
10183
10183
  const baseTableName = this.tableName;
10184
10184
  const tableName = getTableLikeName(table);
10185
10185
  for (const item of extractUsedTable(table)) this.usedTables.add(item);
10186
- if (typeof tableName === "string" && this.config.joins?.some((join108) => join108.alias === tableName)) {
10186
+ if (typeof tableName === "string" && this.config.joins?.some((join109) => join109.alias === tableName)) {
10187
10187
  throw new Error(`Alias "${tableName}" is already used in this query`);
10188
10188
  }
10189
10189
  if (!this.isPartialSelect) {
@@ -11718,7 +11718,7 @@ var init_update = __esm({
11718
11718
  createJoin(joinType) {
11719
11719
  return (table, on) => {
11720
11720
  const tableName = getTableLikeName(table);
11721
- if (typeof tableName === "string" && this.config.joins.some((join108) => join108.alias === tableName)) {
11721
+ if (typeof tableName === "string" && this.config.joins.some((join109) => join109.alias === tableName)) {
11722
11722
  throw new Error(`Alias "${tableName}" is already used in this query`);
11723
11723
  }
11724
11724
  if (typeof on === "function") {
@@ -11814,10 +11814,10 @@ var init_update = __esm({
11814
11814
  const fromFields = this.getTableLikeFields(this.config.from);
11815
11815
  fields[tableName] = fromFields;
11816
11816
  }
11817
- for (const join108 of this.config.joins) {
11818
- const tableName2 = getTableLikeName(join108.table);
11819
- if (typeof tableName2 === "string" && !is(join108.table, SQL)) {
11820
- const fromFields = this.getTableLikeFields(join108.table);
11817
+ for (const join109 of this.config.joins) {
11818
+ const tableName2 = getTableLikeName(join109.table);
11819
+ if (typeof tableName2 === "string" && !is(join109.table, SQL)) {
11820
+ const fromFields = this.getTableLikeFields(join109.table);
11821
11821
  fields[tableName2] = fromFields;
11822
11822
  }
11823
11823
  }
@@ -20421,26 +20421,26 @@ function validateParallelism(nodes2, outgoing, nodesById) {
20421
20421
  }
20422
20422
  const joinsReached = /* @__PURE__ */ new Set();
20423
20423
  for (const edge of branchEdges) {
20424
- const join109 = walkBranchToJoin(edge.to, split.id, outgoing, nodesById);
20425
- if (!join109) {
20424
+ const join110 = walkBranchToJoin(edge.to, split.id, outgoing, nodesById);
20425
+ if (!join110) {
20426
20426
  throw new WorkflowIrError(`split '${split.id}' has a branch with no reachable matching join`);
20427
20427
  }
20428
- joinsReached.add(join109);
20428
+ joinsReached.add(join110);
20429
20429
  }
20430
20430
  if (joinsReached.size !== 1) {
20431
20431
  throw new WorkflowIrError(`split '${split.id}' branches converge on more than one join`);
20432
20432
  }
20433
20433
  const joinId = [...joinsReached][0];
20434
- const join108 = nodesById.get(joinId);
20435
- const mode = join108.config?.mode;
20434
+ const join109 = nodesById.get(joinId);
20435
+ const mode = join109.config?.mode;
20436
20436
  if (mode && typeof mode === "object" && "quorum" in mode) {
20437
20437
  const n = mode.quorum;
20438
20438
  if (typeof n !== "number" || !Number.isInteger(n) || n < 1) {
20439
- throw new WorkflowIrError(`join '${join108.id}' quorum must be a positive integer`);
20439
+ throw new WorkflowIrError(`join '${join109.id}' quorum must be a positive integer`);
20440
20440
  }
20441
20441
  if (n > branchEdges.length) {
20442
20442
  throw new WorkflowIrError(
20443
- `join '${join108.id}' quorum(${n}) exceeds the split's ${branchEdges.length} branches`
20443
+ `join '${join109.id}' quorum(${n}) exceeds the split's ${branchEdges.length} branches`
20444
20444
  );
20445
20445
  }
20446
20446
  }
@@ -23996,14 +23996,14 @@ async function detectWorkspaceRepos(dir, runner = runGitCommand, timeout = DEFAU
23996
23996
  return [];
23997
23997
  }
23998
23998
  const { stat: stat13 } = await import("node:fs/promises");
23999
- const { join: join108 } = await import("node:path");
23999
+ const { join: join109 } = await import("node:path");
24000
24000
  const found = [];
24001
24001
  const EXCLUDED_ENTRIES = /* @__PURE__ */ new Set(["node_modules", ".fusion", ".git", ".pi"]);
24002
24002
  for (const entry of entries) {
24003
24003
  if (EXCLUDED_ENTRIES.has(entry)) continue;
24004
- const childDir = join108(dir, entry);
24004
+ const childDir = join109(dir, entry);
24005
24005
  try {
24006
- const s3 = await stat13(join108(childDir, ".git"));
24006
+ const s3 = await stat13(join109(childDir, ".git"));
24007
24007
  if (!s3.isDirectory() && !s3.isFile()) continue;
24008
24008
  } catch {
24009
24009
  continue;
@@ -24016,9 +24016,9 @@ async function detectWorkspaceRepos(dir, runner = runGitCommand, timeout = DEFAU
24016
24016
  }
24017
24017
  async function isWorkspaceModeExplicitlyDisabled(projectPath) {
24018
24018
  try {
24019
- const { readFile: readFile46 } = await import("node:fs/promises");
24020
- const { join: join108 } = await import("node:path");
24021
- const raw = await readFile46(join108(projectPath, ".fusion", "config.json"), "utf-8");
24019
+ const { readFile: readFile47 } = await import("node:fs/promises");
24020
+ const { join: join109 } = await import("node:path");
24021
+ const raw = await readFile47(join109(projectPath, ".fusion", "config.json"), "utf-8");
24022
24022
  const config3 = JSON.parse(raw);
24023
24023
  return config3.settings?.workspaceMode === false;
24024
24024
  } catch {
@@ -24026,12 +24026,12 @@ async function isWorkspaceModeExplicitlyDisabled(projectPath) {
24026
24026
  }
24027
24027
  }
24028
24028
  async function setWorkspaceModeInConfig(projectPath, value) {
24029
- const { readFile: readFile46, writeFile: writeFile35, mkdir: mkdir32 } = await import("node:fs/promises");
24030
- const { join: join108 } = await import("node:path");
24031
- const configPath = join108(projectPath, ".fusion", "config.json");
24029
+ const { readFile: readFile47, writeFile: writeFile35, mkdir: mkdir32 } = await import("node:fs/promises");
24030
+ const { join: join109 } = await import("node:path");
24031
+ const configPath = join109(projectPath, ".fusion", "config.json");
24032
24032
  let config3 = {};
24033
24033
  try {
24034
- config3 = JSON.parse(await readFile46(configPath, "utf-8"));
24034
+ config3 = JSON.parse(await readFile47(configPath, "utf-8"));
24035
24035
  } catch (err) {
24036
24036
  if (err?.code !== "ENOENT") throw err;
24037
24037
  }
@@ -24040,7 +24040,7 @@ async function setWorkspaceModeInConfig(projectPath, value) {
24040
24040
  }
24041
24041
  const settings = config3.settings;
24042
24042
  settings.workspaceMode = value;
24043
- await mkdir32(join108(projectPath, ".fusion"), { recursive: true });
24043
+ await mkdir32(join109(projectPath, ".fusion"), { recursive: true });
24044
24044
  await writeFile35(configPath, JSON.stringify(config3, null, 2), "utf-8");
24045
24045
  }
24046
24046
  function isInRootRelativePath(entry, pathMod) {
@@ -24053,12 +24053,12 @@ function isInRootRelativePath(entry, pathMod) {
24053
24053
  return true;
24054
24054
  }
24055
24055
  async function loadWorkspaceConfig(rootDir) {
24056
- const { readFile: readFile46 } = await import("node:fs/promises");
24056
+ const { readFile: readFile47 } = await import("node:fs/promises");
24057
24057
  const pathMod = await import("node:path");
24058
- const { join: join108 } = pathMod;
24059
- const configPath = join108(rootDir, ".fusion", WORKSPACE_CONFIG_FILENAME);
24058
+ const { join: join109 } = pathMod;
24059
+ const configPath = join109(rootDir, ".fusion", WORKSPACE_CONFIG_FILENAME);
24060
24060
  try {
24061
- const raw = await readFile46(configPath, "utf-8");
24061
+ const raw = await readFile47(configPath, "utf-8");
24062
24062
  const parsed = JSON.parse(raw);
24063
24063
  if (parsed !== null && typeof parsed === "object" && "repos" in parsed && Array.isArray(parsed.repos) && parsed.repos.every((r) => typeof r === "string")) {
24064
24064
  const rawRepos = parsed.repos;
@@ -24072,11 +24072,11 @@ async function loadWorkspaceConfig(rootDir) {
24072
24072
  }
24073
24073
  async function saveWorkspaceConfig(rootDir, config3) {
24074
24074
  const { mkdir: mkdir32, writeFile: writeFile35 } = await import("node:fs/promises");
24075
- const { join: join108 } = await import("node:path");
24076
- const fusionDir = join108(rootDir, ".fusion");
24075
+ const { join: join109 } = await import("node:path");
24076
+ const fusionDir = join109(rootDir, ".fusion");
24077
24077
  await mkdir32(fusionDir, { recursive: true });
24078
24078
  await writeFile35(
24079
- join108(fusionDir, WORKSPACE_CONFIG_FILENAME),
24079
+ join109(fusionDir, WORKSPACE_CONFIG_FILENAME),
24080
24080
  JSON.stringify(config3, null, 2),
24081
24081
  "utf-8"
24082
24082
  );
@@ -38905,11 +38905,11 @@ async function rekeyFallbackProjectPartition(db, fallbackProjectId, registeredPr
38905
38905
  const parentKey = `${fk.parentSchema}.${fk.parentTable}`;
38906
38906
  if (!targetKeys.has(parentKey)) continue;
38907
38907
  const childTarget = targetKeys.has(`${fk.childSchema}.${fk.childTable}`);
38908
- const join108 = fk.childColumns.map((column, index2) => `child.${quoteIdentifier(column)} IS NOT DISTINCT FROM parent.${quoteIdentifier(fk.parentColumns[index2])}`).join(" AND ");
38908
+ const join109 = fk.childColumns.map((column, index2) => `child.${quoteIdentifier(column)} IS NOT DISTINCT FROM parent.${quoteIdentifier(fk.parentColumns[index2])}`).join(" AND ");
38909
38909
  const registeredChildScope = childTarget ? ` AND child.${quoteIdentifier("project_id")} = ${quoteLiteral(registeredProjectId)}` : "";
38910
38910
  const unreplaced = await tx.execute(sql.raw(`SELECT EXISTS (
38911
38911
  SELECT 1 FROM ${relationName({ schema: fk.childSchema, table: fk.childTable })} child
38912
- JOIN ${relationName({ schema: fk.parentSchema, table: fk.parentTable })} parent ON ${join108}
38912
+ JOIN ${relationName({ schema: fk.parentSchema, table: fk.parentTable })} parent ON ${join109}
38913
38913
  JOIN fusion_rekey_conflict_candidates parent_candidate
38914
38914
  ON parent_candidate.schema_name = ${quoteLiteral(fk.parentSchema)}
38915
38915
  AND parent_candidate.table_name = ${quoteLiteral(fk.parentTable)}
@@ -49746,8 +49746,8 @@ var require_CronFileParser = __commonJS({
49746
49746
  * @throws If file cannot be read
49747
49747
  */
49748
49748
  static async parseFile(filePath) {
49749
- const { readFile: readFile46 } = await Promise.resolve().then(() => __importStar(__require("fs/promises")));
49750
- const data = await readFile46(filePath, "utf8");
49749
+ const { readFile: readFile47 } = await Promise.resolve().then(() => __importStar(__require("fs/promises")));
49750
+ const data = await readFile47(filePath, "utf8");
49751
49751
  return _CronFileParser.#parseContent(data);
49752
49752
  }
49753
49753
  /**
@@ -132671,8 +132671,8 @@ var require_graceful_fs = __commonJS({
132671
132671
  fs10.createReadStream = createReadStream;
132672
132672
  fs10.createWriteStream = createWriteStream;
132673
132673
  var fs$readFile = fs10.readFile;
132674
- fs10.readFile = readFile46;
132675
- function readFile46(path10, options, cb) {
132674
+ fs10.readFile = readFile47;
132675
+ function readFile47(path10, options, cb) {
132676
132676
  if (typeof options === "function")
132677
132677
  cb = options, options = null;
132678
132678
  return go$readFile(path10, options, cb);
@@ -135388,10 +135388,10 @@ async function runHoldReleaseSweep(store, deps) {
135388
135388
  }
135389
135389
  return result2;
135390
135390
  }
135391
- async function issueRelease(store, deps, task, target, ir2) {
135391
+ async function issueRelease(store, deps, task, target, ir2, options = {}) {
135392
135392
  const targetColumn = findColumn2(ir2, target);
135393
135393
  const targetIsProcessing = targetColumn ? resolveColumnFlags(targetColumn).countsTowardWip === true : false;
135394
- if (targetIsProcessing && await isUnplannedForExecution(store, task, ir2)) {
135394
+ if (targetIsProcessing && !options.allowUnplanned && await isUnplannedForExecution(store, task, ir2)) {
135395
135395
  schedulerLog.log(`Hold release for ${task.id} blocked \u2014 card is unplanned and cannot enter processing column ${target}`);
135396
135396
  return false;
135397
135397
  }
@@ -135433,7 +135433,7 @@ async function issueRelease(store, deps, task, target, ir2) {
135433
135433
  return false;
135434
135434
  }
135435
135435
  }
135436
- async function promoteHeldTask(store, taskId, deps = {}) {
135436
+ async function promoteHeldTask(store, taskId, deps = {}, options = {}) {
135437
135437
  const task = await store.getTask(taskId);
135438
135438
  if (!task) return { released: false, rejection: "task-not-found" };
135439
135439
  const ir2 = await resolveWorkflowIrForTask(store, taskId);
@@ -135444,17 +135444,50 @@ async function promoteHeldTask(store, taskId, deps = {}) {
135444
135444
  if (!target) return { released: false, rejection: "no-release-target" };
135445
135445
  const targetColumn = findColumn2(ir2, target);
135446
135446
  const targetIsProcessing = targetColumn ? resolveColumnFlags(targetColumn).countsTowardWip === true : false;
135447
- if (targetIsProcessing && await isUnplannedForExecution(store, task, ir2)) {
135447
+ const unplanned = targetIsProcessing && await isUnplannedForExecution(store, task, ir2);
135448
+ if (unplanned && options.force !== true) {
135448
135449
  return { released: false, rejection: "unplanned-for-execution", toColumn: target };
135449
135450
  }
135451
+ let promoted = task;
135452
+ if (unplanned) {
135453
+ if (task.status === "needs-replan" || task.status === "plan-review-unavailable") {
135454
+ try {
135455
+ await store.updateTask(task.id, { status: null });
135456
+ promoted = { ...task, status: void 0 };
135457
+ } catch (error3) {
135458
+ schedulerLog.warn(
135459
+ `Force-promote for ${task.id} could not clear replan status: ${error3 instanceof Error ? error3.message : String(error3)}`
135460
+ );
135461
+ }
135462
+ }
135463
+ if (typeof store.recordRunAuditEvent === "function") {
135464
+ try {
135465
+ await store.recordRunAuditEvent({
135466
+ domain: "database",
135467
+ mutationType: "task:promote-forced-unplanned",
135468
+ target: task.id,
135469
+ taskId: task.id,
135470
+ agentId: "system",
135471
+ runId: `promote-force-${task.id}`,
135472
+ metadata: { fromColumn: task.column, toColumn: target, priorStatus: task.status ?? null }
135473
+ });
135474
+ } catch {
135475
+ }
135476
+ }
135477
+ schedulerLog.log(`Force-promote for ${task.id} bypassing unplanned gate into ${target} (operator override)`);
135478
+ }
135450
135479
  const released = await issueRelease(
135451
135480
  store,
135452
135481
  { now: () => Date.now(), reserveSlot: deps.reserveSlot, allocateWorktree: deps.allocateWorktree },
135453
- task,
135482
+ promoted,
135454
135483
  target,
135455
- ir2
135484
+ ir2,
135485
+ { allowUnplanned: unplanned }
135456
135486
  );
135457
- return released ? { released: true, toColumn: target } : { released: false, rejection: "capacity-exhausted-or-no-slot" };
135487
+ if (!released) {
135488
+ return { released: false, rejection: "capacity-exhausted-or-no-slot" };
135489
+ }
135490
+ return { released: true, toColumn: target, forcedUnplanned: unplanned || void 0 };
135458
135491
  }
135459
135492
  async function releaseHeldTaskByEvent(store, taskId, eventTag, deps = {}) {
135460
135493
  const task = await store.getTask(taskId);
@@ -138568,6 +138601,12 @@ var init_usage_limit_detector = __esm({
138568
138601
  });
138569
138602
 
138570
138603
  // ../engine/src/transient-error-patterns.ts
138604
+ function isSessionContentionError(errorMessage2) {
138605
+ if (!errorMessage2 || typeof errorMessage2 !== "string") {
138606
+ return false;
138607
+ }
138608
+ return SESSION_CONTENTION_PATTERNS.some((pattern) => pattern.test(errorMessage2));
138609
+ }
138571
138610
  function isTransientError(errorMessage2) {
138572
138611
  if (!errorMessage2 || typeof errorMessage2 !== "string") {
138573
138612
  return false;
@@ -138575,6 +138614,9 @@ function isTransientError(errorMessage2) {
138575
138614
  if (isTransientAuthCredentialError(errorMessage2)) {
138576
138615
  return true;
138577
138616
  }
138617
+ if (isSessionContentionError(errorMessage2)) {
138618
+ return true;
138619
+ }
138578
138620
  return TRANSIENT_ERROR_PATTERNS.some((pattern) => pattern.test(errorMessage2));
138579
138621
  }
138580
138622
  function isTransientAuthCredentialError(errorMessage2) {
@@ -138586,7 +138628,7 @@ function isTransientAuthCredentialError(errorMessage2) {
138586
138628
  }
138587
138629
  return TRANSIENT_AUTH_CREDENTIAL_ROTATION_PATTERN.test(errorMessage2);
138588
138630
  }
138589
- var TRANSIENT_ERROR_PATTERNS, TRANSIENT_AUTH_CREDENTIAL_ROTATION_PATTERN, OPERATOR_ACTIONABLE_AUTH_EXCLUSION_PATTERN;
138631
+ var TRANSIENT_ERROR_PATTERNS, SESSION_CONTENTION_PATTERNS, TRANSIENT_AUTH_CREDENTIAL_ROTATION_PATTERN, OPERATOR_ACTIONABLE_AUTH_EXCLUSION_PATTERN;
138590
138632
  var init_transient_error_patterns = __esm({
138591
138633
  "../engine/src/transient-error-patterns.ts"() {
138592
138634
  "use strict";
@@ -138647,6 +138689,14 @@ var init_transient_error_patterns = __esm({
138647
138689
  /\bACP failed to start\b/i,
138648
138690
  /\bACP session has no live connection\b/i
138649
138691
  ];
138692
+ SESSION_CONTENTION_PATTERNS = [
138693
+ // ActiveSessionPathHeldByForeignTaskError (active-session-registry.ts)
138694
+ /active-session path\s+\S+\s+is held by task\s+\S+/i,
138695
+ // WorkspaceRepoAcquireBusyError (worktree-acquisition.ts)
138696
+ /workspace sub-repo\s+\S+\s+acquisition is in progress for task\s+\S+/i,
138697
+ // WorkspaceRepoLandBusyError (merger-ai.ts)
138698
+ /workspace sub-repo\s+\S+\s+land is in progress for task\s+\S+/i
138699
+ ];
138650
138700
  TRANSIENT_AUTH_CREDENTIAL_ROTATION_PATTERN = /"type":\s*"authentication_error"|invalid authentication credentials|token[_\s]?expired/i;
138651
138701
  OPERATOR_ACTIONABLE_AUTH_EXCLUSION_PATTERN = /oauth token does not meet scope|insufficient[_\s-]?scope|invalid[_\s-]?scope|invalid (?:api[_\s-]?key|x-api-key)|missing\s+(?:\S+\s+)?(?:api[_\s-]?)?key|revoked|suspend(?:ed)?|disabled|deactivated|subscription|account (?:is )?(?:locked|closed|inactive)|access denied/i;
138652
138702
  }
@@ -138655,6 +138705,9 @@ var init_transient_error_patterns = __esm({
138655
138705
  // ../engine/src/transient-error-detector.ts
138656
138706
  function isNonPlanDefectPlanReviewFailure(input) {
138657
138707
  if (input.verdict === "REVISE") return false;
138708
+ if (input.failureValue?.trim().toLowerCase() === "session-contention-hold" || (input.errorMessage ? isSessionContentionError(input.errorMessage) : false)) {
138709
+ return false;
138710
+ }
138658
138711
  const failureValue = input.failureValue?.trim().toLowerCase();
138659
138712
  if (failureValue === "exception" || failureValue === "aborted") return true;
138660
138713
  const errorMessage2 = input.errorMessage?.trim();
@@ -161607,6 +161660,86 @@ var init_research_step_runner = __esm({
161607
161660
  }
161608
161661
  });
161609
161662
 
161663
+ // ../engine/src/plan-artifact-writeback.ts
161664
+ import { readFile as readFile29 } from "node:fs/promises";
161665
+ import { join as join61 } from "node:path";
161666
+ function relativePromptPath(taskId) {
161667
+ return `.fusion/tasks/${taskId}/PROMPT.md`;
161668
+ }
161669
+ async function readIfPresent(path10) {
161670
+ try {
161671
+ return await readFile29(path10, "utf-8");
161672
+ } catch {
161673
+ return null;
161674
+ }
161675
+ }
161676
+ async function reconcileWorktreePlanArtifact(options) {
161677
+ const { store, taskId, rootDir, planningCwd, logger: logger3 } = options;
161678
+ const relPath = relativePromptPath(taskId);
161679
+ const rootContent = await readIfPresent(join61(rootDir, relPath));
161680
+ if (planningCwd === rootDir) {
161681
+ return { outcome: "not-worktree", content: rootContent ?? void 0 };
161682
+ }
161683
+ const worktreeContent = await readIfPresent(join61(planningCwd, relPath));
161684
+ if (worktreeContent === null) {
161685
+ return { outcome: "no-worktree-artifact", content: rootContent ?? void 0 };
161686
+ }
161687
+ if (worktreeContent.trim().length === 0) {
161688
+ return { outcome: "worktree-artifact-empty", content: rootContent ?? void 0 };
161689
+ }
161690
+ if (rootContent === worktreeContent) {
161691
+ return { outcome: "already-authoritative", content: rootContent };
161692
+ }
161693
+ try {
161694
+ await store.updateTask(taskId, { prompt: worktreeContent });
161695
+ logger3?.log?.(
161696
+ `${taskId}: recovered a worktree-local PROMPT.md into the project .fusion folder (${planningCwd})`
161697
+ );
161698
+ return { outcome: "recovered", content: worktreeContent };
161699
+ } catch (error3) {
161700
+ const message = error3 instanceof Error ? error3.message : String(error3);
161701
+ logger3?.warn?.(
161702
+ `${taskId}: failed to copy the worktree-local PROMPT.md back into the project .fusion folder: ${message}`
161703
+ );
161704
+ return { outcome: "recovery-failed", content: rootContent ?? void 0 };
161705
+ }
161706
+ }
161707
+ async function mirrorPlanToProjectDb(store, taskId, content, options = {}) {
161708
+ if (content.trim().length === 0) return false;
161709
+ if (typeof store.upsertTaskDocument !== "function") return false;
161710
+ try {
161711
+ if (typeof store.getTaskDocument === "function") {
161712
+ const existing = await store.getTaskDocument(taskId, PLAN_DOCUMENT_KEY);
161713
+ if (typeof existing?.content === "string" && existing.content === content) return false;
161714
+ }
161715
+ await store.upsertTaskDocument(taskId, {
161716
+ key: PLAN_DOCUMENT_KEY,
161717
+ content,
161718
+ author: options.author ?? "agent"
161719
+ });
161720
+ return true;
161721
+ } catch (error3) {
161722
+ const message = error3 instanceof Error ? error3.message : String(error3);
161723
+ options.logger?.warn?.(`${taskId}: failed to mirror the plan into the project database: ${message}`);
161724
+ return false;
161725
+ }
161726
+ }
161727
+ async function persistPlanArtifact(options) {
161728
+ const result2 = await reconcileWorktreePlanArtifact(options);
161729
+ const mirrored = result2.content ? await mirrorPlanToProjectDb(options.store, options.taskId, result2.content, {
161730
+ author: options.author,
161731
+ logger: options.logger
161732
+ }) : false;
161733
+ return { ...result2, mirrored };
161734
+ }
161735
+ var PLAN_DOCUMENT_KEY;
161736
+ var init_plan_artifact_writeback = __esm({
161737
+ "../engine/src/plan-artifact-writeback.ts"() {
161738
+ "use strict";
161739
+ PLAN_DOCUMENT_KEY = "plan";
161740
+ }
161741
+ });
161742
+
161610
161743
  // ../engine/src/auto-recovery-handlers/message-delivery.ts
161611
161744
  import { setTimeout as delay2 } from "node:timers/promises";
161612
161745
  var baseLog, MessageDeliveryAutoRecoveryHandler;
@@ -163109,6 +163242,23 @@ var init_worktrunk_installer = __esm({
163109
163242
  });
163110
163243
 
163111
163244
  // ../engine/src/active-session-registry.ts
163245
+ function acquireActiveSessionPath(registry3, path10, registration, options = {}) {
163246
+ const existing = registry3.lookupByPath(path10);
163247
+ if (!existing || existing.taskId === registration.taskId) {
163248
+ registry3.registerPath(path10, registration);
163249
+ return { action: "registered" };
163250
+ }
163251
+ const now = options.now?.() ?? Date.now();
163252
+ const ageMs = now - existing.registeredAt;
163253
+ const minIdleMs = options.minIdleMs ?? DEFAULT_SELF_OWNED_MIN_IDLE_MS;
163254
+ const holderIsLive = options.holderLiveProbe?.(existing.taskId, path10) ?? true;
163255
+ if (holderIsLive || ageMs < minIdleMs) {
163256
+ return { action: "contended", holderTaskId: existing.taskId, holderKind: existing.kind, ageMs };
163257
+ }
163258
+ registry3.unregisterPath(path10);
163259
+ registry3.registerPath(path10, registration);
163260
+ return { action: "reclaimed-stale-foreign", holderTaskId: existing.taskId, ageMs };
163261
+ }
163112
163262
  function reconcileSelfOwnedActiveSessionForRemoval(registry3, worktreePath, requestingTaskId, liveBindingProbe, options = {}) {
163113
163263
  const record2 = registry3.lookupByPath(worktreePath);
163114
163264
  if (!record2) {
@@ -163599,7 +163749,7 @@ var init_worktree_prune = __esm({
163599
163749
 
163600
163750
  // ../engine/src/worktree-stale-lock.ts
163601
163751
  import { exec as exec8 } from "node:child_process";
163602
- import { readFile as readFile30, stat as stat8, unlink as unlink9 } from "node:fs/promises";
163752
+ import { readFile as readFile31, stat as stat8, unlink as unlink9 } from "node:fs/promises";
163603
163753
  import { dirname as dirname23, resolve as resolve26 } from "node:path";
163604
163754
  import { promisify as promisify12 } from "node:util";
163605
163755
  function parseIndexLockPath(stderr) {
@@ -163624,7 +163774,7 @@ async function resolveOwningWorktreePath(input) {
163624
163774
  if (!worktreeName) return void 0;
163625
163775
  const gitdirFile = resolve26(dirname23(input.lockPath), "gitdir");
163626
163776
  try {
163627
- const gitdirRef = (await readFile30(gitdirFile, "utf-8")).trim();
163777
+ const gitdirRef = (await readFile31(gitdirFile, "utf-8")).trim();
163628
163778
  if (gitdirRef) {
163629
163779
  const resolvedGitdirRef = resolve26(dirname23(gitdirFile), gitdirRef);
163630
163780
  if (resolvedGitdirRef.endsWith("/.git") || resolvedGitdirRef.endsWith("\\.git")) {
@@ -165003,7 +165153,7 @@ import { exec as exec11, execFile as execFile9 } from "node:child_process";
165003
165153
  import { promisify as promisify15 } from "node:util";
165004
165154
  import { existsSync as existsSync47, lstatSync as lstatSync2, readdirSync as readdirSync7, readFileSync as readFileSync16, rmSync as rmSync5, realpathSync as realpathSync6 } from "node:fs";
165005
165155
  import { mkdir as mkdir24 } from "node:fs/promises";
165006
- import { basename as basename15, dirname as dirname24, join as join61, relative as relative8, resolve as resolve29, isAbsolute as isAbsolute9 } from "node:path";
165156
+ import { basename as basename15, dirname as dirname24, join as join62, relative as relative8, resolve as resolve29, isAbsolute as isAbsolute9 } from "node:path";
165007
165157
  async function getWorktrunkBinary(settings) {
165008
165158
  const cacheKey = `${process.env.HOME ?? ""}::${settings.binaryPath ?? ""}`;
165009
165159
  const cached2 = _worktrunkBinaryCache.get(cacheKey);
@@ -165154,7 +165304,7 @@ async function isRegisteredGitWorktree2(rootDir, worktreePath) {
165154
165304
  return (await getRegisteredWorktreePaths(rootDir)).has(canonicalizePath(worktreePath));
165155
165305
  }
165156
165306
  function hasRequiredWorktreeFiles(worktreePath) {
165157
- return existsSync47(join61(worktreePath, ".git"));
165307
+ return existsSync47(join62(worktreePath, ".git"));
165158
165308
  }
165159
165309
  async function isInsideGitWorkTree2(worktreePath) {
165160
165310
  try {
@@ -165287,7 +165437,7 @@ async function scanIdleWorktrees(rootDir, store, settings) {
165287
165437
  let dirs;
165288
165438
  try {
165289
165439
  const entries = readdirSync7(worktreesDir, { withFileTypes: true });
165290
- dirs = entries.filter((e) => e.isDirectory() && !isAiMergeContainerDir(e.name)).map((e) => join61(worktreesDir, e.name));
165440
+ dirs = entries.filter((e) => e.isDirectory() && !isAiMergeContainerDir(e.name)).map((e) => join62(worktreesDir, e.name));
165291
165441
  } catch (err) {
165292
165442
  const errorMessage2 = err instanceof Error ? err.message : String(err);
165293
165443
  worktreePoolLog.warn(`Failed to read .worktrees/ directory: ${errorMessage2}`);
@@ -165319,7 +165469,7 @@ async function cleanupOrphanedWorktrees(rootDir, store, settings) {
165319
165469
  let dirs = [];
165320
165470
  if (existsSync47(worktreesDir)) {
165321
165471
  try {
165322
- dirs = readdirSync7(worktreesDir, { withFileTypes: true }).filter((e) => e.isDirectory() && !isAiMergeContainerDir(e.name)).map((e) => join61(worktreesDir, e.name));
165472
+ dirs = readdirSync7(worktreesDir, { withFileTypes: true }).filter((e) => e.isDirectory() && !isAiMergeContainerDir(e.name)).map((e) => join62(worktreesDir, e.name));
165323
165473
  } catch (err) {
165324
165474
  const errorMessage2 = err instanceof Error ? err.message : String(err);
165325
165475
  worktreePoolLog.warn(`Failed to read .worktrees/ directory for cleanup: ${errorMessage2}`);
@@ -165397,11 +165547,11 @@ async function reapOrphanWorktrees(projectRoot, settings) {
165397
165547
  entries = readdirSync7(worktreesDir, { withFileTypes: true }).filter((e) => {
165398
165548
  if (!e.isDirectory() || isAiMergeContainerDir(e.name)) return false;
165399
165549
  try {
165400
- return lstatSync2(join61(worktreesDir, e.name)).isDirectory() && !lstatSync2(join61(worktreesDir, e.name)).isSymbolicLink();
165550
+ return lstatSync2(join62(worktreesDir, e.name)).isDirectory() && !lstatSync2(join62(worktreesDir, e.name)).isSymbolicLink();
165401
165551
  } catch {
165402
165552
  return false;
165403
165553
  }
165404
- }).map((e) => ({ name: e.name, fullPath: join61(worktreesDir, e.name) }));
165554
+ }).map((e) => ({ name: e.name, fullPath: join62(worktreesDir, e.name) }));
165405
165555
  } catch (err) {
165406
165556
  const msg = err instanceof Error ? err.message : String(err);
165407
165557
  worktreePoolLog.warn(`reapOrphanWorktrees: failed to read .worktrees/ \u2014 ${msg}`);
@@ -165420,7 +165570,7 @@ async function reapOrphanWorktrees(projectRoot, settings) {
165420
165570
  if (registered2.has(resolvedFull)) {
165421
165571
  continue;
165422
165572
  }
165423
- const dotGit = join61(resolvedFull, ".git");
165573
+ const dotGit = join62(resolvedFull, ".git");
165424
165574
  if (existsSync47(dotGit)) {
165425
165575
  if (!dotGitPointerIsDangling(dotGit)) {
165426
165576
  worktreePoolLog.log(`reapOrphanWorktrees: skipping ${name2} (has .git entry but not in registered list \u2014 may be partially registered)`);
@@ -165786,27 +165936,27 @@ var init_worktree_pool = __esm({
165786
165936
 
165787
165937
  // ../engine/src/worktree-paths.ts
165788
165938
  import { homedir as homedir10 } from "node:os";
165789
- import { basename as basename16, isAbsolute as isAbsolute10, join as join62, relative as relative9, resolve as resolve30 } from "node:path";
165939
+ import { basename as basename16, isAbsolute as isAbsolute10, join as join63, relative as relative9, resolve as resolve30 } from "node:path";
165790
165940
  function isAiMergeContainerDir(name2) {
165791
165941
  return name2 === AI_MERGE_DIRNAME;
165792
165942
  }
165793
165943
  function resolveAiMergeRootPath(rootDir, settings) {
165794
- return join62(resolveWorktreesDir(rootDir, settings), AI_MERGE_DIRNAME);
165944
+ return join63(resolveWorktreesDir(rootDir, settings), AI_MERGE_DIRNAME);
165795
165945
  }
165796
165946
  function resolveLegacyAiMergeRootPath(rootDir) {
165797
- return join62(rootDir, ".fusion", "ai-merge");
165947
+ return join63(rootDir, ".fusion", "ai-merge");
165798
165948
  }
165799
165949
  function resolveWorktreesDir(rootDir, settings) {
165800
165950
  const configured = settings?.worktreesDir;
165801
165951
  if (!configured) {
165802
- return join62(rootDir, ".worktrees");
165952
+ return join63(rootDir, ".worktrees");
165803
165953
  }
165804
165954
  const expandedHome = configured.replace(/^~(?=$|[\\/])/, homedir10());
165805
165955
  const expandedRepo = expandedHome.replaceAll("{repo}", basename16(rootDir));
165806
165956
  return resolve30(rootDir, expandedRepo);
165807
165957
  }
165808
165958
  function resolveTaskWorktreePath(rootDir, settings, worktreeName) {
165809
- return join62(resolveWorktreesDir(rootDir, settings), worktreeName);
165959
+ return join63(resolveWorktreesDir(rootDir, settings), worktreeName);
165810
165960
  }
165811
165961
  async function resolveTaskWorktreePathForBackend(rootDir, worktreeName, settings, backend, branch) {
165812
165962
  if (backend.kind === "worktrunk" && backend.resolveWorktreePath) {
@@ -166920,9 +167070,9 @@ function assertInRootRepoRelPath(repoRelPath, sep17, isAbsolute27, normalize8) {
166920
167070
  async function acquireWorkspaceRepoWorktree(opts) {
166921
167071
  const { repoRelPath, workspaceRootDir, task, store, settings, logger: logger3, secretsStore, audit, runContext, runConfiguredCommand: runConfiguredCommand2, taskEnv } = opts;
166922
167072
  const registry3 = opts.registry ?? activeSessionRegistry;
166923
- const { join: join108, isAbsolute: isAbsolute27, normalize: normalize8, sep: sep17 } = await import("node:path");
167073
+ const { join: join109, isAbsolute: isAbsolute27, normalize: normalize8, sep: sep17 } = await import("node:path");
166924
167074
  assertInRootRepoRelPath(repoRelPath, sep17, isAbsolute27, normalize8);
166925
- const repoAbsPath = join108(workspaceRootDir, repoRelPath);
167075
+ const repoAbsPath = join109(workspaceRootDir, repoRelPath);
166926
167076
  const existing = task.workspaceWorktrees?.[repoRelPath];
166927
167077
  if (existing) {
166928
167078
  let live = existsSync49(existing.worktreePath);
@@ -168646,7 +168796,7 @@ import { execFile as execFile11 } from "node:child_process";
168646
168796
  import { createRequire as createRequire6 } from "node:module";
168647
168797
  import { mkdtemp, rm as rm7, writeFile as writeFile24 } from "node:fs/promises";
168648
168798
  import { tmpdir as tmpdir4 } from "node:os";
168649
- import { join as join63 } from "node:path";
168799
+ import { join as join64 } from "node:path";
168650
168800
  function getTransformSync() {
168651
168801
  if (!cachedTransformSync) {
168652
168802
  const req = createRequire6(import.meta.url);
@@ -168733,9 +168883,9 @@ function readStdin() {
168733
168883
  async function runCodeNode(opts) {
168734
168884
  const timeoutMs = resolveCodeNodeTimeout(opts.timeoutMs);
168735
168885
  const compiled = await compileCodeNodeSource(opts.source);
168736
- const dir = await mkdtemp(join63(tmpdir4(), "fusion-code-node-"));
168737
- const userModuleFile = join63(dir, "user.mjs");
168738
- const harnessFile = join63(dir, "harness.mjs");
168886
+ const dir = await mkdtemp(join64(tmpdir4(), "fusion-code-node-"));
168887
+ const userModuleFile = join64(dir, "user.mjs");
168888
+ const harnessFile = join64(dir, "harness.mjs");
168739
168889
  try {
168740
168890
  await writeFile24(userModuleFile, compiled, "utf8");
168741
168891
  await writeFile24(harnessFile, buildChildHarness(userModuleFile), "utf8");
@@ -168963,11 +169113,11 @@ var init_code_node_runner = __esm({
168963
169113
  });
168964
169114
 
168965
169115
  // ../engine/src/agent-tools.ts
168966
- import { appendFile as appendFile3, mkdir as mkdir26, readFile as readFile31, readdir as readdir9, realpath, stat as stat9, writeFile as writeFile25 } from "node:fs/promises";
169116
+ import { appendFile as appendFile3, mkdir as mkdir26, readFile as readFile32, readdir as readdir9, realpath, stat as stat9, writeFile as writeFile25 } from "node:fs/promises";
168967
169117
  import { existsSync as existsSync50 } from "node:fs";
168968
169118
  import { createHash as createHash16, randomUUID as randomUUID45 } from "node:crypto";
168969
169119
  import { tmpdir as tmpdir5 } from "node:os";
168970
- import { extname as extname3, isAbsolute as isAbsolute12, join as join64, relative as relative11, resolve as resolve32, sep as sep9 } from "node:path";
169120
+ import { extname as extname3, isAbsolute as isAbsolute12, join as join65, relative as relative11, resolve as resolve32, sep as sep9 } from "node:path";
168971
169121
  import { Type as Type2 } from "@earendil-works/pi-ai";
168972
169122
  function sanitizeAgentMemoryId(agentId) {
168973
169123
  return agentId.trim().replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "") || "agent";
@@ -168979,16 +169129,16 @@ function agentDreamsDisplayPath(agentId) {
168979
169129
  return `${AGENT_MEMORY_ROOT3}/${sanitizeAgentMemoryId(agentId)}/${AGENT_DREAMS_FILENAME2}`;
168980
169130
  }
168981
169131
  function agentMemoryDirectory(rootDir, agentId) {
168982
- return join64(rootDir, AGENT_MEMORY_ROOT3, sanitizeAgentMemoryId(agentId));
169132
+ return join65(rootDir, AGENT_MEMORY_ROOT3, sanitizeAgentMemoryId(agentId));
168983
169133
  }
168984
169134
  function agentMemoryFilePath(rootDir, agentId) {
168985
- return join64(agentMemoryDirectory(rootDir, agentId), AGENT_MEMORY_FILENAME2);
169135
+ return join65(agentMemoryDirectory(rootDir, agentId), AGENT_MEMORY_FILENAME2);
168986
169136
  }
168987
169137
  function agentDreamsFilePath(rootDir, agentId) {
168988
- return join64(agentMemoryDirectory(rootDir, agentId), AGENT_DREAMS_FILENAME2);
169138
+ return join65(agentMemoryDirectory(rootDir, agentId), AGENT_DREAMS_FILENAME2);
168989
169139
  }
168990
169140
  function agentDailyFilePath(rootDir, agentId, date5 = /* @__PURE__ */ new Date()) {
168991
- return join64(agentMemoryDirectory(rootDir, agentId), `${date5.toISOString().slice(0, 10)}.md`);
169141
+ return join65(agentMemoryDirectory(rootDir, agentId), `${date5.toISOString().slice(0, 10)}.md`);
168992
169142
  }
168993
169143
  async function readAgentMemoryWorkspaceLongTerm(rootDir, agentId) {
168994
169144
  const safeRoot = typeof rootDir === "string" ? rootDir.trim() : "";
@@ -169002,7 +169152,7 @@ async function readAgentMemoryWorkspaceLongTerm(rootDir, agentId) {
169002
169152
  if (!fileStat.isFile()) {
169003
169153
  return "";
169004
169154
  }
169005
- const content = await readFile31(filePath, "utf-8");
169155
+ const content = await readFile32(filePath, "utf-8");
169006
169156
  return typeof content === "string" ? content.trim() : "";
169007
169157
  } catch {
169008
169158
  return "";
@@ -169081,7 +169231,7 @@ async function listAgentMemoryFiles2(rootDir, agentMemory) {
169081
169231
  }
169082
169232
  for (const entry of entries) {
169083
169233
  if (!DAILY_AGENT_MEMORY_RE2.test(entry)) continue;
169084
- const absPath = join64(dir, entry);
169234
+ const absPath = join65(dir, entry);
169085
169235
  const fileStat = await stat9(absPath);
169086
169236
  if (fileStat.isFile()) {
169087
169237
  files.push({
@@ -169104,7 +169254,7 @@ async function searchAgentMemoryFile(rootDir, agentMemory, query, limit) {
169104
169254
  }
169105
169255
  const results = [];
169106
169256
  for (const file of await listAgentMemoryFiles2(rootDir, agentMemory)) {
169107
- const content = await readFile31(file.absPath, "utf-8");
169257
+ const content = await readFile32(file.absPath, "utf-8");
169108
169258
  const lines = content.split("\n");
169109
169259
  for (let index2 = 0; index2 < lines.length; index2 += 8) {
169110
169260
  const chunk = lines.slice(index2, index2 + 12).join("\n").trim();
@@ -169250,7 +169400,7 @@ function resolveAgentMemoryPath(rootDir, agentId, path10) {
169250
169400
  return null;
169251
169401
  }
169252
169402
  return {
169253
- absPath: join64(agentMemoryDirectory(rootDir, agentId), filename),
169403
+ absPath: join65(agentMemoryDirectory(rootDir, agentId), filename),
169254
169404
  displayPath: `${prefix}${filename}`
169255
169405
  };
169256
169406
  }
@@ -169260,7 +169410,7 @@ async function getAgentMemoryWindow(rootDir, agentMemory, path10, startLine = 1,
169260
169410
  return null;
169261
169411
  }
169262
169412
  await syncAgentMemoryFile(rootDir, agentMemory);
169263
- const content = await readFile31(resolved.absPath, "utf-8");
169413
+ const content = await readFile32(resolved.absPath, "utf-8");
169264
169414
  const lines = content.split("\n");
169265
169415
  const start = Math.max(1, Math.floor(startLine));
169266
169416
  const count2 = Math.max(1, Math.min(Math.floor(lineCount), 200));
@@ -169919,6 +170069,9 @@ function createTaskPromptWriteTool(store, taskId, runContext) {
169919
170069
  if (persisted?.prompt !== params.content) {
169920
170070
  throw new Error("authoritative PROMPT.md read-back did not match the requested content; persistence could not be verified");
169921
170071
  }
170072
+ await mirrorPlanToProjectDb(store, taskId, params.content, {
170073
+ author: runContext?.agentId ?? "agent"
170074
+ });
169922
170075
  return {
169923
170076
  content: [{ type: "text", text: `Updated PROMPT.md for ${taskId}.` }],
169924
170077
  details: {}
@@ -170207,7 +170360,7 @@ async function readArtifactFileFromPath(params, baseDir) {
170207
170360
  if (!mimeType) {
170208
170361
  throw new Error(`Could not infer a MIME type from ${resolvedPath}; pass mimeType explicitly.`);
170209
170362
  }
170210
- const data = await readFile31(canonicalPath);
170363
+ const data = await readFile32(canonicalPath);
170211
170364
  if (params.type === "image") {
170212
170365
  if (!mimeType.startsWith("image/")) {
170213
170366
  throw new Error(`image artifacts require an image/* mimeType, got ${mimeType}.`);
@@ -170550,25 +170703,33 @@ function createTaskPromoteTool(store, currentTaskId) {
170550
170703
  return {
170551
170704
  name: "fn_task_promote",
170552
170705
  label: "Promote Held Task",
170553
- description: "Manually promote a held task out of its hold column, releasing it regardless of the hold's release kind (the explicit operator action a 'manual' hold waits for). Defaults to the current task. Returns the destination column, or a rejection reason when the task is not held or the destination is full.",
170706
+ description: "Manually promote a held task out of its hold column, releasing it regardless of the hold's release kind (the explicit operator action a 'manual' hold waits for). Defaults to the current task. Returns the destination column, or a rejection reason when the task is not held or the destination is full. Pass force:true to start execution even when planning or plan review is still outstanding (that waives the plan gate and cancels the pending replan; capacity still applies).",
170554
170707
  parameters: taskPromoteParams,
170555
170708
  execute: async (_id, params) => {
170556
170709
  const taskId = params.task_id?.trim() || currentTaskId;
170710
+ const force = params.force === true;
170557
170711
  try {
170558
- const outcome = await promoteHeldTask(store, taskId);
170712
+ const outcome = await promoteHeldTask(store, taskId, {}, { force });
170559
170713
  if (outcome.released) {
170714
+ const forcedNote = outcome.forcedUnplanned ? " Forced past the outstanding planning/plan review \u2014 the pending replan was cancelled." : "";
170560
170715
  return {
170561
170716
  content: [{
170562
170717
  type: "text",
170563
- text: `Promoted ${taskId} to column '${outcome.toColumn}'.`
170718
+ text: `Promoted ${taskId} to column '${outcome.toColumn}'.${forcedNote}`
170564
170719
  }],
170565
- details: { taskId, released: true, toColumn: outcome.toColumn }
170720
+ details: {
170721
+ taskId,
170722
+ released: true,
170723
+ toColumn: outcome.toColumn,
170724
+ forcedUnplanned: outcome.forcedUnplanned === true
170725
+ }
170566
170726
  };
170567
170727
  }
170728
+ const forceHint = outcome.rejection === "unplanned-for-execution" ? " Pass force:true to start execution anyway." : "";
170568
170729
  return {
170569
170730
  content: [{
170570
170731
  type: "text",
170571
- text: `ERROR: Could not promote ${taskId}: ${outcome.rejection ?? "unknown"}.`
170732
+ text: `ERROR: Could not promote ${taskId}: ${outcome.rejection ?? "unknown"}.${forceHint}`
170572
170733
  }],
170573
170734
  details: { taskId, released: false, rejection: outcome.rejection },
170574
170735
  isError: true
@@ -172792,6 +172953,7 @@ var init_agent_tools = __esm({
172792
172953
  init_provider_registry();
172793
172954
  init_research_step_runner();
172794
172955
  init_logger3();
172956
+ init_plan_artifact_writeback();
172795
172957
  init_web_fetch();
172796
172958
  init_agent_action_gate();
172797
172959
  init_message_delivery();
@@ -172971,6 +173133,19 @@ var init_agent_tools = __esm({
172971
173133
  taskPromoteParams = Type2.Object({
172972
173134
  task_id: Type2.Optional(
172973
173135
  Type2.String({ description: "Held task to promote. Defaults to the current task." })
173136
+ ),
173137
+ /*
173138
+ FNXC:WorkflowScheduling 2026-07-25-05:40:
173139
+ Agent-native parity with the dashboard's force-promote override: an agent that
173140
+ has read the card and judged the pending replan / Plan Review not worth waiting
173141
+ for can start execution anyway. Opt-in per call and never defaulted on — the
173142
+ automatic surfaces (hold-release sweep, webhook release) still cannot force, so
173143
+ FN-7648 holds for everything that is not an explicit promote request.
173144
+ */
173145
+ force: Type2.Optional(
173146
+ Type2.Boolean({
173147
+ description: "Start execution even when the task is still waiting on planning or plan review (rejection 'unplanned-for-execution'). Waives ONLY that gate \u2014 hold membership and downstream capacity still apply \u2014 and cancels the pending replan. Default false."
173148
+ })
172974
173149
  )
172975
173150
  });
172976
173151
  workflowCreateParams = Type2.Object({
@@ -173488,7 +173663,7 @@ function hasAdvancedPastPlanning(task) {
173488
173663
  if (task.column === "in-progress" || task.column === "in-review" || task.column === "done" || task.column === "archived") {
173489
173664
  return true;
173490
173665
  }
173491
- if (task.worktree != null) {
173666
+ if (task.firstExecutionAt != null || task.executionStartedAt != null) {
173492
173667
  return true;
173493
173668
  }
173494
173669
  if (task.column === "triage") {
@@ -177279,7 +177454,7 @@ Follow instructions precisely and avoid unrelated changes.`,
177279
177454
 
177280
177455
  // ../engine/src/spec-validation/task-document-references.ts
177281
177456
  import { access as access7 } from "node:fs/promises";
177282
- import { join as join65 } from "node:path";
177457
+ import { join as join66 } from "node:path";
177283
177458
  function normalizePathToken(token) {
177284
177459
  return token.replace(/^[`([{"']+/, "").replace(/[`),.;:!?\]}'"]+$/, "");
177285
177460
  }
@@ -177367,7 +177542,7 @@ async function detectDanglingTaskDocReferences(promptContent, opts) {
177367
177542
  }
177368
177543
  const dangling = [];
177369
177544
  for (const [path10, sections] of references.entries()) {
177370
- const absPath = join65(opts.rootDir, path10);
177545
+ const absPath = join66(opts.rootDir, path10);
177371
177546
  if (await existsImpl(absPath)) continue;
177372
177547
  dangling.push({ path: path10, sections: Array.from(sections).sort() });
177373
177548
  }
@@ -177628,8 +177803,8 @@ var init_session_skill_context = __esm({
177628
177803
  });
177629
177804
 
177630
177805
  // ../engine/src/agent-memory-index.ts
177631
- import { readFile as readFile32 } from "node:fs/promises";
177632
- import { join as join66 } from "node:path";
177806
+ import { readFile as readFile33 } from "node:fs/promises";
177807
+ import { join as join67 } from "node:path";
177633
177808
  function clampUtf8(input, maxBytes) {
177634
177809
  if (Buffer.byteLength(input, "utf8") <= maxBytes) return input;
177635
177810
  let out = "";
@@ -177661,7 +177836,7 @@ function parseHeadings(content) {
177661
177836
  }
177662
177837
  async function buildFileSection(sectionHeader, displayPath, fsPath) {
177663
177838
  try {
177664
- const content = await readFile32(fsPath, "utf-8");
177839
+ const content = await readFile33(fsPath, "utf-8");
177665
177840
  const headings = parseHeadings(content);
177666
177841
  const lines = [sectionHeader, `- ${displayPath}`];
177667
177842
  for (const entry of headings) {
@@ -177677,9 +177852,9 @@ async function buildFileSection(sectionHeader, displayPath, fsPath) {
177677
177852
  async function buildMemoryIndex({ rootDir, agentId }) {
177678
177853
  const safeAgentId = agentId.trim().replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "") || "agent";
177679
177854
  const agentDisplayPath = `.fusion/agent-memory/${safeAgentId}/MEMORY.md`;
177680
- const agentFsPath = join66(rootDir, ".fusion", "agent-memory", safeAgentId, "MEMORY.md");
177855
+ const agentFsPath = join67(rootDir, ".fusion", "agent-memory", safeAgentId, "MEMORY.md");
177681
177856
  const projectDisplayPath = ".fusion/memory/MEMORY.md";
177682
- const projectFsPath = join66(rootDir, ".fusion", "memory", "MEMORY.md");
177857
+ const projectFsPath = join67(rootDir, ".fusion", "memory", "MEMORY.md");
177683
177858
  const [agentSection, projectSection] = await Promise.all([
177684
177859
  buildFileSection("## Agent Memory Index (use fn_memory_search / fn_memory_get to read)", agentDisplayPath, agentFsPath),
177685
177860
  buildFileSection("## Project Memory Index", projectDisplayPath, projectFsPath)
@@ -177698,7 +177873,7 @@ var init_agent_memory_index = __esm({
177698
177873
  });
177699
177874
 
177700
177875
  // ../engine/src/agent-instructions.ts
177701
- import { readFile as readFile33, writeFile as writeFile26, mkdir as mkdir27, access as access8 } from "node:fs/promises";
177876
+ import { readFile as readFile34, writeFile as writeFile26, mkdir as mkdir27, access as access8 } from "node:fs/promises";
177702
177877
  import { constants as fsConstants3 } from "node:fs";
177703
177878
  import { isAbsolute as isAbsolute13, resolve as resolve33, relative as relative12, normalize as normalize7, sep as sep10, dirname as dirname28 } from "node:path";
177704
177879
  function trimAndClamp(value, maxLength, label, agentId) {
@@ -177760,7 +177935,7 @@ async function resolveAgentHeartbeatProcedure(agent, rootDir) {
177760
177935
  return null;
177761
177936
  }
177762
177937
  try {
177763
- const content = await readFile33(filePath, "utf-8");
177938
+ const content = await readFile34(filePath, "utf-8");
177764
177939
  const normalized = trimAndClamp(
177765
177940
  content,
177766
177941
  MAX_INSTRUCTIONS_TEXT_LENGTH2,
@@ -177906,7 +178081,7 @@ async function resolveAgentInstructions(agent, rootDir, ratingSummary, inclusion
177906
178081
  const filePath = resolveValidatedInstructionsPath(agent.instructionsPath, rootDir, agent.id);
177907
178082
  if (filePath) {
177908
178083
  try {
177909
- const content = await readFile33(filePath, "utf-8");
178084
+ const content = await readFile34(filePath, "utf-8");
177910
178085
  const normalizedContent = trimAndClamp(
177911
178086
  content,
177912
178087
  MAX_INSTRUCTIONS_TEXT_LENGTH2,
@@ -185913,8 +186088,8 @@ var init_mission_symbol_admission = __esm({
185913
186088
 
185914
186089
  // ../engine/src/scheduler.ts
185915
186090
  import { existsSync as existsSync54 } from "node:fs";
185916
- import { readFile as readFile34 } from "node:fs/promises";
185917
- import { join as join67 } from "node:path";
186091
+ import { readFile as readFile35 } from "node:fs/promises";
186092
+ import { join as join68 } from "node:path";
185918
186093
  function logTaskRouting(taskId, node) {
185919
186094
  const message = `Task ${taskId} routed to node=${node.nodeId ?? "local"} (source=${node.source})`;
185920
186095
  if (node.nodeId === void 0) {
@@ -186547,16 +186722,16 @@ var init_scheduler = __esm({
186547
186722
  if (typeof this.store.getTasksDir !== "function") {
186548
186723
  return { valid: true };
186549
186724
  }
186550
- const taskDir = join67(this.store.getTasksDir(), id);
186725
+ const taskDir = join68(this.store.getTasksDir(), id);
186551
186726
  if (!existsSync54(taskDir)) {
186552
186727
  return { valid: false, reason: "missing directory" };
186553
186728
  }
186554
- const promptPath = join67(taskDir, "PROMPT.md");
186729
+ const promptPath = join68(taskDir, "PROMPT.md");
186555
186730
  if (!existsSync54(promptPath)) {
186556
186731
  return { valid: false, reason: "missing or empty PROMPT.md" };
186557
186732
  }
186558
186733
  try {
186559
- const content = await readFile34(promptPath, "utf-8");
186734
+ const content = await readFile35(promptPath, "utf-8");
186560
186735
  if (!content || content.trim().length === 0) {
186561
186736
  return { valid: false, reason: "missing or empty PROMPT.md" };
186562
186737
  }
@@ -186926,7 +187101,7 @@ var init_scheduler = __esm({
186926
187101
  todo = (await Promise.all(
186927
187102
  todo.map(async (t) => {
186928
187103
  try {
186929
- const content = await readFile34(getPromptPath(this.store.getTasksDir(), t.id), "utf-8");
187104
+ const content = await readFile35(getPromptPath(this.store.getTasksDir(), t.id), "utf-8");
186930
187105
  if (isUnplannedSeedPrompt(content, t.id, t.title, t.description)) return null;
186931
187106
  } catch {
186932
187107
  }
@@ -190493,10 +190668,10 @@ var init_verification_utils = __esm({
190493
190668
  import { exec as exec23 } from "node:child_process";
190494
190669
  import { createHash as createHash18 } from "node:crypto";
190495
190670
  import { existsSync as existsSync55, readFileSync as readFileSync18, writeFileSync as writeFileSync7 } from "node:fs";
190496
- import { join as join68 } from "node:path";
190671
+ import { join as join69 } from "node:path";
190497
190672
  import { promisify as promisify32 } from "node:util";
190498
190673
  function hasInstallState(rootDir) {
190499
- return existsSync55(join68(rootDir, "node_modules")) || existsSync55(join68(rootDir, ".pnp.cjs"));
190674
+ return existsSync55(join69(rootDir, "node_modules")) || existsSync55(join69(rootDir, ".pnp.cjs"));
190500
190675
  }
190501
190676
  function getConfiguredWorktreeInitCommand(settings) {
190502
190677
  const trimmed = settings?.worktreeInitCommand?.trim();
@@ -190505,17 +190680,17 @@ function getConfiguredWorktreeInitCommand(settings) {
190505
190680
  function getDependencySyncCommand(rootDir, settings) {
190506
190681
  const configuredCommand = getConfiguredWorktreeInitCommand(settings);
190507
190682
  if (configuredCommand) return configuredCommand;
190508
- if (existsSync55(join68(rootDir, "pnpm-lock.yaml"))) return "pnpm install --frozen-lockfile";
190509
- if (existsSync55(join68(rootDir, "package-lock.json"))) return "npm install";
190510
- if (existsSync55(join68(rootDir, "yarn.lock"))) return "yarn install --frozen-lockfile";
190511
- if (existsSync55(join68(rootDir, "bun.lock")) || existsSync55(join68(rootDir, "bun.lockb"))) {
190683
+ if (existsSync55(join69(rootDir, "pnpm-lock.yaml"))) return "pnpm install --frozen-lockfile";
190684
+ if (existsSync55(join69(rootDir, "package-lock.json"))) return "npm install";
190685
+ if (existsSync55(join69(rootDir, "yarn.lock"))) return "yarn install --frozen-lockfile";
190686
+ if (existsSync55(join69(rootDir, "bun.lock")) || existsSync55(join69(rootDir, "bun.lockb"))) {
190512
190687
  return "bun install --frozen-lockfile";
190513
190688
  }
190514
190689
  return null;
190515
190690
  }
190516
190691
  function computeLockfileHash(rootDir) {
190517
190692
  for (const name2 of LOCKFILE_CANDIDATES) {
190518
- const p2 = join68(rootDir, name2);
190693
+ const p2 = join69(rootDir, name2);
190519
190694
  if (existsSync55(p2)) {
190520
190695
  try {
190521
190696
  return createHash18("sha256").update(readFileSync18(p2)).digest("hex");
@@ -190528,7 +190703,7 @@ function computeLockfileHash(rootDir) {
190528
190703
  }
190529
190704
  function readInstallMarker(rootDir) {
190530
190705
  try {
190531
- const value = readFileSync18(join68(rootDir, INSTALL_MARKER_RELPATH), "utf-8").trim();
190706
+ const value = readFileSync18(join69(rootDir, INSTALL_MARKER_RELPATH), "utf-8").trim();
190532
190707
  return value || null;
190533
190708
  } catch {
190534
190709
  return null;
@@ -190536,7 +190711,7 @@ function readInstallMarker(rootDir) {
190536
190711
  }
190537
190712
  function writeInstallMarker(rootDir, hash) {
190538
190713
  try {
190539
- writeFileSync7(join68(rootDir, INSTALL_MARKER_RELPATH), hash);
190714
+ writeFileSync7(join69(rootDir, INSTALL_MARKER_RELPATH), hash);
190540
190715
  } catch {
190541
190716
  }
190542
190717
  }
@@ -190627,7 +190802,7 @@ var init_merge_dependency_sync = __esm({
190627
190802
  "../engine/src/merge-dependency-sync.ts"() {
190628
190803
  "use strict";
190629
190804
  execAsync21 = promisify32(exec23);
190630
- INSTALL_MARKER_RELPATH = join68("node_modules", ".fusion-install-marker");
190805
+ INSTALL_MARKER_RELPATH = join69("node_modules", ".fusion-install-marker");
190631
190806
  LOCKFILE_CANDIDATES = ["pnpm-lock.yaml", "package-lock.json", "yarn.lock", "bun.lockb", "bun.lock"];
190632
190807
  INSTALL_TIMEOUT_MS = 3e5;
190633
190808
  }
@@ -191004,7 +191179,7 @@ var init_merger_glob = __esm({
191004
191179
 
191005
191180
  // ../engine/src/merger-workspace-test-commands.ts
191006
191181
  import { existsSync as existsSync56, readdirSync as readdirSync9, readFileSync as readFileSync19 } from "node:fs";
191007
- import { basename as basename18, dirname as dirname31, join as join69 } from "node:path";
191182
+ import { basename as basename18, dirname as dirname31, join as join70 } from "node:path";
191008
191183
  import { execSync as execSync4 } from "node:child_process";
191009
191184
  function quoteArg2(value) {
191010
191185
  return `'${value.replace(/'/g, "'\\''")}'`;
@@ -191035,14 +191210,14 @@ function resolveWorkspacePackageRoots(rootDir, globs) {
191035
191210
  for (const glob of globs) {
191036
191211
  const starIdx = glob.indexOf("*");
191037
191212
  if (starIdx === -1) {
191038
- const candidate = join69(rootDir, glob);
191039
- if (existsSync56(join69(candidate, "package.json"))) {
191213
+ const candidate = join70(rootDir, glob);
191214
+ if (existsSync56(join70(candidate, "package.json"))) {
191040
191215
  roots.push(glob);
191041
191216
  }
191042
191217
  continue;
191043
191218
  }
191044
191219
  const prefix = glob.slice(0, starIdx);
191045
- const parentDir = join69(rootDir, prefix.replace(/\/$/, ""));
191220
+ const parentDir = join70(rootDir, prefix.replace(/\/$/, ""));
191046
191221
  let entries;
191047
191222
  try {
191048
191223
  entries = readdirSync9(parentDir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
@@ -191051,8 +191226,8 @@ function resolveWorkspacePackageRoots(rootDir, globs) {
191051
191226
  }
191052
191227
  for (const entry of entries) {
191053
191228
  const relPath = `${prefix.replace(/\/$/, "")}/${entry}`;
191054
- const absPath = join69(rootDir, relPath);
191055
- if (existsSync56(join69(absPath, "package.json"))) {
191229
+ const absPath = join70(rootDir, relPath);
191230
+ if (existsSync56(join70(absPath, "package.json"))) {
191056
191231
  roots.push(relPath);
191057
191232
  }
191058
191233
  }
@@ -191075,7 +191250,7 @@ function mapChangedFilesToPackageNames(changedFiles, packageRoots, rootDir) {
191075
191250
  }
191076
191251
  if (bestRoot !== null) {
191077
191252
  try {
191078
- const pkgJsonPath = join69(rootDir, bestRoot, "package.json");
191253
+ const pkgJsonPath = join70(rootDir, bestRoot, "package.json");
191079
191254
  const raw = readFileSync19(pkgJsonPath, "utf-8");
191080
191255
  const parsed = JSON.parse(raw);
191081
191256
  if (parsed.name) {
@@ -191092,7 +191267,7 @@ function packageNamesForFiles(rootDir, files) {
191092
191267
  if (files.length === 0) return [];
191093
191268
  let workspaceContent;
191094
191269
  try {
191095
- workspaceContent = readFileSync19(join69(rootDir, "pnpm-workspace.yaml"), "utf-8");
191270
+ workspaceContent = readFileSync19(join70(rootDir, "pnpm-workspace.yaml"), "utf-8");
191096
191271
  } catch {
191097
191272
  return [];
191098
191273
  }
@@ -191103,7 +191278,7 @@ function packageNamesForFiles(rootDir, files) {
191103
191278
  return mapChangedFilesToPackageNames(files, packageRoots, rootDir);
191104
191279
  }
191105
191280
  function deriveScopedPnpmTestCommand(rootDir, baseBranch, branch) {
191106
- const workspacePath = join69(rootDir, "pnpm-workspace.yaml");
191281
+ const workspacePath = join70(rootDir, "pnpm-workspace.yaml");
191107
191282
  let workspaceContent;
191108
191283
  try {
191109
191284
  workspaceContent = readFileSync19(workspacePath, "utf-8");
@@ -191141,7 +191316,7 @@ function deriveScopedPnpmTestCommand(rootDir, baseBranch, branch) {
191141
191316
  function deriveFileScopedPnpmTestCommand(rootDir, baseBranch, branch) {
191142
191317
  let workspaceContent;
191143
191318
  try {
191144
- workspaceContent = readFileSync19(join69(rootDir, "pnpm-workspace.yaml"), "utf-8");
191319
+ workspaceContent = readFileSync19(join70(rootDir, "pnpm-workspace.yaml"), "utf-8");
191145
191320
  } catch {
191146
191321
  return null;
191147
191322
  }
@@ -191183,7 +191358,7 @@ function deriveFileScopedPnpmTestCommand(rootDir, baseBranch, branch) {
191183
191358
  ];
191184
191359
  for (const candidate of candidates) {
191185
191360
  const normalized = candidate.startsWith("./") ? candidate.slice(2) : candidate;
191186
- if (existsSync56(join69(rootDir, normalized))) {
191361
+ if (existsSync56(join70(rootDir, normalized))) {
191187
191362
  resolvedTests.add(normalized);
191188
191363
  }
191189
191364
  }
@@ -191211,7 +191386,7 @@ function deriveFileScopedPnpmTestCommand(rootDir, baseBranch, branch) {
191211
191386
  let name2 = bestRoot;
191212
191387
  try {
191213
191388
  const parsed = JSON.parse(
191214
- readFileSync19(join69(rootDir, bestRoot, "package.json"), "utf-8")
191389
+ readFileSync19(join70(rootDir, bestRoot, "package.json"), "utf-8")
191215
191390
  );
191216
191391
  if (parsed.name) name2 = parsed.name;
191217
191392
  } catch {
@@ -191255,8 +191430,8 @@ function inferDefaultTestCommand(rootDir, explicitTestCommand, explicitBuildComm
191255
191430
  };
191256
191431
  }
191257
191432
  const buildSource = explicitBuildCommand?.trim() ? "explicit" : void 0;
191258
- if (existsSync56(join69(rootDir, "pnpm-lock.yaml"))) {
191259
- if (existsSync56(join69(rootDir, "pnpm-workspace.yaml"))) {
191433
+ if (existsSync56(join70(rootDir, "pnpm-lock.yaml"))) {
191434
+ if (existsSync56(join70(rootDir, "pnpm-workspace.yaml"))) {
191260
191435
  if (baseBranch?.trim() && branch?.trim()) {
191261
191436
  try {
191262
191437
  const scoped = deriveScopedPnpmTestCommand(rootDir, baseBranch.trim(), branch.trim());
@@ -191275,13 +191450,13 @@ function inferDefaultTestCommand(rootDir, explicitTestCommand, explicitBuildComm
191275
191450
  }
191276
191451
  return { command: "pnpm test", testSource: "inferred", buildSource };
191277
191452
  }
191278
- if (existsSync56(join69(rootDir, "yarn.lock"))) {
191453
+ if (existsSync56(join70(rootDir, "yarn.lock"))) {
191279
191454
  return { command: "yarn test", testSource: "inferred", buildSource };
191280
191455
  }
191281
- if (existsSync56(join69(rootDir, "bun.lock")) || existsSync56(join69(rootDir, "bun.lockb"))) {
191456
+ if (existsSync56(join70(rootDir, "bun.lock")) || existsSync56(join70(rootDir, "bun.lockb"))) {
191282
191457
  return { command: "bun test", testSource: "inferred", buildSource };
191283
191458
  }
191284
- if (existsSync56(join69(rootDir, "package-lock.json"))) {
191459
+ if (existsSync56(join70(rootDir, "package-lock.json"))) {
191285
191460
  return { command: "npm test", testSource: "inferred", buildSource };
191286
191461
  }
191287
191462
  return null;
@@ -193558,7 +193733,7 @@ var init_merger_integration_worktree = __esm({
193558
193733
  import { execFile as execFile23, spawn as spawn7 } from "node:child_process";
193559
193734
  import { promisify as promisify43 } from "node:util";
193560
193735
  import { copyFileSync, existsSync as existsSync57, mkdirSync as mkdirSync9, mkdtempSync as mkdtempSync2, readFileSync as readFileSync20, rmSync as rmSync7, writeFileSync as writeFileSync8 } from "node:fs";
193561
- import { dirname as dirname32, join as join70 } from "node:path";
193736
+ import { dirname as dirname32, join as join71 } from "node:path";
193562
193737
  import { tmpdir as tmpdir6 } from "node:os";
193563
193738
  async function runGit4(args, cwd, timeoutMs) {
193564
193739
  const result2 = await execFileAsync16("git", ["-c", "core.quotePath=false", ...args], {
@@ -193644,9 +193819,9 @@ async function syncWorktreeToHead(input) {
193644
193819
  if (mode === "ff-only") {
193645
193820
  return { kind: "skipped-dirty", fromSha: previousSha, reason: "ff-only-mode-requires-clean-tree", dirtyFiles, untrackedFiles };
193646
193821
  }
193647
- const stageDir = mkdtempSync2(join70(tmpdir6(), "fusion-worktree-sync-"));
193648
- const patchPath = join70(stageDir, "edits.patch");
193649
- const untrackedDir = join70(stageDir, "untracked");
193822
+ const stageDir = mkdtempSync2(join71(tmpdir6(), "fusion-worktree-sync-"));
193823
+ const patchPath = join71(stageDir, "edits.patch");
193824
+ const untrackedDir = join71(stageDir, "untracked");
193650
193825
  let preserveStageDir = false;
193651
193826
  try {
193652
193827
  mkdirSync9(untrackedDir, { recursive: true });
@@ -193666,8 +193841,8 @@ async function syncWorktreeToHead(input) {
193666
193841
  }
193667
193842
  }
193668
193843
  for (const rel of untrackedFiles) {
193669
- const src = join70(worktreePath, rel);
193670
- const dst = join70(untrackedDir, rel);
193844
+ const src = join71(worktreePath, rel);
193845
+ const dst = join71(untrackedDir, rel);
193671
193846
  try {
193672
193847
  mkdirSync9(dirname32(dst), { recursive: true });
193673
193848
  copyFileSync(src, dst);
@@ -193730,13 +193905,13 @@ async function syncWorktreeToHead(input) {
193730
193905
  const restored = [];
193731
193906
  const untrackedSkippedAsTracked = [];
193732
193907
  for (const rel of untrackedFiles) {
193733
- const src = join70(untrackedDir, rel);
193908
+ const src = join71(untrackedDir, rel);
193734
193909
  if (!existsSync57(src)) continue;
193735
193910
  if (trackedAtHead.has(rel)) {
193736
193911
  untrackedSkippedAsTracked.push(rel);
193737
193912
  continue;
193738
193913
  }
193739
- const dst = join70(worktreePath, rel);
193914
+ const dst = join71(worktreePath, rel);
193740
193915
  try {
193741
193916
  mkdirSync9(dirname32(dst), { recursive: true });
193742
193917
  const data = readFileSync20(src);
@@ -193805,8 +193980,8 @@ var init_worktree_ref_sync = __esm({
193805
193980
 
193806
193981
  // ../engine/src/merger-scope-auto-widen.ts
193807
193982
  import { exec as exec30 } from "node:child_process";
193808
- import { readFile as readFile35, writeFile as writeFile27 } from "node:fs/promises";
193809
- import { join as join71 } from "node:path";
193983
+ import { readFile as readFile36, writeFile as writeFile27 } from "node:fs/promises";
193984
+ import { join as join72 } from "node:path";
193810
193985
  import { promisify as promisify44 } from "node:util";
193811
193986
  function quoteArg4(value) {
193812
193987
  return `'${value.replace(/'/g, `'\\''`)}'`;
@@ -193945,8 +194120,8 @@ function parseScopeEntries(section) {
193945
194120
  async function appendAutoWidenedScopeToPrompt(params) {
193946
194121
  const { store, taskId, files } = params;
193947
194122
  if (files.length === 0) return [];
193948
- const promptPath = join71(store.getTaskDir(taskId), "PROMPT.md");
193949
- const prompt = await readFile35(promptPath, "utf-8");
194123
+ const promptPath = join72(store.getTaskDir(taskId), "PROMPT.md");
194124
+ const prompt = await readFile36(promptPath, "utf-8");
193950
194125
  const { before, section, after } = splitPromptSections(prompt);
193951
194126
  const existing = parseScopeEntries(section);
193952
194127
  const toAdd = files.filter((file) => !existing.has(file));
@@ -194102,7 +194277,7 @@ import * as childProcess4 from "node:child_process";
194102
194277
  import { promisify as promisify45 } from "node:util";
194103
194278
  import { existsSync as existsSync58, readFileSync as readFileSync21, writeFileSync as writeFileSync9, unlinkSync as unlinkSync3, renameSync as renameSync5 } from "node:fs";
194104
194279
  import { createHash as createHash20 } from "node:crypto";
194105
- import { join as join72, resolve as resolve36 } from "node:path";
194280
+ import { join as join73, resolve as resolve36 } from "node:path";
194106
194281
  import { hostname as hostname2 } from "node:os";
194107
194282
  import { Type as Type4 } from "typebox";
194108
194283
  function mergerCommitEnv() {
@@ -194415,7 +194590,7 @@ async function runDeterministicVerification(store, rootDir, taskId, testCommand,
194415
194590
  const deterministicVerificationMessage = "Running deterministic merge verification" + (hasTestCommand ? ` (test${testSourceDisplayLabel}: ${normalizedTestCommand})` : "") + (hasBuildCommand ? ` (build${buildSource === "inferred" ? " [inferred]" : ""}: ${normalizedBuildCommand})` : "");
194416
194591
  await store.logEntry(taskId, deterministicVerificationMessage);
194417
194592
  await store.appendAgentLog(taskId, deterministicVerificationMessage, "status", void 0, "merger");
194418
- const bootstrapScriptPath = join72(rootDir, "scripts/ensure-test-artifacts.mjs");
194593
+ const bootstrapScriptPath = join73(rootDir, "scripts/ensure-test-artifacts.mjs");
194419
194594
  if (hasTestCommand || hasBuildCommand) {
194420
194595
  if (!existsSync58(bootstrapScriptPath)) {
194421
194596
  const bootstrapMissingMessage = `${taskId}: [verification:bootstrap] script missing at scripts/ensure-test-artifacts.mjs \u2014 skipping preamble`;
@@ -194914,7 +195089,7 @@ async function stashTreesEqual(rootDir, aSha, bSha) {
194914
195089
  }
194915
195090
  function writeActiveMergerStatus(rootDir, taskId) {
194916
195091
  try {
194917
- const statusPath = join72(rootDir, ".git", ACTIVE_MERGER_STATUS_FILENAME);
195092
+ const statusPath = join73(rootDir, ".git", ACTIVE_MERGER_STATUS_FILENAME);
194918
195093
  const payload = {
194919
195094
  taskId,
194920
195095
  pid: process.pid,
@@ -194943,7 +195118,7 @@ function clearActiveMergerStatus(statusPath, taskId) {
194943
195118
  }
194944
195119
  function readActiveMergerStatus(rootDir) {
194945
195120
  try {
194946
- const statusPath = join72(rootDir, ".git", ACTIVE_MERGER_STATUS_FILENAME);
195121
+ const statusPath = join73(rootDir, ".git", ACTIVE_MERGER_STATUS_FILENAME);
194947
195122
  if (!existsSync58(statusPath)) return null;
194948
195123
  const raw = readFileSync21(statusPath, "utf-8");
194949
195124
  const parsed = JSON.parse(raw);
@@ -195516,7 +195691,7 @@ async function findFilesWithConflictMarkers(rootDir, files) {
195516
195691
  const stillConflicted = [];
195517
195692
  for (const file of files) {
195518
195693
  try {
195519
- const fullPath = join72(rootDir, file);
195694
+ const fullPath = join73(rootDir, file);
195520
195695
  if (!existsSync58(fullPath)) continue;
195521
195696
  const { stdout } = await execAsync29(
195522
195697
  `git grep -l -e "^<<<<<<< " -e "^=======$" -e "^>>>>>>> " --no-index -- ${quoteArg3(fullPath)}`,
@@ -195543,7 +195718,7 @@ async function tryRecoverHardFailApply(params) {
195543
195718
  if (!patchText.trim()) {
195544
195719
  mergerLog.warn(`${taskId}: autostash ${sha.slice(0, 7)} produced empty patch; cannot 3-way recover`);
195545
195720
  } else {
195546
- const patchPath = join72(rootDir, ".git", `fusion-autostash-${sha.slice(0, 7)}.patch`);
195721
+ const patchPath = join73(rootDir, ".git", `fusion-autostash-${sha.slice(0, 7)}.patch`);
195547
195722
  writeFileSync9(patchPath, patchText, "utf-8");
195548
195723
  try {
195549
195724
  await execAsync29(`git apply --3way --whitespace=nowarn ${quoteArg3(patchPath)}`, { cwd: rootDir });
@@ -197031,13 +197206,13 @@ async function isCherryPickInProgress(rootDir) {
197031
197206
  cwd: rootDir,
197032
197207
  encoding: "utf-8"
197033
197208
  });
197034
- const cherryPickHeadPath = join72(rootDir, cherryPickHeadPathOut.trim());
197209
+ const cherryPickHeadPath = join73(rootDir, cherryPickHeadPathOut.trim());
197035
197210
  if (existsSync58(cherryPickHeadPath)) return true;
197036
197211
  const { stdout: sequencerPathOut } = await execAsync29("git rev-parse --git-path sequencer", {
197037
197212
  cwd: rootDir,
197038
197213
  encoding: "utf-8"
197039
197214
  });
197040
- const sequencerPath = join72(rootDir, sequencerPathOut.trim());
197215
+ const sequencerPath = join73(rootDir, sequencerPathOut.trim());
197041
197216
  return existsSync58(sequencerPath);
197042
197217
  } catch {
197043
197218
  return false;
@@ -202080,7 +202255,7 @@ import { promisify as promisify46 } from "node:util";
202080
202255
  import { setImmediate as setImmediateCb2 } from "node:timers";
202081
202256
  import { existsSync as existsSync59, mkdirSync as mkdirSync10, readdirSync as readdirSync10, readFileSync as readFileSync22, realpathSync as realpathSync7, rmSync as rmSync8, statSync as statSync10, writeFileSync as writeFileSync10 } from "node:fs";
202082
202257
  import { tmpdir as tmpdir7 } from "node:os";
202083
- import { isAbsolute as isAbsolute14, join as join73, relative as relative13, resolve as resolve37 } from "node:path";
202258
+ import { isAbsolute as isAbsolute14, join as join74, relative as relative13, resolve as resolve37 } from "node:path";
202084
202259
  function resolveRepoLocalAiMergeRoot(rootDir, settings) {
202085
202260
  return resolveAiMergeRootPath(rootDir, settings);
202086
202261
  }
@@ -202113,9 +202288,9 @@ async function preserveWorktreeChanges(repoDir, worktreePath, taskId) {
202113
202288
  return null;
202114
202289
  }
202115
202290
  const diff = (await execAsync30("git diff HEAD --binary", { cwd: worktreePath, encoding: "utf-8", maxBuffer: 10 * 1024 * 1024 })).stdout;
202116
- const recoveryDir = join73(repoDir, ".fusion", "recovery");
202291
+ const recoveryDir = join74(repoDir, ".fusion", "recovery");
202117
202292
  mkdirSync10(recoveryDir, { recursive: true });
202118
- const patchPath = join73(recoveryDir, `${taskId.toLowerCase()}-${formatRecoveryTimestamp()}.patch`);
202293
+ const patchPath = join74(recoveryDir, `${taskId.toLowerCase()}-${formatRecoveryTimestamp()}.patch`);
202119
202294
  writeFileSync10(patchPath, diff, "utf-8");
202120
202295
  return patchPath;
202121
202296
  } catch (error3) {
@@ -202252,7 +202427,7 @@ function isNoTaskDoneFailure2(task) {
202252
202427
  function hasStepProgress2(task) {
202253
202428
  return task.steps.some((step) => step.status !== "pending");
202254
202429
  }
202255
- var log28, worktreeMetadataReconcileLog, execAsync30, yieldEventLoop2, DONE_TASK_INTEGRITY_SWEEP_LIMIT, BOARD_STALL_NOTIFICATION_COOLDOWN_MS, DB_CORRUPTION_NOTIFICATION_COOLDOWN_MS, PHANTOM_EXECUTOR_BINDING_AGE_MULTIPLIER, MAX_NO_PROGRESS_RESUME_ATTEMPTS, APPROVED_TRIAGE_RECOVERY_GRACE_MS, STARVED_REFINEMENT_RECOVERY_GRACE_MS, STARVED_PEER_PROGRESS_THRESHOLD, STARVED_REFINEMENT_ESCALATION_COOLDOWN_MS, ORPHANED_EXECUTION_RECOVERY_GRACE_MS, LEAKED_WORKTREE_SLOT_GRACE_MS, NON_TERMINAL_STEP_STATUSES2, STRANDED_COMPLETED_TODO_ACTIVE_STATUSES, GHOST_REVIEW_PRESERVED_STATUSES, ORPHANED_WITH_WORKTREE_GRACE_MS, MAX_TASK_DONE_RETRIES, RECONCILE_SCOPE_OVERRIDE_MERGE_ACTIVE_STATUS_SET, MAX_STARVATION_DROPS, DEADLOCK_RECOVERY_COOLDOWN_MS, DEFAULT_STALE_MERGING_FANOUT_MIN_AGE_MS, DEFAULT_UNBACKED_MERGING_FANOUT_GRACE_MS, DURABLE_ERROR_RECOVERY_BASE_COOLDOWN_MS, DURABLE_ERROR_RECOVERY_MAX_COOLDOWN_MS, RUNNING_ON_INACTIVE_TASK_STALE_RUN_MS, SelfHealingManager;
202430
+ var log28, worktreeMetadataReconcileLog, execAsync30, yieldEventLoop2, DONE_TASK_INTEGRITY_SWEEP_LIMIT, BOARD_STALL_NOTIFICATION_COOLDOWN_MS, DB_CORRUPTION_NOTIFICATION_COOLDOWN_MS, PHANTOM_EXECUTOR_BINDING_AGE_MULTIPLIER, MAX_NO_PROGRESS_RESUME_ATTEMPTS, PRE_EXECUTION_WORKTREE_MAX_IDLE_MS, APPROVED_TRIAGE_RECOVERY_GRACE_MS, STARVED_REFINEMENT_RECOVERY_GRACE_MS, STARVED_PEER_PROGRESS_THRESHOLD, STARVED_REFINEMENT_ESCALATION_COOLDOWN_MS, ORPHANED_EXECUTION_RECOVERY_GRACE_MS, LEAKED_WORKTREE_SLOT_GRACE_MS, NON_TERMINAL_STEP_STATUSES2, STRANDED_COMPLETED_TODO_ACTIVE_STATUSES, GHOST_REVIEW_PRESERVED_STATUSES, ORPHANED_WITH_WORKTREE_GRACE_MS, MAX_TASK_DONE_RETRIES, RECONCILE_SCOPE_OVERRIDE_MERGE_ACTIVE_STATUS_SET, MAX_STARVATION_DROPS, DEADLOCK_RECOVERY_COOLDOWN_MS, DEFAULT_STALE_MERGING_FANOUT_MIN_AGE_MS, DEFAULT_UNBACKED_MERGING_FANOUT_GRACE_MS, DURABLE_ERROR_RECOVERY_BASE_COOLDOWN_MS, DURABLE_ERROR_RECOVERY_MAX_COOLDOWN_MS, RUNNING_ON_INACTIVE_TASK_STALE_RUN_MS, SelfHealingManager;
202256
202431
  var init_self_healing = __esm({
202257
202432
  "../engine/src/self-healing.ts"() {
202258
202433
  "use strict";
@@ -202308,6 +202483,7 @@ var init_self_healing = __esm({
202308
202483
  DB_CORRUPTION_NOTIFICATION_COOLDOWN_MS = 60 * 60 * 1e3;
202309
202484
  PHANTOM_EXECUTOR_BINDING_AGE_MULTIPLIER = 3;
202310
202485
  MAX_NO_PROGRESS_RESUME_ATTEMPTS = 2;
202486
+ PRE_EXECUTION_WORKTREE_MAX_IDLE_MS = 30 * 24 * 60 * 60 * 1e3;
202311
202487
  APPROVED_TRIAGE_RECOVERY_GRACE_MS = 6e4;
202312
202488
  STARVED_REFINEMENT_RECOVERY_GRACE_MS = 10 * 6e4;
202313
202489
  STARVED_PEER_PROGRESS_THRESHOLD = 3;
@@ -203547,6 +203723,35 @@ var init_self_healing = __esm({
203547
203723
  }
203548
203724
  return false;
203549
203725
  }
203726
+ /**
203727
+ * FNXC:WorkflowRecovery 2026-07-25-09:40:
203728
+ * Single shared decision for "is this branch tip's foreign `Fusion-Task-Id`/`Fusion-Task-Lineage` trailer real
203729
+ * evidence of cross-task contamination, or just an inherited start point?"
203730
+ *
203731
+ * A task branch cut from the integration branch points at whatever landed last. When the task has not committed
203732
+ * anything of its own (planning aborted, spec revise, operator move back to `todo`), the branch tip IS that
203733
+ * previous task's commit — a foreign trailer with zero foreign intent. FN-1406 hit exactly this: `fusion/fn-1406`
203734
+ * was cut from `main` at FN-1401's landed commit, planning ended without a commit, and the reclaim sweep logged
203735
+ * `already-merged rejected ... owner=FN-1401 reason=foreign-task-tip` every pass instead of clearing the stale
203736
+ * worktree/branch metadata.
203737
+ *
203738
+ * Merge-base-to-tip diff proof separates the two: no unique diff means the branch carries no task content, so the
203739
+ * foreign trailer is inherited and the caller may treat the row as stale metadata. The one exception is when the
203740
+ * base ALREADY has an explicit commit for this task — then the branch should have been sitting on the task's own
203741
+ * landed commit, and a foreign tip is genuine misbinding worth rejecting.
203742
+ *
203743
+ * Callers: already-merged recovery (`branchTipForeignOwnership`), branch-misbound recovery
203744
+ * (`isBranchTipMisboundToTask`), and the self-owned-branch reclaim sweep's `tip-already-merged` arm. Keeping the
203745
+ * three on one helper is the point — the reclaim arm drifted without the diff proof and that was the bug.
203746
+ */
203747
+ async foreignTipRejection(input) {
203748
+ const { taskId, lineageId, branchTip, baseBranch, ownership } = input;
203749
+ if (ownership.rejectionReason !== "foreign-task" && ownership.rejectionReason !== "foreign-lineage") return null;
203750
+ const hasNoUniqueDiff = await this.branchHasNoUniqueDiff(branchTip, baseBranch).catch(() => false);
203751
+ const baseAlreadyHasCurrentTask = hasNoUniqueDiff ? await this.baseHasExplicitTaskOwnership(taskId, lineageId, baseBranch).catch(() => false) : false;
203752
+ if (hasNoUniqueDiff && !baseAlreadyHasCurrentTask) return null;
203753
+ return ownership.rejectionReason === "foreign-task" ? { reason: "foreign-task-tip", owner: ownership.ownerTaskId } : { reason: "foreign-lineage-tip", owner: ownership.ownerLineageId };
203754
+ }
203550
203755
  async rejectForeignAlreadyMergedCandidate(input) {
203551
203756
  const { task, candidateSha, candidateOwner, taskBranch, baseBranch, reason, phase } = input;
203552
203757
  await this.store.logEntry(
@@ -203588,20 +203793,14 @@ var init_self_healing = __esm({
203588
203793
  }
203589
203794
  const sha = stdout.trim();
203590
203795
  if (!sha) return null;
203591
- const hasNoUniqueDiff = await this.branchHasNoUniqueDiff(sha, baseBranch).catch(() => false);
203592
203796
  let ownership;
203593
203797
  try {
203594
203798
  ownership = await this.readCommitTaskOwnership(sha, taskId, lineageId);
203595
203799
  } catch {
203596
203800
  return { sha, reason: "ownership-unverifiable" };
203597
203801
  }
203598
- const baseAlreadyHasCurrentTask = hasNoUniqueDiff ? await this.baseHasExplicitTaskOwnership(taskId, lineageId, baseBranch).catch(() => false) : false;
203599
- if ((!hasNoUniqueDiff || baseAlreadyHasCurrentTask) && ownership.rejectionReason === "foreign-task") {
203600
- return { sha, owner: ownership.ownerTaskId, reason: "foreign-task-tip" };
203601
- }
203602
- if ((!hasNoUniqueDiff || baseAlreadyHasCurrentTask) && ownership.rejectionReason === "foreign-lineage") {
203603
- return { sha, owner: ownership.ownerLineageId, reason: "foreign-lineage-tip" };
203604
- }
203802
+ const rejection = await this.foreignTipRejection({ taskId, lineageId, branchTip: sha, baseBranch, ownership });
203803
+ if (rejection) return { sha, owner: rejection.owner, reason: rejection.reason };
203605
203804
  return null;
203606
203805
  }
203607
203806
  async resolveSelfHealingMergeTarget(task, settings, phase) {
@@ -203948,6 +204147,8 @@ var init_self_healing = __esm({
203948
204147
  { name: "reconcile-workspace-partial-lands", fn: () => this.reconcileWorkspacePartialLands() },
203949
204148
  { name: "reclaim-phantom-workspace-land-leases", fn: () => this.reclaimPhantomWorkspaceLandLeases() },
203950
204149
  { name: "reconcile-orphaned-workspace-worktrees", fn: () => this.reconcileOrphanedWorkspaceWorktrees() },
204150
+ // FNXC:PlanningEvacuation 2026-07-25-23:00: reclaim worktrees acquired at planning time by cards that never executed.
204151
+ { name: "reconcile-pre-execution-worktrees", fn: () => this.reconcilePreExecutionWorktrees() },
203951
204152
  { name: "recover-merged-review", fn: () => this.recoverMergedReviewTasks() },
203952
204153
  { name: "recover-already-merged-review", fn: () => this.recoverAlreadyMergedReviewTasks() },
203953
204154
  { name: "recover-post-done-noncontinuable-wedge", fn: () => this.recoverPostDoneNonContinuableWedge() },
@@ -204815,14 +205016,21 @@ var init_self_healing = __esm({
204815
205016
  if (!ownership) {
204816
205017
  continue;
204817
205018
  }
204818
- if (ownership?.rejectionReason === "foreign-task" || ownership?.rejectionReason === "foreign-lineage") {
205019
+ const foreignRejection = await this.foreignTipRejection({
205020
+ taskId: task.id,
205021
+ lineageId: task.lineageId,
205022
+ branchTip: inspection.tipSha,
205023
+ baseBranch: inspection.integrationRef,
205024
+ ownership
205025
+ });
205026
+ if (foreignRejection) {
204819
205027
  await this.rejectForeignAlreadyMergedCandidate({
204820
205028
  task,
204821
205029
  candidateSha: inspection.tipSha,
204822
- candidateOwner: ownership.rejectionReason === "foreign-task" ? ownership.ownerTaskId : ownership.ownerLineageId,
205030
+ candidateOwner: foreignRejection.owner,
204823
205031
  taskBranch: branchName,
204824
205032
  baseBranch: inspection.integrationRef,
204825
- reason: ownership.rejectionReason === "foreign-task" ? "foreign-task-tip" : "foreign-lineage-tip",
205033
+ reason: foreignRejection.reason,
204826
205034
  phase: "tip-already-merged"
204827
205035
  });
204828
205036
  continue;
@@ -208762,7 +208970,7 @@ var init_self_healing = __esm({
208762
208970
  const unrecoverableRepos = [];
208763
208971
  for (const repoRel of repoKeys) {
208764
208972
  const entry = workspaceWorktrees[repoRel];
208765
- const repoRootDir = join73(this.options.rootDir, repoRel);
208973
+ const repoRootDir = join74(this.options.rootDir, repoRel);
208766
208974
  let integrationBranch;
208767
208975
  try {
208768
208976
  integrationBranch = await resolveIntegrationBranch(
@@ -208974,6 +209182,58 @@ var init_self_healing = __esm({
208974
209182
  return 0;
208975
209183
  }
208976
209184
  }
209185
+ /*
209186
+ FNXC:PlanningEvacuation 2026-07-25-23:00 (pre-execution worktree sweep):
209187
+ Planning acquires the task's own worktree, so cards that never reach execution would accumulate
209188
+ worktrees on disk: withdrawn to an intake column, archived from a planner lane, or simply parked.
209189
+ This sweep reclaims them.
209190
+
209191
+ Candidates are addressed from task ROWS (never a directory walk — AGENTS.md forbids unbounded temp
209192
+ scans): tasks holding `worktree` that sit in a non-executing column and carry no execution
209193
+ timestamp. `todo` is deliberately EXCLUDED: a planned card waiting for a WIP slot legitimately
209194
+ keeps its worktree, and churning it would just force a re-acquire minutes later. Every real safety
209195
+ decision (live session, clean branch, execution evidence) belongs to
209196
+ `TaskExecutor.releasePreExecutionWorktree`, so this sweep cannot diverge from the event-driven path
209197
+ that runs on the move itself.
209198
+ */
209199
+ async reconcilePreExecutionWorktrees() {
209200
+ try {
209201
+ const release2 = this.options.releasePreExecutionWorktree;
209202
+ if (!release2) return 0;
209203
+ const settings = await this.store.getSettings();
209204
+ if (settings.globalPause || settings.enginePaused) return 0;
209205
+ const now = Date.now();
209206
+ const parked = await this.store.listTasks({ slim: true });
209207
+ const candidates = parked.filter((task) => {
209208
+ if (!task.worktree || task.deletedAt) return false;
209209
+ if (task.firstExecutionAt || task.executionStartedAt) return false;
209210
+ if (task.column === "todo" || task.column === "in-progress" || task.column === "in-review" || task.column === "done") return false;
209211
+ if (task.paused || task.userPaused) return false;
209212
+ if (task.status != null) return false;
209213
+ if (task.blockedBy || task.overlapBlockedBy || task.nextRecoveryAt) return false;
209214
+ const lastTouchedMs = Math.max(
209215
+ Date.parse(task.columnMovedAt ?? "") || 0,
209216
+ Date.parse(task.updatedAt ?? "") || 0
209217
+ );
209218
+ if (!lastTouchedMs) return false;
209219
+ return now - lastTouchedMs >= PRE_EXECUTION_WORKTREE_MAX_IDLE_MS;
209220
+ });
209221
+ if (candidates.length === 0) return 0;
209222
+ let released = 0;
209223
+ for (const task of candidates) {
209224
+ try {
209225
+ if (await release2(task.id, `parked pre-execution in '${task.column}'`)) released++;
209226
+ } catch (err) {
209227
+ log28.warn(`reconcilePreExecutionWorktrees: release failed for ${task.id}: ${err instanceof Error ? err.message : String(err)}`);
209228
+ }
209229
+ }
209230
+ if (released > 0) log28.log(`reconcilePreExecutionWorktrees: released ${released} pre-execution worktree(s)`);
209231
+ return released;
209232
+ } catch (err) {
209233
+ log28.error(`reconcilePreExecutionWorktrees sweep failed: ${err instanceof Error ? err.message : String(err)}`);
209234
+ return 0;
209235
+ }
209236
+ }
208977
209237
  /*
208978
209238
  FNXC:Workspace 2026-06-22-09:30 (Phase D U1, KTD4 — per-repo worktree cleanup from STORED paths):
208979
209239
  For done/dead workspace tasks, remove each recorded per-repo worktree. The paths are ADDRESSABLE
@@ -209004,7 +209264,7 @@ var init_self_healing = __esm({
209004
209264
  if ((this.orphanWorktreeRemovalFailures.get(worktreePath) ?? 0) >= MAX_STARVATION_DROPS) {
209005
209265
  continue;
209006
209266
  }
209007
- const repoRootDir = join73(this.options.rootDir, repoRel);
209267
+ const repoRootDir = join74(this.options.rootDir, repoRel);
209008
209268
  let success = false;
209009
209269
  let reason = "removed";
209010
209270
  try {
@@ -209925,7 +210185,7 @@ var init_self_healing = __esm({
209925
210185
  if (testWorkerRoot) {
209926
210186
  try {
209927
210187
  for (const entry of readdirSync10(testWorkerRoot)) {
209928
- if (entry.startsWith("redir-")) roots.push(join73(testWorkerRoot, entry));
210188
+ if (entry.startsWith("redir-")) roots.push(join74(testWorkerRoot, entry));
209929
210189
  }
209930
210190
  } catch {
209931
210191
  }
@@ -209939,7 +210199,7 @@ var init_self_healing = __esm({
209939
210199
  } catch {
209940
210200
  continue;
209941
210201
  }
209942
- for (const entry of entries) paths2.push(join73(root, entry));
210202
+ for (const entry of entries) paths2.push(join74(root, entry));
209943
210203
  }
209944
210204
  return paths2;
209945
210205
  }
@@ -210175,14 +210435,10 @@ var init_self_healing = __esm({
210175
210435
  maxBuffer: 1024 * 1024
210176
210436
  });
210177
210437
  const branchTip = tipOut.trim();
210178
- const hasNoUniqueDiff = await this.branchHasNoUniqueDiff(branchTip, baseBranch).catch(() => false);
210179
210438
  const ownership = await this.readCommitTaskOwnership(branchTip, taskId, lineageId);
210180
- const baseAlreadyHasCurrentTask = hasNoUniqueDiff ? await this.baseHasExplicitTaskOwnership(taskId, lineageId, baseBranch).catch(() => false) : false;
210181
- if ((!hasNoUniqueDiff || baseAlreadyHasCurrentTask) && ownership.rejectionReason === "foreign-task") {
210182
- return { misbound: false, branchTip, landed: null, rejection: { reason: "foreign-task-tip", owner: ownership.ownerTaskId } };
210183
- }
210184
- if ((!hasNoUniqueDiff || baseAlreadyHasCurrentTask) && ownership.rejectionReason === "foreign-lineage") {
210185
- return { misbound: false, branchTip, landed: null, rejection: { reason: "foreign-lineage-tip", owner: ownership.ownerLineageId } };
210439
+ const rejection = await this.foreignTipRejection({ taskId, lineageId, branchTip, baseBranch, ownership });
210440
+ if (rejection) {
210441
+ return { misbound: false, branchTip, landed: null, rejection };
210186
210442
  }
210187
210443
  const hasTaskId = ownership.ownerTaskId === taskId;
210188
210444
  const hasLineage = lineageId ? ownership.ownerLineageId === lineageId : false;
@@ -211844,7 +212100,7 @@ var init_self_healing = __esm({
211844
212100
  let processedMarkers = 0;
211845
212101
  for (const task of candidates) {
211846
212102
  try {
211847
- const promptPath = join73(this.options.rootDir, ".fusion", "tasks", task.id, "PROMPT.md");
212103
+ const promptPath = join74(this.options.rootDir, ".fusion", "tasks", task.id, "PROMPT.md");
211848
212104
  if (!existsSync59(promptPath)) {
211849
212105
  continue;
211850
212106
  }
@@ -212231,7 +212487,7 @@ var init_self_healing = __esm({
212231
212487
  if (!existsSync59(worktreesDir)) return 0;
212232
212488
  let dirs;
212233
212489
  try {
212234
- dirs = readdirSync10(worktreesDir, { withFileTypes: true }).filter((e) => e.isDirectory() && !isAiMergeContainerDir(e.name)).map((e) => join73(worktreesDir, e.name));
212490
+ dirs = readdirSync10(worktreesDir, { withFileTypes: true }).filter((e) => e.isDirectory() && !isAiMergeContainerDir(e.name)).map((e) => join74(worktreesDir, e.name));
212235
212491
  } catch (err) {
212236
212492
  log28.warn(`Failed to read .worktrees/ for unregistered orphan reap: ${err instanceof Error ? err.message : String(err)}`);
212237
212493
  return 0;
@@ -212301,7 +212557,7 @@ var init_self_healing = __esm({
212301
212557
  }
212302
212558
  if (entries.length === 0) continue;
212303
212559
  for (const entry of entries) {
212304
- const path10 = join73(tempRoot, entry);
212560
+ const path10 = join74(tempRoot, entry);
212305
212561
  let canonicalPath = path10;
212306
212562
  let cleanupReason = "stale";
212307
212563
  try {
@@ -212539,8 +212795,8 @@ var init_self_healing = __esm({
212539
212795
  // ../engine/src/triage.ts
212540
212796
  import { Type as Type5 } from "@earendil-works/pi-ai";
212541
212797
  import { exec as exec33 } from "node:child_process";
212542
- import { readFile as readFile36, writeFile as writeFile28, rm as rm9 } from "node:fs/promises";
212543
- import { join as join74 } from "node:path";
212798
+ import { readFile as readFile37, writeFile as writeFile28, rm as rm9 } from "node:fs/promises";
212799
+ import { join as join75 } from "node:path";
212544
212800
  import { promisify as promisify47 } from "node:util";
212545
212801
  function inlineTaskListFallback(lines, opts = {}) {
212546
212802
  const maxChars = Math.max(1, Math.floor(opts.maxChars ?? MAX_TASK_LIST_TEXT_CHARS));
@@ -212591,10 +212847,10 @@ async function readAttachmentContents(rootDir, taskId, attachments) {
212591
212847
  if (!attachments || attachments.length === 0) {
212592
212848
  return { attachmentContents, imageContents };
212593
212849
  }
212594
- const { readFile: readFile46 } = await import("node:fs/promises");
212595
- const { join: join108 } = await import("node:path");
212850
+ const { readFile: readFile47 } = await import("node:fs/promises");
212851
+ const { join: join109 } = await import("node:path");
212596
212852
  for (const att of attachments) {
212597
- const filePath = join108(
212853
+ const filePath = join109(
212598
212854
  rootDir,
212599
212855
  ".fusion",
212600
212856
  "tasks",
@@ -212604,7 +212860,7 @@ async function readAttachmentContents(rootDir, taskId, attachments) {
212604
212860
  );
212605
212861
  try {
212606
212862
  if (IMAGE_MIME_TYPES.has(att.mimeType)) {
212607
- const data = await readFile46(filePath);
212863
+ const data = await readFile47(filePath);
212608
212864
  const detectedMimeType = detectImageMimeFromBytes(data);
212609
212865
  const imageMimeType = detectedMimeType ?? att.mimeType;
212610
212866
  if (detectedMimeType && detectedMimeType !== att.mimeType) {
@@ -212621,7 +212877,7 @@ async function readAttachmentContents(rootDir, taskId, attachments) {
212621
212877
  text: null
212622
212878
  });
212623
212879
  } else {
212624
- const data = await readFile46(filePath, "utf-8");
212880
+ const data = await readFile47(filePath, "utf-8");
212625
212881
  const text2 = data.length > TEXT_INLINE_LIMIT ? data.slice(0, TEXT_INLINE_LIMIT) + "\n... (truncated at 50KB)" : data;
212626
212882
  attachmentContents.push({
212627
212883
  originalName: att.originalName,
@@ -212862,6 +213118,7 @@ var init_triage = __esm({
212862
213118
  init_prompt_layers();
212863
213119
  init_fallback_model_observer();
212864
213120
  init_logger3();
213121
+ init_plan_artifact_writeback();
212865
213122
  init_mcp_resolution();
212866
213123
  init_usage_limit_detector();
212867
213124
  init_transient_error_detector();
@@ -212972,6 +213229,33 @@ var init_triage = __esm({
212972
213229
  if (this.processing.has(task.id) || this.hasLivePlanningWork(task.id)) return;
212973
213230
  this.requestImmediatePoll();
212974
213231
  };
213232
+ this.taskEvacuatedFromPlanningHandler = (task) => {
213233
+ if (!task?.id) return;
213234
+ if (typeof task.column !== "string") return;
213235
+ if (task.column === "todo" || task.column === "triage" || task.column === "in-progress") return;
213236
+ if (this.activeSubagentSessions.has(task.id)) {
213237
+ this.disposeSubagentsForTask(task.id, `task moved to ${task.column}`);
213238
+ }
213239
+ const session = this.activeSessions.get(task.id);
213240
+ if (!session) return;
213241
+ planLog.log(`task moved out of planning to '${task.column}' \u2014 terminating triage session for ${task.id}`);
213242
+ this.pauseAborted.add(task.id);
213243
+ this.options.stuckTaskDetector?.untrackTask(task.id);
213244
+ const sessionWithAbort = session;
213245
+ if (typeof sessionWithAbort.abort === "function") {
213246
+ void sessionWithAbort.abort().catch((err) => {
213247
+ planLog.warn(`Failed to abort triage session for ${task.id}: ${err}`);
213248
+ });
213249
+ }
213250
+ this.recordTriageSessionTokenUsageSoon(task.id, session, { agentId: task.assignedAgentId ?? "triage" });
213251
+ session.dispose();
213252
+ this.activeSessions.delete(task.id);
213253
+ if (task.status === "planning") {
213254
+ void Promise.resolve(this.store.updateTask(task.id, { status: null })).catch((err) => {
213255
+ planLog.warn(`${task.id}: failed to clear planning status after evacuation: ${err instanceof Error ? err.message : String(err)}`);
213256
+ });
213257
+ }
213258
+ };
212975
213259
  }
212976
213260
  running = false;
212977
213261
  polling = false;
@@ -213030,6 +213314,8 @@ var init_triage = __esm({
213030
213314
  taskPausedHandler;
213031
213315
  /** FNXC:CodingIdeasWorkflow 2026-07-25-11:20: store-event wake for planning-eligible columns. */
213032
213316
  taskColumnWakeHandler;
213317
+ /** FNXC:PlanningEvacuation 2026-07-25-23:00: stops planning when a card leaves the planner lanes. */
213318
+ taskEvacuatedFromPlanningHandler;
213033
213319
  _approvalRequestStore;
213034
213320
  /**
213035
213321
  * @param store — Task store instance (also used to listen for `settings:updated` events)
@@ -213138,6 +213424,9 @@ var init_triage = __esm({
213138
213424
  this.store.on("task:updated", this.taskColumnWakeHandler);
213139
213425
  this.store.on("task:created", this.taskColumnWakeHandler);
213140
213426
  }
213427
+ if (this.taskEvacuatedFromPlanningHandler && typeof this.store.on === "function") {
213428
+ this.store.on("task:updated", this.taskEvacuatedFromPlanningHandler);
213429
+ }
213141
213430
  this.clearStaleSpecifyingStatuses().catch((err) => {
213142
213431
  planLog.error("Failed to clear stale planning statuses:", err);
213143
213432
  });
@@ -213185,6 +213474,9 @@ var init_triage = __esm({
213185
213474
  this.store.off("task:updated", this.taskColumnWakeHandler);
213186
213475
  this.store.off("task:created", this.taskColumnWakeHandler);
213187
213476
  }
213477
+ if (this.taskEvacuatedFromPlanningHandler && typeof this.store.off === "function") {
213478
+ this.store.off("task:updated", this.taskEvacuatedFromPlanningHandler);
213479
+ }
213188
213480
  this.abortAndDisposeActiveSessions("engine stop");
213189
213481
  planLog.log("Processor stopped");
213190
213482
  }
@@ -213383,8 +213675,8 @@ var init_triage = __esm({
213383
213675
  }
213384
213676
  const settings = await mergeEffectiveSettings(this.store, task, await this.store.getSettings());
213385
213677
  const approvalRequired = resolvePlanApprovalRequired(settings);
213386
- const promptPath = join74(this.rootDir, ".fusion", "tasks", task.id, "PROMPT.md");
213387
- const written = await readFile36(promptPath, "utf-8").catch((err) => {
213678
+ const promptPath = join75(this.rootDir, ".fusion", "tasks", task.id, "PROMPT.md");
213679
+ const written = await readFile37(promptPath, "utf-8").catch((err) => {
213388
213680
  const msg = err instanceof Error ? err.message : String(err);
213389
213681
  planLog.warn(`${task.id}: failed to read PROMPT.md during planning recovery (${promptPath}): ${msg}`);
213390
213682
  return "";
@@ -213422,8 +213714,8 @@ var init_triage = __esm({
213422
213714
  return true;
213423
213715
  }
213424
213716
  async readNonEmptyPromptDraft(taskId, context) {
213425
- const promptPath = join74(this.rootDir, ".fusion", "tasks", taskId, "PROMPT.md");
213426
- const written = await readFile36(promptPath, "utf-8").catch((err) => {
213717
+ const promptPath = join75(this.rootDir, ".fusion", "tasks", taskId, "PROMPT.md");
213718
+ const written = await readFile37(promptPath, "utf-8").catch((err) => {
213427
213719
  const msg = err instanceof Error ? err.message : String(err);
213428
213720
  planLog.warn(`${taskId}: failed to read PROMPT.md during ${context} (${promptPath}): ${msg}`);
213429
213721
  return "";
@@ -213587,8 +213879,8 @@ var init_triage = __esm({
213587
213879
  continue;
213588
213880
  }
213589
213881
  try {
213590
- const promptPath = join74(this.rootDir, ".fusion", "tasks", todoTask.id, "PROMPT.md");
213591
- const content = await readFile36(promptPath, "utf-8");
213882
+ const promptPath = join75(this.rootDir, ".fusion", "tasks", todoTask.id, "PROMPT.md");
213883
+ const content = await readFile37(promptPath, "utf-8");
213592
213884
  if (isUnplannedSeedPrompt(content, todoTask.id, todoTask.title, todoTask.description)) {
213593
213885
  eligibleTodoTasks.push(todoTask);
213594
213886
  }
@@ -213799,7 +214091,7 @@ var init_triage = __esm({
213799
214091
  const detail = await this.store.getTask(task.id);
213800
214092
  const currentTask = detail ?? task;
213801
214093
  const settings = await mergeEffectiveSettings(this.store, currentTask, await this.store.getSettings());
213802
- const promptPath = `.fusion/tasks/${task.id}/PROMPT.md`;
214094
+ const promptPath = relativePromptPath(task.id);
213803
214095
  const isFast = task.executionMode === "fast";
213804
214096
  const leanPlanning = settings.leanPlanning === true || isFast;
213805
214097
  const agentWork = async () => {
@@ -213976,11 +214268,15 @@ You are ${assignedAgent.name}${assignedAgent.title?.trim() ? `, ${assignedAgent.
213976
214268
  planningModel.modelId,
213977
214269
  assignedAgent?.runtimeConfig
213978
214270
  ) : { provider: void 0, modelId: void 0 };
214271
+ const planningCwd = await this.options.acquirePlanningWorktree?.(task.id).catch(() => null) || this.rootDir;
214272
+ if (planningCwd !== this.rootDir) {
214273
+ await this.store.logEntry(task.id, `Planning session running in task worktree ${planningCwd}`).catch(() => void 0);
214274
+ }
213979
214275
  const { session } = await createResolvedAgentSession({
213980
214276
  sessionPurpose: "triage",
213981
214277
  runtimeHint: triageRuntimeHint,
213982
214278
  pluginRunner: this.options.pluginRunner,
213983
- cwd: this.rootDir,
214279
+ cwd: planningCwd,
213984
214280
  systemPrompt: triageSystemPromptFinal,
213985
214281
  systemPromptLayers: triageLayers,
213986
214282
  tools: "coding",
@@ -214131,8 +214427,22 @@ You are ${assignedAgent.name}${assignedAgent.title?.trim() ? `, ${assignedAgent.
214131
214427
  }
214132
214428
  return;
214133
214429
  }
214134
- const written = await readFile36(
214135
- join74(this.rootDir, promptPath),
214430
+ const planPersistence = await persistPlanArtifact({
214431
+ store: this.store,
214432
+ taskId: task.id,
214433
+ rootDir: this.rootDir,
214434
+ planningCwd,
214435
+ author: "triage",
214436
+ logger: { log: (m2) => planLog.log(m2), warn: (m2) => planLog.warn(m2) }
214437
+ });
214438
+ if (planPersistence.outcome === "recovered") {
214439
+ await this.store.logEntry(
214440
+ task.id,
214441
+ "Recovered the plan written inside the task worktree into the project .fusion folder"
214442
+ ).catch(() => void 0);
214443
+ }
214444
+ const written = await readFile37(
214445
+ join75(this.rootDir, promptPath),
214136
214446
  "utf-8"
214137
214447
  ).catch((err) => {
214138
214448
  const msg = err instanceof Error ? err.message : String(err);
@@ -214749,7 +215059,7 @@ Remove or replace these ids and call fn_task_create again.`
214749
215059
  if (isNearDuplicateCanonicalInactive(canonicalTask ?? void 0)) {
214750
215060
  if (canClearInactiveMarker) {
214751
215061
  if (!await this.runIfStillPlanningUnderTaskLock(task, async () => {
214752
- await rm9(join74(this.rootDir, ".fusion", "tasks", task.id, "PROMPT.md"), { force: true });
215062
+ await rm9(join75(this.rootDir, ".fusion", "tasks", task.id, "PROMPT.md"), { force: true });
214753
215063
  })) return;
214754
215064
  await this.updatePlanningStateIfStillCurrent(task, { paused: false, pausedReason: null, status: null });
214755
215065
  }
@@ -214760,7 +215070,7 @@ Remove or replace these ids and call fn_task_create again.`
214760
215070
  if (resolution === "prompt" && keepAcknowledged) {
214761
215071
  if (canClearInactiveMarker) {
214762
215072
  if (!await this.runIfStillPlanningUnderTaskLock(task, async () => {
214763
- await rm9(join74(this.rootDir, ".fusion", "tasks", task.id, "PROMPT.md"), { force: true });
215073
+ await rm9(join75(this.rootDir, ".fusion", "tasks", task.id, "PROMPT.md"), { force: true });
214764
215074
  })) return;
214765
215075
  await this.updatePlanningStateIfStillCurrent(task, {
214766
215076
  paused: false,
@@ -214801,7 +215111,7 @@ Remove or replace these ids and call fn_task_create again.`
214801
215111
  return;
214802
215112
  }
214803
215113
  if (!await this.runIfStillPlanningUnderTaskLock(task, async () => {
214804
- await rm9(join74(this.rootDir, ".fusion", "tasks", task.id, "PROMPT.md"), { force: true });
215114
+ await rm9(join75(this.rootDir, ".fusion", "tasks", task.id, "PROMPT.md"), { force: true });
214805
215115
  })) return;
214806
215116
  if (!await this.updatePlanningStateIfStillCurrent(task, {
214807
215117
  paused: false,
@@ -214866,7 +215176,7 @@ Remove or replace these ids and call fn_task_create again.`
214866
215176
  let nextPrompt = applyOriginalDescription(written, originalDescription);
214867
215177
  nextPrompt = applyFrontendUxCriteria(nextPrompt, parsedFileScope);
214868
215178
  if (nextPrompt !== written) {
214869
- const promptPath = join74(this.rootDir, ".fusion", "tasks", task.id, "PROMPT.md");
215179
+ const promptPath = join75(this.rootDir, ".fusion", "tasks", task.id, "PROMPT.md");
214870
215180
  try {
214871
215181
  if (!await this.runIfStillPlanningUnderTaskLock(task, async () => {
214872
215182
  await writeFile28(promptPath, nextPrompt, "utf-8");
@@ -214878,6 +215188,10 @@ Remove or replace these ids and call fn_task_create again.`
214878
215188
  }
214879
215189
  }
214880
215190
  }
215191
+ await mirrorPlanToProjectDb(this.store, task.id, written, {
215192
+ author: "triage",
215193
+ logger: { warn: (m2) => planLog.warn(m2) }
215194
+ });
214881
215195
  let taskIntentSignature = {
214882
215196
  routePaths: [],
214883
215197
  filePaths: [],
@@ -215124,10 +215438,10 @@ Remove or replace these ids and call fn_task_create again.`
215124
215438
  });
215125
215439
 
215126
215440
  // ../engine/src/review-artifacts/feature-video.ts
215127
- import { access as access9, mkdtemp as mkdtemp2, readFile as readFile37, rm as rm10 } from "node:fs/promises";
215441
+ import { access as access9, mkdtemp as mkdtemp2, readFile as readFile38, rm as rm10 } from "node:fs/promises";
215128
215442
  import { constants as constants2 } from "node:fs";
215129
215443
  import { tmpdir as tmpdir8 } from "node:os";
215130
- import { join as join75 } from "node:path";
215444
+ import { join as join76 } from "node:path";
215131
215445
  function shouldGenerateReviewArtifacts(task, settings) {
215132
215446
  return isReviewArtifactGenerationEligible(settings, task.prompt);
215133
215447
  }
@@ -215153,7 +215467,7 @@ async function generateFeatureVideo(options) {
215153
215467
  let browser;
215154
215468
  let context;
215155
215469
  try {
215156
- recordingDir = await mkdtemp2(join75(tmpdir8(), "fusion-feature-video-"));
215470
+ recordingDir = await mkdtemp2(join76(tmpdir8(), "fusion-feature-video-"));
215157
215471
  try {
215158
215472
  browser = await client.launch({ executablePath, headless: true });
215159
215473
  } catch {
@@ -215171,7 +215485,7 @@ async function generateFeatureVideo(options) {
215171
215485
  await (options.sleep ?? defaultSleep)(Math.min(Math.max(options.durationMs ?? DEFAULT_DURATION_MS, 0), MAX_DURATION_MS));
215172
215486
  await context.close();
215173
215487
  context = void 0;
215174
- const data = await readFile37(await video.path());
215488
+ const data = await readFile38(await video.path());
215175
215489
  if (data.byteLength > (options.maxBytes ?? DEFAULT_MAX_BYTES2)) {
215176
215490
  return { status: "failed", reason: "size-cap-exceeded" };
215177
215491
  }
@@ -215320,16 +215634,16 @@ async function persistBranchState(persistence, state) {
215320
215634
  );
215321
215635
  }
215322
215636
  }
215323
- function resolveJoinConfig(join108) {
215324
- const rawMode = join108.config?.mode;
215637
+ function resolveJoinConfig(join109) {
215638
+ const rawMode = join109.config?.mode;
215325
215639
  let mode = "all";
215326
215640
  if (rawMode === "all" || rawMode === "any") mode = rawMode;
215327
215641
  else if (rawMode && typeof rawMode === "object" && "quorum" in rawMode) {
215328
215642
  const n = rawMode.quorum;
215329
215643
  if (typeof n === "number" && Number.isInteger(n) && n > 0) mode = { quorum: n };
215330
- else throw new WorkflowIrError(`join '${join108.id}' quorum must be a positive integer`);
215644
+ else throw new WorkflowIrError(`join '${join109.id}' quorum must be a positive integer`);
215331
215645
  }
215332
- const rawFail = join108.config?.onBranchFailure;
215646
+ const rawFail = join109.config?.onBranchFailure;
215333
215647
  const onBranchFailure = rawFail === "collect" ? "collect" : "fail-fast";
215334
215648
  return { mode, onBranchFailure };
215335
215649
  }
@@ -215345,9 +215659,9 @@ async function runSplitJoin(split, env) {
215345
215659
  if (branchEdges.length === 0) {
215346
215660
  throw new WorkflowIrError(`split '${split.id}' has no traversable branches`);
215347
215661
  }
215348
- const join108 = findMatchingJoin(branchEdges[0].to, env);
215349
- if (!join108) throw new WorkflowIrError(`split '${split.id}' has no reachable matching join`);
215350
- const joinConfig = resolveJoinConfig(env.nodeMap.get(join108));
215662
+ const join109 = findMatchingJoin(branchEdges[0].to, env);
215663
+ if (!join109) throw new WorkflowIrError(`split '${split.id}' has no reachable matching join`);
215664
+ const joinConfig = resolveJoinConfig(env.nodeMap.get(join109));
215351
215665
  const controller = new AbortController();
215352
215666
  const branchCount = branchEdges.length;
215353
215667
  const required2 = requiredCompletions(joinConfig.mode, branchCount);
@@ -215383,7 +215697,7 @@ async function runSplitJoin(split, env) {
215383
215697
  };
215384
215698
  const branchPromises = branchEdges.map((edge) => {
215385
215699
  const branchId = edge.to;
215386
- return walkBranch(branchId, join108, env, controller.signal, visitedNodeIds).then((result2) => {
215700
+ return walkBranch(branchId, join109, env, controller.signal, visitedNodeIds).then((result2) => {
215387
215701
  branchOutcomes.push({ branchId, outcome: result2.outcome, nodeId: result2.lastNodeId });
215388
215702
  if (result2.outcome === "success") succeeded += 1;
215389
215703
  else failed += 1;
@@ -215401,7 +215715,7 @@ async function runSplitJoin(split, env) {
215401
215715
  });
215402
215716
  const outcome = await joinReached;
215403
215717
  await Promise.allSettled(branchPromises);
215404
- return { joinNodeId: join108, outcome, branchOutcomes, visitedNodeIds };
215718
+ return { joinNodeId: join109, outcome, branchOutcomes, visitedNodeIds };
215405
215719
  }
215406
215720
  async function walkBranch(startNodeId, joinId, env, signal, visitedNodeIds) {
215407
215721
  let currentId = startNodeId;
@@ -216441,12 +216755,13 @@ function recoverPassedPlanReviewFromLatestLog(task) {
216441
216755
  completedAt: latest.timestamp
216442
216756
  };
216443
216757
  }
216444
- var PLAN_REVIEW_PROVIDER_FAILURE_HOLD_VALUE, PLAN_REVIEW_LEASE_HELD_VALUE, WORKFLOW_INTERRUPTED_NODE_ID_CONTEXT_KEY, WORKFLOW_INTERRUPTED_NODE_ABORT_KIND_CONTEXT_KEY, WORKFLOW_OPTIONAL_GROUP_CONTEXT_KEY, WORKFLOW_NODE_ENGINE_PAUSE_ABORT_KIND, WORKFLOW_DRIFT_PARK_CONTEXT_KEY, WorkflowGraphSuspended, MERGE_REGION_KINDS, WorkflowGraphExecutor;
216758
+ var PLAN_REVIEW_PROVIDER_FAILURE_HOLD_VALUE, PLAN_REVIEW_LEASE_HELD_VALUE, SESSION_CONTENTION_HOLD_VALUE, WORKFLOW_INTERRUPTED_NODE_ID_CONTEXT_KEY, WORKFLOW_INTERRUPTED_NODE_ABORT_KIND_CONTEXT_KEY, WORKFLOW_OPTIONAL_GROUP_CONTEXT_KEY, WORKFLOW_NODE_ENGINE_PAUSE_ABORT_KIND, WORKFLOW_DRIFT_PARK_CONTEXT_KEY, WorkflowGraphSuspended, MERGE_REGION_KINDS, WorkflowGraphExecutor;
216445
216759
  var init_workflow_graph_executor = __esm({
216446
216760
  "../engine/src/workflow-graph-executor.ts"() {
216447
216761
  "use strict";
216448
216762
  init_src2();
216449
216763
  init_transient_error_detector();
216764
+ init_transient_error_patterns();
216450
216765
  init_required_workflow_artifacts();
216451
216766
  init_workflow_node_handlers();
216452
216767
  init_workflow_graph_branches();
@@ -216455,6 +216770,7 @@ var init_workflow_graph_executor = __esm({
216455
216770
  init_workflow_node_execution_needs();
216456
216771
  PLAN_REVIEW_PROVIDER_FAILURE_HOLD_VALUE = "plan-review-provider-failure-hold";
216457
216772
  PLAN_REVIEW_LEASE_HELD_VALUE = "plan-review-lease-held";
216773
+ SESSION_CONTENTION_HOLD_VALUE = "session-contention-hold";
216458
216774
  WORKFLOW_INTERRUPTED_NODE_ID_CONTEXT_KEY = "workflow:interruptedNodeId";
216459
216775
  WORKFLOW_INTERRUPTED_NODE_ABORT_KIND_CONTEXT_KEY = "workflow:interruptedNodeAbortKind";
216460
216776
  WORKFLOW_OPTIONAL_GROUP_CONTEXT_KEY = "workflow:optionalGroupActive";
@@ -216774,6 +217090,15 @@ var init_workflow_graph_executor = __esm({
216774
217090
  };
216775
217091
  context[`node:${node.id}:outcome`] = result3.outcome;
216776
217092
  if (result3.value !== void 0) context[`node:${node.id}:value`] = result3.value;
217093
+ const sessionContentionFailure = stepStatus === "failed" && (verdictRaw === SESSION_CONTENTION_HOLD_VALUE || isSessionContentionError(stepOutput ?? stepNotes ?? ""));
217094
+ if (sessionContentionFailure) {
217095
+ this.deps.logTaskEntry?.(
217096
+ `${logPrefix} ${groupName} is waiting on another task to release its session path \u2014 holding in place`
217097
+ );
217098
+ context[`node:${node.id}:outcome`] = "failure";
217099
+ context[`node:${node.id}:value`] = SESSION_CONTENTION_HOLD_VALUE;
217100
+ return { outcome: "failure", value: SESSION_CONTENTION_HOLD_VALUE };
217101
+ }
216777
217102
  const nonPlanDefectPlanReviewFailure = node.id === PLAN_REVIEW_GROUP_ID && stepStatus === "failed" && (isRequiredArtifactReadFailedValue(verdictRaw) || isNonPlanDefectPlanReviewFailure({
216778
217103
  verdict,
216779
217104
  errorMessage: stepOutput ?? stepNotes,
@@ -217157,6 +217482,7 @@ var init_workflow_graph_executor = __esm({
217157
217482
  } catch (error3) {
217158
217483
  if (signal?.aborted) return this.withEnginePauseAbortContext(node, { outcome: "failure", value: "aborted" });
217159
217484
  lastError = error3;
217485
+ if (isSessionContentionError(error3 instanceof Error ? error3.message : String(error3))) break;
217160
217486
  }
217161
217487
  }
217162
217488
  if (signal?.aborted) {
@@ -217175,11 +217501,13 @@ var init_workflow_graph_executor = __esm({
217175
217501
  }
217176
217502
  return degraded;
217177
217503
  }
217504
+ const lastErrorText = lastError instanceof Error ? lastError.message : String(lastError);
217178
217505
  const failureResult = {
217179
217506
  outcome: "failure",
217180
- value: "exception",
217507
+ // FNXC:SessionContention 2026-07-25-21:30: contention is a retryable hold, not an exception.
217508
+ value: isSessionContentionError(lastErrorText) ? SESSION_CONTENTION_HOLD_VALUE : "exception",
217181
217509
  contextPatch: {
217182
- [`node:${node.id}:error`]: lastError instanceof Error ? lastError.message : String(lastError)
217510
+ [`node:${node.id}:error`]: lastErrorText
217183
217511
  }
217184
217512
  };
217185
217513
  if (recordProgress && this.shouldRecordNodeProgress(node)) {
@@ -218803,7 +219131,7 @@ var init_workspace_paths = __esm({
218803
219131
 
218804
219132
  // ../engine/src/cli-agent/hook-scripts.ts
218805
219133
  import { mkdir as mkdir28, rm as rm11, writeFile as writeFile29 } from "node:fs/promises";
218806
- import { join as join76 } from "node:path";
219134
+ import { join as join77 } from "node:path";
218807
219135
  function shellSingleQuote(value) {
218808
219136
  return `'${value.replace(/'/g, `'\\''`)}'`;
218809
219137
  }
@@ -218875,8 +219203,8 @@ exit 0
218875
219203
  async function writeSessionHookScripts(opts) {
218876
219204
  const { sessionId, token, endpointUrl, dir } = opts;
218877
219205
  await mkdir28(dir, { recursive: true, mode: 448 });
218878
- const hookScriptPath = join76(dir, HOOK_SCRIPT_NAMES.hook);
218879
- const notifyScriptPath = join76(dir, HOOK_SCRIPT_NAMES.notify);
219206
+ const hookScriptPath = join77(dir, HOOK_SCRIPT_NAMES.hook);
219207
+ const notifyScriptPath = join77(dir, HOOK_SCRIPT_NAMES.notify);
218880
219208
  await writeFile29(hookScriptPath, buildHookScriptContent({ sessionId, token, endpointUrl }), {
218881
219209
  mode: 448
218882
219210
  });
@@ -219035,7 +219363,7 @@ var init_autonomy = __esm({
219035
219363
  // ../engine/src/cli-agent/task-session.ts
219036
219364
  import { mkdtemp as mkdtemp3, rm as rm12 } from "node:fs/promises";
219037
219365
  import { tmpdir as tmpdir9 } from "node:os";
219038
- import { join as join77 } from "node:path";
219366
+ import { join as join78 } from "node:path";
219039
219367
  function launchCliTaskSession(opts) {
219040
219368
  return CliTaskSession.launch(opts);
219041
219369
  }
@@ -219101,9 +219429,9 @@ var init_task_session = __esm({
219101
219429
  isApproved: opts.isAutonomyApproved ?? (() => false)
219102
219430
  });
219103
219431
  const root = opts.hookDirRoot ?? tmpdir9();
219104
- const hookDir = await mkdtemp3(join77(root, "fusion-cli-hooks-"));
219105
- const hookScriptPath = join77(hookDir, HOOK_SCRIPT_NAMES.hook);
219106
- const settingsPath = join77(hookDir, "settings.json");
219432
+ const hookDir = await mkdtemp3(join78(root, "fusion-cli-hooks-"));
219433
+ const hookScriptPath = join78(hookDir, HOOK_SCRIPT_NAMES.hook);
219434
+ const settingsPath = join78(hookDir, "settings.json");
219107
219435
  const cliAgentSettings = opts.config.cliAgentSettings ?? null;
219108
219436
  const operatorEnvAdditions = (cliAgentSettings?.envAdditions ?? []).filter(
219109
219437
  (k2) => !/^FUSION_/i.test(k2)
@@ -219345,7 +219673,7 @@ var init_task_session = __esm({
219345
219673
  // ../engine/src/pty-native.ts
219346
219674
  import * as fs7 from "node:fs";
219347
219675
  import { createRequire as createRequire7 } from "node:module";
219348
- import { join as join78, dirname as dirname33 } from "node:path";
219676
+ import { join as join79, dirname as dirname33 } from "node:path";
219349
219677
  function getNativePrebuildName() {
219350
219678
  const platform4 = process.platform === "darwin" ? "darwin" : process.platform === "linux" ? "linux" : process.platform === "win32" ? "win32" : "unknown";
219351
219679
  const arch = process.arch === "arm64" ? "arm64" : process.arch === "x64" ? "x64" : "unknown";
@@ -219355,12 +219683,12 @@ function findInstalledNodePtyNativeDir() {
219355
219683
  try {
219356
219684
  const packageJsonPath = require4.resolve("node-pty/package.json");
219357
219685
  const pkgRoot = dirname33(packageJsonPath);
219358
- const releaseDir = join78(pkgRoot, "build", "Release");
219359
- if (fs7.existsSync(join78(releaseDir, "pty.node"))) {
219686
+ const releaseDir = join79(pkgRoot, "build", "Release");
219687
+ if (fs7.existsSync(join79(releaseDir, "pty.node"))) {
219360
219688
  return releaseDir;
219361
219689
  }
219362
- const prebuildDir = join78(pkgRoot, "prebuilds", getNativePrebuildName());
219363
- if (fs7.existsSync(join78(prebuildDir, "pty.node"))) {
219690
+ const prebuildDir = join79(pkgRoot, "prebuilds", getNativePrebuildName());
219691
+ if (fs7.existsSync(join79(prebuildDir, "pty.node"))) {
219364
219692
  return prebuildDir;
219365
219693
  }
219366
219694
  return null;
@@ -219371,14 +219699,14 @@ function findInstalledNodePtyNativeDir() {
219371
219699
  function findStagedNativeDir() {
219372
219700
  const prebuildName = getNativePrebuildName();
219373
219701
  if (process.env.FUSION_RUNTIME_DIR) {
219374
- const envPath = join78(process.env.FUSION_RUNTIME_DIR, prebuildName);
219375
- if (fs7.existsSync(join78(envPath, "pty.node"))) {
219702
+ const envPath = join79(process.env.FUSION_RUNTIME_DIR, prebuildName);
219703
+ if (fs7.existsSync(join79(envPath, "pty.node"))) {
219376
219704
  return envPath;
219377
219705
  }
219378
219706
  }
219379
219707
  const execDir = dirname33(process.execPath);
219380
- const nextToBinary = join78(execDir, "runtime", prebuildName);
219381
- if (fs7.existsSync(join78(nextToBinary, "pty.node"))) {
219708
+ const nextToBinary = join79(execDir, "runtime", prebuildName);
219709
+ if (fs7.existsSync(join79(nextToBinary, "pty.node"))) {
219382
219710
  return nextToBinary;
219383
219711
  }
219384
219712
  return null;
@@ -219401,8 +219729,8 @@ function ensureNodePtyNativePermissions() {
219401
219729
  candidateDirs.add(installedNativeDir);
219402
219730
  }
219403
219731
  for (const nativeDir of candidateDirs) {
219404
- const helperPath = join78(nativeDir, "spawn-helper");
219405
- const nativeModulePath = join78(nativeDir, "pty.node");
219732
+ const helperPath = join79(nativeDir, "spawn-helper");
219733
+ const nativeModulePath = join79(nativeDir, "pty.node");
219406
219734
  try {
219407
219735
  fs7.chmodSync(helperPath, 493);
219408
219736
  } catch {
@@ -219432,7 +219760,7 @@ async function loadPtyModule() {
219432
219760
  process.env.NODE_PTY_SPAWN_HELPER_DIR = nativeDir;
219433
219761
  }
219434
219762
  process.env.FUSION_NATIVE_ASSETS_PATH = nativeDir;
219435
- const nativePath = join78(nativeDir, "pty.node");
219763
+ const nativePath = join79(nativeDir, "pty.node");
219436
219764
  if (fs7.existsSync(nativePath)) {
219437
219765
  try {
219438
219766
  const nativeModule = { exports: {} };
@@ -220971,7 +221299,7 @@ var init_file_scope2 = __esm({
220971
221299
 
220972
221300
  // ../engine/src/run-verification-tool.ts
220973
221301
  import { existsSync as existsSync64, readdirSync as readdirSync11, readFileSync as readFileSync23 } from "node:fs";
220974
- import { isAbsolute as isAbsolute16, join as join79, relative as relative14 } from "node:path";
221302
+ import { isAbsolute as isAbsolute16, join as join80, relative as relative14 } from "node:path";
220975
221303
  import { Type as Type6 } from "@earendil-works/pi-ai";
220976
221304
  function shellSplit(input) {
220977
221305
  const tokens = [];
@@ -221086,7 +221414,7 @@ function findWorkspacePackageDir(rootDir, packageName) {
221086
221414
  for (const candidate of candidates) {
221087
221415
  if (!candidate) continue;
221088
221416
  try {
221089
- const pkg = JSON.parse(readFileSync23(join79(rootDir, candidate, "package.json"), "utf8"));
221417
+ const pkg = JSON.parse(readFileSync23(join80(rootDir, candidate, "package.json"), "utf8"));
221090
221418
  if (pkg.name === packageName) {
221091
221419
  packageDirCache.set(cacheKey, candidate);
221092
221420
  return candidate;
@@ -221101,7 +221429,7 @@ function findWorkspacePackageDir(rootDir, packageName) {
221101
221429
  while (queue.length > 0) {
221102
221430
  const current = queue.shift();
221103
221431
  if (!current) break;
221104
- const abs = join79(rootDir, current.dir);
221432
+ const abs = join80(rootDir, current.dir);
221105
221433
  let entries;
221106
221434
  try {
221107
221435
  entries = readdirSync11(abs, { withFileTypes: true });
@@ -221111,9 +221439,9 @@ function findWorkspacePackageDir(rootDir, packageName) {
221111
221439
  for (const entry of entries) {
221112
221440
  if (!entry.isDirectory()) continue;
221113
221441
  if (entry.name === "node_modules" || entry.name === "dist") continue;
221114
- const child = join79(current.dir, entry.name);
221442
+ const child = join80(current.dir, entry.name);
221115
221443
  try {
221116
- const pkg = JSON.parse(readFileSync23(join79(rootDir, child, "package.json"), "utf8"));
221444
+ const pkg = JSON.parse(readFileSync23(join80(rootDir, child, "package.json"), "utf8"));
221117
221445
  if (pkg.name === packageName) {
221118
221446
  packageDirCache.set(cacheKey, child);
221119
221447
  return child;
@@ -221133,7 +221461,7 @@ function toPackageRelativeFilter(token, rootDir, packageDir) {
221133
221461
  return normalized.slice(normalizedPackageDir.length + 1);
221134
221462
  }
221135
221463
  if (isAbsolute16(token)) {
221136
- const rel = relative14(join79(rootDir, packageDir), token).replace(/\\/g, "/");
221464
+ const rel = relative14(join80(rootDir, packageDir), token).replace(/\\/g, "/");
221137
221465
  if (!rel.startsWith("../") && rel !== "..") return rel;
221138
221466
  }
221139
221467
  return token;
@@ -221390,7 +221718,7 @@ function createRunVerificationTool(opts) {
221390
221718
  if (params.cwd && isAbsolute16(params.cwd)) {
221391
221719
  resolvedCwd = params.cwd;
221392
221720
  } else if (params.cwd) {
221393
- resolvedCwd = join79(worktreePath, params.cwd);
221721
+ resolvedCwd = join80(worktreePath, params.cwd);
221394
221722
  } else {
221395
221723
  resolvedCwd = worktreePath;
221396
221724
  }
@@ -221423,7 +221751,7 @@ function createRunVerificationTool(opts) {
221423
221751
  warnings.push(...normalized.warnings);
221424
221752
  }
221425
221753
  if (effectiveCommand.trimStart().startsWith("pnpm --filter")) {
221426
- const modulesYaml = join79(rootDir, "node_modules", ".modules.yaml");
221754
+ const modulesYaml = join80(rootDir, "node_modules", ".modules.yaml");
221427
221755
  if (!existsSync64(modulesYaml)) {
221428
221756
  const installCmd = "pnpm install --prefer-offline";
221429
221757
  const msg = `node_modules/.modules.yaml not found in workspace root \u2014 auto-prepending \`${installCmd}\` before running the command.`;
@@ -221768,9 +222096,9 @@ var init_pseudo_pause = __esm({
221768
222096
  import { exec as exec38, execFile as execFile26, execSync as execSync7 } from "node:child_process";
221769
222097
  import { promisify as promisify53 } from "node:util";
221770
222098
  import { setImmediate as setImmediateCb3 } from "node:timers";
221771
- import { basename as basename19, delimiter as delimiter2, isAbsolute as isAbsolute18, join as join80, relative as relative16, resolve as resolvePath3 } from "node:path";
222099
+ import { basename as basename19, delimiter as delimiter2, isAbsolute as isAbsolute18, join as join81, relative as relative16, resolve as resolvePath3 } from "node:path";
221772
222100
  import { existsSync as existsSync65, lstatSync as lstatSync3, realpathSync as realpathSync9 } from "node:fs";
221773
- import { readFile as readFile38, rm as rm13, writeFile as writeFile30 } from "node:fs/promises";
222101
+ import { readFile as readFile39, rm as rm13, writeFile as writeFile30 } from "node:fs/promises";
221774
222102
  import { Type as Type7 } from "@earendil-works/pi-ai";
221775
222103
  import { SessionManager as SessionManager2 } from "@earendil-works/pi-coding-agent";
221776
222104
  function mergeAdditionalSkillPaths2(...pathGroups) {
@@ -221784,7 +222112,7 @@ function isWorkflowStepSkillDiscoverable(skillName, additionalSkillPaths, ceSkil
221784
222112
  return false;
221785
222113
  }
221786
222114
  return (additionalSkillPaths ?? []).some(
221787
- (skillPath) => basename19(skillPath) === bareSkillName && existsSync65(join80(skillPath, "SKILL.md")) || existsSync65(join80(skillPath, bareSkillName, "SKILL.md"))
222115
+ (skillPath) => basename19(skillPath) === bareSkillName && existsSync65(join81(skillPath, "SKILL.md")) || existsSync65(join81(skillPath, bareSkillName, "SKILL.md"))
221788
222116
  );
221789
222117
  }
221790
222118
  function getPromptSection(prompt, heading) {
@@ -222093,7 +222421,7 @@ function normalizeWorktreePath(pathValue) {
222093
222421
  }
222094
222422
  async function extractPersistedSessionWorktreePath(sessionFile, rootDir, settings) {
222095
222423
  try {
222096
- const content = await readFile38(sessionFile, "utf-8");
222424
+ const content = await readFile39(sessionFile, "utf-8");
222097
222425
  const matches = content.match(buildSessionWorktreePathRegex(rootDir, settings)) ?? [];
222098
222426
  if (matches.length === 0) return null;
222099
222427
  const normalizedCounts = /* @__PURE__ */ new Map();
@@ -222507,7 +222835,7 @@ function preservePreExecutionWorkflowStepResults(task) {
222507
222835
  }
222508
222836
  ];
222509
222837
  }
222510
- var execAsync35, execFileAsync19, WORKFLOW_THINKING_LEVEL_SET, yieldEventLoop3, tokenCacheMetricsLog, OPTIONAL_STEP_REVISION_KEY_MARKER2, STEP_STATUSES, MAX_WORKFLOW_STEP_RETRIES, MAX_TASK_DONE_SESSION_RETRIES, MAX_TASK_DONE_REQUEUE_RETRIES, MAX_TRANSIENT_GRAPH_RESUME_RETRIES, TRANSIENT_GRAPH_RESUME_RETRY_BACKOFF_MS, COMPLETED_TASK_WATCHDOG_MS, WORKFLOW_RERUN_WATCHDOG_MS, LOOP_COMPACTION_TIMEOUT_MS, TASK_DONE_REFUSAL_SUFFIX, WORKFLOW_SCRIPT_OUTPUT_MAX_CHARS, NonRetryableWorktreeError, taskUpdateParams, taskAddDepParams, spawnAgentParams, USER_QUESTION_TOOL_NAMES, FUSION_WORKFLOW_STEP_CONVENTIONS_PREAMBLE, EXECUTOR_SYSTEM_PROMPT, TaskExecutor;
222838
+ var execAsync35, execFileAsync19, WORKFLOW_THINKING_LEVEL_SET, yieldEventLoop3, tokenCacheMetricsLog, OPTIONAL_STEP_REVISION_KEY_MARKER2, STEP_STATUSES, MAX_WORKFLOW_STEP_RETRIES, MAX_TASK_DONE_SESSION_RETRIES, MAX_TASK_DONE_REQUEUE_RETRIES, MAX_TRANSIENT_GRAPH_RESUME_RETRIES, TRANSIENT_GRAPH_RESUME_RETRY_BACKOFF_MS, MAX_SESSION_CONTENTION_HOLD_RETRIES, SESSION_CONTENTION_HOLD_BACKOFF_MS, SESSION_CONTENTION_HOLD_MAX_BACKOFF_MS, COMPLETED_TASK_WATCHDOG_MS, WORKFLOW_RERUN_WATCHDOG_MS, LOOP_COMPACTION_TIMEOUT_MS, TASK_DONE_REFUSAL_SUFFIX, WORKFLOW_SCRIPT_OUTPUT_MAX_CHARS, NonRetryableWorktreeError, taskUpdateParams, taskAddDepParams, spawnAgentParams, USER_QUESTION_TOOL_NAMES, FUSION_WORKFLOW_STEP_CONVENTIONS_PREAMBLE, EXECUTOR_SYSTEM_PROMPT, TaskExecutor;
222511
222839
  var init_executor = __esm({
222512
222840
  "../engine/src/executor.ts"() {
222513
222841
  "use strict";
@@ -222620,6 +222948,9 @@ var init_executor = __esm({
222620
222948
  MAX_TASK_DONE_REQUEUE_RETRIES = 3;
222621
222949
  MAX_TRANSIENT_GRAPH_RESUME_RETRIES = 2;
222622
222950
  TRANSIENT_GRAPH_RESUME_RETRY_BACKOFF_MS = process.env.VITEST || process.env.NODE_ENV === "test" ? 0 : 1e3;
222951
+ MAX_SESSION_CONTENTION_HOLD_RETRIES = 10;
222952
+ SESSION_CONTENTION_HOLD_BACKOFF_MS = process.env.VITEST || process.env.NODE_ENV === "test" ? 0 : 5e3;
222953
+ SESSION_CONTENTION_HOLD_MAX_BACKOFF_MS = 6e4;
222623
222954
  COMPLETED_TASK_WATCHDOG_MS = 6e4;
222624
222955
  WORKFLOW_RERUN_WATCHDOG_MS = 15e3;
222625
222956
  LOOP_COMPACTION_TIMEOUT_MS = 6e4;
@@ -222980,6 +223311,15 @@ The tool prevents your session from being killed by the inactivity watchdog duri
222980
223311
  }
222981
223312
  })
222982
223313
  );
223314
+ } else if ((from === "todo" || from === "triage") && to2 !== "in-progress" && to2 !== "in-review" && to2 !== "done") {
223315
+ this.trackTaskDisposal(
223316
+ task.id,
223317
+ this.awaitAbortInFlightTaskWork(task.id, `task moved out of planning to ${to2}`, {
223318
+ userCanceled: source === "user"
223319
+ }).then(async () => {
223320
+ await this.releasePreExecutionWorktree(task.id, `moved to ${to2}`);
223321
+ })
223322
+ );
222983
223323
  } else if (from === "in-progress") {
222984
223324
  if (this.workflowLifecycleMovesInFlight.has(task.id) && this.graphRouting.has(task.id)) {
222985
223325
  executorLog.log(
@@ -223569,15 +223909,63 @@ The tool prevents your session from being killed by the inactivity watchdog duri
223569
223909
  getActiveWorktreePaths() consumers that cd into a path are unaffected; only the registry key changes.
223570
223910
  Non-workspace tasks (unique worktree path != rootDir) are returned unchanged.
223571
223911
  */
223912
+ /*
223913
+ FNXC:PlanReviewWorktree 2026-07-25-20:40 (concurrent root-rooted step sessions — single-repo collision):
223914
+ The task-scoped key must apply to the shared repo root in EVERY project mode, not only workspace mode.
223915
+ Read-only graph nodes that need no worktree (Plan Review is the canonical one — it reviews the
223916
+ store-injected PROMPT.md, see FNXC:PlanReviewSpecInjection) run rooted at `this.rootDir`, and a todo
223917
+ task has no worktree of its own. With the bare root as the registry key, two tasks reaching Plan Review
223918
+ at the same time collided: the second failed with "active-session path <root> is held by task <other>;
223919
+ task <self> may not overwrite it", which surfaced as a Plan Review provider failure, burned the
223920
+ in-place retry budget against a hold that retrying can never clear, and left the task parked
223921
+ (reported: FN-1398 holding /home/ubuntu/dev/freemap-svelte while FN-1403 planned).
223922
+ Path-exclusivity on the shared root is not what keeps these sessions correct: write-capable nodes are
223923
+ refused at the root outright (no-worktree-for-write-node above), real per-sub-repo exclusivity is the
223924
+ workspace-repo-acquire lease, and every isPathActive consumer guards removable WORKTREE paths — the
223925
+ root is never one. Liveness still works because the synthetic key stays in the registry under the task.
223926
+ */
223572
223927
  sessionRegistryPath(taskId, worktreePath) {
223573
- if (this.workspaceConfig && worktreePath === this.rootDir) {
223928
+ if (worktreePath === this.rootDir) {
223574
223929
  return `${worktreePath}#session:${taskId}`;
223575
223930
  }
223576
223931
  return worktreePath;
223577
223932
  }
223933
+ /*
223934
+ FNXC:SessionContention 2026-07-25-21:30 (contention prevention at the registration seam):
223935
+ Every executor session registration goes through `acquireActiveSessionPath` instead of the raw
223936
+ `registerPath`, so a LEAKED entry owned by a task with no live session surface in this process is
223937
+ RECLAIMED rather than throwing at the newcomer. That closes the second contention class (a dead
223938
+ holder can never release, so waiting on it is waiting forever). A genuinely live holder still throws
223939
+ the typed error — that case is real serialization, and callers classify it as a retryable contention
223940
+ hold (SESSION_CONTENTION_HOLD_VALUE), never as a provider/model failure.
223941
+ The probe reports LIVE on any uncertainty: an unknown holder with a fresh entry is treated as live by
223942
+ the staleness floor, so the reclaim only ever fires on proven-dead, aged entries.
223943
+ */
223944
+ acquireSessionRegistryPath(taskId, registryPath, kind, ownerKey) {
223945
+ const outcome = acquireActiveSessionPath(activeSessionRegistry, registryPath, { taskId, kind, ownerKey }, {
223946
+ holderLiveProbe: (holderTaskId) => this.hasLiveTaskSessionSurface(holderTaskId) || executingTaskLock.has(holderTaskId)
223947
+ });
223948
+ if (outcome.action === "contended") {
223949
+ throw new ActiveSessionPathHeldByForeignTaskError(registryPath, outcome.holderTaskId, taskId);
223950
+ }
223951
+ if (outcome.action === "reclaimed-stale-foreign") {
223952
+ executorLog.warn(
223953
+ `${taskId}: reclaimed a stale active-session entry on ${registryPath} from dead task ${outcome.holderTaskId} (idle ${outcome.ageMs}ms)`
223954
+ );
223955
+ void this.store.recordRunAuditEvent?.({
223956
+ taskId,
223957
+ agentId: "executor",
223958
+ runId: generateSyntheticRunId("session-path-reclaim", taskId),
223959
+ domain: "database",
223960
+ mutationType: "session:reclaim-stale-foreign-path",
223961
+ target: taskId,
223962
+ metadata: { taskId, holderTaskId: outcome.holderTaskId, kind, ageMs: outcome.ageMs }
223963
+ })?.catch?.(() => void 0);
223964
+ }
223965
+ }
223578
223966
  setActiveSession(taskId, sessionState, worktreePath) {
223579
223967
  this.activeSessions.set(taskId, sessionState);
223580
- activeSessionRegistry.registerPath(this.sessionRegistryPath(taskId, worktreePath), { taskId, kind: "executor", ownerKey: taskId });
223968
+ this.acquireSessionRegistryPath(taskId, this.sessionRegistryPath(taskId, worktreePath), "executor", taskId);
223581
223969
  }
223582
223970
  markGraphExecuteSelfRequeued(taskId) {
223583
223971
  if (this.graphRouting.has(taskId)) {
@@ -223595,7 +223983,7 @@ The tool prevents your session from being killed by the inactivity watchdog duri
223595
223983
  setActiveStepExecutor(taskId, stepExecutor, worktreePath, seenSteeringIds = /* @__PURE__ */ new Set()) {
223596
223984
  this.activeStepExecutors.set(taskId, stepExecutor);
223597
223985
  this.activeStepExecutorSeenSteeringIds.set(taskId, seenSteeringIds);
223598
- activeSessionRegistry.registerPath(this.sessionRegistryPath(taskId, worktreePath), { taskId, kind: "step-session", ownerKey: `${taskId}#step-session` });
223986
+ this.acquireSessionRegistryPath(taskId, this.sessionRegistryPath(taskId, worktreePath), "step-session", `${taskId}#step-session`);
223599
223987
  }
223600
223988
  deleteActiveStepExecutor(taskId, worktreePath) {
223601
223989
  this.activeStepExecutors.delete(taskId);
@@ -223609,7 +223997,7 @@ The tool prevents your session from being killed by the inactivity watchdog duri
223609
223997
  setActiveWorkflowStepSession(taskId, session, worktreePath, seenSteeringIds = /* @__PURE__ */ new Set()) {
223610
223998
  this.activeWorkflowStepSessions.set(taskId, session);
223611
223999
  this.activeWorkflowStepSessionSeenSteeringIds.set(taskId, seenSteeringIds);
223612
- activeSessionRegistry.registerPath(this.sessionRegistryPath(taskId, worktreePath), { taskId, kind: "workflow-step", ownerKey: `${taskId}#workflow-step` });
224000
+ this.acquireSessionRegistryPath(taskId, this.sessionRegistryPath(taskId, worktreePath), "workflow-step", `${taskId}#workflow-step`);
223613
224001
  }
223614
224002
  deleteActiveWorkflowStepSession(taskId, worktreePath) {
223615
224003
  this.activeWorkflowStepSessions.delete(taskId);
@@ -225481,7 +225869,7 @@ ${info.feedback}`, revisionKey),
225481
225869
  const authoritativeAgentLayer = this.store.getAsyncLayer();
225482
225870
  if (!this.authoritativeAssignedAgentStore || authoritativeAgentLayer && !this.authoritativeAssignedAgentStore.backendMode) {
225483
225871
  this.authoritativeAssignedAgentStore = new AgentStore({
225484
- rootDir: join80(this.rootDir, ".fusion"),
225872
+ rootDir: join81(this.rootDir, ".fusion"),
225485
225873
  taskStore: this.store,
225486
225874
  ...authoritativeAgentLayer ? { asyncLayer: authoritativeAgentLayer } : {}
225487
225875
  });
@@ -227928,6 +228316,87 @@ ${info.feedback}`, revisionKey),
227928
228316
  this.unregisterConfiguredCommandController(task.id, commandAbortController);
227929
228317
  }
227930
228318
  }
228319
+ /*
228320
+ FNXC:NodeWorktreeIsolation 2026-07-25-22:10 (planning acquires the task worktree):
228321
+ Public seam for the planning/triage lane. Specification runs a CODING-tool session; pointing it at
228322
+ the shared main checkout meant every planning agent had write tools in the operator's tree and every
228323
+ concurrent planner shared one path. Acquire the task's own worktree up front and let the whole
228324
+ lifecycle — planning, Plan Review, implementation, code review — reuse that single worktree.
228325
+ Returns null (caller falls back to the root, unchanged behavior) when the project is a workspace, or
228326
+ when acquisition fails: planning must never be blocked by a worktree problem.
228327
+ */
228328
+ /*
228329
+ FNXC:PlanningEvacuation 2026-07-25-23:00 (pre-execution worktree release):
228330
+ Planning now acquires a worktree, so a card that never reaches execution — withdrawn to Ideas,
228331
+ archived from a planner lane, or parked pre-execution — would otherwise hold one forever. Release
228332
+ it. Safety conditions, all required:
228333
+ - the task never executed (`firstExecutionAt`/`executionStartedAt` unset): execution evidence means
228334
+ the worktree may hold real work, and only the normal merge/archive lifecycle may remove it;
228335
+ - no live session registered on the path (the same isPathActive guard the other sweeps use);
228336
+ - the branch carries no commits beyond its base — planning writes its spec to the task store, not
228337
+ the worktree, so a clean branch means there is genuinely nothing to lose.
228338
+ Metadata (`worktree`/`branch`) is cleared with it, so a later promotion re-acquires cleanly.
228339
+ Fail-soft throughout: a cleanup problem must never block the lifecycle move that triggered it.
228340
+ */
228341
+ async releasePreExecutionWorktree(taskId, reason) {
228342
+ try {
228343
+ const live = await this.store.getTask(taskId);
228344
+ if (!live?.worktree) return false;
228345
+ if (live.firstExecutionAt || live.executionStartedAt) return false;
228346
+ if (activeSessionRegistry.isPathActive(live.worktree) || activeSessionRegistry.isPathActive(resolvePath3(live.worktree))) return false;
228347
+ if (this.hasLiveTaskSessionSurface(taskId) || executingTaskLock.has(taskId)) return false;
228348
+ if (existsSync65(live.worktree)) {
228349
+ if (await this.preExecutionWorktreeHasWork(live.worktree)) {
228350
+ executorLog.log(`${taskId}: keeping pre-execution worktree ${live.worktree} \u2014 it carries commits or uncommitted changes`);
228351
+ return false;
228352
+ }
228353
+ const settings = await this.store.getSettings();
228354
+ await removeWorktree({
228355
+ rootDir: this.rootDir,
228356
+ worktreePath: live.worktree,
228357
+ settings,
228358
+ taskId,
228359
+ reason: RemovalReason.SelfHealingReclaim
228360
+ });
228361
+ }
228362
+ this.activeWorktrees.get(taskId)?.delete(live.worktree);
228363
+ await this.store.updateTask(taskId, { worktree: null, branch: null, baseCommitSha: null, sessionFile: null }, this.getRunContextFor(taskId));
228364
+ await this.store.logEntry(taskId, `Released the pre-execution worktree (${reason}) \u2014 it will be re-acquired when planning or execution resumes`, void 0, this.getRunContextFor(taskId)).catch(() => void 0);
228365
+ executorLog.log(`${taskId}: released pre-execution worktree ${live.worktree} (${reason})`);
228366
+ return true;
228367
+ } catch (error3) {
228368
+ executorLog.warn(`${taskId}: could not release the pre-execution worktree: ${formatError(error3).message}`);
228369
+ return false;
228370
+ }
228371
+ }
228372
+ /** True when a pre-execution worktree holds commits past its base or any uncommitted change. */
228373
+ async preExecutionWorktreeHasWork(worktreePath) {
228374
+ try {
228375
+ const { stdout: dirty } = await execFileAsync19("git", ["status", "--porcelain"], { cwd: worktreePath, timeout: 3e4 });
228376
+ if (dirty.trim()) return true;
228377
+ const { stdout: ahead } = await execFileAsync19("git", ["log", "--oneline", "@{upstream}..HEAD"], { cwd: worktreePath, timeout: 3e4 }).catch(async () => await execFileAsync19("git", ["log", "--oneline", "-1", "HEAD", "--not", "--remotes", "--branches=main", "--branches=master"], { cwd: worktreePath, timeout: 3e4 }));
228378
+ return Boolean(ahead.trim());
228379
+ } catch {
228380
+ return true;
228381
+ }
228382
+ }
228383
+ async ensureTaskWorktreeForPlanning(taskId) {
228384
+ try {
228385
+ if (this.workspaceConfig === void 0) {
228386
+ this.workspaceConfig = await loadWorkspaceConfig(this.rootDir);
228387
+ }
228388
+ if (this.workspaceConfig && (this.workspaceConfig.repos.length ?? 0) > 0) return null;
228389
+ const live = await this.store.getTask(taskId);
228390
+ if (live.worktree && existsSync65(live.worktree)) return live.worktree;
228391
+ const settings = await this.store.getSettings();
228392
+ const acquisitionTask = live.worktree ? { ...live, worktree: void 0, sessionFile: void 0 } : live;
228393
+ const acquired = await this.ensureGraphCustomNodeWorktree(acquisitionTask, settings, "planning");
228394
+ return acquired.worktree || null;
228395
+ } catch (error3) {
228396
+ executorLog.warn(`${taskId}: could not acquire a planning worktree \u2014 planning falls back to the repo root: ${formatError(error3)}`);
228397
+ return null;
228398
+ }
228399
+ }
227931
228400
  async prepareGraphNodeExecution(node, nodeTask, settings, requirement) {
227932
228401
  if (!requirement.requiresWorktree) return;
227933
228402
  const live = await this.store.getTask(nodeTask.id);
@@ -228054,22 +228523,29 @@ ${info.feedback}`, revisionKey),
228054
228523
  optionalGroupId,
228055
228524
  reviewerInlineFixes: settings.reviewerInlineFixes
228056
228525
  });
228057
- const executionTarget = writeCapable ? await this.store.getTask(live.id) : live;
228058
- if (writeCapable && !executionTarget.worktree && !this.workspaceConfig) {
228059
- return { outcome: "failure", value: "no-worktree-for-write-node" };
228060
- }
228526
+ let executionTarget = writeCapable ? await this.store.getTask(live.id) : live;
228061
228527
  const nodeDisplayName = typeof cfg.name === "string" && cfg.name.trim() ? cfg.name.trim() : node.id;
228062
228528
  const isPlanReviewNode = node.id === "plan-review-step" || nodeDisplayName === "Plan Review" || optionalGroupId === "plan-review";
228063
- let worktreePath = executionTarget.worktree || this.rootDir;
228064
- if (isPlanReviewNode && !writeCapable && executionTarget.worktree && !existsSync65(executionTarget.worktree)) {
228065
- await this.store.logEntry(
228066
- live.id,
228067
- `Plan Review worktree ${executionTarget.worktree} is missing on disk \u2014 running the reviewer from the repo root (spec is store-injected)`,
228068
- void 0,
228069
- this.getRunContextFor(live.id)
228070
- );
228071
- worktreePath = this.rootDir;
228529
+ if (!this.workspaceConfig) {
228530
+ const recordedWorktreeMissing = Boolean(executionTarget.worktree) && !existsSync65(executionTarget.worktree);
228531
+ const shouldAcquire = !executionTarget.worktree || recordedWorktreeMissing && isPlanReviewNode;
228532
+ if (shouldAcquire) {
228533
+ if (recordedWorktreeMissing) {
228534
+ await this.store.logEntry(
228535
+ live.id,
228536
+ `Plan Review worktree ${executionTarget.worktree} is missing on disk \u2014 re-acquiring a task worktree instead of running in the shared checkout`,
228537
+ void 0,
228538
+ this.getRunContextFor(live.id)
228539
+ );
228540
+ }
228541
+ const acquisitionTask = recordedWorktreeMissing ? { ...executionTarget, worktree: void 0, sessionFile: void 0 } : executionTarget;
228542
+ executionTarget = await this.ensureGraphCustomNodeWorktree(acquisitionTask, settings, node.id);
228543
+ }
228544
+ }
228545
+ if (writeCapable && !executionTarget.worktree && !this.workspaceConfig) {
228546
+ return { outcome: "failure", value: "no-worktree-for-write-node" };
228072
228547
  }
228548
+ const worktreePath = executionTarget.worktree || this.rootDir;
228073
228549
  let prompt = typeof cfg.prompt === "string" ? cfg.prompt : "";
228074
228550
  let modelProvider = typeof cfg.modelProvider === "string" && cfg.modelProvider.trim() ? cfg.modelProvider : void 0;
228075
228551
  let modelId = typeof cfg.modelId === "string" && cfg.modelId.trim() ? cfg.modelId : void 0;
@@ -228366,6 +228842,77 @@ ${prompt}`;
228366
228842
  const latestAction = live.log.at(-1)?.action;
228367
228843
  return latestAction === "Resumed after engine restart" || latestAction === "Resuming execution after unpause";
228368
228844
  }
228845
+ /*
228846
+ FNXC:SessionContention 2026-07-25-21:30:
228847
+ Two ways in, because contention must never slip through to a park:
228848
+ - the typed failure value the graph now publishes (SESSION_CONTENTION_HOLD_VALUE), and
228849
+ - a message-shape fallback over the run's `:error` context patches, so contention arriving from a
228850
+ path that has not been taught the typed value is still recognized.
228851
+ */
228852
+ graphFailureErrorTexts(result2) {
228853
+ if (!result2.context) return [];
228854
+ const texts = [];
228855
+ for (const [key, value] of Object.entries(result2.context)) {
228856
+ if (key.endsWith(":error") && typeof value === "string" && value.trim()) texts.push(value);
228857
+ }
228858
+ return texts;
228859
+ }
228860
+ isSessionContentionGraphFailure(result2) {
228861
+ if (this.graphFailureValue(result2) === SESSION_CONTENTION_HOLD_VALUE) return true;
228862
+ return this.graphFailureErrorTexts(result2).some((text2) => isSessionContentionError(text2));
228863
+ }
228864
+ /*
228865
+ FNXC:SessionContention 2026-07-25-21:30 (self-recovering wait — the task is never parked):
228866
+ Retry the graph in place on an exponential backoff while the holder finishes. The counter is
228867
+ IN-MEMORY on purpose: it needs no schema change, and an engine restart resetting it is the desired
228868
+ behavior (a restart also drops the in-process registry, so the contention is gone anyway).
228869
+ When the ladder is exhausted the task is left cleanly dispatchable — status/error cleared, progress
228870
+ untouched — so ordinary scheduling picks it up later with a fresh budget. There is no terminal branch
228871
+ here by design: lease contention always ends (the holder finishes, or self-healing sweeps it), so
228872
+ parking the task would only require a human to press Retry on a condition that fixed itself.
228873
+ */
228874
+ sessionContentionHoldAttempts = /* @__PURE__ */ new Map();
228875
+ clearSessionContentionHold(taskId) {
228876
+ this.sessionContentionHoldAttempts.delete(taskId);
228877
+ }
228878
+ async holdForSessionContention(task, live, result2) {
228879
+ const detail = this.graphFailureErrorTexts(result2).find((text2) => isSessionContentionError(text2));
228880
+ const priorAttempts = this.sessionContentionHoldAttempts.get(task.id) ?? 0;
228881
+ const attempt = priorAttempts + 1;
228882
+ if (attempt > MAX_SESSION_CONTENTION_HOLD_RETRIES) {
228883
+ this.clearSessionContentionHold(task.id);
228884
+ const message2 = `Still waiting on another task to release a shared session path after ${MAX_SESSION_CONTENTION_HOLD_RETRIES} attempts \u2014 leaving the task queued for normal re-dispatch (not a failure)${detail ? `: ${detail}` : ""}`;
228885
+ executorLog.warn(`${task.id}: ${message2}`);
228886
+ await this.store.logEntry(task.id, message2, void 0, this.getRunContextFor(task.id));
228887
+ if (live.status != null || live.error != null) {
228888
+ await this.store.updateTask(task.id, { status: null, error: null }, this.getRunContextFor(task.id));
228889
+ }
228890
+ return;
228891
+ }
228892
+ this.sessionContentionHoldAttempts.set(task.id, attempt);
228893
+ const message = `Waiting on another task to release a shared session path \u2014 retrying in place (${attempt}/${MAX_SESSION_CONTENTION_HOLD_RETRIES})${detail ? `: ${detail}` : ""}`;
228894
+ executorLog.warn(`${task.id}: ${message}`);
228895
+ await this.store.logEntry(task.id, message, void 0, this.getRunContextFor(task.id));
228896
+ if (live.status != null || live.error != null) {
228897
+ await this.store.updateTask(task.id, { status: null, error: null }, this.getRunContextFor(task.id));
228898
+ }
228899
+ const delayMs = SESSION_CONTENTION_HOLD_BACKOFF_MS === 0 ? 0 : Math.min(SESSION_CONTENTION_HOLD_MAX_BACKOFF_MS, SESSION_CONTENTION_HOLD_BACKOFF_MS * 2 ** (attempt - 1));
228900
+ const scheduleRetry = () => {
228901
+ void (async () => {
228902
+ try {
228903
+ const resume = await this.store.getTask(task.id);
228904
+ if (!resume || resume.deletedAt || resume.paused || resume.userPaused) {
228905
+ this.clearSessionContentionHold(task.id);
228906
+ return;
228907
+ }
228908
+ await this.execute(resume);
228909
+ } catch (err) {
228910
+ executorLog.error(`Failed session-contention retry for ${task.id}:`, err);
228911
+ }
228912
+ })();
228913
+ };
228914
+ setTimeout(scheduleRetry, delayMs).unref?.();
228915
+ }
228369
228916
  graphFailureValue(result2) {
228370
228917
  const failedNode = result2.visitedNodeIds[result2.visitedNodeIds.length - 1];
228371
228918
  if (!failedNode || !result2.context) return void 0;
@@ -228957,6 +229504,11 @@ Graph value: ${failureValue}` : ""}`, budget.key),
228957
229504
  await this.persistTokenUsage(task.id);
228958
229505
  return;
228959
229506
  }
229507
+ if (this.isSessionContentionGraphFailure(result2)) {
229508
+ await this.holdForSessionContention(task, live, result2);
229509
+ await this.persistTokenUsage(task.id);
229510
+ return;
229511
+ }
228960
229512
  if (await this.routeUnusableWorktreeGraphFailureToRecovery(task, live, result2)) {
228961
229513
  await this.persistTokenUsage(task.id);
228962
229514
  return;
@@ -229777,7 +230329,7 @@ Graph value: ${failureValue}` : ""}`, budget.key),
229777
230329
  const activeMergeStatuses = /* @__PURE__ */ new Set(["merging", "merging-pr", "merging-fix"]);
229778
230330
  const isActiveTask = activeColumns.has(task.column) || activeMergeStatuses.has(task.status ?? "");
229779
230331
  if (!isActiveTask) {
229780
- const tasksDir = join80(this.store.getFusionDir(), "tasks");
230332
+ const tasksDir = join81(this.store.getFusionDir(), "tasks");
229781
230333
  const promptPath = getPromptPath(tasksDir, task.id);
229782
230334
  const staleness = await evaluateSpecStaleness({ settings, promptPath, task });
229783
230335
  if (staleness.isStale) {
@@ -233638,10 +234190,10 @@ Please fix the issues so the verification can pass on the next attempt.`,
233638
234190
  * The section is replaced entirely to avoid accumulation of old feedback.
233639
234191
  */
233640
234192
  async injectWorkflowStepFailureInstructions(task, failureFeedback, stepName, retry) {
233641
- const promptPath = join80(this.store.getFusionDir(), "tasks", task.id, "PROMPT.md");
234193
+ const promptPath = join81(this.store.getFusionDir(), "tasks", task.id, "PROMPT.md");
233642
234194
  let content;
233643
234195
  try {
233644
- content = await readFile38(promptPath, "utf-8");
234196
+ content = await readFile39(promptPath, "utf-8");
233645
234197
  } catch {
233646
234198
  executorLog.warn(`${task.id}: PROMPT.md not found at ${promptPath}, skipping workflow failure injection`);
233647
234199
  return;
@@ -240180,7 +240732,7 @@ import { execFile as execFile27 } from "node:child_process";
240180
240732
  import { appendFileSync as appendFileSync2, existsSync as existsSync66, mkdirSync as mkdirSync11, readdirSync as readdirSync12, readFileSync as readFileSync24, realpathSync as realpathSync10, rmSync as rmSync9, statSync as statSync13 } from "node:fs";
240181
240733
  import { rm as rm14 } from "node:fs/promises";
240182
240734
  import { tmpdir as tmpdir10 } from "node:os";
240183
- import { isAbsolute as isAbsolute19, join as join81, relative as relative17 } from "node:path";
240735
+ import { isAbsolute as isAbsolute19, join as join82, relative as relative17 } from "node:path";
240184
240736
  import { promisify as promisify56 } from "node:util";
240185
240737
  async function git4(args, cwd, opts = {}) {
240186
240738
  const { stdout } = await execFileAsync20("git", args, {
@@ -240211,7 +240763,7 @@ function isBenignAbsentWorktreeError(err) {
240211
240763
  return /is not a working tree|No such file or directory|spawn\s+.*\bENOENT\b/i.test(description);
240212
240764
  }
240213
240765
  function ensureAiMergeRootIgnored(projectRootDir, settings) {
240214
- const excludePath = join81(projectRootDir, ".git", "info", "exclude");
240766
+ const excludePath = join82(projectRootDir, ".git", "info", "exclude");
240215
240767
  if (!existsSync66(excludePath)) return;
240216
240768
  try {
240217
240769
  const current = readFileSync24(excludePath, "utf-8");
@@ -240243,7 +240795,7 @@ function getAiMergeTempSearchRoots(projectRootDir, settings) {
240243
240795
  if (testWorkerRoot) {
240244
240796
  try {
240245
240797
  for (const entry of readdirSync12(testWorkerRoot)) {
240246
- if (entry.startsWith("redir-")) roots.push(join81(testWorkerRoot, entry));
240798
+ if (entry.startsWith("redir-")) roots.push(join82(testWorkerRoot, entry));
240247
240799
  }
240248
240800
  } catch {
240249
240801
  }
@@ -240266,7 +240818,7 @@ async function pruneExistingAiMergeWorktrees(taskId, projectRootDir, audit, log3
240266
240818
  continue;
240267
240819
  }
240268
240820
  for (const entry of entries) {
240269
- const candidatePath = join81(tempRoot, entry);
240821
+ const candidatePath = join82(tempRoot, entry);
240270
240822
  let canonicalPath = candidatePath;
240271
240823
  try {
240272
240824
  canonicalPath = realpathSync10(candidatePath);
@@ -240689,7 +241241,7 @@ import { promisify as promisify57 } from "node:util";
240689
241241
  import { realpathSync as realpathSync11, readdirSync as readdirSync13 } from "node:fs";
240690
241242
  import { mkdtemp as mkdtemp4 } from "node:fs/promises";
240691
241243
  import { tmpdir as tmpdir11 } from "node:os";
240692
- import { join as join82 } from "node:path";
241244
+ import { join as join83 } from "node:path";
240693
241245
  async function git5(args, cwd, opts = {}) {
240694
241246
  const { stdout } = await execFileAsync21("git", args, {
240695
241247
  cwd,
@@ -240761,7 +241313,7 @@ function listAiMergeWorktreeCandidates(taskId, projectRootDir, settings) {
240761
241313
  if (testWorkerRoot) {
240762
241314
  try {
240763
241315
  for (const entry of readdirSync13(testWorkerRoot)) {
240764
- if (entry.startsWith("redir-")) roots.push(join82(testWorkerRoot, entry));
241316
+ if (entry.startsWith("redir-")) roots.push(join83(testWorkerRoot, entry));
240765
241317
  }
240766
241318
  } catch {
240767
241319
  }
@@ -240774,7 +241326,7 @@ function listAiMergeWorktreeCandidates(taskId, projectRootDir, settings) {
240774
241326
  } catch {
240775
241327
  continue;
240776
241328
  }
240777
- for (const entry of entries) candidates.push(join82(root, entry));
241329
+ for (const entry of entries) candidates.push(join83(root, entry));
240778
241330
  }
240779
241331
  return candidates;
240780
241332
  }
@@ -241108,7 +241660,7 @@ async function landOneRepo(repoRootDir, branch, integrationBranch, ctx) {
241108
241660
  registeredMergePaths.add(pathToRegister);
241109
241661
  };
241110
241662
  try {
241111
- mergeRoot = await mkdtemp4(join82(resolveAiMergeRoot(repoRootDir, settings), `fusion-ai-merge-${taskId.toLowerCase()}-`));
241663
+ mergeRoot = await mkdtemp4(join83(resolveAiMergeRoot(repoRootDir, settings), `fusion-ai-merge-${taskId.toLowerCase()}-`));
241112
241664
  registerMergeRoot(mergeRoot);
241113
241665
  await git5(["worktree", "add", "--detach", mergeRoot, tipSha], repoRootDir);
241114
241666
  worktreeAdded = true;
@@ -241604,7 +242156,7 @@ async function landWorkspaceTask(store, task, workspaceRootDir, options = {}, de
241604
242156
  for (const repoRel of repoKeys) {
241605
242157
  throwIfAborted2(options.signal, taskId);
241606
242158
  const entry = workspaceWorktrees[repoRel];
241607
- const repoRootDir = join82(workspaceRootDir, repoRel);
242159
+ const repoRootDir = join83(workspaceRootDir, repoRel);
241608
242160
  let integrationBranch;
241609
242161
  try {
241610
242162
  integrationBranch = await resolveIntegrationBranch(
@@ -241921,7 +242473,7 @@ async function pushAfterMergeToRemote(input) {
241921
242473
  let worktreeAdded = false;
241922
242474
  const registeredPaths = /* @__PURE__ */ new Set();
241923
242475
  try {
241924
- pushRoot = await mkdtemp4(join82(resolveAiMergeRoot(projectRootDir, settings), `fusion-ai-merge-push-${taskId.toLowerCase()}-`));
242476
+ pushRoot = await mkdtemp4(join83(resolveAiMergeRoot(projectRootDir, settings), `fusion-ai-merge-push-${taskId.toLowerCase()}-`));
241925
242477
  for (const p2 of [pushRoot]) {
241926
242478
  activeSessionRegistry.registerPath(p2, { taskId, kind: "ai-merge", ownerKey: `ai-merge-push:${taskId}` });
241927
242479
  registeredPaths.add(p2);
@@ -242228,7 +242780,7 @@ var init_merger_ai = __esm({
242228
242780
 
242229
242781
  // ../engine/src/task-revert.ts
242230
242782
  import { exec as exec41 } from "node:child_process";
242231
- import { join as join83 } from "node:path";
242783
+ import { join as join84 } from "node:path";
242232
242784
  import { promisify as promisify58 } from "node:util";
242233
242785
  function quoteShellArg7(value) {
242234
242786
  return `'${value.replace(/'/g, `'\\''`)}'`;
@@ -242678,7 +243230,7 @@ async function resolveWorkspaceTaskRevertCommits(task, opts) {
242678
243230
  const result2 = {};
242679
243231
  for (const repoRel of repoKeys) {
242680
243232
  const entry = workspaceWorktrees[repoRel];
242681
- const repoRootDir = join83(opts.workspaceRootDir, repoRel);
243233
+ const repoRootDir = join84(opts.workspaceRootDir, repoRel);
242682
243234
  const landedSha = workspaceLandedShas[repoRel] ?? entry?.landedSha;
242683
243235
  if (landedSha && !entry?.baseCommitSha) {
242684
243236
  result2[repoRel] = { commits: [landedSha], source: "squash" };
@@ -242745,7 +243297,7 @@ async function revertWorkspaceTask(opts) {
242745
243297
  });
242746
243298
  const contexts = [];
242747
243299
  for (const repoRel of repoKeys) {
242748
- const repoRootDir = join83(workspaceRootDir, repoRel);
243300
+ const repoRootDir = join84(workspaceRootDir, repoRel);
242749
243301
  let integrationBranch;
242750
243302
  try {
242751
243303
  integrationBranch = await resolveIntegrationBranch(repoRootDir, { ...opts.settings, integrationBranch: void 0, baseBranch: void 0 });
@@ -242870,7 +243422,7 @@ async function prepareWorkspaceRevertPrBranches(opts) {
242870
243422
  });
242871
243423
  const contexts = [];
242872
243424
  for (const repoRel of repoKeys) {
242873
- const repoRootDir = join83(workspaceRootDir, repoRel);
243425
+ const repoRootDir = join84(workspaceRootDir, repoRel);
242874
243426
  let integrationBranch;
242875
243427
  try {
242876
243428
  integrationBranch = await resolveIntegrationBranch(repoRootDir, { ...opts.settings, integrationBranch: void 0, baseBranch: void 0 });
@@ -243211,7 +243763,7 @@ async function readOptionalTextFile(filePath) {
243211
243763
  }
243212
243764
  async function discoverMcpServers(opts) {
243213
243765
  const homeDir = opts.homeDir ?? process.env.HOME ?? process.env.USERPROFILE ?? osHomedir();
243214
- const readFile46 = opts.readFile ?? readOptionalTextFile;
243766
+ const readFile47 = opts.readFile ?? readOptionalTextFile;
243215
243767
  const sources = getMcpDiscoverySources({
243216
243768
  homeDir,
243217
243769
  platform: osPlatform(),
@@ -243222,7 +243774,7 @@ async function discoverMcpServers(opts) {
243222
243774
  for (const source of sources) {
243223
243775
  let contents;
243224
243776
  try {
243225
- contents = await readFile46(source.path);
243777
+ contents = await readFile47(source.path);
243226
243778
  } catch (error3) {
243227
243779
  errors.push(`${source.label}: ${error3 instanceof Error ? error3.message : String(error3)}`);
243228
243780
  continue;
@@ -243772,7 +244324,7 @@ var agent_reflection_exports = {};
243772
244324
  __export(agent_reflection_exports, {
243773
244325
  AgentReflectionService: () => AgentReflectionService
243774
244326
  });
243775
- import { readFile as readFile39 } from "node:fs/promises";
244327
+ import { readFile as readFile40 } from "node:fs/promises";
243776
244328
  import { isAbsolute as isAbsolute20, resolve as resolve38 } from "node:path";
243777
244329
  var reflectionLog, REFLECTION_SYSTEM_PROMPT, DEFAULT_OUTCOME_LIMIT, AgentReflectionService;
243778
244330
  var init_agent_reflection = __esm({
@@ -244331,7 +244883,7 @@ Rules:
244331
244883
  if (agent.instructionsPath?.trim()) {
244332
244884
  const resolvedPath = isAbsolute20(agent.instructionsPath) ? agent.instructionsPath : resolve38(this.rootDir, agent.instructionsPath);
244333
244885
  try {
244334
- const content = await readFile39(resolvedPath, "utf-8");
244886
+ const content = await readFile40(resolvedPath, "utf-8");
244335
244887
  if (content.trim()) {
244336
244888
  pieces.push(content.trim());
244337
244889
  }
@@ -246439,7 +246991,7 @@ The note must be specific (what/where/why). Prefer citing File Scope, PROMPT, a
246439
246991
 
246440
246992
  // ../engine/src/overseer-watchdog.ts
246441
246993
  import { existsSync as existsSync67, readFileSync as readFileSync25 } from "node:fs";
246442
- import { dirname as dirname34, join as join84, relative as relative18, resolve as resolve39, sep as sep11 } from "node:path";
246994
+ import { dirname as dirname34, join as join85, relative as relative18, resolve as resolve39, sep as sep11 } from "node:path";
246443
246995
  import { homedir as homedir11 } from "node:os";
246444
246996
  function defaultReadText(path10) {
246445
246997
  try {
@@ -246468,7 +247020,7 @@ function discoverOverseerWatchdogFiles(options) {
246468
247020
  };
246469
247021
  if (agentDir) {
246470
247022
  for (const name2 of WATCHDOG_FILENAMES) {
246471
- tryAdd(join84(agentDir, name2), "user", 999);
247023
+ tryAdd(join85(agentDir, name2), "user", 999);
246472
247024
  }
246473
247025
  }
246474
247026
  let current = cwd;
@@ -246476,9 +247028,9 @@ function discoverOverseerWatchdogFiles(options) {
246476
247028
  for (let hop = 0; hop < 64; hop++) {
246477
247029
  const depth = relative18(cwd, current) === "" ? 0 : relative18(cwd, current).split(sep11).filter(Boolean).length;
246478
247030
  for (const name2 of WATCHDOG_FILENAMES) {
246479
- tryAdd(join84(current, name2), "project", depth);
246480
- tryAdd(join84(current, ".fusion", name2), "project", depth);
246481
- tryAdd(join84(current, ".omp", name2), "project", depth);
247031
+ tryAdd(join85(current, name2), "project", depth);
247032
+ tryAdd(join85(current, ".fusion", name2), "project", depth);
247033
+ tryAdd(join85(current, ".omp", name2), "project", depth);
246482
247034
  }
246483
247035
  if (current === stopAt) break;
246484
247036
  const parent = dirname34(current);
@@ -253709,6 +254261,9 @@ var init_in_process_runtime = __esm({
253709
254261
  usageLimitPauser: this.usageLimitPauser,
253710
254262
  agentStore: this.agentStore,
253711
254263
  pluginRunner: this.pluginRunner,
254264
+ // FNXC:NodeWorktreeIsolation 2026-07-25-22:10: planning acquires (or reuses) the task's own
254265
+ // worktree through the executor's acquisition path, so no lane runs in the shared checkout.
254266
+ acquirePlanningWorktree: (taskId) => this.executor.ensureTaskWorktreeForPlanning(taskId),
253712
254267
  onSpecifyStart: (t) => {
253713
254268
  this.recordActivity();
253714
254269
  runtimeLog.log(`Specifying ${t.id}...`);
@@ -253797,6 +254352,8 @@ var init_in_process_runtime = __esm({
253797
254352
  getExecutingTaskIds: () => this.executor?.getExecutingTaskIds() ?? /* @__PURE__ */ new Set(),
253798
254353
  clearPhantomExecutorBinding: (taskId, options) => this.executor?.clearPhantomExecutorBinding(taskId, options),
253799
254354
  listWorktreeHolders: () => this.executor?.listWorktreeHolders() ?? [],
254355
+ // FNXC:PlanningEvacuation 2026-07-25-23:00: the executor owns the release safety conditions.
254356
+ releasePreExecutionWorktree: (taskId, reason) => this.executor?.releasePreExecutionWorktree(taskId, reason) ?? Promise.resolve(false),
253800
254357
  recoverApprovedTriageTask: (task) => this.triageProcessor?.recoverApprovedTask(task) ?? Promise.resolve(false),
253801
254358
  getPlanningTaskIds: () => this.triageProcessor?.getPlanningTaskIds() ?? /* @__PURE__ */ new Set(),
253802
254359
  // FNXC:TaskTiming 2026-08-01-12:00: orphan planning recovery must defer
@@ -254776,7 +255333,7 @@ __export(child_process_runtime_exports, {
254776
255333
  import { EventEmitter as EventEmitter28 } from "node:events";
254777
255334
  import { fork } from "node:child_process";
254778
255335
  import { fileURLToPath as fileURLToPath4 } from "node:url";
254779
- import { dirname as dirname35, join as join85 } from "node:path";
255336
+ import { dirname as dirname35, join as join86 } from "node:path";
254780
255337
  var HealthMonitor, ChildProcessRuntime;
254781
255338
  var init_child_process_runtime = __esm({
254782
255339
  "../engine/src/runtimes/child-process-runtime.ts"() {
@@ -254938,7 +255495,7 @@ var init_child_process_runtime = __esm({
254938
255495
  const isCompiled = !import.meta.url.endsWith(".ts");
254939
255496
  const currentDir = dirname35(fileURLToPath4(import.meta.url));
254940
255497
  const workerFile = isCompiled ? "child-process-worker.js" : "child-process-worker.ts";
254941
- return join85(currentDir, workerFile);
255498
+ return join86(currentDir, workerFile);
254942
255499
  }
254943
255500
  /**
254944
255501
  * Set up event forwarding from IPC host to runtime listeners.
@@ -261205,7 +261762,7 @@ import { mkdirSync as mkdirSync12 } from "node:fs";
261205
261762
  import { access as access10, unlink as unlink10, writeFile as writeFile32 } from "node:fs/promises";
261206
261763
  import net2 from "node:net";
261207
261764
  import { platform as platform3, tmpdir as tmpdir12 } from "node:os";
261208
- import { join as join86 } from "node:path";
261765
+ import { join as join87 } from "node:path";
261209
261766
  import { createHash as createHash24 } from "node:crypto";
261210
261767
  function projectHash(projectId) {
261211
261768
  return createHash24("sha1").update(projectId).digest("hex").slice(0, 16);
@@ -261215,10 +261772,10 @@ function computeEngineSocketPath(projectId) {
261215
261772
  if (platform3() === "win32") {
261216
261773
  return `\\\\.\\pipe\\fusion-engine-${hash}`;
261217
261774
  }
261218
- return join86(tmpdir12(), `fusion-engine-${hash}.sock`);
261775
+ return join87(tmpdir12(), `fusion-engine-${hash}.sock`);
261219
261776
  }
261220
261777
  function computeEngineLockFilePath(workingDir) {
261221
- return join86(workingDir, ".fusion", "engine.lock");
261778
+ return join87(workingDir, ".fusion", "engine.lock");
261222
261779
  }
261223
261780
  async function ensureLockTargetExists(lockPath) {
261224
261781
  try {
@@ -261228,7 +261785,7 @@ async function ensureLockTargetExists(lockPath) {
261228
261785
  }
261229
261786
  }
261230
261787
  async function acquireLockfile(workingDir, onCompromised) {
261231
- const dir = join86(workingDir, ".fusion");
261788
+ const dir = join87(workingDir, ".fusion");
261232
261789
  mkdirSync12(dir, { recursive: true });
261233
261790
  const path10 = computeEngineLockFilePath(workingDir);
261234
261791
  await ensureLockTargetExists(path10);
@@ -266904,7 +267461,7 @@ var init_issue_image_attachments = __esm({
266904
267461
  import { exec as exec45 } from "node:child_process";
266905
267462
  import { mkdtemp as mkdtemp6, rm as rm15 } from "node:fs/promises";
266906
267463
  import { tmpdir as tmpdir13 } from "node:os";
266907
- import { join as join87 } from "node:path";
267464
+ import { join as join88 } from "node:path";
266908
267465
  import { promisify as promisify65 } from "node:util";
266909
267466
  function resolveCommentAuthor(input) {
266910
267467
  const login = input.login || "unknown";
@@ -268073,8 +268630,8 @@ var init_github = __esm({
268073
268630
  { timeout: 3e4, maxBuffer: 10 * 1024 * 1024 }
268074
268631
  );
268075
268632
  const mergeBase = mergeBaseStdout.trim();
268076
- const indexDir = await mkdtemp6(join87(tmpdir13(), "fn-pr-conflict-"));
268077
- const indexPath = join87(indexDir, "index");
268633
+ const indexDir = await mkdtemp6(join88(tmpdir13(), "fn-pr-conflict-"));
268634
+ const indexPath = join88(indexDir, "index");
268078
268635
  const gitEnv = { ...process.env, GIT_INDEX_FILE: indexPath };
268079
268636
  try {
268080
268637
  await execAsync40(`git -C ${quoteGitArg(opts.repoRoot)} read-tree -m ${quoteGitArg(mergeBase)} ${quoteGitArg(baseRef)} ${quoteGitArg(headRef)}`, {
@@ -271661,8 +272218,8 @@ var init_knowledge_index_refresh = __esm({
271661
272218
  // ../dashboard/src/pr-metadata-generator.ts
271662
272219
  import { promisify as promisify66 } from "node:util";
271663
272220
  import { exec as execCb } from "node:child_process";
271664
- import { access as access11, readFile as readFile40 } from "node:fs/promises";
271665
- import { join as join88 } from "node:path";
272221
+ import { access as access11, readFile as readFile41 } from "node:fs/promises";
272222
+ import { join as join89 } from "node:path";
271666
272223
  function buildFallbackPrMetadata(task) {
271667
272224
  return {
271668
272225
  title: task.title ?? task.id,
@@ -271862,14 +272419,14 @@ async function generatePrMetadata(input) {
271862
272419
  ]), combinedSignal);
271863
272420
  let promptContent = "";
271864
272421
  try {
271865
- const promptPath = join88(repoRoot, ".fusion", "tasks", task.id, "PROMPT.md");
271866
- promptContent = (await raceWithAbort(readFile40(promptPath, "utf8"), combinedSignal)).trim();
272422
+ const promptPath = join89(repoRoot, ".fusion", "tasks", task.id, "PROMPT.md");
272423
+ promptContent = (await raceWithAbort(readFile41(promptPath, "utf8"), combinedSignal)).trim();
271867
272424
  } catch {
271868
272425
  promptContent = "";
271869
272426
  }
271870
- const templatePath = join88(repoRoot, ".github", "pull_request_template.md");
272427
+ const templatePath = join89(repoRoot, ".github", "pull_request_template.md");
271871
272428
  const templateExists = await raceWithAbort(access11(templatePath).then(() => true).catch(() => false), combinedSignal);
271872
- const template = templateExists ? await raceWithAbort(readFile40(templatePath, "utf8"), combinedSignal) : "";
272429
+ const template = templateExists ? await raceWithAbort(readFile41(templatePath, "utf8"), combinedSignal) : "";
271873
272430
  const model = resolveTitleSummarizerSettingsModel(settings);
271874
272431
  const systemPrompt = [PR_METADATA_SYSTEM_PROMPT];
271875
272432
  const titleGuidance = settings.prTitlePromptInstructions?.trim();
@@ -279522,12 +280079,12 @@ import {
279522
280079
  unlinkSync as unlinkSync4
279523
280080
  } from "node:fs";
279524
280081
  import { homedir as homedir12 } from "node:os";
279525
- import { basename as basename21, dirname as dirname36, join as join89, resolve as resolve40 } from "node:path";
280082
+ import { basename as basename21, dirname as dirname36, join as join90, resolve as resolve40 } from "node:path";
279526
280083
  import { fileURLToPath as fileURLToPath5 } from "node:url";
279527
280084
  function resolveHermesHome(profile) {
279528
- const base = process.env.HERMES_HOME ?? join89(homedir12(), ".hermes");
280085
+ const base = process.env.HERMES_HOME ?? join90(homedir12(), ".hermes");
279529
280086
  if (!profile || profile === "default") return base;
279530
- return join89(base, "profiles", profile);
280087
+ return join90(base, "profiles", profile);
279531
280088
  }
279532
280089
  function getFusionSkillSourceCandidates(moduleUrl = import.meta.url) {
279533
280090
  const here = fileURLToPath5(moduleUrl);
@@ -279542,13 +280099,13 @@ function getFusionSkillSourceCandidates(moduleUrl = import.meta.url) {
279542
280099
  function resolveBundledFusionSkillSource() {
279543
280100
  const candidates = getFusionSkillSourceCandidates();
279544
280101
  for (const candidate of candidates) {
279545
- if (existsSync69(join89(candidate, "SKILL.md"))) return candidate;
280102
+ if (existsSync69(join90(candidate, "SKILL.md"))) return candidate;
279546
280103
  }
279547
280104
  return null;
279548
280105
  }
279549
280106
  function installFusionSkillIntoHermesHome(options = {}) {
279550
280107
  const sourceDir = options.sourceDir ?? resolveBundledFusionSkillSource();
279551
- const targetDir = join89(resolveHermesHome(options.profile), "skills", FUSION_SKILL_NAME);
280108
+ const targetDir = join90(resolveHermesHome(options.profile), "skills", FUSION_SKILL_NAME);
279552
280109
  if (!sourceDir) {
279553
280110
  return {
279554
280111
  outcome: "warning",
@@ -279637,7 +280194,7 @@ function isBrokenSymlink(path10) {
279637
280194
  }
279638
280195
  }
279639
280196
  function looksLikePriorFusionInstall(path10) {
279640
- const skillMd = join89(path10, "SKILL.md");
280197
+ const skillMd = join90(path10, "SKILL.md");
279641
280198
  if (!existsSync69(skillMd)) return false;
279642
280199
  try {
279643
280200
  const body = readFileSync26(skillMd, "utf-8");
@@ -279649,7 +280206,7 @@ function looksLikePriorFusionInstall(path10) {
279649
280206
  function looksLikeFusionSkillTarget(path10) {
279650
280207
  if (!path10) return false;
279651
280208
  if (basename21(path10).toLowerCase() === FUSION_SKILL_NAME) return true;
279652
- return existsSync69(join89(path10, "SKILL.md"));
280209
+ return existsSync69(join90(path10, "SKILL.md"));
279653
280210
  }
279654
280211
  var FUSION_SKILL_NAME;
279655
280212
  var init_fusion_skill_install = __esm({
@@ -279830,7 +280387,7 @@ var init_src8 = __esm({
279830
280387
  // ../../plugins/fusion-plugin-openclaw-runtime/src/pi-module.ts
279831
280388
  import { spawn as spawn13 } from "node:child_process";
279832
280389
  import { randomUUID as randomUUID50 } from "node:crypto";
279833
- import { readFile as readFile42 } from "node:fs/promises";
280390
+ import { readFile as readFile43 } from "node:fs/promises";
279834
280391
  function asString2(v2) {
279835
280392
  return typeof v2 === "string" && v2.trim() !== "" ? v2.trim() : void 0;
279836
280393
  }
@@ -279901,7 +280458,7 @@ function createCliSession(opts) {
279901
280458
  };
279902
280459
  }
279903
280460
  async function configureOpenClawMcpServer(opts) {
279904
- const serverValue = await readFile42(opts.serverConfigPath, "utf-8");
280461
+ const serverValue = await readFile43(opts.serverConfigPath, "utf-8");
279905
280462
  await new Promise((resolve53, reject) => {
279906
280463
  const child = spawn13(opts.binaryPath, ["--no-color", "--profile", opts.profile, "mcp", "set", opts.serverName, serverValue], {
279907
280464
  stdio: ["ignore", "pipe", "pipe"]
@@ -280045,7 +280602,7 @@ var init_pi_module = __esm({
280045
280602
  // ../../plugins/fusion-plugin-openclaw-runtime/src/mcp-config.ts
280046
280603
  import { writeFileSync as writeFileSync13 } from "node:fs";
280047
280604
  import { tmpdir as tmpdir14 } from "node:os";
280048
- import { join as join90 } from "node:path";
280605
+ import { join as join91 } from "node:path";
280049
280606
  import { fileURLToPath as fileURLToPath6 } from "node:url";
280050
280607
  import { dirname as dirname37 } from "node:path";
280051
280608
  function toolsToMcpToolDefs(tools) {
@@ -280058,16 +280615,16 @@ function toolsToMcpToolDefs(tools) {
280058
280615
  }
280059
280616
  function writeOpenClawMcpBridgeFiles(toolDefs, cacheKey) {
280060
280617
  const suffix = cacheKey ? `${process.pid}-${cacheKey}` : `${process.pid}`;
280061
- const schemaPath = join90(tmpdir14(), `openclaw-runtime-mcp-schemas-${suffix}.json`);
280618
+ const schemaPath = join91(tmpdir14(), `openclaw-runtime-mcp-schemas-${suffix}.json`);
280062
280619
  writeFileSync13(schemaPath, JSON.stringify(toolDefs));
280063
280620
  const __filename = fileURLToPath6(import.meta.url);
280064
280621
  const __dirname3 = dirname37(__filename);
280065
- const serverPath = join90(__dirname3, "mcp-schema-server.cjs");
280622
+ const serverPath = join91(__dirname3, "mcp-schema-server.cjs");
280066
280623
  const serverConfig = {
280067
280624
  command: "node",
280068
280625
  args: [serverPath, schemaPath]
280069
280626
  };
280070
- const serverConfigPath = join90(tmpdir14(), `openclaw-runtime-mcp-server-${suffix}.json`);
280627
+ const serverConfigPath = join91(tmpdir14(), `openclaw-runtime-mcp-server-${suffix}.json`);
280071
280628
  writeFileSync13(serverConfigPath, JSON.stringify(serverConfig));
280072
280629
  return {
280073
280630
  schemaPath,
@@ -280835,9 +281392,9 @@ var init_cli_spawn3 = __esm({
280835
281392
  });
280836
281393
 
280837
281394
  // ../../plugins/fusion-plugin-grok-runtime/src/probe.ts
280838
- import { readFile as readFile43 } from "node:fs/promises";
281395
+ import { readFile as readFile44 } from "node:fs/promises";
280839
281396
  import { homedir as homedir13 } from "node:os";
280840
- import { join as join91 } from "node:path";
281397
+ import { join as join92 } from "node:path";
280841
281398
  function buildCandidates2(binaryPath) {
280842
281399
  const configuredBinaryPath = binaryPath?.trim() || void 0;
280843
281400
  const ordered = configuredBinaryPath ? [configuredBinaryPath, ...CANDIDATES3] : [...CANDIDATES3];
@@ -280854,10 +281411,10 @@ async function probeGrokApiKeyPresence() {
280854
281411
  if (typeof envKey === "string" && envKey.trim().length > 0) {
280855
281412
  return { detected: true };
280856
281413
  }
280857
- const settingsPath = join91(homedir13(), ".grok", "user-settings.json");
281414
+ const settingsPath = join92(homedir13(), ".grok", "user-settings.json");
280858
281415
  let raw;
280859
281416
  try {
280860
- raw = await readFile43(settingsPath, "utf-8");
281417
+ raw = await readFile44(settingsPath, "utf-8");
280861
281418
  } catch {
280862
281419
  return { detected: false, reason: "No Grok API key detected by Fusion (GROK_API_KEY unset, ~/.grok/user-settings.json not found); the CLI will use its own credentials." };
280863
281420
  }
@@ -281006,7 +281563,7 @@ var init_provider2 = __esm({
281006
281563
 
281007
281564
  // ../../plugins/fusion-plugin-grok-runtime/src/acp/cli-spawn.ts
281008
281565
  import { existsSync as existsSync70 } from "node:fs";
281009
- import { dirname as dirname38, isAbsolute as isAbsolute22, join as join92, resolve as resolve41 } from "node:path";
281566
+ import { dirname as dirname38, isAbsolute as isAbsolute22, join as join93, resolve as resolve41 } from "node:path";
281010
281567
  import { fileURLToPath as fileURLToPath7 } from "node:url";
281011
281568
  function asTrimmedString(value) {
281012
281569
  return typeof value === "string" && value.trim().length > 0 ? value.trim() : void 0;
@@ -281024,7 +281581,7 @@ function pluginRootDir() {
281024
281581
  }
281025
281582
  function bundledClaudeBridgeBinPath(pluginRoot = pluginRootDir()) {
281026
281583
  const extension = process.platform === "win32" ? ".cmd" : "";
281027
- return join92(pluginRoot, "node_modules", ".bin", `${CLAUDE_CODE_CLI_ACP_BINARY}${extension}`);
281584
+ return join93(pluginRoot, "node_modules", ".bin", `${CLAUDE_CODE_CLI_ACP_BINARY}${extension}`);
281028
281585
  }
281029
281586
  function resolveBundledClaudeBridgeBinary(options = {}) {
281030
281587
  const root = options.pluginRoot ?? pluginRootDir();
@@ -282387,10 +282944,10 @@ import {
282387
282944
  } from "node:fs";
282388
282945
  import { createRequire as createRequire8 } from "node:module";
282389
282946
  import { tmpdir as tmpdir15 } from "node:os";
282390
- import { basename as basename23, dirname as dirname40, join as join94, resolve as resolve43 } from "node:path";
282947
+ import { basename as basename23, dirname as dirname40, join as join95, resolve as resolve43 } from "node:path";
282391
282948
  import { fileURLToPath as fileURLToPath8 } from "node:url";
282392
282949
  function isSkillDir(dir) {
282393
- return existsSync71(join94(dir, "SKILL.md"));
282950
+ return existsSync71(join95(dir, "SKILL.md"));
282394
282951
  }
282395
282952
  function pushUnique(out, candidate) {
282396
282953
  if (!candidate) return;
@@ -282398,8 +282955,8 @@ function pushUnique(out, candidate) {
282398
282955
  if (!out.includes(resolved)) out.push(resolved);
282399
282956
  }
282400
282957
  function pushSkillLayoutsAtRoot(out, root) {
282401
- pushUnique(out, join94(root, "skill", FUSION_SKILL_NAME2));
282402
- pushUnique(out, join94(root, "packages", "cli", "skill", FUSION_SKILL_NAME2));
282958
+ pushUnique(out, join95(root, "skill", FUSION_SKILL_NAME2));
282959
+ pushUnique(out, join95(root, "packages", "cli", "skill", FUSION_SKILL_NAME2));
282403
282960
  }
282404
282961
  function walkAncestorSkillCandidates(out, startDir, maxParents = 8) {
282405
282962
  let dir = startDir;
@@ -282414,7 +282971,7 @@ function pushPackageRequireCandidates(out, from) {
282414
282971
  try {
282415
282972
  const require5 = createRequire8(from);
282416
282973
  const pkgJson = require5.resolve("@runfusion/fusion/package.json");
282417
- pushUnique(out, join94(dirname40(pkgJson), "skill", FUSION_SKILL_NAME2));
282974
+ pushUnique(out, join95(dirname40(pkgJson), "skill", FUSION_SKILL_NAME2));
282418
282975
  } catch {
282419
282976
  }
282420
282977
  }
@@ -282482,21 +283039,21 @@ function collectSkillsFromRoot(root, out) {
282482
283039
  return;
282483
283040
  }
282484
283041
  for (const entry of entries) {
282485
- const child = join94(root, entry);
283042
+ const child = join95(root, entry);
282486
283043
  if (isSkillDir(child)) {
282487
283044
  out.set(entry, child);
282488
283045
  }
282489
283046
  }
282490
283047
  }
282491
283048
  function stageGrokSessionSkills(options = {}) {
282492
- const pluginDir = mkdtempSync3(join94(tmpdir15(), "fusion-grok-plugin-"));
282493
- const skillsDir = join94(pluginDir, "skills");
283049
+ const pluginDir = mkdtempSync3(join95(tmpdir15(), "fusion-grok-plugin-"));
283050
+ const skillsDir = join95(pluginDir, "skills");
282494
283051
  mkdirSync14(skillsDir, { recursive: true });
282495
283052
  const installed = /* @__PURE__ */ new Map();
282496
283053
  const includeFusion = options.includeFusionSkill !== false;
282497
283054
  if (includeFusion) {
282498
283055
  const fusionSource = resolveBundledFusionSkillSource2();
282499
- if (fusionSource && installSkillDir(fusionSource, join94(skillsDir, FUSION_SKILL_NAME2))) {
283056
+ if (fusionSource && installSkillDir(fusionSource, join95(skillsDir, FUSION_SKILL_NAME2))) {
282500
283057
  installed.set(FUSION_SKILL_NAME2, fusionSource);
282501
283058
  }
282502
283059
  }
@@ -282506,15 +283063,15 @@ function stageGrokSessionSkills(options = {}) {
282506
283063
  }
282507
283064
  for (const [name2, source] of installed) {
282508
283065
  if (name2 === FUSION_SKILL_NAME2 && includeFusion) continue;
282509
- installSkillDir(source, join94(skillsDir, name2));
283066
+ installSkillDir(source, join95(skillsDir, name2));
282510
283067
  }
282511
283068
  for (const [name2, source] of installed) {
282512
- if (!existsSync71(join94(skillsDir, name2))) {
282513
- installSkillDir(source, join94(skillsDir, name2));
283069
+ if (!existsSync71(join95(skillsDir, name2))) {
283070
+ installSkillDir(source, join95(skillsDir, name2));
282514
283071
  }
282515
283072
  }
282516
283073
  writeFileSync14(
282517
- join94(pluginDir, "plugin.json"),
283074
+ join95(pluginDir, "plugin.json"),
282518
283075
  JSON.stringify(
282519
283076
  {
282520
283077
  name: "fusion-session-skills",
@@ -282580,7 +283137,7 @@ var init_skill_loader = __esm({
282580
283137
  import { createServer as createServer2 } from "node:http";
282581
283138
  import { existsSync as existsSync72, writeFileSync as writeFileSync15 } from "node:fs";
282582
283139
  import { tmpdir as tmpdir16 } from "node:os";
282583
- import { dirname as dirname41, join as join95 } from "node:path";
283140
+ import { dirname as dirname41, join as join96 } from "node:path";
282584
283141
  import { fileURLToPath as fileURLToPath9 } from "node:url";
282585
283142
  import { randomUUID as randomUUID52 } from "node:crypto";
282586
283143
  function toolsToMcpToolDefs2(tools) {
@@ -282592,7 +283149,7 @@ function toolsToMcpToolDefs2(tools) {
282592
283149
  }));
282593
283150
  }
282594
283151
  function fusionToolsMcpServerPath() {
282595
- return join95(dirname41(fileURLToPath9(import.meta.url)), "mcp-schema-server.cjs");
283152
+ return join96(dirname41(fileURLToPath9(import.meta.url)), "mcp-schema-server.cjs");
282596
283153
  }
282597
283154
  function missingMcpSchemaServerError(serverPath) {
282598
283155
  const error3 = new Error(`Fusion MCP schema server is missing: ${serverPath}`);
@@ -282633,7 +283190,7 @@ async function startFusionToolBridge(tools) {
282633
283190
  byName.set(tool.name, tool);
282634
283191
  }
282635
283192
  }
282636
- const schemaPath = join95(tmpdir16(), `fusion-grok-mcp-schemas-${process.pid}-${randomUUID52()}.json`);
283193
+ const schemaPath = join96(tmpdir16(), `fusion-grok-mcp-schemas-${process.pid}-${randomUUID52()}.json`);
282637
283194
  writeFileSync15(schemaPath, JSON.stringify(defs));
282638
283195
  const server = createServer2(async (req, res) => {
282639
283196
  if (req.method !== "POST" || req.url !== "/tool-call") {
@@ -283066,7 +283623,7 @@ var init_src11 = __esm({
283066
283623
 
283067
283624
  // ../../plugins/fusion-plugin-claude-runtime/src/acp/cli-spawn.ts
283068
283625
  import { existsSync as existsSync73 } from "node:fs";
283069
- import { dirname as dirname42, isAbsolute as isAbsolute23, join as join96, resolve as resolve44 } from "node:path";
283626
+ import { dirname as dirname42, isAbsolute as isAbsolute23, join as join97, resolve as resolve44 } from "node:path";
283070
283627
  import { fileURLToPath as fileURLToPath10 } from "node:url";
283071
283628
  function asTrimmedString2(value) {
283072
283629
  return typeof value === "string" && value.trim().length > 0 ? value.trim() : void 0;
@@ -283084,7 +283641,7 @@ function pluginRootDir2() {
283084
283641
  }
283085
283642
  function bundledClaudeBridgeBinPath2(pluginRoot = pluginRootDir2()) {
283086
283643
  const extension = process.platform === "win32" ? ".cmd" : "";
283087
- return join96(pluginRoot, "node_modules", ".bin", `${CLAUDE_CODE_CLI_ACP_BINARY2}${extension}`);
283644
+ return join97(pluginRoot, "node_modules", ".bin", `${CLAUDE_CODE_CLI_ACP_BINARY2}${extension}`);
283088
283645
  }
283089
283646
  function resolveBundledClaudeBridgeBinary2(options = {}) {
283090
283647
  const root = options.pluginRoot ?? pluginRootDir2();
@@ -284324,14 +284881,14 @@ var init_acp2 = __esm({
284324
284881
  // ../../plugins/fusion-plugin-claude-runtime/src/cli-spawn.ts
284325
284882
  import { spawn as spawn19 } from "node:child_process";
284326
284883
  import { existsSync as existsSync74 } from "node:fs";
284327
- import { basename as basename25, dirname as dirname44, isAbsolute as isAbsolute24, join as join98, resolve as resolve46 } from "node:path";
284884
+ import { basename as basename25, dirname as dirname44, isAbsolute as isAbsolute24, join as join99, resolve as resolve46 } from "node:path";
284328
284885
  import { fileURLToPath as fileURLToPath11 } from "node:url";
284329
284886
  function pluginRootDir3() {
284330
284887
  const moduleDir = dirname44(fileURLToPath11(import.meta.url));
284331
284888
  return ["src", "dist"].includes(basename25(moduleDir)) ? resolve46(moduleDir, "..") : moduleDir;
284332
284889
  }
284333
284890
  function bundledClaudeBridgeBinPath3(pluginRoot = pluginRootDir3()) {
284334
- return join98(pluginRoot, "bridge", `${CLAUDE_CODE_CLI_ACP_BINARY3}${process.platform === "win32" ? ".cmd" : ""}`);
284891
+ return join99(pluginRoot, "bridge", `${CLAUDE_CODE_CLI_ACP_BINARY3}${process.platform === "win32" ? ".cmd" : ""}`);
284335
284892
  }
284336
284893
  function resolveBundledClaudeBridgeBinary3(options = {}) {
284337
284894
  const candidate = bundledClaudeBridgeBinPath3(options.pluginRoot ?? pluginRootDir3());
@@ -284508,10 +285065,10 @@ import {
284508
285065
  } from "node:fs";
284509
285066
  import { createRequire as createRequire9 } from "node:module";
284510
285067
  import { tmpdir as tmpdir17 } from "node:os";
284511
- import { basename as basename26, dirname as dirname45, join as join99, resolve as resolve47 } from "node:path";
285068
+ import { basename as basename26, dirname as dirname45, join as join100, resolve as resolve47 } from "node:path";
284512
285069
  import { fileURLToPath as fileURLToPath12 } from "node:url";
284513
285070
  function isSkillDir2(dir) {
284514
- return existsSync75(join99(dir, "SKILL.md"));
285071
+ return existsSync75(join100(dir, "SKILL.md"));
284515
285072
  }
284516
285073
  function pushUnique2(out, candidate) {
284517
285074
  if (!candidate) return;
@@ -284519,8 +285076,8 @@ function pushUnique2(out, candidate) {
284519
285076
  if (!out.includes(resolved)) out.push(resolved);
284520
285077
  }
284521
285078
  function pushSkillLayoutsAtRoot2(out, root) {
284522
- pushUnique2(out, join99(root, "skill", FUSION_SKILL_NAME3));
284523
- pushUnique2(out, join99(root, "packages", "cli", "skill", FUSION_SKILL_NAME3));
285079
+ pushUnique2(out, join100(root, "skill", FUSION_SKILL_NAME3));
285080
+ pushUnique2(out, join100(root, "packages", "cli", "skill", FUSION_SKILL_NAME3));
284524
285081
  }
284525
285082
  function walkAncestorSkillCandidates2(out, startDir, maxParents = 8) {
284526
285083
  let dir = startDir;
@@ -284535,7 +285092,7 @@ function pushPackageRequireCandidates2(out, from) {
284535
285092
  try {
284536
285093
  const require5 = createRequire9(from);
284537
285094
  const pkgJson = require5.resolve("@runfusion/fusion/package.json");
284538
- pushUnique2(out, join99(dirname45(pkgJson), "skill", FUSION_SKILL_NAME3));
285095
+ pushUnique2(out, join100(dirname45(pkgJson), "skill", FUSION_SKILL_NAME3));
284539
285096
  } catch {
284540
285097
  }
284541
285098
  }
@@ -284603,21 +285160,21 @@ function collectSkillsFromRoot2(root, out) {
284603
285160
  return;
284604
285161
  }
284605
285162
  for (const entry of entries) {
284606
- const child = join99(root, entry);
285163
+ const child = join100(root, entry);
284607
285164
  if (isSkillDir2(child)) {
284608
285165
  out.set(entry, child);
284609
285166
  }
284610
285167
  }
284611
285168
  }
284612
285169
  function stageClaudeSessionSkills(options = {}) {
284613
- const pluginDir = mkdtempSync4(join99(tmpdir17(), "fusion-claude-plugin-"));
284614
- const skillsDir = join99(pluginDir, "skills");
285170
+ const pluginDir = mkdtempSync4(join100(tmpdir17(), "fusion-claude-plugin-"));
285171
+ const skillsDir = join100(pluginDir, "skills");
284615
285172
  mkdirSync15(skillsDir, { recursive: true });
284616
285173
  const installed = /* @__PURE__ */ new Map();
284617
285174
  const includeFusion = options.includeFusionSkill !== false;
284618
285175
  if (includeFusion) {
284619
285176
  const fusionSource = resolveBundledFusionSkillSource3();
284620
- if (fusionSource && installSkillDir2(fusionSource, join99(skillsDir, FUSION_SKILL_NAME3))) {
285177
+ if (fusionSource && installSkillDir2(fusionSource, join100(skillsDir, FUSION_SKILL_NAME3))) {
284621
285178
  installed.set(FUSION_SKILL_NAME3, fusionSource);
284622
285179
  }
284623
285180
  }
@@ -284627,15 +285184,15 @@ function stageClaudeSessionSkills(options = {}) {
284627
285184
  }
284628
285185
  for (const [name2, source] of installed) {
284629
285186
  if (name2 === FUSION_SKILL_NAME3 && includeFusion) continue;
284630
- installSkillDir2(source, join99(skillsDir, name2));
285187
+ installSkillDir2(source, join100(skillsDir, name2));
284631
285188
  }
284632
285189
  for (const [name2, source] of installed) {
284633
- if (!existsSync75(join99(skillsDir, name2))) {
284634
- installSkillDir2(source, join99(skillsDir, name2));
285190
+ if (!existsSync75(join100(skillsDir, name2))) {
285191
+ installSkillDir2(source, join100(skillsDir, name2));
284635
285192
  }
284636
285193
  }
284637
285194
  writeFileSync16(
284638
- join99(pluginDir, "plugin.json"),
285195
+ join100(pluginDir, "plugin.json"),
284639
285196
  JSON.stringify(
284640
285197
  {
284641
285198
  name: "fusion-session-skills",
@@ -284701,7 +285258,7 @@ var init_skill_loader2 = __esm({
284701
285258
  import { createServer as createServer3 } from "node:http";
284702
285259
  import { existsSync as existsSync76, rmSync as rmSync13, writeFileSync as writeFileSync17 } from "node:fs";
284703
285260
  import { tmpdir as tmpdir18 } from "node:os";
284704
- import { dirname as dirname46, join as join100 } from "node:path";
285261
+ import { dirname as dirname46, join as join101 } from "node:path";
284705
285262
  import { fileURLToPath as fileURLToPath13 } from "node:url";
284706
285263
  import { randomBytes as randomBytes7, randomUUID as randomUUID53 } from "node:crypto";
284707
285264
  function toolsToMcpToolDefs3(tools) {
@@ -284713,7 +285270,7 @@ function toolsToMcpToolDefs3(tools) {
284713
285270
  }));
284714
285271
  }
284715
285272
  function fusionToolsMcpServerPath2() {
284716
- return join100(dirname46(fileURLToPath13(import.meta.url)), "mcp-schema-server.cjs");
285273
+ return join101(dirname46(fileURLToPath13(import.meta.url)), "mcp-schema-server.cjs");
284717
285274
  }
284718
285275
  function missingMcpSchemaServerError2(serverPath) {
284719
285276
  const error3 = new Error(`Fusion MCP schema server is missing: ${serverPath}`);
@@ -284754,7 +285311,7 @@ async function startFusionToolBridge2(tools, options = {}) {
284754
285311
  byName.set(tool.name, tool);
284755
285312
  }
284756
285313
  }
284757
- const schemaPath = join100(tmpdir18(), `fusion-claude-mcp-schemas-${process.pid}-${randomUUID53()}.json`);
285314
+ const schemaPath = join101(tmpdir18(), `fusion-claude-mcp-schemas-${process.pid}-${randomUUID53()}.json`);
284758
285315
  writeFileSync17(schemaPath, JSON.stringify(defs));
284759
285316
  const capabilityToken = randomBytes7(32).toString("base64url");
284760
285317
  const server = createServer3(async (req, res) => {
@@ -285558,7 +286115,7 @@ var init_provider6 = __esm({
285558
286115
 
285559
286116
  // ../../plugins/fusion-plugin-omp-runtime/src/acp/cli-spawn.ts
285560
286117
  import { existsSync as existsSync77 } from "node:fs";
285561
- import { dirname as dirname47, isAbsolute as isAbsolute25, join as join101, resolve as resolve48 } from "node:path";
286118
+ import { dirname as dirname47, isAbsolute as isAbsolute25, join as join102, resolve as resolve48 } from "node:path";
285562
286119
  import { fileURLToPath as fileURLToPath14 } from "node:url";
285563
286120
  function asTrimmedString3(value) {
285564
286121
  return typeof value === "string" && value.trim().length > 0 ? value.trim() : void 0;
@@ -285576,7 +286133,7 @@ function pluginRootDir4() {
285576
286133
  }
285577
286134
  function bundledClaudeBridgeBinPath4(pluginRoot = pluginRootDir4()) {
285578
286135
  const extension = process.platform === "win32" ? ".cmd" : "";
285579
- return join101(pluginRoot, "node_modules", ".bin", `${CLAUDE_CODE_CLI_ACP_BINARY4}${extension}`);
286136
+ return join102(pluginRoot, "node_modules", ".bin", `${CLAUDE_CODE_CLI_ACP_BINARY4}${extension}`);
285580
286137
  }
285581
286138
  function resolveBundledClaudeBridgeBinary4(options = {}) {
285582
286139
  const root = options.pluginRoot ?? pluginRootDir4();
@@ -286894,7 +287451,7 @@ var init_mcp_forwarding3 = __esm({
286894
287451
  import { createServer as createServer4 } from "node:http";
286895
287452
  import { existsSync as existsSync78, unlinkSync as unlinkSync5, writeFileSync as writeFileSync18 } from "node:fs";
286896
287453
  import { tmpdir as tmpdir19 } from "node:os";
286897
- import { dirname as dirname49, join as join103 } from "node:path";
287454
+ import { dirname as dirname49, join as join104 } from "node:path";
286898
287455
  import { fileURLToPath as fileURLToPath15 } from "node:url";
286899
287456
  import { randomUUID as randomUUID54 } from "node:crypto";
286900
287457
  function toolsToMcpToolDefs4(tools) {
@@ -286911,12 +287468,12 @@ function fusionToolsMcpServerPath3() {
286911
287468
  const here = dirname49(fileURLToPath15(import.meta.url));
286912
287469
  const candidates = [
286913
287470
  // Packaged CLI / same-dir as bundled.js or tool-bridge source.
286914
- join103(here, "mcp-schema-server.cjs"),
287471
+ join104(here, "mcp-schema-server.cjs"),
286915
287472
  // Source layout when running from dist/ that still has ../src assets.
286916
- join103(here, "src", "mcp-schema-server.cjs"),
286917
- join103(here, "..", "src", "mcp-schema-server.cjs"),
287473
+ join104(here, "src", "mcp-schema-server.cjs"),
287474
+ join104(here, "..", "src", "mcp-schema-server.cjs"),
286918
287475
  // Monorepo plugin root relative to src/ or dist/plugins/.../bundled.js.
286919
- join103(here, "..", "mcp-schema-server.cjs")
287476
+ join104(here, "..", "mcp-schema-server.cjs")
286920
287477
  ];
286921
287478
  for (const candidate of candidates) {
286922
287479
  if (existsSync78(candidate)) return candidate;
@@ -286953,7 +287510,7 @@ async function startFusionToolBridge3(tools) {
286953
287510
  byName.set(tool.name, tool);
286954
287511
  }
286955
287512
  }
286956
- const schemaPath = join103(tmpdir19(), `fusion-omp-mcp-schemas-${process.pid}-${randomUUID54()}.json`);
287513
+ const schemaPath = join104(tmpdir19(), `fusion-omp-mcp-schemas-${process.pid}-${randomUUID54()}.json`);
286957
287514
  writeFileSync18(schemaPath, JSON.stringify(defs));
286958
287515
  const server = createServer4(async (req, res) => {
286959
287516
  if (req.method !== "POST" || req.url !== "/tool-call") {
@@ -288675,8 +289232,8 @@ var init_types5 = __esm({
288675
289232
 
288676
289233
  // ../dashboard/src/stt/model-manager.ts
288677
289234
  import { createHash as createHash25, randomUUID as randomUUID56 } from "node:crypto";
288678
- import { mkdir as mkdir31, open as open4, readFile as readFile44, readdir as readdir11, rename as rename10, rm as rm18, stat as stat12, writeFile as writeFile34 } from "node:fs/promises";
288679
- import { join as join104, relative as relative19, resolve as resolve50, sep as sep15 } from "node:path";
289235
+ import { mkdir as mkdir31, open as open4, readFile as readFile45, readdir as readdir11, rename as rename10, rm as rm18, stat as stat12, writeFile as writeFile34 } from "node:fs/promises";
289236
+ import { join as join105, relative as relative19, resolve as resolve50, sep as sep15 } from "node:path";
288680
289237
  function parseSafeTarListing(listing) {
288681
289238
  for (const line2 of listing.split("\n").filter(Boolean)) {
288682
289239
  const type = line2[0];
@@ -288691,8 +289248,8 @@ function parseSafeTarListing(listing) {
288691
289248
  function createVoiceModelManager(options = {}) {
288692
289249
  const asset = options.asset ?? PARAKEET_V3_ASSET;
288693
289250
  let configuredCacheDir = options.cacheDir;
288694
- const cacheDir = () => configuredCacheDir ??= join104(resolveGlobalDir(), "models", "parakeet-v3");
288695
- const finalDir = () => join104(cacheDir(), "model");
289251
+ const cacheDir = () => configuredCacheDir ??= join105(resolveGlobalDir(), "models", "parakeet-v3");
289252
+ const finalDir = () => join105(cacheDir(), "model");
288696
289253
  let state = { status: "not-installed" };
288697
289254
  let generation = 0;
288698
289255
  let cleanupBarrier = Promise.resolve();
@@ -288755,8 +289312,8 @@ function createVoiceModelManager(options = {}) {
288755
289312
  if (!current(g2, signal)) return state;
288756
289313
  await mkdir31(cacheDir(), { recursive: true });
288757
289314
  if (!current(g2, signal)) return state;
288758
- partial2 = join104(cacheDir(), `.partial-${g2}-${randomUUID56()}`);
288759
- staging = join104(cacheDir(), `.staging-${g2}-${randomUUID56()}`);
289315
+ partial2 = join105(cacheDir(), `.partial-${g2}-${randomUUID56()}`);
289316
+ staging = join105(cacheDir(), `.staging-${g2}-${randomUUID56()}`);
288760
289317
  const file = await open4(partial2, "wx");
288761
289318
  try {
288762
289319
  const response = await (options.fetch ?? globalThis.fetch)(asset.url, { signal });
@@ -288801,10 +289358,10 @@ function createVoiceModelManager(options = {}) {
288801
289358
  }
288802
289359
  }
288803
289360
  if (!current(g2, signal)) return state;
288804
- await writeFile34(join104(staging, "manifest.json"), JSON.stringify({ filename: asset.filename, sha256: asset.sha256, installedAt: (/* @__PURE__ */ new Date()).toISOString(), expectedFiles: asset.expectedFiles }));
289361
+ await writeFile34(join105(staging, "manifest.json"), JSON.stringify({ filename: asset.filename, sha256: asset.sha256, installedAt: (/* @__PURE__ */ new Date()).toISOString(), expectedFiles: asset.expectedFiles }));
288805
289362
  await options.beforePromote?.(signal);
288806
289363
  if (!current(g2, signal)) return state;
288807
- const backup = join104(cacheDir(), `.backup-${g2}-${randomUUID56()}`);
289364
+ const backup = join105(cacheDir(), `.backup-${g2}-${randomUUID56()}`);
288808
289365
  let movedPrevious = false;
288809
289366
  try {
288810
289367
  try {
@@ -288884,9 +289441,9 @@ function createVoiceModelManager(options = {}) {
288884
289441
  publish(absent);
288885
289442
  return absent;
288886
289443
  }
288887
- const manifest = JSON.parse(await readFile44(join104(finalDir(), "manifest.json"), "utf8"));
289444
+ const manifest = JSON.parse(await readFile45(join105(finalDir(), "manifest.json"), "utf8"));
288888
289445
  if (manifest.sha256 !== asset.sha256) throw new Error("stale manifest digest");
288889
- for (const expected of asset.expectedFiles) if (!await existsFile(join104(finalDir(), expected))) throw new Error("missing expected model file");
289446
+ for (const expected of asset.expectedFiles) if (!await existsFile(join105(finalDir(), expected))) throw new Error("missing expected model file");
288890
289447
  const installed = { status: "installed", checksumVerified: true, installedPath: finalDir() };
288891
289448
  publish(installed);
288892
289449
  return installed;
@@ -288898,7 +289455,7 @@ function createVoiceModelManager(options = {}) {
288898
289455
  try {
288899
289456
  for (const entry of await readdir11(cacheDir())) {
288900
289457
  const own = /^\.(partial|staging|backup)-(\d+)-/.exec(entry);
288901
- if (own && Number(own[2]) !== active?.generation) await rm18(join104(cacheDir(), entry), { recursive: true, force: true });
289458
+ if (own && Number(own[2]) !== active?.generation) await rm18(join105(cacheDir(), entry), { recursive: true, force: true });
288902
289459
  }
288903
289460
  } catch {
288904
289461
  }
@@ -288920,7 +289477,7 @@ function createVoiceModelManager(options = {}) {
288920
289477
  try {
288921
289478
  for (const entry of await readdir11(cacheDir())) {
288922
289479
  const match = /^\.(partial|staging|backup)-(\d+)-/.exec(entry);
288923
- if (match && Number(match[2]) <= g2) await rm18(join104(cacheDir(), entry), { recursive: true, force: true });
289480
+ if (match && Number(match[2]) <= g2) await rm18(join105(cacheDir(), entry), { recursive: true, force: true });
288924
289481
  }
288925
289482
  } catch {
288926
289483
  }
@@ -289706,7 +290263,7 @@ var init_provider_health_monitor = __esm({
289706
290263
 
289707
290264
  // ../dashboard/src/server.ts
289708
290265
  import express3 from "express";
289709
- import { join as join105, dirname as dirname50 } from "node:path";
290266
+ import { join as join106, dirname as dirname50 } from "node:path";
289710
290267
  import { fileURLToPath as fileURLToPath16 } from "node:url";
289711
290268
  import { WebSocketServer as WebSocketServer2 } from "ws";
289712
290269
  function clearAiSessionCleanupInterval() {
@@ -290410,7 +290967,7 @@ __export(task_exports, {
290410
290967
  });
290411
290968
  import { createInterface as createInterface2 } from "node:readline/promises";
290412
290969
  import { watchFile, unwatchFile, statSync as statSync14, existsSync as existsSync80, readFileSync as readFileSync27 } from "node:fs";
290413
- import { basename as basename29, join as join106 } from "node:path";
290970
+ import { basename as basename29, join as join107 } from "node:path";
290414
290971
  function columnLabel(column) {
290415
290972
  return COLUMN_LABELS[column] ?? column;
290416
290973
  }
@@ -290768,7 +291325,7 @@ async function runTaskCreate(descriptionArg, attachFiles, depends, projectName,
290768
291325
  }
290769
291326
  console.log(` Path: .fusion/tasks/${resolvedTask.id}/`);
290770
291327
  if (attachFiles && attachFiles.length > 0) {
290771
- const { readFile: readFile46 } = await import("node:fs/promises");
291328
+ const { readFile: readFile47 } = await import("node:fs/promises");
290772
291329
  const { basename: basename31, extname: extname5, resolve: resolve53 } = await import("node:path");
290773
291330
  for (const filePath of attachFiles) {
290774
291331
  const resolvedPath = resolve53(filePath);
@@ -290781,7 +291338,7 @@ async function runTaskCreate(descriptionArg, attachFiles, depends, projectName,
290781
291338
  }
290782
291339
  let content;
290783
291340
  try {
290784
- content = await readFile46(resolvedPath);
291341
+ content = await readFile47(resolvedPath);
290785
291342
  } catch {
290786
291343
  console.error(` \u2717 Cannot read file: ${filePath}`);
290787
291344
  continue;
@@ -290952,7 +291509,7 @@ async function runTaskLogs(id, options = {}, projectName) {
290952
291509
  printEntries(filteredEntries);
290953
291510
  if (options.follow) {
290954
291511
  const projectPath = context.projectPath ?? process.cwd();
290955
- const logPath = join106(projectPath, ".fusion", "tasks", id, "agent.log");
291512
+ const logPath = join107(projectPath, ".fusion", "tasks", id, "agent.log");
290956
291513
  if (!existsSync80(logPath)) {
290957
291514
  console.log(`
290958
291515
  Waiting for log file to be created...`);
@@ -291157,7 +291714,7 @@ async function runTaskMerge(id, projectName) {
291157
291714
  }
291158
291715
  }
291159
291716
  async function runTaskAttach(id, filePath, projectName) {
291160
- const { readFile: readFile46 } = await import("node:fs/promises");
291717
+ const { readFile: readFile47 } = await import("node:fs/promises");
291161
291718
  const { basename: basename31, extname: extname5 } = await import("node:path");
291162
291719
  const { resolve: resolve53 } = await import("node:path");
291163
291720
  const resolvedPath = resolve53(filePath);
@@ -291171,7 +291728,7 @@ async function runTaskAttach(id, filePath, projectName) {
291171
291728
  }
291172
291729
  let content;
291173
291730
  try {
291174
- content = await readFile46(resolvedPath);
291731
+ content = await readFile47(resolvedPath);
291175
291732
  } catch {
291176
291733
  console.error(`Cannot read file: ${filePath}`);
291177
291734
  process.exit(1);
@@ -292336,8 +292893,8 @@ init_src15();
292336
292893
  import { randomUUID as randomUUID58 } from "node:crypto";
292337
292894
  import { Type as Type9 } from "typebox";
292338
292895
  import { StringEnum } from "@earendil-works/pi-ai";
292339
- import { resolve as resolve52, relative as relative20, isAbsolute as isAbsolute26, sep as sep16, basename as basename30, extname as extname4, join as join107 } from "node:path";
292340
- import { readFile as readFile45 } from "node:fs/promises";
292896
+ import { resolve as resolve52, relative as relative20, isAbsolute as isAbsolute26, sep as sep16, basename as basename30, extname as extname4, join as join108 } from "node:path";
292897
+ import { readFile as readFile46 } from "node:fs/promises";
292341
292898
  import { existsSync as existsSync81 } from "node:fs";
292342
292899
  import { spawn as spawn23 } from "node:child_process";
292343
292900
  import { AsyncLocalStorage } from "node:async_hooks";
@@ -292418,12 +292975,12 @@ function resolveProjectRoot2(cwd) {
292418
292975
  warnedMissingProjectRootResolver = true;
292419
292976
  console.warn("[fusion-extension] @fusion/core.getProjectRootFromWorktree is unavailable; using filesystem fallback resolution.");
292420
292977
  }
292421
- if (worktreeProjectRoot && existsSync81(join107(worktreeProjectRoot, ".fusion"))) {
292978
+ if (worktreeProjectRoot && existsSync81(join108(worktreeProjectRoot, ".fusion"))) {
292422
292979
  return worktreeProjectRoot;
292423
292980
  }
292424
292981
  let current = resolve52(cwd);
292425
292982
  while (true) {
292426
- if (existsSync81(join107(current, ".fusion"))) {
292983
+ if (existsSync81(join108(current, ".fusion"))) {
292427
292984
  return current;
292428
292985
  }
292429
292986
  const parent = resolve52(current, "..");
@@ -292685,7 +293242,7 @@ async function closeCachedStores() {
292685
293242
  }
292686
293243
  }
292687
293244
  function getFusionDir(cwd) {
292688
- return join107(resolveProjectRoot2(cwd), ".fusion");
293245
+ return join108(resolveProjectRoot2(cwd), ".fusion");
292689
293246
  }
292690
293247
  function requireProjectLayer(store, consumer) {
292691
293248
  const layer = store.getAsyncLayer();
@@ -293467,7 +294024,7 @@ ${renderAgentLogEntries(entries)}` : `${header}
293467
294024
  }
293468
294025
  let content;
293469
294026
  try {
293470
- content = await readFile45(filePath);
294027
+ content = await readFile46(filePath);
293471
294028
  } catch {
293472
294029
  throw new Error(`Cannot read file: ${params.path}`);
293473
294030
  }