@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,33 +1,39 @@
1
1
  // Images use saved prompts and can run immediately. A thumbnail's source and an
2
2
  // audio-only export narrow these dependencies through dependenciesOf(). The document is
3
3
  // laid out from the article alone, with the thumbnail as its cover when there is one, so it
4
- // runs beside narration and images rather than after them.
4
+ // runs beside narration and images rather than after them. The video never reads the
5
+ // thumbnail, so a thumbnail that fails does not hold the video back.
5
6
  export const deps = {
6
7
  research: [],
7
8
  article: ["research"],
8
9
  audio: ["article"],
9
10
  images: [],
10
11
  thumbnail: ["article"],
11
- video: ["article", "audio", "images", "thumbnail"],
12
+ video: ["article", "audio", "images"],
12
13
  document: ["article", "thumbnail"],
13
14
  };
14
15
  // `provided` and `skipped` release a dependency exactly as `done` does.
15
16
  export function satisfied(state) {
16
17
  return state === "done" || state === "provided" || state === "skipped";
17
18
  }
19
+ // What a run is for, in order: the first of these the run asks for is the one that has to be
20
+ // there for the run to count as finished. Anything else failing leaves it done with
21
+ // problems rather than failed.
22
+ const headline = ["video", "audio", "article"];
18
23
  // Stage state is derived; an explicit persisted pause takes precedence while calls
