@runfusion/fusion 0.75.1-beta.0 → 0.75.1-beta.2

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 (136) hide show
  1. package/dist/bin.js +523 -212
  2. package/dist/child-process-worker.js +323 -99
  3. package/dist/client/.vite/manifest.json +249 -249
  4. package/dist/client/assets/{AgentDetailView-swTdiyeE.js → AgentDetailView-ltOG8f0-.js} +1 -1
  5. package/dist/client/assets/{AgentPermissionPolicyEditor-Dq-KuDaA.js → AgentPermissionPolicyEditor-CC_rrbbG.js} +1 -1
  6. package/dist/client/assets/{AgentsView-D1GEHyH0.js → AgentsView-DWUdi27N.js} +3 -3
  7. package/dist/client/assets/{ChatView-D1czGvvT.js → ChatView-mFKfoegS.js} +1 -1
  8. package/dist/client/assets/{CommandCenter-DWZ7OTOB.js → CommandCenter-C2wvDq_N.js} +1 -1
  9. package/dist/client/assets/{DevServerView-CAcUKOXd.js → DevServerView-0MWEJM7o.js} +1 -1
  10. package/dist/client/assets/{DirectoryPicker-DVUl5jOk.js → DirectoryPicker-BjMPhCdh.js} +1 -1
  11. package/dist/client/assets/{DocumentsView-CnexOxtr.js → DocumentsView-ckH3rjmd.js} +1 -1
  12. package/dist/client/assets/{EvalsView-rTupzcDf.js → EvalsView-NyA644VF.js} +1 -1
  13. package/dist/client/assets/{ExperimentalAgentOnboardingModal-CXE6Bj3V.js → ExperimentalAgentOnboardingModal-D0ZfbBRN.js} +1 -1
  14. package/dist/client/assets/{GoalsView-CjHwnnIi.js → GoalsView-Ctt5P_yo.js} +1 -1
  15. package/dist/client/assets/{InsightsView-C3XocYP-.js → InsightsView-NDp0bdNB.js} +1 -1
  16. package/dist/client/assets/{MemoryView-CsNHcx_d.js → MemoryView-BKbA4Bm4.js} +1 -1
  17. package/dist/client/assets/{PiExtensionsManager-BPTPoZBI.js → PiExtensionsManager-V3AFWMHm.js} +1 -1
  18. package/dist/client/assets/{PluginManager-CXadn8JE.js → PluginManager-D88G_TZh.js} +1 -1
  19. package/dist/client/assets/{PullRequestView-CC03CtZJ.js → PullRequestView-DQQmFFYC.js} +1 -1
  20. package/dist/client/assets/{ReportModal-Ci0na1eI.js → ReportModal-Clh1S8RF.js} +1 -1
  21. package/dist/client/assets/{ResearchView-DGWyAo8p.js → ResearchView-kxvq4zp5.js} +1 -1
  22. package/dist/client/assets/SecretsView-Dm0Vk8SA.js +1 -0
  23. package/dist/client/assets/{SessionTerminal-B22RlQeo.js → SessionTerminal-DJO4JTEX.js} +1 -1
  24. package/dist/client/assets/{SettingsModal-BkgBQZ19.js → SettingsModal-BN5mL_ym.js} +1 -1
  25. package/dist/client/assets/SettingsModal-CTf5voEW.js +21 -0
  26. package/dist/client/assets/{SettingsTextareaRow-0LXwMUQR.js → SettingsTextareaRow-CvKszqO-.js} +1 -1
  27. package/dist/client/assets/{SetupWizardModal-CyW3AlRO.js → SetupWizardModal-D5hVZh1G.js} +2 -2
  28. package/dist/client/assets/{SkillsView-3oqB1ubq.js → SkillsView-DQVz21w2.js} +1 -1
  29. package/dist/client/assets/{WorkflowNodeEditor-CfUK_xs1.js → WorkflowNodeEditor-CpKIi6Tb.js} +3 -3
  30. package/dist/client/assets/{agent-import-generation-C7-Cmo3M.js → agent-import-generation-DGUNF3nd.js} +1 -1
  31. package/dist/client/assets/{architectureDiagram-3BPJPVTR-BQ7ZN1r_.js → architectureDiagram-3BPJPVTR-CV83wubP.js} +1 -1
  32. package/dist/client/assets/{blockDiagram-GPEHLZMM-Debw1Xka.js → blockDiagram-GPEHLZMM-CNQsA3dD.js} +1 -1
  33. package/dist/client/assets/{c4Diagram-AAUBKEIU-CiU212s3.js → c4Diagram-AAUBKEIU-CXe7ii5H.js} +1 -1
  34. package/dist/client/assets/channel-DoX862zl.js +1 -0
  35. package/dist/client/assets/{chunk-2J33WTMH-BUJQ86zb.js → chunk-2J33WTMH-BS6W9bWv.js} +1 -1
  36. package/dist/client/assets/{chunk-4BX2VUAB-C6GdEm1z.js → chunk-4BX2VUAB-DbehRhdW.js} +1 -1
  37. package/dist/client/assets/{chunk-55IACEB6-CImvrxQL.js → chunk-55IACEB6-DMI5SR42.js} +1 -1
  38. package/dist/client/assets/{chunk-727SXJPM-Dt-A3pkM.js → chunk-727SXJPM-D1d-GBVj.js} +1 -1
  39. package/dist/client/assets/{chunk-AQP2D5EJ-DDj4oXTx.js → chunk-AQP2D5EJ-BzO6K3Uf.js} +1 -1
  40. package/dist/client/assets/{chunk-FMBD7UC4-BD4r1ukC.js → chunk-FMBD7UC4-BVRLV3lK.js} +1 -1
  41. package/dist/client/assets/{chunk-ND2GUHAM-DFSteefi.js → chunk-ND2GUHAM-DdWByQfn.js} +1 -1
  42. package/dist/client/assets/{chunk-QZHKN3VN-t2zF8tjB.js → chunk-QZHKN3VN-1ia12cRV.js} +1 -1
  43. package/dist/client/assets/classDiagram-4FO5ZUOK-mb2IeAGN.js +1 -0
  44. package/dist/client/assets/classDiagram-v2-Q7XG4LA2-mb2IeAGN.js +1 -0
  45. package/dist/client/assets/{cose-bilkent-S5V4N54A-tkvDkXQA.js → cose-bilkent-S5V4N54A-BPDT41HR.js} +1 -1
  46. package/dist/client/assets/{dagre-BM42HDAG-DD2rW7wo.js → dagre-BM42HDAG-Cktn26EH.js} +1 -1
  47. package/dist/client/assets/{dashboard-view-B1vduc8x.js → dashboard-view-8o0EUcRX.js} +1 -1
  48. package/dist/client/assets/{dashboard-view-CciuDS5A.js → dashboard-view-CkCerlVJ.js} +1 -1
  49. package/dist/client/assets/{dashboard-view-DruCruAc.js → dashboard-view-D_HC4lnr.js} +1 -1
  50. package/dist/client/assets/{dashboard-view-C7jJ1lA4.js → dashboard-view-zd8GxC49.js} +1 -1
  51. package/dist/client/assets/{diagram-2AECGRRQ-DASuxGNc.js → diagram-2AECGRRQ-But4Lvf-.js} +1 -1
  52. package/dist/client/assets/{diagram-5GNKFQAL-f9WtiIUk.js → diagram-5GNKFQAL-2bpQAGFc.js} +1 -1
  53. package/dist/client/assets/{diagram-KO2AKTUF-QivICYCd.js → diagram-KO2AKTUF-Ckn9Qd1u.js} +1 -1
  54. package/dist/client/assets/{diagram-LMA3HP47-CMwcDFHR.js → diagram-LMA3HP47-Bjn8h84N.js} +1 -1
  55. package/dist/client/assets/{diagram-OG6HWLK6-C-vPMeHZ.js → diagram-OG6HWLK6-BI6541hx.js} +1 -1
  56. package/dist/client/assets/{erDiagram-TEJ5UH35-CxGtk9vj.js → erDiagram-TEJ5UH35-BqMiwHiM.js} +1 -1
  57. package/dist/client/assets/{flowDiagram-I6XJVG4X-qBZRZoV1.js → flowDiagram-I6XJVG4X-Q_SAB10M.js} +1 -1
  58. package/dist/client/assets/{folder-open-Dysi9Zzg.js → folder-open-CHiAJbTN.js} +1 -1
  59. package/dist/client/assets/{ganttDiagram-6RSMTGT7-DAaXroiu.js → ganttDiagram-6RSMTGT7-CqWFSfPF.js} +1 -1
  60. package/dist/client/assets/{gitGraphDiagram-PVQCEYII-DNUNQot_.js → gitGraphDiagram-PVQCEYII-B1o4uEjO.js} +1 -1
  61. package/dist/client/assets/index-Bdw5eNKV.css +1 -0
  62. package/dist/client/assets/index-kwRywd9M.js +2767 -0
  63. package/dist/client/assets/{infoDiagram-5YYISTIA-BSJmWLwh.js → infoDiagram-5YYISTIA-BzaMa0c2.js} +1 -1
  64. package/dist/client/assets/{ishikawaDiagram-YF4QCWOH-AyVKvoDq.js → ishikawaDiagram-YF4QCWOH-I38yge00.js} +1 -1
  65. package/dist/client/assets/{journeyDiagram-JHISSGLW-972txzzd.js → journeyDiagram-JHISSGLW-WVQSgx4P.js} +1 -1
  66. package/dist/client/assets/{kanban-definition-UN3LZRKU-1xiNvIqK.js → kanban-definition-UN3LZRKU-BWtPQ3AH.js} +1 -1
  67. package/dist/client/assets/{mermaid.core-oeDA8jOm.js → mermaid.core-D1hnBtbi.js} +4 -4
  68. package/dist/client/assets/{mindmap-definition-RKZ34NQL-DcktmI8s.js → mindmap-definition-RKZ34NQL-DqiSLkW7.js} +1 -1
  69. package/dist/client/assets/{pieDiagram-4H26LBE5-ekBwLt-O.js → pieDiagram-4H26LBE5-CcgwbdIN.js} +1 -1
  70. package/dist/client/assets/{puzzle-I6pr6ZrW.js → puzzle-BhXUaTQl.js} +1 -1
  71. package/dist/client/assets/{quadrantDiagram-W4KKPZXB-D_wjTsvn.js → quadrantDiagram-W4KKPZXB-MsqQQfDN.js} +1 -1
  72. package/dist/client/assets/{requirementDiagram-4Y6WPE33-CKGHyiJ1.js → requirementDiagram-4Y6WPE33-CILGjMMA.js} +1 -1
  73. package/dist/client/assets/{sankeyDiagram-5OEKKPKP-CtiZFNk5.js → sankeyDiagram-5OEKKPKP-c14fWbtR.js} +1 -1
  74. package/dist/client/assets/{sequenceDiagram-3UESZ5HK-B6OMrfGk.js → sequenceDiagram-3UESZ5HK-BD5bxMom.js} +1 -1
  75. package/dist/client/assets/{shield-alert-CVMmviMG.js → shield-alert-C9W2WywS.js} +1 -1
  76. package/dist/client/assets/{standing-instructions-template-Cg1a3tjW.js → standing-instructions-template-CJdbDSyg.js} +1 -1
  77. package/dist/client/assets/{stateDiagram-AJRCARHV-CyTY2M_7.js → stateDiagram-AJRCARHV-CZ5YqeOn.js} +1 -1
  78. package/dist/client/assets/{stateDiagram-v2-BHNVJYJU-NWPCCiqC.js → stateDiagram-v2-BHNVJYJU-mdMN-PNX.js} +1 -1
  79. package/dist/client/assets/{timeline-definition-PNZ67QCA-DU4TVYpw.js → timeline-definition-PNZ67QCA-DHKay9uj.js} +1 -1
  80. package/dist/client/assets/{upload-VCzeCiFH.js → upload-DxEwowRU.js} +1 -1
  81. package/dist/client/assets/{users-DB3knZtm.js → users-mNFgW_Zd.js} +1 -1
  82. package/dist/client/assets/{vennDiagram-CIIHVFJN-DX0Ppll6.js → vennDiagram-CIIHVFJN-X29K9hWM.js} +1 -1
  83. package/dist/client/assets/{wardley-L42UT6IY-CBoWxpDv.js → wardley-L42UT6IY-yEoTrfmN.js} +1 -1
  84. package/dist/client/assets/{wardleyDiagram-YWT4CUSO-B_eVqdhC.js → wardleyDiagram-YWT4CUSO-ny4k3kG0.js} +1 -1
  85. package/dist/client/assets/{xychartDiagram-2RQKCTM6-DeZzEX0f.js → xychartDiagram-2RQKCTM6-DC8ODtRJ.js} +1 -1
  86. package/dist/client/index.html +2 -2
  87. package/dist/client/version.json +1 -1
  88. package/dist/droid-cli/package.json +1 -1
  89. package/dist/extension.js +411 -155
  90. package/dist/pi-claude-cli/package.json +1 -1
  91. package/dist/plugin-sdk/index.d.ts +159 -141
  92. package/dist/plugin-sdk/index.js +19 -0
  93. package/dist/plugins/fusion-plugin-acp-runtime/bundled.js +19 -0
  94. package/dist/plugins/fusion-plugin-acp-runtime/package.json +1 -1
  95. package/dist/plugins/fusion-plugin-claude-runtime/bundled.js +19 -0
  96. package/dist/plugins/fusion-plugin-claude-runtime/package.json +1 -1
  97. package/dist/plugins/fusion-plugin-cli-printing-press/bundled.js +19 -0
  98. package/dist/plugins/fusion-plugin-cli-printing-press/package.json +1 -1
  99. package/dist/plugins/fusion-plugin-compound-engineering/bundled.js +19 -0
  100. package/dist/plugins/fusion-plugin-compound-engineering/package.json +1 -1
  101. package/dist/plugins/fusion-plugin-cursor-runtime/bundled.js +19 -0
  102. package/dist/plugins/fusion-plugin-cursor-runtime/package.json +1 -1
  103. package/dist/plugins/fusion-plugin-dependency-graph/bundled.js +19 -0
  104. package/dist/plugins/fusion-plugin-dependency-graph/package.json +1 -1
  105. package/dist/plugins/fusion-plugin-droid-runtime/bundled.js +19 -0
  106. package/dist/plugins/fusion-plugin-droid-runtime/package.json +1 -1
  107. package/dist/plugins/fusion-plugin-grok-runtime/bundled.js +19 -0
  108. package/dist/plugins/fusion-plugin-grok-runtime/package.json +1 -1
  109. package/dist/plugins/fusion-plugin-hermes-runtime/bundled.js +19 -0
  110. package/dist/plugins/fusion-plugin-hermes-runtime/package.json +1 -1
  111. package/dist/plugins/fusion-plugin-linear-import/bundled.js +19 -0
  112. package/dist/plugins/fusion-plugin-linear-import/package.json +1 -1
  113. package/dist/plugins/fusion-plugin-omp-runtime/bundled.js +19 -0
  114. package/dist/plugins/fusion-plugin-omp-runtime/package.json +1 -1
  115. package/dist/plugins/fusion-plugin-openclaw-runtime/bundled.js +19 -0
  116. package/dist/plugins/fusion-plugin-openclaw-runtime/package.json +1 -1
  117. package/dist/plugins/fusion-plugin-paperclip-runtime/bundled.js +19 -0
  118. package/dist/plugins/fusion-plugin-paperclip-runtime/package.json +1 -1
  119. package/dist/plugins/fusion-plugin-quality/bundled.js +19 -0
  120. package/dist/plugins/fusion-plugin-quality/package.json +1 -1
  121. package/dist/plugins/fusion-plugin-reports/bundled.js +19 -0
  122. package/dist/plugins/fusion-plugin-reports/package.json +1 -1
  123. package/dist/plugins/fusion-plugin-roadmap/bundled.js +19 -0
  124. package/dist/plugins/fusion-plugin-roadmap/package.json +1 -1
  125. package/dist/plugins/fusion-plugin-todos/bundled.js +19 -0
  126. package/dist/plugins/fusion-plugin-todos/package.json +1 -1
  127. package/dist/plugins/fusion-plugin-whatsapp-chat/bundled.js +19 -0
  128. package/dist/plugins/fusion-plugin-whatsapp-chat/package.json +1 -1
  129. package/package.json +1 -1
  130. package/dist/client/assets/SecretsView-Cv2IuqFO.js +0 -1
  131. package/dist/client/assets/SettingsModal-CPRpRK71.js +0 -21
  132. package/dist/client/assets/channel-D-O4mRXx.js +0 -1
  133. package/dist/client/assets/classDiagram-4FO5ZUOK-BhHdSetA.js +0 -1
  134. package/dist/client/assets/classDiagram-v2-Q7XG4LA2-BhHdSetA.js +0 -1
  135. package/dist/client/assets/index-D0QgE5F5.js +0 -2767
  136. package/dist/client/assets/index-DHk_s6rA.css +0 -1
package/dist/bin.js CHANGED
@@ -1183,6 +1183,9 @@ function isFusionSiblingBranch(branch) {
1183
1183
  function resolveEffectiveAutoMerge(task, settings) {
1184
1184
  return task.autoMerge ?? settings.autoMerge;
1185
1185
  }
1186
+ function hasUserAutoMergeHold(task) {
1187
+ return task.autoMerge === false && task.autoMergeProvenance === "user";
1188
+ }
1186
1189
  function allowsAutoMergeProcessing(task, settings) {
1187
1190
  return (settings.autoMerge !== false || task.autoMerge === true) && !taskHasManualOpenPullRequest(task);
1188
1191
  }
@@ -10048,6 +10051,7 @@ function validateForeach(node, topLevelNodeIds, columnIds) {
10048
10051
  );
10049
10052
  }
