farming-code 2.2.23 → 2.2.28

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (264) hide show
  1. package/README.md +9 -2
  2. package/README.zh_cn.md +9 -2
  3. package/THIRD_PARTY_NOTICES.md +3 -0
  4. package/backend/acp-runtime.js +15 -1
  5. package/backend/agent-manager.js +1054 -94
  6. package/backend/atomic-json-store.js +37 -0
  7. package/backend/cli-agents.js +9 -7
  8. package/backend/config-manager.js +167 -58
  9. package/backend/control-api.js +2 -1
  10. package/backend/farming-agent-bootstrap.js +49 -0
  11. package/backend/farming-agent-bootstrap.zh_cn.md +5 -0
  12. package/backend/farming-app-cli.js +9 -2
  13. package/backend/farming-cli.js +55 -0
  14. package/backend/farming-session-store.js +92 -89
  15. package/backend/main-agent-skills.js +17 -0
  16. package/backend/native-pty-host-client.js +99 -7
  17. package/backend/native-pty-host-path.js +20 -0
  18. package/backend/native-pty-host.js +6 -5
  19. package/backend/preview-session-manager.js +68 -0
  20. package/backend/provider-adapters.js +44 -5
  21. package/backend/review-session-store.js +32 -25
  22. package/backend/review-state-store.js +13 -14
  23. package/backend/run-history-store.js +13 -16
  24. package/backend/server.js +103 -78
  25. package/backend/storage-layout.js +20 -0
  26. package/backend/workspace-file-router.js +171 -6
  27. package/backend/workspace-file-service.js +19 -0
  28. package/backend/workspace-root-registry.js +6 -4
  29. package/dist/assets/App-CM7Cy-l4.css +1 -0
  30. package/dist/assets/App-CPG4WcjO.js +658 -0
  31. package/dist/assets/{FileEditorMarkdownPreview-KbnE8VC8.js → FileEditorMarkdownPreview-DYOMm-b1.js} +5 -5
  32. package/dist/assets/FileEditorPane-DXXq82aM.js +2 -0
  33. package/dist/assets/IconGlyphs-DrdPjYXr.js +1 -0
  34. package/dist/assets/ProjectFilesSection-CP66XVPl.js +12 -0
  35. package/dist/assets/{ReviewPage-L1vh1Ggs.js → ReviewPage-w21MEPD6.js} +1 -1
  36. package/dist/assets/{abnfDiagram-VRR7QNED-nnglrXFi.js → abnfDiagram-VRR7QNED-BY1qwq8A.js} +1 -1
  37. package/dist/assets/api-w2x5QfxB.js +1 -0
  38. package/dist/assets/{arc-CWoVlxJw.js → arc-DgZRAlwj.js} +1 -1
  39. package/dist/assets/{architectureDiagram-ZJ3FMSHR-BEXMaFfY.js → architectureDiagram-ZJ3FMSHR-jFn144od.js} +1 -1
  40. package/dist/assets/{blockDiagram-677ZJIJ3-1mCezSBk.js → blockDiagram-677ZJIJ3-BmRjlV0H.js} +1 -1
  41. package/dist/assets/{c4Diagram-LMCZKHZV-DhTiI6Uv.js → c4Diagram-LMCZKHZV-DUKul6tT.js} +1 -1
  42. package/dist/assets/{workspace-editor-monaco-DLlws5Ig.js → channel-CI9j3QFD.js} +1 -1
  43. package/dist/assets/chunk-2Q5K7J3B-87UnWaoh.js +1 -0
  44. package/dist/assets/{chunk-32BRIVSS-CCdsKd73.js → chunk-32BRIVSS-rGVu23o5.js} +1 -1
  45. package/dist/assets/{chunk-52WLFC77-Gncjk4LF.js → chunk-52WLFC77-CvFDKgbB.js} +1 -1
  46. package/dist/assets/{chunk-5VM5RSS4-Dlw7AXWp.js → chunk-5VM5RSS4-CFgnRW1C.js} +1 -1
  47. package/dist/assets/{chunk-7BUUIJ7U-ZBRhjLOh.js → chunk-7BUUIJ7U-Cp7bqW6s.js} +1 -1
  48. package/dist/assets/{chunk-C7G6YPKG-CiDVUP82.js → chunk-C7G6YPKG-BIS1gjZO.js} +1 -1
  49. package/dist/assets/{chunk-EX3LRPZG-z6gXP2_N.js → chunk-EX3LRPZG-BKG-cR9j.js} +1 -1
  50. package/dist/assets/{chunk-FWX5IMBZ-DMZBccQa.js → chunk-FWX5IMBZ-B8PsTr8a.js} +2 -2
  51. package/dist/assets/{chunk-HOUHSVGY-D8tIeoPk.js → chunk-HOUHSVGY-BHFOAiXT.js} +1 -1
  52. package/dist/assets/{chunk-ICXQ74PX-b6oQ2Q17.js → chunk-ICXQ74PX-CvLE1J9X.js} +1 -1
  53. package/dist/assets/{chunk-JWPE2WC7-DSyBRaRG.js → chunk-JWPE2WC7-D8misks7.js} +1 -1
  54. package/dist/assets/{chunk-MOJQB5TN-DAVH_p2a.js → chunk-MOJQB5TN-D-oNgSNH.js} +1 -1
  55. package/dist/assets/{chunk-OGEWGWER-DxoiMCI-.js → chunk-OGEWGWER-C2N-la9e.js} +1 -1
  56. package/dist/assets/{chunk-PUDLZKDR-NsOWh7HM.js → chunk-PUDLZKDR-BwI3Jz8q.js} +1 -1
  57. package/dist/assets/{chunk-Q4XR5HBZ-Di4H7L21.js → chunk-Q4XR5HBZ-BuqGfsXA.js} +1 -1
  58. package/dist/assets/{chunk-RYQCIY6F-CZA9pvFX.js → chunk-RYQCIY6F-Bq4XZcGt.js} +1 -1
  59. package/dist/assets/{chunk-V7JOEXUC-DttpZg9Y.js → chunk-V7JOEXUC-QZLzdTTJ.js} +1 -1
  60. package/dist/assets/chunk-VAUOI2AC-Cb0-kQ9g.js +1 -0
  61. package/dist/assets/{chunk-VR4S4FIN-D-Fmtd4Z.js → chunk-VR4S4FIN-DcZTpML_.js} +1 -1
  62. package/dist/assets/{chunk-WYO6CB5R-CUxMtfK2.js → chunk-WYO6CB5R-DCU8wuny.js} +2 -2
  63. package/dist/assets/chunk-XXDRQBXY-BZAC1Krv.js +1 -0
  64. package/dist/assets/{chunk-ZGVPDNZ5-CoV2QEl0.js → chunk-ZGVPDNZ5-B7r85QbY.js} +1 -1
  65. package/dist/assets/{chunk-ZIRB5QZD-DncwlQQc.js → chunk-ZIRB5QZD-BgEnDWhQ.js} +1 -1
  66. package/dist/assets/classDiagram-OUVF2IWQ-DcvGOuvi.js +1 -0
  67. package/dist/assets/classDiagram-v2-EOCWNBFH-DDYUSofQ.js +1 -0
  68. package/dist/assets/code-dark-BDjwj7Um.css +1 -0
  69. package/dist/assets/{cose-bilkent-JH36ORCC-BNIAz0nN.js → cose-bilkent-JH36ORCC-CThXS2qG.js} +1 -1
  70. package/dist/assets/{cssMode-DDWxTPm_.js → cssMode-Bdg5RpcJ.js} +1 -1
  71. package/dist/assets/{cynefinDiagram-TSTJHNR4-0xPI92Lg.js → cynefinDiagram-TSTJHNR4-DxBgzw25.js} +1 -1
  72. package/dist/assets/{dagre-DXlcHgr3.js → dagre-BM-GvGUZ.js} +1 -1
  73. package/dist/assets/{dagre-VKFMJZFB-DfuMG4gU.js → dagre-VKFMJZFB-B7UCRmxR.js} +1 -1
  74. package/dist/assets/{diagram-FQU43EPY-CT00bLZ6.js → diagram-FQU43EPY-CeL2DwKx.js} +1 -1
  75. package/dist/assets/{diagram-G47NLZAW-Xbh7zXUx.js → diagram-G47NLZAW-BEirglCC.js} +1 -1
  76. package/dist/assets/{diagram-NH7WQ7WH-SRDVPfn7.js → diagram-NH7WQ7WH-C6wcI-Rl.js} +1 -1
  77. package/dist/assets/{diagram-OA4YK3LP-CQRli4Np.js → diagram-OA4YK3LP-DpM-XXtJ.js} +1 -1
  78. package/dist/assets/{diagram-WEI45ONY-DMf17WQi.js → diagram-WEI45ONY-BQyaut9k.js} +1 -1
  79. package/dist/assets/{ebnfDiagram-CCIWWBDH-DhpFkgJ5.js → ebnfDiagram-CCIWWBDH-UmAXsUhr.js} +1 -1
  80. package/dist/assets/{erDiagram-Q63AITRT-1IwxWmJo.js → erDiagram-Q63AITRT-CdYLEOch.js} +1 -1
  81. package/dist/assets/file-icons-BOlcJ0ZF.js +1 -0
  82. package/dist/assets/flowDiagram-23GEKE2U-CO-z9-tq.js +1 -0
  83. package/dist/assets/{freemarker2-QBsTERH-.js → freemarker2-DfMDJfnE.js} +1 -1
  84. package/dist/assets/{ganttDiagram-NO4QXBWP-BNCdfyr1.js → ganttDiagram-NO4QXBWP-DgSUNdUB.js} +1 -1
  85. package/dist/assets/{gitGraphDiagram-IHSO6WYX-pbyWZuiX.js → gitGraphDiagram-IHSO6WYX-DjwjtRtF.js} +1 -1
  86. package/dist/assets/{handlebars-CuoxPo5A.js → handlebars-BEdPtzMT.js} +1 -1
  87. package/dist/assets/{html-gX5PEu0Q.js → html-B1fPb1QD.js} +1 -1
  88. package/dist/assets/html2canvas-DsczWxBa.js +5 -0
  89. package/dist/assets/{htmlMode-DIWUbUxd.js → htmlMode-CY2ZjHxH.js} +1 -1
  90. package/dist/assets/{index-DhTUO8DA.js → index-BzQO3pUr.js} +2 -2
  91. package/dist/assets/{infoDiagram-FWYZ7A6U-BcTOgDbe.js → infoDiagram-FWYZ7A6U-Bi9076l2.js} +1 -1
  92. package/dist/assets/{ishikawaDiagram-FXEZZL3T-DCdvGKrn.js → ishikawaDiagram-FXEZZL3T-DaMaNZN7.js} +1 -1
  93. package/dist/assets/{javascript-BYKGIUGs.js → javascript-BV2OsWA0.js} +1 -1
  94. package/dist/assets/{journeyDiagram-5HDEW3XC-BZ-1q_IT.js → journeyDiagram-5HDEW3XC-CfDy69EZ.js} +1 -1
  95. package/dist/assets/{jsonMode-AFmQQP6u.js → jsonMode-zd07aeuX.js} +1 -1
  96. package/dist/assets/{kanban-definition-HUTT4EX6-BIgejCjj.js → kanban-definition-HUTT4EX6-XFvZ722B.js} +1 -1
  97. package/dist/assets/{line-CfptFMmX.js → line-BXK9PKLn.js} +1 -1
  98. package/dist/assets/{linear-Dld3_ERu.js → linear-CULvmq2Z.js} +1 -1
  99. package/dist/assets/{liquid-DBIl-f0z.js → liquid-BParI24X.js} +1 -1
  100. package/dist/assets/{lspLanguageFeatures-DK6tqicV.js → lspLanguageFeatures-BzredkoV.js} +1 -1
  101. package/dist/assets/main-BlVUXfxa.css +1 -0
  102. package/dist/assets/{map-LZWWoeWa.js → map-C9Qw9eWh.js} +1 -1
  103. package/dist/assets/{mdx-DS-bVtZN.js → mdx-vGzUJN98.js} +1 -1
  104. package/dist/assets/{mermaid.core-gSpX30pV.js → mermaid.core-CzazaIKr.js} +3 -3
  105. package/dist/assets/{mindmap-definition-LN4V7U3C-BKBaI4LW.js → mindmap-definition-LN4V7U3C-Uoff7hZ3.js} +1 -1
  106. package/dist/assets/{monaco.contribution-BXz-lfFB.js → monaco.contribution-BmaeBQEc.js} +2 -2
  107. package/dist/assets/{ordinal-BLrOss5K.js → ordinal-r6pIwRdZ.js} +1 -1
  108. package/dist/assets/{pegDiagram-2B236MQR-sBvDY3VX.js → pegDiagram-2B236MQR-CCBiHQjk.js} +1 -1
  109. package/dist/assets/{pieDiagram-ENE6RG2P-DWlWcu08.js → pieDiagram-ENE6RG2P-D61s7l75.js} +1 -1
  110. package/dist/assets/{python-C93q3xXz.js → python-CUwdLgsI.js} +1 -1
  111. package/dist/assets/{quadrantDiagram-ABIIQ3AL-DZutUd3J.js → quadrantDiagram-ABIIQ3AL-D_CWk2DQ.js} +1 -1
  112. package/dist/assets/{railroadDiagram-RFXS5EU6-DQb3lXGM.js → railroadDiagram-RFXS5EU6-CdlCHtxN.js} +1 -1
  113. package/dist/assets/{razor-CBifNGXO.js → razor-BScebUxU.js} +1 -1
  114. package/dist/assets/{requirementDiagram-TGXJPOKE-DqFGXu2e.js → requirementDiagram-TGXJPOKE-BLpHtkoC.js} +1 -1
  115. package/dist/assets/{sankeyDiagram-HTMAVEWB-d7ECahnO.js → sankeyDiagram-HTMAVEWB-CkhYZN2j.js} +1 -1
  116. package/dist/assets/{sequenceDiagram-DBY2YBRQ-DdsbKqct.js → sequenceDiagram-DBY2YBRQ-8bploKJl.js} +1 -1
  117. package/dist/assets/{sizeCapture-X5ZJPWSS-daTB6OcF.js → sizeCapture-X5ZJPWSS-DtSdkS0d.js} +1 -1
  118. package/dist/assets/{src-WBW1QxJt.js → src-6yeq6WBA.js} +1 -1
  119. package/dist/assets/{stateDiagram-2N3HPSRC-P6iSrgwR.js → stateDiagram-2N3HPSRC-DfmVGpfA.js} +1 -1
  120. package/dist/assets/stateDiagram-v2-6OUMAXLB-B2c9CQ-J.js +1 -0
  121. package/dist/assets/{swimlanes-5IMT3BWC-BGnZJx1M.js → swimlanes-5IMT3BWC-DRLk2zRF.js} +2 -2
  122. package/dist/assets/swimlanesDiagram-G3AALYLV-B7_APh_h.js +8 -0
  123. package/dist/assets/{timeline-definition-FHXFAJF6-rA-r3aUR.js → timeline-definition-FHXFAJF6-Cngfy588.js} +1 -1
  124. package/dist/assets/{tsMode-B0oUTcXQ.js → tsMode-DzUAibXK.js} +1 -1
  125. package/dist/assets/{typescript-KJKfYO7K.js → typescript-D9EOeM7N.js} +1 -1
  126. package/dist/assets/{vennDiagram-L72KCM5P-DiVKUjA9.js → vennDiagram-L72KCM5P-DeJn63Lq.js} +1 -1
  127. package/dist/assets/{wardleyDiagram-EHGQE667-Cfxh7UOn.js → wardleyDiagram-EHGQE667-BH1nzy77.js} +1 -1
  128. package/dist/assets/{workers-BSQRIkqa.js → workers-DAiomYZM.js} +1 -1
  129. package/dist/assets/workspace-editor-model-CSGCl21x.js +1 -0
  130. package/dist/assets/workspace-editor-monaco-CFEfFNcA.js +1 -0
  131. package/dist/assets/{workspace-editor-monaco-BIpqcWf1.js → workspace-editor-monaco-tXA-udGx.js} +3 -3
  132. package/dist/assets/workspace-view-state-BS_V8JVs.js +7 -0
  133. package/dist/assets/{xml-C8FAcN6j.js → xml-wWl1dhkQ.js} +1 -1
  134. package/dist/assets/{xychartDiagram-FW5EYKEG--hmryQeL.js → xychartDiagram-FW5EYKEG-SkO2prCU.js} +1 -1
  135. package/dist/assets/{yaml-BQ4NrRhG.js → yaml-Dc2WDaOZ.js} +1 -1
  136. package/dist/crt-markdown-renderer.js +46 -46
  137. package/dist/index.html +1 -1
  138. package/extensions/browser/backend/agent-capability.js +55 -0
  139. package/extensions/browser/backend/browser-mcp-server.js +269 -0
  140. package/extensions/browser/backend/browser-resource-manager.js +524 -0
  141. package/extensions/browser/backend/browser-resource-store.js +149 -0
  142. package/extensions/browser/backend/browser-router.js +128 -0
  143. package/extensions/browser/backend/cdp-browser-runtime.js +648 -0
  144. package/extensions/browser/backend/cdp-client.js +167 -0
  145. package/extensions/browser/backend/executable-discovery.js +91 -0
  146. package/extensions/browser/backend/farming-browser-client.js +83 -0
  147. package/extensions/browser/backend/index.js +7 -0
  148. package/extensions/browser/bin/farming-browser +117 -0
  149. package/extensions/browser/frontend/BrowserSidebarPortals.tsx +362 -0
  150. package/extensions/browser/frontend/BrowserViewer.tsx +329 -0
  151. package/extensions/browser/frontend/browser-resource-state.ts +60 -0
  152. package/extensions/browser/frontend/browser.css +516 -0
  153. package/extensions/browser/frontend/types.ts +25 -0
  154. package/extensions/browser/frontend/useBrowserResources.ts +151 -0
  155. package/frontend/session-bridge.js +7 -6
  156. package/frontend/skins/crt/app.js +64 -4
  157. package/package.json +18 -13
  158. package/shared/browser-protocol.js +1 -1
  159. package/dist/assets/App-BbNBDA__.js +0 -210
  160. package/dist/assets/App-BrP-ENHg.css +0 -1
  161. package/dist/assets/FileEditorPane-5_HUejjd.js +0 -2
  162. package/dist/assets/IconGlyphs-C2WFcoJS.js +0 -1
  163. package/dist/assets/ProjectFilesSection-C1RAJWOE.js +0 -12
  164. package/dist/assets/api-D8nyOEbz.js +0 -1
  165. package/dist/assets/channel-BVWNatUB.js +0 -1
  166. package/dist/assets/chunk-2Q5K7J3B-d5WWh8jo.js +0 -1
  167. package/dist/assets/chunk-VAUOI2AC-UHNr_yhX.js +0 -1
  168. package/dist/assets/chunk-XXDRQBXY-BBXwjhUx.js +0 -1
  169. package/dist/assets/classDiagram-OUVF2IWQ-Db0ib5iE.js +0 -1
  170. package/dist/assets/classDiagram-v2-EOCWNBFH-Bkf69XF9.js +0 -1
  171. package/dist/assets/code-dark-BKtvf0Xc.css +0 -1
  172. package/dist/assets/file-icons-CHWt5nSy.js +0 -1
  173. package/dist/assets/flowDiagram-23GEKE2U-DYFo2xmB.js +0 -1
  174. package/dist/assets/main-XMXgyJJt.css +0 -1
  175. package/dist/assets/stateDiagram-v2-6OUMAXLB-DIYSf_qh.js +0 -1
  176. package/dist/assets/swimlanesDiagram-G3AALYLV-BIkrlKrZ.js +0 -8
  177. package/dist/assets/workspace-editor-model-D0Im34S5.js +0 -1
  178. package/dist/assets/workspace-view-state-BqB4HM7r.js +0 -7
  179. /package/dist/assets/{abap-Cc5D5h5i.js → abap-CtX5nWyX.js} +0 -0
  180. /package/dist/assets/{apex-BKopwLmx.js → apex-DQJRAHwc.js} +0 -0
  181. /package/dist/assets/{array-HeX70jSN.js → array-BTwzYU0x.js} +0 -0
  182. /package/dist/assets/{azcli-DI-C34WA.js → azcli-9SMrOQ4q.js} +0 -0
  183. /package/dist/assets/{bat-Cbp6u42z.js → bat-CBGLUsw1.js} +0 -0
  184. /package/dist/assets/{bicep-4QA1jTCM.js → bicep-pDx8da1v.js} +0 -0
  185. /package/dist/assets/{cameligo-Dda_adPs.js → cameligo-hZV3vQ6_.js} +0 -0
  186. /package/dist/assets/{chunk-Y2CYZVJY-Di0sYzi1.js → chunk-Y2CYZVJY-BYi0W5ea.js} +0 -0
  187. /package/dist/assets/{clojure-CvY8A83L.js → clojure-BhCwU3gq.js} +0 -0
  188. /package/dist/assets/{coffee-4LX2Sv8A.js → coffee-BVB1PsgH.js} +0 -0
  189. /package/dist/assets/{core-B9MwRjDE.js → core-vPkV8P_m.js} +0 -0
  190. /package/dist/assets/{cpp-D4AzSV28.js → cpp-eTjuiNPq.js} +0 -0
  191. /package/dist/assets/{csharp-C4nWN4Yd.js → csharp-1kP-kKZT.js} +0 -0
  192. /package/dist/assets/{csp-CiEaaS5Z.js → csp-C_iQqIes.js} +0 -0
  193. /package/dist/assets/{css-DXxBRsbk.js → css-CxOs7IfP.js} +0 -0
  194. /package/dist/assets/{cypher-DOMzqyFA.js → cypher-Cd-a9Y82.js} +0 -0
  195. /package/dist/assets/{cytoscape.esm-Cl8iC3D7.js → cytoscape.esm-OLojuicg.js} +0 -0
  196. /package/dist/assets/{dart-BWS7MXt_.js → dart-whhXpexn.js} +0 -0
  197. /package/dist/assets/{defaultLocale-GJwWH1Jr.js → defaultLocale-CPimVhfE.js} +0 -0
  198. /package/dist/assets/{dist-ClAL-Tv5.js → dist-CLPYXg1u.js} +0 -0
  199. /package/dist/assets/{dockerfile-Baf4U3xT.js → dockerfile-DibBi8b6.js} +0 -0
  200. /package/dist/assets/{ecl-CT7udlkQ.js → ecl-DlF1KdoW.js} +0 -0
  201. /package/dist/assets/{editor.api2-B9h6iFPK.js → editor.api2-D74ufkbr.js} +0 -0
  202. /package/dist/assets/{elixir-CIbAZw5l.js → elixir-CHQ8uFT3.js} +0 -0
  203. /package/dist/assets/{flow9-Dqiw45WV.js → flow9-8nUnVKk1.js} +0 -0
  204. /package/dist/assets/{fsharp-E8E2L9jU.js → fsharp-DNZNZyEE.js} +0 -0
  205. /package/dist/assets/{go-CouVSN4D.js → go-_kPJjNc_.js} +0 -0
  206. /package/dist/assets/{graphlib-CbSvZcZU.js → graphlib-CBGF9gA9.js} +0 -0
  207. /package/dist/assets/{graphql-a6h6h5-t.js → graphql-4HCNVt5g.js} +0 -0
  208. /package/dist/assets/{hcl-vso3e6N-.js → hcl-C87ymY0o.js} +0 -0
  209. /package/dist/assets/{ini-SwZZkotR.js → ini-BYrrGNTc.js} +0 -0
  210. /package/dist/assets/{init-BPLPMQ3Y.js → init-8_PCa3LO.js} +0 -0
  211. /package/dist/assets/{java-CtRLC4_3.js → java-B211rJbk.js} +0 -0
  212. /package/dist/assets/{julia-CuJWFKQH.js → julia-DVPsRtBm.js} +0 -0
  213. /package/dist/assets/{katex-C8DPyI6s.js → katex-C2iYad0m.js} +0 -0
  214. /package/dist/assets/{kotlin-B_kBhsre.js → kotlin-BYyNerjp.js} +0 -0
  215. /package/dist/assets/{less-CtrHIfz3.js → less-EC9UblGE.js} +0 -0
  216. /package/dist/assets/{lexon-yVt1pJLu.js → lexon-B1yuUdqW.js} +0 -0
  217. /package/dist/assets/{lua-prOwgJ4U.js → lua-Bo6GFzxT.js} +0 -0
  218. /package/dist/assets/{m3-DhrdTQ84.js → m3-C-Y9cBVI.js} +0 -0
  219. /package/dist/assets/{markdown-DoI5HwSM.js → markdown-BHu-wLbe.js} +0 -0
  220. /package/dist/assets/{mermaid-parser.core-BQtepoLd.js → mermaid-parser.core-IxcGtNYG.js} +0 -0
  221. /package/dist/assets/{mips-Dye5jsBU.js → mips-Dkds26rX.js} +0 -0
  222. /package/dist/assets/{msdax-TkNOyIL8.js → msdax-PU4g-8_g.js} +0 -0
  223. /package/dist/assets/{mysql-CEc-RKZF.js → mysql-fPpuGpS7.js} +0 -0
  224. /package/dist/assets/{objective-c-CqqCsQz0.js → objective-c-Wy1e5JUf.js} +0 -0
  225. /package/dist/assets/{pascal-DO_pj8yg.js → pascal-BIXwvrqU.js} +0 -0
  226. /package/dist/assets/{pascaligo-1cbdZdF3.js → pascaligo-DwR0AtL_.js} +0 -0
  227. /package/dist/assets/{path-YdFzr2W6.js → path-BxQuywGl.js} +0 -0
  228. /package/dist/assets/{perl-B_N3PYfC.js → perl-CGHAl8my.js} +0 -0
  229. /package/dist/assets/{pgsql-9iYk1FQ7.js → pgsql-B91M73E2.js} +0 -0
  230. /package/dist/assets/{php-rO2sgoUM.js → php-C70ttrCx.js} +0 -0
  231. /package/dist/assets/{pla-Cbgv60kq.js → pla-BNrXaRh0.js} +0 -0
  232. /package/dist/assets/{postiats-y6xrhy_M.js → postiats-Dv2IAZR9.js} +0 -0
  233. /package/dist/assets/{powerquery-BaD8gWNE.js → powerquery-CkY5Wq7J.js} +0 -0
  234. /package/dist/assets/{powershell-BMhG4JVe.js → powershell-uGIYLU3C.js} +0 -0
  235. /package/dist/assets/{protobuf-CdOP5kN3.js → protobuf-CavfmJu-.js} +0 -0
  236. /package/dist/assets/{pug-Ck60hNF9.js → pug-Drqnyitx.js} +0 -0
  237. /package/dist/assets/{qrcode-DV-_rjU8.js → qrcode-7uGsTEmi.js} +0 -0
  238. /package/dist/assets/{qsharp-BcaKFfP-.js → qsharp-C7yF-Dti.js} +0 -0
  239. /package/dist/assets/{r-rFNes0iK.js → r-HXLGP0_-.js} +0 -0
  240. /package/dist/assets/{redis-DkpQ3K6G.js → redis-C4y6mf2p.js} +0 -0
  241. /package/dist/assets/{redshift-Di9N5TI3.js → redshift-DdQrFPrv.js} +0 -0
  242. /package/dist/assets/{restructuredtext-By5ZzLt6.js → restructuredtext-CBrf2PNj.js} +0 -0
  243. /package/dist/assets/{rough.esm-c4PR5shF.js → rough.esm-6CnTHTkH.js} +0 -0
  244. /package/dist/assets/{ruby-fF5zGiLp.js → ruby-CVtMoX2B.js} +0 -0
  245. /package/dist/assets/{rust-C5b3AYY6.js → rust-Ckq4sbMP.js} +0 -0
  246. /package/dist/assets/{sb-CD07UNRs.js → sb-er66OpE3.js} +0 -0
  247. /package/dist/assets/{scala-jtn1ip9G.js → scala-BdWZz-hH.js} +0 -0
  248. /package/dist/assets/{scheme-C4vqMkn_.js → scheme-kTV_s2kP.js} +0 -0
  249. /package/dist/assets/{scss-I4Glxjbz.js → scss-Bymy2VJQ.js} +0 -0
  250. /package/dist/assets/{shell-DubUcuns.js → shell-bwEP1LQ1.js} +0 -0
  251. /package/dist/assets/{solidity-CNy2KSl2.js → solidity-DCddeBgL.js} +0 -0
  252. /package/dist/assets/{sophia-D9_URt84.js → sophia-BMN3AQig.js} +0 -0
  253. /package/dist/assets/{sparql-DviXpm4C.js → sparql-CSXOLjzi.js} +0 -0
  254. /package/dist/assets/{sql-vDRbAZgK.js → sql-COg8ewmK.js} +0 -0
  255. /package/dist/assets/{st-CnpnRqA7.js → st-CIaZtBS_.js} +0 -0
  256. /package/dist/assets/{swift-D_g8Ganz.js → swift-DXiOhUHu.js} +0 -0
  257. /package/dist/assets/{systemverilog-OuZa5h1M.js → systemverilog-BTDE6Dr2.js} +0 -0
  258. /package/dist/assets/{tcl-DBm9ucsV.js → tcl-B0zy9NvV.js} +0 -0
  259. /package/dist/assets/{twig-BRzEqX5Y.js → twig-D594l4Z1.js} +0 -0
  260. /package/dist/assets/{typespec-D4ZgGeX8.js → typespec-XKVjaPqo.js} +0 -0
  261. /package/dist/assets/{useWorkspaceMenuKeyboard-Brws6Ar9.js → useWorkspaceMenuKeyboard-Bpy_s69Z.js} +0 -0
  262. /package/dist/assets/{vb-DcP4s6WS.js → vb-B2wYoKRV.js} +0 -0
  263. /package/dist/assets/{wgsl-CnRJ_QUf.js → wgsl-D0Yf2LbK.js} +0 -0
  264. /package/dist/assets/{workspace-working-copy-D8-s_Sgh.js → workspace-working-copy-C3FSR3N6.js} +0 -0
