@runfusion/fusion 0.75.1-beta.1 → 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 (117) hide show
  1. package/dist/bin.js +264 -136
  2. package/dist/child-process-worker.js +138 -38
  3. package/dist/client/.vite/manifest.json +253 -253
  4. package/dist/client/assets/{AgentDetailView-CE_7nvDT.js → AgentDetailView-ltOG8f0-.js} +1 -1
  5. package/dist/client/assets/{AgentPermissionPolicyEditor-DYr3dk5X.js → AgentPermissionPolicyEditor-CC_rrbbG.js} +1 -1
  6. package/dist/client/assets/{AgentsView-DZoYoiaD.js → AgentsView-DWUdi27N.js} +3 -3
  7. package/dist/client/assets/{ChatView-BE9zMzYZ.js → ChatView-mFKfoegS.js} +1 -1
  8. package/dist/client/assets/{CommandCenter-DnLsp4_S.js → CommandCenter-C2wvDq_N.js} +1 -1
  9. package/dist/client/assets/{DevServerView-CI-3j-lH.js → DevServerView-0MWEJM7o.js} +1 -1
  10. package/dist/client/assets/{DirectoryPicker-D1ADu0Av.js → DirectoryPicker-BjMPhCdh.js} +1 -1
  11. package/dist/client/assets/{DocumentsView-CF8y1gob.js → DocumentsView-ckH3rjmd.js} +1 -1
  12. package/dist/client/assets/{EvalsView-C9vLiM2_.js → EvalsView-NyA644VF.js} +1 -1
  13. package/dist/client/assets/{ExperimentalAgentOnboardingModal-CX_2Hjh0.js → ExperimentalAgentOnboardingModal-D0ZfbBRN.js} +1 -1
  14. package/dist/client/assets/{GoalsView-DJIA3k8q.js → GoalsView-Ctt5P_yo.js} +1 -1
  15. package/dist/client/assets/{InsightsView--TCeutLO.js → InsightsView-NDp0bdNB.js} +1 -1
  16. package/dist/client/assets/{MemoryView-Btp4m7pK.js → MemoryView-BKbA4Bm4.js} +1 -1
  17. package/dist/client/assets/{PiExtensionsManager-DXdfh-Uf.js → PiExtensionsManager-V3AFWMHm.js} +1 -1
  18. package/dist/client/assets/{PluginManager-BDFelFY6.js → PluginManager-D88G_TZh.js} +1 -1
  19. package/dist/client/assets/{PullRequestView-TSNKGSAg.js → PullRequestView-DQQmFFYC.js} +1 -1
  20. package/dist/client/assets/{ReportModal-DhXMNHDL.js → ReportModal-Clh1S8RF.js} +1 -1
  21. package/dist/client/assets/{ResearchView-0w30HikQ.js → ResearchView-kxvq4zp5.js} +1 -1
  22. package/dist/client/assets/SecretsView-Dm0Vk8SA.js +1 -0
  23. package/dist/client/assets/{SessionTerminal-CF6_uy2u.js → SessionTerminal-DJO4JTEX.js} +1 -1
  24. package/dist/client/assets/{SettingsModal-BMAPX_7v.js → SettingsModal-BN5mL_ym.js} +1 -1
  25. package/dist/client/assets/SettingsModal-CTf5voEW.js +21 -0
  26. package/dist/client/assets/{SettingsTextareaRow-LqXxpJl5.js → SettingsTextareaRow-CvKszqO-.js} +1 -1
  27. package/dist/client/assets/{SetupWizardModal-D1ZvGOcK.js → SetupWizardModal-D5hVZh1G.js} +2 -2
  28. package/dist/client/assets/{SkillsView-CfO5B7SO.js → SkillsView-DQVz21w2.js} +1 -1
  29. package/dist/client/assets/{WorkflowNodeEditor-CjdK9fFS.js → WorkflowNodeEditor-CpKIi6Tb.js} +1 -1
  30. package/dist/client/assets/{agent-import-generation-DqTzyLOZ.js → agent-import-generation-DGUNF3nd.js} +1 -1
  31. package/dist/client/assets/{architectureDiagram-3BPJPVTR-CHkolL_b.js → architectureDiagram-3BPJPVTR-CV83wubP.js} +1 -1
  32. package/dist/client/assets/{blockDiagram-GPEHLZMM-BFRYQi8H.js → blockDiagram-GPEHLZMM-CNQsA3dD.js} +1 -1
  33. package/dist/client/assets/{c4Diagram-AAUBKEIU-D9mMEYbR.js → c4Diagram-AAUBKEIU-CXe7ii5H.js} +1 -1
  34. package/dist/client/assets/channel-DoX862zl.js +1 -0
  35. package/dist/client/assets/{chunk-2J33WTMH-CCv6QaQn.js → chunk-2J33WTMH-BS6W9bWv.js} +1 -1
  36. package/dist/client/assets/{chunk-4BX2VUAB-Bfe22y5T.js → chunk-4BX2VUAB-DbehRhdW.js} +1 -1
  37. package/dist/client/assets/{chunk-55IACEB6-CwicrA9V.js → chunk-55IACEB6-DMI5SR42.js} +1 -1
  38. package/dist/client/assets/{chunk-727SXJPM-DlIpTC03.js → chunk-727SXJPM-D1d-GBVj.js} +1 -1
  39. package/dist/client/assets/{chunk-AQP2D5EJ-CRX4mFVq.js → chunk-AQP2D5EJ-BzO6K3Uf.js} +1 -1
  40. package/dist/client/assets/{chunk-FMBD7UC4-DzG-bovU.js → chunk-FMBD7UC4-BVRLV3lK.js} +1 -1
  41. package/dist/client/assets/{chunk-ND2GUHAM-D5F2jtpM.js → chunk-ND2GUHAM-DdWByQfn.js} +1 -1
  42. package/dist/client/assets/{chunk-QZHKN3VN-BmSaCwG6.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-BbKTljui.js → cose-bilkent-S5V4N54A-BPDT41HR.js} +1 -1
  46. package/dist/client/assets/{dagre-BM42HDAG-u0hwXb_T.js → dagre-BM42HDAG-Cktn26EH.js} +1 -1
  47. package/dist/client/assets/{dashboard-view-BfYZZ9Ms.js → dashboard-view-8o0EUcRX.js} +1 -1
  48. package/dist/client/assets/{dashboard-view-faKSXA4_.js → dashboard-view-CkCerlVJ.js} +1 -1
  49. package/dist/client/assets/{dashboard-view-BjPZG2Fv.js → dashboard-view-D_HC4lnr.js} +1 -1
  50. package/dist/client/assets/{dashboard-view-oCjWDGbW.js → dashboard-view-zd8GxC49.js} +1 -1
  51. package/dist/client/assets/{diagram-2AECGRRQ-Q7towbBl.js → diagram-2AECGRRQ-But4Lvf-.js} +1 -1
  52. package/dist/client/assets/{diagram-5GNKFQAL-UAwBmAxb.js → diagram-5GNKFQAL-2bpQAGFc.js} +1 -1
  53. package/dist/client/assets/{diagram-KO2AKTUF-BL1n0pkf.js → diagram-KO2AKTUF-Ckn9Qd1u.js} +1 -1
  54. package/dist/client/assets/{diagram-LMA3HP47-O-v_IZUJ.js → diagram-LMA3HP47-Bjn8h84N.js} +1 -1
  55. package/dist/client/assets/{diagram-OG6HWLK6-CAYSE2Mz.js → diagram-OG6HWLK6-BI6541hx.js} +1 -1
  56. package/dist/client/assets/{erDiagram-TEJ5UH35-Cr_DgCyp.js → erDiagram-TEJ5UH35-BqMiwHiM.js} +1 -1
  57. package/dist/client/assets/{flowDiagram-I6XJVG4X-TdLzHKPW.js → flowDiagram-I6XJVG4X-Q_SAB10M.js} +1 -1
  58. package/dist/client/assets/{folder-open-BoddkTkv.js → folder-open-CHiAJbTN.js} +1 -1
  59. package/dist/client/assets/{ganttDiagram-6RSMTGT7-CO1ErtJY.js → ganttDiagram-6RSMTGT7-CqWFSfPF.js} +1 -1
  60. package/dist/client/assets/{gitGraphDiagram-PVQCEYII-C8_Kx0Yh.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-Dd6Zo7Dh.js → infoDiagram-5YYISTIA-BzaMa0c2.js} +1 -1
  64. package/dist/client/assets/{ishikawaDiagram-YF4QCWOH-BE9JCLb5.js → ishikawaDiagram-YF4QCWOH-I38yge00.js} +1 -1
  65. package/dist/client/assets/{journeyDiagram-JHISSGLW-B6RIZGDq.js → journeyDiagram-JHISSGLW-WVQSgx4P.js} +1 -1
  66. package/dist/client/assets/{kanban-definition-UN3LZRKU-CPXff-h-.js → kanban-definition-UN3LZRKU-BWtPQ3AH.js} +1 -1
  67. package/dist/client/assets/{mermaid.core-CbGj7iEw.js → mermaid.core-D1hnBtbi.js} +4 -4
  68. package/dist/client/assets/{mindmap-definition-RKZ34NQL-DF5LZmsH.js → mindmap-definition-RKZ34NQL-DqiSLkW7.js} +1 -1
  69. package/dist/client/assets/{pieDiagram-4H26LBE5-Dpy0Xm9T.js → pieDiagram-4H26LBE5-CcgwbdIN.js} +1 -1
  70. package/dist/client/assets/{puzzle-BZa4U8-N.js → puzzle-BhXUaTQl.js} +1 -1
  71. package/dist/client/assets/{quadrantDiagram-W4KKPZXB-BTLAQFpz.js → quadrantDiagram-W4KKPZXB-MsqQQfDN.js} +1 -1
  72. package/dist/client/assets/{requirementDiagram-4Y6WPE33-DSUV9ySs.js → requirementDiagram-4Y6WPE33-CILGjMMA.js} +1 -1
  73. package/dist/client/assets/{sankeyDiagram-5OEKKPKP-Fmfn0XDL.js → sankeyDiagram-5OEKKPKP-c14fWbtR.js} +1 -1
  74. package/dist/client/assets/{sequenceDiagram-3UESZ5HK-_343I_b4.js → sequenceDiagram-3UESZ5HK-BD5bxMom.js} +1 -1
  75. package/dist/client/assets/{shield-alert-CdJ55UvI.js → shield-alert-C9W2WywS.js} +1 -1
  76. package/dist/client/assets/{standing-instructions-template-r03dZ5Z0.js → standing-instructions-template-CJdbDSyg.js} +1 -1
  77. package/dist/client/assets/{stateDiagram-AJRCARHV-C9EbWegm.js → stateDiagram-AJRCARHV-CZ5YqeOn.js} +1 -1
  78. package/dist/client/assets/{stateDiagram-v2-BHNVJYJU-Bmh2GRh6.js → stateDiagram-v2-BHNVJYJU-mdMN-PNX.js} +1 -1
  79. package/dist/client/assets/{timeline-definition-PNZ67QCA-q86S9wyl.js → timeline-definition-PNZ67QCA-DHKay9uj.js} +1 -1
  80. package/dist/client/assets/{upload-DS9-qtCF.js → upload-DxEwowRU.js} +1 -1
  81. package/dist/client/assets/{users-DP5Sngeg.js → users-mNFgW_Zd.js} +1 -1
  82. package/dist/client/assets/{vennDiagram-CIIHVFJN-BFq8uIt3.js → vennDiagram-CIIHVFJN-X29K9hWM.js} +1 -1
  83. package/dist/client/assets/{wardley-L42UT6IY-BL_sLGYI.js → wardley-L42UT6IY-yEoTrfmN.js} +1 -1
  84. package/dist/client/assets/{wardleyDiagram-YWT4CUSO-BEKJJ9Vi.js → wardleyDiagram-YWT4CUSO-ny4k3kG0.js} +1 -1
  85. package/dist/client/assets/{xychartDiagram-2RQKCTM6-DsvFnlxK.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 +225 -93
  90. package/dist/pi-claude-cli/package.json +1 -1
  91. package/dist/plugin-sdk/index.d.ts +2 -1
  92. package/dist/plugins/fusion-plugin-acp-runtime/package.json +1 -1
  93. package/dist/plugins/fusion-plugin-claude-runtime/package.json +1 -1
  94. package/dist/plugins/fusion-plugin-cli-printing-press/package.json +1 -1
  95. package/dist/plugins/fusion-plugin-compound-engineering/package.json +1 -1
  96. package/dist/plugins/fusion-plugin-cursor-runtime/package.json +1 -1
  97. package/dist/plugins/fusion-plugin-dependency-graph/package.json +1 -1
  98. package/dist/plugins/fusion-plugin-droid-runtime/package.json +1 -1
  99. package/dist/plugins/fusion-plugin-grok-runtime/package.json +1 -1
  100. package/dist/plugins/fusion-plugin-hermes-runtime/package.json +1 -1
  101. package/dist/plugins/fusion-plugin-linear-import/package.json +1 -1
  102. package/dist/plugins/fusion-plugin-omp-runtime/package.json +1 -1
  103. package/dist/plugins/fusion-plugin-openclaw-runtime/package.json +1 -1
  104. package/dist/plugins/fusion-plugin-paperclip-runtime/package.json +1 -1
  105. package/dist/plugins/fusion-plugin-quality/package.json +1 -1
  106. package/dist/plugins/fusion-plugin-reports/package.json +1 -1
  107. package/dist/plugins/fusion-plugin-roadmap/package.json +1 -1
  108. package/dist/plugins/fusion-plugin-todos/package.json +1 -1
  109. package/dist/plugins/fusion-plugin-whatsapp-chat/package.json +1 -1
  110. package/package.json +1 -1
  111. package/dist/client/assets/SecretsView-DgMu7279.js +0 -1
  112. package/dist/client/assets/SettingsModal-Dckls6LL.js +0 -21
  113. package/dist/client/assets/channel-CLdamWm5.js +0 -1
  114. package/dist/client/assets/classDiagram-4FO5ZUOK-BYWknNX0.js +0 -1
  115. package/dist/client/assets/classDiagram-v2-Q7XG4LA2-BYWknNX0.js +0 -1
  116. package/dist/client/assets/index-C7KY0q8Q.js +0 -2767
  117. 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
  }
