@agent-native/core-corpus 0.210.0-nightly-20261019153056 → 0.210.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 (182) hide show
  1. package/LICENSE +21 -0
  2. package/corpus/README.md +1 -1
  3. package/corpus/templates/analytics/.agents/skills/actions/SKILL.md +2 -2
  4. package/corpus/templates/analytics/.agents/skills/data-querying/SKILL.md +24 -1
  5. package/corpus/templates/analytics/CHANGELOG.md +71 -0
  6. package/corpus/templates/analytics/actions/configure-first-party-events-model.ts +161 -0
  7. package/corpus/templates/analytics/actions/get-first-party-analytics-backend.ts +59 -0
  8. package/corpus/templates/analytics/actions/get-first-party-analytics-health.ts +1 -1
  9. package/corpus/templates/analytics/actions/list-event-catalog.ts +1 -1
  10. package/corpus/templates/analytics/actions/list-session-event-names.ts +1 -1
  11. package/corpus/templates/analytics/actions/list-session-recordings.ts +1 -1
  12. package/corpus/templates/analytics/actions/query-agent-native-analytics.ts +37 -15
  13. package/corpus/templates/analytics/app/i18n/en-US.ts +0 -4
  14. package/corpus/templates/analytics/app/pages/SemanticLayer.tsx +23 -70
  15. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/RowBreakdownSheet.tsx +16 -6
  16. package/corpus/templates/analytics/changelog/2026-10-16-analytics-can-now-count-its-own-first-party-events-strictly-.md +6 -0
  17. package/corpus/templates/analytics/changelog/2026-10-16-first-party-charts-can-read-a-scheduled-warehouse-events-mod.md +6 -0
  18. package/corpus/templates/analytics/changelog/2026-10-19-keep-warm-is-opt-in.md +6 -0
  19. package/corpus/templates/analytics/changelog/2026-10-19-the-agent-sees-the-row-breakdown-open-in-your-current-browse.md +6 -0
  20. package/corpus/templates/analytics/netlify.toml +0 -1
  21. package/corpus/templates/analytics/server/lib/analytics-alerts.ts +9 -2
  22. package/corpus/templates/analytics/server/lib/first-party-analytics-backend.ts +413 -38
  23. package/corpus/templates/analytics/server/lib/first-party-analytics.ts +106 -22
  24. package/corpus/templates/analytics/shared/sql-query-contract.ts +21 -0
  25. package/corpus/templates/assets/.agents/skills/actions/SKILL.md +2 -2
  26. package/corpus/templates/assets/CHANGELOG.md +6 -0
  27. package/corpus/templates/assets/changelog/2026-10-19-keep-warm-is-opt-in.md +6 -0
  28. package/corpus/templates/assets/netlify.toml +0 -1
  29. package/corpus/templates/brain/.agents/skills/actions/SKILL.md +2 -2
  30. package/corpus/templates/brain/CHANGELOG.md +10 -0
  31. package/corpus/templates/brain/netlify.toml +0 -1
  32. package/corpus/templates/calendar/.agents/skills/actions/SKILL.md +2 -2
  33. package/corpus/templates/calendar/CHANGELOG.md +6 -0
  34. package/corpus/templates/calendar/changelog/2026-10-19-keep-warm-is-opt-in.md +6 -0
  35. package/corpus/templates/calendar/netlify.toml +0 -1
  36. package/corpus/templates/chat/.agents/skills/actions/SKILL.md +2 -2
  37. package/corpus/templates/chat/netlify.toml +0 -1
  38. package/corpus/templates/clips/.agents/skills/actions/SKILL.md +2 -2
  39. package/corpus/templates/clips/.agents/skills/ai-video-tools/SKILL.md +10 -5
  40. package/corpus/templates/clips/.agents/skills/recording/SKILL.md +2 -0
  41. package/corpus/templates/clips/.agents/skills/video-sharing/SKILL.md +11 -2
  42. package/corpus/templates/clips/CHANGELOG.md +78 -24
  43. package/corpus/templates/clips/actions/apply-rewind-extension.ts +84 -24
  44. package/corpus/templates/clips/actions/clear-edits.ts +14 -10
  45. package/corpus/templates/clips/actions/create-recording-upload.ts +8 -4
  46. package/corpus/templates/clips/actions/create-recording.ts +1 -0
  47. package/corpus/templates/clips/actions/delete-recording-permanent.ts +3 -0
  48. package/corpus/templates/clips/actions/get-recording-player-data.ts +8 -0
  49. package/corpus/templates/clips/actions/import-transcript.ts +83 -51
  50. package/corpus/templates/clips/actions/lib/create-recording-schema.ts +10 -0
  51. package/corpus/templates/clips/actions/request-transcript.ts +95 -52
  52. package/corpus/templates/clips/actions/restore-transcript-version.ts +177 -0
  53. package/corpus/templates/clips/app/components/player/clip-agent-webmcp.tsx +111 -7
  54. package/corpus/templates/clips/app/components/player/transcript-history-button.tsx +121 -0
  55. package/corpus/templates/clips/app/components/player/transcript-panel.tsx +30 -3
  56. package/corpus/templates/clips/app/i18n/en-US.ts +8 -0
  57. package/corpus/templates/clips/app/routes/_app.r.$recordingId.tsx +58 -0
  58. package/corpus/templates/clips/changelog/2026-10-15-the-desktop-camera-bubble-can-remove-your-background-and-let.md +6 -0
  59. package/corpus/templates/clips/changelog/2026-10-16-agent-uploads-over-the-hosted-size-limit-are-refused-up-front.md +6 -0
  60. package/corpus/templates/clips/changelog/2026-10-16-agents-connected-over-mcp-can-import-transcript-files.md +6 -0
  61. package/corpus/templates/clips/changelog/2026-10-16-replacing-a-transcript-by-importing-a-file-or-regenerating-i.md +6 -0
  62. package/corpus/templates/clips/changelog/2026-10-17-agents-can-watch-an-animation-in-one-frame-sheet.md +6 -0
  63. package/corpus/templates/clips/changelog/2026-10-17-desktop-recordings-include-balanced-audio-and-compress-large.md +6 -0
  64. package/corpus/templates/clips/changelog/2026-10-19-keep-warm-is-opt-in.md +6 -0
  65. package/corpus/templates/clips/changelog/2026-10-19-screen-memory-transcribes-without-a-system-ffmpeg-install.md +6 -0
  66. package/corpus/templates/clips/desktop/package.json +6 -4
  67. package/corpus/templates/clips/desktop/scripts/prepare-ffmpeg-sidecar.ts +375 -0
  68. package/corpus/templates/clips/desktop/src/app.tsx +13 -0
  69. package/corpus/templates/clips/desktop/src/lib/bubble-hit-mask.ts +231 -0
  70. package/corpus/templates/clips/desktop/src/lib/camera-composite.ts +115 -2
  71. package/corpus/templates/clips/desktop/src/lib/camera-cutout-preference.ts +81 -0
  72. package/corpus/templates/clips/desktop/src/lib/person-cutout.ts +758 -0
  73. package/corpus/templates/clips/desktop/src/lib/recorder.ts +6 -0
  74. package/corpus/templates/clips/desktop/src/overlays/bubble.tsx +252 -2
  75. package/corpus/templates/clips/desktop/src/styles.css +8 -0
  76. package/corpus/templates/clips/desktop/src-tauri/src/clips/mod.rs +258 -5
  77. package/corpus/templates/clips/desktop/src-tauri/src/lib.rs +6 -0
  78. package/corpus/templates/clips/desktop/src-tauri/src/native_screen.rs +139 -9
  79. package/corpus/templates/clips/desktop/src-tauri/src/screen_memory_transcript.rs +1 -31
  80. package/corpus/templates/clips/desktop/src-tauri/tauri.macos.conf.json +6 -0
  81. package/corpus/templates/clips/netlify.toml +0 -1
  82. package/corpus/templates/clips/server/db/schema.ts +30 -0
  83. package/corpus/templates/clips/server/lib/agent-frame-request.ts +224 -0
  84. package/corpus/templates/clips/server/lib/public-agent-context.ts +25 -1
  85. package/corpus/templates/clips/server/lib/transcript-versions.ts +116 -0
  86. package/corpus/templates/clips/server/lib/video-frame-sheet.ts +313 -0
  87. package/corpus/templates/clips/server/lib/video-frame.ts +102 -0
  88. package/corpus/templates/clips/server/lib/video-storage.ts +24 -0
  89. package/corpus/templates/clips/server/plugins/auth.ts +1 -0
  90. package/corpus/templates/clips/server/plugins/db.ts +21 -0
  91. package/corpus/templates/clips/server/routes/api/agent-frame.jpg.get.ts +12 -185
  92. package/corpus/templates/clips/server/routes/api/agent-frames.jpg.get.ts +194 -0
  93. package/corpus/templates/clips/shared/agent-context.ts +69 -1
  94. package/corpus/templates/clips/shared/transcript-segments.ts +39 -6
  95. package/corpus/templates/clips/shared/upload-limits.ts +15 -0
  96. package/corpus/templates/content/.agents/skills/actions/SKILL.md +2 -2
  97. package/corpus/templates/content/CHANGELOG.md +24 -0
  98. package/corpus/templates/content/app/components/editor/BubbleToolbar.tsx +5 -3
  99. package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +4 -2
  100. package/corpus/templates/content/app/components/editor/DocumentToolbar.tsx +1 -1
  101. package/corpus/templates/content/app/components/editor/LinkHoverPreview.tsx +1 -1
  102. package/corpus/templates/content/app/components/editor/TableHoverControls.tsx +6 -6
  103. package/corpus/templates/content/app/components/editor/VisualEditor.tsx +9 -9
  104. package/corpus/templates/content/app/components/editor/comment-lane.ts +1 -1
  105. package/corpus/templates/content/app/components/editor/extensions/AudioBlock.tsx +1 -1
  106. package/corpus/templates/content/app/components/editor/extensions/CodeBlockNode.tsx +1 -1
  107. package/corpus/templates/content/app/components/editor/extensions/ImageBlock.tsx +1 -1
  108. package/corpus/templates/content/app/components/editor/extensions/NotionExtensions.tsx +24 -24
  109. package/corpus/templates/content/app/components/editor/extensions/NotionFidelity.ts +3 -3
  110. package/corpus/templates/content/app/components/editor/extensions/VideoBlock.tsx +1 -1
  111. package/corpus/templates/content/app/global.css +324 -280
  112. package/corpus/templates/content/app/root.tsx +1 -1
  113. package/corpus/templates/content/app/routes/p.$id.tsx +3 -1
  114. package/corpus/templates/content/changelog/2026-10-18-block-text-and-background-colors-use-content-s-own-palette-a.md +6 -0
  115. package/corpus/templates/content/changelog/2026-10-19-keep-warm-is-opt-in.md +6 -0
  116. package/corpus/templates/content/e2e/helpers.ts +1 -1
  117. package/corpus/templates/content/netlify.toml +0 -1
  118. package/corpus/templates/content/shared/content-editor-structural-schema.generated.json +1 -1
  119. package/corpus/templates/content/shared/nfm-export-html.ts +9 -9
  120. package/corpus/templates/crm/.agents/skills/actions/SKILL.md +2 -2
  121. package/corpus/templates/design/.agents/skills/actions/SKILL.md +2 -2
  122. package/corpus/templates/design/CHANGELOG.md +155 -1
  123. package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +2 -107
  124. package/corpus/templates/design/app/components/design/multi-screen/canvas-tools.ts +0 -8
  125. package/corpus/templates/design/app/components/design/multi-screen/types.ts +0 -1
  126. package/corpus/templates/design/app/i18n/en-US.ts +0 -1
  127. package/corpus/templates/design/app/pages/design-editor/domains/use-editor-layer-actions.ts +0 -14
  128. package/corpus/templates/design/app/pages/design-editor/view/overview-canvas.tsx +0 -2
  129. package/corpus/templates/design/changelog/2026-10-19-keep-warm-is-opt-in.md +6 -0
  130. package/corpus/templates/design/changelog/2026-10-19-screen-overviews-no-longer-show-focus-screen-buttons-on-scre.md +6 -0
  131. package/corpus/templates/design/e2e/helpers.ts +10 -14
  132. package/corpus/templates/design/e2e/rails.ts +3 -0
  133. package/corpus/templates/design/netlify.toml +0 -1
  134. package/corpus/templates/dispatch/.agents/skills/actions/SKILL.md +2 -2
  135. package/corpus/templates/dispatch/CHANGELOG.md +6 -0
  136. package/corpus/templates/dispatch/changelog/2026-10-19-keep-warm-is-opt-in.md +6 -0
  137. package/corpus/templates/dispatch/netlify.toml +0 -1
  138. package/corpus/templates/factory/.agents/skills/actions/SKILL.md +2 -2
  139. package/corpus/templates/factory/.agents/skills/review-prs/SKILL.md +44 -21
  140. package/corpus/templates/factory/CHANGELOG.md +4 -0
  141. package/corpus/templates/forms/.agents/skills/actions/SKILL.md +2 -2
  142. package/corpus/templates/forms/netlify.toml +0 -1
  143. package/corpus/templates/mail/.agents/skills/actions/SKILL.md +2 -2
  144. package/corpus/templates/mail/CHANGELOG.md +12 -0
  145. package/corpus/templates/mail/changelog/2026-10-19-keep-warm-is-opt-in.md +6 -0
  146. package/corpus/templates/mail/netlify.toml +0 -1
  147. package/corpus/templates/plan/.agents/skills/actions/SKILL.md +2 -2
  148. package/corpus/templates/plan/CHANGELOG.md +6 -0
  149. package/corpus/templates/plan/changelog/2026-10-19-keep-warm-is-opt-in.md +6 -0
  150. package/corpus/templates/plan/netlify.toml +0 -1
  151. package/corpus/templates/slides/.agents/skills/actions/SKILL.md +2 -2
  152. package/corpus/templates/slides/CHANGELOG.md +24 -0
  153. package/corpus/templates/slides/app/components/editor/DeckPrivacyBadge.tsx +28 -0
  154. package/corpus/templates/slides/app/components/editor/DeckShareAgentTab.tsx +43 -0
  155. package/corpus/templates/slides/app/components/editor/EditorToolbar.tsx +44 -70
  156. package/corpus/templates/slides/app/components/editor/ExportMenu.tsx +65 -2
  157. package/corpus/templates/slides/app/components/editor/PresentMenu.tsx +126 -0
  158. package/corpus/templates/slides/app/components/presentation/PresentationView.tsx +10 -3
  159. package/corpus/templates/slides/app/hooks/use-deck-visibility.ts +21 -0
  160. package/corpus/templates/slides/app/i18n/en-US.ts +15 -2
  161. package/corpus/templates/slides/app/lib/deck-present-url.ts +32 -0
  162. package/corpus/templates/slides/app/pages/DeckEditor.tsx +45 -8
  163. package/corpus/templates/slides/app/pages/Presentation.tsx +1 -0
  164. package/corpus/templates/slides/changelog/2026-10-16-the-top-bar-shows-a-deck-s-privacy-next-to-its-name-share-ha.md +6 -0
  165. package/corpus/templates/slides/changelog/2026-10-19-keep-warm-is-opt-in.md +6 -0
  166. package/corpus/templates/slides/netlify.toml +0 -1
  167. package/corpus/templates/tasks/.agents/skills/actions/SKILL.md +2 -2
  168. package/package.json +1 -1
  169. package/corpus/templates/analytics/.deploy-tmp/agent-native-provided-plugins.mjs +0 -10
  170. package/corpus/templates/assets/.deploy-tmp/agent-native-provided-plugins.mjs +0 -10
  171. package/corpus/templates/brain/.deploy-tmp/agent-native-provided-plugins.mjs +0 -10
  172. package/corpus/templates/calendar/.deploy-tmp/agent-native-provided-plugins.mjs +0 -10
  173. package/corpus/templates/chat/.deploy-tmp/agent-native-provided-plugins.mjs +0 -10
  174. package/corpus/templates/clips/.deploy-tmp/agent-native-provided-plugins.mjs +0 -10
  175. package/corpus/templates/content/.deploy-tmp/agent-native-provided-plugins.mjs +0 -10
  176. package/corpus/templates/crm/.deploy-tmp/agent-native-provided-plugins.mjs +0 -10
  177. package/corpus/templates/design/.deploy-tmp/agent-native-provided-plugins.mjs +0 -10
  178. package/corpus/templates/dispatch/.deploy-tmp/agent-native-provided-plugins.mjs +0 -10
  179. package/corpus/templates/forms/.deploy-tmp/agent-native-provided-plugins.mjs +0 -10
  180. package/corpus/templates/mail/.deploy-tmp/agent-native-provided-plugins.mjs +0 -10
  181. package/corpus/templates/plan/.deploy-tmp/agent-native-provided-plugins.mjs +0 -10
  182. package/corpus/templates/slides/.deploy-tmp/agent-native-provided-plugins.mjs +0 -10
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Builder.io, Inc.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/corpus/README.md CHANGED
@@ -32,4 +32,4 @@ rg -n "defineAction|useActionQuery" node_modules/@agent-native/core-corpus/corpu
32
32
 
