@runfusion/fusion 0.77.0-beta.1 → 0.77.0-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 (137) hide show
  1. package/dist/bin.js +298 -62
  2. package/dist/child-process-worker.js +287 -57
  3. package/dist/client/.vite/manifest.json +262 -262
  4. package/dist/client/assets/{AgentDetailView-Crk9OdF-.js → AgentDetailView-Dew3af1O.js} +1 -1
  5. package/dist/client/assets/{AgentPermissionPolicyEditor-BHQ9RPKE.js → AgentPermissionPolicyEditor-CxPQffiY.js} +1 -1
  6. package/dist/client/assets/{AgentsView-BWOl5J6F.js → AgentsView-Cr4LDGdt.js} +3 -3
  7. package/dist/client/assets/{ChatView-BP8-Are_.js → ChatView-cNYtYx05.js} +1 -1
  8. package/dist/client/assets/{CommandCenter-C1adapJC.js → CommandCenter-DCC90LOP.js} +1 -1
  9. package/dist/client/assets/{DevServerView-B2wexHCU.js → DevServerView-B5b520w6.js} +1 -1
  10. package/dist/client/assets/{DirectoryPicker-C7rzLVyD.js → DirectoryPicker-1JvRd5mC.js} +1 -1
  11. package/dist/client/assets/{DocumentsView-hOjlyCfz.js → DocumentsView-ksK9f7xt.js} +1 -1
  12. package/dist/client/assets/{EvalsView-CdAB_4D_.js → EvalsView-a3LhgRSo.js} +1 -1
  13. package/dist/client/assets/{ExperimentalAgentOnboardingModal-GEypAOXx.js → ExperimentalAgentOnboardingModal-vdVa8h7F.js} +1 -1
  14. package/dist/client/assets/{GoalsView-DW1qRxeP.js → GoalsView-Dz2o91fd.js} +1 -1
  15. package/dist/client/assets/{InsightsView-B5roc0nI.js → InsightsView-De3R_46d.js} +1 -1
  16. package/dist/client/assets/{MemoryView-B0mSfU8f.js → MemoryView-Cagk2wZG.js} +1 -1
  17. package/dist/client/assets/{PiExtensionsManager-ps7tZzok.js → PiExtensionsManager-C9JABO_4.js} +1 -1
  18. package/dist/client/assets/{PluginManager-BrsdP_Zs.js → PluginManager-D2csmdQa.js} +1 -1
  19. package/dist/client/assets/{PullRequestView-CkLoEm3J.js → PullRequestView-DT8vTeVi.js} +1 -1
  20. package/dist/client/assets/{ReportModal-BKdSSVii.js → ReportModal-YMP6V4vu.js} +1 -1
  21. package/dist/client/assets/{ResearchView-OZW77xd4.js → ResearchView-cNsjYu8F.js} +1 -1
  22. package/dist/client/assets/{SecretsView-CawXNPvr.js → SecretsView-BAmf7KyS.js} +1 -1
  23. package/dist/client/assets/{SessionTerminal-C26iOW6f.js → SessionTerminal-BjGcxq22.js} +1 -1
  24. package/dist/client/assets/{SettingsModal-BNWlULmc.js → SettingsModal-C_9RiEI7.js} +1 -1
  25. package/dist/client/assets/{SettingsModal-CfXTMT99.js → SettingsModal-DLfy2e_5.js} +4 -4
  26. package/dist/client/assets/{SettingsTextareaRow-DrXj-rTG.js → SettingsTextareaRow-D8RHSj8b.js} +1 -1
  27. package/dist/client/assets/{SetupWizardModal-Dk4cldse.js → SetupWizardModal-DRJ96pHQ.js} +2 -2
  28. package/dist/client/assets/{SkillsView-NBEe0Qjt.js → SkillsView-DyeCwYit.js} +1 -1
  29. package/dist/client/assets/{WorkflowNodeEditor-CZX7Mlum.js → WorkflowNodeEditor-C5Jc9w_A.js} +3 -3
  30. package/dist/client/assets/{agent-import-generation-TK-wCZBK.js → agent-import-generation-SCPNeF25.js} +1 -1
  31. package/dist/client/assets/{agentsOrgChartLayout-BFC39-tw.js → agentsOrgChartLayout-CqKtNCjy.js} +1 -1
  32. package/dist/client/assets/{architectureDiagram-3BPJPVTR-CYHuEL_5.js → architectureDiagram-3BPJPVTR-Cz2HYn0b.js} +1 -1
  33. package/dist/client/assets/{blockDiagram-GPEHLZMM-BhFoNt44.js → blockDiagram-GPEHLZMM-IimJVscG.js} +1 -1
  34. package/dist/client/assets/{c4Diagram-AAUBKEIU-CQorfIRn.js → c4Diagram-AAUBKEIU-C-xii7zn.js} +1 -1
  35. package/dist/client/assets/channel-D-UHgx8G.js +1 -0
  36. package/dist/client/assets/{chunk-2J33WTMH-DLyODy_m.js → chunk-2J33WTMH-DQ-9Xccu.js} +1 -1
  37. package/dist/client/assets/{chunk-4BX2VUAB-C4ZfqjSh.js → chunk-4BX2VUAB-DmdAX9Bx.js} +1 -1
  38. package/dist/client/assets/{chunk-55IACEB6-CvGg-ubc.js → chunk-55IACEB6-IF2bk5IL.js} +1 -1
  39. package/dist/client/assets/{chunk-727SXJPM-jCzUYl5C.js → chunk-727SXJPM-AeYpIzVu.js} +1 -1
  40. package/dist/client/assets/{chunk-AQP2D5EJ-DYLT-tu8.js → chunk-AQP2D5EJ-DV942DD9.js} +1 -1
  41. package/dist/client/assets/{chunk-FMBD7UC4-CCgBBj1Z.js → chunk-FMBD7UC4-Bt_riVlZ.js} +1 -1
  42. package/dist/client/assets/{chunk-ND2GUHAM-BFdFrIja.js → chunk-ND2GUHAM-CbyjNbXR.js} +1 -1
  43. package/dist/client/assets/{chunk-QZHKN3VN-BUTaQ50G.js → chunk-QZHKN3VN-DqiRniYQ.js} +1 -1
  44. package/dist/client/assets/classDiagram-4FO5ZUOK-Do-8MdAc.js +1 -0
  45. package/dist/client/assets/classDiagram-v2-Q7XG4LA2-Do-8MdAc.js +1 -0
  46. package/dist/client/assets/{cose-bilkent-S5V4N54A-DUbNlc_8.js → cose-bilkent-S5V4N54A-UCvthviL.js} +1 -1
  47. package/dist/client/assets/{dagre-BM42HDAG-Db_33UO6.js → dagre-BM42HDAG-DnMlT1Oi.js} +1 -1
  48. package/dist/client/assets/{dashboard-view-C3PU63FX.js → dashboard-view-C9D0aoRW.js} +1 -1
  49. package/dist/client/assets/dashboard-view-Cz0sp2Wl.js +1 -0
  50. package/dist/client/assets/{dashboard-view-CblIAx4p.js → dashboard-view-JysoyDRX.js} +1 -1
  51. package/dist/client/assets/{dashboard-view-ZGZ80pF3.js → dashboard-view-myjM9tr8.js} +1 -1
  52. package/dist/client/assets/{diagram-2AECGRRQ-BCTLM-Gj.js → diagram-2AECGRRQ-BYFiqXGw.js} +1 -1
  53. package/dist/client/assets/{diagram-5GNKFQAL-CkYPWqJ0.js → diagram-5GNKFQAL-DZU3pSYq.js} +1 -1
  54. package/dist/client/assets/{diagram-KO2AKTUF-7_DJ9TOL.js → diagram-KO2AKTUF-0ybdKuMm.js} +1 -1
  55. package/dist/client/assets/{diagram-LMA3HP47-BAkMEapl.js → diagram-LMA3HP47-Rxbgnbsp.js} +1 -1
  56. package/dist/client/assets/{diagram-OG6HWLK6-DbsQHZ0F.js → diagram-OG6HWLK6-BAU_hTeV.js} +1 -1
  57. package/dist/client/assets/{erDiagram-TEJ5UH35-Bz1kISgV.js → erDiagram-TEJ5UH35-zr8HrwTt.js} +1 -1
  58. package/dist/client/assets/{flowDiagram-I6XJVG4X-fkBLx_JW.js → flowDiagram-I6XJVG4X-CrTw0iFw.js} +1 -1
  59. package/dist/client/assets/{folder-open-l8Z1o786.js → folder-open-DnAlsXXn.js} +1 -1
  60. package/dist/client/assets/{ganttDiagram-6RSMTGT7-BEDb0Uyw.js → ganttDiagram-6RSMTGT7-CCORsxxz.js} +1 -1
  61. package/dist/client/assets/{gitGraphDiagram-PVQCEYII-Cen5lgL7.js → gitGraphDiagram-PVQCEYII-C07wWSjj.js} +1 -1
  62. package/dist/client/assets/index-B_z7OKl4.js +2818 -0
  63. package/dist/client/assets/index-DPo-IAb2.css +1 -0
  64. package/dist/client/assets/{infoDiagram-5YYISTIA-BHqjYX5-.js → infoDiagram-5YYISTIA-hAeccD_4.js} +1 -1
  65. package/dist/client/assets/{ishikawaDiagram-YF4QCWOH-6JaCT-0H.js → ishikawaDiagram-YF4QCWOH-WLCzv-NH.js} +1 -1
  66. package/dist/client/assets/{journeyDiagram-JHISSGLW-C_sUwyWW.js → journeyDiagram-JHISSGLW-ClnasNhy.js} +1 -1
  67. package/dist/client/assets/{kanban-definition-UN3LZRKU-__o8oDEP.js → kanban-definition-UN3LZRKU-CAuARhUz.js} +1 -1
  68. package/dist/client/assets/{mermaid.core-BwF2BF3U.js → mermaid.core-Bh02RvfX.js} +4 -4
  69. package/dist/client/assets/{mindmap-definition-RKZ34NQL-DMhvCrGK.js → mindmap-definition-RKZ34NQL-KNTlbW4V.js} +1 -1
  70. package/dist/client/assets/{pieDiagram-4H26LBE5-Dk0KKcwv.js → pieDiagram-4H26LBE5-BouAfBEw.js} +1 -1
  71. package/dist/client/assets/{puzzle-DcLi6jmv.js → puzzle-CC8Ur3Cy.js} +1 -1
  72. package/dist/client/assets/{quadrantDiagram-W4KKPZXB-DXcBYGil.js → quadrantDiagram-W4KKPZXB-wTdkCUJU.js} +1 -1
  73. package/dist/client/assets/{requirementDiagram-4Y6WPE33--GXgKHm2.js → requirementDiagram-4Y6WPE33-BnjY4Tc_.js} +1 -1
  74. package/dist/client/assets/{sankeyDiagram-5OEKKPKP-fX5WAFdr.js → sankeyDiagram-5OEKKPKP-C-QsS08u.js} +1 -1
  75. package/dist/client/assets/{sequenceDiagram-3UESZ5HK-VQOx2wc2.js → sequenceDiagram-3UESZ5HK-DD36MILE.js} +1 -1
  76. package/dist/client/assets/{shield-alert-DLW2jAvI.js → shield-alert-DcXc56Fy.js} +1 -1
  77. package/dist/client/assets/{standing-instructions-template-RbXYE69q.js → standing-instructions-template-BF5y1BLz.js} +1 -1
  78. package/dist/client/assets/{stateDiagram-AJRCARHV-COZ_NGxp.js → stateDiagram-AJRCARHV-WTq1wQr1.js} +1 -1
  79. package/dist/client/assets/{stateDiagram-v2-BHNVJYJU-CmgPEZwe.js → stateDiagram-v2-BHNVJYJU-eILJdjII.js} +1 -1
  80. package/dist/client/assets/{timeline-definition-PNZ67QCA-eJYKjHOn.js → timeline-definition-PNZ67QCA-PRNwI0NR.js} +1 -1
  81. package/dist/client/assets/{upload-DwRVPojw.js → upload-qH_64O-B.js} +1 -1
  82. package/dist/client/assets/{useDiscoveredSkillsCache-DhScYUmX.js → useDiscoveredSkillsCache-C-GPCNaO.js} +1 -1
  83. package/dist/client/assets/{users-BrTlKeXP.js → users-D2UesRE4.js} +1 -1
  84. package/dist/client/assets/{vennDiagram-CIIHVFJN-Ddt59BdZ.js → vennDiagram-CIIHVFJN-CG2Z4T4O.js} +1 -1
  85. package/dist/client/assets/{wardley-L42UT6IY-C4R0wOO2.js → wardley-L42UT6IY-gO_fGvF9.js} +1 -1
  86. package/dist/client/assets/{wardleyDiagram-YWT4CUSO-BJDm4S7I.js → wardleyDiagram-YWT4CUSO-BkyNpjUb.js} +1 -1
  87. package/dist/client/assets/{xychartDiagram-2RQKCTM6-DlypQnRH.js → xychartDiagram-2RQKCTM6-CMlZsiPq.js} +1 -1
  88. package/dist/client/index.html +2 -2
  89. package/dist/client/version.json +1 -1
  90. package/dist/droid-cli/package.json +1 -1
  91. package/dist/extension.js +290 -59
  92. package/dist/pi-claude-cli/package.json +1 -1
  93. package/dist/plugin-sdk/index.js +29 -2
  94. package/dist/plugins/fusion-plugin-acp-runtime/bundled.js +269 -16
  95. package/dist/plugins/fusion-plugin-acp-runtime/mcp-schema-server.cjs +171 -0
  96. package/dist/plugins/fusion-plugin-acp-runtime/package.json +1 -1
  97. package/dist/plugins/fusion-plugin-claude-runtime/bundled.js +29 -2
  98. package/dist/plugins/fusion-plugin-claude-runtime/package.json +1 -1
  99. package/dist/plugins/fusion-plugin-cli-printing-press/bundled.js +29 -2
  100. package/dist/plugins/fusion-plugin-cli-printing-press/package.json +1 -1
  101. package/dist/plugins/fusion-plugin-compound-engineering/bundled.js +29 -2
  102. package/dist/plugins/fusion-plugin-compound-engineering/package.json +1 -1
  103. package/dist/plugins/fusion-plugin-cursor-runtime/bundled.js +29 -2
  104. package/dist/plugins/fusion-plugin-cursor-runtime/package.json +1 -1
  105. package/dist/plugins/fusion-plugin-dependency-graph/bundled.js +29 -2
  106. package/dist/plugins/fusion-plugin-dependency-graph/package.json +1 -1
  107. package/dist/plugins/fusion-plugin-droid-runtime/bundled.js +29 -2
  108. package/dist/plugins/fusion-plugin-droid-runtime/package.json +1 -1
  109. package/dist/plugins/fusion-plugin-grok-runtime/bundled.js +29 -2
  110. package/dist/plugins/fusion-plugin-grok-runtime/package.json +1 -1
  111. package/dist/plugins/fusion-plugin-hermes-runtime/bundled.js +29 -2
  112. package/dist/plugins/fusion-plugin-hermes-runtime/package.json +1 -1
  113. package/dist/plugins/fusion-plugin-linear-import/bundled.js +29 -2
  114. package/dist/plugins/fusion-plugin-linear-import/package.json +1 -1
  115. package/dist/plugins/fusion-plugin-omp-runtime/bundled.js +29 -2
  116. package/dist/plugins/fusion-plugin-omp-runtime/package.json +1 -1
  117. package/dist/plugins/fusion-plugin-openclaw-runtime/bundled.js +29 -2
  118. package/dist/plugins/fusion-plugin-openclaw-runtime/package.json +1 -1
  119. package/dist/plugins/fusion-plugin-paperclip-runtime/bundled.js +29 -2
  120. package/dist/plugins/fusion-plugin-paperclip-runtime/package.json +1 -1
  121. package/dist/plugins/fusion-plugin-quality/bundled.js +29 -2
  122. package/dist/plugins/fusion-plugin-quality/package.json +1 -1
  123. package/dist/plugins/fusion-plugin-reports/bundled.js +29 -2
  124. package/dist/plugins/fusion-plugin-reports/package.json +1 -1
  125. package/dist/plugins/fusion-plugin-roadmap/bundled.js +29 -2
  126. package/dist/plugins/fusion-plugin-roadmap/package.json +1 -1
  127. package/dist/plugins/fusion-plugin-todos/bundled.js +29 -2
  128. package/dist/plugins/fusion-plugin-todos/package.json +1 -1
  129. package/dist/plugins/fusion-plugin-whatsapp-chat/bundled.js +29 -2
  130. package/dist/plugins/fusion-plugin-whatsapp-chat/package.json +1 -1
  131. package/package.json +1 -1
  132. package/dist/client/assets/channel-DgImfyB5.js +0 -1
  133. package/dist/client/assets/classDiagram-4FO5ZUOK-C3pUNxZS.js +0 -1
  134. package/dist/client/assets/classDiagram-v2-Q7XG4LA2-C3pUNxZS.js +0 -1
  135. package/dist/client/assets/dashboard-view-c3FuWBJb.js +0 -1
  136. package/dist/client/assets/index-BnZB0GkD.js +0 -2818
  137. package/dist/client/assets/index-CPGSlKrd.css +0 -1
