@agent-native/core 0.120.1 → 0.120.2

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 (170) hide show
  1. package/corpus/README.md +2 -2
  2. package/corpus/core/CHANGELOG.md +9 -0
  3. package/corpus/core/docs/content/extensions.mdx +23 -4
  4. package/corpus/core/package.json +1 -1
  5. package/corpus/core/src/agent/production-agent.ts +4 -0
  6. package/corpus/core/src/agent/run-manager.ts +36 -10
  7. package/corpus/core/src/agent/types.ts +2 -0
  8. package/corpus/core/src/client/extensions/ExtensionsListPage.tsx +16 -3
  9. package/corpus/core/src/client/settings/ExtensionsSettingsContent.tsx +24 -0
  10. package/corpus/core/src/client/settings/SettingsPanel.tsx +10 -0
  11. package/corpus/core/src/deploy/build.ts +3 -0
  12. package/corpus/core/src/server/analytics.ts +154 -25
  13. package/corpus/core/src/templates/chat/app/components/layout/Sidebar.tsx +35 -6
  14. package/corpus/core/src/templates/chat/app/routes/extensions._index.tsx +2 -2
  15. package/corpus/core/src/templates/chat/netlify.toml +1 -0
  16. package/corpus/core/src/templates/workspace-core/.agents/skills/extensions/SKILL.md +16 -0
  17. package/corpus/core/src/vite/client.ts +6 -0
  18. package/corpus/templates/analytics/.agents/skills/adhoc-analysis/SKILL.md +27 -43
  19. package/corpus/templates/analytics/.agents/skills/dashboard-management/SKILL.md +19 -8
  20. package/corpus/templates/analytics/.agents/skills/data-querying/SKILL.md +2 -2
  21. package/corpus/templates/analytics/AGENTS.md +20 -8
  22. package/corpus/templates/analytics/actions/get-explorer-dashboard.ts +3 -0
  23. package/corpus/templates/analytics/actions/list-dashboard-usage-stats.ts +140 -46
  24. package/corpus/templates/analytics/actions/migrate-analytics-artifacts.ts +48 -0
  25. package/corpus/templates/analytics/actions/save-analysis.ts +2 -3
  26. package/corpus/templates/analytics/actions/view-screen.ts +1 -1
  27. package/corpus/templates/analytics/app/components/dashboard/DashboardMetadata.tsx +77 -0
  28. package/corpus/templates/analytics/app/components/dashboard/DashboardPanelSkeleton.tsx +7 -1
  29. package/corpus/templates/analytics/app/components/layout/NewDashboardDialog.tsx +1 -0
  30. package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +103 -502
  31. package/corpus/templates/analytics/app/global.css +30 -0
  32. package/corpus/templates/analytics/app/hooks/use-navigation-state.ts +2 -2
  33. package/corpus/templates/analytics/app/i18n-data.ts +80 -0
  34. package/corpus/templates/analytics/app/pages/Agents.tsx +49 -2
  35. package/corpus/templates/analytics/app/pages/adhoc/explorer-dashboard/index.tsx +54 -11
  36. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/SqlChartCard.tsx +3 -53
  37. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +23 -25
  38. package/corpus/templates/analytics/app/pages/analyses/AnalysisDetail.tsx +2 -2
  39. package/corpus/templates/analytics/app/routes/analyses._index.tsx +3 -2
  40. package/corpus/templates/analytics/app/routes/extensions._index.tsx +2 -2
  41. package/corpus/templates/analytics/changelog/2026-07-23-analytics-now-groups-saved-analyses-and-embedded-extensions-.md +6 -0
  42. package/corpus/templates/analytics/changelog/2026-07-23-dashboard-menus-show-who-created-and-last-updated-each-dashb.md +6 -0
  43. package/corpus/templates/analytics/changelog/2026-07-23-dashboard-usage-now-loads-reliably-for-organizations-with-ma.md +6 -0
  44. package/corpus/templates/analytics/changelog/2026-07-23-long-dashboard-building-requests-now-continue-without-stoppi.md +6 -0
  45. package/corpus/templates/analytics/netlify.toml +4 -0
  46. package/corpus/templates/analytics/server/db/schema.ts +4 -0
  47. package/corpus/templates/analytics/server/lib/agent-chat-plan-mode.ts +0 -1
  48. package/corpus/templates/analytics/server/lib/agent-readable-resource-context.ts +1 -0
  49. package/corpus/templates/analytics/server/lib/migrate-analytics-artifacts.ts +1062 -0
  50. package/corpus/templates/analytics/server/plugins/agent-chat.ts +6 -52
  51. package/corpus/templates/analytics/server/plugins/core-routes.ts +1 -1
  52. package/corpus/templates/analytics/server/plugins/db.ts +10 -0
  53. package/corpus/templates/assets/actions/generate-image-batch.ts +36 -1
  54. package/corpus/templates/assets/actions/generate-image.ts +28 -1
  55. package/corpus/templates/assets/app/components/layout/Sidebar.tsx +46 -8
  56. package/corpus/templates/assets/app/routes/extensions._index.tsx +2 -2
  57. package/corpus/templates/assets/changelog/2026-07-23-assets-image-generation-now-recovers-after-a-completed-tool-.md +6 -0
  58. package/corpus/templates/assets/netlify.toml +1 -0
  59. package/corpus/templates/brain/app/components/layout/Sidebar.tsx +38 -8
  60. package/corpus/templates/brain/app/routes/extensions._index.tsx +2 -2
  61. package/corpus/templates/brain/netlify.toml +1 -0
  62. package/corpus/templates/calendar/app/components/layout/Sidebar.tsx +43 -5
  63. package/corpus/templates/calendar/app/routes/_app.extensions._index.tsx +2 -16
  64. package/corpus/templates/calendar/netlify.toml +1 -0
  65. package/corpus/templates/chat/app/components/layout/Sidebar.tsx +35 -6
  66. package/corpus/templates/chat/app/routes/extensions._index.tsx +2 -2
  67. package/corpus/templates/chat/netlify.toml +1 -0
  68. package/corpus/templates/clips/app/components/library/library-layout.tsx +52 -6
  69. package/corpus/templates/clips/app/routes/_app.extensions._index.tsx +2 -16
  70. package/corpus/templates/clips/changelog/2026-07-23-agent-settings-are-clearly-labeled-manage-agent-in-the-sideb.md +6 -0
  71. package/corpus/templates/clips/changelog/2026-07-23-meeting-recording-meters-and-dismissed-prompts.md +6 -0
  72. package/corpus/templates/clips/desktop/src/hooks/useMicMeter.ts +1 -1
  73. package/corpus/templates/clips/desktop/src/lib/meeting-notification-dismissal.ts +2 -0
  74. package/corpus/templates/clips/desktop/src/lib/voice-dictation.ts +1 -3
  75. package/corpus/templates/clips/desktop/src/overlays/live-audio-bars.tsx +86 -0
  76. package/corpus/templates/clips/desktop/src/overlays/meeting-notification.tsx +24 -0
  77. package/corpus/templates/clips/desktop/src/overlays/recording-pill.tsx +5 -69
  78. package/corpus/templates/clips/desktop/src/styles.css +35 -3
  79. package/corpus/templates/clips/desktop/src-tauri/src/adhoc_meetings_watcher.rs +13 -44
  80. package/corpus/templates/clips/desktop/src-tauri/src/notifications.rs +119 -17
  81. package/corpus/templates/clips/netlify.toml +1 -0
  82. package/corpus/templates/content/app/components/sidebar/DocumentSidebar.tsx +3 -7
  83. package/corpus/templates/content/app/routes/_app.extensions._index.tsx +2 -2
  84. package/corpus/templates/content/changelog/2026-07-23-agent-settings-are-clearly-labeled-manage-agent-in-the-sideb.md +6 -0
  85. package/corpus/templates/content/netlify.toml +1 -0
  86. package/corpus/templates/design/app/components/layout/Sidebar.tsx +55 -17
  87. package/corpus/templates/design/app/routes/extensions._index.tsx +2 -2
  88. package/corpus/templates/design/changelog/2026-07-23-agent-settings-are-clearly-labeled-manage-agent-in-the-sideb.md +6 -0
  89. package/corpus/templates/design/netlify.toml +1 -0
  90. package/corpus/templates/dispatch/app/dispatch-extensions.tsx +1 -1
  91. package/corpus/templates/dispatch/changelog/2026-07-23-agent-settings-are-clearly-labeled-manage-agent-in-the-sideb.md +6 -0
  92. package/corpus/templates/dispatch/netlify.toml +1 -0
  93. package/corpus/templates/forms/app/components/layout/Sidebar.tsx +41 -45
  94. package/corpus/templates/forms/app/routes/_app.extensions._index.tsx +2 -2
  95. package/corpus/templates/forms/changelog/2026-07-23-agent-settings-are-clearly-labeled-manage-agent-in-the-sideb.md +6 -0
  96. package/corpus/templates/forms/netlify.toml +1 -0
  97. package/corpus/templates/macros/app/components/layout/AppLayout.tsx +36 -5
  98. package/corpus/templates/macros/app/routes/extensions._index.tsx +2 -2
  99. package/corpus/templates/macros/netlify.toml +1 -0
  100. package/corpus/templates/mail/app/components/layout/AppLayout.tsx +0 -9
  101. package/corpus/templates/mail/app/routes/extensions._index.tsx +2 -2
  102. package/corpus/templates/mail/netlify.toml +1 -0
  103. package/corpus/templates/plan/app/components/layout/Sidebar.tsx +38 -5
  104. package/corpus/templates/plan/app/routes/extensions._index.tsx +2 -2
  105. package/corpus/templates/plan/netlify.toml +1 -0
  106. package/corpus/templates/slides/app/components/layout/Sidebar.tsx +47 -4
  107. package/corpus/templates/slides/app/routes/extensions._index.tsx +2 -2
  108. package/corpus/templates/slides/netlify.toml +1 -0
  109. package/corpus/templates/tasks/app/components/layout/Sidebar.tsx +37 -6
  110. package/corpus/templates/tasks/app/routes/extensions._index.tsx +2 -2
  111. package/corpus/templates/tasks/app/routes/settings.tsx +31 -0
  112. package/corpus/templates/tasks/netlify.toml +1 -0
  113. package/dist/agent/production-agent.d.ts.map +1 -1
  114. package/dist/agent/production-agent.js +4 -0
  115. package/dist/agent/production-agent.js.map +1 -1
  116. package/dist/agent/run-manager.d.ts +9 -0
  117. package/dist/agent/run-manager.d.ts.map +1 -1
  118. package/dist/agent/run-manager.js +25 -10
  119. package/dist/agent/run-manager.js.map +1 -1
  120. package/dist/agent/types.d.ts +2 -2
  121. package/dist/agent/types.d.ts.map +1 -1
  122. package/dist/agent/types.js.map +1 -1
  123. package/dist/client/extensions/ExtensionsListPage.d.ts +5 -1
  124. package/dist/client/extensions/ExtensionsListPage.d.ts.map +1 -1
  125. package/dist/client/extensions/ExtensionsListPage.js +5 -3
  126. package/dist/client/extensions/ExtensionsListPage.js.map +1 -1
  127. package/dist/client/settings/ExtensionsSettingsContent.d.ts +2 -0
  128. package/dist/client/settings/ExtensionsSettingsContent.d.ts.map +1 -0
  129. package/dist/client/settings/ExtensionsSettingsContent.js +10 -0
  130. package/dist/client/settings/ExtensionsSettingsContent.js.map +1 -0
  131. package/dist/client/settings/SettingsPanel.d.ts.map +1 -1
  132. package/dist/client/settings/SettingsPanel.js +10 -1
  133. package/dist/client/settings/SettingsPanel.js.map +1 -1
  134. package/dist/collab/awareness.d.ts +2 -2
  135. package/dist/collab/awareness.d.ts.map +1 -1
  136. package/dist/deploy/build.js +1 -0
  137. package/dist/deploy/build.js.map +1 -1
  138. package/dist/observability/routes.d.ts +3 -3
  139. package/dist/progress/routes.d.ts +1 -1
  140. package/dist/provider-api/actions/custom-provider-registration.d.ts +7 -7
  141. package/dist/provider-api/actions/provider-api.d.ts +11 -11
  142. package/dist/provider-api/corpus-jobs.d.ts +2 -2
  143. package/dist/server/analytics.d.ts +9 -4
  144. package/dist/server/analytics.d.ts.map +1 -1
  145. package/dist/server/analytics.js +111 -22
  146. package/dist/server/analytics.js.map +1 -1
  147. package/dist/templates/chat/app/components/layout/Sidebar.tsx +35 -6
  148. package/dist/templates/chat/app/routes/extensions._index.tsx +2 -2
  149. package/dist/templates/chat/netlify.toml +1 -0
  150. package/dist/templates/workspace-core/.agents/skills/extensions/SKILL.md +16 -0
  151. package/dist/vite/client.d.ts.map +1 -1
  152. package/dist/vite/client.js +2 -0
  153. package/dist/vite/client.js.map +1 -1
  154. package/docs/content/extensions.mdx +23 -4
  155. package/package.json +1 -1
  156. package/src/agent/production-agent.ts +4 -0
  157. package/src/agent/run-manager.ts +36 -10
  158. package/src/agent/types.ts +2 -0
  159. package/src/client/extensions/ExtensionsListPage.tsx +16 -3
  160. package/src/client/settings/ExtensionsSettingsContent.tsx +24 -0
  161. package/src/client/settings/SettingsPanel.tsx +10 -0
  162. package/src/deploy/build.ts +3 -0
  163. package/src/server/analytics.ts +154 -25
  164. package/src/templates/chat/app/components/layout/Sidebar.tsx +35 -6
  165. package/src/templates/chat/app/routes/extensions._index.tsx +2 -2
  166. package/src/templates/chat/netlify.toml +1 -0
  167. package/src/templates/workspace-core/.agents/skills/extensions/SKILL.md +16 -0
  168. package/src/vite/client.ts +6 -0
  169. package/corpus/templates/analytics/app/components/layout/NewAnalysisDialog.tsx +0 -165
  170. package/corpus/templates/analytics/app/pages/analyses/AnalysesList.tsx +0 -175
