videobook 0.0.0-stage → 0.2.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 +54 -0
- package/LICENSE +21 -0
- package/README.md +195 -2
- package/bin/videobook.js +7 -0
- package/dist/acestep/client.js +203 -0
- package/dist/acestep/config.js +62 -0
- package/dist/acestep/errors.js +8 -0
- package/dist/acestep/process.js +175 -0
- package/dist/alive-captions/analyze.js +117 -0
- package/dist/alive-captions/audio.js +179 -0
- package/dist/alive-captions/frames.js +59 -0
- package/dist/alive-captions/model.js +37 -0
- package/dist/alive-captions/schema.js +49 -0
- package/dist/alive-captions/track.js +9 -0
- package/dist/api-key.js +33 -0
- package/dist/app-state.js +56 -0
- package/dist/app.js +257 -0
- package/dist/archive-b2.js +332 -0
- package/dist/archive-store.js +169 -0
- package/dist/artifact-label.js +36 -0
- package/dist/artifact-refs.js +49 -0
- package/dist/asset-tag-extraction.js +169 -0
- package/dist/asset-tags.js +228 -0
- package/dist/asset-usage.js +23 -0
- package/dist/audio-cue.js +71 -0
- package/dist/audio-encoding.js +32 -0
- package/dist/audio-sync-request.js +1 -0
- package/dist/audio-sync.js +223 -0
- package/dist/audio-trim-range.js +31 -0
- package/dist/book-context.js +31 -0
- package/dist/book-manager.js +374 -0
- package/dist/catalog-gc.js +26 -0
- package/dist/chain-executor.js +815 -0
- package/dist/chain-generation.js +115 -0
- package/dist/chain-plan.js +746 -0
- package/dist/chain-step-review.js +60 -0
- package/dist/character-name.js +25 -0
- package/dist/chat-generation-routing.js +73 -0
- package/dist/chat-generation-settings.js +78 -0
- package/dist/chat-models.js +44 -0
- package/dist/chat-prompt-rewrite.js +67 -0
- package/dist/chat-router.js +628 -0
- package/dist/chat-view-actions.js +49 -0
- package/dist/cli/args.js +242 -0
- package/dist/cli/config-command.js +103 -0
- package/dist/cli/doctor-command.js +289 -0
- package/dist/cli/entry.js +2 -0
- package/dist/cli/key-report.js +16 -0
- package/dist/cli/main.js +104 -0
- package/dist/cli/mcp-command.js +88 -0
- package/dist/cli/mcp-relay.js +51 -0
- package/dist/cli/open-browser.js +14 -0
- package/dist/cli/run-command.js +231 -0
- package/dist/cli/serve-command.js +62 -0
- package/dist/cli/setup-command.js +60 -0
- package/dist/cli/stdio-guard.js +8 -0
- package/dist/cold-open-model.js +7 -0
- package/dist/cold-open.js +79 -0
- package/dist/command-reference.js +95 -0
- package/dist/content-store.js +74 -0
- package/dist/cost.js +8 -0
- package/dist/crop-geometry.js +23 -0
- package/dist/dancing-workflow.js +447 -0
- package/dist/data-home.js +44 -0
- package/dist/diagnostics.js +109 -0
- package/dist/dialog-quality.js +34 -0
- package/dist/downloads-dest.js +56 -0
- package/dist/editing-moments.js +194 -0
- package/dist/elevenlabs-audio-extension.js +66 -0
- package/dist/engine.js +54 -0
- package/dist/env-file.js +91 -0
- package/dist/env-secrets.js +148 -0
- package/dist/ffmpeg-binary.js +12 -0
- package/dist/ffmpeg.js +425 -0
- package/dist/ffprobe-binary.js +77 -0
- package/dist/forget-asset.js +216 -0
- package/dist/frame-utils.js +27 -0
- package/dist/generation-credit-error.js +11 -0
- package/dist/generation-input-mentions.js +117 -0
- package/dist/generation-input-policy.js +68 -0
- package/dist/generation-reference-mentions.js +97 -0
- package/dist/goal-characters.js +49 -0
- package/dist/goal-plan-v3.js +273 -0
- package/dist/goal-plan.js +220 -0
- package/dist/goal-planner-prompt.js +28 -0
- package/dist/goal-prompt-budget.js +4 -0
- package/dist/goal-reference-images.js +25 -0
- package/dist/goal-state.js +261 -0
- package/dist/goal-stills.js +153 -0
- package/dist/goal-store.js +144 -0
- package/dist/goal-styles.js +144 -0
- package/dist/goal-v3-runtime.js +157 -0
- package/dist/guides/character_schema_guide.md +70 -0
- package/dist/guides/gpt_image_2_5_prompting_guide.md +21 -0
- package/dist/guides/grok_imagine_prompting_guide.md +72 -0
- package/dist/guides/image_reproduction_guide.md +84 -0
- package/dist/guides/kling_3_0_prompting_guide.md +136 -0
- package/dist/guides/nano_banana_pro_prompting_guide.md +28 -0
- package/dist/guides/seedance_2_prompting_guide.md +172 -0
- package/dist/http-access.js +49 -0
- package/dist/http-byte-range.js +31 -0
- package/dist/hydration.js +56 -0
- package/dist/image-compress.js +77 -0
- package/dist/image-edit-default.js +1 -0
- package/dist/image-edit-models.js +41 -0
- package/dist/image-edit-settings.js +11 -0
- package/dist/image-region-edit.js +32 -0
- package/dist/image-subject-map.js +523 -0
- package/dist/image.js +46 -0
- package/dist/index.js +12 -0
- package/dist/job-deadline.js +60 -0
- package/dist/jobs/_alibaba-upload.js +30 -0
- package/dist/jobs/_audio-chunks.js +31 -0
- package/dist/jobs/_download-into-asset.js +15 -0
- package/dist/jobs/_fal-shared.js +22 -0
- package/dist/jobs/_fal-upload.js +35 -0
- package/dist/jobs/_image-edit-adapter.js +213 -0
- package/dist/jobs/_kie-poll.js +119 -0
- package/dist/jobs/_kie-upload.js +32 -0
- package/dist/jobs/_lease.js +163 -0
- package/dist/jobs/_persist-output.js +19 -0
- package/dist/jobs/_storage-retry.js +21 -0
- package/dist/jobs/_transcribe.js +245 -0
- package/dist/jobs/_translate-transcript.js +270 -0
- package/dist/jobs/analyze-alive-captions.js +10 -0
- package/dist/jobs/analyze-editing-moments.js +104 -0
- package/dist/jobs/analyze-jokes.js +143 -0
- package/dist/jobs/analyze-keyword-placement.js +155 -0
- package/dist/jobs/analyze-music.js +14 -0
- package/dist/jobs/analyze-video.js +282 -0
- package/dist/jobs/apply-cuts.js +83 -0
- package/dist/jobs/apply-sfx.js +107 -0
- package/dist/jobs/archive-clip.js +458 -0
- package/dist/jobs/assemble-goal.js +98 -0
- package/dist/jobs/audio-boost.js +71 -0
- package/dist/jobs/audio-mix.js +161 -0
- package/dist/jobs/backfill-asset-tags.js +144 -0
- package/dist/jobs/burn-watermark.js +316 -0
- package/dist/jobs/change-video-speed.js +176 -0
- package/dist/jobs/clean-video-audio.js +207 -0
- package/dist/jobs/cold-open.js +166 -0
- package/dist/jobs/combine-goal.js +108 -0
- package/dist/jobs/complete-provider-task.js +185 -0
- package/dist/jobs/copy-library-asset.js +33 -0
- package/dist/jobs/cost-estimate-assessment.js +113 -0
- package/dist/jobs/crop-image.js +47 -0
- package/dist/jobs/crop-video.js +71 -0
- package/dist/jobs/describe-image.js +181 -0
- package/dist/jobs/detect-scenes.js +33 -0
- package/dist/jobs/doctor-fix.js +100 -0
- package/dist/jobs/download-video.js +427 -0
- package/dist/jobs/enqueue-search-index.js +24 -0
- package/dist/jobs/even-split.js +122 -0
- package/dist/jobs/export-sequence.js +365 -0
- package/dist/jobs/extend-audio.js +229 -0
- package/dist/jobs/extract-audio.js +72 -0
- package/dist/jobs/extract-frame.js +58 -0
- package/dist/jobs/extract-notebook-audio.js +82 -0
- package/dist/jobs/extract-notebook-frame.js +93 -0
- package/dist/jobs/extract-video-clip.js +8 -0
- package/dist/jobs/extract-video-poster.js +55 -0
- package/dist/jobs/extract-waveform.js +25 -0
- package/dist/jobs/finish-goal.js +255 -0
- package/dist/jobs/forget-asset.js +11 -0
- package/dist/jobs/generate-acestep-cover.js +219 -0
- package/dist/jobs/generate-media.js +70 -0
- package/dist/jobs/image-subjects.js +1059 -0
- package/dist/jobs/index.js +18 -0
- package/dist/jobs/inject-first-frame.js +175 -0
- package/dist/jobs/isolate-vocals.js +69 -0
- package/dist/jobs/kie-paygate.js +177 -0
- package/dist/jobs/label-music-sections.js +5 -0
- package/dist/jobs/osg-placement.js +135 -0
- package/dist/jobs/pad-video.js +72 -0
- package/dist/jobs/plan-goal.js +97 -0
- package/dist/jobs/post-record.js +21 -0
- package/dist/jobs/post-to-x.js +21 -0
- package/dist/jobs/prepare-search-reference.js +190 -0
- package/dist/jobs/prepare-temporal-search.js +16 -0
- package/dist/jobs/process-audio.js +67 -0
- package/dist/jobs/process-image.js +58 -0
- package/dist/jobs/process-upload.js +166 -0
- package/dist/jobs/process-video.js +438 -0
- package/dist/jobs/register-all.js +161 -0
- package/dist/jobs/relayout-notebook.js +78 -0
- package/dist/jobs/remove-sound.js +299 -0
- package/dist/jobs/render-project.js +380 -0
- package/dist/jobs/render-qr-outro.js +138 -0
- package/dist/jobs/render-timeline.js +283 -0
- package/dist/jobs/repair-search-indexes.js +42 -0
- package/dist/jobs/replace-video-audio.js +64 -0
- package/dist/jobs/restore-archive-clip.js +53 -0
- package/dist/jobs/reverse-video.js +53 -0
- package/dist/jobs/rewrite-script.js +173 -0
- package/dist/jobs/run-notebook.js +3335 -0
- package/dist/jobs/semantic-index.js +797 -0
- package/dist/jobs/smooth-scene-concat.js +257 -0
- package/dist/jobs/splice-video.js +280 -0
- package/dist/jobs/split-video.js +127 -0
- package/dist/jobs/suggest-cuts.js +149 -0
- package/dist/jobs/suggest-sfx.js +135 -0
- package/dist/jobs/sync-video-to-audio.js +329 -0
- package/dist/jobs/tag-asset.js +266 -0
- package/dist/jobs/task-failure.js +16 -0
- package/dist/jobs/timewarp-video.js +152 -0
- package/dist/jobs/transcribe-video-audio.js +117 -0
- package/dist/jobs/transcribe.js +35 -0
- package/dist/jobs/translate-transcript.js +55 -0
- package/dist/jobs/trim-audio.js +133 -0
- package/dist/jobs/trim-video.js +291 -0
- package/dist/jobs/xai-paygate.js +26 -0
- package/dist/keyword-placement-cache.js +74 -0
- package/dist/lazy-render.js +142 -0
- package/dist/library-browse.js +78 -0
- package/dist/lightning-defaults.js +206 -0
- package/dist/load-env.js +12 -0
- package/dist/local-search-models.js +206 -0
- package/dist/local-upload.js +318 -0
- package/dist/logger.js +69 -0
- package/dist/media/adapters/alibaba.js +20 -0
- package/dist/media/adapters/elevenlabs.js +58 -0
- package/dist/media/adapters/fal.js +45 -0
- package/dist/media/adapters/index.js +26 -0
- package/dist/media/adapters/kie.js +120 -0
- package/dist/media/adapters/pending.js +21 -0
- package/dist/media/adapters/types.js +5 -0
- package/dist/media/adapters/xai.js +18 -0
- package/dist/media/catalog.js +236 -0
- package/dist/media/complete.js +191 -0
- package/dist/media/cost.js +45 -0
- package/dist/media/descriptor.js +157 -0
- package/dist/media/endpoints.js +11 -0
- package/dist/media/frame-options.js +18 -0
- package/dist/media/insert-video-timing.js +17 -0
- package/dist/media/json-schema.js +75 -0
- package/dist/media/legacy-task-types.js +129 -0
- package/dist/media/listing.js +24 -0
- package/dist/media/model-id.js +14 -0
- package/dist/media/preview.js +86 -0
- package/dist/media/prompt-dialects.js +65 -0
- package/dist/media/ref-fields.js +106 -0
- package/dist/media/refs.js +131 -0
- package/dist/media/request.js +188 -0
- package/dist/media/types.js +6 -0
- package/dist/media/wire.js +161 -0
- package/dist/media-library.js +535 -0
- package/dist/media-output-path.js +33 -0
- package/dist/multyculty-workflow.js +757 -0
- package/dist/notebook-action-catalog.js +608 -0
- package/dist/notebook-cell-outputs.js +726 -0
- package/dist/notebook-credit-retry.js +45 -0
- package/dist/notebook-fixtures.js +8 -0
- package/dist/notebook-generation-contract.js +77 -0
- package/dist/notebook-generation-controls.js +145 -0
- package/dist/notebook-goal-layout.js +40 -0
- package/dist/notebook-graph.js +37 -0
- package/dist/notebook-image-edit.js +84 -0
- package/dist/notebook-initial-generation-prompt.js +222 -0
- package/dist/notebook-live-analysis.js +941 -0
- package/dist/notebook-media-ops.js +579 -0
- package/dist/notebook-mutations.js +20 -0
- package/dist/notebook-placement.js +41 -0
- package/dist/notebook-provider-failure.js +87 -0
- package/dist/notebook-published-post.js +26 -0
- package/dist/notebook-reference-resolver.js +227 -0
- package/dist/notebook-relayout.js +270 -0
- package/dist/notebook-run-plan.js +519 -0
- package/dist/notebook-shots.js +162 -0
- package/dist/notebook-workflow.js +856 -0
- package/dist/one-password.js +200 -0
- package/dist/openai-intelligence.js +99 -0
- package/dist/original-media-file.js +12 -0
- package/dist/osg-assets.js +36 -0
- package/dist/osg-placement-cache.js +64 -0
- package/dist/osg-settings.js +27 -0
- package/dist/paid-tools.js +22 -0
- package/dist/platform-guidance.js +26 -0
- package/dist/posts/dropbox-auth.js +91 -0
- package/dist/posts/dropbox-upload.js +132 -0
- package/dist/posts/icloud-drive.js +134 -0
- package/dist/posts/icloud-status.js +64 -0
- package/dist/posts/post-contract.js +2 -0
- package/dist/posts/post-copy-format.js +43 -0
- package/dist/posts/post-copy.js +193 -0
- package/dist/posts/post-folder.js +18 -0
- package/dist/posts/post-metrics.js +119 -0
- package/dist/posts/post-store.js +340 -0
- package/dist/posts/x-post-target.js +43 -0
- package/dist/preview-payload.js +90 -0
- package/dist/prompt-surfaces.js +22 -0
- package/dist/provider-prompt-transform.js +77 -0
- package/dist/provider-status-link.js +21 -0
- package/dist/provider-task-record.js +78 -0
- package/dist/provider-task-status.js +47 -0
- package/dist/provider-usage.js +294 -0
- package/dist/queue.js +810 -0
- package/dist/reference-video-duration.js +17 -0
- package/dist/remotion/BlurFillVideo.js +51 -0
- package/dist/remotion/KeywordGraphicsOverlay.js +68 -0
- package/dist/remotion/OsgOverlay.js +30 -0
- package/dist/remotion/QrOutroComposition.js +47 -0
- package/dist/remotion/RenderComposition.js +11 -0
- package/dist/remotion/browser.js +59 -0
- package/dist/remotion/constants.js +35 -0
- package/dist/remotion/index.js +66 -0
- package/dist/remotion/keyword-graphics.js +151 -0
- package/dist/remotion/keyword-placement.js +182 -0
- package/dist/remotion/osg-spec.js +198 -0
- package/dist/remotion/public/qr-outro-impact.wav +0 -0
- package/dist/remotion/qr-outro-audio.js +1 -0
- package/dist/remotion/qr-outro-spec.js +211 -0
- package/dist/remotion/shared-bundle.js +37 -0
- package/dist/remotion/subtitles/AliveCaptions.js +83 -0
- package/dist/remotion/subtitles/Highlight.js +58 -0
- package/dist/remotion/subtitles/Impact.js +67 -0
- package/dist/remotion/subtitles/SubtitleOverlay.js +9 -0
- package/dist/remotion/subtitles/alive-layout.js +321 -0
- package/dist/remotion/subtitles/alive-types.js +17 -0
- package/dist/remotion/subtitles/chunker.js +68 -0
- package/dist/remotion/subtitles/segments.js +42 -0
- package/dist/remotion/subtitles/styles.js +4 -0
- package/dist/remotion/subtitles/types.js +1 -0
- package/dist/remotion/util.js +18 -0
- package/dist/remotion-cancellation.js +15 -0
- package/dist/rename-helpers.js +204 -0
- package/dist/rhythm/analyze.js +128 -0
- package/dist/rhythm/contract.js +14 -0
- package/dist/rhythm/draft-model.js +19 -0
- package/dist/rhythm/energy.js +133 -0
- package/dist/rhythm/fft.js +44 -0
- package/dist/rhythm/model.js +71 -0
- package/dist/rhythm/music-analysis.js +124 -0
- package/dist/rhythm/music-draft.js +261 -0
- package/dist/rhythm/onset.js +101 -0
- package/dist/rhythm/run-worker.js +51 -0
- package/dist/rhythm/score.js +47 -0
- package/dist/rhythm/section-labels.js +168 -0
- package/dist/rhythm/sequence-rhythm.js +67 -0
- package/dist/rhythm/synthesize.js +103 -0
- package/dist/rhythm/timing.js +98 -0
- package/dist/rhythm/track.js +320 -0
- package/dist/rhythm/visual-fit.js +4 -0
- package/dist/rhythm/worker.js +25 -0
- package/dist/scene-detect.js +200 -0
- package/dist/scene-transition.js +12 -0
- package/dist/scheduled-posts.js +200 -0
- package/dist/search-index-coverage.js +13 -0
- package/dist/search-index-media.js +71 -0
- package/dist/search-reference-store.js +60 -0
- package/dist/search-scoring.js +17 -0
- package/dist/serve.js +125 -0
- package/dist/server-lock.js +143 -0
- package/dist/server.js +42 -0
- package/dist/srt.js +16 -0
- package/dist/tag-backfill-state.js +121 -0
- package/dist/tag-settings.js +30 -0
- package/dist/tag-vocabulary.js +237 -0
- package/dist/test-queue-cleanup.js +1 -0
- package/dist/time-parse.js +50 -0
- package/dist/timeline-helpers.js +554 -0
- package/dist/timewarp.js +87 -0
- package/dist/tools/_image-inputs.js +9 -0
- package/dist/tools/_media-refs.js +84 -0
- package/dist/tools/_shared.js +59 -0
- package/dist/tools/_suggest-shared.js +279 -0
- package/dist/tools/add-timeline-audio.js +73 -0
- package/dist/tools/alive-captions.js +157 -0
- package/dist/tools/analyze-jokes.js +56 -0
- package/dist/tools/analyze-video.js +66 -0
- package/dist/tools/animate-keywords.js +217 -0
- package/dist/tools/api-status.js +42 -0
- package/dist/tools/application-commands.js +34 -0
- package/dist/tools/apply-cuts.js +112 -0
- package/dist/tools/apply-sfx.js +152 -0
- package/dist/tools/apply-watermark.js +670 -0
- package/dist/tools/archive-clip.js +74 -0
- package/dist/tools/asset-history.js +61 -0
- package/dist/tools/asset-manifest.js +24 -0
- package/dist/tools/asset-tags.js +151 -0
- package/dist/tools/assign-slot.js +97 -0
- package/dist/tools/audio-boost.js +107 -0
- package/dist/tools/audio-mix.js +121 -0
- package/dist/tools/book-usage.js +16 -0
- package/dist/tools/chain.js +136 -0
- package/dist/tools/change-video-speed.js +105 -0
- package/dist/tools/chat-history.js +5 -0
- package/dist/tools/chat.js +848 -0
- package/dist/tools/clean-video-audio.js +97 -0
- package/dist/tools/clear-failed-tasks.js +66 -0
- package/dist/tools/cold-open.js +114 -0
- package/dist/tools/command-plan.js +1680 -0
- package/dist/tools/commands.js +1666 -0
- package/dist/tools/concat-images.js +236 -0
- package/dist/tools/connect-service.js +55 -0
- package/dist/tools/copy-file-to-clipboard.js +58 -0
- package/dist/tools/create-character.js +114 -0
- package/dist/tools/create-qart.js +253 -0
- package/dist/tools/create-qr-outro.js +149 -0
- package/dist/tools/create-qrbtf.js +215 -0
- package/dist/tools/create-watermark.js +97 -0
- package/dist/tools/crop-asset.js +18 -0
- package/dist/tools/crop-image.js +120 -0
- package/dist/tools/crop-video.js +97 -0
- package/dist/tools/delete-asset.js +104 -0
- package/dist/tools/describe-image.js +69 -0
- package/dist/tools/detect-scenes.js +44 -0
- package/dist/tools/doctor.js +126 -0
- package/dist/tools/download-video.js +114 -0
- package/dist/tools/duplicate-asset.js +184 -0
- package/dist/tools/edit-timeline.js +59 -0
- package/dist/tools/editing-moments.js +105 -0
- package/dist/tools/estimate-cost.js +171 -0
- package/dist/tools/even-split.js +295 -0
- package/dist/tools/export-asset-file.js +22 -0
- package/dist/tools/export-asset.js +140 -0
- package/dist/tools/export-sequence.js +250 -0
- package/dist/tools/export-to-downloads.js +54 -0
- package/dist/tools/export-transcript-srt.js +79 -0
- package/dist/tools/extend-audio.js +100 -0
- package/dist/tools/extract-audio.js +174 -0
- package/dist/tools/extract-frame.js +108 -0
- package/dist/tools/extract-notebook-audio.js +173 -0
- package/dist/tools/extract-notebook-frame.js +160 -0
- package/dist/tools/extract-video-clip.js +101 -0
- package/dist/tools/generate-media.js +157 -0
- package/dist/tools/generation-payload.js +48 -0
- package/dist/tools/get-timeline.js +12 -0
- package/dist/tools/get-watermark-config.js +38 -0
- package/dist/tools/goal.js +644 -0
- package/dist/tools/image-subjects.js +711 -0
- package/dist/tools/index.js +323 -0
- package/dist/tools/inject-first-frame.js +154 -0
- package/dist/tools/invert-image.js +70 -0
- package/dist/tools/isolate-vocals.js +66 -0
- package/dist/tools/list-assets.js +98 -0
- package/dist/tools/list-cell-generations.js +225 -0
- package/dist/tools/list-elevenlabs-voices.js +163 -0
- package/dist/tools/log-wrapper.js +29 -0
- package/dist/tools/make-cover-song.js +95 -0
- package/dist/tools/mark-asset-seen.js +23 -0
- package/dist/tools/media-library.js +245 -0
- package/dist/tools/move-timeline-audio.js +35 -0
- package/dist/tools/music-analysis.js +60 -0
- package/dist/tools/music-draft.js +29 -0
- package/dist/tools/pad-video.js +87 -0
- package/dist/tools/post-record.js +83 -0
- package/dist/tools/post.js +168 -0
- package/dist/tools/prompt-history.js +164 -0
- package/dist/tools/provider-key-status.js +248 -0
- package/dist/tools/qrbtf-render.js +849 -0
- package/dist/tools/read-asset-metadata.js +26 -0
- package/dist/tools/read-file-at-commit.js +29 -0
- package/dist/tools/read-transcript.js +41 -0
- package/dist/tools/reconcile-project-work.js +1452 -0
- package/dist/tools/recover-asset.js +28 -0
- package/dist/tools/remove-sound.js +72 -0
- package/dist/tools/remove-timeline-audio.js +34 -0
- package/dist/tools/remove-timeline-slot.js +40 -0
- package/dist/tools/rename-all.js +434 -0
- package/dist/tools/rename-asset.js +34 -0
- package/dist/tools/render-settings.js +47 -0
- package/dist/tools/render-timeline.js +120 -0
- package/dist/tools/render.js +384 -0
- package/dist/tools/reorder-character-images.js +45 -0
- package/dist/tools/replace-video-audio.js +133 -0
- package/dist/tools/restore-archive-clips.js +209 -0
- package/dist/tools/restore-asset-version.js +37 -0
- package/dist/tools/retry-generation.js +54 -0
- package/dist/tools/reverse-video.js +74 -0
- package/dist/tools/rewrite-prompt.js +388 -0
- package/dist/tools/rewrite-script.js +67 -0
- package/dist/tools/schema-from-apicity.js +66 -0
- package/dist/tools/search-archive.js +150 -0
- package/dist/tools/semantic-search.js +1214 -0
- package/dist/tools/sequence-rhythm.js +43 -0
- package/dist/tools/set-character-metadata.js +84 -0
- package/dist/tools/set-env-secret.js +40 -0
- package/dist/tools/set-osg.js +143 -0
- package/dist/tools/show-folder.js +37 -0
- package/dist/tools/smooth-scene-concat.js +122 -0
- package/dist/tools/splice-video.js +149 -0
- package/dist/tools/split-video.js +202 -0
- package/dist/tools/suggest-cuts.js +45 -0
- package/dist/tools/suggest-sfx.js +43 -0
- package/dist/tools/sync-video-to-audio.js +115 -0
- package/dist/tools/tag-backfill.js +177 -0
- package/dist/tools/timewarp-video.js +85 -0
- package/dist/tools/tool-def.js +1 -0
- package/dist/tools/tool-json-schema.js +116 -0
- package/dist/tools/transcribe.js +90 -0
- package/dist/tools/translate-subtitles.js +59 -0
- package/dist/tools/trim-audio.js +139 -0
- package/dist/tools/trim-video.js +168 -0
- package/dist/tools/update-script.js +25 -0
- package/dist/tools/update-timeline-audio.js +40 -0
- package/dist/tools/upload-file.js +41 -0
- package/dist/tools/verify-x-mentions.js +141 -0
- package/dist/tools/video-editing-command-contract.js +291 -0
- package/dist/tools/video-source-reference.js +30 -0
- package/dist/tools/video-time-input.js +18 -0
- package/dist/tools/videobook-domain.js +2021 -0
- package/dist/tools/wait-for-asset-ready.js +143 -0
- package/dist/tools/workspace-actions.js +54 -0
- package/dist/transcript-projection.js +202 -0
- package/dist/transcript-srt.js +33 -0
- package/dist/upload.js +76 -0
- package/dist/usage-providers.js +25 -0
- package/dist/video-analysis-projection.js +46 -0
- package/dist/video-poster.js +33 -0
- package/dist/video-proxy.js +99 -0
- package/dist/vision-regions.js +111 -0
- package/dist/voice-actions.js +90 -0
- package/dist/voice-audio.js +55 -0
- package/dist/voice-config.js +122 -0
- package/dist/voice-continuation.js +54 -0
- package/dist/voice-contract.js +1 -0
- package/dist/voice-http.js +145 -0
- package/dist/voice-reply-progress.js +55 -0
- package/dist/voice-session.js +992 -0
- package/dist/watermark-badge.js +259 -0
- package/dist/watermark-config.js +111 -0
- package/dist/watermark-handle-position.js +55 -0
- package/dist/watermark-platform.js +15 -0
- package/dist/watermark-timing.js +16 -0
- package/dist/workspace-action-catalog.js +315 -0
- package/dist/workspace-action-contract.js +21 -0
- package/dist/workspace-action-groups.js +98 -0
- package/dist/workspace-action-surfaces.js +7 -0
- package/dist/x/account.js +88 -0
- package/dist/x/authenticated-handle.js +30 -0
- package/dist/x/oauth-flow.js +191 -0
- package/dist/x/post-contract.js +1 -0
- package/dist/x/post-metadata.js +44 -0
- package/dist/x/post-to-x.js +456 -0
- package/dist/x/published-post-cell.js +90 -0
- package/dist/x/x-tokens.js +69 -0
- package/dist/ytdlp-binary.js +65 -0
- package/dist/zod-slots.js +334 -0
- package/package.json +84 -4
- package/ui/assets/alive-captions-BWb5nLKj.css +1 -0
- package/ui/assets/alive-captions-DPEr1nKM.js +1 -0
- package/ui/assets/confirm-bodies-D8ouHXKy.js +1 -0
- package/ui/assets/confirm-bodies-DrMhaXC6.css +1 -0
- package/ui/assets/fullscreen-B7nw4YbE.js +1 -0
- package/ui/assets/goal-ui-DK-xTCK0.js +2 -0
- package/ui/assets/goal-ui-DXdUM2KF.css +1 -0
- package/ui/assets/index-DzPAnCUj.css +1 -0
- package/ui/assets/index-kS1905s5.js +37 -0
- package/ui/assets/inspector-MlzQbTVo.js +1 -0
- package/ui/assets/jobs-ui-CoinOWx0.js +1 -0
- package/ui/assets/music-video-Cbe80kmQ.js +1 -0
- package/ui/assets/music-video-DPaiKKEq.css +1 -0
- package/ui/assets/ui-Oirr9VOf.css +1 -0
- package/ui/assets/ui-xo_Y1yQg.js +2 -0
- package/ui/assets/vendor-D2rfiZ5j.css +1 -0
- package/ui/assets/vendor-DfTCWgKm.js +19 -0
- package/ui/assets/vendor-flow-BXbeWxW7.js +7 -0
- package/ui/assets/vendor-flow-BnuhLJ6X.css +1 -0
- package/ui/assets/vendor-icons-CGqhCIXV.js +1 -0
- package/ui/assets/vendor-mcp-BTlEJcHV.js +67 -0
- package/ui/assets/vendor-react-Dj7D1fD4.js +9 -0
- package/ui/assets/vendor-remotion-CH1HXrlc.js +95 -0
- package/ui/assets/videobook-Qlp9HQFu.js +20 -0
- package/ui/favicon.svg +8 -0
- package/ui/index.html +126 -0
|
@@ -0,0 +1,815 @@
|
|
|
1
|
+
import { v7 as uuidv7 } from "uuid";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { notebookOccupiedSlots, NOTEBOOK_GRID_CAPACITY } from "videobook-engine/notebook-grid";
|
|
4
|
+
import { queue, waitForJob } from "./queue.js";
|
|
5
|
+
import { placeChainGeneration } from "./chain-generation.js";
|
|
6
|
+
import { buildChainStepReview } from "./chain-step-review.js";
|
|
7
|
+
import { failNotebookProviderOutput } from "./jobs/run-notebook.js";
|
|
8
|
+
import { tryResolveModel } from "./media/descriptor.js";
|
|
9
|
+
import { isChainRef, preflightChainPlan, readChainParam, revalidatePinnedTargets, substituteChainRefs, writeChainParam, supportedChainTool, } from "./chain-plan.js";
|
|
10
|
+
const SETTINGS_KEY = "application.chain-runs.v1";
|
|
11
|
+
const STEP_TIMEOUT_MS = 180_000;
|
|
12
|
+
// Provider generation is minutes, not seconds; matches awaitNotebookOutput's own budget.
|
|
13
|
+
const GENERATION_STEP_TIMEOUT_MS = 45 * 60_000;
|
|
14
|
+
const POLL_MS = 50;
|
|
15
|
+
const inflight = new Map();
|
|
16
|
+
function emptyStore() {
|
|
17
|
+
return { runs: {}, requestIndex: {} };
|
|
18
|
+
}
|
|
19
|
+
function readStore(engine) {
|
|
20
|
+
const parsed = engine.settings.get(SETTINGS_KEY);
|
|
21
|
+
if (!parsed || typeof parsed !== "object")
|
|
22
|
+
return emptyStore();
|
|
23
|
+
return {
|
|
24
|
+
runs: parsed.runs && typeof parsed.runs === "object" ? parsed.runs : {},
|
|
25
|
+
requestIndex: parsed.requestIndex && typeof parsed.requestIndex === "object"
|
|
26
|
+
? parsed.requestIndex
|
|
27
|
+
: {},
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
function writeStore(engine, store) {
|
|
31
|
+
const written = engine.settings.set(SETTINGS_KEY, store);
|
|
32
|
+
if (!written.ok)
|
|
33
|
+
throw new Error(written.error.message);
|
|
34
|
+
}
|
|
35
|
+
function requestKey(bookId, requestId) {
|
|
36
|
+
return `${bookId}:${requestId}`;
|
|
37
|
+
}
|
|
38
|
+
function nowIso() {
|
|
39
|
+
return new Date().toISOString();
|
|
40
|
+
}
|
|
41
|
+
function isClosedDatabaseError(error) {
|
|
42
|
+
return error instanceof Error && /database is closed/i.test(error.message);
|
|
43
|
+
}
|
|
44
|
+
function saveRun(engine, run) {
|
|
45
|
+
try {
|
|
46
|
+
const store = readStore(engine);
|
|
47
|
+
store.runs[run.chainId] = run;
|
|
48
|
+
if (run.requestId) {
|
|
49
|
+
store.requestIndex[requestKey(run.bookId, run.requestId)] = run.chainId;
|
|
50
|
+
}
|
|
51
|
+
writeStore(engine, store);
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
if (isClosedDatabaseError(error))
|
|
55
|
+
return run;
|
|
56
|
+
throw error;
|
|
57
|
+
}
|
|
58
|
+
return run;
|
|
59
|
+
}
|
|
60
|
+
export function getChainRun(engine, chainId) {
|
|
61
|
+
return readStore(engine).runs[chainId] ?? null;
|
|
62
|
+
}
|
|
63
|
+
function findChainRunByRequestId(engine, bookId, requestId) {
|
|
64
|
+
const store = readStore(engine);
|
|
65
|
+
const chainId = store.requestIndex[requestKey(bookId, requestId)];
|
|
66
|
+
return chainId ? store.runs[chainId] ?? null : null;
|
|
67
|
+
}
|
|
68
|
+
function isTerminal(status) {
|
|
69
|
+
return status === "completed"
|
|
70
|
+
|| status === "failed"
|
|
71
|
+
|| status === "cancelled"
|
|
72
|
+
|| status === "interrupted";
|
|
73
|
+
}
|
|
74
|
+
/** Terminal, or parked waiting on a person. Either way nothing is running. */
|
|
75
|
+
function isSettled(status) {
|
|
76
|
+
return isTerminal(status) || status === "awaiting-approval";
|
|
77
|
+
}
|
|
78
|
+
function parseJsonResult(result) {
|
|
79
|
+
const text = result.content.find((entry) => entry.type === "text")?.text ?? "";
|
|
80
|
+
try {
|
|
81
|
+
const parsed = JSON.parse(text);
|
|
82
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed)
|
|
83
|
+
? parsed
|
|
84
|
+
: { value: parsed };
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
return { text };
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
function delay(ms, signal) {
|
|
91
|
+
return new Promise((resolve, reject) => {
|
|
92
|
+
if (signal.aborted) {
|
|
93
|
+
reject(signal.reason instanceof Error ? signal.reason : new Error("Cancelled"));
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
const timer = setTimeout(resolve, ms);
|
|
97
|
+
const onAbort = () => {
|
|
98
|
+
clearTimeout(timer);
|
|
99
|
+
reject(signal.reason instanceof Error ? signal.reason : new Error("Cancelled"));
|
|
100
|
+
};
|
|
101
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
async function waitWhile(signal, timeoutMs, pending, label) {
|
|
105
|
+
const deadline = Date.now() + timeoutMs;
|
|
106
|
+
while (await pending()) {
|
|
107
|
+
if (signal.aborted) {
|
|
108
|
+
throw signal.reason instanceof Error ? signal.reason : new Error("Cancelled");
|
|
109
|
+
}
|
|
110
|
+
if (Date.now() >= deadline) {
|
|
111
|
+
throw new Error(`${label} timed out after ${timeoutMs}ms`);
|
|
112
|
+
}
|
|
113
|
+
await delay(POLL_MS, signal);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
async function waitForNamedJob(jobId, signal, timeoutMs) {
|
|
117
|
+
const wait = waitForJob(jobId, timeoutMs);
|
|
118
|
+
const abort = new Promise((_, reject) => {
|
|
119
|
+
const fail = () => {
|
|
120
|
+
reject(signal.reason instanceof Error ? signal.reason : new Error("Cancelled"));
|
|
121
|
+
};
|
|
122
|
+
if (signal.aborted)
|
|
123
|
+
fail();
|
|
124
|
+
else
|
|
125
|
+
signal.addEventListener("abort", fail, { once: true });
|
|
126
|
+
});
|
|
127
|
+
await Promise.race([wait, abort]);
|
|
128
|
+
}
|
|
129
|
+
async function waitForInternalJobs(engine, jobIds, signal, timeoutMs) {
|
|
130
|
+
await waitWhile(signal, timeoutMs, () => {
|
|
131
|
+
return jobIds.some((id) => {
|
|
132
|
+
const job = engine.jobs.queue.get(id);
|
|
133
|
+
return job !== null && (job.state === "queued"
|
|
134
|
+
|| job.state === "running"
|
|
135
|
+
|| job.state === "completing");
|
|
136
|
+
});
|
|
137
|
+
}, "Child job");
|
|
138
|
+
for (const id of jobIds) {
|
|
139
|
+
const job = engine.jobs.queue.get(id);
|
|
140
|
+
if (job?.state === "failed" || job?.state === "aborted") {
|
|
141
|
+
throw new Error(job.error?.message ?? `Job ${id} ${job.state}`);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
async function fileExists(engine, artifactId, filename) {
|
|
146
|
+
const manifest = await engine.files.manifest(artifactId);
|
|
147
|
+
return manifest.ok && manifest.value.files.some((file) => file.name === filename);
|
|
148
|
+
}
|
|
149
|
+
function applyPins(params, pins) {
|
|
150
|
+
let next = { ...params };
|
|
151
|
+
for (const pin of pins) {
|
|
152
|
+
const index = pin.index ?? null;
|
|
153
|
+
const current = readChainParam(next, pin.field);
|
|
154
|
+
if (index === null) {
|
|
155
|
+
if (typeof current === "string")
|
|
156
|
+
next = writeChainParam(next, pin.field, pin.artifactId);
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
if (!Array.isArray(current) || typeof current[index] !== "string")
|
|
160
|
+
continue;
|
|
161
|
+
const replaced = [...current];
|
|
162
|
+
replaced[index] = pin.artifactId;
|
|
163
|
+
next = writeChainParam(next, pin.field, replaced);
|
|
164
|
+
}
|
|
165
|
+
return next;
|
|
166
|
+
}
|
|
167
|
+
async function waitForFile(engine, artifactId, filename, signal, label) {
|
|
168
|
+
const deadline = Date.now() + STEP_TIMEOUT_MS;
|
|
169
|
+
while (!(await fileExists(engine, artifactId, filename))) {
|
|
170
|
+
if (signal.aborted) {
|
|
171
|
+
throw signal.reason instanceof Error ? signal.reason : new Error("Cancelled");
|
|
172
|
+
}
|
|
173
|
+
if (Date.now() >= deadline) {
|
|
174
|
+
throw new Error(`${label} finished without ${filename}`);
|
|
175
|
+
}
|
|
176
|
+
await delay(POLL_MS, signal);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
async function completeExtractAudio(engine, outputs, signal) {
|
|
180
|
+
const jobId = typeof outputs.job_id === "string" ? outputs.job_id : null;
|
|
181
|
+
const audioAssetId = typeof outputs.audio_asset_id === "string"
|
|
182
|
+
? outputs.audio_asset_id
|
|
183
|
+
: null;
|
|
184
|
+
if (jobId)
|
|
185
|
+
await waitForNamedJob(jobId, signal, STEP_TIMEOUT_MS);
|
|
186
|
+
if (!audioAssetId) {
|
|
187
|
+
throw new Error("extract_audio did not return audio_asset_id");
|
|
188
|
+
}
|
|
189
|
+
await waitForFile(engine, audioAssetId, "original.mp3", signal, "extract_audio");
|
|
190
|
+
return { ...outputs, status: "completed" };
|
|
191
|
+
}
|
|
192
|
+
async function completeVideoAudioEdit(engine, assetId, beforeIds, outputs, signal, tool) {
|
|
193
|
+
const after = engine.jobs.queue.list({
|
|
194
|
+
type: "process_video",
|
|
195
|
+
artifactId: assetId,
|
|
196
|
+
});
|
|
197
|
+
const newIds = after
|
|
198
|
+
.filter((job) => !beforeIds.has(job.id))
|
|
199
|
+
.map((job) => job.id);
|
|
200
|
+
if (newIds.length > 0) {
|
|
201
|
+
await waitForInternalJobs(engine, newIds, signal, STEP_TIMEOUT_MS);
|
|
202
|
+
}
|
|
203
|
+
await waitWhile(signal, STEP_TIMEOUT_MS, () => {
|
|
204
|
+
return engine.jobs.queue.list({
|
|
205
|
+
type: "process_video",
|
|
206
|
+
artifactId: assetId,
|
|
207
|
+
states: ["queued", "running", "completing"],
|
|
208
|
+
}).length > 0;
|
|
209
|
+
}, `${tool} preview`);
|
|
210
|
+
await waitForFile(engine, assetId, "proxy.mp4", signal, tool);
|
|
211
|
+
return { ...outputs, status: "completed" };
|
|
212
|
+
}
|
|
213
|
+
async function completeExtractFrame(engine, outputs, signal) {
|
|
214
|
+
const jobId = typeof outputs.job_id === "string" ? outputs.job_id : null;
|
|
215
|
+
const imageAssetId = typeof outputs.image_asset_id === "string"
|
|
216
|
+
? outputs.image_asset_id
|
|
217
|
+
: null;
|
|
218
|
+
if (jobId)
|
|
219
|
+
await waitForNamedJob(jobId, signal, STEP_TIMEOUT_MS);
|
|
220
|
+
if (!imageAssetId) {
|
|
221
|
+
throw new Error("extract_frame did not return image_asset_id");
|
|
222
|
+
}
|
|
223
|
+
const deadline = Date.now() + STEP_TIMEOUT_MS;
|
|
224
|
+
while (true) {
|
|
225
|
+
const manifest = await engine.files.manifest(imageAssetId);
|
|
226
|
+
const ready = manifest.ok && manifest.value.files.some((file) => file.name.startsWith("original."));
|
|
227
|
+
if (ready)
|
|
228
|
+
break;
|
|
229
|
+
if (signal.aborted) {
|
|
230
|
+
throw signal.reason instanceof Error ? signal.reason : new Error("Cancelled");
|
|
231
|
+
}
|
|
232
|
+
if (Date.now() >= deadline) {
|
|
233
|
+
throw new Error("extract_frame finished without an image file");
|
|
234
|
+
}
|
|
235
|
+
await delay(POLL_MS, signal);
|
|
236
|
+
}
|
|
237
|
+
return { ...outputs, status: "completed" };
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* A generation handler returns as soon as the provider job is submitted, and the
|
|
241
|
+
* artifact it names is not on the grid and not yet usable. Place it the way a
|
|
242
|
+
* notebook run would, then wait for the same settlement job the notebook uses.
|
|
243
|
+
*/
|
|
244
|
+
async function completeGeneration(engine, context, outputs, signal, onPlaced) {
|
|
245
|
+
const artifactId = outputs[context.artifactOutput];
|
|
246
|
+
if (typeof artifactId !== "string") {
|
|
247
|
+
throw new Error(`${context.tool} did not return ${context.artifactOutput}`);
|
|
248
|
+
}
|
|
249
|
+
const submission = outputs.job_id;
|
|
250
|
+
const runId = `${context.chainId}:${context.stepId}`;
|
|
251
|
+
if (!context.notebookId) {
|
|
252
|
+
if (typeof submission === "string")
|
|
253
|
+
await waitForNamedJob(submission, signal, STEP_TIMEOUT_MS);
|
|
254
|
+
await waitWhile(signal, GENERATION_STEP_TIMEOUT_MS, async () => {
|
|
255
|
+
const status = await engine.status.get(artifactId);
|
|
256
|
+
if (!status.ok)
|
|
257
|
+
throw new Error(status.error.message);
|
|
258
|
+
if (status.value === "error")
|
|
259
|
+
throw new Error(`${context.tool} failed`);
|
|
260
|
+
return status.value !== "ready";
|
|
261
|
+
}, context.tool);
|
|
262
|
+
return { ...outputs, asset_id: artifactId, status: "completed" };
|
|
263
|
+
}
|
|
264
|
+
const placement = await placeChainGeneration({
|
|
265
|
+
engine,
|
|
266
|
+
notebookId: context.notebookId,
|
|
267
|
+
tool: context.tool,
|
|
268
|
+
placement: context.placement,
|
|
269
|
+
params: context.params,
|
|
270
|
+
artifactId,
|
|
271
|
+
runId,
|
|
272
|
+
pins: context.pins,
|
|
273
|
+
});
|
|
274
|
+
const awaitJobId = `chain-await-${context.chainId}-${context.stepId}-${artifactId}`;
|
|
275
|
+
const awaiting = {
|
|
276
|
+
type: "await_notebook_output",
|
|
277
|
+
bookId: context.bookId,
|
|
278
|
+
notebookId: context.notebookId,
|
|
279
|
+
cellId: placement.producerCellId,
|
|
280
|
+
outputCellId: placement.outputCellId,
|
|
281
|
+
providerArtifactId: artifactId,
|
|
282
|
+
runId,
|
|
283
|
+
cellIds: [placement.producerCellId],
|
|
284
|
+
};
|
|
285
|
+
onPlaced({ ...outputs, cell_id: placement.outputCellId, address: placement.address });
|
|
286
|
+
try {
|
|
287
|
+
if (typeof submission === "string")
|
|
288
|
+
await waitForNamedJob(submission, signal, STEP_TIMEOUT_MS);
|
|
289
|
+
}
|
|
290
|
+
catch (error) {
|
|
291
|
+
await failNotebookProviderOutput(engine, awaiting);
|
|
292
|
+
throw error;
|
|
293
|
+
}
|
|
294
|
+
await queue.enqueue(awaitJobId, { ...awaiting });
|
|
295
|
+
await waitForNamedJob(awaitJobId, signal, GENERATION_STEP_TIMEOUT_MS);
|
|
296
|
+
return {
|
|
297
|
+
...outputs,
|
|
298
|
+
asset_id: artifactId,
|
|
299
|
+
cell_id: placement.outputCellId,
|
|
300
|
+
address: placement.address,
|
|
301
|
+
status: "completed",
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
function childJobIdFromOutputs(outputs) {
|
|
305
|
+
return typeof outputs.job_id === "string" ? [outputs.job_id] : [];
|
|
306
|
+
}
|
|
307
|
+
async function abortOwnedJobs(engine, jobIds) {
|
|
308
|
+
if (jobIds.length === 0)
|
|
309
|
+
return;
|
|
310
|
+
const active = engine.jobs.queue.list({
|
|
311
|
+
states: ["queued", "running", "completing"],
|
|
312
|
+
});
|
|
313
|
+
await Promise.all(active.flatMap((job) => {
|
|
314
|
+
if (!jobIds.includes(job.operationId))
|
|
315
|
+
return [];
|
|
316
|
+
return [engine.jobs.queue.abort(job.id, "Chain cancelled")];
|
|
317
|
+
}));
|
|
318
|
+
}
|
|
319
|
+
function audioEditTargetField(step) {
|
|
320
|
+
if (step.spec.completion === "audio_mix")
|
|
321
|
+
return "target_asset_id";
|
|
322
|
+
if (step.spec.completion === "replace_video_audio" || step.spec.completion === "apply_sfx")
|
|
323
|
+
return "asset_id";
|
|
324
|
+
return null;
|
|
325
|
+
}
|
|
326
|
+
async function withAudioTargetDuration(engine, notebookId, step, params, laterSteps, priorOutputs) {
|
|
327
|
+
if (step.spec.placement !== "generate_audio")
|
|
328
|
+
return params;
|
|
329
|
+
const model = typeof params.model === "string" ? tryResolveModel(params.model) : null;
|
|
330
|
+
if (!model?.entry.operations.some((operation) => operation === "sfx" || operation === "music"))
|
|
331
|
+
return params;
|
|
332
|
+
const durationOption = model.options.find((option) => option.name === "duration_seconds" || option.name === "music_length_ms");
|
|
333
|
+
if (!durationOption)
|
|
334
|
+
return params;
|
|
335
|
+
const options = params.options && typeof params.options === "object" && !Array.isArray(params.options)
|
|
336
|
+
? params.options : {};
|
|
337
|
+
if (options[durationOption.name] !== undefined && options[durationOption.name] !== null)
|
|
338
|
+
return params;
|
|
339
|
+
let seconds = 0;
|
|
340
|
+
for (const consumer of laterSteps) {
|
|
341
|
+
const sourceField = consumer.tool === "audio_mix" ? "layer_asset_id"
|
|
342
|
+
: consumer.tool === "replace_video_audio" ? "audio_asset_id" : null;
|
|
343
|
+
const source = sourceField ? consumer.params[sourceField] : undefined;
|
|
344
|
+
if (!isChainRef(source) || source.$ref.step_id !== step.id || source.$ref.output !== "asset_id")
|
|
345
|
+
continue;
|
|
346
|
+
const targetField = consumer.tool === "audio_mix" ? "target_asset_id" : "asset_id";
|
|
347
|
+
const pins = consumer.pins.filter((pin) => pin.field === targetField);
|
|
348
|
+
const stale = await revalidatePinnedTargets(engine, notebookId, pins);
|
|
349
|
+
if (stale)
|
|
350
|
+
throw new Error(stale);
|
|
351
|
+
const target = applyPins(substituteChainRefs({ [targetField]: consumer.params[targetField] }, priorOutputs), pins)[targetField];
|
|
352
|
+
if (typeof target !== "string")
|
|
353
|
+
throw new Error(`Step ${consumer.id} needs a target before matching audio duration`);
|
|
354
|
+
const metadata = await engine.metadata.artifacts.read(target, "original");
|
|
355
|
+
const duration = metadata.ok ? metadata.value.duration : undefined;
|
|
356
|
+
if (typeof duration !== "number" || !Number.isFinite(duration) || duration <= 0) {
|
|
357
|
+
throw new Error(`Cannot read the target duration for step ${consumer.id}; specify options.${durationOption.name}`);
|
|
358
|
+
}
|
|
359
|
+
seconds = Math.max(seconds, duration);
|
|
360
|
+
}
|
|
361
|
+
if (seconds === 0)
|
|
362
|
+
return params;
|
|
363
|
+
const duration = durationOption.name === "music_length_ms" ? Math.round(seconds * 1_000) : seconds;
|
|
364
|
+
if ((durationOption.minimum !== undefined && duration < durationOption.minimum)
|
|
365
|
+
|| (durationOption.maximum !== undefined && duration > durationOption.maximum)) {
|
|
366
|
+
throw new Error(`The ${seconds.toFixed(2)}-second target is outside ${model.title}'s ${durationOption.name} range (${durationOption.minimum ?? 0}–${durationOption.maximum ?? "unlimited"}). Choose a supported duration or split the audio into sections.`);
|
|
367
|
+
}
|
|
368
|
+
return { ...params, options: { ...options, [durationOption.name]: duration } };
|
|
369
|
+
}
|
|
370
|
+
/**
|
|
371
|
+
* Everything a step needs before it can be dispatched, and nothing that spends
|
|
372
|
+
* money. Pins are revalidated here, so a step approved after a long pause still
|
|
373
|
+
* fails loudly if its target moved in the meantime.
|
|
374
|
+
*/
|
|
375
|
+
async function prepareStep(engine, bookId, notebookId, step, laterSteps, priorOutputs, lookup) {
|
|
376
|
+
if (step.spec.placement && notebookId) {
|
|
377
|
+
const notebook = engine.notebooks.read(notebookId);
|
|
378
|
+
if (!notebook.ok)
|
|
379
|
+
throw new Error(notebook.error.message);
|
|
380
|
+
if (NOTEBOOK_GRID_CAPACITY - notebookOccupiedSlots(notebook.value.cells).length < 2) {
|
|
381
|
+
throw new Error("The notebook needs two empty cells before generating another clip");
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
const stale = await revalidatePinnedTargets(engine, notebookId, step.pins);
|
|
385
|
+
if (stale)
|
|
386
|
+
throw new Error(stale);
|
|
387
|
+
const substituted = substituteChainRefs(step.params, priorOutputs);
|
|
388
|
+
const pinned = applyPins(substituted, step.pins);
|
|
389
|
+
const params = await withAudioTargetDuration(engine, notebookId, step, {
|
|
390
|
+
...pinned,
|
|
391
|
+
book_id: bookId,
|
|
392
|
+
}, laterSteps, priorOutputs);
|
|
393
|
+
if (notebookId && params.notebook_id === undefined) {
|
|
394
|
+
params.notebook_id = notebookId;
|
|
395
|
+
}
|
|
396
|
+
const def = lookup(step.tool);
|
|
397
|
+
if (!def)
|
|
398
|
+
throw new Error(`Tool handler not found: ${step.tool}`);
|
|
399
|
+
const validated = z.object(def.schema).safeParse(params);
|
|
400
|
+
if (!validated.success) {
|
|
401
|
+
throw new Error(`Invalid parameters for ${step.tool}: ${z.prettifyError(validated.error)}`);
|
|
402
|
+
}
|
|
403
|
+
const targetField = audioEditTargetField(step);
|
|
404
|
+
const targetAssetId = targetField ? validated.data[targetField] : undefined;
|
|
405
|
+
const beforeProcess = new Set(typeof targetAssetId === "string"
|
|
406
|
+
? engine.jobs.queue.list({
|
|
407
|
+
type: "process_video",
|
|
408
|
+
artifactId: targetAssetId,
|
|
409
|
+
}).map((job) => job.id)
|
|
410
|
+
: []);
|
|
411
|
+
return { def, params: validated.data, beforeProcess };
|
|
412
|
+
}
|
|
413
|
+
async function dispatchStep(engine, context, step, prepared, signal, onDispatched) {
|
|
414
|
+
if (signal.aborted)
|
|
415
|
+
throw new Error("Cancelled");
|
|
416
|
+
const result = await prepared.def.handler(prepared.params);
|
|
417
|
+
if (result.isError) {
|
|
418
|
+
const text = result.content.find((entry) => entry.type === "text")?.text;
|
|
419
|
+
throw new Error(text ?? `${step.tool} failed`);
|
|
420
|
+
}
|
|
421
|
+
let outputs = parseJsonResult(result);
|
|
422
|
+
const childJobIds = childJobIdFromOutputs(outputs);
|
|
423
|
+
onDispatched(outputs, childJobIds);
|
|
424
|
+
if (signal.aborted)
|
|
425
|
+
throw new Error("Cancelled");
|
|
426
|
+
const targetField = audioEditTargetField(step);
|
|
427
|
+
if (step.spec.completion === "extract_audio") {
|
|
428
|
+
outputs = await completeExtractAudio(engine, outputs, signal);
|
|
429
|
+
}
|
|
430
|
+
else if (targetField) {
|
|
431
|
+
const assetId = outputs[targetField] ?? prepared.params[targetField];
|
|
432
|
+
if (typeof assetId !== "string")
|
|
433
|
+
throw new Error(`${step.tool} did not return ${targetField}`);
|
|
434
|
+
const artifact = engine.artifacts.get(assetId);
|
|
435
|
+
if (!artifact.ok)
|
|
436
|
+
throw new Error(artifact.error.message);
|
|
437
|
+
if (artifact.value.kind === "video") {
|
|
438
|
+
outputs = await completeVideoAudioEdit(engine, assetId, prepared.beforeProcess, outputs, signal, step.tool);
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
else if (step.spec.completion === "extract_frame") {
|
|
442
|
+
outputs = await completeExtractFrame(engine, outputs, signal);
|
|
443
|
+
}
|
|
444
|
+
else if (step.spec.completion === "generation") {
|
|
445
|
+
const placement = step.spec.placement;
|
|
446
|
+
const artifactOutput = step.spec.artifactOutput;
|
|
447
|
+
if (!placement || !artifactOutput) {
|
|
448
|
+
throw new Error(`${step.tool} is missing chain generation metadata`);
|
|
449
|
+
}
|
|
450
|
+
outputs = await completeGeneration(engine, {
|
|
451
|
+
bookId: context.bookId,
|
|
452
|
+
notebookId: context.notebookId,
|
|
453
|
+
chainId: context.chainId,
|
|
454
|
+
stepId: step.id,
|
|
455
|
+
tool: step.tool,
|
|
456
|
+
placement,
|
|
457
|
+
artifactOutput,
|
|
458
|
+
params: prepared.params,
|
|
459
|
+
pins: step.pins,
|
|
460
|
+
}, outputs, signal, placed => onDispatched(placed, childJobIds));
|
|
461
|
+
}
|
|
462
|
+
return { outputs, childJobIds };
|
|
463
|
+
}
|
|
464
|
+
function recordFromPlan(plan, chainId, approvedPlan) {
|
|
465
|
+
const timestamp = nowIso();
|
|
466
|
+
return {
|
|
467
|
+
kind: "chain-run",
|
|
468
|
+
chainId,
|
|
469
|
+
bookId: plan.bookId,
|
|
470
|
+
notebookId: plan.notebookId,
|
|
471
|
+
requestId: plan.requestId,
|
|
472
|
+
fingerprint: plan.fingerprint,
|
|
473
|
+
status: "accepted",
|
|
474
|
+
createdAt: timestamp,
|
|
475
|
+
updatedAt: timestamp,
|
|
476
|
+
error: null,
|
|
477
|
+
autoApply: plan.autoApply,
|
|
478
|
+
effect: plan.effect,
|
|
479
|
+
awaitingStepId: null,
|
|
480
|
+
steps: plan.steps.map((step) => ({
|
|
481
|
+
id: step.id,
|
|
482
|
+
tool: step.tool,
|
|
483
|
+
params: step.params,
|
|
484
|
+
status: "pending",
|
|
485
|
+
outputs: null,
|
|
486
|
+
error: null,
|
|
487
|
+
childJobIds: [],
|
|
488
|
+
pins: step.pins,
|
|
489
|
+
autoApply: step.autoApply,
|
|
490
|
+
effect: step.effect,
|
|
491
|
+
requiresApproval: step.requiresApproval,
|
|
492
|
+
approval: approvedPlan && step.requiresApproval ? {
|
|
493
|
+
approvedAt: timestamp,
|
|
494
|
+
actor: approvedPlan.actor,
|
|
495
|
+
estimateUsd: null,
|
|
496
|
+
estimateUnavailable: true,
|
|
497
|
+
} : null,
|
|
498
|
+
review: null,
|
|
499
|
+
})),
|
|
500
|
+
};
|
|
501
|
+
}
|
|
502
|
+
async function runChain(engine, plan, run, lookup) {
|
|
503
|
+
const controller = inflight.get(run.chainId);
|
|
504
|
+
const signal = controller?.signal ?? new AbortController().signal;
|
|
505
|
+
const prior = new Map();
|
|
506
|
+
run.status = "running";
|
|
507
|
+
run.updatedAt = nowIso();
|
|
508
|
+
saveRun(engine, run);
|
|
509
|
+
try {
|
|
510
|
+
for (const [index, step] of plan.steps.entries()) {
|
|
511
|
+
const record = run.steps[index];
|
|
512
|
+
if (record.status === "completed" && record.outputs) {
|
|
513
|
+
prior.set(record.id, record.outputs);
|
|
514
|
+
continue;
|
|
515
|
+
}
|
|
516
|
+
if (signal.aborted) {
|
|
517
|
+
record.status = "cancelled";
|
|
518
|
+
for (const later of run.steps.slice(index + 1)) {
|
|
519
|
+
if (later.status === "pending")
|
|
520
|
+
later.status = "skipped";
|
|
521
|
+
}
|
|
522
|
+
run.status = "cancelled";
|
|
523
|
+
run.error = "Cancelled";
|
|
524
|
+
run.awaitingStepId = null;
|
|
525
|
+
run.updatedAt = nowIso();
|
|
526
|
+
saveRun(engine, run);
|
|
527
|
+
return;
|
|
528
|
+
}
|
|
529
|
+
try {
|
|
530
|
+
const prepared = await prepareStep(engine, plan.bookId, plan.notebookId, step, plan.steps.slice(index + 1), prior, lookup);
|
|
531
|
+
if (signal.aborted)
|
|
532
|
+
throw new Error("Cancelled");
|
|
533
|
+
if (record.requiresApproval && !record.approval) {
|
|
534
|
+
record.review = await buildChainStepReview({
|
|
535
|
+
engine,
|
|
536
|
+
bookId: plan.bookId,
|
|
537
|
+
stepId: step.id,
|
|
538
|
+
tool: step.tool,
|
|
539
|
+
index,
|
|
540
|
+
total: plan.steps.length,
|
|
541
|
+
pins: step.pins,
|
|
542
|
+
dispatchParams: prepared.params,
|
|
543
|
+
});
|
|
544
|
+
record.status = "awaiting-approval";
|
|
545
|
+
run.status = "awaiting-approval";
|
|
546
|
+
run.awaitingStepId = record.id;
|
|
547
|
+
run.updatedAt = nowIso();
|
|
548
|
+
saveRun(engine, run);
|
|
549
|
+
return;
|
|
550
|
+
}
|
|
551
|
+
record.status = "running";
|
|
552
|
+
record.startedAt = nowIso();
|
|
553
|
+
run.status = "running";
|
|
554
|
+
run.awaitingStepId = null;
|
|
555
|
+
run.updatedAt = nowIso();
|
|
556
|
+
saveRun(engine, run);
|
|
557
|
+
const executed = await dispatchStep(engine, { bookId: plan.bookId, notebookId: plan.notebookId, chainId: run.chainId }, step, prepared, signal, (outputs, jobIds) => {
|
|
558
|
+
record.outputs = outputs;
|
|
559
|
+
record.childJobIds = jobIds;
|
|
560
|
+
if (signal.aborted)
|
|
561
|
+
run.status = "cancelled";
|
|
562
|
+
saveRun(engine, run);
|
|
563
|
+
});
|
|
564
|
+
record.status = "completed";
|
|
565
|
+
record.outputs = executed.outputs;
|
|
566
|
+
record.childJobIds = executed.childJobIds;
|
|
567
|
+
record.error = null;
|
|
568
|
+
prior.set(record.id, executed.outputs);
|
|
569
|
+
run.updatedAt = nowIso();
|
|
570
|
+
saveRun(engine, run);
|
|
571
|
+
}
|
|
572
|
+
catch (cause) {
|
|
573
|
+
const message = cause instanceof Error ? cause.message : String(cause);
|
|
574
|
+
const cancelled = signal.aborted || /cancel/i.test(message);
|
|
575
|
+
record.status = cancelled ? "cancelled" : "failed";
|
|
576
|
+
record.error = message;
|
|
577
|
+
for (const later of run.steps.slice(index + 1)) {
|
|
578
|
+
if (later.status === "pending")
|
|
579
|
+
later.status = "skipped";
|
|
580
|
+
}
|
|
581
|
+
run.status = cancelled ? "cancelled" : "failed";
|
|
582
|
+
run.error = message;
|
|
583
|
+
run.awaitingStepId = null;
|
|
584
|
+
run.updatedAt = nowIso();
|
|
585
|
+
saveRun(engine, run);
|
|
586
|
+
if (record.childJobIds.length > 0) {
|
|
587
|
+
await abortOwnedJobs(engine, record.childJobIds).catch(() => undefined);
|
|
588
|
+
}
|
|
589
|
+
return;
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
run.status = "completed";
|
|
593
|
+
run.error = null;
|
|
594
|
+
run.awaitingStepId = null;
|
|
595
|
+
run.updatedAt = nowIso();
|
|
596
|
+
saveRun(engine, run);
|
|
597
|
+
}
|
|
598
|
+
finally {
|
|
599
|
+
inflight.delete(run.chainId);
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
function acceptChainRun(engine, plan, lookup, approvedPlan) {
|
|
603
|
+
if (plan.requestId) {
|
|
604
|
+
const existing = findChainRunByRequestId(engine, plan.bookId, plan.requestId);
|
|
605
|
+
if (existing) {
|
|
606
|
+
if (existing.fingerprint !== plan.fingerprint) {
|
|
607
|
+
throw new Error("A different chain plan was already submitted with this request_id");
|
|
608
|
+
}
|
|
609
|
+
if (!isTerminal(existing.status) && !inflight.has(existing.chainId)) {
|
|
610
|
+
resumeRun(engine, existing, plan, lookup);
|
|
611
|
+
}
|
|
612
|
+
return existing;
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
const run = recordFromPlan(plan, uuidv7(), approvedPlan);
|
|
616
|
+
saveRun(engine, run);
|
|
617
|
+
resumeRun(engine, run, plan, lookup);
|
|
618
|
+
return run;
|
|
619
|
+
}
|
|
620
|
+
function resumeRun(engine, run, plan, lookup) {
|
|
621
|
+
// A run parked on a review is resumed only by approveChainStep, which clears
|
|
622
|
+
// the marker first.
|
|
623
|
+
if (inflight.has(run.chainId) || isSettled(run.status) || run.awaitingStepId)
|
|
624
|
+
return;
|
|
625
|
+
const running = run.steps.find((step) => step.status === "running");
|
|
626
|
+
if (running) {
|
|
627
|
+
running.status = "interrupted";
|
|
628
|
+
running.error = "Chain execution was interrupted";
|
|
629
|
+
for (const later of run.steps) {
|
|
630
|
+
if (later.status === "pending")
|
|
631
|
+
later.status = "skipped";
|
|
632
|
+
}
|
|
633
|
+
run.status = "interrupted";
|
|
634
|
+
run.error = "Chain execution was interrupted";
|
|
635
|
+
run.updatedAt = nowIso();
|
|
636
|
+
saveRun(engine, run);
|
|
637
|
+
return;
|
|
638
|
+
}
|
|
639
|
+
const controller = new AbortController();
|
|
640
|
+
inflight.set(run.chainId, controller);
|
|
641
|
+
void runChain(engine, plan, run, lookup).catch((error) => {
|
|
642
|
+
if (isClosedDatabaseError(error))
|
|
643
|
+
return;
|
|
644
|
+
const current = getChainRun(engine, run.chainId);
|
|
645
|
+
if (!current || isTerminal(current.status))
|
|
646
|
+
return;
|
|
647
|
+
current.status = "failed";
|
|
648
|
+
current.error = error instanceof Error ? error.message : String(error);
|
|
649
|
+
current.updatedAt = nowIso();
|
|
650
|
+
saveRun(engine, current);
|
|
651
|
+
});
|
|
652
|
+
}
|
|
653
|
+
/**
|
|
654
|
+
* Record a decision to spend on one paused step and let the chain carry on.
|
|
655
|
+
* Approval is persisted before the run is resumed so a crash in between cannot
|
|
656
|
+
* lose the decision, and a second approve is a no-op rather than a second
|
|
657
|
+
* dispatch.
|
|
658
|
+
*/
|
|
659
|
+
export function approveChainStep(engine, chainId, stepId, actor, lookup) {
|
|
660
|
+
const run = getChainRun(engine, chainId);
|
|
661
|
+
if (!run)
|
|
662
|
+
return null;
|
|
663
|
+
const step = run.steps.find((candidate) => candidate.id === stepId);
|
|
664
|
+
if (!step)
|
|
665
|
+
throw new Error(`Step ${stepId} is not part of this chain`);
|
|
666
|
+
if (step.approval)
|
|
667
|
+
return run;
|
|
668
|
+
if (step.status !== "awaiting-approval") {
|
|
669
|
+
throw new Error(`Step ${stepId} is not waiting for approval`);
|
|
670
|
+
}
|
|
671
|
+
step.approval = {
|
|
672
|
+
approvedAt: nowIso(),
|
|
673
|
+
actor,
|
|
674
|
+
estimateUsd: step.review?.estimateUsd ?? null,
|
|
675
|
+
estimateUnavailable: step.review?.estimateUnavailable ?? true,
|
|
676
|
+
};
|
|
677
|
+
step.status = "approved";
|
|
678
|
+
run.status = "running";
|
|
679
|
+
run.awaitingStepId = null;
|
|
680
|
+
run.updatedAt = nowIso();
|
|
681
|
+
saveRun(engine, run);
|
|
682
|
+
resumeRun(engine, run, planFromRecord(run), lookup);
|
|
683
|
+
return run;
|
|
684
|
+
}
|
|
685
|
+
export function retryChainRun(engine, chainId, lookup) {
|
|
686
|
+
const run = getChainRun(engine, chainId);
|
|
687
|
+
if (!run)
|
|
688
|
+
throw new Error("Chain run not found");
|
|
689
|
+
if (inflight.has(chainId))
|
|
690
|
+
return run;
|
|
691
|
+
if (run.status !== "failed" && run.status !== "interrupted") {
|
|
692
|
+
throw new Error("Only a failed or interrupted chain can be retried");
|
|
693
|
+
}
|
|
694
|
+
for (const step of run.steps) {
|
|
695
|
+
if (step.status !== "failed" && step.status !== "interrupted" && step.status !== "skipped")
|
|
696
|
+
continue;
|
|
697
|
+
step.status = step.approval ? "approved" : "pending";
|
|
698
|
+
step.outputs = null;
|
|
699
|
+
step.error = null;
|
|
700
|
+
step.childJobIds = [];
|
|
701
|
+
step.review = null;
|
|
702
|
+
step.startedAt = null;
|
|
703
|
+
}
|
|
704
|
+
run.status = "accepted";
|
|
705
|
+
run.error = null;
|
|
706
|
+
run.awaitingStepId = null;
|
|
707
|
+
run.updatedAt = nowIso();
|
|
708
|
+
saveRun(engine, run);
|
|
709
|
+
resumeRun(engine, run, planFromRecord(run), lookup);
|
|
710
|
+
return run;
|
|
711
|
+
}
|
|
712
|
+
export function cancelChainRun(engine, chainId) {
|
|
713
|
+
const run = getChainRun(engine, chainId);
|
|
714
|
+
if (!run)
|
|
715
|
+
return null;
|
|
716
|
+
if (isTerminal(run.status))
|
|
717
|
+
return run;
|
|
718
|
+
const controller = inflight.get(chainId);
|
|
719
|
+
controller?.abort(new Error("Cancelled"));
|
|
720
|
+
const running = run.steps.find((step) => step.status === "running");
|
|
721
|
+
if (running) {
|
|
722
|
+
running.status = "cancelled";
|
|
723
|
+
running.error = "Cancelled";
|
|
724
|
+
void abortOwnedJobs(engine, running.childJobIds).catch(() => undefined);
|
|
725
|
+
}
|
|
726
|
+
const paused = run.steps.find((step) => step.status === "awaiting-approval");
|
|
727
|
+
if (paused) {
|
|
728
|
+
paused.status = "rejected";
|
|
729
|
+
paused.error = "Rejected";
|
|
730
|
+
}
|
|
731
|
+
for (const step of run.steps) {
|
|
732
|
+
if (step.status === "pending" || step.status === "approved") {
|
|
733
|
+
step.status = "skipped";
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
run.status = "cancelled";
|
|
737
|
+
run.error = paused ? "Rejected" : "Cancelled";
|
|
738
|
+
run.awaitingStepId = null;
|
|
739
|
+
run.updatedAt = nowIso();
|
|
740
|
+
return saveRun(engine, run);
|
|
741
|
+
}
|
|
742
|
+
export async function waitForChainRun(engine, chainId, timeoutMs = 300_000) {
|
|
743
|
+
const deadline = Date.now() + timeoutMs;
|
|
744
|
+
while (Date.now() < deadline) {
|
|
745
|
+
const run = getChainRun(engine, chainId);
|
|
746
|
+
if (run && isSettled(run.status))
|
|
747
|
+
return run;
|
|
748
|
+
await new Promise((resolve) => setTimeout(resolve, POLL_MS));
|
|
749
|
+
}
|
|
750
|
+
throw new Error(`Chain ${chainId} timed out after ${timeoutMs}ms`);
|
|
751
|
+
}
|
|
752
|
+
/**
|
|
753
|
+
* Rebuild an executable plan from what was persisted. Pre-upgrade records carry
|
|
754
|
+
* no policy fields, so they default to "needs approval" rather than risking a
|
|
755
|
+
* silent paid dispatch.
|
|
756
|
+
*/
|
|
757
|
+
function planFromRecord(run) {
|
|
758
|
+
return {
|
|
759
|
+
version: 1,
|
|
760
|
+
bookId: run.bookId,
|
|
761
|
+
notebookId: run.notebookId,
|
|
762
|
+
requestId: run.requestId,
|
|
763
|
+
fingerprint: run.fingerprint,
|
|
764
|
+
autoApply: run.autoApply,
|
|
765
|
+
effect: run.effect,
|
|
766
|
+
steps: run.steps.map((step) => ({
|
|
767
|
+
id: step.id,
|
|
768
|
+
tool: step.tool,
|
|
769
|
+
params: step.params,
|
|
770
|
+
spec: supportedChainTool(step.tool) ?? {
|
|
771
|
+
outputs: {},
|
|
772
|
+
completion: "sync",
|
|
773
|
+
gridFields: [],
|
|
774
|
+
inputTypes: {},
|
|
775
|
+
},
|
|
776
|
+
pins: step.pins,
|
|
777
|
+
autoApply: step.autoApply ?? false,
|
|
778
|
+
effect: step.effect ?? "mutation",
|
|
779
|
+
requiresApproval: step.requiresApproval ?? true,
|
|
780
|
+
})),
|
|
781
|
+
};
|
|
782
|
+
}
|
|
783
|
+
export function resumeOpenChains(engine, lookup) {
|
|
784
|
+
const store = readStore(engine);
|
|
785
|
+
for (const run of Object.values(store.runs)) {
|
|
786
|
+
if (isTerminal(run.status))
|
|
787
|
+
continue;
|
|
788
|
+
// A chain parked on a review stays parked. Resuming it would dispatch a step
|
|
789
|
+
// nobody approved.
|
|
790
|
+
if (run.status === "awaiting-approval")
|
|
791
|
+
continue;
|
|
792
|
+
const running = run.steps.find((step) => step.status === "running");
|
|
793
|
+
if (running) {
|
|
794
|
+
running.status = "interrupted";
|
|
795
|
+
running.error = "Chain execution was interrupted";
|
|
796
|
+
for (const later of run.steps) {
|
|
797
|
+
if (later.status === "pending")
|
|
798
|
+
later.status = "skipped";
|
|
799
|
+
}
|
|
800
|
+
run.status = "interrupted";
|
|
801
|
+
run.error = "Chain execution was interrupted";
|
|
802
|
+
run.updatedAt = nowIso();
|
|
803
|
+
saveRun(engine, run);
|
|
804
|
+
continue;
|
|
805
|
+
}
|
|
806
|
+
resumeRun(engine, run, planFromRecord(run), lookup);
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
export async function preflightAndAccept(input, context, lookup, approvedPlan) {
|
|
810
|
+
const preflight = await preflightChainPlan(input, context);
|
|
811
|
+
if (!preflight.ok) {
|
|
812
|
+
throw new Error(preflight.error);
|
|
813
|
+
}
|
|
814
|
+
return acceptChainRun(context.engine, preflight.plan, lookup, approvedPlan);
|
|
815
|
+
}
|