@runfusion/fusion 0.77.0-beta.5 → 0.77.0-beta.6

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 (114) hide show
  1. package/dist/bin.js +433 -102
  2. package/dist/child-process-worker.js +352 -81
  3. package/dist/client/.vite/manifest.json +261 -261
  4. package/dist/client/assets/{AgentDetailView-CSK_Rnlk.js → AgentDetailView-oyY72uSo.js} +1 -1
  5. package/dist/client/assets/{AgentPermissionPolicyEditor-kG44Adcm.js → AgentPermissionPolicyEditor-C3Qo9p1M.js} +1 -1
  6. package/dist/client/assets/{AgentsView-ClKwKWnU.js → AgentsView-M2MhTIGW.js} +3 -3
  7. package/dist/client/assets/{ChatView-DoOqgkPX.js → ChatView-zKrUSqiQ.js} +1 -1
  8. package/dist/client/assets/{CommandCenter-CJxvPbgn.js → CommandCenter-CpcAt6jt.js} +27 -27
  9. package/dist/client/assets/{DevServerView-C4nyLONX.js → DevServerView-CjTneoG7.js} +1 -1
  10. package/dist/client/assets/{DirectoryPicker-BIvXWIB5.js → DirectoryPicker-BY6V5HLV.js} +1 -1
  11. package/dist/client/assets/{DocumentsView-DH_aZysM.js → DocumentsView-RDK56-qg.js} +1 -1
  12. package/dist/client/assets/{EvalsView-klRK-GJQ.js → EvalsView-DKK4ceUW.js} +1 -1
  13. package/dist/client/assets/{ExperimentalAgentOnboardingModal-CgBmukIm.js → ExperimentalAgentOnboardingModal-DiBPZVwC.js} +1 -1
  14. package/dist/client/assets/{GoalsView-ysh3X2x6.js → GoalsView-DPj59B3l.js} +1 -1
  15. package/dist/client/assets/{InsightsView-BbMjCu7J.js → InsightsView-QLJNkKJp.js} +1 -1
  16. package/dist/client/assets/{MemoryView-B3IgA8Bx.js → MemoryView-CDCS-1ig.js} +1 -1
  17. package/dist/client/assets/{PiExtensionsManager-CZlIw7C3.js → PiExtensionsManager-DktXXrmF.js} +1 -1
  18. package/dist/client/assets/{PluginManager-vy0YZ2Nx.js → PluginManager-CQ3iEFMq.js} +1 -1
  19. package/dist/client/assets/{PullRequestView-DhhsJV8I.js → PullRequestView-CkObT6e3.js} +1 -1
  20. package/dist/client/assets/{ReportModal-CQMn4i1y.js → ReportModal-B5LTdDgi.js} +1 -1
  21. package/dist/client/assets/{ResearchView-CVwfs4l3.js → ResearchView-CryFrV8l.js} +1 -1
  22. package/dist/client/assets/{SecretsView-CrJfHle9.js → SecretsView-9YcNW_fo.js} +1 -1
  23. package/dist/client/assets/{SessionTerminal-BRI8W1-G.js → SessionTerminal-BWsA9puD.js} +1 -1
  24. package/dist/client/assets/{SettingsModal-h1A_aFqD.js → SettingsModal-CL6aiXiI.js} +4 -4
  25. package/dist/client/assets/{SettingsModal-B6U5Umru.js → SettingsModal-llkoaJev.js} +1 -1
  26. package/dist/client/assets/{SettingsTextareaRow-O7-u9uMC.js → SettingsTextareaRow-B_Legu-A.js} +1 -1
  27. package/dist/client/assets/{SetupWizardModal-nXfJp8cA.js → SetupWizardModal-DLA4UAGp.js} +2 -2
  28. package/dist/client/assets/{SkillsView-CoWAjvWU.js → SkillsView-CSeauwIw.js} +1 -1
  29. package/dist/client/assets/{WorkflowNodeEditor-BFMM3RF_.js → WorkflowNodeEditor-CGgMiiWy.js} +1 -1
  30. package/dist/client/assets/{agent-import-generation-BqDmtTur.js → agent-import-generation-CttglSmZ.js} +1 -1
  31. package/dist/client/assets/{agentsOrgChartLayout-C4cLqlV6.js → agentsOrgChartLayout-f-9mwRbD.js} +1 -1
  32. package/dist/client/assets/{architectureDiagram-3BPJPVTR-CdxcwdRe.js → architectureDiagram-3BPJPVTR-DA_Hm_di.js} +1 -1
  33. package/dist/client/assets/{blockDiagram-GPEHLZMM-CfQrRfHR.js → blockDiagram-GPEHLZMM-JF7K8CcV.js} +1 -1
  34. package/dist/client/assets/{c4Diagram-AAUBKEIU-Cx7-2SOm.js → c4Diagram-AAUBKEIU-DN-wklCP.js} +1 -1
  35. package/dist/client/assets/channel-CwFfFSI9.js +1 -0
  36. package/dist/client/assets/{chunk-2J33WTMH-D8jvgP3M.js → chunk-2J33WTMH-ChtjU8Cu.js} +1 -1
  37. package/dist/client/assets/{chunk-4BX2VUAB-B5tBx_s_.js → chunk-4BX2VUAB-dJL0n3nn.js} +1 -1
  38. package/dist/client/assets/{chunk-55IACEB6-C-F-HQ5Z.js → chunk-55IACEB6-Bfg-QB9A.js} +1 -1
  39. package/dist/client/assets/{chunk-727SXJPM-BP8Ln-8C.js → chunk-727SXJPM-BHPPwYUS.js} +1 -1
  40. package/dist/client/assets/{chunk-AQP2D5EJ-DMnTmRmn.js → chunk-AQP2D5EJ-DIIpPUQN.js} +1 -1
  41. package/dist/client/assets/{chunk-FMBD7UC4-D_YdfnNc.js → chunk-FMBD7UC4-D230R32b.js} +1 -1
  42. package/dist/client/assets/{chunk-ND2GUHAM-CULXyuXq.js → chunk-ND2GUHAM-0Lhp5CRv.js} +1 -1
  43. package/dist/client/assets/{chunk-QZHKN3VN-CT1BWEJL.js → chunk-QZHKN3VN-BxbHBfKJ.js} +1 -1
  44. package/dist/client/assets/classDiagram-4FO5ZUOK-C8dZsxxX.js +1 -0
  45. package/dist/client/assets/classDiagram-v2-Q7XG4LA2-C8dZsxxX.js +1 -0
  46. package/dist/client/assets/{cose-bilkent-S5V4N54A-M-3DXa6e.js → cose-bilkent-S5V4N54A-3vEwhfYz.js} +1 -1
  47. package/dist/client/assets/{dagre-BM42HDAG-Bsiqvroz.js → dagre-BM42HDAG-Br44UsdY.js} +1 -1
  48. package/dist/client/assets/{dashboard-view-Bo_gFjdO.js → dashboard-view-B0OM3KYC.js} +1 -1
  49. package/dist/client/assets/{dashboard-view-CnFFG3cW.js → dashboard-view-BldxxYfz.js} +1 -1
  50. package/dist/client/assets/{dashboard-view-DAHn20ph.js → dashboard-view-CcPcELPx.js} +1 -1
  51. package/dist/client/assets/{dashboard-view-BEBh_Ei5.js → dashboard-view-_EglxMDl.js} +1 -1
  52. package/dist/client/assets/{diagram-2AECGRRQ-CZiHoXo5.js → diagram-2AECGRRQ-CLNxbHUP.js} +1 -1
  53. package/dist/client/assets/{diagram-5GNKFQAL-BoJVz4Or.js → diagram-5GNKFQAL-D3cNAeQH.js} +1 -1
  54. package/dist/client/assets/{diagram-KO2AKTUF-DivGQuAL.js → diagram-KO2AKTUF-DkX7h6to.js} +1 -1
  55. package/dist/client/assets/{diagram-LMA3HP47-DkMK7OJp.js → diagram-LMA3HP47-6iQIiYUr.js} +1 -1
  56. package/dist/client/assets/{diagram-OG6HWLK6-DFFvRcpW.js → diagram-OG6HWLK6-Caje3229.js} +1 -1
  57. package/dist/client/assets/{erDiagram-TEJ5UH35--217vrom.js → erDiagram-TEJ5UH35-BkU-1HB1.js} +1 -1
  58. package/dist/client/assets/{flowDiagram-I6XJVG4X-CQgWO3xf.js → flowDiagram-I6XJVG4X-BRx3V6RE.js} +1 -1
  59. package/dist/client/assets/{folder-open-DR6cRcNR.js → folder-open-BaSOBfk2.js} +1 -1
  60. package/dist/client/assets/{ganttDiagram-6RSMTGT7-BjDMUrS3.js → ganttDiagram-6RSMTGT7-D9f1kH3t.js} +1 -1
  61. package/dist/client/assets/{gitGraphDiagram-PVQCEYII-gKUcDnln.js → gitGraphDiagram-PVQCEYII-CQwmZcRd.js} +1 -1
  62. package/dist/client/assets/{index-oxnX59eO.js → index-B8PcG8AJ.js} +298 -298
  63. package/dist/client/assets/{infoDiagram-5YYISTIA-BA91T8S5.js → infoDiagram-5YYISTIA-DYWBkGjG.js} +1 -1
  64. package/dist/client/assets/{ishikawaDiagram-YF4QCWOH-0BY5xe-c.js → ishikawaDiagram-YF4QCWOH-Df0Nzvh4.js} +1 -1
  65. package/dist/client/assets/{journeyDiagram-JHISSGLW-B_OwenbQ.js → journeyDiagram-JHISSGLW-bSvrZSyZ.js} +1 -1
  66. package/dist/client/assets/{kanban-definition-UN3LZRKU-GLabUTKp.js → kanban-definition-UN3LZRKU-yHJ101M8.js} +1 -1
  67. package/dist/client/assets/{mermaid.core-BXo4rxYI.js → mermaid.core-Bc_0VLJw.js} +4 -4
  68. package/dist/client/assets/{mindmap-definition-RKZ34NQL-DZl6LPoE.js → mindmap-definition-RKZ34NQL-DWRnMguM.js} +1 -1
  69. package/dist/client/assets/{pieDiagram-4H26LBE5-DtHq-4py.js → pieDiagram-4H26LBE5-Ds-DEOqr.js} +1 -1
  70. package/dist/client/assets/{puzzle-OVm-I1L4.js → puzzle-CdYF81IT.js} +1 -1
  71. package/dist/client/assets/{quadrantDiagram-W4KKPZXB-BdjXhd5d.js → quadrantDiagram-W4KKPZXB-Bz-j2fkf.js} +1 -1
  72. package/dist/client/assets/{requirementDiagram-4Y6WPE33-BoFVjELN.js → requirementDiagram-4Y6WPE33-BaQ_PaZ-.js} +1 -1
  73. package/dist/client/assets/{sankeyDiagram-5OEKKPKP-B0qyJh-P.js → sankeyDiagram-5OEKKPKP-pUCz8V4-.js} +1 -1
  74. package/dist/client/assets/{sequenceDiagram-3UESZ5HK-ByY62Eqp.js → sequenceDiagram-3UESZ5HK-CFwUMspe.js} +1 -1
  75. package/dist/client/assets/{shield-alert-CDy69fl7.js → shield-alert-4qeU3_Rm.js} +1 -1
  76. package/dist/client/assets/{standing-instructions-template-BsnnRlwm.js → standing-instructions-template-Crocrob7.js} +1 -1
  77. package/dist/client/assets/{stateDiagram-AJRCARHV-CwJ-_Phi.js → stateDiagram-AJRCARHV-CHZwo-vd.js} +1 -1
  78. package/dist/client/assets/{stateDiagram-v2-BHNVJYJU-Cfvhq8mV.js → stateDiagram-v2-BHNVJYJU-BTvKaIZR.js} +1 -1
  79. package/dist/client/assets/{timeline-definition-PNZ67QCA-Co8VfGN9.js → timeline-definition-PNZ67QCA-Cn3XIoz2.js} +1 -1
  80. package/dist/client/assets/{upload-CkqDbEeE.js → upload--GqImz6h.js} +1 -1
  81. package/dist/client/assets/{useDiscoveredSkillsCache-BHUasRiZ.js → useDiscoveredSkillsCache-BEHgTFai.js} +1 -1
  82. package/dist/client/assets/{users-B8DkpbXD.js → users-B0h1q4x6.js} +1 -1
  83. package/dist/client/assets/{vennDiagram-CIIHVFJN-XSJteQrL.js → vennDiagram-CIIHVFJN-CS0FNVU_.js} +1 -1
  84. package/dist/client/assets/{wardley-L42UT6IY-Btc0FRfF.js → wardley-L42UT6IY-DdYkfF5d.js} +1 -1
  85. package/dist/client/assets/{wardleyDiagram-YWT4CUSO--IcDNmrf.js → wardleyDiagram-YWT4CUSO-B_rFmPCh.js} +1 -1
  86. package/dist/client/assets/{xychartDiagram-2RQKCTM6-CZW93G6X.js → xychartDiagram-2RQKCTM6-CCoaCgmc.js} +1 -1
  87. package/dist/client/index.html +1 -1
  88. package/dist/client/version.json +1 -1
  89. package/dist/droid-cli/package.json +1 -1
  90. package/dist/extension.js +381 -87
  91. package/dist/pi-claude-cli/package.json +1 -1
  92. package/dist/plugin-sdk/index.d.ts +9 -0
  93. package/dist/plugins/fusion-plugin-acp-runtime/package.json +1 -1
  94. package/dist/plugins/fusion-plugin-claude-runtime/package.json +1 -1
  95. package/dist/plugins/fusion-plugin-cli-printing-press/package.json +1 -1
  96. package/dist/plugins/fusion-plugin-compound-engineering/package.json +1 -1
  97. package/dist/plugins/fusion-plugin-cursor-runtime/package.json +1 -1
  98. package/dist/plugins/fusion-plugin-dependency-graph/package.json +1 -1
  99. package/dist/plugins/fusion-plugin-droid-runtime/package.json +1 -1
  100. package/dist/plugins/fusion-plugin-grok-runtime/package.json +1 -1
  101. package/dist/plugins/fusion-plugin-hermes-runtime/package.json +1 -1
  102. package/dist/plugins/fusion-plugin-linear-import/package.json +1 -1
  103. package/dist/plugins/fusion-plugin-omp-runtime/package.json +1 -1
  104. package/dist/plugins/fusion-plugin-openclaw-runtime/package.json +1 -1
  105. package/dist/plugins/fusion-plugin-paperclip-runtime/package.json +1 -1
  106. package/dist/plugins/fusion-plugin-quality/package.json +1 -1
  107. package/dist/plugins/fusion-plugin-reports/package.json +1 -1
  108. package/dist/plugins/fusion-plugin-roadmap/package.json +1 -1
  109. package/dist/plugins/fusion-plugin-todos/package.json +1 -1
  110. package/dist/plugins/fusion-plugin-whatsapp-chat/package.json +1 -1
  111. package/package.json +1 -1
  112. package/dist/client/assets/channel-B4WN1jC_.js +0 -1
  113. package/dist/client/assets/classDiagram-4FO5ZUOK-CKE8RSOv.js +0 -1
  114. package/dist/client/assets/classDiagram-v2-Q7XG4LA2-CKE8RSOv.js +0 -1
