@hraness/slopcamera 3.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/NOTICE.md +60 -0
- package/PRIVACY.md +46 -0
- package/README.md +311 -0
- package/SECURITY.md +21 -0
- package/apps/desktop/README.md +966 -0
- package/apps/desktop/analysis/protocol.ts +223 -0
- package/apps/desktop/application/capability-binding.ts +594 -0
- package/apps/desktop/application/context.ts +97 -0
- package/apps/desktop/application/creative-iteration.ts +1385 -0
- package/apps/desktop/application/default-registry.ts +146 -0
- package/apps/desktop/application/directing-contract.ts +218 -0
- package/apps/desktop/application/directing-plan.ts +178 -0
- package/apps/desktop/application/errors.ts +18 -0
- package/apps/desktop/application/gateway-port.ts +867 -0
- package/apps/desktop/application/html-overlay-browser-runtime.ts +687 -0
- package/apps/desktop/application/html-overlay-integrity.ts +337 -0
- package/apps/desktop/application/html-overlay-renderer.ts +45 -0
- package/apps/desktop/application/index.ts +13 -0
- package/apps/desktop/application/operation-completion-checkpoint.ts +333 -0
- package/apps/desktop/application/operation-effects.ts +94 -0
- package/apps/desktop/application/operation.ts +224 -0
- package/apps/desktop/application/operations/analysis/faces.ts +244 -0
- package/apps/desktop/application/operations/analysis/music.ts +248 -0
- package/apps/desktop/application/operations/analysis/project-auto-zooms.ts +302 -0
- package/apps/desktop/application/operations/analysis/project-inactivity.ts +381 -0
- package/apps/desktop/application/operations/analysis/scenes.ts +272 -0
- package/apps/desktop/application/operations/analysis/shared.ts +202 -0
- package/apps/desktop/application/operations/derive/edit-batch.ts +967 -0
- package/apps/desktop/application/operations/derive/follow-faces.ts +454 -0
- package/apps/desktop/application/operations/edit/bind-candidate-revision.ts +91 -0
- package/apps/desktop/application/operations/edit/create-candidate-revision.ts +377 -0
- package/apps/desktop/application/operations/edit/create-revision.ts +255 -0
- package/apps/desktop/application/operations/edit/freeze-revision.ts +182 -0
- package/apps/desktop/application/operations/gateway/image.ts +41 -0
- package/apps/desktop/application/operations/gateway/index.ts +17 -0
- package/apps/desktop/application/operations/gateway/shared.ts +122 -0
- package/apps/desktop/application/operations/gateway/speech.ts +41 -0
- package/apps/desktop/application/operations/gateway/transcription.ts +42 -0
- package/apps/desktop/application/operations/gateway/video.ts +41 -0
- package/apps/desktop/application/operations/index.ts +31 -0
- package/apps/desktop/application/operations/iteration/create-candidate.ts +240 -0
- package/apps/desktop/application/operations/iteration/create-matrix.ts +117 -0
- package/apps/desktop/application/operations/iteration/select.ts +155 -0
- package/apps/desktop/application/operations/iteration/shared.ts +250 -0
- package/apps/desktop/application/operations/media/audio-effects.ts +376 -0
- package/apps/desktop/application/operations/media/binding.ts +79 -0
- package/apps/desktop/application/operations/media/capabilities.ts +112 -0
- package/apps/desktop/application/operations/media/color-grade.ts +343 -0
- package/apps/desktop/application/operations/media/html-overlay.ts +802 -0
- package/apps/desktop/application/operations/media/index.ts +8 -0
- package/apps/desktop/application/operations/media/ingest.ts +303 -0
- package/apps/desktop/application/operations/media/overlay.ts +729 -0
- package/apps/desktop/application/operations/media/shared.ts +845 -0
- package/apps/desktop/application/operations/media/transform-platform.ts +120 -0
- package/apps/desktop/application/operations/project/commit-edits.ts +2054 -0
- package/apps/desktop/application/operations/project/promote-selection.ts +495 -0
- package/apps/desktop/application/operations/project/snapshot.ts +95 -0
- package/apps/desktop/application/operations/recording/index.ts +5 -0
- package/apps/desktop/application/operations/recording/pause.ts +34 -0
- package/apps/desktop/application/operations/recording/resume.ts +34 -0
- package/apps/desktop/application/operations/recording/shared.ts +199 -0
- package/apps/desktop/application/operations/recording/start.ts +169 -0
- package/apps/desktop/application/operations/recording/stop.ts +34 -0
- package/apps/desktop/application/operations/render/bind-candidate-output.ts +324 -0
- package/apps/desktop/application/operations/render/materialize-selection.ts +322 -0
- package/apps/desktop/application/operations/render/project-caption-plan.ts +287 -0
- package/apps/desktop/application/operations/render/project-plan.ts +939 -0
- package/apps/desktop/application/operations/render/project-spatial-receipt.ts +70 -0
- package/apps/desktop/application/operations/render/project.ts +1922 -0
- package/apps/desktop/application/operations/shared.ts +67 -0
- package/apps/desktop/application/operations/slopcamera-portable.ts +1188 -0
- package/apps/desktop/application/operations/slopcamera-visuals.ts +784 -0
- package/apps/desktop/application/operations/spatial-project.ts +209 -0
- package/apps/desktop/application/operations/spatial-render.ts +415 -0
- package/apps/desktop/application/operations/spatial-scene.ts +97 -0
- package/apps/desktop/application/operations/studio.ts +143 -0
- package/apps/desktop/application/output-publication-lease.ts +163 -0
- package/apps/desktop/application/project-publication-lease.ts +233 -0
- package/apps/desktop/application/project-store.ts +196 -0
- package/apps/desktop/application/receipts.ts +533 -0
- package/apps/desktop/application/registry.ts +182 -0
- package/apps/desktop/application/spatial-assets.ts +675 -0
- package/apps/desktop/application/spatial-compositor-cadence.ts +23 -0
- package/apps/desktop/application/spatial-project-authority.ts +35 -0
- package/apps/desktop/application/spatial-project-lease.ts +126 -0
- package/apps/desktop/application/spatial-project-render-preparation.ts +225 -0
- package/apps/desktop/application/spatial-project-store.ts +286 -0
- package/apps/desktop/application/spatial-render-projection.ts +201 -0
- package/apps/desktop/application/spatial-render.ts +677 -0
- package/apps/desktop/application/spatial-spz.ts +44 -0
- package/apps/desktop/application/spatial-world-import.ts +135 -0
- package/apps/desktop/application/spatial-world-provenance.ts +46 -0
- package/apps/desktop/application/studio-port.ts +58 -0
- package/apps/desktop/application/verified-receipt-reconciliation.ts +1574 -0
- package/apps/desktop/capture/protocol.ts +689 -0
- package/apps/desktop/cli/analyzer.ts +406 -0
- package/apps/desktop/cli/args.ts +3181 -0
- package/apps/desktop/cli/asset-ingest.ts +747 -0
- package/apps/desktop/cli/atomic-render-effects.ts +210 -0
- package/apps/desktop/cli/atomic-render-platform.ts +205 -0
- package/apps/desktop/cli/atomic-render.ts +50 -0
- package/apps/desktop/cli/audio-analysis.ts +252 -0
- package/apps/desktop/cli/bounded-map.ts +39 -0
- package/apps/desktop/cli/bundle-service.ts +256 -0
- package/apps/desktop/cli/capabilities.ts +212 -0
- package/apps/desktop/cli/capture-bundle.ts +1117 -0
- package/apps/desktop/cli/command-host-resources.ts +251 -0
- package/apps/desktop/cli/commands.ts +6836 -0
- package/apps/desktop/cli/directing-blob.ts +291 -0
- package/apps/desktop/cli/directing-media.ts +517 -0
- package/apps/desktop/cli/directing-quote.ts +89 -0
- package/apps/desktop/cli/directing-service.ts +288 -0
- package/apps/desktop/cli/emoji-assets.ts +538 -0
- package/apps/desktop/cli/errors.ts +29 -0
- package/apps/desktop/cli/face-analysis-service.ts +940 -0
- package/apps/desktop/cli/filter-script.ts +76 -0
- package/apps/desktop/cli/gateway-application-port.ts +1142 -0
- package/apps/desktop/cli/gateway-credential.ts +81 -0
- package/apps/desktop/cli/gateway-media-artifacts.ts +723 -0
- package/apps/desktop/cli/gateway-media-catalog.ts +922 -0
- package/apps/desktop/cli/gateway-media-service.ts +3106 -0
- package/apps/desktop/cli/gateway-media-signature.ts +122 -0
- package/apps/desktop/cli/gateway-provider-options.ts +562 -0
- package/apps/desktop/cli/gateway-scene-provider.ts +391 -0
- package/apps/desktop/cli/help.ts +501 -0
- package/apps/desktop/cli/html-overlay-gpu.ts +97 -0
- package/apps/desktop/cli/html-overlay-renderer.ts +2478 -0
- package/apps/desktop/cli/io.ts +290 -0
- package/apps/desktop/cli/main.ts +123 -0
- package/apps/desktop/cli/media-effects-service.ts +885 -0
- package/apps/desktop/cli/media-ingest-model.ts +364 -0
- package/apps/desktop/cli/media-ingest-platform.ts +227 -0
- package/apps/desktop/cli/media-ingest-program.ts +200 -0
- package/apps/desktop/cli/media-ingest.ts +30 -0
- package/apps/desktop/cli/music-analysis-service.ts +434 -0
- package/apps/desktop/cli/mutation-lock.ts +530 -0
- package/apps/desktop/cli/native-assets.d.ts +12 -0
- package/apps/desktop/cli/native-media-runtime.macos.ts +7 -0
- package/apps/desktop/cli/overlay-playback.ts +177 -0
- package/apps/desktop/cli/paths.ts +265 -0
- package/apps/desktop/cli/portable-surface.ts +242 -0
- package/apps/desktop/cli/project-camera-receipt.ts +112 -0
- package/apps/desktop/cli/project-face-camera.ts +394 -0
- package/apps/desktop/cli/project-inactivity-service.ts +552 -0
- package/apps/desktop/cli/project-media-integrity.ts +121 -0
- package/apps/desktop/cli/project-renderer.ts +1526 -0
- package/apps/desktop/cli/project-service.ts +519 -0
- package/apps/desktop/cli/project-state-transaction.ts +520 -0
- package/apps/desktop/cli/query-limits.ts +1 -0
- package/apps/desktop/cli/recording-controller.ts +1567 -0
- package/apps/desktop/cli/recording-daemon.ts +931 -0
- package/apps/desktop/cli/recording-ref.ts +56 -0
- package/apps/desktop/cli/renamed-environment.ts +6 -0
- package/apps/desktop/cli/renderer.ts +1439 -0
- package/apps/desktop/cli/run-cli-test-helper.ts +60 -0
- package/apps/desktop/cli/scene-analysis-service.ts +879 -0
- package/apps/desktop/cli/spatial-compositor-verifier.ts +213 -0
- package/apps/desktop/cli/spatial-project-service.ts +90 -0
- package/apps/desktop/cli/spatial-scene-service.ts +105 -0
- package/apps/desktop/cli/spatial-world-service.ts +39 -0
- package/apps/desktop/cli/speech-analysis-service.ts +682 -0
- package/apps/desktop/cli/studio-assemble.ts +58 -0
- package/apps/desktop/cli/studio-command.ts +60 -0
- package/apps/desktop/cli/studio-custody.ts +60 -0
- package/apps/desktop/cli/studio-encode.ts +216 -0
- package/apps/desktop/cli/studio-exr.ts +52 -0
- package/apps/desktop/cli/studio-files.ts +179 -0
- package/apps/desktop/cli/studio-output-validation.ts +162 -0
- package/apps/desktop/cli/studio-process.ts +145 -0
- package/apps/desktop/cli/studio-runtime.ts +77 -0
- package/apps/desktop/cli/studio-scaffold.ts +70 -0
- package/apps/desktop/cli/studio-service.ts +301 -0
- package/apps/desktop/cli/studio-spatial-asset.ts +248 -0
- package/apps/desktop/cli/studio-template-names.ts +3 -0
- package/apps/desktop/cli/studio-workflow.ts +19 -0
- package/apps/desktop/cli/time.ts +25 -0
- package/apps/desktop/cli/workflow-code.ts +584 -0
- package/apps/desktop/cli/workflow-runs.ts +576 -0
- package/apps/desktop/code/advanced.ts +139 -0
- package/apps/desktop/code/application-node-planner.ts +1288 -0
- package/apps/desktop/code/compiler.ts +329 -0
- package/apps/desktop/code/contracts.ts +13 -0
- package/apps/desktop/code/creative-recipes.ts +94 -0
- package/apps/desktop/code/define-workflow.ts +115 -0
- package/apps/desktop/code/editing.ts +284 -0
- package/apps/desktop/code/file-candidate-provenance.ts +420 -0
- package/apps/desktop/code/file-candidate.ts +69 -0
- package/apps/desktop/code/graph-builder.ts +10 -0
- package/apps/desktop/code/host-resource-policy.ts +55 -0
- package/apps/desktop/code/plan-contracts.ts +159 -0
- package/apps/desktop/code/planning.ts +361 -0
- package/apps/desktop/code/public.ts +134 -0
- package/apps/desktop/code/reference-projection.ts +62 -0
- package/apps/desktop/code/run-contracts.ts +466 -0
- package/apps/desktop/code/run-store.ts +2478 -0
- package/apps/desktop/code/runtime-identity.ts +328 -0
- package/apps/desktop/code/scheduler.ts +3067 -0
- package/apps/desktop/code/semantic-builder.ts +1436 -0
- package/apps/desktop/code/source-bundle.ts +1641 -0
- package/apps/desktop/code/source-typecheck.ts +211 -0
- package/apps/desktop/code/testing.ts +79 -0
- package/apps/desktop/code/worker-client.ts +1675 -0
- package/apps/desktop/code/worker-effects.ts +141 -0
- package/apps/desktop/code/worker-entry.ts +435 -0
- package/apps/desktop/code/worker-lease-guardian.ts +179 -0
- package/apps/desktop/code/worker-process-identity.ts +484 -0
- package/apps/desktop/code/worker-protocol.ts +211 -0
- package/apps/desktop/code/workflow-effects.ts +132 -0
- package/apps/desktop/contracts/analysis.ts +949 -0
- package/apps/desktop/contracts/edit.ts +576 -0
- package/apps/desktop/contracts/index.ts +9 -0
- package/apps/desktop/contracts/media-effects.ts +139 -0
- package/apps/desktop/contracts/project-render.ts +644 -0
- package/apps/desktop/contracts/project.ts +740 -0
- package/apps/desktop/contracts/recording.ts +1434 -0
- package/apps/desktop/contracts/render.ts +334 -0
- package/apps/desktop/contracts/runtime.ts +155 -0
- package/apps/desktop/contracts/spatial-compositor.ts +58 -0
- package/apps/desktop/contracts/spatial-project.ts +159 -0
- package/apps/desktop/contracts/spatial-world.ts +48 -0
- package/apps/desktop/contracts/video-effects.ts +120 -0
- package/apps/desktop/core/alignment-apply.ts +400 -0
- package/apps/desktop/core/audio-alignment.ts +466 -0
- package/apps/desktop/core/auto-zoom.ts +209 -0
- package/apps/desktop/core/canonical-json.ts +14 -0
- package/apps/desktop/core/emoji.ts +210 -0
- package/apps/desktop/core/events.ts +409 -0
- package/apps/desktop/core/face-framing.ts +533 -0
- package/apps/desktop/core/face-tracks.ts +326 -0
- package/apps/desktop/core/inactivity.ts +137 -0
- package/apps/desktop/core/index.ts +30 -0
- package/apps/desktop/core/intervals.ts +123 -0
- package/apps/desktop/core/montage.ts +236 -0
- package/apps/desktop/core/music-analysis.ts +518 -0
- package/apps/desktop/core/plan.ts +244 -0
- package/apps/desktop/core/project-auto-zoom.ts +75 -0
- package/apps/desktop/core/project-camera-spatial.ts +676 -0
- package/apps/desktop/core/project-camera.ts +403 -0
- package/apps/desktop/core/project-captions.ts +888 -0
- package/apps/desktop/core/project-inactivity.ts +125 -0
- package/apps/desktop/core/project-metadata.ts +927 -0
- package/apps/desktop/core/project-plan.ts +375 -0
- package/apps/desktop/core/project-render-plan.ts +303 -0
- package/apps/desktop/core/project-time.ts +469 -0
- package/apps/desktop/core/render-plan.ts +442 -0
- package/apps/desktop/core/scene-cache.ts +174 -0
- package/apps/desktop/core/scene-sampling.ts +448 -0
- package/apps/desktop/core/spatial-compositor.ts +22 -0
- package/apps/desktop/core/spatial-durability.ts +99 -0
- package/apps/desktop/core/spatial-project.ts +169 -0
- package/apps/desktop/core/spatial-shot-clock.ts +32 -0
- package/apps/desktop/core/speech-analysis.ts +333 -0
- package/apps/desktop/core/storage.ts +970 -0
- package/apps/desktop/core/time-map.ts +91 -0
- package/apps/desktop/core/time.ts +61 -0
- package/apps/desktop/core/video-effects.ts +660 -0
- package/apps/desktop/dist/cli/NebulaSans-Bold-26se8aek.otf +0 -0
- package/apps/desktop/dist/cli/NebulaSans-Bold-bcz7y08t.woff2 +0 -0
- package/apps/desktop/dist/cli/NebulaSans-Book-5ax05zvn.woff2 +0 -0
- package/apps/desktop/dist/cli/NebulaSans-Book-8cenzchw.otf +0 -0
- package/apps/desktop/dist/cli/main.js +5086 -0
- package/apps/desktop/html-overlay/catalog.ts +171 -0
- package/apps/desktop/html-overlay/contracts.ts +372 -0
- package/apps/desktop/html-overlay/execution-profile.ts +77 -0
- package/apps/desktop/html-overlay/index.ts +98 -0
- package/apps/desktop/html-overlay/libraries.ts +263 -0
- package/apps/desktop/html-overlay/random.ts +49 -0
- package/apps/desktop/html-overlay/runtime.ts +813 -0
- package/apps/desktop/html-overlay/scaffolds.ts +792 -0
- package/apps/desktop/html-overlay/spark-runtime.ts +88 -0
- package/apps/desktop/html-overlay/spatial-splat-runtime.ts +60 -0
- package/apps/desktop/html-overlay/spatial.ts +720 -0
- package/apps/desktop/studio/assets/README.md +39 -0
- package/apps/desktop/studio/assets/contracts.ts +129 -0
- package/apps/desktop/studio/assets/network.ts +64 -0
- package/apps/desktop/studio/assets/poly-haven.ts +148 -0
- package/apps/desktop/studio/drivers/blender_driver.py +466 -0
- package/apps/desktop/studio/drivers/cadquery_driver.py +176 -0
- package/apps/desktop/studio/education/driver.py +308 -0
- package/apps/desktop/studio/education/lesson.py +180 -0
- package/apps/desktop/studio/education/toolkit.py +249 -0
- package/apps/desktop/workflows/chaptered-demo.ts +48 -0
- package/apps/desktop/workflows/creative-iteration.ts +107 -0
- package/apps/desktop/workflows/creative-selection.ts +92 -0
- package/apps/desktop/workflows/fragments.ts +486 -0
- package/apps/desktop/workflows/index.ts +93 -0
- package/apps/desktop/workflows/polished-screen-demo.ts +128 -0
- package/apps/desktop/workflows/scene-direction.ts +12 -0
- package/apps/desktop/workflows/social-variants.ts +75 -0
- package/apps/desktop/workflows/talking-head-cleanup.ts +53 -0
- package/dist/NebulaSans-Bold-26se8aek.otf +0 -0
- package/dist/NebulaSans-Bold-bcz7y08t.woff2 +0 -0
- package/dist/NebulaSans-Book-5ax05zvn.woff2 +0 -0
- package/dist/NebulaSans-Book-8cenzchw.otf +0 -0
- package/dist/cli.js +479 -0
- package/dist/code/advanced.js +270 -0
- package/dist/code/index.js +2240 -0
- package/dist/generate.js +24 -0
- package/dist/host-resources.js +26 -0
- package/dist/index-42zsesc1.js +2257 -0
- package/dist/index-7308egqr.js +48 -0
- package/dist/index-8txs6fkn.js +712 -0
- package/dist/index-mcy8z0br.js +192 -0
- package/dist/index-p63wavx0.js +2029 -0
- package/dist/index-r7gdhmsp.js +424 -0
- package/dist/index-sh6xbav6.js +1219 -0
- package/dist/index-z1w83f81.js +4 -0
- package/dist/index-z7239b4h.js +2363 -0
- package/dist/index-zfeg9hmw.js +2013 -0
- package/dist/index.js +241 -0
- package/dist/operations.js +29 -0
- package/dist/skill-install-dh0ntqba.js +10 -0
- package/dist/vectorize/worker.js +142 -0
- package/dist/workflow.js +16 -0
- package/docs/README.md +38 -0
- package/docs/studio.md +215 -0
- package/examples/studio/blender/README.md +26 -0
- package/examples/studio/blender/character.py +152 -0
- package/examples/studio/blender/cloth.py +101 -0
- package/examples/studio/blender/color_chart.py +29 -0
- package/examples/studio/blender/fluid.py +78 -0
- package/examples/studio/blender/import_model.py +74 -0
- package/examples/studio/blender/product.py +53 -0
- package/examples/studio/blender/shaded_street.py +481 -0
- package/examples/studio/blender/studio_scene.py +147 -0
- package/examples/studio/cadquery/README.md +13 -0
- package/examples/studio/cadquery/bracket.py +36 -0
- package/examples/studio/cadquery/import_step.py +12 -0
- package/examples/studio/education/README.md +46 -0
- package/examples/studio/education/lesson.json +99 -0
- package/examples/studio/education/scene.py +7 -0
- package/examples/studio/hybrid-scene.ts +96 -0
- package/examples/studio/native-workflow.ts +14 -0
- package/examples/studio/vgpu/README.md +64 -0
- package/examples/studio/vgpu/heat-field.mjs +290 -0
- package/package.json +304 -0
- package/packages/scene/src/index.ts +1 -0
- package/packages/scene/src/scene.ts +215 -0
- package/schema/diagram.schema.json +334 -0
- package/skills/slopcamera/SKILL.md +45 -0
- package/skills/slopcamera/agents/openai.yaml +4 -0
- package/skills/slopcamera/references/customization.md +77 -0
- package/skills/slopcamera/references/diagrams.md +153 -0
- package/skills/slopcamera/references/directed-scenes.md +42 -0
- package/skills/slopcamera/references/directing-video.md +18 -0
- package/skills/slopcamera/references/educational-video.md +26 -0
- package/skills/slopcamera/references/gateway-media.md +134 -0
- package/skills/slopcamera/references/install.md +62 -0
- package/skills/slopcamera/references/native-studio.md +31 -0
- package/skills/slopcamera/references/reference-led-3d.md +140 -0
- package/skills/slopcamera/references/rubber-stamp-examples/poster-example-1.jpg +0 -0
- package/skills/slopcamera/references/rubber-stamp-examples/poster-example-2.jpg +0 -0
- package/skills/slopcamera/references/rubber-stamp-examples/stamp-style-1.png +0 -0
- package/skills/slopcamera/references/rubber-stamp-examples/stamp-style-2.png +0 -0
- package/skills/slopcamera/references/rubber-stamp-field-notes.md +144 -0
- package/skills/slopcamera/references/vectorization.md +29 -0
- package/skills/slopcamera/references/video-projects.md +187 -0
- package/skills/slopcamera/references/visual-communication.md +120 -0
- package/skills/slopcamera/references/workflows-sdk.md +45 -0
- package/skills/slopcamera/scripts/compose-rubber-stamp-field-note.ts +229 -0
- package/src/artifacts.ts +106 -0
- package/src/assets/fonts/nebula-sans/LICENSE.txt +96 -0
- package/src/assets/fonts/nebula-sans/NebulaSans-Bold.otf +0 -0
- package/src/assets/fonts/nebula-sans/NebulaSans-Bold.woff2 +0 -0
- package/src/assets/fonts/nebula-sans/NebulaSans-Book.otf +0 -0
- package/src/assets/fonts/nebula-sans/NebulaSans-Book.woff2 +0 -0
- package/src/assets/fonts/nebula-sans/PROVENANCE.md +21 -0
- package/src/assets.d.ts +9 -0
- package/src/cli.ts +601 -0
- package/src/cloud-errors.ts +26 -0
- package/src/code/advanced.ts +10 -0
- package/src/code/boundary.ts +21 -0
- package/src/code/canonical-json.ts +129 -0
- package/src/code/compiler.ts +953 -0
- package/src/code/contracts.ts +576 -0
- package/src/code/define-workflow.ts +334 -0
- package/src/code/errors.ts +41 -0
- package/src/code/graph-builder.ts +945 -0
- package/src/code/index.ts +121 -0
- package/src/code/json-snapshot.ts +899 -0
- package/src/code/json-utf8.ts +88 -0
- package/src/code/portable-builder.ts +136 -0
- package/src/code/projection.ts +378 -0
- package/src/code/public-operations.ts +450 -0
- package/src/code/runtime.ts +580 -0
- package/src/code/sha256.ts +36 -0
- package/src/config.ts +180 -0
- package/src/desktop.ts +253 -0
- package/src/fs.ts +10 -0
- package/src/generate.ts +704 -0
- package/src/host-resource-posix.ts +161 -0
- package/src/host-resources.ts +1749 -0
- package/src/icons.ts +58 -0
- package/src/index.ts +200 -0
- package/src/label-layout.ts +130 -0
- package/src/layout.ts +413 -0
- package/src/lint.ts +127 -0
- package/src/mcp/boundary.ts +354 -0
- package/src/mcp/index.ts +33 -0
- package/src/mcp/server.ts +272 -0
- package/src/mcp/tools.ts +963 -0
- package/src/mcp/types.ts +71 -0
- package/src/operations.ts +775 -0
- package/src/parse.ts +861 -0
- package/src/process-environment.ts +17 -0
- package/src/render.ts +437 -0
- package/src/skill-install.ts +49 -0
- package/src/spatial-scene/authoring.ts +27 -0
- package/src/spatial-scene/camera-track.ts +83 -0
- package/src/spatial-scene/contracts.ts +277 -0
- package/src/spatial-scene/evaluate.ts +145 -0
- package/src/spatial-scene/gltf.ts +558 -0
- package/src/spatial-scene/identity.ts +260 -0
- package/src/spatial-scene/index.ts +10 -0
- package/src/spatial-scene/inspect.ts +68 -0
- package/src/spatial-scene/math.ts +280 -0
- package/src/spatial-scene/patch.ts +131 -0
- package/src/spatial-scene/time.ts +53 -0
- package/src/studio/contracts.ts +129 -0
- package/src/studio/index.ts +2 -0
- package/src/studio/plan.ts +156 -0
- package/src/studio/shared.ts +41 -0
- package/src/theme.ts +68 -0
- package/src/tldr.ts +474 -0
- package/src/types.ts +212 -0
- package/src/vectorize/archive.ts +101 -0
- package/src/vectorize/command.ts +797 -0
- package/src/vectorize/index.ts +27 -0
- package/src/vectorize/limits.ts +61 -0
- package/src/vectorize/metrics.ts +351 -0
- package/src/vectorize/pixels.ts +323 -0
- package/src/vectorize/supervisor.ts +339 -0
- package/src/vectorize/svg.ts +188 -0
- package/src/vectorize/tool.ts +542 -0
- package/src/vectorize/types.ts +113 -0
- package/src/vectorize/vectorize.ts +634 -0
- package/src/vectorize/worker-protocol.ts +40 -0
- package/src/vectorize/worker.ts +209 -0
- package/src/version.ts +1 -0
- package/src/workflow.ts +409 -0
|
@@ -0,0 +1,797 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from "node:async_hooks"
|
|
2
|
+
import { constants } from "node:fs"
|
|
3
|
+
import {
|
|
4
|
+
lstat,
|
|
5
|
+
mkdtemp,
|
|
6
|
+
open,
|
|
7
|
+
rmdir,
|
|
8
|
+
unlink,
|
|
9
|
+
type FileHandle,
|
|
10
|
+
} from "node:fs/promises"
|
|
11
|
+
import { join } from "node:path"
|
|
12
|
+
import { nonGatewayChildEnvironment } from "../process-environment.js"
|
|
13
|
+
import { VectorizeError, type VectorizeErrorCode } from "./types.js"
|
|
14
|
+
|
|
15
|
+
const MAX_COMMAND_OUTPUT_BYTES = 64 * 1_024
|
|
16
|
+
const PRIMARY_OUTPUT_READ_BYTES = 64 * 1_024
|
|
17
|
+
const PRIMARY_OUTPUT_POLL_MS = 2
|
|
18
|
+
const PRIMARY_OUTPUT_EXIT_EMPTY_POLLS = 2
|
|
19
|
+
const TERMINATION_GRACE_MS = 50
|
|
20
|
+
const HARD_KILL_WAIT_MS = 500
|
|
21
|
+
const MAX_INHERITED_FILE_DESCRIPTORS = 16
|
|
22
|
+
|
|
23
|
+
const timeoutMarker = Symbol("bounded-command-timeout")
|
|
24
|
+
const noCommandFailure = Symbol("no-command-failure")
|
|
25
|
+
const activePosixProcessGroups = new Set<number>()
|
|
26
|
+
let workerTerminationForwardingInstalled = false
|
|
27
|
+
const inheritedCommandFileDescriptors = new AsyncLocalStorage<readonly number[]>()
|
|
28
|
+
|
|
29
|
+
export interface CommandResult {
|
|
30
|
+
readonly stderr: string
|
|
31
|
+
readonly stdout: string
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface BoundedCommandOptions {
|
|
35
|
+
/**
|
|
36
|
+
* Open descriptors explicitly duplicated into the child from descriptor 3.
|
|
37
|
+
* Values are local execution authority and never enter receipts or logs.
|
|
38
|
+
*/
|
|
39
|
+
readonly inheritedFileDescriptors?: readonly number[]
|
|
40
|
+
/** Bounded bytes delivered to the child on standard input. */
|
|
41
|
+
readonly stdin?: Uint8Array
|
|
42
|
+
/** Raise the stdout quota only for a caller that consumes bounded data there. */
|
|
43
|
+
readonly maxStdoutBytes?: number
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function normalizedInheritedFileDescriptors(
|
|
47
|
+
value: readonly number[] | undefined,
|
|
48
|
+
): readonly number[] {
|
|
49
|
+
const descriptors = value ?? []
|
|
50
|
+
if (
|
|
51
|
+
descriptors.length > MAX_INHERITED_FILE_DESCRIPTORS
|
|
52
|
+
|| descriptors.some((descriptor, index) => (
|
|
53
|
+
!Number.isSafeInteger(descriptor)
|
|
54
|
+
|| descriptor < 0
|
|
55
|
+
|| descriptor > 2_147_483_647
|
|
56
|
+
|| descriptors.indexOf(descriptor) !== index
|
|
57
|
+
))
|
|
58
|
+
) {
|
|
59
|
+
throw new VectorizeError(
|
|
60
|
+
"invalid_input",
|
|
61
|
+
"Inherited vectorizer descriptors must be unique bounded integers.",
|
|
62
|
+
)
|
|
63
|
+
}
|
|
64
|
+
return Object.freeze([...descriptors])
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export async function withInheritedCommandFileDescriptors<T>(
|
|
68
|
+
descriptors: readonly number[] | undefined,
|
|
69
|
+
callback: () => T | Promise<T>,
|
|
70
|
+
): Promise<T> {
|
|
71
|
+
const normalized = normalizedInheritedFileDescriptors(descriptors)
|
|
72
|
+
return await inheritedCommandFileDescriptors.run(
|
|
73
|
+
normalized,
|
|
74
|
+
async () => await callback(),
|
|
75
|
+
)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export interface BoundedPathOutputOptions extends BoundedCommandOptions {
|
|
79
|
+
/** Maximum bytes accepted from the command's pathname-based primary output. */
|
|
80
|
+
readonly maxOutputBytes: number
|
|
81
|
+
/** Existing private directory in which to create the per-call output endpoint. */
|
|
82
|
+
readonly temporaryRoot: string
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export interface PathOutputCommandResult extends CommandResult {
|
|
86
|
+
readonly output: string
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
interface ManagedSubprocess {
|
|
90
|
+
readonly exitCode: number | null
|
|
91
|
+
readonly exited: Promise<number>
|
|
92
|
+
kill(signal?: number | NodeJS.Signals): void
|
|
93
|
+
readonly pid: number
|
|
94
|
+
readonly stderr: ReadableStream<Uint8Array>
|
|
95
|
+
readonly stdout: ReadableStream<Uint8Array>
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Forward supervisor termination into every command group owned by the
|
|
100
|
+
* isolated worker. This is process-local and called only by the worker
|
|
101
|
+
* entrypoint before conversion begins.
|
|
102
|
+
*/
|
|
103
|
+
export function forwardVectorizeWorkerTermination(): void {
|
|
104
|
+
if (
|
|
105
|
+
process.platform === "win32" ||
|
|
106
|
+
workerTerminationForwardingInstalled
|
|
107
|
+
) {
|
|
108
|
+
return
|
|
109
|
+
}
|
|
110
|
+
workerTerminationForwardingInstalled = true
|
|
111
|
+
const signals = [
|
|
112
|
+
["SIGHUP", 129],
|
|
113
|
+
["SIGINT", 130],
|
|
114
|
+
["SIGTERM", 143],
|
|
115
|
+
] as const
|
|
116
|
+
for (const [signal, exitCode] of signals) {
|
|
117
|
+
process.once(signal, () => {
|
|
118
|
+
for (const pid of activePosixProcessGroups) {
|
|
119
|
+
safelyKillPosixProcessGroupByPid(pid, "SIGKILL")
|
|
120
|
+
}
|
|
121
|
+
process.exit(exitCode)
|
|
122
|
+
})
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export async function runBoundedCommand(
|
|
127
|
+
command: readonly string[],
|
|
128
|
+
timeoutMs: number,
|
|
129
|
+
failureCode: VectorizeErrorCode,
|
|
130
|
+
options: BoundedCommandOptions = {},
|
|
131
|
+
): Promise<CommandResult> {
|
|
132
|
+
return runBoundedCommandInternal(command, timeoutMs, failureCode, options)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Run a command whose primary output must be a pathname while retaining a
|
|
137
|
+
* bounded streaming boundary. A private FIFO gives pathname-only tools a
|
|
138
|
+
* portable output argument and applies backpressure before output can exceed
|
|
139
|
+
* the in-memory quota.
|
|
140
|
+
*/
|
|
141
|
+
export async function runBoundedPathOutputCommand(
|
|
142
|
+
commandForOutput: (outputPath: string) => readonly string[],
|
|
143
|
+
timeoutMs: number,
|
|
144
|
+
failureCode: VectorizeErrorCode,
|
|
145
|
+
options: BoundedPathOutputOptions,
|
|
146
|
+
): Promise<PathOutputCommandResult> {
|
|
147
|
+
if (process.platform === "win32") {
|
|
148
|
+
throw new VectorizeError(
|
|
149
|
+
"tool_platform",
|
|
150
|
+
"Bounded pathname output is unavailable on Windows.",
|
|
151
|
+
{ platform: process.platform },
|
|
152
|
+
)
|
|
153
|
+
}
|
|
154
|
+
assertPositiveLimit(
|
|
155
|
+
options.maxOutputBytes,
|
|
156
|
+
"The command primary-output limit must be positive.",
|
|
157
|
+
)
|
|
158
|
+
if (timeoutMs < 1) {
|
|
159
|
+
throw new VectorizeError("timeout", "VTracer exceeded the conversion time limit.")
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const startedAt = performance.now()
|
|
163
|
+
let anchorHandle: FileHandle | undefined
|
|
164
|
+
let directoryHandle: FileHandle | undefined
|
|
165
|
+
let outputDirectory: string | undefined
|
|
166
|
+
let readerHandle: FileHandle | undefined
|
|
167
|
+
let result: PathOutputCommandResult | undefined
|
|
168
|
+
let failure: unknown | typeof noCommandFailure = noCommandFailure
|
|
169
|
+
try {
|
|
170
|
+
outputDirectory = await mkdtemp(
|
|
171
|
+
join(options.temporaryRoot, "slopcamera-command-output-"),
|
|
172
|
+
)
|
|
173
|
+
directoryHandle = await open(
|
|
174
|
+
outputDirectory,
|
|
175
|
+
constants.O_RDONLY | constants.O_DIRECTORY | constants.O_NOFOLLOW,
|
|
176
|
+
)
|
|
177
|
+
await assertOpenedPrivateDirectory(outputDirectory, directoryHandle)
|
|
178
|
+
const outputPath = join(outputDirectory, "output.svg")
|
|
179
|
+
await createPrivateFifo(
|
|
180
|
+
outputPath,
|
|
181
|
+
remainingCommandTime(startedAt, timeoutMs),
|
|
182
|
+
failureCode,
|
|
183
|
+
)
|
|
184
|
+
anchorHandle = await open(
|
|
185
|
+
outputPath,
|
|
186
|
+
constants.O_RDWR | constants.O_NONBLOCK | constants.O_NOFOLLOW,
|
|
187
|
+
)
|
|
188
|
+
readerHandle = await open(
|
|
189
|
+
outputPath,
|
|
190
|
+
constants.O_RDONLY | constants.O_NONBLOCK | constants.O_NOFOLLOW,
|
|
191
|
+
)
|
|
192
|
+
await assertOpenedPrivateFifo(outputPath, anchorHandle, readerHandle)
|
|
193
|
+
// VTracer only needs to traverse this directory and open the FIFO. Removing
|
|
194
|
+
// directory write permission prevents accidental replacement of the path
|
|
195
|
+
// between verification and command startup.
|
|
196
|
+
await directoryHandle.chmod(0o500)
|
|
197
|
+
|
|
198
|
+
const commandResult = await runBoundedCommandInternal(
|
|
199
|
+
commandForOutput(outputPath),
|
|
200
|
+
remainingCommandTime(startedAt, timeoutMs),
|
|
201
|
+
failureCode,
|
|
202
|
+
options,
|
|
203
|
+
{
|
|
204
|
+
handle: readerHandle,
|
|
205
|
+
maximumBytes: options.maxOutputBytes,
|
|
206
|
+
},
|
|
207
|
+
)
|
|
208
|
+
if (commandResult.primaryOutput === undefined) {
|
|
209
|
+
throw new VectorizeError(
|
|
210
|
+
failureCode,
|
|
211
|
+
"VTracer did not expose its bounded primary output.",
|
|
212
|
+
)
|
|
213
|
+
}
|
|
214
|
+
result = {
|
|
215
|
+
output: commandResult.primaryOutput,
|
|
216
|
+
stderr: commandResult.stderr,
|
|
217
|
+
stdout: commandResult.stdout,
|
|
218
|
+
}
|
|
219
|
+
} catch (error) {
|
|
220
|
+
failure =
|
|
221
|
+
error instanceof VectorizeError
|
|
222
|
+
? error
|
|
223
|
+
: executionError(failureCode, error)
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const cleanupError = await removePrimaryOutputEndpoint(
|
|
227
|
+
outputDirectory,
|
|
228
|
+
outputDirectory === undefined
|
|
229
|
+
? undefined
|
|
230
|
+
: join(outputDirectory, "output.svg"),
|
|
231
|
+
readerHandle,
|
|
232
|
+
anchorHandle,
|
|
233
|
+
directoryHandle,
|
|
234
|
+
)
|
|
235
|
+
if (failure !== noCommandFailure) {
|
|
236
|
+
if (cleanupError === undefined) throw failure
|
|
237
|
+
throw new VectorizeError(
|
|
238
|
+
failure instanceof VectorizeError ? failure.code : failureCode,
|
|
239
|
+
`${failure instanceof Error ? failure.message : String(failure)} The temporary primary-output endpoint also could not be removed.`,
|
|
240
|
+
{
|
|
241
|
+
...(failure instanceof VectorizeError ? failure.details : {}),
|
|
242
|
+
cleanup: String(cleanupError),
|
|
243
|
+
},
|
|
244
|
+
{ cause: failure instanceof Error ? failure : undefined },
|
|
245
|
+
)
|
|
246
|
+
}
|
|
247
|
+
if (cleanupError !== undefined) {
|
|
248
|
+
throw new VectorizeError(
|
|
249
|
+
failureCode,
|
|
250
|
+
"The temporary primary-output endpoint could not be removed.",
|
|
251
|
+
{ cleanup: String(cleanupError) },
|
|
252
|
+
{ cause: cleanupError instanceof Error ? cleanupError : undefined },
|
|
253
|
+
)
|
|
254
|
+
}
|
|
255
|
+
if (result === undefined) {
|
|
256
|
+
throw new VectorizeError(
|
|
257
|
+
failureCode,
|
|
258
|
+
"The bounded command completed without a result.",
|
|
259
|
+
)
|
|
260
|
+
}
|
|
261
|
+
return result
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
interface PrimaryOutputReader {
|
|
265
|
+
readonly handle: FileHandle
|
|
266
|
+
readonly maximumBytes: number
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
interface InternalCommandResult extends CommandResult {
|
|
270
|
+
readonly primaryOutput?: string
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
async function runBoundedCommandInternal(
|
|
274
|
+
command: readonly string[],
|
|
275
|
+
timeoutMs: number,
|
|
276
|
+
failureCode: VectorizeErrorCode,
|
|
277
|
+
options: BoundedCommandOptions,
|
|
278
|
+
primaryOutput?: PrimaryOutputReader,
|
|
279
|
+
): Promise<InternalCommandResult> {
|
|
280
|
+
if (command.length === 0) {
|
|
281
|
+
throw new VectorizeError("invalid_input", "A bounded command requires a command.")
|
|
282
|
+
}
|
|
283
|
+
if (timeoutMs < 1) {
|
|
284
|
+
throw new VectorizeError("timeout", "VTracer exceeded the conversion time limit.")
|
|
285
|
+
}
|
|
286
|
+
const maxStdoutBytes = options.maxStdoutBytes ?? MAX_COMMAND_OUTPUT_BYTES
|
|
287
|
+
assertPositiveLimit(maxStdoutBytes, "The command stdout limit must be positive.")
|
|
288
|
+
const inheritedDescriptors = normalizedInheritedFileDescriptors(
|
|
289
|
+
options.inheritedFileDescriptors
|
|
290
|
+
?? inheritedCommandFileDescriptors.getStore(),
|
|
291
|
+
)
|
|
292
|
+
|
|
293
|
+
let child: ManagedSubprocess
|
|
294
|
+
const ownsProcessGroup = process.platform !== "win32"
|
|
295
|
+
try {
|
|
296
|
+
child = Bun.spawn([...command], {
|
|
297
|
+
detached: ownsProcessGroup,
|
|
298
|
+
env: nonGatewayChildEnvironment(),
|
|
299
|
+
stdio: [
|
|
300
|
+
options.stdin ?? "ignore",
|
|
301
|
+
"pipe",
|
|
302
|
+
"pipe",
|
|
303
|
+
...inheritedDescriptors,
|
|
304
|
+
],
|
|
305
|
+
windowsHide: true,
|
|
306
|
+
})
|
|
307
|
+
} catch (error) {
|
|
308
|
+
throw executionError(failureCode, error)
|
|
309
|
+
}
|
|
310
|
+
if (process.platform !== "win32") {
|
|
311
|
+
activePosixProcessGroups.add(child.pid)
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
const streamAbort = new AbortController()
|
|
315
|
+
let childHasExited = false
|
|
316
|
+
const childExitTask = child.exited.then((exitCode) => {
|
|
317
|
+
childHasExited = true
|
|
318
|
+
if (ownsProcessGroup && process.platform !== "win32") {
|
|
319
|
+
// A leader can exit while background descendants still own its pipes or
|
|
320
|
+
// pathname output. The group is private to this command, so terminate it
|
|
321
|
+
// immediately instead of waiting for descendant-held streams to reach
|
|
322
|
+
// EOF and turning a completed leader into a timeout.
|
|
323
|
+
safelyKillPosixProcessGroup(child, "SIGKILL")
|
|
324
|
+
}
|
|
325
|
+
return exitCode
|
|
326
|
+
})
|
|
327
|
+
const stdoutTask = readBoundedText(
|
|
328
|
+
child.stdout,
|
|
329
|
+
maxStdoutBytes,
|
|
330
|
+
streamAbort.signal,
|
|
331
|
+
"VTracer emitted too much primary output.",
|
|
332
|
+
)
|
|
333
|
+
const stderrTask = readBoundedText(
|
|
334
|
+
child.stderr,
|
|
335
|
+
MAX_COMMAND_OUTPUT_BYTES,
|
|
336
|
+
streamAbort.signal,
|
|
337
|
+
"VTracer emitted too much diagnostic output.",
|
|
338
|
+
)
|
|
339
|
+
const primaryOutputTask =
|
|
340
|
+
primaryOutput === undefined
|
|
341
|
+
? Promise.resolve<string | undefined>(undefined)
|
|
342
|
+
: readBoundedFifo(
|
|
343
|
+
primaryOutput.handle,
|
|
344
|
+
primaryOutput.maximumBytes,
|
|
345
|
+
streamAbort.signal,
|
|
346
|
+
() => childHasExited,
|
|
347
|
+
)
|
|
348
|
+
const executionTask = Promise.all([
|
|
349
|
+
childExitTask,
|
|
350
|
+
stdoutTask,
|
|
351
|
+
stderrTask,
|
|
352
|
+
primaryOutputTask,
|
|
353
|
+
]).then(([exitCode, stdout, stderr, boundedPrimaryOutput]) => {
|
|
354
|
+
if (exitCode !== 0) {
|
|
355
|
+
throw new VectorizeError(
|
|
356
|
+
failureCode,
|
|
357
|
+
[
|
|
358
|
+
`Command failed (${exitCode}): ${command[0] ?? "unknown"}`,
|
|
359
|
+
stderr.trim(),
|
|
360
|
+
]
|
|
361
|
+
.filter(Boolean)
|
|
362
|
+
.join("\n"),
|
|
363
|
+
{ exitCode },
|
|
364
|
+
)
|
|
365
|
+
}
|
|
366
|
+
return boundedPrimaryOutput === undefined
|
|
367
|
+
? { stderr, stdout }
|
|
368
|
+
: { primaryOutput: boundedPrimaryOutput, stderr, stdout }
|
|
369
|
+
})
|
|
370
|
+
|
|
371
|
+
let timer: ReturnType<typeof setTimeout> | undefined
|
|
372
|
+
const timeoutTask = new Promise<never>((_resolve, reject) => {
|
|
373
|
+
timer = setTimeout(() => reject(timeoutMarker), timeoutMs)
|
|
374
|
+
})
|
|
375
|
+
|
|
376
|
+
try {
|
|
377
|
+
return await Promise.race([executionTask, timeoutTask])
|
|
378
|
+
} catch (error) {
|
|
379
|
+
let cleanupError: unknown
|
|
380
|
+
try {
|
|
381
|
+
await terminateAndWait(child, ownsProcessGroup)
|
|
382
|
+
} catch (caught) {
|
|
383
|
+
cleanupError = caught
|
|
384
|
+
} finally {
|
|
385
|
+
streamAbort.abort()
|
|
386
|
+
await settlesWithin(
|
|
387
|
+
Promise.allSettled([stdoutTask, stderrTask, primaryOutputTask]),
|
|
388
|
+
HARD_KILL_WAIT_MS,
|
|
389
|
+
)
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
if (error === timeoutMarker) {
|
|
393
|
+
throw new VectorizeError(
|
|
394
|
+
"timeout",
|
|
395
|
+
cleanupError === undefined
|
|
396
|
+
? "VTracer exceeded the conversion time limit."
|
|
397
|
+
: "VTracer exceeded the conversion time limit and did not terminate cleanly.",
|
|
398
|
+
cleanupError === undefined ? {} : { cleanup: String(cleanupError) },
|
|
399
|
+
)
|
|
400
|
+
}
|
|
401
|
+
if (error instanceof VectorizeError) throw error
|
|
402
|
+
if (cleanupError instanceof VectorizeError) throw cleanupError
|
|
403
|
+
throw executionError(failureCode, error)
|
|
404
|
+
} finally {
|
|
405
|
+
if (timer !== undefined) clearTimeout(timer)
|
|
406
|
+
activePosixProcessGroups.delete(child.pid)
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
function assertPositiveLimit(value: number, message: string): void {
|
|
411
|
+
if (!Number.isSafeInteger(value) || value < 1) {
|
|
412
|
+
throw new VectorizeError("invalid_input", message)
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
function remainingCommandTime(startedAt: number, timeoutMs: number): number {
|
|
417
|
+
return Math.floor(timeoutMs - (performance.now() - startedAt))
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
async function createPrivateFifo(
|
|
421
|
+
path: string,
|
|
422
|
+
timeoutMs: number,
|
|
423
|
+
failureCode: VectorizeErrorCode,
|
|
424
|
+
): Promise<void> {
|
|
425
|
+
await runBoundedCommandInternal(
|
|
426
|
+
["mkfifo", "-m", "600", path],
|
|
427
|
+
timeoutMs,
|
|
428
|
+
failureCode,
|
|
429
|
+
{},
|
|
430
|
+
)
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
async function assertOpenedPrivateDirectory(
|
|
434
|
+
path: string,
|
|
435
|
+
directory: FileHandle,
|
|
436
|
+
): Promise<void> {
|
|
437
|
+
const [pathMetadata, openedMetadata] = await Promise.all([
|
|
438
|
+
lstat(path),
|
|
439
|
+
directory.stat(),
|
|
440
|
+
])
|
|
441
|
+
if (
|
|
442
|
+
!pathMetadata.isDirectory() ||
|
|
443
|
+
!openedMetadata.isDirectory() ||
|
|
444
|
+
pathMetadata.dev !== openedMetadata.dev ||
|
|
445
|
+
pathMetadata.ino !== openedMetadata.ino
|
|
446
|
+
) {
|
|
447
|
+
throw new VectorizeError(
|
|
448
|
+
"trace_failed",
|
|
449
|
+
"The bounded primary-output directory changed during setup.",
|
|
450
|
+
)
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
async function assertOpenedPrivateFifo(
|
|
455
|
+
path: string,
|
|
456
|
+
anchor: FileHandle,
|
|
457
|
+
reader: FileHandle,
|
|
458
|
+
): Promise<void> {
|
|
459
|
+
const [pathMetadata, anchorMetadata, readerMetadata] = await Promise.all([
|
|
460
|
+
lstat(path),
|
|
461
|
+
anchor.stat(),
|
|
462
|
+
reader.stat(),
|
|
463
|
+
])
|
|
464
|
+
if (
|
|
465
|
+
!pathMetadata.isFIFO() ||
|
|
466
|
+
!anchorMetadata.isFIFO() ||
|
|
467
|
+
!readerMetadata.isFIFO() ||
|
|
468
|
+
pathMetadata.dev !== anchorMetadata.dev ||
|
|
469
|
+
pathMetadata.ino !== anchorMetadata.ino ||
|
|
470
|
+
anchorMetadata.dev !== readerMetadata.dev ||
|
|
471
|
+
anchorMetadata.ino !== readerMetadata.ino
|
|
472
|
+
) {
|
|
473
|
+
throw new VectorizeError(
|
|
474
|
+
"trace_failed",
|
|
475
|
+
"The bounded primary-output endpoint changed during setup.",
|
|
476
|
+
)
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
async function removePrimaryOutputEndpoint(
|
|
481
|
+
directoryPath: string | undefined,
|
|
482
|
+
path: string | undefined,
|
|
483
|
+
reader: FileHandle | undefined,
|
|
484
|
+
anchor: FileHandle | undefined,
|
|
485
|
+
directoryHandle: FileHandle | undefined,
|
|
486
|
+
): Promise<unknown | undefined> {
|
|
487
|
+
let cleanupError: unknown
|
|
488
|
+
if (directoryPath !== undefined && directoryHandle === undefined) {
|
|
489
|
+
try {
|
|
490
|
+
// This branch is reachable only if opening the freshly-created empty
|
|
491
|
+
// directory failed. rmdir never follows a replacement symlink.
|
|
492
|
+
await rmdir(directoryPath)
|
|
493
|
+
} catch (error) {
|
|
494
|
+
cleanupError ??= error
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
if (directoryPath !== undefined && directoryHandle !== undefined) {
|
|
498
|
+
await directoryHandle.chmod(0o700).catch((error: unknown) => {
|
|
499
|
+
cleanupError ??= error
|
|
500
|
+
})
|
|
501
|
+
try {
|
|
502
|
+
const [pathDirectoryMetadata, openedDirectoryMetadata] = await Promise.all([
|
|
503
|
+
lstat(directoryPath),
|
|
504
|
+
directoryHandle.stat(),
|
|
505
|
+
])
|
|
506
|
+
if (
|
|
507
|
+
!pathDirectoryMetadata.isDirectory() ||
|
|
508
|
+
!openedDirectoryMetadata.isDirectory() ||
|
|
509
|
+
pathDirectoryMetadata.dev !== openedDirectoryMetadata.dev ||
|
|
510
|
+
pathDirectoryMetadata.ino !== openedDirectoryMetadata.ino
|
|
511
|
+
) {
|
|
512
|
+
throw new VectorizeError(
|
|
513
|
+
"trace_failed",
|
|
514
|
+
"The bounded primary-output directory changed before cleanup.",
|
|
515
|
+
)
|
|
516
|
+
}
|
|
517
|
+
if (path !== undefined && cleanupError === undefined) {
|
|
518
|
+
const pathMetadata = await lstat(path).catch((error: unknown) => {
|
|
519
|
+
if (isFileSystemError(error, "ENOENT")) return undefined
|
|
520
|
+
throw error
|
|
521
|
+
})
|
|
522
|
+
const openedMetadata = await (reader ?? anchor)?.stat()
|
|
523
|
+
if (pathMetadata === undefined && openedMetadata !== undefined) {
|
|
524
|
+
throw new VectorizeError(
|
|
525
|
+
"trace_failed",
|
|
526
|
+
"The bounded primary-output endpoint disappeared before cleanup.",
|
|
527
|
+
)
|
|
528
|
+
}
|
|
529
|
+
if (pathMetadata !== undefined) {
|
|
530
|
+
if (
|
|
531
|
+
!pathMetadata.isFIFO() ||
|
|
532
|
+
(openedMetadata !== undefined &&
|
|
533
|
+
(pathMetadata.dev !== openedMetadata.dev ||
|
|
534
|
+
pathMetadata.ino !== openedMetadata.ino))
|
|
535
|
+
) {
|
|
536
|
+
throw new VectorizeError(
|
|
537
|
+
"trace_failed",
|
|
538
|
+
"The bounded primary-output endpoint changed before cleanup.",
|
|
539
|
+
)
|
|
540
|
+
}
|
|
541
|
+
await unlink(path)
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
if (cleanupError === undefined) await rmdir(directoryPath)
|
|
545
|
+
} catch (error) {
|
|
546
|
+
cleanupError ??= error
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
const closes = await Promise.allSettled([
|
|
550
|
+
reader?.close(),
|
|
551
|
+
anchor?.close(),
|
|
552
|
+
directoryHandle?.close(),
|
|
553
|
+
])
|
|
554
|
+
cleanupError ??= closes.find(
|
|
555
|
+
(result): result is PromiseRejectedResult => result.status === "rejected",
|
|
556
|
+
)?.reason
|
|
557
|
+
return cleanupError
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
async function terminateAndWait(
|
|
561
|
+
child: ManagedSubprocess,
|
|
562
|
+
ownsProcessGroup: boolean,
|
|
563
|
+
): Promise<void> {
|
|
564
|
+
if (process.platform === "win32") {
|
|
565
|
+
await killWindowsProcessTree(child.pid)
|
|
566
|
+
} else if (!ownsProcessGroup) {
|
|
567
|
+
safelyKillChild(child, "SIGTERM")
|
|
568
|
+
await delay(TERMINATION_GRACE_MS)
|
|
569
|
+
safelyKillChild(child, "SIGKILL")
|
|
570
|
+
} else {
|
|
571
|
+
safelyKillPosixProcessGroup(child, "SIGTERM")
|
|
572
|
+
await delay(TERMINATION_GRACE_MS)
|
|
573
|
+
// The group may still contain descendants after its leader exits.
|
|
574
|
+
safelyKillPosixProcessGroup(child, "SIGKILL")
|
|
575
|
+
}
|
|
576
|
+
if (await settlesWithin(child.exited, HARD_KILL_WAIT_MS)) return
|
|
577
|
+
throw new VectorizeError(
|
|
578
|
+
"trace_failed",
|
|
579
|
+
"VTracer did not exit after forced termination.",
|
|
580
|
+
)
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
function safelyKillChild(
|
|
584
|
+
child: ManagedSubprocess,
|
|
585
|
+
signal: NodeJS.Signals,
|
|
586
|
+
): void {
|
|
587
|
+
try {
|
|
588
|
+
child.kill(signal)
|
|
589
|
+
} catch {
|
|
590
|
+
// A concurrent exit is equivalent to successful termination.
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
function safelyKillPosixProcessGroup(
|
|
595
|
+
child: ManagedSubprocess,
|
|
596
|
+
signal: NodeJS.Signals,
|
|
597
|
+
): void {
|
|
598
|
+
if (!safelyKillPosixProcessGroupByPid(child.pid, signal)) {
|
|
599
|
+
try {
|
|
600
|
+
child.kill(signal)
|
|
601
|
+
} catch {
|
|
602
|
+
// A concurrent group exit is equivalent to successful termination.
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
function safelyKillPosixProcessGroupByPid(
|
|
608
|
+
pid: number,
|
|
609
|
+
signal: NodeJS.Signals,
|
|
610
|
+
): boolean {
|
|
611
|
+
try {
|
|
612
|
+
process.kill(-pid, signal)
|
|
613
|
+
return true
|
|
614
|
+
} catch {
|
|
615
|
+
return false
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
async function killWindowsProcessTree(pid: number): Promise<void> {
|
|
620
|
+
let killer: ManagedSubprocess
|
|
621
|
+
try {
|
|
622
|
+
killer = Bun.spawn(
|
|
623
|
+
["taskkill.exe", "/PID", String(pid), "/T", "/F"],
|
|
624
|
+
{
|
|
625
|
+
detached: false,
|
|
626
|
+
env: nonGatewayChildEnvironment(),
|
|
627
|
+
stderr: "pipe",
|
|
628
|
+
stdin: "ignore",
|
|
629
|
+
stdout: "pipe",
|
|
630
|
+
windowsHide: true,
|
|
631
|
+
},
|
|
632
|
+
)
|
|
633
|
+
} catch {
|
|
634
|
+
return
|
|
635
|
+
}
|
|
636
|
+
const drain = Promise.all([
|
|
637
|
+
readBoundedText(
|
|
638
|
+
killer.stdout,
|
|
639
|
+
MAX_COMMAND_OUTPUT_BYTES,
|
|
640
|
+
AbortSignal.timeout(HARD_KILL_WAIT_MS),
|
|
641
|
+
"Process-tree cleanup emitted too much output.",
|
|
642
|
+
),
|
|
643
|
+
readBoundedText(
|
|
644
|
+
killer.stderr,
|
|
645
|
+
MAX_COMMAND_OUTPUT_BYTES,
|
|
646
|
+
AbortSignal.timeout(HARD_KILL_WAIT_MS),
|
|
647
|
+
"Process-tree cleanup emitted too much output.",
|
|
648
|
+
),
|
|
649
|
+
])
|
|
650
|
+
if (!(await settlesWithin(Promise.all([killer.exited, drain]), HARD_KILL_WAIT_MS))) {
|
|
651
|
+
try {
|
|
652
|
+
killer.kill("SIGKILL")
|
|
653
|
+
} catch {
|
|
654
|
+
// Cleanup is already best-effort after a taskkill timeout.
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
async function settlesWithin(promise: Promise<unknown>, durationMs: number): Promise<boolean> {
|
|
660
|
+
return new Promise((resolve) => {
|
|
661
|
+
let finished = false
|
|
662
|
+
const timer = setTimeout(() => finish(false), durationMs)
|
|
663
|
+
void promise.then(
|
|
664
|
+
() => finish(true),
|
|
665
|
+
() => finish(true),
|
|
666
|
+
)
|
|
667
|
+
function finish(settled: boolean): void {
|
|
668
|
+
if (finished) return
|
|
669
|
+
finished = true
|
|
670
|
+
clearTimeout(timer)
|
|
671
|
+
resolve(settled)
|
|
672
|
+
}
|
|
673
|
+
})
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
async function readBoundedText(
|
|
677
|
+
stream: ReadableStream<Uint8Array>,
|
|
678
|
+
maximumBytes: number,
|
|
679
|
+
signal: AbortSignal,
|
|
680
|
+
limitMessage: string,
|
|
681
|
+
): Promise<string> {
|
|
682
|
+
const reader = stream.getReader()
|
|
683
|
+
const chunks: Uint8Array[] = []
|
|
684
|
+
let bytes = 0
|
|
685
|
+
const cancel = (): void => {
|
|
686
|
+
void reader.cancel().catch(() => undefined)
|
|
687
|
+
}
|
|
688
|
+
signal.addEventListener("abort", cancel, { once: true })
|
|
689
|
+
try {
|
|
690
|
+
while (true) {
|
|
691
|
+
const { done, value } = await reader.read()
|
|
692
|
+
if (done) break
|
|
693
|
+
bytes += value.byteLength
|
|
694
|
+
if (bytes > maximumBytes) {
|
|
695
|
+
await reader.cancel()
|
|
696
|
+
throw new VectorizeError("output_limit", limitMessage, {
|
|
697
|
+
bytes,
|
|
698
|
+
maximumBytes,
|
|
699
|
+
})
|
|
700
|
+
}
|
|
701
|
+
chunks.push(value)
|
|
702
|
+
}
|
|
703
|
+
return Buffer.concat(chunks, bytes).toString("utf8")
|
|
704
|
+
} finally {
|
|
705
|
+
signal.removeEventListener("abort", cancel)
|
|
706
|
+
reader.releaseLock()
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
async function readBoundedFifo(
|
|
711
|
+
handle: FileHandle,
|
|
712
|
+
maximumBytes: number,
|
|
713
|
+
signal: AbortSignal,
|
|
714
|
+
childHasExited: () => boolean,
|
|
715
|
+
): Promise<string> {
|
|
716
|
+
const buffer = Buffer.allocUnsafe(
|
|
717
|
+
Math.min(PRIMARY_OUTPUT_READ_BYTES, maximumBytes + 1),
|
|
718
|
+
)
|
|
719
|
+
const chunks: Uint8Array[] = []
|
|
720
|
+
let bytes = 0
|
|
721
|
+
let emptyPollsAfterExit = 0
|
|
722
|
+
while (!signal.aborted) {
|
|
723
|
+
const maximumRead = Math.min(
|
|
724
|
+
buffer.byteLength,
|
|
725
|
+
maximumBytes - bytes + 1,
|
|
726
|
+
)
|
|
727
|
+
try {
|
|
728
|
+
const { bytesRead } = await handle.read(
|
|
729
|
+
buffer,
|
|
730
|
+
0,
|
|
731
|
+
maximumRead,
|
|
732
|
+
null,
|
|
733
|
+
)
|
|
734
|
+
if (bytesRead > 0) {
|
|
735
|
+
emptyPollsAfterExit = 0
|
|
736
|
+
bytes += bytesRead
|
|
737
|
+
if (bytes > maximumBytes) {
|
|
738
|
+
throw new VectorizeError(
|
|
739
|
+
"output_limit",
|
|
740
|
+
"VTracer emitted too much primary output.",
|
|
741
|
+
{ bytes, maximumBytes },
|
|
742
|
+
)
|
|
743
|
+
}
|
|
744
|
+
chunks.push(Uint8Array.from(buffer.subarray(0, bytesRead)))
|
|
745
|
+
continue
|
|
746
|
+
}
|
|
747
|
+
} catch (error) {
|
|
748
|
+
if (!isWouldBlockError(error)) throw error
|
|
749
|
+
}
|
|
750
|
+
if (childHasExited()) {
|
|
751
|
+
emptyPollsAfterExit += 1
|
|
752
|
+
// A fast child can write and exit after this nonblocking read reports
|
|
753
|
+
// empty but before its exit promise updates our state. Require another
|
|
754
|
+
// empty read after observing the exit so queued FIFO bytes are drained.
|
|
755
|
+
if (emptyPollsAfterExit >= PRIMARY_OUTPUT_EXIT_EMPTY_POLLS) break
|
|
756
|
+
} else {
|
|
757
|
+
emptyPollsAfterExit = 0
|
|
758
|
+
}
|
|
759
|
+
await delay(PRIMARY_OUTPUT_POLL_MS)
|
|
760
|
+
}
|
|
761
|
+
return Buffer.concat(chunks, bytes).toString("utf8")
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
function isWouldBlockError(error: unknown): boolean {
|
|
765
|
+
return (
|
|
766
|
+
isFileSystemError(error) &&
|
|
767
|
+
["EAGAIN", "EINTR", "EWOULDBLOCK"].includes(
|
|
768
|
+
String(error.code),
|
|
769
|
+
)
|
|
770
|
+
)
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
function isFileSystemError(
|
|
774
|
+
error: unknown,
|
|
775
|
+
code?: string,
|
|
776
|
+
): error is NodeJS.ErrnoException {
|
|
777
|
+
return (
|
|
778
|
+
error instanceof Error &&
|
|
779
|
+
"code" in error &&
|
|
780
|
+
(code === undefined || (error as NodeJS.ErrnoException).code === code)
|
|
781
|
+
)
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
function delay(durationMs: number): Promise<void> {
|
|
785
|
+
return new Promise((resolve) => {
|
|
786
|
+
setTimeout(resolve, durationMs)
|
|
787
|
+
})
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
function executionError(failureCode: VectorizeErrorCode, cause: unknown): VectorizeError {
|
|
791
|
+
return new VectorizeError(
|
|
792
|
+
failureCode,
|
|
793
|
+
"VTracer could not be executed.",
|
|
794
|
+
{},
|
|
795
|
+
{ cause: cause instanceof Error ? cause : new Error(String(cause)) },
|
|
796
|
+
)
|
|
797
|
+
}
|