farming-code 2.2.23 → 2.2.28

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 (264) hide show
  1. package/README.md +9 -2
  2. package/README.zh_cn.md +9 -2
  3. package/THIRD_PARTY_NOTICES.md +3 -0
  4. package/backend/acp-runtime.js +15 -1
  5. package/backend/agent-manager.js +1054 -94
  6. package/backend/atomic-json-store.js +37 -0
  7. package/backend/cli-agents.js +9 -7
  8. package/backend/config-manager.js +167 -58
  9. package/backend/control-api.js +2 -1
  10. package/backend/farming-agent-bootstrap.js +49 -0
  11. package/backend/farming-agent-bootstrap.zh_cn.md +5 -0
  12. package/backend/farming-app-cli.js +9 -2
  13. package/backend/farming-cli.js +55 -0
  14. package/backend/farming-session-store.js +92 -89
  15. package/backend/main-agent-skills.js +17 -0
  16. package/backend/native-pty-host-client.js +99 -7
  17. package/backend/native-pty-host-path.js +20 -0
  18. package/backend/native-pty-host.js +6 -5
  19. package/backend/preview-session-manager.js +68 -0
  20. package/backend/provider-adapters.js +44 -5
  21. package/backend/review-session-store.js +32 -25
  22. package/backend/review-state-store.js +13 -14
  23. package/backend/run-history-store.js +13 -16
  24. package/backend/server.js +103 -78
  25. package/backend/storage-layout.js +20 -0
  26. package/backend/workspace-file-router.js +171 -6
  27. package/backend/workspace-file-service.js +19 -0
  28. package/backend/workspace-root-registry.js +6 -4
  29. package/dist/assets/App-CM7Cy-l4.css +1 -0
  30. package/dist/assets/App-CPG4WcjO.js +658 -0
  31. package/dist/assets/{FileEditorMarkdownPreview-KbnE8VC8.js → FileEditorMarkdownPreview-DYOMm-b1.js} +5 -5
  32. package/dist/assets/FileEditorPane-DXXq82aM.js +2 -0
  33. package/dist/assets/IconGlyphs-DrdPjYXr.js +1 -0
  34. package/dist/assets/ProjectFilesSection-CP66XVPl.js +12 -0
  35. package/dist/assets/{ReviewPage-L1vh1Ggs.js → ReviewPage-w21MEPD6.js} +1 -1
  36. package/dist/assets/{abnfDiagram-VRR7QNED-nnglrXFi.js → abnfDiagram-VRR7QNED-BY1qwq8A.js} +1 -1
  37. package/dist/assets/api-w2x5QfxB.js +1 -0
  38. package/dist/assets/{arc-CWoVlxJw.js → arc-DgZRAlwj.js} +1 -1
  39. package/dist/assets/{architectureDiagram-ZJ3FMSHR-BEXMaFfY.js → architectureDiagram-ZJ3FMSHR-jFn144od.js} +1 -1
  40. package/dist/assets/{blockDiagram-677ZJIJ3-1mCezSBk.js → blockDiagram-677ZJIJ3-BmRjlV0H.js} +1 -1
  41. package/dist/assets/{c4Diagram-LMCZKHZV-DhTiI6Uv.js → c4Diagram-LMCZKHZV-DUKul6tT.js} +1 -1
  42. package/dist/assets/{workspace-editor-monaco-DLlws5Ig.js → channel-CI9j3QFD.js} +1 -1
  43. package/dist/assets/chunk-2Q5K7J3B-87UnWaoh.js +1 -0
  44. package/dist/assets/{chunk-32BRIVSS-CCdsKd73.js → chunk-32BRIVSS-rGVu23o5.js} +1 -1
  45. package/dist/assets/{chunk-52WLFC77-Gncjk4LF.js → chunk-52WLFC77-CvFDKgbB.js} +1 -1
  46. package/dist/assets/{chunk-5VM5RSS4-Dlw7AXWp.js → chunk-5VM5RSS4-CFgnRW1C.js} +1 -1
  47. package/dist/assets/{chunk-7BUUIJ7U-ZBRhjLOh.js → chunk-7BUUIJ7U-Cp7bqW6s.js} +1 -1
  48. package/dist/assets/{chunk-C7G6YPKG-CiDVUP82.js → chunk-C7G6YPKG-BIS1gjZO.js} +1 -1
  49. package/dist/assets/{chunk-EX3LRPZG-z6gXP2_N.js → chunk-EX3LRPZG-BKG-cR9j.js} +1 -1
  50. package/dist/assets/{chunk-FWX5IMBZ-DMZBccQa.js → chunk-FWX5IMBZ-B8PsTr8a.js} +2 -2
  51. package/dist/assets/{chunk-HOUHSVGY-D8tIeoPk.js → chunk-HOUHSVGY-BHFOAiXT.js} +1 -1
  52. package/dist/assets/{chunk-ICXQ74PX-b6oQ2Q17.js → chunk-ICXQ74PX-CvLE1J9X.js} +1 -1
  53. package/dist/assets/{chunk-JWPE2WC7-DSyBRaRG.js → chunk-JWPE2WC7-D8misks7.js} +1 -1
  54. package/dist/assets/{chunk-MOJQB5TN-DAVH_p2a.js → chunk-MOJQB5TN-D-oNgSNH.js} +1 -1
  55. package/dist/assets/{chunk-OGEWGWER-DxoiMCI-.js → chunk-OGEWGWER-C2N-la9e.js} +1 -1
  56. package/dist/assets/{chunk-PUDLZKDR-NsOWh7HM.js → chunk-PUDLZKDR-BwI3Jz8q.js} +1 -1
  57. package/dist/assets/{chunk-Q4XR5HBZ-Di4H7L21.js → chunk-Q4XR5HBZ-BuqGfsXA.js} +1 -1
  58. package/dist/assets/{chunk-RYQCIY6F-CZA9pvFX.js → chunk-RYQCIY6F-Bq4XZcGt.js} +1 -1
  59. package/dist/assets/{chunk-V7JOEXUC-DttpZg9Y.js → chunk-V7JOEXUC-QZLzdTTJ.js} +1 -1
  60. package/dist/assets/chunk-VAUOI2AC-Cb0-kQ9g.js +1 -0
  61. package/dist/assets/{chunk-VR4S4FIN-D-Fmtd4Z.js → chunk-VR4S4FIN-DcZTpML_.js} +1 -1
  62. package/dist/assets/{chunk-WYO6CB5R-CUxMtfK2.js → chunk-WYO6CB5R-DCU8wuny.js} +2 -2
  63. package/dist/assets/chunk-XXDRQBXY-BZAC1Krv.js +1 -0
  64. package/dist/assets/{chunk-ZGVPDNZ5-CoV2QEl0.js → chunk-ZGVPDNZ5-B7r85QbY.js} +1 -1
  65. package/dist/assets/{chunk-ZIRB5QZD-DncwlQQc.js → chunk-ZIRB5QZD-BgEnDWhQ.js} +1 -1
  66. package/dist/assets/classDiagram-OUVF2IWQ-DcvGOuvi.js +1 -0
  67. package/dist/assets/classDiagram-v2-EOCWNBFH-DDYUSofQ.js +1 -0
  68. package/dist/assets/code-dark-BDjwj7Um.css +1 -0
  69. package/dist/assets/{cose-bilkent-JH36ORCC-BNIAz0nN.js → cose-bilkent-JH36ORCC-CThXS2qG.js} +1 -1
  70. package/dist/assets/{cssMode-DDWxTPm_.js → cssMode-Bdg5RpcJ.js} +1 -1
  71. package/dist/assets/{cynefinDiagram-TSTJHNR4-0xPI92Lg.js → cynefinDiagram-TSTJHNR4-DxBgzw25.js} +1 -1
  72. package/dist/assets/{dagre-DXlcHgr3.js → dagre-BM-GvGUZ.js} +1 -1
  73. package/dist/assets/{dagre-VKFMJZFB-DfuMG4gU.js → dagre-VKFMJZFB-B7UCRmxR.js} +1 -1
  74. package/dist/assets/{diagram-FQU43EPY-CT00bLZ6.js → diagram-FQU43EPY-CeL2DwKx.js} +1 -1
  75. package/dist/assets/{diagram-G47NLZAW-Xbh7zXUx.js → diagram-G47NLZAW-BEirglCC.js} +1 -1
  76. package/dist/assets/{diagram-NH7WQ7WH-SRDVPfn7.js → diagram-NH7WQ7WH-C6wcI-Rl.js} +1 -1
  77. package/dist/assets/{diagram-OA4YK3LP-CQRli4Np.js → diagram-OA4YK3LP-DpM-XXtJ.js} +1 -1
  78. package/dist/assets/{diagram-WEI45ONY-DMf17WQi.js → diagram-WEI45ONY-BQyaut9k.js} +1 -1
  79. package/dist/assets/{ebnfDiagram-CCIWWBDH-DhpFkgJ5.js → ebnfDiagram-CCIWWBDH-UmAXsUhr.js} +1 -1
  80. package/dist/assets/{erDiagram-Q63AITRT-1IwxWmJo.js → erDiagram-Q63AITRT-CdYLEOch.js} +1 -1
  81. package/dist/assets/file-icons-BOlcJ0ZF.js +1 -0
  82. package/dist/assets/flowDiagram-23GEKE2U-CO-z9-tq.js +1 -0
  83. package/dist/assets/{freemarker2-QBsTERH-.js → freemarker2-DfMDJfnE.js} +1 -1
  84. package/dist/assets/{ganttDiagram-NO4QXBWP-BNCdfyr1.js → ganttDiagram-NO4QXBWP-DgSUNdUB.js} +1 -1
  85. package/dist/assets/{gitGraphDiagram-IHSO6WYX-pbyWZuiX.js → gitGraphDiagram-IHSO6WYX-DjwjtRtF.js} +1 -1
  86. package/dist/assets/{handlebars-CuoxPo5A.js → handlebars-BEdPtzMT.js} +1 -1
  87. package/dist/assets/{html-gX5PEu0Q.js → html-B1fPb1QD.js} +1 -1
  88. package/dist/assets/html2canvas-DsczWxBa.js +5 -0
  89. package/dist/assets/{htmlMode-DIWUbUxd.js → htmlMode-CY2ZjHxH.js} +1 -1
  90. package/dist/assets/{index-DhTUO8DA.js → index-BzQO3pUr.js} +2 -2
  91. package/dist/assets/{infoDiagram-FWYZ7A6U-BcTOgDbe.js → infoDiagram-FWYZ7A6U-Bi9076l2.js} +1 -1
  92. package/dist/assets/{ishikawaDiagram-FXEZZL3T-DCdvGKrn.js → ishikawaDiagram-FXEZZL3T-DaMaNZN7.js} +1 -1
  93. package/dist/assets/{javascript-BYKGIUGs.js → javascript-BV2OsWA0.js} +1 -1
  94. package/dist/assets/{journeyDiagram-5HDEW3XC-BZ-1q_IT.js → journeyDiagram-5HDEW3XC-CfDy69EZ.js} +1 -1
  95. package/dist/assets/{jsonMode-AFmQQP6u.js → jsonMode-zd07aeuX.js} +1 -1
  96. package/dist/assets/{kanban-definition-HUTT4EX6-BIgejCjj.js → kanban-definition-HUTT4EX6-XFvZ722B.js} +1 -1
  97. package/dist/assets/{line-CfptFMmX.js → line-BXK9PKLn.js} +1 -1
  98. package/dist/assets/{linear-Dld3_ERu.js → linear-CULvmq2Z.js} +1 -1
  99. package/dist/assets/{liquid-DBIl-f0z.js → liquid-BParI24X.js} +1 -1
  100. package/dist/assets/{lspLanguageFeatures-DK6tqicV.js → lspLanguageFeatures-BzredkoV.js} +1 -1
  101. package/dist/assets/main-BlVUXfxa.css +1 -0
  102. package/dist/assets/{map-LZWWoeWa.js → map-C9Qw9eWh.js} +1 -1
  103. package/dist/assets/{mdx-DS-bVtZN.js → mdx-vGzUJN98.js} +1 -1
  104. package/dist/assets/{mermaid.core-gSpX30pV.js → mermaid.core-CzazaIKr.js} +3 -3
  105. package/dist/assets/{mindmap-definition-LN4V7U3C-BKBaI4LW.js → mindmap-definition-LN4V7U3C-Uoff7hZ3.js} +1 -1
  106. package/dist/assets/{monaco.contribution-BXz-lfFB.js → monaco.contribution-BmaeBQEc.js} +2 -2
  107. package/dist/assets/{ordinal-BLrOss5K.js → ordinal-r6pIwRdZ.js} +1 -1
  108. package/dist/assets/{pegDiagram-2B236MQR-sBvDY3VX.js → pegDiagram-2B236MQR-CCBiHQjk.js} +1 -1
  109. package/dist/assets/{pieDiagram-ENE6RG2P-DWlWcu08.js → pieDiagram-ENE6RG2P-D61s7l75.js} +1 -1
  110. package/dist/assets/{python-C93q3xXz.js → python-CUwdLgsI.js} +1 -1
  111. package/dist/assets/{quadrantDiagram-ABIIQ3AL-DZutUd3J.js → quadrantDiagram-ABIIQ3AL-D_CWk2DQ.js} +1 -1
  112. package/dist/assets/{railroadDiagram-RFXS5EU6-DQb3lXGM.js → railroadDiagram-RFXS5EU6-CdlCHtxN.js} +1 -1
  113. package/dist/assets/{razor-CBifNGXO.js → razor-BScebUxU.js} +1 -1
  114. package/dist/assets/{requirementDiagram-TGXJPOKE-DqFGXu2e.js → requirementDiagram-TGXJPOKE-BLpHtkoC.js} +1 -1
  115. package/dist/assets/{sankeyDiagram-HTMAVEWB-d7ECahnO.js → sankeyDiagram-HTMAVEWB-CkhYZN2j.js} +1 -1
  116. package/dist/assets/{sequenceDiagram-DBY2YBRQ-DdsbKqct.js → sequenceDiagram-DBY2YBRQ-8bploKJl.js} +1 -1
  117. package/dist/assets/{sizeCapture-X5ZJPWSS-daTB6OcF.js → sizeCapture-X5ZJPWSS-DtSdkS0d.js} +1 -1
  118. package/dist/assets/{src-WBW1QxJt.js → src-6yeq6WBA.js} +1 -1
  119. package/dist/assets/{stateDiagram-2N3HPSRC-P6iSrgwR.js → stateDiagram-2N3HPSRC-DfmVGpfA.js} +1 -1
  120. package/dist/assets/stateDiagram-v2-6OUMAXLB-B2c9CQ-J.js +1 -0
  121. package/dist/assets/{swimlanes-5IMT3BWC-BGnZJx1M.js → swimlanes-5IMT3BWC-DRLk2zRF.js} +2 -2
  122. package/dist/assets/swimlanesDiagram-G3AALYLV-B7_APh_h.js +8 -0
  123. package/dist/assets/{timeline-definition-FHXFAJF6-rA-r3aUR.js → timeline-definition-FHXFAJF6-Cngfy588.js} +1 -1
  124. package/dist/assets/{tsMode-B0oUTcXQ.js → tsMode-DzUAibXK.js} +1 -1
  125. package/dist/assets/{typescript-KJKfYO7K.js → typescript-D9EOeM7N.js} +1 -1
  126. package/dist/assets/{vennDiagram-L72KCM5P-DiVKUjA9.js → vennDiagram-L72KCM5P-DeJn63Lq.js} +1 -1
  127. package/dist/assets/{wardleyDiagram-EHGQE667-Cfxh7UOn.js → wardleyDiagram-EHGQE667-BH1nzy77.js} +1 -1
  128. package/dist/assets/{workers-BSQRIkqa.js → workers-DAiomYZM.js} +1 -1
  129. package/dist/assets/workspace-editor-model-CSGCl21x.js +1 -0
  130. package/dist/assets/workspace-editor-monaco-CFEfFNcA.js +1 -0
  131. package/dist/assets/{workspace-editor-monaco-BIpqcWf1.js → workspace-editor-monaco-tXA-udGx.js} +3 -3
  132. package/dist/assets/workspace-view-state-BS_V8JVs.js +7 -0
  133. package/dist/assets/{xml-C8FAcN6j.js → xml-wWl1dhkQ.js} +1 -1
  134. package/dist/assets/{xychartDiagram-FW5EYKEG--hmryQeL.js → xychartDiagram-FW5EYKEG-SkO2prCU.js} +1 -1
  135. package/dist/assets/{yaml-BQ4NrRhG.js → yaml-Dc2WDaOZ.js} +1 -1
  136. package/dist/crt-markdown-renderer.js +46 -46
  137. package/dist/index.html +1 -1
  138. package/extensions/browser/backend/agent-capability.js +55 -0
  139. package/extensions/browser/backend/browser-mcp-server.js +269 -0
  140. package/extensions/browser/backend/browser-resource-manager.js +524 -0
  141. package/extensions/browser/backend/browser-resource-store.js +149 -0
  142. package/extensions/browser/backend/browser-router.js +128 -0
  143. package/extensions/browser/backend/cdp-browser-runtime.js +648 -0
  144. package/extensions/browser/backend/cdp-client.js +167 -0
  145. package/extensions/browser/backend/executable-discovery.js +91 -0
  146. package/extensions/browser/backend/farming-browser-client.js +83 -0
  147. package/extensions/browser/backend/index.js +7 -0
  148. package/extensions/browser/bin/farming-browser +117 -0
  149. package/extensions/browser/frontend/BrowserSidebarPortals.tsx +362 -0
  150. package/extensions/browser/frontend/BrowserViewer.tsx +329 -0
  151. package/extensions/browser/frontend/browser-resource-state.ts +60 -0
  152. package/extensions/browser/frontend/browser.css +516 -0
  153. package/extensions/browser/frontend/types.ts +25 -0
  154. package/extensions/browser/frontend/useBrowserResources.ts +151 -0
  155. package/frontend/session-bridge.js +7 -6
  156. package/frontend/skins/crt/app.js +64 -4
  157. package/package.json +18 -13
  158. package/shared/browser-protocol.js +1 -1
  159. package/dist/assets/App-BbNBDA__.js +0 -210
  160. package/dist/assets/App-BrP-ENHg.css +0 -1
  161. package/dist/assets/FileEditorPane-5_HUejjd.js +0 -2
  162. package/dist/assets/IconGlyphs-C2WFcoJS.js +0 -1
  163. package/dist/assets/ProjectFilesSection-C1RAJWOE.js +0 -12
  164. package/dist/assets/api-D8nyOEbz.js +0 -1
  165. package/dist/assets/channel-BVWNatUB.js +0 -1
  166. package/dist/assets/chunk-2Q5K7J3B-d5WWh8jo.js +0 -1
  167. package/dist/assets/chunk-VAUOI2AC-UHNr_yhX.js +0 -1
  168. package/dist/assets/chunk-XXDRQBXY-BBXwjhUx.js +0 -1
  169. package/dist/assets/classDiagram-OUVF2IWQ-Db0ib5iE.js +0 -1
  170. package/dist/assets/classDiagram-v2-EOCWNBFH-Bkf69XF9.js +0 -1
  171. package/dist/assets/code-dark-BKtvf0Xc.css +0 -1
  172. package/dist/assets/file-icons-CHWt5nSy.js +0 -1
  173. package/dist/assets/flowDiagram-23GEKE2U-DYFo2xmB.js +0 -1
  174. package/dist/assets/main-XMXgyJJt.css +0 -1
  175. package/dist/assets/stateDiagram-v2-6OUMAXLB-DIYSf_qh.js +0 -1
  176. package/dist/assets/swimlanesDiagram-G3AALYLV-BIkrlKrZ.js +0 -8
  177. package/dist/assets/workspace-editor-model-D0Im34S5.js +0 -1
  178. package/dist/assets/workspace-view-state-BqB4HM7r.js +0 -7
  179. /package/dist/assets/{abap-Cc5D5h5i.js → abap-CtX5nWyX.js} +0 -0
  180. /package/dist/assets/{apex-BKopwLmx.js → apex-DQJRAHwc.js} +0 -0
  181. /package/dist/assets/{array-HeX70jSN.js → array-BTwzYU0x.js} +0 -0
  182. /package/dist/assets/{azcli-DI-C34WA.js → azcli-9SMrOQ4q.js} +0 -0
  183. /package/dist/assets/{bat-Cbp6u42z.js → bat-CBGLUsw1.js} +0 -0
  184. /package/dist/assets/{bicep-4QA1jTCM.js → bicep-pDx8da1v.js} +0 -0
  185. /package/dist/assets/{cameligo-Dda_adPs.js → cameligo-hZV3vQ6_.js} +0 -0
  186. /package/dist/assets/{chunk-Y2CYZVJY-Di0sYzi1.js → chunk-Y2CYZVJY-BYi0W5ea.js} +0 -0
  187. /package/dist/assets/{clojure-CvY8A83L.js → clojure-BhCwU3gq.js} +0 -0
  188. /package/dist/assets/{coffee-4LX2Sv8A.js → coffee-BVB1PsgH.js} +0 -0
  189. /package/dist/assets/{core-B9MwRjDE.js → core-vPkV8P_m.js} +0 -0
  190. /package/dist/assets/{cpp-D4AzSV28.js → cpp-eTjuiNPq.js} +0 -0
  191. /package/dist/assets/{csharp-C4nWN4Yd.js → csharp-1kP-kKZT.js} +0 -0
  192. /package/dist/assets/{csp-CiEaaS5Z.js → csp-C_iQqIes.js} +0 -0
  193. /package/dist/assets/{css-DXxBRsbk.js → css-CxOs7IfP.js} +0 -0
  194. /package/dist/assets/{cypher-DOMzqyFA.js → cypher-Cd-a9Y82.js} +0 -0
  195. /package/dist/assets/{cytoscape.esm-Cl8iC3D7.js → cytoscape.esm-OLojuicg.js} +0 -0
  196. /package/dist/assets/{dart-BWS7MXt_.js → dart-whhXpexn.js} +0 -0
  197. /package/dist/assets/{defaultLocale-GJwWH1Jr.js → defaultLocale-CPimVhfE.js} +0 -0
  198. /package/dist/assets/{dist-ClAL-Tv5.js → dist-CLPYXg1u.js} +0 -0
  199. /package/dist/assets/{dockerfile-Baf4U3xT.js → dockerfile-DibBi8b6.js} +0 -0
  200. /package/dist/assets/{ecl-CT7udlkQ.js → ecl-DlF1KdoW.js} +0 -0
  201. /package/dist/assets/{editor.api2-B9h6iFPK.js → editor.api2-D74ufkbr.js} +0 -0
  202. /package/dist/assets/{elixir-CIbAZw5l.js → elixir-CHQ8uFT3.js} +0 -0
  203. /package/dist/assets/{flow9-Dqiw45WV.js → flow9-8nUnVKk1.js} +0 -0
  204. /package/dist/assets/{fsharp-E8E2L9jU.js → fsharp-DNZNZyEE.js} +0 -0
  205. /package/dist/assets/{go-CouVSN4D.js → go-_kPJjNc_.js} +0 -0
  206. /package/dist/assets/{graphlib-CbSvZcZU.js → graphlib-CBGF9gA9.js} +0 -0
  207. /package/dist/assets/{graphql-a6h6h5-t.js → graphql-4HCNVt5g.js} +0 -0
  208. /package/dist/assets/{hcl-vso3e6N-.js → hcl-C87ymY0o.js} +0 -0
  209. /package/dist/assets/{ini-SwZZkotR.js → ini-BYrrGNTc.js} +0 -0
  210. /package/dist/assets/{init-BPLPMQ3Y.js → init-8_PCa3LO.js} +0 -0
  211. /package/dist/assets/{java-CtRLC4_3.js → java-B211rJbk.js} +0 -0
  212. /package/dist/assets/{julia-CuJWFKQH.js → julia-DVPsRtBm.js} +0 -0
  213. /package/dist/assets/{katex-C8DPyI6s.js → katex-C2iYad0m.js} +0 -0
  214. /package/dist/assets/{kotlin-B_kBhsre.js → kotlin-BYyNerjp.js} +0 -0
  215. /package/dist/assets/{less-CtrHIfz3.js → less-EC9UblGE.js} +0 -0
  216. /package/dist/assets/{lexon-yVt1pJLu.js → lexon-B1yuUdqW.js} +0 -0
  217. /package/dist/assets/{lua-prOwgJ4U.js → lua-Bo6GFzxT.js} +0 -0
  218. /package/dist/assets/{m3-DhrdTQ84.js → m3-C-Y9cBVI.js} +0 -0
  219. /package/dist/assets/{markdown-DoI5HwSM.js → markdown-BHu-wLbe.js} +0 -0
  220. /package/dist/assets/{mermaid-parser.core-BQtepoLd.js → mermaid-parser.core-IxcGtNYG.js} +0 -0
  221. /package/dist/assets/{mips-Dye5jsBU.js → mips-Dkds26rX.js} +0 -0
  222. /package/dist/assets/{msdax-TkNOyIL8.js → msdax-PU4g-8_g.js} +0 -0
  223. /package/dist/assets/{mysql-CEc-RKZF.js → mysql-fPpuGpS7.js} +0 -0
  224. /package/dist/assets/{objective-c-CqqCsQz0.js → objective-c-Wy1e5JUf.js} +0 -0
  225. /package/dist/assets/{pascal-DO_pj8yg.js → pascal-BIXwvrqU.js} +0 -0
  226. /package/dist/assets/{pascaligo-1cbdZdF3.js → pascaligo-DwR0AtL_.js} +0 -0
  227. /package/dist/assets/{path-YdFzr2W6.js → path-BxQuywGl.js} +0 -0
  228. /package/dist/assets/{perl-B_N3PYfC.js → perl-CGHAl8my.js} +0 -0
  229. /package/dist/assets/{pgsql-9iYk1FQ7.js → pgsql-B91M73E2.js} +0 -0
  230. /package/dist/assets/{php-rO2sgoUM.js → php-C70ttrCx.js} +0 -0
  231. /package/dist/assets/{pla-Cbgv60kq.js → pla-BNrXaRh0.js} +0 -0
  232. /package/dist/assets/{postiats-y6xrhy_M.js → postiats-Dv2IAZR9.js} +0 -0
  233. /package/dist/assets/{powerquery-BaD8gWNE.js → powerquery-CkY5Wq7J.js} +0 -0
  234. /package/dist/assets/{powershell-BMhG4JVe.js → powershell-uGIYLU3C.js} +0 -0
  235. /package/dist/assets/{protobuf-CdOP5kN3.js → protobuf-CavfmJu-.js} +0 -0
  236. /package/dist/assets/{pug-Ck60hNF9.js → pug-Drqnyitx.js} +0 -0
  237. /package/dist/assets/{qrcode-DV-_rjU8.js → qrcode-7uGsTEmi.js} +0 -0
  238. /package/dist/assets/{qsharp-BcaKFfP-.js → qsharp-C7yF-Dti.js} +0 -0
  239. /package/dist/assets/{r-rFNes0iK.js → r-HXLGP0_-.js} +0 -0
  240. /package/dist/assets/{redis-DkpQ3K6G.js → redis-C4y6mf2p.js} +0 -0
  241. /package/dist/assets/{redshift-Di9N5TI3.js → redshift-DdQrFPrv.js} +0 -0
  242. /package/dist/assets/{restructuredtext-By5ZzLt6.js → restructuredtext-CBrf2PNj.js} +0 -0
  243. /package/dist/assets/{rough.esm-c4PR5shF.js → rough.esm-6CnTHTkH.js} +0 -0
  244. /package/dist/assets/{ruby-fF5zGiLp.js → ruby-CVtMoX2B.js} +0 -0
  245. /package/dist/assets/{rust-C5b3AYY6.js → rust-Ckq4sbMP.js} +0 -0
  246. /package/dist/assets/{sb-CD07UNRs.js → sb-er66OpE3.js} +0 -0
  247. /package/dist/assets/{scala-jtn1ip9G.js → scala-BdWZz-hH.js} +0 -0
  248. /package/dist/assets/{scheme-C4vqMkn_.js → scheme-kTV_s2kP.js} +0 -0
  249. /package/dist/assets/{scss-I4Glxjbz.js → scss-Bymy2VJQ.js} +0 -0
  250. /package/dist/assets/{shell-DubUcuns.js → shell-bwEP1LQ1.js} +0 -0
  251. /package/dist/assets/{solidity-CNy2KSl2.js → solidity-DCddeBgL.js} +0 -0
  252. /package/dist/assets/{sophia-D9_URt84.js → sophia-BMN3AQig.js} +0 -0
  253. /package/dist/assets/{sparql-DviXpm4C.js → sparql-CSXOLjzi.js} +0 -0
  254. /package/dist/assets/{sql-vDRbAZgK.js → sql-COg8ewmK.js} +0 -0
  255. /package/dist/assets/{st-CnpnRqA7.js → st-CIaZtBS_.js} +0 -0
  256. /package/dist/assets/{swift-D_g8Ganz.js → swift-DXiOhUHu.js} +0 -0
  257. /package/dist/assets/{systemverilog-OuZa5h1M.js → systemverilog-BTDE6Dr2.js} +0 -0
  258. /package/dist/assets/{tcl-DBm9ucsV.js → tcl-B0zy9NvV.js} +0 -0
  259. /package/dist/assets/{twig-BRzEqX5Y.js → twig-D594l4Z1.js} +0 -0
  260. /package/dist/assets/{typespec-D4ZgGeX8.js → typespec-XKVjaPqo.js} +0 -0
  261. /package/dist/assets/{useWorkspaceMenuKeyboard-Brws6Ar9.js → useWorkspaceMenuKeyboard-Bpy_s69Z.js} +0 -0
  262. /package/dist/assets/{vb-DcP4s6WS.js → vb-B2wYoKRV.js} +0 -0
  263. /package/dist/assets/{wgsl-CnRJ_QUf.js → wgsl-D0Yf2LbK.js} +0 -0
  264. /package/dist/assets/{workspace-working-copy-D8-s_Sgh.js → workspace-working-copy-C3FSR3N6.js} +0 -0
