@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.
- package/LICENSE +21 -0
- package/corpus/README.md +1 -1
- package/corpus/templates/analytics/.agents/skills/actions/SKILL.md +2 -2
- package/corpus/templates/analytics/.agents/skills/data-querying/SKILL.md +24 -1
- package/corpus/templates/analytics/CHANGELOG.md +71 -0
- package/corpus/templates/analytics/actions/configure-first-party-events-model.ts +161 -0
- package/corpus/templates/analytics/actions/get-first-party-analytics-backend.ts +59 -0
- package/corpus/templates/analytics/actions/get-first-party-analytics-health.ts +1 -1
- package/corpus/templates/analytics/actions/list-event-catalog.ts +1 -1
- package/corpus/templates/analytics/actions/list-session-event-names.ts +1 -1
- package/corpus/templates/analytics/actions/list-session-recordings.ts +1 -1
- package/corpus/templates/analytics/actions/query-agent-native-analytics.ts +37 -15
- package/corpus/templates/analytics/app/i18n/en-US.ts +0 -4
- package/corpus/templates/analytics/app/pages/SemanticLayer.tsx +23 -70
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/RowBreakdownSheet.tsx +16 -6
- package/corpus/templates/analytics/changelog/2026-10-16-analytics-can-now-count-its-own-first-party-events-strictly-.md +6 -0
- package/corpus/templates/analytics/changelog/2026-10-16-first-party-charts-can-read-a-scheduled-warehouse-events-mod.md +6 -0
- package/corpus/templates/analytics/changelog/2026-10-19-keep-warm-is-opt-in.md +6 -0
- package/corpus/templates/analytics/changelog/2026-10-19-the-agent-sees-the-row-breakdown-open-in-your-current-browse.md +6 -0
- package/corpus/templates/analytics/netlify.toml +0 -1
- package/corpus/templates/analytics/server/lib/analytics-alerts.ts +9 -2
- package/corpus/templates/analytics/server/lib/first-party-analytics-backend.ts +413 -38
- package/corpus/templates/analytics/server/lib/first-party-analytics.ts +106 -22
- package/corpus/templates/analytics/shared/sql-query-contract.ts +21 -0
- package/corpus/templates/assets/.agents/skills/actions/SKILL.md +2 -2
- package/corpus/templates/assets/CHANGELOG.md +6 -0
- package/corpus/templates/assets/changelog/2026-10-19-keep-warm-is-opt-in.md +6 -0
- package/corpus/templates/assets/netlify.toml +0 -1
- package/corpus/templates/brain/.agents/skills/actions/SKILL.md +2 -2
- package/corpus/templates/brain/CHANGELOG.md +10 -0
- package/corpus/templates/brain/netlify.toml +0 -1
- package/corpus/templates/calendar/.agents/skills/actions/SKILL.md +2 -2
- package/corpus/templates/calendar/CHANGELOG.md +6 -0
- package/corpus/templates/calendar/changelog/2026-10-19-keep-warm-is-opt-in.md +6 -0
- package/corpus/templates/calendar/netlify.toml +0 -1
- package/corpus/templates/chat/.agents/skills/actions/SKILL.md +2 -2
- package/corpus/templates/chat/netlify.toml +0 -1
- package/corpus/templates/clips/.agents/skills/actions/SKILL.md +2 -2
- package/corpus/templates/clips/.agents/skills/ai-video-tools/SKILL.md +10 -5
- package/corpus/templates/clips/.agents/skills/recording/SKILL.md +2 -0
- package/corpus/templates/clips/.agents/skills/video-sharing/SKILL.md +11 -2
- package/corpus/templates/clips/CHANGELOG.md +78 -24
- package/corpus/templates/clips/actions/apply-rewind-extension.ts +84 -24
- package/corpus/templates/clips/actions/clear-edits.ts +14 -10
- package/corpus/templates/clips/actions/create-recording-upload.ts +8 -4
- package/corpus/templates/clips/actions/create-recording.ts +1 -0
- package/corpus/templates/clips/actions/delete-recording-permanent.ts +3 -0
- package/corpus/templates/clips/actions/get-recording-player-data.ts +8 -0
- package/corpus/templates/clips/actions/import-transcript.ts +83 -51
- package/corpus/templates/clips/actions/lib/create-recording-schema.ts +10 -0
- package/corpus/templates/clips/actions/request-transcript.ts +95 -52
- package/corpus/templates/clips/actions/restore-transcript-version.ts +177 -0
- package/corpus/templates/clips/app/components/player/clip-agent-webmcp.tsx +111 -7
- package/corpus/templates/clips/app/components/player/transcript-history-button.tsx +121 -0
- package/corpus/templates/clips/app/components/player/transcript-panel.tsx +30 -3
- package/corpus/templates/clips/app/i18n/en-US.ts +8 -0
- package/corpus/templates/clips/app/routes/_app.r.$recordingId.tsx +58 -0
- package/corpus/templates/clips/changelog/2026-10-15-the-desktop-camera-bubble-can-remove-your-background-and-let.md +6 -0
- package/corpus/templates/clips/changelog/2026-10-16-agent-uploads-over-the-hosted-size-limit-are-refused-up-front.md +6 -0
- package/corpus/templates/clips/changelog/2026-10-16-agents-connected-over-mcp-can-import-transcript-files.md +6 -0
- package/corpus/templates/clips/changelog/2026-10-16-replacing-a-transcript-by-importing-a-file-or-regenerating-i.md +6 -0
- package/corpus/templates/clips/changelog/2026-10-17-agents-can-watch-an-animation-in-one-frame-sheet.md +6 -0
- package/corpus/templates/clips/changelog/2026-10-17-desktop-recordings-include-balanced-audio-and-compress-large.md +6 -0
- package/corpus/templates/clips/changelog/2026-10-19-keep-warm-is-opt-in.md +6 -0
- package/corpus/templates/clips/changelog/2026-10-19-screen-memory-transcribes-without-a-system-ffmpeg-install.md +6 -0
- package/corpus/templates/clips/desktop/package.json +6 -4
- package/corpus/templates/clips/desktop/scripts/prepare-ffmpeg-sidecar.ts +375 -0
- package/corpus/templates/clips/desktop/src/app.tsx +13 -0
- package/corpus/templates/clips/desktop/src/lib/bubble-hit-mask.ts +231 -0
- package/corpus/templates/clips/desktop/src/lib/camera-composite.ts +115 -2
- package/corpus/templates/clips/desktop/src/lib/camera-cutout-preference.ts +81 -0
- package/corpus/templates/clips/desktop/src/lib/person-cutout.ts +758 -0
- package/corpus/templates/clips/desktop/src/lib/recorder.ts +6 -0
- package/corpus/templates/clips/desktop/src/overlays/bubble.tsx +252 -2
- package/corpus/templates/clips/desktop/src/styles.css +8 -0
- package/corpus/templates/clips/desktop/src-tauri/src/clips/mod.rs +258 -5
- package/corpus/templates/clips/desktop/src-tauri/src/lib.rs +6 -0
- package/corpus/templates/clips/desktop/src-tauri/src/native_screen.rs +139 -9
- package/corpus/templates/clips/desktop/src-tauri/src/screen_memory_transcript.rs +1 -31
- package/corpus/templates/clips/desktop/src-tauri/tauri.macos.conf.json +6 -0
- package/corpus/templates/clips/netlify.toml +0 -1
- package/corpus/templates/clips/server/db/schema.ts +30 -0
- package/corpus/templates/clips/server/lib/agent-frame-request.ts +224 -0
- package/corpus/templates/clips/server/lib/public-agent-context.ts +25 -1
- package/corpus/templates/clips/server/lib/transcript-versions.ts +116 -0
- package/corpus/templates/clips/server/lib/video-frame-sheet.ts +313 -0
- package/corpus/templates/clips/server/lib/video-frame.ts +102 -0
- package/corpus/templates/clips/server/lib/video-storage.ts +24 -0
- package/corpus/templates/clips/server/plugins/auth.ts +1 -0
- package/corpus/templates/clips/server/plugins/db.ts +21 -0
- package/corpus/templates/clips/server/routes/api/agent-frame.jpg.get.ts +12 -185
- package/corpus/templates/clips/server/routes/api/agent-frames.jpg.get.ts +194 -0
- package/corpus/templates/clips/shared/agent-context.ts +69 -1
- package/corpus/templates/clips/shared/transcript-segments.ts +39 -6
- package/corpus/templates/clips/shared/upload-limits.ts +15 -0
- package/corpus/templates/content/.agents/skills/actions/SKILL.md +2 -2
- package/corpus/templates/content/CHANGELOG.md +24 -0
- package/corpus/templates/content/app/components/editor/BubbleToolbar.tsx +5 -3
- package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +4 -2
- package/corpus/templates/content/app/components/editor/DocumentToolbar.tsx +1 -1
- package/corpus/templates/content/app/components/editor/LinkHoverPreview.tsx +1 -1
- package/corpus/templates/content/app/components/editor/TableHoverControls.tsx +6 -6
- package/corpus/templates/content/app/components/editor/VisualEditor.tsx +9 -9
- package/corpus/templates/content/app/components/editor/comment-lane.ts +1 -1
- package/corpus/templates/content/app/components/editor/extensions/AudioBlock.tsx +1 -1
- package/corpus/templates/content/app/components/editor/extensions/CodeBlockNode.tsx +1 -1
- package/corpus/templates/content/app/components/editor/extensions/ImageBlock.tsx +1 -1
- package/corpus/templates/content/app/components/editor/extensions/NotionExtensions.tsx +24 -24
- package/corpus/templates/content/app/components/editor/extensions/NotionFidelity.ts +3 -3
- package/corpus/templates/content/app/components/editor/extensions/VideoBlock.tsx +1 -1
- package/corpus/templates/content/app/global.css +324 -280
- package/corpus/templates/content/app/root.tsx +1 -1
- package/corpus/templates/content/app/routes/p.$id.tsx +3 -1
- package/corpus/templates/content/changelog/2026-10-18-block-text-and-background-colors-use-content-s-own-palette-a.md +6 -0
- package/corpus/templates/content/changelog/2026-10-19-keep-warm-is-opt-in.md +6 -0
- package/corpus/templates/content/e2e/helpers.ts +1 -1
- package/corpus/templates/content/netlify.toml +0 -1
- package/corpus/templates/content/shared/content-editor-structural-schema.generated.json +1 -1
- package/corpus/templates/content/shared/nfm-export-html.ts +9 -9
- package/corpus/templates/crm/.agents/skills/actions/SKILL.md +2 -2
- package/corpus/templates/design/.agents/skills/actions/SKILL.md +2 -2
- package/corpus/templates/design/CHANGELOG.md +155 -1
- package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +2 -107
- package/corpus/templates/design/app/components/design/multi-screen/canvas-tools.ts +0 -8
- package/corpus/templates/design/app/components/design/multi-screen/types.ts +0 -1
- package/corpus/templates/design/app/i18n/en-US.ts +0 -1
- package/corpus/templates/design/app/pages/design-editor/domains/use-editor-layer-actions.ts +0 -14
- package/corpus/templates/design/app/pages/design-editor/view/overview-canvas.tsx +0 -2
- package/corpus/templates/design/changelog/2026-10-19-keep-warm-is-opt-in.md +6 -0
- package/corpus/templates/design/changelog/2026-10-19-screen-overviews-no-longer-show-focus-screen-buttons-on-scre.md +6 -0
- package/corpus/templates/design/e2e/helpers.ts +10 -14
- package/corpus/templates/design/e2e/rails.ts +3 -0
- package/corpus/templates/design/netlify.toml +0 -1
- package/corpus/templates/dispatch/.agents/skills/actions/SKILL.md +2 -2
- package/corpus/templates/dispatch/CHANGELOG.md +6 -0
- package/corpus/templates/dispatch/changelog/2026-10-19-keep-warm-is-opt-in.md +6 -0
- package/corpus/templates/dispatch/netlify.toml +0 -1
- package/corpus/templates/factory/.agents/skills/actions/SKILL.md +2 -2
- package/corpus/templates/factory/.agents/skills/review-prs/SKILL.md +44 -21
- package/corpus/templates/factory/CHANGELOG.md +4 -0
- package/corpus/templates/forms/.agents/skills/actions/SKILL.md +2 -2
- package/corpus/templates/forms/netlify.toml +0 -1
- package/corpus/templates/mail/.agents/skills/actions/SKILL.md +2 -2
- package/corpus/templates/mail/CHANGELOG.md +12 -0
- package/corpus/templates/mail/changelog/2026-10-19-keep-warm-is-opt-in.md +6 -0
- package/corpus/templates/mail/netlify.toml +0 -1
- package/corpus/templates/plan/.agents/skills/actions/SKILL.md +2 -2
- package/corpus/templates/plan/CHANGELOG.md +6 -0
- package/corpus/templates/plan/changelog/2026-10-19-keep-warm-is-opt-in.md +6 -0
- package/corpus/templates/plan/netlify.toml +0 -1
- package/corpus/templates/slides/.agents/skills/actions/SKILL.md +2 -2
- package/corpus/templates/slides/CHANGELOG.md +24 -0
- package/corpus/templates/slides/app/components/editor/DeckPrivacyBadge.tsx +28 -0
- package/corpus/templates/slides/app/components/editor/DeckShareAgentTab.tsx +43 -0
- package/corpus/templates/slides/app/components/editor/EditorToolbar.tsx +44 -70
- package/corpus/templates/slides/app/components/editor/ExportMenu.tsx +65 -2
- package/corpus/templates/slides/app/components/editor/PresentMenu.tsx +126 -0
- package/corpus/templates/slides/app/components/presentation/PresentationView.tsx +10 -3
- package/corpus/templates/slides/app/hooks/use-deck-visibility.ts +21 -0
- package/corpus/templates/slides/app/i18n/en-US.ts +15 -2
- package/corpus/templates/slides/app/lib/deck-present-url.ts +32 -0
- package/corpus/templates/slides/app/pages/DeckEditor.tsx +45 -8
- package/corpus/templates/slides/app/pages/Presentation.tsx +1 -0
- 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
- package/corpus/templates/slides/changelog/2026-10-19-keep-warm-is-opt-in.md +6 -0
- package/corpus/templates/slides/netlify.toml +0 -1
- package/corpus/templates/tasks/.agents/skills/actions/SKILL.md +2 -2
- package/package.json +1 -1
- package/corpus/templates/analytics/.deploy-tmp/agent-native-provided-plugins.mjs +0 -10
- package/corpus/templates/assets/.deploy-tmp/agent-native-provided-plugins.mjs +0 -10
- package/corpus/templates/brain/.deploy-tmp/agent-native-provided-plugins.mjs +0 -10
- package/corpus/templates/calendar/.deploy-tmp/agent-native-provided-plugins.mjs +0 -10
- package/corpus/templates/chat/.deploy-tmp/agent-native-provided-plugins.mjs +0 -10
- package/corpus/templates/clips/.deploy-tmp/agent-native-provided-plugins.mjs +0 -10
- package/corpus/templates/content/.deploy-tmp/agent-native-provided-plugins.mjs +0 -10
- package/corpus/templates/crm/.deploy-tmp/agent-native-provided-plugins.mjs +0 -10
- package/corpus/templates/design/.deploy-tmp/agent-native-provided-plugins.mjs +0 -10
- package/corpus/templates/dispatch/.deploy-tmp/agent-native-provided-plugins.mjs +0 -10
- package/corpus/templates/forms/.deploy-tmp/agent-native-provided-plugins.mjs +0 -10
- package/corpus/templates/mail/.deploy-tmp/agent-native-provided-plugins.mjs +0 -10
- package/corpus/templates/plan/.deploy-tmp/agent-native-provided-plugins.mjs +0 -10
- 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
|
@@ -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
|
|
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
|
-
|
|
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
|
|
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 {
|
|
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),
|
|
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
|
|
319
|
-
"
|
|
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:
|
|
382
|
+
let result: FirstPartyAnalyticsQueryResultWithBackend;
|
|
374
383
|
try {
|
|
375
|
-
result = await
|
|
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
|
-
|
|
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",
|