@@ -63343,7 +63346,7 @@ function rowToTask(row) {
63343
63346
  executionStartBranch: row.executionStartBranch || void 0,
63344
63347
  branch: row.branch || void 0,
63345
63348
  autoMerge: row.autoMerge === null ? void 0 : row.autoMerge === 1,
63346
- 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,
63347
63350
  baseCommitSha: row.baseCommitSha || void 0,
63348
63351
  scopeOverride: row.scopeOverride ? true : void 0,
63349
63352
  scopeOverrideReason: row.scopeOverrideReason || void 0,
@@ -72859,10 +72862,10 @@ ${feature.acceptanceCriteria}`);
72859
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.
72860
72863
  */
72861
72864
  /*
72862
- FNXC:MissionAutoMerge 2026-07-18-12:00:
72863
- 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.
72864
72867
  */
72865
- ...mission?.autoMerge === false ? { autoMerge: false } : {},
72868
+ ...mission?.autoMerge === false ? { autoMerge: false, autoMergeProvenance: "mission" } : {},
72866
72869
  // FNXC:MissionTaskPrefix 2026-07-26-12:00: thread the mission's optional taskPrefix into TaskCreateInput for distributed id minting.
72867
72870
  ...mission?.taskPrefix ? { taskPrefix: mission.taskPrefix } : {},
72868
72871
  ...branchOptions?.workflowId !== void 0 ? { workflowId: branchOptions.workflowId } : {}
@@ -76040,10 +76043,10 @@ ${feature.acceptanceCriteria}`);
76040
76043
  }
