@runfusion/fusion 0.73.0-beta.3 → 0.73.0-beta.4

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 (279) hide show
  1. package/dist/bin.js +994 -292
  2. package/dist/child-process-worker.js +632 -153
  3. package/dist/client/.vite/manifest.json +256 -256
  4. package/dist/client/assets/{AgentDetailView-lJqxsPOR.js → AgentDetailView-BBOL6AgZ.js} +1 -1
  5. package/dist/client/assets/{AgentPermissionPolicyEditor-De4wK_UT.js → AgentPermissionPolicyEditor-DV7OPUhR.js} +1 -1
  6. package/dist/client/assets/{AgentsView-CwizRdcA.js → AgentsView-B6xoAHyV.js} +3 -3
  7. package/dist/client/assets/{ChatView-BeQXaz7_.js → ChatView-DwVjnxM8.js} +1 -1
  8. package/dist/client/assets/{CommandCenter-BJZCaPLj.js → CommandCenter-DYWaoYFD.js} +1 -1
  9. package/dist/client/assets/{DevServerView-DQgwQXKx.js → DevServerView-BY5up-NA.js} +1 -1
  10. package/dist/client/assets/{DirectoryPicker-B2UVwp6F.js → DirectoryPicker-fM8MJa2r.js} +1 -1
  11. package/dist/client/assets/{DocumentsView-DOeYrhvg.js → DocumentsView-D2KxsPG_.js} +1 -1
  12. package/dist/client/assets/{EvalsView-LwepJ9Ud.js → EvalsView-U8dOvTRM.js} +1 -1
  13. package/dist/client/assets/{ExperimentalAgentOnboardingModal-DVNSzflw.js → ExperimentalAgentOnboardingModal-C27y8Y-1.js} +1 -1
  14. package/dist/client/assets/{GoalsView-__LpXw4S.js → GoalsView-D-2wmy-O.js} +1 -1
  15. package/dist/client/assets/{InsightsView-Dv388uaO.js → InsightsView-zLyuQL_l.js} +1 -1
  16. package/dist/client/assets/{MemoryView-ChCbEfZT.js → MemoryView-D-tSn48u.js} +1 -1
  17. package/dist/client/assets/{PiExtensionsManager-DehAjrqN.js → PiExtensionsManager-DvwmvGEY.js} +1 -1
  18. package/dist/client/assets/PluginManager-BACOwQAN.js +1 -0
  19. package/dist/client/assets/{PullRequestView-BvmajsBq.js → PullRequestView-DULyv21u.js} +1 -1
  20. package/dist/client/assets/{ReportModal-DM9B_h_w.js → ReportModal-CUlKMFWa.js} +1 -1
  21. package/dist/client/assets/{ResearchView-BzXSgUhp.js → ResearchView-DM_O3IFc.js} +1 -1
  22. package/dist/client/assets/{SecretsView-COBqEdJj.js → SecretsView-Bq3u_nAf.js} +1 -1
  23. package/dist/client/assets/{SessionTerminal-BUw_FTlh.js → SessionTerminal-D00ByR6U.js} +1 -1
  24. package/dist/client/assets/SettingsModal-Bb3pIxiX.js +21 -0
  25. package/dist/client/assets/SettingsModal-CMLHZBhX.css +1 -0
  26. package/dist/client/assets/{SettingsModal-CsLaIrqt.js → SettingsModal-QRaBE1ds.js} +1 -1
  27. package/dist/client/assets/{SettingsTextareaRow-Bv3aZ9aU.js → SettingsTextareaRow-CHOJ-qHz.js} +1 -1
  28. package/dist/client/assets/{SetupWizardModal-C-yMHTlW.js → SetupWizardModal-DriQyd81.js} +2 -2
  29. package/dist/client/assets/{SkillsView-kSHgkBar.js → SkillsView-BtDyujiZ.js} +1 -1
  30. package/dist/client/assets/{TodoView-DYoicJNY.js → TodoView-BNHUnz50.js} +1 -1
  31. package/dist/client/assets/{WorkflowNodeEditor-BtWrziOX.css → WorkflowNodeEditor-BNgkFJ_P.css} +1 -1
  32. package/dist/client/assets/WorkflowNodeEditor-BXikFpra.js +8 -0
  33. package/dist/client/assets/{agent-import-generation-D6P8OUuV.js → agent-import-generation-B2kYEm1O.js} +1 -1
  34. package/dist/client/assets/{app-DegRNirF.js → app-B_HrdDXZ.js} +1 -1
  35. package/dist/client/assets/{app-BsIXfnu-.js → app-C6yo-M_n.js} +1 -1
  36. package/dist/client/assets/{app-B-IdUeIu.js → app-CH8ZgPm4.js} +1 -1
  37. package/dist/client/assets/{app-D9ktpVhR.js → app-D4DpgDss.js} +1 -1
  38. package/dist/client/assets/{app-nBTNvNKK.js → app-Qv0blCyY.js} +1 -1
  39. package/dist/client/assets/{app-C8muVNUU.js → app-kFdtajPy.js} +1 -1
  40. package/dist/client/assets/{architectureDiagram-3BPJPVTR-DLqKCckA.js → architectureDiagram-3BPJPVTR-B-Efjj4Z.js} +1 -1
  41. package/dist/client/assets/{blockDiagram-GPEHLZMM-DRz6F8wZ.js → blockDiagram-GPEHLZMM-CaOVxrlM.js} +1 -1
  42. package/dist/client/assets/{c4Diagram-AAUBKEIU-CJ9glQAp.js → c4Diagram-AAUBKEIU-D8aYt5F1.js} +1 -1
  43. package/dist/client/assets/channel-5bPK6pTS.js +1 -0
  44. package/dist/client/assets/{chunk-2J33WTMH-CjIBb3ln.js → chunk-2J33WTMH-VSDT0J0r.js} +1 -1
  45. package/dist/client/assets/{chunk-4BX2VUAB-DrcoEr1E.js → chunk-4BX2VUAB-Chx1wQgD.js} +1 -1
  46. package/dist/client/assets/{chunk-55IACEB6-DU-WcuA9.js → chunk-55IACEB6-MlqjhIJg.js} +1 -1
  47. package/dist/client/assets/{chunk-727SXJPM-C5Lklr3l.js → chunk-727SXJPM-BheQNUi8.js} +1 -1
  48. package/dist/client/assets/{chunk-AQP2D5EJ-DlyTTghY.js → chunk-AQP2D5EJ-C5EoJhfJ.js} +1 -1
  49. package/dist/client/assets/{chunk-FMBD7UC4-D3C65m5Y.js → chunk-FMBD7UC4-B8_8qP3j.js} +1 -1
  50. package/dist/client/assets/{chunk-ND2GUHAM-BZY-4WZF.js → chunk-ND2GUHAM-BuglCGRx.js} +1 -1
  51. package/dist/client/assets/{chunk-QZHKN3VN-CKUtTYy5.js → chunk-QZHKN3VN-B7_06dxp.js} +1 -1
  52. package/dist/client/assets/classDiagram-4FO5ZUOK-Dv9RQDqG.js +1 -0
  53. package/dist/client/assets/classDiagram-v2-Q7XG4LA2-Dv9RQDqG.js +1 -0
  54. package/dist/client/assets/{cose-bilkent-S5V4N54A-DbpsF4N5.js → cose-bilkent-S5V4N54A-Cm-ZOycx.js} +1 -1
  55. package/dist/client/assets/{dagre-BM42HDAG-BwIo4me1.js → dagre-BM42HDAG-Dj_Gwjpv.js} +1 -1
  56. package/dist/client/assets/{dashboard-view-C94UCci5.js → dashboard-view-B4CRL5Fy.js} +1 -1
  57. package/dist/client/assets/{dashboard-view-BvAsW6UZ.js → dashboard-view-noD9p0Zs.js} +1 -1
  58. package/dist/client/assets/{dashboard-view-BMQm7Lt_.js → dashboard-view-pXXSUxG9.js} +1 -1
  59. package/dist/client/assets/{diagram-2AECGRRQ-C-d8XY1c.js → diagram-2AECGRRQ-C_9BfShy.js} +1 -1
  60. package/dist/client/assets/{diagram-5GNKFQAL-ByRrlL9w.js → diagram-5GNKFQAL-Cmg2qpCj.js} +1 -1
  61. package/dist/client/assets/{diagram-KO2AKTUF-BK7jb_J-.js → diagram-KO2AKTUF-2FNo2HXb.js} +1 -1
  62. package/dist/client/assets/{diagram-LMA3HP47-u2o4bYk6.js → diagram-LMA3HP47-DgnVeCp-.js} +1 -1
  63. package/dist/client/assets/{diagram-OG6HWLK6-Dakk1V3D.js → diagram-OG6HWLK6-iAIR50HH.js} +1 -1
  64. package/dist/client/assets/{erDiagram-TEJ5UH35-Yp33v-Cf.js → erDiagram-TEJ5UH35-Da4I04eN.js} +1 -1
  65. package/dist/client/assets/{flowDiagram-I6XJVG4X-C4-dR2TA.js → flowDiagram-I6XJVG4X-Bv9r2T0m.js} +1 -1
  66. package/dist/client/assets/{folder-open-C9qq38qV.js → folder-open-CwWtrDh6.js} +1 -1
  67. package/dist/client/assets/{ganttDiagram-6RSMTGT7-C0CTCkFN.js → ganttDiagram-6RSMTGT7-BMeO84U_.js} +1 -1
  68. package/dist/client/assets/{gitGraphDiagram-PVQCEYII-DK0Dd5mm.js → gitGraphDiagram-PVQCEYII-CWDh_RIb.js} +1 -1
  69. package/dist/client/assets/index-CB3mYxAB.css +1 -0
  70. package/dist/client/assets/{index-BqLVwYoA.js → index-CE7C_XsS.js} +294 -294
  71. package/dist/client/assets/{infoDiagram-5YYISTIA-u5MwFb7j.js → infoDiagram-5YYISTIA-BAE4KtCL.js} +1 -1
  72. package/dist/client/assets/{ishikawaDiagram-YF4QCWOH-C7R6TziI.js → ishikawaDiagram-YF4QCWOH-C_iXAuOy.js} +1 -1
  73. package/dist/client/assets/{journeyDiagram-JHISSGLW-Dom8W5Ab.js → journeyDiagram-JHISSGLW-BnxSHwDo.js} +1 -1
  74. package/dist/client/assets/{kanban-definition-UN3LZRKU-_47C02t3.js → kanban-definition-UN3LZRKU-DYNRm3Nu.js} +1 -1
  75. package/dist/client/assets/{mermaid.core-Cd3LKosp.js → mermaid.core-B3hvDDep.js} +4 -4
  76. package/dist/client/assets/{mindmap-definition-RKZ34NQL-D-qVOEin.js → mindmap-definition-RKZ34NQL-s7KBEuPD.js} +1 -1
  77. package/dist/client/assets/{pieDiagram-4H26LBE5-B0TXvVbM.js → pieDiagram-4H26LBE5-Cy1_IPUD.js} +1 -1
  78. package/dist/client/assets/{puzzle-BBYTjDQK.js → puzzle-DWc6gFQ7.js} +1 -1
  79. package/dist/client/assets/{quadrantDiagram-W4KKPZXB-KcFvcZPm.js → quadrantDiagram-W4KKPZXB-DqgVGp41.js} +1 -1
  80. package/dist/client/assets/{requirementDiagram-4Y6WPE33-DEObokVc.js → requirementDiagram-4Y6WPE33-CA5-TDeF.js} +1 -1
  81. package/dist/client/assets/{sankeyDiagram-5OEKKPKP-BGYnEp22.js → sankeyDiagram-5OEKKPKP-Cuvi3RgE.js} +1 -1
  82. package/dist/client/assets/{sequenceDiagram-3UESZ5HK-BcYgyJRU.js → sequenceDiagram-3UESZ5HK-Da3GfmGP.js} +1 -1
  83. package/dist/client/assets/{shield-alert-CZg8yaFA.js → shield-alert-_iY63ED4.js} +1 -1
  84. package/dist/client/assets/{standing-instructions-template-BsTtbEqp.js → standing-instructions-template-CCd2YY9c.js} +1 -1
  85. package/dist/client/assets/{stateDiagram-AJRCARHV-CfZQIqmX.js → stateDiagram-AJRCARHV-CZ_I9ENR.js} +1 -1
  86. package/dist/client/assets/{stateDiagram-v2-BHNVJYJU-nBEPRSH7.js → stateDiagram-v2-BHNVJYJU-BomoRVhY.js} +1 -1
  87. package/dist/client/assets/{timeline-definition-PNZ67QCA-DySyabS_.js → timeline-definition-PNZ67QCA-C3CYvIuR.js} +1 -1
  88. package/dist/client/assets/{upload-DMd3zMLx.js → upload-D0RrO65v.js} +1 -1
  89. package/dist/client/assets/{users-9Pyvnn8K.js → users-CGszBY2v.js} +1 -1
  90. package/dist/client/assets/{vennDiagram-CIIHVFJN-DPomfxp_.js → vennDiagram-CIIHVFJN-By9fi8NW.js} +1 -1
  91. package/dist/client/assets/{wardley-L42UT6IY-BPfzngS8.js → wardley-L42UT6IY-DErnXPkI.js} +1 -1
  92. package/dist/client/assets/{wardleyDiagram-YWT4CUSO-Yp4nidAP.js → wardleyDiagram-YWT4CUSO-DFfXZVPk.js} +1 -1
  93. package/dist/client/assets/{xychartDiagram-2RQKCTM6-REgng603.js → xychartDiagram-2RQKCTM6-9Y5oZ5mi.js} +1 -1
  94. package/dist/client/index.html +4 -2
  95. package/dist/client/version.json +1 -1
  96. package/dist/extension.js +752 -207
  97. package/dist/migrations/0000_initial.sql +2 -0
  98. package/dist/migrations/0033_fn-8505_wedge_notification.sql +5 -0
  99. package/dist/plugin-sdk/index.js +1 -0
  100. package/dist/plugins/.fusion-ce-agents/.fusion-ce-upstream-provenance.json +7 -0
  101. package/dist/plugins/.fusion-ce-agents/ce-adversarial-document-reviewer.md +115 -0
  102. package/dist/plugins/.fusion-ce-agents/ce-adversarial-reviewer.md +111 -0
  103. package/dist/plugins/.fusion-ce-agents/ce-agent-native-planning-strategist.md +71 -0
  104. package/dist/plugins/.fusion-ce-agents/ce-agent-native-reviewer.md +181 -0
  105. package/dist/plugins/.fusion-ce-agents/ce-ankane-readme-writer.md +50 -0
  106. package/dist/plugins/.fusion-ce-agents/ce-api-contract-reviewer.md +52 -0
  107. package/dist/plugins/.fusion-ce-agents/ce-architecture-strategist.md +53 -0
  108. package/dist/plugins/.fusion-ce-agents/ce-best-practices-researcher.md +122 -0
  109. package/dist/plugins/.fusion-ce-agents/ce-code-simplicity-reviewer.md +87 -0
  110. package/dist/plugins/.fusion-ce-agents/ce-coherence-reviewer.md +73 -0
  111. package/dist/plugins/.fusion-ce-agents/ce-correctness-reviewer.md +52 -0
  112. package/dist/plugins/.fusion-ce-agents/ce-data-integrity-guardian.md +75 -0
  113. package/dist/plugins/.fusion-ce-agents/ce-data-migration-reviewer.md +119 -0
  114. package/dist/plugins/.fusion-ce-agents/ce-deployment-verification-agent.md +164 -0
  115. package/dist/plugins/.fusion-ce-agents/ce-design-implementation-reviewer.md +94 -0
  116. package/dist/plugins/.fusion-ce-agents/ce-design-iterator.md +197 -0
  117. package/dist/plugins/.fusion-ce-agents/ce-design-lens-reviewer.md +56 -0
  118. package/dist/plugins/.fusion-ce-agents/ce-feasibility-reviewer.md +65 -0
  119. package/dist/plugins/.fusion-ce-agents/ce-figma-design-sync.md +172 -0
  120. package/dist/plugins/.fusion-ce-agents/ce-framework-docs-researcher.md +100 -0
  121. package/dist/plugins/.fusion-ce-agents/ce-git-history-analyzer.md +47 -0
  122. package/dist/plugins/.fusion-ce-agents/ce-issue-intelligence-analyst.md +207 -0
  123. package/dist/plugins/.fusion-ce-agents/ce-julik-frontend-races-reviewer.md +52 -0
  124. package/dist/plugins/.fusion-ce-agents/ce-learnings-researcher.md +254 -0
  125. package/dist/plugins/.fusion-ce-agents/ce-maintainability-reviewer.md +77 -0
  126. package/dist/plugins/.fusion-ce-agents/ce-pattern-recognition-specialist.md +62 -0
  127. package/dist/plugins/.fusion-ce-agents/ce-performance-oracle.md +115 -0
  128. package/dist/plugins/.fusion-ce-agents/ce-performance-reviewer.md +54 -0
  129. package/dist/plugins/.fusion-ce-agents/ce-pr-comment-resolver.md +63 -0
  130. package/dist/plugins/.fusion-ce-agents/ce-previous-comments-reviewer.md +68 -0
  131. package/dist/plugins/.fusion-ce-agents/ce-product-lens-reviewer.md +92 -0
  132. package/dist/plugins/.fusion-ce-agents/ce-project-standards-reviewer.md +84 -0
  133. package/dist/plugins/.fusion-ce-agents/ce-reliability-reviewer.md +52 -0
  134. package/dist/plugins/.fusion-ce-agents/ce-repo-research-analyst.md +263 -0
  135. package/dist/plugins/.fusion-ce-agents/ce-scope-guardian-reviewer.md +79 -0
  136. package/dist/plugins/.fusion-ce-agents/ce-security-lens-reviewer.md +48 -0
  137. package/dist/plugins/.fusion-ce-agents/ce-security-reviewer.md +54 -0
  138. package/dist/plugins/.fusion-ce-agents/ce-security-sentinel.md +98 -0
  139. package/dist/plugins/.fusion-ce-agents/ce-session-historian.md +89 -0
  140. package/dist/plugins/.fusion-ce-agents/ce-slack-researcher.md +133 -0
  141. package/dist/plugins/.fusion-ce-agents/ce-spec-flow-analyzer.md +87 -0
  142. package/dist/plugins/.fusion-ce-agents/ce-swift-ios-reviewer.md +107 -0
  143. package/dist/plugins/.fusion-ce-agents/ce-testing-reviewer.md +52 -0
  144. package/dist/plugins/.fusion-ce-agents/ce-web-researcher.md +127 -0
  145. package/dist/plugins/.fusion-ce-skills/.fusion-ce-upstream-provenance.json +7 -0
  146. package/dist/plugins/.fusion-ce-skills/ce-brainstorm/SKILL.md +318 -0
  147. package/dist/plugins/.fusion-ce-skills/ce-brainstorm/references/agents/slack-researcher.md +127 -0
  148. package/dist/plugins/.fusion-ce-skills/ce-brainstorm/references/brainstorm-sections.md +354 -0
  149. package/dist/plugins/.fusion-ce-skills/ce-brainstorm/references/handoff.md +172 -0
  150. package/dist/plugins/.fusion-ce-skills/ce-brainstorm/references/html-rendering.md +662 -0
  151. package/dist/plugins/.fusion-ce-skills/ce-brainstorm/references/markdown-rendering.md +236 -0
  152. package/dist/plugins/.fusion-ce-skills/ce-brainstorm/references/synthesis-summary.md +271 -0
  153. package/dist/plugins/.fusion-ce-skills/ce-brainstorm/references/universal-brainstorming.md +71 -0
  154. package/dist/plugins/.fusion-ce-skills/ce-brainstorm/references/visual-probes.md +128 -0
  155. package/dist/plugins/.fusion-ce-skills/ce-brainstorm/scripts/visual-probe-server.js +419 -0
  156. package/dist/plugins/.fusion-ce-skills/ce-code-review/SKILL.md +821 -0
  157. package/dist/plugins/.fusion-ce-skills/ce-code-review/references/action-class-rubric.md +26 -0
  158. package/dist/plugins/.fusion-ce-skills/ce-code-review/references/bulk-preview.md +112 -0
  159. package/dist/plugins/.fusion-ce-skills/ce-code-review/references/cross-model-review.md +63 -0
  160. package/dist/plugins/.fusion-ce-skills/ce-code-review/references/diff-scope.md +41 -0
  161. package/dist/plugins/.fusion-ce-skills/ce-code-review/references/findings-schema.json +137 -0
  162. package/dist/plugins/.fusion-ce-skills/ce-code-review/references/persona-catalog.md +63 -0
  163. package/dist/plugins/.fusion-ce-skills/ce-code-review/references/personas/adversarial-reviewer.md +102 -0
  164. package/dist/plugins/.fusion-ce-skills/ce-code-review/references/personas/agent-native-reviewer.md +173 -0
  165. package/dist/plugins/.fusion-ce-skills/ce-code-review/references/personas/api-contract-reviewer.md +43 -0
  166. package/dist/plugins/.fusion-ce-skills/ce-code-review/references/personas/correctness-reviewer.md +43 -0
  167. package/dist/plugins/.fusion-ce-skills/ce-code-review/references/personas/data-migration-reviewer.md +111 -0
  168. package/dist/plugins/.fusion-ce-skills/ce-code-review/references/personas/deployment-verification-agent.md +157 -0
  169. package/dist/plugins/.fusion-ce-skills/ce-code-review/references/personas/julik-frontend-races-reviewer.md +44 -0
  170. package/dist/plugins/.fusion-ce-skills/ce-code-review/references/personas/learnings-researcher.md +247 -0
  171. package/dist/plugins/.fusion-ce-skills/ce-code-review/references/personas/maintainability-reviewer.md +68 -0
  172. package/dist/plugins/.fusion-ce-skills/ce-code-review/references/personas/performance-reviewer.md +45 -0
  173. package/dist/plugins/.fusion-ce-skills/ce-code-review/references/personas/previous-comments-reviewer.md +59 -0
  174. package/dist/plugins/.fusion-ce-skills/ce-code-review/references/personas/project-standards-reviewer.md +75 -0
  175. package/dist/plugins/.fusion-ce-skills/ce-code-review/references/personas/reliability-reviewer.md +43 -0
  176. package/dist/plugins/.fusion-ce-skills/ce-code-review/references/personas/security-reviewer.md +45 -0
  177. package/dist/plugins/.fusion-ce-skills/ce-code-review/references/personas/swift-ios-reviewer.md +99 -0
  178. package/dist/plugins/.fusion-ce-skills/ce-code-review/references/personas/testing-reviewer.md +43 -0
  179. package/dist/plugins/.fusion-ce-skills/ce-code-review/references/review-output-template.md +170 -0
  180. package/dist/plugins/.fusion-ce-skills/ce-code-review/references/subagent-template.md +199 -0
  181. package/dist/plugins/.fusion-ce-skills/ce-code-review/references/tracker-defer.md +149 -0
  182. package/dist/plugins/.fusion-ce-skills/ce-code-review/references/validator-template.md +89 -0
  183. package/dist/plugins/.fusion-ce-skills/ce-code-review/references/walkthrough.md +249 -0
  184. package/dist/plugins/.fusion-ce-skills/ce-code-review/scripts/cross-model-adversarial-review.sh +218 -0
  185. package/dist/plugins/.fusion-ce-skills/ce-commit/SKILL.md +105 -0
  186. package/dist/plugins/.fusion-ce-skills/ce-commit-push-pr/SKILL.md +134 -0
  187. package/dist/plugins/.fusion-ce-skills/ce-commit-push-pr/references/branch-creation.md +55 -0
  188. package/dist/plugins/.fusion-ce-skills/ce-commit-push-pr/references/pr-description-writing.md +115 -0
  189. package/dist/plugins/.fusion-ce-skills/ce-compound/SKILL.md +712 -0
  190. package/dist/plugins/.fusion-ce-skills/ce-compound/assets/resolution-template.md +94 -0
  191. package/dist/plugins/.fusion-ce-skills/ce-compound/references/agents/best-practices-researcher.md +115 -0
  192. package/dist/plugins/.fusion-ce-skills/ce-compound/references/agents/data-integrity-guardian.md +68 -0
  193. package/dist/plugins/.fusion-ce-skills/ce-compound/references/agents/framework-docs-researcher.md +93 -0
  194. package/dist/plugins/.fusion-ce-skills/ce-compound/references/agents/pattern-recognition-specialist.md +55 -0
  195. package/dist/plugins/.fusion-ce-skills/ce-compound/references/agents/performance-oracle.md +108 -0
  196. package/dist/plugins/.fusion-ce-skills/ce-compound/references/agents/security-sentinel.md +91 -0
  197. package/dist/plugins/.fusion-ce-skills/ce-compound/references/agents/session-historian.md +83 -0
  198. package/dist/plugins/.fusion-ce-skills/ce-compound/references/concepts-vocabulary.md +78 -0
  199. package/dist/plugins/.fusion-ce-skills/ce-compound/references/schema.yaml +231 -0
  200. package/dist/plugins/.fusion-ce-skills/ce-compound/references/yaml-schema.md +118 -0
  201. package/dist/plugins/.fusion-ce-skills/ce-compound/scripts/session-history/discover-sessions.sh +130 -0
  202. package/dist/plugins/.fusion-ce-skills/ce-compound/scripts/session-history/extract-errors.py +254 -0
  203. package/dist/plugins/.fusion-ce-skills/ce-compound/scripts/session-history/extract-metadata.py +456 -0
  204. package/dist/plugins/.fusion-ce-skills/ce-compound/scripts/session-history/extract-skeleton.py +570 -0
  205. package/dist/plugins/.fusion-ce-skills/ce-compound/scripts/validate-frontmatter.py +137 -0
  206. package/dist/plugins/.fusion-ce-skills/ce-debug/SKILL.md +257 -0
  207. package/dist/plugins/.fusion-ce-skills/ce-debug/references/anti-patterns.md +91 -0
  208. package/dist/plugins/.fusion-ce-skills/ce-debug/references/defense-in-depth.md +35 -0
  209. package/dist/plugins/.fusion-ce-skills/ce-debug/references/investigation-techniques.md +374 -0
  210. package/dist/plugins/.fusion-ce-skills/ce-doc-review/SKILL.md +70 -0
  211. package/dist/plugins/.fusion-ce-skills/ce-ideate/SKILL.md +401 -0
  212. package/dist/plugins/.fusion-ce-skills/ce-ideate/references/agents/issue-intelligence-analyst.md +200 -0
  213. package/dist/plugins/.fusion-ce-skills/ce-ideate/references/agents/learnings-researcher.md +247 -0
  214. package/dist/plugins/.fusion-ce-skills/ce-ideate/references/agents/slack-researcher.md +127 -0
  215. package/dist/plugins/.fusion-ce-skills/ce-ideate/references/agents/web-researcher.md +121 -0
  216. package/dist/plugins/.fusion-ce-skills/ce-ideate/references/divergent-ideation.md +89 -0
  217. package/dist/plugins/.fusion-ce-skills/ce-ideate/references/html-rendering.md +662 -0
  218. package/dist/plugins/.fusion-ce-skills/ce-ideate/references/ideation-sections.md +191 -0
  219. package/dist/plugins/.fusion-ce-skills/ce-ideate/references/markdown-rendering.md +236 -0
  220. package/dist/plugins/.fusion-ce-skills/ce-ideate/references/post-ideation-workflow.md +166 -0
  221. package/dist/plugins/.fusion-ce-skills/ce-ideate/references/universal-ideation.md +107 -0
  222. package/dist/plugins/.fusion-ce-skills/ce-ideate/references/web-research-cache.md +55 -0
  223. package/dist/plugins/.fusion-ce-skills/ce-plan/SKILL.md +858 -0
  224. package/dist/plugins/.fusion-ce-skills/ce-plan/references/agents/agent-native-planning-strategist.md +62 -0
  225. package/dist/plugins/.fusion-ce-skills/ce-plan/references/agents/architecture-strategist.md +46 -0
  226. package/dist/plugins/.fusion-ce-skills/ce-plan/references/agents/best-practices-researcher.md +114 -0
  227. package/dist/plugins/.fusion-ce-skills/ce-plan/references/agents/data-integrity-guardian.md +68 -0
  228. package/dist/plugins/.fusion-ce-skills/ce-plan/references/agents/data-migration-reviewer.md +103 -0
  229. package/dist/plugins/.fusion-ce-skills/ce-plan/references/agents/deployment-verification-agent.md +157 -0
  230. package/dist/plugins/.fusion-ce-skills/ce-plan/references/agents/framework-docs-researcher.md +93 -0
  231. package/dist/plugins/.fusion-ce-skills/ce-plan/references/agents/git-history-analyzer.md +40 -0
  232. package/dist/plugins/.fusion-ce-skills/ce-plan/references/agents/learnings-researcher.md +247 -0
  233. package/dist/plugins/.fusion-ce-skills/ce-plan/references/agents/pattern-recognition-specialist.md +55 -0
  234. package/dist/plugins/.fusion-ce-skills/ce-plan/references/agents/performance-oracle.md +108 -0
  235. package/dist/plugins/.fusion-ce-skills/ce-plan/references/agents/repo-research-analyst.md +256 -0
  236. package/dist/plugins/.fusion-ce-skills/ce-plan/references/agents/security-sentinel.md +91 -0
  237. package/dist/plugins/.fusion-ce-skills/ce-plan/references/agents/slack-researcher.md +127 -0
  238. package/dist/plugins/.fusion-ce-skills/ce-plan/references/agents/spec-flow-analyzer.md +80 -0
  239. package/dist/plugins/.fusion-ce-skills/ce-plan/references/agents/web-researcher.md +121 -0
  240. package/dist/plugins/.fusion-ce-skills/ce-plan/references/approach-altitude.md +55 -0
  241. package/dist/plugins/.fusion-ce-skills/ce-plan/references/deepening-workflow.md +259 -0
  242. package/dist/plugins/.fusion-ce-skills/ce-plan/references/html-rendering.md +668 -0
  243. package/dist/plugins/.fusion-ce-skills/ce-plan/references/markdown-rendering.md +236 -0
  244. package/dist/plugins/.fusion-ce-skills/ce-plan/references/plan-handoff.md +126 -0
  245. package/dist/plugins/.fusion-ce-skills/ce-plan/references/plan-sections.md +405 -0
  246. package/dist/plugins/.fusion-ce-skills/ce-plan/references/synthesis-summary.md +396 -0
  247. package/dist/plugins/.fusion-ce-skills/ce-plan/references/universal-planning.md +168 -0
  248. package/dist/plugins/.fusion-ce-skills/ce-resolve-pr-feedback/SKILL.md +53 -0
  249. package/dist/plugins/.fusion-ce-skills/ce-resolve-pr-feedback/references/agents/pr-comment-resolver.md +56 -0
  250. package/dist/plugins/.fusion-ce-skills/ce-resolve-pr-feedback/references/evaluation-rubric.md +106 -0
  251. package/dist/plugins/.fusion-ce-skills/ce-resolve-pr-feedback/references/full-mode.md +283 -0
  252. package/dist/plugins/.fusion-ce-skills/ce-resolve-pr-feedback/references/targeted-mode.md +45 -0
  253. package/dist/plugins/.fusion-ce-skills/ce-resolve-pr-feedback/scripts/get-pr-comments +159 -0
  254. package/dist/plugins/.fusion-ce-skills/ce-resolve-pr-feedback/scripts/get-thread-for-comment +76 -0
  255. package/dist/plugins/.fusion-ce-skills/ce-resolve-pr-feedback/scripts/reply-to-pr-thread +33 -0
  256. package/dist/plugins/.fusion-ce-skills/ce-resolve-pr-feedback/scripts/resolve-pr-thread +23 -0
  257. package/dist/plugins/.fusion-ce-skills/ce-strategy/SKILL.md +97 -0
  258. package/dist/plugins/.fusion-ce-skills/ce-strategy/references/interview.md +143 -0
  259. package/dist/plugins/.fusion-ce-skills/ce-strategy/references/strategy-template.md +89 -0
  260. package/dist/plugins/.fusion-ce-skills/ce-work/SKILL.md +429 -0
  261. package/dist/plugins/.fusion-ce-skills/ce-work/references/agents/figma-design-sync.md +165 -0
  262. package/dist/plugins/.fusion-ce-skills/ce-work/references/execution-engines.md +85 -0
  263. package/dist/plugins/.fusion-ce-skills/ce-work/references/non-code-execution.md +23 -0
  264. package/dist/plugins/.fusion-ce-skills/ce-work/references/review-findings-followup.md +104 -0
  265. package/dist/plugins/.fusion-ce-skills/ce-work/references/shipping-workflow.md +133 -0
  266. package/dist/plugins/.fusion-ce-skills/ce-work/references/tracker-defer.md +149 -0
  267. package/dist/plugins/fusion-plugin-compound-engineering/.bundled.reload-3.js +11266 -0
  268. package/dist/plugins/fusion-plugin-compound-engineering/.bundled.reload-4.js +11266 -0
  269. package/dist/plugins/fusion-plugin-dependency-graph/.bundled.reload-1.js +8206 -0
  270. package/dist/plugins/fusion-plugin-grok-runtime/.bundled.reload-2.js +26623 -0
  271. package/package.json +1 -1
  272. package/dist/client/assets/PluginManager-DBhJ1i27.js +0 -1
  273. package/dist/client/assets/SettingsModal-BDgH9XID.js +0 -21
  274. package/dist/client/assets/SettingsModal-BXDnzDkJ.css +0 -1
  275. package/dist/client/assets/WorkflowNodeEditor-DLno6bSJ.js +0 -8
  276. package/dist/client/assets/channel-Dj4nOgJO.js +0 -1
  277. package/dist/client/assets/classDiagram-4FO5ZUOK-DnkFo5Qy.js +0 -1
  278. package/dist/client/assets/classDiagram-v2-Q7XG4LA2-DnkFo5Qy.js +0 -1
  279. package/dist/client/assets/index-6FBJPKkr.css +0 -1
