@agent-native/core 0.120.0 → 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 (207) hide show
  1. package/corpus/README.md +2 -2
  2. package/corpus/core/CHANGELOG.md +16 -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/MultiTabAssistantChat.tsx +45 -0
  9. package/corpus/core/src/client/extensions/ExtensionsListPage.tsx +16 -3
  10. package/corpus/core/src/client/settings/ExtensionsSettingsContent.tsx +24 -0
  11. package/corpus/core/src/client/settings/SettingsPanel.tsx +10 -0
  12. package/corpus/core/src/client/use-chat-models.ts +45 -0
  13. package/corpus/core/src/deploy/build.ts +3 -0
  14. package/corpus/core/src/server/analytics.ts +164 -20
  15. package/corpus/core/src/server/auth.ts +17 -13
  16. package/corpus/core/src/server/better-auth-instance.ts +13 -1
  17. package/corpus/core/src/templates/chat/app/components/layout/Sidebar.tsx +35 -6
  18. package/corpus/core/src/templates/chat/app/routes/extensions._index.tsx +2 -2
  19. package/corpus/core/src/templates/chat/netlify.toml +1 -0
  20. package/corpus/core/src/templates/workspace-core/.agents/skills/extensions/SKILL.md +16 -0
  21. package/corpus/core/src/vite/client.ts +6 -0
  22. package/corpus/templates/analytics/.agents/skills/adhoc-analysis/SKILL.md +27 -43
  23. package/corpus/templates/analytics/.agents/skills/dashboard-management/SKILL.md +19 -8
  24. package/corpus/templates/analytics/.agents/skills/data-querying/SKILL.md +2 -2
  25. package/corpus/templates/analytics/AGENTS.md +20 -8
  26. package/corpus/templates/analytics/actions/get-explorer-dashboard.ts +3 -0
  27. package/corpus/templates/analytics/actions/list-dashboard-usage-stats.ts +140 -46
  28. package/corpus/templates/analytics/actions/migrate-analytics-artifacts.ts +48 -0
  29. package/corpus/templates/analytics/actions/save-analysis.ts +2 -3
  30. package/corpus/templates/analytics/actions/view-screen.ts +1 -1
  31. package/corpus/templates/analytics/app/components/dashboard/DashboardMetadata.tsx +77 -0
  32. package/corpus/templates/analytics/app/components/dashboard/DashboardPanelSkeleton.tsx +7 -1
  33. package/corpus/templates/analytics/app/components/layout/NewDashboardDialog.tsx +1 -0
  34. package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +103 -502
  35. package/corpus/templates/analytics/app/global.css +30 -0
  36. package/corpus/templates/analytics/app/hooks/use-navigation-state.ts +2 -2
  37. package/corpus/templates/analytics/app/i18n-data.ts +80 -0
  38. package/corpus/templates/analytics/app/pages/Agents.tsx +49 -2
  39. package/corpus/templates/analytics/app/pages/adhoc/explorer-dashboard/index.tsx +54 -11
  40. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/SqlChartCard.tsx +3 -53
  41. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +23 -25
  42. package/corpus/templates/analytics/app/pages/analyses/AnalysisDetail.tsx +2 -2
  43. package/corpus/templates/analytics/app/routes/analyses._index.tsx +3 -2
  44. package/corpus/templates/analytics/app/routes/extensions._index.tsx +2 -2
  45. package/corpus/templates/analytics/changelog/2026-07-23-analytics-now-groups-saved-analyses-and-embedded-extensions-.md +6 -0
  46. package/corpus/templates/analytics/changelog/2026-07-23-dashboard-menus-show-who-created-and-last-updated-each-dashb.md +6 -0
  47. package/corpus/templates/analytics/changelog/2026-07-23-dashboard-usage-now-loads-reliably-for-organizations-with-ma.md +6 -0
  48. package/corpus/templates/analytics/changelog/2026-07-23-long-dashboard-building-requests-now-continue-without-stoppi.md +6 -0
  49. package/corpus/templates/analytics/netlify.toml +4 -0
  50. package/corpus/templates/analytics/server/db/schema.ts +4 -0
  51. package/corpus/templates/analytics/server/lib/agent-chat-plan-mode.ts +0 -1
  52. package/corpus/templates/analytics/server/lib/agent-readable-resource-context.ts +1 -0
  53. package/corpus/templates/analytics/server/lib/migrate-analytics-artifacts.ts +1062 -0
  54. package/corpus/templates/analytics/server/plugins/agent-chat.ts +6 -52
  55. package/corpus/templates/analytics/server/plugins/core-routes.ts +1 -1
  56. package/corpus/templates/analytics/server/plugins/db.ts +10 -0
  57. package/corpus/templates/assets/actions/generate-image-batch.ts +36 -1
  58. package/corpus/templates/assets/actions/generate-image.ts +28 -1
  59. package/corpus/templates/assets/app/components/layout/Sidebar.tsx +47 -9
  60. package/corpus/templates/assets/app/routes/extensions._index.tsx +2 -2
  61. package/corpus/templates/assets/changelog/2026-07-23-assets-image-generation-now-recovers-after-a-completed-tool-.md +6 -0
  62. package/corpus/templates/assets/changelog/2026-07-23-new-chat-aligns-with-sidebar-controls.md +6 -0
  63. package/corpus/templates/assets/netlify.toml +1 -0
  64. package/corpus/templates/brain/app/components/layout/Sidebar.tsx +38 -8
  65. package/corpus/templates/brain/app/routes/extensions._index.tsx +2 -2
  66. package/corpus/templates/brain/netlify.toml +1 -0
  67. package/corpus/templates/calendar/app/components/layout/Sidebar.tsx +43 -5
  68. package/corpus/templates/calendar/app/routes/_app.extensions._index.tsx +2 -16
  69. package/corpus/templates/calendar/netlify.toml +1 -0
  70. package/corpus/templates/chat/app/components/layout/Sidebar.tsx +35 -6
  71. package/corpus/templates/chat/app/routes/extensions._index.tsx +2 -2
  72. package/corpus/templates/chat/netlify.toml +1 -0
  73. package/corpus/templates/clips/app/components/library/library-layout.tsx +52 -6
  74. package/corpus/templates/clips/app/routes/_app.extensions._index.tsx +2 -16
  75. package/corpus/templates/clips/changelog/2026-07-23-agent-settings-are-clearly-labeled-manage-agent-in-the-sideb.md +6 -0
  76. package/corpus/templates/clips/changelog/2026-07-23-meeting-recording-meters-and-dismissed-prompts.md +6 -0
  77. package/corpus/templates/clips/desktop/src/hooks/useMicMeter.ts +1 -1
  78. package/corpus/templates/clips/desktop/src/lib/meeting-notification-dismissal.ts +2 -0
  79. package/corpus/templates/clips/desktop/src/lib/voice-dictation.ts +1 -3
  80. package/corpus/templates/clips/desktop/src/overlays/live-audio-bars.tsx +86 -0
  81. package/corpus/templates/clips/desktop/src/overlays/meeting-notification.tsx +24 -0
  82. package/corpus/templates/clips/desktop/src/overlays/recording-pill.tsx +5 -69
  83. package/corpus/templates/clips/desktop/src/styles.css +35 -3
  84. package/corpus/templates/clips/desktop/src-tauri/src/adhoc_meetings_watcher.rs +13 -44
  85. package/corpus/templates/clips/desktop/src-tauri/src/notifications.rs +119 -17
  86. package/corpus/templates/clips/netlify.toml +1 -0
  87. package/corpus/templates/content/app/components/sidebar/DocumentSidebar.tsx +3 -7
  88. package/corpus/templates/content/app/routes/_app.extensions._index.tsx +2 -2
  89. package/corpus/templates/content/changelog/2026-07-23-agent-settings-are-clearly-labeled-manage-agent-in-the-sideb.md +6 -0
  90. package/corpus/templates/content/netlify.toml +1 -0
  91. package/corpus/templates/design/app/components/layout/Sidebar.tsx +55 -17
  92. package/corpus/templates/design/app/routes/extensions._index.tsx +2 -2
  93. package/corpus/templates/design/changelog/2026-07-23-agent-settings-are-clearly-labeled-manage-agent-in-the-sideb.md +6 -0
  94. package/corpus/templates/design/netlify.toml +1 -0
  95. package/corpus/templates/dispatch/app/dispatch-extensions.tsx +1 -1
  96. package/corpus/templates/dispatch/changelog/2026-07-23-agent-settings-are-clearly-labeled-manage-agent-in-the-sideb.md +6 -0
  97. package/corpus/templates/dispatch/netlify.toml +1 -0
  98. package/corpus/templates/forms/.agents/skills/form-publishing/SKILL.md +9 -3
  99. package/corpus/templates/forms/AGENTS.md +11 -1
  100. package/corpus/templates/forms/README.md +24 -1
  101. package/corpus/templates/forms/actions/update-form.ts +12 -5
  102. package/corpus/templates/forms/app/components/builder/FieldPropertiesPanel.tsx +199 -1
  103. package/corpus/templates/forms/app/components/layout/Sidebar.tsx +41 -45
  104. package/corpus/templates/forms/app/i18n/en-US.ts +13 -1
  105. package/corpus/templates/forms/app/pages/FormBuilderPage.tsx +35 -4
  106. package/corpus/templates/forms/app/pages/FormFillPage.tsx +7 -16
  107. package/corpus/templates/forms/app/routes/_app.extensions._index.tsx +2 -2
  108. package/corpus/templates/forms/changelog/2026-07-23-agent-settings-are-clearly-labeled-manage-agent-in-the-sideb.md +6 -0
  109. package/corpus/templates/forms/changelog/2026-07-23-forms-can-branch-into-follow-up-questions-and-route-response.md +6 -0
  110. package/corpus/templates/forms/netlify.toml +1 -0
  111. package/corpus/templates/forms/server/handlers/submissions.ts +22 -31
  112. package/corpus/templates/forms/server/lib/integrations.ts +11 -2
  113. package/corpus/templates/forms/server/lib/public-form-ssr.ts +26 -5
  114. package/corpus/templates/forms/server/lib/validate-fields.ts +48 -11
  115. package/corpus/templates/forms/server/plugins/agent-chat.ts +1 -0
  116. package/corpus/templates/forms/shared/conditional.ts +67 -0
  117. package/corpus/templates/macros/app/components/layout/AppLayout.tsx +36 -5
  118. package/corpus/templates/macros/app/routes/extensions._index.tsx +2 -2
  119. package/corpus/templates/macros/netlify.toml +1 -0
  120. package/corpus/templates/mail/app/components/layout/AppLayout.tsx +0 -9
  121. package/corpus/templates/mail/app/routes/extensions._index.tsx +2 -2
  122. package/corpus/templates/mail/netlify.toml +1 -0
  123. package/corpus/templates/plan/app/components/layout/Sidebar.tsx +38 -5
  124. package/corpus/templates/plan/app/routes/extensions._index.tsx +2 -2
  125. package/corpus/templates/plan/netlify.toml +1 -0
  126. package/corpus/templates/slides/app/components/layout/Sidebar.tsx +47 -4
  127. package/corpus/templates/slides/app/routes/extensions._index.tsx +2 -2
  128. package/corpus/templates/slides/netlify.toml +1 -0
  129. package/corpus/templates/tasks/app/components/layout/Sidebar.tsx +37 -6
  130. package/corpus/templates/tasks/app/routes/extensions._index.tsx +2 -2
  131. package/corpus/templates/tasks/app/routes/settings.tsx +31 -0
  132. package/corpus/templates/tasks/netlify.toml +1 -0
  133. package/dist/agent/production-agent.d.ts.map +1 -1
  134. package/dist/agent/production-agent.js +4 -0
  135. package/dist/agent/production-agent.js.map +1 -1
  136. package/dist/agent/run-manager.d.ts +9 -0
  137. package/dist/agent/run-manager.d.ts.map +1 -1
  138. package/dist/agent/run-manager.js +25 -10
  139. package/dist/agent/run-manager.js.map +1 -1
  140. package/dist/agent/types.d.ts +2 -2
  141. package/dist/agent/types.d.ts.map +1 -1
  142. package/dist/agent/types.js.map +1 -1
  143. package/dist/client/MultiTabAssistantChat.d.ts.map +1 -1
  144. package/dist/client/MultiTabAssistantChat.js +35 -0
  145. package/dist/client/MultiTabAssistantChat.js.map +1 -1
  146. package/dist/client/extensions/ExtensionsListPage.d.ts +5 -1
  147. package/dist/client/extensions/ExtensionsListPage.d.ts.map +1 -1
  148. package/dist/client/extensions/ExtensionsListPage.js +5 -3
  149. package/dist/client/extensions/ExtensionsListPage.js.map +1 -1
  150. package/dist/client/settings/ExtensionsSettingsContent.d.ts +2 -0
  151. package/dist/client/settings/ExtensionsSettingsContent.d.ts.map +1 -0
  152. package/dist/client/settings/ExtensionsSettingsContent.js +10 -0
  153. package/dist/client/settings/ExtensionsSettingsContent.js.map +1 -0
  154. package/dist/client/settings/SettingsPanel.d.ts.map +1 -1
  155. package/dist/client/settings/SettingsPanel.js +10 -1
  156. package/dist/client/settings/SettingsPanel.js.map +1 -1
  157. package/dist/client/use-chat-models.d.ts +1 -0
  158. package/dist/client/use-chat-models.d.ts.map +1 -1
  159. package/dist/client/use-chat-models.js +33 -0
  160. package/dist/client/use-chat-models.js.map +1 -1
  161. package/dist/collab/awareness.d.ts +2 -2
  162. package/dist/collab/awareness.d.ts.map +1 -1
  163. package/dist/deploy/build.js +1 -0
  164. package/dist/deploy/build.js.map +1 -1
  165. package/dist/file-upload/actions/upload-image.d.ts +1 -1
  166. package/dist/observability/routes.d.ts +3 -3
  167. package/dist/provider-api/actions/custom-provider-registration.d.ts +4 -4
  168. package/dist/provider-api/actions/provider-api.d.ts +2 -2
  169. package/dist/server/analytics.d.ts +17 -4
  170. package/dist/server/analytics.d.ts.map +1 -1
  171. package/dist/server/analytics.js +122 -17
  172. package/dist/server/analytics.js.map +1 -1
  173. package/dist/server/auth.d.ts.map +1 -1
  174. package/dist/server/auth.js +5 -4
  175. package/dist/server/auth.js.map +1 -1
  176. package/dist/server/better-auth-instance.d.ts +8 -0
  177. package/dist/server/better-auth-instance.d.ts.map +1 -1
  178. package/dist/server/better-auth-instance.js +10 -1
  179. package/dist/server/better-auth-instance.js.map +1 -1
  180. package/dist/templates/chat/app/components/layout/Sidebar.tsx +35 -6
  181. package/dist/templates/chat/app/routes/extensions._index.tsx +2 -2
  182. package/dist/templates/chat/netlify.toml +1 -0
  183. package/dist/templates/workspace-core/.agents/skills/extensions/SKILL.md +16 -0
  184. package/dist/vite/client.d.ts.map +1 -1
  185. package/dist/vite/client.js +2 -0
  186. package/dist/vite/client.js.map +1 -1
  187. package/docs/content/extensions.mdx +23 -4
  188. package/package.json +1 -1
  189. package/src/agent/production-agent.ts +4 -0
  190. package/src/agent/run-manager.ts +36 -10
  191. package/src/agent/types.ts +2 -0
  192. package/src/client/MultiTabAssistantChat.tsx +45 -0
  193. package/src/client/extensions/ExtensionsListPage.tsx +16 -3
  194. package/src/client/settings/ExtensionsSettingsContent.tsx +24 -0
  195. package/src/client/settings/SettingsPanel.tsx +10 -0
  196. package/src/client/use-chat-models.ts +45 -0
  197. package/src/deploy/build.ts +3 -0
  198. package/src/server/analytics.ts +164 -20
  199. package/src/server/auth.ts +17 -13
  200. package/src/server/better-auth-instance.ts +13 -1
  201. package/src/templates/chat/app/components/layout/Sidebar.tsx +35 -6
  202. package/src/templates/chat/app/routes/extensions._index.tsx +2 -2
  203. package/src/templates/chat/netlify.toml +1 -0
  204. package/src/templates/workspace-core/.agents/skills/extensions/SKILL.md +16 -0
  205. package/src/vite/client.ts +6 -0
  206. package/corpus/templates/analytics/app/components/layout/NewAnalysisDialog.tsx +0 -165
  207. package/corpus/templates/analytics/app/pages/analyses/AnalysesList.tsx +0 -175
