@agent-native/core 0.131.7 → 0.132.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (320) hide show
  1. package/corpus/README.md +2 -2
  2. package/corpus/core/CHANGELOG.md +123 -0
  3. package/corpus/core/docs/content/durable-background-runs.mdx +15 -6
  4. package/corpus/core/docs/design/durable-agent-runs.md +15 -11
  5. package/corpus/core/package.json +1 -1
  6. package/corpus/core/src/a2a/activity.ts +127 -10
  7. package/corpus/core/src/a2a/index.ts +3 -0
  8. package/corpus/core/src/agent/durable-background.ts +38 -28
  9. package/corpus/core/src/agent/harness/background.ts +12 -2
  10. package/corpus/core/src/agent/production-agent.ts +18 -15
  11. package/corpus/core/src/agent/run-store.ts +45 -3
  12. package/corpus/core/src/application-state/handlers.ts +45 -0
  13. package/corpus/core/src/application-state/index.ts +3 -0
  14. package/corpus/core/src/application-state/store.ts +38 -34
  15. package/corpus/core/src/audit/redact.ts +104 -18
  16. package/corpus/core/src/cli/create.ts +115 -16
  17. package/corpus/core/src/client/AgentPanel.tsx +2 -7
  18. package/corpus/core/src/client/AssistantChat.tsx +10 -2
  19. package/corpus/core/src/client/MultiTabAssistantChat.tsx +3 -0
  20. package/corpus/core/src/client/RunStuckBanner.tsx +29 -6
  21. package/corpus/core/src/client/agent-chat.ts +4 -9
  22. package/corpus/core/src/client/application-state.ts +115 -12
  23. package/corpus/core/src/client/dynamic-suggestions.ts +4 -12
  24. package/corpus/core/src/client/frame-protocol.ts +5 -1
  25. package/corpus/core/src/client/guided-questions.tsx +12 -23
  26. package/corpus/core/src/db/client.ts +148 -28
  27. package/corpus/core/src/db/create-get-db.ts +39 -6
  28. package/corpus/core/src/db/runtime-diagnostics.ts +41 -6
  29. package/corpus/core/src/deploy/build.ts +26 -33
  30. package/corpus/core/src/feature-flags/store.ts +10 -5
  31. package/corpus/core/src/integrations/a2a-continuations-store.ts +11 -0
  32. package/corpus/core/src/integrations/config-store.ts +13 -0
  33. package/corpus/core/src/integrations/google-docs-poller.ts +26 -2
  34. package/corpus/core/src/mcp-client/routes.ts +33 -2
  35. package/corpus/core/src/onboarding/plugin.ts +31 -25
  36. package/corpus/core/src/org/accept-pending.ts +2 -0
  37. package/corpus/core/src/org/auto-join-domain.ts +2 -0
  38. package/corpus/core/src/org/context.ts +35 -10
  39. package/corpus/core/src/org/handlers.ts +6 -0
  40. package/corpus/core/src/org/request-org-cache.ts +71 -0
  41. package/corpus/core/src/resources/store.ts +10 -9
  42. package/corpus/core/src/scripts/runner.ts +6 -3
  43. package/corpus/core/src/secrets/storage.ts +97 -5
  44. package/corpus/core/src/server/action-change.ts +33 -0
  45. package/corpus/core/src/server/action-routes.ts +11 -9
  46. package/corpus/core/src/server/agent-teams.ts +10 -2
  47. package/corpus/core/src/server/better-auth-instance.ts +39 -12
  48. package/corpus/core/src/server/core-routes-plugin.ts +19 -2
  49. package/corpus/core/src/server/credential-provider.ts +57 -14
  50. package/corpus/core/src/server/http-response-telemetry.ts +1 -0
  51. package/corpus/core/src/server/poll.ts +95 -57
  52. package/corpus/core/src/server/voice-providers-status.ts +19 -4
  53. package/corpus/core/src/workspace-files/tool.ts +18 -11
  54. package/corpus/templates/analytics/.agents/skills/dashboard-management/SKILL.md +38 -0
  55. package/corpus/templates/analytics/actions/dashboard-mutation-api.ts +1 -1
  56. package/corpus/templates/analytics/actions/ensure-native-v2-dashboards.ts +299 -0
  57. package/corpus/templates/analytics/actions/list-native-v2-dashboard-templates.ts +34 -0
  58. package/corpus/templates/analytics/app/components/dashboard/SqlChart.tsx +82 -0
  59. package/corpus/templates/analytics/app/components/layout/NewDashboardDialog.tsx +2 -2
  60. package/corpus/templates/analytics/app/hooks/use-dashboard-chat-context.ts +40 -34
  61. package/corpus/templates/analytics/app/hooks/use-user-pref.ts +3 -3
  62. package/corpus/templates/analytics/app/i18n-data.ts +40 -0
  63. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/PanelEditorDialog.tsx +18 -10
  64. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +14 -0
  65. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/types.ts +1 -0
  66. package/corpus/templates/analytics/app/routes/chart.tsx +3 -0
  67. package/corpus/templates/analytics/changelog/2026-07-30-analytics-dashboards-now-expose-native-funnel-heatmap-callou.md +6 -0
  68. package/corpus/templates/analytics/changelog/2026-07-30-analytics-now-ships-reusable-native-v2-dashboard-manifests-w.md +6 -0
  69. package/corpus/templates/analytics/server/lib/dashboard-report-render.ts +81 -1
  70. package/corpus/templates/analytics/server/lib/native-v2-dashboards.ts +457 -0
  71. package/corpus/templates/analytics/server/lib/report-chart-svg.ts +73 -2
  72. package/corpus/templates/analytics/server/plugins/agent-chat.ts +1 -1
  73. package/corpus/templates/analytics/shared/dashboard-funnel.ts +83 -0
  74. package/corpus/templates/clips/actions/generate-workflow.ts +6 -4
  75. package/corpus/templates/clips/actions/reconcile-workflow-generation.ts +257 -0
  76. package/corpus/templates/clips/app/hooks/use-auto-title.ts +166 -4
  77. package/corpus/templates/clips/app/routes/r.$recordingId.tsx +1 -1
  78. package/corpus/templates/clips/changelog/2026-07-27-stopped-agent-runs-no-longer-leave-generated-workflow-cards-.md +6 -0
  79. package/corpus/templates/clips/changelog/2026-07-30-workflow-generation-now-stops-retrying-after-repeated-failur.md +6 -0
  80. package/corpus/templates/clips/shared/workflow.ts +5 -0
  81. package/corpus/templates/content/changelog/2026-07-30-long-running-delegated-requests-now-continue-reliably.md +6 -0
  82. package/corpus/templates/content/netlify.toml +3 -0
  83. package/corpus/templates/content/server/plugins/agent-chat.ts +1 -0
  84. package/corpus/templates/slides/.agents/skills/create-deck/SKILL.md +2 -1
  85. package/corpus/templates/slides/actions/patch-deck.ts +25 -1
  86. package/corpus/templates/slides/app/components/deck/DeckCard.tsx +17 -4
  87. package/corpus/templates/slides/app/components/deck/SlideRenderer.tsx +9 -1
  88. package/corpus/templates/slides/app/components/layout/Layout.tsx +1 -1
  89. package/corpus/templates/slides/app/context/DeckContext.tsx +11 -3
  90. package/corpus/templates/slides/app/lib/sanitize-slide-html.ts +39 -11
  91. package/corpus/templates/slides/app/pages/Index.tsx +2 -2
  92. package/corpus/templates/slides/changelog/2026-07-30-slide-previews-keep-their-aspect-ratio-isolate-embedded-styl.md +6 -0
  93. package/corpus/templates/slides/changelog/2026-07-30-slides-no-longer-lose-decks-while-organization-access-is-loa.md +6 -0
  94. package/corpus/toolkit/CHANGELOG.md +8 -0
  95. package/corpus/toolkit/package.json +1 -1
  96. package/corpus/toolkit/src/composer/runtime-adapters.tsx +16 -11
  97. package/dist/a2a/activity.d.ts +22 -4
  98. package/dist/a2a/activity.d.ts.map +1 -1
  99. package/dist/a2a/activity.js +97 -11
  100. package/dist/a2a/activity.js.map +1 -1
  101. package/dist/a2a/index.d.ts +1 -1
  102. package/dist/a2a/index.d.ts.map +1 -1
  103. package/dist/a2a/index.js +1 -1
  104. package/dist/a2a/index.js.map +1 -1
  105. package/dist/agent/durable-background.d.ts +13 -8
  106. package/dist/agent/durable-background.d.ts.map +1 -1
  107. package/dist/agent/durable-background.js +36 -28
  108. package/dist/agent/durable-background.js.map +1 -1
  109. package/dist/agent/harness/background.d.ts.map +1 -1
  110. package/dist/agent/harness/background.js +12 -2
  111. package/dist/agent/harness/background.js.map +1 -1
  112. package/dist/agent/production-agent.d.ts.map +1 -1
  113. package/dist/agent/production-agent.js +18 -15
  114. package/dist/agent/production-agent.js.map +1 -1
  115. package/dist/agent/run-store.d.ts +2 -0
  116. package/dist/agent/run-store.d.ts.map +1 -1
  117. package/dist/agent/run-store.js +44 -3
  118. package/dist/agent/run-store.js.map +1 -1
  119. package/dist/application-state/handlers.d.ts +15 -0
  120. package/dist/application-state/handlers.d.ts.map +1 -1
  121. package/dist/application-state/handlers.js +42 -2
  122. package/dist/application-state/handlers.js.map +1 -1
  123. package/dist/application-state/index.d.ts +2 -2
  124. package/dist/application-state/index.d.ts.map +1 -1
  125. package/dist/application-state/index.js +2 -2
  126. package/dist/application-state/index.js.map +1 -1
  127. package/dist/application-state/store.d.ts +11 -0
  128. package/dist/application-state/store.d.ts.map +1 -1
  129. package/dist/application-state/store.js +37 -40
  130. package/dist/application-state/store.js.map +1 -1
  131. package/dist/audit/redact.d.ts +25 -2
  132. package/dist/audit/redact.d.ts.map +1 -1
  133. package/dist/audit/redact.js +85 -21
  134. package/dist/audit/redact.js.map +1 -1
  135. package/dist/cli/create.d.ts +8 -1
  136. package/dist/cli/create.d.ts.map +1 -1
  137. package/dist/cli/create.js +98 -16
  138. package/dist/cli/create.js.map +1 -1
  139. package/dist/client/AgentPanel.d.ts.map +1 -1
  140. package/dist/client/AgentPanel.js +2 -7
  141. package/dist/client/AgentPanel.js.map +1 -1
  142. package/dist/client/AssistantChat.d.ts.map +1 -1
  143. package/dist/client/AssistantChat.js +10 -2
  144. package/dist/client/AssistantChat.js.map +1 -1
  145. package/dist/client/MultiTabAssistantChat.d.ts.map +1 -1
  146. package/dist/client/MultiTabAssistantChat.js +1 -1
  147. package/dist/client/MultiTabAssistantChat.js.map +1 -1
  148. package/dist/client/RunStuckBanner.d.ts +11 -1
  149. package/dist/client/RunStuckBanner.d.ts.map +1 -1
  150. package/dist/client/RunStuckBanner.js +20 -7
  151. package/dist/client/RunStuckBanner.js.map +1 -1
  152. package/dist/client/agent-chat.d.ts.map +1 -1
  153. package/dist/client/agent-chat.js +4 -6
  154. package/dist/client/agent-chat.js.map +1 -1
  155. package/dist/client/application-state.d.ts +11 -0
  156. package/dist/client/application-state.d.ts.map +1 -1
  157. package/dist/client/application-state.js +87 -9
  158. package/dist/client/application-state.js.map +1 -1
  159. package/dist/client/dynamic-suggestions.d.ts.map +1 -1
  160. package/dist/client/dynamic-suggestions.js +4 -13
  161. package/dist/client/dynamic-suggestions.js.map +1 -1
  162. package/dist/client/frame-protocol.d.ts +1 -0
  163. package/dist/client/frame-protocol.d.ts.map +1 -1
  164. package/dist/client/frame-protocol.js.map +1 -1
  165. package/dist/client/guided-questions.d.ts.map +1 -1
  166. package/dist/client/guided-questions.js +6 -22
  167. package/dist/client/guided-questions.js.map +1 -1
  168. package/dist/collab/awareness.d.ts +2 -2
  169. package/dist/collab/awareness.d.ts.map +1 -1
  170. package/dist/collab/routes.d.ts +1 -1
  171. package/dist/db/client.d.ts +57 -10
  172. package/dist/db/client.d.ts.map +1 -1
  173. package/dist/db/client.js +122 -31
  174. package/dist/db/client.js.map +1 -1
  175. package/dist/db/create-get-db.d.ts.map +1 -1
  176. package/dist/db/create-get-db.js +31 -7
  177. package/dist/db/create-get-db.js.map +1 -1
  178. package/dist/db/runtime-diagnostics.d.ts +1 -0
  179. package/dist/db/runtime-diagnostics.d.ts.map +1 -1
  180. package/dist/db/runtime-diagnostics.js +31 -6
  181. package/dist/db/runtime-diagnostics.js.map +1 -1
  182. package/dist/deploy/build.d.ts +5 -20
  183. package/dist/deploy/build.d.ts.map +1 -1
  184. package/dist/deploy/build.js +18 -30
  185. package/dist/deploy/build.js.map +1 -1
  186. package/dist/feature-flags/store.d.ts.map +1 -1
  187. package/dist/feature-flags/store.js +10 -5
  188. package/dist/feature-flags/store.js.map +1 -1
  189. package/dist/integrations/a2a-continuations-store.d.ts.map +1 -1
  190. package/dist/integrations/a2a-continuations-store.js +12 -0
  191. package/dist/integrations/a2a-continuations-store.js.map +1 -1
  192. package/dist/integrations/config-store.d.ts +1 -0
  193. package/dist/integrations/config-store.d.ts.map +1 -1
  194. package/dist/integrations/config-store.js +11 -0
  195. package/dist/integrations/config-store.js.map +1 -1
  196. package/dist/integrations/google-docs-poller.d.ts.map +1 -1
  197. package/dist/integrations/google-docs-poller.js +19 -2
  198. package/dist/integrations/google-docs-poller.js.map +1 -1
  199. package/dist/mcp/screen-memory-stdio.d.ts +7 -7
  200. package/dist/mcp/screen-memory-stdio.d.ts.map +1 -1
  201. package/dist/mcp-client/routes.d.ts.map +1 -1
  202. package/dist/mcp-client/routes.js +30 -2
  203. package/dist/mcp-client/routes.js.map +1 -1
  204. package/dist/notifications/routes.d.ts +6 -6
  205. package/dist/onboarding/plugin.d.ts.map +1 -1
  206. package/dist/onboarding/plugin.js +12 -8
  207. package/dist/onboarding/plugin.js.map +1 -1
  208. package/dist/org/accept-pending.d.ts.map +1 -1
  209. package/dist/org/accept-pending.js +2 -0
  210. package/dist/org/accept-pending.js.map +1 -1
  211. package/dist/org/auto-join-domain.d.ts.map +1 -1
  212. package/dist/org/auto-join-domain.js +2 -0
  213. package/dist/org/auto-join-domain.js.map +1 -1
  214. package/dist/org/context.d.ts.map +1 -1
  215. package/dist/org/context.js +29 -9
  216. package/dist/org/context.js.map +1 -1
  217. package/dist/org/handlers.d.ts.map +1 -1
  218. package/dist/org/handlers.js +5 -0
  219. package/dist/org/handlers.js.map +1 -1
  220. package/dist/org/request-org-cache.d.ts +16 -0
  221. package/dist/org/request-org-cache.d.ts.map +1 -0
  222. package/dist/org/request-org-cache.js +62 -0
  223. package/dist/org/request-org-cache.js.map +1 -0
  224. package/dist/progress/routes.d.ts +1 -1
  225. package/dist/resources/handlers.d.ts +1 -1
  226. package/dist/resources/store.d.ts.map +1 -1
  227. package/dist/resources/store.js +11 -9
  228. package/dist/resources/store.js.map +1 -1
  229. package/dist/scripts/runner.d.ts.map +1 -1
  230. package/dist/scripts/runner.js +3 -3
  231. package/dist/scripts/runner.js.map +1 -1
  232. package/dist/secrets/storage.d.ts.map +1 -1
  233. package/dist/secrets/storage.js +82 -5
  234. package/dist/secrets/storage.js.map +1 -1
  235. package/dist/server/action-change.d.ts +20 -0
  236. package/dist/server/action-change.d.ts.map +1 -1
  237. package/dist/server/action-change.js +29 -0
  238. package/dist/server/action-change.js.map +1 -1
  239. package/dist/server/action-routes.d.ts.map +1 -1
  240. package/dist/server/action-routes.js +7 -8
  241. package/dist/server/action-routes.js.map +1 -1
  242. package/dist/server/agent-teams.d.ts.map +1 -1
  243. package/dist/server/agent-teams.js +10 -2
  244. package/dist/server/agent-teams.js.map +1 -1
  245. package/dist/server/better-auth-instance.d.ts.map +1 -1
  246. package/dist/server/better-auth-instance.js +31 -14
  247. package/dist/server/better-auth-instance.js.map +1 -1
  248. package/dist/server/core-routes-plugin.d.ts +1 -0
  249. package/dist/server/core-routes-plugin.d.ts.map +1 -1
  250. package/dist/server/core-routes-plugin.js +18 -4
  251. package/dist/server/core-routes-plugin.js.map +1 -1
  252. package/dist/server/credential-provider.d.ts +16 -0
  253. package/dist/server/credential-provider.d.ts.map +1 -1
  254. package/dist/server/credential-provider.js +49 -13
  255. package/dist/server/credential-provider.js.map +1 -1
  256. package/dist/server/http-response-telemetry.d.ts.map +1 -1
  257. package/dist/server/http-response-telemetry.js +1 -0
  258. package/dist/server/http-response-telemetry.js.map +1 -1
  259. package/dist/server/poll.d.ts +6 -0
  260. package/dist/server/poll.d.ts.map +1 -1
  261. package/dist/server/poll.js +84 -47
  262. package/dist/server/poll.js.map +1 -1
  263. package/dist/server/realtime-token.d.ts +1 -1
  264. package/dist/server/voice-providers-status.d.ts.map +1 -1
  265. package/dist/server/voice-providers-status.js +12 -3
  266. package/dist/server/voice-providers-status.js.map +1 -1
  267. package/dist/workspace-files/tool.d.ts.map +1 -1
  268. package/dist/workspace-files/tool.js +18 -11
  269. package/dist/workspace-files/tool.js.map +1 -1
  270. package/docs/content/durable-background-runs.mdx +15 -6
  271. package/docs/design/durable-agent-runs.md +15 -11
  272. package/package.json +3 -3
  273. package/src/a2a/activity.ts +127 -10
  274. package/src/a2a/index.ts +3 -0
  275. package/src/agent/durable-background.ts +38 -28
  276. package/src/agent/harness/background.ts +12 -2
  277. package/src/agent/production-agent.ts +18 -15
  278. package/src/agent/run-store.ts +45 -3
  279. package/src/application-state/handlers.ts +45 -0
  280. package/src/application-state/index.ts +3 -0
  281. package/src/application-state/store.ts +38 -34
  282. package/src/audit/redact.ts +104 -18
  283. package/src/cli/create.ts +115 -16
  284. package/src/client/AgentPanel.tsx +2 -7
  285. package/src/client/AssistantChat.tsx +10 -2
  286. package/src/client/MultiTabAssistantChat.tsx +3 -0
  287. package/src/client/RunStuckBanner.tsx +29 -6
  288. package/src/client/agent-chat.ts +4 -9
  289. package/src/client/application-state.ts +115 -12
  290. package/src/client/dynamic-suggestions.ts +4 -12
  291. package/src/client/frame-protocol.ts +5 -1
  292. package/src/client/guided-questions.tsx +12 -23
  293. package/src/db/client.ts +148 -28
  294. package/src/db/create-get-db.ts +39 -6
  295. package/src/db/runtime-diagnostics.ts +41 -6
  296. package/src/deploy/build.ts +26 -33
  297. package/src/feature-flags/store.ts +10 -5
  298. package/src/integrations/a2a-continuations-store.ts +11 -0
  299. package/src/integrations/config-store.ts +13 -0
  300. package/src/integrations/google-docs-poller.ts +26 -2
  301. package/src/mcp-client/routes.ts +33 -2
  302. package/src/onboarding/plugin.ts +31 -25
  303. package/src/org/accept-pending.ts +2 -0
  304. package/src/org/auto-join-domain.ts +2 -0
  305. package/src/org/context.ts +35 -10
  306. package/src/org/handlers.ts +6 -0
  307. package/src/org/request-org-cache.ts +71 -0
  308. package/src/resources/store.ts +10 -9
  309. package/src/scripts/runner.ts +6 -3
  310. package/src/secrets/storage.ts +97 -5
  311. package/src/server/action-change.ts +33 -0
  312. package/src/server/action-routes.ts +11 -9
  313. package/src/server/agent-teams.ts +10 -2
  314. package/src/server/better-auth-instance.ts +39 -12
  315. package/src/server/core-routes-plugin.ts +19 -2
  316. package/src/server/credential-provider.ts +57 -14
  317. package/src/server/http-response-telemetry.ts +1 -0
  318. package/src/server/poll.ts +95 -57
  319. package/src/server/voice-providers-status.ts +19 -4
  320. package/src/workspace-files/tool.ts +18 -11