package/dist/bin.js CHANGED
@@ -16882,8 +16882,34 @@ function didFallBackToDefault(ir2) {
16882
16882
  async function resolveWorkflowIrForTaskWithProvenance(store, taskId, irCache, selectionCache) {
16883
16883
  let workflowId;
16884
16884
  try {
16885
- const selection = selectionCache?.has(taskId) ? selectionCache.get(taskId) : store.getTaskWorkflowSelectionAsync ? await store.getTaskWorkflowSelectionAsync(taskId) : store.getTaskWorkflowSelection(taskId);
16886
- if (!selectionCache?.has(taskId)) selectionCache?.set(taskId, selection);
16885
+ const isSelectionCached = selectionCache?.has(taskId) ?? false;
16886
+ let selection = isSelectionCached && selectionCache ? selectionCache.get(taskId) : void 0;
16887
+ if (!isSelectionCached) {
16888
+ let inflight;
16889
+ let coalesced = false;
16890
+ if (selectionCache) {
16891
+ inflight = inflightSelectionReads.get(selectionCache);
16892
+ if (!inflight) {
16893
+ inflight = /* @__PURE__ */ new Map();
16894
+ inflightSelectionReads.set(selectionCache, inflight);
16895
+ }
16896
+ const pending = inflight.get(taskId);
16897
+ if (pending) {
16898
+ selection = await pending;
16899
+ coalesced = true;
16900
+ }
16901
+ }
16902
+ if (!coalesced) {
16903
+ const readPromise = store.getTaskWorkflowSelectionAsync ? store.getTaskWorkflowSelectionAsync(taskId) : Promise.resolve(store.getTaskWorkflowSelection(taskId));
16904
+ if (inflight) inflight.set(taskId, readPromise);
16905
+ try {
16906
+ selection = await readPromise;
16907
+ } finally {
16908
+ inflight?.delete(taskId);
16909
+ }
16910
+ }
16911
+ selectionCache?.set(taskId, selection);
16912
+ }
16887
16913
  workflowId = selection?.workflowId;
16888
16914
  } catch {
16889
16915
  return { ir: defaultCodingWorkflowIr(), source: "default" };
@@ -16907,13 +16933,14 @@ async function isTaskTerminalNodeIdAsync(store, taskId, nodeId) {
16907
16933
  }
16908
16934
  return nodeId === "end";
16909
16935
  }
16910
- var FELL_BACK_TO_DEFAULT;
16936
+ var inflightSelectionReads, FELL_BACK_TO_DEFAULT;
16911
16937
  var init_workflow_ir_resolver = __esm({
16912
16938
  "../core/src/workflows/workflow-ir-resolver.ts"() {
16913
16939
  "use strict";
16914
16940
  init_builtin_workflows();
16915
16941
  init_workflow_ir();
16916
16942
  init_workflow_prompt_overrides();
16943
+ inflightSelectionReads = /* @__PURE__ */ new WeakMap();
16917
16944
  FELL_BACK_TO_DEFAULT = /* @__PURE__ */ Symbol.for("fusion.workflowIr.fellBackToDefault");
16918
16945
  }
16919
16946
  });
@@ -36534,9 +36561,12 @@ async function markApprovalRequestCompleted(layer, requestId, input) {
36534
36561
  });
36535
36562
  }