33
33
  ## Generated Counts
34
34
 
35
- - template files: 12906
35
+ - template files: 12899
@@ -170,10 +170,10 @@ Everything else — CRUD, settings, search, list/detail reads, auth state, anyth
170
170
  - **Do** keep one action, one job; document a reusable action (when to use it, key args, return fields to preserve) in `AGENTS.md` once it's called from outside one narrow screen; promote workflow-heavy actions (provider-backed, cross-app, MCP/A2A, multi-step) into a skill.
171
171
  - **Do** use `fail(message, { errorCode, statusCode })` for user-friendly errors and import primitives from `@agent-native/core`(`/action`) instead of redefining them; use the core `upload-image` action or `uploadFile()` for durable images/files — never base64 into SQL, markdown, or action results.
172
172
  - **Do** signal failure by throwing, using `fail()` for expected caller-readable failures. Never return `{ error: ... }` as a failure result: a normal return is treated as a successful action. Reserve bare errors for unexpected internal faults.
173
- - **Do** use `fail()` rather than a bare `Error` for caller-readable failures. It carries the action's message, `errorCode`, and `details` to the browser; unexpected errors become a generic 500. Reserve bare throws for internal faults.
173
+ - **Do** use `fail()` rather than a bare `Error` for caller-readable failures. It carries the action's message, `errorCode`, and `details` to the browser and to agents; unexpected errors become a generic 500. Reserve bare throws for internal faults.
174
174
  - **Do** give `fail()` the status that matches the cause: it defaults to `400`, and `useActionQuery` retries only `429`, `502`, `503`, and `504`. Mutations do not retry; use transient statuses only for retryable failures.
