@agent-native/core 0.121.2 → 0.122.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (460) hide show
  1. package/corpus/README.md +2 -2
  2. package/corpus/core/CHANGELOG.md +139 -0
  3. package/corpus/core/docs/content/actions.mdx +2 -2
  4. package/corpus/core/docs/content/agent-surfaces.mdx +1 -1
  5. package/corpus/core/docs/content/authentication.mdx +2 -0
  6. package/corpus/core/docs/content/deployment.mdx +36 -0
  7. package/corpus/core/docs/content/http-api.mdx +266 -0
  8. package/corpus/core/docs/content/syncing-template-changes.mdx +159 -0
  9. package/corpus/core/package.json +2 -1
  10. package/corpus/core/src/a2a/activity.ts +38 -27
  11. package/corpus/core/src/agent/engine/builder-engine.ts +35 -4
  12. package/corpus/core/src/agent/run-manager.ts +20 -1
  13. package/corpus/core/src/agent/run-store.ts +4 -1
  14. package/corpus/core/src/agent/types.ts +28 -8
  15. package/corpus/core/src/chat-threads/store.ts +45 -0
  16. package/corpus/core/src/checkpoints/index.ts +2 -0
  17. package/corpus/core/src/checkpoints/route-match.ts +13 -0
  18. package/corpus/core/src/cli/create.ts +105 -17
  19. package/corpus/core/src/cli/index.ts +20 -0
  20. package/corpus/core/src/cli/template-baseline.ts +410 -0
  21. package/corpus/core/src/cli/template-sync.ts +1004 -0
  22. package/corpus/core/src/cli/templates-meta.ts +1 -0
  23. package/corpus/core/src/client/AssistantChat.tsx +38 -12
  24. package/corpus/core/src/client/NewWorkspaceAppFlow.tsx +75 -10
  25. package/corpus/core/src/client/chat/message-components.tsx +127 -66
  26. package/corpus/core/src/client/chat/tool-call-display.tsx +29 -5
  27. package/corpus/core/src/client/error-format.ts +18 -0
  28. package/corpus/core/src/client/org/OrgSwitcher.tsx +30 -8
  29. package/corpus/core/src/client/org/TeamPage.tsx +7 -6
  30. package/corpus/core/src/client/sse-event-processor.ts +3 -0
  31. package/corpus/core/src/data-widgets/index.ts +41 -0
  32. package/corpus/core/src/deploy/build.ts +11 -9
  33. package/corpus/core/src/integrations/webhook-handler.ts +63 -9
  34. package/corpus/core/src/org/auto-join-domain.ts +4 -3
  35. package/corpus/core/src/org/context.ts +108 -39
  36. package/corpus/core/src/org/index.ts +5 -0
  37. package/corpus/core/src/org/service-identity.ts +60 -0
  38. package/corpus/core/src/provider-api/index.ts +22 -1
  39. package/corpus/core/src/scripts/docs/search.ts +12 -1
  40. package/corpus/core/src/server/agent-chat/context-tools.ts +9 -4
  41. package/corpus/core/src/server/agent-chat/framework-prompts.ts +38 -66
  42. package/corpus/core/src/server/agent-chat-plugin.ts +17 -10
  43. package/corpus/core/src/server/agents-bundle.ts +84 -5
  44. package/corpus/core/src/server/auth.ts +4 -3
  45. package/corpus/core/src/server/builder-browser.ts +47 -19
  46. package/corpus/core/src/server/core-routes-plugin.ts +2 -2
  47. package/corpus/core/src/server/credential-provider.ts +209 -68
  48. package/corpus/core/src/server/index.ts +3 -0
  49. package/corpus/core/src/server/prompts/framework-core-compact.ts +6 -13
  50. package/corpus/core/src/server/prompts/framework-core.ts +9 -21
  51. package/corpus/core/src/server/prompts/index.ts +0 -1
  52. package/corpus/core/src/server/prompts/shared-rules.ts +19 -11
  53. package/corpus/core/src/server/ssr-handler.ts +13 -2
  54. package/corpus/core/src/settings/user-settings.ts +13 -1
  55. package/corpus/core/src/shared/cache-control.ts +141 -0
  56. package/corpus/core/src/templates/chat/.agents/skills/upgrade-agent-native/SKILL.md +24 -2
  57. package/corpus/core/src/templates/chat/_gitignore +1 -0
  58. package/corpus/core/src/templates/default/.agents/skills/upgrade-agent-native/SKILL.md +24 -2
  59. package/corpus/core/src/templates/headless/.agents/skills/upgrade-agent-native/SKILL.md +24 -2
  60. package/corpus/core/src/templates/workspace-core/.agents/skills/performance/SKILL.md +19 -0
  61. package/corpus/core/src/templates/workspace-core/.agents/skills/upgrade-agent-native/SKILL.md +24 -2
  62. package/corpus/core/src/templates/workspace-core/.agents/skills/visual-answer/SKILL.md +1 -0
  63. package/corpus/core/src/vite/agents-bundle-plugin.ts +7 -6
  64. package/corpus/core/src/vite/client.ts +5 -0
  65. package/corpus/templates/analytics/.agents/skills/adhoc-analysis/SKILL.md +4 -5
  66. package/corpus/templates/analytics/.agents/skills/admin-surfaces/SKILL.md +55 -0
  67. package/corpus/templates/analytics/.agents/skills/dashboard-management/SKILL.md +10 -0
  68. package/corpus/templates/analytics/.agents/skills/dashboard-management/references/template-catalog-and-demo.md +62 -0
  69. package/corpus/templates/analytics/.agents/skills/dashboard-ops/SKILL.md +85 -0
  70. package/corpus/templates/analytics/.agents/skills/data-querying/SKILL.md +27 -2
  71. package/corpus/templates/analytics/.agents/skills/data-querying/references/inline-chart-embeds.md +139 -0
  72. package/corpus/templates/analytics/.agents/skills/monitoring/SKILL.md +77 -0
  73. package/corpus/templates/analytics/.agents/skills/upgrade-agent-native/SKILL.md +24 -2
  74. package/corpus/templates/analytics/AGENTS.md +91 -563
  75. package/corpus/templates/analytics/_gitignore +1 -0
  76. package/corpus/templates/analytics/actions/bigquery.ts +12 -4
  77. package/corpus/templates/analytics/actions/generate-chart.ts +5 -5
  78. package/corpus/templates/analytics/app/components/dashboard/SqlChart.tsx +30 -1
  79. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/DashboardFilterBar.tsx +3 -0
  80. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/SqlChartCard.tsx +1 -1
  81. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +1 -0
  82. package/corpus/templates/analytics/changelog/2026-07-24-analytics-finds-saved-dashboards-and-queries-far-more-reliab.md +6 -0
  83. package/corpus/templates/analytics/changelog/2026-07-24-notion-access-errors-now-explain-that-a-page-needs-sharing-w.md +6 -0
  84. package/corpus/templates/analytics/server/lib/agent-chat-plan-mode.ts +7 -0
  85. package/corpus/templates/analytics/server/lib/analytics-query-catalog.ts +191 -30
  86. package/corpus/templates/analytics/server/lib/bigquery.ts +11 -1
  87. package/corpus/templates/analytics/server/lib/notion.ts +15 -4
  88. package/corpus/templates/analytics/server/plugins/agent-chat.ts +10 -0
  89. package/corpus/templates/assets/.agents/skills/upgrade-agent-native/SKILL.md +24 -2
  90. package/corpus/templates/brain/.agents/skills/upgrade-agent-native/SKILL.md +24 -2
  91. package/corpus/templates/brain/_gitignore +1 -0
  92. package/corpus/templates/calendar/.agents/skills/upgrade-agent-native/SKILL.md +24 -2
  93. package/corpus/templates/calendar/_gitignore +1 -0
  94. package/corpus/templates/chat/.agents/skills/upgrade-agent-native/SKILL.md +24 -2
  95. package/corpus/templates/chat/_gitignore +1 -0
  96. package/corpus/templates/clips/.agents/skills/upgrade-agent-native/SKILL.md +24 -2
  97. package/corpus/templates/clips/.agents/skills/video-sharing/SKILL.md +34 -0
  98. package/corpus/templates/clips/AGENTS.md +5 -4
  99. package/corpus/templates/clips/_gitignore +1 -0
  100. package/corpus/templates/clips/actions/cleanup-transcript.ts +5 -0
  101. package/corpus/templates/clips/actions/get-recording-insights.ts +44 -8
  102. package/corpus/templates/clips/actions/get-recording-player-data.ts +39 -70
  103. package/corpus/templates/clips/actions/list-clip-views.ts +2 -4
  104. package/corpus/templates/clips/actions/list-recordings.ts +69 -19
  105. package/corpus/templates/clips/actions/list-viewers.ts +5 -2
  106. package/corpus/templates/clips/actions/regenerate-summary.ts +5 -1
  107. package/corpus/templates/clips/actions/request-transcript.ts +28 -1
  108. package/corpus/templates/clips/app/components/editor/stitch-manager.tsx +21 -1
  109. package/corpus/templates/clips/app/components/meetings/quick-ask-sidebar.tsx +14 -1
  110. package/corpus/templates/clips/app/components/player/insights-panel.tsx +8 -20
  111. package/corpus/templates/clips/app/components/player/recording-views-badge.tsx +335 -0
  112. package/corpus/templates/clips/app/components/player/video-player.tsx +13 -8
  113. package/corpus/templates/clips/app/components/sharing/share-ui.tsx +2 -1
  114. package/corpus/templates/clips/app/i18n/en-US.ts +13 -2
  115. package/corpus/templates/clips/app/lib/capture-permissions.ts +110 -0
  116. package/corpus/templates/clips/app/lib/recording-link.ts +32 -0
  117. package/corpus/templates/clips/app/routes/_app.meetings.$meetingId.tsx +27 -0
  118. package/corpus/templates/clips/app/routes/r.$recordingId.tsx +46 -15
  119. package/corpus/templates/clips/app/routes/record.tsx +77 -31
  120. package/corpus/templates/clips/app/routes/share.$shareId.tsx +42 -31
  121. package/corpus/templates/clips/changelog/2026-07-24-clip-pages-now-show-a-view-count-with-viewer-avatars-next-to.md +6 -0
  122. package/corpus/templates/clips/changelog/2026-07-24-clip-summaries-read-like-a-description-you-d-write-yourself-.md +6 -0
  123. package/corpus/templates/clips/changelog/2026-07-24-clip-view-counts-now-separate-human-views-from-agent-views-s.md +6 -0
  124. package/corpus/templates/clips/changelog/2026-07-24-clips-actions-can-now-be-called-by-org-service-tokens-so-ext.md +6 -0
  125. package/corpus/templates/clips/changelog/2026-07-24-clips-no-longer-briefly-shows-a-dock-icon-while-launching.md +6 -0
  126. package/corpus/templates/clips/changelog/2026-07-24-fixed-owner-and-editor-share-links-redirecting-to-the-record.md +6 -0
  127. package/corpus/templates/clips/changelog/2026-07-24-meeting-and-note-recordings-now-start-as-a-compact-pill-on-t.md +6 -0
  128. package/corpus/templates/clips/changelog/2026-07-24-new-clips-now-copy-a-shareable-link-instead-of-the-owner-onl.md +6 -0
  129. package/corpus/templates/clips/changelog/2026-07-24-opening-a-share-link-as-the-clip-s-owner-now-goes-straight-t.md +6 -0
  130. package/corpus/templates/clips/changelog/2026-07-24-permissions-and-rewind-controls-now-use-compact-consistent-h.md +6 -0
  131. package/corpus/templates/clips/changelog/2026-07-24-quitting-the-desktop-app-no-longer-shows-a-false-macos-crash.md +6 -0
  132. package/corpus/templates/clips/changelog/2026-07-24-screen-recording-permission-errors-now-name-the-exact-app-to.md +6 -0
  133. package/corpus/templates/clips/changelog/2026-07-24-the-desktop-app-now-copies-a-new-clip-s-share-link-to-your-c.md +6 -0
  134. package/corpus/templates/clips/changelog/2026-07-24-the-recording-toolbar-now-stays-above-other-apps-including-o.md +6 -0
  135. package/corpus/templates/clips/changelog/2026-07-24-transcript-captions-now-use-real-speech-timings-instead-of-a.md +6 -0
  136. package/corpus/templates/clips/changelog/2026-07-24-video-playback-no-longer-stutters-and-replays-the-last-momen.md +6 -0
  137. package/corpus/templates/clips/chrome-extension/src/background.ts +17 -1
  138. package/corpus/templates/clips/desktop/package.json +2 -0
  139. package/corpus/templates/clips/desktop/src/app.tsx +89 -0
  140. package/corpus/templates/clips/desktop/src/hooks/useMeetingTranscription.ts +9 -3
  141. package/corpus/templates/clips/desktop/src/lib/audio-meter.ts +51 -0
  142. package/corpus/templates/clips/desktop/src/lib/native-notification.ts +44 -0
  143. package/corpus/templates/clips/desktop/src/lib/recording-link.ts +67 -0
  144. package/corpus/templates/clips/desktop/src/overlays/live-audio-bars.tsx +35 -35
  145. package/corpus/templates/clips/desktop/src/overlays/live-transcript.tsx +3 -2
  146. package/corpus/templates/clips/desktop/src/overlays/recording-pill.tsx +47 -46
  147. package/corpus/templates/clips/desktop/src/styles.css +127 -78
  148. package/corpus/templates/clips/desktop/src-tauri/Cargo.toml +7 -0
  149. package/corpus/templates/clips/desktop/src-tauri/Info.plist +6 -0
  150. package/corpus/templates/clips/desktop/src-tauri/capabilities/default.json +1 -0
  151. package/corpus/templates/clips/desktop/src-tauri/src/clips/mod.rs +7 -3
  152. package/corpus/templates/clips/desktop/src-tauri/src/lib.rs +4 -1
  153. package/corpus/templates/clips/desktop/src-tauri/src/main.rs +8 -0
  154. package/corpus/templates/clips/desktop/src-tauri/src/recording_indicator.rs +21 -10
  155. package/corpus/templates/clips/desktop/src-tauri/src/util.rs +40 -0
  156. package/corpus/templates/clips/server/db/schema.ts +25 -0
  157. package/corpus/templates/clips/server/lib/agent-views.ts +145 -0
  158. package/corpus/templates/clips/server/lib/public-agent-context.ts +7 -0
  159. package/corpus/templates/clips/server/lib/recording-share-grant.ts +79 -0
  160. package/corpus/templates/clips/server/lib/recordings.ts +51 -3
  161. package/corpus/templates/clips/server/plugins/db.ts +23 -0
  162. package/corpus/templates/clips/server/routes/api/public-recording.get.ts +49 -17
  163. package/corpus/templates/clips/shared/recording-link.ts +43 -0
  164. package/corpus/templates/clips/shared/share-attribution.ts +4 -0
  165. package/corpus/templates/clips/shared/share-dashboard-redirect.ts +42 -0
  166. package/corpus/templates/clips/shared/view-analytics.ts +26 -0
  167. package/corpus/templates/content/.agents/skills/content/SKILL.md +5 -0
  168. package/corpus/templates/content/.agents/skills/content/references/local-file-mode.md +135 -0
  169. package/corpus/templates/content/.agents/skills/document-editing/SKILL.md +12 -0
  170. package/corpus/templates/content/.agents/skills/document-editing/references/databases.md +238 -0
  171. package/corpus/templates/content/.agents/skills/document-editing/references/document-behavior.md +91 -0
  172. package/corpus/templates/content/.agents/skills/upgrade-agent-native/SKILL.md +24 -2
  173. package/corpus/templates/content/AGENTS.md +62 -583
  174. package/corpus/templates/content/_gitignore +1 -0
  175. package/corpus/templates/design/.agents/skills/creative-context/SKILL.md +11 -0
  176. package/corpus/templates/design/.agents/skills/design-generation/SKILL.md +50 -8
  177. package/corpus/templates/design/.agents/skills/design-generation/references/code-layers.md +81 -0
  178. package/corpus/templates/design/.agents/skills/design-generation/references/code-workspace.md +42 -0
  179. package/corpus/templates/design/.agents/skills/design-generation/references/editor-extensions.md +34 -0
  180. package/corpus/templates/design/.agents/skills/responsive-breakpoints/SKILL.md +4 -1
  181. package/corpus/templates/design/.agents/skills/upgrade-agent-native/SKILL.md +24 -2
  182. package/corpus/templates/design/.agents/skills/visual-edit/SKILL.md +17 -0
  183. package/corpus/templates/design/.generated/bridge/editor-chrome.generated.ts +8196 -0
  184. package/corpus/templates/design/.generated/bridge/embedded-wheel.generated.ts +265 -0
  185. package/corpus/templates/design/.generated/bridge/hit-test.generated.ts +642 -0
  186. package/corpus/templates/design/.generated/bridge/motion-preview.generated.ts +571 -0
  187. package/corpus/templates/design/.generated/bridge/nav.generated.ts +91 -0
  188. package/corpus/templates/design/.generated/bridge/sample.generated.ts +23 -0
  189. package/corpus/templates/design/.generated/bridge/shader-fill-preview.generated.ts +263 -0
  190. package/corpus/templates/design/.generated/bridge/shader-runtime.generated.ts +518 -0
  191. package/corpus/templates/design/.generated/bridge/tweak.generated.ts +20 -0
  192. package/corpus/templates/design/.generated/bridge/zoom.generated.ts +32 -0
  193. package/corpus/templates/design/AGENTS.md +88 -503
  194. package/corpus/templates/design/_gitignore +1 -0
  195. package/corpus/templates/dispatch/.agents/skills/upgrade-agent-native/SKILL.md +24 -2
  196. package/corpus/templates/dispatch/_gitignore +1 -0
  197. package/corpus/templates/dispatch/changelog/2026-07-24-signing-in-now-puts-you-in-your-companys-workspace.md +6 -0
  198. package/corpus/templates/forms/.agents/skills/upgrade-agent-native/SKILL.md +24 -2
  199. package/corpus/templates/forms/_gitignore +1 -0
  200. package/corpus/templates/forms/server/lib/public-form-ssr.ts +2 -2
  201. package/corpus/templates/macros/.agents/skills/upgrade-agent-native/SKILL.md +24 -2
  202. package/corpus/templates/macros/_gitignore +1 -0
  203. package/corpus/templates/mail/.agents/skills/upgrade-agent-native/SKILL.md +24 -2
  204. package/corpus/templates/mail/_gitignore +1 -0
  205. package/corpus/templates/plan/.agents/skills/upgrade-agent-native/SKILL.md +24 -2
  206. package/corpus/templates/plan/_gitignore +1 -0
  207. package/corpus/templates/slides/.agents/skills/upgrade-agent-native/SKILL.md +24 -2
  208. package/corpus/templates/slides/_gitignore +1 -0
  209. package/corpus/templates/slides/server/plugins/db.ts +4 -0
  210. package/corpus/templates/tasks/.agents/skills/upgrade-agent-native/SKILL.md +24 -2
  211. package/corpus/templates/tasks/AGENTS.md +1 -1
  212. package/corpus/templates/tasks/_gitignore +1 -0
  213. package/dist/a2a/activity.d.ts +4 -1
  214. package/dist/a2a/activity.d.ts.map +1 -1
  215. package/dist/a2a/activity.js +27 -16
  216. package/dist/a2a/activity.js.map +1 -1
  217. package/dist/agent/engine/builder-engine.d.ts +1 -0
  218. package/dist/agent/engine/builder-engine.d.ts.map +1 -1
  219. package/dist/agent/engine/builder-engine.js +31 -4
  220. package/dist/agent/engine/builder-engine.js.map +1 -1
  221. package/dist/agent/run-manager.d.ts.map +1 -1
  222. package/dist/agent/run-manager.js +18 -1
  223. package/dist/agent/run-manager.js.map +1 -1
  224. package/dist/agent/run-store.d.ts +1 -0
  225. package/dist/agent/run-store.d.ts.map +1 -1
  226. package/dist/agent/run-store.js +2 -1
  227. package/dist/agent/run-store.js.map +1 -1
  228. package/dist/agent/types.d.ts +12 -1
  229. package/dist/agent/types.d.ts.map +1 -1
  230. package/dist/agent/types.js +21 -1
  231. package/dist/agent/types.js.map +1 -1
  232. package/dist/chat-threads/store.d.ts +10 -0
  233. package/dist/chat-threads/store.d.ts.map +1 -1
  234. package/dist/chat-threads/store.js +41 -0
  235. package/dist/chat-threads/store.js.map +1 -1
  236. package/dist/checkpoints/index.d.ts +1 -0
  237. package/dist/checkpoints/index.d.ts.map +1 -1
  238. package/dist/checkpoints/index.js +1 -0
  239. package/dist/checkpoints/index.js.map +1 -1
  240. package/dist/checkpoints/route-match.d.ts +8 -0
  241. package/dist/checkpoints/route-match.d.ts.map +1 -0
  242. package/dist/checkpoints/route-match.js +13 -0
  243. package/dist/checkpoints/route-match.js.map +1 -0
  244. package/dist/cli/create.d.ts +35 -4
  245. package/dist/cli/create.d.ts.map +1 -1
  246. package/dist/cli/create.js +69 -19
  247. package/dist/cli/create.js.map +1 -1
  248. package/dist/cli/index.js +19 -0
  249. package/dist/cli/index.js.map +1 -1
  250. package/dist/cli/template-baseline.d.ts +54 -0
  251. package/dist/cli/template-baseline.d.ts.map +1 -0
  252. package/dist/cli/template-baseline.js +334 -0
  253. package/dist/cli/template-baseline.js.map +1 -0
  254. package/dist/cli/template-sync.d.ts +57 -0
  255. package/dist/cli/template-sync.d.ts.map +1 -0
  256. package/dist/cli/template-sync.js +787 -0
  257. package/dist/cli/template-sync.js.map +1 -0
  258. package/dist/cli/templates-meta.d.ts.map +1 -1
  259. package/dist/cli/templates-meta.js +1 -0
  260. package/dist/cli/templates-meta.js.map +1 -1
  261. package/dist/client/AssistantChat.d.ts +2 -1
  262. package/dist/client/AssistantChat.d.ts.map +1 -1
  263. package/dist/client/AssistantChat.js +38 -10
  264. package/dist/client/AssistantChat.js.map +1 -1
  265. package/dist/client/NewWorkspaceAppFlow.d.ts.map +1 -1
  266. package/dist/client/NewWorkspaceAppFlow.js +32 -2
  267. package/dist/client/NewWorkspaceAppFlow.js.map +1 -1
  268. package/dist/client/chat/message-components.d.ts +20 -0
  269. package/dist/client/chat/message-components.d.ts.map +1 -1
  270. package/dist/client/chat/message-components.js +81 -46
  271. package/dist/client/chat/message-components.js.map +1 -1
  272. package/dist/client/chat/tool-call-display.d.ts.map +1 -1
  273. package/dist/client/chat/tool-call-display.js +14 -6
  274. package/dist/client/chat/tool-call-display.js.map +1 -1
  275. package/dist/client/error-format.d.ts.map +1 -1
  276. package/dist/client/error-format.js +13 -0
  277. package/dist/client/error-format.js.map +1 -1
  278. package/dist/client/org/OrgSwitcher.d.ts +8 -1
  279. package/dist/client/org/OrgSwitcher.d.ts.map +1 -1
  280. package/dist/client/org/OrgSwitcher.js +3 -2
  281. package/dist/client/org/OrgSwitcher.js.map +1 -1
  282. package/dist/client/org/TeamPage.d.ts.map +1 -1
  283. package/dist/client/org/TeamPage.js +1 -1
  284. package/dist/client/org/TeamPage.js.map +1 -1
  285. package/dist/client/sse-event-processor.d.ts.map +1 -1
  286. package/dist/client/sse-event-processor.js +3 -0
  287. package/dist/client/sse-event-processor.js.map +1 -1
  288. package/dist/collab/awareness.d.ts +2 -2
  289. package/dist/collab/awareness.d.ts.map +1 -1
  290. package/dist/data-widgets/index.d.ts +3 -0
  291. package/dist/data-widgets/index.d.ts.map +1 -1
  292. package/dist/data-widgets/index.js +33 -0
  293. package/dist/data-widgets/index.js.map +1 -1
  294. package/dist/deploy/build.d.ts.map +1 -1
  295. package/dist/deploy/build.js +11 -7
  296. package/dist/deploy/build.js.map +1 -1
  297. package/dist/eject/provider-api-definitions.d.ts +1 -0
  298. package/dist/eject/provider-api-definitions.d.ts.map +1 -1
  299. package/dist/integrations/webhook-handler.d.ts.map +1 -1
  300. package/dist/integrations/webhook-handler.js +46 -9
  301. package/dist/integrations/webhook-handler.js.map +1 -1
  302. package/dist/org/auto-join-domain.d.ts +3 -2
  303. package/dist/org/auto-join-domain.d.ts.map +1 -1
  304. package/dist/org/auto-join-domain.js +1 -1
  305. package/dist/org/auto-join-domain.js.map +1 -1
  306. package/dist/org/context.d.ts.map +1 -1
  307. package/dist/org/context.js +84 -28
  308. package/dist/org/context.js.map +1 -1
  309. package/dist/org/index.d.ts +1 -0
  310. package/dist/org/index.d.ts.map +1 -1
  311. package/dist/org/index.js +1 -0
  312. package/dist/org/index.js.map +1 -1
  313. package/dist/org/service-identity.d.ts +43 -0
  314. package/dist/org/service-identity.d.ts.map +1 -0
  315. package/dist/org/service-identity.js +34 -0
  316. package/dist/org/service-identity.js.map +1 -0
  317. package/dist/provider-api/actions/custom-provider-registration.d.ts +14 -14
  318. package/dist/provider-api/actions/provider-api.d.ts +13 -11
  319. package/dist/provider-api/actions/provider-api.d.ts.map +1 -1
  320. package/dist/provider-api/index.d.ts +4 -0
  321. package/dist/provider-api/index.d.ts.map +1 -1
  322. package/dist/provider-api/index.js +17 -1
  323. package/dist/provider-api/index.js.map +1 -1
  324. package/dist/resources/handlers.d.ts +1 -1
  325. package/dist/scripts/docs/search.d.ts.map +1 -1
  326. package/dist/scripts/docs/search.js +12 -1
  327. package/dist/scripts/docs/search.js.map +1 -1
  328. package/dist/server/agent-chat/context-tools.d.ts.map +1 -1
  329. package/dist/server/agent-chat/context-tools.js +3 -3
  330. package/dist/server/agent-chat/context-tools.js.map +1 -1
  331. package/dist/server/agent-chat/framework-prompts.d.ts +8 -3
  332. package/dist/server/agent-chat/framework-prompts.d.ts.map +1 -1
  333. package/dist/server/agent-chat/framework-prompts.js +33 -62
  334. package/dist/server/agent-chat/framework-prompts.js.map +1 -1
  335. package/dist/server/agent-chat-plugin.d.ts.map +1 -1
  336. package/dist/server/agent-chat-plugin.js +16 -10
  337. package/dist/server/agent-chat-plugin.js.map +1 -1
  338. package/dist/server/agents-bundle.d.ts +18 -0
  339. package/dist/server/agents-bundle.d.ts.map +1 -1
  340. package/dist/server/agents-bundle.js +70 -4
  341. package/dist/server/agents-bundle.js.map +1 -1
  342. package/dist/server/auth.d.ts.map +1 -1
  343. package/dist/server/auth.js +4 -3
  344. package/dist/server/auth.js.map +1 -1
  345. package/dist/server/builder-browser.d.ts.map +1 -1
  346. package/dist/server/builder-browser.js +43 -19
  347. package/dist/server/builder-browser.js.map +1 -1
  348. package/dist/server/core-routes-plugin.js +2 -2
  349. package/dist/server/core-routes-plugin.js.map +1 -1
  350. package/dist/server/credential-provider.d.ts +38 -2
  351. package/dist/server/credential-provider.d.ts.map +1 -1
  352. package/dist/server/credential-provider.js +142 -64
  353. package/dist/server/credential-provider.js.map +1 -1
  354. package/dist/server/index.d.ts +1 -1
  355. package/dist/server/index.d.ts.map +1 -1
  356. package/dist/server/index.js +1 -1
  357. package/dist/server/index.js.map +1 -1
  358. package/dist/server/prompts/framework-core-compact.d.ts +1 -1
  359. package/dist/server/prompts/framework-core-compact.d.ts.map +1 -1
  360. package/dist/server/prompts/framework-core-compact.js +7 -13
  361. package/dist/server/prompts/framework-core-compact.js.map +1 -1
  362. package/dist/server/prompts/framework-core.d.ts +1 -1
  363. package/dist/server/prompts/framework-core.d.ts.map +1 -1
  364. package/dist/server/prompts/framework-core.js +10 -21
  365. package/dist/server/prompts/framework-core.js.map +1 -1
  366. package/dist/server/prompts/index.d.ts +1 -1
  367. package/dist/server/prompts/index.d.ts.map +1 -1
  368. package/dist/server/prompts/index.js +1 -1
  369. package/dist/server/prompts/index.js.map +1 -1
  370. package/dist/server/prompts/shared-rules.d.ts +16 -10
  371. package/dist/server/prompts/shared-rules.d.ts.map +1 -1
  372. package/dist/server/prompts/shared-rules.js +19 -10
  373. package/dist/server/prompts/shared-rules.js.map +1 -1
  374. package/dist/server/ssr-handler.d.ts +1 -1
  375. package/dist/server/ssr-handler.d.ts.map +1 -1
  376. package/dist/server/ssr-handler.js +10 -3
  377. package/dist/server/ssr-handler.js.map +1 -1
  378. package/dist/settings/user-settings.d.ts.map +1 -1
  379. package/dist/settings/user-settings.js +13 -1
  380. package/dist/settings/user-settings.js.map +1 -1
  381. package/dist/shared/cache-control.d.ts +52 -0
  382. package/dist/shared/cache-control.d.ts.map +1 -1
  383. package/dist/shared/cache-control.js +107 -0
  384. package/dist/shared/cache-control.js.map +1 -1
  385. package/dist/templates/chat/.agents/skills/upgrade-agent-native/SKILL.md +24 -2
  386. package/dist/templates/chat/_gitignore +1 -0
  387. package/dist/templates/default/.agents/skills/upgrade-agent-native/SKILL.md +24 -2
  388. package/dist/templates/headless/.agents/skills/upgrade-agent-native/SKILL.md +24 -2
  389. package/dist/templates/workspace-core/.agents/skills/performance/SKILL.md +19 -0
  390. package/dist/templates/workspace-core/.agents/skills/upgrade-agent-native/SKILL.md +24 -2
  391. package/dist/templates/workspace-core/.agents/skills/visual-answer/SKILL.md +1 -0
  392. package/dist/vite/agents-bundle-plugin.d.ts.map +1 -1
  393. package/dist/vite/agents-bundle-plugin.js +8 -7
  394. package/dist/vite/agents-bundle-plugin.js.map +1 -1
  395. package/dist/vite/client.d.ts.map +1 -1
  396. package/dist/vite/client.js +2 -0
  397. package/dist/vite/client.js.map +1 -1
  398. package/docs/content/actions.mdx +2 -2
  399. package/docs/content/agent-surfaces.mdx +1 -1
  400. package/docs/content/authentication.mdx +2 -0
  401. package/docs/content/deployment.mdx +36 -0
  402. package/docs/content/http-api.mdx +266 -0
  403. package/docs/content/syncing-template-changes.mdx +159 -0
  404. package/package.json +4 -3
  405. package/src/a2a/activity.ts +38 -27
  406. package/src/agent/engine/builder-engine.ts +35 -4
  407. package/src/agent/run-manager.ts +20 -1
  408. package/src/agent/run-store.ts +4 -1
  409. package/src/agent/types.ts +28 -8
  410. package/src/chat-threads/store.ts +45 -0
  411. package/src/checkpoints/index.ts +2 -0
  412. package/src/checkpoints/route-match.ts +13 -0
  413. package/src/cli/create.ts +105 -17
  414. package/src/cli/index.ts +20 -0
  415. package/src/cli/template-baseline.ts +410 -0
  416. package/src/cli/template-sync.ts +1004 -0
  417. package/src/cli/templates-meta.ts +1 -0
  418. package/src/client/AssistantChat.tsx +38 -12
  419. package/src/client/NewWorkspaceAppFlow.tsx +75 -10
  420. package/src/client/chat/message-components.tsx +127 -66
  421. package/src/client/chat/tool-call-display.tsx +29 -5
  422. package/src/client/error-format.ts +18 -0
  423. package/src/client/org/OrgSwitcher.tsx +30 -8
  424. package/src/client/org/TeamPage.tsx +7 -6
  425. package/src/client/sse-event-processor.ts +3 -0
  426. package/src/data-widgets/index.ts +41 -0
  427. package/src/deploy/build.ts +11 -9
  428. package/src/integrations/webhook-handler.ts +63 -9
  429. package/src/org/auto-join-domain.ts +4 -3
  430. package/src/org/context.ts +108 -39
  431. package/src/org/index.ts +5 -0
  432. package/src/org/service-identity.ts +60 -0
  433. package/src/provider-api/index.ts +22 -1
  434. package/src/scripts/docs/search.ts +12 -1
  435. package/src/server/__snapshots__/agent-chat-plugin.surface.spec.ts.snap +25 -36
  436. package/src/server/agent-chat/context-tools.ts +9 -4
  437. package/src/server/agent-chat/framework-prompts.ts +38 -66
  438. package/src/server/agent-chat-plugin.ts +17 -10
  439. package/src/server/agents-bundle.ts +84 -5
  440. package/src/server/auth.ts +4 -3
  441. package/src/server/builder-browser.ts +47 -19
  442. package/src/server/core-routes-plugin.ts +2 -2
  443. package/src/server/credential-provider.ts +209 -68
  444. package/src/server/index.ts +3 -0
  445. package/src/server/prompts/framework-core-compact.ts +6 -13
  446. package/src/server/prompts/framework-core.ts +9 -21
  447. package/src/server/prompts/index.ts +0 -1
  448. package/src/server/prompts/shared-rules.ts +19 -11
  449. package/src/server/ssr-handler.ts +13 -2
  450. package/src/settings/user-settings.ts +13 -1
  451. package/src/shared/cache-control.ts +141 -0
  452. package/src/templates/chat/.agents/skills/upgrade-agent-native/SKILL.md +24 -2
  453. package/src/templates/chat/_gitignore +1 -0
  454. package/src/templates/default/.agents/skills/upgrade-agent-native/SKILL.md +24 -2
  455. package/src/templates/headless/.agents/skills/upgrade-agent-native/SKILL.md +24 -2
  456. package/src/templates/workspace-core/.agents/skills/performance/SKILL.md +19 -0
  457. package/src/templates/workspace-core/.agents/skills/upgrade-agent-native/SKILL.md +24 -2
  458. package/src/templates/workspace-core/.agents/skills/visual-answer/SKILL.md +1 -0
  459. package/src/vite/agents-bundle-plugin.ts +7 -6
  460. package/src/vite/client.ts +5 -0
