@agent-native/core 0.134.1 → 0.135.1

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 (463) hide show
  1. package/corpus/README.md +3 -3
  2. package/corpus/core/CHANGELOG.md +113 -0
  3. package/corpus/core/package.json +3 -2
  4. package/corpus/core/src/automations/service.ts +41 -4
  5. package/corpus/core/src/cli/mcp-config-writers.ts +27 -21
  6. package/corpus/core/src/client/agent-page/AgentJobsTab.tsx +187 -85
  7. package/corpus/core/src/client/agent-page/AutomationDetailsDialog.tsx +208 -0
  8. package/corpus/core/src/client/agent-page/AutomationScheduleDialog.tsx +180 -0
  9. package/corpus/core/src/client/agent-page/TimezoneSelect.tsx +241 -0
  10. package/corpus/core/src/client/agent-page/use-jobs.ts +50 -10
  11. package/corpus/core/src/client/org/OrgSwitcher.tsx +26 -0
  12. package/corpus/core/src/client/settings/AccountSettingsCard.tsx +35 -31
  13. package/corpus/core/src/client/settings/SchedulingTimezoneField.tsx +71 -0
  14. package/corpus/core/src/client/settings/SettingsRow.tsx +113 -0
  15. package/corpus/core/src/client/settings/agent-settings-search.ts +2 -1
  16. package/corpus/core/src/client/settings/index.ts +6 -0
  17. package/corpus/core/src/integrations/identity.ts +2 -12
  18. package/corpus/core/src/jobs/actions/list-automation-runs.ts +33 -0
  19. package/corpus/core/src/jobs/actions/list-recurring-jobs.ts +27 -6
  20. package/corpus/core/src/jobs/actions/manage-recurring-job.ts +41 -9
  21. package/corpus/core/src/jobs/background-automation-runner.ts +88 -0
  22. package/corpus/core/src/jobs/cron.ts +52 -6
  23. package/corpus/core/src/jobs/frontmatter.ts +20 -0
  24. package/corpus/core/src/jobs/run-history.ts +242 -0
  25. package/corpus/core/src/jobs/scheduler.ts +73 -18
  26. package/corpus/core/src/jobs/tools.ts +54 -6
  27. package/corpus/core/src/localization/actions/get-localization-preference.ts +2 -2
  28. package/corpus/core/src/localization/actions/set-localization-preference.ts +49 -11
  29. package/corpus/core/src/localization/default-messages.ts +27 -0
  30. package/corpus/core/src/localization/shared.ts +39 -6
  31. package/corpus/core/src/localization/user-timezone.ts +36 -0
  32. package/corpus/core/src/org/app-roles-handlers.ts +1 -9
  33. package/corpus/core/src/org/index.ts +2 -0
  34. package/corpus/core/src/org/membership.ts +18 -0
  35. package/corpus/core/src/review/actions/create-review-comment.ts +4 -1
  36. package/corpus/core/src/review/actions/reply-review-comment.ts +4 -1
  37. package/corpus/core/src/review/index.ts +5 -0
  38. package/corpus/core/src/review/notifications.ts +163 -0
  39. package/corpus/core/src/review/types.ts +7 -0
  40. package/corpus/core/src/server/action-discovery.ts +4 -0
  41. package/corpus/core/src/server/activity-notifications.ts +174 -0
  42. package/corpus/core/src/server/app-name.ts +31 -0
  43. package/corpus/core/src/server/better-auth-instance.ts +4 -4
  44. package/corpus/core/src/server/email-templates.ts +70 -14
  45. package/corpus/core/src/server/email.ts +66 -3
  46. package/corpus/core/src/server/index.ts +10 -0
  47. package/corpus/core/src/sharing/access.ts +44 -2
  48. package/corpus/core/src/sharing/index.ts +5 -0
  49. package/corpus/core/src/sharing/recipients.ts +67 -0
  50. package/corpus/core/src/triggers/actions/list-automations.ts +27 -8
  51. package/corpus/core/src/triggers/actions/manage-automation.ts +17 -6
  52. package/corpus/core/src/triggers/actions.ts +16 -4
  53. package/corpus/core/src/triggers/dispatcher.ts +44 -30
  54. package/corpus/core/src/triggers/routes.ts +19 -6
  55. package/corpus/core/src/vitest-config.ts +68 -0
  56. package/corpus/core/vitest.config.ts +5 -0
  57. package/corpus/templates/analytics/app/pages/Settings.tsx +37 -53
  58. package/corpus/templates/analytics/changelog/2026-08-01-new-error-alerts-now-reach-you-by-email-and-a-scheduled-dash.md +6 -0
  59. package/corpus/templates/analytics/server/jobs/dashboard-report.ts +43 -0
  60. package/corpus/templates/analytics/server/lib/error-capture.ts +5 -1
  61. package/corpus/templates/analytics/vitest.config.ts +16 -12
  62. package/corpus/templates/assets/actions/refresh-generation-run.ts +3 -0
  63. package/corpus/templates/assets/app/components/ui/switch.tsx +1 -0
  64. package/corpus/templates/assets/app/hooks/use-assets-prefs.ts +55 -0
  65. package/corpus/templates/assets/app/i18n-data.ts +30 -0
  66. package/corpus/templates/assets/app/routes/settings.tsx +58 -55
  67. package/corpus/templates/assets/changelog/2026-08-01-get-an-email-when-a-generation-you-started-finishes-or-fails.md +6 -0
  68. package/corpus/templates/assets/server/lib/generation-run-notifications.ts +89 -0
  69. package/corpus/templates/assets/server/lib/video-runs.ts +8 -1
  70. package/corpus/templates/assets/server/routes/_agent-native/assets/user-prefs.get.ts +17 -0
  71. package/corpus/templates/assets/server/routes/_agent-native/assets/user-prefs.put.ts +36 -0
  72. package/corpus/templates/assets/shared/assets-user-prefs.ts +10 -0
  73. package/corpus/templates/assets/vitest.config.ts +16 -12
  74. package/corpus/templates/brain/app/routes/settings.tsx +15 -15
  75. package/corpus/templates/brain/vitest.config.ts +16 -12
  76. package/corpus/templates/calendar/app/pages/Settings.tsx +32 -40
  77. package/corpus/templates/calendar/changelog/2026-08-01-language-and-appearance-now-sit-together-in-one-preferences-.md +6 -0
  78. package/corpus/templates/calendar/vitest.config.ts +16 -12
  79. package/corpus/templates/chat/app/routes/settings.tsx +14 -22
  80. package/corpus/templates/chat/vitest.config.ts +9 -0
  81. package/corpus/templates/clips/.agents/skills/video-editing/SKILL.md +31 -0
  82. package/corpus/templates/clips/AGENTS.md +1 -0
  83. package/corpus/templates/clips/actions/add-comment.ts +12 -1
  84. package/corpus/templates/clips/actions/create-meeting.ts +2 -2
  85. package/corpus/templates/clips/actions/create-recording.ts +2 -2
  86. package/corpus/templates/clips/actions/finalize-recording.ts +42 -6
  87. package/corpus/templates/clips/actions/generate-filmstrip.ts +154 -0
  88. package/corpus/templates/clips/actions/get-recording-player-data.ts +6 -0
  89. package/corpus/templates/clips/actions/import-loom-recording.ts +2 -2
  90. package/corpus/templates/clips/actions/lib/ensure-recording-filmstrip.ts +258 -0
  91. package/corpus/templates/clips/actions/lib/ensure-seekable-video.ts +11 -3
  92. package/corpus/templates/clips/actions/react-to-comment.ts +13 -0
  93. package/corpus/templates/clips/actions/react-to-recording.ts +10 -1
  94. package/corpus/templates/clips/actions/reply-to-comment.ts +12 -1
  95. package/corpus/templates/clips/actions/stitch-recordings.ts +2 -2
  96. package/corpus/templates/clips/app/components/editor/editor-layout.tsx +152 -23
  97. package/corpus/templates/clips/app/components/editor/waveform.tsx +237 -75
  98. package/corpus/templates/clips/app/components/recorder/recorder-engine.ts +23 -1
  99. package/corpus/templates/clips/app/components/settings/ai-setup-section.tsx +441 -0
  100. package/corpus/templates/clips/app/components/settings/slack-section.tsx +329 -0
  101. package/corpus/templates/clips/app/components/settings/types.ts +14 -0
  102. package/corpus/templates/clips/app/components/settings/video-storage-section.tsx +443 -0
  103. package/corpus/templates/clips/app/hooks/use-secret-status.ts +69 -0
  104. package/corpus/templates/clips/app/i18n/en-US.ts +9 -1
  105. package/corpus/templates/clips/app/lib/video-filmstrip.ts +262 -0
  106. package/corpus/templates/clips/app/routes/_app.settings._index.tsx +165 -1351
  107. package/corpus/templates/clips/changelog/2026-07-27-saved-clips-now-resume-from-the-last-uploaded-byte-after-an-.md +6 -0
  108. package/corpus/templates/clips/changelog/2026-07-31-editor-timeline-now-shows-video-frames-behind-the-waveform-g.md +6 -0
  109. package/corpus/templates/clips/changelog/2026-07-31-email-notifications-now-go-out-when-someone-comments-on-or-r.md +6 -0
  110. package/corpus/templates/clips/changelog/2026-07-31-you-can-now-set-a-default-visibility-for-new-recordings-in-s.md +6 -0
  111. package/corpus/templates/clips/changelog/2026-08-01-email-notifications-for-comments-and-reactions-now-actually-.md +6 -0
  112. package/corpus/templates/clips/changelog/2026-08-03-the-desktop-app-now-opens-directly-beneath-its-tray-icon-on-.md +6 -0
  113. package/corpus/templates/clips/chrome-extension/package.json +1 -0
  114. package/corpus/templates/clips/chrome-extension/vitest.config.ts +9 -0
  115. package/corpus/templates/clips/desktop/package.json +1 -0
  116. package/corpus/templates/clips/desktop/src/app.tsx +17 -1
  117. package/corpus/templates/clips/desktop/src/lib/recorder.ts +331 -36
  118. package/corpus/templates/clips/desktop/src/lib/upload-recovery.ts +129 -0
  119. package/corpus/templates/clips/desktop/src-tauri/src/native_screen.rs +815 -15
  120. package/corpus/templates/clips/desktop/src-tauri/src/tray.rs +61 -11
  121. package/corpus/templates/clips/desktop/vitest.config.ts +9 -0
  122. package/corpus/templates/clips/server/db/schema.ts +17 -0
  123. package/corpus/templates/clips/server/jobs/media-verification.ts +4 -0
  124. package/corpus/templates/clips/server/lib/activity-notifications.ts +185 -0
  125. package/corpus/templates/clips/server/lib/recording-media-cleanup.ts +2 -0
  126. package/corpus/templates/clips/server/lib/recording-upload-state.ts +46 -9
  127. package/corpus/templates/clips/server/lib/recordings.ts +24 -0
  128. package/corpus/templates/clips/server/lib/resumable-session.ts +10 -4
  129. package/corpus/templates/clips/server/lib/transactional-email-templates.ts +109 -0
  130. package/corpus/templates/clips/server/lib/upload-interruption.ts +8 -0
  131. package/corpus/templates/clips/server/lib/upload-lease.ts +50 -4
  132. package/corpus/templates/clips/server/lib/video-filmstrip-sprite.ts +207 -0
  133. package/corpus/templates/clips/server/lib/video-remux.ts +13 -4
  134. package/corpus/templates/clips/server/plugins/db.ts +16 -0
  135. package/corpus/templates/clips/server/routes/api/_agent-native-background/post-finalize-worker.post.ts +4 -0
  136. package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/abort.post.ts +123 -36
  137. package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/chunk.post.ts +159 -29
  138. package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/interrupt.post.ts +197 -0
  139. package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/reset-chunks.post.ts +193 -39
  140. package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/resume.get.ts +191 -12
  141. package/corpus/templates/clips/shared/clips-ai-prefs.ts +6 -0
  142. package/corpus/templates/clips/shared/feature-flags.ts +12 -0
  143. package/corpus/templates/clips/shared/recording-core.ts +4 -0
  144. package/corpus/templates/clips/vitest.config.ts +23 -19
  145. package/corpus/templates/content/actions/_database-utils.ts +113 -4
  146. package/corpus/templates/content/actions/add-comment.ts +15 -1
  147. package/corpus/templates/content/actions/get-document.ts +84 -3
  148. package/corpus/templates/content/actions/list-documents.ts +1 -0
  149. package/corpus/templates/content/app/components/editor/BuilderBodySyncingNotice.tsx +9 -2
  150. package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +34 -15
  151. package/corpus/templates/content/app/components/editor/DocumentToolbar.tsx +55 -13
  152. package/corpus/templates/content/app/components/editor/body-hydration.ts +12 -6
  153. package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +2 -3
  154. package/corpus/templates/content/app/components/sidebar/DocumentSidebar.tsx +5 -12
  155. package/corpus/templates/content/app/components/ui/switch.tsx +1 -0
  156. package/corpus/templates/content/app/hooks/use-content-database.ts +16 -27
  157. package/corpus/templates/content/app/hooks/use-content-prefs.ts +55 -0
  158. package/corpus/templates/content/app/hooks/use-create-page.ts +3 -6
  159. package/corpus/templates/content/app/hooks/use-document-properties.ts +9 -16
  160. package/corpus/templates/content/app/hooks/use-document-versions.ts +3 -3
  161. package/corpus/templates/content/app/hooks/use-documents.ts +85 -29
  162. package/corpus/templates/content/app/hooks/use-notion.ts +15 -13
  163. package/corpus/templates/content/app/i18n-data.ts +72 -1
  164. package/corpus/templates/content/app/lib/document-query.ts +36 -0
  165. package/corpus/templates/content/app/routes/_app.settings.tsx +47 -18
  166. package/corpus/templates/content/changelog/2026-07-30-breadcrumb-menus-now-hide-internal-system-pages.md +6 -0
  167. package/corpus/templates/content/changelog/2026-08-01-get-an-email-when-someone-comments-on-replies-in-or-mentions.md +6 -0
  168. package/corpus/templates/content/changelog/2026-08-02-pages-opened-from-a-database-now-keep-that-database-s-fields.md +6 -0
  169. package/corpus/templates/content/server/lib/comment-notifications.ts +146 -0
  170. package/corpus/templates/content/server/lib/document-context.ts +11 -6
  171. package/corpus/templates/content/server/routes/_agent-native/content/user-prefs.get.ts +17 -0
  172. package/corpus/templates/content/server/routes/_agent-native/content/user-prefs.put.ts +36 -0
  173. package/corpus/templates/content/shared/api.ts +8 -0
  174. package/corpus/templates/content/shared/content-user-prefs.ts +11 -0
  175. package/corpus/templates/content/vitest.config.ts +20 -16
  176. package/corpus/templates/crm/app/routes/settings.tsx +14 -22
  177. package/corpus/templates/crm/vitest.config.ts +8 -4
  178. package/corpus/templates/design/app/routes/settings.tsx +14 -22
  179. package/corpus/templates/design/changelog/2026-08-01-review-comments-replies-and-mentions-now-send-an-email-to-th.md +6 -0
  180. package/corpus/templates/design/vitest.config.ts +18 -12
  181. package/corpus/templates/dispatch/app/routes/settings.tsx +26 -40
  182. package/corpus/templates/dispatch/vitest.config.ts +11 -7
  183. package/corpus/templates/factory/app/routes/settings.tsx +14 -22
  184. package/corpus/templates/factory/vitest.config.ts +9 -0
  185. package/corpus/templates/forms/app/routes/_app.settings.tsx +14 -22
  186. package/corpus/templates/forms/vitest.config.ts +27 -23
  187. package/corpus/templates/macros/app/routes/settings.tsx +14 -22
  188. package/corpus/templates/macros/vitest.config.ts +16 -12
  189. package/corpus/templates/mail/app/pages/SettingsPage.tsx +14 -16
  190. package/corpus/templates/mail/vitest.config.ts +16 -12
  191. package/corpus/templates/plan/app/routes/settings.tsx +30 -44
  192. package/corpus/templates/plan/vitest.config.ts +24 -20
  193. package/corpus/templates/slides/actions/add-slide-comment.ts +12 -1
  194. package/corpus/templates/slides/app/components/ui/switch.tsx +1 -0
  195. package/corpus/templates/slides/app/hooks/use-slides-prefs.ts +55 -0
  196. package/corpus/templates/slides/app/i18n/en-US.ts +4 -0
  197. package/corpus/templates/slides/app/routes/settings.tsx +45 -22
  198. package/corpus/templates/slides/changelog/2026-08-01-get-an-email-when-someone-comments-on-or-replies-in-your-dec.md +6 -0
  199. package/corpus/templates/slides/server/lib/comment-notifications.ts +182 -0
  200. package/corpus/templates/slides/server/routes/_agent-native/slides/user-prefs.get.ts +16 -0
  201. package/corpus/templates/slides/server/routes/_agent-native/slides/user-prefs.put.ts +36 -0
  202. package/corpus/templates/slides/shared/slides-user-prefs.ts +11 -0
  203. package/corpus/templates/slides/vitest.config.ts +17 -13
  204. package/corpus/templates/tasks/app/components/layout/Sidebar.tsx +2 -0
  205. package/corpus/templates/tasks/app/routes/settings.tsx +14 -22
  206. package/corpus/templates/tasks/vitest.config.ts +17 -13
  207. package/corpus/toolkit/CHANGELOG.md +9 -0
  208. package/corpus/toolkit/package.json +1 -1
  209. package/corpus/toolkit/vitest.config.ts +3 -0
  210. package/dist/automations/service.d.ts +2 -0
  211. package/dist/automations/service.d.ts.map +1 -1
  212. package/dist/automations/service.js +29 -4
  213. package/dist/automations/service.js.map +1 -1
  214. package/dist/cli/mcp-config-writers.d.ts.map +1 -1
  215. package/dist/cli/mcp-config-writers.js +24 -23
  216. package/dist/cli/mcp-config-writers.js.map +1 -1
  217. package/dist/client/agent-page/AgentJobsTab.d.ts.map +1 -1
  218. package/dist/client/agent-page/AgentJobsTab.js +86 -29
  219. package/dist/client/agent-page/AgentJobsTab.js.map +1 -1
  220. package/dist/client/agent-page/AutomationDetailsDialog.d.ts +21 -0
  221. package/dist/client/agent-page/AutomationDetailsDialog.d.ts.map +1 -0
  222. package/dist/client/agent-page/AutomationDetailsDialog.js +33 -0
  223. package/dist/client/agent-page/AutomationDetailsDialog.js.map +1 -0
  224. package/dist/client/agent-page/AutomationScheduleDialog.d.ts +15 -0
  225. package/dist/client/agent-page/AutomationScheduleDialog.d.ts.map +1 -0
  226. package/dist/client/agent-page/AutomationScheduleDialog.js +46 -0
  227. package/dist/client/agent-page/AutomationScheduleDialog.js.map +1 -0
  228. package/dist/client/agent-page/TimezoneSelect.d.ts +17 -0
  229. package/dist/client/agent-page/TimezoneSelect.d.ts.map +1 -0
  230. package/dist/client/agent-page/TimezoneSelect.js +107 -0
  231. package/dist/client/agent-page/TimezoneSelect.js.map +1 -0
  232. package/dist/client/agent-page/use-jobs.d.ts +20 -7
  233. package/dist/client/agent-page/use-jobs.d.ts.map +1 -1
  234. package/dist/client/agent-page/use-jobs.js +24 -4
  235. package/dist/client/agent-page/use-jobs.js.map +1 -1
  236. package/dist/client/org/OrgSwitcher.d.ts +8 -1
  237. package/dist/client/org/OrgSwitcher.d.ts.map +1 -1
  238. package/dist/client/org/OrgSwitcher.js +8 -2
  239. package/dist/client/org/OrgSwitcher.js.map +1 -1
  240. package/dist/client/settings/AccountSettingsCard.d.ts.map +1 -1
  241. package/dist/client/settings/AccountSettingsCard.js +10 -9
  242. package/dist/client/settings/AccountSettingsCard.js.map +1 -1
  243. package/dist/client/settings/SchedulingTimezoneField.d.ts +10 -0
  244. package/dist/client/settings/SchedulingTimezoneField.d.ts.map +1 -0
  245. package/dist/client/settings/SchedulingTimezoneField.js +30 -0
  246. package/dist/client/settings/SchedulingTimezoneField.js.map +1 -0
  247. package/dist/client/settings/SettingsRow.d.ts +36 -0
  248. package/dist/client/settings/SettingsRow.d.ts.map +1 -0
  249. package/dist/client/settings/SettingsRow.js +15 -0
  250. package/dist/client/settings/SettingsRow.js.map +1 -0
  251. package/dist/client/settings/agent-settings-search.d.ts.map +1 -1
  252. package/dist/client/settings/agent-settings-search.js +1 -1
  253. package/dist/client/settings/agent-settings-search.js.map +1 -1
  254. package/dist/client/settings/index.d.ts +1 -0
  255. package/dist/client/settings/index.d.ts.map +1 -1
  256. package/dist/client/settings/index.js +1 -0
  257. package/dist/client/settings/index.js.map +1 -1
  258. package/dist/file-upload/actions/upload-image.d.ts +1 -1
  259. package/dist/integrations/identity.d.ts.map +1 -1
  260. package/dist/integrations/identity.js +2 -11
  261. package/dist/integrations/identity.js.map +1 -1
  262. package/dist/jobs/actions/list-automation-runs.d.ts +8 -0
  263. package/dist/jobs/actions/list-automation-runs.d.ts.map +1 -0
  264. package/dist/jobs/actions/list-automation-runs.js +29 -0
  265. package/dist/jobs/actions/list-automation-runs.js.map +1 -0
  266. package/dist/jobs/actions/list-recurring-jobs.d.ts +2 -0
  267. package/dist/jobs/actions/list-recurring-jobs.d.ts.map +1 -1
  268. package/dist/jobs/actions/list-recurring-jobs.js +20 -7
  269. package/dist/jobs/actions/list-recurring-jobs.js.map +1 -1
  270. package/dist/jobs/actions/manage-recurring-job.d.ts +2 -0
  271. package/dist/jobs/actions/manage-recurring-job.js +30 -9
  272. package/dist/jobs/actions/manage-recurring-job.js.map +1 -1
  273. package/dist/jobs/background-automation-runner.d.ts.map +1 -1
  274. package/dist/jobs/background-automation-runner.js +60 -0
  275. package/dist/jobs/background-automation-runner.js.map +1 -1
  276. package/dist/jobs/cron.d.ts +16 -2
  277. package/dist/jobs/cron.d.ts.map +1 -1
  278. package/dist/jobs/cron.js +43 -6
  279. package/dist/jobs/cron.js.map +1 -1
  280. package/dist/jobs/frontmatter.d.ts +12 -0
  281. package/dist/jobs/frontmatter.d.ts.map +1 -1
  282. package/dist/jobs/frontmatter.js +8 -0
  283. package/dist/jobs/frontmatter.js.map +1 -1
  284. package/dist/jobs/run-history.d.ts +56 -0
  285. package/dist/jobs/run-history.d.ts.map +1 -0
  286. package/dist/jobs/run-history.js +169 -0
  287. package/dist/jobs/run-history.js.map +1 -0
  288. package/dist/jobs/scheduler.d.ts.map +1 -1
  289. package/dist/jobs/scheduler.js +51 -18
  290. package/dist/jobs/scheduler.js.map +1 -1
  291. package/dist/jobs/tools.d.ts.map +1 -1
  292. package/dist/jobs/tools.js +37 -6
  293. package/dist/jobs/tools.js.map +1 -1
  294. package/dist/localization/actions/get-localization-preference.d.ts +1 -2
  295. package/dist/localization/actions/get-localization-preference.d.ts.map +1 -1
  296. package/dist/localization/actions/get-localization-preference.js.map +1 -1
  297. package/dist/localization/actions/set-localization-preference.d.ts +3 -3
  298. package/dist/localization/actions/set-localization-preference.d.ts.map +1 -1
  299. package/dist/localization/actions/set-localization-preference.js +33 -8
  300. package/dist/localization/actions/set-localization-preference.js.map +1 -1
  301. package/dist/localization/default-messages.d.ts +25 -0
  302. package/dist/localization/default-messages.d.ts.map +1 -1
  303. package/dist/localization/default-messages.js +25 -0
  304. package/dist/localization/default-messages.js.map +1 -1
  305. package/dist/localization/shared.d.ts +16 -1
  306. package/dist/localization/shared.d.ts.map +1 -1
  307. package/dist/localization/shared.js +22 -4
  308. package/dist/localization/shared.js.map +1 -1
  309. package/dist/localization/user-timezone.d.ts +11 -0
  310. package/dist/localization/user-timezone.d.ts.map +1 -0
  311. package/dist/localization/user-timezone.js +31 -0
  312. package/dist/localization/user-timezone.js.map +1 -0
  313. package/dist/notifications/routes.d.ts +4 -4
  314. package/dist/observability/routes.d.ts +3 -3
  315. package/dist/org/app-roles-handlers.d.ts.map +1 -1
  316. package/dist/org/app-roles-handlers.js +1 -8
  317. package/dist/org/app-roles-handlers.js.map +1 -1
  318. package/dist/org/index.d.ts +1 -0
  319. package/dist/org/index.d.ts.map +1 -1
  320. package/dist/org/index.js +1 -0
  321. package/dist/org/index.js.map +1 -1
  322. package/dist/org/membership.d.ts +6 -0
  323. package/dist/org/membership.d.ts.map +1 -0
  324. package/dist/org/membership.js +16 -0
  325. package/dist/org/membership.js.map +1 -0
  326. package/dist/progress/routes.d.ts +1 -1
  327. package/dist/provider-api/actions/custom-provider-registration.d.ts +13 -13
  328. package/dist/provider-api/actions/provider-api.d.ts +8 -8
  329. package/dist/provider-api/corpus-jobs.d.ts +2 -2
  330. package/dist/review/actions/create-review-comment.d.ts +32 -1
  331. package/dist/review/actions/create-review-comment.d.ts.map +1 -1
  332. package/dist/review/actions/create-review-comment.js +3 -1
  333. package/dist/review/actions/create-review-comment.js.map +1 -1
  334. package/dist/review/actions/reply-review-comment.d.ts +32 -1
  335. package/dist/review/actions/reply-review-comment.d.ts.map +1 -1
  336. package/dist/review/actions/reply-review-comment.js +3 -1
  337. package/dist/review/actions/reply-review-comment.js.map +1 -1
  338. package/dist/review/index.d.ts +1 -0
  339. package/dist/review/index.d.ts.map +1 -1
  340. package/dist/review/index.js +1 -0
  341. package/dist/review/index.js.map +1 -1
  342. package/dist/review/notifications.d.ts +24 -0
  343. package/dist/review/notifications.d.ts.map +1 -0
  344. package/dist/review/notifications.js +123 -0
  345. package/dist/review/notifications.js.map +1 -0
  346. package/dist/review/types.d.ts +5 -0
  347. package/dist/review/types.d.ts.map +1 -1
  348. package/dist/review/types.js.map +1 -1
  349. package/dist/server/action-discovery.d.ts.map +1 -1
  350. package/dist/server/action-discovery.js +4 -0
  351. package/dist/server/action-discovery.js.map +1 -1
  352. package/dist/server/activity-notifications.d.ts +61 -0
  353. package/dist/server/activity-notifications.d.ts.map +1 -0
  354. package/dist/server/activity-notifications.js +103 -0
  355. package/dist/server/activity-notifications.js.map +1 -0
  356. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  357. package/dist/server/app-name.d.ts +18 -0
  358. package/dist/server/app-name.d.ts.map +1 -1
  359. package/dist/server/app-name.js +31 -0
  360. package/dist/server/app-name.js.map +1 -1
  361. package/dist/server/better-auth-instance.js +4 -4
  362. package/dist/server/better-auth-instance.js.map +1 -1
  363. package/dist/server/email-templates.d.ts +12 -0
  364. package/dist/server/email-templates.d.ts.map +1 -1
  365. package/dist/server/email-templates.js +57 -14
  366. package/dist/server/email-templates.js.map +1 -1
  367. package/dist/server/email.d.ts +12 -0
  368. package/dist/server/email.d.ts.map +1 -1
  369. package/dist/server/email.js +52 -5
  370. package/dist/server/email.js.map +1 -1
  371. package/dist/server/index.d.ts +1 -0
  372. package/dist/server/index.d.ts.map +1 -1
  373. package/dist/server/index.js +1 -0
  374. package/dist/server/index.js.map +1 -1
  375. package/dist/server/transcribe-voice.d.ts +1 -1
  376. package/dist/sharing/access.d.ts.map +1 -1
  377. package/dist/sharing/access.js +32 -2
  378. package/dist/sharing/access.js.map +1 -1
  379. package/dist/sharing/index.d.ts +1 -0
  380. package/dist/sharing/index.d.ts.map +1 -1
  381. package/dist/sharing/index.js +1 -0
  382. package/dist/sharing/index.js.map +1 -1
  383. package/dist/sharing/recipients.d.ts +31 -0
  384. package/dist/sharing/recipients.d.ts.map +1 -0
  385. package/dist/sharing/recipients.js +40 -0
  386. package/dist/sharing/recipients.js.map +1 -0
  387. package/dist/templates/chat/app/routes/settings.tsx +14 -22
  388. package/dist/templates/chat/vitest.config.ts +9 -0
  389. package/dist/triggers/actions/list-automations.d.ts +2 -0
  390. package/dist/triggers/actions/list-automations.d.ts.map +1 -1
  391. package/dist/triggers/actions/list-automations.js +19 -8
  392. package/dist/triggers/actions/list-automations.js.map +1 -1
  393. package/dist/triggers/actions/manage-automation.d.ts +6 -0
  394. package/dist/triggers/actions/manage-automation.js +12 -6
  395. package/dist/triggers/actions/manage-automation.js.map +1 -1
  396. package/dist/triggers/actions.d.ts.map +1 -1
  397. package/dist/triggers/actions.js +15 -4
  398. package/dist/triggers/actions.js.map +1 -1
  399. package/dist/triggers/dispatcher.d.ts.map +1 -1
  400. package/dist/triggers/dispatcher.js +19 -17
  401. package/dist/triggers/dispatcher.js.map +1 -1
  402. package/dist/triggers/routes.d.ts.map +1 -1
  403. package/dist/triggers/routes.js +6 -6
  404. package/dist/triggers/routes.js.map +1 -1
  405. package/dist/vitest-config.d.ts +10 -0
  406. package/dist/vitest-config.d.ts.map +1 -0
  407. package/dist/vitest-config.js +55 -0
  408. package/dist/vitest-config.js.map +1 -0
  409. package/package.json +5 -4
  410. package/src/automations/service.ts +41 -4
  411. package/src/cli/mcp-config-writers.ts +27 -21
  412. package/src/client/agent-page/AgentJobsTab.tsx +187 -85
  413. package/src/client/agent-page/AutomationDetailsDialog.tsx +208 -0
  414. package/src/client/agent-page/AutomationScheduleDialog.tsx +180 -0
  415. package/src/client/agent-page/TimezoneSelect.tsx +241 -0
  416. package/src/client/agent-page/use-jobs.ts +50 -10
  417. package/src/client/org/OrgSwitcher.tsx +26 -0
  418. package/src/client/settings/AccountSettingsCard.tsx +35 -31
  419. package/src/client/settings/SchedulingTimezoneField.tsx +71 -0
  420. package/src/client/settings/SettingsRow.tsx +113 -0
  421. package/src/client/settings/agent-settings-search.ts +2 -1
  422. package/src/client/settings/index.ts +6 -0
  423. package/src/integrations/identity.ts +2 -12
  424. package/src/jobs/actions/list-automation-runs.ts +33 -0
  425. package/src/jobs/actions/list-recurring-jobs.ts +27 -6
  426. package/src/jobs/actions/manage-recurring-job.ts +41 -9
  427. package/src/jobs/background-automation-runner.ts +88 -0
  428. package/src/jobs/cron.ts +52 -6
  429. package/src/jobs/frontmatter.ts +20 -0
  430. package/src/jobs/run-history.ts +242 -0
  431. package/src/jobs/scheduler.ts +73 -18
  432. package/src/jobs/tools.ts +54 -6
  433. package/src/localization/actions/get-localization-preference.ts +2 -2
  434. package/src/localization/actions/set-localization-preference.ts +49 -11
  435. package/src/localization/default-messages.ts +27 -0
  436. package/src/localization/shared.ts +39 -6
  437. package/src/localization/user-timezone.ts +36 -0
  438. package/src/org/app-roles-handlers.ts +1 -9
  439. package/src/org/index.ts +2 -0
  440. package/src/org/membership.ts +18 -0
  441. package/src/review/actions/create-review-comment.ts +4 -1
  442. package/src/review/actions/reply-review-comment.ts +4 -1
  443. package/src/review/index.ts +5 -0
  444. package/src/review/notifications.ts +163 -0
  445. package/src/review/types.ts +7 -0
  446. package/src/server/action-discovery.ts +4 -0
  447. package/src/server/activity-notifications.ts +174 -0
  448. package/src/server/app-name.ts +31 -0
  449. package/src/server/better-auth-instance.ts +4 -4
  450. package/src/server/email-templates.ts +70 -14
  451. package/src/server/email.ts +66 -3
  452. package/src/server/index.ts +10 -0
  453. package/src/sharing/access.ts +44 -2
  454. package/src/sharing/index.ts +5 -0
  455. package/src/sharing/recipients.ts +67 -0
  456. package/src/templates/chat/app/routes/settings.tsx +14 -22
  457. package/src/templates/chat/vitest.config.ts +9 -0
  458. package/src/triggers/actions/list-automations.ts +27 -8
  459. package/src/triggers/actions/manage-automation.ts +17 -6
  460. package/src/triggers/actions.ts +16 -4
  461. package/src/triggers/dispatcher.ts +44 -30
  462. package/src/triggers/routes.ts +19 -6
  463. package/src/vitest-config.ts +68 -0