@@ -2,16 +2,21 @@
2
2
  * Opt-in analytics injection for SSR streams.
3
3
  * Supported environment variables:
4
4
  * - `GA_MEASUREMENT_ID` — Google Analytics 4 measurement ID
5
+ * - `GTM_CONTAINER_ID` — Google Tag Manager web container ID
5
6
  *
6
7
  * Netlify configuration-file env vars are build-time only for serverless
7
8
  * functions, so the Vite/Nitro build paths also bake this public value into
8
9
  * SSR bundles.
9
10
  *
10
11
  * Amplitude and Sentry are initialized client-side via their npm packages
11
- * (see `packages/core/src/client/analytics.ts`). Only GA requires script
12
- * tag injection because the gtag.js loader must be a `<script src>`.
12
+ * (see `packages/core/src/client/analytics.ts`). GTM and GA require script
13
+ * tag injection because their loaders must be `<script>` elements.
13
14
  *
14
- * When set, the corresponding script tags are injected before `</head>`.
15
+ * When GTM is set, its head bootstrap is injected before `</head>` and its
16
+ * noscript fallback immediately after `<body>`. GTM takes precedence over GA
17
+ * so pageviews are not double-counted; configure the GA tag inside GTM.
18
+ * When only GA is set, the corresponding script tags are injected before
19
+ * `</head>`.
15
20
  * When not set, the stream passes through untouched (zero overhead).