@@ -0,0 +1,524 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const { EventEmitter } = require('events');
4
+ const storageLayout = require('../../../backend/storage-layout');
5
+ const { BrowserResourceStore, RESOURCE_ID_RE } = require('./browser-resource-store');
6
+ const { CdpBrowserRuntime } = require('./cdp-browser-runtime');
7
+ const { discoverBrowserExecutable } = require('./executable-discovery');
8
+
9
+ const MAX_VIEWER_BUFFER_BYTES = 2 * 1024 * 1024;
10
+
11
+ function publicResource(resource, collectionRevision) {
12
+ return {
13
+ id: resource.id,
14
+ projectRootId: resource.projectRootId,
15
+ workspace: resource.workspace,
16
+ name: resource.name,
17
+ status: resource.status,
18
+ generation: resource.generation,
19
+ revision: resource.revision,
20
+ collectionRevision,
21
+ url: resource.url,
22
+ title: resource.title,
23
+ browserKind: resource.browserKind,
24
+ error: resource.error,
25
+ createdAt: resource.createdAt,
26
+ updatedAt: resource.updatedAt,
27
+ };
28
+ }
29
+
30
+ function browserError(message, status = 400, code = 'BROWSER_INVALID_REQUEST') {
31
+ const error = new Error(message);
32
+ error.status = status;
33
+ error.code = code;
34
+ return error;
35
+ }
36
+
37
+ function normalizeUrl(value) {
38
+ const input = String(value || '').trim();
39
+ if (!input) return 'about:blank';
40
+ if (input === 'about:blank') return input;
41
+ const url = /^[A-Za-z][A-Za-z0-9+.-]*:\/\//.test(input) ? input : `http://${input}`;
42
+ try {
43
+ const parsed = new URL(url);
44
+ if (!['http:', 'https:'].includes(parsed.protocol)) {
45
+ throw browserError('Browser navigation supports only http, https, and about:blank URLs');
46
+ }
47
+ return parsed.href;
48
+ } catch (error) {
49
+ if (error?.status) throw error;
50
+ throw browserError('Invalid Browser URL');
51
+ }
52
+ }
53
+
54
+ class BrowserResourceManager extends EventEmitter {
55
+ constructor(options) {
56
+ super();
57
+ this.configDir = options.configDir;
58
+ this.store = options.store || new BrowserResourceStore(options.configDir);
59
+ this.discoverExecutable = options.discoverExecutable || (() => discoverBrowserExecutable(options));
60
+ this.createRuntime = options.createRuntime || (input => new CdpBrowserRuntime(input));
61
+ this.isEnabled = typeof options.isEnabled === 'function' ? options.isEnabled : () => true;
62
+ this.runtimes = new Map();
63
+ this.operations = new Map();
64
+ this.disposed = false;
65
+ }
66
+
67
+ init() {
68
+ this.store.init();
69
+ }
70
+
71
+ capability() {
72
+ const executable = this.discoverExecutable();
73
+ const enabled = this.isEnabled() === true;
74
+ return {
75
+ enabled,
76
+ available: enabled && Boolean(executable),
77
+ browser: executable ? { kind: executable.kind, path: executable.path } : null,
78
+ message: !enabled
79
+ ? 'Browser extension is disabled'
80
+ : (executable ? '' : 'Install a Chromium-based browser to use the system Browser in Farming'),
81
+ };
82
+ }
83
+
84
+ list() {
85
+ this.requireEnabled();
86
+ return this.store.list().map(resource => publicResource(resource, this.store.revision));
87
+ }
88
+
89
+ snapshot() {
90
+ return {
91
+ collectionRevision: this.store.revision,
92
+ resources: this.list(),
93
+ };
94
+ }
95
+
96
+ get(id) {
97
+ this.requireEnabled();
98
+ return publicResource(this.requireStored(id), this.store.revision);
99
+ }
100
+
101
+ create(input) {
102
+ this.requireAvailable();
103
+ if (this.disposed) throw browserError('Browser manager is stopping', 503, 'BROWSER_MANAGER_STOPPING');
104
+ const resource = this.store.create({
105
+ projectRootId: input.projectRootId,
106
+ workspace: input.workspace,
107
+ name: input.name,
108
+ url: normalizeUrl(input.url),
109
+ });
110
+ this.emitResource(resource);
111
+ return publicResource(resource, this.store.revision);
112
+ }
113
+
114
+ rename(id, name) {
115
+ this.requireEnabled();
116
+ const title = String(name || '').trim();
117
+ if (!title) throw browserError('Browser name is required');
118
+ const resource = this.requireStored(id);
119
+ const next = this.store.update(resource.id, { name: title.slice(0, 120) });
120
+ this.emitResource(next);
121
+ return publicResource(next, this.store.revision);
122
+ }
123
+
124
+ start(id) {
125
+ this.requireAvailable();
126
+ return this.enqueue(id, async () => {
127
+ const resource = this.requireStored(id);
128
+ const existing = this.runtimes.get(id);
129
+ if (resource.status === 'running' && existing) return publicResource(resource, this.store.revision);
130
+ if (existing) {
131
+ throw browserError(
132
+ 'A previous Browser runtime still owns this resource; stop it before restarting',
133
+ 409,
134
+ 'BROWSER_RUNTIME_OWNED',
135
+ );
136
+ }
137
+ if (resource.status === 'starting' || resource.status === 'stopping') {
138
+ throw browserError(`Browser is ${resource.status}`, 409, 'BROWSER_BUSY');
139
+ }
140
+ const executable = this.discoverExecutable();
141
+ if (!executable) {
142
+ const failed = this.store.update(id, {
143
+ status: 'failed',
144
+ error: 'Install a Chromium-based browser to use the system Browser in Farming',
145
+ });
146
+ this.emitResource(failed);
147
+ throw browserError(failed.error, 503, 'BROWSER_EXECUTABLE_NOT_FOUND');
148
+ }
149
+
150
+ const generation = resource.generation + 1;
151
+ const starting = this.store.update(id, {
152
+ status: 'starting',
153
+ generation,
154
+ browserKind: executable.kind,
155
+ error: '',
156
+ });
157
+ this.emitResource(starting);
158
+ const runtime = this.createRuntime({
159
+ id,
160
+ generation,
161
+ executablePath: executable.path,
162
+ profileDir: storageLayout.browserProfileDir(this.configDir, id),
163
+ });
164
+ this.runtimes.set(id, runtime);
165
+ this.bindRuntime(runtime);
166
+ try {
167
+ const metadata = await runtime.start(resource.url);
168
+ if (this.runtimes.get(id) !== runtime) {
169
+ throw browserError('Browser startup lost runtime ownership', 409, 'BROWSER_START_REPLACED');
170
+ }
171
+ const running = this.store.update(id, {
172
+ status: 'running',
173
+ url: metadata.url || resource.url,
174
+ title: metadata.title || '',
175
+ error: '',
176
+ });
177
+ this.emitResource(running);
178
+ this.broadcastRuntimeState(runtime);
179
+ return publicResource(running, this.store.revision);
180
+ } catch (error) {
181
+ let cleanupError = null;
182
+ try {
183
+ await runtime.close();
184
+ } catch (closeError) {
185
+ cleanupError = closeError;
186
+ }
187
+ if (!cleanupError && this.runtimes.get(id) === runtime) this.runtimes.delete(id);
188
+ const current = this.store.get(id);
189
+ const failed = current?.generation === generation
190
+ ? this.store.update(id, {
191
+ status: 'failed',
192
+ error: cleanupError
193
+ ? `${error?.message || 'Failed to start system browser'}; cleanup failed: ${cleanupError.message}`
194
+ : error?.message || 'Failed to start system browser',
195
+ })
196
+ : null;
197
+ if (failed) this.emitResource(failed);
198
+ throw browserError(
199
+ failed?.error || error?.message || 'Failed to start system browser',
200
+ 500,
201
+ 'BROWSER_START_FAILED',
202
+ );
203
+ }
204
+ });
205
+ }
206
+
207
+ stop(id) {
208
+ this.requireEnabled();
209
+ return this.enqueue(id, async () => {
210
+ this.requireStored(id);
211
+ const runtime = this.runtimes.get(id);
212
+ if (!runtime) {
213
+ const stopped = this.store.update(id, { status: 'stopped', error: '' });
214
+ this.emitResource(stopped);
215
+ return publicResource(stopped, this.store.revision);
216
+ }
217
+ const stopping = this.store.update(id, { status: 'stopping', error: '' });
218
+ this.emitResource(stopping);
219
+ this.broadcastRuntimeState(runtime);
220
+ await runtime.close();
221
+ if (this.runtimes.get(id) === runtime) this.runtimes.delete(id);
222
+ const stopped = this.store.update(id, { status: 'stopped', error: '' });
223
+ this.emitResource(stopped);
224
+ this.broadcastRuntimeState(runtime);
225
+ runtime.viewers?.clear?.();
226
+ return publicResource(stopped, this.store.revision);
227
+ });
228
+ }
229
+
230
+ async delete(id) {
231
+ this.requireEnabled();
232
+ await this.stop(id);
233
+ this.requireStored(id);
234
+ this.store.delete(id);
235
+ const profileDir = storageLayout.browserProfileDir(this.configDir, id);
236
+ const browsersDir = path.resolve(storageLayout.browserResourcesDir(this.configDir));
237
+ const resourceDir = path.resolve(profileDir, '..');
238
+ if (resourceDir.startsWith(`${browsersDir}${path.sep}`) && RESOURCE_ID_RE.test(id)) {
239
+ fs.rmSync(resourceDir, { recursive: true, force: true });
240
+ }
241
+ this.emit('deleted', { id, collectionRevision: this.store.revision });
242
+ return { id, collectionRevision: this.store.revision };
243
+ }
244
+
245
+ navigate(id, url) {
246
+ this.requireEnabled();
247
+ const normalized = normalizeUrl(url);
248
+ return this.withRuntime(id, async runtime => {
249
+ const metadata = await runtime.navigate(normalized);
250
+ this.updateMetadata(runtime, metadata);
251
+ return this.get(id);
252
+ });
253
+ }
254
+
255
+ goBack(id) {
256
+ return this.withRuntime(id, async runtime => {
257
+ const metadata = await runtime.goBack();
258
+ this.updateMetadata(runtime, metadata);
259
+ return this.get(id);
260
+ });
261
+ }
262
+
263
+ goForward(id) {
264
+ return this.withRuntime(id, async runtime => {
265
+ const metadata = await runtime.goForward();
266
+ this.updateMetadata(runtime, metadata);
267
+ return this.get(id);
268
+ });
269
+ }
270
+
271
+ reload(id) {
272
+ return this.withRuntime(id, async runtime => {
273
+ const metadata = await runtime.reload();
274
+ this.updateMetadata(runtime, metadata);
275
+ return this.get(id);
276
+ });
277
+ }
278
+
279
+ action(id, input) {
280
+ this.requireEnabled();
281
+ const kind = String(input?.kind || '').trim();
282
+ if (kind === 'snapshot') return this.withRuntime(id, runtime => runtime.snapshot());
283
+ if (kind === 'screenshot') return this.withRuntime(id, runtime => runtime.screenshot());
284
+ if (kind === 'navigate') return this.navigate(id, input.url);
285
+ if (kind === 'back') return this.goBack(id);
286
+ if (kind === 'forward') return this.goForward(id);
287
+ if (kind === 'reload') return this.reload(id);
288
+ if (kind === 'click') return this.withRuntime(id, runtime => runtime.click(input));
289
+ if (kind === 'type') return this.withRuntime(id, runtime => runtime.type(input, false));
290
+ if (kind === 'fill') return this.withRuntime(id, runtime => runtime.type(input, true));
291
+ if (kind === 'press') return this.withRuntime(id, runtime => runtime.press(input));
292
+ if (kind === 'scroll') return this.withRuntime(id, async runtime => {
293
+ await runtime.wheel(input);
294
+ return { ok: true };
295
+ });
296
+ throw browserError(`Unsupported Browser action: ${kind || '(missing)'}`);
297
+ }
298
+
299
+ attachViewer(id, ws) {
300
+ this.requireEnabled();
301
+ const resource = this.requireStored(id);
302
+ const runtime = this.runtimes.get(id);
303
+ ws.send(JSON.stringify({
304
+ type: 'browser-state',
305
+ resource: publicResource(resource, this.store.revision),
306
+ }));
307
+ if (!runtime || resource.status !== 'running') return () => {};
308
+ if (!runtime.viewers) runtime.viewers = new Set();
309
+ runtime.viewers.add(ws);
310
+ if (runtime.latestFrame) ws.send(JSON.stringify(runtime.latestFrame));
311
+ const onMessage = raw => {
312
+ let message;
313
+ try {
314
+ message = JSON.parse(raw.toString());
315
+ } catch {
316
+ return;
317
+ }
318
+ void this.handleViewerMessage(runtime, message).catch(error => {
319
+ if (ws.readyState === 1) {
320
+ ws.send(JSON.stringify({ type: 'browser-error', message: error?.message || 'Browser input failed' }));
321
+ }
322
+ });
323
+ };
324
+ ws.on('message', onMessage);
325
+ const detach = () => {
326
+ runtime.viewers.delete(ws);
327
+ ws.off('message', onMessage);
328
+ };
329
+ ws.once('close', detach);
330
+ return detach;
331
+ }
332
+
333
+ handleViewerMessage(runtime, message) {
334
+ const next = (runtime.actionChain || Promise.resolve())
335
+ .catch(() => {})
336
+ .then(() => this.performViewerMessage(runtime, message));
337
+ runtime.actionChain = next;
338
+ return next;
339
+ }
340
+
341
+ async performViewerMessage(runtime, message) {
342
+ if (message.generation !== runtime.generation || this.runtimes.get(runtime.id) !== runtime) {
343
+ throw browserError('Browser Viewer generation is stale', 409, 'BROWSER_STALE_GENERATION');
344
+ }
345
+ if (message.type === 'resize') {
346
+ // A Browser resource owns one authoritative viewport. Viewer layout is
347
+ // presentation-only: allowing each attached desktop or mobile Viewer to
348
+ // resize the page makes concurrent viewers fight over shared page state.
349
+ return;
350
+ }
351
+ if (message.type === 'pointer') {
352
+ await runtime.pointer(message);
353
+ return;
354
+ }
355
+ if (message.type === 'wheel') {
356
+ await runtime.wheel(message);
357
+ return;
358
+ }
359
+ if (message.type === 'key') {
360
+ await runtime.press(message);
361
+ return;
362
+ }
363
+ if (message.type === 'text') {
364
+ await runtime.insertText(message.text);
365
+ }
366
+ }
367
+
368
+ async dispose() {
369
+ this.disposed = true;
370
+ const runtimes = [...this.runtimes.values()];
371
+ const results = await Promise.allSettled(runtimes.map(runtime => runtime.close()));
372
+ const failures = results
373
+ .filter(result => result.status === 'rejected')
374
+ .map(result => result.reason?.message || String(result.reason));
375
+ if (failures.length > 0) {
376
+ throw new Error(`Browser runtime cleanup failed: ${failures.join('; ')}`);
377
+ }
378
+ this.runtimes.clear();
379
+ }
380
+
381
+ async stopAll() {
382
+ const ids = [...this.runtimes.keys()];
383
+ const results = await Promise.allSettled(ids.map(id => this.stop(id)));
384
+ const failures = results
385
+ .filter(result => result.status === 'rejected')
386
+ .map(result => result.reason?.message || String(result.reason));
387
+ if (failures.length > 0) {
388
+ throw browserError(
389
+ `Browser extension could not stop every running Browser: ${failures.join('; ')}`,
390
+ 500,
391
+ 'BROWSER_DISABLE_FAILED',
392
+ );
393
+ }
394
+ }
395
+
396
+ requireEnabled() {
397
+ if (this.isEnabled() !== true) {
398
+ throw browserError('Browser extension is disabled', 409, 'BROWSER_EXTENSION_DISABLED');
399
+ }
400
+ }
401
+
402
+ requireAvailable() {
403
+ this.requireEnabled();
404
+ if (!this.discoverExecutable()) {
405
+ throw browserError(
406
+ 'Install a Chromium-based browser to use the system Browser in Farming',
407
+ 503,
408
+ 'BROWSER_EXECUTABLE_NOT_FOUND',
409
+ );
410
+ }
411
+ }
412
+
413
+ requireStored(id) {
414
+ if (!RESOURCE_ID_RE.test(String(id || ''))) {
415
+ throw browserError('Invalid Browser resource id');
416
+ }
417
+ const resource = this.store.get(id);
418
+ if (!resource) throw browserError('Browser resource not found', 404, 'BROWSER_NOT_FOUND');
419
+ return resource;
420
+ }
421
+
422
+ enqueue(id, operation) {
423
+ const previous = this.operations.get(id) || Promise.resolve();
424
+ const next = previous.catch(() => {}).then(operation);
425
+ this.operations.set(id, next);
426
+ return next.finally(() => {
427
+ if (this.operations.get(id) === next) this.operations.delete(id);
428
+ });
429
+ }
430
+
431
+ withRuntime(id, operation) {
432
+ const runtime = this.runtimes.get(id);
433
+ const resource = this.requireStored(id);
434
+ if (!runtime || resource.status !== 'running') {
435
+ throw browserError('Browser is not running', 409, 'BROWSER_NOT_RUNNING');
436
+ }
437
+ const next = (runtime.actionChain || Promise.resolve())
438
+ .catch(() => {})
439
+ .then(() => operation(runtime));
440
+ runtime.actionChain = next;
441
+ return next;
442
+ }
443
+
444
+ bindRuntime(runtime) {
445
+ runtime.on('frame', frame => {
446
+ runtime.latestFrame = frame;
447
+ for (const viewer of runtime.viewers || []) {
448
+ if (viewer.readyState === 1 && (Number(viewer.bufferedAmount) || 0) <= MAX_VIEWER_BUFFER_BYTES) {
449
+ viewer.send(JSON.stringify(frame));
450
+ }
451
+ }
452
+ });
453
+ runtime.on('metadata', metadata => this.updateMetadata(runtime, metadata));
454
+ runtime.on('error', error => {
455
+ for (const viewer of runtime.viewers || []) {
456
+ if (viewer.readyState === 1) {
457
+ viewer.send(JSON.stringify({ type: 'browser-error', message: error?.message || 'Browser runtime failed' }));
458
+ }
459
+ }
460
+ });
461
+ runtime.once('exit', message => {
462
+ void this.handleRuntimeExit(runtime, message);
463
+ });
464
+ }
465
+
466
+ updateMetadata(runtime, metadata) {
467
+ const current = this.store.get(runtime.id);
468
+ if (!current || current.generation !== runtime.generation || !metadata) return;
469
+ const next = this.store.update(runtime.id, {
470
+ url: String(metadata.url || current.url),
471
+ title: String(metadata.title || ''),
472
+ });
473
+ this.emitResource(next);
474
+ this.broadcastRuntimeState(runtime);
475
+ }
476
+
477
+ emitResource(resource) {
478
+ this.emit('resource', publicResource(resource, this.store.revision));
479
+ }
480
+
481
+ broadcastRuntimeState(runtime) {
482
+ const resource = this.store.get(runtime.id);
483
+ if (!resource) return;
484
+ const message = JSON.stringify({
485
+ type: 'browser-state',
486
+ resource: publicResource(resource, this.store.revision),
487
+ });
488
+ for (const viewer of runtime.viewers || []) {
489
+ if (viewer.readyState === 1) viewer.send(message);
490
+ }
491
+ }
492
+
493
+ async handleRuntimeExit(runtime, message) {
494
+ const current = this.store.get(runtime.id);
495
+ if (
496
+ this.runtimes.get(runtime.id) !== runtime
497
+ || !current
498
+ || current.generation !== runtime.generation
499
+ ) return;
500
+ const failed = this.store.update(runtime.id, {
501
+ status: 'failed',
502
+ error: message || 'System browser exited',
503
+ });
504
+ this.emitResource(failed);
505
+ this.broadcastRuntimeState(runtime);
506
+ try {
507
+ await runtime.close();
508
+ if (this.runtimes.get(runtime.id) === runtime) this.runtimes.delete(runtime.id);
509
+ } catch (error) {
510
+ const cleanupFailed = this.store.update(runtime.id, {
511
+ status: 'failed',
512
+ error: `${failed.error}; cleanup failed: ${error?.message || error}`,
513
+ });
514
+ this.emitResource(cleanupFailed);
515
+ this.broadcastRuntimeState(runtime);
516
+ }
517
+ }
518
+ }
519
+
520
+ module.exports = {
521
+ BrowserResourceManager,
522
+ browserError,
523
+ normalizeUrl,
524
+ };
@@ -0,0 +1,149 @@
1
+ const fs = require('fs');
2
+ const crypto = require('crypto');
3
+ const { atomicWriteJson } = require('../../../backend/atomic-json-store');
4
+ const storageLayout = require('../../../backend/storage-layout');
5
+
6
+ const STORE_VERSION = 2;
7
+ const RESOURCE_ID_RE = /^browser_[A-Za-z0-9_-]+$/;
8
+ const STATUSES = new Set(['stopped', 'starting', 'running', 'stopping', 'failed']);
9
+
10
+ function createBrowserId() {
11
+ return `browser_${Date.now().toString(36)}_${crypto.randomBytes(6).toString('hex')}`;
12
+ }
13
+
14
+ function normalizeResource(value) {
15
+ if (!value || typeof value !== 'object' || !RESOURCE_ID_RE.test(String(value.id || ''))) return null;
16
+ const projectRootId = String(value.projectRootId || '').trim();
17
+ const workspace = String(value.workspace || '').trim();
18
+ if (!projectRootId || !workspace) return null;
19
+ return {
20
+ id: value.id,
21
+ projectRootId,
22
+ workspace,
23
+ name: String(value.name || 'Browser').trim().slice(0, 120) || 'Browser',
24
+ status: STATUSES.has(value.status) ? value.status : 'failed',
25
+ generation: Number.isSafeInteger(value.generation) && value.generation >= 0 ? value.generation : 0,
26
+ revision: Number.isSafeInteger(value.revision) && value.revision >= 0 ? value.revision : 0,
27
+ url: String(value.url || 'about:blank').slice(0, 8_192) || 'about:blank',
28
+ title: String(value.title || '').slice(0, 512),
29
+ browserKind: String(value.browserKind || ''),
30
+ error: String(value.error || '').slice(0, 2_000),
31
+ createdAt: Number.isFinite(value.createdAt) ? value.createdAt : Date.now(),
32
+ updatedAt: Number.isFinite(value.updatedAt) ? value.updatedAt : Date.now(),
33
+ };
34
+ }
35
+
36
+ class BrowserResourceStore {
37
+ constructor(configDir, options = {}) {
38
+ this.directory = storageLayout.browserResourcesDir(configDir);
39
+ this.file = storageLayout.browserResourcesFile(configDir);
40
+ this.writeJson = options.writeJson || ((file, value) => atomicWriteJson(file, value, { mode: 0o600 }));
41
+ this.resources = new Map();
42
+ this.revision = 0;
43
+ }
44
+
45
+ init() {
46
+ fs.mkdirSync(this.directory, { recursive: true });
47
+ let parsed = {};
48
+ try {
49
+ parsed = JSON.parse(fs.readFileSync(this.file, 'utf8'));
50
+ } catch (error) {
51
+ if (error?.code !== 'ENOENT') {
52
+ console.warn('Failed to read Browser resources:', error?.message || error);
53
+ }
54
+ }
55
+ this.revision = Number.isSafeInteger(parsed?.revision) && parsed.revision >= 0
56
+ ? parsed.revision
57
+ : 0;
58
+ const resources = Array.isArray(parsed?.resources) ? parsed.resources : [];
59
+ for (const value of resources) {
60
+ const resource = normalizeResource(value);
61
+ if (!resource || this.resources.has(resource.id)) continue;
62
+ if (resource.status === 'running' || resource.status === 'starting' || resource.status === 'stopping') {
63
+ resource.status = 'failed';
64
+ resource.error = 'Farming restarted before the Browser runtime could be reconciled';
65
+ resource.updatedAt = Date.now();
66
+ }
67
+ this.resources.set(resource.id, resource);
68
+ }
69
+ this.commit();
70
+ }
71
+
72
+ list() {
73
+ return [...this.resources.values()]
74
+ .map(resource => ({ ...resource }))
75
+ .sort((left, right) => left.createdAt - right.createdAt);
76
+ }
77
+
78
+ get(id) {
79
+ const resource = this.resources.get(id);
80
+ return resource ? { ...resource } : null;
81
+ }
82
+
83
+ create(input) {
84
+ const resource = normalizeResource({
85
+ id: createBrowserId(),
86
+ projectRootId: input.projectRootId,
87
+ workspace: input.workspace,
88
+ name: input.name || 'Browser',
89
+ status: 'stopped',
90
+ generation: 0,
91
+ revision: 0,
92
+ url: input.url || 'about:blank',
93
+ title: '',
94
+ browserKind: '',
95
+ error: '',
96
+ createdAt: Date.now(),
97
+ updatedAt: Date.now(),
98
+ });
99
+ if (!resource) throw new Error('Invalid Browser resource');
100
+ this.resources.set(resource.id, resource);
101
+ this.commit();
102
+ return { ...resource };
103
+ }
104
+
105
+ update(id, patch) {
106
+ const current = this.resources.get(id);
107
+ if (!current) return null;
108
+ const next = normalizeResource({
109
+ ...current,
110
+ ...patch,
111
+ id: current.id,
112
+ projectRootId: current.projectRootId,
113
+ workspace: current.workspace,
114
+ revision: current.revision + 1,
115
+ updatedAt: Date.now(),
116
+ });
117
+ if (!next) throw new Error('Invalid Browser resource update');
118
+ this.resources.set(id, next);
119
+ this.commit();
120
+ return { ...next };
121
+ }
122
+
123
+ delete(id) {
124
+ if (!this.resources.delete(id)) return false;
125
+ this.commit();
126
+ return true;
127
+ }
128
+
129
+ commit() {
130
+ const previousRevision = this.revision;
131
+ this.revision += 1;
132
+ try {
133
+ this.writeJson(this.file, {
134
+ version: STORE_VERSION,
135
+ revision: this.revision,
136
+ resources: this.list(),
137
+ updatedAt: Date.now(),
138
+ });
139
+ } catch (error) {
140
+ this.revision = previousRevision;
141
+ throw error;
142
+ }
143
+ }
144
+ }
145
+
146
+ module.exports = {
147
+ BrowserResourceStore,
148
+ RESOURCE_ID_RE,
149
+ };