package/dist/bin.js CHANGED
@@ -269,10 +269,16 @@ function resolveEntryPointBranchAssignment(input) {
269
269
  };
270
270
  }
271
271
  }
272
- var isValidTaskBranchName;
272
+ var BranchWriteProvenanceError, isValidTaskBranchName;
273
273
  var init_branch_assignment = __esm({
274
274
  "../core/src/branch/branch-assignment.ts"() {
275
275
  "use strict";
276
+ BranchWriteProvenanceError = class extends Error {
277
+ constructor() {
278
+ super("branchWriteOrigin is required when branch is provided");
279
+ this.name = "BranchWriteProvenanceError";
280
+ }
281
+ };
276
282
  isValidTaskBranchName = isValidBranchGroupBranchName;
277
283
  }
278
284
  });
@@ -4650,8 +4656,8 @@ var init_research_settings = __esm({
4650
4656
  function resolveResearchFindingId(finding) {
4651
4657
  const explicitId = typeof finding.id === "string" ? finding.id.trim() : "";
4652
4658
  if (explicitId) return explicitId;
4653
- const normalize11 = (value2) => value2.trim().replace(/\s+/g, " ").toLowerCase();
4654
- const canonical = [normalize11(finding.heading ?? ""), normalize11(finding.content ?? ""), ...[...finding.sources ?? []].map(normalize11).filter(Boolean).sort()].join("\n");
4659
+ const normalize12 = (value2) => value2.trim().replace(/\s+/g, " ").toLowerCase();
4660
+ const canonical = [normalize12(finding.heading ?? ""), normalize12(finding.content ?? ""), ...[...finding.sources ?? []].map(normalize12).filter(Boolean).sort()].join("\n");
4655
4661
  let hash3 = 2166136261;
4656
4662
  for (let index3 = 0; index3 < canonical.length; index3 += 1) {
4657
4663
  hash3 ^= canonical.charCodeAt(index3);
@@ -36105,17 +36111,17 @@ var init_agent_store = __esm({
36105
36111
  * @returns Matching agent when unambiguous; otherwise null
36106
36112
  */
36107
36113
  async resolveAgent(shortname) {
36108
- const normalize11 = (value2) => value2.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
36114
+ const normalize12 = (value2) => value2.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
36109
36115
  const all = await this.listAgents();
36110
36116
  const exact = all.find((agent) => agent.id === shortname);
36111
36117
  if (exact) {
36112
36118
  return exact;
36113
36119
  }
36114
- const normalizedTarget = normalize11(shortname);
36120
+ const normalizedTarget = normalize12(shortname);
36115
36121
  if (!normalizedTarget) {
36116
36122
  return null;
36117
36123
  }
36118
- const matches = all.filter((agent) => normalize11(agent.name) === normalizedTarget);
36124
+ const matches = all.filter((agent) => normalize12(agent.name) === normalizedTarget);
36119
36125
  return matches.length === 1 ? matches[0] : null;
36120
36126
  }
36121
36127
  // ─────────────────────────────────────────────────────────────────────────
@@ -45357,11 +45363,11 @@ function withWindowsNativeBinPath(environment, nativeRoot, platform6 = process.p
45357
45363
  if (platform6 !== "win32") return environment;
45358
45364
  const pathKey = Object.keys(environment).find((key) => key.toLowerCase() === "path") ?? "PATH";
45359
45365
  const bin = nativeRoot.replace(/[\\/]+$/, "") + "\\bin";
45360
- const normalize11 = (value2) => value2.replace(/\//g, "\\").toLowerCase();
45366
+ const normalize12 = (value2) => value2.replace(/\//g, "\\").toLowerCase();
45361
45367
  const inherited = environment[pathKey] ?? "";
45362
45368
  const segments = inherited.split(";").filter(Boolean);
45363
- const normalizedBin = normalize11(bin);
45364
- const deduplicated = segments.filter((segment) => normalize11(segment) !== normalizedBin);
45369
+ const normalizedBin = normalize12(bin);
45370
+ const deduplicated = segments.filter((segment) => normalize12(segment) !== normalizedBin);
45365
45371
  return { ...environment, [pathKey]: [bin, ...deduplicated].join(";") };
45366
45372
  }
45367
45373
  function readPostgresPid(dataDir) {
@@ -67310,14 +67316,14 @@ function resolveGlobalDir(dir2) {
67310
67316
  const isFusionDirInsideRepo = basename8(dir2) === ".fusion" && existsSync15(join19(dirname12(dir2), ".git"));
67311
67317
  if (isFusionDirInsideRepo) {
67312
67318
  const homeGlobalDir = resolveGlobalDirForHome(getHomeDir2());
67313
- const normalize11 = (p2) => {
67319
+ const normalize12 = (p2) => {
67314
67320
  try {
67315
67321
  return realpathSync3.native(p2);
67316
67322
  } catch {
67317
67323
  return resolve14(p2);
67318
67324
  }
67319
67325
  };
67320
- if (normalize11(dir2) !== normalize11(homeGlobalDir)) {
67326
+ if (normalize12(dir2) !== normalize12(homeGlobalDir)) {
67321
67327
  throw new Error(
67322
67328
  `resolveGlobalDir(): refusing project-local '.fusion' directory '${dir2}' for the central/global store. This would create a stray per-project central database seeded with default global settings and silently reset them. Pass the resolved global dir (or omit the argument so it defaults to ~/.fusion); see TaskStore.getGlobalSettingsDir(). If this really is your intended global dir (e.g. a version-controlled dotfiles repo), set FUSION_ALLOW_PROJECT_LOCAL_GLOBAL_DIR=true to override.`
67323
67329
  );
@@ -88044,15 +88050,15 @@ async function appendRecall(layer, input) {
88044
88050
  async function mergeRecallGraphNodeIds(layer, recordId, addGraphNodeIds) {
88045
88051
  const pid = projectId(layer);
88046
88052
  const table = project_exports.memoryRecallRecords;
88047
- const normalize11 = (ids) => [...new Set(ids.map((id) => id.trim()).filter(Boolean))].sort();
88053
+ const normalize12 = (ids) => [...new Set(ids.map((id) => id.trim()).filter(Boolean))].sort();
88048
88054
  return layer.transactionImmediate(async (tx) => {
88049
88055
  await tx.execute(sql`SELECT pg_advisory_xact_lock(hashtext(${recallGraphNodeIdsLockKey(pid, recordId)}))`);
88050
88056
  const rows = await tx.select().from(table).where(and(eq(table.projectId, pid), eq(table.id, recordId))).limit(1);
88051
88057
  await recallGraphNodeIdsTestHooks?.afterRowRead?.();
88052
88058
  const row = rows[0];
88053
88059
  if (!row) return { status: "missing" };
88054
- const stored = normalize11(row.graphNodeIds);
88055
- const next = normalize11([...stored, ...addGraphNodeIds]);
88060
+ const stored = normalize12(row.graphNodeIds);
88061
+ const next = normalize12([...stored, ...addGraphNodeIds]);
88056
88062
  if (stored.length === next.length && stored.every((value2, index3) => value2 === next[index3])) return { status: "unchanged" };
88057
88063
  await tx.update(table).set({ graphNodeIds: next, updatedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(and(eq(table.projectId, pid), eq(table.id, recordId)));
88058
88064
  return { status: "updated" };
@@ -92455,7 +92461,7 @@ import { randomUUID as randomUUID31 } from "node:crypto";
92455
92461
  function normalizeCreateBranchProvenance(input) {
92456
92462
  if (input.branch === void 0) return input;
92457
92463
  if (input.branchWriteOrigin !== "operator" && input.branchWriteOrigin !== "engine") {
92458
- throw new Error("branchWriteOrigin is required when branch is provided");
92464
+ throw new BranchWriteProvenanceError();
92459
92465
  }
92460
92466
  validateTaskBranchName(input.branch);
92461
92467
  if (input.branchWriteOrigin === "operator") {
@@ -94672,7 +94678,7 @@ async function updateTaskRepositoryScopeImpl(store, id, requestedScope) {
94672
94678
  ...replacement,
94673
94679
  repositories: nextRepositories,
94674
94680
  revision: Math.max((current.repositoryScope?.revision ?? 0) + 1, replacement.revision ?? 0),
94675
- ...scopeChanged ? { reviewEvidence: void 0 } : {}
94681
+ ...scopeChanged ? { reviewEvidence: void 0, reviewRemediation: void 0 } : {}
94676
94682
  };
94677
94683
  const [updatedRow] = await tx.update(project_exports.tasks).set({
94678
94684
  repositoryScope: normalized2 ?? null,
@@ -94688,6 +94694,35 @@ async function updateTaskRepositoryScopeImpl(store, id, requestedScope) {
94688
94694
  return outcome;
94689
94695
  }));
94690
94696
  }
94697
+ async function updateWorkspaceReviewStateImpl(store, id, expectedScopeRevision, reviewRemediation) {
94698
+ return store.withTaskLock(id, async () => {
94699
+ const layer = store.asyncLayer;
94700
+ const outcome = await layer.transactionImmediate(async (tx) => {
94701
+ await acquireTaskAdvisoryXactLock(tx, layer.projectId, id);
94702
+ const row = await readTaskRowInTransaction(tx, id, { includeDeleted: true }, layer.projectId);
94703
+ if (!row) throw new TaskNotFoundError(id);
94704
+ if (row.deletedAt) throw new TaskDeletedError(id, row.deletedAt);
94705
+ const current = store.rowToTask(store.pgRowToTaskRow(row));
94706
+ if (current.repositoryScope?.revision !== expectedScopeRevision) return { task: current, updated: false };
94707
+ const repositoryScope = {
94708
+ ...current.repositoryScope,
94709
+ ...reviewRemediation ? { reviewRemediation } : { reviewRemediation: void 0 }
94710
+ };
94711
+ const [updatedRow] = await tx.update(project_exports.tasks).set({
94712
+ repositoryScope,
94713
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
94714
+ }).where(and(eq(project_exports.tasks.id, id), taskProjectScope(layer))).returning();
94715
+ if (!updatedRow) throw new TaskNotFoundError(id);
94716
+ return { task: store.rowToTask(store.pgRowToTaskRow(updatedRow)), updated: true };
94717
+ });
94718
+ if (outcome.updated) {
94719
+ await store.writeTaskJsonFile(store.taskDir(id), outcome.task);
94720
+ if (store.isWatching) store.taskCache.set(id, { ...outcome.task });
94721
+ store.emitTaskLifecycleEventSafely("task:updated", [outcome.task]);
94722
+ }
94723
+ return outcome;
94724
+ });
94725
+ }
94691
94726
  async function resolveTaskWedgeNotificationEpisodeImpl(store, id, episodeId) {
94692
94727
  const layer = store.asyncLayer;
94693
94728
  const row = await resolveActiveTaskWedgeEpisodeRow(layer, id, episodeId, (/* @__PURE__ */ new Date()).toISOString());
@@ -103491,7 +103526,7 @@ async function updateTaskUnlockedImpl(store, id, updates, runContext) {
103491
103526
  if (updates.recommendations !== void 0) assertValidRecommendations(updates.recommendations);
103492
103527
  if (updates.branch !== void 0) {
103493
103528
  if (updates.branchWriteOrigin !== "operator" && updates.branchWriteOrigin !== "engine") {
103494
- throw new Error("branchWriteOrigin is required when branch is provided");
103529
+ throw new BranchWriteProvenanceError();
103495
103530
  }
103496
103531
  if (updates.branch !== null) validateTaskBranchName(updates.branch);
103497
103532
  }
@@ -103580,7 +103615,7 @@ async function updateTaskUnlockedImpl(store, id, updates, runContext) {
103580
103615
  task.repositoryScope = void 0;
103581
103616
  } else if (updates.repositoryScope !== void 0) {
103582
103617
  const scopeRevisionChanged = task.repositoryScope?.revision !== updates.repositoryScope.revision;
103583
- task.repositoryScope = scopeRevisionChanged ? { ...updates.repositoryScope, reviewEvidence: void 0 } : updates.repositoryScope;
103618
+ task.repositoryScope = scopeRevisionChanged ? { ...updates.repositoryScope, reviewEvidence: void 0, reviewRemediation: void 0 } : updates.repositoryScope;
103584
103619
  if (scopeRevisionChanged) {
103585
103620
  task.workflowStepResults = invalidateSupersededRepositoryScopeReviews(
103586
103621
  task.workflowStepResults,
@@ -106080,6 +106115,9 @@ var init_store = __esm({
106080
106115
  async mutateTaskRepositoryScope(id, mutation) {
106081
106116
  return updateTaskRepositoryScopeImpl(this, id, mutation);
106082
106117
  }
106118
+ async updateWorkspaceReviewState(id, expectedScopeRevision, reviewRemediation) {
106119
+ return updateWorkspaceReviewStateImpl(this, id, expectedScopeRevision, reviewRemediation);
106120
+ }
106083
106121
  async resolveTaskWedgeNotificationEpisode(id, episodeId) {
106084
106122
  return resolveTaskWedgeNotificationEpisodeImpl(this, id, episodeId);
106085
106123
  }
@@ -136087,6 +136125,7 @@ __export(src_exports, {
136087
136125
  BUNDLED_PLUGIN_IDS: () => BUNDLED_PLUGIN_IDS,
136088
136126
  BackupManager: () => BackupManager,
136089
136127
  BackwardCompat: () => BackwardCompat,
136128
+ BranchWriteProvenanceError: () => BranchWriteProvenanceError,
136090
136129
  CENTRAL_BACKUP_SCHEMAS: () => CENTRAL_BACKUP_SCHEMAS,
136091
136130
  CLAUDE_SONNET_5_MODEL_ID: () => CLAUDE_SONNET_5_MODEL_ID,
136092
136131
  CLEAN_COMPLETION_MARKERS: () => CLEAN_COMPLETION_MARKERS,
@@ -158737,8 +158776,8 @@ var require_resolve = __commonJS({
158737
158776
  }
158738
158777
  return count2;
158739
158778
  }
158740
- function getFullPath(resolver, id = "", normalize11) {
158741
- if (normalize11 !== false)
158779
+ function getFullPath(resolver, id = "", normalize12) {
158780
+ if (normalize12 !== false)
158742
158781
  id = normalizeId2(id);
158743
158782
  const p2 = resolver.parse(id);
158744
158783
  return _getFullPath(resolver, p2);
@@ -160078,7 +160117,7 @@ var require_fast_uri = __commonJS({
160078
160117
  "use strict";
160079
160118
  var { normalizeIPv6, removeDotSegments, recomposeAuthority, normalizeComponentEncoding, isIPv4, nonSimpleDomain } = require_utils();
160080
160119
  var { SCHEMES, getSchemeHandler } = require_schemes();
160081
- function normalize11(uri, options) {
160120
+ function normalize12(uri, options) {
160082
160121
  if (typeof uri === "string") {
160083
160122
  uri = /** @type {T} */
160084
160123
  serialize(parse4(uri, options), options);
@@ -160314,7 +160353,7 @@ var require_fast_uri = __commonJS({
160314
160353
  }
160315
160354
  var fastUri = {
160316
160355
  SCHEMES,
160317
- normalize: normalize11,
160356
+ normalize: normalize12,
160318
160357
  resolve: resolve114,
160319
160358
  resolveComponent,
160320
160359
  equal,
@@ -175735,6 +175774,39 @@ var init_worktrunk_failure_handler = __esm({
175735
175774
  }
175736
175775
  });
175737
175776
 
175777
+ // ../engine/src/executor/workspace-review-remediation.ts
175778
+ function normalize8(value2) {
175779
+ return (value2 ?? "").replace(/\s+/g, " ").trim().toLowerCase();
175780
+ }
175781
+ function resolveWorkspaceReviewRemediationRepository(task, declaredRepositories) {
175782
+ const scope = task.repositoryScope;
175783
+ const remediation2 = scope?.reviewRemediation;
175784
+ if (!remediation2) return void 0;
175785
+ if (scope?.state !== "confirmed" || scope.revision !== remediation2.scopeRevision) {
175786
+ throw new Error(`Workspace Code Review remediation target is stale for ${task.id}`);
175787
+ }
175788
+ if (!declaredRepositories.includes(remediation2.repository)) {
175789
+ throw new Error(`Workspace Code Review remediation repository ${remediation2.repository} is not declared for ${task.id}`);
175790
+ }
175791
+ return remediation2.repository;
175792
+ }
175793
+ function deriveWorkspaceReviewRemediation(result2) {
175794
+ if (typeof result2.repositoryScopeRevision !== "number") return void 0;
175795
+ const blocking = (result2.repositoryReviewOutcomes ?? []).filter((outcome) => outcome.status === "REVIEWED" && (outcome.verdict === "REVISE" || outcome.verdict === "RETHINK")).sort((left, right) => left.repository.localeCompare(right.repository))[0];
175796
+ if (!blocking) return void 0;
175797
+ const findings = (blocking.findings ?? []).map((finding) => `${finding.id}:${normalize8(finding.title)}:${normalize8(finding.body)}`).sort().join("|");
175798
+ return {
175799
+ scopeRevision: result2.repositoryScopeRevision,
175800
+ repository: blocking.repository,
175801
+ inputSignature: `${result2.workflowStepId}\0${blocking.repository}\0${blocking.fingerprint ?? ""}\0${blocking.verdict}\0${findings}`
175802
+ };
175803
+ }
175804
+ var init_workspace_review_remediation = __esm({
175805
+ "../engine/src/executor/workspace-review-remediation.ts"() {
175806
+ "use strict";
175807
+ }
175808
+ });
175809
+
175738
175810
  // ../engine/src/worktree/worktree-copy-files.ts
175739
175811
  import { constants as fsConstants3 } from "node:fs";
175740
175812
  import { access as access7, copyFile as copyFile2, lstat as lstat2, mkdir as mkdir26 } from "node:fs/promises";
@@ -176121,14 +176193,15 @@ function invalidateWorkspaceConfigCache(owner) {
176121
176193
  workspaceConfigEpochs.set(owner, (workspaceConfigEpochs.get(owner) ?? 0) + 1);
176122
176194
  }
176123
176195
  async function normalizeWorkspaceTaskRouting(store, taskId) {
176124
- const normalize11 = store.normalizeWorkspaceTaskWorktreeMetadata;
176125
- if (typeof normalize11 === "function") return normalize11.call(store, taskId);
176196
+ const normalize12 = store.normalizeWorkspaceTaskWorktreeMetadata;
176197
+ if (typeof normalize12 === "function") return normalize12.call(store, taskId);
176126
176198
  let task = await store.getTask(taskId);
176127
176199
  if (task.worktree || task.branch || task.executionStartBranch || task.baseCommitSha) {
176128
176200
  if (typeof store.updateTask !== "function") return task;
176129
176201
  await store.updateTask(taskId, {
176130
176202
  worktree: null,
176131
176203
  branch: null,
176204
+ branchWriteOrigin: "engine",
176132
176205
  executionStartBranch: null,
176133
176206
  baseCommitSha: null,
176134
176207
  sessionFile: null
@@ -176330,11 +176403,15 @@ async function ensureWorkspaceGroupOwnership(workspaceContext, settings) {
176330
176403
  async function acquireTaskWorktree(opts) {
176331
176404
  const { task, rootDir, store, settings, pool, logger: logger3, audit, runContext, createWorktree: createWorktree2, runConfiguredCommand: runConfiguredCommand2, runInitCommand, taskEnv, secretsStore, workspaceContext } = opts;
176332
176405
  const persistWorktreeAssignment = async (patch) => {
176406
+ const provenancePatch = patch.branch === void 0 ? patch : {
176407
+ ...patch,
176408
+ branchWriteOrigin: patch.branchWriteOrigin ?? (classifyTaskBranchOrigin(task, patch.branch ?? void 0) === "operator-supplied" ? "operator" : "engine")
176409
+ };
176333
176410
  if (!opts.suppressSingularWorktreePersist) {
176334
- await store.updateTask(task.id, patch);
176411
+ await store.updateTask(task.id, provenancePatch);
176335
176412
  return;
176336
176413
  }
176337
- const { worktree: _worktree, branch: _branch, ...nonSingularPatch } = patch;
176414
+ const { worktree: _worktree, branch: _branch, branchWriteOrigin: _origin, ...nonSingularPatch } = provenancePatch;
176338
176415
  if (Object.keys(nonSingularPatch).length > 0) {
176339
176416
  await store.updateTask(task.id, nonSingularPatch);
176340
176417
  }
@@ -176571,7 +176648,22 @@ async function acquireTaskWorktree(opts) {
176571
176648
  }
176572
176649
  worktreePath = created2.path;
176573
176650
  branch = created2.branch;
176574
- await persistWorktreeAssignment({ worktree: created2.path, branch: created2.branch });
176651
+ try {
176652
+ await persistWorktreeAssignment({ worktree: created2.path, branch: created2.branch });
176653
+ } catch (error3) {
176654
+ try {
176655
+ await removeWorktree({
176656
+ worktreePath: created2.path,
176657
+ rootDir,
176658
+ settings,
176659
+ taskId: task.id,
176660
+ reason: RemovalReason.ExecutorDispose
176661
+ });
176662
+ } catch (cleanupError) {
176663
+ logger3?.warn(`${task.id}: failed to compensate unpersisted worktree ${created2.path}: ${formatError(cleanupError)}`);
176664
+ }
176665
+ throw error3;
176666
+ }
176575
176667
  await audit?.git({ type: "worktree:create", target: created2.path, metadata: { branch: created2.branch, source: logOrigin === "return-guard" ? "acquire-return-guard" : void 0 } });
176576
176668
  await audit?.git({ type: "branch:create", target: created2.branch });
176577
176669
  if (created2.branch !== branchName) {
@@ -176813,6 +176905,17 @@ async function acquireTaskWorktree(opts) {
176813
176905
  if (pinned) {
176814
176906
  return acquirePinnedWorktree();
176815
176907
  }
176908
+ if (!workspaceContext && !task.worktree && !(pool && settings.recycleWorktrees)) {
176909
+ const registeredMatches = (await getRegisteredWorktreeBranches(rootDir)).filter((entry) => entry.branch === branchName);
176910
+ if (registeredMatches.length === 1) {
176911
+ const registeredPath = registeredMatches[0].worktreePath;
176912
+ const classification = await classifyTaskWorktree(rootDir, registeredPath);
176913
+ if (classification.ok) {
176914
+ await persistWorktreeAssignment({ worktree: registeredPath, branch: branchName });
176915
+ return reuseWarmWorktree(registeredPath, branchName, "existing");
176916
+ }
176917
+ }
176918
+ }
176816
176919
  if (task.worktree && isResume) {
176817
176920
  if (logger3?.debug) logger3.debug(`Reusing existing worktree: ${worktreePath}`);
176818
176921
  const cleanup = await removeDesktopBuildArtifacts(worktreePath, logger3);
@@ -176848,6 +176951,7 @@ async function acquireTaskWorktree(opts) {
176848
176951
  }
176849
176952
  }
176850
176953
  if (pooled) {
176954
+ let poolAssignmentPersistenceStarted = false;
176851
176955
  try {
176852
176956
  const preparedRaw = await pool.prepareForTask(pooled, branchName, freshStartPoint, {
176853
176957
  allowSiblingBranchRename,
@@ -176905,7 +177009,9 @@ async function acquireTaskWorktree(opts) {
176905
177009
  });
176906
177010
  acquiredFromPool = true;
176907
177011
  logger3?.log(`Acquired worktree from pool: ${worktreePath}`);
177012
+ poolAssignmentPersistenceStarted = true;
176908
177013
  await persistWorktreeAssignment({ worktree: worktreePath, branch });
177014
+ poolAssignmentPersistenceStarted = false;
176909
177015
  await audit?.git({ type: "worktree:reuse", target: worktreePath, metadata: { branch, reclaimed: prepared.reclaimed } });
176910
177016
  if (prepared.reclaimed) {
176911
177017
  await store.logEntry(task.id, `Acquired reclaimed worktree from pool: ${worktreePath} (${prepared.strandedCommitCount ?? 0} commits preserved)`, void 0, runContext);
@@ -176959,6 +177065,10 @@ async function acquireTaskWorktree(opts) {
176959
177065
  });
176960
177066
  }
176961
177067
  } catch (poolErr) {
177068
+ if (poolAssignmentPersistenceStarted) {
177069
+ pool.release(pooled, task.id);
177070
+ throw poolErr;
177071
+ }
176962
177072
  if (poolErr instanceof WorktreeBaseRefreshError) {
176963
177073
  await persistWorktreeAssignment({ worktree: null, branch: null, branchWriteOrigin: "engine", sessionFile: null });
176964
177074
  pool.release(pooled, task.id);
@@ -177270,7 +177380,11 @@ async function acquireWorkspaceTaskWorktrees(opts) {
177270
177380
  current = await opts.store.getTask(opts.task.id);
177271
177381
  }
177272
177382
  const entries2 = current.workspaceWorktrees ?? {};
177273
- const coordinatorWorktreePath = repoRelPaths.map((repoRelPath) => entries2[repoRelPath]?.worktreePath).find((path17) => typeof path17 === "string" && path17.length > 0);
177383
+ const remediationRepository = resolveWorkspaceReviewRemediationRepository(current, repoRelPaths);
177384
+ if (opts.remediationRepository !== remediationRepository) {
177385
+ throw new Error(`Workspace Code Review remediation target changed during acquisition for ${opts.task.id}`);
177386
+ }
177387
+ const coordinatorWorktreePath = remediationRepository ? entries2[remediationRepository]?.worktreePath : repoRelPaths.map((repoRelPath) => entries2[repoRelPath]?.worktreePath).find((path17) => typeof path17 === "string" && path17.length > 0);
177274
177388
  if (!coordinatorWorktreePath) {
177275
177389
  throw new Error(`Workspace task ${opts.task.id} did not acquire a declared repository worktree`);
177276
177390
  }
@@ -177291,6 +177405,7 @@ var init_worktree_acquisition = __esm({
177291
177405
  init_worktree_backend();
177292
177406
  init_worktrunk_installer();
177293
177407
  init_worktrunk_failure_handler();
177408
+ init_workspace_review_remediation();
177294
177409
  init_secrets_env_writer();
177295
177410
  init_worktree_desktop_artifacts();
177296
177411
  init_worktree_hooks();
@@ -192124,7 +192239,7 @@ var init_agent_memory_index = __esm({
192124
192239
  // ../engine/src/agents/agent-instructions.ts
192125
192240
  import { readFile as readFile42, writeFile as writeFile28, mkdir as mkdir29, access as access9 } from "node:fs/promises";
192126
192241
  import { constants as fsConstants4 } from "node:fs";
192127
- import { isAbsolute as isAbsolute16, resolve as resolve41, relative as relative15, normalize as normalize8, sep as sep12, dirname as dirname32 } from "node:path";
192242
+ import { isAbsolute as isAbsolute16, resolve as resolve41, relative as relative15, normalize as normalize9, sep as sep12, dirname as dirname32 } from "node:path";
192128
192243
  function trimAndClamp(value2, maxLength, label, agentId) {
192129
192244
  const trimmed = value2.trim();
192130
192245
  if (!trimmed) {
@@ -192158,7 +192273,7 @@ function resolveValidatedMarkdownPath(rawPath, rootDir, agentId, fieldLabel) {
192158
192273
  log26.warn(`${fieldLabel} must be project-relative for agent ${agentId}: ${trimmed}`);
192159
192274
  return null;
192160
192275
  }
192161
- const normalized2 = normalize8(trimmed);
192276
+ const normalized2 = normalize9(trimmed);
192162
192277
  if (isPathTraversal2(normalized2)) {
192163
192278
  log26.warn(`${fieldLabel} traversal is not allowed for agent ${agentId}: ${trimmed}`);
192164
192279
  return null;
@@ -200921,8 +201036,8 @@ async function autoRecoverWorktreeSessionStartFailure(store, task, opts) {
200921
201036
  }
200922
201037
  const workspaceTask = configuredWorkspace || isWorkspaceTask(task);
200923
201038
  if (workspaceTask) {
200924
- const normalize11 = store.normalizeWorkspaceTaskWorktreeMetadata;
200925
- if (typeof normalize11 === "function") task = await normalize11.call(store, task.id);
201039
+ const normalize12 = store.normalizeWorkspaceTaskWorktreeMetadata;
201040
+ if (typeof normalize12 === "function") task = await normalize12.call(store, task.id);
200926
201041
  }
200927
201042
  const resetRetryBudget = opts.resetRetryBudgetOnStaleMetadataClear === true;
200928
201043
  const staleMetadataClearRecoveryRetryCount = opts.staleMetadataClearRecoveryRetryCount;
@@ -200977,7 +201092,13 @@ async function autoRecoverWorktreeSessionStartFailure(store, task, opts) {
200977
201092
  worktreeSessionRetryCount: nextCount,
200978
201093
  ...nextStaleMetadataClearRecoveryCount === void 0 ? {} : { recoveryRetryCount: nextStaleMetadataClearRecoveryCount },
200979
201094
  worktree: clearWorktreeMetadata ? null : staleWorktree,
200980
- branch: nextBranch,
201095
+ /*
201096
+ * FNXC:BranchNaming 2026-08-21-09:36:
201097
+ * Session recovery often changes only worktree/session metadata. Do not reassert an unchanged
201098
+ * branch with engine provenance: that would erase a matching operator override and later make
201099
+ * its branch eligible for engine cleanup. A real canonical clear remains an explicit engine write.
201100
+ */
201101
+ ...(task.branch ?? null) === nextBranch ? {} : { branch: nextBranch, branchWriteOrigin: "engine" },
200981
201102
  sessionFile: null
200982
201103
  });
200983
201104
  await opts.auditor?.database({
@@ -210706,7 +210827,8 @@ async function tryEarlyEmptyOwnDiffFinalize(input) {
210706
210827
  try {
210707
210828
  await store.updateTask(taskId, {
210708
210829
  worktree: worktreeRemoved ? null : task.worktree,
210709
- branch: branchDeleted ? null : task.branch
210830
+ branch: branchDeleted ? null : task.branch,
210831
+ ...branchDeleted ? { branchWriteOrigin: "engine" } : {}
210710
210832
  });
210711
210833
  if (worktreeRemoved) task.worktree = void 0;
210712
210834
  if (branchDeleted) task.branch = void 0;
@@ -211066,7 +211188,7 @@ async function aiMergeTask(store, rootDir, taskId, options = {}) {
211066
211188
  rootDir,
211067
211189
  integrationBranch: mergeTarget.branch
211068
211190
  });
211069
- await store.updateTask(taskId, { worktree: reusableMatch.path, branch: reusableMatch.branch });
211191
+ await store.updateTask(taskId, { worktree: reusableMatch.path, branch: reusableMatch.branch, branchWriteOrigin: "engine" });
211070
211192
  await emitReuseHandoffAuditEvent(
211071
211193
  "merge:reuse-fallback-reused-existing-registration",
211072
211194
  {
@@ -218626,7 +218748,7 @@ var init_self_healing = __esm({
218626
218748
  } catch {
218627
218749
  }
218628
218750
  }
218629
- const patch = preservedWorktree ? { branch: selected.branch } : { branch: selected.branch, worktree: null };
218751
+ const patch = preservedWorktree ? { branch: selected.branch, branchWriteOrigin: "engine" } : { branch: selected.branch, branchWriteOrigin: "engine", worktree: null };
218630
218752
  if (!task.baseCommitSha) {
218631
218753
  const derivedBaseCommit = (await execAsync18(
218632
218754
  `git merge-base ${shellQuote2(integrationBase)} ${shellQuote2(selected.branch)}`,
@@ -222303,6 +222425,12 @@ var init_self_healing = __esm({
222303
222425
  await this.emitWorkspacePartialLandNoAction(task, "merge-pending", liveness.livePaths);
222304
222426
  continue;
222305
222427
  }
222428
+ const latestTask = await this.store.getTask(task.id).catch(() => null);
222429
+ const blockerTask = latestTask?.status === "failed" && latestTask.error?.startsWith("Workspace partial land:") ? { ...latestTask, status: null, error: void 0 } : latestTask;
222430
+ if (!blockerTask || getTaskMergeBlocker(blockerTask, { skipColumnIdentityCheck: true }) !== void 0) {
222431
+ await this.emitWorkspacePartialLandNoAction(task, "merge-blocked", []);
222432
+ continue;
222433
+ }
222306
222434
  const workspaceWorktrees = task.workspaceWorktrees ?? {};
222307
222435
  const explicitScope = task.repositoryScope?.state === "confirmed" ? task.repositoryScope.repositories : void 0;
222308
222436
  if (!explicitScope) {
@@ -222310,8 +222438,12 @@ var init_self_healing = __esm({
222310
222438
  continue;
222311
222439
  }
222312
222440
  const repoKeys = Object.keys(workspaceWorktrees).filter(
222313
- (repoRel) => explicitScope.includes(repoRel) && (task.modifiedFiles ?? []).some((file) => file.startsWith(`${repoRel}/`))
222441
+ (repoRel) => explicitScope.includes(repoRel) && ((task.modifiedFiles ?? []).some((file) => file.startsWith(`${repoRel}/`)) || Boolean(workspaceWorktrees[repoRel]?.landedSha))
222314
222442
  );
222443
+ if (repoKeys.length === 0) {
222444
+ await this.emitWorkspacePartialLandNoAction(task, "empty-obligations", []);
222445
+ continue;
222446
+ }
222315
222447
  const landedRepos = [];
222316
222448
  const unlandedRepos = [];
222317
222449
  const unrecoverableRepos = [];
@@ -222368,13 +222500,12 @@ var init_self_healing = __esm({
222368
222500
  phase: "reconcile-workspace-partial-land"
222369
222501
  });
222370
222502
  if (evidenceUnavailableRepos.length > 0) {
222371
- const defers = (this.workspacePartialLandEvidenceDefers.get(task.id) ?? 0) + 1;
222372
- this.workspacePartialLandEvidenceDefers.set(task.id, defers);
222503
+ const defers = (task.mergeTransientRetryCount ?? 0) + 1;
222504
+ await this.store.updateTask(task.id, { mergeTransientRetryCount: defers });
222373
222505
  if (defers >= MAX_STARVATION_DROPS) {
222374
222506
  const error3 = `Workspace partial-land evidence unavailable: branch state could not be read after ${MAX_STARVATION_DROPS} sweeps for sub-repo(s) ${evidenceUnavailableRepos.join(", ")} \u2014 manual intervention required.`;
222375
222507
  await this.store.updateTask(task.id, { status: "failed", error: error3 });
222376
222508
  await this.store.logEntry(task.id, error3);
222377
- this.workspacePartialLandEvidenceDefers.delete(task.id);
222378
222509
  await auditor.database({
222379
222510
  type: "task:reconcile-workspace-partial-land",
222380
222511
  target: task.id,
@@ -222387,7 +222518,6 @@ var init_self_healing = __esm({
222387
222518
  }
222388
222519
  continue;
222389
222520
  }
222390
- this.workspacePartialLandEvidenceDefers.delete(task.id);
222391
222521
  if (unrecoverableRepos.length > 0) {
222392
222522
  const missingBranches = unrecoverableRepos.map((repoRel) => workspaceWorktrees[repoRel]?.branch ?? resolveTaskWorkingBranch(task)).join(", ");
222393
222523
  const error3 = `Workspace partial-land unrecoverable: sub-repo(s) ${unrecoverableRepos.join(", ")} have no branch (${missingBranches}) and no landedSha \u2014 manual intervention required.`;
@@ -222415,7 +222545,7 @@ var init_self_healing = __esm({
222415
222545
  landedRepos,
222416
222546
  unlandedRepos: [],
222417
222547
  reason: "all-landed-not-finalized",
222418
- successLog: "Auto-recovered (workspace): all sub-repos landed but task not finalized \u2014 re-enqueued finalize-once"
222548
+ successLog: "Workspace merge recovery scheduled: all sub-repositories have proven landing evidence; awaiting finalize-once result"
222419
222549
  });
222420
222550
  recovered++;
222421
222551
  continue;
@@ -222424,7 +222554,7 @@ var init_self_healing = __esm({
222424
222554
  landedRepos,
222425
222555
  unlandedRepos,
222426
222556
  reason: landedRepos.length > 0 ? "partial-land" : "zero-land",
222427
- successLog: `Auto-recovered (workspace): re-enqueued partial land (${landedRepos.length} landed, ${unlandedRepos.length} pending)`
222557
+ successLog: `Workspace merge recovery scheduled: ${landedRepos.length} landed, ${unlandedRepos.length} pending`
222428
222558
  });
222429
222559
  recovered++;
222430
222560
  } catch (err) {
@@ -222489,7 +222619,8 @@ var init_self_healing = __esm({
222489
222619
  }).catch(() => void 0);
222490
222620
  return false;
222491
222621
  }
222492
- const drops = (this.workspacePartialLandDrops.get(task.id) ?? 0) + 1;
222622
+ const drops = (task.mergeTransientRetryCount ?? 0) + 1;
222623
+ await this.store.updateTask(task.id, { mergeTransientRetryCount: drops });
222493
222624
  this.workspacePartialLandDrops.set(task.id, drops);
222494
222625
  log34.warn(`reconcileWorkspacePartialLands: enqueue dropped for ${task.id} (${drops}/${MAX_STARVATION_DROPS}); merge queue rejected re-enqueue`);
222495
222626
  if (drops >= MAX_STARVATION_DROPS) {
@@ -232085,7 +232216,7 @@ function recoverPassedPlanReviewFromLatestLog(task) {
232085
232216
  completedAt: latest.timestamp
232086
232217
  };
232087
232218
  }
232088
- 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_REVIEW_KIND_CONTEXT_KEY, WORKFLOW_NODE_ENGINE_PAUSE_ABORT_KIND, WORKFLOW_DRIFT_PARK_CONTEXT_KEY, WorkflowGraphSuspended, MERGE_REGION_KINDS, WorkflowGraphExecutor;
232219
+ var PLAN_REVIEW_PROVIDER_FAILURE_HOLD_VALUE, BRANCH_WRITE_PROVENANCE_FAILURE_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_REVIEW_KIND_CONTEXT_KEY, WORKFLOW_NODE_ENGINE_PAUSE_ABORT_KIND, WORKFLOW_DRIFT_PARK_CONTEXT_KEY, WorkflowGraphSuspended, MERGE_REGION_KINDS, WorkflowGraphExecutor;
232089
232220
  var init_workflow_graph_executor = __esm({
232090
232221
  "../engine/src/workflows/workflow-graph-executor.ts"() {
232091
232222
  "use strict";
@@ -232098,8 +232229,10 @@ var init_workflow_graph_executor = __esm({
232098
232229
  init_workflow_graph_foreach();
232099
232230
  init_workflow_graph_loop();
232100
232231
  init_workflow_node_execution_needs();
232232
+ init_src2();
232101
232233
  init_worktree_acquisition();
232102
232234
  PLAN_REVIEW_PROVIDER_FAILURE_HOLD_VALUE = "plan-review-provider-failure-hold";
232235
+ BRANCH_WRITE_PROVENANCE_FAILURE_VALUE = "branch-write-provenance-failure";
232103
232236
  PLAN_REVIEW_LEASE_HELD_VALUE = "plan-review-lease-held";
232104
232237
  SESSION_CONTENTION_HOLD_VALUE = "session-contention-hold";
232105
232238
  WORKFLOW_INTERRUPTED_NODE_ID_CONTEXT_KEY = "workflow:interruptedNodeId";
@@ -233016,7 +233149,7 @@ var init_workflow_graph_executor = __esm({
233016
233149
  const failureResult = {
233017
233150
  outcome: "failure",
233018
233151
  // FNXC:SessionContention 2026-07-25-21:30: contention is a retryable hold, not an exception.
233019
- value: isSessionContentionError(lastErrorText) ? SESSION_CONTENTION_HOLD_VALUE : "exception",
233152
+ value: lastError instanceof BranchWriteProvenanceError ? BRANCH_WRITE_PROVENANCE_FAILURE_VALUE : isSessionContentionError(lastErrorText) ? SESSION_CONTENTION_HOLD_VALUE : "exception",
233020
233153
  contextPatch: {
233021
233154
  [`node:${node.id}:error`]: lastErrorText
233022
233155
  }
@@ -237104,6 +237237,7 @@ async function handleBranchConflict(deps, task, error3) {
237104
237237
  status: "failed",
237105
237238
  error: conflictMessage,
237106
237239
  branch: error3.branchName,
237240
+ branchWriteOrigin: "engine",
237107
237241
  worktree: error3.conflictingWorktreePath,
237108
237242
  paused: true,
237109
237243
  pausedReason: "branch-conflict-unrecoverable"
@@ -237159,8 +237293,8 @@ async function recoverMissingWorktreeSessionStartFailure(deps, task, worktreePat
237159
237293
  }
237160
237294
  let recoveryTask = task;
237161
237295
  if (workspaceTask) {
237162
- const normalize11 = deps.store.normalizeWorkspaceTaskWorktreeMetadata;
237163
- if (typeof normalize11 === "function") recoveryTask = await normalize11.call(deps.store, task.id);
237296
+ const normalize12 = deps.store.normalizeWorkspaceTaskWorktreeMetadata;
237297
+ if (typeof normalize12 === "function") recoveryTask = await normalize12.call(deps.store, task.id);
237164
237298
  }
237165
237299
  if (!workspaceTask && isInsideWorktreesDir(deps.rootDir, staleWorktreePath)) {
237166
237300
  try {
@@ -237578,8 +237712,21 @@ async function detectWorkspaceMainCheckoutWork(deps, task, repos, declaredScope)
237578
237712
  const { stdout } = await execAsync41("git log -n 200 --format=%H%x1f%ct%x1f%B%x1e HEAD", { ...probeOptions, cwd: checkout });
237579
237713
  const attributed = new RegExp(`(?:${task.id.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}|Fusion-Task-Id:\\s*${task.id.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")})`, "i");
237580
237714
  for (const commit of parseCommits(stdout)) {
237581
- const evidence = attributed.test(commit.body) ? "task-attributed-commit" : anchor !== null && commit.committedAt >= anchor ? "post-anchor-commit" : null;
237582
- if (evidence) violations.push({ repo, files: [], commits: [commit.sha], evidence });
237715
+ const entry = workspaceWorktrees[repo];
237716
+ const recordedLanding = entry?.landedSha === commit.sha || task.mergeDetails?.workspaceLandedShas?.[repo] === commit.sha || task.mergeDetails?.commitSha === commit.sha;
237717
+ let reachableFromBaseline = false;
237718
+ if (entry?.baseCommitSha) {
237719
+ try {
237720
+ await execAsync41(`git merge-base --is-ancestor ${commit.sha} ${entry.baseCommitSha}`, { ...probeOptions, cwd: checkout });
237721
+ reachableFromBaseline = true;
237722
+ } catch {
237723
+ }
237724
+ }
237725
+ if (attributed.test(commit.body) && !recordedLanding && !reachableFromBaseline && anchor !== null && commit.committedAt >= anchor) {
237726
+ violations.push({ repo, files: [], commits: [commit.sha], evidence: "task-attributed-commit" });
237727
+ } else if (!recordedLanding && !reachableFromBaseline && anchor !== null && commit.committedAt >= anchor) {
237728
+ warnings.push({ repo, files: [], commits: [commit.sha], reason: "pre-existing-dirt" });
237729
+ }
237583
237730
  }
237584
237731
  } catch {
237585
237732
  warnings.push({ repo, files: [], commits: [], reason: "commit-scan-unavailable" });
@@ -237659,7 +237806,7 @@ async function verifyWorktreeInvariants(deps, task, worktreePathOverride, allowR
237659
237806
  reason: "main_checkout_edit",
237660
237807
  repo: firstMainCheckoutViolation.repo,
237661
237808
  observed: `${firstMainCheckoutViolation.evidence}: ${observed}`,
237662
- expected: `move task work into the acquired fusion/${task.id} worktree for ${firstMainCheckoutViolation.repo} (acquire it first if needed), restore its main checkout, then retry fn_task_done; only 3 requeue attempts are available`
237809
+ expected: `move task work into the acquired fusion/${task.id} worktree for ${firstMainCheckoutViolation.repo} (acquire it first if needed), restore its main checkout, then ask an operator to retry fn_task_done`
237663
237810
  };
237664
237811
  }
237665
237812
  const zeroAcquire = classifyWorkspaceZeroAcquire(task, {
@@ -239142,7 +239289,7 @@ var init_workflow_step_failure_injection = __esm({
239142
239289
  });
239143
239290
 
239144
239291
  // ../engine/src/executor/send-task-back-for-fix.ts
239145
- async function sendTaskBackForFix(deps, task, worktreePath, failureFeedback, stepName, reason, preserveResumeState = true, mergeVerificationFailure = false, retryPresentation, findings) {
239292
+ async function sendTaskBackForFix(deps, task, worktreePath, failureFeedback, stepName, reason, preserveResumeState = true, mergeVerificationFailure = false, retryPresentation, findings, persistWorktreePath) {
239146
239293
  const taskId = task.id;
239147
239294
  deps.clearCompletedTaskWatchdog(taskId);
239148
239295
  const { task: authoritativeRemediationTask, route: externalExecutionRoute } = await resolveAuthoritativeExternalExecutionRoute(deps.store, task);
@@ -239185,7 +239332,7 @@ Please fix the issues so the verification can pass on the next attempt.`,
239185
239332
  remediationWorktreePath,
239186
239333
  `${taskId}: sent back to in-progress for remediation`,
239187
239334
  preserveResumeState,
239188
- !externalExecutionRoute.configured
239335
+ persistWorktreePath ?? !externalExecutionRoute.configured
239189
239336
  );
239190
239337
  }
239191
239338
  var init_send_task_back_for_fix = __esm({
@@ -244281,6 +244428,7 @@ async function ensureGraphCustomNodeWorktree(deps, task, settings, nodeId, refre
244281
244428
  deps.getRunContextFor(task.id)
244282
244429
  );
244283
244430
  if (workspaceConfig) {
244431
+ const remediationRepository = resolveWorkspaceReviewRemediationRepository(task, workspaceConfig.repos);
244284
244432
  const workspace = await acquireWorkspaceTaskWorktrees({
244285
244433
  workspaceConfig,
244286
244434
  workspaceRootDir: deps.rootDir,
@@ -244305,7 +244453,8 @@ async function ensureGraphCustomNodeWorktree(deps, task, settings, nodeId, refre
244305
244453
  return result2;
244306
244454
  }),
244307
244455
  taskEnv: process.env,
244308
- addActiveWorktree: deps.addActiveWorktree
244456
+ addActiveWorktree: deps.addActiveWorktree,
244457
+ remediationRepository
244309
244458
  });
244310
244459
  deps.onStart?.(workspace.task, workspace.coordinatorWorktreePath);
244311
244460
  executorLog.debug(`${task.id}: workflow node '${nodeId}' using workspace coordinator ${workspace.coordinatorWorktreePath}`);
@@ -244369,6 +244518,7 @@ var init_ensure_graph_custom_node_worktree = __esm({
244369
244518
  init_worktree_git_refs();
244370
244519
  init_task_predicates();
244371
244520
  init_workspace_config_resolver();
244521
+ init_workspace_review_remediation();
244372
244522
  }
244373
244523
  });
244374
244524
 
@@ -244957,7 +245107,22 @@ ${info.feedback}`,
244957
245107
  return false;
244958
245108
  }
244959
245109
  const revisionKey = optionalStepRevisionKey2(info.nodeId, info.stepName);
244960
- if (hasRepeatedUnchangedCodeReview(liveTask, info)) {
245110
+ const reviewResult = (liveTask.workflowStepResults ?? []).find(
245111
+ (result2) => (result2.workflowStepId === info.nodeId || result2.workflowStepName === info.stepName) && result2.verdict === "REVISE"
245112
+ );
245113
+ const derivedRemediation = reviewResult ? deriveWorkspaceReviewRemediation(reviewResult) : void 0;
245114
+ const remediation2 = derivedRemediation && liveTask.workspaceWorktrees ? derivedRemediation : void 0;
245115
+ const priorRemediation = liveTask.repositoryScope?.reviewRemediation;
245116
+ const hasDurableRepeatedWorkspaceReview = remediation2 !== void 0 && priorRemediation?.scopeRevision === remediation2.scopeRevision && priorRemediation.repository === remediation2.repository && priorRemediation.inputSignature === remediation2.inputSignature;
245117
+ const updateWorkspaceReviewState = deps.store.updateWorkspaceReviewState;
245118
+ if (remediation2 && !hasDurableRepeatedWorkspaceReview && updateWorkspaceReviewState) {
245119
+ const persisted = await updateWorkspaceReviewState.call(deps.store, taskId, remediation2.scopeRevision, remediation2);
245120
+ if (!persisted.updated) {
245121
+ executorLog.warn(`${taskId}: workspace Code Review remediation was superseded by a repository scope change.`);
245122
+ return false;
245123
+ }
245124
+ }
245125
+ if (hasDurableRepeatedWorkspaceReview || (!remediation2 || !updateWorkspaceReviewState) && hasRepeatedUnchangedCodeReview(liveTask, info)) {
244961
245126
  const runContext = deps.getRunContextFor(taskId);
244962
245127
  await deps.store.logEntry(
244963
245128
  taskId,
@@ -245005,9 +245170,18 @@ Feedback:
245005
245170
  ${info.feedback}`, revisionKey),
245006
245171
  deps.getRunContextFor(taskId)
245007
245172
  );
245008
- await deps.sendTaskBackForFix(
245173
+ const remediationWorktreePath = remediation2 ? liveTask.workspaceWorktrees?.[remediation2.repository]?.worktreePath : liveTask.worktree;
245174
+ if (remediation2 && !remediationWorktreePath) {
245175
+ await deps.store.updateTask(taskId, {
245176
+ status: "awaiting-approval",
245177
+ awaitingApprovalReason: "code-review-non-convergence",
245178
+ error: `Workspace Code Review remediation target ${remediation2.repository} has no acquired worktree.`
245179
+ });
245180
+ return false;
245181
+ }
245182
+ const sendArgs = [
245009
245183
  liveTask,
245010
- liveTask.worktree ?? "",
245184
+ remediationWorktreePath ?? "",
245011
245185
  info.feedback,
245012
245186
  info.stepName,
245013
245187
  `Pre-merge optional workflow step "${info.stepName}" requested revision`,
@@ -245015,7 +245189,12 @@ ${info.feedback}`, revisionKey),
245015
245189
  false,
245016
245190
  { attempt: nextCount, max: budget.unbounded ? void 0 : budget.max },
245017
245191
  info.findings
245018
- );
245192
+ ];
245193
+ if (remediation2) {
245194
+ await deps.sendTaskBackForFix(...sendArgs, false);
245195
+ } else {
245196
+ await deps.sendTaskBackForFix(...sendArgs);
245197
+ }
245019
245198
  return true;
245020
245199
  }
245021
245200
  var init_request_pre_merge_optional_step_fix = __esm({
@@ -245030,6 +245209,7 @@ var init_request_pre_merge_optional_step_fix = __esm({
245030
245209
  init_plan_review_feedback_history();
245031
245210
  init_logger3();
245032
245211
  init_emit_bounded_run_audit3();
245212
+ init_workspace_review_remediation();
245033
245213
  }
245034
245214
  });
245035
245215
 
@@ -245918,6 +246098,14 @@ function createTaskDoneTool(deps, taskId, worktreePath, promptContent, codeRevie
245918
246098
  const refusalMessage = `fn_task_done refused: ${invariantCheck.reason} \u2014 observed=${invariantCheck.observed}, expected=${invariantCheck.expected}`;
245919
246099
  await store.logEntry(taskId, refusalMessage, void 0, deps.getRunContextFor(task.id));
245920
246100
  executorLog.error(`${taskId}: fn_task_done refused (${invariantCheck.reason}) \u2014 observed=${invariantCheck.observed}, expected=${invariantCheck.expected}`);
246101
+ if (invariantCheck.reason === "main_checkout_edit") {
246102
+ await store.updateTask(taskId, { status: "failed", error: refusalMessage });
246103
+ await store.logEntry(taskId, `${refusalMessage} \u2014 operator cleanup and Retry are required; executor retry budget preserved`, void 0, deps.getRunContextFor(task.id));
246104
+ return {
246105
+ content: [{ type: "text", text: refusalMessage }],
246106
+ details: { error: refusalMessage }
246107
+ };
246108
+ }
245921
246109
  const priorRequeues = task.taskDoneRetryCount ?? 0;
245922
246110
  const nextRequeueCount = priorRequeues + 1;
245923
246111
  if (priorRequeues < MAX_TASK_DONE_REQUEUE_RETRIES) {
@@ -247750,7 +247938,9 @@ Repository scope (task-level; review this plan once): ${detail.repositoryScope?.
247750
247938
  return {
247751
247939
  repositoryScope: {
247752
247940
  ...currentScope,
247753
- reviewEvidence: Object.fromEntries(Object.entries(review.repositoryDiffFingerprints).map(([repo, fingerprint]) => [repo, { fingerprint, approvedAt }]))
247941
+ reviewEvidence: Object.fromEntries(Object.entries(review.repositoryDiffFingerprints).map(([repo, fingerprint]) => [repo, { fingerprint, approvedAt }])),
247942
+ // FNXC:WorkspaceFinalization 2026-08-21-09:50: An APPROVE for this fenced scope ends its REVISE episode; retaining its target would misroute later acquisition.
247943
+ ...currentScope.reviewRemediation?.scopeRevision === review.repositoryScopeRevision ? { reviewRemediation: void 0 } : {}
247754
247944
  }
247755
247945
  };
247756
247946
  });
@@ -249155,7 +249345,8 @@ async function runGraphCustomNode(deps, node, nodeTask, settings, columnBinding,
249155
249345
  executionTarget = await deps.ensureGraphCustomNodeWorktree(acquisitionTask, settings, node.id);
249156
249346
  }
249157
249347
  }
249158
- const workspaceCoordinator = workspaceConfig ? Object.keys(executionTarget.workspaceWorktrees ?? {}).filter((repoRelPath) => workspaceConfig.repos.includes(repoRelPath)).sort().map((repoRelPath) => executionTarget.workspaceWorktrees?.[repoRelPath]?.worktreePath).find((path17) => typeof path17 === "string" && path17.length > 0) : void 0;
249348
+ const remediationRepository = workspaceConfig ? resolveWorkspaceReviewRemediationRepository(executionTarget, workspaceConfig.repos) : void 0;
249349
+ const workspaceCoordinator = workspaceConfig ? remediationRepository ? executionTarget.workspaceWorktrees?.[remediationRepository]?.worktreePath : Object.keys(executionTarget.workspaceWorktrees ?? {}).filter((repoRelPath) => workspaceConfig.repos.includes(repoRelPath)).sort().map((repoRelPath) => executionTarget.workspaceWorktrees?.[repoRelPath]?.worktreePath).find((path17) => typeof path17 === "string" && path17.length > 0) : void 0;
249159
249350
  if (!executionTarget.worktree && !workspaceCoordinator) {
249160
249351
  return { outcome: "failure", value: "no-worktree-for-write-node" };
249161
249352
  }
@@ -249315,7 +249506,9 @@ ${prompt}`;
249315
249506
  return {
249316
249507
  repositoryScope: {
249317
249508
  ...currentScope,
249318
- reviewEvidence: Object.fromEntries(Object.entries(aggregate.repositoryDiffFingerprints).map(([repo, fingerprint]) => [repo, { fingerprint, approvedAt }]))
249509
+ reviewEvidence: Object.fromEntries(Object.entries(aggregate.repositoryDiffFingerprints).map(([repo, fingerprint]) => [repo, { fingerprint, approvedAt }])),
249510
+ // FNXC:WorkspaceFinalization 2026-08-21-09:50: A current-scope APPROVE clears the durable REVISE coordinator before graph completion can schedule another run.
249511
+ ...currentScope.reviewRemediation?.scopeRevision === aggregate.repositoryScopeRevision ? { reviewRemediation: void 0 } : {}
249319
249512
  }
249320
249513
  };
249321
249514
  });
@@ -249413,6 +249606,7 @@ var init_run_graph_custom_node = __esm({
249413
249606
  "use strict";
249414
249607
  init_src2();
249415
249608
  init_logger3();
249609
+ init_workspace_review_remediation();
249416
249610
  init_workflow_graph_executor();
249417
249611
  init_workflow_node_execution_needs();
249418
249612
  init_workflow_step_verdict();
@@ -249468,6 +249662,13 @@ async function handleGraphFailure(deps, task, result2) {
249468
249662
  await deps.persistTokenUsage(task.id);
249469
249663
  return;
249470
249664
  }
249665
+ if (graphFailureValue(result2) === BRANCH_WRITE_PROVENANCE_FAILURE_VALUE) {
249666
+ const diagnostic = graphFailureErrorTexts(result2).find((message2) => message2.includes("branchWriteOrigin is required when branch is provided")) ?? "branchWriteOrigin is required when branch is provided";
249667
+ await deps.store.logEntry(task.id, diagnostic, void 0, deps.getRunContextFor(task.id));
249668
+ await deps.store.updateTask(task.id, { status: "failed", error: diagnostic }, deps.getRunContextFor(task.id));
249669
+ await deps.persistTokenUsage(task.id);
249670
+ return;
249671
+ }
249471
249672
  if (isSessionContentionGraphFailure(result2)) {
249472
249673
  await deps.holdForSessionContention(task, live, result2);
249473
249674
  await deps.persistTokenUsage(task.id);
@@ -252205,6 +252406,7 @@ async function runImplementation(deps, task, graphCompletion, reportImplementati
252205
252406
  deps.registerConfiguredCommandController(task.id, taskCommandAbortController);
252206
252407
  let workspaceCoordinatorWorktree;
252207
252408
  if (hasWorkspaceRepos && deps.workspaceConfig) {
252409
+ const remediationRepository = resolveWorkspaceReviewRemediationRepository(task, deps.workspaceConfig.repos);
252208
252410
  const workspace = await acquireWorkspaceTaskWorktrees({
252209
252411
  workspaceConfig: deps.workspaceConfig,
252210
252412
  workspaceRootDir: deps.rootDir,
@@ -252229,7 +252431,8 @@ async function runImplementation(deps, task, graphCompletion, reportImplementati
252229
252431
  return result2;
252230
252432
  }),
252231
252433
  taskEnv,
252232
- addActiveWorktree: deps.addActiveWorktree
252434
+ addActiveWorktree: deps.addActiveWorktree,
252435
+ remediationRepository
252233
252436
  });
252234
252437
  task = workspace.task;
252235
252438
  workspaceCoordinatorWorktree = workspace.coordinatorWorktreePath;
@@ -254789,6 +254992,7 @@ var init_run_implementation = __esm({
254789
254992
  init_run_audit();
254790
254993
  init_emit_bounded_run_audit3();
254791
254994
  init_worktree_acquisition();
254995
+ init_workspace_review_remediation();
254792
254996
  init_worktree_paths();
254793
254997
  init_worktree_pool();
254794
254998
  execAsync52 = promisify75(exec56);
@@ -263696,6 +263900,40 @@ var init_overseer_noop_finalize_veto = __esm({
263696
263900
  }
263697
263901
  });
263698
263902
 
263903
+ // ../engine/src/merge/workspace-merge-readiness.ts
263904
+ function resolveWorkspaceMergeReadiness(task, freshModifiedRepositories, netZeroBranchRepositories) {
263905
+ const scope = task.repositoryScope;
263906
+ if (scope?.state !== "confirmed") {
263907
+ return { kind: "blocked", reason: `Workspace repository scope is unresolved for ${task.id}; operator confirmation is required before landing` };
263908
+ }
263909
+ const entries2 = task.workspaceWorktrees ?? {};
263910
+ const declaredRepositories = scope.repositories.map((repository) => repository.trim());
263911
+ if (declaredRepositories.some((repository) => !repository) || new Set(declaredRepositories).size !== declaredRepositories.length) {
263912
+ return { kind: "blocked", reason: `Workspace repository scope has duplicate or invalid declarations for ${task.id}; operator repair is required` };
263913
+ }
263914
+ const worktreePaths = Object.values(entries2).map((entry) => entry.worktreePath?.trim()).filter((path17) => Boolean(path17));
263915
+ if (new Set(worktreePaths).size !== worktreePaths.length) {
263916
+ return { kind: "blocked", reason: `Workspace merge has duplicate worktree paths for ${task.id}; operator repair is required` };
263917
+ }
263918
+ const repositories = declaredRepositories.filter((repo) => Boolean(entries2[repo])).filter((repo) => freshModifiedRepositories.has(repo) || netZeroBranchRepositories.has(repo) || Boolean(entries2[repo]?.landedSha)).sort();
263919
+ const preservedFiles = [...new Set(task.modifiedFiles ?? [])].sort();
263920
+ if (repositories.length > 0) return { kind: "ready", repositories, preservedFiles };
263921
+ if (task.noCommitsExpected === true && scope.repositories.length === 0) {
263922
+ return { kind: "no-op", repositories: [], preservedFiles: [] };
263923
+ }
263924
+ const declared = [...declaredRepositories].sort();
263925
+ const knownEntries = declared.filter((repo) => entries2[repo] !== void 0);
263926
+ if (knownEntries.length === 0) {
263927
+ return { kind: "blocked", reason: `Workspace merge has no acquired repositories matching confirmed scope for ${task.id}; operator repair is required` };
263928
+ }
263929
+ return { kind: "blocked", reason: `Workspace merge has no evidenced landing obligations for ${task.id}; operator repair is required` };
263930
+ }
263931
+ var init_workspace_merge_readiness = __esm({
263932
+ "../engine/src/merge/workspace-merge-readiness.ts"() {
263933
+ "use strict";
263934
+ }
263935
+ });
263936
+
263699
263937
  // ../engine/src/merge/workspace-fence-ref.ts
263700
263938
  import { execFile as execFile32 } from "node:child_process";
263701
263939
  import { createHash as createHash33 } from "node:crypto";
@@ -265460,8 +265698,21 @@ async function landWorkspaceTask(store, task, workspaceRootDir, options = {}, de
265460
265698
  const taskTitle = task.title?.trim() ? task.title.split("\n")[0] : void 0;
265461
265699
  const liveMergeBoundaryTask = await store.getTask(taskId).catch(() => void 0);
265462
265700
  const mergeBoundaryTask = liveMergeBoundaryTask?.workspaceWorktrees ? liveMergeBoundaryTask : task;
265701
+ const reviewColumns = /* @__PURE__ */ new Set(["in-review"]);
265702
+ const workflowIr = await resolveWorkflowIrForTask(store, taskId).catch(() => void 0);
265703
+ if (workflowIr) for (const column of resolveReviewColumns(workflowIr)) reviewColumns.add(column);
265704
+ const completeColumns = /* @__PURE__ */ new Set(["done"]);
265705
+ if (workflowIr) for (const column of resolveTerminalColumns(workflowIr)) completeColumns.add(column);
265706
+ if (!completeColumns.has(mergeBoundaryTask.column)) {
265707
+ const mergeBlocker = getTaskMergeBlocker(mergeBoundaryTask, {
265708
+ manual: options.manual === true,
265709
+ reviewColumns
265710
+ });
265711
+ if (mergeBlocker) throw new WorkspaceFinalizeBlockedError(taskId, mergeBlocker);
265712
+ }
265463
265713
  const confirmedScope = mergeBoundaryTask.repositoryScope?.state === "confirmed" ? new Set(mergeBoundaryTask.repositoryScope.repositories) : void 0;
265464
265714
  const mergeBoundaryModifiedFiles = [];
265715
+ const mergeBoundaryModifiedRepositories = /* @__PURE__ */ new Set();
265465
265716
  const mergeBoundaryFingerprints = {};
265466
265717
  const modifiedOutOfScopeRepositories = /* @__PURE__ */ new Set();
265467
265718
  const netZeroBranchRepositories = /* @__PURE__ */ new Set();
@@ -265478,6 +265729,7 @@ async function landWorkspaceTask(store, task, workspaceRootDir, options = {}, de
265478
265729
  if (files.length > 0 && !confirmedScope?.has(repoRel)) {
265479
265730
  modifiedOutOfScopeRepositories.add(repoRel);
265480
265731
  } else if (confirmedScope?.has(repoRel)) {
265732
+ if (files.length > 0) mergeBoundaryModifiedRepositories.add(repoRel);
265481
265733
  mergeBoundaryModifiedFiles.push(...files.map((file) => `${repoRel}/${file}`));
265482
265734
  if (files.length === 0) {
265483
265735
  const { stdout: aheadCount } = await execFileAsync28("git", ["rev-list", "--count", `HEAD..${entry.branch}`], { cwd: entry.worktreePath, encoding: "utf8" });
@@ -265493,19 +265745,26 @@ async function landWorkspaceTask(store, task, workspaceRootDir, options = {}, de
265493
265745
  }
265494
265746
  const normalizedMergeBoundaryFiles = [...new Set(mergeBoundaryModifiedFiles)].sort();
265495
265747
  const persistedReviewFiles = [...new Set(mergeBoundaryTask.modifiedFiles ?? [])].sort();
265496
- const approvedReviewEvidence = mergeBoundaryTask.repositoryScope?.reviewEvidence ?? {};
265497
- const missingOrStaleReviewApproval = Object.entries(mergeBoundaryFingerprints).some(([repoRel, fingerprint]) => approvedReviewEvidence[repoRel]?.fingerprint !== fingerprint);
265498
- if (normalizedMergeBoundaryFiles.some((file) => !persistedReviewFiles.includes(file)) || missingOrStaleReviewApproval) {
265748
+ const approvedReviewEvidence = mergeBoundaryTask.repositoryScope?.reviewEvidence;
265749
+ const requiresRepositoryReviewEvidence = approvedReviewEvidence !== void 0 || (mergeBoundaryTask.enabledWorkflowSteps ?? []).some((step) => /review/i.test(step));
265750
+ const missingOrStaleReviewApproval = requiresRepositoryReviewEvidence && Object.entries(mergeBoundaryFingerprints).some(([repoRel, fingerprint]) => approvedReviewEvidence?.[repoRel]?.fingerprint !== fingerprint);
265751
+ if (requiresRepositoryReviewEvidence && normalizedMergeBoundaryFiles.some((file) => !persistedReviewFiles.includes(file)) || missingOrStaleReviewApproval) {
265499
265752
  throw new Error(`Workspace merge evidence changed after review for ${taskId}; return the task to Code Review before landing`);
265500
265753
  }
265501
- await store.updateTask(taskId, { modifiedFiles: normalizedMergeBoundaryFiles });
265502
- task = { ...mergeBoundaryTask, modifiedFiles: normalizedMergeBoundaryFiles };
265503
- const workspaceWorktrees = task.workspaceWorktrees ?? {};
265504
- const explicitScope = task.repositoryScope?.state === "confirmed" ? task.repositoryScope.repositories : void 0;
265505
- if (!explicitScope) {
265506
- throw new Error(`Workspace repository scope is unresolved for ${taskId}; operator confirmation is required before landing`);
265754
+ const readiness = resolveWorkspaceMergeReadiness(
265755
+ mergeBoundaryTask,
265756
+ mergeBoundaryModifiedRepositories,
265757
+ netZeroBranchRepositories
265758
+ );
265759
+ if (readiness.kind === "blocked") throw new Error(readiness.reason);
265760
+ if (readiness.kind === "no-op") {
265761
+ throw new Error(`Workspace task ${taskId} has explicit no-commits policy and cannot enter repository landing`);
265507
265762
  }
265508
- const repoKeys = Object.keys(workspaceWorktrees).filter((repoRel) => explicitScope.includes(repoRel) && ((task.modifiedFiles ?? []).some((file) => file.startsWith(`${repoRel}/`)) || netZeroBranchRepositories.has(repoRel))).sort();
265763
+ const retainedModifiedFiles = [.../* @__PURE__ */ new Set([...normalizedMergeBoundaryFiles, ...readiness.preservedFiles])].sort();
265764
+ await store.updateTask(taskId, { modifiedFiles: retainedModifiedFiles });
265765
+ task = { ...mergeBoundaryTask, modifiedFiles: retainedModifiedFiles };
265766
+ const workspaceWorktrees = task.workspaceWorktrees ?? {};
265767
+ const repoKeys = readiness.repositories;
265509
265768
  const repos = [];
265510
265769
  let allLanded = true;
265511
265770
  await setStatus("merging");
@@ -265534,7 +265793,7 @@ async function landWorkspaceTask(store, task, workspaceRootDir, options = {}, de
265534
265793
  };
265535
265794
  } catch (error3) {
265536
265795
  if (error3 instanceof WorkspaceFenceRefError) {
265537
- throw new WorkspaceRepoLandBusyError(repoKeys[0] ?? "workspace", "workspace-merge-dispatch-fence", taskId);
265796
+ throw new WorkspaceMergeTechnicalError("dispatch-fence-publication", `Workspace merge dispatch fence publication failed for ${taskId}: ${error3.message}`);
265538
265797
  }
265539
265798
  throw error3;
265540
265799
  }
@@ -265623,7 +265882,7 @@ async function landWorkspaceTask(store, task, workspaceRootDir, options = {}, de
265623
265882
  } catch (error3) {
265624
265883
  if (durableLandLease) await store.releaseWorkspaceLease(durableLandLease).catch(() => void 0);
265625
265884
  if (error3 instanceof WorkspaceRepoLandBusyError) throw error3;
265626
- throw new WorkspaceRepoLandBusyError(repoRel, "durable-workspace-lease", taskId);
265885
+ throw new WorkspaceMergeTechnicalError("durable-lease", `Workspace repository lease unavailable for ${repoRel}: ${getErrorMessage4(error3)}`);
265627
265886
  }
265628
265887
  try {
265629
265888
  activeSessionRegistry.registerPath(repoRootDir, {
@@ -265660,7 +265919,7 @@ async function landWorkspaceTask(store, task, workspaceRootDir, options = {}, de
265660
265919
  renewalTimer?.unref?.();
265661
265920
  const landSignal = options.signal ? AbortSignal.any([options.signal, leaseAbort.signal]) : leaseAbort.signal;
265662
265921
  const assertLeaseLive = () => {
265663
- if (leaseLost || leaseAbort.signal.aborted) throw new WorkspaceRepoLandBusyError(repoRel, "durable-workspace-lease", taskId);
265922
+ if (leaseLost || leaseAbort.signal.aborted) throw new WorkspaceMergeTechnicalError("durable-lease", `Workspace repository lease renewal was lost for ${repoRel}`);
265664
265923
  };
265665
265924
  try {
265666
265925
  const landResult = await landOneRepo(repoRootDir, entry.branch, integrationBranch, {
@@ -265745,11 +266004,11 @@ async function landWorkspaceTask(store, task, workspaceRootDir, options = {}, de
265745
266004
  repos.push({ repo: repoRel, repoRootDir, integrationBranch, branch: entry.branch, status: "empty" });
265746
266005
  }
265747
266006
  } catch (err) {
265748
- if (leaseLost) throw new WorkspaceRepoLandBusyError(repoRel, "durable-workspace-lease", taskId);
266007
+ if (leaseLost) throw new WorkspaceMergeTechnicalError("durable-lease", `Workspace repository lease renewal was lost for ${repoRel}`);
265749
266008
  if (isMergeAbortedError(err)) throw err;
265750
266009
  if (err instanceof WorkspacePartialLandError) throw err;
265751
266010
  if (err instanceof WorkspaceFenceRefError) {
265752
- throw new WorkspaceRepoLandBusyError(repoRel, "workspace-merge-dispatch-fence", taskId);
266011
+ throw new WorkspaceMergeTechnicalError("repository-fence-publication", `Workspace repository fence publication failed for ${repoRel}: ${err.message}`);
265753
266012
  }
265754
266013
  const message = getErrorMessage4(err);
265755
266014
  await log44(`AI merge (workspace): sub-repo ${repoRel} land failed: ${message}`);
@@ -266308,7 +266567,7 @@ async function finalizeTask(store, taskId, result2, audit, log44, rootDir, fence
266308
266567
  function throwIfAborted2(signal, taskId) {
266309
266568
  assertMergeGenerationOwned(signal, taskId);
266310
266569
  }
266311
- var execFileAsync28, aiMergeLog, MAX_CONCURRENT_ADVANCE_RETRIES, MAX_BLOCKING_REVIEW_REASONS, AiMergeBlockedError, AiMergeReviewReconciliationInvalidatedError, LEGACY_COMPLETE_COLUMN, LEGACY_ARCHIVED_COLUMN, LEGACY_TERMINAL_COLUMNS3, LEGACY_REBOUND_COLUMN2, STRONG_LANDED_STRATEGIES, WORKSPACE_REPO_LAND_OWNER_KEY, WorkspaceRepoLandBusyError, WorkspacePartialLandError, WorkspaceFinalizeBlockedError, WorkspaceMergeDispatchSupersededError;
266570
+ var execFileAsync28, aiMergeLog, MAX_CONCURRENT_ADVANCE_RETRIES, MAX_BLOCKING_REVIEW_REASONS, AiMergeBlockedError, AiMergeReviewReconciliationInvalidatedError, LEGACY_COMPLETE_COLUMN, LEGACY_ARCHIVED_COLUMN, LEGACY_TERMINAL_COLUMNS3, LEGACY_REBOUND_COLUMN2, STRONG_LANDED_STRATEGIES, WORKSPACE_REPO_LAND_OWNER_KEY, WorkspaceRepoLandBusyError, WorkspaceMergeTechnicalError, WorkspacePartialLandError, WorkspaceFinalizeBlockedError, WorkspaceMergeDispatchSupersededError;
266312
266571
  var init_merger_ai = __esm({
266313
266572
  "../engine/src/merge/merger-ai.ts"() {
266314
266573
  "use strict";
@@ -266340,6 +266599,7 @@ var init_merger_ai = __esm({
266340
266599
  init_active_session_registry();
266341
266600
  init_mcp_resolution();
266342
266601
  init_workspace_land_predicate();
266602
+ init_workspace_merge_readiness();
266343
266603
  init_workspace_land_failure();
266344
266604
  init_workspace_fence_ref();
266345
266605
  init_auto_merge_finalization();
@@ -266385,6 +266645,14 @@ var init_merger_ai = __esm({
266385
266645
  }
266386
266646
  retryable = true;
266387
266647
  };
266648
+ WorkspaceMergeTechnicalError = class extends Error {
266649
+ constructor(kind, message) {
266650
+ super(message);
266651
+ this.kind = kind;
266652
+ this.name = "WorkspaceMergeTechnicalError";
266653
+ }
266654
+ retryable = true;
266655
+ };
266388
266656
  WorkspacePartialLandError = class extends Error {
266389
266657
  constructor(landedCount, failedRepos, message) {
266390
266658
  super(message);
@@ -285578,6 +285846,15 @@ ${detail}`);
285578
285846
  }
285579
285847
  continue;
285580
285848
  }
285849
+ if (err instanceof WorkspaceMergeTechnicalError) {
285850
+ await store.logEntry(taskId, `Workspace merge technical failure (${err.kind}): ${err.message}`, "WorkspaceMergeTechnicalFailure").catch(() => void 0);
285851
+ if (hasManualResolver) {
285852
+ this.rejectMergeResolvers(taskId, err);
285853
+ } else if (!await this.maybeRetryTransientMerge(store, taskId, await store.getTask(taskId).catch(() => null), err.message, true)) {
285854
+ await store.updateTask(taskId, { status: "failed", error: err.message }).catch(() => void 0);
285855
+ }
285856
+ continue;
285857
+ }
285581
285858
  const isWorkspaceBusyError = err instanceof WorkspaceRepoLandBusyError || err instanceof WorkspaceMergeDispatchBusyError || err instanceof WorkspaceMergeDispatchSupersededError;
285582
285859
  if (isWorkspaceBusyError && hasManualResolver) {
285583
285860
  await store.logEntry(taskId, `Workspace sub-repo land busy (contention): ${errorMsg}`, "WorkspaceRepoLandBusy").catch(() => void 0);
@@ -285585,18 +285862,18 @@ ${detail}`);
285585
285862
  continue;
285586
285863
  }
285587
285864
  if (isWorkspaceBusyError && !hasManualResolver) {
285588
- const busyCount = this.workspaceBusyReenqueues.get(taskId) ?? 0;
285865
+ const liveTask = await store.getTask(taskId).catch(() => null);
285866
+ const busyCount = liveTask?.mergeTransientRetryCount ?? 0;
285589
285867
  await store.logEntry(taskId, `Workspace sub-repo land busy (contention): ${errorMsg}`, "WorkspaceRepoLandBusy").catch(() => void 0);
285590
285868
  if (busyCount < _ProjectEngine.WORKSPACE_BUSY_MAX_REENQUEUES) {
285591
- this.workspaceBusyReenqueues.set(taskId, busyCount + 1);
285869
+ const nextCount = busyCount + 1;
285592
285870
  const delayMs = Math.min(5e3 * Math.pow(2, busyCount), 6e4);
285593
- await store.updateTask(taskId, { status: null }).catch(() => void 0);
285871
+ await store.updateTask(taskId, { status: null, mergeTransientRetryCount: nextCount }).catch(() => void 0);
285594
285872
  runtimeLog.log(
285595
- `Workspace land busy re-enqueue ${busyCount + 1}/${_ProjectEngine.WORKSPACE_BUSY_MAX_REENQUEUES} for ${taskId} in ${delayMs / 1e3}s (no mergeRetry consumed \u2014 pure lease contention)`
285873
+ `Workspace land busy re-enqueue ${nextCount}/${_ProjectEngine.WORKSPACE_BUSY_MAX_REENQUEUES} for ${taskId} in ${delayMs / 1e3}s (durable transient retry)`
285596
285874
  );
285597
285875
  this.scheduleWorkspaceBusyReenqueue(taskId, delayMs);
285598
285876
  } else {
285599
- this.workspaceBusyReenqueues.delete(taskId);
285600
285877
  await store.updateTask(taskId, { status: "failed", error: errorMsg }).catch(() => void 0);
285601
285878
  runtimeLog.error(
285602
285879
  `Auto-merge: ${taskId} workspace land busy ${_ProjectEngine.WORKSPACE_BUSY_MAX_REENQUEUES} times \u2014 parked as failed (sustained sub-repo lease contention)`
@@ -286925,7 +287202,7 @@ var init_engine_singleton_lock = __esm({
286925
287202
  import { realpathSync as realpathSync16 } from "node:fs";
286926
287203
  import { resolve as pathResolve } from "node:path";
286927
287204
  function sameProjectRoot(a, b3) {
286928
- const normalize11 = (p2) => {
287205
+ const normalize12 = (p2) => {
286929
287206
  const resolved = pathResolve(p2);
286930
287207
  try {
286931
287208
  return realpathSync16(resolved);
@@ -286933,7 +287210,7 @@ function sameProjectRoot(a, b3) {
286933
287210
  return resolved;
286934
287211
  }
286935
287212
  };
286936
- return normalize11(a) === normalize11(b3);
287213
+ return normalize12(a) === normalize12(b3);
286937
287214
  }
286938
287215
  var DEFAULT_RECONCILIATION_INTERVAL_MS, ProjectEngineManager;
286939
287216
  var init_project_engine_manager = __esm({
@@ -303626,7 +303903,7 @@ var init_gitlab = __esm({
303626
303903
  if (response.status === 204) return void 0;
303627
303904
  return await response.json();
303628
303905
  }
303629
- async listPaginated(path17, options, normalize11) {
303906
+ async listPaginated(path17, options, normalize12) {
303630
303907
  const limit2 = clampLimit(options.limit);
303631
303908
  const out = [];
303632
303909
  let page = 1;
@@ -303637,7 +303914,7 @@ var init_gitlab = __esm({
303637
303914
  appendListParams(params, options);
303638
303915
  const rows = await this.request(`${path17}?${params.toString()}`);
303639
303916
  if (!Array.isArray(rows) || rows.length === 0) break;
303640
- out.push(...rows.map(normalize11));
303917
+ out.push(...rows.map(normalize12));
303641
303918
  if (rows.length < Number(params.get("per_page"))) break;
303642
303919
  page += 1;
303643
303920
  }
@@ -319584,6 +319861,7 @@ function registerPlanningSubtaskRoutes(ctx, deps) {
319584
319861
  priority: isTaskPriority3(summary.priority) ? summary.priority : DEFAULT_TASK_PRIORITY,
319585
319862
  ...sourceContext ? { sourceIssue: sourceContext.sourceIssue, source: { sourceType: "github_import", sourceMetadata: sourceContext.sourceMetadata }, ...trackingDecision?.githubTracking ? { githubTracking: trackingDecision.githubTracking } : {} } : { source: { sourceType: "api" } },
319586
319863
  branch: resolvedBranch,
319864
+ ...resolvedBranch !== void 0 ? { branchWriteOrigin: "operator" } : {},
319587
319865
  baseBranch: resolvedBaseBranch,
319588
319866
  /*
319589
319867
  FNXC:WorkflowSelection 2026-06-20-16:48:
@@ -351306,7 +351584,7 @@ var require_commonjs4 = __commonJS({
351306
351584
  var TYPEMASK = 1023;
351307
351585
  var entToType = (s3) => s3.isFile() ? IFREG : s3.isDirectory() ? IFDIR : s3.isSymbolicLink() ? IFLNK : s3.isCharacterDevice() ? IFCHR : s3.isBlockDevice() ? IFBLK : s3.isSocket() ? IFSOCK : s3.isFIFO() ? IFIFO : UNKNOWN;
351308
351586
  var normalizeCache = /* @__PURE__ */ new Map();
351309
- var normalize11 = (s3) => {
351587
+ var normalize12 = (s3) => {
351310
351588
  const c = normalizeCache.get(s3);
351311
351589
  if (c)
351312
351590
  return c;
@@ -351319,7 +351597,7 @@ var require_commonjs4 = __commonJS({
351319
351597
  const c = normalizeNocaseCache.get(s3);
351320
351598
  if (c)
351321
351599
  return c;
351322
- const n = normalize11(s3.toLowerCase());
351600
+ const n = normalize12(s3.toLowerCase());
351323
351601
  normalizeNocaseCache.set(s3, n);
351324
351602
  return n;
351325
351603
  };
@@ -351488,7 +351766,7 @@ var require_commonjs4 = __commonJS({
351488
351766
  */
351489
351767
  constructor(name2, type = UNKNOWN, root, roots, nocase, children, opts) {
351490
351768
  this.name = name2;
351491
- this.#matchName = nocase ? normalizeNocase(name2) : normalize11(name2);
351769
+ this.#matchName = nocase ? normalizeNocase(name2) : normalize12(name2);
351492
351770
  this.#type = type & TYPEMASK;
351493
351771
  this.nocase = nocase;
351494
351772
  this.roots = roots;
@@ -351581,7 +351859,7 @@ var require_commonjs4 = __commonJS({
351581
351859
  return this.parent || this;
351582
351860
  }
351583
351861
  const children = this.children();
351584
- const name2 = this.nocase ? normalizeNocase(pathPart) : normalize11(pathPart);
351862
+ const name2 = this.nocase ? normalizeNocase(pathPart) : normalize12(pathPart);
351585
351863
  for (const p2 of children) {
351586
351864
  if (p2.#matchName === name2) {
351587
351865
  return p2;
@@ -351826,7 +352104,7 @@ var require_commonjs4 = __commonJS({
351826
352104
  * directly.
351827
352105
  */
351828
352106
  isNamed(n) {
351829
- return !this.nocase ? this.#matchName === normalize11(n) : this.#matchName === normalizeNocase(n);
352107
+ return !this.nocase ? this.#matchName === normalize12(n) : this.#matchName === normalizeNocase(n);
351830
352108
  }
351831
352109
  /**
351832
352110
  * Return the Path object corresponding to the target of a symbolic link.
@@ -351965,7 +352243,7 @@ var require_commonjs4 = __commonJS({
351965
352243
  #readdirMaybePromoteChild(e, c) {
351966
352244
  for (let p2 = c.provisional; p2 < c.length; p2++) {
351967
352245
  const pchild = c[p2];
351968
- const name2 = this.nocase ? normalizeNocase(e.name) : normalize11(e.name);
352246
+ const name2 = this.nocase ? normalizeNocase(e.name) : normalize12(e.name);
351969
352247
  if (name2 !== pchild.#matchName) {
351970
352248
  continue;
351971
352249
  }
@@ -394484,6 +394762,22 @@ var init_update_install_coordinator = __esm({
394484
394762
  pendingVersion;
394485
394763
  pendingResult;
394486
394764
  restartRequested = false;
394765
+ /**
394766
+ * FNXC:PendingUpdateInstall 2026-08-21-05:58:
394767
+ * A completed install belongs to the still-running old process, not a mounted
394768
+ * Settings dialog. Expose its target until process replacement so every route
394769
+ * can reject a second install and every dashboard remount can offer restart.
394770
+ */
394771
+ getPendingInstall() {
394772
+ if (!this.pendingVersion || !this.pendingResult) return void 0;
394773
+ return {
394774
+ ...this.pendingResult,
394775
+ latestVersion: this.pendingVersion,
394776
+ restartAttempted: this.restartRequested,
394777
+ restartScheduled: this.restartRequested,
394778
+ priorPid: this.restartRequested ? process.pid : void 0
394779
+ };
394780
+ }
394487
394781
  async install(targetVersion, operation) {
394488
394782
  if (this.pendingVersion && this.pendingResult) return this.pendingResult;
394489
394783
  if (!this.inFlight) {
@@ -394523,8 +394817,25 @@ var init_register_update_check_routes = __esm({
394523
394817
  const { router, store, rethrowAsApiError: rethrowAsApiError8 } = ctx;
394524
394818
  const cliPackageVersion = getCliPackageVersion(import.meta.url);
394525
394819
  const coordinator = ctx.options?.systemControl ? processUpdateInstallCoordinator : new UpdateInstallCoordinator();
394820
+ const pendingResponse = () => {
394821
+ const pendingInstall = coordinator.getPendingInstall();
394822
+ if (!pendingInstall) return void 0;
394823
+ return {
394824
+ currentVersion: pendingInstall.currentVersion,
394825
+ latestVersion: pendingInstall.latestVersion,
394826
+ // Keep the old version's availability signal compatible for legacy readers.
394827
+ updateAvailable: true,
394828
+ pendingInstall,
394829
+ lastChecked: Date.now()
394830
+ };
394831
+ };
394526
394832
  router.get("/update-check", async (_req, res) => {
394527
394833
  try {
394834
+ const pending = pendingResponse();
394835
+ if (pending) {
394836
+ res.json(pending);
394837
+ return;
394838
+ }
394528
394839
  const globalSettings = await store.getGlobalSettingsStore().getSettings();
394529
394840
  if (globalSettings.updateCheckEnabled === false) {
394530
394841
  res.json({
@@ -394547,6 +394858,11 @@ var init_register_update_check_routes = __esm({
394547
394858
  });
394548
394859
  router.post("/update-check/refresh", async (_req, res) => {
394549
394860
  try {
394861
+ const pending = pendingResponse();
394862
+ if (pending) {
394863
+ res.json(pending);
394864
+ return;
394865
+ }
394550
394866
  const globalSettings = await store.getGlobalSettingsStore().getSettings();
394551
394867
  const fusionDir = resolveGlobalDir();
394552
394868
  await clearUpdateCheckCache(fusionDir);
@@ -394561,6 +394877,11 @@ var init_register_update_check_routes = __esm({
394561
394877
  });
394562
394878
  router.post("/update-check/install", async (_req, res) => {
394563
394879
  try {
394880
+ const pending = coordinator.getPendingInstall();
394881
+ if (pending) {
394882
+ res.json(pending);
394883
+ return;
394884
+ }
394564
394885
  const globalSettings = await store.getGlobalSettingsStore().getSettings();
394565
394886
  const fusionDir = resolveGlobalDir();
394566
394887
  const updateCheck = await performUpdateCheck(fusionDir, cliPackageVersion, {
@@ -394593,7 +394914,7 @@ var init_register_update_check_routes = __esm({
394593
394914
  }
394594
394915
  const restartAttempted = result2.updated && result2.outcome === "installed" && resolveUpdateAutomationSettings(latestSettings).autoRestartAfterUpdate;
394595
394916
  const restartScheduled = restartAttempted ? coordinator.requestRestart(() => ctx.options?.systemControl?.requestRestart("update-install") === true) : false;
394596
- res.json({ ...result2, restartAttempted, restartScheduled, priorPid: restartAttempted ? process.pid : void 0 });
394917
+ res.json(coordinator.getPendingInstall() ?? { ...result2, restartAttempted, restartScheduled, priorPid: restartAttempted ? process.pid : void 0 });
394597
394918
  } catch (error3) {
394598
394919
  rethrowAsApiError8(error3, "Failed to install update");
394599
394920
  }
@@ -405763,7 +406084,7 @@ var init_badge_pubsub = __esm({
405763
406084
  });
405764
406085
 
405765
406086
  // ../dashboard/src/terminal-websocket-diagnostics.ts
405766
- import { basename as basename37, normalize as normalize9, sep as sep25 } from "node:path";
406087
+ import { basename as basename37, normalize as normalize10, sep as sep25 } from "node:path";
405767
406088
  function toSessionTag(sessionId) {
405768
406089
  const normalized2 = sessionId.trim();
405769
406090
  if (normalized2.length <= 8) {
@@ -405772,7 +406093,7 @@ function toSessionTag(sessionId) {
405772
406093
  return `${normalized2.slice(0, 4)}\u2026${normalized2.slice(-4)}#${normalized2.length}`;
405773
406094
  }
405774
406095
  function toRedactedPathHint(pathValue) {
405775
- const normalized2 = normalize9(pathValue);
406096
+ const normalized2 = normalize10(pathValue);
405776
406097
  const segments = normalized2.split(sep25).filter(Boolean);
405777
406098
  if (segments.length === 0) {
405778
406099
  return "<empty>";
@@ -406712,6 +407033,8 @@ async function runAutoUpdateCycle(deps) {
406712
407033
  });
406713
407034
  return "unsupervised";
406714
407035
  }
407036
+ const coordinator = deps.coordinator ?? new UpdateInstallCoordinator();
407037
+ if (coordinator.getPendingInstall()) return "restart-waiting";
406715
407038
  const fusionDir = deps.fusionDir ?? resolveGlobalDir();
406716
407039
  const check3 = deps.checkForUpdate ?? performUpdateCheck;
406717
407040
  const install = deps.installUpdate ?? performUpdateInstall;
@@ -406737,7 +407060,6 @@ async function runAutoUpdateCycle(deps) {
406737
407060
  });
406738
407061
  let installed;
406739
407062
  try {
406740
- const coordinator = deps.coordinator ?? new UpdateInstallCoordinator();
406741
407063
  installed = await coordinator.install(result2.latestVersion, () => install(result2.currentVersion, result2.latestVersion, {
406742
407064
  fusionDir,
406743
407065
  installMethod: { sourceWorkspaceRoot: deps.sourceWorkspaceRoot }
@@ -406762,7 +407084,7 @@ async function runAutoUpdateCycle(deps) {
406762
407084
  latestSettings = {};
406763
407085
  }
406764
407086
  if (!resolveUpdateAutomationSettings(latestSettings).autoRestartAfterUpdate) return "up-to-date";
406765
- const scheduled = (deps.coordinator ?? new UpdateInstallCoordinator()).requestRestart(() => deps.requestRestart("auto-update"));
407087
+ const scheduled = coordinator.requestRestart(() => deps.requestRestart("auto-update"));
406766
407088
  if (!scheduled) {
406767
407089
  deps.log.warn("Auto-update installed but restart was not scheduled", {
406768
407090
  message: `v${installed.latestVersion} is installed; restart Fusion manually to run it.`
@@ -408596,9 +408918,17 @@ function createServer6(store, options) {
408596
408918
  };
408597
408919
  const jsonParser = express3.json({ verify: preserveRawBody });
408598
408920
  const planningImageCaptureParser = express3.json({ limit: "5mb", verify: preserveRawBody });
408921
+ const chatMessageParser = express3.json({ limit: 2 * 1024 * 1024, verify: preserveRawBody });
408922
+ const fileSaveParser = express3.json({ limit: 6 * MAX_FILE_SIZE + 1024, verify: preserveRawBody });
408923
+ const isChatMessagePath = (path17) => /^\/api\/chat\/(?:sessions|rooms)\/[^/]+\/messages\/?$/.test(path17);
408924
+ const isTaskFileSavePath = (path17) => /^\/api\/tasks\/[^/]+\/files\/.+\/?$/.test(path17);
408925
+ const isWorkspaceFileSavePath = (path17) => {
408926
+ if (!/^\/api\/files\/.+/.test(path17) || /^\/api\/files\/mkdir\/?$/.test(path17)) return false;
408927
+ return !/^\/api\/files\/.+\/(?:copy|move|delete|rename)\/?$/.test(path17);
408928
+ };
408599
408929
  app.use((req, res, next) => {
408600
408930
  if (req.path === "/api/voice/transcribe" || req.path === "/api/voice/transcribe/") return next();
408601
- const parser = req.path === "/api/planning/start-streaming" || req.path === "/api/planning/start-streaming/" ? planningImageCaptureParser : jsonParser;
408931
+ const parser = req.path === "/api/planning/start-streaming" || req.path === "/api/planning/start-streaming/" ? planningImageCaptureParser : req.method === "POST" && isChatMessagePath(req.path) ? chatMessageParser : req.method === "POST" && (isTaskFileSavePath(req.path) || isWorkspaceFileSavePath(req.path)) ? fileSaveParser : jsonParser;
408602
408932
  return parser(req, res, (error3) => {
408603
408933
  if (error3?.type === "entity.too.large") return res.status(413).json({ error: "payload-too-large" });
408604
408934
  return next(error3);
@@ -410014,6 +410344,7 @@ var init_server2 = __esm({
410014
410344
  init_api_error();
410015
410345
  init_project_store_resolver();
410016
410346
  init_chat_project_services();
410347
+ init_file_service();
410017
410348
  init_terminal_viewport();
410018
410349
  init_terminal_service();
410019
410350
  init_terminal();
@@ -431037,7 +431368,7 @@ var init_mcp_memory_server = __esm({
431037
431368
 
431038
431369
  // src/project-resolver.ts
431039
431370
  import { existsSync as existsSync126, statSync as statSync21 } from "node:fs";
431040
- import { basename as basename43, dirname as dirname77, join as join153, normalize as normalize10, resolve as resolve97 } from "node:path";
431371
+ import { basename as basename43, dirname as dirname77, join as join153, normalize as normalize11, resolve as resolve97 } from "node:path";
431041
431372
  import { createInterface as createInterface5 } from "node:readline/promises";
431042
431373
  async function getCentralCore() {
431043
431374
  if (!centralCoreInstance) {
@@ -431134,8 +431465,8 @@ Run \`fn project remove ` + match.name + "` to clean up the registry entry.",
431134
431465
  const fusionDir = findKbDir(cwd);
431135
431466
  if (fusionDir) {
431136
431467
  const allProjects2 = await central.listProjects();
431137
- const normalizedKbDir = normalize10(fusionDir);
431138
- const match = allProjects2.find((p2) => normalize10(p2.path) === normalizedKbDir);
431468
+ const normalizedKbDir = normalize11(fusionDir);
431469
+ const match = allProjects2.find((p2) => normalize11(p2.path) === normalizedKbDir);
431139
431470
  if (match) {
431140
431471
  if (!existsSync126(match.path)) {
431141
431472
  throw new ProjectResolutionError(