36536
36563
  async function getApprovalAuditHistory(handle2, requestId, projectId2) {
36537
- const rows = await handle2.select().from(project_exports.approvalRequestAuditEvents).where(and(eq(project_exports.approvalRequestAuditEvents.requestId, requestId), projectScopeFor(project_exports.approvalRequestAuditEvents.projectId, projectId2))).orderBy(
36538
- sql`${project_exports.approvalRequestAuditEvents.createdAt} ASC, ${project_exports.approvalRequestAuditEvents.id} ASC`
36539
- );
36564
+ const eventType = project_exports.approvalRequestAuditEvents.eventType;
36565
+ const lifecycleRank = sql`CASE ${eventType} ${sql.join(
36566
+ APPROVAL_REQUEST_AUDIT_EVENT_TYPES.map((type, rank) => sql`WHEN ${type} THEN ${rank}`),
36567
+ sql` `
36568
+ )} ELSE ${APPROVAL_REQUEST_AUDIT_EVENT_TYPES.length} END`;
36569
+ const rows = await handle2.select().from(project_exports.approvalRequestAuditEvents).where(and(eq(project_exports.approvalRequestAuditEvents.requestId, requestId), projectScopeFor(project_exports.approvalRequestAuditEvents.projectId, projectId2))).orderBy(sql`${project_exports.approvalRequestAuditEvents.createdAt} ASC, ${lifecycleRank} ASC, ${project_exports.approvalRequestAuditEvents.id} ASC`);
36540
36570
  return rows.map((row) => rowToAuditEvent(row));
36541
36571
  }
