cozyclay 2.1.1 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +60 -0
- package/README.md +12 -30
- package/bin/agent/agent-routes.mjs +40 -113
- package/bin/agent/agent-runner.mjs +9 -10
- package/bin/agent/cliproxy-provider.mjs +135 -0
- package/bin/agent/credential-store.mjs +30 -7
- package/bin/agent/pi-tools.mjs +4 -1
- package/bin/agent/provider-keys.mjs +32 -0
- package/bin/agent/provider-login.mjs +110 -0
- package/bin/agent/provider-routes.mjs +99 -0
- package/bin/agent/providers.mjs +19 -97
- package/bin/agent/script-tool.mjs +17 -4
- package/bin/agent/studio-motion-reference.mjs +23 -0
- package/bin/agent/studio-prompt.mjs +1 -1
- package/bin/agent/studio-rebase-text.mjs +32 -0
- package/bin/agent/studio-rebase.mjs +26 -39
- package/bin/agent/studio-tools.mjs +9 -28
- package/bin/agent/tool-arguments.mjs +49 -0
- package/bin/agent/turn-frames.mjs +66 -0
- package/bin/codex-auth.mjs +9 -2
- package/bin/cozyclay.mjs +10 -116
- package/bin/live/cli.mjs +3 -3
- package/bin/mcp-runtime.mjs +2 -12
- package/bin/package-signature.mjs +0 -4
- package/dist/app/index.html +7 -6
- package/dist/assets/EXRLoader-CvVIvGVE.js +1 -0
- package/dist/assets/analytics-C9sUnndd.js +1 -0
- package/dist/assets/app-CCh--1Hc.js +5087 -0
- package/dist/assets/app-f-fkV5uP.css +1 -0
- package/dist/assets/compile-worker-CkxiD6lH.js +5 -0
- package/dist/assets/fflate.module-KbGj5-H6.js +5 -0
- package/dist/assets/{first-shot-handoff-msLI71g1.js → first-shot-handoff-CC4xe4HM.js} +1 -1
- package/dist/assets/{landing-ChUvB_zL.js → landing-DjHD8u7k.js} +1 -1
- package/dist/assets/scene-asset-sync-Hx_FzdOH.js +20 -0
- package/dist/assets/scene-asset-sync-_S8NQl5B.css +1 -0
- package/dist/assets/workflow-C4yKH1p4.js +179 -0
- package/dist/cozyclay-package.json +1 -1
- package/dist/index.html +3 -3
- package/dist/sw.js +3 -6
- package/dist/workflow/index.html +5 -4
- package/mcp/LIVE-PROTOCOL.md +7 -47
- package/mcp/README.md +16 -26
- package/mcp/live-hub.mjs +6 -10
- package/mcp/package.json +1 -3
- package/mcp/tool-handlers.mjs +4 -168
- package/package.json +3 -22
- package/src/App.jsx +150 -2624
- package/src/agent-progress.js +79 -0
- package/src/agent-turn-mark.js +30 -0
- package/src/analytics.js +1 -19
- package/src/app-context.js +5 -0
- package/src/app-stage.jsx +2 -34
- package/src/asset-pane.jsx +6 -5
- package/src/camera-block.js +21 -0
- package/src/camera-direction.js +120 -0
- package/src/character-kind.js +3 -9
- package/src/command-bus.js +8 -6
- package/src/commands/cast.js +5 -34
- package/src/commands/elements/character.js +1 -1
- package/src/commands/elements/motion.js +1 -6
- package/src/commands/elements/object.js +6 -2
- package/src/commands/elements/shot.js +4 -1
- package/src/commands/elements.js +38 -14
- package/src/commands/index.js +3 -1
- package/src/commands/kits.js +37 -0
- package/src/commands/motion-timing.js +57 -0
- package/src/commands/motion.js +9 -152
- package/src/commands/motion3-compile.js +65 -0
- package/src/commands/motion3.js +207 -0
- package/src/commands/objects.js +6 -4
- package/src/commands/shot.js +49 -11
- package/src/commands/stage.js +14 -1
- package/src/commands/view.js +4 -1
- package/src/document-mirrors.js +42 -0
- package/src/document-store.js +4 -0
- package/src/domains/cast.js +19 -194
- package/src/domains/motion/motion-range-pin-rules.js +1 -1
- package/src/domains/motion/take-source.js +51 -0
- package/src/domains/motion.js +60 -1838
- package/src/domains/scenes.js +12 -55
- package/src/domains/shots.js +2 -2
- package/src/domains/stage.js +4 -2
- package/src/draft-ghost.js +46 -0
- package/src/draft-ghost.jsx +129 -0
- package/src/dualview.jsx +4 -3
- package/src/effects/EffectsLayer.jsx +112 -0
- package/src/effects/effect-meshes.js +232 -0
- package/src/effects/effect-record.js +103 -0
- package/src/effects/emitter.js +59 -0
- package/src/effects/simulate.js +261 -0
- package/src/effects/state.js +59 -0
- package/src/execution-telemetry.js +2 -3
- package/src/first-success-guide.jsx +5 -5
- package/src/ghost-set.js +257 -0
- package/src/held-object-edit.jsx +113 -0
- package/src/held-object-pose.js +175 -0
- package/src/hierarchy-model.js +1 -1
- package/src/hierarchy-panel.jsx +1 -1
- package/src/imported-mesh.jsx +103 -0
- package/src/kits/held.js +84 -0
- package/src/kits/index.js +70 -0
- package/src/kits/smoking.js +57 -0
- package/src/light-blockers.js +93 -0
- package/src/motion/beat-board-model.js +84 -0
- package/src/motion/beat-board.css +124 -0
- package/src/motion/beat-board.jsx +59 -0
- package/src/motion/center-of-mass.js +66 -0
- package/src/motion/fix-collisions.js +850 -0
- package/src/motion/physics-review.js +118 -0
- package/src/{ardy → motion}/physics-support.js +1 -76
- package/src/motion/rig-snapshot.js +22 -0
- package/src/motion/take-segments.jsx +140 -0
- package/src/{ardy → motion}/timeline-coordinates.js +0 -21
- package/src/{ardy → motion}/timeline.css +1 -2
- package/src/{ardy → motion}/timeline.jsx +42 -413
- package/src/motion/waypoints.js +76 -0
- package/src/motion3/beats.js +83 -0
- package/src/motion3/body.js +158 -0
- package/src/motion3/bones.js +47 -0
- package/src/motion3/carried.js +47 -0
- package/src/motion3/compile-job.js +75 -0
- package/src/motion3/compile-worker.js +10 -0
- package/src/motion3/compiler/anchors.js +105 -0
- package/src/motion3/compiler/arm-orient.js +128 -0
- package/src/motion3/compiler/channels.js +125 -0
- package/src/motion3/compiler/compile.js +205 -0
- package/src/motion3/compiler/gait-rhythm.js +97 -0
- package/src/motion3/compiler/grip.js +171 -0
- package/src/motion3/compiler/head-steady.js +48 -0
- package/src/motion3/compiler/hold.js +134 -0
- package/src/motion3/compiler/idle.js +113 -0
- package/src/motion3/compiler/limbs.js +170 -0
- package/src/motion3/compiler/math.js +72 -0
- package/src/motion3/compiler/pose.js +162 -0
- package/src/motion3/compiler/quat.js +49 -0
- package/src/motion3/compiler/skeleton.js +74 -0
- package/src/motion3/compiler/validate-common.js +32 -0
- package/src/motion3/compiler/validate-goals.js +118 -0
- package/src/motion3/compiler/validate-objects.js +143 -0
- package/src/motion3/compiler/validate-parts.js +116 -0
- package/src/motion3/compiler/validate.js +122 -0
- package/src/motion3/compiler/vocab.js +73 -0
- package/src/motion3/compiler/walk-pelvis.js +275 -0
- package/src/motion3/compiler/walk.js +207 -0
- package/src/motion3/compiler/wrist-clamp.js +32 -0
- package/src/motion3/delta.js +47 -0
- package/src/motion3/describe.js +174 -0
- package/src/motion3/docs.js +27 -0
- package/src/motion3/edit-carry.js +21 -0
- package/src/motion3/fix/ease.js +16 -0
- package/src/motion3/fix/ground.js +37 -0
- package/src/motion3/fix/index.js +52 -0
- package/src/motion3/fix/push.js +71 -0
- package/src/motion3/fix/reach.js +46 -0
- package/src/motion3/fix/retime.js +79 -0
- package/src/motion3/fix/self.js +211 -0
- package/src/motion3/fix/slide.js +70 -0
- package/src/motion3/fix/turn.js +51 -0
- package/src/motion3/follow.js +39 -0
- package/src/motion3/free-motion.js +173 -0
- package/src/motion3/grip-choice.js +130 -0
- package/src/motion3/hold-plan.js +157 -0
- package/src/motion3/json-patch.js +77 -0
- package/src/motion3/key-aim.js +105 -0
- package/src/motion3/key-edit.js +82 -0
- package/src/motion3/lenient.js +127 -0
- package/src/motion3/object-keys.js +236 -0
- package/src/motion3/object-pose.js +182 -0
- package/src/motion3/object-tracks.js +12 -0
- package/src/motion3/pipeline.js +102 -0
- package/src/motion3/playback.js +51 -0
- package/src/motion3/probes.js +91 -0
- package/src/motion3/qa/body-checks.js +34 -0
- package/src/motion3/qa/check-names.js +14 -0
- package/src/motion3/qa/checks.js +121 -0
- package/src/motion3/qa/contact-checks.js +51 -0
- package/src/motion3/qa/explain.js +80 -0
- package/src/motion3/qa/fmt.js +5 -0
- package/src/motion3/qa/foot-checks.js +72 -0
- package/src/motion3/qa/geometry.js +77 -0
- package/src/motion3/qa/goals.js +191 -0
- package/src/motion3/qa/ground.js +60 -0
- package/src/motion3/qa/hints-contact.js +179 -0
- package/src/motion3/qa/hints-goal.js +160 -0
- package/src/motion3/qa/hints-knee.js +24 -0
- package/src/motion3/qa/hints-motion.js +209 -0
- package/src/motion3/qa/hints-object.js +91 -0
- package/src/motion3/qa/index.js +42 -0
- package/src/motion3/qa/key-checks.js +82 -0
- package/src/motion3/qa/near.js +96 -0
- package/src/motion3/qa/object-checks.js +135 -0
- package/src/motion3/qa/patch.js +161 -0
- package/src/motion3/qa/severity.js +38 -0
- package/src/motion3/qa/spec-checks.js +65 -0
- package/src/motion3/qa/style.js +218 -0
- package/src/motion3/qa/wrist-checks.js +69 -0
- package/src/motion3/qa/wrist-obs.js +60 -0
- package/src/motion3/refs.js +120 -0
- package/src/motion3/repair-tune.js +121 -0
- package/src/motion3/repair.js +83 -0
- package/src/motion3/report.js +67 -0
- package/src/motion3/rigs/index.js +14 -0
- package/src/motion3/rigs/x-bot-tpose-body.json +1 -0
- package/src/motion3/rigs/x-bot-tpose.json +1 -0
- package/src/motion3/rigs/y-bot-tpose-body.json +1 -0
- package/src/motion3/rigs/y-bot-tpose.json +1 -0
- package/src/motion3/scene.js +60 -0
- package/src/motion3/schema/brief.js +89 -0
- package/src/motion3/schema/fields.js +44 -0
- package/src/motion3/schema/spec-examples.js +46 -0
- package/src/motion3/schema/topics.js +111 -0
- package/src/motion3/schema/types.js +147 -0
- package/src/motion3/search.js +117 -0
- package/src/motion3/second-hand.js +112 -0
- package/src/motion3/skills/basic.js +82 -0
- package/src/motion3/skills/book.js +76 -0
- package/src/motion3/skills/car.js +145 -0
- package/src/motion3/skills/frames-auto.js +29 -0
- package/src/motion3/skills/index.js +83 -0
- package/src/motion3/skills/info.js +65 -0
- package/src/motion3/skills/smoke-kit.js +109 -0
- package/src/motion3/skills/smoke.js +160 -0
- package/src/motion3/spec-store.js +59 -0
- package/src/motion3/take-edit.js +155 -0
- package/src/motion3/take.js +63 -0
- package/src/motion3/timeline.js +127 -0
- package/src/motion3/views.js +121 -0
- package/src/motion3/walk-plan.js +119 -0
- package/src/object-catalog.jsx +7 -1
- package/src/object-colors.js +98 -0
- package/src/object-gizmo-parts.js +115 -0
- package/src/object-gizmo.jsx +37 -122
- package/src/object-glow.js +139 -0
- package/src/object-hidden.js +33 -0
- package/src/object-look.js +196 -0
- package/src/object-size.js +10 -0
- package/src/object-travel.js +71 -6
- package/src/panels/CameraPanel.jsx +7 -7
- package/src/panels/HdrEnvironmentControls.jsx +59 -0
- package/src/panels/LightPanel.jsx +25 -4
- package/src/panels/ObjectLookPanel.jsx +88 -0
- package/src/panels/PosePanel.jsx +2 -17
- package/src/panels/RigControlPanel.jsx +5 -53
- package/src/panels/SubjectBox.jsx +4 -1
- package/src/panels/details.css +14 -0
- package/src/posestudio.jsx +4 -27
- package/src/project-browser.jsx +3 -4
- package/src/project-resources.js +18 -31
- package/src/project.js +13 -104
- package/src/props.jsx +25 -280
- package/src/rail-dot-drag.js +23 -0
- package/src/render-passes.js +10 -0
- package/src/resource-status.jsx +1 -0
- package/src/result-modal.jsx +4 -19
- package/src/room.jsx +20 -12
- package/src/scene-assets.js +8 -6
- package/src/scene-light-place.js +72 -0
- package/src/scene-lights.jsx +148 -0
- package/src/scene-objects.js +31 -139
- package/src/scenes.js +15 -105
- package/src/semantic-edit.js +2 -5
- package/src/set-pieces.jsx +183 -0
- package/src/settings-menu.jsx +3 -11
- package/src/shell/BottomDock.jsx +9 -51
- package/src/shell/DetailsSlot.jsx +16 -200
- package/src/shell/GhostsMenuGroup.jsx +74 -0
- package/src/shell/LibrarySlot.jsx +1 -3
- package/src/shell/MenuBar.jsx +3 -3
- package/src/shell/PreferencesDialog.jsx +15 -264
- package/src/shell/PreferencesSlot.jsx +2 -8
- package/src/shell/TopBar.jsx +5 -84
- package/src/shell/ViewportToolbar.jsx +10 -70
- package/src/shell/agent-glass.css +19 -6
- package/src/shell/details-selection.js +42 -0
- package/src/shell/ghosts-menu.css +22 -0
- package/src/shell/glass-regions.css +29 -3
- package/src/shell/preferences.css +6 -1
- package/src/shot-authoring.js +4 -7
- package/src/soft-shadows.js +37 -0
- package/src/stage-attach-frames.js +12 -12
- package/src/stage-canvas-rigs.jsx +3 -3
- package/src/stage-capture.jsx +42 -8
- package/src/stage-characters.jsx +22 -216
- package/src/stage-editor-gizmos.jsx +6 -6
- package/src/stage-environment.js +93 -0
- package/src/stage-hdr-builtins.js +107 -0
- package/src/stage-hdr-image.js +116 -0
- package/src/stage-hdr-library.js +70 -0
- package/src/stage-hdr-map.js +94 -0
- package/src/stage-hdr.js +130 -0
- package/src/stage-lighting.js +149 -0
- package/src/stage-occlusion.js +210 -0
- package/src/stage-sky.jsx +159 -0
- package/src/stage-startup-defaults.js +5 -9
- package/src/studio-actions.js +12 -14
- package/src/studio-agent-commands.js +19 -16
- package/src/studio-agent-motion.js +9 -9
- package/src/studio-agent-protocol.js +41 -71
- package/src/studio-app-binding.js +25 -45
- package/src/studio-asset-list.js +17 -0
- package/src/studio-contact-sheet.js +37 -5
- package/src/studio-elements.js +27 -7
- package/src/studio-entity-bounds.js +18 -0
- package/src/studio-error-codes.js +8 -0
- package/src/studio-export-framing.js +152 -0
- package/src/studio-lighting-check.js +103 -0
- package/src/studio-motion-visual.js +150 -0
- package/src/studio-schema-errors.js +80 -0
- package/src/studio-support-gap.js +74 -0
- package/src/styles.css +1 -19
- package/src/timeline-defaults.js +5 -0
- package/src/timeline-extent.js +8 -20
- package/src/tutorial-walk.js +17 -0
- package/src/ui-scrub.js +1 -1
- package/src/use-shot-camera-tracks.js +12 -0
- package/src/view-ghosts.js +97 -0
- package/src/view-ghosts.jsx +49 -0
- package/src/workflow/AgentPanel.jsx +47 -166
- package/src/workflow/AgentProgressCard.jsx +107 -0
- package/src/workflow/AgentReplyText.jsx +37 -0
- package/src/workflow/AgentTurnSteps.jsx +84 -0
- package/src/workflow/LoginPanel.jsx +140 -0
- package/src/workflow/WorkflowBuilder.jsx +7 -23
- package/src/workflow/agent-client.js +54 -51
- package/src/workflow/agent-panel.css +71 -1
- package/src/workflow/agent-progress-card.css +167 -0
- package/src/workflow/agent-progress-view.js +47 -0
- package/src/workflow/agent-turn-steps.css +113 -0
- package/src/workflow/agent-turn.js +191 -0
- package/src/workflow/login-client.js +34 -0
- package/src/workflow/motion-input.js +18 -31
- package/src/workflow/node-schema.js +1 -1
- package/src/workflow/reply-markdown.js +69 -0
- package/src/workflow/scene-asset-sync.js +8 -8
- package/tools/agent-session-stats.mjs +61 -0
- package/tools/dev-full.mjs +5 -56
- package/tools/eslint-baseline.json +12 -13
- package/tools/knip-baseline.json +0 -179
- package/tools/loc-baseline.json +31 -81
- package/tools/motion3/extract-body.mjs +103 -0
- package/tools/motion3/extract-rig.mjs +85 -0
- package/tools/process-supervisor.mjs +0 -88
- package/tools/run-tests.mjs +8 -124
- package/bin/agent/motion-runtime.mjs +0 -76
- package/dist/ardy/cskel27-rest.json +0 -557
- package/dist/assets/analytics-C9pMpW7s.js +0 -1
- package/dist/assets/app-B_2XB9rw.css +0 -1
- package/dist/assets/app-DAuvKPP8.js +0 -4920
- package/dist/assets/config-Bx7pYsD9.js +0 -1
- package/dist/assets/config-DeoFTYzb.css +0 -1
- package/dist/assets/demo-CvKuBgy5.js +0 -1
- package/dist/assets/scene-asset-sync-DKkpjKBV.js +0 -19
- package/dist/assets/scene-asset-sync-DjJ_cJ2R.css +0 -1
- package/dist/assets/ticket-BPk2Ni95.js +0 -1
- package/dist/assets/workflow-jJRBR94Z.js +0 -179
- package/dist/demo/walk-then-stop.npz +0 -0
- package/mcp/ardy-prompts.mjs +0 -218
- package/src/ardy/auto-fix-panel.css +0 -108
- package/src/ardy/auto-physics.js +0 -1194
- package/src/ardy/client.js +0 -153
- package/src/ardy/convert.js +0 -313
- package/src/ardy/cskel27-neutral.js +0 -39
- package/src/ardy/cskel27.js +0 -77
- package/src/ardy/export.js +0 -165
- package/src/ardy/fix-collisions.js +0 -2257
- package/src/ardy/foot-lock.js +0 -230
- package/src/ardy/motion-calibration.js +0 -118
- package/src/ardy/motion-edit.js +0 -213
- package/src/ardy/motion-url.js +0 -55
- package/src/ardy/mp4-duration.js +0 -102
- package/src/ardy/npz.js +0 -595
- package/src/ardy/physics-panel.css +0 -21
- package/src/ardy/physics-panel.jsx +0 -89
- package/src/ardy/physics-refine.js +0 -111
- package/src/ardy/physics-review.js +0 -567
- package/src/ardy/physics-temporal.js +0 -54
- package/src/ardy/platform-fit-panel.css +0 -64
- package/src/ardy/platform-fit-panel.jsx +0 -41
- package/src/ardy/platform-fit.js +0 -326
- package/src/ardy/playback.js +0 -711
- package/src/ardy/pose-pin.js +0 -97
- package/src/ardy/prompt-clips.js +0 -30
- package/src/ardy/retime.js +0 -211
- package/src/ardy/root-drop.js +0 -369
- package/src/ardy/timeline-resize.js +0 -13
- package/src/ardy/to-cskel27.js +0 -225
- package/src/ardy/trim.js +0 -33
- package/src/ardy/waypoints.js +0 -369
- package/src/ardy-bridge-contract.js +0 -281
- package/src/domains/motion/motion-video-capture.js +0 -664
- package/src/i2v-motion-client.js +0 -77
- package/src/i2v-motion-studio.jsx +0 -180
- package/src/line-edit.js +0 -1348
- package/src/motion/generation.js +0 -105
- package/src/motion-readiness-ui.jsx +0 -67
- package/src/motion-readiness.js +0 -40
- package/src/motion-resources.js +0 -177
- package/src/motion-store.js +0 -65
- package/src/motion-trail.js +0 -601
- package/src/multimodel-ingest.js +0 -394
- package/src/panels/PromptBlocksPanel.jsx +0 -444
- package/src/panels/VideoCapturePanel.jsx +0 -181
- package/src/pose-extract/browser.js +0 -42
- package/src/pose-extract/detector.js +0 -100
- package/src/pose-extract/fit.js +0 -227
- package/src/pose-extract/image-frame.js +0 -51
- package/src/pose-extract/index.js +0 -58
- package/src/pose-extract/keyframes.js +0 -74
- package/src/pose-extract/landmarks.js +0 -162
- package/src/pose-extract/mirror.js +0 -153
- package/src/pose-extract/take.js +0 -131
- package/src/pose-extract/video-frames.js +0 -91
- package/src/take-recipe.js +0 -165
- package/src/trail-key-conflicts.js +0 -39
- package/src/trail-pick.js +0 -59
- package/src/use-i2v-capture-modal.js +0 -15
- package/tools/ardy/BRIDGE.md +0 -37
- package/tools/ardy/GVHMR-NPZ.md +0 -68
- package/tools/ardy/README.md +0 -124
- package/tools/ardy/__pycache__/cclay_gvhmr_worker.cpython-313.pyc +0 -0
- package/tools/ardy/artifacts.mjs +0 -34
- package/tools/ardy/bridge.mjs +0 -1732
- package/tools/ardy/bvh-cskel27.mjs +0 -1265
- package/tools/ardy/cclay_constrained_generate.py +0 -561
- package/tools/ardy/cclay_gvhmr_worker.py +0 -264
- package/tools/ardy/cclay_motion_edit.py +0 -510
- package/tools/ardy/cclay_sequence_generate.py +0 -705
- package/tools/ardy/cclay_worker.py +0 -304
- package/tools/ardy/dump-npz.py +0 -205
- package/tools/ardy/extract-rest.mjs +0 -299
- package/tools/ardy/extract.mjs +0 -507
- package/tools/ardy/footage.mjs +0 -467
- package/tools/ardy/gvhmr-floor.mjs +0 -102
- package/tools/ardy/gvhmr-to-cskel27.mjs +0 -21
- package/tools/ardy/gvhmr_fastpath.py +0 -168
- package/tools/ardy/gvhmr_trajectory.py +0 -240
- package/tools/ardy/merge-text-encoder.py +0 -160
- package/tools/ardy/mocap-metrics.mjs +0 -200
- package/tools/ardy/mocap-quality-gate.mjs +0 -110
- package/tools/ardy/motion-stabilize.mjs +0 -348
- package/tools/ardy/npz.mjs +0 -414
- package/tools/ardy/pose-to-npz.mjs +0 -106
- package/tools/ardy/prompt-limits.mjs +0 -5
- package/tools/ardy/run-edit-on-box.sh +0 -73
- package/tools/ardy/run-local.mjs +0 -233
- package/tools/ardy/run-on-box.sh +0 -593
- package/tools/ardy/run-sequence-on-box.sh +0 -177
- package/tools/ardy/runners/gvhmr-worker.mjs +0 -206
- package/tools/ardy/runners/index.mjs +0 -18
- package/tools/ardy/runners/local.mjs +0 -455
- package/tools/ardy/runners/proc.mjs +0 -125
- package/tools/ardy/runners/remote.mjs +0 -252
- package/tools/ardy/setup-local.mjs +0 -189
- package/tools/ardy/setup-text-encoder-on-box.sh +0 -93
- package/tools/ardy/setup-text-encoder.py +0 -306
- package/tools/ardy/smpl-cskel27.mjs +0 -178
- package/tools/ardy/visual-qa.mjs +0 -196
- package/tools/ardy/vq-car.mjs +0 -52
- package/tools/bench/EXP3.md +0 -61
- package/tools/bench/cclay_bench_extract_incam.py +0 -125
- package/tools/bench/cclay_bench_extract_obs.py +0 -204
- package/tools/bench/cclay_bench_runner.py +0 -42
- package/tools/bench/cube-contact.mjs +0 -162
- package/tools/bench/exp3.mjs +0 -152
- package/tools/bench/extract-bench-lib.mjs +0 -216
- package/tools/bench/extract-bench.mjs +0 -302
- package/tools/bench/fal-generate.mjs +0 -88
- package/tools/bench/fit/README.md +0 -189
- package/tools/bench/fit/camera.mjs +0 -59
- package/tools/bench/fit/contact.mjs +0 -419
- package/tools/bench/fit/footlock.mjs +0 -234
- package/tools/bench/fit/motion.mjs +0 -85
- package/tools/bench/fit/pin.mjs +0 -27
- package/tools/bench/fit/remote.mjs +0 -105
- package/tools/bench/fit-bench.mjs +0 -126
- package/tools/bench/fit-sanity.mjs +0 -45
- package/tools/bench/metrics.mjs +0 -313
- package/tools/bench/obs/depth.mjs +0 -258
- package/tools/bench/obs/extrinsics.mjs +0 -175
- package/tools/bench/obs/fit_mannequin_betas.py +0 -340
- package/tools/bench/obs/ground.mjs +0 -294
- package/tools/bench/obs/heading.mjs +0 -151
- package/tools/bench/obs/ladder.mjs +0 -555
- package/tools/bench/obs/mannequin-betas.json +0 -124
- package/tools/bench/obs/remote.mjs +0 -83
- package/tools/bench/obs/rest_joints.py +0 -52
- package/tools/bench/obs/ybot-targets.mjs +0 -49
- package/tools/bench/obs-bench.mjs +0 -520
- package/tools/bench/score.mjs +0 -343
- package/tools/bench/summarize.mjs +0 -86
- package/tools/demo-worker/README.md +0 -137
- package/tools/demo-worker/api-client.mjs +0 -233
- package/tools/demo-worker/generate.mjs +0 -301
- package/tools/demo-worker/index.mjs +0 -466
- package/tools/demo-worker/sign-core.mjs +0 -112
- package/tools/demo-worker/sign.mjs +0 -161
- package/tools/gt-render/browser.mjs +0 -182
- package/tools/gt-render/camera-math.mjs +0 -276
- package/tools/gt-render/page.mjs +0 -267
- package/tools/gt-render/render.mjs +0 -514
- package/tools/gt-render/scene-box.mjs +0 -31
- package/tools/gt-render/take-transform.mjs +0 -87
- package/tools/kimodo/compare-seam.mjs +0 -108
- package/tools/kimodo/constraints.mjs +0 -244
- package/tools/kimodo/edit.mjs +0 -259
- package/tools/kimodo/effector-constraints.mjs +0 -200
- package/tools/kimodo/generate.mjs +0 -646
- package/tools/kimodo/local-output.mjs +0 -265
- package/tools/kimodo/measure-pose.mjs +0 -65
- package/tools/kimodo/measure-preserve.mjs +0 -899
- package/tools/kimodo/measure-waypoints.mjs +0 -55
- package/tools/kimodo/mlx-generate.py +0 -111
- package/tools/kimodo/pose-constraints.mjs +0 -248
- package/tools/kimodo/preserve-mask.mjs +0 -543
- package/tools/kimodo/read-npz.mjs +0 -189
- package/tools/kimodo/run-edit-on-box.mjs +0 -155
- package/tools/kimodo/run-sequence-on-box.mjs +0 -178
- package/tools/kimodo/runner.mjs +0 -284
- package/tools/kimodo/seam-batch.mjs +0 -77
- package/tools/kimodo/setup-local.mjs +0 -30
- package/tools/kimodo/setup-on-box.sh +0 -339
- package/tools/kimodo/soma77-to-cskel27.mjs +0 -305
- package/tools/morphgs/assets/gen_truth.py +0 -39
- package/tools/morphgs/assets/mesh_ori_rig.txt +0 -27
- package/tools/morphgs/assets/playback-check.mjs +0 -30
- package/tools/morphgs/demo-gate.sh +0 -26
- package/tools/morphgs/fbx2morphgs.mjs +0 -68
- package/tools/morphgs/morphgs-to-cskel27.mjs +0 -105
- package/tools/morphgs/patches/preprocess_src-none-mode.patch +0 -76
- package/tools/morphgs/setup-on-cluster.sh +0 -126
- package/tools/projflow/driver.py +0 -1069
- package/tools/projflow/generate.mjs +0 -805
- package/tools/projflow/hml22-to-cskel27.mjs +0 -735
- package/tools/projflow/line-edit-job.mjs +0 -547
- package/tools/projflow/replay.mjs +0 -452
- package/tools/projflow/runner.mjs +0 -269
- package/tools/projflow/service.mjs +0 -742
- package/tools/track/DESIGN.md +0 -513
- package/tools/track/backfill-provenance.mjs +0 -116
- package/tools/track/budget.mjs +0 -8
- package/tools/track/check-rig.mjs +0 -209
- package/tools/track/diagnostics.schema.json +0 -60
- package/tools/track/export-rig.mjs +0 -256
- package/tools/track/fallback.mjs +0 -7
- package/tools/track/fk-parity-fixture.mjs +0 -161
- package/tools/track/gate.mjs +0 -349
- package/tools/track/masks.mjs +0 -318
- package/tools/track/metrics.mjs +0 -201
- package/tools/track/publish-obs.mjs +0 -108
- package/tools/track/py/check_env.py +0 -61
- package/tools/track/py/eval_lr.py +0 -229
- package/tools/track/py/lr_viterbi.py +0 -294
- package/tools/track/py/masks.py +0 -405
- package/tools/track/py/objective.py +0 -382
- package/tools/track/py/rig.py +0 -508
- package/tools/track/py/scene.py +0 -326
- package/tools/track/py/test_joint_indices.py +0 -123
- package/tools/track/py/test_lr_viterbi.py +0 -195
- package/tools/track/py/test_masks.py +0 -229
- package/tools/track/py/test_rig.py +0 -288
- package/tools/track/py/test_scene.py +0 -354
- package/tools/track/py/test_track.py +0 -328
- package/tools/track/py/track.py +0 -411
- package/tools/track/remote.mjs +0 -140
- package/tools/track/rig-dump.mjs +0 -227
- package/tools/track/run-box-tests.mjs +0 -14
- package/tools/track/run-box.mjs +0 -168
- package/tools/track/setup-box.sh +0 -59
- package/tools/track/study-2d.mjs +0 -728
- /package/src/{ardy → motion}/collision-blockers.js +0 -0
- /package/src/{ardy → motion}/ground.js +0 -0
- /package/src/{ardy → motion}/ik-drag.js +0 -0
- /package/src/{ardy → motion}/ik-key-json.js +0 -0
- /package/src/{ardy → motion}/ik.js +0 -0
- /package/src/{ardy → motion}/physics-surface.js +0 -0
- /package/src/{ardy → motion}/playback-clock.js +0 -0
- /package/src/{ardy → motion}/range-pin.js +0 -0
|
@@ -1,742 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* service.mjs — the resident (warm) ProjFlow child, contract C11.
|
|
3
|
-
*
|
|
4
|
-
* WHY THIS EXISTS. Gate GP3 measured a line edit at 7.6-8.6 s wall, of which
|
|
5
|
-
* 3.9 s is loading the same 625 MB checkpoint the previous edit already loaded
|
|
6
|
-
* and 1.3 s is the actual sample. The detail loop ("drag, look, drag again") is
|
|
7
|
-
* ProjFlow's job now, so the load has to be paid once per SESSION rather than
|
|
8
|
-
* once per drag. That is the entire idea: one `ssh <host> python driver.py
|
|
9
|
-
* --serve` child, owned by this module, fed one NDJSON request per line.
|
|
10
|
-
*
|
|
11
|
-
* WHAT THIS MODULE IS NOT. It is not a scheduler, not a pool and not a server.
|
|
12
|
-
* There is no port, no tunnel and nothing listening on the box — the transport
|
|
13
|
-
* is the ssh child's own stdin/stdout, which means the resident inherits ssh's
|
|
14
|
-
* authentication and dies with the pipe. One child, one request in flight, a
|
|
15
|
-
* queue for the rest.
|
|
16
|
-
*
|
|
17
|
-
* THE COLD PATH IS THE CONTRACT OF RECORD. Every failure here — a dead child, a
|
|
18
|
-
* response whose id does not match the request, a timeout, a driver error
|
|
19
|
-
* envelope — turns into a `ResidentError`, and generate.mjs answers a
|
|
20
|
-
* ResidentError by running the cold spawn path it always had. So the worst a
|
|
21
|
-
* broken resident can do is make an edit as slow as it is today. That is why
|
|
22
|
-
* the failure paths below are aggressive rather than forgiving: there is no
|
|
23
|
-
* situation in which retrying inside the warm path beats falling back.
|
|
24
|
-
*
|
|
25
|
-
* TWO KINDS OF FAILURE, DELIBERATELY TREATED DIFFERENTLY:
|
|
26
|
-
*
|
|
27
|
-
* TRANSPORT failures (child died, id mismatch, unsolicited line, timeout)
|
|
28
|
-
* mean the stream can no longer be trusted to pair responses with
|
|
29
|
-
* requests. The child is killed and restarted with backoff.
|
|
30
|
-
* ENVELOPE failures ({"ok": false} — a bad track, a frame range past the end
|
|
31
|
-
* of the source) mean the driver understood us perfectly and refused. The
|
|
32
|
-
* child is HEALTHY and stays up; the caller still falls back cold, where
|
|
33
|
-
* the same request produces the same named error from the path the
|
|
34
|
-
* contract is written against.
|
|
35
|
-
*
|
|
36
|
-
* RESTART POLICY. 1 s, 5 s, 25 s, capped, and only while a session looks
|
|
37
|
-
* recoverable: after MAX_CONSECUTIVE_FAILURES the background loop gives up and
|
|
38
|
-
* the service goes idle, so a box that is simply gone does not leave a retry
|
|
39
|
-
* loop spawning ssh forever. The next edit starts it again on demand, which is
|
|
40
|
-
* the right trigger — someone is at the keyboard.
|
|
41
|
-
*
|
|
42
|
-
* ENV
|
|
43
|
-
* CCLAY_PROJFLOW_RESIDENT=0 disable the resident entirely (cold only)
|
|
44
|
-
* CCLAY_PROJFLOW_RESIDENT_READY_MS how long to wait for the model load
|
|
45
|
-
* CCLAY_PROJFLOW_RESIDENT_REQ_MS per-request ceiling on the warm path
|
|
46
|
-
*/
|
|
47
|
-
|
|
48
|
-
import { spawn } from "node:child_process";
|
|
49
|
-
import { randomBytes } from "node:crypto";
|
|
50
|
-
import { dirname, join } from "node:path";
|
|
51
|
-
import { fileURLToPath } from "node:url";
|
|
52
|
-
|
|
53
|
-
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
54
|
-
const DRIVER = join(HERE, "driver.py");
|
|
55
|
-
|
|
56
|
-
/** The same ssh options generate.mjs uses for the cold path, plus the keepalive
|
|
57
|
-
* that matters more here: this child is expected to sit idle between edits. */
|
|
58
|
-
const SSH_OPTS = [
|
|
59
|
-
"-o", "BatchMode=yes",
|
|
60
|
-
"-o", "ConnectTimeout=10",
|
|
61
|
-
"-o", "ServerAliveInterval=30",
|
|
62
|
-
"-o", "ServerAliveCountMax=6",
|
|
63
|
-
];
|
|
64
|
-
|
|
65
|
-
/** Bumped in lockstep with driver.py's PROTOCOL_VERSION. A resident left over
|
|
66
|
-
* from an earlier build answers with the old number and is refused rather than
|
|
67
|
-
* trusted, which is the whole reason the field exists.
|
|
68
|
-
*
|
|
69
|
-
* NOT bumped for `seamPin` (2026-08-29), and the reason is worth stating because
|
|
70
|
-
* it looks like an omission. The version guards FRAMING and field MEANING: an
|
|
71
|
-
* old resident handed the new field ignores it, a new resident handed a request
|
|
72
|
-
* without it applies its own default, and in both directions every existing
|
|
73
|
-
* field still means exactly what it meant. Bumping would have been the safer
|
|
74
|
-
* instinct and was measurably wrong — a long-lived wrapper process holds this
|
|
75
|
-
* constant in memory while it re-ships driver.py from DISK on every resident
|
|
76
|
-
* start, so a bump makes the freshly shipped driver announce a number its own
|
|
77
|
-
* launcher refuses, and the supervisor crash-loops the box until the wrapper
|
|
78
|
-
* itself is restarted. Bump this only when a stale resident would produce a
|
|
79
|
-
* WRONG answer rather than an older one, and expect to restart every wrapper. */
|
|
80
|
-
export const PROTOCOL_VERSION = 1;
|
|
81
|
-
|
|
82
|
-
/** 1 s, 5 s, 25 s, then 25 s forever. Short enough that a transient ssh blip
|
|
83
|
-
* costs one cold edit, long enough that a box being rebooted is not hammered. */
|
|
84
|
-
export const BACKOFF_MS = Object.freeze([1_000, 5_000, 25_000]);
|
|
85
|
-
export const MAX_CONSECUTIVE_FAILURES = 6;
|
|
86
|
-
|
|
87
|
-
/** scp + ssh + a 3.9 s model load, with room for a cold page cache. */
|
|
88
|
-
const DEFAULT_READY_MS = 45_000;
|
|
89
|
-
/** A 100-step edit samples in ~1.3 s; the ceiling exists to bound a hung child,
|
|
90
|
-
* not to be a realistic budget. */
|
|
91
|
-
const DEFAULT_REQUEST_MS = 120_000;
|
|
92
|
-
const PING_MS = 10_000;
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* A failure that means "use the cold path".
|
|
96
|
-
*
|
|
97
|
-
* `kind` is the only field callers branch on and it exists for one decision:
|
|
98
|
-
* whether the child is still trustworthy. `transport` kills and restarts it;
|
|
99
|
-
* `envelope` and `disabled` leave it alone.
|
|
100
|
-
*/
|
|
101
|
-
export class ResidentError extends Error {
|
|
102
|
-
constructor(message, { kind = "transport", cause } = {}) {
|
|
103
|
-
super(message);
|
|
104
|
-
this.name = "ResidentError";
|
|
105
|
-
this.kind = kind;
|
|
106
|
-
if (cause !== undefined) this.cause = cause;
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
/** How long to wait before the Nth consecutive restart (N counted from 1).
|
|
111
|
-
* `schedule` exists so a service and this function cannot end up with two
|
|
112
|
-
* spellings of the same clamp; only tests ever pass one. */
|
|
113
|
-
export function backoffDelay(failures, schedule = BACKOFF_MS) {
|
|
114
|
-
const index = Math.max(0, Math.floor(failures) - 1);
|
|
115
|
-
return schedule[Math.min(index, schedule.length - 1)];
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
/** The resident is opt-OUT: it is the fast path and the cold path is always
|
|
119
|
-
* underneath it, so the default is on and the env var is an escape hatch for
|
|
120
|
-
* bisecting a bad session. */
|
|
121
|
-
export function residentEnabled(env = process.env) {
|
|
122
|
-
const value = env.CCLAY_PROJFLOW_RESIDENT;
|
|
123
|
-
return !(value === "0" || value === "false" || value === "off");
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
function positiveInt(value, fallback) {
|
|
127
|
-
const number = Number(value);
|
|
128
|
-
return Number.isFinite(number) && number > 0 ? Math.floor(number) : fallback;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
/* ------------------------------------------------------------------------- */
|
|
132
|
-
/* the transport */
|
|
133
|
-
/* ------------------------------------------------------------------------- */
|
|
134
|
-
|
|
135
|
-
function runOnce(argv, timeoutMs) {
|
|
136
|
-
return new Promise((resolve) => {
|
|
137
|
-
const child = spawn(argv[0], argv.slice(1), { stdio: ["ignore", "pipe", "pipe"] });
|
|
138
|
-
let stdout = "";
|
|
139
|
-
let stderr = "";
|
|
140
|
-
const timer = setTimeout(() => child.kill("SIGKILL"), timeoutMs);
|
|
141
|
-
child.stdout.on("data", (chunk) => (stdout += chunk));
|
|
142
|
-
child.stderr.on("data", (chunk) => (stderr += chunk));
|
|
143
|
-
child.on("error", (error) => {
|
|
144
|
-
clearTimeout(timer);
|
|
145
|
-
resolve({ code: -1, stdout, stderr: `${stderr}${error.message}` });
|
|
146
|
-
});
|
|
147
|
-
child.on("close", (code) => {
|
|
148
|
-
clearTimeout(timer);
|
|
149
|
-
resolve({ code, stdout, stderr });
|
|
150
|
-
});
|
|
151
|
-
});
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
/**
|
|
155
|
-
* Ship the CURRENT driver.py and start it in serve mode.
|
|
156
|
-
*
|
|
157
|
-
* The deploy is the cold path's mechanism, unchanged: scp the file that is in
|
|
158
|
-
* the tree right now, to a path this process generated. Nothing is installed on
|
|
159
|
-
* the box and nothing survives the session — the remote copy is removed when the
|
|
160
|
-
* service stops, and if the local process dies without stopping cleanly, a
|
|
161
|
-
* single file in /tmp is the whole residue.
|
|
162
|
-
*
|
|
163
|
-
* `env HOME=... exec python` rather than a bare assignment: `exec` guarantees
|
|
164
|
-
* the ssh channel owns the python process directly, so when this end of the
|
|
165
|
-
* pipe closes, sshd's SIGHUP lands on the driver rather than on a shell that
|
|
166
|
-
* would leave it orphaned holding 800 MiB of VRAM.
|
|
167
|
-
*/
|
|
168
|
-
async function defaultStartChild({ host, python, repo, boxHome, driverPath, onLine }) {
|
|
169
|
-
const remoteDriver = `/tmp/cclay-projflow-serve-${Date.now()}-${randomBytes(4).toString("hex")}.py`;
|
|
170
|
-
const pushed = await runOnce(["scp", ...SSH_OPTS, driverPath, `${host}:${remoteDriver}`], 60_000);
|
|
171
|
-
if (pushed.code !== 0) {
|
|
172
|
-
throw new ResidentError(
|
|
173
|
-
`could not copy the serve driver to ${host} (exit ${pushed.code}): ${pushed.stderr.trim()}`
|
|
174
|
-
);
|
|
175
|
-
}
|
|
176
|
-
const remote = `exec env HOME=${boxHome} ${python} ${remoteDriver} --serve --repo ${JSON.stringify(repo)}`;
|
|
177
|
-
onLine?.(`projflow-resident: starting ${host}:${remoteDriver}`);
|
|
178
|
-
const child = spawn("ssh", [...SSH_OPTS, host, remote], { stdio: ["pipe", "pipe", "pipe"] });
|
|
179
|
-
child.cleanupRemote = async () => {
|
|
180
|
-
await runOnce(["ssh", ...SSH_OPTS, host, `rm -f ${remoteDriver}`], 20_000);
|
|
181
|
-
};
|
|
182
|
-
return child;
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
/* ------------------------------------------------------------------------- */
|
|
186
|
-
/* the service */
|
|
187
|
-
/* ------------------------------------------------------------------------- */
|
|
188
|
-
|
|
189
|
-
/**
|
|
190
|
-
* One resident child and everything that can go wrong with it.
|
|
191
|
-
*
|
|
192
|
-
* @param {object} config
|
|
193
|
-
* @param {string} config.host ssh destination
|
|
194
|
-
* @param {string} config.python the scout venv's python on the box
|
|
195
|
-
* @param {string} config.repo the ProjFlow clone on the box
|
|
196
|
-
* @param {string} config.boxHome HOME override (keeps CLIP's cache in the scout dir)
|
|
197
|
-
* @param {Function} [config.startChild] injected for tests: returns a child
|
|
198
|
-
* process-like object with piped stdio. The default scps driver.py and spawns
|
|
199
|
-
* `ssh <host> python driver.py --serve`.
|
|
200
|
-
*/
|
|
201
|
-
export function createResidentService({
|
|
202
|
-
host,
|
|
203
|
-
python,
|
|
204
|
-
repo,
|
|
205
|
-
boxHome,
|
|
206
|
-
driverPath = DRIVER,
|
|
207
|
-
startChild = defaultStartChild,
|
|
208
|
-
// The schedule is a parameter for one reason: the restart state machine is
|
|
209
|
-
// worth testing and 1 + 5 + 25 seconds is not a test. Production never passes
|
|
210
|
-
// it.
|
|
211
|
-
backoff = BACKOFF_MS,
|
|
212
|
-
readyTimeoutMs = positiveInt(process.env.CCLAY_PROJFLOW_RESIDENT_READY_MS, DEFAULT_READY_MS),
|
|
213
|
-
requestTimeoutMs = positiveInt(process.env.CCLAY_PROJFLOW_RESIDENT_REQ_MS, DEFAULT_REQUEST_MS),
|
|
214
|
-
onLine,
|
|
215
|
-
registerExitHook = true,
|
|
216
|
-
} = {}) {
|
|
217
|
-
if (!host) throw new Error("createResidentService: host is required");
|
|
218
|
-
|
|
219
|
-
let child = null;
|
|
220
|
-
let state = "idle"; // idle | starting | ready | backoff | stopped
|
|
221
|
-
let readyPromise = null;
|
|
222
|
-
let readyInfo = null;
|
|
223
|
-
let failures = 0;
|
|
224
|
-
let restartTimer = null;
|
|
225
|
-
let pending = null; // the single in-flight request
|
|
226
|
-
let readyWaiters = [];
|
|
227
|
-
let queue = Promise.resolve();
|
|
228
|
-
let counter = 0;
|
|
229
|
-
let stdoutBuffer = "";
|
|
230
|
-
|
|
231
|
-
/**
|
|
232
|
-
* Does the warm child count as work this process is waiting for?
|
|
233
|
-
*
|
|
234
|
-
* It must, while a request is in flight or the model is loading — otherwise
|
|
235
|
-
* node would exit out from under an awaited edit, because the only pending
|
|
236
|
-
* handle is a pipe. It must NOT while the child is merely warm, or every
|
|
237
|
-
* short-lived process that ever ran an edit would hang forever holding an
|
|
238
|
-
* idle ssh open. So the child is ref'd exactly for the duration of the work
|
|
239
|
-
* and unref'd the rest of the time; the exit hook does the killing.
|
|
240
|
-
*/
|
|
241
|
-
function keepAlive(on) {
|
|
242
|
-
if (!child) return;
|
|
243
|
-
const handles = [child, child.stdin, child.stdout, child.stderr];
|
|
244
|
-
for (const handle of handles) {
|
|
245
|
-
try {
|
|
246
|
-
if (on) handle?.ref?.();
|
|
247
|
-
else handle?.unref?.();
|
|
248
|
-
} catch {
|
|
249
|
-
// A stream that is already destroyed has nothing to ref.
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
const say = (text) => {
|
|
255
|
-
try {
|
|
256
|
-
(pending?.onLine || onLine)?.(text);
|
|
257
|
-
} catch {
|
|
258
|
-
// A status sink that throws must not take the service with it.
|
|
259
|
-
}
|
|
260
|
-
};
|
|
261
|
-
|
|
262
|
-
/** Kill the child and forget it. `reason` is null for an orderly stop. */
|
|
263
|
-
function tearDown(reason) {
|
|
264
|
-
const dying = child;
|
|
265
|
-
child = null;
|
|
266
|
-
readyPromise = null;
|
|
267
|
-
readyInfo = null;
|
|
268
|
-
stdoutBuffer = "";
|
|
269
|
-
const error = new ResidentError(reason || "the resident was stopped", { kind: "transport" });
|
|
270
|
-
if (pending) {
|
|
271
|
-
const failed = pending;
|
|
272
|
-
pending = null;
|
|
273
|
-
clearTimeout(failed.timer);
|
|
274
|
-
failed.reject(error);
|
|
275
|
-
}
|
|
276
|
-
const waiting = readyWaiters;
|
|
277
|
-
readyWaiters = [];
|
|
278
|
-
for (const waiter of waiting) waiter.reject(error);
|
|
279
|
-
if (dying) {
|
|
280
|
-
dying.removeAllListeners?.("close");
|
|
281
|
-
dying.removeAllListeners?.("error");
|
|
282
|
-
try {
|
|
283
|
-
dying.stdin?.end?.();
|
|
284
|
-
} catch {
|
|
285
|
-
// The pipe may already be gone; killing is what matters.
|
|
286
|
-
}
|
|
287
|
-
try {
|
|
288
|
-
dying.kill?.("SIGKILL");
|
|
289
|
-
} catch {
|
|
290
|
-
// Same.
|
|
291
|
-
}
|
|
292
|
-
// Best-effort: the remote /tmp copy of the driver. A failure here is
|
|
293
|
-
// one stale file, never a failed edit, so it is never awaited.
|
|
294
|
-
Promise.resolve(dying.cleanupRemote?.()).catch(() => {});
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
/** A transport failure: drop the child, count it, and schedule a restart so
|
|
299
|
-
* the NEXT edit is warm even though this one goes cold. */
|
|
300
|
-
function fail(reason) {
|
|
301
|
-
if (state === "stopped") return;
|
|
302
|
-
tearDown(reason);
|
|
303
|
-
failures += 1;
|
|
304
|
-
if (failures >= MAX_CONSECUTIVE_FAILURES) {
|
|
305
|
-
// The box is not coming back on its own. Go idle rather than keep an
|
|
306
|
-
// ssh retry loop alive; the next request restarts on demand.
|
|
307
|
-
state = "idle";
|
|
308
|
-
say(`projflow-resident: giving up after ${failures} failed starts (${reason}); edits stay cold`);
|
|
309
|
-
return;
|
|
310
|
-
}
|
|
311
|
-
const delay = backoffDelay(failures, backoff);
|
|
312
|
-
state = "backoff";
|
|
313
|
-
say(`projflow-resident: ${reason}; restarting in ${delay / 1000}s`);
|
|
314
|
-
clearTimeout(restartTimer);
|
|
315
|
-
restartTimer = setTimeout(() => {
|
|
316
|
-
restartTimer = null;
|
|
317
|
-
if (state !== "backoff") return;
|
|
318
|
-
state = "idle";
|
|
319
|
-
// Fire and forget: a background restart that fails just schedules the
|
|
320
|
-
// next one, and nobody is awaiting this.
|
|
321
|
-
start().catch(() => {});
|
|
322
|
-
}, delay);
|
|
323
|
-
// The timer must never hold the process open — a warm model is a
|
|
324
|
-
// convenience, not a reason for `node` to refuse to exit.
|
|
325
|
-
restartTimer.unref?.();
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
function handleMessage(message) {
|
|
329
|
-
if (message && message.type === "ready") {
|
|
330
|
-
if (readyInfo) {
|
|
331
|
-
fail("the resident announced itself twice");
|
|
332
|
-
return;
|
|
333
|
-
}
|
|
334
|
-
readyInfo = message;
|
|
335
|
-
const waiting = readyWaiters;
|
|
336
|
-
readyWaiters = [];
|
|
337
|
-
for (const waiter of waiting) waiter.resolve(message);
|
|
338
|
-
return;
|
|
339
|
-
}
|
|
340
|
-
if (!pending) {
|
|
341
|
-
// Nothing is in flight, so this line cannot be an answer to anything.
|
|
342
|
-
// The two sides no longer agree about framing.
|
|
343
|
-
fail(`the resident sent an unsolicited response (id ${JSON.stringify(message?.id)})`);
|
|
344
|
-
return;
|
|
345
|
-
}
|
|
346
|
-
if (message?.id !== pending.id) {
|
|
347
|
-
// THE stale-response guard: pairing this with the in-flight request
|
|
348
|
-
// would hand the caller another edit's motion.
|
|
349
|
-
const wanted = pending.id;
|
|
350
|
-
fail(`response id ${JSON.stringify(message?.id)} does not match request ${JSON.stringify(wanted)}`);
|
|
351
|
-
return;
|
|
352
|
-
}
|
|
353
|
-
const settled = pending;
|
|
354
|
-
pending = null;
|
|
355
|
-
clearTimeout(settled.timer);
|
|
356
|
-
keepAlive(false);
|
|
357
|
-
if (message.ok) {
|
|
358
|
-
settled.resolve(message.result);
|
|
359
|
-
return;
|
|
360
|
-
}
|
|
361
|
-
// The driver refused the request. It is still healthy, so it stays up and
|
|
362
|
-
// only this request falls back.
|
|
363
|
-
const detail = message.error?.message || "the resident refused the request";
|
|
364
|
-
settled.reject(new ResidentError(detail, { kind: "envelope" }));
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
function attach(spawned) {
|
|
368
|
-
spawned.stdout?.setEncoding?.("utf8");
|
|
369
|
-
spawned.stdout?.on("data", (chunk) => {
|
|
370
|
-
stdoutBuffer += chunk;
|
|
371
|
-
let newline = stdoutBuffer.indexOf("\n");
|
|
372
|
-
while (newline >= 0) {
|
|
373
|
-
const text = stdoutBuffer.slice(0, newline).trim();
|
|
374
|
-
stdoutBuffer = stdoutBuffer.slice(newline + 1);
|
|
375
|
-
if (text) {
|
|
376
|
-
let message;
|
|
377
|
-
try {
|
|
378
|
-
message = JSON.parse(text);
|
|
379
|
-
} catch {
|
|
380
|
-
// driver.py points fd 1 at stderr before it imports anything,
|
|
381
|
-
// so a non-JSON line on stdout means something wrote past that
|
|
382
|
-
// guard and the stream is no longer a protocol.
|
|
383
|
-
fail(`the resident wrote a non-protocol line to stdout: ${text.slice(0, 120)}`);
|
|
384
|
-
return;
|
|
385
|
-
}
|
|
386
|
-
handleMessage(message);
|
|
387
|
-
if (!child) return; // handleMessage tore the child down
|
|
388
|
-
}
|
|
389
|
-
newline = stdoutBuffer.indexOf("\n");
|
|
390
|
-
}
|
|
391
|
-
});
|
|
392
|
-
spawned.stderr?.setEncoding?.("utf8");
|
|
393
|
-
spawned.stderr?.on("data", (chunk) => {
|
|
394
|
-
for (const text of String(chunk).split("\n")) if (text.trim()) say(text);
|
|
395
|
-
});
|
|
396
|
-
spawned.on("error", (error) => fail(`the resident child errored: ${error.message}`));
|
|
397
|
-
spawned.on("close", (code, signal) => {
|
|
398
|
-
if (state === "stopped") return;
|
|
399
|
-
fail(`the resident exited (code ${code}${signal ? `, ${signal}` : ""})`);
|
|
400
|
-
});
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
/**
|
|
404
|
-
* Start the child and resolve once it has announced a compatible protocol.
|
|
405
|
-
*
|
|
406
|
-
* Memoised, and the memo is CLEARED by `fail` (through tearDown), so a start
|
|
407
|
-
* that failed is never handed to the next caller as a cached rejection — the
|
|
408
|
-
* next edit gets a fresh attempt or a fast "in backoff" refusal, both of
|
|
409
|
-
* which are honest answers.
|
|
410
|
-
*/
|
|
411
|
-
function start() {
|
|
412
|
-
if (state === "stopped") return Promise.reject(new ResidentError("the resident is stopped"));
|
|
413
|
-
// Backoff means "we already know this is broken, and the clock has not run
|
|
414
|
-
// out". Starting anyway would spend the backoff's whole purpose — and it
|
|
415
|
-
// would add a start attempt to an edit that should already be running
|
|
416
|
-
// cold. Only the restart timer moves the state out of backoff.
|
|
417
|
-
if (state === "backoff") {
|
|
418
|
-
return Promise.reject(new ResidentError("the resident is restarting; this edit goes cold"));
|
|
419
|
-
}
|
|
420
|
-
if (readyPromise) return readyPromise;
|
|
421
|
-
state = "starting";
|
|
422
|
-
const attempt = (async () => {
|
|
423
|
-
const spawned = await startChild({ host, python, repo, boxHome, driverPath, onLine: say });
|
|
424
|
-
child = spawned;
|
|
425
|
-
attach(spawned);
|
|
426
|
-
keepAlive(true); // the model load is work this process is waiting on
|
|
427
|
-
const announced = readyInfo ?? await new Promise((resolve, reject) => {
|
|
428
|
-
const timer = setTimeout(
|
|
429
|
-
() => fail(`the resident did not become ready within ${readyTimeoutMs} ms`),
|
|
430
|
-
readyTimeoutMs
|
|
431
|
-
);
|
|
432
|
-
// NOT unref'd, unlike the restart timer below: this one is the
|
|
433
|
-
// promise's own guarantee that it settles, and a process whose only
|
|
434
|
-
// remaining work is an awaited start must not exit out from under it.
|
|
435
|
-
readyWaiters.push({
|
|
436
|
-
resolve: (message) => {
|
|
437
|
-
clearTimeout(timer);
|
|
438
|
-
resolve(message);
|
|
439
|
-
},
|
|
440
|
-
reject: (error) => {
|
|
441
|
-
clearTimeout(timer);
|
|
442
|
-
reject(error);
|
|
443
|
-
},
|
|
444
|
-
});
|
|
445
|
-
});
|
|
446
|
-
if (Number(announced.protocol) !== PROTOCOL_VERSION) {
|
|
447
|
-
throw new ResidentError(
|
|
448
|
-
`the resident speaks protocol ${announced.protocol}, this build speaks ${PROTOCOL_VERSION}`
|
|
449
|
-
);
|
|
450
|
-
}
|
|
451
|
-
state = "ready";
|
|
452
|
-
failures = 0;
|
|
453
|
-
if (!pending) keepAlive(false);
|
|
454
|
-
say(
|
|
455
|
-
`projflow-resident: ready on ${announced.device ?? "?"} in ${announced.loadSeconds ?? "?"}s ` +
|
|
456
|
-
`(protocol ${announced.protocol})`
|
|
457
|
-
);
|
|
458
|
-
return announced;
|
|
459
|
-
})().catch((error) => {
|
|
460
|
-
const wrapped = error instanceof ResidentError
|
|
461
|
-
? error
|
|
462
|
-
: new ResidentError(`the resident failed to start: ${error.message}`, { cause: error });
|
|
463
|
-
// `fail` is idempotent enough for this: when the child died on its own
|
|
464
|
-
// it has already run, and a second call only costs another backoff
|
|
465
|
-
// slot on a service that is not going to be used until it restarts.
|
|
466
|
-
if (state === "starting" || state === "ready") fail(wrapped.message);
|
|
467
|
-
throw wrapped;
|
|
468
|
-
});
|
|
469
|
-
readyPromise = attempt;
|
|
470
|
-
return attempt;
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
function send(payload, timeoutMs, lineSink) {
|
|
474
|
-
return new Promise((resolve, reject) => {
|
|
475
|
-
if (!child || state !== "ready") {
|
|
476
|
-
reject(new ResidentError("the resident is not ready"));
|
|
477
|
-
return;
|
|
478
|
-
}
|
|
479
|
-
counter += 1;
|
|
480
|
-
const id = `r${counter}`;
|
|
481
|
-
const timer = setTimeout(() => {
|
|
482
|
-
// A timeout is a TRANSPORT failure, not a slow answer we can wait
|
|
483
|
-
// out: if the response arrives later it would be paired with the
|
|
484
|
-
// next request, which is exactly the bug the ids exist to prevent.
|
|
485
|
-
fail(`request ${id} timed out after ${timeoutMs} ms`);
|
|
486
|
-
}, timeoutMs);
|
|
487
|
-
// Ref'd on purpose (see the ready timeout): this timer is what makes an
|
|
488
|
-
// awaited edit always settle, warm child or not.
|
|
489
|
-
pending = { id, resolve, reject, timer, onLine: lineSink };
|
|
490
|
-
keepAlive(true);
|
|
491
|
-
try {
|
|
492
|
-
child.stdin.write(`${JSON.stringify({ id, ...payload })}\n`);
|
|
493
|
-
} catch (error) {
|
|
494
|
-
fail(`could not write to the resident: ${error.message}`);
|
|
495
|
-
}
|
|
496
|
-
});
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
/**
|
|
500
|
-
* Queue one request against the child, starting it if necessary.
|
|
501
|
-
*
|
|
502
|
-
* Serialised on purpose. The bridge sends one line edit at a time today, but
|
|
503
|
-
* "today" is not a guarantee, and a second writer would interleave lines on
|
|
504
|
-
* one stdin.
|
|
505
|
-
*/
|
|
506
|
-
function request(payload, { timeoutMs = requestTimeoutMs, onLine: lineSink } = {}) {
|
|
507
|
-
if (state === "stopped") return Promise.reject(new ResidentError("the resident is stopped"));
|
|
508
|
-
// A child that is in backoff is a child we already know is broken. Waiting
|
|
509
|
-
// for it would add its backoff to an edit that could already be running
|
|
510
|
-
// cold.
|
|
511
|
-
if (state === "backoff") {
|
|
512
|
-
return Promise.reject(new ResidentError("the resident is restarting; this edit goes cold", { kind: "transport" }));
|
|
513
|
-
}
|
|
514
|
-
const run = async () => {
|
|
515
|
-
await start();
|
|
516
|
-
return send(payload, timeoutMs, lineSink);
|
|
517
|
-
};
|
|
518
|
-
// `.then(run, run)` rather than `.then(run)`: one failed request must not
|
|
519
|
-
// poison the queue for the next one.
|
|
520
|
-
const result = queue.then(run, run);
|
|
521
|
-
queue = result.then(() => {}, () => {});
|
|
522
|
-
return result;
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
function stop() {
|
|
526
|
-
state = "stopped";
|
|
527
|
-
clearTimeout(restartTimer);
|
|
528
|
-
restartTimer = null;
|
|
529
|
-
tearDown(null);
|
|
530
|
-
LIVE.delete(service);
|
|
531
|
-
}
|
|
532
|
-
|
|
533
|
-
const service = {
|
|
534
|
-
key: JSON.stringify({ host, python, repo, boxHome }),
|
|
535
|
-
host,
|
|
536
|
-
request,
|
|
537
|
-
start,
|
|
538
|
-
stop,
|
|
539
|
-
state: () => state,
|
|
540
|
-
failures: () => failures,
|
|
541
|
-
isReady: () => state === "ready" && Boolean(child),
|
|
542
|
-
info: () => readyInfo,
|
|
543
|
-
/** A ping is the only way to claim a resident is up: `state === "ready"`
|
|
544
|
-
* says the child announced itself, an answered ping says it is still
|
|
545
|
-
* there. probeHealth reports the second, never the first. */
|
|
546
|
-
ping: (timeoutMs = PING_MS) => request({ type: "ping" }, { timeoutMs }),
|
|
547
|
-
};
|
|
548
|
-
|
|
549
|
-
if (registerExitHook) armExitCleanup(service);
|
|
550
|
-
return service;
|
|
551
|
-
}
|
|
552
|
-
|
|
553
|
-
/* ------------------------------------------------------------------------- */
|
|
554
|
-
/* process lifetime */
|
|
555
|
-
/* ------------------------------------------------------------------------- */
|
|
556
|
-
|
|
557
|
-
const LIVE = new Set();
|
|
558
|
-
let hooksArmed = false;
|
|
559
|
-
|
|
560
|
-
/**
|
|
561
|
-
* Kill every resident when this process goes away.
|
|
562
|
-
*
|
|
563
|
-
* `exit` covers the normal case. The signal handlers are more careful than they
|
|
564
|
-
* look: adding a SIGTERM listener SUPPRESSES node's default termination, so a
|
|
565
|
-
* handler installed by a library would silently make the bridge unkillable.
|
|
566
|
-
* When nobody else is listening we kill the children, remove ourselves and
|
|
567
|
-
* re-raise the signal so the default behaviour still happens; when the host
|
|
568
|
-
* process has its own handler we only do the cleanup and let it decide.
|
|
569
|
-
*/
|
|
570
|
-
function armExitCleanup(service) {
|
|
571
|
-
LIVE.add(service);
|
|
572
|
-
if (hooksArmed) return;
|
|
573
|
-
hooksArmed = true;
|
|
574
|
-
const killAll = () => {
|
|
575
|
-
for (const live of LIVE) {
|
|
576
|
-
try {
|
|
577
|
-
live.stop();
|
|
578
|
-
} catch {
|
|
579
|
-
// Nothing useful to do on the way out.
|
|
580
|
-
}
|
|
581
|
-
}
|
|
582
|
-
LIVE.clear();
|
|
583
|
-
};
|
|
584
|
-
process.once("exit", killAll);
|
|
585
|
-
for (const signal of ["SIGINT", "SIGTERM", "SIGHUP"]) {
|
|
586
|
-
const hostHandles = process.listenerCount(signal) > 0;
|
|
587
|
-
const handler = () => {
|
|
588
|
-
killAll();
|
|
589
|
-
if (!hostHandles) {
|
|
590
|
-
process.removeListener(signal, handler);
|
|
591
|
-
process.kill(process.pid, signal);
|
|
592
|
-
}
|
|
593
|
-
};
|
|
594
|
-
process.on(signal, handler);
|
|
595
|
-
}
|
|
596
|
-
}
|
|
597
|
-
|
|
598
|
-
/* ------------------------------------------------------------------------- */
|
|
599
|
-
/* the shared instance */
|
|
600
|
-
/* ------------------------------------------------------------------------- */
|
|
601
|
-
|
|
602
|
-
let shared = null;
|
|
603
|
-
|
|
604
|
-
/** The process-wide resident for one box configuration, created on demand.
|
|
605
|
-
* Changing the box (or the venv, or the repo) replaces it: a warm child pointed
|
|
606
|
-
* at the wrong repo is worse than no child at all. */
|
|
607
|
-
export function getResidentService(config) {
|
|
608
|
-
const key = JSON.stringify({
|
|
609
|
-
host: config.host,
|
|
610
|
-
python: config.python,
|
|
611
|
-
repo: config.repo,
|
|
612
|
-
boxHome: config.boxHome,
|
|
613
|
-
});
|
|
614
|
-
if (shared && shared.key !== key) {
|
|
615
|
-
shared.stop();
|
|
616
|
-
shared = null;
|
|
617
|
-
}
|
|
618
|
-
if (!shared) shared = createResidentService(config);
|
|
619
|
-
return shared;
|
|
620
|
-
}
|
|
621
|
-
|
|
622
|
-
/** The existing resident, or null. Used by probeHealth, which must never START
|
|
623
|
-
* a model load just to answer a health question. */
|
|
624
|
-
export function peekResidentService(config) {
|
|
625
|
-
if (!shared) return null;
|
|
626
|
-
if (!config) return shared;
|
|
627
|
-
const key = JSON.stringify({
|
|
628
|
-
host: config.host,
|
|
629
|
-
python: config.python,
|
|
630
|
-
repo: config.repo,
|
|
631
|
-
boxHome: config.boxHome,
|
|
632
|
-
});
|
|
633
|
-
return shared.key === key ? shared : null;
|
|
634
|
-
}
|
|
635
|
-
|
|
636
|
-
export function stopResidentService() {
|
|
637
|
-
if (shared) shared.stop();
|
|
638
|
-
shared = null;
|
|
639
|
-
}
|
|
640
|
-
|
|
641
|
-
/* ------------------------------------------------------------------------- */
|
|
642
|
-
/* base64 float32 arrays */
|
|
643
|
-
/* ------------------------------------------------------------------------- */
|
|
644
|
-
|
|
645
|
-
/** A Float32Array as the {shape, dtype, data} blob driver.py decodes. Little
|
|
646
|
-
* endian is the wire format and every machine either side of this is; the
|
|
647
|
-
* dtype name travels so the driver can refuse rather than reinterpret. */
|
|
648
|
-
export function encodeFloat32(data, shape) {
|
|
649
|
-
if (!(data instanceof Float32Array)) throw new TypeError("encodeFloat32: data must be a Float32Array");
|
|
650
|
-
const count = shape.reduce((total, dim) => total * dim, 1);
|
|
651
|
-
if (count !== data.length) {
|
|
652
|
-
throw new Error(`encodeFloat32: shape (${shape.join(",")}) needs ${count} values, got ${data.length}`);
|
|
653
|
-
}
|
|
654
|
-
return {
|
|
655
|
-
shape: [...shape],
|
|
656
|
-
dtype: "float32",
|
|
657
|
-
data: Buffer.from(data.buffer, data.byteOffset, data.byteLength).toString("base64"),
|
|
658
|
-
};
|
|
659
|
-
}
|
|
660
|
-
|
|
661
|
-
/** The inverse, with the shape checked rather than trusted: a payload whose
|
|
662
|
-
* byte count disagrees with its own shape is a corrupt response, not a motion. */
|
|
663
|
-
export function decodeFloat32(blob) {
|
|
664
|
-
if (!blob || typeof blob !== "object") throw new ResidentError("the resident returned no array");
|
|
665
|
-
const shape = Array.isArray(blob.shape) ? blob.shape.map(Number) : null;
|
|
666
|
-
if (!shape || shape.some((dim) => !Number.isInteger(dim) || dim < 0)) {
|
|
667
|
-
throw new ResidentError(`the resident returned a bad shape ${JSON.stringify(blob.shape)}`);
|
|
668
|
-
}
|
|
669
|
-
if (blob.dtype && blob.dtype !== "float32") {
|
|
670
|
-
throw new ResidentError(`the resident returned dtype ${blob.dtype}; this path is float32 only`);
|
|
671
|
-
}
|
|
672
|
-
const bytes = Buffer.from(String(blob.data || ""), "base64");
|
|
673
|
-
const count = shape.reduce((total, dim) => total * dim, 1);
|
|
674
|
-
if (bytes.length !== count * 4) {
|
|
675
|
-
throw new ResidentError(
|
|
676
|
-
`the resident returned ${bytes.length} bytes for shape (${shape.join(",")}), expected ${count * 4}`
|
|
677
|
-
);
|
|
678
|
-
}
|
|
679
|
-
// A copy, not a view: Buffer.from(base64) has no alignment guarantee and a
|
|
680
|
-
// Float32Array view over an odd byteOffset throws.
|
|
681
|
-
const data = new Float32Array(count);
|
|
682
|
-
for (let index = 0; index < count; index += 1) data[index] = bytes.readFloatLE(index * 4);
|
|
683
|
-
return { data, shape };
|
|
684
|
-
}
|
|
685
|
-
|
|
686
|
-
/* ------------------------------------------------------------------------- */
|
|
687
|
-
/* one warm line edit */
|
|
688
|
-
/* ------------------------------------------------------------------------- */
|
|
689
|
-
|
|
690
|
-
/**
|
|
691
|
-
* Run one line edit on the warm child.
|
|
692
|
-
*
|
|
693
|
-
* Everything a one-shot invocation carries travels in the request, because the
|
|
694
|
-
* resident has no per-run argv: the C6 line document, the source motion as a
|
|
695
|
-
* base64 float32 blob, the step count (or the preview flag), the ridge, the
|
|
696
|
-
* preserve knobs, the seed and the cfg. What comes back is the same
|
|
697
|
-
* `(T,22,3)` motion and the same meta the one-shot writes to
|
|
698
|
-
* `<out>.meta.json`.
|
|
699
|
-
*
|
|
700
|
-
* @throws {ResidentError} on ANY failure. The caller's answer to that is the
|
|
701
|
-
* cold path — never a retry here.
|
|
702
|
-
*/
|
|
703
|
-
export async function residentLineEdit({
|
|
704
|
-
service,
|
|
705
|
-
source,
|
|
706
|
-
sourceShape,
|
|
707
|
-
line,
|
|
708
|
-
steps,
|
|
709
|
-
preview = false,
|
|
710
|
-
ridge,
|
|
711
|
-
preserveStride,
|
|
712
|
-
preserveMargin,
|
|
713
|
-
seamPin,
|
|
714
|
-
seed,
|
|
715
|
-
cfg,
|
|
716
|
-
timeoutMs,
|
|
717
|
-
onLine,
|
|
718
|
-
} = {}) {
|
|
719
|
-
if (!service) throw new ResidentError("residentLineEdit: no resident service");
|
|
720
|
-
const payload = {
|
|
721
|
-
type: "lineEdit",
|
|
722
|
-
line,
|
|
723
|
-
source: encodeFloat32(source, sourceShape),
|
|
724
|
-
preview: Boolean(preview),
|
|
725
|
-
steps: Number(steps),
|
|
726
|
-
ridge: Number(ridge),
|
|
727
|
-
preserveStride: Number(preserveStride),
|
|
728
|
-
preserveMargin: Number(preserveMargin),
|
|
729
|
-
};
|
|
730
|
-
if (seamPin !== undefined) payload.seamPin = Number(seamPin);
|
|
731
|
-
// Omitted rather than defaulted on this side: driver.py owns the defaults
|
|
732
|
-
// (0 and 3.0) and two places spelling them is how they drift.
|
|
733
|
-
if (seed !== undefined) payload.seed = Number(seed);
|
|
734
|
-
if (cfg !== undefined) payload.cfg = Number(cfg);
|
|
735
|
-
|
|
736
|
-
const result = await service.request(payload, { timeoutMs, onLine });
|
|
737
|
-
const { data, shape } = decodeFloat32(result);
|
|
738
|
-
if (!result.meta || typeof result.meta !== "object") {
|
|
739
|
-
throw new ResidentError("the resident returned a motion with no metadata");
|
|
740
|
-
}
|
|
741
|
-
return { positions: data, shape, meta: result.meta };
|
|
742
|
-
}
|