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.
Files changed (565) hide show
  1. package/CHANGELOG.md +54 -0
  2. package/LICENSE +21 -0
  3. package/README.md +195 -2
  4. package/bin/videobook.js +7 -0
  5. package/dist/acestep/client.js +203 -0
  6. package/dist/acestep/config.js +62 -0
  7. package/dist/acestep/errors.js +8 -0
  8. package/dist/acestep/process.js +175 -0
  9. package/dist/alive-captions/analyze.js +117 -0
  10. package/dist/alive-captions/audio.js +179 -0
  11. package/dist/alive-captions/frames.js +59 -0
  12. package/dist/alive-captions/model.js +37 -0
  13. package/dist/alive-captions/schema.js +49 -0
  14. package/dist/alive-captions/track.js +9 -0
  15. package/dist/api-key.js +33 -0
  16. package/dist/app-state.js +56 -0
  17. package/dist/app.js +257 -0
  18. package/dist/archive-b2.js +332 -0
  19. package/dist/archive-store.js +169 -0
  20. package/dist/artifact-label.js +36 -0
  21. package/dist/artifact-refs.js +49 -0
  22. package/dist/asset-tag-extraction.js +169 -0
  23. package/dist/asset-tags.js +228 -0
  24. package/dist/asset-usage.js +23 -0
  25. package/dist/audio-cue.js +71 -0
  26. package/dist/audio-encoding.js +32 -0
  27. package/dist/audio-sync-request.js +1 -0
  28. package/dist/audio-sync.js +223 -0
  29. package/dist/audio-trim-range.js +31 -0
  30. package/dist/book-context.js +31 -0
  31. package/dist/book-manager.js +374 -0
  32. package/dist/catalog-gc.js +26 -0
  33. package/dist/chain-executor.js +815 -0
  34. package/dist/chain-generation.js +115 -0
  35. package/dist/chain-plan.js +746 -0
  36. package/dist/chain-step-review.js +60 -0
  37. package/dist/character-name.js +25 -0
  38. package/dist/chat-generation-routing.js +73 -0
  39. package/dist/chat-generation-settings.js +78 -0
  40. package/dist/chat-models.js +44 -0
  41. package/dist/chat-prompt-rewrite.js +67 -0
  42. package/dist/chat-router.js +628 -0
  43. package/dist/chat-view-actions.js +49 -0
  44. package/dist/cli/args.js +242 -0
  45. package/dist/cli/config-command.js +103 -0
  46. package/dist/cli/doctor-command.js +289 -0
  47. package/dist/cli/entry.js +2 -0
  48. package/dist/cli/key-report.js +16 -0
  49. package/dist/cli/main.js +104 -0
  50. package/dist/cli/mcp-command.js +88 -0
  51. package/dist/cli/mcp-relay.js +51 -0
  52. package/dist/cli/open-browser.js +14 -0
  53. package/dist/cli/run-command.js +231 -0
  54. package/dist/cli/serve-command.js +62 -0
  55. package/dist/cli/setup-command.js +60 -0
  56. package/dist/cli/stdio-guard.js +8 -0
  57. package/dist/cold-open-model.js +7 -0
  58. package/dist/cold-open.js +79 -0
  59. package/dist/command-reference.js +95 -0
  60. package/dist/content-store.js +74 -0
  61. package/dist/cost.js +8 -0
  62. package/dist/crop-geometry.js +23 -0
  63. package/dist/dancing-workflow.js +447 -0
  64. package/dist/data-home.js +44 -0
  65. package/dist/diagnostics.js +109 -0
  66. package/dist/dialog-quality.js +34 -0
  67. package/dist/downloads-dest.js +56 -0
  68. package/dist/editing-moments.js +194 -0
  69. package/dist/elevenlabs-audio-extension.js +66 -0
  70. package/dist/engine.js +54 -0
  71. package/dist/env-file.js +91 -0
  72. package/dist/env-secrets.js +148 -0
  73. package/dist/ffmpeg-binary.js +12 -0
  74. package/dist/ffmpeg.js +425 -0
  75. package/dist/ffprobe-binary.js +77 -0
  76. package/dist/forget-asset.js +216 -0
  77. package/dist/frame-utils.js +27 -0
  78. package/dist/generation-credit-error.js +11 -0
  79. package/dist/generation-input-mentions.js +117 -0
  80. package/dist/generation-input-policy.js +68 -0
  81. package/dist/generation-reference-mentions.js +97 -0
  82. package/dist/goal-characters.js +49 -0
  83. package/dist/goal-plan-v3.js +273 -0
  84. package/dist/goal-plan.js +220 -0
  85. package/dist/goal-planner-prompt.js +28 -0
  86. package/dist/goal-prompt-budget.js +4 -0
  87. package/dist/goal-reference-images.js +25 -0
  88. package/dist/goal-state.js +261 -0
  89. package/dist/goal-stills.js +153 -0
  90. package/dist/goal-store.js +144 -0
  91. package/dist/goal-styles.js +144 -0
  92. package/dist/goal-v3-runtime.js +157 -0
  93. package/dist/guides/character_schema_guide.md +70 -0
  94. package/dist/guides/gpt_image_2_5_prompting_guide.md +21 -0
  95. package/dist/guides/grok_imagine_prompting_guide.md +72 -0
  96. package/dist/guides/image_reproduction_guide.md +84 -0
  97. package/dist/guides/kling_3_0_prompting_guide.md +136 -0
  98. package/dist/guides/nano_banana_pro_prompting_guide.md +28 -0
  99. package/dist/guides/seedance_2_prompting_guide.md +172 -0
  100. package/dist/http-access.js +49 -0
  101. package/dist/http-byte-range.js +31 -0
  102. package/dist/hydration.js +56 -0
  103. package/dist/image-compress.js +77 -0
  104. package/dist/image-edit-default.js +1 -0
  105. package/dist/image-edit-models.js +41 -0
  106. package/dist/image-edit-settings.js +11 -0
  107. package/dist/image-region-edit.js +32 -0
  108. package/dist/image-subject-map.js +523 -0
  109. package/dist/image.js +46 -0
  110. package/dist/index.js +12 -0
  111. package/dist/job-deadline.js +60 -0
  112. package/dist/jobs/_alibaba-upload.js +30 -0
  113. package/dist/jobs/_audio-chunks.js +31 -0
  114. package/dist/jobs/_download-into-asset.js +15 -0
  115. package/dist/jobs/_fal-shared.js +22 -0
  116. package/dist/jobs/_fal-upload.js +35 -0
  117. package/dist/jobs/_image-edit-adapter.js +213 -0
  118. package/dist/jobs/_kie-poll.js +119 -0
  119. package/dist/jobs/_kie-upload.js +32 -0
  120. package/dist/jobs/_lease.js +163 -0
  121. package/dist/jobs/_persist-output.js +19 -0
  122. package/dist/jobs/_storage-retry.js +21 -0
  123. package/dist/jobs/_transcribe.js +245 -0
  124. package/dist/jobs/_translate-transcript.js +270 -0
  125. package/dist/jobs/analyze-alive-captions.js +10 -0
  126. package/dist/jobs/analyze-editing-moments.js +104 -0
  127. package/dist/jobs/analyze-jokes.js +143 -0
  128. package/dist/jobs/analyze-keyword-placement.js +155 -0
  129. package/dist/jobs/analyze-music.js +14 -0
  130. package/dist/jobs/analyze-video.js +282 -0
  131. package/dist/jobs/apply-cuts.js +83 -0
  132. package/dist/jobs/apply-sfx.js +107 -0
  133. package/dist/jobs/archive-clip.js +458 -0
  134. package/dist/jobs/assemble-goal.js +98 -0
  135. package/dist/jobs/audio-boost.js +71 -0
  136. package/dist/jobs/audio-mix.js +161 -0
  137. package/dist/jobs/backfill-asset-tags.js +144 -0
  138. package/dist/jobs/burn-watermark.js +316 -0
  139. package/dist/jobs/change-video-speed.js +176 -0
  140. package/dist/jobs/clean-video-audio.js +207 -0
  141. package/dist/jobs/cold-open.js +166 -0
  142. package/dist/jobs/combine-goal.js +108 -0
  143. package/dist/jobs/complete-provider-task.js +185 -0
  144. package/dist/jobs/copy-library-asset.js +33 -0
  145. package/dist/jobs/cost-estimate-assessment.js +113 -0
  146. package/dist/jobs/crop-image.js +47 -0
  147. package/dist/jobs/crop-video.js +71 -0
  148. package/dist/jobs/describe-image.js +181 -0
  149. package/dist/jobs/detect-scenes.js +33 -0
  150. package/dist/jobs/doctor-fix.js +100 -0
  151. package/dist/jobs/download-video.js +427 -0
  152. package/dist/jobs/enqueue-search-index.js +24 -0
  153. package/dist/jobs/even-split.js +122 -0
  154. package/dist/jobs/export-sequence.js +365 -0
  155. package/dist/jobs/extend-audio.js +229 -0
  156. package/dist/jobs/extract-audio.js +72 -0
  157. package/dist/jobs/extract-frame.js +58 -0
  158. package/dist/jobs/extract-notebook-audio.js +82 -0
  159. package/dist/jobs/extract-notebook-frame.js +93 -0
  160. package/dist/jobs/extract-video-clip.js +8 -0
  161. package/dist/jobs/extract-video-poster.js +55 -0
  162. package/dist/jobs/extract-waveform.js +25 -0
  163. package/dist/jobs/finish-goal.js +255 -0
  164. package/dist/jobs/forget-asset.js +11 -0
  165. package/dist/jobs/generate-acestep-cover.js +219 -0
  166. package/dist/jobs/generate-media.js +70 -0
  167. package/dist/jobs/image-subjects.js +1059 -0
  168. package/dist/jobs/index.js +18 -0
  169. package/dist/jobs/inject-first-frame.js +175 -0
  170. package/dist/jobs/isolate-vocals.js +69 -0
  171. package/dist/jobs/kie-paygate.js +177 -0
  172. package/dist/jobs/label-music-sections.js +5 -0
  173. package/dist/jobs/osg-placement.js +135 -0
  174. package/dist/jobs/pad-video.js +72 -0
  175. package/dist/jobs/plan-goal.js +97 -0
  176. package/dist/jobs/post-record.js +21 -0
  177. package/dist/jobs/post-to-x.js +21 -0
  178. package/dist/jobs/prepare-search-reference.js +190 -0
  179. package/dist/jobs/prepare-temporal-search.js +16 -0
  180. package/dist/jobs/process-audio.js +67 -0
  181. package/dist/jobs/process-image.js +58 -0
  182. package/dist/jobs/process-upload.js +166 -0
  183. package/dist/jobs/process-video.js +438 -0
  184. package/dist/jobs/register-all.js +161 -0
  185. package/dist/jobs/relayout-notebook.js +78 -0
  186. package/dist/jobs/remove-sound.js +299 -0
  187. package/dist/jobs/render-project.js +380 -0
  188. package/dist/jobs/render-qr-outro.js +138 -0
  189. package/dist/jobs/render-timeline.js +283 -0
  190. package/dist/jobs/repair-search-indexes.js +42 -0
  191. package/dist/jobs/replace-video-audio.js +64 -0
  192. package/dist/jobs/restore-archive-clip.js +53 -0
  193. package/dist/jobs/reverse-video.js +53 -0
  194. package/dist/jobs/rewrite-script.js +173 -0
  195. package/dist/jobs/run-notebook.js +3335 -0
  196. package/dist/jobs/semantic-index.js +797 -0
  197. package/dist/jobs/smooth-scene-concat.js +257 -0
  198. package/dist/jobs/splice-video.js +280 -0
  199. package/dist/jobs/split-video.js +127 -0
  200. package/dist/jobs/suggest-cuts.js +149 -0
  201. package/dist/jobs/suggest-sfx.js +135 -0
  202. package/dist/jobs/sync-video-to-audio.js +329 -0
  203. package/dist/jobs/tag-asset.js +266 -0
  204. package/dist/jobs/task-failure.js +16 -0
  205. package/dist/jobs/timewarp-video.js +152 -0
  206. package/dist/jobs/transcribe-video-audio.js +117 -0
  207. package/dist/jobs/transcribe.js +35 -0
  208. package/dist/jobs/translate-transcript.js +55 -0
  209. package/dist/jobs/trim-audio.js +133 -0
  210. package/dist/jobs/trim-video.js +291 -0
  211. package/dist/jobs/xai-paygate.js +26 -0
  212. package/dist/keyword-placement-cache.js +74 -0
  213. package/dist/lazy-render.js +142 -0
  214. package/dist/library-browse.js +78 -0
  215. package/dist/lightning-defaults.js +206 -0
  216. package/dist/load-env.js +12 -0
  217. package/dist/local-search-models.js +206 -0
  218. package/dist/local-upload.js +318 -0
  219. package/dist/logger.js +69 -0
  220. package/dist/media/adapters/alibaba.js +20 -0
  221. package/dist/media/adapters/elevenlabs.js +58 -0
  222. package/dist/media/adapters/fal.js +45 -0
  223. package/dist/media/adapters/index.js +26 -0
  224. package/dist/media/adapters/kie.js +120 -0
  225. package/dist/media/adapters/pending.js +21 -0
  226. package/dist/media/adapters/types.js +5 -0
  227. package/dist/media/adapters/xai.js +18 -0
  228. package/dist/media/catalog.js +236 -0
  229. package/dist/media/complete.js +191 -0
  230. package/dist/media/cost.js +45 -0
  231. package/dist/media/descriptor.js +157 -0
  232. package/dist/media/endpoints.js +11 -0
  233. package/dist/media/frame-options.js +18 -0
  234. package/dist/media/insert-video-timing.js +17 -0
  235. package/dist/media/json-schema.js +75 -0
  236. package/dist/media/legacy-task-types.js +129 -0
  237. package/dist/media/listing.js +24 -0
  238. package/dist/media/model-id.js +14 -0
  239. package/dist/media/preview.js +86 -0
  240. package/dist/media/prompt-dialects.js +65 -0
  241. package/dist/media/ref-fields.js +106 -0
  242. package/dist/media/refs.js +131 -0
  243. package/dist/media/request.js +188 -0
  244. package/dist/media/types.js +6 -0
  245. package/dist/media/wire.js +161 -0
  246. package/dist/media-library.js +535 -0
  247. package/dist/media-output-path.js +33 -0
  248. package/dist/multyculty-workflow.js +757 -0
  249. package/dist/notebook-action-catalog.js +608 -0
  250. package/dist/notebook-cell-outputs.js +726 -0
  251. package/dist/notebook-credit-retry.js +45 -0
  252. package/dist/notebook-fixtures.js +8 -0
  253. package/dist/notebook-generation-contract.js +77 -0
  254. package/dist/notebook-generation-controls.js +145 -0
  255. package/dist/notebook-goal-layout.js +40 -0
  256. package/dist/notebook-graph.js +37 -0
  257. package/dist/notebook-image-edit.js +84 -0
  258. package/dist/notebook-initial-generation-prompt.js +222 -0
  259. package/dist/notebook-live-analysis.js +941 -0
  260. package/dist/notebook-media-ops.js +579 -0
  261. package/dist/notebook-mutations.js +20 -0
  262. package/dist/notebook-placement.js +41 -0
  263. package/dist/notebook-provider-failure.js +87 -0
  264. package/dist/notebook-published-post.js +26 -0
  265. package/dist/notebook-reference-resolver.js +227 -0
  266. package/dist/notebook-relayout.js +270 -0
  267. package/dist/notebook-run-plan.js +519 -0
  268. package/dist/notebook-shots.js +162 -0
  269. package/dist/notebook-workflow.js +856 -0
  270. package/dist/one-password.js +200 -0
  271. package/dist/openai-intelligence.js +99 -0
  272. package/dist/original-media-file.js +12 -0
  273. package/dist/osg-assets.js +36 -0
  274. package/dist/osg-placement-cache.js +64 -0
  275. package/dist/osg-settings.js +27 -0
  276. package/dist/paid-tools.js +22 -0
  277. package/dist/platform-guidance.js +26 -0
  278. package/dist/posts/dropbox-auth.js +91 -0
  279. package/dist/posts/dropbox-upload.js +132 -0
  280. package/dist/posts/icloud-drive.js +134 -0
  281. package/dist/posts/icloud-status.js +64 -0
  282. package/dist/posts/post-contract.js +2 -0
  283. package/dist/posts/post-copy-format.js +43 -0
  284. package/dist/posts/post-copy.js +193 -0
  285. package/dist/posts/post-folder.js +18 -0
  286. package/dist/posts/post-metrics.js +119 -0
  287. package/dist/posts/post-store.js +340 -0
  288. package/dist/posts/x-post-target.js +43 -0
  289. package/dist/preview-payload.js +90 -0
  290. package/dist/prompt-surfaces.js +22 -0
  291. package/dist/provider-prompt-transform.js +77 -0
  292. package/dist/provider-status-link.js +21 -0
  293. package/dist/provider-task-record.js +78 -0
  294. package/dist/provider-task-status.js +47 -0
  295. package/dist/provider-usage.js +294 -0
  296. package/dist/queue.js +810 -0
  297. package/dist/reference-video-duration.js +17 -0
  298. package/dist/remotion/BlurFillVideo.js +51 -0
  299. package/dist/remotion/KeywordGraphicsOverlay.js +68 -0
  300. package/dist/remotion/OsgOverlay.js +30 -0
  301. package/dist/remotion/QrOutroComposition.js +47 -0
  302. package/dist/remotion/RenderComposition.js +11 -0
  303. package/dist/remotion/browser.js +59 -0
  304. package/dist/remotion/constants.js +35 -0
  305. package/dist/remotion/index.js +66 -0
  306. package/dist/remotion/keyword-graphics.js +151 -0
  307. package/dist/remotion/keyword-placement.js +182 -0
  308. package/dist/remotion/osg-spec.js +198 -0
  309. package/dist/remotion/public/qr-outro-impact.wav +0 -0
  310. package/dist/remotion/qr-outro-audio.js +1 -0
  311. package/dist/remotion/qr-outro-spec.js +211 -0
  312. package/dist/remotion/shared-bundle.js +37 -0
  313. package/dist/remotion/subtitles/AliveCaptions.js +83 -0
  314. package/dist/remotion/subtitles/Highlight.js +58 -0
  315. package/dist/remotion/subtitles/Impact.js +67 -0
  316. package/dist/remotion/subtitles/SubtitleOverlay.js +9 -0
  317. package/dist/remotion/subtitles/alive-layout.js +321 -0
  318. package/dist/remotion/subtitles/alive-types.js +17 -0
  319. package/dist/remotion/subtitles/chunker.js +68 -0
  320. package/dist/remotion/subtitles/segments.js +42 -0
  321. package/dist/remotion/subtitles/styles.js +4 -0
  322. package/dist/remotion/subtitles/types.js +1 -0
  323. package/dist/remotion/util.js +18 -0
  324. package/dist/remotion-cancellation.js +15 -0
  325. package/dist/rename-helpers.js +204 -0
  326. package/dist/rhythm/analyze.js +128 -0
  327. package/dist/rhythm/contract.js +14 -0
  328. package/dist/rhythm/draft-model.js +19 -0
  329. package/dist/rhythm/energy.js +133 -0
  330. package/dist/rhythm/fft.js +44 -0
  331. package/dist/rhythm/model.js +71 -0
  332. package/dist/rhythm/music-analysis.js +124 -0
  333. package/dist/rhythm/music-draft.js +261 -0
  334. package/dist/rhythm/onset.js +101 -0
  335. package/dist/rhythm/run-worker.js +51 -0
  336. package/dist/rhythm/score.js +47 -0
  337. package/dist/rhythm/section-labels.js +168 -0
  338. package/dist/rhythm/sequence-rhythm.js +67 -0
  339. package/dist/rhythm/synthesize.js +103 -0
  340. package/dist/rhythm/timing.js +98 -0
  341. package/dist/rhythm/track.js +320 -0
  342. package/dist/rhythm/visual-fit.js +4 -0
  343. package/dist/rhythm/worker.js +25 -0
  344. package/dist/scene-detect.js +200 -0
  345. package/dist/scene-transition.js +12 -0
  346. package/dist/scheduled-posts.js +200 -0
  347. package/dist/search-index-coverage.js +13 -0
  348. package/dist/search-index-media.js +71 -0
  349. package/dist/search-reference-store.js +60 -0
  350. package/dist/search-scoring.js +17 -0
  351. package/dist/serve.js +125 -0
  352. package/dist/server-lock.js +143 -0
  353. package/dist/server.js +42 -0
  354. package/dist/srt.js +16 -0
  355. package/dist/tag-backfill-state.js +121 -0
  356. package/dist/tag-settings.js +30 -0
  357. package/dist/tag-vocabulary.js +237 -0
  358. package/dist/test-queue-cleanup.js +1 -0
  359. package/dist/time-parse.js +50 -0
  360. package/dist/timeline-helpers.js +554 -0
  361. package/dist/timewarp.js +87 -0
  362. package/dist/tools/_image-inputs.js +9 -0
  363. package/dist/tools/_media-refs.js +84 -0
  364. package/dist/tools/_shared.js +59 -0
  365. package/dist/tools/_suggest-shared.js +279 -0
  366. package/dist/tools/add-timeline-audio.js +73 -0
  367. package/dist/tools/alive-captions.js +157 -0
  368. package/dist/tools/analyze-jokes.js +56 -0
  369. package/dist/tools/analyze-video.js +66 -0
  370. package/dist/tools/animate-keywords.js +217 -0
  371. package/dist/tools/api-status.js +42 -0
  372. package/dist/tools/application-commands.js +34 -0
  373. package/dist/tools/apply-cuts.js +112 -0
  374. package/dist/tools/apply-sfx.js +152 -0
  375. package/dist/tools/apply-watermark.js +670 -0
  376. package/dist/tools/archive-clip.js +74 -0
  377. package/dist/tools/asset-history.js +61 -0
  378. package/dist/tools/asset-manifest.js +24 -0
  379. package/dist/tools/asset-tags.js +151 -0
  380. package/dist/tools/assign-slot.js +97 -0
  381. package/dist/tools/audio-boost.js +107 -0
  382. package/dist/tools/audio-mix.js +121 -0
  383. package/dist/tools/book-usage.js +16 -0
  384. package/dist/tools/chain.js +136 -0
  385. package/dist/tools/change-video-speed.js +105 -0
  386. package/dist/tools/chat-history.js +5 -0
  387. package/dist/tools/chat.js +848 -0
  388. package/dist/tools/clean-video-audio.js +97 -0
  389. package/dist/tools/clear-failed-tasks.js +66 -0
  390. package/dist/tools/cold-open.js +114 -0
  391. package/dist/tools/command-plan.js +1680 -0
  392. package/dist/tools/commands.js +1666 -0
  393. package/dist/tools/concat-images.js +236 -0
  394. package/dist/tools/connect-service.js +55 -0
  395. package/dist/tools/copy-file-to-clipboard.js +58 -0
  396. package/dist/tools/create-character.js +114 -0
  397. package/dist/tools/create-qart.js +253 -0
  398. package/dist/tools/create-qr-outro.js +149 -0
  399. package/dist/tools/create-qrbtf.js +215 -0
  400. package/dist/tools/create-watermark.js +97 -0
  401. package/dist/tools/crop-asset.js +18 -0
  402. package/dist/tools/crop-image.js +120 -0
  403. package/dist/tools/crop-video.js +97 -0
  404. package/dist/tools/delete-asset.js +104 -0
  405. package/dist/tools/describe-image.js +69 -0
  406. package/dist/tools/detect-scenes.js +44 -0
  407. package/dist/tools/doctor.js +126 -0
  408. package/dist/tools/download-video.js +114 -0
  409. package/dist/tools/duplicate-asset.js +184 -0
  410. package/dist/tools/edit-timeline.js +59 -0
  411. package/dist/tools/editing-moments.js +105 -0
  412. package/dist/tools/estimate-cost.js +171 -0
  413. package/dist/tools/even-split.js +295 -0
  414. package/dist/tools/export-asset-file.js +22 -0
  415. package/dist/tools/export-asset.js +140 -0
  416. package/dist/tools/export-sequence.js +250 -0
  417. package/dist/tools/export-to-downloads.js +54 -0
  418. package/dist/tools/export-transcript-srt.js +79 -0
  419. package/dist/tools/extend-audio.js +100 -0
  420. package/dist/tools/extract-audio.js +174 -0
  421. package/dist/tools/extract-frame.js +108 -0
  422. package/dist/tools/extract-notebook-audio.js +173 -0
  423. package/dist/tools/extract-notebook-frame.js +160 -0
  424. package/dist/tools/extract-video-clip.js +101 -0
  425. package/dist/tools/generate-media.js +157 -0
  426. package/dist/tools/generation-payload.js +48 -0
  427. package/dist/tools/get-timeline.js +12 -0
  428. package/dist/tools/get-watermark-config.js +38 -0
  429. package/dist/tools/goal.js +644 -0
  430. package/dist/tools/image-subjects.js +711 -0
  431. package/dist/tools/index.js +323 -0
  432. package/dist/tools/inject-first-frame.js +154 -0
  433. package/dist/tools/invert-image.js +70 -0
  434. package/dist/tools/isolate-vocals.js +66 -0
  435. package/dist/tools/list-assets.js +98 -0
  436. package/dist/tools/list-cell-generations.js +225 -0
  437. package/dist/tools/list-elevenlabs-voices.js +163 -0
  438. package/dist/tools/log-wrapper.js +29 -0
  439. package/dist/tools/make-cover-song.js +95 -0
  440. package/dist/tools/mark-asset-seen.js +23 -0
  441. package/dist/tools/media-library.js +245 -0
  442. package/dist/tools/move-timeline-audio.js +35 -0
  443. package/dist/tools/music-analysis.js +60 -0
  444. package/dist/tools/music-draft.js +29 -0
  445. package/dist/tools/pad-video.js +87 -0
  446. package/dist/tools/post-record.js +83 -0
  447. package/dist/tools/post.js +168 -0
  448. package/dist/tools/prompt-history.js +164 -0
  449. package/dist/tools/provider-key-status.js +248 -0
  450. package/dist/tools/qrbtf-render.js +849 -0
  451. package/dist/tools/read-asset-metadata.js +26 -0
  452. package/dist/tools/read-file-at-commit.js +29 -0
  453. package/dist/tools/read-transcript.js +41 -0
  454. package/dist/tools/reconcile-project-work.js +1452 -0
  455. package/dist/tools/recover-asset.js +28 -0
  456. package/dist/tools/remove-sound.js +72 -0
  457. package/dist/tools/remove-timeline-audio.js +34 -0
  458. package/dist/tools/remove-timeline-slot.js +40 -0
  459. package/dist/tools/rename-all.js +434 -0
  460. package/dist/tools/rename-asset.js +34 -0
  461. package/dist/tools/render-settings.js +47 -0
  462. package/dist/tools/render-timeline.js +120 -0
  463. package/dist/tools/render.js +384 -0
  464. package/dist/tools/reorder-character-images.js +45 -0
  465. package/dist/tools/replace-video-audio.js +133 -0
  466. package/dist/tools/restore-archive-clips.js +209 -0
  467. package/dist/tools/restore-asset-version.js +37 -0
  468. package/dist/tools/retry-generation.js +54 -0
  469. package/dist/tools/reverse-video.js +74 -0
  470. package/dist/tools/rewrite-prompt.js +388 -0
  471. package/dist/tools/rewrite-script.js +67 -0
  472. package/dist/tools/schema-from-apicity.js +66 -0
  473. package/dist/tools/search-archive.js +150 -0
  474. package/dist/tools/semantic-search.js +1214 -0
  475. package/dist/tools/sequence-rhythm.js +43 -0
  476. package/dist/tools/set-character-metadata.js +84 -0
  477. package/dist/tools/set-env-secret.js +40 -0
  478. package/dist/tools/set-osg.js +143 -0
  479. package/dist/tools/show-folder.js +37 -0
  480. package/dist/tools/smooth-scene-concat.js +122 -0
  481. package/dist/tools/splice-video.js +149 -0
  482. package/dist/tools/split-video.js +202 -0
  483. package/dist/tools/suggest-cuts.js +45 -0
  484. package/dist/tools/suggest-sfx.js +43 -0
  485. package/dist/tools/sync-video-to-audio.js +115 -0
  486. package/dist/tools/tag-backfill.js +177 -0
  487. package/dist/tools/timewarp-video.js +85 -0
  488. package/dist/tools/tool-def.js +1 -0
  489. package/dist/tools/tool-json-schema.js +116 -0
  490. package/dist/tools/transcribe.js +90 -0
  491. package/dist/tools/translate-subtitles.js +59 -0
  492. package/dist/tools/trim-audio.js +139 -0
  493. package/dist/tools/trim-video.js +168 -0
  494. package/dist/tools/update-script.js +25 -0
  495. package/dist/tools/update-timeline-audio.js +40 -0
  496. package/dist/tools/upload-file.js +41 -0
  497. package/dist/tools/verify-x-mentions.js +141 -0
  498. package/dist/tools/video-editing-command-contract.js +291 -0
  499. package/dist/tools/video-source-reference.js +30 -0
  500. package/dist/tools/video-time-input.js +18 -0
  501. package/dist/tools/videobook-domain.js +2021 -0
  502. package/dist/tools/wait-for-asset-ready.js +143 -0
  503. package/dist/tools/workspace-actions.js +54 -0
  504. package/dist/transcript-projection.js +202 -0
  505. package/dist/transcript-srt.js +33 -0
  506. package/dist/upload.js +76 -0
  507. package/dist/usage-providers.js +25 -0
  508. package/dist/video-analysis-projection.js +46 -0
  509. package/dist/video-poster.js +33 -0
  510. package/dist/video-proxy.js +99 -0
  511. package/dist/vision-regions.js +111 -0
  512. package/dist/voice-actions.js +90 -0
  513. package/dist/voice-audio.js +55 -0
  514. package/dist/voice-config.js +122 -0
  515. package/dist/voice-continuation.js +54 -0
  516. package/dist/voice-contract.js +1 -0
  517. package/dist/voice-http.js +145 -0
  518. package/dist/voice-reply-progress.js +55 -0
  519. package/dist/voice-session.js +992 -0
  520. package/dist/watermark-badge.js +259 -0
  521. package/dist/watermark-config.js +111 -0
  522. package/dist/watermark-handle-position.js +55 -0
  523. package/dist/watermark-platform.js +15 -0
  524. package/dist/watermark-timing.js +16 -0
  525. package/dist/workspace-action-catalog.js +315 -0
  526. package/dist/workspace-action-contract.js +21 -0
  527. package/dist/workspace-action-groups.js +98 -0
  528. package/dist/workspace-action-surfaces.js +7 -0
  529. package/dist/x/account.js +88 -0
  530. package/dist/x/authenticated-handle.js +30 -0
  531. package/dist/x/oauth-flow.js +191 -0
  532. package/dist/x/post-contract.js +1 -0
  533. package/dist/x/post-metadata.js +44 -0
  534. package/dist/x/post-to-x.js +456 -0
  535. package/dist/x/published-post-cell.js +90 -0
  536. package/dist/x/x-tokens.js +69 -0
  537. package/dist/ytdlp-binary.js +65 -0
  538. package/dist/zod-slots.js +334 -0
  539. package/package.json +84 -4
  540. package/ui/assets/alive-captions-BWb5nLKj.css +1 -0
  541. package/ui/assets/alive-captions-DPEr1nKM.js +1 -0
  542. package/ui/assets/confirm-bodies-D8ouHXKy.js +1 -0
  543. package/ui/assets/confirm-bodies-DrMhaXC6.css +1 -0
  544. package/ui/assets/fullscreen-B7nw4YbE.js +1 -0
  545. package/ui/assets/goal-ui-DK-xTCK0.js +2 -0
  546. package/ui/assets/goal-ui-DXdUM2KF.css +1 -0
  547. package/ui/assets/index-DzPAnCUj.css +1 -0
  548. package/ui/assets/index-kS1905s5.js +37 -0
  549. package/ui/assets/inspector-MlzQbTVo.js +1 -0
  550. package/ui/assets/jobs-ui-CoinOWx0.js +1 -0
  551. package/ui/assets/music-video-Cbe80kmQ.js +1 -0
  552. package/ui/assets/music-video-DPaiKKEq.css +1 -0
  553. package/ui/assets/ui-Oirr9VOf.css +1 -0
  554. package/ui/assets/ui-xo_Y1yQg.js +2 -0
  555. package/ui/assets/vendor-D2rfiZ5j.css +1 -0
  556. package/ui/assets/vendor-DfTCWgKm.js +19 -0
  557. package/ui/assets/vendor-flow-BXbeWxW7.js +7 -0
  558. package/ui/assets/vendor-flow-BnuhLJ6X.css +1 -0
  559. package/ui/assets/vendor-icons-CGqhCIXV.js +1 -0
  560. package/ui/assets/vendor-mcp-BTlEJcHV.js +67 -0
  561. package/ui/assets/vendor-react-Dj7D1fD4.js +9 -0
  562. package/ui/assets/vendor-remotion-CH1HXrlc.js +95 -0
  563. package/ui/assets/videobook-Qlp9HQFu.js +20 -0
  564. package/ui/favicon.svg +8 -0
  565. package/ui/index.html +126 -0