175
175
  - **Do** render `actionErrorMessage(error) ?? yourCopy` in UI, never bare `error.message`. The message keeps an `Action <name> failed:` prefix that belongs in a console, so a toast built from it reads "Action update-brand-kit failed: That name is taken." The helper returns only what the action wrote, and `undefined` when nothing did (network drop, proxy HTML page), which is why the fallback is not optional.
176
- - **Do** pass a real `errorCode` when the agent should branch on the failure rather than re-read it. Codes other than the default `action_failed` are appended to the tool result as `(errorCode: not_found)`, on both the in-app agent and MCP; `details` and the status never reach either.
176
+ - **Do** pass a real `errorCode` when the agent should branch on the failure rather than re-read it. Codes other than the default `action_failed` are appended to the tool result as `(errorCode: not_found)`, on both the in-app agent and MCP. `details` follow as an `Error details: {...}` JSON line, and as `structuredContent.error` over MCP, capped at 2,000 characters, so keep them to what the agent acts on next: ids, limits, field names. The status never reaches either.
177
177
  - **Don't** re-export actions as REST — `/_agent-native/actions/:name` is already the REST surface; duplicating it under `/api/*` hides the operation from agents.
178
178
  - **Don't** reach for provider integrations, `outputSchema`, `authorize`, `needsApproval`, or `_agentImages` without checking `references/` first — each has sharp edges covered there, not here.
179
179
 
@@ -79,7 +79,14 @@ exception is the legacy `@app_events` table, which keeps a test identity's
79
79
  `$exception` rows marked `JSON_VALUE(data, '$.test_identity') = 'true'`;
