@gentbajko/slopify 2.4.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (469) hide show
  1. package/LICENSE +201 -21
  2. package/NOTICE +12 -0
  3. package/README.md +21 -40
  4. package/SUBTITLES.md +1 -1
  5. package/dist/adapter-registry.js +11 -1
  6. package/dist/adapters/alignment/cache.js +4 -1
  7. package/dist/adapters/alignment/ctc.js +25 -23
  8. package/dist/adapters/alignment/index.js +37 -6
  9. package/dist/adapters/alignment/multilingual.js +267 -0
  10. package/dist/adapters/alignment/protocol.js +11 -0
  11. package/dist/adapters/alignment/quality.js +4 -3
  12. package/dist/adapters/alignment/sentences.js +95 -0
  13. package/dist/adapters/alignment/spec.js +24 -0
  14. package/dist/adapters/alignment/spell.js +769 -0
  15. package/dist/adapters/alignment/text.js +56 -9
  16. package/dist/adapters/alignment/window.js +34 -30
  17. package/dist/adapters/alignment/worker.js +23 -21
  18. package/dist/adapters/explain.js +11 -11
  19. package/dist/adapters/host-cli/index.js +55 -5
  20. package/dist/adapters/host-cli/transport.js +13 -6
  21. package/dist/adapters/image/bytes.js +2 -2
  22. package/dist/adapters/image/codex-output.js +60 -18
  23. package/dist/adapters/image/codex.js +166 -25
  24. package/dist/adapters/image/fal.js +31 -3
  25. package/dist/adapters/image/google.js +17 -2
  26. package/dist/adapters/image/openai.js +45 -16
  27. package/dist/adapters/image/reference.js +41 -0
  28. package/dist/adapters/image/replicate.js +14 -2
  29. package/dist/adapters/image/video.js +2 -2
  30. package/dist/adapters/key-probes.js +36 -0
  31. package/dist/adapters/llm/claude-code.js +168 -32
  32. package/dist/adapters/llm/cli-login-error.js +1 -1
  33. package/dist/adapters/llm/codex-limits.js +157 -0
  34. package/dist/adapters/llm/codex-models.js +2 -1
  35. package/dist/adapters/llm/codex-server-models.js +2 -1
  36. package/dist/adapters/llm/codex.js +99 -12
  37. package/dist/adapters/llm/document-workspace.js +2 -2
  38. package/dist/adapters/llm/gemini-workspace.js +1 -1
  39. package/dist/adapters/llm/gemini.js +16 -3
  40. package/dist/adapters/llm/image-workspace.js +58 -0
  41. package/dist/adapters/llm/openrouter.js +11 -3
  42. package/dist/adapters/llm/run-cli.js +26 -9
  43. package/dist/adapters/tts/cartesia.js +10 -0
  44. package/dist/adapters/tts/elevenlabs.js +31 -9
  45. package/dist/adapters/tts/inworld-async.js +9 -5
  46. package/dist/adapters/tts/inworld.js +17 -3
  47. package/dist/adapters/tts/openai.js +3 -0
  48. package/dist/adapters/tts/voice-languages.js +80 -0
  49. package/dist/assets/fonts/noto/NotoSans-Regular.ttf +0 -0
  50. package/dist/assets/fonts/noto/NotoSansArabic-Regular.ttf +0 -0
  51. package/dist/assets/fonts/noto/NotoSansDevanagari-Regular.ttf +0 -0
  52. package/dist/assets/fonts/noto/NotoSansHebrew-Regular.ttf +0 -0
  53. package/dist/assets/fonts/noto/NotoSansThai-Regular.ttf +0 -0
  54. package/dist/assets/fonts/noto/OFL.txt +93 -0
  55. package/dist/assets/fonts/noto/SOURCE.txt +18 -0
  56. package/dist/assets/models.yaml +7 -0
  57. package/dist/assets/sample/sample-audiobook.tar +0 -0
  58. package/dist/assets/sample/sample-podcast.tar +0 -0
  59. package/dist/assets/sample/sample-project.tar +0 -0
  60. package/dist/catalog/merge.js +132 -0
  61. package/dist/catalog/registry.js +33 -12
  62. package/dist/catalog/runtime-models.js +1 -1
  63. package/dist/catalog/schema.js +12 -1
  64. package/dist/catalog/store.js +147 -8
  65. package/dist/catalog/validate.js +17 -2
  66. package/dist/compose.yaml +71 -0
  67. package/dist/edge/autostart/docker-record.js +32 -0
  68. package/dist/edge/autostart/docker.js +50 -0
  69. package/dist/edge/autostart/index.js +78 -0
  70. package/dist/edge/autostart/launcher.js +176 -0
  71. package/dist/edge/autostart/model.js +3 -0
  72. package/dist/edge/autostart/native.js +210 -0
  73. package/dist/edge/autostart/prompt.js +66 -0
  74. package/dist/edge/autostart/service.js +81 -0
  75. package/dist/edge/cli.js +57 -51
  76. package/dist/edge/docker-install/activation.js +56 -0
  77. package/dist/edge/docker-install/apply.js +458 -0
  78. package/dist/edge/docker-install/engine.js +362 -0
  79. package/dist/edge/docker-install/run.js +154 -0
  80. package/dist/edge/docker-install/state.js +146 -0
  81. package/dist/edge/{docker-projects → docker-install}/volume.js +1 -5
  82. package/dist/edge/docker.js +14 -22
  83. package/dist/edge/events/hub.js +11 -0
  84. package/dist/edge/host-cli.js +1 -1
  85. package/dist/edge/http/actions.js +32 -10
  86. package/dist/edge/http/app.js +60 -2
  87. package/dist/edge/http/auditions.js +70 -0
  88. package/dist/edge/http/autostart.js +37 -0
  89. package/dist/edge/http/backups.js +40 -0
  90. package/dist/edge/http/channel-memory.js +92 -0
  91. package/dist/edge/http/channels.js +128 -0
  92. package/dist/edge/http/entries.js +22 -3
  93. package/dist/edge/http/files.js +1 -1
  94. package/dist/edge/http/folder-location.js +1 -1
  95. package/dist/edge/http/home.js +39 -0
  96. package/dist/edge/http/host-cli.js +45 -6
  97. package/dist/edge/http/narration-peaks.js +76 -0
  98. package/dist/edge/http/onboarding.js +152 -0
  99. package/dist/edge/http/open-folder.js +1 -1
  100. package/dist/edge/http/patch-notes.js +45 -0
  101. package/dist/edge/http/planning.js +4 -2
  102. package/dist/edge/http/problem.js +1 -0
  103. package/dist/edge/http/project-create.js +87 -0
  104. package/dist/edge/http/projects.js +31 -99
  105. package/dist/edge/http/prompts.js +40 -3
  106. package/dist/edge/http/pronunciations.js +23 -4
  107. package/dist/edge/http/providers.js +86 -0
  108. package/dist/edge/http/reviews.js +121 -0
  109. package/dist/edge/http/revision-files.js +1 -1
  110. package/dist/edge/http/revisions.js +1 -1
  111. package/dist/edge/http/run-cost.js +25 -0
  112. package/dist/edge/http/schedules.js +111 -6
  113. package/dist/edge/http/settings.js +128 -6
  114. package/dist/edge/http/storage-files.js +50 -0
  115. package/dist/edge/http/storage.js +29 -2
  116. package/dist/edge/http/studio.js +207 -0
  117. package/dist/edge/http/style-preview.js +100 -0
  118. package/dist/edge/http/subtitles.js +1 -1
  119. package/dist/edge/http/trash.js +69 -0
  120. package/dist/edge/http/update.js +24 -3
  121. package/dist/edge/http/whats-new.js +15 -0
  122. package/dist/edge/http/youtube-edits.js +83 -0
  123. package/dist/edge/native-update.js +62 -0
  124. package/dist/host-cli/open-folder.js +10 -10
  125. package/dist/host-cli/runtime.js +12 -3
  126. package/dist/kernel/db/migrations/0022-drop-duplicate-held-work.sql +56 -0
  127. package/dist/kernel/db/migrations/0024-reference-attachments.sql +18 -0
  128. package/dist/kernel/db/migrations/0025-automatic-reviews.sql +40 -0
  129. package/dist/kernel/db/migrations/0026-schedule-topic-generation.sql +23 -0
  130. package/dist/kernel/db/migrations/0027-run-cost.sql +58 -0
  131. package/dist/kernel/db/migrations/0030-automatic-retries.sql +18 -0
  132. package/dist/kernel/db/migrations/0031-prompt-history-and-description-edits.sql +33 -0
  133. package/dist/kernel/db/migrations/0032-channels.sql +65 -0
  134. package/dist/kernel/db/migrations/0034-script-prompts.sql +23 -0
  135. package/dist/kernel/db/migrations/0035-narration-aliases.sql +13 -0
  136. package/dist/kernel/db/migrations/0037-project-uploads.sql +8 -0
  137. package/dist/kernel/db/migrations/0038-voice-languages.sql +7 -0
  138. package/dist/kernel/db/migrations/0039-channel-essentials.sql +64 -0
  139. package/dist/kernel/db/migrations/0040-standalone-usage.sql +35 -0
  140. package/dist/kernel/paths.js +27 -6
  141. package/dist/kernel/pipeline.js +4 -1
  142. package/dist/kernel/ports/host-cli.js +139 -16
  143. package/dist/kernel/ports/image.js +3 -1
  144. package/dist/kernel/ports/key-probe.js +1 -0
  145. package/dist/kernel/ports/languages.js +157 -0
  146. package/dist/kernel/ports/llm.js +3 -1
  147. package/dist/kernel/ports/model.js +5 -2
  148. package/dist/kernel/ports/narration-aliases.js +60 -0
  149. package/dist/kernel/ports/plan-limits.js +36 -0
  150. package/dist/kernel/runner/attempt.js +18 -9
  151. package/dist/kernel/runner/audition.js +36 -0
  152. package/dist/kernel/runner/graph.js +24 -5
  153. package/dist/kernel/runner/index.js +35 -5
  154. package/dist/kernel/runner/meter.js +1 -0
  155. package/dist/kernel/runner/providers.js +164 -9
  156. package/dist/kernel/runner/retry-policy.js +43 -0
  157. package/dist/kernel/runner/standalone.js +134 -0
  158. package/dist/kernel/runner/work-authority.js +28 -2
  159. package/dist/main.js +321 -20
  160. package/dist/patch-notes/0.4.2-to-1.0.md +93 -0
  161. package/dist/patch-notes/1.0-to-2.0.md +74 -0
  162. package/dist/patch-notes/2.0-to-3.0.md +257 -0
  163. package/dist/patch-notes/3.0.0.md +70 -0
  164. package/dist/patch-notes/index.json +26 -0
  165. package/dist/slices/admission/model.js +35 -0
  166. package/dist/slices/admission/repo.js +24 -4
  167. package/dist/slices/admission/rules.js +126 -1
  168. package/dist/slices/admission/schema.js +68 -1
  169. package/dist/slices/admission/short-mode.js +47 -0
  170. package/dist/slices/admission/start.js +90 -16
  171. package/dist/slices/article/continuation.js +2 -2
  172. package/dist/slices/article/provided-entries.js +2 -2
  173. package/dist/slices/article/run.js +6 -2
  174. package/dist/slices/article/segments.js +3 -3
  175. package/dist/slices/backups/files.js +135 -0
  176. package/dist/slices/backups/folder.js +43 -0
  177. package/dist/slices/backups/model.js +65 -0
  178. package/dist/slices/backups/repo.js +28 -0
  179. package/dist/slices/backups/schedule.js +60 -0
  180. package/dist/slices/backups/service.js +311 -0
  181. package/dist/slices/batch/index.js +9 -2
  182. package/dist/slices/cancel/index.js +1 -1
  183. package/dist/slices/channels/cast-images.js +110 -0
  184. package/dist/slices/channels/cast-match.js +32 -0
  185. package/dist/slices/channels/images.js +19 -0
  186. package/dist/slices/channels/model.js +5 -0
  187. package/dist/slices/channels/rebrand.js +100 -0
  188. package/dist/slices/channels/repo.js +218 -0
  189. package/dist/slices/channels/runs.js +129 -0
  190. package/dist/slices/channels/schema.js +129 -0
  191. package/dist/slices/channels/service.js +180 -0
  192. package/dist/slices/channels/studio-csv.js +110 -0
  193. package/dist/slices/channels/videos.js +120 -0
  194. package/dist/slices/checkpoints/recovery.js +10 -0
  195. package/dist/slices/control/index.js +2 -1
  196. package/dist/slices/document/fonts.js +1 -1
  197. package/dist/slices/document/render.js +1 -1
  198. package/dist/slices/episodes/related.js +90 -0
  199. package/dist/slices/episodes/repo.js +60 -0
  200. package/dist/slices/episodes/service.js +58 -0
  201. package/dist/slices/episodes/summarize.js +170 -0
  202. package/dist/slices/estimate/index.js +113 -24
  203. package/dist/slices/estimate/requests.js +60 -2
  204. package/dist/slices/fixes/rules.js +73 -0
  205. package/dist/slices/fonts/coverage.js +82 -0
  206. package/dist/slices/fonts/sfnt.js +74 -0
  207. package/dist/slices/images/run.js +8 -5
  208. package/dist/slices/images/scale.js +84 -0
  209. package/dist/slices/library/diff.js +122 -0
  210. package/dist/slices/library/history.js +100 -0
  211. package/dist/slices/library/model.js +3 -0
  212. package/dist/slices/library/photorealistic.js +49 -0
  213. package/dist/slices/library/repo.js +19 -14
  214. package/dist/slices/library/save.js +62 -12
  215. package/dist/slices/library/slots.js +15 -3
  216. package/dist/slices/library/snapshot.js +8 -2
  217. package/dist/slices/library/used-by.js +153 -0
  218. package/dist/slices/loudness/level-pieces.js +41 -0
  219. package/dist/slices/loudness/loudnorm.js +164 -0
  220. package/dist/slices/loudness/model.js +153 -0
  221. package/dist/slices/model-upkeep/model.js +10 -0
  222. package/dist/slices/model-upkeep/switch.js +217 -0
  223. package/dist/slices/model-upkeep/usage.js +183 -0
  224. package/dist/slices/narration/aliases-library.js +31 -0
  225. package/dist/slices/narration/aliases-schema.js +53 -0
  226. package/dist/slices/narration/aliases.js +49 -0
  227. package/dist/slices/narration/blocks.js +352 -0
  228. package/dist/slices/narration/chunk.js +19 -7
  229. package/dist/slices/narration/concat.js +2 -2
  230. package/dist/slices/narration/describe.js +45 -0
  231. package/dist/slices/narration/pauses-model.js +63 -0
  232. package/dist/slices/narration/pauses.js +269 -0
  233. package/dist/slices/narration/peaks-model.js +1 -0
  234. package/dist/slices/narration/peaks.js +94 -0
  235. package/dist/slices/narration/preparation.js +21 -3
  236. package/dist/slices/narration/pronunciation-chunks.js +3 -3
  237. package/dist/slices/narration/pronunciation.js +72 -25
  238. package/dist/slices/narration/run.js +6 -6
  239. package/dist/slices/narration/shared-glossary.js +1 -1
  240. package/dist/slices/narration/steering.js +15 -4
  241. package/dist/slices/notifications/notifier.js +94 -0
  242. package/dist/slices/notifications/rules.js +102 -0
  243. package/dist/slices/notifications/send.js +40 -0
  244. package/dist/slices/notifications/settings.js +34 -0
  245. package/dist/slices/onboarding/copy.js +143 -0
  246. package/dist/slices/onboarding/first-run.js +46 -0
  247. package/dist/slices/onboarding/install.js +119 -0
  248. package/dist/slices/onboarding/model.js +64 -0
  249. package/dist/slices/onboarding/packs.js +491 -0
  250. package/dist/slices/onboarding/quick-short.js +129 -0
  251. package/dist/slices/onboarding/sample.js +132 -0
  252. package/dist/slices/onboarding/state.js +68 -0
  253. package/dist/slices/onboarding/template.js +73 -0
  254. package/dist/slices/patch-notes/library.js +73 -0
  255. package/dist/slices/patch-notes/seen.js +77 -0
  256. package/dist/slices/play-drafts/convert.js +160 -9
  257. package/dist/slices/play-drafts/readiness.js +3 -0
  258. package/dist/slices/play-drafts/repo.js +9 -1
  259. package/dist/slices/play-drafts/review-inputs.js +22 -3
  260. package/dist/slices/play-drafts/schema.js +109 -2
  261. package/dist/slices/play-drafts/service.js +10 -1
  262. package/dist/slices/play-drafts/start.js +43 -18
  263. package/dist/slices/play-drafts/uploads.js +2 -1
  264. package/dist/slices/project-templates/from-project.js +70 -4
  265. package/dist/slices/project-templates/one-off.js +26 -0
  266. package/dist/slices/project-templates/repo.js +2 -1
  267. package/dist/slices/project-templates/service.js +15 -4
  268. package/dist/slices/project-templates/setup.js +29 -2
  269. package/dist/slices/rebuild/admission-repo.js +1 -1
  270. package/dist/slices/rebuild/model.js +6 -0
  271. package/dist/slices/rebuild/preview-details.js +67 -2
  272. package/dist/slices/rebuild/preview-plan.js +13 -6
  273. package/dist/slices/rebuild/recipe-audio-parts.js +40 -7
  274. package/dist/slices/rebuild/recipe-audio.js +66 -12
  275. package/dist/slices/rebuild/recipe-build.js +15 -5
  276. package/dist/slices/rebuild/recipe-cast.js +18 -0
  277. package/dist/slices/rebuild/recipe-checkpoints.js +11 -9
  278. package/dist/slices/rebuild/recipe-describe.js +93 -0
  279. package/dist/slices/rebuild/recipe-edit.js +13 -1
  280. package/dist/slices/rebuild/recipe-exports.js +62 -8
  281. package/dist/slices/rebuild/recipe-input-schema.js +43 -1
  282. package/dist/slices/rebuild/recipe-legacy.js +12 -0
  283. package/dist/slices/rebuild/recipe-loudness.js +45 -0
  284. package/dist/slices/rebuild/recipe-model.js +25 -6
  285. package/dist/slices/rebuild/recipe-pauses.js +7 -0
  286. package/dist/slices/rebuild/recipe-preparation.js +3 -3
  287. package/dist/slices/rebuild/recipe-provider-choice.js +11 -1
  288. package/dist/slices/rebuild/recipe-reference.js +58 -0
  289. package/dist/slices/rebuild/recipe-reviews.js +117 -0
  290. package/dist/slices/rebuild/recipe-save.js +4 -1
  291. package/dist/slices/rebuild/recipe-shorts.js +32 -5
  292. package/dist/slices/rebuild/recipe-text.js +114 -19
  293. package/dist/slices/rebuild/recipe-validation.js +29 -3
  294. package/dist/slices/rebuild/recipe-visual.js +104 -29
  295. package/dist/slices/rebuild/recipe-voices.js +286 -0
  296. package/dist/slices/rebuild/recipe-work.js +77 -6
  297. package/dist/slices/rebuild/recipe-youtube.js +2 -0
  298. package/dist/slices/rebuild/recovery-conflict.js +7 -3
  299. package/dist/slices/rebuild/recovery-model.js +3 -0
  300. package/dist/slices/rebuild/recovery-selection.js +28 -2
  301. package/dist/slices/rebuild/recovery.js +35 -17
  302. package/dist/slices/rebuild/repo.js +3 -3
  303. package/dist/slices/rebuild/retry.js +18 -0
  304. package/dist/slices/rebuild/review-redo.js +110 -0
  305. package/dist/slices/rebuild/runtime-actions.js +14 -2
  306. package/dist/slices/rebuild/runtime-admission.js +4 -1
  307. package/dist/slices/rebuild/runtime-animate.js +4 -4
  308. package/dist/slices/rebuild/runtime-article.js +5 -5
  309. package/dist/slices/rebuild/runtime-document.js +4 -4
  310. package/dist/slices/rebuild/runtime-export-bed.js +43 -0
  311. package/dist/slices/rebuild/runtime-export-edit.js +70 -11
  312. package/dist/slices/rebuild/runtime-export-inputs.js +52 -10
  313. package/dist/slices/rebuild/runtime-export-short.js +85 -0
  314. package/dist/slices/rebuild/runtime-export.js +84 -19
  315. package/dist/slices/rebuild/runtime-figure-card.js +87 -0
  316. package/dist/slices/rebuild/runtime-image.js +69 -0
  317. package/dist/slices/rebuild/runtime-local.js +154 -27
  318. package/dist/slices/rebuild/runtime-materialize.js +8 -0
  319. package/dist/slices/rebuild/runtime-narration-reuse.js +2 -1
  320. package/dist/slices/rebuild/runtime-narration-text.js +7 -7
  321. package/dist/slices/rebuild/runtime-pictures.js +28 -0
  322. package/dist/slices/rebuild/runtime-piece-label.js +29 -0
  323. package/dist/slices/rebuild/runtime-provider.js +134 -19
  324. package/dist/slices/rebuild/runtime-publication.js +21 -5
  325. package/dist/slices/rebuild/runtime-review.js +237 -0
  326. package/dist/slices/rebuild/runtime-run.js +17 -11
  327. package/dist/slices/rebuild/runtime-shorts.js +48 -23
  328. package/dist/slices/rebuild/runtime-store.js +39 -10
  329. package/dist/slices/rebuild/runtime-subtitles.js +72 -19
  330. package/dist/slices/rebuild/runtime-voices.js +103 -0
  331. package/dist/slices/rebuild/runtime-youtube.js +7 -6
  332. package/dist/slices/rebuild/service-readiness.js +15 -1
  333. package/dist/slices/rebuild/soften.js +49 -0
  334. package/dist/slices/rebuild/transition-repo.js +10 -3
  335. package/dist/slices/research/run.js +4 -4
  336. package/dist/slices/research/synthesis.js +2 -2
  337. package/dist/slices/reviews/model.js +105 -0
  338. package/dist/slices/reviews/outcome.js +22 -0
  339. package/dist/slices/reviews/repo.js +106 -0
  340. package/dist/slices/reviews/rules.js +63 -0
  341. package/dist/slices/reviews/verdict.js +71 -0
  342. package/dist/slices/reviews/view.js +30 -0
  343. package/dist/slices/revisions/adopt-content.js +12 -2
  344. package/dist/slices/revisions/adopt.js +9 -2
  345. package/dist/slices/revisions/downloads.js +7 -1
  346. package/dist/slices/revisions/image-plan.js +183 -0
  347. package/dist/slices/revisions/mutation-assets.js +43 -9
  348. package/dist/slices/revisions/mutation-cues.js +71 -0
  349. package/dist/slices/revisions/mutation-prepare.js +8 -10
  350. package/dist/slices/revisions/mutation-request.js +2 -1
  351. package/dist/slices/revisions/mutations.js +97 -3
  352. package/dist/slices/revisions/schema.js +16 -2
  353. package/dist/slices/run-cost/limits.js +128 -0
  354. package/dist/slices/run-cost/meter.js +47 -0
  355. package/dist/slices/run-cost/panel.js +234 -0
  356. package/dist/slices/run-cost/pricing.js +92 -0
  357. package/dist/slices/run-cost/week.js +108 -0
  358. package/dist/slices/schedules/agenda.js +158 -0
  359. package/dist/slices/schedules/repo.js +58 -18
  360. package/dist/slices/schedules/scheduler.js +67 -16
  361. package/dist/slices/schedules/schema.js +138 -1
  362. package/dist/slices/schedules/service.js +48 -4
  363. package/dist/slices/schedules/similar.js +90 -0
  364. package/dist/slices/schedules/topic-list.js +163 -0
  365. package/dist/slices/schedules/topics.js +422 -0
  366. package/dist/slices/settings/first-run.js +102 -0
  367. package/dist/slices/settings/health.js +190 -0
  368. package/dist/slices/settings/key-guides.js +128 -0
  369. package/dist/slices/settings/key-test.js +85 -0
  370. package/dist/slices/settings/model.js +11 -1
  371. package/dist/slices/settings/playback.js +15 -0
  372. package/dist/slices/settings/repo.js +17 -3
  373. package/dist/slices/settings/voices.js +64 -4
  374. package/dist/slices/settings/whats-new.js +64 -0
  375. package/dist/slices/shorts/captions.js +9 -0
  376. package/dist/slices/shorts/pick.js +3 -3
  377. package/dist/slices/shorts/prompts.js +1 -1
  378. package/dist/slices/shorts/render.js +54 -5
  379. package/dist/slices/storage/asset-name.js +3 -0
  380. package/dist/slices/storage/backup-export.js +48 -9
  381. package/dist/slices/storage/backup-format.js +38 -1
  382. package/dist/slices/storage/backup-import.js +191 -19
  383. package/dist/slices/storage/delete-project.js +7 -4
  384. package/dist/slices/storage/documents.js +79 -0
  385. package/dist/slices/storage/files-location.js +531 -0
  386. package/dist/slices/storage/layout.js +20 -0
  387. package/dist/slices/storage/model.js +14 -0
  388. package/dist/slices/storage/portable.js +95 -12
  389. package/dist/slices/storage/reconcile.js +19 -2
  390. package/dist/slices/storage/repo.js +4 -1
  391. package/dist/slices/storage/schema.js +11 -0
  392. package/dist/slices/storage/trim.js +146 -0
  393. package/dist/slices/studio/disclosure.js +74 -0
  394. package/dist/slices/studio/model.js +28 -0
  395. package/dist/slices/studio/pack.js +209 -0
  396. package/dist/slices/studio/settings.js +133 -0
  397. package/dist/slices/style-preview/images.js +61 -0
  398. package/dist/slices/style-preview/render.js +161 -0
  399. package/dist/slices/style-preview/schema.js +45 -0
  400. package/dist/slices/style-preview/service.js +93 -0
  401. package/dist/slices/style-preview/settings.js +89 -0
  402. package/dist/slices/subtitles/captions.js +42 -9
  403. package/dist/slices/subtitles/model.js +22 -0
  404. package/dist/slices/subtitles/prepare.js +27 -16
  405. package/dist/slices/subtitles/transcript.js +1 -1
  406. package/dist/slices/thumbnail/run.js +7 -7
  407. package/dist/slices/trash/model.js +25 -0
  408. package/dist/slices/trash/service.js +273 -0
  409. package/dist/slices/uploads/repo.js +20 -0
  410. package/dist/slices/video/ambient-bed-schema.js +15 -0
  411. package/dist/slices/video/ambient-bed.js +95 -0
  412. package/dist/slices/video/ambient-mix.js +72 -0
  413. package/dist/slices/video/audio-inputs.js +2 -2
  414. package/dist/slices/video/cards.js +5 -2
  415. package/dist/slices/video/edit-list.js +23 -2
  416. package/dist/slices/video/edit-settings.js +13 -3
  417. package/dist/slices/video/ffmpeg.js +63 -9
  418. package/dist/slices/video/figure-card.js +492 -0
  419. package/dist/slices/video/figure-spans.js +51 -0
  420. package/dist/slices/video/plan.js +125 -13
  421. package/dist/slices/video/run.js +14 -12
  422. package/dist/slices/video/slideshow.js +39 -4
  423. package/dist/slices/voices/attribution.js +69 -0
  424. package/dist/slices/voices/audio-files.js +82 -0
  425. package/dist/slices/voices/audition.js +25 -0
  426. package/dist/slices/voices/cast.js +56 -0
  427. package/dist/slices/voices/grouping.js +49 -0
  428. package/dist/slices/voices/join.js +35 -0
  429. package/dist/slices/voices/languages.js +25 -0
  430. package/dist/slices/voices/model.js +179 -0
  431. package/dist/slices/voices/palette.js +26 -0
  432. package/dist/slices/voices/panel.js +91 -0
  433. package/dist/slices/voices/portraits.js +55 -0
  434. package/dist/slices/voices/script.js +136 -0
  435. package/dist/slices/voices/timing.js +58 -0
  436. package/dist/slices/youtube/answer.js +1 -1
  437. package/dist/slices/youtube/chapters.js +126 -0
  438. package/dist/slices/youtube/edits-repo.js +69 -0
  439. package/dist/slices/youtube/edits.js +89 -0
  440. package/dist/slices/youtube/placeholders.js +98 -0
  441. package/dist/slices/youtube/transcript.js +11 -4
  442. package/dist/updater/service.js +66 -21
  443. package/dist/web/assets/index-CUA2sUxU.css +1 -0
  444. package/dist/web/assets/index-CX590kE-.js +303 -0
  445. package/dist/web/assets/jetbrains-mono-cyrillic-400-normal-BEIGL1Tu.woff2 +0 -0
  446. package/dist/web/assets/jetbrains-mono-cyrillic-400-normal-ugxPyKxw.woff +0 -0
  447. package/dist/web/assets/jetbrains-mono-greek-400-normal-B9oWc5Lo.woff +0 -0
  448. package/dist/web/assets/jetbrains-mono-greek-400-normal-C190GLew.woff2 +0 -0
  449. package/dist/web/assets/jetbrains-mono-latin-400-normal-6-qcROiO.woff +0 -0
  450. package/dist/web/assets/jetbrains-mono-latin-400-normal-V6pRDFza.woff2 +0 -0
  451. package/dist/web/assets/jetbrains-mono-latin-ext-400-normal-Bc8Ftmh3.woff2 +0 -0
  452. package/dist/web/assets/jetbrains-mono-latin-ext-400-normal-fXTG6kC5.woff +0 -0
  453. package/dist/web/assets/jetbrains-mono-vietnamese-400-normal-CqNFfHCs.woff +0 -0
  454. package/dist/web/assets/{pdf-CAJNT254.js → pdf-BG8n_uc5.js} +1 -1
  455. package/dist/web/index.html +2 -2
  456. package/package.json +4 -4
  457. package/dist/edge/docker-launch.js +0 -81
  458. package/dist/edge/docker-projects/activation.js +0 -35
  459. package/dist/edge/docker-projects/claims.js +0 -85
  460. package/dist/edge/docker-projects/committed.js +0 -40
  461. package/dist/edge/docker-projects/engine.js +0 -461
  462. package/dist/edge/docker-projects/install.js +0 -254
  463. package/dist/edge/docker-projects/prune.js +0 -66
  464. package/dist/edge/docker-projects/recover.js +0 -100
  465. package/dist/edge/docker-projects/state.js +0 -225
  466. package/dist/web/assets/index-BGgYMm2u.css +0 -1
  467. package/dist/web/assets/index-BVVgtntL.js +0 -171
  468. package/scripts/docker-run.sh +0 -3
  469. /package/dist/edge/{docker-projects → docker-install}/tree.js +0 -0