@@ -16,7 +16,12 @@ const { archiveCodexSession, unarchiveCodexSession } = require('./codex-session-
16
16
  const { buildAgentProviderSessionPlan, sessionFromExactResumeSource } = require('./agent-provider-session');
17
17
  const { resolveAgentExecutable, resolveCompatibleCodexExecutable } = require('./executable-discovery');
18
18
  const { ensureMainAgentSkillFiles, renderMainAgentBootstrap } = require('./main-agent-skills');
19
+ const {
20
+ appendOpenCodeBootstrap,
21
+ renderFarmingAgentBootstrap,
22
+ } = require('./farming-agent-bootstrap');
19
23
  const { mainPageAgentSessionKey, resumedAgentSource } = require('./main-page-session');
24
+ const storageLayout = require('./storage-layout');
20
25
  const { isSafeProviderSessionId, isTemporaryProviderSessionId } = require('./provider-session-id');
21
26
  const { ProviderSessionService } = require('./provider-session-service');
22
27
  const {
@@ -67,6 +72,7 @@ const { inspectGitWorktree } = require('./git-worktree-info');
67
72
  const { deserializeTerminalState } = require('./terminal-state-serialization');
68
73
  const { compareNativePtyRuntimeEpochs } = require('./native-pty-controller-generation');
69
74
  const { canonicalWorkspacePath } = require('./workspace-root-registry');
75
+ const { mergeBrowserMcpServer } = require('../extensions/browser/backend/agent-capability');
70
76
  const {
71
77
  TERMINAL_OPERATION_STATES,
72
78
  activeLifecycleOperation,
@@ -83,6 +89,7 @@ const AGENT_USAGE_RATE_REFRESH_MS = 5 * 1000;
83
89
  const ACTIVITY_UPDATE_INTERVAL_MS = 1000;
84
90
  const ACTIVITY_HOT_SEC = 30 * 60;
85
91
  const ACTIVITY_WARM_SEC = 3 * 60 * 60;
92
+ const MAX_COMPOSER_COMMANDS = 64;
86
93
  const ACTIVITY_COOL_SEC = 12 * 60 * 60;
87
94
  const ZOMBIE_IDLE_MS = 72 * 60 * 60 * 1000;
88
95
  const ZOMBIE_SWEEP_INTERVAL_MS = 60 * 1000;
@@ -369,6 +376,41 @@ function normalizedComposerPrompt(message) {
369
376
  return prompt;
370
377
  }
371
378
 
379
+ function composerCommandHash(prompt) {
380
+ return crypto.createHash('sha256')
381
+ .update(JSON.stringify(stableJsonValue(prompt)))
382
+ .digest('hex');
383
+ }
384
+
385
+ function composerAdmissionError(message, uncertain = false) {
386
+ const error = new Error(message);
387
+ if (uncertain) error.uncertain = true;
388
+ return error;
389
+ }
390
+
391
+ function normalizedComposerCommands(commands) {
392
+ return (Array.isArray(commands) ? commands : [])
393
+ .filter(command => (
394
+ command
395
+ && typeof command === 'object'
396
+ && /^[A-Za-z0-9._:-]{1,160}$/.test(command.requestId)
397
+ && typeof command.contentHash === 'string'
398
+ && ['intent', 'accepted', 'unknown', 'failed'].includes(command.state)
399
+ ))
400
+ .slice(-MAX_COMPOSER_COMMANDS)
401
+ .map(command => ({
402
+ requestId: command.requestId,
403
+ contentHash: command.contentHash,
404
+ state: command.state,
405
+ result: command.result && typeof command.result === 'object'
406
+ ? JSON.parse(JSON.stringify(command.result))
407
+ : null,
408
+ error: typeof command.error === 'string' ? command.error.slice(0, 2000) : '',
409
+ createdAt: Number(command.createdAt) || 0,
410
+ updatedAt: Number(command.updatedAt) || 0,
411
+ }));
412
+ }
413
+
372
414
  function isShellProgram(command) {
373
415
  return ['bash', 'zsh', 'sh', 'fish'].includes(agentProgramName(command).toLowerCase());
374
416
  }
@@ -738,6 +780,27 @@ function timestampSlug(now = new Date()) {
738
780
  ].join('');
739
781
  }
740
782
 
783
+ function projectOperationSignature(value) {
784
+ return crypto.createHash('sha256')
785
+ .update(JSON.stringify(stableJsonValue(value)))
786
+ .digest('hex');
787
+ }
788
+
789
+ function worktreesFromPorcelain(output) {
790
+ const worktrees = [];
791
+ let current = null;
792
+ for (const line of String(output || '').split(/\r?\n/)) {
793
+ if (line.startsWith('worktree ')) {
794
+ if (current) worktrees.push(current);
795
+ current = { workspace: path.resolve(line.slice('worktree '.length)), branch: '' };
796
+ } else if (current && line.startsWith('branch refs/heads/')) {
797
+ current.branch = line.slice('branch refs/heads/'.length);
798
+ }
799
+ }
800
+ if (current) worktrees.push(current);
801
+ return worktrees;
802
+ }
803
+
741
804
  function isFarmingForkWorktreePath(workspace) {
742
805
  const basename = path.basename(String(workspace || '').replace(/[\\/]+$/, ''));
743
806
  return /-farming-fork-\d{8}-\d{6}(?:-\d+)?$/.test(basename);
@@ -757,6 +820,7 @@ class AgentManager extends EventEmitter {
757
820
  this.controlUrl = options.controlUrl || '';
758
821
  this.tokenFile = options.tokenFile || '';
759
822
  this.authDisabled = options.authDisabled === true;
823
+ this.browserMcpEnabled = options.browserMcpEnabled === true;
760
824
  this.skipExecutablePreflight = options.skipExecutablePreflight === true;
761
825
  this.cliBinDir = options.cliBinDir || path.join(__dirname, '..', 'bin');
762
826
  this.agentShellEnvProvider = typeof options.agentShellEnvProvider === 'function'
@@ -780,6 +844,7 @@ class AgentManager extends EventEmitter {
780
844
  this.pendingResizeByAgent = new Map();
781
845
  this.resizeDrains = new Map();
782
846
  this.inputQueues = new Map();
847
+ this.composerAdmissions = new Map();
783
848
  this.codexTerminalProfileQueues = new Map();
784
849
  this.codexTerminalStartQueues = new Map();
785
850
  this.codexTerminalStartOutput = new Map();
@@ -787,6 +852,7 @@ class AgentManager extends EventEmitter {
787
852
  this.agentLifecycleOperations = new Map();
788
853
  this.agentStartAdmissions = new Map();
789
854
  this.createRequestAdmissions = new Map();
855
+ this.projectOperationAdmissions = new Map();
790
856
  this.projectWorkspaceDeleteAdmissions = new Map();
791
857
  this.activeInputOperations = new Set();
792
858
  this.verifiedStoppedAgentIds = new Set();
@@ -1427,9 +1493,12 @@ class AgentManager extends EventEmitter {
1427
1493
  const recoveredLifecycleOperation = activeLifecycleOperation(agentRecord);
1428
1494
  if (
1429
1495
  recoveredLifecycleOperation?.type === 'create'
1430
- && recoveredLifecycleOperation.state === 'pending'
1496
+ && ['pending', 'membership-pending'].includes(recoveredLifecycleOperation.state)
1431
1497
  ) {
1432
1498
  try {
1499
+ if (recoveredLifecycleOperation.state === 'membership-pending') {
1500
+ this.rememberMainPageProviderSession(agentRecord);
1501
+ }
1433
1502
  this.transitionPersistentAgentOperation(
1434
1503
  agentRecord,
1435
1504
  recoveredLifecycleOperation.id,
@@ -1482,7 +1551,7 @@ class AgentManager extends EventEmitter {
1482
1551
  changed = true;
1483
1552
  }
1484
1553
 
1485
- if (this.reconcileMissingTerminalLifecycleOperations(
1554
+ if (await this.reconcileMissingTerminalLifecycleOperations(
1486
1555
  persistedRecords,
1487
1556
  recoveredRuntimeAgentIds,
1488
1557
  )) {
@@ -1519,7 +1588,7 @@ class AgentManager extends EventEmitter {
1519
1588
  }
1520
1589
  }
1521
1590
 
1522
- reconcileMissingTerminalLifecycleOperations(records, recoveredRuntimeAgentIds) {
1591
+ async reconcileMissingTerminalLifecycleOperations(records, recoveredRuntimeAgentIds) {
1523
1592
  let changed = false;
1524
1593
  for (const record of Array.isArray(records) ? records : []) {
1525
1594
  const agentId = String(record?.runtimeAgentId || '').trim();
@@ -1553,7 +1622,7 @@ class AgentManager extends EventEmitter {
1553
1622
  operation.request?.previousRuntimeAgentId,
1554
1623
  ),
1555
1624
  );
1556
- } else {
1625
+ } else if (operation.type === 'delete') {
1557
1626
  this.transitionPersistentAgentOperation(
1558
1627
  recoveredAgent,
1559
1628
  operation.id,
@@ -1567,7 +1636,27 @@ class AgentManager extends EventEmitter {
1567
1636
  },
1568
1637
  );
1569
1638
  this.removeMainPageProviderSessionsForAgents([recoveredAgent]);
1570
- if (operation.type === 'archive') this.scheduleCodexSessionArchive(recoveredAgent);
1639
+ } else {
1640
+ this.transitionPersistentAgentOperation(
1641
+ recoveredAgent,
1642
+ operation.id,
1643
+ 'provider-archive-pending',
1644
+ '',
1645
+ {
1646
+ visibleOnMainPage: false,
1647
+ archived: true,
1648
+ archivedAt: Date.now(),
1649
+ runtimeAgentId: '',
1650
+ },
1651
+ );
1652
+ this.removeMainPageProviderSessionsForAgents([recoveredAgent]);
1653
+ const providerArchive = await this.archiveCodexProviderSession(recoveredAgent);
1654
+ this.transitionPersistentAgentOperation(
1655
+ recoveredAgent,
1656
+ operation.id,
1657
+ providerArchive?.error ? 'blocked' : 'succeeded',
1658
+ providerArchive?.error || '',
1659
+ );
1571
1660
  }
1572
1661
  changed = true;
1573
1662
  } catch (error) {
@@ -1936,11 +2025,16 @@ class AgentManager extends EventEmitter {
1936
2025
  ? this.configManager.getCodexApprovalMode()
1937
2026
  : 'approve'
1938
2027
  );
2028
+ const recoveryEnv = this.buildAgentEnv(agentId, agent);
2029
+ const recoveryMcpServers = this.projectAcpMcpServers(
2030
+ Array.isArray(record.acpMcpServers) ? record.acpMcpServers : [],
2031
+ recoveryEnv,
2032
+ );
1939
2033
  const prepared = await this.acpRuntime.prepareAgent({
1940
2034
  agentId,
1941
2035
  provider,
1942
2036
  executable,
1943
- env: this.buildAgentEnv(agentId, agent),
2037
+ env: recoveryEnv,
1944
2038
  cwd: agent.cwd,
1945
2039
  sessionId,
1946
2040
  historyMode: 'checkpoint',
@@ -1951,8 +2045,9 @@ class AgentManager extends EventEmitter {
1951
2045
  model: 'config',
1952
2046
  reasoningEffort: 'config',
1953
2047
  serviceTier: 'config',
2048
+ farmingSystemPrompt: renderFarmingAgentBootstrap(),
1954
2049
  additionalDirectories: Array.isArray(record.acpAdditionalDirectories) ? record.acpAdditionalDirectories : [],
1955
- mcpServers: Array.isArray(record.acpMcpServers) ? record.acpMcpServers : [],
2050
+ mcpServers: recoveryMcpServers,
1956
2051
  onProcessStarted: async processIdentity => {
1957
2052
  agent.structuredRuntimeProcess = {
1958
2053
  kind: 'acp-process-group',
@@ -1967,13 +2062,21 @@ class AgentManager extends EventEmitter {
1967
2062
  prepared.sessionId,
1968
2063
  agent.providerHomeId || record.providerHomeId || 'default'
1969
2064
  );
1970
- this.acpSessionOptionsByKey.set(agent.providerSessionKey, {
2065
+ let recoveredSessionOptions = {
1971
2066
  additionalDirectories: Array.isArray(record.acpAdditionalDirectories)
1972
- ? [...record.acpAdditionalDirectories]
1973
- : [],
1974
- mcpServers: Array.isArray(record.acpMcpServers)
1975
- ? JSON.parse(JSON.stringify(record.acpMcpServers))
2067
+ ? record.acpAdditionalDirectories
1976
2068
  : [],
2069
+ mcpServers: recoveryMcpServers,
2070
+ };
2071
+ try {
2072
+ recoveredSessionOptions = this.acpRuntime.getSessionRequestOptions(agentId);
2073
+ } catch {
2074
+ // The live binding already validated these options. Retain the
2075
+ // projected copy for custom runtimes that do not expose it.
2076
+ }
2077
+ this.acpSessionOptionsByKey.set(agent.providerSessionKey, {
2078
+ additionalDirectories: [...recoveredSessionOptions.additionalDirectories],
2079
+ mcpServers: JSON.parse(JSON.stringify(recoveredSessionOptions.mcpServers)),
1977
2080
  });
1978
2081
  agent.providerSessionTemporary = false;
1979
2082
  agent.providerSessionSource = `acp-${prepared.historyMode}`;
@@ -2053,7 +2156,16 @@ class AgentManager extends EventEmitter {
2053
2156
  setAgentRecordId(staged, record.id || '');
2054
2157
  staged.structuredRuntimeProcess = null;
2055
2158
  try {
2056
- if (operation.type === 'create') {
2159
+ if (operation.type === 'create' && operation.state === 'membership-pending') {
2160
+ this.rememberMainPageProviderSession(staged);
2161
+ this.transitionPersistentAgentOperation(
2162
+ staged,
2163
+ operation.id,
2164
+ 'succeeded',
2165
+ '',
2166
+ { archived: false },
2167
+ );
2168
+ } else if (operation.type === 'create') {
2057
2169
  this.transitionPersistentAgentOperation(
2058
2170
  staged,
2059
2171
  operation.id,
@@ -2061,7 +2173,7 @@ class AgentManager extends EventEmitter {
2061
2173
  'Create ACP process was stopped during restart recovery',
2062
2174
  createFailurePatch(operation, operation.request?.previousRuntimeAgentId),
2063
2175
  );
2064
- } else {
2176
+ } else if (operation.type === 'delete') {
2065
2177
  this.transitionPersistentAgentOperation(
2066
2178
  staged,
2067
2179
  operation.id,
@@ -2076,7 +2188,28 @@ class AgentManager extends EventEmitter {
2076
2188
  },
2077
2189
  );
2078
2190
  this.removeMainPageProviderSessionsForAgents([staged]);
2079
- if (operation.type === 'archive') this.scheduleCodexSessionArchive(staged);
2191
+ } else {
2192
+ this.transitionPersistentAgentOperation(
2193
+ staged,
2194
+ operation.id,
2195
+ 'provider-archive-pending',
2196
+ '',
2197
+ {
2198
+ visibleOnMainPage: false,
2199
+ archived: true,
2200
+ archivedAt: Date.now(),
2201
+ runtimeAgentId: '',
2202
+ structuredRuntimeProcess: null,
2203
+ },
2204
+ );
2205
+ this.removeMainPageProviderSessionsForAgents([staged]);
2206
+ const providerArchive = await this.archiveCodexProviderSession(staged);
2207
+ this.transitionPersistentAgentOperation(
2208
+ staged,
2209
+ operation.id,
2210
+ providerArchive?.error ? 'blocked' : 'succeeded',
2211
+ providerArchive?.error || '',
2212
+ );
2080
2213
  }
2081
2214
  } catch (error) {
2082
2215
  console.warn(
@@ -2201,6 +2334,7 @@ class AgentManager extends EventEmitter {
2201
2334
  category: metadata.category || 'coding',
2202
2335
  launchPermissionMode: metadata.launchPermissionMode || '',
2203
2336
  parentAgentId: metadata.parentAgentId || '',
2337
+ forkRequestId: metadata.forkRequestId || '',
2204
2338
  task: metadata.task || '',
2205
2339
  workflowTemplate: metadata.workflowTemplate || '',
2206
2340
  source: metadata.source || 'recovered',
@@ -2241,6 +2375,7 @@ class AgentManager extends EventEmitter {
2241
2375
  agentRecordId,
2242
2376
  persistentSessionId: agentRecordId,
2243
2377
  lifecycleJournal: lifecycleJournal(metadata),
2378
+ composerCommands: normalizedComposerCommands(metadata.composerCommands),
2244
2379
  customTitle: metadata.customTitle || '',
2245
2380
  terminalBusy: typeof state.terminalBusy === 'boolean' ? state.terminalBusy : null,
2246
2381
  shellCwd: state.shellCwd || metadata.cwd || '',
@@ -2469,6 +2604,26 @@ class AgentManager extends EventEmitter {
2469
2604
  return operation;
2470
2605
  }
2471
2606
 
2607
+ completePersistentAgentOperation(agent, operationId, result, patch = {}) {
2608
+ const staged = {
2609
+ ...agent,
2610
+ lifecycleJournal: lifecycleJournal(agent),
2611
+ };
2612
+ const operation = setLifecycleOperationResult(staged, operationId, result);
2613
+ if (!operation) throw new Error(`Agent operation ${operationId} was not found`);
2614
+ transitionLifecycleOperation(staged, operationId, 'succeeded');
2615
+ const persistentSessionId = this.ensurePersistentAgentSession(staged, patch);
2616
+ if (
2617
+ typeof this.configManager?.ensureAgentSessionRecord === 'function'
2618
+ && !persistentSessionId
2619
+ ) {
2620
+ throw new Error('Agent session store did not return a persistent id');
2621
+ }
2622
+ agent.lifecycleJournal = staged.lifecycleJournal;
2623
+ setAgentRecordId(agent, staged.agentRecordId || staged.persistentSessionId || '');
2624
+ return operation;
2625
+ }
2626
+
2472
2627
  beginPersistentAgentUpdate(agent, requestKey, request) {
2473
2628
  const latest = latestLifecycleOperation(agent);
2474
2629
  if (
@@ -2975,6 +3130,7 @@ class AgentManager extends EventEmitter {
2975
3130
  env.FARMING_AGENT_ID = agentId;
2976
3131
  env.FARMING_IS_MAIN_AGENT = agent.wantsMain ? '1' : '0';
2977
3132
  env.FARMING_SKILLS_COMMAND = 'farming skills';
3133
+ env.FARMING_CAPABILITIES_COMMAND = 'farming capabilities';
2978
3134
  env.FARMING_MAIN_WORKSPACE = agent.mainWorkspace || '';
2979
3135
  env.FARMING_PROJECT_WORKSPACE = effectiveAgentWorkspaceRoot(agent);
2980
3136
 
@@ -2992,6 +3148,7 @@ class AgentManager extends EventEmitter {
2992
3148
  }
2993
3149
  if (this.configManager && this.configManager.farmingDir) {
2994
3150
  env.FARMING_CONFIG_DIR = this.configManager.farmingDir;
3151
+ env.FARMING_STARTUP_PROMPT_FILE = storageLayout.farmingAgentBootstrapFile(this.configManager.farmingDir);
2995
3152
  }
2996
3153
  if (agent.mainWorkspace) {
2997
3154
  env.FARMING_SKILLS_FILE = path.join(agent.mainWorkspace, 'FARMING_MAIN_AGENT_SKILLS.md');
@@ -3004,10 +3161,22 @@ class AgentManager extends EventEmitter {
3004
3161
  agent.providerHomePath
3005
3162
  );
3006
3163
  }
3164
+ const provider = agent.providerSessionProvider || agentHomeProviderForProgram(agent.forkCommand || agent.command);
3165
+ if (provider === 'opencode' && env.FARMING_STARTUP_PROMPT_FILE) {
3166
+ Object.assign(env, appendOpenCodeBootstrap(env, env.FARMING_STARTUP_PROMPT_FILE));
3167
+ }
3007
3168
 
3008
3169
  return env;
3009
3170
  }
3010
3171
 
3172
+ projectAcpMcpServers(mcpServers, agentEnv) {
3173
+ if (!this.browserMcpEnabled) return Array.isArray(mcpServers) ? mcpServers : [];
3174
+ return mergeBrowserMcpServer(mcpServers, {
3175
+ cliBinDir: this.cliBinDir,
3176
+ agentEnv,
3177
+ });
3178
+ }
3179
+
3011
3180
  expandWorkspacePath(workspace) {
3012
3181
  if (typeof workspace !== 'string') return '';
3013
3182
  const value = workspace.trim();
@@ -3253,6 +3422,7 @@ class AgentManager extends EventEmitter {
3253
3422
  category: agent.category,
3254
3423
  launchPermissionMode: agent.launchPermissionMode,
3255
3424
  parentAgentId: agent.parentAgentId || '',
3425
+ forkRequestId: agent.forkRequestId || '',
3256
3426
  task: agent.task,
3257
3427
  workflowTemplate: agent.workflowTemplate,
3258
3428
  source: agent.source,
@@ -3615,6 +3785,7 @@ class AgentManager extends EventEmitter {
3615
3785
  codexModel,
3616
3786
  codexReasoningEffort,
3617
3787
  codexServiceTier,
3788
+ farmingSystemPrompt: renderFarmingAgentBootstrap(),
3618
3789
  mainAgentSystemPrompt: wantsMain ? renderMainAgentBootstrap() : '',
3619
3790
  });
3620
3791
  const program = launch.program;
@@ -3863,6 +4034,7 @@ class AgentManager extends EventEmitter {
3863
4034
  category: resolution.spec ? resolution.spec.category : 'other',
3864
4035
  launchPermissionMode: launch.permissionMode || '',
3865
4036
  parentAgentId,
4037
+ forkRequestId: typeof options.forkRequestId === 'string' ? options.forkRequestId : '',
3866
4038
  task: typeof options.task === 'string' ? options.task : '',
3867
4039
  workflowTemplate: typeof options.workflowTemplate === 'string' ? options.workflowTemplate : '',
3868
4040
  source: typeof options.source === 'string' ? options.source : 'ui',
@@ -4076,7 +4248,10 @@ class AgentManager extends EventEmitter {
4076
4248
  const requestedAdditionalDirectories = Array.isArray(options.additionalDirectories)
4077
4249
  ? options.additionalDirectories
4078
4250
  : [];
4079
- const requestedMcpServers = Array.isArray(options.mcpServers) ? options.mcpServers : [];
4251
+ const requestedMcpServers = this.projectAcpMcpServers(
4252
+ Array.isArray(options.mcpServers) ? options.mcpServers : [],
4253
+ identityEnv,
4254
+ );
4080
4255
  const created = await this.createProviderSessionIdentity({
4081
4256
  provider: providerSessionPlan.provider,
4082
4257
  executable: spawnProgram,
@@ -4087,6 +4262,7 @@ class AgentManager extends EventEmitter {
4087
4262
  model: codexModel,
4088
4263
  reasoningEffort: codexReasoningEffort,
4089
4264
  serviceTier: codexServiceTier,
4265
+ farmingSystemPrompt: renderFarmingAgentBootstrap(),
4090
4266
  additionalDirectories: requestedAdditionalDirectories,
4091
4267
  mcpServers: requestedMcpServers,
4092
4268
  });
@@ -4230,14 +4406,16 @@ class AgentManager extends EventEmitter {
4230
4406
  const additionalDirectories = Array.isArray(options.additionalDirectories)
4231
4407
  ? options.additionalDirectories
4232
4408
  : rememberedSessionOptions.additionalDirectories || [];
4233
- const mcpServers = Array.isArray(options.mcpServers)
4409
+ const requestedMcpServers = Array.isArray(options.mcpServers)
4234
4410
  ? options.mcpServers
4235
4411
  : rememberedSessionOptions.mcpServers || [];
4412
+ const acpEnv = this.buildAgentEnv(agentId, agentRecord);
4413
+ const mcpServers = this.projectAcpMcpServers(requestedMcpServers, acpEnv);
4236
4414
  const prepared = await this.acpRuntime.prepareAgent({
4237
4415
  agentId,
4238
4416
  provider: structuredRuntimeProvider,
4239
4417
  executable: spawnProgram,
4240
- env: this.buildAgentEnv(agentId, agentRecord),
4418
+ env: acpEnv,
4241
4419
  cwd: workspace,
4242
4420
  sessionId: options.acpStartFresh === true || agentRecord.providerSessionTemporary || acpGeneratedFreshSession
4243
4421
  ? ''
@@ -4250,6 +4428,7 @@ class AgentManager extends EventEmitter {
4250
4428
  model: codexModel,
4251
4429
  reasoningEffort: codexReasoningEffort,
4252
4430
  serviceTier: codexServiceTier,
4431
+ farmingSystemPrompt: renderFarmingAgentBootstrap(),
4253
4432
  additionalDirectories,
4254
4433
  mcpServers,
4255
4434
  forkSourceSessionId: options.acpForkSourceSessionId || '',
@@ -4350,13 +4529,18 @@ class AgentManager extends EventEmitter {
4350
4529
  await startTerminal();
4351
4530
  }
4352
4531
  }
4353
- this.transitionPersistentAgentOperation(agentRecord, createOperationId, 'succeeded', '', {
4532
+ this.transitionPersistentAgentOperation(agentRecord, createOperationId, 'membership-pending', '', {
4354
4533
  visibleOnMainPage: true,
4355
4534
  archived: false,
4356
4535
  ...(options.customTitleExplicit === true
4357
4536
  ? { customTitle: agentRecord.customTitle }
4358
4537
  : {}),
4359
4538
  });
4539
+ this.rememberMainPageProviderSession(agentRecord);
4540
+ this.transitionPersistentAgentOperation(agentRecord, createOperationId, 'succeeded', '', {
4541
+ visibleOnMainPage: true,
4542
+ archived: false,
4543
+ });
4360
4544
  if (
4361
4545
  previousPersistentRuntimeAgentId
4362
4546
  && previousPersistentRuntimeAgentId !== agentId
@@ -4576,7 +4760,171 @@ class AgentManager extends EventEmitter {
4576
4760
  return this.enqueueInputOperation(agentId, () => this.sendInputNow(agentId, input, options));
4577
4761
  }
4578
4762
 
4579
- async sendComposerMessage(agentId, message) {
4763
+ commitComposerCommand(agent, command) {
4764
+ const commands = normalizedComposerCommands(agent.composerCommands)
4765
+ .filter(candidate => candidate.requestId !== command.requestId);
4766
+ commands.push(command);
4767
+ const staged = {
4768
+ ...agent,
4769
+ composerCommands: normalizedComposerCommands(commands),
4770
+ };
4771
+ const persistentSessionId = this.ensurePersistentAgentSession(staged);
4772
+ if (
4773
+ typeof this.configManager?.ensureAgentSessionRecord === 'function'
4774
+ && !persistentSessionId
4775
+ ) {
4776
+ throw new Error('Agent session store did not return a persistent id');
4777
+ }
4778
+ agent.composerCommands = staged.composerCommands;
4779
+ setAgentRecordId(agent, staged.agentRecordId || staged.persistentSessionId || '');
4780
+ return command;
4781
+ }
4782
+
4783
+ setComposerCommandInMemory(agent, command) {
4784
+ agent.composerCommands = normalizedComposerCommands([
4785
+ ...normalizedComposerCommands(agent.composerCommands)
4786
+ .filter(candidate => candidate.requestId !== command.requestId),
4787
+ command,
4788
+ ]);
4789
+ }
4790
+
4791
+ sendPersistentComposerMessage(agentId, message, requestId) {
4792
+ const agent = this.agents.get(agentId);
4793
+ if (!agent) return Promise.reject(new Error('Agent not found'));
4794
+ if (runtimeKind(agent) === 'terminal') {
4795
+ return Promise.reject(new Error('Persistent Composer admission requires a structured runtime'));
4796
+ }
4797
+ const prompt = normalizedComposerPrompt(message);
4798
+ const contentHash = composerCommandHash(prompt);
4799
+ const commands = normalizedComposerCommands(agent.composerCommands);
4800
+ const existing = commands.find(command => command.requestId === requestId);
4801
+ const admissionKey = `${agentId}:${requestId}`;
4802
+ const inFlight = this.composerAdmissions.get(admissionKey);
4803
+ if (existing?.contentHash && existing.contentHash !== contentHash) {
4804
+ return Promise.reject(new Error(`Composer request ${requestId} was already used for different content`));
4805
+ }
4806
+ if (existing?.state === 'accepted') {
4807
+ return Promise.resolve({ ...(existing.result || {}), accepted: true, deduplicated: true });
4808
+ }
4809
+ if (inFlight) return inFlight.promise;
4810
+ if (existing?.state === 'unknown' || existing?.state === 'intent') {
4811
+ const detail = existing.error || `Composer request ${requestId} has an uncertain outcome and will not be replayed automatically`;
4812
+ if (existing.state === 'intent') {
4813
+ const unknown = { ...existing, state: 'unknown', error: detail, updatedAt: Date.now() };
4814
+ try {
4815
+ this.commitComposerCommand(agent, unknown);
4816
+ } catch {
4817
+ this.setComposerCommandInMemory(agent, unknown);
4818
+ }
4819
+ }
4820
+ return Promise.reject(composerAdmissionError(detail, true));
4821
+ }
4822
+ if (existing?.state === 'failed') {
4823
+ return Promise.reject(new Error(existing.error || `Composer request ${requestId} was not accepted`));
4824
+ }
4825
+
4826
+ const intent = {
4827
+ requestId,
4828
+ contentHash,
4829
+ state: 'intent',
4830
+ result: null,
4831
+ error: '',
4832
+ createdAt: Date.now(),
4833
+ updatedAt: Date.now(),
4834
+ };
4835
+ try {
4836
+ this.commitComposerCommand(agent, intent);
4837
+ } catch (error) {
4838
+ return Promise.reject(new Error(`Failed to persist Composer intent: ${error.message || error}`));
4839
+ }
4840
+
4841
+ let resolveAdmission;
4842
+ let rejectAdmission;
4843
+ const admissionPromise = new Promise((resolve, reject) => {
4844
+ resolveAdmission = resolve;
4845
+ rejectAdmission = reject;
4846
+ });
4847
+ const entry = { contentHash, promise: admissionPromise };
4848
+ this.composerAdmissions.set(admissionKey, entry);
4849
+ let submitted = false;
4850
+ const onSubmitted = (result = { kind: runtimeKind(agent) }) => {
4851
+ if (submitted) return;
4852
+ submitted = true;
4853
+ const accepted = {
4854
+ ...intent,
4855
+ state: 'accepted',
4856
+ result,
4857
+ updatedAt: Date.now(),
4858
+ };
4859
+ try {
4860
+ this.commitComposerCommand(agent, accepted);
4861
+ resolveAdmission({ ...result, accepted: true });
4862
+ } catch (error) {
4863
+ const unknown = {
4864
+ ...intent,
4865
+ state: 'unknown',
4866
+ error: `Provider accepted Composer request, but admission could not be saved: ${error.message || error}`,
4867
+ updatedAt: Date.now(),
4868
+ };
4869
+ this.setComposerCommandInMemory(agent, unknown);
4870
+ rejectAdmission(composerAdmissionError(unknown.error, true));
4871
+ }
4872
+ };
4873
+
4874
+ const completion = isAcpAgent(agent)
4875
+ ? this.enqueueInputOperationUntilReleased(
4876
+ agentId,
4877
+ releaseInput => this.sendComposerMessageNow(agentId, prompt, {
4878
+ onSubmitted: () => {
4879
+ try {
4880
+ onSubmitted({ kind: 'acp' });
4881
+ } finally {
4882
+ releaseInput();
4883
+ }
4884
+ },
4885
+ }),
4886
+ )
4887
+ : this.enqueueInputOperation(
4888
+ agentId,
4889
+ () => this.sendComposerMessageNow(agentId, prompt, {
4890
+ onSubmitted: result => onSubmitted(result),
4891
+ }),
4892
+ );
4893
+ void Promise.resolve(completion).then(result => {
4894
+ if (!submitted) onSubmitted(result);
4895
+ }).catch(error => {
4896
+ if (submitted) return;
4897
+ const failed = {
4898
+ ...intent,
4899
+ state: 'failed',
4900
+ error: error.message || String(error),
4901
+ updatedAt: Date.now(),
4902
+ };
4903
+ let uncertain = false;
4904
+ try {
4905
+ this.commitComposerCommand(agent, failed);
4906
+ } catch (persistError) {
4907
+ failed.state = 'unknown';
4908
+ failed.error = `${failed.error}; failed to persist rejection: ${persistError.message || persistError}`;
4909
+ this.setComposerCommandInMemory(agent, failed);
4910
+ uncertain = true;
4911
+ }
4912
+ rejectAdmission(composerAdmissionError(failed.error, uncertain));
4913
+ });
4914
+ void admissionPromise.finally(() => {
4915
+ if (this.composerAdmissions.get(admissionKey) === entry) {
4916
+ this.composerAdmissions.delete(admissionKey);
4917
+ }
4918
+ }).catch(() => {});
4919
+ return admissionPromise;
4920
+ }
4921
+
4922
+ async sendComposerMessage(agentId, message, options = {}) {
4923
+ const requestId = String(options.requestId || '').trim();
4924
+ if (requestId) {
4925
+ if (!/^[A-Za-z0-9._:-]{1,160}$/.test(requestId)) throw new Error('Composer requestId is invalid');
4926
+ return this.sendPersistentComposerMessage(agentId, message, requestId);
4927
+ }
4580
4928
  const agent = this.agents.get(agentId);
4581
4929
  if (agent && isAcpAgent(agent)) {
4582
4930
  return this.enqueueInputOperationUntilReleased(
@@ -4695,13 +5043,15 @@ class AgentManager extends EventEmitter {
4695
5043
  agent.providerSessionId = result.sessionId || agent.providerSessionId;
4696
5044
  agent.providerSessionTemporary = !agent.providerSessionId;
4697
5045
  this.ensurePersistentAgentSession(agent);
4698
- return { kind: 'json', sessionId: agent.providerSessionId };
5046
+ const submitted = { kind: 'json', sessionId: agent.providerSessionId };
5047
+ options.onSubmitted?.(submitted);
5048
+ return submitted;
4699
5049
  }
4700
5050
 
4701
5051
  if (isAcpAgent(agent)) {
4702
5052
  this.requireLiveAcpAgent(agentId);
4703
5053
  const result = await this.acpRuntime.submitMessage(agentId, prompt, {
4704
- onSubmitted: options.releaseInput,
5054
+ onSubmitted: options.onSubmitted || options.releaseInput,
4705
5055
  });
4706
5056
  if (result.steered !== true) {
4707
5057
  const runtime = runtimeBindingOf(agent, 'acp');
@@ -4713,7 +5063,9 @@ class AgentManager extends EventEmitter {
4713
5063
  }
4714
5064
 
4715
5065
  await this.sendInputNow(agentId, [{ type: 'paste', text }, '\r']);
4716
- return { kind: 'terminal' };
5066
+ const submitted = { kind: 'terminal' };
5067
+ options.onSubmitted?.(submitted);
5068
+ return submitted;
4717
5069
  }
4718
5070
 
4719
5071
  getJsonCliTranscript(agentId, options = {}) {
@@ -5942,6 +6294,104 @@ class AgentManager extends EventEmitter {
5942
6294
  : this.markAgentReadCursor(agentId);
5943
6295
  }
5944
6296
 
6297
+ persistentProjectOperation(requestId, type, signature, request) {
6298
+ if (
6299
+ !requestId
6300
+ || typeof this.configManager?.getProjectOperation !== 'function'
6301
+ || typeof this.configManager?.commitProjectOperation !== 'function'
6302
+ ) {
6303
+ return { operation: null, created: true };
6304
+ }
6305
+ const existing = this.configManager.getProjectOperation(requestId);
6306
+ if (existing) {
6307
+ if (existing.type !== type || existing.signature !== signature) {
6308
+ return { error: `Project operation request ${requestId} was already used for different parameters` };
6309
+ }
6310
+ return { operation: existing, created: false };
6311
+ }
6312
+ const operation = {
6313
+ id: requestId,
6314
+ type,
6315
+ state: 'pending',
6316
+ signature,
6317
+ request,
6318
+ result: null,
6319
+ error: '',
6320
+ startedAt: Date.now(),
6321
+ updatedAt: Date.now(),
6322
+ finishedAt: null,
6323
+ };
6324
+ try {
6325
+ this.configManager.commitProjectOperation(operation);
6326
+ return { operation, created: true };
6327
+ } catch (error) {
6328
+ return { error: `Failed to persist Project operation intent: ${error.message || error}` };
6329
+ }
6330
+ }
6331
+
6332
+ commitPersistentProjectOperation(operation, state, result = null, error = '', membership = {}) {
6333
+ if (!operation || typeof this.configManager?.commitProjectOperation !== 'function') {
6334
+ return { operation: null, projectWorkspaces: null, pinnedProjectWorkspaces: null };
6335
+ }
6336
+ const terminal = ['succeeded', 'failed', 'blocked'].includes(state);
6337
+ const nextOperation = {
6338
+ ...operation,
6339
+ state,
6340
+ result,
6341
+ error,
6342
+ updatedAt: Date.now(),
6343
+ finishedAt: terminal ? Date.now() : null,
6344
+ };
6345
+ return this.configManager.commitProjectOperation(nextOperation, membership);
6346
+ }
6347
+
6348
+ async listGitWorktrees(sourceWorkspace) {
6349
+ const { stdout } = await execFileAsync('git', ['-C', sourceWorkspace, 'worktree', 'list', '--porcelain'], {
6350
+ timeout: 15000,
6351
+ maxBuffer: 1024 * 1024 * 4,
6352
+ });
6353
+ return worktreesFromPorcelain(stdout);
6354
+ }
6355
+
6356
+ async inspectGitWorktreePostcondition(sourceWorkspace, workspace, branch = '') {
6357
+ const target = path.resolve(workspace);
6358
+ let worktrees;
6359
+ try {
6360
+ worktrees = await this.listGitWorktrees(sourceWorkspace);
6361
+ } catch (error) {
6362
+ return { proven: false, error: error.message || String(error) };
6363
+ }
6364
+ const registered = worktrees.find(entry => entry.workspace === target) || null;
6365
+ let exists = false;
6366
+ try {
6367
+ exists = fs.statSync(target).isDirectory();
6368
+ } catch {
6369
+ exists = false;
6370
+ }
6371
+ let branchExists = false;
6372
+ if (branch) {
6373
+ try {
6374
+ await execFileAsync('git', ['-C', sourceWorkspace, 'show-ref', '--verify', '--quiet', `refs/heads/${branch}`], {
6375
+ timeout: 15000,
6376
+ maxBuffer: 1024 * 1024,
6377
+ });
6378
+ branchExists = true;
6379
+ } catch (error) {
6380
+ if (error?.code !== 1) {
6381
+ return { proven: false, error: error.message || String(error) };
6382
+ }
6383
+ }
6384
+ }
6385
+ return {
6386
+ proven: true,
6387
+ exists,
6388
+ registered: Boolean(registered),
6389
+ branchMatches: !branch || registered?.branch === branch,
6390
+ branchExists,
6391
+ worktree: registered,
6392
+ };
6393
+ }
6394
+
5945
6395
  async resolveGitWorktreeSourceRoot(workspace) {
5946
6396
  const sourceWorkspace = this.expandWorkspacePath(workspace);
5947
6397
  if (!sourceWorkspace) {
@@ -5986,47 +6436,182 @@ class AgentManager extends EventEmitter {
5986
6436
  return target;
5987
6437
  }
5988
6438
 
5989
- async createPermanentWorktree(workspace) {
6439
+ createPermanentWorktree(workspace, options = {}) {
6440
+ const requestId = String(options.requestId || '').trim().slice(0, 160);
6441
+ if (!requestId) return this.createPermanentWorktreeAdmitted(workspace, options);
6442
+ const workspaceKey = canonicalWorkspacePath(this.expandWorkspacePath(workspace));
6443
+ const current = this.projectOperationAdmissions.get(requestId);
6444
+ if (current) {
6445
+ if (current.workspaceKey === workspaceKey) return current.promise;
6446
+ return Promise.reject(new Error(`Project operation request ${requestId} was already used for different parameters`));
6447
+ }
6448
+ const promise = this.createPermanentWorktreeAdmitted(workspace, options);
6449
+ const admission = { workspaceKey, promise };
6450
+ this.projectOperationAdmissions.set(requestId, admission);
6451
+ void promise.finally(() => {
6452
+ if (this.projectOperationAdmissions.get(requestId) === admission) {
6453
+ this.projectOperationAdmissions.delete(requestId);
6454
+ }
6455
+ }).catch(() => {});
6456
+ return promise;
6457
+ }
6458
+
6459
+ async createPermanentWorktreeAdmitted(workspace, options = {}) {
5990
6460
  const root = await this.resolveGitWorktreeSourceRoot(workspace);
6461
+ const requestId = String(options.requestId || '').trim().slice(0, 160);
6462
+ const signature = projectOperationSignature({ sourceWorkspace: root, type: 'create-worktree' });
6463
+ const existingOperation = requestId && typeof this.configManager?.getProjectOperation === 'function'
6464
+ ? this.configManager.getProjectOperation(requestId)
6465
+ : null;
6466
+ const operationWasExisting = Boolean(existingOperation);
6467
+ if (
6468
+ existingOperation
6469
+ && (
6470
+ existingOperation.type !== 'create-worktree'
6471
+ || existingOperation.signature !== signature
6472
+ )
6473
+ ) {
6474
+ throw new Error(`Project operation request ${requestId} was already used for different parameters`);
6475
+ }
6476
+ if (existingOperation?.state === 'succeeded' && existingOperation.result) {
6477
+ const settings = this.configManager.getSettings?.() || {};
6478
+ return {
6479
+ ...existingOperation.result,
6480
+ deduplicated: true,
6481
+ projectWorkspaces: settings.projectWorkspaces || [],
6482
+ pinnedProjectWorkspaces: settings.pinnedProjectWorkspaces || [],
6483
+ };
6484
+ }
6485
+
5991
6486
  const parentDir = path.dirname(root);
5992
6487
  const baseName = path.basename(root);
5993
- const slug = timestampSlug();
5994
- let suffix = 1;
5995
-
5996
- while (suffix < 1000) {
5997
- const suffixText = suffix === 1 ? '' : `-${suffix}`;
5998
- const target = path.join(parentDir, `${baseName}-farming-worktree-${slug}${suffixText}`);
5999
- const branch = `farming/worktree-${slug}${suffixText}`;
6000
- let branchExists = false;
6001
- try {
6002
- await execFileAsync('git', ['-C', root, 'show-ref', '--verify', '--quiet', `refs/heads/${branch}`], {
6003
- timeout: 15000,
6004
- maxBuffer: 1024 * 1024,
6005
- });
6006
- branchExists = true;
6007
- } catch (error) {
6008
- if (error?.code !== 1) {
6009
- const message = error && error.stderr ? String(error.stderr).trim() : '';
6010
- throw new Error(message || 'Failed to inspect git branches', { cause: error });
6011
- }
6012
- }
6013
-
6014
- if (!fs.existsSync(target) && !branchExists) {
6488
+ let target = String(existingOperation?.request?.workspace || '');
6489
+ let branch = String(existingOperation?.request?.branch || '');
6490
+ let operation = existingOperation;
6491
+ if (!operation) {
6492
+ const slug = timestampSlug();
6493
+ let suffix = 1;
6494
+ while (suffix < 1000) {
6495
+ const suffixText = suffix === 1 ? '' : `-${suffix}`;
6496
+ const candidateTarget = path.join(parentDir, `${baseName}-farming-worktree-${slug}${suffixText}`);
6497
+ const candidateBranch = `farming/worktree-${slug}${suffixText}`;
6498
+ let branchExists = false;
6015
6499
  try {
6016
- await execFileAsync('git', ['-C', root, 'worktree', 'add', '-b', branch, target, 'HEAD'], {
6017
- timeout: 60000,
6018
- maxBuffer: 1024 * 1024 * 4,
6500
+ await execFileAsync('git', ['-C', root, 'show-ref', '--verify', '--quiet', `refs/heads/${candidateBranch}`], {
6501
+ timeout: 15000,
6502
+ maxBuffer: 1024 * 1024,
6019
6503
  });
6504
+ branchExists = true;
6020
6505
  } catch (error) {
6021
- const message = error && error.stderr ? String(error.stderr).trim() : '';
6022
- throw new Error(message || 'Failed to create permanent git worktree', { cause: error });
6506
+ if (error?.code !== 1) {
6507
+ const message = error && error.stderr ? String(error.stderr).trim() : '';
6508
+ throw new Error(message || 'Failed to inspect git branches', { cause: error });
6509
+ }
6510
+ }
6511
+
6512
+ if (!fs.existsSync(candidateTarget) && !branchExists) {
6513
+ target = candidateTarget;
6514
+ branch = candidateBranch;
6515
+ break;
6023
6516
  }
6024
- return { workspace: target, branch, sourceWorkspace: root };
6517
+ suffix += 1;
6025
6518
  }
6026
- suffix += 1;
6519
+ if (!target || !branch) throw new Error('Unable to allocate a permanent worktree name');
6520
+ const admission = this.persistentProjectOperation(requestId, 'create-worktree', signature, {
6521
+ sourceWorkspace: root,
6522
+ workspace: target,
6523
+ branch,
6524
+ });
6525
+ if (admission.error) throw new Error(admission.error);
6526
+ operation = admission.operation;
6527
+ }
6528
+
6529
+ const commitSuccess = () => {
6530
+ const result = { workspace: target, branch, sourceWorkspace: root, ...(requestId ? { requestId } : {}) };
6531
+ if (!operation) return result;
6532
+ const committed = this.commitPersistentProjectOperation(
6533
+ operation,
6534
+ 'succeeded',
6535
+ result,
6536
+ '',
6537
+ { mountWorkspace: target },
6538
+ );
6539
+ return {
6540
+ ...result,
6541
+ projectWorkspaces: committed.projectWorkspaces,
6542
+ pinnedProjectWorkspaces: committed.pinnedProjectWorkspaces,
6543
+ };
6544
+ };
6545
+
6546
+ if (operation && !['pending', 'unknown'].includes(operation.state)) {
6547
+ throw new Error(operation.error || `Project operation ${requestId} already finished with state ${operation.state}`);
6548
+ }
6549
+ if (operation && operationWasExisting) {
6550
+ const postcondition = await this.inspectGitWorktreePostcondition(root, target, branch);
6551
+ if (
6552
+ postcondition.proven
6553
+ && postcondition.exists
6554
+ && postcondition.registered
6555
+ && postcondition.branchMatches
6556
+ ) {
6557
+ return commitSuccess();
6558
+ }
6559
+ if (operation.state === 'unknown') {
6560
+ throw new Error(operation.error || 'Permanent worktree creation has an uncertain outcome and will not be replayed automatically');
6561
+ }
6562
+ if (
6563
+ !postcondition.proven
6564
+ || postcondition.exists
6565
+ || postcondition.registered
6566
+ || postcondition.branchExists
6567
+ ) {
6568
+ const detail = postcondition.error || 'Permanent worktree creation has a partial or unverifiable result';
6569
+ this.commitPersistentProjectOperation(operation, 'unknown', null, detail);
6570
+ throw new Error(`${detail}; the operation will not be replayed automatically`);
6571
+ }
6572
+ }
6573
+
6574
+ try {
6575
+ await execFileAsync('git', ['-C', root, 'worktree', 'add', '-b', branch, target, 'HEAD'], {
6576
+ timeout: 60000,
6577
+ maxBuffer: 1024 * 1024 * 4,
6578
+ });
6579
+ } catch (error) {
6580
+ const postcondition = await this.inspectGitWorktreePostcondition(root, target, branch);
6581
+ if (
6582
+ postcondition.proven
6583
+ && postcondition.exists
6584
+ && postcondition.registered
6585
+ && postcondition.branchMatches
6586
+ ) {
6587
+ return commitSuccess();
6588
+ }
6589
+ const message = error && error.stderr ? String(error.stderr).trim() : '';
6590
+ const detail = message || 'Failed to create permanent git worktree';
6591
+ if (operation) {
6592
+ const state = postcondition.proven
6593
+ && !postcondition.exists
6594
+ && !postcondition.registered
6595
+ && !postcondition.branchExists
6596
+ ? 'failed'
6597
+ : 'unknown';
6598
+ this.commitPersistentProjectOperation(operation, state, null, detail);
6599
+ }
6600
+ throw new Error(detail, { cause: error });
6027
6601
  }
6028
6602
 
6029
- throw new Error('Unable to allocate a permanent worktree name');
6603
+ const postcondition = await this.inspectGitWorktreePostcondition(root, target, branch);
6604
+ if (
6605
+ !postcondition.proven
6606
+ || !postcondition.exists
6607
+ || !postcondition.registered
6608
+ || !postcondition.branchMatches
6609
+ ) {
6610
+ const detail = postcondition.error || 'Permanent worktree creation could not be proven';
6611
+ if (operation) this.commitPersistentProjectOperation(operation, 'unknown', null, detail);
6612
+ throw new Error(`${detail}; the operation will not be replayed automatically`);
6613
+ }
6614
+ return commitSuccess();
6030
6615
  }
6031
6616
 
6032
6617
  async rollbackPermanentWorktree(created) {
@@ -6093,6 +6678,8 @@ class AgentManager extends EventEmitter {
6093
6678
  }
6094
6679
 
6095
6680
  try {
6681
+ const worktrees = await this.listGitWorktrees(resolvedWorkspace);
6682
+ const sourceWorkspace = worktrees[0]?.workspace || resolvedWorkspace;
6096
6683
  const { stdout } = await execFileAsync('git', ['-C', resolvedWorkspace, 'status', '--porcelain', '--untracked-files=all'], {
6097
6684
  timeout: 30000,
6098
6685
  maxBuffer: 1024 * 1024 * 4,
@@ -6100,6 +6687,7 @@ class AgentManager extends EventEmitter {
6100
6687
  const dirtyEntries = statusEntriesFromPorcelain(stdout);
6101
6688
  return {
6102
6689
  workspace: resolvedWorkspace,
6690
+ sourceWorkspace,
6103
6691
  dirtyEntries,
6104
6692
  requiresForce: dirtyEntries.length > 0,
6105
6693
  };
@@ -6151,21 +6739,118 @@ class AgentManager extends EventEmitter {
6151
6739
  const workspaceKey = canonicalWorkspacePath(this.expandWorkspacePath(workspace));
6152
6740
  if (!workspaceKey) return this.deleteForkWorktreeProjectAdmitted(workspace, options);
6153
6741
  const inFlight = this.projectWorkspaceDeleteAdmissions.get(workspaceKey);
6154
- if (inFlight) return inFlight;
6742
+ const requestId = String(options.requestId || '').trim();
6743
+ if (inFlight) {
6744
+ if (requestId && inFlight.requestId === requestId) return inFlight.promise;
6745
+ return inFlight.promise
6746
+ .catch(() => {})
6747
+ .then(() => this.deleteForkWorktreeProject(workspace, options));
6748
+ }
6155
6749
 
6156
- const operation = this.deleteForkWorktreeProjectAdmitted(workspace, options);
6157
- this.projectWorkspaceDeleteAdmissions.set(workspaceKey, operation);
6158
- void operation.finally(() => {
6159
- if (this.projectWorkspaceDeleteAdmissions.get(workspaceKey) === operation) {
6750
+ const promise = this.deleteForkWorktreeProjectAdmitted(workspace, options);
6751
+ const admission = { requestId, promise };
6752
+ this.projectWorkspaceDeleteAdmissions.set(workspaceKey, admission);
6753
+ void promise.finally(() => {
6754
+ if (this.projectWorkspaceDeleteAdmissions.get(workspaceKey) === admission) {
6160
6755
  this.projectWorkspaceDeleteAdmissions.delete(workspaceKey);
6161
6756
  }
6162
6757
  }).catch(() => {});
6163
- return operation;
6758
+ return promise;
6164
6759
  }
6165
6760
 
6166
6761
  async deleteForkWorktreeProjectAdmitted(workspace, options = {}) {
6167
6762
  await this.whenRecovered();
6168
6763
  const workspaceKey = canonicalWorkspacePath(this.expandWorkspacePath(workspace));
6764
+ const requestId = String(options.requestId || '').trim().slice(0, 160);
6765
+ const signature = projectOperationSignature({
6766
+ force: options.force === true,
6767
+ type: 'delete-worktree',
6768
+ workspace: workspaceKey,
6769
+ });
6770
+ const existingOperation = requestId && typeof this.configManager?.getProjectOperation === 'function'
6771
+ ? this.configManager.getProjectOperation(requestId)
6772
+ : null;
6773
+ if (
6774
+ existingOperation
6775
+ && (
6776
+ existingOperation.type !== 'delete-worktree'
6777
+ || existingOperation.signature !== signature
6778
+ )
6779
+ ) {
6780
+ return { workspace: workspaceKey, error: `Project operation request ${requestId} was already used for different parameters` };
6781
+ }
6782
+ if (existingOperation?.state === 'succeeded' && existingOperation.result) {
6783
+ const settings = this.configManager.getSettings?.() || {};
6784
+ return {
6785
+ ...existingOperation.result,
6786
+ deduplicated: true,
6787
+ projectWorkspaces: settings.projectWorkspaces || [],
6788
+ pinnedProjectWorkspaces: settings.pinnedProjectWorkspaces || [],
6789
+ };
6790
+ }
6791
+ let operation = existingOperation;
6792
+ const storedSourceWorkspace = String(operation?.request?.sourceWorkspace || '');
6793
+ const commitDeleted = (baseResult) => {
6794
+ const result = { ...baseResult, ...(requestId ? { requestId } : {}) };
6795
+ if (!operation) return result;
6796
+ const committed = this.commitPersistentProjectOperation(
6797
+ operation,
6798
+ 'succeeded',
6799
+ result,
6800
+ '',
6801
+ { removeWorkspace: workspaceKey },
6802
+ );
6803
+ return {
6804
+ ...result,
6805
+ projectWorkspaces: committed.projectWorkspaces,
6806
+ pinnedProjectWorkspaces: committed.pinnedProjectWorkspaces,
6807
+ };
6808
+ };
6809
+ if (operation && ['pending', 'unknown'].includes(operation.state) && storedSourceWorkspace) {
6810
+ const postcondition = await this.inspectGitWorktreePostcondition(
6811
+ storedSourceWorkspace,
6812
+ operation.request.workspace || workspaceKey,
6813
+ );
6814
+ if (postcondition.proven && !postcondition.exists && !postcondition.registered) {
6815
+ try {
6816
+ return commitDeleted({
6817
+ workspace: workspaceKey,
6818
+ deleted: true,
6819
+ forced: operation.request.force === true,
6820
+ archivedAgentIds: [],
6821
+ removedMainPageSessionKeys: [],
6822
+ });
6823
+ } catch (error) {
6824
+ return {
6825
+ workspace: workspaceKey,
6826
+ deleted: true,
6827
+ retryable: true,
6828
+ error: `Worktree was deleted, but Project operation commit failed: ${error.message || error}`,
6829
+ };
6830
+ }
6831
+ }
6832
+ if (operation.state === 'unknown') {
6833
+ return {
6834
+ workspace: workspaceKey,
6835
+ error: operation.error || 'Worktree deletion has an uncertain outcome and will not be replayed automatically',
6836
+ uncertain: true,
6837
+ };
6838
+ }
6839
+ if (!postcondition.proven || postcondition.exists !== postcondition.registered) {
6840
+ const detail = postcondition.error || 'Worktree deletion has a partial or unverifiable result';
6841
+ try {
6842
+ this.commitPersistentProjectOperation(operation, 'unknown', null, detail);
6843
+ } catch {
6844
+ // The previously persisted pending intent still prevents blind replay.
6845
+ }
6846
+ return { workspace: workspaceKey, error: `${detail}; the operation will not be replayed automatically`, uncertain: true };
6847
+ }
6848
+ } else if (operation && !['pending', 'unknown'].includes(operation.state)) {
6849
+ return {
6850
+ workspace: workspaceKey,
6851
+ error: operation.error || `Project operation ${requestId} already finished with state ${operation.state}`,
6852
+ };
6853
+ }
6169
6854
  const relatedStarts = workspaceKey
6170
6855
  ? [...this.agentStartAdmissions.values()].filter(admission => (
6171
6856
  !admission.workspaceKey
@@ -6193,6 +6878,16 @@ class AgentManager extends EventEmitter {
6193
6878
  };
6194
6879
  }
6195
6880
 
6881
+ if (!operation) {
6882
+ const admission = this.persistentProjectOperation(requestId, 'delete-worktree', signature, {
6883
+ workspace: inspected.workspace,
6884
+ sourceWorkspace: inspected.sourceWorkspace,
6885
+ force: options.force === true,
6886
+ });
6887
+ if (admission.error) return { ...inspected, error: admission.error };
6888
+ operation = admission.operation;
6889
+ }
6890
+
6196
6891
  const archivedAgentIds = [];
6197
6892
  const removedMainPageSessionKeys = [];
6198
6893
  const projectAgents = this.agentsForProjectWorkspace(inspected.workspace);
@@ -6220,25 +6915,227 @@ class AgentManager extends EventEmitter {
6220
6915
  maxBuffer: 1024 * 1024 * 4,
6221
6916
  });
6222
6917
  } catch (error) {
6918
+ const postcondition = await this.inspectGitWorktreePostcondition(
6919
+ inspected.sourceWorkspace,
6920
+ inspected.workspace,
6921
+ );
6922
+ if (postcondition.proven && !postcondition.exists && !postcondition.registered) {
6923
+ try {
6924
+ return commitDeleted({
6925
+ workspace: inspected.workspace,
6926
+ deleted: true,
6927
+ forced: options.force === true,
6928
+ archivedAgentIds,
6929
+ removedMainPageSessionKeys: Array.from(new Set(removedMainPageSessionKeys)),
6930
+ });
6931
+ } catch (commitError) {
6932
+ return {
6933
+ ...inspected,
6934
+ deleted: true,
6935
+ retryable: true,
6936
+ error: `Worktree was deleted, but Project operation commit failed: ${commitError.message || commitError}`,
6937
+ archivedAgentIds,
6938
+ removedMainPageSessionKeys: Array.from(new Set(removedMainPageSessionKeys)),
6939
+ };
6940
+ }
6941
+ }
6223
6942
  const message = error && error.stderr ? String(error.stderr).trim() : '';
6943
+ const detail = message || 'Failed to delete git worktree';
6944
+ if (operation) {
6945
+ const state = postcondition.proven && postcondition.exists && postcondition.registered
6946
+ ? 'failed'
6947
+ : 'unknown';
6948
+ try {
6949
+ this.commitPersistentProjectOperation(operation, state, null, detail);
6950
+ } catch {
6951
+ // Preserve the earlier durable pending intent when the result write fails.
6952
+ }
6953
+ }
6224
6954
  return {
6225
6955
  ...inspected,
6226
- error: message || 'Failed to delete git worktree',
6956
+ error: detail,
6957
+ ...(postcondition.proven && postcondition.exists && postcondition.registered
6958
+ ? {}
6959
+ : { uncertain: true }),
6227
6960
  archivedAgentIds,
6228
6961
  removedMainPageSessionKeys: Array.from(new Set(removedMainPageSessionKeys)),
6229
6962
  };
6230
6963
  }
6231
6964
 
6232
- return {
6233
- workspace: inspected.workspace,
6234
- deleted: true,
6235
- forced: options.force === true,
6236
- archivedAgentIds,
6237
- removedMainPageSessionKeys: Array.from(new Set(removedMainPageSessionKeys)),
6238
- };
6965
+ const postcondition = await this.inspectGitWorktreePostcondition(
6966
+ inspected.sourceWorkspace,
6967
+ inspected.workspace,
6968
+ );
6969
+ if (!postcondition.proven || postcondition.exists || postcondition.registered) {
6970
+ const detail = postcondition.error || 'Worktree deletion could not be proven';
6971
+ if (operation) {
6972
+ try {
6973
+ this.commitPersistentProjectOperation(operation, 'unknown', null, detail);
6974
+ } catch {
6975
+ // Preserve the earlier durable pending intent when the result write fails.
6976
+ }
6977
+ }
6978
+ return {
6979
+ ...inspected,
6980
+ error: `${detail}; the operation will not be replayed automatically`,
6981
+ uncertain: true,
6982
+ archivedAgentIds,
6983
+ removedMainPageSessionKeys: Array.from(new Set(removedMainPageSessionKeys)),
6984
+ };
6985
+ }
6986
+ try {
6987
+ return commitDeleted({
6988
+ workspace: inspected.workspace,
6989
+ deleted: true,
6990
+ forced: options.force === true,
6991
+ archivedAgentIds,
6992
+ removedMainPageSessionKeys: Array.from(new Set(removedMainPageSessionKeys)),
6993
+ });
6994
+ } catch (error) {
6995
+ return {
6996
+ ...inspected,
6997
+ deleted: true,
6998
+ retryable: true,
6999
+ error: `Worktree was deleted, but Project operation commit failed: ${error.message || error}`,
7000
+ archivedAgentIds,
7001
+ removedMainPageSessionKeys: Array.from(new Set(removedMainPageSessionKeys)),
7002
+ };
7003
+ }
7004
+ }
7005
+
7006
+ async replayPersistentForkRequest(agent, requestId, signature) {
7007
+ const requestKey = `fork-request:${requestId}`;
7008
+ const operation = lifecycleJournal(agent).entries.find(candidate => (
7009
+ candidate.type === 'fork' && candidate.requestKey === requestKey
7010
+ ));
7011
+ if (!operation) return null;
7012
+ if (operation.request?.signature && operation.request.signature !== signature) {
7013
+ return { error: `Fork request ${requestId} was already used for different parameters` };
7014
+ }
7015
+ if (operation.state === 'succeeded' && operation.result) {
7016
+ return { ...operation.result, deduplicated: true };
7017
+ }
7018
+ if (TERMINAL_OPERATION_STATES.has(operation.state)) {
7019
+ return { error: operation.error || `Fork request ${requestId} finished with state ${operation.state}` };
7020
+ }
7021
+
7022
+ const children = typeof this.configManager?.listAgentSessionRecords === 'function'
7023
+ ? this.configManager.listAgentSessionRecords().filter(record => (
7024
+ record?.parentAgentId === agent.id
7025
+ && record?.forkRequestId === requestId
7026
+ && record?.archived !== true
7027
+ ))
7028
+ : [];
7029
+ if (children.length === 1) {
7030
+ const child = children[0];
7031
+ const request = operation.request || {};
7032
+ const result = {
7033
+ agentId: child.runtimeAgentId,
7034
+ workspace: child.projectWorkspace || child.cwd || '',
7035
+ mode: request.mode || 'same-worktree',
7036
+ ...(request.targetRuntime ? { targetRuntime: request.targetRuntime } : {}),
7037
+ ...(child.providerSessionId ? { providerSessionId: child.providerSessionId } : {}),
7038
+ requestId,
7039
+ };
7040
+ try {
7041
+ this.completePersistentAgentOperation(agent, operation.id, result);
7042
+ return { ...result, deduplicated: true, reconciled: true };
7043
+ } catch (error) {
7044
+ return {
7045
+ ...result,
7046
+ error: `Fork exists, but its result could not be committed: ${error.message || error}`,
7047
+ retryable: true,
7048
+ };
7049
+ }
7050
+ }
7051
+
7052
+ const detail = children.length > 1
7053
+ ? `Fork request ${requestId} has multiple child Agent records and cannot be reconciled safely`
7054
+ : `Fork request ${requestId} has an uncertain outcome and will not be replayed automatically`;
7055
+ if (operation.state !== 'blocked') {
7056
+ try {
7057
+ this.transitionPersistentAgentOperation(agent, operation.id, 'blocked', detail);
7058
+ } catch (error) {
7059
+ return { error: `${detail}; failed to persist blocked state: ${error.message || error}`, uncertain: true };
7060
+ }
7061
+ }
7062
+ return { error: detail, uncertain: true };
6239
7063
  }
6240
7064
 
6241
7065
  async forkAgent(agentId, mode = 'same-worktree', options = {}) {
7066
+ const requestId = String(options.requestId || '').trim().slice(0, 160);
7067
+ if (!requestId) return this.forkAgentUntracked(agentId, mode, options);
7068
+ if (!/^[A-Za-z0-9._:-]{1,160}$/.test(requestId)) {
7069
+ return { error: 'Fork requires a valid requestId' };
7070
+ }
7071
+ await this.whenRecovered();
7072
+ const agent = this.agents.get(agentId);
7073
+ if (!agent) return { error: 'Agent not found' };
7074
+ const signature = projectOperationSignature({
7075
+ agentRecordId: agent.agentRecordId || agent.persistentSessionId || '',
7076
+ expectedRevision: Number.isSafeInteger(options.expectedRevision) ? options.expectedRevision : null,
7077
+ mode,
7078
+ targetRuntime: options.targetRuntime || '',
7079
+ });
7080
+ const key = `fork:${requestId}:${signature}`;
7081
+ const inFlight = this.agentLifecycleOperations.get(agentId);
7082
+ if (inFlight?.key === key) return inFlight.promise;
7083
+ const replay = await this.replayPersistentForkRequest(agent, requestId, signature);
7084
+ if (replay) return replay;
7085
+ return this.runAgentLifecycleOperation(
7086
+ agentId,
7087
+ key,
7088
+ 'fork',
7089
+ 'fork',
7090
+ async lifecycleToken => {
7091
+ const admission = this.beginPersistentAgentOperation(
7092
+ agent,
7093
+ 'fork',
7094
+ `fork-request:${requestId}`,
7095
+ {
7096
+ signature,
7097
+ mode,
7098
+ targetRuntime: options.targetRuntime || '',
7099
+ expectedRevision: Number.isSafeInteger(options.expectedRevision) ? options.expectedRevision : null,
7100
+ },
7101
+ );
7102
+ if (admission.error) return { error: admission.error };
7103
+ const result = await this.forkAgentUntracked(agentId, mode, {
7104
+ ...options,
7105
+ requestId: '',
7106
+ forkRequestId: requestId,
7107
+ lifecycleToken,
7108
+ });
7109
+ if (result?.error) {
7110
+ try {
7111
+ this.transitionPersistentAgentOperation(
7112
+ agent,
7113
+ admission.operation.id,
7114
+ 'blocked',
7115
+ result.error,
7116
+ );
7117
+ } catch (error) {
7118
+ return { ...result, error: `${result.error}; failed to persist Fork outcome: ${error.message || error}` };
7119
+ }
7120
+ return { ...result, requestId, uncertain: true };
7121
+ }
7122
+ const committedResult = { ...result, requestId };
7123
+ try {
7124
+ this.completePersistentAgentOperation(agent, admission.operation.id, committedResult);
7125
+ return committedResult;
7126
+ } catch (error) {
7127
+ return {
7128
+ ...committedResult,
7129
+ error: `Fork was created, but its result could not be committed: ${error.message || error}`,
7130
+ retainedAgentId: result.agentId,
7131
+ retryable: true,
7132
+ };
7133
+ }
7134
+ },
7135
+ );
7136
+ }
7137
+
7138
+ async forkAgentUntracked(agentId, mode = 'same-worktree', options = {}) {
6242
7139
  await this.whenRecovered();
6243
7140
  if (this.disposing) {
6244
7141
  return { error: 'Farming is shutting down; Agent lifecycle changes are not accepted' };
@@ -6258,12 +7155,20 @@ class AgentManager extends EventEmitter {
6258
7155
  if (!Number.isSafeInteger(expectedRevision) || expectedRevision < 0) {
6259
7156
  return { error: 'Conversation Fork requires an exact transcript revision' };
6260
7157
  }
7158
+ if (options.lifecycleToken) {
7159
+ return this.performAcpConversationFork(
7160
+ agentId,
7161
+ expectedRevision,
7162
+ options.lifecycleToken,
7163
+ options.forkRequestId || '',
7164
+ );
7165
+ }
6261
7166
  return this.runAgentLifecycleOperation(
6262
7167
  agentId,
6263
7168
  `conversation-fork:${expectedRevision}`,
6264
7169
  'conversation-fork',
6265
7170
  'conversation fork',
6266
- lifecycleToken => this.performAcpConversationFork(agentId, expectedRevision, lifecycleToken),
7171
+ lifecycleToken => this.performAcpConversationFork(agentId, expectedRevision, lifecycleToken, ''),
6267
7172
  );
6268
7173
  }
6269
7174
  if (agent.providerSessionProvider === 'codex' && agent.providerSessionTemporary === true) {
@@ -6319,6 +7224,7 @@ class AgentManager extends EventEmitter {
6319
7224
  }, {
6320
7225
  wantsMain: false,
6321
7226
  parentAgentId: agent.id,
7227
+ forkRequestId: options.forkRequestId || '',
6322
7228
  task: agent.task ? `Fork: ${agent.task}` : `Fork of ${agent.command}`,
6323
7229
  workflowTemplate: agent.workflowTemplate || '',
6324
7230
  source: mode === 'new-worktree' ? 'ui-fork-new-worktree' : 'ui-fork-same-worktree',
@@ -6331,7 +7237,7 @@ class AgentManager extends EventEmitter {
6331
7237
  });
6332
7238
  }
6333
7239
 
6334
- async performAcpConversationFork(agentId, expectedRevision, lifecycleToken) {
7240
+ async performAcpConversationFork(agentId, expectedRevision, lifecycleToken, forkRequestId = '') {
6335
7241
  const agent = this.agents.get(agentId);
6336
7242
  if (!agent) return { error: 'Agent not found' };
6337
7243
  if (runtimeKind(agent) !== 'acp') {
@@ -6360,6 +7266,7 @@ class AgentManager extends EventEmitter {
6360
7266
  expectedRevision,
6361
7267
  lifecycleToken,
6362
7268
  acpSessionOptions,
7269
+ forkRequestId,
6363
7270
  });
6364
7271
  }
6365
7272
 
@@ -6423,6 +7330,7 @@ class AgentManager extends EventEmitter {
6423
7330
  const started = this.startAgent(command, workspace, finish, {
6424
7331
  wantsMain: false,
6425
7332
  parentAgentId: agent.id,
7333
+ forkRequestId,
6426
7334
  task: agent.task ? `Fork: ${agent.task}` : `Fork of ${agent.command}`,
6427
7335
  workflowTemplate: agent.workflowTemplate || '',
6428
7336
  source: 'ui-fork-acp-chat',
@@ -6467,6 +7375,7 @@ class AgentManager extends EventEmitter {
6467
7375
  expectedRevision,
6468
7376
  lifecycleToken,
6469
7377
  acpSessionOptions,
7378
+ forkRequestId,
6470
7379
  } = options;
6471
7380
  const command = getProviderAdapter(provider)?.executable || provider;
6472
7381
  let preparedSessionId = '';
@@ -6507,6 +7416,7 @@ class AgentManager extends EventEmitter {
6507
7416
  const started = this.startAgent(command, workspace, finish, {
6508
7417
  wantsMain: false,
6509
7418
  parentAgentId: agent.id,
7419
+ forkRequestId,
6510
7420
  task: agent.task ? `Fork: ${agent.task}` : `Fork of ${agent.command}`,
6511
7421
  workflowTemplate: agent.workflowTemplate || '',
6512
7422
  source: 'ui-fork-acp-chat',
@@ -6732,7 +7642,7 @@ class AgentManager extends EventEmitter {
6732
7642
  }
6733
7643
  let removedMainPageSessionKeys = [];
6734
7644
  try {
6735
- this.transitionPersistentAgentOperation(agent, operationId, 'succeeded', '', {
7645
+ this.transitionPersistentAgentOperation(agent, operationId, 'provider-archive-pending', '', {
6736
7646
  visibleOnMainPage: false,
6737
7647
  archived: true,
6738
7648
  archivedAt: Date.now(),
@@ -6750,6 +7660,8 @@ class AgentManager extends EventEmitter {
6750
7660
  removedMainPageSessionKeys,
6751
7661
  };
6752
7662
  }
7663
+ agent.archived = true;
7664
+ agent.archivedAt = Date.now();
6753
7665
  let metadataWarning = '';
6754
7666
  try {
6755
7667
  removedMainPageSessionKeys = this.removeMainPageProviderSessionsForAgents([agent]);
@@ -6759,7 +7671,7 @@ class AgentManager extends EventEmitter {
6759
7671
  }
6760
7672
 
6761
7673
  let historyWarning = '';
6762
- if (options.recordHistory !== false && !isEphemeralShellAgent(agent)) {
7674
+ if (!admission.joined && options.recordHistory !== false && !isEphemeralShellAgent(agent)) {
6763
7675
  const previousTaskHistory = this.taskHistory;
6764
7676
  try {
6765
7677
  this.recordTaskHistory(agent, {
@@ -6772,8 +7684,59 @@ class AgentManager extends EventEmitter {
6772
7684
  console.error(historyWarning);
6773
7685
  }
6774
7686
  }
7687
+ if (options.scheduleProviderArchive !== false) {
7688
+ const providerArchive = await this.archiveCodexProviderSession(agent);
7689
+ if (providerArchive?.error) {
7690
+ try {
7691
+ this.transitionPersistentAgentOperation(agent, operationId, 'blocked', providerArchive.error, {
7692
+ visibleOnMainPage: false,
7693
+ archived: true,
7694
+ runtimeAgentId: '',
7695
+ });
7696
+ } catch (error) {
7697
+ return {
7698
+ agentId,
7699
+ archived: true,
7700
+ stopped: true,
7701
+ retryable: true,
7702
+ operationId,
7703
+ removedMainPageSessionKeys,
7704
+ error: `Provider archive failed: ${providerArchive.error}; failed to persist blocked Archive: ${error.message || error}`,
7705
+ };
7706
+ }
7707
+ this.emit('update');
7708
+ return {
7709
+ agentId,
7710
+ archived: true,
7711
+ stopped: true,
7712
+ providerArchived: false,
7713
+ retryable: true,
7714
+ operationId,
7715
+ removedMainPageSessionKeys,
7716
+ error: `Provider archive failed: ${providerArchive.error}`,
7717
+ };
7718
+ }
7719
+ }
7720
+ try {
7721
+ this.transitionPersistentAgentOperation(agent, operationId, 'succeeded', '', {
7722
+ visibleOnMainPage: false,
7723
+ archived: true,
7724
+ runtimeAgentId: '',
7725
+ });
7726
+ } catch (error) {
7727
+ this.emit('update');
7728
+ return {
7729
+ agentId,
7730
+ archived: true,
7731
+ stopped: true,
7732
+ providerArchived: true,
7733
+ retryable: true,
7734
+ operationId,
7735
+ removedMainPageSessionKeys,
7736
+ error: `Provider archive succeeded, but terminal Archive result could not be saved: ${error.message || error}`,
7737
+ };
7738
+ }
6775
7739
  this.forgetStoppedAgentRecord(agentId);
6776
- if (options.scheduleProviderArchive !== false) this.scheduleCodexSessionArchive(agent);
6777
7740
  return {
6778
7741
  agentId,
6779
7742
  archived: true,
@@ -6788,14 +7751,14 @@ class AgentManager extends EventEmitter {
6788
7751
  };
6789
7752
  }
6790
7753
 
6791
- scheduleCodexSessionArchive(agent) {
7754
+ async archiveCodexProviderSession(agent) {
6792
7755
  if (
6793
7756
  !agent
6794
7757
  || agent.providerSessionProvider !== 'codex'
6795
7758
  || !agent.providerSessionId
6796
7759
  || agent.providerSessionTemporary === true
6797
7760
  ) {
6798
- return;
7761
+ return null;
6799
7762
  }
6800
7763
 
6801
7764
  const sessionId = agent.providerSessionId;
@@ -6805,26 +7768,23 @@ class AgentManager extends EventEmitter {
6805
7768
  workspace: agent.projectWorkspace || '',
6806
7769
  providerHomePath: agent.providerHomePath || '',
6807
7770
  };
6808
- void this.enqueueCodexSessionMutation(
6809
- sessionId,
6810
- {
6811
- providerHomeId: agent.providerHomeId || 'default',
6812
- providerHomePath: agent.providerHomePath || '',
6813
- },
6814
- 'archive',
6815
- () => this.archiveCodexSession(sessionId, session),
6816
- true,
6817
- )
6818
- .then(result => {
6819
- if (result?.error) {
6820
- console.error(`Failed to archive Codex session ${sessionId}: ${result.error}`);
6821
- }
6822
- })
6823
- .catch(error => {
6824
- console.error(`Failed to archive Codex session ${sessionId}:`, error);
6825
- });
7771
+ try {
7772
+ const result = await this.enqueueCodexSessionMutation(
7773
+ sessionId,
7774
+ {
7775
+ providerHomeId: agent.providerHomeId || 'default',
7776
+ providerHomePath: agent.providerHomePath || '',
7777
+ },
7778
+ 'archive',
7779
+ () => this.archiveCodexSession(sessionId, session),
7780
+ true,
7781
+ );
7782
+ return result?.error ? { error: result.error } : { archived: true };
7783
+ } catch (error) {
7784
+ return { error: error.message || String(error) };
7785
+ }
6826
7786
  }
6827
-
7787
+
6828
7788
  admitPersistentDelete(agent, options = {}) {
6829
7789
  const activeOperation = activeLifecycleOperation(agent);
6830
7790
  if (