@agent-native/core 0.108.0 → 0.109.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 (368) hide show
  1. package/corpus/README.md +2 -2
  2. package/corpus/core/CHANGELOG.md +25 -0
  3. package/corpus/core/package.json +1 -1
  4. package/corpus/core/src/a2a/client.ts +162 -17
  5. package/corpus/core/src/a2a/correlation.ts +50 -0
  6. package/corpus/core/src/a2a/handlers.ts +200 -14
  7. package/corpus/core/src/a2a/index.ts +7 -1
  8. package/corpus/core/src/a2a/invoke.ts +77 -1
  9. package/corpus/core/src/a2a/server.ts +13 -1
  10. package/corpus/core/src/a2a/task-store.ts +146 -6
  11. package/corpus/core/src/a2a/types.ts +32 -0
  12. package/corpus/core/src/action.ts +5 -2
  13. package/corpus/core/src/agent/production-agent.ts +33 -4
  14. package/corpus/core/src/client/AssistantChat.tsx +648 -603
  15. package/corpus/core/src/client/MultiTabAssistantChat.tsx +31 -4
  16. package/corpus/core/src/client/agent-chat-adapter.ts +23 -37
  17. package/corpus/core/src/client/agent-sidebar-state.ts +4 -0
  18. package/corpus/core/src/client/analytics.ts +84 -0
  19. package/corpus/core/src/client/chat/message-components.tsx +94 -22
  20. package/corpus/core/src/client/chat/tool-call-display.tsx +170 -47
  21. package/corpus/core/src/client/chat/use-agent-chat-lifecycle-tracking.ts +74 -0
  22. package/corpus/core/src/client/chat-thread-url.ts +11 -0
  23. package/corpus/core/src/client/error-capture.ts +12 -0
  24. package/corpus/core/src/client/extensions/ExtensionSlot.tsx +37 -1
  25. package/corpus/core/src/client/index.ts +2 -0
  26. package/corpus/core/src/client/route-chunk-recovery.ts +20 -8
  27. package/corpus/core/src/client/session-replay.ts +31 -0
  28. package/corpus/core/src/client/use-run-stuck-detection.ts +27 -1
  29. package/corpus/core/src/db/client.ts +55 -0
  30. package/corpus/core/src/extensions/actions.ts +2 -14
  31. package/corpus/core/src/extensions/store.ts +1 -1
  32. package/corpus/core/src/localization/default-messages.ts +3 -0
  33. package/corpus/core/src/mcp/build-server.ts +2 -2
  34. package/corpus/core/src/observability/traces.ts +172 -19
  35. package/corpus/core/src/progress/actions.ts +4 -2
  36. package/corpus/core/src/provider-api/index.ts +205 -105
  37. package/corpus/core/src/scripts/call-agent.ts +190 -19
  38. package/corpus/core/src/server/agent-chat/action-filters-a2a.ts +112 -25
  39. package/corpus/core/src/server/agent-chat-plugin.ts +136 -4
  40. package/corpus/core/src/styles/agent-conversation.css +7 -0
  41. package/corpus/core/src/templates/default/app/i18n/ar-SA.ts +20 -0
  42. package/corpus/core/src/templates/default/app/i18n/de-DE.ts +20 -0
  43. package/corpus/core/src/templates/default/app/i18n/en-US.ts +20 -0
  44. package/corpus/core/src/templates/default/app/i18n/es-ES.ts +20 -0
  45. package/corpus/core/src/templates/default/app/i18n/fr-FR.ts +21 -0
  46. package/corpus/core/src/templates/default/app/i18n/hi-IN.ts +19 -0
  47. package/corpus/core/src/templates/default/app/i18n/ja-JP.ts +20 -0
  48. package/corpus/core/src/templates/default/app/i18n/ko-KR.ts +19 -0
  49. package/corpus/core/src/templates/default/app/i18n/pt-BR.ts +20 -0
  50. package/corpus/core/src/templates/default/app/i18n/zh-CN.ts +19 -0
  51. package/corpus/core/src/templates/default/app/i18n/zh-TW.ts +19 -0
  52. package/corpus/core/src/templates/workspace-core/.agents/skills/a2a-protocol/SKILL.md +49 -0
  53. package/corpus/core/src/templates/workspace-core/.agents/skills/composable-mini-apps/SKILL.md +5 -0
  54. package/corpus/core/src/templates/workspace-core/.agents/skills/observability/SKILL.md +7 -1
  55. package/corpus/core/src/templates/workspace-core/.agents/skills/performance/SKILL.md +13 -0
  56. package/corpus/core/src/templates/workspace-core/.agents/skills/tracking/SKILL.md +1 -1
  57. package/corpus/templates/analytics/.agents/skills/dashboard-management/SKILL.md +29 -10
  58. package/corpus/templates/analytics/.agents/skills/gong/SKILL.md +54 -23
  59. package/corpus/templates/analytics/.builder/skills/apollo/SKILL.md +19 -41
  60. package/corpus/templates/analytics/.builder/skills/commonroom/SKILL.md +20 -35
  61. package/corpus/templates/analytics/.builder/skills/github/SKILL.md +23 -67
  62. package/corpus/templates/analytics/.builder/skills/pylon/SKILL.md +25 -43
  63. package/corpus/templates/analytics/AGENTS.md +22 -13
  64. package/corpus/templates/analytics/actions/account-deep-dive.ts +1 -0
  65. package/corpus/templates/analytics/actions/configure-gong-native-insights.ts +29 -0
  66. package/corpus/templates/analytics/actions/content-calendar-schema.ts +8 -4
  67. package/corpus/templates/analytics/actions/content-calendar.ts +12 -4
  68. package/corpus/templates/analytics/actions/dashboard-mutation-api.ts +2 -1
  69. package/corpus/templates/analytics/actions/gong-calls.ts +113 -22
  70. package/corpus/templates/analytics/actions/gong-native-insights.ts +166 -0
  71. package/corpus/templates/analytics/actions/hubspot-deals.ts +190 -46
  72. package/corpus/templates/analytics/actions/pylon-issues.ts +123 -0
  73. package/corpus/templates/analytics/actions/query-agent-native-analytics.ts +1 -1
  74. package/corpus/templates/analytics/actions/slack-messages.ts +179 -27
  75. package/corpus/templates/analytics/app/components/dashboard/SqlChart.tsx +48 -152
  76. package/corpus/templates/analytics/app/components/ui/chart.tsx +1 -1
  77. package/corpus/templates/analytics/app/hooks/use-chart-tooltip-flip.ts +21 -6
  78. package/corpus/templates/analytics/app/i18n/zh-TW.ts +13 -0
  79. package/corpus/templates/analytics/app/i18n-data.ts +134 -0
  80. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/PanelEditorDialog.tsx +181 -30
  81. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/SqlChartCard.tsx +6 -0
  82. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/types.ts +4 -3
  83. package/corpus/templates/analytics/changelog/2026-07-17-analytics-now-includes-a-default-alert-for-spikes-in-stuck-agent-chats.md +6 -0
  84. package/corpus/templates/analytics/changelog/2026-07-17-dashboard-editors-can-add-shared-extensions-or-opt-into-per-.md +6 -0
  85. package/corpus/templates/analytics/changelog/2026-07-17-dashboard-extensions-show-a-loading-skeleton.md +6 -0
  86. package/corpus/templates/analytics/changelog/2026-07-17-errors-now-group-repeat-failures-consistently-and-keep-priva.md +6 -0
  87. package/corpus/templates/analytics/changelog/2026-07-17-gong-transcript-reviews-now-batch-bounded-call-windows-for-f.md +6 -0
  88. package/corpus/templates/analytics/changelog/2026-07-17-hubspot-deal-filters-now-run-in-hubspot-before-paging-result.md +6 -0
  89. package/corpus/templates/analytics/changelog/2026-07-17-notion-content-calendars-now-discover-the-matching-database-.md +6 -0
  90. package/corpus/templates/analytics/changelog/2026-07-17-pylon-dashboard-reads-now-use-the-paginated-issue-search-api.md +6 -0
  91. package/corpus/templates/analytics/changelog/2026-07-17-slack-reads-no-longer-join-channels-and-report-pagination-coverage.md +6 -0
  92. package/corpus/templates/analytics/docs/schemas/first-party-analytics.md +23 -14
  93. package/corpus/templates/analytics/docs/schemas/tracked-events.md +5 -1
  94. package/corpus/templates/analytics/server/handlers/notion.ts +14 -3
  95. package/corpus/templates/analytics/server/handlers/slack.ts +182 -29
  96. package/corpus/templates/analytics/server/jobs/analytics-alerts.ts +3 -3
  97. package/corpus/templates/analytics/server/lib/agent-chat-plan-mode.ts +2 -6
  98. package/corpus/templates/analytics/server/lib/analytics-alerts.ts +209 -87
  99. package/corpus/templates/analytics/server/lib/analytics-connector-catalog.ts +6 -2
  100. package/corpus/templates/analytics/server/lib/error-capture.ts +75 -8
  101. package/corpus/templates/analytics/server/lib/gong-native-policy.ts +72 -0
  102. package/corpus/templates/analytics/server/lib/gong.ts +54 -66
  103. package/corpus/templates/analytics/server/lib/hubspot.ts +40 -3
  104. package/corpus/templates/analytics/server/lib/notion.ts +83 -8
  105. package/corpus/templates/analytics/server/lib/real-data-actions.ts +4 -0
  106. package/corpus/templates/analytics/server/lib/slack.ts +242 -90
  107. package/corpus/templates/analytics/server/plugins/agent-chat.ts +3 -3
  108. package/corpus/templates/analytics/server/routes/api/pylon/accounts.get.ts +42 -1
  109. package/corpus/templates/analytics/server/routes/api/pylon/issues.get.ts +71 -1
  110. package/corpus/templates/analytics/server/routes/api/test-connection.post.ts +44 -4
  111. package/corpus/templates/clips/actions/finalize-recording.ts +541 -62
  112. package/corpus/templates/clips/app/components/library/bulk-action-toolbar.tsx +19 -1
  113. package/corpus/templates/clips/app/components/library/create-folder-dialog.tsx +123 -0
  114. package/corpus/templates/clips/app/components/library/library-grid.tsx +42 -4
  115. package/corpus/templates/clips/app/components/library/recording-card.tsx +30 -0
  116. package/corpus/templates/clips/app/components/player/playback-comment-overlay.tsx +17 -2
  117. package/corpus/templates/clips/app/components/player/video-player.tsx +14 -2
  118. package/corpus/templates/clips/app/components/recorder/recorder-engine.ts +2 -2
  119. package/corpus/templates/clips/app/entry.client.tsx +6 -1
  120. package/corpus/templates/clips/app/i18n/ar-SA.ts +12 -1
  121. package/corpus/templates/clips/app/i18n/de-DE.ts +12 -1
  122. package/corpus/templates/clips/app/i18n/en-US.ts +12 -1
  123. package/corpus/templates/clips/app/i18n/es-ES.ts +11 -1
  124. package/corpus/templates/clips/app/i18n/fr-FR.ts +11 -1
  125. package/corpus/templates/clips/app/i18n/hi-IN.ts +10 -1
  126. package/corpus/templates/clips/app/i18n/ja-JP.ts +12 -1
  127. package/corpus/templates/clips/app/i18n/ko-KR.ts +11 -1
  128. package/corpus/templates/clips/app/i18n/pt-BR.ts +11 -1
  129. package/corpus/templates/clips/app/i18n/zh-CN.ts +11 -1
  130. package/corpus/templates/clips/app/i18n/zh-TW.ts +11 -1
  131. package/corpus/templates/clips/app/routes/_app.settings._index.tsx +72 -1
  132. package/corpus/templates/clips/app/routes/r.$recordingId.tsx +1 -1
  133. package/corpus/templates/clips/app/routes/record.tsx +3 -3
  134. package/corpus/templates/clips/app/routes/share.$shareId.tsx +6 -3
  135. package/corpus/templates/clips/changelog/2026-07-17-choose-which-workspace-receives-new-clips-recordings-and-des.md +6 -0
  136. package/corpus/templates/clips/changelog/2026-07-17-clips-now-retries-storage-verification-in-the-background-ins.md +6 -0
  137. package/corpus/templates/clips/changelog/2026-07-17-create-folders-directly-from-the-move-menu.md +6 -0
  138. package/corpus/templates/clips/changelog/2026-07-17-failed-desktop-uploads-now-offer-a-direct-way-to-reopen-clip.md +6 -0
  139. package/corpus/templates/clips/changelog/2026-07-17-fixed-broken-thumbnail-icons-appearing-over-playable-clip-em.md +6 -0
  140. package/corpus/templates/clips/changelog/2026-07-17-playback-comments-stay-readable-at-faster-playback-speeds.md +6 -0
  141. package/corpus/templates/clips/chrome-extension/src/finalize-recovery.ts +3 -0
  142. package/corpus/templates/clips/chrome-extension/src/offscreen.ts +3 -3
  143. package/corpus/templates/clips/desktop/src/app.tsx +19 -7
  144. package/corpus/templates/clips/desktop/src/lib/recorder.ts +111 -13
  145. package/corpus/templates/clips/desktop/src/lib/upload-verification.ts +16 -7
  146. package/corpus/templates/clips/desktop/src-tauri/src/lib.rs +2 -4
  147. package/corpus/templates/clips/desktop/src-tauri/src/native_screen/live_upload.rs +32 -15
  148. package/corpus/templates/clips/desktop/src-tauri/src/native_screen.rs +45 -18
  149. package/corpus/templates/clips/server/jobs/media-verification.ts +130 -0
  150. package/corpus/templates/clips/server/lib/builder-media-compression.ts +15 -0
  151. package/corpus/templates/clips/server/lib/media-verification-state.ts +38 -0
  152. package/corpus/templates/clips/server/lib/player-thumbnail-url.ts +31 -0
  153. package/corpus/templates/clips/server/lib/post-finalize-dispatch.ts +10 -3
  154. package/corpus/templates/clips/server/plugins/auth.ts +5 -5
  155. package/corpus/templates/clips/server/plugins/calendar-jobs.ts +2 -0
  156. package/corpus/templates/clips/server/routes/api/_agent-native-background/post-finalize-worker.post.ts +12 -2
  157. package/corpus/templates/clips/server/routes/api/public-recording.get.ts +6 -1
  158. package/corpus/templates/clips/server/routes/api/thumbnail/[recordingId].get.ts +340 -0
  159. package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/abort.post.ts +24 -7
  160. package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/chunk.post.ts +91 -7
  161. package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/status.get.ts +14 -0
  162. package/corpus/templates/clips/shared/finalize-recovery.ts +61 -2
  163. package/corpus/templates/clips/shared/share-meta.ts +1 -1
  164. package/corpus/templates/content/app/entry.client.tsx +6 -1
  165. package/corpus/templates/dispatch/AGENTS.md +5 -0
  166. package/corpus/templates/dispatch/changelog/2026-07-17-apps-page-links-to-other-available-apps.md +6 -0
  167. package/corpus/templates/dispatch/changelog/2026-07-17-curated-template-remixes.md +6 -0
  168. package/corpus/templates/mail/app/entry.client.tsx +6 -1
  169. package/corpus/templates/slides/.agents/skills/slide-images/SKILL.md +11 -7
  170. package/corpus/templates/slides/.env.example +0 -1
  171. package/corpus/templates/slides/AGENTS.md +4 -0
  172. package/corpus/templates/slides/DEVELOPING.md +2 -2
  173. package/corpus/templates/slides/actions/index-design-system-with-builder.ts +1 -1
  174. package/corpus/templates/slides/actions/logo-config.ts +6 -7
  175. package/corpus/templates/slides/actions/search-images.ts +11 -39
  176. package/corpus/templates/slides/actions/search-logos.ts +5 -52
  177. package/corpus/templates/slides/app/components/editor/ImageSearchPanel.tsx +1 -1
  178. package/corpus/templates/slides/app/components/editor/LogoSearchPanel.tsx +16 -26
  179. package/corpus/templates/slides/app/entry.client.tsx +6 -1
  180. package/corpus/templates/slides/server/lib/media-search-providers.ts +216 -0
  181. package/corpus/templates/slides/server/register-secrets.ts +30 -0
  182. package/dist/a2a/client.d.ts +47 -1
  183. package/dist/a2a/client.d.ts.map +1 -1
  184. package/dist/a2a/client.js +94 -12
  185. package/dist/a2a/client.js.map +1 -1
  186. package/dist/a2a/correlation.d.ts +10 -0
  187. package/dist/a2a/correlation.d.ts.map +1 -0
  188. package/dist/a2a/correlation.js +40 -0
  189. package/dist/a2a/correlation.js.map +1 -0
  190. package/dist/a2a/handlers.d.ts.map +1 -1
  191. package/dist/a2a/handlers.js +147 -15
  192. package/dist/a2a/handlers.js.map +1 -1
  193. package/dist/a2a/index.d.ts +4 -4
  194. package/dist/a2a/index.d.ts.map +1 -1
  195. package/dist/a2a/index.js +2 -2
  196. package/dist/a2a/index.js.map +1 -1
  197. package/dist/a2a/invoke.d.ts +29 -2
  198. package/dist/a2a/invoke.d.ts.map +1 -1
  199. package/dist/a2a/invoke.js +33 -1
  200. package/dist/a2a/invoke.js.map +1 -1
  201. package/dist/a2a/server.d.ts.map +1 -1
  202. package/dist/a2a/server.js +14 -1
  203. package/dist/a2a/server.js.map +1 -1
  204. package/dist/a2a/task-store.d.ts +12 -1
  205. package/dist/a2a/task-store.d.ts.map +1 -1
  206. package/dist/a2a/task-store.js +96 -6
  207. package/dist/a2a/task-store.js.map +1 -1
  208. package/dist/a2a/types.d.ts +27 -0
  209. package/dist/a2a/types.d.ts.map +1 -1
  210. package/dist/a2a/types.js.map +1 -1
  211. package/dist/action.d.ts +5 -2
  212. package/dist/action.d.ts.map +1 -1
  213. package/dist/action.js.map +1 -1
  214. package/dist/agent/production-agent.d.ts +14 -0
  215. package/dist/agent/production-agent.d.ts.map +1 -1
  216. package/dist/agent/production-agent.js +19 -3
  217. package/dist/agent/production-agent.js.map +1 -1
  218. package/dist/client/AssistantChat.d.ts +14 -1
  219. package/dist/client/AssistantChat.d.ts.map +1 -1
  220. package/dist/client/AssistantChat.js +158 -136
  221. package/dist/client/AssistantChat.js.map +1 -1
  222. package/dist/client/MultiTabAssistantChat.d.ts.map +1 -1
  223. package/dist/client/MultiTabAssistantChat.js +27 -4
  224. package/dist/client/MultiTabAssistantChat.js.map +1 -1
  225. package/dist/client/agent-chat-adapter.d.ts.map +1 -1
  226. package/dist/client/agent-chat-adapter.js +21 -39
  227. package/dist/client/agent-chat-adapter.js.map +1 -1
  228. package/dist/client/agent-sidebar-state.d.ts +1 -0
  229. package/dist/client/agent-sidebar-state.d.ts.map +1 -1
  230. package/dist/client/agent-sidebar-state.js +4 -0
  231. package/dist/client/agent-sidebar-state.js.map +1 -1
  232. package/dist/client/analytics.d.ts +14 -0
  233. package/dist/client/analytics.d.ts.map +1 -1
  234. package/dist/client/analytics.js +67 -0
  235. package/dist/client/analytics.js.map +1 -1
  236. package/dist/client/chat/message-components.d.ts +8 -0
  237. package/dist/client/chat/message-components.d.ts.map +1 -1
  238. package/dist/client/chat/message-components.js +62 -19
  239. package/dist/client/chat/message-components.js.map +1 -1
  240. package/dist/client/chat/tool-call-display.d.ts +6 -0
  241. package/dist/client/chat/tool-call-display.d.ts.map +1 -1
  242. package/dist/client/chat/tool-call-display.js +83 -14
  243. package/dist/client/chat/tool-call-display.js.map +1 -1
  244. package/dist/client/chat/use-agent-chat-lifecycle-tracking.d.ts +8 -0
  245. package/dist/client/chat/use-agent-chat-lifecycle-tracking.d.ts.map +1 -0
  246. package/dist/client/chat/use-agent-chat-lifecycle-tracking.js +51 -0
  247. package/dist/client/chat/use-agent-chat-lifecycle-tracking.js.map +1 -0
  248. package/dist/client/chat-thread-url.d.ts +2 -0
  249. package/dist/client/chat-thread-url.d.ts.map +1 -0
  250. package/dist/client/chat-thread-url.js +12 -0
  251. package/dist/client/chat-thread-url.js.map +1 -0
  252. package/dist/client/error-capture.d.ts.map +1 -1
  253. package/dist/client/error-capture.js +7 -0
  254. package/dist/client/error-capture.js.map +1 -1
  255. package/dist/client/extensions/ExtensionSlot.d.ts +3 -1
  256. package/dist/client/extensions/ExtensionSlot.d.ts.map +1 -1
  257. package/dist/client/extensions/ExtensionSlot.js +28 -3
  258. package/dist/client/extensions/ExtensionSlot.js.map +1 -1
  259. package/dist/client/index.d.ts +1 -1
  260. package/dist/client/index.d.ts.map +1 -1
  261. package/dist/client/index.js +1 -1
  262. package/dist/client/index.js.map +1 -1
  263. package/dist/client/route-chunk-recovery.d.ts.map +1 -1
  264. package/dist/client/route-chunk-recovery.js +14 -9
  265. package/dist/client/route-chunk-recovery.js.map +1 -1
  266. package/dist/client/session-replay.d.ts +15 -0
  267. package/dist/client/session-replay.d.ts.map +1 -1
  268. package/dist/client/session-replay.js +20 -0
  269. package/dist/client/session-replay.js.map +1 -1
  270. package/dist/client/use-run-stuck-detection.d.ts.map +1 -1
  271. package/dist/client/use-run-stuck-detection.js +25 -1
  272. package/dist/client/use-run-stuck-detection.js.map +1 -1
  273. package/dist/db/client.d.ts +7 -0
  274. package/dist/db/client.d.ts.map +1 -1
  275. package/dist/db/client.js +33 -0
  276. package/dist/db/client.js.map +1 -1
  277. package/dist/extensions/actions.d.ts.map +1 -1
  278. package/dist/extensions/actions.js +2 -10
  279. package/dist/extensions/actions.js.map +1 -1
  280. package/dist/extensions/store.js +1 -1
  281. package/dist/extensions/store.js.map +1 -1
  282. package/dist/localization/default-messages.d.ts +3 -0
  283. package/dist/localization/default-messages.d.ts.map +1 -1
  284. package/dist/localization/default-messages.js +3 -0
  285. package/dist/localization/default-messages.js.map +1 -1
  286. package/dist/mcp/build-server.d.ts +2 -0
  287. package/dist/mcp/build-server.d.ts.map +1 -1
  288. package/dist/mcp/build-server.js +2 -2
  289. package/dist/mcp/build-server.js.map +1 -1
  290. package/dist/notifications/routes.d.ts +2 -2
  291. package/dist/observability/routes.d.ts +5 -5
  292. package/dist/observability/traces.d.ts +9 -0
  293. package/dist/observability/traces.d.ts.map +1 -1
  294. package/dist/observability/traces.js +137 -19
  295. package/dist/observability/traces.js.map +1 -1
  296. package/dist/progress/actions.d.ts.map +1 -1
  297. package/dist/progress/actions.js +3 -2
  298. package/dist/progress/actions.js.map +1 -1
  299. package/dist/progress/routes.d.ts +1 -1
  300. package/dist/provider-api/corpus-jobs.d.ts +2 -2
  301. package/dist/provider-api/index.d.ts.map +1 -1
  302. package/dist/provider-api/index.js +91 -8
  303. package/dist/provider-api/index.js.map +1 -1
  304. package/dist/scripts/call-agent.d.ts.map +1 -1
  305. package/dist/scripts/call-agent.js +140 -17
  306. package/dist/scripts/call-agent.js.map +1 -1
  307. package/dist/secrets/routes.d.ts +9 -9
  308. package/dist/server/agent-chat/action-filters-a2a.d.ts +24 -2
  309. package/dist/server/agent-chat/action-filters-a2a.d.ts.map +1 -1
  310. package/dist/server/agent-chat/action-filters-a2a.js +62 -7
  311. package/dist/server/agent-chat/action-filters-a2a.js.map +1 -1
  312. package/dist/server/agent-chat-plugin.d.ts.map +1 -1
  313. package/dist/server/agent-chat-plugin.js +118 -5
  314. package/dist/server/agent-chat-plugin.js.map +1 -1
  315. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  316. package/dist/server/transcribe-voice.d.ts +1 -1
  317. package/dist/styles/agent-conversation.css +7 -0
  318. package/dist/templates/default/app/i18n/ar-SA.ts +20 -0
  319. package/dist/templates/default/app/i18n/de-DE.ts +20 -0
  320. package/dist/templates/default/app/i18n/en-US.ts +20 -0
  321. package/dist/templates/default/app/i18n/es-ES.ts +20 -0
  322. package/dist/templates/default/app/i18n/fr-FR.ts +21 -0
  323. package/dist/templates/default/app/i18n/hi-IN.ts +19 -0
  324. package/dist/templates/default/app/i18n/ja-JP.ts +20 -0
  325. package/dist/templates/default/app/i18n/ko-KR.ts +19 -0
  326. package/dist/templates/default/app/i18n/pt-BR.ts +20 -0
  327. package/dist/templates/default/app/i18n/zh-CN.ts +19 -0
  328. package/dist/templates/default/app/i18n/zh-TW.ts +19 -0
  329. package/dist/templates/workspace-core/.agents/skills/a2a-protocol/SKILL.md +49 -0
  330. package/dist/templates/workspace-core/.agents/skills/composable-mini-apps/SKILL.md +5 -0
  331. package/dist/templates/workspace-core/.agents/skills/observability/SKILL.md +7 -1
  332. package/dist/templates/workspace-core/.agents/skills/performance/SKILL.md +13 -0
  333. package/dist/templates/workspace-core/.agents/skills/tracking/SKILL.md +1 -1
  334. package/package.json +1 -1
  335. package/src/templates/default/app/i18n/ar-SA.ts +20 -0
  336. package/src/templates/default/app/i18n/de-DE.ts +20 -0
  337. package/src/templates/default/app/i18n/en-US.ts +20 -0
  338. package/src/templates/default/app/i18n/es-ES.ts +20 -0
  339. package/src/templates/default/app/i18n/fr-FR.ts +21 -0
  340. package/src/templates/default/app/i18n/hi-IN.ts +19 -0
  341. package/src/templates/default/app/i18n/ja-JP.ts +20 -0
  342. package/src/templates/default/app/i18n/ko-KR.ts +19 -0
  343. package/src/templates/default/app/i18n/pt-BR.ts +20 -0
  344. package/src/templates/default/app/i18n/zh-CN.ts +19 -0
  345. package/src/templates/default/app/i18n/zh-TW.ts +19 -0
  346. package/src/templates/workspace-core/.agents/skills/a2a-protocol/SKILL.md +49 -0
  347. package/src/templates/workspace-core/.agents/skills/composable-mini-apps/SKILL.md +5 -0
  348. package/src/templates/workspace-core/.agents/skills/observability/SKILL.md +7 -1
  349. package/src/templates/workspace-core/.agents/skills/performance/SKILL.md +13 -0
  350. package/src/templates/workspace-core/.agents/skills/tracking/SKILL.md +1 -1
  351. package/corpus/templates/analytics/server/handlers/github.ts +0 -200
  352. package/corpus/templates/analytics/server/handlers/pylon.ts +0 -43
  353. package/corpus/templates/analytics/server/lib/apollo.ts +0 -167
  354. package/corpus/templates/analytics/server/lib/commonroom.ts +0 -149
  355. package/corpus/templates/analytics/server/lib/github.ts +0 -660
  356. package/corpus/templates/analytics/server/lib/mixpanel.ts +0 -156
  357. package/corpus/templates/analytics/server/lib/posthog.ts +0 -194
  358. package/corpus/templates/analytics/server/lib/pylon.ts +0 -109
  359. package/corpus/templates/analytics/server/routes/api/github/graphql.post.ts +0 -1
  360. package/corpus/templates/analytics/server/routes/api/github/issue.get.ts +0 -1
  361. package/corpus/templates/analytics/server/routes/api/github/org-prs.get.ts +0 -1
  362. package/corpus/templates/analytics/server/routes/api/github/pr.get.ts +0 -1
  363. package/corpus/templates/analytics/server/routes/api/github/prs.get.ts +0 -1
  364. package/corpus/templates/analytics/server/routes/api/github/search.get.ts +0 -1
  365. package/corpus/templates/slides/actions/fetch-logos.ts +0 -90
  366. package/corpus/templates/slides/actions/image-search.ts +0 -64
  367. package/corpus/templates/slides/actions/import-github.ts +0 -182
  368. package/corpus/templates/slides/actions/logo-lookup.ts +0 -113