@@ -2,12 +2,12 @@
2
2
  name: adhoc-analysis
3
3
  description: >-
4
4
  How to conduct ad-hoc analyses: gather data from multiple sources, synthesize
5
- findings, save reusable analysis artifacts that anyone can re-run for fresh results.
5
+ findings, and save durable results as dashboard artifacts that anyone can re-run.
6
6
  ---
7
7
 
8
8
  # Ad-Hoc Analysis
9
9
 
10
- Ad-hoc analyses are deep-dive investigations that cross-reference multiple data sources and produce a written report with findings. Saving a reusable artifact is optional: answer in chat first unless the user explicitly asks to save the analysis, create a reusable artifact, or re-run/update an existing saved analysis.
10
+ Ad-hoc analyses are deep-dive investigations that cross-reference multiple data sources and produce a written report with findings. The durable user-facing result is a dashboard. Answer in chat first unless the user explicitly asks to save the result, create a reusable artifact, or re-run/update an existing saved analysis.
11
11
 
12
12
  ## When to Use
13
13
 
@@ -17,21 +17,20 @@ Use the ad-hoc analysis workflow when:
17
17
  - The investigation involves cross-referencing (e.g., CRM deals matched against call recordings)
18
18
  - The user explicitly asks for an "analysis" or "deep dive"
