farming-code 2.2.27 → 2.2.29

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 (284) hide show
  1. package/README.md +15 -30
  2. package/README.zh_cn.md +15 -30
  3. package/THIRD_PARTY_NOTICES.md +28 -6
  4. package/backend/acp/packaged-claude-acp.js +13 -0
  5. package/backend/acp/packaged-codex-acp.js +2 -2
  6. package/backend/acp-runtime.js +52 -3
  7. package/backend/acp-transcript.js +174 -9
  8. package/backend/agent-manager.js +1081 -96
  9. package/backend/atomic-json-store.js +37 -0
  10. package/backend/cli-agents.js +9 -7
  11. package/backend/config-manager.js +211 -58
  12. package/backend/control-api.js +2 -1
  13. package/backend/data/LICENSE.agent-browser +201 -0
  14. package/backend/data/runtime-dependency-manifest.json +199 -0
  15. package/backend/data/runtime-dependency-sources.json +5 -0
  16. package/backend/executable-discovery.js +7 -0
  17. package/backend/farming-agent-bootstrap.js +49 -0
  18. package/backend/farming-agent-bootstrap.zh_cn.md +7 -0
  19. package/backend/farming-app-cli.js +168 -65
  20. package/backend/farming-cli.js +57 -0
  21. package/backend/farming-session-store.js +92 -89
  22. package/backend/index-html.js +13 -0
  23. package/backend/main-agent-skills.js +13 -0
  24. package/backend/npm-update-helper.js +43 -6
  25. package/backend/preview-session-manager.js +68 -0
  26. package/backend/provider-adapters.js +54 -5
  27. package/backend/review-session-store.js +32 -25
  28. package/backend/review-state-store.js +13 -14
  29. package/backend/run-history-store.js +13 -16
  30. package/backend/runtime-dependency-manager.js +759 -0
  31. package/backend/runtime-executable-invocation.js +20 -0
  32. package/backend/server-process-identity.js +47 -0
  33. package/backend/server.js +231 -223
  34. package/backend/slash-command-discovery.js +138 -1
  35. package/backend/storage-layout.js +35 -0
  36. package/backend/update-service.js +27 -1
  37. package/backend/workspace-file-router.js +141 -0
  38. package/backend/workspace-file-service.js +19 -0
  39. package/backend/workspace-root-registry.js +6 -4
  40. package/config/farming.install.env.example +4 -0
  41. package/dist/acp/LICENSE.claude-agent-acp +191 -0
  42. package/dist/acp/LICENSE.claude-agent-sdk +1 -0
  43. package/dist/acp/claude-agent-acp-0.59.0.mjs +41606 -0
  44. package/dist/assets/App-BKNUILuL.css +1 -0
  45. package/dist/assets/App-Ex2oVVXO.js +693 -0
  46. package/dist/assets/{FileEditorMarkdownPreview-Da5jguUs.js → FileEditorMarkdownPreview-Cs9cLv8a.js} +5 -5
  47. package/dist/assets/FileEditorPane-CNtuGjQ3.js +2 -0
  48. package/dist/assets/IconGlyphs-BClgag6E.js +1 -0
  49. package/dist/assets/ProjectFilesSection-Cf2BEcAN.js +12 -0
  50. package/dist/assets/ReviewPage-BRuxFaH5.js +3 -0
  51. package/dist/assets/{abnfDiagram-VRR7QNED-nnglrXFi.js → abnfDiagram-VRR7QNED-BY1qwq8A.js} +1 -1
  52. package/dist/assets/api-w2x5QfxB.js +1 -0
  53. package/dist/assets/{arc-CWoVlxJw.js → arc-DgZRAlwj.js} +1 -1
  54. package/dist/assets/{architectureDiagram-ZJ3FMSHR-BEXMaFfY.js → architectureDiagram-ZJ3FMSHR-jFn144od.js} +1 -1
  55. package/dist/assets/{blockDiagram-677ZJIJ3-1mCezSBk.js → blockDiagram-677ZJIJ3-BmRjlV0H.js} +1 -1
  56. package/dist/assets/{c4Diagram-LMCZKHZV-DhTiI6Uv.js → c4Diagram-LMCZKHZV-DUKul6tT.js} +1 -1
  57. package/dist/assets/{workspace-editor-monaco-DLlws5Ig.js → channel-CI9j3QFD.js} +1 -1
  58. package/dist/assets/chunk-2Q5K7J3B-87UnWaoh.js +1 -0
  59. package/dist/assets/{chunk-32BRIVSS-CCdsKd73.js → chunk-32BRIVSS-rGVu23o5.js} +1 -1
  60. package/dist/assets/{chunk-52WLFC77-Gncjk4LF.js → chunk-52WLFC77-CvFDKgbB.js} +1 -1
  61. package/dist/assets/{chunk-5VM5RSS4-Dlw7AXWp.js → chunk-5VM5RSS4-CFgnRW1C.js} +1 -1
  62. package/dist/assets/{chunk-7BUUIJ7U-ZBRhjLOh.js → chunk-7BUUIJ7U-Cp7bqW6s.js} +1 -1
  63. package/dist/assets/{chunk-C7G6YPKG-CiDVUP82.js → chunk-C7G6YPKG-BIS1gjZO.js} +1 -1
  64. package/dist/assets/{chunk-EX3LRPZG-z6gXP2_N.js → chunk-EX3LRPZG-BKG-cR9j.js} +1 -1
  65. package/dist/assets/{chunk-FWX5IMBZ-DMZBccQa.js → chunk-FWX5IMBZ-B8PsTr8a.js} +2 -2
  66. package/dist/assets/{chunk-HOUHSVGY-D8tIeoPk.js → chunk-HOUHSVGY-BHFOAiXT.js} +1 -1
  67. package/dist/assets/{chunk-ICXQ74PX-b6oQ2Q17.js → chunk-ICXQ74PX-CvLE1J9X.js} +1 -1
  68. package/dist/assets/{chunk-JWPE2WC7-DSyBRaRG.js → chunk-JWPE2WC7-D8misks7.js} +1 -1
  69. package/dist/assets/{chunk-MOJQB5TN-DAVH_p2a.js → chunk-MOJQB5TN-D-oNgSNH.js} +1 -1
  70. package/dist/assets/{chunk-OGEWGWER-DxoiMCI-.js → chunk-OGEWGWER-C2N-la9e.js} +1 -1
  71. package/dist/assets/{chunk-PUDLZKDR-NsOWh7HM.js → chunk-PUDLZKDR-BwI3Jz8q.js} +1 -1
  72. package/dist/assets/{chunk-Q4XR5HBZ-Di4H7L21.js → chunk-Q4XR5HBZ-BuqGfsXA.js} +1 -1
  73. package/dist/assets/{chunk-RYQCIY6F-CZA9pvFX.js → chunk-RYQCIY6F-Bq4XZcGt.js} +1 -1
  74. package/dist/assets/{chunk-V7JOEXUC-DttpZg9Y.js → chunk-V7JOEXUC-QZLzdTTJ.js} +1 -1
  75. package/dist/assets/chunk-VAUOI2AC-Cb0-kQ9g.js +1 -0
  76. package/dist/assets/{chunk-VR4S4FIN-D-Fmtd4Z.js → chunk-VR4S4FIN-DcZTpML_.js} +1 -1
  77. package/dist/assets/{chunk-WYO6CB5R-CUxMtfK2.js → chunk-WYO6CB5R-DCU8wuny.js} +2 -2
  78. package/dist/assets/chunk-XXDRQBXY-BZAC1Krv.js +1 -0
  79. package/dist/assets/{chunk-ZGVPDNZ5-CoV2QEl0.js → chunk-ZGVPDNZ5-B7r85QbY.js} +1 -1
  80. package/dist/assets/{chunk-ZIRB5QZD-DncwlQQc.js → chunk-ZIRB5QZD-BgEnDWhQ.js} +1 -1
  81. package/dist/assets/classDiagram-OUVF2IWQ-DcvGOuvi.js +1 -0
  82. package/dist/assets/classDiagram-v2-EOCWNBFH-DDYUSofQ.js +1 -0
  83. package/dist/assets/code-dark-BEZTQwYZ.css +1 -0
  84. package/dist/assets/{cose-bilkent-JH36ORCC-BNIAz0nN.js → cose-bilkent-JH36ORCC-CThXS2qG.js} +1 -1
  85. package/dist/assets/{cssMode-DDWxTPm_.js → cssMode-Bdg5RpcJ.js} +1 -1
  86. package/dist/assets/{cynefinDiagram-TSTJHNR4-0xPI92Lg.js → cynefinDiagram-TSTJHNR4-DxBgzw25.js} +1 -1
  87. package/dist/assets/{dagre-DXlcHgr3.js → dagre-BM-GvGUZ.js} +1 -1
  88. package/dist/assets/{dagre-VKFMJZFB-DfuMG4gU.js → dagre-VKFMJZFB-B7UCRmxR.js} +1 -1
  89. package/dist/assets/{diagram-FQU43EPY-CT00bLZ6.js → diagram-FQU43EPY-CeL2DwKx.js} +1 -1
  90. package/dist/assets/{diagram-G47NLZAW-Xbh7zXUx.js → diagram-G47NLZAW-BEirglCC.js} +1 -1
  91. package/dist/assets/{diagram-NH7WQ7WH-SRDVPfn7.js → diagram-NH7WQ7WH-C6wcI-Rl.js} +1 -1
  92. package/dist/assets/{diagram-OA4YK3LP-CQRli4Np.js → diagram-OA4YK3LP-DpM-XXtJ.js} +1 -1
  93. package/dist/assets/{diagram-WEI45ONY-DMf17WQi.js → diagram-WEI45ONY-BQyaut9k.js} +1 -1
  94. package/dist/assets/{ebnfDiagram-CCIWWBDH-DhpFkgJ5.js → ebnfDiagram-CCIWWBDH-UmAXsUhr.js} +1 -1
  95. package/dist/assets/{erDiagram-Q63AITRT-1IwxWmJo.js → erDiagram-Q63AITRT-CdYLEOch.js} +1 -1
  96. package/dist/assets/file-icons-BOlcJ0ZF.js +1 -0
  97. package/dist/assets/flowDiagram-23GEKE2U-CO-z9-tq.js +1 -0
  98. package/dist/assets/{freemarker2-QBsTERH-.js → freemarker2-DfMDJfnE.js} +1 -1
  99. package/dist/assets/{ganttDiagram-NO4QXBWP-BNCdfyr1.js → ganttDiagram-NO4QXBWP-DgSUNdUB.js} +1 -1
  100. package/dist/assets/{gitGraphDiagram-IHSO6WYX-pbyWZuiX.js → gitGraphDiagram-IHSO6WYX-DjwjtRtF.js} +1 -1
  101. package/dist/assets/{handlebars-CuoxPo5A.js → handlebars-BEdPtzMT.js} +1 -1
  102. package/dist/assets/{html-gX5PEu0Q.js → html-B1fPb1QD.js} +1 -1
  103. package/dist/assets/html2canvas-DsczWxBa.js +5 -0
  104. package/dist/assets/{htmlMode-DIWUbUxd.js → htmlMode-CY2ZjHxH.js} +1 -1
  105. package/dist/assets/{index-CtwKYTiC.js → index-CQNaZc3l.js} +2 -2
  106. package/dist/assets/{infoDiagram-FWYZ7A6U-BcTOgDbe.js → infoDiagram-FWYZ7A6U-Bi9076l2.js} +1 -1
  107. package/dist/assets/{ishikawaDiagram-FXEZZL3T-DCdvGKrn.js → ishikawaDiagram-FXEZZL3T-DaMaNZN7.js} +1 -1
  108. package/dist/assets/{javascript-BYKGIUGs.js → javascript-BV2OsWA0.js} +1 -1
  109. package/dist/assets/{journeyDiagram-5HDEW3XC-BZ-1q_IT.js → journeyDiagram-5HDEW3XC-CfDy69EZ.js} +1 -1
  110. package/dist/assets/{jsonMode-AFmQQP6u.js → jsonMode-zd07aeuX.js} +1 -1
  111. package/dist/assets/{kanban-definition-HUTT4EX6-BIgejCjj.js → kanban-definition-HUTT4EX6-XFvZ722B.js} +1 -1
  112. package/dist/assets/{line-CfptFMmX.js → line-BXK9PKLn.js} +1 -1
  113. package/dist/assets/{linear-Dld3_ERu.js → linear-CULvmq2Z.js} +1 -1
  114. package/dist/assets/{liquid-DBIl-f0z.js → liquid-BParI24X.js} +1 -1
  115. package/dist/assets/{lspLanguageFeatures-DK6tqicV.js → lspLanguageFeatures-BzredkoV.js} +1 -1
  116. package/dist/assets/main-DK9Sy7-c.css +1 -0
  117. package/dist/assets/{map-LZWWoeWa.js → map-C9Qw9eWh.js} +1 -1
  118. package/dist/assets/{mdx-DS-bVtZN.js → mdx-vGzUJN98.js} +1 -1
  119. package/dist/assets/{mermaid.core-gSpX30pV.js → mermaid.core-CzazaIKr.js} +3 -3
  120. package/dist/assets/{mindmap-definition-LN4V7U3C-BKBaI4LW.js → mindmap-definition-LN4V7U3C-Uoff7hZ3.js} +1 -1
  121. package/dist/assets/{monaco.contribution-BXz-lfFB.js → monaco.contribution-BmaeBQEc.js} +2 -2
  122. package/dist/assets/{ordinal-BLrOss5K.js → ordinal-r6pIwRdZ.js} +1 -1
  123. package/dist/assets/{pegDiagram-2B236MQR-sBvDY3VX.js → pegDiagram-2B236MQR-CCBiHQjk.js} +1 -1
  124. package/dist/assets/{pieDiagram-ENE6RG2P-DWlWcu08.js → pieDiagram-ENE6RG2P-D61s7l75.js} +1 -1
  125. package/dist/assets/{python-C93q3xXz.js → python-CUwdLgsI.js} +1 -1
  126. package/dist/assets/{quadrantDiagram-ABIIQ3AL-DZutUd3J.js → quadrantDiagram-ABIIQ3AL-D_CWk2DQ.js} +1 -1
  127. package/dist/assets/{railroadDiagram-RFXS5EU6-DQb3lXGM.js → railroadDiagram-RFXS5EU6-CdlCHtxN.js} +1 -1
  128. package/dist/assets/{razor-CBifNGXO.js → razor-BScebUxU.js} +1 -1
  129. package/dist/assets/{requirementDiagram-TGXJPOKE-DqFGXu2e.js → requirementDiagram-TGXJPOKE-BLpHtkoC.js} +1 -1
  130. package/dist/assets/{sankeyDiagram-HTMAVEWB-d7ECahnO.js → sankeyDiagram-HTMAVEWB-CkhYZN2j.js} +1 -1
  131. package/dist/assets/{sequenceDiagram-DBY2YBRQ-DdsbKqct.js → sequenceDiagram-DBY2YBRQ-8bploKJl.js} +1 -1
  132. package/dist/assets/{sizeCapture-X5ZJPWSS-daTB6OcF.js → sizeCapture-X5ZJPWSS-DtSdkS0d.js} +1 -1
  133. package/dist/assets/{src-WBW1QxJt.js → src-6yeq6WBA.js} +1 -1
  134. package/dist/assets/{stateDiagram-2N3HPSRC-P6iSrgwR.js → stateDiagram-2N3HPSRC-DfmVGpfA.js} +1 -1
  135. package/dist/assets/stateDiagram-v2-6OUMAXLB-B2c9CQ-J.js +1 -0
  136. package/dist/assets/{swimlanes-5IMT3BWC-BGnZJx1M.js → swimlanes-5IMT3BWC-DRLk2zRF.js} +2 -2
  137. package/dist/assets/swimlanesDiagram-G3AALYLV-B7_APh_h.js +8 -0
  138. package/dist/assets/{timeline-definition-FHXFAJF6-rA-r3aUR.js → timeline-definition-FHXFAJF6-Cngfy588.js} +1 -1
  139. package/dist/assets/{tsMode-B0oUTcXQ.js → tsMode-DzUAibXK.js} +1 -1
  140. package/dist/assets/{typescript-KJKfYO7K.js → typescript-D9EOeM7N.js} +1 -1
  141. package/dist/assets/{vennDiagram-L72KCM5P-DiVKUjA9.js → vennDiagram-L72KCM5P-DeJn63Lq.js} +1 -1
  142. package/dist/assets/{wardleyDiagram-EHGQE667-Cfxh7UOn.js → wardleyDiagram-EHGQE667-BH1nzy77.js} +1 -1
  143. package/dist/assets/{workers-BSQRIkqa.js → workers-DAiomYZM.js} +1 -1
  144. package/dist/assets/workspace-editor-model-CSGCl21x.js +1 -0
  145. package/dist/assets/{workspace-editor-monaco-BIpqcWf1.js → workspace-editor-monaco-CJNwbpCy.js} +3 -3
  146. package/dist/assets/workspace-editor-monaco-CO9w8YV0.js +1 -0
  147. package/dist/assets/workspace-view-state-BjKPYp0E.js +7 -0
  148. package/dist/assets/{xml-C8FAcN6j.js → xml-wWl1dhkQ.js} +1 -1
  149. package/dist/assets/{xychartDiagram-FW5EYKEG--hmryQeL.js → xychartDiagram-FW5EYKEG-SkO2prCU.js} +1 -1
  150. package/dist/assets/{yaml-BQ4NrRhG.js → yaml-Dc2WDaOZ.js} +1 -1
  151. package/dist/crt-markdown-renderer.js +46 -46
  152. package/dist/index.html +32 -3
  153. package/extensions/browser/backend/agent-browser-runtime.js +1041 -0
  154. package/extensions/browser/backend/agent-capability.js +55 -0
  155. package/extensions/browser/backend/browser-mcp-server.js +621 -0
  156. package/extensions/browser/backend/browser-resource-manager.js +1309 -0
  157. package/extensions/browser/backend/browser-resource-store.js +190 -0
  158. package/extensions/browser/backend/browser-router.js +133 -0
  159. package/extensions/browser/backend/executable-discovery.js +224 -0
  160. package/extensions/browser/backend/farming-browser-client.js +83 -0
  161. package/extensions/browser/backend/index.js +7 -0
  162. package/extensions/browser/bin/farming-browser +471 -0
  163. package/extensions/browser/frontend/BrowserSidebarPortals.tsx +360 -0
  164. package/extensions/browser/frontend/BrowserViewer.tsx +500 -0
  165. package/extensions/browser/frontend/browser-resource-state.ts +60 -0
  166. package/extensions/browser/frontend/browser.css +567 -0
  167. package/extensions/browser/frontend/types.ts +31 -0
  168. package/extensions/browser/frontend/useBrowserResources.ts +158 -0
  169. package/frontend/session-bridge.js +7 -6
  170. package/frontend/skins/crt/app.js +64 -4
  171. package/index.html +31 -2
  172. package/package.json +27 -18
  173. package/shared/backend-connection-status.d.ts +12 -0
  174. package/shared/backend-connection-status.js +35 -4
  175. package/shared/browser-protocol.js +1 -1
  176. package/backend/agent-activity.js +0 -35
  177. package/dist/assets/App-BrP-ENHg.css +0 -1
  178. package/dist/assets/App-Cm8qTjsc.js +0 -210
  179. package/dist/assets/FileEditorPane-fFqHA4wW.js +0 -2
  180. package/dist/assets/IconGlyphs-C2WFcoJS.js +0 -1
  181. package/dist/assets/ProjectFilesSection-Cyy3Vcn8.js +0 -12
  182. package/dist/assets/ReviewPage-L1vh1Ggs.js +0 -3
  183. package/dist/assets/api-D8nyOEbz.js +0 -1
  184. package/dist/assets/channel-BVWNatUB.js +0 -1
  185. package/dist/assets/chunk-2Q5K7J3B-d5WWh8jo.js +0 -1
  186. package/dist/assets/chunk-VAUOI2AC-UHNr_yhX.js +0 -1
  187. package/dist/assets/chunk-XXDRQBXY-BBXwjhUx.js +0 -1
  188. package/dist/assets/classDiagram-OUVF2IWQ-Db0ib5iE.js +0 -1
  189. package/dist/assets/classDiagram-v2-EOCWNBFH-Bkf69XF9.js +0 -1
  190. package/dist/assets/code-dark-BKtvf0Xc.css +0 -1
  191. package/dist/assets/file-icons-BqiH0rDI.js +0 -1
  192. package/dist/assets/flowDiagram-23GEKE2U-DYFo2xmB.js +0 -1
  193. package/dist/assets/main-XMXgyJJt.css +0 -1
  194. package/dist/assets/stateDiagram-v2-6OUMAXLB-DIYSf_qh.js +0 -1
  195. package/dist/assets/swimlanesDiagram-G3AALYLV-BIkrlKrZ.js +0 -8
  196. package/dist/assets/workspace-editor-model-D0Im34S5.js +0 -1
  197. package/dist/assets/workspace-view-state-BiuEbmRr.js +0 -7
  198. /package/dist/acp/{codex-acp-1.1.4.js → codex-acp-1.1.4.mjs} +0 -0
  199. /package/dist/assets/{abap-Cc5D5h5i.js → abap-CtX5nWyX.js} +0 -0
  200. /package/dist/assets/{apex-BKopwLmx.js → apex-DQJRAHwc.js} +0 -0
  201. /package/dist/assets/{array-HeX70jSN.js → array-BTwzYU0x.js} +0 -0
  202. /package/dist/assets/{azcli-DI-C34WA.js → azcli-9SMrOQ4q.js} +0 -0
  203. /package/dist/assets/{bat-Cbp6u42z.js → bat-CBGLUsw1.js} +0 -0
  204. /package/dist/assets/{bicep-4QA1jTCM.js → bicep-pDx8da1v.js} +0 -0
  205. /package/dist/assets/{cameligo-Dda_adPs.js → cameligo-hZV3vQ6_.js} +0 -0
  206. /package/dist/assets/{chunk-Y2CYZVJY-Di0sYzi1.js → chunk-Y2CYZVJY-BYi0W5ea.js} +0 -0
  207. /package/dist/assets/{clojure-CvY8A83L.js → clojure-BhCwU3gq.js} +0 -0
  208. /package/dist/assets/{coffee-4LX2Sv8A.js → coffee-BVB1PsgH.js} +0 -0
  209. /package/dist/assets/{core-B9MwRjDE.js → core-vPkV8P_m.js} +0 -0
  210. /package/dist/assets/{cpp-D4AzSV28.js → cpp-eTjuiNPq.js} +0 -0
  211. /package/dist/assets/{csharp-C4nWN4Yd.js → csharp-1kP-kKZT.js} +0 -0
  212. /package/dist/assets/{csp-CiEaaS5Z.js → csp-C_iQqIes.js} +0 -0
  213. /package/dist/assets/{css-DXxBRsbk.js → css-CxOs7IfP.js} +0 -0
  214. /package/dist/assets/{cypher-DOMzqyFA.js → cypher-Cd-a9Y82.js} +0 -0
  215. /package/dist/assets/{cytoscape.esm-Cl8iC3D7.js → cytoscape.esm-OLojuicg.js} +0 -0
  216. /package/dist/assets/{dart-BWS7MXt_.js → dart-whhXpexn.js} +0 -0
  217. /package/dist/assets/{defaultLocale-GJwWH1Jr.js → defaultLocale-CPimVhfE.js} +0 -0
  218. /package/dist/assets/{dist-ClAL-Tv5.js → dist-CLPYXg1u.js} +0 -0
  219. /package/dist/assets/{dockerfile-Baf4U3xT.js → dockerfile-DibBi8b6.js} +0 -0
  220. /package/dist/assets/{ecl-CT7udlkQ.js → ecl-DlF1KdoW.js} +0 -0
  221. /package/dist/assets/{editor.api2-B9h6iFPK.js → editor.api2-D74ufkbr.js} +0 -0
  222. /package/dist/assets/{elixir-CIbAZw5l.js → elixir-CHQ8uFT3.js} +0 -0
  223. /package/dist/assets/{flow9-Dqiw45WV.js → flow9-8nUnVKk1.js} +0 -0
  224. /package/dist/assets/{fsharp-E8E2L9jU.js → fsharp-DNZNZyEE.js} +0 -0
  225. /package/dist/assets/{go-CouVSN4D.js → go-_kPJjNc_.js} +0 -0
  226. /package/dist/assets/{graphlib-CbSvZcZU.js → graphlib-CBGF9gA9.js} +0 -0
  227. /package/dist/assets/{graphql-a6h6h5-t.js → graphql-4HCNVt5g.js} +0 -0
  228. /package/dist/assets/{hcl-vso3e6N-.js → hcl-C87ymY0o.js} +0 -0
  229. /package/dist/assets/{ini-SwZZkotR.js → ini-BYrrGNTc.js} +0 -0
  230. /package/dist/assets/{init-BPLPMQ3Y.js → init-8_PCa3LO.js} +0 -0
  231. /package/dist/assets/{java-CtRLC4_3.js → java-B211rJbk.js} +0 -0
  232. /package/dist/assets/{julia-CuJWFKQH.js → julia-DVPsRtBm.js} +0 -0
  233. /package/dist/assets/{katex-C8DPyI6s.js → katex-C2iYad0m.js} +0 -0
  234. /package/dist/assets/{kotlin-B_kBhsre.js → kotlin-BYyNerjp.js} +0 -0
  235. /package/dist/assets/{less-CtrHIfz3.js → less-EC9UblGE.js} +0 -0
  236. /package/dist/assets/{lexon-yVt1pJLu.js → lexon-B1yuUdqW.js} +0 -0
  237. /package/dist/assets/{lua-prOwgJ4U.js → lua-Bo6GFzxT.js} +0 -0
  238. /package/dist/assets/{m3-DhrdTQ84.js → m3-C-Y9cBVI.js} +0 -0
  239. /package/dist/assets/{markdown-DoI5HwSM.js → markdown-BHu-wLbe.js} +0 -0
  240. /package/dist/assets/{mermaid-parser.core-BQtepoLd.js → mermaid-parser.core-IxcGtNYG.js} +0 -0
  241. /package/dist/assets/{mips-Dye5jsBU.js → mips-Dkds26rX.js} +0 -0
  242. /package/dist/assets/{msdax-TkNOyIL8.js → msdax-PU4g-8_g.js} +0 -0
  243. /package/dist/assets/{mysql-CEc-RKZF.js → mysql-fPpuGpS7.js} +0 -0
  244. /package/dist/assets/{objective-c-CqqCsQz0.js → objective-c-Wy1e5JUf.js} +0 -0
  245. /package/dist/assets/{pascal-DO_pj8yg.js → pascal-BIXwvrqU.js} +0 -0
  246. /package/dist/assets/{pascaligo-1cbdZdF3.js → pascaligo-DwR0AtL_.js} +0 -0
  247. /package/dist/assets/{path-YdFzr2W6.js → path-BxQuywGl.js} +0 -0
  248. /package/dist/assets/{perl-B_N3PYfC.js → perl-CGHAl8my.js} +0 -0
  249. /package/dist/assets/{pgsql-9iYk1FQ7.js → pgsql-B91M73E2.js} +0 -0
  250. /package/dist/assets/{php-rO2sgoUM.js → php-C70ttrCx.js} +0 -0
  251. /package/dist/assets/{pla-Cbgv60kq.js → pla-BNrXaRh0.js} +0 -0
  252. /package/dist/assets/{postiats-y6xrhy_M.js → postiats-Dv2IAZR9.js} +0 -0
  253. /package/dist/assets/{powerquery-BaD8gWNE.js → powerquery-CkY5Wq7J.js} +0 -0
  254. /package/dist/assets/{powershell-BMhG4JVe.js → powershell-uGIYLU3C.js} +0 -0
  255. /package/dist/assets/{protobuf-CdOP5kN3.js → protobuf-CavfmJu-.js} +0 -0
  256. /package/dist/assets/{pug-Ck60hNF9.js → pug-Drqnyitx.js} +0 -0
  257. /package/dist/assets/{qrcode-DV-_rjU8.js → qrcode-7uGsTEmi.js} +0 -0
  258. /package/dist/assets/{qsharp-BcaKFfP-.js → qsharp-C7yF-Dti.js} +0 -0
  259. /package/dist/assets/{r-rFNes0iK.js → r-HXLGP0_-.js} +0 -0
  260. /package/dist/assets/{redis-DkpQ3K6G.js → redis-C4y6mf2p.js} +0 -0
  261. /package/dist/assets/{redshift-Di9N5TI3.js → redshift-DdQrFPrv.js} +0 -0
  262. /package/dist/assets/{restructuredtext-By5ZzLt6.js → restructuredtext-CBrf2PNj.js} +0 -0
  263. /package/dist/assets/{rough.esm-c4PR5shF.js → rough.esm-6CnTHTkH.js} +0 -0
  264. /package/dist/assets/{ruby-fF5zGiLp.js → ruby-CVtMoX2B.js} +0 -0
  265. /package/dist/assets/{rust-C5b3AYY6.js → rust-Ckq4sbMP.js} +0 -0
  266. /package/dist/assets/{sb-CD07UNRs.js → sb-er66OpE3.js} +0 -0
  267. /package/dist/assets/{scala-jtn1ip9G.js → scala-BdWZz-hH.js} +0 -0
  268. /package/dist/assets/{scheme-C4vqMkn_.js → scheme-kTV_s2kP.js} +0 -0
  269. /package/dist/assets/{scss-I4Glxjbz.js → scss-Bymy2VJQ.js} +0 -0
  270. /package/dist/assets/{shell-DubUcuns.js → shell-bwEP1LQ1.js} +0 -0
  271. /package/dist/assets/{solidity-CNy2KSl2.js → solidity-DCddeBgL.js} +0 -0
  272. /package/dist/assets/{sophia-D9_URt84.js → sophia-BMN3AQig.js} +0 -0
  273. /package/dist/assets/{sparql-DviXpm4C.js → sparql-CSXOLjzi.js} +0 -0
  274. /package/dist/assets/{sql-vDRbAZgK.js → sql-COg8ewmK.js} +0 -0
  275. /package/dist/assets/{st-CnpnRqA7.js → st-CIaZtBS_.js} +0 -0
  276. /package/dist/assets/{swift-D_g8Ganz.js → swift-DXiOhUHu.js} +0 -0
  277. /package/dist/assets/{systemverilog-OuZa5h1M.js → systemverilog-BTDE6Dr2.js} +0 -0
  278. /package/dist/assets/{tcl-DBm9ucsV.js → tcl-B0zy9NvV.js} +0 -0
  279. /package/dist/assets/{twig-BRzEqX5Y.js → twig-D594l4Z1.js} +0 -0
  280. /package/dist/assets/{typespec-D4ZgGeX8.js → typespec-XKVjaPqo.js} +0 -0
  281. /package/dist/assets/{useWorkspaceMenuKeyboard-Brws6Ar9.js → useWorkspaceMenuKeyboard-Bpy_s69Z.js} +0 -0
  282. /package/dist/assets/{vb-DcP4s6WS.js → vb-B2wYoKRV.js} +0 -0
  283. /package/dist/assets/{wgsl-CnRJ_QUf.js → wgsl-D0Yf2LbK.js} +0 -0
  284. /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 {
@@ -45,7 +50,13 @@ const {
45
50
  stopPersistedAcpProcessGroup,
46
51
  } = require('./acp-runtime');
47
52
  const { chatRuntimeForProvider, isChatMode } = require('./chat-runtime');
48
- const { acpToolChanges, acpToolDetail, acpToolReviewChanges, acpTranscriptToolEntry } = require('./acp-transcript');
53
+ const {
54
+ acpTranscriptEntries,
55
+ acpTranscriptMedia,
56
+ acpToolChanges,
57
+ acpToolDetail,
58
+ acpToolReviewChanges,
59
+ } = require('./acp-transcript');
49
60
  const {
50
61
  applyCodexTerminalProfile,
51
62
  codexTerminalProfileFromOutput,
@@ -67,6 +78,7 @@ const { inspectGitWorktree } = require('./git-worktree-info');
67
78
  const { deserializeTerminalState } = require('./terminal-state-serialization');
68
79
  const { compareNativePtyRuntimeEpochs } = require('./native-pty-controller-generation');
69
80
  const { canonicalWorkspacePath } = require('./workspace-root-registry');
81
+ const { mergeBrowserMcpServer } = require('../extensions/browser/backend/agent-capability');
70
82
  const {
71
83
  TERMINAL_OPERATION_STATES,
72
84
  activeLifecycleOperation,
@@ -83,6 +95,7 @@ const AGENT_USAGE_RATE_REFRESH_MS = 5 * 1000;
83
95
  const ACTIVITY_UPDATE_INTERVAL_MS = 1000;
84
96
  const ACTIVITY_HOT_SEC = 30 * 60;
85
97
  const ACTIVITY_WARM_SEC = 3 * 60 * 60;
98
+ const MAX_COMPOSER_COMMANDS = 64;
86
99
  const ACTIVITY_COOL_SEC = 12 * 60 * 60;
87
100
  const ZOMBIE_IDLE_MS = 72 * 60 * 60 * 1000;
88
101
  const ZOMBIE_SWEEP_INTERVAL_MS = 60 * 1000;
@@ -369,6 +382,41 @@ function normalizedComposerPrompt(message) {
369
382
  return prompt;
370
383
  }
371
384
 
385
+ function composerCommandHash(prompt) {
386
+ return crypto.createHash('sha256')
387
+ .update(JSON.stringify(stableJsonValue(prompt)))
388
+ .digest('hex');
389
+ }
390
+
391
+ function composerAdmissionError(message, uncertain = false) {
392
+ const error = new Error(message);
393
+ if (uncertain) error.uncertain = true;
394
+ return error;
395
+ }
396
+
397
+ function normalizedComposerCommands(commands) {
398
+ return (Array.isArray(commands) ? commands : [])
399
+ .filter(command => (
400
+ command
401
+ && typeof command === 'object'
402
+ && /^[A-Za-z0-9._:-]{1,160}$/.test(command.requestId)
403
+ && typeof command.contentHash === 'string'
404
+ && ['intent', 'accepted', 'unknown', 'failed'].includes(command.state)
405
+ ))
406
+ .slice(-MAX_COMPOSER_COMMANDS)
407
+ .map(command => ({
408
+ requestId: command.requestId,
409
+ contentHash: command.contentHash,
410
+ state: command.state,
411
+ result: command.result && typeof command.result === 'object'
412
+ ? JSON.parse(JSON.stringify(command.result))
413
+ : null,
414
+ error: typeof command.error === 'string' ? command.error.slice(0, 2000) : '',
415
+ createdAt: Number(command.createdAt) || 0,
416
+ updatedAt: Number(command.updatedAt) || 0,
417
+ }));
418
+ }
419
+
372
420
  function isShellProgram(command) {
373
421
  return ['bash', 'zsh', 'sh', 'fish'].includes(agentProgramName(command).toLowerCase());
374
422
  }
@@ -738,6 +786,27 @@ function timestampSlug(now = new Date()) {
738
786
  ].join('');
739
787
  }
740
788
 
789
+ function projectOperationSignature(value) {
790
+ return crypto.createHash('sha256')
791
+ .update(JSON.stringify(stableJsonValue(value)))
792
+ .digest('hex');
793
+ }
794
+
795
+ function worktreesFromPorcelain(output) {
796
+ const worktrees = [];
797
+ let current = null;
798
+ for (const line of String(output || '').split(/\r?\n/)) {
799
+ if (line.startsWith('worktree ')) {
800
+ if (current) worktrees.push(current);
801
+ current = { workspace: path.resolve(line.slice('worktree '.length)), branch: '' };
802
+ } else if (current && line.startsWith('branch refs/heads/')) {
803
+ current.branch = line.slice('branch refs/heads/'.length);
804
+ }
805
+ }
806
+ if (current) worktrees.push(current);
807
+ return worktrees;
808
+ }
809
+
741
810
  function isFarmingForkWorktreePath(workspace) {
742
811
  const basename = path.basename(String(workspace || '').replace(/[\\/]+$/, ''));
743
812
  return /-farming-fork-\d{8}-\d{6}(?:-\d+)?$/.test(basename);
@@ -757,6 +826,7 @@ class AgentManager extends EventEmitter {
757
826
  this.controlUrl = options.controlUrl || '';
758
827
  this.tokenFile = options.tokenFile || '';
759
828
  this.authDisabled = options.authDisabled === true;
829
+ this.browserMcpEnabled = options.browserMcpEnabled === true;
760
830
  this.skipExecutablePreflight = options.skipExecutablePreflight === true;
761
831
  this.cliBinDir = options.cliBinDir || path.join(__dirname, '..', 'bin');
762
832
  this.agentShellEnvProvider = typeof options.agentShellEnvProvider === 'function'
@@ -780,6 +850,7 @@ class AgentManager extends EventEmitter {
780
850
  this.pendingResizeByAgent = new Map();
781
851
  this.resizeDrains = new Map();
782
852
  this.inputQueues = new Map();
853
+ this.composerAdmissions = new Map();
783
854
  this.codexTerminalProfileQueues = new Map();
784
855
  this.codexTerminalStartQueues = new Map();
785
856
  this.codexTerminalStartOutput = new Map();
@@ -787,6 +858,7 @@ class AgentManager extends EventEmitter {
787
858
  this.agentLifecycleOperations = new Map();
788
859
  this.agentStartAdmissions = new Map();
789
860
  this.createRequestAdmissions = new Map();
861
+ this.projectOperationAdmissions = new Map();
790
862
  this.projectWorkspaceDeleteAdmissions = new Map();
791
863
  this.activeInputOperations = new Set();
792
864
  this.verifiedStoppedAgentIds = new Set();
@@ -1427,9 +1499,12 @@ class AgentManager extends EventEmitter {
1427
1499
  const recoveredLifecycleOperation = activeLifecycleOperation(agentRecord);
1428
1500
  if (
1429
1501
  recoveredLifecycleOperation?.type === 'create'
1430
- && recoveredLifecycleOperation.state === 'pending'
1502
+ && ['pending', 'membership-pending'].includes(recoveredLifecycleOperation.state)
1431
1503
  ) {
1432
1504
  try {
1505
+ if (recoveredLifecycleOperation.state === 'membership-pending') {
1506
+ this.rememberMainPageProviderSession(agentRecord);
1507
+ }
1433
1508
  this.transitionPersistentAgentOperation(
1434
1509
  agentRecord,
1435
1510
  recoveredLifecycleOperation.id,
@@ -1482,7 +1557,7 @@ class AgentManager extends EventEmitter {
1482
1557
  changed = true;
1483
1558
  }
1484
1559
 
1485
- if (this.reconcileMissingTerminalLifecycleOperations(
1560
+ if (await this.reconcileMissingTerminalLifecycleOperations(
1486
1561
  persistedRecords,
1487
1562
  recoveredRuntimeAgentIds,
1488
1563
  )) {
@@ -1519,7 +1594,7 @@ class AgentManager extends EventEmitter {
1519
1594
  }
1520
1595
  }
1521
1596
 
1522
- reconcileMissingTerminalLifecycleOperations(records, recoveredRuntimeAgentIds) {
1597
+ async reconcileMissingTerminalLifecycleOperations(records, recoveredRuntimeAgentIds) {
1523
1598
  let changed = false;
1524
1599
  for (const record of Array.isArray(records) ? records : []) {
1525
1600
  const agentId = String(record?.runtimeAgentId || '').trim();
@@ -1553,7 +1628,7 @@ class AgentManager extends EventEmitter {
1553
1628
  operation.request?.previousRuntimeAgentId,
1554
1629
  ),
1555
1630
  );
1556
- } else {
1631
+ } else if (operation.type === 'delete') {
1557
1632
  this.transitionPersistentAgentOperation(
1558
1633
  recoveredAgent,
1559
1634
  operation.id,
@@ -1567,7 +1642,27 @@ class AgentManager extends EventEmitter {
1567
1642
  },
1568
1643
  );
1569
1644
  this.removeMainPageProviderSessionsForAgents([recoveredAgent]);
1570
- if (operation.type === 'archive') this.scheduleCodexSessionArchive(recoveredAgent);
1645
+ } else {
1646
+ this.transitionPersistentAgentOperation(
1647
+ recoveredAgent,
1648
+ operation.id,
1649
+ 'provider-archive-pending',
1650
+ '',
1651
+ {
1652
+ visibleOnMainPage: false,
1653
+ archived: true,
1654
+ archivedAt: Date.now(),
1655
+ runtimeAgentId: '',
1656
+ },
1657
+ );
1658
+ this.removeMainPageProviderSessionsForAgents([recoveredAgent]);
1659
+ const providerArchive = await this.archiveCodexProviderSession(recoveredAgent);
1660
+ this.transitionPersistentAgentOperation(
1661
+ recoveredAgent,
1662
+ operation.id,
1663
+ providerArchive?.error ? 'blocked' : 'succeeded',
1664
+ providerArchive?.error || '',
1665
+ );
1571
1666
  }
1572
1667
  changed = true;
1573
1668
  } catch (error) {
@@ -1936,11 +2031,16 @@ class AgentManager extends EventEmitter {
1936
2031
  ? this.configManager.getCodexApprovalMode()
1937
2032
  : 'approve'
1938
2033
  );
2034
+ const recoveryEnv = this.buildAgentEnv(agentId, agent);
2035
+ const recoveryMcpServers = this.projectAcpMcpServers(
2036
+ Array.isArray(record.acpMcpServers) ? record.acpMcpServers : [],
2037
+ recoveryEnv,
2038
+ );
1939
2039
  const prepared = await this.acpRuntime.prepareAgent({
1940
2040
  agentId,
1941
2041
  provider,
1942
2042
  executable,
1943
- env: this.buildAgentEnv(agentId, agent),
2043
+ env: recoveryEnv,
1944
2044
  cwd: agent.cwd,
1945
2045
  sessionId,
1946
2046
  historyMode: 'checkpoint',
@@ -1951,8 +2051,9 @@ class AgentManager extends EventEmitter {
1951
2051
  model: 'config',
1952
2052
  reasoningEffort: 'config',
1953
2053
  serviceTier: 'config',
2054
+ farmingSystemPrompt: renderFarmingAgentBootstrap(),
1954
2055
  additionalDirectories: Array.isArray(record.acpAdditionalDirectories) ? record.acpAdditionalDirectories : [],
1955
- mcpServers: Array.isArray(record.acpMcpServers) ? record.acpMcpServers : [],
2056
+ mcpServers: recoveryMcpServers,
1956
2057
  onProcessStarted: async processIdentity => {
1957
2058
  agent.structuredRuntimeProcess = {
1958
2059
  kind: 'acp-process-group',
@@ -1967,13 +2068,21 @@ class AgentManager extends EventEmitter {
1967
2068
  prepared.sessionId,
1968
2069
  agent.providerHomeId || record.providerHomeId || 'default'
1969
2070
  );
1970
- this.acpSessionOptionsByKey.set(agent.providerSessionKey, {
2071
+ let recoveredSessionOptions = {
1971
2072
  additionalDirectories: Array.isArray(record.acpAdditionalDirectories)
1972
- ? [...record.acpAdditionalDirectories]
1973
- : [],
1974
- mcpServers: Array.isArray(record.acpMcpServers)
1975
- ? JSON.parse(JSON.stringify(record.acpMcpServers))
2073
+ ? record.acpAdditionalDirectories
1976
2074
  : [],
2075
+ mcpServers: recoveryMcpServers,
2076
+ };
2077
+ try {
2078
+ recoveredSessionOptions = this.acpRuntime.getSessionRequestOptions(agentId);
2079
+ } catch {
2080
+ // The live binding already validated these options. Retain the
2081
+ // projected copy for custom runtimes that do not expose it.
2082
+ }
2083
+ this.acpSessionOptionsByKey.set(agent.providerSessionKey, {
2084
+ additionalDirectories: [...recoveredSessionOptions.additionalDirectories],
2085
+ mcpServers: JSON.parse(JSON.stringify(recoveredSessionOptions.mcpServers)),
1977
2086
  });
1978
2087
  agent.providerSessionTemporary = false;
1979
2088
  agent.providerSessionSource = `acp-${prepared.historyMode}`;
@@ -2053,7 +2162,16 @@ class AgentManager extends EventEmitter {
2053
2162
  setAgentRecordId(staged, record.id || '');
2054
2163
  staged.structuredRuntimeProcess = null;
2055
2164
  try {
2056
- if (operation.type === 'create') {
2165
+ if (operation.type === 'create' && operation.state === 'membership-pending') {
2166
+ this.rememberMainPageProviderSession(staged);
2167
+ this.transitionPersistentAgentOperation(
2168
+ staged,
2169
+ operation.id,
2170
+ 'succeeded',
2171
+ '',
2172
+ { archived: false },
2173
+ );
2174
+ } else if (operation.type === 'create') {
2057
2175
  this.transitionPersistentAgentOperation(
2058
2176
  staged,
2059
2177
  operation.id,
@@ -2061,7 +2179,7 @@ class AgentManager extends EventEmitter {
2061
2179
  'Create ACP process was stopped during restart recovery',
2062
2180
  createFailurePatch(operation, operation.request?.previousRuntimeAgentId),
2063
2181
  );
2064
- } else {
2182
+ } else if (operation.type === 'delete') {
2065
2183
  this.transitionPersistentAgentOperation(
2066
2184
  staged,
2067
2185
  operation.id,
@@ -2076,7 +2194,28 @@ class AgentManager extends EventEmitter {
2076
2194
  },
2077
2195
  );
2078
2196
  this.removeMainPageProviderSessionsForAgents([staged]);
2079
- if (operation.type === 'archive') this.scheduleCodexSessionArchive(staged);
2197
+ } else {
2198
+ this.transitionPersistentAgentOperation(
2199
+ staged,
2200
+ operation.id,
2201
+ 'provider-archive-pending',
2202
+ '',
2203
+ {
2204
+ visibleOnMainPage: false,
2205
+ archived: true,
2206
+ archivedAt: Date.now(),
2207
+ runtimeAgentId: '',
2208
+ structuredRuntimeProcess: null,
2209
+ },
2210
+ );
2211
+ this.removeMainPageProviderSessionsForAgents([staged]);
2212
+ const providerArchive = await this.archiveCodexProviderSession(staged);
2213
+ this.transitionPersistentAgentOperation(
2214
+ staged,
2215
+ operation.id,
2216
+ providerArchive?.error ? 'blocked' : 'succeeded',
2217
+ providerArchive?.error || '',
2218
+ );
2080
2219
  }
2081
2220
  } catch (error) {
2082
2221
  console.warn(
@@ -2201,6 +2340,7 @@ class AgentManager extends EventEmitter {
2201
2340
  category: metadata.category || 'coding',
2202
2341
  launchPermissionMode: metadata.launchPermissionMode || '',
2203
2342
  parentAgentId: metadata.parentAgentId || '',
2343
+ forkRequestId: metadata.forkRequestId || '',
2204
2344
  task: metadata.task || '',
2205
2345
  workflowTemplate: metadata.workflowTemplate || '',
2206
2346
  source: metadata.source || 'recovered',
@@ -2241,6 +2381,7 @@ class AgentManager extends EventEmitter {
2241
2381
  agentRecordId,
2242
2382
  persistentSessionId: agentRecordId,
2243
2383
  lifecycleJournal: lifecycleJournal(metadata),
2384
+ composerCommands: normalizedComposerCommands(metadata.composerCommands),
2244
2385
  customTitle: metadata.customTitle || '',
2245
2386
  terminalBusy: typeof state.terminalBusy === 'boolean' ? state.terminalBusy : null,
2246
2387
  shellCwd: state.shellCwd || metadata.cwd || '',
@@ -2469,6 +2610,26 @@ class AgentManager extends EventEmitter {
2469
2610
  return operation;
2470
2611
  }
2471
2612
 
2613
+ completePersistentAgentOperation(agent, operationId, result, patch = {}) {
2614
+ const staged = {
2615
+ ...agent,
2616
+ lifecycleJournal: lifecycleJournal(agent),
2617
+ };
2618
+ const operation = setLifecycleOperationResult(staged, operationId, result);
2619
+ if (!operation) throw new Error(`Agent operation ${operationId} was not found`);
2620
+ transitionLifecycleOperation(staged, operationId, 'succeeded');
2621
+ const persistentSessionId = this.ensurePersistentAgentSession(staged, patch);
2622
+ if (
2623
+ typeof this.configManager?.ensureAgentSessionRecord === 'function'
2624
+ && !persistentSessionId
2625
+ ) {
2626
+ throw new Error('Agent session store did not return a persistent id');
2627
+ }
2628
+ agent.lifecycleJournal = staged.lifecycleJournal;
2629
+ setAgentRecordId(agent, staged.agentRecordId || staged.persistentSessionId || '');
2630
+ return operation;
2631
+ }
2632
+
2472
2633
  beginPersistentAgentUpdate(agent, requestKey, request) {
2473
2634
  const latest = latestLifecycleOperation(agent);
2474
2635
  if (
@@ -2975,6 +3136,7 @@ class AgentManager extends EventEmitter {
2975
3136
  env.FARMING_AGENT_ID = agentId;
2976
3137
  env.FARMING_IS_MAIN_AGENT = agent.wantsMain ? '1' : '0';
2977
3138
  env.FARMING_SKILLS_COMMAND = 'farming skills';
3139
+ env.FARMING_CAPABILITIES_COMMAND = 'farming capabilities';
2978
3140
  env.FARMING_MAIN_WORKSPACE = agent.mainWorkspace || '';
2979
3141
  env.FARMING_PROJECT_WORKSPACE = effectiveAgentWorkspaceRoot(agent);
2980
3142
 
@@ -2992,6 +3154,7 @@ class AgentManager extends EventEmitter {
2992
3154
  }
2993
3155
  if (this.configManager && this.configManager.farmingDir) {
2994
3156
  env.FARMING_CONFIG_DIR = this.configManager.farmingDir;
3157
+ env.FARMING_STARTUP_PROMPT_FILE = storageLayout.farmingAgentBootstrapFile(this.configManager.farmingDir);
2995
3158
  }
2996
3159
  if (agent.mainWorkspace) {
2997
3160
  env.FARMING_SKILLS_FILE = path.join(agent.mainWorkspace, 'FARMING_MAIN_AGENT_SKILLS.md');
@@ -3004,10 +3167,22 @@ class AgentManager extends EventEmitter {
3004
3167
  agent.providerHomePath
3005
3168
  );
3006
3169
  }
3170
+ const provider = agent.providerSessionProvider || agentHomeProviderForProgram(agent.forkCommand || agent.command);
3171
+ if (provider === 'opencode' && env.FARMING_STARTUP_PROMPT_FILE) {
3172
+ Object.assign(env, appendOpenCodeBootstrap(env, env.FARMING_STARTUP_PROMPT_FILE));
3173
+ }
3007
3174
 
3008
3175
  return env;
3009
3176
  }
3010
3177
 
3178
+ projectAcpMcpServers(mcpServers, agentEnv) {
3179
+ if (!this.browserMcpEnabled) return Array.isArray(mcpServers) ? mcpServers : [];
3180
+ return mergeBrowserMcpServer(mcpServers, {
3181
+ cliBinDir: this.cliBinDir,
3182
+ agentEnv,
3183
+ });
3184
+ }
3185
+
3011
3186
  expandWorkspacePath(workspace) {
3012
3187
  if (typeof workspace !== 'string') return '';
3013
3188
  const value = workspace.trim();
@@ -3253,6 +3428,7 @@ class AgentManager extends EventEmitter {
3253
3428
  category: agent.category,
3254
3429
  launchPermissionMode: agent.launchPermissionMode,
3255
3430
  parentAgentId: agent.parentAgentId || '',
3431
+ forkRequestId: agent.forkRequestId || '',
3256
3432
  task: agent.task,
3257
3433
  workflowTemplate: agent.workflowTemplate,
3258
3434
  source: agent.source,
@@ -3615,6 +3791,7 @@ class AgentManager extends EventEmitter {
3615
3791
  codexModel,
3616
3792
  codexReasoningEffort,
3617
3793
  codexServiceTier,
3794
+ farmingSystemPrompt: renderFarmingAgentBootstrap(),
3618
3795
  mainAgentSystemPrompt: wantsMain ? renderMainAgentBootstrap() : '',
3619
3796
  });
3620
3797
  const program = launch.program;
@@ -3863,6 +4040,7 @@ class AgentManager extends EventEmitter {
3863
4040
  category: resolution.spec ? resolution.spec.category : 'other',
3864
4041
  launchPermissionMode: launch.permissionMode || '',
3865
4042
  parentAgentId,
4043
+ forkRequestId: typeof options.forkRequestId === 'string' ? options.forkRequestId : '',
3866
4044
  task: typeof options.task === 'string' ? options.task : '',
3867
4045
  workflowTemplate: typeof options.workflowTemplate === 'string' ? options.workflowTemplate : '',
3868
4046
  source: typeof options.source === 'string' ? options.source : 'ui',
@@ -4076,7 +4254,10 @@ class AgentManager extends EventEmitter {
4076
4254
  const requestedAdditionalDirectories = Array.isArray(options.additionalDirectories)
4077
4255
  ? options.additionalDirectories
4078
4256
  : [];
4079
- const requestedMcpServers = Array.isArray(options.mcpServers) ? options.mcpServers : [];
4257
+ const requestedMcpServers = this.projectAcpMcpServers(
4258
+ Array.isArray(options.mcpServers) ? options.mcpServers : [],
4259
+ identityEnv,
4260
+ );
4080
4261
  const created = await this.createProviderSessionIdentity({
4081
4262
  provider: providerSessionPlan.provider,
4082
4263
  executable: spawnProgram,
@@ -4087,6 +4268,7 @@ class AgentManager extends EventEmitter {
4087
4268
  model: codexModel,
4088
4269
  reasoningEffort: codexReasoningEffort,
4089
4270
  serviceTier: codexServiceTier,
4271
+ farmingSystemPrompt: renderFarmingAgentBootstrap(),
4090
4272
  additionalDirectories: requestedAdditionalDirectories,
4091
4273
  mcpServers: requestedMcpServers,
4092
4274
  });
@@ -4230,14 +4412,16 @@ class AgentManager extends EventEmitter {
4230
4412
  const additionalDirectories = Array.isArray(options.additionalDirectories)
4231
4413
  ? options.additionalDirectories
4232
4414
  : rememberedSessionOptions.additionalDirectories || [];
4233
- const mcpServers = Array.isArray(options.mcpServers)
4415
+ const requestedMcpServers = Array.isArray(options.mcpServers)
4234
4416
  ? options.mcpServers
4235
4417
  : rememberedSessionOptions.mcpServers || [];
4418
+ const acpEnv = this.buildAgentEnv(agentId, agentRecord);
4419
+ const mcpServers = this.projectAcpMcpServers(requestedMcpServers, acpEnv);
4236
4420
  const prepared = await this.acpRuntime.prepareAgent({
4237
4421
  agentId,
4238
4422
  provider: structuredRuntimeProvider,
4239
4423
  executable: spawnProgram,
4240
- env: this.buildAgentEnv(agentId, agentRecord),
4424
+ env: acpEnv,
4241
4425
  cwd: workspace,
4242
4426
  sessionId: options.acpStartFresh === true || agentRecord.providerSessionTemporary || acpGeneratedFreshSession
4243
4427
  ? ''
@@ -4250,6 +4434,7 @@ class AgentManager extends EventEmitter {
4250
4434
  model: codexModel,
4251
4435
  reasoningEffort: codexReasoningEffort,
4252
4436
  serviceTier: codexServiceTier,
4437
+ farmingSystemPrompt: renderFarmingAgentBootstrap(),
4253
4438
  additionalDirectories,
4254
4439
  mcpServers,
4255
4440
  forkSourceSessionId: options.acpForkSourceSessionId || '',
@@ -4350,13 +4535,18 @@ class AgentManager extends EventEmitter {
4350
4535
  await startTerminal();
4351
4536
  }
4352
4537
  }
4353
- this.transitionPersistentAgentOperation(agentRecord, createOperationId, 'succeeded', '', {
4538
+ this.transitionPersistentAgentOperation(agentRecord, createOperationId, 'membership-pending', '', {
4354
4539
  visibleOnMainPage: true,
4355
4540
  archived: false,
4356
4541
  ...(options.customTitleExplicit === true
4357
4542
  ? { customTitle: agentRecord.customTitle }
4358
4543
  : {}),
4359
4544
  });
4545
+ this.rememberMainPageProviderSession(agentRecord);
4546
+ this.transitionPersistentAgentOperation(agentRecord, createOperationId, 'succeeded', '', {
4547
+ visibleOnMainPage: true,
4548
+ archived: false,
4549
+ });
4360
4550
  if (
4361
4551
  previousPersistentRuntimeAgentId
4362
4552
  && previousPersistentRuntimeAgentId !== agentId
@@ -4576,7 +4766,171 @@ class AgentManager extends EventEmitter {
4576
4766
  return this.enqueueInputOperation(agentId, () => this.sendInputNow(agentId, input, options));
4577
4767
  }
4578
4768
 
4579
- async sendComposerMessage(agentId, message) {
4769
+ commitComposerCommand(agent, command) {
4770
+ const commands = normalizedComposerCommands(agent.composerCommands)
4771
+ .filter(candidate => candidate.requestId !== command.requestId);
4772
+ commands.push(command);
4773
+ const staged = {
4774
+ ...agent,
4775
+ composerCommands: normalizedComposerCommands(commands),
4776
+ };
4777
+ const persistentSessionId = this.ensurePersistentAgentSession(staged);
4778
+ if (
4779
+ typeof this.configManager?.ensureAgentSessionRecord === 'function'
4780
+ && !persistentSessionId
4781
+ ) {
4782
+ throw new Error('Agent session store did not return a persistent id');
4783
+ }
4784
+ agent.composerCommands = staged.composerCommands;
4785
+ setAgentRecordId(agent, staged.agentRecordId || staged.persistentSessionId || '');
4786
+ return command;
4787
+ }
4788
+
4789
+ setComposerCommandInMemory(agent, command) {
4790
+ agent.composerCommands = normalizedComposerCommands([
4791
+ ...normalizedComposerCommands(agent.composerCommands)
4792
+ .filter(candidate => candidate.requestId !== command.requestId),
4793
+ command,
4794
+ ]);
4795
+ }
4796
+
4797
+ sendPersistentComposerMessage(agentId, message, requestId) {
4798
+ const agent = this.agents.get(agentId);
4799
+ if (!agent) return Promise.reject(new Error('Agent not found'));
4800
+ if (runtimeKind(agent) === 'terminal') {
4801
+ return Promise.reject(new Error('Persistent Composer admission requires a structured runtime'));
4802
+ }
4803
+ const prompt = normalizedComposerPrompt(message);
4804
+ const contentHash = composerCommandHash(prompt);
4805
+ const commands = normalizedComposerCommands(agent.composerCommands);
4806
+ const existing = commands.find(command => command.requestId === requestId);
4807
+ const admissionKey = `${agentId}:${requestId}`;
4808
+ const inFlight = this.composerAdmissions.get(admissionKey);
4809
+ if (existing?.contentHash && existing.contentHash !== contentHash) {
4810
+ return Promise.reject(new Error(`Composer request ${requestId} was already used for different content`));
4811
+ }
4812
+ if (existing?.state === 'accepted') {
4813
+ return Promise.resolve({ ...(existing.result || {}), accepted: true, deduplicated: true });
4814
+ }
4815
+ if (inFlight) return inFlight.promise;
4816
+ if (existing?.state === 'unknown' || existing?.state === 'intent') {
4817
+ const detail = existing.error || `Composer request ${requestId} has an uncertain outcome and will not be replayed automatically`;
4818
+ if (existing.state === 'intent') {
4819
+ const unknown = { ...existing, state: 'unknown', error: detail, updatedAt: Date.now() };
4820
+ try {
4821
+ this.commitComposerCommand(agent, unknown);
4822
+ } catch {
4823
+ this.setComposerCommandInMemory(agent, unknown);
4824
+ }
4825
+ }
4826
+ return Promise.reject(composerAdmissionError(detail, true));
4827
+ }
4828
+ if (existing?.state === 'failed') {
4829
+ return Promise.reject(new Error(existing.error || `Composer request ${requestId} was not accepted`));
4830
+ }
4831
+
4832
+ const intent = {
4833
+ requestId,
4834
+ contentHash,
4835
+ state: 'intent',
4836
+ result: null,
4837
+ error: '',
4838
+ createdAt: Date.now(),
4839
+ updatedAt: Date.now(),
4840
+ };
4841
+ try {
4842
+ this.commitComposerCommand(agent, intent);
4843
+ } catch (error) {
4844
+ return Promise.reject(new Error(`Failed to persist Composer intent: ${error.message || error}`));
4845
+ }
4846
+
4847
+ let resolveAdmission;
4848
+ let rejectAdmission;
4849
+ const admissionPromise = new Promise((resolve, reject) => {
4850
+ resolveAdmission = resolve;
4851
+ rejectAdmission = reject;
4852
+ });
4853
+ const entry = { contentHash, promise: admissionPromise };
4854
+ this.composerAdmissions.set(admissionKey, entry);
4855
+ let submitted = false;
4856
+ const onSubmitted = (result = { kind: runtimeKind(agent) }) => {
4857
+ if (submitted) return;
4858
+ submitted = true;
4859
+ const accepted = {
4860
+ ...intent,
4861
+ state: 'accepted',
4862
+ result,
4863
+ updatedAt: Date.now(),
4864
+ };
4865
+ try {
4866
+ this.commitComposerCommand(agent, accepted);
4867
+ resolveAdmission({ ...result, accepted: true });
4868
+ } catch (error) {
4869
+ const unknown = {
4870
+ ...intent,
4871
+ state: 'unknown',
4872
+ error: `Provider accepted Composer request, but admission could not be saved: ${error.message || error}`,
4873
+ updatedAt: Date.now(),
4874
+ };
4875
+ this.setComposerCommandInMemory(agent, unknown);
4876
+ rejectAdmission(composerAdmissionError(unknown.error, true));
4877
+ }
4878
+ };
4879
+
4880
+ const completion = isAcpAgent(agent)
4881
+ ? this.enqueueInputOperationUntilReleased(
4882
+ agentId,
4883
+ releaseInput => this.sendComposerMessageNow(agentId, prompt, {
4884
+ onSubmitted: () => {
4885
+ try {
4886
+ onSubmitted({ kind: 'acp' });
4887
+ } finally {
4888
+ releaseInput();
4889
+ }
4890
+ },
4891
+ }),
4892
+ )
4893
+ : this.enqueueInputOperation(
4894
+ agentId,
4895
+ () => this.sendComposerMessageNow(agentId, prompt, {
4896
+ onSubmitted: result => onSubmitted(result),
4897
+ }),
4898
+ );
4899
+ void Promise.resolve(completion).then(result => {
4900
+ if (!submitted) onSubmitted(result);
4901
+ }).catch(error => {
4902
+ if (submitted) return;
4903
+ const failed = {
4904
+ ...intent,
4905
+ state: 'failed',
4906
+ error: error.message || String(error),
4907
+ updatedAt: Date.now(),
4908
+ };
4909
+ let uncertain = false;
4910
+ try {
4911
+ this.commitComposerCommand(agent, failed);
4912
+ } catch (persistError) {
4913
+ failed.state = 'unknown';
4914
+ failed.error = `${failed.error}; failed to persist rejection: ${persistError.message || persistError}`;
4915
+ this.setComposerCommandInMemory(agent, failed);
4916
+ uncertain = true;
4917
+ }
4918
+ rejectAdmission(composerAdmissionError(failed.error, uncertain));
4919
+ });
4920
+ void admissionPromise.finally(() => {
4921
+ if (this.composerAdmissions.get(admissionKey) === entry) {
4922
+ this.composerAdmissions.delete(admissionKey);
4923
+ }
4924
+ }).catch(() => {});
4925
+ return admissionPromise;
4926
+ }
4927
+
4928
+ async sendComposerMessage(agentId, message, options = {}) {
4929
+ const requestId = String(options.requestId || '').trim();
4930
+ if (requestId) {
4931
+ if (!/^[A-Za-z0-9._:-]{1,160}$/.test(requestId)) throw new Error('Composer requestId is invalid');
4932
+ return this.sendPersistentComposerMessage(agentId, message, requestId);
4933
+ }
4580
4934
  const agent = this.agents.get(agentId);
4581
4935
  if (agent && isAcpAgent(agent)) {
4582
4936
  return this.enqueueInputOperationUntilReleased(
@@ -4695,13 +5049,15 @@ class AgentManager extends EventEmitter {
4695
5049
  agent.providerSessionId = result.sessionId || agent.providerSessionId;
4696
5050
  agent.providerSessionTemporary = !agent.providerSessionId;
4697
5051
  this.ensurePersistentAgentSession(agent);
4698
- return { kind: 'json', sessionId: agent.providerSessionId };
5052
+ const submitted = { kind: 'json', sessionId: agent.providerSessionId };
5053
+ options.onSubmitted?.(submitted);
5054
+ return submitted;
4699
5055
  }
4700
5056
 
4701
5057
  if (isAcpAgent(agent)) {
4702
5058
  this.requireLiveAcpAgent(agentId);
4703
5059
  const result = await this.acpRuntime.submitMessage(agentId, prompt, {
4704
- onSubmitted: options.releaseInput,
5060
+ onSubmitted: options.onSubmitted || options.releaseInput,
4705
5061
  });
4706
5062
  if (result.steered !== true) {
4707
5063
  const runtime = runtimeBindingOf(agent, 'acp');
@@ -4713,7 +5069,9 @@ class AgentManager extends EventEmitter {
4713
5069
  }
4714
5070
 
4715
5071
  await this.sendInputNow(agentId, [{ type: 'paste', text }, '\r']);
4716
- return { kind: 'terminal' };
5072
+ const submitted = { kind: 'terminal' };
5073
+ options.onSubmitted?.(submitted);
5074
+ return submitted;
4717
5075
  }
4718
5076
 
4719
5077
  getJsonCliTranscript(agentId, options = {}) {
@@ -4733,10 +5091,21 @@ class AgentManager extends EventEmitter {
4733
5091
  const transcript = this.acpRuntime.getTranscriptSession(agentId, options);
4734
5092
  return {
4735
5093
  ...transcript,
4736
- entries: transcript.entries.map(entry => entry?.type === 'tool' ? acpTranscriptToolEntry(entry) : entry),
5094
+ entries: acpTranscriptEntries(transcript.entries, {
5095
+ mediaPathPrefix: options.mediaPathPrefix,
5096
+ }),
4737
5097
  };
4738
5098
  }
4739
5099
 
5100
+ getAcpTranscriptMedia(agentId, entryId, mediaId) {
5101
+ this.requireLiveAcpAgent(agentId);
5102
+ const entry = this.acpRuntime.getTranscriptEntry(agentId, entryId);
5103
+ if (!entry) throw new Error('ACP transcript entry not found');
5104
+ const media = acpTranscriptMedia(entry, mediaId);
5105
+ if (!media) throw new Error('ACP transcript media not found');
5106
+ return media;
5107
+ }
5108
+
4740
5109
  getAcpToolDetail(agentId, toolCallId) {
4741
5110
  this.requireLiveAcpAgent(agentId);
4742
5111
  const entry = this.acpRuntime.getToolEntry(agentId, toolCallId);
@@ -5309,6 +5678,14 @@ class AgentManager extends EventEmitter {
5309
5678
  if (admission) {
5310
5679
  transitionLifecycleOperation(staged, admission.operation.id, 'succeeded');
5311
5680
  }
5681
+ if (!admission && !structuralUpdateChanged && !readUpdateChanged) {
5682
+ return {
5683
+ agentId,
5684
+ ...updates,
5685
+ changed: false,
5686
+ requiresState: false,
5687
+ };
5688
+ }
5312
5689
  try {
5313
5690
  this.ensurePersistentAgentSession(staged);
5314
5691
  } catch (error) {
@@ -5942,6 +6319,104 @@ class AgentManager extends EventEmitter {
5942
6319
  : this.markAgentReadCursor(agentId);
5943
6320
  }
5944
6321
 
6322
+ persistentProjectOperation(requestId, type, signature, request) {
6323
+ if (
6324
+ !requestId
6325
+ || typeof this.configManager?.getProjectOperation !== 'function'
6326
+ || typeof this.configManager?.commitProjectOperation !== 'function'
6327
+ ) {
6328
+ return { operation: null, created: true };
6329
+ }
6330
+ const existing = this.configManager.getProjectOperation(requestId);
6331
+ if (existing) {
6332
+ if (existing.type !== type || existing.signature !== signature) {
6333
+ return { error: `Project operation request ${requestId} was already used for different parameters` };
6334
+ }
6335
+ return { operation: existing, created: false };
6336
+ }
6337
+ const operation = {
6338
+ id: requestId,
6339
+ type,
6340
+ state: 'pending',
6341
+ signature,
6342
+ request,
6343
+ result: null,
6344
+ error: '',
6345
+ startedAt: Date.now(),
6346
+ updatedAt: Date.now(),
6347
+ finishedAt: null,
6348
+ };
6349
+ try {
6350
+ this.configManager.commitProjectOperation(operation);
6351
+ return { operation, created: true };
6352
+ } catch (error) {
6353
+ return { error: `Failed to persist Project operation intent: ${error.message || error}` };
6354
+ }
6355
+ }
6356
+
6357
+ commitPersistentProjectOperation(operation, state, result = null, error = '', membership = {}) {
6358
+ if (!operation || typeof this.configManager?.commitProjectOperation !== 'function') {
6359
+ return { operation: null, projectWorkspaces: null, pinnedProjectWorkspaces: null };
6360
+ }
6361
+ const terminal = ['succeeded', 'failed', 'blocked'].includes(state);
6362
+ const nextOperation = {
6363
+ ...operation,
6364
+ state,
6365
+ result,
6366
+ error,
6367
+ updatedAt: Date.now(),
6368
+ finishedAt: terminal ? Date.now() : null,
6369
+ };
6370
+ return this.configManager.commitProjectOperation(nextOperation, membership);
6371
+ }
6372
+
6373
+ async listGitWorktrees(sourceWorkspace) {
6374
+ const { stdout } = await execFileAsync('git', ['-C', sourceWorkspace, 'worktree', 'list', '--porcelain'], {
6375
+ timeout: 15000,
6376
+ maxBuffer: 1024 * 1024 * 4,
6377
+ });
6378
+ return worktreesFromPorcelain(stdout);
6379
+ }
6380
+
6381
+ async inspectGitWorktreePostcondition(sourceWorkspace, workspace, branch = '') {
6382
+ const target = path.resolve(workspace);
6383
+ let worktrees;
6384
+ try {
6385
+ worktrees = await this.listGitWorktrees(sourceWorkspace);
6386
+ } catch (error) {
6387
+ return { proven: false, error: error.message || String(error) };
6388
+ }
6389
+ const registered = worktrees.find(entry => entry.workspace === target) || null;
6390
+ let exists = false;
6391
+ try {
6392
+ exists = fs.statSync(target).isDirectory();
6393
+ } catch {
6394
+ exists = false;
6395
+ }
6396
+ let branchExists = false;
6397
+ if (branch) {
6398
+ try {
6399
+ await execFileAsync('git', ['-C', sourceWorkspace, 'show-ref', '--verify', '--quiet', `refs/heads/${branch}`], {
6400
+ timeout: 15000,
6401
+ maxBuffer: 1024 * 1024,
6402
+ });
6403
+ branchExists = true;
6404
+ } catch (error) {
6405
+ if (error?.code !== 1) {
6406
+ return { proven: false, error: error.message || String(error) };
6407
+ }
6408
+ }
6409
+ }
6410
+ return {
6411
+ proven: true,
6412
+ exists,
6413
+ registered: Boolean(registered),
6414
+ branchMatches: !branch || registered?.branch === branch,
6415
+ branchExists,
6416
+ worktree: registered,
6417
+ };
6418
+ }
6419
+
5945
6420
  async resolveGitWorktreeSourceRoot(workspace) {
5946
6421
  const sourceWorkspace = this.expandWorkspacePath(workspace);
5947
6422
  if (!sourceWorkspace) {
@@ -5986,47 +6461,182 @@ class AgentManager extends EventEmitter {
5986
6461
  return target;
5987
6462
  }
5988
6463
 
5989
- async createPermanentWorktree(workspace) {
6464
+ createPermanentWorktree(workspace, options = {}) {
6465
+ const requestId = String(options.requestId || '').trim().slice(0, 160);
6466
+ if (!requestId) return this.createPermanentWorktreeAdmitted(workspace, options);
6467
+ const workspaceKey = canonicalWorkspacePath(this.expandWorkspacePath(workspace));
6468
+ const current = this.projectOperationAdmissions.get(requestId);
6469
+ if (current) {
6470
+ if (current.workspaceKey === workspaceKey) return current.promise;
6471
+ return Promise.reject(new Error(`Project operation request ${requestId} was already used for different parameters`));
6472
+ }
6473
+ const promise = this.createPermanentWorktreeAdmitted(workspace, options);
6474
+ const admission = { workspaceKey, promise };
6475
+ this.projectOperationAdmissions.set(requestId, admission);
6476
+ void promise.finally(() => {
6477
+ if (this.projectOperationAdmissions.get(requestId) === admission) {
6478
+ this.projectOperationAdmissions.delete(requestId);
6479
+ }
6480
+ }).catch(() => {});
6481
+ return promise;
6482
+ }
6483
+
6484
+ async createPermanentWorktreeAdmitted(workspace, options = {}) {
5990
6485
  const root = await this.resolveGitWorktreeSourceRoot(workspace);
6486
+ const requestId = String(options.requestId || '').trim().slice(0, 160);
6487
+ const signature = projectOperationSignature({ sourceWorkspace: root, type: 'create-worktree' });
6488
+ const existingOperation = requestId && typeof this.configManager?.getProjectOperation === 'function'
6489
+ ? this.configManager.getProjectOperation(requestId)
6490
+ : null;
6491
+ const operationWasExisting = Boolean(existingOperation);
6492
+ if (
6493
+ existingOperation
6494
+ && (
6495
+ existingOperation.type !== 'create-worktree'
6496
+ || existingOperation.signature !== signature
6497
+ )
6498
+ ) {
6499
+ throw new Error(`Project operation request ${requestId} was already used for different parameters`);
6500
+ }
6501
+ if (existingOperation?.state === 'succeeded' && existingOperation.result) {
6502
+ const settings = this.configManager.getSettings?.() || {};
6503
+ return {
6504
+ ...existingOperation.result,
6505
+ deduplicated: true,
6506
+ projectWorkspaces: settings.projectWorkspaces || [],
6507
+ pinnedProjectWorkspaces: settings.pinnedProjectWorkspaces || [],
6508
+ };
6509
+ }
6510
+
5991
6511
  const parentDir = path.dirname(root);
5992
6512
  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) {
6513
+ let target = String(existingOperation?.request?.workspace || '');
6514
+ let branch = String(existingOperation?.request?.branch || '');
6515
+ let operation = existingOperation;
6516
+ if (!operation) {
6517
+ const slug = timestampSlug();
6518
+ let suffix = 1;
6519
+ while (suffix < 1000) {
6520
+ const suffixText = suffix === 1 ? '' : `-${suffix}`;
6521
+ const candidateTarget = path.join(parentDir, `${baseName}-farming-worktree-${slug}${suffixText}`);
6522
+ const candidateBranch = `farming/worktree-${slug}${suffixText}`;
6523
+ let branchExists = false;
6015
6524
  try {
6016
- await execFileAsync('git', ['-C', root, 'worktree', 'add', '-b', branch, target, 'HEAD'], {
6017
- timeout: 60000,
6018
- maxBuffer: 1024 * 1024 * 4,
6525
+ await execFileAsync('git', ['-C', root, 'show-ref', '--verify', '--quiet', `refs/heads/${candidateBranch}`], {
6526
+ timeout: 15000,
6527
+ maxBuffer: 1024 * 1024,
6019
6528
  });
6529
+ branchExists = true;
6020
6530
  } 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 });
6531
+ if (error?.code !== 1) {
6532
+ const message = error && error.stderr ? String(error.stderr).trim() : '';
6533
+ throw new Error(message || 'Failed to inspect git branches', { cause: error });
6534
+ }
6023
6535
  }
6024
- return { workspace: target, branch, sourceWorkspace: root };
6536
+
6537
+ if (!fs.existsSync(candidateTarget) && !branchExists) {
6538
+ target = candidateTarget;
6539
+ branch = candidateBranch;
6540
+ break;
6541
+ }
6542
+ suffix += 1;
6543
+ }
6544
+ if (!target || !branch) throw new Error('Unable to allocate a permanent worktree name');
6545
+ const admission = this.persistentProjectOperation(requestId, 'create-worktree', signature, {
6546
+ sourceWorkspace: root,
6547
+ workspace: target,
6548
+ branch,
6549
+ });
6550
+ if (admission.error) throw new Error(admission.error);
6551
+ operation = admission.operation;
6552
+ }
6553
+
6554
+ const commitSuccess = () => {
6555
+ const result = { workspace: target, branch, sourceWorkspace: root, ...(requestId ? { requestId } : {}) };
6556
+ if (!operation) return result;
6557
+ const committed = this.commitPersistentProjectOperation(
6558
+ operation,
6559
+ 'succeeded',
6560
+ result,
6561
+ '',
6562
+ { mountWorkspace: target },
6563
+ );
6564
+ return {
6565
+ ...result,
6566
+ projectWorkspaces: committed.projectWorkspaces,
6567
+ pinnedProjectWorkspaces: committed.pinnedProjectWorkspaces,
6568
+ };
6569
+ };
6570
+
6571
+ if (operation && !['pending', 'unknown'].includes(operation.state)) {
6572
+ throw new Error(operation.error || `Project operation ${requestId} already finished with state ${operation.state}`);
6573
+ }
6574
+ if (operation && operationWasExisting) {
6575
+ const postcondition = await this.inspectGitWorktreePostcondition(root, target, branch);
6576
+ if (
6577
+ postcondition.proven
6578
+ && postcondition.exists
6579
+ && postcondition.registered
6580
+ && postcondition.branchMatches
6581
+ ) {
6582
+ return commitSuccess();
6583
+ }
6584
+ if (operation.state === 'unknown') {
6585
+ throw new Error(operation.error || 'Permanent worktree creation has an uncertain outcome and will not be replayed automatically');
6586
+ }
6587
+ if (
6588
+ !postcondition.proven
6589
+ || postcondition.exists
6590
+ || postcondition.registered
6591
+ || postcondition.branchExists
6592
+ ) {
6593
+ const detail = postcondition.error || 'Permanent worktree creation has a partial or unverifiable result';
6594
+ this.commitPersistentProjectOperation(operation, 'unknown', null, detail);
6595
+ throw new Error(`${detail}; the operation will not be replayed automatically`);
6025
6596
  }
6026
- suffix += 1;
6027
6597
  }
6028
6598
 
6029
- throw new Error('Unable to allocate a permanent worktree name');
6599
+ try {
6600
+ await execFileAsync('git', ['-C', root, 'worktree', 'add', '-b', branch, target, 'HEAD'], {
6601
+ timeout: 60000,
6602
+ maxBuffer: 1024 * 1024 * 4,
6603
+ });
6604
+ } catch (error) {
6605
+ const postcondition = await this.inspectGitWorktreePostcondition(root, target, branch);
6606
+ if (
6607
+ postcondition.proven
6608
+ && postcondition.exists
6609
+ && postcondition.registered
6610
+ && postcondition.branchMatches
6611
+ ) {
6612
+ return commitSuccess();
6613
+ }
6614
+ const message = error && error.stderr ? String(error.stderr).trim() : '';
6615
+ const detail = message || 'Failed to create permanent git worktree';
6616
+ if (operation) {
6617
+ const state = postcondition.proven
6618
+ && !postcondition.exists
6619
+ && !postcondition.registered
6620
+ && !postcondition.branchExists
6621
+ ? 'failed'
6622
+ : 'unknown';
6623
+ this.commitPersistentProjectOperation(operation, state, null, detail);
6624
+ }
6625
+ throw new Error(detail, { cause: error });
6626
+ }
6627
+
6628
+ const postcondition = await this.inspectGitWorktreePostcondition(root, target, branch);
6629
+ if (
6630
+ !postcondition.proven
6631
+ || !postcondition.exists
6632
+ || !postcondition.registered
6633
+ || !postcondition.branchMatches
6634
+ ) {
6635
+ const detail = postcondition.error || 'Permanent worktree creation could not be proven';
6636
+ if (operation) this.commitPersistentProjectOperation(operation, 'unknown', null, detail);
6637
+ throw new Error(`${detail}; the operation will not be replayed automatically`);
6638
+ }
6639
+ return commitSuccess();
6030
6640
  }
6031
6641
 
6032
6642
  async rollbackPermanentWorktree(created) {
@@ -6093,6 +6703,8 @@ class AgentManager extends EventEmitter {
6093
6703
  }
6094
6704
 
6095
6705
  try {
6706
+ const worktrees = await this.listGitWorktrees(resolvedWorkspace);
6707
+ const sourceWorkspace = worktrees[0]?.workspace || resolvedWorkspace;
6096
6708
  const { stdout } = await execFileAsync('git', ['-C', resolvedWorkspace, 'status', '--porcelain', '--untracked-files=all'], {
6097
6709
  timeout: 30000,
6098
6710
  maxBuffer: 1024 * 1024 * 4,
@@ -6100,6 +6712,7 @@ class AgentManager extends EventEmitter {
6100
6712
  const dirtyEntries = statusEntriesFromPorcelain(stdout);
6101
6713
  return {
6102
6714
  workspace: resolvedWorkspace,
6715
+ sourceWorkspace,
6103
6716
  dirtyEntries,
6104
6717
  requiresForce: dirtyEntries.length > 0,
6105
6718
  };
@@ -6151,21 +6764,118 @@ class AgentManager extends EventEmitter {
6151
6764
  const workspaceKey = canonicalWorkspacePath(this.expandWorkspacePath(workspace));
6152
6765
  if (!workspaceKey) return this.deleteForkWorktreeProjectAdmitted(workspace, options);
6153
6766
  const inFlight = this.projectWorkspaceDeleteAdmissions.get(workspaceKey);
6154
- if (inFlight) return inFlight;
6767
+ const requestId = String(options.requestId || '').trim();
6768
+ if (inFlight) {
6769
+ if (requestId && inFlight.requestId === requestId) return inFlight.promise;
6770
+ return inFlight.promise
6771
+ .catch(() => {})
6772
+ .then(() => this.deleteForkWorktreeProject(workspace, options));
6773
+ }
6155
6774
 
6156
- const operation = this.deleteForkWorktreeProjectAdmitted(workspace, options);
6157
- this.projectWorkspaceDeleteAdmissions.set(workspaceKey, operation);
6158
- void operation.finally(() => {
6159
- if (this.projectWorkspaceDeleteAdmissions.get(workspaceKey) === operation) {
6775
+ const promise = this.deleteForkWorktreeProjectAdmitted(workspace, options);
6776
+ const admission = { requestId, promise };
6777
+ this.projectWorkspaceDeleteAdmissions.set(workspaceKey, admission);
6778
+ void promise.finally(() => {
6779
+ if (this.projectWorkspaceDeleteAdmissions.get(workspaceKey) === admission) {
6160
6780
  this.projectWorkspaceDeleteAdmissions.delete(workspaceKey);
6161
6781
  }
6162
6782
  }).catch(() => {});
6163
- return operation;
6783
+ return promise;
6164
6784
  }
6165
6785
 
6166
6786
  async deleteForkWorktreeProjectAdmitted(workspace, options = {}) {
6167
6787
  await this.whenRecovered();
6168
6788
  const workspaceKey = canonicalWorkspacePath(this.expandWorkspacePath(workspace));
6789
+ const requestId = String(options.requestId || '').trim().slice(0, 160);
6790
+ const signature = projectOperationSignature({
6791
+ force: options.force === true,
6792
+ type: 'delete-worktree',
6793
+ workspace: workspaceKey,
6794
+ });
6795
+ const existingOperation = requestId && typeof this.configManager?.getProjectOperation === 'function'
6796
+ ? this.configManager.getProjectOperation(requestId)
6797
+ : null;
6798
+ if (
6799
+ existingOperation
6800
+ && (
6801
+ existingOperation.type !== 'delete-worktree'
6802
+ || existingOperation.signature !== signature
6803
+ )
6804
+ ) {
6805
+ return { workspace: workspaceKey, error: `Project operation request ${requestId} was already used for different parameters` };
6806
+ }
6807
+ if (existingOperation?.state === 'succeeded' && existingOperation.result) {
6808
+ const settings = this.configManager.getSettings?.() || {};
6809
+ return {
6810
+ ...existingOperation.result,
6811
+ deduplicated: true,
6812
+ projectWorkspaces: settings.projectWorkspaces || [],
6813
+ pinnedProjectWorkspaces: settings.pinnedProjectWorkspaces || [],
6814
+ };
6815
+ }
6816
+ let operation = existingOperation;
6817
+ const storedSourceWorkspace = String(operation?.request?.sourceWorkspace || '');
6818
+ const commitDeleted = (baseResult) => {
6819
+ const result = { ...baseResult, ...(requestId ? { requestId } : {}) };
6820
+ if (!operation) return result;
6821
+ const committed = this.commitPersistentProjectOperation(
6822
+ operation,
6823
+ 'succeeded',
6824
+ result,
6825
+ '',
6826
+ { removeWorkspace: workspaceKey },
6827
+ );
6828
+ return {
6829
+ ...result,
6830
+ projectWorkspaces: committed.projectWorkspaces,
6831
+ pinnedProjectWorkspaces: committed.pinnedProjectWorkspaces,
6832
+ };
6833
+ };
6834
+ if (operation && ['pending', 'unknown'].includes(operation.state) && storedSourceWorkspace) {
6835
+ const postcondition = await this.inspectGitWorktreePostcondition(
6836
+ storedSourceWorkspace,
6837
+ operation.request.workspace || workspaceKey,
6838
+ );
6839
+ if (postcondition.proven && !postcondition.exists && !postcondition.registered) {
6840
+ try {
6841
+ return commitDeleted({
6842
+ workspace: workspaceKey,
6843
+ deleted: true,
6844
+ forced: operation.request.force === true,
6845
+ archivedAgentIds: [],
6846
+ removedMainPageSessionKeys: [],
6847
+ });
6848
+ } catch (error) {
6849
+ return {
6850
+ workspace: workspaceKey,
6851
+ deleted: true,
6852
+ retryable: true,
6853
+ error: `Worktree was deleted, but Project operation commit failed: ${error.message || error}`,
6854
+ };
6855
+ }
6856
+ }
6857
+ if (operation.state === 'unknown') {
6858
+ return {
6859
+ workspace: workspaceKey,
6860
+ error: operation.error || 'Worktree deletion has an uncertain outcome and will not be replayed automatically',
6861
+ uncertain: true,
6862
+ };
6863
+ }
6864
+ if (!postcondition.proven || postcondition.exists !== postcondition.registered) {
6865
+ const detail = postcondition.error || 'Worktree deletion has a partial or unverifiable result';
6866
+ try {
6867
+ this.commitPersistentProjectOperation(operation, 'unknown', null, detail);
6868
+ } catch {
6869
+ // The previously persisted pending intent still prevents blind replay.
6870
+ }
6871
+ return { workspace: workspaceKey, error: `${detail}; the operation will not be replayed automatically`, uncertain: true };
6872
+ }
6873
+ } else if (operation && !['pending', 'unknown'].includes(operation.state)) {
6874
+ return {
6875
+ workspace: workspaceKey,
6876
+ error: operation.error || `Project operation ${requestId} already finished with state ${operation.state}`,
6877
+ };
6878
+ }
6169
6879
  const relatedStarts = workspaceKey
6170
6880
  ? [...this.agentStartAdmissions.values()].filter(admission => (
6171
6881
  !admission.workspaceKey
@@ -6193,6 +6903,16 @@ class AgentManager extends EventEmitter {
6193
6903
  };
6194
6904
  }
6195
6905
 
6906
+ if (!operation) {
6907
+ const admission = this.persistentProjectOperation(requestId, 'delete-worktree', signature, {
6908
+ workspace: inspected.workspace,
6909
+ sourceWorkspace: inspected.sourceWorkspace,
6910
+ force: options.force === true,
6911
+ });
6912
+ if (admission.error) return { ...inspected, error: admission.error };
6913
+ operation = admission.operation;
6914
+ }
6915
+
6196
6916
  const archivedAgentIds = [];
6197
6917
  const removedMainPageSessionKeys = [];
6198
6918
  const projectAgents = this.agentsForProjectWorkspace(inspected.workspace);
@@ -6220,25 +6940,227 @@ class AgentManager extends EventEmitter {
6220
6940
  maxBuffer: 1024 * 1024 * 4,
6221
6941
  });
6222
6942
  } catch (error) {
6943
+ const postcondition = await this.inspectGitWorktreePostcondition(
6944
+ inspected.sourceWorkspace,
6945
+ inspected.workspace,
6946
+ );
6947
+ if (postcondition.proven && !postcondition.exists && !postcondition.registered) {
6948
+ try {
6949
+ return commitDeleted({
6950
+ workspace: inspected.workspace,
6951
+ deleted: true,
6952
+ forced: options.force === true,
6953
+ archivedAgentIds,
6954
+ removedMainPageSessionKeys: Array.from(new Set(removedMainPageSessionKeys)),
6955
+ });
6956
+ } catch (commitError) {
6957
+ return {
6958
+ ...inspected,
6959
+ deleted: true,
6960
+ retryable: true,
6961
+ error: `Worktree was deleted, but Project operation commit failed: ${commitError.message || commitError}`,
6962
+ archivedAgentIds,
6963
+ removedMainPageSessionKeys: Array.from(new Set(removedMainPageSessionKeys)),
6964
+ };
6965
+ }
6966
+ }
6223
6967
  const message = error && error.stderr ? String(error.stderr).trim() : '';
6968
+ const detail = message || 'Failed to delete git worktree';
6969
+ if (operation) {
6970
+ const state = postcondition.proven && postcondition.exists && postcondition.registered
6971
+ ? 'failed'
6972
+ : 'unknown';
6973
+ try {
6974
+ this.commitPersistentProjectOperation(operation, state, null, detail);
6975
+ } catch {
6976
+ // Preserve the earlier durable pending intent when the result write fails.
6977
+ }
6978
+ }
6224
6979
  return {
6225
6980
  ...inspected,
6226
- error: message || 'Failed to delete git worktree',
6981
+ error: detail,
6982
+ ...(postcondition.proven && postcondition.exists && postcondition.registered
6983
+ ? {}
6984
+ : { uncertain: true }),
6227
6985
  archivedAgentIds,
6228
6986
  removedMainPageSessionKeys: Array.from(new Set(removedMainPageSessionKeys)),
6229
6987
  };
6230
6988
  }
6231
6989
 
6232
- return {
6233
- workspace: inspected.workspace,
6234
- deleted: true,
6235
- forced: options.force === true,
6236
- archivedAgentIds,
6237
- removedMainPageSessionKeys: Array.from(new Set(removedMainPageSessionKeys)),
6238
- };
6990
+ const postcondition = await this.inspectGitWorktreePostcondition(
6991
+ inspected.sourceWorkspace,
6992
+ inspected.workspace,
6993
+ );
6994
+ if (!postcondition.proven || postcondition.exists || postcondition.registered) {
6995
+ const detail = postcondition.error || 'Worktree deletion could not be proven';
6996
+ if (operation) {
6997
+ try {
6998
+ this.commitPersistentProjectOperation(operation, 'unknown', null, detail);
6999
+ } catch {
7000
+ // Preserve the earlier durable pending intent when the result write fails.
7001
+ }
7002
+ }
7003
+ return {
7004
+ ...inspected,
7005
+ error: `${detail}; the operation will not be replayed automatically`,
7006
+ uncertain: true,
7007
+ archivedAgentIds,
7008
+ removedMainPageSessionKeys: Array.from(new Set(removedMainPageSessionKeys)),
7009
+ };
7010
+ }
7011
+ try {
7012
+ return commitDeleted({
7013
+ workspace: inspected.workspace,
7014
+ deleted: true,
7015
+ forced: options.force === true,
7016
+ archivedAgentIds,
7017
+ removedMainPageSessionKeys: Array.from(new Set(removedMainPageSessionKeys)),
7018
+ });
7019
+ } catch (error) {
7020
+ return {
7021
+ ...inspected,
7022
+ deleted: true,
7023
+ retryable: true,
7024
+ error: `Worktree was deleted, but Project operation commit failed: ${error.message || error}`,
7025
+ archivedAgentIds,
7026
+ removedMainPageSessionKeys: Array.from(new Set(removedMainPageSessionKeys)),
7027
+ };
7028
+ }
7029
+ }
7030
+
7031
+ async replayPersistentForkRequest(agent, requestId, signature) {
7032
+ const requestKey = `fork-request:${requestId}`;
7033
+ const operation = lifecycleJournal(agent).entries.find(candidate => (
7034
+ candidate.type === 'fork' && candidate.requestKey === requestKey
7035
+ ));
7036
+ if (!operation) return null;
7037
+ if (operation.request?.signature && operation.request.signature !== signature) {
7038
+ return { error: `Fork request ${requestId} was already used for different parameters` };
7039
+ }
7040
+ if (operation.state === 'succeeded' && operation.result) {
7041
+ return { ...operation.result, deduplicated: true };
7042
+ }
7043
+ if (TERMINAL_OPERATION_STATES.has(operation.state)) {
7044
+ return { error: operation.error || `Fork request ${requestId} finished with state ${operation.state}` };
7045
+ }
7046
+
7047
+ const children = typeof this.configManager?.listAgentSessionRecords === 'function'
7048
+ ? this.configManager.listAgentSessionRecords().filter(record => (
7049
+ record?.parentAgentId === agent.id
7050
+ && record?.forkRequestId === requestId
7051
+ && record?.archived !== true
7052
+ ))
7053
+ : [];
7054
+ if (children.length === 1) {
7055
+ const child = children[0];
7056
+ const request = operation.request || {};
7057
+ const result = {
7058
+ agentId: child.runtimeAgentId,
7059
+ workspace: child.projectWorkspace || child.cwd || '',
7060
+ mode: request.mode || 'same-worktree',
7061
+ ...(request.targetRuntime ? { targetRuntime: request.targetRuntime } : {}),
7062
+ ...(child.providerSessionId ? { providerSessionId: child.providerSessionId } : {}),
7063
+ requestId,
7064
+ };
7065
+ try {
7066
+ this.completePersistentAgentOperation(agent, operation.id, result);
7067
+ return { ...result, deduplicated: true, reconciled: true };
7068
+ } catch (error) {
7069
+ return {
7070
+ ...result,
7071
+ error: `Fork exists, but its result could not be committed: ${error.message || error}`,
7072
+ retryable: true,
7073
+ };
7074
+ }
7075
+ }
7076
+
7077
+ const detail = children.length > 1
7078
+ ? `Fork request ${requestId} has multiple child Agent records and cannot be reconciled safely`
7079
+ : `Fork request ${requestId} has an uncertain outcome and will not be replayed automatically`;
7080
+ if (operation.state !== 'blocked') {
7081
+ try {
7082
+ this.transitionPersistentAgentOperation(agent, operation.id, 'blocked', detail);
7083
+ } catch (error) {
7084
+ return { error: `${detail}; failed to persist blocked state: ${error.message || error}`, uncertain: true };
7085
+ }
7086
+ }
7087
+ return { error: detail, uncertain: true };
6239
7088
  }
6240
7089
 
6241
7090
  async forkAgent(agentId, mode = 'same-worktree', options = {}) {
7091
+ const requestId = String(options.requestId || '').trim().slice(0, 160);
7092
+ if (!requestId) return this.forkAgentUntracked(agentId, mode, options);
7093
+ if (!/^[A-Za-z0-9._:-]{1,160}$/.test(requestId)) {
7094
+ return { error: 'Fork requires a valid requestId' };
7095
+ }
7096
+ await this.whenRecovered();
7097
+ const agent = this.agents.get(agentId);
7098
+ if (!agent) return { error: 'Agent not found' };
7099
+ const signature = projectOperationSignature({
7100
+ agentRecordId: agent.agentRecordId || agent.persistentSessionId || '',
7101
+ expectedRevision: Number.isSafeInteger(options.expectedRevision) ? options.expectedRevision : null,
7102
+ mode,
7103
+ targetRuntime: options.targetRuntime || '',
7104
+ });
7105
+ const key = `fork:${requestId}:${signature}`;
7106
+ const inFlight = this.agentLifecycleOperations.get(agentId);
7107
+ if (inFlight?.key === key) return inFlight.promise;
7108
+ const replay = await this.replayPersistentForkRequest(agent, requestId, signature);
7109
+ if (replay) return replay;
7110
+ return this.runAgentLifecycleOperation(
7111
+ agentId,
7112
+ key,
7113
+ 'fork',
7114
+ 'fork',
7115
+ async lifecycleToken => {
7116
+ const admission = this.beginPersistentAgentOperation(
7117
+ agent,
7118
+ 'fork',
7119
+ `fork-request:${requestId}`,
7120
+ {
7121
+ signature,
7122
+ mode,
7123
+ targetRuntime: options.targetRuntime || '',
7124
+ expectedRevision: Number.isSafeInteger(options.expectedRevision) ? options.expectedRevision : null,
7125
+ },
7126
+ );
7127
+ if (admission.error) return { error: admission.error };
7128
+ const result = await this.forkAgentUntracked(agentId, mode, {
7129
+ ...options,
7130
+ requestId: '',
7131
+ forkRequestId: requestId,
7132
+ lifecycleToken,
7133
+ });
7134
+ if (result?.error) {
7135
+ try {
7136
+ this.transitionPersistentAgentOperation(
7137
+ agent,
7138
+ admission.operation.id,
7139
+ 'blocked',
7140
+ result.error,
7141
+ );
7142
+ } catch (error) {
7143
+ return { ...result, error: `${result.error}; failed to persist Fork outcome: ${error.message || error}` };
7144
+ }
7145
+ return { ...result, requestId, uncertain: true };
7146
+ }
7147
+ const committedResult = { ...result, requestId };
7148
+ try {
7149
+ this.completePersistentAgentOperation(agent, admission.operation.id, committedResult);
7150
+ return committedResult;
7151
+ } catch (error) {
7152
+ return {
7153
+ ...committedResult,
7154
+ error: `Fork was created, but its result could not be committed: ${error.message || error}`,
7155
+ retainedAgentId: result.agentId,
7156
+ retryable: true,
7157
+ };
7158
+ }
7159
+ },
7160
+ );
7161
+ }
7162
+
7163
+ async forkAgentUntracked(agentId, mode = 'same-worktree', options = {}) {
6242
7164
  await this.whenRecovered();
6243
7165
  if (this.disposing) {
6244
7166
  return { error: 'Farming is shutting down; Agent lifecycle changes are not accepted' };
@@ -6258,12 +7180,20 @@ class AgentManager extends EventEmitter {
6258
7180
  if (!Number.isSafeInteger(expectedRevision) || expectedRevision < 0) {
6259
7181
  return { error: 'Conversation Fork requires an exact transcript revision' };
6260
7182
  }
7183
+ if (options.lifecycleToken) {
7184
+ return this.performAcpConversationFork(
7185
+ agentId,
7186
+ expectedRevision,
7187
+ options.lifecycleToken,
7188
+ options.forkRequestId || '',
7189
+ );
7190
+ }
6261
7191
  return this.runAgentLifecycleOperation(
6262
7192
  agentId,
6263
7193
  `conversation-fork:${expectedRevision}`,
6264
7194
  'conversation-fork',
6265
7195
  'conversation fork',
6266
- lifecycleToken => this.performAcpConversationFork(agentId, expectedRevision, lifecycleToken),
7196
+ lifecycleToken => this.performAcpConversationFork(agentId, expectedRevision, lifecycleToken, ''),
6267
7197
  );
6268
7198
  }
6269
7199
  if (agent.providerSessionProvider === 'codex' && agent.providerSessionTemporary === true) {
@@ -6319,6 +7249,7 @@ class AgentManager extends EventEmitter {
6319
7249
  }, {
6320
7250
  wantsMain: false,
6321
7251
  parentAgentId: agent.id,
7252
+ forkRequestId: options.forkRequestId || '',
6322
7253
  task: agent.task ? `Fork: ${agent.task}` : `Fork of ${agent.command}`,
6323
7254
  workflowTemplate: agent.workflowTemplate || '',
6324
7255
  source: mode === 'new-worktree' ? 'ui-fork-new-worktree' : 'ui-fork-same-worktree',
@@ -6331,7 +7262,7 @@ class AgentManager extends EventEmitter {
6331
7262
  });
6332
7263
  }
6333
7264
 
6334
- async performAcpConversationFork(agentId, expectedRevision, lifecycleToken) {
7265
+ async performAcpConversationFork(agentId, expectedRevision, lifecycleToken, forkRequestId = '') {
6335
7266
  const agent = this.agents.get(agentId);
6336
7267
  if (!agent) return { error: 'Agent not found' };
6337
7268
  if (runtimeKind(agent) !== 'acp') {
@@ -6360,6 +7291,7 @@ class AgentManager extends EventEmitter {
6360
7291
  expectedRevision,
6361
7292
  lifecycleToken,
6362
7293
  acpSessionOptions,
7294
+ forkRequestId,
6363
7295
  });
6364
7296
  }
6365
7297
 
@@ -6423,6 +7355,7 @@ class AgentManager extends EventEmitter {
6423
7355
  const started = this.startAgent(command, workspace, finish, {
6424
7356
  wantsMain: false,
6425
7357
  parentAgentId: agent.id,
7358
+ forkRequestId,
6426
7359
  task: agent.task ? `Fork: ${agent.task}` : `Fork of ${agent.command}`,
6427
7360
  workflowTemplate: agent.workflowTemplate || '',
6428
7361
  source: 'ui-fork-acp-chat',
@@ -6467,6 +7400,7 @@ class AgentManager extends EventEmitter {
6467
7400
  expectedRevision,
6468
7401
  lifecycleToken,
6469
7402
  acpSessionOptions,
7403
+ forkRequestId,
6470
7404
  } = options;
6471
7405
  const command = getProviderAdapter(provider)?.executable || provider;
6472
7406
  let preparedSessionId = '';
@@ -6507,6 +7441,7 @@ class AgentManager extends EventEmitter {
6507
7441
  const started = this.startAgent(command, workspace, finish, {
6508
7442
  wantsMain: false,
6509
7443
  parentAgentId: agent.id,
7444
+ forkRequestId,
6510
7445
  task: agent.task ? `Fork: ${agent.task}` : `Fork of ${agent.command}`,
6511
7446
  workflowTemplate: agent.workflowTemplate || '',
6512
7447
  source: 'ui-fork-acp-chat',
@@ -6732,7 +7667,7 @@ class AgentManager extends EventEmitter {
6732
7667
  }
6733
7668
  let removedMainPageSessionKeys = [];
6734
7669
  try {
6735
- this.transitionPersistentAgentOperation(agent, operationId, 'succeeded', '', {
7670
+ this.transitionPersistentAgentOperation(agent, operationId, 'provider-archive-pending', '', {
6736
7671
  visibleOnMainPage: false,
6737
7672
  archived: true,
6738
7673
  archivedAt: Date.now(),
@@ -6750,6 +7685,8 @@ class AgentManager extends EventEmitter {
6750
7685
  removedMainPageSessionKeys,
6751
7686
  };
6752
7687
  }
7688
+ agent.archived = true;
7689
+ agent.archivedAt = Date.now();
6753
7690
  let metadataWarning = '';
6754
7691
  try {
6755
7692
  removedMainPageSessionKeys = this.removeMainPageProviderSessionsForAgents([agent]);
@@ -6759,7 +7696,7 @@ class AgentManager extends EventEmitter {
6759
7696
  }
6760
7697
 
6761
7698
  let historyWarning = '';
6762
- if (options.recordHistory !== false && !isEphemeralShellAgent(agent)) {
7699
+ if (!admission.joined && options.recordHistory !== false && !isEphemeralShellAgent(agent)) {
6763
7700
  const previousTaskHistory = this.taskHistory;
6764
7701
  try {
6765
7702
  this.recordTaskHistory(agent, {
@@ -6772,8 +7709,59 @@ class AgentManager extends EventEmitter {
6772
7709
  console.error(historyWarning);
6773
7710
  }
6774
7711
  }
7712
+ if (options.scheduleProviderArchive !== false) {
7713
+ const providerArchive = await this.archiveCodexProviderSession(agent);
7714
+ if (providerArchive?.error) {
7715
+ try {
7716
+ this.transitionPersistentAgentOperation(agent, operationId, 'blocked', providerArchive.error, {
7717
+ visibleOnMainPage: false,
7718
+ archived: true,
7719
+ runtimeAgentId: '',
7720
+ });
7721
+ } catch (error) {
7722
+ return {
7723
+ agentId,
7724
+ archived: true,
7725
+ stopped: true,
7726
+ retryable: true,
7727
+ operationId,
7728
+ removedMainPageSessionKeys,
7729
+ error: `Provider archive failed: ${providerArchive.error}; failed to persist blocked Archive: ${error.message || error}`,
7730
+ };
7731
+ }
7732
+ this.emit('update');
7733
+ return {
7734
+ agentId,
7735
+ archived: true,
7736
+ stopped: true,
7737
+ providerArchived: false,
7738
+ retryable: true,
7739
+ operationId,
7740
+ removedMainPageSessionKeys,
7741
+ error: `Provider archive failed: ${providerArchive.error}`,
7742
+ };
7743
+ }
7744
+ }
7745
+ try {
7746
+ this.transitionPersistentAgentOperation(agent, operationId, 'succeeded', '', {
7747
+ visibleOnMainPage: false,
7748
+ archived: true,
7749
+ runtimeAgentId: '',
7750
+ });
7751
+ } catch (error) {
7752
+ this.emit('update');
7753
+ return {
7754
+ agentId,
7755
+ archived: true,
7756
+ stopped: true,
7757
+ providerArchived: true,
7758
+ retryable: true,
7759
+ operationId,
7760
+ removedMainPageSessionKeys,
7761
+ error: `Provider archive succeeded, but terminal Archive result could not be saved: ${error.message || error}`,
7762
+ };
7763
+ }
6775
7764
  this.forgetStoppedAgentRecord(agentId);
6776
- if (options.scheduleProviderArchive !== false) this.scheduleCodexSessionArchive(agent);
6777
7765
  return {
6778
7766
  agentId,
6779
7767
  archived: true,
@@ -6788,14 +7776,14 @@ class AgentManager extends EventEmitter {
6788
7776
  };
6789
7777
  }
6790
7778
 
6791
- scheduleCodexSessionArchive(agent) {
7779
+ async archiveCodexProviderSession(agent) {
6792
7780
  if (
6793
7781
  !agent
6794
7782
  || agent.providerSessionProvider !== 'codex'
6795
7783
  || !agent.providerSessionId
6796
7784
  || agent.providerSessionTemporary === true
6797
7785
  ) {
6798
- return;
7786
+ return null;
6799
7787
  }
6800
7788
 
6801
7789
  const sessionId = agent.providerSessionId;
@@ -6805,26 +7793,23 @@ class AgentManager extends EventEmitter {
6805
7793
  workspace: agent.projectWorkspace || '',
6806
7794
  providerHomePath: agent.providerHomePath || '',
6807
7795
  };
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
- });
7796
+ try {
7797
+ const result = await this.enqueueCodexSessionMutation(
7798
+ sessionId,
7799
+ {
7800
+ providerHomeId: agent.providerHomeId || 'default',
7801
+ providerHomePath: agent.providerHomePath || '',
7802
+ },
7803
+ 'archive',
7804
+ () => this.archiveCodexSession(sessionId, session),
7805
+ true,
7806
+ );
7807
+ return result?.error ? { error: result.error } : { archived: true };
7808
+ } catch (error) {
7809
+ return { error: error.message || String(error) };
7810
+ }
6826
7811
  }
6827
-
7812
+
6828
7813
  admitPersistentDelete(agent, options = {}) {
6829
7814
  const activeOperation = activeLifecycleOperation(agent);
6830
7815
  if (