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
package/backend/server.js CHANGED
@@ -42,14 +42,17 @@ const { createControlRouter } = require('./control-api');
42
42
  const { WorkspaceFileService, WorkspaceFileError } = require('./workspace-file-service');
43
43
  const { createWorkspaceFileRouter, resolveWorkspaceRoot } = require('./workspace-file-router');
44
44
  const { WorkspaceRootRegistry, rootIdForPath } = require('./workspace-root-registry');
45
+ const {
46
+ BrowserResourceManager,
47
+ createBrowserRouter,
48
+ } = require('../extensions/browser/backend');
45
49
  const { UsageMonitor } = require('./usage-monitor');
46
50
  const { CodexContextWindowReader } = require('./codex-context-window');
47
51
  const { DEFAULT_MAX_TURNS: DEFAULT_CODEX_TRANSCRIPT_MAX_TURNS, readCodexTranscript } = require('./codex-transcript');
48
52
  const { AsyncCache } = require('./async-cache');
49
53
  const { getMainAgentSkillsCatalog } = require('./main-agent-skills');
50
- const { discoverSlashCommands } = require('./slash-command-discovery');
54
+ const { discoverAgentExtensions, discoverSlashCommands } = require('./slash-command-discovery');
51
55
  const { FarmingUpdateService } = require('./update-service');
52
- const { isRestartBlockingAgent } = require('./agent-activity');
53
56
  const { inputPartsFromMessage } = require('./input-parts');
54
57
  const { cleanupTerminalRuntime } = require('./terminal-runtime-cleanup');
55
58
  const { QrShareTicketStore, SHARE_TICKET_TTL_MS } = require('./qr-share-tickets');
@@ -61,11 +64,13 @@ const { ReviewSessionStore } = require('./review-session-store');
61
64
  const { ReviewSessionService } = require('./review-session-service');
62
65
  const { createReviewSessionRouter } = require('./review-session-router');
63
66
  const {
67
+ applyIndexHtmlAppearance,
64
68
  normalizeBasePath,
65
69
  routePath,
66
70
  rewriteIndexHtmlForBasePath,
67
71
  appendIndexHtmlAssetToken,
68
72
  } = require('./index-html');