16
21
  *
17
22
  * Usage in entry.server.tsx:
@@ -22,6 +27,7 @@
22
27
  */
23
28
 
24
29
  declare const __AGENT_NATIVE_BUILD_GA_MEASUREMENT_ID__: string | undefined;
30
+ declare const __AGENT_NATIVE_BUILD_GTM_CONTAINER_ID__: string | undefined;
25
31
 
26
32
  function normalizeMeasurementId(value: string | undefined): string | null {
27
33
  const trimmed = value?.trim();
@@ -34,6 +40,25 @@ function getViteBakedGaMeasurementId(): string | undefined {
34
40
  : undefined;
35
41
  }
36
42
 
43
+ function normalizeContainerId(value: string | undefined): string | null {
44
+ const trimmed = value?.trim().toUpperCase();
45
+ return trimmed && /^GTM-[A-Z0-9]+$/.test(trimmed) ? trimmed : null;
46
+ }
47
+
48
+ function getViteBakedGtmContainerId(): string | undefined {
49
+ return typeof __AGENT_NATIVE_BUILD_GTM_CONTAINER_ID__ === "string"
50
+ ? __AGENT_NATIVE_BUILD_GTM_CONTAINER_ID__
51
+ : undefined;
52
+ }
53
+
54
+ function getGtmContainerId(): string | null {
55
+ return (
56
+ normalizeContainerId(process.env.GTM_CONTAINER_ID) ||
57
+ normalizeContainerId(process.env.AGENT_NATIVE_BUILD_GTM_CONTAINER_ID) ||
58
+ normalizeContainerId(getViteBakedGtmContainerId())
59
+ );
60
+ }
61
+
37
62
  function getGaMeasurementId(): string | null {
38
63
  return (
39
64
  normalizeMeasurementId(process.env.GA_MEASUREMENT_ID) ||
@@ -74,32 +99,151 @@ function getGaScript(): string | null {
74
99
  );
75
100
  }
76
101
 
77
- export function wrapWithAnalytics(body: ReadableStream): ReadableStream {
78
- const scripts = [getGaScript()].filter(Boolean).join("");
79
- if (!scripts) return body;
102
+ function getGtmHeadScript(containerId: string): string {
103
+ const jsId = JSON.stringify(containerId);
104
+ return `<script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src='https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);})(window,document,'script','dataLayer',${jsId});</script>`;
105
+ }
106
+
107
+ function getGtmBodyFallback(containerId: string): string {
108
+ const src = encodeURIComponent(containerId);
109
+ return `<noscript><iframe src="https://www.googletagmanager.com/ns.html?id=${src}" height="0" width="0" style="display:none;visibility:hidden" title="Google Tag Manager"></iframe></noscript>`;
110
+ }
111
+
112
+ type AnalyticsInjection = {
113
+ head: string;
114
+ body: string;
115
+ };
116
+
117
+ function getAnalyticsInjection(): AnalyticsInjection | null {
118
+ const containerId = getGtmContainerId();
119
+ if (containerId) {
120
+ return {
121
+ head: getGtmHeadScript(containerId),
122
+ body: getGtmBodyFallback(containerId),
123
+ };
124
+ }
125
+
126
+ const gaScript = getGaScript();
127
+ return gaScript ? { head: gaScript, body: "" } : null;
128
+ }
129
+
130
+ const HEAD_CLOSE_PATTERN = /<\/head>/i;
131
+ const BODY_OPEN_PATTERN = /<body\b[^>]*>/i;
132
+
133
+ /**
134
+ * Add the configured analytics scripts to a complete HTML document.
135
+ *
136
+ * The normal app document is streamed through `wrapWithAnalytics`, but
137
+ * framework-owned documents such as `/signup` are returned as strings by the
138
+ * auth guard and need the same injection path.
139
+ */
140
+ export function injectAnalyticsIntoHtml(html: string): string {
141
+ const injection = getAnalyticsInjection();
142
+ if (!injection) return html;
143
+
144
+ const headCloseMatch = HEAD_CLOSE_PATTERN.exec(html);
145
+ if (!headCloseMatch || headCloseMatch.index === undefined) return html;
146
+
147
+ let output =
148
+ html.slice(0, headCloseMatch.index) +
149
+ injection.head +
150
+ html.slice(headCloseMatch.index);
151
+
152
+ if (injection.body) {
153
+ const bodyOpenMatch = BODY_OPEN_PATTERN.exec(output);
154
+ if (bodyOpenMatch && bodyOpenMatch.index !== undefined) {
155
+ const bodyEnd = bodyOpenMatch.index + bodyOpenMatch[0].length;
156
+ output =
157
+ output.slice(0, bodyEnd) + injection.body + output.slice(bodyEnd);
158
+ }
159
+ }
160
+
161
+ return output;
162
+ }
163
+
164
+ export function wrapWithAnalytics(
165
+ body: ReadableStream<Uint8Array>,
166
+ ): ReadableStream<Uint8Array> {
167
+ const injection = getAnalyticsInjection();
168
+ if (!injection) return body;
80
169
 
81
170
  const decoder = new TextDecoder();
82
171
  const encoder = new TextEncoder();
83
- let injected = false;
172
+ let pending = "";
173
+ let headInjected = false;
174
+ let bodyInjected = !injection.body;
84
175
 
85
176
  return body.pipeThrough(
86
- new TransformStream({
177
+ new TransformStream<Uint8Array, Uint8Array>({
87
178
  transform(chunk, controller) {
88
- if (injected) {
89
- controller.enqueue(chunk);
90
- return;
179
+ pending += decoder.decode(chunk, { stream: true });
180
+
181
+ if (!headInjected) {
182
+ const headCloseMatch = HEAD_CLOSE_PATTERN.exec(pending);
183
+ if (!headCloseMatch || headCloseMatch.index === undefined) return;
184
+
185
+ const headEnd = headCloseMatch.index + headCloseMatch[0].length;
186
+ controller.enqueue(
187
+ encoder.encode(
188
+ pending.slice(0, headCloseMatch.index) +
189
+ injection.head +
190
+ pending.slice(headCloseMatch.index, headEnd),
191
+ ),
192
+ );
193
+ pending = pending.slice(headEnd);
194
+ headInjected = true;
91
195
  }
92
- const text = decoder.decode(chunk, { stream: true });
93
- const headCloseIdx = text.indexOf("</head>");
94
- if (headCloseIdx !== -1) {
95
- const modified =
96
- text.slice(0, headCloseIdx) + scripts + text.slice(headCloseIdx);
97
- controller.enqueue(encoder.encode(modified));
98
- injected = true;
99
- } else {
100
- controller.enqueue(chunk);
196
+
197
+ if (!bodyInjected) {
198
+ const bodyOpenMatch = BODY_OPEN_PATTERN.exec(pending);
199
+ if (!bodyOpenMatch || bodyOpenMatch.index === undefined) return;
200
+
201
+ const bodyEnd = bodyOpenMatch.index + bodyOpenMatch[0].length;
202
+ controller.enqueue(
203
+ encoder.encode(pending.slice(0, bodyEnd) + injection.body),
204
+ );
205
+ pending = pending.slice(bodyEnd);
206
+ bodyInjected = true;
207
+ }
208
+
209
+ if (pending) {
210
+ controller.enqueue(encoder.encode(pending));
211
+ pending = "";
101
212
  }
102
213
  },
214
+ flush(controller) {
215
+ pending += decoder.decode();
216
+
217
+ if (!headInjected) {
218
+ const headCloseMatch = HEAD_CLOSE_PATTERN.exec(pending);
219
+ if (headCloseMatch && headCloseMatch.index !== undefined) {
220
+ const headEnd = headCloseMatch.index + headCloseMatch[0].length;
221
+ controller.enqueue(
222
+ encoder.encode(
223
+ pending.slice(0, headCloseMatch.index) +
224
+ injection.head +
225
+ pending.slice(headCloseMatch.index, headEnd),
226
+ ),
227
+ );
228
+ pending = pending.slice(headEnd);
229
+ headInjected = true;
230
+ }
231
+ }
232
+
233
+ if (headInjected && !bodyInjected) {
234
+ const bodyOpenMatch = BODY_OPEN_PATTERN.exec(pending);
235
+ if (bodyOpenMatch && bodyOpenMatch.index !== undefined) {
236
+ const bodyEnd = bodyOpenMatch.index + bodyOpenMatch[0].length;
237
+ controller.enqueue(
238
+ encoder.encode(pending.slice(0, bodyEnd) + injection.body),
239
+ );
240
+ pending = pending.slice(bodyEnd);
241
+ bodyInjected = true;
242
+ }
243
+ }
244
+
245
+ if (pending) controller.enqueue(encoder.encode(pending));
246
+ },
103
247
  }),
104
248
  );
105
249
  }
@@ -98,6 +98,7 @@ import {
98
98
  type WorkspaceAppAudience,
99
99
  } from "../shared/workspace-app-audience.js";
100
100
  import { isValidWorkspaceAppIdFormat } from "../shared/workspace-app-id.js";
101
+ import { injectAnalyticsIntoHtml } from "./analytics.js";
101
102
  import { signupAttributionFromCookieHeader } from "./attribution.js";
102
103
  import { getBetterAuth, getBetterAuthSync } from "./better-auth-instance.js";
103
104
  import type { BetterAuthConfig } from "./better-auth-instance.js";
@@ -1646,20 +1647,23 @@ function injectLoginSocialImageMeta(loginHtml: string, event: H3Event): string {
1646
1647
  }
1647
1648
 
1648
1649
  function loginHtmlResponse(loginHtml: string, event: H3Event): Response {
1649
- return new Response(injectLoginSocialImageMeta(loginHtml, event), {
1650
- status: 200,
1651
- headers: {
1652
- "Content-Type": "text/html; charset=utf-8",
1653
- // The sign-in document is part of the public server shell. Keep it on the
1654
- // same long-fresh/long-SWR CDN policy as React Router SSR so hosted
1655
- // template roots do not invoke origin just to render anonymous login UI.
1656
- // The login HTML is env-INDEPENDENT (a Google-only app always renders a
1657
- // working button), so a cached copy is never "wrong" never downgrade
1658
- // this to private/no-store.
1659
- ...DEFAULT_SSR_CACHE_HEADERS,
1660
- "X-Robots-Tag": "noindex, nofollow",
1650
+ return new Response(
1651
+ injectAnalyticsIntoHtml(injectLoginSocialImageMeta(loginHtml, event)),
1652
+ {
1653
+ status: 200,
1654
+ headers: {
1655
+ "Content-Type": "text/html; charset=utf-8",
1656
+ // The sign-in document is part of the public server shell. Keep it on the
1657
+ // same long-fresh/long-SWR CDN policy as React Router SSR so hosted
1658
+ // template roots do not invoke origin just to render anonymous login UI.
1659
+ // The login markup is env-INDEPENDENT (a Google-only app always renders
1660
+ // a working button); the analytics script is public build configuration,
1661
+ // not user/session state. Never downgrade this to private/no-store.
1662
+ ...DEFAULT_SSR_CACHE_HEADERS,
1663
+ "X-Robots-Tag": "noindex, nofollow",
1664
+ },
1661
1665
  },
1662
- });
1666
+ );
1663
1667
  }
1664
1668
 
1665
1669
  function isHtmlDocumentRequest(event: H3Event, pathname: string): boolean {
@@ -1109,6 +1109,18 @@ async function createBetterAuthInstance(
1109
1109
  return auth as unknown as BetterAuthInstance;
1110
1110
  }
1111
1111
 
1112
+ /**
1113
+ * Configure the local auth connection with the same write contention settings
1114
+ * as the shared app connection. Better Auth uses its own SQLite handle, so the
1115
+ * app connection's busy timeout does not protect first-run account creation.
1116
+ */
1117
+ export function configureLocalSqlite(sqlite: {
1118
+ pragma(statement: string): unknown;
1119
+ }): void {
1120
+ sqlite.pragma("busy_timeout = 10000");
1121
+ sqlite.pragma("journal_mode = WAL");
1122
+ }
1123
+
1112
1124
  async function buildDatabaseConfig(
1113
1125
  dialect: string,
1114
1126
  ): Promise<BetterAuthOptions["database"]> {
@@ -1182,7 +1194,7 @@ async function buildDatabaseConfig(
1182
1194
  const { default: Database } = await import("better-sqlite3");
1183
1195
  const filePath = url.replace(/^file:/, "");
1184
1196
  const sqlite = new Database(filePath);
1185
- sqlite.pragma("journal_mode = WAL");
1197
+ configureLocalSqlite(sqlite);
1186
1198
  const { drizzle } = await import("drizzle-orm/better-sqlite3");
1187
1199
  const db = drizzle(sqlite, { schema: sqliteAuthSchema });
1188
1200
  const { drizzleAdapter } = await import("better-auth/adapters/drizzle");
@@ -4,7 +4,6 @@ import {
4
4
  type ChatThreadSummary,
5
5
  } from "@agent-native/core/client/agent-chat";
6
6
  import { appPath } from "@agent-native/core/client/api-path";
7
- import { ExtensionsSidebarSection } from "@agent-native/core/client/extensions";
8
7
  import { useT } from "@agent-native/core/client/i18n";
9
8
  import { OrgSwitcher } from "@agent-native/core/client/org";
10
9
  import { FeedbackButton } from "@agent-native/core/client/ui";
@@ -38,6 +37,9 @@ const navItems = [
38
37
  href: "/",
39
38
  view: "chat",
40
39
  },
40
+ ];
41
+
42
+ const bottomNavItems = [
41
43
  {
42
44
  icon: IconHierarchy2,
43
45
  labelKey: "settings.agentTitle",
@@ -419,11 +421,38 @@ export function Sidebar({
419
421
  </nav>
420
422
 
421
423
  <div className={cn("mt-auto shrink-0", collapsed && "py-2")}>
422
- {!collapsed ? (
423
- <div className="px-2 py-1">
424
- <ExtensionsSidebarSection />
425
- </div>
426
- ) : null}
424
+ <nav
425
+ className={cn(
426
+ "grid",
427
+ collapsed ? "gap-0 px-1 py-1" : "gap-1 px-2 py-1",
428
+ )}
429
+ >
430
+ {bottomNavItems.map((item) => {
431
+ const Icon = item.icon;
432
+ const isActive = location.pathname.startsWith(item.href);
433
+ const link = (
434
+ <Link
435
+ to={item.href}
436
+ className={navClass({ isActive })}
437
+ aria-current={isActive ? "page" : undefined}
438
+ aria-label={collapsed ? t(item.labelKey) : undefined}
439
+ >
440
+ <Icon className="size-4 shrink-0" />
441
+ <span className={collapsed ? "sr-only" : "truncate"}>
442
+ {t(item.labelKey)}
443
+ </span>
444
+ </Link>
445
+ );
446
+ return collapsed ? (
447
+ <Tooltip key={item.href}>
448
+ <TooltipTrigger asChild>{link}</TooltipTrigger>
449
+ <TooltipContent side="right">{t(item.labelKey)}</TooltipContent>
450
+ </Tooltip>
451
+ ) : (
452
+ <div key={item.href}>{link}</div>
453
+ );
454
+ })}
455
+ </nav>
427
456
 
428
457
  <div className={cn(collapsed ? "px-1 py-1" : "px-3 py-2")}>
429
458
  <OrgSwitcher
@@ -1,4 +1,4 @@
1
- import { ExtensionsListPage } from "@agent-native/core/client/extensions";
1
+ import { Navigate } from "react-router";
2
2
 
3
3
  import { APP_TITLE } from "@/lib/app-config";
4
4
 
@@ -7,5 +7,5 @@ export function meta() {
7
7
  }
8
8
 
9
9
  export default function ExtensionsRoute() {
10
- return <ExtensionsListPage />;
10
+ return <Navigate to="/settings#extensions" replace />;
11
11
  }
@@ -6,5 +6,6 @@ functions = "templates/chat/.netlify/functions-internal"
6
6
 
7
7
  [build.environment]
8
8
  GA_MEASUREMENT_ID = "G-ESF7FYXGN9"
9
+ GTM_CONTAINER_ID = "GTM-N3WSTXZ"
9
10
  NITRO_PRESET = "netlify"
10
11
  NPM_CONFIG_PRODUCTION = "false"
@@ -76,6 +76,22 @@ Full source-code customization is a core Agent Native capability. Extensions
76
76
  are the fast, sandboxed, no-deploy layer—not the limit of what the app can
77
77
  become.
78
78
 
79
+ Use this decision rule when either path could solve the request:
80
+
81
+ - Start with an extension for a self-contained, relatively one-off widget,
82
+ custom visualization or interaction, dashboard tile, standalone utility, or
83
+ add-on in an existing slot.
84
+ - Use app code for a core template feature, native UI/layout/routes/business
85
+ logic, a new slot, or behavior that should be reused broadly across dashboards
86
+ or users.
87
+ - If the same extension keeps getting rebuilt, promote it into app code as one
88
+ reusable native feature instead of maintaining copies.
89
+
90
+ As a rule of thumb, if an extension can solve the problem, generally start
91
+ there. An isolated custom dashboard visualization belongs in an extension; a
92
+ new chart type that should be available across dashboards belongs in the
93
+ template code.
94
+
79
95
  **When a user asks to "make an extension", "create an extension", or "build
80
96
  a ... extension" (or the older phrasings "make a tool" / "create a tool"):**
81
97
 
@@ -3066,6 +3066,12 @@ function createAgentNativeConfig(
3066
3066
  "process.env.AGENT_NATIVE_BUILD_GA_MEASUREMENT_ID": JSON.stringify(
3067
3067
  process.env.GA_MEASUREMENT_ID?.trim() || "",
3068
3068
  ),
3069
+ __AGENT_NATIVE_BUILD_GTM_CONTAINER_ID__: JSON.stringify(
3070
+ process.env.GTM_CONTAINER_ID?.trim() || "",
3071
+ ),
3072
+ "process.env.AGENT_NATIVE_BUILD_GTM_CONTAINER_ID": JSON.stringify(
3073
+ process.env.GTM_CONTAINER_ID?.trim() || "",
3074
+ ),
3069
3075
  // Framework route warmup controls how SSR `.data` routes are fetched:
3070
3076
  // ordinary fetches keep them CDN-cacheable, while native prefetch headers
3071
3077
  // can be refused before the CDN/origin sees the request. Keep this value
@@ -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`.