@gentbajko/slopify 3.25.9 → 4.1.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 (718) hide show
  1. package/dist/adapters/account-limits.js +30 -0
  2. package/dist/adapters/alignment/audio.js +1 -1
  3. package/dist/adapters/alignment/cache.js +4 -6
  4. package/dist/adapters/alignment/ctc.js +1 -2
  5. package/dist/adapters/alignment/lock.js +4 -3
  6. package/dist/adapters/alignment/multilingual.js +2 -4
  7. package/dist/adapters/alignment/prefetch.js +2 -1
  8. package/dist/adapters/alignment/runner.js +1 -1
  9. package/dist/adapters/alignment/worker.js +3 -2
  10. package/dist/adapters/explain.js +10 -12
  11. package/dist/adapters/ffmpeg.js +19 -1
  12. package/dist/adapters/host-cli/index.js +21 -4
  13. package/dist/adapters/host-cli/transport.js +2 -1
  14. package/dist/adapters/http-failure.js +53 -0
  15. package/dist/adapters/image/bytes.js +2 -1
  16. package/dist/adapters/image/codex.js +42 -9
  17. package/dist/adapters/image/fal.js +21 -53
  18. package/dist/adapters/image/google.js +33 -56
  19. package/dist/adapters/image/models.js +2 -2
  20. package/dist/adapters/image/openai.js +22 -48
  21. package/dist/adapters/image/reference.js +36 -0
  22. package/dist/adapters/image/replicate.js +21 -49
  23. package/dist/adapters/image/video.js +2 -1
  24. package/dist/adapters/interest/search-interest.js +113 -0
  25. package/dist/adapters/key-probes.js +54 -3
  26. package/dist/adapters/llm/antigravity-models.js +1 -1
  27. package/dist/adapters/llm/antigravity.js +11 -6
  28. package/dist/adapters/llm/assistant-cli.js +388 -0
  29. package/dist/adapters/llm/child-session.js +83 -0
  30. package/dist/adapters/llm/claude-code-models.js +52 -99
  31. package/dist/adapters/llm/claude-code.js +16 -4
  32. package/dist/adapters/llm/codex-limits.js +27 -68
  33. package/dist/adapters/llm/codex-models.js +1 -1
  34. package/dist/adapters/llm/codex-server-models.js +61 -109
  35. package/dist/adapters/llm/codex.js +12 -1
  36. package/dist/adapters/llm/document-workspace.js +3 -2
  37. package/dist/adapters/llm/image-workspace.js +3 -2
  38. package/dist/adapters/llm/openrouter.js +12 -48
  39. package/dist/adapters/llm/run-cli.js +9 -24
  40. package/dist/adapters/llm/sse-lines.js +5 -1
  41. package/dist/adapters/model-access.js +37 -0
  42. package/dist/adapters/tts/cartesia.js +15 -46
  43. package/dist/adapters/tts/elevenlabs.js +20 -52
  44. package/dist/adapters/tts/gemini.js +21 -41
  45. package/dist/adapters/tts/inworld-async.js +6 -2
  46. package/dist/adapters/tts/inworld.js +6 -2
  47. package/dist/adapters/tts/openai.js +20 -50
  48. package/dist/adapters/tts/system.js +2 -1
  49. package/dist/assets/models-lifecycle.yaml +6 -0
  50. package/dist/assistant-wiring.js +60 -0
  51. package/dist/autopilot-timers.js +59 -0
  52. package/dist/catalog/lifecycle.js +52 -0
  53. package/dist/catalog/propose.js +204 -0
  54. package/dist/catalog/registry.js +23 -14
  55. package/dist/catalog/schema.js +4 -1
  56. package/dist/catalog/store.js +39 -0
  57. package/dist/catalog/validate.js +5 -2
  58. package/dist/edge/autostart/native.js +3 -5
  59. package/dist/edge/autostart/prompt.js +2 -1
  60. package/dist/edge/autostart/service.js +3 -4
  61. package/dist/edge/cli-args.js +3 -4
  62. package/dist/edge/cli.js +2 -1
  63. package/dist/edge/docker-install/apply.js +11 -11
  64. package/dist/edge/docker-install/run.js +2 -1
  65. package/dist/edge/docker-install/state.js +4 -3
  66. package/dist/edge/docker-install/tree.js +2 -4
  67. package/dist/edge/docker-install/volume.js +3 -2
  68. package/dist/edge/docker.js +5 -4
  69. package/dist/edge/events/hub.js +5 -5
  70. package/dist/edge/http/actions.js +11 -32
  71. package/dist/edge/http/actor.js +5 -3
  72. package/dist/edge/http/app.js +86 -11
  73. package/dist/edge/http/assistant.js +129 -0
  74. package/dist/edge/http/audio-preview.js +4 -11
  75. package/dist/edge/http/auditions.js +1 -1
  76. package/dist/edge/http/autostart.js +5 -5
  77. package/dist/edge/http/backups.js +2 -4
  78. package/dist/edge/http/channel-memory.js +9 -22
  79. package/dist/edge/http/channels.js +14 -27
  80. package/dist/edge/http/checkpoints.js +39 -21
  81. package/dist/edge/http/design-templates.js +167 -0
  82. package/dist/edge/http/designs.js +178 -0
  83. package/dist/edge/http/diagnostics.js +5 -1
  84. package/dist/edge/http/document-themes.js +5 -14
  85. package/dist/edge/http/draft-files.js +1 -3
  86. package/dist/edge/http/draft-problem.js +5 -10
  87. package/dist/edge/http/drafts.js +3 -3
  88. package/dist/edge/http/entries.js +6 -9
  89. package/dist/edge/http/extension-auth.js +27 -0
  90. package/dist/edge/http/external-reviews.js +12 -11
  91. package/dist/edge/http/files.js +5 -11
  92. package/dist/edge/http/folder-location.js +1 -4
  93. package/dist/edge/http/fonts.js +0 -7
  94. package/dist/edge/http/home.js +47 -14
  95. package/dist/edge/http/host-cli.js +18 -8
  96. package/dist/edge/http/ids.js +8 -0
  97. package/dist/edge/http/keywords.js +35 -0
  98. package/dist/edge/http/model-checks.js +132 -0
  99. package/dist/edge/http/narration-peaks.js +5 -13
  100. package/dist/edge/http/onboarding.js +30 -26
  101. package/dist/edge/http/open-folder.js +5 -10
  102. package/dist/edge/http/outputs.js +7 -14
  103. package/dist/edge/http/patch-notes.js +6 -12
  104. package/dist/edge/http/planning.js +3 -7
  105. package/dist/edge/http/problem.js +2 -4
  106. package/dist/edge/http/project-create.js +2 -1
  107. package/dist/edge/http/project-templates.js +31 -19
  108. package/dist/edge/http/projects.js +24 -16
  109. package/dist/edge/http/prompts.js +9 -16
  110. package/dist/edge/http/pronunciations.js +1 -2
  111. package/dist/edge/http/providers.js +48 -34
  112. package/dist/edge/http/refusal.js +29 -0
  113. package/dist/edge/http/remote-access.js +40 -0
  114. package/dist/edge/http/remote-guard.js +71 -0
  115. package/dist/edge/http/request-guard.js +91 -0
  116. package/dist/edge/http/reviews.js +12 -25
  117. package/dist/edge/http/revision-files.js +6 -11
  118. package/dist/edge/http/revisions.js +14 -19
  119. package/dist/edge/http/run-cost.js +1 -2
  120. package/dist/edge/http/same-page.js +80 -0
  121. package/dist/edge/http/schedules.js +69 -40
  122. package/dist/edge/http/settings.js +27 -14
  123. package/dist/edge/http/social.js +283 -0
  124. package/dist/edge/http/staging.js +3 -5
  125. package/dist/edge/http/storage-files.js +5 -7
  126. package/dist/edge/http/storage.js +33 -25
  127. package/dist/edge/http/studio.js +94 -69
  128. package/dist/edge/http/style-preview.js +3 -6
  129. package/dist/edge/http/subtitles.js +3 -17
  130. package/dist/edge/http/template-studio.js +436 -0
  131. package/dist/edge/http/trash.js +11 -12
  132. package/dist/edge/http/tutorial.js +1 -2
  133. package/dist/edge/http/tutorials.js +5 -11
  134. package/dist/edge/http/update.js +7 -13
  135. package/dist/edge/http/waveform.js +3 -6
  136. package/dist/edge/http/youtube-edits.js +3 -4
  137. package/dist/edge/mcp/assistant.js +279 -0
  138. package/dist/edge/mcp/attribution.js +8 -2
  139. package/dist/edge/mcp/call-route.js +11 -2
  140. package/dist/edge/mcp/excluded-routes.js +29 -3
  141. package/dist/edge/mcp/http.js +11 -2
  142. package/dist/edge/mcp/instructions.js +6 -4
  143. package/dist/edge/mcp/library-tools.js +9 -5
  144. package/dist/edge/mcp/remake-tools.js +7 -4
  145. package/dist/edge/mcp/route-tool.js +35 -15
  146. package/dist/edge/mcp/server.js +13 -8
  147. package/dist/edge/mcp/tools-channels.js +11 -9
  148. package/dist/edge/mcp/tools-designs.js +165 -0
  149. package/dist/edge/mcp/tools-library.js +19 -7
  150. package/dist/edge/mcp/tools-planning.js +40 -21
  151. package/dist/edge/mcp/tools-projects.js +38 -11
  152. package/dist/edge/mcp/tools-publishing.js +9 -9
  153. package/dist/edge/mcp/tools-runs.js +23 -10
  154. package/dist/edge/mcp/tools-schedules.js +32 -8
  155. package/dist/edge/mcp/tools-setup.js +75 -11
  156. package/dist/edge/mcp/tools-social.js +47 -0
  157. package/dist/edge/mcp/tools-storage.js +11 -3
  158. package/dist/edge/mcp/tools-template-studio.js +166 -0
  159. package/dist/edge/mcp/tools.js +6 -0
  160. package/dist/edge/remote-access/cli.js +71 -0
  161. package/dist/edge/remote-access/model.js +32 -0
  162. package/dist/edge/remote-access/rules.js +207 -0
  163. package/dist/edge/remote-access/service.js +203 -0
  164. package/dist/edge/remote-access/store.js +44 -0
  165. package/dist/edge/remote-access/views.js +43 -0
  166. package/dist/extension/slopify-studio-chrome.zip +0 -0
  167. package/dist/extension/slopify-studio-firefox.zip +0 -0
  168. package/dist/host-cli/install.js +2 -2
  169. package/dist/host-cli/open-folder.js +2 -2
  170. package/dist/host-cli/paths.js +2 -4
  171. package/dist/host-cli/server.js +4 -4
  172. package/dist/host-cli/service.js +3 -3
  173. package/dist/host-cli/status.js +7 -7
  174. package/dist/kernel/db/migrations/0063-model-checks.sql +20 -0
  175. package/dist/kernel/db/migrations/0064-template-studio.sql +46 -0
  176. package/dist/kernel/db/migrations/0065-promise-and-assistant-reviews.sql +58 -0
  177. package/dist/kernel/db/migrations/0066-assistant.sql +44 -0
  178. package/dist/kernel/db/migrations/0067-autopilot.sql +6 -0
  179. package/dist/kernel/db/migrations/0068-topic-scores.sql +17 -0
  180. package/dist/kernel/db/migrations/0070-designs.sql +53 -0
  181. package/dist/kernel/db/migrations/0071-design-usage.sql +33 -0
  182. package/dist/kernel/db/migrations/0072-design-templates.sql +33 -0
  183. package/dist/kernel/db/migrations/0073-schedule-design-kind.sql +5 -0
  184. package/dist/kernel/db/migrations/0074-template-bakeoffs.sql +44 -0
  185. package/dist/kernel/db/migrations/0075-social-posts.sql +25 -0
  186. package/dist/kernel/db/migrations/0076-channel-social.sql +5 -0
  187. package/dist/kernel/db/migrations/0077-post-prompts.sql +17 -0
  188. package/dist/kernel/db/migrations/0078-uploaded-from-links.sql +7 -0
  189. package/dist/kernel/db/tx.js +2 -4
  190. package/dist/kernel/errors.js +27 -1
  191. package/dist/kernel/json-answer.js +15 -0
  192. package/dist/kernel/lock.js +4 -9
  193. package/dist/kernel/paths.js +8 -0
  194. package/dist/kernel/pipeline.js +12 -0
  195. package/dist/kernel/ports/assistant-cli.js +4 -0
  196. package/dist/kernel/ports/host-cli.js +3 -0
  197. package/dist/kernel/ports/interest.js +4 -0
  198. package/dist/kernel/ports/languages.js +0 -3
  199. package/dist/kernel/ports/model.js +2 -0
  200. package/dist/kernel/ports/settings-places.js +31 -0
  201. package/dist/kernel/ports/system-speech.js +31 -3
  202. package/dist/kernel/runner/attempt.js +14 -4
  203. package/dist/kernel/runner/audition.js +3 -1
  204. package/dist/kernel/runner/checkpoint-authority.js +2 -1
  205. package/dist/kernel/runner/index.js +8 -10
  206. package/dist/kernel/runner/model-access.js +40 -0
  207. package/dist/kernel/runner/model-check.js +153 -0
  208. package/dist/kernel/runner/providers.js +18 -12
  209. package/dist/kernel/runner/standalone.js +11 -7
  210. package/dist/main.js +338 -21
  211. package/dist/model-catalog.js +20 -0
  212. package/dist/model-checks.js +166 -0
  213. package/dist/patch-notes/4.0.0.md +51 -0
  214. package/dist/patch-notes/4.1.0.md +42 -0
  215. package/dist/patch-notes/index.json +12 -0
  216. package/dist/slices/admission/heads.js +2 -1
  217. package/dist/slices/admission/listing-facts.js +95 -0
  218. package/dist/slices/admission/repo.js +11 -7
  219. package/dist/slices/admission/rules.js +50 -15
  220. package/dist/slices/admission/schema.js +8 -0
  221. package/dist/slices/admission/stage-writes.js +0 -3
  222. package/dist/slices/admission/start.js +48 -31
  223. package/dist/slices/admission/state-words.js +34 -5
  224. package/dist/slices/admission/substitute.js +7 -4
  225. package/dist/slices/agents/attention-posts.js +31 -0
  226. package/dist/slices/agents/attention-templates.js +33 -0
  227. package/dist/slices/agents/attention.js +10 -1
  228. package/dist/slices/agents/connection.js +2 -6
  229. package/dist/slices/agents/model.js +6 -3
  230. package/dist/slices/agents/repo.js +15 -7
  231. package/dist/slices/article/continuation.js +0 -63
  232. package/dist/slices/article/plain.js +3 -2
  233. package/dist/slices/article/segments.js +2 -62
  234. package/dist/slices/assistant/access.js +1 -0
  235. package/dist/slices/assistant/asks.js +80 -0
  236. package/dist/slices/assistant/cost.js +54 -0
  237. package/dist/slices/assistant/gate.js +222 -0
  238. package/dist/slices/assistant/model.js +86 -0
  239. package/dist/slices/assistant/names.js +23 -0
  240. package/dist/slices/assistant/prompt.js +74 -0
  241. package/dist/slices/assistant/repo.js +74 -0
  242. package/dist/slices/assistant/tokens.js +18 -0
  243. package/dist/slices/assistant/turn.js +344 -0
  244. package/dist/slices/autopilot/buffer.js +76 -0
  245. package/dist/slices/autopilot/model.js +14 -0
  246. package/dist/slices/autopilot/notify.js +27 -0
  247. package/dist/slices/autopilot/repo.js +34 -0
  248. package/dist/slices/autopilot/signals.js +194 -0
  249. package/dist/slices/autopilot/warnings.js +85 -0
  250. package/dist/slices/autopilot/weekly.js +100 -0
  251. package/dist/slices/backups/files.js +2 -6
  252. package/dist/slices/backups/repo.js +3 -15
  253. package/dist/slices/backups/service.js +8 -7
  254. package/dist/slices/channels/cast-images.js +3 -2
  255. package/dist/slices/channels/model.js +0 -1
  256. package/dist/slices/channels/rebrand.js +0 -3
  257. package/dist/slices/channels/repo.js +4 -0
  258. package/dist/slices/channels/schema.js +7 -1
  259. package/dist/slices/channels/service.js +10 -0
  260. package/dist/slices/channels/videos.js +3 -15
  261. package/dist/slices/checkpoints/index.js +0 -1
  262. package/dist/slices/control/providers.js +2 -28
  263. package/dist/slices/designs/artwork.js +66 -0
  264. package/dist/slices/designs/budget.js +23 -0
  265. package/dist/slices/designs/calls.js +124 -0
  266. package/dist/slices/designs/check-context.js +38 -0
  267. package/dist/slices/designs/check-text.js +43 -0
  268. package/dist/slices/designs/fields.js +88 -0
  269. package/dist/slices/designs/frames.js +34 -0
  270. package/dist/slices/designs/gate.js +130 -0
  271. package/dist/slices/designs/layout.js +293 -0
  272. package/dist/slices/designs/model.js +35 -0
  273. package/dist/slices/designs/pdf-job.js +23 -0
  274. package/dist/slices/designs/pdf-pages.js +30 -0
  275. package/dist/slices/designs/pdf-runner.js +68 -0
  276. package/dist/slices/designs/pdf-worker.js +24 -0
  277. package/dist/slices/designs/picture-size.js +79 -0
  278. package/dist/slices/designs/pictures.js +55 -0
  279. package/dist/slices/designs/quote.js +24 -0
  280. package/dist/slices/designs/render-args.js +36 -0
  281. package/dist/slices/designs/render-input.js +59 -0
  282. package/dist/slices/designs/render.js +215 -0
  283. package/dist/slices/designs/repo.js +101 -0
  284. package/dist/slices/designs/requests.js +80 -0
  285. package/dist/slices/designs/runner.js +239 -0
  286. package/dist/slices/designs/sample-repo.js +91 -0
  287. package/dist/slices/designs/sample.js +223 -0
  288. package/dist/slices/designs/schema.js +87 -0
  289. package/dist/slices/designs/sections.js +44 -0
  290. package/dist/slices/designs/service.js +233 -0
  291. package/dist/slices/designs/steps-repo.js +102 -0
  292. package/dist/slices/designs/studio-kind.js +39 -0
  293. package/dist/slices/designs/template-checks.js +44 -0
  294. package/dist/slices/designs/template-make.js +118 -0
  295. package/dist/slices/designs/templates.js +218 -0
  296. package/dist/slices/document/fonts.js +2 -1
  297. package/dist/slices/document/model.js +7 -4
  298. package/dist/slices/document/render.js +2 -1
  299. package/dist/slices/document/theme-schema.js +1 -1
  300. package/dist/slices/document/theme.js +49 -3
  301. package/dist/slices/episodes/summarize.js +6 -2
  302. package/dist/slices/estimate/index.js +72 -28
  303. package/dist/slices/estimate/requests.js +4 -4
  304. package/dist/slices/fixes/rules.js +3 -1
  305. package/dist/slices/fonts/catalog.js +5 -3
  306. package/dist/slices/fonts/coverage.js +2 -1
  307. package/dist/slices/fonts/discovery.js +3 -2
  308. package/dist/slices/fonts/files.js +2 -6
  309. package/dist/slices/fonts/index.js +0 -1
  310. package/dist/slices/fonts/sfnt.js +45 -10
  311. package/dist/slices/images/appearance.js +8 -16
  312. package/dist/slices/images/scenes.js +102 -28
  313. package/dist/slices/keywords/digest.js +85 -0
  314. package/dist/slices/keywords/facts.js +92 -0
  315. package/dist/slices/keywords/fill.js +80 -0
  316. package/dist/slices/keywords/model.js +75 -0
  317. package/dist/slices/keywords/rules.js +187 -0
  318. package/dist/slices/keywords/schema.js +28 -0
  319. package/dist/slices/keywords/specs.js +260 -0
  320. package/dist/slices/keywords/values.js +74 -0
  321. package/dist/slices/library/lint.js +29 -12
  322. package/dist/slices/library/model.js +2 -0
  323. package/dist/slices/library/photorealistic.js +4 -12
  324. package/dist/slices/library/rename.js +9 -7
  325. package/dist/slices/library/save.js +14 -10
  326. package/dist/slices/library/slots.js +37 -11
  327. package/dist/slices/library/transfer.js +5 -4
  328. package/dist/slices/library/used-by.js +25 -10
  329. package/dist/slices/loudness/loudnorm.js +53 -12
  330. package/dist/slices/loudness/model.js +3 -2
  331. package/dist/slices/loudness/sentence-level.js +2 -1
  332. package/dist/slices/model-checks/cost.js +61 -0
  333. package/dist/slices/model-checks/listing.js +124 -0
  334. package/dist/slices/model-checks/model.js +20 -0
  335. package/dist/slices/model-checks/record.js +32 -0
  336. package/dist/slices/model-checks/repo.js +120 -0
  337. package/dist/slices/model-checks/rules.js +125 -0
  338. package/dist/slices/model-checks/run.js +143 -0
  339. package/dist/slices/model-checks/service.js +198 -0
  340. package/dist/slices/model-upkeep/model.js +1 -1
  341. package/dist/slices/model-upkeep/switch.js +72 -27
  342. package/dist/slices/model-upkeep/usage.js +39 -13
  343. package/dist/slices/narration/breaths.js +3 -1
  344. package/dist/slices/narration/clean-pieces.js +3 -1
  345. package/dist/slices/narration/concat.js +2 -1
  346. package/dist/slices/narration/pauses-model.js +3 -2
  347. package/dist/slices/narration/peaks.js +3 -4
  348. package/dist/slices/narration/pronunciation-chunks.js +4 -3
  349. package/dist/slices/narration/pronunciation.js +6 -4
  350. package/dist/slices/narration/steering.js +4 -3
  351. package/dist/slices/notifications/notifier.js +4 -3
  352. package/dist/slices/notifications/rules.js +5 -2
  353. package/dist/slices/notifications/settings.js +4 -12
  354. package/dist/slices/notifications/subject.js +4 -3
  355. package/dist/slices/onboarding/copy.js +21 -3
  356. package/dist/slices/onboarding/first-run.js +16 -40
  357. package/dist/slices/onboarding/full-video.js +3 -2
  358. package/dist/slices/onboarding/install.js +6 -4
  359. package/dist/slices/onboarding/model.js +26 -37
  360. package/dist/slices/onboarding/packs.js +22 -22
  361. package/dist/slices/onboarding/preferred-model.js +14 -0
  362. package/dist/slices/onboarding/quick-short.js +105 -65
  363. package/dist/slices/onboarding/sample.js +4 -2
  364. package/dist/slices/onboarding/state.js +9 -11
  365. package/dist/slices/onboarding/template.js +8 -6
  366. package/dist/slices/outputs/add.js +3 -2
  367. package/dist/slices/outputs/service.js +2 -2
  368. package/dist/slices/patch-notes/library.js +4 -3
  369. package/dist/slices/play-drafts/convert.js +4 -0
  370. package/dist/slices/play-drafts/readiness.js +3 -3
  371. package/dist/slices/play-drafts/review-inputs.js +9 -3
  372. package/dist/slices/play-drafts/review.js +5 -4
  373. package/dist/slices/play-drafts/schema.js +4 -0
  374. package/dist/slices/play-drafts/service.js +17 -7
  375. package/dist/slices/project-templates/from-project.js +9 -2
  376. package/dist/slices/project-templates/next-chapter.js +2 -1
  377. package/dist/slices/project-templates/provider-defaults.js +22 -0
  378. package/dist/slices/project-templates/repo.js +6 -1
  379. package/dist/slices/project-templates/setup-sections.js +26 -0
  380. package/dist/slices/project-templates/setup.js +14 -1
  381. package/dist/slices/project-templates/stand-ins.js +22 -0
  382. package/dist/slices/rebuild/activity.js +2 -0
  383. package/dist/slices/rebuild/admission-repo.js +3 -2
  384. package/dist/slices/rebuild/narration-history.js +4 -5
  385. package/dist/slices/rebuild/narration-retry.js +2 -1
  386. package/dist/slices/rebuild/preview-details.js +32 -14
  387. package/dist/slices/rebuild/preview-plan.js +3 -8
  388. package/dist/slices/rebuild/recipe-build.js +8 -3
  389. package/dist/slices/rebuild/recipe-describe.js +3 -3
  390. package/dist/slices/rebuild/recipe-digest.js +58 -0
  391. package/dist/slices/rebuild/recipe-input-schema.js +10 -0
  392. package/dist/slices/rebuild/recipe-keywords.js +71 -0
  393. package/dist/slices/rebuild/recipe-legacy.js +5 -1
  394. package/dist/slices/rebuild/recipe-model.js +4 -0
  395. package/dist/slices/rebuild/recipe-narration-text.js +2 -1
  396. package/dist/slices/rebuild/recipe-preparation.js +4 -4
  397. package/dist/slices/rebuild/recipe-reference.js +8 -4
  398. package/dist/slices/rebuild/recipe-reviews.js +27 -7
  399. package/dist/slices/rebuild/recipe-save.js +8 -5
  400. package/dist/slices/rebuild/recipe-scenes.js +40 -11
  401. package/dist/slices/rebuild/recipe-shorts.js +8 -8
  402. package/dist/slices/rebuild/recipe-social.js +46 -0
  403. package/dist/slices/rebuild/recipe-text.js +64 -20
  404. package/dist/slices/rebuild/recipe-validation.js +102 -31
  405. package/dist/slices/rebuild/recipe-visual.js +55 -16
  406. package/dist/slices/rebuild/recipe-voices.js +3 -2
  407. package/dist/slices/rebuild/recipe-work.js +19 -7
  408. package/dist/slices/rebuild/recipe-youtube.js +4 -3
  409. package/dist/slices/rebuild/recovery-selection.js +3 -1
  410. package/dist/slices/rebuild/recovery.js +9 -8
  411. package/dist/slices/rebuild/remake-outdated.js +5 -5
  412. package/dist/slices/rebuild/repo.js +4 -3
  413. package/dist/slices/rebuild/review-promise.js +25 -0
  414. package/dist/slices/rebuild/review-redo.js +3 -2
  415. package/dist/slices/rebuild/runtime-admission.js +4 -3
  416. package/dist/slices/rebuild/runtime-agent-review.js +11 -5
  417. package/dist/slices/rebuild/runtime-animate.js +10 -9
  418. package/dist/slices/rebuild/runtime-article.js +7 -5
  419. package/dist/slices/rebuild/runtime-checkpoints.js +5 -4
  420. package/dist/slices/rebuild/runtime-document.js +11 -13
  421. package/dist/slices/rebuild/runtime-export-bed.js +8 -12
  422. package/dist/slices/rebuild/runtime-export-edit.js +8 -16
  423. package/dist/slices/rebuild/runtime-export-inputs.js +12 -11
  424. package/dist/slices/rebuild/runtime-export-short.js +9 -5
  425. package/dist/slices/rebuild/runtime-export.js +38 -15
  426. package/dist/slices/rebuild/runtime-figure-card.js +2 -1
  427. package/dist/slices/rebuild/runtime-image.js +8 -12
  428. package/dist/slices/rebuild/runtime-instructions.js +2 -0
  429. package/dist/slices/rebuild/runtime-keywords.js +59 -0
  430. package/dist/slices/rebuild/runtime-lines.js +2 -11
  431. package/dist/slices/rebuild/runtime-local.js +17 -22
  432. package/dist/slices/rebuild/runtime-narration-publication.js +2 -5
  433. package/dist/slices/rebuild/runtime-narration-reuse.js +9 -13
  434. package/dist/slices/rebuild/runtime-narration-text.js +13 -17
  435. package/dist/slices/rebuild/runtime-pictures.js +2 -4
  436. package/dist/slices/rebuild/runtime-piece-label.js +10 -6
  437. package/dist/slices/rebuild/runtime-plan.js +31 -5
  438. package/dist/slices/rebuild/runtime-provider.js +67 -25
  439. package/dist/slices/rebuild/runtime-publication.js +10 -9
  440. package/dist/slices/rebuild/runtime-review.js +39 -33
  441. package/dist/slices/rebuild/runtime-run.js +4 -1
  442. package/dist/slices/rebuild/runtime-shorts.js +31 -50
  443. package/dist/slices/rebuild/runtime-store.js +20 -10
  444. package/dist/slices/rebuild/runtime-subtitles.js +28 -25
  445. package/dist/slices/rebuild/runtime-voices.js +10 -16
  446. package/dist/slices/rebuild/runtime-youtube.js +14 -31
  447. package/dist/slices/rebuild/service-readiness.js +11 -3
  448. package/dist/slices/rebuild/transition-repo.js +6 -5
  449. package/dist/slices/rebuild/work-records.js +6 -0
  450. package/dist/slices/reruns/cascade.js +1 -2
  451. package/dist/slices/reruns/index.js +6 -9
  452. package/dist/slices/research/synthesis.js +3 -2
  453. package/dist/slices/reviews/external-model.js +4 -1
  454. package/dist/slices/reviews/external-repo.js +2 -1
  455. package/dist/slices/reviews/external-rows.js +2 -3
  456. package/dist/slices/reviews/holds.js +5 -4
  457. package/dist/slices/reviews/model.js +32 -11
  458. package/dist/slices/reviews/repo.js +2 -1
  459. package/dist/slices/reviews/rules.js +4 -3
  460. package/dist/slices/reviews/verdict.js +2 -1
  461. package/dist/slices/reviews/view.js +5 -2
  462. package/dist/slices/revisions/downloads.js +4 -5
  463. package/dist/slices/revisions/image-plan.js +1 -1
  464. package/dist/slices/revisions/mutation-assets.js +9 -5
  465. package/dist/slices/revisions/mutation-cues.js +2 -5
  466. package/dist/slices/revisions/mutations.js +3 -3
  467. package/dist/slices/revisions/repo.js +3 -1
  468. package/dist/slices/revisions/schema.js +1 -0
  469. package/dist/slices/revisions/view.js +20 -0
  470. package/dist/slices/run-cost/limits.js +5 -2
  471. package/dist/slices/run-cost/meter.js +12 -5
  472. package/dist/slices/run-cost/panel.js +8 -74
  473. package/dist/slices/run-cost/plan-readings.js +127 -0
  474. package/dist/slices/run-cost/plan-use.js +169 -0
  475. package/dist/slices/run-cost/pricing.js +3 -3
  476. package/dist/slices/run-cost/week.js +32 -4
  477. package/dist/slices/schedules/agenda.js +18 -7
  478. package/dist/slices/schedules/by-date.js +42 -0
  479. package/dist/slices/schedules/calendar.js +5 -0
  480. package/dist/slices/schedules/design-agenda.js +36 -0
  481. package/dist/slices/schedules/design-check.js +41 -0
  482. package/dist/slices/schedules/design-run.js +126 -0
  483. package/dist/slices/schedules/next-run.js +62 -0
  484. package/dist/slices/schedules/prepare.js +18 -2
  485. package/dist/slices/schedules/repo.js +30 -21
  486. package/dist/slices/schedules/rescore.js +35 -0
  487. package/dist/slices/schedules/scheduler.js +13 -3
  488. package/dist/slices/schedules/schema.js +57 -3
  489. package/dist/slices/schedules/score.js +107 -0
  490. package/dist/slices/schedules/service.js +57 -24
  491. package/dist/slices/schedules/topic-list.js +15 -0
  492. package/dist/slices/schedules/topic-scores.js +104 -0
  493. package/dist/slices/schedules/topics.js +53 -41
  494. package/dist/slices/settings/cli-paths.js +4 -6
  495. package/dist/slices/settings/first-run.js +8 -43
  496. package/dist/slices/settings/health.js +30 -15
  497. package/dist/slices/settings/key-test.js +1 -1
  498. package/dist/slices/settings/model-reach.js +3 -17
  499. package/dist/slices/settings/model.js +14 -2
  500. package/dist/slices/settings/playback.js +34 -23
  501. package/dist/slices/settings/probe-read.js +16 -0
  502. package/dist/slices/settings/repo.js +33 -0
  503. package/dist/slices/settings/tutorial.js +2 -2
  504. package/dist/slices/settings/voices.js +4 -12
  505. package/dist/slices/shorts/keep.js +2 -1
  506. package/dist/slices/shorts/pick.js +6 -18
  507. package/dist/slices/shorts/prompts.js +4 -14
  508. package/dist/slices/shorts/render.js +6 -2
  509. package/dist/slices/social/channel.js +44 -0
  510. package/dist/slices/social/defaults.js +17 -0
  511. package/dist/slices/social/due.js +36 -0
  512. package/dist/slices/social/extension.js +18 -0
  513. package/dist/slices/social/fit.js +115 -0
  514. package/dist/slices/social/limits.js +186 -0
  515. package/dist/slices/social/links.js +21 -0
  516. package/dist/slices/social/media.js +132 -0
  517. package/dist/slices/social/model.js +75 -0
  518. package/dist/slices/social/notice.js +19 -0
  519. package/dist/slices/social/pack.js +176 -0
  520. package/dist/slices/social/post-prompt.js +56 -0
  521. package/dist/slices/social/queue.js +31 -0
  522. package/dist/slices/social/repo.js +133 -0
  523. package/dist/slices/social/reshape.js +97 -0
  524. package/dist/slices/social/rules.js +5 -0
  525. package/dist/slices/social/schema.js +70 -0
  526. package/dist/slices/social/serve.js +51 -0
  527. package/dist/slices/social/service.js +129 -0
  528. package/dist/slices/social/settings.js +21 -0
  529. package/dist/slices/social/sources.js +114 -0
  530. package/dist/slices/social/state.js +110 -0
  531. package/dist/slices/social/text.js +149 -0
  532. package/dist/slices/storage/arrange.js +3 -1
  533. package/dist/slices/storage/assets.js +8 -10
  534. package/dist/slices/storage/backup-designs.js +74 -0
  535. package/dist/slices/storage/backup-export.js +8 -3
  536. package/dist/slices/storage/backup-format.js +10 -0
  537. package/dist/slices/storage/backup-import.js +22 -4
  538. package/dist/slices/storage/delete-project.js +6 -5
  539. package/dist/slices/storage/files-location.js +6 -8
  540. package/dist/slices/storage/layout.js +13 -2
  541. package/dist/slices/storage/model.js +3 -0
  542. package/dist/slices/storage/portable.js +17 -12
  543. package/dist/slices/storage/schema.js +2 -0
  544. package/dist/slices/storage/staging-refs.js +2 -1
  545. package/dist/slices/storage/staging.js +6 -9
  546. package/dist/slices/storage/trim.js +26 -9
  547. package/dist/slices/studio/calendar.js +7 -2
  548. package/dist/slices/studio/disclosure.js +4 -4
  549. package/dist/slices/studio/model.js +1 -1
  550. package/dist/slices/studio/pack.js +9 -8
  551. package/dist/slices/studio/pick.js +2 -11
  552. package/dist/slices/studio/plan.js +7 -16
  553. package/dist/slices/studio/queue.js +3 -14
  554. package/dist/slices/studio/releases.js +26 -0
  555. package/dist/slices/studio/settings.js +20 -57
  556. package/dist/slices/studio/videos.js +35 -4
  557. package/dist/slices/style-preview/render.js +37 -16
  558. package/dist/slices/style-preview/schema.js +14 -1
  559. package/dist/slices/style-preview/service.js +2 -1
  560. package/dist/slices/style-preview/settings.js +33 -5
  561. package/dist/slices/subtitles/captions.js +1 -1
  562. package/dist/slices/telemetry/collector-client.js +2 -4
  563. package/dist/slices/telemetry/flush.js +2 -4
  564. package/dist/slices/telemetry/model.js +0 -7
  565. package/dist/slices/telemetry/record.js +10 -3
  566. package/dist/slices/template-studio/anatomy-view.js +161 -0
  567. package/dist/slices/template-studio/anatomy.js +73 -0
  568. package/dist/slices/template-studio/bakeoff-make.js +225 -0
  569. package/dist/slices/template-studio/bakeoff-repo.js +83 -0
  570. package/dist/slices/template-studio/bakeoff.js +333 -0
  571. package/dist/slices/template-studio/budget.js +106 -0
  572. package/dist/slices/template-studio/cards.js +78 -0
  573. package/dist/slices/template-studio/checks.js +322 -0
  574. package/dist/slices/template-studio/document-sample.js +56 -0
  575. package/dist/slices/template-studio/edit.js +15 -0
  576. package/dist/slices/template-studio/for-all.js +161 -0
  577. package/dist/slices/template-studio/free-video.js +31 -0
  578. package/dist/slices/template-studio/free.js +176 -0
  579. package/dist/slices/template-studio/gate.js +40 -0
  580. package/dist/slices/template-studio/hidden.js +20 -0
  581. package/dist/slices/template-studio/jobs.js +79 -0
  582. package/dist/slices/template-studio/loudness.js +111 -0
  583. package/dist/slices/template-studio/make.js +40 -0
  584. package/dist/slices/template-studio/model.js +58 -0
  585. package/dist/slices/template-studio/pack-import.js +253 -0
  586. package/dist/slices/template-studio/pack-samples.js +60 -0
  587. package/dist/slices/template-studio/pack.js +207 -0
  588. package/dist/slices/template-studio/paid-view.js +66 -0
  589. package/dist/slices/template-studio/paid.js +355 -0
  590. package/dist/slices/template-studio/quote.js +155 -0
  591. package/dist/slices/template-studio/ready.js +104 -0
  592. package/dist/slices/template-studio/repo.js +134 -0
  593. package/dist/slices/template-studio/reviews-path.js +58 -0
  594. package/dist/slices/template-studio/runner.js +84 -0
  595. package/dist/slices/template-studio/save.js +236 -0
  596. package/dist/slices/template-studio/say-it-as.js +248 -0
  597. package/dist/slices/template-studio/schedule-gate.js +34 -0
  598. package/dist/slices/template-studio/schedule-price.js +32 -0
  599. package/dist/slices/template-studio/schedule-sample.js +91 -0
  600. package/dist/slices/template-studio/schema.js +243 -0
  601. package/dist/slices/template-studio/scope.js +124 -0
  602. package/dist/slices/template-studio/sections.js +176 -0
  603. package/dist/slices/template-studio/service.js +175 -0
  604. package/dist/slices/template-studio/status.js +191 -0
  605. package/dist/slices/template-studio/topic.js +223 -0
  606. package/dist/slices/thumbnail/by-llm.js +1 -1
  607. package/dist/slices/trash/designs.js +76 -0
  608. package/dist/slices/trash/model.js +13 -4
  609. package/dist/slices/trash/service.js +52 -2
  610. package/dist/slices/tutorials/library.js +3 -2
  611. package/dist/slices/uploads/repo.js +37 -1
  612. package/dist/slices/uploads/rules.js +3 -2
  613. package/dist/slices/video/ass-text.js +17 -0
  614. package/dist/slices/video/audio-export.js +2 -64
  615. package/dist/slices/video/edit-list.js +2 -2
  616. package/dist/slices/video/edit-settings.js +1 -1
  617. package/dist/slices/video/encoding.js +169 -0
  618. package/dist/slices/video/ffmpeg.js +23 -21
  619. package/dist/slices/video/figure-card.js +1 -15
  620. package/dist/slices/video/plan.js +2 -1
  621. package/dist/slices/video/slideshow.js +43 -18
  622. package/dist/slices/voices/attribution.js +3 -2
  623. package/dist/slices/voices/model.js +9 -8
  624. package/dist/slices/voices/palette.js +0 -3
  625. package/dist/slices/voices/script.js +2 -1
  626. package/dist/slices/youtube/answer.js +4 -16
  627. package/dist/slices/youtube/edits-repo.js +3 -12
  628. package/dist/tutorials/AI-CLIs.md +25 -25
  629. package/dist/tutorials/Agents-MCP.md +34 -33
  630. package/dist/tutorials/Assistant.md +42 -0
  631. package/dist/tutorials/Backups.md +7 -7
  632. package/dist/tutorials/Calendar.md +17 -25
  633. package/dist/tutorials/Cast-Library.md +19 -19
  634. package/dist/tutorials/Channels.md +42 -35
  635. package/dist/tutorials/Costs-and-Run-Cost.md +38 -40
  636. package/dist/tutorials/Create-Article.md +114 -0
  637. package/dist/tutorials/Create-Extras.md +112 -0
  638. package/dist/tutorials/Create-Images.md +165 -0
  639. package/dist/tutorials/Create-Narration.md +178 -0
  640. package/dist/tutorials/Create-Overview.md +244 -0
  641. package/dist/tutorials/Create-Video-and-Style.md +144 -0
  642. package/dist/tutorials/Docker.md +3 -3
  643. package/dist/tutorials/Document-Themes.md +32 -32
  644. package/dist/tutorials/Editing-a-Project.md +94 -94
  645. package/dist/tutorials/FAQ.md +18 -18
  646. package/dist/tutorials/First-Launch-and-Welcome.md +50 -64
  647. package/dist/tutorials/Home-and-Projects.md +115 -71
  648. package/dist/tutorials/Home.md +33 -33
  649. package/dist/tutorials/Install.md +14 -13
  650. package/dist/tutorials/Intros-and-Outros.md +33 -34
  651. package/dist/tutorials/Keyboard-Shortcuts-and-Command-Palette.md +62 -58
  652. package/dist/tutorials/Keywords-Slopify-Fills.md +87 -0
  653. package/dist/tutorials/Models.md +53 -21
  654. package/dist/tutorials/Multiple-Voices.md +41 -40
  655. package/dist/tutorials/Narration-Aliases-and-Glossary.md +14 -14
  656. package/dist/tutorials/Notifications.md +27 -17
  657. package/dist/tutorials/Other-Languages.md +15 -15
  658. package/dist/tutorials/PDF-Documents.md +38 -29
  659. package/dist/tutorials/Posting-to-Social-Sites.md +121 -0
  660. package/dist/tutorials/Project-Page.md +118 -120
  661. package/dist/tutorials/Prompts-and-Templates.md +84 -0
  662. package/dist/tutorials/Prompts.md +48 -44
  663. package/dist/tutorials/Providers-and-Keys.md +105 -98
  664. package/dist/tutorials/Publishing-to-YouTube.md +31 -31
  665. package/dist/tutorials/Recovery-and-Retries.md +31 -31
  666. package/dist/tutorials/Remote-Access.md +63 -0
  667. package/dist/tutorials/Reviews-and-Checkpoints.md +98 -90
  668. package/dist/tutorials/Schedules.md +46 -27
  669. package/dist/tutorials/Settings-Reference.md +194 -174
  670. package/dist/tutorials/Shorts.md +34 -33
  671. package/dist/tutorials/Start-at-Login.md +3 -3
  672. package/dist/tutorials/Studio-Extension.md +22 -24
  673. package/dist/tutorials/Template-Studio.md +128 -0
  674. package/dist/tutorials/Templates.md +51 -77
  675. package/dist/tutorials/Trash.md +4 -4
  676. package/dist/tutorials/Troubleshooting.md +44 -44
  677. package/dist/tutorials/Uninstalling-and-Moving.md +5 -5
  678. package/dist/tutorials/Updating-and-Patch-Notes.md +18 -18
  679. package/dist/tutorials/Video-Editing.md +34 -34
  680. package/dist/tutorials/Where-Your-Files-Live.md +10 -10
  681. package/dist/tutorials/YouTube-Description.md +26 -26
  682. package/dist/tutorials/Your-First-Short.md +35 -35
  683. package/dist/tutorials/_Sidebar.md +19 -14
  684. package/dist/updater/work-in-progress.js +42 -12
  685. package/dist/web/assets/index-BNftKQrA.css +1 -0
  686. package/dist/web/assets/index-Cpa58woP.js +459 -0
  687. package/dist/web/assets/pdf-BKh8f7R6.js +56 -0
  688. package/dist/web/assets/pdf.worker.min-BmVo14Nb.mjs +28 -0
  689. package/dist/web/assets/pdf.worker.min-D7i8o3wR.js +1 -0
  690. package/dist/web/index.html +2 -2
  691. package/package.json +6 -2
  692. package/dist/slices/article/provided-entries.js +0 -66
  693. package/dist/slices/article/run.js +0 -145
  694. package/dist/slices/images/run.js +0 -234
  695. package/dist/slices/narration/legacy-plan.js +0 -58
  696. package/dist/slices/narration/run.js +0 -270
  697. package/dist/slices/rebuild/runtime-actions.js +0 -90
  698. package/dist/slices/research/run.js +0 -224
  699. package/dist/slices/subtitles/prepare.js +0 -194
  700. package/dist/slices/subtitles/transcript.js +0 -34
  701. package/dist/slices/thumbnail/run.js +0 -219
  702. package/dist/slices/video/audio-inputs.js +0 -33
  703. package/dist/slices/video/reuse-audio.js +0 -62
  704. package/dist/slices/video/run.js +0 -78
  705. package/dist/slices/video/write-export.js +0 -158
  706. package/dist/slices/video/write-subtitles.js +0 -59
  707. package/dist/tutorials/Library-Overview.md +0 -76
  708. package/dist/tutorials/Play-Images.md +0 -161
  709. package/dist/tutorials/Play-Narration.md +0 -167
  710. package/dist/tutorials/Play-Outputs.md +0 -109
  711. package/dist/tutorials/Play-Overview.md +0 -227
  712. package/dist/tutorials/Play-Title-and-Article.md +0 -136
  713. package/dist/tutorials/Play-Video-and-Style.md +0 -143
  714. package/dist/web/assets/index-BadsoORk.css +0 -1
  715. package/dist/web/assets/index-D8LqNsCl.js +0 -326
  716. package/dist/web/assets/pdf-T7MCwjIv.js +0 -54
  717. package/dist/web/assets/pdf.worker.min-Dswkl-cV.mjs +0 -29
  718. package/dist/web/assets/pdf.worker.min-zC6nfbxG.js +0 -1