@@ -0,0 +1,2021 @@
1
+ import { migrateLegacyPosts, notebookPosts } from "../posts/post-store.js";
2
+ import { notebookCellOccupiedSlots, notebookOccupiedSlots } from "videobook-engine/notebook-grid";
3
+ import { nextHorizontalSlotFrom, NOTEBOOK_CELL_TYPES, NOTEBOOK_GRID_ROW_COUNT, NOTEBOOK_GRID_COLUMN_COUNT, notebookGridTag, } from "videobook-engine";
4
+ import { slotRight } from "../notebook-placement.js";
5
+ import { z } from "zod";
6
+ import { v7 as uuidv7 } from "uuid";
7
+ import { ensureGenerateAudioOutput, normalizeLegacyExtractCells, } from "../notebook-cell-outputs.js";
8
+ import { mutateNotebook, withNotebookMutation } from "../notebook-mutations.js";
9
+ import { jobIdForKey, queue, stopBookQueue } from "../queue.js";
10
+ import { claimNotebookGenerationPlan, failQueuedNotebookCells, generationPlanClaimOutputCellId, notebookCellDefinitionFingerprint, notebookCellIsPaid, notebookCellIsRunnable, notebookCellMediaType, markNotebookCellQueued, prepareNotebookGeneration, } from "../jobs/run-notebook.js";
11
+ import { buildGenerationInsertPlan, continueQueuedInsert, } from "./command-plan.js";
12
+ import { assessToolCallEstimate, logUnavailableCostEstimate, } from "../jobs/cost-estimate-assessment.js";
13
+ import { estimateCostDef } from "./estimate-cost.js";
14
+ import { pairForCell } from "./list-cell-generations.js";
15
+ import { advertisedAs } from "./tool-json-schema.js";
16
+ import { listNotebookActions, markDescendantsStale, normalizeCellActionFields, normalizeLegacyAnalyzeDocument, selectedCellDetails, validateCellConfiguration, } from "../notebook-action-catalog.js";
17
+ import { hydrateNotebookProviderFailures } from "../notebook-provider-failure.js";
18
+ import { buildSevenStageScaffold } from "../notebook-workflow.js";
19
+ import { previewGeneration } from "../media/preview.js";
20
+ import { generationInputMentions, remapGenerationInputPrompt } from "../generation-input-mentions.js";
21
+ import { applyPreviewPlaceholderUrls } from "../preview-payload.js";
22
+ import { jobProviderTaskStatus } from "../provider-task-status.js";
23
+ import { enqueueNotebookRunPlan, notebookRunPlanRecords, planNotebookRun, planSelectedNotebookSubgraph, } from "../notebook-run-plan.js";
24
+ import { reconcileInitialGenerationPrompt, refreshInitialGenerationPrompts, seedInitialGenerationPrompt, } from "../notebook-initial-generation-prompt.js";
25
+ import { notebookConnectionEdges, notebookConnectionWouldCreateCycle } from "../notebook-graph.js";
26
+ const bookSchema = { book_id: z.string().uuid() };
27
+ const cellTypeSchema = z.enum(NOTEBOOK_CELL_TYPES);
28
+ const GOAL_TILE_CREATE_ERROR = "Goal tiles are created by goal mode. Start one with /goal and a brief.";
29
+ function moveGoalTile(cell, patch) {
30
+ const changed = Object.entries(patch).filter(([key, value]) => key !== "slot" && value !== undefined);
31
+ if (changed.length > 0)
32
+ throw new Error("Edit a goal's plan from its tile in goal mode");
33
+ return patch.slot === undefined ? cell : { ...cell, slot: patch.slot };
34
+ }
35
+ const notebookGridSlotSchema = z.object({
36
+ row: z.number().int().min(0).max(NOTEBOOK_GRID_ROW_COUNT - 1),
37
+ column: z.number().int().min(0).max(NOTEBOOK_GRID_COLUMN_COUNT - 1),
38
+ });
39
+ function notebookConnectionError(notebook, sourceId, targetId, targetInput) {
40
+ const source = notebook.cells.find((cell) => cell.id === sourceId);
41
+ const target = notebook.cells.find((cell) => cell.id === targetId);
42
+ if (!source)
43
+ return `Source cell not found: ${sourceId}`;
44
+ if (!target)
45
+ return `Target cell not found: ${targetId}`;
46
+ if (source.type === "post" || target.type === "post")
47
+ return "Post connections are frozen; use create_post or update_post to choose a source";
48
+ if (notebookConnectionWouldCreateCycle(notebook.edges, sourceId, targetId)) {
49
+ return `Connecting ${notebookGridTag(source.slot)} to ${notebookGridTag(target.slot)} would create a cycle`;
50
+ }
51
+ if (target.type === "image"
52
+ && targetInput !== "media"
53
+ && targetInput !== "segment") {
54
+ return "Image cells accept media or segment inputs";
55
+ }
56
+ if (target.type === "analysis") {
57
+ if (targetInput !== "result") {
58
+ return "Analysis result cells accept only the result input";
59
+ }
60
+ if (source.type !== "analyze") {
61
+ return "Analysis result cells require an Analyze source";
62
+ }
63
+ const existing = notebook.edges.find((edge) => edge.source === source.id && edge.targetInput === "result");
64
+ return existing && existing.target !== target.id
65
+ ? "Analyze cells can have only one Analysis result"
66
+ : null;
67
+ }
68
+ if (target.type === "extract_audio"
69
+ || target.inputs?.mediaRole === "extract-audio-output") {
70
+ if (targetInput !== "video" && targetInput !== "segment") {
71
+ return "Extract audio cells accept only the video input";
72
+ }
73
+ return notebookCellMediaType(source) === "video"
74
+ && source.type !== "split_video"
75
+ ? null
76
+ : "Extract audio cells require a video-producing source";
77
+ }
78
+ if (target.type === "split_video") {
79
+ if (targetInput === "analysis") {
80
+ return source.type === "analysis"
81
+ ? null
82
+ : "Split video analysis input requires an Analysis result cell";
83
+ }
84
+ if (targetInput !== "video") {
85
+ return "Split video cells accept video and analysis inputs";
86
+ }
87
+ return notebookCellMediaType(source) === "video"
88
+ && source.type !== "split_video"
89
+ ? null
90
+ : "Split video cells require a video-producing source";
91
+ }
92
+ if (target.type === "prompt" && targetInput === "analysis") {
93
+ return source.type === "analysis"
94
+ ? null
95
+ : "Prompt analysis input requires an Analysis result cell";
96
+ }
97
+ if (targetInput === "media"
98
+ && target.type === "audio"
99
+ && source.type !== "extract_audio"
100
+ && source.type !== "generate_audio") {
101
+ return "Audio media outputs require an extract audio or generate audio source";
102
+ }
103
+ if (targetInput === "media"
104
+ && target.type === "video"
105
+ && source.type !== "split_video"
106
+ && source.type !== "generate_video") {
107
+ return "Video media outputs require a split video or generate video source";
108
+ }
109
+ if (targetInput === "media"
110
+ && target.type === "image"
111
+ && source.type !== "generate_image") {
112
+ return "Image media outputs require a generate image source";
113
+ }
114
+ if (targetInput === "segment"
115
+ && target.type === "image"
116
+ && (notebookCellMediaType(source) !== "video"
117
+ || source.type === "split_video")) {
118
+ return "Image frame outputs require a video-producing source";
119
+ }
120
+ return null;
121
+ }
122
+ function prepareNotebookConnection(engine, notebook, sourceId, targetId, targetInput) {
123
+ const edges = notebookConnectionEdges(notebook.cells, notebook.edges, sourceId, targetId);
124
+ const prepared = edges && edges !== notebook.edges
125
+ ? { ...notebook, edges: [...edges] }
126
+ : notebook;
127
+ const error = notebookConnectionError(prepared, sourceId, targetId, targetInput);
128
+ if (error)
129
+ throw new Error(error);
130
+ if (prepared !== notebook) {
131
+ const source = notebook.cells.find((cell) => cell.id === sourceId);
132
+ const artifact = engine.artifacts.get(source.outputArtifactId);
133
+ if (!artifact.ok || artifact.value.kind !== "image") {
134
+ throw new Error(`${notebookGridTag(source.slot)} needs an available image before it can be reused`);
135
+ }
136
+ }
137
+ return prepared;
138
+ }
139
+ function markCellStale(notebook, cellId) {
140
+ const execution = { ...(notebook.execution ?? {}) };
141
+ const current = execution[cellId];
142
+ execution[cellId] = current
143
+ && typeof current === "object"
144
+ && !Array.isArray(current)
145
+ ? { ...current, stale: true }
146
+ : { stale: true };
147
+ return {
148
+ ...notebook,
149
+ execution,
150
+ cells: notebook.cells.map((cell) => cell.id === cellId
151
+ ? {
152
+ ...cell,
153
+ outputArtifactId: undefined,
154
+ inputs: {
155
+ ...(cell.inputs ?? {}),
156
+ state: "pending",
157
+ },
158
+ }
159
+ : cell),
160
+ };
161
+ }
162
+ function markCellAndDescendantsStale(notebook, cellId) {
163
+ return markCellStale(markDescendantsStale(notebook, cellId), cellId);
164
+ }
165
+ const cellReferenceSchema = z.object({
166
+ id: z.string().uuid(),
167
+ kind: z.enum([
168
+ "artifact",
169
+ "stream",
170
+ "source-range",
171
+ "transcript",
172
+ "sequence",
173
+ "cell-output",
174
+ ]),
175
+ target_id: z.string().min(1),
176
+ snapshot: z.record(z.string(), z.unknown()),
177
+ ordinal: z.number().int().nonnegative(),
178
+ });
179
+ const rationalSchema = z.object({
180
+ numerator: z.number().int().positive(),
181
+ denominator: z.number().int().positive(),
182
+ });
183
+ const sourceRangeSchema = z.object({
184
+ streamId: z.string().min(1),
185
+ objectHash: z.string().min(1),
186
+ startTick: z.number().int().nonnegative(),
187
+ durationTicks: z.number().int().positive(),
188
+ timeBase: rationalSchema,
189
+ });
190
+ const searchLocationSchema = z.discriminatedUnion("kind", [
191
+ z.object({
192
+ kind: z.literal("still"),
193
+ artifactId: z.string().uuid(),
194
+ sourcePath: z.string().min(1),
195
+ objectHash: z.string().min(1),
196
+ }),
197
+ z.object({
198
+ kind: z.literal("timed"),
199
+ artifactId: z.string().uuid(),
200
+ range: sourceRangeSchema,
201
+ }),
202
+ z.object({
203
+ kind: z.literal("document"),
204
+ artifactId: z.string().uuid(),
205
+ sourcePath: z.string().min(1),
206
+ objectHash: z.string().min(1),
207
+ startUtf8Byte: z.number().int().nonnegative(),
208
+ endUtf8Byte: z.number().int().positive(),
209
+ }),
210
+ ]);
211
+ const searchQuerySchema = z.record(z.string(), z.unknown());
212
+ const searchSignalSchema = z.object({
213
+ kind: z.enum(["visual", "speech", "ocr", "audio", "metadata", "exact", "near"]),
214
+ rank: z.number().int().positive(),
215
+ score: z.number().optional(),
216
+ explanation: z.string().optional(),
217
+ });
218
+ const pinnedSearchResultSchema = advertisedAs(z.object({
219
+ id: z.string().uuid(),
220
+ artifact_id: z.string().uuid(),
221
+ object_hash: z.string().min(1),
222
+ location: searchLocationSchema,
223
+ representative_tick: z.number().int().nonnegative().optional(),
224
+ query: searchQuerySchema,
225
+ signals: z.array(searchSignalSchema),
226
+ selected_revision: z.string().min(1),
227
+ ordinal: z.number().int().nonnegative(),
228
+ created_at: z.number().int().nonnegative(),
229
+ }), {
230
+ type: "object",
231
+ description: "A pinned result copied unchanged from get_notebook; pin new ones with pin_search_moment",
232
+ });
233
+ const cellInputSchema = z.object({
234
+ id: z.string().uuid().optional(),
235
+ type: cellTypeSchema,
236
+ label: z.string().optional(),
237
+ slot: notebookGridSlotSchema,
238
+ output_entity_id: z.string().uuid().optional(),
239
+ prompt: z.string().optional(),
240
+ provider: z.string().optional(),
241
+ model: z.string().optional(),
242
+ operation: z.string().optional(),
243
+ tool: z.string().optional(),
244
+ inputs: z.record(z.string(), z.unknown()).optional(),
245
+ output_artifact_id: z.string().uuid().optional(),
246
+ references: z.array(cellReferenceSchema).optional(),
247
+ pinned_results: z.array(pinnedSearchResultSchema).optional(),
248
+ });
249
+ function referenceFromInput(input) {
250
+ return {
251
+ id: input.id,
252
+ kind: input.kind,
253
+ targetId: input.target_id,
254
+ snapshot: input.snapshot,
255
+ ordinal: input.ordinal,
256
+ };
257
+ }
258
+ function pinnedResultFromInput(input) {
259
+ return {
260
+ id: input.id,
261
+ artifactId: input.artifact_id,
262
+ objectHash: input.object_hash,
263
+ location: input.location,
264
+ ...(input.representative_tick === undefined
265
+ ? {}
266
+ : { representativeTick: input.representative_tick }),
267
+ query: input.query,
268
+ signals: input.signals,
269
+ selectedRevision: input.selected_revision,
270
+ ordinal: input.ordinal,
271
+ createdAt: input.created_at,
272
+ };
273
+ }
274
+ function jobsByIdOrKey(engine, bookId, ids, keys) {
275
+ const byId = ids.map((id) => ({ id, jobKey: null }));
276
+ const byKey = keys.flatMap((jobKey) => {
277
+ const id = jobIdForKey(jobKey, bookId);
278
+ return id === null ? [] : [{ id, jobKey }];
279
+ });
280
+ return [...byId, ...byKey].flatMap(({ id, jobKey }) => {
281
+ const job = engine.jobs.queue.get(id);
282
+ return job ? [{ job, jobKey }] : [];
283
+ });
284
+ }
285
+ function selectJobs(engine, args) {
286
+ const ids = Array.isArray(args.job_ids) ? args.job_ids.map(Number) : null;
287
+ const keys = Array.isArray(args.job_keys) ? args.job_keys.map(String) : null;
288
+ if (!ids && !keys)
289
+ return engine.jobs.queue.list({ limit: Number(args.limit) || 200 }).map((job) => ({ job, jobKey: null }));
290
+ return jobsByIdOrKey(engine, String(args.book_id), ids ?? [], keys ?? []);
291
+ }
292
+ function json(value) {
293
+ return { content: [{ type: "text", text: JSON.stringify(value) }] };
294
+ }
295
+ function failure(message) {
296
+ return { isError: true, content: [{ type: "text", text: message }] };
297
+ }
298
+ function notebookGenerationPlans(notebook) {
299
+ return (notebook.generationPlans ?? []);
300
+ }
301
+ async function persistNotebookGenerationPlan(engine, notebookId, next) {
302
+ const result = await mutateNotebook(engine, notebookId, "persist_generation_plan", (notebook) => ({
303
+ ...notebook,
304
+ generationPlans: [
305
+ ...notebookGenerationPlans(notebook)
306
+ .filter((candidate) => candidate.planId !== next.planId),
307
+ next,
308
+ ],
309
+ }), {
310
+ planId: next.planId,
311
+ cellId: next.cellId,
312
+ status: next.status,
313
+ outputArtifactId: next.outputArtifactId,
314
+ error: next.error,
315
+ });
316
+ if (!result.ok)
317
+ throw new Error(result.error.message);
318
+ }
319
+ async function notebookGenerationPlan(engine, notebook, cellId, sourceSurface, actor, destination) {
320
+ const cell = notebook.cells.find((candidate) => candidate.id === cellId);
321
+ if (!cell || !notebookCellIsPaid(cell)) {
322
+ throw new Error("Choose a configured generation cell");
323
+ }
324
+ const prepared = await prepareNotebookGeneration(engine, notebook, cell, engine.book.get().bookId);
325
+ const assessment = await assessToolCallEstimate(engine, prepared.tool, prepared.input);
326
+ if (!assessment.available) {
327
+ await logUnavailableCostEstimate({
328
+ assessment,
329
+ engine,
330
+ correlation: {
331
+ bookId: engine.book.get().bookId,
332
+ notebookId: notebook.id,
333
+ cellId: cell.id,
334
+ tool: prepared.tool,
335
+ provider: prepared.provider,
336
+ model: prepared.model,
337
+ stage: "plan-generation",
338
+ },
339
+ });
340
+ }
341
+ const estimatedCost = assessment.available ? assessment.usd : undefined;
342
+ const estimateUnavailable = !assessment.available;
343
+ const cellDefinitionFingerprint = notebookCellDefinitionFingerprint(notebook, cell);
344
+ if (cell.operation === "rewrite_prompt") {
345
+ const planId = uuidv7();
346
+ const enriched = {
347
+ planVersion: 1,
348
+ planId,
349
+ sourceSurface,
350
+ actor,
351
+ bookId: engine.book.get().bookId,
352
+ summary: `Rewrite ${notebookGridTag(cell.slot)} for Seedance 2`,
353
+ normalizedReferences: [{
354
+ kind: "notebook",
355
+ targetId: notebook.id,
356
+ revision: engine.head,
357
+ }],
358
+ stages: [{
359
+ stageId: `${planId}:queued:0`,
360
+ kind: "queued",
361
+ summary: `Run ${prepared.tool}`,
362
+ jobType: "generation",
363
+ tool: "run_notebook_cell",
364
+ input: {
365
+ notebook_id: notebook.id,
366
+ cell_id: cell.id,
367
+ generation_tool: prepared.tool,
368
+ generation_input: prepared.input,
369
+ },
370
+ provider: prepared.provider,
371
+ model: prepared.model,
372
+ ...(estimateUnavailable
373
+ ? { estimateUnavailable: true }
374
+ : { estimatedCostUsd: estimatedCost }),
375
+ insertsOutput: false,
376
+ }],
377
+ confirmation: {
378
+ required: true,
379
+ policy: "always",
380
+ reason: estimateUnavailable
381
+ ? "Prompt rewrite pricing is unavailable and requires explicit confirmation"
382
+ : `This prompt rewrite can incur up to $${Number(estimatedCost).toFixed(4)}`,
383
+ },
384
+ cellDefinitionFingerprint,
385
+ };
386
+ const now = new Date().toISOString();
387
+ await persistNotebookGenerationPlan(engine, notebook.id, {
388
+ planId,
389
+ cellId: cell.id,
390
+ status: "planned",
391
+ plan: enriched,
392
+ createdAt: now,
393
+ updatedAt: now,
394
+ });
395
+ return enriched;
396
+ }
397
+ const sequence = engine.sequences.getPrimary();
398
+ const outputTrackKind = cell.type === "generate_audio"
399
+ ? "audio"
400
+ : "video";
401
+ const track = destination.trackId
402
+ ? sequence.tracks.find((candidate) => candidate.trackId === destination.trackId)
403
+ : sequence.tracks.find((candidate) => candidate.kind === outputTrackKind
404
+ && candidate.enabled
405
+ && !candidate.locked);
406
+ if (!track || track.kind !== outputTrackKind) {
407
+ throw new Error(`No editable ${outputTrackKind} track is available for the generated output`);
408
+ }
409
+ const plan = buildGenerationInsertPlan(engine, {
410
+ tool: prepared.tool,
411
+ input: {
412
+ notebook_id: notebook.id,
413
+ cell_id: cell.id,
414
+ generation_tool: prepared.tool,
415
+ generation_input: prepared.input,
416
+ },
417
+ provider: prepared.provider,
418
+ model: prepared.model,
419
+ ...(estimateUnavailable
420
+ ? { estimate_unavailable: true }
421
+ : { estimated_cost_usd: estimatedCost }),
422
+ track_id: track.trackId,
423
+ timeline_start_frame: destination.timelineStartFrame,
424
+ ...(destination.durationFrames === undefined
425
+ ? {}
426
+ : { duration_frames: destination.durationFrames }),
427
+ }, sourceSurface, actor);
428
+ const stages = Array.isArray(plan.stages)
429
+ ? plan.stages.map((stage) => {
430
+ if (!stage || typeof stage !== "object" || Array.isArray(stage))
431
+ return stage;
432
+ return {
433
+ ...stage,
434
+ tool: "run_notebook_cell",
435
+ };
436
+ })
437
+ : [];
438
+ const enriched = {
439
+ ...plan,
440
+ cellDefinitionFingerprint,
441
+ normalizedReferences: [
442
+ ...(Array.isArray(plan.normalizedReferences)
443
+ ? plan.normalizedReferences
444
+ : []),
445
+ {
446
+ kind: "notebook",
447
+ targetId: notebook.id,
448
+ revision: engine.head,
449
+ },
450
+ ],
451
+ stages,
452
+ };
453
+ const now = new Date().toISOString();
454
+ await persistNotebookGenerationPlan(engine, notebook.id, {
455
+ planId: String(enriched.planId),
456
+ cellId: cell.id,
457
+ status: "planned",
458
+ plan: enriched,
459
+ createdAt: now,
460
+ updatedAt: now,
461
+ });
462
+ return enriched;
463
+ }
464
+ function cellFromInput(input) {
465
+ return {
466
+ ...(input.id ? { id: input.id } : {}),
467
+ type: input.type,
468
+ ...(input.label === undefined ? {} : { label: input.label }),
469
+ slot: input.slot,
470
+ ...(input.output_entity_id
471
+ ? { outputEntityId: input.output_entity_id }
472
+ : {}),
473
+ ...(input.prompt === undefined ? {} : { prompt: input.prompt }),
474
+ ...(input.provider === undefined ? {} : { provider: input.provider }),
475
+ ...(input.model === undefined ? {} : { model: input.model }),
476
+ ...(input.operation === undefined ? {} : { operation: input.operation }),
477
+ ...(input.tool === undefined ? {} : { tool: input.tool }),
478
+ ...(input.inputs === undefined ? {} : { inputs: input.inputs }),
479
+ ...(input.output_artifact_id ? { outputArtifactId: input.output_artifact_id } : {}),
480
+ ...(input.references === undefined
481
+ ? {}
482
+ : { references: input.references.map(referenceFromInput) }),
483
+ ...(input.pinned_results === undefined
484
+ ? {}
485
+ : { pinnedResults: input.pinned_results.map(pinnedResultFromInput) }),
486
+ };
487
+ }
488
+ function cellPayload(cell) {
489
+ return {
490
+ id: cell.id,
491
+ type: cell.type,
492
+ ...(cell.label === undefined ? {} : { label: cell.label }),
493
+ address: notebookGridTag(cell.slot),
494
+ slot: cell.slot,
495
+ ...(cell.outputEntityId
496
+ ? { output_entity_id: cell.outputEntityId }
497
+ : {}),
498
+ ...(cell.prompt === undefined ? {} : { prompt: cell.prompt }),
499
+ ...(cell.provider === undefined ? {} : { provider: cell.provider }),
500
+ ...(cell.model === undefined ? {} : { model: cell.model }),
501
+ ...(cell.operation === undefined ? {} : { operation: cell.operation }),
502
+ ...(cell.tool === undefined ? {} : { tool: cell.tool }),
503
+ ...(cell.inputs === undefined ? {} : { inputs: cell.inputs }),
504
+ ...(cell.outputArtifactId ? { output_artifact_id: cell.outputArtifactId } : {}),
505
+ ...(cell.references === undefined
506
+ ? {}
507
+ : {
508
+ references: cell.references.map((reference) => ({
509
+ id: reference.id,
510
+ kind: reference.kind,
511
+ target_id: reference.targetId,
512
+ snapshot: reference.snapshot,
513
+ ordinal: reference.ordinal,
514
+ })),
515
+ }),
516
+ ...(cell.pinnedResults === undefined
517
+ ? {}
518
+ : {
519
+ pinned_results: cell.pinnedResults.map((result) => ({
520
+ id: result.id,
521
+ artifact_id: result.artifactId,
522
+ object_hash: result.objectHash,
523
+ location: result.location,
524
+ ...(result.representativeTick === undefined
525
+ ? {}
526
+ : { representative_tick: result.representativeTick }),
527
+ query: result.query,
528
+ signals: result.signals,
529
+ selected_revision: result.selectedRevision,
530
+ ordinal: result.ordinal,
531
+ created_at: result.createdAt,
532
+ })),
533
+ }),
534
+ };
535
+ }
536
+ function notebookPayload(engine, notebook) {
537
+ const normalized = hydrateNotebookProviderFailures(engine, normalizeLegacyAnalyzeDocument(normalizeLegacyExtractCells(notebook)));
538
+ return {
539
+ id: normalized.id,
540
+ name: normalized.name,
541
+ revision: engine.head,
542
+ created_at: normalized.createdAt,
543
+ ...(normalized.description === undefined
544
+ ? {}
545
+ : { description: normalized.description }),
546
+ ...(normalized.lifecycleState === undefined
547
+ ? {}
548
+ : { lifecycleState: normalized.lifecycleState }),
549
+ ...(normalized.workflowVersion === undefined
550
+ ? {}
551
+ : { workflowVersion: normalized.workflowVersion }),
552
+ ...(normalized.analysisRevision === undefined
553
+ ? {}
554
+ : { analysisRevision: normalized.analysisRevision }),
555
+ ...(normalized.fixture === undefined ? {} : { fixture: normalized.fixture }),
556
+ execution: normalized.execution ?? {},
557
+ generationPlans: normalized.generationPlans ?? [],
558
+ notebookRunPlans: normalized.notebookRunPlans ?? [],
559
+ cells: normalized.cells.map(cellPayload),
560
+ posts: notebookPosts(engine, normalized),
561
+ edges: normalized.edges,
562
+ };
563
+ }
564
+ const entityKindSchema = z.enum(["prompt", "scene", "character"]);
565
+ function entityDefs(engine) {
566
+ return [
567
+ {
568
+ name: "create_entity",
569
+ description: "Create a reusable prompt, scene or character",
570
+ scope: "book",
571
+ schema: {
572
+ ...bookSchema,
573
+ kind: entityKindSchema,
574
+ name: z.string().min(1),
575
+ description: z.string().optional(),
576
+ prompt: z.string().optional(),
577
+ data: z.record(z.string(), z.unknown()).optional(),
578
+ },
579
+ handler: async (args) => {
580
+ const result = await engine.entities.create(args.kind, String(args.name), {
581
+ ...(typeof args.description === "string" ? { description: args.description } : {}),
582
+ ...(typeof args.prompt === "string" ? { prompt: args.prompt } : {}),
583
+ data: args.data,
584
+ });
585
+ return result.ok ? json(result.value) : failure(result.error.message);
586
+ },
587
+ },
588
+ {
589
+ name: "list_entities",
590
+ description: "List reusable prompts, scenes and characters, optionally of one kind",
591
+ scope: "book",
592
+ schema: { ...bookSchema, kind: entityKindSchema.optional() },
593
+ handler: async (args) => json(engine.entities.list(args.kind)),
594
+ },
595
+ {
596
+ name: "get_entity",
597
+ description: "Read a reusable prompt, scene or character",
598
+ scope: "book",
599
+ schema: { ...bookSchema, entity_id: z.string().uuid() },
600
+ handler: async (args) => {
601
+ const result = engine.entities.read(String(args.entity_id));
602
+ return result.ok ? json(result.value) : failure(result.error.message);
603
+ },
604
+ },
605
+ {
606
+ name: "update_entity",
607
+ description: "Replace a reusable prompt, scene or character with an edited copy from get_entity",
608
+ scope: "book",
609
+ schema: { ...bookSchema, entity: z.record(z.string(), z.unknown()) },
610
+ handler: async (args) => {
611
+ const result = await engine.entities.write(args.entity);
612
+ return result.ok ? json(result.value) : failure(result.error.message);
613
+ },
614
+ },
615
+ {
616
+ name: "delete_entity",
617
+ description: "Delete a reusable prompt, scene or character",
618
+ scope: "book",
619
+ schema: { ...bookSchema, entity_id: z.string().uuid() },
620
+ handler: async (args) => {
621
+ const result = await engine.entities.delete(String(args.entity_id));
622
+ return result.ok ? json(result.value) : failure(result.error.message);
623
+ },
624
+ },
625
+ ];
626
+ }
627
+ function refreshGenerationInputPrompt(previous, next, cellId) {
628
+ const seeded = seedInitialGenerationPrompt(next, cellId);
629
+ const before = previous.cells.find((cell) => cell.id === cellId);
630
+ const after = seeded.cells.find((cell) => cell.id === cellId);
631
+ if (!after?.prompt || before?.inputs?.generationMode !== "r2v" || after.prompt !== before.prompt)
632
+ return seeded;
633
+ const prompt = remapGenerationInputPrompt(after.prompt, generationInputMentions(cellId, previous.cells, previous.edges), generationInputMentions(cellId, seeded.cells, seeded.edges));
634
+ return prompt === after.prompt ? seeded : {
635
+ ...seeded,
636
+ cells: seeded.cells.map((cell) => cell.id === cellId ? { ...cell, prompt } : cell),
637
+ };
638
+ }
639
+ export function videobookDomainDefs(engine, books) {
640
+ return [
641
+ {
642
+ name: "list_books",
643
+ description: "List all Videobook books",
644
+ scope: "global",
645
+ schema: { sort: z.enum(["newest", "oldest"]).optional() },
646
+ handler: async (args) => {
647
+ await books.ready();
648
+ return json(books.list({
649
+ ...(args.sort === "newest" || args.sort === "oldest" ? { sort: args.sort } : {}),
650
+ }));
651
+ },
652
+ },
653
+ {
654
+ name: "create_book",
655
+ description: "Create a book with a Main notebook. Project names are normalized to lowercase slugs with hyphens.",
656
+ scope: "global",
657
+ schema: { name: z.string().min(1).describe("Project slug, such as my-project. Input is normalized to lowercase with hyphens.") },
658
+ handler: async (args) => {
659
+ const result = await books.create(String(args.name));
660
+ return result.ok ? json(result.value) : failure(result.error.message);
661
+ },
662
+ },
663
+ {
664
+ name: "rename_book",
665
+ description: "Rename a book. Project names are normalized to lowercase slugs with hyphens.",
666
+ scope: "global",
667
+ schema: { ...bookSchema, name: z.string().min(1).describe("Project slug, such as my-project. Input is normalized to lowercase with hyphens.") },
668
+ handler: async (args) => {
669
+ const result = await books.rename(String(args.book_id), String(args.name));
670
+ return result.ok ? json(result.value) : failure(result.error.message);
671
+ },
672
+ },
673
+ {
674
+ name: "delete_book",
675
+ description: "Permanently delete a book after typed name confirmation",
676
+ scope: "global",
677
+ schema: { ...bookSchema, confirm_name: z.string().min(1) },
678
+ handler: async (args) => {
679
+ const result = await books.delete(String(args.book_id), {
680
+ confirmName: String(args.confirm_name),
681
+ beforeDelete: async (book) => stopBookQueue(book.bookId),
682
+ });
683
+ return result.ok ? json(result.value) : failure(result.error.message);
684
+ },
685
+ },
686
+ {
687
+ name: "get_book",
688
+ description: "Read the current book identity",
689
+ scope: "book",
690
+ schema: bookSchema,
691
+ handler: async () => json(engine.book.get()),
692
+ },
693
+ {
694
+ name: "check_duplicate_bytes",
695
+ description: "Find existing book files with the same immutable content hash before import",
696
+ scope: "book",
697
+ schema: {
698
+ ...bookSchema,
699
+ object_hash: z.string().regex(/^[0-9a-f]{64}$/i),
700
+ },
701
+ handler: async (args) => {
702
+ const objectHash = String(args.object_hash).toLowerCase();
703
+ const matches = [];
704
+ for (const artifact of engine.artifacts.list()) {
705
+ const manifest = await engine.files.manifest(artifact.artifactId);
706
+ if (!manifest.ok)
707
+ continue;
708
+ for (const file of manifest.value.files) {
709
+ if (file.objectHash.toLowerCase() !== objectHash)
710
+ continue;
711
+ matches.push({
712
+ artifact_id: artifact.artifactId,
713
+ ...(artifact.label === undefined ? {} : { label: artifact.label }),
714
+ kind: artifact.kind,
715
+ filename: file.name,
716
+ object_hash: file.objectHash,
717
+ });
718
+ }
719
+ }
720
+ return json({ object_hash: objectHash, matches });
721
+ },
722
+ },
723
+ {
724
+ name: "list_notebooks",
725
+ description: "List canonical notebook documents",
726
+ scope: "book",
727
+ schema: bookSchema,
728
+ handler: async () => json(engine.notebooks.list().map((notebook) => notebookPayload(engine, notebook))),
729
+ },
730
+ {
731
+ name: "get_primary_sequence",
732
+ description: "Read the canonical primary sequence",
733
+ scope: "book",
734
+ schema: bookSchema,
735
+ handler: async () => json(engine.sequences.getPrimary()),
736
+ },
737
+ {
738
+ name: "add_sequence_track",
739
+ description: "Append a canonical video, audio, or caption track",
740
+ scope: "book",
741
+ schema: {
742
+ ...bookSchema,
743
+ kind: z.enum(["video", "audio", "caption"]),
744
+ name: z.string().min(1).optional(),
745
+ },
746
+ handler: async (args) => {
747
+ const sequence = engine.sequences.getPrimary();
748
+ const result = await engine.sequences.addTrack(sequence.sequenceId, {
749
+ kind: args.kind,
750
+ ...(typeof args.name === "string" ? { name: args.name } : {}),
751
+ });
752
+ return result.ok ? json(result.value) : failure(result.error.message);
753
+ },
754
+ },
755
+ {
756
+ name: "remove_sequence_track",
757
+ description: "Remove an empty canonical track while preserving required track kinds",
758
+ scope: "book",
759
+ schema: {
760
+ ...bookSchema,
761
+ track_id: z.string().uuid(),
762
+ },
763
+ handler: async (args) => {
764
+ const result = await engine.sequences.removeTrack(String(args.track_id));
765
+ return result.ok ? json(result.value) : failure(result.error.message);
766
+ },
767
+ },
768
+ {
769
+ name: "plan_notebook_generation",
770
+ description: "Plan a notebook prompt, image, or video generation with provider, model, cost, and revision-pinned execution",
771
+ scope: "book",
772
+ schema: {
773
+ ...bookSchema,
774
+ notebook_id: z.string().uuid(),
775
+ cell_id: z.string().uuid(),
776
+ source_surface: z.enum(["ui", "slash", "chat"]),
777
+ actor: z.string().min(1),
778
+ track_id: z.string().uuid().optional(),
779
+ timeline_start_frame: z.number().int().nonnegative(),
780
+ duration_frames: z.number().int().positive().optional(),
781
+ },
782
+ handler: async (args) => {
783
+ try {
784
+ const notebook = engine.notebooks.read(String(args.notebook_id));
785
+ if (!notebook.ok)
786
+ return failure(notebook.error.message);
787
+ return json(await notebookGenerationPlan(engine, notebook.value, String(args.cell_id), args.source_surface, String(args.actor), {
788
+ ...(typeof args.track_id === "string"
789
+ ? { trackId: args.track_id }
790
+ : {}),
791
+ timelineStartFrame: Number(args.timeline_start_frame),
792
+ ...(typeof args.duration_frames === "number"
793
+ ? { durationFrames: args.duration_frames }
794
+ : {}),
795
+ }));
796
+ }
797
+ catch (error) {
798
+ return failure(error instanceof Error ? error.message : String(error));
799
+ }
800
+ },
801
+ },
802
+ {
803
+ name: "list_notebook_generation_plans",
804
+ description: "List durable generation plans and their retryable lifecycle state",
805
+ scope: "book",
806
+ schema: {
807
+ ...bookSchema,
808
+ notebook_id: z.string().uuid(),
809
+ },
810
+ handler: async (args) => {
811
+ const notebook = engine.notebooks.read(String(args.notebook_id));
812
+ return notebook.ok
813
+ ? json(notebookGenerationPlans(notebook.value))
814
+ : failure(notebook.error.message);
815
+ },
816
+ },
817
+ {
818
+ name: "plan_notebook_run",
819
+ description: "Plan every missing or stale notebook cell with aggregate provider cost and media disclosure",
820
+ scope: "book",
821
+ schema: {
822
+ ...bookSchema,
823
+ notebook_id: z.string().uuid(),
824
+ source_surface: z.enum(["ui", "slash", "chat"]),
825
+ actor: z.string().min(1),
826
+ force: z.boolean().optional(),
827
+ },
828
+ handler: async (args) => {
829
+ try {
830
+ const notebook = engine.notebooks.read(String(args.notebook_id));
831
+ if (!notebook.ok)
832
+ return failure(notebook.error.message);
833
+ return json(await planNotebookRun(engine, notebook.value, args.source_surface, String(args.actor), { force: args.force === true }));
834
+ }
835
+ catch (error) {
836
+ return failure(error instanceof Error ? error.message : String(error));
837
+ }
838
+ },
839
+ },
840
+ {
841
+ name: "plan_notebook_subgraph",
842
+ description: "Plan the selected cell plus required stale ancestors, with paid-stage fingerprints, cost, and confirmation",
843
+ scope: "book",
844
+ schema: {
845
+ ...bookSchema,
846
+ notebook_id: z.string().uuid(),
847
+ cell_id: z.string().uuid(),
848
+ source_surface: z.enum(["ui", "slash", "chat"]),
849
+ actor: z.string().min(1),
850
+ },
851
+ handler: async (args) => {
852
+ try {
853
+ const notebook = engine.notebooks.read(String(args.notebook_id));
854
+ if (!notebook.ok)
855
+ return failure(notebook.error.message);
856
+ return json(await planSelectedNotebookSubgraph(engine, notebook.value, String(args.cell_id), args.source_surface, String(args.actor)));
857
+ }
858
+ catch (error) {
859
+ return failure(error instanceof Error ? error.message : String(error));
860
+ }
861
+ },
862
+ },
863
+ {
864
+ name: "list_notebook_run_plans",
865
+ description: "List durable aggregate workbook run plans and their lifecycle state",
866
+ scope: "book",
867
+ schema: {
868
+ ...bookSchema,
869
+ notebook_id: z.string().uuid(),
870
+ },
871
+ handler: async (args) => {
872
+ const notebook = engine.notebooks.read(String(args.notebook_id));
873
+ return notebook.ok
874
+ ? json(notebookRunPlanRecords(notebook.value))
875
+ : failure(notebook.error.message);
876
+ },
877
+ },
878
+ {
879
+ name: "enqueue_notebook_run",
880
+ description: "Revalidate and enqueue a persisted aggregate workbook plan",
881
+ scope: "book",
882
+ schema: {
883
+ ...bookSchema,
884
+ notebook_id: z.string().uuid(),
885
+ plan_id: z.string().uuid(),
886
+ actor: z.string().min(1),
887
+ confirm_unpriced: z.boolean(),
888
+ },
889
+ handler: async (args) => {
890
+ try {
891
+ return json(await enqueueNotebookRunPlan(engine, String(args.notebook_id), String(args.plan_id), {
892
+ actor: String(args.actor),
893
+ confirmUnpriced: args.confirm_unpriced === true,
894
+ }));
895
+ }
896
+ catch (error) {
897
+ return failure(error instanceof Error ? error.message : String(error));
898
+ }
899
+ },
900
+ },
901
+ {
902
+ name: "revise_notebook_generation_plan",
903
+ description: "Retry, switch the allowed generation model, or omit a preserved notebook generation plan",
904
+ scope: "book",
905
+ schema: {
906
+ ...bookSchema,
907
+ notebook_id: z.string().uuid(),
908
+ plan_id: z.string().uuid(),
909
+ action: z.enum(["retry", "switch", "omit"]),
910
+ model: z.string().min(1).optional(),
911
+ source_surface: z.enum(["ui", "slash", "chat"]),
912
+ actor: z.string().min(1),
913
+ },
914
+ handler: async (args) => {
915
+ try {
916
+ const notebookResult = engine.notebooks.read(String(args.notebook_id));
917
+ if (!notebookResult.ok)
918
+ return failure(notebookResult.error.message);
919
+ let notebook = notebookResult.value;
920
+ const existing = notebookGenerationPlans(notebook).find((candidate) => candidate.planId === String(args.plan_id));
921
+ if (!existing)
922
+ return failure("Generation plan not found");
923
+ if (args.action === "omit") {
924
+ await persistNotebookGenerationPlan(engine, notebook.id, {
925
+ ...existing,
926
+ status: "omitted",
927
+ updatedAt: new Date().toISOString(),
928
+ });
929
+ return json({
930
+ kind: "generation-omitted",
931
+ planId: existing.planId,
932
+ outputArtifactId: existing.outputArtifactId,
933
+ });
934
+ }
935
+ if (args.action === "switch") {
936
+ if (typeof args.model !== "string") {
937
+ return failure("Switching requires an allowed generation model");
938
+ }
939
+ const nextModel = String(args.model);
940
+ const updated = await mutateNotebook(engine, notebook.id, "switch_generation_model", (document) => ({
941
+ ...document,
942
+ cells: document.cells.map((cell) => cell.id === existing.cellId
943
+ ? {
944
+ ...cell,
945
+ model: nextModel,
946
+ // Model switch can pass a generation tool name; keep tool in sync.
947
+ ...(nextModel.startsWith("generate_")
948
+ || nextModel.startsWith("extend_")
949
+ || nextModel.startsWith("upscale_")
950
+ ? { tool: nextModel }
951
+ : {}),
952
+ }
953
+ : cell),
954
+ }), {
955
+ sourcePlanId: existing.planId,
956
+ cellId: existing.cellId,
957
+ model: args.model,
958
+ });
959
+ if (!updated.ok)
960
+ return failure(updated.error.message);
961
+ const refreshed = engine.notebooks.read(notebook.id);
962
+ if (!refreshed.ok)
963
+ return failure(refreshed.error.message);
964
+ notebook = refreshed.value;
965
+ }
966
+ const stages = Array.isArray(existing.plan.stages)
967
+ ? existing.plan.stages
968
+ : [];
969
+ const queued = stages.find((stage) => stage
970
+ && typeof stage === "object"
971
+ && !Array.isArray(stage)
972
+ && stage.kind === "queued");
973
+ const continuation = queued?.continuation
974
+ && typeof queued.continuation === "object"
975
+ && !Array.isArray(queued.continuation)
976
+ ? queued.continuation
977
+ : null;
978
+ const destination = continuation?.destination
979
+ && typeof continuation.destination === "object"
980
+ && !Array.isArray(continuation.destination)
981
+ ? continuation.destination
982
+ : null;
983
+ if (!destination
984
+ || typeof destination.timelineStartFrame !== "number") {
985
+ return failure("The preserved generation destination is invalid");
986
+ }
987
+ const next = await notebookGenerationPlan(engine, notebook, existing.cellId, args.source_surface, String(args.actor), {
988
+ ...(typeof destination.trackId === "string"
989
+ ? { trackId: destination.trackId }
990
+ : {}),
991
+ timelineStartFrame: destination.timelineStartFrame,
992
+ ...(typeof destination.durationFrames === "number"
993
+ ? { durationFrames: destination.durationFrames }
994
+ : {}),
995
+ });
996
+ return json({
997
+ ...next,
998
+ parentActionId: existing.planId,
999
+ });
1000
+ }
1001
+ catch (error) {
1002
+ return failure(error instanceof Error ? error.message : String(error));
1003
+ }
1004
+ },
1005
+ },
1006
+ {
1007
+ name: "continue_notebook_generation",
1008
+ description: "Create a reviewable insertion plan for a completed generation or report a stale destination",
1009
+ scope: "book",
1010
+ schema: {
1011
+ ...bookSchema,
1012
+ notebook_id: z.string().uuid(),
1013
+ plan_id: z.string().uuid(),
1014
+ actor: z.string().min(1),
1015
+ },
1016
+ handler: async (args) => {
1017
+ try {
1018
+ const notebook = engine.notebooks.read(String(args.notebook_id));
1019
+ if (!notebook.ok)
1020
+ return failure(notebook.error.message);
1021
+ const existing = notebookGenerationPlans(notebook.value).find((candidate) => candidate.planId === String(args.plan_id));
1022
+ if (!existing)
1023
+ return failure("Generation plan not found");
1024
+ if (!existing.outputArtifactId) {
1025
+ return failure("Generation output is not ready");
1026
+ }
1027
+ const continuation = await continueQueuedInsert(engine, existing.plan, existing.outputArtifactId, String(args.actor));
1028
+ if (continuation.kind !== "stale-continuation") {
1029
+ await persistNotebookGenerationPlan(engine, notebook.value.id, {
1030
+ ...existing,
1031
+ status: "continued",
1032
+ updatedAt: new Date().toISOString(),
1033
+ });
1034
+ }
1035
+ return json(continuation);
1036
+ }
1037
+ catch (error) {
1038
+ return failure(error instanceof Error ? error.message : String(error));
1039
+ }
1040
+ },
1041
+ },
1042
+ {
1043
+ name: "create_notebook",
1044
+ description: "Create an empty notebook",
1045
+ scope: "book",
1046
+ schema: { ...bookSchema, name: z.string().min(1) },
1047
+ handler: async (args) => {
1048
+ const result = await engine.notebooks.create(String(args.name));
1049
+ return result.ok ? json(notebookPayload(engine, result.value)) : failure(result.error.message);
1050
+ },
1051
+ },
1052
+ {
1053
+ name: "get_notebook",
1054
+ description: "Read a notebook graph",
1055
+ scope: "book",
1056
+ schema: { ...bookSchema, notebook_id: z.string().uuid() },
1057
+ handler: async (args) => {
1058
+ await migrateLegacyPosts(engine);
1059
+ const result = engine.notebooks.read(String(args.notebook_id));
1060
+ return result.ok ? json(notebookPayload(engine, result.value)) : failure(result.error.message);
1061
+ },
1062
+ },
1063
+ {
1064
+ name: "list_notebook_actions",
1065
+ description: "List the deterministic notebook action catalog for runnable cells",
1066
+ scope: "book",
1067
+ schema: {
1068
+ ...bookSchema,
1069
+ cell_type: cellTypeSchema.optional(),
1070
+ },
1071
+ handler: async (args) => {
1072
+ const actions = listNotebookActions()
1073
+ .filter((action) => args.cell_type
1074
+ ? action.cellTypes.includes(args.cell_type)
1075
+ : true);
1076
+ return json({ actions });
1077
+ },
1078
+ },
1079
+ {
1080
+ name: "get_notebook_cell_details",
1081
+ description: "Return selected-cell details including raw Dolt row, edges, and action validation",
1082
+ scope: "book",
1083
+ schema: {
1084
+ ...bookSchema,
1085
+ notebook_id: z.string().uuid(),
1086
+ cell_id: z.string().uuid(),
1087
+ },
1088
+ handler: async (args) => {
1089
+ await migrateLegacyPosts(engine);
1090
+ const result = engine.notebooks.read(String(args.notebook_id));
1091
+ if (!result.ok)
1092
+ return failure(result.error.message);
1093
+ try {
1094
+ const document = hydrateNotebookProviderFailures(engine, normalizeLegacyExtractCells(result.value));
1095
+ const { generateCell } = pairForCell(document, String(args.cell_id));
1096
+ const generationsResult = engine.generations.listForCell(String(args.notebook_id), generateCell.id);
1097
+ return json(selectedCellDetails(String(args.notebook_id), document, String(args.cell_id), generationsResult.ok ? generationsResult.value : []));
1098
+ }
1099
+ catch (cause) {
1100
+ return failure(cause instanceof Error ? cause.message : String(cause));
1101
+ }
1102
+ },
1103
+ },
1104
+ {
1105
+ name: "ensure_notebook_workflow_scaffold",
1106
+ description: "Ensure the seven-stage VIDEO→CONCAT workflow scaffold exists",
1107
+ scope: "book",
1108
+ schema: {
1109
+ ...bookSchema,
1110
+ notebook_id: z.string().uuid(),
1111
+ source_artifact_id: z.string().uuid().optional(),
1112
+ },
1113
+ handler: async (args) => {
1114
+ const result = await mutateNotebook(engine, String(args.notebook_id), "ensure_notebook_workflow_scaffold", (notebook) => buildSevenStageScaffold(engine, notebook, typeof args.source_artifact_id === "string"
1115
+ ? args.source_artifact_id
1116
+ : undefined));
1117
+ if (!result.ok)
1118
+ return failure(result.error.message);
1119
+ const read = engine.notebooks.read(String(args.notebook_id));
1120
+ return read.ok
1121
+ ? json(notebookPayload(engine, read.value))
1122
+ : failure(read.error.message);
1123
+ },
1124
+ },
1125
+ {
1126
+ name: "rename_notebook",
1127
+ description: "Rename a notebook",
1128
+ scope: "book",
1129
+ schema: { ...bookSchema, notebook_id: z.string().uuid(), name: z.string().min(1) },
1130
+ handler: async (args) => {
1131
+ const result = await mutateNotebook(engine, String(args.notebook_id), "rename_notebook", (notebook) => ({ ...notebook, name: String(args.name) }));
1132
+ return result.ok ? json({ notebook_id: args.notebook_id, revision: result.value.hash }) : failure(result.error.message);
1133
+ },
1134
+ },
1135
+ {
1136
+ name: "delete_notebook",
1137
+ description: "Delete a notebook and its cells, edges, and runs",
1138
+ scope: "book",
1139
+ schema: { ...bookSchema, notebook_id: z.string().uuid() },
1140
+ handler: async (args) => {
1141
+ const result = await engine.notebooks.delete(String(args.notebook_id));
1142
+ return result.ok ? json(result.value) : failure(result.error.message);
1143
+ },
1144
+ },
1145
+ {
1146
+ name: "create_notebook_cell",
1147
+ description: "Create a UUIDv7 notebook cell",
1148
+ scope: "book",
1149
+ schema: {
1150
+ ...bookSchema,
1151
+ notebook_id: z.string().uuid(),
1152
+ cell: cellInputSchema,
1153
+ expected_revision: z.string().min(1).optional(),
1154
+ },
1155
+ handler: async (args) => {
1156
+ if (args.cell.type === "post")
1157
+ return failure("Use create_post to freeze source media and create a Post cell");
1158
+ if (args.cell.type === "goal")
1159
+ return failure(GOAL_TILE_CREATE_ERROR);
1160
+ let cell = engine.notebooks.createCell(cellFromInput(args.cell));
1161
+ let outputCell = null;
1162
+ let outputEdge = null;
1163
+ try {
1164
+ const result = await mutateNotebook(engine, String(args.notebook_id), "create_notebook_cell", (notebook) => {
1165
+ if (typeof args.expected_revision === "string"
1166
+ && args.expected_revision !== engine.head) {
1167
+ throw new Error(`Stale notebook revision: expected ${args.expected_revision}`);
1168
+ }
1169
+ cell = normalizeCellActionFields(cell, notebook);
1170
+ const withCell = seedInitialGenerationPrompt({
1171
+ ...notebook,
1172
+ cells: [...notebook.cells, cell],
1173
+ }, cell.id);
1174
+ cell = withCell.cells.find((candidate) => candidate.id === cell.id);
1175
+ if (cell.type === "analyze") {
1176
+ const cells = withCell.cells;
1177
+ outputCell = engine.notebooks.createCell({
1178
+ type: "analysis",
1179
+ ...(cell.label === undefined ? {} : { label: cell.label }),
1180
+ slot: nextHorizontalSlotFrom(slotRight(cell.slot), notebookOccupiedSlots(cells)),
1181
+ inputs: {
1182
+ analysisRole: "result",
1183
+ producerCellId: cell.id,
1184
+ state: "pending",
1185
+ },
1186
+ });
1187
+ outputEdge = engine.notebooks.createEdge({
1188
+ source: cell.id,
1189
+ target: outputCell.id,
1190
+ targetInput: "result",
1191
+ });
1192
+ }
1193
+ if (cell.type === "generate_audio") {
1194
+ const ensured = ensureGenerateAudioOutput(engine, withCell, cell.id);
1195
+ cell = ensured.document.cells.find((candidate) => candidate.id === cell.id) ?? cell;
1196
+ outputCell = ensured.outputCell;
1197
+ outputEdge = ensured.outputEdge;
1198
+ return ensured.document;
1199
+ }
1200
+ return {
1201
+ ...withCell,
1202
+ cells: [
1203
+ ...withCell.cells,
1204
+ ...(outputCell ? [outputCell] : []),
1205
+ ],
1206
+ edges: [
1207
+ ...withCell.edges,
1208
+ ...(outputEdge ? [outputEdge] : []),
1209
+ ],
1210
+ };
1211
+ }, { cellId: cell.id });
1212
+ return result.ok
1213
+ ? json({
1214
+ ...cellPayload(cell),
1215
+ ...(outputCell ? { output_cell: cellPayload(outputCell) } : {}),
1216
+ ...(outputEdge ? { output_edge: outputEdge } : {}),
1217
+ })
1218
+ : failure(result.error.message);
1219
+ }
1220
+ catch (cause) {
1221
+ return failure(cause instanceof Error ? cause.message : String(cause));
1222
+ }
1223
+ },
1224
+ },
1225
+ {
1226
+ name: "update_notebook_cell",
1227
+ description: "Update one notebook cell",
1228
+ scope: "book",
1229
+ schema: {
1230
+ ...bookSchema,
1231
+ notebook_id: z.string().uuid(),
1232
+ cell_id: z.string().uuid(),
1233
+ cell: cellInputSchema.partial(),
1234
+ expected_revision: z.string().min(1).optional(),
1235
+ },
1236
+ handler: async (args) => {
1237
+ if (typeof args.expected_revision === "string"
1238
+ && args.expected_revision !== engine.head) {
1239
+ return failure(`Stale notebook revision: expected ${args.expected_revision}`);
1240
+ }
1241
+ const patch = args.cell;
1242
+ try {
1243
+ const result = await mutateNotebook(engine, String(args.notebook_id), "update_notebook_cell", (notebook) => {
1244
+ if (typeof args.expected_revision === "string"
1245
+ && args.expected_revision !== engine.head) {
1246
+ throw new Error(`Stale notebook revision: expected ${args.expected_revision}`);
1247
+ }
1248
+ const document = notebook;
1249
+ let found = false;
1250
+ const cells = notebook.cells.map((cell) => {
1251
+ if (cell.id !== args.cell_id)
1252
+ return cell;
1253
+ found = true;
1254
+ if (patch.type === "post" && cell.type !== "post")
1255
+ throw new Error("Use create_post to freeze source media and create a Post cell");
1256
+ if (cell.type === "post") {
1257
+ if (Object.keys(patch).some((key) => key !== "slot" && key !== "label"))
1258
+ throw new Error("Use update_post to edit publication details");
1259
+ return { ...cell, ...(patch.slot ? { slot: patch.slot } : {}), ...(patch.label !== undefined ? { label: patch.label } : {}) };
1260
+ }
1261
+ if (cell.type === "goal")
1262
+ return moveGoalTile(cell, patch);
1263
+ if (patch.type && patch.type !== cell.type) {
1264
+ const isUnconnected = !notebook.edges.some((edge) => edge.source === cell.id || edge.target === cell.id);
1265
+ const replacement = typeof patch.output_artifact_id === "string"
1266
+ ? engine.artifacts.get(patch.output_artifact_id)
1267
+ : null;
1268
+ const isDownloadImageReplacement = cell.type === "video"
1269
+ && patch.type === "image"
1270
+ && cell.inputs?.sourceOperation === "download_video"
1271
+ && patch.output_artifact_id !== cell.outputArtifactId
1272
+ && replacement?.ok === true
1273
+ && replacement.value.kind === "image"
1274
+ && isUnconnected;
1275
+ if (!isDownloadImageReplacement) {
1276
+ throw new Error("Cell type cannot be changed after creation");
1277
+ }
1278
+ }
1279
+ const normalized = normalizeCellActionFields({
1280
+ ...cell,
1281
+ ...(patch.type === undefined ? {} : { type: patch.type }),
1282
+ ...(patch.label === undefined ? {} : { label: patch.label }),
1283
+ ...(patch.slot === undefined ? {} : { slot: patch.slot }),
1284
+ ...(patch.output_entity_id === undefined
1285
+ ? {}
1286
+ : { outputEntityId: patch.output_entity_id }),
1287
+ ...(patch.prompt === undefined ? {} : { prompt: patch.prompt }),
1288
+ ...(patch.provider === undefined ? {} : { provider: patch.provider }),
1289
+ ...(patch.model === undefined ? {} : { model: patch.model }),
1290
+ ...(patch.operation === undefined ? {} : { operation: patch.operation }),
1291
+ ...(patch.tool === undefined ? {} : { tool: patch.tool }),
1292
+ ...(patch.inputs === undefined ? {} : { inputs: patch.inputs }),
1293
+ ...(patch.output_artifact_id === undefined ? {} : { outputArtifactId: patch.output_artifact_id }),
1294
+ ...(patch.references === undefined
1295
+ ? {}
1296
+ : { references: patch.references.map(referenceFromInput) }),
1297
+ ...(patch.pinned_results === undefined
1298
+ ? {}
1299
+ : { pinnedResults: patch.pinned_results.map(pinnedResultFromInput) }),
1300
+ }, document);
1301
+ const next = reconcileInitialGenerationPrompt(cell, normalized);
1302
+ const validation = validateCellConfiguration(next, document);
1303
+ if (!validation.ok)
1304
+ throw new Error(validation.message);
1305
+ return next;
1306
+ });
1307
+ if (!found)
1308
+ throw new Error(`Cell not found: ${String(args.cell_id)}`);
1309
+ const updated = refreshInitialGenerationPrompts({
1310
+ ...document,
1311
+ cells,
1312
+ });
1313
+ return markDescendantsStale(updated, String(args.cell_id));
1314
+ }, { cellId: args.cell_id });
1315
+ return result.ok
1316
+ ? json({ cell_id: args.cell_id, revision: result.value.hash })
1317
+ : failure(result.error.message);
1318
+ }
1319
+ catch (cause) {
1320
+ return failure(cause instanceof Error ? cause.message : String(cause));
1321
+ }
1322
+ },
1323
+ },
1324
+ {
1325
+ name: "relayout_notebook",
1326
+ description: "Recompute notebook cell slots with directional placement: video stacks vertical near column 1, image/frame chains horizontal, compact near @a1. Migrates legacy generate_video / generate_image inline artifacts into output cells.",
1327
+ scope: "book",
1328
+ recordsOwnActivity: true,
1329
+ schema: {
1330
+ ...bookSchema,
1331
+ notebook_id: z.string().uuid(),
1332
+ expected_revision: z.string().min(1).optional(),
1333
+ },
1334
+ handler: async (args) => {
1335
+ try {
1336
+ const notebookId = String(args.notebook_id);
1337
+ if (typeof args.expected_revision === "string"
1338
+ && args.expected_revision !== engine.head) {
1339
+ return failure(`Stale notebook revision: expected ${args.expected_revision}`);
1340
+ }
1341
+ const before = engine.notebooks.read(notebookId);
1342
+ if (!before.ok)
1343
+ return failure(before.error.message);
1344
+ const jobId = `relayout-notebook-${notebookId}-${Date.now()}`;
1345
+ const result = await queue.enqueueAndWait(jobId, {
1346
+ type: "relayout_notebook",
1347
+ bookId: String(args.book_id),
1348
+ notebookId,
1349
+ expectedRevision: args.expected_revision,
1350
+ });
1351
+ return json(result);
1352
+ }
1353
+ catch (cause) {
1354
+ return failure(cause instanceof Error ? cause.message : String(cause));
1355
+ }
1356
+ },
1357
+ },
1358
+ {
1359
+ name: "move_notebook_cells",
1360
+ description: "Persist notebook cell grid slots",
1361
+ scope: "book",
1362
+ recordsOwnActivity: true,
1363
+ schema: {
1364
+ ...bookSchema,
1365
+ notebook_id: z.string().uuid(),
1366
+ slots: z.array(notebookGridSlotSchema.extend({
1367
+ id: z.string().uuid(),
1368
+ })).min(1),
1369
+ expected_revision: z.string().min(1).optional(),
1370
+ },
1371
+ handler: async (args) => {
1372
+ const requested = args.slots;
1373
+ try {
1374
+ const result = await mutateNotebook(engine, String(args.notebook_id), "move_notebook_cells", (notebook) => {
1375
+ if (typeof args.expected_revision === "string"
1376
+ && args.expected_revision !== engine.head) {
1377
+ throw new Error(`Stale notebook revision: expected ${args.expected_revision}`);
1378
+ }
1379
+ const slots = new Map(requested.map((slot) => [slot.id, slot]));
1380
+ if (slots.size !== requested.length) {
1381
+ throw new Error("Each moved notebook cell must appear once");
1382
+ }
1383
+ const cellsById = new Map(notebook.cells.map((cell) => [cell.id, cell]));
1384
+ for (const id of slots.keys()) {
1385
+ if (!cellsById.has(id))
1386
+ throw new Error(`Cell not found: ${id}`);
1387
+ }
1388
+ if (requested.length === 1) {
1389
+ const destination = requested[0];
1390
+ const source = cellsById.get(destination.id);
1391
+ const footprint = new Set(notebookCellOccupiedSlots({ type: source.type, slot: destination })
1392
+ .map((slot) => `${slot.row}:${slot.column}`));
1393
+ const occupied = notebook.cells.find((cell) => cell.id !== source.id
1394
+ && notebookCellOccupiedSlots(cell).some((slot) => footprint.has(`${slot.row}:${slot.column}`)));
1395
+ if (occupied && (source.type === "timeline" || occupied.type === "timeline")) {
1396
+ throw new Error("Choose an empty row for the timeline and keep its occupied row free");
1397
+ }
1398
+ const moved = {
1399
+ ...notebook,
1400
+ cells: notebook.cells.map((cell) => {
1401
+ if (cell.id === source.id) {
1402
+ return {
1403
+ ...cell,
1404
+ slot: { row: destination.row, column: destination.column },
1405
+ };
1406
+ }
1407
+ return cell.id === occupied?.id
1408
+ ? { ...cell, slot: source.slot }
1409
+ : cell;
1410
+ }),
1411
+ };
1412
+ return refreshInitialGenerationPrompts(moved);
1413
+ }
1414
+ const targetKeys = new Set();
1415
+ for (const slot of requested) {
1416
+ const key = `${slot.row}:${slot.column}`;
1417
+ if (targetKeys.has(key)) {
1418
+ throw new Error(`Notebook grid square ${notebookGridTag(slot)} is targeted twice`);
1419
+ }
1420
+ targetKeys.add(key);
1421
+ }
1422
+ const movingIds = new Set(slots.keys());
1423
+ const collision = notebook.cells.find((cell) => !movingIds.has(cell.id)
1424
+ && targetKeys.has(`${cell.slot.row}:${cell.slot.column}`));
1425
+ if (collision) {
1426
+ throw new Error(`Notebook grid square ${notebookGridTag(collision.slot)} is occupied`);
1427
+ }
1428
+ const moved = {
1429
+ ...notebook,
1430
+ cells: notebook.cells.map((cell) => {
1431
+ const slot = slots.get(cell.id);
1432
+ return slot
1433
+ ? { ...cell, slot: { row: slot.row, column: slot.column } }
1434
+ : cell;
1435
+ }),
1436
+ };
1437
+ return refreshInitialGenerationPrompts(moved);
1438
+ }, { cellIds: requested.map((slot) => slot.id) });
1439
+ return result.ok
1440
+ ? json({ notebook_id: args.notebook_id, revision: result.value.hash })
1441
+ : failure(result.error.message);
1442
+ }
1443
+ catch (cause) {
1444
+ return failure(cause instanceof Error ? cause.message : String(cause));
1445
+ }
1446
+ },
1447
+ },
1448
+ {
1449
+ name: "delete_notebook_cell",
1450
+ description: "Delete one notebook cell and incident edges",
1451
+ scope: "book",
1452
+ schema: {
1453
+ ...bookSchema,
1454
+ notebook_id: z.string().uuid(),
1455
+ cell_id: z.string().uuid(),
1456
+ expected_revision: z.string().min(1).optional(),
1457
+ },
1458
+ handler: async (args) => {
1459
+ if (typeof args.expected_revision === "string"
1460
+ && args.expected_revision !== engine.head) {
1461
+ return failure(`Stale notebook revision: expected ${args.expected_revision}`);
1462
+ }
1463
+ const notebookId = String(args.notebook_id);
1464
+ const cellId = String(args.cell_id);
1465
+ try {
1466
+ const result = await withNotebookMutation(notebookId, async () => {
1467
+ if (typeof args.expected_revision === "string"
1468
+ && args.expected_revision !== engine.head) {
1469
+ throw new Error(`Stale notebook revision: expected ${args.expected_revision}`);
1470
+ }
1471
+ return await engine.notebooks.removeCell(notebookId, cellId);
1472
+ });
1473
+ return result.ok ? json({ cell_id: cellId, revision: result.value.hash }) : failure(result.error.message);
1474
+ }
1475
+ catch (cause) {
1476
+ return failure(cause instanceof Error ? cause.message : String(cause));
1477
+ }
1478
+ },
1479
+ },
1480
+ {
1481
+ name: "connect_notebook_cells",
1482
+ description: "Create a UUIDv7 notebook edge",
1483
+ scope: "book",
1484
+ schema: {
1485
+ ...bookSchema,
1486
+ notebook_id: z.string().uuid(),
1487
+ source_cell_id: z.string().uuid(),
1488
+ target_cell_id: z.string().uuid(),
1489
+ target_input: z.string().min(1),
1490
+ expected_revision: z.string().min(1).optional(),
1491
+ },
1492
+ handler: async (args) => {
1493
+ const edge = engine.notebooks.createEdge({
1494
+ source: String(args.source_cell_id),
1495
+ target: String(args.target_cell_id),
1496
+ targetInput: String(args.target_input),
1497
+ });
1498
+ try {
1499
+ const result = await mutateNotebook(engine, String(args.notebook_id), "connect_notebook_cells", (notebook) => {
1500
+ if (typeof args.expected_revision === "string"
1501
+ && args.expected_revision !== engine.head) {
1502
+ throw new Error(`Stale notebook revision: expected ${args.expected_revision}`);
1503
+ }
1504
+ const prepared = prepareNotebookConnection(engine, notebook, edge.source, edge.target, edge.targetInput);
1505
+ return refreshGenerationInputPrompt(notebook, {
1506
+ ...prepared,
1507
+ edges: [...prepared.edges, edge],
1508
+ }, edge.target);
1509
+ }, { sourceCellId: edge.source, targetCellId: edge.target, edgeId: edge.id });
1510
+ return result.ok ? json(edge) : failure(result.error.message);
1511
+ }
1512
+ catch (cause) {
1513
+ return failure(cause instanceof Error ? cause.message : String(cause));
1514
+ }
1515
+ },
1516
+ },
1517
+ {
1518
+ name: "disconnect_notebook_cells",
1519
+ description: "Delete a notebook edge",
1520
+ scope: "book",
1521
+ schema: { ...bookSchema, notebook_id: z.string().uuid(), edge_id: z.string().uuid() },
1522
+ handler: async (args) => {
1523
+ const edgeId = String(args.edge_id);
1524
+ const result = await mutateNotebook(engine, String(args.notebook_id), "disconnect_notebook_cells", (notebook) => {
1525
+ const removed = notebook.edges.find((edge) => edge.id === edgeId);
1526
+ if (removed && notebook.cells.some((cell) => cell.id === removed.target && cell.type === "post"))
1527
+ throw new Error("A Post keeps its frozen source connection");
1528
+ const disconnected = {
1529
+ ...notebook,
1530
+ edges: notebook.edges.filter((edge) => edge.id !== edgeId),
1531
+ };
1532
+ return removed
1533
+ ? refreshGenerationInputPrompt(notebook, disconnected, removed.target)
1534
+ : disconnected;
1535
+ }, { edgeId });
1536
+ return result.ok ? json({ edge_id: edgeId, revision: result.value.hash }) : failure(result.error.message);
1537
+ },
1538
+ },
1539
+ {
1540
+ name: "set_notebook_cell_input",
1541
+ description: "Atomically replace or clear one named notebook cell input",
1542
+ scope: "book",
1543
+ schema: {
1544
+ ...bookSchema,
1545
+ notebook_id: z.string().uuid(),
1546
+ target_cell_id: z.string().uuid(),
1547
+ target_input: z.string().min(1),
1548
+ source_cell_id: z.string().uuid().nullable(),
1549
+ expected_revision: z.string().min(1).optional(),
1550
+ },
1551
+ handler: async (args) => {
1552
+ if (typeof args.expected_revision === "string"
1553
+ && args.expected_revision !== engine.head) {
1554
+ return failure(`Stale notebook revision: expected ${args.expected_revision}`);
1555
+ }
1556
+ const targetCellId = String(args.target_cell_id);
1557
+ const targetInput = String(args.target_input);
1558
+ const sourceCellId = typeof args.source_cell_id === "string"
1559
+ ? args.source_cell_id
1560
+ : null;
1561
+ let edgeId = null;
1562
+ try {
1563
+ const result = await mutateNotebook(engine, String(args.notebook_id), "set_notebook_cell_input", (notebook) => {
1564
+ if (typeof args.expected_revision === "string"
1565
+ && args.expected_revision !== engine.head) {
1566
+ throw new Error(`Stale notebook revision: expected ${args.expected_revision}`);
1567
+ }
1568
+ const target = notebook.cells.find((cell) => cell.id === targetCellId);
1569
+ if (!target) {
1570
+ throw new Error(`Target cell not found: ${targetCellId}`);
1571
+ }
1572
+ if (target.type === "post")
1573
+ throw new Error("Use update_post to choose a Post source");
1574
+ const existing = notebook.edges.filter((edge) => edge.target === targetCellId
1575
+ && edge.targetInput === targetInput);
1576
+ const retained = notebook.edges.filter((edge) => edge.target !== targetCellId
1577
+ || edge.targetInput !== targetInput);
1578
+ let edges = retained;
1579
+ if (sourceCellId) {
1580
+ const prepared = prepareNotebookConnection(engine, { ...notebook, edges: retained }, sourceCellId, targetCellId, targetInput);
1581
+ const matching = existing.find((edge) => edge.source === sourceCellId);
1582
+ const edge = matching ?? engine.notebooks.createEdge({
1583
+ source: sourceCellId,
1584
+ target: targetCellId,
1585
+ targetInput,
1586
+ });
1587
+ edgeId = edge.id;
1588
+ edges = [...prepared.edges, edge];
1589
+ }
1590
+ const updated = { ...notebook, edges };
1591
+ const marked = markCellAndDescendantsStale(updated, targetCellId);
1592
+ return refreshGenerationInputPrompt(notebook, marked, targetCellId);
1593
+ }, {
1594
+ targetCellId,
1595
+ targetInput,
1596
+ sourceCellId,
1597
+ edgeId,
1598
+ });
1599
+ return result.ok
1600
+ ? json({
1601
+ target_cell_id: targetCellId,
1602
+ target_input: targetInput,
1603
+ source_cell_id: sourceCellId,
1604
+ edge_id: edgeId,
1605
+ revision: result.value.hash,
1606
+ })
1607
+ : failure(result.error.message);
1608
+ }
1609
+ catch (cause) {
1610
+ return failure(cause instanceof Error ? cause.message : String(cause));
1611
+ }
1612
+ },
1613
+ },
1614
+ {
1615
+ name: "preview_notebook_generation",
1616
+ description: "Preview the exact provider payload a generation cell would submit, without running it",
1617
+ scope: "book",
1618
+ schema: {
1619
+ ...bookSchema,
1620
+ notebook_id: z.string().uuid(),
1621
+ cell_id: z.string().uuid(),
1622
+ draft: z.object({
1623
+ type: cellTypeSchema.optional(),
1624
+ label: z.string().optional(),
1625
+ slot: notebookGridSlotSchema.optional(),
1626
+ prompt: z.string(),
1627
+ provider: z.string().min(1),
1628
+ model: z.string().min(1),
1629
+ operation: z.string().min(1),
1630
+ tool: z.string().min(1),
1631
+ inputs: z.record(z.string(), z.unknown()).optional(),
1632
+ }).optional(),
1633
+ connections: z.array(z.object({
1634
+ source_cell_id: z.string().uuid(),
1635
+ target_input: z.string().min(1),
1636
+ })).optional(),
1637
+ },
1638
+ handler: async (args) => {
1639
+ const notebook = engine.notebooks.read(String(args.notebook_id));
1640
+ if (!notebook.ok)
1641
+ return failure(notebook.error.message);
1642
+ try {
1643
+ const draft = args.draft;
1644
+ const cellId = String(args.cell_id);
1645
+ const cell = notebook.value.cells.find((candidate) => candidate.id === cellId);
1646
+ if (!cell && (!draft?.type || !draft.slot)) {
1647
+ return failure("Notebook cell not found");
1648
+ }
1649
+ const baseCell = cell ?? {
1650
+ id: cellId,
1651
+ type: draft.type,
1652
+ slot: draft.slot,
1653
+ };
1654
+ const previewCell = draft
1655
+ ? { ...baseCell, ...draft, id: cellId }
1656
+ : baseCell;
1657
+ const connections = args.connections;
1658
+ const previewWithCell = {
1659
+ ...notebook.value,
1660
+ cells: [
1661
+ ...notebook.value.cells.filter((candidate) => candidate.id !== cellId),
1662
+ previewCell,
1663
+ ],
1664
+ };
1665
+ let previewNotebook = connections
1666
+ ? {
1667
+ ...previewWithCell,
1668
+ edges: notebook.value.edges.filter((edge) => edge.target !== cellId),
1669
+ }
1670
+ : previewWithCell;
1671
+ for (const connection of connections ?? []) {
1672
+ previewNotebook = prepareNotebookConnection(engine, previewNotebook, connection.source_cell_id, cellId, connection.target_input);
1673
+ previewNotebook = {
1674
+ ...previewNotebook,
1675
+ edges: [...previewNotebook.edges, engine.notebooks.createEdge({
1676
+ source: connection.source_cell_id,
1677
+ target: cellId,
1678
+ targetInput: connection.target_input,
1679
+ })],
1680
+ };
1681
+ }
1682
+ const validation = validateCellConfiguration(previewCell, previewNotebook);
1683
+ if (!validation.ok)
1684
+ return failure(validation.message);
1685
+ const prepared = await prepareNotebookGeneration(engine, previewNotebook, previewCell, String(args.book_id), { allowReferenceIssues: true });
1686
+ const promptLeaks = [
1687
+ ...prepared.mentions.unresolved.map((reference) => ({
1688
+ token: reference.mention,
1689
+ code: reference.code,
1690
+ message: reference.message,
1691
+ })),
1692
+ ...prepared.mentions.unconnected.map((reference) => ({
1693
+ token: reference.mention,
1694
+ code: "not_connected",
1695
+ message: reference.message,
1696
+ })),
1697
+ ];
1698
+ const mediaPreview = promptLeaks.length === 0 && !prepared.imageEdit
1699
+ ? previewGeneration(prepared.input)
1700
+ : null;
1701
+ if (mediaPreview && !mediaPreview.ok)
1702
+ return failure(mediaPreview.error);
1703
+ const translated = promptLeaks.length === 0
1704
+ ? prepared.imageEdit
1705
+ ? { payload: prepared.imageEdit.providerPayload, provider_path: undefined }
1706
+ : mediaPreview?.ok
1707
+ ? { payload: mediaPreview.preview.payload, provider_path: mediaPreview.preview.providerPath }
1708
+ : null
1709
+ : null;
1710
+ const previewPayload = mediaPreview?.ok
1711
+ ? { payload: mediaPreview.preview.payload, placeholders: mediaPreview.preview.placeholders }
1712
+ : translated
1713
+ ? applyPreviewPlaceholderUrls(translated.payload)
1714
+ : null;
1715
+ const costResult = await estimateCostDef(engine).handler({
1716
+ calls: [{
1717
+ tool_name: prepared.tool,
1718
+ input: prepared.input,
1719
+ }],
1720
+ });
1721
+ const costText = costResult.content.find((item) => item.type === "text")?.text;
1722
+ const costValue = !costResult.isError && costText
1723
+ ? JSON.parse(costText)
1724
+ : {};
1725
+ return json({
1726
+ tool: prepared.tool,
1727
+ provider: prepared.provider,
1728
+ model: prepared.model,
1729
+ ...(translated?.provider_path
1730
+ ? { provider_path: translated.provider_path }
1731
+ : {}),
1732
+ payload: promptLeaks.length > 0
1733
+ ? null
1734
+ : previewPayload?.payload ?? prepared.input,
1735
+ estimate: costValue.estimate ?? null,
1736
+ mentions: {
1737
+ prompt_leaks: promptLeaks,
1738
+ url_placeholders: previewPayload?.placeholders ?? [],
1739
+ },
1740
+ });
1741
+ }
1742
+ catch (cause) {
1743
+ return failure(cause instanceof Error ? cause.message : String(cause));
1744
+ }
1745
+ },
1746
+ },
1747
+ {
1748
+ name: "run_notebook_cell",
1749
+ description: "Run one selected notebook cell using saved upstream media. retry_failed_cell_id replaces an output that failed only because provider credits ran out.",
1750
+ scope: "book",
1751
+ schema: {
1752
+ ...bookSchema,
1753
+ notebook_id: z.string().uuid(),
1754
+ cell_id: z.string().uuid(),
1755
+ retry_failed_cell_id: z.string().uuid().optional().describe("Delete this out-of-credits output when reserving its replacement; requires a generation plan"),
1756
+ plan: z.record(z.string(), z.unknown()).optional(),
1757
+ actor: z.string().min(1).optional(),
1758
+ expected_revision: z.string().min(1).optional(),
1759
+ },
1760
+ handler: async (args) => {
1761
+ if (typeof args.expected_revision === "string"
1762
+ && args.expected_revision !== engine.head) {
1763
+ return failure(`Stale notebook revision: expected ${args.expected_revision}`);
1764
+ }
1765
+ const notebook = engine.notebooks.read(String(args.notebook_id));
1766
+ if (!notebook.ok)
1767
+ return failure(notebook.error.message);
1768
+ const cell = notebook.value.cells.find((candidate) => candidate.id === String(args.cell_id));
1769
+ if (!cell)
1770
+ return failure("Notebook cell not found");
1771
+ if (!notebookCellIsRunnable(cell)) {
1772
+ return failure("Media cells are passive outputs and cannot be run");
1773
+ }
1774
+ const requestedPlan = args.plan;
1775
+ let generationPlan;
1776
+ let generationPlanRecord;
1777
+ if (requestedPlan) {
1778
+ const record = notebookGenerationPlans(notebook.value).find((candidate) => candidate.planId === requestedPlan.planId
1779
+ && candidate.cellId === cell.id);
1780
+ if (!record)
1781
+ return failure("Generation plan is not preserved in this notebook");
1782
+ if (record.status !== "planned") {
1783
+ return failure(`Generation plan is already ${record.status}`);
1784
+ }
1785
+ const expected = record.plan.cellDefinitionFingerprint
1786
+ ?? record.plan.cellFingerprint;
1787
+ const actual = notebookCellDefinitionFingerprint(notebook.value, cell);
1788
+ if (expected !== actual) {
1789
+ return failure("Generation plan is stale for the selected cell");
1790
+ }
1791
+ generationPlan = record.plan;
1792
+ generationPlanRecord = record;
1793
+ }
1794
+ if (notebookCellIsPaid(cell) && !generationPlan) {
1795
+ return failure("Paid notebook cells require a persisted generation plan");
1796
+ }
1797
+ if (args.retry_failed_cell_id && !generationPlan) {
1798
+ return failure("Retrying an out-of-credits output requires a persisted generation plan");
1799
+ }
1800
+ const runId = uuidv7();
1801
+ const jobId = `run-notebook-${String(args.notebook_id)}-${String(args.cell_id)}-${runId}`;
1802
+ try {
1803
+ if (generationPlanRecord) {
1804
+ generationPlan = await claimNotebookGenerationPlan(engine, String(args.notebook_id), String(args.cell_id), generationPlanRecord.planId, runId, typeof args.retry_failed_cell_id === "string" ? args.retry_failed_cell_id : undefined);
1805
+ }
1806
+ else {
1807
+ await markNotebookCellQueued(engine, String(args.notebook_id), String(args.cell_id), runId);
1808
+ }
1809
+ const outputCellId = generationPlan
1810
+ ? generationPlanClaimOutputCellId(generationPlan)
1811
+ : null;
1812
+ await queue.enqueue(jobId, {
1813
+ type: "run_notebook",
1814
+ bookId: String(args.book_id),
1815
+ notebookId: String(args.notebook_id),
1816
+ cellIds: [String(args.cell_id)],
1817
+ force: true,
1818
+ runId,
1819
+ ...(outputCellId ? { outputCellId } : {}),
1820
+ ...(generationPlan
1821
+ ? {
1822
+ generationPlan,
1823
+ actor: typeof args.actor === "string" ? args.actor : "videobook-user",
1824
+ jobId,
1825
+ }
1826
+ : {}),
1827
+ });
1828
+ }
1829
+ catch (cause) {
1830
+ const message = cause instanceof Error ? cause.message : String(cause);
1831
+ const generationPlanId = typeof generationPlan?.planId === "string"
1832
+ ? generationPlan.planId
1833
+ : undefined;
1834
+ await failQueuedNotebookCells(engine, String(args.notebook_id), {
1835
+ cellIds: [String(args.cell_id)],
1836
+ runId,
1837
+ error: message,
1838
+ ...(generationPlanId ? { generationPlanId } : {}),
1839
+ }).catch(() => undefined);
1840
+ return failure(message);
1841
+ }
1842
+ const outputCellId = generationPlan
1843
+ ? generationPlanClaimOutputCellId(generationPlan)
1844
+ : null;
1845
+ return json({
1846
+ job_id: jobId,
1847
+ run_id: runId,
1848
+ ...(generationPlan ? { plan_id: generationPlan.planId } : {}),
1849
+ ...(outputCellId ? { output_cell_id: outputCellId } : {}),
1850
+ });
1851
+ },
1852
+ },
1853
+ {
1854
+ name: "run_notebook",
1855
+ description: "Run missing, stale, or explicitly selected notebook cells topologically",
1856
+ scope: "book",
1857
+ schema: {
1858
+ ...bookSchema,
1859
+ notebook_id: z.string().uuid(),
1860
+ cell_ids: z.array(z.string().uuid()).min(1).max(100).optional(),
1861
+ actor: z.string().min(1).optional(),
1862
+ },
1863
+ handler: async (args) => {
1864
+ const cellIds = Array.isArray(args.cell_ids)
1865
+ ? [...new Set(args.cell_ids.map(String))]
1866
+ : undefined;
1867
+ if (cellIds) {
1868
+ const notebook = engine.notebooks.read(String(args.notebook_id));
1869
+ if (!notebook.ok)
1870
+ return failure(notebook.error.message);
1871
+ for (const cellId of cellIds) {
1872
+ const cell = notebook.value.cells.find((candidate) => candidate.id === cellId);
1873
+ if (!cell)
1874
+ return failure(`Notebook cell not found: ${cellId}`);
1875
+ if (!notebookCellIsRunnable(cell)) {
1876
+ return failure(`Notebook cell ${notebookGridTag(cell.slot)} is not runnable`);
1877
+ }
1878
+ if (notebookCellIsPaid(cell)) {
1879
+ return failure(`Paid notebook cell ${notebookGridTag(cell.slot)} requires its own persisted generation plan`);
1880
+ }
1881
+ }
1882
+ }
1883
+ const runId = uuidv7();
1884
+ const jobId = `run-notebook-${String(args.notebook_id)}-${runId}`;
1885
+ try {
1886
+ if (cellIds) {
1887
+ for (const cellId of cellIds) {
1888
+ await markNotebookCellQueued(engine, String(args.notebook_id), cellId, runId);
1889
+ }
1890
+ }
1891
+ await queue.enqueue(jobId, {
1892
+ type: "run_notebook",
1893
+ bookId: String(args.book_id),
1894
+ notebookId: String(args.notebook_id),
1895
+ runId,
1896
+ force: cellIds !== undefined,
1897
+ ...(cellIds ? { cellIds } : {}),
1898
+ ...(typeof args.actor === "string" ? { actor: args.actor } : {}),
1899
+ });
1900
+ }
1901
+ catch (cause) {
1902
+ const message = cause instanceof Error ? cause.message : String(cause);
1903
+ await failQueuedNotebookCells(engine, String(args.notebook_id), {
1904
+ cellIds: cellIds ?? [],
1905
+ runId,
1906
+ error: message,
1907
+ }).catch(() => undefined);
1908
+ return failure(message);
1909
+ }
1910
+ return json({
1911
+ job_id: jobId,
1912
+ run_id: runId,
1913
+ ...(cellIds ? { cell_ids: cellIds } : {}),
1914
+ });
1915
+ },
1916
+ },
1917
+ {
1918
+ name: "list_jobs",
1919
+ description: "List persistent jobs for a book. Filter by numeric job_ids, or by job_keys, the job_id strings other tools return while the server that queued them is running.",
1920
+ scope: "book",
1921
+ schema: {
1922
+ ...bookSchema,
1923
+ limit: z.number().int().positive().max(500).optional(),
1924
+ job_ids: z.array(z.number().int().positive()).min(1).max(100).optional(),
1925
+ job_keys: z.array(z.string().min(1).max(500)).min(1).max(100).optional(),
1926
+ },
1927
+ handler: async (args) => {
1928
+ const jobs = selectJobs(engine, args);
1929
+ return json(await Promise.all(jobs.map(async ({ job, jobKey }) => ({
1930
+ ...job, ...(jobKey ? { jobKey } : {}), providerTask: await jobProviderTaskStatus(engine, job),
1931
+ }))));
1932
+ },
1933
+ },
1934
+ {
1935
+ name: "cancel_job",
1936
+ description: "Cancel a queued or running persistent job",
1937
+ scope: "book",
1938
+ schema: {
1939
+ ...bookSchema,
1940
+ job_id: z.number().int().positive(),
1941
+ reason: z.string().min(1).max(500).optional(),
1942
+ },
1943
+ handler: async (args) => {
1944
+ const jobId = Number(args.job_id);
1945
+ const job = engine.jobs.queue.get(jobId);
1946
+ if (!job)
1947
+ return failure(`Job ${jobId} was not found`);
1948
+ const cancelled = await engine.jobs.queue.abort(jobId, typeof args.reason === "string" ? args.reason : "Cancelled by user");
1949
+ if (!cancelled) {
1950
+ return failure(`Job ${jobId} is already ${job.state}`);
1951
+ }
1952
+ return json(engine.jobs.queue.get(jobId));
1953
+ },
1954
+ },
1955
+ {
1956
+ name: "retry_job",
1957
+ description: "Re-enqueue a failed or cancelled persistent job with the same durable payload",
1958
+ scope: "book",
1959
+ schema: {
1960
+ ...bookSchema,
1961
+ job_id: z.number().int().positive(),
1962
+ },
1963
+ handler: async (args) => {
1964
+ const jobId = Number(args.job_id);
1965
+ const job = engine.jobs.queue.get(jobId);
1966
+ if (!job)
1967
+ return failure(`Job ${jobId} was not found`);
1968
+ if (job.state !== "failed" && job.state !== "aborted") {
1969
+ return failure(`Job ${jobId} is ${job.state}, not retryable`);
1970
+ }
1971
+ const retryId = `retry-${job.type}-${uuidv7()}`;
1972
+ await queue.enqueue(retryId, {
1973
+ ...job.payload,
1974
+ type: job.type,
1975
+ bookId: engine.book.get().bookId,
1976
+ });
1977
+ return json({ status: "enqueued", retry_job_id: retryId, source_job_id: jobId });
1978
+ },
1979
+ },
1980
+ {
1981
+ name: "list_book_actions",
1982
+ description: "List authored semantic revisions for a book",
1983
+ scope: "book",
1984
+ schema: { ...bookSchema, limit: z.number().int().positive().max(10_000).optional(), cursor: z.string().optional() },
1985
+ handler: async (args) => {
1986
+ const limit = typeof args.limit === "number" ? args.limit : 100;
1987
+ return json({
1988
+ actions: engine.history.revisions(limit),
1989
+ nextCursor: null,
1990
+ });
1991
+ },
1992
+ },
1993
+ {
1994
+ name: "get_history_action",
1995
+ description: "Read one authored semantic revision",
1996
+ scope: "book",
1997
+ schema: { ...bookSchema, action_id: z.string().min(1) },
1998
+ handler: async (args) => {
1999
+ const revision = engine.history.resolveRevision(String(args.action_id));
2000
+ return revision
2001
+ ? json(revision)
2002
+ : failure(`Revision not found: ${String(args.action_id)}`);
2003
+ },
2004
+ },
2005
+ {
2006
+ name: "storage_status",
2007
+ description: "Read catalog and object publication status",
2008
+ scope: "book",
2009
+ schema: bookSchema,
2010
+ handler: async () => json(engine.storage.status()),
2011
+ },
2012
+ {
2013
+ name: "storage_sync",
2014
+ description: "Publish a book's pending content-addressed objects and catalog",
2015
+ scope: "book",
2016
+ schema: bookSchema,
2017
+ handler: async () => json(await engine.storage.backup()),
2018
+ },
2019
+ ...entityDefs(engine),
2020
+ ];
2021
+ }