@volter/editor-core 0.5.57
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/BUNDLED_NOTICES +1527 -0
- package/LICENSE +686 -0
- package/LICENSE-APACHE +202 -0
- package/NOTICE +18 -0
- package/README.md +24 -0
- package/dist/build/chunk-JS4D4XZ3.js +175 -0
- package/dist/build/chunk-JS4D4XZ3.js.map +7 -0
- package/dist/build/vite-plugin-product-contributions.js +100 -0
- package/dist/build/vite-plugin-product-contributions.js.map +7 -0
- package/dist/build/vite-plugin-shared-react.js +25 -0
- package/dist/build/vite-plugin-shared-react.js.map +7 -0
- package/dist/build/vite-plugin-shared-three.js +86 -0
- package/dist/build/vite-plugin-shared-three.js.map +7 -0
- package/dist/server/chunk-KMJC7AGO.js +98 -0
- package/dist/server/chunk-KMJC7AGO.js.map +7 -0
- package/dist/server/chunk-PSILUMGS.js +46 -0
- package/dist/server/chunk-PSILUMGS.js.map +7 -0
- package/dist/server/frame-proxy.js +255 -0
- package/dist/server/frame-proxy.js.map +7 -0
- package/dist/server/open-browser.js +53 -0
- package/dist/server/open-browser.js.map +7 -0
- package/dist/server/process-shutdown.js +127 -0
- package/dist/server/process-shutdown.js.map +7 -0
- package/dist/server/session-registry.js +141 -0
- package/dist/server/session-registry.js.map +7 -0
- package/dist/server/spawn-opener.js +11 -0
- package/dist/server/spawn-opener.js.map +7 -0
- package/dist/server/worktree-identity.js +11 -0
- package/dist/server/worktree-identity.js.map +7 -0
- package/dist-server/packaged.mjs +88816 -0
- package/package.json +118 -0
- package/scripts/build-plugins.mjs +14 -0
- package/scripts/build-server.mjs +26 -0
- package/scripts/build-session.mjs +9 -0
- package/server/account-credentials.ts +60 -0
- package/server/account-service.ts +1434 -0
- package/server/adapter-region-includes.ts +47 -0
- package/server/asset-catalog-v2.ts +175 -0
- package/server/asset-history-snapshots.ts +196 -0
- package/server/asset-ledger-store.ts +195 -0
- package/server/asset-library-routes.ts +1595 -0
- package/server/blender-wasm-artifact.ts +430 -0
- package/server/boot-timings.ts +106 -0
- package/server/canonical-path.ts +50 -0
- package/server/checkout-workspace-preflight.ts +349 -0
- package/server/cloud-asset-catalog.ts +158 -0
- package/server/coding-inference-launch.ts +237 -0
- package/server/collaboration-account-client.ts +202 -0
- package/server/collaboration-attribution.ts +122 -0
- package/server/collaboration-session.ts +934 -0
- package/server/comfyui-bridge.ts +248 -0
- package/server/console-ledger.ts +499 -0
- package/server/creation-site-transform.ts +304 -0
- package/server/creation-site-write.ts +648 -0
- package/server/data-file-serialize.ts +158 -0
- package/server/editor-brand-html.ts +72 -0
- package/server/editor-control-socket.ts +366 -0
- package/server/editor-server-options.ts +190 -0
- package/server/editor-server.ts +1483 -0
- package/server/editor-sse.ts +352 -0
- package/server/engine-provenance.ts +169 -0
- package/server/engine-source-restart.ts +117 -0
- package/server/frame-bridge.ts +115 -0
- package/server/frame-proxy.ts +490 -0
- package/server/frame-workbench.ts +259 -0
- package/server/frontend-handoff.ts +229 -0
- package/server/game-globals-shadow.ts +113 -0
- package/server/gameplay-session-retention.ts +149 -0
- package/server/gameplay-sessions.ts +123 -0
- package/server/generation-jobs.ts +201 -0
- package/server/generation-reconciler.ts +137 -0
- package/server/generative-execution-context.ts +59 -0
- package/server/git-workflow.ts +612 -0
- package/server/harness-chat-caller.ts +36 -0
- package/server/harness-chat-service.ts +1681 -0
- package/server/idle-shutdown.ts +170 -0
- package/server/js-profiling-policy.ts +52 -0
- package/server/launcher-settings.ts +84 -0
- package/server/local-asset-catalog.ts +410 -0
- package/server/managed-account-defaults.ts +22 -0
- package/server/mock-control-plane.ts +540 -0
- package/server/model-import-conversion.ts +345 -0
- package/server/native-credential-store.ts +162 -0
- package/server/open-browser.ts +118 -0
- package/server/packaged.ts +1192 -0
- package/server/play-stall.ts +126 -0
- package/server/process-shutdown.ts +218 -0
- package/server/product-presets.ts +55 -0
- package/server/project-build-artifact.ts +95 -0
- package/server/project-components.ts +100 -0
- package/server/project-dependency-invalidation.ts +114 -0
- package/server/project-file-scan.ts +235 -0
- package/server/project-hmr-files.ts +215 -0
- package/server/project-install-roots.ts +210 -0
- package/server/project-kinds.ts +71 -0
- package/server/project-mcp-servers.ts +82 -0
- package/server/project-module-freshness.ts +109 -0
- package/server/project-module-instance.ts +375 -0
- package/server/project-optimize-deps-entries.ts +827 -0
- package/server/project-output-writer.ts +679 -0
- package/server/project-package-origin.ts +63 -0
- package/server/project-root-surface.ts +831 -0
- package/server/project-scratch-path.ts +47 -0
- package/server/project-script-hmr.ts +619 -0
- package/server/project-serving-plugins.ts +236 -0
- package/server/project-tools.ts +653 -0
- package/server/project-validation.ts +250 -0
- package/server/project-verbs.ts +127 -0
- package/server/project-view.ts +52 -0
- package/server/project-watch.ts +1134 -0
- package/server/project-work-coordinator.ts +288 -0
- package/server/provider-credentials.ts +331 -0
- package/server/recent-projects-store.ts +98 -0
- package/server/redact-secrets.ts +71 -0
- package/server/repository-presence.ts +267 -0
- package/server/routes/account.ts +357 -0
- package/server/routes/agents.ts +60 -0
- package/server/routes/assets.ts +154 -0
- package/server/routes/build.ts +13 -0
- package/server/routes/collaboration.ts +595 -0
- package/server/routes/configurations.ts +419 -0
- package/server/routes/context.ts +264 -0
- package/server/routes/control-plane.ts +1811 -0
- package/server/routes/logs.ts +272 -0
- package/server/routes/project-identity.ts +302 -0
- package/server/routes/project-open.ts +429 -0
- package/server/routes/project-source.ts +686 -0
- package/server/routes/project-state.ts +383 -0
- package/server/routes/relay.ts +1029 -0
- package/server/routes/route-helpers.ts +29 -0
- package/server/routes/served-modules.ts +138 -0
- package/server/routes/session-tabs.ts +196 -0
- package/server/routes/settings.ts +121 -0
- package/server/routes/share-control.ts +486 -0
- package/server/routes/themes.ts +116 -0
- package/server/routes/tools.ts +230 -0
- package/server/routes/worktrees.ts +595 -0
- package/server/scoped-game-css.ts +461 -0
- package/server/server-utils.ts +1194 -0
- package/server/session-product.ts +112 -0
- package/server/session-registry.ts +230 -0
- package/server/share-claims.ts +138 -0
- package/server/share-host.ts +526 -0
- package/server/share-session-gateway.ts +1362 -0
- package/server/share-tunnel.ts +123 -0
- package/server/spawn-opener.ts +74 -0
- package/server/support/play/log-format.ts +76 -0
- package/server/support/play/session-record.ts +199 -0
- package/server/support/project/build-discipline.ts +718 -0
- package/server/support/project/inspection-node.ts +119 -0
- package/server/support/project/log-naming.ts +54 -0
- package/server/support/project/provenance.ts +290 -0
- package/server/support/project/run-name.ts +18 -0
- package/server/support/project/session-journal.ts +830 -0
- package/server/support/project/shared.ts +327 -0
- package/server/tab-bootstrap.ts +62 -0
- package/server/tab-heartbeat.ts +477 -0
- package/server/tab-lifecycle.ts +550 -0
- package/server/tab-presence.ts +1508 -0
- package/server/team-agent-mirror.ts +95 -0
- package/server/twin-auth.ts +138 -0
- package/server/vendored-lock-recorder.ts +968 -0
- package/server/worktree-identity.ts +121 -0
- package/server/worktree-management.ts +398 -0
- package/server/worktree-retention.d.mts +13 -0
- package/server/worktree-retention.mjs +205 -0
- package/src/EditorContext.tsx +169 -0
- package/src/account.ts +333 -0
- package/src/action-registry.ts +532 -0
- package/src/active-product.ts +74 -0
- package/src/active-project.ts +155 -0
- package/src/adapter-editor-config.ts +25 -0
- package/src/adapter-observation.ts +49 -0
- package/src/animation/stage-transport.ts +379 -0
- package/src/animation/three-clips-subject.ts +172 -0
- package/src/api/asset-library-wire.ts +45 -0
- package/src/api/assets.ts +365 -0
- package/src/api/base.ts +10 -0
- package/src/api/build.ts +99 -0
- package/src/api/git-wire.ts +56 -0
- package/src/api/logs.ts +92 -0
- package/src/api/project-identity.ts +74 -0
- package/src/api/project-open.ts +355 -0
- package/src/api/project-source.ts +162 -0
- package/src/api/project-state.ts +107 -0
- package/src/api/relay.ts +268 -0
- package/src/api/settings.ts +36 -0
- package/src/api/themes.ts +45 -0
- package/src/api/worktrees.ts +257 -0
- package/src/asset-compare-core.ts +171 -0
- package/src/asset-compare.ts +294 -0
- package/src/asset-editor-context.tsx +100 -0
- package/src/asset-events.ts +97 -0
- package/src/asset-inspector-actions.ts +87 -0
- package/src/asset-preview-framing.ts +357 -0
- package/src/asset-preview.ts +2802 -0
- package/src/asset-selection-viewer-registry.ts +113 -0
- package/src/asset-selection.ts +133 -0
- package/src/asset-workflow/asset-capabilities.ts +344 -0
- package/src/asset-workflow/asset-import-jobs.ts +106 -0
- package/src/asset-workflow/asset-ledger-backend.ts +126 -0
- package/src/asset-workflow/asset-ledger.ts +156 -0
- package/src/asset-workflow/asset-materialization-report.ts +140 -0
- package/src/asset-workflow/asset-pack-manifest.ts +320 -0
- package/src/asset-workflow/asset-types.ts +142 -0
- package/src/asset-workflow/asset-workflow-quality.ts +61 -0
- package/src/asset-workflow/audio-preview-player.ts +193 -0
- package/src/asset-workflow/audio-waveform.ts +22 -0
- package/src/asset-workflow/cloud-asset-client.ts +263 -0
- package/src/asset-workflow/folder-preview.ts +345 -0
- package/src/asset-workflow/hosted-asset-materialization.ts +236 -0
- package/src/asset-workflow/image-view-scale.ts +32 -0
- package/src/asset-workflow/import-contract.ts +124 -0
- package/src/asset-workflow/ledger-write-lock.ts +244 -0
- package/src/asset-workflow/model-inspection.ts +818 -0
- package/src/asset-workflow/pixi-spritesheet.ts +197 -0
- package/src/asset-workflow/preview-resource-lifetime.ts +44 -0
- package/src/asset-workflow/project-asset-commands.ts +23 -0
- package/src/asset-workflow/project-asset-health.ts +139 -0
- package/src/asset-workflow/project-asset-operations.ts +154 -0
- package/src/asset-workflow/project-asset-roots.ts +68 -0
- package/src/asset-workflow/project-content.ts +288 -0
- package/src/asset-workflow/project-source-index.ts +550 -0
- package/src/asset-workflow/thumbnail-system.ts +256 -0
- package/src/authoring/active-adapter.ts +199 -0
- package/src/authoring/active-systems.ts +422 -0
- package/src/authoring/adapter-key.ts +18 -0
- package/src/authoring/authoring-asset-url.ts +27 -0
- package/src/authoring/bootstrap-state.ts +49 -0
- package/src/authoring/boundary-authoring-adapter.ts +184 -0
- package/src/authoring/breakpoint-state.ts +43 -0
- package/src/authoring/canvas-scene-guides.ts +76 -0
- package/src/authoring/component-instance-root.ts +171 -0
- package/src/authoring/composite-authoring-adapter.ts +2110 -0
- package/src/authoring/consumer-actions.ts +520 -0
- package/src/authoring/css-numeric-style.ts +97 -0
- package/src/authoring/design-time-layers.ts +849 -0
- package/src/authoring/design-time-mount-registry.ts +235 -0
- package/src/authoring/design-time-settle.ts +343 -0
- package/src/authoring/edit-mode-authoring.ts +619 -0
- package/src/authoring/eyedropper-session.ts +60 -0
- package/src/authoring/instance-source-menu-register.ts +13 -0
- package/src/authoring/instance-source-menu.ts +135 -0
- package/src/authoring/layered-pick.ts +183 -0
- package/src/authoring/live-gesture-lock.ts +50 -0
- package/src/authoring/live-object-transform.ts +62 -0
- package/src/authoring/mount-failure-report.ts +154 -0
- package/src/authoring/mounted-root-subjects.ts +144 -0
- package/src/authoring/no-authoring-adapter.ts +55 -0
- package/src/authoring/null-inspection-subjects.tsx +75 -0
- package/src/authoring/object3d-document-persistence.ts +106 -0
- package/src/authoring/object3d-document-session-registry.ts +120 -0
- package/src/authoring/object3d-document-session.ts +1326 -0
- package/src/authoring/object3d-gesture-controller.ts +113 -0
- package/src/authoring/panel-authoring.ts +121 -0
- package/src/authoring/prefab-instance-inspector-section.tsx +237 -0
- package/src/authoring/prefab-instance-section-model.ts +55 -0
- package/src/authoring/project-authoring-session.ts +105 -0
- package/src/authoring/provenance.ts +99 -0
- package/src/authoring/quarks-particle-systems.ts +19 -0
- package/src/authoring/react-canvas-navigation.ts +255 -0
- package/src/authoring/react-design-canvas-style.ts +20 -0
- package/src/authoring/react-story-board.ts +937 -0
- package/src/authoring/selection-scope.ts +195 -0
- package/src/authoring/shell-document-ops.ts +169 -0
- package/src/authoring/shell-object3d-document-write-policy.ts +108 -0
- package/src/authoring/shell-viewport-policy.ts +48 -0
- package/src/authoring/source-object3d-authoring-adapter.ts +526 -0
- package/src/authoring/stories-scope.ts +35 -0
- package/src/authoring/story-board-chrome-fit.ts +107 -0
- package/src/authoring/story-board-presentation.ts +111 -0
- package/src/authoring/three-projection-core.ts +226 -0
- package/src/authoring/viewport-pick-context.ts +40 -0
- package/src/authoring/viewport-raycast.ts +240 -0
- package/src/authoring/viewport-tool-context.ts +73 -0
- package/src/authoring/world-canvas-viewport-state.ts +35 -0
- package/src/authoring/world-hidden-viewport.ts +152 -0
- package/src/authoring/world-pan-state.ts +198 -0
- package/src/authoring/world-session-state.ts +101 -0
- package/src/authoring/write-pipe.ts +173 -0
- package/src/availability-tick.ts +66 -0
- package/src/bitmap-label.ts +120 -0
- package/src/blender-tab-metrics.ts +161 -0
- package/src/board-open-actions.ts +20 -0
- package/src/boot-routing.ts +391 -0
- package/src/build-identity.ts +15 -0
- package/src/bytes-codec.ts +62 -0
- package/src/camera-authoring.ts +175 -0
- package/src/cancellation-reason.ts +58 -0
- package/src/canvas-preview-frames.ts +482 -0
- package/src/capture-camera-pose.ts +77 -0
- package/src/chrome-registry.ts +159 -0
- package/src/chrome-slot-registry.ts +91 -0
- package/src/collaboration-client.ts +264 -0
- package/src/collaboration-presence.ts +41 -0
- package/src/command-dispatch.ts +19 -0
- package/src/command-listener.ts +2743 -0
- package/src/command-registry.ts +70 -0
- package/src/component-board-registry.ts +205 -0
- package/src/component-states-registry.ts +200 -0
- package/src/components/AgentPresentationNotice.tsx +75 -0
- package/src/components/AlignToolbar.tsx +204 -0
- package/src/components/AppRoot.tsx +394 -0
- package/src/components/ApplicationMenus.tsx +415 -0
- package/src/components/AssetBrowser.tsx +2843 -0
- package/src/components/AssetEditorShell.tsx +216 -0
- package/src/components/AssetInspectorToolSection.tsx +124 -0
- package/src/components/BoardRulers.tsx +354 -0
- package/src/components/CameraInfo.tsx +81 -0
- package/src/components/CanvasSceneViewport.tsx +556 -0
- package/src/components/CapabilityCoverageSection.tsx +185 -0
- package/src/components/CenterDocuments.tsx +22 -0
- package/src/components/ChromeSlot.tsx +20 -0
- package/src/components/CodeView.tsx +470 -0
- package/src/components/CollaborationHeaderControl.css +74 -0
- package/src/components/CompactInspectorCard.tsx +190 -0
- package/src/components/CompactInspectorShell.tsx +39 -0
- package/src/components/ConsolePanel.css +55 -0
- package/src/components/ConsolePanel.tsx +270 -0
- package/src/components/DefaultEditorLayout.tsx +106 -0
- package/src/components/DocumentHeaderStrip.tsx +96 -0
- package/src/components/DocumentShelfRail.tsx +45 -0
- package/src/components/DocumentThumbnail.tsx +345 -0
- package/src/components/EditorLeaseGuard.tsx +372 -0
- package/src/components/ErrorBanner.tsx +123 -0
- package/src/components/FolderPreviewTile.tsx +159 -0
- package/src/components/GameHierarchy.tsx +3842 -0
- package/src/components/GameplaySessionTimeline.tsx +295 -0
- package/src/components/InspectionProjection.tsx +968 -0
- package/src/components/Inspector.tsx +233 -0
- package/src/components/InspectorCanvasPreview.tsx +35 -0
- package/src/components/InspectorFieldsSection.tsx +290 -0
- package/src/components/InspectorObjectPreview.tsx +57 -0
- package/src/components/InspectorStoriesSection.tsx +92 -0
- package/src/components/InspectorToolSection.tsx +96 -0
- package/src/components/InspectorTransformSection.tsx +245 -0
- package/src/components/LightExplorerPanel.tsx +433 -0
- package/src/components/MediaProperties.tsx +145 -0
- package/src/components/NonThreeAuthoringBootstrap.tsx +19 -0
- package/src/components/Object3DDocumentToolbar.css +100 -0
- package/src/components/Object3DDocumentToolbar.tsx +454 -0
- package/src/components/Object3DDocumentViewport.tsx +58 -0
- package/src/components/ProjectHeader.css +617 -0
- package/src/components/ProjectHeader.tsx +328 -0
- package/src/components/ProjectLayout.tsx +249 -0
- package/src/components/ReactCanvasControls.tsx +284 -0
- package/src/components/RootSelectionOverlay.tsx +3162 -0
- package/src/components/RootTextEditor.tsx +79 -0
- package/src/components/SaveStatus.tsx +70 -0
- package/src/components/StageHost.tsx +1954 -0
- package/src/components/StageOverlays.tsx +21 -0
- package/src/components/StartupErrorScreen.tsx +76 -0
- package/src/components/StartupLoadingScreen.tsx +65 -0
- package/src/components/StatsOverlay.tsx +78 -0
- package/src/components/SurfaceStateOverlay.tsx +24 -0
- package/src/components/ToolContributionSurfaces.tsx +65 -0
- package/src/components/ToolHost.tsx +370 -0
- package/src/components/ToolObject3DPreview.tsx +39 -0
- package/src/components/Toolbar.tsx +595 -0
- package/src/components/TransientHint.tsx +44 -0
- package/src/components/TransportStrip.tsx +174 -0
- package/src/components/VersionControlSection.tsx +470 -0
- package/src/components/VgaiLogo.css +83 -0
- package/src/components/VgaiLogo.tsx +35 -0
- package/src/components/ViewportControlsHint.tsx +60 -0
- package/src/components/ViewportFurniture.tsx +401 -0
- package/src/components/ViewportOverlay.tsx +145 -0
- package/src/components/ViewportOverlaysMenu.tsx +153 -0
- package/src/components/ViewportShadingMenu.tsx +300 -0
- package/src/components/ViewportViewMenu.tsx +101 -0
- package/src/components/WorkspaceDocumentSurface.tsx +204 -0
- package/src/components/WorkspaceUtilitySurface.tsx +22 -0
- package/src/components/WorktreeSwitcher.css +239 -0
- package/src/components/WorktreeSwitcher.tsx +890 -0
- package/src/components/account-documents.tsx +1162 -0
- package/src/components/asset-documents.tsx +812 -0
- package/src/components/asset-editor-persistence.ts +216 -0
- package/src/components/asset-selection-section.tsx +535 -0
- package/src/components/asset-thumbnails.tsx +312 -0
- package/src/components/asset-viewers/AudioViewer.tsx +201 -0
- package/src/components/asset-viewers/EntityModelDocument.tsx +122 -0
- package/src/components/asset-viewers/EnvironmentAssetDocument.tsx +440 -0
- package/src/components/asset-viewers/GenericJsonViewer.tsx +102 -0
- package/src/components/asset-viewers/ImageViewer.tsx +300 -0
- package/src/components/asset-viewers/JsonAssetDocument.tsx +93 -0
- package/src/components/asset-viewers/LiveModuleDocument.tsx +417 -0
- package/src/components/asset-viewers/LutAssetDocument.tsx +444 -0
- package/src/components/asset-viewers/ModelAssetDocument.tsx +105 -0
- package/src/components/asset-viewers/Object3DPreview.tsx +359 -0
- package/src/components/asset-viewers/OnlineAssetDetail.tsx +421 -0
- package/src/components/asset-viewers/PasteboardModuleDocument.tsx +67 -0
- package/src/components/asset-viewers/QuarksAssetDocument.tsx +526 -0
- package/src/components/asset-viewers/ShaderAssetDocument.tsx +743 -0
- package/src/components/asset-viewers/SourceAssetViewer.tsx +281 -0
- package/src/components/asset-viewers/SpritesheetSpriteView.tsx +102 -0
- package/src/components/asset-viewers/VideoViewer.tsx +101 -0
- package/src/components/asset-viewers/shader-source.ts +144 -0
- package/src/components/asset-workflow.css +731 -0
- package/src/components/board-guides.ts +150 -0
- package/src/components/compact-inspector.css +539 -0
- package/src/components/core-utilities.tsx +90 -0
- package/src/components/editor-notifications.css +40 -0
- package/src/components/engine-workspace.css +274 -0
- package/src/components/inspector-preview-section.tsx +238 -0
- package/src/components/inspector-property-grouping.ts +64 -0
- package/src/components/inspector-revert-label.ts +20 -0
- package/src/components/inspector-selection.ts +42 -0
- package/src/components/inspector-stories-gating.ts +171 -0
- package/src/components/inspector-transform-subject.ts +11 -0
- package/src/components/inspector-transform.ts +75 -0
- package/src/components/kind-documents.tsx +500 -0
- package/src/components/palette-action-publisher.tsx +140 -0
- package/src/components/primitives/DraftColorInput.tsx +74 -0
- package/src/components/product-shell.css +1829 -0
- package/src/components/project-tool-documents.tsx +410 -0
- package/src/components/scene-documents.tsx +223 -0
- package/src/components/stage-keyboard.tsx +37 -0
- package/src/components/stage-overlay-set.tsx +107 -0
- package/src/components/stage-presence-markers.ts +478 -0
- package/src/components/standard-viewport-dressing.ts +417 -0
- package/src/components/status-contributions.tsx +403 -0
- package/src/components/tool-documents.tsx +309 -0
- package/src/components/tool-schema-form.tsx +262 -0
- package/src/components/use-after-paint.ts +41 -0
- package/src/components/use-project-image-assets.ts +86 -0
- package/src/components/viewport-header-controls.css +90 -0
- package/src/components/viewport-surface-status.tsx +55 -0
- package/src/components/workspace-history.ts +32 -0
- package/src/components/workspace-static-panel-registry.tsx +152 -0
- package/src/components/workspace-surfaces.css +621 -0
- package/src/components/world-documents.tsx +586 -0
- package/src/components/world-overlay-gestures.ts +1694 -0
- package/src/composite-screenshot.ts +1641 -0
- package/src/console-sync.ts +131 -0
- package/src/constraint-helper.ts +338 -0
- package/src/content-entry-source-registry.ts +184 -0
- package/src/coverage/authoring-seam-evidence.ts +300 -0
- package/src/coverage/canvas-reveal.ts +192 -0
- package/src/coverage/design-time-surfaces.ts +101 -0
- package/src/coverage/live-seam-evidence.ts +11 -0
- package/src/coverage/ontology-invariants.ts +466 -0
- package/src/coverage/session-vitals.ts +501 -0
- package/src/coverage/system-seam-evidence.ts +72 -0
- package/src/crash-null-boundary.ts +36 -0
- package/src/creation-site-edit.ts +1479 -0
- package/src/creation-site-registry.ts +160 -0
- package/src/delegate-harness-registry.ts +143 -0
- package/src/document-context-registry.ts +177 -0
- package/src/document-open-registry.ts +200 -0
- package/src/document-preview-source.ts +20 -0
- package/src/document-renderer-session.ts +138 -0
- package/src/editor-api.ts +46 -0
- package/src/editor-chrome-capture.ts +139 -0
- package/src/editor-commands.ts +164 -0
- package/src/editor-console.ts +563 -0
- package/src/editor-current-view.ts +82 -0
- package/src/editor-document-probe.ts +881 -0
- package/src/editor-git-client.ts +115 -0
- package/src/editor-host-door.ts +462 -0
- package/src/editor-hotkeys.ts +842 -0
- package/src/editor-lease-view.ts +39 -0
- package/src/editor-lease.ts +415 -0
- package/src/editor-mode.ts +19 -0
- package/src/editor-notifications.ts +140 -0
- package/src/editor-presence.ts +563 -0
- package/src/editor-presentation-activity.ts +50 -0
- package/src/editor-presentation-notice.ts +42 -0
- package/src/editor-runtime.tsx +145 -0
- package/src/editor-server-response.ts +86 -0
- package/src/editor-session-attribution.ts +75 -0
- package/src/editor-session-mode.ts +54 -0
- package/src/editor-shell-store.ts +1304 -0
- package/src/editor-state-facets.ts +74 -0
- package/src/editor-styles.css +33 -0
- package/src/editor-view-presentation.ts +810 -0
- package/src/editor-viewport.ts +5302 -0
- package/src/entity-lod.ts +31 -0
- package/src/entity-object.ts +91 -0
- package/src/files/file-provider.ts +62 -0
- package/src/files/project-files.ts +264 -0
- package/src/finders/index.ts +136 -0
- package/src/finders/scenes-from-entrypoint-selection.ts +387 -0
- package/src/frame/bridge.tsx +1408 -0
- package/src/frame/product.ts +77 -0
- package/src/gameplay-dom-recording.ts +319 -0
- package/src/gameplay-export-state.ts +14 -0
- package/src/gameplay-replay.ts +417 -0
- package/src/gameplay-session-time.ts +9 -0
- package/src/gameplay-sessions.ts +204 -0
- package/src/harness-chat-types.ts +253 -0
- package/src/hierarchy-component-marks.ts +298 -0
- package/src/hierarchy-drop.ts +91 -0
- package/src/hierarchy-expansion-state.ts +80 -0
- package/src/hierarchy-header-slot.ts +52 -0
- package/src/hierarchy-internals.ts +197 -0
- package/src/hierarchy-kind-icon.ts +217 -0
- package/src/hierarchy-mark-reader.ts +73 -0
- package/src/hierarchy-menu-registry.ts +67 -0
- package/src/hierarchy-node-rows.ts +307 -0
- package/src/hierarchy-panel-view.ts +280 -0
- package/src/hierarchy-projection.ts +76 -0
- package/src/hierarchy-row-cache.ts +243 -0
- package/src/hierarchy-row-model.ts +308 -0
- package/src/hierarchy-rows.ts +11 -0
- package/src/hierarchy-walk.ts +86 -0
- package/src/history/editor-session.ts +25 -0
- package/src/history/history-commands.ts +147 -0
- package/src/history/history-delegate.ts +163 -0
- package/src/history/history-limit-notices.ts +43 -0
- package/src/history/history-service.ts +1173 -0
- package/src/history/persistence-coordinator.ts +35 -0
- package/src/history/project-file-history.ts +386 -0
- package/src/history/project-root-history-backends.ts +139 -0
- package/src/history/resource-registry.ts +209 -0
- package/src/history/snapshot-store.ts +103 -0
- package/src/history/source-history-backend.ts +546 -0
- package/src/history/types.ts +124 -0
- package/src/hmr-registration-group.ts +67 -0
- package/src/hmr-stable-react-context.ts +23 -0
- package/src/hotkeys.ts +188 -0
- package/src/inference-diagnostics.ts +69 -0
- package/src/initial-project.ts +80 -0
- package/src/inspection/active-subject.ts +578 -0
- package/src/inspection/active-surface.ts +142 -0
- package/src/inspection/compose.ts +1064 -0
- package/src/inspection/display.ts +170 -0
- package/src/inspection/document-subject.ts +109 -0
- package/src/inspection/game-subject.ts +85 -0
- package/src/inspection/model.ts +542 -0
- package/src/inspection/null-subject.ts +115 -0
- package/src/inspection/serialize.ts +357 -0
- package/src/inspection/use-active-inspection.ts +180 -0
- package/src/inspector-presentation.ts +201 -0
- package/src/inspector-section-registry.ts +221 -0
- package/src/instance-source-actions.ts +163 -0
- package/src/instanced-presentation.ts +164 -0
- package/src/js-heap.ts +71 -0
- package/src/key-actions.ts +91 -0
- package/src/keymap-presets.ts +401 -0
- package/src/layout-policy.ts +31 -0
- package/src/learn-links.ts +73 -0
- package/src/light-explorer-model.ts +134 -0
- package/src/live-canvas-frame.ts +55 -0
- package/src/live-document.ts +279 -0
- package/src/live-module-source.ts +230 -0
- package/src/live-session-registry.ts +220 -0
- package/src/live-transition.ts +633 -0
- package/src/manifest-project.ts +107 -0
- package/src/model-thumbnail.ts +523 -0
- package/src/native-selection-style.ts +202 -0
- package/src/object3d-document-write-policy.ts +137 -0
- package/src/packaged-runtime.ts +108 -0
- package/src/palettes/maya.palette.json +57 -0
- package/src/palettes/substance.palette.json +57 -0
- package/src/pasteboard-module.ts +81 -0
- package/src/performance-profiler.ts +367 -0
- package/src/performance-sources.ts +69 -0
- package/src/play-boot-phase.ts +145 -0
- package/src/presentation-surface.ts +248 -0
- package/src/project-adapter.ts +1143 -0
- package/src/project-asset-refresh.ts +26 -0
- package/src/project-declaration-refresh.ts +31 -0
- package/src/project-local-state.ts +118 -0
- package/src/project-manager.ts +243 -0
- package/src/project-module-changes.ts +163 -0
- package/src/project-module-split.ts +266 -0
- package/src/project-provenance.ts +115 -0
- package/src/project-ready.ts +42 -0
- package/src/project-session-reset.ts +67 -0
- package/src/project-shape.ts +68 -0
- package/src/project-tool-discovery.ts +66 -0
- package/src/project-tools.ts +107 -0
- package/src/project-work-types.ts +149 -0
- package/src/projection/three.ts +898 -0
- package/src/projection/types.ts +44 -0
- package/src/readiness.ts +113 -0
- package/src/reflection-probe-helper.ts +142 -0
- package/src/reported-play-state.ts +90 -0
- package/src/resolve-relative-specifier.ts +33 -0
- package/src/scene-document-plan.ts +315 -0
- package/src/scene-framing.ts +315 -0
- package/src/scene-live-open.ts +210 -0
- package/src/scene-view-fog.ts +89 -0
- package/src/scoped-game-css.ts +152 -0
- package/src/session-orphan-record.ts +193 -0
- package/src/session-tombstone.ts +126 -0
- package/src/settings/settings-provider.ts +82 -0
- package/src/settings-store.ts +345 -0
- package/src/shared-view-restore.ts +42 -0
- package/src/shell-store-door.ts +45 -0
- package/src/source-conflict.ts +122 -0
- package/src/spatial-handle-visuals.ts +332 -0
- package/src/stage-context.ts +341 -0
- package/src/stage-store-registry.ts +63 -0
- package/src/stale-chunk-recovery.ts +34 -0
- package/src/state-report-deferral.ts +73 -0
- package/src/storage/host-files-storage.ts +97 -0
- package/src/storage/http-storage.ts +174 -0
- package/src/storage/index.ts +75 -0
- package/src/storage/mem-storage.ts +149 -0
- package/src/storage/path-lock.ts +44 -0
- package/src/storage/paths.ts +26 -0
- package/src/storage/types.ts +116 -0
- package/src/stories/StoryComponentThumbnail.tsx +184 -0
- package/src/stories/StoryPreviewMount.tsx +306 -0
- package/src/stories/component-content-source.tsx +106 -0
- package/src/stories/component-states-source.ts +78 -0
- package/src/stories/compose-project-stories.ts +255 -0
- package/src/stories/pixi-story-model.ts +30 -0
- package/src/stories/prefabs-finder.ts +54 -0
- package/src/stories/prefabs-from-stories.ts +182 -0
- package/src/stories/project-story-discovery.ts +78 -0
- package/src/stories/project-story-regions.ts +24 -0
- package/src/stories/story-actions.ts +54 -0
- package/src/stories/story-capture-command.ts +124 -0
- package/src/stories/story-capture.ts +584 -0
- package/src/stories/story-declared-medium.ts +126 -0
- package/src/stories/story-discovery.ts +176 -0
- package/src/stories/story-document-openers.ts +36 -0
- package/src/stories/story-dom-runtime.ts +78 -0
- package/src/stories/story-grouping.ts +111 -0
- package/src/stories/story-lane.ts +88 -0
- package/src/stories/story-mount-turn.ts +27 -0
- package/src/stories/story-opener.ts +260 -0
- package/src/stories/story-pixi-preview.ts +408 -0
- package/src/stories/story-presentation.ts +215 -0
- package/src/stories/story-registry.ts +517 -0
- package/src/stories/story-three-preview.ts +807 -0
- package/src/stories/three-story-model.ts +96 -0
- package/src/story-three-preview-runtime.ts +56 -0
- package/src/surface-keyboard.ts +112 -0
- package/src/surface-state.ts +124 -0
- package/src/tab-bootstrap.js +628 -0
- package/src/tab-census.ts +198 -0
- package/src/tab-lifecycle-client.ts +196 -0
- package/src/theme-library.ts +844 -0
- package/src/theme-preference.ts +378 -0
- package/src/theme.css +3437 -0
- package/src/three-viewport/bone-selection-highlight.ts +119 -0
- package/src/three-viewport/camera-fit.ts +41 -0
- package/src/three-viewport/interactive-renderer.ts +132 -0
- package/src/three-viewport/selection-brackets.ts +310 -0
- package/src/three-viewport/selection-outline.ts +183 -0
- package/src/three-viewport/skeleton-helper.ts +61 -0
- package/src/three-viewport/source-color.ts +167 -0
- package/src/three-viewport/studio-environment.ts +96 -0
- package/src/three-viewport-presentation.ts +23 -0
- package/src/tool-contribution-play.ts +74 -0
- package/src/tool-loader.ts +1798 -0
- package/src/transform-mode-request.ts +57 -0
- package/src/transient-hint.ts +78 -0
- package/src/trigger-volume-helper.ts +116 -0
- package/src/ui-source/adapter-region-includes.ts +241 -0
- package/src/ui-source/ensure-import.ts +132 -0
- package/src/ui-source/file-region-resolver.ts +302 -0
- package/src/ui-source/inspect.ts +775 -0
- package/src/ui-source/oid-transform.ts +1845 -0
- package/src/ui-source/plan-csf-story.ts +196 -0
- package/src/ui-source/plan-extract-component.ts +421 -0
- package/src/ui-source/plan-fork-component.ts +621 -0
- package/src/ui-source/plan-named-style.ts +95 -0
- package/src/ui-source/plan-source-edit.ts +353 -0
- package/src/ui-source/r3f-contract-resolver.ts +327 -0
- package/src/ui-source/r3f-diagnostic-index.ts +165 -0
- package/src/ui-source/r3f-environment-binding.ts +133 -0
- package/src/ui-source/r3f-joint-binding.ts +251 -0
- package/src/ui-source/r3f-lod-binding.ts +91 -0
- package/src/ui-source/r3f-particle-binding.ts +235 -0
- package/src/ui-source/r3f-physics-binding.ts +483 -0
- package/src/ui-source/r3f-project-contracts.ts +444 -0
- package/src/ui-source/relative-import-specifier.ts +34 -0
- package/src/ui-source/reparent-guard.ts +544 -0
- package/src/ui-source/source-edit-request.ts +97 -0
- package/src/ui-source/source-write-backend.ts +618 -0
- package/src/ui-source/syntactic-prop-specs.ts +296 -0
- package/src/ui-source/tier-source-write-backend.ts +279 -0
- package/src/ui-source/ts-ast.ts +171 -0
- package/src/ui-source/utility-class-support.ts +169 -0
- package/src/ui-source/write-component-default.ts +133 -0
- package/src/ui-source/writer.ts +1989 -0
- package/src/viewport-activation-timings.ts +840 -0
- package/src/viewport-authoring-policy.ts +186 -0
- package/src/viewport-controls-hint.ts +57 -0
- package/src/viewport-door.ts +237 -0
- package/src/viewport-shading-boundary.ts +12 -0
- package/src/vite-error-surface.ts +141 -0
- package/src/wait-until.ts +37 -0
- package/src/workspace-areas.ts +156 -0
- package/src/workspace-aux-commands.ts +11 -0
- package/src/workspace-available-documents.ts +127 -0
- package/src/workspace-core-utilities.ts +31 -0
- package/src/workspace-document-ids.ts +59 -0
- package/src/workspace-document-registry.ts +612 -0
- package/src/workspace-document-restore.ts +145 -0
- package/src/workspace-host-commands.ts +141 -0
- package/src/workspace-persistence-gate.ts +40 -0
- package/src/workspace-play-utilities.ts +44 -0
- package/src/workspace-presets.ts +425 -0
- package/src/workspace-regions.ts +294 -0
- package/src/workspace-state-persistence.ts +536 -0
- package/src/workspace-static-panels.ts +73 -0
- package/src/workspace-status-registry.ts +121 -0
- package/src/workspace-style.ts +239 -0
- package/src/workspace-utility-commands.ts +74 -0
- package/src/workspace-utility-registry.ts +263 -0
- package/src/workspace-viewport-rect.ts +97 -0
- package/src/world-adoption.ts +115 -0
- package/src/world-document-routing.ts +104 -0
- package/vite-plugin-creation-site-write.ts +184 -0
- package/vite-plugin-creation-site.ts +80 -0
- package/vite-plugin-game-static.ts +303 -0
- package/vite-plugin-module-doorways.ts +336 -0
- package/vite-plugin-product-contributions.ts +197 -0
- package/vite-plugin-project-game-static.ts +125 -0
- package/vite-plugin-project-jsx-js.ts +65 -0
- package/vite-plugin-project-root-absolute-assets.ts +359 -0
- package/vite-plugin-shared-react.ts +420 -0
- package/vite-plugin-shared-three.ts +203 -0
- package/vite-plugin-ui-oid.ts +2106 -0
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../server/frame-proxy.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * THE ONE ORIGIN in front of two servers: the Code-OSS workbench and this\n * session. It listens IN THE SESSION PROCESS \u2014 no spawn, no second program \u2014\n * exactly as the loopback Colyseus server does.\n *\n * /$product-segment/*, /static, /vscode-remote, \u2026 the workbench (the REH)\n * everything else this session\n *\n * WHY A PROXY AND NOT TWO ORIGINS (measured 2026-09-19 against the REH). The\n * REH stamps its own CSP on the workbench page\n * (`createWorkbenchContentSecurityPolicy`, src/vs/server/node/webClientServer.ts):\n *\n * script-src 'self' 'unsafe-eval' blob: 'nonce-\u2026' <sha> http://<the REH's own authority>\n * connect-src 'self' ws: wss: https:\n *\n * The ONLY http origin on either list is the server's own. The editor's own\n * entry module and every project module are imported as module scripts from\n * this session, and every `/__editor/*` call is a fetch to it \u2014 so a session on\n * a second port is refused by `script-src` before CORS is ever consulted, and\n * its fetches by `connect-src` (which carries no `http:` at all). Relaxing that\n * means editing a core service; the proxy costs none.\n *\n * CROSS-ORIGIN ISOLATION. Every response carries COOP `same-origin` and COEP\n * `credentialless`, because the Blender build is `-sPROXY_TO_PTHREAD` and needs\n * `SharedArrayBuffer`. The REH has no cross-origin-isolation flag or header\n * hook of its own \u2014 `--enable-coi` is an ELECTRON-MAIN argument \u2014 so these\n * headers are this proxy's. `credentialless` rather than `require-corp` so the\n * workbench's own cross-origin loads keep working, the same choice `dev.ts`\n * makes.\n *\n * THE WEBVIEW'S SECOND ORIGIN. VS Code loads every webview from\n * `webviewExternalEndpoint`, which defaults to `https://{{uuid}}.vscode-cdn.net/\u2026`\n * \u2014 a host that does not exist for this fork and that a cross-origin-isolated\n * page could not embed anyway. `IWorkbenchConstructionOptions.webviewEndpoint`\n * is the supported door and the REH leaves it unset, so this rewrites the page\n * config to `http://{{uuid}}.localhost:<port>`. It has to be a SUBDOMAIN and\n * not this origin itself: `pre/index.html` refuses to start unless\n * `location.hostname` IS the hash of `{parentOrigin, salt}` or begins with it,\n * and `*.localhost` is the one wildcard host a browser resolves to loopback\n * with no DNS. Two consequences handled here: the webview is genuinely\n * cross-origin, so its responses need CORP `cross-origin` (without it the frame\n * dies as `corp-not-same-origin-after-defaulted-to-same-origin-by-coep`, which\n * Chrome renders as \"refused to connect\" and which cost an hour); and the REH's\n * own `frame-src 'self' https://*.vscode-cdn.net` is widened on the way through.\n *\n * COMPRESSION IS THIS PROXY'S JOB, and it is the reason the frame's page weight\n * is not the workbench's disk size. U3 measured `transferSize ===\n * decodedBodySize` on every entry of a 41.75 MB page: the REH writes no\n * `content-encoding` at all, and neither did this proxy. It negotiates\n * `accept-encoding` now \u2014 brotli, then gzip \u2014 over text, JavaScript, CSS, JSON\n * and wasm only; everything else is byte-for-byte.\n */\n\nimport {\n createServer,\n request as httpRequest,\n type IncomingMessage,\n type Server,\n type ServerResponse,\n} from 'node:http';\nimport { connect as netConnect, type Socket } from 'node:net';\nimport { basename } from 'node:path';\nimport type { Transform } from 'node:stream';\nimport { pipeline } from 'node:stream/promises';\nimport { createBrotliCompress, createGzip, constants as zlibConstants } from 'node:zlib';\n\nexport interface FrameProxyOptions {\n /** The one port a person opens: this proxy's. */\n readonly port: number;\n /** The Code-OSS workbench (REH) behind it. */\n readonly upstreamPort: number;\n /** This session's own port. */\n readonly sessionPort: number;\n /** The project the workbench opens; its folder name is the `?project=` id. */\n readonly projectRoot: string;\n /**\n * Which way round the PRODUCT paints, from its own declaration\n * (`package.json#vgai.product.colorTheme`, `@volter/editor-sdk/session/\n * product-locator`), or `null` when this session could not resolve a product\n * \u2014 in which case the page keeps the workbench's own web default and the\n * `served-modules` door is what says why there is no product.\n */\n readonly colorTheme: 'dark' | 'light' | null;\n readonly log: (line: string) => void;\n}\n\nexport interface FrameProxy {\n /** The url to open: this origin, on this project. */\n readonly url: string;\n close(): Promise<void>;\n}\n\n/**\n * The workbench's own paths. `<product segment>` (`/oss-dev` out of a source\n * checkout, the quality segment out of a build \u2014 `getServerProductSegment`) is\n * DISCOVERED at boot rather than spelled here, because the page states it.\n */\nconst VSCODE_PATH_BASE =\n '^\\\\/$|^\\\\/(static|favicon|manifest\\\\.json|code-192|code-512|callback|oauth|out|extensions|builtin|remote|vscode-remote|_static|web-extension|version|delay-shutdown)';\n\n/** What is worth compressing. Everything else goes through byte for byte. */\nconst COMPRESSIBLE =\n /^(?:text\\/|application\\/(?:javascript|json|wasm|manifest\\+json)|image\\/svg\\+xml)/;\n\n/** `"`-encoded HTML attribute value \u2194 JSON, the way webClientServer writes it. */\nconst decodeAttr = (value: string): string =>\n value\n .replaceAll('"', '\"')\n .replaceAll(''', \"'\")\n .replaceAll('<', '<')\n .replaceAll('>', '>')\n .replaceAll('&', '&');\nconst encodeAttr = (value: string): string =>\n value\n .replaceAll('&', '&')\n .replaceAll('\"', '"')\n .replaceAll(\"'\", ''')\n .replaceAll('<', '<')\n .replaceAll('>', '>');\n\n/**\n * Which encoding the browser asked for, in this proxy's order of preference.\n * `null` means \"send it as it is\" \u2014 no `accept-encoding`, or none we write.\n */\nfunction negotiateEncoding(header: string | string[] | undefined): 'br' | 'gzip' | null {\n const accepted = (Array.isArray(header) ? header.join(',') : (header ?? '')).toLowerCase();\n if (accepted.includes('br')) return 'br';\n if (accepted.includes('gzip')) return 'gzip';\n return null;\n}\n\n/**\n * Quality 5 rather than brotli's default 11. The default spends seconds on a\n * multi-megabyte workbench chunk and this proxy compresses on the request path,\n * with the person waiting; 5 is the knee where the ratio is nearly the same and\n * the CPU is not.\n */\nfunction compressor(encoding: 'br' | 'gzip'): Transform {\n return encoding === 'br'\n ? createBrotliCompress({ params: { [zlibConstants.BROTLI_PARAM_QUALITY]: 5 } })\n : createGzip({ level: 6 });\n}\n\nexport async function startFrameProxy(options: FrameProxyOptions): Promise<FrameProxy> {\n const { port, upstreamPort, sessionPort, projectRoot, colorTheme, log } = options;\n const upstream = { host: '127.0.0.1', port: upstreamPort };\n const session = { host: '127.0.0.1', port: sessionPort };\n const upstreamOrigin = `http://${upstream.host}:${upstream.port}`;\n const selfOrigin = `http://127.0.0.1:${port}`;\n const webviewOriginTemplate = `http://{{uuid}}.localhost:${port}`;\n const webviewOriginPattern = `http://*.localhost:${port}`;\n const projectId = basename(projectRoot);\n\n const isolation: Record<string, string> = {\n 'Cross-Origin-Opener-Policy': 'same-origin',\n 'Cross-Origin-Embedder-Policy': 'credentialless',\n 'Cross-Origin-Resource-Policy': 'cross-origin',\n };\n\n let vscodePath = new RegExp(VSCODE_PATH_BASE);\n\n /**\n * THE DOCUMENT'S OWN COLOUR, before it has any styles at all.\n *\n * `initialColorTheme` below fixes the WORKBENCH's first paint, and that is not the page's\n * first paint: measured 2026-09-21 on the live release, `html` and `body` compute\n * `rgba(0, 0, 0, 0)` \u2014 upstream's `workbench.css` styles `.monaco-workbench` and nothing\n * above it \u2014 so from the document's first frame until `Workbench.renderWorkbench` builds that\n * element, the canvas is the BROWSER's default, which is white. One frame of it survived F1's\n * first reading (frame 0 at 0.0 s white, frame 1 at 0.5 s already the product's splash).\n *\n * `color-scheme` is the platform's own answer to exactly that question, and it is why this is\n * a meta tag and not a background rule: it hands the browser the product's `dark`/`light`\n * declaration UNCHANGED and lets it paint its own dark canvas, so nothing here becomes a\n * second author of a palette value. The workbench sets `color-scheme` itself once its theme\n * loads; this only fills the window before that.\n */\n const colorSchemeMeta = (html: string): string =>\n colorTheme === null\n ? html\n : html.replace('<head>', `<head>\\n\\t\\t<meta name=\"color-scheme\" content=\"${colorTheme}\">`);\n\n /** Rewrite the workbench page's own configuration for THIS origin.\n *\n * - `remoteAuthority` needs NOTHING here, and that is worth stating: the REH\n * derives it from the request's HOST header (measured \u2014 a direct fetch to\n * the REH reports its own authority, the same fetch through this proxy\n * reports this one), so forwarding the original Host is already what makes\n * the management websocket, the `vscode-remote://` file system, every\n * `vscode-remote-resource` url AND the page's own CSP address this origin.\n * - `webviewEndpoint` is set (see the header).\n * - `initialColorTheme` is set (see below). */\n const patchWorkbenchConfig = (html: string): string => {\n const webBaseUrl =\n html.match(/id=\"vscode-workbench-web-base-url\" data-settings=\"([^\"]*)\"/)?.[1] ?? '/static';\n return html.replace(\n /(id=\"vscode-workbench-web-configuration\" data-settings=\")([^\"]*)(\")/,\n (_all, head: string, value: string, tail: string) => {\n const config = JSON.parse(decodeAttr(value)) as Record<string, unknown>;\n config['webviewEndpoint'] =\n `${webviewOriginTemplate}${decodeAttr(webBaseUrl)}/out/vs/workbench/contrib/webview/browser/pre/`;\n // THE FIRST PAINT IS THE PRODUCT'S, NOT THE WEB DEFAULT'S. Measured\n // 2026-09-21 against both releases: with nothing here the page's first\n // frame is LIGHT \u2014 `workbenchThemeService` falls back to\n // `isWeb ? ColorScheme.LIGHT : ColorScheme.DARK` when it has no stored\n // theme \u2014 so the product's opening cover, every colour of which is a\n // `--vscode-*` variable by design, was WHITE from the first frame until\n // the real theme loaded seconds later, and then flipped.\n //\n // `IWorkbenchConstructionOptions.initialColorTheme` is the supported\n // door and the REH leaves it unset, the same shape as `webviewEndpoint`\n // above. It has to be HERE and not in a setting or an extension: the\n // theme service reads it in its constructor, before any extension is\n // registered, which is exactly the window the cover lives in. The\n // themeType strings ARE `ColorScheme`'s values (`dark`, `light`).\n //\n // The colour comes from the PRODUCT's own declaration and nothing else\n // (`package.json#vgai.product.colorTheme`), and it is a FALLBACK in the\n // workbench's own order: a person who has already picked a theme has it\n // in storage, and `ColorThemeData.fromStorageData` is preferred over\n // this on every open after the first.\n if (colorTheme !== null) config['initialColorTheme'] = { themeType: colorTheme };\n return head + encodeAttr(JSON.stringify(config)) + tail;\n },\n );\n };\n\n const sendText = (res: ServerResponse, status: number, body: string, type: string): void => {\n res.writeHead(status, { ...isolation, 'content-type': type, 'cache-control': 'no-store' });\n res.end(body);\n };\n\n /** An unknown `?project=` says so by name rather than opening an empty workbench. */\n const refuseProject = (res: ServerResponse, id: string): void => {\n sendText(\n res,\n 404,\n `<!doctype html><meta charset=\"utf-8\"><title>no such project</title>` +\n `<body style=\"font:14px system-ui;padding:2rem\"><h1>No project \"${encodeAttr(id)}\" is open here.</h1>` +\n `<p>This workbench is the session for <code>${encodeAttr(projectRoot)}</code>, whose id is ` +\n `<code>${encodeAttr(projectId)}</code>.</p>`,\n 'text/html; charset=utf-8',\n );\n };\n\n const handleRequest = (req: IncomingMessage, res: ServerResponse): void => {\n const url = new URL(req.url ?? '/', selfOrigin);\n const pathname = url.pathname;\n // THE ONE HOSTED BOOT PARAM, ONTO THE WORKBENCH'S OWN. `?project=<id>` is\n // what a link to the vgai editor carries; the workbench's own boot contract\n // is `?folder=<path>` (`WorkspaceProvider.create`, which reads a bare path\n // against `remoteAuthority`). This is a REDIRECT and not an injected\n // `folderUri`, and the reason was measured (2026-09-19): under this shape\n // the workbench page is ALSO the vgai editor's page, and the editor's boot\n // refuses `?project=` on a session-backed surface by name \u2014 \"project\n // identity does NOT live in the URL on a local editor\"\n // (`assertNoRemovedBootParams`, packages/editor/src/boot-routing.ts). The\n // redirect hands the page a url both halves accept.\n if (pathname === '/') {\n const id = url.searchParams.get('project');\n if (id && !url.searchParams.has('folder') && !url.searchParams.has('workspace')) {\n if (id !== projectId) {\n refuseProject(res, id);\n return;\n }\n const next = new URL(url);\n next.searchParams.delete('project');\n next.searchParams.set('folder', projectRoot);\n res.writeHead(302, {\n ...isolation,\n location: `${next.pathname}${next.search}`,\n 'cache-control': 'no-store',\n });\n res.end();\n return;\n }\n }\n\n const target = vscodePath.test(pathname) ? upstream : session;\n const encoding = negotiateEncoding(req.headers['accept-encoding']);\n const forward = httpRequest(\n { ...target, method: req.method, path: req.url, headers: req.headers },\n (answer) => {\n const headers: Record<string, string | string[]> = {};\n for (const [key, value] of Object.entries(answer.headers)) {\n if (value !== undefined) headers[key] = value;\n }\n delete headers['cross-origin-opener-policy'];\n delete headers['cross-origin-embedder-policy'];\n delete headers['cross-origin-resource-policy'];\n // The webview's second origin, onto the REH's own `frame-src`.\n const csp = headers['content-security-policy'];\n if (typeof csp === 'string' && csp.includes('frame-src')) {\n // GLTFLoader fetches local blob textures; img-src alone cannot\n // authorize that fetch. Media previews use local blobs as well.\n headers['content-security-policy'] = csp\n .replace(/frame-src([^;]*)/, `frame-src$1 ${webviewOriginPattern}`)\n .replace(/connect-src([^;]*)/, 'connect-src$1 blob:')\n .replace(/media-src([^;]*)/, 'media-src$1 blob:');\n }\n const type = String(headers['content-type'] ?? '');\n const alreadyEncoded = headers['content-encoding'] !== undefined;\n // The workbench HTML and its config spell the upstream's OWN origin\n // into absolute script and endpoint URLs; through this origin those are\n // cross-origin and CORS refuses them. Text bodies are re-spelled here,\n // which needs the whole body \u2014 so this branch buffers and the streaming\n // one below does not.\n if (/text\\/html|javascript|json/.test(type) && !alreadyEncoded) {\n const chunks: Buffer[] = [];\n answer.on('data', (chunk: Buffer) => chunks.push(chunk));\n answer.on('end', () => {\n let body = Buffer.concat(chunks)\n .toString('utf8')\n .replaceAll(upstreamOrigin, selfOrigin);\n if (pathname === '/' && target === upstream && /text\\/html/.test(type)) {\n body = colorSchemeMeta(patchWorkbenchConfig(body));\n }\n delete headers['content-length'];\n const raw = Buffer.from(body, 'utf8');\n if (encoding === null) {\n res.writeHead(answer.statusCode ?? 502, {\n ...headers,\n ...isolation,\n 'content-length': raw.byteLength,\n });\n res.end(raw);\n return;\n }\n res.writeHead(answer.statusCode ?? 502, {\n ...headers,\n ...isolation,\n 'content-encoding': encoding,\n vary: 'accept-encoding',\n });\n const stream = compressor(encoding);\n stream.pipe(res);\n stream.end(raw);\n });\n return;\n }\n if (encoding !== null && !alreadyEncoded && COMPRESSIBLE.test(type)) {\n delete headers['content-length'];\n res.writeHead(answer.statusCode ?? 502, {\n ...headers,\n ...isolation,\n 'content-encoding': encoding,\n vary: 'accept-encoding',\n });\n void pipeline(answer, compressor(encoding), res).catch(() => res.destroy());\n return;\n }\n res.writeHead(answer.statusCode ?? 502, { ...headers, ...isolation });\n answer.pipe(res);\n },\n );\n forward.on('error', (error) => {\n if (!res.headersSent) {\n sendText(\n res,\n 502,\n `upstream ${target.port}: ${error.message}`,\n 'text/plain; charset=utf-8',\n );\n } else res.destroy();\n });\n // A browser that goes away takes its upstream request with it \u2014 otherwise a\n // dead tab's event stream stays open, this session keeps that tab blessed,\n // and EVERY later tab lands on `/__editor/tab-yielded` (measured\n // 2026-09-19 in the session journal).\n res.on('close', () => forward.destroy());\n req.pipe(forward);\n };\n\n const handleUpgrade = (req: IncomingMessage, socket: Socket, head: Buffer): void => {\n const target = vscodePath.test(new URL(req.url ?? '/', selfOrigin).pathname)\n ? upstream\n : session;\n const proxied = netConnect(target.port, target.host, () => {\n proxied.write(\n `${req.method} ${req.url} HTTP/${req.httpVersion}\\r\\n` +\n Object.entries(req.headers)\n .map(([key, value]) => `${key}: ${Array.isArray(value) ? value.join(', ') : value}`)\n .join('\\r\\n') +\n '\\r\\n\\r\\n',\n );\n if (head.length) proxied.write(head);\n socket.pipe(proxied).pipe(socket);\n });\n proxied.on('error', () => socket.destroy());\n socket.on('error', () => proxied.destroy());\n };\n\n /**\n * Wait for the upstream to report its shape before accepting requests. The REH serves its static\n * assets under a PRODUCT SEGMENT (`/oss-dev/static` out of a source checkout,\n * `/<quality>/static` out of a build), and this cannot route without knowing\n * it. The page itself states it in `vscode-workbench-web-base-url`, so it is\n * read rather than spelled.\n */\n const discoverUpstream = (): Promise<string | null> =>\n new Promise((done) => {\n const probe = httpRequest({ ...upstream, path: '/' }, (answer) => {\n const chunks: Buffer[] = [];\n answer.on('data', (chunk: Buffer) => chunks.push(chunk));\n answer.on('end', () => {\n const html = Buffer.concat(chunks).toString('utf8');\n const base = html.match(\n /id=\"vscode-workbench-web-base-url\" data-settings=\"([^\"]*)\"/,\n )?.[1];\n done(answer.statusCode === 200 && base ? decodeAttr(base).replace(/\\/static\\/?$/, '') : null);\n });\n });\n probe.on('error', () => done(null));\n probe.setTimeout(3_000, () => {\n probe.destroy();\n done(null);\n });\n probe.end();\n });\n\n // A listening socket is not proof that Code-OSS can serve its HTML yet.\n // Never expose a half-configured proxy: unknown prefixes route native JS to\n // the session server and produce a blank workbench with script 404s.\n const discoveryDeadline = Date.now() + 30_000;\n let segment: string | null = null;\n do {\n segment = await discoverUpstream();\n if (segment !== null) break;\n await new Promise(resolve => setTimeout(resolve, 100));\n } while (Date.now() < discoveryDeadline);\n if (segment === null) throw new Error(\n `Code-OSS on port ${upstream.port} did not report its workbench asset base within 30s; refusing to start an incorrectly routed proxy.`,\n );\n if (segment) {\n vscodePath = new RegExp(\n `${VSCODE_PATH_BASE}|^${segment.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')}(\\\\/|$)`,\n );\n log(`frame: workbench product segment ${segment}`);\n }\n\n // ONE handler, TWO loopback listeners \u2014 127.0.0.1 and ::1. The webview's\n // origin is a `*.localhost` subdomain (see the header), and macOS answers\n // `<anything>.localhost` with the IPv6 `::1` as well as 127.0.0.1\n // (`dscacheutil -q host -a name x.localhost`); a browser that picks the IPv6\n // address finds nothing listening and the webview iframe fails as\n // `chrome-error://chromewebdata` with no message at all \u2014 measured\n // 2026-09-19 against a proxy bound to 127.0.0.1 alone.\n const servers: Server[] = [];\n await new Promise<void>((ready, failed) => {\n let settled = false;\n for (const host of ['127.0.0.1', '::1']) {\n const one = createServer(handleRequest);\n one.on('upgrade', handleUpgrade);\n one.on('error', (error) => {\n if (host === '::1') {\n log(\n `frame: no IPv6 loopback listener (${error.message}); webviews need one if the browser resolves *.localhost to ::1`,\n );\n return;\n }\n if (!settled) {\n settled = true;\n failed(error);\n }\n });\n servers.push(one);\n one.listen(port, host, () => {\n if (!settled) {\n settled = true;\n ready();\n }\n });\n }\n });\n\n return {\n url: `${selfOrigin}/?project=${encodeURIComponent(projectId)}`,\n close: async () => {\n await Promise.all(\n servers.map(\n (one) =>\n new Promise<void>((done) => {\n one.close(() => done());\n one.closeAllConnections?.();\n }),\n ),\n );\n },\n };\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import {
|
|
2
|
+
spawnOpener
|
|
3
|
+
} from "./chunk-PSILUMGS.js";
|
|
4
|
+
|
|
5
|
+
// server/open-browser.ts
|
|
6
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
7
|
+
function isWSL() {
|
|
8
|
+
if (process.platform !== "linux") return false;
|
|
9
|
+
try {
|
|
10
|
+
return readFileSync("/proc/version", "utf8").toLowerCase().includes("microsoft");
|
|
11
|
+
} catch {
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
var AGENT_WORKTREE_MARKER = "/.claude/worktrees/";
|
|
16
|
+
var TAB_BACKGROUND_ENV = "VGAI_TAB_BACKGROUND";
|
|
17
|
+
function shouldOpenTabInBackground(input) {
|
|
18
|
+
const override = input.env[TAB_BACKGROUND_ENV];
|
|
19
|
+
if (override !== void 0 && override !== "") {
|
|
20
|
+
return override !== "0" && override.toLowerCase() !== "false";
|
|
21
|
+
}
|
|
22
|
+
const root = input.projectRoot;
|
|
23
|
+
if (root === null || root === void 0 || root === "") return false;
|
|
24
|
+
return root.replaceAll("\\", "/").includes(AGENT_WORKTREE_MARKER);
|
|
25
|
+
}
|
|
26
|
+
function openBrowserUrl(browserUrl, options) {
|
|
27
|
+
if (process.platform === "darwin") {
|
|
28
|
+
spawnOpener("open", options?.background === true ? ["-g", browserUrl] : [browserUrl]);
|
|
29
|
+
} else if (process.platform === "win32") {
|
|
30
|
+
spawnOpener("cmd", ["/c", "start", "", browserUrl]);
|
|
31
|
+
} else if (isWSL()) {
|
|
32
|
+
const interop = process.env["WSL_INTEROP"];
|
|
33
|
+
if (interop && !existsSync(interop)) {
|
|
34
|
+
console.error(
|
|
35
|
+
`[vgai-editor] cannot open a browser from this shell \u2014 the WSL interop socket (${interop}) is gone, so Windows executables cannot launch. Open ${browserUrl} from any regular terminal or browser instead.`
|
|
36
|
+
);
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
spawnOpener(
|
|
40
|
+
"explorer.exe",
|
|
41
|
+
[browserUrl],
|
|
42
|
+
() => spawnOpener("powershell.exe", ["-NoProfile", "-Command", `Start-Process '${browserUrl}'`])
|
|
43
|
+
);
|
|
44
|
+
} else {
|
|
45
|
+
spawnOpener("xdg-open", [browserUrl]);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
export {
|
|
49
|
+
TAB_BACKGROUND_ENV,
|
|
50
|
+
openBrowserUrl,
|
|
51
|
+
shouldOpenTabInBackground
|
|
52
|
+
};
|
|
53
|
+
//# sourceMappingURL=open-browser.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../server/open-browser.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * THE platform browser hand-off \u2014 the one open ladder (darwin / win32 / WSL\n * explorer.exe\u2192powershell.exe / xdg-open), owned here because the editor\n * server must keep maintaining its tab (tab-bijection self-heal,\n * ensure-driven opens) long after any launching CLI process has exited. The\n * CLI imports this module; it does not carry its own copy \u2014 the old\n * \"no editor-package dependency\" premise stopped being true when the CLI\n * grew six editor-server imports and an `@vgai/editor` dependency, and the\n * two ladders had already drifted (`-g` background support on one side\n * only).\n */\n\nimport { existsSync, readFileSync } from 'node:fs';\nimport { spawnOpener } from './spawn-opener';\n\nfunction isWSL(): boolean {\n if (process.platform !== 'linux') return false;\n try {\n return readFileSync('/proc/version', 'utf8').toLowerCase().includes('microsoft');\n } catch {\n return false;\n }\n}\n\n/**\n * The path segment that identifies a session rooted in an AGENT's git\n * worktree. Agents take one of these before they write code\n * (docs/LOCAL-DEV.md), so it is the one honest, always-present signal that a\n * tab belongs to a background builder rather than to the person at the\n * keyboard.\n */\nconst AGENT_WORKTREE_MARKER = '/.claude/worktrees/';\n\n/** Env override for the placement decision (`1` background, `0` foreground). */\nexport const TAB_BACKGROUND_ENV = 'VGAI_TAB_BACKGROUND';\n\nexport interface TabOpenPlacementInput {\n /**\n * The session's project root, ALREADY symlink-resolved\n * (`canonicalProjectRoot`) \u2014 the marker check is on the real path, because\n * a worktree reached through a symlinked parent would otherwise read as a\n * human checkout.\n */\n readonly projectRoot: string | null | undefined;\n readonly env: Readonly<Record<string, string | undefined>>;\n}\n\n/**\n * Decide whether this session's ONE tab should open behind the human's\n * current window instead of stealing focus.\n *\n * Measured (owner, three times in one day): every builder/reviewer `vgai edit`\n * auto-opened a FOREGROUND tab on the one display, so the owner kept ending up\n * looking at an agent's session instead of their own. A session rooted in an\n * agent worktree therefore opens in the BACKGROUND; the human flow \u2014 a normal\n * checkout, or a scaffolded game anywhere else \u2014 keeps foreground, because for\n * a person the whole point of `vgai edit` is that the editor comes up in front\n * of them.\n *\n * `VGAI_TAB_BACKGROUND` overrides in BOTH directions: `1` backgrounds a tab\n * that would have been foreground, `0` foregrounds one inside a worktree (an\n * agent deliberately showing the owner something).\n *\n * Pure: the caller supplies the resolved root and the environment.\n */\nexport function shouldOpenTabInBackground(input: TabOpenPlacementInput): boolean {\n const override = input.env[TAB_BACKGROUND_ENV];\n if (override !== undefined && override !== '') {\n return override !== '0' && override.toLowerCase() !== 'false';\n }\n const root = input.projectRoot;\n if (root === null || root === undefined || root === '') return false;\n return root.replaceAll('\\\\', '/').includes(AGENT_WORKTREE_MARKER);\n}\n\nexport interface OpenBrowserUrlOptions {\n /**\n * Open behind the frontmost window instead of activating the browser.\n * Honored on macOS only (`open -g`) \u2014 Windows `start` and `xdg-open` have no\n * portable equivalent, so every other platform keeps its existing\n * focus-stealing behavior rather than getting a half-kept promise.\n */\n readonly background?: boolean;\n}\n\n/** Best-effort open of `browserUrl` in the platform default browser. */\nexport function openBrowserUrl(browserUrl: string, options?: OpenBrowserUrlOptions): void {\n if (process.platform === 'darwin') {\n // `-g` = do not bring the opened application to the foreground. The tab\n // still opens, still loads, and still registers with the bijection \u2014 it\n // just does not seize the display.\n spawnOpener('open', options?.background === true ? ['-g', browserUrl] : [browserUrl]);\n } else if (process.platform === 'win32') {\n // `start` is a cmd builtin, not an executable \u2014 it needs cmd itself.\n spawnOpener('cmd', ['/c', 'start', '', browserUrl]);\n } else if (isWSL()) {\n // WSL has no display of its own; hand the URL to the Windows side. A dead\n // interop socket (stale background session) means no Windows .exe can\n // launch \u2014 name it instead of failing silently.\n const interop = process.env['WSL_INTEROP'];\n if (interop && !existsSync(interop)) {\n console.error(\n `[vgai-editor] cannot open a browser from this shell \u2014 the WSL interop socket ` +\n `(${interop}) is gone, so Windows executables cannot launch. Open ${browserUrl} ` +\n 'from any regular terminal or browser instead.',\n );\n return;\n }\n // Explorer is the direct Windows shell-association path. Start-Process\n // can exit zero even when a transient browser process fails to forward\n // the URL to the existing browser, so PowerShell is the fallback.\n spawnOpener('explorer.exe', [browserUrl], () =>\n spawnOpener('powershell.exe', ['-NoProfile', '-Command', `Start-Process '${browserUrl}'`]),\n );\n } else {\n spawnOpener('xdg-open', [browserUrl]);\n }\n}\n"],
|
|
5
|
+
"mappings": ";;;;;AAYA,SAAS,YAAY,oBAAoB;AAGzC,SAAS,QAAiB;AACxB,MAAI,QAAQ,aAAa,QAAS,QAAO;AACzC,MAAI;AACF,WAAO,aAAa,iBAAiB,MAAM,EAAE,YAAY,EAAE,SAAS,WAAW;AAAA,EACjF,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AASA,IAAM,wBAAwB;AAGvB,IAAM,qBAAqB;AA+B3B,SAAS,0BAA0B,OAAuC;AAC/E,QAAM,WAAW,MAAM,IAAI,kBAAkB;AAC7C,MAAI,aAAa,UAAa,aAAa,IAAI;AAC7C,WAAO,aAAa,OAAO,SAAS,YAAY,MAAM;AAAA,EACxD;AACA,QAAM,OAAO,MAAM;AACnB,MAAI,SAAS,QAAQ,SAAS,UAAa,SAAS,GAAI,QAAO;AAC/D,SAAO,KAAK,WAAW,MAAM,GAAG,EAAE,SAAS,qBAAqB;AAClE;AAaO,SAAS,eAAe,YAAoB,SAAuC;AACxF,MAAI,QAAQ,aAAa,UAAU;AAIjC,gBAAY,QAAQ,SAAS,eAAe,OAAO,CAAC,MAAM,UAAU,IAAI,CAAC,UAAU,CAAC;AAAA,EACtF,WAAW,QAAQ,aAAa,SAAS;AAEvC,gBAAY,OAAO,CAAC,MAAM,SAAS,IAAI,UAAU,CAAC;AAAA,EACpD,WAAW,MAAM,GAAG;AAIlB,UAAM,UAAU,QAAQ,IAAI,aAAa;AACzC,QAAI,WAAW,CAAC,WAAW,OAAO,GAAG;AACnC,cAAQ;AAAA,QACN,sFACM,OAAO,yDAAyD,UAAU;AAAA,MAElF;AACA;AAAA,IACF;AAIA;AAAA,MAAY;AAAA,MAAgB,CAAC,UAAU;AAAA,MAAG,MACxC,YAAY,kBAAkB,CAAC,cAAc,YAAY,kBAAkB,UAAU,GAAG,CAAC;AAAA,IAC3F;AAAA,EACF,OAAO;AACL,gBAAY,YAAY,CAAC,UAAU,CAAC;AAAA,EACtC;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
// server/process-shutdown.ts
|
|
2
|
+
import { execFileSync } from "node:child_process";
|
|
3
|
+
var DEFAULT_STOP_GRACE_MS = 5e3;
|
|
4
|
+
function stopProcess(proc, graceMs = DEFAULT_STOP_GRACE_MS) {
|
|
5
|
+
const signalGroup = (signal) => {
|
|
6
|
+
if (proc.pid === void 0) return;
|
|
7
|
+
if (process.platform === "win32") {
|
|
8
|
+
try {
|
|
9
|
+
execFileSync("taskkill", ["/PID", String(proc.pid), "/T", "/F"], { stdio: "ignore" });
|
|
10
|
+
} catch {
|
|
11
|
+
}
|
|
12
|
+
try {
|
|
13
|
+
proc.kill(signal);
|
|
14
|
+
} catch {
|
|
15
|
+
}
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
try {
|
|
19
|
+
process.kill(-proc.pid, signal);
|
|
20
|
+
} catch {
|
|
21
|
+
try {
|
|
22
|
+
proc.kill(signal);
|
|
23
|
+
} catch {
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
return new Promise((resolve) => {
|
|
28
|
+
if (proc.exitCode !== null || proc.signalCode !== null) {
|
|
29
|
+
signalGroup("SIGKILL");
|
|
30
|
+
resolve();
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
const timer = setTimeout(() => {
|
|
34
|
+
signalGroup("SIGKILL");
|
|
35
|
+
resolve();
|
|
36
|
+
}, graceMs);
|
|
37
|
+
proc.on("exit", () => {
|
|
38
|
+
clearTimeout(timer);
|
|
39
|
+
signalGroup("SIGKILL");
|
|
40
|
+
resolve();
|
|
41
|
+
});
|
|
42
|
+
signalGroup("SIGTERM");
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
function closeHttpServer(server) {
|
|
46
|
+
return new Promise((resolve, reject) => {
|
|
47
|
+
if (!server.listening) {
|
|
48
|
+
server.closeAllConnections?.();
|
|
49
|
+
resolve();
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
server.close((error) => error ? reject(error) : resolve());
|
|
53
|
+
server.closeAllConnections?.();
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
function createProcessShutdown(options) {
|
|
57
|
+
const taskTimeoutMs = options.taskTimeoutMs ?? 4e3;
|
|
58
|
+
const timeoutMs = options.timeoutMs ?? 4800;
|
|
59
|
+
const exit = options.exit ?? ((code) => process.exit(code));
|
|
60
|
+
const log = options.log ?? ((message) => process.stderr.write(`${message}
|
|
61
|
+
`));
|
|
62
|
+
const journal = options.journal ?? (() => {
|
|
63
|
+
});
|
|
64
|
+
let pending = null;
|
|
65
|
+
const runTask = async (task) => {
|
|
66
|
+
const startedAt = Date.now();
|
|
67
|
+
let timer;
|
|
68
|
+
try {
|
|
69
|
+
const outcome = await Promise.race([
|
|
70
|
+
Promise.resolve(task.run()).then(() => "done"),
|
|
71
|
+
new Promise((resolve) => {
|
|
72
|
+
timer = setTimeout(() => resolve("timeout"), taskTimeoutMs);
|
|
73
|
+
})
|
|
74
|
+
]);
|
|
75
|
+
if (outcome === "timeout") {
|
|
76
|
+
journal({ task: task.name, outcome: "timeout", ms: Date.now() - startedAt });
|
|
77
|
+
throw new Error(`${task.name} did not finish within ${taskTimeoutMs}ms`);
|
|
78
|
+
}
|
|
79
|
+
} catch (error) {
|
|
80
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
81
|
+
if (!detail.endsWith(`within ${taskTimeoutMs}ms`))
|
|
82
|
+
journal({ task: task.name, outcome: "failed", ms: Date.now() - startedAt, detail });
|
|
83
|
+
throw error;
|
|
84
|
+
} finally {
|
|
85
|
+
if (timer) clearTimeout(timer);
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
return (why) => {
|
|
89
|
+
if (pending) return pending;
|
|
90
|
+
pending = (async () => {
|
|
91
|
+
let timeout;
|
|
92
|
+
const cleanup = Promise.allSettled(options.tasks.map(runTask));
|
|
93
|
+
const result = await Promise.race([
|
|
94
|
+
cleanup.then((settled) => ({ type: "settled", settled })),
|
|
95
|
+
new Promise((resolve) => {
|
|
96
|
+
timeout = setTimeout(() => resolve({ type: "timeout" }), timeoutMs);
|
|
97
|
+
})
|
|
98
|
+
]);
|
|
99
|
+
if (timeout) clearTimeout(timeout);
|
|
100
|
+
if (result.type === "timeout") {
|
|
101
|
+
log(`[vgai-editor] cleanup timed out after ${timeoutMs}ms (${why}); forcing exit`);
|
|
102
|
+
exit(1);
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
const failures = result.settled.filter(
|
|
106
|
+
(item) => item.status === "rejected"
|
|
107
|
+
);
|
|
108
|
+
if (failures.length > 0) {
|
|
109
|
+
log(
|
|
110
|
+
`[vgai-editor] cleanup failed (${why}): ${failures.map(
|
|
111
|
+
(item) => item.reason instanceof Error ? item.reason.message : String(item.reason)
|
|
112
|
+
).join("; ")}`
|
|
113
|
+
);
|
|
114
|
+
exit(1);
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
exit(0);
|
|
118
|
+
})();
|
|
119
|
+
return pending;
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
export {
|
|
123
|
+
closeHttpServer,
|
|
124
|
+
createProcessShutdown,
|
|
125
|
+
stopProcess
|
|
126
|
+
};
|
|
127
|
+
//# sourceMappingURL=process-shutdown.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../server/process-shutdown.ts"],
|
|
4
|
+
"sourcesContent": ["import { type ChildProcess, execFileSync } from 'node:child_process';\nimport type { Server } from 'node:http';\n\n/** How long a detached child gets between SIGTERM and SIGKILL when its caller\n * does not say. A caller INSIDE the session's shutdown list passes a smaller\n * number, because its own budget is smaller than this \u2014 see\n * `createProcessShutdown`'s note on the arithmetic. */\nconst DEFAULT_STOP_GRACE_MS = 5_000;\n\n/**\n * Terminate a detached child and its whole process tree. Shared by the e2e\n * server harness and the packaged E2E runner: keeping this under `server/`\n * makes the runtime utility part of the editor package instead of forcing a\n * published command to import an unshipped e2e helper.\n *\n * `graceMs` is the SIGTERM\u2192SIGKILL window. It is a parameter because the\n * session's shutdown list has a tighter budget than this default: a child that\n * outlives its caller's own exit is an ORPHAN holding a reserved port, so the\n * escalation has to complete inside the caller's window, not after it.\n */\nexport function stopProcess(proc: ChildProcess, graceMs = DEFAULT_STOP_GRACE_MS): Promise<void> {\n const signalGroup = (signal: NodeJS.Signals) => {\n if (proc.pid === undefined) return;\n if (process.platform === 'win32') {\n try {\n execFileSync('taskkill', ['/PID', String(proc.pid), '/T', '/F'], { stdio: 'ignore' });\n } catch {\n /* already dead, or taskkill unavailable */\n }\n try {\n proc.kill(signal);\n } catch {\n /* already dead */\n }\n return;\n }\n try {\n process.kill(-proc.pid, signal);\n } catch {\n try {\n proc.kill(signal);\n } catch {\n /* already dead */\n }\n }\n };\n\n return new Promise((resolve) => {\n if (proc.exitCode !== null || proc.signalCode !== null) {\n signalGroup('SIGKILL');\n resolve();\n return;\n }\n const timer = setTimeout(() => {\n signalGroup('SIGKILL');\n resolve();\n }, graceMs);\n proc.on('exit', () => {\n clearTimeout(timer);\n signalGroup('SIGKILL');\n resolve();\n });\n signalGroup('SIGTERM');\n });\n}\n\n/**\n * ONE RESOURCE THIS SESSION IS STOPPING, and the name a post-mortem will read.\n *\n * The name is required because the failure this list exists to survive is\n * UNATTENDED: a task that never settles is only visible as the ABSENCE of\n * everything after it, and \"cleanup timed out\" with no name leaves a reader\n * guessing which resource is still alive.\n */\nexport interface ShutdownTask {\n /** What this stops, in the words a journal line should carry. */\n readonly name: string;\n run(): void | Promise<void>;\n}\n\nexport interface ProcessShutdownOptions {\n /** The WHOLE list's backstop. Bigger than `taskTimeoutMs` and nothing more:\n * with every task bounded and independent, the list's own duration is the\n * slowest task's, and this only catches a bug in the bounding itself. */\n timeoutMs?: number;\n /** Each task's OWN bound. See the note on `createProcessShutdown`. */\n taskTimeoutMs?: number;\n tasks: ReadonlyArray<ShutdownTask>;\n exit?: (code: number) => void;\n log?: (message: string) => void;\n /** Where a task that did not finish, or threw, is RECORDED \u2014 the session\n * journal, handed in by the host because this module owns no project. */\n journal?: (record: {\n readonly task: string;\n readonly outcome: 'timeout' | 'failed';\n readonly ms: number;\n readonly detail?: string;\n }) => void;\n}\n\n/** Stop accepting requests and forcibly release persistent HTTP/SSE sockets. */\nexport function closeHttpServer(server: Server): Promise<void> {\n return new Promise((resolve, reject) => {\n if (!server.listening) {\n server.closeAllConnections?.();\n resolve();\n return;\n }\n server.close((error) => (error ? reject(error) : resolve()));\n // `server.close()` waits for persistent SSE/keep-alive connections. They\n // have no useful work after process shutdown begins, so close them after\n // removing the listener and eliminate the restart race on the HTTP port.\n server.closeAllConnections?.();\n });\n}\n\n/**\n * Build an idempotent, bounded signal handler.\n *\n * Node's default signal exit is disabled as soon as an application installs\n * a SIGTERM/SIGINT listener. Tooling loaders (notably tsx/esbuild) may retain\n * internal handles even after every application server and watcher closes,\n * so waiting for an empty event loop is not a shutdown contract. Resources\n * get one bounded graceful window and then the process exits explicitly.\n *\n * EVERY TASK RUNS INDEPENDENTLY AND EVERY TASK IS BOUNDED BY ITSELF. Both\n * halves were bought by one measurement (2026-09-21, load average ~30, one\n * `vgai close`): the session's journal had no `session-shutdown` line, the\n * Code-OSS server survived as PPID 1 still holding its reserved `frame` port,\n * and the next `vgai edit` refused by name. A shared bound is what makes one\n * slow resource spend everybody's window, and the arithmetic has to close:\n * `vgai close` SIGTERMs, waits 5.5 s and SIGKILLs the session's process GROUP \u2014\n * which never reaches the Code-OSS server, because that child is spawned\n * detached into a group of its own. So the last of these tasks has to have\n * FINISHED by then, not merely started, or a child outlives its parent with\n * nobody left to signal it.\n *\n * A TASK THAT DOES NOT FINISH IS NAMED (`journal`), because the only other\n * evidence it leaves is the absence of everything downstream of it.\n */\nexport function createProcessShutdown(\n options: ProcessShutdownOptions,\n): (why: string) => Promise<void> {\n // 4 s per task, 4.8 s for the list: the whole shutdown fits inside `vgai\n // close`'s 5.5 s grace with room for the process's own exit, which the old\n // 2 s tab wait plus a 5 s shared cleanup budget did not. The 4 s is sized so\n // a task that must follow the tab-close notice (whose own budget is 2 s)\n // still has ~2 s of its own \u2014 see dev.ts's list.\n const taskTimeoutMs = options.taskTimeoutMs ?? 4_000;\n const timeoutMs = options.timeoutMs ?? 4_800;\n const exit = options.exit ?? ((code: number) => process.exit(code));\n const log = options.log ?? ((message: string) => process.stderr.write(`${message}\\n`));\n const journal = options.journal ?? (() => {});\n let pending: Promise<void> | null = null;\n\n /** One task, bounded by itself. Rejects with the task's NAME in the message,\n * so the aggregate line below reads as a list of resources. */\n const runTask = async (task: ShutdownTask): Promise<void> => {\n const startedAt = Date.now();\n let timer: ReturnType<typeof setTimeout> | undefined;\n try {\n const outcome = await Promise.race([\n Promise.resolve(task.run()).then(() => 'done' as const),\n new Promise<'timeout'>((resolve) => {\n timer = setTimeout(() => resolve('timeout'), taskTimeoutMs);\n }),\n ]);\n if (outcome === 'timeout') {\n journal({ task: task.name, outcome: 'timeout', ms: Date.now() - startedAt });\n throw new Error(`${task.name} did not finish within ${taskTimeoutMs}ms`);\n }\n } catch (error) {\n const detail = error instanceof Error ? error.message : String(error);\n if (!detail.endsWith(`within ${taskTimeoutMs}ms`))\n journal({ task: task.name, outcome: 'failed', ms: Date.now() - startedAt, detail });\n throw error;\n } finally {\n if (timer) clearTimeout(timer);\n }\n };\n\n return (why: string) => {\n if (pending) return pending;\n pending = (async () => {\n let timeout: ReturnType<typeof setTimeout> | undefined;\n const cleanup = Promise.allSettled(options.tasks.map(runTask));\n const result = await Promise.race([\n cleanup.then((settled) => ({ type: 'settled' as const, settled })),\n new Promise<{ type: 'timeout' }>((resolve) => {\n timeout = setTimeout(() => resolve({ type: 'timeout' }), timeoutMs);\n }),\n ]);\n if (timeout) clearTimeout(timeout);\n\n if (result.type === 'timeout') {\n log(`[vgai-editor] cleanup timed out after ${timeoutMs}ms (${why}); forcing exit`);\n exit(1);\n return;\n }\n const failures = result.settled.filter(\n (item): item is PromiseRejectedResult => item.status === 'rejected',\n );\n if (failures.length > 0) {\n log(\n `[vgai-editor] cleanup failed (${why}): ${failures\n .map((item) =>\n item.reason instanceof Error ? item.reason.message : String(item.reason),\n )\n .join('; ')}`,\n );\n exit(1);\n return;\n }\n exit(0);\n })();\n return pending;\n };\n}\n"],
|
|
5
|
+
"mappings": ";AAAA,SAA4B,oBAAoB;AAOhD,IAAM,wBAAwB;AAavB,SAAS,YAAY,MAAoB,UAAU,uBAAsC;AAC9F,QAAM,cAAc,CAAC,WAA2B;AAC9C,QAAI,KAAK,QAAQ,OAAW;AAC5B,QAAI,QAAQ,aAAa,SAAS;AAChC,UAAI;AACF,qBAAa,YAAY,CAAC,QAAQ,OAAO,KAAK,GAAG,GAAG,MAAM,IAAI,GAAG,EAAE,OAAO,SAAS,CAAC;AAAA,MACtF,QAAQ;AAAA,MAER;AACA,UAAI;AACF,aAAK,KAAK,MAAM;AAAA,MAClB,QAAQ;AAAA,MAER;AACA;AAAA,IACF;AACA,QAAI;AACF,cAAQ,KAAK,CAAC,KAAK,KAAK,MAAM;AAAA,IAChC,QAAQ;AACN,UAAI;AACF,aAAK,KAAK,MAAM;AAAA,MAClB,QAAQ;AAAA,MAER;AAAA,IACF;AAAA,EACF;AAEA,SAAO,IAAI,QAAQ,CAAC,YAAY;AAC9B,QAAI,KAAK,aAAa,QAAQ,KAAK,eAAe,MAAM;AACtD,kBAAY,SAAS;AACrB,cAAQ;AACR;AAAA,IACF;AACA,UAAM,QAAQ,WAAW,MAAM;AAC7B,kBAAY,SAAS;AACrB,cAAQ;AAAA,IACV,GAAG,OAAO;AACV,SAAK,GAAG,QAAQ,MAAM;AACpB,mBAAa,KAAK;AAClB,kBAAY,SAAS;AACrB,cAAQ;AAAA,IACV,CAAC;AACD,gBAAY,SAAS;AAAA,EACvB,CAAC;AACH;AAqCO,SAAS,gBAAgB,QAA+B;AAC7D,SAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,QAAI,CAAC,OAAO,WAAW;AACrB,aAAO,sBAAsB;AAC7B,cAAQ;AACR;AAAA,IACF;AACA,WAAO,MAAM,CAAC,UAAW,QAAQ,OAAO,KAAK,IAAI,QAAQ,CAAE;AAI3D,WAAO,sBAAsB;AAAA,EAC/B,CAAC;AACH;AA0BO,SAAS,sBACd,SACgC;AAMhC,QAAM,gBAAgB,QAAQ,iBAAiB;AAC/C,QAAM,YAAY,QAAQ,aAAa;AACvC,QAAM,OAAO,QAAQ,SAAS,CAAC,SAAiB,QAAQ,KAAK,IAAI;AACjE,QAAM,MAAM,QAAQ,QAAQ,CAAC,YAAoB,QAAQ,OAAO,MAAM,GAAG,OAAO;AAAA,CAAI;AACpF,QAAM,UAAU,QAAQ,YAAY,MAAM;AAAA,EAAC;AAC3C,MAAI,UAAgC;AAIpC,QAAM,UAAU,OAAO,SAAsC;AAC3D,UAAM,YAAY,KAAK,IAAI;AAC3B,QAAI;AACJ,QAAI;AACF,YAAM,UAAU,MAAM,QAAQ,KAAK;AAAA,QACjC,QAAQ,QAAQ,KAAK,IAAI,CAAC,EAAE,KAAK,MAAM,MAAe;AAAA,QACtD,IAAI,QAAmB,CAAC,YAAY;AAClC,kBAAQ,WAAW,MAAM,QAAQ,SAAS,GAAG,aAAa;AAAA,QAC5D,CAAC;AAAA,MACH,CAAC;AACD,UAAI,YAAY,WAAW;AACzB,gBAAQ,EAAE,MAAM,KAAK,MAAM,SAAS,WAAW,IAAI,KAAK,IAAI,IAAI,UAAU,CAAC;AAC3E,cAAM,IAAI,MAAM,GAAG,KAAK,IAAI,0BAA0B,aAAa,IAAI;AAAA,MACzE;AAAA,IACF,SAAS,OAAO;AACd,YAAM,SAAS,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AACpE,UAAI,CAAC,OAAO,SAAS,UAAU,aAAa,IAAI;AAC9C,gBAAQ,EAAE,MAAM,KAAK,MAAM,SAAS,UAAU,IAAI,KAAK,IAAI,IAAI,WAAW,OAAO,CAAC;AACpF,YAAM;AAAA,IACR,UAAE;AACA,UAAI,MAAO,cAAa,KAAK;AAAA,IAC/B;AAAA,EACF;AAEA,SAAO,CAAC,QAAgB;AACtB,QAAI,QAAS,QAAO;AACpB,eAAW,YAAY;AACrB,UAAI;AACJ,YAAM,UAAU,QAAQ,WAAW,QAAQ,MAAM,IAAI,OAAO,CAAC;AAC7D,YAAM,SAAS,MAAM,QAAQ,KAAK;AAAA,QAChC,QAAQ,KAAK,CAAC,aAAa,EAAE,MAAM,WAAoB,QAAQ,EAAE;AAAA,QACjE,IAAI,QAA6B,CAAC,YAAY;AAC5C,oBAAU,WAAW,MAAM,QAAQ,EAAE,MAAM,UAAU,CAAC,GAAG,SAAS;AAAA,QACpE,CAAC;AAAA,MACH,CAAC;AACD,UAAI,QAAS,cAAa,OAAO;AAEjC,UAAI,OAAO,SAAS,WAAW;AAC7B,YAAI,yCAAyC,SAAS,OAAO,GAAG,iBAAiB;AACjF,aAAK,CAAC;AACN;AAAA,MACF;AACA,YAAM,WAAW,OAAO,QAAQ;AAAA,QAC9B,CAAC,SAAwC,KAAK,WAAW;AAAA,MAC3D;AACA,UAAI,SAAS,SAAS,GAAG;AACvB;AAAA,UACE,iCAAiC,GAAG,MAAM,SACvC;AAAA,YAAI,CAAC,SACJ,KAAK,kBAAkB,QAAQ,KAAK,OAAO,UAAU,OAAO,KAAK,MAAM;AAAA,UACzE,EACC,KAAK,IAAI,CAAC;AAAA,QACf;AACA,aAAK,CAAC;AACN;AAAA,MACF;AACA,WAAK,CAAC;AAAA,IACR,GAAG;AACH,WAAO;AAAA,EACT;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import {
|
|
2
|
+
resolveWorktreeIdentity
|
|
3
|
+
} from "./chunk-KMJC7AGO.js";
|
|
4
|
+
|
|
5
|
+
// ../editor-sdk/src/session/registry-format.ts
|
|
6
|
+
import { homedir } from "node:os";
|
|
7
|
+
import { join, resolve } from "node:path";
|
|
8
|
+
var EDITOR_SESSIONS_REGISTRY_FILE = join(homedir(), ".vgai", "editor-sessions.json");
|
|
9
|
+
function isEditorSessionEntry(v) {
|
|
10
|
+
if (typeof v !== "object" || v === null) return false;
|
|
11
|
+
const s = v;
|
|
12
|
+
const optionalIdentity = (value) => value === void 0 || value === null || typeof value === "string";
|
|
13
|
+
return (typeof s["project"] === "string" || s["project"] === null) && typeof s["port"] === "number" && typeof s["pid"] === "number" && typeof s["startedAt"] === "string" && optionalIdentity(s["sessionId"]) && optionalIdentity(s["controlSecret"]) && optionalIdentity(s["repositoryId"]) && optionalIdentity(s["worktreeId"]) && optionalIdentity(s["worktreeRoot"]) && optionalIdentity(s["projectRelativePath"]) && optionalIdentity(s["branch"]) && optionalIdentity(s["headCommit"]) && optionalIdentity(s["baseCommit"]);
|
|
14
|
+
}
|
|
15
|
+
function normalizeEditorSessionEntry(session) {
|
|
16
|
+
return {
|
|
17
|
+
...session,
|
|
18
|
+
sessionId: session.sessionId ?? null,
|
|
19
|
+
controlSecret: session.controlSecret ?? null,
|
|
20
|
+
repositoryId: session.repositoryId ?? null,
|
|
21
|
+
worktreeId: session.worktreeId ?? null,
|
|
22
|
+
worktreeRoot: session.worktreeRoot ?? null,
|
|
23
|
+
projectRelativePath: session.projectRelativePath ?? null,
|
|
24
|
+
branch: session.branch ?? null,
|
|
25
|
+
headCommit: session.headCommit ?? null,
|
|
26
|
+
baseCommit: session.baseCommit ?? null
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
function pidAlive(pid) {
|
|
30
|
+
try {
|
|
31
|
+
process.kill(pid, 0);
|
|
32
|
+
return true;
|
|
33
|
+
} catch {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// server/session-registry.ts
|
|
39
|
+
import { randomBytes, randomUUID } from "node:crypto";
|
|
40
|
+
import { chmodSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
41
|
+
import { homedir as homedir2 } from "node:os";
|
|
42
|
+
import { join as join2 } from "node:path";
|
|
43
|
+
var PROCESS_SESSION_ID = randomUUID();
|
|
44
|
+
var PROCESS_CONTROL_SECRET = randomBytes(32).toString("base64url");
|
|
45
|
+
function processSessionId() {
|
|
46
|
+
return PROCESS_SESSION_ID;
|
|
47
|
+
}
|
|
48
|
+
function processControlSecret() {
|
|
49
|
+
return PROCESS_CONTROL_SECRET;
|
|
50
|
+
}
|
|
51
|
+
function sessionIdentity(project) {
|
|
52
|
+
return project === null ? null : resolveWorktreeIdentity(project);
|
|
53
|
+
}
|
|
54
|
+
function materializeSession(announcement, id = PROCESS_SESSION_ID) {
|
|
55
|
+
const identity = sessionIdentity(announcement.project);
|
|
56
|
+
return {
|
|
57
|
+
...announcement,
|
|
58
|
+
sessionId: id,
|
|
59
|
+
controlSecret: PROCESS_CONTROL_SECRET,
|
|
60
|
+
repositoryId: identity?.repositoryId ?? null,
|
|
61
|
+
worktreeId: identity?.worktreeId ?? null,
|
|
62
|
+
worktreeRoot: identity?.worktreeRoot ?? null,
|
|
63
|
+
projectRelativePath: identity?.projectRelativePath ?? null,
|
|
64
|
+
branch: identity?.branch ?? null,
|
|
65
|
+
headCommit: identity?.headCommit ?? null,
|
|
66
|
+
baseCommit: identity?.baseCommit ?? null
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
var REGISTRY_DIR = join2(homedir2(), ".vgai");
|
|
70
|
+
var REGISTRY_FILE = EDITOR_SESSIONS_REGISTRY_FILE;
|
|
71
|
+
function sessionRegistryPath() {
|
|
72
|
+
return REGISTRY_FILE;
|
|
73
|
+
}
|
|
74
|
+
function isEphemeralSession(env = process.env) {
|
|
75
|
+
const value = env["VGAI_EPHEMERAL_SESSION"];
|
|
76
|
+
return value !== void 0 && value !== "" && value !== "0";
|
|
77
|
+
}
|
|
78
|
+
function writesRecentProjects(env = process.env) {
|
|
79
|
+
if (isEphemeralSession(env)) return false;
|
|
80
|
+
return !env["VGAI_NO_OPEN"];
|
|
81
|
+
}
|
|
82
|
+
function readSessions() {
|
|
83
|
+
try {
|
|
84
|
+
const raw = JSON.parse(readFileSync(REGISTRY_FILE, "utf8"));
|
|
85
|
+
return Array.isArray(raw) ? raw.filter(isEditorSessionEntry).map(normalizeEditorSessionEntry) : [];
|
|
86
|
+
} catch {
|
|
87
|
+
return [];
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
function liveSessions() {
|
|
91
|
+
return readSessions().filter((s) => pidAlive(s.pid));
|
|
92
|
+
}
|
|
93
|
+
function replaceRegistry(sessions) {
|
|
94
|
+
const tmp = `${REGISTRY_FILE}.${process.pid}.tmp`;
|
|
95
|
+
writeFileSync(tmp, `${JSON.stringify(sessions, null, 2)}
|
|
96
|
+
`, { mode: 384 });
|
|
97
|
+
renameSync(tmp, REGISTRY_FILE);
|
|
98
|
+
try {
|
|
99
|
+
chmodSync(REGISTRY_FILE, 384);
|
|
100
|
+
} catch {
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
function pruneDeadSessions(sessions, isAlive = pidAlive) {
|
|
104
|
+
return sessions.filter((s) => isAlive(s.pid));
|
|
105
|
+
}
|
|
106
|
+
function registryAfterRegistration(sessions, incoming, isAlive = pidAlive) {
|
|
107
|
+
const rest = pruneDeadSessions(sessions, isAlive).filter(
|
|
108
|
+
(s) => s.pid !== incoming.pid && s.port !== incoming.port
|
|
109
|
+
);
|
|
110
|
+
return [...rest, incoming];
|
|
111
|
+
}
|
|
112
|
+
function registerSession(announcement) {
|
|
113
|
+
try {
|
|
114
|
+
mkdirSync(REGISTRY_DIR, { recursive: true });
|
|
115
|
+
replaceRegistry(registryAfterRegistration(readSessions(), materializeSession(announcement)));
|
|
116
|
+
} catch {
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
function unregisterSession(pid) {
|
|
120
|
+
try {
|
|
121
|
+
replaceRegistry(pruneDeadSessions(readSessions().filter((s) => s.pid !== pid)));
|
|
122
|
+
} catch {
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
export {
|
|
126
|
+
isEphemeralSession,
|
|
127
|
+
liveSessions,
|
|
128
|
+
materializeSession,
|
|
129
|
+
pidAlive,
|
|
130
|
+
processControlSecret,
|
|
131
|
+
processSessionId,
|
|
132
|
+
pruneDeadSessions,
|
|
133
|
+
readSessions,
|
|
134
|
+
registerSession,
|
|
135
|
+
registryAfterRegistration,
|
|
136
|
+
sessionIdentity,
|
|
137
|
+
sessionRegistryPath,
|
|
138
|
+
unregisterSession,
|
|
139
|
+
writesRecentProjects
|
|
140
|
+
};
|
|
141
|
+
//# sourceMappingURL=session-registry.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../editor-sdk/src/session/registry-format.ts", "../../server/session-registry.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * `~/.vgai/editor-sessions.json` \u2014 the ONE spelling of the editor-session\n * registry contract, and the shared machinery every reader was hand-copying.\n *\n * The registry's WRITE half stays with its owner\n * (`packages/editor/server/session-registry.ts`); this module owns the\n * FORMAT: the entry shape, the shape guards, the file path, the\n * liveness-filtered read, the `/__editor/project` answer parser, and the\n * pending-launch coordination. It existed as FOUR drifting copies\n * (server registry, the CLI, and both vgai-sdk transports) held together by\n * a \"the CLI has no editor dependency\" premise that had stopped being true \u2014\n * and the drift was already real: one copy's `/__editor/project` parser\n * dropped `manifestError`, reporting a degraded session as belonging to no\n * project.\n */\n\nimport {\n mkdirSync,\n readdirSync,\n readFileSync,\n realpathSync,\n unlinkSync,\n writeFileSync,\n} from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join, resolve } from 'node:path';\nimport { setTimeout as delay } from 'node:timers/promises';\n\n/** One registry entry, exactly as the server's write half records it. */\nexport interface EditorSessionEntry {\n /** Canonical project root currently open, or null when none. */\n project: string | null;\n port: number;\n /** PID of the dev-server process. */\n pid: number;\n /** ISO timestamp of server start. */\n startedAt: string;\n sessionId: string | null;\n /** Private loopback control credential \u2014 never returned by an editor HTTP\n * route; exists only in this user-private registry. */\n controlSecret?: string | null;\n repositoryId: string | null;\n worktreeId: string | null;\n worktreeRoot: string | null;\n projectRelativePath: string | null;\n branch: string | null;\n headCommit: string | null;\n baseCommit: string | null;\n}\n\nexport const EDITOR_SESSIONS_REGISTRY_FILE = join(homedir(), '.vgai', 'editor-sessions.json');\n\nexport function isEditorSessionEntry(v: unknown): v is EditorSessionEntry {\n if (typeof v !== 'object' || v === null) return false;\n const s = v as Record<string, unknown>;\n const optionalIdentity = (value: unknown) =>\n value === undefined || value === null || typeof value === 'string';\n return (\n (typeof s['project'] === 'string' || s['project'] === null) &&\n typeof s['port'] === 'number' &&\n typeof s['pid'] === 'number' &&\n typeof s['startedAt'] === 'string' &&\n optionalIdentity(s['sessionId']) &&\n optionalIdentity(s['controlSecret']) &&\n optionalIdentity(s['repositoryId']) &&\n optionalIdentity(s['worktreeId']) &&\n optionalIdentity(s['worktreeRoot']) &&\n optionalIdentity(s['projectRelativePath']) &&\n optionalIdentity(s['branch']) &&\n optionalIdentity(s['headCommit']) &&\n optionalIdentity(s['baseCommit'])\n );\n}\n\nexport function normalizeEditorSessionEntry(session: EditorSessionEntry): EditorSessionEntry {\n return {\n ...session,\n sessionId: session.sessionId ?? null,\n controlSecret: session.controlSecret ?? null,\n repositoryId: session.repositoryId ?? null,\n worktreeId: session.worktreeId ?? null,\n worktreeRoot: session.worktreeRoot ?? null,\n projectRelativePath: session.projectRelativePath ?? null,\n branch: session.branch ?? null,\n headCommit: session.headCommit ?? null,\n baseCommit: session.baseCommit ?? null,\n };\n}\n\nexport function pidAlive(pid: number): boolean {\n try {\n process.kill(pid, 0);\n return true;\n } catch {\n return false;\n }\n}\n\n/** The registry's live entries \u2014 shape-validated and PID-liveness-filtered. */\nexport function readLiveRegisteredSessions(): EditorSessionEntry[] {\n try {\n const raw: unknown = JSON.parse(readFileSync(EDITOR_SESSIONS_REGISTRY_FILE, 'utf8'));\n return Array.isArray(raw)\n ? raw\n .filter(isEditorSessionEntry)\n .map(normalizeEditorSessionEntry)\n .filter((s) => pidAlive(s.pid))\n : [];\n } catch {\n return [];\n }\n}\n\n/** Announce a launch before the server can register. Each launcher owns its file. */\nexport function announceEditorLaunch(project: string): () => void {\n const directory = join(project, '.vgai', 'editor-launches');\n mkdirSync(directory, { recursive: true });\n const file = join(directory, `${process.pid}.json`);\n writeFileSync(file, JSON.stringify({ pid: process.pid }));\n const clear = () => {\n process.removeListener('exit', clear);\n try {\n unlinkSync(file);\n } catch {\n // Already cleared when the server became ready.\n }\n };\n process.once('exit', clear);\n return clear;\n}\n\nfunction hasPendingEditorLaunch(project: string): boolean {\n const directory = join(project, '.vgai', 'editor-launches');\n try {\n return readdirSync(directory).some((name) => {\n if (!/^\\d+\\.json$/.test(name)) return false;\n const pid = Number(name.slice(0, -5));\n return pid !== process.pid && pidAlive(pid);\n });\n } catch {\n return false;\n }\n}\n\n/** Wait only for an observed live launcher, never for a missing editor. */\nexport async function waitForPendingEditorLaunch(project: string): Promise<void> {\n const canon = canonicalizeProjectPath(project);\n const ready = () =>\n readLiveRegisteredSessions().some(\n (session) => session.project !== null && canonicalizeProjectPath(session.project) === canon,\n );\n if (ready() || !hasPendingEditorLaunch(canon)) return;\n const deadline = Date.now() + 180_000;\n while (!ready()) {\n if (!hasPendingEditorLaunch(canon)) {\n // The launcher clears its announcement only after registration.\n if (ready()) return;\n throw new Error(`The editor launch for ${canon} ended before registering a session.`);\n }\n if (Date.now() >= deadline) {\n throw new Error(\n `The editor launch for ${canon} is still running but has not registered within 180s.`,\n );\n }\n await delay(100);\n }\n}\n\n/**\n * WHICH PROJECT a `/__editor/project` body says its server is serving.\n *\n * `serving` is that server's own statement of \"I AM serving this project, I\n * just cannot describe it\" (its manifest is unparseable or fails strict\n * validation) \u2014 added to the route precisely because a bare\n * `{ project: null }` is indistinguishable from \"no project open\". Reading\n * only `project.path` collapses the two, and the cost is that every\n * project-matched command loses a live session the moment a save breaks its\n * manifest, reporting it as belonging to no project rather than as this\n * project's degraded session.\n */\nexport function servedProjectAnswer(body: unknown): {\n path: string | null;\n manifestError: string | null;\n} {\n const b = body as {\n project?: { path?: string } | null;\n serving?: { path?: string; error?: string } | null;\n };\n return {\n path: b.project?.path ?? b.serving?.path ?? null,\n manifestError: b.project ? null : (b.serving?.error ?? null),\n };\n}\n\n/** Canonical (realpathed when possible) absolute form of a project path. */\nfunction canonicalizeProjectPath(p: string): string {\n const absolute = resolve(p);\n try {\n return realpathSync(absolute);\n } catch {\n return absolute;\n }\n}\n", "/**\n * Editor session registry \u2014 the file-backed ledger of live editor dev servers.\n * This module is the WRITER (dev.ts registers on listen, re-registers on\n * project switch, unregisters on shutdown). The FORMAT \u2014 entry shape, guards,\n * path, liveness-filtered read \u2014 lives once in `@vgai/sdk`'s\n * `session-registry-format`, imported by every reader (this file, the CLI,\n * and both SDK transports) instead of copied.\n *\n * Entries are best-effort \u2014 every reader must PID-liveness-filter (a crashed\n * server can't unregister itself), and writers sweep dead entries on write.\n */\n\nimport {\n EDITOR_SESSIONS_REGISTRY_FILE,\n type EditorSessionEntry as EditorSession,\n isEditorSessionEntry,\n normalizeEditorSessionEntry,\n pidAlive,\n} from '@volter/editor-sdk/session/registry-format';\n\nexport type { EditorSessionEntry as EditorSession } from '@volter/editor-sdk/session/registry-format';\nexport { pidAlive } from '@volter/editor-sdk/session/registry-format';\n\nimport { randomBytes, randomUUID } from 'node:crypto';\nimport { chmodSync, mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport { resolveWorktreeIdentity, type WorktreeIdentity } from './worktree-identity';\n\nexport type EditorSessionAnnouncement = Pick<\n EditorSession,\n 'project' | 'port' | 'pid' | 'startedAt'\n>;\n\nconst PROCESS_SESSION_ID = randomUUID();\nconst PROCESS_CONTROL_SECRET = randomBytes(32).toString('base64url');\n\nexport function processSessionId(): string {\n return PROCESS_SESSION_ID;\n}\n\nexport function processControlSecret(): string {\n return PROCESS_CONTROL_SECRET;\n}\n\nexport function sessionIdentity(project: string | null): WorktreeIdentity | null {\n return project === null ? null : resolveWorktreeIdentity(project);\n}\n\nexport function materializeSession(\n announcement: EditorSessionAnnouncement,\n id: string = PROCESS_SESSION_ID,\n): EditorSession {\n const identity = sessionIdentity(announcement.project);\n return {\n ...announcement,\n sessionId: id,\n controlSecret: PROCESS_CONTROL_SECRET,\n repositoryId: identity?.repositoryId ?? null,\n worktreeId: identity?.worktreeId ?? null,\n worktreeRoot: identity?.worktreeRoot ?? null,\n projectRelativePath: identity?.projectRelativePath ?? null,\n branch: identity?.branch ?? null,\n headCommit: identity?.headCommit ?? null,\n baseCommit: identity?.baseCommit ?? null,\n };\n}\n\nconst REGISTRY_DIR = join(homedir(), '.vgai');\nconst REGISTRY_FILE = EDITOR_SESSIONS_REGISTRY_FILE;\n\nexport function sessionRegistryPath(): string {\n return REGISTRY_FILE;\n}\n\n/**\n * Is this dev server an EPHEMERAL PROBE rather than a session anyone owns?\n *\n * `vgai doctor` spawns a real dev server on the target folder for a few\n * seconds and drives it with its own headless browser. It is not an editing\n * session: nobody should be able to reuse it, `vgai close` should not list\n * it, and \u2014 the defect this exists for (SimCity ingest dogfood, S-6) \u2014 it\n * must not touch the project's `.vgai/session.json`, which belongs to\n * whichever `vgai edit` session is actually serving that folder. The probe\n * used to OVERWRITE that file at boot with its own throwaway port/pid and\n * then DELETE it on exit, so a live session the user was watching became\n * undiscoverable (`cat .vgai/session.json` \u2192 ENOENT) because they ran a\n * read-only diagnostic against it.\n *\n * Same shape as `VGAI_NO_OPEN`: an env flag the spawning tool sets on the\n * child, never a mode the server infers. `'0'` explicitly opts back in, so\n * the variable can be cleared by value in an inherited environment.\n */\nexport function isEphemeralSession(env: NodeJS.ProcessEnv = process.env): boolean {\n const value = env['VGAI_EPHEMERAL_SESSION'];\n return value !== undefined && value !== '' && value !== '0';\n}\n\n/**\n * Does this dev server contribute to the PERSON's launcher memory\n * (`~/.vgai/recent-projects.json`, read by the Projects hub and by the\n * opt-in \"Reopen last project on launch\")?\n *\n * Recents is machine-local state that belongs to a\n * HUMAN at a launcher \u2014 it is what the hub shows first and what reopen-last\n * reaches for. Two kinds of dev server have no such human by construction and\n * must therefore stay out of it:\n *\n * - an EPHEMERAL PROBE (`isEphemeralSession`, today `vgai doctor`), which is\n * not an editing session at all; and\n * - a HEADLESS session (`VGAI_NO_OPEN` \u2014 the CLI's `--no-open`), which\n * maintains no browser tab whatsoever: CI, headless harnesses, and every\n * background-agent session. Measured: an agent's transient session wrote\n * its scratchpad project into the owner's Recents, and the owner's next\n * launcher boot reached for it.\n *\n * The read is the same truthiness `dev.ts`/`packaged.ts` already use for\n * `tabBijection.enabled`, so \"this session maintains a tab\" and \"this session\n * is someone's launcher memory\" can never disagree.\n */\nexport function writesRecentProjects(env: NodeJS.ProcessEnv = process.env): boolean {\n if (isEphemeralSession(env)) return false;\n return !env['VGAI_NO_OPEN'];\n}\n\n/** All recorded sessions, unvalidated beyond shape (callers liveness-filter). */\nexport function readSessions(): EditorSession[] {\n try {\n const raw: unknown = JSON.parse(readFileSync(REGISTRY_FILE, 'utf8'));\n return Array.isArray(raw)\n ? raw.filter(isEditorSessionEntry).map(normalizeEditorSessionEntry)\n : [];\n } catch {\n return [];\n }\n}\n\n/** Sessions whose server process is still alive. */\nexport function liveSessions(): EditorSession[] {\n return readSessions().filter((s) => pidAlive(s.pid));\n}\n\n/**\n * Atomic registry replace: write a private temp file, then rename() it over\n * the registry. rename(2) is atomic on POSIX (and effectively so on NTFS), so\n * a concurrent reader always parses either the complete old array or the\n * complete new one \u2014 never a torn/truncated file. Torn reads are not\n * hypothetical: `readSessions()` maps ANY parse failure to `[]`, so a\n * reader-that-then-writes (register/unregister \u2014 which now runs on every\n * dev-server source-change restart) that catches another writer mid-truncate\n * would persist that empty view and silently wipe every other live session's\n * entry. Reproduced live 2026-07-25: two concurrent restart-churn processes\n * emptied a registry seeded with three entries whose PIDs were still alive \u2014\n * the wiped sessions kept running but became invisible to `vgai\n * edit`/`sessions`/`close`, which is the \"new editors starting over and over\n * while strays accumulate\" failure mode.\n */\nfunction replaceRegistry(sessions: EditorSession[]): void {\n const tmp = `${REGISTRY_FILE}.${process.pid}.tmp`;\n writeFileSync(tmp, `${JSON.stringify(sessions, null, 2)}\\n`, { mode: 0o600 });\n renameSync(tmp, REGISTRY_FILE);\n // Owner-only is the intent; a filesystem without modes (a browser runtime)\n // keeps the file and skips the bit.\n try {\n chmodSync(REGISTRY_FILE, 0o600);\n } catch {\n /* no modes here */\n }\n}\n\n/**\n * A registry record whose pid is not alive is HISTORY, not identity.\n *\n * A dead record must\n * never contribute a project \u2014 or anything else \u2014 to the port it used to\n * hold, and no live server may inherit one. The registry is a ledger of what\n * IS running, so every write prunes what is not; a reader that forgets to\n * liveness-filter can then only ever see stale entries this process has not\n * swept yet, never entries the registry deliberately kept.\n *\n * `isAlive` is injectable so the rule is testable against fabricated pids\n * without spawning (or killing) real processes.\n */\nexport function pruneDeadSessions(\n sessions: readonly EditorSession[],\n isAlive: (pid: number) => boolean = pidAlive,\n): EditorSession[] {\n return sessions.filter((s) => isAlive(s.pid));\n}\n\n/**\n * The registry as it must look once `incoming` has taken its port: every dead\n * record gone, the previous claim on this pid or this port replaced, and\n * `incoming` appended. Pure, for the same reason as `pruneDeadSessions`.\n */\nexport function registryAfterRegistration(\n sessions: readonly EditorSession[],\n incoming: EditorSession,\n isAlive: (pid: number) => boolean = pidAlive,\n): EditorSession[] {\n const rest = pruneDeadSessions(sessions, isAlive).filter(\n (s) => s.pid !== incoming.pid && s.port !== incoming.port,\n );\n return [...rest, incoming];\n}\n\n/**\n * Record (or refresh) this process's session. Sweeps dead entries and any\n * stale claim on the same port while it's here.\n */\nexport function registerSession(announcement: EditorSessionAnnouncement): void {\n try {\n mkdirSync(REGISTRY_DIR, { recursive: true });\n replaceRegistry(registryAfterRegistration(readSessions(), materializeSession(announcement)));\n } catch {\n // Registry is best-effort \u2014 never let bookkeeping kill the server.\n }\n}\n\nexport function unregisterSession(pid: number): void {\n try {\n // Same rule on the way out: this process's record goes, and so does every\n // record whose server is already gone. Leaving those behind is what lets a\n // dead entry outlive the server it described and keep answering \"which\n // project is on port N?\" long after nothing is.\n replaceRegistry(pruneDeadSessions(readSessions().filter((s) => s.pid !== pid)));\n } catch {\n // Best-effort (see registerSession).\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["homedir", "join", "join", "homedir"]
|
|
7
|
+
}
|