80
80
  exclude those from any metric over it.
81
81
 
82
- Before a large or historical first-party query, call
82
+ When the user rules out BigQuery, skip the health check below, which queries
83
+ BigQuery on a BigQuery-backed scope. Call `get-first-party-analytics-backend`
84
+ to see which tables Postgres serves, then pass `backend: "postgres"` to
85
+ `query-agent-native-analytics`. If it fails with
86
+ `first_party_backend_not_postgres`, this scope reads those tables only from
87
+ BigQuery: say so and let the user decide.
88
+
89
+ Otherwise, before a large or historical first-party query, call
83
90
  `get-first-party-analytics-health`. Keep Neon as the default while its status is
84
91
  `healthy` or `monitor`; a `recommend_bigquery` result means the app has observed
85
92
  1M+ events, repeated slow queries, or a timeout/30-second query. Treat that
@@ -103,6 +110,22 @@ BigQuery `insertId` values for retry-safe writes. After cutover, `/track`
103
110
  writes and event queries use BigQuery; public-key metadata, derived exception
104
111
  issues, and session-replay data remain in SQL.
105
112
 
113
+ After cutover, an org admin can point charts at a scheduled warehouse events
114
+ model with `configure-first-party-events-model`; run `check` first, which
115
+ dry-runs as the app and saves nothing. With a model set, a first-party query
116
+ reads `analytics_events` from the model only when its WHERE filters
117
+ `event_name` to named events (`= 'x'` or `IN (...)`, ANDed, no OR or NOT),
118
+ because the model has no `http.response` rows; its result carries
119
+ `eventsSource` and `sourceGeneration` (the model build it read). Unfiltered
120
+ queries, `SELECT *`, and queries that name `public_key_id` or `context`, read
121
+ `http.response`, or read `properties` other than through `JSON_VALUE`,
122
+ `JSON_EXTRACT_SCALAR`, `JSON_VALUE_ARRAY`, or `JSON_EXTRACT_STRING_ARRAY` stay
123
+ on the raw table, as do alerts and onboarding journeys;
124
+ `eventsSource.modelSkipped` says why. A model result trails the newest events
125
+ by up to one rebuild, so for the last few minutes give the model's age from
126
+ `get-analytics-source-generation` (a metadata read, no query) rather than
127
+ calling the count complete.
128
+
106
129
  Example pageviews query for a local calendar day:
107
130
 