@@ -2,6 +2,7 @@ export interface FinalizeReceipt {
2
2
  ok?: unknown;
3
3
  finalized?: unknown;
4
4
  status?: unknown;
5
+ verificationPending?: unknown;
5
6
  sourceSizeBytes?: unknown;
6
7
  durationMs?: unknown;
7
8
  }
@@ -11,16 +12,22 @@ export interface LocalRecordingProof {
11
12
  durationMs: number;
12
13
  }
13
14
 
15
+ export type FinalizeReceiptStatus = "processing" | "ready";
16
+
14
17
  export function verifyFinalizeReceipt(
15
18
  receipt: FinalizeReceipt | null,
16
19
  local: LocalRecordingProof,
17
- ): void {
18
- if (
19
- !receipt ||
20
- receipt.ok !== true ||
21
- receipt.finalized !== true ||
22
- receipt.status !== "ready"
23
- ) {
20
+ ): FinalizeReceiptStatus {
21
+ const ready =
22
+ receipt?.ok === true &&
23
+ receipt.finalized === true &&
24
+ receipt.status === "ready";
25
+ const processing =
26
+ receipt?.ok === true &&
27
+ receipt.finalized === false &&
28
+ receipt.status === "processing" &&
29
+ receipt.verificationPending === true;
30
+ if (!ready && !processing) {
24
31
  throw new Error(
25
32
  "Clip may be incomplete. Finalization was not confirmed; the local backup was kept.",
26
33
  );
@@ -48,4 +55,6 @@ export function verifyFinalizeReceipt(
48
55
  "Clip may be incomplete. The uploaded duration did not match the local recording; the local backup was kept.",
49
56
  );
50
57
  }
58
+
59
+ return ready ? "ready" : "processing";
51
60
  }
@@ -249,8 +249,7 @@ pub fn run() {
249
249
  use tauri_plugin_deep_link::DeepLinkExt;
250
250
  let dl_handle = app.handle().clone();
251
251
  app.deep_link().on_open_url(move |event| {
252
- let urls: Vec<String> =
253
- event.urls().iter().map(|u| u.to_string()).collect();
252
+ let urls: Vec<String> = event.urls().iter().map(|u| u.to_string()).collect();
254
253
  dlog!("[clips-tray] deep link opened: {:?}", urls);
255
254
  present_popover(&dl_handle);
256
255
  let _ = dl_handle.emit("clips:deep-link", urls);
@@ -263,8 +262,7 @@ pub fn run() {
263
262
  // arguments rather than an on_open_url event. Consume it here so
264
263
  // the popover appears and the browser does not hit its fallback.
265
264
  if let Ok(Some(urls)) = app.deep_link().get_current() {
266
- let url_strings: Vec<String> =
267
- urls.iter().map(|u| u.to_string()).collect();
265
+ let url_strings: Vec<String> = urls.iter().map(|u| u.to_string()).collect();
268
266
  dlog!("[clips-tray] deep link cold start: {:?}", url_strings);
269
267
  present_popover(app.handle());
270
268
  let _ = app.handle().emit("clips:deep-link", url_strings);
@@ -36,8 +36,12 @@ pub(super) struct LiveUploadCtrl {
36
36
 
37
37
  pub(super) struct LiveUpload {
38
38
  pub(super) ctrl: Arc<LiveUploadCtrl>,
39
- /// Resolves with the total bytes uploaded, or an error string.
40
- pub(super) result_rx: tokio::sync::oneshot::Receiver<Result<u64, String>>,
39
+ pub(super) result_rx: tokio::sync::oneshot::Receiver<Result<LiveUploadResult, String>>,
40
+ }
41
+
42
+ pub(super) struct LiveUploadResult {
43
+ pub(super) bytes: u64,
44
+ pub(super) verification_pending: bool,
41
45
  }
42
46
 
43
47
  struct LiveUploadParams {
@@ -163,7 +167,7 @@ async fn send_live_upload_post_with_retry(
163
167
  duration_ms: Option<u128>,
164
168
  expected_source_bytes: Option<u64>,
165
169
  bytes: &[u8],
166
- ) -> Result<(), String> {
170
+ ) -> Result<bool, String> {
167
171
  let rec = &params.recording_id;
168
172
  let mut attempt: u32 = 0;
169
173
  loop {
@@ -198,7 +202,7 @@ async fn send_live_upload_post_with_retry(
198
202
  )
199
203
  .await;
200
204
  let err = match result {
201
- Ok(()) => return Ok(()),
205
+ Ok(verification_pending) => return Ok(verification_pending),
202
206
  Err(err) => err,
203
207
  };
204
208
  if attempt >= LIVE_UPLOAD_CHUNK_ATTEMPTS {
@@ -219,7 +223,7 @@ async fn live_upload_loop(
219
223
  app: AppHandle,
220
224
  ctrl: Arc<LiveUploadCtrl>,
221
225
  params: LiveUploadParams,
222
- ) -> Result<u64, String> {
226
+ ) -> Result<LiveUploadResult, String> {
223
227
  let rec = params.recording_id.clone();
224
228
  eprintln!(
225
229
  "[live-upload] loop started for {rec}: file={} chunk={} bytes",
@@ -290,6 +294,7 @@ async fn live_upload_loop(
290
294
  // sentinel makes GCS silently commit only the aligned prefix, and
291
295
  // the session close then fails forever with 308 (incomplete).
292
296
  let mut final_sent = false;
297
+ let mut verification_pending = false;
293
298
  while final_len > offset {
294
299
  let take = chunk.min(final_len - offset);
295
300
  let is_last = offset + take >= final_len;
@@ -302,7 +307,7 @@ async fn live_upload_loop(
302
307
  eprintln!(
303
308
  "[live-upload] {rec}: sending tail chunk #{index} offset={offset} size={take} final={is_last}"
304
309
  );
305
- if let Err(e) = send_live_upload_post_with_retry(
310
+ let receipt = send_live_upload_post_with_retry(
306
311
  &client,
307
312
  &ctrl,
308
313
  &params,
@@ -313,10 +318,16 @@ async fn live_upload_loop(
313
318
  is_last.then_some(final_len),
314
319
  &bytes,
315
320
  )
316
- .await
317
- {
318
- eprintln!("[live-upload] {rec}: tail chunk #{index} failed: {e}");
319
- return Err(e);
321
+ .await;
322
+ let pending = match receipt {
323
+ Ok(pending) => pending,
324
+ Err(e) => {
325
+ eprintln!("[live-upload] {rec}: tail chunk #{index} failed: {e}");
326
+ return Err(e);
327
+ }
328
+ };
329
+ if is_last {
330
+ verification_pending = pending;
320
331
  }
321
332
  offset += take;
322
333
  index += 1;
@@ -331,7 +342,7 @@ async fn live_upload_loop(
331
342
  "[live-upload] {rec}: sending final post #{index} (total={}, duration_ms={duration_ms})",
332
343
  index + 1
333
344
  );
334
- if let Err(e) = send_live_upload_post_with_retry(
345
+ verification_pending = match send_live_upload_post_with_retry(
335
346
  &client,
336
347
  &ctrl,
337
348
  &params,
@@ -344,13 +355,19 @@ async fn live_upload_loop(
344
355
  )
345
356
  .await
346
357
  {
347
- eprintln!("[live-upload] {rec}: final post failed: {e}");
348
- return Err(e);
349
- }
358
+ Ok(pending) => pending,
359
+ Err(e) => {
360
+ eprintln!("[live-upload] {rec}: final post failed: {e}");
361
+ return Err(e);
362
+ }
363
+ };
350
364
  }
351
365
  emit_native_upload_progress(&app, "opening", "Uploading clip", None, Some(1.0));
352
366
  eprintln!("[live-upload] {rec}: done — {index} post(s), {final_len} bytes total");
353
- return Ok(final_len);
367
+ return Ok(LiveUploadResult {
368
+ bytes: final_len,
369
+ verification_pending,
370
+ });
354
371
  }
355
372
 
356
373
  if !wait_logged {
@@ -692,6 +692,7 @@ pub struct NativeFullscreenUploadResult {
692
692
  width: Option<u32>,
693
693
  height: Option<u32>,
694
694
  bytes: u64,
695
+ verification_pending: bool,
695
696
  }
696
697
 
697
698
  #[derive(Serialize, Clone)]
@@ -1388,20 +1389,23 @@ pub async fn native_fullscreen_recording_stop_and_upload(
1388
1389
  eprintln!(
1389
1390
  "[live-upload] stop: finalize result for {recording_id}: {}",
1390
1391
  match &result {
1391
- Ok(bytes) => format!("ok ({bytes} bytes)"),
1392
+ Ok(upload) => format!("ok ({} bytes)", upload.bytes),
1392
1393
  Err(e) => format!("error: {e}"),
1393
1394
  }
1394
1395
  );
1395
1396
  return match result {
1396
- Ok(bytes) => {
1397
- clear_saved_recording_after_success(&app, &saved);
1397
+ Ok(upload) => {
1398
+ if !upload.verification_pending {
1399
+ clear_saved_recording_after_success(&app, &saved);
1400
+ }
1398
1401
  emit_native_upload_finished(&app, &server_url, &recording_id, true, None, None);
1399
1402
  Ok(NativeFullscreenUploadResult {
1400
1403
  recording_id,
1401
1404
  duration_ms: verified_duration_ms,
1402
1405
  width: session.width,
1403
1406
  height: session.height,
1404
- bytes,
1407
+ bytes: upload.bytes,
1408
+ verification_pending: upload.verification_pending,
1405
1409
  })
1406
1410
  }
1407
1411
  Err(err) => {
@@ -1481,7 +1485,9 @@ pub async fn native_fullscreen_recording_stop_and_upload(
1481
1485
 
1482
1486
  match result {
1483
1487
  Ok(result) => {
1484
- clear_saved_recording_after_success(&app, &saved);
1488
+ if !result.verification_pending {
1489
+ clear_saved_recording_after_success(&app, &saved);
1490
+ }
1485
1491
  emit_native_upload_finished(&app, &server_url, &recording_id, true, None, None);
1486
1492
  Ok(result)
1487
1493
  }
@@ -2599,7 +2605,9 @@ pub async fn native_fullscreen_recording_retry_upload(
2599
2605
 
2600
2606
  match result {
2601
2607
  Ok(result) => {
2602
- clear_saved_recording_after_success(&app, &saved);
2608
+ if !result.verification_pending {
2609
+ clear_saved_recording_after_success(&app, &saved);
2610
+ }
2603
2611
  Ok(result)
2604
2612
  }
2605
2613
  Err(err) => {
@@ -4003,6 +4011,7 @@ async fn upload_prepared_recording_file(
4003
4011
  .map_err(|e| format!("upload client failed: {e}"))?;
4004
4012
  let mut file =
4005
4013
  File::open(&prepared.path).map_err(|e| format!("native recording open failed: {e}"))?;
4014
+ let verification_pending;
4006
4015
 
4007
4016
  if upload_mode == NativeUploadMode::Streaming {
4008
4017
  // Resumable providers require every non-final body to be aligned. The
@@ -4055,7 +4064,7 @@ async fn upload_prepared_recording_file(
4055
4064
  None,
4056
4065
  Some(streaming_full_chunks as f32 / total_posts as f32),
4057
4066
  );
4058
- send_upload_post(
4067
+ verification_pending = send_upload_post(
4059
4068
  &client,
4060
4069
  &server_url,
4061
4070
  &recording_id,
@@ -4123,7 +4132,7 @@ async fn upload_prepared_recording_file(
4123
4132
  None,
4124
4133
  Some(total_chunks as f32 / total_posts as f32),
4125
4134
  );
4126
- send_upload_post(
4135
+ verification_pending = send_upload_post(
4127
4136
  &client,
4128
4137
  &server_url,
4129
4138
  &recording_id,
@@ -4153,6 +4162,7 @@ async fn upload_prepared_recording_file(
4153
4162
  width,
4154
4163
  height,
4155
4164
  bytes: total_bytes,
4165
+ verification_pending,
4156
4166
  })
4157
4167
  }
4158
4168
 
@@ -4270,7 +4280,7 @@ async fn send_upload_post(
4270
4280
  locally_transcoded: bool,
4271
4281
  expected_source_bytes: Option<u64>,
4272
4282
  body: Vec<u8>,
4273
- ) -> Result<(), String> {
4283
+ ) -> Result<bool, String> {
4274
4284
  let body_len = body.len();
4275
4285
  let url = upload_url(
4276
4286
  server_url,
@@ -4319,7 +4329,7 @@ async fn send_upload_post(
4319
4329
  body.chars().take(400).collect::<String>()
4320
4330
  ));
4321
4331
  }
4322
- if is_final {
4332
+ let verification_pending = if is_final {
4323
4333
  verify_native_finalize_receipt(
4324
4334
  &body,
4325
4335
  expected_source_bytes.ok_or_else(|| {
@@ -4328,13 +4338,15 @@ async fn send_upload_post(
4328
4338
  duration_ms.ok_or_else(|| {
4329
4339
  "Clip may be incomplete: final upload had no local duration".to_string()
4330
4340
  })?,
4331
- )?;
4332
- }
4341
+ )?
4342
+ } else {
4343
+ false
4344
+ };
4333
4345
  eprintln!(
4334
4346
  "[clips-tray] native upload post ok recording={recording_id} mode={} index={index}/{total} final={is_final}",
4335
4347
  upload_mode.label()
4336
4348
  );
4337
- Ok(())
4349
+ Ok(verification_pending)
4338
4350
  }
4339
4351
 
4340
4352
  #[derive(Deserialize)]
@@ -4343,6 +4355,7 @@ struct NativeFinalizeReceipt {
4343
4355
  ok: bool,
4344
4356
  finalized: bool,
4345
4357
  status: Option<String>,
4358
+ verification_pending: Option<bool>,
4346
4359
  source_size_bytes: Option<u64>,
4347
4360
  duration_ms: Option<u128>,
4348
4361
  }
@@ -4351,12 +4364,17 @@ fn verify_native_finalize_receipt(
4351
4364
  body: &str,
4352
4365
  expected_source_bytes: u64,
4353
4366
  expected_duration_ms: u128,
4354
- ) -> Result<(), String> {
4367
+ ) -> Result<bool, String> {
4355
4368
  let receipt: NativeFinalizeReceipt = serde_json::from_str(body).map_err(|_| {
4356
4369
  "Clip may be incomplete. The final upload receipt was unreadable; the local backup was kept."
4357
4370
  .to_string()
4358
4371
  })?;
4359
- if !receipt.ok || !receipt.finalized || receipt.status.as_deref() != Some("ready") {
4372
+ let ready = receipt.ok && receipt.finalized && receipt.status.as_deref() == Some("ready");
4373
+ let processing = receipt.ok
4374
+ && !receipt.finalized
4375
+ && receipt.status.as_deref() == Some("processing")
4376
+ && receipt.verification_pending == Some(true);
4377
+ if !ready && !processing {
4360
4378
  return Err(
4361
4379
  "Clip may be incomplete. Finalization was not confirmed; the local backup was kept."
4362
4380
  .to_string(),
@@ -4374,7 +4392,7 @@ fn verify_native_finalize_receipt(
4374
4392
  .to_string(),
4375
4393
  );
4376
4394
  }
4377
- Ok(())
4395
+ Ok(processing)
4378
4396
  }
4379
4397
 
4380
4398
  fn media_durations_materially_match(expected_ms: u128, actual_ms: u128) -> bool {
@@ -4399,9 +4417,18 @@ mod native_finalize_receipt_tests {
4399
4417
  }
4400
4418
 
4401
4419
  #[test]
4402
- fn accepts_only_matching_ready_receipts() {
4420
+ fn accepts_matching_ready_and_pending_verification_receipts() {
4403
4421
  let ready = r#"{"ok":true,"finalized":true,"status":"ready","sourceSizeBytes":581614005,"durationMs":1593259}"#;
4404
- assert!(verify_native_finalize_receipt(ready, 581_614_005, 1_592_773).is_ok());
4422
+ assert_eq!(
4423
+ verify_native_finalize_receipt(ready, 581_614_005, 1_592_773),
4424
+ Ok(false)
4425
+ );
4426
+
4427
+ let processing = r#"{"ok":true,"finalized":false,"status":"processing","verificationPending":true,"sourceSizeBytes":581614005,"durationMs":1593259}"#;
4428
+ assert_eq!(
4429
+ verify_native_finalize_receipt(processing, 581_614_005, 1_592_773),
4430
+ Ok(true)
4431
+ );
4405
4432
 
4406
4433
  let short = r#"{"ok":true,"finalized":true,"status":"ready","sourceSizeBytes":581614005,"durationMs":483000}"#;
4407
4434
  assert!(
@@ -0,0 +1,130 @@
1
+ import { getDbExec } from "@agent-native/core/db";
2
+ import { runWithRequestContext } from "@agent-native/core/server";
3
+ import { and, eq, isNull } from "drizzle-orm";
4
+
5
+ import finalizeRecording from "../../actions/finalize-recording.js";
6
+ import { getDb, schema } from "../db/index.js";
7
+ import {
8
+ MEDIA_VERIFICATION_STATE_PREFIX,
9
+ parseMediaVerificationMarker,
10
+ } from "../lib/media-verification-state.js";
11
+ import { ownerEmailMatches } from "../lib/recordings.js";
12
+
13
+ const SWEEP_INTERVAL_MS = 60_000;
14
+ const DISPATCH_FALLBACK_GRACE_MS = 30_000;
15
+ const MAX_ATTEMPTS = 10;
16
+ let skippingLogged = false;
17
+
18
+ export async function runMediaVerificationSweepOnce(): Promise<void> {
19
+ const { rows } = await getDbExec().execute({
20
+ sql: `SELECT session_id, key, value FROM application_state WHERE key LIKE ?`,
21
+ args: [`${MEDIA_VERIFICATION_STATE_PREFIX}%`],
22
+ });
23
+ const now = Date.now();
24
+
25
+ for (const row of rows as Array<{
26
+ session_id?: unknown;
27
+ key?: unknown;
28
+ value?: unknown;
29
+ }>) {
30
+ const sessionId =
31
+ typeof row.session_id === "string" ? row.session_id.trim() : "";
32
+ const key = typeof row.key === "string" ? row.key : "";
33
+ const recordingId = key.startsWith(MEDIA_VERIFICATION_STATE_PREFIX)
34
+ ? key.slice(MEDIA_VERIFICATION_STATE_PREFIX.length)
35
+ : "";
36
+ const rawValue = typeof row.value === "string" ? row.value : "";
37
+ let rawState: unknown;
38
+ try {
39
+ rawState = JSON.parse(rawValue);
40
+ } catch {
41
+ continue;
42
+ }
43
+ const marker = parseMediaVerificationMarker(rawState);
44
+ if (
45
+ !sessionId ||
46
+ !recordingId ||
47
+ !marker ||
48
+ marker.recordingId !== recordingId
49
+ ) {
50
+ continue;
51
+ }
52
+
53
+ const nextAttemptAt = Date.parse(marker.nextAttemptAt);
54
+ const leaseUntil = marker.leaseUntil
55
+ ? Date.parse(marker.leaseUntil)
56
+ : Number.NEGATIVE_INFINITY;
57
+ const due =
58
+ marker.status === "pending"
59
+ ? now >= nextAttemptAt + DISPATCH_FALLBACK_GRACE_MS
60
+ : now >= leaseUntil;
61
+ if (marker.completedAttempts >= MAX_ATTEMPTS || !due) {
62
+ continue;
63
+ }
64
+
65
+ try {
66
+ const [recording] = await getDb()
67
+ .select({
68
+ ownerEmail: schema.recordings.ownerEmail,
69
+ orgId: schema.recordings.orgId,
70
+ })
71
+ .from(schema.recordings)
72
+ .where(
73
+ and(
74
+ eq(schema.recordings.id, recordingId),
75
+ ownerEmailMatches(schema.recordings.ownerEmail, sessionId),
76
+ eq(schema.recordings.status, "processing"),
77
+ isNull(schema.recordings.trashedAt),
78
+ ),
79
+ )
80
+ .limit(1);
81
+ if (!recording) continue;
82
+
83
+ await runWithRequestContext(
84
+ {
85
+ userEmail: recording.ownerEmail,
86
+ orgId: recording.orgId ?? undefined,
87
+ },
88
+ async () => {
89
+ await finalizeRecording.run({
90
+ id: recordingId,
91
+ mediaVerificationRetryAttempt: Math.min(
92
+ MAX_ATTEMPTS,
93
+ marker.completedAttempts + 1,
94
+ ),
95
+ });
96
+ },
97
+ );
98
+ } catch (err) {
99
+ console.warn("[media-verification] sweep item failed", {
100
+ key: String(row.key ?? ""),
101
+ recordingId,
102
+ error: err instanceof Error ? err.message : String(err),
103
+ });
104
+ }
105
+ }
106
+ }
107
+
108
+ export default function registerMediaVerificationJob(): void {
109
+ const isProd = process.env.NODE_ENV === "production";
110
+ const flag = process.env.RUN_BACKGROUND_JOBS;
111
+ const enabled = flag === "1" || (isProd && flag !== "0");
112
+ if (!enabled) {
113
+ if (process.env.DEBUG && !skippingLogged) {
114
+ console.log(
115
+ "[media-verification] Skipping background sweep (set RUN_BACKGROUND_JOBS=1 to enable in dev).",
116
+ );
117
+ skippingLogged = true;
118
+ }
119
+ return;
120
+ }
121
+
122
+ setInterval(() => {
123
+ runMediaVerificationSweepOnce().catch((err) =>
124
+ console.error("[media-verification] interval failed:", err),
125
+ );
126
+ }, SWEEP_INTERVAL_MS);
127
+ console.log(
128
+ `[media-verification] Recurring recovery sweep every ${SWEEP_INTERVAL_MS / 1000}s.`,
129
+ );
130
+ }
@@ -648,6 +648,21 @@ async function processCompressionState(
648
648
  try {
649
649
  const compressed = await triggerBuilderCompression(state);
650
650
  if (compressed) {
651
+ if (!compressed.durationMs) {
652
+ if (attempts >= MAX_TRIGGER_ATTEMPTS) {
653
+ return markCompressionFailed(
654
+ { ...state, attempts },
655
+ `Builder media compression did not provide a verified duration after ${attempts} attempts`,
656
+ );
657
+ }
658
+ return writeCompressionState(state, {
659
+ status: "triggered",
660
+ attempts,
661
+ lastTriggeredAt: new Date().toISOString(),
662
+ nextAttemptAt: isoAfter(TRIGGERED_POLL_DELAY_MS),
663
+ detail: "Compressed media is not ready with a verified duration yet",
664
+ });
665
+ }
651
666
  return swapRecordingToCompressed(
652
667
  state,
653
668
  compressed.url,
@@ -0,0 +1,38 @@
1
+ export const MEDIA_VERIFICATION_STATE_PREFIX = "recording-media-verification-";
2
+
3
+ export type MediaVerificationMarker = {
4
+ recordingId: string;
5
+ status: "pending" | "leased";
6
+ completedAttempts: number;
7
+ nextAttemptAt: string;
8
+ leaseUntil: string | null;
9
+ updatedAt: string;
10
+ };
11
+
12
+ export function mediaVerificationStateKey(recordingId: string): string {
13
+ return `${MEDIA_VERIFICATION_STATE_PREFIX}${recordingId}`;
14
+ }
15
+
16
+ export function parseMediaVerificationMarker(
17
+ value: unknown,
18
+ ): MediaVerificationMarker | null {
19
+ if (!value || typeof value !== "object") return null;
20
+ const state = value as Record<string, unknown>;
21
+ if (
22
+ typeof state.recordingId !== "string" ||
23
+ !state.recordingId ||
24
+ (state.status !== "pending" && state.status !== "leased") ||
25
+ typeof state.completedAttempts !== "number" ||
26
+ !Number.isInteger(state.completedAttempts) ||
27
+ state.completedAttempts < 0 ||
28
+ typeof state.nextAttemptAt !== "string" ||
29
+ !Number.isFinite(Date.parse(state.nextAttemptAt)) ||
30
+ (state.leaseUntil !== null &&
31
+ (typeof state.leaseUntil !== "string" ||
32
+ !Number.isFinite(Date.parse(state.leaseUntil)))) ||
33
+ typeof state.updatedAt !== "string"
34
+ ) {
35
+ return null;
36
+ }
37
+ return state as MediaVerificationMarker;
38
+ }
@@ -0,0 +1,31 @@
1
+ export type PlayerThumbnailRecording = {
2
+ id: string;
3
+ thumbnailUrl?: string | null;
4
+ animatedThumbnailUrl?: string | null;
5
+ };
6
+
7
+ function appendQueryParam(url: string, key: string, value: string): string {
8
+ const separator = url.includes("?") ? "&" : "?";
9
+ return `${url}${separator}${encodeURIComponent(key)}=${encodeURIComponent(value)}`;
10
+ }
11
+
12
+ export function localRecordingThumbnailRoute(recordingId: string): string {
13
+ return `/api/thumbnail/${encodeURIComponent(recordingId)}`;
14
+ }
15
+
16
+ export function resolvePlayerThumbnailUrl(
17
+ recording: PlayerThumbnailRecording,
18
+ options: {
19
+ accessToken?: string | null;
20
+ appPath?: (path: string) => string;
21
+ } = {},
22
+ ): string | null {
23
+ if (!recording.thumbnailUrl && !recording.animatedThumbnailUrl) return null;
24
+
25
+ let resolved = localRecordingThumbnailRoute(recording.id);
26
+ if (options.accessToken) {
27
+ resolved = appendQueryParam(resolved, "t", options.accessToken);
28
+ }
29
+ if (options.appPath) resolved = options.appPath(resolved);
30
+ return resolved;
31
+ }
@@ -7,7 +7,7 @@ import {
7
7
  signScopedAgentAccessToken,
8
8
  } from "@agent-native/core/server";
9
9
 
10
- export type PostFinalizeJobKind = "seekable" | "transcript";
10
+ export type PostFinalizeJobKind = "media-ready" | "seekable" | "transcript";
11
11
 
12
12
  export const POST_FINALIZE_JOB_TOKEN_KIND = "clips-post-finalize-job";
13
13
 
@@ -47,10 +47,12 @@ export async function dispatchPostFinalizeJob(args: {
47
47
  delayMs?: number;
48
48
  retryAttempt?: number;
49
49
  regenerate?: boolean;
50
+ requireAccepted?: boolean;
50
51
  }): Promise<void> {
52
+ const { requireAccepted = false, ...job } = args;
51
53
  const token = signScopedAgentAccessToken({
52
54
  resourceKind: POST_FINALIZE_JOB_TOKEN_KIND,
53
- resourceId: postFinalizeJobResourceId(args.recordingId, args.kind),
55
+ resourceId: postFinalizeJobResourceId(job.recordingId, job.kind),
54
56
  ttlSeconds: 10 * 60,
55
57
  });
56
58
  const basePath = normalizeBasePath(
@@ -62,7 +64,7 @@ export async function dispatchPostFinalizeJob(args: {
62
64
  const usesDurableBackground =
63
65
  dispatchPathTargetsNetlifyBackgroundFunction(dispatchPath);
64
66
  const body = JSON.stringify({
65
- ...args,
67
+ ...job,
66
68
  token,
67
69
  ...(usesDurableBackground
68
70
  ? {
@@ -100,6 +102,11 @@ export async function dispatchPostFinalizeJob(args: {
100
102
  });
101
103
  });
102
104
 
105
+ if (requireAccepted) {
106
+ await dispatch;
107
+ return;
108
+ }
109
+
103
110
  await Promise.race([
104
111
  dispatch,
105
112
  new Promise<void>((resolve) => setTimeout(resolve, DISPATCH_SETTLE_MS)),
@@ -37,12 +37,12 @@ export default createAuthPlugin({
37
37
  "/api/media",
38
38
  "/api/clips-latest.json",
39
39
  "/api/clips-updater.json",
40
- // Blob-serving for the dev-fallback (no provider) path.
41
- // The route itself enforces resolveAccess + password/expiry checks;
42
- // we add it to publicPaths so anonymous viewers on /share/:id can
43
- // actually fetch the <video> bytes for public recordings. The chunk
44
- // upload POSTs stay behind auth under /api/uploads/*.
40
+ // Public media-serving routes enforce resolveAccess + password/expiry
41
+ // checks themselves. They need to bypass the app auth shell so anonymous
42
+ // viewers and crawlers can fetch public share media. Chunk upload POSTs
43
+ // stay behind auth under /api/uploads/*.
45
44
  "/api/video",
45
+ "/api/thumbnail",
46
46
  "/api/auth/google-calendar",
47
47
  "/_agent-native/google/auth-url",
48
48
  "/_agent-native/google/callback",
@@ -6,6 +6,7 @@
6
6
  */
7
7
 
8
8
  import registerBuilderMediaCompressionJob from "../jobs/builder-media-compression.js";
9
+ import registerMediaVerificationJob from "../jobs/media-verification.js";
9
10
  import registerMeetingRemindersJob from "../jobs/meeting-reminders.js";
10
11
  import registerPollCalendarsJob from "../jobs/poll-calendars.js";
11
12
  import registerStaleMeetingSweeperJob from "../jobs/stale-meeting-sweeper.js";
@@ -16,6 +17,7 @@ export default () => {
16
17
  // background loop is off.
17
18
  registerMeetingRemindersJob();
18
19
  registerBuilderMediaCompressionJob();
20
+ registerMediaVerificationJob();
19
21
  registerPollCalendarsJob();
20
22
  registerStaleMeetingSweeperJob();
21
23
  };