@@ -382,6 +382,31 @@ export const recordingViews = table("recording_views", {
382
382
  viewedAt: text("viewed_at").notNull().default(now()),
383
383
  });
384
384
 
385
+ // Agent views — one row per (clip, agent, time bucket). Deliberately separate
386
+ // from `recording_viewers` / `recording_views` so no human-view count can ever
387
+ // pick agents up by forgetting a filter: the human tables stay agent-free.
388
+ export const recordingAgentViews = table(
389
+ "recording_agent_views",
390
+ {
391
+ id: text("id").primaryKey(),
392
+ recordingId: text("recording_id").notNull(),
393
+ // sha256 of user-agent + request IP. Never stores the raw IP.
394
+ agentKey: text("agent_key").notNull(),
395
+ agentLabel: text("agent_label"),
396
+ // Time bucket that collapses one agent's burst of context/transcript/frame
397
+ // polls into a single view.
398
+ viewSessionId: text("view_session_id").notNull(),
399
+ firstSeenAt: text("first_seen_at").notNull().default(now()),
400
+ lastSeenAt: text("last_seen_at").notNull().default(now()),
401
+ requestCount: integer("request_count").notNull().default(1),
402
+ },
403
+ (view) => ({
404
+ recordingAgentViewSessionUnique: uniqueIndex(
405
+ "recording_agent_views_session_unique_idx",
406
+ ).on(view.recordingId, view.agentKey, view.viewSessionId),
407
+ }),
408
+ );
409
+
385
410
  // -----------------------------------------------------------------------------