@@ -0,0 +1,35 @@
1
+ import { transact } from "../../kernel/db/tx.js";
2
+ import { scheduleById } from "./repo.js";
3
+ import { rankedItems, saveScores, scoreTopics } from "./topic-scores.js";
4
+ import { heldTopics, knownTitles, rankHeld } from "./topics.js";
5
+ // Rescore: every queued and held topic scored again now, then the queue and the held list put
6
+ // in score order. Topics the person added or moved keep their places: one scored for the first
7
+ // time here is scored where it stands.
8
+ export async function rescoreTopics(deps, scheduleId, signal) {
9
+ const schedule = scheduleById(deps.db, scheduleId);
10
+ if (schedule === undefined || schedule.deletedAt !== null)
11
+ return { ok: false, reason: "not-found" };
12
+ if (schedule.topicScoring === "off")
13
+ return {
14
+ ok: false,
15
+ reason: "scoring-off",
16
+ message: "Topic ranking is off for this schedule. Choose how to rank topics under Edit → Topic generation → Rank topics, save, then rescore.",
17
+ };
18
+ const titles = [
19
+ ...schedule.items.map((item) => item.title),
20
+ ...heldTopics(deps, scheduleId).map((topic) => topic.title),
21
+ ];
22
+ const scores = await scoreTopics(deps, schedule.topicScoring, titles, knownTitles(deps, scheduleId), deps.template(schedule.templateId)?.document.form.language ?? "en", signal);
23
+ return transact(deps.db, () => {
24
+ const current = scheduleById(deps.db, scheduleId);
25
+ if (current === undefined || current.deletedAt !== null)
26
+ return { ok: false, reason: "not-found" };
27
+ const at = deps.clock.now().toISOString();
28
+ saveScores(deps.db, scheduleId, scores, at, true);
29
+ deps.db
30
+ .prepare("UPDATE schedules SET items_json=?,version=version+1,updated_at=? WHERE id=? AND version=?")
31
+ .run(JSON.stringify(rankedItems(deps.db, scheduleId, current.items)), at, scheduleId, current.version);
32
+ rankHeld(deps, scheduleId);
33
+ return { ok: true, value: scheduleById(deps.db, scheduleId) };
34
+ });
35
+ }
@@ -3,10 +3,11 @@ import { reviewDraft } from "../play-drafts/review.js";
3
3
  import { createDraft } from "../play-drafts/service.js";