108
131
  ```sql
@@ -3,10 +3,51 @@
3
3
  All notable user-facing changes to Agent-Native Analytics are documented here. Open it any
4
4
  time from the command menu (Cmd+K → "What's new") or from Settings.
5
5
 
6
+ ## 2026-10-19
7
+
8
+ ### Fixed
9
+
10
+ - Dashboard upgrades preserve saved history, custom data sources, and legacy edits.
11
+ - The agent sees the row breakdown open in your current browser tab.
12
+
13
+ ### Changed
14
+
15
+ - Apps no longer keep server functions and databases warm by default; set `AGENT_NATIVE_ENABLE_KEEP_WARM=1` to opt in.
16
+
17
+ ## 2026-10-17
18
+
19
+ ### Added
20
+
21
+ - Dashboard descriptions and custom blocks now open safe Markdown links in a new tab
22
+
23
+ ### Improved
24
+
25
+ - Analytics distinguishes email-domain filters from creator self-view exclusions
26
+ - Analytics validates explicit day-first date ranges before querying dbt metrics
27
+ - Keyboard chart point lists show every visible metric with localized dates and values
28
+
29
+ ### Fixed
30
+
31
+ - Failed chart drilldowns no longer allow exporting stale cached rows
32
+ - Keep chart-point pagination stable and preserve aggregate result columns
33
+ - Keyboard chart controls now describe the chart points they open
34
+
6
35
  ## 2026-10-16
7
36
 
37
+ ### Added
38
+
39
+ - Click a table row to see its numbers broken down by app, or another column, in a side panel.
40
+ - Connect a personal Sigma account to import its dashboards
41
+ - First-party charts can read a scheduled warehouse events model instead of the raw event table, so repeat loads reuse BigQuery's cache between model rebuilds.
42
+
8
43
  ### Improved
9
44
 
45
+ - Analytics preserves requested organization grain and guides supported ratio query retries
46
+ - Charts that compute their window from today's date can now reuse BigQuery's cached results within the day instead of rerunning the full query on every load.
47
+ - Dashboard callouts and descriptions support Markdown links
48
+ - Dashboard filters show your selection immediately while charts update.
49
+ - Dashboard panels can be resized by dragging their edges.
50
+ - Keyboard chart point lists now paginate and show each value
10
51
  - Dashboard freshness stays in the title row, with compact update times on wide cards and a clock on narrow cards.
11
52
  - Analytics preserves metric breakdowns across continued queries and keeps partial totals clearly labeled.
12
53
  - Analytics query errors now explain safe next steps without exposing raw SQL or backend details
@@ -14,6 +55,29 @@ time from the command menu (Cmd+K → "What's new") or from Settings.
14
55
 
15
56
  ### Fixed
16
57
 
58
+ - Alert polls enforce a query byte limit and report evaluations that exceed it as errors.
59
+ - All-time dashboard panels keep their historical label when you edit a title or description
60
+ - Analytics answers owner-defined dbt metrics through the Semantic Layer and say when dbt is not ready, instead of reading warehouse tables as the approved definition.
61
+ - Analytics can now count its own first-party events strictly from Postgres when you rule out BigQuery, and refuses instead of quietly querying BigQuery when the data only lives there
62
+ - Analytics recognizes more natural date range formats, including day-first and same-month shorthand.
63
+ - Chart drilldowns are accessible from the keyboard
64
+ - Clarified dbt setup errors when organization context is unavailable
65
+ - Month and quarter comparisons now stay aligned with their named periods, including requests that use “with”.
66
+ - Dashboard event colors render consistently in email reports
67
+ - Dashboard filters show when navigation temporarily prevents changes and close open menus safely
68
+ - Dashboard panel sizes stay put when multiple edits save together
69
+ - Date-based metric comparisons now honor both requested periods.
70
+ - First-party Analytics health checks bound BigQuery reads and identify recent activity counts.
71
+ - First-party dashboards apply the selected time range earlier to avoid unnecessary BigQuery event scans.
72
+ - Fixed a dashboard crash caused by an invalid panel description.
73
+ - Fixed dashboard panels reverting to stale SQL, repeating query validation while resizing, and lacking a reset to content-based height.
74
+ - Formatted negative numbers stay numeric in CSV exports, and funnel drilldowns remain accessible to screen readers
75
+ - Funnel outcome stages now open drilldown details, and CSV exports preserve negative numeric values.
76
+ - Markdown links in chart descriptions and custom blocks open external pages in a new tab.
77
+ - Organization breakdowns now distinguish a root organization from its individual organizations
78
+ - Ratio recovery preserves fractional results for integer data
79
+ - Resized dashboard panels keep their size through concurrent edits
80
+ - Resizing a dashboard panel keeps its size when other edits are saved.
17
81
  - Explorer flags incomplete query results
18
82
  - Chart drilldowns preserve the selected series and report lookup or export failures safely.
19
83
  - CSV downloads show when results are limited to the first 500 records
@@ -21,6 +85,10 @@ time from the command menu (Cmd+K → "What's new") or from Settings.
21
85
  - Normalize typed date literals in first-party BigQuery queries
22
86
  - Treat incomplete schema and truncated BigQuery results as partial evidence
23
87
 
88
+ ### Changed
89
+
90
+ - Built-in dashboards stay read-only and update with each release; Duplicate to customize makes your own editable copy
91
+
24
92
  ## 2026-10-15
25
93
 
26
94
  ### Added
@@ -33,6 +101,7 @@ time from the command menu (Cmd+K → "What's new") or from Settings.
33
101
 
34
102
  ### Improved
35
103
 
104
+ - Collapsed Analytics navigation icons stay centered when the list is short
36
105
  - Active-user answers distinguish Builder product activity from Agent-Native app opens, and dbt metric follow-ups keep their original filters.
37
106
  - Analytics keeps ambiguous active-user questions open across product activity and membership sources, and validates dbt metric date ranges before reporting results
38
107
  - Analytics keeps ambiguous active-user searches open across Builder activity, organization membership, and app opens until you choose the population.
@@ -47,6 +116,7 @@ time from the command menu (Cmd+K → "What's new") or from Settings.
47
116
 
48
117
  ### Fixed
49
118
 
119
+ - Collapsed navigation icons stay centered in the Analytics sidebar
50
120
  - Dashboard query costs and refresh confirmations reflect actual execution, and disconnected sources clear saved rows
51
121
  - Dashboard results disappear when access is withdrawn, and Explorer query costs exclude cached scans
52
122
  - Analytics now distinguishes empty and partial dbt metric results from complete answers.
@@ -264,6 +334,7 @@ time from the command menu (Cmd+K → "What's new") or from Settings.
264
334
 
265
335
  ### Improved
266
336
 
337
+ - AI tools connected over MCP now get a checked result format for the analytics health check and the connected database tables list
267
338
  - Session replays now show the date and time the session started, and the Sessions list shows start times in a shorter, easier-to-scan format.
268
339
  - Agent dashboard edits are now checked against the live chart before they save, so the agent no longer reports a chart change that did not happen.
269
340
 
@@ -0,0 +1,161 @@
1
+ import { defineAction, fail } from "@agent-native/core/action";
2
+ import {
3
+ getRequestOrgId,
4
+ getRequestUserEmail,
5
+ } from "@agent-native/core/server";
6
+ import { z } from "zod";
7
+
8
+ import { dryRunQuerySchema } from "../server/lib/bigquery.js";
9
+ import { requireAnalyticsAdminContext } from "../server/lib/db-admin-connections.js";
10
+ import {
11
+ firstPartyEventsModelProjection,
12
+ getFirstPartyAnalyticsBackend,
13
+ saveFirstPartyEventsModelTable,
14
+ } from "../server/lib/first-party-analytics-backend.js";
15
+ import { resolveSourceGeneration } from "../server/lib/source-generation.js";
16
+
17
+ const TABLE_ID = /^[a-z][a-z0-9-]{4,61}[a-z0-9]\.[A-Za-z_]\w*\.[A-Za-z_]\w*$/;
18
+
19
+ type ModelCheck =
20
+ | {
21
+ status: "ok";
22
+ table: string;
23
+ modifiedAt: string;
24
+ ageMs: number;
25
+ stale: boolean;
26
+ recentDayBytes: number | null;
27
+ fullScanBytes: number | null;
28
+ }
29
+ | { status: "unavailable"; table: string; reason: string; message?: string };
30
+
31
+ function utcDateDaysAgo(days: number): string {
32
+ return new Date(Date.now() - days * 86_400_000).toISOString().slice(0, 10);
33
+ }
34
+
35
+ // Dry runs only: BigQuery plans the query and reports the bytes it would
36
+ // scan, but runs and bills nothing.
37
+ async function checkModel(
38
+ table: string,
39
+ signal?: AbortSignal,
40
+ ): Promise<ModelCheck> {
41
+ const generation = await resolveSourceGeneration([table], {
42
+ force: true,
43
+ signal,
44
+ });
45
+ if (generation.status === "unavailable") {
46
+ return { status: "unavailable", table, reason: generation.reason };
47
+ }
48
+ const projection = `(${firstPartyEventsModelProjection(table)}) AS analytics_events`;
49
+ const recent = await dryRunQuerySchema(
50
+ `SELECT COUNT(*) AS events FROM ${projection} WHERE event_date >= DATE '${utcDateDaysAgo(1)}'`,
51
+ { signal },
52
+ );
53
+ if (recent.error) {
54
+ return {
55
+ status: "unavailable",
56
+ table,
57
+ reason: "query_denied",
58
+ message: recent.error,
59
+ };
60
+ }
61
+ // Eligible chart SQL need not bound event_date, so a model that refuses an
62
+ // unbounded read (a required partition filter, say) would fail those charts.
63
+ const full = await dryRunQuerySchema(
64
+ `SELECT COUNT(*) AS events FROM ${projection}`,
65
+ { signal },
66
+ );
67
+ if (full.error) {
68
+ return {
69
+ status: "unavailable",
70
+ table,
71
+ reason: "full_read_denied",
72
+ message: full.error,
73
+ };
74
+ }
75
+ return {
76
+ status: "ok",
77
+ table,
78
+ modifiedAt: generation.oldestModifiedAt,
79
+ ageMs: generation.ageMs,
80
+ stale: generation.stale,
81
+ recentDayBytes: recent.totalBytesProcessed ?? null,
82
+ fullScanBytes: full.totalBytesProcessed ?? null,
83
+ };
84
+ }
85
+
86
+ export default defineAction({
87
+ // set and clear replace the org-wide model every first-party chart reads.
88
+ mcpAnnotations: {
89
+ readOnlyHint: false,
90
+ destructiveHint: true,
91
+ openWorldHint: true,
92
+ },
93
+ description:
94
+ "Check or set the warehouse events model that first-party Analytics charts read instead of the raw event table. `check` (any org member) reads the model's table metadata and dry-runs the chart projection against it as the app, reporting whether the app can query it and the bytes a one-day and a full read would scan; it runs and bills no query. `set` (org admin, needs approval) runs the same check and saves the model only if it passes. `clear` (org admin, needs approval) sends charts back to the raw table. Charts that read columns or events the model lacks, alerts, and onboarding journeys keep reading the raw table either way.",
95
+ schema: z.object({
96
+ mode: z.enum(["check", "set", "clear"]),
97
+ table: z
98
+ .string()
99
+ .trim()
100
+ .optional()
101
+ .describe(
102
+ "Model table as project.dataset.table. Required for set; check defaults to the configured model.",
103
+ ),
104
+ }),
105
+ needsApproval: ({ mode }) => mode !== "check",
106
+ run: async ({ mode, table }, context) => {
107
+ const userEmail = getRequestUserEmail();
108
+ if (!userEmail) {
109
+ fail("Sign in to configure the Analytics model", {
110
+ errorCode: "unauthenticated",
111
+ statusCode: 401,
112
+ });
113
+ }
114
+ const orgId = getRequestOrgId();
115
+ if (!orgId) {
116
+ fail("An active organization is required to configure the model", {
117
+ errorCode: "organization_required",
118
+ statusCode: 403,
119
+ });
120
+ }
121
+ const signal = context?.signal;
122
+ const scope =
123
+ mode === "check"
124
+ ? { userEmail, orgId }
125
+ : await requireAnalyticsAdminContext({ userEmail, orgId });
126
+
127
+ if (mode === "clear") {
128
+ await saveFirstPartyEventsModelTable(scope, null);
129
+ return { status: "cleared" as const };
130
+ }
131
+
132
+ const target =
133
+ table ||
134
+ (mode === "check"
135
+ ? (await getFirstPartyAnalyticsBackend(scope, signal)).modelTable
136
+ : null);
137
+ if (!target) {
138
+ fail("Name the model table as project.dataset.table", {
139
+ errorCode: "model_table_required",
140
+ statusCode: 400,
141
+ });
142
+ }
143
+ if (!TABLE_ID.test(target)) {
144
+ fail("The model table must be project.dataset.table", {
145
+ errorCode: "invalid_model_table",
146
+ statusCode: 400,
147
+ });
148
+ }
149
+
150
+ const check = await checkModel(target, signal);
151
+ if (mode === "check") return check;
152
+ if (check.status !== "ok") {
153
+ fail(
154
+ `The app cannot read ${target} (${check.reason}); the model was not saved`,
155
+ { errorCode: "model_unreadable", statusCode: 424 },
156
+ );
157
+ }
158
+ await saveFirstPartyEventsModelTable(scope, target);
159
+ return { ...check, status: "saved" as const };
160
+ },
161
+ });
@@ -0,0 +1,59 @@
1
+ import { defineAction, fail } from "@agent-native/core/action";
2
+ import {
3
+ getRequestOrgId,
4
+ getRequestUserEmail,
5
+ } from "@agent-native/core/server";
6
+ import { z } from "zod";
7
+
8
+ import { getFirstPartyAnalyticsBackend } from "../server/lib/first-party-analytics-backend.js";
9
+ import { firstPartyAnalyticsTableBackends } from "../server/lib/first-party-analytics.js";
10
+
11
+ const queryBackendSchema = z.enum(["postgres", "bigquery"]);
12
+
13
+ export default defineAction({
14
+ description:
15
+ "Report where first-party Analytics queries run for the current user/org, read from its stored backend setting; it never queries BigQuery or any warehouse. Returns `sink` (`postgres`; `dual`, which also writes to BigQuery but reads Postgres; or `bigquery`) and `tables`, the backend each query-agent-native-analytics table is read from. `setting` is `absent` (the Postgres default), `recognized`, or `unrecognized`: a stored value that is not a known backend, which also routes to Postgres; report that the setting needs fixing. Fails with errorCode first_party_backend_unreadable when the setting cannot be read; that is unknown, not Postgres.",
16
+ schema: z.object({}),
17
+ outputSchema: z.object({
18
+ sink: z.enum(["postgres", "dual", "bigquery"]),
19
+ setting: z.enum(["absent", "recognized", "unrecognized"]),
20
+ tables: z.object({
21
+ analytics_events: queryBackendSchema,
22
+ analytics_event_daily_rollups: queryBackendSchema,
23
+ analytics_user_days: queryBackendSchema,
24
+ session_recordings: queryBackendSchema,
25
+ }),
26
+ }),
27
+ http: { method: "GET" },
28
+ readOnly: true,
29
+ publicAgent: { expose: true, readOnly: true, requiresAuth: true },
30
+ run: async () => {
31
+ const userEmail = getRequestUserEmail();
32
+ if (!userEmail) {
33
+ fail("no authenticated user", {
34
+ errorCode: "unauthenticated",
35
+ statusCode: 401,
36
+ });
37
+ }
38
+ let backend: Awaited<ReturnType<typeof getFirstPartyAnalyticsBackend>>;
39
+ try {
40
+ backend = await getFirstPartyAnalyticsBackend({
41
+ userEmail,
42
+ orgId: getRequestOrgId() || null,
43
+ });
44
+ } catch (error) {
45
+ console.warn("[first-party-analytics] Backend setting unreadable:", {
46
+ errorType: error instanceof Error ? error.name : "non_error",
47
+ });
48
+ fail(
49
+ "Could not read this scope's first-party analytics backend setting, so it is unknown whether queries would run on Postgres or BigQuery. Do not assume Postgres; retry later.",
50
+ { errorCode: "first_party_backend_unreadable", statusCode: 503 },
51
+ );
52
+ }
53
+ return {
54
+ sink: backend.sink,
55
+ setting: backend.sinkSetting,
56
+ tables: firstPartyAnalyticsTableBackends(backend.sink),
57
+ };
58
+ },
59
+ });
@@ -92,7 +92,7 @@ const healthSchema = z.object({
92
92
 
93
93
  export default defineAction({
94
94
  description:
95
- "Check first-party Analytics source health before a large or historical query. Returns query pressure, delivery health, and external backend recommendations. Neon/Postgres reads compact all-time daily rollups; BigQuery reads at most the last 30 UTC calendar days (including today) with a small billed-byte cap. BigQuery eventCount, dailyRollupRows, and lastEventDate cover only the dates in metrics.coverage (kind recent_window), while all-time firstEventDate and spanDays are null with an explicit reason. A cap failure returns unavailable coverage, null event metrics, and reason maximum_bytes_billed. Both sinks read the small query-pressure ledger. For Neon/Postgres, event volume or query pressure can recommend BigQuery; for an existing BigQuery sink, only query pressure or delivery lag can produce monitor. A pending delivery-queue migration is reported as degraded delivery health. External options include BigQuery for warehouse SQL and history and Amplitude for product analytics, with safe setup links. Connecting a backend does not reroute /track or backfill existing events.",
95
+ "Check first-party Analytics source health before a large or historical query. Returns query pressure, delivery health, and external backend recommendations. Neon/Postgres reads compact all-time daily rollups; BigQuery reads at most the last 30 UTC calendar days (including today) with a small billed-byte cap. BigQuery eventCount, dailyRollupRows, and lastEventDate cover only the dates in metrics.coverage (kind recent_window), while all-time firstEventDate and spanDays are null with an explicit reason. A cap failure returns unavailable coverage, null event metrics, and reason maximum_bytes_billed. Both sinks read the small query-pressure ledger. To learn only where queries run, without querying BigQuery, call get-first-party-analytics-backend. For Neon/Postgres, event volume or query pressure can recommend BigQuery; for an existing BigQuery sink, only query pressure or delivery lag can produce monitor. A pending delivery-queue migration is reported as degraded delivery health. External options include BigQuery for warehouse SQL and history and Amplitude for product analytics, with safe setup links. Connecting a backend does not reroute /track or backfill existing events.",
96
96
  schema: z.object({}),
97
97
  outputSchema: healthSchema,
98
98
  outputErrorStrategy: "strict",
@@ -49,7 +49,7 @@ async function dataDictionaryDescriptions(
49
49
 
50
50
  export default defineAction({
51
51
  description:
52
- "List the events Analytics has received, with each event's app, volume in the range, last-seen time, sample property keys, and Data Dictionary description. Flags apps that send only automatic events and events that stopped firing. Keeps the 1,000 most recently seen events, sorted by volume, and sets truncated when more exist; app flags still count every event in the range. Reads Analytics' own index. Requires the Sessions triage Lab.",
52
+ "List the events Analytics has received, with each event's app, volume in the range, last-seen time, sample property keys, and Data Dictionary description. Flags apps that send only automatic events and events that stopped firing. Keeps the 1,000 most recently seen events, sorted by volume, and sets truncated when more exist; app flags still count every event in the range. Reads Analytics' own index; Postgres-only, never queries BigQuery. Requires the Sessions triage Lab.",
53
53
  schema: z.object({
54
54
  from: sessionEventBoundSchema
55
55
  .optional()
@@ -11,7 +11,7 @@ import { sessionEventBoundSchema } from "../shared/session-events.js";
11
11
 
12
12
  export default defineAction({
13
13
  description:
14
- "List event names tracked in Analytics sessions with the number of sessions that tracked each, for building did/didn't event filters on list-session-recordings. Reads Analytics' own session event index. Requires the Sessions triage Lab.",
14
+ "List event names tracked in Analytics sessions with the number of sessions that tracked each, for building did/didn't event filters on list-session-recordings. Reads Analytics' own session event index; Postgres-only, never queries BigQuery. Requires the Sessions triage Lab.",
15
15
  schema: z.object({
16
16
  from: sessionEventBoundSchema
17
17
  .optional()
@@ -42,7 +42,7 @@ async function withFriction(
42
42
 
43
43
  export default defineAction({
44
44
  description:
45
- "List first-party Analytics session replay recordings accessible to the current user/org. `visitorType: anonymous` explicitly includes playable recordings with no signed-in user id and a non-empty client-supplied anonymous id; it does not verify the page origin or a person's identity. Returns summaries only, not raw replay chunks.",
45
+ "List first-party Analytics session replay recordings accessible to the current user/org. `visitorType: anonymous` explicitly includes playable recordings with no signed-in user id and a non-empty client-supplied anonymous id; it does not verify the page origin or a person's identity. Returns summaries only, not raw replay chunks. Postgres-only; never queries BigQuery.",
46
46
  schema: z.object({
47
47
  query: z
48
48
  .string()
@@ -1,5 +1,5 @@
1
1
  import { dataTableWidgetResultSchema, defineAction } from "@agent-native/core";
2
- import type { ActionRunContext } from "@agent-native/core/action";
2
+ import { fail, type ActionRunContext } from "@agent-native/core/action";
3
3
  import { createDataTableWidgetResult } from "@agent-native/core/data-widgets";
4
4
  import {
5
5
  getRequestOrgId,
@@ -9,7 +9,11 @@ import { track } from "@agent-native/core/tracking";
9
9
  import { z } from "zod";
10
10
 
11
11
  import { FirstPartyAnalyticsValidationError } from "../server/lib/first-party-analytics-errors.js";
12
- import { queryFirstPartyAnalytics } from "../server/lib/first-party-analytics.js";
12
+ import {
13
+ FirstPartyAnalyticsBackendNotPostgresError,
14
+ queryFirstPartyAnalyticsWithBackend,
15
+ type FirstPartyAnalyticsQueryResultWithBackend,
16
+ } from "../server/lib/first-party-analytics.js";
13
17
  import {
14
18
  ANALYTICS_ANALYSIS_RESULT_RENDERER,
15
19
  getSingleNumericAnalysisResult,
@@ -21,11 +25,7 @@ function resolveScope() {
21
25
  return { userEmail, orgId: getRequestOrgId() || null };
22
26
  }
23
27
 
24
- function toDataTableResult(result: {
25
- rows: Record<string, unknown>[];
26
- schema: { name: string; type: string }[];
27
- truncated?: boolean;
28
- }) {
28
+ function toDataTableResult(result: FirstPartyAnalyticsQueryResultWithBackend) {
29
29
  const numericTypes = new Set([
30
30
  "number",
31
31
  "integer",
@@ -48,6 +48,7 @@ function toDataTableResult(result: {
48
48
  rows: result.rows,
49
49
  ...(result.truncated ? { truncated: true } : {}),
50
50
  },
51
+ backend: result.backend,
51
52
  });
52
53
  }
53
54
 
@@ -72,6 +73,7 @@ type QueryFailureClass =
72
73
  | "quota_exceeded"
73
74
  | "rate_limited"
74
75
  | "backend_error"
76
+ | "backend_not_postgres"
75
77
  | "timeout"
76
78
  | "query_error"
77
79
  | "unknown";
@@ -99,7 +101,7 @@ type QueryFailure = {
99
101
  };
100
102
 
101
103
  const QUERY_FAILURES: Record<
102
- Exclude<QueryFailureClass, "unknown">,
104
+ Exclude<QueryFailureClass, "unknown" | "backend_not_postgres">,
103
105
  QueryFailure
104
106
  > = {
105
107
  unsupported_sql: {
@@ -314,9 +316,9 @@ function safeCancellationError(): Error {
314
316
 
315
317
  export default defineAction({
316
318
  description:
317
- "Query the built-in first-party Analytics source: events recorded through this app's analytics collector endpoint (/track), compact daily rollups updated transactionally with new ingest, identifiable user-day rollups, and session replay summaries. It needs no external provider connection. Use it for app/site traffic, product events, template/app usage, conversions, session recordings, and LLM/agent observability (event_name = '$ai_generation': model cost, tokens, latency); use the BigQuery, GA4, Mixpanel, PostHog, or Amplitude actions when the user asks for those or the data lives there. " +
318
- "SQL may read analytics_events, analytics_event_daily_rollups, analytics_user_days, and session_recordings; session_replay_chunks is unavailable. Reads are scoped to the current user/org and exclude test identities (QA/E2E) unless includeTestIdentities is true. Prefer analytics_event_daily_rollups for event counts and analytics_user_days for active-user or retention questions. On the Builder.io production organization after the explicit BigQuery cutover, events and rollups read from BigQuery while session_recordings stays in the SQL store; cross-backend joins are not supported. The BigQuery path translates a restricted PostgreSQL-compatible SQL subset: extract event properties with `properties::jsonb ->> 'key'`; bare JSON operators, LATERAL/unnest/generate_series, FILTER (WHERE ...), DISTINCT ON, and ILIKE are unsupported. Use `||` explicitly between strings. Do not call DIV(); the first-party SQL function policy rejects it. For ratios, use `CAST(numerator AS NUMERIC) / NULLIF(denominator, 0)` to avoid integer truncation. " +
319
- "On query errors, follow the typed recovery instruction: rewrite unsupported or invalid SQL once, check schema for a missing source, narrow a billed-byte-limit query, retry only timeouts/rate limits/temporary backend errors once, and stop on permission or unclassified errors. Never repeat unchanged SQL after a non-retryable failure. Before a large or historical query, call get-first-party-analytics-health. Aggregate, project only needed columns, use bounded recent drill-downs, and add a LIMIT for raw or high-cardinality reads; an explicit all-time or lifetime request remains all-time. Safe scoped results are cached for up to five minutes. " +
319
+ "Query the built-in first-party Analytics source: events recorded through this app's analytics collector endpoint (/track), daily rollups updated transactionally with new ingest, identifiable user-day rollups, and session replay summaries. Use it for app/site traffic, product events, template/app usage, conversions, session recordings, and LLM/agent observability (event_name = '$ai_generation': model cost, tokens, latency); use the BigQuery, GA4, Mixpanel, PostHog, or Amplitude actions when the user asks for those or the data lives there. " +
320
+ "SQL may read analytics_events, analytics_event_daily_rollups, analytics_user_days, and session_recordings; session_replay_chunks is unavailable. Reads are scoped to the current user/org. Prefer analytics_event_daily_rollups for event counts and analytics_user_days for active-user or retention questions. After the explicit BigQuery cutover, events and rollups read from BigQuery while session_recordings stays in Postgres; cross-backend joins are not supported. " +
321
+ "Do not call DIV(); for ratios use CAST(numerator AS NUMERIC) / NULLIF(denominator, 0). Extract properties with properties::jsonb ->> 'key'; LATERAL/unnest/generate_series are unsupported. Use `||` explicitly between strings. On errors, follow typed recovery instructions and never repeat non-retryable SQL unchanged. If the user rules out BigQuery, skip get-first-party-analytics-health; otherwise call it before large or historical queries. Aggregate, project only needed columns, use bounded recent drill-downs, and add a LIMIT for raw or high-cardinality reads; an explicit all-time or lifetime request remains all-time. " +
320
322
  "Columns. analytics_events: event_name, timestamp, event_date, user_id, anonymous_id, user_key, session_id, app, template, signed_in, url, path, hostname, referrer, properties, context. analytics_event_daily_rollups: tenant_key, owner_email, org_id, event_date, event_name, app, template, event_count. analytics_user_days: tenant_key, owner_email, org_id, event_date, user_key. session_recordings: id, session_id, user_id, anonymous_id, user_key, started_at, ended_at, duration_ms, chunk_count, event_count, page_count, error_count, rage_click_count, app, template, status, first_url, last_url, path, hostname, referrer, metadata. " +
321
323
  "The data-querying skill lists the `$ai_generation` properties and backend routing.",
322
324
  schema: z.object({
@@ -337,6 +339,12 @@ export default defineAction({
337
339
  .describe(
338
340
  "Set true only when the user explicitly asks to see the query rows as a table.",
339
341
  ),
342
+ backend: z
343
+ .enum(["postgres"])
344
+ .optional()
345
+ .describe(
346
+ 'Set "postgres" when the user rules out BigQuery; skip get-first-party-analytics-health then. BigQuery-only queries fail with first_party_backend_not_postgres.',
347
+ ),
340
348
  }),
341
349
  outputSchema: z.union([
342
350
  dataTableWidgetResultSchema,
@@ -344,6 +352,7 @@ export default defineAction({
344
352
  rows: z.array(z.record(z.string(), z.unknown())),
345
353
  schema: z.array(z.object({ name: z.string(), type: z.string() })),
346
354
  truncated: z.boolean().optional(),
355
+ backend: z.enum(["postgres", "bigquery"]),
347
356
  }),
348
357
  ]),
349
358
  chatUI: {
@@ -370,11 +379,12 @@ export default defineAction({
370
379
  },
371
380
  actionContext,
372
381
  );
373
- let result: Awaited<ReturnType<typeof queryFirstPartyAnalytics>>;
382
+ let result: FirstPartyAnalyticsQueryResultWithBackend;
374
383
  try {
375
- result = await queryFirstPartyAnalytics(args.sql, scope, {
384
+ result = await queryFirstPartyAnalyticsWithBackend(args.sql, scope, {
376
385
  cache: true,
377
386
  includeTestIdentities: args.includeTestIdentities === true,
387
+ ...(args.backend ? { requireBackend: args.backend } : {}),
378
388
  ...(actionContext?.signal ? { signal: actionContext.signal } : {}),
379
389
  });
380
390
  } catch (error) {
@@ -398,8 +408,14 @@ export default defineAction({
398
408
  }
399
409
  throw safeCancellationError();
400
410
  }
401
-
402
- const failure = queryFailure(error);
411
+ const failure: QueryFailure =
412
+ error instanceof FirstPartyAnalyticsBackendNotPostgresError
413
+ ? {
414
+ failureClass: "backend_not_postgres",
415
+ recovery: "stop",
416
+ message: error.message,
417
+ }
418
+ : queryFailure(error);
403
419
  try {
404
420
  track(
405
421
  "query_failed",
@@ -418,6 +434,12 @@ export default defineAction({
418
434
  } catch {
419
435
  // Telemetry is best-effort and must not replace the safe query error.
420
436
  }
437
+ if (error instanceof FirstPartyAnalyticsBackendNotPostgresError) {
438
+ fail(error.message, {
439
+ errorCode: error.code,
440
+ details: { bigQueryTables: error.bigQueryTables },
441
+ });
442
+ }
421
443
  throw new FirstPartyAnalyticsActionQueryError(failure);
422
444
  }
423
445
  track(
@@ -2114,10 +2114,6 @@ export default {
2114
2114
  noMatches: "No definitions match your search.",
2115
2115
  searchPlaceholder: "Search definitions",
2116
2116
  sourceIndex: "Source index",
2117
- statusApproved: "Approved",
2118
- statusSuggested: "Suggested",
2119
- statusUnreviewed: "Unreviewed",
2120
- unsourced: "No source",
2121
2117
  },
2122
2118
  indexPanel: {
2123
2119
  presetDaily: "Daily at 06:00",