10050
10053
  for (const inner of templateNodes) {
10054
+ validateReviewKind(inner, "nested");
10051
10055
  if (inner.kind === "foreach" || inner.kind === "loop") {
10052
10056
  throw new WorkflowIrError(
10053
10057
  `foreach node '${node.id}' template may not contain nested loop/foreach ('${inner.id}')`
@@ -10166,6 +10170,7 @@ function validateLoop(node, topLevelNodeIds, columnIds) {
10166
10170
  );
10167
10171
  }
10168
10172
  for (const inner of templateNodes) {
10173
+ validateReviewKind(inner, "nested");
10169
10174
  if (inner.kind === "loop" || inner.kind === "foreach") {
10170
10175
  throw new WorkflowIrError(
10171
10176
  `loop node '${node.id}' template may not contain nested loop/foreach ('${inner.id}')`
@@ -10259,6 +10264,7 @@ function validateOptionalGroup(node, topLevelNodeIds, columnIds) {
10259
10264
  throw new WorkflowIrError(`optional-group node '${node.id}' template has duplicate node ids`);
10260
10265
  }
10261
10266
  for (const inner of templateNodes) {
10267
+ validateReviewKind(inner, "nested");
10262
10268
  if (inner.kind === "loop" || inner.kind === "foreach" || inner.kind === "optional-group") {
10263
10269
  throw new WorkflowIrError(
10264
10270
  `optional-group node '${node.id}' template may not contain nested loop/foreach/optional-group ('${inner.id}')`
@@ -10930,6 +10936,19 @@ function validateColumnRecovery(column) {
10930
10936
  );
10931
10937
  }
10932
10938
  }
10939
+ function validateReviewKind(node, placement) {
10940
+ const value = node.config?.reviewKind;
10941
+ if (value === void 0) return;
10942
+ if (value !== "plan" && value !== "code") {
10943
+ throw new WorkflowIrError(`Workflow node '${node.id}' has invalid reviewKind '${String(value)}'; expected 'plan' or 'code'`);
10944
+ }
10945
+ if (placement === "nested") {
10946
+ throw new WorkflowIrError(`Workflow node '${node.id}' has reviewKind in an unsupported nested template placement`);
10947
+ }
10948
+ if (node.kind !== "prompt" && node.kind !== "gate" && node.kind !== "script" && node.kind !== "optional-group") {
10949
+ throw new WorkflowIrError(`Workflow node '${node.id}' has reviewKind on unsupported node kind '${node.kind}'`);
10950
+ }
10951
+ }
10933
10952
  function validateV2(ir2) {
10934
10953
  validateColumns(ir2);
10935
10954
  for (const [index3, column] of ir2.columns.entries()) {
@@ -10952,6 +10971,7 @@ function validateV2(ir2) {
10952
10971
  }
10953
10972
  const nodesById = new Map(ir2.nodes.map((n) => [n.id, n]));
10954
10973
  for (const node of ir2.nodes) {
10974
+ validateReviewKind(node, "top-level");
10955
10975
  validateExtensionMetadata(`Workflow node '${node.id}'`, node.extensions);
10956
10976
  if (node.column !== void 0 && !columnIds.has(node.column)) {
10957
10977
  throw new WorkflowIrError(
@@ -13415,6 +13435,7 @@ function codeReviewOptionalGroupNode(column, options = {}) {
13415
13435
  column,
13416
13436
  config: {
13417
13437
  name: CODE_REVIEW_NAME,
13438
+ reviewKind: "code",
13418
13439
  // Default-ON: runs for every coding task by default, but operators can toggle it
13419
13440
  // off per task (remove `code-review` from enabledWorkflowSteps).
13420
13441
  defaultOn: options.defaultOn ?? true,
@@ -13560,6 +13581,7 @@ function planReviewOptionalGroupNode(column, options = {}) {
13560
13581
  ...column ? { column } : {},
13561
13582
  config: {
13562
13583
  name: PLAN_REVIEW_NAME,
13584
+ reviewKind: "plan",
13563
13585
  defaultOn: options.defaultOn ?? true,
13564
13586
  /*
13565
13587
  * FNXC:WorkflowRemediation 2026-06-29-12:14:
@@ -63404,7 +63426,7 @@ function rowToTask(row) {
63404
63426
  executionStartBranch: row.executionStartBranch || void 0,
63405
63427
  branch: row.branch || void 0,
63406
63428
  autoMerge: row.autoMerge === null ? void 0 : row.autoMerge === 1,
63407
- autoMergeProvenance: row.autoMergeProvenance === "user" || row.autoMergeProvenance === "legacy-stamp" ? row.autoMergeProvenance : void 0,
63429
+ autoMergeProvenance: row.autoMergeProvenance === "user" || row.autoMergeProvenance === "mission" || row.autoMergeProvenance === "legacy-stamp" ? row.autoMergeProvenance : void 0,
63408
63430
  baseCommitSha: row.baseCommitSha || void 0,
63409
63431
  scopeOverride: row.scopeOverride ? true : void 0,
63410
63432
  scopeOverrideReason: row.scopeOverrideReason || void 0,
@@ -72920,10 +72942,10 @@ ${feature.acceptanceCriteria}`);
72920
72942
  Apply the selected Missions header workflow atomically during TaskStore.createTask so newly triaged features land in the intended workflow lane. Duplicate-guard reuses skip this create path, preserving existing duplicate tasks without workflow mutation.
72921
72943
  */
72922
72944
  /*
72923
- FNXC:MissionAutoMerge 2026-07-18-12:00:
72924
- An autoMerge:false mission stamps each newly triaged task so its shared branch produces one PR instead of per-task auto-merges. Duplicate reuse intentionally bypasses this create-only override.
72945
+ FNXC:MissionAutoMerge 2026-08-05-22:50:
72946
+ An autoMerge:false mission stamps each newly triaged task as mission policy so its shared branch produces one PR instead of per-task auto-merges. Duplicate reuse intentionally bypasses this create-only override; policy must not impersonate an operator manual-hold choice.
72925
72947
  */
72926
- ...mission?.autoMerge === false ? { autoMerge: false } : {},
72948
+ ...mission?.autoMerge === false ? { autoMerge: false, autoMergeProvenance: "mission" } : {},
72927
72949
  // FNXC:MissionTaskPrefix 2026-07-26-12:00: thread the mission's optional taskPrefix into TaskCreateInput for distributed id minting.
72928
72950
  ...mission?.taskPrefix ? { taskPrefix: mission.taskPrefix } : {},
72929
72951
  ...branchOptions?.workflowId !== void 0 ? { workflowId: branchOptions.workflowId } : {}
@@ -76101,10 +76123,10 @@ ${feature.acceptanceCriteria}`);
76101
76123
  }
76102
76124
  } : {},
76103
76125
  /*
76104
- FNXC:MissionAutoMerge 2026-07-18-12:00:
76105
- An autoMerge:false mission stamps each newly triaged task so its shared branch produces one PR instead of per-task auto-merges. Duplicate reuse intentionally bypasses this create-only override.
76126
+ FNXC:MissionAutoMerge 2026-08-05-22:50:
76127
+ An autoMerge:false mission stamps each newly triaged task as mission policy so its shared branch produces one PR instead of per-task auto-merges. Duplicate reuse intentionally bypasses this create-only override; policy must not impersonate an operator manual-hold choice.
76106
76128
  */
76107
- ...mission?.autoMerge === false ? { autoMerge: false } : {},
76129
+ ...mission?.autoMerge === false ? { autoMerge: false, autoMergeProvenance: "mission" } : {},
76108
76130
  // FNXC:MissionTaskPrefix 2026-07-26-12:00: thread the mission's optional taskPrefix into TaskCreateInput so the distributed allocator mints ERR-N (etc.) instead of the project prefix.
76109
76131
  ...mission?.taskPrefix ? { taskPrefix: mission.taskPrefix } : {},
76110
76132
  ...branchOptions?.workflowId !== void 0 ? { workflowId: branchOptions.workflowId } : {}
@@ -86455,7 +86477,9 @@ async function _createTaskInternalBackendImpl(store, input, title2, resolvedWork
86455
86477
  sourceMetadata: withTaskBranchContextInSourceMetadata(input.source?.sourceMetadata, input.branchContext),
86456
86478
  branchContext: input.branchContext,
86457
86479
  autoMerge: input.autoMerge,
86458
- autoMergeProvenance: input.autoMerge === void 0 ? void 0 : "user",
86480
+ // FNXC:SharedBranchMemberHold 2026-08-05-22:50: trusted mission creation
86481
+ // preserves policy provenance; operator/API create requests retain user provenance.
86482
+ autoMergeProvenance: input.autoMerge === void 0 ? void 0 : input.autoMergeProvenance ?? "user",
86459
86483
  // FNXC:CodingIdeasWorkflow 2026-07-05-19:45: land the task in its
86460
86484
  // workflow's manual intake column (e.g. Coding (Ideas) → "ideas") when
86461
86485
  // no explicit column is given (main FN-7591 parity).
@@ -127160,6 +127184,36 @@ var init_async_knowledge = __esm({
127160
127184
  });
127161
127185
 
127162
127186
  // ../core/src/workflows/workflow-step-results.ts
127187
+ function normalizeWorkflowReviewFindings(raw) {
127188
+ if (!Array.isArray(raw)) return void 0;
127189
+ const normalized = [];
127190
+ const usedIds = /* @__PURE__ */ new Set();
127191
+ for (const candidate of raw) {
127192
+ if (normalized.length >= MAX_WORKFLOW_REVIEW_FINDINGS || !candidate || typeof candidate !== "object") continue;
127193
+ const value = candidate;
127194
+ const title2 = boundedTrimmedString(value.title, MAX_FINDING_TITLE_LENGTH);
127195
+ const body = boundedTrimmedString(value.body, MAX_FINDING_BODY_LENGTH);
127196
+ if (!title2 || !body) continue;
127197
+ const baseId = boundedTrimmedString(value.id, MAX_FINDING_ID_LENGTH) || `finding-${normalized.length + 1}`;
127198
+ let id = baseId;
127199
+ let suffix = 2;
127200
+ while (usedIds.has(id)) id = `${baseId.slice(0, Math.max(1, MAX_FINDING_ID_LENGTH - String(suffix).length - 1))}-${suffix++}`;
127201
+ usedIds.add(id);
127202
+ const filePath = boundedTrimmedString(value.filePath, MAX_FINDING_PATH_LENGTH);
127203
+ const line2 = typeof value.line === "number" && Number.isFinite(value.line) && value.line > 0 ? Math.floor(value.line) : void 0;
127204
+ const severity = isWorkflowReviewFindingSeverity(value.severity) ? value.severity : void 0;
127205
+ normalized.push({ id, title: title2, body, ...filePath ? { filePath } : {}, ...line2 ? { line: line2 } : {}, ...severity ? { severity } : {} });
127206
+ }
127207
+ return normalized.length > 0 ? normalized : void 0;
127208
+ }
127209
+ function boundedTrimmedString(value, maxLength) {
127210
+ if (typeof value !== "string") return void 0;
127211
+ const trimmed = value.trim();
127212
+ return trimmed && trimmed.length <= maxLength ? trimmed : void 0;
127213
+ }
127214
+ function isWorkflowReviewFindingSeverity(value) {
127215
+ return typeof value === "string" && WORKFLOW_REVIEW_FINDING_SEVERITIES.includes(value);
127216
+ }
127163
127217
  function isTerminalFailure(result2) {
127164
127218
  return TERMINAL_FAILURE_STATUSES.has(result2.status);
127165
127219
  }
@@ -127225,10 +127279,16 @@ function makeReviewLeaseRecord(args) {
127225
127279
  leaseOwner: args.owner
127226
127280
  };
127227
127281
  }
127228
- var MAX_WORKFLOW_STEP_PRIOR_ATTEMPTS, TERMINAL_FAILURE_STATUSES, PLAN_REVIEW_LEASE_STALENESS_MS, TERMINAL_STEP_STATUSES;
127282
+ var WORKFLOW_REVIEW_FINDING_SEVERITIES, MAX_WORKFLOW_REVIEW_FINDINGS, MAX_FINDING_ID_LENGTH, MAX_FINDING_TITLE_LENGTH, MAX_FINDING_BODY_LENGTH, MAX_FINDING_PATH_LENGTH, MAX_WORKFLOW_STEP_PRIOR_ATTEMPTS, TERMINAL_FAILURE_STATUSES, PLAN_REVIEW_LEASE_STALENESS_MS, TERMINAL_STEP_STATUSES;
127229
127283
  var init_workflow_step_results = __esm({
127230
127284
  "../core/src/workflows/workflow-step-results.ts"() {
127231
127285
  "use strict";
127286
+ WORKFLOW_REVIEW_FINDING_SEVERITIES = ["low", "medium", "high", "critical"];
127287
+ MAX_WORKFLOW_REVIEW_FINDINGS = 20;
127288
+ MAX_FINDING_ID_LENGTH = 128;
127289
+ MAX_FINDING_TITLE_LENGTH = 240;
127290
+ MAX_FINDING_BODY_LENGTH = 4e3;
127291
+ MAX_FINDING_PATH_LENGTH = 1e3;
127232
127292
  MAX_WORKFLOW_STEP_PRIOR_ATTEMPTS = 5;
127233
127293
  TERMINAL_FAILURE_STATUSES = /* @__PURE__ */ new Set([
127234
127294
  "failed",
@@ -127297,6 +127357,7 @@ var init_task_timing = __esm({
127297
127357
  });
127298
127358
 
127299
127359
  // ../core/src/session-identity-registry.ts
127360
+ import { AsyncLocalStorage } from "node:async_hooks";
127300
127361
  import { resolve as resolve21 } from "node:path";
127301
127362
  import { realpathSync as realpathSync5 } from "node:fs";
127302
127363
  function getRegistry() {
@@ -127308,6 +127369,16 @@ function getRegistry() {
127308
127369
  }
127309
127370
  return registry3;
127310
127371
  }
127372
+ function getActiveInvocationStorage() {
127373
+ const holder = globalThis;
127374
+ const existing = holder[ACTIVE_INVOCATION_KEY];
127375
+ if (existing instanceof AsyncLocalStorage) {
127376
+ return existing;
127377
+ }
127378
+ const storage = new AsyncLocalStorage();
127379
+ holder[ACTIVE_INVOCATION_KEY] = storage;
127380
+ return storage;
127381
+ }
127311
127382
  function canonicalizeCwd(cwd) {
127312
127383
  const resolved = resolve21(cwd);
127313
127384
  try {
@@ -127335,8 +127406,13 @@ function registerFusionSessionIdentity(cwd, identity) {
127335
127406
  };
127336
127407
  }
127337
127408
  function resolveFusionSessionPrincipal(cwd) {
127409
+ const canonicalCwd = canonicalizeCwd(cwd);
127410
+ const activeInvocation = getActiveInvocationStorage().getStore();
127411
+ if (activeInvocation?.cwdKeys.has(canonicalCwd)) {
127412
+ return { kind: "agent", identity: activeInvocation.identity };
127413
+ }
127338
127414
  const registry3 = getRegistry();
127339
- const list = registry3.get(canonicalizeCwd(cwd));
127415
+ const list = registry3.get(canonicalCwd);
127340
127416
  if (!list || list.length === 0) {
127341
127417
  return { kind: "operator" };
127342
127418
  }
@@ -127345,14 +127421,24 @@ function resolveFusionSessionPrincipal(cwd) {
127345
127421
  }
127346
127422
  return { kind: "ambiguous", identities: [...list] };
127347
127423
  }
127424
+ function runWithFusionSessionIdentity(cwdKeys, identity, callback) {
127425
+ const invocation = {
127426
+ identity: { ...identity, registeredAt: Date.now() },
127427
+ cwdKeys: new Set(cwdKeys.map(canonicalizeCwd))
127428
+ };
127429
+ return getActiveInvocationStorage().run(invocation, callback);
127430
+ }
127348
127431
  function __clearFusionSessionIdentityRegistryForTests() {
127349
- globalThis[REGISTRY_KEY] = /* @__PURE__ */ new Map();
127432
+ const holder = globalThis;
127433
+ holder[REGISTRY_KEY] = /* @__PURE__ */ new Map();
127434
+ holder[ACTIVE_INVOCATION_KEY] = new AsyncLocalStorage();
127350
127435
  }
127351
- var REGISTRY_KEY;
127436
+ var REGISTRY_KEY, ACTIVE_INVOCATION_KEY;
127352
127437
  var init_session_identity_registry = __esm({
127353
127438
  "../core/src/session-identity-registry.ts"() {
127354
127439
  "use strict";
127355
127440
  REGISTRY_KEY = "__FUSION_SESSION_IDENTITY_REGISTRY_V1__";
127441
+ ACTIVE_INVOCATION_KEY = "__FUSION_SESSION_IDENTITY_ACTIVE_INVOCATION_V1__";
127356
127442
  }
127357
127443
  });
127358
127444
 
@@ -127715,6 +127801,7 @@ __export(src_exports, {
127715
127801
  MAX_TITLE_LENGTH: () => MAX_TITLE_LENGTH,
127716
127802
  MAX_TITLE_SUMMARIZE_INPUT_LENGTH: () => MAX_TITLE_SUMMARIZE_INPUT_LENGTH,
127717
127803
  MAX_WORKFLOW_ICON_LENGTH: () => MAX_WORKFLOW_ICON_LENGTH,
127804
+ MAX_WORKFLOW_REVIEW_FINDINGS: () => MAX_WORKFLOW_REVIEW_FINDINGS,
127718
127805
  MAX_WORKFLOW_STEP_PRIOR_ATTEMPTS: () => MAX_WORKFLOW_STEP_PRIOR_ATTEMPTS,
127719
127806
  MEMORY_AUDIT_PATH: () => MEMORY_AUDIT_PATH,
127720
127807
  MEMORY_BACKEND_SETTINGS_KEYS: () => MEMORY_BACKEND_SETTINGS_KEYS,
@@ -127916,6 +128003,7 @@ __export(src_exports, {
127916
128003
  WAKE_DELTA_ASSIGNED_TASKS_CAP: () => WAKE_DELTA_ASSIGNED_TASKS_CAP,
127917
128004
  WEDGE_RENOTIFY_COOLDOWN_MS: () => WEDGE_RENOTIFY_COOLDOWN_MS,
127918
128005
  WORKFLOW_EXTENSION_SCHEMA_VERSION: () => WORKFLOW_EXTENSION_SCHEMA_VERSION,
128006
+ WORKFLOW_REVIEW_FINDING_SEVERITIES: () => WORKFLOW_REVIEW_FINDING_SEVERITIES,
127919
128007
  WORKFLOW_SETTING_REJECTION_CODES: () => WORKFLOW_SETTING_REJECTION_CODES,
127920
128008
  WORKFLOW_SETTING_TYPES: () => WORKFLOW_SETTING_TYPES,
127921
128009
  WORKFLOW_WORK_ITEM_KINDS: () => WORKFLOW_WORK_ITEM_KINDS,
@@ -128321,6 +128409,7 @@ __export(src_exports, {
128321
128409
  hasRoleOverrides: () => hasRoleOverrides,
128322
128410
  hasSyncPassphraseConfigured: () => hasSyncPassphraseConfigured,
128323
128411
  hasTitleIdDrift: () => hasTitleIdDrift,
128412
+ hasUserAutoMergeHold: () => hasUserAutoMergeHold,
128324
128413
  hashWorkflowIr: () => hashWorkflowIr,
128325
128414
  healSchemaDrift: () => healSchemaDrift,
128326
128415
  importMcpServersJson: () => importMcpServersJson,
@@ -128443,6 +128532,7 @@ __export(src_exports, {
128443
128532
  isWipColumn: () => isWipColumn,
128444
128533
  isWipColumnRole: () => isWipColumnRole,
128445
128534
  isWorkflowOptionalGroupEnabled: () => isWorkflowOptionalGroupEnabled,
128535
+ isWorkflowReviewFindingSeverity: () => isWorkflowReviewFindingSeverity,
128446
128536
  isWorkspaceTask: () => isWorkspaceTask,
128447
128537
  layoutForIr: () => layoutForIr,
128448
128538
  listActiveAiSessions: () => listActiveAiSessions,
@@ -128516,6 +128606,7 @@ __export(src_exports, {
128516
128606
  normalizeValidationDiagnostics: () => normalizeValidationDiagnostics,
128517
128607
  normalizeWorkflowIcon: () => normalizeWorkflowIcon,
128518
128608
  normalizeWorkflowPromptOverrides: () => normalizeWorkflowPromptOverrides,
128609
+ normalizeWorkflowReviewFindings: () => normalizeWorkflowReviewFindings,
128519
128610
  notifyOperatorOfNonOperatorDelete: () => notifyOperatorOfNonOperatorDelete,
128520
128611
  overseerAdviceSeverityRank: () => overseerAdviceSeverityRank,
128521
128612
  parseAgentManifest: () => parseAgentManifest,
@@ -128779,6 +128870,7 @@ __export(src_exports, {
128779
128870
  runMemoryBackupCommand: () => runMemoryBackupCommand,
128780
128871
  runReconciliationAbort: () => runReconciliationAbort,
128781
128872
  runScheduledEvalBatch: () => runScheduledEvalBatch,
128873
+ runWithFusionSessionIdentity: () => runWithFusionSessionIdentity,
128782
128874
  sanitizeBranchSegment: () => sanitizeBranchSegment,
128783
128875
  sanitizeCliAgentSettings: () => sanitizeCliAgentSettings,
128784
128876
  sanitizeCliAgentsSettings: () => sanitizeCliAgentsSettings,
@@ -158170,6 +158262,7 @@ import { existsSync as existsSync43, readFileSync as readFileSync15, realpathSyn
158170
158262
  import { exec as exec2, execFile as execFile7 } from "node:child_process";
158171
158263
  import { promisify as promisify6 } from "node:util";
158172
158264
  import { createRequire as createRequire5 } from "node:module";
158265
+ import { randomUUID as randomUUID43 } from "node:crypto";
158173
158266
  import { basename as basename13, dirname as dirname21, join as join56, relative as relative6, isAbsolute as isAbsolute8, resolve as resolve24 } from "node:path";
158174
158267
  import {
158175
158268
  createAgentSession,
@@ -159880,7 +159973,8 @@ async function createFnAgent2(options) {
159880
159973
  });
159881
159974
  };
159882
159975
  const sessionIdentity = (() => {
159883
- const principalAgentId = options.actionGateContext?.agentId ?? options.permanentAgentGating?.requester?.actorId ?? "engine-session";
159976
+ const namedAgentId = options.actionGateContext?.agentId ?? options.permanentAgentGating?.requester?.actorId;
159977
+ const principalAgentId = namedAgentId ?? `engine-session-${randomUUID43()}`;
159884
159978
  const principalAgentName = options.actionGateContext?.agentName ?? options.permanentAgentGating?.requester?.actorName;
159885
159979
  return {
159886
159980
  agentId: principalAgentId,
@@ -159892,6 +159986,14 @@ async function createFnAgent2(options) {
159892
159986
  const sessionIdentityKeys = [...new Set([options.cwd, resolvedProjectRoot].filter((key) => Boolean(key)))];
159893
159987
  const attachSessionIdentity = (session) => {
159894
159988
  const identityDisposers = sessionIdentityKeys.map((key) => registerFusionSessionIdentity(key, sessionIdentity));
159989
+ const sessionInvocations = session;
159990
+ const wrapInvocation = (methodName) => {
159991
+ const original = sessionInvocations[methodName];
159992
+ if (typeof original !== "function") return;
159993
+ sessionInvocations[methodName] = (...args) => runWithFusionSessionIdentity(sessionIdentityKeys, sessionIdentity, () => original.apply(session, args));
159994
+ };
159995
+ wrapInvocation("prompt");
159996
+ wrapInvocation("promptWithFallback");
159895
159997
  const disposeBeforeIdentity = typeof session.dispose === "function" ? session.dispose.bind(session) : () => void 0;
159896
159998
  session.dispose = async () => {
159897
159999
  for (const disposeIdentity of identityDisposers) {
@@ -168091,10 +168193,11 @@ function createMergeAttemptHandler(deps) {
168091
168193
  );
168092
168194
  };
168093
168195
  }
168094
- function createMergeGateHandler() {
168196
+ function createMergeGateHandler(deps = {}) {
168095
168197
  return async (_node, ctx) => {
168096
168198
  const settingsAutoMerge = ctx.settings?.autoMerge;
168097
- const autoMerge = ctx.task.autoMerge !== false && settingsAutoMerge !== false;
168199
+ const settings = { autoMerge: settingsAutoMerge ?? true };
168200
+ const autoMerge = !hasUserAutoMergeHold(ctx.task) && (await deps.isLiveSharedBranchMember?.(ctx.task, settings) === true || ctx.task.autoMerge !== false && settings.autoMerge !== false);
168098
168201
  return {
168099
168202
  outcome: "success",
168100
168203
  value: autoMerge ? "auto-on" : "auto-off"
@@ -168104,6 +168207,7 @@ function createMergeGateHandler() {
168104
168207
  var init_merge_runner = __esm({
168105
168208
  "../engine/src/workflow-node-runners/merge-runner.ts"() {
168106
168209
  "use strict";
168210
+ init_src2();
168107
168211
  init_workflow_merge_nodes();
168108
168212
  }
168109
168213
  });
@@ -168447,7 +168551,7 @@ function createDefaultNodeHandlers(seams, runCustomNode, deps) {
168447
168551
  "parse-steps": parseSteps,
168448
168552
  code: createCodeNodeHandler(deps?.runCode),
168449
168553
  notify: createNotifyHandler(deps?.notifyDispatch),
168450
- "merge-gate": createMergeGateHandler(),
168554
+ "merge-gate": createMergeGateHandler({ isLiveSharedBranchMember: deps?.isLiveSharedBranchMember }),
168451
168555
  "merge-attempt": createMergeAttemptHandler({
168452
168556
  primitives: deps?.primitives,
168453
168557
  seams,
@@ -168845,7 +168949,7 @@ var init_code_node_runner = __esm({
168845
168949
  // ../engine/src/agent-tools.ts
168846
168950
  import { appendFile as appendFile3, mkdir as mkdir26, readFile as readFile32, readdir as readdir9, realpath, stat as stat9, writeFile as writeFile24 } from "node:fs/promises";
168847
168951
  import { existsSync as existsSync50 } from "node:fs";
168848
- import { createHash as createHash19, randomUUID as randomUUID43 } from "node:crypto";
168952
+ import { createHash as createHash19, randomUUID as randomUUID44 } from "node:crypto";
168849
168953
  import { tmpdir as tmpdir5 } from "node:os";
168850
168954
  import { extname as extname3, isAbsolute as isAbsolute12, join as join62, relative as relative11, resolve as resolve33, sep as sep9 } from "node:path";
168851
168955
  import { Type as Type2 } from "@earendil-works/pi-ai";
@@ -169491,7 +169595,7 @@ function createTaskCreateTool(store, provenance, options) {
169491
169595
  toType: "user",
169492
169596
  type: "agent-to-user",
169493
169597
  content: `Task proposal awaiting validation: ${title2}`,
169494
- metadata: { kind: "task-proposal", proposalStatus: "pending", proposalIdempotencyKey: randomUUID43(), taskId: options.sourceTaskId, proposedTask: { title: title2, description: params.description, priority: params.priority, workflowId: params.workflow_id, dependencies: params.dependencies } }
169598
+ metadata: { kind: "task-proposal", proposalStatus: "pending", proposalIdempotencyKey: randomUUID44(), taskId: options.sourceTaskId, proposedTask: { title: title2, description: params.description, priority: params.priority, workflowId: params.workflow_id, dependencies: params.dependencies } }
169495
169599
  });
169496
169600
  return { content: [{ type: "text", text: "Task proposal submitted to the operator for validation; no task was created." }], details: { proposed: true } };
169497
169601
  }
@@ -173916,37 +174020,6 @@ var init_effective_settings = __esm({
173916
174020
  }
173917
174021
  });
173918
174022
 
173919
- // ../engine/src/errors/transient-merge-error-classifier.ts
173920
- function classifyTransientMergeError(error3) {
173921
- if (!error3) return null;
173922
- if (/lease-handoff-failed[^a-z]+target-not-queued/i.test(error3)) {
173923
- return "lease-handoff-target-not-queued";
173924
- }
173925
- if (/\bACP turn failed\b/i.test(error3) || /\bacp rpc code -32(?:603|00[0-3])\b/i.test(error3)) {
173926
- return "ai-provider-turn-failure";
173927
- }
173928
- if (/\bspawn(?:\s+\S+)?\s+ENO(?:TDIR|ENT)\b/i.test(error3)) {
173929
- return "process-spawn-failure";
173930
- }
173931
- if (/\bis not a working tree\b/i.test(error3)) {
173932
- return "process-spawn-failure";
173933
- }
173934
- const sameSha = error3.match(/advanced concurrently \(expected ([0-9a-f]{7,40}),\s+observed ([0-9a-f]{7,40})\)/i);
173935
- if (sameSha && sameSha[1].toLowerCase() === sameSha[2].toLowerCase()) {
173936
- return "spurious-concurrent-advance-same-sha";
173937
- }
173938
- if (isTransientError(error3)) {
173939
- return "network-transport-failure";
173940
- }
173941
- return null;
173942
- }
173943
- var init_transient_merge_error_classifier = __esm({
173944
- "../engine/src/errors/transient-merge-error-classifier.ts"() {
173945
- "use strict";
173946
- init_transient_error_patterns();
173947
- }
173948
- });
173949
-
173950
174023
  // ../engine/src/notification/ntfy-provider.ts
173951
174024
  function resolveParticipantLabel(metadata, kind) {
173952
174025
  const nameKey = kind === "from" ? "fromName" : "toName";
@@ -174420,7 +174493,50 @@ var init_webhook_provider = __esm({
174420
174493
  }
174421
174494
  });
174422
174495
 
174496
+ // ../engine/src/errors/transient-merge-error-classifier.ts
174497
+ function classifyTransientMergeError(error3) {
174498
+ if (!error3) return null;
174499
+ if (/lease-handoff-failed[^a-z]+target-not-queued/i.test(error3)) {
174500
+ return "lease-handoff-target-not-queued";
174501
+ }
174502
+ if (/\bACP turn failed\b/i.test(error3) || /\bacp rpc code -32(?:603|00[0-3])\b/i.test(error3)) {
174503
+ return "ai-provider-turn-failure";
174504
+ }
174505
+ if (/\bspawn(?:\s+\S+)?\s+ENO(?:TDIR|ENT)\b/i.test(error3)) {
174506
+ return "process-spawn-failure";
174507
+ }
174508
+ if (/\bis not a working tree\b/i.test(error3)) {
174509
+ return "process-spawn-failure";
174510
+ }
174511
+ const sameSha = error3.match(/advanced concurrently \(expected ([0-9a-f]{7,40}),\s+observed ([0-9a-f]{7,40})\)/i);
174512
+ if (sameSha && sameSha[1].toLowerCase() === sameSha[2].toLowerCase()) {
174513
+ return "spurious-concurrent-advance-same-sha";
174514
+ }
174515
+ if (isTransientError(error3)) {
174516
+ return "network-transport-failure";
174517
+ }
174518
+ return null;
174519
+ }
174520
+ function hasTransientMergeRecoveryOwner(task) {
174521
+ return classifyTransientMergeError(task.error) !== null && typeof task.mergeTransientRetryCount === "number" && Number.isInteger(task.mergeTransientRetryCount) && task.mergeTransientRetryCount >= 0 && task.mergeTransientRetryCount < MAX_AUTO_MERGE_TRANSIENT_RETRIES;
174522
+ }
174523
+ var MAX_AUTO_MERGE_TRANSIENT_RETRIES;
174524
+ var init_transient_merge_error_classifier = __esm({
174525
+ "../engine/src/errors/transient-merge-error-classifier.ts"() {
174526
+ "use strict";
174527
+ init_transient_error_patterns();
174528
+ MAX_AUTO_MERGE_TRANSIENT_RETRIES = 5;
174529
+ }
174530
+ });
174531
+
174423
174532
  // ../engine/src/notification/task-wedge-notification.ts
174533
+ function describeTaskRecoveryOwner(task) {
174534
+ if (hasTransientMergeRecoveryOwner(task)) return { kind: "transient-merge-retry" };
174535
+ if (typeof task.recoveryRetryCount === "number" && Number.isInteger(task.recoveryRetryCount) && task.recoveryRetryCount >= 0 && typeof task.nextRecoveryAt === "string" && Number.isFinite(Date.parse(task.nextRecoveryAt))) {
174536
+ return { kind: "scheduled-recovery" };
174537
+ }
174538
+ return null;
174539
+ }
174424
174540
  function describeSelfHealingNoActionWedge(task, stage, metadata) {
174425
174541
  const description = SELF_HEALING_NO_ACTIONS[stage];
174426
174542
  if (!description || task.userPaused || task.paused || task.autoMerge === false) return null;
@@ -174478,6 +174594,7 @@ function describeTaskWedge(task) {
174478
174594
  if (/merge.*(?:blocked|verification|gate)|(?:verification|gate).*(?:failed|exhaust)/i.test(error3)) {
174479
174595
  return { reasonKey: "merge-blocked", reason: "Merge verification cannot progress without operator action.", action: "Fix the failing verification, then retry the task." };
174480
174596
  }
174597
+ if (describeTaskRecoveryOwner(task)) return null;
174481
174598
  if (!error3 && (task.mergeRetries ?? 0) < 3) return null;
174482
174599
  return {
174483
174600
  reasonKey: "terminal-failed",
@@ -174489,6 +174606,7 @@ var SELF_HEALING_NO_ACTIONS;
174489
174606
  var init_task_wedge_notification = __esm({
174490
174607
  "../engine/src/notification/task-wedge-notification.ts"() {
174491
174608
  "use strict";
174609
+ init_transient_merge_error_classifier();
174492
174610
  SELF_HEALING_NO_ACTIONS = {
174493
174611
  "reclaim-pr-conflict": {
174494
174612
  reason: "Self-healing could not reclaim a stalled pull-request conflict.",
@@ -174554,7 +174672,6 @@ var init_notification_service = __esm({
174554
174672
  init_src2();
174555
174673
  init_notifier();
174556
174674
  init_logger3();
174557
- init_transient_merge_error_classifier();
174558
174675
  init_ntfy_provider();
174559
174676
  init_webhook_provider();
174560
174677
  init_task_wedge_notification();
@@ -174780,10 +174897,10 @@ var init_notification_service = __esm({
174780
174897
  }
174781
174898
  }
174782
174899
  handleTaskUpdated = (task, meta2) => {
174783
- const transientFailure = task.status === "failed" ? classifyTransientMergeError(task.error) : null;
174784
- const wedge = transientFailure ? null : describeTaskWedge(task);
174900
+ const recoveryOwner = task.status === "failed" ? describeTaskRecoveryOwner(task) : null;
174901
+ const wedge = describeTaskWedge(task);
174785
174902
  if (wedge) this.cancelPendingFailureNotification(task.id, "classified-terminal-wedge");
174786
- if (!transientFailure) void this.enqueueWedgeHandling(task.id, () => this.maybeNotifyTaskWedge(task, wedge));
174903
+ void this.enqueueWedgeHandling(task.id, () => this.maybeNotifyTaskWedge(task, wedge));
174787
174904
  void this.maybeSuppressTransientFailedNotification(task, `status=${task.status ?? "undefined"}`);
174788
174905
  if (task.status === "awaiting-approval") {
174789
174906
  void this.writeAwaitingApprovalMailboxMessage(task);
@@ -174794,17 +174911,12 @@ var init_notification_service = __esm({
174794
174911
  if (!this.notificationsEnabled) {
174795
174912
  return;
174796
174913
  }
174797
- if (task.status === "failed" && !wedge) {
174798
- const transientClass = classifyTransientMergeError(task.error);
174799
- if (transientClass) {
174800
- this.failureNotificationSuppressedCount += 1;
174801
- schedulerLog.debug(
174802
- `[notify] ${task.id} transient merge failure (${transientClass}) \u2014 suppressed notification (self-heal in flight)`
174803
- );
174804
- return;
174805
- }
174914
+ if (task.status === "failed" && recoveryOwner) {
174915
+ this.failureNotificationSuppressedCount += 1;
174916
+ schedulerLog.debug(`[notify] ${task.id} recovery-owned failure \u2014 suppressed notification`);
174917
+ } else if (task.status === "failed" && !wedge) {
174806
174918
  if (this.failureNotificationMode === "all") {
174807
- this.maybeNotify(task.id, "failed", this.createTaskPayload(task, "failed"));
174919
+ void this.maybeNotifyImmediateFailure(task);
174808
174920
  } else {
174809
174921
  this.scheduleFailureNotification(task);
174810
174922
  }
@@ -174891,7 +175003,17 @@ var init_notification_service = __esm({
174891
175003
  await this.enqueueWedgeHandling(task.id, () => this.maybeNotifyTaskWedge(task, descriptor));
174892
175004
  }
174893
175005
  async maybeNotifyTaskWedge(task, suppliedDescriptor) {
174894
- const descriptor = suppliedDescriptor ?? describeTaskWedge(task);
175006
+ const liveTask = this.store.getTask ? await this.store.getTask(task.id) ?? task : task;
175007
+ const recoveryOwner = describeTaskRecoveryOwner(liveTask);
175008
+ if (recoveryOwner) {
175009
+ if (this.activeWedgeReasons.has(task.id) || liveTask.wedgeNotification?.status === "active") {
175010
+ this.activeWedgeReasons.delete(task.id);
175011
+ await this.store.claimTaskWedgeNotificationEpisode?.(task.id, null);
175012
+ }
175013
+ return;
175014
+ }
175015
+ const descriptor = suppliedDescriptor ?? describeTaskWedge(liveTask);
175016
+ task = liveTask;
174895
175017
  let episode;
174896
175018
  if (!descriptor) {
174897
175019
  const isActiveSelfHealingNoAction = task.wedgeNotification?.status === "active" && task.wedgeNotification.reasonKey.startsWith("self-healing-no-action:");
@@ -175258,6 +175380,14 @@ var init_notification_service = __esm({
175258
175380
  this.failureNotificationDelayMs = typeof settings.failureNotificationDelayMs === "number" && settings.failureNotificationDelayMs >= 0 ? settings.failureNotificationDelayMs : this.failedNotificationGraceMs;
175259
175381
  this.failureNotificationMode = settings.failureNotificationMode ?? "sticky-only";
175260
175382
  }
175383
+ async maybeNotifyImmediateFailure(task) {
175384
+ const liveTask = this.store.getTask ? await this.store.getTask(task.id) ?? task : task;
175385
+ if (liveTask.status !== "failed" || describeTaskRecoveryOwner(liveTask) || describeTaskWedge(liveTask)) {
175386
+ this.failureNotificationSuppressedCount += 1;
175387
+ return;
175388
+ }
175389
+ this.maybeNotify(liveTask.id, "failed", this.createTaskPayload(liveTask, "failed"));
175390
+ }
175261
175391
  scheduleFailureNotification(task) {
175262
175392
  if (this.pendingFailureNotifications.has(task.id)) {
175263
175393
  return;
@@ -175314,12 +175444,9 @@ var init_notification_service = __esm({
175314
175444
  schedulerLog.debug(`[notify] ${taskId} no longer failed at dispatch time \u2014 suppressed notification`);
175315
175445
  return;
175316
175446
  }
175317
- const transientClassAtDispatch = classifyTransientMergeError(task.error);
175318
- if (transientClassAtDispatch) {
175447
+ if (describeTaskRecoveryOwner(task)) {
175319
175448
  this.failureNotificationSuppressedCount += 1;
175320
- schedulerLog.debug(
175321
- `[notify] ${taskId} transient merge failure (${transientClassAtDispatch}) at dispatch time \u2014 suppressed notification (self-heal in flight)`
175322
- );
175449
+ schedulerLog.debug(`[notify] ${taskId} recovery-owned failure at dispatch time \u2014 suppressed notification`);
175323
175450
  return;
175324
175451
  }
175325
175452
  if (describeTaskWedge(task)) {
@@ -186974,10 +187101,21 @@ var init_scheduler = __esm({
186974
187101
  const unmetDeps = getUnmetSchedulingDependencies(task, tasks2, schedulingDependencyOptions);
186975
187102
  if (unmetDeps.length > 0) {
186976
187103
  const normalizedUnmetDeps = [...new Set(unmetDeps)].sort();
187104
+ let activeOverlapBlockedBy = null;
187105
+ if (activeScopes.size > 0) {
187106
+ try {
187107
+ const taskScope = await getFilteredFileScope(task.id);
187108
+ if (taskScope.length > 0 && !isCoordinationOnlyTask(task, taskScope)) {
187109
+ activeOverlapBlockedBy = Array.from(activeScopes.entries()).sort(([aId], [bId]) => aId.localeCompare(bId)).find(([, activeScope]) => this.pathsOverlap(taskScope, activeScope))?.[0] ?? null;
187110
+ }
187111
+ } catch (error3) {
187112
+ schedulerLog.warn(`Failed to refresh file-scope overlap blocker for dependency-blocked task ${task.id}`, error3);
187113
+ }
187114
+ }
186977
187115
  await this.transitionQueuedEpisode(task, {
186978
187116
  signature: `dependency:${normalizedUnmetDeps.join(",")}`,
186979
187117
  blockedBy: unmetDeps[0] ?? null,
186980
- overlapBlockedBy: task.overlapBlockedBy ?? null,
187118
+ overlapBlockedBy: activeOverlapBlockedBy,
186981
187119
  action: `queued \u2014 unmet dependencies: ${unmetDeps.join(", ")}`
186982
187120
  });
186983
187121
  this.options.onBlocked?.(task, unmetDeps);
@@ -203034,16 +203172,29 @@ var init_self_healing = __esm({
203034
203172
  activeWork: await this.resolveActiveWorkColumnsFor(taskId, cache7)
203035
203173
  };
203036
203174
  }
203037
- classifyPausedAbortWorkflowRecovery(task, settings, isExecuting, columns) {
203175
+ /*
203176
+ FNXC:SharedBranchMemberHold 2026-08-05-23:35:
203177
+ Pause-abort recovery must resolve the same live intermediate-group predicate as
203178
+ merge admission. Shared metadata alone is stale after group closure or a
203179
+ default-branch collision, so it cannot suppress that task's standalone hold.
203180
+ */
203181
+ async isLiveSharedMemberIntegration(task, settings) {
203182
+ const groupId = task.branchContext?.groupId?.trim();
203183
+ const group = groupId ? await this.store.getBranchGroup?.(groupId) : null;
203184
+ const defaultBranch = await resolveIntegrationBranch(this.options.rootDir, settings);
203185
+ return isLiveSharedBranchGroupMemberIntegration(task, group, defaultBranch);
203186
+ }
203187
+ async classifyPausedAbortWorkflowRecovery(task, settings, isExecuting, columns) {
203038
203188
  const isPausedAbortPark = task.status === "failed" && typeof task.error === "string" && task.error.includes(PAUSE_ABORT_PARK_OPERATOR_MARKER) && task.error.includes(PAUSE_ABORT_PARK_ERROR_MARKER);
203039
203189
  if (!isPausedAbortPark) return { kind: "no-action", reason: "not-pause-abort" };
203040
203190
  if (task.paused || task.userPaused || isExecuting) return { kind: "no-action", reason: "unsafe-or-not-routable" };
203041
203191
  const errorText = typeof task.error === "string" ? task.error.toLowerCase() : "";
203042
203192
  const isTerminalMergePark = errorText.includes("conflict") || errorText.includes("contamination") || errorText.includes("foreign") || errorText.includes("retry-exhausted") || errorText.includes("retries exhausted") || errorText.includes("max retries");
203043
203193
  const completedSteps = task.steps.length > 0 && task.steps.every((step) => step.status === "done" || step.status === "skipped");
203044
- const sharedBranchMember = isSharedBranchGroupMemberIntegration(task);
203045
- const hasReviewProgress = columns.review.has(task.column) && allowsAutoMergeProcessing(task, settings) && task.mergeDetails?.mergeConfirmed !== true && !isTerminalMergePark && completedSteps;
203046
- const hasManualMergeHoldProgress = columns.review.has(task.column) && (!allowsAutoMergeProcessing(task, settings) || resolveEffectiveAutoMerge(task, settings) === false) && !sharedBranchMember && task.mergeDetails?.mergeConfirmed !== true && !isTerminalMergePark && completedSteps;
203194
+ const liveSharedBranchMember = await this.isLiveSharedMemberIntegration(task, settings);
203195
+ const autoMergeProcessing = allowsAutoMergeProcessing(task, settings) || liveSharedBranchMember;
203196
+ const hasReviewProgress = columns.review.has(task.column) && autoMergeProcessing && task.mergeDetails?.mergeConfirmed !== true && !isTerminalMergePark && completedSteps;
203197
+ const hasManualMergeHoldProgress = columns.review.has(task.column) && (hasUserAutoMergeHold(task) || !allowsAutoMergeProcessing(task, settings) || resolveEffectiveAutoMerge(task, settings) === false) && (!liveSharedBranchMember || hasUserAutoMergeHold(task)) && task.mergeDetails?.mergeConfirmed !== true && !isTerminalMergePark && completedSteps;
203047
203198
  if (hasManualMergeHoldProgress) {
203048
203199
  return { kind: "work-item-resume", reason: "pause-abort-manual-merge-hold" };
203049
203200
  }
@@ -208711,8 +208862,23 @@ var init_self_healing = __esm({
208711
208862
  return own.length > 0 ? new Set(own) : /* @__PURE__ */ new Set(["in-review"]);
208712
208863
  };
208713
208864
  const executingIds = this.options.getExecutingTaskIds?.() ?? /* @__PURE__ */ new Set();
208865
+ const canRecoverMergeableReviewTask = async (task) => {
208866
+ if (hasUserAutoMergeHold(task)) return false;
208867
+ const groupId = task.branchContext?.groupId?.trim();
208868
+ const branchGroup = groupId ? await this.store.getBranchGroup(groupId) : null;
208869
+ const projectDefaultBranch = await resolveIntegrationBranch(this.options.rootDir, settings);
208870
+ if (isLiveSharedBranchGroupMemberIntegration(task, branchGroup, projectDefaultBranch)) {
208871
+ return true;
208872
+ }
208873
+ return allowsAutoMergeProcessing(task, settings) && resolveEffectiveAutoMerge(task, settings) !== false;
208874
+ };
208875
+ const mergeAdmission = await Promise.all(tasks2.map(async (task) => [
208876
+ task.id,
208877
+ await canRecoverMergeableReviewTask(task)
208878
+ ]));
208879
+ const mergeAdmissionByTaskId = new Map(mergeAdmission);
208714
208880
  const mergeable = tasks2.filter(
208715
- (t) => allowsAutoMergeProcessing(t, settings) && !t.paused && !executingIds.has(t.id) && t.status !== "failed" && // Exclude transient merge statuses. Active merges should be left alone;
208881
+ (t) => mergeAdmissionByTaskId.get(t.id) === true && !t.paused && !executingIds.has(t.id) && t.status !== "failed" && // Exclude transient merge statuses. Active merges should be left alone;
208716
208882
  // stale ones are handled by recoverStaleMergingStatus().
208717
208883
  t.status !== "merging" && t.status !== "merging-pr" && // FNXC:Workspace 2026-06-22-09:30 (Phase D U1, KTD1 — admit workspace tasks):
208718
208884
  // A workspace task has task.worktree===null (its worktrees live per-repo in
@@ -211759,7 +211925,7 @@ var init_self_healing = __esm({
211759
211925
  for (const t of tasks2) {
211760
211926
  if (!this.isPauseAbortParkCandidate(t)) continue;
211761
211927
  const columns = await this.resolvePauseAbortColumnsFor(t.id, columnCache);
211762
- if (this.classifyPausedAbortWorkflowRecovery(t, settings, executingIds.has(t.id), columns).kind !== "no-action") {
211928
+ if ((await this.classifyPausedAbortWorkflowRecovery(t, settings, executingIds.has(t.id), columns)).kind !== "no-action") {
211763
211929
  parked.push(t);
211764
211930
  }
211765
211931
  }
@@ -211776,7 +211942,7 @@ var init_self_healing = __esm({
211776
211942
  continue;
211777
211943
  }
211778
211944
  const freshColumns = await this.resolvePauseAbortColumnsFor(fresh.id, columnCache);
211779
- const route = this.classifyPausedAbortWorkflowRecovery(fresh, settings, latestExecutingIds.has(fresh.id), freshColumns);
211945
+ const route = await this.classifyPausedAbortWorkflowRecovery(fresh, settings, latestExecutingIds.has(fresh.id), freshColumns);
211780
211946
  if (route.kind === "no-action") {
211781
211947
  continue;
211782
211948
  }
@@ -218779,7 +218945,7 @@ function recoverPassedPlanReviewFromLatestLog(task) {
218779
218945
  completedAt: latest.timestamp
218780
218946
  };
218781
218947
  }
218782
- var PLAN_REVIEW_PROVIDER_FAILURE_HOLD_VALUE, PLAN_REVIEW_LEASE_HELD_VALUE, SESSION_CONTENTION_HOLD_VALUE, WORKFLOW_INTERRUPTED_NODE_ID_CONTEXT_KEY, WORKFLOW_INTERRUPTED_NODE_ABORT_KIND_CONTEXT_KEY, WORKFLOW_OPTIONAL_GROUP_CONTEXT_KEY, WORKFLOW_NODE_ENGINE_PAUSE_ABORT_KIND, WORKFLOW_DRIFT_PARK_CONTEXT_KEY, WorkflowGraphSuspended, MERGE_REGION_KINDS, WorkflowGraphExecutor;
218948
+ 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;
218783
218949
  var init_workflow_graph_executor = __esm({
218784
218950
  "../engine/src/workflows/workflow-graph-executor.ts"() {
218785
218951
  "use strict";
@@ -218799,6 +218965,7 @@ var init_workflow_graph_executor = __esm({
218799
218965
  WORKFLOW_INTERRUPTED_NODE_ID_CONTEXT_KEY = "workflow:interruptedNodeId";
218800
218966
  WORKFLOW_INTERRUPTED_NODE_ABORT_KIND_CONTEXT_KEY = "workflow:interruptedNodeAbortKind";
218801
218967
  WORKFLOW_OPTIONAL_GROUP_CONTEXT_KEY = "workflow:optionalGroupActive";
218968
+ WORKFLOW_REVIEW_KIND_CONTEXT_KEY = "workflow:reviewKind";
218802
218969
  WORKFLOW_NODE_ENGINE_PAUSE_ABORT_KIND = "engine-pause";
218803
218970
  WORKFLOW_DRIFT_PARK_CONTEXT_KEY = "workflow:driftPark";
218804
218971
  WorkflowGraphSuspended = class extends Error {
@@ -218826,7 +218993,8 @@ var init_workflow_graph_executor = __esm({
218826
218993
  parseSteps: deps.parseStepsDeps,
218827
218994
  runCode: deps.runCode,
218828
218995
  notifyDispatch: deps.notifyDispatch,
218829
- prNodes: deps.prNodes
218996
+ prNodes: deps.prNodes,
218997
+ isLiveSharedBranchMember: deps.isLiveSharedBranchMember
218830
218998
  }),
218831
218999
  ...deps.runnerRegistry?.toHandlers() ?? {},
218832
219000
  ...deps.handlers ?? {}
@@ -219047,6 +219215,7 @@ var init_workflow_graph_executor = __esm({
219047
219215
  phase: stepPhase,
219048
219216
  status: "pending",
219049
219217
  source: "optional-group",
219218
+ ...this.workflowReviewKind(node) ? { reviewKind: this.workflowReviewKind(node) } : {},
219050
219219
  startedAt: stepStartedAt,
219051
219220
  // U3/KTD-4: stamp the lease owner so a concurrent/crashed re-entry
219052
219221
  // adopts this pending gate instead of dispatching a second reviewer.
@@ -219066,7 +219235,8 @@ var init_workflow_graph_executor = __esm({
219066
219235
  runTemplateNode: (tNode, sig, contextOverride) => {
219067
219236
  const optionalGroupContext = {
219068
219237
  ...contextOverride ?? context,
219069
- [WORKFLOW_OPTIONAL_GROUP_CONTEXT_KEY]: node.id
219238
+ [WORKFLOW_OPTIONAL_GROUP_CONTEXT_KEY]: node.id,
219239
+ ...this.workflowReviewKind(node) ? { [WORKFLOW_REVIEW_KIND_CONTEXT_KEY]: this.workflowReviewKind(node) } : {}
219070
219240
  };
219071
219241
  return this.executeNodeWithRetries(tNode, task, settings, optionalGroupContext, ir2, sig, false);
219072
219242
  },
@@ -219084,6 +219254,7 @@ var init_workflow_graph_executor = __esm({
219084
219254
  const exitContextPatch = exitResult?.contextPatch;
219085
219255
  let stepOutput = typeof exitContextPatch?.output === "string" ? exitContextPatch.output : void 0;
219086
219256
  const stepNotes = typeof exitContextPatch?.notes === "string" ? exitContextPatch.notes : void 0;
219257
+ const stepFindings = this.workflowReviewKind(node) && Array.isArray(exitContextPatch?.findings) ? exitContextPatch.findings : void 0;
219087
219258
  if (stepStatus === "failed" && !verdict && stepOutput === void 0 && stepNotes === void 0) {
219088
219259
  const lastVisited = groupResult.visitedNodeIds[groupResult.visitedNodeIds.length - 1];
219089
219260
  const templateNodeId = lastVisited?.includes("::") ? lastVisited.slice(lastVisited.indexOf("::") + 2) : void 0;
@@ -219101,9 +219272,11 @@ var init_workflow_graph_executor = __esm({
219101
219272
  phase: stepPhase,
219102
219273
  source: "optional-group",
219103
219274
  status: stepStatus,
219275
+ ...this.workflowReviewKind(node) ? { reviewKind: this.workflowReviewKind(node) } : {},
219104
219276
  ...verdict ? { verdict } : {},
219105
219277
  ...stepOutput !== void 0 ? { output: stepOutput } : {},
219106
219278
  ...stepNotes !== void 0 ? { notes: stepNotes } : {},
219279
+ ...stepFindings?.length ? { findings: stepFindings } : {},
219107
219280
  startedAt: stepStartedAt,
219108
219281
  completedAt: (/* @__PURE__ */ new Date()).toISOString()
219109
219282
  });
@@ -219563,9 +219736,15 @@ var init_workflow_graph_executor = __esm({
219563
219736
  }
219564
219737
  return failureResult;
219565
219738
  }
219739
+ /** FNXC:WorkflowReviewKind 2026-08-05-02:31: Persist only the declared closed
219740
+ * marker; execution never derives review semantics from a label, verdict, or gate mode. */
219741
+ workflowReviewKind(node) {
219742
+ return node.config?.reviewKind === "plan" || node.config?.reviewKind === "code" ? node.config.reviewKind : void 0;
219743
+ }
219566
219744
  shouldRecordNodeProgress(node) {
219567
219745
  const skillName = typeof node.config?.skillName === "string" ? node.config.skillName.trim() : "";
219568
- return skillName.length > 0 && (node.kind === "prompt" || node.kind === "gate");
219746
+ const markedReview = this.workflowReviewKind(node) !== void 0;
219747
+ return markedReview && (node.kind === "prompt" || node.kind === "gate" || node.kind === "script") || skillName.length > 0 && (node.kind === "prompt" || node.kind === "gate");
219569
219748
  }
219570
219749
  workflowNodeProgressName(node) {
219571
219750
  const configuredName = typeof node.config?.name === "string" ? node.config.name.trim() : "";
@@ -219579,6 +219758,7 @@ var init_workflow_graph_executor = __esm({
219579
219758
  phase: node.config?.phase === "post-merge" ? "post-merge" : "pre-merge",
219580
219759
  source: "node",
219581
219760
  status: "pending",
219761
+ ...this.workflowReviewKind(node) ? { reviewKind: this.workflowReviewKind(node) } : {},
219582
219762
  startedAt
219583
219763
  };
219584
219764
  await this.recordOptionalGroupStepResult(taskId, result2);
@@ -219589,6 +219769,7 @@ var init_workflow_graph_executor = __esm({
219589
219769
  const contextPatch = nodeResult.contextPatch ?? {};
219590
219770
  let output = typeof contextPatch.output === "string" ? contextPatch.output : void 0;
219591
219771
  const notes = typeof contextPatch.notes === "string" ? contextPatch.notes : void 0;
219772
+ const findings = this.workflowReviewKind(node) && Array.isArray(contextPatch.findings) ? contextPatch.findings : void 0;
219592
219773
  if (status === "failed" && output === void 0 && notes === void 0) {
219593
219774
  output = this.synthesizeNonVerdictFailureOutput({
219594
219775
  stepLabel: this.workflowNodeProgressName(node),
@@ -219603,8 +219784,10 @@ var init_workflow_graph_executor = __esm({
219603
219784
  phase: started?.phase ?? (node.config?.phase === "post-merge" ? "post-merge" : "pre-merge"),
219604
219785
  source: "node",
219605
219786
  status,
219787
+ ...this.workflowReviewKind(node) ? { reviewKind: this.workflowReviewKind(node) } : {},
219606
219788
  ...output !== void 0 ? { output } : {},
219607
219789
  ...notes !== void 0 ? { notes } : {},
219790
+ ...findings?.length ? { findings } : {},
219608
219791
  startedAt: started?.startedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
219609
219792
  completedAt: (/* @__PURE__ */ new Date()).toISOString()
219610
219793
  });
@@ -220047,6 +220230,10 @@ var init_workflow_graph_task_runner = __esm({
220047
220230
  runCode: this.deps.runCode,
220048
220231
  notifyDispatch: this.deps.notifyDispatch,
220049
220232
  prNodes: this.deps.prNodes,
220233
+ // FNXC:SharedBranchMemberHold 2026-08-06-00:24: carry the executor's
220234
+ // live group resolver into the graph merge gate; omitting it turns
220235
+ // mission-policy members into manual holds before integration.
220236
+ isLiveSharedBranchMember: this.deps.isLiveSharedBranchMember,
220050
220237
  /*
220051
220238
  FNXC:WorkflowResume 2026-06-29-08:49:
220052
220239
  Production graph runs must fetch live task steps during foreach replay. The runner is the workflow boundary that has store access, so it supplies the fresh projection seam instead of making executor self-healing guess after a stale step node fails.
@@ -224885,9 +225072,11 @@ function parseWorkflowStepVerdict(rawOutput) {
224885
225072
  verdict = "REVISE";
224886
225073
  }
224887
225074
  if (!verdict) continue;
225075
+ const findings = normalizeWorkflowReviewFindings(parsed.findings);
224888
225076
  return {
224889
225077
  verdict,
224890
- notes: typeof parsed.notes === "string" ? parsed.notes : ""
225078
+ notes: typeof parsed.notes === "string" ? parsed.notes : "",
225079
+ ...findings ? { findings } : {}
224891
225080
  };
224892
225081
  } catch {
224893
225082
  }
@@ -224919,7 +225108,8 @@ function parseWorkflowStepOutput(rawOutput, options = {}) {
224919
225108
  return {
224920
225109
  output: parsed.notes || "",
224921
225110
  verdict: parsed.verdict,
224922
- notes: parsed.notes
225111
+ notes: parsed.notes,
225112
+ ...parsed.findings ? { findings: parsed.findings } : {}
224923
225113
  };
224924
225114
  }
224925
225115
  const inferred = inferWorkflowStepVerdictFromProse(trimmed);
@@ -228156,6 +228346,7 @@ ${feedback}`,
228156
228346
  if (info.phase !== "pre-merge") return false;
228157
228347
  if (info.status !== "advisory_failure" && info.status !== "failed") return false;
228158
228348
  const liveTask = await this.store.getTask(taskId).catch(() => fallbackTask);
228349
+ if (hasUserAutoMergeHold(liveTask)) return false;
228159
228350
  const missingArtifactKeys = parseRequiredArtifactMissingValue(info.failureValue);
228160
228351
  if (missingArtifactKeys) {
228161
228352
  await this.recoverMissingRequiredArtifacts(liveTask, missingArtifactKeys, {
@@ -228412,6 +228603,7 @@ ${info.feedback}`, revisionKey),
228412
228603
  */
228413
228604
  async recoverFailedPreMergeWorkflowStep(task) {
228414
228605
  try {
228606
+ if (hasUserAutoMergeHold(task)) return false;
228415
228607
  const failed = (task.workflowStepResults ?? []).filter((r) => (r.phase || "pre-merge") === "pre-merge" && r.status === "failed").sort((a, b3) => {
228416
228608
  const aTs = Date.parse(a.completedAt || a.startedAt || "");
228417
228609
  const bTs = Date.parse(b3.completedAt || b3.startedAt || "");
@@ -228938,6 +229130,7 @@ ${info.feedback}`, revisionKey),
228938
229130
  getTask: (taskId) => this.store.getTask(taskId)
228939
229131
  },
228940
229132
  runId: resolvedRunId,
229133
+ isLiveSharedBranchMember: (nodeTask) => this.isLiveSharedBranchGroupMember(nodeTask),
228941
229134
  primitives: this.createAuthoritativeWorkflowPrimitives(settings),
228942
229135
  seams: this.createAuthoritativeWorkflowSeams(settings),
228943
229136
  prepareNodeExecution: (node, nodeTask, requirement) => this.prepareGraphNodeExecution(node, nodeTask, settings, requirement),
@@ -231180,6 +231373,7 @@ ${info.feedback}`, revisionKey),
231180
231373
  return this.runCliAgentNode(node, await this.store.getTask(live.id), cfg);
231181
231374
  }
231182
231375
  const optionalGroupId = typeof graphContext?.[WORKFLOW_OPTIONAL_GROUP_CONTEXT_KEY] === "string" ? graphContext[WORKFLOW_OPTIONAL_GROUP_CONTEXT_KEY] : void 0;
231376
+ const declaredReviewKind = cfg.reviewKind === "plan" || cfg.reviewKind === "code" ? cfg.reviewKind : graphContext?.[WORKFLOW_REVIEW_KIND_CONTEXT_KEY] === "plan" || graphContext?.[WORKFLOW_REVIEW_KIND_CONTEXT_KEY] === "code" ? graphContext[WORKFLOW_REVIEW_KIND_CONTEXT_KEY] : void 0;
231183
231377
  const isCompletionSummaryNode2 = cfg.summaryTarget === "task" || node.id === "completion-summary";
231184
231378
  if (live.executionMode === "fast" && !isCompletionSummaryNode2 && !optionalGroupId && !cfg.seam && (node.kind === "prompt" || node.kind === "script" || node.kind === "gate")) {
231185
231379
  executorLog.debug(`${live.id}: fast mode \u2014 skipping custom graph node '${node.id}'`);
@@ -231328,6 +231522,9 @@ ${prompt}`;
231328
231522
  if (optionalGroupId) {
231329
231523
  step.optionalGroupId = optionalGroupId;
231330
231524
  }
231525
+ if (declaredReviewKind) {
231526
+ step.reviewKind = declaredReviewKind;
231527
+ }
231331
231528
  if (cfg.reviewCanFixInline === true) {
231332
231529
  step.reviewCanFixInline = true;
231333
231530
  }
@@ -231340,7 +231537,11 @@ ${prompt}`;
231340
231537
  executorLog.debug(`${live.id}: graph node '${node.id}' runtime env injected (${injected.pathEntryCount} PATH entries, ${injected.injectedKeyCount} env keys)`);
231341
231538
  }
231342
231539
  const unattended = this.graphUnattendedRuns.has(live.id);
231343
- const outcome = mode === "script" ? await this.executeScriptWorkflowStep(live, step, worktreePath, settings, nodeEnv) : await this.executeWorkflowStep(live, step, worktreePath, settings, nodeEnv, { unattended });
231540
+ let outcome = mode === "script" ? await this.executeScriptWorkflowStep(live, step, worktreePath, settings, nodeEnv) : await this.executeWorkflowStep(live, step, worktreePath, settings, nodeEnv, { unattended });
231541
+ if (declaredReviewKind && typeof outcome.output === "string") {
231542
+ const parsedReviewOutput = parseWorkflowStepOutput(outcome.output, { requireVerdict: false });
231543
+ if (parsedReviewOutput.findings?.length) outcome = { ...outcome, findings: parsedReviewOutput.findings };
231544
+ }
231344
231545
  const awaitQuestion = parseAwaitInputSentinel(outcome.output);
231345
231546
  if (awaitQuestion) {
231346
231547
  await this.store.logEntry(
@@ -231363,6 +231564,8 @@ ${prompt}`;
231363
231564
  const contextPatch = {};
231364
231565
  if (typeof stepOutput === "string") contextPatch.output = stepOutput;
231365
231566
  if (typeof stepNotes === "string" && stepNotes) contextPatch.notes = stepNotes;
231567
+ const stepFindings = outcome.findings;
231568
+ if (stepFindings?.length) contextPatch.findings = stepFindings;
231366
231569
  if (cfg.summaryTarget === "task" && typeof stepOutput === "string" && stepOutput.trim()) {
231367
231570
  contextPatch.summary = stepOutput.trim();
231368
231571
  }
@@ -231869,6 +232072,7 @@ Graph value: ${failureValue}` : ""}`, budget.key),
231869
232072
  } catch {
231870
232073
  return false;
231871
232074
  }
232075
+ if (hasUserAutoMergeHold(live)) return false;
231872
232076
  const sharedBranchMember = await this.isLiveSharedBranchGroupMember(live);
231873
232077
  if (!sharedBranchMember && !allowsAutoMergeProcessing(live, settings)) return false;
231874
232078
  if (!sharedBranchMember && resolveEffectiveAutoMerge(live, settings) === false) return false;
@@ -231930,8 +232134,8 @@ Graph value: ${failureValue}` : ""}`, budget.key),
231930
232134
  } catch {
231931
232135
  return false;
231932
232136
  }
231933
- if (await this.isLiveSharedBranchGroupMember(live)) return false;
231934
- return !allowsAutoMergeProcessing(live, settings) || resolveEffectiveAutoMerge(live, settings) === false;
232137
+ if (await this.isLiveSharedBranchGroupMember(live) && !hasUserAutoMergeHold(live)) return false;
232138
+ return hasUserAutoMergeHold(live) || !allowsAutoMergeProcessing(live, settings) || resolveEffectiveAutoMerge(live, settings) === false;
231935
232139
  }
231936
232140
  async handleStaleInReviewPlanPauseAbortReplay(live, result2, abortProvenance, pausedAborted, userCanceled, resumeLanesMemo) {
231937
232141
  if (!pausedAborted) return false;
@@ -232095,9 +232299,9 @@ Graph value: ${failureValue}` : ""}`, budget.key),
232095
232299
  if (settings.globalPause === true) return false;
232096
232300
  }
232097
232301
  if (live.column === resumeLanes.review) {
232098
- if (live.autoMerge === false) return false;
232099
232302
  if (!settings) return false;
232100
232303
  const sharedBranchMember = await this.isLiveSharedBranchGroupMember(live);
232304
+ if (hasUserAutoMergeHold(live) || live.autoMerge === false && !sharedBranchMember) return false;
232101
232305
  if (!sharedBranchMember && !allowsAutoMergeProcessing(live, settings)) return false;
232102
232306
  if (live.mergeDetails?.mergeConfirmed === true) return false;
232103
232307
  }
@@ -237865,13 +238069,14 @@ CRITICAL SCOPING RULES \u2014 read before doing anything else:
237865
238069
  const isSkillStep = typeof workflowStep.skillName === "string" && workflowStep.skillName.trim().length > 0;
237866
238070
  const isSummaryProjectionStep = workflowStep.summaryTarget === "task";
237867
238071
  const requireVerdict = !isSummaryProjectionStep && (workflowStep.gateMode === "gate" || !isSkillStep);
238072
+ const reviewFindingsContract = workflowStepMetadata.reviewKind === "plan" || workflowStepMetadata.reviewKind === "code";
237868
238073
  const verdictBlock = requireVerdict ? `
237869
238074
 
237870
238075
  ## Feedback Format
237871
238076
 
237872
238077
  When your review is complete, your final line MUST be a single JSON object (no markdown fences):
237873
238078
 
237874
- {"verdict":"APPROVE|APPROVE_WITH_NOTES|REVISE","notes":"..."}
238079
+ ${reviewFindingsContract ? '{"verdict":"APPROVE|APPROVE_WITH_NOTES|REVISE","notes":"...","findings":[{"id":"stable-id","title":"concise issue","body":"actionable detail","filePath":"optional/path","line":1,"severity":"low|medium|high|critical"}]}' : '{"verdict":"APPROVE|APPROVE_WITH_NOTES|REVISE","notes":"..."}'}
237875
238080
 
237876
238081
  Rules:
237877
238082
  - Output exactly one trailing JSON object and stop.
@@ -238181,7 +238386,8 @@ ${detectedQuestion}
238181
238386
  revisionRequested,
238182
238387
  output: parsed.output,
238183
238388
  verdict: parsed.verdict,
238184
- notes: parsed.notes
238389
+ notes: parsed.notes,
238390
+ ...parsed.findings ? { findings: parsed.findings } : {}
238185
238391
  };
238186
238392
  }
238187
238393
  if (parsed.malformed) {
@@ -249170,7 +249376,7 @@ var init_metric_parser = __esm({
249170
249376
  });
249171
249377
 
249172
249378
  // ../engine/src/eval/experiment-executor.ts
249173
- import { randomUUID as randomUUID44 } from "node:crypto";
249379
+ import { randomUUID as randomUUID45 } from "node:crypto";
249174
249380
  var ExperimentMaxIterationsError, ExperimentGitNotConfiguredError, ExperimentExecutor;
249175
249381
  var init_experiment_executor = __esm({
249176
249382
  "../engine/src/eval/experiment-executor.ts"() {
@@ -249238,7 +249444,7 @@ var init_experiment_executor = __esm({
249238
249444
  }
249239
249445
  await this.semaphore.acquire();
249240
249446
  const controller = new AbortController();
249241
- const runHandle = randomUUID44();
249447
+ const runHandle = randomUUID45();
249242
249448
  if (opts?.abortSignal) {
249243
249449
  opts.abortSignal.addEventListener("abort", () => controller.abort(), { once: true });
249244
249450
  }
@@ -258245,6 +258451,7 @@ var init_in_process_runtime = __esm({
258245
258451
  }
258246
258452
  }
258247
258453
  const prNodeGithubOps = this.config.prNodeGithubOps;
258454
+ const secretsStore = await this.taskStore.getSecretsStore();
258248
258455
  const executorOptions = {
258249
258456
  /*
258250
258457
  FNXC:PlanReviewLease 2026-07-26-21:12:
@@ -258260,6 +258467,7 @@ var init_in_process_runtime = __esm({
258260
258467
  cliAgentRuntime: this.cliAgentRuntime?.bundle,
258261
258468
  pluginRunner: this.pluginRunner,
258262
258469
  messageStore: this.messageStore,
258470
+ secretsStore,
258263
258471
  missionStore,
258264
258472
  reflectionService,
258265
258473
  // PR-entity nodes (U3): assemble the handler deps from the CLI-injected
@@ -258361,6 +258569,7 @@ var init_in_process_runtime = __esm({
258361
258569
  reflectionService,
258362
258570
  selfImproveService,
258363
258571
  credentialRotator: this.credentialRotator,
258572
+ secretsStore,
258364
258573
  snapshotManager: autoClaimSnapshotManager,
258365
258574
  onMissed: (agentId, reason) => {
258366
258575
  runtimeLog.warn(`Agent ${agentId} missed heartbeat: ${reason}`);
@@ -262222,7 +262431,21 @@ var init_project_engine = __esm({
262222
262431
  Cleared on the first non-busy outcome (success path resets it).
262223
262432
  */
262224
262433
  workspaceBusyReenqueues = /* @__PURE__ */ new Map();
262434
+ workspaceBusyReenqueueTimers = /* @__PURE__ */ new Set();
262225
262435
  static WORKSPACE_BUSY_MAX_REENQUEUES = 10;
262436
+ /*
262437
+ FNXC:WorkspaceMergeDispatch 2026-08-05-23:56:
262438
+ Workspace lease-contention retries are engine-owned lifecycle work, not detached callbacks.
262439
+ Track only these busy re-enqueue timers so stop() can cancel them and tests can measure the
262440
+ capped workspace ladder without confusing it with merge-body or maintenance timers.
262441
+ */
262442
+ scheduleWorkspaceBusyReenqueue(taskId, delayMs) {
262443
+ const timer2 = setTimeout(() => {
262444
+ this.workspaceBusyReenqueueTimers.delete(timer2);
262445
+ if (!this.shuttingDown) this.internalEnqueueMerge(taskId);
262446
+ }, delayMs);
262447
+ this.workspaceBusyReenqueueTimers.add(timer2);
262448
+ }
262226
262449
  /**
262227
262450
  * Pending manual merge resolvers — keyed by taskId.
262228
262451
  * When `onMerge` is called, the task is enqueued like auto-merge but a
@@ -262283,7 +262506,7 @@ var init_project_engine = __esm({
262283
262506
  *
262284
262507
  * Readable (not private) so tests derive the cap from this single source of truth rather
262285
262508
  * than hardcoding it — the FN-8004 bump broke two suites that had baked in the old `3`. */
262286
- static MAX_AUTO_MERGE_TRANSIENT_RETRIES = 5;
262509
+ static MAX_AUTO_MERGE_TRANSIENT_RETRIES = MAX_AUTO_MERGE_TRANSIENT_RETRIES;
262287
262510
  static MERGE_REQUEST_RETRY_EXHAUSTED_AGE_MS = 30 * 60 * 1e3;
262288
262511
  /** Cap on outer in-review→in-progress bounces caused by deterministic
262289
262512
  * verification failures during auto-merge. After this many failed merges
@@ -262757,6 +262980,10 @@ ${detail}`);
262757
262980
  clearInterval(this.mergeActiveReconcileTimer);
262758
262981
  this.mergeActiveReconcileTimer = null;
262759
262982
  }
262983
+ for (const timer2 of this.workspaceBusyReenqueueTimers) {
262984
+ clearTimeout(timer2);
262985
+ }
262986
+ this.workspaceBusyReenqueueTimers.clear();
262760
262987
  for (const [taskId, deferred] of this.capacityDeferredMerges) {
262761
262988
  clearTimeout(deferred.timer);
262762
262989
  for (const resolver of deferred.resolvers) {
@@ -264056,16 +264283,15 @@ ${detail}`);
264056
264283
  * older low-priority task would start before a later urgent one.
264057
264284
  */
264058
264285
  async allowInReviewMergeProcessing(task, settings, store = this.runtime.getTaskStore()) {
264059
- if (allowsAutoMergeProcessing(task, settings)) {
264060
- return true;
264061
- }
264286
+ if (hasUserAutoMergeHold(task)) return false;
264062
264287
  const groupId = task.branchContext?.groupId?.trim();
264063
264288
  const branchGroup = groupId ? await store.getBranchGroup?.(groupId) : null;
264064
- if (!branchGroup || branchGroup.status !== "open" || !branchGroup.branchName.trim()) {
264065
- return false;
264066
- }
264067
264289
  const projectDefaultBranch = await resolveIntegrationBranch(this.config.workingDirectory, settings);
264068
- return isLiveSharedBranchGroupMemberIntegration(task, branchGroup, projectDefaultBranch);
264290
+ if (isLiveSharedBranchGroupMemberIntegration(task, branchGroup, projectDefaultBranch)) {
264291
+ return true;
264292
+ }
264293
+ if (task.autoMerge === false && groupId) return false;
264294
+ return allowsAutoMergeProcessing(task, settings);
264069
264295
  }
264070
264296
  async emitLegacyAutoMergeStampAdvisory(store) {
264071
264297
  if (this.legacyAutoMergeStampAdvisoryEmitted) {
@@ -265018,9 +265244,7 @@ ${detail}`);
265018
265244
  runtimeLog.log(
265019
265245
  `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)`
265020
265246
  );
265021
- setTimeout(() => {
265022
- if (!this.shuttingDown) this.internalEnqueueMerge(taskId);
265023
- }, delayMs);
265247
+ this.scheduleWorkspaceBusyReenqueue(taskId, delayMs);
265024
265248
  } else {
265025
265249
  this.workspaceBusyReenqueues.delete(taskId);
265026
265250
  await store.updateTask(taskId, { status: "failed", error: errorMsg }).catch(() => void 0);
@@ -268658,7 +268882,7 @@ __export(planning_exports, {
268658
268882
  updatePlanningSessionTitle: () => updatePlanningSessionTitle,
268659
268883
  validateSession: () => validateSession
268660
268884
  });
268661
- import { randomUUID as randomUUID45 } from "node:crypto";
268885
+ import { randomUUID as randomUUID46 } from "node:crypto";
268662
268886
  import { EventEmitter as EventEmitter34 } from "node:events";
268663
268887
  async function createPlanningRuntimeSession(options) {
268664
268888
  const { pluginRunner, runtimeHint, settings, ...runtimeOptions } = options ?? {};
@@ -269122,7 +269346,7 @@ async function createSession(ip, initialPlan, store, rootDir, promptOverrides, p
269122
269346
  if (!store) {
269123
269347
  throw new Error("store is required for AI-powered planning sessions");
269124
269348
  }
269125
- const sessionId = randomUUID45();
269349
+ const sessionId = randomUUID46();
269126
269350
  const session = {
269127
269351
  id: sessionId,
269128
269352
  ip,
@@ -269337,7 +269561,7 @@ async function createDraftSession(ip, initialPlan, _rootDir, modelProvider, mode
269337
269561
  `Rate limit exceeded. Maximum ${MAX_SESSIONS_PER_IP_PER_HOUR} planning sessions per hour. Reset at ${resetTime?.toISOString() || "unknown"}`
269338
269562
  );
269339
269563
  }
269340
- const sessionId = randomUUID45();
269564
+ const sessionId = randomUUID46();
269341
269565
  const title2 = DRAFT_PLACEHOLDER_TITLE;
269342
269566
  const hasModelOverride = Boolean(modelProvider && modelId);
269343
269567
  const session = {
@@ -269513,7 +269737,7 @@ async function createSessionWithAgent(ip, initialPlan, rootDir, store, modelProv
269513
269737
  `Rate limit exceeded. Maximum ${MAX_SESSIONS_PER_IP_PER_HOUR} planning sessions per hour. Reset at ${resetTime?.toISOString() || "unknown"}`
269514
269738
  );
269515
269739
  }
269516
- const sessionId = randomUUID45();
269740
+ const sessionId = randomUUID46();
269517
269741
  const session = {
269518
269742
  id: sessionId,
269519
269743
  ip,
@@ -270023,7 +270247,7 @@ function normalizePlanningQuestion(input, userInput = "") {
270023
270247
  const type = source.type === "multi_select" || source.type === "single_select" ? source.type : "single_select";
270024
270248
  const question = typeof source.question === "string" && source.question.trim() ? source.question.trim() : fallback2.question;
270025
270249
  const normalized = {
270026
- id: typeof source.id === "string" && source.id.trim() ? source.id : randomUUID45(),
270250
+ id: typeof source.id === "string" && source.id.trim() ? source.id : randomUUID46(),
270027
270251
  type,
270028
270252
  question,
270029
270253
  ...typeof source.description === "string" && source.description.trim() ? { description: source.description.trim() } : {}
@@ -270049,7 +270273,7 @@ function normalizePlanningQuestion(input, userInput = "") {
270049
270273
  }
270050
270274
  function coerceQuestionResponse(response, session) {
270051
270275
  if (response.type === "question") return normalizePlanningQuestion(response.data, session.initialPlan);
270052
- return normalizePlanningQuestion({ id: randomUUID45(), type: "single_select", question: planningFallbackCopy(session.initialPlan).question }, session.initialPlan);
270276
+ return normalizePlanningQuestion({ id: randomUUID46(), type: "single_select", question: planningFallbackCopy(session.initialPlan).question }, session.initialPlan);
270053
270277
  }
270054
270278
  async function validateSession(sessionId) {
270055
270279
  const session = await getSession(sessionId);
@@ -270991,7 +271215,7 @@ async function createTaskFromPlanSession(sessionId, store, options) {
270991
271215
  throw new InvalidSessionStateError("PLANNING_CREATED_TASK_MISSING");
270992
271216
  }
270993
271217
  }
270994
- const claimOwnerToken = randomUUID45();
271218
+ const claimOwnerToken = randomUUID46();
270995
271219
  let claimed = await claimPlanningTaskCreation(sessionId, claimOwnerToken, (/* @__PURE__ */ new Date()).toISOString(), claimEpoch);
270996
271220
  if (!claimed) {
270997
271221
  session = await getDurablePlanningSession(sessionId).catch(() => void 0) ?? session;
@@ -271660,7 +271884,7 @@ __export(subtask_breakdown_exports, {
271660
271884
  stopSubtaskGeneration: () => stopSubtaskGeneration,
271661
271885
  subtaskStreamManager: () => subtaskStreamManager
271662
271886
  });
271663
- import { randomUUID as randomUUID46 } from "node:crypto";
271887
+ import { randomUUID as randomUUID47 } from "node:crypto";
271664
271888
  import { EventEmitter as EventEmitter35 } from "node:events";
271665
271889
  function __getSubtaskBreakdownDiagnostics() {
271666
271890
  return diagnostics3;
@@ -271835,7 +272059,7 @@ async function decomposeForTriage(description, rootDir, promptOverrides, store)
271835
272059
  }
271836
272060
  }
271837
272061
  async function createSubtaskSession(initialDescription, store, rootDir, promptOverrides, projectId) {
271838
- const sessionId = randomUUID46();
272062
+ const sessionId = randomUUID47();
271839
272063
  const session = {
271840
272064
  sessionId,
271841
272065
  projectId,
@@ -272272,7 +272496,7 @@ __export(mission_interview_exports, {
272272
272496
  stopMissionInterviewGeneration: () => stopMissionInterviewGeneration,
272273
272497
  submitMissionInterviewResponse: () => submitMissionInterviewResponse
272274
272498
  });
272275
- import { randomUUID as randomUUID47 } from "node:crypto";
272499
+ import { randomUUID as randomUUID48 } from "node:crypto";
272276
272500
  import { EventEmitter as EventEmitter36 } from "node:events";
272277
272501
  function __getMissionInterviewDiagnostics() {
272278
272502
  return diagnostics4;
@@ -273015,7 +273239,7 @@ async function createMissionInterviewSession(ip, missionTitle, rootDir, store, p
273015
273239
  `Rate limit exceeded. Maximum ${MAX_SESSIONS_PER_IP_PER_HOUR2} sessions per hour. Reset at ${resetTime?.toISOString() || "unknown"}`
273016
273240
  );
273017
273241
  }
273018
- const sessionId = randomUUID47();
273242
+ const sessionId = randomUUID48();
273019
273243
  const session = {
273020
273244
  id: sessionId,
273021
273245
  ip,
@@ -273478,7 +273702,7 @@ __export(milestone_slice_interview_exports, {
273478
273702
  stopMilestoneSliceInterviewGeneration: () => stopMilestoneSliceInterviewGeneration,
273479
273703
  submitTargetInterviewResponse: () => submitTargetInterviewResponse
273480
273704
  });
273481
- import { randomUUID as randomUUID48 } from "node:crypto";
273705
+ import { randomUUID as randomUUID49 } from "node:crypto";
273482
273706
  import { EventEmitter as EventEmitter37 } from "node:events";
273483
273707
  function parseTargetInterviewResponseImpl(text3) {
273484
273708
  const candidate = extractJsonCandidate3(text3);
@@ -274107,7 +274331,7 @@ async function createTargetInterviewSession(ip, targetType, targetId, targetTitl
274107
274331
  `Rate limit exceeded. Maximum ${MAX_SESSIONS_PER_IP_PER_HOUR3} sessions per hour. Reset at ${resetTime?.toISOString() || "unknown"}`
274108
274332
  );
274109
274333
  }
274110
- const sessionId = randomUUID48();
274334
+ const sessionId = randomUUID49();
274111
274335
  const session = {
274112
274336
  id: sessionId,
274113
274337
  ip,
@@ -292714,10 +292938,32 @@ function parseTaskReviewVerdict(value) {
292714
292938
  return void 0;
292715
292939
  }
292716
292940
  }
292717
- function isCurrentWorkflowReviewResult(result2) {
292718
- return CURRENT_WORKFLOW_REVIEW_STEP_IDS.has(result2.workflowStepId) && CURRENT_WORKFLOW_REVIEW_STATUSES.has(result2.status) && result2.verdict !== void 0 && result2.supersededAt === void 0 && result2.bypassedBy === void 0 && result2.bypassedAt === void 0 && result2.bypassReason === void 0 && result2.bypassedFromStatus === void 0 && result2.bypassedFromVerdict === void 0;
292941
+ function hasCurrentWorkflowReviewIdentity(result2) {
292942
+ return result2.supersededAt === void 0 && result2.supersededReason === void 0 && result2.bypassedBy === void 0 && result2.bypassedAt === void 0 && result2.bypassReason === void 0 && result2.bypassedFromStatus === void 0 && result2.bypassedFromVerdict === void 0;
292943
+ }
292944
+ function isCurrentMarkedWorkflowReviewResult(result2) {
292945
+ return CURRENT_WORKFLOW_REVIEW_STATUSES.has(result2.status) && hasCurrentWorkflowReviewIdentity(result2) && Boolean(result2.output?.trim() || result2.notes?.trim()) && (result2.reviewKind === "plan" || result2.reviewKind === "code") && (result2.source === "node" || result2.source === "optional-group");
292946
+ }
292947
+ async function getDeclaredTopLevelReviewResultSources(task, store) {
292948
+ const resolved = await resolveWorkflowIrForTaskWithProvenance(store, task.id);
292949
+ if (resolved.source !== "selection") return /* @__PURE__ */ new Map();
292950
+ return new Map(resolved.ir.nodes.flatMap((node) => {
292951
+ if (node.kind === "optional-group") return [[node.id, "optional-group"]];
292952
+ if (node.kind === "prompt" || node.kind === "gate" || node.kind === "script") return [[node.id, "node"]];
292953
+ return [];
292954
+ }));
292719
292955
  }
292720
- function buildWorkflowReviewItemId(task, result2) {
292956
+ function isCurrentLegacyWorkflowReviewResult(result2) {
292957
+ return CURRENT_WORKFLOW_REVIEW_STEP_IDS.has(result2.workflowStepId) && result2.reviewKind === void 0 && LEGACY_WORKFLOW_REVIEW_STATUSES.has(result2.status) && result2.verdict !== void 0 && hasCurrentWorkflowReviewIdentity(result2);
292958
+ }
292959
+ function getWorkflowReviewKind(result2, declaredTopLevelReviewResultSources) {
292960
+ if (isCurrentMarkedWorkflowReviewResult(result2) && declaredTopLevelReviewResultSources.get(result2.workflowStepId) === result2.source) return result2.reviewKind;
292961
+ if (isCurrentLegacyWorkflowReviewResult(result2)) {
292962
+ return result2.workflowStepId === "plan-review" ? "plan" : "code";
292963
+ }
292964
+ return void 0;
292965
+ }
292966
+ function buildWorkflowReviewItemId(task, result2, findingId) {
292721
292967
  const identity = JSON.stringify({
292722
292968
  taskId: task.id,
292723
292969
  workflowStepId: result2.workflowStepId,
@@ -292725,22 +292971,44 @@ function buildWorkflowReviewItemId(task, result2) {
292725
292971
  phase: result2.phase,
292726
292972
  status: result2.status,
292727
292973
  verdict: result2.verdict,
292974
+ reviewKind: result2.reviewKind,
292728
292975
  completedAt: result2.completedAt,
292729
292976
  startedAt: result2.startedAt,
292730
292977
  output: result2.output,
292731
- notes: result2.notes
292978
+ notes: result2.notes,
292979
+ findingId
292732
292980
  });
292733
292981
  return `workflow-review-${createHash29("sha256").update(identity).digest("hex").slice(0, 24)}`;
292734
292982
  }
292735
- function buildWorkflowReviewItems(task) {
292736
- return (task.workflowStepResults ?? []).filter(isCurrentWorkflowReviewResult).map((result2) => {
292737
- const reviewType = result2.workflowStepId === "plan-review" ? "plan" : "code";
292738
- const body = result2.output?.trim() || result2.notes?.trim() || "No written feedback was provided by this review step.";
292983
+ async function buildWorkflowReviewItems(task, store) {
292984
+ const declaredTopLevelReviewResultSources = await getDeclaredTopLevelReviewResultSources(task, store);
292985
+ return (task.workflowStepResults ?? []).flatMap((result2) => {
292986
+ const reviewType = getWorkflowReviewKind(result2, declaredTopLevelReviewResultSources);
292987
+ if (!reviewType) return [];
292739
292988
  const timestamp2 = result2.completedAt ?? result2.startedAt ?? task.updatedAt ?? task.createdAt;
292740
- return {
292989
+ if (result2.findings?.length) {
292990
+ return result2.findings.map((finding) => ({
292991
+ itemId: buildWorkflowReviewItemId(task, result2, finding.id),
292992
+ sourceMode: "reviewer-agent",
292993
+ title: finding.title,
292994
+ body: finding.body,
292995
+ author: "reviewer-agent",
292996
+ createdAt: timestamp2,
292997
+ updatedAt: timestamp2,
292998
+ ...finding.filePath ? { filePath: finding.filePath } : {},
292999
+ ...finding.line ? { line: finding.line } : {},
293000
+ ...finding.severity ? { severity: finding.severity } : {},
293001
+ reviewState: result2.verdict,
293002
+ verdict: result2.verdict,
293003
+ reviewType,
293004
+ progressStatus: null
293005
+ }));
293006
+ }
293007
+ const body = result2.output?.trim() || result2.notes?.trim() || "No written feedback was provided by this review step.";
293008
+ return [{
292741
293009
  itemId: buildWorkflowReviewItemId(task, result2),
292742
293010
  sourceMode: "reviewer-agent",
292743
- title: `${result2.workflowStepName || result2.workflowStepId} ${result2.verdict}`,
293011
+ title: `${result2.workflowStepName || result2.workflowStepId} ${result2.verdict ?? result2.status}`,
292744
293012
  body,
292745
293013
  author: "reviewer-agent",
292746
293014
  createdAt: timestamp2,
@@ -292749,7 +293017,7 @@ function buildWorkflowReviewItems(task) {
292749
293017
  verdict: result2.verdict,
292750
293018
  reviewType,
292751
293019
  progressStatus: null
292752
- };
293020
+ }];
292753
293021
  }).sort((a, b3) => Date.parse(b3.createdAt ?? "") - Date.parse(a.createdAt ?? "") || a.itemId.localeCompare(b3.itemId));
292754
293022
  }
292755
293023
  function buildDirectReviewSummary(items) {
@@ -292757,7 +293025,7 @@ function buildDirectReviewSummary(items) {
292757
293025
  return latest ? { summary: latest.title, verdict: latest.verdict } : null;
292758
293026
  }
292759
293027
  async function buildDirectTaskReviewData(task, store) {
292760
- const structuredItems = buildWorkflowReviewItems(task);
293028
+ const structuredItems = await buildWorkflowReviewItems(task, store);
292761
293029
  if (structuredItems.length > 0) {
292762
293030
  return {
292763
293031
  mode: "reviewer-agent",
@@ -293207,6 +293475,7 @@ function registerTaskWorkflowRoutes(ctx, deps) {
293207
293475
  reviewLevel,
293208
293476
  executionMode,
293209
293477
  autoMerge,
293478
+ autoMergeProvenance,
293210
293479
  priority,
293211
293480
  source,
293212
293481
  branch,
@@ -293256,6 +293525,9 @@ function registerTaskWorkflowRoutes(ctx, deps) {
293256
293525
  if (autoMerge !== void 0 && typeof autoMerge !== "boolean") {
293257
293526
  throw badRequest("autoMerge must be a boolean");
293258
293527
  }
293528
+ if (autoMergeProvenance !== void 0) {
293529
+ throw badRequest("autoMergeProvenance is server-managed");
293530
+ }
293259
293531
  if (priority !== void 0 && priority !== null && !isTaskPriority(priority)) {
293260
293532
  throw badRequest(`priority must be one of: ${TASK_PRIORITIES.join(", ")}`);
293261
293533
  }
@@ -294821,6 +295093,17 @@ See \`GET /api/tasks/${task.id}/diff\` for the landed diff being reverted.
294821
295093
  rethrowTaskApiError(err, req.params.id, "Failed to read task verification status");
294822
295094
  }
294823
295095
  });
295096
+ router.get("/tasks/:id/prompt", async (req, res) => {
295097
+ try {
295098
+ const { store: scopedStore } = await getProjectContext2(req);
295099
+ const task = await scopedStore.getTask(req.params.id);
295100
+ res.json(task.prompt === void 0 ? { id: task.id } : { id: task.id, prompt: task.prompt });
295101
+ } catch (err) {
295102
+ if (err instanceof ApiError) throw err;
295103
+ if (isTaskLookupMiss(err)) throw notFound(`Task ${req.params.id} not found`);
295104
+ rethrowAsApiError8(err, "Internal server error");
295105
+ }
295106
+ });
294824
295107
  router.get("/tasks/:id", async (req, res) => {
294825
295108
  try {
294826
295109
  const { store: scopedStore, engine } = await getProjectContext2(req);
@@ -295943,6 +296226,9 @@ See \`GET /api/tasks/${task.id}/diff\` for the landed diff being reverted.
295943
296226
  if (hasBodyField("autoMerge") && autoMerge !== void 0 && autoMerge !== null && typeof autoMerge !== "boolean") {
295944
296227
  throw new Error("autoMerge must be a boolean");
295945
296228
  }
296229
+ if (hasBodyField("autoMergeProvenance")) {
296230
+ throw badRequest("autoMergeProvenance is server-managed");
296231
+ }
295946
296232
  let validatedSourceIssue;
295947
296233
  if (hasBodyField("sourceIssue")) {
295948
296234
  if (sourceIssue === null) {
@@ -296164,7 +296450,7 @@ See \`GET /api/tasks/${task.id}/diff\` for the landed diff being reverted.
296164
296450
  if (dependencies !== void 0) updates.dependencies = dependencies;
296165
296451
  if (enabledWorkflowSteps !== void 0) updates.enabledWorkflowSteps = enabledWorkflowSteps;
296166
296452
  if (hasBodyField("noCommitsExpected")) updates.noCommitsExpected = noCommitsExpected;
296167
- if (hasBodyField("autoMerge")) updates.autoMerge = autoMerge === null ? void 0 : autoMerge;
296453
+ if (hasBodyField("autoMerge")) updates.autoMerge = autoMerge;
296168
296454
  if (hasBodyField("modelProvider")) updates.modelProvider = validatedModelProvider;
296169
296455
  if (hasBodyField("modelId")) updates.modelId = validatedModelId;
296170
296456
  if (hasBodyField("validatorModelProvider")) updates.validatorModelProvider = validatedValidatorModelProvider;
@@ -296466,6 +296752,7 @@ See \`GET /api/tasks/${task.id}/diff\` for the landed diff being reverted.
296466
296752
  updatedAt: item.updatedAt ?? void 0,
296467
296753
  path: item.filePath,
296468
296754
  line: item.line,
296755
+ severity: item.severity,
296469
296756
  threadId: item.threadId,
296470
296757
  htmlUrl: item.url,
296471
296758
  state: item.reviewState ?? void 0,
@@ -296508,6 +296795,7 @@ See \`GET /api/tasks/${task.id}/diff\` for the landed diff being reverted.
296508
296795
  author: item.author.login,
296509
296796
  filePath: item.path,
296510
296797
  lineNumber: item.line,
296798
+ severity: item.severity,
296511
296799
  threadId: item.threadId,
296512
296800
  url: item.htmlUrl
296513
296801
  };
@@ -296543,6 +296831,7 @@ See \`GET /api/tasks/${task.id}/diff\` for the landed diff being reverted.
296543
296831
  authorLogin: item.author,
296544
296832
  filePath: item.filePath,
296545
296833
  lineNumber: item.lineNumber,
296834
+ severity: item.severity,
296546
296835
  threadId: item.threadId,
296547
296836
  url: item.url
296548
296837
  },
@@ -296835,7 +297124,7 @@ See \`GET /api/tasks/${task.id}/diff\` for the landed diff being reverted.
296835
297124
  }
296836
297125
  });
296837
297126
  }
296838
- var severityAuditLog33, AWAITING_PLANNING_ENRICH_LIMIT, LEGACY_WIP_LANES3, LEGACY_ARCHIVE_LANES5, REVIEW_BLOCK_RE, REVIEW_VERDICT_RE, REVIEW_STEP_RE, DUPLICATE_STOPWORDS, ARTIFACT_TYPES, ADDRESS_PR_FEEDBACK_PROMPT, __fingerprintCreateLocksForTests, RESET_TASK_FIELDS, RESET_DRIFT_CORRECTION_FIELDS, CURRENT_WORKFLOW_REVIEW_STEP_IDS, CURRENT_WORKFLOW_REVIEW_STATUSES;
297127
+ var severityAuditLog33, AWAITING_PLANNING_ENRICH_LIMIT, LEGACY_WIP_LANES3, LEGACY_ARCHIVE_LANES5, REVIEW_BLOCK_RE, REVIEW_VERDICT_RE, REVIEW_STEP_RE, DUPLICATE_STOPWORDS, ARTIFACT_TYPES, ADDRESS_PR_FEEDBACK_PROMPT, __fingerprintCreateLocksForTests, RESET_TASK_FIELDS, RESET_DRIFT_CORRECTION_FIELDS, CURRENT_WORKFLOW_REVIEW_STEP_IDS, CURRENT_WORKFLOW_REVIEW_STATUSES, LEGACY_WORKFLOW_REVIEW_STATUSES;
296839
297128
  var init_register_task_workflow_routes = __esm({
296840
297129
  "../dashboard/src/routes/register-task-workflow-routes.ts"() {
296841
297130
  "use strict";
@@ -296898,7 +297187,8 @@ var init_register_task_workflow_routes = __esm({
296898
297187
  sessionFile: null
296899
297188
  };
296900
297189
  CURRENT_WORKFLOW_REVIEW_STEP_IDS = /* @__PURE__ */ new Set(["code-review", "plan-review"]);
296901
- CURRENT_WORKFLOW_REVIEW_STATUSES = /* @__PURE__ */ new Set(["passed", "failed", "advisory_failure"]);
297190
+ CURRENT_WORKFLOW_REVIEW_STATUSES = /* @__PURE__ */ new Set(["passed", "failed"]);
297191
+ LEGACY_WORKFLOW_REVIEW_STATUSES = /* @__PURE__ */ new Set(["passed", "failed", "advisory_failure"]);
296902
297192
  }
296903
297193
  });
296904
297194
 
@@ -297758,7 +298048,7 @@ at this boundary regardless.`;
297758
298048
  });
297759
298049
 
297760
298050
  // ../dashboard/src/routes/register-planning-subtask-routes.ts
297761
- import { randomUUID as randomUUID49 } from "node:crypto";
298051
+ import { randomUUID as randomUUID50 } from "node:crypto";
297762
298052
  async function acquirePlanningCreateLock(sessionId) {
297763
298053
  const previous = planningCreateLocks.get(sessionId);
297764
298054
  let releaseGate;
@@ -298739,7 +299029,7 @@ function registerPlanningSubtaskRoutes(ctx, deps) {
298739
299029
  }
298740
299030
  session = await getDurablePlanningSession2(sessionId) ?? session;
298741
299031
  if (await returnLinkedTask(session)) return;
298742
- const claimOwnerToken = randomUUID49();
299032
+ const claimOwnerToken = randomUUID50();
298743
299033
  claimedOwnerToken = claimOwnerToken;
298744
299034
  claimedSessionId = sessionId;
298745
299035
  const claimStartedAt = (/* @__PURE__ */ new Date()).toISOString();
@@ -299915,7 +300205,7 @@ __export(chat_exports, {
299915
300205
  resolveFileReferences: () => resolveFileReferences
299916
300206
  });
299917
300207
  import { EventEmitter as EventEmitter40 } from "node:events";
299918
- import { randomUUID as randomUUID50 } from "node:crypto";
300208
+ import { randomUUID as randomUUID51 } from "node:crypto";
299919
300209
  import { existsSync as existsSync71 } from "node:fs";
299920
300210
  import { join as join90, resolve as resolve48, relative as relative20 } from "node:path";
299921
300211
  import { SessionManager as SessionManager3 } from "@earendil-works/pi-coding-agent";
@@ -300127,7 +300417,7 @@ function createTaskVerificationTools(taskStore, actionGateContext) {
300127
300417
  const settings = await taskStore.getSettings();
300128
300418
  const command = profile === "verify:fast" ? "pnpm verify:fast" : typeof settings.testCommand === "string" ? settings.testCommand : "";
300129
300419
  if (!command) return { content: [{ type: "text", text: "ERROR: the selected verification profile is not configured." }], isError: true, details: {} };
300130
- const created = await taskStore.createTaskVerificationRequest({ taskId, requestId: randomUUID50(), profile, command, scope: "workspace", requestedBy: "chat" });
300420
+ const created = await taskStore.createTaskVerificationRequest({ taskId, requestId: randomUUID51(), profile, command, scope: "workspace", requestedBy: "chat" });
300131
300421
  if (created.inFlightRequestId) return { content: [{ type: "text", text: `ERROR: verification request ${created.inFlightRequestId} is already in flight.` }], isError: true, details: created };
300132
300422
  return { content: [{ type: "text", text: `Queued verification request ${created.request.requestId} for ${taskId}.` }], details: created.request };
300133
300423
  }
@@ -302287,7 +302577,7 @@ var init_rate_limit = __esm({
302287
302577
  });
302288
302578
 
302289
302579
  // ../dashboard/src/routes/register-chat-routes.ts
302290
- import { randomUUID as randomUUID51 } from "node:crypto";
302580
+ import { randomUUID as randomUUID52 } from "node:crypto";
302291
302581
  import { createReadStream as createReadStream2 } from "node:fs";
302292
302582
  import { mkdir as mkdir30, rm as rm16, writeFile as writeFile32 } from "node:fs/promises";
302293
302583
  import { basename as basename22, join as join91, resolve as resolve49 } from "node:path";
@@ -302346,7 +302636,7 @@ function registerChatRoutes(ctx, deps) {
302346
302636
  const filePath = join91(sessionDir, filename);
302347
302637
  await writeFile32(filePath, file.buffer);
302348
302638
  return {
302349
- id: `att-${randomUUID51().slice(0, 8)}`,
302639
+ id: `att-${randomUUID52().slice(0, 8)}`,
302350
302640
  filename,
302351
302641
  originalName: file.originalname,
302352
302642
  mimeType: file.mimetype,
@@ -303113,7 +303403,7 @@ var init_register_chat_routes = __esm({
303113
303403
  });
303114
303404
 
303115
303405
  // ../dashboard/src/routes/register-chat-room-routes.ts
303116
- import { randomUUID as randomUUID52 } from "node:crypto";
303406
+ import { randomUUID as randomUUID53 } from "node:crypto";
303117
303407
  import { createReadStream as createReadStream3 } from "node:fs";
303118
303408
  import { mkdir as mkdir31, writeFile as writeFile33 } from "node:fs/promises";
303119
303409
  import { basename as basename23, join as join92, resolve as resolve50 } from "node:path";
@@ -303454,7 +303744,7 @@ function registerChatRoomRoutes(ctx, deps) {
303454
303744
  const filePath = join92(roomDir, filename);
303455
303745
  await writeFile33(filePath, file.buffer);
303456
303746
  const attachment = {
303457
- id: `att-${randomUUID52().slice(0, 8)}`,
303747
+ id: `att-${randomUUID53().slice(0, 8)}`,
303458
303748
  filename,
303459
303749
  originalName: file.originalname,
303460
303750
  mimeType: file.mimetype,
@@ -309969,6 +310259,17 @@ function assertObject(value) {
309969
310259
  throw badRequest("body must be an object");
309970
310260
  }
309971
310261
  }
310262
+ function getExplicitProjectId(req, ctx) {
310263
+ const projectId = ctx.getProjectIdFromRequest(req);
310264
+ if (!projectId) throw badRequest("projectId is required");
310265
+ return projectId;
310266
+ }
310267
+ async function getExplicitSecretsStore(req, ctx) {
310268
+ const requestedProjectId = getExplicitProjectId(req, ctx);
310269
+ const { store, projectId } = await ctx.getProjectContext(req);
310270
+ if (projectId !== requestedProjectId) throw badRequest("project context does not match projectId");
310271
+ return store.getSecretsStore();
310272
+ }
309972
310273
  function emitSecretsAudit(req, ctx, type, metadata) {
309973
310274
  const requestWithAuditor = req;
309974
310275
  if (requestWithAuditor.runAuditor?.filesystem) {
@@ -309984,11 +310285,10 @@ var init_register_secrets_routes = __esm({
309984
310285
  init_src2();
309985
310286
  init_api_error();
309986
310287
  registerSecretsRoutes = (ctx) => {
309987
- const { router, getProjectContext: getProjectContext2, rethrowAsApiError: rethrowAsApiError8 } = ctx;
310288
+ const { router, rethrowAsApiError: rethrowAsApiError8 } = ctx;
309988
310289
  router.get("/secrets", async (req, res) => {
309989
310290
  try {
309990
- const { store: scopedStore } = await getProjectContext2(req);
309991
- const secretsStore = await scopedStore.getSecretsStore();
310291
+ const secretsStore = await getExplicitSecretsStore(req, ctx);
309992
310292
  const secrets2 = await secretsStore.listSecrets();
309993
310293
  const visibleSecrets = secrets2.filter(
309994
310294
  (secret) => !(secret.scope === "global" && secret.key === RESERVED_SYNC_PASSPHRASE_KEY)
@@ -310002,8 +310302,7 @@ var init_register_secrets_routes = __esm({
310002
310302
  });
310003
310303
  router.get("/secrets/sync-passphrase", async (req, res) => {
310004
310304
  try {
310005
- const { store: scopedStore } = await getProjectContext2(req);
310006
- const secretsStore = await scopedStore.getSecretsStore();
310305
+ const secretsStore = await getExplicitSecretsStore(req, ctx);
310007
310306
  const configured = await hasSyncPassphraseConfigured(secretsStore);
310008
310307
  res.json({ configured });
310009
310308
  } catch (err) {
@@ -310014,14 +310313,14 @@ var init_register_secrets_routes = __esm({
310014
310313
  });
310015
310314
  router.put("/secrets/sync-passphrase", async (req, res) => {
310016
310315
  try {
310316
+ getExplicitProjectId(req, ctx);
310017
310317
  assertObject(req.body);
310018
310318
  const passphrase = req.body.passphrase;
310019
310319
  if (typeof passphrase !== "string" || passphrase.trim().length === 0) {
310020
310320
  res.status(400).json({ error: "invalid-passphrase" });
310021
310321
  return;
310022
310322
  }
310023
- const { store: scopedStore } = await getProjectContext2(req);
310024
- const secretsStore = await scopedStore.getSecretsStore();
310323
+ const secretsStore = await getExplicitSecretsStore(req, ctx);
310025
310324
  const hadConfiguredPassphrase = await hasSyncPassphraseConfigured(secretsStore);
310026
310325
  await setSyncPassphrase(secretsStore, passphrase);
310027
310326
  emitSecretsAudit(req, ctx, hadConfiguredPassphrase ? "secret:update" : "secret:create", {
@@ -310037,8 +310336,7 @@ var init_register_secrets_routes = __esm({
310037
310336
  });
310038
310337
  router.delete("/secrets/sync-passphrase", async (req, res) => {
310039
310338
  try {
310040
- const { store: scopedStore } = await getProjectContext2(req);
310041
- const secretsStore = await scopedStore.getSecretsStore();
310339
+ const secretsStore = await getExplicitSecretsStore(req, ctx);
310042
310340
  await clearSyncPassphrase(secretsStore);
310043
310341
  emitSecretsAudit(req, ctx, "secret:delete", { key: RESERVED_SYNC_PASSPHRASE_KEY, scope: "global" });
310044
310342
  res.json({ success: true });
@@ -310050,6 +310348,7 @@ var init_register_secrets_routes = __esm({
310050
310348
  });
310051
310349
  router.post("/secrets", async (req, res) => {
310052
310350
  try {
310351
+ getExplicitProjectId(req, ctx);
310053
310352
  assertObject(req.body);
310054
310353
  const { scope, key, value, description, accessPolicy, envExportable, envExportKey } = req.body;
310055
310354
  const parsedScope = parseScope(scope);
@@ -310066,8 +310365,7 @@ var init_register_secrets_routes = __esm({
310066
310365
  }
310067
310366
  parsedAccessPolicy = accessPolicy;
310068
310367
  }
310069
- const { store: scopedStore } = await getProjectContext2(req);
310070
- const secretsStore = await scopedStore.getSecretsStore();
310368
+ const secretsStore = await getExplicitSecretsStore(req, ctx);
310071
310369
  const secret = await secretsStore.createSecret({
310072
310370
  scope: parsedScope,
310073
310371
  key,
@@ -310086,6 +310384,7 @@ var init_register_secrets_routes = __esm({
310086
310384
  });
310087
310385
  router.patch("/secrets/:scope/:id", async (req, res) => {
310088
310386
  try {
310387
+ getExplicitProjectId(req, ctx);
310089
310388
  const scope = parseScope(req.params.scope);
310090
310389
  const id = String(req.params.id ?? "").trim();
310091
310390
  if (!id) throw badRequest("id is required");
@@ -310129,8 +310428,7 @@ var init_register_secrets_routes = __esm({
310129
310428
  patch.plaintextValue = req.body.value;
310130
310429
  }
310131
310430
  }
310132
- const { store: scopedStore } = await getProjectContext2(req);
310133
- const secretsStore = await scopedStore.getSecretsStore();
310431
+ const secretsStore = await getExplicitSecretsStore(req, ctx);
310134
310432
  const secret = await secretsStore.updateSecret(id, scope, patch);
310135
310433
  res.json(secret);
310136
310434
  } catch (err) {
@@ -310141,11 +310439,11 @@ var init_register_secrets_routes = __esm({
310141
310439
  });
310142
310440
  router.delete("/secrets/:scope/:id", async (req, res) => {
310143
310441
  try {
310442
+ getExplicitProjectId(req, ctx);
310144
310443
  const scope = parseScope(req.params.scope);
310145
310444
  const id = String(req.params.id ?? "").trim();
310146
310445
  if (!id) throw badRequest("id is required");
310147
- const { store: scopedStore } = await getProjectContext2(req);
310148
- const secretsStore = await scopedStore.getSecretsStore();
310446
+ const secretsStore = await getExplicitSecretsStore(req, ctx);
310149
310447
  await secretsStore.deleteSecret(id, scope);
310150
310448
  res.status(204).send();
310151
310449
  } catch (err) {
@@ -310156,11 +310454,11 @@ var init_register_secrets_routes = __esm({
310156
310454
  });
310157
310455
  router.post("/secrets/:scope/:id/reveal", async (req, res) => {
310158
310456
  try {
310457
+ getExplicitProjectId(req, ctx);
310159
310458
  const scope = parseScope(req.params.scope);
310160
310459
  const id = String(req.params.id ?? "").trim();
310161
310460
  if (!id) throw badRequest("id is required");
310162
- const { store: scopedStore } = await getProjectContext2(req);
310163
- const secretsStore = await scopedStore.getSecretsStore();
310461
+ const secretsStore = await getExplicitSecretsStore(req, ctx);
310164
310462
  const secret = await secretsStore.revealSecret(id, scope, {
310165
310463
  userId: null
310166
310464
  });
@@ -311759,7 +312057,7 @@ var init_register_gitlab = __esm({
311759
312057
 
311760
312058
  // ../dashboard/src/terminal.ts
311761
312059
  import { spawn as spawn11 } from "node:child_process";
311762
- import { randomUUID as randomUUID53 } from "node:crypto";
312060
+ import { randomUUID as randomUUID54 } from "node:crypto";
311763
312061
  import { EventEmitter as EventEmitter42 } from "node:events";
311764
312062
  function extractBaseCommand(command) {
311765
312063
  let trimmed = command.trim();
@@ -311919,7 +312217,7 @@ var init_terminal = __esm({
311919
312217
  if (!validation.valid) {
311920
312218
  return { sessionId: "", error: validation.error };
311921
312219
  }
311922
- const sessionId = randomUUID53();
312220
+ const sessionId = randomUUID54();
311923
312221
  const childProcess6 = spawn11(command, [], {
311924
312222
  cwd,
311925
312223
  shell: true,
@@ -340729,7 +341027,7 @@ var init_register_node_routes = __esm({
340729
341027
  });
340730
341028
 
340731
341029
  // ../dashboard/src/routes/register-docker-node-routes.ts
340732
- import { randomUUID as randomUUID54 } from "node:crypto";
341030
+ import { randomUUID as randomUUID55 } from "node:crypto";
340733
341031
  function sanitizeHostConfig(input) {
340734
341032
  const host = input ?? {};
340735
341033
  return {
@@ -341108,7 +341406,7 @@ var init_register_docker_node_routes = __esm({
341108
341406
  if (!managedNode) {
341109
341407
  throw notFound("Managed Docker node not found");
341110
341408
  }
341111
- const newKey = randomUUID54().replace(/-/g, "");
341409
+ const newKey = randomUUID55().replace(/-/g, "");
341112
341410
  await central.updateManagedDockerNode(managedId, { apiKey: newKey });
341113
341411
  if (managedNode.nodeId) {
341114
341412
  await central.updateNode(managedNode.nodeId, { apiKey: newKey });
@@ -349976,7 +350274,7 @@ var init_register_agent_reflection_rating_routes = __esm({
349976
350274
  });
349977
350275
 
349978
350276
  // ../dashboard/src/agent-generation.ts
349979
- import { randomUUID as randomUUID55 } from "node:crypto";
350277
+ import { randomUUID as randomUUID56 } from "node:crypto";
349980
350278
  async function initPromptCatalog() {
349981
350279
  if (promptCatalogReady) return;
349982
350280
  try {
@@ -350182,7 +350480,7 @@ async function startAgentGeneration(ip, roleDescription) {
350182
350480
  `Rate limit exceeded. Maximum ${MAX_SESSIONS_PER_IP_PER_HOUR4} generation sessions per hour. Reset at ${resetTime?.toISOString() || "unknown"}`
350183
350481
  );
350184
350482
  }
350185
- const sessionId = randomUUID55();
350483
+ const sessionId = randomUUID56();
350186
350484
  const session = {
350187
350485
  id: sessionId,
350188
350486
  ip,
@@ -350381,7 +350679,7 @@ __export(agent_onboarding_exports, {
350381
350679
  startAgentOnboardingSession: () => startAgentOnboardingSession,
350382
350680
  stopAgentOnboardingGeneration: () => stopAgentOnboardingGeneration
350383
350681
  });
350384
- import { randomUUID as randomUUID56 } from "node:crypto";
350682
+ import { randomUUID as randomUUID57 } from "node:crypto";
350385
350683
  import { EventEmitter as EventEmitter43 } from "node:events";
350386
350684
  function extractJsonCandidate6(text3) {
350387
350685
  const codeBlockMatch = text3.match(/```(?:json)?\s*([\s\S]*?)\s*```/);
@@ -350481,7 +350779,7 @@ Current agent configuration:
350481
350779
  ${currentConfigLines}`;
350482
350780
  }
350483
350781
  async function startAgentOnboardingSession(ip, initialContext, rootDir, modelProvider, modelId, promptOverrides, pluginRunner, store) {
350484
- const id = randomUUID56();
350782
+ const id = randomUUID57();
350485
350783
  const mode = initialContext.mode ?? "create";
350486
350784
  const session = {
350487
350785
  id,
@@ -353263,7 +353561,7 @@ var init_src8 = __esm({
353263
353561
 
353264
353562
  // ../../plugins/fusion-plugin-openclaw-runtime/src/pi-module.ts
353265
353563
  import { spawn as spawn15 } from "node:child_process";
353266
- import { randomUUID as randomUUID57 } from "node:crypto";
353564
+ import { randomUUID as randomUUID58 } from "node:crypto";
353267
353565
  import { readFile as readFile51 } from "node:fs/promises";
353268
353566
  function asString2(v2) {
353269
353567
  return typeof v2 === "string" && v2.trim() !== "" ? v2.trim() : void 0;
@@ -353323,7 +353621,7 @@ function extractStderrError(stderr, stdout) {
353323
353621
  }
353324
353622
  function createCliSession(opts) {
353325
353623
  return {
353326
- sessionId: randomUUID57(),
353624
+ sessionId: randomUUID58(),
353327
353625
  agentId: opts.agentId ?? DEFAULT_AGENT_ID,
353328
353626
  systemPrompt: opts.systemPrompt,
353329
353627
  messages: [{ role: "developer", content: opts.systemPrompt }],
@@ -353518,7 +353816,7 @@ var init_mcp_config2 = __esm({
353518
353816
  });
353519
353817
 
353520
353818
  // ../../plugins/fusion-plugin-openclaw-runtime/src/runtime-adapter.ts
353521
- import { randomUUID as randomUUID58 } from "node:crypto";
353819
+ import { randomUUID as randomUUID59 } from "node:crypto";
353522
353820
  var OpenClawRuntimeAdapter;
353523
353821
  var init_runtime_adapter2 = __esm({
353524
353822
  "../../plugins/fusion-plugin-openclaw-runtime/src/runtime-adapter.ts"() {
@@ -353541,8 +353839,8 @@ var init_runtime_adapter2 = __esm({
353541
353839
  let mcpProfile;
353542
353840
  let mcpConfigPath;
353543
353841
  if (toolDefs.length > 0) {
353544
- const bridge = writeOpenClawMcpBridgeFiles(toolDefs, randomUUID58());
353545
- mcpProfile = `fusion-${randomUUID58()}`;
353842
+ const bridge = writeOpenClawMcpBridgeFiles(toolDefs, randomUUID59());
353843
+ mcpProfile = `fusion-${randomUUID59()}`;
353546
353844
  mcpConfigPath = bridge.serverConfigPath;
353547
353845
  await configureOpenClawMcpServer({
353548
353846
  binaryPath: this.config.binaryPath,
@@ -357597,7 +357895,7 @@ import { existsSync as existsSync77, writeFileSync as writeFileSync15 } from "no
357597
357895
  import { tmpdir as tmpdir17 } from "node:os";
357598
357896
  import { dirname as dirname45, join as join104 } from "node:path";
357599
357897
  import { fileURLToPath as fileURLToPath11 } from "node:url";
357600
- import { randomUUID as randomUUID59 } from "node:crypto";
357898
+ import { randomUUID as randomUUID60 } from "node:crypto";
357601
357899
  function toolsToMcpToolDefs2(tools) {
357602
357900
  if (!Array.isArray(tools)) return [];
357603
357901
  return tools.filter((tool) => tool && typeof tool.name === "string" && tool.name.trim().length > 0 && !BUILT_IN_TOOL_NAMES2.has(tool.name)).map((tool) => ({
@@ -357648,7 +357946,7 @@ async function startFusionToolBridge(tools) {
357648
357946
  byName.set(tool.name, tool);
357649
357947
  }
357650
357948
  }
357651
- const schemaPath = join104(tmpdir17(), `fusion-grok-mcp-schemas-${process.pid}-${randomUUID59()}.json`);
357949
+ const schemaPath = join104(tmpdir17(), `fusion-grok-mcp-schemas-${process.pid}-${randomUUID60()}.json`);
357652
357950
  writeFileSync15(schemaPath, JSON.stringify(defs));
357653
357951
  const server = createServer2(async (req, res) => {
357654
357952
  if (req.method !== "POST" || req.url !== "/tool-call") {
@@ -357674,7 +357972,7 @@ async function startFusionToolBridge(tools) {
357674
357972
  return;
357675
357973
  }
357676
357974
  try {
357677
- const result2 = await tool.execute(`grok-mcp-${randomUUID59()}`, parsed.arguments ?? {}, void 0, void 0, void 0);
357975
+ const result2 = await tool.execute(`grok-mcp-${randomUUID60()}`, parsed.arguments ?? {}, void 0, void 0, void 0);
357678
357976
  res.statusCode = 200;
357679
357977
  res.setHeader("content-type", "application/json");
357680
357978
  res.end(
@@ -359718,7 +360016,7 @@ import { existsSync as existsSync81, rmSync as rmSync13, writeFileSync as writeF
359718
360016
  import { tmpdir as tmpdir19 } from "node:os";
359719
360017
  import { dirname as dirname50, join as join109 } from "node:path";
359720
360018
  import { fileURLToPath as fileURLToPath15 } from "node:url";
359721
- import { randomBytes as randomBytes8, randomUUID as randomUUID60 } from "node:crypto";
360019
+ import { randomBytes as randomBytes8, randomUUID as randomUUID61 } from "node:crypto";
359722
360020
  function toolsToMcpToolDefs3(tools) {
359723
360021
  if (!Array.isArray(tools)) return [];
359724
360022
  return tools.filter((tool) => tool && typeof tool.name === "string" && tool.name.trim().length > 0 && !BUILT_IN_TOOL_NAMES3.has(tool.name)).map((tool) => ({
@@ -359769,7 +360067,7 @@ async function startFusionToolBridge2(tools, options = {}) {
359769
360067
  byName.set(tool.name, tool);
359770
360068
  }
359771
360069
  }
359772
- const schemaPath = join109(tmpdir19(), `fusion-claude-mcp-schemas-${process.pid}-${randomUUID60()}.json`);
360070
+ const schemaPath = join109(tmpdir19(), `fusion-claude-mcp-schemas-${process.pid}-${randomUUID61()}.json`);
359773
360071
  writeFileSync17(schemaPath, JSON.stringify(defs));
359774
360072
  const capabilityToken = randomBytes8(32).toString("base64url");
359775
360073
  const server = createServer3(async (req, res) => {
@@ -359834,7 +360132,7 @@ async function startFusionToolBridge2(tools, options = {}) {
359834
360132
  return;
359835
360133
  }
359836
360134
  try {
359837
- const result2 = await tool.execute(`claude-mcp-${randomUUID60()}`, parsed.arguments ?? {}, void 0, void 0, void 0);
360135
+ const result2 = await tool.execute(`claude-mcp-${randomUUID61()}`, parsed.arguments ?? {}, void 0, void 0, void 0);
359838
360136
  res.statusCode = 200;
359839
360137
  res.setHeader("content-type", "application/json");
359840
360138
  res.end(
@@ -361911,7 +362209,7 @@ import { existsSync as existsSync83, unlinkSync as unlinkSync5, writeFileSync as
361911
362209
  import { tmpdir as tmpdir20 } from "node:os";
361912
362210
  import { dirname as dirname53, join as join112 } from "node:path";
361913
362211
  import { fileURLToPath as fileURLToPath17 } from "node:url";
361914
- import { randomUUID as randomUUID61 } from "node:crypto";
362212
+ import { randomUUID as randomUUID62 } from "node:crypto";
361915
362213
  function toolsToMcpToolDefs4(tools) {
361916
362214
  if (!Array.isArray(tools)) return [];
361917
362215
  return tools.filter(
@@ -361968,7 +362266,7 @@ async function startFusionToolBridge3(tools) {
361968
362266
  byName.set(tool.name, tool);
361969
362267
  }
361970
362268
  }
361971
- const schemaPath = join112(tmpdir20(), `fusion-omp-mcp-schemas-${process.pid}-${randomUUID61()}.json`);
362269
+ const schemaPath = join112(tmpdir20(), `fusion-omp-mcp-schemas-${process.pid}-${randomUUID62()}.json`);
361972
362270
  writeFileSync18(schemaPath, JSON.stringify(defs));
361973
362271
  const server = createServer4(async (req, res) => {
361974
362272
  if (req.method !== "POST" || req.url !== "/tool-call") {
@@ -361995,7 +362293,7 @@ async function startFusionToolBridge3(tools) {
361995
362293
  }
361996
362294
  try {
361997
362295
  const result2 = await tool.execute(
361998
- `omp-mcp-${randomUUID61()}`,
362296
+ `omp-mcp-${randomUUID62()}`,
361999
362297
  parsed.arguments ?? {},
362000
362298
  void 0,
362001
362299
  void 0,
@@ -363091,7 +363389,7 @@ var init_paperclip_client = __esm({
363091
363389
  });
363092
363390
 
363093
363391
  // ../../plugins/fusion-plugin-paperclip-runtime/src/runtime-adapter.ts
363094
- import { randomUUID as randomUUID62 } from "node:crypto";
363392
+ import { randomUUID as randomUUID63 } from "node:crypto";
363095
363393
  function sleep7(ms2) {
363096
363394
  return new Promise((resolve104) => setTimeout(resolve104, ms2));
363097
363395
  }
@@ -363220,7 +363518,7 @@ var init_runtime_adapter10 = __esm({
363220
363518
  apiKey: effectiveApiKey,
363221
363519
  agentId,
363222
363520
  companyId,
363223
- sessionId: randomUUID62(),
363521
+ sessionId: randomUUID63(),
363224
363522
  systemPrompt: options.systemPrompt,
363225
363523
  cwd: options.cwd,
363226
363524
  mode: normalizeMode3(this.config.mode),
@@ -366720,7 +367018,7 @@ var init_command_center_csv = __esm({
366720
367018
  });
366721
367019
 
366722
367020
  // ../dashboard/src/monitor-store.ts
366723
- import { randomUUID as randomUUID63 } from "node:crypto";
367021
+ import { randomUUID as randomUUID64 } from "node:crypto";
366724
367022
  function monitorProjectId(layer) {
366725
367023
  if (!layer.projectId) {
366726
367024
  throw new Error("PostgreSQL monitor writes require asyncLayer.projectId");
@@ -366752,7 +367050,7 @@ async function recordDeployment(db, input) {
366752
367050
  return recordDeploymentAsync(layer.db, input, monitorProjectId(layer));
366753
367051
  }
366754
367052
  const sqliteDb = db;
366755
- const deploymentId = input.deploymentId?.trim() || `dep-${randomUUID63()}`;
367053
+ const deploymentId = input.deploymentId?.trim() || `dep-${randomUUID64()}`;
366756
367054
  const now = (/* @__PURE__ */ new Date()).toISOString();
366757
367055
  const deployedAt = input.deployedAt ?? now;
366758
367056
  const meta2 = input.meta ? JSON.stringify(input.meta) : null;
@@ -366818,7 +367116,7 @@ async function ingestIncidentSignal(db, input) {
366818
367116
  const updated = getIncident(sqliteDb, existing.incidentId);
366819
367117
  return { incident: updated ?? existing, created: false };
366820
367118
  }
366821
- const incidentId = `inc-${randomUUID63()}`;
367119
+ const incidentId = `inc-${randomUUID64()}`;
366822
367120
  const meta2 = {
366823
367121
  ...input.meta ?? {},
366824
367122
  [OCCURRENCES_META_KEY2]: 1,
@@ -371419,7 +371717,7 @@ var init_types6 = __esm({
371419
371717
  });
371420
371718
 
371421
371719
  // ../dashboard/src/stt/model-manager.ts
371422
- import { createHash as createHash31, randomUUID as randomUUID64 } from "node:crypto";
371720
+ import { createHash as createHash31, randomUUID as randomUUID65 } from "node:crypto";
371423
371721
  import { mkdir as mkdir38, open as open4, readFile as readFile56, readdir as readdir14, rename as rename10, rm as rm21, stat as stat16, writeFile as writeFile37 } from "node:fs/promises";
371424
371722
  import { join as join118, relative as relative22, resolve as resolve68, sep as sep18 } from "node:path";
371425
371723
  function parseSafeTarListing(listing) {
@@ -371500,8 +371798,8 @@ function createVoiceModelManager(options = {}) {
371500
371798
  if (!current(g2, signal)) return state;
371501
371799
  await mkdir38(cacheDir(), { recursive: true });
371502
371800
  if (!current(g2, signal)) return state;
371503
- partial2 = join118(cacheDir(), `.partial-${g2}-${randomUUID64()}`);
371504
- staging = join118(cacheDir(), `.staging-${g2}-${randomUUID64()}`);
371801
+ partial2 = join118(cacheDir(), `.partial-${g2}-${randomUUID65()}`);
371802
+ staging = join118(cacheDir(), `.staging-${g2}-${randomUUID65()}`);
371505
371803
  const file = await open4(partial2, "wx");
371506
371804
  try {
371507
371805
  const response = await (options.fetch ?? globalThis.fetch)(asset.url, { signal });
@@ -371549,7 +371847,7 @@ function createVoiceModelManager(options = {}) {
371549
371847
  await writeFile37(join118(staging, "manifest.json"), JSON.stringify({ filename: asset.filename, sha256: asset.sha256, installedAt: (/* @__PURE__ */ new Date()).toISOString(), expectedFiles: asset.expectedFiles }));
371550
371848
  await options.beforePromote?.(signal);
371551
371849
  if (!current(g2, signal)) return state;
371552
- const backup = join118(cacheDir(), `.backup-${g2}-${randomUUID64()}`);
371850
+ const backup = join118(cacheDir(), `.backup-${g2}-${randomUUID65()}`);
371553
371851
  let movedPrevious = false;
371554
371852
  try {
371555
371853
  try {
@@ -371770,7 +372068,7 @@ var init_parakeet_service = __esm({
371770
372068
  });
371771
372069
 
371772
372070
  // ../dashboard/src/routes/register-voice-routes.ts
371773
- import { randomUUID as randomUUID65 } from "node:crypto";
372071
+ import { randomUUID as randomUUID66 } from "node:crypto";
371774
372072
  import express from "express";
371775
372073
  async function settingsFor(ctx, req) {
371776
372074
  const scoped = await ctx.getScopedStore(req);
@@ -371849,7 +372147,7 @@ function createRegisterVoiceRoutes(deps = {}) {
371849
372147
  recognizer.close();
371850
372148
  return error2(res, 409, { error: "unavailable", reason: "model-removed" });
371851
372149
  }
371852
- const id = randomUUID65();
372150
+ const id = randomUUID66();
371853
372151
  const now = Date.now();
371854
372152
  sessions7.set(id, { projectId, recognizer, next: 0, bytes: 0, last: now, expires: now + 3e5 });
371855
372153
  res.status(201).json({ sessionId: id, expiresAt: new Date(now + 3e5).toISOString(), modelId: model.id, language: language.language });
@@ -372320,7 +372618,7 @@ var init_fn_binary_local_install = __esm({
372320
372618
  });
372321
372619
 
372322
372620
  // ../dashboard/src/routes/register-system-routes.ts
372323
- import { randomUUID as randomUUID66 } from "node:crypto";
372621
+ import { randomUUID as randomUUID67 } from "node:crypto";
372324
372622
  import { existsSync as existsSync87 } from "node:fs";
372325
372623
  import { join as join120 } from "node:path";
372326
372624
  function rejectCrossOrigin(req, res) {
@@ -372517,7 +372815,7 @@ function registerSystemRoutes(ctx, deps) {
372517
372815
  throw new ApiError(409, `Build script missing: ${scriptPath}`);
372518
372816
  }
372519
372817
  const job = {
372520
- id: randomUUID66(),
372818
+ id: randomUUID67(),
372521
372819
  kind: "rebuild",
372522
372820
  scope,
372523
372821
  restartAfter,
@@ -372807,7 +373105,7 @@ function registerSystemRoutes(ctx, deps) {
372807
373105
  throw new ApiError(409, `A ${activeJob.kind}/${activeJob.scope} job is already running`);
372808
373106
  }
372809
373107
  const job = {
372810
- id: randomUUID66(),
373108
+ id: randomUUID67(),
372811
373109
  kind,
372812
373110
  scope,
372813
373111
  restartAfter,
@@ -373095,7 +373393,7 @@ var init_cli_agent_settings = __esm({
373095
373393
 
373096
373394
  // ../dashboard/src/mission-routes.ts
373097
373395
  import { Router as Router5 } from "express";
373098
- import { AsyncLocalStorage } from "node:async_hooks";
373396
+ import { AsyncLocalStorage as AsyncLocalStorage2 } from "node:async_hooks";
373099
373397
  function resolveMissionInterviewThinkingLevel(settings, thinkingLevel) {
373100
373398
  return resolvePlanningThinkingLevel(settings, thinkingLevel);
373101
373399
  }
@@ -373279,7 +373577,7 @@ function replayBufferedSSE(res, bufferedEvents) {
373279
373577
  }
373280
373578
  function createMissionRouter(store, missionAutopilot, aiSessionStore, missionExecutionLoop, engineManager, pluginRunner, options) {
373281
373579
  const router = Router5();
373282
- const requestContext = new AsyncLocalStorage();
373580
+ const requestContext = new AsyncLocalStorage2();
373283
373581
  function getScopedStore2() {
373284
373582
  return requestContext.getStore() ?? store;
373285
373583
  }
@@ -375790,11 +376088,11 @@ var init_mission_routes = __esm({
375790
376088
  });
375791
376089
 
375792
376090
  // ../dashboard/src/ideation-routes.ts
375793
- import { AsyncLocalStorage as AsyncLocalStorage2 } from "node:async_hooks";
376091
+ import { AsyncLocalStorage as AsyncLocalStorage3 } from "node:async_hooks";
375794
376092
  import { Router as Router6 } from "express";
375795
376093
  function createIdeationRouter(store, options) {
375796
376094
  const router = Router6();
375797
- const requestContext = new AsyncLocalStorage2();
376095
+ const requestContext = new AsyncLocalStorage3();
375798
376096
  const ideation = () => (requestContext.getStore() ?? store).getIdeationStore();
375799
376097
  router.use(async (req, _res, next) => {
375800
376098
  try {
@@ -375978,7 +376276,7 @@ var init_insight_run_sweeper = __esm({
375978
376276
 
375979
376277
  // ../dashboard/src/insights-routes.ts
375980
376278
  import { Router as Router7 } from "express";
375981
- import { AsyncLocalStorage as AsyncLocalStorage3 } from "node:async_hooks";
376279
+ import { AsyncLocalStorage as AsyncLocalStorage4 } from "node:async_hooks";
375982
376280
  function rethrowAsApiError4(error3, fallbackMessage = "Internal server error") {
375983
376281
  if (error3 instanceof ApiError) throw error3;
375984
376282
  if (error3 instanceof Error) throw new ApiError(500, error3.message);
@@ -376122,7 +376420,7 @@ function toInsightTitle(content) {
376122
376420
  }
376123
376421
  function createInsightsRouter(store, options) {
376124
376422
  const router = Router7();
376125
- const requestContext = new AsyncLocalStorage3();
376423
+ const requestContext = new AsyncLocalStorage4();
376126
376424
  let rootInsightStore;
376127
376425
  if (typeof store.getInsightStore === "function") {
376128
376426
  try {
@@ -376626,7 +376924,7 @@ var init_insights_routes = __esm({
376626
376924
 
376627
376925
  // ../dashboard/src/evals-routes.ts
376628
376926
  import { Router as Router8 } from "express";
376629
- import { AsyncLocalStorage as AsyncLocalStorage4 } from "node:async_hooks";
376927
+ import { AsyncLocalStorage as AsyncLocalStorage5 } from "node:async_hooks";
376630
376928
  function rethrowAsApiError5(error3, fallbackMessage = "Internal server error") {
376631
376929
  if (error3 instanceof ApiError) throw error3;
376632
376930
  if (error3 instanceof Error) throw new ApiError(500, error3.message);
@@ -376653,7 +376951,7 @@ function normalizeEvalText(result2) {
376653
376951
  }
376654
376952
  function createEvalsRouter(store, options) {
376655
376953
  const router = Router8();
376656
- const requestContext = new AsyncLocalStorage4();
376954
+ const requestContext = new AsyncLocalStorage5();
376657
376955
  router.use((req, _res, next) => {
376658
376956
  getScopedStore(req, store, options).then((scopedStore) => requestContext.run(scopedStore, () => next())).catch((err) => {
376659
376957
  try {
@@ -376737,7 +377035,7 @@ var init_evals_routes = __esm({
376737
377035
 
376738
377036
  // ../dashboard/src/research-routes.ts
376739
377037
  import { Router as Router9 } from "express";
376740
- import { AsyncLocalStorage as AsyncLocalStorage5 } from "node:async_hooks";
377038
+ import { AsyncLocalStorage as AsyncLocalStorage6 } from "node:async_hooks";
376741
377039
  function rethrowAsApiError6(error3, fallback2 = "Internal server error") {
376742
377040
  if (error3 instanceof ApiError) throw error3;
376743
377041
  if (error3 instanceof ResearchLifecycleError) {
@@ -376829,7 +377127,7 @@ async function addFindingAttachment(scopedStore, taskId, filename, markdown) {
376829
377127
  }
376830
377128
  function createResearchRouter(store, options) {
376831
377129
  const router = Router9();
376832
- const requestContext = new AsyncLocalStorage5();
377130
+ const requestContext = new AsyncLocalStorage6();
376833
377131
  router.use((req, _res, next) => {
376834
377132
  getScopedStore(req, store, options).then((scopedStore) => requestContext.run(scopedStore, () => next())).catch((error3) => {
376835
377133
  try {
@@ -377332,7 +377630,7 @@ var init_experiment_routes = __esm({
377332
377630
 
377333
377631
  // ../dashboard/src/goals-routes.ts
377334
377632
  import { Router as Router11 } from "express";
377335
- import { AsyncLocalStorage as AsyncLocalStorage6 } from "node:async_hooks";
377633
+ import { AsyncLocalStorage as AsyncLocalStorage7 } from "node:async_hooks";
377336
377634
  function getGoalStore(store) {
377337
377635
  return store.getGoalStore();
377338
377636
  }
@@ -377388,7 +377686,7 @@ function rethrowGoalCapError(error3) {
377388
377686
  }
377389
377687
  function createGoalsRouter(store, options) {
377390
377688
  const router = Router11();
377391
- const requestContext = new AsyncLocalStorage6();
377689
+ const requestContext = new AsyncLocalStorage7();
377392
377690
  function getScopedStore2() {
377393
377691
  return requestContext.getStore() ?? store;
377394
377692
  }
@@ -384061,7 +384359,7 @@ var init_provider_health_monitor = __esm({
384061
384359
 
384062
384360
  // ../dashboard/src/server.ts
384063
384361
  import express3 from "express";
384064
- import { randomUUID as randomUUID67 } from "node:crypto";
384362
+ import { randomUUID as randomUUID68 } from "node:crypto";
384065
384363
  import { join as join125, dirname as dirname57 } from "node:path";
384066
384364
  import { existsSync as existsSync89, readFileSync as readFileSync32, statSync as statSync16 } from "node:fs";
384067
384365
  import { fileURLToPath as fileURLToPath18 } from "node:url";
@@ -385526,7 +385824,7 @@ function setupBadgeWebSocket(app, server, store, options) {
385526
385824
  const dashboardApp = app;
385527
385825
  const wsManager = new WebSocketManager();
385528
385826
  const badgeSnapshots = /* @__PURE__ */ new Map();
385529
- const serverId = randomUUID67();
385827
+ const serverId = randomUUID68();
385530
385828
  const badgePubSub = options?.badgePubSub ?? createBadgePubSub({ sourceId: serverId });
385531
385829
  void badgePubSub.start();
385532
385830
  const scopedStores = /* @__PURE__ */ new Map();
@@ -385668,7 +385966,7 @@ function setupBadgeWebSocket(app, server, store, options) {
385668
385966
  const url2 = new URL(req.url || "", `http://${req.headers.host}`);
385669
385967
  const projectId = url2.searchParams.get("projectId") ?? "default";
385670
385968
  void ensureScopedListeners(projectId);
385671
- wsManager.addClient(ws2, randomUUID67(), projectId);
385969
+ wsManager.addClient(ws2, randomUUID68(), projectId);
385672
385970
  });
385673
385971
  server.once("close", async () => {
385674
385972
  for (const cleanup of scopedCleanups.values()) {
@@ -386500,7 +386798,7 @@ import { Type as Type9 } from "typebox";
386500
386798
  import { StringEnum } from "@earendil-works/pi-ai";
386501
386799
  import { resolve as resolve75, relative as relative25, isAbsolute as isAbsolute33, sep as sep22, basename as basename36, extname as extname5, join as join127 } from "node:path";
386502
386800
  import { existsSync as existsSync90 } from "node:fs";
386503
- import { AsyncLocalStorage as AsyncLocalStorage7 } from "node:async_hooks";
386801
+ import { AsyncLocalStorage as AsyncLocalStorage8 } from "node:async_hooks";
386504
386802
  function resolveProjectRoot2(cwd) {
386505
386803
  const worktreeProjectRoot = typeof getProjectRootFromWorktree === "function" ? getProjectRootFromWorktree(cwd) : null;
386506
386804
  if (typeof getProjectRootFromWorktree !== "function" && !warnedMissingProjectRootResolver) {
@@ -386566,7 +386864,7 @@ var init_extension = __esm({
386566
386864
  storeCache2 = extensionStoreState.cache;
386567
386865
  storeBootInflight = extensionStoreState.bootInflight;
386568
386866
  storeBootFailureCooldown = extensionStoreState.bootFailureCooldown;
386569
- extensionToolSignal = new AsyncLocalStorage7();
386867
+ extensionToolSignal = new AsyncLocalStorage8();
386570
386868
  }
386571
386869
  });
386572
386870
 
@@ -396969,6 +397267,7 @@ var init_dev_source_restart = __esm({
396969
397267
  var dashboard_exports = {};
396970
397268
  __export(dashboard_exports, {
396971
397269
  StreamedLogBuffer: () => StreamedLogBuffer,
397270
+ buildUiOnlyHeartbeatMonitorOptions: () => buildUiOnlyHeartbeatMonitorOptions,
396972
397271
  classifyDashboardFatalExit: () => classifyDashboardFatalExit,
396973
397272
  countActiveTasks: () => countActiveTasks,
396974
397273
  hasLiveSupervisingParent: () => hasLiveSupervisingParent,
@@ -396997,6 +397296,15 @@ async function countActiveTasks(store, tasks2) {
396997
397296
  }
396998
397297
  return active;
396999
397298
  }
397299
+ function buildUiOnlyHeartbeatMonitorOptions(input) {
397300
+ return {
397301
+ store: input.agentStore,
397302
+ agentStore: input.agentStore,
397303
+ taskStore: input.taskStore,
397304
+ rootDir: input.rootDir,
397305
+ secretsStore: input.secretsStore
397306
+ };
397307
+ }
397000
397308
  function formatRuntimeContext(context) {
397001
397309
  if (context === void 0) {
397002
397310
  return "";
@@ -398481,11 +398789,14 @@ async function runDashboard(port, opts = {}) {
398481
398789
  logSink.warn(`Failed to initialize mesh networking: ${message}`, "dashboard");
398482
398790
  }
398483
398791
  try {
398792
+ const secretsStore = await store.getSecretsStore();
398484
398793
  heartbeatMonitorImpl = new HeartbeatMonitor({
398485
- store: agentStore,
398486
- agentStore,
398487
- taskStore: store,
398488
- rootDir: cwd,
398794
+ ...buildUiOnlyHeartbeatMonitorOptions({
398795
+ agentStore,
398796
+ taskStore: store,
398797
+ rootDir: cwd,
398798
+ secretsStore
398799
+ }),
398489
398800
  onMissed: (agentId, reason) => {
398490
398801
  logSink.warn(`Agent ${agentId} missed heartbeat: ${reason}`, "engine");
398491
398802
  },