@@ -156,6 +156,43 @@ flattens into the baseline and reads as "no data."
156
156
  - Scheduled email reports render the same two scales, so a dual-axis panel is
157
157
  safe to put on a subscribed dashboard.
158
158
 
159
+ ## Reusable Native Dashboard Patterns
160
+
161
+ The recent extension-backed dashboards in Builder Analytics cluster into a few
162
+ repeatable compositions. Prefer these native panels, with a real SQL or Data
163
+ Program result behind each one, when creating a replacement or a new dashboard:
164
+
165
+ | Pattern | Native composition |
166
+ | --- | --- |
167
+ | Customer ROI / value realization | `metric` KPI cards, `line` or `area` trends, `table` detail, and `callout` or `section` panels for the business narrative |
168
+ | Account engagement / outreach | `metric` coverage and adoption cards, a daily `line` trend, `heatmap` or `table` segmentation, and `callout` alerts |
169
+ | GTM pipeline / cross-sell | `funnel` for ordered stages, `metric` totals, `bar` or `line` trends, and a `table` for account-level follow-up |
170
+ | Win/loss analysis | `section` groups with `metric`, `table`, `bar`, `callout`, and trend panels; use a Data Program for provider joins and evidence rows |
171
+
172
+ Funnel panels use `config.xKey` for the stage label and `config.yKey` for a
173
+ non-negative count or value. The renderer preserves the SQL row order, shows
174
+ each stage's share of the first stage, and shows the change from the previous
175
+ stage. Keep the intended stage order in SQL with `ORDER BY`.
176
+
177
+ When a dashboard is being migrated from an extension, create a new v2 copy,
178
+ bind its panels to the real provider schema or Data Programs, and compare it
179
+ with the original before retiring the extension-backed version. Do not invent
180
+ customer-specific SQL, provider joins, cached rows, or extension ids in a
181
+ catalog template. Existing dashboards remain readable while the native
182
+ replacement is validated. Bespoke interaction flows, arbitrary layouts, and
183
+ visualizations outside these contracts may remain Custom Blocks.
184
+
185
+ The source tree ships four provider-free v2 manifests in
186
+ `server/lib/native-v2-dashboards.ts`: Customer ROI, Account Engagement,
187
+ Cross-sell, and Win / Loss. They intentionally contain no customer names,
188
+ provider ids, SQL, cached rows, or guessed joins. After deployment, an
189
+ organization owner or admin provisions them with `ensure-native-v2-dashboards`
190
+ by supplying one real Data Program per binding key. The action validates the
191
+ stored program output contract, shares the programs with the organization,
192
+ creates deterministic `native-*-v2-*` dashboard copies, and preserves the
193
+ extension-backed originals and any existing v2 edits. Do not add these to the
194
+ root demo bootstrap or silently auto-bind them to guessed provider schemas.
195
+
159
196
  ## When To Use An Extension Instead