386
411
  // Meetings (Granola-style) — recording + transcript + AI notes anchored to
387
412
  // a calendar event or an ad-hoc meeting block. Composes with the existing
@@ -0,0 +1,145 @@
1
+ /**
2
+ * Agent views — counting when an *outside agent* reads a clip through the
3
+ * public agent APIs (`/api/agent-context.json`, `/api/agent-transcript.json`,
4
+ * `/api/agent-frame.jpg`), as opposed to a human opening the player.
5
+ *
6
+ * Those routes are agent-only surfaces: a human watching a clip never hits
7
+ * them, so a request on one is the signal. Counts live in their own table so
8
+ * every existing human-view query stays agent-free by construction.
9
+ */
10
+
11
+ import { createHash } from "node:crypto";
12
+
13
+ import { count, desc, eq, sql } from "drizzle-orm";
14
+ import { getRequestHeader, getRequestIP, type H3Event } from "h3";
15
+
16
+ import { getDb, schema } from "../db/index.js";
17
+ import { nanoid } from "./recordings.js";
18
+
19
+ /**
20
+ * One agent's burst of context + transcript + frame polls is one view.
21
+ * Fixed windows mean a burst straddling a boundary counts twice; switch to
22
+ * last-seen-gap dedup if that ever shows up in the numbers.
23
+ */
24
+ export const AGENT_VIEW_SESSION_MS = 30 * 60 * 1000;
25
+
26
+ const AGENT_LABELS: [RegExp, string][] = [
27
+ [/claude|anthropic/i, "Claude"],
28
+ [/chatgpt|gptbot|oai-searchbot|openai/i, "ChatGPT"],
29
+ [/perplexity/i, "Perplexity"],
30
+ [/gemini|google-extended|googleother/i, "Gemini"],
31
+ [/copilot/i, "Copilot"],
32
+ [/cursor/i, "Cursor"],
33
+ [/bingbot|bingpreview/i, "Bing"],
34
+ [/applebot/i, "Apple Intelligence"],
35
+ [/meta-external|facebookbot/i, "Meta AI"],
36
+ [/bytespider|amazonbot|ccbot|diffbot|youbot/i, "Crawler"],
37
+ ];
38
+
39
+ export const UNKNOWN_AGENT_LABEL = "Agent";
40
+
41
+ export function agentLabelFromUserAgent(userAgent: string): string {
42
+ for (const [pattern, label] of AGENT_LABELS) {
43
+ if (pattern.test(userAgent)) return label;
44
+ }
45
+ return UNKNOWN_AGENT_LABEL;
46
+ }
47
+
48
+ export function agentViewSessionId(
49
+ now: number,
50
+ bucketMs = AGENT_VIEW_SESSION_MS,
51
+ ): string {
52
+ return String(Math.floor(now / bucketMs));
53
+ }
54
+
55
+ /** Hashed so an agent is distinguishable across polls without storing its IP. */
56
+ export function agentKeyFor(userAgent: string, ip: string): string {
57
+ return createHash("sha256")
58
+ .update(`${userAgent}|${ip}`)
59
+ .digest("hex")
60
+ .slice(0, 32);
61
+ }
62
+
63
+ /**
64
+ * Best-effort: never let view accounting fail an agent's read of a clip.
65
+ */
66
+ export async function recordAgentView(
67
+ event: H3Event,
68
+ recordingId: string,
69
+ nowMs: number = Date.now(),
70
+ ): Promise<void> {
71
+ try {
72
+ const userAgent = (getRequestHeader(event, "user-agent") ?? "").slice(
73
+ 0,
74
+ 512,
75
+ );
76
+ const ip = getRequestIP(event) || "unknown";
77
+ const now = new Date(nowMs).toISOString();
78
+
79
+ await getDb()
80
+ .insert(schema.recordingAgentViews)
81
+ .values({
82
+ id: nanoid(),
83
+ recordingId,
84
+ agentKey: agentKeyFor(userAgent, ip),
85
+ agentLabel: agentLabelFromUserAgent(userAgent),
86
+ viewSessionId: agentViewSessionId(nowMs),
87
+ firstSeenAt: now,
88
+ lastSeenAt: now,
89
+ requestCount: 1,
90
+ })
91
+ .onConflictDoUpdate({
92
+ target: [
93
+ schema.recordingAgentViews.recordingId,
94
+ schema.recordingAgentViews.agentKey,
95
+ schema.recordingAgentViews.viewSessionId,
96
+ ],
97
+ set: {
98
+ lastSeenAt: now,
99
+ requestCount: sql`${schema.recordingAgentViews.requestCount} + 1`,
100
+ },
101
+ });
102
+ } catch (err) {
103
+ console.warn("[agent-views] failed to record agent view:", err);
104
+ }
105
+ }
106
+
107
+ export async function countRecordingAgentViews(
108
+ recordingId: string,
109
+ ): Promise<number> {
110
+ const [row] = await getDb()
111
+ .select({ value: count() })
112
+ .from(schema.recordingAgentViews)
113
+ .where(eq(schema.recordingAgentViews.recordingId, recordingId));
114
+ return Number(row?.value ?? 0);
115
+ }
116
+
117
+ export interface AgentViewerSummary {
118
+ agentLabel: string;
119
+ views: number;
120
+ lastSeenAt: string;
121
+ }
122
+
123
+ /** Agents that read this clip, most recent first, grouped by product label. */
124
+ export async function listRecordingAgentViewers(
125
+ recordingId: string,
126
+ limit = 8,
127
+ ): Promise<AgentViewerSummary[]> {
128
+ const rows = await getDb()
129
+ .select({
130
+ agentLabel: schema.recordingAgentViews.agentLabel,
131
+ views: count(),
132
+ lastSeenAt: sql<string>`MAX(${schema.recordingAgentViews.lastSeenAt})`,
133
+ })
134
+ .from(schema.recordingAgentViews)
135
+ .where(eq(schema.recordingAgentViews.recordingId, recordingId))
136
+ .groupBy(schema.recordingAgentViews.agentLabel)
137
+ .orderBy(desc(sql`MAX(${schema.recordingAgentViews.lastSeenAt})`))
138
+ .limit(limit);
139
+
140
+ return rows.map((r) => ({
141
+ agentLabel: r.agentLabel || UNKNOWN_AGENT_LABEL,
142
+ views: Number(r.views ?? 0),
143
+ lastSeenAt: r.lastSeenAt,
144
+ }));
145
+ }
@@ -31,6 +31,7 @@ import {
31
31
  } from "../../shared/transcript-segments.js";