19
19
 
20
- Save a reusable analysis only when:
20
+ Save a reusable dashboard artifact when:
21
21
 
22
22
  - The user explicitly asks to save, create, publish, or re-run a saved analysis
23
23
  - The user is already viewing or re-running an existing saved analysis
24
24
  - The output needs a durable artifact because it includes generated chart images or a reusable refresh workflow
25
25
 
26
- For one-off questions and exploratory deep dives, query the data and answer in chat. Do not call `save-analysis` just because the user said "analysis" or "deep dive".
26
+ For one-off questions and exploratory deep dives, query the data and answer in chat. Do not create a dashboard or call the legacy `save-analysis` action just because the user said "analysis" or "deep dive".
27
27
 
28
28
  If the user asks for an analysis output that needs a bespoke interactive
29
29
  surface, custom visualization, multi-step workflow, or UI that cannot be
30
- faithfully represented as saved-analysis Markdown, generated chart images, and
31
- structured `resultData`, create an extension instead of forcing the request into
32
- the saved-analysis format. In production mode, call `create-extension`
33
- automatically and tell the user the requested analysis needed bespoke UI/code, so
34
- you built it as an extension.
30
+ faithfully represented by native dashboard panels, create an extension and
31
+ immediately embed it in the dashboard as one or more `chartType: "extension"`
32
+ panels with `config.extensionId`. Never leave the extension as a standalone
33
+ Analytics result or direct the user to an Extensions page.
35
34
 
36
35
  ## Workflow
37
36
 
@@ -159,46 +158,33 @@ Time range: Jan 1 – Mar 31, 2026
159
158
  Filters: S1+ pipeline, closed-lost only
160
159
  ```
161
160
 
162
- ### Step 6: Save the Analysis (only when requested)
161
+ ### Step 6: Save the Dashboard Artifact (only when requested)
163
162
 
164
- Call `save-analysis` with all required fields only when the user asked for a saved/re-runnable analysis or this turn is updating an existing saved analysis:
163
+ Call `update-dashboard` with a complete dashboard config when the user asks for a saved/re-runnable analysis or this turn is creating a durable report. If the requested report needs bespoke UI, create the extension first and include its id in an extension panel.
165
164
 
166
165
  ```
