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
package/backend/server.js CHANGED
@@ -42,6 +42,10 @@ 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');
@@ -129,6 +133,11 @@ async function requireAgentRecoveryForHttp(res) {
129
133
  const themeManager = new ThemeManager({ configDir: configManager.farmingDir });
130
134
  const workspaceFileService = new WorkspaceFileService();
131
135
  const workspaceRootRegistry = new WorkspaceRootRegistry(agentManager);
136
+ const browserResourceManager = new BrowserResourceManager({
137
+ configDir: configManager.farmingDir,
138
+ isEnabled: () => configManager.getSettings().browserExtensionEnabled === true,
139
+ });
140
+ browserResourceManager.init();
132
141
  const updateService = new FarmingUpdateService({
133
142
  rootDir: path.join(__dirname, '..'),
134
143
  configDir: configManager.farmingDir,
@@ -527,6 +536,7 @@ app.use(BASE_PATH || '/', express.static(staticAppDir, { index: false }));
527
536
  app.use(routePath(BASE_PATH, '/api/files'), createWorkspaceFileRouter(agentManager, workspaceFileService, {
528
537
  rootRegistry: workspaceRootRegistry,
529
538
  }));
539
+ app.use(routePath(BASE_PATH, '/api/browsers'), createBrowserRouter(browserResourceManager, workspaceRootRegistry));
530
540
 
531
541
  app.use(routePath(BASE_PATH, '/api/review-sessions'), createReviewSessionRouter(reviewSessionService));
532
542
  app.use(routePath(BASE_PATH, '/api/reviews'), createReviewDiffRouter(reviewDiffService, reviewSessionService));
@@ -1554,7 +1564,13 @@ app.post(routePath(BASE_PATH, '/api/agents/:agentId/fork'), express.json(), asyn
1554
1564
  res.status(400).json({ error: 'Unsupported Fork target runtime' });
1555
1565
  return;
1556
1566
  }
1567
+ const requestId = typeof req.body?.requestId === 'string' ? req.body.requestId.trim() : '';
1568
+ if (!/^[A-Za-z0-9._:-]{1,160}$/.test(requestId)) {
1569
+ res.status(400).json({ error: 'Fork requires a valid requestId' });
1570
+ return;
1571
+ }
1557
1572
  const result = await agentManager.forkAgent(req.params.agentId, mode, {
1573
+ requestId,
1558
1574
  ...(targetRuntime ? { targetRuntime } : {}),
1559
1575
  ...(Number.isSafeInteger(req.body?.expectedRevision)
1560
1576
  ? { expectedRevision: req.body.expectedRevision }
@@ -1562,7 +1578,7 @@ app.post(routePath(BASE_PATH, '/api/agents/:agentId/fork'), express.json(), asyn
1562
1578
  });
1563
1579
  if (result.error) {
1564
1580
  const status = result.error === 'Agent not found' ? 404 : 400;
1565
- res.status(status).json({ error: result.error });
1581
+ res.status(status).json(result);
1566
1582
  return;
1567
1583
  }
1568
1584
 
@@ -1571,32 +1587,12 @@ app.post(routePath(BASE_PATH, '/api/agents/:agentId/fork'), express.json(), asyn
1571
1587
  result.projectWorkspaces = membership.projectWorkspaces;
1572
1588
  result.pinnedProjectWorkspaces = membership.pinnedProjectWorkspaces;
1573
1589
  } 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
1590
  broadcastState();
1594
1591
  const mountError = error.message || 'Failed to create Project';
1595
1592
  res.status(500).json({
1596
- error: rollbackError
1597
- ? `${mountError}. Rollback failed: ${rollbackError}`
1598
- : mountError,
1599
- ...(rollbackError ? { rollbackError } : {}),
1593
+ ...result,
1594
+ error: `${mountError}. Retry the same Fork request to reconcile Project membership.`,
1595
+ retryable: true,
1600
1596
  });
1601
1597
  return;
1602
1598
  }
@@ -1676,31 +1672,26 @@ app.patch(routePath(BASE_PATH, '/api/projects/name'), express.json(), (req, res)
1676
1672
  });
1677
1673
 
1678
1674
  app.post(routePath(BASE_PATH, '/api/projects/create-worktree'), express.json(), async (req, res) => {
1679
- let created = null;
1675
+ const requestId = typeof req.body?.requestId === 'string' ? req.body.requestId.trim() : '';
1676
+ if (!/^[A-Za-z0-9._:-]{1,160}$/.test(requestId)) {
1677
+ res.status(400).json({ error: 'Project worktree creation requires a valid requestId' });
1678
+ return;
1679
+ }
1680
1680
  try {
1681
1681
  const root = resolveProjectActionRoot(req.body?.rootId);
1682
- created = await agentManager.createPermanentWorktree(root.canonicalPath);
1683
- const membership = configManager.mountProjectWorkspace(created.workspace);
1682
+ const created = await agentManager.createPermanentWorktree(root.canonicalPath, { requestId });
1684
1683
  broadcastState();
1685
1684
  res.status(201).json({
1686
- workspace: created.workspace,
1687
- branch: created.branch,
1688
- projectWorkspaces: membership.projectWorkspaces,
1689
- pinnedProjectWorkspaces: membership.pinnedProjectWorkspaces,
1685
+ ...created,
1690
1686
  });
1691
1687
  } 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
1688
  const status = error instanceof WorkspaceFileError ? error.statusCode : 400;
1698
- const createError = error.message || 'Failed to create permanent worktree';
1689
+ const operation = configManager.getProjectOperation?.(requestId);
1699
1690
  res.status(status).json({
1700
- error: rollbackError
1701
- ? `${createError}. Rollback failed: ${rollbackError}`
1702
- : createError,
1703
- ...(rollbackError ? { rollbackError } : {}),
1691
+ error: error.message || 'Failed to create permanent worktree',
1692
+ requestId,
1693
+ ...(operation?.state === 'pending' ? { retryable: true } : {}),
1694
+ ...(['unknown', 'blocked'].includes(operation?.state) ? { uncertain: true } : {}),
1704
1695
  });
1705
1696
  }
1706
1697
  });
@@ -1709,45 +1700,26 @@ app.post(routePath(BASE_PATH, '/api/projects/delete-worktree'), express.json(),
1709
1700
  if (!await requireAgentRecoveryForHttp(res)) return;
1710
1701
  const body = req.body || {};
1711
1702
  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);
1703
+ const requestId = typeof body.requestId === 'string' ? body.requestId.trim() : '';
1704
+ if (!/^[A-Za-z0-9._:-]{1,160}$/.test(requestId)) {
1705
+ res.status(400).json({ error: 'Project worktree deletion requires a valid requestId' });
1723
1706
  return;
1724
1707
  }
1725
1708
 
1726
- const result = await agentManager.deleteForkWorktreeProject(inspected.workspace, { force: body.force === true });
1709
+ const result = await agentManager.deleteForkWorktreeProject(workspace, {
1710
+ force: body.force === true,
1711
+ requestId,
1712
+ });
1727
1713
  if (result.error) {
1728
1714
  broadcastState();
1729
- const status = result.requiresForce ? 409 : 400;
1715
+ const status = result.requiresForce
1716
+ ? 409
1717
+ : (result.error === 'Workspace not found' || result.error === 'Workspace is required' ? 404 : 400);
1730
1718
  res.status(status).json(result);
1731
1719
  return;
1732
1720
  }
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
1721
  broadcastState();
1746
- res.json({
1747
- ...result,
1748
- projectWorkspaces: membership.projectWorkspaces,
1749
- pinnedProjectWorkspaces: membership.pinnedProjectWorkspaces,
1750
- });
1722
+ res.json(result);
1751
1723
  });