32
32
  import { resolveTranscriptPresentation } from "../../shared/transcript-status.js";
33
33
  import { getDb, schema } from "../db/index.js";
34
+ import { recordAgentView } from "./agent-views.js";
34
35
  import { verifySharePassword } from "./share-password.js";
35
36
 
36
37
  export type PublicAgentRecording = typeof schema.recordings._.inferSelect;
@@ -290,6 +291,12 @@ export async function loadPublicAgentAccess(
290
291
  }
291
292
  }
292
293
 
294
+ // Every agent API route funnels through here, so this is the one place that
295
+ // sees an outside agent read a clip. Owner requests are previews, not views.
296
+ if (!viewerIsOwner) {
297
+ await recordAgentView(event, recording.id);
298
+ }
299
+
293
300
  return {
294
301
  ok: true,
295
302
  access: {
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Explicit share-grant lookup for the direct `/r/:id` recording page.
3
+ *
4
+ * Lives beside `recording-page-access.ts` rather than inside it so that module
5
+ * stays free of database imports and their registration side effects. Every
6
+ * surface that decides whether a viewer may open the authenticated recording
7
+ * page — the player action and the public share endpoint that auto-redirects
8
+ * into it — must use this helper, or the share page and the page it redirects
9
+ * to will disagree and ping-pong.
10
+ */
11
+
12
+ import {
13
+ getRequestOrgId,
14
+ getRequestUserEmail,
15
+ } from "@agent-native/core/server/request-context";
16
+ import { and, eq, or, sql } from "drizzle-orm";
17
+
18
+ import { getDb, schema } from "../db/index.js";
19
+ import type { RecordingPageAccessRole } from "./recording-page-access.js";
20
+ import type { RecordingVisibility } from "./recordings.js";
21
+
22
+ export interface RecordingShareGrantInput {
23
+ recordingId: string;
24
+ role: RecordingPageAccessRole;
25
+ visibility: RecordingVisibility;
26
+ hasPassword: boolean;
27
+ /** Agent/MCP/A2A callers, which may open password-less public clips. */
28
+ isAgentCaller?: boolean;
29
+ /** Omit to read the ambient request context. */
30
+ userEmail?: string | null;
31
+ /** Omit to read the ambient request context. */
32
+ orgId?: string | null;
33
+ }
34
+
35
+ export async function hasExplicitRecordingShare(
36
+ input: RecordingShareGrantInput,
37
+ ): Promise<boolean> {
38
+ if (input.role === "owner") return true;
39
+ if (input.visibility !== "public") return false;
40
+ if (!input.hasPassword && input.isAgentCaller) return true;
41
+
42
+ const userEmail = (
43
+ input.userEmail === undefined ? getRequestUserEmail() : input.userEmail
44
+ )
45
+ ?.trim()
46
+ .toLowerCase();
47
+ const orgId = input.orgId === undefined ? getRequestOrgId() : input.orgId;
48
+
49
+ const principals = [];
50
+ if (userEmail) {
51
+ principals.push(
52
+ and(
53
+ eq(schema.recordingShares.principalType, "user"),
54
+ sql`lower(${schema.recordingShares.principalId}) = ${userEmail}`,
55
+ ),
56
+ );
57
+ }
58
+ if (orgId) {
59
+ principals.push(
60
+ and(
61
+ eq(schema.recordingShares.principalType, "org"),
62
+ eq(schema.recordingShares.principalId, orgId),
63
+ ),
64
+ );
65
+ }
66
+ if (principals.length === 0) return false;
67
+
68
+ const [share] = await getDb()
69
+ .select({ id: schema.recordingShares.id })
70
+ .from(schema.recordingShares)
71
+ .where(
72
+ and(
73
+ eq(schema.recordingShares.resourceId, input.recordingId),
74
+ or(...principals),
75
+ ),
76
+ )
77
+ .limit(1);
78
+ return Boolean(share);
79
+ }
@@ -1,11 +1,11 @@
1
1
  import { readAppState } from "@agent-native/core/application-state";
2
- import { orgMembers } from "@agent-native/core/org";
2
+ import { implicitServiceOrgRole, orgMembers } from "@agent-native/core/org";
3
3
  import { getSession } from "@agent-native/core/server";
4
4
  import {
5
5
  getRequestUserEmail,
6
6
  getRequestOrgId,
7
7
  } from "@agent-native/core/server/request-context";
8
- import { and, desc, eq, sql } from "drizzle-orm";
8
+ import { and, count, desc, eq, sql } from "drizzle-orm";
9
9
  import { HTTPError, type H3Event } from "h3";
10
10
 
11
11
  import { getDb, schema } from "../db/index.js";
@@ -142,7 +142,11 @@ export async function getOrganizationRoleForEmail(
142
142
  // org_members table may not exist yet on first boot before migrations finish.
143
143
  }
144
144
 
145
- return null;
145
+ return implicitServiceOrgRole({
146
+ email,
147
+ orgId: organizationId,
148
+ requestOrgId: getRequestOrgId(),
149
+ });
146
150
  }
147
151
 
148
152
  export async function requireOrganizationAccess(
@@ -362,3 +366,47 @@ export function shouldCountView(
362
366
  ): boolean {
363
367
  return totalWatchMs >= 5000 || completedPct >= 75 || scrubbedToEnd;
364
368
  }
369
+
370
+ /**
371
+ * The single definition of a counted *viewer*: one `recording_viewers` row
372
+ * whose `countedView` flag is set. That is one row per person, so it answers
373
+ * "how many distinct viewers", not "how many views" — use
374
+ * `countRecordingViews` for the total. The in-memory twin is
375
+ * `isCountedViewerRow` in `shared/view-analytics.ts`.
376
+ */
377
+ export function countedViewCondition() {
378
+ return eq(schema.recordingViewers.countedView, true);
379
+ }
380
+
381
+ /**
382
+ * Total views for a recording: one per counted view *session*, so a returning
383
+ * viewer's second visit counts again. Every surface that reports a view count
384
+ * (library list, insights, player, public share page) goes through this.
385
+ */
386
+ export async function countRecordingViews(
387
+ recordingId: string,
388
+ ): Promise<number> {
389
+ const db = getDb();
390
+ const [viewerRow] = await db
391
+ .select({ value: count() })
392
+ .from(schema.recordingViewers)
393
+ .where(
394
+ and(
395
+ eq(schema.recordingViewers.recordingId, recordingId),
396
+ countedViewCondition(),
397
+ ),
398
+ );
399
+ const [viewLogRow] = await db
400
+ .select({ value: count() })
401
+ .from(schema.recordingViews)
402
+ .where(eq(schema.recordingViews.recordingId, recordingId));
403
+
404
+ // `recording_views` only exists from migration v46, so clips recorded before
405
+ // it have zero log rows. Floor the total at the counted-viewer count so those
406
+ // clips keep reporting a real number instead of dropping to 0, and so the
407
+ // total can never read below the unique-viewer count beside it.
408
+ return Math.max(
409
+ Number(viewLogRow?.value ?? 0),
410
+ Number(viewerRow?.value ?? 0),
411
+ );
412
+ }
@@ -850,6 +850,29 @@ const migrations = runMigrations(
850
850
  `UPDATE organization_settings SET default_visibility = 'public' WHERE default_visibility = 'private' AND updated_at = created_at`,
851
851
  ].join("; "),
852
852
  },
853
+ // -------------------------------------------------------------------------
854
+ // Agent views — external agents polling a public clip's agent context,
855
+ // transcript, or frame APIs. Kept in its own table so human view counts
856
+ // cannot accidentally include agents.
857
+ // -------------------------------------------------------------------------
858
+ {
859
+ version: 51,
860
+ name: "recording-agent-views",
861
+ sql: [
862
+ `CREATE TABLE IF NOT EXISTS recording_agent_views (
863
+ id TEXT PRIMARY KEY,
864
+ recording_id TEXT NOT NULL,
865
+ agent_key TEXT NOT NULL,
866
+ agent_label TEXT,
867
+ view_session_id TEXT NOT NULL,
868
+ first_seen_at TEXT NOT NULL DEFAULT (datetime('now')),
869
+ last_seen_at TEXT NOT NULL DEFAULT (datetime('now')),
870
+ request_count INTEGER NOT NULL DEFAULT 1
871
+ )`,
872
+ `CREATE UNIQUE INDEX IF NOT EXISTS recording_agent_views_session_unique_idx ON recording_agent_views (recording_id, agent_key, view_session_id)`,
873
+ `CREATE INDEX IF NOT EXISTS recording_agent_views_recording_idx ON recording_agent_views (recording_id, last_seen_at)`,
874
+ ].join("; "),
875
+ },
853
876
  ],