167
- save-analysis
168
- --id "closed-lost-q1-2026"
169
- --name "Q1 2026 Closed-Lost Analysis"
170
- --description "Deep dive into 54 Fusion deals closed-lost in Q1, cross-referenced with Gong calls and Slack activity"
171
- --question "Why are we losing deals in Q1? Cross-reference with Gong calls and Slack."
172
- --dataSources '["hubspot", "gong", "slack"]'
173
- --instructions "1. Fetch closed-lost S1+ deals from HubSpot (pipeline: Fusion, close date: Q1 2026)
174
- 2. For each deal, fetch associated contacts and their emails
175
- 3. Search Gong calls matching those contact emails (lookback: 6 months before close date)
176
- 4. For top 15 deals by amount, search Slack for '{company name} fusion'
177
- 5. Calculate: total deals, avg deal size, win rate, Gong coverage rate
178
- 6. Break down by: lost reason, stage reached, deal size tier
179
- 7. Highlight deals with no Gong coverage (blind spots)
180
- 8. Save results with save-analysis using id='closed-lost-q1-2026'"
181
- --resultMarkdown "[the full markdown report]"
182
- --resultData '{"deals": [...], "metrics": {...}}'
166
+ update-dashboard
167
+ --dashboardId "closed-lost-q1-2026"
168
+ --config '{"name":"Q1 2026 Closed-Lost Analysis","panels":[{"id":"findings","title":"Findings","source":"first-party","chartType":"table","width":2,"sql":"...","config":{"description":"Evidence-backed report for the requested cohort."}}]}'
183
169
  ```
184
170
 
185
- `resultData` is required. Fill it with compact structured evidence from the real data-source action results you used: row samples, aggregate metrics, match decisions, call/message IDs, short transcript/message excerpts, coded themes, sentiment labels, and explicit provider errors for any gaps. Do not include full Gong transcripts, full tool outputs, or raw provider payload dumps. If you cannot query a source, do not save a guessed analysis; report the unavailable/error result instead.
171
+ The dashboard config must preserve compact evidence from the real data-source action results you used: row samples, aggregate metrics, match decisions, call/message IDs, short transcript/message excerpts, coded themes, sentiment labels, and explicit provider errors for any gaps. Do not include full Gong transcripts, full tool outputs, or raw provider payload dumps. If you cannot query a source, do not save guessed dashboard content; report the unavailable/error result instead.
186
172
 
187
- **Critical: Write good instructions.** The `instructions` field is what gets sent to the agent on re-run. Be specific:
173
+ **Critical: Write good dashboard definitions.** The dashboard description/config is what the agent uses on re-run. Be specific:
188
174
 
189
175
  - Which actions to call with which parameters
190
176
  - What filters to apply
191
177
  - How to match records across sources
192
178
  - What metrics to calculate
193
179
  - What structure the output should have
194
- - End with "Save results with save-analysis using id='...'"
180
+ - End with "Update dashboard with id='...'"
195
181
 
196
182
  ### Step 7: Navigate to the Result
197
183
 
198
- After saving, navigate the user to see the saved analysis:
184
+ After saving, navigate the user to see the saved dashboard:
199
185
 
200
186
  ```
