@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/extension.js CHANGED
@@ -1103,6 +1103,9 @@ function isFusionSiblingBranch(branch) {
1103
1103
  function resolveEffectiveAutoMerge(task, settings) {
1104
1104
  return task.autoMerge ?? settings.autoMerge;
1105
1105
  }
1106
+ function hasUserAutoMergeHold(task) {
1107
+ return task.autoMerge === false && task.autoMergeProvenance === "user";
1108
+ }
1106
1109
  function allowsAutoMergeProcessing(task, settings) {
1107
1110
  return (settings.autoMerge !== false || task.autoMerge === true) && !taskHasManualOpenPullRequest(task);
1108
1111
  }
@@ -9968,6 +9971,7 @@ function validateForeach(node, topLevelNodeIds, columnIds) {
9968
9971
  );
9969
9972
  }
9970
9973
  for (const inner of templateNodes) {
9974
+ validateReviewKind(inner, "nested");
9971
9975
  if (inner.kind === "foreach" || inner.kind === "loop") {
9972
9976
  throw new WorkflowIrError(
9973
9977
  `foreach node '${node.id}' template may not contain nested loop/foreach ('${inner.id}')`
@@ -10086,6 +10090,7 @@ function validateLoop(node, topLevelNodeIds, columnIds) {
10086
10090
  );
10087
10091
  }
10088
10092
  for (const inner of templateNodes) {
10093
+ validateReviewKind(inner, "nested");
10089
10094
  if (inner.kind === "loop" || inner.kind === "foreach") {
10090
10095
  throw new WorkflowIrError(
10091
10096
  `loop node '${node.id}' template may not contain nested loop/foreach ('${inner.id}')`
@@ -10179,6 +10184,7 @@ function validateOptionalGroup(node, topLevelNodeIds, columnIds) {
10179
10184
  throw new WorkflowIrError(`optional-group node '${node.id}' template has duplicate node ids`);
10180
10185
  }
10181
10186
  for (const inner of templateNodes) {
10187
+ validateReviewKind(inner, "nested");
10182
10188
  if (inner.kind === "loop" || inner.kind === "foreach" || inner.kind === "optional-group") {
10183
10189
  throw new WorkflowIrError(
10184
10190
  `optional-group node '${node.id}' template may not contain nested loop/foreach/optional-group ('${inner.id}')`
@@ -10850,6 +10856,19 @@ function validateColumnRecovery(column) {
10850
10856
  );
10851
10857
  }
10852
10858
  }
10859
+ function validateReviewKind(node, placement) {
10860
+ const value = node.config?.reviewKind;
10861
+ if (value === void 0) return;
10862
+ if (value !== "plan" && value !== "code") {
10863
+ throw new WorkflowIrError(`Workflow node '${node.id}' has invalid reviewKind '${String(value)}'; expected 'plan' or 'code'`);
10864
+ }
10865
+ if (placement === "nested") {
10866
+ throw new WorkflowIrError(`Workflow node '${node.id}' has reviewKind in an unsupported nested template placement`);
10867
+ }
10868
+ if (node.kind !== "prompt" && node.kind !== "gate" && node.kind !== "script" && node.kind !== "optional-group") {
10869
+ throw new WorkflowIrError(`Workflow node '${node.id}' has reviewKind on unsupported node kind '${node.kind}'`);
10870
+ }
10871
+ }
10853
10872
  function validateV2(ir2) {
10854
10873
  validateColumns(ir2);
10855
10874
  for (const [index2, column] of ir2.columns.entries()) {
@@ -10872,6 +10891,7 @@ function validateV2(ir2) {
10872
10891
  }
10873
10892
  const nodesById = new Map(ir2.nodes.map((n) => [n.id, n]));
10874
10893
  for (const node of ir2.nodes) {
10894
+ validateReviewKind(node, "top-level");
10875
10895
  validateExtensionMetadata(`Workflow node '${node.id}'`, node.extensions);
10876
10896
  if (node.column !== void 0 && !columnIds.has(node.column)) {
10877
10897
  throw new WorkflowIrError(
@@ -13335,6 +13355,7 @@ function codeReviewOptionalGroupNode(column, options = {}) {
13335
13355
  column,
13336
13356
  config: {
13337
13357
  name: CODE_REVIEW_NAME,
13358
+ reviewKind: "code",
13338
13359
  // Default-ON: runs for every coding task by default, but operators can toggle it
13339
13360
  // off per task (remove `code-review` from enabledWorkflowSteps).
13340
13361
  defaultOn: options.defaultOn ?? true,
@@ -13480,6 +13501,7 @@ function planReviewOptionalGroupNode(column, options = {}) {
13480
13501
  ...column ? { column } : {},
13481
13502
  config: {
13482
13503
  name: PLAN_REVIEW_NAME,
13504
+ reviewKind: "plan",
13483
13505
  defaultOn: options.defaultOn ?? true,
13484
13506
  /*
13485
13507
  * FNXC:WorkflowRemediation 2026-06-29-12:14:
@@ -63324,7 +63346,7 @@ function rowToTask(row) {
63324
63346
  executionStartBranch: row.executionStartBranch || void 0,
63325
63347
  branch: row.branch || void 0,
63326
63348
  autoMerge: row.autoMerge === null ? void 0 : row.autoMerge === 1,
63327
- autoMergeProvenance: row.autoMergeProvenance === "user" || row.autoMergeProvenance === "legacy-stamp" ? row.autoMergeProvenance : void 0,
63349
+ autoMergeProvenance: row.autoMergeProvenance === "user" || row.autoMergeProvenance === "mission" || row.autoMergeProvenance === "legacy-stamp" ? row.autoMergeProvenance : void 0,
63328
63350
  baseCommitSha: row.baseCommitSha || void 0,
63329
63351
  scopeOverride: row.scopeOverride ? true : void 0,
63330
63352
  scopeOverrideReason: row.scopeOverrideReason || void 0,
@@ -72840,10 +72862,10 @@ ${feature.acceptanceCriteria}`);
72840
72862
  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.
72841
72863
  */
72842
72864
  /*
72843
- FNXC:MissionAutoMerge 2026-07-18-12:00:
72844
- 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.
72865
+ FNXC:MissionAutoMerge 2026-08-05-22:50:
72866
+ 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.
72845
72867
  */
72846
- ...mission?.autoMerge === false ? { autoMerge: false } : {},
72868
+ ...mission?.autoMerge === false ? { autoMerge: false, autoMergeProvenance: "mission" } : {},
72847
72869
  // FNXC:MissionTaskPrefix 2026-07-26-12:00: thread the mission's optional taskPrefix into TaskCreateInput for distributed id minting.
72848
72870
  ...mission?.taskPrefix ? { taskPrefix: mission.taskPrefix } : {},
72849
72871
  ...branchOptions?.workflowId !== void 0 ? { workflowId: branchOptions.workflowId } : {}
@@ -76021,10 +76043,10 @@ ${feature.acceptanceCriteria}`);
76021
76043
  }
76022
76044
  } : {},
76023
76045
  /*
76024
- FNXC:MissionAutoMerge 2026-07-18-12:00:
76025
- 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.
76046
+ FNXC:MissionAutoMerge 2026-08-05-22:50:
76047
+ 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.
76026
76048
  */
76027
- ...mission?.autoMerge === false ? { autoMerge: false } : {},
76049
+ ...mission?.autoMerge === false ? { autoMerge: false, autoMergeProvenance: "mission" } : {},
76028
76050
  // 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.
76029
76051
  ...mission?.taskPrefix ? { taskPrefix: mission.taskPrefix } : {},
76030
76052
  ...branchOptions?.workflowId !== void 0 ? { workflowId: branchOptions.workflowId } : {}
@@ -86375,7 +86397,9 @@ async function _createTaskInternalBackendImpl(store, input, title, resolvedWorkf
86375
86397
  sourceMetadata: withTaskBranchContextInSourceMetadata(input.source?.sourceMetadata, input.branchContext),
86376
86398
  branchContext: input.branchContext,
86377
86399
  autoMerge: input.autoMerge,
86378
- autoMergeProvenance: input.autoMerge === void 0 ? void 0 : "user",
86400
+ // FNXC:SharedBranchMemberHold 2026-08-05-22:50: trusted mission creation
86401
+ // preserves policy provenance; operator/API create requests retain user provenance.
86402
+ autoMergeProvenance: input.autoMerge === void 0 ? void 0 : input.autoMergeProvenance ?? "user",
86379
86403
  // FNXC:CodingIdeasWorkflow 2026-07-05-19:45: land the task in its
86380
86404
  // workflow's manual intake column (e.g. Coding (Ideas) → "ideas") when
86381
86405
  // no explicit column is given (main FN-7591 parity).
@@ -127080,6 +127104,36 @@ var init_async_knowledge = __esm({
127080
127104
  });
127081
127105
 
127082
127106
  // ../core/src/workflows/workflow-step-results.ts
127107
+ function normalizeWorkflowReviewFindings(raw) {
127108
+ if (!Array.isArray(raw)) return void 0;
127109
+ const normalized = [];
127110
+ const usedIds = /* @__PURE__ */ new Set();
127111
+ for (const candidate of raw) {
127112
+ if (normalized.length >= MAX_WORKFLOW_REVIEW_FINDINGS || !candidate || typeof candidate !== "object") continue;
127113
+ const value = candidate;
127114
+ const title = boundedTrimmedString(value.title, MAX_FINDING_TITLE_LENGTH);
127115
+ const body = boundedTrimmedString(value.body, MAX_FINDING_BODY_LENGTH);
127116
+ if (!title || !body) continue;
127117
+ const baseId = boundedTrimmedString(value.id, MAX_FINDING_ID_LENGTH) || `finding-${normalized.length + 1}`;
127118
+ let id = baseId;
127119
+ let suffix = 2;
127120
+ while (usedIds.has(id)) id = `${baseId.slice(0, Math.max(1, MAX_FINDING_ID_LENGTH - String(suffix).length - 1))}-${suffix++}`;
127121
+ usedIds.add(id);
127122
+ const filePath = boundedTrimmedString(value.filePath, MAX_FINDING_PATH_LENGTH);
127123
+ const line2 = typeof value.line === "number" && Number.isFinite(value.line) && value.line > 0 ? Math.floor(value.line) : void 0;
127124
+ const severity = isWorkflowReviewFindingSeverity(value.severity) ? value.severity : void 0;
127125
+ normalized.push({ id, title, body, ...filePath ? { filePath } : {}, ...line2 ? { line: line2 } : {}, ...severity ? { severity } : {} });
127126
+ }
127127
+ return normalized.length > 0 ? normalized : void 0;
127128
+ }
127129
+ function boundedTrimmedString(value, maxLength) {
127130
+ if (typeof value !== "string") return void 0;
127131
+ const trimmed = value.trim();
127132
+ return trimmed && trimmed.length <= maxLength ? trimmed : void 0;
127133
+ }
127134
+ function isWorkflowReviewFindingSeverity(value) {
127135
+ return typeof value === "string" && WORKFLOW_REVIEW_FINDING_SEVERITIES.includes(value);
127136
+ }
127083
127137
  function isTerminalFailure(result2) {
127084
127138
  return TERMINAL_FAILURE_STATUSES.has(result2.status);
127085
127139
  }
@@ -127145,10 +127199,16 @@ function makeReviewLeaseRecord(args) {
127145
127199
  leaseOwner: args.owner
127146
127200
  };
127147
127201
  }
127148
- var MAX_WORKFLOW_STEP_PRIOR_ATTEMPTS, TERMINAL_FAILURE_STATUSES, PLAN_REVIEW_LEASE_STALENESS_MS, TERMINAL_STEP_STATUSES;
127202
+ 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;
127149
127203
  var init_workflow_step_results = __esm({
127150
127204
  "../core/src/workflows/workflow-step-results.ts"() {
127151
127205
  "use strict";
127206
+ WORKFLOW_REVIEW_FINDING_SEVERITIES = ["low", "medium", "high", "critical"];
127207
+ MAX_WORKFLOW_REVIEW_FINDINGS = 20;
127208
+ MAX_FINDING_ID_LENGTH = 128;
127209
+ MAX_FINDING_TITLE_LENGTH = 240;
127210
+ MAX_FINDING_BODY_LENGTH = 4e3;
127211
+ MAX_FINDING_PATH_LENGTH = 1e3;
127152
127212
  MAX_WORKFLOW_STEP_PRIOR_ATTEMPTS = 5;
127153
127213
  TERMINAL_FAILURE_STATUSES = /* @__PURE__ */ new Set([
127154
127214
  "failed",
@@ -127217,6 +127277,7 @@ var init_task_timing = __esm({
127217
127277
  });
127218
127278
 
127219
127279
  // ../core/src/session-identity-registry.ts
127280
+ import { AsyncLocalStorage } from "node:async_hooks";
127220
127281
  import { resolve as resolve21 } from "node:path";
127221
127282
  import { realpathSync as realpathSync5 } from "node:fs";
127222
127283
  function getRegistry() {
@@ -127228,6 +127289,16 @@ function getRegistry() {
127228
127289
  }
127229
127290
  return registry3;
127230
127291
  }
127292
+ function getActiveInvocationStorage() {
127293
+ const holder = globalThis;
127294
+ const existing = holder[ACTIVE_INVOCATION_KEY];
127295
+ if (existing instanceof AsyncLocalStorage) {
127296
+ return existing;
127297
+ }
127298
+ const storage = new AsyncLocalStorage();
127299
+ holder[ACTIVE_INVOCATION_KEY] = storage;
127300
+ return storage;
127301
+ }
127231
127302
  function canonicalizeCwd(cwd) {
127232
127303
  const resolved = resolve21(cwd);
127233
127304
  try {
@@ -127255,8 +127326,13 @@ function registerFusionSessionIdentity(cwd, identity) {
127255
127326
  };
127256
127327
  }
127257
127328
  function resolveFusionSessionPrincipal(cwd) {
127329
+ const canonicalCwd = canonicalizeCwd(cwd);
127330
+ const activeInvocation = getActiveInvocationStorage().getStore();
127331
+ if (activeInvocation?.cwdKeys.has(canonicalCwd)) {
127332
+ return { kind: "agent", identity: activeInvocation.identity };
127333
+ }
127258
127334
  const registry3 = getRegistry();
127259
- const list = registry3.get(canonicalizeCwd(cwd));
127335
+ const list = registry3.get(canonicalCwd);
127260
127336
  if (!list || list.length === 0) {
127261
127337
  return { kind: "operator" };
127262
127338
  }
@@ -127265,14 +127341,24 @@ function resolveFusionSessionPrincipal(cwd) {
127265
127341
  }
127266
127342
  return { kind: "ambiguous", identities: [...list] };
127267
127343
  }
127344
+ function runWithFusionSessionIdentity(cwdKeys, identity, callback) {
127345
+ const invocation = {
127346
+ identity: { ...identity, registeredAt: Date.now() },
127347
+ cwdKeys: new Set(cwdKeys.map(canonicalizeCwd))
127348
+ };
127349
+ return getActiveInvocationStorage().run(invocation, callback);
127350
+ }
127268
127351
  function __clearFusionSessionIdentityRegistryForTests() {
127269
- globalThis[REGISTRY_KEY] = /* @__PURE__ */ new Map();
127352
+ const holder = globalThis;
127353
+ holder[REGISTRY_KEY] = /* @__PURE__ */ new Map();
127354
+ holder[ACTIVE_INVOCATION_KEY] = new AsyncLocalStorage();
127270
127355
  }
127271
- var REGISTRY_KEY;
127356
+ var REGISTRY_KEY, ACTIVE_INVOCATION_KEY;
127272
127357
  var init_session_identity_registry = __esm({
127273
127358
  "../core/src/session-identity-registry.ts"() {
127274
127359
  "use strict";
127275
127360
  REGISTRY_KEY = "__FUSION_SESSION_IDENTITY_REGISTRY_V1__";
127361
+ ACTIVE_INVOCATION_KEY = "__FUSION_SESSION_IDENTITY_ACTIVE_INVOCATION_V1__";
127276
127362
  }
127277
127363
  });
127278
127364
 
@@ -127635,6 +127721,7 @@ __export(src_exports, {
127635
127721
  MAX_TITLE_LENGTH: () => MAX_TITLE_LENGTH,
127636
127722
  MAX_TITLE_SUMMARIZE_INPUT_LENGTH: () => MAX_TITLE_SUMMARIZE_INPUT_LENGTH,
127637
127723
  MAX_WORKFLOW_ICON_LENGTH: () => MAX_WORKFLOW_ICON_LENGTH,
127724
+ MAX_WORKFLOW_REVIEW_FINDINGS: () => MAX_WORKFLOW_REVIEW_FINDINGS,
127638
127725
  MAX_WORKFLOW_STEP_PRIOR_ATTEMPTS: () => MAX_WORKFLOW_STEP_PRIOR_ATTEMPTS,
127639
127726
  MEMORY_AUDIT_PATH: () => MEMORY_AUDIT_PATH,
127640
127727
  MEMORY_BACKEND_SETTINGS_KEYS: () => MEMORY_BACKEND_SETTINGS_KEYS,
@@ -127836,6 +127923,7 @@ __export(src_exports, {
127836
127923
  WAKE_DELTA_ASSIGNED_TASKS_CAP: () => WAKE_DELTA_ASSIGNED_TASKS_CAP,
127837
127924
  WEDGE_RENOTIFY_COOLDOWN_MS: () => WEDGE_RENOTIFY_COOLDOWN_MS,
127838
127925
  WORKFLOW_EXTENSION_SCHEMA_VERSION: () => WORKFLOW_EXTENSION_SCHEMA_VERSION,
127926
+ WORKFLOW_REVIEW_FINDING_SEVERITIES: () => WORKFLOW_REVIEW_FINDING_SEVERITIES,
127839
127927
  WORKFLOW_SETTING_REJECTION_CODES: () => WORKFLOW_SETTING_REJECTION_CODES,
127840
127928
  WORKFLOW_SETTING_TYPES: () => WORKFLOW_SETTING_TYPES,
127841
127929
  WORKFLOW_WORK_ITEM_KINDS: () => WORKFLOW_WORK_ITEM_KINDS,
@@ -128241,6 +128329,7 @@ __export(src_exports, {
128241
128329
  hasRoleOverrides: () => hasRoleOverrides,
128242
128330
  hasSyncPassphraseConfigured: () => hasSyncPassphraseConfigured,
128243
128331
  hasTitleIdDrift: () => hasTitleIdDrift,
128332
+ hasUserAutoMergeHold: () => hasUserAutoMergeHold,
128244
128333
  hashWorkflowIr: () => hashWorkflowIr,
128245
128334
  healSchemaDrift: () => healSchemaDrift,
128246
128335
  importMcpServersJson: () => importMcpServersJson,
@@ -128363,6 +128452,7 @@ __export(src_exports, {
128363
128452
  isWipColumn: () => isWipColumn,
128364
128453
  isWipColumnRole: () => isWipColumnRole,
128365
128454
  isWorkflowOptionalGroupEnabled: () => isWorkflowOptionalGroupEnabled,
128455
+ isWorkflowReviewFindingSeverity: () => isWorkflowReviewFindingSeverity,
128366
128456
  isWorkspaceTask: () => isWorkspaceTask,
128367
128457
  layoutForIr: () => layoutForIr,
128368
128458
  listActiveAiSessions: () => listActiveAiSessions,
@@ -128436,6 +128526,7 @@ __export(src_exports, {
128436
128526
  normalizeValidationDiagnostics: () => normalizeValidationDiagnostics,
128437
128527
  normalizeWorkflowIcon: () => normalizeWorkflowIcon,
128438
128528
  normalizeWorkflowPromptOverrides: () => normalizeWorkflowPromptOverrides,
128529
+ normalizeWorkflowReviewFindings: () => normalizeWorkflowReviewFindings,
128439
128530
  notifyOperatorOfNonOperatorDelete: () => notifyOperatorOfNonOperatorDelete,
128440
128531
  overseerAdviceSeverityRank: () => overseerAdviceSeverityRank,
128441
128532
  parseAgentManifest: () => parseAgentManifest,
@@ -128699,6 +128790,7 @@ __export(src_exports, {
128699
128790
  runMemoryBackupCommand: () => runMemoryBackupCommand,
128700
128791
  runReconciliationAbort: () => runReconciliationAbort,
128701
128792
  runScheduledEvalBatch: () => runScheduledEvalBatch,
128793
+ runWithFusionSessionIdentity: () => runWithFusionSessionIdentity,
128702
128794
  sanitizeBranchSegment: () => sanitizeBranchSegment,
128703
128795
  sanitizeCliAgentSettings: () => sanitizeCliAgentSettings,
128704
128796
  sanitizeCliAgentsSettings: () => sanitizeCliAgentsSettings,
@@ -157348,6 +157440,7 @@ import { existsSync as existsSync43, readFileSync as readFileSync15, realpathSyn
157348
157440
  import { exec as exec2, execFile as execFile7 } from "node:child_process";
157349
157441
  import { promisify as promisify6 } from "node:util";
157350
157442
  import { createRequire as createRequire5 } from "node:module";
157443
+ import { randomUUID as randomUUID42 } from "node:crypto";
157351
157444
  import { basename as basename13, dirname as dirname21, join as join56, relative as relative6, isAbsolute as isAbsolute8, resolve as resolve24 } from "node:path";
157352
157445
  import {
157353
157446
  createAgentSession,
@@ -159058,7 +159151,8 @@ async function createFnAgent2(options) {
159058
159151
  });
159059
159152
  };
159060
159153
  const sessionIdentity = (() => {
159061
- const principalAgentId = options.actionGateContext?.agentId ?? options.permanentAgentGating?.requester?.actorId ?? "engine-session";
159154
+ const namedAgentId = options.actionGateContext?.agentId ?? options.permanentAgentGating?.requester?.actorId;
159155
+ const principalAgentId = namedAgentId ?? `engine-session-${randomUUID42()}`;
159062
159156
  const principalAgentName = options.actionGateContext?.agentName ?? options.permanentAgentGating?.requester?.actorName;
159063
159157
  return {
159064
159158
  agentId: principalAgentId,
@@ -159070,6 +159164,14 @@ async function createFnAgent2(options) {
159070
159164
  const sessionIdentityKeys = [...new Set([options.cwd, resolvedProjectRoot].filter((key) => Boolean(key)))];
159071
159165
  const attachSessionIdentity = (session) => {
159072
159166
  const identityDisposers = sessionIdentityKeys.map((key) => registerFusionSessionIdentity(key, sessionIdentity));
159167
+ const sessionInvocations = session;
159168
+ const wrapInvocation = (methodName) => {
159169
+ const original = sessionInvocations[methodName];
159170
+ if (typeof original !== "function") return;
159171
+ sessionInvocations[methodName] = (...args) => runWithFusionSessionIdentity(sessionIdentityKeys, sessionIdentity, () => original.apply(session, args));
159172
+ };
159173
+ wrapInvocation("prompt");
159174
+ wrapInvocation("promptWithFallback");
159073
159175
  const disposeBeforeIdentity = typeof session.dispose === "function" ? session.dispose.bind(session) : () => void 0;
159074
159176
  session.dispose = async () => {
159075
159177
  for (const disposeIdentity of identityDisposers) {
@@ -167269,10 +167371,11 @@ function createMergeAttemptHandler(deps) {
167269
167371
  );
167270
167372
  };
167271
167373
  }
167272
- function createMergeGateHandler() {
167374
+ function createMergeGateHandler(deps = {}) {
167273
167375
  return async (_node, ctx) => {
167274
167376
  const settingsAutoMerge = ctx.settings?.autoMerge;
167275
- const autoMerge = ctx.task.autoMerge !== false && settingsAutoMerge !== false;
167377
+ const settings = { autoMerge: settingsAutoMerge ?? true };
167378
+ const autoMerge = !hasUserAutoMergeHold(ctx.task) && (await deps.isLiveSharedBranchMember?.(ctx.task, settings) === true || ctx.task.autoMerge !== false && settings.autoMerge !== false);
167276
167379
  return {
167277
167380
  outcome: "success",
167278
167381
  value: autoMerge ? "auto-on" : "auto-off"
@@ -167282,6 +167385,7 @@ function createMergeGateHandler() {
167282
167385
  var init_merge_runner = __esm({
167283
167386
  "../engine/src/workflow-node-runners/merge-runner.ts"() {
167284
167387
  "use strict";
167388
+ init_src2();
167285
167389
  init_workflow_merge_nodes();
167286
167390
  }
167287
167391
  });
@@ -167625,7 +167729,7 @@ function createDefaultNodeHandlers(seams, runCustomNode, deps) {
167625
167729
  "parse-steps": parseSteps,
167626
167730
  code: createCodeNodeHandler(deps?.runCode),
167627
167731
  notify: createNotifyHandler(deps?.notifyDispatch),
167628
- "merge-gate": createMergeGateHandler(),
167732
+ "merge-gate": createMergeGateHandler({ isLiveSharedBranchMember: deps?.isLiveSharedBranchMember }),
167629
167733
  "merge-attempt": createMergeAttemptHandler({
167630
167734
  primitives: deps?.primitives,
167631
167735
  seams,
@@ -168023,7 +168127,7 @@ var init_code_node_runner = __esm({
168023
168127
  // ../engine/src/agent-tools.ts
168024
168128
  import { appendFile as appendFile3, mkdir as mkdir26, readFile as readFile32, readdir as readdir9, realpath, stat as stat9, writeFile as writeFile24 } from "node:fs/promises";
168025
168129
  import { existsSync as existsSync50 } from "node:fs";
168026
- import { createHash as createHash19, randomUUID as randomUUID42 } from "node:crypto";
168130
+ import { createHash as createHash19, randomUUID as randomUUID43 } from "node:crypto";
168027
168131
  import { tmpdir as tmpdir5 } from "node:os";
168028
168132
  import { extname as extname3, isAbsolute as isAbsolute12, join as join62, relative as relative11, resolve as resolve33, sep as sep9 } from "node:path";
168029
168133
  import { Type as Type2 } from "@earendil-works/pi-ai";
@@ -168669,7 +168773,7 @@ function createTaskCreateTool(store, provenance, options) {
168669
168773
  toType: "user",
168670
168774
  type: "agent-to-user",
168671
168775
  content: `Task proposal awaiting validation: ${title}`,
168672
- metadata: { kind: "task-proposal", proposalStatus: "pending", proposalIdempotencyKey: randomUUID42(), taskId: options.sourceTaskId, proposedTask: { title, description: params.description, priority: params.priority, workflowId: params.workflow_id, dependencies: params.dependencies } }
168776
+ metadata: { kind: "task-proposal", proposalStatus: "pending", proposalIdempotencyKey: randomUUID43(), taskId: options.sourceTaskId, proposedTask: { title, description: params.description, priority: params.priority, workflowId: params.workflow_id, dependencies: params.dependencies } }
168673
168777
  });
168674
168778
  return { content: [{ type: "text", text: "Task proposal submitted to the operator for validation; no task was created." }], details: { proposed: true } };
168675
168779
  }
@@ -173094,37 +173198,6 @@ var init_effective_settings = __esm({
173094
173198
  }
173095
173199
  });
173096
173200
 
173097
- // ../engine/src/errors/transient-merge-error-classifier.ts
173098
- function classifyTransientMergeError(error3) {
173099
- if (!error3) return null;
173100
- if (/lease-handoff-failed[^a-z]+target-not-queued/i.test(error3)) {
173101
- return "lease-handoff-target-not-queued";
173102
- }
173103
- if (/\bACP turn failed\b/i.test(error3) || /\bacp rpc code -32(?:603|00[0-3])\b/i.test(error3)) {
173104
- return "ai-provider-turn-failure";
173105
- }
173106
- if (/\bspawn(?:\s+\S+)?\s+ENO(?:TDIR|ENT)\b/i.test(error3)) {
173107
- return "process-spawn-failure";
173108
- }
173109
- if (/\bis not a working tree\b/i.test(error3)) {
173110
- return "process-spawn-failure";
173111
- }
173112
- const sameSha = error3.match(/advanced concurrently \(expected ([0-9a-f]{7,40}),\s+observed ([0-9a-f]{7,40})\)/i);
173113
- if (sameSha && sameSha[1].toLowerCase() === sameSha[2].toLowerCase()) {
173114
- return "spurious-concurrent-advance-same-sha";
173115
- }
173116
- if (isTransientError(error3)) {
173117
- return "network-transport-failure";
173118
- }
173119
- return null;
173120
- }
173121
- var init_transient_merge_error_classifier = __esm({
173122
- "../engine/src/errors/transient-merge-error-classifier.ts"() {
173123
- "use strict";
173124
- init_transient_error_patterns();
173125
- }
173126
- });
173127
-
173128
173201
  // ../engine/src/notification/ntfy-provider.ts
173129
173202
  function resolveParticipantLabel(metadata, kind) {
173130
173203
  const nameKey = kind === "from" ? "fromName" : "toName";
@@ -173598,7 +173671,50 @@ var init_webhook_provider = __esm({
173598
173671
  }
173599
173672
  });
173600
173673
 
173674
+ // ../engine/src/errors/transient-merge-error-classifier.ts
173675
+ function classifyTransientMergeError(error3) {
173676
+ if (!error3) return null;
173677
+ if (/lease-handoff-failed[^a-z]+target-not-queued/i.test(error3)) {
173678
+ return "lease-handoff-target-not-queued";
173679
+ }
173680
+ if (/\bACP turn failed\b/i.test(error3) || /\bacp rpc code -32(?:603|00[0-3])\b/i.test(error3)) {
173681
+ return "ai-provider-turn-failure";
173682
+ }
173683
+ if (/\bspawn(?:\s+\S+)?\s+ENO(?:TDIR|ENT)\b/i.test(error3)) {
173684
+ return "process-spawn-failure";
173685
+ }
173686
+ if (/\bis not a working tree\b/i.test(error3)) {
173687
+ return "process-spawn-failure";
173688
+ }
173689
+ const sameSha = error3.match(/advanced concurrently \(expected ([0-9a-f]{7,40}),\s+observed ([0-9a-f]{7,40})\)/i);
173690
+ if (sameSha && sameSha[1].toLowerCase() === sameSha[2].toLowerCase()) {
173691
+ return "spurious-concurrent-advance-same-sha";
173692
+ }
173693
+ if (isTransientError(error3)) {
173694
+ return "network-transport-failure";
173695
+ }
173696
+ return null;
173697
+ }
173698
+ function hasTransientMergeRecoveryOwner(task) {
173699
+ return classifyTransientMergeError(task.error) !== null && typeof task.mergeTransientRetryCount === "number" && Number.isInteger(task.mergeTransientRetryCount) && task.mergeTransientRetryCount >= 0 && task.mergeTransientRetryCount < MAX_AUTO_MERGE_TRANSIENT_RETRIES;
173700
+ }
173701
+ var MAX_AUTO_MERGE_TRANSIENT_RETRIES;
173702
+ var init_transient_merge_error_classifier = __esm({
173703
+ "../engine/src/errors/transient-merge-error-classifier.ts"() {
173704
+ "use strict";
173705
+ init_transient_error_patterns();
173706
+ MAX_AUTO_MERGE_TRANSIENT_RETRIES = 5;
173707
+ }
173708
+ });
173709
+
173601
173710
  // ../engine/src/notification/task-wedge-notification.ts
173711
+ function describeTaskRecoveryOwner(task) {
173712
+ if (hasTransientMergeRecoveryOwner(task)) return { kind: "transient-merge-retry" };
173713
+ if (typeof task.recoveryRetryCount === "number" && Number.isInteger(task.recoveryRetryCount) && task.recoveryRetryCount >= 0 && typeof task.nextRecoveryAt === "string" && Number.isFinite(Date.parse(task.nextRecoveryAt))) {
173714
+ return { kind: "scheduled-recovery" };
173715
+ }
173716
+ return null;
173717
+ }
173602
173718
  function describeSelfHealingNoActionWedge(task, stage, metadata) {
173603
173719
  const description = SELF_HEALING_NO_ACTIONS[stage];
173604
173720
  if (!description || task.userPaused || task.paused || task.autoMerge === false) return null;
@@ -173656,6 +173772,7 @@ function describeTaskWedge(task) {
173656
173772
  if (/merge.*(?:blocked|verification|gate)|(?:verification|gate).*(?:failed|exhaust)/i.test(error3)) {
173657
173773
  return { reasonKey: "merge-blocked", reason: "Merge verification cannot progress without operator action.", action: "Fix the failing verification, then retry the task." };
173658
173774
  }
173775
+ if (describeTaskRecoveryOwner(task)) return null;
173659
173776
  if (!error3 && (task.mergeRetries ?? 0) < 3) return null;
173660
173777
  return {
173661
173778
  reasonKey: "terminal-failed",
@@ -173667,6 +173784,7 @@ var SELF_HEALING_NO_ACTIONS;
173667
173784
  var init_task_wedge_notification = __esm({
173668
173785
  "../engine/src/notification/task-wedge-notification.ts"() {
173669
173786
  "use strict";
173787
+ init_transient_merge_error_classifier();
173670
173788
  SELF_HEALING_NO_ACTIONS = {
173671
173789
  "reclaim-pr-conflict": {
173672
173790
  reason: "Self-healing could not reclaim a stalled pull-request conflict.",
@@ -173732,7 +173850,6 @@ var init_notification_service = __esm({
173732
173850
  init_src2();
173733
173851
  init_notifier();
173734
173852
  init_logger3();
173735
- init_transient_merge_error_classifier();
173736
173853
  init_ntfy_provider();
173737
173854
  init_webhook_provider();
173738
173855
  init_task_wedge_notification();
@@ -173958,10 +174075,10 @@ var init_notification_service = __esm({
173958
174075
  }
173959
174076
  }
173960
174077
  handleTaskUpdated = (task, meta2) => {
173961
- const transientFailure = task.status === "failed" ? classifyTransientMergeError(task.error) : null;
173962
- const wedge = transientFailure ? null : describeTaskWedge(task);
174078
+ const recoveryOwner = task.status === "failed" ? describeTaskRecoveryOwner(task) : null;
174079
+ const wedge = describeTaskWedge(task);
173963
174080
  if (wedge) this.cancelPendingFailureNotification(task.id, "classified-terminal-wedge");
173964
- if (!transientFailure) void this.enqueueWedgeHandling(task.id, () => this.maybeNotifyTaskWedge(task, wedge));
174081
+ void this.enqueueWedgeHandling(task.id, () => this.maybeNotifyTaskWedge(task, wedge));
173965
174082
  void this.maybeSuppressTransientFailedNotification(task, `status=${task.status ?? "undefined"}`);
173966
174083
  if (task.status === "awaiting-approval") {
173967
174084
  void this.writeAwaitingApprovalMailboxMessage(task);
@@ -173972,17 +174089,12 @@ var init_notification_service = __esm({
173972
174089
  if (!this.notificationsEnabled) {
173973
174090
  return;
173974
174091
  }
173975
- if (task.status === "failed" && !wedge) {
173976
- const transientClass = classifyTransientMergeError(task.error);
173977
- if (transientClass) {
173978
- this.failureNotificationSuppressedCount += 1;
173979
- schedulerLog.debug(
173980
- `[notify] ${task.id} transient merge failure (${transientClass}) \u2014 suppressed notification (self-heal in flight)`
173981
- );
173982
- return;
173983
- }
174092
+ if (task.status === "failed" && recoveryOwner) {
174093
+ this.failureNotificationSuppressedCount += 1;
174094
+ schedulerLog.debug(`[notify] ${task.id} recovery-owned failure \u2014 suppressed notification`);
174095
+ } else if (task.status === "failed" && !wedge) {
173984
174096
  if (this.failureNotificationMode === "all") {
173985
- this.maybeNotify(task.id, "failed", this.createTaskPayload(task, "failed"));
174097
+ void this.maybeNotifyImmediateFailure(task);
173986
174098
  } else {
173987
174099
  this.scheduleFailureNotification(task);
173988
174100
  }
@@ -174069,7 +174181,17 @@ var init_notification_service = __esm({
174069
174181
  await this.enqueueWedgeHandling(task.id, () => this.maybeNotifyTaskWedge(task, descriptor));
174070
174182
  }
174071
174183
  async maybeNotifyTaskWedge(task, suppliedDescriptor) {
174072
- const descriptor = suppliedDescriptor ?? describeTaskWedge(task);
174184
+ const liveTask = this.store.getTask ? await this.store.getTask(task.id) ?? task : task;
174185
+ const recoveryOwner = describeTaskRecoveryOwner(liveTask);
174186
+ if (recoveryOwner) {
174187
+ if (this.activeWedgeReasons.has(task.id) || liveTask.wedgeNotification?.status === "active") {
174188
+ this.activeWedgeReasons.delete(task.id);
174189
+ await this.store.claimTaskWedgeNotificationEpisode?.(task.id, null);
174190
+ }
174191
+ return;
174192
+ }
174193
+ const descriptor = suppliedDescriptor ?? describeTaskWedge(liveTask);
174194
+ task = liveTask;
174073
174195
  let episode;
174074
174196
  if (!descriptor) {
174075
174197
  const isActiveSelfHealingNoAction = task.wedgeNotification?.status === "active" && task.wedgeNotification.reasonKey.startsWith("self-healing-no-action:");
@@ -174436,6 +174558,14 @@ var init_notification_service = __esm({
174436
174558
  this.failureNotificationDelayMs = typeof settings.failureNotificationDelayMs === "number" && settings.failureNotificationDelayMs >= 0 ? settings.failureNotificationDelayMs : this.failedNotificationGraceMs;
174437
174559
  this.failureNotificationMode = settings.failureNotificationMode ?? "sticky-only";
174438
174560
  }
174561
+ async maybeNotifyImmediateFailure(task) {
174562
+ const liveTask = this.store.getTask ? await this.store.getTask(task.id) ?? task : task;
174563
+ if (liveTask.status !== "failed" || describeTaskRecoveryOwner(liveTask) || describeTaskWedge(liveTask)) {
174564
+ this.failureNotificationSuppressedCount += 1;
174565
+ return;
174566
+ }
174567
+ this.maybeNotify(liveTask.id, "failed", this.createTaskPayload(liveTask, "failed"));
174568
+ }
174439
174569
  scheduleFailureNotification(task) {
174440
174570
  if (this.pendingFailureNotifications.has(task.id)) {
174441
174571
  return;
@@ -174492,12 +174622,9 @@ var init_notification_service = __esm({
174492
174622
  schedulerLog.debug(`[notify] ${taskId} no longer failed at dispatch time \u2014 suppressed notification`);
174493
174623
  return;
174494
174624
  }
174495
- const transientClassAtDispatch = classifyTransientMergeError(task.error);
174496
- if (transientClassAtDispatch) {
174625
+ if (describeTaskRecoveryOwner(task)) {
174497
174626
  this.failureNotificationSuppressedCount += 1;
174498
- schedulerLog.debug(
174499
- `[notify] ${taskId} transient merge failure (${transientClassAtDispatch}) at dispatch time \u2014 suppressed notification (self-heal in flight)`
174500
- );
174627
+ schedulerLog.debug(`[notify] ${taskId} recovery-owned failure at dispatch time \u2014 suppressed notification`);
174501
174628
  return;
174502
174629
  }
174503
174630
  if (describeTaskWedge(task)) {
@@ -186152,10 +186279,21 @@ var init_scheduler = __esm({
186152
186279
  const unmetDeps = getUnmetSchedulingDependencies(task, tasks2, schedulingDependencyOptions);
186153
186280
  if (unmetDeps.length > 0) {
186154
186281
  const normalizedUnmetDeps = [...new Set(unmetDeps)].sort();
186282
+ let activeOverlapBlockedBy = null;
186283
+ if (activeScopes.size > 0) {
186284
+ try {
186285
+ const taskScope = await getFilteredFileScope(task.id);
186286
+ if (taskScope.length > 0 && !isCoordinationOnlyTask(task, taskScope)) {
186287
+ activeOverlapBlockedBy = Array.from(activeScopes.entries()).sort(([aId], [bId]) => aId.localeCompare(bId)).find(([, activeScope]) => this.pathsOverlap(taskScope, activeScope))?.[0] ?? null;
186288
+ }
186289
+ } catch (error3) {
186290
+ schedulerLog.warn(`Failed to refresh file-scope overlap blocker for dependency-blocked task ${task.id}`, error3);
186291
+ }
186292
+ }
186155
186293
  await this.transitionQueuedEpisode(task, {
186156
186294
  signature: `dependency:${normalizedUnmetDeps.join(",")}`,
186157
186295
  blockedBy: unmetDeps[0] ?? null,
186158
- overlapBlockedBy: task.overlapBlockedBy ?? null,
186296
+ overlapBlockedBy: activeOverlapBlockedBy,
186159
186297
  action: `queued \u2014 unmet dependencies: ${unmetDeps.join(", ")}`
186160
186298
  });
186161
186299
  this.options.onBlocked?.(task, unmetDeps);
@@ -202212,16 +202350,29 @@ var init_self_healing = __esm({
202212
202350
  activeWork: await this.resolveActiveWorkColumnsFor(taskId, cache)
202213
202351
  };
202214
202352
  }
202215
- classifyPausedAbortWorkflowRecovery(task, settings, isExecuting, columns) {
202353
+ /*
202354
+ FNXC:SharedBranchMemberHold 2026-08-05-23:35:
202355
+ Pause-abort recovery must resolve the same live intermediate-group predicate as
202356
+ merge admission. Shared metadata alone is stale after group closure or a
202357
+ default-branch collision, so it cannot suppress that task's standalone hold.
202358
+ */
202359
+ async isLiveSharedMemberIntegration(task, settings) {
202360
+ const groupId = task.branchContext?.groupId?.trim();
202361
+ const group = groupId ? await this.store.getBranchGroup?.(groupId) : null;
202362
+ const defaultBranch = await resolveIntegrationBranch(this.options.rootDir, settings);
202363
+ return isLiveSharedBranchGroupMemberIntegration(task, group, defaultBranch);
202364
+ }
202365
+ async classifyPausedAbortWorkflowRecovery(task, settings, isExecuting, columns) {
202216
202366
  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);
202217
202367
  if (!isPausedAbortPark) return { kind: "no-action", reason: "not-pause-abort" };
202218
202368
  if (task.paused || task.userPaused || isExecuting) return { kind: "no-action", reason: "unsafe-or-not-routable" };
202219
202369
  const errorText = typeof task.error === "string" ? task.error.toLowerCase() : "";
202220
202370
  const isTerminalMergePark = errorText.includes("conflict") || errorText.includes("contamination") || errorText.includes("foreign") || errorText.includes("retry-exhausted") || errorText.includes("retries exhausted") || errorText.includes("max retries");
202221
202371
  const completedSteps = task.steps.length > 0 && task.steps.every((step) => step.status === "done" || step.status === "skipped");
202222
- const sharedBranchMember = isSharedBranchGroupMemberIntegration(task);
202223
- const hasReviewProgress = columns.review.has(task.column) && allowsAutoMergeProcessing(task, settings) && task.mergeDetails?.mergeConfirmed !== true && !isTerminalMergePark && completedSteps;
202224
- const hasManualMergeHoldProgress = columns.review.has(task.column) && (!allowsAutoMergeProcessing(task, settings) || resolveEffectiveAutoMerge(task, settings) === false) && !sharedBranchMember && task.mergeDetails?.mergeConfirmed !== true && !isTerminalMergePark && completedSteps;
202372
+ const liveSharedBranchMember = await this.isLiveSharedMemberIntegration(task, settings);
202373
+ const autoMergeProcessing = allowsAutoMergeProcessing(task, settings) || liveSharedBranchMember;
202374
+ const hasReviewProgress = columns.review.has(task.column) && autoMergeProcessing && task.mergeDetails?.mergeConfirmed !== true && !isTerminalMergePark && completedSteps;
202375
+ 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;
202225
202376
  if (hasManualMergeHoldProgress) {
202226
202377
  return { kind: "work-item-resume", reason: "pause-abort-manual-merge-hold" };
202227
202378
  }
@@ -207889,8 +208040,23 @@ var init_self_healing = __esm({
207889
208040
  return own.length > 0 ? new Set(own) : /* @__PURE__ */ new Set(["in-review"]);
207890
208041
  };
207891
208042
  const executingIds = this.options.getExecutingTaskIds?.() ?? /* @__PURE__ */ new Set();
208043
+ const canRecoverMergeableReviewTask = async (task) => {
208044
+ if (hasUserAutoMergeHold(task)) return false;
208045
+ const groupId = task.branchContext?.groupId?.trim();
208046
+ const branchGroup = groupId ? await this.store.getBranchGroup(groupId) : null;
208047
+ const projectDefaultBranch = await resolveIntegrationBranch(this.options.rootDir, settings);
208048
+ if (isLiveSharedBranchGroupMemberIntegration(task, branchGroup, projectDefaultBranch)) {
208049
+ return true;
208050
+ }
208051
+ return allowsAutoMergeProcessing(task, settings) && resolveEffectiveAutoMerge(task, settings) !== false;
208052
+ };
208053
+ const mergeAdmission = await Promise.all(tasks2.map(async (task) => [
208054
+ task.id,
208055
+ await canRecoverMergeableReviewTask(task)
208056
+ ]));
208057
+ const mergeAdmissionByTaskId = new Map(mergeAdmission);
207892
208058
  const mergeable = tasks2.filter(
207893
- (t) => allowsAutoMergeProcessing(t, settings) && !t.paused && !executingIds.has(t.id) && t.status !== "failed" && // Exclude transient merge statuses. Active merges should be left alone;
208059
+ (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;
207894
208060
  // stale ones are handled by recoverStaleMergingStatus().
207895
208061
  t.status !== "merging" && t.status !== "merging-pr" && // FNXC:Workspace 2026-06-22-09:30 (Phase D U1, KTD1 — admit workspace tasks):
207896
208062
  // A workspace task has task.worktree===null (its worktrees live per-repo in
@@ -210937,7 +211103,7 @@ var init_self_healing = __esm({
210937
211103
  for (const t of tasks2) {
210938
211104
  if (!this.isPauseAbortParkCandidate(t)) continue;
210939
211105
  const columns = await this.resolvePauseAbortColumnsFor(t.id, columnCache);
210940
- if (this.classifyPausedAbortWorkflowRecovery(t, settings, executingIds.has(t.id), columns).kind !== "no-action") {
211106
+ if ((await this.classifyPausedAbortWorkflowRecovery(t, settings, executingIds.has(t.id), columns)).kind !== "no-action") {
210941
211107
  parked.push(t);
210942
211108
  }
210943
211109
  }
@@ -210954,7 +211120,7 @@ var init_self_healing = __esm({
210954
211120
  continue;
210955
211121
  }
210956
211122
  const freshColumns = await this.resolvePauseAbortColumnsFor(fresh.id, columnCache);
210957
- const route = this.classifyPausedAbortWorkflowRecovery(fresh, settings, latestExecutingIds.has(fresh.id), freshColumns);
211123
+ const route = await this.classifyPausedAbortWorkflowRecovery(fresh, settings, latestExecutingIds.has(fresh.id), freshColumns);
210958
211124
  if (route.kind === "no-action") {
210959
211125
  continue;
210960
211126
  }
@@ -217957,7 +218123,7 @@ function recoverPassedPlanReviewFromLatestLog(task) {
217957
218123
  completedAt: latest.timestamp
217958
218124
  };
217959
218125
  }
217960
- 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;
218126
+ 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;
217961
218127
  var init_workflow_graph_executor = __esm({
217962
218128
  "../engine/src/workflows/workflow-graph-executor.ts"() {
217963
218129
  "use strict";
@@ -217977,6 +218143,7 @@ var init_workflow_graph_executor = __esm({
217977
218143
  WORKFLOW_INTERRUPTED_NODE_ID_CONTEXT_KEY = "workflow:interruptedNodeId";
217978
218144
  WORKFLOW_INTERRUPTED_NODE_ABORT_KIND_CONTEXT_KEY = "workflow:interruptedNodeAbortKind";
217979
218145
  WORKFLOW_OPTIONAL_GROUP_CONTEXT_KEY = "workflow:optionalGroupActive";
218146
+ WORKFLOW_REVIEW_KIND_CONTEXT_KEY = "workflow:reviewKind";
217980
218147
  WORKFLOW_NODE_ENGINE_PAUSE_ABORT_KIND = "engine-pause";
217981
218148
  WORKFLOW_DRIFT_PARK_CONTEXT_KEY = "workflow:driftPark";
217982
218149
  WorkflowGraphSuspended = class extends Error {
@@ -218004,7 +218171,8 @@ var init_workflow_graph_executor = __esm({
218004
218171
  parseSteps: deps.parseStepsDeps,
218005
218172
  runCode: deps.runCode,
218006
218173
  notifyDispatch: deps.notifyDispatch,
218007
- prNodes: deps.prNodes
218174
+ prNodes: deps.prNodes,
218175
+ isLiveSharedBranchMember: deps.isLiveSharedBranchMember
218008
218176
  }),
218009
218177
  ...deps.runnerRegistry?.toHandlers() ?? {},
218010
218178
  ...deps.handlers ?? {}
@@ -218225,6 +218393,7 @@ var init_workflow_graph_executor = __esm({
218225
218393
  phase: stepPhase,
218226
218394
  status: "pending",
218227
218395
  source: "optional-group",
218396
+ ...this.workflowReviewKind(node) ? { reviewKind: this.workflowReviewKind(node) } : {},
218228
218397
  startedAt: stepStartedAt,
218229
218398
  // U3/KTD-4: stamp the lease owner so a concurrent/crashed re-entry
218230
218399
  // adopts this pending gate instead of dispatching a second reviewer.
@@ -218244,7 +218413,8 @@ var init_workflow_graph_executor = __esm({
218244
218413
  runTemplateNode: (tNode, sig, contextOverride) => {
218245
218414
  const optionalGroupContext = {
218246
218415
  ...contextOverride ?? context,
218247
- [WORKFLOW_OPTIONAL_GROUP_CONTEXT_KEY]: node.id
218416
+ [WORKFLOW_OPTIONAL_GROUP_CONTEXT_KEY]: node.id,
218417
+ ...this.workflowReviewKind(node) ? { [WORKFLOW_REVIEW_KIND_CONTEXT_KEY]: this.workflowReviewKind(node) } : {}
218248
218418
  };
218249
218419
  return this.executeNodeWithRetries(tNode, task, settings, optionalGroupContext, ir2, sig, false);
218250
218420
  },
@@ -218262,6 +218432,7 @@ var init_workflow_graph_executor = __esm({
218262
218432
  const exitContextPatch = exitResult?.contextPatch;
218263
218433
  let stepOutput = typeof exitContextPatch?.output === "string" ? exitContextPatch.output : void 0;
218264
218434
  const stepNotes = typeof exitContextPatch?.notes === "string" ? exitContextPatch.notes : void 0;
218435
+ const stepFindings = this.workflowReviewKind(node) && Array.isArray(exitContextPatch?.findings) ? exitContextPatch.findings : void 0;
218265
218436
  if (stepStatus === "failed" && !verdict && stepOutput === void 0 && stepNotes === void 0) {
218266
218437
  const lastVisited = groupResult.visitedNodeIds[groupResult.visitedNodeIds.length - 1];
218267
218438
  const templateNodeId = lastVisited?.includes("::") ? lastVisited.slice(lastVisited.indexOf("::") + 2) : void 0;
@@ -218279,9 +218450,11 @@ var init_workflow_graph_executor = __esm({
218279
218450
  phase: stepPhase,
218280
218451
  source: "optional-group",
218281
218452
  status: stepStatus,
218453
+ ...this.workflowReviewKind(node) ? { reviewKind: this.workflowReviewKind(node) } : {},
218282
218454
  ...verdict ? { verdict } : {},
218283
218455
  ...stepOutput !== void 0 ? { output: stepOutput } : {},
218284
218456
  ...stepNotes !== void 0 ? { notes: stepNotes } : {},
218457
+ ...stepFindings?.length ? { findings: stepFindings } : {},
218285
218458
  startedAt: stepStartedAt,
218286
218459
  completedAt: (/* @__PURE__ */ new Date()).toISOString()
218287
218460
  });
@@ -218741,9 +218914,15 @@ var init_workflow_graph_executor = __esm({
218741
218914
  }
218742
218915
  return failureResult;
218743
218916
  }
218917
+ /** FNXC:WorkflowReviewKind 2026-08-05-02:31: Persist only the declared closed
218918
+ * marker; execution never derives review semantics from a label, verdict, or gate mode. */
218919
+ workflowReviewKind(node) {
218920
+ return node.config?.reviewKind === "plan" || node.config?.reviewKind === "code" ? node.config.reviewKind : void 0;
218921
+ }
218744
218922
  shouldRecordNodeProgress(node) {
218745
218923
  const skillName = typeof node.config?.skillName === "string" ? node.config.skillName.trim() : "";
218746
- return skillName.length > 0 && (node.kind === "prompt" || node.kind === "gate");
218924
+ const markedReview = this.workflowReviewKind(node) !== void 0;
218925
+ return markedReview && (node.kind === "prompt" || node.kind === "gate" || node.kind === "script") || skillName.length > 0 && (node.kind === "prompt" || node.kind === "gate");
218747
218926
  }
218748
218927
  workflowNodeProgressName(node) {
218749
218928
  const configuredName = typeof node.config?.name === "string" ? node.config.name.trim() : "";
@@ -218757,6 +218936,7 @@ var init_workflow_graph_executor = __esm({
218757
218936
  phase: node.config?.phase === "post-merge" ? "post-merge" : "pre-merge",
218758
218937
  source: "node",
218759
218938
  status: "pending",
218939
+ ...this.workflowReviewKind(node) ? { reviewKind: this.workflowReviewKind(node) } : {},
218760
218940
  startedAt
218761
218941
  };
218762
218942
  await this.recordOptionalGroupStepResult(taskId, result2);
@@ -218767,6 +218947,7 @@ var init_workflow_graph_executor = __esm({
218767
218947
  const contextPatch = nodeResult.contextPatch ?? {};
218768
218948
  let output = typeof contextPatch.output === "string" ? contextPatch.output : void 0;
218769
218949
  const notes = typeof contextPatch.notes === "string" ? contextPatch.notes : void 0;
218950
+ const findings = this.workflowReviewKind(node) && Array.isArray(contextPatch.findings) ? contextPatch.findings : void 0;
218770
218951
  if (status === "failed" && output === void 0 && notes === void 0) {
218771
218952
  output = this.synthesizeNonVerdictFailureOutput({
218772
218953
  stepLabel: this.workflowNodeProgressName(node),
@@ -218781,8 +218962,10 @@ var init_workflow_graph_executor = __esm({
218781
218962
  phase: started?.phase ?? (node.config?.phase === "post-merge" ? "post-merge" : "pre-merge"),
218782
218963
  source: "node",
218783
218964
  status,
218965
+ ...this.workflowReviewKind(node) ? { reviewKind: this.workflowReviewKind(node) } : {},
218784
218966
  ...output !== void 0 ? { output } : {},
218785
218967
  ...notes !== void 0 ? { notes } : {},
218968
+ ...findings?.length ? { findings } : {},
218786
218969
  startedAt: started?.startedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
218787
218970
  completedAt: (/* @__PURE__ */ new Date()).toISOString()
218788
218971
  });
@@ -219225,6 +219408,10 @@ var init_workflow_graph_task_runner = __esm({
219225
219408
  runCode: this.deps.runCode,
219226
219409
  notifyDispatch: this.deps.notifyDispatch,
219227
219410
  prNodes: this.deps.prNodes,
219411
+ // FNXC:SharedBranchMemberHold 2026-08-06-00:24: carry the executor's
219412
+ // live group resolver into the graph merge gate; omitting it turns
219413
+ // mission-policy members into manual holds before integration.
219414
+ isLiveSharedBranchMember: this.deps.isLiveSharedBranchMember,
219228
219415
  /*
219229
219416
  FNXC:WorkflowResume 2026-06-29-08:49:
219230
219417
  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.
@@ -224063,9 +224250,11 @@ function parseWorkflowStepVerdict(rawOutput) {
224063
224250
  verdict = "REVISE";
224064
224251
  }
224065
224252
  if (!verdict) continue;
224253
+ const findings = normalizeWorkflowReviewFindings(parsed.findings);
224066
224254
  return {
224067
224255
  verdict,
224068
- notes: typeof parsed.notes === "string" ? parsed.notes : ""
224256
+ notes: typeof parsed.notes === "string" ? parsed.notes : "",
224257
+ ...findings ? { findings } : {}
224069
224258
  };
224070
224259
  } catch {
224071
224260
  }
@@ -224097,7 +224286,8 @@ function parseWorkflowStepOutput(rawOutput, options = {}) {
224097
224286
  return {
224098
224287
  output: parsed.notes || "",
224099
224288
  verdict: parsed.verdict,
224100
- notes: parsed.notes
224289
+ notes: parsed.notes,
224290
+ ...parsed.findings ? { findings: parsed.findings } : {}
224101
224291
  };
224102
224292
  }
224103
224293
  const inferred = inferWorkflowStepVerdictFromProse(trimmed);
@@ -227334,6 +227524,7 @@ ${feedback}`,
227334
227524
  if (info.phase !== "pre-merge") return false;
227335
227525
  if (info.status !== "advisory_failure" && info.status !== "failed") return false;
227336
227526
  const liveTask = await this.store.getTask(taskId).catch(() => fallbackTask);
227527
+ if (hasUserAutoMergeHold(liveTask)) return false;
227337
227528
  const missingArtifactKeys = parseRequiredArtifactMissingValue(info.failureValue);
227338
227529
  if (missingArtifactKeys) {
227339
227530
  await this.recoverMissingRequiredArtifacts(liveTask, missingArtifactKeys, {
@@ -227590,6 +227781,7 @@ ${info.feedback}`, revisionKey),
227590
227781
  */
227591
227782
  async recoverFailedPreMergeWorkflowStep(task) {
227592
227783
  try {
227784
+ if (hasUserAutoMergeHold(task)) return false;
227593
227785
  const failed = (task.workflowStepResults ?? []).filter((r) => (r.phase || "pre-merge") === "pre-merge" && r.status === "failed").sort((a, b3) => {
227594
227786
  const aTs = Date.parse(a.completedAt || a.startedAt || "");
227595
227787
  const bTs = Date.parse(b3.completedAt || b3.startedAt || "");
@@ -228116,6 +228308,7 @@ ${info.feedback}`, revisionKey),
228116
228308
  getTask: (taskId) => this.store.getTask(taskId)
228117
228309
  },
228118
228310
  runId: resolvedRunId,
228311
+ isLiveSharedBranchMember: (nodeTask) => this.isLiveSharedBranchGroupMember(nodeTask),
228119
228312
  primitives: this.createAuthoritativeWorkflowPrimitives(settings),
228120
228313
  seams: this.createAuthoritativeWorkflowSeams(settings),
228121
228314
  prepareNodeExecution: (node, nodeTask, requirement) => this.prepareGraphNodeExecution(node, nodeTask, settings, requirement),
@@ -230358,6 +230551,7 @@ ${info.feedback}`, revisionKey),
230358
230551
  return this.runCliAgentNode(node, await this.store.getTask(live.id), cfg);
230359
230552
  }
230360
230553
  const optionalGroupId = typeof graphContext?.[WORKFLOW_OPTIONAL_GROUP_CONTEXT_KEY] === "string" ? graphContext[WORKFLOW_OPTIONAL_GROUP_CONTEXT_KEY] : void 0;
230554
+ 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;
230361
230555
  const isCompletionSummaryNode2 = cfg.summaryTarget === "task" || node.id === "completion-summary";
230362
230556
  if (live.executionMode === "fast" && !isCompletionSummaryNode2 && !optionalGroupId && !cfg.seam && (node.kind === "prompt" || node.kind === "script" || node.kind === "gate")) {
230363
230557
  executorLog.debug(`${live.id}: fast mode \u2014 skipping custom graph node '${node.id}'`);
@@ -230506,6 +230700,9 @@ ${prompt}`;
230506
230700
  if (optionalGroupId) {
230507
230701
  step.optionalGroupId = optionalGroupId;
230508
230702
  }
230703
+ if (declaredReviewKind) {
230704
+ step.reviewKind = declaredReviewKind;
230705
+ }
230509
230706
  if (cfg.reviewCanFixInline === true) {
230510
230707
  step.reviewCanFixInline = true;
230511
230708
  }
@@ -230518,7 +230715,11 @@ ${prompt}`;
230518
230715
  executorLog.debug(`${live.id}: graph node '${node.id}' runtime env injected (${injected.pathEntryCount} PATH entries, ${injected.injectedKeyCount} env keys)`);
230519
230716
  }
230520
230717
  const unattended = this.graphUnattendedRuns.has(live.id);
230521
- const outcome = mode === "script" ? await this.executeScriptWorkflowStep(live, step, worktreePath, settings, nodeEnv) : await this.executeWorkflowStep(live, step, worktreePath, settings, nodeEnv, { unattended });
230718
+ let outcome = mode === "script" ? await this.executeScriptWorkflowStep(live, step, worktreePath, settings, nodeEnv) : await this.executeWorkflowStep(live, step, worktreePath, settings, nodeEnv, { unattended });
230719
+ if (declaredReviewKind && typeof outcome.output === "string") {
230720
+ const parsedReviewOutput = parseWorkflowStepOutput(outcome.output, { requireVerdict: false });
230721
+ if (parsedReviewOutput.findings?.length) outcome = { ...outcome, findings: parsedReviewOutput.findings };
230722
+ }
230522
230723
  const awaitQuestion = parseAwaitInputSentinel(outcome.output);
230523
230724
  if (awaitQuestion) {
230524
230725
  await this.store.logEntry(
@@ -230541,6 +230742,8 @@ ${prompt}`;
230541
230742
  const contextPatch = {};
230542
230743
  if (typeof stepOutput === "string") contextPatch.output = stepOutput;
230543
230744
  if (typeof stepNotes === "string" && stepNotes) contextPatch.notes = stepNotes;
230745
+ const stepFindings = outcome.findings;
230746
+ if (stepFindings?.length) contextPatch.findings = stepFindings;
230544
230747
  if (cfg.summaryTarget === "task" && typeof stepOutput === "string" && stepOutput.trim()) {
230545
230748
  contextPatch.summary = stepOutput.trim();
230546
230749
  }
@@ -231047,6 +231250,7 @@ Graph value: ${failureValue}` : ""}`, budget.key),
231047
231250
  } catch {
231048
231251
  return false;
231049
231252
  }
231253
+ if (hasUserAutoMergeHold(live)) return false;
231050
231254
  const sharedBranchMember = await this.isLiveSharedBranchGroupMember(live);
231051
231255
  if (!sharedBranchMember && !allowsAutoMergeProcessing(live, settings)) return false;
231052
231256
  if (!sharedBranchMember && resolveEffectiveAutoMerge(live, settings) === false) return false;
@@ -231108,8 +231312,8 @@ Graph value: ${failureValue}` : ""}`, budget.key),
231108
231312
  } catch {
231109
231313
  return false;
231110
231314
  }
231111
- if (await this.isLiveSharedBranchGroupMember(live)) return false;
231112
- return !allowsAutoMergeProcessing(live, settings) || resolveEffectiveAutoMerge(live, settings) === false;
231315
+ if (await this.isLiveSharedBranchGroupMember(live) && !hasUserAutoMergeHold(live)) return false;
231316
+ return hasUserAutoMergeHold(live) || !allowsAutoMergeProcessing(live, settings) || resolveEffectiveAutoMerge(live, settings) === false;
231113
231317
  }
231114
231318
  async handleStaleInReviewPlanPauseAbortReplay(live, result2, abortProvenance, pausedAborted, userCanceled, resumeLanesMemo) {
231115
231319
  if (!pausedAborted) return false;
@@ -231273,9 +231477,9 @@ Graph value: ${failureValue}` : ""}`, budget.key),
231273
231477
  if (settings.globalPause === true) return false;
231274
231478
  }
231275
231479
  if (live.column === resumeLanes.review) {
231276
- if (live.autoMerge === false) return false;
231277
231480
  if (!settings) return false;
231278
231481
  const sharedBranchMember = await this.isLiveSharedBranchGroupMember(live);
231482
+ if (hasUserAutoMergeHold(live) || live.autoMerge === false && !sharedBranchMember) return false;
231279
231483
  if (!sharedBranchMember && !allowsAutoMergeProcessing(live, settings)) return false;
231280
231484
  if (live.mergeDetails?.mergeConfirmed === true) return false;
231281
231485
  }
@@ -237043,13 +237247,14 @@ CRITICAL SCOPING RULES \u2014 read before doing anything else:
237043
237247
  const isSkillStep = typeof workflowStep.skillName === "string" && workflowStep.skillName.trim().length > 0;
237044
237248
  const isSummaryProjectionStep = workflowStep.summaryTarget === "task";
237045
237249
  const requireVerdict = !isSummaryProjectionStep && (workflowStep.gateMode === "gate" || !isSkillStep);
237250
+ const reviewFindingsContract = workflowStepMetadata.reviewKind === "plan" || workflowStepMetadata.reviewKind === "code";
237046
237251
  const verdictBlock = requireVerdict ? `
237047
237252
 
237048
237253
  ## Feedback Format
237049
237254
 
237050
237255
  When your review is complete, your final line MUST be a single JSON object (no markdown fences):
237051
237256
 
237052
- {"verdict":"APPROVE|APPROVE_WITH_NOTES|REVISE","notes":"..."}
237257
+ ${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":"..."}'}
237053
237258
 
237054
237259
  Rules:
237055
237260
  - Output exactly one trailing JSON object and stop.
@@ -237359,7 +237564,8 @@ ${detectedQuestion}
237359
237564
  revisionRequested,
237360
237565
  output: parsed.output,
237361
237566
  verdict: parsed.verdict,
237362
- notes: parsed.notes
237567
+ notes: parsed.notes,
237568
+ ...parsed.findings ? { findings: parsed.findings } : {}
237363
237569
  };
237364
237570
  }
237365
237571
  if (parsed.malformed) {
@@ -248348,7 +248554,7 @@ var init_metric_parser = __esm({
248348
248554
  });
248349
248555
 
248350
248556
  // ../engine/src/eval/experiment-executor.ts
248351
- import { randomUUID as randomUUID43 } from "node:crypto";
248557
+ import { randomUUID as randomUUID44 } from "node:crypto";
248352
248558
  var ExperimentMaxIterationsError, ExperimentGitNotConfiguredError, ExperimentExecutor;
248353
248559
  var init_experiment_executor = __esm({
248354
248560
  "../engine/src/eval/experiment-executor.ts"() {
@@ -248416,7 +248622,7 @@ var init_experiment_executor = __esm({
248416
248622
  }
248417
248623
  await this.semaphore.acquire();
248418
248624
  const controller = new AbortController();
248419
- const runHandle = randomUUID43();
248625
+ const runHandle = randomUUID44();
248420
248626
  if (opts?.abortSignal) {
248421
248627
  opts.abortSignal.addEventListener("abort", () => controller.abort(), { once: true });
248422
248628
  }
@@ -257423,6 +257629,7 @@ var init_in_process_runtime = __esm({
257423
257629
  }
257424
257630
  }
257425
257631
  const prNodeGithubOps = this.config.prNodeGithubOps;
257632
+ const secretsStore = await this.taskStore.getSecretsStore();
257426
257633
  const executorOptions = {
257427
257634
  /*
257428
257635
  FNXC:PlanReviewLease 2026-07-26-21:12:
@@ -257438,6 +257645,7 @@ var init_in_process_runtime = __esm({
257438
257645
  cliAgentRuntime: this.cliAgentRuntime?.bundle,
257439
257646
  pluginRunner: this.pluginRunner,
257440
257647
  messageStore: this.messageStore,
257648
+ secretsStore,
257441
257649
  missionStore,
257442
257650
  reflectionService,
257443
257651
  // PR-entity nodes (U3): assemble the handler deps from the CLI-injected
@@ -257539,6 +257747,7 @@ var init_in_process_runtime = __esm({
257539
257747
  reflectionService,
257540
257748
  selfImproveService,
257541
257749
  credentialRotator: this.credentialRotator,
257750
+ secretsStore,
257542
257751
  snapshotManager: autoClaimSnapshotManager,
257543
257752
  onMissed: (agentId, reason) => {
257544
257753
  runtimeLog.warn(`Agent ${agentId} missed heartbeat: ${reason}`);
@@ -261400,7 +261609,21 @@ var init_project_engine = __esm({
261400
261609
  Cleared on the first non-busy outcome (success path resets it).
261401
261610
  */
261402
261611
  workspaceBusyReenqueues = /* @__PURE__ */ new Map();
261612
+ workspaceBusyReenqueueTimers = /* @__PURE__ */ new Set();
261403
261613
  static WORKSPACE_BUSY_MAX_REENQUEUES = 10;
261614
+ /*
261615
+ FNXC:WorkspaceMergeDispatch 2026-08-05-23:56:
261616
+ Workspace lease-contention retries are engine-owned lifecycle work, not detached callbacks.
261617
+ Track only these busy re-enqueue timers so stop() can cancel them and tests can measure the
261618
+ capped workspace ladder without confusing it with merge-body or maintenance timers.
261619
+ */
261620
+ scheduleWorkspaceBusyReenqueue(taskId, delayMs) {
261621
+ const timer2 = setTimeout(() => {
261622
+ this.workspaceBusyReenqueueTimers.delete(timer2);
261623
+ if (!this.shuttingDown) this.internalEnqueueMerge(taskId);
261624
+ }, delayMs);
261625
+ this.workspaceBusyReenqueueTimers.add(timer2);
261626
+ }
261404
261627
  /**
261405
261628
  * Pending manual merge resolvers — keyed by taskId.
261406
261629
  * When `onMerge` is called, the task is enqueued like auto-merge but a
@@ -261461,7 +261684,7 @@ var init_project_engine = __esm({
261461
261684
  *
261462
261685
  * Readable (not private) so tests derive the cap from this single source of truth rather
261463
261686
  * than hardcoding it — the FN-8004 bump broke two suites that had baked in the old `3`. */
261464
- static MAX_AUTO_MERGE_TRANSIENT_RETRIES = 5;
261687
+ static MAX_AUTO_MERGE_TRANSIENT_RETRIES = MAX_AUTO_MERGE_TRANSIENT_RETRIES;
261465
261688
  static MERGE_REQUEST_RETRY_EXHAUSTED_AGE_MS = 30 * 60 * 1e3;
261466
261689
  /** Cap on outer in-review→in-progress bounces caused by deterministic
261467
261690
  * verification failures during auto-merge. After this many failed merges
@@ -261935,6 +262158,10 @@ ${detail}`);
261935
262158
  clearInterval(this.mergeActiveReconcileTimer);
261936
262159
  this.mergeActiveReconcileTimer = null;
261937
262160
  }
262161
+ for (const timer2 of this.workspaceBusyReenqueueTimers) {
262162
+ clearTimeout(timer2);
262163
+ }
262164
+ this.workspaceBusyReenqueueTimers.clear();
261938
262165
  for (const [taskId, deferred] of this.capacityDeferredMerges) {
261939
262166
  clearTimeout(deferred.timer);
261940
262167
  for (const resolver of deferred.resolvers) {
@@ -263234,16 +263461,15 @@ ${detail}`);
263234
263461
  * older low-priority task would start before a later urgent one.
263235
263462
  */
263236
263463
  async allowInReviewMergeProcessing(task, settings, store = this.runtime.getTaskStore()) {
263237
- if (allowsAutoMergeProcessing(task, settings)) {
263238
- return true;
263239
- }
263464
+ if (hasUserAutoMergeHold(task)) return false;
263240
263465
  const groupId = task.branchContext?.groupId?.trim();
263241
263466
  const branchGroup = groupId ? await store.getBranchGroup?.(groupId) : null;
263242
- if (!branchGroup || branchGroup.status !== "open" || !branchGroup.branchName.trim()) {
263243
- return false;
263244
- }
263245
263467
  const projectDefaultBranch = await resolveIntegrationBranch(this.config.workingDirectory, settings);
263246
- return isLiveSharedBranchGroupMemberIntegration(task, branchGroup, projectDefaultBranch);
263468
+ if (isLiveSharedBranchGroupMemberIntegration(task, branchGroup, projectDefaultBranch)) {
263469
+ return true;
263470
+ }
263471
+ if (task.autoMerge === false && groupId) return false;
263472
+ return allowsAutoMergeProcessing(task, settings);
263247
263473
  }
263248
263474
  async emitLegacyAutoMergeStampAdvisory(store) {
263249
263475
  if (this.legacyAutoMergeStampAdvisoryEmitted) {
@@ -264196,9 +264422,7 @@ ${detail}`);
264196
264422
  runtimeLog.log(
264197
264423
  `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)`
264198
264424
  );
264199
- setTimeout(() => {
264200
- if (!this.shuttingDown) this.internalEnqueueMerge(taskId);
264201
- }, delayMs);
264425
+ this.scheduleWorkspaceBusyReenqueue(taskId, delayMs);
264202
264426
  } else {
264203
264427
  this.workspaceBusyReenqueues.delete(taskId);
264204
264428
  await store.updateTask(taskId, { status: "failed", error: errorMsg }).catch(() => void 0);
@@ -267690,7 +267914,7 @@ var init_task_lifecycle_lanes = __esm({
267690
267914
  });
267691
267915
 
267692
267916
  // ../dashboard/src/ai-session-diagnostics.ts
267693
- import { randomUUID as randomUUID44 } from "node:crypto";
267917
+ import { randomUUID as randomUUID45 } from "node:crypto";
267694
267918
  function defaultSink(level, scope, message, context) {
267695
267919
  const log41 = createLogger(scope);
267696
267920
  try {
@@ -267733,7 +267957,7 @@ function emit(level, scope, message, context) {
267733
267957
  const fullContext = {
267734
267958
  ...context,
267735
267959
  _emittedAt: (/* @__PURE__ */ new Date()).toISOString(),
267736
- _diagnosticsId: randomUUID44()
267960
+ _diagnosticsId: randomUUID45()
267737
267961
  };
267738
267962
  try {
267739
267963
  _sink(level, scope, message, fullContext);
@@ -268449,7 +268673,7 @@ var init_planning_board_tools = __esm({
268449
268673
  });
268450
268674
 
268451
268675
  // ../dashboard/src/planning.ts
268452
- import { randomUUID as randomUUID45 } from "node:crypto";
268676
+ import { randomUUID as randomUUID46 } from "node:crypto";
268453
268677
  import { EventEmitter as EventEmitter34 } from "node:events";
268454
268678
  async function createPlanningRuntimeSession(options) {
268455
268679
  const { pluginRunner, runtimeHint, settings, ...runtimeOptions } = options ?? {};
@@ -268832,7 +269056,7 @@ async function createSession(ip, initialPlan, store, rootDir, promptOverrides, p
268832
269056
  if (!store) {
268833
269057
  throw new Error("store is required for AI-powered planning sessions");
268834
269058
  }
268835
- const sessionId = randomUUID45();
269059
+ const sessionId = randomUUID46();
268836
269060
  const session = {
268837
269061
  id: sessionId,
268838
269062
  ip,
@@ -269402,7 +269626,7 @@ function normalizePlanningQuestion(input, userInput = "") {
269402
269626
  const type = source.type === "multi_select" || source.type === "single_select" ? source.type : "single_select";
269403
269627
  const question = typeof source.question === "string" && source.question.trim() ? source.question.trim() : fallback.question;
269404
269628
  const normalized = {
269405
- id: typeof source.id === "string" && source.id.trim() ? source.id : randomUUID45(),
269629
+ id: typeof source.id === "string" && source.id.trim() ? source.id : randomUUID46(),
269406
269630
  type,
269407
269631
  question,
269408
269632
  ...typeof source.description === "string" && source.description.trim() ? { description: source.description.trim() } : {}
@@ -269428,7 +269652,7 @@ function normalizePlanningQuestion(input, userInput = "") {
269428
269652
  }
269429
269653
  function coerceQuestionResponse(response, session) {
269430
269654
  if (response.type === "question") return normalizePlanningQuestion(response.data, session.initialPlan);
269431
- return normalizePlanningQuestion({ id: randomUUID45(), type: "single_select", question: planningFallbackCopy(session.initialPlan).question }, session.initialPlan);
269655
+ return normalizePlanningQuestion({ id: randomUUID46(), type: "single_select", question: planningFallbackCopy(session.initialPlan).question }, session.initialPlan);
269432
269656
  }
269433
269657
  async function validateSession(sessionId) {
269434
269658
  const session = await getSession(sessionId);
@@ -270176,7 +270400,7 @@ async function createTaskFromPlanSession(sessionId, store, options) {
270176
270400
  throw new InvalidSessionStateError("PLANNING_CREATED_TASK_MISSING");
270177
270401
  }
270178
270402
  }
270179
- const claimOwnerToken = randomUUID45();
270403
+ const claimOwnerToken = randomUUID46();
270180
270404
  let claimed = await claimPlanningTaskCreation(sessionId, claimOwnerToken, (/* @__PURE__ */ new Date()).toISOString(), claimEpoch);
270181
270405
  if (!claimed) {
270182
270406
  session = await getDurablePlanningSession(sessionId).catch(() => void 0) ?? session;
@@ -282764,7 +282988,7 @@ var init_register_gitlab = __esm({
282764
282988
 
282765
282989
  // ../dashboard/src/terminal.ts
282766
282990
  import { spawn as spawn11 } from "node:child_process";
282767
- import { randomUUID as randomUUID46 } from "node:crypto";
282991
+ import { randomUUID as randomUUID47 } from "node:crypto";
282768
282992
  import { EventEmitter as EventEmitter41 } from "node:events";
282769
282993
  function extractBaseCommand(command) {
282770
282994
  let trimmed = command.trim();
@@ -282924,7 +283148,7 @@ var init_terminal = __esm({
282924
283148
  if (!validation.valid) {
282925
283149
  return { sessionId: "", error: validation.error };
282926
283150
  }
282927
- const sessionId = randomUUID46();
283151
+ const sessionId = randomUUID47();
282928
283152
  const childProcess5 = spawn11(command, [], {
282929
283153
  cwd,
282930
283154
  shell: true,
@@ -284396,7 +284620,7 @@ var init_src8 = __esm({
284396
284620
 
284397
284621
  // ../../plugins/fusion-plugin-openclaw-runtime/src/pi-module.ts
284398
284622
  import { spawn as spawn13 } from "node:child_process";
284399
- import { randomUUID as randomUUID47 } from "node:crypto";
284623
+ import { randomUUID as randomUUID48 } from "node:crypto";
284400
284624
  import { readFile as readFile44 } from "node:fs/promises";
284401
284625
  function asString2(v2) {
284402
284626
  return typeof v2 === "string" && v2.trim() !== "" ? v2.trim() : void 0;
@@ -284456,7 +284680,7 @@ function extractStderrError(stderr, stdout) {
284456
284680
  }
284457
284681
  function createCliSession(opts) {
284458
284682
  return {
284459
- sessionId: randomUUID47(),
284683
+ sessionId: randomUUID48(),
284460
284684
  agentId: opts.agentId ?? DEFAULT_AGENT_ID,
284461
284685
  systemPrompt: opts.systemPrompt,
284462
284686
  messages: [{ role: "developer", content: opts.systemPrompt }],
@@ -284651,7 +284875,7 @@ var init_mcp_config2 = __esm({
284651
284875
  });
284652
284876
 
284653
284877
  // ../../plugins/fusion-plugin-openclaw-runtime/src/runtime-adapter.ts
284654
- import { randomUUID as randomUUID48 } from "node:crypto";
284878
+ import { randomUUID as randomUUID49 } from "node:crypto";
284655
284879
  var OpenClawRuntimeAdapter;
284656
284880
  var init_runtime_adapter2 = __esm({
284657
284881
  "../../plugins/fusion-plugin-openclaw-runtime/src/runtime-adapter.ts"() {
@@ -284674,8 +284898,8 @@ var init_runtime_adapter2 = __esm({
284674
284898
  let mcpProfile;
284675
284899
  let mcpConfigPath;
284676
284900
  if (toolDefs.length > 0) {
284677
- const bridge = writeOpenClawMcpBridgeFiles(toolDefs, randomUUID48());
284678
- mcpProfile = `fusion-${randomUUID48()}`;
284901
+ const bridge = writeOpenClawMcpBridgeFiles(toolDefs, randomUUID49());
284902
+ mcpProfile = `fusion-${randomUUID49()}`;
284679
284903
  mcpConfigPath = bridge.serverConfigPath;
284680
284904
  await configureOpenClawMcpServer({
284681
284905
  binaryPath: this.config.binaryPath,
@@ -287149,7 +287373,7 @@ import { existsSync as existsSync73, writeFileSync as writeFileSync15 } from "no
287149
287373
  import { tmpdir as tmpdir16 } from "node:os";
287150
287374
  import { dirname as dirname41, join as join93 } from "node:path";
287151
287375
  import { fileURLToPath as fileURLToPath9 } from "node:url";
287152
- import { randomUUID as randomUUID49 } from "node:crypto";
287376
+ import { randomUUID as randomUUID50 } from "node:crypto";
287153
287377
  function toolsToMcpToolDefs2(tools) {
287154
287378
  if (!Array.isArray(tools)) return [];
287155
287379
  return tools.filter((tool) => tool && typeof tool.name === "string" && tool.name.trim().length > 0 && !BUILT_IN_TOOL_NAMES2.has(tool.name)).map((tool) => ({
@@ -287200,7 +287424,7 @@ async function startFusionToolBridge(tools) {
287200
287424
  byName.set(tool.name, tool);
287201
287425
  }
287202
287426
  }
287203
- const schemaPath = join93(tmpdir16(), `fusion-grok-mcp-schemas-${process.pid}-${randomUUID49()}.json`);
287427
+ const schemaPath = join93(tmpdir16(), `fusion-grok-mcp-schemas-${process.pid}-${randomUUID50()}.json`);
287204
287428
  writeFileSync15(schemaPath, JSON.stringify(defs));
287205
287429
  const server = createServer2(async (req, res) => {
287206
287430
  if (req.method !== "POST" || req.url !== "/tool-call") {
@@ -287226,7 +287450,7 @@ async function startFusionToolBridge(tools) {
287226
287450
  return;
287227
287451
  }
287228
287452
  try {
287229
- const result2 = await tool.execute(`grok-mcp-${randomUUID49()}`, parsed.arguments ?? {}, void 0, void 0, void 0);
287453
+ const result2 = await tool.execute(`grok-mcp-${randomUUID50()}`, parsed.arguments ?? {}, void 0, void 0, void 0);
287230
287454
  res.statusCode = 200;
287231
287455
  res.setHeader("content-type", "application/json");
287232
287456
  res.end(
@@ -289270,7 +289494,7 @@ import { existsSync as existsSync77, rmSync as rmSync13, writeFileSync as writeF
289270
289494
  import { tmpdir as tmpdir18 } from "node:os";
289271
289495
  import { dirname as dirname46, join as join98 } from "node:path";
289272
289496
  import { fileURLToPath as fileURLToPath13 } from "node:url";
289273
- import { randomBytes as randomBytes7, randomUUID as randomUUID50 } from "node:crypto";
289497
+ import { randomBytes as randomBytes7, randomUUID as randomUUID51 } from "node:crypto";
289274
289498
  function toolsToMcpToolDefs3(tools) {
289275
289499
  if (!Array.isArray(tools)) return [];
289276
289500
  return tools.filter((tool) => tool && typeof tool.name === "string" && tool.name.trim().length > 0 && !BUILT_IN_TOOL_NAMES3.has(tool.name)).map((tool) => ({
@@ -289321,7 +289545,7 @@ async function startFusionToolBridge2(tools, options = {}) {
289321
289545
  byName.set(tool.name, tool);
289322
289546
  }
289323
289547
  }
289324
- const schemaPath = join98(tmpdir18(), `fusion-claude-mcp-schemas-${process.pid}-${randomUUID50()}.json`);
289548
+ const schemaPath = join98(tmpdir18(), `fusion-claude-mcp-schemas-${process.pid}-${randomUUID51()}.json`);
289325
289549
  writeFileSync17(schemaPath, JSON.stringify(defs));
289326
289550
  const capabilityToken = randomBytes7(32).toString("base64url");
289327
289551
  const server = createServer3(async (req, res) => {
@@ -289386,7 +289610,7 @@ async function startFusionToolBridge2(tools, options = {}) {
289386
289610
  return;
289387
289611
  }
289388
289612
  try {
289389
- const result2 = await tool.execute(`claude-mcp-${randomUUID50()}`, parsed.arguments ?? {}, void 0, void 0, void 0);
289613
+ const result2 = await tool.execute(`claude-mcp-${randomUUID51()}`, parsed.arguments ?? {}, void 0, void 0, void 0);
289390
289614
  res.statusCode = 200;
289391
289615
  res.setHeader("content-type", "application/json");
289392
289616
  res.end(
@@ -291463,7 +291687,7 @@ import { existsSync as existsSync79, unlinkSync as unlinkSync5, writeFileSync as
291463
291687
  import { tmpdir as tmpdir19 } from "node:os";
291464
291688
  import { dirname as dirname49, join as join101 } from "node:path";
291465
291689
  import { fileURLToPath as fileURLToPath15 } from "node:url";
291466
- import { randomUUID as randomUUID51 } from "node:crypto";
291690
+ import { randomUUID as randomUUID52 } from "node:crypto";
291467
291691
  function toolsToMcpToolDefs4(tools) {
291468
291692
  if (!Array.isArray(tools)) return [];
291469
291693
  return tools.filter(
@@ -291520,7 +291744,7 @@ async function startFusionToolBridge3(tools) {
291520
291744
  byName.set(tool.name, tool);
291521
291745
  }
291522
291746
  }
291523
- const schemaPath = join101(tmpdir19(), `fusion-omp-mcp-schemas-${process.pid}-${randomUUID51()}.json`);
291747
+ const schemaPath = join101(tmpdir19(), `fusion-omp-mcp-schemas-${process.pid}-${randomUUID52()}.json`);
291524
291748
  writeFileSync18(schemaPath, JSON.stringify(defs));
291525
291749
  const server = createServer4(async (req, res) => {
291526
291750
  if (req.method !== "POST" || req.url !== "/tool-call") {
@@ -291547,7 +291771,7 @@ async function startFusionToolBridge3(tools) {
291547
291771
  }
291548
291772
  try {
291549
291773
  const result2 = await tool.execute(
291550
- `omp-mcp-${randomUUID51()}`,
291774
+ `omp-mcp-${randomUUID52()}`,
291551
291775
  parsed.arguments ?? {},
291552
291776
  void 0,
291553
291777
  void 0,
@@ -292440,7 +292664,7 @@ var init_paperclip_client = __esm({
292440
292664
  });
292441
292665
 
292442
292666
  // ../../plugins/fusion-plugin-paperclip-runtime/src/runtime-adapter.ts
292443
- import { randomUUID as randomUUID52 } from "node:crypto";
292667
+ import { randomUUID as randomUUID53 } from "node:crypto";
292444
292668
  function sleep7(ms2) {
292445
292669
  return new Promise((resolve54) => setTimeout(resolve54, ms2));
292446
292670
  }
@@ -292569,7 +292793,7 @@ var init_runtime_adapter10 = __esm({
292569
292793
  apiKey: effectiveApiKey,
292570
292794
  agentId,
292571
292795
  companyId,
292572
- sessionId: randomUUID52(),
292796
+ sessionId: randomUUID53(),
292573
292797
  systemPrompt: options.systemPrompt,
292574
292798
  cwd: options.cwd,
292575
292799
  mode: normalizeMode3(this.config.mode),
@@ -293247,7 +293471,7 @@ var init_types5 = __esm({
293247
293471
  });
293248
293472
 
293249
293473
  // ../dashboard/src/stt/model-manager.ts
293250
- import { createHash as createHash28, randomUUID as randomUUID53 } from "node:crypto";
293474
+ import { createHash as createHash28, randomUUID as randomUUID54 } from "node:crypto";
293251
293475
  import { mkdir as mkdir31, open as open4, readFile as readFile46, readdir as readdir11, rename as rename10, rm as rm17, stat as stat12, writeFile as writeFile33 } from "node:fs/promises";
293252
293476
  import { join as join102, relative as relative19, resolve as resolve51, sep as sep15 } from "node:path";
293253
293477
  function parseSafeTarListing(listing) {
@@ -293328,8 +293552,8 @@ function createVoiceModelManager(options = {}) {
293328
293552
  if (!current(g2, signal)) return state;
293329
293553
  await mkdir31(cacheDir(), { recursive: true });
293330
293554
  if (!current(g2, signal)) return state;
293331
- partial2 = join102(cacheDir(), `.partial-${g2}-${randomUUID53()}`);
293332
- staging = join102(cacheDir(), `.staging-${g2}-${randomUUID53()}`);
293555
+ partial2 = join102(cacheDir(), `.partial-${g2}-${randomUUID54()}`);
293556
+ staging = join102(cacheDir(), `.staging-${g2}-${randomUUID54()}`);
293333
293557
  const file = await open4(partial2, "wx");
293334
293558
  try {
293335
293559
  const response = await (options.fetch ?? globalThis.fetch)(asset.url, { signal });
@@ -293377,7 +293601,7 @@ function createVoiceModelManager(options = {}) {
293377
293601
  await writeFile33(join102(staging, "manifest.json"), JSON.stringify({ filename: asset.filename, sha256: asset.sha256, installedAt: (/* @__PURE__ */ new Date()).toISOString(), expectedFiles: asset.expectedFiles }));
293378
293602
  await options.beforePromote?.(signal);
293379
293603
  if (!current(g2, signal)) return state;
293380
- const backup = join102(cacheDir(), `.backup-${g2}-${randomUUID53()}`);
293604
+ const backup = join102(cacheDir(), `.backup-${g2}-${randomUUID54()}`);
293381
293605
  let movedPrevious = false;
293382
293606
  try {
293383
293607
  try {
@@ -293598,7 +293822,7 @@ var init_parakeet_service = __esm({
293598
293822
  });
293599
293823
 
293600
293824
  // ../dashboard/src/routes/register-voice-routes.ts
293601
- import { randomUUID as randomUUID54 } from "node:crypto";
293825
+ import { randomUUID as randomUUID55 } from "node:crypto";
293602
293826
  import express from "express";
293603
293827
  async function settingsFor(ctx, req) {
293604
293828
  const scoped = await ctx.getScopedStore(req);
@@ -293677,7 +293901,7 @@ function createRegisterVoiceRoutes(deps = {}) {
293677
293901
  recognizer.close();
293678
293902
  return error2(res, 409, { error: "unavailable", reason: "model-removed" });
293679
293903
  }
293680
- const id = randomUUID54();
293904
+ const id = randomUUID55();
293681
293905
  const now = Date.now();
293682
293906
  sessions6.set(id, { projectId, recognizer, next: 0, bytes: 0, last: now, expires: now + 3e5 });
293683
293907
  res.status(201).json({ sessionId: id, expiresAt: new Date(now + 3e5).toISOString(), modelId: model.id, language: language.language });
@@ -297003,14 +297227,14 @@ init_src2();
297003
297227
  init_gh_cli();
297004
297228
  init_src5();
297005
297229
  init_src15();
297006
- import { randomUUID as randomUUID55 } from "node:crypto";
297230
+ import { randomUUID as randomUUID56 } from "node:crypto";
297007
297231
  import { Type as Type9 } from "typebox";
297008
297232
  import { StringEnum } from "@earendil-works/pi-ai";
297009
297233
  import { resolve as resolve53, relative as relative20, isAbsolute as isAbsolute26, sep as sep16, basename as basename29, extname as extname4, join as join105 } from "node:path";
297010
297234
  import { readFile as readFile47 } from "node:fs/promises";
297011
297235
  import { existsSync as existsSync82 } from "node:fs";
297012
297236
  import { spawn as spawn23 } from "node:child_process";
297013
- import { AsyncLocalStorage } from "node:async_hooks";
297237
+ import { AsyncLocalStorage as AsyncLocalStorage2 } from "node:async_hooks";
297014
297238
  function truncateAgentDiagnosticText(value, maxChars) {
297015
297239
  return value.length > maxChars ? `${value.slice(0, maxChars)}\u2026` : value;
297016
297240
  }
@@ -297115,7 +297339,7 @@ var storeCache2 = extensionStoreState.cache;
297115
297339
  var storeBootInflight = extensionStoreState.bootInflight;
297116
297340
  var storeBootFailureCooldown = extensionStoreState.bootFailureCooldown;
297117
297341
  var BOOT_FAILURE_COOLDOWN_MS = 5e3;
297118
- var extensionToolSignal = new AsyncLocalStorage();
297342
+ var extensionToolSignal = new AsyncLocalStorage2();
297119
297343
  var EXTENSION_STORE_BOOT_TIMEOUT_MS = 3e4;
297120
297344
  var EXTENSION_TOOL_TIMEOUT_MS = 6e4;
297121
297345
  var SKILLS_INSTALL_TIMEOUT_MS = 3e5;
@@ -297417,6 +297641,28 @@ async function isEphemeralCallerAgent(cwd, callerAgentId) {
297417
297641
  }
297418
297642
  }
297419
297643
  var AMBIGUOUS_AGENT_PRINCIPAL_ID = "unknown-agent";
297644
+ function resolveSecretAccessPrincipal(ctx) {
297645
+ const principal = resolveExtensionCallerPrincipal(ctx);
297646
+ if (principal.kind === "ambiguous") return { kind: "ambiguous" };
297647
+ if (principal.kind === "operator") {
297648
+ return {
297649
+ kind: "resolved",
297650
+ actor: { actorId: "user", actorType: "user", actorName: "CLI User" },
297651
+ agentId: null
297652
+ };
297653
+ }
297654
+ return {
297655
+ kind: "resolved",
297656
+ actor: {
297657
+ actorId: principal.identity.agentId,
297658
+ actorType: "agent",
297659
+ actorName: principal.identity.agentName ?? principal.identity.agentId
297660
+ },
297661
+ agentId: principal.identity.agentId,
297662
+ ...principal.identity.agentName ? { agentName: principal.identity.agentName } : {},
297663
+ ...principal.identity.taskId ? { taskId: principal.identity.taskId } : {}
297664
+ };
297665
+ }
297420
297666
  function resolveExtensionCallerPrincipal(ctx) {
297421
297667
  const explicitAgentId = typeof ctx.agentId === "string" && ctx.agentId.trim().length > 0 ? ctx.agentId.trim() : void 0;
297422
297668
  if (explicitAgentId) {
@@ -297976,7 +298222,7 @@ function kbExtension(pi2) {
297976
298222
  const layer = store.getAsyncLayer();
297977
298223
  const messageStore = layer ? new MessageStore(null, { asyncLayer: layer }) : new MessageStore(store.getDatabase());
297978
298224
  const title = params.description.split(/\r?\n/, 1)[0]?.trim().slice(0, 80) || "Follow-up task";
297979
- await messageStore.sendMessage({ fromId: fnCtx.agentId ?? "ephemeral-worker", fromType: "agent", toId: DASHBOARD_USER_ID, toType: "user", type: "agent-to-user", content: `Task proposal awaiting validation: ${title}`, metadata: { kind: "task-proposal", proposalStatus: "pending", proposalIdempotencyKey: randomUUID55(), proposedTask: { title, description: params.description, priority: params.priority, workflowId: params.workflow_id, dependencies: params.depends } } });
298225
+ await messageStore.sendMessage({ fromId: fnCtx.agentId ?? "ephemeral-worker", fromType: "agent", toId: DASHBOARD_USER_ID, toType: "user", type: "agent-to-user", content: `Task proposal awaiting validation: ${title}`, metadata: { kind: "task-proposal", proposalStatus: "pending", proposalIdempotencyKey: randomUUID56(), proposedTask: { title, description: params.description, priority: params.priority, workflowId: params.workflow_id, dependencies: params.depends } } });
297980
298226
  return { content: [{ type: "text", text: "Task proposal submitted to the operator for validation; no task was created." }], details: { proposed: true } };
297981
298227
  }
297982
298228
  }
@@ -299226,6 +299472,20 @@ Planning session completed. Task ${taskId} is now in planning and will be auto-p
299226
299472
  }),
299227
299473
  async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
299228
299474
  const fnCtx = ctx;
299475
+ const secretPrincipal = resolveSecretAccessPrincipal(fnCtx);
299476
+ if (secretPrincipal.kind === "ambiguous") {
299477
+ return {
299478
+ content: [{ type: "text", text: "Secret access was not requested because the calling agent identity is ambiguous. End one concurrent session and retry." }],
299479
+ isError: true,
299480
+ details: { error: "ambiguous-caller-identity", key: params.key, scope: params.scope ?? null }
299481
+ };
299482
+ }
299483
+ const effectiveCtx = {
299484
+ ...secretPrincipal.agentId ? { agentId: secretPrincipal.agentId } : {},
299485
+ ...secretPrincipal.agentName ? { agentName: secretPrincipal.agentName } : {},
299486
+ ...typeof fnCtx.taskId === "string" ? { taskId: fnCtx.taskId } : secretPrincipal.taskId ? { taskId: secretPrincipal.taskId } : {},
299487
+ ...typeof fnCtx.runId === "string" ? { runId: fnCtx.runId } : {}
299488
+ };
299229
299489
  const store = await getStore(ctx.cwd);
299230
299490
  const secretsStore = await store.getSecretsStore();
299231
299491
  const scopes = params.scope ? [params.scope] : ["project", "global"];
@@ -299248,36 +299508,32 @@ Planning session completed. Task ${taskId} is now in planning and will be auto-p
299248
299508
  settings: { secretsAccessPolicy: globalSettings.secretsAccessPolicy }
299249
299509
  });
299250
299510
  if (decision.policy === "deny") {
299251
- emitSecretAudit(store, fnCtx, "secret:approval-denied", `${resolvedScope}:${params.key}`);
299511
+ emitSecretAudit(store, effectiveCtx, "secret:approval-denied", `${resolvedScope}:${params.key}`);
299252
299512
  return { content: [{ type: "text", text: "Secret access denied by policy." }], details: { error: "denied", key: params.key, scope: resolvedScope, policySource: decision.source } };
299253
299513
  }
299254
299514
  if (decision.policy === "prompt") {
299255
299515
  const cliLayer = requireProjectLayer(store, "CLI secret approval store");
299256
299516
  const approvalStore = new ApprovalRequestStore(null, { asyncLayer: cliLayer });
299257
- const dedupeKey = `secret-read:${resolvedScope}:${params.key}:${fnCtx.agentId ?? "unknown"}`;
299258
- const requesterActorId = fnCtx.agentId ?? "user";
299259
- const requesterSnapshot = fnCtx.agentId ? {
299260
- actorId: requesterActorId,
299261
- actorType: "agent",
299262
- actorName: fnCtx.agentName ?? fnCtx.agentId
299263
- } : { actorId: "user", actorType: "user", actorName: "CLI User" };
299264
- const existing = await findLatestApprovalRequestByDedupeKey(approvalStore, { requesterActorId, ...fnCtx.taskId ? { taskId: fnCtx.taskId } : {}, dedupeKey });
299517
+ const requesterSnapshot = secretPrincipal.actor;
299518
+ const requesterActorId = requesterSnapshot.actorId;
299519
+ const dedupeKey = `secret-read:${resolvedScope}:${params.key}:${requesterActorId}`;
299520
+ const existing = await findLatestApprovalRequestByDedupeKey(approvalStore, { requesterActorId, ...effectiveCtx.taskId ? { taskId: effectiveCtx.taskId } : {}, dedupeKey });
299265
299521
  if (existing?.status === "pending") {
299266
- emitSecretAudit(store, fnCtx, "secret:approval-requested", `${resolvedScope}:${params.key}`);
299522
+ emitSecretAudit(store, effectiveCtx, "secret:approval-requested", `${resolvedScope}:${params.key}`);
299267
299523
  return {
299268
299524
  content: [{ type: "text", text: `Secret access approval request ${existing.id} is still pending. Approve via POST /api/approvals/:id/decision.` }],
299269
299525
  details: { outcome: "pending_approval", approvalRequestId: existing.id, key: params.key, scope: resolvedScope }
299270
299526
  };
299271
299527
  }
299272
299528
  if (existing?.status === "denied") {
299273
- emitSecretAudit(store, fnCtx, "secret:approval-denied", `${resolvedScope}:${params.key}`);
299529
+ emitSecretAudit(store, effectiveCtx, "secret:approval-denied", `${resolvedScope}:${params.key}`);
299274
299530
  return {
299275
299531
  content: [{ type: "text", text: `Secret access request ${existing.id} was denied by the operator. Do not retry without operator direction.` }],
299276
299532
  details: { outcome: "denied", approvalRequestId: existing.id, key: params.key, scope: resolvedScope }
299277
299533
  };
299278
299534
  }
299279
299535
  if (existing?.status === "approved") {
299280
- const revealedAfterApproval = await secretsStore.revealSecret(record2.id, resolvedScope, { agentId: fnCtx.agentId ?? null });
299536
+ const revealedAfterApproval = await secretsStore.revealSecret(record2.id, resolvedScope, { agentId: secretPrincipal.agentId });
299281
299537
  await approvalStore.markCompleted(existing.id, {
299282
299538
  actor: requesterSnapshot,
299283
299539
  note: "Secret revealed after approval",
@@ -299285,7 +299541,7 @@ Planning session completed. Task ${taskId} is now in planning and will be auto-p
299285
299541
  // get the same expectedRequesterActorId enforcement as the gate path.
299286
299542
  expectedRequesterActorId: requesterActorId
299287
299543
  });
299288
- emitSecretAudit(store, fnCtx, "secret:read", `${resolvedScope}:${params.key}`, { key: params.key, scope: resolvedScope, approvalRequestId: existing.id });
299544
+ emitSecretAudit(store, effectiveCtx, "secret:read", `${resolvedScope}:${params.key}`, { key: params.key, scope: resolvedScope, approvalRequestId: existing.id });
299289
299545
  return {
299290
299546
  content: [{ type: "text", text: `Loaded secret '${params.key}' from ${resolvedScope} scope (approval ${existing.id} consumed).` }],
299291
299547
  details: { key: params.key, value: revealedAfterApproval.plaintextValue, scope: resolvedScope, approvalRequestId: existing.id }
@@ -299301,17 +299557,17 @@ Planning session completed. Task ${taskId} is now in planning and will be auto-p
299301
299557
  resourceId: record2.id,
299302
299558
  context: { approvalDedupeKey: dedupeKey, key: params.key, scope: resolvedScope }
299303
299559
  },
299304
- ...fnCtx.runId ? { runId: fnCtx.runId } : {},
299305
- ...fnCtx.taskId ? { taskId: fnCtx.taskId } : {}
299560
+ ...effectiveCtx.runId ? { runId: effectiveCtx.runId } : {},
299561
+ ...effectiveCtx.taskId ? { taskId: effectiveCtx.taskId } : {}
299306
299562
  });
299307
- emitSecretAudit(store, fnCtx, "secret:approval-requested", `${resolvedScope}:${params.key}`);
299563
+ emitSecretAudit(store, effectiveCtx, "secret:approval-requested", `${resolvedScope}:${params.key}`);
299308
299564
  return {
299309
299565
  content: [{ type: "text", text: `Secret access requires approval. Request ${request2.id} is pending. Approve via POST /api/approvals/:id/decision.` }],
299310
299566
  details: { outcome: "pending_approval", approvalRequestId: request2.id, key: params.key, scope: resolvedScope }
299311
299567
  };
299312
299568
  }
299313
- const revealed = await secretsStore.revealSecret(record2.id, resolvedScope, { agentId: fnCtx.agentId ?? null });
299314
- emitSecretAudit(store, fnCtx, "secret:read", `${resolvedScope}:${params.key}`, { key: params.key, scope: resolvedScope });
299569
+ const revealed = await secretsStore.revealSecret(record2.id, resolvedScope, { agentId: secretPrincipal.agentId });
299570
+ emitSecretAudit(store, effectiveCtx, "secret:read", `${resolvedScope}:${params.key}`, { key: params.key, scope: resolvedScope });
299315
299571
  return {
299316
299572
  content: [{ type: "text", text: `Loaded secret '${params.key}' from ${resolvedScope} scope.` }],
299317
299573
  details: { key: params.key, value: revealed.plaintextValue, scope: resolvedScope }