76041
76044
  } : {},
76042
76045
  /*
76043
- FNXC:MissionAutoMerge 2026-07-18-12:00:
76044
- 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.
76045
76048
  */
76046
- ...mission?.autoMerge === false ? { autoMerge: false } : {},
76049
+ ...mission?.autoMerge === false ? { autoMerge: false, autoMergeProvenance: "mission" } : {},
76047
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.
76048
76051
  ...mission?.taskPrefix ? { taskPrefix: mission.taskPrefix } : {},
76049
76052
  ...branchOptions?.workflowId !== void 0 ? { workflowId: branchOptions.workflowId } : {}
@@ -86394,7 +86397,9 @@ async function _createTaskInternalBackendImpl(store, input, title, resolvedWorkf
86394
86397
  sourceMetadata: withTaskBranchContextInSourceMetadata(input.source?.sourceMetadata, input.branchContext),
86395
86398
  branchContext: input.branchContext,
86396
86399
  autoMerge: input.autoMerge,
86397
- 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",
86398
86403
  // FNXC:CodingIdeasWorkflow 2026-07-05-19:45: land the task in its
86399
86404
  // workflow's manual intake column (e.g. Coding (Ideas) → "ideas") when
86400
86405
  // no explicit column is given (main FN-7591 parity).
@@ -127272,6 +127277,7 @@ var init_task_timing = __esm({
127272
127277
  });
127273
127278
 
127274
127279
  // ../core/src/session-identity-registry.ts
127280
+ import { AsyncLocalStorage } from "node:async_hooks";
127275
127281
  import { resolve as resolve21 } from "node:path";
127276
127282
  import { realpathSync as realpathSync5 } from "node:fs";
127277
127283
  function getRegistry() {
@@ -127283,6 +127289,16 @@ function getRegistry() {
127283
127289
  }
127284
127290
  return registry3;
127285
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
+ }
127286
127302
  function canonicalizeCwd(cwd) {
127287
127303
  const resolved = resolve21(cwd);
127288
127304
  try {
@@ -127310,8 +127326,13 @@ function registerFusionSessionIdentity(cwd, identity) {
127310
127326
  };
127311
127327
  }
127312
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
+ }
127313
127334
  const registry3 = getRegistry();
127314
- const list = registry3.get(canonicalizeCwd(cwd));
127335
+ const list = registry3.get(canonicalCwd);
127315
127336
  if (!list || list.length === 0) {
127316
127337
  return { kind: "operator" };
127317
127338
  }
@@ -127320,14 +127341,24 @@ function resolveFusionSessionPrincipal(cwd) {
127320
127341
  }
127321
127342
  return { kind: "ambiguous", identities: [...list] };
127322
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
+ }
127323
127351
  function __clearFusionSessionIdentityRegistryForTests() {
127324
- globalThis[REGISTRY_KEY] = /* @__PURE__ */ new Map();
127352
+ const holder = globalThis;
127353
+ holder[REGISTRY_KEY] = /* @__PURE__ */ new Map();
127354
+ holder[ACTIVE_INVOCATION_KEY] = new AsyncLocalStorage();
127325
127355
  }
127326
- var REGISTRY_KEY;
127356
+ var REGISTRY_KEY, ACTIVE_INVOCATION_KEY;
127327
127357
  var init_session_identity_registry = __esm({
127328
127358
  "../core/src/session-identity-registry.ts"() {
127329
127359
  "use strict";
127330
127360
  REGISTRY_KEY = "__FUSION_SESSION_IDENTITY_REGISTRY_V1__";
127361
+ ACTIVE_INVOCATION_KEY = "__FUSION_SESSION_IDENTITY_ACTIVE_INVOCATION_V1__";
127331
127362
  }
127332
127363
  });
127333
127364
 