@@ -1,11 +1,17 @@
1
1
  import { z } from "zod";
2
2
  import { estimateRequests } from "../estimate/index.js";
3
+ import { skippedGlossaryNotice, skippedSpeakerPronunciations, skippedSpeakerPronunciationsNotice, } from "../narration/pronunciation.js";
4
+ import { isLocalCliProvider } from "../settings/model.js";
3
5
  import { defaultShortsPrompt, shortsImageUpperBound } from "../shorts/model.js";
6
+ import { usesVoices } from "../voices/model.js";
4
7
  import { planDependencies } from "./dependencies.js";
5
8
  import { matchingNarrationPiece } from "./narration-reuse.js";
6
9
  import { buildRecipes } from "./recipe-build.js";
7
10
  import { manualCuesNeedReview } from "./recipe-exports.js";
8
11
  import { selectedReference, } from "./recipe-model.js";
12
+ import { recipeProviderChoice } from "./recipe-provider-choice.js";
13
+ import { narrationGlossary } from "./recipe-text.js";
14
+ import { readsIpa } from "./recipe-voices.js";
9
15
  export function planRevisionWork(revision, manifest, catalogue, availableAssetIds, resolved, retainedNarration = []) {
10
16
  const selected = {
11
17
  outputs: manifest.outputs.filter(selectedReference),
@@ -27,6 +33,7 @@ export function planRevisionWork(revision, manifest, catalogue, availableAssetId
27
33
  resolved: { ...resolved, articleMarkdown: usableArticle ? resolved.articleMarkdown : null },
28
34
  };
29
35
  const logical = buildRecipes(logicalContext);
36
+ const glossary = narrationGlossary(logicalContext);
30
37
  const context = { ...logicalContext, catalogue };
31
38
  const recipes = buildRecipes(context).map((row) => ({
32
39
  ...row,
@@ -64,7 +71,7 @@ export function planRevisionWork(revision, manifest, catalogue, availableAssetId
64
71
  null,
65
72
  after: row.fingerprint,
66
73
  }));
67
- const priced = work.flatMap((row) => priceRecipes(row, recipes.find((one) => one.key === row.key)));
74
+ const priced = work.flatMap((row) => priceRecipes(row, recipes.find((one) => one.key === row.key), revision.config));
68
75
  return {
69
76
  recipes,
70
77
  work,
@@ -74,6 +81,13 @@ export function planRevisionWork(revision, manifest, catalogue, availableAssetId
74
81
  (revision.config.chunking?.mode ?? "whole") === "whole"
75
82
  ? "Whole-text narration is one logical request. Editing its text rebuilds every provider part of that request."
76
83
  : null,
84
+ glossaryNotice: glossary?.ok === true && glossary.skipped !== undefined
85
+ ? skippedGlossaryNotice(glossary.skipped)
86
+ : null,
87
+ // Only speakers whose voice reads IPA use their pronunciations at all.
88
+ speakerPronunciationNotice: usesVoices(revision.config) && revision.config.voices !== undefined
89
+ ? skippedSpeakerPronunciationsNotice(skippedSpeakerPronunciations(revision.config.voices.speakers.filter(readsIpa), revision.config.language))
90
+ : null,
77
91
  costs: estimateRequests(priced, catalogue),
78
92
  };
79
93
  }
@@ -187,14 +201,16 @@ const shortsTranscriptEstimate = 24000;
187
201
  // What a step is charged as. Most steps are one request; what stands in for the shorts
188
202
  // until they are picked is every clip's image prompts and, at the longest length allowed,
189
203
  // every image, which is the most the step can cost.
190
- export function priceRecipes(work, value) {
204
+ export function priceRecipes(work, value,
205
+ // The revision's settings, which name the provider of a request built only when it runs.
206
+ config) {
191
207
  const input = value?.input;
192
208
  const unfolds = value?.unfoldsImages;
193
209
  // A short's image prompts once the clips are picked: the call, and the images it leads to,
194
210
  // as many as the clip's length asks for.
195
211
  if (work.disposition === "generate" && unfolds !== undefined)
196
212
  return [
197
- priceRecipe(work, value),
213
+ priceRecipe(work, value, config),
198
214
  ...Array.from({ length: unfolds.count }, () => ({
199
215
  kind: "image",
200
216
  stage: work.key,
@@ -223,7 +239,7 @@ export function priceRecipes(work, value) {
223
239
  input?.kind !== "deferred" ||
224
240
  input.operation !== "shorts" ||
225
241
  !Array.isArray(input.template))
226
- return [priceRecipe(work, value)];
242
+ return [priceRecipe(work, value, config)];
227
243
  const [, style, provider, model, imageSeconds, count, maxSeconds, llmProvider, llmModel] = input.template;
228
244
  const clips = typeof count === "number" ? count : 0;
229
245
  const images = typeof imageSeconds === "number" && typeof maxSeconds === "number"
@@ -249,7 +265,7 @@ export function priceRecipes(work, value) {
249
265
  })),
250
266
  ];
251
267
  }
252
- export function priceRecipe(work, value) {
268
+ export function priceRecipe(work, value, config) {
253
269
  if (value === undefined || work.disposition !== "generate")
254
270
  return {
255
271
  kind: "local",
@@ -259,12 +275,21 @@ export function priceRecipe(work, value) {
259
275
  : "Local, provided, or blocked work; no admitted generation charge.",
260
276
  };
261
277
  const input = value.input;
278
+ const onPlan = config === undefined ? undefined : cliRequest(work, value, config);
279
+ if (onPlan !== undefined)
280
+ return onPlan;
262
281
  if (input.kind === "deferred" && input.operation === "narration-preparation")
263
282
  return {
264
283
  kind: "unknown",
265
284
  stage: work.key,
266
285
  detail: "Narration Preparation: one LLM call per future logical chunk or entry; source length is not yet known.",
267
286
  };
287
+ if (input.kind === "deferred" && input.operation === "narration-description")
288
+ return {
289
+ kind: "unknown",
290
+ stage: work.key,
291
+ detail: "Narration descriptions: one LLM call per table, figure, equation or code block in the article; how many is known once the article is written.",
292
+ };
268
293
  if (input.kind === "llm")
269
294
  return {
270
295
  kind: "llm",
@@ -273,7 +298,7 @@ export function priceRecipe(work, value) {
273
298
  model: input.model,
274
299
  inputCharacters: input.messages.reduce((sum, message) => sum + message.content.length, 0) +
275
300
  (input.documents ?? []).reduce((sum, document) => sum + document.content.length, 0),
276
- outputCharacters: work.key === "article:body" ? 9000 : 2400,
301
+ outputCharacters: work.key === "article:body" ? 9000 : input.describe !== undefined ? 600 : 2400,
277
302
  detail: "Actual saved request input; output length is estimated. Retries and tools are excluded.",
278
303
  };
279
304
  if (input.kind === "tts")
@@ -299,6 +324,21 @@ export function priceRecipe(work, value) {
299
324
  detail: "The numbered transcript is built when the step runs; its length is estimated. One more call is made when the first answer breaks the rules. Retries are excluded.",
300
325
  };
301
326
  }
327
+ // A review: the stage's prompt and the item (the article's text, or a short brief beside an
328
+ // image, which the provider prices as it does).
329
+ if (input.kind === "local" && input.operation === "review-v1") {
330
+ const [stage, , , provider, model, , prompt] = Array.isArray(input.values) ? input.values : [];
331
+ return {
332
+ kind: "llm",
333
+ stage: work.key,
334
+ provider: typeof provider === "string" ? provider : "",
335
+ model: typeof model === "string" ? model : "",
336
+ inputCharacters: (typeof prompt === "string" && prompt !== "" ? prompt.length : 600) +
337
+ (stage === "article" || stage === "narration" ? 20000 : 1500),
338
+ outputCharacters: 400,
339
+ detail: "The item is read when the step runs; its length is estimated. A failed item made again is reviewed again, which is not in this estimate.",
340
+ };
341
+ }
302
342
  if (input.kind === "local" && input.operation === "youtube-description-v1") {
303
343
  const [, provider, model, , prompt] = Array.isArray(input.values) ? input.values : [];
304
344
  return {
@@ -320,3 +360,34 @@ export function priceRecipe(work, value) {
320
360
  detail: "Future generated input is unknown. This group materializes under the same admitted revision; its final request count and charge are unknown.",
321
361
  };
322
362
  }
363
+ // A request built only when its step runs, on a command-line provider: it runs on the user's
364
+ // plan, so it is known at $0, as Play's review prices the same step. Its size is estimated
365
+ // for the API figure beside it; Narration Preparation's number of calls is not known yet, so
366
+ // it has none.
367
+ function cliRequest(work, value, config) {
368
+ if (value.input.kind !== "deferred")
369
+ return undefined;
370
+ const choice = recipeProviderChoice(value, config);
371
+ if (choice === undefined || !isLocalCliProvider(choice.provider))
372
+ return undefined;
373
+ if (choice.family === "image")
374
+ return { kind: "image", stage: work.key, provider: choice.provider, model: choice.model };
375
+ if (choice.family !== "llm")
376
+ return undefined;
377
+ const preparation = value.input.operation === "narration-preparation" ||
378
+ value.input.operation === "narration-description";
379
+ return {
380
+ kind: "llm",
381
+ stage: work.key,
382
+ provider: choice.provider,
383
+ model: choice.model,
384
+ inputCharacters: 6000,
385
+ outputCharacters: work.key === "article:body" ? 9000 : 2400,
386
+ ...(preparation ? { apiUnknown: true } : {}),
387
+ detail: preparation
388
+ ? value.input.operation === "narration-description"
389
+ ? "Runs on your CLI plan, so it adds no charge. Narration descriptions make one call per table, figure, equation or code block; how many is known once the article is written, so no API figure is given."
390
+ : "Runs on your CLI plan, so it adds no charge. Narration Preparation makes one call per logical chunk or entry; how many is known when the step runs, so no API figure is given."
391
+ : "Runs on your CLI plan, so it adds no charge. The request is built when the step runs, so its API figure uses an estimated length.",
392
+ };
393
+ }
@@ -25,6 +25,8 @@ export function youtubeRecipes(context, exports) {
25
25
  // ships as a new operation version rather than a silent change here.
26
26
  config.descriptionPrompt?.trim() ? renderedPrompt(context, "description") : "",
27
27
  config.title,
28
+ // The project language, only when it is not English, so English keeps its fingerprint.
29
+ ...(config.language === undefined || config.language === "en" ? [] : [config.language]),
28
30
  ],
29
31
  }, [timing.key],
30
32
  // A paid LLM call: readiness, the charge warning and the estimate treat it like one.
@@ -1,5 +1,9 @@
1
1
  import { projectPaused } from "../admission/repo.js";
2
- export function activeConflict(deps, projectId, keys) {
2
+ //
3
+ // An automatic redo (`pendingSuperseded`) runs while the rest of the run waits: admitted work
4
+ // that has not started is replaced by the redo's own, so only running work and accepted jobs
5
+ // stand in its way.
6
+ export function activeConflict(deps, projectId, keys, pendingSuperseded = false) {
3
7
  const rows = deps.db
4
8
  .prepare("SELECT work_key,logical_key,live FROM (SELECT p.work_key,r.logical_key," +
5
9
  "(w.state='running' OR (? AND w.state='pending' AND w.dispatch_state='allowed' AND NOT EXISTS(" +
@@ -10,7 +14,7 @@ export function activeConflict(deps, projectId, keys) {
10
14
  "FROM revision_work w JOIN revision_work_pieces p ON p.work_id=w.id " +
11
15
  "LEFT JOIN revision_work_reservations r ON r.work_id=w.id AND r.piece_id=p.id " +
12
16
  "WHERE w.project_id=?) WHERE live OR accepted")
13
- .all(projectPaused(deps.db, projectId) ? 0 : 1, projectId)
17
+ .all(pendingSuperseded || projectPaused(deps.db, projectId) ? 0 : 1, projectId)
14
18
  .filter((row) => keys.some((key) => row.work_key === key ||
15
19
  row.logical_key === key ||
16
20
  (key.endsWith(":future") && String(row.work_key).startsWith(key.slice(0, -6)))));
@@ -18,7 +22,7 @@ export function activeConflict(deps, projectId, keys) {
18
22
  return undefined;
19
23
  return rows.some((row) => Number(row.live) === 1) ? "running" : "accepted-job";
20
24
  }
21
- export const acceptedJobMessage = "An accepted provider job for this section is waiting to be collected. Use Retry stage or Resume, then rerun.";
25
+ export const acceptedJobMessage = "An accepted provider job for this section is waiting to be collected. Use Try again or Continue the run, then rerun.";
22
26
  export function conflictRefusal(conflict) {
23
27
  return conflict === "running"
24
28
  ? { ok: false, reason: "running" }
@@ -6,6 +6,9 @@ export const recoveryActionSchema = z.discriminatedUnion("kind", [
6
6
  z.object({ kind: z.literal("resume") }).strict(),
7
7
  z.object({ kind: z.literal("retry"), stage: z.enum(stageKinds) }).strict(),
8
8
  z.object({ kind: z.literal("rerun"), stage: z.enum(stageKinds) }).strict(),
9
+ // Make one reviewed item again (`slices/reviews`): an image, the thumbnail, the article, the
10
+ // narration or one short, and everything that depends on it, its review included.
11
+ z.object({ kind: z.literal("redo"), item: z.string().min(1).max(200) }).strict(),
9
12
  ]);
10
13
  export const recoveryRequestSchema = revisionControlSchema
11
14
  .extend({
@@ -9,19 +9,45 @@ export function sectionRoots(view, plan, stage) {
9
9
  return false;
10
10
  if (stage === "video")
11
11
  return (row.input.kind === "local" &&
12
- (row.key.startsWith("export:") || row.key === "subtitles:files"));
12
+ (row.key.startsWith("export:") ||
13
+ row.key === "subtitles:files" ||
14
+ row.key === "voices:files"));
13
15
  if (stage === "audio")
14
16
  return (row.input.kind === "tts" ||
15
17
  (row.input.kind === "deferred" &&
16
18
  ["body-narration", "intro-narration", "outro-narration"].includes(row.input.operation)));
17
19
  if (stage === "article")
18
- return row.key === "article:body" && row.kind === "provider";
20
+ return ((row.key === "article:body" || row.key === "script:attribute") && row.kind === "provider");
19
21
  if (stage === "document")
20
22
  return row.key === "document:pdf";
21
23
  return row.kind === "provider";
22
24
  })
23
25
  .map((row) => row.key);
24
26
  }
27
+ // What making one reviewed item again regenerates and where its rebuild starts. The article
28
+ // and the narration are redone like More → make it again in its section on their stage; an image or the thumbnail
29
+ // by its own token, like Regenerate; a short by "Make this short again"'s token.
30
+ export function redoTarget(view, plan, item) {
31
+ const section = (stage) => {
32
+ const edit = regenerationEdit(view, plan, stage);
33
+ return edit === undefined ? undefined : { roots: sectionRoots(view, plan, stage), edit };
34
+ };
35
+ if (item === "narration")
36
+ return section("audio");
37
+ if (item === "article:body")
38
+ return section("article");
39
+ const own = (roots) => ({
40
+ roots,
41
+ edit: { config: view.revision.config, content: view.revision.content, regenerate: [item] },
42
+ });
43
+ if (/^shorts:\d+$/.test(item))
44
+ return plan.recipes.some((row) => row.key === `${item}:prompts`)
45
+ ? own([`${item}:prompts`])
46
+ : undefined;
47
+ return plan.recipes.some((row) => row.key === item && row.kind === "provider")
48
+ ? own([item])
49
+ : undefined;
50
+ }
25
51
  export function dependentClosure(recipes, roots) {
26
52
  const selected = new Set(roots);
27
53
  let changed = true;
@@ -10,7 +10,7 @@ import { recipeProviderChoice } from "./recipe-provider-choice.js";
10
10
  import { acceptedJobMessage, activeConflict, conflictRefusal } from "./recovery-conflict.js";
11
11
  import { recoveryRequestSchema, } from "./recovery-model.js";
12
12
  import { readRecovery, recoveryAuthority, recoveryKey, rememberRecovery, reserveRecovery, } from "./recovery-repo.js";
13
- import { dependentClosure, recoverySelection, regenerationEdit, sectionRoots, } from "./recovery-selection.js";
13
+ import { dependentClosure, recoverySelection, redoTarget, regenerationEdit, sectionRoots, } from "./recovery-selection.js";
14
14
  import { previewById, storePreview } from "./repo.js";
15
15
  import { admitCheckedPreview, wakeRebuild } from "./service.js";
16
16
  import { checkReadiness } from "./service-readiness.js";
@@ -27,7 +27,10 @@ function credentialsStamp(deps, plan, view) {
27
27
  .filter((row) => providers.has(String(row.provider)));
28
28
  return requestHash("recovery-credentials", view.revision.id, metadata);
29
29
  }
30
- export async function recoverProject(deps, projectId, raw) {
30
+ export async function recoverProject(deps, projectId, raw,
31
+ // Set only by an automatic review redo (`review-redo.ts`): see `activeConflict`.
32
+ options = {}) {
33
+ const superseded = options.pendingSuperseded === true;
31
34
  const parsed = recoveryRequestSchema.safeParse(raw);
32
35
  if (!parsed.success)
33
36
  return {
@@ -39,6 +42,12 @@ export async function recoverProject(deps, projectId, raw) {
39
42
  })),
40
43
  };
41
44
  const input = parsed.data;
45
+ // Where a rerun or a redo starts: the stage's generated work, or the reviewed item's.
46
+ const rootsOf = (view, plan) => input.action.kind === "rerun"
47
+ ? sectionRoots(view, plan, input.action.stage)
48
+ : input.action.kind === "redo"
49
+ ? (redoTarget(view, plan, input.action.item)?.roots ?? [])
50
+ : [];
42
51
  const prepared = await withProjectControl(deps.db, projectId, async () => {
43
52
  const found = readRecovery(deps, projectId, input);
44
53
  if (found !== undefined && "ok" in found)
@@ -54,7 +63,11 @@ export async function recoverProject(deps, projectId, raw) {
54
63
  if (!base.current)
55
64
  return { ok: false, reason: "conflict" };
56
65
  const plan = retainedPreviewPlan(deps, base, deps.catalogue.read());
57
- const edit = input.action.kind === "rerun" ? regenerationEdit(base, plan, input.action.stage) : null;
66
+ const edit = input.action.kind === "rerun"
67
+ ? regenerationEdit(base, plan, input.action.stage)
68
+ : input.action.kind === "redo"
69
+ ? redoTarget(base, plan, input.action.item)?.edit
70
+ : null;
58
71
  if (edit === undefined)
59
72
  return {
60
73
  ok: false,
@@ -62,7 +75,9 @@ export async function recoverProject(deps, projectId, raw) {
62
75
  fields: [
63
76
  {
64
77
  field: "stage",
65
- message: "This section has no generated work to rerun. Use Edit project to change its source.",
78
+ message: input.action.kind === "redo"
79
+ ? "This item is no longer made by the project (it was removed, or its source changed), so it can't be made again. Reload the project page."
80
+ : "This section has no generated work to rerun. Use Edit project to change its source.",
66
81
  },
67
82
  ],
68
83
  };
@@ -76,14 +91,15 @@ export async function recoverProject(deps, projectId, raw) {
76
91
  let view = base;
77
92
  if (record.edit !== null) {
78
93
  const prior = retainedPreviewPlan(deps, base, deps.catalogue.read());
79
- const action = input.action;
80
- if (action.kind !== "rerun")
94
+ if (input.action.kind !== "rerun" && input.action.kind !== "redo")
81
95
  throw new Error("Slopify hit an internal error (a recovery edit has nothing to re-run). Try again; if it happens again, use Download diagnostics in Settings and report it.");
82
- const affected = dependentClosure(prior.recipes, sectionRoots(base, prior, action.stage));
96
+ const affected = dependentClosure(prior.recipes, rootsOf(base, prior));
83
97
  const savedIntent = deps.db
84
98
  .prepare("SELECT result_revision_id FROM revision_mutations WHERE project_id=? AND idempotency_key=?")
85
99
  .get(projectId, recoveryKey(input, "save"));
86
- const conflict = savedIntent === undefined ? activeConflict(deps, projectId, affected) : undefined;
100
+ const conflict = savedIntent === undefined
101
+ ? activeConflict(deps, projectId, affected, superseded)
102
+ : undefined;
87
103
  if (conflict !== undefined)
88
104
  return finish(conflictRefusal(conflict));
89
105
  const saved = await saveRevision(deps, {
@@ -92,7 +108,7 @@ export async function recoverProject(deps, projectId, raw) {
92
108
  idempotencyKey: recoveryKey(input, "save"),
93
109
  edit: record.edit,
94
110
  beforeCommit: () => {
95
- const late = activeConflict(deps, projectId, affected);
111
+ const late = activeConflict(deps, projectId, affected, superseded);
96
112
  return late === undefined
97
113
  ? undefined
98
114
  : {
@@ -121,10 +137,10 @@ export async function recoverProject(deps, projectId, raw) {
121
137
  if (currentRevisionId(deps.db, projectId) !== view.revision.id)
122
138
  return finish({ ok: false, reason: "conflict" });
123
139
  const plan = retainedPreviewPlan(deps, view, deps.catalogue.read());
124
- const rerunKeys = input.action.kind === "rerun"
125
- ? dependentClosure(plan.recipes, sectionRoots(view, plan, input.action.stage))
140
+ const rerunKeys = input.action.kind === "rerun" || input.action.kind === "redo"
141
+ ? dependentClosure(plan.recipes, rootsOf(view, plan))
126
142
  : [];
127
- const selection = input.action.kind === "rerun"
143
+ const selection = input.action.kind === "rerun" || input.action.kind === "redo"
128
144
  ? { kind: "selected", workKeys: rerunKeys }
129
145
  : recoverySelection(plan, input.action.kind === "retry" ? input.action.stage : undefined);
130
146
  if (selection.kind === "selected" && selection.workKeys.length === 0)
@@ -167,15 +183,15 @@ export async function recoverProject(deps, projectId, raw) {
167
183
  input.action.stage === "video"
168
184
  ? {
169
185
  field: "video",
170
- message: "Source media is incomplete. Use Resume to recover it before rerunning the export.",
186
+ message: "Source media is incomplete. Use Continue the run to recover it before rerunning the export.",
171
187
  }
172
188
  : {
173
189
  field: "document",
174
- message: "The article or thumbnail the document is made from isn't finished. Use Resume to finish it, then Re-run section on Document.",
190
+ message: "The article or thumbnail the document is made from isn't finished. Use Continue the run to finish it, then More → make it again in its section on Document.",
175
191
  },
176
192
  ],
177
193
  });
178
- const conflict = rerunKeys.length > 0 ? activeConflict(deps, projectId, rerunKeys) : undefined;
194
+ const conflict = rerunKeys.length > 0 ? activeConflict(deps, projectId, rerunKeys, superseded) : undefined;
179
195
  if (conflict !== undefined)
180
196
  return finish(conflictRefusal(conflict));
181
197
  storePreview(deps, preview.value.preview, preview.value.execution);
@@ -199,7 +215,9 @@ export async function recoverProject(deps, projectId, raw) {
199
215
  catch {
200
216
  deps.log.write("warn", "project.recovery", {
201
217
  projectId,
202
- ...(input.action.kind === "resume" ? {} : { stage: input.action.stage }),
218
+ ...(input.action.kind === "resume" || input.action.kind === "redo"
219
+ ? {}
220
+ : { stage: input.action.stage }),
203
221
  detail: "Provider readiness could not be loaded.",
204
222
  });
205
223
  ready = {
@@ -232,7 +250,7 @@ export async function recoverProject(deps, projectId, raw) {
232
250
  if (ready.fields.length > 0)
233
251
  return finish({ ok: false, reason: "readiness", fields: [...ready.fields] });
234
252
  const conflict = prepared.rerunKeys.length > 0
235
- ? activeConflict(deps, projectId, prepared.rerunKeys)
253
+ ? activeConflict(deps, projectId, prepared.rerunKeys, superseded)
236
254
  : undefined;
237
255
  if (conflict !== undefined)
238
256
  return finish(conflictRefusal(conflict));
@@ -7,7 +7,7 @@ export { transitionRevisionWork } from "./transition-repo.js";
7
7
  export function publicationTargets(db, publication, workKey, workFingerprint) {
8
8
  const { work, pieceId, publicationId } = publication;
9
9
  if (publicationId !== (pieceId ?? work.workId))
10
- throw new Error("Slopify hit an internal error (a finished result is filed under the wrong step). Retry stage; if it happens again, use Download diagnostics in Settings and report it.");
10
+ throw new Error("Slopify hit an internal error (a finished result is filed under the wrong step). Try again; if it happens again, use Download diagnostics in Settings and report it.");
11
11
  if (!workExists(db, work))
12
12
  return [];
13
13
  if (pieceId !== null) {
@@ -15,9 +15,9 @@ export function publicationTargets(db, publication, workKey, workFingerprint) {
15
15
  .prepare("SELECT work_key,fingerprint FROM revision_work_pieces WHERE id=? AND work_id=?")
16
16
  .get(pieceId, work.workId);
17
17
  if (piece === undefined)
18
- throw new Error("Slopify hit an internal error (a finished result belongs to a different step). Retry stage; if it happens again, use Download diagnostics in Settings and report it.");
18
+ throw new Error("Slopify hit an internal error (a finished result belongs to a different step). Try again; if it happens again, use Download diagnostics in Settings and report it.");
19
19
  if (piece.work_key !== workKey || piece.fingerprint !== workFingerprint)
20
- throw new Error("Slopify hit an internal error (a finished result doesn't match its saved step). Retry stage; if it happens again, use Download diagnostics in Settings and report it.");
20
+ throw new Error("Slopify hit an internal error (a finished result doesn't match its saved step). Try again; if it happens again, use Download diagnostics in Settings and report it.");
21
21
  }
22
22
  const currentId = currentRevisionId(db, work.projectId);
23
23
  const owns = (revisionId) => {
@@ -0,0 +1,18 @@
1
+ import { autoRetryDelay } from "../../kernel/runner/retry-policy.js";
2
+ import { autoRetriesOf, deferWork, dueRetries } from "../../kernel/runner/work-authority.js";
3
+ // The runner's store for the second tier of the retry policy (`kernel/runner/retry-policy.ts`):
4
+ // a failure time can fix puts the step back to wait, with the time it may run again.
5
+ export function waitToRetry(deps, work, fault, reason, random) {
6
+ const delay = autoRetryDelay(fault, autoRetriesOf(deps.db, work), random);
7
+ if (delay === undefined || fault === undefined)
8
+ return undefined;
9
+ const at = new Date(deps.clock.now().getTime() + delay).toISOString();
10
+ return deferWork(deps.db, work, at, reason, fault.kind) ? at : undefined;
11
+ }
12
+ // Ticks every project with a wait that has run out. Returns them, for the log and tests.
13
+ export function wakeRetries(db, clock, runner) {
14
+ const due = dueRetries(db, clock.now().toISOString());
15
+ for (const projectId of due)
16
+ runner.tick(projectId);
17
+ return due;
18
+ }
@@ -0,0 +1,110 @@
1
+ import { createHash } from "node:crypto";
2
+ import { reviewKey } from "../reviews/model.js";
3
+ import { pendingRedos, setRedoState } from "../reviews/repo.js";
4
+ import { currentRevisionId } from "../revisions/repo.js";
5
+ import { recoverProject } from "./recovery.js";
6
+ import { dependentClosure } from "./recovery-selection.js";
7
+ import { executionPlan, executionView, savedCatalogue } from "./runtime-plan.js";
8
+ import { workPieces } from "./work-records.js";
9
+ // While a review's verdict waits to send its item back, what depends on the item waits too:
10
+ // the automatic redo replaces that work, and starting it on the failed item would be wasted.
11
+ // The same claim-time hold a review checkpoint uses, answered by the reviewer.
12
+ export function reviewHold(deps, work) {
13
+ const waiting = pendingRedos(deps.db, work.projectId).filter((row) => row.revisionId === work.revisionId);
14
+ if (waiting.length === 0)
15
+ return { kind: "eligible" };
16
+ const view = executionView(deps, work.projectId, work.revisionId);
17
+ const row = deps.db
18
+ .prepare("SELECT recipe_context FROM revision_work WHERE id=?")
19
+ .get(work.workId);
20
+ if (view === undefined || row === undefined)
21
+ return { kind: "eligible" };
22
+ const plan = executionPlan(deps, view, savedCatalogue(row.recipe_context));
23
+ const keys = new Set(workPieces(deps.db, work.workId).map((piece) => piece.key));
24
+ const held = waiting.filter((verdict) => {
25
+ const review = reviewKey(verdict.itemKey);
26
+ return dependentClosure(plan.recipes, [review]).some((key) => key !== review && keys.has(key));
27
+ });
28
+ return held.length === 0
29
+ ? { kind: "eligible" }
30
+ : { kind: "held", checkpointIds: held.map((verdict) => `review:${verdict.id}`) };
31
+ }
32
+ // Starts the redos reviews asked for, once their review step has finished: the item is made
33
+ // again through Re-run's path (a new regeneration token saved as a new project version, then
34
+ // its dependents rebuilt), so the redone item, its new review and everything after it are
35
+ // ordinary recipe work. One at a time per verdict; a redo that cannot start leaves the item
36
+ // flagged with the reason and releases what was waiting on it.
37
+ export function createReviewRedos() {
38
+ let bound;
39
+ const running = new Set();
40
+ const kick = (projectId) => {
41
+ const deps = bound;
42
+ if (deps === undefined)
43
+ return;
44
+ for (const verdict of pendingRedos(deps.db, projectId)) {
45
+ if (running.has(verdict.id))
46
+ continue;
47
+ running.add(verdict.id);
48
+ void startRedo(deps, verdict)
49
+ .catch((error) => {
50
+ deps.log.write("error", "review.redo", {
51
+ projectId: verdict.projectId,
52
+ detail: error instanceof Error ? error.message : String(error),
53
+ });
54
+ setRedoState(deps.db, verdict.id, "failed", "Slopify hit an internal error while making this item again. Use Redo beside it; if it happens again, use Download diagnostics in Settings and report it.");
55
+ })
56
+ .finally(() => {
57
+ running.delete(verdict.id);
58
+ try {
59
+ deps.runner.tick(verdict.projectId);
60
+ }
61
+ catch {
62
+ // The next tick of this project picks the released work up.
63
+ }
64
+ });
65
+ }
66
+ };
67
+ return {
68
+ bind: (deps) => {
69
+ bound = deps;
70
+ },
71
+ kick,
72
+ };
73
+ }
74
+ export async function startRedo(deps, verdict) {
75
+ const base = currentRevisionId(deps.db, verdict.projectId);
76
+ if (base === undefined) {
77
+ setRedoState(deps.db, verdict.id, "failed", "The project no longer exists.");
78
+ return;
79
+ }
80
+ const result = await recoverProject(deps, verdict.projectId, {
81
+ baseRevisionId: base,
82
+ idempotencyKey: redoKey(verdict.id),
83
+ action: { kind: "redo", item: verdict.itemKey },
84
+ }, { pendingSuperseded: true });
85
+ if (result.ok)
86
+ setRedoState(deps.db, verdict.id, "started");
87
+ else
88
+ setRedoState(deps.db, verdict.id, "failed", redoRefusal(result));
89
+ deps.emit(verdict.projectId, { type: "project.updated", projectId: verdict.projectId });
90
+ }
91
+ // A stable request identity per verdict, so a restart that starts the same redo again gets
92
+ // the first attempt's answer instead of a second redo.
93
+ export function redoKey(verdictId) {
94
+ const hex = createHash("sha256").update(`review-redo:${verdictId}`).digest("hex");
95
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-4${hex.slice(13, 16)}-8${hex.slice(17, 20)}-${hex.slice(20, 32)}`;
96
+ }
97
+ export function redoRefusal(result) {
98
+ const detail = result.fields?.map((field) => field.message).join(" ");
99
+ switch (result.reason) {
100
+ case "running":
101
+ return "Slopify couldn't make this item again automatically because work that depends on it was already running. Use Redo beside it once the run has finished.";
102
+ case "readiness":
103
+ return `Slopify couldn't make this item again automatically: ${detail ?? "a provider isn't ready"}. Fix that in Settings → Providers, then use Redo beside it.`;
104
+ case "conflict":
105
+ case "control-changed":
106
+ return "Slopify couldn't make this item again automatically because the project changed at the same moment (an edit, Pause or Cancel). Use Redo beside it.";
107
+ default:
108
+ return `Slopify couldn't make this item again automatically${detail ? `: ${detail}` : "."} Use Redo beside it.`;
109
+ }
110
+ }
@@ -12,7 +12,19 @@ export async function revisionAction(deps, projectId, action) {
12
12
  const config = view.revision.config;
13
13
  const content = view.revision.content;
14
14
  const image = action.kind === "delete-image" || action.kind === "regenerate-image"
15
- ? view.outputs.find((row) => row.selected && row.output.id === action.outputId && row.output.role === "image")
15
+ ? view.outputs.find((row) => row.selected &&
16
+ row.output.id === action.outputId &&
17
+ (row.output.role === "image" ||
18
+ // One of the generated thumbnails is made again on its own, the others kept.
19
+ (action.kind === "regenerate-image" &&
20
+ row.output.role === "thumbnail" &&
21
+ (config.sources.thumbnail === "from_prompt" ||
22
+ config.sources.thumbnail === "prompt_by_llm")) ||
23
+ // A generated establishing image is made again like any image; it is never deleted
24
+ // on its own (Edit project sets it Off), and an uploaded one has nothing to redo.
25
+ (action.kind === "regenerate-image" &&
26
+ row.output.role === "reference" &&
27
+ config.reference?.source === "prompt")))
16
28
  : undefined;
17
29
  if ((action.kind === "delete-image" || action.kind === "regenerate-image") && image === undefined)
18
30
  return { ok: false, reason: "unknown-image" };
@@ -64,7 +76,7 @@ function keyStage(key) {
64
76
  const prefix = key.split(":")[0];
65
77
  if (prefix === "entry" || prefix === "article")
66
78
  return "article";
67
- if (prefix === "image")
79
+ if (prefix === "image" || prefix === "reference")
68
80
  return "images";
69
81
  if (prefix === "audio")
70
82
  return "audio";
@@ -12,7 +12,10 @@ export function reservationKey(recipe, view) {
12
12
  ? `audio:${recipe.input.segment}:future`
13
13
  : recipe.input.kind === "llm" && recipe.input.preparation
14
14
  ? `narration:prepare:${recipe.input.preparation.segment}:future`
15
- : key;
15
+ : (recipe.input.kind === "llm" && recipe.input.describe) ||
16
+ recipe.key.startsWith("figure:card:")
17
+ ? "narration:describe:future"
18
+ : key;
16
19
  return view.revision.fingerprints[future] === undefined ? key : future;
17
20
  }
18
21
  export function admitInitialRevision(deps, view, catalogue) {
@@ -22,7 +22,7 @@ export async function executeAnimateRecipe(deps, context, providers, piece) {
22
22
  return "done";
23
23
  const input = piece.input;
24
24
  if (input.kind !== "image" || input.animate === undefined)
25
- throw new Error("Slopify hit an internal error (an animated image was set up wrongly). Use Retry stage; if it happens again, use Download diagnostics in Settings and report it.");
25
+ throw new Error("Slopify hit an internal error (an animated image was set up wrongly). Use Try again; if it happens again, use Download diagnostics in Settings and report it.");
26
26
  const imageKey = piece.key.slice("animate:".length);
27
27
  const view = executionView(deps, context.work.projectId, context.work.revisionId);
28
28
  const index = (view?.revision.content.imageOrder.indexOf(imageKey) ?? -1) + 1;
@@ -35,10 +35,10 @@ export async function executeAnimateRecipe(deps, context, providers, piece) {
35
35
  return "done";
36
36
  };
37
37
  if (still === undefined)
38
- return fallback("its image wasn't ready to animate. Use Re-run section on Video after the image is made.");
38
+ return fallback("its image wasn't ready to animate. Use More → Render the video again in the Video section after the image is made.");
39
39
  const animate = providers.forPiece(piece.id).animate;
40
40
  if (animate === undefined)
41
- throw new Error("Slopify hit an internal error (animating images isn't wired up in this build). Use Retry stage; if it happens again, use Download diagnostics in Settings and report it.");
41
+ throw new Error("Slopify hit an internal error (animating images isn't wired up in this build). Use Try again; if it happens again, use Download diagnostics in Settings and report it.");
42
42
  if (!context.maySubmit(piece.id))
43
43
  return "held";
44
44
  let made;
@@ -55,7 +55,7 @@ export async function executeAnimateRecipe(deps, context, providers, piece) {
55
55
  catch (error) {
56
56
  if (context.signal.aborted)
57
57
  throw error;
58
- return fallback(`${error instanceof Error ? error.message : String(error)} To try again, use Re-run section on Video.`);
58
+ return fallback(`${error instanceof Error ? error.message : String(error)} To try again, use More → Render the video again in the Video section.`);
59
59
  }
60
60
  if (!made.ok)
61
61
  return "held";