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
|
@@ -1,172 +1,246 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
2
|
+
// Generated by scripts/build-backend-runtime.ts. Do not edit.
|
|
3
|
+
"use strict";
|
|
4
|
+
var __create = Object.create;
|
|
5
|
+
var __defProp = Object.defineProperty;
|
|
6
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
7
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
8
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
9
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
10
|
+
var __export = (target, all) => {
|
|
11
|
+
for (var name in all)
|
|
12
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
13
|
+
};
|
|
14
|
+
var __copyProps = (to, from, except, desc) => {
|
|
15
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
16
|
+
for (let key of __getOwnPropNames(from))
|
|
17
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
18
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
19
|
+
}
|
|
20
|
+
return to;
|
|
21
|
+
};
|
|
22
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
23
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
24
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
25
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
26
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
27
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
28
|
+
mod
|
|
29
|
+
));
|
|
30
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
31
|
+
var native_pty_host_exports = {};
|
|
32
|
+
__export(native_pty_host_exports, {
|
|
33
|
+
NativePtyHost: () => NativePtyHost,
|
|
34
|
+
startNativePtyHostProcess: () => startNativePtyHostProcess
|
|
35
|
+
});
|
|
36
|
+
module.exports = __toCommonJS(native_pty_host_exports);
|
|
37
|
+
var crypto = __toESM(require("crypto"));
|
|
38
|
+
var fs = __toESM(require("fs"));
|
|
39
|
+
var net = __toESM(require("net"));
|
|
40
|
+
var os = __toESM(require("os"));
|
|
41
|
+
var path = __toESM(require("path"));
|
|
42
|
+
var import_terminal_screen_worker_pool = require("./terminal-screen-worker-pool.cjs");
|
|
43
|
+
var nativePtyHostPathModule = __toESM(require("./native-pty-host-path.cjs"));
|
|
44
|
+
var inputPartsModule = __toESM(require("./input-parts.cjs"));
|
|
45
|
+
var agentEnvModule = __toESM(require("./agent-env.cjs"));
|
|
46
|
+
var shellBusyIntegrationModule = __toESM(require("./shell-busy-integration.cjs"));
|
|
47
|
+
var localSessionEngineModule = __toESM(require("./local-session-engine.cjs"));
|
|
48
|
+
var terminalStatusModule = __toESM(require("./terminal-status.cjs"));
|
|
49
|
+
var terminalRuntimeCleanupModule = __toESM(require("./terminal-runtime-cleanup.cjs"));
|
|
50
|
+
var nativePtyHostIdentityModule = __toESM(require("./native-pty-host-identity.cjs"));
|
|
51
|
+
var runtimeGenerationModule = __toESM(require("./native-pty-controller-generation.cjs"));
|
|
52
|
+
var reducerFlowControlModule = __toESM(require("./terminal-reducer-flow-control.cjs"));
|
|
53
|
+
var terminalStateSerializationModule = __toESM(require("./terminal-state-serialization.cjs"));
|
|
54
|
+
var terminalAttachCheckpointModule = __toESM(require("./terminal-attach-checkpoint.cjs"));
|
|
55
|
+
var terminalExitQuiescenceModule = __toESM(require("./terminal-exit-quiescence.cjs"));
|
|
10
56
|
const {
|
|
11
57
|
nativePtyHostPrivateSocketPath,
|
|
12
|
-
nativePtyHostSocketPath
|
|
13
|
-
} =
|
|
14
|
-
const { terminalInputToPtyString } =
|
|
15
|
-
const { normalizeInteractiveTerminalEnv } =
|
|
58
|
+
nativePtyHostSocketPath
|
|
59
|
+
} = nativePtyHostPathModule;
|
|
60
|
+
const { terminalInputToPtyString } = inputPartsModule;
|
|
61
|
+
const { normalizeInteractiveTerminalEnv } = agentEnvModule;
|
|
16
62
|
const {
|
|
17
63
|
cleanupShellBusyIntegration,
|
|
18
|
-
parseShellBusyMarkers
|
|
19
|
-
} =
|
|
64
|
+
parseShellBusyMarkers
|
|
65
|
+
} = shellBusyIntegrationModule;
|
|
20
66
|
const {
|
|
21
67
|
extractLatestTerminalTitle,
|
|
22
68
|
normalizeShellSessionOptions,
|
|
23
|
-
createPtyProcess
|
|
24
|
-
} =
|
|
25
|
-
const { deriveTerminalStatus } =
|
|
26
|
-
const { probeUnixSocket } =
|
|
27
|
-
const { nativePtyHostRuntimeIdentity } =
|
|
69
|
+
createPtyProcess
|
|
70
|
+
} = localSessionEngineModule;
|
|
71
|
+
const { deriveTerminalStatus } = terminalStatusModule;
|
|
72
|
+
const { probeUnixSocket } = terminalRuntimeCleanupModule;
|
|
73
|
+
const { nativePtyHostRuntimeIdentity } = nativePtyHostIdentityModule;
|
|
28
74
|
const {
|
|
29
75
|
allocateNativePtyRuntimeGeneration,
|
|
30
|
-
formatNativePtyRuntimeEpoch
|
|
31
|
-
} =
|
|
76
|
+
formatNativePtyRuntimeEpoch
|
|
77
|
+
} = runtimeGenerationModule;
|
|
32
78
|
const {
|
|
33
79
|
acknowledgeTerminalReducerData,
|
|
34
80
|
createTerminalReducerFlowControl,
|
|
35
81
|
ensureTerminalReducerFlowControl,
|
|
36
82
|
enqueueTerminalReducerData,
|
|
37
83
|
resetTerminalReducerFlowControl,
|
|
38
|
-
setTerminalExternalFlowControlBlocked
|
|
39
|
-
} =
|
|
84
|
+
setTerminalExternalFlowControlBlocked
|
|
85
|
+
} = reducerFlowControlModule;
|
|
40
86
|
const {
|
|
41
87
|
normalizeTerminalStateEntry,
|
|
42
|
-
serializeTerminalState
|
|
43
|
-
} =
|
|
44
|
-
const {
|
|
45
|
-
captureTerminalAttachCheckpoint,
|
|
46
|
-
} = require('./terminal-attach-checkpoint');
|
|
88
|
+
serializeTerminalState
|
|
89
|
+
} = terminalStateSerializationModule;
|
|
90
|
+
const { captureTerminalAttachCheckpoint } = terminalAttachCheckpointModule;
|
|
47
91
|
const {
|
|
48
92
|
acceptTerminalExitData,
|
|
49
|
-
waitForTerminalExitDataQuiescence
|
|
50
|
-
} =
|
|
51
|
-
|
|
52
|
-
const OUTPUT_LIMIT = 10000;
|
|
93
|
+
waitForTerminalExitDataQuiescence
|
|
94
|
+
} = terminalExitQuiescenceModule;
|
|
95
|
+
const OUTPUT_LIMIT = 1e4;
|
|
53
96
|
const DEFAULT_COLS = 80;
|
|
54
97
|
const DEFAULT_ROWS = 30;
|
|
55
|
-
const OWNER_CHECK_INTERVAL_MS =
|
|
98
|
+
const OWNER_CHECK_INTERVAL_MS = 1e3;
|
|
56
99
|
const DISPOSE_PTY_GRACE_MS = 800;
|
|
57
|
-
const DEFAULT_IDLE_EXIT_MS =
|
|
100
|
+
const DEFAULT_IDLE_EXIT_MS = 6e4;
|
|
58
101
|
const DEFAULT_CLIENT_MAX_BUFFERED_BYTES = 16 * 1024 * 1024;
|
|
59
102
|
const DEFAULT_CLIENT_MAX_REQUEST_BYTES = 16 * 1024 * 1024;
|
|
60
103
|
const HOST_RUNTIME_IDENTITY = nativePtyHostRuntimeIdentity();
|
|
61
|
-
const CONTROLLER_MUTATION_ADMISSION = Symbol(
|
|
62
|
-
const TERMINAL_HISTORY_RESTORED_MESSAGE =
|
|
63
|
-
|
|
104
|
+
const CONTROLLER_MUTATION_ADMISSION = /* @__PURE__ */ Symbol("controllerMutationAdmission");
|
|
105
|
+
const TERMINAL_HISTORY_RESTORED_MESSAGE = "\r\n\x1B[7m History restored \x1B[0m\r\n";
|
|
106
|
+
function errorMessage(error) {
|
|
107
|
+
if (error instanceof Error) return error.message;
|
|
108
|
+
if (typeof error === "object" && error !== null && "message" in error) {
|
|
109
|
+
return String(error.message || "");
|
|
110
|
+
}
|
|
111
|
+
return "";
|
|
112
|
+
}
|
|
113
|
+
function errorCode(error) {
|
|
114
|
+
return typeof error === "object" && error !== null && "code" in error ? String(error.code || "") : "";
|
|
115
|
+
}
|
|
116
|
+
function isControllerAdmission(value) {
|
|
117
|
+
return CONTROLLER_MUTATION_ADMISSION in value && value[CONTROLLER_MUTATION_ADMISSION] === true;
|
|
118
|
+
}
|
|
64
119
|
function trimOutput(output) {
|
|
65
|
-
const text = typeof output ===
|
|
120
|
+
const text = typeof output === "string" ? output : "";
|
|
66
121
|
return text.length > OUTPUT_LIMIT ? text.slice(-OUTPUT_LIMIT) : text;
|
|
67
122
|
}
|
|
68
|
-
|
|
69
123
|
function normalizePositiveInteger(value, fallback, min, max) {
|
|
70
124
|
const parsed = Math.floor(Number(value));
|
|
71
125
|
if (!Number.isFinite(parsed)) return fallback;
|
|
72
126
|
return Math.max(min, Math.min(max, parsed));
|
|
73
127
|
}
|
|
74
|
-
|
|
75
128
|
function sanitizeAgentEnv(env) {
|
|
76
|
-
const next = { ...
|
|
129
|
+
const next = { ...env || process.env };
|
|
77
130
|
return normalizeInteractiveTerminalEnv(next, {
|
|
78
131
|
stripRuntimeShims: false,
|
|
79
|
-
stripNodeOptions: false
|
|
132
|
+
stripNodeOptions: false
|
|
80
133
|
});
|
|
81
134
|
}
|
|
82
|
-
|
|
83
135
|
function socketIdentity(socketPath) {
|
|
84
136
|
const stat = fs.statSync(socketPath, { bigint: true });
|
|
85
137
|
return { dev: stat.dev, ino: stat.ino };
|
|
86
138
|
}
|
|
87
|
-
|
|
88
139
|
function sameSocketIdentity(left, right) {
|
|
89
140
|
return Boolean(left && right && left.dev === right.dev && left.ino === right.ino);
|
|
90
141
|
}
|
|
91
|
-
|
|
92
142
|
class NativePtyHost {
|
|
143
|
+
configDir;
|
|
144
|
+
socketPath;
|
|
145
|
+
ownerPid;
|
|
146
|
+
runtimeIdentity;
|
|
147
|
+
exitOnShutdown;
|
|
148
|
+
boundSocketPath;
|
|
149
|
+
boundSocketIdentity;
|
|
150
|
+
sessions;
|
|
151
|
+
clients;
|
|
152
|
+
ownerCheckTimer;
|
|
153
|
+
idleExitTimer;
|
|
154
|
+
hasAcceptedClient;
|
|
155
|
+
activeControllerClient;
|
|
156
|
+
activeControllerIdentity;
|
|
157
|
+
sessionMutationQueues;
|
|
158
|
+
activeControllerMutations;
|
|
159
|
+
controllerRegistrationQueue;
|
|
160
|
+
controllerHandoff;
|
|
161
|
+
rotationPreparation;
|
|
162
|
+
terminalExitDataFlushMs;
|
|
163
|
+
clientMaxBufferedBytes;
|
|
164
|
+
clientMaxRequestBytes;
|
|
165
|
+
idleExitMs;
|
|
166
|
+
disposed;
|
|
167
|
+
screenWorkerPool;
|
|
168
|
+
server;
|
|
93
169
|
constructor(options = {}) {
|
|
94
|
-
this.configDir = options.configDir || process.env.FARMING_CONFIG_DIR || path.join(os.homedir(),
|
|
170
|
+
this.configDir = options.configDir || process.env.FARMING_CONFIG_DIR || path.join(os.homedir(), ".farming");
|
|
95
171
|
this.socketPath = options.socketPath || process.env.FARMING_NATIVE_PTY_HOST_SOCKET || nativePtyHostSocketPath(this.configDir);
|
|
96
172
|
this.ownerPid = Number(options.ownerPid || process.env.FARMING_NATIVE_PTY_HOST_OWNER_PID || 0);
|
|
97
173
|
this.runtimeIdentity = options.runtimeIdentity || HOST_RUNTIME_IDENTITY;
|
|
98
174
|
this.exitOnShutdown = options.exitOnShutdown !== false;
|
|
99
|
-
this.boundSocketPath = process.platform ===
|
|
100
|
-
? this.socketPath
|
|
101
|
-
: nativePtyHostPrivateSocketPath(this.socketPath);
|
|
175
|
+
this.boundSocketPath = process.platform === "win32" ? this.socketPath : nativePtyHostPrivateSocketPath(this.socketPath);
|
|
102
176
|
this.boundSocketIdentity = null;
|
|
103
|
-
this.sessions = new Map();
|
|
104
|
-
this.clients = new Set();
|
|
177
|
+
this.sessions = /* @__PURE__ */ new Map();
|
|
178
|
+
this.clients = /* @__PURE__ */ new Set();
|
|
105
179
|
this.ownerCheckTimer = null;
|
|
106
180
|
this.idleExitTimer = null;
|
|
107
181
|
this.hasAcceptedClient = false;
|
|
108
182
|
this.activeControllerClient = null;
|
|
109
183
|
this.activeControllerIdentity = null;
|
|
110
|
-
this.sessionMutationQueues = new Map();
|
|
111
|
-
this.activeControllerMutations = new Set();
|
|
184
|
+
this.sessionMutationQueues = /* @__PURE__ */ new Map();
|
|
185
|
+
this.activeControllerMutations = /* @__PURE__ */ new Set();
|
|
112
186
|
this.controllerRegistrationQueue = Promise.resolve();
|
|
113
187
|
this.controllerHandoff = null;
|
|
114
188
|
this.rotationPreparation = null;
|
|
189
|
+
this.terminalExitDataFlushMs = options.terminalExitDataFlushMs;
|
|
115
190
|
this.clientMaxBufferedBytes = normalizePositiveInteger(
|
|
116
191
|
options.clientMaxBufferedBytes,
|
|
117
192
|
DEFAULT_CLIENT_MAX_BUFFERED_BYTES,
|
|
118
193
|
1024,
|
|
119
|
-
256 * 1024 * 1024
|
|
194
|
+
256 * 1024 * 1024
|
|
120
195
|
);
|
|
121
196
|
this.clientMaxRequestBytes = normalizePositiveInteger(
|
|
122
197
|
options.clientMaxRequestBytes,
|
|
123
198
|
DEFAULT_CLIENT_MAX_REQUEST_BYTES,
|
|
124
199
|
1024,
|
|
125
|
-
256 * 1024 * 1024
|
|
200
|
+
256 * 1024 * 1024
|
|
126
201
|
);
|
|
127
202
|
this.idleExitMs = normalizePositiveInteger(
|
|
128
203
|
options.idleExitMs ?? process.env.FARMING_NATIVE_PTY_HOST_IDLE_EXIT_MS,
|
|
129
204
|
DEFAULT_IDLE_EXIT_MS,
|
|
130
205
|
0,
|
|
131
|
-
|
|
206
|
+
36e5
|
|
132
207
|
);
|
|
133
208
|
this.disposed = false;
|
|
134
|
-
this.screenWorkerPool = new TerminalScreenWorkerPool({
|
|
209
|
+
this.screenWorkerPool = new import_terminal_screen_worker_pool.TerminalScreenWorkerPool({
|
|
135
210
|
size: normalizePositiveInteger(process.env.FARMING_NATIVE_PTY_SCREEN_WORKERS, 3, 0, 12),
|
|
136
211
|
workerOptions: {
|
|
137
212
|
cols: DEFAULT_COLS,
|
|
138
213
|
rows: DEFAULT_ROWS,
|
|
139
|
-
previewSnapshot: true
|
|
140
|
-
}
|
|
214
|
+
previewSnapshot: true
|
|
215
|
+
}
|
|
141
216
|
});
|
|
142
217
|
}
|
|
143
|
-
|
|
144
218
|
async start() {
|
|
145
219
|
await this.prepareSocket();
|
|
146
|
-
|
|
220
|
+
const server = net.createServer((socket) => this.handleConnection(socket));
|
|
221
|
+
this.server = server;
|
|
147
222
|
await new Promise((resolve, reject) => {
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
223
|
+
server.once("error", reject);
|
|
224
|
+
server.listen(this.boundSocketPath, () => {
|
|
225
|
+
server.off("error", reject);
|
|
151
226
|
resolve();
|
|
152
227
|
});
|
|
153
228
|
});
|
|
154
|
-
if (process.platform !==
|
|
229
|
+
if (process.platform !== "win32") {
|
|
155
230
|
this.boundSocketIdentity = socketIdentity(this.boundSocketPath);
|
|
156
|
-
fs.chmodSync(this.boundSocketPath,
|
|
231
|
+
fs.chmodSync(this.boundSocketPath, 384);
|
|
157
232
|
try {
|
|
158
233
|
fs.linkSync(this.boundSocketPath, this.socketPath);
|
|
159
234
|
} catch (error) {
|
|
160
|
-
await new Promise(resolve => this.server
|
|
235
|
+
await new Promise((resolve) => this.server?.close(() => resolve()));
|
|
161
236
|
this.server = null;
|
|
162
237
|
this.boundSocketIdentity = null;
|
|
163
238
|
throw error;
|
|
164
239
|
}
|
|
165
240
|
}
|
|
166
|
-
console.error(`[${new Date().toISOString()}] Native PTY host listening on ${this.socketPath} ownerPid=${this.ownerPid ||
|
|
241
|
+
console.error(`[${(/* @__PURE__ */ new Date()).toISOString()}] Native PTY host listening on ${this.socketPath} ownerPid=${this.ownerPid || ""}`);
|
|
167
242
|
this.startOwnerWatch();
|
|
168
243
|
}
|
|
169
|
-
|
|
170
244
|
startOwnerWatch() {
|
|
171
245
|
if (!this.ownerPid || this.ownerPid === process.pid || this.ownerCheckTimer) return;
|
|
172
246
|
this.ownerCheckTimer = setInterval(() => {
|
|
@@ -176,19 +250,18 @@ class NativePtyHost {
|
|
|
176
250
|
this.dispose().finally(() => process.exit(0));
|
|
177
251
|
}
|
|
178
252
|
}, OWNER_CHECK_INTERVAL_MS);
|
|
179
|
-
if (typeof this.ownerCheckTimer.unref ===
|
|
253
|
+
if (typeof this.ownerCheckTimer.unref === "function") {
|
|
180
254
|
this.ownerCheckTimer.unref();
|
|
181
255
|
}
|
|
182
256
|
}
|
|
183
|
-
|
|
184
257
|
async prepareSocket() {
|
|
185
|
-
if (process.platform ===
|
|
258
|
+
if (process.platform === "win32") return;
|
|
186
259
|
fs.mkdirSync(path.dirname(this.socketPath), { recursive: true });
|
|
187
260
|
if (fs.existsSync(this.socketPath)) {
|
|
188
261
|
const probe = await probeUnixSocket(this.socketPath);
|
|
189
262
|
if (probe.active) {
|
|
190
263
|
const error = new Error(`Native PTY host socket is already active: ${this.socketPath}`);
|
|
191
|
-
error.code =
|
|
264
|
+
error.code = "EADDRINUSE";
|
|
192
265
|
error.socketPath = this.socketPath;
|
|
193
266
|
throw error;
|
|
194
267
|
}
|
|
@@ -196,57 +269,46 @@ class NativePtyHost {
|
|
|
196
269
|
try {
|
|
197
270
|
fs.unlinkSync(this.socketPath);
|
|
198
271
|
} catch (error) {
|
|
199
|
-
if (
|
|
272
|
+
if (errorCode(error) !== "ENOENT") throw error;
|
|
200
273
|
}
|
|
201
274
|
try {
|
|
202
275
|
fs.unlinkSync(this.boundSocketPath);
|
|
203
276
|
} catch (error) {
|
|
204
|
-
if (
|
|
277
|
+
if (errorCode(error) !== "ENOENT") throw error;
|
|
205
278
|
}
|
|
206
279
|
}
|
|
207
|
-
|
|
208
280
|
handleConnection(socket) {
|
|
209
|
-
const client = { socket, buffer:
|
|
281
|
+
const client = { socket, buffer: "", disconnected: false };
|
|
210
282
|
this.hasAcceptedClient = true;
|
|
211
283
|
this.cancelIdleExit();
|
|
212
284
|
this.clients.add(client);
|
|
213
|
-
socket.on(
|
|
214
|
-
socket.on(
|
|
215
|
-
socket.on(
|
|
285
|
+
socket.on("data", (chunk) => this.handleClientData(client, chunk));
|
|
286
|
+
socket.on("close", () => this.removeClient(client));
|
|
287
|
+
socket.on("error", () => this.removeClient(client));
|
|
216
288
|
}
|
|
217
|
-
|
|
218
289
|
removeClient(client) {
|
|
219
290
|
if (!client || client.disconnected === true) return Promise.resolve();
|
|
220
291
|
client.disconnected = true;
|
|
221
292
|
this.clients.delete(client);
|
|
222
293
|
let retirement = Promise.resolve();
|
|
223
|
-
if (
|
|
224
|
-
this.
|
|
225
|
-
|
|
226
|
-
|| this.controllerHandoff?.toClient === client
|
|
227
|
-
) {
|
|
228
|
-
retirement = this.controllerRegistrationQueue
|
|
229
|
-
.catch(() => {})
|
|
230
|
-
.then(() => this.retireControllerClient(client));
|
|
294
|
+
if (this.activeControllerClient === client || this.controllerHandoff?.fromClient === client || this.controllerHandoff?.toClient === client) {
|
|
295
|
+
retirement = this.controllerRegistrationQueue.catch(() => {
|
|
296
|
+
}).then(() => this.retireControllerClient(client));
|
|
231
297
|
this.controllerRegistrationQueue = retirement;
|
|
232
298
|
}
|
|
233
299
|
this.scheduleIdleExitIfUnused();
|
|
234
300
|
return retirement;
|
|
235
301
|
}
|
|
236
|
-
|
|
237
302
|
async retireControllerClient(client) {
|
|
238
303
|
if (this.activeControllerClient !== client) return;
|
|
239
304
|
const handoff = {
|
|
240
305
|
fromClient: client,
|
|
241
306
|
fromIdentity: this.activeControllerIdentity,
|
|
242
307
|
toClient: null,
|
|
243
|
-
toIdentity: null
|
|
308
|
+
toIdentity: null
|
|
244
309
|
};
|
|
245
310
|
this.controllerHandoff = handoff;
|
|
246
311
|
try {
|
|
247
|
-
// A disconnect closes admission immediately, but an operation that was
|
|
248
|
-
// already admitted owns its linearization point until it completes.
|
|
249
|
-
// Retire the server client only after that exact set has drained.
|
|
250
312
|
await Promise.allSettled([...this.activeControllerMutations]);
|
|
251
313
|
if (this.activeControllerClient !== client) return;
|
|
252
314
|
this.activeControllerClient = null;
|
|
@@ -254,56 +316,38 @@ class NativePtyHost {
|
|
|
254
316
|
if (this.controllerHandoff === handoff) this.controllerHandoff = null;
|
|
255
317
|
}
|
|
256
318
|
}
|
|
257
|
-
|
|
258
319
|
hasLiveSessions() {
|
|
259
320
|
for (const session of this.sessions.values()) {
|
|
260
|
-
if (session && session.status !==
|
|
321
|
+
if (session && session.status !== "exited") return true;
|
|
261
322
|
}
|
|
262
323
|
return false;
|
|
263
324
|
}
|
|
264
|
-
|
|
265
325
|
cancelIdleExit() {
|
|
266
326
|
if (!this.idleExitTimer) return;
|
|
267
327
|
clearTimeout(this.idleExitTimer);
|
|
268
328
|
this.idleExitTimer = null;
|
|
269
329
|
}
|
|
270
|
-
|
|
271
330
|
scheduleIdleExitIfUnused() {
|
|
272
|
-
if (
|
|
273
|
-
this.disposed ||
|
|
274
|
-
this.ownerPid ||
|
|
275
|
-
this.idleExitMs <= 0 ||
|
|
276
|
-
!this.hasAcceptedClient ||
|
|
277
|
-
this.clients.size > 0 ||
|
|
278
|
-
this.hasLiveSessions() ||
|
|
279
|
-
this.idleExitTimer
|
|
280
|
-
) {
|
|
331
|
+
if (this.disposed || this.ownerPid || this.idleExitMs <= 0 || !this.hasAcceptedClient || this.clients.size > 0 || this.hasLiveSessions() || this.idleExitTimer) {
|
|
281
332
|
return;
|
|
282
333
|
}
|
|
283
|
-
|
|
284
334
|
this.idleExitTimer = setTimeout(() => {
|
|
285
335
|
this.idleExitTimer = null;
|
|
286
|
-
if (
|
|
287
|
-
this.disposed ||
|
|
288
|
-
this.ownerPid ||
|
|
289
|
-
this.clients.size > 0 ||
|
|
290
|
-
this.hasLiveSessions()
|
|
291
|
-
) {
|
|
336
|
+
if (this.disposed || this.ownerPid || this.clients.size > 0 || this.hasLiveSessions()) {
|
|
292
337
|
return;
|
|
293
338
|
}
|
|
294
339
|
this.dispose().finally(() => process.exit(0));
|
|
295
340
|
}, this.idleExitMs);
|
|
296
|
-
if (typeof this.idleExitTimer.unref ===
|
|
341
|
+
if (typeof this.idleExitTimer.unref === "function") {
|
|
297
342
|
this.idleExitTimer.unref();
|
|
298
343
|
}
|
|
299
344
|
}
|
|
300
|
-
|
|
301
345
|
handleClientData(client, chunk) {
|
|
302
|
-
client.buffer += chunk.toString(
|
|
303
|
-
let newline = client.buffer.indexOf(
|
|
346
|
+
client.buffer += chunk.toString("utf8");
|
|
347
|
+
let newline = client.buffer.indexOf("\n");
|
|
304
348
|
while (newline >= 0) {
|
|
305
349
|
if (newline > this.clientMaxRequestBytes) {
|
|
306
|
-
this.disconnectSlowClient(client,
|
|
350
|
+
this.disconnectSlowClient(client, "native pty request exceeded limit");
|
|
307
351
|
return;
|
|
308
352
|
}
|
|
309
353
|
const line = client.buffer.slice(0, newline);
|
|
@@ -311,13 +355,12 @@ class NativePtyHost {
|
|
|
311
355
|
if (line.trim()) {
|
|
312
356
|
this.handleClientMessage(client, line);
|
|
313
357
|
}
|
|
314
|
-
newline = client.buffer.indexOf(
|
|
358
|
+
newline = client.buffer.indexOf("\n");
|
|
315
359
|
}
|
|
316
360
|
if (client.buffer.length > this.clientMaxRequestBytes) {
|
|
317
|
-
this.disconnectSlowClient(client,
|
|
361
|
+
this.disconnectSlowClient(client, "native pty request exceeded limit");
|
|
318
362
|
}
|
|
319
363
|
}
|
|
320
|
-
|
|
321
364
|
async handleClientMessage(client, line) {
|
|
322
365
|
let message;
|
|
323
366
|
try {
|
|
@@ -325,10 +368,8 @@ class NativePtyHost {
|
|
|
325
368
|
} catch {
|
|
326
369
|
return;
|
|
327
370
|
}
|
|
328
|
-
|
|
329
371
|
const id = message && message.id;
|
|
330
372
|
if (!id) return;
|
|
331
|
-
|
|
332
373
|
try {
|
|
333
374
|
const result = await this.dispatch(message.method, message.params || {}, client);
|
|
334
375
|
this.send(client, { id, ok: true, result });
|
|
@@ -336,92 +377,88 @@ class NativePtyHost {
|
|
|
336
377
|
this.send(client, {
|
|
337
378
|
id,
|
|
338
379
|
ok: false,
|
|
339
|
-
error: error
|
|
380
|
+
error: errorMessage(error) || "Native pty host request failed"
|
|
340
381
|
});
|
|
341
382
|
}
|
|
342
383
|
}
|
|
343
|
-
|
|
344
384
|
dispatch(method, params, client) {
|
|
385
|
+
const sessionId = typeof params.sessionId === "string" ? params.sessionId : "";
|
|
345
386
|
switch (method) {
|
|
346
|
-
case
|
|
387
|
+
case "ping":
|
|
347
388
|
return {
|
|
348
389
|
ok: true,
|
|
349
390
|
pid: process.pid,
|
|
350
|
-
privateSocketPath: process.platform ===
|
|
351
|
-
runtimeIdentity: this.runtimeIdentity
|
|
391
|
+
privateSocketPath: process.platform === "win32" ? "" : this.boundSocketPath,
|
|
392
|
+
runtimeIdentity: this.runtimeIdentity
|
|
352
393
|
};
|
|
353
|
-
case
|
|
394
|
+
case "registerController":
|
|
354
395
|
return this.registerController(client, params.identity || {});
|
|
355
|
-
case
|
|
396
|
+
case "createSession":
|
|
356
397
|
return this.enqueueControllerMutation(
|
|
357
398
|
params.options?.agentId,
|
|
358
399
|
client,
|
|
359
|
-
() => this.createSession(params.options || {})
|
|
400
|
+
() => this.createSession(params.options || { agentId: "", command: "" })
|
|
360
401
|
);
|
|
361
|
-
case
|
|
402
|
+
case "sendInput":
|
|
362
403
|
return this.enqueueControllerMutation(
|
|
363
|
-
|
|
404
|
+
sessionId,
|
|
364
405
|
client,
|
|
365
|
-
() => this.sendInput(
|
|
406
|
+
() => this.sendInput(sessionId, params.input, params.expectedRuntimeEpoch || "")
|
|
366
407
|
);
|
|
367
|
-
case
|
|
408
|
+
case "resizeSession":
|
|
368
409
|
return this.enqueueControllerMutation(
|
|
369
|
-
|
|
410
|
+
sessionId,
|
|
370
411
|
client,
|
|
371
|
-
() => this.resizeSession(
|
|
412
|
+
() => this.resizeSession(sessionId, params.cols, params.rows)
|
|
372
413
|
);
|
|
373
|
-
case
|
|
414
|
+
case "clearBuffer":
|
|
374
415
|
return this.enqueueControllerMutation(
|
|
375
|
-
|
|
416
|
+
sessionId,
|
|
376
417
|
client,
|
|
377
|
-
() => this.clearBuffer(
|
|
418
|
+
() => this.clearBuffer(sessionId, params.expectedRuntimeEpoch || "")
|
|
378
419
|
);
|
|
379
|
-
case
|
|
420
|
+
case "killSession":
|
|
380
421
|
return this.enqueueControllerMutation(
|
|
381
|
-
|
|
422
|
+
sessionId,
|
|
382
423
|
client,
|
|
383
|
-
() => this.killSession(
|
|
424
|
+
() => this.killSession(sessionId)
|
|
384
425
|
);
|
|
385
|
-
case
|
|
386
|
-
return this.getSessionState(
|
|
387
|
-
case
|
|
388
|
-
return this.getSessionAttachCheckpoint(
|
|
389
|
-
case
|
|
390
|
-
return this.getSessionPreview(
|
|
391
|
-
case
|
|
426
|
+
case "getSessionState":
|
|
427
|
+
return this.getSessionState(sessionId);
|
|
428
|
+
case "getSessionAttachCheckpoint":
|
|
429
|
+
return this.getSessionAttachCheckpoint(sessionId, client);
|
|
430
|
+
case "getSessionPreview":
|
|
431
|
+
return this.getSessionPreview(sessionId);
|
|
432
|
+
case "recoverSessions":
|
|
392
433
|
return this.recoverSessions();
|
|
393
|
-
case
|
|
434
|
+
case "serializeTerminalState":
|
|
394
435
|
return this.serializeTerminalState(client);
|
|
395
|
-
case
|
|
396
|
-
return this.resumeTerminalState(client, params.preparationToken ||
|
|
397
|
-
case
|
|
436
|
+
case "resumeTerminalState":
|
|
437
|
+
return this.resumeTerminalState(client, params.preparationToken || "");
|
|
438
|
+
case "updateSessionMetadata":
|
|
398
439
|
return this.enqueueControllerMutation(
|
|
399
|
-
|
|
440
|
+
sessionId,
|
|
400
441
|
client,
|
|
401
|
-
() => this.updateSessionMetadata(
|
|
442
|
+
() => this.updateSessionMetadata(sessionId, params.patch || {})
|
|
402
443
|
);
|
|
403
|
-
case
|
|
444
|
+
case "shutdownHost":
|
|
404
445
|
return this.shutdownHost(
|
|
405
446
|
client,
|
|
406
447
|
params.controller || {},
|
|
407
|
-
params.preparationToken ||
|
|
448
|
+
params.preparationToken || ""
|
|
408
449
|
);
|
|
409
450
|
default:
|
|
410
451
|
throw new Error(`Unknown native pty host method: ${method}`);
|
|
411
452
|
}
|
|
412
453
|
}
|
|
413
|
-
|
|
414
454
|
enqueueControllerMutation(sessionId, client, operation) {
|
|
415
|
-
// Admission is fenced before queueing. Once a newer controller starts its
|
|
416
|
-
// handoff, no additional work from the retiring controller may enter the
|
|
417
|
-
// mutation set; work admitted before that cut is allowed to finish.
|
|
418
455
|
try {
|
|
419
456
|
this.assertActiveController(client);
|
|
420
457
|
if (this.controllerHandoff) {
|
|
421
|
-
throw new Error(
|
|
458
|
+
throw new Error("Native pty controller handoff is in progress");
|
|
422
459
|
}
|
|
423
460
|
if (this.rotationPreparation) {
|
|
424
|
-
throw new Error(
|
|
461
|
+
throw new Error("Native pty host is frozen for runtime rotation");
|
|
425
462
|
}
|
|
426
463
|
} catch (error) {
|
|
427
464
|
return Promise.reject(error);
|
|
@@ -430,18 +467,17 @@ class NativePtyHost {
|
|
|
430
467
|
[CONTROLLER_MUTATION_ADMISSION]: true,
|
|
431
468
|
client,
|
|
432
469
|
controllerId: client.controllerId,
|
|
433
|
-
controllerGeneration: client.controllerGeneration
|
|
470
|
+
controllerGeneration: client.controllerGeneration
|
|
434
471
|
});
|
|
435
|
-
const key = typeof sessionId ===
|
|
472
|
+
const key = typeof sessionId === "string" && sessionId ? sessionId : "__host__";
|
|
436
473
|
const previous = this.sessionMutationQueues.get(key) || Promise.resolve();
|
|
437
|
-
const next = previous
|
|
438
|
-
|
|
439
|
-
.
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
});
|
|
474
|
+
const next = previous.catch(() => {
|
|
475
|
+
}).then(() => {
|
|
476
|
+
if (this.rotationPreparation) {
|
|
477
|
+
throw new Error("Native pty host is frozen for runtime rotation");
|
|
478
|
+
}
|
|
479
|
+
return operation(admission);
|
|
480
|
+
});
|
|
445
481
|
this.activeControllerMutations.add(next);
|
|
446
482
|
this.sessionMutationQueues.set(key, next);
|
|
447
483
|
const cleanup = () => {
|
|
@@ -453,195 +489,161 @@ class NativePtyHost {
|
|
|
453
489
|
next.then(cleanup, cleanup);
|
|
454
490
|
return next;
|
|
455
491
|
}
|
|
456
|
-
|
|
457
492
|
send(client, message) {
|
|
458
|
-
this.writeClientMessage(client, `${JSON.stringify(message)}
|
|
493
|
+
this.writeClientMessage(client, `${JSON.stringify(message)}
|
|
494
|
+
`);
|
|
459
495
|
}
|
|
460
|
-
|
|
461
496
|
disconnectSlowClient(client, reason) {
|
|
462
497
|
if (!client || !client.socket || client.socket.destroyed) return false;
|
|
463
498
|
client.socket.destroy(new Error(reason));
|
|
464
499
|
return true;
|
|
465
500
|
}
|
|
466
|
-
|
|
467
501
|
writeClientMessage(client, message) {
|
|
468
502
|
if (!client || !client.socket || client.socket.destroyed) return false;
|
|
469
503
|
if (client.socket.writableLength > this.clientMaxBufferedBytes) {
|
|
470
|
-
this.disconnectSlowClient(client,
|
|
504
|
+
this.disconnectSlowClient(client, "native pty client backpressure");
|
|
471
505
|
return false;
|
|
472
506
|
}
|
|
473
507
|
const accepted = client.socket.write(message);
|
|
474
508
|
if (!accepted && client.socket.writableLength > this.clientMaxBufferedBytes) {
|
|
475
|
-
this.disconnectSlowClient(client,
|
|
509
|
+
this.disconnectSlowClient(client, "native pty client backpressure");
|
|
476
510
|
return false;
|
|
477
511
|
}
|
|
478
512
|
return true;
|
|
479
513
|
}
|
|
480
|
-
|
|
481
514
|
broadcast(event, payload) {
|
|
482
|
-
const message = `${JSON.stringify({ event, payload })}
|
|
515
|
+
const message = `${JSON.stringify({ event, payload })}
|
|
516
|
+
`;
|
|
483
517
|
for (const client of this.clients) {
|
|
484
518
|
this.writeClientMessage(client, message);
|
|
485
519
|
}
|
|
486
520
|
}
|
|
487
|
-
|
|
488
521
|
emitSessionEvent(event, payload) {
|
|
489
522
|
this.broadcast(event, payload);
|
|
490
523
|
}
|
|
491
|
-
|
|
492
524
|
registerController(client, rawIdentity) {
|
|
493
525
|
if (!client || client.disconnected === true) {
|
|
494
|
-
throw new Error(
|
|
526
|
+
throw new Error("Native pty controller client is disconnected");
|
|
495
527
|
}
|
|
496
528
|
const identity = {
|
|
497
|
-
id: typeof rawIdentity.id ===
|
|
498
|
-
generation: Math.floor(Number(rawIdentity.generation))
|
|
529
|
+
id: typeof rawIdentity.id === "string" ? rawIdentity.id : "",
|
|
530
|
+
generation: Math.floor(Number(rawIdentity.generation))
|
|
499
531
|
};
|
|
500
532
|
if (!identity.id || !Number.isFinite(identity.generation) || identity.generation <= 0) {
|
|
501
|
-
throw new Error(
|
|
502
|
-
}
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
const replacesController = !current
|
|
517
|
-
|| current.id !== identity.id
|
|
518
|
-
|| current.generation !== identity.generation
|
|
519
|
-
|| this.activeControllerClient !== client;
|
|
520
|
-
if (replacesController && current) {
|
|
521
|
-
const handoff = {
|
|
522
|
-
fromClient: this.activeControllerClient,
|
|
523
|
-
fromIdentity: current,
|
|
524
|
-
toClient: client,
|
|
525
|
-
toIdentity: identity,
|
|
526
|
-
};
|
|
527
|
-
this.controllerHandoff = handoff;
|
|
528
|
-
try {
|
|
529
|
-
// New admissions from the retiring controller are now closed.
|
|
530
|
-
// Drain the exact set admitted before that cut before publishing
|
|
531
|
-
// the new generation as active.
|
|
532
|
-
await Promise.allSettled([...this.activeControllerMutations]);
|
|
533
|
-
} finally {
|
|
534
|
-
if (this.controllerHandoff === handoff) this.controllerHandoff = null;
|
|
535
|
-
}
|
|
536
|
-
}
|
|
537
|
-
|
|
538
|
-
if (client.disconnected === true) {
|
|
539
|
-
throw new Error('Native pty controller client is disconnected');
|
|
540
|
-
}
|
|
541
|
-
|
|
542
|
-
this.activeControllerIdentity = identity;
|
|
543
|
-
this.activeControllerClient = client;
|
|
544
|
-
client.controllerId = identity.id;
|
|
545
|
-
client.controllerGeneration = identity.generation;
|
|
546
|
-
return {
|
|
547
|
-
registered: true,
|
|
548
|
-
controllerId: identity.id,
|
|
549
|
-
controllerGeneration: identity.generation,
|
|
533
|
+
throw new Error("Invalid native pty controller identity");
|
|
534
|
+
}
|
|
535
|
+
const registration = this.controllerRegistrationQueue.catch(() => {
|
|
536
|
+
}).then(async () => {
|
|
537
|
+
const current = this.activeControllerIdentity;
|
|
538
|
+
if (current && (identity.generation < current.generation || identity.generation === current.generation && current.id !== identity.id)) {
|
|
539
|
+
throw new Error("Stale native pty controller");
|
|
540
|
+
}
|
|
541
|
+
const replacesController = !current || current.id !== identity.id || current.generation !== identity.generation || this.activeControllerClient !== client;
|
|
542
|
+
if (replacesController && current) {
|
|
543
|
+
const handoff = {
|
|
544
|
+
fromClient: this.activeControllerClient,
|
|
545
|
+
fromIdentity: current,
|
|
546
|
+
toClient: client,
|
|
547
|
+
toIdentity: identity
|
|
550
548
|
};
|
|
551
|
-
|
|
549
|
+
this.controllerHandoff = handoff;
|
|
550
|
+
try {
|
|
551
|
+
await Promise.allSettled([...this.activeControllerMutations]);
|
|
552
|
+
} finally {
|
|
553
|
+
if (this.controllerHandoff === handoff) this.controllerHandoff = null;
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
if (client.disconnected === true) {
|
|
557
|
+
throw new Error("Native pty controller client is disconnected");
|
|
558
|
+
}
|
|
559
|
+
this.activeControllerIdentity = identity;
|
|
560
|
+
this.activeControllerClient = client;
|
|
561
|
+
client.controllerId = identity.id;
|
|
562
|
+
client.controllerGeneration = identity.generation;
|
|
563
|
+
return {
|
|
564
|
+
registered: true,
|
|
565
|
+
controllerId: identity.id,
|
|
566
|
+
controllerGeneration: identity.generation
|
|
567
|
+
};
|
|
568
|
+
});
|
|
552
569
|
this.controllerRegistrationQueue = registration;
|
|
553
570
|
return registration;
|
|
554
571
|
}
|
|
555
|
-
|
|
556
572
|
assertActiveController(client) {
|
|
557
|
-
if (client
|
|
558
|
-
if (
|
|
559
|
-
|
|
560
|
-
client.disconnected === true ||
|
|
561
|
-
client !== this.activeControllerClient ||
|
|
562
|
-
!this.activeControllerIdentity ||
|
|
563
|
-
client.controllerId !== this.activeControllerIdentity.id ||
|
|
564
|
-
client.controllerGeneration !== this.activeControllerIdentity.generation
|
|
565
|
-
) {
|
|
566
|
-
throw new Error('Native pty mutation requires the active controller');
|
|
573
|
+
if (client && isControllerAdmission(client)) return;
|
|
574
|
+
if (!client || client.disconnected === true || client !== this.activeControllerClient || !this.activeControllerIdentity || client.controllerId !== this.activeControllerIdentity.id || client.controllerGeneration !== this.activeControllerIdentity.generation) {
|
|
575
|
+
throw new Error("Native pty mutation requires the active controller");
|
|
567
576
|
}
|
|
568
577
|
}
|
|
569
|
-
|
|
570
578
|
async createSession(options) {
|
|
571
579
|
if (this.rotationPreparation) {
|
|
572
|
-
throw new Error(
|
|
580
|
+
throw new Error("Native pty host is frozen for runtime rotation");
|
|
573
581
|
}
|
|
574
582
|
this.cancelIdleExit();
|
|
575
|
-
const normalized = options.shellIntegrationPrepared === true
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
:
|
|
582
|
-
|
|
583
|
-
env: sanitizeAgentEnv(options.env),
|
|
584
|
-
});
|
|
583
|
+
const normalized = options.shellIntegrationPrepared === true ? {
|
|
584
|
+
...options,
|
|
585
|
+
args: [...options.args || []],
|
|
586
|
+
env: sanitizeAgentEnv(options.env)
|
|
587
|
+
} : normalizeShellSessionOptions({
|
|
588
|
+
...options,
|
|
589
|
+
env: sanitizeAgentEnv(options.env)
|
|
590
|
+
});
|
|
585
591
|
const agentId = normalized.agentId;
|
|
586
592
|
if (!agentId) {
|
|
587
|
-
throw new Error(
|
|
593
|
+
throw new Error("Missing native pty session id");
|
|
588
594
|
}
|
|
589
|
-
|
|
590
595
|
const existing = this.sessions.get(agentId);
|
|
591
|
-
if (existing && existing.status !==
|
|
596
|
+
if (existing && existing.status !== "exited") {
|
|
592
597
|
return { sessionId: agentId, status: existing.status };
|
|
593
598
|
}
|
|
594
|
-
|
|
595
|
-
const reviveState = options.reviveState
|
|
596
|
-
? normalizeTerminalStateEntry(options.reviveState)
|
|
597
|
-
: null;
|
|
599
|
+
const reviveState = options.reviveState ? normalizeTerminalStateEntry(options.reviveState) : null;
|
|
598
600
|
const reviveEvent = reviveState?.replayEvent?.events?.[0] || null;
|
|
599
601
|
const cols = reviveEvent?.cols || normalized.cols || DEFAULT_COLS;
|
|
600
602
|
const rows = reviveEvent?.rows || normalized.rows || DEFAULT_ROWS;
|
|
601
603
|
const runtimeGeneration = await allocateNativePtyRuntimeGeneration(this.configDir);
|
|
602
604
|
const runtimeEpoch = formatNativePtyRuntimeEpoch(runtimeGeneration);
|
|
603
605
|
const screenWorker = await this.screenWorkerPool.acquire({ cols, rows, runtimeEpoch });
|
|
604
|
-
const replayText = reviveEvent?.data ||
|
|
605
|
-
const restoredOutput = reviveState
|
|
606
|
-
? `${replayText}${TERMINAL_HISTORY_RESTORED_MESSAGE}`
|
|
607
|
-
: '';
|
|
606
|
+
const replayText = reviveEvent?.data || "";
|
|
607
|
+
const restoredOutput = reviveState ? `${replayText}${TERMINAL_HISTORY_RESTORED_MESSAGE}` : "";
|
|
608
608
|
let restoredScreenState = null;
|
|
609
609
|
if (restoredOutput) {
|
|
610
610
|
try {
|
|
611
611
|
await screenWorker.append(restoredOutput, 1, 1);
|
|
612
612
|
restoredScreenState = await screenWorker.getState();
|
|
613
613
|
} catch (error) {
|
|
614
|
-
await screenWorker.dispose().catch(() => {
|
|
615
|
-
|
|
616
|
-
|
|
614
|
+
await screenWorker.dispose().catch(() => {
|
|
615
|
+
});
|
|
616
|
+
throw new Error(`Failed to restore serialized terminal history: ${errorMessage(error)}`, {
|
|
617
|
+
cause: error
|
|
617
618
|
});
|
|
618
619
|
}
|
|
619
620
|
}
|
|
621
|
+
const persistedCommand = normalized.metadata?.command;
|
|
622
|
+
const persistedStartedAt = normalized.metadata?.startedAt;
|
|
620
623
|
const metadata = {
|
|
621
|
-
...
|
|
624
|
+
...normalized.metadata || {},
|
|
622
625
|
protocolVersion: 1,
|
|
623
|
-
engineName:
|
|
626
|
+
engineName: "native",
|
|
624
627
|
agentId,
|
|
625
|
-
command:
|
|
628
|
+
command: typeof persistedCommand === "string" && persistedCommand ? persistedCommand : normalized.command,
|
|
626
629
|
cwd: normalized.cwd || process.cwd(),
|
|
627
|
-
startedAt:
|
|
630
|
+
startedAt: typeof persistedStartedAt === "number" && Number.isFinite(persistedStartedAt) ? persistedStartedAt : Date.now()
|
|
628
631
|
};
|
|
629
|
-
|
|
630
632
|
let ptyProcess;
|
|
631
633
|
try {
|
|
632
634
|
ptyProcess = createPtyProcess(normalized.command, normalized.args || [], {
|
|
633
|
-
name:
|
|
635
|
+
name: "xterm-256color",
|
|
634
636
|
cols,
|
|
635
637
|
rows,
|
|
636
638
|
cwd: normalized.cwd,
|
|
637
|
-
env: normalized.env || process.env
|
|
639
|
+
env: normalized.env || process.env
|
|
638
640
|
});
|
|
639
641
|
} catch (error) {
|
|
640
642
|
cleanupShellBusyIntegration(normalized.shellBusyIntegration);
|
|
641
|
-
await screenWorker.dispose().catch(() => {
|
|
643
|
+
await screenWorker.dispose().catch(() => {
|
|
644
|
+
});
|
|
642
645
|
throw error;
|
|
643
646
|
}
|
|
644
|
-
|
|
645
647
|
const session = {
|
|
646
648
|
id: agentId,
|
|
647
649
|
command: normalized.command,
|
|
@@ -662,39 +664,38 @@ class NativePtyHost {
|
|
|
662
664
|
previewSnapshot: restoredScreenState?.previewSnapshot || null,
|
|
663
665
|
previewCols: restoredScreenState?.cols || cols,
|
|
664
666
|
previewRows: restoredScreenState?.rows || rows,
|
|
665
|
-
title: restoredScreenState?.title ||
|
|
666
|
-
status:
|
|
667
|
+
title: restoredScreenState?.title || "",
|
|
668
|
+
status: "running",
|
|
667
669
|
terminalBusy: null,
|
|
668
|
-
shellCwd:
|
|
670
|
+
shellCwd: "",
|
|
669
671
|
shellLastExitCode: null,
|
|
670
|
-
shellLastEvent:
|
|
671
|
-
shellCommand:
|
|
672
|
-
shellLastCommand:
|
|
672
|
+
shellLastEvent: "",
|
|
673
|
+
shellCommand: "",
|
|
674
|
+
shellLastCommand: "",
|
|
673
675
|
shellCommandStartedAt: null,
|
|
674
676
|
shellLastCommandStartedAt: null,
|
|
675
677
|
shellLastCommandFinishedAt: null,
|
|
676
678
|
shellLastCommandDurationMs: null,
|
|
677
|
-
shellBusyMarkerPending:
|
|
679
|
+
shellBusyMarkerPending: "",
|
|
678
680
|
shellBusyIntegration: normalized.shellBusyIntegration || null,
|
|
679
681
|
startedAt: metadata.startedAt || Date.now(),
|
|
680
682
|
lastActivityAt: Date.now(),
|
|
681
683
|
exitedAt: null,
|
|
682
|
-
screenWorker
|
|
684
|
+
screenWorker
|
|
683
685
|
};
|
|
684
|
-
|
|
685
686
|
this.sessions.set(agentId, session);
|
|
686
687
|
this.bindScreenWorker(session);
|
|
687
688
|
this.bindPty(session);
|
|
688
|
-
this.emitSessionEvent(
|
|
689
|
+
this.emitSessionEvent("session-started", {
|
|
689
690
|
sessionId: agentId,
|
|
690
691
|
status: session.status,
|
|
691
692
|
startedAt: session.startedAt,
|
|
692
693
|
runtimeEpoch: session.runtimeEpoch,
|
|
693
694
|
outputSeq: session.outputSeq,
|
|
694
|
-
stateRevision: session.stateRevision
|
|
695
|
+
stateRevision: session.stateRevision
|
|
695
696
|
});
|
|
696
697
|
if (restoredOutput) {
|
|
697
|
-
this.emitSessionEvent(
|
|
698
|
+
this.emitSessionEvent("session-sync", {
|
|
698
699
|
sessionId: agentId,
|
|
699
700
|
output: session.renderOutput,
|
|
700
701
|
textOutput: session.output,
|
|
@@ -704,93 +705,80 @@ class NativePtyHost {
|
|
|
704
705
|
stateRevision: session.stateRevision,
|
|
705
706
|
cols: session.previewCols,
|
|
706
707
|
rows: session.previewRows,
|
|
707
|
-
revived: true
|
|
708
|
+
revived: true
|
|
708
709
|
});
|
|
709
710
|
}
|
|
710
|
-
|
|
711
711
|
return { sessionId: agentId, status: session.status };
|
|
712
712
|
}
|
|
713
|
-
|
|
714
713
|
bindScreenWorker(session) {
|
|
715
|
-
session.screenWorker
|
|
714
|
+
const screenWorker = session.screenWorker;
|
|
715
|
+
if (!screenWorker) throw new TypeError("Terminal screen worker is unavailable");
|
|
716
|
+
screenWorker.on("preview", ({ previewText, title, cols, rows, previewSnapshot }) => {
|
|
716
717
|
const current = this.sessions.get(session.id);
|
|
717
718
|
if (current !== session) return;
|
|
718
|
-
|
|
719
|
-
current.previewText = previewText || '';
|
|
719
|
+
current.previewText = previewText || "";
|
|
720
720
|
current.previewSnapshot = previewSnapshot || null;
|
|
721
721
|
current.previewCols = cols || current.previewCols;
|
|
722
722
|
current.previewRows = rows || current.previewRows;
|
|
723
|
-
|
|
724
723
|
if (title && title !== current.title) {
|
|
725
724
|
current.title = title;
|
|
726
|
-
this.emitSessionEvent(
|
|
725
|
+
this.emitSessionEvent("session-title", {
|
|
727
726
|
sessionId: current.id,
|
|
728
727
|
title: current.title,
|
|
729
|
-
runtimeEpoch: current.runtimeEpoch
|
|
728
|
+
runtimeEpoch: current.runtimeEpoch
|
|
730
729
|
});
|
|
731
730
|
}
|
|
732
|
-
|
|
733
|
-
this.emitSessionEvent('session-preview', {
|
|
731
|
+
this.emitSessionEvent("session-preview", {
|
|
734
732
|
sessionId: current.id,
|
|
735
733
|
previewText: current.previewText,
|
|
736
734
|
cols: current.previewCols,
|
|
737
735
|
rows: current.previewRows,
|
|
738
736
|
previewSnapshot: current.previewSnapshot,
|
|
739
|
-
runtimeEpoch: current.runtimeEpoch
|
|
737
|
+
runtimeEpoch: current.runtimeEpoch
|
|
740
738
|
});
|
|
741
739
|
});
|
|
742
|
-
|
|
743
|
-
session.screenWorker.on('error', error => {
|
|
740
|
+
screenWorker.on("error", (error) => {
|
|
744
741
|
this.failSessionScreenState(session, error);
|
|
745
742
|
});
|
|
746
743
|
}
|
|
747
|
-
|
|
748
744
|
failSessionScreenState(session, error) {
|
|
749
745
|
const current = this.sessions.get(session.id);
|
|
750
746
|
if (current !== session || current.stateProofAvailable === false) return;
|
|
751
747
|
current.stateProofAvailable = false;
|
|
752
|
-
const message = error instanceof Error ? error.message : String(error ||
|
|
753
|
-
this.emitSessionEvent(
|
|
748
|
+
const message = error instanceof Error ? error.message : String(error || "unknown reducer failure");
|
|
749
|
+
this.emitSessionEvent("session-error", {
|
|
754
750
|
sessionId: current.id,
|
|
755
751
|
error: `Terminal state reducer failed: ${message}`,
|
|
756
752
|
fatal: true,
|
|
757
|
-
runtimeEpoch: current.runtimeEpoch
|
|
753
|
+
runtimeEpoch: current.runtimeEpoch
|
|
758
754
|
});
|
|
759
755
|
try {
|
|
760
756
|
current.process?.kill();
|
|
761
757
|
} catch {
|
|
762
|
-
// The session is already unusable because its authoritative reducer failed.
|
|
763
758
|
}
|
|
764
759
|
}
|
|
765
|
-
|
|
766
760
|
bindPty(session) {
|
|
767
|
-
session.process.onData(data => this.handleSessionData(session.id, data, session));
|
|
761
|
+
session.process.onData((data) => this.handleSessionData(session.id, data, session));
|
|
768
762
|
session.process.onExit(({ code }) => {
|
|
769
|
-
this.handleSessionExit(session.id, code, session).catch(error => {
|
|
763
|
+
this.handleSessionExit(session.id, code, session).catch((error) => {
|
|
770
764
|
if (this.sessions.get(session.id) !== session) return;
|
|
771
|
-
this.emitSessionEvent(
|
|
765
|
+
this.emitSessionEvent("session-error", {
|
|
772
766
|
sessionId: session.id,
|
|
773
767
|
error: error.message,
|
|
774
768
|
fatal: false,
|
|
775
|
-
runtimeEpoch: session.runtimeEpoch
|
|
769
|
+
runtimeEpoch: session.runtimeEpoch
|
|
776
770
|
});
|
|
777
771
|
});
|
|
778
772
|
});
|
|
779
773
|
}
|
|
780
|
-
|
|
781
774
|
handleSessionData(sessionId, rawData, expectedSession = null) {
|
|
782
775
|
const current = this.sessions.get(sessionId);
|
|
783
|
-
if (
|
|
784
|
-
!current ||
|
|
785
|
-
(expectedSession && current !== expectedSession) ||
|
|
786
|
-
current.stateProofAvailable === false
|
|
787
|
-
) return;
|
|
776
|
+
if (!current || expectedSession && current !== expectedSession || current.stateProofAvailable === false) return;
|
|
788
777
|
if (!acceptTerminalExitData(current)) {
|
|
789
778
|
current.finalCheckpoint = null;
|
|
790
|
-
this.failSessionScreenState(current, new Error(
|
|
779
|
+
this.failSessionScreenState(current, new Error("PTY emitted data after its final checkpoint cut"));
|
|
791
780
|
return;
|
|
792
781
|
}
|
|
793
|
-
|
|
794
782
|
const busyState = parseShellBusyMarkers(rawData, current.terminalBusy, current.shellBusyMarkerPending);
|
|
795
783
|
current.shellBusyMarkerPending = busyState.pending;
|
|
796
784
|
if (busyState.markerSeen) {
|
|
@@ -805,26 +793,26 @@ class NativePtyHost {
|
|
|
805
793
|
if (busyState.shellEvent) {
|
|
806
794
|
current.shellLastEvent = busyState.shellEvent;
|
|
807
795
|
}
|
|
808
|
-
if (busyState.shellEvent ===
|
|
796
|
+
if (busyState.shellEvent === "start") {
|
|
809
797
|
current.shellCommandStartedAt = markerAt;
|
|
810
798
|
}
|
|
811
799
|
if (busyState.commandTextSeen) {
|
|
812
|
-
current.shellCommand = busyState.shellCommand ||
|
|
800
|
+
current.shellCommand = busyState.shellCommand || "";
|
|
813
801
|
}
|
|
814
|
-
if (busyState.shellEvent ===
|
|
802
|
+
if (busyState.shellEvent === "finish") {
|
|
815
803
|
const commandStartedAt = current.shellCommandStartedAt;
|
|
816
804
|
if (current.shellCommand) {
|
|
817
805
|
current.shellLastCommand = current.shellCommand;
|
|
818
806
|
}
|
|
819
|
-
if (typeof commandStartedAt ===
|
|
807
|
+
if (typeof commandStartedAt === "number") {
|
|
820
808
|
current.shellLastCommandStartedAt = commandStartedAt;
|
|
821
809
|
current.shellLastCommandFinishedAt = markerAt;
|
|
822
810
|
current.shellLastCommandDurationMs = Math.max(0, markerAt - commandStartedAt);
|
|
823
811
|
}
|
|
824
|
-
current.shellCommand =
|
|
812
|
+
current.shellCommand = "";
|
|
825
813
|
current.shellCommandStartedAt = null;
|
|
826
814
|
}
|
|
827
|
-
this.emitSessionEvent(
|
|
815
|
+
this.emitSessionEvent("session-busy-state", {
|
|
828
816
|
sessionId,
|
|
829
817
|
terminalBusy: current.terminalBusy,
|
|
830
818
|
cwd: current.shellCwd || current.cwd,
|
|
@@ -838,17 +826,15 @@ class NativePtyHost {
|
|
|
838
826
|
shellLastCommandDurationMs: current.shellLastCommandDurationMs,
|
|
839
827
|
statusMarkerSeen: busyState.statusMarkerSeen,
|
|
840
828
|
busyMarkerSeen: busyState.busyMarkerSeen,
|
|
841
|
-
runtimeEpoch: current.runtimeEpoch
|
|
829
|
+
runtimeEpoch: current.runtimeEpoch
|
|
842
830
|
});
|
|
843
831
|
}
|
|
844
|
-
|
|
845
832
|
const data = busyState.data;
|
|
846
833
|
if (!data) return;
|
|
847
|
-
|
|
848
834
|
const reducerDelivery = enqueueTerminalReducerData(
|
|
849
835
|
ensureTerminalReducerFlowControl(current),
|
|
850
836
|
current.process,
|
|
851
|
-
data
|
|
837
|
+
data
|
|
852
838
|
);
|
|
853
839
|
if (reducerDelivery.error) {
|
|
854
840
|
this.failSessionScreenState(current, reducerDelivery.error);
|
|
@@ -860,17 +846,15 @@ class NativePtyHost {
|
|
|
860
846
|
const stateRevision = current.stateRevision;
|
|
861
847
|
current.output = trimOutput(current.output + data);
|
|
862
848
|
current.lastActivityAt = Date.now();
|
|
863
|
-
|
|
864
849
|
const fallbackTitle = extractLatestTerminalTitle(data);
|
|
865
850
|
if (fallbackTitle && fallbackTitle !== current.title) {
|
|
866
851
|
current.title = fallbackTitle;
|
|
867
|
-
this.emitSessionEvent(
|
|
852
|
+
this.emitSessionEvent("session-title", {
|
|
868
853
|
sessionId,
|
|
869
854
|
title: current.title,
|
|
870
|
-
runtimeEpoch: current.runtimeEpoch
|
|
855
|
+
runtimeEpoch: current.runtimeEpoch
|
|
871
856
|
});
|
|
872
857
|
}
|
|
873
|
-
|
|
874
858
|
if (current.screenWorker) {
|
|
875
859
|
const commit = current.screenWorker.append(data, stateRevision, outputSeq);
|
|
876
860
|
current.reducerCommitQueue = current.reducerCommitQueue.then(() => commit).then(() => {
|
|
@@ -879,49 +863,43 @@ class NativePtyHost {
|
|
|
879
863
|
const flowError = acknowledgeTerminalReducerData(
|
|
880
864
|
ensureTerminalReducerFlowControl(latest),
|
|
881
865
|
latest.process,
|
|
882
|
-
reducerDelivery.bytes
|
|
866
|
+
reducerDelivery.bytes
|
|
883
867
|
);
|
|
884
868
|
if (flowError) {
|
|
885
869
|
this.failSessionScreenState(latest, flowError);
|
|
886
870
|
return;
|
|
887
871
|
}
|
|
888
|
-
this.emitSessionEvent(
|
|
872
|
+
this.emitSessionEvent("session-output", {
|
|
889
873
|
sessionId,
|
|
890
874
|
data,
|
|
891
875
|
runtimeEpoch: latest.runtimeEpoch,
|
|
892
876
|
outputSeq,
|
|
893
|
-
stateRevision
|
|
877
|
+
stateRevision
|
|
894
878
|
});
|
|
895
|
-
this.emitSessionEvent(
|
|
879
|
+
this.emitSessionEvent("session-activity", {
|
|
896
880
|
sessionId,
|
|
897
881
|
lastActivityAt: latest.lastActivityAt,
|
|
898
|
-
runtimeEpoch: latest.runtimeEpoch
|
|
882
|
+
runtimeEpoch: latest.runtimeEpoch
|
|
899
883
|
});
|
|
900
|
-
}).catch(error => this.failSessionScreenState(current, error));
|
|
884
|
+
}).catch((error) => this.failSessionScreenState(current, error));
|
|
901
885
|
}
|
|
902
886
|
}
|
|
903
|
-
|
|
904
887
|
handleSessionExit(sessionId, code, expectedSession = null) {
|
|
905
888
|
const session = this.sessions.get(sessionId);
|
|
906
|
-
if (
|
|
907
|
-
!session ||
|
|
908
|
-
(expectedSession && session !== expectedSession) ||
|
|
909
|
-
session.status === 'exited'
|
|
910
|
-
) return Promise.resolve();
|
|
889
|
+
if (!session || expectedSession && session !== expectedSession || session.status === "exited") return Promise.resolve();
|
|
911
890
|
if (session.exitFinalizationPromise) return session.exitFinalizationPromise;
|
|
912
891
|
const finalization = this.finalizeSessionExit(sessionId, code, session);
|
|
913
892
|
session.exitFinalizationPromise = finalization;
|
|
914
893
|
return finalization;
|
|
915
894
|
}
|
|
916
|
-
|
|
917
895
|
async finalizeSessionExit(sessionId, code, session) {
|
|
918
896
|
const quiesced = await waitForTerminalExitDataQuiescence(session, {
|
|
919
897
|
flushMs: this.terminalExitDataFlushMs,
|
|
920
|
-
isCurrent: () => this.sessions.get(sessionId) === session
|
|
898
|
+
isCurrent: () => this.sessions.get(sessionId) === session
|
|
921
899
|
});
|
|
922
900
|
if (!quiesced) return;
|
|
923
|
-
|
|
924
|
-
|
|
901
|
+
await Promise.resolve(session.reducerCommitQueue).catch(() => {
|
|
902
|
+
});
|
|
925
903
|
if (this.sessions.get(sessionId) !== session) return;
|
|
926
904
|
const finalCheckpoint = await captureTerminalAttachCheckpoint(session, { requireCurrentCut: true });
|
|
927
905
|
if (this.sessions.get(sessionId) !== session) return;
|
|
@@ -934,64 +912,61 @@ class NativePtyHost {
|
|
|
934
912
|
session.previewRows = finalCheckpoint.rows;
|
|
935
913
|
session.title = finalCheckpoint.title || session.title;
|
|
936
914
|
} else {
|
|
937
|
-
this.failSessionScreenState(session, new Error(
|
|
915
|
+
this.failSessionScreenState(session, new Error("Unable to capture the exact final Terminal checkpoint"));
|
|
938
916
|
}
|
|
939
|
-
|
|
940
|
-
session.status = 'exited';
|
|
917
|
+
session.status = "exited";
|
|
941
918
|
session.exitedAt = Date.now();
|
|
942
919
|
resetTerminalReducerFlowControl(
|
|
943
920
|
ensureTerminalReducerFlowControl(session),
|
|
944
|
-
session.process
|
|
921
|
+
session.process
|
|
945
922
|
);
|
|
946
923
|
cleanupShellBusyIntegration(session.shellBusyIntegration);
|
|
947
924
|
if (session.screenWorker) {
|
|
948
|
-
await session.screenWorker.dispose().catch(() => {
|
|
925
|
+
await session.screenWorker.dispose().catch(() => {
|
|
926
|
+
});
|
|
949
927
|
session.screenWorker = null;
|
|
950
928
|
}
|
|
951
|
-
|
|
952
|
-
this.emitSessionEvent('session-exited', {
|
|
929
|
+
this.emitSessionEvent("session-exited", {
|
|
953
930
|
sessionId,
|
|
954
|
-
code: code == null ?
|
|
931
|
+
code: code == null ? "unknown" : code,
|
|
955
932
|
exitedAt: session.exitedAt,
|
|
956
933
|
runtimeEpoch: session.runtimeEpoch,
|
|
957
934
|
outputSeq: session.outputSeq,
|
|
958
935
|
stateRevision: session.stateRevision,
|
|
959
|
-
stateProofAvailable: session.stateProofAvailable !== false
|
|
936
|
+
stateProofAvailable: session.stateProofAvailable !== false
|
|
960
937
|
});
|
|
961
938
|
if (finalCheckpoint) {
|
|
962
|
-
this.emitSessionEvent(
|
|
939
|
+
this.emitSessionEvent("session-preview", {
|
|
963
940
|
sessionId,
|
|
964
941
|
previewText: session.previewText,
|
|
965
942
|
cols: session.previewCols,
|
|
966
943
|
rows: session.previewRows,
|
|
967
944
|
previewSnapshot: session.previewSnapshot,
|
|
968
|
-
runtimeEpoch: session.runtimeEpoch
|
|
945
|
+
runtimeEpoch: session.runtimeEpoch
|
|
969
946
|
});
|
|
970
947
|
}
|
|
971
948
|
this.scheduleIdleExitIfUnused();
|
|
972
949
|
}
|
|
973
|
-
|
|
974
|
-
async sendInput(sessionId, input, expectedRuntimeEpoch = '') {
|
|
950
|
+
async sendInput(sessionId, input, expectedRuntimeEpoch = "") {
|
|
975
951
|
const session = this.sessions.get(sessionId);
|
|
976
|
-
if (!session || !session.process || session.status ===
|
|
977
|
-
throw new Error(
|
|
952
|
+
if (!session || !session.process || session.status === "exited" || session.exitFinalizing === true) {
|
|
953
|
+
throw new Error("Session not available");
|
|
978
954
|
}
|
|
979
955
|
if (session.rotationFrozen === true) {
|
|
980
|
-
throw new Error(
|
|
956
|
+
throw new Error("Terminal session is frozen for native PTY host rotation");
|
|
981
957
|
}
|
|
982
958
|
if (expectedRuntimeEpoch && expectedRuntimeEpoch !== session.runtimeEpoch) {
|
|
983
|
-
return { status:
|
|
959
|
+
return { status: "input-rejected", reason: "runtime-epoch-mismatch" };
|
|
984
960
|
}
|
|
985
961
|
session.process.write(terminalInputToPtyString(input));
|
|
986
962
|
session.lastActivityAt = Date.now();
|
|
987
|
-
this.emitSessionEvent(
|
|
963
|
+
this.emitSessionEvent("session-activity", {
|
|
988
964
|
sessionId,
|
|
989
965
|
lastActivityAt: session.lastActivityAt,
|
|
990
|
-
runtimeEpoch: session.runtimeEpoch
|
|
966
|
+
runtimeEpoch: session.runtimeEpoch
|
|
991
967
|
});
|
|
992
968
|
return { sent: true };
|
|
993
969
|
}
|
|
994
|
-
|
|
995
970
|
async getSessionAttachCheckpoint(sessionId, client) {
|
|
996
971
|
this.assertActiveController(client);
|
|
997
972
|
const session = this.sessions.get(sessionId);
|
|
@@ -999,34 +974,35 @@ class NativePtyHost {
|
|
|
999
974
|
const checkpoint = await captureTerminalAttachCheckpoint(session);
|
|
1000
975
|
return this.sessions.get(sessionId) === session ? checkpoint : null;
|
|
1001
976
|
}
|
|
1002
|
-
|
|
1003
977
|
async resizeSession(sessionId, cols, rows) {
|
|
1004
978
|
const session = this.sessions.get(sessionId);
|
|
1005
|
-
if (!session || !session.process || session.status ===
|
|
1006
|
-
return { status:
|
|
979
|
+
if (!session || !session.process || session.status === "exited" || session.exitFinalizing === true) {
|
|
980
|
+
return { status: "resize-rejected", reason: "session-unavailable", resized: false };
|
|
1007
981
|
}
|
|
1008
982
|
if (session.rotationFrozen === true) {
|
|
1009
|
-
return { status:
|
|
983
|
+
return { status: "resize-rejected", reason: "runtime-rotation", resized: false };
|
|
1010
984
|
}
|
|
1011
|
-
|
|
1012
|
-
const
|
|
1013
|
-
const nextRows = normalizePositiveInteger(rows, session.previewRows || DEFAULT_ROWS, 1, 1000);
|
|
985
|
+
const nextCols = normalizePositiveInteger(cols, session.previewCols || DEFAULT_COLS, 1, 1e3);
|
|
986
|
+
const nextRows = normalizePositiveInteger(rows, session.previewRows || DEFAULT_ROWS, 1, 1e3);
|
|
1014
987
|
if (nextCols === session.previewCols && nextRows === session.previewRows) {
|
|
1015
988
|
return {
|
|
1016
|
-
status:
|
|
989
|
+
status: "resize-committed",
|
|
1017
990
|
resized: true,
|
|
1018
|
-
unchanged: true
|
|
991
|
+
unchanged: true
|
|
1019
992
|
};
|
|
1020
993
|
}
|
|
1021
|
-
|
|
994
|
+
const resize = session.process.resize;
|
|
995
|
+
if (!resize) {
|
|
996
|
+
return { status: "resize-rejected", reason: "session-unavailable", resized: false };
|
|
997
|
+
}
|
|
1022
998
|
try {
|
|
1023
|
-
session.process
|
|
999
|
+
resize.call(session.process, nextCols, nextRows);
|
|
1024
1000
|
} catch (error) {
|
|
1025
1001
|
return {
|
|
1026
|
-
status:
|
|
1027
|
-
reason:
|
|
1002
|
+
status: "resize-rejected",
|
|
1003
|
+
reason: "pty-resize-failed",
|
|
1028
1004
|
resized: false,
|
|
1029
|
-
error: error instanceof Error ? error.message : String(error ||
|
|
1005
|
+
error: error instanceof Error ? error.message : String(error || "unknown PTY resize failure")
|
|
1030
1006
|
};
|
|
1031
1007
|
}
|
|
1032
1008
|
session.stateRevision += 1;
|
|
@@ -1035,135 +1011,120 @@ class NativePtyHost {
|
|
|
1035
1011
|
const runtimeEpoch = session.runtimeEpoch;
|
|
1036
1012
|
session.previewCols = nextCols;
|
|
1037
1013
|
session.previewRows = nextRows;
|
|
1038
|
-
|
|
1039
1014
|
if (session.screenWorker) {
|
|
1040
1015
|
const reducerCommit = session.screenWorker.resize(nextCols, nextRows, stateRevision);
|
|
1041
1016
|
const publishedCommit = session.reducerCommitQueue.then(() => reducerCommit).then((screenState) => {
|
|
1042
1017
|
if (this.sessions.get(sessionId) !== session) {
|
|
1043
|
-
throw new Error(
|
|
1018
|
+
throw new Error("Terminal session was replaced during resize");
|
|
1044
1019
|
}
|
|
1045
|
-
if (
|
|
1046
|
-
|
|
1047
|
-
screenState.outputSeq !== outputSeq ||
|
|
1048
|
-
screenState.stateRevision !== stateRevision
|
|
1049
|
-
) {
|
|
1050
|
-
throw new Error('Terminal screen resize returned a non-authoritative state cut');
|
|
1020
|
+
if (screenState.runtimeEpoch !== runtimeEpoch || screenState.outputSeq !== outputSeq || screenState.stateRevision !== stateRevision) {
|
|
1021
|
+
throw new Error("Terminal screen resize returned a non-authoritative state cut");
|
|
1051
1022
|
}
|
|
1052
|
-
session.previewText = screenState.previewText ||
|
|
1023
|
+
session.previewText = screenState.previewText || "";
|
|
1053
1024
|
session.previewSnapshot = screenState.previewSnapshot || session.previewSnapshot;
|
|
1054
|
-
session.renderOutput = typeof screenState.renderOutput ===
|
|
1055
|
-
? screenState.renderOutput
|
|
1056
|
-
: session.renderOutput;
|
|
1025
|
+
session.renderOutput = typeof screenState.renderOutput === "string" ? screenState.renderOutput : session.renderOutput;
|
|
1057
1026
|
session.previewCols = screenState.cols || nextCols;
|
|
1058
1027
|
session.previewRows = screenState.rows || nextRows;
|
|
1059
1028
|
session.title = screenState.title || session.title;
|
|
1060
|
-
this.emitSessionEvent(
|
|
1029
|
+
this.emitSessionEvent("session-transition", {
|
|
1061
1030
|
sessionId,
|
|
1062
|
-
kind:
|
|
1063
|
-
data:
|
|
1031
|
+
kind: "resize",
|
|
1032
|
+
data: "",
|
|
1064
1033
|
runtimeEpoch: screenState.runtimeEpoch,
|
|
1065
1034
|
outputSeq: screenState.outputSeq,
|
|
1066
1035
|
stateRevision: screenState.stateRevision,
|
|
1067
1036
|
cols: screenState.cols,
|
|
1068
|
-
rows: screenState.rows
|
|
1037
|
+
rows: screenState.rows
|
|
1069
1038
|
});
|
|
1070
1039
|
return screenState;
|
|
1071
1040
|
});
|
|
1072
|
-
session.reducerCommitQueue = publishedCommit.catch(error => {
|
|
1041
|
+
session.reducerCommitQueue = publishedCommit.catch((error) => {
|
|
1073
1042
|
this.failSessionScreenState(session, error);
|
|
1074
1043
|
});
|
|
1075
1044
|
try {
|
|
1076
1045
|
await publishedCommit;
|
|
1077
1046
|
} catch {
|
|
1078
|
-
return { status:
|
|
1047
|
+
return { status: "resize-rejected", reason: "screen-reducer-failed", resized: false };
|
|
1079
1048
|
}
|
|
1080
1049
|
}
|
|
1081
1050
|
if (this.sessions.get(sessionId) !== session) {
|
|
1082
|
-
return { status:
|
|
1051
|
+
return { status: "resize-rejected", reason: "session-replaced", resized: false };
|
|
1083
1052
|
}
|
|
1084
|
-
|
|
1085
1053
|
return {
|
|
1086
|
-
status:
|
|
1054
|
+
status: "resize-committed",
|
|
1087
1055
|
resized: true,
|
|
1088
1056
|
unchanged: false,
|
|
1089
1057
|
runtimeEpoch,
|
|
1090
1058
|
outputSeq,
|
|
1091
1059
|
stateRevision,
|
|
1092
1060
|
cols: nextCols,
|
|
1093
|
-
rows: nextRows
|
|
1061
|
+
rows: nextRows
|
|
1094
1062
|
};
|
|
1095
1063
|
}
|
|
1096
|
-
|
|
1097
|
-
async clearBuffer(sessionId, expectedRuntimeEpoch = '') {
|
|
1064
|
+
async clearBuffer(sessionId, expectedRuntimeEpoch = "") {
|
|
1098
1065
|
const session = this.sessions.get(sessionId);
|
|
1099
|
-
if (!session || session.status ===
|
|
1066
|
+
if (!session || session.status === "exited" || session.exitFinalizing === true) {
|
|
1100
1067
|
return { cleared: false };
|
|
1101
1068
|
}
|
|
1102
1069
|
if (session.rotationFrozen === true) {
|
|
1103
|
-
return { cleared: false, reason:
|
|
1070
|
+
return { cleared: false, reason: "runtime-rotation" };
|
|
1104
1071
|
}
|
|
1105
1072
|
if (expectedRuntimeEpoch && expectedRuntimeEpoch !== session.runtimeEpoch) {
|
|
1106
|
-
return { cleared: false, reason:
|
|
1073
|
+
return { cleared: false, reason: "runtime-epoch-mismatch" };
|
|
1107
1074
|
}
|
|
1108
|
-
|
|
1109
|
-
session.output = '';
|
|
1075
|
+
session.output = "";
|
|
1110
1076
|
session.stateRevision += 1;
|
|
1111
1077
|
const stateRevision = session.stateRevision;
|
|
1112
1078
|
const outputSeq = session.outputSeq;
|
|
1113
1079
|
const runtimeEpoch = session.runtimeEpoch;
|
|
1114
|
-
session.renderOutput =
|
|
1115
|
-
session.previewText =
|
|
1080
|
+
session.renderOutput = "";
|
|
1081
|
+
session.previewText = "";
|
|
1116
1082
|
session.previewSnapshot = null;
|
|
1117
1083
|
session.lastActivityAt = Date.now();
|
|
1118
|
-
|
|
1119
1084
|
let exactState = {
|
|
1120
|
-
renderOutput:
|
|
1085
|
+
renderOutput: "",
|
|
1121
1086
|
runtimeEpoch,
|
|
1122
1087
|
outputSeq,
|
|
1123
1088
|
stateRevision,
|
|
1124
1089
|
cols: session.previewCols,
|
|
1125
|
-
rows: session.previewRows
|
|
1090
|
+
rows: session.previewRows
|
|
1126
1091
|
};
|
|
1127
1092
|
if (session.screenWorker) {
|
|
1128
1093
|
const reducerCommit = session.screenWorker.clear(stateRevision, outputSeq);
|
|
1129
1094
|
const publishedCommit = session.reducerCommitQueue.then(() => reducerCommit).then((screenState) => {
|
|
1130
1095
|
if (this.sessions.get(sessionId) !== session) {
|
|
1131
|
-
throw new Error(
|
|
1096
|
+
throw new Error("Terminal session was replaced during clear");
|
|
1132
1097
|
}
|
|
1133
|
-
if (
|
|
1134
|
-
|
|
1135
|
-
screenState.outputSeq !== outputSeq ||
|
|
1136
|
-
screenState.stateRevision !== stateRevision
|
|
1137
|
-
) {
|
|
1138
|
-
throw new Error('Terminal screen clear returned a non-authoritative state cut');
|
|
1098
|
+
if (screenState.runtimeEpoch !== runtimeEpoch || screenState.outputSeq !== outputSeq || screenState.stateRevision !== stateRevision) {
|
|
1099
|
+
throw new Error("Terminal screen clear returned a non-authoritative state cut");
|
|
1139
1100
|
}
|
|
1140
|
-
session.renderOutput = typeof screenState.renderOutput ===
|
|
1141
|
-
session.previewText = screenState.previewText ||
|
|
1101
|
+
session.renderOutput = typeof screenState.renderOutput === "string" ? screenState.renderOutput : "";
|
|
1102
|
+
session.previewText = screenState.previewText || "";
|
|
1142
1103
|
session.previewSnapshot = screenState.previewSnapshot || null;
|
|
1143
1104
|
session.previewCols = screenState.cols || session.previewCols;
|
|
1144
1105
|
session.previewRows = screenState.rows || session.previewRows;
|
|
1145
1106
|
exactState = screenState;
|
|
1146
1107
|
if (screenState.title && screenState.title !== session.title) {
|
|
1147
1108
|
session.title = screenState.title;
|
|
1148
|
-
this.emitSessionEvent(
|
|
1109
|
+
this.emitSessionEvent("session-title", {
|
|
1149
1110
|
sessionId,
|
|
1150
1111
|
title: session.title,
|
|
1151
|
-
runtimeEpoch: session.runtimeEpoch
|
|
1112
|
+
runtimeEpoch: session.runtimeEpoch
|
|
1152
1113
|
});
|
|
1153
1114
|
}
|
|
1154
|
-
this.emitSessionEvent(
|
|
1115
|
+
this.emitSessionEvent("session-transition", {
|
|
1155
1116
|
sessionId,
|
|
1156
|
-
kind:
|
|
1157
|
-
data:
|
|
1117
|
+
kind: "clear",
|
|
1118
|
+
data: "\x1B[2J\x1B[3J\x1B[H",
|
|
1158
1119
|
runtimeEpoch: screenState.runtimeEpoch,
|
|
1159
1120
|
outputSeq: screenState.outputSeq,
|
|
1160
1121
|
stateRevision: screenState.stateRevision,
|
|
1161
1122
|
cols: screenState.cols,
|
|
1162
|
-
rows: screenState.rows
|
|
1123
|
+
rows: screenState.rows
|
|
1163
1124
|
});
|
|
1164
1125
|
return screenState;
|
|
1165
1126
|
});
|
|
1166
|
-
session.reducerCommitQueue = publishedCommit.catch(error => {
|
|
1127
|
+
session.reducerCommitQueue = publishedCommit.catch((error) => {
|
|
1167
1128
|
this.failSessionScreenState(session, error);
|
|
1168
1129
|
});
|
|
1169
1130
|
try {
|
|
@@ -1172,20 +1133,20 @@ class NativePtyHost {
|
|
|
1172
1133
|
return { cleared: false };
|
|
1173
1134
|
}
|
|
1174
1135
|
}
|
|
1175
|
-
if (this.sessions.get(sessionId) !== session) return { cleared: false, reason:
|
|
1176
|
-
this.emitSessionEvent(
|
|
1136
|
+
if (this.sessions.get(sessionId) !== session) return { cleared: false, reason: "session-replaced" };
|
|
1137
|
+
this.emitSessionEvent("session-preview", {
|
|
1177
1138
|
sessionId,
|
|
1178
1139
|
previewText: session.previewText,
|
|
1179
1140
|
cols: session.previewCols,
|
|
1180
1141
|
rows: session.previewRows,
|
|
1181
1142
|
previewSnapshot: session.previewSnapshot,
|
|
1182
1143
|
title: session.title,
|
|
1183
|
-
runtimeEpoch: session.runtimeEpoch
|
|
1144
|
+
runtimeEpoch: session.runtimeEpoch
|
|
1184
1145
|
});
|
|
1185
|
-
this.emitSessionEvent(
|
|
1146
|
+
this.emitSessionEvent("session-activity", {
|
|
1186
1147
|
sessionId,
|
|
1187
1148
|
lastActivityAt: session.lastActivityAt,
|
|
1188
|
-
runtimeEpoch: session.runtimeEpoch
|
|
1149
|
+
runtimeEpoch: session.runtimeEpoch
|
|
1189
1150
|
});
|
|
1190
1151
|
return {
|
|
1191
1152
|
cleared: true,
|
|
@@ -1193,34 +1154,29 @@ class NativePtyHost {
|
|
|
1193
1154
|
outputSeq: exactState.outputSeq,
|
|
1194
1155
|
stateRevision: exactState.stateRevision,
|
|
1195
1156
|
cols: exactState.cols,
|
|
1196
|
-
rows: exactState.rows
|
|
1157
|
+
rows: exactState.rows
|
|
1197
1158
|
};
|
|
1198
1159
|
}
|
|
1199
|
-
|
|
1200
1160
|
async killSession(sessionId) {
|
|
1201
1161
|
const session = this.sessions.get(sessionId);
|
|
1202
1162
|
if (!session || !session.process) return { killed: false };
|
|
1203
|
-
if (session.status ===
|
|
1204
|
-
session.status =
|
|
1163
|
+
if (session.status === "exited") return { killed: false };
|
|
1164
|
+
session.status = "stopping";
|
|
1205
1165
|
session.killRequestedAt = Date.now();
|
|
1206
|
-
session.process.kill(
|
|
1166
|
+
session.process.kill("SIGTERM");
|
|
1207
1167
|
const timer = setTimeout(() => {
|
|
1208
1168
|
const latest = this.sessions.get(sessionId);
|
|
1209
|
-
if (latest !== session || latest.status ===
|
|
1210
|
-
latest.process.kill(
|
|
1169
|
+
if (latest !== session || latest.status === "exited" || !latest.process) return;
|
|
1170
|
+
latest.process.kill("SIGKILL");
|
|
1211
1171
|
}, 1500);
|
|
1212
|
-
if (typeof timer.unref ===
|
|
1172
|
+
if (typeof timer.unref === "function") timer.unref();
|
|
1213
1173
|
return { killed: true };
|
|
1214
1174
|
}
|
|
1215
|
-
|
|
1216
1175
|
async getSessionState(sessionId) {
|
|
1217
1176
|
const session = this.sessions.get(sessionId);
|
|
1218
1177
|
if (!session) return null;
|
|
1219
|
-
// Cut the raw output before the async worker request. The screen worker
|
|
1220
|
-
// receives every append with its output sequence and reports the exact
|
|
1221
|
-
// sequence represented by its serialized terminal state.
|
|
1222
1178
|
const snapshotOutput = session.output;
|
|
1223
|
-
const fallbackPreviewText = session.previewText || snapshotOutput.slice(-
|
|
1179
|
+
const fallbackPreviewText = session.previewText || snapshotOutput.slice(-2e3);
|
|
1224
1180
|
const fallbackPreviewSnapshot = session.previewSnapshot;
|
|
1225
1181
|
const fallbackPreviewCols = session.previewCols;
|
|
1226
1182
|
const fallbackPreviewRows = session.previewRows;
|
|
@@ -1229,8 +1185,7 @@ class NativePtyHost {
|
|
|
1229
1185
|
const checkpoint = stateProofAvailable ? await captureTerminalAttachCheckpoint(session) : null;
|
|
1230
1186
|
if (this.sessions.get(sessionId) !== session) return null;
|
|
1231
1187
|
const title = checkpoint ? checkpoint.title : fallbackTitle;
|
|
1232
|
-
const previewText = checkpoint ? checkpoint.previewText :
|
|
1233
|
-
|
|
1188
|
+
const previewText = checkpoint ? checkpoint.previewText : stateProofAvailable ? fallbackPreviewText : "";
|
|
1234
1189
|
return {
|
|
1235
1190
|
sessionId: session.id,
|
|
1236
1191
|
status: session.status,
|
|
@@ -1239,9 +1194,9 @@ class NativePtyHost {
|
|
|
1239
1194
|
outputSeq: checkpoint?.outputSeq ?? null,
|
|
1240
1195
|
stateRevision: checkpoint?.stateRevision ?? null,
|
|
1241
1196
|
stateProofAvailable,
|
|
1242
|
-
renderOutput: checkpoint ? checkpoint.renderOutput :
|
|
1197
|
+
renderOutput: checkpoint ? checkpoint.renderOutput : stateProofAvailable ? snapshotOutput : "",
|
|
1243
1198
|
previewText,
|
|
1244
|
-
previewSnapshot: checkpoint ? checkpoint.previewSnapshot :
|
|
1199
|
+
previewSnapshot: checkpoint ? checkpoint.previewSnapshot : stateProofAvailable ? fallbackPreviewSnapshot : null,
|
|
1245
1200
|
previewCols: checkpoint ? checkpoint.cols : fallbackPreviewCols,
|
|
1246
1201
|
previewRows: checkpoint ? checkpoint.rows : fallbackPreviewRows,
|
|
1247
1202
|
title,
|
|
@@ -1249,8 +1204,8 @@ class NativePtyHost {
|
|
|
1249
1204
|
startedAt: session.startedAt,
|
|
1250
1205
|
exitedAt: session.exitedAt || null,
|
|
1251
1206
|
terminalBusy: session.terminalBusy,
|
|
1252
|
-
shellCommand: session.shellCommand ||
|
|
1253
|
-
shellLastCommand: session.shellLastCommand ||
|
|
1207
|
+
shellCommand: session.shellCommand || "",
|
|
1208
|
+
shellLastCommand: session.shellLastCommand || "",
|
|
1254
1209
|
shellCommandStartedAt: session.shellCommandStartedAt ?? null,
|
|
1255
1210
|
shellLastCommandStartedAt: session.shellLastCommandStartedAt ?? null,
|
|
1256
1211
|
shellLastCommandFinishedAt: session.shellLastCommandFinishedAt ?? null,
|
|
@@ -1269,138 +1224,115 @@ class NativePtyHost {
|
|
|
1269
1224
|
shellCommandStartedAt: session.shellCommandStartedAt,
|
|
1270
1225
|
shellLastCommandStartedAt: session.shellLastCommandStartedAt,
|
|
1271
1226
|
shellLastCommandFinishedAt: session.shellLastCommandFinishedAt,
|
|
1272
|
-
shellLastCommandDurationMs: session.shellLastCommandDurationMs
|
|
1273
|
-
})
|
|
1227
|
+
shellLastCommandDurationMs: session.shellLastCommandDurationMs
|
|
1228
|
+
})
|
|
1274
1229
|
};
|
|
1275
1230
|
}
|
|
1276
|
-
|
|
1277
1231
|
async getSessionPreview(sessionId) {
|
|
1278
1232
|
const state = await this.getSessionState(sessionId);
|
|
1279
|
-
return state
|
|
1233
|
+
return state && typeof state.previewText === "string" ? state.previewText : "";
|
|
1280
1234
|
}
|
|
1281
|
-
|
|
1282
1235
|
async recoverSessions() {
|
|
1283
1236
|
const recovered = [];
|
|
1284
1237
|
for (const session of this.sessions.values()) {
|
|
1285
|
-
if (session.status ===
|
|
1238
|
+
if (session.status === "exited") continue;
|
|
1286
1239
|
recovered.push({
|
|
1287
1240
|
agentId: session.id,
|
|
1288
1241
|
metadata: session.metadata || {},
|
|
1289
|
-
state: await this.getSessionState(session.id)
|
|
1242
|
+
state: await this.getSessionState(session.id)
|
|
1290
1243
|
});
|
|
1291
1244
|
}
|
|
1292
1245
|
return recovered;
|
|
1293
1246
|
}
|
|
1294
|
-
|
|
1295
1247
|
serializeTerminalState(client = null) {
|
|
1296
1248
|
if (client) this.assertActiveController(client);
|
|
1297
1249
|
if (this.rotationPreparation) {
|
|
1298
|
-
if (
|
|
1299
|
-
|
|
1300
|
-
this.rotationPreparation.controllerClient &&
|
|
1301
|
-
this.rotationPreparation.controllerClient !== client
|
|
1302
|
-
) {
|
|
1303
|
-
throw new Error('Native pty host rotation is owned by another controller');
|
|
1250
|
+
if (client && this.rotationPreparation.controllerClient && this.rotationPreparation.controllerClient !== client) {
|
|
1251
|
+
throw new Error("Native pty host rotation is owned by another controller");
|
|
1304
1252
|
}
|
|
1305
1253
|
return this.rotationPreparation.promise;
|
|
1306
1254
|
}
|
|
1307
|
-
|
|
1308
1255
|
const preparation = {
|
|
1309
1256
|
token: crypto.randomUUID(),
|
|
1310
1257
|
controllerClient: client,
|
|
1311
|
-
phase:
|
|
1258
|
+
phase: "preparing",
|
|
1312
1259
|
promise: null,
|
|
1313
|
-
serializedTerminalState:
|
|
1260
|
+
serializedTerminalState: ""
|
|
1314
1261
|
};
|
|
1315
1262
|
this.rotationPreparation = preparation;
|
|
1316
1263
|
preparation.promise = this.prepareSerializedTerminalState(preparation);
|
|
1317
1264
|
return preparation.promise;
|
|
1318
1265
|
}
|
|
1319
|
-
|
|
1320
1266
|
async prepareSerializedTerminalState(preparation) {
|
|
1321
1267
|
const entries = [];
|
|
1322
1268
|
try {
|
|
1323
1269
|
await Promise.allSettled([...this.activeControllerMutations]);
|
|
1324
1270
|
if (this.rotationPreparation !== preparation) {
|
|
1325
|
-
throw new Error(
|
|
1271
|
+
throw new Error("Native pty host rotation preparation was cancelled");
|
|
1326
1272
|
}
|
|
1327
|
-
|
|
1328
|
-
const liveSessions = [...this.sessions.values()]
|
|
1329
|
-
.filter(session => session.status === 'running' && session.stateProofAvailable !== false);
|
|
1273
|
+
const liveSessions = [...this.sessions.values()].filter((session) => session.status === "running" && session.stateProofAvailable !== false);
|
|
1330
1274
|
for (const session of liveSessions) {
|
|
1331
1275
|
session.rotationFrozen = true;
|
|
1332
1276
|
const flowError = setTerminalExternalFlowControlBlocked(
|
|
1333
1277
|
session.reducerFlowControl,
|
|
1334
1278
|
session.process,
|
|
1335
|
-
true
|
|
1279
|
+
true
|
|
1336
1280
|
);
|
|
1337
1281
|
if (flowError) throw flowError;
|
|
1338
1282
|
}
|
|
1339
|
-
|
|
1340
1283
|
for (const session of liveSessions) {
|
|
1341
1284
|
await session.reducerCommitQueue;
|
|
1342
1285
|
const state = await this.getSessionState(session.id);
|
|
1343
1286
|
const current = this.sessions.get(session.id);
|
|
1344
|
-
if (
|
|
1345
|
-
current !== session ||
|
|
1346
|
-
session.status !== 'running' ||
|
|
1347
|
-
state?.status !== 'running'
|
|
1348
|
-
) {
|
|
1287
|
+
if (current !== session || session.status !== "running" || state?.status !== "running") {
|
|
1349
1288
|
continue;
|
|
1350
1289
|
}
|
|
1351
|
-
if (
|
|
1352
|
-
!state ||
|
|
1353
|
-
typeof state.renderOutput !== 'string' ||
|
|
1354
|
-
!Number.isFinite(state.outputSeq) ||
|
|
1355
|
-
!Number.isFinite(state.stateRevision)
|
|
1356
|
-
) {
|
|
1290
|
+
if (!state || typeof state.renderOutput !== "string" || !Number.isFinite(state.outputSeq) || !Number.isFinite(state.stateRevision)) {
|
|
1357
1291
|
throw new Error(`Cannot serialize terminal ${session.id} without an exact reducer checkpoint`);
|
|
1358
1292
|
}
|
|
1359
1293
|
entries.push({
|
|
1360
1294
|
id: session.id,
|
|
1361
1295
|
metadata: session.metadata || {},
|
|
1362
1296
|
processDetails: {
|
|
1363
|
-
cwd: session.shellCwd || session.cwd ||
|
|
1364
|
-
title: state.title || session.title ||
|
|
1297
|
+
cwd: session.shellCwd || session.cwd || "",
|
|
1298
|
+
title: state.title || session.title || ""
|
|
1365
1299
|
},
|
|
1366
1300
|
processLaunchConfig: {
|
|
1367
|
-
command: session.command ||
|
|
1301
|
+
command: session.command || "",
|
|
1368
1302
|
args: session.args || [],
|
|
1369
|
-
category: session.metadata?.category ||
|
|
1303
|
+
category: session.metadata?.category || ""
|
|
1370
1304
|
},
|
|
1371
1305
|
replayEvent: {
|
|
1372
1306
|
events: [{
|
|
1373
1307
|
data: state.renderOutput,
|
|
1374
1308
|
cols: state.previewCols || session.previewCols || DEFAULT_COLS,
|
|
1375
|
-
rows: state.previewRows || session.previewRows || DEFAULT_ROWS
|
|
1376
|
-
}]
|
|
1309
|
+
rows: state.previewRows || session.previewRows || DEFAULT_ROWS
|
|
1310
|
+
}]
|
|
1377
1311
|
},
|
|
1378
|
-
timestamp: Date.now()
|
|
1312
|
+
timestamp: Date.now()
|
|
1379
1313
|
});
|
|
1380
1314
|
}
|
|
1381
1315
|
const serializedTerminalState = serializeTerminalState(entries);
|
|
1382
|
-
preparation.phase =
|
|
1316
|
+
preparation.phase = "prepared";
|
|
1383
1317
|
preparation.serializedTerminalState = serializedTerminalState;
|
|
1384
1318
|
return {
|
|
1385
1319
|
preparationToken: preparation.token,
|
|
1386
|
-
serializedTerminalState
|
|
1320
|
+
serializedTerminalState
|
|
1387
1321
|
};
|
|
1388
1322
|
} catch (error) {
|
|
1389
1323
|
this.resumePreparedTerminalState(preparation.token);
|
|
1390
1324
|
throw error;
|
|
1391
1325
|
}
|
|
1392
1326
|
}
|
|
1393
|
-
|
|
1394
|
-
resumeTerminalState(client = null, preparationToken = '') {
|
|
1327
|
+
resumeTerminalState(client = null, preparationToken = "") {
|
|
1395
1328
|
if (client) this.assertActiveController(client);
|
|
1396
1329
|
return this.resumePreparedTerminalState(preparationToken);
|
|
1397
1330
|
}
|
|
1398
|
-
|
|
1399
|
-
resumePreparedTerminalState(preparationToken = '') {
|
|
1331
|
+
resumePreparedTerminalState(preparationToken = "") {
|
|
1400
1332
|
const preparation = this.rotationPreparation;
|
|
1401
1333
|
if (!preparation) return { resumed: 0 };
|
|
1402
1334
|
if (preparationToken && preparation.token !== preparationToken) {
|
|
1403
|
-
throw new Error(
|
|
1335
|
+
throw new Error("Native pty rotation preparation token does not match");
|
|
1404
1336
|
}
|
|
1405
1337
|
let resumed = 0;
|
|
1406
1338
|
for (const session of this.sessions.values()) {
|
|
@@ -1409,7 +1341,7 @@ class NativePtyHost {
|
|
|
1409
1341
|
const flowError = setTerminalExternalFlowControlBlocked(
|
|
1410
1342
|
session.reducerFlowControl,
|
|
1411
1343
|
session.process,
|
|
1412
|
-
false
|
|
1344
|
+
false
|
|
1413
1345
|
);
|
|
1414
1346
|
if (flowError) {
|
|
1415
1347
|
this.failSessionScreenState(session, flowError);
|
|
@@ -1422,47 +1354,34 @@ class NativePtyHost {
|
|
|
1422
1354
|
}
|
|
1423
1355
|
return { resumed };
|
|
1424
1356
|
}
|
|
1425
|
-
|
|
1426
1357
|
async updateSessionMetadata(sessionId, patch) {
|
|
1427
1358
|
const session = this.sessions.get(sessionId);
|
|
1428
1359
|
if (!session) return null;
|
|
1429
1360
|
session.metadata = {
|
|
1430
|
-
...
|
|
1431
|
-
...
|
|
1361
|
+
...session.metadata || {},
|
|
1362
|
+
...patch || {},
|
|
1432
1363
|
agentId: sessionId,
|
|
1433
|
-
engineName:
|
|
1364
|
+
engineName: "native"
|
|
1434
1365
|
};
|
|
1435
1366
|
return session.metadata;
|
|
1436
1367
|
}
|
|
1437
|
-
|
|
1438
|
-
shutdownHost(client, rawIdentity = {}, preparationToken = '') {
|
|
1368
|
+
shutdownHost(client, rawIdentity = {}, preparationToken = "") {
|
|
1439
1369
|
const requestedGeneration = Math.floor(Number(rawIdentity.generation));
|
|
1440
|
-
const requestedId = typeof rawIdentity.id ===
|
|
1370
|
+
const requestedId = typeof rawIdentity.id === "string" ? rawIdentity.id : "";
|
|
1441
1371
|
const active = this.activeControllerIdentity;
|
|
1442
|
-
const activeClient = client
|
|
1443
|
-
|
|
1444
|
-
&& active
|
|
1445
|
-
&& client.controllerId === active.id
|
|
1446
|
-
&& client.controllerGeneration === active.generation;
|
|
1447
|
-
const newerController = requestedId
|
|
1448
|
-
&& Number.isFinite(requestedGeneration)
|
|
1449
|
-
&& requestedGeneration > 0
|
|
1450
|
-
&& (!active || requestedGeneration > active.generation);
|
|
1372
|
+
const activeClient = client && client === this.activeControllerClient && active && client.controllerId === active.id && client.controllerGeneration === active.generation;
|
|
1373
|
+
const newerController = requestedId && Number.isFinite(requestedGeneration) && requestedGeneration > 0 && (!active || requestedGeneration > active.generation);
|
|
1451
1374
|
if (!activeClient && !newerController) {
|
|
1452
|
-
throw new Error(
|
|
1375
|
+
throw new Error("Native pty shutdown requires the active or a newer controller");
|
|
1453
1376
|
}
|
|
1454
1377
|
const preparation = this.rotationPreparation;
|
|
1455
1378
|
if (preparation) {
|
|
1456
|
-
if (
|
|
1457
|
-
|
|
1458
|
-
!preparationToken ||
|
|
1459
|
-
preparationToken !== preparation.token
|
|
1460
|
-
) {
|
|
1461
|
-
throw new Error('Native pty shutdown requires the prepared rotation token');
|
|
1379
|
+
if (preparation.phase !== "prepared" || !preparationToken || preparationToken !== preparation.token) {
|
|
1380
|
+
throw new Error("Native pty shutdown requires the prepared rotation token");
|
|
1462
1381
|
}
|
|
1463
|
-
preparation.phase =
|
|
1382
|
+
preparation.phase = "committing";
|
|
1464
1383
|
} else if (this.hasLiveSessions()) {
|
|
1465
|
-
throw new Error(
|
|
1384
|
+
throw new Error("Native pty shutdown requires a prepared terminal checkpoint");
|
|
1466
1385
|
}
|
|
1467
1386
|
setImmediate(() => {
|
|
1468
1387
|
this.dispose().finally(() => {
|
|
@@ -1471,26 +1390,21 @@ class NativePtyHost {
|
|
|
1471
1390
|
});
|
|
1472
1391
|
return { shuttingDown: true };
|
|
1473
1392
|
}
|
|
1474
|
-
|
|
1475
1393
|
async disposeSessionProcess(session) {
|
|
1476
|
-
if (!session || !session.process || session.status ===
|
|
1477
|
-
session.status =
|
|
1394
|
+
if (!session || !session.process || session.status === "exited") return;
|
|
1395
|
+
session.status = "stopping";
|
|
1478
1396
|
try {
|
|
1479
|
-
session.process.kill(
|
|
1397
|
+
session.process.kill("SIGTERM");
|
|
1480
1398
|
} catch {
|
|
1481
1399
|
return;
|
|
1482
1400
|
}
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
if (session.status === 'exited') return;
|
|
1486
|
-
|
|
1401
|
+
await new Promise((resolve) => setTimeout(resolve, DISPOSE_PTY_GRACE_MS));
|
|
1402
|
+
if (session.status === "exited") return;
|
|
1487
1403
|
try {
|
|
1488
|
-
session.process.kill(
|
|
1404
|
+
session.process.kill("SIGKILL");
|
|
1489
1405
|
} catch {
|
|
1490
|
-
// ignore dispose races
|
|
1491
1406
|
}
|
|
1492
1407
|
}
|
|
1493
|
-
|
|
1494
1408
|
async dispose() {
|
|
1495
1409
|
if (this.disposed) return;
|
|
1496
1410
|
this.disposed = true;
|
|
@@ -1499,54 +1413,51 @@ class NativePtyHost {
|
|
|
1499
1413
|
this.ownerCheckTimer = null;
|
|
1500
1414
|
}
|
|
1501
1415
|
this.cancelIdleExit();
|
|
1502
|
-
await Promise.allSettled([...this.sessions.values()].map(session => this.disposeSessionProcess(session)));
|
|
1416
|
+
await Promise.allSettled([...this.sessions.values()].map((session) => this.disposeSessionProcess(session)));
|
|
1503
1417
|
for (const session of this.sessions.values()) {
|
|
1504
1418
|
cleanupShellBusyIntegration(session.shellBusyIntegration);
|
|
1505
1419
|
if (session.screenWorker) {
|
|
1506
|
-
await session.screenWorker.dispose().catch(() => {
|
|
1420
|
+
await session.screenWorker.dispose().catch(() => {
|
|
1421
|
+
});
|
|
1507
1422
|
}
|
|
1508
1423
|
}
|
|
1509
1424
|
await this.screenWorkerPool.dispose();
|
|
1510
|
-
if (this.server && process.platform !==
|
|
1425
|
+
if (this.server && process.platform !== "win32") {
|
|
1511
1426
|
try {
|
|
1512
1427
|
const currentSocketIdentity = socketIdentity(this.socketPath);
|
|
1513
1428
|
if (sameSocketIdentity(currentSocketIdentity, this.boundSocketIdentity)) {
|
|
1514
|
-
// Unlink while this listener is still active. A competing host cannot
|
|
1515
|
-
// replace an active socket between the ownership check and unlink.
|
|
1516
1429
|
fs.unlinkSync(this.socketPath);
|
|
1517
1430
|
}
|
|
1518
1431
|
} catch (error) {
|
|
1519
|
-
if (
|
|
1432
|
+
if (errorCode(error) !== "ENOENT") throw error;
|
|
1520
1433
|
}
|
|
1521
1434
|
this.boundSocketIdentity = null;
|
|
1522
1435
|
}
|
|
1523
1436
|
if (this.server) {
|
|
1524
|
-
await new Promise(resolve => this.server
|
|
1437
|
+
await new Promise((resolve) => this.server?.close(() => resolve()));
|
|
1525
1438
|
this.server = null;
|
|
1526
1439
|
}
|
|
1527
1440
|
}
|
|
1528
1441
|
}
|
|
1529
|
-
|
|
1530
1442
|
function startNativePtyHostProcess() {
|
|
1531
1443
|
const host = new NativePtyHost();
|
|
1532
|
-
host.start().catch(error => {
|
|
1444
|
+
host.start().catch((error) => {
|
|
1533
1445
|
console.error(error && error.stack ? error.stack : error);
|
|
1534
1446
|
process.exit(1);
|
|
1535
1447
|
});
|
|
1536
|
-
|
|
1537
|
-
process.on('SIGTERM', () => {
|
|
1448
|
+
process.on("SIGTERM", () => {
|
|
1538
1449
|
host.dispose().finally(() => process.exit(0));
|
|
1539
1450
|
});
|
|
1540
|
-
process.on(
|
|
1451
|
+
process.on("SIGINT", () => {
|
|
1541
1452
|
host.dispose().finally(() => process.exit(0));
|
|
1542
1453
|
});
|
|
1543
|
-
|
|
1544
1454
|
return host;
|
|
1545
1455
|
}
|
|
1546
|
-
|
|
1547
1456
|
if (require.main === module) {
|
|
1548
1457
|
startNativePtyHostProcess();
|
|
1549
1458
|
}
|
|
1550
|
-
|
|
1551
|
-
module.exports =
|
|
1552
|
-
|
|
1459
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
1460
|
+
0 && (module.exports = {
|
|
1461
|
+
NativePtyHost,
|
|
1462
|
+
startNativePtyHostProcess
|
|
1463
|
+
});
|