160
197
 
161
198
  Native Analytics dashboards are JSON configs rendered by the built-in dashboard
@@ -474,6 +511,7 @@ type PanelPatch = {
474
511
  | "table"
475
512
  | "pie"
476
513
  | "section"
514
+ | "funnel"
477
515
  | "heatmap"
478
516
  | "callout"
479
517
  | "extension";
@@ -49,7 +49,7 @@ type PanelPatch = {
49
49
  title?: string;
50
50
  sql?: string;
51
51
  source?: "bigquery" | "ga4" | "amplitude" | "first-party" | "demo" | "prometheus" | "program";
52
- chartType?: "line" | "area" | "bar" | "metric" | "table" | "pie" | "section" | "heatmap" | "callout" | "extension";
52
+ chartType?: "line" | "area" | "bar" | "metric" | "table" | "pie" | "funnel" | "section" | "heatmap" | "callout" | "extension";
53
53
  width?: number;
54
54
  columns?: number;
55
55
  tab?: string;
@@ -0,0 +1,299 @@
1
+ import { createHash } from "node:crypto";
2
+
3
+ import { defineAction, embedApp } from "@agent-native/core";
4
+ import { getDataProgram } from "@agent-native/core/data-programs";
5
+ import {
6
+ buildDeepLink,
7
+ getRequestOrgId,
8
+ getRequestUserEmail,
9
+ } from "@agent-native/core/server";
10
+ import { resolveAccess } from "@agent-native/core/sharing";
11
+ import setResourceVisibilityAction from "@agent-native/core/sharing/actions/set-resource-visibility";
12
+ import { z } from "zod";
13
+
14
+ import { getDashboard, upsertDashboard } from "../server/lib/dashboards-store";
15
+ import { requireAnalyticsAdminContext } from "../server/lib/db-admin-connections";
16
+ import {
17
+ compileNativeV2Dashboard,
18
+ getNativeV2DashboardManifest,
19
+ nativeV2DashboardManifests,
20
+ NATIVE_V2_DASHBOARD_IDS,
21
+ type NativeV2Binding,
22
+ type NativeV2BindingKey,
23
+ type NativeV2DashboardId,
24
+ } from "../server/lib/native-v2-dashboards";
25
+
26
+ const ANALYTICS_APP_ID = "analytics";
27
+
28
+ const bindingSchema = z.object({
29
+ programId: z.string().min(1),
30
+ params: z.record(z.string(), z.unknown()).optional(),
31
+ });
32
+
33
+ function orgDashboardId(
34
+ orgId: string,
35
+ templateId: NativeV2DashboardId,
36
+ ): string {
37
+ const suffix = createHash("sha256").update(orgId).digest("hex").slice(0, 10);
38
+ return `native-${templateId}-${suffix}`;
39
+ }
40
+
41
+ function parseOutputColumns(value: string | null): Set<string> {
42
+ if (!value) return new Set();
43
+ try {
44
+ const parsed = JSON.parse(value);
45
+ if (!Array.isArray(parsed)) return new Set();
46
+ return new Set(
47
+ parsed.flatMap((column) =>
48
+ column && typeof column === "object" && typeof column.name === "string"
49
+ ? [column.name]
50
+ : [],
51
+ ),
52
+ );
53
+ } catch {
54
+ return new Set();
55
+ }
56
+ }
57
+
58
+ function templateIdFromDashboard(
59
+ config: Record<string, unknown>,
60
+ ): string | null {
61
+ const catalog = config.catalog;
62
+ if (!catalog || typeof catalog !== "object" || Array.isArray(catalog)) {
63
+ return null;
64
+ }
65
+ const templateId = (catalog as Record<string, unknown>).templateId;
66
+ return typeof templateId === "string" ? templateId : null;
67
+ }
68
+
69
+ async function validateAndShareBindings(
70
+ bindings: Record<string, NativeV2Binding>,
71
+ requiredKeys: NativeV2BindingKey[],
72
+ requiredColumnsByBinding: Partial<Record<NativeV2BindingKey, string[]>>,
73
+ admin: { userEmail: string; orgId: string },
74
+ ): Promise<void> {
75
+ const missing = requiredKeys.filter((key) => !bindings[key]?.programId);
76
+ if (missing.length > 0) {
77
+ throw new Error(`Missing required v2 bindings: ${missing.join(", ")}`);
78
+ }
79
+
80
+ const uniqueProgramIds = new Set(
81
+ requiredKeys.map((key) => bindings[key]?.programId).filter(Boolean),
82
+ );
83
+ const programsToShare: string[] = [];
84
+ for (const programId of uniqueProgramIds) {
85
+ const program = await getDataProgram(programId, ANALYTICS_APP_ID);
86
+ if (!program) {
87
+ throw new Error(`Data program not found: ${programId}`);
88
+ }
89
+ if (program.orgId && program.orgId !== admin.orgId) {
90
+ throw new Error(
91
+ `Data program belongs to another organization: ${programId}`,
92
+ );
93
+ }
94
+ if (program.archivedAt) {
95
+ throw new Error(`Data program is archived: ${programId}`);
96
+ }
97
+ const access = await resolveAccess("data_program", programId, {
98
+ userEmail: admin.userEmail,
99
+ orgId: admin.orgId,
100
+ });
101
+ if (!access) {
102
+ throw new Error(`No access to data program: ${programId}`);
103
+ }
104
+ const outputColumns = parseOutputColumns(program.outputColumns);
105
+ const missingColumns = [
106
+ ...new Set(
107
+ requiredKeys.flatMap((key) =>
108
+ bindings[key]?.programId === programId
109
+ ? (requiredColumnsByBinding[key] ?? [])
110
+ : [],
111
+ ),
112
+ ),
113
+ ].filter((column) => !outputColumns.has(column));
114
+ if (missingColumns.length > 0) {
115
+ throw new Error(
116
+ `Data program ${programId} is missing required output columns: ${missingColumns.join(", ")}. Re-save it after a successful dry-run.`,
117
+ );
118
+ }
119
+
120
+ if (program.visibility !== "org" || program.orgId !== admin.orgId) {
121
+ programsToShare.push(programId);
122
+ }
123
+ }
124
+
125
+ for (const programId of programsToShare) {
126
+ await setResourceVisibilityAction.run({
127
+ resourceType: "data_program",
128
+ resourceId: programId,
129
+ visibility: "org",
130
+ });
131
+ }
132
+ }
133
+
134
+ export interface EnsureNativeV2DashboardsResult {
135
+ orgId: string;
136
+ dashboards: Array<{
137
+ templateId: NativeV2DashboardId;
138
+ dashboardId: string;
139
+ status: "created" | "updated" | "preserved";
140
+ requiredBindings: NativeV2BindingKey[];
141
+ }>;
142
+ }
143
+
144
+ export async function ensureNativeV2Dashboards(
145
+ args: {
146
+ templateIds?: NativeV2DashboardId[];
147
+ bindings: Record<string, NativeV2Binding>;
148
+ overwrite: boolean;
149
+ },
150
+ ctx?: { userEmail?: string; orgId?: string | null },
151
+ ): Promise<EnsureNativeV2DashboardsResult> {
152
+ const admin = await requireAnalyticsAdminContext(ctx);
153
+ const templateIds = args.templateIds?.length
154
+ ? args.templateIds
155
+ : [...NATIVE_V2_DASHBOARD_IDS];
156
+ const manifests = templateIds.map(getNativeV2DashboardManifest);
157
+ const allRequiredBindings = [
158
+ ...new Set(manifests.flatMap((manifest) => manifest.requiredBindings)),
159
+ ];
160
+ const requiredColumnsByBinding: Partial<
161
+ Record<NativeV2BindingKey, string[]>
162
+ > = Object.fromEntries(
163
+ manifests.flatMap((manifest) =>
164
+ manifest.panels
165
+ .filter(
166
+ (panel): panel is typeof panel & { bindingKey: NativeV2BindingKey } =>
167
+ Boolean(panel.bindingKey),
168
+ )
169
+ .map((panel) => [panel.bindingKey, panel.requiredColumns ?? []]),
170
+ ),
171
+ );
172
+
173
+ await validateAndShareBindings(
174
+ args.bindings,
175
+ allRequiredBindings,
176
+ requiredColumnsByBinding,
177
+ admin,
178
+ );
179
+
180
+ const installedAt = new Date().toISOString();
181
+ const dashboards: EnsureNativeV2DashboardsResult["dashboards"] = [];
182
+ for (const manifest of manifests) {
183
+ const dashboardId = orgDashboardId(admin.orgId, manifest.id);
184
+ const existing = await getDashboard(dashboardId, {
185
+ email: admin.userEmail,
186
+ orgId: admin.orgId,
187
+ });
188
+ if (existing && templateIdFromDashboard(existing.config) !== manifest.id) {
189
+ throw new Error(
190
+ `Refusing to overwrite unrelated dashboard at deterministic id ${dashboardId}.`,
191
+ );
192
+ }
193
+
194
+ if (existing && !args.overwrite) {
195
+ dashboards.push({
196
+ templateId: manifest.id,
197
+ dashboardId,
198
+ status: "preserved",
199
+ requiredBindings: manifest.requiredBindings,
200
+ });
201
+ continue;
202
+ }
203
+
204
+ const config = compileNativeV2Dashboard(
205
+ manifest,
206
+ args.bindings,
207
+ installedAt,
208
+ );
209
+ await upsertDashboard(
210
+ dashboardId,
211
+ "sql",
212
+ config as unknown as Record<string, unknown>,
213
+ {
214
+ email: admin.userEmail,
215
+ orgId: admin.orgId,
216
+ },
217
+ );
218
+ if (!existing) {
219
+ await setResourceVisibilityAction.run({
220
+ resourceType: "dashboard",
221
+ resourceId: dashboardId,
222
+ visibility: "org",
223
+ });
224
+ }
225
+ dashboards.push({
226
+ templateId: manifest.id,
227
+ dashboardId,
228
+ status: existing ? "updated" : "created",
229
+ requiredBindings: manifest.requiredBindings,
230
+ });
231
+ }
232
+
233
+ return { orgId: admin.orgId, dashboards };
234
+ }
235
+
236
+ export default defineAction({
237
+ description:
238
+ "Provision the four reusable native Analytics v2 dashboard patterns for the active organization from explicit Data Program bindings. " +
239
+ "This is an admin-only post-deploy action: it validates every program exists and exposes the manifest's required output columns, shares the bound programs with the organization, creates deterministic native-* dashboard copies, and never changes the legacy extension dashboards. " +
240
+ "Re-running preserves existing v2 dashboard edits unless overwrite=true.",
241
+ schema: z.object({
242
+ templateIds: z
243
+ .array(z.enum(NATIVE_V2_DASHBOARD_IDS))
244
+ .min(1)
245
+ .optional()
246
+ .describe("Optional subset of native v2 templates to provision."),
247
+ bindings: z
248
+ .record(z.string(), bindingSchema)
249
+ .describe(
250
+ 'Binding key to stored Analytics Data Program, e.g. {"roi.kpi": {"programId": "dp_..."}}.',
251
+ ),
252
+ overwrite: z
253
+ .boolean()
254
+ .optional()
255
+ .default(false)
256
+ .describe("Replace an existing v2 copy only when explicitly true."),
257
+ }),
258
+ mcpApp: {
259
+ compactCatalog: true,
260
+ resource: embedApp({
261
+ title: "Native Analytics v2 dashboards",
262
+ description: "Open the provisioned native v2 dashboard in Analytics.",
263
+ iframeTitle: "Agent-Native Analytics",
264
+ openLabel: "Open native v2 dashboard",
265
+ height: 680,
266
+ }),
267
+ },
268
+ run: async (args, ctx) => {
269
+ const userEmail = ctx?.userEmail ?? getRequestUserEmail();
270
+ const orgId = ctx?.orgId ?? getRequestOrgId() ?? null;
271
+ return ensureNativeV2Dashboards(
272
+ {
273
+ templateIds: args.templateIds,
274
+ bindings: args.bindings,
275
+ overwrite: args.overwrite,
276
+ },
277
+ { userEmail, orgId },
278
+ );
279
+ },
280
+ link: ({ result }) => {
281
+ const dashboardId =
282
+ result && typeof result === "object"
283
+ ? (result as EnsureNativeV2DashboardsResult).dashboards?.[0]
284
+ ?.dashboardId
285
+ : null;
286
+ if (!dashboardId) return null;
287
+ return {
288
+ url: buildDeepLink({
289
+ app: "analytics",
290
+ view: "adhoc",
291
+ params: { dashboardId },
292
+ }),
293
+ label: "Open native v2 dashboard",
294
+ view: "adhoc",
295
+ };
296
+ },
297
+ });
298
+
299
+ export { nativeV2DashboardManifests };
@@ -0,0 +1,34 @@
1
+ import { defineAction } from "@agent-native/core";
2
+ import { z } from "zod";
3
+
4
+ import {
5
+ nativeV2DashboardManifests,
6
+ NATIVE_V2_DASHBOARD_VERSION,
7
+ } from "../server/lib/native-v2-dashboards";
8
+
9
+ export default defineAction({
10
+ description:
11
+ "List the source-controlled native Analytics v2 dashboard manifests, including required Data Program binding keys and output columns. These provider-free manifests are compiled into org dashboards by ensure-native-v2-dashboards.",
12
+ schema: z.object({}),
13
+ http: { method: "GET" },
14
+ readOnly: true,
15
+ publicAgent: { expose: true, readOnly: true, requiresAuth: true },
16
+ run: async () => ({
17
+ version: NATIVE_V2_DASHBOARD_VERSION,
18
+ templates: nativeV2DashboardManifests.map((manifest) => ({
19
+ id: manifest.id,
20
+ name: manifest.name,
21
+ description: manifest.description,
22
+ category: manifest.category,
23
+ tags: manifest.tags,
24
+ requiredBindings: manifest.requiredBindings,
25
+ panels: manifest.panels.map((panel) => ({
26
+ id: panel.id,
27
+ title: panel.title,
28
+ chartType: panel.chartType,
29
+ bindingKey: panel.bindingKey ?? null,
30
+ requiredColumns: panel.requiredColumns ?? [],
31
+ })),
32
+ })),
33
+ }),
34
+ });
@@ -64,6 +64,8 @@ import {
64
64
  import { useChartTooltipPortalPosition } from "@/hooks/use-chart-tooltip-portal";
65
65
 
66
66
  const PAGE_SIZE_OPTIONS = [10, 25, 50, 100];
67
+ import { resolveDashboardFunnelRows } from "@shared/dashboard-funnel";
68
+
67
69
  import { createDemoChartTrendRows } from "@/lib/demo-chart-trend";
68
70
  import { useSqlQuery } from "@/lib/sql-query";
69
71
  import {
@@ -1454,6 +1456,12 @@ export function SqlChart({
1454
1456
  );
1455
1457
  }
1456
1458
 
1459
+ if (chartType === "funnel") {
1460
+ return withConfigWarning(
1461
+ <FunnelRenderer rows={rows} panel={panel} colors={colors} />,
1462
+ );
1463
+ }
1464
+
1457
1465
  if (chartType === "heatmap") {
1458
1466
  return withConfigWarning(<HeatmapRenderer rows={rows} panel={panel} />);
1459
1467
  }
@@ -2350,6 +2358,80 @@ function TimeSeriesRenderer({
2350
2358
  );
2351
2359
  }
2352
2360
 
2361
+ function FunnelRenderer({
2362
+ rows,
2363
+ panel,
2364
+ colors,
2365
+ }: {
2366
+ rows: Record<string, unknown>[];
2367
+ panel: SqlPanel;
2368
+ colors: string[];
2369
+ }) {
2370
+ const t = useT();
2371
+ const funnel = useMemo(
2372
+ () =>
2373
+ resolveDashboardFunnelRows(rows, panel.config?.xKey, panel.config?.yKey),
2374
+ [rows, panel.config?.xKey, panel.config?.yKey],
2375
+ );
2376
+
2377
+ if (funnel.items.length === 0) {
2378
+ return (
2379
+ <div
2380
+ className={`flex items-center justify-center py-8 ${TABLE_PANEL_MIN_HEIGHT_CLASS}`}
2381
+ >
2382
+ <p className="text-sm text-muted-foreground text-center">
2383
+ {t("common.noData")}
2384
+ </p>
2385
+ </div>
2386
+ );
2387
+ }
2388
+
2389
+ const maxValue = Math.max(...funnel.items.map((item) => item.value), 1);
2390
+ const formatter = panel.config?.yFormatter;
2391
+ const funnelColors = colors.length > 0 ? colors : DEFAULT_COLORS;
2392
+
2393
+ return (
2394
+ <div
2395
+ className="space-y-3 py-2"
2396
+ role="img"
2397
+ aria-label={panel.title}
2398
+ data-dashboard-funnel="true"
2399
+ >
2400
+ {funnel.items.map((item, index) => {
2401
+ const width =
2402
+ item.value > 0 ? Math.max(2, (item.value / maxValue) * 100) : 0;
2403
+ const dropOff =
2404
+ item.dropOffPercent === null
2405
+ ? null
2406
+ : `${item.dropOffPercent < 0 ? "↑" : "↓"} ${Math.abs(item.dropOffPercent).toFixed(1)}%`;
2407
+ return (
2408
+ <div key={`${item.label}-${index}`} className="space-y-1">
2409
+ <div className="flex items-baseline justify-between gap-3 text-xs">
2410
+ <span className="min-w-0 truncate font-medium text-foreground">
2411
+ {item.label}
2412
+ </span>
2413
+ <span className="shrink-0 tabular-nums text-muted-foreground">
2414
+ {formatYValue(item.value, formatter)} ·{" "}
2415
+ {item.percentOfFirst.toFixed(1)}%
2416
+ {dropOff ? ` · ${dropOff}` : ""}
2417
+ </span>
2418
+ </div>
2419
+ <div className="h-5 overflow-hidden rounded-sm bg-muted/60">
2420
+ <div
2421
+ className="h-full rounded-sm transition-[width]"
2422
+ style={{
2423
+ width: `${width}%`,
2424
+ backgroundColor: funnelColors[index % funnelColors.length],
2425
+ }}
2426
+ />
2427
+ </div>
2428
+ </div>
2429
+ );
2430
+ })}
2431
+ </div>
2432
+ );
2433
+ }
2434
+
2353
2435
  // Heatmap config: `xKey` = x-axis column, `yKey` = numeric value column,
2354
2436
  // `color` = optional row-label column. If `color` is omitted, the renderer
2355
2437
  // auto-detects the row-label as the first non-x non-value string column.
@@ -22,9 +22,9 @@ const DASHBOARD_CONTEXT =
22
22
  "NATIVE PANELS — Create a SQL-driven dashboard by calling the `update-dashboard` action with `dashboardId` and `config`; use a Data Program first when reusable fetching or transformation is the missing layer. " +
23
23
  "The config shape is: { name: string, panels: [{ id, title, sql, source, chartType, width, tab?, config? }] }. " +
24
24
  "Each panel needs: id (unique string), title, sql (the query), source ('bigquery' | 'ga4' | 'amplitude' | 'first-party' | 'demo' | 'prometheus'), " +
25
- "chartType ('line' | 'area' | 'bar' | 'metric' | 'table' | 'pie'), width (1 or 2). " +
25
+ "chartType ('line' | 'area' | 'bar' | 'metric' | 'table' | 'pie' | 'funnel' | 'heatmap' | 'callout' | 'section'), width (1 or 2). " +
26
26
  "Optional tab labels can use 'Group / Tab' for primary and secondary dashboard tabs. " +
27
- "Optional config: { xKey, yKey, yKeys, color, colors, yFormatter ('number'|'currency'|'percent'), description, valueLabels }. " +
27
+ "Optional config: { xKey, yKey, yKeys, color, colors, yFormatter ('number'|'currency'|'percent'), description, valueLabels }. For funnel panels, xKey is the stage label, yKey is the non-negative value, and SQL ORDER BY defines stage order. " +
28
28
  "For first-party analytics, source is 'first-party' and sql may read analytics_events only; do not use db-query for datasource panels. " +
29
29
  "For the built-in demo dashboard, source is 'demo' and sql uses the same Prometheus JSON descriptor shape as source 'prometheus': { promql, mode, range, step }. " +
30
30
  "Call `data-source-status` if you need to see which data sources are connected. " +
@@ -18,6 +18,7 @@ const DASHBOARD_CONTEXT_KEY = "analytics-selected-dashboard";
18
18
  const DASHBOARD_PANEL_CONTEXT_KEY = "analytics-selected-dashboard-panel";
19
19
  const SELECTED_OBJECT_STATE_KEY = "selected-object";
20
20
  const SELECTED_OBJECT_SOURCE_FIELD = "__agentNativeSelectedObjectSource";
21
+ const CONTEXT_PUBLISH_DELAY_MS = 250;
21
22
 
22
23
  export interface DashboardChatContextArgs {
23
24
  id: string | null | undefined;
@@ -233,56 +234,61 @@ export function useDashboardChatContext(
233
234
  );
234
235
  }, []);
235
236
 
237
+ // Dashboard metadata lands in pieces — title first, then panel count, then
238
+ // the access role. Publishing each piece costs a round-trip and a sync event
239
+ // that invalidates every mounted query, so only the settled value is sent.
236
240
  useEffect(() => {
237
241
  if (!id) return;
238
242
  const displayTitle = title?.trim() || id;
239
-
240
- setAgentChatContextItem({
241
- key: DASHBOARD_CONTEXT_KEY,
242
- title: `Dashboard: ${displayTitle}`,
243
- context: dashboardContext({
244
- id,
245
- kind,
246
- title: displayTitle,
247
- panelCount,
248
- canEdit,
249
- }),
250
- openSidebar: false,
251
- focus: false,
252
- });
253
-
254
- return () => {
255
- removeAgentChatContextItem({
243
+ const timer = setTimeout(() => {
244
+ setAgentChatContextItem({
256
245
  key: DASHBOARD_CONTEXT_KEY,
246
+ title: `Dashboard: ${displayTitle}`,
247
+ context: dashboardContext({
248
+ id,
249
+ kind,
250
+ title: displayTitle,
251
+ panelCount,
252
+ canEdit,
253
+ }),
257
254
  openSidebar: false,
255
+ focus: false,
258
256
  });
259
- };
257
+ }, CONTEXT_PUBLISH_DELAY_MS);
258
+ return () => clearTimeout(timer);
260
259
  }, [canEdit, id, kind, panelCount, title]);
261
260
 
262
261
  useEffect(() => {
263
262
  if (!id || hasSelectedPanel) return;
264
263
  const displayTitle = title?.trim() || id;
265
- setClientAppState(
266
- SELECTED_OBJECT_STATE_KEY,
267
- {
268
- type: "dashboard",
269
- id,
270
- kind,
271
- title: displayTitle,
272
- panelCount,
273
- canEdit,
274
- [SELECTED_OBJECT_SOURCE_FIELD]: TAB_ID,
275
- },
276
- {
277
- keepalive: true,
278
- requestSource: TAB_ID,
279
- },
280
- ).catch(() => {});
264
+ const timer = setTimeout(() => {
265
+ setClientAppState(
266
+ SELECTED_OBJECT_STATE_KEY,
267
+ {
268
+ type: "dashboard",
269
+ id,
270
+ kind,
271
+ title: displayTitle,
272
+ panelCount,
273
+ canEdit,
274
+ [SELECTED_OBJECT_SOURCE_FIELD]: TAB_ID,
275
+ },
276
+ {
277
+ keepalive: true,
278
+ requestSource: TAB_ID,
279
+ },
280
+ ).catch(() => {});
281
+ }, CONTEXT_PUBLISH_DELAY_MS);
282
+ return () => clearTimeout(timer);
281
283
  }, [canEdit, hasSelectedPanel, id, kind, panelCount, title]);
282
284
 
283
285
  useEffect(() => {
284
286
  if (!id) return;
285
287
  return () => {
288
+ removeAgentChatContextItem({
289
+ key: DASHBOARD_CONTEXT_KEY,
290
+ openSidebar: false,
291
+ });
286
292
  removeAgentChatContextItem({
287
293
  key: DASHBOARD_PANEL_CONTEXT_KEY,
288
294
  openSidebar: false,
@@ -32,13 +32,13 @@ export function useUserPref<T extends Record<string, unknown>>(key: string) {
32
32
  queryClient.setQueryData(queryKey, value);
33
33
  return { previousValue };
34
34
  },
35
+ // No invalidate on success: the optimistic value above is exactly what the
36
+ // server stored, so refetching it only buys a round-trip. `onError` is the
37
+ // path that restores truth.
35
38
  onError: (_err, _value, context) => {
36
39
  queryClient.setQueryData(queryKey, context?.previousValue);
37
40
  toast.error("Failed to save preference");
38
41
  },
39
- onSettled: () => {
40
- queryClient.invalidateQueries({ queryKey });
41
- },
42
42
  });
43
43
 
44
44
  const { mutate: remove } = useMutation({