19
- // drain. Completed independent images do not imply that a run was canceled.
24
+ // drain. Completed independent images do not imply that a run was canceled. A stage
25
+ // waiting out a rate limit is still part of a run in progress.
20
26
  export function derive(stages, paused = false) {
21
27
  if (paused)
22
28
  return "paused";
23
- if (stages.some((stage) => stage.state === "running")) {
29
+ if (stages.some((stage) => stage.state === "running" || waiting(stage))) {
24
30
  return "running";
25
31
  }
26
32
  if (stages.some((stage) => stage.state === "canceled")) {
27
33
  return "canceled";
28
34
  }
29
35
  if (stages.some((stage) => stage.state === "failed")) {
30
- return "failed";
36
+ return headlineMade(stages) ? "partial" : "failed";
31
37
  }
32
38
  if (stages.length > 0 && stages.every((stage) => satisfied(stage.state))) {
33
39
  return "done";
@@ -35,6 +41,19 @@ export function derive(stages, paused = false) {
35
41
  // Includes a run created but not yet claimed and unfinished work after restart.
36
42
  return "pending";
37
43
  }
44
+ function waiting(stage) {
45
+ return stage.state === "pending" && stage.retryAt !== undefined && stage.retryAt !== null;
46
+ }
47
+ function headlineMade(stages) {
48
+ for (const kind of headline) {
49
+ const stage = stages.find((one) => one.kind === kind);
50
+ if (stage === undefined || stage.state === "skipped")
51
+ continue;
52
+ // A file the user supplied is not something this run made.
53
+ return stage.state === "done";
54
+ }
55
+ return false;
56
+ }
38
57
  export function dependenciesOf(kind, sources) {
39
58
  if (kind === "thumbnail" && sources.thumbnail === "from_prompt")
40
59
  return [];
@@ -1,6 +1,7 @@
1
1
  import { causedBy } from "../errors.js";
2
2
  import { derive, deps as graph, satisfied } from "./graph.js";
3
3
  import { progressGate } from "./progress.js";
4
+ import { faultOf } from "./retry-policy.js";
4
5
  const pauseReason = new Error("paused by user");
5
6
  export function createRunner(deps) {
6
7
  const inflight = new Map();
@@ -15,7 +16,7 @@ export function createRunner(deps) {
15
16
  const stopped = new Set();
16
17
  let count = 0;
17
18
  let shuttingDown = false;
18
- const emitStage = (stage, state, reason) => {
19
+ const emitStage = (stage, state, reason, detail = {}) => {
19
20
  deps.emit(stage.projectId, {
20
21
  type: "stage.state",
21
22
  revisionId: stage.work.revisionId,
@@ -24,6 +25,8 @@ export function createRunner(deps) {
24
25
  stage: stage.kind,
25
26
  state,
26
27
  ...(reason === null ? {} : { failureReason: reason }),
28
+ ...(detail.retryAt === undefined ? {} : { retryAt: detail.retryAt }),
29
+ ...(detail.failureKind === undefined ? {} : { failureKind: detail.failureKind }),
27
30
  });
28
31
  };
29
32
  // The global tally counts projects with a stage in flight, read off the in-flight set
@@ -46,9 +49,9 @@ export function createRunner(deps) {
46
49
  announced.set(projectId, state);
47
50
  deps.emit(projectId, { type: "project.state", projectId, state });
48
51
  };
49
- const conclude = (stage, state, reason) => {
52
+ const conclude = (stage, state, reason, kind) => {
50
53
  try {
51
- deps.stages.finish(stage.work, state, reason);
54
+ deps.stages.finish(stage.work, state, reason, kind ?? null);
52
55
  }
53
56
  catch (error) {
54
57
  // The row stays `running` when the write fails. The event still goes out, and the
@@ -59,7 +62,31 @@ export function createRunner(deps) {
59
62
  detail: reasonOf(error),
60
63
  });
61
64
  }
62
- emitStage(stage, state, reason);
65
+ emitStage(stage, state, reason, { failureKind: kind });
66
+ };
67
+ // False when the failure is to be shown now.
68
+ const deferred = (stage, fault, reason) => {
69
+ let at;
70
+ try {
71
+ at = deps.stages.waitToRetry?.(stage.work, fault, reason);
72
+ }
73
+ catch (error) {
74
+ deps.log.write("error", "stage.retry", {
75
+ projectId: stage.projectId,
76
+ stage: stage.kind,
77
+ detail: reasonOf(error),
78
+ });
79
+ return false;
80
+ }
81
+ if (at === undefined)
82
+ return false;
83
+ deps.log.write("warn", "stage.retry", {
84
+ projectId: stage.projectId,
85
+ stage: stage.kind,
86
+ detail: `waiting until ${at} to run again after ${fault?.kind ?? "a failure"}: ${reason}`,
87
+ });
88
+ emitStage(stage, "pending", reason, { retryAt: at, failureKind: fault?.kind });
89
+ return true;
63
90
  };
64
91
  async function execute(stage, controller) {
65
92
  const tag = (event) => ({
@@ -103,12 +130,15 @@ export function createRunner(deps) {
103
130
  conclude(stage, paused ? "pending" : "canceled", paused ? null : "canceled by user");
104
131
  return;
105
132
  }
133
+ const fault = faultOf(error);
134
+ if (deferred(stage, fault, reasonOf(error)))
135
+ return;
106
136
  deps.log.write("error", "stage.failed", {
107
137
  projectId: stage.projectId,
108
138
  stage: stage.kind,
109
139
  detail: causedBy(error),
110
140
  });
111
- conclude(stage, "failed", reasonOf(error));
141
+ conclude(stage, "failed", reasonOf(error), fault?.kind);
112
142
  }
113
143
  }
114
144
  function start(stage) {
@@ -0,0 +1 @@
1
+ export {};
@@ -1,5 +1,6 @@
1
1
  import { randomUUID } from "node:crypto";
2
- import { providerError } from "../ports/model.js";
2
+ import { isProviderError, providerError } from "../ports/model.js";
3
+ import { planAccountOf } from "../ports/plan-limits.js";
3
4
  import { attempt } from "./attempt.js";
4
5
  export function stageProviders(deps, context, pieceId) {
5
6
  const ctx = {
@@ -15,7 +16,44 @@ export function stageProviders(deps, context, pieceId) {
15
16
  ...(pieceId === undefined ? {} : { pieceId }),
16
17
  signal: context.signal,
17
18
  };
18
- const schedule = (provider, work) => deps.queue ? deps.queue.run(provider, context.signal, work) : work();
19
+ const queued = (provider, work) => deps.queue ? deps.queue.run(provider, context.signal, work) : work();
20
+ const waiter = { projectId: context.stage.projectId, stage: context.stage.kind };
21
+ // A CLI whose plan allowance is used up is waited for here, outside the queue, so the wait
22
+ // holds no provider slot; the call is then made afresh with a full set of attempts.
23
+ const schedule = async (provider, work) => {
24
+ const account = planAccountOf(provider);
25
+ const gate = deps.limits;
26
+ if (account === undefined || gate === undefined)
27
+ return queued(provider, work);
28
+ for (;;) {
29
+ await gate.ready(account, waiter, context.signal);
30
+ try {
31
+ return await queued(provider, work);
32
+ }
33
+ catch (error) {
34
+ const hit = isProviderError(error) ? error.fault.planLimit : undefined;
35
+ if (hit === undefined || context.signal.aborted)
36
+ throw error;
37
+ gate.exhausted(hit);
38
+ }
39
+ }
40
+ };
41
+ // Metering never fails a call: the answer is already paid for.
42
+ const meter = (call) => {
43
+ if (deps.meter === undefined)
44
+ return;
45
+ try {
46
+ deps.meter.record({ ...call, projectId: context.stage.projectId, stage: context.stage.kind });
47
+ }
48
+ catch (error) {
49
+ deps.log.write("warn", "usage.record", {
50
+ projectId: context.stage.projectId,
51
+ stage: context.stage.kind,
52
+ detail: error instanceof Error ? error.message : String(error),
53
+ });
54
+ }
55
+ };
56
+ const since = (started) => Math.max(0, deps.clock.now().getTime() - started);
19
57
  return {
20
58
  llm: (call, onEvent) => {
21
59
  // Resolved once; the adapter reads the stored key per request, so a key replaced
@@ -32,11 +70,17 @@ export function stageProviders(deps, context, pieceId) {
32
70
  text,
33
71
  ...(reset === undefined ? {} : { reset }),
34
72
  });
35
- return schedule(call.provider, () => attempt(ctx, async (signal, progress) => {
73
+ const started = deps.clock.now().getTime();
74
+ let limits;
75
+ const answered = schedule(call.provider, () => attempt(ctx, async (signal, progress) => {
36
76
  preview("", true);
37
77
  let text = "";
78
+ // Typed-ahead text the panel shows past the committed answer; the next delta
79
+ // replaces it, so the panel never shows a sentence twice.
80
+ let typed = false;
38
81
  let usage = null;
39
82
  let finishReason = null;
83
+ limits = undefined;
40
84
  for await (const event of port.complete({
41
85
  model: call.model,
42
86
  ...(call.thinkingConfig === undefined ? {} : { thinkingConfig: call.thinkingConfig }),
@@ -44,6 +88,7 @@ export function stageProviders(deps, context, pieceId) {
44
88
  messages: call.messages,
45
89
  ...(call.documents === undefined ? {} : { documents: call.documents }),
46
90
  ...(call.webSearch === undefined ? {} : { webSearch: call.webSearch }),
91
+ ...(call.images === undefined ? {} : { images: call.images }),
47
92
  signal,
48
93
  })) {
49
94
  signal.throwIfAborted();
@@ -51,13 +96,22 @@ export function stageProviders(deps, context, pieceId) {
51
96
  progress();
52
97
  if (event.type === "delta") {
53
98
  text += event.text;
99
+ if (typed)
100
+ preview(text, true);
101
+ else
102
+ preview(event.text);
103
+ typed = false;
104
+ }
105
+ else if (event.type === "partial") {
106
+ typed = true;
54
107
  preview(event.text);
55
108
  }
56
109
  else if (event.type === "done") {
57
110
  usage = event.usage;
58
111
  finishReason = event.finishReason;
112
+ limits = event.limits;
59
113
  }
60
- if (event.type !== "activity")
114
+ if (event.type === "delta" || event.type === "done")
61
115
  onEvent?.(event);
62
116
  }
63
117
  const answer = { text, usage, finishReason };
@@ -68,6 +122,28 @@ export function stageProviders(deps, context, pieceId) {
68
122
  }
69
123
  return answer;
70
124
  }, { kind: "llm", streaming: port.capabilities.streams }));
125
+ return answered.then((result) => {
126
+ if (result.ok) {
127
+ const usage = result.value.usage;
128
+ meter({
129
+ kind: "llm",
130
+ provider: call.provider,
131
+ model: usage?.model ?? call.model,
132
+ ...(usage === null
133
+ ? {}
134
+ : {
135
+ tokensIn: usage.inputTokens,
136
+ tokensOut: usage.outputTokens,
137
+ ...(usage.cachedInputTokens === undefined
138
+ ? {}
139
+ : { cachedTokens: usage.cachedInputTokens }),
140
+ }),
141
+ wallMs: since(started),
142
+ ...(limits === undefined ? {} : { limits }),
143
+ });
144
+ }
145
+ return result;
146
+ });
71
147
  },
72
148
  tts: (call, observe) => {
73
149
  const port = deps.registry.tts(call.provider);
@@ -95,13 +171,20 @@ export function stageProviders(deps, context, pieceId) {
95
171
  });
96
172
  }
97
173
  };
98
- return schedule(call.provider, () => attempt({ ...ctx, continuation }, async (signal, progress) => {
174
+ const started = deps.clock.now().getTime();
175
+ const spoken = schedule(call.provider, () => attempt({ ...ctx, continuation }, async (signal, progress) => {
99
176
  notify({ type: "start" });
100
177
  try {
178
+ if (call.dialogue !== undefined && port.capabilities.dialogue !== true)
179
+ throw providerError({
180
+ kind: "unsupported",
181
+ message: `${call.provider} can't speak several voices in one request. Turn off Native multi-speaker under Speakers (Play → Audio, or Edit project → Providers), then Try again.`,
182
+ });
101
183
  const spoken = await port.synthesize({
102
184
  model: call.model,
103
185
  voiceId: call.voiceId,
104
186
  text: call.text,
187
+ ...(call.dialogue === undefined ? {} : { dialogue: call.dialogue }),
105
188
  signal,
106
189
  onActivity: progress,
107
190
  continuation,
@@ -138,17 +221,76 @@ export function stageProviders(deps, context, pieceId) {
138
221
  throw error;
139
222
  }
140
223
  }, { kind: "tts", streaming: port.capabilities.streams }));
224
+ return spoken.then((result) => {
225
+ // Narration is billed by the characters sent, which is the text of this call.
226
+ if (result.ok)
227
+ meter({
228
+ kind: "tts",
229
+ provider: call.provider,
230
+ model: call.model ?? "",
231
+ characters: call.text.length,
232
+ wallMs: since(started),
233
+ });
234
+ return result;
235
+ });
141
236
  },
142
237
  image: (call) => {
143
238
  const port = deps.registry.image(call.provider);
144
- return schedule(call.provider, () => attempt(ctx, (signal) => port.generate({
239
+ const callId = randomUUID();
240
+ // A long job (the Codex CLI reviewing and redrawing its image) says how far it has got
241
+ // on the stage's live panel; the other providers report nothing and show no panel.
242
+ const onProgress = (text) => context.emit({
243
+ type: "llm.preview",
244
+ ...(pieceId === undefined ? {} : { workPieceId: pieceId }),
245
+ projectId: context.stage.projectId,
246
+ stage: context.stage.kind,
247
+ callId,
248
+ label: call.previewLabel ?? "Image",
249
+ text,
250
+ reset: true,
251
+ });
252
+ const started = deps.clock.now().getTime();
253
+ const drawn = schedule(call.provider, () => attempt(ctx, (signal) => port.generate({
145
254
  model: call.model,
146
255
  prompt: call.prompt,
147
256
  aspect: call.aspect,
257
+ ...(call.thinking === undefined ? {} : { thinking: call.thinking }),
258
+ ...(call.reference === undefined ? {} : { reference: call.reference }),
259
+ ...(call.cast === undefined ? {} : { cast: call.cast }),
260
+ onProgress,
148
261
  signal,
149
262
  }),
150
- // One request, one answer: the 300 s runs over the whole call.
151
- { kind: "image" }));
263
+ // One request, one answer: the limit runs over the whole call - 300 s, or the
264
+ // provider's own when it needs longer.
265
+ {
266
+ kind: "image",
267
+ ...(port.timeoutMs === undefined ? {} : { timeoutMs: port.timeoutMs }),
268
+ }));
269
+ return drawn.then((result) => {
270
+ if (result.ok) {
271
+ const { usage, limits } = result.value;
272
+ meter({
273
+ kind: "image",
274
+ provider: call.provider,
275
+ model: call.model,
276
+ images: 1,
277
+ size: call.aspect,
278
+ ...(call.thinking === undefined ? {} : { quality: call.thinking }),
279
+ ...(usage === undefined
280
+ ? {}
281
+ : {
282
+ tokensIn: usage.inputTokens,
283
+ tokensOut: usage.outputTokens,
284
+ ...(usage.cachedInputTokens === undefined
285
+ ? {}
286
+ : { cachedTokens: usage.cachedInputTokens }),
287
+ }),
288
+ wallMs: since(started),
289
+ ...(limits === undefined ? {} : { limits }),
290
+ });
291
+ }
292
+ return result;
293
+ });
152
294
  },
153
295
  animate: (call) => {
154
296
  const port = deps.registry.image(call.provider);
@@ -158,7 +300,8 @@ export function stageProviders(deps, context, pieceId) {
158
300
  kind: "unsupported",
159
301
  message: `${call.provider} can't turn images into video clips. Choose fal.ai or Replicate as the image provider in Edit project → Providers, or turn Animate images off in Edit project → Inputs → Look.`,
160
302
  }));
161
- return schedule(call.provider, () => attempt(ctx, (signal) => animate({
303
+ const started = deps.clock.now().getTime();
304
+ const animated = schedule(call.provider, () => attempt(ctx, (signal) => animate({
162
305
  model: call.model,
163
306
  prompt: call.prompt,
164
307
  image: call.image,
@@ -168,6 +311,18 @@ export function stageProviders(deps, context, pieceId) {
168
311
  }),
169
312
  // Queued and slow: the 900 s runs over the whole call, polling included.
170
313
  { kind: "video" }));
314
+ return animated.then((result) => {
315
+ if (result.ok)
316
+ meter({
317
+ kind: "video",
318
+ provider: call.provider,
319
+ model: call.model,
320
+ seconds: call.seconds,
321
+ size: call.aspect,
322
+ wallMs: since(started),
323
+ });
324
+ return result;
325
+ });
171
326
  },
172
327
  forPiece: (piece) => stageProviders(deps, context, piece),
173
328
  };
@@ -0,0 +1,43 @@
1
+ import { isProviderError } from "../ports/model.js";
2
+ // The second tier of the retry policy. `attempt.ts` retries a failed call a few times within
3
+ // seconds; when those run out on a failure that time can fix, the step is put back to wait
4
+ // and runs again later, so a rate limit, a timeout or a dropped connection costs the user
5
+ // nothing but time. The wait is written down, not held in memory: a restart keeps it.
6
+ // The failures worth waiting out. A refusal, a rejected or missing key, an unsupported
7
+ // request and a lost submission are the provider's final answer (or need a person), so
8
+ // they are shown at once. `other` covers a request the provider rejected as well as an
9
+ // unreadable answer; the quick in-call retries are all it gets.
10
+ export const waitableKinds = ["rate_limit", "timeout", "dropped"];
11
+ // Four waits after the first failed run: about 2, 4, 8 and 16 minutes, 30 in all.
12
+ export const autoRetryLimit = 4;
13
+ export const autoRetryBaseMs = 2 * 60_000;
14
+ // ±25 %, so a burst of steps that hit the same limit together do not all return together.
15
+ export const autoRetryJitter = 0.25;
16
+ // A provider asking for longer than this (a daily quota) is reported rather than waited on.
17
+ export const retryAfterCeilingMs = 60 * 60_000;
18
+ // A slice may wrap the wrapper's error in its own sentence ("Image 3: ..."); the kind is on
19
+ // the cause.
20
+ export function faultOf(error) {
21
+ let current = error;
22
+ for (let depth = 0; depth < 6 && current !== undefined && current !== null; depth += 1) {
23
+ if (isProviderError(current))
24
+ return current.fault;
25
+ current = current instanceof Error ? current.cause : undefined;
26
+ }
27
+ return undefined;
28
+ }
29
+ // `retries` is how many waits the step already had. Undefined: report the failure now.
30
+ export function autoRetryDelay(fault, retries, random) {
31
+ if (fault === undefined || !waitableKinds.includes(fault.kind))
32
+ return undefined;
33
+ if (retries >= autoRetryLimit)
34
+ return undefined;
35
+ const nominal = autoRetryBaseMs * 2 ** retries;
36
+ const jittered = Math.round(nominal * (1 - autoRetryJitter + 2 * autoRetryJitter * random()));
37
+ if (fault.retryAfterMs === undefined)
38
+ return jittered;
39
+ if (fault.retryAfterMs > retryAfterCeilingMs)
40
+ return undefined;
41
+ // The provider's own wait is the floor; ours still spreads the burst above it.
42
+ return Math.max(fault.retryAfterMs, jittered);
43
+ }
@@ -0,0 +1,134 @@
1
+ import { isProviderError } from "../ports/model.js";
2
+ import { attempt } from "./attempt.js";
3
+ export async function standaloneLlm(deps, call) {
4
+ const port = deps.registry.llm(call.provider);
5
+ const started = deps.clock.now().getTime();
6
+ const answer = await wrapped(deps, call, async (signal, progress) => {
7
+ let text = "";
8
+ let usage = null;
9
+ let limits;
10
+ for await (const event of port.complete({
11
+ model: call.model,
12
+ ...(call.thinking === undefined ? {} : { thinking: call.thinking }),
13
+ messages: call.messages,
14
+ signal,
15
+ })) {
16
+ signal.throwIfAborted();
17
+ // Every event is a sign of life, so the idle clock restarts here.
18
+ progress();
19
+ if (event.type === "delta")
20
+ text += event.text;
21
+ else if (event.type === "done") {
22
+ usage = event.usage;
23
+ limits = event.limits;
24
+ }
25
+ }
26
+ return { text, usage, ...(limits === undefined ? {} : { limits }) };
27
+ }, { kind: "llm", streaming: port.capabilities.streams });
28
+ meter(deps, call, {
29
+ kind: "llm",
30
+ provider: call.provider,
31
+ model: answer.usage?.model ?? call.model,
32
+ ...tokens(answer.usage ?? undefined),
33
+ wallMs: Math.max(0, deps.clock.now().getTime() - started),
34
+ ...(answer.limits === undefined ? {} : { limits: answer.limits }),
35
+ });
36
+ return answer;
37
+ }
38
+ export async function standaloneImage(deps, call) {
39
+ const port = deps.registry.image(call.provider);
40
+ const started = deps.clock.now().getTime();
41
+ const image = await wrapped(deps, call, (signal) => port.generate({
42
+ model: call.model,
43
+ prompt: call.prompt,
44
+ aspect: call.aspect,
45
+ signal,
46
+ }), { kind: "image", ...(port.timeoutMs === undefined ? {} : { timeoutMs: port.timeoutMs }) });
47
+ meter(deps, call, {
48
+ kind: "image",
49
+ provider: call.provider,
50
+ model: call.model,
51
+ images: 1,
52
+ size: call.aspect,
53
+ ...tokens(image.usage),
54
+ wallMs: Math.max(0, deps.clock.now().getTime() - started),
55
+ ...(image.limits === undefined ? {} : { limits: image.limits }),
56
+ });
57
+ return image;
58
+ }
59
+ async function wrapped(deps, use, call, opts) {
60
+ // The wrapper names its log lines by project and stage; these name what the call was for.
61
+ const label = `${use.purpose}:${use.owner.kind}:${use.owner.id}`;
62
+ let next = 0;
63
+ const attempts = {
64
+ start: () => {
65
+ next += 1;
66
+ return String(next);
67
+ },
68
+ end: () => { },
69
+ };
70
+ const stage = opts.kind === "image" ? "images" : "article";
71
+ try {
72
+ const result = await attempt({
73
+ work: {
74
+ projectId: label,
75
+ revisionId: label,
76
+ workId: label,
77
+ stageId: label,
78
+ kind: stage,
79
+ fingerprint: label,
80
+ },
81
+ maySubmit: () => true,
82
+ clock: deps.clock,
83
+ log: deps.log,
84
+ attempts,
85
+ projectId: label,
86
+ stage,
87
+ stageId: label,
88
+ signal: use.signal ?? new AbortController().signal,
89
+ }, call, opts);
90
+ if (!result.ok)
91
+ throw new Error("The provider did not take the request.");
92
+ return result.value;
93
+ }
94
+ catch (error) {
95
+ throw standaloneError(error);
96
+ }
97
+ }
98
+ // The wrapper's own advice points at a stage's buttons ("use Try again"); a standalone call
99
+ // has none, and each caller says itself where to go next, so that advice is dropped.
100
+ function standaloneError(error) {
101
+ if (!isProviderError(error))
102
+ return error;
103
+ const kept = error.message
104
+ .replace(/,? then use (?:Retry stage|Try again)(?:, or choose another model in the Providers section of Edit project)?/g, "")
105
+ .split(/(?<=\.)\s+/)
106
+ .filter((sentence) => !/Retry stage|Try again|Edit project/.test(sentence))
107
+ .join(" ")
108
+ .trim();
109
+ if (kept === error.message || kept === "")
110
+ return error;
111
+ return Object.assign(new Error(kept, { cause: error }), { fault: error.fault });
112
+ }
113
+ function tokens(usage) {
114
+ if (usage === undefined)
115
+ return {};
116
+ return {
117
+ tokensIn: usage.inputTokens,
118
+ tokensOut: usage.outputTokens,
119
+ ...(usage.cachedInputTokens === undefined ? {} : { cachedTokens: usage.cachedInputTokens }),
120
+ };
121
+ }
122
+ // Metering never fails a call: the answer is already paid for.
123
+ function meter(deps, use, call) {
124
+ if (deps.meter === undefined)
125
+ return;
126
+ try {
127
+ deps.meter.record({ ...call, owner: use.owner, purpose: use.purpose });
128
+ }
129
+ catch (error) {
130
+ deps.log.write("warn", "usage.record", {
131
+ detail: `${use.purpose} for ${use.owner.kind} ${use.owner.id}: ${error instanceof Error ? error.message : String(error)}`,
132
+ });
133
+ }
134
+ }
@@ -29,9 +29,35 @@ export function ownsCurrentWork(db, work, pieceId) {
29
29
  .prepare(`SELECT 1 FROM revision_work_reservations r JOIN project_heads h ON h.project_id=r.project_id AND h.revision_id=r.revision_id JOIN project_revisions v ON v.id=r.revision_id JOIN json_each(v.fingerprints) f ON f.key=COALESCE(r.logical_key,r.work_key) AND f.value=COALESCE(r.desired_fingerprint,r.fingerprint) WHERE r.project_id=? AND r.work_id=? AND r.piece_id IS ? AND (r.piece_id IS NULL OR EXISTS(SELECT 1 FROM revision_work_pieces p WHERE p.id=r.piece_id AND p.work_id=r.work_id AND p.work_key=r.work_key AND p.fingerprint=r.fingerprint))`)
30
30
  .get(work.projectId, work.workId, pieceId) !== undefined);
31
31
  }
32
- export function finishWork(db, work, state, reason) {
32
+ export function finishWork(db, work, state, reason, kind = null) {
33
33
  if (!workExists(db, work))
34
34
  return;
35
35
  const normalized = state === "provided" || state === "skipped" ? "done" : state;
36
- db.prepare(`UPDATE revision_work SET state=?,failure_reason=?,dispatch_state=CASE WHEN ?='pending' THEN 'held' ELSE dispatch_state END WHERE id=?`).run(normalized, reason, normalized, work.workId);
36
+ db.prepare(`UPDATE revision_work SET state=?,failure_reason=?,failure_kind=?,retry_at=NULL,dispatch_state=CASE WHEN ?='pending' THEN 'held' ELSE dispatch_state END WHERE id=?`).run(normalized, reason, normalized === "failed" ? kind : null, normalized, work.workId);
37
+ }
38
+ // How many times the automatic retry already put this step back to wait.
39
+ export function autoRetriesOf(db, work) {
40
+ const row = db.prepare("SELECT auto_retries FROM revision_work WHERE id=?").get(work.workId);
41
+ return typeof row?.auto_retries === "number" ? row.auto_retries : 0;
42
+ }
43
+ // A running step that failed on something time can fix goes back to `pending` with the time
44
+ // it may start again. It stays dispatchable, unlike a paused one, and its unfinished pieces
45
+ // may be sent again: the failure that ended them was not a lost submission.
46
+ export function deferWork(db, work, at, reason, kind) {
47
+ if (!workExists(db, work))
48
+ return false;
49
+ const moved = db
50
+ .prepare(`UPDATE revision_work SET state='pending',retry_at=?,auto_retries=auto_retries+1,failure_reason=?,failure_kind=? WHERE id=? AND state='running' AND dispatch_state='allowed'`)
51
+ .run(at, reason, kind, work.workId).changes === 1;
52
+ if (moved)
53
+ db.prepare("UPDATE revision_work_pieces SET state='pending' WHERE work_id=? AND state IN ('failed','running')").run(work.workId);
54
+ return moved;
55
+ }
56
+ // The projects with a wait that has run out, their wait cleared so the next tick may claim
57
+ // them. A paused project keeps its wait: Resume ticks it, and nothing else should.
58
+ export function dueRetries(db, now) {
59
+ const rows = db
60
+ .prepare(`UPDATE revision_work SET retry_at=NULL WHERE state='pending' AND retry_at IS NOT NULL AND retry_at<=? AND NOT EXISTS(SELECT 1 FROM project_controls c WHERE c.project_id=revision_work.project_id AND c.paused=1) RETURNING project_id`)
61
+ .all(now);
62
+ return [...new Set(rows.map((row) => String(row.project_id)))];
37
63
  }