73
+ const { decodeAcpTranscriptMedia } = require('./acp-transcript');
69
74
  const {
70
75
  coalesceSessionStream,
71
76
  deliverSessionStreamToClients,
@@ -129,6 +134,15 @@ async function requireAgentRecoveryForHttp(res) {
129
134
  const themeManager = new ThemeManager({ configDir: configManager.farmingDir });
130
135
  const workspaceFileService = new WorkspaceFileService();
131
136
  const workspaceRootRegistry = new WorkspaceRootRegistry(agentManager);
137
+ const browserResourceManager = new BrowserResourceManager({
138
+ configDir: configManager.farmingDir,
139
+ isEnabled: () => configManager.getSettings().browserExtensionEnabled === true,
140
+ getBrowserSettings: () => configManager.getSettings(),
141
+ });
142
+ const browserRuntimeRecoveryPromise = browserResourceManager.init().catch(error => {
143
+ console.warn('Failed to recover Browser runtimes:', error?.message || error);
144
+ return null;
145
+ });
132
146
  const updateService = new FarmingUpdateService({
133
147
  rootDir: path.join(__dirname, '..'),
134
148
  configDir: configManager.farmingDir,
@@ -246,6 +260,22 @@ function getAvailableAgentsForRequest() {
246
260
  supported: true,
247
261
  interactive: true,
248
262
  },
263
+ {
264
+ name: 'opencode',
265
+ command: 'opencode',
266
+ description: 'OpenCode - AI coding assistant',
267
+ category: 'coding',
268
+ supported: true,
269
+ interactive: true,
270
+ },
271
+ {
272
+ name: 'qoder',
273
+ command: 'qodercli',
274
+ description: 'Qoder - AI coding assistant',
275
+ category: 'coding',
276
+ supported: true,
277
+ interactive: true,
278
+ },
249
279
  {
250
280
  name: 'bash',
251
281
  command: 'bash',
@@ -527,6 +557,7 @@ app.use(BASE_PATH || '/', express.static(staticAppDir, { index: false }));
527
557
  app.use(routePath(BASE_PATH, '/api/files'), createWorkspaceFileRouter(agentManager, workspaceFileService, {
528
558
  rootRegistry: workspaceRootRegistry,
529
559
  }));
560
+ app.use(routePath(BASE_PATH, '/api/browsers'), createBrowserRouter(browserResourceManager, workspaceRootRegistry));
530
561
 
531
562
  app.use(routePath(BASE_PATH, '/api/review-sessions'), createReviewSessionRouter(reviewSessionService));
532
563
  app.use(routePath(BASE_PATH, '/api/reviews'), createReviewDiffRouter(reviewDiffService, reviewSessionService));
@@ -564,7 +595,10 @@ app.get([
564
595
  }
565
596
  res.setHeader('Content-Type', 'text/html; charset=utf-8');
566
597
  res.setHeader('Cache-Control', 'no-cache');
567
- const rewrittenHtml = rewriteIndexHtmlForBasePath(html, BASE_PATH);
598
+ const rewrittenHtml = applyIndexHtmlAppearance(
599
+ rewriteIndexHtmlForBasePath(html, BASE_PATH),
600
+ configManager.getSettings().appearance
601
+ );
568
602
  const requestUrl = new URL(req.url || '/', `http://${req.headers.host || 'localhost'}`);
569
603
  const requestToken = requestUrl.searchParams.has('token') ? tokenAuth.extractToken(req) : '';
570
604
  const htmlWithAssetToken = authEnabled && requestToken && tokenAuth.verify(requestToken)
@@ -603,6 +637,40 @@ app.get(routePath(BASE_PATH, '/api/skills'), (_req, res) => {
603
637
  res.json({ skills: getMainAgentSkillsCatalog() });
604
638
  });
605
639
 
640
+ app.get(routePath(BASE_PATH, '/api/agent-extensions'), (_req, res) => {
641
+ const agents = getAvailableAgentsForRequest()
642
+ .filter(agent => agent.category === 'coding')
643
+ .map(agent => {
644
+ const provider = String(agent.name || agent.command || '').trim().toLowerCase();
645
+ const configuredHomes = configManager.getAgentHomes(provider);
646
+ const homes = configuredHomes.length > 0
647
+ ? configuredHomes
648
+ : [{ id: 'default', path: '' }];
649
+ return {
650
+ id: provider,
651
+ name: agent.name,
652
+ description: agent.description || '',
653
+ discoverySupported: provider === 'codex' || provider === 'claude',
654
+ homes: homes.map(home => ({
655
+ id: home.id,
656
+ extensions: discoverAgentExtensions({
657
+ provider,
658
+ providerHomePath: home.path,
659
+ }).map(extension => ({
660
+ id: extension.command,
661
+ command: extension.command,
662
+ name: extension.label,
663
+ description: extension.description,
664
+ kind: extension.source === 'custom' ? 'command' : extension.source || 'command',
665
+ scope: extension.scope || '',
666
+ })),
667
+ })),
668
+ };
669
+ });
670
+ res.setHeader('Cache-Control', 'no-store');
671
+ res.json({ agents });
672
+ });
673
+
606
674
  app.get(routePath(BASE_PATH, '/api/slash-commands'), (req, res) => {
607
675
  const provider = typeof req.query.provider === 'string' ? req.query.provider : '';
608
676
  const workspace = typeof req.query.workspace === 'string' ? req.query.workspace : '';
@@ -805,26 +873,10 @@ app.post(routePath(BASE_PATH, '/api/codex/context-windows'), express.json(), asy
805
873
  }
806
874
  });
807
875
 
808
- function blockingUpdateAgents() {
809
- return agentManager.getState().agents
810
- .filter(isRestartBlockingAgent)
811
- .map(agent => ({
812
- id: agent.id,
813
- command: agent.command,
814
- task: agent.task || agent.customTitle || agent.sessionTitle || '',
815
- cwd: agent.cwd,
816
- }));
817
- }
818
-
819
876
  app.get(routePath(BASE_PATH, '/api/update'), async (req, res) => {
820
877
  try {
821
878
  const update = await updateService.check({ force: req.query.force === '1' });
822
- res.json({
823
- update: {
824
- ...update,
825
- blockingAgents: blockingUpdateAgents(),
826
- },
827
- });
879
+ res.json({ update });
828
880
  } catch (error) {
829
881
  res.status(502).json({ error: error.message || 'Failed to check for updates' });
830
882
  }
@@ -835,31 +887,16 @@ app.post(routePath(BASE_PATH, '/api/update/install'), express.json(), async (req
835
887
  const state = await updateService.startInstall({
836
888
  assetName: req.body && typeof req.body.assetName === 'string' ? req.body.assetName : '',
837
889
  });
838
- res.status(202).json({
839
- update: {
840
- state,
841
- blockingAgents: blockingUpdateAgents(),
842
- },
843
- });
890
+ res.status(202).json({ update: { state } });
844
891
  } catch (error) {
845
892
  res.status(500).json({ error: error.message || 'Failed to start update' });
846
893
  }
847
894
  });
848
895
 
849
896
  app.post(routePath(BASE_PATH, '/api/update/restart'), express.json(), async (req, res) => {
850
- const blockers = blockingUpdateAgents();
851
- const force = req.body && req.body.force === true;
852
- if (blockers.length > 0 && !force) {
853
- res.status(409).json({
854
- error: 'Cannot restart for update while non-recoverable project agents are running',
855
- blockingAgents: blockers,
856
- });
857
- return;
858
- }
859
-
860
897
  try {
861
898
  const state = await updateService.applyPreparedUpdate();
862
- res.status(202).json({ update: { state, blockingAgents: blockers } });
899
+ res.status(202).json({ update: { state } });
863
900
  } catch (error) {
864
901
  res.status(500).json({ error: error.message || 'Failed to restart for update' });
865
902
  }
@@ -1142,8 +1179,17 @@ app.get(routePath(BASE_PATH, '/api/agents/:agentId/acp-transcript'), async (req,
1142
1179
  ? Math.min(MAX_CODEX_TRANSCRIPT_TURNS, Math.max(20, requestedMaxTurns))
1143
1180
  : DEFAULT_CODEX_TRANSCRIPT_MAX_TURNS;
1144
1181
  const requestedRevision = Number.parseInt(String(req.query.sinceRevision || ''), 10);
1182
+ const externalMedia = req.query.media === 'external-v1';
1145
1183
  res.json({ transcript: agentManager.getAcpTranscript(req.params.agentId, {
1146
1184
  maxTurns,
1185
+ ...(externalMedia
1186
+ ? {
1187
+ mediaPathPrefix: routePath(
1188
+ BASE_PATH,
1189
+ `/api/agents/${encodeURIComponent(req.params.agentId)}/acp-media`
1190
+ ),
1191
+ }
1192
+ : {}),
1147
1193
  ...(Number.isFinite(requestedRevision) && requestedRevision >= 0
1148
1194
  ? { sinceRevision: requestedRevision }
1149
1195
  : {}),
@@ -1154,6 +1200,32 @@ app.get(routePath(BASE_PATH, '/api/agents/:agentId/acp-transcript'), async (req,
1154
1200
  }
1155
1201
  });
1156
1202
 
1203
+ app.get(routePath(BASE_PATH, '/api/agents/:agentId/acp-media/:entryId/:mediaId'), (req, res) => {
1204
+ try {
1205
+ const media = agentManager.getAcpTranscriptMedia(
1206
+ req.params.agentId,
1207
+ req.params.entryId,
1208
+ req.params.mediaId
1209
+ );
1210
+ const decoded = decodeAcpTranscriptMedia(media);
1211
+ if (!decoded) {
1212
+ res.status(415).json({ error: 'unsupported ACP transcript media' });
1213
+ return;
1214
+ }
1215
+ res.setHeader('Cache-Control', 'private, max-age=31536000, immutable');
1216
+ res.setHeader('X-Content-Type-Options', 'nosniff');
1217
+ res.type(decoded.mimeType).send(decoded.content);
1218
+ } catch (error) {
1219
+ const message = error && error.message ? error.message : 'Failed to read ACP transcript media';
1220
+ const status = message === 'Agent not found'
1221
+ || message === 'ACP transcript entry not found'
1222
+ || message === 'ACP transcript media not found'
1223
+ ? 404
1224
+ : 409;
1225
+ res.status(status).json({ error: message });
1226
+ }
1227
+ });
1228
+
1157
1229
  app.get(routePath(BASE_PATH, '/api/agents/:agentId/acp-tool-details/:toolCallId'), async (req, res) => {
1158
1230
  try {
1159
1231
  res.json(agentManager.getAcpToolDetail(req.params.agentId, req.params.toolCallId));
@@ -1554,7 +1626,13 @@ app.post(routePath(BASE_PATH, '/api/agents/:agentId/fork'), express.json(), asyn
1554
1626
  res.status(400).json({ error: 'Unsupported Fork target runtime' });
1555
1627
  return;
1556
1628
  }
1629
+ const requestId = typeof req.body?.requestId === 'string' ? req.body.requestId.trim() : '';
1630
+ if (!/^[A-Za-z0-9._:-]{1,160}$/.test(requestId)) {
1631
+ res.status(400).json({ error: 'Fork requires a valid requestId' });
1632
+ return;
1633
+ }
1557
1634
  const result = await agentManager.forkAgent(req.params.agentId, mode, {
1635
+ requestId,
1558
1636
  ...(targetRuntime ? { targetRuntime } : {}),
1559
1637
  ...(Number.isSafeInteger(req.body?.expectedRevision)
1560
1638
  ? { expectedRevision: req.body.expectedRevision }
@@ -1562,7 +1640,7 @@ app.post(routePath(BASE_PATH, '/api/agents/:agentId/fork'), express.json(), asyn
1562
1640
  });
1563
1641
  if (result.error) {
1564
1642
  const status = result.error === 'Agent not found' ? 404 : 400;
1565
- res.status(status).json({ error: result.error });
1643
+ res.status(status).json(result);
1566
1644
  return;
1567
1645
  }
1568
1646
 
@@ -1571,32 +1649,12 @@ app.post(routePath(BASE_PATH, '/api/agents/:agentId/fork'), express.json(), asyn
1571
1649
  result.projectWorkspaces = membership.projectWorkspaces;
1572
1650
  result.pinnedProjectWorkspaces = membership.pinnedProjectWorkspaces;
1573
1651
  } catch (error) {
1574
- let rollbackError = '';
1575
- try {
1576
- if (mode === 'new-worktree') {
1577
- const rollback = await agentManager.deleteForkWorktreeProject(result.workspace, { force: true });
1578
- if (!rollback?.deleted || rollback.cleanupError) {
1579
- rollbackError = rollback?.error || rollback?.cleanupError || 'Failed to delete the fork worktree';
1580
- }
1581
- } else {
1582
- const rollback = await agentManager.archiveAgent(result.agentId, {
1583
- reason: 'project-mount-failed',
1584
- recordHistory: false,
1585
- requireEngineExit: true,
1586
- scheduleProviderArchive: false,
1587
- });
1588
- if (rollback?.error) rollbackError = rollback.error;
1589
- }
1590
- } catch (cleanupError) {
1591
- rollbackError = cleanupError?.message || String(cleanupError);
1592
- }
1593
1652
  broadcastState();
1594
1653
  const mountError = error.message || 'Failed to create Project';
1595
1654
  res.status(500).json({
1596
- error: rollbackError
1597
- ? `${mountError}. Rollback failed: ${rollbackError}`
1598
- : mountError,
1599
- ...(rollbackError ? { rollbackError } : {}),
1655
+ ...result,
1656
+ error: `${mountError}. Retry the same Fork request to reconcile Project membership.`,
1657
+ retryable: true,
1600
1658
  });
1601
1659
  return;
1602
1660
  }
@@ -1676,31 +1734,26 @@ app.patch(routePath(BASE_PATH, '/api/projects/name'), express.json(), (req, res)
1676
1734
  });
1677
1735
 
1678
1736
  app.post(routePath(BASE_PATH, '/api/projects/create-worktree'), express.json(), async (req, res) => {
1679
- let created = null;
1737
+ const requestId = typeof req.body?.requestId === 'string' ? req.body.requestId.trim() : '';
1738
+ if (!/^[A-Za-z0-9._:-]{1,160}$/.test(requestId)) {
1739
+ res.status(400).json({ error: 'Project worktree creation requires a valid requestId' });
1740
+ return;
1741
+ }
1680
1742
  try {
1681
1743
  const root = resolveProjectActionRoot(req.body?.rootId);
1682
- created = await agentManager.createPermanentWorktree(root.canonicalPath);
1683
- const membership = configManager.mountProjectWorkspace(created.workspace);
1744
+ const created = await agentManager.createPermanentWorktree(root.canonicalPath, { requestId });
1684
1745
  broadcastState();
1685
1746
  res.status(201).json({
1686
- workspace: created.workspace,
1687
- branch: created.branch,
1688
- projectWorkspaces: membership.projectWorkspaces,
1689
- pinnedProjectWorkspaces: membership.pinnedProjectWorkspaces,
1747
+ ...created,
1690
1748
  });
1691
1749
  } catch (error) {
1692
- let rollbackError = '';
1693
- if (created) {
1694
- const rollback = await agentManager.rollbackPermanentWorktree(created);
1695
- if (rollback?.rolledBack === false) rollbackError = rollback.error || 'Unknown rollback failure';
1696
- }
1697
1750
  const status = error instanceof WorkspaceFileError ? error.statusCode : 400;
1698
- const createError = error.message || 'Failed to create permanent worktree';
1751
+ const operation = configManager.getProjectOperation?.(requestId);
1699
1752
  res.status(status).json({
1700
- error: rollbackError
1701
- ? `${createError}. Rollback failed: ${rollbackError}`
1702
- : createError,
1703
- ...(rollbackError ? { rollbackError } : {}),
1753
+ error: error.message || 'Failed to create permanent worktree',
1754
+ requestId,
1755
+ ...(operation?.state === 'pending' ? { retryable: true } : {}),
1756
+ ...(['unknown', 'blocked'].includes(operation?.state) ? { uncertain: true } : {}),
1704
1757
  });
1705
1758
  }
1706
1759
  });
@@ -1709,45 +1762,26 @@ app.post(routePath(BASE_PATH, '/api/projects/delete-worktree'), express.json(),
1709
1762
  if (!await requireAgentRecoveryForHttp(res)) return;
1710
1763
  const body = req.body || {};
1711
1764
  const workspace = typeof body.workspace === 'string' ? body.workspace : '';
1712
- const inspected = await agentManager.inspectForkWorktreeProject(workspace);
1713
- if (inspected.error || (inspected.requiresForce && body.force !== true)) {
1714
- if (inspected.requiresForce) {
1715
- res.status(409).json({
1716
- ...inspected,
1717
- error: 'Worktree has uncommitted or untracked files',
1718
- });
1719
- return;
1720
- }
1721
- const status = inspected.error === 'Workspace not found' || inspected.error === 'Workspace is required' ? 404 : 400;
1722
- res.status(status).json(inspected);
1765
+ const requestId = typeof body.requestId === 'string' ? body.requestId.trim() : '';
1766
+ if (!/^[A-Za-z0-9._:-]{1,160}$/.test(requestId)) {
1767
+ res.status(400).json({ error: 'Project worktree deletion requires a valid requestId' });
1723
1768
  return;
1724
1769
  }
1725
1770
 
1726
- const result = await agentManager.deleteForkWorktreeProject(inspected.workspace, { force: body.force === true });
1771
+ const result = await agentManager.deleteForkWorktreeProject(workspace, {
1772
+ force: body.force === true,
1773
+ requestId,
1774
+ });
1727
1775
  if (result.error) {
1728
1776
  broadcastState();
1729
- const status = result.requiresForce ? 409 : 400;
1777
+ const status = result.requiresForce
1778
+ ? 409
1779
+ : (result.error === 'Workspace not found' || result.error === 'Workspace is required' ? 404 : 400);
1730
1780
  res.status(status).json(result);
1731
1781
  return;
1732
1782
  }
1733
-
1734
- let membership;
1735
- try {
1736
- membership = configManager.removeProjectWorkspace(inspected.workspace);
1737
- } catch (error) {
1738
- broadcastState();
1739
- res.status(500).json({
1740
- ...result,
1741
- error: `Worktree was deleted, but Project membership cleanup failed: ${error.message || error}`,
1742
- });
1743
- return;
1744
- }
1745
1783
  broadcastState();
1746
- res.json({
1747
- ...result,
1748
- projectWorkspaces: membership.projectWorkspaces,
1749
- pinnedProjectWorkspaces: membership.pinnedProjectWorkspaces,
1750
- });
1784
+ res.json(result);
1751
1785
  });
1752
1786
 
1753
1787
  app.post(routePath(BASE_PATH, '/api/codex/sessions/:sessionId/resume'), express.json(), async (req, res) => {
@@ -2232,12 +2266,64 @@ async function autoResumeMainPageAgentSessions() {
2232
2266
  }
2233
2267
  }
2234
2268
 
2235
- app.post(routePath(BASE_PATH, '/api/settings'), express.json(), (req, res) => {
2269
+ app.post(routePath(BASE_PATH, '/api/settings'), express.json(), async (req, res) => {
2236
2270
  const settingsPatch = { ...(req.body || {}) };
2237
2271
  delete settingsPatch.mainPageSessionKeys;
2238
2272
  delete settingsPatch.projectWorkspaces;
2239
2273
  delete settingsPatch.pinnedProjectWorkspaces;
2274
+ const browserConfigurationKeys = [
2275
+ 'browserSource',
2276
+ 'browserExecutablePath',
2277
+ 'browserExternalCdpUrl',
2278
+ ];
2279
+ const changesBrowserExtension = Object.prototype.hasOwnProperty.call(settingsPatch, 'browserExtensionEnabled');
2280
+ const changesBrowserConfiguration = browserConfigurationKeys.some(key =>
2281
+ Object.prototype.hasOwnProperty.call(settingsPatch, key)
2282
+ );
2283
+ const currentSettings = configManager.getSettings();
2284
+ const browserExtensionEnabled = settingsPatch.browserExtensionEnabled === true;
2285
+ const desiredBrowserEnabled = changesBrowserExtension
2286
+ ? browserExtensionEnabled
2287
+ : currentSettings.browserExtensionEnabled === true;
2288
+ if ((changesBrowserExtension && browserExtensionEnabled) || changesBrowserConfiguration) {
2289
+ const desiredSelection = browserResourceManager.browserSelection({
2290
+ ...currentSettings,
2291
+ ...settingsPatch,
2292
+ });
2293
+ const probe = await browserResourceManager.probeCapability(desiredSelection);
2294
+ if (
2295
+ (changesBrowserConfiguration || desiredBrowserEnabled)
2296
+ && (!probe.runtimeCapability || probe.runtimeCapability.error)
2297
+ ) {
2298
+ res.status(400).json({
2299
+ error: probe.runtimeCapability?.error
2300
+ || 'Install a Chromium-based browser or configure a loopback external CDP endpoint',
2301
+ code: 'BROWSER_EXECUTABLE_NOT_FOUND',
2302
+ });
2303
+ return;
2304
+ }
2305
+ }
2306
+ if (
2307
+ currentSettings.browserExtensionEnabled === true
2308
+ && (
2309
+ (changesBrowserExtension && !browserExtensionEnabled)
2310
+ || changesBrowserConfiguration
2311
+ )
2312
+ ) {
2313
+ try {
2314
+ await browserResourceManager.stopAll();
2315
+ } catch (error) {
2316
+ res.status(Number(error?.status) || 500).json({
2317
+ error: error?.message || 'Browser extension could not be disabled',
2318
+ code: error?.code || 'BROWSER_DISABLE_FAILED',
2319
+ });
2320
+ return;
2321
+ }
2322
+ }
2240
2323
  configManager.updateSettings(settingsPatch);
2324
+ if (changesBrowserExtension || changesBrowserConfiguration) {
2325
+ await browserResourceManager.refreshCapability();
2326
+ }
2241
2327
  agentSessionsCache.invalidate();
2242
2328
  res.json({
2243
2329
  success: true,
@@ -2299,6 +2385,20 @@ app.get(routePath(BASE_PATH, '/api/themes/:themeId'), (req, res) => {
2299
2385
 
2300
2386
  wss.on('connection', (ws, req) => {
2301
2387
  const url = new URL(req.url || '/', `http://${req.headers.host || 'localhost'}`);
2388
+ const viewerPrefix = routePath(BASE_PATH, '/api/browsers/');
2389
+ if (url.pathname.startsWith(viewerPrefix) && url.pathname.endsWith('/viewer')) {
2390
+ if (authEnabled && !tokenAuth.verifyWebSocket(req)) {
2391
+ ws.close(4001, 'Authentication required');
2392
+ return;
2393
+ }
2394
+ const encodedId = url.pathname.slice(viewerPrefix.length, -'/viewer'.length);
2395
+ try {
2396
+ browserResourceManager.attachViewer(decodeURIComponent(encodedId), ws);
2397
+ } catch (error) {
2398
+ ws.close(4004, error?.message || 'Browser resource not found');
2399
+ }
2400
+ return;
2401
+ }
2302
2402
  if (url.pathname !== WS_PATH) {
2303
2403
  ws.close(1008, 'Invalid path');
2304
2404
  return;
@@ -2442,9 +2542,9 @@ async function sendInputMessage(ws, data) {
2442
2542
 
2443
2543
  async function sendComposerInputMessage(ws, data) {
2444
2544
  const targetAgentId = resolveInputTargetAgentId(ws, data);
2445
- const requestId = typeof data.requestId === 'string' ? data.requestId : '';
2545
+ const requestId = typeof data.requestId === 'string' ? data.requestId.trim() : '';
2446
2546
  const responseAgentId = targetAgentId || (typeof data.agentId === 'string' ? data.agentId : '');
2447
- const respond = (accepted, message = '') => {
2547
+ const respond = (accepted, message = '', uncertain = false) => {
2448
2548
  if (!requestId || !responseAgentId) return;
2449
2549
  ws.send(JSON.stringify({
2450
2550
  type: 'composer-input-result',
@@ -2452,9 +2552,22 @@ async function sendComposerInputMessage(ws, data) {
2452
2552
  agentId: responseAgentId,
2453
2553
  accepted,
2454
2554
  ...(message ? { message } : {}),
2555
+ ...(uncertain ? { uncertain: true } : {}),
2455
2556
  }));
2456
2557
  };
2457
2558
  const message = typeof data.message === 'string' ? data.message : '';
2559
+ if (!/^[A-Za-z0-9._:-]{1,160}$/.test(requestId)) {
2560
+ if (responseAgentId) {
2561
+ ws.send(JSON.stringify({
2562
+ type: 'composer-input-result',
2563
+ requestId: requestId || 'invalid-request',
2564
+ agentId: responseAgentId,
2565
+ accepted: false,
2566
+ message: 'Structured Composer input requires a valid requestId',
2567
+ }));
2568
+ }
2569
+ return;
2570
+ }
2458
2571
  const content = [];
2459
2572
  if (message.trim()) content.push({ type: 'text', text: message });
2460
2573
  const attachmentsRoot = path.resolve(imageAttachmentsDir());
@@ -2496,11 +2609,11 @@ async function sendComposerInputMessage(ws, data) {
2496
2609
  return;
2497
2610
  }
2498
2611
  try {
2499
- await agentManager.sendComposerMessage(targetAgentId, content);
2612
+ await agentManager.sendComposerMessage(targetAgentId, content, { requestId });
2500
2613
  respond(true);
2501
2614
  } catch (error) {
2502
2615
  const message = error && error.message ? error.message : 'Failed to send Composer message';
2503
- if (requestId) respond(false, message);
2616
+ if (requestId) respond(false, message, error?.uncertain === true);
2504
2617
  else ws.send(JSON.stringify({ type: 'error', message }));
2505
2618
  }
2506
2619
  }
@@ -2834,7 +2947,6 @@ const pendingAgentUpdates = new Map();
2834
2947
  const pendingSessionStreams = new Map();
2835
2948
  let sessionStreamBroadcastTimer = null;
2836
2949
  let lastSessionStreamBroadcastAt = 0;
2837
- let shutdownStarted = false;
2838
2950
 
2839
2951
  function broadcastState() {
2840
2952
  lastStateBroadcastAt = Date.now();
@@ -3049,105 +3161,6 @@ function scheduleSessionStreamBroadcast(stream) {
3049
3161
  }
3050
3162
  }
3051
3163
 
3052
- function clearBroadcastTimers() {
3053
- if (stateBroadcastTimer) {
3054
- clearTimeout(stateBroadcastTimer);
3055
- stateBroadcastTimer = null;
3056
- }
3057
- for (const entry of pendingPreviewBroadcasts.values()) {
3058
- if (entry && entry.timer) {
3059
- clearTimeout(entry.timer);
3060
- }
3061
- }
3062
- pendingPreviewBroadcasts.clear();
3063
- for (const entry of pendingAgentActivityBroadcasts.values()) {
3064
- if (entry && entry.timer) clearTimeout(entry.timer);
3065
- }
3066
- pendingAgentActivityBroadcasts.clear();
3067
- for (const entry of pendingAgentUpdates.values()) {
3068
- if (entry && entry.timer) clearTimeout(entry.timer);
3069
- }
3070
- pendingAgentUpdates.clear();
3071
- if (sessionStreamBroadcastTimer) {
3072
- clearTimeout(sessionStreamBroadcastTimer);
3073
- sessionStreamBroadcastTimer = null;
3074
- }
3075
- pendingSessionStreams.clear();
3076
- }
3077
-
3078
- function closeHttpServer() {
3079
- return new Promise(resolve => {
3080
- if (!server.listening) {
3081
- resolve();
3082
- return;
3083
- }
3084
- server.close(error => {
3085
- if (error && error.code !== 'ERR_SERVER_NOT_RUNNING') {
3086
- console.warn('Failed to close HTTP server:', error.message || error);
3087
- }
3088
- resolve();
3089
- });
3090
- });
3091
- }
3092
-
3093
- function closeWebSocketServer() {
3094
- for (const client of wss.clients) {
3095
- clearWorkspaceFileWatch(client);
3096
- try {
3097
- client.close(1001, 'Farming server shutting down');
3098
- } catch {
3099
- // ignore shutdown races
3100
- }
3101
- }
3102
- return new Promise(resolve => {
3103
- wss.close(() => resolve());
3104
- setTimeout(resolve, 250).unref?.();
3105
- });
3106
- }
3107
-
3108
- async function shutdownServer(options = {}) {
3109
- if (shutdownStarted) return;
3110
- shutdownStarted = true;
3111
- const preserveTerminalHost = options.preserveTerminalHost !== undefined
3112
- ? options.preserveTerminalHost === true
3113
- : process.env.FARMING_NATIVE_PTY_HOST_PERSIST !== '0';
3114
-
3115
- try {
3116
- await agentManager.dispose({ preserveTerminalHost });
3117
- } catch (error) {
3118
- const teardownFrozen = agentManager.disposing === true;
3119
- shutdownStarted = false;
3120
- console.error('Farming shutdown blocked because Agent runtime cleanup could not be verified:', error);
3121
- if (teardownFrozen) {
3122
- clearBroadcastTimers();
3123
- tokenAuth.cleanup();
3124
- await Promise.allSettled([
3125
- closeWebSocketServer(),
3126
- workspaceFileService.dispose(),
3127
- closeHttpServer(),
3128
- ]);
3129
- }
3130
- if (options.exit === true) process.exitCode = 1;
3131
- return {
3132
- error: error?.message || 'Agent runtime cleanup could not be verified',
3133
- ...(teardownFrozen ? { frozen: true } : {}),
3134
- };
3135
- }
3136
-
3137
- clearBroadcastTimers();
3138
- tokenAuth.cleanup();
3139
- await Promise.allSettled([
3140
- closeWebSocketServer(),
3141
- workspaceFileService.dispose(),
3142
- closeHttpServer(),
3143
- ]);
3144
-
3145
- if (options.exit === true) {
3146
- process.exit(options.exitCode || 0);
3147
- }
3148
- return { stopped: true };
3149
- }
3150
-
3151
3164
  agentManager.onSessionStream((stream) => {
3152
3165
  scheduleSessionStreamBroadcast(stream);
3153
3166
  });
@@ -3197,8 +3210,10 @@ function startServer() {
3197
3210
  if (serverStarted) return server;
3198
3211
  serverStarted = true;
3199
3212
 
3200
- void runTerminalRuntimeStartupCleanup().finally(() => {
3201
- if (shutdownStarted) return;
3213
+ void Promise.all([
3214
+ runTerminalRuntimeStartupCleanup(),
3215
+ browserRuntimeRecoveryPromise,
3216
+ ]).finally(() => {
3202
3217
  server.listen(PORT, () => {
3203
3218
  const token = tokenAuth.getToken();
3204
3219
  const localIPs = getLocalIPs();
@@ -3228,13 +3243,6 @@ function startServer() {
3228
3243
  });
3229
3244
  });
3230
3245
 
3231
- process.on('SIGINT', () => {
3232
- void shutdownServer({ exit: true });
3233
- });
3234
- process.on('SIGTERM', () => {
3235
- void shutdownServer({ exit: true });
3236
- });
3237
-
3238
3246
  return server;
3239
3247
  }
3240
3248
 
@@ -3247,6 +3255,7 @@ module.exports = {
3247
3255
  server,
3248
3256
  wss,
3249
3257
  agentManager,
3258
+ browserResourceManager,
3250
3259
  workspaceFileService,
3251
3260
  handleMessage,
3252
3261
  resolveCliBinDir,
@@ -3254,6 +3263,5 @@ module.exports = {
3254
3263
  rewriteIndexHtmlForBasePath,
3255
3264
  appendIndexHtmlAssetToken,
3256
3265
  startServer,
3257
- shutdownServer,
3258
3266
  runTerminalRuntimeStartupCleanup,
3259
3267
  };