@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/extension.js CHANGED
@@ -16765,8 +16765,34 @@ function didFallBackToDefault(ir2) {
16765
16765
  async function resolveWorkflowIrForTaskWithProvenance(store, taskId, irCache, selectionCache) {
16766
16766
  let workflowId;
16767
16767
  try {
16768
- const selection = selectionCache?.has(taskId) ? selectionCache.get(taskId) : store.getTaskWorkflowSelectionAsync ? await store.getTaskWorkflowSelectionAsync(taskId) : store.getTaskWorkflowSelection(taskId);
16769
- if (!selectionCache?.has(taskId)) selectionCache?.set(taskId, selection);
16768
+ const isSelectionCached = selectionCache?.has(taskId) ?? false;
16769
+ let selection = isSelectionCached && selectionCache ? selectionCache.get(taskId) : void 0;
16770
+ if (!isSelectionCached) {
16771
+ let inflight;
16772
+ let coalesced = false;
16773
+ if (selectionCache) {
16774
+ inflight = inflightSelectionReads.get(selectionCache);
16775
+ if (!inflight) {
16776
+ inflight = /* @__PURE__ */ new Map();
16777
+ inflightSelectionReads.set(selectionCache, inflight);
16778
+ }
16779
+ const pending = inflight.get(taskId);
16780
+ if (pending) {
16781
+ selection = await pending;
16782
+ coalesced = true;
16783
+ }
16784
+ }
16785
+ if (!coalesced) {
16786
+ const readPromise = store.getTaskWorkflowSelectionAsync ? store.getTaskWorkflowSelectionAsync(taskId) : Promise.resolve(store.getTaskWorkflowSelection(taskId));
16787
+ if (inflight) inflight.set(taskId, readPromise);
16788
+ try {
16789
+ selection = await readPromise;
16790
+ } finally {
16791
+ inflight?.delete(taskId);
16792
+ }
16793
+ }
16794
+ selectionCache?.set(taskId, selection);
16795
+ }
16770
16796
  workflowId = selection?.workflowId;
16771
16797
  } catch {
16772
16798
  return { ir: defaultCodingWorkflowIr(), source: "default" };
@@ -16790,13 +16816,14 @@ async function isTaskTerminalNodeIdAsync(store, taskId, nodeId) {
16790
16816
  }
16791
16817
  return nodeId === "end";
16792
16818
  }
16793
- var FELL_BACK_TO_DEFAULT;
16819
+ var inflightSelectionReads, FELL_BACK_TO_DEFAULT;
16794
16820
  var init_workflow_ir_resolver = __esm({
16795
16821
  "../core/src/workflows/workflow-ir-resolver.ts"() {
16796
16822
  "use strict";
16797
16823
  init_builtin_workflows();
16798
16824
  init_workflow_ir();
16799
16825
  init_workflow_prompt_overrides();
16826
+ inflightSelectionReads = /* @__PURE__ */ new WeakMap();
16800
16827
  FELL_BACK_TO_DEFAULT = /* @__PURE__ */ Symbol.for("fusion.workflowIr.fellBackToDefault");
16801
16828
  }
16802
16829
  });
@@ -36417,9 +36444,12 @@ async function markApprovalRequestCompleted(layer, requestId, input) {
36417
36444
  });
36418
36445
  }