854
877
  { table: "clips_migrations" },
855
878
  );
@@ -46,8 +46,16 @@ import { isMediaVerificationPending } from "../../lib/media-verification-state.j
46
46
  import { resolvePlayerThumbnailUrl } from "../../lib/player-thumbnail-url.js";
47
47
  import { resolvePlayerVideoUrl } from "../../lib/player-video-url.js";
48
48
  import {
49
+ canOpenDirectRecordingPage,
50
+ isRecordingExpired,
51
+ type RecordingPageAccessRole,
52
+ } from "../../lib/recording-page-access.js";
53
+ import { hasExplicitRecordingShare } from "../../lib/recording-share-grant.js";
54
+ import {
55
+ countRecordingViews,
49
56
  getOrganizationRoleForEmail,
50
57
  parseSpaceIds,
58
+ type RecordingVisibility,
51
59
  } from "../../lib/recordings.js";
52
60
  import { verifySharePassword } from "../../lib/share-password.js";
53
61
 
@@ -258,12 +266,10 @@ export default defineEventHandler(async (event) => {
258
266
  }
259
267
 
260
268
  // Expiry check
261
- if (rec.expiresAt) {
262
- const expires = new Date(rec.expiresAt).getTime();
263
- if (isFinite(expires) && expires < Date.now()) {
264
- setResponseStatus(event, 410);
265
- return { error: "Recording has expired", expired: true };
266
- }
269
+ const recordingExpired = isRecordingExpired(rec.expiresAt);
270
+ if (recordingExpired) {
271
+ setResponseStatus(event, 410);
272
+ return { error: "Recording has expired", expired: true };
267
273
  }
268
274
 
269
275
  // Password check
@@ -405,6 +411,32 @@ export default defineEventHandler(async (event) => {
405
411
  recordingStatus: rec.status,
406
412
  });
407
413
 
414
+ const viewCount = await countRecordingViews(recordingId);
415
+
416
+ const viewerRole =
417
+ viewerAccess?.role ?? (viewerIsOrgMember ? "viewer" : null);
418
+ // Mirrors the gate in `get-recording-player-data` exactly: the share page
419
+ // auto-redirects on this flag, so a false positive bounces the viewer
420
+ // between /share/:id and /r/:id forever. Only a resolved access role can
421
+ // open the direct page — the org-member fallback above is a display role,
422
+ // not access the player action would grant.
423
+ const canOpenDashboard =
424
+ Boolean(session?.email) && viewerAccess && !recordingExpired
425
+ ? canOpenDirectRecordingPage({
426
+ role: viewerAccess.role as RecordingPageAccessRole,
427
+ visibility: rec.visibility as RecordingVisibility,
428
+ hasPassword: Boolean(rec.password),
429
+ hasExplicitShare: await hasExplicitRecordingShare({
430
+ recordingId,
431
+ role: viewerAccess.role as RecordingPageAccessRole,
432
+ visibility: rec.visibility as RecordingVisibility,
433
+ hasPassword: Boolean(rec.password),
434
+ userEmail: session?.email ?? null,
435
+ orgId: session?.orgId ?? null,
436
+ }),
437
+ })
438
+ : false;
439
+
408
440
  return {
409
441
  recording: {
410
442
  id: rec.id,
@@ -439,6 +471,8 @@ export default defineEventHandler(async (event) => {
439
471
  updatedAt: rec.updatedAt,
440
472
  },
441
473
  agentContextUrl,
474
+ // Aggregate count only — never viewer identities on this public payload.
475
+ viewCount,
442
476
  transcript: transcript
443
477
  ? {
444
478
  status: transcriptPresentation.status,
@@ -479,17 +513,15 @@ export default defineEventHandler(async (event) => {
479
513
  placement: c.placement,
480
514
  })),
481
515
  viewer: session?.email
482
- ? (() => {
483
- const role =
484
- viewerAccess?.role ?? (viewerIsOrgMember ? "viewer" : null);
485
- const canEdit =
486
- role === "owner" || role === "admin" || role === "editor";
487
- return {
488
- canEdit,
489
- isOwner: role === "owner",
490
- role: role ?? "viewer",
491
- };
492
- })()
516
+ ? {
517
+ canEdit:
518
+ viewerRole === "owner" ||
519
+ viewerRole === "admin" ||
520
+ viewerRole === "editor",
521
+ isOwner: viewerRole === "owner",
522
+ role: viewerRole ?? "viewer",
523
+ canOpenDashboard,
524
+ }
493
525
  : null,
494
526
  };
495
527
  });
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Canonical share URL for a recording, shared by every surface that hands a
3
+ * user a link to paste: the web recorder, the desktop app, and the Chrome
4
+ * extension.
5
+ *
6
+ * `/share/<id>` is the public viewer page — SSR-rendered, password/expiry
7
+ * aware, and the shape Slack unfurls. `/r/<id>` is the owner dashboard: it
8
+ * renders client-side only and shows a sign-in prompt to a recipient. Copying
9
+ * `/r/<id>` produces a link that works for the author and looks broken to
10
+ * everyone they send it to, so share flows must build URLs from here rather
11
+ * than hand-rolling a path.
12
+ */
13
+ import { withShareAttribution } from "./share-attribution";
14
+
15
+ /** Public share path for a recording, relative to the app base path. */
16
+ export function recordingSharePath(recordingId: string): string {
17
+ return `/share/${encodeURIComponent(recordingId)}`;
18
+ }
19
+
20
+ export interface RecordingShareUrlParams {
21
+ recordingId: string;
22
+ /** Absolute origin, e.g. `https://clips.example.com` or a desktop serverUrl. */
23
+ origin: string;
24
+ /** App base path when the app is mounted under a subpath. */
25
+ basePath?: string;
26
+ /** Non-PII owner id for viral attribution. Omitted when unknown. */
27
+ ownerId?: string | null;
28
+ }
29
+
30
+ /**
31
+ * Absolute, ready-to-paste share URL for a recording, carrying the same
32
+ * attribution params the Share dialog mints so auto-copied links measure the
33
+ * signup funnel identically.
34
+ */
35
+ export function buildRecordingShareUrl(
36
+ params: RecordingShareUrlParams,
37
+ ): string {
38
+ const { recordingId, origin, basePath = "", ownerId } = params;
39
+ const trimmedOrigin = origin.trim().replace(/\/+$/, "");
40
+ const trimmedBase = basePath.trim().replace(/\/+$/, "");
41
+ const url = `${trimmedOrigin}${trimmedBase}${recordingSharePath(recordingId)}`;
42
+ return withShareAttribution(url, ownerId);
43
+ }
@@ -21,6 +21,10 @@ export const CLIP_SHARE_REF = "clip_share";
21
21
  export const REF_PARAM = "ref";
22
22
  export const VIA_PARAM = "via";
23
23
 
24
+ /** Marker used only when `/r/:id` falls back to the public share route. */
25
+ export const DASHBOARD_REDIRECT_PARAM = "dashboard_redirect";
26
+ export const DASHBOARD_REDIRECT_VALUE = "1";
27
+
24
28
  /**
25
29
  * Append `ref=clip_share` (and `via=<ownerId>` when a non-PII owner id is
26
30
  * known) to an absolute share/embed URL, preserving any existing query params.
@@ -0,0 +1,42 @@
1
+ import {
2
+ DASHBOARD_REDIRECT_PARAM,
3
+ DASHBOARD_REDIRECT_VALUE,
4
+ } from "./share-attribution.js";
5
+
6
+ /** Query params worth carrying from /share/:id over to /r/:id. */
7
+ const FORWARDED_PARAMS = ["t", "panel"] as const;
8
+
9
+ export interface DashboardRedirectInput {
10
+ recordingId: string | null | undefined;
11
+ /**
12
+ * The server's `canOpenDirectRecordingPage` verdict, not a display role.
13
+ * /r sends anyone it rejects back to /share, so a looser predicate here
14
+ * bounces the viewer between the two routes forever.
15
+ */
16
+ canOpenDashboard: boolean;
17
+ /** Current location search string, with or without the leading `?`. */
18
+ search: string;
19
+ }
20
+
21
+ /**
22
+ * Where a share-link visitor should be sent instead of rendering /share/:id,
23
+ * or null to stay put.
24
+ */
25
+ export function resolveDashboardRedirect(
26
+ input: DashboardRedirectInput,
27
+ ): string | null {
28
+ if (!input.canOpenDashboard || !input.recordingId) return null;
29
+
30
+ const params = new URLSearchParams(input.search);
31
+ if (params.get(DASHBOARD_REDIRECT_PARAM) === DASHBOARD_REDIRECT_VALUE)
32
+ return null;
33
+
34
+ const forwarded = new URLSearchParams();
35
+ for (const key of FORWARDED_PARAMS) {
36
+ const value = params.get(key);
37
+ if (value) forwarded.set(key, value);
38
+ }
39
+
40
+ const query = forwarded.toString();
41
+ return `/r/${encodeURIComponent(input.recordingId)}${query ? `?${query}` : ""}`;
42
+ }
@@ -2,3 +2,29 @@ export function clampCompletionPct(value: number | null | undefined): number {
2
2
  if (typeof value !== "number" || !Number.isFinite(value)) return 0;
3
3
  return Math.min(100, Math.max(0, value));
4
4
  }
5
+
6
+ /**
7
+ * In-memory twin of `countedViewCondition` / `countRecordingViews` in
8
+ * `server/lib/recordings.ts`: a view is one `recording_viewers` row whose
9
+ * `countedView` flag is set. Use it whenever the rows are already loaded so
10
+ * every surface reports the same number.
11
+ */
12
+ export function isCountedViewerRow(row: { countedView?: unknown }): boolean {
13
+ return Boolean(row.countedView);
14
+ }
15
+
16
+ export const ANONYMOUS_VIEWER_NAME_PREFIX = "anon:";
17
+
18
+ /**
19
+ * Anonymous rows keep their `anon:<sessionId>` dedup key in `viewer_name`.
20
+ * That key is storage plumbing, never a display name — return null so callers
21
+ * render their own "Someone" placeholder instead of leaking the session key.
22
+ */
23
+ export function displayViewerName(
24
+ viewerName: string | null | undefined,
25
+ ): string | null {
26
+ if (viewerName == null) return null;
27
+ return viewerName.startsWith(ANONYMOUS_VIEWER_NAME_PREFIX)
28
+ ? null
29
+ : viewerName;
30
+ }
@@ -237,6 +237,11 @@ File access still requires a local Content app, Agent Native Desktop, or another
237
237
  trusted bridge. Disconnecting a folder leaves the SQL pages and disk files in
238
238
  place. Concurrent edits and missing source files require explicit review.
239
239
 
240
+ See **`references/local-file-mode.md`** for the full folder-source Pull/Check/Push
241
+ workflow, the manifest/CLI launch commands, Builder Symbols and Builder
242
+ source-component preservation rules, picked-folder/Desktop component
243
+ previews, and the agent component-edit workflow.
244
+
240
245
  ## MDX And Components
241
246
 
242
247
  - Preserve frontmatter keys you do not understand. Preserve MDX imports,