@@ -128298,6 +128329,7 @@ __export(src_exports, {
128298
128329
  hasRoleOverrides: () => hasRoleOverrides,
128299
128330
  hasSyncPassphraseConfigured: () => hasSyncPassphraseConfigured,
128300
128331
  hasTitleIdDrift: () => hasTitleIdDrift,
128332
+ hasUserAutoMergeHold: () => hasUserAutoMergeHold,
128301
128333
  hashWorkflowIr: () => hashWorkflowIr,
128302
128334
  healSchemaDrift: () => healSchemaDrift,
128303
128335
  importMcpServersJson: () => importMcpServersJson,
@@ -128758,6 +128790,7 @@ __export(src_exports, {
128758
128790
  runMemoryBackupCommand: () => runMemoryBackupCommand,
128759
128791
  runReconciliationAbort: () => runReconciliationAbort,
128760
128792
  runScheduledEvalBatch: () => runScheduledEvalBatch,
128793
+ runWithFusionSessionIdentity: () => runWithFusionSessionIdentity,
128761
128794
  sanitizeBranchSegment: () => sanitizeBranchSegment,
128762
128795
  sanitizeCliAgentSettings: () => sanitizeCliAgentSettings,
128763
128796
  sanitizeCliAgentsSettings: () => sanitizeCliAgentsSettings,
@@ -157407,6 +157440,7 @@ import { existsSync as existsSync43, readFileSync as readFileSync15, realpathSyn
157407
157440
  import { exec as exec2, execFile as execFile7 } from "node:child_process";
157408
157441
  import { promisify as promisify6 } from "node:util";
157409
157442
  import { createRequire as createRequire5 } from "node:module";
157443
+ import { randomUUID as randomUUID42 } from "node:crypto";
157410
157444
  import { basename as basename13, dirname as dirname21, join as join56, relative as relative6, isAbsolute as isAbsolute8, resolve as resolve24 } from "node:path";
157411
157445
  import {
157412
157446
  createAgentSession,
@@ -159117,7 +159151,8 @@ async function createFnAgent2(options) {
159117
159151
  });
159118
159152
  };
159119
159153
  const sessionIdentity = (() => {
159120
- 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()}`;
159121
159156
  const principalAgentName = options.actionGateContext?.agentName ?? options.permanentAgentGating?.requester?.actorName;
159122
159157
  return {
159123
159158
  agentId: principalAgentId,
@@ -159129,6 +159164,14 @@ async function createFnAgent2(options) {
159129
159164
  const sessionIdentityKeys = [...new Set([options.cwd, resolvedProjectRoot].filter((key) => Boolean(key)))];
159130
159165
  const attachSessionIdentity = (session) => {
159131
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");
159132
159175
  const disposeBeforeIdentity = typeof session.dispose === "function" ? session.dispose.bind(session) : () => void 0;
159133
159176
  session.dispose = async () => {
159134
159177
  for (const disposeIdentity of identityDisposers) {
@@ -167328,10 +167371,11 @@ function createMergeAttemptHandler(deps) {
167328
167371
  );
167329
167372
  };
167330
167373
  }
167331
- function createMergeGateHandler() {
167374
+ function createMergeGateHandler(deps = {}) {
167332
167375
  return async (_node, ctx) => {
167333
167376
  const settingsAutoMerge = ctx.settings?.autoMerge;
167334
- 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);
167335
167379
  return {
167336
167380
  outcome: "success",
167337
167381
  value: autoMerge ? "auto-on" : "auto-off"
@@ -167341,6 +167385,7 @@ function createMergeGateHandler() {
167341
167385
  var init_merge_runner = __esm({
167342
167386
  "../engine/src/workflow-node-runners/merge-runner.ts"() {
167343
167387
  "use strict";
167388
+ init_src2();
167344
167389
  init_workflow_merge_nodes();
167345
167390
  }
167346
167391
  });
@@ -167684,7 +167729,7 @@ function createDefaultNodeHandlers(seams, runCustomNode, deps) {
167684
167729
  "parse-steps": parseSteps,
167685
167730
  code: createCodeNodeHandler(deps?.runCode),
167686
167731
  notify: createNotifyHandler(deps?.notifyDispatch),
167687
- "merge-gate": createMergeGateHandler(),
167732
+ "merge-gate": createMergeGateHandler({ isLiveSharedBranchMember: deps?.isLiveSharedBranchMember }),
167688
167733
  "merge-attempt": createMergeAttemptHandler({
167689
167734
  primitives: deps?.primitives,
167690
167735
  seams,
@@ -168082,7 +168127,7 @@ var init_code_node_runner = __esm({
168082
168127
  // ../engine/src/agent-tools.ts
168083
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";
168084
168129
  import { existsSync as existsSync50 } from "node:fs";
168085
- import { createHash as createHash19, randomUUID as randomUUID42 } from "node:crypto";
168130
+ import { createHash as createHash19, randomUUID as randomUUID43 } from "node:crypto";
168086
168131
  import { tmpdir as tmpdir5 } from "node:os";
168087
168132
  import { extname as extname3, isAbsolute as isAbsolute12, join as join62, relative as relative11, resolve as resolve33, sep as sep9 } from "node:path";
168088
168133
  import { Type as Type2 } from "@earendil-works/pi-ai";
@@ -168728,7 +168773,7 @@ function createTaskCreateTool(store, provenance, options) {
168728
168773
  toType: "user",
168729
168774
  type: "agent-to-user",
168730
168775
  content: `Task proposal awaiting validation: ${title}`,
168731
- 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 } }
168732
168777
  });
168733
168778
  return { content: [{ type: "text", text: "Task proposal submitted to the operator for validation; no task was created." }], details: { proposed: true } };
168734
168779
  }
@@ -202305,16 +202350,29 @@ var init_self_healing = __esm({
202305
202350
  activeWork: await this.resolveActiveWorkColumnsFor(taskId, cache)
202306
202351
  };
202307
202352
  }
202308
- 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) {
202309
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);
202310
202367
  if (!isPausedAbortPark) return { kind: "no-action", reason: "not-pause-abort" };
202311
202368
  if (task.paused || task.userPaused || isExecuting) return { kind: "no-action", reason: "unsafe-or-not-routable" };
202312
202369
  const errorText = typeof task.error === "string" ? task.error.toLowerCase() : "";
202313
202370
  const isTerminalMergePark = errorText.includes("conflict") || errorText.includes("contamination") || errorText.includes("foreign") || errorText.includes("retry-exhausted") || errorText.includes("retries exhausted") || errorText.includes("max retries");
202314
202371
  const completedSteps = task.steps.length > 0 && task.steps.every((step) => step.status === "done" || step.status === "skipped");
202315
- const sharedBranchMember = isSharedBranchGroupMemberIntegration(task);
202316
- const hasReviewProgress = columns.review.has(task.column) && allowsAutoMergeProcessing(task, settings) && task.mergeDetails?.mergeConfirmed !== true && !isTerminalMergePark && completedSteps;
202317
- 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;
202318
202376
  if (hasManualMergeHoldProgress) {
202319
202377
  return { kind: "work-item-resume", reason: "pause-abort-manual-merge-hold" };
202320
202378
  }
@@ -207982,8 +208040,23 @@ var init_self_healing = __esm({
207982
208040
  return own.length > 0 ? new Set(own) : /* @__PURE__ */ new Set(["in-review"]);
207983
208041
  };
207984
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);
207985
208058
  const mergeable = tasks2.filter(
207986
- (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;
207987
208060
  // stale ones are handled by recoverStaleMergingStatus().
207988
208061
  t.status !== "merging" && t.status !== "merging-pr" && // FNXC:Workspace 2026-06-22-09:30 (Phase D U1, KTD1 — admit workspace tasks):
207989
208062
  // A workspace task has task.worktree===null (its worktrees live per-repo in
@@ -211030,7 +211103,7 @@ var init_self_healing = __esm({
211030
211103
  for (const t of tasks2) {
211031
211104
  if (!this.isPauseAbortParkCandidate(t)) continue;
211032
211105
  const columns = await this.resolvePauseAbortColumnsFor(t.id, columnCache);
211033
- 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") {
211034
211107
  parked.push(t);
211035
211108
  }
211036
211109
  }
@@ -211047,7 +211120,7 @@ var init_self_healing = __esm({
211047
211120
  continue;
211048
211121
  }
211049
211122
  const freshColumns = await this.resolvePauseAbortColumnsFor(fresh.id, columnCache);
211050
- const route = this.classifyPausedAbortWorkflowRecovery(fresh, settings, latestExecutingIds.has(fresh.id), freshColumns);
211123
+ const route = await this.classifyPausedAbortWorkflowRecovery(fresh, settings, latestExecutingIds.has(fresh.id), freshColumns);
211051
211124
  if (route.kind === "no-action") {
211052
211125
  continue;
211053
211126
  }
@@ -218098,7 +218171,8 @@ var init_workflow_graph_executor = __esm({
218098
218171
  parseSteps: deps.parseStepsDeps,
218099
218172
  runCode: deps.runCode,
218100
218173
  notifyDispatch: deps.notifyDispatch,
218101
- prNodes: deps.prNodes
218174
+ prNodes: deps.prNodes,
218175
+ isLiveSharedBranchMember: deps.isLiveSharedBranchMember
218102
218176
  }),
218103
218177
  ...deps.runnerRegistry?.toHandlers() ?? {},
218104
218178
  ...deps.handlers ?? {}
@@ -219334,6 +219408,10 @@ var init_workflow_graph_task_runner = __esm({
219334
219408
  runCode: this.deps.runCode,
219335
219409
  notifyDispatch: this.deps.notifyDispatch,
219336
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,
219337
219415
  /*
219338
219416
  FNXC:WorkflowResume 2026-06-29-08:49:
219339
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.
@@ -227446,6 +227524,7 @@ ${feedback}`,
227446
227524
  if (info.phase !== "pre-merge") return false;
227447
227525
  if (info.status !== "advisory_failure" && info.status !== "failed") return false;
227448
227526
  const liveTask = await this.store.getTask(taskId).catch(() => fallbackTask);
227527
+ if (hasUserAutoMergeHold(liveTask)) return false;
227449
227528
  const missingArtifactKeys = parseRequiredArtifactMissingValue(info.failureValue);
227450
227529
  if (missingArtifactKeys) {
227451
227530
  await this.recoverMissingRequiredArtifacts(liveTask, missingArtifactKeys, {
@@ -227702,6 +227781,7 @@ ${info.feedback}`, revisionKey),
227702
227781
  */
227703
227782
  async recoverFailedPreMergeWorkflowStep(task) {
227704
227783
  try {
227784
+ if (hasUserAutoMergeHold(task)) return false;
227705
227785
  const failed = (task.workflowStepResults ?? []).filter((r) => (r.phase || "pre-merge") === "pre-merge" && r.status === "failed").sort((a, b3) => {
227706
227786
  const aTs = Date.parse(a.completedAt || a.startedAt || "");
227707
227787
  const bTs = Date.parse(b3.completedAt || b3.startedAt || "");
@@ -228228,6 +228308,7 @@ ${info.feedback}`, revisionKey),
228228
228308
  getTask: (taskId) => this.store.getTask(taskId)
228229
228309
  },
228230
228310
  runId: resolvedRunId,
228311
+ isLiveSharedBranchMember: (nodeTask) => this.isLiveSharedBranchGroupMember(nodeTask),
228231
228312
  primitives: this.createAuthoritativeWorkflowPrimitives(settings),
228232
228313
  seams: this.createAuthoritativeWorkflowSeams(settings),
228233
228314
  prepareNodeExecution: (node, nodeTask, requirement) => this.prepareGraphNodeExecution(node, nodeTask, settings, requirement),
@@ -231169,6 +231250,7 @@ Graph value: ${failureValue}` : ""}`, budget.key),
231169
231250
  } catch {
231170
231251
  return false;
231171
231252
  }
231253
+ if (hasUserAutoMergeHold(live)) return false;
231172
231254
  const sharedBranchMember = await this.isLiveSharedBranchGroupMember(live);
231173
231255
  if (!sharedBranchMember && !allowsAutoMergeProcessing(live, settings)) return false;
231174
231256
  if (!sharedBranchMember && resolveEffectiveAutoMerge(live, settings) === false) return false;
@@ -231230,8 +231312,8 @@ Graph value: ${failureValue}` : ""}`, budget.key),
231230
231312
  } catch {
231231
231313
  return false;
231232
231314
  }
231233
- if (await this.isLiveSharedBranchGroupMember(live)) return false;
231234
- 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;
231235
231317
  }
231236
231318
  async handleStaleInReviewPlanPauseAbortReplay(live, result2, abortProvenance, pausedAborted, userCanceled, resumeLanesMemo) {
231237
231319
  if (!pausedAborted) return false;
@@ -231395,9 +231477,9 @@ Graph value: ${failureValue}` : ""}`, budget.key),
231395
231477
  if (settings.globalPause === true) return false;
231396
231478
  }
