parasor 0.1.0
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/LICENSE +21 -0
- package/README.md +208 -0
- package/THIRD-PARTY-NOTICES.md +138 -0
- package/bin/parasor.mjs +3 -0
- package/node_modules/@parasor/shared/dist/client.d.ts +8 -0
- package/node_modules/@parasor/shared/dist/client.d.ts.map +1 -0
- package/node_modules/@parasor/shared/dist/client.js +2 -0
- package/node_modules/@parasor/shared/dist/client.js.map +1 -0
- package/node_modules/@parasor/shared/dist/drops.d.ts +24 -0
- package/node_modules/@parasor/shared/dist/drops.d.ts.map +1 -0
- package/node_modules/@parasor/shared/dist/drops.js +2 -0
- package/node_modules/@parasor/shared/dist/drops.js.map +1 -0
- package/node_modules/@parasor/shared/dist/file-uploads.d.ts +56 -0
- package/node_modules/@parasor/shared/dist/file-uploads.d.ts.map +1 -0
- package/node_modules/@parasor/shared/dist/file-uploads.js +13 -0
- package/node_modules/@parasor/shared/dist/file-uploads.js.map +1 -0
- package/node_modules/@parasor/shared/dist/ide-commands.d.ts +8 -0
- package/node_modules/@parasor/shared/dist/ide-commands.d.ts.map +1 -0
- package/node_modules/@parasor/shared/dist/ide-commands.js +59 -0
- package/node_modules/@parasor/shared/dist/ide-commands.js.map +1 -0
- package/node_modules/@parasor/shared/dist/pane-commands.d.ts +7 -0
- package/node_modules/@parasor/shared/dist/pane-commands.d.ts.map +1 -0
- package/node_modules/@parasor/shared/dist/pane-commands.js +47 -0
- package/node_modules/@parasor/shared/dist/pane-commands.js.map +1 -0
- package/node_modules/@parasor/shared/dist/pane-model.d.ts +63 -0
- package/node_modules/@parasor/shared/dist/pane-model.d.ts.map +1 -0
- package/node_modules/@parasor/shared/dist/pane-model.js +89 -0
- package/node_modules/@parasor/shared/dist/pane-model.js.map +1 -0
- package/node_modules/@parasor/shared/dist/panes.d.ts +33 -0
- package/node_modules/@parasor/shared/dist/panes.d.ts.map +1 -0
- package/node_modules/@parasor/shared/dist/panes.js +2 -0
- package/node_modules/@parasor/shared/dist/panes.js.map +1 -0
- package/node_modules/@parasor/shared/dist/runtime.d.ts +180 -0
- package/node_modules/@parasor/shared/dist/runtime.d.ts.map +1 -0
- package/node_modules/@parasor/shared/dist/runtime.js +2 -0
- package/node_modules/@parasor/shared/dist/runtime.js.map +1 -0
- package/node_modules/@parasor/shared/dist/state.d.ts +192 -0
- package/node_modules/@parasor/shared/dist/state.d.ts.map +1 -0
- package/node_modules/@parasor/shared/dist/state.js +10 -0
- package/node_modules/@parasor/shared/dist/state.js.map +1 -0
- package/node_modules/@parasor/shared/dist/terminal.d.ts +191 -0
- package/node_modules/@parasor/shared/dist/terminal.d.ts.map +1 -0
- package/node_modules/@parasor/shared/dist/terminal.js +174 -0
- package/node_modules/@parasor/shared/dist/terminal.js.map +1 -0
- package/node_modules/@parasor/shared/dist/types.d.ts +13 -0
- package/node_modules/@parasor/shared/dist/types.d.ts.map +1 -0
- package/node_modules/@parasor/shared/dist/types.js +13 -0
- package/node_modules/@parasor/shared/dist/types.js.map +1 -0
- package/node_modules/@parasor/shared/dist/worktree-local-files.d.ts +14 -0
- package/node_modules/@parasor/shared/dist/worktree-local-files.d.ts.map +1 -0
- package/node_modules/@parasor/shared/dist/worktree-local-files.js +41 -0
- package/node_modules/@parasor/shared/dist/worktree-local-files.js.map +1 -0
- package/node_modules/@parasor/shared/dist/ws-events.d.ts +152 -0
- package/node_modules/@parasor/shared/dist/ws-events.d.ts.map +1 -0
- package/node_modules/@parasor/shared/dist/ws-events.js +2 -0
- package/node_modules/@parasor/shared/dist/ws-events.js.map +1 -0
- package/node_modules/@parasor/shared/package.json +13 -0
- package/package.json +61 -0
- package/server/agent-detector/agent-state-store.d.ts +23 -0
- package/server/agent-detector/agent-state-store.d.ts.map +1 -0
- package/server/agent-detector/agent-state-store.js +110 -0
- package/server/agent-detector/agent-state-store.js.map +1 -0
- package/server/agent-detector/detector.d.ts +80 -0
- package/server/agent-detector/detector.d.ts.map +1 -0
- package/server/agent-detector/detector.js +219 -0
- package/server/agent-detector/detector.js.map +1 -0
- package/server/agent-detector/event-map.d.ts +25 -0
- package/server/agent-detector/event-map.d.ts.map +1 -0
- package/server/agent-detector/event-map.js +136 -0
- package/server/agent-detector/event-map.js.map +1 -0
- package/server/agent-detector/manual-agent-tracker.d.ts +19 -0
- package/server/agent-detector/manual-agent-tracker.d.ts.map +1 -0
- package/server/agent-detector/manual-agent-tracker.js +123 -0
- package/server/agent-detector/manual-agent-tracker.js.map +1 -0
- package/server/agent-detector/output-eligibility.d.ts +3 -0
- package/server/agent-detector/output-eligibility.d.ts.map +1 -0
- package/server/agent-detector/output-eligibility.js +39 -0
- package/server/agent-detector/output-eligibility.js.map +1 -0
- package/server/application/files/errors.d.ts +28 -0
- package/server/application/files/errors.d.ts.map +1 -0
- package/server/application/files/errors.js +55 -0
- package/server/application/files/errors.js.map +1 -0
- package/server/application/files/local-filesystem.d.ts +36 -0
- package/server/application/files/local-filesystem.d.ts.map +1 -0
- package/server/application/files/local-filesystem.js +171 -0
- package/server/application/files/local-filesystem.js.map +1 -0
- package/server/application/files/project-file-queries.d.ts +16 -0
- package/server/application/files/project-file-queries.d.ts.map +1 -0
- package/server/application/files/project-file-queries.js +126 -0
- package/server/application/files/project-file-queries.js.map +1 -0
- package/server/application/integrations/errors.d.ts +16 -0
- package/server/application/integrations/errors.d.ts.map +1 -0
- package/server/application/integrations/errors.js +31 -0
- package/server/application/integrations/errors.js.map +1 -0
- package/server/application/integrations/hook-notify.d.ts +32 -0
- package/server/application/integrations/hook-notify.d.ts.map +1 -0
- package/server/application/integrations/hook-notify.js +118 -0
- package/server/application/integrations/hook-notify.js.map +1 -0
- package/server/application/integrations/open-url.d.ts +11 -0
- package/server/application/integrations/open-url.d.ts.map +1 -0
- package/server/application/integrations/open-url.js +23 -0
- package/server/application/integrations/open-url.js.map +1 -0
- package/server/application/ports.d.ts +11 -0
- package/server/application/ports.d.ts.map +1 -0
- package/server/application/ports.js +2 -0
- package/server/application/ports.js.map +1 -0
- package/server/application/workspace/errors.d.ts +21 -0
- package/server/application/workspace/errors.d.ts.map +1 -0
- package/server/application/workspace/errors.js +21 -0
- package/server/application/workspace/errors.js.map +1 -0
- package/server/application/workspace/pane-commands.d.ts +47 -0
- package/server/application/workspace/pane-commands.d.ts.map +1 -0
- package/server/application/workspace/pane-commands.js +193 -0
- package/server/application/workspace/pane-commands.js.map +1 -0
- package/server/application/workspace/project-commands.d.ts +34 -0
- package/server/application/workspace/project-commands.d.ts.map +1 -0
- package/server/application/workspace/project-commands.js +81 -0
- package/server/application/workspace/project-commands.js.map +1 -0
- package/server/application/workspace/project-queries.d.ts +21 -0
- package/server/application/workspace/project-queries.d.ts.map +1 -0
- package/server/application/workspace/project-queries.js +216 -0
- package/server/application/workspace/project-queries.js.map +1 -0
- package/server/application/workspace/session-commands.d.ts +24 -0
- package/server/application/workspace/session-commands.d.ts.map +1 -0
- package/server/application/workspace/session-commands.js +123 -0
- package/server/application/workspace/session-commands.js.map +1 -0
- package/server/application/workspace/session-queries.d.ts +14 -0
- package/server/application/workspace/session-queries.d.ts.map +1 -0
- package/server/application/workspace/session-queries.js +47 -0
- package/server/application/workspace/session-queries.js.map +1 -0
- package/server/application/workspace/worktree-commands.d.ts +98 -0
- package/server/application/workspace/worktree-commands.d.ts.map +1 -0
- package/server/application/workspace/worktree-commands.js +323 -0
- package/server/application/workspace/worktree-commands.js.map +1 -0
- package/server/application/workspace/worktree-local-files.d.ts +11 -0
- package/server/application/workspace/worktree-local-files.d.ts.map +1 -0
- package/server/application/workspace/worktree-local-files.js +223 -0
- package/server/application/workspace/worktree-local-files.js.map +1 -0
- package/server/application/workspace/worktree-reconcile.d.ts +21 -0
- package/server/application/workspace/worktree-reconcile.d.ts.map +1 -0
- package/server/application/workspace/worktree-reconcile.js +49 -0
- package/server/application/workspace/worktree-reconcile.js.map +1 -0
- package/server/auth/origin.d.ts +17 -0
- package/server/auth/origin.d.ts.map +1 -0
- package/server/auth/origin.js +93 -0
- package/server/auth/origin.js.map +1 -0
- package/server/auth/pairing-token.d.ts +28 -0
- package/server/auth/pairing-token.d.ts.map +1 -0
- package/server/auth/pairing-token.js +46 -0
- package/server/auth/pairing-token.js.map +1 -0
- package/server/auth/token-exchange.d.ts +11 -0
- package/server/auth/token-exchange.d.ts.map +1 -0
- package/server/auth/token-exchange.js +27 -0
- package/server/auth/token-exchange.js.map +1 -0
- package/server/auth/token.d.ts +18 -0
- package/server/auth/token.d.ts.map +1 -0
- package/server/auth/token.js +115 -0
- package/server/auth/token.js.map +1 -0
- package/server/bootstrap/create-app-server.d.ts +49 -0
- package/server/bootstrap/create-app-server.d.ts.map +1 -0
- package/server/bootstrap/create-app-server.js +154 -0
- package/server/bootstrap/create-app-server.js.map +1 -0
- package/server/bootstrap/project-runtime.d.ts +29 -0
- package/server/bootstrap/project-runtime.d.ts.map +1 -0
- package/server/bootstrap/project-runtime.js +253 -0
- package/server/bootstrap/project-runtime.js.map +1 -0
- package/server/bootstrap/pty-env.d.ts +3 -0
- package/server/bootstrap/pty-env.d.ts.map +1 -0
- package/server/bootstrap/pty-env.js +15 -0
- package/server/bootstrap/pty-env.js.map +1 -0
- package/server/bootstrap/reconcile-state.d.ts +16 -0
- package/server/bootstrap/reconcile-state.d.ts.map +1 -0
- package/server/bootstrap/reconcile-state.js +73 -0
- package/server/bootstrap/reconcile-state.js.map +1 -0
- package/server/bootstrap/runtime-loops.d.ts +51 -0
- package/server/bootstrap/runtime-loops.d.ts.map +1 -0
- package/server/bootstrap/runtime-loops.js +189 -0
- package/server/bootstrap/runtime-loops.js.map +1 -0
- package/server/bootstrap/runtime-port.d.ts +4 -0
- package/server/bootstrap/runtime-port.d.ts.map +1 -0
- package/server/bootstrap/runtime-port.js +52 -0
- package/server/bootstrap/runtime-port.js.map +1 -0
- package/server/bootstrap/safety-gate.d.ts +22 -0
- package/server/bootstrap/safety-gate.d.ts.map +1 -0
- package/server/bootstrap/safety-gate.js +43 -0
- package/server/bootstrap/safety-gate.js.map +1 -0
- package/server/bootstrap/shutdown-marker.d.ts +35 -0
- package/server/bootstrap/shutdown-marker.d.ts.map +1 -0
- package/server/bootstrap/shutdown-marker.js +100 -0
- package/server/bootstrap/shutdown-marker.js.map +1 -0
- package/server/bootstrap/shutdown-runtime.d.ts +62 -0
- package/server/bootstrap/shutdown-runtime.d.ts.map +1 -0
- package/server/bootstrap/shutdown-runtime.js +66 -0
- package/server/bootstrap/shutdown-runtime.js.map +1 -0
- package/server/bootstrap/startup-banner.d.ts +34 -0
- package/server/bootstrap/startup-banner.d.ts.map +1 -0
- package/server/bootstrap/startup-banner.js +114 -0
- package/server/bootstrap/startup-banner.js.map +1 -0
- package/server/bootstrap/wire-runtime.d.ts +42 -0
- package/server/bootstrap/wire-runtime.d.ts.map +1 -0
- package/server/bootstrap/wire-runtime.js +205 -0
- package/server/bootstrap/wire-runtime.js.map +1 -0
- package/server/cli/help.d.ts +3 -0
- package/server/cli/help.d.ts.map +1 -0
- package/server/cli/help.js +39 -0
- package/server/cli/help.js.map +1 -0
- package/server/cli/hook-client.d.ts +12 -0
- package/server/cli/hook-client.d.ts.map +1 -0
- package/server/cli/hook-client.js +81 -0
- package/server/cli/hook-client.js.map +1 -0
- package/server/cli/hook.d.ts +3 -0
- package/server/cli/hook.d.ts.map +1 -0
- package/server/cli/hook.js +152 -0
- package/server/cli/hook.js.map +1 -0
- package/server/cli/main.d.ts +2 -0
- package/server/cli/main.d.ts.map +1 -0
- package/server/cli/main.js +133 -0
- package/server/cli/main.js.map +1 -0
- package/server/cli/notify.d.ts +19 -0
- package/server/cli/notify.d.ts.map +1 -0
- package/server/cli/notify.js +77 -0
- package/server/cli/notify.js.map +1 -0
- package/server/cli/open.d.ts +2 -0
- package/server/cli/open.d.ts.map +1 -0
- package/server/cli/open.js +47 -0
- package/server/cli/open.js.map +1 -0
- package/server/cli/probe-daemon-version.d.ts +46 -0
- package/server/cli/probe-daemon-version.d.ts.map +1 -0
- package/server/cli/probe-daemon-version.js +167 -0
- package/server/cli/probe-daemon-version.js.map +1 -0
- package/server/cli/pty-host.d.ts +36 -0
- package/server/cli/pty-host.d.ts.map +1 -0
- package/server/cli/pty-host.js +390 -0
- package/server/cli/pty-host.js.map +1 -0
- package/server/cli/qr.d.ts +2 -0
- package/server/cli/qr.d.ts.map +1 -0
- package/server/cli/qr.js +70 -0
- package/server/cli/qr.js.map +1 -0
- package/server/cli/restart-confirm.d.ts +33 -0
- package/server/cli/restart-confirm.d.ts.map +1 -0
- package/server/cli/restart-confirm.js +107 -0
- package/server/cli/restart-confirm.js.map +1 -0
- package/server/cli/restart.d.ts +17 -0
- package/server/cli/restart.d.ts.map +1 -0
- package/server/cli/restart.js +145 -0
- package/server/cli/restart.js.map +1 -0
- package/server/cli/service-darwin.d.ts +70 -0
- package/server/cli/service-darwin.d.ts.map +1 -0
- package/server/cli/service-darwin.js +562 -0
- package/server/cli/service-darwin.js.map +1 -0
- package/server/cli/service-linux.d.ts +81 -0
- package/server/cli/service-linux.d.ts.map +1 -0
- package/server/cli/service-linux.js +412 -0
- package/server/cli/service-linux.js.map +1 -0
- package/server/cli/service.d.ts +33 -0
- package/server/cli/service.d.ts.map +1 -0
- package/server/cli/service.js +133 -0
- package/server/cli/service.js.map +1 -0
- package/server/cli/shim-installer.d.ts +35 -0
- package/server/cli/shim-installer.d.ts.map +1 -0
- package/server/cli/shim-installer.js +662 -0
- package/server/cli/shim-installer.js.map +1 -0
- package/server/cli/shim-open.d.ts +2 -0
- package/server/cli/shim-open.d.ts.map +1 -0
- package/server/cli/shim-open.js +128 -0
- package/server/cli/shim-open.js.map +1 -0
- package/server/cli/shutdown-deps.d.ts +41 -0
- package/server/cli/shutdown-deps.d.ts.map +1 -0
- package/server/cli/shutdown-deps.js +158 -0
- package/server/cli/shutdown-deps.js.map +1 -0
- package/server/cli/stop.d.ts +11 -0
- package/server/cli/stop.d.ts.map +1 -0
- package/server/cli/stop.js +123 -0
- package/server/cli/stop.js.map +1 -0
- package/server/cli/unknown-command.d.ts +8 -0
- package/server/cli/unknown-command.d.ts.map +1 -0
- package/server/cli/unknown-command.js +14 -0
- package/server/cli/unknown-command.js.map +1 -0
- package/server/debug/agent-status-recorder.d.ts +57 -0
- package/server/debug/agent-status-recorder.d.ts.map +1 -0
- package/server/debug/agent-status-recorder.js +209 -0
- package/server/debug/agent-status-recorder.js.map +1 -0
- package/server/debug/terminal-trace-recorder.d.ts +41 -0
- package/server/debug/terminal-trace-recorder.d.ts.map +1 -0
- package/server/debug/terminal-trace-recorder.js +112 -0
- package/server/debug/terminal-trace-recorder.js.map +1 -0
- package/server/fonts/catalog.d.ts +35 -0
- package/server/fonts/catalog.d.ts.map +1 -0
- package/server/fonts/catalog.js +76 -0
- package/server/fonts/catalog.js.map +1 -0
- package/server/fonts/installer.d.ts +45 -0
- package/server/fonts/installer.d.ts.map +1 -0
- package/server/fonts/installer.js +201 -0
- package/server/fonts/installer.js.map +1 -0
- package/server/fonts/routes.d.ts +4 -0
- package/server/fonts/routes.d.ts.map +1 -0
- package/server/fonts/routes.js +91 -0
- package/server/fonts/routes.js.map +1 -0
- package/server/fs/drops.d.ts +50 -0
- package/server/fs/drops.d.ts.map +1 -0
- package/server/fs/drops.js +137 -0
- package/server/fs/drops.js.map +1 -0
- package/server/fs/file-uploads.d.ts +57 -0
- package/server/fs/file-uploads.d.ts.map +1 -0
- package/server/fs/file-uploads.js +214 -0
- package/server/fs/file-uploads.js.map +1 -0
- package/server/fs/file-watcher.d.ts +22 -0
- package/server/fs/file-watcher.d.ts.map +1 -0
- package/server/fs/file-watcher.js +105 -0
- package/server/fs/file-watcher.js.map +1 -0
- package/server/fs/git-watcher.d.ts +76 -0
- package/server/fs/git-watcher.d.ts.map +1 -0
- package/server/fs/git-watcher.js +356 -0
- package/server/fs/git-watcher.js.map +1 -0
- package/server/fs/media.d.ts +35 -0
- package/server/fs/media.d.ts.map +1 -0
- package/server/fs/media.js +271 -0
- package/server/fs/media.js.map +1 -0
- package/server/fs/service.d.ts +97 -0
- package/server/fs/service.d.ts.map +1 -0
- package/server/fs/service.js +306 -0
- package/server/fs/service.js.map +1 -0
- package/server/fs/upload-staging.d.ts +76 -0
- package/server/fs/upload-staging.d.ts.map +1 -0
- package/server/fs/upload-staging.js +283 -0
- package/server/fs/upload-staging.js.map +1 -0
- package/server/fs/watcher-lifecycle.d.ts +24 -0
- package/server/fs/watcher-lifecycle.d.ts.map +1 -0
- package/server/fs/watcher-lifecycle.js +105 -0
- package/server/fs/watcher-lifecycle.js.map +1 -0
- package/server/index.d.ts +17 -0
- package/server/index.d.ts.map +1 -0
- package/server/index.js +510 -0
- package/server/index.js.map +1 -0
- package/server/ipc/socket-server.d.ts +28 -0
- package/server/ipc/socket-server.d.ts.map +1 -0
- package/server/ipc/socket-server.js +206 -0
- package/server/ipc/socket-server.js.map +1 -0
- package/server/lib/git-exec.d.ts +38 -0
- package/server/lib/git-exec.d.ts.map +1 -0
- package/server/lib/git-exec.js +96 -0
- package/server/lib/git-exec.js.map +1 -0
- package/server/lib/open-in-ide.d.ts +22 -0
- package/server/lib/open-in-ide.d.ts.map +1 -0
- package/server/lib/open-in-ide.js +90 -0
- package/server/lib/open-in-ide.js.map +1 -0
- package/server/lib/open-in-os.d.ts +20 -0
- package/server/lib/open-in-os.d.ts.map +1 -0
- package/server/lib/open-in-os.js +51 -0
- package/server/lib/open-in-os.js.map +1 -0
- package/server/lib/path.d.ts +2 -0
- package/server/lib/path.d.ts.map +1 -0
- package/server/lib/path.js +10 -0
- package/server/lib/path.js.map +1 -0
- package/server/lib/promise-mutex.d.ts +7 -0
- package/server/lib/promise-mutex.d.ts.map +1 -0
- package/server/lib/promise-mutex.js +26 -0
- package/server/lib/promise-mutex.js.map +1 -0
- package/server/lib/sd-notify.d.ts +8 -0
- package/server/lib/sd-notify.d.ts.map +1 -0
- package/server/lib/sd-notify.js +27 -0
- package/server/lib/sd-notify.js.map +1 -0
- package/server/net/local-machine.d.ts +7 -0
- package/server/net/local-machine.d.ts.map +1 -0
- package/server/net/local-machine.js +79 -0
- package/server/net/local-machine.js.map +1 -0
- package/server/net/reachable-host.d.ts +12 -0
- package/server/net/reachable-host.d.ts.map +1 -0
- package/server/net/reachable-host.js +25 -0
- package/server/net/reachable-host.js.map +1 -0
- package/server/network/endpoints.d.ts +14 -0
- package/server/network/endpoints.d.ts.map +1 -0
- package/server/network/endpoints.js +104 -0
- package/server/network/endpoints.js.map +1 -0
- package/server/network/qr.d.ts +16 -0
- package/server/network/qr.d.ts.map +1 -0
- package/server/network/qr.js +89 -0
- package/server/network/qr.js.map +1 -0
- package/server/port-forwarder/forwarder.d.ts +47 -0
- package/server/port-forwarder/forwarder.d.ts.map +1 -0
- package/server/port-forwarder/forwarder.js +159 -0
- package/server/port-forwarder/forwarder.js.map +1 -0
- package/server/port-scanner/scanner.d.ts +24 -0
- package/server/port-scanner/scanner.d.ts.map +1 -0
- package/server/port-scanner/scanner.js +193 -0
- package/server/port-scanner/scanner.js.map +1 -0
- package/server/pty/connection-lifecycle.d.ts +50 -0
- package/server/pty/connection-lifecycle.d.ts.map +1 -0
- package/server/pty/connection-lifecycle.js +113 -0
- package/server/pty/connection-lifecycle.js.map +1 -0
- package/server/pty/daemon-connect.d.ts +52 -0
- package/server/pty/daemon-connect.d.ts.map +1 -0
- package/server/pty/daemon-connect.js +62 -0
- package/server/pty/daemon-connect.js.map +1 -0
- package/server/pty/handshake-timeout-race.d.ts +22 -0
- package/server/pty/handshake-timeout-race.d.ts.map +1 -0
- package/server/pty/handshake-timeout-race.js +23 -0
- package/server/pty/handshake-timeout-race.js.map +1 -0
- package/server/pty/headless-replay-snapshot.d.ts +28 -0
- package/server/pty/headless-replay-snapshot.d.ts.map +1 -0
- package/server/pty/headless-replay-snapshot.js +285 -0
- package/server/pty/headless-replay-snapshot.js.map +1 -0
- package/server/pty/headless-terminal-state-cache.d.ts +31 -0
- package/server/pty/headless-terminal-state-cache.d.ts.map +1 -0
- package/server/pty/headless-terminal-state-cache.js +119 -0
- package/server/pty/headless-terminal-state-cache.js.map +1 -0
- package/server/pty/hello-ack-validator.d.ts +15 -0
- package/server/pty/hello-ack-validator.d.ts.map +1 -0
- package/server/pty/hello-ack-validator.js +30 -0
- package/server/pty/hello-ack-validator.js.map +1 -0
- package/server/pty/host-daemon/bootstrap.d.ts +100 -0
- package/server/pty/host-daemon/bootstrap.d.ts.map +1 -0
- package/server/pty/host-daemon/bootstrap.js +611 -0
- package/server/pty/host-daemon/bootstrap.js.map +1 -0
- package/server/pty/host-daemon/daemon.d.ts +111 -0
- package/server/pty/host-daemon/daemon.d.ts.map +1 -0
- package/server/pty/host-daemon/daemon.js +648 -0
- package/server/pty/host-daemon/daemon.js.map +1 -0
- package/server/pty/host-daemon/entry.d.ts +3 -0
- package/server/pty/host-daemon/entry.d.ts.map +1 -0
- package/server/pty/host-daemon/entry.js +72 -0
- package/server/pty/host-daemon/entry.js.map +1 -0
- package/server/pty/host-daemon/lockfile.d.ts +12 -0
- package/server/pty/host-daemon/lockfile.d.ts.map +1 -0
- package/server/pty/host-daemon/lockfile.js +120 -0
- package/server/pty/host-daemon/lockfile.js.map +1 -0
- package/server/pty/host-daemon/mode-marker.d.ts +77 -0
- package/server/pty/host-daemon/mode-marker.d.ts.map +1 -0
- package/server/pty/host-daemon/mode-marker.js +228 -0
- package/server/pty/host-daemon/mode-marker.js.map +1 -0
- package/server/pty/host-daemon/orphan-cleanup.d.ts +42 -0
- package/server/pty/host-daemon/orphan-cleanup.d.ts.map +1 -0
- package/server/pty/host-daemon/orphan-cleanup.js +84 -0
- package/server/pty/host-daemon/orphan-cleanup.js.map +1 -0
- package/server/pty/host-daemon/paths.d.ts +10 -0
- package/server/pty/host-daemon/paths.d.ts.map +1 -0
- package/server/pty/host-daemon/paths.js +56 -0
- package/server/pty/host-daemon/paths.js.map +1 -0
- package/server/pty/host-daemon/server-connection.d.ts +44 -0
- package/server/pty/host-daemon/server-connection.d.ts.map +1 -0
- package/server/pty/host-daemon/server-connection.js +100 -0
- package/server/pty/host-daemon/server-connection.js.map +1 -0
- package/server/pty/host-daemon/service-detection.d.ts +24 -0
- package/server/pty/host-daemon/service-detection.d.ts.map +1 -0
- package/server/pty/host-daemon/service-detection.js +16 -0
- package/server/pty/host-daemon/service-detection.js.map +1 -0
- package/server/pty/host-daemon/socket-ready.d.ts +22 -0
- package/server/pty/host-daemon/socket-ready.d.ts.map +1 -0
- package/server/pty/host-daemon/socket-ready.js +33 -0
- package/server/pty/host-daemon/socket-ready.js.map +1 -0
- package/server/pty/host-daemon/spawn-daemon.d.ts +45 -0
- package/server/pty/host-daemon/spawn-daemon.d.ts.map +1 -0
- package/server/pty/host-daemon/spawn-daemon.js +155 -0
- package/server/pty/host-daemon/spawn-daemon.js.map +1 -0
- package/server/pty/host-daemon/terminate-daemon.d.ts +16 -0
- package/server/pty/host-daemon/terminate-daemon.d.ts.map +1 -0
- package/server/pty/host-daemon/terminate-daemon.js +128 -0
- package/server/pty/host-daemon/terminate-daemon.js.map +1 -0
- package/server/pty/host-protocol/frames.d.ts +110 -0
- package/server/pty/host-protocol/frames.d.ts.map +1 -0
- package/server/pty/host-protocol/frames.js +255 -0
- package/server/pty/host-protocol/frames.js.map +1 -0
- package/server/pty/host-protocol/messages.d.ts +140 -0
- package/server/pty/host-protocol/messages.d.ts.map +1 -0
- package/server/pty/host-protocol/messages.js +117 -0
- package/server/pty/host-protocol/messages.js.map +1 -0
- package/server/pty/host.d.ts +273 -0
- package/server/pty/host.d.ts.map +1 -0
- package/server/pty/host.js +184 -0
- package/server/pty/host.js.map +1 -0
- package/server/pty/in-process-host.d.ts +224 -0
- package/server/pty/in-process-host.d.ts.map +1 -0
- package/server/pty/in-process-host.js +1183 -0
- package/server/pty/in-process-host.js.map +1 -0
- package/server/pty/osc7-lifecycle.d.ts +8 -0
- package/server/pty/osc7-lifecycle.d.ts.map +1 -0
- package/server/pty/osc7-lifecycle.js +22 -0
- package/server/pty/osc7-lifecycle.js.map +1 -0
- package/server/pty/osc7-parser.d.ts +23 -0
- package/server/pty/osc7-parser.d.ts.map +1 -0
- package/server/pty/osc7-parser.js +91 -0
- package/server/pty/osc7-parser.js.map +1 -0
- package/server/pty/remote-host.d.ts +188 -0
- package/server/pty/remote-host.d.ts.map +1 -0
- package/server/pty/remote-host.js +810 -0
- package/server/pty/remote-host.js.map +1 -0
- package/server/pty/request-correlator.d.ts +59 -0
- package/server/pty/request-correlator.d.ts.map +1 -0
- package/server/pty/request-correlator.js +75 -0
- package/server/pty/request-correlator.js.map +1 -0
- package/server/pty/scrollback-log.d.ts +130 -0
- package/server/pty/scrollback-log.d.ts.map +1 -0
- package/server/pty/scrollback-log.js +344 -0
- package/server/pty/scrollback-log.js.map +1 -0
- package/server/pty/scrollback-sanitize.d.ts +2 -0
- package/server/pty/scrollback-sanitize.d.ts.map +1 -0
- package/server/pty/scrollback-sanitize.js +54 -0
- package/server/pty/scrollback-sanitize.js.map +1 -0
- package/server/pty/session-mirror.d.ts +67 -0
- package/server/pty/session-mirror.d.ts.map +1 -0
- package/server/pty/session-mirror.js +112 -0
- package/server/pty/session-mirror.js.map +1 -0
- package/server/pty/session-policy.d.ts +85 -0
- package/server/pty/session-policy.d.ts.map +1 -0
- package/server/pty/session-policy.js +186 -0
- package/server/pty/session-policy.js.map +1 -0
- package/server/pty/version-mismatch-recovery.d.ts +71 -0
- package/server/pty/version-mismatch-recovery.d.ts.map +1 -0
- package/server/pty/version-mismatch-recovery.js +63 -0
- package/server/pty/version-mismatch-recovery.js.map +1 -0
- package/server/routes/debug-agent-status.d.ts +5 -0
- package/server/routes/debug-agent-status.d.ts.map +1 -0
- package/server/routes/debug-agent-status.js +22 -0
- package/server/routes/debug-agent-status.js.map +1 -0
- package/server/routes/debug-diagnostics.d.ts +8 -0
- package/server/routes/debug-diagnostics.d.ts.map +1 -0
- package/server/routes/debug-diagnostics.js +34 -0
- package/server/routes/debug-diagnostics.js.map +1 -0
- package/server/routes/debug-terminal-trace.d.ts +16 -0
- package/server/routes/debug-terminal-trace.d.ts.map +1 -0
- package/server/routes/debug-terminal-trace.js +545 -0
- package/server/routes/debug-terminal-trace.js.map +1 -0
- package/server/routes/drops.d.ts +13 -0
- package/server/routes/drops.d.ts.map +1 -0
- package/server/routes/drops.js +126 -0
- package/server/routes/drops.js.map +1 -0
- package/server/routes/file-uploads.d.ts +9 -0
- package/server/routes/file-uploads.d.ts.map +1 -0
- package/server/routes/file-uploads.js +119 -0
- package/server/routes/file-uploads.js.map +1 -0
- package/server/routes/files.d.ts +9 -0
- package/server/routes/files.d.ts.map +1 -0
- package/server/routes/files.js +441 -0
- package/server/routes/files.js.map +1 -0
- package/server/routes/filesystem.d.ts +6 -0
- package/server/routes/filesystem.d.ts.map +1 -0
- package/server/routes/filesystem.js +76 -0
- package/server/routes/filesystem.js.map +1 -0
- package/server/routes/git.d.ts +38 -0
- package/server/routes/git.d.ts.map +1 -0
- package/server/routes/git.js +585 -0
- package/server/routes/git.js.map +1 -0
- package/server/routes/healthz.d.ts +8 -0
- package/server/routes/healthz.d.ts.map +1 -0
- package/server/routes/healthz.js +24 -0
- package/server/routes/healthz.js.map +1 -0
- package/server/routes/hook.d.ts +14 -0
- package/server/routes/hook.d.ts.map +1 -0
- package/server/routes/hook.js +86 -0
- package/server/routes/hook.js.map +1 -0
- package/server/routes/ide-commands.d.ts +9 -0
- package/server/routes/ide-commands.d.ts.map +1 -0
- package/server/routes/ide-commands.js +23 -0
- package/server/routes/ide-commands.js.map +1 -0
- package/server/routes/lib/resolve-worktree.d.ts +30 -0
- package/server/routes/lib/resolve-worktree.d.ts.map +1 -0
- package/server/routes/lib/resolve-worktree.js +35 -0
- package/server/routes/lib/resolve-worktree.js.map +1 -0
- package/server/routes/open.d.ts +4 -0
- package/server/routes/open.d.ts.map +1 -0
- package/server/routes/open.js +21 -0
- package/server/routes/open.js.map +1 -0
- package/server/routes/pane-commands.d.ts +9 -0
- package/server/routes/pane-commands.d.ts.map +1 -0
- package/server/routes/pane-commands.js +23 -0
- package/server/routes/pane-commands.js.map +1 -0
- package/server/routes/projects.d.ts +15 -0
- package/server/routes/projects.d.ts.map +1 -0
- package/server/routes/projects.js +363 -0
- package/server/routes/projects.js.map +1 -0
- package/server/routes/server-notices.d.ts +4 -0
- package/server/routes/server-notices.d.ts.map +1 -0
- package/server/routes/server-notices.js +29 -0
- package/server/routes/server-notices.js.map +1 -0
- package/server/routes/service-config.d.ts +11 -0
- package/server/routes/service-config.d.ts.map +1 -0
- package/server/routes/service-config.js +64 -0
- package/server/routes/service-config.js.map +1 -0
- package/server/routes/sessions.d.ts +7 -0
- package/server/routes/sessions.d.ts.map +1 -0
- package/server/routes/sessions.js +218 -0
- package/server/routes/sessions.js.map +1 -0
- package/server/service/caffeinate.d.ts +33 -0
- package/server/service/caffeinate.d.ts.map +1 -0
- package/server/service/caffeinate.js +72 -0
- package/server/service/caffeinate.js.map +1 -0
- package/server/state/app-state.d.ts +167 -0
- package/server/state/app-state.d.ts.map +1 -0
- package/server/state/app-state.js +335 -0
- package/server/state/app-state.js.map +1 -0
- package/server/state/project-manager.d.ts +30 -0
- package/server/state/project-manager.d.ts.map +1 -0
- package/server/state/project-manager.js +128 -0
- package/server/state/project-manager.js.map +1 -0
- package/server/state/server-notices.d.ts +12 -0
- package/server/state/server-notices.d.ts.map +1 -0
- package/server/state/server-notices.js +32 -0
- package/server/state/server-notices.js.map +1 -0
- package/server/state/worktree-cache.d.ts +25 -0
- package/server/state/worktree-cache.d.ts.map +1 -0
- package/server/state/worktree-cache.js +53 -0
- package/server/state/worktree-cache.js.map +1 -0
- package/server/ws/events.d.ts +45 -0
- package/server/ws/events.d.ts.map +1 -0
- package/server/ws/events.js +134 -0
- package/server/ws/events.js.map +1 -0
- package/server/ws/keepalive.d.ts +27 -0
- package/server/ws/keepalive.d.ts.map +1 -0
- package/server/ws/keepalive.js +63 -0
- package/server/ws/keepalive.js.map +1 -0
- package/server/ws/terminal-attach.d.ts +13 -0
- package/server/ws/terminal-attach.d.ts.map +1 -0
- package/server/ws/terminal-attach.js +233 -0
- package/server/ws/terminal-attach.js.map +1 -0
- package/server/ws/terminal-flow.d.ts +17 -0
- package/server/ws/terminal-flow.d.ts.map +1 -0
- package/server/ws/terminal-flow.js +64 -0
- package/server/ws/terminal-flow.js.map +1 -0
- package/server/ws/terminal.d.ts +69 -0
- package/server/ws/terminal.d.ts.map +1 -0
- package/server/ws/terminal.js +311 -0
- package/server/ws/terminal.js.map +1 -0
- package/web/assets/EditorPane-CzzT3iYY.js +123 -0
- package/web/assets/SymbolsNerdFontMono-Regular-CwEZqMeU.woff2 +0 -0
- package/web/assets/TerminalPane-CjbYzePr.js +68 -0
- package/web/assets/TerminalPane-DkTCHfhq.css +1 -0
- package/web/assets/file-icons-JBi09j0r.js +6 -0
- package/web/assets/index-CTTkRpnn.css +2 -0
- package/web/assets/index-CmhewzMp.js +34 -0
- package/web/assets/session-resume-7f-tB-ZU.js +2 -0
- package/web/assets/terminal-trace-PuuFRybC.js +2803 -0
- package/web/assets/useVirtualKeyboard-DgJb9u9d.js +1 -0
- package/web/index.html +52 -0
|
@@ -0,0 +1,810 @@
|
|
|
1
|
+
import { Buffer } from "node:buffer";
|
|
2
|
+
import { ConnectionLifecycle } from "./connection-lifecycle.js";
|
|
3
|
+
import { raceHandshakeWithTimeout } from "./handshake-timeout-race.js";
|
|
4
|
+
import { HeadlessTerminalStateCache } from "./headless-terminal-state-cache.js";
|
|
5
|
+
import { validateHelloAck } from "./hello-ack-validator.js";
|
|
6
|
+
import { decodeGenerationStreamPayload, decodeStreamPayload, encodeFrame, encodeGenerationStreamPayload, FrameError, FrameParser, FrameType, } from "./host-protocol/frames.js";
|
|
7
|
+
import { decodeJsonPayload, encodeJsonPayload, PROTOCOL_VERSION, } from "./host-protocol/messages.js";
|
|
8
|
+
import { RequestCorrelator } from "./request-correlator.js";
|
|
9
|
+
import { stripQueryEscapes } from "./scrollback-sanitize.js";
|
|
10
|
+
import { SessionMirror } from "./session-mirror.js";
|
|
11
|
+
const DEFAULT_DAEMON_LEGACY_REPLAY_MAX_BYTES = 256 * 1024;
|
|
12
|
+
const DEFAULT_HEADLESS_REPLAY_SCROLLBACK_LINES = 10_000;
|
|
13
|
+
const DEFAULT_HEADLESS_REPLAY_MAX_BYTES = DEFAULT_DAEMON_LEGACY_REPLAY_MAX_BYTES;
|
|
14
|
+
const DEFAULT_HEADLESS_STATE_MAX_SESSIONS = 8;
|
|
15
|
+
const DEFAULT_HEADLESS_STATE_TTL_MS = 10 * 60_000;
|
|
16
|
+
function readPositiveIntegerEnv(name) {
|
|
17
|
+
const raw = process.env[name];
|
|
18
|
+
if (!raw)
|
|
19
|
+
return null;
|
|
20
|
+
const value = Number(raw);
|
|
21
|
+
if (!Number.isSafeInteger(value) || value <= 0)
|
|
22
|
+
return null;
|
|
23
|
+
return value;
|
|
24
|
+
}
|
|
25
|
+
function readBooleanEnv(name) {
|
|
26
|
+
const raw = process.env[name];
|
|
27
|
+
if (raw === undefined)
|
|
28
|
+
return null;
|
|
29
|
+
const value = raw.trim().toLowerCase();
|
|
30
|
+
if (value === "1" || value === "true" || value === "yes" || value === "on")
|
|
31
|
+
return true;
|
|
32
|
+
if (value === "0" || value === "false" || value === "no" || value === "off")
|
|
33
|
+
return false;
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
function readHeadlessReplayEnabled() {
|
|
37
|
+
return (readBooleanEnv("PARASOR_HEADLESS_REPLAY") ??
|
|
38
|
+
readBooleanEnv("PARASOR_EXPERIMENT_HEADLESS_REPLAY") ??
|
|
39
|
+
true);
|
|
40
|
+
}
|
|
41
|
+
function utf8Tail(text, maxBytes) {
|
|
42
|
+
const buf = Buffer.from(text, "utf8");
|
|
43
|
+
if (buf.length <= maxBytes)
|
|
44
|
+
return text;
|
|
45
|
+
const slice = buf.subarray(buf.length - maxBytes);
|
|
46
|
+
let start = 0;
|
|
47
|
+
while (start < slice.length && start < 3 && (slice[start] & 0xc0) === 0x80) {
|
|
48
|
+
start++;
|
|
49
|
+
}
|
|
50
|
+
return slice.subarray(start).toString("utf8");
|
|
51
|
+
}
|
|
52
|
+
export class RemotePtyHostError extends Error {
|
|
53
|
+
code;
|
|
54
|
+
constructor(code, message) {
|
|
55
|
+
super(message);
|
|
56
|
+
this.code = code;
|
|
57
|
+
this.name = "RemotePtyHostError";
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
const ACK_FRAME_TYPES = new Set([
|
|
61
|
+
FrameType.CREATE_ACK,
|
|
62
|
+
FrameType.RESTART_ACK,
|
|
63
|
+
FrameType.DISPOSE_ACK,
|
|
64
|
+
FrameType.DISPOSE_ALL_ACK,
|
|
65
|
+
FrameType.SHUTDOWN_ALL_ACK,
|
|
66
|
+
FrameType.INIT_CLIENT_ACK,
|
|
67
|
+
FrameType.PERSIST_PROJECT_DOMAINS_ACK,
|
|
68
|
+
]);
|
|
69
|
+
export class RemotePtyHost {
|
|
70
|
+
socket;
|
|
71
|
+
parser = new FrameParser();
|
|
72
|
+
requestTimeoutMs;
|
|
73
|
+
scrollbackLog;
|
|
74
|
+
daemonLegacyReplayMaxBytes;
|
|
75
|
+
headlessReplayEnabled;
|
|
76
|
+
headlessReplayScrollbackLines;
|
|
77
|
+
headlessReplayMaxBytes;
|
|
78
|
+
headlessStateCache;
|
|
79
|
+
/**
|
|
80
|
+
* owns the `connecting ->
|
|
81
|
+
* snapshot-pending -> ready / dropped` transitions, the connectionId +
|
|
82
|
+
* generation stamped at HELLO_ACK time, and the deferred handshake
|
|
83
|
+
* Promise. Socket-agnostic by construction; this host drives transitions
|
|
84
|
+
* and reads the stamped headers when emitting frames.
|
|
85
|
+
* See {@link ConnectionLifecycle}.
|
|
86
|
+
*/
|
|
87
|
+
lifecycle = new ConnectionLifecycle();
|
|
88
|
+
/**
|
|
89
|
+
* Request/response correlation: monotonic requestId, the pending-id Map,
|
|
90
|
+
* per-request timeout scheduling, and ACK/NACK/drop dispatch. Socket-
|
|
91
|
+
* agnostic by construction; state-machine guards and wire encoding live
|
|
92
|
+
* on this host. See {@link RequestCorrelator}.
|
|
93
|
+
*/
|
|
94
|
+
correlator;
|
|
95
|
+
/**
|
|
96
|
+
* Local view-state of daemon-owned sessions plus the PTY generation gate per-session
|
|
97
|
+
* generation latch. Pure, socket-free reconciliation lives in
|
|
98
|
+
* {@link SessionMirror}; this shell only feeds it decoded frames.
|
|
99
|
+
*/
|
|
100
|
+
mirror = new SessionMirror();
|
|
101
|
+
attached = new Map();
|
|
102
|
+
/** Attach fencing -- server-local monotonic counter for attach-token mintage. */
|
|
103
|
+
nextAttachToken = 1;
|
|
104
|
+
/**
|
|
105
|
+
* Session IDs we have ever appended scrollback for. Tracked separately
|
|
106
|
+
* from `mirror` so disposeAll() can purge files even if the daemon's
|
|
107
|
+
* SESSION_LIST snapshot lagged behind a DATA broadcast at shutdown
|
|
108
|
+
* time. Cleared on `dispose(id)` / `disposeAll()`.
|
|
109
|
+
*/
|
|
110
|
+
scrollbackOwnedIds = new Set();
|
|
111
|
+
ptyEnv = {};
|
|
112
|
+
dataListeners = [];
|
|
113
|
+
inputListeners = [];
|
|
114
|
+
onSessionExit = null;
|
|
115
|
+
/**
|
|
116
|
+
* Build + handshake in one step. Use this from production code; tests
|
|
117
|
+
* may want to drive HELLO_ACK manually and can poke `_internal` instead.
|
|
118
|
+
*/
|
|
119
|
+
static async connect(deps) {
|
|
120
|
+
const host = new RemotePtyHost(deps);
|
|
121
|
+
/*
|
|
122
|
+
* a daemon that accept()s the socket but
|
|
123
|
+
* never sends HELLO_ACK / SESSION_LIST would hang server boot
|
|
124
|
+
* indefinitely. Race the handshake against a deadline; on timeout
|
|
125
|
+
* destroy the socket (which fires the `error` listener attached in
|
|
126
|
+
* the constructor -> handleDrop -> rejectHandshake), then surface a
|
|
127
|
+
* typed `handshake-timeout` error to the caller.
|
|
128
|
+
*/
|
|
129
|
+
await raceHandshakeWithTimeout({
|
|
130
|
+
awaiter: host.lifecycle.awaitReady,
|
|
131
|
+
timeoutMs: deps.handshakeTimeoutMs ?? 10_000,
|
|
132
|
+
onTimeout: () => deps.socket.destroy(),
|
|
133
|
+
buildTimeoutError: (timeoutMs) => new RemotePtyHostError("handshake-timeout", `parasor-pty-host handshake did not complete within ${timeoutMs}ms`),
|
|
134
|
+
});
|
|
135
|
+
return host;
|
|
136
|
+
}
|
|
137
|
+
constructor(deps) {
|
|
138
|
+
this.socket = deps.socket;
|
|
139
|
+
this.requestTimeoutMs = deps.requestTimeoutMs ?? 30_000;
|
|
140
|
+
this.scrollbackLog = deps.scrollbackLog ?? null;
|
|
141
|
+
const configuredDaemonLegacyReplayMaxBytes = deps.daemonLegacyReplayMaxBytes;
|
|
142
|
+
this.daemonLegacyReplayMaxBytes =
|
|
143
|
+
(typeof configuredDaemonLegacyReplayMaxBytes === "number" &&
|
|
144
|
+
Number.isSafeInteger(configuredDaemonLegacyReplayMaxBytes) &&
|
|
145
|
+
configuredDaemonLegacyReplayMaxBytes > 0
|
|
146
|
+
? configuredDaemonLegacyReplayMaxBytes
|
|
147
|
+
: null) ??
|
|
148
|
+
readPositiveIntegerEnv("PARASOR_DAEMON_LEGACY_REPLAY_MAX_BYTES") ??
|
|
149
|
+
DEFAULT_DAEMON_LEGACY_REPLAY_MAX_BYTES;
|
|
150
|
+
this.headlessReplayEnabled = readHeadlessReplayEnabled();
|
|
151
|
+
this.headlessReplayScrollbackLines =
|
|
152
|
+
readPositiveIntegerEnv("PARASOR_HEADLESS_REPLAY_SCROLLBACK_LINES") ??
|
|
153
|
+
DEFAULT_HEADLESS_REPLAY_SCROLLBACK_LINES;
|
|
154
|
+
this.headlessReplayMaxBytes =
|
|
155
|
+
readPositiveIntegerEnv("PARASOR_HEADLESS_REPLAY_MAX_BYTES") ??
|
|
156
|
+
DEFAULT_HEADLESS_REPLAY_MAX_BYTES;
|
|
157
|
+
this.headlessStateCache = this.headlessReplayEnabled
|
|
158
|
+
? new HeadlessTerminalStateCache({
|
|
159
|
+
cols: 80,
|
|
160
|
+
rows: 24,
|
|
161
|
+
scrollbackLines: this.headlessReplayScrollbackLines,
|
|
162
|
+
maxBytes: this.headlessReplayMaxBytes,
|
|
163
|
+
maxSessions: readPositiveIntegerEnv("PARASOR_HEADLESS_STATE_MAX_SESSIONS") ??
|
|
164
|
+
DEFAULT_HEADLESS_STATE_MAX_SESSIONS,
|
|
165
|
+
ttlMs: readPositiveIntegerEnv("PARASOR_HEADLESS_STATE_TTL_MS") ??
|
|
166
|
+
DEFAULT_HEADLESS_STATE_TTL_MS,
|
|
167
|
+
})
|
|
168
|
+
: null;
|
|
169
|
+
this.correlator = new RequestCorrelator({
|
|
170
|
+
timeoutMs: this.requestTimeoutMs,
|
|
171
|
+
buildTimeoutError: (requestId, timeoutMs) => new RemotePtyHostError("ipc-timeout", `request ${requestId} timed out after ${timeoutMs}ms`),
|
|
172
|
+
send: (type, requestId, payload) => this.send(type, requestId, payload),
|
|
173
|
+
});
|
|
174
|
+
this.socket.on("data", (chunk) => this.handleChunk(chunk));
|
|
175
|
+
this.socket.on("close", () => this.handleDrop("connection closed"));
|
|
176
|
+
this.socket.on("error", () => this.handleDrop("socket error"));
|
|
177
|
+
this.sendHello(deps.serverPid ?? process.pid);
|
|
178
|
+
}
|
|
179
|
+
// --- handshake ---
|
|
180
|
+
sendHello(serverPid) {
|
|
181
|
+
const payload = {
|
|
182
|
+
protocolVersion: PROTOCOL_VERSION,
|
|
183
|
+
serverPid,
|
|
184
|
+
};
|
|
185
|
+
this.socket.write(encodeFrame({
|
|
186
|
+
type: FrameType.HELLO,
|
|
187
|
+
connectionId: 0,
|
|
188
|
+
generation: 0n,
|
|
189
|
+
requestId: 1,
|
|
190
|
+
payload: encodeJsonPayload(payload),
|
|
191
|
+
}));
|
|
192
|
+
}
|
|
193
|
+
// --- frame ingestion ---
|
|
194
|
+
handleChunk(chunk) {
|
|
195
|
+
let frames;
|
|
196
|
+
try {
|
|
197
|
+
frames = this.parser.push(chunk);
|
|
198
|
+
}
|
|
199
|
+
catch (err) {
|
|
200
|
+
this.handleDrop(err instanceof FrameError ? `parser: ${err.message}` : "parser failure");
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
for (const frame of frames) {
|
|
204
|
+
if (this.lifecycle.isDropped)
|
|
205
|
+
return;
|
|
206
|
+
this.dispatch(frame);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
dispatch(frame) {
|
|
210
|
+
if (frame.type === FrameType.HELLO_ACK) {
|
|
211
|
+
this.onHelloAck(frame);
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
if (frame.type === FrameType.NACK) {
|
|
215
|
+
this.onNack(frame);
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
/*
|
|
219
|
+
* Permit SESSION_LIST during snapshot-pending -- that frame *completes*
|
|
220
|
+
* the handshake. Everything else (ACKs, SESSION_UPDATE/EXIT, DATA,
|
|
221
|
+
* SESSION_INPUT) requires `ready`: requests are gated behind
|
|
222
|
+
* handshakePromise so no ACK should arrive early, and broadcasts
|
|
223
|
+
* before the initial snapshot would land in an empty mirror.
|
|
224
|
+
*/
|
|
225
|
+
if (this.lifecycle.current === "snapshot-pending") {
|
|
226
|
+
if (frame.type === FrameType.SESSION_LIST) {
|
|
227
|
+
this.applySessionList(frame);
|
|
228
|
+
}
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
if (!this.lifecycle.isReady)
|
|
232
|
+
return;
|
|
233
|
+
if (ACK_FRAME_TYPES.has(frame.type)) {
|
|
234
|
+
// Stale acks (peer slow / fenced) return false from the correlator
|
|
235
|
+
// and are silently dropped -- same behavior as the prior inline lookup.
|
|
236
|
+
this.correlator.ack(frame);
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
switch (frame.type) {
|
|
240
|
+
case FrameType.SESSION_UPDATE:
|
|
241
|
+
this.applySessionUpdate(frame);
|
|
242
|
+
return;
|
|
243
|
+
case FrameType.SESSION_LIST:
|
|
244
|
+
this.applySessionList(frame);
|
|
245
|
+
return;
|
|
246
|
+
case FrameType.SESSION_EXIT:
|
|
247
|
+
this.applySessionExit(frame);
|
|
248
|
+
return;
|
|
249
|
+
case FrameType.DATA:
|
|
250
|
+
this.applyData(frame);
|
|
251
|
+
return;
|
|
252
|
+
case FrameType.SESSION_INPUT:
|
|
253
|
+
this.applySessionInput(frame);
|
|
254
|
+
return;
|
|
255
|
+
default:
|
|
256
|
+
// Unknown daemon->server frame type. Log-equivalent is no-op
|
|
257
|
+
// since we don't have a console channel here; the daemon would
|
|
258
|
+
// never send something we don't know during normal operation.
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
onHelloAck(frame) {
|
|
263
|
+
if (this.lifecycle.current !== "connecting")
|
|
264
|
+
return;
|
|
265
|
+
const result = validateHelloAck(frame.payload, PROTOCOL_VERSION);
|
|
266
|
+
if (!result.ok) {
|
|
267
|
+
this.lifecycle.drop(new RemotePtyHostError(result.code, result.message));
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
/*
|
|
271
|
+
* Design contract: handshake "ready" is HELLO_ACK + first SESSION_LIST applied.
|
|
272
|
+
* applyHelloAck transitions to snapshot-pending; the resolve fires from
|
|
273
|
+
* applySessionList -> lifecycle.markReady once the initial snapshot lands.
|
|
274
|
+
*/
|
|
275
|
+
this.lifecycle.applyHelloAck(result.connectionId, result.generation);
|
|
276
|
+
}
|
|
277
|
+
onNack(frame) {
|
|
278
|
+
let body;
|
|
279
|
+
try {
|
|
280
|
+
body = decodeJsonPayload(frame.payload);
|
|
281
|
+
}
|
|
282
|
+
catch {
|
|
283
|
+
this.handleDrop("undecodable NACK");
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
if (frame.requestId !== 0) {
|
|
287
|
+
// requestId=0 is handled below as a connection-level NACK; otherwise
|
|
288
|
+
// delegate to the correlator. Stale ids (peer slow / fenced) are a
|
|
289
|
+
// no-op -- same behavior as the prior inline lookup.
|
|
290
|
+
this.correlator.nack(frame.requestId, new RemotePtyHostError(body.code, body.message));
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
/*
|
|
294
|
+
* requestId=0 NACK is a connection-level fault: handshake-required,
|
|
295
|
+
* version-mismatch, evicted, daemon-shutting-down, or a malformed
|
|
296
|
+
* frame the daemon refused. Either way, the wire is dead -- drop and
|
|
297
|
+
* let the supervisor rebuild.
|
|
298
|
+
*/
|
|
299
|
+
if (this.lifecycle.isAwaitingHandshake) {
|
|
300
|
+
// Reject the awaiter with the daemon's specific NACK code BEFORE
|
|
301
|
+
// handleDrop runs -- Promise rejection is idempotent so the subsequent
|
|
302
|
+
// drop()'s "connection-dropped" reject is a no-op, preserving the
|
|
303
|
+
// daemon-specific error on the connect() awaiter side.
|
|
304
|
+
this.lifecycle.rejectHandshakeOnly(new RemotePtyHostError(body.code, body.message));
|
|
305
|
+
}
|
|
306
|
+
this.handleDrop(`daemon NACK: ${body.code} ${body.message}`);
|
|
307
|
+
}
|
|
308
|
+
// --- broadcast handlers (mirror reconciliation + listener fan-out) ---
|
|
309
|
+
applySessionUpdate(frame) {
|
|
310
|
+
const body = decodeJsonPayload(frame.payload);
|
|
311
|
+
this.mirror.upsert(body.session);
|
|
312
|
+
}
|
|
313
|
+
applySessionList(frame) {
|
|
314
|
+
const body = decodeJsonPayload(frame.payload);
|
|
315
|
+
this.mirror.applyList(body.sessions);
|
|
316
|
+
// Idempotent: markReady returns false on any state other than
|
|
317
|
+
// snapshot-pending, so later SESSION_LIST broadcasts just update the
|
|
318
|
+
// mirror without re-resolving the handshake.
|
|
319
|
+
this.lifecycle.markReady();
|
|
320
|
+
}
|
|
321
|
+
applySessionExit(frame) {
|
|
322
|
+
const body = decodeJsonPayload(frame.payload);
|
|
323
|
+
this.mirror.applyExit(body.sessionId, body.endReason);
|
|
324
|
+
this.onSessionExit?.(body.sessionId, body.sessionGeneration, body.endReason);
|
|
325
|
+
}
|
|
326
|
+
applyData(frame) {
|
|
327
|
+
/*
|
|
328
|
+
* PTY generation gate: DATA frames now carry a `[idLen][sessionId][gen:u32 BE][data]`
|
|
329
|
+
* payload (PROTOCOL_VERSION 2.0.0). The generation is recorded into
|
|
330
|
+
* `latestGeneration` so the binary `attachClient` adapter can read
|
|
331
|
+
* the live value when wrapping `sink.onChunk`, and is forwarded to
|
|
332
|
+
* `dataListeners` (the WS handler stamps it into OUTPUT frames so
|
|
333
|
+
* the client echoes it back on INPUT). String per-client listeners
|
|
334
|
+
* stay generation-agnostic -- those are the legacy non-binary attach
|
|
335
|
+
* path that does not feed back into the input gate.
|
|
336
|
+
*/
|
|
337
|
+
const decoded = decodeGenerationStreamPayload(frame.payload);
|
|
338
|
+
const data = decoded.data.toString("utf8");
|
|
339
|
+
/*
|
|
340
|
+
* PTY generation gate: advance the monotonic generation latch and learn whether this
|
|
341
|
+
* chunk is stale. The daemon's in-process host can flush an old-generation
|
|
342
|
+
* batch AFTER auto-resume bumps the generation (the setImmediate that
|
|
343
|
+
* captured generationAtSpawn fires post-respawn); those late chunks arrive
|
|
344
|
+
* tagged with the old gen. The mirror only moves the latch forward, so the
|
|
345
|
+
* next INPUT frame echoes the current gen rather than the stale one.
|
|
346
|
+
*/
|
|
347
|
+
const { stale: isStale } = this.mirror.recordDataGeneration(decoded.sessionId, decoded.generation);
|
|
348
|
+
/*
|
|
349
|
+
* PTY generation gate: stale-gen DATA must not reach attached clients
|
|
350
|
+
* or scrollback in daemon mode. The in-process side already gates
|
|
351
|
+
* its `attachedClients` broadcast on `generationStillCurrent` so
|
|
352
|
+
* old-PTY bytes never hit a client xterm; the daemon symmetric path
|
|
353
|
+
* is here. If we forwarded a stale chunk, the per-client wrapper in
|
|
354
|
+
* `attachClient()` would read the latch value and re-tag those bytes
|
|
355
|
+
* with the NEW gen -- defeating the whole PTY generation gate fence. Worse: scrollback
|
|
356
|
+
* would record stale bytes AFTER fresh ones, corrupting the on-disk
|
|
357
|
+
* replay order. Drop the fanout / scrollback when stale; keep
|
|
358
|
+
* `dataListeners` so debug / recording observers still see every byte
|
|
359
|
+
* tagged with its true emit-time generation (they self-decide on staleness).
|
|
360
|
+
*/
|
|
361
|
+
if (!isStale) {
|
|
362
|
+
void this.headlessStateCache
|
|
363
|
+
?.writeExisting(decoded.sessionId, data)
|
|
364
|
+
.catch((err) => {
|
|
365
|
+
console.warn(`[terminal] headless state update failed for session=${decoded.sessionId.slice(0, 8)}: ${err.message}`);
|
|
366
|
+
this.headlessStateCache?.delete(decoded.sessionId);
|
|
367
|
+
});
|
|
368
|
+
const clients = this.attached.get(decoded.sessionId);
|
|
369
|
+
if (clients) {
|
|
370
|
+
for (const client of clients.values()) {
|
|
371
|
+
try {
|
|
372
|
+
client.listener(data);
|
|
373
|
+
}
|
|
374
|
+
catch {
|
|
375
|
+
// listener faults are not fatal; isolate so one bad client
|
|
376
|
+
// doesn't break broadcast for siblings.
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
for (const listener of this.dataListeners) {
|
|
382
|
+
try {
|
|
383
|
+
listener(decoded.sessionId, data, decoded.generation);
|
|
384
|
+
}
|
|
385
|
+
catch {
|
|
386
|
+
/* same isolation */
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
/*
|
|
390
|
+
* Persist every fresh DATA chunk to the per-session disk log so a
|
|
391
|
+
* re-mounting xterm (tab switch, dev-server reload while the daemon
|
|
392
|
+
* survives) can rehydrate via `replay:"full"`. The daemon owns PTY
|
|
393
|
+
* lifetime in this mode; without our own append the server has no
|
|
394
|
+
* way to feed scrollback back to a fresh client. Buffered+throttled
|
|
395
|
+
* inside ScrollbackLog so the broadcast hot path stays cheap.
|
|
396
|
+
* Stale-gen chunks are intentionally skipped so a
|
|
397
|
+
* subsequent `replay:"full"` does not surface old-PTY bytes after
|
|
398
|
+
* the new spawn's prompt.
|
|
399
|
+
*/
|
|
400
|
+
if (this.scrollbackLog && !isStale) {
|
|
401
|
+
this.scrollbackLog.append(decoded.sessionId, data);
|
|
402
|
+
this.scrollbackOwnedIds.add(decoded.sessionId);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
applySessionInput(frame) {
|
|
406
|
+
const decoded = decodeStreamPayload(frame.payload);
|
|
407
|
+
const data = decoded.data.toString("utf8");
|
|
408
|
+
for (const listener of this.inputListeners) {
|
|
409
|
+
try {
|
|
410
|
+
listener(decoded.sessionId, data);
|
|
411
|
+
}
|
|
412
|
+
catch {
|
|
413
|
+
/* ignore */
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
// --- send helpers ---
|
|
418
|
+
send(type, requestId, payload) {
|
|
419
|
+
if (this.lifecycle.isDropped)
|
|
420
|
+
return;
|
|
421
|
+
try {
|
|
422
|
+
this.socket.write(encodeFrame({
|
|
423
|
+
type,
|
|
424
|
+
connectionId: this.lifecycle.connectionId,
|
|
425
|
+
generation: this.lifecycle.generation,
|
|
426
|
+
requestId,
|
|
427
|
+
payload,
|
|
428
|
+
}));
|
|
429
|
+
}
|
|
430
|
+
catch {
|
|
431
|
+
this.handleDrop("write failure");
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
fireAndForget(type, payload) {
|
|
435
|
+
if (!this.lifecycle.isReady)
|
|
436
|
+
return;
|
|
437
|
+
this.send(type, 0, payload);
|
|
438
|
+
}
|
|
439
|
+
async request(type, payload) {
|
|
440
|
+
if (this.lifecycle.isDropped) {
|
|
441
|
+
throw new RemotePtyHostError("connection-dropped", "RemotePtyHost socket is dropped");
|
|
442
|
+
}
|
|
443
|
+
if (this.lifecycle.isAwaitingHandshake) {
|
|
444
|
+
// Defer until handshake completes (HELLO_ACK + first SESSION_LIST);
|
|
445
|
+
// if it rejects, propagate.
|
|
446
|
+
await this.lifecycle.awaitReady;
|
|
447
|
+
}
|
|
448
|
+
return this.correlator.request(type, payload);
|
|
449
|
+
}
|
|
450
|
+
handleDrop(reason) {
|
|
451
|
+
const err = new RemotePtyHostError("connection-dropped", reason);
|
|
452
|
+
// lifecycle.drop transitions to 'dropped' and rejects the handshake
|
|
453
|
+
// promise if it was still awaiting (Promise reject is idempotent, so a
|
|
454
|
+
// prior rejectHandshakeOnly with a daemon-specific code wins). Returns
|
|
455
|
+
// false when state was already 'dropped' -- gate correlator.rejectAll on
|
|
456
|
+
// the actual transition so a re-entry from the close/error listener
|
|
457
|
+
// pair doesn't fire a second bulk reject.
|
|
458
|
+
if (this.lifecycle.drop(err)) {
|
|
459
|
+
this.correlator.rejectAll(err);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
// --- PtyHost interface (async) ---
|
|
463
|
+
async create(input) {
|
|
464
|
+
const payload = {
|
|
465
|
+
projectId: input.projectId,
|
|
466
|
+
command: input.command,
|
|
467
|
+
cwd: input.cwd,
|
|
468
|
+
title: input.title,
|
|
469
|
+
bootstrapInput: input.bootstrapInput,
|
|
470
|
+
};
|
|
471
|
+
const ack = await this.request(FrameType.CREATE_REQ, encodeJsonPayload(payload));
|
|
472
|
+
const body = decodeJsonPayload(ack.payload);
|
|
473
|
+
this.mirror.upsert(body.session);
|
|
474
|
+
return body.session;
|
|
475
|
+
}
|
|
476
|
+
async restart(id) {
|
|
477
|
+
const payload = { sessionId: id };
|
|
478
|
+
const ack = await this.request(FrameType.RESTART_REQ, encodeJsonPayload(payload));
|
|
479
|
+
const body = decodeJsonPayload(ack.payload);
|
|
480
|
+
/*
|
|
481
|
+
* PTY generation gate: restart bumps the generation server-side. `upsert` seeds the
|
|
482
|
+
* latch so the WS client's first INPUT after restart is sent under the
|
|
483
|
+
* new generation; otherwise it races the first DATA chunk under the old
|
|
484
|
+
* gen, trips the daemon-side input gate, and drops the keystroke.
|
|
485
|
+
*/
|
|
486
|
+
this.mirror.upsert(body.session);
|
|
487
|
+
return body.session;
|
|
488
|
+
}
|
|
489
|
+
async dispose(id) {
|
|
490
|
+
const payload = { sessionId: id };
|
|
491
|
+
await this.request(FrameType.DISPOSE_REQ, encodeJsonPayload(payload));
|
|
492
|
+
this.mirror.remove(id);
|
|
493
|
+
this.attached.delete(id);
|
|
494
|
+
this.headlessStateCache?.delete(id);
|
|
495
|
+
if (this.scrollbackLog) {
|
|
496
|
+
this.scrollbackLog.remove(id);
|
|
497
|
+
this.scrollbackOwnedIds.delete(id);
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
async disposeAll() {
|
|
501
|
+
await this.request(FrameType.DISPOSE_ALL_REQ, encodeJsonPayload({}));
|
|
502
|
+
this.mirror.clear();
|
|
503
|
+
this.attached.clear();
|
|
504
|
+
this.headlessStateCache?.clear();
|
|
505
|
+
if (this.scrollbackLog) {
|
|
506
|
+
for (const id of this.scrollbackOwnedIds)
|
|
507
|
+
this.scrollbackLog.remove(id);
|
|
508
|
+
this.scrollbackOwnedIds.clear();
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
/**
|
|
512
|
+
* By protocol, `shutdownAll` is detach-only: the
|
|
513
|
+
* daemon ACKs and then evicts our connection. We swallow the
|
|
514
|
+
* subsequent `connection-dropped` to make caller code shape-identical
|
|
515
|
+
* to `InProcessPtyHost.shutdownAll()` (which never throws).
|
|
516
|
+
*
|
|
517
|
+
* The `reason` parameter is accepted to satisfy the `PtyHost`
|
|
518
|
+
* interface (in-process uses it to stamp `endReason` on every session
|
|
519
|
+
* record); on the remote path it has no effect -- the daemon owns the
|
|
520
|
+
* sessions and will stamp its own `daemon-graceful`/`daemon-crash` on
|
|
521
|
+
* its next own SIGTERM.
|
|
522
|
+
*/
|
|
523
|
+
async shutdownAll(_reason) {
|
|
524
|
+
if (!this.lifecycle.isReady)
|
|
525
|
+
return;
|
|
526
|
+
try {
|
|
527
|
+
await this.request(FrameType.SHUTDOWN_ALL_REQ, encodeJsonPayload({}));
|
|
528
|
+
}
|
|
529
|
+
catch (err) {
|
|
530
|
+
if (err instanceof RemotePtyHostError &&
|
|
531
|
+
err.code === "connection-dropped")
|
|
532
|
+
return;
|
|
533
|
+
throw err;
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
/**
|
|
537
|
+
* state persistence delegate / daemon state ownership -- implements `AppStatePersistenceDelegate.persist`. Ships
|
|
538
|
+
* the server-owned project-domain snapshot to the daemon, which is the
|
|
539
|
+
* sole writer of `state.json`. Resolves on PERSIST_PROJECT_DOMAINS_ACK,
|
|
540
|
+
* rejects on NACK / connection drop / IPC timeout. AppStateStore routes
|
|
541
|
+
* those rejections through `onPersistError` so a transient IPC fault
|
|
542
|
+
* does not silently lose project / projectStates / serviceConfig /
|
|
543
|
+
* paneCommands / ideCommands
|
|
544
|
+
* mutations.
|
|
545
|
+
*
|
|
546
|
+
* `sessions` and `sessionRecords` are deliberately not forwarded -- the
|
|
547
|
+
* daemon owns those domains and would overwrite its own snapshot if we
|
|
548
|
+
* did. Only the server-owned slices ride along.
|
|
549
|
+
*/
|
|
550
|
+
async persist(state) {
|
|
551
|
+
const payload = {
|
|
552
|
+
projects: state.projects,
|
|
553
|
+
projectStates: state.projectStates,
|
|
554
|
+
serviceConfig: state.serviceConfig,
|
|
555
|
+
paneCommands: state.paneCommands,
|
|
556
|
+
ideCommands: state.ideCommands,
|
|
557
|
+
};
|
|
558
|
+
await this.request(FrameType.PERSIST_PROJECT_DOMAINS_REQ, encodeJsonPayload(payload));
|
|
559
|
+
}
|
|
560
|
+
async initClient(id, clientId, cols, rows, listener) {
|
|
561
|
+
const attachToken = this.nextAttachToken++;
|
|
562
|
+
const payload = {
|
|
563
|
+
sessionId: id,
|
|
564
|
+
clientId,
|
|
565
|
+
cols,
|
|
566
|
+
rows,
|
|
567
|
+
attachToken,
|
|
568
|
+
};
|
|
569
|
+
const ack = await this.request(FrameType.INIT_CLIENT_REQ, encodeJsonPayload(payload));
|
|
570
|
+
const body = decodeJsonPayload(ack.payload);
|
|
571
|
+
if (!body.accepted)
|
|
572
|
+
return { ok: false };
|
|
573
|
+
let bySession = this.attached.get(id);
|
|
574
|
+
if (!bySession) {
|
|
575
|
+
bySession = new Map();
|
|
576
|
+
this.attached.set(id, bySession);
|
|
577
|
+
}
|
|
578
|
+
bySession.set(clientId, { listener, attachToken });
|
|
579
|
+
return { ok: true, attachToken };
|
|
580
|
+
}
|
|
581
|
+
/**
|
|
582
|
+
* : daemon-mode chunked replay is out of scope for the
|
|
583
|
+
* initial implementation -- the daemon's STREAM_DATA frame does not yet
|
|
584
|
+
* carry the (gen, seq) header needed for the chunk ring to be
|
|
585
|
+
* authoritative end-to-end. We therefore negotiate
|
|
586
|
+
* `binary=false / chunkedReplay=false` so the client knows scrollback
|
|
587
|
+
* cursor semantics are legacy.
|
|
588
|
+
*
|
|
589
|
+
* Live OUTPUT is forwarded through the binary `sink.onChunk` path
|
|
590
|
+
* with a synthetic per-attach seq counter; the generation now comes
|
|
591
|
+
* from `latestGeneration` (PTY generation gate) so the WS layer stamps each OUTPUT
|
|
592
|
+
* with the producing PTY's true generation, the client echoes that
|
|
593
|
+
* back on INPUT, and the daemon-side gate can drop stale input that
|
|
594
|
+
* targets a no-longer-current generation. The client-side
|
|
595
|
+
* `serverState.lastDeliveredSeq=null` we return causes the client to
|
|
596
|
+
* drop any persisted cursor on init-ack -- so the synthetic seq cannot
|
|
597
|
+
* collide with a stale ring entry on reconnect.
|
|
598
|
+
*
|
|
599
|
+
* Scrollback rehydration: when a `ScrollbackLog` is supplied, the disk
|
|
600
|
+
* tail is shipped as `replay:"full"`. This fixes the "blank terminal
|
|
601
|
+
* on tab switch" regression -- without it, a re-mount sees nothing
|
|
602
|
+
* until fresh PTY output arrives, even though the daemon still owns
|
|
603
|
+
* the live session.
|
|
604
|
+
*/
|
|
605
|
+
async attachClient(id, clientId, cols, rows, _capabilities, sink) {
|
|
606
|
+
let syntheticSeq = 0n;
|
|
607
|
+
const result = await this.initClient(id, clientId, cols, rows, (data) => {
|
|
608
|
+
const gen = this.mirror.generationOf(id);
|
|
609
|
+
sink.onChunk(gen, syntheticSeq++, Buffer.from(data, "utf8"));
|
|
610
|
+
});
|
|
611
|
+
if (!result.ok)
|
|
612
|
+
return { ok: false };
|
|
613
|
+
const seedGen = this.mirror.generationOf(id);
|
|
614
|
+
const tail = this.scrollbackLog?.readTail(id) ?? "";
|
|
615
|
+
if (tail.length > 0) {
|
|
616
|
+
let fullReplay;
|
|
617
|
+
let replayDiagnostics;
|
|
618
|
+
const rawBytes = Buffer.byteLength(tail, "utf8");
|
|
619
|
+
if (this.headlessReplayEnabled && this.headlessStateCache) {
|
|
620
|
+
try {
|
|
621
|
+
const headlessSnapshot = (await this.headlessStateCache.snapshot(id, { cols, rows })) ??
|
|
622
|
+
(await this.headlessStateCache.rebuild(id, tail, { cols, rows }));
|
|
623
|
+
if (!headlessSnapshot) {
|
|
624
|
+
throw new Error("empty headless replay snapshot");
|
|
625
|
+
}
|
|
626
|
+
const snapshot = headlessSnapshot.snapshot;
|
|
627
|
+
fullReplay = headlessSnapshot.snapshot.text;
|
|
628
|
+
replayDiagnostics = {
|
|
629
|
+
source: headlessSnapshot.source,
|
|
630
|
+
rawBytes: snapshot.rawBytes,
|
|
631
|
+
replayBytes: snapshot.snapshotBytes,
|
|
632
|
+
headlessDurationMs: snapshot.durationMs,
|
|
633
|
+
headlessBufferLines: snapshot.bufferLines,
|
|
634
|
+
headlessEmittedLines: snapshot.emittedLines,
|
|
635
|
+
scrollbackLines: this.headlessReplayScrollbackLines,
|
|
636
|
+
maxBytes: this.headlessReplayMaxBytes,
|
|
637
|
+
};
|
|
638
|
+
}
|
|
639
|
+
catch (err) {
|
|
640
|
+
console.warn(`[terminal] headless replay snapshot failed for session=${id.slice(0, 8)}: ${err.message}`);
|
|
641
|
+
fullReplay = stripQueryEscapes(utf8Tail(tail, this.daemonLegacyReplayMaxBytes));
|
|
642
|
+
replayDiagnostics = {
|
|
643
|
+
source: "headless-fallback",
|
|
644
|
+
rawBytes,
|
|
645
|
+
replayBytes: Buffer.byteLength(fullReplay, "utf8"),
|
|
646
|
+
scrollbackLines: this.headlessReplayScrollbackLines,
|
|
647
|
+
maxBytes: this.headlessReplayMaxBytes,
|
|
648
|
+
};
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
else {
|
|
652
|
+
fullReplay = stripQueryEscapes(utf8Tail(tail, this.daemonLegacyReplayMaxBytes));
|
|
653
|
+
replayDiagnostics = {
|
|
654
|
+
source: "raw-tail",
|
|
655
|
+
rawBytes,
|
|
656
|
+
replayBytes: Buffer.byteLength(fullReplay, "utf8"),
|
|
657
|
+
maxBytes: this.daemonLegacyReplayMaxBytes,
|
|
658
|
+
};
|
|
659
|
+
}
|
|
660
|
+
return {
|
|
661
|
+
ok: true,
|
|
662
|
+
attachToken: result.attachToken,
|
|
663
|
+
capabilities: { binary: false, chunkedReplay: false },
|
|
664
|
+
serverState: {
|
|
665
|
+
generation: seedGen,
|
|
666
|
+
lastDeliveredSeq: null,
|
|
667
|
+
oldestSeq: null,
|
|
668
|
+
},
|
|
669
|
+
replay: "full",
|
|
670
|
+
fullReplay,
|
|
671
|
+
replayDiagnostics,
|
|
672
|
+
};
|
|
673
|
+
}
|
|
674
|
+
return {
|
|
675
|
+
ok: true,
|
|
676
|
+
attachToken: result.attachToken,
|
|
677
|
+
capabilities: { binary: false, chunkedReplay: false },
|
|
678
|
+
serverState: {
|
|
679
|
+
generation: seedGen,
|
|
680
|
+
lastDeliveredSeq: null,
|
|
681
|
+
oldestSeq: null,
|
|
682
|
+
},
|
|
683
|
+
replay: "none",
|
|
684
|
+
};
|
|
685
|
+
}
|
|
686
|
+
// --- PtyHost interface (sync mutators, fire-and-forget) ---
|
|
687
|
+
setPtyEnv(env) {
|
|
688
|
+
this.ptyEnv = { ...this.ptyEnv, ...env };
|
|
689
|
+
const payload = { env };
|
|
690
|
+
this.fireAndForget(FrameType.SET_PTY_ENV, encodeJsonPayload(payload));
|
|
691
|
+
}
|
|
692
|
+
setTitle(id, title, titleManual = false) {
|
|
693
|
+
const session = this.mirror.get(id);
|
|
694
|
+
if (!session)
|
|
695
|
+
return false;
|
|
696
|
+
const next = titleManual
|
|
697
|
+
? { ...session, title, titleManual: true }
|
|
698
|
+
: (() => {
|
|
699
|
+
const { titleManual: _drop, ...rest } = session;
|
|
700
|
+
return { ...rest, title };
|
|
701
|
+
})();
|
|
702
|
+
this.mirror.replace(next);
|
|
703
|
+
const payload = { sessionId: id, title, titleManual };
|
|
704
|
+
this.fireAndForget(FrameType.SET_TITLE, encodeJsonPayload(payload));
|
|
705
|
+
return true;
|
|
706
|
+
}
|
|
707
|
+
setPinned(id, pinned) {
|
|
708
|
+
const session = this.mirror.get(id);
|
|
709
|
+
if (!session)
|
|
710
|
+
return false;
|
|
711
|
+
/*
|
|
712
|
+
* Match InProcessPtyHost's optimistic shape: drop the `pinned`
|
|
713
|
+
* key entirely when it goes false, set it to `true` when true.
|
|
714
|
+
* This keeps the Liskov contract intact -- callers reading the
|
|
715
|
+
* mirror sync after setPinned() see the same shape regardless of
|
|
716
|
+
* implementation.
|
|
717
|
+
*/
|
|
718
|
+
if (pinned) {
|
|
719
|
+
this.mirror.replace({ ...session, pinned: true });
|
|
720
|
+
}
|
|
721
|
+
else {
|
|
722
|
+
const { pinned: _drop, ...rest } = session;
|
|
723
|
+
this.mirror.replace(rest);
|
|
724
|
+
}
|
|
725
|
+
const payload = { sessionId: id, pinned };
|
|
726
|
+
this.fireAndForget(FrameType.SET_PINNED, encodeJsonPayload(payload));
|
|
727
|
+
return true;
|
|
728
|
+
}
|
|
729
|
+
write(id, data, generation) {
|
|
730
|
+
/*
|
|
731
|
+
* PTY generation gate: forward the client-supplied generation across the IPC so the
|
|
732
|
+
* daemon-side InProcessPtyHost can apply the same drop-stale-input
|
|
733
|
+
* gate that in-process mode uses. `0` is a sentinel meaning "no
|
|
734
|
+
* gating" (legacy callers / non-WS writes); the daemon treats `0`
|
|
735
|
+
* the same way an `undefined` generation arg would be treated.
|
|
736
|
+
*/
|
|
737
|
+
this.fireAndForget(FrameType.WRITE, encodeGenerationStreamPayload(id, Buffer.from(data, "utf8"), generation ?? 0));
|
|
738
|
+
}
|
|
739
|
+
resize(id, cols, rows) {
|
|
740
|
+
const payload = { sessionId: id, cols, rows };
|
|
741
|
+
this.fireAndForget(FrameType.RESIZE, encodeJsonPayload(payload));
|
|
742
|
+
}
|
|
743
|
+
refresh(id) {
|
|
744
|
+
const payload = { sessionId: id };
|
|
745
|
+
this.fireAndForget(FrameType.REFRESH, encodeJsonPayload(payload));
|
|
746
|
+
}
|
|
747
|
+
pauseOutput(id, clientId) {
|
|
748
|
+
const payload = { sessionId: id, clientId };
|
|
749
|
+
this.fireAndForget(FrameType.PAUSE_OUTPUT, encodeJsonPayload(payload));
|
|
750
|
+
}
|
|
751
|
+
resumeOutput(id, clientId) {
|
|
752
|
+
const payload = { sessionId: id, clientId };
|
|
753
|
+
this.fireAndForget(FrameType.RESUME_OUTPUT, encodeJsonPayload(payload));
|
|
754
|
+
}
|
|
755
|
+
detachClient(id, clientId, expectedToken) {
|
|
756
|
+
const bySession = this.attached.get(id);
|
|
757
|
+
if (expectedToken !== undefined) {
|
|
758
|
+
const entry = bySession?.get(clientId);
|
|
759
|
+
if (!entry || entry.attachToken !== expectedToken)
|
|
760
|
+
return;
|
|
761
|
+
}
|
|
762
|
+
if (bySession) {
|
|
763
|
+
bySession.delete(clientId);
|
|
764
|
+
if (bySession.size === 0)
|
|
765
|
+
this.attached.delete(id);
|
|
766
|
+
}
|
|
767
|
+
const payload = {
|
|
768
|
+
sessionId: id,
|
|
769
|
+
clientId,
|
|
770
|
+
...(expectedToken !== undefined && { attachToken: expectedToken }),
|
|
771
|
+
};
|
|
772
|
+
this.fireAndForget(FrameType.DETACH_CLIENT, encodeJsonPayload(payload));
|
|
773
|
+
}
|
|
774
|
+
// --- PtyHost interface (sync read accessors, mirror-served) ---
|
|
775
|
+
list() {
|
|
776
|
+
return this.mirror.list();
|
|
777
|
+
}
|
|
778
|
+
get(id) {
|
|
779
|
+
return this.mirror.get(id);
|
|
780
|
+
}
|
|
781
|
+
listByProject(projectId) {
|
|
782
|
+
return this.mirror.listByProject(projectId);
|
|
783
|
+
}
|
|
784
|
+
/*
|
|
785
|
+
* Scrollback is served from the server-side disk log populated by
|
|
786
|
+
* `applyData`. Foreground-process state still lives in the daemon's
|
|
787
|
+
* AppStateStore and is reachable only via SESSION_UPDATE -- the
|
|
788
|
+
* accessor stays null per the mismatch column.
|
|
789
|
+
*/
|
|
790
|
+
getScrollback(id) {
|
|
791
|
+
if (!this.scrollbackLog)
|
|
792
|
+
return null;
|
|
793
|
+
const tail = this.scrollbackLog.readTail(id);
|
|
794
|
+
return tail.length > 0 ? tail : null;
|
|
795
|
+
}
|
|
796
|
+
getForegroundProcess(_id) {
|
|
797
|
+
return null;
|
|
798
|
+
}
|
|
799
|
+
loadPersistedSession(_session, _wasGracefulShutdown) {
|
|
800
|
+
// No-op on remote: the daemon owns persistence and SESSION_LIST is the
|
|
801
|
+
// server mirror's source of truth.
|
|
802
|
+
}
|
|
803
|
+
onSessionInput(listener) {
|
|
804
|
+
this.inputListeners.push(listener);
|
|
805
|
+
}
|
|
806
|
+
onSessionData(listener) {
|
|
807
|
+
this.dataListeners.push(listener);
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
//# sourceMappingURL=remote-host.js.map
|