4
4
  import { startPlayDraft } from "../play-drafts/start.js";
5
5
  import { nextOccurrence } from "./calendar.js";
6
+ import { claimDesignRun } from "./design-run.js";
7
+ import { missedGraceMs } from "./next-run.js";
6
8
  import { continuePrepared, preparedProject, scheduledDocument, scheduledTitle } from "./prepare.js";
7
9
  import { activeRun, dueSchedules, insertRun, recordRunDispatch, recordRunStartIdentity, recoverRunningRuns, scheduleById, scheduleRows, updateRun, } from "./repo.js";
8
10
  import { generateTopics, generationDue, releaseTopicLeases } from "./topics.js";
9
- const missedGraceMs = 60_000;
10
11
  export function createScheduleRunner(deps) {
11
12
  let ticking = false;
12
13
  // ceiling: one entry per schedule generating now.
@@ -76,6 +77,11 @@ export function createScheduleRunner(deps) {
76
77
  function claim(deps, schedule, now) {
77
78
  if (schedule.nextRunAt === null)
78
79
  return [];
80
+ // A design schedule's run makes its piece at once and never reaches `execute`.
81
+ if (schedule.templateKind === "design") {
82
+ claimDesignRun(deps, schedule, now);
83
+ return [];
84
+ }
79
85
  const scheduledFor = new Date(schedule.nextRunAt);
80
86
  const occurrence = schedule.missedPolicy === "skip" && now.valueOf() - scheduledFor.valueOf() > missedGraceMs;
81
87
  let next = nextOccurrence(schedule.cadence, schedule.timezone, new Date(scheduledFor.valueOf() + 1000));
@@ -94,7 +100,10 @@ function claim(deps, schedule, now) {
94
100
  const noTopic = current.topicGeneration.mode !== "off" && current.items.length === 0
95
101
  ? noTopicReason(current)
96
102
  : null;
97
- const skip = occurrence || overlapping || noTopic !== null;
103
+ // A template with no version marked ready (or saved before the studio) waits: the run is
104
+ // skipped, nothing is spent and no topic is used.
105
+ const notReady = noTopic === null ? (deps.notReady?.(current.templateId) ?? null) : null;
106
+ const skip = occurrence || overlapping || noTopic !== null || notReady !== null;
98
107
  const run = {
99
108
  id: deps.uuid(),
100
109
  scheduleId: schedule.id,
@@ -102,6 +111,7 @@ function claim(deps, schedule, now) {
102
111
  status: skip ? "skipped" : "running",
103
112
  requestId: null,
104
113
  projectIds: [],
114
+ designIds: [],
105
115
  estimate: null,
106
116
  startedAt: now.toISOString(),
107
117
  endedAt: skip ? now.toISOString() : null,
@@ -110,7 +120,7 @@ function claim(deps, schedule, now) {
110
120
  ? "Skipped because the previous run of this schedule was still going."
111
121
  : occurrence
112
122
  ? "Skipped because Slopify was not running at the scheduled time."
113
- : noTopic
123
+ : (noTopic ?? notReady)
114
124
  : null,
115
125
  };
116
126
  if (!insertRun(deps.db, run))
@@ -85,13 +85,28 @@ export const scheduleReleaseSchema = z
85
85
  .strict()
86
86
  .readonly();
87
87
  const releases = z.array(scheduleReleaseSchema).max(7).readonly();
88
+ // How generated topics are ranked (`score.ts`): not at all, by what the library already
89
+ // covers, or also by public search interest (topic titles go to Google, DuckDuckGo and
90
+ // Wikipedia). Off is what every schedule saved before it has.
91
+ export const topicScorings = ["off", "library", "search"];
88
92
  // Enough for a year of daily runs, pasted as one list.
89
93
  export const queueMax = 500;
94
+ // What a schedule makes: projects from a project template (every schedule before designs), or
95
+ // designed pieces from a design template, one per row ahead of its date.
96
+ export const scheduleTemplateKinds = ["project", "design"];
97
+ // A project template's id is a UUID (checked by the service); a design template's may be any
98
+ // id its creator chose.
99
+ const templateId = z
100
+ .string()
101
+ .min(1)
102
+ .max(64)
103
+ .regex(/^[A-Za-z0-9_-]+$/);
90
104
  export const scheduleCreateSchema = z
91
105
  .object({
92
106
  id,
93
107
  name: z.string().trim().min(1).max(200),
94
- templateId: id,
108
+ templateKind: z.enum(scheduleTemplateKinds).default("project"),
109
+ templateId,
95
110
  templateVersion: z.number().int().positive(),
96
111
  cadence: cadenceSchema,
97
112
  timezone: z
@@ -112,6 +127,9 @@ export const scheduleCreateSchema = z
112
127
  brief: z.string().trim().max(briefMax).nullable().default(null),
113
128
  topicGeneration: topicGenerationSchema.default(topicGenerationOff),
114
129
  releases: releases.default([]),
130
+ // Absent: off for a new schedule, unchanged on a save, so a caller that doesn't know it
131
+ // never turns it off.
132
+ topicScoring: z.enum(topicScorings).optional(),
115
133
  })
116
134
  .strict()
117
135
  .readonly();
@@ -120,6 +138,12 @@ export const scheduleUpdateSchema = scheduleCreateSchema
120
138
  .extend({ baseVersion: z.number().int().positive(), mutationId: id })
121
139
  .strict()
122
140
  .readonly();
141
+ // The bodies the schedule routes take: a save names its schedule in the path, and pause,
142
+ // resume, cancel and delete carry only the version they were read at.
143
+ export const scheduleSaveBodySchema = scheduleUpdateSchema.unwrap().omit({ id: true });
144
+ export const scheduleVersionBodySchema = z
145
+ .object({ baseVersion: z.number().int().positive() })
146
+ .strict();
123
147
  export const scheduleDeleteSchema = z
124
148
  .object({ id, baseVersion: z.number().int().positive() })
125
149
  .strict()
@@ -128,11 +152,21 @@ export const scheduleControlSchema = z
128
152
  .object({ id, baseVersion: z.number().int().positive() })
129
153
  .strict()
130
154
  .readonly();
155
+ export const scheduleTemplateGateSchema = z
156
+ .object({
157
+ head: z.number().int().positive(),
158
+ runs: z.number().int().positive().nullable(),
159
+ gated: z.boolean(),
160
+ gate: z.enum(["legacy", "ready", "carried", "unchecked"]),
161
+ })
162
+ .strict()
163
+ .readonly();
131
164
  export const scheduleSummarySchema = z
132
165
  .object({
133
166
  id,
134
167
  name: z.string(),
135
- templateId: id,
168
+ templateKind: z.enum(scheduleTemplateKinds).default("project"),
169
+ templateId,
136
170
  templateVersion: z.number().int().positive(),
137
171
  cadence: cadenceSchema,
138
172
  timezone: z.string(),
@@ -145,6 +179,7 @@ export const scheduleSummarySchema = z
145
179
  brief: z.string().nullable().default(null),
146
180
  topicGeneration: topicGenerationSchema.default(topicGenerationOff),
147
181
  releases: releases.default([]),
182
+ topicScoring: z.enum(topicScorings).default("off"),
148
183
  // The generation's own state: how many topics wait for approval, whether an LLM is being
149
184
  // asked now, and the last failure's plain reason (cleared by the next success).
150
185
  topics: topicState.default(topicStateIdle),
@@ -154,6 +189,11 @@ export const scheduleSummarySchema = z
154
189
  createdAt: z.string(),
155
190
  updatedAt: z.string(),
156
191
  deletedAt: z.string().datetime({ offset: true }).nullable(),
192
+ // Which version of its template the schedule runs (`runs`): the newest one until a version
193
+ // was first marked ready (`gated`), then the newest legacy, ready or carried one (null when
194
+ // none is); the template's newest (`head`) and the gate of the one that runs. Absent from a
195
+ // Slopify before the template studio.
196
+ templateGate: scheduleTemplateGateSchema.optional(),
157
197
  })
158
198
  .strict()
159
199
  .readonly();
@@ -165,6 +205,8 @@ export const scheduleRunSchema = z
165
205
  status: z.enum(["running", "succeeded", "failed", "skipped"]),
166
206
  requestId: id.nullable(),
167
207
  projectIds: z.array(z.string()).readonly(),
208
+ // The designed pieces a design schedule's run made. Older servers leave it out.
209
+ designIds: z.array(z.string()).readonly().default([]),
168
210
  estimate: z.unknown().nullable(),
169
211
  startedAt: z.string().datetime({ offset: true }),
170
212
  endedAt: z.string().datetime({ offset: true }).nullable(),
@@ -217,7 +259,7 @@ export const calendarRunSchema = z
217
259
  scheduleName: z.string(),
218
260
  scheduleVersion: z.number().int().positive(),
219
261
  paused: z.boolean(),
220
- templateId: id,
262
+ templateId,
221
263
  templateVersion: z.number().int().positive(),
222
264
  templateName: z.string().nullable(),
223
265
  // The topic's position in the schedule's queue, for reordering; null without one.
@@ -231,6 +273,15 @@ export const calendarRunSchema = z
231
273
  // The project prepared ahead for this run (Prepare), which its day continues. Older servers
232
274
  // leave it out.
233
275
  prepared: z.string().nullable().default(null),
276
+ // No version of its template is marked ready (or saved before the template studio), so
277
+ // this run is skipped unless one is by then. Older servers leave it out.
278
+ notReady: z.boolean().default(false),
279
+ // A design schedule's run makes a designed piece on its day, never a video: it has nothing
280
+ // to prepare ahead and fills no video slot. Older servers leave it out.
281
+ templateKind: z.enum(scheduleTemplateKinds).default("project"),
282
+ // The schedule's time zone: its runs are shown on that zone's clock. Older servers leave
283
+ // it out, and the browser's is used.
284
+ timezone: z.string().optional(),
234
285
  })
235
286
  .strict()
236
287
  .readonly();
@@ -258,6 +309,9 @@ export const calendarProjectSchema = z
258
309
  // A step failed while the run still reads by a wait (`readsByWaitBesideFailure`): listed as a
259
310
  // failed item beside what it waits for, as on Home. Older servers leave it out.
260
311
  stepFailed: z.literal(true).optional(),
312
+ // When a step that failed on something time can fix runs again by itself, while no step
313
+ // runs (`admission/state-words.ts` retryingAt): it reads Waiting to try again, as on Home.
314
+ retryAt: z.string().optional(),
261
315
  // Stages waiting for a CLI plan's limits to reset.
262
316
  limitWaits: z
263
317
  .array(z
@@ -0,0 +1,107 @@
1
+ // Topic scoring: how new a generated topic is next to what the library already covers, and,
2
+ // when the person opted in, how much public search interest it has. Pure, so the web bundle
3
+ // can import it too. Never reads a channel's analytics.
4
+ import { normaliseTopic, topicWords } from "./similar.js";
5
+ // A suggestion names the topic when it holds every word of it.
6
+ export function readingOf(topic, result) {
7
+ const words = topicWords(topic);
8
+ const matching = result.suggestions?.slice(0, 10).filter((suggestion) => {
9
+ const said = topicWords(suggestion);
10
+ return words.size > 0 && [...words].every((word) => said.has(word));
11
+ }) ?? null;
12
+ return {
13
+ suggestHits: matching === null ? null : matching.length,
14
+ examples: matching?.filter((one) => normaliseTopic(one) !== normaliseTopic(topic)).slice(0, 2) ?? [],
15
+ wikiViews: result.wikiViews,
16
+ };
17
+ }
18
+ // The key a topic's score is kept by: its normalised title, so a recased or repunctuated
19
+ // topic keeps its score.
20
+ export function topicKey(title) {
21
+ return normaliseTopic(title);
22
+ }
23
+ function overlap(a, b) {
24
+ if (a.size === 0 || b.size === 0)
25
+ return 0;
26
+ let common = 0;
27
+ for (const word of a)
28
+ if (b.has(word))
29
+ common += 1;
30
+ // A known title that holds every word of the topic covers it, however long the title is.
31
+ return common === a.size ? 1 : common / (a.size + b.size - common);
32
+ }
33
+ export function noveltyOf(topic, known) {
34
+ const words = topicWords(topic);
35
+ let nearest;
36
+ for (const title of [...known.topics, ...known.projects]) {
37
+ const share = overlap(words, topicWords(title));
38
+ if (share > 0 && (nearest === undefined || share > nearest.overlap))
39
+ nearest = { title, overlap: share };
40
+ }
41
+ return nearest === undefined
42
+ ? { value: 1 }
43
+ : { value: Math.max(0, 1 - nearest.overlap), nearest };
44
+ }
45
+ // ceiling: ten million views a month (log10 7) is as popular as a topic gets here.
46
+ const viewsCeiling = 7;
47
+ export function interestOf(reading) {
48
+ if (reading === null)
49
+ return null;
50
+ const views = Math.min(1, Math.log10((reading.wikiViews ?? 0) + 1) / viewsCeiling);
51
+ const hits = Math.min(10, reading.suggestHits ?? 0) / 10;
52
+ return 0.6 * views + 0.4 * hits;
53
+ }
54
+ export function scoreOf(interest, novelty) {
55
+ return Math.round(100 * (interest === null ? novelty : 0.6 * interest + 0.4 * novelty));
56
+ }
57
+ function views(count) {
58
+ if (count >= 1_000_000)
59
+ return `${(count / 1_000_000).toFixed(1).replace(/\.0$/, "")}M`;
60
+ if (count >= 1000)
61
+ return `${String(Math.round(count / 1000))}k`;
62
+ return String(count);
63
+ }
64
+ // Why a topic scored as it did, one line per signal, as the score chip shows them.
65
+ export function reasonsOf(novelty, reading, search) {
66
+ const lines = [];
67
+ if (search === "read" && reading !== null) {
68
+ if (reading.suggestHits !== null) {
69
+ const example = reading.examples[0];
70
+ lines.push(`Search: ${String(reading.suggestHits)} of 10 suggestions match${example === undefined ? "" : `, e.g. “${example}”`}`);
71
+ }
72
+ lines.push(reading.wikiViews === null
73
+ ? "Wikipedia: no article found"
74
+ : `Wikipedia: ${views(reading.wikiViews)} views last month`);
75
+ }
76
+ if (search === "unreachable")
77
+ lines.push("Search interest unknown: no connection to Google, DuckDuckGo or Wikipedia");
78
+ lines.push(novelty.nearest === undefined
79
+ ? "Library: new; nothing like it made or queued"
80
+ : novelty.value === 0
81
+ ? `Library: already covered by “${novelty.nearest.title}”`
82
+ : `Library: new; nearest “${novelty.nearest.title}” (${String(Math.round(novelty.nearest.overlap * 100))}% overlap)`);
83
+ return lines;
84
+ }
85
+ export function topicScore(novelty, reading, search) {
86
+ const interest = search === "read" ? interestOf(reading) : null;
87
+ return {
88
+ score: scoreOf(interest, novelty.value),
89
+ interest,
90
+ novelty: novelty.value,
91
+ reasons: reasonsOf(novelty, reading, search),
92
+ };
93
+ }
94
+ // The queue once scored: topics the person added or moved keep their place at the front in
95
+ // their own order, then the scored ones, best first. Stable: equal scores keep their order.
96
+ export function orderQueue(items, scores) {
97
+ const kept = [];
98
+ const scored = [];
99
+ for (const item of items) {
100
+ const found = scores.get(topicKey(item.title));
101
+ if (found === undefined || found.pinned)
102
+ kept.push(item);
103
+ else
104
+ scored.push({ item, score: found.score });
105
+ }
106
+ return [...kept, ...scored.toSorted((a, b) => b.score - a.score).map((one) => one.item)];
107
+ }
@@ -1,12 +1,24 @@
1
1
  import { z } from "zod";
2
2
  import { transact } from "../../kernel/db/tx.js";
3
3
  import { requestHash } from "../play-drafts/repo.js";
4
- import { nextOccurrence } from "./calendar.js";
4
+ import { checkDesign } from "./design-check.js";
5
+ import { nextRunOf } from "./next-run.js";
5
6
  import { insertSchedule, runsForSchedule, scheduleById, scheduleRows, setScheduleStatus, tombstoneScheduleRow, updateScheduleRow, } from "./repo.js";
6
7
  import { scheduleControlSchema, scheduleCreateSchema, scheduleDeleteSchema, scheduleUpdateSchema, topicStateIdle, } from "./schema.js";
7
- import { templateKeywords, topicRowProblems } from "./topic-list.js";
8
+ import { scheduleKeywords, topicRowProblems } from "./topic-list.js";
8
9
  export function listSchedules(deps) {
9
- return scheduleRows(deps.db);
10
+ const rows = scheduleRows(deps.db);
11
+ const gateOf = deps.templateGate;
12
+ if (gateOf === undefined)
13
+ return rows;
14
+ return rows.map((schedule) => {
15
+ // The template studio's gate is a project template's; a design schedule's template is
16
+ // a design template, readied by the designs.
17
+ if (schedule.templateKind !== "project")
18
+ return schedule;
19
+ const templateGate = gateOf(schedule.templateId);
20
+ return templateGate === undefined ? schedule : { ...schedule, templateGate };
21
+ });
10
22
  }
11
23
  export function listScheduleRuns(deps, id) {
12
24
  if (!z.uuid().safeParse(id).success)
@@ -35,18 +47,16 @@ export function createSchedule(deps, input) {
35
47
  ? { ok: true, value: scheduleById(deps.db, parsed.data.id) }
36
48
  : { ok: false, reason: "conflict" };
37
49
  }
38
- const checked = checkTemplate(deps, parsed.data.templateId);
50
+ const checked = checkKind(deps, parsed.data, []);
39
51
  if (!checked.ok)
40
52
  return checked;
41
- const topics = checkTopics(deps, parsed.data, []);
42
- if (!topics.ok)
43
- return topics;
44
53
  const now = deps.clock.now();
45
- const next = nextOccurrence(parsed.data.cadence, parsed.data.timezone, now);
46
- if (next === null)
54
+ const next = nextRunOf(deps.db, parsed.data, now);
55
+ // A design schedule with no row still to come waits for rows; any other has to run.
56
+ if (next === null && parsed.data.templateKind === "project")
47
57
  return { ok: false, reason: "not-due" };
48
58
  // The version it was saved against, for the record; runs use the newest.
49
- const value = summary({ ...parsed.data, templateVersion: checked.value }, next.toISOString(), now.toISOString());
59
+ const value = summary({ ...parsed.data, templateVersion: checked.value }, next?.toISOString() ?? null, now.toISOString());
50
60
  insertSchedule(deps.db, value, hash);
51
61
  return { ok: true, value };
52
62
  });
@@ -78,22 +88,25 @@ export function updateSchedule(deps, input) {
78
88
  return { ok: false, reason: "conflict" };
79
89
  if (previous.status === "canceled" || previous.status === "completed")
80
90
  return { ok: false, reason: "conflict" };
81
- const checked = checkTemplate(deps, parsed.data.templateId);
91
+ if (parsed.data.templateKind !== previous.templateKind)
92
+ return {
93
+ ok: false,
94
+ reason: "invalid-input",
95
+ message: "A schedule can't switch between making videos and making designed pieces. Make a new schedule for the other kind.",
96
+ };
97
+ const checked = checkKind(deps, parsed.data, previous.items);
82
98
  if (!checked.ok)
83
99
  return checked;
84
- const topics = checkTopics(deps, parsed.data, previous.items);
85
- if (!topics.ok)
86
- return topics;
87
100
  const now = deps.clock.now();
88
101
  const next = previous.status === "active"
89
- ? nextOccurrence(parsed.data.cadence, parsed.data.timezone, now)
90
- : previous.nextRunAt === null
102
+ ? nextRunOf(deps.db, parsed.data, now)
103
+ : previous.nextRunAt === null && previous.templateKind === "project"
91
104
  ? null
92
- : nextOccurrence(parsed.data.cadence, parsed.data.timezone, now);
105
+ : nextRunOf(deps.db, parsed.data, now);
93
106
  // The version it was saved against, for the record; runs use the newest.
94
107
  const value = summary({ ...parsed.data, templateVersion: checked.value }, next?.toISOString() ?? null, now.toISOString(), previous.status, previous.version + 1,
95
108
  // Saving clears a recorded generation failure, so the next tick tries again.
96
- { ...previous.topics, failedAt: null, error: null });
109
+ { ...previous.topics, failedAt: null, error: null }, previous.topicScoring);
97
110
  if (!updateScheduleRow(deps.db, value, parsed.data.mutationId, hash, parsed.data.baseVersion))
98
111
  return { ok: false, reason: "conflict" };
99
112
  return { ok: true, value };
@@ -113,8 +126,9 @@ export function resumeSchedule(deps, input) {
113
126
  return { ok: false, reason: "conflict" };
114
127
  if (previous.version !== parsed.data.baseVersion || previous.status !== "paused")
115
128
  return { ok: false, reason: "conflict" };
116
- const next = nextOccurrence(previous.cadence, previous.timezone, deps.clock.now());
117
- const status = next === null ? "completed" : "active";
129
+ const next = nextRunOf(deps.db, previous, deps.clock.now());
130
+ // A design schedule with no row to come stays active, waiting for rows.
131
+ const status = next === null && previous.templateKind === "project" ? "completed" : "active";
118
132
  if (!setScheduleStatus(deps.db, previous.id, previous.version, status, next?.toISOString() ?? null, deps.clock.now().toISOString()))
119
133
  return { ok: false, reason: "conflict" };
120
134
  return { ok: true, value: scheduleById(deps.db, previous.id) };
@@ -157,6 +171,24 @@ function control(deps, input, status) {
157
171
  return { ok: false, reason: "conflict" };
158
172
  return { ok: true, value: scheduleById(deps.db, previous.id) };
159
173
  }
174
+ // Answers the template's newest version when a schedule may use it, and checks its queue
175
+ // against that template: a project template's keywords, or a design template's fields and
176
+ // dates.
177
+ function checkKind(deps, input, kept) {
178
+ if (input.templateKind === "design")
179
+ return checkDesign(deps, input);
180
+ // Exactly as before design schedules: a project template's id is a UUID, and its schedule
181
+ // runs once, daily or weekly.
182
+ if (!z.uuid().safeParse(input.templateId).success || input.cadence.kind === "by-date")
183
+ return { ok: false, reason: "invalid-input" };
184
+ const checked = checkTemplate(deps, input.templateId);
185
+ if (!checked.ok)
186
+ return checked;
187
+ const topics = checkTopics(deps, input, kept);
188
+ if (!topics.ok)
189
+ return topics;
190
+ return checked;
191
+ }
160
192
  // Answers the template's newest version when a schedule may use it.
161
193
  function checkTemplate(deps, id) {
162
194
  const template = deps.template(id);
@@ -178,10 +210,9 @@ function checkTemplate(deps, id) {
178
210
  // the template doesn't use would silently do nothing. Topics saved before, unchanged, are left
179
211
  // alone, so a queue from before this check still saves.
180
212
  function checkTopics(deps, input, kept) {
181
- const template = deps.template(input.templateId);
182
- if (!template)
213
+ const keywords = scheduleKeywords(deps, input.templateId);
214
+ if (keywords === undefined)
183
215
  return { ok: false, reason: "missing-template" };
184
- const keywords = templateKeywords(template.document.form);
185
216
  const named = keywords.length === 0 ? "none" : keywords.map((one) => `“${one}”`).join(", ");
186
217
  const problems = [];
187
218
  if (input.topicKeyword !== null && !keywords.includes(input.topicKeyword))
@@ -200,10 +231,11 @@ function checkTopics(deps, input, kept) {
200
231
  message: `The schedule wasn't saved. ${shown.join(" ")}${more > 0 ? ` And ${String(more)} more like these.` : ""}`,
201
232
  };
202
233
  }
203
- function summary(input, nextRunAt, now, status = "active", version = 1, topics = topicStateIdle) {
234
+ function summary(input, nextRunAt, now, status = "active", version = 1, topics = topicStateIdle, topicScoring = "off") {
204
235
  return {
205
236
  id: input.id,
206
237
  name: input.name,
238
+ templateKind: input.templateKind,
207
239
  templateId: input.templateId,
208
240
  templateVersion: input.templateVersion,
209
241
  cadence: input.cadence,
@@ -217,6 +249,7 @@ function summary(input, nextRunAt, now, status = "active", version = 1, topics =
217
249
  brief: input.brief === null || input.brief === "" ? null : input.brief,
218
250
  topicGeneration: input.topicGeneration,
219
251
  releases: input.releases,
252
+ topicScoring: input.topicScoring ?? topicScoring,
220
253
  topics,
221
254
  status,
222
255
  version,
@@ -161,3 +161,18 @@ export function formatTopicList(rows, topicKeyword) {
161
161
  : { [topicKeyword]: row.title, ...row.values });
162
162
  return stringify(items, { lineWidth: 0 });
163
163
  }
164
+ // The keywords a schedule's topics may fill: the version it runs, and the newest one, which a
165
+ // schedule may be set up for before it is marked ready (a keyword the run doesn't use is
166
+ // filled and left unused).
167
+ export function scheduleKeywords(deps, templateId) {
168
+ const runs = deps.template(templateId);
169
+ if (runs === undefined)
170
+ return undefined;
171
+ const head = deps.templateHead?.(templateId);
172
+ return [
173
+ ...new Set([
174
+ ...templateKeywords(runs.document.form),
175
+ ...(head === undefined ? [] : templateKeywords(head.document.form)),
176
+ ]),
177
+ ];
178
+ }
@@ -0,0 +1,104 @@
1
+ import { z } from "zod";
2
+ import { noveltyOf, orderQueue, readingOf, topicKey, topicScore, } from "./score.js";
3
+ // ceiling: three topics asked at once, and the whole scoring of one generation stops after
4
+ // 15 seconds; what wasn't read by then is scored by novelty alone, never held up.
5
+ const concurrency = 3;
6
+ export const scoringBudgetMs = 15_000;
7
+ const scoreRow = z.object({
8
+ topic_key: z.string(),
9
+ title: z.string(),
10
+ score: z.number(),
11
+ reasons: z.string(),
12
+ pinned: z.number(),
13
+ });
14
+ export function scoresOf(db, scheduleId) {
15
+ return new Map(db
16
+ .prepare("SELECT topic_key,title,score,reasons,pinned FROM topic_scores WHERE schedule_id=?")
17
+ .all(scheduleId)
18
+ .map((raw) => {
19
+ const row = scoreRow.parse(raw);
20
+ return [
21
+ row.topic_key,
22
+ {
23
+ title: row.title,
24
+ score: row.score,
25
+ reasons: z.array(z.string()).parse(JSON.parse(row.reasons)),
26
+ pinned: row.pinned === 1,
27
+ },
28
+ ];
29
+ }));
30
+ }
31
+ // A topic scored again keeps whether the person pinned it. `pinNew` scores topics that had no
32
+ // score (ones the person added) without moving them.
33
+ export function saveScores(db, scheduleId, scores, at, pinNew = false) {
34
+ const upsert = db.prepare(`INSERT INTO topic_scores (schedule_id,topic_key,title,score,interest,novelty,reasons,pinned,scored_at)
35
+ VALUES (?,?,?,?,?,?,?,${pinNew ? 1 : 0},?)
36
+ ON CONFLICT(schedule_id,topic_key) DO UPDATE SET title=excluded.title,score=excluded.score,
37
+ interest=excluded.interest,novelty=excluded.novelty,reasons=excluded.reasons,
38
+ scored_at=excluded.scored_at`);
39
+ for (const [title, score] of scores)
40
+ upsert.run(scheduleId, topicKey(title), title, score.score, score.interest, score.novelty, JSON.stringify(score.reasons), at);
41
+ }
42
+ // A topic the person moved stays where they put it.
43
+ export function pinTopic(db, scheduleId, title) {
44
+ db.prepare("UPDATE topic_scores SET pinned=1 WHERE schedule_id=? AND topic_key=?").run(scheduleId, topicKey(title));
45
+ }
46
+ // The queue in score order (`orderQueue`), from the scores kept for it.
47
+ export function rankedItems(db, scheduleId, items) {
48
+ return orderQueue(items, scoresOf(db, scheduleId));
49
+ }
50
+ // Scores each title: its novelty against the library, and with "search" its public interest.
51
+ // "library" never asks anything outside this computer. Never throws: a topic search didn't
52
+ // answer for in time is scored by novelty and says so.
53
+ export async function scoreTopics(deps, scoring, titles, known, language, signal) {
54
+ const readings = new Map();
55
+ const interest = deps.interest;
56
+ if (scoring === "search" && interest !== undefined) {
57
+ const budget = AbortSignal.any([
58
+ AbortSignal.timeout(scoringBudgetMs),
59
+ ...(signal === undefined ? [] : [signal]),
60
+ ]);
61
+ const queue = [...titles];
62
+ const worker = async () => {
63
+ for (let title = queue.shift(); title !== undefined; title = queue.shift()) {
64
+ if (budget.aborted)
65
+ return;
66
+ try {
67
+ readings.set(title, await interest(title, language, budget));
68
+ }
69
+ catch {
70
+ // The port never throws; a broken adapter is treated as no connection.
71
+ }
72
+ }
73
+ };
74
+ await Promise.race([
75
+ Promise.all(Array.from({ length: concurrency }, worker)),
76
+ new Promise((resolve) => budget.addEventListener("abort", () => resolve(), { once: true })),
77
+ ]);
78
+ }
79
+ return new Map(titles.map((title) => {
80
+ // A queued topic is in the library's list itself; it is compared with the others.
81
+ const novelty = noveltyOf(title, {
82
+ topics: known.topics.filter((other) => topicKey(other) !== topicKey(title)),
83
+ projects: known.projects,
84
+ });
85
+ if (scoring === "library")
86
+ return [title, topicScore(novelty, null, "off")];
87
+ const result = readings.get(title);
88
+ const reading = result?.ok === true ? readingOf(title, result) : null;
89
+ return [
90
+ title,
91
+ topicScore(novelty, reading, reading === null ? "unreachable" : "read"),
92
+ ];
93
+ }));
94
+ }
95
+ // The scores kept for these titles, by key, as the schedule page and agents read them.
96
+ export function scoresFor(db, scheduleId, titles) {
97
+ const kept = scoresOf(db, scheduleId);
98
+ return Object.fromEntries(titles.flatMap((title) => {
99
+ const found = kept.get(topicKey(title));
100
+ return found === undefined
101
+ ? []
102
+ : [[topicKey(title), { score: found.score, reasons: found.reasons }]];
103
+ }));
104
+ }