@@ -3,6 +3,8 @@ import { LanguagePicker, useT } from "@agent-native/core/client/i18n";
3
3
  import { TeamPage } from "@agent-native/core/client/org";
4
4
  import {
5
5
  AccountSettingsCard,
6
+ SettingsGroup,
7
+ SettingsRow,
6
8
  SettingsTabsPage,
7
9
  useAgentSettingsTabs,
8
10
  type SettingsSearchEntry,
@@ -10,14 +12,6 @@ import {
10
12
  import { useSetPageTitle } from "@agent-native/toolkit/app-shell";
11
13
  import { useMemo } from "react";
12
14
 
13
- import {
14
- Card,
15
- CardContent,
16
- CardDescription,
17
- CardHeader,
18
- CardTitle,
19
- } from "@/components/ui/card";
20
- import { Label } from "@/components/ui/label";
21
15
  import { APP_TITLE } from "@/lib/app-config";
22
16
 
23
17
  import changelog from "../../CHANGELOG.md?raw";
@@ -55,20 +49,18 @@ export default function SettingsRoute() {
55
49
  {t("settings.description")}
56
50
  </p>
57
51
 
58
- <Card id="language" className="scroll-mt-16">
59
- <CardHeader>
60
- <CardTitle className="text-base">
61
- {t("settings.languageTitle")}
62
- </CardTitle>
63
- <CardDescription>
64
- {t("settings.languageDescription")}
65
- </CardDescription>
66
- </CardHeader>
67
- <CardContent className="max-w-xs space-y-1.5">
68
- <Label>{t("settings.languageLabel")}</Label>
69
- <LanguagePicker label={t("settings.languageLabel")} />
70
- </CardContent>
71
- </Card>
52
+ <SettingsGroup>
53
+ <SettingsRow
54
+ id="language"
55
+ label={t("settings.languageTitle")}
56
+ description={t("settings.languageDescription")}
57
+ control={
58
+ <div className="w-56">
59
+ <LanguagePicker label={t("settings.languageLabel")} />
60
+ </div>
61
+ }
62
+ />
63
+ </SettingsGroup>
72
64
  </div>
73
65
  }
74
66
  team={
@@ -0,0 +1,9 @@
1
+ import baseConfig from "@agent-native/core/vitest-config";
2
+ import { mergeConfig } from "vitest/config";
3
+
4
+ import viteConfig from "./vite.config";
5
+
6
+ // Kept separate from vite.config.ts so the production config never imports
7
+ // vitest: `agent-native build` loads vite.config.ts in installs where vitest
8
+ // is absent.
9
+ export default mergeConfig(viteConfig, baseConfig);
@@ -229,6 +229,37 @@ downsamples to a 2000-point peaks array. Peaks are cached in
229
229
  recompute. The editor reads the cache first and falls back to computing only
230
230
  when the key is missing or corrupted.
231
231
 
232
+ ### Timeline filmstrip
233
+
234
+ The trim track shows video frames behind the waveform. Two paths, and the order
235
+ matters:
236
+
237
+ 1. **Sprite (preferred).** `generate-filmstrip` runs one ffmpeg pass
238
+ (`fps` → `scale` → `pad` → `tile`) into a single JPEG grid, uploads it, and
239
+ stores the URL plus grid geometry on the recording. The editor renders cells
240
+ with CSS `background-position` — one cached image request, no video decoding
241
+ in the browser.
242
+ 2. **Browser extraction (fallback).** `extractFilmstripThumbnails()` seeks a
243
+ detached `<video>` once per frame. Only used when there is no sprite, which
244
+ means hosts without ffmpeg and local/dev media the server cannot fetch.
245
+
246
+ Three traps this design exists to avoid:
247
+
248
+ - **Pass the proxied URL.** Frame extraction must use `getWaveformMediaUrl()`,
249
+ never `recording.videoUrl` — reading pixels back out of a cross-origin video
250
+ taints the canvas and `toDataURL` throws, so provider media silently yields no
251
+ filmstrip at all.
252
+ - **Both paths sample cell midpoints,** not `0 … duration` endpoints. The strip
253
+ renders N equal cells, so cell `i` must show the middle of the slot it
254
+ occupies or every thumbnail sits up to half a cell from the time beneath it.
255
+ - **Cell count comes from the geometry,** `trackWidth / (height × aspect)`, not
256
+ from the sprite's frame count. Rendering all 40 frames across an unzoomed
257
+ track makes each cell portrait, and `object-cover` then shows a narrow centre
258
+ slice of each frame instead of a recognisable thumbnail.
259
+
260
+ The filmstrip never replaces the waveform — peaks still draw on top of a scrim,
261
+ because frames say nothing about the audio.
262
+
232
263
  ### Keyboard shortcuts (editor scope)
233
264
 
234
265
  - **Space** — play / pause (even while focused in the editor area)
@@ -92,6 +92,7 @@ Read the matching skill before deeper work in that area:
92
92
  | `move-recording` | Move `id` or `ids` to a folder or root |
93
93
  | `archive-`, `trash-`, `restore-recording` | Lifecycle |
94
94
  | `reprocess-recording` | Repair unseekable/frozen media |
95
+ | `generate-filmstrip` | Editor timeline frame sprite; `all` backfills |
95
96
  | `request-transcript`, `cleanup-transcript` | Transcribe; `force`/`regenerate` |
96
97
  | `regenerate-title`, `-summary`, `-chapters` | AI metadata |
97
98
  | `trim-`, `split-recording`, `remove-silences`, `remove-filler-words` | Edits |
@@ -15,6 +15,7 @@ import { eq } from "drizzle-orm";
15
15
  import { z } from "zod";
16
16
 
17
17
  import { getDb, schema } from "../server/db/index.js";
18
+ import { notifyRecordingComment } from "../server/lib/activity-notifications.js";
18
19
  import { nanoid } from "../server/lib/recordings.js";
19
20
 
20
21
  export default defineAction({
@@ -80,12 +81,22 @@ export default defineAction({
80
81
  updatedAt: now,
81
82
  });
82
83
 
84
+ const notified = await notifyRecordingComment({
85
+ recordingId: args.recordingId,
86
+ threadId,
87
+ authorEmail,
88
+ authorName: args.authorName,
89
+ content: args.content,
90
+ videoTimestampMs: args.videoTimestampMs,
91
+ isReply: Boolean(parentId),
92
+ });
93
+
83
94
  await writeAppState("refresh-signal", { ts: Date.now() });
84
95
 
85
96
  console.log(
86
97
  `Added comment to recording ${args.recordingId} @ ${args.videoTimestampMs}ms (thread: ${threadId})`,
87
98
  );
88
99
 
89
- return { id, threadId };
100
+ return { id, threadId, notified };
90
101
  },
91
102
  });
@@ -17,7 +17,7 @@ import { getDb, schema } from "../server/db/index.js";
17
17
  import {
18
18
  getCurrentOwnerEmail,
19
19
  getActiveOrganizationId,
20
- getOrganizationDefaultVisibility,
20
+ getDefaultRecordingVisibility,
21
21
  nanoid,
22
22
  } from "../server/lib/recordings.js";
23
23
 
@@ -191,7 +191,7 @@ export default defineAction({
191
191
  }
192
192
 
193
193
  const visibility =
194
- args.visibility ?? (await getOrganizationDefaultVisibility(orgId));
194
+ args.visibility ?? (await getDefaultRecordingVisibility(orgId));
195
195
 
196
196
  try {
197
197
  await db.insert(schema.meetings).values({
@@ -20,7 +20,7 @@ import { createError } from "h3";
20
20
  import { getDb, schema } from "../server/db/index.js";
21
21
  import {
22
22
  getCurrentOwnerEmail,
23
- getOrganizationDefaultVisibility,
23
+ getDefaultRecordingVisibility,
24
24
  nanoid,
25
25
  requireOrganizationAccess,
26
26
  stringifySpaceIds,
@@ -53,7 +53,7 @@ export default defineAction({
53
53
  args.organizationId,
54
54
  );
55
55
  const defaultVisibility =
56
- await getOrganizationDefaultVisibility(organizationId);
56
+ await getDefaultRecordingVisibility(organizationId);
57
57
 
58
58
  const spaceIds = (args.spaceIds ?? []).filter(
59
59
  (value, index, arr) => value && arr.indexOf(value) === index,
@@ -902,6 +902,12 @@ export default defineAction({
902
902
  "Whether the uploaded video bytes were already locally transcoded/compressed before upload",
903
903
  ),
904
904
  mediaVerificationRetryAttempt: z.number().int().min(1).max(10).optional(),
905
+ uploadGenerationId: z
906
+ .string()
907
+ .min(1)
908
+ .max(128)
909
+ .optional()
910
+ .describe("Upload generation that owns the scratch data being finalized"),
905
911
  }),
906
912
  run: async (args) => {
907
913
  const db = getDb();
@@ -921,7 +927,7 @@ export default defineAction({
921
927
  // never retain scratch video blobs.
922
928
  let chunkKeysToPurge: string[] = [];
923
929
  try {
924
- const [existing] = await db
930
+ let [existing] = await db
925
931
  .select()
926
932
  .from(schema.recordings)
927
933
  .where(
@@ -938,6 +944,32 @@ export default defineAction({
938
944
  throw new Error(`Recording not found: ${id}`);
939
945
  }
940
946
 
947
+ const generationId = args.uploadGenerationId ?? null;
948
+ if ((existing.uploadGenerationId ?? null) !== generationId) {
949
+ throw new Error("Upload generation changed before finalization");
950
+ }
951
+ // Claim finalization before touching provider/scratch state. Reset only
952
+ // admits uploading/failed rows, so once this CAS succeeds it cannot
953
+ // replace the generation underneath a delayed final chunk.
954
+ if (generationId !== null && existing.status === "uploading") {
955
+ const claimed = await db
956
+ .update(schema.recordings)
957
+ .set({ status: "processing", updatedAt: new Date().toISOString() })
958
+ .where(
959
+ and(
960
+ eq(schema.recordings.id, id),
961
+ ownerEmailMatches(schema.recordings.ownerEmail, ownerEmail),
962
+ eq(schema.recordings.status, "uploading"),
963
+ eq(schema.recordings.uploadGenerationId, generationId),
964
+ ),
965
+ )
966
+ .returning();
967
+ if (claimed.length !== 1) {
968
+ throw new Error("Upload changed before finalization could claim it");
969
+ }
970
+ existing = claimed[0]!;
971
+ }
972
+
941
973
  // Idempotency guard: finalize can be re-invoked when a client retries the
942
974
  // final chunk after a lost response. If already 'ready' return the existing
943
975
  // result instead of re-running the complete/assembly path (session and
@@ -947,7 +979,7 @@ export default defineAction({
947
979
  // A prior attempt may have persisted the ready row and then failed
948
980
  // before deleting its resumable-session handle. The provider upload is
949
981
  // complete at this point, so retire only the local retry state.
950
- await deleteResumableSession(id).catch((err) =>
982
+ await deleteResumableSession(id, generationId).catch((err) =>
951
983
  console.warn("[finalize] failed to delete resumable session:", err),
952
984
  );
953
985
  await deleteAppState(mediaVerificationStateKey(id)).catch(() => {});
@@ -1038,7 +1070,7 @@ export default defineAction({
1038
1070
 
1039
1071
  // Resumable path: create-recording initialized a session and chunk.post.ts
1040
1072
  // forwarded all chunks to the provider. Complete the session to get the CDN URL.
1041
- const resumableSession = await getResumableSession(id);
1073
+ const resumableSession = await getResumableSession(id, generationId);
1042
1074
  if (resumableSession && isStreamingUploadDisabled()) {
1043
1075
  console.warn(
1044
1076
  `[finalize] streaming uploads are disabled, but completing existing resumable session for in-flight recording: ${id}`,
@@ -1147,7 +1179,7 @@ export default defineAction({
1147
1179
  locallyTranscoded: args.locallyTranscoded === true,
1148
1180
  },
1149
1181
  });
1150
- await deleteResumableSession(id).catch((deleteErr) =>
1182
+ await deleteResumableSession(id, generationId).catch((deleteErr) =>
1151
1183
  console.warn(
1152
1184
  "[finalize] failed to retire pending resumable session:",
1153
1185
  deleteErr,
@@ -1178,7 +1210,7 @@ export default defineAction({
1178
1210
  }
1179
1211
  // Delete only after durable state is written — so a retry before
1180
1212
  // this point can still find the session and re-enter this path.
1181
- deleteResumableSession(id).catch((err) =>
1213
+ deleteResumableSession(id, generationId).catch((err) =>
1182
1214
  console.warn("[finalize] failed to delete resumable session:", err),
1183
1215
  );
1184
1216
  return result;
@@ -1262,7 +1294,11 @@ export default defineAction({
1262
1294
  // Pull chunk keys first, then fetch values one at a time. A single
1263
1295
  // SELECT key,value over many base64 chunks can exceed Neon's 8s op
1264
1296
  // timeout before we even start assembling the recording.
1265
- const chunkKeys = await listRecordingChunkKeys(ownerEmail, id);
1297
+ const chunkKeys = await listRecordingChunkKeys(
1298
+ ownerEmail,
1299
+ id,
1300
+ generationId,
1301
+ );
1266
1302
  const expectedDataChunks = stateNumber(uploadState, "expectedDataChunks");
1267
1303
  debugLog("[finalize] chunks found", {
1268
1304
  id,
@@ -0,0 +1,154 @@
1
+ /**
2
+ * Generate editor-timeline filmstrip sprites for stored recordings.
3
+ *
4
+ * The editor shows video frames behind the waveform so a trim range can be
5
+ * found visually. Generating them server-side with one ffmpeg pass replaces
6
+ * per-frame decoding in the browser, which cannot read cross-origin media at
7
+ * all and needs one seek per frame.
8
+ *
9
+ * Non-destructive and idempotent: a recording whose sprite already matches its
10
+ * current media is skipped, and any failure leaves the row untouched so the
11
+ * editor keeps its browser-side fallback.
12
+ *
13
+ * Usage:
14
+ * pnpm action generate-filmstrip --id=<recordingId>
15
+ * pnpm action generate-filmstrip --id=<recordingId> --force
16
+ * pnpm action generate-filmstrip --all --limit=20
17
+ */
18
+
19
+ import { defineAction } from "@agent-native/core";
20
+ import { and, desc, eq, isNotNull, isNull } from "drizzle-orm";
21
+ import { z } from "zod";
22
+
23
+ import { getDb, schema } from "../server/db/index.js";
24
+ import {
25
+ getCurrentOwnerEmail,
26
+ ownerEmailMatches,
27
+ } from "../server/lib/recordings.js";
28
+ import {
29
+ DEFAULT_FILMSTRIP_FRAME_COUNT,
30
+ MAX_FILMSTRIP_FRAME_COUNT,
31
+ } from "../server/lib/video-filmstrip-sprite.js";
32
+ import {
33
+ ensureRecordingFilmstrip,
34
+ type EnsureFilmstripResult,
35
+ } from "./lib/ensure-recording-filmstrip.js";
36
+
37
+ const MAX_TARGETS_PER_CALL = 50;
38
+ const DEFAULT_ALL_LIMIT = 10;
39
+ const cliBoolean = z.preprocess((value) => {
40
+ if (value === "true" || value === "1") return true;
41
+ if (value === "false" || value === "0") return false;
42
+ return value;
43
+ }, z.boolean());
44
+
45
+ export default defineAction({
46
+ description:
47
+ "Generate the editor timeline filmstrip (a sprite of evenly-spaced video frames) for one or more of the caller's recordings, so the editor can show frames behind the waveform without decoding video in the browser. Pass `id` for one clip, `ids` for several, or `all: true` to backfill clips that have no filmstrip yet. Recordings whose sprite already matches their current media are skipped unless `force` is true. Requires ffmpeg on the server; without it this reports `skipped-no-ffmpeg` and the editor falls back to browser-side extraction.",
48
+ schema: z.object({
49
+ id: z
50
+ .string()
51
+ .optional()
52
+ .describe("A single recording id to generate a filmstrip for."),
53
+ ids: z
54
+ .array(z.string())
55
+ .optional()
56
+ .describe("Several recording ids to process in one call."),
57
+ all: cliBoolean
58
+ .optional()
59
+ .describe(
60
+ "Backfill the caller's ready clips that have no filmstrip yet, most recent first, up to `limit`.",
61
+ ),
62
+ limit: z.coerce
63
+ .number()
64
+ .int()
65
+ .min(1)
66
+ .max(MAX_TARGETS_PER_CALL)
67
+ .optional()
68
+ .describe(
69
+ `Max clips to process when using \`all\` (default ${DEFAULT_ALL_LIMIT}).`,
70
+ ),
71
+ frameCount: z.coerce
72
+ .number()
73
+ .int()
74
+ .min(4)
75
+ .max(MAX_FILMSTRIP_FRAME_COUNT)
76
+ .optional()
77
+ .describe(
78
+ `How many frames to sample across the clip (default ${DEFAULT_FILMSTRIP_FRAME_COUNT}). More frames means finer scrubbing detail and a larger sprite.`,
79
+ ),
80
+ force: cliBoolean
81
+ .optional()
82
+ .describe("Regenerate even when a current filmstrip already exists."),
83
+ }),
84
+ run: async (args) => {
85
+ const db = getDb();
86
+ const ownerEmail = getCurrentOwnerEmail();
87
+
88
+ let targetIds: string[] = [];
89
+ if (args.id) targetIds.push(args.id);
90
+ if (args.ids?.length) targetIds.push(...args.ids.filter(Boolean));
91
+
92
+ if (args.all) {
93
+ const conditions = [
94
+ ownerEmailMatches(schema.recordings.ownerEmail, ownerEmail),
95
+ eq(schema.recordings.status, "ready"),
96
+ isNotNull(schema.recordings.videoUrl),
97
+ ];
98
+ if (!args.force) {
99
+ conditions.push(isNull(schema.recordings.filmstripUrl));
100
+ }
101
+ const rows = await db
102
+ .select({ id: schema.recordings.id })
103
+ .from(schema.recordings)
104
+ .where(and(...conditions))
105
+ .orderBy(desc(schema.recordings.createdAt))
106
+ .limit(args.limit ?? DEFAULT_ALL_LIMIT);
107
+ targetIds.push(...rows.map((r) => r.id));
108
+ }
109
+
110
+ // De-dupe while preserving order, and bound the batch so one call can't run
111
+ // unboundedly under the hosted foreground budget.
112
+ targetIds = Array.from(new Set(targetIds)).slice(0, MAX_TARGETS_PER_CALL);
113
+
114
+ if (targetIds.length === 0) {
115
+ return {
116
+ ok: true,
117
+ processed: 0,
118
+ changed: 0,
119
+ results: [] as EnsureFilmstripResult[],
120
+ message:
121
+ "No recordings to process. Pass id / ids, or all: true to backfill clips without a filmstrip.",
122
+ };
123
+ }
124
+
125
+ const results: EnsureFilmstripResult[] = [];
126
+ for (const recordingId of targetIds) {
127
+ try {
128
+ results.push(
129
+ await ensureRecordingFilmstrip({
130
+ recordingId,
131
+ ownerEmail,
132
+ frameCount: args.frameCount,
133
+ force: Boolean(args.force),
134
+ }),
135
+ );
136
+ } catch (err) {
137
+ console.warn("[generate-filmstrip] failed for", recordingId, err);
138
+ results.push({
139
+ recordingId,
140
+ status: "failed-ffmpeg",
141
+ changed: false,
142
+ detail: err instanceof Error ? err.message : String(err),
143
+ });
144
+ }
145
+ }
146
+
147
+ const changed = results.filter((r) => r.changed).length;
148
+ console.log(
149
+ `Processed ${results.length} recording(s); ${changed} filmstrip(s) generated.`,
150
+ );
151
+
152
+ return { ok: true, processed: results.length, changed, results };
153
+ },
154
+ });
@@ -314,6 +314,12 @@ export default defineAction({
314
314
  description: rec.description,
315
315
  thumbnailUrl: rec.thumbnailUrl,
316
316
  animatedThumbnailUrl: rec.animatedThumbnailUrl,
317
+ filmstripUrl: rec.filmstripUrl ?? null,
318
+ filmstripFrameCount: rec.filmstripFrameCount ?? 0,
319
+ filmstripColumns: rec.filmstripColumns ?? 0,
320
+ filmstripRows: rec.filmstripRows ?? 0,
321
+ filmstripFrameWidth: rec.filmstripFrameWidth ?? 0,
322
+ filmstripFrameHeight: rec.filmstripFrameHeight ?? 0,
317
323
  sourceAppName: rec.sourceAppName,
318
324
  sourceWindowTitle: rec.sourceWindowTitle,
319
325
  durationMs: rec.durationMs,
@@ -12,7 +12,7 @@ import { queueBuilderMediaCompression } from "../server/lib/builder-media-compre
12
12
  import { dispatchPostFinalizeJob } from "../server/lib/post-finalize-dispatch.js";
13
13
  import {
14
14
  getCurrentOwnerEmail,
15
- getOrganizationDefaultVisibility,
15
+ getDefaultRecordingVisibility,
16
16
  nanoid,
17
17
  ownerEmailMatches,
18
18
  parseSpaceIds,
@@ -215,7 +215,7 @@ export default defineAction({
215
215
  existingRecording?.organizationId ?? args.organizationId,
216
216
  );
217
217
  const defaultVisibility =
218
- await getOrganizationDefaultVisibility(organizationId);
218
+ await getDefaultRecordingVisibility(organizationId);
219
219
 
220
220
  const now = new Date().toISOString();
221
221
  const id = existingRecording?.id ?? nanoid();