farming-code 2.2.30 → 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/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} +1220 -1112
- 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.js → update-service.cjs} +258 -248
- 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/dist/acp/codex-acp-1.1.4.mjs +18 -1
- 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-D-zetSol.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 +1 -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/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 -634
- 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-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 -986
- package/backend/async-cache.js +0 -94
- package/backend/atomic-json-store.js +0 -37
- package/backend/auth.js +0 -251
- 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-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 -2362
- package/backend/command-runner-child.js +0 -67
- package/backend/config-manager.js +0 -1117
- 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 -403
- 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 -80
- 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 -288
- 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 -3225
- 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 -205
- 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/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-Z1N7T0eM.js +0 -695
- package/dist/assets/FileEditorPane-CNtuGjQ3.js +0 -2
- package/dist/assets/IconGlyphs-BClgag6E.js +0 -1
- package/dist/assets/ProjectFilesSection-K3GHuQ4U.js +0 -12
- package/dist/assets/ReviewPage-BRuxFaH5.js +0 -3
- package/dist/assets/code-dark-kLsQLvpf.css +0 -1
- package/dist/assets/file-icons-BOlcJ0ZF.js +0 -1
- package/dist/assets/main-DUTz-Rd2.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 -1331
- package/extensions/browser/backend/browser-resource-store.js +0 -190
- package/extensions/browser/backend/browser-router.js +0 -141
- package/extensions/browser/backend/executable-discovery.js +0 -238
- package/extensions/browser/backend/farming-browser-client.js +0 -83
- package/extensions/browser/backend/index.js +0 -7
- package/extensions/browser/backend/managed-chromium-installer.js +0 -593
- 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 -38
- package/extensions/browser/frontend/useBrowserResources.ts +0 -166
- 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,3356 @@
|
|
|
1
|
+
// Generated by scripts/build-backend-runtime.ts. Do not edit.
|
|
2
|
+
"use strict";
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
11
|
+
var __copyProps = (to, from, except, desc) => {
|
|
12
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
13
|
+
for (let key of __getOwnPropNames(from))
|
|
14
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
15
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
16
|
+
}
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
20
|
+
var server_exports = {};
|
|
21
|
+
__export(server_exports, {
|
|
22
|
+
agentManager: () => agentManager,
|
|
23
|
+
app: () => app,
|
|
24
|
+
appendIndexHtmlAssetToken: () => import_index_html.appendIndexHtmlAssetToken,
|
|
25
|
+
browserResourceManager: () => browserResourceManager,
|
|
26
|
+
computerResourceManager: () => computerResourceManager,
|
|
27
|
+
handleMessage: () => handleMessage,
|
|
28
|
+
resolveCliBinDir: () => resolveCliBinDir,
|
|
29
|
+
resolveInputTargetAgentId: () => import_input_routing.resolveInputTargetAgentId,
|
|
30
|
+
rewriteIndexHtmlForBasePath: () => import_index_html.rewriteIndexHtmlForBasePath,
|
|
31
|
+
runTerminalRuntimeStartupCleanup: () => runTerminalRuntimeStartupCleanup,
|
|
32
|
+
server: () => server,
|
|
33
|
+
startServer: () => startServer,
|
|
34
|
+
workspaceFileService: () => workspaceFileService,
|
|
35
|
+
wss: () => wss
|
|
36
|
+
});
|
|
37
|
+
module.exports = __toCommonJS(server_exports);
|
|
38
|
+
var import_agent_manager = require("./agent-manager.cjs");
|
|
39
|
+
var import_agent_runtime_binding = require("./agent-runtime-binding.cjs");
|
|
40
|
+
var import_config_manager = require("./config-manager.cjs");
|
|
41
|
+
var import_theme_manager = require("./theme-manager.cjs");
|
|
42
|
+
var import_auth = require("./auth.cjs");
|
|
43
|
+
var import_network = require("./network.cjs");
|
|
44
|
+
var import_executable_discovery = require("./executable-discovery.cjs");
|
|
45
|
+
var import_claude_settings = require("./claude-settings.cjs");
|
|
46
|
+
var import_codex_models = require("./codex-models.cjs");
|
|
47
|
+
var import_provider_adapters = require("./provider-adapters.cjs");
|
|
48
|
+
var import_codex_session_history = require("./codex-session-history.cjs");
|
|
49
|
+
var import_agent_session_history = require("./agent-session-history.cjs");
|
|
50
|
+
var import_main_page_session = require("./main-page-session.cjs");
|
|
51
|
+
var import_workspace_discovery = require("./workspace-discovery.cjs");
|
|
52
|
+
var import_git_worktree_info = require("./git-worktree-info.cjs");
|
|
53
|
+
var import_workspace_directory = require("./workspace-directory.cjs");
|
|
54
|
+
var import_control_api = require("./control-api.cjs");
|
|
55
|
+
var import_workspace_file_service = require("./workspace-file-service.cjs");
|
|
56
|
+
var import_workspace_file_router = require("./workspace-file-router.cjs");
|
|
57
|
+
var import_workspace_root_registry = require("./workspace-root-registry.cjs");
|
|
58
|
+
var import_backend = require("../extensions/browser/backend/index.cjs");
|
|
59
|
+
var import_backend2 = require("../extensions/computer/backend/index.cjs");
|
|
60
|
+
var import_usage_monitor = require("./usage-monitor.cjs");
|
|
61
|
+
var import_codex_context_window = require("./codex-context-window.cjs");
|
|
62
|
+
var import_async_cache = require("./async-cache.cjs");
|
|
63
|
+
var import_main_agent_skills = require("./main-agent-skills.cjs");
|
|
64
|
+
var import_agent_extension_inventory = require("./agent-extension-inventory.cjs");
|
|
65
|
+
var import_agent_session_inventory = require("./agent-session-inventory.cjs");
|
|
66
|
+
var import_slash_command_discovery = require("./slash-command-discovery.cjs");
|
|
67
|
+
var import_storage_layout = require("./storage-layout.cjs");
|
|
68
|
+
var import_update_service = require("./update-service.cjs");
|
|
69
|
+
var import_input_parts = require("./input-parts.cjs");
|
|
70
|
+
var import_terminal_runtime_cleanup = require("./terminal-runtime-cleanup.cjs");
|
|
71
|
+
var import_resource_broadcast_protocol = require("./resource-broadcast-protocol.cjs");
|
|
72
|
+
var import_qr_share_tickets = require("./qr-share-tickets.cjs");
|
|
73
|
+
var import_review_state_store = require("./review-state-store.cjs");
|
|
74
|
+
var import_review_state_router = require("./review-state-router.cjs");
|
|
75
|
+
var import_review_diff_service = require("./review-diff-service.cjs");
|
|
76
|
+
var import_review_diff_router = require("./review-diff-router.cjs");
|
|
77
|
+
var import_review_session_store = require("./review-session-store.cjs");
|
|
78
|
+
var import_review_session_service = require("./review-session-service.cjs");
|
|
79
|
+
var import_review_session_router = require("./review-session-router.cjs");
|
|
80
|
+
var import_index_html = require("./index-html.cjs");
|
|
81
|
+
var import_acp_transcript = require("./acp-transcript.cjs");
|
|
82
|
+
var import_session_stream_protocol = require("./session-stream-protocol.cjs");
|
|
83
|
+
var import_business_health = require("./business-health.cjs");
|
|
84
|
+
var import_input_routing = require("./input-routing.cjs");
|
|
85
|
+
function isRecord(value) {
|
|
86
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
87
|
+
}
|
|
88
|
+
function optionalString(value) {
|
|
89
|
+
return typeof value === "string" ? value : void 0;
|
|
90
|
+
}
|
|
91
|
+
function requiredString(value) {
|
|
92
|
+
return typeof value === "string" ? value : "";
|
|
93
|
+
}
|
|
94
|
+
function optionalNumber(value) {
|
|
95
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
96
|
+
}
|
|
97
|
+
function isAcpConfigValue(value) {
|
|
98
|
+
return value === null || ["string", "number", "boolean"].includes(typeof value) || Array.isArray(value) && value.every((item) => typeof item === "string");
|
|
99
|
+
}
|
|
100
|
+
function isForkMode(value) {
|
|
101
|
+
return value === "conversation" || value === "same-worktree" || value === "new-worktree";
|
|
102
|
+
}
|
|
103
|
+
function caughtError(error) {
|
|
104
|
+
if (error instanceof Error) return error;
|
|
105
|
+
const normalized = new Error(String(error));
|
|
106
|
+
if (error && typeof error === "object") Object.assign(normalized, error);
|
|
107
|
+
return normalized;
|
|
108
|
+
}
|
|
109
|
+
const express = require("express");
|
|
110
|
+
const compression = require("compression");
|
|
111
|
+
const WebSocket = require("ws");
|
|
112
|
+
const http = require("http");
|
|
113
|
+
const path = require("path");
|
|
114
|
+
const fs = require("fs");
|
|
115
|
+
const os = require("os");
|
|
116
|
+
const crypto = require("crypto");
|
|
117
|
+
const { execFile } = require("child_process");
|
|
118
|
+
const { promisify } = require("util");
|
|
119
|
+
const { URLSearchParams, pathToFileURL } = require("url");
|
|
120
|
+
const {
|
|
121
|
+
MIN_PROTOCOL_VERSION,
|
|
122
|
+
PROTOCOL_VERSION,
|
|
123
|
+
protocolCompatible,
|
|
124
|
+
sanitizeAgentUpdatePatch,
|
|
125
|
+
validateClientMessage
|
|
126
|
+
} = require("../shared/browser-protocol");
|
|
127
|
+
const {
|
|
128
|
+
initializeWebSocketLiveness,
|
|
129
|
+
startWebSocketLivenessMonitor
|
|
130
|
+
} = require("../shared/websocket-liveness.js");
|
|
131
|
+
const BASE_PATH = (0, import_index_html.normalizeBasePath)(process.env.FARMING_BASE_PATH || "/");
|
|
132
|
+
const PORT = process.env.PORT || 3e3;
|
|
133
|
+
const tokenAuth = new import_auth.TokenAuth({ basePath: BASE_PATH || "/" });
|
|
134
|
+
const authEnabled = tokenAuth.isEnabled();
|
|
135
|
+
const WS_PATH = (0, import_index_html.routePath)(BASE_PATH, "/ws");
|
|
136
|
+
const SERVER_EPOCH = crypto.randomUUID();
|
|
137
|
+
const DEFAULT_TRANSCRIPT_MAX_TURNS = 240;
|
|
138
|
+
const MAX_TRANSCRIPT_TURNS = 1e3;
|
|
139
|
+
const INTERACTIVE_REFRESH_CACHE_MAX_AGE_MS = 3e3;
|
|
140
|
+
const execFileAsync = promisify(execFile);
|
|
141
|
+
const app = express();
|
|
142
|
+
app.use(compression());
|
|
143
|
+
const server = http.createServer(app);
|
|
144
|
+
const wss = new WebSocket.Server({ server });
|
|
145
|
+
const websocketLivenessTimer = startWebSocketLivenessMonitor(wss, { openState: WebSocket.OPEN });
|
|
146
|
+
server.on("close", () => clearInterval(websocketLivenessTimer));
|
|
147
|
+
const configManager = new import_config_manager.ConfigManager();
|
|
148
|
+
configManager.init();
|
|
149
|
+
const agentSessionInventory = new import_agent_session_inventory.AgentSessionInventory({
|
|
150
|
+
cacheFile: (0, import_storage_layout.agentSessionInventoryCacheFile)(configManager.farmingDir)
|
|
151
|
+
});
|
|
152
|
+
const agentExtensionInventory = new import_agent_extension_inventory.AgentExtensionInventory({
|
|
153
|
+
cacheFile: (0, import_storage_layout.agentExtensionInventoryCacheFile)(configManager.farmingDir)
|
|
154
|
+
});
|
|
155
|
+
function resolveCliBinDir() {
|
|
156
|
+
if (process.env.FARMING_CLI_BIN_DIR) {
|
|
157
|
+
return process.env.FARMING_CLI_BIN_DIR;
|
|
158
|
+
}
|
|
159
|
+
if (process.pkg || process.env.FARMING_PACKAGED_RUNTIME === "1") {
|
|
160
|
+
return path.dirname(process.execPath);
|
|
161
|
+
}
|
|
162
|
+
return path.join(__dirname, "..", "bin");
|
|
163
|
+
}
|
|
164
|
+
const computerResourceManager = new import_backend2.ComputerResourceManager({
|
|
165
|
+
configDir: configManager.farmingDir,
|
|
166
|
+
getSettings: () => configManager.getSettings(),
|
|
167
|
+
isEnabled: () => configManager.getSettings().computerExtensionEnabled === true
|
|
168
|
+
});
|
|
169
|
+
const isolatedBrowserProvider = new import_backend2.IsolatedBrowserProvider({
|
|
170
|
+
configDir: configManager.farmingDir,
|
|
171
|
+
computerResourceManager
|
|
172
|
+
});
|
|
173
|
+
const browserResourceManager = new import_backend.BrowserResourceManager({
|
|
174
|
+
configDir: configManager.farmingDir,
|
|
175
|
+
isEnabled: () => configManager.getSettings().browserExtensionEnabled === true,
|
|
176
|
+
getBrowserSettings: () => configManager.getSettings(),
|
|
177
|
+
saveBrowserSelection: (selection) => configManager.updateSettings({
|
|
178
|
+
browserSource: selection.source,
|
|
179
|
+
browserExecutablePath: selection.executablePath
|
|
180
|
+
}),
|
|
181
|
+
isolatedBrowserProvider
|
|
182
|
+
});
|
|
183
|
+
const agentManager = new import_agent_manager.AgentManager(
|
|
184
|
+
configManager,
|
|
185
|
+
{
|
|
186
|
+
controlUrl: `http://127.0.0.1:${PORT}${BASE_PATH}`,
|
|
187
|
+
tokenFile: tokenAuth.getTokenFile(),
|
|
188
|
+
authDisabled: !authEnabled,
|
|
189
|
+
cliBinDir: resolveCliBinDir(),
|
|
190
|
+
browserMcpEnabled: () => configManager.getSettings().browserExtensionEnabled === true,
|
|
191
|
+
browserPermissionDecision: (agentId, tool, input) => browserResourceManager.permissionDecision(agentId, tool, input),
|
|
192
|
+
computerMcpEnabled: () => configManager.getSettings().computerExtensionEnabled === true
|
|
193
|
+
}
|
|
194
|
+
);
|
|
195
|
+
async function requireAgentRecoveryForHttp(res) {
|
|
196
|
+
try {
|
|
197
|
+
await agentManager.whenRecovered();
|
|
198
|
+
return true;
|
|
199
|
+
} catch (caught) {
|
|
200
|
+
const error = caughtError(caught);
|
|
201
|
+
res.status(503).json({
|
|
202
|
+
error: error?.message || "Agent lifecycle recovery is unavailable",
|
|
203
|
+
retryable: true
|
|
204
|
+
});
|
|
205
|
+
return false;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
const themeManager = new import_theme_manager.ThemeManager({ configDir: configManager.farmingDir });
|
|
209
|
+
const workspaceFileService = new import_workspace_file_service.WorkspaceFileService();
|
|
210
|
+
const workspaceRootRegistry = new import_workspace_root_registry.WorkspaceRootRegistry(
|
|
211
|
+
agentManager
|
|
212
|
+
);
|
|
213
|
+
let agentResourceReconcileRequested = false;
|
|
214
|
+
let agentResourceReconcileRunning = false;
|
|
215
|
+
const reconcileAgentResourceLifecycle = () => {
|
|
216
|
+
agentResourceReconcileRequested = true;
|
|
217
|
+
if (agentResourceReconcileRunning) return;
|
|
218
|
+
agentResourceReconcileRunning = true;
|
|
219
|
+
void (async () => {
|
|
220
|
+
try {
|
|
221
|
+
await agentManager.whenRecovered();
|
|
222
|
+
while (agentResourceReconcileRequested) {
|
|
223
|
+
agentResourceReconcileRequested = false;
|
|
224
|
+
const agents = agentManager.getState().agents;
|
|
225
|
+
await browserResourceManager.reconcileAgentLifecycle(Array.isArray(agents) ? agents : []);
|
|
226
|
+
await computerResourceManager.reconcileAgentLifecycle(Array.isArray(agents) ? agents : []);
|
|
227
|
+
}
|
|
228
|
+
} catch (error) {
|
|
229
|
+
console.warn("Failed to reconcile Agent-owned resources:", caughtError(error).message || error);
|
|
230
|
+
} finally {
|
|
231
|
+
agentResourceReconcileRunning = false;
|
|
232
|
+
if (agentResourceReconcileRequested) reconcileAgentResourceLifecycle();
|
|
233
|
+
}
|
|
234
|
+
})();
|
|
235
|
+
};
|
|
236
|
+
const computerRuntimeRecoveryPromise = computerResourceManager.init().catch((error) => {
|
|
237
|
+
console.warn("Failed to recover Computer runtimes:", caughtError(error).message || error);
|
|
238
|
+
return null;
|
|
239
|
+
});
|
|
240
|
+
const browserRuntimeRecoveryPromise = computerRuntimeRecoveryPromise.then(() => isolatedBrowserProvider.recover()).then(() => browserResourceManager.init()).then(() => {
|
|
241
|
+
agentManager.on("update", reconcileAgentResourceLifecycle);
|
|
242
|
+
reconcileAgentResourceLifecycle();
|
|
243
|
+
}).catch((error) => {
|
|
244
|
+
console.warn("Failed to recover Browser runtimes:", caughtError(error).message || error);
|
|
245
|
+
return null;
|
|
246
|
+
});
|
|
247
|
+
const updateService = new import_update_service.FarmingUpdateService({
|
|
248
|
+
rootDir: path.join(__dirname, ".."),
|
|
249
|
+
configDir: configManager.farmingDir,
|
|
250
|
+
platform: process.platform,
|
|
251
|
+
arch: process.arch,
|
|
252
|
+
packagedRuntime: Boolean(process.pkg || process.env.FARMING_PACKAGED_RUNTIME === "1")
|
|
253
|
+
});
|
|
254
|
+
const usageMonitor = new import_usage_monitor.UsageMonitor({
|
|
255
|
+
agentManager,
|
|
256
|
+
configDir: configManager.farmingDir,
|
|
257
|
+
getProviderHomes: configuredProviderHomes
|
|
258
|
+
});
|
|
259
|
+
const codexContextWindowReader = new import_codex_context_window.CodexContextWindowReader();
|
|
260
|
+
const usageSummaryCache = new import_async_cache.AsyncCache(() => usageMonitor.getUsageSummary(), {
|
|
261
|
+
ttlMs: 15e3,
|
|
262
|
+
staleMs: 2 * 6e4
|
|
263
|
+
});
|
|
264
|
+
const codexModelOptionsCache = new import_async_cache.AsyncCache((homePath) => (0, import_codex_models.listCodexModelOptions)({
|
|
265
|
+
env: (0, import_provider_adapters.applyProviderHomeEnvironment)({ ...process.env }, "codex", homePath)
|
|
266
|
+
}), {
|
|
267
|
+
ttlMs: 5 * 6e4,
|
|
268
|
+
staleMs: 5 * 6e4
|
|
269
|
+
});
|
|
270
|
+
function configuredProviderMetadata() {
|
|
271
|
+
const result = {};
|
|
272
|
+
const settings = configManager.getSettings();
|
|
273
|
+
const records = configManager.listAgentSessionRecords();
|
|
274
|
+
const homeBindings = configManager.agentHomeBindings(records);
|
|
275
|
+
const providers = Object.keys(settings.agentHomes || {});
|
|
276
|
+
for (const provider of providers) {
|
|
277
|
+
result[provider] = configManager.getKnownAgentHomes(provider, homeBindings).map((home) => ({
|
|
278
|
+
id: String(home.id || "default"),
|
|
279
|
+
path: String(home.path || "")
|
|
280
|
+
})).filter((home) => home.id && home.path);
|
|
281
|
+
}
|
|
282
|
+
const providerSessionBindings = records.flatMap((record) => {
|
|
283
|
+
const provider = String(record.provider || "").trim().toLowerCase();
|
|
284
|
+
const providerHomeId = String(record.providerHomeId || "default").trim() || "default";
|
|
285
|
+
const providerHomePath = configManager.expandWorkspacePath(String(record.providerHomePath || ""));
|
|
286
|
+
const providerSessionId = String(record.providerSessionId || "").trim();
|
|
287
|
+
return provider && providerHomePath && providerSessionId && record.providerSessionTemporary !== true ? [{ provider, providerHomeId, providerHomePath, providerSessionId }] : [];
|
|
288
|
+
});
|
|
289
|
+
return { providerHomes: result, providerSessionBindings };
|
|
290
|
+
}
|
|
291
|
+
function configuredProviderHomes() {
|
|
292
|
+
return configuredProviderMetadata().providerHomes;
|
|
293
|
+
}
|
|
294
|
+
function requestedProviderHome(provider, rawHomeId) {
|
|
295
|
+
const homeId = typeof rawHomeId === "string" && rawHomeId.trim() ? rawHomeId.trim() : "default";
|
|
296
|
+
if (!/^[A-Za-z0-9._-]+$/.test(homeId)) {
|
|
297
|
+
return { error: "Invalid Agent Home id", home: null, status: 400 };
|
|
298
|
+
}
|
|
299
|
+
const home = configManager.getKnownAgentHome(provider, homeId);
|
|
300
|
+
return home ? { error: "", home, status: 200 } : { error: `Unknown ${provider} Agent Home: ${homeId}`, home: null, status: 404 };
|
|
301
|
+
}
|
|
302
|
+
function currentAgentSessions() {
|
|
303
|
+
return agentSessionInventory.list(
|
|
304
|
+
() => configuredProviderMetadata()
|
|
305
|
+
);
|
|
306
|
+
}
|
|
307
|
+
function withSearchTimeout(promise, timeoutMs) {
|
|
308
|
+
let timer;
|
|
309
|
+
const timeout = new Promise((_, reject) => {
|
|
310
|
+
timer = setTimeout(() => {
|
|
311
|
+
const error = new Error("Agent search timed out");
|
|
312
|
+
error.code = "ETIMEDOUT";
|
|
313
|
+
reject(error);
|
|
314
|
+
}, timeoutMs);
|
|
315
|
+
});
|
|
316
|
+
return Promise.race([promise, timeout]).finally(() => {
|
|
317
|
+
if (timer) clearTimeout(timer);
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
const qrShareTickets = new import_qr_share_tickets.QrShareTicketStore({ ttlMs: import_qr_share_tickets.SHARE_TICKET_TTL_MS });
|
|
321
|
+
const reviewStateStore = new import_review_state_store.ReviewStateStore(configManager.farmingDir, {
|
|
322
|
+
seedReviews: {
|
|
323
|
+
"review-fixture-553987": {
|
|
324
|
+
patchsets: {
|
|
325
|
+
"Patchset 20": { reviewedPaths: ["clis/dataflow.py", "clis/fetch_instance_log.py"], revision: 0 },
|
|
326
|
+
"Patchset 19": { reviewedPaths: ["clis/fetch_instance_log.py"], revision: 0 }
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
});
|
|
331
|
+
const reviewDiffService = new import_review_diff_service.ReviewDiffService(
|
|
332
|
+
agentManager,
|
|
333
|
+
workspaceFileService
|
|
334
|
+
);
|
|
335
|
+
const reviewSessionStore = new import_review_session_store.ReviewSessionStore(configManager.farmingDir);
|
|
336
|
+
const reviewSessionService = new import_review_session_service.ReviewSessionService(workspaceFileService, reviewSessionStore, reviewStateStore, {
|
|
337
|
+
resolveAgentRoot: (agentId) => agentManager.getAgentWorkspaceRoot(agentId),
|
|
338
|
+
resolveAcpReviewChanges: (agentId, itemIds) => {
|
|
339
|
+
if (typeof agentId !== "string" || !Array.isArray(itemIds) || !itemIds.every((item) => typeof item === "string")) {
|
|
340
|
+
throw new Error("ACP review request is invalid");
|
|
341
|
+
}
|
|
342
|
+
return agentManager.getAcpReviewChanges(agentId, itemIds);
|
|
343
|
+
}
|
|
344
|
+
});
|
|
345
|
+
const workspaceDiscoveryCache = new import_async_cache.AsyncCache((key) => {
|
|
346
|
+
const request = JSON.parse(key);
|
|
347
|
+
return (0, import_workspace_discovery.discoverAgentWorkspaces)({
|
|
348
|
+
limit: request.limit,
|
|
349
|
+
agent: request.agent
|
|
350
|
+
});
|
|
351
|
+
}, {
|
|
352
|
+
ttlMs: 3e4,
|
|
353
|
+
staleMs: 2 * 6e4
|
|
354
|
+
});
|
|
355
|
+
const frontendDir = path.join(__dirname, "../frontend");
|
|
356
|
+
const crtFrontendDir = path.join(frontendDir, "skins", "crt");
|
|
357
|
+
const distDir = path.join(__dirname, "../dist");
|
|
358
|
+
const staticAppDir = fs.existsSync(distDir) ? distDir : frontendDir;
|
|
359
|
+
const immutableAssetStaticOptions = {
|
|
360
|
+
immutable: true,
|
|
361
|
+
index: false,
|
|
362
|
+
maxAge: "1y"
|
|
363
|
+
};
|
|
364
|
+
const xtermBrowserEntryPath = path.join(__dirname, "..", "node_modules", "@xterm", "xterm", "lib", "xterm.js");
|
|
365
|
+
const xtermFitEntryPath = path.join(__dirname, "..", "node_modules", "@xterm", "addon-fit", "lib", "addon-fit.js");
|
|
366
|
+
const xtermWebglEntryPath = path.join(__dirname, "..", "node_modules", "@xterm", "addon-webgl", "lib", "addon-webgl.js");
|
|
367
|
+
const xtermCssPath = path.join(__dirname, "..", "node_modules", "@xterm", "xterm", "css", "xterm.css");
|
|
368
|
+
const materialIconDir = path.join(__dirname, "..", "node_modules", "material-icon-theme", "icons");
|
|
369
|
+
function getAvailableAgentsForRequest() {
|
|
370
|
+
if (process.env.FARMING_E2E_FAKE_EXECUTABLES === "1") {
|
|
371
|
+
return withLaunchCapabilities([
|
|
372
|
+
{
|
|
373
|
+
name: "codex",
|
|
374
|
+
command: "codex",
|
|
375
|
+
description: "Codex CLI - OpenAI coding assistant",
|
|
376
|
+
category: "coding",
|
|
377
|
+
supported: true,
|
|
378
|
+
interactive: true
|
|
379
|
+
},
|
|
380
|
+
{
|
|
381
|
+
name: "claude",
|
|
382
|
+
command: "claude",
|
|
383
|
+
description: "Claude CLI - Anthropic assistant",
|
|
384
|
+
category: "coding",
|
|
385
|
+
supported: true,
|
|
386
|
+
interactive: true
|
|
387
|
+
},
|
|
388
|
+
{
|
|
389
|
+
name: "opencode",
|
|
390
|
+
command: "opencode",
|
|
391
|
+
description: "OpenCode - AI coding assistant",
|
|
392
|
+
category: "coding",
|
|
393
|
+
supported: true,
|
|
394
|
+
interactive: true
|
|
395
|
+
},
|
|
396
|
+
{
|
|
397
|
+
name: "qoder",
|
|
398
|
+
command: "qodercli",
|
|
399
|
+
description: "Qoder - AI coding assistant",
|
|
400
|
+
category: "coding",
|
|
401
|
+
supported: true,
|
|
402
|
+
interactive: true
|
|
403
|
+
},
|
|
404
|
+
{
|
|
405
|
+
name: "qwen",
|
|
406
|
+
command: "qwen",
|
|
407
|
+
description: "Qwen Code - AI coding assistant",
|
|
408
|
+
category: "coding",
|
|
409
|
+
supported: true,
|
|
410
|
+
interactive: true
|
|
411
|
+
},
|
|
412
|
+
{
|
|
413
|
+
name: "bash",
|
|
414
|
+
command: "bash",
|
|
415
|
+
description: "Shell session",
|
|
416
|
+
category: "shell",
|
|
417
|
+
supported: true,
|
|
418
|
+
interactive: true
|
|
419
|
+
},
|
|
420
|
+
{
|
|
421
|
+
name: "zsh",
|
|
422
|
+
command: "zsh",
|
|
423
|
+
description: "Z shell",
|
|
424
|
+
category: "shell",
|
|
425
|
+
supported: true,
|
|
426
|
+
interactive: true
|
|
427
|
+
}
|
|
428
|
+
]);
|
|
429
|
+
}
|
|
430
|
+
const shellEnv = agentManager.resolveAgentShellEnv("", { maxAgeMs: INTERACTIVE_REFRESH_CACHE_MAX_AGE_MS });
|
|
431
|
+
const pathEnv = typeof shellEnv?.PATH === "string" && shellEnv.PATH.trim() ? shellEnv.PATH : process.env.PATH || "";
|
|
432
|
+
return withLaunchCapabilities((0, import_executable_discovery.listAvailableAgents)(pathEnv));
|
|
433
|
+
}
|
|
434
|
+
function withLaunchCapabilities(agents) {
|
|
435
|
+
return agents.map((agent) => ({
|
|
436
|
+
...agent,
|
|
437
|
+
capabilities: {
|
|
438
|
+
supportsChat: (0, import_provider_adapters.providerCapabilities)(agent.name).supportsChat === true
|
|
439
|
+
}
|
|
440
|
+
}));
|
|
441
|
+
}
|
|
442
|
+
function normalizeWorkspaceCompletionInput(value) {
|
|
443
|
+
const raw = typeof value === "string" ? value.trim() : "";
|
|
444
|
+
if (!raw) {
|
|
445
|
+
return { raw: "", parent: os.homedir(), prefix: "", displayParent: "~", explicitDirectory: false };
|
|
446
|
+
}
|
|
447
|
+
const home = os.homedir();
|
|
448
|
+
const expanded = raw === "~" ? home : raw.startsWith("~/") ? path.join(home, raw.slice(2)) : raw;
|
|
449
|
+
const explicitDirectory = raw.endsWith(path.sep) || raw === "~";
|
|
450
|
+
const parent = explicitDirectory ? expanded : path.dirname(expanded);
|
|
451
|
+
const prefix = explicitDirectory ? "" : path.basename(expanded);
|
|
452
|
+
const trimTrailingSeparator = (input) => {
|
|
453
|
+
let next = input;
|
|
454
|
+
while (next.length > 1 && next.endsWith(path.sep)) next = next.slice(0, -1);
|
|
455
|
+
return next;
|
|
456
|
+
};
|
|
457
|
+
const displayParent = explicitDirectory ? trimTrailingSeparator(raw) : trimTrailingSeparator(raw.slice(0, raw.length - prefix.length));
|
|
458
|
+
return {
|
|
459
|
+
raw,
|
|
460
|
+
parent: parent || path.sep,
|
|
461
|
+
prefix,
|
|
462
|
+
displayParent: displayParent || (path.isAbsolute(raw) ? path.sep : ""),
|
|
463
|
+
explicitDirectory
|
|
464
|
+
};
|
|
465
|
+
}
|
|
466
|
+
async function listWorkspacePathCompletions(partialPath, limit = 12) {
|
|
467
|
+
const query = normalizeWorkspaceCompletionInput(partialPath);
|
|
468
|
+
const entries = await fs.promises.readdir(query.parent, { withFileTypes: true });
|
|
469
|
+
const normalizedPrefix = query.prefix.toLowerCase();
|
|
470
|
+
const maxResults = Math.max(1, Math.min(Number(limit) || 12, 100));
|
|
471
|
+
return entries.filter((entry) => entry.isDirectory()).filter((entry) => normalizedPrefix.startsWith(".") || !entry.name.startsWith(".")).filter((entry) => !normalizedPrefix || entry.name.toLowerCase().startsWith(normalizedPrefix)).sort((a, b) => a.name.localeCompare(b.name)).slice(0, maxResults).map((entry) => {
|
|
472
|
+
const fullPath = path.join(query.parent, entry.name);
|
|
473
|
+
const displayPath = query.raw.startsWith("~") ? path.join(query.displayParent || "~", entry.name) : fullPath;
|
|
474
|
+
return {
|
|
475
|
+
name: entry.name,
|
|
476
|
+
path: `${displayPath}${path.sep}`
|
|
477
|
+
};
|
|
478
|
+
});
|
|
479
|
+
}
|
|
480
|
+
const publicProductAssetsDir = path.join(staticAppDir, "farming-2");
|
|
481
|
+
app.use((0, import_index_html.routePath)(BASE_PATH, "/farming-2"), express.static(publicProductAssetsDir, { index: false }));
|
|
482
|
+
const sendPublicProductAsset = (filename) => (_req, res) => {
|
|
483
|
+
res.sendFile(path.join(publicProductAssetsDir, filename));
|
|
484
|
+
};
|
|
485
|
+
app.get(["/favicon.ico", (0, import_index_html.routePath)(BASE_PATH, "/favicon.ico")], sendPublicProductAsset("favicon-v2.ico"));
|
|
486
|
+
app.get([
|
|
487
|
+
"/apple-touch-icon.png",
|
|
488
|
+
"/apple-touch-icon-precomposed.png",
|
|
489
|
+
"/apple-touch-icon-180x180.png",
|
|
490
|
+
(0, import_index_html.routePath)(BASE_PATH, "/apple-touch-icon.png"),
|
|
491
|
+
(0, import_index_html.routePath)(BASE_PATH, "/apple-touch-icon-precomposed.png"),
|
|
492
|
+
(0, import_index_html.routePath)(BASE_PATH, "/apple-touch-icon-180x180.png")
|
|
493
|
+
], sendPublicProductAsset("app-icon-v2-180.png"));
|
|
494
|
+
app.get((0, import_index_html.routePath)(BASE_PATH, "/j/:code"), (req, res) => {
|
|
495
|
+
if (req.method === "HEAD") {
|
|
496
|
+
res.status(204).end();
|
|
497
|
+
return;
|
|
498
|
+
}
|
|
499
|
+
const ticket = qrShareTickets.consume(req.params.code);
|
|
500
|
+
if (!ticket || authEnabled && !tokenAuth.verify(ticket.token)) {
|
|
501
|
+
res.status(410).send("Farming share link expired.");
|
|
502
|
+
return;
|
|
503
|
+
}
|
|
504
|
+
if (authEnabled) {
|
|
505
|
+
res.setHeader(
|
|
506
|
+
"Set-Cookie",
|
|
507
|
+
`farming_token=${(0, import_auth.encodeCookieToken)(ticket.token)}; Path=/; HttpOnly; SameSite=Lax`
|
|
508
|
+
);
|
|
509
|
+
}
|
|
510
|
+
res.redirect(302, entryPathWithQuery(ticket.targetQuery));
|
|
511
|
+
});
|
|
512
|
+
app.use(tokenAuth.middleware());
|
|
513
|
+
app.get((0, import_index_html.routePath)(BASE_PATH, "/api/auth/status"), (req, res) => {
|
|
514
|
+
res.json({ authRequired: authEnabled });
|
|
515
|
+
});
|
|
516
|
+
function absoluteClientUrl(req, urlPath) {
|
|
517
|
+
const forwardedProto = String(req.headers["x-forwarded-proto"] || "").split(",")[0].trim();
|
|
518
|
+
const protocol = forwardedProto || req.protocol || "http";
|
|
519
|
+
const forwardedHost = String(req.headers["x-forwarded-host"] || "").split(",")[0].trim();
|
|
520
|
+
const host = forwardedHost || req.headers.host || `127.0.0.1:${PORT}`;
|
|
521
|
+
return `${protocol}://${host}${urlPath}`;
|
|
522
|
+
}
|
|
523
|
+
function shareTargetPositiveInteger(value) {
|
|
524
|
+
const number = Number(value);
|
|
525
|
+
return Number.isInteger(number) && number > 0 ? String(number) : "";
|
|
526
|
+
}
|
|
527
|
+
function shareTargetString(value, maxLength) {
|
|
528
|
+
const string = String(value || "").trim();
|
|
529
|
+
if (!string || string.length > maxLength || string.includes("\0")) return "";
|
|
530
|
+
return string;
|
|
531
|
+
}
|
|
532
|
+
function shareTargetQueryFromBody(body) {
|
|
533
|
+
const target = body && typeof body === "object" ? body.target : null;
|
|
534
|
+
if (!target || typeof target !== "object") return "";
|
|
535
|
+
const targetRecord = target;
|
|
536
|
+
const kind = targetRecord.kind === "file" ? "file" : targetRecord.kind === "folder" ? "folder" : targetRecord.kind === "agent" ? "agent" : "";
|
|
537
|
+
const agentId = shareTargetString(targetRecord.agentId, 160);
|
|
538
|
+
const absolutePath = shareTargetString(targetRecord.absolutePath, 2048);
|
|
539
|
+
const projectLabel = shareTargetString(targetRecord.projectLabel, 160);
|
|
540
|
+
const readingAnchor = shareTargetString(targetRecord.readingAnchor, 1800);
|
|
541
|
+
if (!kind || kind === "agent" && !agentId || kind !== "agent" && !absolutePath && !agentId && !projectLabel) return "";
|
|
542
|
+
const params = new URLSearchParams();
|
|
543
|
+
params.set("ftarget", kind);
|
|
544
|
+
if (agentId) params.set("agent", agentId);
|
|
545
|
+
if (kind === "agent" && readingAnchor && /^[A-Za-z0-9_-]+$/.test(readingAnchor)) params.set("fra", readingAnchor);
|
|
546
|
+
if (absolutePath) params.set("path", absolutePath);
|
|
547
|
+
if (projectLabel) params.set("project", projectLabel);
|
|
548
|
+
if (kind === "folder") {
|
|
549
|
+
const folderPath = shareTargetString(targetRecord.folderPath, 2048);
|
|
550
|
+
if (!absolutePath && !folderPath) return "";
|
|
551
|
+
if (folderPath) params.set("folder", folderPath);
|
|
552
|
+
} else if (kind === "file") {
|
|
553
|
+
const filePath = shareTargetString(targetRecord.filePath, 2048);
|
|
554
|
+
if (!absolutePath && !filePath) return "";
|
|
555
|
+
if (filePath) params.set("file", filePath);
|
|
556
|
+
if (targetRecord.view === "diff") params.set("view", "diff");
|
|
557
|
+
const line = shareTargetPositiveInteger(targetRecord.lineNumber);
|
|
558
|
+
const column = shareTargetPositiveInteger(targetRecord.column);
|
|
559
|
+
const endColumn = shareTargetPositiveInteger(targetRecord.endColumn);
|
|
560
|
+
if (line) params.set("line", line);
|
|
561
|
+
if (column) params.set("column", column);
|
|
562
|
+
if (endColumn) params.set("endColumn", endColumn);
|
|
563
|
+
}
|
|
564
|
+
if (absolutePath) {
|
|
565
|
+
const absoluteParams = new URLSearchParams(params);
|
|
566
|
+
absoluteParams.delete("agent");
|
|
567
|
+
absoluteParams.delete(kind === "folder" ? "folder" : "file");
|
|
568
|
+
if (absoluteParams.toString().length <= 1800) return absoluteParams.toString();
|
|
569
|
+
params.delete("path");
|
|
570
|
+
}
|
|
571
|
+
return params.toString();
|
|
572
|
+
}
|
|
573
|
+
function entryPathWithQuery(query = "", options = {}) {
|
|
574
|
+
const entryPath = BASE_PATH || "/";
|
|
575
|
+
const params = new URLSearchParams(query || "");
|
|
576
|
+
if (options.includeToken && authEnabled) {
|
|
577
|
+
params.set("token", tokenAuth.getToken());
|
|
578
|
+
}
|
|
579
|
+
const queryString = params.toString();
|
|
580
|
+
return queryString ? `${entryPath}?${queryString}` : entryPath;
|
|
581
|
+
}
|
|
582
|
+
function entryPathWithToken(targetQuery = "") {
|
|
583
|
+
return entryPathWithQuery(targetQuery, { includeToken: true });
|
|
584
|
+
}
|
|
585
|
+
app.post((0, import_index_html.routePath)(BASE_PATH, "/api/share/qr-ticket"), express.json({ limit: "8kb" }), (req, res) => {
|
|
586
|
+
try {
|
|
587
|
+
const targetQuery = shareTargetQueryFromBody(req.body);
|
|
588
|
+
const ticket = qrShareTickets.create(authEnabled ? tokenAuth.getToken() : "", { targetQuery });
|
|
589
|
+
const shortPath = (0, import_index_html.routePath)(BASE_PATH, `/j/${ticket.code}`);
|
|
590
|
+
const longPath = entryPathWithToken(ticket.targetQuery);
|
|
591
|
+
res.json({
|
|
592
|
+
code: ticket.code,
|
|
593
|
+
expiresAt: ticket.expiresAt,
|
|
594
|
+
ttlMs: import_qr_share_tickets.SHARE_TICKET_TTL_MS,
|
|
595
|
+
shortPath,
|
|
596
|
+
shortUrl: absoluteClientUrl(req, shortPath),
|
|
597
|
+
longUrl: absoluteClientUrl(req, longPath),
|
|
598
|
+
tokenLabel: authEnabled ? tokenAuth.getToken() : ""
|
|
599
|
+
});
|
|
600
|
+
} catch (caught) {
|
|
601
|
+
const error = caughtError(caught);
|
|
602
|
+
res.status(500).json({ error: error.message || "Failed to create share ticket" });
|
|
603
|
+
}
|
|
604
|
+
});
|
|
605
|
+
app.delete((0, import_index_html.routePath)(BASE_PATH, "/api/share/qr-ticket/:code"), (req, res) => {
|
|
606
|
+
res.json({ revoked: qrShareTickets.revoke(req.params.code) });
|
|
607
|
+
});
|
|
608
|
+
app.use((0, import_index_html.routePath)(BASE_PATH, "/vendor"), express.static(path.join(frontendDir, "vendor")));
|
|
609
|
+
app.get((0, import_index_html.routePath)(BASE_PATH, "/vendor/xterm/xterm.js"), (_req, res) => {
|
|
610
|
+
res.sendFile(xtermBrowserEntryPath);
|
|
611
|
+
});
|
|
612
|
+
app.get((0, import_index_html.routePath)(BASE_PATH, "/vendor/xterm/addon-fit.js"), (_req, res) => {
|
|
613
|
+
res.sendFile(xtermFitEntryPath);
|
|
614
|
+
});
|
|
615
|
+
app.get((0, import_index_html.routePath)(BASE_PATH, "/vendor/xterm/addon-webgl.js"), (_req, res) => {
|
|
616
|
+
res.sendFile(xtermWebglEntryPath);
|
|
617
|
+
});
|
|
618
|
+
app.get((0, import_index_html.routePath)(BASE_PATH, "/vendor/xterm/xterm.css"), (_req, res) => {
|
|
619
|
+
res.sendFile(xtermCssPath);
|
|
620
|
+
});
|
|
621
|
+
app.use((0, import_index_html.routePath)(BASE_PATH, "/vendor/material-icons"), express.static(materialIconDir));
|
|
622
|
+
app.get((0, import_index_html.routePath)(BASE_PATH, "/vendor/material-icons/:iconId.svg"), (req, res) => {
|
|
623
|
+
const fallbackIcon = String(req.params.iconId || "").startsWith("folder-") ? "folder.svg" : "file.svg";
|
|
624
|
+
res.sendFile(path.join(materialIconDir, fallbackIcon));
|
|
625
|
+
});
|
|
626
|
+
app.use(
|
|
627
|
+
(0, import_index_html.routePath)(BASE_PATH, "/assets"),
|
|
628
|
+
express.static(path.join(staticAppDir, "assets"), immutableAssetStaticOptions)
|
|
629
|
+
);
|
|
630
|
+
if (BASE_PATH) {
|
|
631
|
+
app.use("/assets", express.static(path.join(staticAppDir, "assets"), immutableAssetStaticOptions));
|
|
632
|
+
app.use("/farming-2", express.static(path.join(staticAppDir, "farming-2"), { index: false }));
|
|
633
|
+
}
|
|
634
|
+
const crtEntryPath = (0, import_index_html.routePath)(BASE_PATH, "/crt");
|
|
635
|
+
app.get(crtEntryPath, (req, res) => {
|
|
636
|
+
if (req.path.endsWith("/")) {
|
|
637
|
+
res.setHeader("Cache-Control", "no-cache");
|
|
638
|
+
res.sendFile(path.join(crtFrontendDir, "index.html"));
|
|
639
|
+
return;
|
|
640
|
+
}
|
|
641
|
+
const requestUrl = new URL(req.url || "/", `http://${req.headers.host || "localhost"}`);
|
|
642
|
+
res.redirect(308, `${crtEntryPath}/${requestUrl.search}`);
|
|
643
|
+
});
|
|
644
|
+
app.use(`${crtEntryPath}/shared`, express.static(frontendDir, { index: false }));
|
|
645
|
+
app.get(`${crtEntryPath}/crt-markdown-renderer.js`, (_req, res) => {
|
|
646
|
+
res.sendFile(path.join(distDir, "crt-markdown-renderer.js"));
|
|
647
|
+
});
|
|
648
|
+
app.get(`${crtEntryPath}/crt-markdown-renderer.css`, (_req, res) => {
|
|
649
|
+
res.sendFile(path.join(distDir, "crt-markdown-renderer.css"));
|
|
650
|
+
});
|
|
651
|
+
app.use(`${crtEntryPath}/crt-markdown-assets`, express.static(path.join(distDir, "crt-markdown-assets"), { index: false }));
|
|
652
|
+
app.get(`${crtEntryPath}/crt-mermaid-renderer.js`, (_req, res) => {
|
|
653
|
+
res.sendFile(path.join(distDir, "crt-mermaid-renderer.js"));
|
|
654
|
+
});
|
|
655
|
+
app.use(`${crtEntryPath}/`, express.static(crtFrontendDir, { index: false }));
|
|
656
|
+
app.use(BASE_PATH || "/", express.static(staticAppDir, { index: false }));
|
|
657
|
+
app.use((0, import_index_html.routePath)(BASE_PATH, "/api/files"), (0, import_workspace_file_router.createWorkspaceFileRouter)(
|
|
658
|
+
agentManager,
|
|
659
|
+
workspaceFileService,
|
|
660
|
+
{
|
|
661
|
+
rootRegistry: workspaceRootRegistry
|
|
662
|
+
}
|
|
663
|
+
));
|
|
664
|
+
app.use((0, import_index_html.routePath)(BASE_PATH, "/api/browsers"), (0, import_backend.createBrowserRouter)(
|
|
665
|
+
browserResourceManager,
|
|
666
|
+
workspaceRootRegistry,
|
|
667
|
+
agentManager
|
|
668
|
+
));
|
|
669
|
+
app.use((0, import_index_html.routePath)(BASE_PATH, "/api/computers"), (0, import_backend2.createComputerRouter)(
|
|
670
|
+
computerResourceManager,
|
|
671
|
+
workspaceRootRegistry,
|
|
672
|
+
agentManager
|
|
673
|
+
));
|
|
674
|
+
app.use((0, import_index_html.routePath)(BASE_PATH, "/api/review-sessions"), (0, import_review_session_router.createReviewSessionRouter)(reviewSessionService));
|
|
675
|
+
app.use((0, import_index_html.routePath)(BASE_PATH, "/api/reviews"), (0, import_review_diff_router.createReviewDiffRouter)(reviewDiffService, reviewSessionService));
|
|
676
|
+
app.use(
|
|
677
|
+
(0, import_index_html.routePath)(BASE_PATH, "/api/reviews"),
|
|
678
|
+
(0, import_review_state_router.createReviewStateRouter)(reviewStateStore)
|
|
679
|
+
);
|
|
680
|
+
if (process.env.NODE_ENV === "test" && process.env.FARMING_E2E_FAKE_EXECUTABLES === "1") {
|
|
681
|
+
app.post((0, import_index_html.routePath)(BASE_PATH, "/api/control/e2e/close-websockets"), express.json(), (_req, res) => {
|
|
682
|
+
const clients = [...wss.clients].filter((client) => client.readyState === WebSocket.OPEN);
|
|
683
|
+
res.json({ closing: clients.length, code: 1013 });
|
|
684
|
+
setImmediate(() => {
|
|
685
|
+
clients.forEach((client) => client.close(1013, "terminal stream backpressure test"));
|
|
686
|
+
});
|
|
687
|
+
});
|
|
688
|
+
}
|
|
689
|
+
app.use((0, import_index_html.routePath)(BASE_PATH, "/api/control"), (0, import_control_api.createControlRouter)(
|
|
690
|
+
agentManager,
|
|
691
|
+
{
|
|
692
|
+
notifyUpdate: broadcastState,
|
|
693
|
+
allowConcurrentTestControl: process.env.NODE_ENV === "test" && process.env.FARMING_E2E_FAKE_EXECUTABLES === "1"
|
|
694
|
+
}
|
|
695
|
+
));
|
|
696
|
+
app.get([
|
|
697
|
+
BASE_PATH || "/",
|
|
698
|
+
`${BASE_PATH || ""}/`,
|
|
699
|
+
(0, import_index_html.routePath)(BASE_PATH, "/code"),
|
|
700
|
+
(0, import_index_html.routePath)(BASE_PATH, "/code/"),
|
|
701
|
+
(0, import_index_html.routePath)(BASE_PATH, "/error-preview"),
|
|
702
|
+
(0, import_index_html.routePath)(BASE_PATH, "/review")
|
|
703
|
+
].filter(Boolean), (req, res) => {
|
|
704
|
+
const indexPath = path.join(staticAppDir, "index.html");
|
|
705
|
+
fs.readFile(indexPath, "utf8", (error, html) => {
|
|
706
|
+
if (error) {
|
|
707
|
+
res.status(500).send("Farming frontend is not built");
|
|
708
|
+
return;
|
|
709
|
+
}
|
|
710
|
+
res.setHeader("Content-Type", "text/html; charset=utf-8");
|
|
711
|
+
res.setHeader("Cache-Control", "no-cache");
|
|
712
|
+
const rewrittenHtml = (0, import_index_html.applyIndexHtmlAppearance)(
|
|
713
|
+
(0, import_index_html.rewriteIndexHtmlForBasePath)(html, BASE_PATH),
|
|
714
|
+
configManager.getSettings().appearance
|
|
715
|
+
);
|
|
716
|
+
const requestUrl = new URL(req.url || "/", `http://${req.headers.host || "localhost"}`);
|
|
717
|
+
const requestToken = requestUrl.searchParams.has("token") ? tokenAuth.extractToken(req) : "";
|
|
718
|
+
const htmlWithAssetToken = authEnabled && requestToken && tokenAuth.verify(requestToken) ? (0, import_index_html.appendIndexHtmlAssetToken)(rewrittenHtml, requestToken) : rewrittenHtml;
|
|
719
|
+
res.send(htmlWithAssetToken);
|
|
720
|
+
});
|
|
721
|
+
});
|
|
722
|
+
app.get((0, import_index_html.routePath)(BASE_PATH, "/api/executables"), (req, res) => {
|
|
723
|
+
const availableAgents = getAvailableAgentsForRequest();
|
|
724
|
+
res.setHeader("Cache-Control", "no-store");
|
|
725
|
+
res.json({
|
|
726
|
+
agents: availableAgents,
|
|
727
|
+
total: availableAgents.length
|
|
728
|
+
});
|
|
729
|
+
});
|
|
730
|
+
app.get((0, import_index_html.routePath)(BASE_PATH, "/api/workspaces/complete"), async (req, res) => {
|
|
731
|
+
try {
|
|
732
|
+
const partialPath = typeof req.query.path === "string" ? req.query.path : "";
|
|
733
|
+
const requestedLimit = Number(req.query.limit);
|
|
734
|
+
const suggestions = await listWorkspacePathCompletions(partialPath, requestedLimit);
|
|
735
|
+
res.json({ suggestions });
|
|
736
|
+
} catch (caught) {
|
|
737
|
+
const error = caughtError(caught);
|
|
738
|
+
res.status(200).json({
|
|
739
|
+
suggestions: [],
|
|
740
|
+
error: error.message || "Failed to read directory"
|
|
741
|
+
});
|
|
742
|
+
}
|
|
743
|
+
});
|
|
744
|
+
app.use((0, import_index_html.routePath)(BASE_PATH, "/api/workspaces"), (0, import_workspace_directory.createWorkspaceDirectoryRouter)());
|
|
745
|
+
app.get((0, import_index_html.routePath)(BASE_PATH, "/api/skills"), (_req, res) => {
|
|
746
|
+
res.json({ skills: (0, import_main_agent_skills.getMainAgentSkillsCatalog)() });
|
|
747
|
+
});
|
|
748
|
+
app.get((0, import_index_html.routePath)(BASE_PATH, "/api/agent-extensions"), async (_req, res) => {
|
|
749
|
+
try {
|
|
750
|
+
const availableAgents = getAvailableAgentsForRequest().filter((agent) => agent.category === "coding");
|
|
751
|
+
const availableByProvider = new Map(availableAgents.map((agent) => [
|
|
752
|
+
String(agent.name || agent.command || "").trim().toLowerCase(),
|
|
753
|
+
agent
|
|
754
|
+
]));
|
|
755
|
+
const configuredProviders = Object.keys(configManager.getSettings().agentHomes || {});
|
|
756
|
+
const retainedHomes = [];
|
|
757
|
+
const agents = await Promise.all(configuredProviders.map(async (provider) => {
|
|
758
|
+
const agent = availableByProvider.get(provider);
|
|
759
|
+
const configuredHomes = configManager.getAgentHomes(provider);
|
|
760
|
+
const homes = configuredHomes.length > 0 ? configuredHomes : [{
|
|
761
|
+
id: "default",
|
|
762
|
+
path: "",
|
|
763
|
+
order: Number.MAX_SAFE_INTEGER,
|
|
764
|
+
newAgentDefaults: { model: "inherit", reasoning: "inherit", fast: "inherit" }
|
|
765
|
+
}];
|
|
766
|
+
return {
|
|
767
|
+
id: provider,
|
|
768
|
+
name: agent?.name || provider,
|
|
769
|
+
description: agent?.description || "",
|
|
770
|
+
available: Boolean(agent),
|
|
771
|
+
discoverySupported: true,
|
|
772
|
+
homes: await Promise.all(homes.map(async (home) => {
|
|
773
|
+
if (home.path) retainedHomes.push({ provider, path: home.path });
|
|
774
|
+
const inventory = await agentExtensionInventory.get(provider, home.path);
|
|
775
|
+
return {
|
|
776
|
+
id: home.id,
|
|
777
|
+
path: home.path,
|
|
778
|
+
order: home.order,
|
|
779
|
+
newAgentDefaults: home.newAgentDefaults,
|
|
780
|
+
configuration: {
|
|
781
|
+
rootId: (0, import_workspace_root_registry.rootIdForPath)(home.path),
|
|
782
|
+
...inventory.configuration
|
|
783
|
+
},
|
|
784
|
+
extensions: inventory.extensions.map((extension) => ({
|
|
785
|
+
...extension,
|
|
786
|
+
rootId: (0, import_workspace_root_registry.rootIdForPath)(home.path)
|
|
787
|
+
}))
|
|
788
|
+
};
|
|
789
|
+
}))
|
|
790
|
+
};
|
|
791
|
+
}));
|
|
792
|
+
await agentExtensionInventory.retain(retainedHomes);
|
|
793
|
+
res.setHeader("Cache-Control", "no-store");
|
|
794
|
+
res.json({ agents });
|
|
795
|
+
} catch (caught) {
|
|
796
|
+
const error = caughtError(caught);
|
|
797
|
+
console.error("Failed to read Agent extension inventory:", error);
|
|
798
|
+
res.setHeader("Cache-Control", "no-store");
|
|
799
|
+
res.status(500).json({ error: error.message || "Failed to read Agent extensions" });
|
|
800
|
+
}
|
|
801
|
+
});
|
|
802
|
+
app.get((0, import_index_html.routePath)(BASE_PATH, "/api/slash-commands"), (req, res) => {
|
|
803
|
+
const provider = typeof req.query.provider === "string" ? req.query.provider : "";
|
|
804
|
+
const workspace = typeof req.query.workspace === "string" ? req.query.workspace : "";
|
|
805
|
+
const requested = requestedProviderHome(provider, req.query.homeId);
|
|
806
|
+
if (!requested.home) {
|
|
807
|
+
res.status(requested.status).json({ error: requested.error });
|
|
808
|
+
return;
|
|
809
|
+
}
|
|
810
|
+
res.json({
|
|
811
|
+
commands: (0, import_slash_command_discovery.discoverSlashCommands)({
|
|
812
|
+
provider,
|
|
813
|
+
providerHomePath: requested.home.path,
|
|
814
|
+
workspace
|
|
815
|
+
})
|
|
816
|
+
});
|
|
817
|
+
});
|
|
818
|
+
const IMAGE_ATTACHMENT_EXTENSIONS = {
|
|
819
|
+
"image/png": "png",
|
|
820
|
+
"image/jpeg": "jpg",
|
|
821
|
+
"image/gif": "gif",
|
|
822
|
+
"image/webp": "webp"
|
|
823
|
+
};
|
|
824
|
+
const AUDIO_ATTACHMENT_EXTENSIONS = {
|
|
825
|
+
"audio/aac": "aac",
|
|
826
|
+
"audio/flac": "flac",
|
|
827
|
+
"audio/mp4": "m4a",
|
|
828
|
+
"audio/mpeg": "mp3",
|
|
829
|
+
"audio/ogg": "ogg",
|
|
830
|
+
"audio/wav": "wav",
|
|
831
|
+
"audio/wave": "wav",
|
|
832
|
+
"audio/webm": "webm",
|
|
833
|
+
"audio/x-wav": "wav"
|
|
834
|
+
};
|
|
835
|
+
const IMAGE_ATTACHMENT_RETENTION_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
836
|
+
const IMAGE_ATTACHMENT_GC_INTERVAL_MS = 60 * 60 * 1e3;
|
|
837
|
+
const IMAGE_ATTACHMENT_FILENAME_RE = /^pasted-image-\d+-[a-f0-9]{8}\.(?:png|jpg|gif|webp)$/;
|
|
838
|
+
const AUDIO_ATTACHMENT_FILENAME_RE = /^pasted-audio-\d+-[a-f0-9]{8}\.(?:aac|flac|m4a|mp3|ogg|wav|webm)$/;
|
|
839
|
+
let lastImageAttachmentGcAt = 0;
|
|
840
|
+
function imageAttachmentExtension(contentType) {
|
|
841
|
+
const normalized = String(contentType || "").split(";")[0].trim().toLowerCase();
|
|
842
|
+
return IMAGE_ATTACHMENT_EXTENSIONS[normalized] || "";
|
|
843
|
+
}
|
|
844
|
+
function audioAttachmentExtension(contentType) {
|
|
845
|
+
const normalized = String(contentType || "").split(";")[0].trim().toLowerCase();
|
|
846
|
+
return AUDIO_ATTACHMENT_EXTENSIONS[normalized] || "";
|
|
847
|
+
}
|
|
848
|
+
function imageAttachmentsDir() {
|
|
849
|
+
return path.join(configManager.farmingDir, "attachments");
|
|
850
|
+
}
|
|
851
|
+
async function cleanupExpiredImageAttachments(options = {}) {
|
|
852
|
+
const now = Date.now();
|
|
853
|
+
if (!options.force && now - lastImageAttachmentGcAt < IMAGE_ATTACHMENT_GC_INTERVAL_MS) return;
|
|
854
|
+
lastImageAttachmentGcAt = now;
|
|
855
|
+
const attachmentsDir = imageAttachmentsDir();
|
|
856
|
+
let entries = [];
|
|
857
|
+
try {
|
|
858
|
+
entries = await fs.promises.readdir(attachmentsDir, { withFileTypes: true });
|
|
859
|
+
} catch (caught) {
|
|
860
|
+
const error = caughtError(caught);
|
|
861
|
+
if (error && error.code !== "ENOENT") {
|
|
862
|
+
console.warn("Failed to scan image attachments:", error.message || error);
|
|
863
|
+
}
|
|
864
|
+
return;
|
|
865
|
+
}
|
|
866
|
+
const cutoff = now - IMAGE_ATTACHMENT_RETENTION_MS;
|
|
867
|
+
await Promise.all(entries.map(async (entry) => {
|
|
868
|
+
if (!entry.isFile() || !IMAGE_ATTACHMENT_FILENAME_RE.test(entry.name) && !AUDIO_ATTACHMENT_FILENAME_RE.test(entry.name)) return;
|
|
869
|
+
const filePath = path.join(attachmentsDir, entry.name);
|
|
870
|
+
try {
|
|
871
|
+
const stat = await fs.promises.stat(filePath);
|
|
872
|
+
if (stat.mtimeMs < cutoff) {
|
|
873
|
+
await fs.promises.unlink(filePath);
|
|
874
|
+
}
|
|
875
|
+
} catch (caught) {
|
|
876
|
+
const error = caughtError(caught);
|
|
877
|
+
if (!error || error.code !== "ENOENT") {
|
|
878
|
+
console.warn("Failed to remove expired image attachment:", error && (error.message || error));
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
}));
|
|
882
|
+
}
|
|
883
|
+
void cleanupExpiredImageAttachments({ force: true });
|
|
884
|
+
app.post(
|
|
885
|
+
(0, import_index_html.routePath)(BASE_PATH, "/api/attachments/image"),
|
|
886
|
+
express.raw({ type: "image/*", limit: "12mb" }),
|
|
887
|
+
(req, res) => {
|
|
888
|
+
const contentType = String(req.headers["content-type"] || "").split(";")[0].trim().toLowerCase();
|
|
889
|
+
const extension = imageAttachmentExtension(contentType);
|
|
890
|
+
if (!extension) {
|
|
891
|
+
res.status(415).json({ error: "unsupported image type" });
|
|
892
|
+
return;
|
|
893
|
+
}
|
|
894
|
+
if (!Buffer.isBuffer(req.body) || req.body.length === 0) {
|
|
895
|
+
res.status(400).json({ error: "empty image attachment" });
|
|
896
|
+
return;
|
|
897
|
+
}
|
|
898
|
+
const attachmentsDir = imageAttachmentsDir();
|
|
899
|
+
fs.mkdirSync(attachmentsDir, { recursive: true });
|
|
900
|
+
const filename = `pasted-image-${Date.now()}-${crypto.randomBytes(4).toString("hex")}.${extension}`;
|
|
901
|
+
const filePath = path.join(attachmentsDir, filename);
|
|
902
|
+
fs.writeFileSync(filePath, req.body);
|
|
903
|
+
void cleanupExpiredImageAttachments();
|
|
904
|
+
res.status(201).json({
|
|
905
|
+
path: filePath,
|
|
906
|
+
name: filename,
|
|
907
|
+
type: contentType,
|
|
908
|
+
size: req.body.length
|
|
909
|
+
});
|
|
910
|
+
}
|
|
911
|
+
);
|
|
912
|
+
app.post(
|
|
913
|
+
(0, import_index_html.routePath)(BASE_PATH, "/api/attachments/audio"),
|
|
914
|
+
express.raw({ type: "audio/*", limit: "25mb" }),
|
|
915
|
+
(req, res) => {
|
|
916
|
+
const contentType = String(req.headers["content-type"] || "").split(";")[0].trim().toLowerCase();
|
|
917
|
+
const extension = audioAttachmentExtension(contentType);
|
|
918
|
+
if (!extension) {
|
|
919
|
+
res.status(415).json({ error: "unsupported audio type" });
|
|
920
|
+
return;
|
|
921
|
+
}
|
|
922
|
+
if (!Buffer.isBuffer(req.body) || req.body.length === 0) {
|
|
923
|
+
res.status(400).json({ error: "empty audio attachment" });
|
|
924
|
+
return;
|
|
925
|
+
}
|
|
926
|
+
const attachmentsDir = imageAttachmentsDir();
|
|
927
|
+
fs.mkdirSync(attachmentsDir, { recursive: true });
|
|
928
|
+
const filename = `pasted-audio-${Date.now()}-${crypto.randomBytes(4).toString("hex")}.${extension}`;
|
|
929
|
+
const filePath = path.join(attachmentsDir, filename);
|
|
930
|
+
fs.writeFileSync(filePath, req.body);
|
|
931
|
+
void cleanupExpiredImageAttachments();
|
|
932
|
+
res.status(201).json({ path: filePath, name: filename, type: contentType, size: req.body.length });
|
|
933
|
+
}
|
|
934
|
+
);
|
|
935
|
+
app.get((0, import_index_html.routePath)(BASE_PATH, "/api/codex/models"), async (req, res) => {
|
|
936
|
+
const requested = requestedProviderHome("codex", req.query.homeId);
|
|
937
|
+
if (!requested.home) {
|
|
938
|
+
res.status(requested.status).json({ error: requested.error });
|
|
939
|
+
return;
|
|
940
|
+
}
|
|
941
|
+
try {
|
|
942
|
+
const catalog = await codexModelOptionsCache.get(requested.home.path);
|
|
943
|
+
res.json(catalog);
|
|
944
|
+
} catch (caught) {
|
|
945
|
+
const error = caughtError(caught);
|
|
946
|
+
const timedOut = error && error.code === "CODEX_MODELS_TIMEOUT";
|
|
947
|
+
res.status(timedOut ? 504 : 502).json({
|
|
948
|
+
error: error && error.message ? error.message : "Failed to load Codex model catalog",
|
|
949
|
+
code: error && error.code ? error.code : "CODEX_MODELS_FAILED"
|
|
950
|
+
});
|
|
951
|
+
}
|
|
952
|
+
});
|
|
953
|
+
app.get((0, import_index_html.routePath)(BASE_PATH, "/api/claude/settings"), (req, res) => {
|
|
954
|
+
const requested = requestedProviderHome("claude", req.query.homeId);
|
|
955
|
+
if (!requested.home) {
|
|
956
|
+
res.status(requested.status).json({ error: requested.error });
|
|
957
|
+
return;
|
|
958
|
+
}
|
|
959
|
+
res.json({
|
|
960
|
+
settings: (0, import_claude_settings.readClaudeSettingsSummary)({
|
|
961
|
+
settingsFile: path.join(requested.home.path, "settings.json")
|
|
962
|
+
})
|
|
963
|
+
});
|
|
964
|
+
});
|
|
965
|
+
app.get((0, import_index_html.routePath)(BASE_PATH, "/api/usage"), async (req, res) => {
|
|
966
|
+
try {
|
|
967
|
+
const fresh = req.query.fresh === "1";
|
|
968
|
+
const live = req.query.live === "1";
|
|
969
|
+
if (fresh) usageMonitor.invalidateDailyCache();
|
|
970
|
+
const usage = await usageSummaryCache.get(
|
|
971
|
+
"summary",
|
|
972
|
+
fresh ? { force: true } : live ? { maxAgeMs: 15e3 } : {}
|
|
973
|
+
);
|
|
974
|
+
res.json({ usage });
|
|
975
|
+
} catch (caught) {
|
|
976
|
+
const error = caughtError(caught);
|
|
977
|
+
res.status(500).json({ error: error.message || "Failed to read usage information" });
|
|
978
|
+
}
|
|
979
|
+
});
|
|
980
|
+
app.get((0, import_index_html.routePath)(BASE_PATH, "/api/usage/day"), async (req, res) => {
|
|
981
|
+
try {
|
|
982
|
+
const date = String(req.query.date || "").trim();
|
|
983
|
+
const detail = await usageMonitor.getUsageDay(date, {
|
|
984
|
+
fresh: req.query.fresh === "1",
|
|
985
|
+
live: req.query.live === "1"
|
|
986
|
+
});
|
|
987
|
+
res.json({ detail });
|
|
988
|
+
} catch (caught) {
|
|
989
|
+
const error = caughtError(caught);
|
|
990
|
+
const invalidDate = error instanceof RangeError;
|
|
991
|
+
res.status(invalidDate ? 400 : 500).json({
|
|
992
|
+
error: invalidDate ? error.message : error.message || "Failed to read usage day information"
|
|
993
|
+
});
|
|
994
|
+
}
|
|
995
|
+
});
|
|
996
|
+
app.post((0, import_index_html.routePath)(BASE_PATH, "/api/codex/context-windows"), express.json(), async (req, res) => {
|
|
997
|
+
try {
|
|
998
|
+
const requestedIds = Array.isArray(req.body?.agentIds) ? req.body.agentIds.map((value) => String(value || "").trim()).filter(Boolean).slice(0, 20) : [];
|
|
999
|
+
const requestedIdSet = new Set(requestedIds);
|
|
1000
|
+
const agents = agentManager.getState().agents.filter((agent) => requestedIdSet.has(agent.id));
|
|
1001
|
+
const contextWindows = await codexContextWindowReader.readForAgents(agents);
|
|
1002
|
+
res.json({ contextWindows });
|
|
1003
|
+
} catch (caught) {
|
|
1004
|
+
const error = caughtError(caught);
|
|
1005
|
+
res.status(500).json({ error: error.message || "Failed to read Codex context windows" });
|
|
1006
|
+
}
|
|
1007
|
+
});
|
|
1008
|
+
app.get((0, import_index_html.routePath)(BASE_PATH, "/api/update"), async (req, res) => {
|
|
1009
|
+
try {
|
|
1010
|
+
const update = await updateService.check({ force: req.query.force === "1" });
|
|
1011
|
+
res.json({ update });
|
|
1012
|
+
} catch (caught) {
|
|
1013
|
+
const error = caughtError(caught);
|
|
1014
|
+
res.status(502).json({ error: error.message || "Failed to check for updates" });
|
|
1015
|
+
}
|
|
1016
|
+
});
|
|
1017
|
+
app.post((0, import_index_html.routePath)(BASE_PATH, "/api/update/install"), express.json(), async (req, res) => {
|
|
1018
|
+
try {
|
|
1019
|
+
const state = await updateService.startInstall({
|
|
1020
|
+
assetName: req.body && typeof req.body.assetName === "string" ? req.body.assetName : ""
|
|
1021
|
+
});
|
|
1022
|
+
res.status(202).json({ update: { state } });
|
|
1023
|
+
} catch (caught) {
|
|
1024
|
+
const error = caughtError(caught);
|
|
1025
|
+
res.status(500).json({ error: error.message || "Failed to start update" });
|
|
1026
|
+
}
|
|
1027
|
+
});
|
|
1028
|
+
app.post((0, import_index_html.routePath)(BASE_PATH, "/api/update/restart"), express.json(), async (req, res) => {
|
|
1029
|
+
try {
|
|
1030
|
+
const state = await updateService.applyPreparedUpdate();
|
|
1031
|
+
res.status(202).json({ update: { state } });
|
|
1032
|
+
} catch (caught) {
|
|
1033
|
+
const error = caughtError(caught);
|
|
1034
|
+
res.status(500).json({ error: error.message || "Failed to restart for update" });
|
|
1035
|
+
}
|
|
1036
|
+
});
|
|
1037
|
+
function warmCodexExecutableVersionCache() {
|
|
1038
|
+
const startedAt = Date.now();
|
|
1039
|
+
try {
|
|
1040
|
+
const result = (0, import_executable_discovery.resolveCompatibleCodexExecutable)("");
|
|
1041
|
+
if (result.path) {
|
|
1042
|
+
console.log(`Codex executable ready: ${result.version || "unknown version"} (${Date.now() - startedAt}ms)`);
|
|
1043
|
+
}
|
|
1044
|
+
} catch (caught) {
|
|
1045
|
+
const error = caughtError(caught);
|
|
1046
|
+
console.warn("Failed to warm Codex executable version cache:", error.message || error);
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
1049
|
+
app.get((0, import_index_html.routePath)(BASE_PATH, "/api/codex/sessions"), async (req, res) => {
|
|
1050
|
+
const requested = requestedProviderHome("codex", req.query.homeId);
|
|
1051
|
+
if (!requested.home) {
|
|
1052
|
+
res.status(requested.status).json({ error: requested.error });
|
|
1053
|
+
return;
|
|
1054
|
+
}
|
|
1055
|
+
const requestedLimit = Number(req.query.limit);
|
|
1056
|
+
const limit = Number.isFinite(requestedLimit) ? Math.max(0, Math.min(1e3, requestedLimit)) : 40;
|
|
1057
|
+
const requestedScanLimit = Number(req.query.scanLimit);
|
|
1058
|
+
const scanLimit = Number.isFinite(requestedScanLimit) ? Math.max(limit, Math.min(5e3, requestedScanLimit)) : void 0;
|
|
1059
|
+
const sessions = await (0, import_codex_session_history.listCodexSessions)({ codexHome: requested.home.path, limit, scanLimit });
|
|
1060
|
+
res.json({ sessions });
|
|
1061
|
+
});
|
|
1062
|
+
function providerSessionDisplayStates() {
|
|
1063
|
+
const states = /* @__PURE__ */ new Map();
|
|
1064
|
+
for (const record of configManager.listAgentSessionRecords()) {
|
|
1065
|
+
if (typeof record.providerSessionKey === "string" && record.providerSessionKey) {
|
|
1066
|
+
states.set(record.providerSessionKey, record);
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
1069
|
+
return states;
|
|
1070
|
+
}
|
|
1071
|
+
app.get((0, import_index_html.routePath)(BASE_PATH, "/api/agent-sessions"), async (req, res) => {
|
|
1072
|
+
try {
|
|
1073
|
+
res.setHeader("Cache-Control", "no-store");
|
|
1074
|
+
const requestedLimit = Number(req.query.limit);
|
|
1075
|
+
const limit = Number.isFinite(requestedLimit) ? Math.max(0, Math.min(1e3, requestedLimit)) : 60;
|
|
1076
|
+
const cursor = typeof req.query.cursor === "string" ? req.query.cursor : "";
|
|
1077
|
+
if (req.query.force === "1") agentSessionInventory.invalidate();
|
|
1078
|
+
const sessions = await currentAgentSessions();
|
|
1079
|
+
const page = (0, import_agent_session_history.paginateAgentSessions)(sessions, { limit: Math.max(1, limit), cursor });
|
|
1080
|
+
if (page.invalidCursor) {
|
|
1081
|
+
res.status(400).json({ error: "Invalid Agent session cursor" });
|
|
1082
|
+
return;
|
|
1083
|
+
}
|
|
1084
|
+
const displayStateByKey = providerSessionDisplayStates();
|
|
1085
|
+
res.json({
|
|
1086
|
+
sessions: page.sessions.map((session) => {
|
|
1087
|
+
const key = (0, import_main_page_session.mainPageAgentSessionKey)(session.provider, session.id, session.providerHomeId);
|
|
1088
|
+
const displayState = displayStateByKey.get(key);
|
|
1089
|
+
return typeof displayState?.displayPinned === "boolean" ? { ...session, pinned: displayState.displayPinned } : session;
|
|
1090
|
+
}),
|
|
1091
|
+
nextCursor: page.nextCursor,
|
|
1092
|
+
hasMore: page.hasMore,
|
|
1093
|
+
total: sessions.length
|
|
1094
|
+
});
|
|
1095
|
+
} catch (caught) {
|
|
1096
|
+
const error = caughtError(caught);
|
|
1097
|
+
console.error("Failed to read agent sessions:", error);
|
|
1098
|
+
res.status(500).json({ error: error.message || "Failed to read agent sessions" });
|
|
1099
|
+
}
|
|
1100
|
+
});
|
|
1101
|
+
app.get((0, import_index_html.routePath)(BASE_PATH, "/api/agent-sessions/search"), async (req, res) => {
|
|
1102
|
+
try {
|
|
1103
|
+
res.setHeader("Cache-Control", "no-store");
|
|
1104
|
+
const query = typeof req.query.q === "string" ? req.query.q : "";
|
|
1105
|
+
const requestedLimit = Number(req.query.limit);
|
|
1106
|
+
const limit = Number.isFinite(requestedLimit) ? Math.max(1, Math.min(1e3, requestedLimit)) : 100;
|
|
1107
|
+
const settings = configManager.getSettings();
|
|
1108
|
+
if (req.query.force === "1") agentSessionInventory.invalidate();
|
|
1109
|
+
const sessions = await withSearchTimeout(
|
|
1110
|
+
currentAgentSessions(),
|
|
1111
|
+
Number(settings.searchTimeoutMs) || 1e4
|
|
1112
|
+
);
|
|
1113
|
+
const result = (0, import_agent_session_history.searchAgentSessions)(sessions, query, {
|
|
1114
|
+
limit,
|
|
1115
|
+
projectNames: settings.projectNames
|
|
1116
|
+
});
|
|
1117
|
+
const displayStateByKey = providerSessionDisplayStates();
|
|
1118
|
+
res.json({
|
|
1119
|
+
...result,
|
|
1120
|
+
sessions: result.sessions.map((session) => {
|
|
1121
|
+
const key = (0, import_main_page_session.mainPageAgentSessionKey)(session.provider, session.id, session.providerHomeId);
|
|
1122
|
+
const displayState = displayStateByKey.get(key);
|
|
1123
|
+
return typeof displayState?.displayPinned === "boolean" ? { ...session, pinned: displayState.displayPinned } : session;
|
|
1124
|
+
})
|
|
1125
|
+
});
|
|
1126
|
+
} catch (caught) {
|
|
1127
|
+
const error = caughtError(caught);
|
|
1128
|
+
if (error?.code === "ETIMEDOUT") {
|
|
1129
|
+
res.status(504).json({ error: error.message });
|
|
1130
|
+
return;
|
|
1131
|
+
}
|
|
1132
|
+
console.error("Failed to search agent sessions:", error);
|
|
1133
|
+
res.status(500).json({ error: error.message || "Failed to search Agent sessions" });
|
|
1134
|
+
}
|
|
1135
|
+
});
|
|
1136
|
+
app.patch((0, import_index_html.routePath)(BASE_PATH, "/api/agent-sessions/:provider/:sessionId"), express.json(), (req, res) => {
|
|
1137
|
+
const provider = (0, import_agent_session_history.normalizeProvider)(req.params.provider);
|
|
1138
|
+
const sessionId = String(req.params.sessionId || "").trim();
|
|
1139
|
+
const providerHomeId = String(req.body?.providerHomeId || "default").trim() || "default";
|
|
1140
|
+
if (!provider || !(0, import_agent_session_history.isSafeSessionId)(sessionId) || !/^[A-Za-z0-9._-]+$/.test(providerHomeId)) {
|
|
1141
|
+
res.status(400).json({ error: "Invalid Agent session" });
|
|
1142
|
+
return;
|
|
1143
|
+
}
|
|
1144
|
+
if (typeof req.body?.pinned !== "boolean") {
|
|
1145
|
+
res.status(400).json({ error: "Pinned state is required" });
|
|
1146
|
+
return;
|
|
1147
|
+
}
|
|
1148
|
+
const sessionKey = (0, import_main_page_session.mainPageAgentSessionKey)(provider, sessionId, providerHomeId);
|
|
1149
|
+
configManager.setProviderSessionDisplayState(sessionKey, { pinned: req.body.pinned });
|
|
1150
|
+
res.json({ sessionKey, pinned: req.body.pinned });
|
|
1151
|
+
});
|
|
1152
|
+
app.post((0, import_index_html.routePath)(BASE_PATH, "/api/main-page-agent-sessions"), express.json(), (req, res) => {
|
|
1153
|
+
const operation = typeof req.body?.operation === "string" ? req.body.operation : "";
|
|
1154
|
+
const requestedKeys = Array.isArray(req.body?.sessionKeys) ? req.body.sessionKeys : [];
|
|
1155
|
+
const sessionKeys = [...new Set(requestedKeys.map((key) => String(key || "").trim()).filter(Boolean))];
|
|
1156
|
+
if (!["add", "remove"].includes(operation) || sessionKeys.length === 0 || sessionKeys.length > 50 || sessionKeys.some((key) => !(0, import_main_page_session.mainPageAgentSessionFromKey)(key))) {
|
|
1157
|
+
res.status(400).json({ error: "A valid main-page Agent session mutation is required" });
|
|
1158
|
+
return;
|
|
1159
|
+
}
|
|
1160
|
+
if (operation === "add") {
|
|
1161
|
+
[...sessionKeys].reverse().forEach((sessionKey) => {
|
|
1162
|
+
const session = (0, import_main_page_session.mainPageAgentSessionFromKey)(sessionKey);
|
|
1163
|
+
if (!session) return;
|
|
1164
|
+
configManager.rememberMainPageSessionKey(sessionKey, {
|
|
1165
|
+
provider: session.provider,
|
|
1166
|
+
providerSessionId: session.sessionId,
|
|
1167
|
+
providerSessionKey: sessionKey,
|
|
1168
|
+
providerHomeId: session.providerHomeId || "default"
|
|
1169
|
+
});
|
|
1170
|
+
});
|
|
1171
|
+
} else {
|
|
1172
|
+
configManager.removeMainPageSessionKeys(sessionKeys);
|
|
1173
|
+
}
|
|
1174
|
+
const mainPageSessionKeys = configManager.getMainPageSessionKeys();
|
|
1175
|
+
agentSessionInventory.invalidate();
|
|
1176
|
+
res.json({ success: true, mainPageSessionKeys });
|
|
1177
|
+
scheduleBroadcastState();
|
|
1178
|
+
});
|
|
1179
|
+
app.get((0, import_index_html.routePath)(BASE_PATH, "/api/themes"), (req, res) => {
|
|
1180
|
+
const currentTheme = configManager.getSettings().theme || "terminal";
|
|
1181
|
+
res.json({
|
|
1182
|
+
themes: themeManager.getAllThemes(),
|
|
1183
|
+
current: currentTheme
|
|
1184
|
+
});
|
|
1185
|
+
});
|
|
1186
|
+
app.get((0, import_index_html.routePath)(BASE_PATH, "/api/settings"), (req, res) => {
|
|
1187
|
+
res.json({
|
|
1188
|
+
settings: configManager.getSettings()
|
|
1189
|
+
});
|
|
1190
|
+
});
|
|
1191
|
+
app.get((0, import_index_html.routePath)(BASE_PATH, "/api/workspaces/discovered"), (req, res) => {
|
|
1192
|
+
const requestedLimit = Number(req.query.limit);
|
|
1193
|
+
const limit = Number.isFinite(requestedLimit) ? Math.max(0, Math.min(20, requestedLimit)) : 12;
|
|
1194
|
+
const agent = typeof req.query.agent === "string" ? req.query.agent : "";
|
|
1195
|
+
const cacheToken = JSON.stringify({ limit, agent });
|
|
1196
|
+
workspaceDiscoveryCache.get(cacheToken).then((workspaces) => {
|
|
1197
|
+
res.json({ workspaces });
|
|
1198
|
+
}).catch((error) => {
|
|
1199
|
+
const message = caughtError(error).message;
|
|
1200
|
+
console.error("Failed to discover workspaces:", error);
|
|
1201
|
+
res.status(500).json({ error: message || "Failed to discover workspaces" });
|
|
1202
|
+
});
|
|
1203
|
+
});
|
|
1204
|
+
app.get((0, import_index_html.routePath)(BASE_PATH, "/api/agents/:agentId/session-text"), async (req, res) => {
|
|
1205
|
+
const text = await agentManager.getAgentSessionText(req.params.agentId);
|
|
1206
|
+
if (text === null) {
|
|
1207
|
+
res.status(404).json({ error: "Agent not found" });
|
|
1208
|
+
return;
|
|
1209
|
+
}
|
|
1210
|
+
res.type("text/plain");
|
|
1211
|
+
res.send(text);
|
|
1212
|
+
});
|
|
1213
|
+
app.get((0, import_index_html.routePath)(BASE_PATH, "/api/agents/:agentId/session-view"), async (req, res) => {
|
|
1214
|
+
const sessionView = await agentManager.getAgentSessionView(req.params.agentId);
|
|
1215
|
+
if (!sessionView) {
|
|
1216
|
+
res.status(404).json({ error: "Agent not found" });
|
|
1217
|
+
return;
|
|
1218
|
+
}
|
|
1219
|
+
res.json({ session: sessionView });
|
|
1220
|
+
});
|
|
1221
|
+
app.get((0, import_index_html.routePath)(BASE_PATH, "/api/agents/:agentId/acp-session"), async (req, res) => {
|
|
1222
|
+
try {
|
|
1223
|
+
res.json({
|
|
1224
|
+
session: agentManager.getAcpSession(req.params.agentId, {
|
|
1225
|
+
includeUpdates: req.query.includeUpdates === "1",
|
|
1226
|
+
includeEntries: req.query.includeEntries !== "0"
|
|
1227
|
+
})
|
|
1228
|
+
});
|
|
1229
|
+
} catch (caught) {
|
|
1230
|
+
const error = caughtError(caught);
|
|
1231
|
+
const message = error && error.message ? error.message : "Failed to read ACP session";
|
|
1232
|
+
res.status(message === "Agent not found" ? 404 : 409).json({ error: message });
|
|
1233
|
+
}
|
|
1234
|
+
});
|
|
1235
|
+
app.get((0, import_index_html.routePath)(BASE_PATH, "/api/agents/:agentId/acp-transcript"), async (req, res) => {
|
|
1236
|
+
try {
|
|
1237
|
+
const requestedMaxTurns = Number.parseInt(String(req.query.maxTurns || ""), 10);
|
|
1238
|
+
const maxTurns = Number.isFinite(requestedMaxTurns) ? Math.min(MAX_TRANSCRIPT_TURNS, Math.max(20, requestedMaxTurns)) : DEFAULT_TRANSCRIPT_MAX_TURNS;
|
|
1239
|
+
const requestedRevision = Number.parseInt(String(req.query.sinceRevision || ""), 10);
|
|
1240
|
+
const externalMedia = req.query.media === "external-v1";
|
|
1241
|
+
res.json({ transcript: agentManager.getAcpTranscript(req.params.agentId, {
|
|
1242
|
+
maxTurns,
|
|
1243
|
+
...externalMedia ? {
|
|
1244
|
+
mediaPathPrefix: (0, import_index_html.routePath)(
|
|
1245
|
+
BASE_PATH,
|
|
1246
|
+
`/api/agents/${encodeURIComponent(req.params.agentId)}/acp-media`
|
|
1247
|
+
)
|
|
1248
|
+
} : {},
|
|
1249
|
+
...Number.isFinite(requestedRevision) && requestedRevision >= 0 ? { sinceRevision: requestedRevision } : {}
|
|
1250
|
+
}) });
|
|
1251
|
+
} catch (caught) {
|
|
1252
|
+
const error = caughtError(caught);
|
|
1253
|
+
const message = error && error.message ? error.message : "Failed to read ACP transcript";
|
|
1254
|
+
res.status(message === "Agent not found" ? 404 : 409).json({ error: message });
|
|
1255
|
+
}
|
|
1256
|
+
});
|
|
1257
|
+
app.get((0, import_index_html.routePath)(BASE_PATH, "/api/agents/:agentId/acp-media/:entryId/:mediaId"), (req, res) => {
|
|
1258
|
+
try {
|
|
1259
|
+
const media = agentManager.getAcpTranscriptMedia(
|
|
1260
|
+
req.params.agentId,
|
|
1261
|
+
req.params.entryId,
|
|
1262
|
+
req.params.mediaId
|
|
1263
|
+
);
|
|
1264
|
+
const decoded = (0, import_acp_transcript.decodeAcpTranscriptMedia)(media);
|
|
1265
|
+
if (!decoded) {
|
|
1266
|
+
res.status(415).json({ error: "unsupported ACP transcript media" });
|
|
1267
|
+
return;
|
|
1268
|
+
}
|
|
1269
|
+
res.setHeader("Cache-Control", "private, max-age=31536000, immutable");
|
|
1270
|
+
res.setHeader("X-Content-Type-Options", "nosniff");
|
|
1271
|
+
res.type(decoded.mimeType).send(decoded.content);
|
|
1272
|
+
} catch (caught) {
|
|
1273
|
+
const error = caughtError(caught);
|
|
1274
|
+
const message = error && error.message ? error.message : "Failed to read ACP transcript media";
|
|
1275
|
+
const status = message === "Agent not found" || message === "ACP transcript entry not found" || message === "ACP transcript media not found" ? 404 : 409;
|
|
1276
|
+
res.status(status).json({ error: message });
|
|
1277
|
+
}
|
|
1278
|
+
});
|
|
1279
|
+
app.get((0, import_index_html.routePath)(BASE_PATH, "/api/agents/:agentId/acp-tool-details/:toolCallId"), async (req, res) => {
|
|
1280
|
+
try {
|
|
1281
|
+
res.json(agentManager.getAcpToolDetail(req.params.agentId, req.params.toolCallId));
|
|
1282
|
+
} catch (caught) {
|
|
1283
|
+
const error = caughtError(caught);
|
|
1284
|
+
const message = error && error.message ? error.message : "Failed to read ACP tool details";
|
|
1285
|
+
const status = message === "Agent not found" || message === "ACP tool call not found" ? 404 : 409;
|
|
1286
|
+
res.status(status).json({ error: message });
|
|
1287
|
+
}
|
|
1288
|
+
});
|
|
1289
|
+
app.post((0, import_index_html.routePath)(BASE_PATH, "/api/agents/:agentId/acp-terminals/:terminalId/kill"), (req, res) => {
|
|
1290
|
+
try {
|
|
1291
|
+
res.json(agentManager.killAcpTerminal(req.params.agentId, req.params.terminalId));
|
|
1292
|
+
} catch (caught) {
|
|
1293
|
+
const error = caughtError(caught);
|
|
1294
|
+
const message = error && error.message ? error.message : "Failed to stop ACP terminal";
|
|
1295
|
+
const status = message === "Agent not found" || message === "Unknown ACP terminal" ? 404 : 409;
|
|
1296
|
+
res.status(status).json({ error: message });
|
|
1297
|
+
}
|
|
1298
|
+
});
|
|
1299
|
+
app.post((0, import_index_html.routePath)(BASE_PATH, "/api/agents/:agentId/acp-terminals/:terminalId/input"), express.json({ limit: "96kb" }), (req, res) => {
|
|
1300
|
+
try {
|
|
1301
|
+
res.json(agentManager.inputAcpTerminal(
|
|
1302
|
+
req.params.agentId,
|
|
1303
|
+
req.params.terminalId,
|
|
1304
|
+
requiredString(req.body?.input)
|
|
1305
|
+
));
|
|
1306
|
+
} catch (caught) {
|
|
1307
|
+
const error = caughtError(caught);
|
|
1308
|
+
const message = error && error.message ? error.message : "Failed to write ACP terminal input";
|
|
1309
|
+
const status = message === "Agent not found" || message === "Unknown ACP terminal" ? 404 : 409;
|
|
1310
|
+
res.status(status).json({ error: message });
|
|
1311
|
+
}
|
|
1312
|
+
});
|
|
1313
|
+
app.post((0, import_index_html.routePath)(BASE_PATH, "/api/agents/:agentId/acp-terminals/:terminalId/resize"), express.json(), (req, res) => {
|
|
1314
|
+
try {
|
|
1315
|
+
res.json(agentManager.resizeAcpTerminal(
|
|
1316
|
+
req.params.agentId,
|
|
1317
|
+
req.params.terminalId,
|
|
1318
|
+
Number(req.body?.cols),
|
|
1319
|
+
Number(req.body?.rows)
|
|
1320
|
+
));
|
|
1321
|
+
} catch (caught) {
|
|
1322
|
+
const error = caughtError(caught);
|
|
1323
|
+
const message = error && error.message ? error.message : "Failed to resize ACP terminal";
|
|
1324
|
+
const status = message === "Agent not found" || message === "Unknown ACP terminal" ? 404 : 409;
|
|
1325
|
+
res.status(status).json({ error: message });
|
|
1326
|
+
}
|
|
1327
|
+
});
|
|
1328
|
+
app.post((0, import_index_html.routePath)(BASE_PATH, "/api/agents/:agentId/acp-subagents/:sessionId/cancel"), async (req, res) => {
|
|
1329
|
+
try {
|
|
1330
|
+
res.json(await agentManager.cancelAcpSubagent(req.params.agentId, req.params.sessionId));
|
|
1331
|
+
} catch (caught) {
|
|
1332
|
+
const error = caughtError(caught);
|
|
1333
|
+
const message = error && error.message ? error.message : "Failed to stop ACP subagent";
|
|
1334
|
+
const status = message === "Agent not found" || message === "ACP subagent session not found" ? 404 : 409;
|
|
1335
|
+
res.status(status).json({ error: message });
|
|
1336
|
+
}
|
|
1337
|
+
});
|
|
1338
|
+
app.post((0, import_index_html.routePath)(BASE_PATH, "/api/agents/:agentId/acp-patches/:toolCallId/decision"), express.json({ limit: "16kb" }), async (req, res) => {
|
|
1339
|
+
try {
|
|
1340
|
+
const requestedDecision = req.body?.decision;
|
|
1341
|
+
const decision = requestedDecision === "accept" ? "keep" : requestedDecision === "reject" ? "revert" : requestedDecision;
|
|
1342
|
+
if (decision !== "keep" && decision !== "revert") {
|
|
1343
|
+
throw new Error("ACP patch decision is invalid");
|
|
1344
|
+
}
|
|
1345
|
+
res.json(await agentManager.decideAcpPatch(
|
|
1346
|
+
req.params.agentId,
|
|
1347
|
+
req.params.toolCallId,
|
|
1348
|
+
requiredString(req.body?.path),
|
|
1349
|
+
decision
|
|
1350
|
+
));
|
|
1351
|
+
} catch (caught) {
|
|
1352
|
+
const error = caughtError(caught);
|
|
1353
|
+
const message = error && error.message ? error.message : "Failed to decide ACP patch";
|
|
1354
|
+
const status = Number(error?.statusCode) || (message === "Agent not found" || message === "ACP tool call not found" ? 404 : 409);
|
|
1355
|
+
res.status(status).json({ error: message });
|
|
1356
|
+
}
|
|
1357
|
+
});
|
|
1358
|
+
app.get((0, import_index_html.routePath)(BASE_PATH, "/api/agents/:agentId/acp-sessions"), async (req, res) => {
|
|
1359
|
+
try {
|
|
1360
|
+
const result = await agentManager.listAcpSessions(req.params.agentId, {
|
|
1361
|
+
cwd: typeof req.query.cwd === "string" ? req.query.cwd : "",
|
|
1362
|
+
cursor: typeof req.query.cursor === "string" ? req.query.cursor : ""
|
|
1363
|
+
});
|
|
1364
|
+
res.json(result);
|
|
1365
|
+
} catch (caught) {
|
|
1366
|
+
const error = caughtError(caught);
|
|
1367
|
+
const message = error && error.message ? error.message : "Failed to list ACP sessions";
|
|
1368
|
+
res.status(message === "Agent not found" ? 404 : 409).json({ error: message });
|
|
1369
|
+
}
|
|
1370
|
+
});
|
|
1371
|
+
app.post((0, import_index_html.routePath)(BASE_PATH, "/api/agents/:agentId/acp-permission"), express.json(), (req, res) => {
|
|
1372
|
+
try {
|
|
1373
|
+
const result = agentManager.respondToAcpPermission(
|
|
1374
|
+
req.params.agentId,
|
|
1375
|
+
requiredString(req.body?.requestId),
|
|
1376
|
+
requiredString(req.body?.optionId),
|
|
1377
|
+
req.body?.cancelled === true
|
|
1378
|
+
);
|
|
1379
|
+
res.json(result);
|
|
1380
|
+
} catch (caught) {
|
|
1381
|
+
const error = caughtError(caught);
|
|
1382
|
+
const message = error && error.message ? error.message : "Failed to respond to ACP permission";
|
|
1383
|
+
res.status(message === "Agent not found" ? 404 : 409).json({ error: message });
|
|
1384
|
+
}
|
|
1385
|
+
});
|
|
1386
|
+
app.post((0, import_index_html.routePath)(BASE_PATH, "/api/agents/:agentId/acp-elicitation"), express.json(), (req, res) => {
|
|
1387
|
+
try {
|
|
1388
|
+
const result = agentManager.respondToAcpElicitation(
|
|
1389
|
+
req.params.agentId,
|
|
1390
|
+
requiredString(req.body?.requestId),
|
|
1391
|
+
requiredString(req.body?.action),
|
|
1392
|
+
req.body?.content
|
|
1393
|
+
);
|
|
1394
|
+
res.json(result);
|
|
1395
|
+
} catch (caught) {
|
|
1396
|
+
const error = caughtError(caught);
|
|
1397
|
+
const message = error && error.message ? error.message : "Failed to respond to ACP input request";
|
|
1398
|
+
res.status(message === "Agent not found" ? 404 : 409).json({ error: message });
|
|
1399
|
+
}
|
|
1400
|
+
});
|
|
1401
|
+
app.post((0, import_index_html.routePath)(BASE_PATH, "/api/agents/:agentId/acp-session/authenticate"), express.json(), async (req, res) => {
|
|
1402
|
+
try {
|
|
1403
|
+
res.json(await agentManager.authenticateAcpAgent(req.params.agentId, requiredString(req.body?.methodId)));
|
|
1404
|
+
} catch (caught) {
|
|
1405
|
+
const error = caughtError(caught);
|
|
1406
|
+
const message = error && error.message ? error.message : "Failed to authenticate ACP Agent";
|
|
1407
|
+
res.status(message === "Agent not found" ? 404 : 409).json({ error: message });
|
|
1408
|
+
}
|
|
1409
|
+
});
|
|
1410
|
+
app.post((0, import_index_html.routePath)(BASE_PATH, "/api/agents/:agentId/acp-session/logout"), async (req, res) => {
|
|
1411
|
+
try {
|
|
1412
|
+
res.json(await agentManager.logoutAcpAgent(req.params.agentId));
|
|
1413
|
+
} catch (caught) {
|
|
1414
|
+
const error = caughtError(caught);
|
|
1415
|
+
const message = error && error.message ? error.message : "Failed to log out ACP Agent";
|
|
1416
|
+
res.status(message === "Agent not found" ? 404 : 409).json({ error: message });
|
|
1417
|
+
}
|
|
1418
|
+
});
|
|
1419
|
+
app.post((0, import_index_html.routePath)(BASE_PATH, "/api/agents/:agentId/acp-session/fork"), express.json(), async (req, res) => {
|
|
1420
|
+
try {
|
|
1421
|
+
res.json(await agentManager.forkAcpSession(req.params.agentId, req.body || {}));
|
|
1422
|
+
} catch (caught) {
|
|
1423
|
+
const error = caughtError(caught);
|
|
1424
|
+
const message = error && error.message ? error.message : "Failed to fork ACP session";
|
|
1425
|
+
res.status(message === "Agent not found" ? 404 : 409).json({ error: message });
|
|
1426
|
+
}
|
|
1427
|
+
});
|
|
1428
|
+
app.delete((0, import_index_html.routePath)(BASE_PATH, "/api/agents/:agentId/acp-sessions/:sessionId"), async (req, res) => {
|
|
1429
|
+
try {
|
|
1430
|
+
res.json(await agentManager.deleteAcpSession(req.params.agentId, req.params.sessionId));
|
|
1431
|
+
} catch (caught) {
|
|
1432
|
+
const error = caughtError(caught);
|
|
1433
|
+
const message = error && error.message ? error.message : "Failed to delete ACP session";
|
|
1434
|
+
res.status(message === "Agent not found" ? 404 : 409).json({ error: message });
|
|
1435
|
+
}
|
|
1436
|
+
});
|
|
1437
|
+
app.post((0, import_index_html.routePath)(BASE_PATH, "/api/agents/:agentId/acp-session/close"), async (req, res) => {
|
|
1438
|
+
try {
|
|
1439
|
+
res.json(await agentManager.closeAcpSession(req.params.agentId));
|
|
1440
|
+
} catch (caught) {
|
|
1441
|
+
const error = caughtError(caught);
|
|
1442
|
+
const message = error && error.message ? error.message : "Failed to close ACP session";
|
|
1443
|
+
res.status(message === "Agent not found" ? 404 : 409).json({ error: message });
|
|
1444
|
+
}
|
|
1445
|
+
});
|
|
1446
|
+
app.patch((0, import_index_html.routePath)(BASE_PATH, "/api/agents/:agentId/acp-session"), express.json(), async (req, res) => {
|
|
1447
|
+
try {
|
|
1448
|
+
if (typeof req.body?.modeId === "string") {
|
|
1449
|
+
res.json(await agentManager.setAcpSessionMode(req.params.agentId, req.body.modeId));
|
|
1450
|
+
return;
|
|
1451
|
+
}
|
|
1452
|
+
if (Array.isArray(req.body?.configOptions)) {
|
|
1453
|
+
res.json(await agentManager.setAcpSessionConfigOptions(req.params.agentId, req.body.configOptions));
|
|
1454
|
+
return;
|
|
1455
|
+
}
|
|
1456
|
+
if (typeof req.body?.configId === "string" && Object.prototype.hasOwnProperty.call(req.body, "value") && isAcpConfigValue(req.body.value)) {
|
|
1457
|
+
res.json(await agentManager.setAcpSessionConfigOption(
|
|
1458
|
+
req.params.agentId,
|
|
1459
|
+
req.body.configId,
|
|
1460
|
+
req.body.value
|
|
1461
|
+
));
|
|
1462
|
+
return;
|
|
1463
|
+
}
|
|
1464
|
+
res.status(400).json({ error: "ACP modeId, configOptions, or configId/value is required" });
|
|
1465
|
+
} catch (caught) {
|
|
1466
|
+
const error = caughtError(caught);
|
|
1467
|
+
const message = error && error.message ? error.message : "Failed to update ACP session";
|
|
1468
|
+
res.status(message === "Agent not found" ? 404 : 409).json({ error: message });
|
|
1469
|
+
}
|
|
1470
|
+
});
|
|
1471
|
+
app.post((0, import_index_html.routePath)(BASE_PATH, "/api/agents/:agentId/codex-terminal-profile"), express.json(), async (req, res) => {
|
|
1472
|
+
const requestAbort = new globalThis.AbortController();
|
|
1473
|
+
const abortRequest = () => requestAbort.abort(new Error("Terminal profile request was canceled"));
|
|
1474
|
+
const abortClosedResponse = () => {
|
|
1475
|
+
if (!res.writableEnded) abortRequest();
|
|
1476
|
+
};
|
|
1477
|
+
req.once("aborted", abortRequest);
|
|
1478
|
+
res.once("close", abortClosedResponse);
|
|
1479
|
+
try {
|
|
1480
|
+
const profile = await agentManager.setCodexTerminalProfile(req.params.agentId, {
|
|
1481
|
+
model: optionalString(req.body?.model),
|
|
1482
|
+
effort: optionalString(req.body?.effort),
|
|
1483
|
+
serviceTier: optionalString(req.body?.serviceTier)
|
|
1484
|
+
}, {
|
|
1485
|
+
signal: requestAbort.signal,
|
|
1486
|
+
timeoutMs: 44e3
|
|
1487
|
+
});
|
|
1488
|
+
res.json({ profile });
|
|
1489
|
+
} catch (caught) {
|
|
1490
|
+
const error = caughtError(caught);
|
|
1491
|
+
const message = error && error.message ? error.message : "Failed to update Codex Terminal profile";
|
|
1492
|
+
const status = message === "Agent not found" ? 404 : /^A valid Codex /.test(message) ? 400 : 409;
|
|
1493
|
+
if (!res.headersSent && !res.destroyed) res.status(status).json({ error: message });
|
|
1494
|
+
} finally {
|
|
1495
|
+
req.off("aborted", abortRequest);
|
|
1496
|
+
res.off("close", abortClosedResponse);
|
|
1497
|
+
}
|
|
1498
|
+
});
|
|
1499
|
+
app.patch((0, import_index_html.routePath)(BASE_PATH, "/api/agents/:agentId"), express.json(), async (req, res) => {
|
|
1500
|
+
if (!await requireAgentRecoveryForHttp(res)) return;
|
|
1501
|
+
const body = req.body || {};
|
|
1502
|
+
const updates = {};
|
|
1503
|
+
const providedPatchFields = [
|
|
1504
|
+
"customTitle",
|
|
1505
|
+
"task",
|
|
1506
|
+
"pinned",
|
|
1507
|
+
"unread",
|
|
1508
|
+
"archived",
|
|
1509
|
+
"readAttentionSeq",
|
|
1510
|
+
"readOutputEpoch",
|
|
1511
|
+
"readOutputSeq",
|
|
1512
|
+
"launchPermissionMode",
|
|
1513
|
+
"agentRuntimeMode"
|
|
1514
|
+
].filter((field) => Object.prototype.hasOwnProperty.call(body, field));
|
|
1515
|
+
const lifecyclePatchFields = providedPatchFields.filter((field) => field === "launchPermissionMode" || field === "agentRuntimeMode" || field === "archived" && body.archived === true);
|
|
1516
|
+
if (lifecyclePatchFields.length > 0 && providedPatchFields.length > 1) {
|
|
1517
|
+
res.status(400).json({
|
|
1518
|
+
error: "Archive, permission restart, and runtime switch must be requested separately from other Agent updates"
|
|
1519
|
+
});
|
|
1520
|
+
return;
|
|
1521
|
+
}
|
|
1522
|
+
const ordinaryPatchGroups = [
|
|
1523
|
+
providedPatchFields.includes("customTitle") ? "customTitle" : "",
|
|
1524
|
+
providedPatchFields.includes("task") ? "task" : "",
|
|
1525
|
+
providedPatchFields.some((field) => [
|
|
1526
|
+
"pinned",
|
|
1527
|
+
"unread",
|
|
1528
|
+
"archived",
|
|
1529
|
+
"readAttentionSeq",
|
|
1530
|
+
"readOutputEpoch",
|
|
1531
|
+
"readOutputSeq"
|
|
1532
|
+
].includes(field)) ? "flags" : ""
|
|
1533
|
+
].filter(Boolean);
|
|
1534
|
+
if (ordinaryPatchGroups.length > 1) {
|
|
1535
|
+
res.status(400).json({
|
|
1536
|
+
error: "Agent title, task, and flags must be updated in separate requests"
|
|
1537
|
+
});
|
|
1538
|
+
return;
|
|
1539
|
+
}
|
|
1540
|
+
await agentManager.whenAgentLifecycleIdle(req.params.agentId);
|
|
1541
|
+
if (typeof body.customTitle === "string") {
|
|
1542
|
+
const result = agentManager.renameAgent(req.params.agentId, body.customTitle);
|
|
1543
|
+
if (result.error) {
|
|
1544
|
+
const status = result.error === "Agent not found" ? 404 : result.error.startsWith("Failed to ") ? 500 : 409;
|
|
1545
|
+
res.status(status).json({ error: result.error });
|
|
1546
|
+
return;
|
|
1547
|
+
}
|
|
1548
|
+
updates.customTitle = result.customTitle;
|
|
1549
|
+
}
|
|
1550
|
+
if (typeof body.task === "string") {
|
|
1551
|
+
const result = agentManager.setAgentTask(req.params.agentId, body.task);
|
|
1552
|
+
if (result.error) {
|
|
1553
|
+
const status = result.error === "Agent not found" ? 404 : result.error.startsWith("Failed to ") ? 500 : 409;
|
|
1554
|
+
res.status(status).json({ error: result.error });
|
|
1555
|
+
return;
|
|
1556
|
+
}
|
|
1557
|
+
updates.task = result.task;
|
|
1558
|
+
}
|
|
1559
|
+
const flagPatch = {};
|
|
1560
|
+
["pinned", "unread", "archived"].forEach((flagName) => {
|
|
1561
|
+
if (typeof body[flagName] === "boolean") {
|
|
1562
|
+
flagPatch[flagName] = body[flagName];
|
|
1563
|
+
}
|
|
1564
|
+
});
|
|
1565
|
+
if (typeof body.readAttentionSeq === "number" && Number.isFinite(body.readAttentionSeq)) {
|
|
1566
|
+
flagPatch.readAttentionSeq = body.readAttentionSeq;
|
|
1567
|
+
}
|
|
1568
|
+
if (typeof body.readOutputEpoch === "string" && body.readOutputEpoch && typeof body.readOutputSeq === "number" && Number.isFinite(body.readOutputSeq)) {
|
|
1569
|
+
flagPatch.readOutputEpoch = body.readOutputEpoch;
|
|
1570
|
+
flagPatch.readOutputSeq = body.readOutputSeq;
|
|
1571
|
+
}
|
|
1572
|
+
if (flagPatch.archived === true) {
|
|
1573
|
+
const result = await agentManager.archiveAgent(req.params.agentId);
|
|
1574
|
+
if (result.error) {
|
|
1575
|
+
const status = result.stopped === true ? 409 : result.error === "Agent not found" ? 404 : 400;
|
|
1576
|
+
res.status(status).json(result);
|
|
1577
|
+
return;
|
|
1578
|
+
}
|
|
1579
|
+
Object.assign(updates, result);
|
|
1580
|
+
delete updates.agentId;
|
|
1581
|
+
delete flagPatch.archived;
|
|
1582
|
+
}
|
|
1583
|
+
let flagUpdateRequiresState = false;
|
|
1584
|
+
if (Object.keys(flagPatch).length > 0) {
|
|
1585
|
+
const result = agentManager.updateAgentFlags(req.params.agentId, flagPatch);
|
|
1586
|
+
if (result.error) {
|
|
1587
|
+
const status = result.error === "Agent not found" ? 404 : result.error.startsWith("Failed to ") ? 500 : 409;
|
|
1588
|
+
res.status(status).json({ error: result.error });
|
|
1589
|
+
return;
|
|
1590
|
+
}
|
|
1591
|
+
Object.assign(updates, result);
|
|
1592
|
+
delete updates.agentId;
|
|
1593
|
+
flagUpdateRequiresState = "requiresState" in result && result.requiresState === true;
|
|
1594
|
+
}
|
|
1595
|
+
if (typeof body.launchPermissionMode === "string") {
|
|
1596
|
+
const result = await agentManager.syncCodexTerminalPermissionMode(req.params.agentId, body.launchPermissionMode);
|
|
1597
|
+
if (result.error) {
|
|
1598
|
+
const status = result.error === "Agent not found" ? 404 : 400;
|
|
1599
|
+
res.status(status).json({ error: result.error });
|
|
1600
|
+
return;
|
|
1601
|
+
}
|
|
1602
|
+
updates.launchPermissionMode = result.launchPermissionMode;
|
|
1603
|
+
if (result.restarted === true) updates.restarted = true;
|
|
1604
|
+
if (result.restartedAgentId) updates.restartedAgentId = result.restartedAgentId;
|
|
1605
|
+
}
|
|
1606
|
+
if (typeof body.agentRuntimeMode === "string") {
|
|
1607
|
+
if (!(0, import_agent_runtime_binding.isAgentRuntimeModeRequest)(body.agentRuntimeMode)) {
|
|
1608
|
+
res.status(400).json({ error: "Unsupported Agent runtime mode" });
|
|
1609
|
+
return;
|
|
1610
|
+
}
|
|
1611
|
+
const result = await agentManager.restartAgentRuntimeMode(req.params.agentId, body.agentRuntimeMode);
|
|
1612
|
+
if (result.error) {
|
|
1613
|
+
const status = result.error === "Agent not found" ? 404 : 400;
|
|
1614
|
+
res.status(status).json({ error: result.error });
|
|
1615
|
+
return;
|
|
1616
|
+
}
|
|
1617
|
+
updates.agentRuntimeMode = result.agentRuntimeMode;
|
|
1618
|
+
if (result.restarted === true) updates.restarted = true;
|
|
1619
|
+
if (result.restartedAgentId) updates.restartedAgentId = result.restartedAgentId;
|
|
1620
|
+
if (result.switchFailed === true) updates.switchFailed = true;
|
|
1621
|
+
if (result.warning) updates.warning = result.warning;
|
|
1622
|
+
}
|
|
1623
|
+
if (Object.keys(updates).length === 0) {
|
|
1624
|
+
res.status(400).json({ error: "customTitle, task, pinned, unread, archived, readAttentionSeq, readOutputEpoch/readOutputSeq, launchPermissionMode, or agentRuntimeMode is required" });
|
|
1625
|
+
return;
|
|
1626
|
+
}
|
|
1627
|
+
if (flagUpdateRequiresState || typeof body.task === "string" || typeof body.customTitle === "string" || typeof body.launchPermissionMode === "string" || typeof body.agentRuntimeMode === "string") {
|
|
1628
|
+
scheduleBroadcastState();
|
|
1629
|
+
}
|
|
1630
|
+
res.json({ agentId: req.params.agentId, ...updates });
|
|
1631
|
+
});
|
|
1632
|
+
app.post((0, import_index_html.routePath)(BASE_PATH, "/api/agents/:agentId/reorder"), express.json(), async (req, res) => {
|
|
1633
|
+
if (!await requireAgentRecoveryForHttp(res)) return;
|
|
1634
|
+
await agentManager.whenAgentLifecycleIdle(req.params.agentId);
|
|
1635
|
+
const result = agentManager.reorderAgent(req.params.agentId, {
|
|
1636
|
+
beforeAgentId: optionalString(req.body?.beforeAgentId),
|
|
1637
|
+
afterAgentId: optionalString(req.body?.afterAgentId)
|
|
1638
|
+
});
|
|
1639
|
+
if (result.error) {
|
|
1640
|
+
const status = result.error === "Agent not found" ? 404 : result.error.startsWith("Failed to ") ? 500 : 409;
|
|
1641
|
+
res.status(status).json({ error: result.error });
|
|
1642
|
+
return;
|
|
1643
|
+
}
|
|
1644
|
+
scheduleBroadcastState();
|
|
1645
|
+
res.json(result);
|
|
1646
|
+
});
|
|
1647
|
+
app.post((0, import_index_html.routePath)(BASE_PATH, "/api/agents/:agentId/fork"), express.json(), async (req, res) => {
|
|
1648
|
+
if (!await requireAgentRecoveryForHttp(res)) return;
|
|
1649
|
+
const mode = req.body && typeof req.body.mode === "string" ? req.body.mode : "same-worktree";
|
|
1650
|
+
if (!isForkMode(mode)) {
|
|
1651
|
+
res.status(400).json({ error: "Unsupported Fork mode" });
|
|
1652
|
+
return;
|
|
1653
|
+
}
|
|
1654
|
+
const targetRuntime = req.body && typeof req.body.targetRuntime === "string" ? req.body.targetRuntime : "";
|
|
1655
|
+
if (targetRuntime && targetRuntime !== "chat") {
|
|
1656
|
+
res.status(400).json({ error: "Unsupported Fork target runtime" });
|
|
1657
|
+
return;
|
|
1658
|
+
}
|
|
1659
|
+
const requestId = typeof req.body?.requestId === "string" ? req.body.requestId.trim() : "";
|
|
1660
|
+
if (!/^[A-Za-z0-9._:-]{1,160}$/.test(requestId)) {
|
|
1661
|
+
res.status(400).json({ error: "Fork requires a valid requestId" });
|
|
1662
|
+
return;
|
|
1663
|
+
}
|
|
1664
|
+
const result = await agentManager.forkAgent(req.params.agentId, mode, {
|
|
1665
|
+
requestId,
|
|
1666
|
+
...targetRuntime === "chat" ? { targetRuntime: "chat" } : {},
|
|
1667
|
+
...Number.isSafeInteger(req.body?.expectedRevision) ? { expectedRevision: optionalNumber(req.body.expectedRevision) } : {}
|
|
1668
|
+
});
|
|
1669
|
+
if (result.error) {
|
|
1670
|
+
const status = result.error === "Agent not found" ? 404 : 400;
|
|
1671
|
+
res.status(status).json(result);
|
|
1672
|
+
return;
|
|
1673
|
+
}
|
|
1674
|
+
try {
|
|
1675
|
+
const membership = configManager.mountProjectWorkspace(result.workspace);
|
|
1676
|
+
result.projectWorkspaces = membership.projectWorkspaces;
|
|
1677
|
+
result.pinnedProjectWorkspaces = membership.pinnedProjectWorkspaces;
|
|
1678
|
+
} catch (caught) {
|
|
1679
|
+
const error = caughtError(caught);
|
|
1680
|
+
broadcastState();
|
|
1681
|
+
const mountError = error.message || "Failed to create Project";
|
|
1682
|
+
res.status(500).json({
|
|
1683
|
+
...result,
|
|
1684
|
+
error: `${mountError}. Retry the same Fork request to reconcile Project membership.`,
|
|
1685
|
+
retryable: true
|
|
1686
|
+
});
|
|
1687
|
+
return;
|
|
1688
|
+
}
|
|
1689
|
+
broadcastState();
|
|
1690
|
+
res.status(201).json(result);
|
|
1691
|
+
});
|
|
1692
|
+
function resolveProjectActionRoot(rootId) {
|
|
1693
|
+
const root = workspaceRootRegistry.resolve(rootId);
|
|
1694
|
+
if (!root || root.kind === "global") {
|
|
1695
|
+
throw new import_workspace_file_service.WorkspaceFileError("project workspace is required", 400);
|
|
1696
|
+
}
|
|
1697
|
+
return root;
|
|
1698
|
+
}
|
|
1699
|
+
app.post((0, import_index_html.routePath)(BASE_PATH, "/api/projects/reveal"), express.json(), async (req, res) => {
|
|
1700
|
+
try {
|
|
1701
|
+
const root = resolveProjectActionRoot(typeof req.body?.rootId === "string" ? req.body.rootId : "");
|
|
1702
|
+
const command = process.platform === "darwin" ? "open" : process.platform === "win32" ? "explorer" : "xdg-open";
|
|
1703
|
+
await execFileAsync(command, [root.canonicalPath], {
|
|
1704
|
+
timeout: 15e3,
|
|
1705
|
+
maxBuffer: 1024 * 1024
|
|
1706
|
+
});
|
|
1707
|
+
res.json({ revealed: true, workspace: root.canonicalPath });
|
|
1708
|
+
} catch (caught) {
|
|
1709
|
+
const error = caughtError(caught);
|
|
1710
|
+
const status = error instanceof import_workspace_file_service.WorkspaceFileError ? error.statusCode || 500 : 500;
|
|
1711
|
+
res.status(status).json({ error: error.message || "Failed to reveal project workspace" });
|
|
1712
|
+
}
|
|
1713
|
+
});
|
|
1714
|
+
app.post((0, import_index_html.routePath)(BASE_PATH, "/api/projects/mount"), express.json(), async (req, res) => {
|
|
1715
|
+
try {
|
|
1716
|
+
const workspace = await canonicalProjectWorkspace(typeof req.body?.workspace === "string" ? req.body.workspace : "");
|
|
1717
|
+
const membership = configManager.mountProjectWorkspace(workspace);
|
|
1718
|
+
broadcastState();
|
|
1719
|
+
res.json(membership);
|
|
1720
|
+
} catch (caught) {
|
|
1721
|
+
const error = caughtError(caught);
|
|
1722
|
+
res.status(400).json({ error: error.message || "Failed to create Project" });
|
|
1723
|
+
}
|
|
1724
|
+
});
|
|
1725
|
+
app.post((0, import_index_html.routePath)(BASE_PATH, "/api/projects/remove"), express.json(), (req, res) => {
|
|
1726
|
+
try {
|
|
1727
|
+
const membership = configManager.removeProjectWorkspace(req.body?.workspace);
|
|
1728
|
+
broadcastState();
|
|
1729
|
+
res.json(membership);
|
|
1730
|
+
} catch (caught) {
|
|
1731
|
+
const error = caughtError(caught);
|
|
1732
|
+
res.status(400).json({ error: error.message || "Failed to remove Project" });
|
|
1733
|
+
}
|
|
1734
|
+
});
|
|
1735
|
+
app.post((0, import_index_html.routePath)(BASE_PATH, "/api/projects/pin"), express.json(), (req, res) => {
|
|
1736
|
+
try {
|
|
1737
|
+
const membership = configManager.setProjectWorkspacePinned(
|
|
1738
|
+
req.body?.workspace,
|
|
1739
|
+
req.body?.pinned === true
|
|
1740
|
+
);
|
|
1741
|
+
broadcastState();
|
|
1742
|
+
res.json(membership);
|
|
1743
|
+
} catch (caught) {
|
|
1744
|
+
const error = caughtError(caught);
|
|
1745
|
+
res.status(400).json({ error: error.message || "Failed to update Project pin" });
|
|
1746
|
+
}
|
|
1747
|
+
});
|
|
1748
|
+
app.patch((0, import_index_html.routePath)(BASE_PATH, "/api/projects/name"), express.json(), (req, res) => {
|
|
1749
|
+
try {
|
|
1750
|
+
const result = configManager.setProjectName(req.body?.workspace, req.body?.name);
|
|
1751
|
+
scheduleBroadcastState();
|
|
1752
|
+
res.json(result);
|
|
1753
|
+
} catch (caught) {
|
|
1754
|
+
const error = caughtError(caught);
|
|
1755
|
+
res.status(400).json({ error: error.message || "Failed to rename Project" });
|
|
1756
|
+
}
|
|
1757
|
+
});
|
|
1758
|
+
app.post((0, import_index_html.routePath)(BASE_PATH, "/api/projects/create-worktree"), express.json(), async (req, res) => {
|
|
1759
|
+
const requestId = typeof req.body?.requestId === "string" ? req.body.requestId.trim() : "";
|
|
1760
|
+
if (!/^[A-Za-z0-9._:-]{1,160}$/.test(requestId)) {
|
|
1761
|
+
res.status(400).json({ error: "Project worktree creation requires a valid requestId" });
|
|
1762
|
+
return;
|
|
1763
|
+
}
|
|
1764
|
+
try {
|
|
1765
|
+
const root = resolveProjectActionRoot(typeof req.body?.rootId === "string" ? req.body.rootId : "");
|
|
1766
|
+
const created = await agentManager.createPermanentWorktree(root.canonicalPath, { requestId });
|
|
1767
|
+
if (!isRecord(created)) throw new Error("Project worktree creation returned an invalid result");
|
|
1768
|
+
broadcastState();
|
|
1769
|
+
res.status(201).json({
|
|
1770
|
+
...created
|
|
1771
|
+
});
|
|
1772
|
+
} catch (caught) {
|
|
1773
|
+
const error = caughtError(caught);
|
|
1774
|
+
const status = error instanceof import_workspace_file_service.WorkspaceFileError ? error.statusCode || 400 : 400;
|
|
1775
|
+
const operation = configManager.getProjectOperation?.(requestId);
|
|
1776
|
+
res.status(status).json({
|
|
1777
|
+
error: error.message || "Failed to create permanent worktree",
|
|
1778
|
+
requestId,
|
|
1779
|
+
...operation?.state === "pending" ? { retryable: true } : {},
|
|
1780
|
+
...["unknown", "blocked"].includes(operation?.state || "") ? { uncertain: true } : {}
|
|
1781
|
+
});
|
|
1782
|
+
}
|
|
1783
|
+
});
|
|
1784
|
+
app.post((0, import_index_html.routePath)(BASE_PATH, "/api/projects/delete-worktree"), express.json(), async (req, res) => {
|
|
1785
|
+
if (!await requireAgentRecoveryForHttp(res)) return;
|
|
1786
|
+
const body = req.body || {};
|
|
1787
|
+
const workspace = typeof body.workspace === "string" ? body.workspace : "";
|
|
1788
|
+
const requestId = typeof body.requestId === "string" ? body.requestId.trim() : "";
|
|
1789
|
+
if (!/^[A-Za-z0-9._:-]{1,160}$/.test(requestId)) {
|
|
1790
|
+
res.status(400).json({ error: "Project worktree deletion requires a valid requestId" });
|
|
1791
|
+
return;
|
|
1792
|
+
}
|
|
1793
|
+
const result = await agentManager.deleteForkWorktreeProject(workspace, {
|
|
1794
|
+
force: body.force === true,
|
|
1795
|
+
requestId
|
|
1796
|
+
});
|
|
1797
|
+
if (result.error) {
|
|
1798
|
+
broadcastState();
|
|
1799
|
+
const status = result.requiresForce ? 409 : result.error === "Workspace not found" || result.error === "Workspace is required" ? 404 : 400;
|
|
1800
|
+
res.status(status).json(result);
|
|
1801
|
+
return;
|
|
1802
|
+
}
|
|
1803
|
+
broadcastState();
|
|
1804
|
+
res.json(result);
|
|
1805
|
+
});
|
|
1806
|
+
app.post((0, import_index_html.routePath)(BASE_PATH, "/api/codex/sessions/:sessionId/resume"), express.json(), async (req, res) => {
|
|
1807
|
+
await startResumedAgentSession(req, res, "codex", req.params.sessionId);
|
|
1808
|
+
});
|
|
1809
|
+
app.post((0, import_index_html.routePath)(BASE_PATH, "/api/agent-sessions/:provider/:sessionId/resume"), express.json(), async (req, res) => {
|
|
1810
|
+
await startResumedAgentSession(req, res, req.params.provider, req.params.sessionId);
|
|
1811
|
+
});
|
|
1812
|
+
const pendingResumeStarts = /* @__PURE__ */ new Map();
|
|
1813
|
+
const pendingProjectWorkspaceResolutions = /* @__PURE__ */ new Map();
|
|
1814
|
+
function resumedAgentStartKey(provider, sessionId, options = {}) {
|
|
1815
|
+
return [
|
|
1816
|
+
provider,
|
|
1817
|
+
options.providerHomeId || "default",
|
|
1818
|
+
sessionId,
|
|
1819
|
+
options.fork === true ? "fork" : "resume",
|
|
1820
|
+
options.asMain === true ? "main" : "agent"
|
|
1821
|
+
].join(":");
|
|
1822
|
+
}
|
|
1823
|
+
function findResumedAgent(provider, sessionId, providerHomeId = "") {
|
|
1824
|
+
return (0, import_main_page_session.findActiveAgentClaimingSession)(agentManager.getState().agents, provider, { id: sessionId, providerHomeId });
|
|
1825
|
+
}
|
|
1826
|
+
function isMainAgentSessionWorkspace(session) {
|
|
1827
|
+
const values = [session?.cwd, session?.workspace];
|
|
1828
|
+
return values.some((value) => {
|
|
1829
|
+
const normalized = String(value || "").trim().replace(/[\\/]+$/, "");
|
|
1830
|
+
return normalized === "~/.farming" || /(^|[/\\])\.farming$/.test(normalized);
|
|
1831
|
+
});
|
|
1832
|
+
}
|
|
1833
|
+
function rememberMainPageAgentSession(provider, sessionId, providerHomeId = "") {
|
|
1834
|
+
const sessionKey = (0, import_main_page_session.mainPageAgentSessionKey)(provider, sessionId, providerHomeId);
|
|
1835
|
+
if (typeof configManager.rememberMainPageSessionKey === "function") {
|
|
1836
|
+
configManager.rememberMainPageSessionKey(sessionKey, {
|
|
1837
|
+
provider,
|
|
1838
|
+
providerSessionId: sessionId,
|
|
1839
|
+
providerSessionKey: sessionKey,
|
|
1840
|
+
providerHomeId: providerHomeId || "default",
|
|
1841
|
+
source: "resume"
|
|
1842
|
+
});
|
|
1843
|
+
return;
|
|
1844
|
+
}
|
|
1845
|
+
const currentKeys = typeof configManager.getMainPageSessionKeys === "function" ? configManager.getMainPageSessionKeys() : Array.isArray(configManager.getSettings().mainPageSessionKeys) ? configManager.getSettings().mainPageSessionKeys : [];
|
|
1846
|
+
configManager.updateSettings({
|
|
1847
|
+
mainPageSessionKeys: [
|
|
1848
|
+
sessionKey,
|
|
1849
|
+
...currentKeys.filter((key) => key !== sessionKey)
|
|
1850
|
+
]
|
|
1851
|
+
});
|
|
1852
|
+
}
|
|
1853
|
+
function forgetMainPageAgentSession(provider, sessionId, providerHomeId = "") {
|
|
1854
|
+
const sessionKey = (0, import_main_page_session.mainPageAgentSessionKey)(provider, sessionId, providerHomeId);
|
|
1855
|
+
if (typeof configManager.removeMainPageSessionKey === "function") {
|
|
1856
|
+
configManager.removeMainPageSessionKey(sessionKey);
|
|
1857
|
+
return;
|
|
1858
|
+
}
|
|
1859
|
+
const currentKeys = typeof configManager.getMainPageSessionKeys === "function" ? configManager.getMainPageSessionKeys() : Array.isArray(configManager.getSettings().mainPageSessionKeys) ? configManager.getSettings().mainPageSessionKeys : [];
|
|
1860
|
+
if (!currentKeys.includes(sessionKey)) return;
|
|
1861
|
+
configManager.updateSettings({
|
|
1862
|
+
mainPageSessionKeys: currentKeys.filter((key) => key !== sessionKey)
|
|
1863
|
+
});
|
|
1864
|
+
}
|
|
1865
|
+
function savedFarmingAgentSession(provider, sessionId, providerHomeId = "") {
|
|
1866
|
+
if (typeof configManager.getAgentSessionRecordForProviderSessionKey !== "function") return null;
|
|
1867
|
+
const sessionKey = (0, import_main_page_session.mainPageAgentSessionKey)(provider, sessionId, providerHomeId);
|
|
1868
|
+
return sessionKey ? configManager.getAgentSessionRecordForProviderSessionKey(sessionKey) : null;
|
|
1869
|
+
}
|
|
1870
|
+
async function resumeAgentSessionById(provider, rawSessionId, options = {}) {
|
|
1871
|
+
const normalizedProvider = (0, import_agent_session_history.normalizeProvider)(provider);
|
|
1872
|
+
const sessionId = String(rawSessionId || "").trim();
|
|
1873
|
+
const providerHomeId = typeof options.providerHomeId === "string" && options.providerHomeId.trim() ? options.providerHomeId.trim() : "default";
|
|
1874
|
+
if (!normalizedProvider || !(0, import_agent_session_history.isSafeSessionId)(sessionId)) {
|
|
1875
|
+
return { error: "invalid session id", status: 400 };
|
|
1876
|
+
}
|
|
1877
|
+
const shouldFork = options.fork === true;
|
|
1878
|
+
const requestedAsMain = options.asMain === true && !shouldFork;
|
|
1879
|
+
const shouldRememberMainPageSession = options.rememberMainPageSession !== false && !shouldFork && !requestedAsMain;
|
|
1880
|
+
const pendingResumeId = resumedAgentStartKey(normalizedProvider, sessionId, {
|
|
1881
|
+
fork: shouldFork,
|
|
1882
|
+
asMain: requestedAsMain,
|
|
1883
|
+
providerHomeId
|
|
1884
|
+
});
|
|
1885
|
+
if (!shouldFork) {
|
|
1886
|
+
const existingAgent = findResumedAgent(normalizedProvider, sessionId, providerHomeId);
|
|
1887
|
+
if (existingAgent) {
|
|
1888
|
+
if (shouldRememberMainPageSession) rememberMainPageAgentSession(normalizedProvider, sessionId, providerHomeId);
|
|
1889
|
+
const projectWorkspace = requestedAsMain ? "" : await canonicalProjectWorkspace(
|
|
1890
|
+
existingAgent.gitWorktree?.workspace || existingAgent.projectWorkspace || existingAgent.cwd || null
|
|
1891
|
+
);
|
|
1892
|
+
return { agentId: existingAgent.id, projectWorkspace, reused: true };
|
|
1893
|
+
}
|
|
1894
|
+
}
|
|
1895
|
+
const pendingStart = pendingResumeStarts.get(pendingResumeId);
|
|
1896
|
+
if (pendingStart) {
|
|
1897
|
+
const result2 = await pendingStart;
|
|
1898
|
+
if (result2.error) {
|
|
1899
|
+
return result2;
|
|
1900
|
+
}
|
|
1901
|
+
if (shouldRememberMainPageSession) rememberMainPageAgentSession(normalizedProvider, sessionId, providerHomeId);
|
|
1902
|
+
return {
|
|
1903
|
+
agentId: result2.agentId,
|
|
1904
|
+
projectWorkspace: result2.projectWorkspace || "",
|
|
1905
|
+
reused: true,
|
|
1906
|
+
pending: true,
|
|
1907
|
+
...result2.claimed ? { claimed: true } : {}
|
|
1908
|
+
};
|
|
1909
|
+
}
|
|
1910
|
+
const startPromise = (async () => {
|
|
1911
|
+
let session = await (0, import_agent_session_history.findAgentSession)(normalizedProvider, sessionId, {
|
|
1912
|
+
limit: 1e3,
|
|
1913
|
+
providerLimit: 1e3,
|
|
1914
|
+
scanLimit: 5e3,
|
|
1915
|
+
providerHomeId,
|
|
1916
|
+
providerHomes: configuredProviderHomes()
|
|
1917
|
+
});
|
|
1918
|
+
if (options.allowUnarchiveArchived === true && normalizedProvider === "codex" && !requestedAsMain) {
|
|
1919
|
+
const providerHomes = configuredProviderHomes();
|
|
1920
|
+
const configuredHomePath = (providerHomes.codex || []).find((home) => home.id === providerHomeId)?.path || "";
|
|
1921
|
+
const unarchiveResult = await agentManager.ensureCodexSessionAvailable(sessionId, {
|
|
1922
|
+
providerHomeId,
|
|
1923
|
+
providerHomePath: session?.providerHomePath || configuredHomePath,
|
|
1924
|
+
providerHomes,
|
|
1925
|
+
cwd: session?.cwd || session?.workspace || ""
|
|
1926
|
+
});
|
|
1927
|
+
if (unarchiveResult?.error) return unarchiveResult;
|
|
1928
|
+
session = await (0, import_agent_session_history.findAgentSession)(normalizedProvider, sessionId, {
|
|
1929
|
+
limit: 1e3,
|
|
1930
|
+
providerLimit: 1e3,
|
|
1931
|
+
scanLimit: 5e3,
|
|
1932
|
+
providerHomeId,
|
|
1933
|
+
providerHomes
|
|
1934
|
+
}) || (session ? { ...session, archived: false } : session);
|
|
1935
|
+
}
|
|
1936
|
+
if (session && session.archived && !shouldFork) {
|
|
1937
|
+
forgetMainPageAgentSession(normalizedProvider, sessionId, providerHomeId);
|
|
1938
|
+
return {
|
|
1939
|
+
error: `${session.providerName || normalizedProvider} session is archived. Unarchive it before resuming.`,
|
|
1940
|
+
status: 409,
|
|
1941
|
+
archived: true
|
|
1942
|
+
};
|
|
1943
|
+
}
|
|
1944
|
+
if (!shouldFork && !requestedAsMain) {
|
|
1945
|
+
const claimingAgent = (0, import_main_page_session.findActiveAgentClaimingSession)(agentManager.getState().agents, normalizedProvider, {
|
|
1946
|
+
id: sessionId,
|
|
1947
|
+
providerHomeId,
|
|
1948
|
+
...session || {}
|
|
1949
|
+
});
|
|
1950
|
+
if (claimingAgent) {
|
|
1951
|
+
if (shouldRememberMainPageSession) rememberMainPageAgentSession(normalizedProvider, sessionId, providerHomeId);
|
|
1952
|
+
const projectWorkspace2 = await canonicalProjectWorkspace(
|
|
1953
|
+
claimingAgent.gitWorktree?.workspace || claimingAgent.projectWorkspace || claimingAgent.cwd || null
|
|
1954
|
+
);
|
|
1955
|
+
return { agentId: claimingAgent.id, projectWorkspace: projectWorkspace2, reused: true, claimed: true };
|
|
1956
|
+
}
|
|
1957
|
+
}
|
|
1958
|
+
const resumeAsMain = requestedAsMain && isMainAgentSessionWorkspace(session);
|
|
1959
|
+
if (requestedAsMain && !resumeAsMain) {
|
|
1960
|
+
return { error: "session is not a Main Agent session", status: 400 };
|
|
1961
|
+
}
|
|
1962
|
+
const savedSession = shouldFork ? null : savedFarmingAgentSession(normalizedProvider, sessionId, session ? session.providerHomeId || providerHomeId : providerHomeId);
|
|
1963
|
+
const hasRequestedCustomTitle = Object.prototype.hasOwnProperty.call(options, "customTitle");
|
|
1964
|
+
const savedAttentionSeq = Number(savedSession?.attentionSeq) || 0;
|
|
1965
|
+
const savedReadAttentionSeq = Number(savedSession?.readAttentionSeq) || 0;
|
|
1966
|
+
const workingDirectory = session?.cwd || session?.workspace || null;
|
|
1967
|
+
const projectWorkspace = resumeAsMain ? "" : await canonicalProjectWorkspace(
|
|
1968
|
+
String(savedSession?.projectWorkspace || (session ? session.workspace || session.cwd || "" : workingDirectory) || "")
|
|
1969
|
+
);
|
|
1970
|
+
const command = (0, import_agent_session_history.buildAgentSessionResumeCommand)(normalizedProvider, sessionId, {
|
|
1971
|
+
fork: shouldFork,
|
|
1972
|
+
cwd: workingDirectory,
|
|
1973
|
+
modelProvider: normalizedProvider === "codex" ? (0, import_agent_session_history.resolveCodexResumeModelProvider)(session?.providerHomePath || "") : ""
|
|
1974
|
+
});
|
|
1975
|
+
if (!command) {
|
|
1976
|
+
return { error: "invalid session id", status: 400 };
|
|
1977
|
+
}
|
|
1978
|
+
return new Promise((resolve) => {
|
|
1979
|
+
const resolvedProviderHomeId = session ? session.providerHomeId || providerHomeId : providerHomeId;
|
|
1980
|
+
const resumeSource = (0, import_main_page_session.resumedAgentSource)(normalizedProvider, sessionId, resolvedProviderHomeId);
|
|
1981
|
+
const startResult = agentManager.startAgent(command, workingDirectory, (agentId, error) => {
|
|
1982
|
+
if (error) {
|
|
1983
|
+
resolve({ error, status: 400 });
|
|
1984
|
+
return;
|
|
1985
|
+
}
|
|
1986
|
+
if (!agentId) {
|
|
1987
|
+
resolve({ error: "failed to resume agent session", status: 500 });
|
|
1988
|
+
return;
|
|
1989
|
+
}
|
|
1990
|
+
resolve({ agentId, projectWorkspace });
|
|
1991
|
+
}, {
|
|
1992
|
+
wantsMain: resumeAsMain,
|
|
1993
|
+
task: savedSession?.task || (session ? session.title : ""),
|
|
1994
|
+
workflowTemplate: savedSession?.workflowTemplate || "",
|
|
1995
|
+
customTitle: hasRequestedCustomTitle ? typeof options.customTitle === "string" ? options.customTitle : "" : savedSession?.customTitle || "",
|
|
1996
|
+
customTitleExplicit: hasRequestedCustomTitle,
|
|
1997
|
+
requiredCliVersion: normalizedProvider === "codex" && session ? session.cliVersion : "",
|
|
1998
|
+
projectWorkspace,
|
|
1999
|
+
source: shouldFork ? resumeSource.replace("-history:", "-history-fork:") : resumeSource,
|
|
2000
|
+
agentRuntimeMode: typeof options.agentRuntimeMode === "string" && ["chat", "acp"].includes(options.agentRuntimeMode) ? "chat" : "terminal",
|
|
2001
|
+
acpHistoryMode: options.acpHistoryMode === "resume" ? "resume" : "load",
|
|
2002
|
+
providerHomeId: resolvedProviderHomeId,
|
|
2003
|
+
providerHomePath: session ? session.providerHomePath || "" : "",
|
|
2004
|
+
providerSessionTitle: session?.title || savedSession?.providerSessionTitle || "",
|
|
2005
|
+
persistentSessionId: savedSession?.id || "",
|
|
2006
|
+
pinned: savedSession?.pinned === true,
|
|
2007
|
+
projectOrder: savedSession?.projectOrder,
|
|
2008
|
+
pinnedOrder: savedSession?.pinnedOrder,
|
|
2009
|
+
attentionSeq: savedAttentionSeq,
|
|
2010
|
+
readAttentionSeq: savedReadAttentionSeq,
|
|
2011
|
+
attentionUpdatedAt: savedSession?.attentionUpdatedAt,
|
|
2012
|
+
readAttentionAt: savedSession?.readAttentionAt,
|
|
2013
|
+
attentionReason: savedSession?.attentionReason,
|
|
2014
|
+
attentionOutputEpoch: savedSession?.attentionOutputEpoch,
|
|
2015
|
+
attentionOutputSeq: savedSession?.attentionOutputSeq,
|
|
2016
|
+
readOutputEpoch: savedSession?.readOutputEpoch,
|
|
2017
|
+
readOutputSeq: savedSession?.readOutputSeq,
|
|
2018
|
+
autoReadInitialAttention: options.autoReadInitialAttention === true && savedAttentionSeq <= savedReadAttentionSeq,
|
|
2019
|
+
preserveProviderSessionProfile: normalizedProvider === "codex" || normalizedProvider === "claude"
|
|
2020
|
+
});
|
|
2021
|
+
Promise.resolve(startResult).catch((error) => {
|
|
2022
|
+
resolve({ error: error.message || "failed to resume agent session", status: 500 });
|
|
2023
|
+
});
|
|
2024
|
+
});
|
|
2025
|
+
})();
|
|
2026
|
+
pendingResumeStarts.set(pendingResumeId, startPromise);
|
|
2027
|
+
const result = await startPromise;
|
|
2028
|
+
if (pendingResumeStarts.get(pendingResumeId) === startPromise) {
|
|
2029
|
+
pendingResumeStarts.delete(pendingResumeId);
|
|
2030
|
+
}
|
|
2031
|
+
if (result.error) {
|
|
2032
|
+
return result;
|
|
2033
|
+
}
|
|
2034
|
+
if (shouldRememberMainPageSession) rememberMainPageAgentSession(normalizedProvider, sessionId, providerHomeId);
|
|
2035
|
+
if (result.reused) {
|
|
2036
|
+
return {
|
|
2037
|
+
agentId: result.agentId,
|
|
2038
|
+
projectWorkspace: result.projectWorkspace || "",
|
|
2039
|
+
reused: true,
|
|
2040
|
+
...result.claimed ? { claimed: true } : {}
|
|
2041
|
+
};
|
|
2042
|
+
}
|
|
2043
|
+
return {
|
|
2044
|
+
agentId: result.agentId,
|
|
2045
|
+
projectWorkspace: result.projectWorkspace || ""
|
|
2046
|
+
};
|
|
2047
|
+
}
|
|
2048
|
+
async function canonicalProjectWorkspace(workspace) {
|
|
2049
|
+
const candidate = configManager.expandWorkspacePath(String(workspace || "").trim());
|
|
2050
|
+
if (!candidate) return "";
|
|
2051
|
+
const pending = pendingProjectWorkspaceResolutions.get(candidate);
|
|
2052
|
+
if (pending) return pending;
|
|
2053
|
+
const resolution = (async () => {
|
|
2054
|
+
try {
|
|
2055
|
+
const worktree = await (0, import_git_worktree_info.inspectGitWorktree)(candidate);
|
|
2056
|
+
if (worktree?.workspace) return worktree.workspace;
|
|
2057
|
+
} catch (caught) {
|
|
2058
|
+
const error = caughtError(caught);
|
|
2059
|
+
console.warn("Failed to resolve project worktree:", candidate, error?.message || error);
|
|
2060
|
+
}
|
|
2061
|
+
try {
|
|
2062
|
+
return fs.realpathSync(path.resolve(candidate));
|
|
2063
|
+
} catch {
|
|
2064
|
+
return candidate;
|
|
2065
|
+
}
|
|
2066
|
+
})();
|
|
2067
|
+
pendingProjectWorkspaceResolutions.set(candidate, resolution);
|
|
2068
|
+
try {
|
|
2069
|
+
return await resolution;
|
|
2070
|
+
} finally {
|
|
2071
|
+
if (pendingProjectWorkspaceResolutions.get(candidate) === resolution) {
|
|
2072
|
+
pendingProjectWorkspaceResolutions.delete(candidate);
|
|
2073
|
+
}
|
|
2074
|
+
}
|
|
2075
|
+
}
|
|
2076
|
+
async function startResumedAgentSession(req, res, provider, rawSessionId) {
|
|
2077
|
+
const shouldFork = req.body && req.body.fork === true;
|
|
2078
|
+
const requestedAsMain = req.body && req.body.asMain === true && !shouldFork;
|
|
2079
|
+
const allowUnarchiveArchived = req.body && req.body.unarchiveArchived === true && !shouldFork && !requestedAsMain;
|
|
2080
|
+
const providerHomeId = req.body && typeof req.body.providerHomeId === "string" ? req.body.providerHomeId : "";
|
|
2081
|
+
const sessionKey = (0, import_main_page_session.mainPageAgentSessionKey)(provider, rawSessionId, providerHomeId);
|
|
2082
|
+
const mainPageSessionWasRemembered = !shouldFork && !requestedAsMain && configManager.getMainPageSessionKeys().includes(sessionKey);
|
|
2083
|
+
if (req.body && Object.prototype.hasOwnProperty.call(req.body, "customTitle") && typeof req.body.customTitle !== "string") {
|
|
2084
|
+
res.status(400).json({ error: "customTitle must be a string" });
|
|
2085
|
+
return;
|
|
2086
|
+
}
|
|
2087
|
+
const result = await resumeAgentSessionById(provider, rawSessionId, {
|
|
2088
|
+
fork: shouldFork,
|
|
2089
|
+
asMain: requestedAsMain,
|
|
2090
|
+
allowUnarchiveArchived,
|
|
2091
|
+
providerHomeId,
|
|
2092
|
+
...req.body && Object.prototype.hasOwnProperty.call(req.body, "customTitle") ? { customTitle: req.body.customTitle } : {},
|
|
2093
|
+
agentRuntimeMode: typeof req.body?.agentRuntimeMode === "string" && ["chat", "acp"].includes(req.body.agentRuntimeMode) ? "chat" : "terminal",
|
|
2094
|
+
acpHistoryMode: req.body && req.body.acpHistoryMode === "resume" ? "resume" : "load"
|
|
2095
|
+
});
|
|
2096
|
+
if (result.error) {
|
|
2097
|
+
res.status(result.status || 400).json({ error: result.error });
|
|
2098
|
+
return;
|
|
2099
|
+
}
|
|
2100
|
+
if (!result.agentId) {
|
|
2101
|
+
res.status(500).json({ error: "Agent session resume returned no Agent identity" });
|
|
2102
|
+
return;
|
|
2103
|
+
}
|
|
2104
|
+
let projectMembership = {
|
|
2105
|
+
projectWorkspaces: configManager.getSettings().projectWorkspaces || [],
|
|
2106
|
+
pinnedProjectWorkspaces: configManager.getSettings().pinnedProjectWorkspaces || []
|
|
2107
|
+
};
|
|
2108
|
+
try {
|
|
2109
|
+
if (!requestedAsMain && result.projectWorkspace) {
|
|
2110
|
+
projectMembership = configManager.mountProjectWorkspace(result.projectWorkspace);
|
|
2111
|
+
}
|
|
2112
|
+
} catch (caught) {
|
|
2113
|
+
const error = caughtError(caught);
|
|
2114
|
+
let rollbackError = "";
|
|
2115
|
+
if (!result.reused) {
|
|
2116
|
+
try {
|
|
2117
|
+
const rollback = await agentManager.archiveAgent(result.agentId, {
|
|
2118
|
+
reason: "project-mount-failed",
|
|
2119
|
+
recordHistory: false,
|
|
2120
|
+
requireEngineExit: true,
|
|
2121
|
+
scheduleProviderArchive: false
|
|
2122
|
+
});
|
|
2123
|
+
if (rollback?.error) rollbackError = rollback.error;
|
|
2124
|
+
} catch (cleanupError) {
|
|
2125
|
+
rollbackError = caughtError(cleanupError).message || String(cleanupError);
|
|
2126
|
+
}
|
|
2127
|
+
}
|
|
2128
|
+
if (!mainPageSessionWasRemembered && !rollbackError) {
|
|
2129
|
+
forgetMainPageAgentSession(provider, rawSessionId, providerHomeId || "default");
|
|
2130
|
+
}
|
|
2131
|
+
broadcastState();
|
|
2132
|
+
const mountError = error.message || "Failed to create Project";
|
|
2133
|
+
res.status(500).json({
|
|
2134
|
+
error: rollbackError ? `${mountError}. Rollback failed: ${rollbackError}` : mountError,
|
|
2135
|
+
...rollbackError ? { rollbackError } : {}
|
|
2136
|
+
});
|
|
2137
|
+
return;
|
|
2138
|
+
}
|
|
2139
|
+
broadcastState();
|
|
2140
|
+
if (result.reused) {
|
|
2141
|
+
res.status(200).json({
|
|
2142
|
+
agentId: result.agentId,
|
|
2143
|
+
...result.projectWorkspace ? { projectWorkspace: result.projectWorkspace } : {},
|
|
2144
|
+
projectWorkspaces: projectMembership.projectWorkspaces,
|
|
2145
|
+
pinnedProjectWorkspaces: projectMembership.pinnedProjectWorkspaces,
|
|
2146
|
+
reused: true,
|
|
2147
|
+
...result.claimed ? { claimed: true } : {},
|
|
2148
|
+
...result.pending ? { pending: true } : {}
|
|
2149
|
+
});
|
|
2150
|
+
return;
|
|
2151
|
+
}
|
|
2152
|
+
res.status(201).json({
|
|
2153
|
+
agentId: result.agentId,
|
|
2154
|
+
...result.projectWorkspace ? { projectWorkspace: result.projectWorkspace } : {},
|
|
2155
|
+
projectWorkspaces: projectMembership.projectWorkspaces,
|
|
2156
|
+
pinnedProjectWorkspaces: projectMembership.pinnedProjectWorkspaces
|
|
2157
|
+
});
|
|
2158
|
+
}
|
|
2159
|
+
async function autoResumeMainPageAgentSessions() {
|
|
2160
|
+
if (typeof agentManager.whenRecovered === "function") {
|
|
2161
|
+
await agentManager.whenRecovered();
|
|
2162
|
+
}
|
|
2163
|
+
const sessions = (0, import_main_page_session.mainPageAgentSessionsToAutoResume)(configManager.getSettings());
|
|
2164
|
+
if (sessions.length === 0) return;
|
|
2165
|
+
let knownSessions;
|
|
2166
|
+
try {
|
|
2167
|
+
knownSessions = await currentAgentSessions();
|
|
2168
|
+
} catch (caught) {
|
|
2169
|
+
const error = caughtError(caught);
|
|
2170
|
+
console.warn("Failed to load Agent session catalog for auto-resume:", error && (error.message || error));
|
|
2171
|
+
return;
|
|
2172
|
+
}
|
|
2173
|
+
const knownSessionByKey = new Map(knownSessions.map((session) => [
|
|
2174
|
+
(0, import_main_page_session.mainPageAgentSessionKey)(session.provider, session.id, session.providerHomeId || "default"),
|
|
2175
|
+
session
|
|
2176
|
+
]));
|
|
2177
|
+
let resumedCount = 0;
|
|
2178
|
+
for (const session of sessions) {
|
|
2179
|
+
try {
|
|
2180
|
+
const sessionDetails = knownSessionByKey.get((0, import_main_page_session.mainPageAgentSessionKey)(
|
|
2181
|
+
session.provider,
|
|
2182
|
+
session.sessionId,
|
|
2183
|
+
session.providerHomeId || "default"
|
|
2184
|
+
));
|
|
2185
|
+
if (!sessionDetails) {
|
|
2186
|
+
console.warn("Dropping stale main-page session from auto-resume:", session.provider, session.sessionId);
|
|
2187
|
+
forgetMainPageAgentSession(session.provider, session.sessionId, session.providerHomeId || "default");
|
|
2188
|
+
continue;
|
|
2189
|
+
}
|
|
2190
|
+
const claimingAgent = (0, import_main_page_session.findActiveAgentClaimingSession)(agentManager.getState().agents, session.provider, {
|
|
2191
|
+
id: session.sessionId,
|
|
2192
|
+
...sessionDetails || {}
|
|
2193
|
+
});
|
|
2194
|
+
if (claimingAgent) {
|
|
2195
|
+
continue;
|
|
2196
|
+
}
|
|
2197
|
+
const result = await resumeAgentSessionById(session.provider, session.sessionId, {
|
|
2198
|
+
rememberMainPageSession: false,
|
|
2199
|
+
providerHomeId: session.providerHomeId || "default",
|
|
2200
|
+
autoReadInitialAttention: true
|
|
2201
|
+
});
|
|
2202
|
+
if (result.error) {
|
|
2203
|
+
const message = String(result.error || "").toLowerCase();
|
|
2204
|
+
if (session.provider === "qoder" && message.includes("invalid session identifier")) {
|
|
2205
|
+
console.warn("Dropping stale qoder session from auto-resume:", session.provider, session.sessionId, result.error);
|
|
2206
|
+
forgetMainPageAgentSession(session.provider, session.sessionId, session.providerHomeId || "default");
|
|
2207
|
+
continue;
|
|
2208
|
+
}
|
|
2209
|
+
console.warn("Failed to auto-resume main page agent session:", session.provider, session.sessionId, result.error);
|
|
2210
|
+
} else {
|
|
2211
|
+
resumedCount += 1;
|
|
2212
|
+
}
|
|
2213
|
+
} catch (caught) {
|
|
2214
|
+
const error = caughtError(caught);
|
|
2215
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2216
|
+
console.warn("Failed to auto-resume main page agent session:", session.provider, session.sessionId, message);
|
|
2217
|
+
}
|
|
2218
|
+
}
|
|
2219
|
+
if (resumedCount > 0) {
|
|
2220
|
+
broadcastState();
|
|
2221
|
+
}
|
|
2222
|
+
}
|
|
2223
|
+
app.post((0, import_index_html.routePath)(BASE_PATH, "/api/settings"), express.json(), async (req, res) => {
|
|
2224
|
+
const settingsPatch = { ...req.body || {} };
|
|
2225
|
+
delete settingsPatch.mainPageSessionKeys;
|
|
2226
|
+
delete settingsPatch.projectWorkspaces;
|
|
2227
|
+
delete settingsPatch.pinnedProjectWorkspaces;
|
|
2228
|
+
const browserConfigurationKeys = [
|
|
2229
|
+
"browserSource",
|
|
2230
|
+
"browserExecutablePath",
|
|
2231
|
+
"browserExternalCdpUrl"
|
|
2232
|
+
];
|
|
2233
|
+
const computerConfigurationKeys = [
|
|
2234
|
+
"computerImage",
|
|
2235
|
+
"computerCompatibilityMode"
|
|
2236
|
+
];
|
|
2237
|
+
const currentSettings = configManager.getSettings();
|
|
2238
|
+
const requestsIsolatedBrowser = (Object.prototype.hasOwnProperty.call(settingsPatch, "browserExtensionEnabled") ? settingsPatch.browserExtensionEnabled === true : currentSettings.browserExtensionEnabled === true) && (optionalString(settingsPatch.browserSource) ?? currentSettings.browserSource) === "isolated";
|
|
2239
|
+
if (requestsIsolatedBrowser) {
|
|
2240
|
+
settingsPatch.computerExtensionEnabled = true;
|
|
2241
|
+
}
|
|
2242
|
+
const changesAgentHomes = Object.prototype.hasOwnProperty.call(settingsPatch, "agentHomes");
|
|
2243
|
+
const changesBrowserExtension = Object.prototype.hasOwnProperty.call(settingsPatch, "browserExtensionEnabled");
|
|
2244
|
+
const changesBrowserConfiguration = browserConfigurationKeys.some(
|
|
2245
|
+
(key) => Object.prototype.hasOwnProperty.call(settingsPatch, key)
|
|
2246
|
+
);
|
|
2247
|
+
let changesComputerExtension = Object.prototype.hasOwnProperty.call(settingsPatch, "computerExtensionEnabled");
|
|
2248
|
+
const changesComputerConfiguration = computerConfigurationKeys.some(
|
|
2249
|
+
(key) => Object.prototype.hasOwnProperty.call(settingsPatch, key)
|
|
2250
|
+
);
|
|
2251
|
+
const browserExtensionEnabled = settingsPatch.browserExtensionEnabled === true;
|
|
2252
|
+
const desiredBrowserEnabled = changesBrowserExtension ? browserExtensionEnabled : currentSettings.browserExtensionEnabled === true;
|
|
2253
|
+
let computerExtensionEnabled = settingsPatch.computerExtensionEnabled === true;
|
|
2254
|
+
let desiredComputerEnabled = changesComputerExtension ? computerExtensionEnabled : currentSettings.computerExtensionEnabled === true;
|
|
2255
|
+
if (changesBrowserExtension && browserExtensionEnabled || changesBrowserConfiguration) {
|
|
2256
|
+
const desiredSelection = browserResourceManager.browserSelection({
|
|
2257
|
+
browserSource: optionalString(settingsPatch.browserSource) ?? currentSettings.browserSource,
|
|
2258
|
+
browserExecutablePath: optionalString(settingsPatch.browserExecutablePath) ?? currentSettings.browserExecutablePath,
|
|
2259
|
+
browserExternalCdpUrl: optionalString(settingsPatch.browserExternalCdpUrl) ?? currentSettings.browserExternalCdpUrl
|
|
2260
|
+
});
|
|
2261
|
+
const probe = await browserResourceManager.probeCapability(desiredSelection);
|
|
2262
|
+
if ((changesBrowserConfiguration || desiredBrowserEnabled) && (!probe.runtimeCapability || probe.runtimeCapability.error)) {
|
|
2263
|
+
res.status(400).json({
|
|
2264
|
+
error: probe.runtimeCapability?.error || "Choose a local Chromium browser or prepare the isolated Browser runtime",
|
|
2265
|
+
code: "BROWSER_EXECUTABLE_NOT_FOUND"
|
|
2266
|
+
});
|
|
2267
|
+
return;
|
|
2268
|
+
}
|
|
2269
|
+
if (desiredBrowserEnabled && probe.runtimeCapability?.kind === "isolated-computer") {
|
|
2270
|
+
settingsPatch.computerExtensionEnabled = true;
|
|
2271
|
+
changesComputerExtension = true;
|
|
2272
|
+
computerExtensionEnabled = true;
|
|
2273
|
+
desiredComputerEnabled = true;
|
|
2274
|
+
}
|
|
2275
|
+
}
|
|
2276
|
+
if (currentSettings.browserExtensionEnabled === true && (changesBrowserExtension && !browserExtensionEnabled || changesBrowserConfiguration)) {
|
|
2277
|
+
try {
|
|
2278
|
+
await browserResourceManager.stopAll();
|
|
2279
|
+
} catch (caught) {
|
|
2280
|
+
const error = caughtError(caught);
|
|
2281
|
+
res.status(Number(error?.status) || 500).json({
|
|
2282
|
+
error: error?.message || "Browser extension could not be disabled",
|
|
2283
|
+
code: error?.code || "BROWSER_DISABLE_FAILED"
|
|
2284
|
+
});
|
|
2285
|
+
return;
|
|
2286
|
+
}
|
|
2287
|
+
}
|
|
2288
|
+
if (changesComputerExtension && computerExtensionEnabled || changesComputerConfiguration) {
|
|
2289
|
+
try {
|
|
2290
|
+
const probe = await computerResourceManager.probeSettings({
|
|
2291
|
+
...currentSettings,
|
|
2292
|
+
...settingsPatch
|
|
2293
|
+
});
|
|
2294
|
+
if (desiredComputerEnabled && !probe.imageReady) {
|
|
2295
|
+
res.status(400).json({
|
|
2296
|
+
error: probe.error || "Prepare the pinned Computer runtime before enabling this plugin",
|
|
2297
|
+
code: probe.dockerAvailable ? "COMPUTER_IMAGE_NOT_READY" : "COMPUTER_DOCKER_NOT_AVAILABLE"
|
|
2298
|
+
});
|
|
2299
|
+
return;
|
|
2300
|
+
}
|
|
2301
|
+
} catch (caught) {
|
|
2302
|
+
const error = caughtError(caught);
|
|
2303
|
+
res.status(Number(error?.status) || 400).json({
|
|
2304
|
+
error: error?.message || "Computer configuration is invalid",
|
|
2305
|
+
code: error?.code || "COMPUTER_CONFIGURATION_INVALID"
|
|
2306
|
+
});
|
|
2307
|
+
return;
|
|
2308
|
+
}
|
|
2309
|
+
}
|
|
2310
|
+
if (currentSettings.computerExtensionEnabled === true && (changesComputerExtension && !computerExtensionEnabled || changesComputerConfiguration)) {
|
|
2311
|
+
try {
|
|
2312
|
+
if (changesComputerConfiguration) {
|
|
2313
|
+
await computerResourceManager.resetAllContainers();
|
|
2314
|
+
} else {
|
|
2315
|
+
await computerResourceManager.stopAll();
|
|
2316
|
+
}
|
|
2317
|
+
} catch (caught) {
|
|
2318
|
+
const error = caughtError(caught);
|
|
2319
|
+
res.status(Number(error?.status) || 500).json({
|
|
2320
|
+
error: error?.message || "Computer extension could not be disabled",
|
|
2321
|
+
code: error?.code || "COMPUTER_DISABLE_FAILED"
|
|
2322
|
+
});
|
|
2323
|
+
return;
|
|
2324
|
+
}
|
|
2325
|
+
}
|
|
2326
|
+
try {
|
|
2327
|
+
configManager.updateSettings(settingsPatch);
|
|
2328
|
+
} catch (caught) {
|
|
2329
|
+
const error = caughtError(caught);
|
|
2330
|
+
if (error?.code && String(error.code).startsWith("AGENT_HOME_")) {
|
|
2331
|
+
res.status(Number(error.status) || 409).json({
|
|
2332
|
+
error: error.message || "Agent Home configuration conflicts with persisted Agent metadata",
|
|
2333
|
+
code: error.code
|
|
2334
|
+
});
|
|
2335
|
+
return;
|
|
2336
|
+
}
|
|
2337
|
+
throw caught;
|
|
2338
|
+
}
|
|
2339
|
+
if (changesBrowserExtension || changesBrowserConfiguration) {
|
|
2340
|
+
await browserResourceManager.refreshCapability();
|
|
2341
|
+
}
|
|
2342
|
+
if (changesComputerExtension || changesComputerConfiguration) {
|
|
2343
|
+
computerResourceManager.capabilityCache = null;
|
|
2344
|
+
await computerResourceManager.capability(true);
|
|
2345
|
+
}
|
|
2346
|
+
if (changesAgentHomes) {
|
|
2347
|
+
agentSessionInventory.invalidate();
|
|
2348
|
+
agentExtensionInventory.invalidate();
|
|
2349
|
+
}
|
|
2350
|
+
res.json({
|
|
2351
|
+
success: true,
|
|
2352
|
+
settings: configManager.getSettings()
|
|
2353
|
+
});
|
|
2354
|
+
scheduleBroadcastState();
|
|
2355
|
+
});
|
|
2356
|
+
app.post((0, import_index_html.routePath)(BASE_PATH, "/api/themes/:themeId/set"), express.json(), (req, res) => {
|
|
2357
|
+
const theme = themeManager.getTheme(req.params.themeId);
|
|
2358
|
+
if (!theme) {
|
|
2359
|
+
res.status(404).json({ error: "Theme not found" });
|
|
2360
|
+
return;
|
|
2361
|
+
}
|
|
2362
|
+
configManager.updateSettings({ theme: req.params.themeId });
|
|
2363
|
+
res.json({ success: true, theme: req.params.themeId });
|
|
2364
|
+
});
|
|
2365
|
+
app.get((0, import_index_html.routePath)(BASE_PATH, "/api/themes/:themeId/settings"), (req, res) => {
|
|
2366
|
+
const theme = themeManager.getTheme(req.params.themeId);
|
|
2367
|
+
if (!theme) {
|
|
2368
|
+
res.status(404).json({ error: "Theme not found" });
|
|
2369
|
+
return;
|
|
2370
|
+
}
|
|
2371
|
+
const settings = themeManager.getThemeSettings(req.params.themeId);
|
|
2372
|
+
res.json({ settings });
|
|
2373
|
+
});
|
|
2374
|
+
app.post((0, import_index_html.routePath)(BASE_PATH, "/api/themes/:themeId/settings"), express.json(), (req, res) => {
|
|
2375
|
+
const theme = themeManager.getTheme(req.params.themeId);
|
|
2376
|
+
if (!theme) {
|
|
2377
|
+
res.status(404).json({ error: "Theme not found" });
|
|
2378
|
+
return;
|
|
2379
|
+
}
|
|
2380
|
+
const success = themeManager.updateThemeSettings(req.params.themeId, req.body);
|
|
2381
|
+
if (success) {
|
|
2382
|
+
res.json({ success: true, settings: themeManager.getThemeSettings(req.params.themeId) });
|
|
2383
|
+
} else {
|
|
2384
|
+
res.status(500).json({ error: "Failed to update theme settings" });
|
|
2385
|
+
}
|
|
2386
|
+
});
|
|
2387
|
+
app.get((0, import_index_html.routePath)(BASE_PATH, "/api/themes/:themeId"), (req, res) => {
|
|
2388
|
+
const theme = themeManager.getTheme(req.params.themeId);
|
|
2389
|
+
if (!theme) {
|
|
2390
|
+
res.status(404).json({ error: "Theme not found" });
|
|
2391
|
+
return;
|
|
2392
|
+
}
|
|
2393
|
+
const css = themeManager.getThemeCSS(req.params.themeId);
|
|
2394
|
+
res.json({
|
|
2395
|
+
theme,
|
|
2396
|
+
css
|
|
2397
|
+
});
|
|
2398
|
+
});
|
|
2399
|
+
wss.on("connection", (ws, req) => {
|
|
2400
|
+
initializeWebSocketLiveness(ws);
|
|
2401
|
+
const url = new URL(req.url || "/", `http://${req.headers.host || "localhost"}`);
|
|
2402
|
+
const computerViewerPrefix = (0, import_index_html.routePath)(BASE_PATH, "/api/computers/");
|
|
2403
|
+
if (url.pathname.startsWith(computerViewerPrefix) && url.pathname.endsWith("/viewer-websocket")) {
|
|
2404
|
+
if (authEnabled && !tokenAuth.verifyWebSocket(req)) {
|
|
2405
|
+
ws.close(4001, "Authentication required");
|
|
2406
|
+
return;
|
|
2407
|
+
}
|
|
2408
|
+
const encodedId = url.pathname.slice(
|
|
2409
|
+
computerViewerPrefix.length,
|
|
2410
|
+
-"/viewer-websocket".length
|
|
2411
|
+
);
|
|
2412
|
+
try {
|
|
2413
|
+
computerResourceManager.attachViewer(decodeURIComponent(encodedId), ws);
|
|
2414
|
+
} catch (caught) {
|
|
2415
|
+
const error = caughtError(caught);
|
|
2416
|
+
ws.close(4004, error?.message || "Computer Resource not found");
|
|
2417
|
+
}
|
|
2418
|
+
return;
|
|
2419
|
+
}
|
|
2420
|
+
const viewerPrefix = (0, import_index_html.routePath)(BASE_PATH, "/api/browsers/");
|
|
2421
|
+
if (url.pathname.startsWith(viewerPrefix) && url.pathname.endsWith("/viewer")) {
|
|
2422
|
+
if (authEnabled && !tokenAuth.verifyWebSocket(req)) {
|
|
2423
|
+
ws.close(4001, "Authentication required");
|
|
2424
|
+
return;
|
|
2425
|
+
}
|
|
2426
|
+
const encodedId = url.pathname.slice(viewerPrefix.length, -"/viewer".length);
|
|
2427
|
+
try {
|
|
2428
|
+
browserResourceManager.attachViewer(decodeURIComponent(encodedId), ws);
|
|
2429
|
+
} catch (caught) {
|
|
2430
|
+
const error = caughtError(caught);
|
|
2431
|
+
ws.close(4004, error?.message || "Browser resource not found");
|
|
2432
|
+
}
|
|
2433
|
+
return;
|
|
2434
|
+
}
|
|
2435
|
+
if (url.pathname !== WS_PATH) {
|
|
2436
|
+
ws.close(1008, "Invalid path");
|
|
2437
|
+
return;
|
|
2438
|
+
}
|
|
2439
|
+
if (authEnabled && !tokenAuth.verifyWebSocket(req)) {
|
|
2440
|
+
ws.close(4001, "Authentication required");
|
|
2441
|
+
return;
|
|
2442
|
+
}
|
|
2443
|
+
console.log("Client connected");
|
|
2444
|
+
ws.connectionId = crypto.randomUUID();
|
|
2445
|
+
ws.on("message", (message) => {
|
|
2446
|
+
try {
|
|
2447
|
+
const data = JSON.parse(message);
|
|
2448
|
+
const validation = validateClientMessage(data);
|
|
2449
|
+
if (validation.ok === false) {
|
|
2450
|
+
ws.send(JSON.stringify({
|
|
2451
|
+
type: "protocol-error",
|
|
2452
|
+
protocolVersion: PROTOCOL_VERSION,
|
|
2453
|
+
requestId: typeof data?.requestId === "string" ? data.requestId : "",
|
|
2454
|
+
message: validation.error
|
|
2455
|
+
}));
|
|
2456
|
+
return;
|
|
2457
|
+
}
|
|
2458
|
+
handleMessage(ws, validation.value);
|
|
2459
|
+
} catch (e) {
|
|
2460
|
+
console.error("Failed to parse message:", e);
|
|
2461
|
+
ws.send(JSON.stringify({
|
|
2462
|
+
type: "protocol-error",
|
|
2463
|
+
protocolVersion: PROTOCOL_VERSION,
|
|
2464
|
+
requestId: "",
|
|
2465
|
+
message: "message must be valid JSON"
|
|
2466
|
+
}));
|
|
2467
|
+
}
|
|
2468
|
+
});
|
|
2469
|
+
ws.on("close", () => {
|
|
2470
|
+
clearWorkspaceFileWatch(ws);
|
|
2471
|
+
console.log("Client disconnected");
|
|
2472
|
+
});
|
|
2473
|
+
ws.send(JSON.stringify({
|
|
2474
|
+
type: "protocol-hello",
|
|
2475
|
+
protocolVersion: PROTOCOL_VERSION,
|
|
2476
|
+
minProtocolVersion: MIN_PROTOCOL_VERSION
|
|
2477
|
+
}));
|
|
2478
|
+
sendState(ws);
|
|
2479
|
+
});
|
|
2480
|
+
const MAIN_AGENT_RESTART_COMMANDS = /* @__PURE__ */ new Set(["codex", "claude", "opencode", "qoder", "qwen", "bash", "zsh"]);
|
|
2481
|
+
function normalizeMainAgentRestartCommand(command) {
|
|
2482
|
+
const normalized = String(command || "").trim();
|
|
2483
|
+
return MAIN_AGENT_RESTART_COMMANDS.has(normalized) ? normalized : "";
|
|
2484
|
+
}
|
|
2485
|
+
function restartMainAgent(ws, command) {
|
|
2486
|
+
const normalizedCommand = normalizeMainAgentRestartCommand(command);
|
|
2487
|
+
if (!normalizedCommand) {
|
|
2488
|
+
ws.send(JSON.stringify({ type: "error", message: "Unsupported Main Agent restart command" }));
|
|
2489
|
+
return;
|
|
2490
|
+
}
|
|
2491
|
+
void (async () => {
|
|
2492
|
+
try {
|
|
2493
|
+
const state = agentManager.getState();
|
|
2494
|
+
const currentMain = state.agents.find((agent) => agent.id === state.mainAgentId || agent.isMain === true);
|
|
2495
|
+
if (currentMain) {
|
|
2496
|
+
const killed = await agentManager.killAgent(currentMain.id);
|
|
2497
|
+
if (killed?.error) {
|
|
2498
|
+
ws.send(JSON.stringify({ type: "error", message: killed.error }));
|
|
2499
|
+
return;
|
|
2500
|
+
}
|
|
2501
|
+
}
|
|
2502
|
+
await agentManager.startAgent(normalizedCommand, null, (agentId, error) => {
|
|
2503
|
+
if (error) {
|
|
2504
|
+
ws.send(JSON.stringify({ type: "error", message: error }));
|
|
2505
|
+
} else if (agentId) {
|
|
2506
|
+
ws.agentId = agentId;
|
|
2507
|
+
broadcastState();
|
|
2508
|
+
ws.send(JSON.stringify({ type: "agent-started", agentId }));
|
|
2509
|
+
}
|
|
2510
|
+
}, {
|
|
2511
|
+
wantsMain: true
|
|
2512
|
+
});
|
|
2513
|
+
} catch (caught) {
|
|
2514
|
+
const error = caughtError(caught);
|
|
2515
|
+
const message = error instanceof Error ? error.message : "Failed to restart Main Agent";
|
|
2516
|
+
ws.send(JSON.stringify({ type: "error", message }));
|
|
2517
|
+
broadcastState();
|
|
2518
|
+
}
|
|
2519
|
+
})();
|
|
2520
|
+
}
|
|
2521
|
+
async function killAgentFromMessage(ws, agentId, options = {}) {
|
|
2522
|
+
try {
|
|
2523
|
+
const requested = await agentManager.requestKillAgent(agentId, {
|
|
2524
|
+
acknowledgeUnprovenAcpExit: options.acknowledgeUnprovenAcpExit === true
|
|
2525
|
+
});
|
|
2526
|
+
const result = requested.result;
|
|
2527
|
+
if (result && "error" in result && result.error) {
|
|
2528
|
+
ws.send(JSON.stringify({ type: "error", message: result.error }));
|
|
2529
|
+
} else if (result?.accepted) {
|
|
2530
|
+
ws.send(JSON.stringify({ type: "agent-delete-accepted", ...result }));
|
|
2531
|
+
void requested.completion.then((completed) => {
|
|
2532
|
+
if (completed?.error && ws.readyState === WebSocket.OPEN) {
|
|
2533
|
+
ws.send(JSON.stringify({ type: "error", message: completed.error }));
|
|
2534
|
+
}
|
|
2535
|
+
}).catch((error) => {
|
|
2536
|
+
if (ws.readyState === WebSocket.OPEN) {
|
|
2537
|
+
ws.send(JSON.stringify({
|
|
2538
|
+
type: "error",
|
|
2539
|
+
message: error instanceof Error ? error.message : "Failed to stop Agent"
|
|
2540
|
+
}));
|
|
2541
|
+
}
|
|
2542
|
+
}).finally(() => broadcastState());
|
|
2543
|
+
}
|
|
2544
|
+
} catch (caught) {
|
|
2545
|
+
const error = caughtError(caught);
|
|
2546
|
+
ws.send(JSON.stringify({
|
|
2547
|
+
type: "error",
|
|
2548
|
+
message: error instanceof Error ? error.message : "Failed to stop Agent"
|
|
2549
|
+
}));
|
|
2550
|
+
} finally {
|
|
2551
|
+
broadcastState();
|
|
2552
|
+
}
|
|
2553
|
+
}
|
|
2554
|
+
async function sendInputMessage(ws, data) {
|
|
2555
|
+
const targetAgentId = (0, import_input_routing.resolveInputTargetAgentId)(ws, data);
|
|
2556
|
+
if (!targetAgentId) return;
|
|
2557
|
+
const inputParts = (0, import_input_parts.inputPartsFromMessage)(data);
|
|
2558
|
+
if (inputParts.length === 0) return;
|
|
2559
|
+
await agentManager.sendInput(targetAgentId, inputParts);
|
|
2560
|
+
}
|
|
2561
|
+
async function sendComposerInputMessage(ws, data) {
|
|
2562
|
+
const targetAgentId = (0, import_input_routing.resolveInputTargetAgentId)(ws, data);
|
|
2563
|
+
const requestId = typeof data.requestId === "string" ? data.requestId.trim() : "";
|
|
2564
|
+
const delivery = data.delivery === "steer" || data.delivery === "prompt" ? data.delivery : "auto";
|
|
2565
|
+
const responseAgentId = targetAgentId || (typeof data.agentId === "string" ? data.agentId : "");
|
|
2566
|
+
const respond = (accepted, message2 = "", uncertain = false) => {
|
|
2567
|
+
if (!requestId || !responseAgentId) return;
|
|
2568
|
+
ws.send(JSON.stringify({
|
|
2569
|
+
type: "composer-input-result",
|
|
2570
|
+
requestId,
|
|
2571
|
+
agentId: responseAgentId,
|
|
2572
|
+
accepted,
|
|
2573
|
+
...message2 ? { message: message2 } : {},
|
|
2574
|
+
...uncertain ? { uncertain: true } : {}
|
|
2575
|
+
}));
|
|
2576
|
+
};
|
|
2577
|
+
const message = typeof data.message === "string" ? data.message : "";
|
|
2578
|
+
if (!/^[A-Za-z0-9._:-]{1,160}$/.test(requestId)) {
|
|
2579
|
+
if (responseAgentId) {
|
|
2580
|
+
ws.send(JSON.stringify({
|
|
2581
|
+
type: "composer-input-result",
|
|
2582
|
+
requestId: requestId || "invalid-request",
|
|
2583
|
+
agentId: responseAgentId,
|
|
2584
|
+
accepted: false,
|
|
2585
|
+
message: "Structured Composer input requires a valid requestId"
|
|
2586
|
+
}));
|
|
2587
|
+
}
|
|
2588
|
+
return;
|
|
2589
|
+
}
|
|
2590
|
+
const content = [];
|
|
2591
|
+
if (message.trim()) content.push({ type: "text", text: message });
|
|
2592
|
+
const attachmentsRoot = path.resolve(imageAttachmentsDir());
|
|
2593
|
+
const attachments = Array.isArray(data.attachments) ? data.attachments.slice(0, 8) : [];
|
|
2594
|
+
for (const attachment of attachments) {
|
|
2595
|
+
if (!["image", "audio"].includes(attachment?.kind) || typeof attachment.path !== "string") continue;
|
|
2596
|
+
const filePath = path.resolve(attachment.path);
|
|
2597
|
+
if (!filePath.startsWith(`${attachmentsRoot}${path.sep}`)) continue;
|
|
2598
|
+
const mimeType = typeof attachment.type === "string" && attachment.kind === "image" && /^image\/(?:png|jpe?g|gif|webp)$/i.test(attachment.type) ? attachment.type.toLowerCase() : typeof attachment.type === "string" && attachment.kind === "audio" && Object.hasOwn(AUDIO_ATTACHMENT_EXTENSIONS, attachment.type.toLowerCase()) ? attachment.type.toLowerCase() : "";
|
|
2599
|
+
if (!mimeType) continue;
|
|
2600
|
+
try {
|
|
2601
|
+
const data2 = await fs.promises.readFile(filePath);
|
|
2602
|
+
if (data2.length === 0 || data2.length > 12 * 1024 * 1024) continue;
|
|
2603
|
+
content.push({
|
|
2604
|
+
type: attachment.kind,
|
|
2605
|
+
data: data2.toString("base64"),
|
|
2606
|
+
mimeType,
|
|
2607
|
+
path: filePath,
|
|
2608
|
+
uri: pathToFileURL(filePath).href
|
|
2609
|
+
});
|
|
2610
|
+
} catch {
|
|
2611
|
+
}
|
|
2612
|
+
}
|
|
2613
|
+
if (!targetAgentId || content.length === 0) {
|
|
2614
|
+
respond(false, "Composer message is empty");
|
|
2615
|
+
return;
|
|
2616
|
+
}
|
|
2617
|
+
const targetAgent = agentManager.getState().agents.find((agent) => agent.id === targetAgentId);
|
|
2618
|
+
const structuredRuntime = targetAgent && (0, import_agent_runtime_binding.runtimeKind)(targetAgent) !== "terminal";
|
|
2619
|
+
if (!structuredRuntime) {
|
|
2620
|
+
const error = "Terminal Composer input requires the active terminal owner";
|
|
2621
|
+
if (requestId) respond(false, error);
|
|
2622
|
+
else ws.send(JSON.stringify({ type: "error", message: error }));
|
|
2623
|
+
return;
|
|
2624
|
+
}
|
|
2625
|
+
try {
|
|
2626
|
+
await agentManager.sendComposerMessage(targetAgentId, content, { requestId, delivery });
|
|
2627
|
+
respond(true);
|
|
2628
|
+
} catch (caught) {
|
|
2629
|
+
const error = caughtError(caught);
|
|
2630
|
+
const message2 = error && error.message ? error.message : "Failed to send Composer message";
|
|
2631
|
+
if (requestId) respond(false, message2, error?.uncertain === true);
|
|
2632
|
+
else ws.send(JSON.stringify({ type: "error", message: message2 }));
|
|
2633
|
+
}
|
|
2634
|
+
}
|
|
2635
|
+
async function sendBusinessHealthResult(ws, requestId) {
|
|
2636
|
+
const health = await (0, import_business_health.probeAgentManagerBusinessHealth)(agentManager);
|
|
2637
|
+
if (ws.readyState !== WebSocket.OPEN) return;
|
|
2638
|
+
recoverResourceSnapshotIfReady(ws);
|
|
2639
|
+
ws.send(JSON.stringify({
|
|
2640
|
+
type: "business-health-result",
|
|
2641
|
+
requestId,
|
|
2642
|
+
serverEpoch: SERVER_EPOCH,
|
|
2643
|
+
protocolVersion: PROTOCOL_VERSION,
|
|
2644
|
+
...health
|
|
2645
|
+
}));
|
|
2646
|
+
}
|
|
2647
|
+
function handleMessage(ws, data) {
|
|
2648
|
+
switch (data.type) {
|
|
2649
|
+
case "protocol-hello":
|
|
2650
|
+
if (!protocolCompatible(data.protocolVersion)) {
|
|
2651
|
+
ws.close(4002, `Unsupported Farming protocol version ${data.protocolVersion}`);
|
|
2652
|
+
return;
|
|
2653
|
+
}
|
|
2654
|
+
ws.protocolVersion = data.protocolVersion;
|
|
2655
|
+
sendResourceSnapshots(ws);
|
|
2656
|
+
break;
|
|
2657
|
+
case "business-health-probe":
|
|
2658
|
+
if (!ws.protocolVersion) {
|
|
2659
|
+
ws.send(JSON.stringify({
|
|
2660
|
+
type: "protocol-error",
|
|
2661
|
+
protocolVersion: PROTOCOL_VERSION,
|
|
2662
|
+
requestId: data.requestId,
|
|
2663
|
+
message: "Business health requires a negotiated Farming protocol"
|
|
2664
|
+
}));
|
|
2665
|
+
return;
|
|
2666
|
+
}
|
|
2667
|
+
void sendBusinessHealthResult(ws, data.requestId);
|
|
2668
|
+
break;
|
|
2669
|
+
case "start-agent": {
|
|
2670
|
+
const workspace = typeof data.workspace === "string" ? data.workspace : null;
|
|
2671
|
+
void (async () => {
|
|
2672
|
+
const projectWorkspace = data.asMain === true ? "" : await canonicalProjectWorkspace(
|
|
2673
|
+
typeof data.projectWorkspace === "string" && data.projectWorkspace.trim() ? data.projectWorkspace : workspace
|
|
2674
|
+
);
|
|
2675
|
+
agentManager.startAgent(data.command, workspace, (agentId, error) => {
|
|
2676
|
+
if (error) {
|
|
2677
|
+
ws.send(JSON.stringify({ type: "error", message: error }));
|
|
2678
|
+
} else if (agentId) {
|
|
2679
|
+
void (async () => {
|
|
2680
|
+
try {
|
|
2681
|
+
if (projectWorkspace) configManager.mountProjectWorkspace(projectWorkspace);
|
|
2682
|
+
} catch (mountError) {
|
|
2683
|
+
let rollbackError = "";
|
|
2684
|
+
try {
|
|
2685
|
+
const rollback = await agentManager.archiveAgent(agentId, {
|
|
2686
|
+
reason: "project-mount-failed",
|
|
2687
|
+
recordHistory: false,
|
|
2688
|
+
requireEngineExit: true,
|
|
2689
|
+
scheduleProviderArchive: false
|
|
2690
|
+
});
|
|
2691
|
+
if (rollback?.error) rollbackError = rollback.error;
|
|
2692
|
+
} catch (cleanupError) {
|
|
2693
|
+
rollbackError = caughtError(cleanupError).message || String(cleanupError);
|
|
2694
|
+
}
|
|
2695
|
+
broadcastState();
|
|
2696
|
+
const errorMessage = caughtError(mountError).message || "Failed to create Project";
|
|
2697
|
+
ws.send(JSON.stringify({
|
|
2698
|
+
type: "error",
|
|
2699
|
+
message: rollbackError ? `${errorMessage}. Rollback failed: ${rollbackError}` : errorMessage
|
|
2700
|
+
}));
|
|
2701
|
+
return;
|
|
2702
|
+
}
|
|
2703
|
+
ws.agentId = agentId;
|
|
2704
|
+
broadcastState();
|
|
2705
|
+
ws.send(JSON.stringify({ type: "agent-started", agentId }));
|
|
2706
|
+
})().catch((callbackError) => {
|
|
2707
|
+
console.warn("Failed to finish started Agent transition:", agentId, caughtError(callbackError).message || callbackError);
|
|
2708
|
+
});
|
|
2709
|
+
}
|
|
2710
|
+
}, {
|
|
2711
|
+
wantsMain: data.asMain === true,
|
|
2712
|
+
projectWorkspace,
|
|
2713
|
+
task: typeof data.task === "string" ? data.task : "",
|
|
2714
|
+
workflowTemplate: typeof data.workflowTemplate === "string" ? data.workflowTemplate : "",
|
|
2715
|
+
customTitle: typeof data.customTitle === "string" ? data.customTitle : "",
|
|
2716
|
+
createRequestId: typeof data.requestId === "string" ? data.requestId : "",
|
|
2717
|
+
codexApprovalMode: typeof data.codexApprovalMode === "string" ? data.codexApprovalMode : void 0,
|
|
2718
|
+
agentRuntimeMode: typeof data.agentRuntimeMode === "string" && ["acp", "chat"].includes(data.agentRuntimeMode) ? data.agentRuntimeMode : "terminal",
|
|
2719
|
+
acpHistoryMode: data.acpHistoryMode === "resume" ? "resume" : "load",
|
|
2720
|
+
providerHomeId: typeof data.providerHomeId === "string" ? data.providerHomeId : "",
|
|
2721
|
+
...Array.isArray(data.additionalDirectories) ? { additionalDirectories: data.additionalDirectories } : {},
|
|
2722
|
+
...Array.isArray(data.mcpServers) ? { mcpServers: data.mcpServers } : {},
|
|
2723
|
+
...data.dangerouslySkipPermissions === true ? { dangerouslySkipPermissions: true } : {}
|
|
2724
|
+
});
|
|
2725
|
+
})().catch((error) => {
|
|
2726
|
+
ws.send(JSON.stringify({
|
|
2727
|
+
type: "error",
|
|
2728
|
+
message: caughtError(error).message || "Failed to resolve Project workspace"
|
|
2729
|
+
}));
|
|
2730
|
+
});
|
|
2731
|
+
break;
|
|
2732
|
+
}
|
|
2733
|
+
case "input":
|
|
2734
|
+
{
|
|
2735
|
+
void sendInputMessage(ws, data);
|
|
2736
|
+
}
|
|
2737
|
+
break;
|
|
2738
|
+
case "composer-input":
|
|
2739
|
+
{
|
|
2740
|
+
void sendComposerInputMessage(ws, data);
|
|
2741
|
+
}
|
|
2742
|
+
break;
|
|
2743
|
+
case "acp-permission-response":
|
|
2744
|
+
try {
|
|
2745
|
+
agentManager.respondToAcpPermission(
|
|
2746
|
+
data.agentId,
|
|
2747
|
+
data.requestId,
|
|
2748
|
+
data.optionId,
|
|
2749
|
+
data.cancelled === true
|
|
2750
|
+
);
|
|
2751
|
+
} catch (caught) {
|
|
2752
|
+
const error = caughtError(caught);
|
|
2753
|
+
ws.send(JSON.stringify({
|
|
2754
|
+
type: "error",
|
|
2755
|
+
message: error && error.message ? error.message : "Failed to respond to ACP permission"
|
|
2756
|
+
}));
|
|
2757
|
+
}
|
|
2758
|
+
break;
|
|
2759
|
+
case "interrupt-agent":
|
|
2760
|
+
if (data.agentId) {
|
|
2761
|
+
void agentManager.interruptAgent(data.agentId);
|
|
2762
|
+
}
|
|
2763
|
+
break;
|
|
2764
|
+
case "focus-agent":
|
|
2765
|
+
ws.focusedAgentId = data.agentId;
|
|
2766
|
+
if (data.streamScope === "focused" || data.streamScope === "all") {
|
|
2767
|
+
ws.streamScope = data.streamScope;
|
|
2768
|
+
}
|
|
2769
|
+
if (data.previewScope === "none" || data.previewScope === "focused" || data.previewScope === "all") {
|
|
2770
|
+
ws.previewScope = data.previewScope;
|
|
2771
|
+
}
|
|
2772
|
+
if (data.refreshState === true) {
|
|
2773
|
+
sendState(ws);
|
|
2774
|
+
}
|
|
2775
|
+
break;
|
|
2776
|
+
case "resize-agent":
|
|
2777
|
+
if (data.agentId && Number.isFinite(data.cols) && Number.isFinite(data.rows)) {
|
|
2778
|
+
agentManager.requestAgentSessionResize(data.agentId, data.cols, data.rows);
|
|
2779
|
+
}
|
|
2780
|
+
break;
|
|
2781
|
+
case "clear-terminal":
|
|
2782
|
+
if (data.agentId) void agentManager.clearAgentSessionBuffer(data.agentId);
|
|
2783
|
+
break;
|
|
2784
|
+
case "watch-workspace-files":
|
|
2785
|
+
watchWorkspaceFiles(ws, data);
|
|
2786
|
+
break;
|
|
2787
|
+
case "unwatch-workspace-files":
|
|
2788
|
+
clearWorkspaceFileWatch(ws, data.agentId);
|
|
2789
|
+
break;
|
|
2790
|
+
case "kill-agent":
|
|
2791
|
+
void killAgentFromMessage(ws, data.agentId, {
|
|
2792
|
+
acknowledgeUnprovenAcpExit: data.acknowledgeUnprovenAcpExit === true
|
|
2793
|
+
});
|
|
2794
|
+
break;
|
|
2795
|
+
case "restart-main-agent":
|
|
2796
|
+
restartMainAgent(ws, data.command);
|
|
2797
|
+
break;
|
|
2798
|
+
default:
|
|
2799
|
+
console.log("Unknown message type");
|
|
2800
|
+
}
|
|
2801
|
+
}
|
|
2802
|
+
function sendWorkspaceFileWatchError(ws, error) {
|
|
2803
|
+
if (ws.readyState !== WebSocket.OPEN) return;
|
|
2804
|
+
const message = error instanceof import_workspace_file_service.WorkspaceFileError ? caughtError(error).message : "failed to watch workspace files";
|
|
2805
|
+
ws.send(JSON.stringify({ type: "error", message }));
|
|
2806
|
+
}
|
|
2807
|
+
function isCurrentWorkspaceFileWatch(ws, agentId, record) {
|
|
2808
|
+
return Boolean(
|
|
2809
|
+
!record.cancelled && ws.workspaceFileUnsubscribes?.get(agentId) === record
|
|
2810
|
+
);
|
|
2811
|
+
}
|
|
2812
|
+
function closeWorkspaceFileWatchRecord(record) {
|
|
2813
|
+
record.cancelled = true;
|
|
2814
|
+
if (!record.unsubscribe) return;
|
|
2815
|
+
Promise.resolve(record.unsubscribe()).catch((error) => {
|
|
2816
|
+
console.error("Failed to clear workspace file watch:", error);
|
|
2817
|
+
});
|
|
2818
|
+
}
|
|
2819
|
+
function clearWorkspaceFileWatch(ws, agentId = null) {
|
|
2820
|
+
const watches = ws.workspaceFileUnsubscribes;
|
|
2821
|
+
if (!watches) return;
|
|
2822
|
+
const entries = agentId ? [[agentId, watches.get(agentId)]] : Array.from(watches.entries());
|
|
2823
|
+
entries.forEach(([watchedAgentId, record]) => {
|
|
2824
|
+
if (!record) return;
|
|
2825
|
+
if (watches.get(watchedAgentId) === record) watches.delete(watchedAgentId);
|
|
2826
|
+
closeWorkspaceFileWatchRecord(record);
|
|
2827
|
+
});
|
|
2828
|
+
if (watches.size === 0) {
|
|
2829
|
+
ws.workspaceFileUnsubscribes = null;
|
|
2830
|
+
}
|
|
2831
|
+
}
|
|
2832
|
+
async function watchWorkspaceFiles(ws, data) {
|
|
2833
|
+
try {
|
|
2834
|
+
if (!data.agentId) {
|
|
2835
|
+
throw new import_workspace_file_service.WorkspaceFileError("agentId is required", 400);
|
|
2836
|
+
}
|
|
2837
|
+
if (!ws.workspaceFileUnsubscribes) {
|
|
2838
|
+
ws.workspaceFileUnsubscribes = /* @__PURE__ */ new Map();
|
|
2839
|
+
}
|
|
2840
|
+
const watches = ws.workspaceFileUnsubscribes;
|
|
2841
|
+
const existing = watches.get(data.agentId);
|
|
2842
|
+
if (existing) {
|
|
2843
|
+
const watching = await existing.ready;
|
|
2844
|
+
if (watching && isCurrentWorkspaceFileWatch(ws, data.agentId, existing) && ws.readyState === WebSocket.OPEN) {
|
|
2845
|
+
ws.send(JSON.stringify({
|
|
2846
|
+
type: "workspace-file-watch",
|
|
2847
|
+
agentId: data.agentId,
|
|
2848
|
+
watching: true
|
|
2849
|
+
}));
|
|
2850
|
+
}
|
|
2851
|
+
return;
|
|
2852
|
+
}
|
|
2853
|
+
const root = (0, import_workspace_file_router.resolveWorkspaceRoot)(agentManager, data.agentId);
|
|
2854
|
+
const record = {
|
|
2855
|
+
cancelled: false,
|
|
2856
|
+
unsubscribe: null,
|
|
2857
|
+
ready: null
|
|
2858
|
+
};
|
|
2859
|
+
record.ready = (async () => {
|
|
2860
|
+
const unsubscribe = await workspaceFileService.subscribe(root, (event) => {
|
|
2861
|
+
if (!isCurrentWorkspaceFileWatch(ws, data.agentId, record) || ws.readyState !== WebSocket.OPEN) return;
|
|
2862
|
+
ws.send(JSON.stringify({
|
|
2863
|
+
type: "workspace-file-event",
|
|
2864
|
+
event: {
|
|
2865
|
+
agentId: data.agentId,
|
|
2866
|
+
...event
|
|
2867
|
+
}
|
|
2868
|
+
}));
|
|
2869
|
+
});
|
|
2870
|
+
record.unsubscribe = unsubscribe;
|
|
2871
|
+
if (!isCurrentWorkspaceFileWatch(ws, data.agentId, record) || ws.readyState !== WebSocket.OPEN) {
|
|
2872
|
+
closeWorkspaceFileWatchRecord(record);
|
|
2873
|
+
return false;
|
|
2874
|
+
}
|
|
2875
|
+
return true;
|
|
2876
|
+
})();
|
|
2877
|
+
watches.set(data.agentId, record);
|
|
2878
|
+
try {
|
|
2879
|
+
const watching = await record.ready;
|
|
2880
|
+
if (watching && isCurrentWorkspaceFileWatch(ws, data.agentId, record) && ws.readyState === WebSocket.OPEN) {
|
|
2881
|
+
ws.send(JSON.stringify({
|
|
2882
|
+
type: "workspace-file-watch",
|
|
2883
|
+
agentId: data.agentId,
|
|
2884
|
+
watching: true
|
|
2885
|
+
}));
|
|
2886
|
+
}
|
|
2887
|
+
} catch (caught) {
|
|
2888
|
+
const error = caughtError(caught);
|
|
2889
|
+
if (watches.get(data.agentId) === record) watches.delete(data.agentId);
|
|
2890
|
+
closeWorkspaceFileWatchRecord(record);
|
|
2891
|
+
if (watches.size === 0 && ws.workspaceFileUnsubscribes === watches) {
|
|
2892
|
+
ws.workspaceFileUnsubscribes = null;
|
|
2893
|
+
}
|
|
2894
|
+
throw error;
|
|
2895
|
+
}
|
|
2896
|
+
} catch (caught) {
|
|
2897
|
+
const error = caughtError(caught);
|
|
2898
|
+
sendWorkspaceFileWatchError(ws, error);
|
|
2899
|
+
}
|
|
2900
|
+
}
|
|
2901
|
+
function buildStatePayload() {
|
|
2902
|
+
const state = agentManager.getState();
|
|
2903
|
+
const settings = configManager.getSettings();
|
|
2904
|
+
return {
|
|
2905
|
+
...state,
|
|
2906
|
+
agents: state.agents.map((agent) => ({
|
|
2907
|
+
...agent,
|
|
2908
|
+
workspaceRootId: (0, import_workspace_root_registry.rootIdForPath)(String(agent.projectWorkspace || agent.gitWorktree?.workspace || agent.cwd || ""))
|
|
2909
|
+
})),
|
|
2910
|
+
workspaceRoots: workspaceRootRegistry.list(),
|
|
2911
|
+
mainPageSessionKeys: typeof configManager.getMainPageSessionKeys === "function" ? configManager.getMainPageSessionKeys() : Array.isArray(settings.mainPageSessionKeys) ? settings.mainPageSessionKeys : [],
|
|
2912
|
+
projectWorkspaces: settings.projectWorkspaces || [],
|
|
2913
|
+
pinnedProjectWorkspaces: settings.pinnedProjectWorkspaces || []
|
|
2914
|
+
};
|
|
2915
|
+
}
|
|
2916
|
+
function sendState(ws) {
|
|
2917
|
+
const state = buildStatePayload();
|
|
2918
|
+
ws.send(JSON.stringify({ type: "state", state }));
|
|
2919
|
+
agentManager.getPreviewPayloads().forEach((preview) => {
|
|
2920
|
+
sendPreview(ws, preview);
|
|
2921
|
+
});
|
|
2922
|
+
}
|
|
2923
|
+
function sendResourceSnapshots(ws) {
|
|
2924
|
+
if (ws.readyState !== WebSocket.OPEN || ws.protocolVersion !== PROTOCOL_VERSION) return;
|
|
2925
|
+
ws.send(JSON.stringify({
|
|
2926
|
+
type: "browser-resource-snapshot",
|
|
2927
|
+
snapshot: browserResourceManager.stateSnapshot()
|
|
2928
|
+
}));
|
|
2929
|
+
ws.send(JSON.stringify({
|
|
2930
|
+
type: "computer-resource-snapshot",
|
|
2931
|
+
snapshot: computerResourceManager.stateSnapshot()
|
|
2932
|
+
}));
|
|
2933
|
+
ws.resourceSnapshotPending = false;
|
|
2934
|
+
}
|
|
2935
|
+
function previewForClient(preview, client) {
|
|
2936
|
+
if (!preview || !client || client.focusedAgentId !== preview.agentId || !preview.previewSnapshot) {
|
|
2937
|
+
return preview;
|
|
2938
|
+
}
|
|
2939
|
+
return {
|
|
2940
|
+
...preview,
|
|
2941
|
+
previewSnapshot: null
|
|
2942
|
+
};
|
|
2943
|
+
}
|
|
2944
|
+
function sendPreview(ws, preview) {
|
|
2945
|
+
ws.send(JSON.stringify({
|
|
2946
|
+
type: "session-preview",
|
|
2947
|
+
preview: previewForClient(preview, ws)
|
|
2948
|
+
}));
|
|
2949
|
+
}
|
|
2950
|
+
const STATE_BROADCAST_INTERVAL_MS = 120;
|
|
2951
|
+
const PREVIEW_BROADCAST_INTERVAL_MS = 500;
|
|
2952
|
+
const SESSION_STREAM_BROADCAST_INTERVAL_MS = 33;
|
|
2953
|
+
const AGENT_ACTIVITY_BROADCAST_DELAY_MS = SESSION_STREAM_BROADCAST_INTERVAL_MS;
|
|
2954
|
+
const MAX_SESSION_STREAM_CLIENT_BUFFERED_AMOUNT = 4 * 1024 * 1024;
|
|
2955
|
+
const RESOURCE_BROADCAST_INTERVAL_MS = 100;
|
|
2956
|
+
const MAX_RESOURCE_CLIENT_BUFFERED_AMOUNT = 512 * 1024;
|
|
2957
|
+
function isAgentScopedServerEvent(value) {
|
|
2958
|
+
return isRecord(value) && typeof value.agentId === "string" && value.agentId.length > 0;
|
|
2959
|
+
}
|
|
2960
|
+
let stateBroadcastTimer = null;
|
|
2961
|
+
let lastStateBroadcastAt = 0;
|
|
2962
|
+
const pendingPreviewBroadcasts = /* @__PURE__ */ new Map();
|
|
2963
|
+
const pendingAgentActivityBroadcasts = /* @__PURE__ */ new Map();
|
|
2964
|
+
const pendingAgentUpdates = /* @__PURE__ */ new Map();
|
|
2965
|
+
const pendingAcpSessionRevisions = /* @__PURE__ */ new Map();
|
|
2966
|
+
const pendingSessionStreams = /* @__PURE__ */ new Map();
|
|
2967
|
+
const pendingResourceBroadcasts = /* @__PURE__ */ new Map();
|
|
2968
|
+
let sessionStreamBroadcastTimer = null;
|
|
2969
|
+
let lastSessionStreamBroadcastAt = 0;
|
|
2970
|
+
let resourceBroadcastTimer = null;
|
|
2971
|
+
function broadcastResourceEvent(event) {
|
|
2972
|
+
const message = JSON.stringify(event.message);
|
|
2973
|
+
wss.clients.forEach((client) => {
|
|
2974
|
+
if (client.readyState !== WebSocket.OPEN || client.protocolVersion !== PROTOCOL_VERSION) return;
|
|
2975
|
+
const delivery = (0, import_resource_broadcast_protocol.resourceClientDelivery)(
|
|
2976
|
+
client.bufferedAmount,
|
|
2977
|
+
client.resourceSnapshotPending === true,
|
|
2978
|
+
MAX_RESOURCE_CLIENT_BUFFERED_AMOUNT
|
|
2979
|
+
);
|
|
2980
|
+
if (delivery === "defer") {
|
|
2981
|
+
client.resourceSnapshotPending = true;
|
|
2982
|
+
return;
|
|
2983
|
+
}
|
|
2984
|
+
if (delivery === "snapshot") {
|
|
2985
|
+
sendResourceSnapshots(client);
|
|
2986
|
+
return;
|
|
2987
|
+
}
|
|
2988
|
+
client.send(message);
|
|
2989
|
+
});
|
|
2990
|
+
}
|
|
2991
|
+
function recoverResourceSnapshotIfReady(client) {
|
|
2992
|
+
if (client.readyState !== WebSocket.OPEN || client.protocolVersion !== PROTOCOL_VERSION) return;
|
|
2993
|
+
const delivery = (0, import_resource_broadcast_protocol.resourceClientDelivery)(
|
|
2994
|
+
client.bufferedAmount,
|
|
2995
|
+
client.resourceSnapshotPending === true,
|
|
2996
|
+
MAX_RESOURCE_CLIENT_BUFFERED_AMOUNT
|
|
2997
|
+
);
|
|
2998
|
+
if (delivery === "snapshot") sendResourceSnapshots(client);
|
|
2999
|
+
}
|
|
3000
|
+
function flushResourceBroadcasts() {
|
|
3001
|
+
resourceBroadcastTimer = null;
|
|
3002
|
+
(0, import_resource_broadcast_protocol.drainResourceBroadcasts)(pendingResourceBroadcasts).forEach(broadcastResourceEvent);
|
|
3003
|
+
}
|
|
3004
|
+
function scheduleResourceBroadcast(event) {
|
|
3005
|
+
(0, import_resource_broadcast_protocol.coalesceResourceBroadcast)(pendingResourceBroadcasts, event);
|
|
3006
|
+
if (resourceBroadcastTimer) return;
|
|
3007
|
+
resourceBroadcastTimer = setTimeout(flushResourceBroadcasts, RESOURCE_BROADCAST_INTERVAL_MS);
|
|
3008
|
+
}
|
|
3009
|
+
function scheduleResourceUpdate(domain, resource) {
|
|
3010
|
+
if (!isRecord(resource)) return;
|
|
3011
|
+
const id = typeof resource.id === "string" ? resource.id : "";
|
|
3012
|
+
const collectionRevision = Number(resource.collectionRevision);
|
|
3013
|
+
const revision = Number(resource.revision);
|
|
3014
|
+
if (!id || !Number.isInteger(collectionRevision) || collectionRevision < 0 || !Number.isInteger(revision) || revision < 0) return;
|
|
3015
|
+
scheduleResourceBroadcast({
|
|
3016
|
+
domain,
|
|
3017
|
+
id,
|
|
3018
|
+
collectionRevision,
|
|
3019
|
+
kind: "updated",
|
|
3020
|
+
message: {
|
|
3021
|
+
type: `${domain}-resource-updated`,
|
|
3022
|
+
resource
|
|
3023
|
+
}
|
|
3024
|
+
});
|
|
3025
|
+
}
|
|
3026
|
+
function scheduleResourceDeletion(domain, deletion) {
|
|
3027
|
+
if (!isRecord(deletion)) return;
|
|
3028
|
+
const id = typeof deletion.id === "string" ? deletion.id : "";
|
|
3029
|
+
const collectionRevision = Number(deletion.collectionRevision);
|
|
3030
|
+
if (!id || !Number.isInteger(collectionRevision) || collectionRevision < 0) return;
|
|
3031
|
+
scheduleResourceBroadcast({
|
|
3032
|
+
domain,
|
|
3033
|
+
id,
|
|
3034
|
+
collectionRevision,
|
|
3035
|
+
kind: "deleted",
|
|
3036
|
+
message: {
|
|
3037
|
+
type: `${domain}-resource-deleted`,
|
|
3038
|
+
deletion
|
|
3039
|
+
}
|
|
3040
|
+
});
|
|
3041
|
+
}
|
|
3042
|
+
browserResourceManager.on("resource", (resource) => scheduleResourceUpdate("browser", resource));
|
|
3043
|
+
browserResourceManager.on("deleted", (deletion) => scheduleResourceDeletion("browser", deletion));
|
|
3044
|
+
computerResourceManager.on("resource", (resource) => scheduleResourceUpdate("computer", resource));
|
|
3045
|
+
computerResourceManager.on("deleted", (deletion) => scheduleResourceDeletion("computer", deletion));
|
|
3046
|
+
function broadcastState() {
|
|
3047
|
+
lastStateBroadcastAt = Date.now();
|
|
3048
|
+
stateBroadcastTimer = null;
|
|
3049
|
+
const state = buildStatePayload();
|
|
3050
|
+
const message = JSON.stringify({ type: "state", state });
|
|
3051
|
+
wss.clients.forEach((client) => {
|
|
3052
|
+
if (client.readyState === WebSocket.OPEN) {
|
|
3053
|
+
client.send(message);
|
|
3054
|
+
}
|
|
3055
|
+
});
|
|
3056
|
+
}
|
|
3057
|
+
function scheduleBroadcastState() {
|
|
3058
|
+
const now = Date.now();
|
|
3059
|
+
const elapsed = now - lastStateBroadcastAt;
|
|
3060
|
+
if (elapsed >= STATE_BROADCAST_INTERVAL_MS) {
|
|
3061
|
+
broadcastState();
|
|
3062
|
+
return;
|
|
3063
|
+
}
|
|
3064
|
+
if (stateBroadcastTimer) {
|
|
3065
|
+
return;
|
|
3066
|
+
}
|
|
3067
|
+
stateBroadcastTimer = setTimeout(() => {
|
|
3068
|
+
broadcastState();
|
|
3069
|
+
}, STATE_BROADCAST_INTERVAL_MS - elapsed);
|
|
3070
|
+
}
|
|
3071
|
+
function broadcastSessionPreview(preview) {
|
|
3072
|
+
wss.clients.forEach((client) => {
|
|
3073
|
+
const previewAllowed = client.previewScope !== "none" && (client.previewScope !== "focused" || client.focusedAgentId === preview.agentId);
|
|
3074
|
+
if (client.readyState === WebSocket.OPEN && previewAllowed) {
|
|
3075
|
+
sendPreview(client, preview);
|
|
3076
|
+
}
|
|
3077
|
+
});
|
|
3078
|
+
}
|
|
3079
|
+
function schedulePreviewBroadcast(preview) {
|
|
3080
|
+
const agentId = preview && preview.agentId;
|
|
3081
|
+
if (!agentId) {
|
|
3082
|
+
broadcastSessionPreview(preview);
|
|
3083
|
+
return;
|
|
3084
|
+
}
|
|
3085
|
+
const now = Date.now();
|
|
3086
|
+
const entry = pendingPreviewBroadcasts.get(agentId) || {
|
|
3087
|
+
lastAt: 0,
|
|
3088
|
+
timer: null,
|
|
3089
|
+
preview: null
|
|
3090
|
+
};
|
|
3091
|
+
entry.preview = preview;
|
|
3092
|
+
const elapsed = now - entry.lastAt;
|
|
3093
|
+
if (elapsed >= PREVIEW_BROADCAST_INTERVAL_MS) {
|
|
3094
|
+
if (entry.timer) {
|
|
3095
|
+
clearTimeout(entry.timer);
|
|
3096
|
+
entry.timer = null;
|
|
3097
|
+
}
|
|
3098
|
+
entry.lastAt = now;
|
|
3099
|
+
const latest = entry.preview;
|
|
3100
|
+
entry.preview = null;
|
|
3101
|
+
pendingPreviewBroadcasts.set(agentId, entry);
|
|
3102
|
+
broadcastSessionPreview(latest);
|
|
3103
|
+
return;
|
|
3104
|
+
}
|
|
3105
|
+
if (!entry.timer) {
|
|
3106
|
+
entry.timer = setTimeout(() => {
|
|
3107
|
+
entry.timer = null;
|
|
3108
|
+
entry.lastAt = Date.now();
|
|
3109
|
+
const latest = entry.preview;
|
|
3110
|
+
entry.preview = null;
|
|
3111
|
+
pendingPreviewBroadcasts.set(agentId, entry);
|
|
3112
|
+
if (latest) {
|
|
3113
|
+
broadcastSessionPreview(latest);
|
|
3114
|
+
}
|
|
3115
|
+
}, PREVIEW_BROADCAST_INTERVAL_MS - elapsed);
|
|
3116
|
+
}
|
|
3117
|
+
pendingPreviewBroadcasts.set(agentId, entry);
|
|
3118
|
+
}
|
|
3119
|
+
agentManager.onUpdate(() => {
|
|
3120
|
+
scheduleBroadcastState();
|
|
3121
|
+
});
|
|
3122
|
+
agentManager.on("provider-session-updated", () => {
|
|
3123
|
+
agentSessionInventory.invalidate();
|
|
3124
|
+
scheduleBroadcastState();
|
|
3125
|
+
});
|
|
3126
|
+
function broadcastAgentActivity(activity) {
|
|
3127
|
+
const message = JSON.stringify({
|
|
3128
|
+
type: "agent-activity",
|
|
3129
|
+
activity
|
|
3130
|
+
});
|
|
3131
|
+
wss.clients.forEach((client) => {
|
|
3132
|
+
if (client.readyState === WebSocket.OPEN) {
|
|
3133
|
+
client.send(message);
|
|
3134
|
+
}
|
|
3135
|
+
});
|
|
3136
|
+
}
|
|
3137
|
+
function scheduleAgentActivityBroadcast(activity) {
|
|
3138
|
+
if (!isAgentScopedServerEvent(activity)) return;
|
|
3139
|
+
const agentId = activity.agentId;
|
|
3140
|
+
const existing = pendingAgentActivityBroadcasts.get(agentId);
|
|
3141
|
+
if (existing) {
|
|
3142
|
+
existing.activity = activity;
|
|
3143
|
+
return;
|
|
3144
|
+
}
|
|
3145
|
+
const entry = {
|
|
3146
|
+
activity,
|
|
3147
|
+
timer: setTimeout(() => {
|
|
3148
|
+
pendingAgentActivityBroadcasts.delete(agentId);
|
|
3149
|
+
broadcastAgentActivity(entry.activity);
|
|
3150
|
+
}, AGENT_ACTIVITY_BROADCAST_DELAY_MS)
|
|
3151
|
+
};
|
|
3152
|
+
entry.timer.unref?.();
|
|
3153
|
+
pendingAgentActivityBroadcasts.set(agentId, entry);
|
|
3154
|
+
}
|
|
3155
|
+
agentManager.onAgentActivity(scheduleAgentActivityBroadcast);
|
|
3156
|
+
function scheduleAgentUpdate(update) {
|
|
3157
|
+
if (!isAgentScopedServerEvent(update)) return;
|
|
3158
|
+
const patch = sanitizeAgentUpdatePatch(update?.patch);
|
|
3159
|
+
if (!patch) return;
|
|
3160
|
+
const existing = pendingAgentUpdates.get(update.agentId);
|
|
3161
|
+
if (existing) {
|
|
3162
|
+
Object.assign(existing.patch, patch);
|
|
3163
|
+
return;
|
|
3164
|
+
}
|
|
3165
|
+
const entry = {
|
|
3166
|
+
patch,
|
|
3167
|
+
timer: setTimeout(() => {
|
|
3168
|
+
pendingAgentUpdates.delete(update.agentId);
|
|
3169
|
+
const message = JSON.stringify({
|
|
3170
|
+
type: "agent-update",
|
|
3171
|
+
update: { agentId: update.agentId, patch: entry.patch }
|
|
3172
|
+
});
|
|
3173
|
+
wss.clients.forEach((client) => {
|
|
3174
|
+
if (client.readyState === WebSocket.OPEN) client.send(message);
|
|
3175
|
+
});
|
|
3176
|
+
}, AGENT_ACTIVITY_BROADCAST_DELAY_MS)
|
|
3177
|
+
};
|
|
3178
|
+
entry.timer.unref?.();
|
|
3179
|
+
pendingAgentUpdates.set(update.agentId, entry);
|
|
3180
|
+
}
|
|
3181
|
+
agentManager.on("agent-update", scheduleAgentUpdate);
|
|
3182
|
+
function scheduleAcpSessionRevision(session) {
|
|
3183
|
+
if (!isAgentScopedServerEvent(session) || !Number.isFinite(Number(session.revision)) || typeof session.updatedAt !== "string") return;
|
|
3184
|
+
const previous = pendingAcpSessionRevisions.get(session.agentId);
|
|
3185
|
+
if (previous) {
|
|
3186
|
+
if (Number(session.revision) >= Number(previous.session.revision)) {
|
|
3187
|
+
previous.session = session;
|
|
3188
|
+
}
|
|
3189
|
+
return;
|
|
3190
|
+
}
|
|
3191
|
+
const entry = {
|
|
3192
|
+
session,
|
|
3193
|
+
timer: setTimeout(() => {
|
|
3194
|
+
pendingAcpSessionRevisions.delete(session.agentId);
|
|
3195
|
+
const message = JSON.stringify({
|
|
3196
|
+
type: "acp-session-revision",
|
|
3197
|
+
session: entry.session
|
|
3198
|
+
});
|
|
3199
|
+
wss.clients.forEach((client) => {
|
|
3200
|
+
if (client.readyState === WebSocket.OPEN) client.send(message);
|
|
3201
|
+
});
|
|
3202
|
+
}, AGENT_ACTIVITY_BROADCAST_DELAY_MS)
|
|
3203
|
+
};
|
|
3204
|
+
entry.timer.unref?.();
|
|
3205
|
+
pendingAcpSessionRevisions.set(session.agentId, entry);
|
|
3206
|
+
}
|
|
3207
|
+
agentManager.on("acp-session-revision", scheduleAcpSessionRevision);
|
|
3208
|
+
function broadcastAgentRead(read) {
|
|
3209
|
+
if (!isAgentScopedServerEvent(read)) return;
|
|
3210
|
+
const message = JSON.stringify({ type: "agent-read", read });
|
|
3211
|
+
wss.clients.forEach((client) => {
|
|
3212
|
+
if (client.readyState === WebSocket.OPEN) {
|
|
3213
|
+
client.send(message);
|
|
3214
|
+
}
|
|
3215
|
+
});
|
|
3216
|
+
}
|
|
3217
|
+
agentManager.on("agent-read", broadcastAgentRead);
|
|
3218
|
+
function sessionStreamKey(stream) {
|
|
3219
|
+
return `${stream.agentId}\0${stream.sessionSource || ""}`;
|
|
3220
|
+
}
|
|
3221
|
+
function broadcastSessionStream(stream) {
|
|
3222
|
+
const message = JSON.stringify({
|
|
3223
|
+
type: "session-output",
|
|
3224
|
+
stream
|
|
3225
|
+
});
|
|
3226
|
+
(0, import_session_stream_protocol.deliverSessionStreamToClients)(Array.from(wss.clients), stream, {
|
|
3227
|
+
openState: WebSocket.OPEN,
|
|
3228
|
+
maxBufferedAmount: MAX_SESSION_STREAM_CLIENT_BUFFERED_AMOUNT,
|
|
3229
|
+
message
|
|
3230
|
+
});
|
|
3231
|
+
}
|
|
3232
|
+
function flushSessionStreams() {
|
|
3233
|
+
sessionStreamBroadcastTimer = null;
|
|
3234
|
+
const streams = Array.from(pendingSessionStreams.values());
|
|
3235
|
+
pendingSessionStreams.clear();
|
|
3236
|
+
if (streams.length > 0) lastSessionStreamBroadcastAt = Date.now();
|
|
3237
|
+
streams.forEach(broadcastSessionStream);
|
|
3238
|
+
}
|
|
3239
|
+
function scheduleSessionStreamBroadcast(stream) {
|
|
3240
|
+
if (!isAgentScopedServerEvent(stream)) return;
|
|
3241
|
+
const now = Date.now();
|
|
3242
|
+
if ((0, import_session_stream_protocol.shouldBroadcastSessionStreamImmediately)({
|
|
3243
|
+
pendingCount: pendingSessionStreams.size,
|
|
3244
|
+
lastBroadcastAt: lastSessionStreamBroadcastAt,
|
|
3245
|
+
now,
|
|
3246
|
+
intervalMs: SESSION_STREAM_BROADCAST_INTERVAL_MS
|
|
3247
|
+
})) {
|
|
3248
|
+
lastSessionStreamBroadcastAt = now;
|
|
3249
|
+
broadcastSessionStream((0, import_session_stream_protocol.coalesceSessionStream)(null, stream));
|
|
3250
|
+
return;
|
|
3251
|
+
}
|
|
3252
|
+
const key = sessionStreamKey(stream);
|
|
3253
|
+
const existing = pendingSessionStreams.get(key);
|
|
3254
|
+
pendingSessionStreams.set(key, (0, import_session_stream_protocol.coalesceSessionStream)(existing, stream));
|
|
3255
|
+
if (!sessionStreamBroadcastTimer) {
|
|
3256
|
+
sessionStreamBroadcastTimer = setTimeout(flushSessionStreams, SESSION_STREAM_BROADCAST_INTERVAL_MS);
|
|
3257
|
+
if (typeof sessionStreamBroadcastTimer.unref === "function") sessionStreamBroadcastTimer.unref();
|
|
3258
|
+
}
|
|
3259
|
+
}
|
|
3260
|
+
agentManager.onSessionStream((stream) => {
|
|
3261
|
+
scheduleSessionStreamBroadcast(stream);
|
|
3262
|
+
});
|
|
3263
|
+
agentManager.onSessionPreview((preview) => {
|
|
3264
|
+
if (isAgentScopedServerEvent(preview)) schedulePreviewBroadcast(preview);
|
|
3265
|
+
});
|
|
3266
|
+
agentManager.onSystemStats((systemStats) => {
|
|
3267
|
+
if (!isRecord(systemStats)) return;
|
|
3268
|
+
const usageSnapshot = agentManager.getAgentUsageSnapshots();
|
|
3269
|
+
const message = JSON.stringify({
|
|
3270
|
+
type: "system-stats",
|
|
3271
|
+
stats: {
|
|
3272
|
+
...systemStats,
|
|
3273
|
+
ip: (0, import_network.getPrimaryLocalIP)(),
|
|
3274
|
+
timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone
|
|
3275
|
+
},
|
|
3276
|
+
uptime: agentManager.getUptime(),
|
|
3277
|
+
usageRate: {
|
|
3278
|
+
windowMs: usageSnapshot.windowMs,
|
|
3279
|
+
estimatedTokensPerMinute: usageSnapshot.estimatedTokensPerMinute,
|
|
3280
|
+
source: usageSnapshot.source
|
|
3281
|
+
}
|
|
3282
|
+
});
|
|
3283
|
+
wss.clients.forEach((client) => {
|
|
3284
|
+
if (client.readyState === WebSocket.OPEN) {
|
|
3285
|
+
client.send(message);
|
|
3286
|
+
}
|
|
3287
|
+
});
|
|
3288
|
+
});
|
|
3289
|
+
let serverStarted = false;
|
|
3290
|
+
let terminalRuntimeCleanupPromise = null;
|
|
3291
|
+
function runTerminalRuntimeStartupCleanup() {
|
|
3292
|
+
if (!terminalRuntimeCleanupPromise) {
|
|
3293
|
+
terminalRuntimeCleanupPromise = (0, import_terminal_runtime_cleanup.cleanupTerminalRuntime)({ configDir: configManager.farmingDir }).catch((error) => {
|
|
3294
|
+
console.warn("Failed to clean terminal runtime leftovers:", caughtError(error).message || error);
|
|
3295
|
+
return null;
|
|
3296
|
+
});
|
|
3297
|
+
}
|
|
3298
|
+
return terminalRuntimeCleanupPromise;
|
|
3299
|
+
}
|
|
3300
|
+
function startServer() {
|
|
3301
|
+
if (serverStarted) return server;
|
|
3302
|
+
serverStarted = true;
|
|
3303
|
+
void Promise.all([
|
|
3304
|
+
runTerminalRuntimeStartupCleanup(),
|
|
3305
|
+
browserRuntimeRecoveryPromise,
|
|
3306
|
+
computerRuntimeRecoveryPromise
|
|
3307
|
+
]).finally(() => {
|
|
3308
|
+
server.listen(PORT, () => {
|
|
3309
|
+
const token = tokenAuth.getToken();
|
|
3310
|
+
const localIPs = (0, import_network.getLocalIPs)();
|
|
3311
|
+
const entryPath = BASE_PATH || "/";
|
|
3312
|
+
const entrySuffix = authEnabled ? `${entryPath}?token=${token}` : entryPath;
|
|
3313
|
+
console.log("");
|
|
3314
|
+
console.log(" Farming server running on:");
|
|
3315
|
+
console.log("");
|
|
3316
|
+
console.log(` Local: http://localhost:${PORT}${entrySuffix}`);
|
|
3317
|
+
localIPs.forEach((ip) => {
|
|
3318
|
+
console.log(` Network: http://${ip}:${PORT}${entrySuffix}`);
|
|
3319
|
+
});
|
|
3320
|
+
console.log("");
|
|
3321
|
+
if (authEnabled) {
|
|
3322
|
+
console.log(` Token: ${token}`);
|
|
3323
|
+
const tokenInfo = tokenAuth.getTokenInfo();
|
|
3324
|
+
if (tokenInfo) {
|
|
3325
|
+
console.log(` Token style: ${tokenInfo.style} (${tokenInfo.source}, ~${tokenInfo.entropyBits} bits)`);
|
|
3326
|
+
}
|
|
3327
|
+
} else {
|
|
3328
|
+
console.log(" Token auth: disabled");
|
|
3329
|
+
}
|
|
3330
|
+
console.log("");
|
|
3331
|
+
setTimeout(warmCodexExecutableVersionCache, 100);
|
|
3332
|
+
void autoResumeMainPageAgentSessions();
|
|
3333
|
+
});
|
|
3334
|
+
});
|
|
3335
|
+
return server;
|
|
3336
|
+
}
|
|
3337
|
+
if (require.main === module) {
|
|
3338
|
+
startServer();
|
|
3339
|
+
}
|
|
3340
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
3341
|
+
0 && (module.exports = {
|
|
3342
|
+
agentManager,
|
|
3343
|
+
app,
|
|
3344
|
+
appendIndexHtmlAssetToken,
|
|
3345
|
+
browserResourceManager,
|
|
3346
|
+
computerResourceManager,
|
|
3347
|
+
handleMessage,
|
|
3348
|
+
resolveCliBinDir,
|
|
3349
|
+
resolveInputTargetAgentId,
|
|
3350
|
+
rewriteIndexHtmlForBasePath,
|
|
3351
|
+
runTerminalRuntimeStartupCleanup,
|
|
3352
|
+
server,
|
|
3353
|
+
startServer,
|
|
3354
|
+
workspaceFileService,
|
|
3355
|
+
wss
|
|
3356
|
+
});
|