36419
36446
  async function getApprovalAuditHistory(handle2, requestId, projectId2) {
36420
- const rows = await handle2.select().from(project_exports.approvalRequestAuditEvents).where(and(eq(project_exports.approvalRequestAuditEvents.requestId, requestId), projectScopeFor(project_exports.approvalRequestAuditEvents.projectId, projectId2))).orderBy(
36421
- sql`${project_exports.approvalRequestAuditEvents.createdAt} ASC, ${project_exports.approvalRequestAuditEvents.id} ASC`
36422
- );
36447
+ const eventType = project_exports.approvalRequestAuditEvents.eventType;
36448
+ const lifecycleRank = sql`CASE ${eventType} ${sql.join(
36449
+ APPROVAL_REQUEST_AUDIT_EVENT_TYPES.map((type, rank) => sql`WHEN ${type} THEN ${rank}`),
36450
+ sql` `
36451
+ )} ELSE ${APPROVAL_REQUEST_AUDIT_EVENT_TYPES.length} END`;
36452
+ 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`);
36423
36453
  return rows.map((row) => rowToAuditEvent(row));
36424
36454
  }
36425
36455
  var init_async_approval_request_store = __esm({
@@ -139941,17 +139971,40 @@ var init_auth_storage = __esm({
139941
139971
  async getApiKey(provider, instance) {
139942
139972
  return resolveStoredCredentialApiKey(provider, instance ? this.getInstance(instance) : this.get(provider));
139943
139973
  }
139974
+ /*
139975
+ FNXC:ProviderAuth 2026-08-18-04:40:
139976
+ MODIFY MUST BE ABLE TO CREATE. This is the seam pi persists a COMPLETED LOGIN through
139977
+ (`Models.login` -> `credentials.modify(provider.id, ...)` in pi-ai's models.js), not just the seam
139978
+ it refreshes an existing token through.
139979
+
139980
+ It used to resolve its write target with `creating: false` and then bail —
139981
+ `if (!target || !this.credential(target, current)) return { changed: false }` — whenever the
139982
+ provider had no credential row yet. The callback was never invoked, nothing was written, and pi's
139983
+ login resolved as a SUCCESS. So a first-ever login for a provider could not be saved: the browser
139984
+ flow completed, the token exchange succeeded, the lock file was taken and released, `auth.json`
139985
+ stayed `{}`, and the dashboard's poll saw `authenticated: false` and reported the useless "Login
139986
+ did not complete. Please try again."
139987
+
139988
+ It looked provider-specific and environment-specific for the worst possible reason: it only
139989
+ reproduces on a store with NO existing row, so every developer and every long-lived install — where
139990
+ a row already exists and this path is a plain refresh — works flawlessly, while every fresh install
139991
+ (a new container, a new machine, a wiped `~/.fusion`) can never complete its first login.
139992
+
139993
+ Create when absent, update when present. A callback returning undefined still writes nothing, so
139994
+ pi's refresh-bails-out behaviour is unchanged.
139995
+ */
139944
139996
  async modify(provider, fn2) {
139945
139997
  this.assertRefFromKey(provider);
139946
139998
  return this.withLock(async (current) => {
139947
- const target = this.resolveWriteTarget(provider, current, false);
139948
- if (!target || !this.credential(target, current)) return { result: void 0, changed: false };
139949
- const next = await fn2(this.credential(target, current));
139999
+ const target = this.resolveWriteTarget(provider, current, true);
140000
+ if (!target) return { result: void 0, changed: false };
140001
+ const existing = this.credential(target, current);
140002
+ const next = await fn2(existing);
139950
140003
  if (next !== void 0) {
139951
140004
  current[formatProviderInstanceKey(target)] = next;
139952
140005
  return { result: next, changed: true };
139953
140006
  }
139954
- return { result: this.credential(target, current), changed: false };
140007
+ return { result: existing, changed: false };
139955
140008
  });
139956
140009
  }
139957
140010
  getOAuthProviders() {
@@ -139964,11 +140017,24 @@ var init_auth_storage = __esm({
139964
140017
  if (!this.modelRuntime) throw new Error("OAuth login requires a ModelRuntime-backed Fusion auth storage");
139965
140018
  const executionProvider = toExecutionModelProviderId(provider);
139966
140019
  const legacy = callbacks;
139967
- const interaction = { signal: legacy.signal, prompt: async (prompt) => legacy.onPrompt?.({ message: prompt.message, placeholder: "placeholder" in prompt ? prompt.placeholder : void 0 }) ?? "", notify: (event) => {
139968
- if (event.type === "auth_url") legacy.onAuth?.({ url: event.url, instructions: event.instructions });
139969
- else if (event.type === "device_code") legacy.onDeviceCode?.(event);
139970
- else if (event.type === "progress") legacy.onProgress?.(event.message);
139971
- } };
140020
+ const interaction = {
140021
+ signal: legacy.signal,
140022
+ prompt: async (prompt) => {
140023
+ if (prompt.type === "select") {
140024
+ const chosen = await legacy.onSelect?.({ message: prompt.message, options: prompt.options });
140025
+ return chosen ?? prompt.options[0]?.id ?? "";
140026
+ }
140027
+ if (prompt.type === "manual_code" && legacy.onManualCodeInput) {
140028
+ return await legacy.onManualCodeInput();
140029
+ }
140030
+ return await legacy.onPrompt?.({ message: prompt.message, placeholder: "placeholder" in prompt ? prompt.placeholder : void 0 }) ?? "";
140031
+ },
140032
+ notify: (event) => {
140033
+ if (event.type === "auth_url") legacy.onAuth?.({ url: event.url, instructions: event.instructions });
140034
+ else if (event.type === "device_code") legacy.onDeviceCode?.(event);
140035
+ else if (event.type === "progress") legacy.onProgress?.(event.message);
140036
+ }
140037
+ };
139972
140038
  await this.modelRuntime.login(executionProvider, "oauth", interaction);
139973
140039
  this.reload();
139974
140040
  }
@@ -177671,9 +177737,9 @@ function mergeParkedColumns(base, lanes) {
177671
177737
  terminal: /* @__PURE__ */ new Set([...base.terminal, ...lanes.terminal ?? [], complete, archived])
177672
177738
  };
177673
177739
  }
177674
- async function resolveTaskParkedColumns(store, taskId) {
177740
+ async function resolveTaskParkedColumns(store, taskId, selectionCache) {
177675
177741
  try {
177676
- const ir2 = await resolveWorkflowIrForTask(store, taskId);
177742
+ const ir2 = await resolveWorkflowIrForTask(store, taskId, void 0, selectionCache);
177677
177743
  const l = resolveLifecycleColumns(ir2);
177678
177744
  const complete = l?.complete ?? LEGACY_PARKED_COLUMNS2.complete;
177679
177745
  const archived = l?.archived ?? LEGACY_PARKED_COLUMNS2.archived;
@@ -177918,6 +177984,7 @@ var init_scheduler = __esm({
177918
177984
  }
177919
177985
  });
177920
177986
  this.store.on("task:moved", async ({ task, from, to: to2, source, lanes }) => {
177987
+ const movedSelectionCache = /* @__PURE__ */ new Map();
177921
177988
  this.lastAutoClaimFingerprint.set(task.id, computeAutoClaimFingerprint(task));
177922
177989
  const parked = mergeParkedColumns(LEGACY_PARKED_COLUMNS2, lanes);
177923
177990
  if (from === parked.hold || to2 === parked.hold) {
@@ -177973,7 +178040,7 @@ var init_scheduler = __esm({
177973
178040
  });
177974
178041
  }
177975
178042
  }
177976
- const resolvedParked = mergeParkedColumns(await resolveTaskParkedColumns(this.store, task.id), lanes);
178043
+ const resolvedParked = mergeParkedColumns(await resolveTaskParkedColumns(this.store, task.id, movedSelectionCache), lanes);
177977
178044
  if (resolvedParked.terminal.has(to2)) {
177978
178045
  try {
177979
178046
  const settings = await this.store.getSettings();
@@ -178039,6 +178106,7 @@ var init_scheduler = __esm({
178039
178106
  }
178040
178107
  });
178041
178108
  this.store.on("task:updated", (task, meta2) => {
178109
+ const updatedSelectionCache = /* @__PURE__ */ new Map();
178042
178110
  const nextFingerprint = computeAutoClaimFingerprint(task);
178043
178111
  const previousFingerprint = this.lastAutoClaimFingerprint.get(task.id);
178044
178112
  if (!previousFingerprint || previousFingerprint !== nextFingerprint) {
@@ -178069,7 +178137,7 @@ var init_scheduler = __esm({
178069
178137
  });
178070
178138
  }
178071
178139
  void (async () => {
178072
- const unpausedParked = await resolveTaskParkedColumns(this.store, task.id);
178140
+ const unpausedParked = await resolveTaskParkedColumns(this.store, task.id, updatedSelectionCache);
178073
178141
  if (this.running && unpausedParked.wake.has(task.column)) {
178074
178142
  schedulerLog.log(`Task ${task.id} unpaused \u2014 triggering scheduling`);
178075
178143
  void this.schedule();
@@ -178081,7 +178149,7 @@ var init_scheduler = __esm({
178081
178149
  } else if (this.planningTaskIds.has(task.id)) {
178082
178150
  this.planningTaskIds.delete(task.id);
178083
178151
  void (async () => {
178084
- const planningParked = await resolveTaskParkedColumns(this.store, task.id);
178152
+ const planningParked = await resolveTaskParkedColumns(this.store, task.id, updatedSelectionCache);
178085
178153
  if (this.running && !task.status && !task.paused && !task.userPaused && planningParked.wake.has(task.column)) {
178086
178154
  schedulerLog.log(`Task ${task.id} finished planning \u2014 triggering scheduling`);
178087
178155
  void this.schedule();
@@ -178094,7 +178162,7 @@ var init_scheduler = __esm({
178094
178162
  } 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))) {
178095
178163
  this.approvalReleasedTaskIds.add(task.id);
178096
178164
  void (async () => {
178097
- const approvalParked = await resolveTaskParkedColumns(this.store, task.id);
178165
+ const approvalParked = await resolveTaskParkedColumns(this.store, task.id, updatedSelectionCache);
178098
178166
  if (this.running && !task.status && !task.paused && !task.userPaused && approvalParked.wake.has(task.column)) {
178099
178167
  schedulerLog.log(`Task ${task.id} plan approval cleared \u2014 triggering scheduling`);
178100
178168
  void this.schedule();
@@ -178133,7 +178201,8 @@ var init_scheduler = __esm({
178133
178201
  if (settings.globalPause || settings.enginePaused) {
178134
178202
  return;
178135
178203
  }
178136
- const deletedParked = await resolveTaskParkedColumns(this.store, task.id);
178204
+ const deletedSelectionCache = /* @__PURE__ */ new Map();
178205
+ const deletedParked = await resolveTaskParkedColumns(this.store, task.id, deletedSelectionCache);
178137
178206
  const deletedWipColumns = await resolveProjectColumnsForRoles(this.store, ["countsTowardWip"]);
178138
178207
  const todoTasks = await this.store.listTasks({ column: deletedParked.hold, slim: true });
178139
178208
  const inProgressById = /* @__PURE__ */ new Map();
@@ -178342,7 +178411,16 @@ var init_scheduler = __esm({
178342
178411
  }
178343
178412
  }
178344
178413
  async transitionQueuedEpisode(task, input) {
178345
- return (await this.store.transitionQueuedEpisode(task.id, input)).appended;
178414
+ if (typeof this.store.transitionQueuedEpisode === "function") {
178415
+ return (await this.store.transitionQueuedEpisode(task.id, input)).appended;
178416
+ }
178417
+ await this.store.updateTask(task.id, {
178418
+ status: "queued",
178419
+ blockedBy: input.blockedBy ?? null,
178420
+ overlapBlockedBy: input.overlapBlockedBy ?? null
178421
+ });
178422
+ await this.logDispatchQueuedReason(task.id, input.action);
178423
+ return true;
178346
178424
  }
178347
178425
  async logDispatchQueuedReason(taskId, reason, memoKey) {
178348
178426
  const key = `${taskId}:${memoKey ?? reason}`;
@@ -178394,13 +178472,14 @@ var init_scheduler = __esm({
178394
178472
  }
178395
178473
  async emitHighOverlapFanoutWarnings(tasks2) {
178396
178474
  const escalationIrCache = /* @__PURE__ */ new Map();
178475
+ const escalationSelectionCache = /* @__PURE__ */ new Map();
178397
178476
  const escalationByTaskId = /* @__PURE__ */ new Map();
178398
178477
  const escalationColumns = /* @__PURE__ */ new Set();
178399
178478
  const holdByTaskId = /* @__PURE__ */ new Map();
178400
178479
  const terminalByTaskId = /* @__PURE__ */ new Map();
178401
178480
  const blockerReviewColumns = /* @__PURE__ */ new Set();
178402
178481
  for (const task of tasks2) {
178403
- const ir2 = await resolveWorkflowIrForTask(this.store, task.id, escalationIrCache).catch(() => void 0);
178482
+ const ir2 = await resolveWorkflowIrForTask(this.store, task.id, escalationIrCache, escalationSelectionCache).catch(() => void 0);
178404
178483
  if (!ir2) continue;
178405
178484
  for (const id of columnsWithFlag(ir2, "countsTowardWip")) escalationColumns.add(id);
178406
178485
  for (const id of columnsWithFlag(ir2, "mergeOrchestration")) escalationColumns.add(id);
@@ -178470,9 +178549,10 @@ var init_scheduler = __esm({
178470
178549
  if (!agentStore) return;
178471
178550
  const runningAgents = await agentStore.listAgents({ state: "running", includeEphemeral: false });
178472
178551
  const linkedAgents = runningAgents.filter((agent) => agent.taskId === taskId);
178552
+ const rollbackSelectionCache = /* @__PURE__ */ new Map();
178473
178553
  for (const agent of linkedAgents) {
178474
178554
  const activeRun = await agentStore.getActiveHeartbeatRun?.(agent.id);
178475
- const rollbackParked = await resolveTaskParkedColumns(this.store, taskId);
178555
+ const rollbackParked = await resolveTaskParkedColumns(this.store, taskId, rollbackSelectionCache);
178476
178556
  const proof = evaluateParkedAgentTaskLink({
178477
178557
  agent,
178478
178558
  linkedTask: { column: rollbackParked.hold },
@@ -178520,11 +178600,12 @@ var init_scheduler = __esm({
178520
178600
  const repo = getCurrentRepo(this.store.getRootDir());
178521
178601
  if (!repo) return;
178522
178602
  const hydrationIrCache = /* @__PURE__ */ new Map();
178603
+ const hydrationSelectionCache = /* @__PURE__ */ new Map();
178523
178604
  for (const task of tasks2) {
178524
178605
  if (!task.prInfo) continue;
178525
178606
  let flags;
178526
178607
  try {
178527
- const ir2 = await resolveWorkflowIrForTask(this.store, task.id, hydrationIrCache);
178608
+ const ir2 = await resolveWorkflowIrForTask(this.store, task.id, hydrationIrCache, hydrationSelectionCache);
178528
178609
  const column = ir2.columns?.find((candidate) => candidate.id === task.column);
178529
178610
  if (column) flags = resolveColumnFlags(column);
178530
178611
  } catch {
@@ -179453,19 +179534,20 @@ var init_scheduler = __esm({
179453
179534
  return;
179454
179535
  }
179455
179536
  const sliceIdBeforeUpdate = feature.sliceId;
179456
- const featureSlice = await missionStore.getSlice(feature.sliceId);
179457
- const milestone = featureSlice ? await missionStore.getMilestone(featureSlice.milestoneId) : void 0;
179458
- const missionId = task.missionId ?? milestone?.missionId;
179459
- if (!missionId) {
179460
- schedulerLog.warn(`Task ${taskId} feature ${feature.id} has no resolvable mission; skipping reconciliation`);
179461
- return;
179462
- }
179463
- if (missionId !== task.missionId) {
179464
- try {
179537
+ let missionId = task.missionId;
179538
+ try {
179539
+ if (!missionId && typeof missionStore.getSlice === "function") {
179540
+ const featureSlice = await missionStore.getSlice(feature.sliceId);
179541
+ const milestone = featureSlice && typeof missionStore.getMilestone === "function" ? await missionStore.getMilestone(featureSlice.milestoneId) : void 0;
179542
+ missionId = milestone?.missionId;
179543
+ }
179544
+ if (!missionId) {
179545
+ schedulerLog.warn(`Task ${taskId} feature ${feature.id} has no resolvable mission; skipping reconciliation`);
179546
+ } else if (missionId !== task.missionId) {
179465
179547
  await reconcileMissionState({ taskStore: this.store, missionStore }, { missionId, source: "task-move" });
179466
- } catch (error3) {
179467
- schedulerLog.warn(`Mission reconciliation failed after resolving task ${taskId}; continuing mission completion handling:`, error3);
179468
179548
  }
179549
+ } catch (error3) {
179550
+ schedulerLog.warn(`Task ${taskId} feature ${feature.id} mission reconcile skipped (${String(error3)}); continuing to completion check`);
179469
179551
  }
179470
179552
  let completionFlags;
179471
179553
  try {
@@ -187134,7 +187216,6 @@ var init_notifier = __esm({
187134
187216
  }
187135
187217
  notifyGridlock(event) {
187136
187218
  if (event === null) {
187137
- this.lastGridlockNotificationAt = null;
187138
187219
  return;
187139
187220
  }
187140
187221
  if (!this.config.enabled || !this.config.topic || !this.isEventEnabled("gridlock")) return;
@@ -197950,6 +198031,63 @@ var init_agent_user_comments = __esm({
197950
198031
  }
197951
198032
  });
197952
198033
 
198034
+ // ../engine/src/git-identity.ts
198035
+ function slugifyAgentEmailLocalPart(value) {
198036
+ return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 64);
198037
+ }
198038
+ function resolveCommitIdentity(input = {}) {
198039
+ const { agent, settings } = input;
198040
+ if (settings?.commitAuthorEnabled === false) {
198041
+ return void 0;
198042
+ }
198043
+ const configuredName = settings?.commitAuthorName?.trim();
198044
+ const configuredEmail = settings?.commitAuthorEmail?.trim();
198045
+ if (configuredName && configuredEmail) {
198046
+ return { name: configuredName, email: configuredEmail };
198047
+ }
198048
+ const agentName = agent?.name?.trim() || agent?.id?.trim() || "";
198049
+ if (agentName) {
198050
+ const localPart = slugifyAgentEmailLocalPart(agentName);
198051
+ if (localPart) {
198052
+ return {
198053
+ name: configuredName || `${agentName} (Fusion)`,
198054
+ email: configuredEmail || `${localPart}@agents.fusion.local`
198055
+ };
198056
+ }
198057
+ }
198058
+ return {
198059
+ name: configuredName || FUSION_FALLBACK_IDENTITY.name,
198060
+ email: configuredEmail || FUSION_FALLBACK_IDENTITY.email
198061
+ };
198062
+ }
198063
+ function commitIdentityEnv(identity) {
198064
+ if (!identity) {
198065
+ return {};
198066
+ }
198067
+ return {
198068
+ GIT_AUTHOR_NAME: identity.name,
198069
+ GIT_AUTHOR_EMAIL: identity.email,
198070
+ GIT_COMMITTER_NAME: identity.name,
198071
+ GIT_COMMITTER_EMAIL: identity.email
198072
+ };
198073
+ }
198074
+ function commitIdentityArgs(identity) {
198075
+ if (!identity) {
198076
+ return [];
198077
+ }
198078
+ return ["-c", `user.name=${identity.name}`, "-c", `user.email=${identity.email}`];
198079
+ }
198080
+ var FUSION_FALLBACK_IDENTITY;
198081
+ var init_git_identity = __esm({
198082
+ "../engine/src/git-identity.ts"() {
198083
+ "use strict";
198084
+ FUSION_FALLBACK_IDENTITY = {
198085
+ name: "Fusion",
198086
+ email: "noreply@runfusion.ai"
198087
+ };
198088
+ }
198089
+ });
198090
+
197953
198091
  // ../engine/src/sandbox/bubblewrap-detect.ts
197954
198092
  import { exec as exec20 } from "node:child_process";
197955
198093
  import { promisify as promisify30 } from "node:util";
@@ -203187,8 +203325,12 @@ import { createHash as createHash29 } from "node:crypto";
203187
203325
  import { join as join73, resolve as resolve44 } from "node:path";
203188
203326
  import { hostname as hostname3 } from "node:os";
203189
203327
  import { Type as Type4 } from "typebox";
203190
- function mergerCommitEnv() {
203191
- return { ...process.env, [IDENTITY_GUARD_BYPASS_ENV]: "1" };
203328
+ function mergerCommitEnv(identity) {
203329
+ return {
203330
+ ...process.env,
203331
+ [IDENTITY_GUARD_BYPASS_ENV]: "1",
203332
+ ...commitIdentityEnv(identity ?? resolveCommitIdentity())
203333
+ };
203192
203334
  }
203193
203335
  async function resolveMergerLifecycleColumn(store, taskId, which) {
203194
203336
  const fallback = which === "complete" ? "done" : which === "merge" ? "in-review" : "todo";
@@ -211164,6 +211306,7 @@ var init_merger = __esm({
211164
211306
  init_worktree_hooks();
211165
211307
  init_effective_settings();
211166
211308
  init_agent_user_comments();
211309
+ init_git_identity();
211167
211310
  init_verification_utils();
211168
211311
  init_sandbox();
211169
211312
  init_verification_utils();
@@ -211359,7 +211502,7 @@ function hasTerminalInvalidDoneTransition(task) {
211359
211502
  function isPrincipalHeldPlanningStatusOwned(status) {
211360
211503
  return status === null || status === void 0;
211361
211504
  }
211362
- 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;
211505
+ 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;
211363
211506
  var init_self_healing = __esm({
211364
211507
  "../engine/src/self-healing.ts"() {
211365
211508
  "use strict";
@@ -211422,7 +211565,9 @@ var init_self_healing = __esm({
211422
211565
  log34 = createLogger2("self-healing");
211423
211566
  worktreeMetadataReconcileLog = createLogger2("worktree-metadata-reconcile");
211424
211567
  yieldEventLoop2 = () => new Promise((resolve61) => setImmediateCb2(resolve61));
211568
+ DONE_METADATA_REPAIR_CAP = 25;
211425
211569
  DONE_TASK_INTEGRITY_SWEEP_LIMIT = 50;
211570
+ GIT_WORKTREE_CHURN_INTERVAL_MS = 60 * 60 * 1e3;
211426
211571
  BOARD_STALL_NOTIFICATION_COOLDOWN_MS = 60 * 6e4;
211427
211572
  DB_CORRUPTION_NOTIFICATION_COOLDOWN_MS = 60 * 60 * 1e3;
211428
211573
  PHANTOM_EXECUTOR_BINDING_AGE_MULTIPLIER = 3;
@@ -211478,7 +211623,10 @@ var init_self_healing = __esm({
211478
211623
  lastUnpauseAt = 0;
211479
211624
  // ── Maintenance timer ───────────────────────────────────────────────
211480
211625
  maintenanceInterval = null;
211626
+ maintenanceStarting = false;
211481
211627
  maintenanceRunning = false;
211628
+ /** Last wall-clock a batch-1 git-churn pass ran on this manager (coarse-cadence gate). */
211629
+ lastGitWorktreeChurnAt = 0;
211482
211630
  /** In-process belt only; the durable apply token remains the cross-process fence. */
211483
211631
  autoRecoverTerminalFailuresInFlight = false;
211484
211632
  // ── Event listener cleanup ──────────────────────────────────────────
@@ -212376,6 +212524,7 @@ var init_self_healing = __esm({
212376
212524
  }
212377
212525
  // ── Auto-unpause ───────────────────────────────────────────────────
212378
212526
  onSettingsUpdated(settings, previous) {
212527
+ this.syncMaintenanceOnRefresh(settings);
212379
212528
  if (!previous.globalPause && settings.globalPause) {
212380
212529
  if (!settings.autoUnpauseEnabled) {
212381
212530
  log34.log("Global pause activated \u2014 auto-unpause disabled, requires manual intervention");
@@ -212559,6 +212708,7 @@ var init_self_healing = __esm({
212559
212708
  try {
212560
212709
  await this.store.moveTask(taskId, await resolveReboundTargetForTask(this.store, taskId), {
212561
212710
  preserveProgress: true,
212711
+ preserveWorktree: true,
212562
212712
  preserveStatus: true,
212563
212713
  // #1411: backward recovery — skip order-derived adjacency.
212564
212714
  moveSource: "engine",
@@ -212639,7 +212789,23 @@ var init_self_healing = __esm({
212639
212789
  }
212640
212790
  // ── Periodic maintenance ──────────────────────────────────────────
212641
212791
  async startMaintenance() {
212792
+ if (this.maintenanceInterval) return;
212793
+ if (this.maintenanceStarting) return;
212794
+ this.maintenanceStarting = true;
212795
+ try {
212796
+ await this.armMaintenanceInternal();
212797
+ } finally {
212798
+ this.maintenanceStarting = false;
212799
+ }
212800
+ }
212801
+ async armMaintenanceInternal() {
212802
+ if (this.maintenanceInterval) return;
212642
212803
  const settings = await this.store.getSettings();
212804
+ if (this.maintenanceInterval) return;
212805
+ if (settings.globalPause || settings.enginePaused) {
212806
+ log34.debug("Periodic maintenance not armed \u2014 engine/global pause is active");
212807
+ return;
212808
+ }
212643
212809
  const intervalMs = settings.maintenanceIntervalMs ?? 9e5;
212644
212810
  if (intervalMs <= 0) {
212645
212811
  log34.debug("Periodic maintenance disabled (maintenanceIntervalMs <= 0)");
@@ -212650,6 +212816,38 @@ var init_self_healing = __esm({
212650
212816
  void this.runMaintenance();
212651
212817
  }, intervalMs);
212652
212818
  }
212819
+ /*
212820
+ * FNXC:PauseGatedMaintenance 2026-08-13-03:08 (RUFU-076):
212821
+ * Keep the periodic maintenance timer in sync with the engine/global pause state. `onSettingsUpdated`
212822
+ * calls this on every settings:updated event; pausing clears the armed timer (stopping the git churn),
212823
+ * unpausing (re)arms it idempotently via `startMaintenance`'s guard. This is the only writer of the
212824
+ * pause->stop-maintenance transition besides `stop()` teardown.
212825
+ */
212826
+ /*
212827
+ * FNXC:GitWorktreeChurnCadenceCoarsen 2026-08-13-04:05 (RUFU-076):
212828
+ * Coarse-cadence gate for batch-1 git-churn steps. Returns true only when a full coarse window
212829
+ * (hourly) has elapsed since the last batch-1 git-worktree pass, so an idle/healthy active project
212830
+ * does not re-run `git worktree prune` / `git worktree list` / `git branch` on every maintenance
212831
+ * interval. Note the shared timestamp also bounds cleanup-orphaned-branches and enforce-worktree-cap
212832
+ * even though they live further down batch-1.
212833
+ */
212834
+ isGitWorktreeChurnDue() {
212835
+ return Date.now() - this.lastGitWorktreeChurnAt >= GIT_WORKTREE_CHURN_INTERVAL_MS;
212836
+ }
212837
+ syncMaintenanceOnRefresh(settings) {
212838
+ const paused = settings.globalPause || settings.enginePaused;
212839
+ if (paused) {
212840
+ if (this.maintenanceInterval) {
212841
+ clearInterval(this.maintenanceInterval);
212842
+ this.maintenanceInterval = null;
212843
+ log34.debug("Periodic maintenance paused \u2014 cleared maintenance timer");
212844
+ }
212845
+ return;
212846
+ }
212847
+ if (!this.maintenanceInterval) {
212848
+ void this.startMaintenance();
212849
+ }
212850
+ }
212653
212851
  /*
212654
212852
  FNXC:CrossProcessDeleteObservation 2026-08-01-11:39:
212655
212853
  Periodic self-healing owns outbox retention. Each already-open PostgreSQL project is gated to
@@ -212986,17 +213184,26 @@ var init_self_healing = __esm({
212986
213184
  log34.debug("Maintenance cycle starting");
212987
213185
  try {
212988
213186
  const settings = await this.store.getSettings();
213187
+ const maintenancePaused = settings.globalPause || settings.enginePaused;
213188
+ const gitWorktreeChurnDue = this.isGitWorktreeChurnDue();
213189
+ if (!maintenancePaused && gitWorktreeChurnDue) {
213190
+ this.lastGitWorktreeChurnAt = Date.now();
213191
+ }
212989
213192
  const batch1Fns = [
212990
- { name: "prune-worktrees", fn: () => this.pruneWorktrees() },
213193
+ {
213194
+ name: "prune-worktrees",
213195
+ fn: () => maintenancePaused || !gitWorktreeChurnDue ? Promise.resolve(0) : this.pruneWorktrees()
213196
+ },
212991
213197
  {
212992
213198
  name: "prune-task-lifecycle-events",
212993
213199
  fn: async () => this.pruneTaskLifecycleEventsForMaintenance()
212994
213200
  },
212995
213201
  { name: "prune-github-check-states", fn: async () => this.pruneGitHubCheckStatesForMaintenance() },
212996
- { name: "cleanup-orphans", fn: () => this.cleanupOrphans() },
213202
+ { name: "cleanup-orphans", fn: () => maintenancePaused || !gitWorktreeChurnDue ? Promise.resolve(0) : this.cleanupOrphans() },
212997
213203
  {
212998
213204
  name: "cleanup-stale-temp-merge-worktrees",
212999
213205
  fn: async () => {
213206
+ if (maintenancePaused || !gitWorktreeChurnDue) return 0;
213000
213207
  const cleaned = await this.cleanupStaleTempMergeWorktrees();
213001
213208
  if (cleaned > 0) {
213002
213209
  log34.debug(`Cleaned ${cleaned} stale AI merge temp worktree(s)`);
@@ -213004,7 +213211,10 @@ var init_self_healing = __esm({
213004
213211
  return cleaned;
213005
213212
  }
213006
213213
  },
213007
- { name: "cleanup-orphaned-branches", fn: () => this.cleanupOrphanedBranches() },
213214
+ {
213215
+ name: "cleanup-orphaned-branches",
213216
+ fn: () => maintenancePaused || !gitWorktreeChurnDue ? Promise.resolve(0) : this.cleanupOrphanedBranches()
213217
+ },
213008
213218
  {
213009
213219
  name: "reconcile-orphaned-task-dirs",
213010
213220
  fn: async () => {
@@ -213112,7 +213322,7 @@ var init_self_healing = __esm({
213112
213322
  },
213113
213323
  { name: "fts-maintenance", fn: () => this.maintainTaskFts() },
213114
213324
  { name: "checkpoint-wal", fn: () => Promise.resolve(this.checkpointWal()) },
213115
- { name: "enforce-worktree-cap", fn: () => this.enforceWorktreeCap() }
213325
+ { name: "enforce-worktree-cap", fn: () => maintenancePaused || !gitWorktreeChurnDue ? Promise.resolve(0) : this.enforceWorktreeCap() }
213116
213326
  ];
213117
213327
  for (const fn2 of batch1Fns) {
213118
213328
  try {
@@ -214394,6 +214604,7 @@ var init_self_healing = __esm({
214394
214604
  moveSource: "engine",
214395
214605
  // #1411: backward recovery — skip order-derived adjacency.
214396
214606
  recoveryRehome: true,
214607
+ // 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.
214397
214608
  preserveProgress: true,
214398
214609
  preserveResumeState: true
214399
214610
  });
@@ -214484,6 +214695,7 @@ var init_self_healing = __esm({
214484
214695
  moveSource: "engine",
214485
214696
  // #1411: backward recovery — skip order-derived adjacency.
214486
214697
  recoveryRehome: true,
214698
+ // 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.
214487
214699
  preserveProgress: true,
214488
214700
  preserveResumeState: true
214489
214701
  });
@@ -215280,7 +215492,18 @@ var init_self_healing = __esm({
215280
215492
  const withUniqueWork = existingCandidates.filter((candidate) => candidate.aheadCount > 0);
215281
215493
  if (withUniqueWork.length === 1) {
215282
215494
  const selected = withUniqueWork[0];
215283
- const patch = { branch: selected.branch, worktree: null };
215495
+ let preservedWorktree = false;
215496
+ if (task.worktree && existsSync59(task.worktree)) {
215497
+ try {
215498
+ const { stdout } = await execAsync18("git rev-parse --abbrev-ref HEAD", {
215499
+ cwd: task.worktree,
215500
+ timeout: 3e4
215501
+ });
215502
+ preservedWorktree = stdout.trim() === selected.branch;
215503
+ } catch {
215504
+ }
215505
+ }
215506
+ const patch = preservedWorktree ? { branch: selected.branch } : { branch: selected.branch, worktree: null };
215284
215507
  if (!task.baseCommitSha) {
215285
215508
  const derivedBaseCommit = (await execAsync18(
215286
215509
  `git merge-base ${shellQuote2(integrationBase)} ${shellQuote2(selected.branch)}`,
@@ -215319,7 +215542,8 @@ var init_self_healing = __esm({
215319
215542
  aheadCount: selected.aheadCount,
215320
215543
  integrationBase,
215321
215544
  source: "auto-rebind-in-review",
215322
- previousBranch: task.branch ?? null
215545
+ previousBranch: task.branch ?? null,
215546
+ preservedWorktree
215323
215547
  }
215324
215548
  });
215325
215549
  result2.repaired++;
@@ -215329,7 +215553,8 @@ var init_self_healing = __esm({
215329
215553
  branch: selected.branch,
215330
215554
  aheadCount: selected.aheadCount,
215331
215555
  integrationBase,
215332
- previousBranch: task.branch ?? null
215556
+ previousBranch: task.branch ?? null,
215557
+ preservedWorktree
215333
215558
  });
215334
215559
  continue;
215335
215560
  }
@@ -216861,7 +217086,8 @@ var init_self_healing = __esm({
216861
217086
  */
216862
217087
  recoveryRehome: true,
216863
217088
  bypassGuards: true,
216864
- preserveProgress: true
217089
+ preserveProgress: true,
217090
+ preserveWorktree: true
216865
217091
  });
216866
217092
  rehomed += 1;
216867
217093
  await createRunAuditor(this.store, {
@@ -217499,7 +217725,7 @@ var init_self_healing = __esm({
217499
217725
  await this.emitBackwardMoveNoAction(task, "finalize-no-op-review", "task:finalize-no-op-review-no-action", proof);
217500
217726
  continue;
217501
217727
  }
217502
- await this.store.moveTask(task.id, await resolveReboundTargetForTask(this.store, task.id), { preserveProgress: true, moveSource: "engine", recoveryRehome: true });
217728
+ await this.store.moveTask(task.id, await resolveReboundTargetForTask(this.store, task.id), { preserveProgress: true, preserveWorktree: true, moveSource: "engine", recoveryRehome: true });
217503
217729
  continue;
217504
217730
  }
217505
217731
  const mergedAt = (/* @__PURE__ */ new Date()).toISOString();
@@ -217549,7 +217775,7 @@ var init_self_healing = __esm({
217549
217775
  baseRef: classification.baseRef,
217550
217776
  lane: "self-healing-finalize-no-op-review"
217551
217777
  });
217552
- await this.store.moveTask(task.id, await resolveReboundTargetForTask(this.store, task.id), { preserveProgress: true, moveSource: "engine", recoveryRehome: true });
217778
+ await this.store.moveTask(task.id, await resolveReboundTargetForTask(this.store, task.id), { preserveProgress: true, preserveWorktree: true, moveSource: "engine", recoveryRehome: true });
217553
217779
  recovered++;
217554
217780
  continue;
217555
217781
  }
@@ -217568,7 +217794,7 @@ var init_self_healing = __esm({
217568
217794
  classification: "proven-no-op",
217569
217795
  baseRef: classification.baseRef
217570
217796
  });
217571
- await this.store.moveTask(task.id, await resolveReboundTargetForTask(this.store, task.id), { preserveProgress: true, moveSource: "engine", recoveryRehome: true });
217797
+ await this.store.moveTask(task.id, await resolveReboundTargetForTask(this.store, task.id), { preserveProgress: true, preserveWorktree: true, moveSource: "engine", recoveryRehome: true });
217572
217798
  recovered++;
217573
217799
  continue;
217574
217800
  }
@@ -218111,7 +218337,7 @@ var init_self_healing = __esm({
218111
218337
  task.id,
218112
218338
  "Auto-recovered: in-review task still had incomplete steps \u2014 moved back to todo for retry"
218113
218339
  );
218114
- await this.store.moveTask(task.id, await resolveReboundTargetForTask(this.store, task.id), { preserveProgress: true, moveSource: "engine", recoveryRehome: true });
218340
+ await this.store.moveTask(task.id, await resolveReboundTargetForTask(this.store, task.id), { preserveProgress: true, preserveWorktree: true, moveSource: "engine", recoveryRehome: true });
218115
218341
  log34.log(`Recovered stale incomplete review task ${task.id}: moved back to todo`);
218116
218342
  recovered++;
218117
218343
  } catch (err) {
@@ -218505,7 +218731,7 @@ var init_self_healing = __esm({
218505
218731
  task.id,
218506
218732
  "Auto-recovered: in-review task idle past stuck-task timeout \u2014 kicked back to todo"
218507
218733
  );
218508
- await this.store.moveTask(task.id, await resolveReboundTargetForTask(this.store, task.id), { preserveProgress: true, moveSource: "engine", recoveryRehome: true });
218734
+ await this.store.moveTask(task.id, await resolveReboundTargetForTask(this.store, task.id), { preserveProgress: true, preserveWorktree: true, moveSource: "engine", recoveryRehome: true });
218509
218735
  log34.log(`Kicked ghost review task ${task.id} back to todo`);
218510
218736
  recovered++;
218511
218737
  } catch (err) {
@@ -219429,8 +219655,9 @@ var init_self_healing = __esm({
219429
219655
  return Boolean(task.baseCommitSha);
219430
219656
  });
219431
219657
  if (candidates.length === 0) return 0;
219658
+ const batch = candidates.slice(0, DONE_METADATA_REPAIR_CAP);
219432
219659
  let repaired = 0;
219433
- for (const task of candidates) {
219660
+ for (const task of batch) {
219434
219661
  if (isWorkspaceTask(task)) continue;
219435
219662
  if (task.mergeDetails?.landedFilesAttributionRestricted || task.mergeDetails?.noOpVerifiedShortCircuit) {
219436
219663
  log34.debug(`recoverDoneTaskMergeMetadata: skipped ${task.id} \u2014 attribution-restricted`);
@@ -221087,7 +221314,7 @@ var init_self_healing = __esm({
221087
221314
  nextRecoveryAt: new Date(Date.now() + delayMs).toISOString()
221088
221315
  },
221089
221316
  targetColumn: await resolveReboundTargetForTask(this.store, task.id),
221090
- moveOptions: { preserveProgress: true, moveSource: "engine" }
221317
+ moveOptions: { preserveProgress: true, preserveWorktree: true, moveSource: "engine" }
221091
221318
  });
221092
221319
  if (applied.outcome === "applied") {
221093
221320
  await this.store.logEntry(task.id, `Auto-recovered generic terminal failure (attempt ${claim.attempt}/${MAX_TERMINAL_FAILURE_AUTO_RETRIES})`);
@@ -221229,6 +221456,7 @@ var init_self_healing = __esm({
221229
221456
  if (route.kind === "node-requeue" && fresh.column !== requeueTarget) {
221230
221457
  await this.store.moveTask(task.id, requeueTarget, {
221231
221458
  preserveProgress: true,
221459
+ preserveWorktree: true,
221232
221460
  moveSource: "engine",
221233
221461
  recoveryRehome: true
221234
221462
  });
@@ -221462,6 +221690,7 @@ var init_self_healing = __esm({
221462
221690
  const leaseRecovered = await this.options.leaseManager.recoverAbandonedLease(
221463
221691
  task.id,
221464
221692
  `in-progress limbo: ${describeWorktreeState(task)} + null branch`,
221693
+ // worktree-discard-intended: limbo recovery — the worktree is already missing or its metadata cleared; there is no live checkout to hold.
221465
221694
  { preserveProgress: true }
221466
221695
  );
221467
221696
  if (!leaseRecovered) {
@@ -222647,7 +222876,7 @@ var init_self_healing = __esm({
222647
222876
  task.id,
222648
222877
  `Auto-retry ${nextCount}/${MAX_TASK_DONE_RETRIES}: agent finished without fn_task_done \u2014 requeuing to todo to resume partial work`
222649
222878
  );
222650
- await this.store.moveTask(task.id, await resolveReboundTargetForTask(this.store, task.id), { preserveProgress: true, moveSource: "engine", recoveryRehome: true });
222879
+ await this.store.moveTask(task.id, await resolveReboundTargetForTask(this.store, task.id), { preserveProgress: true, preserveWorktree: true, moveSource: "engine", recoveryRehome: true });
222651
222880
  recovered++;
222652
222881
  } catch (err) {
222653
222882
  const errorMessage2 = err instanceof Error ? err.message : String(err);
@@ -260484,7 +260713,7 @@ async function ensureCommitTaskMetadata(mergeRoot, taskId, includeTaskId, traile
260484
260713
  const needsPrefix = includeTaskId && !subject.toLowerCase().startsWith(taskId.toLowerCase());
260485
260714
  const missingTrailers = trailers.filter((t) => !fullMessage.includes(t));
260486
260715
  if (!needsPrefix && missingTrailers.length === 0) return;
260487
- const args = ["-c", "trailer.ifExists=addIfDifferent", "commit", "--amend"];
260716
+ const args = [...commitIdentityArgs(resolveCommitIdentity()), "-c", "trailer.ifExists=addIfDifferent", "commit", "--amend"];
260488
260717
  if (needsPrefix) {
260489
260718
  args.push("-m", `${taskId}: ${subject}`);
260490
260719
  if (body.trim()) args.push("-m", body);
@@ -262119,6 +262348,7 @@ var execFileAsync25, aiMergeLog, MAX_CONCURRENT_ADVANCE_RETRIES, AiMergeBlockedE
262119
262348
  var init_merger_ai = __esm({
262120
262349
  "../engine/src/merge/merger-ai.ts"() {
262121
262350
  "use strict";
262351
+ init_git_identity();
262122
262352
  init_src2();
262123
262353
  init_agent_user_comments();
262124
262354
  init_worktree_names();
@@ -264897,7 +265127,7 @@ function defaultGitOps(cwd) {
264897
265127
  await runGit8(cwd, ["add", ...paths3]);
264898
265128
  },
264899
265129
  async commit(message) {
264900
- await runGit8(cwd, ["commit", "-m", JSON.stringify(message)]);
265130
+ await runGit8(cwd, [...commitIdentityArgs(resolveCommitIdentity()), "commit", "-m", JSON.stringify(message)]);
264901
265131
  return await runGit8(cwd, ["rev-parse", "HEAD"]);
264902
265132
  },
264903
265133
  async resetHard(ref) {
@@ -264975,6 +265205,7 @@ var execAsync55, GIT_TIMEOUT_MS10, GIT_MAX_BUFFER8;
264975
265205
  var init_git_ops = __esm({
264976
265206
  "../engine/src/experiment/git-ops.ts"() {
264977
265207
  "use strict";
265208
+ init_git_identity();
264978
265209
  init_finalize_types();
264979
265210
  execAsync55 = promisify81(exec59);
264980
265211
  GIT_TIMEOUT_MS10 = 3e4;