36542
36572
  var init_async_approval_request_store = __esm({
@@ -140804,17 +140834,40 @@ var init_auth_storage = __esm({
140804
140834
  async getApiKey(provider, instance) {
140805
140835
  return resolveStoredCredentialApiKey(provider, instance ? this.getInstance(instance) : this.get(provider));
140806
140836
  }
140837
+ /*
140838
+ FNXC:ProviderAuth 2026-08-18-04:40:
140839
+ MODIFY MUST BE ABLE TO CREATE. This is the seam pi persists a COMPLETED LOGIN through
140840
+ (`Models.login` -> `credentials.modify(provider.id, ...)` in pi-ai's models.js), not just the seam
140841
+ it refreshes an existing token through.
140842
+
140843
+ It used to resolve its write target with `creating: false` and then bail —
140844
+ `if (!target || !this.credential(target, current)) return { changed: false }` — whenever the
140845
+ provider had no credential row yet. The callback was never invoked, nothing was written, and pi's
140846
+ login resolved as a SUCCESS. So a first-ever login for a provider could not be saved: the browser
140847
+ flow completed, the token exchange succeeded, the lock file was taken and released, `auth.json`
140848
+ stayed `{}`, and the dashboard's poll saw `authenticated: false` and reported the useless "Login
140849
+ did not complete. Please try again."
140850
+
140851
+ It looked provider-specific and environment-specific for the worst possible reason: it only
140852
+ reproduces on a store with NO existing row, so every developer and every long-lived install — where
140853
+ a row already exists and this path is a plain refresh — works flawlessly, while every fresh install
140854
+ (a new container, a new machine, a wiped `~/.fusion`) can never complete its first login.
140855
+
140856
+ Create when absent, update when present. A callback returning undefined still writes nothing, so
140857
+ pi's refresh-bails-out behaviour is unchanged.
140858
+ */
140807
140859
  async modify(provider, fn2) {
140808
140860
  this.assertRefFromKey(provider);
140809
140861
  return this.withLock(async (current) => {
140810
- const target = this.resolveWriteTarget(provider, current, false);
140811
- if (!target || !this.credential(target, current)) return { result: void 0, changed: false };
140812
- const next = await fn2(this.credential(target, current));
140862
+ const target = this.resolveWriteTarget(provider, current, true);
140863
+ if (!target) return { result: void 0, changed: false };
140864
+ const existing = this.credential(target, current);
140865
+ const next = await fn2(existing);
140813
140866
  if (next !== void 0) {
140814
140867
  current[formatProviderInstanceKey(target)] = next;
140815
140868
  return { result: next, changed: true };
140816
140869
  }
140817
- return { result: this.credential(target, current), changed: false };
140870
+ return { result: existing, changed: false };
140818
140871
  });
140819
140872
  }
140820
140873
  getOAuthProviders() {
@@ -140827,11 +140880,24 @@ var init_auth_storage = __esm({
140827
140880
  if (!this.modelRuntime) throw new Error("OAuth login requires a ModelRuntime-backed Fusion auth storage");
140828
140881
  const executionProvider = toExecutionModelProviderId(provider);
140829
140882
  const legacy = callbacks;
140830
- const interaction = { signal: legacy.signal, prompt: async (prompt) => legacy.onPrompt?.({ message: prompt.message, placeholder: "placeholder" in prompt ? prompt.placeholder : void 0 }) ?? "", notify: (event) => {
140831
- if (event.type === "auth_url") legacy.onAuth?.({ url: event.url, instructions: event.instructions });
140832
- else if (event.type === "device_code") legacy.onDeviceCode?.(event);
140833
- else if (event.type === "progress") legacy.onProgress?.(event.message);
140834
- } };
140883
+ const interaction = {
140884
+ signal: legacy.signal,
140885
+ prompt: async (prompt) => {
140886
+ if (prompt.type === "select") {
140887
+ const chosen = await legacy.onSelect?.({ message: prompt.message, options: prompt.options });
140888
+ return chosen ?? prompt.options[0]?.id ?? "";
140889
+ }
140890
+ if (prompt.type === "manual_code" && legacy.onManualCodeInput) {
140891
+ return await legacy.onManualCodeInput();
140892
+ }
140893
+ return await legacy.onPrompt?.({ message: prompt.message, placeholder: "placeholder" in prompt ? prompt.placeholder : void 0 }) ?? "";
140894
+ },
140895
+ notify: (event) => {
140896
+ if (event.type === "auth_url") legacy.onAuth?.({ url: event.url, instructions: event.instructions });
140897
+ else if (event.type === "device_code") legacy.onDeviceCode?.(event);
140898
+ else if (event.type === "progress") legacy.onProgress?.(event.message);
140899
+ }
140900
+ };
140835
140901
  await this.modelRuntime.login(executionProvider, "oauth", interaction);
140836
140902
  this.reload();
140837
140903
  }
@@ -178534,9 +178600,9 @@ function mergeParkedColumns(base, lanes) {
178534
178600
  terminal: /* @__PURE__ */ new Set([...base.terminal, ...lanes.terminal ?? [], complete, archived])
178535
178601
  };
178536
178602
  }
178537
- async function resolveTaskParkedColumns(store, taskId) {
178603
+ async function resolveTaskParkedColumns(store, taskId, selectionCache) {
178538
178604
  try {
178539
- const ir2 = await resolveWorkflowIrForTask(store, taskId);
178605
+ const ir2 = await resolveWorkflowIrForTask(store, taskId, void 0, selectionCache);
178540
178606
  const l = resolveLifecycleColumns(ir2);
178541
178607
  const complete = l?.complete ?? LEGACY_PARKED_COLUMNS2.complete;
178542
178608
  const archived = l?.archived ?? LEGACY_PARKED_COLUMNS2.archived;
@@ -178781,6 +178847,7 @@ var init_scheduler = __esm({
178781
178847
  }
178782
178848
  });
178783
178849
  this.store.on("task:moved", async ({ task, from, to: to2, source, lanes }) => {
178850
+ const movedSelectionCache = /* @__PURE__ */ new Map();
178784
178851
  this.lastAutoClaimFingerprint.set(task.id, computeAutoClaimFingerprint(task));
178785
178852
  const parked = mergeParkedColumns(LEGACY_PARKED_COLUMNS2, lanes);
178786
178853
  if (from === parked.hold || to2 === parked.hold) {
@@ -178836,7 +178903,7 @@ var init_scheduler = __esm({
178836
178903
  });
178837
178904
  }
178838
178905
  }
178839
- const resolvedParked = mergeParkedColumns(await resolveTaskParkedColumns(this.store, task.id), lanes);
178906
+ const resolvedParked = mergeParkedColumns(await resolveTaskParkedColumns(this.store, task.id, movedSelectionCache), lanes);
178840
178907
  if (resolvedParked.terminal.has(to2)) {
178841
178908
  try {
178842
178909
  const settings = await this.store.getSettings();
@@ -178902,6 +178969,7 @@ var init_scheduler = __esm({
178902
178969
  }
178903
178970
  });
178904
178971
  this.store.on("task:updated", (task, meta2) => {
178972
+ const updatedSelectionCache = /* @__PURE__ */ new Map();
178905
178973
  const nextFingerprint = computeAutoClaimFingerprint(task);
178906
178974
  const previousFingerprint = this.lastAutoClaimFingerprint.get(task.id);
178907
178975
  if (!previousFingerprint || previousFingerprint !== nextFingerprint) {
@@ -178932,7 +179000,7 @@ var init_scheduler = __esm({
178932
179000
  });
178933
179001
  }
178934
179002
  void (async () => {
178935
- const unpausedParked = await resolveTaskParkedColumns(this.store, task.id);
179003
+ const unpausedParked = await resolveTaskParkedColumns(this.store, task.id, updatedSelectionCache);
178936
179004
  if (this.running && unpausedParked.wake.has(task.column)) {
178937
179005
  schedulerLog.log(`Task ${task.id} unpaused \u2014 triggering scheduling`);
178938
179006
  void this.schedule();
@@ -178944,7 +179012,7 @@ var init_scheduler = __esm({
178944
179012
  } else if (this.planningTaskIds.has(task.id)) {
178945
179013
  this.planningTaskIds.delete(task.id);
178946
179014
  void (async () => {
178947
- const planningParked = await resolveTaskParkedColumns(this.store, task.id);
179015
+ const planningParked = await resolveTaskParkedColumns(this.store, task.id, updatedSelectionCache);
178948
179016
  if (this.running && !task.status && !task.paused && !task.userPaused && planningParked.wake.has(task.column)) {
178949
179017
  schedulerLog.log(`Task ${task.id} finished planning \u2014 triggering scheduling`);
178950
179018
  void this.schedule();
@@ -178957,7 +179025,7 @@ var init_scheduler = __esm({
178957
179025
  } else if (!task.status && !task.paused && !task.userPaused && (this.approvalHeldTaskIds.delete(task.id) || (Boolean(task.approvedPlanFingerprint) || task.workflowStepResults?.some(isPlanReviewSatisfied) === true) && !this.approvalReleasedTaskIds.has(task.id))) {
178958
179026
  this.approvalReleasedTaskIds.add(task.id);
178959
179027
  void (async () => {
178960
- const approvalParked = await resolveTaskParkedColumns(this.store, task.id);
179028
+ const approvalParked = await resolveTaskParkedColumns(this.store, task.id, updatedSelectionCache);
178961
179029
  if (this.running && !task.status && !task.paused && !task.userPaused && approvalParked.wake.has(task.column)) {
178962
179030
  schedulerLog.log(`Task ${task.id} plan approval cleared \u2014 triggering scheduling`);
178963
179031
  void this.schedule();
@@ -178996,7 +179064,8 @@ var init_scheduler = __esm({
178996
179064
  if (settings.globalPause || settings.enginePaused) {
178997
179065
  return;
178998
179066
  }
178999
- const deletedParked = await resolveTaskParkedColumns(this.store, task.id);
179067
+ const deletedSelectionCache = /* @__PURE__ */ new Map();
179068
+ const deletedParked = await resolveTaskParkedColumns(this.store, task.id, deletedSelectionCache);
179000
179069
  const deletedWipColumns = await resolveProjectColumnsForRoles(this.store, ["countsTowardWip"]);
179001
179070
  const todoTasks = await this.store.listTasks({ column: deletedParked.hold, slim: true });
179002
179071
  const inProgressById = /* @__PURE__ */ new Map();
@@ -179205,7 +179274,16 @@ var init_scheduler = __esm({
179205
179274
  }
179206
179275
  }
179207
179276
  async transitionQueuedEpisode(task, input) {
179208
- return (await this.store.transitionQueuedEpisode(task.id, input)).appended;
179277
+ if (typeof this.store.transitionQueuedEpisode === "function") {
179278
+ return (await this.store.transitionQueuedEpisode(task.id, input)).appended;
179279
+ }
179280
+ await this.store.updateTask(task.id, {
179281
+ status: "queued",
179282
+ blockedBy: input.blockedBy ?? null,
179283
+ overlapBlockedBy: input.overlapBlockedBy ?? null
179284
+ });
179285
+ await this.logDispatchQueuedReason(task.id, input.action);
179286
+ return true;
179209
179287
  }
179210
179288
  async logDispatchQueuedReason(taskId, reason, memoKey) {
179211
179289
  const key = `${taskId}:${memoKey ?? reason}`;
@@ -179257,13 +179335,14 @@ var init_scheduler = __esm({
179257
179335
  }
179258
179336
  async emitHighOverlapFanoutWarnings(tasks2) {
179259
179337
  const escalationIrCache = /* @__PURE__ */ new Map();
179338
+ const escalationSelectionCache = /* @__PURE__ */ new Map();
179260
179339
  const escalationByTaskId = /* @__PURE__ */ new Map();
179261
179340
  const escalationColumns = /* @__PURE__ */ new Set();
179262
179341
  const holdByTaskId = /* @__PURE__ */ new Map();
179263
179342
  const terminalByTaskId = /* @__PURE__ */ new Map();
179264
179343
  const blockerReviewColumns = /* @__PURE__ */ new Set();
179265
179344
  for (const task of tasks2) {
179266
- const ir2 = await resolveWorkflowIrForTask(this.store, task.id, escalationIrCache).catch(() => void 0);
179345
+ const ir2 = await resolveWorkflowIrForTask(this.store, task.id, escalationIrCache, escalationSelectionCache).catch(() => void 0);
179267
179346
  if (!ir2) continue;
179268
179347
  for (const id of columnsWithFlag(ir2, "countsTowardWip")) escalationColumns.add(id);
179269
179348
  for (const id of columnsWithFlag(ir2, "mergeOrchestration")) escalationColumns.add(id);
@@ -179333,9 +179412,10 @@ var init_scheduler = __esm({
179333
179412
  if (!agentStore) return;
179334
179413
  const runningAgents = await agentStore.listAgents({ state: "running", includeEphemeral: false });
179335
179414
  const linkedAgents = runningAgents.filter((agent) => agent.taskId === taskId);
179415
+ const rollbackSelectionCache = /* @__PURE__ */ new Map();
179336
179416
  for (const agent of linkedAgents) {
179337
179417
  const activeRun = await agentStore.getActiveHeartbeatRun?.(agent.id);
179338
- const rollbackParked = await resolveTaskParkedColumns(this.store, taskId);
179418
+ const rollbackParked = await resolveTaskParkedColumns(this.store, taskId, rollbackSelectionCache);
179339
179419
  const proof = evaluateParkedAgentTaskLink({
179340
179420
  agent,
179341
179421
  linkedTask: { column: rollbackParked.hold },
@@ -179383,11 +179463,12 @@ var init_scheduler = __esm({
179383
179463
  const repo = getCurrentRepo(this.store.getRootDir());
179384
179464
  if (!repo) return;
179385
179465
  const hydrationIrCache = /* @__PURE__ */ new Map();
179466
+ const hydrationSelectionCache = /* @__PURE__ */ new Map();
179386
179467
  for (const task of tasks2) {
179387
179468
  if (!task.prInfo) continue;
179388
179469
  let flags;
179389
179470
  try {
179390
- const ir2 = await resolveWorkflowIrForTask(this.store, task.id, hydrationIrCache);
179471
+ const ir2 = await resolveWorkflowIrForTask(this.store, task.id, hydrationIrCache, hydrationSelectionCache);
179391
179472
  const column = ir2.columns?.find((candidate) => candidate.id === task.column);
179392
179473
  if (column) flags = resolveColumnFlags(column);
179393
179474
  } catch {
@@ -180316,19 +180397,20 @@ var init_scheduler = __esm({
180316
180397
  return;
180317
180398
  }
180318
180399
  const sliceIdBeforeUpdate = feature.sliceId;
180319
- const featureSlice = await missionStore.getSlice(feature.sliceId);
180320
- const milestone = featureSlice ? await missionStore.getMilestone(featureSlice.milestoneId) : void 0;
180321
- const missionId = task.missionId ?? milestone?.missionId;
180322
- if (!missionId) {
180323
- schedulerLog.warn(`Task ${taskId} feature ${feature.id} has no resolvable mission; skipping reconciliation`);
180324
- return;
180325
- }
180326
- if (missionId !== task.missionId) {
180327
- try {
180400
+ let missionId = task.missionId;
180401
+ try {
180402
+ if (!missionId && typeof missionStore.getSlice === "function") {
180403
+ const featureSlice = await missionStore.getSlice(feature.sliceId);
180404
+ const milestone = featureSlice && typeof missionStore.getMilestone === "function" ? await missionStore.getMilestone(featureSlice.milestoneId) : void 0;
180405
+ missionId = milestone?.missionId;
180406
+ }
180407
+ if (!missionId) {
180408
+ schedulerLog.warn(`Task ${taskId} feature ${feature.id} has no resolvable mission; skipping reconciliation`);
180409
+ } else if (missionId !== task.missionId) {
180328
180410
  await reconcileMissionState({ taskStore: this.store, missionStore }, { missionId, source: "task-move" });
180329
- } catch (error3) {
180330
- schedulerLog.warn(`Mission reconciliation failed after resolving task ${taskId}; continuing mission completion handling:`, error3);
180331
180411
  }
180412
+ } catch (error3) {
180413
+ schedulerLog.warn(`Task ${taskId} feature ${feature.id} mission reconcile skipped (${String(error3)}); continuing to completion check`);
180332
180414
  }
180333
180415
  let completionFlags;
180334
180416
  try {
@@ -187997,7 +188079,6 @@ var init_notifier = __esm({
187997
188079
  }
187998
188080
  notifyGridlock(event) {
187999
188081
  if (event === null) {
188000
- this.lastGridlockNotificationAt = null;
188001
188082
  return;
188002
188083
  }
188003
188084
  if (!this.config.enabled || !this.config.topic || !this.isEventEnabled("gridlock")) return;
@@ -198813,6 +198894,63 @@ var init_agent_user_comments = __esm({
198813
198894
  }
198814
198895
  });
198815
198896
 
198897
+ // ../engine/src/git-identity.ts
198898
+ function slugifyAgentEmailLocalPart(value2) {
198899
+ return value2.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 64);
198900
+ }
198901
+ function resolveCommitIdentity(input = {}) {
198902
+ const { agent, settings } = input;
198903
+ if (settings?.commitAuthorEnabled === false) {
198904
+ return void 0;
198905
+ }
198906
+ const configuredName = settings?.commitAuthorName?.trim();
198907
+ const configuredEmail = settings?.commitAuthorEmail?.trim();
198908
+ if (configuredName && configuredEmail) {
198909
+ return { name: configuredName, email: configuredEmail };
198910
+ }
198911
+ const agentName = agent?.name?.trim() || agent?.id?.trim() || "";
198912
+ if (agentName) {
198913
+ const localPart = slugifyAgentEmailLocalPart(agentName);
198914
+ if (localPart) {
198915
+ return {
198916
+ name: configuredName || `${agentName} (Fusion)`,
198917
+ email: configuredEmail || `${localPart}@agents.fusion.local`
198918
+ };
198919
+ }
198920
+ }
198921
+ return {
198922
+ name: configuredName || FUSION_FALLBACK_IDENTITY.name,
198923
+ email: configuredEmail || FUSION_FALLBACK_IDENTITY.email
198924
+ };
198925
+ }
198926
+ function commitIdentityEnv(identity) {
198927
+ if (!identity) {
198928
+ return {};
198929
+ }
198930
+ return {
198931
+ GIT_AUTHOR_NAME: identity.name,
198932
+ GIT_AUTHOR_EMAIL: identity.email,
198933
+ GIT_COMMITTER_NAME: identity.name,
198934
+ GIT_COMMITTER_EMAIL: identity.email
198935
+ };
198936
+ }
198937
+ function commitIdentityArgs(identity) {
198938
+ if (!identity) {
198939
+ return [];
198940
+ }
198941
+ return ["-c", `user.name=${identity.name}`, "-c", `user.email=${identity.email}`];
198942
+ }
198943
+ var FUSION_FALLBACK_IDENTITY;
198944
+ var init_git_identity = __esm({
198945
+ "../engine/src/git-identity.ts"() {
198946
+ "use strict";
198947
+ FUSION_FALLBACK_IDENTITY = {
198948
+ name: "Fusion",
198949
+ email: "noreply@runfusion.ai"
198950
+ };
198951
+ }
198952
+ });
198953
+
198816
198954
  // ../engine/src/sandbox/bubblewrap-detect.ts
198817
198955
  import { exec as exec20 } from "node:child_process";
198818
198956
  import { promisify as promisify30 } from "node:util";
@@ -204050,8 +204188,12 @@ import { createHash as createHash29 } from "node:crypto";
204050
204188
  import { join as join73, resolve as resolve45 } from "node:path";
204051
204189
  import { hostname as hostname3 } from "node:os";
204052
204190
  import { Type as Type4 } from "typebox";
204053
- function mergerCommitEnv() {
204054
- return { ...process.env, [IDENTITY_GUARD_BYPASS_ENV]: "1" };
204191
+ function mergerCommitEnv(identity) {
204192
+ return {
204193
+ ...process.env,
204194
+ [IDENTITY_GUARD_BYPASS_ENV]: "1",
204195
+ ...commitIdentityEnv(identity ?? resolveCommitIdentity())
204196
+ };
204055
204197
  }
204056
204198
  async function resolveMergerLifecycleColumn(store, taskId, which) {
204057
204199
  const fallback2 = which === "complete" ? "done" : which === "merge" ? "in-review" : "todo";
@@ -212027,6 +212169,7 @@ var init_merger = __esm({
212027
212169
  init_worktree_hooks();
212028
212170
  init_effective_settings();
212029
212171
  init_agent_user_comments();
212172
+ init_git_identity();
212030
212173
  init_verification_utils();
212031
212174
  init_sandbox();
212032
212175
  init_verification_utils();
@@ -212222,7 +212365,7 @@ function hasTerminalInvalidDoneTransition(task) {
212222
212365
  function isPrincipalHeldPlanningStatusOwned(status) {
212223
212366
  return status === null || status === void 0;
212224
212367
  }
212225
- var log34, worktreeMetadataReconcileLog, yieldEventLoop2, DONE_TASK_INTEGRITY_SWEEP_LIMIT, BOARD_STALL_NOTIFICATION_COOLDOWN_MS, DB_CORRUPTION_NOTIFICATION_COOLDOWN_MS, PHANTOM_EXECUTOR_BINDING_AGE_MULTIPLIER, MAX_NO_PROGRESS_RESUME_ATTEMPTS, PRE_EXECUTION_WORKTREE_MAX_IDLE_MS, APPROVED_TRIAGE_RECOVERY_GRACE_MS, STARVED_REFINEMENT_RECOVERY_GRACE_MS, STARVED_PEER_PROGRESS_THRESHOLD, STARVED_REFINEMENT_ESCALATION_COOLDOWN_MS, ORPHANED_EXECUTION_RECOVERY_GRACE_MS, STALLED_CARD_WATCHDOG_MS, LEAKED_WORKTREE_SLOT_GRACE_MS, NON_TERMINAL_STEP_STATUSES2, STRANDED_COMPLETED_TODO_ACTIVE_STATUSES, GHOST_REVIEW_PRESERVED_STATUSES, ORPHANED_WITH_WORKTREE_GRACE_MS, MAX_TASK_DONE_RETRIES, RECONCILE_SCOPE_OVERRIDE_MERGE_ACTIVE_STATUS_SET, MAX_STARVATION_DROPS, TERMINAL_WORKSPACE_WORKTREE_TEARDOWN_MIN_IDLE_MS, DEADLOCK_RECOVERY_COOLDOWN_MS, DEFAULT_STALE_MERGING_FANOUT_MIN_AGE_MS, DEFAULT_UNBACKED_MERGING_FANOUT_GRACE_MS, DURABLE_ERROR_RECOVERY_BASE_COOLDOWN_MS, DURABLE_ERROR_RECOVERY_MAX_COOLDOWN_MS, RUNNING_ON_INACTIVE_TASK_STALE_RUN_MS, UNREADABLE_WORKFLOW_SELECTION, SelfHealingManager;
212368
+ var log34, worktreeMetadataReconcileLog, yieldEventLoop2, DONE_METADATA_REPAIR_CAP, DONE_TASK_INTEGRITY_SWEEP_LIMIT, GIT_WORKTREE_CHURN_INTERVAL_MS, BOARD_STALL_NOTIFICATION_COOLDOWN_MS, DB_CORRUPTION_NOTIFICATION_COOLDOWN_MS, PHANTOM_EXECUTOR_BINDING_AGE_MULTIPLIER, MAX_NO_PROGRESS_RESUME_ATTEMPTS, PRE_EXECUTION_WORKTREE_MAX_IDLE_MS, APPROVED_TRIAGE_RECOVERY_GRACE_MS, STARVED_REFINEMENT_RECOVERY_GRACE_MS, STARVED_PEER_PROGRESS_THRESHOLD, STARVED_REFINEMENT_ESCALATION_COOLDOWN_MS, ORPHANED_EXECUTION_RECOVERY_GRACE_MS, STALLED_CARD_WATCHDOG_MS, LEAKED_WORKTREE_SLOT_GRACE_MS, NON_TERMINAL_STEP_STATUSES2, STRANDED_COMPLETED_TODO_ACTIVE_STATUSES, GHOST_REVIEW_PRESERVED_STATUSES, ORPHANED_WITH_WORKTREE_GRACE_MS, MAX_TASK_DONE_RETRIES, RECONCILE_SCOPE_OVERRIDE_MERGE_ACTIVE_STATUS_SET, MAX_STARVATION_DROPS, TERMINAL_WORKSPACE_WORKTREE_TEARDOWN_MIN_IDLE_MS, DEADLOCK_RECOVERY_COOLDOWN_MS, DEFAULT_STALE_MERGING_FANOUT_MIN_AGE_MS, DEFAULT_UNBACKED_MERGING_FANOUT_GRACE_MS, DURABLE_ERROR_RECOVERY_BASE_COOLDOWN_MS, DURABLE_ERROR_RECOVERY_MAX_COOLDOWN_MS, RUNNING_ON_INACTIVE_TASK_STALE_RUN_MS, UNREADABLE_WORKFLOW_SELECTION, SelfHealingManager;
212226
212369
  var init_self_healing = __esm({
212227
212370
  "../engine/src/self-healing.ts"() {
212228
212371
  "use strict";
@@ -212285,7 +212428,9 @@ var init_self_healing = __esm({
212285
212428
  log34 = createLogger2("self-healing");
212286
212429
  worktreeMetadataReconcileLog = createLogger2("worktree-metadata-reconcile");
212287
212430
  yieldEventLoop2 = () => new Promise((resolve112) => setImmediateCb2(resolve112));
212431
+ DONE_METADATA_REPAIR_CAP = 25;
212288
212432
  DONE_TASK_INTEGRITY_SWEEP_LIMIT = 50;
212433
+ GIT_WORKTREE_CHURN_INTERVAL_MS = 60 * 60 * 1e3;
212289
212434
  BOARD_STALL_NOTIFICATION_COOLDOWN_MS = 60 * 6e4;
212290
212435
  DB_CORRUPTION_NOTIFICATION_COOLDOWN_MS = 60 * 60 * 1e3;
212291
212436
  PHANTOM_EXECUTOR_BINDING_AGE_MULTIPLIER = 3;
@@ -212341,7 +212486,10 @@ var init_self_healing = __esm({
212341
212486
  lastUnpauseAt = 0;
212342
212487
  // ── Maintenance timer ───────────────────────────────────────────────
212343
212488
  maintenanceInterval = null;
212489
+ maintenanceStarting = false;
212344
212490
  maintenanceRunning = false;
212491
+ /** Last wall-clock a batch-1 git-churn pass ran on this manager (coarse-cadence gate). */
212492
+ lastGitWorktreeChurnAt = 0;
212345
212493
  /** In-process belt only; the durable apply token remains the cross-process fence. */
212346
212494
  autoRecoverTerminalFailuresInFlight = false;
212347
212495
  // ── Event listener cleanup ──────────────────────────────────────────
@@ -213239,6 +213387,7 @@ var init_self_healing = __esm({
213239
213387
  }
213240
213388
  // ── Auto-unpause ───────────────────────────────────────────────────
213241
213389
  onSettingsUpdated(settings, previous) {
213390
+ this.syncMaintenanceOnRefresh(settings);
213242
213391
  if (!previous.globalPause && settings.globalPause) {
213243
213392
  if (!settings.autoUnpauseEnabled) {
213244
213393
  log34.log("Global pause activated \u2014 auto-unpause disabled, requires manual intervention");
@@ -213422,6 +213571,7 @@ var init_self_healing = __esm({
213422
213571
  try {
213423
213572
  await this.store.moveTask(taskId, await resolveReboundTargetForTask(this.store, taskId), {
213424
213573
  preserveProgress: true,
213574
+ preserveWorktree: true,
213425
213575
  preserveStatus: true,
213426
213576
  // #1411: backward recovery — skip order-derived adjacency.
213427
213577
  moveSource: "engine",
@@ -213502,7 +213652,23 @@ var init_self_healing = __esm({
213502
213652
  }
213503
213653
  // ── Periodic maintenance ──────────────────────────────────────────
213504
213654
  async startMaintenance() {
213655
+ if (this.maintenanceInterval) return;
213656
+ if (this.maintenanceStarting) return;
213657
+ this.maintenanceStarting = true;
213658
+ try {
213659
+ await this.armMaintenanceInternal();
213660
+ } finally {
213661
+ this.maintenanceStarting = false;
213662
+ }
213663
+ }
213664
+ async armMaintenanceInternal() {
213665
+ if (this.maintenanceInterval) return;
213505
213666
  const settings = await this.store.getSettings();
213667
+ if (this.maintenanceInterval) return;
213668
+ if (settings.globalPause || settings.enginePaused) {
213669
+ log34.debug("Periodic maintenance not armed \u2014 engine/global pause is active");
213670
+ return;
213671
+ }
213506
213672
  const intervalMs = settings.maintenanceIntervalMs ?? 9e5;
213507
213673
  if (intervalMs <= 0) {
213508
213674
  log34.debug("Periodic maintenance disabled (maintenanceIntervalMs <= 0)");
@@ -213513,6 +213679,38 @@ var init_self_healing = __esm({
213513
213679
  void this.runMaintenance();
213514
213680
  }, intervalMs);
213515
213681
  }
213682
+ /*
213683
+ * FNXC:PauseGatedMaintenance 2026-08-13-03:08 (RUFU-076):
213684
+ * Keep the periodic maintenance timer in sync with the engine/global pause state. `onSettingsUpdated`
213685
+ * calls this on every settings:updated event; pausing clears the armed timer (stopping the git churn),
213686
+ * unpausing (re)arms it idempotently via `startMaintenance`'s guard. This is the only writer of the
213687
+ * pause->stop-maintenance transition besides `stop()` teardown.
213688
+ */
213689
+ /*
213690
+ * FNXC:GitWorktreeChurnCadenceCoarsen 2026-08-13-04:05 (RUFU-076):
213691
+ * Coarse-cadence gate for batch-1 git-churn steps. Returns true only when a full coarse window
213692
+ * (hourly) has elapsed since the last batch-1 git-worktree pass, so an idle/healthy active project
213693
+ * does not re-run `git worktree prune` / `git worktree list` / `git branch` on every maintenance
213694
+ * interval. Note the shared timestamp also bounds cleanup-orphaned-branches and enforce-worktree-cap
213695
+ * even though they live further down batch-1.
213696
+ */
213697
+ isGitWorktreeChurnDue() {
213698
+ return Date.now() - this.lastGitWorktreeChurnAt >= GIT_WORKTREE_CHURN_INTERVAL_MS;
213699
+ }
213700
+ syncMaintenanceOnRefresh(settings) {
213701
+ const paused = settings.globalPause || settings.enginePaused;
213702
+ if (paused) {
213703
+ if (this.maintenanceInterval) {
213704
+ clearInterval(this.maintenanceInterval);
213705
+ this.maintenanceInterval = null;
213706
+ log34.debug("Periodic maintenance paused \u2014 cleared maintenance timer");
213707
+ }
213708
+ return;
213709
+ }
213710
+ if (!this.maintenanceInterval) {
213711
+ void this.startMaintenance();
213712
+ }
213713
+ }
213516
213714
  /*
213517
213715
  FNXC:CrossProcessDeleteObservation 2026-08-01-11:39:
213518
213716
  Periodic self-healing owns outbox retention. Each already-open PostgreSQL project is gated to
@@ -213849,17 +214047,26 @@ var init_self_healing = __esm({
213849
214047
  log34.debug("Maintenance cycle starting");
213850
214048
  try {
213851
214049
  const settings = await this.store.getSettings();
214050
+ const maintenancePaused = settings.globalPause || settings.enginePaused;
214051
+ const gitWorktreeChurnDue = this.isGitWorktreeChurnDue();
214052
+ if (!maintenancePaused && gitWorktreeChurnDue) {
214053
+ this.lastGitWorktreeChurnAt = Date.now();
214054
+ }
213852
214055
  const batch1Fns = [
213853
- { name: "prune-worktrees", fn: () => this.pruneWorktrees() },
214056
+ {
214057
+ name: "prune-worktrees",
214058
+ fn: () => maintenancePaused || !gitWorktreeChurnDue ? Promise.resolve(0) : this.pruneWorktrees()
214059
+ },
213854
214060
  {
213855
214061
  name: "prune-task-lifecycle-events",
213856
214062
  fn: async () => this.pruneTaskLifecycleEventsForMaintenance()
213857
214063
  },
213858
214064
  { name: "prune-github-check-states", fn: async () => this.pruneGitHubCheckStatesForMaintenance() },
213859
- { name: "cleanup-orphans", fn: () => this.cleanupOrphans() },
214065
+ { name: "cleanup-orphans", fn: () => maintenancePaused || !gitWorktreeChurnDue ? Promise.resolve(0) : this.cleanupOrphans() },
213860
214066
  {
213861
214067
  name: "cleanup-stale-temp-merge-worktrees",
213862
214068
  fn: async () => {
214069
+ if (maintenancePaused || !gitWorktreeChurnDue) return 0;
213863
214070
  const cleaned = await this.cleanupStaleTempMergeWorktrees();
213864
214071
  if (cleaned > 0) {
213865
214072
  log34.debug(`Cleaned ${cleaned} stale AI merge temp worktree(s)`);
@@ -213867,7 +214074,10 @@ var init_self_healing = __esm({
213867
214074
  return cleaned;
213868
214075
  }
213869
214076
  },
213870
- { name: "cleanup-orphaned-branches", fn: () => this.cleanupOrphanedBranches() },
214077
+ {
214078
+ name: "cleanup-orphaned-branches",
214079
+ fn: () => maintenancePaused || !gitWorktreeChurnDue ? Promise.resolve(0) : this.cleanupOrphanedBranches()
214080
+ },
213871
214081
  {
213872
214082
  name: "reconcile-orphaned-task-dirs",
213873
214083
  fn: async () => {
@@ -213975,7 +214185,7 @@ var init_self_healing = __esm({
213975
214185
  },
213976
214186
  { name: "fts-maintenance", fn: () => this.maintainTaskFts() },
213977
214187
  { name: "checkpoint-wal", fn: () => Promise.resolve(this.checkpointWal()) },
213978
- { name: "enforce-worktree-cap", fn: () => this.enforceWorktreeCap() }
214188
+ { name: "enforce-worktree-cap", fn: () => maintenancePaused || !gitWorktreeChurnDue ? Promise.resolve(0) : this.enforceWorktreeCap() }
213979
214189
  ];
213980
214190
  for (const fn2 of batch1Fns) {
213981
214191
  try {
@@ -215257,6 +215467,7 @@ var init_self_healing = __esm({
215257
215467
  moveSource: "engine",
215258
215468
  // #1411: backward recovery — skip order-derived adjacency.
215259
215469
  recoveryRehome: true,
215470
+ // worktree-discard-intended: reclaim proved the branch already merged / has zero unique commits; the checkout holds nothing worth keeping and worktree was explicitly nulled above.
215260
215471
  preserveProgress: true,
215261
215472
  preserveResumeState: true
215262
215473
  });
@@ -215347,6 +215558,7 @@ var init_self_healing = __esm({
215347
215558
  moveSource: "engine",
215348
215559
  // #1411: backward recovery — skip order-derived adjacency.
215349
215560
  recoveryRehome: true,
215561
+ // worktree-discard-intended: reclaim proved the branch already merged / has zero unique commits; the checkout holds nothing worth keeping and worktree was explicitly nulled above.
215350
215562
  preserveProgress: true,
215351
215563
  preserveResumeState: true
215352
215564
  });
@@ -216143,7 +216355,18 @@ var init_self_healing = __esm({
216143
216355
  const withUniqueWork = existingCandidates.filter((candidate) => candidate.aheadCount > 0);
216144
216356
  if (withUniqueWork.length === 1) {
216145
216357
  const selected = withUniqueWork[0];
216146
- const patch = { branch: selected.branch, worktree: null };
216358
+ let preservedWorktree = false;
216359
+ if (task.worktree && existsSync60(task.worktree)) {
216360
+ try {
216361
+ const { stdout } = await execAsync18("git rev-parse --abbrev-ref HEAD", {
216362
+ cwd: task.worktree,
216363
+ timeout: 3e4
216364
+ });
216365
+ preservedWorktree = stdout.trim() === selected.branch;
216366
+ } catch {
216367
+ }
216368
+ }
216369
+ const patch = preservedWorktree ? { branch: selected.branch } : { branch: selected.branch, worktree: null };
216147
216370
  if (!task.baseCommitSha) {
216148
216371
  const derivedBaseCommit = (await execAsync18(
216149
216372
  `git merge-base ${shellQuote2(integrationBase)} ${shellQuote2(selected.branch)}`,
@@ -216182,7 +216405,8 @@ var init_self_healing = __esm({
216182
216405
  aheadCount: selected.aheadCount,
216183
216406
  integrationBase,
216184
216407
  source: "auto-rebind-in-review",
216185
- previousBranch: task.branch ?? null
216408
+ previousBranch: task.branch ?? null,
216409
+ preservedWorktree
216186
216410
  }
216187
216411
  });
216188
216412
  result2.repaired++;
@@ -216192,7 +216416,8 @@ var init_self_healing = __esm({
216192
216416
  branch: selected.branch,
216193
216417
  aheadCount: selected.aheadCount,
216194
216418
  integrationBase,
216195
- previousBranch: task.branch ?? null
216419
+ previousBranch: task.branch ?? null,
216420
+ preservedWorktree
216196
216421
  });
216197
216422
  continue;
216198
216423
  }
@@ -217724,7 +217949,8 @@ var init_self_healing = __esm({
217724
217949
  */
217725
217950
  recoveryRehome: true,
217726
217951
  bypassGuards: true,
217727
- preserveProgress: true
217952
+ preserveProgress: true,
217953
+ preserveWorktree: true
217728
217954
  });
217729
217955
  rehomed += 1;
217730
217956
  await createRunAuditor(this.store, {
@@ -218362,7 +218588,7 @@ var init_self_healing = __esm({
218362
218588
  await this.emitBackwardMoveNoAction(task, "finalize-no-op-review", "task:finalize-no-op-review-no-action", proof);
218363
218589
  continue;
218364
218590
  }
218365
- await this.store.moveTask(task.id, await resolveReboundTargetForTask(this.store, task.id), { preserveProgress: true, moveSource: "engine", recoveryRehome: true });
218591
+ await this.store.moveTask(task.id, await resolveReboundTargetForTask(this.store, task.id), { preserveProgress: true, preserveWorktree: true, moveSource: "engine", recoveryRehome: true });
218366
218592
  continue;
218367
218593
  }
218368
218594
  const mergedAt = (/* @__PURE__ */ new Date()).toISOString();
@@ -218412,7 +218638,7 @@ var init_self_healing = __esm({
218412
218638
  baseRef: classification.baseRef,
218413
218639
  lane: "self-healing-finalize-no-op-review"
218414
218640
  });
218415
- await this.store.moveTask(task.id, await resolveReboundTargetForTask(this.store, task.id), { preserveProgress: true, moveSource: "engine", recoveryRehome: true });
218641
+ await this.store.moveTask(task.id, await resolveReboundTargetForTask(this.store, task.id), { preserveProgress: true, preserveWorktree: true, moveSource: "engine", recoveryRehome: true });
218416
218642
  recovered++;
218417
218643
  continue;
218418
218644
  }
@@ -218431,7 +218657,7 @@ var init_self_healing = __esm({
218431
218657
  classification: "proven-no-op",
218432
218658
  baseRef: classification.baseRef
218433
218659
  });
218434
- await this.store.moveTask(task.id, await resolveReboundTargetForTask(this.store, task.id), { preserveProgress: true, moveSource: "engine", recoveryRehome: true });
218660
+ await this.store.moveTask(task.id, await resolveReboundTargetForTask(this.store, task.id), { preserveProgress: true, preserveWorktree: true, moveSource: "engine", recoveryRehome: true });
218435
218661
  recovered++;
218436
218662
  continue;
218437
218663
  }
@@ -218974,7 +219200,7 @@ var init_self_healing = __esm({
218974
219200
  task.id,
218975
219201
  "Auto-recovered: in-review task still had incomplete steps \u2014 moved back to todo for retry"
218976
219202
  );
218977
- await this.store.moveTask(task.id, await resolveReboundTargetForTask(this.store, task.id), { preserveProgress: true, moveSource: "engine", recoveryRehome: true });
219203
+ await this.store.moveTask(task.id, await resolveReboundTargetForTask(this.store, task.id), { preserveProgress: true, preserveWorktree: true, moveSource: "engine", recoveryRehome: true });
218978
219204
  log34.log(`Recovered stale incomplete review task ${task.id}: moved back to todo`);
218979
219205
  recovered++;
218980
219206
  } catch (err) {
@@ -219368,7 +219594,7 @@ var init_self_healing = __esm({
219368
219594
  task.id,
219369
219595
  "Auto-recovered: in-review task idle past stuck-task timeout \u2014 kicked back to todo"
219370
219596
  );
219371
- await this.store.moveTask(task.id, await resolveReboundTargetForTask(this.store, task.id), { preserveProgress: true, moveSource: "engine", recoveryRehome: true });
219597
+ await this.store.moveTask(task.id, await resolveReboundTargetForTask(this.store, task.id), { preserveProgress: true, preserveWorktree: true, moveSource: "engine", recoveryRehome: true });
219372
219598
  log34.log(`Kicked ghost review task ${task.id} back to todo`);
219373
219599
  recovered++;
219374
219600
  } catch (err) {
@@ -220292,8 +220518,9 @@ var init_self_healing = __esm({
220292
220518
  return Boolean(task.baseCommitSha);
220293
220519
  });
220294
220520
  if (candidates.length === 0) return 0;
220521
+ const batch = candidates.slice(0, DONE_METADATA_REPAIR_CAP);
220295
220522
  let repaired = 0;
220296
- for (const task of candidates) {
220523
+ for (const task of batch) {
220297
220524
  if (isWorkspaceTask(task)) continue;
220298
220525
  if (task.mergeDetails?.landedFilesAttributionRestricted || task.mergeDetails?.noOpVerifiedShortCircuit) {
220299
220526
  log34.debug(`recoverDoneTaskMergeMetadata: skipped ${task.id} \u2014 attribution-restricted`);
@@ -221950,7 +222177,7 @@ var init_self_healing = __esm({
221950
222177
  nextRecoveryAt: new Date(Date.now() + delayMs).toISOString()
221951
222178
  },
221952
222179
  targetColumn: await resolveReboundTargetForTask(this.store, task.id),
221953
- moveOptions: { preserveProgress: true, moveSource: "engine" }
222180
+ moveOptions: { preserveProgress: true, preserveWorktree: true, moveSource: "engine" }
221954
222181
  });
221955
222182
  if (applied.outcome === "applied") {
221956
222183
  await this.store.logEntry(task.id, `Auto-recovered generic terminal failure (attempt ${claim.attempt}/${MAX_TERMINAL_FAILURE_AUTO_RETRIES})`);
@@ -222092,6 +222319,7 @@ var init_self_healing = __esm({
222092
222319
  if (route.kind === "node-requeue" && fresh.column !== requeueTarget) {
222093
222320
  await this.store.moveTask(task.id, requeueTarget, {
222094
222321
  preserveProgress: true,
222322
+ preserveWorktree: true,
222095
222323
  moveSource: "engine",
222096
222324
  recoveryRehome: true
222097
222325
  });
@@ -222325,6 +222553,7 @@ var init_self_healing = __esm({
222325
222553
  const leaseRecovered = await this.options.leaseManager.recoverAbandonedLease(
222326
222554
  task.id,
222327
222555
  `in-progress limbo: ${describeWorktreeState(task)} + null branch`,
222556
+ // worktree-discard-intended: limbo recovery — the worktree is already missing or its metadata cleared; there is no live checkout to hold.
222328
222557
  { preserveProgress: true }
222329
222558
  );
222330
222559
  if (!leaseRecovered) {
@@ -223510,7 +223739,7 @@ var init_self_healing = __esm({
223510
223739
  task.id,
223511
223740
  `Auto-retry ${nextCount}/${MAX_TASK_DONE_RETRIES}: agent finished without fn_task_done \u2014 requeuing to todo to resume partial work`
223512
223741
  );
223513
- await this.store.moveTask(task.id, await resolveReboundTargetForTask(this.store, task.id), { preserveProgress: true, moveSource: "engine", recoveryRehome: true });
223742
+ await this.store.moveTask(task.id, await resolveReboundTargetForTask(this.store, task.id), { preserveProgress: true, preserveWorktree: true, moveSource: "engine", recoveryRehome: true });
223514
223743
  recovered++;
223515
223744
  } catch (err) {
223516
223745
  const errorMessage5 = err instanceof Error ? err.message : String(err);
@@ -261347,7 +261576,7 @@ async function ensureCommitTaskMetadata(mergeRoot, taskId, includeTaskId, traile
261347
261576
  const needsPrefix = includeTaskId && !subject.toLowerCase().startsWith(taskId.toLowerCase());
261348
261577
  const missingTrailers = trailers.filter((t) => !fullMessage.includes(t));
261349
261578
  if (!needsPrefix && missingTrailers.length === 0) return;
261350
- const args = ["-c", "trailer.ifExists=addIfDifferent", "commit", "--amend"];
261579
+ const args = [...commitIdentityArgs(resolveCommitIdentity()), "-c", "trailer.ifExists=addIfDifferent", "commit", "--amend"];
261351
261580
  if (needsPrefix) {
261352
261581
  args.push("-m", `${taskId}: ${subject}`);
261353
261582
  if (body.trim()) args.push("-m", body);
@@ -262982,6 +263211,7 @@ var execFileAsync25, aiMergeLog, MAX_CONCURRENT_ADVANCE_RETRIES, AiMergeBlockedE
262982
263211
  var init_merger_ai = __esm({
262983
263212
  "../engine/src/merge/merger-ai.ts"() {
262984
263213
  "use strict";
263214
+ init_git_identity();
262985
263215
  init_src2();
262986
263216
  init_agent_user_comments();
262987
263217
  init_worktree_names();
@@ -265760,7 +265990,7 @@ function defaultGitOps(cwd) {
265760
265990
  await runGit8(cwd, ["add", ...paths3]);
265761
265991
  },
265762
265992
  async commit(message) {
265763
- await runGit8(cwd, ["commit", "-m", JSON.stringify(message)]);
265993
+ await runGit8(cwd, [...commitIdentityArgs(resolveCommitIdentity()), "commit", "-m", JSON.stringify(message)]);
265764
265994
  return await runGit8(cwd, ["rev-parse", "HEAD"]);
265765
265995
  },
265766
265996
  async resetHard(ref) {
@@ -265838,6 +266068,7 @@ var execAsync55, GIT_TIMEOUT_MS10, GIT_MAX_BUFFER8;
265838
266068
  var init_git_ops = __esm({
265839
266069
  "../engine/src/experiment/git-ops.ts"() {
265840
266070
  "use strict";
266071
+ init_git_identity();
265841
266072
  init_finalize_types();
265842
266073
  execAsync55 = promisify81(exec59);
265843
266074
  GIT_TIMEOUT_MS10 = 3e4;
@@ -390609,9 +390840,14 @@ var init_register_auth_routes = __esm({
390609
390840
  }
390610
390841
  return await pendingLogin.inputPromise;
390611
390842
  },
390612
- // AuthStorage.login() forwards callbacks to provider-specific OAuth
390613
- // implementations verbatim. openai-codex supports this optional hook
390614
- // to race pasted codes against the localhost callback server.
390843
+ /*
390844
+ FNXC:ProviderAuth 2026-08-18-00:26:
390845
+ `onManualCodeInput` and `onSelect` are dispatched by prompt TYPE in AuthStorage.login()'s
390846
+ pi `AuthInteraction` shim (packages/engine/src/auth/auth-storage.ts), not forwarded
390847
+ verbatim as an older comment here claimed. Both were dead code after login moved to pi's
390848
+ ModelRuntime, which is what broke Codex login: its `select` prompt fell through to
390849
+ `onPrompt` and waited on a paste the UI never asked for.
390850
+ */
390615
390851
  onManualCodeInput: async () => await pendingLogin.inputPromise,
390616
390852
  onProgress: () => {
390617
390853
  },