package/dist/bin.js CHANGED
@@ -908,6 +908,7 @@ var init_workflow_steps = __esm({
908
908
  "in-review",
909
909
  "merged",
910
910
  "failed",
911
+ "task-wedged",
911
912
  "awaiting-approval",
912
913
  "awaiting-user-review",
913
914
  "planning-awaiting-input",
@@ -981,6 +982,50 @@ var init_documents_artifacts = __esm({
981
982
  }
982
983
  });
983
984
 
985
+ // ../core/src/types/task-core.ts
986
+ function assertNotWorkspaceTaskMerge(task) {
987
+ if (isWorkspaceTask(task)) {
988
+ throw new WorkspaceTaskMergeError(
989
+ `Workspace task ${task.id} cannot merge until per-repo merge support (master-plan U6) lands`
990
+ );
991
+ }
992
+ }
993
+ function isWorkspaceTask(task) {
994
+ const worktrees = task.workspaceWorktrees;
995
+ return !!worktrees && Object.keys(worktrees).length > 0;
996
+ }
997
+ var CheckoutConflictError, DUPLICATE_OF_METADATA_KEY, WorkspaceTaskMergeError;
998
+ var init_task_core = __esm({
999
+ "../core/src/types/task-core.ts"() {
1000
+ "use strict";
1001
+ CheckoutConflictError = class extends Error {
1002
+ constructor(taskId, currentHolderId, requestedById) {
1003
+ super(`Task ${taskId} is already checked out by agent ${currentHolderId}`);
1004
+ this.taskId = taskId;
1005
+ this.currentHolderId = currentHolderId;
1006
+ this.requestedById = requestedById;
1007
+ this.name = "CheckoutConflictError";
1008
+ }
1009
+ };
1010
+ DUPLICATE_OF_METADATA_KEY = "duplicateOfTaskIds";
1011
+ WorkspaceTaskMergeError = class extends Error {
1012
+ constructor(message) {
1013
+ super(message);
1014
+ this.name = "WorkspaceTaskMergeError";
1015
+ }
1016
+ };
1017
+ }
1018
+ });
1019
+
1020
+ // ../core/src/types/todo-list.ts
1021
+ var SHARED_STATE_SNAPSHOT_VERSION;
1022
+ var init_todo_list = __esm({
1023
+ "../core/src/types/todo-list.ts"() {
1024
+ "use strict";
1025
+ SHARED_STATE_SNAPSHOT_VERSION = 1;
1026
+ }
1027
+ });
1028
+
984
1029
  // ../core/src/task-helpers.ts
985
1030
  function getPrimaryPrInfo(task) {
986
1031
  return task.prInfos?.[0] ?? task.prInfo;
@@ -1470,10 +1515,18 @@ var init_settings_schema = __esm({
1470
1515
  "use strict";
1471
1516
  init_in_review_stall();
1472
1517
  DEFAULT_GLOBAL_SETTINGS = {
1473
- // Embedded PostgreSQL is shared by all local Fusion projects and processes.
1474
- // Keep this well above the conservative external-pool budget while still
1475
- // bounded for a local machine.
1476
- embeddedPostgresMaxConnections: 500,
1518
+ /*
1519
+ FNXC:PostgresEmbedded 2026-07-22-23:55:
1520
+ Embedded PostgreSQL is shared by all local Fusion projects and processes.
1521
+ Deliberately undefined (not 500): getSettings() merges these defaults, so a
1522
+ concrete value here would be indistinguishable from an operator choice and
1523
+ would mask the platform-aware server-side default in
1524
+ resolveEmbeddedMaxConnections (win32 150, else 500). Issue #2411: Windows
1525
+ backends are separate processes; a 500-connection cap exhausts the desktop
1526
+ heap under load and forked backends die with 0xC0000142, taking the embedded
1527
+ cluster and dashboard down.
1528
+ */
1529
+ embeddedPostgresMaxConnections: void 0,
1477
1530
  /*
1478
1531
  FNXC:DashboardTheming 2026-07-03-00:00:
1479
1532
  Fresh installs must follow the operating system theme until the user explicitly chooses Light, Dark, or System. Keep this global default aligned with dashboard and desktop pre-hydration fallbacks.
@@ -2236,6 +2289,53 @@ var init_settings_schema = __esm({
2236
2289
  }
2237
2290
  });
2238
2291
 
2292
+ // ../core/src/types/settings-scope.ts
2293
+ function isMcpSecretRef(value) {
2294
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
2295
+ const candidate = value;
2296
+ return typeof candidate.secretRef === "string" && candidate.secretRef.trim().length > 0 && (candidate.scope === "project" || candidate.scope === "global");
2297
+ }
2298
+ var init_settings_scope = __esm({
2299
+ "../core/src/types/settings-scope.ts"() {
2300
+ "use strict";
2301
+ init_settings_schema();
2302
+ }
2303
+ });
2304
+
2305
+ // ../core/src/types/board-config.ts
2306
+ var COLUMN_LABELS, COLUMN_DESCRIPTIONS, VALID_TRANSITIONS;
2307
+ var init_board_config = __esm({
2308
+ "../core/src/types/board-config.ts"() {
2309
+ "use strict";
2310
+ COLUMN_LABELS = {
2311
+ triage: "Planning",
2312
+ todo: "Todo",
2313
+ "in-progress": "In Progress",
2314
+ "in-review": "In Review",
2315
+ done: "Done",
2316
+ archived: "Archived"
2317
+ };
2318
+ COLUMN_DESCRIPTIONS = {
2319
+ triage: "Raw ideas \u2014 AI will plan these",
2320
+ "in-progress": "AI is working on this in a worktree",
2321
+ done: "Merged and closed",
2322
+ archived: "Completed and archived"
2323
+ };
2324
+ VALID_TRANSITIONS = {
2325
+ // FN-4892: intake-side heuristics may cold-archive tasks before execution starts.
2326
+ triage: ["todo", "archived"],
2327
+ // FN-4892: allow direct archival for newly specified intake tasks.
2328
+ todo: ["in-progress", "triage", "archived"],
2329
+ // NOTE: "in-progress" → "done" is enabled for mission validation tasks that complete directly.
2330
+ // Regular implementation tasks should move through "in-review" before "done".
2331
+ "in-progress": ["in-review", "todo", "triage", "done"],
2332
+ "in-review": ["done", "in-progress", "todo", "triage"],
2333
+ done: ["todo", "triage", "archived"],
2334
+ archived: ["done"]
2335
+ };
2336
+ }
2337
+ });
2338
+
2239
2339
  // ../core/src/types/docker-nodes.ts
2240
2340
  function validateDockerNodeConfig(config3) {
2241
2341
  const errors = [];
@@ -2346,6 +2446,41 @@ var init_docker_nodes = __esm({
2346
2446
  }
2347
2447
  });
2348
2448
 
2449
+ // ../core/src/types/archive-planning.ts
2450
+ function formatPlanningPlanMd(summary) {
2451
+ const normalizeListItem = (item) => item.replace(/\s+/g, " ").trim();
2452
+ const list = (items) => items && items.length > 0 ? items.map((item) => `- ${normalizeListItem(item)}`).join("\n") : "_None_";
2453
+ const proposedChanges = list(summary.proposedChanges);
2454
+ const acceptanceCriteria = list(summary.acceptanceCriteria);
2455
+ const dependencies = list(summary.suggestedDependencies);
2456
+ const deliverables = list(summary.keyDeliverables);
2457
+ return `# ${summary.title}
2458
+
2459
+ ${summary.description}
2460
+
2461
+ ## What to change
2462
+ ${proposedChanges}
2463
+
2464
+ ## Acceptance criteria
2465
+ ${acceptanceCriteria}
2466
+
2467
+ ## Size
2468
+ ${summary.suggestedSize}
2469
+
2470
+ ## Suggested dependencies
2471
+ ${dependencies}
2472
+
2473
+ ## Key deliverables
2474
+ ${deliverables}
2475
+ `;
2476
+ }
2477
+ var init_archive_planning = __esm({
2478
+ "../core/src/types/archive-planning.ts"() {
2479
+ "use strict";
2480
+ init_docker_nodes();
2481
+ }
2482
+ });
2483
+
2349
2484
  // ../core/src/types/agent-state.ts
2350
2485
  function isEphemeralAgent(agent) {
2351
2486
  const metadata = agent.metadata ?? {};
@@ -4071,49 +4206,6 @@ var init_workflow_lifecycle_validation = __esm({
4071
4206
  });
4072
4207
 
4073
4208
  // ../core/src/types.ts
4074
- function assertNotWorkspaceTaskMerge(task) {
4075
- if (isWorkspaceTask(task)) {
4076
- throw new WorkspaceTaskMergeError(
4077
- `Workspace task ${task.id} cannot merge until per-repo merge support (master-plan U6) lands`
4078
- );
4079
- }
4080
- }
4081
- function isWorkspaceTask(task) {
4082
- const worktrees = task.workspaceWorktrees;
4083
- return !!worktrees && Object.keys(worktrees).length > 0;
4084
- }
4085
- function isMcpSecretRef(value) {
4086
- if (!value || typeof value !== "object" || Array.isArray(value)) return false;
4087
- const candidate = value;
4088
- return typeof candidate.secretRef === "string" && candidate.secretRef.trim().length > 0 && (candidate.scope === "project" || candidate.scope === "global");
4089
- }
4090
- function formatPlanningPlanMd(summary) {
4091
- const normalizeListItem = (item) => item.replace(/\s+/g, " ").trim();
4092
- const list = (items) => items && items.length > 0 ? items.map((item) => `- ${normalizeListItem(item)}`).join("\n") : "_None_";
4093
- const proposedChanges = list(summary.proposedChanges);
4094
- const acceptanceCriteria = list(summary.acceptanceCriteria);
4095
- const dependencies = list(summary.suggestedDependencies);
4096
- const deliverables = list(summary.keyDeliverables);
4097
- return `# ${summary.title}
4098
-
4099
- ${summary.description}
4100
-
4101
- ## What to change
4102
- ${proposedChanges}
4103
-
4104
- ## Acceptance criteria
4105
- ${acceptanceCriteria}
4106
-
4107
- ## Size
4108
- ${summary.suggestedSize}
4109
-
4110
- ## Suggested dependencies
4111
- ${dependencies}
4112
-
4113
- ## Key deliverables
4114
- ${deliverables}
4115
- `;
4116
- }
4117
4209
  function validateMessageMetadata(metadata) {
4118
4210
  if (!metadata) {
4119
4211
  return;
@@ -4165,7 +4257,7 @@ function validateMessageMetadata(metadata) {
4165
4257
  if (metadata.proposalStatus === "pending" && (metadata.claimOwnerToken !== void 0 || metadata.claimStartedAt !== void 0)) throw new Error("pending proposal cannot have a creation lease");
4166
4258
  }
4167
4259
  }
4168
- var DEPRECATED_BUILTIN_WORKFLOW_IDS, CheckoutConflictError, DUPLICATE_OF_METADATA_KEY, WorkspaceTaskMergeError, SHARED_STATE_SNAPSHOT_VERSION, COLUMN_LABELS, COLUMN_DESCRIPTIONS, VALID_TRANSITIONS;
4260
+ var DEPRECATED_BUILTIN_WORKFLOW_IDS;
4169
4261
  var init_types = __esm({
4170
4262
  "../core/src/types.ts"() {
4171
4263
  "use strict";
@@ -4179,8 +4271,11 @@ var init_types = __esm({
4179
4271
  init_merge_policy();
4180
4272
  init_workflow_steps();
4181
4273
  init_documents_artifacts();
4182
- init_settings_schema();
4183
- init_docker_nodes();
4274
+ init_task_core();
4275
+ init_todo_list();
4276
+ init_settings_scope();
4277
+ init_board_config();
4278
+ init_archive_planning();
4184
4279
  init_agent_state();
4185
4280
  init_planner_intervention();
4186
4281
  init_agents();
@@ -4195,49 +4290,6 @@ var init_types = __esm({
4195
4290
  DEPRECATED_BUILTIN_WORKFLOW_IDS = /* @__PURE__ */ new Set([
4196
4291
  "builtin:brainstorming"
4197
4292
  ]);
4198
- CheckoutConflictError = class extends Error {
4199
- constructor(taskId, currentHolderId, requestedById) {
4200
- super(`Task ${taskId} is already checked out by agent ${currentHolderId}`);
4201
- this.taskId = taskId;
4202
- this.currentHolderId = currentHolderId;
4203
- this.requestedById = requestedById;
4204
- this.name = "CheckoutConflictError";
4205
- }
4206
- };
4207
- DUPLICATE_OF_METADATA_KEY = "duplicateOfTaskIds";
4208
- WorkspaceTaskMergeError = class extends Error {
4209
- constructor(message) {
4210
- super(message);
4211
- this.name = "WorkspaceTaskMergeError";
4212
- }
4213
- };
4214
- SHARED_STATE_SNAPSHOT_VERSION = 1;
4215
- COLUMN_LABELS = {
4216
- triage: "Planning",
4217
- todo: "Todo",
4218
- "in-progress": "In Progress",
4219
- "in-review": "In Review",
4220
- done: "Done",
4221
- archived: "Archived"
4222
- };
4223
- COLUMN_DESCRIPTIONS = {
4224
- triage: "Raw ideas \u2014 AI will plan these",
4225
- "in-progress": "AI is working on this in a worktree",
4226
- done: "Merged and closed",
4227
- archived: "Completed and archived"
4228
- };
4229
- VALID_TRANSITIONS = {
4230
- // FN-4892: intake-side heuristics may cold-archive tasks before execution starts.
4231
- triage: ["todo", "archived"],
4232
- // FN-4892: allow direct archival for newly specified intake tasks.
4233
- todo: ["in-progress", "triage", "archived"],
4234
- // NOTE: "in-progress" → "done" is enabled for mission validation tasks that complete directly.
4235
- // Regular implementation tasks should move through "in-review" before "done".
4236
- "in-progress": ["in-review", "todo", "triage", "done"],
4237
- "in-review": ["done", "in-progress", "todo", "triage"],
4238
- done: ["todo", "triage", "archived"],
4239
- archived: ["done"]
4240
- };
4241
4293
  }
4242
4294
  });
4243
4295
 
@@ -12858,6 +12910,7 @@ var init_project = __esm({
12858
12910
  paused: integer("paused").default(0),
12859
12911
  userPaused: integer("user_paused").default(0),
12860
12912
  pausedReason: text("paused_reason"),
12913
+ wedgeNotification: text("wedge_notification"),
12861
12914
  baseBranch: text("base_branch"),
12862
12915
  branch: text("branch"),
12863
12916
  autoMerge: integer("auto_merge"),
@@ -21222,6 +21275,9 @@ function validateColumns(ir2) {
21222
21275
  throw new WorkflowIrError(`Workflow IR has duplicate column id '${column.id}'`);
21223
21276
  }
21224
21277
  seen.add(column.id);
21278
+ if (column.description !== void 0 && typeof column.description !== "string") {
21279
+ throw new WorkflowIrError(`Workflow IR column '${column.id}' description must be a string`);
21280
+ }
21225
21281
  if (!Array.isArray(column.traits)) {
21226
21282
  throw new WorkflowIrError(`Workflow IR column '${column.id}' traits must be an array`);
21227
21283
  }
@@ -36445,6 +36501,7 @@ async function applySchemaBaseline(db, options = {}) {
36445
36501
  const legacyAdoptionDrainedMarkerRuntimeGrantsAlreadyApplied = applied.includes(
36446
36502
  LEGACY_ADOPTION_DRAINED_MARKER_RUNTIME_GRANTS_VERSION
36447
36503
  );
36504
+ const taskWedgeNotificationAlreadyApplied = applied.includes(TASK_WEDGE_NOTIFICATION_VERSION);
36448
36505
  assertBinaryNotOlderThanDatabase(applied);
36449
36506
  let schemaChanged = false;
36450
36507
  if (!baselineAlreadyApplied) {
@@ -36768,17 +36825,25 @@ async function applySchemaBaseline(db, options = {}) {
36768
36825
  );
36769
36826
  schemaChanged = true;
36770
36827
  }
36828
+ if (!taskWedgeNotificationAlreadyApplied) {
36829
+ const migrationSql = await readFile4(TASK_WEDGE_NOTIFICATION_MIGRATION_PATH, "utf8");
36830
+ await tx.execute(sql.raw(migrationSql));
36831
+ await tx.execute(
36832
+ sql`INSERT INTO public.${sql.identifier(MIGRATION_BOOKKEEPING_TABLE)} (version) VALUES (${TASK_WEDGE_NOTIFICATION_VERSION}) ON CONFLICT (version) DO NOTHING`
36833
+ );
36834
+ schemaChanged = true;
36835
+ }
36771
36836
  return { applied: schemaChanged, pluginHooksRun: pluginHooks.length };
36772
36837
  });
36773
36838
  }
36774
- var SCHEMA_BASELINE_VERSION, TASK_DECLARED_SYMBOLS_VERSION, INITIAL_SCHEMA_VERSION, AUTOMATION_ISOLATION_SCHEMA_VERSION, ANALYTICS_ISOLATION_SCHEMA_VERSION, MONITOR_APPROVAL_ISOLATION_SCHEMA_VERSION, LEGACY_CUTOVER_PRESERVATION_SCHEMA_VERSION, MULTI_PROJECT_CUTOVER_SCHEMA_VERSION, PROJECT_OWNERSHIP_SCHEMA_VERSION, SQLITE_SCHEMA_PARITY_VERSION, SESSION_ADVISOR_ENABLED_SCHEMA_VERSION, MISSION_FIX_IDEMPOTENCY_VERSION, IMPORT_TRANSLATION_CACHE_VERSION, IMPORT_TRANSLATION_CACHE_SCOPE_FIX_VERSION, IMPORT_TRANSLATION_CACHE_LEGACY_PARTITION_BACKFILL_VERSION, OWNER_PROJECT_ID_SPLIT_VERSION, CHAT_SESSION_PINS_VERSION, EXECUTOR_TOOL_FAILURE_RETRY_VERSION, EXECUTOR_ESCALATION_ATTEMPT_VERSION, GLOBAL_ROUTINES_SCHEMA_VERSION, TASK_MERGER_MODEL_LANE_VERSION, BULK_COMPLETION_REFUSAL_AT_VERSION, TASK_PROPOSAL_CLAIM_VERSION, CONFIGURATION_REVISIONS_VERSION, IDEATION_SCHEMA_VERSION, RESEARCH_FEATURE_PROVENANCE_VERSION, TASK_VERIFICATION_REQUEST_VERSION, SYMBOL_LOCKS_SCHEMA_VERSION, BIGINT_COUNTERS_VERSION, PLANNING_ACTIVE_TIMING_VERSION, SQLITE_MIGRATION_RUNTIME_READ_VERSION, WORKFLOW_TASK_CONTINUATIONS_VERSION, LEGACY_ADOPTION_DRAINED_MARKER_RUNTIME_GRANTS_VERSION, LEGACY_ADOPTION_DRAINED_MARKER_FUNCTION, StaleBinarySchemaError, MIGRATION_BOOKKEEPING_TABLE, LEGACY_ADOPTION_DRAINED_MARKER, __dirname, MIGRATIONS_DIR, BASELINE_MIGRATION_PATH, AUTOMATION_ISOLATION_MIGRATION_PATH, ANALYTICS_ISOLATION_MIGRATION_PATH, MONITOR_APPROVAL_ISOLATION_MIGRATION_PATH, LEGACY_CUTOVER_PRESERVATION_MIGRATION_PATH, MULTI_PROJECT_CUTOVER_MIGRATION_PATH, PROJECT_OWNERSHIP_MIGRATION_PATH, SQLITE_SCHEMA_PARITY_MIGRATION_PATH, SESSION_ADVISOR_ENABLED_MIGRATION_PATH, MISSION_FIX_IDEMPOTENCY_MIGRATION_PATH, IMPORT_TRANSLATION_CACHE_MIGRATION_PATH, IMPORT_TRANSLATION_CACHE_SCOPE_FIX_MIGRATION_PATH, IMPORT_TRANSLATION_CACHE_LEGACY_PARTITION_BACKFILL_MIGRATION_PATH, OWNER_PROJECT_ID_SPLIT_MIGRATION_PATH, CHAT_SESSION_PINS_MIGRATION_PATH, EXECUTOR_TOOL_FAILURE_RETRY_MIGRATION_PATH, EXECUTOR_ESCALATION_ATTEMPT_MIGRATION_PATH, GLOBAL_ROUTINES_MIGRATION_PATH, TASK_MERGER_MODEL_LANE_MIGRATION_PATH, BULK_COMPLETION_REFUSAL_AT_MIGRATION_PATH, TASK_PROPOSAL_CLAIM_MIGRATION_PATH, CONFIGURATION_REVISIONS_MIGRATION_PATH, IDEATION_MIGRATION_PATH, RESEARCH_FEATURE_PROVENANCE_MIGRATION_PATH, TASK_VERIFICATION_REQUEST_MIGRATION_PATH, SYMBOL_LOCKS_MIGRATION_PATH, BIGINT_COUNTERS_MIGRATION_PATH, WORKFLOW_IR_PIN_AND_LEGACY_ADOPTION_VERSION, WORKFLOW_IR_PIN_AND_LEGACY_ADOPTION_MIGRATION_PATH, PLANNING_ACTIVE_TIMING_MIGRATION_PATH, TASK_DECLARED_SYMBOLS_MIGRATION_PATH, SQLITE_MIGRATION_RUNTIME_READ_PATH, WORKFLOW_TASK_CONTINUATIONS_PATH, LEGACY_ADOPTION_DRAINED_MARKER_RUNTIME_GRANTS_PATH;
36839
+ var SCHEMA_BASELINE_VERSION, TASK_DECLARED_SYMBOLS_VERSION, INITIAL_SCHEMA_VERSION, AUTOMATION_ISOLATION_SCHEMA_VERSION, ANALYTICS_ISOLATION_SCHEMA_VERSION, MONITOR_APPROVAL_ISOLATION_SCHEMA_VERSION, LEGACY_CUTOVER_PRESERVATION_SCHEMA_VERSION, MULTI_PROJECT_CUTOVER_SCHEMA_VERSION, PROJECT_OWNERSHIP_SCHEMA_VERSION, SQLITE_SCHEMA_PARITY_VERSION, SESSION_ADVISOR_ENABLED_SCHEMA_VERSION, MISSION_FIX_IDEMPOTENCY_VERSION, IMPORT_TRANSLATION_CACHE_VERSION, IMPORT_TRANSLATION_CACHE_SCOPE_FIX_VERSION, IMPORT_TRANSLATION_CACHE_LEGACY_PARTITION_BACKFILL_VERSION, OWNER_PROJECT_ID_SPLIT_VERSION, CHAT_SESSION_PINS_VERSION, EXECUTOR_TOOL_FAILURE_RETRY_VERSION, EXECUTOR_ESCALATION_ATTEMPT_VERSION, GLOBAL_ROUTINES_SCHEMA_VERSION, TASK_MERGER_MODEL_LANE_VERSION, BULK_COMPLETION_REFUSAL_AT_VERSION, TASK_PROPOSAL_CLAIM_VERSION, CONFIGURATION_REVISIONS_VERSION, IDEATION_SCHEMA_VERSION, RESEARCH_FEATURE_PROVENANCE_VERSION, TASK_VERIFICATION_REQUEST_VERSION, SYMBOL_LOCKS_SCHEMA_VERSION, BIGINT_COUNTERS_VERSION, PLANNING_ACTIVE_TIMING_VERSION, SQLITE_MIGRATION_RUNTIME_READ_VERSION, WORKFLOW_TASK_CONTINUATIONS_VERSION, LEGACY_ADOPTION_DRAINED_MARKER_RUNTIME_GRANTS_VERSION, TASK_WEDGE_NOTIFICATION_VERSION, LEGACY_ADOPTION_DRAINED_MARKER_FUNCTION, StaleBinarySchemaError, MIGRATION_BOOKKEEPING_TABLE, LEGACY_ADOPTION_DRAINED_MARKER, __dirname, MIGRATIONS_DIR, BASELINE_MIGRATION_PATH, AUTOMATION_ISOLATION_MIGRATION_PATH, ANALYTICS_ISOLATION_MIGRATION_PATH, MONITOR_APPROVAL_ISOLATION_MIGRATION_PATH, LEGACY_CUTOVER_PRESERVATION_MIGRATION_PATH, MULTI_PROJECT_CUTOVER_MIGRATION_PATH, PROJECT_OWNERSHIP_MIGRATION_PATH, SQLITE_SCHEMA_PARITY_MIGRATION_PATH, SESSION_ADVISOR_ENABLED_MIGRATION_PATH, MISSION_FIX_IDEMPOTENCY_MIGRATION_PATH, IMPORT_TRANSLATION_CACHE_MIGRATION_PATH, IMPORT_TRANSLATION_CACHE_SCOPE_FIX_MIGRATION_PATH, IMPORT_TRANSLATION_CACHE_LEGACY_PARTITION_BACKFILL_MIGRATION_PATH, OWNER_PROJECT_ID_SPLIT_MIGRATION_PATH, CHAT_SESSION_PINS_MIGRATION_PATH, EXECUTOR_TOOL_FAILURE_RETRY_MIGRATION_PATH, EXECUTOR_ESCALATION_ATTEMPT_MIGRATION_PATH, GLOBAL_ROUTINES_MIGRATION_PATH, TASK_MERGER_MODEL_LANE_MIGRATION_PATH, BULK_COMPLETION_REFUSAL_AT_MIGRATION_PATH, TASK_PROPOSAL_CLAIM_MIGRATION_PATH, CONFIGURATION_REVISIONS_MIGRATION_PATH, IDEATION_MIGRATION_PATH, RESEARCH_FEATURE_PROVENANCE_MIGRATION_PATH, TASK_VERIFICATION_REQUEST_MIGRATION_PATH, SYMBOL_LOCKS_MIGRATION_PATH, BIGINT_COUNTERS_MIGRATION_PATH, WORKFLOW_IR_PIN_AND_LEGACY_ADOPTION_VERSION, WORKFLOW_IR_PIN_AND_LEGACY_ADOPTION_MIGRATION_PATH, PLANNING_ACTIVE_TIMING_MIGRATION_PATH, TASK_DECLARED_SYMBOLS_MIGRATION_PATH, SQLITE_MIGRATION_RUNTIME_READ_PATH, WORKFLOW_TASK_CONTINUATIONS_PATH, LEGACY_ADOPTION_DRAINED_MARKER_RUNTIME_GRANTS_PATH, TASK_WEDGE_NOTIFICATION_MIGRATION_PATH;
36775
36840
  var init_schema_applier = __esm({
36776
36841
  "../core/src/postgres/schema-applier.ts"() {
36777
36842
  "use strict";
36778
36843
  init_drizzle_orm();
36779
36844
  init_plugin_schema_hook();
36780
36845
  init_advisory_locks();
36781
- SCHEMA_BASELINE_VERSION = "0032";
36846
+ SCHEMA_BASELINE_VERSION = "0033";
36782
36847
  TASK_DECLARED_SYMBOLS_VERSION = "0028";
36783
36848
  INITIAL_SCHEMA_VERSION = "0000";
36784
36849
  AUTOMATION_ISOLATION_SCHEMA_VERSION = "0001";
@@ -36811,6 +36876,7 @@ var init_schema_applier = __esm({
36811
36876
  SQLITE_MIGRATION_RUNTIME_READ_VERSION = "0030";
36812
36877
  WORKFLOW_TASK_CONTINUATIONS_VERSION = "0031";
36813
36878
  LEGACY_ADOPTION_DRAINED_MARKER_RUNTIME_GRANTS_VERSION = "0032";
36879
+ TASK_WEDGE_NOTIFICATION_VERSION = "0033";
36814
36880
  LEGACY_ADOPTION_DRAINED_MARKER_FUNCTION = "fusion_mark_legacy_adoption_drained";
36815
36881
  StaleBinarySchemaError = class extends Error {
36816
36882
  constructor(databaseVersion, binaryVersion) {
@@ -36917,6 +36983,10 @@ var init_schema_applier = __esm({
36917
36983
  MIGRATIONS_DIR,
36918
36984
  "0032_legacy_adoption_drained_marker_runtime_grants.sql"
36919
36985
  );
36986
+ TASK_WEDGE_NOTIFICATION_MIGRATION_PATH = join8(
36987
+ MIGRATIONS_DIR,
36988
+ "0033_fn-8505_wedge_notification.sql"
36989
+ );
36920
36990
  }
36921
36991
  });
36922
36992
 
@@ -37307,6 +37377,7 @@ __export(sqlite_migrator_exports, {
37307
37377
  isSqliteMigrationComplete: () => isSqliteMigrationComplete,
37308
37378
  migrateLegacyProjectPluginRows: () => migrateLegacyProjectPluginRows,
37309
37379
  migrateSqliteToPostgres: () => migrateSqliteToPostgres,
37380
+ projectPluginSqliteMigrationKey: () => projectPluginSqliteMigrationKey,
37310
37381
  recordSqliteMigrationComplete: () => recordSqliteMigrationComplete,
37311
37382
  toSnakeCase: () => toSnakeCase2
37312
37383
  });
@@ -37849,7 +37920,12 @@ function normalizeLegacyJson(value, fallback2) {
37849
37920
  return fallback2;
37850
37921
  }
37851
37922
  }
37923
+ function projectPluginSqliteMigrationKey(projectPath) {
37924
+ return `project-plugins:${resolve6(projectPath)}`;
37925
+ }
37852
37926
  async function migrateLegacyProjectPluginRows(db, sqlitePath, projectPath) {
37927
+ const migrationKey = projectPluginSqliteMigrationKey(projectPath);
37928
+ if (await isSqliteMigrationComplete(db, migrationKey)) return;
37853
37929
  await db.transaction(async (tx) => {
37854
37930
  await migrateLegacyProjectPluginRowsOnSession(
37855
37931
  tx,
@@ -37859,19 +37935,14 @@ async function migrateLegacyProjectPluginRows(db, sqlitePath, projectPath) {
37859
37935
  });
37860
37936
  }
37861
37937
  async function migrateLegacyProjectPluginRowsOnSession(db, sqlitePath, projectPath) {
37862
- if (!sqliteTableExists(sqlitePath, "plugins")) return;
37863
- await acquireSqliteMigrationStateLock(db);
37864
37938
  const canonicalProjectPath = resolve6(projectPath);
37865
- const migrationKey = `project-plugins:${canonicalProjectPath}`;
37939
+ const migrationKey = projectPluginSqliteMigrationKey(projectPath);
37940
+ await acquireSqliteMigrationStateLock(db);
37866
37941
  await ensureMigrationStateTable(db);
37867
37942
  await db.execute(sql`SELECT pg_advisory_xact_lock(hashtextextended(${migrationKey}, 0))`);
37868
- const completed = await db.execute(sql`
37869
- SELECT 1 AS complete
37870
- FROM public.${sql.identifier(SQLITE_MIGRATION_STATE_TABLE)}
37871
- WHERE migration_key = ${migrationKey} AND status = 'complete'
37872
- LIMIT 1
37873
- `);
37874
- if (completed.length > 0) return;
37943
+ const completed = await isSqliteMigrationComplete(db, migrationKey);
37944
+ if (completed) return;
37945
+ if (!sqliteTableExists(sqlitePath, "plugins")) return;
37875
37946
  const sqlite = openSqlite(sqlitePath);
37876
37947
  let rows;
37877
37948
  try {
@@ -38957,6 +39028,17 @@ function buildPgCtlOptionsString(port, flags) {
38957
39028
  function buildPgCtlStartArgs(dataDir, logFile, optionsString) {
38958
39029
  return ["-D", dataDir, "-o", optionsString, "-l", logFile, "-W", "start"];
38959
39030
  }
39031
+ function withWindowsNativeBinPath(environment, nativeRoot, platform6 = process.platform) {
39032
+ if (platform6 !== "win32") return environment;
39033
+ const pathKey = Object.keys(environment).find((key) => key.toLowerCase() === "path") ?? "PATH";
39034
+ const bin = nativeRoot.replace(/[\\/]+$/, "") + "\\bin";
39035
+ const normalize10 = (value) => value.replace(/\//g, "\\").toLowerCase();
39036
+ const inherited = environment[pathKey] ?? "";
39037
+ const segments = inherited.split(";").filter(Boolean);
39038
+ const normalizedBin = normalize10(bin);
39039
+ const deduplicated = segments.filter((segment) => normalize10(segment) !== normalizedBin);
39040
+ return { ...environment, [pathKey]: [bin, ...deduplicated].join(";") };
39041
+ }
38960
39042
  function readPostgresPid(dataDir) {
38961
39043
  try {
38962
39044
  const lines = readFileSync3(join9(dataDir, "postmaster.pid"), "utf-8").split("\n");
@@ -39011,7 +39093,13 @@ async function startServerElevatedRestricted(opts) {
39011
39093
  `embedded postgres: elevated start via pg_ctl restricted token (no helper account); log ${logFile}`
39012
39094
  );
39013
39095
  let stopped = false;
39096
+ let logMonitor = null;
39097
+ const stopMonitoring = () => {
39098
+ if (logMonitor) clearInterval(logMonitor);
39099
+ logMonitor = null;
39100
+ };
39014
39101
  const killAll = () => {
39102
+ stopMonitoring();
39015
39103
  if (stopped) return;
39016
39104
  stopped = true;
39017
39105
  const r = spawnSync(pgCtl, ["-D", opts.dataDir, "-m", "fast", "-t", "30", "-w", "stop"], {
@@ -39045,14 +39133,21 @@ async function startServerElevatedRestricted(opts) {
39045
39133
  killAll();
39046
39134
  await waitForDown();
39047
39135
  },
39136
+ stopMonitoring,
39048
39137
  async stopWrapperOnly() {
39138
+ stopMonitoring();
39049
39139
  stopped = true;
39050
39140
  }
39051
39141
  };
39052
39142
  opts.onLaunched?.(handle);
39053
39143
  const launch = await new Promise(
39054
39144
  (resolve101, reject2) => {
39055
- const child = spawn(pgCtl, args, { windowsHide: true });
39145
+ const child = spawn(pgCtl, args, {
39146
+ windowsHide: true,
39147
+ // Do not mutate process.env: sibling dashboard/engine children must
39148
+ // retain their inherited environment unchanged.
39149
+ env: withWindowsNativeBinPath(process.env, opts.nativeRoot)
39150
+ });
39056
39151
  let output = "";
39057
39152
  child.stdout.on("data", (d) => output += d.toString());
39058
39153
  child.stderr.on("data", (d) => output += d.toString());
@@ -39077,16 +39172,14 @@ ${readTail(logFile, 2e3)}`
39077
39172
  killAll();
39078
39173
  throw new Error("embedded postgres: elevated launch cancelled before ready.");
39079
39174
  }
39175
+ if (await probeTcpPort(opts.port, 500)) {
39176
+ ready = true;
39177
+ break;
39178
+ }
39080
39179
  const tail = readTail(logFile, 3e3);
39081
39180
  if (tail !== lastSnapshot) {
39082
39181
  lastSnapshot = tail;
39083
- opts.onLog(`elevated poll pg={${tail.slice(-400)}}`);
39084
- }
39085
- if (/database system is ready to accept connections/.test(tail)) {
39086
- if (await probeTcpPort(opts.port, 500)) {
39087
- ready = true;
39088
- break;
39089
- }
39182
+ opts.onLog(`elevated diagnostic pg={${tail.slice(-400)}}`);
39090
39183
  }
39091
39184
  if (/\bFATAL\b|\bPANIC\b|could not (bind|start|create|access|connect|load)|not permitted|Permission denied|is not the owner/i.test(tail)) {
39092
39185
  killAll();
@@ -39118,6 +39211,18 @@ ${tail}`
39118
39211
  opts.onLog(
39119
39212
  `embedded postgres: elevated server ready on 127.0.0.1:${opts.port} (pid ${postgresPid ?? 0}, restricted token)`
39120
39213
  );
39214
+ let lastLogSnapshot = "";
39215
+ const observePostReadyLog = () => {
39216
+ if (stopped) return;
39217
+ const snapshot = readTail(logFile, 16384);
39218
+ if (snapshot === "(no log file)" || snapshot === lastLogSnapshot) return;
39219
+ const next = snapshot.startsWith(lastLogSnapshot) ? snapshot.slice(lastLogSnapshot.length) : snapshot;
39220
+ lastLogSnapshot = snapshot;
39221
+ if (next) opts.onLog(next);
39222
+ };
39223
+ observePostReadyLog();
39224
+ logMonitor = setInterval(observePostReadyLog, 250);
39225
+ if (typeof logMonitor.unref === "function") logMonitor.unref();
39121
39226
  return handle;
39122
39227
  }
39123
39228
  function probeTcpPort(port, timeoutMs) {
@@ -39137,10 +39242,26 @@ function probeTcpPort(port, timeoutMs) {
39137
39242
  socket.once("error", () => finish(false));
39138
39243
  });
39139
39244
  }
39140
- var elevatedCache;
39245
+ var WindowsPostgresFatalDetector, elevatedCache;
39141
39246
  var init_embedded_windows_elevated = __esm({
39142
39247
  "../core/src/postgres/embedded-windows-elevated.ts"() {
39143
39248
  "use strict";
39249
+ WindowsPostgresFatalDetector = class {
39250
+ buffer = "";
39251
+ matched = false;
39252
+ push(chunk) {
39253
+ if (this.matched || !chunk) return false;
39254
+ this.buffer = (this.buffer + chunk).slice(-16384);
39255
+ const text3 = this.buffer.toLowerCase();
39256
+ if (/server process(?:\s+\(pid\s+\d+\))?\s+was terminated by exception\s+0xc0000142[\s\S]*?terminating any other active server processes[\s\S]*?shutting down due to startup process failure[\s\S]*?database system is shut down/.test(
39257
+ text3
39258
+ )) {
39259
+ this.matched = true;
39260
+ return true;
39261
+ }
39262
+ return false;
39263
+ }
39264
+ };
39144
39265
  elevatedCache = null;
39145
39266
  }
39146
39267
  });
@@ -39150,10 +39271,14 @@ var embedded_lifecycle_exports = {};
39150
39271
  __export(embedded_lifecycle_exports, {
39151
39272
  DEFAULT_EMBEDDED_DATABASE: () => DEFAULT_EMBEDDED_DATABASE,
39152
39273
  DEFAULT_EMBEDDED_INITDB_FLAGS: () => DEFAULT_EMBEDDED_INITDB_FLAGS,
39274
+ DEFAULT_EMBEDDED_MAX_CONNECTIONS: () => DEFAULT_EMBEDDED_MAX_CONNECTIONS,
39275
+ DEFAULT_EMBEDDED_MAX_CONNECTIONS_WIN32: () => DEFAULT_EMBEDDED_MAX_CONNECTIONS_WIN32,
39153
39276
  DEFAULT_EMBEDDED_PASSWORD: () => DEFAULT_EMBEDDED_PASSWORD,
39154
39277
  DEFAULT_EMBEDDED_POSTGRES_FLAGS: () => DEFAULT_EMBEDDED_POSTGRES_FLAGS,
39155
39278
  DEFAULT_EMBEDDED_USER: () => DEFAULT_EMBEDDED_USER,
39156
39279
  DEFAULT_START_TIMEOUT_MS: () => DEFAULT_START_TIMEOUT_MS,
39280
+ EMBEDDED_MAX_CONNECTIONS_MAX: () => EMBEDDED_MAX_CONNECTIONS_MAX,
39281
+ EMBEDDED_MAX_CONNECTIONS_MIN: () => EMBEDDED_MAX_CONNECTIONS_MIN,
39157
39282
  EmbeddedPostgresLifecycle: () => EmbeddedPostgresLifecycle,
39158
39283
  EmbeddedStartTimeoutError: () => EmbeddedStartTimeoutError,
39159
39284
  __setEmbeddedPostgresCtorForTests: () => __setEmbeddedPostgresCtorForTests,
@@ -39172,6 +39297,7 @@ __export(embedded_lifecycle_exports, {
39172
39297
  normalizeMacosEmbeddedPostgresDylibSymlinks: () => normalizeMacosEmbeddedPostgresDylibSymlinks,
39173
39298
  readPortFromPostmasterPid: () => readPortFromPostmasterPid,
39174
39299
  resolveElectronAsarUnpackedPath: () => resolveElectronAsarUnpackedPath,
39300
+ resolveEmbeddedMaxConnections: () => resolveEmbeddedMaxConnections,
39175
39301
  uninstallElectronAsarNativePathPatchForTests: () => uninstallElectronAsarNativePathPatchForTests
39176
39302
  });
39177
39303
  import {
@@ -39381,6 +39507,23 @@ function materializeEmbeddedPostgresRuntimeBinaries(nativeRoot, options) {
39381
39507
  writeFileSync2(marker, sourceMarker, "utf8");
39382
39508
  return destRoot;
39383
39509
  }
39510
+ function isWindowsEmbeddedPostgresBinary(command) {
39511
+ return process.platform === "win32" && typeof command === "string" && /[\\/]bin[\\/](?:postgres|initdb|pg_ctl)\.exe$/i.test(command);
39512
+ }
39513
+ function withWindowsPostgresSpawnEnvironment(command, rest) {
39514
+ if (!isWindowsEmbeddedPostgresBinary(command)) return rest;
39515
+ const args = [...rest];
39516
+ const optionIndex = args.length - 1;
39517
+ const existing = args[optionIndex];
39518
+ if (!existing || typeof existing !== "object" || Array.isArray(existing)) return args;
39519
+ const binDir = dirname6(command);
39520
+ const nativeRoot = dirname6(binDir);
39521
+ args[optionIndex] = {
39522
+ ...existing,
39523
+ env: withWindowsNativeBinPath(existing.env ?? process.env, nativeRoot)
39524
+ };
39525
+ return args;
39526
+ }
39384
39527
  function installElectronAsarNativePathPatch() {
39385
39528
  if (electronAsarNativePathPatchInstalled) return;
39386
39529
  electronAsarNativePathPatchInstalled = true;
@@ -39398,7 +39541,10 @@ function installElectronAsarNativePathPatch() {
39398
39541
  const originalSpawn = childProcessMod.spawn.bind(childProcessMod);
39399
39542
  childProcessMod.spawn = (command, ...rest) => {
39400
39543
  const fixedCommand = typeof command === "string" ? resolveElectronAsarUnpackedPath(command) : command;
39401
- return originalSpawn(fixedCommand, ...rest);
39544
+ return originalSpawn(
39545
+ fixedCommand,
39546
+ ...withWindowsPostgresSpawnEnvironment(fixedCommand, rest)
39547
+ );
39402
39548
  };
39403
39549
  const fsPromisesMod = require2("fs/promises");
39404
39550
  const originalStat = fsPromisesMod.stat.bind(fsPromisesMod);
@@ -39447,6 +39593,12 @@ function getEmbeddedPostgresCtor() {
39447
39593
  function defaultEmbeddedPostgresFlagsFor(platform6) {
39448
39594
  return platform6 === "win32" ? [] : ["-c", "shared_memory_type=mmap"];
39449
39595
  }
39596
+ function resolveEmbeddedMaxConnections(configured, platform6 = process.platform) {
39597
+ if (typeof configured === "number" && Number.isInteger(configured)) {
39598
+ return Math.min(EMBEDDED_MAX_CONNECTIONS_MAX, Math.max(EMBEDDED_MAX_CONNECTIONS_MIN, configured));
39599
+ }
39600
+ return platform6 === "win32" ? DEFAULT_EMBEDDED_MAX_CONNECTIONS_WIN32 : DEFAULT_EMBEDDED_MAX_CONNECTIONS;
39601
+ }
39450
39602
  function defaultEmbeddedDataDir() {
39451
39603
  const home = process.env.HOME || process.env.USERPROFILE || process.cwd();
39452
39604
  return join10(home, ".fusion", "embedded-postgres", "default");
@@ -39633,7 +39785,7 @@ function signalNumber(signal) {
39633
39785
  return 0;
39634
39786
  }
39635
39787
  }
39636
- var require2, stagedEmbeddedPgBundleCache, EMBEDDED_PG_BIN_NAMES, MATERIALIZATION_MARKER_VERSION, electronAsarNativePathPatchInstalled, electronAsarNativePathPatchRestore, embeddedPostgresCtorCache, embeddedPostgresCtorIsTestOverride, windowsElevatedAdminForTests, windowsNativeRootForTests, windowsLauncherForTests, log4, DEFAULT_EMBEDDED_USER, DEFAULT_EMBEDDED_PASSWORD, DEFAULT_EMBEDDED_DATABASE, DEFAULT_EMBEDDED_POSTGRES_FLAGS, DEFAULT_EMBEDDED_INITDB_FLAGS, DEFAULT_START_TIMEOUT_MS, PG_VERSION_FILENAME, MACOS_EMBEDDED_DYLIB_SYMLINKS, runningInstances, POSTMASTER_PID_READ_ATTEMPTS, POSTMASTER_PID_READ_RETRY_MS, EmbeddedPostgresLifecycle, EmbeddedStartCancelledError, EmbeddedStartTimeoutError;
39788
+ var require2, stagedEmbeddedPgBundleCache, EMBEDDED_PG_BIN_NAMES, MATERIALIZATION_MARKER_VERSION, electronAsarNativePathPatchInstalled, electronAsarNativePathPatchRestore, embeddedPostgresCtorCache, embeddedPostgresCtorIsTestOverride, windowsElevatedAdminForTests, windowsNativeRootForTests, windowsLauncherForTests, log4, DEFAULT_EMBEDDED_USER, DEFAULT_EMBEDDED_PASSWORD, DEFAULT_EMBEDDED_DATABASE, DEFAULT_EMBEDDED_POSTGRES_FLAGS, DEFAULT_EMBEDDED_MAX_CONNECTIONS, DEFAULT_EMBEDDED_MAX_CONNECTIONS_WIN32, EMBEDDED_MAX_CONNECTIONS_MIN, EMBEDDED_MAX_CONNECTIONS_MAX, DEFAULT_EMBEDDED_INITDB_FLAGS, DEFAULT_START_TIMEOUT_MS, PG_VERSION_FILENAME, MACOS_EMBEDDED_DYLIB_SYMLINKS, runningInstances, POSTMASTER_PID_READ_ATTEMPTS, POSTMASTER_PID_READ_RETRY_MS, EmbeddedPostgresLifecycle, EmbeddedStartCancelledError, EmbeddedStartTimeoutError;
39637
39789
  var init_embedded_lifecycle = __esm({
39638
39790
  "../core/src/postgres/embedded-lifecycle.ts"() {
39639
39791
  "use strict";
@@ -39664,6 +39816,10 @@ var init_embedded_lifecycle = __esm({
39664
39816
  DEFAULT_EMBEDDED_PASSWORD = "password";
39665
39817
  DEFAULT_EMBEDDED_DATABASE = "fusion";
39666
39818
  DEFAULT_EMBEDDED_POSTGRES_FLAGS = defaultEmbeddedPostgresFlagsFor(process.platform);
39819
+ DEFAULT_EMBEDDED_MAX_CONNECTIONS = 500;
39820
+ DEFAULT_EMBEDDED_MAX_CONNECTIONS_WIN32 = 150;
39821
+ EMBEDDED_MAX_CONNECTIONS_MIN = 32;
39822
+ EMBEDDED_MAX_CONNECTIONS_MAX = 2e3;
39667
39823
  DEFAULT_EMBEDDED_INITDB_FLAGS = [
39668
39824
  "--encoding=UTF8",
39669
39825
  "--locale=C"
@@ -39706,6 +39862,10 @@ var init_embedded_lifecycle = __esm({
39706
39862
  * on a failure that is handled before the timeout fires.
39707
39863
  */
39708
39864
  startTimer = null;
39865
+ windowsFatalDetector = new WindowsPostgresFatalDetector();
39866
+ recoveryAttempts = 0;
39867
+ recoveryInFlight = null;
39868
+ stopRequested = false;
39709
39869
  constructor(opts) {
39710
39870
  this.options = {
39711
39871
  dataDir: opts.dataDir,
@@ -39720,6 +39880,15 @@ var init_embedded_lifecycle = __esm({
39720
39880
  onError: opts.onError ?? ((err) => log4.error(String(err)))
39721
39881
  };
39722
39882
  }
39883
+ /**
39884
+ * Forward native process output to the existing sink, then schedule recovery
39885
+ * only for a confirmed owned Windows fatal shutdown sequence.
39886
+ */
39887
+ forwardPostgresLog = (message) => {
39888
+ this.options.onLog(message);
39889
+ if (process.platform !== "win32" || !this.running || !this.ownsProcess) return;
39890
+ if (this.windowsFatalDetector.push(message)) void this.recoverWindowsFatalOnce();
39891
+ };
39723
39892
  /** The configured or discovered port. Undefined until assigned (explicit or discovered in `start()`). */
39724
39893
  getPort() {
39725
39894
  return this.options.port ?? this.resolvedPort;
@@ -39816,11 +39985,19 @@ var init_embedded_lifecycle = __esm({
39816
39985
  migrationUrlOverridden: false
39817
39986
  };
39818
39987
  }
39988
+ return this.startBounded();
39989
+ }
39990
+ /**
39991
+ * Start an owned postmaster with the same cancellation and timeout contract
39992
+ * used by public startup. Recovery calls this directly because it must not
39993
+ * join a stale pid file or allocate a new endpoint between pool reconnects.
39994
+ */
39995
+ async startBounded(preferredPort) {
39819
39996
  if (this.options.startTimeoutMs <= 0) {
39820
- return this.startInternal();
39997
+ return this.startInternal(void 0, preferredPort);
39821
39998
  }
39822
39999
  const controller = new AbortController();
39823
- const startAttempt = this.startInternal(controller.signal);
40000
+ const startAttempt = this.startInternal(controller.signal, preferredPort);
39824
40001
  let timer2;
39825
40002
  const timeout2 = new Promise((_resolve, reject2) => {
39826
40003
  timer2 = setTimeout(() => {
@@ -39846,12 +40023,9 @@ var init_embedded_lifecycle = __esm({
39846
40023
  this.startTimer = null;
39847
40024
  }
39848
40025
  }
39849
- /**
39850
- * The actual start sequence, with no timeout wrapper. Called by {@link start}
39851
- * either directly (timeout disabled) or via Promise.race with the timeout.
39852
- */
39853
- async startInternal(signal) {
39854
- const port = this.options.port ?? await findFreePort();
40026
+ /** The actual start sequence, invoked only through {@link startBounded}. */
40027
+ async startInternal(signal, preferredPort) {
40028
+ const port = this.options.port ?? preferredPort ?? this.resolvedPort ?? await findFreePort();
39855
40029
  if (signal?.aborted) throw new EmbeddedStartCancelledError(this.options.dataDir);
39856
40030
  this.resolvedPort = port;
39857
40031
  const alreadyInitialized = isDataDirInitialized(this.options.dataDir);
@@ -39865,7 +40039,7 @@ var init_embedded_lifecycle = __esm({
39865
40039
  authMethod: "password",
39866
40040
  initdbFlags: [...this.options.initdbFlags],
39867
40041
  postgresFlags: [...this.options.postgresFlags],
39868
- onLog: this.options.onLog,
40042
+ onLog: this.forwardPostgresLog,
39869
40043
  onError: this.options.onError
39870
40044
  });
39871
40045
  this.pg = pg;
@@ -39897,7 +40071,7 @@ var init_embedded_lifecycle = __esm({
39897
40071
  dataDir: this.options.dataDir,
39898
40072
  port,
39899
40073
  postgresFlags: this.options.postgresFlags,
39900
- onLog: this.options.onLog,
40074
+ onLog: this.forwardPostgresLog,
39901
40075
  onError: this.options.onError,
39902
40076
  startTimeoutMs: this.options.startTimeoutMs,
39903
40077
  signal,
@@ -39975,6 +40149,43 @@ var init_embedded_lifecycle = __esm({
39975
40149
  migrationUrlOverridden: false
39976
40150
  };
39977
40151
  }
40152
+ /**
40153
+ * FNXC:PostgresEmbedded 2026-07-22-16:25:
40154
+ * A 0xC0000142 backend crash shuts down its whole PostgreSQL cluster. One
40155
+ * lifecycle-owned retry reuses the initialized directory and same resolved
40156
+ * port; joiners, stop/detach, and a second incident are deliberately inert.
40157
+ */
40158
+ async recoverWindowsFatalOnce() {
40159
+ if (this.recoveryInFlight || this.recoveryAttempts >= 1 || this.stopRequested || !this.ownsProcess) return;
40160
+ this.recoveryAttempts += 1;
40161
+ this.recoveryInFlight = (async () => {
40162
+ this.options.onLog("embedded postgres: detected Windows DLL initialization shutdown; attempting one owned-cluster recovery");
40163
+ try {
40164
+ if (this.nonAdminHandle) await this.nonAdminHandle.stop();
40165
+ else await this.pg?.stop();
40166
+ this.pg = null;
40167
+ this.nonAdminHandle = null;
40168
+ this.running = false;
40169
+ runningInstances.delete(this.options.dataDir);
40170
+ if (this.stopRequested || !this.ownsProcess) return;
40171
+ const recoveryPort = this.resolvedPort;
40172
+ if (recoveryPort === void 0) {
40173
+ throw new Error("embedded postgres: recovery lost its resolved port");
40174
+ }
40175
+ await this.startBounded(recoveryPort);
40176
+ this.options.onLog("embedded postgres: Windows owned-cluster recovery completed; existing pools may reconnect");
40177
+ } catch (error2) {
40178
+ this.running = false;
40179
+ runningInstances.delete(this.options.dataDir);
40180
+ this.options.onError(
40181
+ `embedded postgres: Windows DLL initialization recovery failed after one retry; restart Fusion and inspect the System log. ${error2 instanceof Error ? error2.message : String(error2)}`
40182
+ );
40183
+ } finally {
40184
+ this.recoveryInFlight = null;
40185
+ }
40186
+ })();
40187
+ await this.recoveryInFlight;
40188
+ }
39978
40189
  async settleCancelledStart(pg) {
39979
40190
  if (this.nonAdminHandle) {
39980
40191
  try {
@@ -40121,6 +40332,7 @@ var init_embedded_lifecycle = __esm({
40121
40332
  */
40122
40333
  detachWithoutStop() {
40123
40334
  this.uninstallShutdownHook();
40335
+ this.nonAdminHandle?.stopMonitoring();
40124
40336
  this.pg = null;
40125
40337
  this.nonAdminHandle = null;
40126
40338
  this.running = false;
@@ -40128,6 +40340,7 @@ var init_embedded_lifecycle = __esm({
40128
40340
  runningInstances.delete(this.options.dataDir);
40129
40341
  }
40130
40342
  async stop() {
40343
+ this.stopRequested = true;
40131
40344
  this.uninstallShutdownHook();
40132
40345
  if (!this.ownsProcess) {
40133
40346
  this.running = false;
@@ -52152,10 +52365,10 @@ async function bootSchemaBackendOnce(options, bypassProjectIsolation = false) {
52152
52365
  let resolvedBackend = backend;
52153
52366
  let embeddedDataDir = null;
52154
52367
  if (backend.mode === "embedded") {
52155
- const { EmbeddedPostgresLifecycle: EmbeddedPostgresLifecycle2, defaultEmbeddedDataDir: defaultEmbeddedDataDir2, DEFAULT_EMBEDDED_DATABASE: DEFAULT_EMBEDDED_DATABASE2 } = await Promise.resolve().then(() => (init_embedded_lifecycle(), embedded_lifecycle_exports));
52368
+ const { EmbeddedPostgresLifecycle: EmbeddedPostgresLifecycle2, defaultEmbeddedDataDir: defaultEmbeddedDataDir2, DEFAULT_EMBEDDED_DATABASE: DEFAULT_EMBEDDED_DATABASE2, resolveEmbeddedMaxConnections: resolveEmbeddedMaxConnections2 } = await Promise.resolve().then(() => (init_embedded_lifecycle(), embedded_lifecycle_exports));
52156
52369
  const { GlobalSettingsStore: GlobalSettingsStore2 } = await Promise.resolve().then(() => (init_global_settings(), global_settings_exports));
52157
52370
  const configuredMaxConnections = process.env.VITEST === "true" && !options.globalSettingsDir ? void 0 : (await new GlobalSettingsStore2(options.globalSettingsDir).getSettings()).embeddedPostgresMaxConnections;
52158
- const maxConnections = Number.isInteger(configuredMaxConnections) ? Math.min(2e3, Math.max(32, configuredMaxConnections)) : 500;
52371
+ const maxConnections = resolveEmbeddedMaxConnections2(configuredMaxConnections);
52159
52372
  const dataDir = resolve8(options.embeddedDataDir ?? defaultEmbeddedDataDir2());
52160
52373
  embeddedDataDir = dataDir;
52161
52374
  log5.log(`startup-factory: starting embedded PostgreSQL (data dir ${dataDir})`);
@@ -52392,7 +52605,20 @@ async function createTaskStoreForBackend(options) {
52392
52605
  try {
52393
52606
  const fusionDir = join16(rootDir, ".fusion");
52394
52607
  const legacySqlitePath = join16(fusionDir, "fusion.db");
52395
- if (existsSync11(legacySqlitePath)) {
52608
+ const {
52609
+ migrateSqliteToPostgres: migrateSqliteToPostgres2,
52610
+ defaultMigrationSources: defaultMigrationSources2,
52611
+ formatMigrationProgress: formatMigrationProgress2,
52612
+ isSqliteMigrationComplete: isSqliteMigrationComplete2,
52613
+ completeSqliteMigration: completeSqliteMigration2,
52614
+ recordSqliteMigrationComplete: recordSqliteMigrationComplete2,
52615
+ CENTRAL_SQLITE_MIGRATION_KEY: CENTRAL_SQLITE_MIGRATION_KEY2
52616
+ } = await Promise.resolve().then(() => (init_sqlite_migrator(), sqlite_migrator_exports));
52617
+ const fallbackProjectId = fallbackProjectIdForRoot(rootDir);
52618
+ let migrationProjectId = options.projectId ?? await lookupRegisteredProjectIdByPath(connections.migration, rootDir) ?? fallbackProjectId;
52619
+ let migrationKey = `project:${migrationProjectId}`;
52620
+ let migrationComplete = await isSqliteMigrationComplete2(connections.migration, migrationKey);
52621
+ if (!migrationComplete && existsSync11(legacySqlitePath)) {
52396
52622
  let globalDir = options.globalSettingsDir;
52397
52623
  if (!globalDir) {
52398
52624
  try {
@@ -52402,21 +52628,24 @@ async function createTaskStoreForBackend(options) {
52402
52628
  globalDir = void 0;
52403
52629
  }
52404
52630
  }
52405
- let migrationProjectId = options.projectId ?? await lookupRegisteredProjectIdByPath(connections.migration, rootDir);
52631
+ let resolvedFromLegacyCentral = false;
52632
+ const centralMigrationComplete = await isSqliteMigrationComplete2(
52633
+ connections.migration,
52634
+ CENTRAL_SQLITE_MIGRATION_KEY2
52635
+ );
52406
52636
  const legacyCentralPath = globalDir ? join16(globalDir, "fusion-central.db") : void 0;
52407
- if (!migrationProjectId && legacyCentralPath && existsSync11(legacyCentralPath) && isValidSqliteDatabaseFile(legacyCentralPath)) {
52637
+ if (!centralMigrationComplete && migrationProjectId === fallbackProjectId && legacyCentralPath && existsSync11(legacyCentralPath) && isValidSqliteDatabaseFile(legacyCentralPath)) {
52408
52638
  const { DatabaseSync: DatabaseSync2 } = await Promise.resolve().then(() => (init_sqlite_adapter(), sqlite_adapter_exports));
52409
52639
  const legacyCentral = new DatabaseSync2(legacyCentralPath, { readOnly: true });
52410
52640
  try {
52411
52641
  const row = legacyCentral.prepare(`SELECT id FROM projects WHERE path = ? LIMIT 1`).get(rootDir);
52412
- migrationProjectId = row?.id;
52642
+ migrationProjectId = row?.id ?? migrationProjectId;
52643
+ resolvedFromLegacyCentral = Boolean(row?.id);
52413
52644
  } catch {
52414
52645
  } finally {
52415
52646
  legacyCentral.close();
52416
52647
  }
52417
52648
  }
52418
- const fallbackProjectId = fallbackProjectIdForRoot(rootDir);
52419
- migrationProjectId ??= fallbackProjectId;
52420
52649
  if (migrationProjectId !== fallbackProjectId) {
52421
52650
  try {
52422
52651
  await rekeyFallbackProjectPartition(connections.migration, fallbackProjectId, migrationProjectId);
@@ -52432,14 +52661,9 @@ async function createTaskStoreForBackend(options) {
52432
52661
  }
52433
52662
  }
52434
52663
  }
52435
- const migrationKey = `project:${migrationProjectId ?? rootDir}`;
52436
- const { migrateSqliteToPostgres: migrateSqliteToPostgres2, migrateLegacyProjectPluginRows: migrateLegacyProjectPluginRows2, defaultMigrationSources: defaultMigrationSources2, formatMigrationProgress: formatMigrationProgress2, isSqliteMigrationComplete: isSqliteMigrationComplete2, completeSqliteMigration: completeSqliteMigration2, recordSqliteMigrationComplete: recordSqliteMigrationComplete2, CENTRAL_SQLITE_MIGRATION_KEY: CENTRAL_SQLITE_MIGRATION_KEY2 } = await Promise.resolve().then(() => (init_sqlite_migrator(), sqlite_migrator_exports));
52437
- const migrationComplete = await isSqliteMigrationComplete2(connections.migration, migrationKey);
52664
+ migrationKey = `project:${migrationProjectId}`;
52665
+ migrationComplete = await isSqliteMigrationComplete2(connections.migration, migrationKey);
52438
52666
  if (!migrationComplete && isValidSqliteDatabaseFile(legacySqlitePath)) {
52439
- const centralMigrationComplete = await isSqliteMigrationComplete2(
52440
- connections.migration,
52441
- CENTRAL_SQLITE_MIGRATION_KEY2
52442
- );
52443
52667
  const sources = defaultMigrationSources2(fusionDir, globalDir ?? join16(fusionDir, "__no-global-dir__")).filter((source) => !centralMigrationComplete || source.pgSchema !== "central").filter((source) => existsSync11(source.sqlitePath) && isValidSqliteDatabaseFile(source.sqlitePath));
52444
52668
  if (sources.length > 0) {
52445
52669
  log5.log(`startup-factory: empty PostgreSQL database with legacy SQLite data present \u2014 auto-migrating ${sources.length} source(s) (SQLite files are kept as backups)`);
@@ -52474,6 +52698,12 @@ async function createTaskStoreForBackend(options) {
52474
52698
  rootDir
52475
52699
  });
52476
52700
  }
52701
+ if (resolvedFromLegacyCentral) {
52702
+ const registeredId = await lookupRegisteredProjectIdByPath(connections.migration, rootDir);
52703
+ if (registeredId !== migrationProjectId) {
52704
+ throw new Error(`legacy project identity ${migrationProjectId} was not materialized for ${rootDir} before migration completion`);
52705
+ }
52706
+ }
52477
52707
  await completeSqliteMigration2(connections.migration, migrationKey);
52478
52708
  if (sources.some((source) => source.pgSchema === "central")) {
52479
52709
  await recordSqliteMigrationComplete2(
@@ -52491,14 +52721,9 @@ async function createTaskStoreForBackend(options) {
52491
52721
  log5.log(`startup-factory: SQLite \u2192 PostgreSQL auto-migration complete (${migratedRows} row(s) across ${report.tables.length} table(s))`);
52492
52722
  }
52493
52723
  }
52494
- if (isValidSqliteDatabaseFile(legacySqlitePath)) {
52495
- await migrateLegacyProjectPluginRows2(
52496
- connections.migration,
52497
- legacySqlitePath,
52498
- rootDir
52499
- );
52500
- }
52501
52724
  }
52725
+ const { migrateLegacyProjectPluginRows: migrateLegacyProjectPluginRows2 } = await Promise.resolve().then(() => (init_sqlite_migrator(), sqlite_migrator_exports));
52726
+ await migrateLegacyProjectPluginRows2(connections.migration, legacySqlitePath, rootDir);
52502
52727
  } catch (err) {
52503
52728
  await connections.close().catch(() => void 0);
52504
52729
  await stopEmbeddedRuntime(
@@ -62165,6 +62390,7 @@ var init_persistence = __esm({
62165
62390
  defineTaskColumn("overlapBlockedBy", (task) => task.overlapBlockedBy ?? null),
62166
62391
  defineTaskColumn("paused", (task) => task.paused ? 1 : 0),
62167
62392
  defineTaskColumn("pausedReason", (task) => task.pausedReason ?? null),
62393
+ defineTaskColumn("wedgeNotification", (task) => toJsonNullable(task.wedgeNotification)),
62168
62394
  defineTaskColumn("userPaused", (task) => task.userPaused ? 1 : 0),
62169
62395
  defineTaskColumn("baseBranch", (task) => task.baseBranch ?? null),
62170
62396
  defineTaskColumn("branch", (task) => task.branch ?? null),
@@ -62574,6 +62800,7 @@ function rowToTask(row) {
62574
62800
  overlapBlockedBy: row.overlapBlockedBy || void 0,
62575
62801
  paused: row.paused ? true : void 0,
62576
62802
  pausedReason: row.pausedReason || void 0,
62803
+ wedgeNotification: fromJson(row.wedgeNotification) ?? void 0,
62577
62804
  userPaused: row.userPaused ? true : void 0,
62578
62805
  baseBranch: row.baseBranch || void 0,
62579
62806
  executionStartBranch: row.executionStartBranch || void 0,
@@ -84829,6 +85056,7 @@ function getTaskSelectClauseImpl2(store, slim, tableAlias) {
84829
85056
  "overlapBlockedBy",
84830
85057
  "paused",
84831
85058
  "pausedReason",
85059
+ "wedgeNotification",
84832
85060
  "userPaused",
84833
85061
  "baseBranch",
84834
85062
  "branch",
@@ -91547,7 +91775,7 @@ var init_async_eval_store = __esm({
91547
91775
  }
91548
91776
  });
91549
91777
 
91550
- // ../core/src/task-store/remaining-ops-1.ts
91778
+ // ../core/src/task-store/project-store-ops.ts
91551
91779
  import { randomUUID as randomUUID33 } from "node:crypto";
91552
91780
  import { mkdir as mkdir13, writeFile as writeFile12 } from "node:fs/promises";
91553
91781
  import { join as join33 } from "node:path";
@@ -92415,8 +92643,8 @@ function getEvalStoreImpl(store) {
92415
92643
  return store.evalStore;
92416
92644
  }
92417
92645
  var workflowDefinitionBeforeInsertForTesting;
92418
- var init_remaining_ops_1 = __esm({
92419
- "../core/src/task-store/remaining-ops-1.ts"() {
92646
+ var init_project_store_ops = __esm({
92647
+ "../core/src/task-store/project-store-ops.ts"() {
92420
92648
  "use strict";
92421
92649
  init_store();
92422
92650
  init_errors3();
@@ -99116,6 +99344,11 @@ async function updateTaskUnlockedImpl(store, id, updates, runContext) {
99116
99344
  } else if (updates.pausedReason !== void 0) {
99117
99345
  task.pausedReason = updates.pausedReason;
99118
99346
  }
99347
+ if (updates.wedgeNotification === null) {
99348
+ task.wedgeNotification = void 0;
99349
+ } else if (updates.wedgeNotification !== void 0) {
99350
+ task.wedgeNotification = updates.wedgeNotification;
99351
+ }
99119
99352
  if (updates.tokenBudgetSoftAlertedAt === null) {
99120
99353
  task.tokenBudgetSoftAlertedAt = void 0;
99121
99354
  } else if (updates.tokenBudgetSoftAlertedAt !== void 0) {
@@ -99778,7 +100011,7 @@ var init_store = __esm({
99778
100011
  init_task_store_helpers();
99779
100012
  init_task_row_mappers();
99780
100013
  init_task_mutation_ops();
99781
- init_remaining_ops_1();
100014
+ init_project_store_ops();
99782
100015
  init_workflow_integrity();
99783
100016
  init_branch_group_ops();
99784
100017
  init_archive_lifecycle_2();
@@ -100642,6 +100875,26 @@ var init_store = __esm({
100642
100875
  async updateTask(id, updates, runContext) {
100643
100876
  return updateTaskImpl(this, id, updates, runContext);
100644
100877
  }
100878
+ /**
100879
+ * Atomically opens or resolves the durable task-wedge episode. The task lock is
100880
+ * shared by SQLite and PostgreSQL persistence writes, so concurrent engine
100881
+ * observers cannot allocate two episodes for one active normalized reason.
100882
+ */
100883
+ async claimTaskWedgeNotificationEpisode(taskId, reasonKey) {
100884
+ let result = { claimed: false };
100885
+ await this.updateTaskAtomic(taskId, (current) => {
100886
+ const prior = current.wedgeNotification;
100887
+ if (reasonKey === null) {
100888
+ if (!prior || prior.status === "resolved") return null;
100889
+ return { wedgeNotification: { ...prior, status: "resolved", transitionedAt: (/* @__PURE__ */ new Date()).toISOString() } };
100890
+ }
100891
+ if (prior?.status === "active" && prior.reasonKey === reasonKey) return null;
100892
+ const episodeId = randomUUID37();
100893
+ result = { episodeId, claimed: true };
100894
+ return { wedgeNotification: { reasonKey, episodeId, status: "active", transitionedAt: (/* @__PURE__ */ new Date()).toISOString() } };
100895
+ });
100896
+ return result;
100897
+ }
100645
100898
  async claimNextToolFailureRetry(taskId, expectedCursor, maxRetries) {
100646
100899
  return claimNextToolFailureRetryImpl(this, taskId, expectedCursor, maxRetries);
100647
100900
  }
@@ -172700,6 +172953,7 @@ var init_ntfy_provider = __esm({
172700
172953
  "in-review",
172701
172954
  "merged",
172702
172955
  "failed",
172956
+ "task-wedged",
172703
172957
  "awaiting-approval",
172704
172958
  "awaiting-user-review",
172705
172959
  "planning-awaiting-input",
@@ -172803,6 +173057,15 @@ var init_ntfy_provider = __esm({
172803
173057
  message: `Task "${identifier}" has failed and needs attention`,
172804
173058
  priority: "high"
172805
173059
  },
173060
+ "task-wedged": {
173061
+ title: `Task ${taskId} needs operator action`,
173062
+ message: [
173063
+ `Task "${identifier}" is wedged${typeof payload.metadata?.reason === "string" ? `: ${payload.metadata.reason}` : " and needs operator action"}`,
173064
+ typeof payload.metadata?.gate === "string" ? `Gate: ${payload.metadata.gate}` : null,
173065
+ typeof payload.metadata?.action === "string" ? `Recommended action: ${payload.metadata.action}` : null
173066
+ ].filter((part) => part !== null).join("\n"),
173067
+ priority: "high"
173068
+ },
172806
173069
  "awaiting-approval": {
172807
173070
  title: payload.metadata?.awaitingApprovalReason === "plan-review-replan-cap" ? `Plan Review cap reached for ${taskId}` : `Plan needs approval for ${taskId}`,
172808
173071
  /*
@@ -173157,6 +173420,132 @@ var init_transient_merge_error_classifier = __esm({
173157
173420
  }
173158
173421
  });
173159
173422
 
173423
+ // ../engine/src/notification/task-wedge-notification.ts
173424
+ function describeSelfHealingNoActionWedge(task, stage, metadata) {
173425
+ const description = SELF_HEALING_NO_ACTIONS[stage];
173426
+ if (!description || task.userPaused || task.paused || task.autoMerge === false) return null;
173427
+ const proof = metadata ?? {};
173428
+ if (proof.taskActive === true || proof.hasExecutingTaskLock === true || proof.mergePending === true) return null;
173429
+ if (Array.isArray(proof.livePaths) && proof.livePaths.length > 0) return null;
173430
+ return { reasonKey: `self-healing-no-action:${stage}`, ...description };
173431
+ }
173432
+ function describeTaskWedge(task) {
173433
+ const error2 = task.error ?? "";
173434
+ if (task.pausedReason === "completed-blocked") {
173435
+ return { reasonKey: "completion-blocked", reason: "Completed work is blocked from advancing to review.", action: "Clear the blocker or reset the task to todo." };
173436
+ }
173437
+ if (task.pausedReason === "error-retry-exhausted") {
173438
+ return { reasonKey: "heartbeat-retry-exhausted", reason: "The assigned agent exhausted its heartbeat recovery budget.", action: "Repair the agent configuration, then retry the task." };
173439
+ }
173440
+ if (task.pausedReason === "error-unrecoverable") {
173441
+ return { reasonKey: "heartbeat-error-unrecoverable", reason: "The assigned agent needs operator repair before it can resume.", action: "Repair credentials, access, or configuration, then retry the task." };
173442
+ }
173443
+ const pausedDescriptors = {
173444
+ "branch-cross-contamination": { reasonKey: "branch-cross-contamination", reason: "Branch contamination recovery requires operator intervention.", action: "Inspect the branch history, repair the contamination, then retry or reset to todo." },
173445
+ "branch-conflict-tripwire": { reasonKey: "branch-conflict-tripwire", reason: "A repeated branch conflict stopped automatic recovery.", action: "Resolve the branch conflict, then retry or reset to todo." },
173446
+ "branch-conflict-recovery-exhausted": { reasonKey: "branch-conflict-recovery-exhausted", reason: "Branch conflict recovery retries were exhausted.", action: "Resolve the conflict, then retry or reset to todo." },
173447
+ "branch-conflict-unrecoverable": { reasonKey: "branch-conflict-unrecoverable", reason: "An unrecoverable branch conflict needs operator intervention.", action: "Resolve the conflict or reset the task to todo and retry." },
173448
+ "stuck-loop-exhausted-manual-intervention-required": { reasonKey: "stuck-loop-exhausted", reason: "Self-healing exhausted its stalled-task recovery loop.", action: "Inspect the task state, then retry or reset to todo." },
173449
+ "non-retryable-provider-error": { reasonKey: "non-retryable-provider-error", reason: "A non-retryable provider error stopped the task.", action: "Repair provider access or configuration, then retry the task." },
173450
+ "in-review-stall-deadlock": { reasonKey: "in-review-stall-deadlock", reason: "Review stalled in a deadlock that needs operator intervention.", action: "Inspect review ownership and retry or reset to todo." }
173451
+ };
173452
+ if (task.pausedReason && pausedDescriptors[task.pausedReason]) return pausedDescriptors[task.pausedReason];
173453
+ if (task.status !== "failed") return null;
173454
+ if (error2.startsWith("EXECUTION_DISPATCH_LOOP_EXHAUSTED")) {
173455
+ return { reasonKey: "execution-dispatch-loop-exhausted", reason: "Execution re-queued without progress until its retry budget was exhausted.", action: "Retry, decompose, or rescope the task." };
173456
+ }
173457
+ if (error2.includes("tool failure") || error2.includes("Tool failure")) {
173458
+ return { reasonKey: "tool-failure-retry-exhausted", reason: "Execution tool-failure retries were exhausted.", action: "Inspect the failing tool and retry the task." };
173459
+ }
173460
+ if (/escalat(?:ion|ed).*exhaust|exhaust.*escalat/i.test(error2)) {
173461
+ return { reasonKey: "execution-escalation-exhausted", reason: "The alternate execution escalation was exhausted.", action: "Inspect the failure and retry or rescope the task." };
173462
+ }
173463
+ if (/review.*(?:retry|rework|revision).*(?:exhaust|limit)|(?:exhaust|limit).*review/i.test(error2)) {
173464
+ return { reasonKey: "review-retry-exhausted", reason: "The configured review recovery budget was exhausted.", action: "Review the feedback, fix the task, or use the approved review recovery action." };
173465
+ }
173466
+ const gate = error2.match(/check:([\w:-]+)/)?.[1];
173467
+ if (gate) {
173468
+ return {
173469
+ reasonKey: `merge-blocked:${gate}`,
173470
+ reason: `Merge is blocked by the check:${gate} verification gate.`,
173471
+ action: gate === "changeset-format" ? "Fix the changeset format, then retry the task." : "Fix the failing gate or reset the task to todo and retry.",
173472
+ gate: `check:${gate}`
173473
+ };
173474
+ }
173475
+ if (error2.startsWith("BLOCKED:")) {
173476
+ return { reasonKey: "execution-blocked", reason: "Execution was parked because an external blocker requires operator action.", action: "Resolve the dependency or blocker, then retry the task." };
173477
+ }
173478
+ if (/merge.*(?:blocked|verification|gate)|(?:verification|gate).*(?:failed|exhaust)/i.test(error2)) {
173479
+ return { reasonKey: "merge-blocked", reason: "Merge verification cannot progress without operator action.", action: "Fix the failing verification, then retry the task." };
173480
+ }
173481
+ if (!error2 && (task.mergeRetries ?? 0) < 3) return null;
173482
+ return {
173483
+ reasonKey: "terminal-failed",
173484
+ reason: "The task entered a terminal failed state and needs operator intervention.",
173485
+ action: "Inspect the task error, fix the underlying issue, then retry or reset to todo."
173486
+ };
173487
+ }
173488
+ var SELF_HEALING_NO_ACTIONS;
173489
+ var init_task_wedge_notification = __esm({
173490
+ "../engine/src/notification/task-wedge-notification.ts"() {
173491
+ "use strict";
173492
+ SELF_HEALING_NO_ACTIONS = {
173493
+ "reclaim-pr-conflict": {
173494
+ reason: "Self-healing could not reclaim a stalled pull-request conflict.",
173495
+ action: "Inspect the branch conflict and retry or reset the task to todo."
173496
+ },
173497
+ "reclaim-self-owned-branch-conflict": {
173498
+ reason: "Self-healing could not recover a stalled branch conflict.",
173499
+ action: "Inspect the branch conflict and retry or reset the task to todo."
173500
+ },
173501
+ "reconcile-in-review-unmet-dependencies": {
173502
+ reason: "Unmet dependencies are preventing review from progressing.",
173503
+ action: "Resolve the dependencies, then retry or reset the task to todo."
173504
+ },
173505
+ "reconcile-dependency-blocking-lease": {
173506
+ reason: "A dependency-blocking task lease could not be safely reclaimed.",
173507
+ action: "Inspect the task owner and lease, then retry or reset the task to todo."
173508
+ },
173509
+ "auto-rebound-paused-scope-decay": {
173510
+ reason: "A paused task with stale scope could not be safely resumed.",
173511
+ action: "Inspect the task scope and retry or reset the task to todo."
173512
+ },
173513
+ "stuck-merge-deadlock": {
173514
+ reason: "A merge deadlock needs operator intervention.",
173515
+ action: "Inspect merge ownership and retry or reset the task to todo."
173516
+ },
173517
+ "missing-worktree-merge-active": {
173518
+ reason: "An active merge has an unusable worktree and could not be recovered.",
173519
+ action: "Repair the worktree or reset the task to todo and retry."
173520
+ },
173521
+ "missing-worktree-review": {
173522
+ reason: "Review has an unusable worktree and could not be recovered.",
173523
+ action: "Repair the worktree or reset the task to todo and retry."
173524
+ },
173525
+ "finalize-no-op-review": {
173526
+ reason: "A no-op review task could not be safely finalized.",
173527
+ action: "Inspect merge state and retry or reset the task to todo."
173528
+ },
173529
+ "stale-incomplete-review": {
173530
+ reason: "Incomplete review work could not be safely resumed.",
173531
+ action: "Inspect incomplete steps and retry or reset the task to todo."
173532
+ },
173533
+ "ghost-review": {
173534
+ reason: "A review task has no recoverable workflow owner.",
173535
+ action: "Inspect workflow state and retry or reset the task to todo."
173536
+ },
173537
+ "no-progress-no-task-done": {
173538
+ reason: "Execution stopped without progress and could not be safely requeued.",
173539
+ action: "Inspect the task worktree and retry or reset the task to todo."
173540
+ },
173541
+ "partial-progress-no-task-done": {
173542
+ reason: "Partial execution progress could not be safely resumed.",
173543
+ action: "Inspect partial work and retry or reset the task to todo."
173544
+ }
173545
+ };
173546
+ }
173547
+ });
173548
+
173160
173549
  // ../engine/src/notification/notification-service.ts
173161
173550
  var NotificationService;
173162
173551
  var init_notification_service = __esm({
@@ -173168,6 +173557,7 @@ var init_notification_service = __esm({
173168
173557
  init_transient_merge_error_classifier();
173169
173558
  init_ntfy_provider();
173170
173559
  init_webhook_provider();
173560
+ init_task_wedge_notification();
173171
173561
  NotificationService = class {
173172
173562
  constructor(store, options = {}) {
173173
173563
  this.store = store;
@@ -173198,6 +173588,8 @@ var init_notification_service = __esm({
173198
173588
  failureNotificationSuppressedCount = 0;
173199
173589
  failureNotificationDelayMs = 6e4;
173200
173590
  failureNotificationMode = "sticky-only";
173591
+ /** Compatibility fallback for lightweight test stores without the durable TaskStore CAS. */
173592
+ activeWedgeReasons = /* @__PURE__ */ new Map();
173201
173593
  attachChatStore(chatStore) {
173202
173594
  if (this.chatStore && this.chatStore !== chatStore) {
173203
173595
  this.detachChatStoreListener(this.chatStore);
@@ -173309,6 +173701,10 @@ var init_notification_service = __esm({
173309
173701
  }
173310
173702
  }
173311
173703
  handleTaskUpdated = (task) => {
173704
+ const transientFailure = task.status === "failed" ? classifyTransientMergeError(task.error) : null;
173705
+ const wedge = transientFailure ? null : describeTaskWedge(task);
173706
+ if (wedge) this.cancelPendingFailureNotification(task.id, "classified-terminal-wedge");
173707
+ if (!transientFailure) void this.maybeNotifyTaskWedge(task, wedge);
173312
173708
  void this.maybeSuppressTransientFailedNotification(task, `status=${task.status ?? "undefined"}`);
173313
173709
  if (task.status === "awaiting-approval") {
173314
173710
  void this.writeAwaitingApprovalMailboxMessage(task);
@@ -173319,7 +173715,7 @@ var init_notification_service = __esm({
173319
173715
  if (!this.notificationsEnabled) {
173320
173716
  return;
173321
173717
  }
173322
- if (task.status === "failed") {
173718
+ if (task.status === "failed" && !wedge) {
173323
173719
  const transientClass = classifyTransientMergeError(task.error);
173324
173720
  if (transientClass) {
173325
173721
  this.failureNotificationSuppressedCount += 1;
@@ -173405,6 +173801,69 @@ var init_notification_service = __esm({
173405
173801
  );
173406
173802
  }
173407
173803
  }
173804
+ /*
173805
+ FNXC:TaskWedgeNotifications 2026-07-22-12:00:
173806
+ Terminal task updates are the shared seam for merger, executor, heartbeat, and
173807
+ self-healing parks. Clear only on a non-wedge lifecycle update, allowing a
173808
+ resolved-then-reparked reason to begin a new actionable episode.
173809
+ */
173810
+ /** Delivers a self-healing no-action escalation through the durable wedge episode seam. */
173811
+ async notifyTaskWedge(task, descriptor) {
173812
+ await this.maybeNotifyTaskWedge(task, descriptor);
173813
+ }
173814
+ async maybeNotifyTaskWedge(task, suppliedDescriptor) {
173815
+ const descriptor = suppliedDescriptor ?? describeTaskWedge(task);
173816
+ let episode;
173817
+ if (!descriptor) {
173818
+ const isActiveSelfHealingNoAction = task.wedgeNotification?.status === "active" && task.wedgeNotification.reasonKey.startsWith("self-healing-no-action:");
173819
+ const hasProgressed = task.column === "todo" || task.column === "in-progress" || task.column === "done" || task.column === "archived" || !isActiveSelfHealingNoAction && typeof task.status === "string" && task.status !== "failed" || isActiveSelfHealingNoAction && ["queued", "planning", "in-progress", "merging", "merging-pr", "merged", "done"].includes(task.status ?? "");
173820
+ if (hasProgressed) {
173821
+ this.activeWedgeReasons.delete(task.id);
173822
+ await this.store.claimTaskWedgeNotificationEpisode?.(task.id, null);
173823
+ }
173824
+ return;
173825
+ }
173826
+ if (this.store.claimTaskWedgeNotificationEpisode) {
173827
+ const claim = await this.store.claimTaskWedgeNotificationEpisode(task.id, descriptor.reasonKey);
173828
+ if (!claim.claimed || !claim.episodeId) return;
173829
+ episode = claim.episodeId;
173830
+ } else {
173831
+ if (this.activeWedgeReasons.get(task.id) === descriptor.reasonKey) return;
173832
+ this.activeWedgeReasons.set(task.id, descriptor.reasonKey);
173833
+ episode = `${task.id}:${descriptor.reasonKey}:${task.updatedAt}`;
173834
+ }
173835
+ const link = buildNtfyClickUrl({ dashboardHost: this.dashboardHost, projectId: this.options.projectId, taskId: task.id });
173836
+ const content = [
173837
+ `**${formatTaskIdentifier(task)} needs operator action**`,
173838
+ "",
173839
+ descriptor.reason,
173840
+ ...descriptor.gate ? [`Gate: \`${descriptor.gate}\`.`] : [],
173841
+ `Recommended action: ${descriptor.action}`,
173842
+ ...link ? ["", `[Open ${task.id}](${link})`] : []
173843
+ ].join("\n");
173844
+ const payload = {
173845
+ taskId: task.id,
173846
+ taskTitle: task.title,
173847
+ taskDescription: task.description,
173848
+ event: "task-wedged",
173849
+ // Bounded descriptor text is operator-facing provider content, never audit metadata.
173850
+ metadata: { wedgeReason: descriptor.reasonKey, reason: descriptor.reason, action: descriptor.action, ...descriptor.gate ? { gate: descriptor.gate } : {}, notificationDedupeKey: `task-wedge:${episode}` }
173851
+ };
173852
+ void this.dispatch("task-wedged", payload);
173853
+ try {
173854
+ await this.options.messageStore?.sendMessageOnce?.({
173855
+ fromId: "system",
173856
+ fromType: "system",
173857
+ toId: DASHBOARD_USER_ID,
173858
+ toType: "user",
173859
+ type: "system",
173860
+ content,
173861
+ metadata: { taskId: task.id, kind: "task-wedge", wedgeReason: descriptor.reasonKey, ...descriptor.gate ? { gate: descriptor.gate } : {} }
173862
+ }, `task-wedge:${episode}`);
173863
+ } catch (error2) {
173864
+ schedulerLog.log(`[notify] ${task.id} wedge mailbox message failed: ${error2 instanceof Error ? error2.message : String(error2)}`);
173865
+ }
173866
+ }
173408
173867
  isTriageDuplicateDecision(task) {
173409
173868
  return task.paused === true && task.pausedReason === "duplicate-decision-required" && task.sourceMetadata?.duplicateSource === "triage-marker" && typeof task.sourceMetadata.nearDuplicateOf === "string";
173410
173869
  }
@@ -173770,6 +174229,11 @@ var init_notification_service = __esm({
173770
174229
  );
173771
174230
  return;
173772
174231
  }
174232
+ if (describeTaskWedge(task)) {
174233
+ this.failureNotificationSuppressedCount += 1;
174234
+ schedulerLog.log(`[notify] ${taskId} classified terminal wedge at dispatch time \u2014 suppressed generic failed notification`);
174235
+ return;
174236
+ }
173773
174237
  const isTerminal2 = task.paused === true || task.column === "in-review";
173774
174238
  if (this.failureNotificationMode === "terminal-only" && !isTerminal2) {
173775
174239
  this.failureNotificationSuppressedCount += 1;
@@ -174598,7 +175062,10 @@ var init_notifier = __esm({
174598
175062
  DEFAULT_NTFY_EVENTS = [
174599
175063
  "in-review",
174600
175064
  "merged",
175065
+ // FNXC:TaskWedgeNotifications 2026-07-22-19:00: Wedge episodes are operator
175066
+ // escalation events, so normal Ntfy defaults must not silently filter them.
174601
175067
  "failed",
175068
+ "task-wedged",
174602
175069
  "awaiting-approval",
174603
175070
  "awaiting-user-review",
174604
175071
  "planning-awaiting-input",
@@ -201146,6 +201613,7 @@ var init_self_healing = __esm({
201146
201613
  init_dependency_blocked_todo_reporter();
201147
201614
  init_scheduler();
201148
201615
  init_task_agent_sync();
201616
+ init_task_wedge_notification();
201149
201617
  init_self_healing_constants();
201150
201618
  init_self_healing_constants();
201151
201619
  init_self_healing_branch();
@@ -201453,6 +201921,15 @@ var init_self_healing = __esm({
201453
201921
  const message = error2 instanceof Error ? error2.message : String(error2);
201454
201922
  log28.warn(`[${stage}] ${task.id}: no-action audit emission failed: ${message}`);
201455
201923
  }
201924
+ const descriptor = describeSelfHealingNoActionWedge(task, stage, proof.metadata);
201925
+ if (descriptor) {
201926
+ try {
201927
+ await getActiveNotificationService()?.notifyTaskWedge(task, descriptor);
201928
+ } catch (error2) {
201929
+ const message = error2 instanceof Error ? error2.message : String(error2);
201930
+ log28.warn(`[${stage}] ${task.id}: wedge notification failed: ${message}`);
201931
+ }
201932
+ }
201456
201933
  log28.log(`[${stage}] ${task.id}: triple-proof not satisfied \u2014 no action (operator-decides)`);
201457
201934
  }
201458
201935
  /*
@@ -262299,6 +262776,70 @@ async function resolvePlanningModeSystemPrompt(store, promptOverrides, workflowI
262299
262776
 
262300
262777
  ${PLANNING_SYSTEM_PROMPT}`;
262301
262778
  }
262779
+ function isPlanningTurnActive(sessionId) {
262780
+ return activeGenerations.has(sessionId) || pendingTurnReservations.has(sessionId);
262781
+ }
262782
+ function reservePlanningTurn(sessionId) {
262783
+ if (isPlanningTurnActive(sessionId)) {
262784
+ throw new GenerationInProgressError("Generation already in progress");
262785
+ }
262786
+ let resolveDone;
262787
+ const done = new Promise((resolve101) => {
262788
+ resolveDone = resolve101;
262789
+ });
262790
+ const reservation = { done, resolveDone };
262791
+ pendingTurnReservations.set(sessionId, reservation);
262792
+ return () => {
262793
+ if (pendingTurnReservations.get(sessionId) === reservation) {
262794
+ pendingTurnReservations.delete(sessionId);
262795
+ }
262796
+ reservation.resolveDone();
262797
+ };
262798
+ }
262799
+ function trackTurnOperationSettled(sessionId, operationPromise) {
262800
+ const settled = operationPromise.then(
262801
+ () => {
262802
+ },
262803
+ () => {
262804
+ }
262805
+ );
262806
+ settlingTurnOperations.set(sessionId, settled);
262807
+ void settled.then(() => {
262808
+ if (settlingTurnOperations.get(sessionId) === settled) {
262809
+ settlingTurnOperations.delete(sessionId);
262810
+ }
262811
+ });
262812
+ }
262813
+ async function waitForTurnOperationSettled(sessionId, timeoutMs = 2e3) {
262814
+ const settling = settlingTurnOperations.get(sessionId);
262815
+ if (!settling) return true;
262816
+ let timer2;
262817
+ const timedOut = await Promise.race([
262818
+ settling.then(() => false),
262819
+ new Promise((resolve101) => {
262820
+ timer2 = setTimeout(() => resolve101(true), timeoutMs);
262821
+ })
262822
+ ]);
262823
+ clearTimeout(timer2);
262824
+ return !timedOut;
262825
+ }
262826
+ async function waitForPlanningTurnRelease(sessionId, timeoutMs = 2e3) {
262827
+ const deadline = Date.now() + timeoutMs;
262828
+ while (isPlanningTurnActive(sessionId)) {
262829
+ const remaining = deadline - Date.now();
262830
+ if (remaining <= 0) return false;
262831
+ const reservation = pendingTurnReservations.get(sessionId);
262832
+ if (reservation) {
262833
+ await Promise.race([
262834
+ reservation.done,
262835
+ new Promise((resolve101) => setTimeout(resolve101, Math.min(remaining, 50)))
262836
+ ]);
262837
+ } else {
262838
+ await new Promise((resolve101) => setTimeout(resolve101, 10));
262839
+ }
262840
+ }
262841
+ return true;
262842
+ }
262302
262843
  function isTaskPriority2(value) {
262303
262844
  return typeof value === "string" && TASK_PRIORITIES.includes(value);
262304
262845
  }
@@ -262695,7 +263236,7 @@ async function getFirstQuestionFromAgent(session, message) {
262695
263236
  if (!session.agent) {
262696
263237
  throw new InvalidSessionStateError("AI agent not initialized");
262697
263238
  }
262698
- await session.agent.session.prompt(message);
263239
+ await promptPlanningAgent(session.agent.session, message);
262699
263240
  const lastMessage = session.agent.session.state.messages.filter((m2) => m2.role === "assistant").pop();
262700
263241
  let responseText = "";
262701
263242
  if (lastMessage?.content) {
@@ -262734,7 +263275,8 @@ async function getFirstQuestionFromAgent(session, message) {
262734
263275
  lastError = err instanceof Error ? err : new Error(String(err));
262735
263276
  if (attempt < MAX_PARSE_RETRIES2) {
262736
263277
  try {
262737
- await session.agent.session.prompt(
263278
+ await promptPlanningAgent(
263279
+ session.agent.session,
262738
263280
  'Your previous response could not be parsed as JSON. Please respond with ONLY a valid JSON object: {"type":"question","data":{"runningPlan":{...},...}}. No markdown, no explanation, just the JSON.'
262739
263281
  );
262740
263282
  const retryMessage = session.agent.session.state.messages.filter((m2) => m2.role === "assistant").pop();
@@ -262794,7 +263336,8 @@ function buildMandatoryFirstPlanningQuestion(userInput = "") {
262794
263336
  }
262795
263337
  async function requestMandatoryFirstPlanningQuestion(session, abortSignal) {
262796
263338
  try {
262797
- await session.agent.session.prompt(
263339
+ await promptPlanningAgent(
263340
+ session.agent.session,
262798
263341
  'Before producing a plan, ask one clarifying question. Return ONLY valid JSON: {"type":"question","data":{...}}.',
262799
263342
  { signal: abortSignal }
262800
263343
  );
@@ -262967,8 +263510,14 @@ async function startExistingSession(sessionId, rootDir, store, modelProvider, mo
262967
263510
  session.clarificationEnabled = runtimeOptions.clarificationEnabled === true;
262968
263511
  session.ntfyConfig = runtimeOptions.ntfyConfig;
262969
263512
  }
263513
+ if (isPlanningTurnActive(sessionId) || planningStreamManager.hasPendingInitialTurn(sessionId)) {
263514
+ diagnostics2.warn("Ignoring duplicate start for planning session with an active generation", {
263515
+ sessionId,
263516
+ operation: "start-existing-duplicate"
263517
+ });
263518
+ return;
263519
+ }
262970
263520
  beginPlanningGeneration(session, "initial_plan");
262971
- await persistSession(session, "generating");
262972
263521
  planningStreamManager.registerInitialTurn(sessionId, () => {
262973
263522
  session.pluginRunner = pluginRunner;
262974
263523
  initializeAgent(session, rootDir, store, modelProvider, modelId, session.draftThinkingLevel, promptOverrides, pluginRunner).catch((err) => {
@@ -262980,6 +263529,7 @@ async function startExistingSession(sessionId, rootDir, store, modelProvider, mo
262980
263529
  });
262981
263530
  });
262982
263531
  });
263532
+ await persistSession(session, "generating");
262983
263533
  }
262984
263534
  async function createSessionWithAgent(ip, initialPlan, rootDir, store, modelProvider, modelId, thinkingLevelOrPromptOverrides, promptOverridesOrOptions, optionsMaybe) {
262985
263535
  const thinkingLevel = isThinkingLevel(thinkingLevelOrPromptOverrides) ? thinkingLevelOrPromptOverrides : void 0;
@@ -263042,6 +263592,19 @@ async function createSessionWithAgent(ip, initialPlan, rootDir, store, modelProv
263042
263592
  return sessionId;
263043
263593
  }
263044
263594
  async function initializeAgent(session, rootDir, store, modelProvider, modelId, thinkingLevel, promptOverrides, pluginRunner) {
263595
+ let releaseTurn;
263596
+ try {
263597
+ releaseTurn = reservePlanningTurn(session.id);
263598
+ } catch (err) {
263599
+ if (err instanceof GenerationInProgressError) {
263600
+ diagnostics2.warn("Skipping duplicate initial planning turn \u2014 generation already active", {
263601
+ sessionId: session.id,
263602
+ operation: "initialize-agent-duplicate"
263603
+ });
263604
+ return;
263605
+ }
263606
+ throw err;
263607
+ }
263045
263608
  try {
263046
263609
  await runGenerationWithTimeout(session, async (abortSignal) => {
263047
263610
  const agentPromise = createPlanningAgent(
@@ -263091,12 +263654,16 @@ async function initializeAgent(session, rootDir, store, modelProvider, modelId,
263091
263654
  type: "error",
263092
263655
  data: errorMessage4
263093
263656
  });
263657
+ } finally {
263658
+ releaseTurn();
263094
263659
  }
263095
263660
  }
263096
263661
  async function createPlanningAgent(session, rootDir, store, modelProvider, modelId, thinkingLevel, promptOverrides, pluginRunner) {
263097
263662
  await ensureEngineReady();
263098
263663
  const systemPrompt = await resolvePlanningModeSystemPrompt(store, promptOverrides, session.workflowId);
263099
263664
  const skillContext = buildSessionSkillContextSync(null, "executor", rootDir, pluginRunner);
263665
+ const callbackEpoch = session.agentCallbackEpoch ?? 0;
263666
+ const callbacksInvalidated = () => (session.agentCallbackEpoch ?? 0) !== callbackEpoch;
263100
263667
  return createFnAgent4({
263101
263668
  cwd: rootDir,
263102
263669
  systemPrompt,
@@ -263120,6 +263687,7 @@ async function createPlanningAgent(session, rootDir, store, modelProvider, model
263120
263687
  } : {},
263121
263688
  ...thinkingLevel ? { defaultThinkingLevel: thinkingLevel } : {},
263122
263689
  onThinking: (delta) => {
263690
+ if (callbacksInvalidated()) return;
263123
263691
  markPlanningGenerationProgress(session.id, delta);
263124
263692
  session.thinkingOutput += delta;
263125
263693
  persistThinking(session.id, session.thinkingOutput);
@@ -263129,6 +263697,7 @@ async function createPlanningAgent(session, rootDir, store, modelProvider, model
263129
263697
  });
263130
263698
  },
263131
263699
  onText: (delta) => {
263700
+ if (callbacksInvalidated()) return;
263132
263701
  markPlanningGenerationProgress(session.id, delta);
263133
263702
  session.thinkingOutput += delta;
263134
263703
  persistThinking(session.id, session.thinkingOutput);
@@ -263175,7 +263744,7 @@ async function ensureSessionAgent(session, rootDir, historyForReplay, promptOver
263175
263744
  if (abortSignal.aborted) {
263176
263745
  throw createAbortError2();
263177
263746
  }
263178
- await session.agent.session.prompt(contextMessage, {
263747
+ await promptPlanningAgent(session.agent.session, contextMessage, {
263179
263748
  signal: abortSignal
263180
263749
  });
263181
263750
  if (abortSignal.aborted) {
@@ -263213,7 +263782,7 @@ async function maybeNotifyPlanningAwaitingInput(session, question, proactiveClar
263213
263782
  }
263214
263783
  }
263215
263784
  function buildRetryableParseErrorMessage(error2) {
263216
- const baseMessage = (error2?.message || "Failed to parse AI response").replace(/\s*Please try again\.?\s*$/i, "").trim();
263785
+ const baseMessage = (error2?.message || "Failed to parse AI response").replace(/\s*Please try again\.?\s*$/i, "").replace(/[.\s]+$/, "").trim();
263217
263786
  return `${baseMessage}. Retry this planning session or start a new one.`;
263218
263787
  }
263219
263788
  function setSessionError(session, message) {
@@ -263230,6 +263799,19 @@ function createAbortError2() {
263230
263799
  error2.name = "AbortError";
263231
263800
  return error2;
263232
263801
  }
263802
+ async function promptPlanningAgent(agentSession, message, options) {
263803
+ let promptWithFallback2;
263804
+ try {
263805
+ promptWithFallback2 = promptWithFallback;
263806
+ } catch {
263807
+ promptWithFallback2 = void 0;
263808
+ }
263809
+ if (typeof promptWithFallback2 === "function") {
263810
+ await promptWithFallback2(agentSession, message, options);
263811
+ return;
263812
+ }
263813
+ await agentSession.prompt(message, options);
263814
+ }
263233
263815
  function normalizeGenerationProgress(output) {
263234
263816
  return output.replace(/\s+/g, " ").trim();
263235
263817
  }
@@ -263305,7 +263887,9 @@ async function runGenerationWithTimeout(session, operation) {
263305
263887
  );
263306
263888
  });
263307
263889
  try {
263308
- return await Promise.race([operation(abortController.signal), abortPromise]);
263890
+ const operationPromise = operation(abortController.signal);
263891
+ trackTurnOperationSettled(session.id, operationPromise);
263892
+ return await Promise.race([operationPromise, abortPromise]);
263309
263893
  } finally {
263310
263894
  clearTimeout(generationRecord.timer);
263311
263895
  if (activeGenerations.get(session.id) === generationRecord) {
@@ -263468,7 +264052,7 @@ async function continueAgentConversation(session, message) {
263468
264052
  if (abortSignal.aborted) {
263469
264053
  throw createAbortError2();
263470
264054
  }
263471
- await session.agent.session.prompt(message, {
264055
+ await promptPlanningAgent(session.agent.session, message, {
263472
264056
  signal: abortSignal
263473
264057
  });
263474
264058
  if (abortSignal.aborted) {
@@ -263518,7 +264102,8 @@ async function continueAgentConversation(session, message) {
263518
264102
  if (abortSignal.aborted) {
263519
264103
  throw createAbortError2();
263520
264104
  }
263521
- await session.agent.session.prompt(
264105
+ await promptPlanningAgent(
264106
+ session.agent.session,
263522
264107
  'Your previous response could not be parsed as JSON. Please respond with ONLY a valid JSON object: {"type":"question","data":{"runningPlan":{...},...}}. No markdown, no explanation, just the JSON.',
263523
264108
  { signal: abortSignal }
263524
264109
  );
@@ -263794,57 +264379,62 @@ async function submitResponse(sessionId, responses, rootDir, promptOverrides, st
263794
264379
  }
263795
264380
  if (store && !session.store) session.store = store;
263796
264381
  if (rootDir && !session.rootDir) session.rootDir = rootDir;
263797
- if (activeGenerations.has(session.id)) {
264382
+ if (isPlanningTurnActive(session.id)) {
263798
264383
  if (didSubmitSameAnswer(session, responses)) {
263799
264384
  throw new GenerationInProgressError("Generation already in progress for this response");
263800
264385
  }
263801
264386
  throw new GenerationInProgressError("Generation already in progress");
263802
264387
  }
264388
+ const releaseTurn = reservePlanningTurn(session.id);
263803
264389
  let answeredQuestion;
263804
- if (isRefineRequest(responses) && session.summary) {
263805
- beginPlanningGeneration(session, "question");
263806
- session.currentQuestion = void 0;
263807
- session.error = void 0;
263808
- await persistSession(session, "generating");
263809
- await ensureSessionAgent(session, rootDir, session.history, promptOverrides, store);
263810
- const focus = typeof responses.focus === "string" ? responses.focus.trim() : void 0;
263811
- const refineMessage = formatRefineRequestForAgent(session.summary, focus);
263812
- await continueAgentConversation(session, refineMessage);
263813
- } else if (!session.currentQuestion) {
263814
- throw new InvalidSessionStateError("No active question in session");
263815
- } else {
263816
- const currentQuestion = captureOtherCustomText(session.currentQuestion, responses);
263817
- const historyEntry = {
263818
- question: currentQuestion,
263819
- response: responses,
263820
- thinkingOutput: session.lastGeneratedThinking || ""
263821
- };
263822
- session.error = void 0;
263823
- const editIndex = session.editingQuestionId ? session.history.findIndex((entry) => entry.question.id === session.editingQuestionId) : -1;
263824
- const isEditingPriorAnswer = editIndex >= 0;
263825
- if (isEditingPriorAnswer) {
263826
- session.history[editIndex] = historyEntry;
263827
- session.editingQuestionId = void 0;
263828
- session.summary = buildRunningSummary(session.initialPlan, session.history);
263829
- disposeSessionAgentForRetry(session);
264390
+ try {
264391
+ if (isRefineRequest(responses) && session.summary) {
264392
+ beginPlanningGeneration(session, "question");
264393
+ session.currentQuestion = void 0;
264394
+ session.error = void 0;
264395
+ await persistSession(session, "generating");
264396
+ await ensureSessionAgent(session, rootDir, session.history, promptOverrides, store);
264397
+ const focus = typeof responses.focus === "string" ? responses.focus.trim() : void 0;
264398
+ const refineMessage = formatRefineRequestForAgent(session.summary, focus);
264399
+ await continueAgentConversation(session, refineMessage);
264400
+ } else if (!session.currentQuestion) {
264401
+ throw new InvalidSessionStateError("No active question in session");
263830
264402
  } else {
263831
- session.history.push(historyEntry);
263832
- }
263833
- answeredQuestion = currentQuestion;
263834
- beginPlanningGeneration(session, "plan_update");
263835
- session.currentQuestion = void 0;
263836
- await persistSession(session, "generating");
263837
- if (!session.agent) {
263838
- await ensureSessionAgent(
263839
- session,
263840
- rootDir,
263841
- isEditingPriorAnswer ? session.history : session.history.slice(0, -1),
263842
- promptOverrides,
263843
- store
263844
- );
264403
+ const currentQuestion = captureOtherCustomText(session.currentQuestion, responses);
264404
+ const historyEntry = {
264405
+ question: currentQuestion,
264406
+ response: responses,
264407
+ thinkingOutput: session.lastGeneratedThinking || ""
264408
+ };
264409
+ session.error = void 0;
264410
+ const editIndex = session.editingQuestionId ? session.history.findIndex((entry) => entry.question.id === session.editingQuestionId) : -1;
264411
+ const isEditingPriorAnswer = editIndex >= 0;
264412
+ if (isEditingPriorAnswer) {
264413
+ session.history[editIndex] = historyEntry;
264414
+ session.editingQuestionId = void 0;
264415
+ session.summary = buildRunningSummary(session.initialPlan, session.history);
264416
+ disposeSessionAgentForRetry(session);
264417
+ } else {
264418
+ session.history.push(historyEntry);
264419
+ }
264420
+ answeredQuestion = currentQuestion;
264421
+ beginPlanningGeneration(session, "plan_update");
264422
+ session.currentQuestion = void 0;
264423
+ await persistSession(session, "generating");
264424
+ if (!session.agent) {
264425
+ await ensureSessionAgent(
264426
+ session,
264427
+ rootDir,
264428
+ isEditingPriorAnswer ? session.history : session.history.slice(0, -1),
264429
+ promptOverrides,
264430
+ store
264431
+ );
264432
+ }
264433
+ const message = isEditingPriorAnswer ? "An earlier answer was edited. Use the complete preserved interview context above, regenerate the running plan, and ask exactly one next question." : formatResponseForAgent(currentQuestion, responses);
264434
+ await continueAgentConversation(session, message);
263845
264435
  }
263846
- const message = isEditingPriorAnswer ? "An earlier answer was edited. Use the complete preserved interview context above, regenerate the running plan, and ask exactly one next question." : formatResponseForAgent(currentQuestion, responses);
263847
- await continueAgentConversation(session, message);
264436
+ } finally {
264437
+ releaseTurn();
263848
264438
  }
263849
264439
  if (session.currentQuestion) {
263850
264440
  return { type: "question", data: session.currentQuestion };
@@ -263875,26 +264465,31 @@ async function retrySession(sessionId, rootDir, promptOverrides, store) {
263875
264465
  if (!inErrorState) {
263876
264466
  throw new InvalidSessionStateError(`Planning session ${sessionId} is not in an error state`);
263877
264467
  }
263878
- disposeSessionAgentForRetry(session);
263879
- session.error = void 0;
263880
- session.summary = void 0;
263881
- session.currentQuestion = void 0;
263882
- session.updatedAt = /* @__PURE__ */ new Date();
263883
- beginPlanningGeneration(session, session.history.length === 0 ? "initial_plan" : "plan_update");
263884
- await persistSession(session, "generating");
263885
- if (session.history.length === 0) {
263886
- await ensureSessionAgent(session, rootDir, [], promptOverrides, store);
263887
- await continueAgentConversation(session, formatInitialRunningPlanRequestForAgent(session.initialPlan));
263888
- return;
264468
+ const releaseTurn = reservePlanningTurn(session.id);
264469
+ try {
264470
+ disposeSessionAgentForRetry(session);
264471
+ session.error = void 0;
264472
+ session.summary = void 0;
264473
+ session.currentQuestion = void 0;
264474
+ session.updatedAt = /* @__PURE__ */ new Date();
264475
+ beginPlanningGeneration(session, session.history.length === 0 ? "initial_plan" : "plan_update");
264476
+ await persistSession(session, "generating");
264477
+ if (session.history.length === 0) {
264478
+ await ensureSessionAgent(session, rootDir, [], promptOverrides, store);
264479
+ await continueAgentConversation(session, formatInitialRunningPlanRequestForAgent(session.initialPlan));
264480
+ return;
264481
+ }
264482
+ const replayHistory = session.history.slice(0, -1);
264483
+ const lastEntry = session.history[session.history.length - 1];
264484
+ await ensureSessionAgent(session, rootDir, replayHistory, promptOverrides, store);
264485
+ const replayMessage = formatResponseForAgent(
264486
+ lastEntry.question,
264487
+ coerceResponseRecord(lastEntry.question, lastEntry.response)
264488
+ );
264489
+ await continueAgentConversation(session, replayMessage);
264490
+ } finally {
264491
+ releaseTurn();
263889
264492
  }
263890
- const replayHistory = session.history.slice(0, -1);
263891
- const lastEntry = session.history[session.history.length - 1];
263892
- await ensureSessionAgent(session, rootDir, replayHistory, promptOverrides, store);
263893
- const replayMessage = formatResponseForAgent(
263894
- lastEntry.question,
263895
- coerceResponseRecord(lastEntry.question, lastEntry.response)
263896
- );
263897
- await continueAgentConversation(session, replayMessage);
263898
264493
  }
263899
264494
  async function rewindSession(sessionId, questionId, rootDir, promptOverrides, store) {
263900
264495
  const session = await getSession(sessionId);
@@ -263909,30 +264504,52 @@ async function rewindSession(sessionId, questionId, rootDir, promptOverrides, st
263909
264504
  if (session.history.length === 0) {
263910
264505
  throw new InvalidSessionStateError("Planning session has no previous question to rewind to");
263911
264506
  }
263912
- const rewindIndex = questionId ? session.history.findIndex((entry) => entry.question.id === questionId) : session.history.length - 1;
263913
- if (rewindIndex < 0) {
263914
- throw new InvalidSessionStateError("Planning question to edit was not found");
264507
+ const activeGeneration = activeGenerations.get(session.id);
264508
+ if (activeGeneration) {
264509
+ activeGeneration.abortReason = "user-stop";
264510
+ clearTimeout(activeGeneration.timer);
264511
+ activeGeneration.abortTeardown();
264512
+ activeGeneration.abortController.abort();
264513
+ activeGenerations.delete(session.id);
263915
264514
  }
263916
- const rewindEntry = session.history[rewindIndex];
263917
- if (!questionId) session.history.pop();
263918
- disposeSessionAgentForRetry(session);
263919
- session.currentQuestion = rewindEntry.question;
263920
- session.editingQuestionId = questionId ? questionId : void 0;
263921
- session.summary = buildRunningSummary(session.initialPlan, session.history);
263922
- session.error = void 0;
263923
- session.lastGeneratedThinking = session.history[session.history.length - 1]?.thinkingOutput ?? "";
263924
- session.thinkingOutput = "";
263925
- session.updatedAt = /* @__PURE__ */ new Date();
263926
- if (!session.agent && rootDir) {
263927
- await ensureSessionAgent(session, rootDir, session.history, promptOverrides, store);
264515
+ if (!await waitForPlanningTurnRelease(session.id)) {
264516
+ throw new GenerationInProgressError("Generation already in progress");
264517
+ }
264518
+ const releaseTurn = reservePlanningTurn(session.id);
264519
+ try {
264520
+ if (!await waitForTurnOperationSettled(session.id)) {
264521
+ diagnostics2.warn("Rewinding past a provider prompt that has not settled after abort", {
264522
+ sessionId: session.id,
264523
+ operation: "rewind-unsettled-prompt"
264524
+ });
264525
+ }
264526
+ const rewindIndex = questionId ? session.history.findIndex((entry) => entry.question.id === questionId) : session.history.length - 1;
264527
+ if (rewindIndex < 0) {
264528
+ throw new InvalidSessionStateError("Planning question to edit was not found");
264529
+ }
264530
+ const rewindEntry = session.history[rewindIndex];
264531
+ if (!questionId) session.history.pop();
264532
+ disposeSessionAgentForRetry(session);
264533
+ session.currentQuestion = rewindEntry.question;
264534
+ session.editingQuestionId = questionId ? questionId : void 0;
264535
+ session.summary = buildRunningSummary(session.initialPlan, session.history);
264536
+ session.error = void 0;
264537
+ session.lastGeneratedThinking = session.history[session.history.length - 1]?.thinkingOutput ?? "";
264538
+ session.thinkingOutput = "";
264539
+ session.updatedAt = /* @__PURE__ */ new Date();
264540
+ if (!session.agent && rootDir) {
264541
+ await ensureSessionAgent(session, rootDir, session.history, promptOverrides, store);
264542
+ }
264543
+ persistSession(session, "awaiting_input");
264544
+ planningStreamManager.broadcast(session.id, { type: "summary", data: session.summary });
264545
+ planningStreamManager.broadcast(session.id, { type: "question", data: rewindEntry.question });
264546
+ return {
264547
+ currentQuestion: rewindEntry.question,
264548
+ history: [...session.history]
264549
+ };
264550
+ } finally {
264551
+ releaseTurn();
263928
264552
  }
263929
- persistSession(session, "awaiting_input");
263930
- planningStreamManager.broadcast(session.id, { type: "summary", data: session.summary });
263931
- planningStreamManager.broadcast(session.id, { type: "question", data: rewindEntry.question });
263932
- return {
263933
- currentQuestion: rewindEntry.question,
263934
- history: [...session.history]
263935
- };
263936
264553
  }
263937
264554
  function stopGeneration(sessionId) {
263938
264555
  const session = sessions.get(sessionId);
@@ -264047,6 +264664,7 @@ function coerceResponseRecord(question, response) {
264047
264664
  };
264048
264665
  }
264049
264666
  function disposeSessionAgentForRetry(session) {
264667
+ session.agentCallbackEpoch = (session.agentCallbackEpoch ?? 0) + 1;
264050
264668
  if (!session.agent) {
264051
264669
  return;
264052
264670
  }
@@ -264340,6 +264958,8 @@ function __resetPlanningState() {
264340
264958
  rateLimits2.clear();
264341
264959
  planningStreamManager.reset();
264342
264960
  activeGenerations.clear();
264961
+ pendingTurnReservations.clear();
264962
+ settlingTurnOperations.clear();
264343
264963
  if (_aiSessionStore && _aiSessionDeletedListener) {
264344
264964
  _aiSessionStore.off("ai_session:deleted", _aiSessionDeletedListener);
264345
264965
  }
@@ -264364,7 +264984,7 @@ async function __runGenerationWithTimeoutForTests(sessionId, operation) {
264364
264984
  }
264365
264985
  return runGenerationWithTimeout(session, operation);
264366
264986
  }
264367
- var PLANNING_NO_AMBIENT_TASK_ID, PLANNING_BUILTIN_WEB_TOOLS, createFnAgent4, planningNtfyHelpers, diagnostics2, PLANNING_SYSTEM_PROMPT, DRAFT_PLACEHOLDER_TITLE, SESSION_TTL_MS, CLEANUP_INTERVAL_MS2, MAX_SESSIONS_PER_IP_PER_HOUR, RATE_LIMIT_WINDOW_MS2, GENERATION_TIMEOUT_MS, GENERATION_LOOP_REPEAT_LIMIT, PLANNING_STUCK_ERROR_MESSAGE, PLANNING_LOOP_ERROR_MESSAGE, sessions, rateLimits2, activeGenerations, _aiSessionStore, _aiSessionDeletedListener, sessionPersistenceQueues, cleanupInterval2, PlanningStreamManager, planningStreamManager, MAX_PARSE_RETRIES2, TruncatedPlanningCompletionError, RateLimitError3, SessionNotFoundError, InvalidSessionStateError, GenerationInProgressError;
264987
+ var PLANNING_NO_AMBIENT_TASK_ID, PLANNING_BUILTIN_WEB_TOOLS, createFnAgent4, planningNtfyHelpers, diagnostics2, PLANNING_SYSTEM_PROMPT, DRAFT_PLACEHOLDER_TITLE, SESSION_TTL_MS, CLEANUP_INTERVAL_MS2, MAX_SESSIONS_PER_IP_PER_HOUR, RATE_LIMIT_WINDOW_MS2, GENERATION_TIMEOUT_MS, GENERATION_LOOP_REPEAT_LIMIT, PLANNING_STUCK_ERROR_MESSAGE, PLANNING_LOOP_ERROR_MESSAGE, sessions, rateLimits2, activeGenerations, pendingTurnReservations, settlingTurnOperations, _aiSessionStore, _aiSessionDeletedListener, sessionPersistenceQueues, cleanupInterval2, PlanningStreamManager, planningStreamManager, MAX_PARSE_RETRIES2, TruncatedPlanningCompletionError, RateLimitError3, SessionNotFoundError, InvalidSessionStateError, GenerationInProgressError;
264368
264988
  var init_planning = __esm({
264369
264989
  "../dashboard/src/planning.ts"() {
264370
264990
  "use strict";
@@ -264400,6 +265020,8 @@ Every turn must include the running-plan fields: only title, description, concre
264400
265020
  sessions = /* @__PURE__ */ new Map();
264401
265021
  rateLimits2 = /* @__PURE__ */ new Map();
264402
265022
  activeGenerations = /* @__PURE__ */ new Map();
265023
+ pendingTurnReservations = /* @__PURE__ */ new Map();
265024
+ settlingTurnOperations = /* @__PURE__ */ new Map();
264403
265025
  sessionPersistenceQueues = /* @__PURE__ */ new Map();
264404
265026
  cleanupInterval2 = setInterval(cleanupExpiredSessions, CLEANUP_INTERVAL_MS2);
264405
265027
  cleanupInterval2.unref?.();
@@ -264473,6 +265095,10 @@ Every turn must include the running-plan fields: only title, description, concre
264473
265095
  }
264474
265096
  this.pendingInitialTurns.set(sessionId, start);
264475
265097
  }
265098
+ /** True while a registered initial turn has not been consumed by a stream connection yet. */
265099
+ hasPendingInitialTurn(sessionId) {
265100
+ return this.pendingInitialTurns.has(sessionId);
265101
+ }
264476
265102
  consumeInitialTurn(sessionId) {
264477
265103
  const start = this.pendingInitialTurns.get(sessionId);
264478
265104
  if (!start) {
@@ -264512,7 +265138,7 @@ Every turn must include the running-plan fields: only title, description, concre
264512
265138
  this.removeAllListeners();
264513
265139
  }
264514
265140
  };
264515
- planningStreamManager = new PlanningStreamManager();
265141
+ planningStreamManager = new PlanningStreamManager(2e3);
264516
265142
  MAX_PARSE_RETRIES2 = 1;
264517
265143
  TruncatedPlanningCompletionError = class extends Error {
264518
265144
  constructor() {
@@ -269009,7 +269635,7 @@ async function readAndValidateManifest(manifestPath) {
269009
269635
  }
269010
269636
  return manifest;
269011
269637
  }
269012
- function createPluginRouter(pluginStore, pluginLoader, pluginRunner, defaultTaskStore) {
269638
+ function createPluginRouter(pluginStore, pluginLoader, pluginRunner, defaultTaskStore, resolveProjectPluginLoader) {
269013
269639
  const router = Router();
269014
269640
  router.get("/", catchHandler(async (_req, res) => {
269015
269641
  const plugins2 = await pluginStore.listPlugins();
@@ -269238,24 +269864,37 @@ function createPluginRouter(pluginStore, pluginLoader, pluginRunner, defaultTask
269238
269864
  throw internalError(err instanceof Error ? err.message : "Failed to update settings");
269239
269865
  }
269240
269866
  }));
269241
- const pluginRoutesByKey = /* @__PURE__ */ new Map();
269242
- const addPluginRoutes = (entries) => {
269243
- for (const entry of entries) {
269244
- const key = `${entry.pluginId}\0${entry.route.method}\0${entry.route.path}`;
269245
- pluginRoutesByKey.set(key, entry);
269867
+ const collectDispatchEntries = (resolvedLoader) => {
269868
+ const byKey = /* @__PURE__ */ new Map();
269869
+ const addPluginRoutes = (entries, execLoader) => {
269870
+ if (!entries) return;
269871
+ for (const entry of entries) {
269872
+ const key = `${entry.pluginId}\0${entry.route.method}\0${entry.route.path}`;
269873
+ if (!byKey.has(key)) {
269874
+ byKey.set(key, { ...entry, execLoader });
269875
+ }
269876
+ }
269877
+ };
269878
+ if (resolvedLoader && resolvedLoader !== pluginLoader) {
269879
+ addPluginRoutes(resolvedLoader.getPluginRoutes?.(), resolvedLoader);
269246
269880
  }
269881
+ addPluginRoutes(pluginLoader.getPluginRoutes?.(), pluginLoader);
269882
+ if (pluginRunner && typeof pluginRunner.getPluginRoutes === "function") {
269883
+ addPluginRoutes(pluginRunner.getPluginRoutes(), pluginLoader);
269884
+ }
269885
+ return byKey;
269247
269886
  };
269248
- if (pluginRunner && typeof pluginRunner.getPluginRoutes === "function") {
269249
- addPluginRoutes(pluginRunner.getPluginRoutes());
269250
- }
269251
- const loaderRoutes = pluginLoader.getPluginRoutes?.();
269252
- if (loaderRoutes) {
269253
- addPluginRoutes(loaderRoutes);
269254
- }
269255
- for (const { pluginId, route } of pluginRoutesByKey.values()) {
269887
+ const buildDispatchRouter = (entries) => {
269888
+ const dispatchRouter = Router();
269889
+ for (const { pluginId, route, execLoader } of entries.values()) {
269890
+ registerPluginRoute(dispatchRouter, pluginId, route, execLoader);
269891
+ }
269892
+ return dispatchRouter;
269893
+ };
269894
+ const registerPluginRoute = (targetRouter, pluginId, route, execLoader) => {
269256
269895
  const fullPath = `/${pluginId}${route.path.startsWith("/") ? route.path : `/${route.path}`}`;
269257
269896
  const handler = catchHandler(async (req, res) => {
269258
- const plugin9 = pluginLoader.getPlugin(pluginId);
269897
+ const plugin9 = execLoader.getPlugin(pluginId);
269259
269898
  if (!plugin9) {
269260
269899
  throw notFound(`Plugin "${pluginId}" not loaded`);
269261
269900
  }
@@ -269280,7 +269919,7 @@ function createPluginRouter(pluginStore, pluginLoader, pluginRunner, defaultTask
269280
269919
  } catch {
269281
269920
  }
269282
269921
  }
269283
- const ctx = await pluginLoader.createRouteContext(pluginId, {
269922
+ const ctx = await execLoader.createRouteContext(pluginId, {
269284
269923
  taskStore,
269285
269924
  settings,
269286
269925
  resolveProjectTaskStore: getOrCreateProjectStore,
@@ -269320,22 +269959,42 @@ function createPluginRouter(pluginStore, pluginLoader, pluginRunner, defaultTask
269320
269959
  });
269321
269960
  switch (route.method) {
269322
269961
  case "GET":
269323
- router.get(fullPath, handler);
269962
+ targetRouter.get(fullPath, handler);
269324
269963
  break;
269325
269964
  case "POST":
269326
- router.post(fullPath, handler);
269965
+ targetRouter.post(fullPath, handler);
269327
269966
  break;
269328
269967
  case "PUT":
269329
- router.put(fullPath, handler);
269968
+ targetRouter.put(fullPath, handler);
269330
269969
  break;
269331
269970
  case "PATCH":
269332
- router.patch(fullPath, handler);
269971
+ targetRouter.patch(fullPath, handler);
269333
269972
  break;
269334
269973
  case "DELETE":
269335
- router.delete(fullPath, handler);
269974
+ targetRouter.delete(fullPath, handler);
269336
269975
  break;
269337
269976
  }
269338
- }
269977
+ };
269978
+ const dispatchRouterCache = /* @__PURE__ */ new WeakMap();
269979
+ const dispatchSignature = (entries, resolvedLoader) => [...entries.entries()].map(([key, entry]) => `${key}\0${entry.execLoader === resolvedLoader ? "p" : "h"}`).sort().join("\n");
269980
+ router.use((req, res, next) => {
269981
+ void (async () => {
269982
+ const resolvedLoader = resolveProjectPluginLoader ? await resolveProjectPluginLoader(req) : void 0;
269983
+ const entries = collectDispatchEntries(resolvedLoader);
269984
+ if (entries.size === 0) {
269985
+ next();
269986
+ return;
269987
+ }
269988
+ const signature = dispatchSignature(entries, resolvedLoader);
269989
+ const cacheKey = resolvedLoader ?? pluginLoader;
269990
+ let cached2 = dispatchRouterCache.get(cacheKey);
269991
+ if (!cached2 || cached2.signature !== signature) {
269992
+ cached2 = { signature, router: buildDispatchRouter(entries) };
269993
+ dispatchRouterCache.set(cacheKey, cached2);
269994
+ }
269995
+ cached2.router(req, res, next);
269996
+ })().catch(next);
269997
+ });
269339
269998
  return router;
269340
269999
  }
269341
270000
  var registryManifestUrl, cachedRegistryManifest, DIST_DIR_NAMES;
@@ -269508,6 +270167,24 @@ function createApiRoutesContext(store, options) {
269508
270167
  return [...projects2].sort((a, b3) => rankProject(b3.path) - rankProject(a.path));
269509
270168
  }
269510
270169
  const resolveScopedStore2 = (req) => getScopedStore(req, store, options);
270170
+ const fallbackProjectLoaders = /* @__PURE__ */ new WeakMap();
270171
+ const getProjectPluginLoader = async (scopedStore, engine) => {
270172
+ const engineLoader = engine?.getPluginRunner?.()?.getLoader?.();
270173
+ if (engineLoader) return engineLoader;
270174
+ const scopedPluginStore = scopedStore.getPluginStore();
270175
+ if (scopedPluginStore === options?.pluginStore) return options?.pluginLoader;
270176
+ let fallback2 = fallbackProjectLoaders.get(scopedStore);
270177
+ if (!fallback2) {
270178
+ const loader = new PluginLoader({ pluginStore: scopedPluginStore, taskStore: scopedStore });
270179
+ fallback2 = {
270180
+ loader,
270181
+ initialized: loader.loadAllPlugins().then(() => void 0)
270182
+ };
270183
+ fallbackProjectLoaders.set(scopedStore, fallback2);
270184
+ }
270185
+ await fallback2.initialized;
270186
+ return fallback2.loader;
270187
+ };
269511
270188
  const fallbackMcpLoaders = /* @__PURE__ */ new WeakMap();
269512
270189
  const projectMcpProviders = /* @__PURE__ */ new WeakMap();
269513
270190
  const bindProjectScopedPluginMcpProvider = async (context) => {
@@ -269667,6 +270344,7 @@ function createApiRoutesContext(store, options) {
269667
270344
  getProjectIdFromRequest,
269668
270345
  getScopedStore: resolveScopedStore2,
269669
270346
  getProjectContext: resolveProjectContext,
270347
+ getProjectPluginLoader,
269670
270348
  emitRemoteRouteDiagnostic: (input) => emitRemoteRouteDiagnostic(runtimeLogger, input),
269671
270349
  emitAuthSyncAuditLog,
269672
270350
  parseScopeParam,
@@ -282879,6 +283557,7 @@ function describeColumns(ir2, canonicalizeLifecycle = false) {
282879
283557
  return v2.columns.map((col) => ({
282880
283558
  id: col.id,
282881
283559
  name: displayColumnName(col.id, col.name, canonicalizeLifecycle),
283560
+ ...col.description ? { description: col.description } : {},
282882
283561
  flags: resolveColumnFlags(col)
282883
283562
  }));
282884
283563
  }
@@ -291884,6 +292563,7 @@ var chat_exports = {};
291884
292563
  __export(chat_exports, {
291885
292564
  CHAT_AGENT_MESSAGE_ROUTING_GUIDANCE: () => CHAT_AGENT_MESSAGE_ROUTING_GUIDANCE,
291886
292565
  CHAT_ASK_QUESTION_GUIDANCE: () => CHAT_ASK_QUESTION_GUIDANCE,
292566
+ CHAT_CODEBASE_ACCURACY_GUIDANCE: () => CHAT_CODEBASE_ACCURACY_GUIDANCE,
291887
292567
  CHAT_SYSTEM_PROMPT: () => CHAT_SYSTEM_PROMPT,
291888
292568
  ChatManager: () => ChatManager,
291889
292569
  ChatStreamManager: () => ChatStreamManager,
@@ -292535,7 +293215,7 @@ function __resetChatState() {
292535
293215
  createResolvedAgentSession2 = createResolvedAgentSession;
292536
293216
  __setChatDiagnostics(null);
292537
293217
  }
292538
- var createFnAgent10, createResolvedAgentSession2, buildAgentChatPromptFn, defaultDiagnostics, _diagnostics, diagnostics6, SKILL_COMMAND_PATTERN, CHAT_SYSTEM_PROMPT, CHAT_AGENT_MESSAGE_ROUTING_GUIDANCE, CHAT_ASK_QUESTION_GUIDANCE, RATE_LIMIT_WINDOW_MS6, MAX_MESSAGES_PER_IP_PER_MINUTE, MAX_REFERENCED_FILE_SIZE, TASK_PLANNER_CHAT_AGENT_ID_PREFIX2, CHAT_CODING_TOOLS, ROOM_AMBIENT_MAX_RESPONDERS, CHAT_MISSION_READ_TOOL_NAMES, CHAT_IDEATION_READ_TOOL_NAMES, ROOM_SKIP_SENTINEL, DEFAULT_ROOM_THREAD_RECENT_VERBATIM_MESSAGES, DEFAULT_ROOM_THREAD_COMPACTION_FETCH_LIMIT, ROOM_THREAD_CONTEXT_MAX_CHARS, ROOM_THREAD_MESSAGE_CONTENT_MAX_CHARS, DEFAULT_ROOM_THREAD_SUMMARY_MAX_CHARS, IN_FLIGHT_PERSIST_DEBOUNCE_MS, rateLimits6, ChatStreamManager, chatStreamManager, RoomReplyGenerationError, ChatManager;
293218
+ var createFnAgent10, createResolvedAgentSession2, buildAgentChatPromptFn, defaultDiagnostics, _diagnostics, diagnostics6, SKILL_COMMAND_PATTERN, CHAT_SYSTEM_PROMPT, CHAT_AGENT_MESSAGE_ROUTING_GUIDANCE, CHAT_CODEBASE_ACCURACY_GUIDANCE, CHAT_ASK_QUESTION_GUIDANCE, RATE_LIMIT_WINDOW_MS6, MAX_MESSAGES_PER_IP_PER_MINUTE, MAX_REFERENCED_FILE_SIZE, TASK_PLANNER_CHAT_AGENT_ID_PREFIX2, CHAT_CODING_TOOLS, ROOM_AMBIENT_MAX_RESPONDERS, CHAT_MISSION_READ_TOOL_NAMES, CHAT_IDEATION_READ_TOOL_NAMES, ROOM_SKIP_SENTINEL, DEFAULT_ROOM_THREAD_RECENT_VERBATIM_MESSAGES, DEFAULT_ROOM_THREAD_COMPACTION_FETCH_LIMIT, ROOM_THREAD_CONTEXT_MAX_CHARS, ROOM_THREAD_MESSAGE_CONTENT_MAX_CHARS, DEFAULT_ROOM_THREAD_SUMMARY_MAX_CHARS, IN_FLIGHT_PERSIST_DEBOUNCE_MS, rateLimits6, ChatStreamManager, chatStreamManager, RoomReplyGenerationError, ChatManager;
292539
293219
  var init_chat = __esm({
292540
293220
  "../dashboard/src/chat.ts"() {
292541
293221
  "use strict";
@@ -292575,12 +293255,33 @@ var init_chat = __esm({
292575
293255
  SKILL_COMMAND_PATTERN = /(^|\s)\/skill:([^\s]+)/gi;
292576
293256
  CHAT_SYSTEM_PROMPT = `${FUSION_RUNTIME_SELF_AWARENESS}
292577
293257
 
292578
- You are a helpful AI assistant integrated into the fn task board system. You help users with questions about their project, code, architecture, and tasks. You have coding workspace tools on the project checkout: \`read\`, \`write\`, \`edit\`, \`bash\`, \`grep\`, \`find\`, and \`ls\`. Use \`write\`, \`edit\`, and \`bash\` for user-requested code changes, file edits, or shell investigation; prefer minimal, user-directed mutations and respect any pending-approval or blocked tool result. Do not claim that you only have read access. Do not change the branch the working directory is checked out on: do not run \`git checkout <branch>\` or \`git switch <branch>\` to a different branch unless the user explicitly asks. Read-only Git and branch inspection, such as \`git status\`, \`git branch\`, and \`git log\`, is allowed. Response length policy: default to a short, crisp reply (a few sentences or a short bulleted list) that directly answers the user; avoid preamble, restating the question, and filler. If a thorough answer genuinely needs long-form content (for example multi-step plans, design proposals, deep analyses, or long file excerpts), keep the chat reply brief with a one- or two-sentence summary and then send the full write-up via \`fn_send_message\` using \`type: "agent-to-user"\` and \`to_id: "dashboard"\`. That mailbox follow-up must add new substantive detail and must not duplicate the chat reply.`;
293258
+ You are a helpful AI assistant integrated into the fn task board system. You help users with questions about their project, code, architecture, and tasks. You have coding workspace tools on the project checkout: \`read\`, \`write\`, \`edit\`, \`bash\`, \`grep\`, \`find\`, and \`ls\`. Use \`write\`, \`edit\`, and \`bash\` for user-requested code changes, file edits, or shell investigation; prefer minimal, user-directed mutations and respect any pending-approval or blocked tool result. Do not claim that you only have read access. Do not change the branch the working directory is checked out on: do not run \`git checkout <branch>\` or \`git switch <branch>\` to a different branch unless the user explicitly asks. Read-only Git and branch inspection, such as \`git status\`, \`git branch\`, and \`git log\`, is allowed. Response length policy: default to a short, crisp reply (a few sentences or a short bulleted list) that directly answers the user; avoid preamble, restating the question, and filler. For questions about this repository's code or architecture, prioritize correctness and cite real paths/symbols over maximum brevity \u2014 still lead short, but do not omit the evidence needed to be accurate. If a thorough answer genuinely needs long-form content (for example multi-step plans, design proposals, deep multi-file traces, deep analyses, or long file excerpts), keep the chat reply brief with a one- or two-sentence summary plus key citations. When \`fn_send_message\` is available in this session, send the full write-up via \`fn_send_message\` using \`type: "agent-to-user"\` and \`to_id: "dashboard"\` (additive detail that must not duplicate the chat reply). When that tool is not available, put the necessary detail in the chat reply itself rather than inventing a mailbox path.`;
292579
293259
  CHAT_AGENT_MESSAGE_ROUTING_GUIDANCE = `## Messaging Semantics
292580
293260
 
292581
293261
  Your chat reply is the primary response to the user. Do not also call \`fn_send_message\` with the same content just to mirror your chat response into mailbox.
292582
293262
 
292583
293263
  Use \`fn_send_message\` only when either (a) the user explicitly asks for mailbox/inbox/notification delivery (for example: "send me this in mail", "ntfy me when\u2026", or "leave me a note in my inbox"), or (b) you are sending a genuinely longer follow-up that did not fit in a short chat reply. In either case, send with \`type: "agent-to-user"\` and target the dashboard user alias (\`to_id: "dashboard"\` is preferred), and ensure the mailbox message is additive rather than a duplicate of the chat reply. Never route that as a user/CLI \u2192 agent message.`;
293264
+ CHAT_CODEBASE_ACCURACY_GUIDANCE = `## Codebase accuracy
293265
+
293266
+ When the user asks about **this project's** code, architecture, behavior, APIs, files, tests, settings, or "how does X work here", ground the answer in the live checkout **before** you reply. Do not answer from training-data memory or generic framework knowledge as if it were this repo.
293267
+
293268
+ ### Investigate first
293269
+ 1. Use readonly tools first: \`grep\`, \`find\`, \`ls\`, \`read\` (and readonly git such as \`git status\` / \`git log\` / \`git branch\` when relevant). Prefer \`grep\`/\`find\` to locate symbols, then \`read\` the concrete files. Restrict \`find\` (and directory walks) to the project checkout or a known merge/worktree path with bounded, prefix-filtered scans; never recurse through the OS temp root (\`$TMPDIR\`, \`/tmp\`, macOS \`/var/folders/...\`) or an unbounded path.
293270
+ 2. Open the real definitions and call sites you will cite. For behavioral questions, also check tests and docs under \`docs/\` / \`CONCEPTS.md\` when they exist.
293271
+ 3. If board/task context matters, use \`fn_task_list\` / \`fn_task_show\` (and search tools if available) rather than inventing task state.
293272
+ 4. Only after you have evidence, write the chat reply.
293273
+
293274
+ ### How to answer
293275
+ - Name **real** paths, symbols, and modules from the checkout (e.g. \`packages/foo/src/bar.ts\`, \`functionName\`). Prefer evidence over abstraction.
293276
+ - Prefer a short answer that is **correct and cited** over a fluent guess. A crisp 3\u20136 bullet list with file paths is better than a long ungrounded narrative.
293277
+ - If tools conflict with your prior assumptions, **trust the tools**.
293278
+ - If you cannot find something after a reasonable search, say what you searched and that it may not exist \u2014 do not invent modules, routes, tables, or APIs.
293279
+ - Distinguish **verified in this checkout** from **general recommendation**. Label speculation explicitly.
293280
+
293281
+ ### When brevity still applies
293282
+ - Purely conversational, product-how-to, or non-code questions: keep the existing short/crisp default.
293283
+ - Code/architecture questions: still lead with a short answer, but include the grounding (paths / symbols). For long excerpts, multi-file traces, or deep designs: lead with a brief chat summary; if \`fn_send_message\` is available in this session, send the full write-up via that tool (\`type: "agent-to-user"\`, \`to_id: "dashboard"\`); if it is not available, keep the necessary detail in the chat reply instead of calling a missing tool.
293284
+ - Do **not** start a Planning Mode interview, write PROMPT.md, or invent steps/file-scope specs unless the user asks to plan or create a task.`;
292584
293285
  CHAT_ASK_QUESTION_GUIDANCE = `## Asking the User
292585
293286
 
292586
293287
  When you need structured input, or whenever you present options, choices, or a decision between alternatives, call \`fn_ask_question\` with one or more questions using the right shape (single_select, multi_select, confirm/yes-no, or text) instead of listing options only in prose, then stop and wait for the user's next chat message.`;
@@ -293200,6 +293901,9 @@ ${mentionContext}`;
293200
293901
  systemPrompt = `${systemPrompt}
293201
293902
 
293202
293903
  ${CHAT_AGENT_MESSAGE_ROUTING_GUIDANCE}`;
293904
+ systemPrompt = `${systemPrompt}
293905
+
293906
+ ${CHAT_CODEBASE_ACCURACY_GUIDANCE}`;
293203
293907
  const roomCompactionSettings = await this.getRoomCompactionSettings();
293204
293908
  const roomMessages = await this.chatStore.getRoomMessages(input.roomId, { limit: roomCompactionSettings.fetchLimit });
293205
293909
  const { attachmentContents, imageContents } = await readChatAttachmentContents(
@@ -293518,6 +294222,9 @@ ${CHAT_AGENT_MESSAGE_ROUTING_GUIDANCE}`;
293518
294222
  }
293519
294223
  systemPrompt = `${systemPrompt}
293520
294224
 
294225
+ ${CHAT_CODEBASE_ACCURACY_GUIDANCE}`;
294226
+ systemPrompt = `${systemPrompt}
294227
+
293521
294228
  ${CHAT_ASK_QUESTION_GUIDANCE}`;
293522
294229
  const taskPlannerChatTaskId = typeof session.agentId === "string" && session.agentId.startsWith(TASK_PLANNER_CHAT_AGENT_ID_PREFIX2) ? session.agentId.slice(TASK_PLANNER_CHAT_AGENT_ID_PREFIX2.length).trim() : "";
293523
294230
  let taskPlannerTaskColumn = "";
@@ -345253,26 +345960,8 @@ var init_plugin_bundled_runtimes = __esm({
345253
345960
  import { isAbsolute as isAbsolute27, resolve as resolve56 } from "node:path";
345254
345961
  import * as nodeFs2 from "node:fs";
345255
345962
  function registerPluginsAutomationRoutes(ctx, deps) {
345256
- const { router, options, parseScopeParam, resolveAutomationStore, resolveRoutineStore, resolveRoutineRunner, getScopedStore: getScopedStore2, getProjectContext: getProjectContext2, rethrowAsApiError: rethrowAsApiError9, runtimeLogger } = ctx;
345963
+ const { router, options, parseScopeParam, resolveAutomationStore, resolveRoutineStore, resolveRoutineRunner, getScopedStore: getScopedStore2, getProjectContext: getProjectContext2, getProjectPluginLoader, rethrowAsApiError: rethrowAsApiError9, runtimeLogger } = ctx;
345257
345964
  const makeRunStreamHandler = createAutomationRunStreamHandlerFactory({ parseScopeParam, rethrowAsApiError: rethrowAsApiError9, ...deps });
345258
- const fallbackProjectLoaders = /* @__PURE__ */ new WeakMap();
345259
- const getProjectPluginLoader = async (scopedStore, engine) => {
345260
- const engineLoader = engine?.getPluginRunner?.()?.getLoader?.();
345261
- if (engineLoader) return engineLoader;
345262
- const scopedPluginStore = scopedStore.getPluginStore();
345263
- if (scopedPluginStore === options?.pluginStore) return options?.pluginLoader;
345264
- let fallback2 = fallbackProjectLoaders.get(scopedStore);
345265
- if (!fallback2) {
345266
- const loader = new PluginLoader({ pluginStore: scopedPluginStore, taskStore: scopedStore });
345267
- fallback2 = {
345268
- loader,
345269
- initialized: loader.loadAllPlugins().then(() => void 0)
345270
- };
345271
- fallbackProjectLoaders.set(scopedStore, fallback2);
345272
- }
345273
- await fallback2.initialized;
345274
- return fallback2.loader;
345275
- };
345276
345965
  router.get("/automations", async (req, res) => {
345277
345966
  if (!options?.automationStore) {
345278
345967
  return res.json([]);
@@ -371669,6 +372358,7 @@ function createApiRoutes(store, options) {
371669
372358
  getProjectIdFromRequest: getProjectIdFromRequest2,
371670
372359
  getScopedStore: getScopedStore2,
371671
372360
  getProjectContext: getProjectContext2,
372361
+ getProjectPluginLoader,
371672
372362
  emitRemoteRouteDiagnostic: emitRemoteRouteDiagnostic2,
371673
372363
  emitAuthSyncAuditLog,
371674
372364
  parseScopeParam,
@@ -371689,6 +372379,7 @@ function createApiRoutes(store, options) {
371689
372379
  getProjectIdFromRequest: getProjectIdFromRequest2,
371690
372380
  getScopedStore: getScopedStore2,
371691
372381
  getProjectContext: getProjectContext2,
372382
+ getProjectPluginLoader,
371692
372383
  emitRemoteRouteDiagnostic: emitRemoteRouteDiagnostic2,
371693
372384
  emitAuthSyncAuditLog,
371694
372385
  parseScopeParam,
@@ -372386,7 +373077,18 @@ function createApiRoutes(store, options) {
372386
373077
  options.pluginStore,
372387
373078
  options.pluginLoader,
372388
373079
  pluginRunner,
372389
- store
373080
+ store,
373081
+ /*
373082
+ FNXC:PluginRoutes 2026-07-22-20:30:
373083
+ Per-request project-scoped loader resolution for plugin-defined routes — the
373084
+ same getProjectPluginLoader cache the plugin management registrar uses — so a
373085
+ plugin enabled after boot, or enabled only in a non-launch project, serves its
373086
+ API routes the moment its dashboard view appears.
373087
+ */
373088
+ async (req) => {
373089
+ const { store: scopedStore, engine } = await routeContext.getProjectContext(req);
373090
+ return routeContext.getProjectPluginLoader(scopedStore, engine);
373091
+ }
372390
373092
  )
372391
373093
  );
372392
373094
  }