@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,209 @@
|
|
|
1
|
+
import { lstat, realpath } from "node:fs/promises"
|
|
2
|
+
import { tmpdir } from "node:os"
|
|
3
|
+
import { basename, dirname } from "node:path"
|
|
4
|
+
import {
|
|
5
|
+
MAX_VECTORIZE_REQUEST_BYTES,
|
|
6
|
+
MAX_VECTORIZE_RESPONSE_BYTES,
|
|
7
|
+
VECTORIZE_WORKER_PROTOCOL,
|
|
8
|
+
type VectorizeWorkerRequest,
|
|
9
|
+
type VectorizeWorkerResponse,
|
|
10
|
+
} from "./worker-protocol.js"
|
|
11
|
+
import {
|
|
12
|
+
VectorizeError,
|
|
13
|
+
type VectorizeErrorCode,
|
|
14
|
+
type VectorizeInput,
|
|
15
|
+
} from "./types.js"
|
|
16
|
+
import {
|
|
17
|
+
forwardVectorizeWorkerTermination,
|
|
18
|
+
withInheritedCommandFileDescriptors,
|
|
19
|
+
} from "./command.js"
|
|
20
|
+
import { vectorizeImageInProcess } from "./vectorize.js"
|
|
21
|
+
import { configureVectorizeSharpConcurrency } from "./pixels.js"
|
|
22
|
+
|
|
23
|
+
const errorCodes = new Set<VectorizeErrorCode>([
|
|
24
|
+
"input_limit",
|
|
25
|
+
"invalid_input",
|
|
26
|
+
"output_limit",
|
|
27
|
+
"quality_limit",
|
|
28
|
+
"timeout",
|
|
29
|
+
"tool_download",
|
|
30
|
+
"tool_integrity",
|
|
31
|
+
"tool_platform",
|
|
32
|
+
"tool_version",
|
|
33
|
+
"trace_failed",
|
|
34
|
+
"unsafe_svg",
|
|
35
|
+
])
|
|
36
|
+
|
|
37
|
+
forwardVectorizeWorkerTermination()
|
|
38
|
+
await main()
|
|
39
|
+
|
|
40
|
+
async function main(): Promise<void> {
|
|
41
|
+
let response: VectorizeWorkerResponse
|
|
42
|
+
try {
|
|
43
|
+
configureVectorizeSharpConcurrency()
|
|
44
|
+
const requestText = await readBoundedInput()
|
|
45
|
+
const request = parseRequest(requestText)
|
|
46
|
+
const input = decodeInput(request.input)
|
|
47
|
+
const temporaryRoot = await validateTemporaryRoot(request.temporaryRoot)
|
|
48
|
+
const result = await withInheritedCommandFileDescriptors(
|
|
49
|
+
request.options.inheritedFileDescriptors,
|
|
50
|
+
async () => await vectorizeImageInProcess(
|
|
51
|
+
input,
|
|
52
|
+
request.options,
|
|
53
|
+
temporaryRoot,
|
|
54
|
+
),
|
|
55
|
+
)
|
|
56
|
+
response = {
|
|
57
|
+
ok: true,
|
|
58
|
+
protocol: VECTORIZE_WORKER_PROTOCOL,
|
|
59
|
+
result,
|
|
60
|
+
}
|
|
61
|
+
} catch (error) {
|
|
62
|
+
response = {
|
|
63
|
+
error: serializeError(error),
|
|
64
|
+
ok: false,
|
|
65
|
+
protocol: VECTORIZE_WORKER_PROTOCOL,
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
let encoded = JSON.stringify(response)
|
|
69
|
+
if (Buffer.byteLength(encoded) > MAX_VECTORIZE_RESPONSE_BYTES) {
|
|
70
|
+
encoded = JSON.stringify({
|
|
71
|
+
error: {
|
|
72
|
+
code: "output_limit",
|
|
73
|
+
details: { maximumBytes: MAX_VECTORIZE_RESPONSE_BYTES },
|
|
74
|
+
message: "The vectorization worker response exceeds its IPC limit.",
|
|
75
|
+
},
|
|
76
|
+
ok: false,
|
|
77
|
+
protocol: VECTORIZE_WORKER_PROTOCOL,
|
|
78
|
+
} satisfies VectorizeWorkerResponse)
|
|
79
|
+
}
|
|
80
|
+
await Bun.write(Bun.stdout, encoded)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async function readBoundedInput(): Promise<string> {
|
|
84
|
+
const reader = Bun.stdin.stream().getReader()
|
|
85
|
+
const chunks: Uint8Array[] = []
|
|
86
|
+
let bytes = 0
|
|
87
|
+
try {
|
|
88
|
+
while (true) {
|
|
89
|
+
const { done, value } = await reader.read()
|
|
90
|
+
if (done) break
|
|
91
|
+
bytes += value.byteLength
|
|
92
|
+
if (bytes > MAX_VECTORIZE_REQUEST_BYTES) {
|
|
93
|
+
await reader.cancel()
|
|
94
|
+
throw new VectorizeError(
|
|
95
|
+
"input_limit",
|
|
96
|
+
"The vectorization worker request exceeds its IPC limit.",
|
|
97
|
+
{ bytes, maximumBytes: MAX_VECTORIZE_REQUEST_BYTES },
|
|
98
|
+
)
|
|
99
|
+
}
|
|
100
|
+
chunks.push(value)
|
|
101
|
+
}
|
|
102
|
+
} finally {
|
|
103
|
+
reader.releaseLock()
|
|
104
|
+
}
|
|
105
|
+
return Buffer.concat(chunks, bytes).toString("utf8")
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function parseRequest(text: string): VectorizeWorkerRequest {
|
|
109
|
+
let parsed: unknown
|
|
110
|
+
try {
|
|
111
|
+
parsed = JSON.parse(text)
|
|
112
|
+
} catch (error) {
|
|
113
|
+
throw new VectorizeError(
|
|
114
|
+
"invalid_input",
|
|
115
|
+
"The vectorization worker request is malformed.",
|
|
116
|
+
{},
|
|
117
|
+
{ cause: error },
|
|
118
|
+
)
|
|
119
|
+
}
|
|
120
|
+
if (
|
|
121
|
+
!isRecord(parsed) ||
|
|
122
|
+
parsed.protocol !== VECTORIZE_WORKER_PROTOCOL ||
|
|
123
|
+
!isRecord(parsed.input) ||
|
|
124
|
+
!isRecord(parsed.options) ||
|
|
125
|
+
typeof parsed.temporaryRoot !== "string" ||
|
|
126
|
+
parsed.temporaryRoot.length === 0 ||
|
|
127
|
+
typeof parsed.input.kind !== "string" ||
|
|
128
|
+
typeof parsed.input.value !== "string" ||
|
|
129
|
+
!["bytes", "path"].includes(parsed.input.kind)
|
|
130
|
+
) {
|
|
131
|
+
throw new VectorizeError(
|
|
132
|
+
"invalid_input",
|
|
133
|
+
"The vectorization worker request is invalid.",
|
|
134
|
+
)
|
|
135
|
+
}
|
|
136
|
+
return parsed as unknown as VectorizeWorkerRequest
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function decodeInput(input: VectorizeWorkerRequest["input"]): VectorizeInput {
|
|
140
|
+
if (input.kind === "path") return input.value
|
|
141
|
+
if (
|
|
142
|
+
input.value.length > Math.ceil(MAX_VECTORIZE_REQUEST_BYTES / 3) * 4 ||
|
|
143
|
+
input.value.length % 4 !== 0
|
|
144
|
+
) {
|
|
145
|
+
throw new VectorizeError("input_limit", "Encoded raster input exceeds its IPC limit.")
|
|
146
|
+
}
|
|
147
|
+
const bytes = Buffer.from(input.value, "base64")
|
|
148
|
+
if (bytes.toString("base64") !== input.value) {
|
|
149
|
+
throw new VectorizeError("invalid_input", "Encoded raster input is not canonical base64.")
|
|
150
|
+
}
|
|
151
|
+
return bytes
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
async function validateTemporaryRoot(path: string): Promise<string> {
|
|
155
|
+
let realRoot: string
|
|
156
|
+
let realTemporaryDirectory: string
|
|
157
|
+
try {
|
|
158
|
+
const resolved = await Promise.all([
|
|
159
|
+
realpath(path),
|
|
160
|
+
realpath(tmpdir()),
|
|
161
|
+
])
|
|
162
|
+
realRoot = resolved[0]
|
|
163
|
+
realTemporaryDirectory = resolved[1]
|
|
164
|
+
const metadata = await lstat(path)
|
|
165
|
+
if (
|
|
166
|
+
!metadata.isDirectory() ||
|
|
167
|
+
metadata.isSymbolicLink() ||
|
|
168
|
+
dirname(realRoot) !== realTemporaryDirectory ||
|
|
169
|
+
!/^slopcamera-vectorize-[A-Za-z0-9_-]{6,}$/u.test(basename(realRoot))
|
|
170
|
+
) {
|
|
171
|
+
throw new VectorizeError(
|
|
172
|
+
"invalid_input",
|
|
173
|
+
"The supervisor temporary directory is invalid.",
|
|
174
|
+
)
|
|
175
|
+
}
|
|
176
|
+
} catch (error) {
|
|
177
|
+
if (error instanceof VectorizeError) throw error
|
|
178
|
+
throw new VectorizeError(
|
|
179
|
+
"invalid_input",
|
|
180
|
+
"The supervisor temporary directory could not be verified.",
|
|
181
|
+
{},
|
|
182
|
+
{ cause: error },
|
|
183
|
+
)
|
|
184
|
+
}
|
|
185
|
+
return realRoot
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function serializeError(error: unknown): Readonly<{
|
|
189
|
+
code: VectorizeErrorCode
|
|
190
|
+
details: Readonly<Record<string, unknown>>
|
|
191
|
+
message: string
|
|
192
|
+
}> {
|
|
193
|
+
const code =
|
|
194
|
+
error instanceof VectorizeError && errorCodes.has(error.code)
|
|
195
|
+
? error.code
|
|
196
|
+
: "trace_failed"
|
|
197
|
+
const message =
|
|
198
|
+
error instanceof Error ? error.message : "The vectorization worker failed."
|
|
199
|
+
const details =
|
|
200
|
+
error instanceof VectorizeError &&
|
|
201
|
+
Buffer.byteLength(JSON.stringify(error.details)) <= 64 * 1_024
|
|
202
|
+
? error.details
|
|
203
|
+
: {}
|
|
204
|
+
return { code, details, message }
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
208
|
+
return typeof value === "object" && value !== null && !Array.isArray(value)
|
|
209
|
+
}
|
package/src/version.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const SLOPCAMERA_VERSION = "3.2.4" as const
|
package/src/workflow.ts
ADDED
|
@@ -0,0 +1,409 @@
|
|
|
1
|
+
import {
|
|
2
|
+
executeSlopcameraOperationWithLease,
|
|
3
|
+
isSlopcameraOperationCode,
|
|
4
|
+
parseSlopcameraOperationInput,
|
|
5
|
+
slopcameraOperationHostResourceClaims,
|
|
6
|
+
type SlopcameraOperationCode,
|
|
7
|
+
type SlopcameraOperationDependencies,
|
|
8
|
+
type SlopcameraOperationInputMap,
|
|
9
|
+
type SlopcameraOperationResultMap,
|
|
10
|
+
} from "./operations.js"
|
|
11
|
+
import {
|
|
12
|
+
createDefaultHostResourceCoordinator,
|
|
13
|
+
type HostResourceCoordinator,
|
|
14
|
+
type HostResourceLease,
|
|
15
|
+
} from "./host-resources.js"
|
|
16
|
+
|
|
17
|
+
const workflowIdPattern = /^[a-z0-9]+(?:[._-][a-z0-9]+)*$/u
|
|
18
|
+
const workflowStepIdPattern = /^[A-Za-z0-9]+(?:[._:-][A-Za-z0-9]+)*$/u
|
|
19
|
+
const defaultMaximumSteps = 64
|
|
20
|
+
const hardMaximumSteps = 256
|
|
21
|
+
|
|
22
|
+
export type SlopcameraWorkflowErrorCode =
|
|
23
|
+
| "INVALID_WORKFLOW"
|
|
24
|
+
| "INVALID_WORKFLOW_INPUT"
|
|
25
|
+
| "INVALID_WORKFLOW_STEP"
|
|
26
|
+
| "WORKFLOW_ABORTED"
|
|
27
|
+
| "WORKFLOW_FAILED"
|
|
28
|
+
| "WORKFLOW_STEP_FAILED"
|
|
29
|
+
|
|
30
|
+
export interface SlopcameraWorkflowStepReceipt {
|
|
31
|
+
readonly index: number
|
|
32
|
+
readonly id: string
|
|
33
|
+
readonly operation: SlopcameraOperationCode
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export class SlopcameraWorkflowError extends Error {
|
|
37
|
+
readonly code: SlopcameraWorkflowErrorCode
|
|
38
|
+
readonly completedSteps: readonly SlopcameraWorkflowStepReceipt[]
|
|
39
|
+
readonly failedStep?: Readonly<{
|
|
40
|
+
id: string
|
|
41
|
+
operation: SlopcameraOperationCode
|
|
42
|
+
}>
|
|
43
|
+
|
|
44
|
+
constructor(
|
|
45
|
+
code: SlopcameraWorkflowErrorCode,
|
|
46
|
+
message: string,
|
|
47
|
+
options: {
|
|
48
|
+
readonly cause?: unknown
|
|
49
|
+
readonly completedSteps?: readonly SlopcameraWorkflowStepReceipt[]
|
|
50
|
+
readonly failedStep?: Readonly<{
|
|
51
|
+
id: string
|
|
52
|
+
operation: SlopcameraOperationCode
|
|
53
|
+
}>
|
|
54
|
+
} = {},
|
|
55
|
+
) {
|
|
56
|
+
super(`[${code}] ${message}`, { cause: options.cause })
|
|
57
|
+
this.name = "SlopcameraWorkflowError"
|
|
58
|
+
this.code = code
|
|
59
|
+
this.completedSteps = Object.freeze(
|
|
60
|
+
[...(options.completedSteps ?? [])].sort(
|
|
61
|
+
(left, right) => left.index - right.index,
|
|
62
|
+
),
|
|
63
|
+
)
|
|
64
|
+
if (options.failedStep !== undefined) {
|
|
65
|
+
this.failedStep = Object.freeze({ ...options.failedStep })
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export interface SlopcameraWorkflowExecutorContext {
|
|
71
|
+
readonly hostResourceLease: HostResourceLease
|
|
72
|
+
readonly signal: AbortSignal
|
|
73
|
+
readonly stepId: string
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export type SlopcameraWorkflowExecutor = <C extends SlopcameraOperationCode>(
|
|
77
|
+
code: C,
|
|
78
|
+
input: SlopcameraOperationInputMap[C],
|
|
79
|
+
context: SlopcameraWorkflowExecutorContext,
|
|
80
|
+
) => Promise<SlopcameraOperationResultMap[C]>
|
|
81
|
+
|
|
82
|
+
export interface SlopcameraWorkflowContext {
|
|
83
|
+
readonly signal: AbortSignal
|
|
84
|
+
operation<C extends SlopcameraOperationCode>(
|
|
85
|
+
id: string,
|
|
86
|
+
code: C,
|
|
87
|
+
input: SlopcameraOperationInputMap[C],
|
|
88
|
+
): Promise<SlopcameraOperationResultMap[C]>
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export interface SlopcameraWorkflowDefinition<Input, Output> {
|
|
92
|
+
readonly id: string
|
|
93
|
+
readonly version: number
|
|
94
|
+
readonly parseInput: (value: unknown) => Input
|
|
95
|
+
readonly run: (
|
|
96
|
+
context: SlopcameraWorkflowContext,
|
|
97
|
+
input: Input,
|
|
98
|
+
) => Output | Promise<Output>
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export interface DefineSlopcameraWorkflowOptions<Input, Output>
|
|
102
|
+
extends SlopcameraWorkflowDefinition<Input, Output> {}
|
|
103
|
+
|
|
104
|
+
export interface RunSlopcameraWorkflowOptions {
|
|
105
|
+
/**
|
|
106
|
+
* Operation dependencies may also carry admission controls. Explicit
|
|
107
|
+
* workflow-level controls take precedence when both are present.
|
|
108
|
+
*/
|
|
109
|
+
readonly dependencies?: SlopcameraOperationDependencies
|
|
110
|
+
readonly executor?: SlopcameraWorkflowExecutor
|
|
111
|
+
readonly hostResourceCoordinator?: HostResourceCoordinator
|
|
112
|
+
readonly maximumSteps?: number
|
|
113
|
+
readonly signal?: AbortSignal
|
|
114
|
+
readonly waitTimeoutMilliseconds?: number
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export interface SlopcameraWorkflowRun<Output> {
|
|
118
|
+
readonly workflow: Readonly<{
|
|
119
|
+
id: string
|
|
120
|
+
version: number
|
|
121
|
+
}>
|
|
122
|
+
readonly output: Output
|
|
123
|
+
readonly steps: readonly SlopcameraWorkflowStepReceipt[]
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function workflowError(
|
|
127
|
+
code: SlopcameraWorkflowErrorCode,
|
|
128
|
+
message: string,
|
|
129
|
+
): never {
|
|
130
|
+
throw new SlopcameraWorkflowError(code, message)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function validateWorkflowId(id: unknown): asserts id is string {
|
|
134
|
+
if (
|
|
135
|
+
typeof id !== "string" ||
|
|
136
|
+
id.length < 1 ||
|
|
137
|
+
id.length > 80 ||
|
|
138
|
+
!workflowIdPattern.test(id)
|
|
139
|
+
) {
|
|
140
|
+
workflowError(
|
|
141
|
+
"INVALID_WORKFLOW",
|
|
142
|
+
"Workflow id must be 1 through 80 lowercase letters, numbers, dots, underscores, or hyphens.",
|
|
143
|
+
)
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function validateStepId(
|
|
148
|
+
id: unknown,
|
|
149
|
+
completedSteps: readonly SlopcameraWorkflowStepReceipt[] = [],
|
|
150
|
+
): asserts id is string {
|
|
151
|
+
if (
|
|
152
|
+
typeof id !== "string" ||
|
|
153
|
+
id.length < 1 ||
|
|
154
|
+
id.length > 80 ||
|
|
155
|
+
!workflowStepIdPattern.test(id)
|
|
156
|
+
) {
|
|
157
|
+
throw new SlopcameraWorkflowError(
|
|
158
|
+
"INVALID_WORKFLOW_STEP",
|
|
159
|
+
"Step id must be 1 through 80 letters, numbers, dots, underscores, colons, or hyphens.",
|
|
160
|
+
{ completedSteps },
|
|
161
|
+
)
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export function defineSlopcameraWorkflow<Input, Output>(
|
|
166
|
+
options: DefineSlopcameraWorkflowOptions<Input, Output>,
|
|
167
|
+
): SlopcameraWorkflowDefinition<Input, Output> {
|
|
168
|
+
if (typeof options !== "object" || options === null) {
|
|
169
|
+
workflowError("INVALID_WORKFLOW", "Workflow definition must be an object.")
|
|
170
|
+
}
|
|
171
|
+
validateWorkflowId(options.id)
|
|
172
|
+
if (!Number.isSafeInteger(options.version) || options.version < 1) {
|
|
173
|
+
workflowError(
|
|
174
|
+
"INVALID_WORKFLOW",
|
|
175
|
+
"Workflow version must be a positive safe integer.",
|
|
176
|
+
)
|
|
177
|
+
}
|
|
178
|
+
if (typeof options.parseInput !== "function" || typeof options.run !== "function") {
|
|
179
|
+
workflowError(
|
|
180
|
+
"INVALID_WORKFLOW",
|
|
181
|
+
"Workflow definition requires parseInput and run functions.",
|
|
182
|
+
)
|
|
183
|
+
}
|
|
184
|
+
return Object.freeze({
|
|
185
|
+
id: options.id,
|
|
186
|
+
version: options.version,
|
|
187
|
+
parseInput: options.parseInput,
|
|
188
|
+
run: options.run,
|
|
189
|
+
})
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function maximumSteps(value: number | undefined): number {
|
|
193
|
+
const resolved = value ?? defaultMaximumSteps
|
|
194
|
+
if (
|
|
195
|
+
!Number.isSafeInteger(resolved) ||
|
|
196
|
+
resolved < 1 ||
|
|
197
|
+
resolved > hardMaximumSteps
|
|
198
|
+
) {
|
|
199
|
+
workflowError(
|
|
200
|
+
"INVALID_WORKFLOW",
|
|
201
|
+
`maximumSteps must be an integer from 1 through ${hardMaximumSteps}.`,
|
|
202
|
+
)
|
|
203
|
+
}
|
|
204
|
+
return resolved
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function aborted(
|
|
208
|
+
completedSteps: readonly SlopcameraWorkflowStepReceipt[],
|
|
209
|
+
cause?: unknown,
|
|
210
|
+
): SlopcameraWorkflowError {
|
|
211
|
+
return new SlopcameraWorkflowError(
|
|
212
|
+
"WORKFLOW_ABORTED",
|
|
213
|
+
"Workflow execution was aborted.",
|
|
214
|
+
{ cause, completedSteps },
|
|
215
|
+
)
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
export async function runSlopcameraWorkflow<Input, Output>(
|
|
219
|
+
definition: SlopcameraWorkflowDefinition<Input, Output>,
|
|
220
|
+
value: unknown,
|
|
221
|
+
options: RunSlopcameraWorkflowOptions = {},
|
|
222
|
+
): Promise<SlopcameraWorkflowRun<Awaited<Output>>> {
|
|
223
|
+
const normalized = defineSlopcameraWorkflow(definition)
|
|
224
|
+
const limit = maximumSteps(options.maximumSteps)
|
|
225
|
+
const signal = options.signal
|
|
226
|
+
?? options.dependencies?.signal
|
|
227
|
+
?? new AbortController().signal
|
|
228
|
+
const invoked = new Set<string>()
|
|
229
|
+
const completed: SlopcameraWorkflowStepReceipt[] = []
|
|
230
|
+
const dispatched: Promise<unknown>[] = []
|
|
231
|
+
let acceptingOperations = true
|
|
232
|
+
let nextIndex = 0
|
|
233
|
+
|
|
234
|
+
if (signal.aborted) throw aborted(completed)
|
|
235
|
+
|
|
236
|
+
let input: Input
|
|
237
|
+
try {
|
|
238
|
+
input = normalized.parseInput(value)
|
|
239
|
+
} catch (cause) {
|
|
240
|
+
throw new SlopcameraWorkflowError(
|
|
241
|
+
"INVALID_WORKFLOW_INPUT",
|
|
242
|
+
"Workflow input did not satisfy its parser.",
|
|
243
|
+
{ cause },
|
|
244
|
+
)
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const executor: SlopcameraWorkflowExecutor = options.executor
|
|
248
|
+
?? (<C extends SlopcameraOperationCode>(
|
|
249
|
+
code: C,
|
|
250
|
+
operationInput: SlopcameraOperationInputMap[C],
|
|
251
|
+
context: SlopcameraWorkflowExecutorContext,
|
|
252
|
+
) => executeSlopcameraOperationWithLease(
|
|
253
|
+
code,
|
|
254
|
+
operationInput,
|
|
255
|
+
context.hostResourceLease,
|
|
256
|
+
options.dependencies,
|
|
257
|
+
))
|
|
258
|
+
const hostResourceCoordinator = options.hostResourceCoordinator
|
|
259
|
+
?? options.dependencies?.hostResourceCoordinator
|
|
260
|
+
?? createDefaultHostResourceCoordinator()
|
|
261
|
+
const waitTimeoutMilliseconds = options.waitTimeoutMilliseconds
|
|
262
|
+
?? options.dependencies?.waitTimeoutMilliseconds
|
|
263
|
+
|
|
264
|
+
async function dispatchOperation<C extends SlopcameraOperationCode>(
|
|
265
|
+
id: string,
|
|
266
|
+
code: C,
|
|
267
|
+
operationInput: SlopcameraOperationInputMap[C],
|
|
268
|
+
): Promise<SlopcameraOperationResultMap[C]> {
|
|
269
|
+
if (signal.aborted) throw aborted(completed)
|
|
270
|
+
validateStepId(id, completed)
|
|
271
|
+
if (!isSlopcameraOperationCode(code)) {
|
|
272
|
+
throw new SlopcameraWorkflowError(
|
|
273
|
+
"INVALID_WORKFLOW_STEP",
|
|
274
|
+
`Workflow step ${id} names an unknown Slopcamera operation.`,
|
|
275
|
+
{ completedSteps: completed },
|
|
276
|
+
)
|
|
277
|
+
}
|
|
278
|
+
if (invoked.has(id)) {
|
|
279
|
+
throw new SlopcameraWorkflowError(
|
|
280
|
+
"INVALID_WORKFLOW_STEP",
|
|
281
|
+
`Duplicate workflow step id: ${id}.`,
|
|
282
|
+
{ completedSteps: completed },
|
|
283
|
+
)
|
|
284
|
+
}
|
|
285
|
+
if (nextIndex >= limit) {
|
|
286
|
+
throw new SlopcameraWorkflowError(
|
|
287
|
+
"INVALID_WORKFLOW_STEP",
|
|
288
|
+
`Workflow exceeds its ${String(limit)}-step limit.`,
|
|
289
|
+
{ completedSteps: completed },
|
|
290
|
+
)
|
|
291
|
+
}
|
|
292
|
+
const index = nextIndex
|
|
293
|
+
nextIndex += 1
|
|
294
|
+
invoked.add(id)
|
|
295
|
+
let normalizedInput: SlopcameraOperationInputMap[C]
|
|
296
|
+
try {
|
|
297
|
+
normalizedInput = parseSlopcameraOperationInput(code, operationInput)
|
|
298
|
+
} catch (cause) {
|
|
299
|
+
throw new SlopcameraWorkflowError(
|
|
300
|
+
"INVALID_WORKFLOW_STEP",
|
|
301
|
+
`Workflow step ${id} has invalid input for ${code}.`,
|
|
302
|
+
{ cause, completedSteps: completed },
|
|
303
|
+
)
|
|
304
|
+
}
|
|
305
|
+
try {
|
|
306
|
+
const result = await hostResourceCoordinator.withLease(
|
|
307
|
+
slopcameraOperationHostResourceClaims(code),
|
|
308
|
+
async (hostResourceLease) => await executor(code, normalizedInput, {
|
|
309
|
+
hostResourceLease,
|
|
310
|
+
signal,
|
|
311
|
+
stepId: id,
|
|
312
|
+
}),
|
|
313
|
+
{
|
|
314
|
+
signal,
|
|
315
|
+
...(waitTimeoutMilliseconds === undefined
|
|
316
|
+
? {}
|
|
317
|
+
: { waitTimeoutMilliseconds }),
|
|
318
|
+
},
|
|
319
|
+
)
|
|
320
|
+
completed.push(Object.freeze({ id, index, operation: code }))
|
|
321
|
+
if (signal.aborted) throw aborted(completed)
|
|
322
|
+
return result
|
|
323
|
+
} catch (cause) {
|
|
324
|
+
if (
|
|
325
|
+
cause instanceof SlopcameraWorkflowError &&
|
|
326
|
+
cause.code === "WORKFLOW_ABORTED"
|
|
327
|
+
) {
|
|
328
|
+
throw cause
|
|
329
|
+
}
|
|
330
|
+
if (signal.aborted) throw aborted(completed, cause)
|
|
331
|
+
throw new SlopcameraWorkflowError(
|
|
332
|
+
"WORKFLOW_STEP_FAILED",
|
|
333
|
+
`Workflow step ${id} (${code}) failed.`,
|
|
334
|
+
{
|
|
335
|
+
cause,
|
|
336
|
+
completedSteps: completed,
|
|
337
|
+
failedStep: { id, operation: code },
|
|
338
|
+
},
|
|
339
|
+
)
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
const context: SlopcameraWorkflowContext = Object.freeze({
|
|
344
|
+
signal,
|
|
345
|
+
operation<C extends SlopcameraOperationCode>(
|
|
346
|
+
id: string,
|
|
347
|
+
code: C,
|
|
348
|
+
operationInput: SlopcameraOperationInputMap[C],
|
|
349
|
+
): Promise<SlopcameraOperationResultMap[C]> {
|
|
350
|
+
if (!acceptingOperations) {
|
|
351
|
+
const closed = Promise.reject<SlopcameraOperationResultMap[C]>(
|
|
352
|
+
new SlopcameraWorkflowError(
|
|
353
|
+
"INVALID_WORKFLOW_STEP",
|
|
354
|
+
"Workflow operations cannot start after authored workflow code has settled.",
|
|
355
|
+
{ completedSteps: completed },
|
|
356
|
+
),
|
|
357
|
+
)
|
|
358
|
+
void closed.catch(() => undefined)
|
|
359
|
+
return closed
|
|
360
|
+
}
|
|
361
|
+
const operation = dispatchOperation(id, code, operationInput)
|
|
362
|
+
dispatched.push(operation)
|
|
363
|
+
// Attach a rejection observer immediately so a deliberately un-awaited
|
|
364
|
+
// branch cannot become an unhandled rejection before the runner drains it.
|
|
365
|
+
void operation.catch(() => undefined)
|
|
366
|
+
return operation
|
|
367
|
+
},
|
|
368
|
+
})
|
|
369
|
+
|
|
370
|
+
let output!: Awaited<Output>
|
|
371
|
+
let runFailed = false
|
|
372
|
+
let runFailure: unknown
|
|
373
|
+
try {
|
|
374
|
+
output = await normalized.run(context, input) as Awaited<Output>
|
|
375
|
+
} catch (cause) {
|
|
376
|
+
runFailed = true
|
|
377
|
+
runFailure = cause
|
|
378
|
+
} finally {
|
|
379
|
+
acceptingOperations = false
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
const operationResults = await Promise.allSettled(dispatched)
|
|
383
|
+
if (signal.aborted) throw aborted(completed, runFailed ? runFailure : undefined)
|
|
384
|
+
if (runFailed) {
|
|
385
|
+
if (runFailure instanceof SlopcameraWorkflowError) throw runFailure
|
|
386
|
+
throw new SlopcameraWorkflowError(
|
|
387
|
+
"WORKFLOW_FAILED",
|
|
388
|
+
`Workflow ${normalized.id} failed in authored code.`,
|
|
389
|
+
{ cause: runFailure, completedSteps: completed },
|
|
390
|
+
)
|
|
391
|
+
}
|
|
392
|
+
const operationFailure = operationResults.find(
|
|
393
|
+
(result): result is PromiseRejectedResult => result.status === "rejected",
|
|
394
|
+
)
|
|
395
|
+
if (operationFailure !== undefined) {
|
|
396
|
+
const cause: unknown = operationFailure.reason
|
|
397
|
+
if (cause instanceof SlopcameraWorkflowError) throw cause
|
|
398
|
+
throw new SlopcameraWorkflowError(
|
|
399
|
+
"WORKFLOW_STEP_FAILED",
|
|
400
|
+
"A dispatched workflow operation failed.",
|
|
401
|
+
{ cause, completedSteps: completed },
|
|
402
|
+
)
|
|
403
|
+
}
|
|
404
|
+
return Object.freeze({
|
|
405
|
+
workflow: Object.freeze({ id: normalized.id, version: normalized.version }),
|
|
406
|
+
output,
|
|
407
|
+
steps: Object.freeze([...completed].sort((left, right) => left.index - right.index)),
|
|
408
|
+
})
|
|
409
|
+
}
|