201
- navigate --view=analyses --analysisId=closed-lost-q1-2026
187
+ navigate --view=adhoc --dashboardId=closed-lost-q1-2026
202
188
  ```
203
189
 
204
190
  ## Re-Running an Analysis
@@ -209,7 +195,7 @@ When a user clicks "Re-run" on a saved analysis, the agent receives:
209
195
  - The saved instructions (step-by-step)
210
196
  - The analysis ID to update
211
197
 
212
- Follow the instructions to gather fresh data, then call `save-analysis` with the same `id` to update the results. The `createdAt` timestamp is preserved; `updatedAt` is refreshed.
198
+ Follow the instructions to gather fresh data, then call `update-dashboard` with the same `dashboardId` to update the panels/report. Existing legacy analysis deep links remain readable, but new durable results must be written to dashboards.
213
199
 
214
200
  If the user challenges the coverage of a chat answer or saved analysis ("why
215
201
  aren't you pulling more deals?", "where is the updated response?"), rerun the
@@ -221,21 +207,19 @@ showing it or saving it.
221
207
 
222
208
  | Action | Purpose |
223
209
  | ----------------- | ---------------------------------------------------------------- |
224
- | `save-analysis` | Save or update an analysis (id, name, instructions, results) |
225
- | `get-analysis` | Retrieve a saved analysis by ID |
226
- | `list-analyses` | List all saved analyses (id, name, description, timestamps) |
227
- | `delete-analysis` | Delete a saved analysis |
228
- | `navigate` | Navigate to analyses view: `--view=analyses [--analysisId=<id>]` |
210
+ | `update-dashboard` | Save or update the dashboard artifact and its panels |
211
+ | `get-sql-dashboard` | Retrieve a dashboard by ID |
212
+ | `list-sql-dashboards` | List dashboard artifacts |
213
+ | `navigate` | Navigate to a dashboard: `--view=adhoc --dashboardId=<id>` |
229
214
 
230
215
  ## Storage
231
216
 
232
- Analyses are stored in the SQL settings table with key prefix `adhoc-analysis-{id}`. They respect org/user scoping org-scoped analyses are visible to all org members.
217
+ Dashboard artifacts are stored in SQL and respect the normal dashboard access model. Legacy analyses remain in their existing SQL tables for compatibility and should not be used for new artifacts.
233
218
 
234
219
  API endpoints (for UI consumption):
235
220
 
236
- - `GET /api/analyses` — list all
237
- - `GET /api/analyses/{id}` — get one
238
- - `DELETE /api/analyses/{id}` — delete one
221
+ - `GET /api/sql-dashboards` — list dashboard artifacts
222
+ - `GET /api/sql-dashboards/{id}` — get one
239
223
 
240
224
  ## Best Practices
241
225
 
@@ -257,7 +241,7 @@ hold everything in one pass:
257
241
  from the primary source (HubSpot, BigQuery, etc.).
258
242
  2. **Chunk and persist** — process 5-10 items per iteration. For each chunk,
259
243
  write a short per-item findings note (key signals, gaps, theme tags) as an
260
- intermediate result to `save-analysis` or agent scratch.
244
+ intermediate result to the dashboard definition or agent scratch.
261
245
  3. **Synthesize** — after all chunks are complete, read the intermediate notes
262
246
  back in and produce the final cross-item synthesis (patterns, rankings,
263
247
  themes, recommendations).
@@ -7,7 +7,7 @@ description: >-
7
7
 
8
8
  # Dashboard Management
9
9
 
10
- Dashboards are SQL-backed resources. New dashboards and analyses live in the template tables, not in settings KV rows.
10
+ Dashboards are the canonical SQL-backed Analytics resources. New dashboards, saved analyses, reports, and bespoke extensions are represented as dashboard artifacts—not as separate user-facing Analytics objects. Legacy analysis tables and actions remain available only for compatibility.
11
11
 
12
12
  ## Storage
13
13
 
@@ -25,6 +25,15 @@ Current storage:
25
25
 
26
26
  Legacy settings keys such as `u:<email>:dashboard-*`, `u:<email>:sql-dashboard-*`, `o:<orgId>:sql-dashboard-*`, and `adhoc-analysis-*` are still read as a fallback and copied into SQL on access. Do not create new dashboard settings rows.
27
27
 
28
+ For organization-wide consolidation, use `migrate-analytics-artifacts` first
29
+ with `dryRun: true`. The write requires an organization owner/admin and the
30
+ exact confirmation token `MIGRATE_ANALYTICS_ARTIFACTS`. It materializes
31
+ organization-scoped legacy settings, creates dashboard blocks for saved
32
+ analyses and standalone extensions, archives exact duplicates, copies shares,
33
+ and removes legacy settings keys only after SQL rows are materialized. Source
34
+ rows remain recoverable. Private member-only rows are intentionally outside
35
+ this organization-scoped operation.
36
+
28
37
  Use `mutate-dashboard` for existing dashboard edits. It resolves the current
29
38
  user/org context, validates the resulting config, writes the SQL-backed record,
30
39
  syncs collab, and returns compact proof. Use `update-dashboard` for new
@@ -104,13 +113,15 @@ standard panels, supported chart types, filters, variables, sections, and grid
104
113
  layout.
105
114
 
106
115
  If the user asks for a dashboard or analytical surface that needs bespoke UI or
107
- code beyond the dashboard JSON/component model, create an extension instead.
108
- Examples include custom interaction flows, non-standard visualizations, complex
109
- multi-step workflows, highly custom layouts, custom client-side state, or a
110
- dashboard-like app that needs behavior the built-in renderer cannot express. In
111
- production mode, call `create-extension` automatically and then tell the user
112
- that the request needed a bespoke surface, so you built it as an extension
113
- rather than forcing it into a native dashboard config.
116
+ code beyond the dashboard JSON/component model, create an extension and embed it
117
+ in the dashboard. Examples include custom interaction flows, non-standard
118
+ visualizations, complex multi-step workflows, highly custom layouts, custom
119
+ client-side state, or a dashboard-like app that needs behavior the built-in
120
+ renderer cannot express. In production mode, call `create-extension`
121
+ automatically, then call `update-dashboard` with one or more
122
+ `chartType: "extension"` panels using `config.extensionId`. Never leave the
123
+ extension as a standalone Analytics result or direct the user to the Extensions
124
+ page.
114
125
 
115
126
  ## Embedding An Extension As A Panel
116
127
 
@@ -36,7 +36,7 @@ through `2026-05-02T04:00:00Z`.
36
36
 
37
37
  ## Showing Charts In Chat
38
38
 
39
- For an in-chat answer, **emit a live `/chart` embed** — never `generate-chart`. The embed mounts a live `SqlChart` that re-queries when its source changes, and it doesn't choke on rigid JSON params the way the PNG action does. Full shape in `AGENTS.md` ("Inline Charts in Chat" section). Reach for `generate-chart` only when you're building a `save-analysis` artifact whose markdown will render outside the app.
39
+ For an in-chat answer, **emit a live `/chart` embed** — never `generate-chart`. The embed mounts a live `SqlChart` that re-queries when its source changes, and it doesn't choke on rigid JSON params the way the PNG action does. Full shape in `AGENTS.md` ("Inline Charts in Chat" section). Reach for `generate-chart` only when you're building a dashboard artifact that needs a persisted report image.
40
40
 
41
41
  If `generate-chart` returns an error in any chat-answering flow, the recovery is to switch to the live embed, not to retry with reformatted params.
42
42
 
@@ -136,7 +136,7 @@ future analyses.
136
136
  - Before finalizing an analytics answer, make the evidence trail explicit enough
137
137
  to audit: source(s), time window, filters, sample size or row count, join or
138
138
  match method, caveats/gaps, and what action to take next when useful.
139
- - Data-source status, data-dictionary reads, dashboard dry-runs, `update-dashboard`, `generate-chart`, and `save-analysis` are not data queries. For analyses and dashboards, run at least one provider query action and preserve the result evidence in the final answer or `resultData`.
139
+ - Data-source status, data-dictionary reads, dashboard dry-runs, `update-dashboard`, and `generate-chart` are not data queries. For dashboard artifacts, run at least one provider query action and preserve the result evidence in the final answer or dashboard config/description.
140
140
  - Use action arguments such as `query`, `objectType`, `properties`, `owner`, `limit`, or provider-specific filters to narrow output; if an action returns a broad batch, filter it in your analysis and cite the records used.
141
141
  - Update the relevant `.agents/skills/<provider>/SKILL.md` when you discover new patterns.
142
142
  - For BigQuery queries, check `.agents/skills/bigquery/SKILL.md` first; if the data dictionary does not contain the exact table/columns, call `search-bigquery-schema`.
@@ -1,8 +1,9 @@
1
1
  # Analytics — Agent Guide
2
2
 
3
3
  Analytics is an agent-native BI workspace. The agent manages data sources,
4
- queries, dashboards, charts, analyses, and connected warehouse integrations
5
- through actions and SQL-backed state.
4
+ queries, dashboards, charts, and connected warehouse integrations through
5
+ actions and SQL-backed state. Dashboards are the canonical user-facing
6
+ analytics artifact; legacy analyses remain readable only for compatibility.
6
7
 
7
8
  Keep this file essential. Querying, dashboard, warehouse, and implementation
8
9
  details live in `.agents/skills/`.
@@ -175,13 +176,22 @@ membership id when its native update status reports `update-available`.
175
176
  `/agents?view=dashboards` shows the
176
177
  admin-only dashboard usage audit; call `list-dashboard-usage-stats` when
177
178
  admins ask about dashboard created/modified dates, owners, last tracked
178
- modifier, views, engagements, saved views, or cleanup candidates. The
179
+ modifier, views, edits, engagements, saved views, or cleanup candidates. The
180
+ dashboard overflow menu shows created/updated timestamps and their tracked
181
+ actors for both SQL and Explorer dashboards. The
179
182
  Advanced menu opens `/agents?view=database`, where organization owners/admins
180
183
  can connect other agent-native app databases and use the shared database admin
181
184
  tool for table browsing, row editing, and SQL inspection. This database
182
185
  surface is for connected target app databases, not broad access to all
183
186
  Analytics data. Keep future admin additions inside this route instead of
184
187
  adding many top-level sidebar tabs.
188
+ - The `migrate-analytics-artifacts` action is the organization-scoped
189
+ consolidation path. Run it first with `dryRun: true`, then use the exact
190
+ `confirm: "MIGRATE_ANALYTICS_ARTIFACTS"` token for an owner/admin-approved
191
+ write. It materializes legacy settings, turns saved analyses and standalone
192
+ extensions into dashboard blocks, archives exact duplicates, copies shares,
193
+ and keeps source rows recoverable. It intentionally covers organization-
194
+ scoped rows, not private member-only rows.
185
195
  - For dashboard edits, default to `mutate-dashboard` with its typed
186
196
  `dashboard.*` script API. It supports id-based panel moves, title/SQL/config
187
197
  edits, inserts, duplication, removal, and dashboard field patches in one
@@ -212,11 +222,13 @@ membership id when its native update status reports `update-available`.
212
222
  - `get-sql-dashboard` is compact by default for agents. Use its `panels`
213
223
  summaries and `layout.panelOrder` / `layout.firstPanelIds` for orientation and
214
224
  proof. Pass `includeConfig: true` only when full panel SQL/config is needed.
215
- - Native dashboards and saved analyses are constrained artifacts. If a requested
216
- dashboard, analysis surface, visualization, interaction model, custom layout,
217
- or bespoke workflow cannot be done faithfully with the built-in dashboard JSON
218
- config/components or saved-analysis markdown/chart format, automatically build
219
- it as an extension instead and tell the user why.
225
+ - Native dashboards are constrained artifacts. If a requested dashboard,
226
+ analysis/report surface, visualization, interaction model, custom layout, or
227
+ bespoke workflow cannot be done faithfully with the built-in dashboard JSON
228
+ config/components, automatically create an extension and embed it as one or
229
+ more `chartType: "extension"` panels using `config.extensionId`. Never leave
230
+ the extension standalone or direct the user to an Extensions page from
231
+ Analytics.
220
232
  - For an existing extension-backed dashboard or migrated surface such as Risk
221
233
  Meeting, separate data repair from visual redesign. Inspect the dashboard and
222
234
  extension first, then call `update-extension` with exactly `id`,
@@ -33,6 +33,9 @@ export default defineAction({
33
33
  ownerEmail: dash.ownerEmail,
34
34
  orgId: dash.orgId,
35
35
  visibility: dash.visibility,
36
+ createdAt: dash.createdAt,
37
+ updatedAt: dash.updatedAt,
38
+ updatedBy: dash.updatedBy,
36
39
  role: dash.role,
37
40
  canEdit: dash.canEdit,
38
41
  canManage: dash.canManage,
@@ -1,6 +1,6 @@
1
1
  import { defineAction } from "@agent-native/core/action";
2
2
  import { buildDeepLink } from "@agent-native/core/server";
3
- import { and, eq, like, or, sql } from "drizzle-orm";
3
+ import { and, eq, isNull, like, or, sql } from "drizzle-orm";
4
4
  import { z } from "zod";
5
5
 
6
6
  import { getDb, schema } from "../server/db/index.js";
@@ -21,6 +21,7 @@ type DashboardUsageStats = {
21
21
  viewCount: number;
22
22
  engagementCount: number;
23
23
  eventEngagementCount: number;
24
+ editCount: number;
24
25
  savedViewCount: number;
25
26
  uniqueUserCount: number;
26
27
  lastViewedAt: string | null;
@@ -96,6 +97,8 @@ export function dashboardIdFromEventLocation(
96
97
  if (!raw) return null;
97
98
  try {
98
99
  const parsed = new URL(raw, "https://analytics.local");
100
+ const parsedDashboardId = dashboardIdFromPath(parsed.pathname);
101
+ if (parsedDashboardId) return parsedDashboardId;
99
102
  if (
100
103
  /(?:^|\/)(?:dashboards|adhoc)\/explorer-dashboard$/.test(parsed.pathname)
101
104
  ) {
@@ -124,7 +127,7 @@ function dashboardUrl(row: { id: string; kind: string }): string {
124
127
 
125
128
  export default defineAction({
126
129
  description:
127
- "List admin-only dashboard usage and cleanup stats for every dashboard in the active organization. Includes lifecycle metadata, owner, last tracked modifier, pageviews, engagement events, unique viewers, saved view counts, archive/hidden state, and dashboard links. Requires active organization owner/admin role.",
130
+ "List admin-only dashboard usage and cleanup stats for every dashboard in the active organization. Includes lifecycle metadata, owner, last tracked modifier, pageviews, edit counts, engagement events, unique viewers, saved view counts, archive/hidden state, and dashboard links. Requires active organization owner/admin role.",
128
131
  schema: z.object({}),
129
132
  http: { method: "GET" },
130
133
  readOnly: true,
@@ -144,24 +147,120 @@ export default defineAction({
144
147
  // guard:allow-unscoped — org owner/admin audit intentionally spans all
145
148
  // dashboard rows in the active org after requireDbAdminContextFromRequest.
146
149
  const dashboardRows = await db
147
- .select()
150
+ .select({
151
+ id: schema.dashboards.id,
152
+ kind: schema.dashboards.kind,
153
+ title: schema.dashboards.title,
154
+ config: schema.dashboards.config,
155
+ ownerEmail: schema.dashboards.ownerEmail,
156
+ visibility: schema.dashboards.visibility,
157
+ createdAt: schema.dashboards.createdAt,
158
+ updatedAt: schema.dashboards.updatedAt,
159
+ updatedBy: schema.dashboards.updatedBy,
160
+ archivedAt: schema.dashboards.archivedAt,
161
+ hiddenAt: schema.dashboards.hiddenAt,
162
+ hiddenBy: schema.dashboards.hiddenBy,
163
+ })
148
164
  .from(schema.dashboards)
149
165
  .where(eq(schema.dashboards.orgId, admin.orgId));
150
166
 
167
+ if (dashboardRows.length === 0) return [];
168
+
151
169
  const dashboardIds = new Set<string>(
152
170
  dashboardRows.map((row: { id: string }) => row.id),
153
171
  );
154
172
 
155
- const savedViewRows = await db
156
- .select({
157
- dashboardId: schema.dashboardViews.dashboardId,
158
- count: sql<number>`count(*)`,
159
- lastSavedViewAt: sql<
160
- string | null
161
- >`max(${schema.dashboardViews.createdAt})`,
162
- })
163
- .from(schema.dashboardViews)
164
- .groupBy(schema.dashboardViews.dashboardId);
173
+ const analyticsUserIdentity = sql<
174
+ string | null
175
+ >`coalesce(nullif(${schema.analyticsEvents.userKey}, ''), nullif(${schema.analyticsEvents.userId}, ''), nullif(${schema.analyticsEvents.anonymousId}, ''), nullif(${schema.analyticsEvents.sessionId}, ''))`;
176
+ const indexedDashboardEventLocation = or(
177
+ like(schema.analyticsEvents.path, "/dashboards/%"),
178
+ like(schema.analyticsEvents.path, "/adhoc/%"),
179
+ );
180
+ const legacyAbsoluteUrlDashboardEventLocation = and(
181
+ or(
182
+ isNull(schema.analyticsEvents.path),
183
+ eq(schema.analyticsEvents.path, ""),
184
+ ),
185
+ or(
186
+ like(schema.analyticsEvents.url, "%/dashboards/%"),
187
+ like(schema.analyticsEvents.url, "%/adhoc/%"),
188
+ ),
189
+ );
190
+ const dashboardEventLocation = or(
191
+ indexedDashboardEventLocation,
192
+ legacyAbsoluteUrlDashboardEventLocation,
193
+ );
194
+
195
+ const [savedViewRows, revisionRows, eventRows, eventUserRows] =
196
+ await Promise.all([
197
+ db
198
+ .select({
199
+ dashboardId: schema.dashboardViews.dashboardId,
200
+ count: sql<number>`count(*)`,
201
+ lastSavedViewAt: sql<
202
+ string | null
203
+ >`max(${schema.dashboardViews.createdAt})`,
204
+ })
205
+ .from(schema.dashboardViews)
206
+ .innerJoin(
207
+ schema.dashboards,
208
+ and(
209
+ eq(schema.dashboardViews.dashboardId, schema.dashboards.id),
210
+ eq(schema.dashboards.orgId, admin.orgId),
211
+ ),
212
+ )
213
+ .groupBy(schema.dashboardViews.dashboardId),
214
+ db
215
+ .select({
216
+ dashboardId: schema.dashboardRevisions.dashboardId,
217
+ count: sql<number>`count(*)`,
218
+ })
219
+ .from(schema.dashboardRevisions)
220
+ .where(eq(schema.dashboardRevisions.orgId, admin.orgId))
221
+ .groupBy(schema.dashboardRevisions.dashboardId),
222
+ db
223
+ .select({
224
+ path: schema.analyticsEvents.path,
225
+ url: schema.analyticsEvents.url,
226
+ eventName: schema.analyticsEvents.eventName,
227
+ count: sql<number>`count(*)`,
228
+ lastSeenAt: sql<
229
+ string | null
230
+ >`max(${schema.analyticsEvents.receivedAt})`,
231
+ })
232
+ .from(schema.analyticsEvents)
233
+ .where(
234
+ and(
235
+ eq(schema.analyticsEvents.orgId, admin.orgId),
236
+ dashboardEventLocation,
237
+ ),
238
+ )
239
+ .groupBy(
240
+ schema.analyticsEvents.path,
241
+ schema.analyticsEvents.url,
242
+ schema.analyticsEvents.eventName,
243
+ ),
244
+ db
245
+ .select({
246
+ path: schema.analyticsEvents.path,
247
+ url: schema.analyticsEvents.url,
248
+ userIdentity: analyticsUserIdentity,
249
+ })
250
+ .from(schema.analyticsEvents)
251
+ .where(
252
+ and(
253
+ eq(schema.analyticsEvents.orgId, admin.orgId),
254
+ dashboardEventLocation,
255
+ sql`${analyticsUserIdentity} IS NOT NULL`,
256
+ ),
257
+ )
258
+ .groupBy(
259
+ schema.analyticsEvents.path,
260
+ schema.analyticsEvents.url,
261
+ analyticsUserIdentity,
262
+ ),
263
+ ]);
165
264
 
166
265
  const savedViewsByDashboard = new Map<
167
266
  string,
@@ -179,38 +278,15 @@ export default defineAction({
179
278
  });
180
279
  }
181
280
 
182
- const analyticsUserIdentity = sql<
183
- string | null
184
- >`coalesce(nullif(${schema.analyticsEvents.userKey}, ''), nullif(${schema.analyticsEvents.userId}, ''), nullif(${schema.analyticsEvents.anonymousId}, ''), nullif(${schema.analyticsEvents.sessionId}, ''))`;
185
- const eventRows = await db
186
- .select({
187
- path: schema.analyticsEvents.path,
188
- url: schema.analyticsEvents.url,
189
- eventName: schema.analyticsEvents.eventName,
190
- count: sql<number>`count(*)`,
191
- userIdentity: analyticsUserIdentity,
192
- lastSeenAt: sql<
193
- string | null
194
- >`max(${schema.analyticsEvents.receivedAt})`,
195
- })
196
- .from(schema.analyticsEvents)
197
- .where(
198
- and(
199
- eq(schema.analyticsEvents.orgId, admin.orgId),
200
- or(
201
- like(schema.analyticsEvents.path, "%/dashboards/%"),
202
- like(schema.analyticsEvents.path, "%/adhoc/%"),
203
- like(schema.analyticsEvents.url, "%/dashboards/%"),
204
- like(schema.analyticsEvents.url, "%/adhoc/%"),
205
- ),
206
- ),
207
- )
208
- .groupBy(
209
- schema.analyticsEvents.path,
210
- schema.analyticsEvents.url,
211
- schema.analyticsEvents.eventName,
212
- analyticsUserIdentity,
213
- );
281
+ const editsByDashboard = new Map<string, number>();
282
+ for (const row of revisionRows as Array<{
283
+ dashboardId: string;
284
+ count: unknown;
285
+ }>) {
286
+ if (dashboardIds.has(row.dashboardId)) {
287
+ editsByDashboard.set(row.dashboardId, toNumber(row.count));
288
+ }
289
+ }
214
290
 
215
291
  const eventsByDashboard = new Map<
216
292
  string,
@@ -226,7 +302,6 @@ export default defineAction({
226
302
  url: string | null;
227
303
  eventName: string;
228
304
  count: unknown;
229
- userIdentity: string | null;
230
305
  lastSeenAt: string | null;
231
306
  }>) {
232
307
  const dashboardId = dashboardIdFromEventLocation(row.path, row.url);
@@ -248,6 +323,22 @@ export default defineAction({
248
323
  } else {
249
324
  current.eventEngagementCount += count;
250
325
  }
326
+ eventsByDashboard.set(dashboardId, current);
327
+ }
328
+
329
+ for (const row of eventUserRows as Array<{
330
+ path: string | null;
331
+ url: string | null;
332
+ userIdentity: string | null;
333
+ }>) {
334
+ const dashboardId = dashboardIdFromEventLocation(row.path, row.url);
335
+ if (!dashboardId || !dashboardIds.has(dashboardId)) continue;
336
+ const current = eventsByDashboard.get(dashboardId) ?? {
337
+ viewCount: 0,
338
+ eventEngagementCount: 0,
339
+ uniqueUsers: new Set<string>(),
340
+ lastViewedAt: null,
341
+ };
251
342
  if (row.userIdentity) current.uniqueUsers.add(row.userIdentity);
252
343
  eventsByDashboard.set(dashboardId, current);
253
344
  }
@@ -281,6 +372,7 @@ export default defineAction({
281
372
  hiddenBy: row.hiddenBy ?? null,
282
373
  viewCount: events.viewCount,
283
374
  eventEngagementCount: events.eventEngagementCount,
375
+ editCount: editsByDashboard.get(row.id) ?? 0,
284
376
  savedViewCount: savedViews.count,
285
377
  engagementCount: events.eventEngagementCount + savedViews.count,
286
378
  uniqueUserCount: events.uniqueUsers.size,
@@ -293,6 +385,8 @@ export default defineAction({
293
385
  .sort((a, b) => {
294
386
  const views = b.viewCount - a.viewCount;
295
387
  if (views !== 0) return views;
388
+ const edits = b.editCount - a.editCount;
389
+ if (edits !== 0) return edits;
296
390
  return b.updatedAt.localeCompare(a.updatedAt);
297
391
  });
298
392
  },
@@ -0,0 +1,48 @@
1
+ import { defineAction } 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 { requireAnalyticsAdminContext } from "../server/lib/db-admin-connections";
9
+ import {
10
+ migrateAnalyticsArtifacts,
11
+ type AnalyticsArtifactMigrationContext,
12
+ } from "../server/lib/migrate-analytics-artifacts.js";
13
+
14
+ const confirmation = "MIGRATE_ANALYTICS_ARTIFACTS" as const;
15
+
16
+ export default defineAction({
17
+ description:
18
+ "Inventory or consolidate all organization-scoped Analytics dashboards, saved analyses, and extensions into the canonical Dashboards list. The default is a read-only dry run. A write requires an organization owner/admin and confirm=MIGRATE_ANALYTICS_ARTIFACTS; exact duplicates are archived/hidden, source rows remain recoverable, shares are copied, and legacy settings keys are removed only after SQL materialization.",
19
+ schema: z.object({
20
+ dryRun: z
21
+ .boolean()
22
+ .optional()
23
+ .default(true)
24
+ .describe("Inspect the migration without writing when true (default)."),
25
+ confirm: z
26
+ .literal(confirmation)
27
+ .optional()
28
+ .describe(
29
+ "Required for writes: MIGRATE_ANALYTICS_ARTIFACTS. Do not send this for a dry run.",
30
+ ),
31
+ }),
32
+ needsApproval: ({ dryRun }) => !dryRun,
33
+ run: async ({ dryRun, confirm }, ctx) => {
34
+ const userEmail = getRequestUserEmail() || ctx?.userEmail;
35
+ const orgId = getRequestOrgId() || ctx?.orgId || null;
36
+ const admin = await requireAnalyticsAdminContext({ userEmail, orgId });
37
+ if (!dryRun && confirm !== confirmation) {
38
+ throw new Error(
39
+ `Refusing the Analytics organization migration without confirm=${confirmation}. Run a dry run first, then repeat with the exact confirmation token.`,
40
+ );
41
+ }
42
+ const migrationContext: AnalyticsArtifactMigrationContext = {
43
+ userEmail: admin.userEmail,
44
+ orgId: admin.orgId,
45
+ };
46
+ return migrateAnalyticsArtifacts(migrationContext, { dryRun });
47
+ },
48
+ });
@@ -113,9 +113,8 @@ function assertCompactAnalysisPayload(args: {
113
113
 
114
114
  export default defineAction({
115
115
  description:
116
- "Save an ad-hoc analysis as a reusable artifact. Do not call this for ordinary in-chat analysis or deep-dive answers unless the user explicitly asks to save/create a reusable analysis, or this turn is re-running an existing saved analysis. Stores the analysis question, instructions for re-running, data sources used, and compact results. " +
117
- "This creates a reusable analysis that anyone can re-run later to get updated results. " +
118
- "Saved analyses appear in the Analyses sidebar, so do not use this as scratch storage, as a transient summary, or as a duplicate companion artifact when creating a dashboard or extension unless the user explicitly asked for a saved analysis too. " +
116
+ "Legacy compatibility only: use this to re-run or update an existing saved analysis when the user explicitly names its legacy analysis id or asks to preserve that legacy record. Do not call this to create a new saved analysis, report, visualization, or bespoke workflow; new requests become dashboards, using update-dashboard and embedding any bespoke extension as one or more dashboard panels. Stores the legacy analysis question, instructions for re-running, data sources used, and compact results. " +
117
+ "Do not use this as scratch storage, a transient summary, or a duplicate companion artifact for a dashboard. " +
119
118
  "Call this only after you've gathered real evidence and include non-empty, compact resultData with structured evidence from those data-source action results. For qualitative analyses, resultData may include call/message IDs, short transcript excerpts, coded themes, mention counts, and sentiment labels derived from actual source records. Never include full Gong transcripts, full tool outputs, or bulk raw provider payloads.",
120
119
  schema: z.object({
121
120
  id: z
@@ -438,7 +438,7 @@ export default defineAction({
438
438
  includes: [
439
439
  "dashboard created and modified dates",
440
440
  "last tracked modifier",
441
- "view and engagement counts",
441
+ "view, edit, and engagement counts",
442
442
  "saved view counts",
443
443
  "hidden and archived state",
444
444
  ],