231397
231479
  if (live.column === resumeLanes.review) {
231398
- if (live.autoMerge === false) return false;
231399
231480
  if (!settings) return false;
231400
231481
  const sharedBranchMember = await this.isLiveSharedBranchGroupMember(live);
231482
+ if (hasUserAutoMergeHold(live) || live.autoMerge === false && !sharedBranchMember) return false;
231401
231483
  if (!sharedBranchMember && !allowsAutoMergeProcessing(live, settings)) return false;
231402
231484
  if (live.mergeDetails?.mergeConfirmed === true) return false;
231403
231485
  }
@@ -248472,7 +248554,7 @@ var init_metric_parser = __esm({
248472
248554
  });
248473
248555
 
248474
248556
  // ../engine/src/eval/experiment-executor.ts
248475
- import { randomUUID as randomUUID43 } from "node:crypto";
248557
+ import { randomUUID as randomUUID44 } from "node:crypto";
248476
248558
  var ExperimentMaxIterationsError, ExperimentGitNotConfiguredError, ExperimentExecutor;
248477
248559
  var init_experiment_executor = __esm({
248478
248560
  "../engine/src/eval/experiment-executor.ts"() {
@@ -248540,7 +248622,7 @@ var init_experiment_executor = __esm({
248540
248622
  }
248541
248623
  await this.semaphore.acquire();
248542
248624
  const controller = new AbortController();
248543
- const runHandle = randomUUID43();
248625
+ const runHandle = randomUUID44();
248544
248626
  if (opts?.abortSignal) {
248545
248627
  opts.abortSignal.addEventListener("abort", () => controller.abort(), { once: true });
248546
248628
  }
@@ -257547,6 +257629,7 @@ var init_in_process_runtime = __esm({
257547
257629
  }
257548
257630
  }
257549
257631
  const prNodeGithubOps = this.config.prNodeGithubOps;
257632
+ const secretsStore = await this.taskStore.getSecretsStore();
257550
257633
  const executorOptions = {
257551
257634
  /*
257552
257635
  FNXC:PlanReviewLease 2026-07-26-21:12:
@@ -257562,6 +257645,7 @@ var init_in_process_runtime = __esm({
257562
257645
  cliAgentRuntime: this.cliAgentRuntime?.bundle,
257563
257646
  pluginRunner: this.pluginRunner,
257564
257647
  messageStore: this.messageStore,
257648
+ secretsStore,
257565
257649
  missionStore,
257566
257650
  reflectionService,
257567
257651
  // PR-entity nodes (U3): assemble the handler deps from the CLI-injected
@@ -257663,6 +257747,7 @@ var init_in_process_runtime = __esm({
257663
257747
  reflectionService,
257664
257748
  selfImproveService,
257665
257749
  credentialRotator: this.credentialRotator,
257750
+ secretsStore,
257666
257751
  snapshotManager: autoClaimSnapshotManager,
257667
257752
  onMissed: (agentId, reason) => {
257668
257753
  runtimeLog.warn(`Agent ${agentId} missed heartbeat: ${reason}`);
@@ -261524,7 +261609,21 @@ var init_project_engine = __esm({
261524
261609
  Cleared on the first non-busy outcome (success path resets it).
261525
261610
  */
261526
261611
  workspaceBusyReenqueues = /* @__PURE__ */ new Map();
261612
+ workspaceBusyReenqueueTimers = /* @__PURE__ */ new Set();
261527
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
+ }
261528
261627
  /**
261529
261628
  * Pending manual merge resolvers — keyed by taskId.
261530
261629
  * When `onMerge` is called, the task is enqueued like auto-merge but a
@@ -262059,6 +262158,10 @@ ${detail}`);
262059
262158
  clearInterval(this.mergeActiveReconcileTimer);
262060
262159
  this.mergeActiveReconcileTimer = null;
262061
262160
  }
262161
+ for (const timer2 of this.workspaceBusyReenqueueTimers) {
262162
+ clearTimeout(timer2);
262163
+ }
262164
+ this.workspaceBusyReenqueueTimers.clear();
262062
262165
  for (const [taskId, deferred] of this.capacityDeferredMerges) {
262063
262166
  clearTimeout(deferred.timer);
262064
262167
  for (const resolver of deferred.resolvers) {
@@ -263358,16 +263461,15 @@ ${detail}`);
263358
263461
  * older low-priority task would start before a later urgent one.
263359
263462
  */
263360
263463
  async allowInReviewMergeProcessing(task, settings, store = this.runtime.getTaskStore()) {
263361
- if (allowsAutoMergeProcessing(task, settings)) {
263362
- return true;
263363
- }
263464
+ if (hasUserAutoMergeHold(task)) return false;
263364
263465
  const groupId = task.branchContext?.groupId?.trim();
263365
263466
  const branchGroup = groupId ? await store.getBranchGroup?.(groupId) : null;
263366
- if (!branchGroup || branchGroup.status !== "open" || !branchGroup.branchName.trim()) {
263367
- return false;
263368
- }
263369
263467
  const projectDefaultBranch = await resolveIntegrationBranch(this.config.workingDirectory, settings);
263370
- 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);
263371
263473
  }
263372
263474
  async emitLegacyAutoMergeStampAdvisory(store) {
263373
263475
  if (this.legacyAutoMergeStampAdvisoryEmitted) {
@@ -264320,9 +264422,7 @@ ${detail}`);
264320
264422
  runtimeLog.log(
264321
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)`
264322
264424
  );
264323
- setTimeout(() => {
264324
- if (!this.shuttingDown) this.internalEnqueueMerge(taskId);
264325
- }, delayMs);
264425
+ this.scheduleWorkspaceBusyReenqueue(taskId, delayMs);
264326
264426
  } else {
264327
264427
  this.workspaceBusyReenqueues.delete(taskId);
264328
264428
  await store.updateTask(taskId, { status: "failed", error: errorMsg }).catch(() => void 0);
@@ -267814,7 +267914,7 @@ var init_task_lifecycle_lanes = __esm({
267814
267914
  });
267815
267915
 
267816
267916
  // ../dashboard/src/ai-session-diagnostics.ts
267817
- import { randomUUID as randomUUID44 } from "node:crypto";
267917
+ import { randomUUID as randomUUID45 } from "node:crypto";
267818
267918
  function defaultSink(level, scope, message, context) {
267819
267919
  const log41 = createLogger(scope);
267820
267920
  try {
@@ -267857,7 +267957,7 @@ function emit(level, scope, message, context) {
267857
267957
  const fullContext = {
267858
267958
  ...context,
267859
267959
  _emittedAt: (/* @__PURE__ */ new Date()).toISOString(),
267860
- _diagnosticsId: randomUUID44()
267960
+ _diagnosticsId: randomUUID45()
267861
267961
  };
267862
267962
  try {
267863
267963
  _sink(level, scope, message, fullContext);
@@ -268573,7 +268673,7 @@ var init_planning_board_tools = __esm({
268573
268673
  });
268574
268674
 
268575
268675
  // ../dashboard/src/planning.ts
268576
- import { randomUUID as randomUUID45 } from "node:crypto";
268676
+ import { randomUUID as randomUUID46 } from "node:crypto";
268577
268677
  import { EventEmitter as EventEmitter34 } from "node:events";
268578
268678
  async function createPlanningRuntimeSession(options) {
268579
268679
  const { pluginRunner, runtimeHint, settings, ...runtimeOptions } = options ?? {};
@@ -268956,7 +269056,7 @@ async function createSession(ip, initialPlan, store, rootDir, promptOverrides, p
268956
269056
  if (!store) {
268957
269057
  throw new Error("store is required for AI-powered planning sessions");
268958
269058
  }
268959
- const sessionId = randomUUID45();
269059
+ const sessionId = randomUUID46();
268960
269060
  const session = {
268961
269061
  id: sessionId,
268962
269062
  ip,
@@ -269526,7 +269626,7 @@ function normalizePlanningQuestion(input, userInput = "") {
269526
269626
  const type = source.type === "multi_select" || source.type === "single_select" ? source.type : "single_select";
269527
269627
  const question = typeof source.question === "string" && source.question.trim() ? source.question.trim() : fallback.question;
269528
269628
  const normalized = {
269529
- id: typeof source.id === "string" && source.id.trim() ? source.id : randomUUID45(),
269629
+ id: typeof source.id === "string" && source.id.trim() ? source.id : randomUUID46(),
269530
269630
  type,
269531
269631
  question,
269532
269632
  ...typeof source.description === "string" && source.description.trim() ? { description: source.description.trim() } : {}
@@ -269552,7 +269652,7 @@ function normalizePlanningQuestion(input, userInput = "") {
269552
269652
  }
269553
269653
  function coerceQuestionResponse(response, session) {
269554
269654
  if (response.type === "question") return normalizePlanningQuestion(response.data, session.initialPlan);
269555
- 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);
269556
269656
  }
269557
269657
  async function validateSession(sessionId) {
269558
269658
  const session = await getSession(sessionId);
@@ -270300,7 +270400,7 @@ async function createTaskFromPlanSession(sessionId, store, options) {
270300
270400
  throw new InvalidSessionStateError("PLANNING_CREATED_TASK_MISSING");
270301
270401
  }
270302
270402
  }
270303
- const claimOwnerToken = randomUUID45();
270403
+ const claimOwnerToken = randomUUID46();
270304
270404
  let claimed = await claimPlanningTaskCreation(sessionId, claimOwnerToken, (/* @__PURE__ */ new Date()).toISOString(), claimEpoch);
270305
270405
  if (!claimed) {
270306
270406
  session = await getDurablePlanningSession(sessionId).catch(() => void 0) ?? session;
@@ -282888,7 +282988,7 @@ var init_register_gitlab = __esm({
282888
282988
 
282889
282989
  // ../dashboard/src/terminal.ts
282890
282990
  import { spawn as spawn11 } from "node:child_process";
282891
- import { randomUUID as randomUUID46 } from "node:crypto";
282991
+ import { randomUUID as randomUUID47 } from "node:crypto";
282892
282992
  import { EventEmitter as EventEmitter41 } from "node:events";
282893
282993
  function extractBaseCommand(command) {
282894
282994
  let trimmed = command.trim();
@@ -283048,7 +283148,7 @@ var init_terminal = __esm({
283048
283148
  if (!validation.valid) {
283049
283149
  return { sessionId: "", error: validation.error };
283050
283150
  }
283051
- const sessionId = randomUUID46();
283151
+ const sessionId = randomUUID47();
283052
283152
  const childProcess5 = spawn11(command, [], {
283053
283153
  cwd,
283054
283154
  shell: true,
@@ -284520,7 +284620,7 @@ var init_src8 = __esm({
284520
284620
 
284521
284621
  // ../../plugins/fusion-plugin-openclaw-runtime/src/pi-module.ts
284522
284622
  import { spawn as spawn13 } from "node:child_process";
284523
- import { randomUUID as randomUUID47 } from "node:crypto";
284623
+ import { randomUUID as randomUUID48 } from "node:crypto";
284524
284624
  import { readFile as readFile44 } from "node:fs/promises";
284525
284625
  function asString2(v2) {
284526
284626
  return typeof v2 === "string" && v2.trim() !== "" ? v2.trim() : void 0;
@@ -284580,7 +284680,7 @@ function extractStderrError(stderr, stdout) {
284580
284680
  }
284581
284681
  function createCliSession(opts) {
284582
284682
  return {
284583
- sessionId: randomUUID47(),
284683
+ sessionId: randomUUID48(),
284584
284684
  agentId: opts.agentId ?? DEFAULT_AGENT_ID,
284585
284685
  systemPrompt: opts.systemPrompt,
284586
284686
  messages: [{ role: "developer", content: opts.systemPrompt }],
@@ -284775,7 +284875,7 @@ var init_mcp_config2 = __esm({
284775
284875
  });
284776
284876
 
284777
284877
  // ../../plugins/fusion-plugin-openclaw-runtime/src/runtime-adapter.ts
284778
- import { randomUUID as randomUUID48 } from "node:crypto";
284878
+ import { randomUUID as randomUUID49 } from "node:crypto";
284779
284879
  var OpenClawRuntimeAdapter;
284780
284880
  var init_runtime_adapter2 = __esm({
284781
284881
  "../../plugins/fusion-plugin-openclaw-runtime/src/runtime-adapter.ts"() {
@@ -284798,8 +284898,8 @@ var init_runtime_adapter2 = __esm({
284798
284898
  let mcpProfile;
284799
284899
  let mcpConfigPath;
284800
284900
  if (toolDefs.length > 0) {
284801
- const bridge = writeOpenClawMcpBridgeFiles(toolDefs, randomUUID48());
284802
- mcpProfile = `fusion-${randomUUID48()}`;
284901
+ const bridge = writeOpenClawMcpBridgeFiles(toolDefs, randomUUID49());
284902
+ mcpProfile = `fusion-${randomUUID49()}`;
284803
284903
  mcpConfigPath = bridge.serverConfigPath;
284804
284904
  await configureOpenClawMcpServer({
284805
284905
  binaryPath: this.config.binaryPath,
@@ -287273,7 +287373,7 @@ import { existsSync as existsSync73, writeFileSync as writeFileSync15 } from "no
287273
287373
  import { tmpdir as tmpdir16 } from "node:os";
287274
287374
  import { dirname as dirname41, join as join93 } from "node:path";
287275
287375
  import { fileURLToPath as fileURLToPath9 } from "node:url";
287276
- import { randomUUID as randomUUID49 } from "node:crypto";
287376
+ import { randomUUID as randomUUID50 } from "node:crypto";
287277
287377
  function toolsToMcpToolDefs2(tools) {
287278
287378
  if (!Array.isArray(tools)) return [];
287279
287379
  return tools.filter((tool) => tool && typeof tool.name === "string" && tool.name.trim().length > 0 && !BUILT_IN_TOOL_NAMES2.has(tool.name)).map((tool) => ({
@@ -287324,7 +287424,7 @@ async function startFusionToolBridge(tools) {
287324
287424
  byName.set(tool.name, tool);
287325
287425
  }
287326
287426
  }
287327
- 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`);
287328
287428
  writeFileSync15(schemaPath, JSON.stringify(defs));
287329
287429
  const server = createServer2(async (req, res) => {
287330
287430
  if (req.method !== "POST" || req.url !== "/tool-call") {
@@ -287350,7 +287450,7 @@ async function startFusionToolBridge(tools) {
287350
287450
  return;
287351
287451
  }
287352
287452
  try {
287353
- 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);
287354
287454
  res.statusCode = 200;
287355
287455
  res.setHeader("content-type", "application/json");
287356
287456
  res.end(
@@ -289394,7 +289494,7 @@ import { existsSync as existsSync77, rmSync as rmSync13, writeFileSync as writeF
289394
289494
  import { tmpdir as tmpdir18 } from "node:os";
289395
289495
  import { dirname as dirname46, join as join98 } from "node:path";
289396
289496
  import { fileURLToPath as fileURLToPath13 } from "node:url";
289397
- import { randomBytes as randomBytes7, randomUUID as randomUUID50 } from "node:crypto";
289497
+ import { randomBytes as randomBytes7, randomUUID as randomUUID51 } from "node:crypto";
289398
289498
  function toolsToMcpToolDefs3(tools) {
289399
289499
  if (!Array.isArray(tools)) return [];
289400
289500
  return tools.filter((tool) => tool && typeof tool.name === "string" && tool.name.trim().length > 0 && !BUILT_IN_TOOL_NAMES3.has(tool.name)).map((tool) => ({
@@ -289445,7 +289545,7 @@ async function startFusionToolBridge2(tools, options = {}) {
289445
289545
  byName.set(tool.name, tool);
289446
289546
  }
289447
289547
  }
289448
- 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`);
289449
289549
  writeFileSync17(schemaPath, JSON.stringify(defs));
289450
289550
  const capabilityToken = randomBytes7(32).toString("base64url");
289451
289551
  const server = createServer3(async (req, res) => {
@@ -289510,7 +289610,7 @@ async function startFusionToolBridge2(tools, options = {}) {
289510
289610
  return;
289511
289611
  }
289512
289612
  try {
289513
- 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);
289514
289614
  res.statusCode = 200;
289515
289615
  res.setHeader("content-type", "application/json");
289516
289616
  res.end(
@@ -291587,7 +291687,7 @@ import { existsSync as existsSync79, unlinkSync as unlinkSync5, writeFileSync as
291587
291687
  import { tmpdir as tmpdir19 } from "node:os";
291588
291688
  import { dirname as dirname49, join as join101 } from "node:path";
291589
291689
  import { fileURLToPath as fileURLToPath15 } from "node:url";
291590
- import { randomUUID as randomUUID51 } from "node:crypto";
291690
+ import { randomUUID as randomUUID52 } from "node:crypto";
291591
291691
  function toolsToMcpToolDefs4(tools) {
291592
291692
  if (!Array.isArray(tools)) return [];
291593
291693
  return tools.filter(
@@ -291644,7 +291744,7 @@ async function startFusionToolBridge3(tools) {
291644
291744
  byName.set(tool.name, tool);
291645
291745
  }
291646
291746
  }
291647
- 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`);
291648
291748
  writeFileSync18(schemaPath, JSON.stringify(defs));
291649
291749
  const server = createServer4(async (req, res) => {
291650
291750
  if (req.method !== "POST" || req.url !== "/tool-call") {
@@ -291671,7 +291771,7 @@ async function startFusionToolBridge3(tools) {
291671
291771
  }
291672
291772
  try {
291673
291773
  const result2 = await tool.execute(
291674
- `omp-mcp-${randomUUID51()}`,
291774
+ `omp-mcp-${randomUUID52()}`,
291675
291775
  parsed.arguments ?? {},
291676
291776
  void 0,
291677
291777
  void 0,
@@ -292564,7 +292664,7 @@ var init_paperclip_client = __esm({
292564
292664
  });
292565
292665
 
292566
292666
  // ../../plugins/fusion-plugin-paperclip-runtime/src/runtime-adapter.ts
292567
- import { randomUUID as randomUUID52 } from "node:crypto";
292667
+ import { randomUUID as randomUUID53 } from "node:crypto";
292568
292668
  function sleep7(ms2) {
292569
292669
  return new Promise((resolve54) => setTimeout(resolve54, ms2));
292570
292670
  }
@@ -292693,7 +292793,7 @@ var init_runtime_adapter10 = __esm({
292693
292793
  apiKey: effectiveApiKey,
292694
292794
  agentId,
292695
292795
  companyId,
292696
- sessionId: randomUUID52(),
292796
+ sessionId: randomUUID53(),
292697
292797
  systemPrompt: options.systemPrompt,
292698
292798
  cwd: options.cwd,
292699
292799
  mode: normalizeMode3(this.config.mode),
@@ -293371,7 +293471,7 @@ var init_types5 = __esm({
293371
293471
  });
293372
293472
 
293373
293473
  // ../dashboard/src/stt/model-manager.ts
293374
- import { createHash as createHash28, randomUUID as randomUUID53 } from "node:crypto";
293474
+ import { createHash as createHash28, randomUUID as randomUUID54 } from "node:crypto";
293375
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";
293376
293476
  import { join as join102, relative as relative19, resolve as resolve51, sep as sep15 } from "node:path";
293377
293477
  function parseSafeTarListing(listing) {
@@ -293452,8 +293552,8 @@ function createVoiceModelManager(options = {}) {
293452
293552
  if (!current(g2, signal)) return state;
293453
293553
  await mkdir31(cacheDir(), { recursive: true });
293454
293554
  if (!current(g2, signal)) return state;
293455
- partial2 = join102(cacheDir(), `.partial-${g2}-${randomUUID53()}`);
293456
- staging = join102(cacheDir(), `.staging-${g2}-${randomUUID53()}`);
293555
+ partial2 = join102(cacheDir(), `.partial-${g2}-${randomUUID54()}`);
293556
+ staging = join102(cacheDir(), `.staging-${g2}-${randomUUID54()}`);
293457
293557
  const file = await open4(partial2, "wx");
293458
293558
  try {
293459
293559
  const response = await (options.fetch ?? globalThis.fetch)(asset.url, { signal });
@@ -293501,7 +293601,7 @@ function createVoiceModelManager(options = {}) {
293501
293601
  await writeFile33(join102(staging, "manifest.json"), JSON.stringify({ filename: asset.filename, sha256: asset.sha256, installedAt: (/* @__PURE__ */ new Date()).toISOString(), expectedFiles: asset.expectedFiles }));
293502
293602
  await options.beforePromote?.(signal);
293503
293603
  if (!current(g2, signal)) return state;
293504
- const backup = join102(cacheDir(), `.backup-${g2}-${randomUUID53()}`);
293604
+ const backup = join102(cacheDir(), `.backup-${g2}-${randomUUID54()}`);
293505
293605
  let movedPrevious = false;
293506
293606
  try {
293507
293607
  try {
@@ -293722,7 +293822,7 @@ var init_parakeet_service = __esm({
293722
293822
  });
293723
293823
 
293724
293824
  // ../dashboard/src/routes/register-voice-routes.ts
293725
- import { randomUUID as randomUUID54 } from "node:crypto";
293825
+ import { randomUUID as randomUUID55 } from "node:crypto";
293726
293826
  import express from "express";
293727
293827
  async function settingsFor(ctx, req) {
293728
293828
  const scoped = await ctx.getScopedStore(req);
@@ -293801,7 +293901,7 @@ function createRegisterVoiceRoutes(deps = {}) {
293801
293901
  recognizer.close();
293802
293902
  return error2(res, 409, { error: "unavailable", reason: "model-removed" });
293803
293903
  }
293804
- const id = randomUUID54();
293904
+ const id = randomUUID55();
293805
293905
  const now = Date.now();
293806
293906
  sessions6.set(id, { projectId, recognizer, next: 0, bytes: 0, last: now, expires: now + 3e5 });
293807
293907
  res.status(201).json({ sessionId: id, expiresAt: new Date(now + 3e5).toISOString(), modelId: model.id, language: language.language });
@@ -297127,14 +297227,14 @@ init_src2();
297127
297227
  init_gh_cli();
297128
297228
  init_src5();
297129
297229
  init_src15();
297130
- import { randomUUID as randomUUID55 } from "node:crypto";
297230
+ import { randomUUID as randomUUID56 } from "node:crypto";
297131
297231
  import { Type as Type9 } from "typebox";
297132
297232
  import { StringEnum } from "@earendil-works/pi-ai";
297133
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";
297134
297234
  import { readFile as readFile47 } from "node:fs/promises";
297135
297235
  import { existsSync as existsSync82 } from "node:fs";
297136
297236
  import { spawn as spawn23 } from "node:child_process";
297137
- import { AsyncLocalStorage } from "node:async_hooks";
297237
+ import { AsyncLocalStorage as AsyncLocalStorage2 } from "node:async_hooks";
297138
297238
  function truncateAgentDiagnosticText(value, maxChars) {
297139
297239
  return value.length > maxChars ? `${value.slice(0, maxChars)}\u2026` : value;
297140
297240
  }
@@ -297239,7 +297339,7 @@ var storeCache2 = extensionStoreState.cache;
297239
297339
  var storeBootInflight = extensionStoreState.bootInflight;
297240
297340
  var storeBootFailureCooldown = extensionStoreState.bootFailureCooldown;
297241
297341
  var BOOT_FAILURE_COOLDOWN_MS = 5e3;
297242
- var extensionToolSignal = new AsyncLocalStorage();
297342
+ var extensionToolSignal = new AsyncLocalStorage2();
297243
297343
  var EXTENSION_STORE_BOOT_TIMEOUT_MS = 3e4;
297244
297344
  var EXTENSION_TOOL_TIMEOUT_MS = 6e4;
297245
297345
  var SKILLS_INSTALL_TIMEOUT_MS = 3e5;
@@ -297541,6 +297641,28 @@ async function isEphemeralCallerAgent(cwd, callerAgentId) {
297541
297641
  }
297542
297642
  }
297543
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
+ }
297544
297666
  function resolveExtensionCallerPrincipal(ctx) {
297545
297667
  const explicitAgentId = typeof ctx.agentId === "string" && ctx.agentId.trim().length > 0 ? ctx.agentId.trim() : void 0;
297546
297668
  if (explicitAgentId) {
@@ -298100,7 +298222,7 @@ function kbExtension(pi2) {
298100
298222
  const layer = store.getAsyncLayer();
298101
298223
  const messageStore = layer ? new MessageStore(null, { asyncLayer: layer }) : new MessageStore(store.getDatabase());
298102
298224
  const title = params.description.split(/\r?\n/, 1)[0]?.trim().slice(0, 80) || "Follow-up task";
298103
- 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 } } });
298104
298226
  return { content: [{ type: "text", text: "Task proposal submitted to the operator for validation; no task was created." }], details: { proposed: true } };
298105
298227
  }
298106
298228
  }
@@ -299350,6 +299472,20 @@ Planning session completed. Task ${taskId} is now in planning and will be auto-p
299350
299472
  }),
299351
299473
  async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
299352
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
+ };
299353
299489
  const store = await getStore(ctx.cwd);
299354
299490
  const secretsStore = await store.getSecretsStore();
299355
299491
  const scopes = params.scope ? [params.scope] : ["project", "global"];
@@ -299372,36 +299508,32 @@ Planning session completed. Task ${taskId} is now in planning and will be auto-p
299372
299508
  settings: { secretsAccessPolicy: globalSettings.secretsAccessPolicy }
299373
299509
  });
299374
299510
  if (decision.policy === "deny") {
299375
- emitSecretAudit(store, fnCtx, "secret:approval-denied", `${resolvedScope}:${params.key}`);
299511
+ emitSecretAudit(store, effectiveCtx, "secret:approval-denied", `${resolvedScope}:${params.key}`);
299376
299512
  return { content: [{ type: "text", text: "Secret access denied by policy." }], details: { error: "denied", key: params.key, scope: resolvedScope, policySource: decision.source } };
299377
299513
  }
299378
299514
  if (decision.policy === "prompt") {
299379
299515
  const cliLayer = requireProjectLayer(store, "CLI secret approval store");
299380
299516
  const approvalStore = new ApprovalRequestStore(null, { asyncLayer: cliLayer });
299381
- const dedupeKey = `secret-read:${resolvedScope}:${params.key}:${fnCtx.agentId ?? "unknown"}`;
299382
- const requesterActorId = fnCtx.agentId ?? "user";
299383
- const requesterSnapshot = fnCtx.agentId ? {
299384
- actorId: requesterActorId,
299385
- actorType: "agent",
299386
- actorName: fnCtx.agentName ?? fnCtx.agentId
299387
- } : { actorId: "user", actorType: "user", actorName: "CLI User" };
299388
- 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 });
299389
299521
  if (existing?.status === "pending") {
299390
- emitSecretAudit(store, fnCtx, "secret:approval-requested", `${resolvedScope}:${params.key}`);
299522
+ emitSecretAudit(store, effectiveCtx, "secret:approval-requested", `${resolvedScope}:${params.key}`);
299391
299523
  return {
299392
299524
  content: [{ type: "text", text: `Secret access approval request ${existing.id} is still pending. Approve via POST /api/approvals/:id/decision.` }],
299393
299525
  details: { outcome: "pending_approval", approvalRequestId: existing.id, key: params.key, scope: resolvedScope }
299394
299526
  };
299395
299527
  }
299396
299528
  if (existing?.status === "denied") {
299397
- emitSecretAudit(store, fnCtx, "secret:approval-denied", `${resolvedScope}:${params.key}`);
299529
+ emitSecretAudit(store, effectiveCtx, "secret:approval-denied", `${resolvedScope}:${params.key}`);
299398
299530
  return {
299399
299531
  content: [{ type: "text", text: `Secret access request ${existing.id} was denied by the operator. Do not retry without operator direction.` }],
299400
299532
  details: { outcome: "denied", approvalRequestId: existing.id, key: params.key, scope: resolvedScope }
299401
299533
  };
299402
299534
  }
299403
299535
  if (existing?.status === "approved") {
299404
- const revealedAfterApproval = await secretsStore.revealSecret(record2.id, resolvedScope, { agentId: fnCtx.agentId ?? null });
299536
+ const revealedAfterApproval = await secretsStore.revealSecret(record2.id, resolvedScope, { agentId: secretPrincipal.agentId });
299405
299537
  await approvalStore.markCompleted(existing.id, {
299406
299538
  actor: requesterSnapshot,
299407
299539
  note: "Secret revealed after approval",
@@ -299409,7 +299541,7 @@ Planning session completed. Task ${taskId} is now in planning and will be auto-p
299409
299541
  // get the same expectedRequesterActorId enforcement as the gate path.
299410
299542
  expectedRequesterActorId: requesterActorId
299411
299543
  });
299412
- 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 });
299413
299545
  return {
299414
299546
  content: [{ type: "text", text: `Loaded secret '${params.key}' from ${resolvedScope} scope (approval ${existing.id} consumed).` }],
299415
299547
  details: { key: params.key, value: revealedAfterApproval.plaintextValue, scope: resolvedScope, approvalRequestId: existing.id }
@@ -299425,17 +299557,17 @@ Planning session completed. Task ${taskId} is now in planning and will be auto-p
299425
299557
  resourceId: record2.id,
299426
299558
  context: { approvalDedupeKey: dedupeKey, key: params.key, scope: resolvedScope }
299427
299559
  },
299428
- ...fnCtx.runId ? { runId: fnCtx.runId } : {},
299429
- ...fnCtx.taskId ? { taskId: fnCtx.taskId } : {}
299560
+ ...effectiveCtx.runId ? { runId: effectiveCtx.runId } : {},
299561
+ ...effectiveCtx.taskId ? { taskId: effectiveCtx.taskId } : {}
299430
299562
  });
299431
- emitSecretAudit(store, fnCtx, "secret:approval-requested", `${resolvedScope}:${params.key}`);
299563
+ emitSecretAudit(store, effectiveCtx, "secret:approval-requested", `${resolvedScope}:${params.key}`);
299432
299564
  return {
299433
299565
  content: [{ type: "text", text: `Secret access requires approval. Request ${request2.id} is pending. Approve via POST /api/approvals/:id/decision.` }],
299434
299566
  details: { outcome: "pending_approval", approvalRequestId: request2.id, key: params.key, scope: resolvedScope }
299435
299567
  };
299436
299568
  }
299437
- const revealed = await secretsStore.revealSecret(record2.id, resolvedScope, { agentId: fnCtx.agentId ?? null });
299438
- 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 });
299439
299571
  return {
299440
299572
  content: [{ type: "text", text: `Loaded secret '${params.key}' from ${resolvedScope} scope.` }],
299441
299573
  details: { key: params.key, value: revealed.plaintextValue, scope: resolvedScope }