1752
1724
 
1753
1725
  app.post(routePath(BASE_PATH, '/api/codex/sessions/:sessionId/resume'), express.json(), async (req, res) => {
@@ -2232,11 +2204,35 @@ async function autoResumeMainPageAgentSessions() {
2232
2204
  }
2233
2205
  }
2234
2206
 
2235
- app.post(routePath(BASE_PATH, '/api/settings'), express.json(), (req, res) => {
2207
+ app.post(routePath(BASE_PATH, '/api/settings'), express.json(), async (req, res) => {
2236
2208
  const settingsPatch = { ...(req.body || {}) };
2237
2209
  delete settingsPatch.mainPageSessionKeys;
2238
2210
  delete settingsPatch.projectWorkspaces;
2239
2211
  delete settingsPatch.pinnedProjectWorkspaces;
2212
+ const changesBrowserExtension = Object.prototype.hasOwnProperty.call(settingsPatch, 'browserExtensionEnabled');
2213
+ const browserExtensionEnabled = settingsPatch.browserExtensionEnabled === true;
2214
+ if (changesBrowserExtension && browserExtensionEnabled && !browserResourceManager.capability().browser) {
2215
+ res.status(400).json({
2216
+ error: 'Install a Chromium-based browser to use the system Browser in Farming',
2217
+ code: 'BROWSER_EXECUTABLE_NOT_FOUND',
2218
+ });
2219
+ return;
2220
+ }
2221
+ if (
2222
+ changesBrowserExtension
2223
+ && !browserExtensionEnabled
2224
+ && configManager.getSettings().browserExtensionEnabled === true
2225
+ ) {
2226
+ try {
2227
+ await browserResourceManager.stopAll();
2228
+ } catch (error) {
2229
+ res.status(Number(error?.status) || 500).json({
2230
+ error: error?.message || 'Browser extension could not be disabled',
2231
+ code: error?.code || 'BROWSER_DISABLE_FAILED',
2232
+ });
2233
+ return;
2234
+ }
2235
+ }
2240
2236
  configManager.updateSettings(settingsPatch);
2241
2237
  agentSessionsCache.invalidate();
2242
2238
  res.json({
@@ -2299,6 +2295,20 @@ app.get(routePath(BASE_PATH, '/api/themes/:themeId'), (req, res) => {
2299
2295
 
2300
2296
  wss.on('connection', (ws, req) => {
2301
2297
  const url = new URL(req.url || '/', `http://${req.headers.host || 'localhost'}`);
2298
+ const viewerPrefix = routePath(BASE_PATH, '/api/browsers/');
2299
+ if (url.pathname.startsWith(viewerPrefix) && url.pathname.endsWith('/viewer')) {
2300
+ if (authEnabled && !tokenAuth.verifyWebSocket(req)) {
2301
+ ws.close(4001, 'Authentication required');
2302
+ return;
2303
+ }
2304
+ const encodedId = url.pathname.slice(viewerPrefix.length, -'/viewer'.length);
2305
+ try {
2306
+ browserResourceManager.attachViewer(decodeURIComponent(encodedId), ws);
2307
+ } catch (error) {
2308
+ ws.close(4004, error?.message || 'Browser resource not found');
2309
+ }
2310
+ return;
2311
+ }
2302
2312
  if (url.pathname !== WS_PATH) {
2303
2313
  ws.close(1008, 'Invalid path');
2304
2314
  return;
@@ -2442,9 +2452,9 @@ async function sendInputMessage(ws, data) {
2442
2452
 
2443
2453
  async function sendComposerInputMessage(ws, data) {
2444
2454
  const targetAgentId = resolveInputTargetAgentId(ws, data);
2445
- const requestId = typeof data.requestId === 'string' ? data.requestId : '';
2455
+ const requestId = typeof data.requestId === 'string' ? data.requestId.trim() : '';
2446
2456
  const responseAgentId = targetAgentId || (typeof data.agentId === 'string' ? data.agentId : '');
2447
- const respond = (accepted, message = '') => {
2457
+ const respond = (accepted, message = '', uncertain = false) => {
2448
2458
  if (!requestId || !responseAgentId) return;
2449
2459
  ws.send(JSON.stringify({
2450
2460
  type: 'composer-input-result',
@@ -2452,9 +2462,22 @@ async function sendComposerInputMessage(ws, data) {
2452
2462
  agentId: responseAgentId,
2453
2463
  accepted,
2454
2464
  ...(message ? { message } : {}),
2465
+ ...(uncertain ? { uncertain: true } : {}),
2455
2466
  }));
2456
2467
  };
2457
2468
  const message = typeof data.message === 'string' ? data.message : '';
2469
+ if (!/^[A-Za-z0-9._:-]{1,160}$/.test(requestId)) {
2470
+ if (responseAgentId) {
2471
+ ws.send(JSON.stringify({
2472
+ type: 'composer-input-result',
2473
+ requestId: requestId || 'invalid-request',
2474
+ agentId: responseAgentId,
2475
+ accepted: false,
2476
+ message: 'Structured Composer input requires a valid requestId',
2477
+ }));
2478
+ }
2479
+ return;
2480
+ }
2458
2481
  const content = [];
2459
2482
  if (message.trim()) content.push({ type: 'text', text: message });
2460
2483
  const attachmentsRoot = path.resolve(imageAttachmentsDir());
@@ -2496,11 +2519,11 @@ async function sendComposerInputMessage(ws, data) {
2496
2519
  return;
2497
2520
  }
2498
2521
  try {
2499
- await agentManager.sendComposerMessage(targetAgentId, content);
2522
+ await agentManager.sendComposerMessage(targetAgentId, content, { requestId });
2500
2523
  respond(true);
2501
2524
  } catch (error) {
2502
2525
  const message = error && error.message ? error.message : 'Failed to send Composer message';
2503
- if (requestId) respond(false, message);
2526
+ if (requestId) respond(false, message, error?.uncertain === true);
2504
2527
  else ws.send(JSON.stringify({ type: 'error', message }));
2505
2528
  }
2506
2529
  }
@@ -3113,11 +3136,12 @@ async function shutdownServer(options = {}) {
3113
3136
  : process.env.FARMING_NATIVE_PTY_HOST_PERSIST !== '0';
3114
3137
 
3115
3138
  try {
3139
+ await browserResourceManager.dispose();
3116
3140
  await agentManager.dispose({ preserveTerminalHost });
3117
3141
  } catch (error) {
3118
3142
  const teardownFrozen = agentManager.disposing === true;
3119
3143
  shutdownStarted = false;
3120
- console.error('Farming shutdown blocked because Agent runtime cleanup could not be verified:', error);
3144
+ console.error('Farming shutdown blocked because runtime cleanup could not be verified:', error);
3121
3145
  if (teardownFrozen) {
3122
3146
  clearBroadcastTimers();
3123
3147
  tokenAuth.cleanup();
@@ -3129,7 +3153,7 @@ async function shutdownServer(options = {}) {
3129
3153
  }
3130
3154
  if (options.exit === true) process.exitCode = 1;
3131
3155
  return {
3132
- error: error?.message || 'Agent runtime cleanup could not be verified',
3156
+ error: error?.message || 'Runtime cleanup could not be verified',
3133
3157
  ...(teardownFrozen ? { frozen: true } : {}),
3134
3158
  };
3135
3159
  }
@@ -3247,6 +3271,7 @@ module.exports = {
3247
3271
  server,
3248
3272
  wss,
3249
3273
  agentManager,
3274
+ browserResourceManager,
3250
3275
  workspaceFileService,
3251
3276
  handleMessage,
3252
3277
  resolveCliBinDir,
@@ -49,6 +49,22 @@ function usageHistoryCacheFile(configDir) {
49
49
  return path.join(historyDir(configDir), 'usage-history-v2.sqlite3');
50
50
  }
51
51
 
52
+ function browserResourcesDir(configDir) {
53
+ return path.join(configDir, 'browsers');
54
+ }
55
+
56
+ function browserResourcesFile(configDir) {
57
+ return path.join(browserResourcesDir(configDir), 'resources.json');
58
+ }
59
+
60
+ function browserProfileDir(configDir, browserId) {
61
+ return path.join(browserResourcesDir(configDir), browserId, 'profile');
62
+ }
63
+
64
+ function farmingAgentBootstrapFile(configDir) {
65
+ return path.join(configDir, 'farming-agent-bootstrap.zh_cn.md');
66
+ }
67
+
52
68
  function sessionIndexFile(configDir) {
53
69
  return path.join(sessionsDir(configDir), 'index.json');
54
70
  }
@@ -124,6 +140,10 @@ function farmingNetTrustFile(configDir) {
124
140
  module.exports = {
125
141
  acpCheckpointsDir,
126
142
  agentStateFile,
143
+ browserProfileDir,
144
+ browserResourcesDir,
145
+ browserResourcesFile,
146
+ farmingAgentBootstrapFile,
127
147
  farmingNetInstancesFile,
128
148
  farmingNetServerLogFile,
129
149
  farmingNetServerStateFile,
@@ -4,6 +4,7 @@ const os = require('os');
4
4
  const path = require('path');
5
5
  const { inspectGitWorktree } = require('./git-worktree-info');
6
6
  const { WorkspaceFileError } = require('./workspace-file-service');
7
+ const { PreviewSessionManager } = require('./preview-session-manager');
7
8
  const {
8
9
  GLOBAL_WORKSPACE_FILES_AGENT_ID,
9
10
  GLOBAL_WORKSPACE_FILES_ROOT,
@@ -57,6 +58,53 @@ function isSameOrDescendantPath(root, target) {
57
58
  return relative === '' || Boolean(relative) && !relative.startsWith('..') && !path.isAbsolute(relative);
58
59
  }
59
60
 
61
+ function previewAuthorizedRootForTarget(allowedRoots, target) {
62
+ return allowedRoots
63
+ .filter(root => isSameOrDescendantPath(root, target))
64
+ .sort((left, right) => right.length - left.length)[0] || '';
65
+ }
66
+
67
+ function normalizePreviewAssetPath(value) {
68
+ const normalized = path.posix.normalize(String(value || '').replace(/\\/g, '/').replace(/^\/+/, ''));
69
+ if (!normalized || normalized === '.') return '';
70
+ if (normalized === '..' || normalized.startsWith('../') || path.posix.isAbsolute(normalized)) {
71
+ throw new WorkspaceFileError('preview path must stay inside the authorized root', 403);
72
+ }
73
+ return normalized;
74
+ }
75
+
76
+ function previewContentSecurityPolicy() {
77
+ return [
78
+ "default-src 'none'",
79
+ "script-src 'none'",
80
+ "style-src 'self' 'unsafe-inline'",
81
+ "img-src 'self' data:",
82
+ "font-src 'self' data:",
83
+ "media-src 'self' data:",
84
+ "connect-src 'none'",
85
+ "frame-src 'none'",
86
+ "object-src 'none'",
87
+ "base-uri 'none'",
88
+ "form-action 'none'",
89
+ "frame-ancestors 'self'",
90
+ ].join('; ');
91
+ }
92
+
93
+ function escapeHtmlAttribute(value) {
94
+ return String(value || '')
95
+ .replace(/&/g, '&')
96
+ .replace(/"/g, '"')
97
+ .replace(/</g, '&lt;')
98
+ .replace(/>/g, '&gt;');
99
+ }
100
+
101
+ function rewritePreviewHtmlRootReferences(source, rootUrl) {
102
+ const safeRootUrl = escapeHtmlAttribute(rootUrl);
103
+ return String(source || '')
104
+ .replace(/(\b(?:href|src|poster)\s*=\s*["'])\/(?!\/)/gi, `$1${safeRootUrl}`)
105
+ .replace(/(url\(\s*["']?)\/(?!\/)/gi, `$1${safeRootUrl}`);
106
+ }
107
+
60
108
  function globalUserPathToAbsolute(userPath = '') {
61
109
  const normalized = String(userPath || '')
62
110
  .trim()
@@ -136,6 +184,23 @@ function assertGlobalWorkspacePathAllowed(agentManager, userPath, options = {})
136
184
  return { target: requestedTarget, allowedRoots };
137
185
  }
138
186
 
187
+ function assertExactExternalFileReadable(userPath) {
188
+ const requestedTarget = globalUserPathToAbsolute(userPath);
189
+ let target;
190
+ let stat;
191
+ try {
192
+ target = fs.realpathSync(requestedTarget);
193
+ stat = fs.statSync(target);
194
+ fs.accessSync(target, fs.constants.R_OK);
195
+ } catch {
196
+ throw new WorkspaceFileError('external file is not readable', 403);
197
+ }
198
+ if (!stat.isFile()) {
199
+ throw new WorkspaceFileError('external path must be a file', 400);
200
+ }
201
+ return relativeGlobalPath(target);
202
+ }
203
+
139
204
  function globalSyntheticTree(agentManager, userPath = '') {
140
205
  const { target, allowedRoots } = assertGlobalWorkspacePathAllowed(agentManager, userPath, {
141
206
  allowAllowedRootAncestor: true,
@@ -205,6 +270,7 @@ function readOptionsForAgent(agentManager, agentId) {
205
270
  function createWorkspaceFileRouter(agentManager, fileService, options = {}) {
206
271
  const router = express.Router();
207
272
  const rootRegistry = options.rootRegistry || workspaceRootRegistryFor(agentManager);
273
+ const previewSessions = options.previewSessionManager || new PreviewSessionManager();
208
274
 
209
275
  router.use(express.json({ limit: '3mb' }));
210
276
 
@@ -233,11 +299,14 @@ function createWorkspaceFileRouter(agentManager, fileService, options = {}) {
233
299
  router.get('/file', async (req, res) => {
234
300
  try {
235
301
  const rootRef = workspaceRef(req.query);
236
- if (isGlobalWorkspaceFilesAgentId(rootRef)) {
237
- assertGlobalWorkspacePathAllowed(agentManager, req.query.path || '');
302
+ const requestPath = isGlobalWorkspaceFilesAgentId(rootRef) && req.query.exact === '1'
303
+ ? assertExactExternalFileReadable(req.query.path || '')
304
+ : req.query.path || '';
305
+ if (isGlobalWorkspaceFilesAgentId(rootRef) && req.query.exact !== '1') {
306
+ assertGlobalWorkspacePathAllowed(agentManager, requestPath);
238
307
  }
239
308
  const { root, rootId } = resolveRequestRoot(req.query);
240
- const file = await fileService.readFile(root, req.query.path || '', readOptionsForAgent(agentManager, rootRef));
309
+ const file = await fileService.readFile(root, requestPath, readOptionsForAgent(agentManager, rootRef));
241
310
  res.json({ rootId, root, file });
242
311
  } catch (error) {
243
312
  sendWorkspaceFileError(res, error);
@@ -247,11 +316,14 @@ function createWorkspaceFileRouter(agentManager, fileService, options = {}) {
247
316
  router.get('/raw', async (req, res) => {
248
317
  try {
249
318
  const rootRef = workspaceRef(req.query);
250
- if (isGlobalWorkspaceFilesAgentId(rootRef)) {
251
- assertGlobalWorkspacePathAllowed(agentManager, req.query.path || '');
319
+ const requestPath = isGlobalWorkspaceFilesAgentId(rootRef) && req.query.exact === '1'
320
+ ? assertExactExternalFileReadable(req.query.path || '')
321
+ : req.query.path || '';
322
+ if (isGlobalWorkspaceFilesAgentId(rootRef) && req.query.exact !== '1') {
323
+ assertGlobalWorkspacePathAllowed(agentManager, requestPath);
252
324
  }
253
325
  const { root } = resolveRequestRoot(req.query);
254
- const file = await fileService.readPreviewFile(root, req.query.path || '', readOptionsForAgent(agentManager, rootRef));
326
+ const file = await fileService.readPreviewFile(root, requestPath, readOptionsForAgent(agentManager, rootRef));
255
327
  res
256
328
  .status(200)
257
329
  .type(file.preview.mediaType)
@@ -264,6 +336,98 @@ function createWorkspaceFileRouter(agentManager, fileService, options = {}) {
264
336
  }
265
337
  });
266
338
 
339
+ router.post('/previews', async (req, res) => {
340
+ try {
341
+ const body = req.body || {};
342
+ const rootRef = workspaceRef(body);
343
+ let entryPath = String(body.path || '').trim();
344
+ if (!/\.html?$/i.test(entryPath)) {
345
+ throw new WorkspaceFileError('HTML preview requires an .html or .htm file', 415);
346
+ }
347
+
348
+ let authorizedRoot;
349
+ if (isGlobalWorkspaceFilesAgentId(rootRef)) {
350
+ if (body.exact === true) {
351
+ entryPath = assertExactExternalFileReadable(entryPath);
352
+ authorizedRoot = path.dirname(realPathIfPresent(globalUserPathToAbsolute(entryPath)));
353
+ if (authorizedRoot === path.parse(authorizedRoot).root) {
354
+ throw new WorkspaceFileError('external HTML preview directory cannot be the filesystem root', 403);
355
+ }
356
+ } else {
357
+ const authorization = assertGlobalWorkspacePathAllowed(agentManager, entryPath);
358
+ authorizedRoot = previewAuthorizedRootForTarget(authorization.allowedRoots, realPathIfPresent(authorization.target));
359
+ if (!authorizedRoot) throw new WorkspaceFileError('global file path is outside allowed workspaces', 403);
360
+ }
361
+ }
362
+
363
+ const { root, rootId } = resolveRequestRoot(body);
364
+ await fileService.readFile(root, entryPath, { allowedExternalRoots: [] });
365
+ const session = previewSessions.createStatic({
366
+ rootId,
367
+ workspaceRoot: root,
368
+ authorizedRoot: realPathIfPresent(authorizedRoot || root),
369
+ entryPath,
370
+ baseDirectory: path.posix.dirname(entryPath) === '.' ? '' : path.posix.dirname(entryPath),
371
+ });
372
+ res.status(201).json({
373
+ preview: {
374
+ id: session.id,
375
+ kind: session.kind,
376
+ expiresAt: session.expiresAt,
377
+ },
378
+ });
379
+ } catch (error) {
380
+ sendWorkspaceFileError(res, error);
381
+ }
382
+ });
383
+
384
+ router.get('/previews/:sessionId/:scope/*', async (req, res) => {
385
+ try {
386
+ const session = previewSessions.get(req.params.sessionId);
387
+ if (!session) throw new WorkspaceFileError('preview session not found or expired', 404);
388
+ const scope = req.params.scope;
389
+ if (scope !== 'base' && scope !== 'root') {
390
+ throw new WorkspaceFileError('preview scope is invalid', 400);
391
+ }
392
+ const assetPath = normalizePreviewAssetPath(req.params[0] || '');
393
+ const resourcePath = scope === 'base'
394
+ ? path.posix.join(session.baseDirectory, assetPath)
395
+ : assetPath;
396
+ if (!resourcePath) throw new WorkspaceFileError('preview resource path is required', 400);
397
+
398
+ const absoluteTarget = realPathIfPresent(path.resolve(session.workspaceRoot, resourcePath));
399
+ if (!isSameOrDescendantPath(session.authorizedRoot, absoluteTarget)) {
400
+ throw new WorkspaceFileError('preview path must stay inside the authorized root', 403);
401
+ }
402
+
403
+ const file = await fileService.readResourceFile(session.workspaceRoot, resourcePath, {
404
+ allowedExternalRoots: [],
405
+ });
406
+ const isHtml = /\.html?$/i.test(file.path);
407
+ const body = isHtml
408
+ ? Buffer.from(rewritePreviewHtmlRootReferences(
409
+ file.buffer.toString('utf8'),
410
+ `${req.baseUrl}/previews/${encodeURIComponent(session.id)}/root/`,
411
+ ))
412
+ : file.buffer;
413
+ res
414
+ .status(200)
415
+ .type(path.extname(file.path) || 'application/octet-stream')
416
+ .set('Cache-Control', 'no-store')
417
+ .set('X-Content-Type-Options', 'nosniff')
418
+ .set('Content-Length', String(body.length));
419
+ if (isHtml) res.set('Content-Security-Policy', previewContentSecurityPolicy());
420
+ res.send(body);
421
+ } catch (error) {
422
+ sendWorkspaceFileError(res, error);
423
+ }
424
+ });
425
+
426
+ router.delete('/previews/:sessionId', (req, res) => {
427
+ const deleted = previewSessions.delete(req.params.sessionId);
428
+ res.status(deleted ? 204 : 404).end();
429
+ });
430
+
267
431
  router.put('/file', async (req, res) => {
268
432
  try {
269
433
  const body = req.body || {};
@@ -533,6 +697,7 @@ module.exports = {
533
697
  PROJECT_FILES_WORKSPACE_PREFIX,
534
698
  PROJECT_FILES_AGENT_PREFIX: PROJECT_FILES_WORKSPACE_PREFIX,
535
699
  assertGlobalWorkspacePathAllowed,
700
+ assertExactExternalFileReadable,
536
701
  createWorkspaceFileRouter,
537
702
  globalWorkspaceAllowedRoots,
538
703
  isGlobalWorkspaceFilesAgentId,
@@ -2199,6 +2199,25 @@ class WorkspaceFileService {
2199
2199
  };
2200
2200
  }
2201
2201
 
2202
+ async readResourceFile(workspaceRoot, userPath, options = {}) {
2203
+ const { target, relativePath } = await this.resolvePath(workspaceRoot, userPath, options);
2204
+ const stat = await fsp.stat(target);
2205
+ if (!stat.isFile()) {
2206
+ throw new WorkspaceFileError('path must be a file', 400);
2207
+ }
2208
+ if (stat.size > this.maxPreviewFileSize) {
2209
+ throw new WorkspaceFileError('file is too large to preview', 413, { size: stat.size });
2210
+ }
2211
+ const buffer = await fsp.readFile(target);
2212
+ return {
2213
+ path: relativePath,
2214
+ buffer,
2215
+ size: stat.size,
2216
+ mtimeMs: stat.mtimeMs,
2217
+ sha1: sha1(buffer),
2218
+ };
2219
+ }
2220
+
2202
2221
  async blameCapability(workspaceRoot, userPath, options = {}) {
2203
2222
  const { root, target, relativePath, actualRelativePath, external } = await this.resolvePath(workspaceRoot, userPath, options);
2204
2223
  const capability = (available, reason = '') => ({
@@ -27,6 +27,7 @@ function canonicalWorkspacePath(value) {
27
27
  function rootIdForPath(value) {
28
28
  const canonicalPath = canonicalWorkspacePath(value);
29
29
  if (!canonicalPath) return '';
30
+ if (canonicalPath === GLOBAL_WORKSPACE_FILES_ROOT) return GLOBAL_WORKSPACE_ROOT_ID;
30
31
  let hash = 0xcbf29ce484222325n;
31
32
  for (const byte of Buffer.from(canonicalPath, 'utf8')) {
32
33
  hash ^= BigInt(byte);
@@ -77,15 +78,16 @@ class WorkspaceRootRegistry {
77
78
  if (current && current.canonicalPath !== canonicalPath) {
78
79
  throw new WorkspaceFileError('workspace root identity collision', 409);
79
80
  }
81
+ const isGlobalRoot = rootId === GLOBAL_WORKSPACE_ROOT_ID;
80
82
  const root = Object.freeze({
81
83
  rootId,
82
- kind: options.kind || current?.kind || 'directory',
84
+ kind: isGlobalRoot ? 'global' : (options.kind || current?.kind || 'directory'),
83
85
  canonicalPath,
84
86
  repositoryId: options.repositoryId || current?.repositoryId || '',
85
87
  accessPolicy: Object.freeze({
86
- readOnly: options.accessPolicy?.readOnly === true,
87
- watch: options.accessPolicy?.watch !== false,
88
- externalReads: options.accessPolicy?.externalReads !== false,
88
+ readOnly: isGlobalRoot || options.accessPolicy?.readOnly === true,
89
+ watch: isGlobalRoot ? false : options.accessPolicy?.watch !== false,
90
+ externalReads: isGlobalRoot ? false : options.accessPolicy?.externalReads !== false,
89
91
  }),
90
92
  });
91
93
  this.roots.set(rootId, root);