farming-code 2.2.29 → 2.2.32
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 +25 -66
- package/README.zh_cn.md +15 -66
- package/THIRD_PARTY_NOTICES.md +7 -0
- package/backend/acp/{client-services.js → client-services.cjs} +132 -134
- package/backend/acp/packaged-claude-acp.cjs +37 -0
- package/backend/acp/packaged-codex-acp.cjs +37 -0
- package/backend/acp/patch-decisions.cjs +182 -0
- package/backend/acp/permission-security.cjs +131 -0
- package/backend/{acp-checkpoint-store.js → acp-checkpoint-store.cjs} +90 -59
- package/backend/{acp-runtime.js → acp-runtime.cjs} +1219 -1125
- package/backend/acp-session-state.cjs +622 -0
- package/backend/acp-transcript.cjs +525 -0
- package/backend/agent-env.cjs +257 -0
- package/backend/agent-extension-discovery.cjs +452 -0
- package/backend/agent-extension-inventory.cjs +116 -0
- package/backend/agent-json-stream.cjs +283 -0
- package/backend/agent-lifecycle-journal.cjs +185 -0
- package/backend/{agent-manager.js → agent-manager.cjs} +3116 -4275
- package/backend/agent-order-transaction.cjs +85 -0
- package/backend/agent-order.cjs +159 -0
- package/backend/agent-provider-session.cjs +98 -0
- package/backend/agent-runtime-binding.cjs +179 -0
- package/backend/agent-session-history.cjs +1022 -0
- package/backend/agent-session-inventory.cjs +291 -0
- package/backend/async-cache.cjs +104 -0
- package/backend/atomic-json-store.cjs +67 -0
- package/backend/auth.cjs +261 -0
- package/backend/authoritative-inventory-cache.cjs +383 -0
- package/backend/business-health.cjs +68 -0
- package/backend/chat-runtime.cjs +50 -0
- package/backend/claude-settings.cjs +132 -0
- package/backend/cli-agents.cjs +435 -0
- package/backend/codex-context-window.cjs +196 -0
- package/backend/codex-models.cjs +228 -0
- package/backend/codex-session-archive.cjs +82 -0
- package/backend/codex-session-history.cjs +590 -0
- package/backend/{codex-terminal-profile.js → codex-terminal-profile.cjs} +149 -155
- package/backend/codex-transcript-sanitizer.cjs +130 -0
- package/backend/codex-transcript.cjs +2107 -0
- package/backend/command-runner-child.cjs +105 -0
- package/backend/config-manager.cjs +1263 -0
- package/backend/control-api.cjs +439 -0
- package/backend/data/LICENSE.cua +21 -0
- package/backend/executable-discovery.cjs +204 -0
- package/backend/farming-agent-bootstrap.cjs +81 -0
- package/backend/farming-agent-bootstrap.zh_cn.md +6 -2
- package/backend/{farming-app-cli.js → farming-app-cli.cjs} +441 -493
- package/backend/farming-cli.cjs +414 -0
- package/backend/farming-net-pass.cjs +319 -0
- package/backend/farming-net-registry.cjs +123 -0
- package/backend/farming-net-server.cjs +309 -0
- package/backend/farming-session-store.cjs +651 -0
- package/backend/git-worktree-info.cjs +181 -0
- package/backend/haiku-token.cjs +828 -0
- package/backend/index-html.cjs +96 -0
- package/backend/input-parts.cjs +68 -0
- package/backend/input-routing.cjs +40 -0
- package/backend/{local-session-engine.js → local-session-engine.cjs} +276 -282
- package/backend/main-agent-skills.cjs +309 -0
- package/backend/main-page-session.cjs +132 -0
- package/backend/native-pty-controller-generation.cjs +139 -0
- package/backend/{native-pty-host-client.js → native-pty-host-client.cjs} +338 -346
- package/backend/native-pty-host-identity.cjs +125 -0
- package/backend/native-pty-host-path.cjs +68 -0
- package/backend/{native-pty-host.js → native-pty-host.cjs} +480 -569
- package/backend/native-session-engine.cjs +296 -0
- package/backend/network.cjs +57 -0
- package/backend/npm-update-helper.cjs +399 -0
- package/backend/packaged-node-pty.cjs +120 -0
- package/backend/preview-session-manager.cjs +89 -0
- package/backend/provider-adapters.cjs +492 -0
- package/backend/provider-home-configuration.cjs +211 -0
- package/backend/provider-session-id.cjs +79 -0
- package/backend/provider-session-service.cjs +333 -0
- package/backend/qr-share-tickets.cjs +119 -0
- package/backend/resource-broadcast-protocol.cjs +50 -0
- package/backend/review-diff-router.cjs +202 -0
- package/backend/review-diff-service.cjs +1267 -0
- package/backend/review-session-router.cjs +93 -0
- package/backend/review-session-service.cjs +470 -0
- package/backend/review-session-store.cjs +161 -0
- package/backend/review-state-router.cjs +132 -0
- package/backend/review-state-store.cjs +299 -0
- package/backend/run-history-store.cjs +97 -0
- package/backend/runtime-dependency-manager.cjs +893 -0
- package/backend/runtime-executable-invocation.cjs +37 -0
- package/backend/runtime-observation.cjs +96 -0
- package/backend/server-process-identity.cjs +70 -0
- package/backend/server.cjs +3356 -0
- package/backend/session-engine-bridge.cjs +144 -0
- package/backend/session-engine-router.cjs +105 -0
- package/backend/session-engine.cjs +64 -0
- package/backend/session-stream-protocol.cjs +181 -0
- package/backend/shell-busy-integration.cjs +442 -0
- package/backend/slash-command-discovery.cjs +423 -0
- package/backend/storage-layout.cjs +263 -0
- package/backend/system-monitor.cjs +96 -0
- package/backend/terminal-attach-checkpoint.cjs +73 -0
- package/backend/terminal-exit-quiescence.cjs +57 -0
- package/backend/{terminal-reducer-flow-control.js → terminal-reducer-flow-control.cjs} +45 -22
- package/backend/terminal-runtime-cleanup.cjs +99 -0
- package/backend/{terminal-screen-state.js → terminal-screen-state.cjs} +100 -108
- package/backend/{terminal-screen-worker-pool.js → terminal-screen-worker-pool.cjs} +89 -79
- package/backend/{terminal-screen-worker-thread.js → terminal-screen-worker-thread.cjs} +64 -65
- package/backend/terminal-screen-worker.cjs +352 -0
- package/backend/terminal-state-serialization.cjs +147 -0
- package/backend/{terminal-status.js → terminal-status.cjs} +137 -187
- package/backend/theme-manager.cjs +185 -0
- package/backend/update-service.cjs +675 -0
- package/backend/usage-forecast.cjs +95 -0
- package/backend/{usage-history-client.js → usage-history-client.cjs} +144 -94
- package/backend/usage-history-smoke.cjs +85 -0
- package/backend/usage-history-worker.cjs +60 -0
- package/backend/{usage-monitor.js → usage-monitor.cjs} +363 -390
- package/backend/workspace-directory.cjs +239 -0
- package/backend/workspace-discovery.cjs +271 -0
- package/backend/{workspace-file-router.js → workspace-file-router.cjs} +213 -282
- package/backend/{workspace-file-service.js → workspace-file-service.cjs} +1139 -1318
- package/backend/workspace-root-registry.cjs +192 -0
- package/bin/farming +1 -1
- package/config/farming.deploy.env.example +1 -1
- package/config/farming.install.env.example +1 -1
- package/dist/acp/codex-acp-1.1.4.mjs +405 -8
- package/dist/assets/App-BCkdpWB6.css +1 -0
- package/dist/assets/App-CRjKpaGp.js +697 -0
- package/dist/assets/CodeSelect-Ci8sKZPU.js +1 -0
- package/dist/assets/CodeSelect-T2eNzrOc.css +1 -0
- package/dist/assets/{FileEditorMarkdownPreview-Cs9cLv8a.js → FileEditorMarkdownPreview-V06QHlAg.js} +55 -55
- package/dist/assets/FileEditorPane-BX-Wjg9U.js +2 -0
- package/dist/assets/IconGlyphs-YhV7tqd4.js +1 -0
- package/dist/assets/ProjectFilesSection-BDT2_r0o.js +12 -0
- package/dist/assets/ReviewPage-BqAs5sG1.js +3 -0
- package/dist/assets/code-dark-B7eM_9G9.css +1 -0
- package/dist/assets/{cssMode-Bdg5RpcJ.js → cssMode-ADdAlxTO.js} +1 -1
- package/dist/assets/{editor.api2-D74ufkbr.js → editor.api2-BFLAr0YF.js} +162 -162
- package/dist/assets/file-icons-BpeqWkIB.js +1 -0
- package/dist/assets/{freemarker2-DfMDJfnE.js → freemarker2-C90q5jrp.js} +1 -1
- package/dist/assets/{handlebars-BEdPtzMT.js → handlebars-BwUQcEDq.js} +1 -1
- package/dist/assets/{html-B1fPb1QD.js → html-1s3uPKfh.js} +1 -1
- package/dist/assets/{htmlMode-CY2ZjHxH.js → htmlMode-xtKD99de.js} +1 -1
- package/dist/assets/{index-CQNaZc3l.js → index-C8MchCem.js} +3 -3
- package/dist/assets/{javascript-BV2OsWA0.js → javascript-Ph7ijyny.js} +1 -1
- package/dist/assets/{jsonMode-zd07aeuX.js → jsonMode-D-jGAnFH.js} +1 -1
- package/dist/assets/linkComputer-CbGyI_qx.js +1 -0
- package/dist/assets/{liquid-BParI24X.js → liquid-0IamE_cI.js} +1 -1
- package/dist/assets/{lspLanguageFeatures-BzredkoV.js → lspLanguageFeatures-BxnQunWF.js} +1 -1
- package/dist/assets/main-CLKXa6hf.css +1 -0
- package/dist/assets/{mdx-vGzUJN98.js → mdx-DdNmfivn.js} +1 -1
- package/dist/assets/{monaco.contribution-BmaeBQEc.js → monaco.contribution-B43jsUs1.js} +2 -2
- package/dist/assets/{python-CUwdLgsI.js → python-IAt4X5TS.js} +1 -1
- package/dist/assets/{razor-BScebUxU.js → razor-DRkFYW0S.js} +1 -1
- package/dist/assets/review-C8-oPKXk.css +1 -0
- package/dist/assets/{tsMode-DzUAibXK.js → tsMode-_d_4U2gf.js} +1 -1
- package/dist/assets/{typescript-D9EOeM7N.js → typescript-C7zcMq9l.js} +1 -1
- package/dist/assets/{workers-DAiomYZM.js → workers-D0nOyJzf.js} +1 -1
- package/dist/assets/workspace-editor-model-DND_Y-Bj.js +1 -0
- package/dist/assets/workspace-editor-monaco-BPWhgcIB.js +4 -0
- package/dist/assets/workspace-editor-monaco-N641qPjX.js +1 -0
- package/dist/assets/workspace-view-state-k1bTlJ_Z.js +13 -0
- package/dist/assets/{xml-wWl1dhkQ.js → xml-BR-HvxT8.js} +1 -1
- package/dist/assets/{yaml-Dc2WDaOZ.js → yaml-YtFQwh4q.js} +1 -1
- package/dist/index.html +2 -1
- package/extensions/browser/backend/agent-browser-runtime.cjs +1040 -0
- package/extensions/browser/backend/agent-capability.cjs +85 -0
- package/extensions/browser/backend/browser-mcp-server.cjs +655 -0
- package/extensions/browser/backend/browser-resource-manager.cjs +1486 -0
- package/extensions/browser/backend/browser-resource-store.cjs +228 -0
- package/extensions/browser/backend/browser-router.cjs +203 -0
- package/extensions/browser/backend/executable-discovery.cjs +248 -0
- package/extensions/browser/backend/farming-browser-client.cjs +116 -0
- package/extensions/browser/backend/index.cjs +32 -0
- package/extensions/browser/backend/managed-chromium-installer.cjs +833 -0
- package/extensions/browser/bin/farming-browser +16 -7
- package/extensions/computer/backend/agent-capability.cjs +72 -0
- package/extensions/computer/backend/computer-constants.cjs +59 -0
- package/extensions/computer/backend/computer-mcp-server.cjs +182 -0
- package/extensions/computer/backend/computer-resource-manager.cjs +1219 -0
- package/extensions/computer/backend/computer-resource-store.cjs +190 -0
- package/extensions/computer/backend/computer-router.cjs +257 -0
- package/extensions/computer/backend/cua-tools.json +3067 -0
- package/extensions/computer/backend/farming-computer-client.cjs +84 -0
- package/extensions/computer/backend/index.cjs +35 -0
- package/extensions/computer/backend/isolated-browser-provider.cjs +157 -0
- package/extensions/computer/bin/farming-computer +131 -0
- package/frontend/farming-net/app.js +255 -262
- package/frontend/ghostty-loader.js +24 -20
- package/frontend/reading-anchor.js +210 -193
- package/frontend/runtime-paths.js +24 -26
- package/frontend/session-bridge.js +90 -111
- package/frontend/session-modal-bridge.js +184 -302
- package/frontend/skin-bridge.js +20 -23
- package/frontend/skins/crt/app.js +7686 -7901
- package/frontend/skins/crt/effects.js +7 -6
- package/frontend/skins/crt/index.html +6 -6
- package/frontend/skins/crt/styles/markdown.css +4 -4
- package/frontend/terminal-bridge.js +174 -181
- package/frontend/terminal-replay.js +320 -345
- package/frontend/theme-loader.js +55 -71
- package/index.html +1 -0
- package/package.json +64 -37
- package/shared/backend-connection-status.js +38 -69
- package/shared/browser-protocol.js +235 -110
- package/shared/websocket-liveness.js +40 -0
- package/backend/acp/packaged-claude-acp.js +0 -13
- package/backend/acp/packaged-codex-acp.js +0 -13
- package/backend/acp/patch-decisions.js +0 -153
- package/backend/acp/permission-security.js +0 -108
- package/backend/acp-session-state.js +0 -644
- package/backend/acp-transcript.js +0 -512
- package/backend/agent-env.js +0 -250
- package/backend/agent-json-stream.js +0 -268
- package/backend/agent-lifecycle-journal.js +0 -133
- package/backend/agent-memory-report.js +0 -583
- package/backend/agent-order.js +0 -166
- package/backend/agent-provider-session.js +0 -76
- package/backend/agent-runtime-binding.js +0 -174
- package/backend/agent-session-history.js +0 -953
- package/backend/async-cache.js +0 -94
- package/backend/atomic-json-store.js +0 -37
- package/backend/auth.js +0 -251
- package/backend/bundle-update-helper.js +0 -113
- package/backend/chat-runtime.js +0 -28
- package/backend/claude-settings.js +0 -121
- package/backend/cli-agents.js +0 -486
- package/backend/codex-context-window.js +0 -189
- package/backend/codex-models.js +0 -226
- package/backend/codex-rollout-follower.js +0 -326
- package/backend/codex-session-archive.js +0 -65
- package/backend/codex-session-history.js +0 -647
- package/backend/codex-transcript-sanitizer.js +0 -115
- package/backend/codex-transcript.js +0 -2564
- package/backend/command-runner-child.js +0 -67
- package/backend/config-manager.js +0 -1133
- package/backend/control-api.js +0 -455
- package/backend/executable-discovery.js +0 -216
- package/backend/farming-agent-bootstrap.js +0 -49
- package/backend/farming-cli.js +0 -457
- package/backend/farming-net-pass.js +0 -285
- package/backend/farming-net-registry.js +0 -112
- package/backend/farming-net-server.js +0 -298
- package/backend/farming-session-store.js +0 -677
- package/backend/git-worktree-info.js +0 -181
- package/backend/haiku-token.js +0 -772
- package/backend/index-html.js +0 -62
- package/backend/input-parts.js +0 -54
- package/backend/input-routing.js +0 -24
- package/backend/json-cli-runtime.js +0 -275
- package/backend/main-agent-skills.js +0 -305
- package/backend/main-page-session.js +0 -122
- package/backend/native-pty-controller-generation.js +0 -106
- package/backend/native-pty-host-identity.js +0 -86
- package/backend/native-pty-host-path.js +0 -42
- package/backend/native-session-engine.js +0 -268
- package/backend/network.js +0 -23
- package/backend/npm-update-helper.js +0 -374
- package/backend/packaged-node-pty.js +0 -99
- package/backend/preview-session-manager.js +0 -68
- package/backend/provider-adapters.js +0 -399
- package/backend/provider-session-id.js +0 -47
- package/backend/provider-session-service.js +0 -345
- package/backend/qr-share-tickets.js +0 -100
- package/backend/review-diff-router.js +0 -180
- package/backend/review-diff-service.js +0 -1270
- package/backend/review-session-router.js +0 -74
- package/backend/review-session-service.js +0 -446
- package/backend/review-session-store.js +0 -151
- package/backend/review-state-router.js +0 -116
- package/backend/review-state-store.js +0 -286
- package/backend/run-history-store.js +0 -67
- package/backend/runtime-dependency-manager.js +0 -759
- package/backend/runtime-executable-invocation.js +0 -20
- package/backend/runtime-observation.js +0 -81
- package/backend/server-process-identity.js +0 -47
- package/backend/server.js +0 -3267
- package/backend/session-engine-bridge.js +0 -129
- package/backend/session-engine-router.js +0 -66
- package/backend/session-engine.js +0 -39
- package/backend/session-stream-protocol.js +0 -185
- package/backend/shell-busy-integration.js +0 -472
- package/backend/slash-command-discovery.js +0 -433
- package/backend/storage-layout.js +0 -190
- package/backend/system-monitor.js +0 -77
- package/backend/terminal-attach-checkpoint.js +0 -74
- package/backend/terminal-exit-quiescence.js +0 -39
- package/backend/terminal-runtime-cleanup.js +0 -80
- package/backend/terminal-screen-worker.js +0 -337
- package/backend/terminal-state-serialization.js +0 -127
- package/backend/theme-manager.js +0 -163
- package/backend/update-service.js +0 -1545
- package/backend/usage-forecast.js +0 -86
- package/backend/usage-history-scanner.ts +0 -2718
- package/backend/usage-history-smoke.js +0 -64
- package/backend/usage-history-worker.js +0 -34
- package/backend/workspace-directory.js +0 -232
- package/backend/workspace-discovery.js +0 -311
- package/backend/workspace-root-registry.js +0 -166
- package/dist/assets/App-BKNUILuL.css +0 -1
- package/dist/assets/App-Ex2oVVXO.js +0 -693
- package/dist/assets/FileEditorPane-CNtuGjQ3.js +0 -2
- package/dist/assets/IconGlyphs-BClgag6E.js +0 -1
- package/dist/assets/ProjectFilesSection-Cf2BEcAN.js +0 -12
- package/dist/assets/ReviewPage-BRuxFaH5.js +0 -3
- package/dist/assets/code-dark-BEZTQwYZ.css +0 -1
- package/dist/assets/file-icons-BOlcJ0ZF.js +0 -1
- package/dist/assets/main-DK9Sy7-c.css +0 -1
- package/dist/assets/review-BcnwQtJv.css +0 -1
- package/dist/assets/workspace-editor-model-CSGCl21x.js +0 -1
- package/dist/assets/workspace-editor-monaco-CJNwbpCy.js +0 -4
- package/dist/assets/workspace-editor-monaco-CO9w8YV0.js +0 -1
- package/dist/assets/workspace-view-state-BjKPYp0E.js +0 -7
- package/extensions/browser/backend/agent-browser-runtime.js +0 -1041
- package/extensions/browser/backend/agent-capability.js +0 -55
- package/extensions/browser/backend/browser-mcp-server.js +0 -621
- package/extensions/browser/backend/browser-resource-manager.js +0 -1309
- package/extensions/browser/backend/browser-resource-store.js +0 -190
- package/extensions/browser/backend/browser-router.js +0 -133
- package/extensions/browser/backend/executable-discovery.js +0 -224
- package/extensions/browser/backend/farming-browser-client.js +0 -83
- package/extensions/browser/backend/index.js +0 -7
- package/extensions/browser/frontend/BrowserSidebarPortals.tsx +0 -360
- package/extensions/browser/frontend/BrowserViewer.tsx +0 -500
- package/extensions/browser/frontend/browser-resource-state.ts +0 -60
- package/extensions/browser/frontend/browser.css +0 -567
- package/extensions/browser/frontend/types.ts +0 -31
- package/extensions/browser/frontend/useBrowserResources.ts +0 -158
- package/frontend/vendor/ghostty-web/index.d.ts +0 -1842
- package/shared/backend-connection-status.d.ts +0 -37
- package/shared/browser-protocol.d.ts +0 -5
- /package/dist/assets/{api-w2x5QfxB.js → api-ByCV9MEr.js} +0 -0
- /package/dist/assets/{core-vPkV8P_m.js → core-Czwdx12k.js} +0 -0
- /package/dist/assets/{fsharp-DNZNZyEE.js → fsharp-BjPpkoJe.js} +0 -0
- /package/dist/assets/{go-_kPJjNc_.js → go-BkIDmSUP.js} +0 -0
- /package/dist/assets/{graphql-4HCNVt5g.js → graphql-CCJTpqi1.js} +0 -0
- /package/dist/assets/{hcl-C87ymY0o.js → hcl-s4z8lUFg.js} +0 -0
- /package/dist/assets/{ini-BYrrGNTc.js → ini-CyUW1I7Z.js} +0 -0
- /package/dist/assets/{java-B211rJbk.js → java-CFCcGRiM.js} +0 -0
- /package/dist/assets/{julia-DVPsRtBm.js → julia-DQyH_EYb.js} +0 -0
- /package/dist/assets/{kotlin-BYyNerjp.js → kotlin-B8hA5O51.js} +0 -0
- /package/dist/assets/{less-EC9UblGE.js → less-DaVdaas0.js} +0 -0
- /package/dist/assets/{lexon-B1yuUdqW.js → lexon-DLBhPd3y.js} +0 -0
- /package/dist/assets/{lua-Bo6GFzxT.js → lua-gB1c_cif.js} +0 -0
- /package/dist/assets/{m3-C-Y9cBVI.js → m3-C2T3z3gB.js} +0 -0
- /package/dist/assets/{markdown-BHu-wLbe.js → markdown-CKq-Euj1.js} +0 -0
- /package/dist/assets/{mips-Dkds26rX.js → mips-Bcs5qIDX.js} +0 -0
- /package/dist/assets/{msdax-PU4g-8_g.js → msdax-CBP48FHF.js} +0 -0
- /package/dist/assets/{mysql-fPpuGpS7.js → mysql-c7tiPcFj.js} +0 -0
- /package/dist/assets/{objective-c-Wy1e5JUf.js → objective-c-D4BZkeEV.js} +0 -0
- /package/dist/assets/{pascal-BIXwvrqU.js → pascal-CJDZg0MN.js} +0 -0
- /package/dist/assets/{pascaligo-DwR0AtL_.js → pascaligo-Bygi8Ynl.js} +0 -0
- /package/dist/assets/{perl-CGHAl8my.js → perl-aS8bkf7E.js} +0 -0
- /package/dist/assets/{pgsql-B91M73E2.js → pgsql-CzNAYFYl.js} +0 -0
- /package/dist/assets/{php-C70ttrCx.js → php-DOtoEFTT.js} +0 -0
- /package/dist/assets/{pla-BNrXaRh0.js → pla-DugRnao1.js} +0 -0
- /package/dist/assets/{postiats-Dv2IAZR9.js → postiats-DsXwusKh.js} +0 -0
- /package/dist/assets/{powerquery-CkY5Wq7J.js → powerquery-DL3o72oD.js} +0 -0
- /package/dist/assets/{powershell-uGIYLU3C.js → powershell-C1qHekys.js} +0 -0
- /package/dist/assets/{protobuf-CavfmJu-.js → protobuf-DHgsc-cH.js} +0 -0
- /package/dist/assets/{pug-Drqnyitx.js → pug-FZXlE_Iu.js} +0 -0
- /package/dist/assets/{qrcode-7uGsTEmi.js → qrcode-DJDTdjL7.js} +0 -0
- /package/dist/assets/{qsharp-C7yF-Dti.js → qsharp-bLyhI3DT.js} +0 -0
- /package/dist/assets/{r-HXLGP0_-.js → r-DjFGXbAa.js} +0 -0
- /package/dist/assets/{redis-C4y6mf2p.js → redis-DSoZ_V6u.js} +0 -0
- /package/dist/assets/{redshift-DdQrFPrv.js → redshift-CzkdCsrg.js} +0 -0
- /package/dist/assets/{restructuredtext-CBrf2PNj.js → restructuredtext-B_XPrGCQ.js} +0 -0
- /package/dist/assets/{ruby-CVtMoX2B.js → ruby-Cry2RneJ.js} +0 -0
- /package/dist/assets/{rust-Ckq4sbMP.js → rust-CWzdbFq1.js} +0 -0
- /package/dist/assets/{sb-er66OpE3.js → sb-DmofU59h.js} +0 -0
- /package/dist/assets/{scala-BdWZz-hH.js → scala-BUC2l-vN.js} +0 -0
- /package/dist/assets/{scheme-kTV_s2kP.js → scheme-CNIB_72f.js} +0 -0
- /package/dist/assets/{scss-Bymy2VJQ.js → scss-DcL4-NbX.js} +0 -0
- /package/dist/assets/{shell-bwEP1LQ1.js → shell-CgAGvSSA.js} +0 -0
- /package/dist/assets/{solidity-DCddeBgL.js → solidity-DSkt6O_P.js} +0 -0
- /package/dist/assets/{sophia-BMN3AQig.js → sophia--5ytcjDN.js} +0 -0
- /package/dist/assets/{sparql-CSXOLjzi.js → sparql-C1Srr7cD.js} +0 -0
- /package/dist/assets/{sql-COg8ewmK.js → sql-2k_O5fj7.js} +0 -0
- /package/dist/assets/{st-CIaZtBS_.js → st-DCCCPqfz.js} +0 -0
- /package/dist/assets/{swift-DXiOhUHu.js → swift-Dd8mAfOt.js} +0 -0
- /package/dist/assets/{systemverilog-BTDE6Dr2.js → systemverilog-B0-aZYXu.js} +0 -0
- /package/dist/assets/{tcl-B0zy9NvV.js → tcl-CZAZwPVX.js} +0 -0
- /package/dist/assets/{twig-D594l4Z1.js → twig-D-Ag0tdS.js} +0 -0
- /package/dist/assets/{typespec-XKVjaPqo.js → typespec-B7kTFp0Z.js} +0 -0
- /package/dist/assets/{useWorkspaceMenuKeyboard-Bpy_s69Z.js → useWorkspaceMenuKeyboard-uYZpDxhU.js} +0 -0
- /package/dist/assets/{vb-B2wYoKRV.js → vb-20w1JPum.js} +0 -0
- /package/dist/assets/{wgsl-D0Yf2LbK.js → wgsl-CLJfqFt0.js} +0 -0
- /package/dist/assets/{workspace-working-copy-C3FSR3N6.js → workspace-working-copy-Cfz07cOd.js} +0 -0
|
@@ -0,0 +1,1219 @@
|
|
|
1
|
+
// Generated by scripts/build-backend-runtime.ts. Do not edit.
|
|
2
|
+
"use strict";
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __export = (target, all) => {
|
|
10
|
+
for (var name in all)
|
|
11
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
12
|
+
};
|
|
13
|
+
var __copyProps = (to, from, except, desc) => {
|
|
14
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
15
|
+
for (let key of __getOwnPropNames(from))
|
|
16
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
17
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
18
|
+
}
|
|
19
|
+
return to;
|
|
20
|
+
};
|
|
21
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
22
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
23
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
24
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
25
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
26
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
27
|
+
mod
|
|
28
|
+
));
|
|
29
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
30
|
+
var computer_resource_manager_exports = {};
|
|
31
|
+
__export(computer_resource_manager_exports, {
|
|
32
|
+
ComputerResourceManager: () => ComputerResourceManager,
|
|
33
|
+
exactImageRef: () => exactImageRef
|
|
34
|
+
});
|
|
35
|
+
module.exports = __toCommonJS(computer_resource_manager_exports);
|
|
36
|
+
var storageLayout = __toESM(require("../../../backend/storage-layout.cjs"));
|
|
37
|
+
var import_computer_constants = require("./computer-constants.cjs");
|
|
38
|
+
var import_computer_resource_store = require("./computer-resource-store.cjs");
|
|
39
|
+
const { EventEmitter } = require("events");
|
|
40
|
+
const { execFile } = require("child_process");
|
|
41
|
+
const { promisify } = require("util");
|
|
42
|
+
const crypto = require("crypto");
|
|
43
|
+
const fs = require("fs");
|
|
44
|
+
const http = require("http");
|
|
45
|
+
const path = require("path");
|
|
46
|
+
const WebSocket = require("ws");
|
|
47
|
+
const execFileAsync = promisify(execFile);
|
|
48
|
+
const INACTIVE_AGENT_STATUSES = /* @__PURE__ */ new Set(["dead", "error", "exited", "stopped"]);
|
|
49
|
+
const CUA_TOOL_MANIFEST = require("./cua-tools.json");
|
|
50
|
+
const SUPPORTED_UPSTREAM_TOOLS = new Set(
|
|
51
|
+
(CUA_TOOL_MANIFEST.tools || []).map((tool) => String(tool.upstreamName || "").trim()).filter(Boolean)
|
|
52
|
+
);
|
|
53
|
+
const READ_ONLY_TOOLS = new Set(
|
|
54
|
+
(CUA_TOOL_MANIFEST.tools || []).filter((tool) => tool.annotations?.readOnlyHint === true).map((tool) => String(tool.upstreamName || "").trim()).filter(Boolean)
|
|
55
|
+
);
|
|
56
|
+
const STATE_OBSERVATION_TOOLS = /* @__PURE__ */ new Set([
|
|
57
|
+
"get_accessibility_tree",
|
|
58
|
+
"get_browser_state",
|
|
59
|
+
"get_desktop_state",
|
|
60
|
+
"get_window_state"
|
|
61
|
+
]);
|
|
62
|
+
const SCREENSHOT_TOOLS = /* @__PURE__ */ new Set([
|
|
63
|
+
"get_browser_state",
|
|
64
|
+
"get_desktop_state",
|
|
65
|
+
"get_window_state",
|
|
66
|
+
"zoom"
|
|
67
|
+
]);
|
|
68
|
+
const DRIVER_CALL_TIMEOUT_MS = 45e3;
|
|
69
|
+
const DOCKER_TIMEOUT_MS = 9e4;
|
|
70
|
+
const START_TIMEOUT_MS = 45e3;
|
|
71
|
+
const COMPUTER_BROWSER_CDP_PORT = "9223/tcp";
|
|
72
|
+
const COMPUTER_BROWSER_MOUNT = "/opt/farming/chromium";
|
|
73
|
+
const COMPUTER_BROWSER_RELAY_SCRIPT = [
|
|
74
|
+
"import select,socket",
|
|
75
|
+
"listener=socket.socket()",
|
|
76
|
+
"listener.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)",
|
|
77
|
+
'listener.bind(("0.0.0.0",9223))',
|
|
78
|
+
"listener.listen(32)",
|
|
79
|
+
"while True:",
|
|
80
|
+
" client,_=listener.accept()",
|
|
81
|
+
' upstream=socket.create_connection(("127.0.0.1",9222),timeout=5)',
|
|
82
|
+
" sockets=[client,upstream]",
|
|
83
|
+
" while sockets:",
|
|
84
|
+
" readable,_,failed=select.select(sockets,[],sockets,30)",
|
|
85
|
+
" if failed: break",
|
|
86
|
+
" if not readable: continue",
|
|
87
|
+
" for source in readable:",
|
|
88
|
+
" data=source.recv(65536)",
|
|
89
|
+
" if not data: sockets=[]; break",
|
|
90
|
+
" (upstream if source is client else client).sendall(data)",
|
|
91
|
+
" client.close()",
|
|
92
|
+
" upstream.close()"
|
|
93
|
+
].join("\n");
|
|
94
|
+
function recordValue(value) {
|
|
95
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
96
|
+
}
|
|
97
|
+
function pathInside(root, candidate) {
|
|
98
|
+
const relative = path.relative(path.resolve(root), path.resolve(candidate));
|
|
99
|
+
return relative === "" || !relative.startsWith("..") && !path.isAbsolute(relative);
|
|
100
|
+
}
|
|
101
|
+
function computerError(message, status, code, extra = {}) {
|
|
102
|
+
return Object.assign(new Error(message), { status, code, ...extra });
|
|
103
|
+
}
|
|
104
|
+
function randomPassword() {
|
|
105
|
+
return crypto.randomBytes(9).toString("base64url").slice(0, 12);
|
|
106
|
+
}
|
|
107
|
+
function exactImageRef(value) {
|
|
108
|
+
const requested = String(value || "").trim() || import_computer_constants.COMPUTER_IMAGE;
|
|
109
|
+
if (!requested.endsWith(`@${import_computer_constants.COMPUTER_IMAGE_INDEX_DIGEST}`)) {
|
|
110
|
+
throw computerError(
|
|
111
|
+
`Computer image must be pinned to ${import_computer_constants.COMPUTER_IMAGE_INDEX_DIGEST}`,
|
|
112
|
+
400,
|
|
113
|
+
"COMPUTER_IMAGE_NOT_PINNED"
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
return requested;
|
|
117
|
+
}
|
|
118
|
+
function safeNamePart(value) {
|
|
119
|
+
return value.toLowerCase().replace(/[^a-z0-9_.-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 42);
|
|
120
|
+
}
|
|
121
|
+
function parsePort(inspect) {
|
|
122
|
+
return parsePublishedPort(inspect, "6901/tcp");
|
|
123
|
+
}
|
|
124
|
+
function parsePublishedPort(inspect, containerPort) {
|
|
125
|
+
const networkSettings = recordValue(inspect.NetworkSettings);
|
|
126
|
+
const ports = recordValue(networkSettings.Ports);
|
|
127
|
+
const mappings = ports[containerPort];
|
|
128
|
+
if (!Array.isArray(mappings) || mappings.length !== 1) return 0;
|
|
129
|
+
const mapping = recordValue(mappings[0]);
|
|
130
|
+
if (mapping.HostIp !== "127.0.0.1") return 0;
|
|
131
|
+
const port = Number(mapping.HostPort);
|
|
132
|
+
return Number.isSafeInteger(port) && port > 0 && port <= 65535 ? port : 0;
|
|
133
|
+
}
|
|
134
|
+
function waitForHttpPath(port, requestPath, failureMessage, failureCode, timeoutMs = START_TIMEOUT_MS) {
|
|
135
|
+
const deadline = Date.now() + timeoutMs;
|
|
136
|
+
return new Promise((resolve, reject) => {
|
|
137
|
+
const attempt = () => {
|
|
138
|
+
if (Date.now() >= deadline) {
|
|
139
|
+
reject(computerError(failureMessage, 504, failureCode));
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
let successfulResponse = false;
|
|
143
|
+
let attemptFinished = false;
|
|
144
|
+
const retry = () => {
|
|
145
|
+
if (attemptFinished) return;
|
|
146
|
+
attemptFinished = true;
|
|
147
|
+
setTimeout(attempt, 250);
|
|
148
|
+
};
|
|
149
|
+
const request = http.get({
|
|
150
|
+
hostname: "127.0.0.1",
|
|
151
|
+
port,
|
|
152
|
+
path: requestPath,
|
|
153
|
+
timeout: 2e3,
|
|
154
|
+
agent: false,
|
|
155
|
+
headers: { Connection: "close" }
|
|
156
|
+
}, (response) => {
|
|
157
|
+
response.resume();
|
|
158
|
+
if (Number(response.statusCode) >= 200 && Number(response.statusCode) < 500) {
|
|
159
|
+
successfulResponse = true;
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
request.on("timeout", () => request.destroy());
|
|
164
|
+
request.on("error", () => {
|
|
165
|
+
if (!successfulResponse) retry();
|
|
166
|
+
});
|
|
167
|
+
request.on("close", () => {
|
|
168
|
+
if (attemptFinished) return;
|
|
169
|
+
attemptFinished = true;
|
|
170
|
+
if (successfulResponse) resolve();
|
|
171
|
+
else setTimeout(attempt, 250);
|
|
172
|
+
});
|
|
173
|
+
};
|
|
174
|
+
attempt();
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
function waitForHttp(port, timeoutMs = START_TIMEOUT_MS) {
|
|
178
|
+
return waitForHttpPath(
|
|
179
|
+
port,
|
|
180
|
+
"/vnc.html",
|
|
181
|
+
"Computer Viewer did not become ready in time",
|
|
182
|
+
"COMPUTER_VIEWER_TIMEOUT",
|
|
183
|
+
timeoutMs
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
function waitForCdp(port, timeoutMs = START_TIMEOUT_MS) {
|
|
187
|
+
return waitForHttpPath(
|
|
188
|
+
port,
|
|
189
|
+
"/json/version",
|
|
190
|
+
"Computer Chromium did not expose DevTools in time",
|
|
191
|
+
"COMPUTER_BROWSER_CDP_TIMEOUT",
|
|
192
|
+
timeoutMs
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
class ComputerResourceManager extends EventEmitter {
|
|
196
|
+
configDir;
|
|
197
|
+
isEnabled;
|
|
198
|
+
getSettings;
|
|
199
|
+
store;
|
|
200
|
+
docker;
|
|
201
|
+
configFingerprint;
|
|
202
|
+
operations = /* @__PURE__ */ new Map();
|
|
203
|
+
stopAdmissions = /* @__PURE__ */ new Map();
|
|
204
|
+
controlAdmissions = /* @__PURE__ */ new Map();
|
|
205
|
+
viewerSockets = /* @__PURE__ */ new Map();
|
|
206
|
+
browserLeases = /* @__PURE__ */ new Map();
|
|
207
|
+
capabilityCache = null;
|
|
208
|
+
preparePromise = null;
|
|
209
|
+
constructor(options) {
|
|
210
|
+
super();
|
|
211
|
+
this.configDir = options.configDir;
|
|
212
|
+
this.isEnabled = options.isEnabled || (() => false);
|
|
213
|
+
this.getSettings = options.getSettings || (() => ({}));
|
|
214
|
+
this.store = new import_computer_resource_store.ComputerResourceStore(options.configDir);
|
|
215
|
+
this.configFingerprint = crypto.createHash("sha256").update(options.configDir).digest("hex").slice(0, 12);
|
|
216
|
+
this.docker = options.dockerRunner || (async (args, runOptions = {}) => {
|
|
217
|
+
const result = await execFileAsync("docker", args, {
|
|
218
|
+
encoding: "utf8",
|
|
219
|
+
timeout: runOptions.timeoutMs || DOCKER_TIMEOUT_MS,
|
|
220
|
+
maxBuffer: runOptions.maxBuffer || 20 * 1024 * 1024
|
|
221
|
+
});
|
|
222
|
+
return {
|
|
223
|
+
stdout: String(result.stdout || ""),
|
|
224
|
+
stderr: String(result.stderr || "")
|
|
225
|
+
};
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
async init() {
|
|
229
|
+
this.store.init();
|
|
230
|
+
for (const resource of this.store.list()) {
|
|
231
|
+
if (!resource.containerId) {
|
|
232
|
+
if (resource.status !== "stopped") {
|
|
233
|
+
this.patch(resource.id, {
|
|
234
|
+
status: "failed",
|
|
235
|
+
error: "Owned Computer container identity is missing",
|
|
236
|
+
viewerPort: 0,
|
|
237
|
+
sessionId: ""
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
continue;
|
|
241
|
+
}
|
|
242
|
+
try {
|
|
243
|
+
const inspect = await this.inspectOwnedContainer(resource);
|
|
244
|
+
const running = recordValue(inspect.State).Running === true;
|
|
245
|
+
if (!running) {
|
|
246
|
+
this.patch(resource.id, {
|
|
247
|
+
status: "stopped",
|
|
248
|
+
viewerPort: 0,
|
|
249
|
+
sessionId: "",
|
|
250
|
+
error: ""
|
|
251
|
+
});
|
|
252
|
+
continue;
|
|
253
|
+
}
|
|
254
|
+
const viewerPort = parsePort(inspect);
|
|
255
|
+
if (!viewerPort) throw new Error("Computer container has no loopback Viewer port");
|
|
256
|
+
await this.ensureDriver(resource, resource.sessionId || this.sessionId(resource));
|
|
257
|
+
this.patch(resource.id, {
|
|
258
|
+
status: "running",
|
|
259
|
+
viewerPort,
|
|
260
|
+
sessionId: resource.sessionId || this.sessionId(resource),
|
|
261
|
+
error: ""
|
|
262
|
+
});
|
|
263
|
+
} catch (caught) {
|
|
264
|
+
const error = caught;
|
|
265
|
+
this.patch(resource.id, {
|
|
266
|
+
status: "failed",
|
|
267
|
+
viewerPort: 0,
|
|
268
|
+
sessionId: "",
|
|
269
|
+
error: error.message || "Computer recovery failed"
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
requireEnabled() {
|
|
275
|
+
if (!this.isEnabled()) {
|
|
276
|
+
throw computerError("Computer plugin is disabled", 409, "COMPUTER_DISABLED");
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
imageRef() {
|
|
280
|
+
return exactImageRef(this.getSettings().computerImage);
|
|
281
|
+
}
|
|
282
|
+
compatibilityMode() {
|
|
283
|
+
return this.getSettings().computerCompatibilityMode === true;
|
|
284
|
+
}
|
|
285
|
+
list() {
|
|
286
|
+
return this.store.snapshot();
|
|
287
|
+
}
|
|
288
|
+
get(id) {
|
|
289
|
+
const resource = this.store.get(id);
|
|
290
|
+
if (!resource) throw computerError("Computer Resource was not found", 404, "COMPUTER_NOT_FOUND");
|
|
291
|
+
return (0, import_computer_resource_store.publicResource)(resource, this.store.revision);
|
|
292
|
+
}
|
|
293
|
+
privateResource(id) {
|
|
294
|
+
const resource = this.store.get(id);
|
|
295
|
+
if (!resource) throw computerError("Computer Resource was not found", 404, "COMPUTER_NOT_FOUND");
|
|
296
|
+
return resource;
|
|
297
|
+
}
|
|
298
|
+
create(input) {
|
|
299
|
+
this.requireEnabled();
|
|
300
|
+
const existing = this.store.list().find(
|
|
301
|
+
(resource2) => resource2.ownerAgentId === input.ownerAgentId
|
|
302
|
+
);
|
|
303
|
+
if (existing) return (0, import_computer_resource_store.publicResource)(existing, this.store.revision);
|
|
304
|
+
const resource = this.store.create(input);
|
|
305
|
+
this.emitResource(resource);
|
|
306
|
+
return (0, import_computer_resource_store.publicResource)(resource, this.store.revision);
|
|
307
|
+
}
|
|
308
|
+
async acquireBrowser(input) {
|
|
309
|
+
this.requireEnabled();
|
|
310
|
+
const executablePath = this.browserExecutableInContainer(input.executablePath);
|
|
311
|
+
this.ensureBrowserCacheTraversal(input.executablePath);
|
|
312
|
+
const resource = this.create({
|
|
313
|
+
ownerAgentId: input.ownerAgentId,
|
|
314
|
+
projectRootId: input.projectRootId,
|
|
315
|
+
workspace: input.workspace,
|
|
316
|
+
name: "Desktop"
|
|
317
|
+
});
|
|
318
|
+
if (resource.projectRootId !== input.projectRootId || resource.workspace !== input.workspace) {
|
|
319
|
+
throw computerError(
|
|
320
|
+
"The Agent Computer belongs to a different Project workspace",
|
|
321
|
+
409,
|
|
322
|
+
"COMPUTER_BROWSER_OWNER_MISMATCH"
|
|
323
|
+
);
|
|
324
|
+
}
|
|
325
|
+
await this.ensureBrowserCacheMount(resource.id);
|
|
326
|
+
await this.start(resource.id);
|
|
327
|
+
const result = await this.enqueue(resource.id, async () => {
|
|
328
|
+
const current = this.privateResource(resource.id);
|
|
329
|
+
if (current.status !== "running" || !current.containerId) {
|
|
330
|
+
throw computerError(
|
|
331
|
+
"Computer stopped before Chromium could start",
|
|
332
|
+
409,
|
|
333
|
+
"COMPUTER_NOT_RUNNING"
|
|
334
|
+
);
|
|
335
|
+
}
|
|
336
|
+
const inspect = await this.inspectOwnedContainer(current);
|
|
337
|
+
const cdpPort = parsePublishedPort(inspect, COMPUTER_BROWSER_CDP_PORT);
|
|
338
|
+
if (!cdpPort) {
|
|
339
|
+
throw computerError(
|
|
340
|
+
"Computer container has no loopback DevTools port",
|
|
341
|
+
409,
|
|
342
|
+
"COMPUTER_BROWSER_PORT_MISSING"
|
|
343
|
+
);
|
|
344
|
+
}
|
|
345
|
+
try {
|
|
346
|
+
await waitForCdp(cdpPort, 500);
|
|
347
|
+
} catch {
|
|
348
|
+
const displayReady = await this.waitForContainer(current.containerId, [
|
|
349
|
+
"test",
|
|
350
|
+
"-S",
|
|
351
|
+
"/tmp/.X11-unix/X1"
|
|
352
|
+
]);
|
|
353
|
+
if (!displayReady) {
|
|
354
|
+
throw computerError(
|
|
355
|
+
"Computer desktop did not become ready for Chromium",
|
|
356
|
+
504,
|
|
357
|
+
"COMPUTER_BROWSER_DISPLAY_TIMEOUT"
|
|
358
|
+
);
|
|
359
|
+
}
|
|
360
|
+
let chromiumReady = await this.waitForContainer(current.containerId, [
|
|
361
|
+
"python3",
|
|
362
|
+
"-c",
|
|
363
|
+
'import urllib.request; urllib.request.urlopen("http://127.0.0.1:9222/json/version", timeout=1)'
|
|
364
|
+
], 500);
|
|
365
|
+
if (!chromiumReady) {
|
|
366
|
+
await this.docker([
|
|
367
|
+
"exec",
|
|
368
|
+
"-d",
|
|
369
|
+
"-u",
|
|
370
|
+
import_computer_constants.COMPUTER_USER,
|
|
371
|
+
"-e",
|
|
372
|
+
"HOME=/home/cua",
|
|
373
|
+
"-e",
|
|
374
|
+
"DISPLAY=:1",
|
|
375
|
+
current.containerId,
|
|
376
|
+
executablePath,
|
|
377
|
+
"--no-sandbox",
|
|
378
|
+
"--remote-debugging-port=9222",
|
|
379
|
+
"--user-data-dir=/home/cua/.farming-browser",
|
|
380
|
+
"--no-first-run",
|
|
381
|
+
"--no-default-browser-check",
|
|
382
|
+
"about:blank"
|
|
383
|
+
], { timeoutMs: 1e4 });
|
|
384
|
+
chromiumReady = await this.waitForContainer(current.containerId, [
|
|
385
|
+
"python3",
|
|
386
|
+
"-c",
|
|
387
|
+
'import urllib.request; urllib.request.urlopen("http://127.0.0.1:9222/json/version", timeout=1)'
|
|
388
|
+
]);
|
|
389
|
+
}
|
|
390
|
+
if (!chromiumReady) {
|
|
391
|
+
throw computerError(
|
|
392
|
+
"Chromium did not expose its internal DevTools endpoint",
|
|
393
|
+
504,
|
|
394
|
+
"COMPUTER_BROWSER_CHROMIUM_TIMEOUT"
|
|
395
|
+
);
|
|
396
|
+
}
|
|
397
|
+
await this.docker([
|
|
398
|
+
"exec",
|
|
399
|
+
"-d",
|
|
400
|
+
current.containerId,
|
|
401
|
+
"python3",
|
|
402
|
+
"-c",
|
|
403
|
+
COMPUTER_BROWSER_RELAY_SCRIPT
|
|
404
|
+
], { timeoutMs: 1e4 });
|
|
405
|
+
await waitForCdp(cdpPort);
|
|
406
|
+
}
|
|
407
|
+
this.browserLeases.set(
|
|
408
|
+
resource.id,
|
|
409
|
+
(this.browserLeases.get(resource.id) || 0) + 1
|
|
410
|
+
);
|
|
411
|
+
return { cdpUrl: `http://127.0.0.1:${cdpPort}`, leaseKey: resource.id };
|
|
412
|
+
});
|
|
413
|
+
return result;
|
|
414
|
+
}
|
|
415
|
+
releaseBrowser(leaseKey) {
|
|
416
|
+
const current = this.browserLeases.get(leaseKey) || 0;
|
|
417
|
+
if (current <= 1) this.browserLeases.delete(leaseKey);
|
|
418
|
+
else this.browserLeases.set(leaseKey, current - 1);
|
|
419
|
+
return Promise.resolve();
|
|
420
|
+
}
|
|
421
|
+
async verifyBrowserExecutable(executablePath) {
|
|
422
|
+
const containerPath = this.browserExecutableInContainer(executablePath);
|
|
423
|
+
this.ensureBrowserCacheTraversal(executablePath);
|
|
424
|
+
const result = await this.docker([
|
|
425
|
+
"run",
|
|
426
|
+
"--rm",
|
|
427
|
+
"--label",
|
|
428
|
+
"farming.dev/kind=computer-browser-probe",
|
|
429
|
+
"--label",
|
|
430
|
+
`farming.dev/config=${this.configFingerprint}`,
|
|
431
|
+
...this.compatibilityMode() ? ["--security-opt", "seccomp=unconfined"] : [],
|
|
432
|
+
"--user",
|
|
433
|
+
import_computer_constants.COMPUTER_USER,
|
|
434
|
+
"-e",
|
|
435
|
+
"HOME=/home/cua",
|
|
436
|
+
"--entrypoint",
|
|
437
|
+
containerPath,
|
|
438
|
+
"-v",
|
|
439
|
+
`${this.browserCacheRoot()}:${COMPUTER_BROWSER_MOUNT}:ro`,
|
|
440
|
+
this.imageRef(),
|
|
441
|
+
"--no-sandbox",
|
|
442
|
+
"--version"
|
|
443
|
+
], { timeoutMs: 3e4 });
|
|
444
|
+
const version = result.stdout.trim() || result.stderr.trim();
|
|
445
|
+
if (!/(?:Google Chrome|Chromium)/i.test(version)) {
|
|
446
|
+
throw computerError(
|
|
447
|
+
`Computer Chromium verification returned an unexpected version: ${version}`,
|
|
448
|
+
409,
|
|
449
|
+
"COMPUTER_BROWSER_VERSION_MISMATCH"
|
|
450
|
+
);
|
|
451
|
+
}
|
|
452
|
+
return version;
|
|
453
|
+
}
|
|
454
|
+
rename(id, name) {
|
|
455
|
+
const normalized = String(name || "").trim().slice(0, 120);
|
|
456
|
+
if (!normalized) throw computerError("Computer name is required", 400, "COMPUTER_NAME_REQUIRED");
|
|
457
|
+
return this.patch(id, { name: normalized });
|
|
458
|
+
}
|
|
459
|
+
snapshot() {
|
|
460
|
+
return this.stateSnapshot();
|
|
461
|
+
}
|
|
462
|
+
stateSnapshot() {
|
|
463
|
+
return this.store.snapshot();
|
|
464
|
+
}
|
|
465
|
+
async capability(refresh = false) {
|
|
466
|
+
if (this.capabilityCache && !refresh) {
|
|
467
|
+
return { ...this.capabilityCache, enabled: this.isEnabled() };
|
|
468
|
+
}
|
|
469
|
+
const probe = await this.probeSettings(this.getSettings());
|
|
470
|
+
this.capabilityCache = {
|
|
471
|
+
available: probe.dockerAvailable && probe.imageReady,
|
|
472
|
+
enabled: this.isEnabled(),
|
|
473
|
+
...probe,
|
|
474
|
+
imageDigest: import_computer_constants.COMPUTER_IMAGE_INDEX_DIGEST,
|
|
475
|
+
driverVersion: import_computer_constants.COMPUTER_DRIVER_VERSION,
|
|
476
|
+
compatibilityMode: this.compatibilityMode()
|
|
477
|
+
};
|
|
478
|
+
return this.capabilityCache;
|
|
479
|
+
}
|
|
480
|
+
async probeSettings(settings) {
|
|
481
|
+
const image = exactImageRef(settings.computerImage);
|
|
482
|
+
let dockerAvailable = false;
|
|
483
|
+
let imageReady = false;
|
|
484
|
+
let error = "";
|
|
485
|
+
try {
|
|
486
|
+
await this.docker(["version", "--format", "{{.Server.Version}}"], { timeoutMs: 8e3 });
|
|
487
|
+
dockerAvailable = true;
|
|
488
|
+
await this.docker(["image", "inspect", image, "--format", "{{.Id}}"], { timeoutMs: 8e3 });
|
|
489
|
+
imageReady = true;
|
|
490
|
+
} catch (caught) {
|
|
491
|
+
error = caught.message || String(caught);
|
|
492
|
+
}
|
|
493
|
+
return { dockerAvailable, imageReady, image, error };
|
|
494
|
+
}
|
|
495
|
+
prepare() {
|
|
496
|
+
if (this.preparePromise) return this.preparePromise;
|
|
497
|
+
this.preparePromise = (async () => {
|
|
498
|
+
await this.docker(["version", "--format", "{{.Server.Version}}"], { timeoutMs: 8e3 });
|
|
499
|
+
await this.docker(["pull", this.imageRef()], { timeoutMs: 20 * 6e4, maxBuffer: 64 * 1024 * 1024 });
|
|
500
|
+
const probeName = `farming-computer-probe-${this.configFingerprint}-${crypto.randomBytes(3).toString("hex")}`;
|
|
501
|
+
const args = [
|
|
502
|
+
"run",
|
|
503
|
+
"--rm",
|
|
504
|
+
"--name",
|
|
505
|
+
probeName,
|
|
506
|
+
"--label",
|
|
507
|
+
"farming.dev/kind=computer-probe",
|
|
508
|
+
"--label",
|
|
509
|
+
`farming.dev/config=${this.configFingerprint}`,
|
|
510
|
+
...this.compatibilityMode() ? ["--security-opt", "seccomp=unconfined"] : [],
|
|
511
|
+
"--entrypoint",
|
|
512
|
+
"/bin/sh",
|
|
513
|
+
this.imageRef(),
|
|
514
|
+
"-lc",
|
|
515
|
+
`su -s /bin/sh -c 'HOME=/home/cua ${import_computer_constants.COMPUTER_DRIVER_BIN} --version' ${import_computer_constants.COMPUTER_USER}`
|
|
516
|
+
];
|
|
517
|
+
try {
|
|
518
|
+
const result = await this.docker(args, { timeoutMs: 6e4 });
|
|
519
|
+
if (!result.stdout.includes(import_computer_constants.COMPUTER_DRIVER_VERSION)) {
|
|
520
|
+
throw computerError(
|
|
521
|
+
`Computer image contains an unexpected Cua Driver version: ${result.stdout.trim()}`,
|
|
522
|
+
409,
|
|
523
|
+
"COMPUTER_DRIVER_VERSION_MISMATCH"
|
|
524
|
+
);
|
|
525
|
+
}
|
|
526
|
+
} catch (caught) {
|
|
527
|
+
const message = caught.message || String(caught);
|
|
528
|
+
const compatibilityRequired = /operation not permitted|pthread_create|clone3/i.test(
|
|
529
|
+
`${message}
|
|
530
|
+
${caught.stderr || ""}`
|
|
531
|
+
);
|
|
532
|
+
throw computerError(
|
|
533
|
+
compatibilityRequired ? "This Docker Engine requires explicit Computer compatibility mode" : `Computer runtime probe failed: ${message}`,
|
|
534
|
+
409,
|
|
535
|
+
compatibilityRequired ? "COMPUTER_COMPATIBILITY_REQUIRED" : "COMPUTER_PROBE_FAILED",
|
|
536
|
+
{ compatibilityRequired }
|
|
537
|
+
);
|
|
538
|
+
}
|
|
539
|
+
this.capabilityCache = null;
|
|
540
|
+
return this.capability(true);
|
|
541
|
+
})().finally(() => {
|
|
542
|
+
this.preparePromise = null;
|
|
543
|
+
});
|
|
544
|
+
return this.preparePromise;
|
|
545
|
+
}
|
|
546
|
+
start(id) {
|
|
547
|
+
this.requireEnabled();
|
|
548
|
+
this.assertTransitionAdmissionOpen(id);
|
|
549
|
+
return this.enqueue(id, async () => {
|
|
550
|
+
this.assertTransitionAdmissionOpen(id);
|
|
551
|
+
let resource = this.privateResource(id);
|
|
552
|
+
if (resource.status === "running") return (0, import_computer_resource_store.publicResource)(resource, this.store.revision);
|
|
553
|
+
const generation = resource.generation + 1;
|
|
554
|
+
const password = resource.vncPassword || randomPassword();
|
|
555
|
+
const containerName = resource.containerName || this.containerName(resource);
|
|
556
|
+
resource = this.patch(id, {
|
|
557
|
+
status: "starting",
|
|
558
|
+
generation,
|
|
559
|
+
controlOwner: "agent",
|
|
560
|
+
controlEpoch: resource.controlEpoch + 1,
|
|
561
|
+
needsObserve: false,
|
|
562
|
+
vncPassword: password,
|
|
563
|
+
containerName,
|
|
564
|
+
viewerPort: 0,
|
|
565
|
+
sessionId: "",
|
|
566
|
+
error: ""
|
|
567
|
+
}, true);
|
|
568
|
+
try {
|
|
569
|
+
let containerId = resource.containerId;
|
|
570
|
+
if (containerId) {
|
|
571
|
+
await this.inspectOwnedContainer(resource);
|
|
572
|
+
} else {
|
|
573
|
+
const labels = this.containerLabels(resource);
|
|
574
|
+
const create = await this.docker([
|
|
575
|
+
"create",
|
|
576
|
+
"--name",
|
|
577
|
+
containerName,
|
|
578
|
+
...labels.flatMap(([key, value]) => ["--label", `${key}=${value}`]),
|
|
579
|
+
"--cpus",
|
|
580
|
+
import_computer_constants.COMPUTER_CONTAINER_CPUS,
|
|
581
|
+
"--memory",
|
|
582
|
+
import_computer_constants.COMPUTER_CONTAINER_MEMORY,
|
|
583
|
+
"--shm-size",
|
|
584
|
+
import_computer_constants.COMPUTER_CONTAINER_SHM_SIZE,
|
|
585
|
+
"--pids-limit",
|
|
586
|
+
import_computer_constants.COMPUTER_CONTAINER_PIDS,
|
|
587
|
+
"--add-host",
|
|
588
|
+
"host.docker.internal:host-gateway",
|
|
589
|
+
"-p",
|
|
590
|
+
"127.0.0.1::6901",
|
|
591
|
+
"-p",
|
|
592
|
+
"127.0.0.1::9223",
|
|
593
|
+
"-v",
|
|
594
|
+
`${this.browserCacheRoot()}:${COMPUTER_BROWSER_MOUNT}:ro`,
|
|
595
|
+
"-e",
|
|
596
|
+
`VNC_PW=${password}`,
|
|
597
|
+
...this.compatibilityMode() ? ["--security-opt", "seccomp=unconfined"] : [],
|
|
598
|
+
this.imageRef()
|
|
599
|
+
]);
|
|
600
|
+
containerId = create.stdout.trim();
|
|
601
|
+
if (!/^[a-f0-9]{12,64}$/i.test(containerId)) {
|
|
602
|
+
throw new Error("Docker did not return an exact Computer container identity");
|
|
603
|
+
}
|
|
604
|
+
resource = this.patch(id, { containerId }, true);
|
|
605
|
+
await this.inspectOwnedContainer(resource);
|
|
606
|
+
}
|
|
607
|
+
await this.docker(["start", containerId]);
|
|
608
|
+
const inspect = await this.inspectOwnedContainer(this.privateResource(id));
|
|
609
|
+
const viewerPort = parsePort(inspect);
|
|
610
|
+
if (!viewerPort) throw new Error("Docker did not publish the Computer Viewer on loopback");
|
|
611
|
+
await waitForHttp(viewerPort);
|
|
612
|
+
const sessionId = this.sessionId(this.privateResource(id));
|
|
613
|
+
await this.ensureDriver(this.privateResource(id), sessionId);
|
|
614
|
+
return this.patch(id, {
|
|
615
|
+
status: "running",
|
|
616
|
+
viewerPort,
|
|
617
|
+
sessionId,
|
|
618
|
+
error: ""
|
|
619
|
+
});
|
|
620
|
+
} catch (caught) {
|
|
621
|
+
const error = caught;
|
|
622
|
+
this.patch(id, {
|
|
623
|
+
status: "failed",
|
|
624
|
+
viewerPort: 0,
|
|
625
|
+
sessionId: "",
|
|
626
|
+
error: error.message || "Computer start failed"
|
|
627
|
+
});
|
|
628
|
+
throw error;
|
|
629
|
+
}
|
|
630
|
+
});
|
|
631
|
+
}
|
|
632
|
+
stop(id, internal = false) {
|
|
633
|
+
if (!internal) this.requireEnabled();
|
|
634
|
+
if (!internal && (this.browserLeases.get(id) || 0) > 0) {
|
|
635
|
+
throw computerError(
|
|
636
|
+
"Stop the Agent Browsers using this Computer first",
|
|
637
|
+
409,
|
|
638
|
+
"COMPUTER_IN_USE_BY_BROWSER"
|
|
639
|
+
);
|
|
640
|
+
}
|
|
641
|
+
this.holdStopAdmission(id);
|
|
642
|
+
return this.enqueue(id, async () => {
|
|
643
|
+
const resource = this.privateResource(id);
|
|
644
|
+
if (!resource.containerId || resource.status === "stopped") {
|
|
645
|
+
return this.patch(id, {
|
|
646
|
+
status: "stopped",
|
|
647
|
+
viewerPort: 0,
|
|
648
|
+
sessionId: "",
|
|
649
|
+
controlOwner: "agent",
|
|
650
|
+
needsObserve: false,
|
|
651
|
+
error: ""
|
|
652
|
+
});
|
|
653
|
+
}
|
|
654
|
+
this.patch(id, { status: "stopping" });
|
|
655
|
+
this.closeViewers(id, 4e3, "Computer stopped");
|
|
656
|
+
try {
|
|
657
|
+
if (resource.sessionId) {
|
|
658
|
+
await this.driverCall(resource, "end_session", { session: resource.sessionId }).catch(() => null);
|
|
659
|
+
}
|
|
660
|
+
await this.inspectOwnedContainer(resource);
|
|
661
|
+
await this.docker(["stop", "--time", "10", resource.containerId], { timeoutMs: 3e4 });
|
|
662
|
+
return this.patch(id, {
|
|
663
|
+
status: "stopped",
|
|
664
|
+
viewerPort: 0,
|
|
665
|
+
sessionId: "",
|
|
666
|
+
controlOwner: "agent",
|
|
667
|
+
controlEpoch: resource.controlEpoch + 1,
|
|
668
|
+
needsObserve: false,
|
|
669
|
+
error: ""
|
|
670
|
+
});
|
|
671
|
+
} catch (caught) {
|
|
672
|
+
const error = caught;
|
|
673
|
+
this.patch(id, { status: "failed", error: error.message || "Computer stop failed" });
|
|
674
|
+
throw error;
|
|
675
|
+
}
|
|
676
|
+
}).finally(() => this.releaseStopAdmission(id));
|
|
677
|
+
}
|
|
678
|
+
async delete(id, internal = false) {
|
|
679
|
+
if (!internal) this.requireEnabled();
|
|
680
|
+
if (!internal && (this.browserLeases.get(id) || 0) > 0) {
|
|
681
|
+
throw computerError(
|
|
682
|
+
"Delete the Agent Browsers using this Computer first",
|
|
683
|
+
409,
|
|
684
|
+
"COMPUTER_IN_USE_BY_BROWSER"
|
|
685
|
+
);
|
|
686
|
+
}
|
|
687
|
+
this.holdStopAdmission(id);
|
|
688
|
+
try {
|
|
689
|
+
await this.stop(id, true);
|
|
690
|
+
return await this.enqueue(id, async () => {
|
|
691
|
+
const resource = this.privateResource(id);
|
|
692
|
+
if (resource.containerId) {
|
|
693
|
+
await this.inspectOwnedContainer(resource);
|
|
694
|
+
await this.docker(["rm", resource.containerId], { timeoutMs: 3e4 });
|
|
695
|
+
}
|
|
696
|
+
this.closeViewers(id, 4e3, "Computer deleted");
|
|
697
|
+
this.browserLeases.delete(id);
|
|
698
|
+
this.store.remove(id);
|
|
699
|
+
this.emit("deleted", { id, collectionRevision: this.store.revision });
|
|
700
|
+
return { id, collectionRevision: this.store.revision };
|
|
701
|
+
});
|
|
702
|
+
} finally {
|
|
703
|
+
this.releaseStopAdmission(id);
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
takeControl(id, owner) {
|
|
707
|
+
this.requireEnabled();
|
|
708
|
+
const resource = this.privateResource(id);
|
|
709
|
+
if (resource.status !== "running") {
|
|
710
|
+
throw computerError("Computer is not running", 409, "COMPUTER_NOT_RUNNING");
|
|
711
|
+
}
|
|
712
|
+
if (owner === resource.controlOwner) {
|
|
713
|
+
return Promise.resolve((0, import_computer_resource_store.publicResource)(resource, this.store.revision));
|
|
714
|
+
}
|
|
715
|
+
if (this.isStopping(id)) {
|
|
716
|
+
throw computerError("Computer is stopping", 409, "COMPUTER_STOPPING");
|
|
717
|
+
}
|
|
718
|
+
if (this.isControlChanging(id)) {
|
|
719
|
+
throw computerError("Computer control is changing", 409, "COMPUTER_CONTROL_CHANGING");
|
|
720
|
+
}
|
|
721
|
+
this.holdControlAdmission(id);
|
|
722
|
+
return this.enqueue(id, async () => {
|
|
723
|
+
const current = this.privateResource(id);
|
|
724
|
+
if (current.status !== "running") {
|
|
725
|
+
throw computerError("Computer is not running", 409, "COMPUTER_NOT_RUNNING");
|
|
726
|
+
}
|
|
727
|
+
if (owner === current.controlOwner) {
|
|
728
|
+
return (0, import_computer_resource_store.publicResource)(current, this.store.revision);
|
|
729
|
+
}
|
|
730
|
+
this.closeViewers(id, 4002, "Computer control changed");
|
|
731
|
+
return this.patch(id, {
|
|
732
|
+
controlOwner: owner,
|
|
733
|
+
controlEpoch: current.controlEpoch + 1,
|
|
734
|
+
needsObserve: owner === "agent"
|
|
735
|
+
});
|
|
736
|
+
}).finally(() => this.releaseControlAdmission(id));
|
|
737
|
+
}
|
|
738
|
+
callTool(id, tool, input, caller = "agent") {
|
|
739
|
+
this.requireEnabled();
|
|
740
|
+
if (!SUPPORTED_UPSTREAM_TOOLS.has(tool)) {
|
|
741
|
+
throw computerError(`Computer tool is not supported: ${tool}`, 400, "COMPUTER_TOOL_NOT_SUPPORTED");
|
|
742
|
+
}
|
|
743
|
+
const resource = this.privateResource(id);
|
|
744
|
+
if (this.isStopping(id)) {
|
|
745
|
+
throw computerError("Computer is stopping", 409, "COMPUTER_STOPPING");
|
|
746
|
+
}
|
|
747
|
+
if (this.isControlChanging(id)) {
|
|
748
|
+
throw computerError("Computer control is changing", 409, "COMPUTER_CONTROL_CHANGING");
|
|
749
|
+
}
|
|
750
|
+
if (resource.status !== "running") {
|
|
751
|
+
throw computerError("Computer is not running", 409, "COMPUTER_NOT_RUNNING");
|
|
752
|
+
}
|
|
753
|
+
if (resource.controlOwner !== caller) {
|
|
754
|
+
throw computerError(
|
|
755
|
+
caller === "agent" ? "A human currently controls this Computer" : "The Agent currently controls this Computer",
|
|
756
|
+
409,
|
|
757
|
+
"COMPUTER_CONTROL_OWNER_MISMATCH"
|
|
758
|
+
);
|
|
759
|
+
}
|
|
760
|
+
if (caller === "agent" && resource.needsObserve && !READ_ONLY_TOOLS.has(tool)) {
|
|
761
|
+
throw computerError(
|
|
762
|
+
"Observe the Computer after human control before sending another action",
|
|
763
|
+
409,
|
|
764
|
+
"COMPUTER_OBSERVE_REQUIRED"
|
|
765
|
+
);
|
|
766
|
+
}
|
|
767
|
+
const admittedGeneration = resource.generation;
|
|
768
|
+
const admittedEpoch = resource.controlEpoch;
|
|
769
|
+
return this.enqueue(id, async () => {
|
|
770
|
+
const current = this.privateResource(id);
|
|
771
|
+
if (current.generation !== admittedGeneration || current.controlEpoch !== admittedEpoch || current.status !== "running" || current.controlOwner !== caller) {
|
|
772
|
+
throw computerError("Computer ownership changed before this action ran", 409, "COMPUTER_STALE_ADMISSION");
|
|
773
|
+
}
|
|
774
|
+
const args = { ...input };
|
|
775
|
+
if ("session" in args || this.toolAcceptsSession(tool)) args.session = current.sessionId;
|
|
776
|
+
if (tool === "start_session") {
|
|
777
|
+
return { content: [{ type: "text", text: `Farming manages session ${current.sessionId}` }] };
|
|
778
|
+
}
|
|
779
|
+
if (tool === "end_session") {
|
|
780
|
+
return { content: [{ type: "text", text: "Farming keeps this Computer session alive until Stop." }] };
|
|
781
|
+
}
|
|
782
|
+
const screenshotPath = SCREENSHOT_TOOLS.has(tool) ? `/tmp/farming-${current.id}-${crypto.randomBytes(5).toString("hex")}.png` : "";
|
|
783
|
+
let result;
|
|
784
|
+
try {
|
|
785
|
+
result = await this.driverCall(current, tool, args, screenshotPath);
|
|
786
|
+
} catch (caught) {
|
|
787
|
+
const error = caught;
|
|
788
|
+
if (error.killed || error.signal || /timed out/i.test(error.message || "")) {
|
|
789
|
+
this.patch(id, { needsObserve: true, error: `Uncertain ${tool} outcome; observe before retrying` });
|
|
790
|
+
throw computerError(
|
|
791
|
+
`Computer action ${tool} timed out with an uncertain outcome; observe before retrying`,
|
|
792
|
+
504,
|
|
793
|
+
"COMPUTER_ACTION_UNCERTAIN",
|
|
794
|
+
{ uncertain: true }
|
|
795
|
+
);
|
|
796
|
+
}
|
|
797
|
+
throw error;
|
|
798
|
+
}
|
|
799
|
+
const latest = this.privateResource(id);
|
|
800
|
+
if (caller === "agent" && STATE_OBSERVATION_TOOLS.has(tool) && latest.needsObserve) {
|
|
801
|
+
this.patch(id, { needsObserve: false, error: "" });
|
|
802
|
+
}
|
|
803
|
+
return result;
|
|
804
|
+
});
|
|
805
|
+
}
|
|
806
|
+
async reconcileAgentLifecycle(agentStates) {
|
|
807
|
+
const agents = new Map(agentStates.map((agent) => [String(agent.id || ""), agent]));
|
|
808
|
+
for (const resource of this.store.list()) {
|
|
809
|
+
const owner = agents.get(resource.ownerAgentId);
|
|
810
|
+
if (!owner) {
|
|
811
|
+
await this.delete(resource.id, true);
|
|
812
|
+
continue;
|
|
813
|
+
}
|
|
814
|
+
const operation = String(owner.lifecycleOperation?.type || "");
|
|
815
|
+
const preservesRuntime = ["permission-restart", "runtime-switch"].includes(operation);
|
|
816
|
+
const stopped = owner.archived === true || !preservesRuntime && INACTIVE_AGENT_STATUSES.has(String(owner.status || ""));
|
|
817
|
+
if (stopped && resource.status !== "stopped") {
|
|
818
|
+
await this.stop(resource.id, true);
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
async stopAll() {
|
|
823
|
+
for (const resource of this.store.list()) {
|
|
824
|
+
if (resource.status !== "stopped" || resource.containerId) {
|
|
825
|
+
await this.stop(resource.id, true);
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
async resetAllContainers() {
|
|
830
|
+
for (const candidate of this.store.list()) {
|
|
831
|
+
this.holdStopAdmission(candidate.id);
|
|
832
|
+
try {
|
|
833
|
+
await this.stop(candidate.id, true);
|
|
834
|
+
await this.enqueue(candidate.id, async () => {
|
|
835
|
+
const resource = this.privateResource(candidate.id);
|
|
836
|
+
if (resource.containerId) {
|
|
837
|
+
await this.inspectOwnedContainer(resource);
|
|
838
|
+
await this.docker(["rm", resource.containerId], { timeoutMs: 3e4 });
|
|
839
|
+
}
|
|
840
|
+
this.patch(resource.id, {
|
|
841
|
+
containerId: "",
|
|
842
|
+
containerName: "",
|
|
843
|
+
vncPassword: "",
|
|
844
|
+
viewerPort: 0,
|
|
845
|
+
sessionId: "",
|
|
846
|
+
status: "stopped",
|
|
847
|
+
error: ""
|
|
848
|
+
});
|
|
849
|
+
});
|
|
850
|
+
} finally {
|
|
851
|
+
this.releaseStopAdmission(candidate.id);
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
viewerConfig(id) {
|
|
856
|
+
const resource = this.privateResource(id);
|
|
857
|
+
if (resource.status !== "running" || !resource.viewerPort) {
|
|
858
|
+
throw computerError("Computer Viewer is not running", 409, "COMPUTER_NOT_RUNNING");
|
|
859
|
+
}
|
|
860
|
+
return {
|
|
861
|
+
host: "127.0.0.1",
|
|
862
|
+
port: resource.viewerPort,
|
|
863
|
+
password: resource.vncPassword,
|
|
864
|
+
viewOnly: resource.controlOwner !== "human",
|
|
865
|
+
generation: resource.generation,
|
|
866
|
+
controlEpoch: resource.controlEpoch
|
|
867
|
+
};
|
|
868
|
+
}
|
|
869
|
+
attachViewer(id, client) {
|
|
870
|
+
const config = this.viewerConfig(id);
|
|
871
|
+
const upstream = new WebSocket(`ws://127.0.0.1:${config.port}/websockify`);
|
|
872
|
+
const sockets = this.viewerSockets.get(id) || /* @__PURE__ */ new Set();
|
|
873
|
+
sockets.add(client);
|
|
874
|
+
this.viewerSockets.set(id, sockets);
|
|
875
|
+
const close = () => {
|
|
876
|
+
sockets.delete(client);
|
|
877
|
+
if (sockets.size === 0) this.viewerSockets.delete(id);
|
|
878
|
+
if (upstream.readyState === WebSocket.OPEN || upstream.readyState === WebSocket.CONNECTING) {
|
|
879
|
+
upstream.close();
|
|
880
|
+
}
|
|
881
|
+
};
|
|
882
|
+
upstream.on("message", (data, isBinary) => {
|
|
883
|
+
if (client.readyState === WebSocket.OPEN) client.send(data, { binary: isBinary });
|
|
884
|
+
});
|
|
885
|
+
client.on("message", (data, isBinary) => {
|
|
886
|
+
const latest = this.privateResource(id);
|
|
887
|
+
if (latest.generation !== config.generation || latest.controlEpoch !== config.controlEpoch) {
|
|
888
|
+
client.close(4003, "Computer Viewer is stale");
|
|
889
|
+
return;
|
|
890
|
+
}
|
|
891
|
+
if (upstream.readyState === WebSocket.OPEN) upstream.send(data, { binary: isBinary });
|
|
892
|
+
});
|
|
893
|
+
upstream.on("close", () => client.close(4e3, "Computer Viewer closed"));
|
|
894
|
+
upstream.on("error", () => client.close(4005, "Computer Viewer failed"));
|
|
895
|
+
client.on("close", close);
|
|
896
|
+
client.on("error", close);
|
|
897
|
+
}
|
|
898
|
+
closeViewers(id, code, reason) {
|
|
899
|
+
const sockets = this.viewerSockets.get(id);
|
|
900
|
+
if (!sockets) return;
|
|
901
|
+
this.viewerSockets.delete(id);
|
|
902
|
+
for (const socket of sockets) socket.close(code, reason);
|
|
903
|
+
}
|
|
904
|
+
containerName(resource) {
|
|
905
|
+
return safeNamePart(`farming-computer-${this.configFingerprint}-${resource.id.slice(-12)}`);
|
|
906
|
+
}
|
|
907
|
+
containerLabels(resource) {
|
|
908
|
+
return [
|
|
909
|
+
["farming.dev/kind", "computer"],
|
|
910
|
+
["farming.dev/config", this.configFingerprint],
|
|
911
|
+
["farming.dev/resource", resource.id],
|
|
912
|
+
["farming.dev/owner-agent", resource.ownerAgentId],
|
|
913
|
+
["farming.dev/image-digest", import_computer_constants.COMPUTER_IMAGE_INDEX_DIGEST]
|
|
914
|
+
];
|
|
915
|
+
}
|
|
916
|
+
browserCacheRoot() {
|
|
917
|
+
const root = storageLayout.managedChromiumRootDir(this.configDir);
|
|
918
|
+
fs.mkdirSync(root, { recursive: true, mode: 448 });
|
|
919
|
+
return root;
|
|
920
|
+
}
|
|
921
|
+
hasBrowserRuntimeWiring(inspect) {
|
|
922
|
+
const mounts = Array.isArray(inspect.Mounts) ? inspect.Mounts : [];
|
|
923
|
+
return mounts.some(
|
|
924
|
+
(mount) => recordValue(mount).Destination === COMPUTER_BROWSER_MOUNT && recordValue(mount).RW === false
|
|
925
|
+
) && Boolean(recordValue(recordValue(inspect.HostConfig).PortBindings)[COMPUTER_BROWSER_CDP_PORT]);
|
|
926
|
+
}
|
|
927
|
+
async ensureBrowserCacheMount(id) {
|
|
928
|
+
const resource = this.privateResource(id);
|
|
929
|
+
if (!resource.containerId) return;
|
|
930
|
+
const inspect = await this.inspectOwnedContainer(resource);
|
|
931
|
+
if (this.hasBrowserRuntimeWiring(inspect)) return;
|
|
932
|
+
await this.resetContainer(id);
|
|
933
|
+
}
|
|
934
|
+
async resetContainer(id) {
|
|
935
|
+
await this.stop(id, true);
|
|
936
|
+
this.holdStopAdmission(id);
|
|
937
|
+
try {
|
|
938
|
+
await this.enqueue(id, async () => {
|
|
939
|
+
const resource = this.privateResource(id);
|
|
940
|
+
if (resource.containerId) {
|
|
941
|
+
await this.inspectOwnedContainer(resource);
|
|
942
|
+
await this.docker(["rm", resource.containerId], { timeoutMs: 3e4 });
|
|
943
|
+
}
|
|
944
|
+
this.patch(resource.id, {
|
|
945
|
+
containerId: "",
|
|
946
|
+
containerName: "",
|
|
947
|
+
vncPassword: "",
|
|
948
|
+
viewerPort: 0,
|
|
949
|
+
sessionId: "",
|
|
950
|
+
status: "stopped",
|
|
951
|
+
error: ""
|
|
952
|
+
});
|
|
953
|
+
});
|
|
954
|
+
} finally {
|
|
955
|
+
this.releaseStopAdmission(id);
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
browserExecutableInContainer(executablePath) {
|
|
959
|
+
const root = this.browserCacheRoot();
|
|
960
|
+
const resolved = path.resolve(String(executablePath || "").trim());
|
|
961
|
+
const relative = path.relative(root, resolved);
|
|
962
|
+
if (!relative || relative.startsWith("..") || path.isAbsolute(relative)) {
|
|
963
|
+
throw computerError(
|
|
964
|
+
"Computer Chromium must come from Farming managed runtime storage",
|
|
965
|
+
400,
|
|
966
|
+
"COMPUTER_BROWSER_EXECUTABLE_INVALID"
|
|
967
|
+
);
|
|
968
|
+
}
|
|
969
|
+
return `${COMPUTER_BROWSER_MOUNT}/${relative.split(path.sep).join("/")}`;
|
|
970
|
+
}
|
|
971
|
+
ensureBrowserCacheTraversal(executablePath) {
|
|
972
|
+
const root = path.resolve(this.browserCacheRoot());
|
|
973
|
+
let current = path.dirname(path.resolve(String(executablePath || "").trim()));
|
|
974
|
+
while (current === root || pathInside(root, current)) {
|
|
975
|
+
const mode = fs.statSync(current).mode & 511;
|
|
976
|
+
if ((mode & 9) !== 9) fs.chmodSync(current, mode | 9);
|
|
977
|
+
if (current === root) return;
|
|
978
|
+
current = path.dirname(current);
|
|
979
|
+
}
|
|
980
|
+
throw computerError(
|
|
981
|
+
"Computer Chromium must come from Farming managed runtime storage",
|
|
982
|
+
400,
|
|
983
|
+
"COMPUTER_BROWSER_EXECUTABLE_INVALID"
|
|
984
|
+
);
|
|
985
|
+
}
|
|
986
|
+
async waitForContainer(containerId, command, timeoutMs = START_TIMEOUT_MS) {
|
|
987
|
+
const deadline = Date.now() + timeoutMs;
|
|
988
|
+
while (Date.now() < deadline) {
|
|
989
|
+
try {
|
|
990
|
+
await this.docker(["exec", containerId, ...command], { timeoutMs: 3e3 });
|
|
991
|
+
return true;
|
|
992
|
+
} catch {
|
|
993
|
+
await new Promise((resolve) => setTimeout(resolve, 250));
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
return false;
|
|
997
|
+
}
|
|
998
|
+
async inspectOwnedContainer(resource) {
|
|
999
|
+
if (!resource.containerId) {
|
|
1000
|
+
throw computerError("Computer container identity is missing", 409, "COMPUTER_CONTAINER_IDENTITY_MISSING");
|
|
1001
|
+
}
|
|
1002
|
+
const result = await this.docker(["inspect", resource.containerId], { timeoutMs: 1e4 });
|
|
1003
|
+
const parsed = JSON.parse(result.stdout);
|
|
1004
|
+
const inspect = Array.isArray(parsed) ? recordValue(parsed[0]) : {};
|
|
1005
|
+
if (!inspect.Id || String(inspect.Id) !== resource.containerId) {
|
|
1006
|
+
throw computerError("Docker returned a different Computer container identity", 409, "COMPUTER_CONTAINER_MISMATCH");
|
|
1007
|
+
}
|
|
1008
|
+
const labels = recordValue(recordValue(inspect.Config).Labels);
|
|
1009
|
+
for (const [key, value] of this.containerLabels(resource)) {
|
|
1010
|
+
if (labels[key] !== value) {
|
|
1011
|
+
throw computerError(`Computer container ownership label mismatch: ${key}`, 409, "COMPUTER_CONTAINER_OWNER_MISMATCH");
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
return inspect;
|
|
1015
|
+
}
|
|
1016
|
+
sessionId(resource) {
|
|
1017
|
+
return `${resource.id}-g${resource.generation}`;
|
|
1018
|
+
}
|
|
1019
|
+
async ensureDriver(resource, sessionId) {
|
|
1020
|
+
await this.docker([
|
|
1021
|
+
"exec",
|
|
1022
|
+
"-u",
|
|
1023
|
+
import_computer_constants.COMPUTER_USER,
|
|
1024
|
+
"-e",
|
|
1025
|
+
"HOME=/home/cua",
|
|
1026
|
+
resource.containerId,
|
|
1027
|
+
import_computer_constants.COMPUTER_DRIVER_BIN,
|
|
1028
|
+
"telemetry",
|
|
1029
|
+
"disable"
|
|
1030
|
+
], { timeoutMs: 1e4 }).catch(() => null);
|
|
1031
|
+
await this.docker([
|
|
1032
|
+
"exec",
|
|
1033
|
+
"-d",
|
|
1034
|
+
"-u",
|
|
1035
|
+
import_computer_constants.COMPUTER_USER,
|
|
1036
|
+
"-e",
|
|
1037
|
+
"HOME=/home/cua",
|
|
1038
|
+
"-e",
|
|
1039
|
+
"DISPLAY=:1",
|
|
1040
|
+
resource.containerId,
|
|
1041
|
+
import_computer_constants.COMPUTER_DRIVER_BIN,
|
|
1042
|
+
"serve",
|
|
1043
|
+
"--dangerously-bypass-approvals"
|
|
1044
|
+
], { timeoutMs: 1e4 }).catch(() => null);
|
|
1045
|
+
const deadline = Date.now() + START_TIMEOUT_MS;
|
|
1046
|
+
let lastError = null;
|
|
1047
|
+
while (Date.now() < deadline) {
|
|
1048
|
+
try {
|
|
1049
|
+
const version = await this.docker([
|
|
1050
|
+
"exec",
|
|
1051
|
+
"-u",
|
|
1052
|
+
import_computer_constants.COMPUTER_USER,
|
|
1053
|
+
"-e",
|
|
1054
|
+
"HOME=/home/cua",
|
|
1055
|
+
"-e",
|
|
1056
|
+
"DISPLAY=:1",
|
|
1057
|
+
resource.containerId,
|
|
1058
|
+
import_computer_constants.COMPUTER_DRIVER_BIN,
|
|
1059
|
+
"--version"
|
|
1060
|
+
], { timeoutMs: 5e3 });
|
|
1061
|
+
if (!version.stdout.includes(import_computer_constants.COMPUTER_DRIVER_VERSION)) {
|
|
1062
|
+
throw new Error(`Expected Cua Driver ${import_computer_constants.COMPUTER_DRIVER_VERSION}, got ${version.stdout.trim()}`);
|
|
1063
|
+
}
|
|
1064
|
+
await this.driverCall(resource, "start_session", {
|
|
1065
|
+
session: sessionId,
|
|
1066
|
+
capture_scope: "desktop"
|
|
1067
|
+
});
|
|
1068
|
+
return;
|
|
1069
|
+
} catch (caught) {
|
|
1070
|
+
lastError = caught;
|
|
1071
|
+
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
throw lastError || new Error("Cua Driver did not become ready");
|
|
1075
|
+
}
|
|
1076
|
+
async driverCall(resource, tool, input, screenshotPath = "") {
|
|
1077
|
+
const args = [
|
|
1078
|
+
"exec",
|
|
1079
|
+
"-u",
|
|
1080
|
+
import_computer_constants.COMPUTER_USER,
|
|
1081
|
+
"-e",
|
|
1082
|
+
"HOME=/home/cua",
|
|
1083
|
+
"-e",
|
|
1084
|
+
"DISPLAY=:1",
|
|
1085
|
+
resource.containerId,
|
|
1086
|
+
import_computer_constants.COMPUTER_DRIVER_BIN,
|
|
1087
|
+
"call",
|
|
1088
|
+
tool,
|
|
1089
|
+
JSON.stringify(input),
|
|
1090
|
+
...screenshotPath ? ["--screenshot-out-file", screenshotPath] : []
|
|
1091
|
+
];
|
|
1092
|
+
const result = await this.docker(args, {
|
|
1093
|
+
timeoutMs: DRIVER_CALL_TIMEOUT_MS,
|
|
1094
|
+
maxBuffer: 32 * 1024 * 1024
|
|
1095
|
+
});
|
|
1096
|
+
let structuredContent;
|
|
1097
|
+
const text = result.stdout.trim();
|
|
1098
|
+
try {
|
|
1099
|
+
structuredContent = JSON.parse(text);
|
|
1100
|
+
} catch {
|
|
1101
|
+
structuredContent = void 0;
|
|
1102
|
+
}
|
|
1103
|
+
const content = [{
|
|
1104
|
+
type: "text",
|
|
1105
|
+
text: text || result.stderr.trim() || `${tool} completed`
|
|
1106
|
+
}];
|
|
1107
|
+
if (screenshotPath) {
|
|
1108
|
+
try {
|
|
1109
|
+
await this.docker([
|
|
1110
|
+
"exec",
|
|
1111
|
+
"-u",
|
|
1112
|
+
import_computer_constants.COMPUTER_USER,
|
|
1113
|
+
resource.containerId,
|
|
1114
|
+
"test",
|
|
1115
|
+
"-s",
|
|
1116
|
+
screenshotPath
|
|
1117
|
+
], { timeoutMs: 5e3 });
|
|
1118
|
+
const image = await this.docker([
|
|
1119
|
+
"exec",
|
|
1120
|
+
"-u",
|
|
1121
|
+
import_computer_constants.COMPUTER_USER,
|
|
1122
|
+
resource.containerId,
|
|
1123
|
+
"base64",
|
|
1124
|
+
"-w",
|
|
1125
|
+
"0",
|
|
1126
|
+
screenshotPath
|
|
1127
|
+
], { timeoutMs: 1e4, maxBuffer: 24 * 1024 * 1024 });
|
|
1128
|
+
if (image.stdout.trim()) content.push({
|
|
1129
|
+
type: "image",
|
|
1130
|
+
data: image.stdout.trim(),
|
|
1131
|
+
mimeType: "image/png"
|
|
1132
|
+
});
|
|
1133
|
+
} catch {
|
|
1134
|
+
} finally {
|
|
1135
|
+
await this.docker([
|
|
1136
|
+
"exec",
|
|
1137
|
+
"-u",
|
|
1138
|
+
import_computer_constants.COMPUTER_USER,
|
|
1139
|
+
resource.containerId,
|
|
1140
|
+
"rm",
|
|
1141
|
+
"-f",
|
|
1142
|
+
screenshotPath
|
|
1143
|
+
], { timeoutMs: 5e3 }).catch(() => null);
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
return {
|
|
1147
|
+
content,
|
|
1148
|
+
...structuredContent === void 0 ? {} : { structuredContent }
|
|
1149
|
+
};
|
|
1150
|
+
}
|
|
1151
|
+
toolAcceptsSession(tool) {
|
|
1152
|
+
return ![
|
|
1153
|
+
"check_for_update",
|
|
1154
|
+
"check_permissions",
|
|
1155
|
+
"get_config",
|
|
1156
|
+
"health_report",
|
|
1157
|
+
"install_ffmpeg",
|
|
1158
|
+
"list_apps",
|
|
1159
|
+
"list_windows",
|
|
1160
|
+
"set_config",
|
|
1161
|
+
"start_recording",
|
|
1162
|
+
"stop_recording",
|
|
1163
|
+
"get_recording_state",
|
|
1164
|
+
"replay_trajectory"
|
|
1165
|
+
].includes(tool);
|
|
1166
|
+
}
|
|
1167
|
+
holdStopAdmission(id) {
|
|
1168
|
+
this.stopAdmissions.set(id, (this.stopAdmissions.get(id) || 0) + 1);
|
|
1169
|
+
}
|
|
1170
|
+
releaseStopAdmission(id) {
|
|
1171
|
+
const next = (this.stopAdmissions.get(id) || 1) - 1;
|
|
1172
|
+
if (next <= 0) this.stopAdmissions.delete(id);
|
|
1173
|
+
else this.stopAdmissions.set(id, next);
|
|
1174
|
+
}
|
|
1175
|
+
isStopping(id) {
|
|
1176
|
+
return (this.stopAdmissions.get(id) || 0) > 0;
|
|
1177
|
+
}
|
|
1178
|
+
holdControlAdmission(id) {
|
|
1179
|
+
this.controlAdmissions.set(id, (this.controlAdmissions.get(id) || 0) + 1);
|
|
1180
|
+
}
|
|
1181
|
+
releaseControlAdmission(id) {
|
|
1182
|
+
const next = (this.controlAdmissions.get(id) || 1) - 1;
|
|
1183
|
+
if (next <= 0) this.controlAdmissions.delete(id);
|
|
1184
|
+
else this.controlAdmissions.set(id, next);
|
|
1185
|
+
}
|
|
1186
|
+
isControlChanging(id) {
|
|
1187
|
+
return (this.controlAdmissions.get(id) || 0) > 0;
|
|
1188
|
+
}
|
|
1189
|
+
assertTransitionAdmissionOpen(id) {
|
|
1190
|
+
if (this.isStopping(id)) {
|
|
1191
|
+
throw computerError("Computer is stopping", 409, "COMPUTER_STOPPING");
|
|
1192
|
+
}
|
|
1193
|
+
if (this.isControlChanging(id)) {
|
|
1194
|
+
throw computerError("Computer control is changing", 409, "COMPUTER_CONTROL_CHANGING");
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
enqueue(id, action) {
|
|
1198
|
+
const previous = this.operations.get(id) || Promise.resolve();
|
|
1199
|
+
const next = previous.catch(() => null).then(action);
|
|
1200
|
+
this.operations.set(id, next);
|
|
1201
|
+
return next.finally(() => {
|
|
1202
|
+
if (this.operations.get(id) === next) this.operations.delete(id);
|
|
1203
|
+
});
|
|
1204
|
+
}
|
|
1205
|
+
patch(id, patch, privateResult = false) {
|
|
1206
|
+
const resource = this.store.patch(id, patch);
|
|
1207
|
+
this.emitResource(resource);
|
|
1208
|
+
return privateResult ? resource : (0, import_computer_resource_store.publicResource)(resource, this.store.revision);
|
|
1209
|
+
}
|
|
1210
|
+
emitResource(resource) {
|
|
1211
|
+
this.emit("resource", (0, import_computer_resource_store.publicResource)(resource, this.store.revision));
|
|
1212
|
+
this.emit("resources", this.store.snapshot());
|
|
1213
|
+
}
|
|
1214
|
+
}
|
|
1215
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
1216
|
+
0 && (module.exports = {
|
|
1217
|
+
ComputerResourceManager,
|
|
1218
|
+
exactImageRef
|
|
1219
|
+
});
|