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.
- package/README.md +9 -2
- package/README.zh_cn.md +9 -2
- package/THIRD_PARTY_NOTICES.md +3 -0
- package/backend/acp-runtime.js +15 -1
- package/backend/agent-manager.js +1054 -94
- package/backend/atomic-json-store.js +37 -0
- package/backend/cli-agents.js +9 -7
- package/backend/config-manager.js +167 -58
- package/backend/control-api.js +2 -1
- package/backend/farming-agent-bootstrap.js +49 -0
- package/backend/farming-agent-bootstrap.zh_cn.md +5 -0
- package/backend/farming-app-cli.js +9 -2
- package/backend/farming-cli.js +55 -0
- package/backend/farming-session-store.js +92 -89
- package/backend/main-agent-skills.js +17 -0
- package/backend/native-pty-host-client.js +99 -7
- package/backend/native-pty-host-path.js +20 -0
- package/backend/native-pty-host.js +6 -5
- package/backend/preview-session-manager.js +68 -0
- package/backend/provider-adapters.js +44 -5
- package/backend/review-session-store.js +32 -25
- package/backend/review-state-store.js +13 -14
- package/backend/run-history-store.js +13 -16
- package/backend/server.js +103 -78
- package/backend/storage-layout.js +20 -0
- package/backend/workspace-file-router.js +171 -6
- package/backend/workspace-file-service.js +19 -0
- package/backend/workspace-root-registry.js +6 -4
- package/dist/assets/App-CM7Cy-l4.css +1 -0
- package/dist/assets/App-CPG4WcjO.js +658 -0
- package/dist/assets/{FileEditorMarkdownPreview-KbnE8VC8.js → FileEditorMarkdownPreview-DYOMm-b1.js} +5 -5
- package/dist/assets/FileEditorPane-DXXq82aM.js +2 -0
- package/dist/assets/IconGlyphs-DrdPjYXr.js +1 -0
- package/dist/assets/ProjectFilesSection-CP66XVPl.js +12 -0
- package/dist/assets/{ReviewPage-L1vh1Ggs.js → ReviewPage-w21MEPD6.js} +1 -1
- package/dist/assets/{abnfDiagram-VRR7QNED-nnglrXFi.js → abnfDiagram-VRR7QNED-BY1qwq8A.js} +1 -1
- package/dist/assets/api-w2x5QfxB.js +1 -0
- package/dist/assets/{arc-CWoVlxJw.js → arc-DgZRAlwj.js} +1 -1
- package/dist/assets/{architectureDiagram-ZJ3FMSHR-BEXMaFfY.js → architectureDiagram-ZJ3FMSHR-jFn144od.js} +1 -1
- package/dist/assets/{blockDiagram-677ZJIJ3-1mCezSBk.js → blockDiagram-677ZJIJ3-BmRjlV0H.js} +1 -1
- package/dist/assets/{c4Diagram-LMCZKHZV-DhTiI6Uv.js → c4Diagram-LMCZKHZV-DUKul6tT.js} +1 -1
- package/dist/assets/{workspace-editor-monaco-DLlws5Ig.js → channel-CI9j3QFD.js} +1 -1
- package/dist/assets/chunk-2Q5K7J3B-87UnWaoh.js +1 -0
- package/dist/assets/{chunk-32BRIVSS-CCdsKd73.js → chunk-32BRIVSS-rGVu23o5.js} +1 -1
- package/dist/assets/{chunk-52WLFC77-Gncjk4LF.js → chunk-52WLFC77-CvFDKgbB.js} +1 -1
- package/dist/assets/{chunk-5VM5RSS4-Dlw7AXWp.js → chunk-5VM5RSS4-CFgnRW1C.js} +1 -1
- package/dist/assets/{chunk-7BUUIJ7U-ZBRhjLOh.js → chunk-7BUUIJ7U-Cp7bqW6s.js} +1 -1
- package/dist/assets/{chunk-C7G6YPKG-CiDVUP82.js → chunk-C7G6YPKG-BIS1gjZO.js} +1 -1
- package/dist/assets/{chunk-EX3LRPZG-z6gXP2_N.js → chunk-EX3LRPZG-BKG-cR9j.js} +1 -1
- package/dist/assets/{chunk-FWX5IMBZ-DMZBccQa.js → chunk-FWX5IMBZ-B8PsTr8a.js} +2 -2
- package/dist/assets/{chunk-HOUHSVGY-D8tIeoPk.js → chunk-HOUHSVGY-BHFOAiXT.js} +1 -1
- package/dist/assets/{chunk-ICXQ74PX-b6oQ2Q17.js → chunk-ICXQ74PX-CvLE1J9X.js} +1 -1
- package/dist/assets/{chunk-JWPE2WC7-DSyBRaRG.js → chunk-JWPE2WC7-D8misks7.js} +1 -1
- package/dist/assets/{chunk-MOJQB5TN-DAVH_p2a.js → chunk-MOJQB5TN-D-oNgSNH.js} +1 -1
- package/dist/assets/{chunk-OGEWGWER-DxoiMCI-.js → chunk-OGEWGWER-C2N-la9e.js} +1 -1
- package/dist/assets/{chunk-PUDLZKDR-NsOWh7HM.js → chunk-PUDLZKDR-BwI3Jz8q.js} +1 -1
- package/dist/assets/{chunk-Q4XR5HBZ-Di4H7L21.js → chunk-Q4XR5HBZ-BuqGfsXA.js} +1 -1
- package/dist/assets/{chunk-RYQCIY6F-CZA9pvFX.js → chunk-RYQCIY6F-Bq4XZcGt.js} +1 -1
- package/dist/assets/{chunk-V7JOEXUC-DttpZg9Y.js → chunk-V7JOEXUC-QZLzdTTJ.js} +1 -1
- package/dist/assets/chunk-VAUOI2AC-Cb0-kQ9g.js +1 -0
- package/dist/assets/{chunk-VR4S4FIN-D-Fmtd4Z.js → chunk-VR4S4FIN-DcZTpML_.js} +1 -1
- package/dist/assets/{chunk-WYO6CB5R-CUxMtfK2.js → chunk-WYO6CB5R-DCU8wuny.js} +2 -2
- package/dist/assets/chunk-XXDRQBXY-BZAC1Krv.js +1 -0
- package/dist/assets/{chunk-ZGVPDNZ5-CoV2QEl0.js → chunk-ZGVPDNZ5-B7r85QbY.js} +1 -1
- package/dist/assets/{chunk-ZIRB5QZD-DncwlQQc.js → chunk-ZIRB5QZD-BgEnDWhQ.js} +1 -1
- package/dist/assets/classDiagram-OUVF2IWQ-DcvGOuvi.js +1 -0
- package/dist/assets/classDiagram-v2-EOCWNBFH-DDYUSofQ.js +1 -0
- package/dist/assets/code-dark-BDjwj7Um.css +1 -0
- package/dist/assets/{cose-bilkent-JH36ORCC-BNIAz0nN.js → cose-bilkent-JH36ORCC-CThXS2qG.js} +1 -1
- package/dist/assets/{cssMode-DDWxTPm_.js → cssMode-Bdg5RpcJ.js} +1 -1
- package/dist/assets/{cynefinDiagram-TSTJHNR4-0xPI92Lg.js → cynefinDiagram-TSTJHNR4-DxBgzw25.js} +1 -1
- package/dist/assets/{dagre-DXlcHgr3.js → dagre-BM-GvGUZ.js} +1 -1
- package/dist/assets/{dagre-VKFMJZFB-DfuMG4gU.js → dagre-VKFMJZFB-B7UCRmxR.js} +1 -1
- package/dist/assets/{diagram-FQU43EPY-CT00bLZ6.js → diagram-FQU43EPY-CeL2DwKx.js} +1 -1
- package/dist/assets/{diagram-G47NLZAW-Xbh7zXUx.js → diagram-G47NLZAW-BEirglCC.js} +1 -1
- package/dist/assets/{diagram-NH7WQ7WH-SRDVPfn7.js → diagram-NH7WQ7WH-C6wcI-Rl.js} +1 -1
- package/dist/assets/{diagram-OA4YK3LP-CQRli4Np.js → diagram-OA4YK3LP-DpM-XXtJ.js} +1 -1
- package/dist/assets/{diagram-WEI45ONY-DMf17WQi.js → diagram-WEI45ONY-BQyaut9k.js} +1 -1
- package/dist/assets/{ebnfDiagram-CCIWWBDH-DhpFkgJ5.js → ebnfDiagram-CCIWWBDH-UmAXsUhr.js} +1 -1
- package/dist/assets/{erDiagram-Q63AITRT-1IwxWmJo.js → erDiagram-Q63AITRT-CdYLEOch.js} +1 -1
- package/dist/assets/file-icons-BOlcJ0ZF.js +1 -0
- package/dist/assets/flowDiagram-23GEKE2U-CO-z9-tq.js +1 -0
- package/dist/assets/{freemarker2-QBsTERH-.js → freemarker2-DfMDJfnE.js} +1 -1
- package/dist/assets/{ganttDiagram-NO4QXBWP-BNCdfyr1.js → ganttDiagram-NO4QXBWP-DgSUNdUB.js} +1 -1
- package/dist/assets/{gitGraphDiagram-IHSO6WYX-pbyWZuiX.js → gitGraphDiagram-IHSO6WYX-DjwjtRtF.js} +1 -1
- package/dist/assets/{handlebars-CuoxPo5A.js → handlebars-BEdPtzMT.js} +1 -1
- package/dist/assets/{html-gX5PEu0Q.js → html-B1fPb1QD.js} +1 -1
- package/dist/assets/html2canvas-DsczWxBa.js +5 -0
- package/dist/assets/{htmlMode-DIWUbUxd.js → htmlMode-CY2ZjHxH.js} +1 -1
- package/dist/assets/{index-DhTUO8DA.js → index-BzQO3pUr.js} +2 -2
- package/dist/assets/{infoDiagram-FWYZ7A6U-BcTOgDbe.js → infoDiagram-FWYZ7A6U-Bi9076l2.js} +1 -1
- package/dist/assets/{ishikawaDiagram-FXEZZL3T-DCdvGKrn.js → ishikawaDiagram-FXEZZL3T-DaMaNZN7.js} +1 -1
- package/dist/assets/{javascript-BYKGIUGs.js → javascript-BV2OsWA0.js} +1 -1
- package/dist/assets/{journeyDiagram-5HDEW3XC-BZ-1q_IT.js → journeyDiagram-5HDEW3XC-CfDy69EZ.js} +1 -1
- package/dist/assets/{jsonMode-AFmQQP6u.js → jsonMode-zd07aeuX.js} +1 -1
- package/dist/assets/{kanban-definition-HUTT4EX6-BIgejCjj.js → kanban-definition-HUTT4EX6-XFvZ722B.js} +1 -1
- package/dist/assets/{line-CfptFMmX.js → line-BXK9PKLn.js} +1 -1
- package/dist/assets/{linear-Dld3_ERu.js → linear-CULvmq2Z.js} +1 -1
- package/dist/assets/{liquid-DBIl-f0z.js → liquid-BParI24X.js} +1 -1
- package/dist/assets/{lspLanguageFeatures-DK6tqicV.js → lspLanguageFeatures-BzredkoV.js} +1 -1
- package/dist/assets/main-BlVUXfxa.css +1 -0
- package/dist/assets/{map-LZWWoeWa.js → map-C9Qw9eWh.js} +1 -1
- package/dist/assets/{mdx-DS-bVtZN.js → mdx-vGzUJN98.js} +1 -1
- package/dist/assets/{mermaid.core-gSpX30pV.js → mermaid.core-CzazaIKr.js} +3 -3
- package/dist/assets/{mindmap-definition-LN4V7U3C-BKBaI4LW.js → mindmap-definition-LN4V7U3C-Uoff7hZ3.js} +1 -1
- package/dist/assets/{monaco.contribution-BXz-lfFB.js → monaco.contribution-BmaeBQEc.js} +2 -2
- package/dist/assets/{ordinal-BLrOss5K.js → ordinal-r6pIwRdZ.js} +1 -1
- package/dist/assets/{pegDiagram-2B236MQR-sBvDY3VX.js → pegDiagram-2B236MQR-CCBiHQjk.js} +1 -1
- package/dist/assets/{pieDiagram-ENE6RG2P-DWlWcu08.js → pieDiagram-ENE6RG2P-D61s7l75.js} +1 -1
- package/dist/assets/{python-C93q3xXz.js → python-CUwdLgsI.js} +1 -1
- package/dist/assets/{quadrantDiagram-ABIIQ3AL-DZutUd3J.js → quadrantDiagram-ABIIQ3AL-D_CWk2DQ.js} +1 -1
- package/dist/assets/{railroadDiagram-RFXS5EU6-DQb3lXGM.js → railroadDiagram-RFXS5EU6-CdlCHtxN.js} +1 -1
- package/dist/assets/{razor-CBifNGXO.js → razor-BScebUxU.js} +1 -1
- package/dist/assets/{requirementDiagram-TGXJPOKE-DqFGXu2e.js → requirementDiagram-TGXJPOKE-BLpHtkoC.js} +1 -1
- package/dist/assets/{sankeyDiagram-HTMAVEWB-d7ECahnO.js → sankeyDiagram-HTMAVEWB-CkhYZN2j.js} +1 -1
- package/dist/assets/{sequenceDiagram-DBY2YBRQ-DdsbKqct.js → sequenceDiagram-DBY2YBRQ-8bploKJl.js} +1 -1
- package/dist/assets/{sizeCapture-X5ZJPWSS-daTB6OcF.js → sizeCapture-X5ZJPWSS-DtSdkS0d.js} +1 -1
- package/dist/assets/{src-WBW1QxJt.js → src-6yeq6WBA.js} +1 -1
- package/dist/assets/{stateDiagram-2N3HPSRC-P6iSrgwR.js → stateDiagram-2N3HPSRC-DfmVGpfA.js} +1 -1
- package/dist/assets/stateDiagram-v2-6OUMAXLB-B2c9CQ-J.js +1 -0
- package/dist/assets/{swimlanes-5IMT3BWC-BGnZJx1M.js → swimlanes-5IMT3BWC-DRLk2zRF.js} +2 -2
- package/dist/assets/swimlanesDiagram-G3AALYLV-B7_APh_h.js +8 -0
- package/dist/assets/{timeline-definition-FHXFAJF6-rA-r3aUR.js → timeline-definition-FHXFAJF6-Cngfy588.js} +1 -1
- package/dist/assets/{tsMode-B0oUTcXQ.js → tsMode-DzUAibXK.js} +1 -1
- package/dist/assets/{typescript-KJKfYO7K.js → typescript-D9EOeM7N.js} +1 -1
- package/dist/assets/{vennDiagram-L72KCM5P-DiVKUjA9.js → vennDiagram-L72KCM5P-DeJn63Lq.js} +1 -1
- package/dist/assets/{wardleyDiagram-EHGQE667-Cfxh7UOn.js → wardleyDiagram-EHGQE667-BH1nzy77.js} +1 -1
- package/dist/assets/{workers-BSQRIkqa.js → workers-DAiomYZM.js} +1 -1
- package/dist/assets/workspace-editor-model-CSGCl21x.js +1 -0
- package/dist/assets/workspace-editor-monaco-CFEfFNcA.js +1 -0
- package/dist/assets/{workspace-editor-monaco-BIpqcWf1.js → workspace-editor-monaco-tXA-udGx.js} +3 -3
- package/dist/assets/workspace-view-state-BS_V8JVs.js +7 -0
- package/dist/assets/{xml-C8FAcN6j.js → xml-wWl1dhkQ.js} +1 -1
- package/dist/assets/{xychartDiagram-FW5EYKEG--hmryQeL.js → xychartDiagram-FW5EYKEG-SkO2prCU.js} +1 -1
- package/dist/assets/{yaml-BQ4NrRhG.js → yaml-Dc2WDaOZ.js} +1 -1
- package/dist/crt-markdown-renderer.js +46 -46
- package/dist/index.html +1 -1
- package/extensions/browser/backend/agent-capability.js +55 -0
- package/extensions/browser/backend/browser-mcp-server.js +269 -0
- package/extensions/browser/backend/browser-resource-manager.js +524 -0
- package/extensions/browser/backend/browser-resource-store.js +149 -0
- package/extensions/browser/backend/browser-router.js +128 -0
- package/extensions/browser/backend/cdp-browser-runtime.js +648 -0
- package/extensions/browser/backend/cdp-client.js +167 -0
- package/extensions/browser/backend/executable-discovery.js +91 -0
- package/extensions/browser/backend/farming-browser-client.js +83 -0
- package/extensions/browser/backend/index.js +7 -0
- package/extensions/browser/bin/farming-browser +117 -0
- package/extensions/browser/frontend/BrowserSidebarPortals.tsx +362 -0
- package/extensions/browser/frontend/BrowserViewer.tsx +329 -0
- package/extensions/browser/frontend/browser-resource-state.ts +60 -0
- package/extensions/browser/frontend/browser.css +516 -0
- package/extensions/browser/frontend/types.ts +25 -0
- package/extensions/browser/frontend/useBrowserResources.ts +151 -0
- package/frontend/session-bridge.js +7 -6
- package/frontend/skins/crt/app.js +64 -4
- package/package.json +18 -13
- package/shared/browser-protocol.js +1 -1
- package/dist/assets/App-BbNBDA__.js +0 -210
- package/dist/assets/App-BrP-ENHg.css +0 -1
- package/dist/assets/FileEditorPane-5_HUejjd.js +0 -2
- package/dist/assets/IconGlyphs-C2WFcoJS.js +0 -1
- package/dist/assets/ProjectFilesSection-C1RAJWOE.js +0 -12
- package/dist/assets/api-D8nyOEbz.js +0 -1
- package/dist/assets/channel-BVWNatUB.js +0 -1
- package/dist/assets/chunk-2Q5K7J3B-d5WWh8jo.js +0 -1
- package/dist/assets/chunk-VAUOI2AC-UHNr_yhX.js +0 -1
- package/dist/assets/chunk-XXDRQBXY-BBXwjhUx.js +0 -1
- package/dist/assets/classDiagram-OUVF2IWQ-Db0ib5iE.js +0 -1
- package/dist/assets/classDiagram-v2-EOCWNBFH-Bkf69XF9.js +0 -1
- package/dist/assets/code-dark-BKtvf0Xc.css +0 -1
- package/dist/assets/file-icons-CHWt5nSy.js +0 -1
- package/dist/assets/flowDiagram-23GEKE2U-DYFo2xmB.js +0 -1
- package/dist/assets/main-XMXgyJJt.css +0 -1
- package/dist/assets/stateDiagram-v2-6OUMAXLB-DIYSf_qh.js +0 -1
- package/dist/assets/swimlanesDiagram-G3AALYLV-BIkrlKrZ.js +0 -8
- package/dist/assets/workspace-editor-model-D0Im34S5.js +0 -1
- package/dist/assets/workspace-view-state-BqB4HM7r.js +0 -7
- /package/dist/assets/{abap-Cc5D5h5i.js → abap-CtX5nWyX.js} +0 -0
- /package/dist/assets/{apex-BKopwLmx.js → apex-DQJRAHwc.js} +0 -0
- /package/dist/assets/{array-HeX70jSN.js → array-BTwzYU0x.js} +0 -0
- /package/dist/assets/{azcli-DI-C34WA.js → azcli-9SMrOQ4q.js} +0 -0
- /package/dist/assets/{bat-Cbp6u42z.js → bat-CBGLUsw1.js} +0 -0
- /package/dist/assets/{bicep-4QA1jTCM.js → bicep-pDx8da1v.js} +0 -0
- /package/dist/assets/{cameligo-Dda_adPs.js → cameligo-hZV3vQ6_.js} +0 -0
- /package/dist/assets/{chunk-Y2CYZVJY-Di0sYzi1.js → chunk-Y2CYZVJY-BYi0W5ea.js} +0 -0
- /package/dist/assets/{clojure-CvY8A83L.js → clojure-BhCwU3gq.js} +0 -0
- /package/dist/assets/{coffee-4LX2Sv8A.js → coffee-BVB1PsgH.js} +0 -0
- /package/dist/assets/{core-B9MwRjDE.js → core-vPkV8P_m.js} +0 -0
- /package/dist/assets/{cpp-D4AzSV28.js → cpp-eTjuiNPq.js} +0 -0
- /package/dist/assets/{csharp-C4nWN4Yd.js → csharp-1kP-kKZT.js} +0 -0
- /package/dist/assets/{csp-CiEaaS5Z.js → csp-C_iQqIes.js} +0 -0
- /package/dist/assets/{css-DXxBRsbk.js → css-CxOs7IfP.js} +0 -0
- /package/dist/assets/{cypher-DOMzqyFA.js → cypher-Cd-a9Y82.js} +0 -0
- /package/dist/assets/{cytoscape.esm-Cl8iC3D7.js → cytoscape.esm-OLojuicg.js} +0 -0
- /package/dist/assets/{dart-BWS7MXt_.js → dart-whhXpexn.js} +0 -0
- /package/dist/assets/{defaultLocale-GJwWH1Jr.js → defaultLocale-CPimVhfE.js} +0 -0
- /package/dist/assets/{dist-ClAL-Tv5.js → dist-CLPYXg1u.js} +0 -0
- /package/dist/assets/{dockerfile-Baf4U3xT.js → dockerfile-DibBi8b6.js} +0 -0
- /package/dist/assets/{ecl-CT7udlkQ.js → ecl-DlF1KdoW.js} +0 -0
- /package/dist/assets/{editor.api2-B9h6iFPK.js → editor.api2-D74ufkbr.js} +0 -0
- /package/dist/assets/{elixir-CIbAZw5l.js → elixir-CHQ8uFT3.js} +0 -0
- /package/dist/assets/{flow9-Dqiw45WV.js → flow9-8nUnVKk1.js} +0 -0
- /package/dist/assets/{fsharp-E8E2L9jU.js → fsharp-DNZNZyEE.js} +0 -0
- /package/dist/assets/{go-CouVSN4D.js → go-_kPJjNc_.js} +0 -0
- /package/dist/assets/{graphlib-CbSvZcZU.js → graphlib-CBGF9gA9.js} +0 -0
- /package/dist/assets/{graphql-a6h6h5-t.js → graphql-4HCNVt5g.js} +0 -0
- /package/dist/assets/{hcl-vso3e6N-.js → hcl-C87ymY0o.js} +0 -0
- /package/dist/assets/{ini-SwZZkotR.js → ini-BYrrGNTc.js} +0 -0
- /package/dist/assets/{init-BPLPMQ3Y.js → init-8_PCa3LO.js} +0 -0
- /package/dist/assets/{java-CtRLC4_3.js → java-B211rJbk.js} +0 -0
- /package/dist/assets/{julia-CuJWFKQH.js → julia-DVPsRtBm.js} +0 -0
- /package/dist/assets/{katex-C8DPyI6s.js → katex-C2iYad0m.js} +0 -0
- /package/dist/assets/{kotlin-B_kBhsre.js → kotlin-BYyNerjp.js} +0 -0
- /package/dist/assets/{less-CtrHIfz3.js → less-EC9UblGE.js} +0 -0
- /package/dist/assets/{lexon-yVt1pJLu.js → lexon-B1yuUdqW.js} +0 -0
- /package/dist/assets/{lua-prOwgJ4U.js → lua-Bo6GFzxT.js} +0 -0
- /package/dist/assets/{m3-DhrdTQ84.js → m3-C-Y9cBVI.js} +0 -0
- /package/dist/assets/{markdown-DoI5HwSM.js → markdown-BHu-wLbe.js} +0 -0
- /package/dist/assets/{mermaid-parser.core-BQtepoLd.js → mermaid-parser.core-IxcGtNYG.js} +0 -0
- /package/dist/assets/{mips-Dye5jsBU.js → mips-Dkds26rX.js} +0 -0
- /package/dist/assets/{msdax-TkNOyIL8.js → msdax-PU4g-8_g.js} +0 -0
- /package/dist/assets/{mysql-CEc-RKZF.js → mysql-fPpuGpS7.js} +0 -0
- /package/dist/assets/{objective-c-CqqCsQz0.js → objective-c-Wy1e5JUf.js} +0 -0
- /package/dist/assets/{pascal-DO_pj8yg.js → pascal-BIXwvrqU.js} +0 -0
- /package/dist/assets/{pascaligo-1cbdZdF3.js → pascaligo-DwR0AtL_.js} +0 -0
- /package/dist/assets/{path-YdFzr2W6.js → path-BxQuywGl.js} +0 -0
- /package/dist/assets/{perl-B_N3PYfC.js → perl-CGHAl8my.js} +0 -0
- /package/dist/assets/{pgsql-9iYk1FQ7.js → pgsql-B91M73E2.js} +0 -0
- /package/dist/assets/{php-rO2sgoUM.js → php-C70ttrCx.js} +0 -0
- /package/dist/assets/{pla-Cbgv60kq.js → pla-BNrXaRh0.js} +0 -0
- /package/dist/assets/{postiats-y6xrhy_M.js → postiats-Dv2IAZR9.js} +0 -0
- /package/dist/assets/{powerquery-BaD8gWNE.js → powerquery-CkY5Wq7J.js} +0 -0
- /package/dist/assets/{powershell-BMhG4JVe.js → powershell-uGIYLU3C.js} +0 -0
- /package/dist/assets/{protobuf-CdOP5kN3.js → protobuf-CavfmJu-.js} +0 -0
- /package/dist/assets/{pug-Ck60hNF9.js → pug-Drqnyitx.js} +0 -0
- /package/dist/assets/{qrcode-DV-_rjU8.js → qrcode-7uGsTEmi.js} +0 -0
- /package/dist/assets/{qsharp-BcaKFfP-.js → qsharp-C7yF-Dti.js} +0 -0
- /package/dist/assets/{r-rFNes0iK.js → r-HXLGP0_-.js} +0 -0
- /package/dist/assets/{redis-DkpQ3K6G.js → redis-C4y6mf2p.js} +0 -0
- /package/dist/assets/{redshift-Di9N5TI3.js → redshift-DdQrFPrv.js} +0 -0
- /package/dist/assets/{restructuredtext-By5ZzLt6.js → restructuredtext-CBrf2PNj.js} +0 -0
- /package/dist/assets/{rough.esm-c4PR5shF.js → rough.esm-6CnTHTkH.js} +0 -0
- /package/dist/assets/{ruby-fF5zGiLp.js → ruby-CVtMoX2B.js} +0 -0
- /package/dist/assets/{rust-C5b3AYY6.js → rust-Ckq4sbMP.js} +0 -0
- /package/dist/assets/{sb-CD07UNRs.js → sb-er66OpE3.js} +0 -0
- /package/dist/assets/{scala-jtn1ip9G.js → scala-BdWZz-hH.js} +0 -0
- /package/dist/assets/{scheme-C4vqMkn_.js → scheme-kTV_s2kP.js} +0 -0
- /package/dist/assets/{scss-I4Glxjbz.js → scss-Bymy2VJQ.js} +0 -0
- /package/dist/assets/{shell-DubUcuns.js → shell-bwEP1LQ1.js} +0 -0
- /package/dist/assets/{solidity-CNy2KSl2.js → solidity-DCddeBgL.js} +0 -0
- /package/dist/assets/{sophia-D9_URt84.js → sophia-BMN3AQig.js} +0 -0
- /package/dist/assets/{sparql-DviXpm4C.js → sparql-CSXOLjzi.js} +0 -0
- /package/dist/assets/{sql-vDRbAZgK.js → sql-COg8ewmK.js} +0 -0
- /package/dist/assets/{st-CnpnRqA7.js → st-CIaZtBS_.js} +0 -0
- /package/dist/assets/{swift-D_g8Ganz.js → swift-DXiOhUHu.js} +0 -0
- /package/dist/assets/{systemverilog-OuZa5h1M.js → systemverilog-BTDE6Dr2.js} +0 -0
- /package/dist/assets/{tcl-DBm9ucsV.js → tcl-B0zy9NvV.js} +0 -0
- /package/dist/assets/{twig-BRzEqX5Y.js → twig-D594l4Z1.js} +0 -0
- /package/dist/assets/{typespec-D4ZgGeX8.js → typespec-XKVjaPqo.js} +0 -0
- /package/dist/assets/{useWorkspaceMenuKeyboard-Brws6Ar9.js → useWorkspaceMenuKeyboard-Bpy_s69Z.js} +0 -0
- /package/dist/assets/{vb-DcP4s6WS.js → vb-B2wYoKRV.js} +0 -0
- /package/dist/assets/{wgsl-CnRJ_QUf.js → wgsl-D0Yf2LbK.js} +0 -0
- /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(
|
|
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
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
1689
|
+
const operation = configManager.getProjectOperation?.(requestId);
|
|
1699
1690
|
res.status(status).json({
|
|
1700
|
-
error:
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
...(
|
|
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
|
|
1713
|
-
if (
|
|
1714
|
-
|
|
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(
|
|
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
|
|
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
|
|
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 || '
|
|
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, '<')
|
|
98
|
+
.replace(/>/g, '>');
|
|
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
|
-
|
|
237
|
-
|
|
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,
|
|
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
|
-
|
|
251
|
-
|
|
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,
|
|
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);
|