@agent-native/core 0.141.6 → 0.141.7

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 (210) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/analytics/.agents/skills/dashboard-management/SKILL.md +3 -14
  3. package/corpus/templates/analytics/.agents/skills/data-querying/SKILL.md +11 -6
  4. package/corpus/templates/analytics/.agents/skills/prometheus/SKILL.md +5 -5
  5. package/corpus/templates/analytics/AGENTS.md +2 -2
  6. package/corpus/templates/analytics/actions/compose-dashboard.ts +1 -1
  7. package/corpus/templates/analytics/actions/migrate-first-party-analytics-to-bigquery.ts +135 -0
  8. package/corpus/templates/analytics/actions/navigate.ts +1 -1
  9. package/corpus/templates/analytics/actions/query-agent-native-analytics.ts +2 -2
  10. package/corpus/templates/analytics/actions/update-dashboard.ts +0 -1
  11. package/corpus/templates/analytics/actions/view-screen.ts +0 -20
  12. package/corpus/templates/analytics/app/components/layout/Header.tsx +0 -1
  13. package/corpus/templates/analytics/app/hooks/use-navigation-state.ts +5 -5
  14. package/corpus/templates/analytics/app/i18n-data.ts +2 -90
  15. package/corpus/templates/analytics/app/pages/Agents.tsx +0 -3
  16. package/corpus/templates/analytics/app/pages/Settings.tsx +22 -43
  17. package/corpus/templates/analytics/app/pages/settings/settings-search.ts +23 -20
  18. package/corpus/templates/analytics/app/routes/extensions._index.tsx +1 -1
  19. package/corpus/templates/analytics/app/routes/settings.$.tsx +1 -0
  20. package/corpus/templates/analytics/app/routes/team.tsx +1 -1
  21. package/corpus/templates/analytics/changelog/2026-08-05-settings-no-longer-includes-an-unnecessary-about-section.md +6 -0
  22. package/corpus/templates/analytics/docs/schemas/first-party-analytics.md +1 -1
  23. package/corpus/templates/analytics/scripts/first-party-analytics-bigquery.sql +104 -0
  24. package/corpus/templates/analytics/server/lib/agent-chat-plan-mode.ts +0 -2
  25. package/corpus/templates/analytics/server/lib/first-party-analytics-backend.ts +1058 -0
  26. package/corpus/templates/analytics/server/lib/first-party-analytics-health.ts +34 -15
  27. package/corpus/templates/analytics/server/lib/first-party-analytics.ts +57 -4
  28. package/corpus/templates/analytics/server/lib/real-data-actions.ts +1 -3
  29. package/corpus/templates/analytics/server/plugins/agent-chat.ts +2 -4
  30. package/corpus/templates/assets/.agents/skills/asset-generation/SKILL.md +8 -6
  31. package/corpus/templates/assets/.agents/skills/library-management/SKILL.md +16 -1
  32. package/corpus/templates/assets/AGENTS.md +1 -0
  33. package/corpus/templates/assets/actions/_helpers.ts +12 -0
  34. package/corpus/templates/assets/actions/generate-image.ts +9 -3
  35. package/corpus/templates/assets/actions/import-style-from-url.ts +115 -0
  36. package/corpus/templates/assets/app/hooks/use-navigation-state.ts +3 -1
  37. package/corpus/templates/assets/app/routes/extensions._index.tsx +1 -1
  38. package/corpus/templates/assets/app/routes/settings.$.tsx +1 -0
  39. package/corpus/templates/assets/app/routes/settings.tsx +5 -0
  40. package/corpus/templates/assets/app/routes/team.tsx +1 -1
  41. package/corpus/templates/assets/changelog/2026-08-05-brand-kits-can-import-a-website-s-hydrated-visual-language-a.md +6 -0
  42. package/corpus/templates/assets/changelog/2026-08-05-generated-images-now-hand-off-embeddable-previews-to-other-w.md +6 -0
  43. package/corpus/templates/assets/server/lib/generation.ts +58 -0
  44. package/corpus/templates/assets/shared/api.ts +12 -0
  45. package/corpus/templates/brain/app/routes/extensions._index.tsx +1 -1
  46. package/corpus/templates/brain/app/routes/team.tsx +1 -1
  47. package/corpus/templates/brain/app/routes.ts +1 -1
  48. package/corpus/templates/calendar/app/components/layout/AppLayout.tsx +3 -1
  49. package/corpus/templates/calendar/app/routes/_app.extensions._index.tsx +1 -1
  50. package/corpus/templates/calendar/app/routes/_app.settings.$.tsx +1 -0
  51. package/corpus/templates/calendar/app/routes/_app.team.tsx +1 -1
  52. package/corpus/templates/chat/app/components/layout/Layout.tsx +1 -1
  53. package/corpus/templates/chat/app/hooks/use-navigation-state.ts +1 -1
  54. package/corpus/templates/chat/app/root.tsx +1 -1
  55. package/corpus/templates/chat/app/routes/agent.tsx +7 -7
  56. package/corpus/templates/chat/app/routes/extensions._index.tsx +1 -1
  57. package/corpus/templates/chat/app/routes/settings.$.tsx +1 -0
  58. package/corpus/templates/chat/app/routes/team.tsx +1 -1
  59. package/corpus/templates/clips/app/components/library/library-layout.tsx +1 -1
  60. package/corpus/templates/clips/app/components/player/transcript-panel.tsx +1 -1
  61. package/corpus/templates/clips/app/components/recorder/storage-setup-card.tsx +1 -1
  62. package/corpus/templates/clips/app/routes/_app.extensions._index.tsx +1 -1
  63. package/corpus/templates/clips/app/routes/_app.settings.$.tsx +1 -0
  64. package/corpus/templates/content/app/routes/_app.extensions._index.tsx +1 -1
  65. package/corpus/templates/content/app/routes/_app.settings.$.tsx +1 -0
  66. package/corpus/templates/content/app/routes/_app.team.tsx +1 -1
  67. package/corpus/templates/content/e2e/global-setup.ts +1 -1
  68. package/corpus/templates/design/.agents/skills/design-systems/SKILL.md +6 -1
  69. package/corpus/templates/design/actions/analyze-brand-assets.ts +7 -7
  70. package/corpus/templates/design/actions/import-from-url.ts +6 -5
  71. package/corpus/templates/design/app/pages/DesignSystemSetup.tsx +2 -2
  72. package/corpus/templates/design/app/routes/extensions._index.tsx +1 -1
  73. package/corpus/templates/design/app/routes/settings.$.tsx +1 -0
  74. package/corpus/templates/design/app/routes/team.tsx +1 -1
  75. package/corpus/templates/design/changelog/2026-08-05-website-url-imports-now-capture-hydrated-browser-computed-de.md +6 -0
  76. package/corpus/templates/design/e2e/global-setup.ts +1 -1
  77. package/corpus/templates/dispatch/app/hooks/use-navigation-state.ts +1 -1
  78. package/corpus/templates/dispatch/app/routes/settings.$.tsx +1 -0
  79. package/corpus/templates/dispatch/app/routes/team.tsx +1 -1
  80. package/corpus/templates/factory/app/hooks/use-navigation-state.ts +1 -1
  81. package/corpus/templates/factory/app/routes/extensions._index.tsx +1 -1
  82. package/corpus/templates/factory/app/routes/settings.$.tsx +1 -0
  83. package/corpus/templates/factory/app/routes/team.tsx +1 -1
  84. package/corpus/templates/forms/app/routes/_app.extensions._index.tsx +1 -1
  85. package/corpus/templates/forms/app/routes/_app.settings.$.tsx +1 -0
  86. package/corpus/templates/forms/app/routes/_app.team.tsx +1 -1
  87. package/corpus/templates/forms/shared/navigation.ts +1 -1
  88. package/corpus/templates/macros/app/routes/extensions._index.tsx +1 -1
  89. package/corpus/templates/macros/app/routes/settings.$.tsx +1 -0
  90. package/corpus/templates/macros/app/routes/team.tsx +1 -1
  91. package/corpus/templates/mail/app/routes/extensions._index.tsx +1 -1
  92. package/corpus/templates/mail/app/routes/settings.$.tsx +1 -0
  93. package/corpus/templates/plan/app/hooks/use-navigation-state.ts +1 -1
  94. package/corpus/templates/plan/app/routes/extensions._index.tsx +1 -1
  95. package/corpus/templates/plan/app/routes/settings.$.tsx +1 -0
  96. package/corpus/templates/plan/app/routes/team.tsx +1 -1
  97. package/corpus/templates/slides/.agents/skills/design-systems/SKILL.md +3 -1
  98. package/corpus/templates/slides/actions/analyze-brand-assets.ts +7 -7
  99. package/corpus/templates/slides/actions/import-from-url.ts +6 -6
  100. package/corpus/templates/slides/app/components/design-system/DesignSystemSetup.tsx +1 -1
  101. package/corpus/templates/slides/app/components/editor/PromptDialog.tsx +3 -0
  102. package/corpus/templates/slides/app/components/onboarding/FirstDeckOnboardingFlow.tsx +9 -3
  103. package/corpus/templates/slides/app/context/DeckContext.tsx +71 -20
  104. package/corpus/templates/slides/app/global.css +6 -0
  105. package/corpus/templates/slides/app/lib/create-deck-generation.ts +9 -5
  106. package/corpus/templates/slides/app/pages/DeckEditor.tsx +17 -0
  107. package/corpus/templates/slides/app/pages/Index.tsx +16 -5
  108. package/corpus/templates/slides/app/routes/extensions._index.tsx +1 -1
  109. package/corpus/templates/slides/app/routes/settings.$.tsx +1 -0
  110. package/corpus/templates/slides/app/routes/team.tsx +1 -1
  111. package/corpus/templates/slides/changelog/2026-08-05-failed-deck-creation-and-reference-uploads-now-recover-clean.md +6 -0
  112. package/corpus/templates/slides/changelog/2026-08-05-slides-recover-completed-generation-results-after-a-sync-hic.md +6 -0
  113. package/corpus/templates/slides/changelog/2026-08-05-website-url-imports-now-capture-hydrated-browser-computed-st.md +6 -0
  114. package/corpus/templates/tasks/app/components/layout/Header.tsx +3 -1
  115. package/corpus/templates/tasks/app/routes/extensions._index.tsx +1 -1
  116. package/corpus/templates/tasks/app/routes/settings.$.tsx +1 -0
  117. package/dist/agent/engine/ai-sdk-engine.js +49 -3
  118. package/dist/agent/production-agent.d.ts +1 -0
  119. package/dist/agent/production-agent.js +8 -0
  120. package/dist/agent/types.d.ts +7 -0
  121. package/dist/client/agent-chat-adapter.d.ts +1 -1
  122. package/dist/client/agent-chat-adapter.js +30 -3
  123. package/dist/client/agent-page/AgentWorkspaceContent.d.ts +1 -1
  124. package/dist/client/agent-page/AgentWorkspaceContent.js +13 -13
  125. package/dist/client/chat/run-recovery.js +5 -1
  126. package/dist/client/embed-auth.js +6 -2
  127. package/dist/client/error-format.js +12 -0
  128. package/dist/client/integrations/IntegrationsPanel.js +40 -6
  129. package/dist/client/org/OrgSwitcher.js +2 -2
  130. package/dist/client/org/TeamPage.js +92 -72
  131. package/dist/client/require-session.d.ts +1 -1
  132. package/dist/client/require-session.js +3 -3
  133. package/dist/client/resources/use-mcp-servers.d.ts +5 -0
  134. package/dist/client/resources/use-mcp-servers.js +36 -0
  135. package/dist/client/settings/AccountSettingsCard.js +40 -35
  136. package/dist/client/settings/AutomationsSection.js +1 -1
  137. package/dist/client/settings/DemoModeSection.d.ts +3 -1
  138. package/dist/client/settings/DemoModeSection.js +4 -1
  139. package/dist/client/settings/ExtensionsSettingsContent.js +1 -1
  140. package/dist/client/settings/SchedulingTimezoneField.d.ts +3 -1
  141. package/dist/client/settings/SchedulingTimezoneField.js +9 -5
  142. package/dist/client/settings/SettingsPanel.d.ts +4 -1
  143. package/dist/client/settings/SettingsPanel.js +21 -21
  144. package/dist/client/settings/SettingsTabsPage.d.ts +2 -2
  145. package/dist/client/settings/SettingsTabsPage.js +98 -45
  146. package/dist/client/settings/VoiceTranscriptionSection.js +6 -1
  147. package/dist/client/settings/useSettingsPanelController.js +47 -8
  148. package/dist/collab/struct-routes.d.ts +1 -1
  149. package/dist/file-upload/actions/upload-image.d.ts +1 -1
  150. package/dist/ingestion/website.d.ts +30 -0
  151. package/dist/localization/default-messages.d.ts +5 -0
  152. package/dist/localization/default-messages.js +5 -0
  153. package/dist/mcp-client/oauth-routes.js +1 -1
  154. package/dist/mcp-client/routes.d.ts +1 -0
  155. package/dist/mcp-client/routes.js +49 -0
  156. package/dist/navigation/index.js +16 -5
  157. package/dist/observability/routes.d.ts +5 -5
  158. package/dist/progress/routes.d.ts +1 -1
  159. package/dist/provider-api/actions/custom-provider-registration.d.ts +13 -13
  160. package/dist/provider-api/actions/provider-api.d.ts +11 -11
  161. package/dist/resources/handlers.d.ts +1 -1
  162. package/dist/scripts/call-agent.js +11 -0
  163. package/dist/secrets/routes.d.ts +3 -3
  164. package/dist/server/auth.js +9 -7
  165. package/dist/server/identity-sso.js +2 -1
  166. package/dist/server/onboarding-html.js +57 -34
  167. package/dist/server/realtime-token.d.ts +1 -1
  168. package/dist/server/transcribe-voice.d.ts +1 -1
  169. package/dist/server/workspace-provider-oauth.js +1 -1
  170. package/dist/setup-connections/onboarding.js +1 -1
  171. package/dist/shared/index.d.ts +1 -1
  172. package/dist/shared/index.js +1 -1
  173. package/dist/shared/sign-in-journey.d.ts +5 -2
  174. package/dist/shared/sign-in-journey.js +10 -4
  175. package/dist/templates/chat/app/components/layout/Layout.tsx +1 -1
  176. package/dist/templates/chat/app/hooks/use-navigation-state.ts +1 -1
  177. package/dist/templates/chat/app/root.tsx +1 -1
  178. package/dist/templates/chat/app/routes/agent.tsx +7 -7
  179. package/dist/templates/chat/app/routes/extensions._index.tsx +1 -1
  180. package/dist/templates/chat/app/routes/settings.$.tsx +1 -0
  181. package/dist/templates/chat/app/routes/team.tsx +1 -1
  182. package/dist/templates/workspace-core/.agents/skills/address-feedback/SKILL.md +37 -6
  183. package/dist/templates/workspace-core/.agents/skills/authentication/SKILL.md +4 -2
  184. package/docs/content/authentication.mdx +4 -5
  185. package/docs/content/template-analytics-developers.mdx +1 -1
  186. package/docs/content/template-calendar-developers.mdx +1 -1
  187. package/docs/content/template-chat-developers.mdx +2 -2
  188. package/docs/content/template-content-developers.mdx +1 -1
  189. package/docs/content/template-mail-developers.mdx +1 -1
  190. package/docs/content/template-plan-developers.mdx +1 -1
  191. package/docs/content/toolkit-command-navigation.mdx +6 -6
  192. package/docs/content/toolkit-org-team.mdx +1 -1
  193. package/docs/content/toolkit-settings.mdx +8 -8
  194. package/docs/content/toolkit-setup-connections.mdx +1 -1
  195. package/package.json +1 -1
  196. package/src/templates/chat/app/components/layout/Layout.tsx +1 -1
  197. package/src/templates/chat/app/hooks/use-navigation-state.ts +1 -1
  198. package/src/templates/chat/app/root.tsx +1 -1
  199. package/src/templates/chat/app/routes/agent.tsx +7 -7
  200. package/src/templates/chat/app/routes/extensions._index.tsx +1 -1
  201. package/src/templates/chat/app/routes/settings.$.tsx +1 -0
  202. package/src/templates/chat/app/routes/team.tsx +1 -1
  203. package/src/templates/workspace-core/.agents/skills/address-feedback/SKILL.md +37 -6
  204. package/src/templates/workspace-core/.agents/skills/authentication/SKILL.md +4 -2
  205. package/corpus/templates/analytics/.agents/skills/dashboard-management/references/template-catalog-and-demo.md +0 -62
  206. package/corpus/templates/analytics/actions/install-dashboard-template.ts +0 -369
  207. package/corpus/templates/analytics/actions/list-dashboard-templates.ts +0 -47
  208. package/corpus/templates/analytics/actions/list-native-v2-dashboard-templates.ts +0 -34
  209. package/corpus/templates/analytics/app/routes/catalog.tsx +0 -240
  210. package/src/templates/default/actions/navigate.spec.ts +0 -53
@@ -1,369 +0,0 @@
1
- import { defineAction, embedApp } from "@agent-native/core";
2
- import {
3
- applyText,
4
- hasCollabState,
5
- seedFromText,
6
- } from "@agent-native/core/collab";
7
- import {
8
- buildDeepLink,
9
- getRequestOrgId,
10
- getRequestUserEmail,
11
- } from "@agent-native/core/server";
12
- import { z } from "zod";
13
-
14
- import {
15
- applyCatalogMetadata,
16
- cloneDashboardConfig,
17
- generateDashboardId,
18
- getDashboardCatalogEntry,
19
- listDashboardCatalog,
20
- } from "../server/lib/dashboard-catalog";
21
- import {
22
- getDashboard,
23
- upsertDashboard,
24
- upsertDashboardWithRetry,
25
- type DashboardRecord,
26
- } from "../server/lib/dashboards-store";
27
-
28
- async function syncToCollab(
29
- dashboardId: string,
30
- config: Record<string, unknown>,
31
- requestSource?: string,
32
- ): Promise<void> {
33
- const docId = `dash-${dashboardId}`;
34
- const configStr = JSON.stringify(config);
35
- try {
36
- if (await hasCollabState(docId)) {
37
- await applyText(docId, configStr, "content", requestSource);
38
- } else {
39
- await seedFromText(docId, configStr);
40
- }
41
- } catch {
42
- // SQL is the source of truth; collab state can seed lazily later.
43
- }
44
- }
45
-
46
- function uniqueConstraintMessage(err: unknown): boolean {
47
- const message = err instanceof Error ? err.message : String(err ?? "");
48
- return /unique|constraint|primary key/i.test(message);
49
- }
50
-
51
- function filterId(filter: unknown): string | null {
52
- if (!filter || typeof filter !== "object" || Array.isArray(filter)) {
53
- return null;
54
- }
55
- const id = (filter as { id?: unknown }).id;
56
- return typeof id === "string" && id.trim() ? id : null;
57
- }
58
-
59
- function mergeMissingFilters(
60
- targetConfig: Record<string, unknown>,
61
- seedConfig: Record<string, unknown>,
62
- ): { config: Record<string, unknown>; addedFilterIds: string[] } {
63
- const seedFilters = Array.isArray(seedConfig.filters)
64
- ? (seedConfig.filters as unknown[])
65
- : [];
66
- if (seedFilters.length === 0) {
67
- return { config: targetConfig, addedFilterIds: [] };
68
- }
69
-
70
- const targetFilters = Array.isArray(targetConfig.filters)
71
- ? [...targetConfig.filters]
72
- : [];
73
- const existingIds = new Set(
74
- targetFilters
75
- .map((filter) => filterId(filter))
76
- .filter((id): id is string => id !== null),
77
- );
78
- const addedFilterIds: string[] = [];
79
- for (const filter of seedFilters) {
80
- const id = filterId(filter);
81
- if (id && existingIds.has(id)) continue;
82
- targetFilters.push(filter);
83
- if (id) {
84
- existingIds.add(id);
85
- addedFilterIds.push(id);
86
- }
87
- }
88
-
89
- return addedFilterIds.length > 0
90
- ? { config: { ...targetConfig, filters: targetFilters }, addedFilterIds }
91
- : { config: targetConfig, addedFilterIds };
92
- }
93
-
94
- export default defineAction({
95
- description:
96
- "Install a dashboard template from the Analytics catalog into the user's SQL-backed dashboards. Use list-dashboard-templates first when choosing a template. " +
97
- "To ADD a template's panels to an EXISTING dashboard in ONE call (the preferred way to bulk-add panels), pass `mergePanels: true` with the `dashboardId` of the existing dashboard: it appends every template panel whose id is not already present, preserves all existing panels and their order, and saves once. This avoids looping update-dashboard, which times out on the ~40s hosted run budget.",
98
- schema: z.object({
99
- templateId: z
100
- .string()
101
- .describe("Catalog template id from list-dashboard-templates"),
102
- dashboardId: z
103
- .string()
104
- .optional()
105
- .describe(
106
- "Optional dashboard id to write. Omit to reuse an existing installed copy or create a unique id. Required when mergePanels is true.",
107
- ),
108
- name: z
109
- .string()
110
- .optional()
111
- .describe("Optional installed dashboard name override"),
112
- overwrite: z
113
- .boolean()
114
- .optional()
115
- .describe(
116
- "If true, replace an existing accessible dashboard at dashboardId.",
117
- ),
118
- forceNew: z
119
- .boolean()
120
- .optional()
121
- .describe(
122
- "If true, create another copy even when this template is installed.",
123
- ),
124
- mergePanels: z
125
- .boolean()
126
- .optional()
127
- .describe(
128
- "If true AND a dashboard already exists at dashboardId, APPEND this template's panels (only the ones whose id is not already present) to the existing dashboard in one atomic save, preserving all existing panels and their order. Returns { addedPanelIds, skippedExistingIds, panelCount }. Non-destructive; does not change overwrite/forceNew behavior.",
129
- ),
130
- }),
131
- mcpApp: {
132
- compactCatalog: true,
133
- resource: embedApp({
134
- title: "Installed dashboard",
135
- description: "Open the installed dashboard in the real Analytics UI.",
136
- iframeTitle: "Agent-Native Analytics",
137
- openLabel: "Open dashboard",
138
- height: 760,
139
- }),
140
- },
141
- run: async (args, actionContext) => {
142
- const email = getRequestUserEmail();
143
- if (!email) throw new Error("no authenticated user");
144
- const ctx = { email, orgId: getRequestOrgId() || null };
145
-
146
- const entry = getDashboardCatalogEntry(args.templateId);
147
- if (!entry)
148
- throw new Error(`Unknown dashboard template: ${args.templateId}`);
149
-
150
- // Append/merge mode: add this template's panels to an existing dashboard
151
- // in ONE atomic save instead of looping update-dashboard (which times out
152
- // on the ~40s hosted run budget). Non-destructive: existing panels and
153
- // their order are preserved; only template panels with a new id are added.
154
- //
155
- // This is a genuine read-modify-write of an EXISTING dashboard (an agent
156
- // merging a template while a human drags a panel, or two merge calls
157
- // racing), so the save is fenced through `upsertDashboardWithRetry`.
158
- // `computeMerge` is invoked with the freshest `DashboardRecord` on every
159
- // attempt and recomputes `existingIds`/`appended`/`mergedConfig` from
160
- // THAT record's config — never from a closure over the earlier `target`
161
- // read — so a lost race re-merges against whatever the concurrent writer
162
- // actually saved instead of clobbering it. `seedConfig`/`seedPanels` are
163
- // the template's own panels and don't depend on dashboard state, so they
164
- // are computed once outside the retry loop.
165
- if (args.mergePanels) {
166
- const targetId = args.dashboardId?.trim();
167
- if (!targetId) {
168
- throw new Error(
169
- "mergePanels=true requires dashboardId (the existing dashboard to append the template's panels to).",
170
- );
171
- }
172
-
173
- const target = await getDashboard(targetId, ctx);
174
- if (!target) {
175
- throw new Error(
176
- `Dashboard "${targetId}" not found (or you don't have access). mergePanels appends to an existing dashboard — install the template normally first, or omit mergePanels to create a new copy.`,
177
- );
178
- }
179
-
180
- const seedConfig = cloneDashboardConfig(entry) as unknown as Record<
181
- string,
182
- unknown
183
- >;
184
- const seedPanels = Array.isArray(seedConfig.panels)
185
- ? (seedConfig.panels as unknown as Array<Record<string, unknown>>)
186
- : [];
187
-
188
- function computeMerge(existing: Pick<DashboardRecord, "config">) {
189
- const targetConfig = existing.config as Record<string, unknown>;
190
- const existingPanels = Array.isArray(targetConfig.panels)
191
- ? (targetConfig.panels as Array<Record<string, unknown>>)
192
- : [];
193
- const existingIds = new Set(
194
- existingPanels
195
- .map((panel) => (typeof panel?.id === "string" ? panel.id : null))
196
- .filter((id): id is string => !!id),
197
- );
198
-
199
- const addedPanelIds: string[] = [];
200
- const skippedExistingIds: string[] = [];
201
- const appended: Array<Record<string, unknown>> = [];
202
- for (const panel of seedPanels) {
203
- const id = typeof panel?.id === "string" ? panel.id : null;
204
- if (id && existingIds.has(id)) {
205
- skippedExistingIds.push(id);
206
- continue;
207
- }
208
- appended.push(panel);
209
- if (id) {
210
- addedPanelIds.push(id);
211
- existingIds.add(id);
212
- }
213
- }
214
-
215
- let mergedConfig: Record<string, unknown> = {
216
- ...targetConfig,
217
- panels: [...existingPanels, ...appended],
218
- };
219
- const filterMerge = mergeMissingFilters(mergedConfig, seedConfig);
220
- mergedConfig = filterMerge.config;
221
-
222
- return {
223
- mergedConfig,
224
- addedPanelIds,
225
- skippedExistingIds,
226
- appended,
227
- filterMerge,
228
- };
229
- }
230
-
231
- let computed = computeMerge(target);
232
- let savedTitle = target.title;
233
-
234
- if (
235
- computed.appended.length > 0 ||
236
- computed.filterMerge.addedFilterIds.length > 0
237
- ) {
238
- const saved = await upsertDashboardWithRetry(
239
- targetId,
240
- ctx,
241
- (existing) => {
242
- computed = computeMerge(existing);
243
- return { kind: existing.kind, body: computed.mergedConfig };
244
- },
245
- );
246
- await syncToCollab(
247
- targetId,
248
- saved.config as Record<string, unknown>,
249
- actionContext?.caller === "frontend" ? undefined : "agent",
250
- );
251
- savedTitle = saved.title;
252
- }
253
-
254
- const panelCount = (computed.mergedConfig.panels as unknown[]).length;
255
-
256
- return {
257
- templateId: entry.id,
258
- templateName: entry.name,
259
- dashboardId: targetId,
260
- name: savedTitle,
261
- merged: true,
262
- addedPanelIds: computed.addedPanelIds,
263
- skippedExistingIds: computed.skippedExistingIds,
264
- panelCount,
265
- urlPath: `/dashboards/${targetId}`,
266
- deepLink: buildDeepLink({
267
- app: "analytics",
268
- view: "adhoc",
269
- params: { dashboardId: targetId },
270
- }),
271
- message:
272
- computed.appended.length > 0
273
- ? `Added ${computed.addedPanelIds.length} panel(s) from "${entry.name}" to "${savedTitle}"; ${computed.skippedExistingIds.length} already present. Dashboard now has ${panelCount} panel(s).`
274
- : `No new panels to add from "${entry.name}" — all ${computed.skippedExistingIds.length} template panel id(s) already present. Dashboard has ${panelCount} panel(s).`,
275
- };
276
- }
277
-
278
- const installed = (await listDashboardCatalog(ctx)).find(
279
- (template) => template.id === entry.id,
280
- );
281
- const existingInstall = installed?.installedDashboards[0];
282
- if (existingInstall && !args.forceNew && !args.dashboardId) {
283
- return {
284
- templateId: entry.id,
285
- dashboardId: existingInstall.id,
286
- name: existingInstall.name,
287
- alreadyInstalled: true,
288
- urlPath: `/dashboards/${existingInstall.id}`,
289
- deepLink: buildDeepLink({
290
- app: "analytics",
291
- view: "adhoc",
292
- params: { dashboardId: existingInstall.id },
293
- }),
294
- message: `Template "${entry.name}" is already installed as "${existingInstall.name}".`,
295
- };
296
- }
297
-
298
- const dashboardId = args.dashboardId?.trim() || generateDashboardId(entry);
299
- if (!/^[a-zA-Z0-9][a-zA-Z0-9._-]*$/.test(dashboardId)) {
300
- throw new Error(
301
- "dashboardId must start with a letter or number and contain only letters, numbers, dots, underscores, or hyphens",
302
- );
303
- }
304
-
305
- const existing = await getDashboard(dashboardId, ctx);
306
- if (existing && !args.overwrite) {
307
- throw new Error(
308
- `Dashboard "${dashboardId}" already exists. Pass overwrite=true to replace it or omit dashboardId to create a new copy.`,
309
- );
310
- }
311
-
312
- const config = applyCatalogMetadata(entry, cloneDashboardConfig(entry));
313
- if (args.name?.trim()) config.name = args.name.trim();
314
- const dashboardConfig = config as unknown as Record<string, unknown>;
315
-
316
- try {
317
- const dashboard = await upsertDashboard(
318
- dashboardId,
319
- "sql",
320
- dashboardConfig,
321
- ctx,
322
- );
323
- await syncToCollab(
324
- dashboardId,
325
- dashboardConfig,
326
- actionContext?.caller === "frontend" ? undefined : "agent",
327
- );
328
-
329
- return {
330
- templateId: entry.id,
331
- templateName: entry.name,
332
- dashboardId,
333
- name: dashboard.title,
334
- alreadyInstalled: false,
335
- overwritten: !!existing,
336
- urlPath: `/dashboards/${dashboardId}`,
337
- deepLink: buildDeepLink({
338
- app: "analytics",
339
- view: "adhoc",
340
- params: { dashboardId },
341
- }),
342
- message: `Installed "${entry.name}" as "${dashboard.title}".`,
343
- };
344
- } catch (err) {
345
- if (uniqueConstraintMessage(err)) {
346
- throw new Error(
347
- `Dashboard id "${dashboardId}" is already in use. Omit dashboardId or choose a different one.`,
348
- );
349
- }
350
- throw err;
351
- }
352
- },
353
- link: ({ result }) => {
354
- const dashboardId =
355
- result && typeof result === "object"
356
- ? (result as { dashboardId?: string }).dashboardId
357
- : undefined;
358
- if (!dashboardId) return null;
359
- return {
360
- url: buildDeepLink({
361
- app: "analytics",
362
- view: "adhoc",
363
- params: { dashboardId },
364
- }),
365
- label: "Open installed dashboard",
366
- view: "adhoc",
367
- };
368
- },
369
- });
@@ -1,47 +0,0 @@
1
- import { defineAction } from "@agent-native/core";
2
- import {
3
- buildDeepLink,
4
- getRequestOrgId,
5
- getRequestUserEmail,
6
- } from "@agent-native/core/server";
7
- import { z } from "zod";
8
-
9
- import { listDashboardCatalog } from "../server/lib/dashboard-catalog";
10
-
11
- export default defineAction({
12
- description:
13
- "List source-controlled dashboard templates in the Analytics catalog, including whether each template is already installed as a SQL dashboard.",
14
- schema: z.object({
15
- category: z
16
- .enum(["Acquisition", "Product", "Observability", "Operations"])
17
- .optional()
18
- .describe("Optional template category filter"),
19
- dataSource: z
20
- .enum(["demo", "first-party", "ga4", "prometheus"])
21
- .optional()
22
- .describe("Optional data source filter"),
23
- }),
24
- http: { method: "GET" },
25
- readOnly: true,
26
- publicAgent: { expose: true, readOnly: true, requiresAuth: true },
27
- link: () => ({
28
- url: buildDeepLink({ app: "analytics", view: "catalog" }),
29
- label: "Open template catalog in Analytics",
30
- view: "catalog",
31
- }),
32
- run: async (args) => {
33
- const email = getRequestUserEmail();
34
- if (!email) throw new Error("no authenticated user");
35
- const templates = await listDashboardCatalog({
36
- email,
37
- orgId: getRequestOrgId() || null,
38
- });
39
- return templates.filter((template) => {
40
- if (args.category && template.category !== args.category) return false;
41
- if (args.dataSource && !template.dataSources.includes(args.dataSource)) {
42
- return false;
43
- }
44
- return true;
45
- });
46
- },
47
- });
@@ -1,34 +0,0 @@
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
- });
@@ -1,240 +0,0 @@
1
- import {
2
- useActionMutation,
3
- useActionQuery,
4
- } from "@agent-native/core/client/hooks";
5
- import { useT } from "@agent-native/core/client/i18n";
6
- import { IconLayoutDashboard } from "@tabler/icons-react";
7
- import { useMemo, useState } from "react";
8
- import { useNavigate } from "react-router";
9
- import { toast } from "sonner";
10
-
11
- import { Button } from "@/components/ui/button";
12
- import {
13
- Card,
14
- CardContent,
15
- CardDescription,
16
- CardFooter,
17
- CardHeader,
18
- CardTitle,
19
- } from "@/components/ui/card";
20
- import { Skeleton } from "@/components/ui/skeleton";
21
- import { demoNodeExporterDashboardPath } from "@/lib/demo-dashboard-path";
22
-
23
- type InstalledDashboard = {
24
- id: string;
25
- name: string;
26
- visibility: "private" | "org" | "public";
27
- updatedAt: string;
28
- archivedAt: string | null;
29
- };
30
-
31
- type DashboardTemplate = {
32
- id: string;
33
- name: string;
34
- description: string;
35
- defaultDashboardId: string;
36
- dataSources: string[];
37
- panelCount: number;
38
- installedDashboards: InstalledDashboard[];
39
- };
40
-
41
- function isDemoTemplate(template: DashboardTemplate): boolean {
42
- return template.dataSources.includes("demo");
43
- }
44
-
45
- function dashboardPathForTemplate(
46
- template: DashboardTemplate,
47
- dashboardId: string,
48
- options: { intro?: boolean } = {},
49
- ): string {
50
- if (template.id === "demo-node-exporter") {
51
- return demoNodeExporterDashboardPath(dashboardId, options);
52
- }
53
- return `/dashboards/${dashboardId}`;
54
- }
55
-
56
- function sourceLabel(source: string): string {
57
- if (source === "first-party") return "First-party";
58
- if (source === "demo") return "Demo Prometheus";
59
- if (source === "ga4") return "GA4";
60
- if (source === "prometheus") return "Prometheus";
61
- return source;
62
- }
63
-
64
- function TemplateSkeleton() {
65
- return (
66
- <Card className="flex min-h-[250px] flex-col">
67
- <CardHeader>
68
- <div className="space-y-3">
69
- <Skeleton className="h-5 w-48" />
70
- <Skeleton className="h-4 w-full" />
71
- <Skeleton className="h-4 w-5/6" />
72
- <Skeleton className="h-4 w-3/5" />
73
- </div>
74
- </CardHeader>
75
- <CardContent className="mt-auto space-y-4">
76
- <Skeleton className="h-4 w-40" />
77
- <Skeleton className="h-9 w-full" />
78
- </CardContent>
79
- </Card>
80
- );
81
- }
82
-
83
- function TemplateCard({
84
- template,
85
- installing,
86
- onInstall,
87
- onOpen,
88
- }: {
89
- template: DashboardTemplate;
90
- installing: boolean;
91
- onInstall: () => void;
92
- onOpen: (dashboardId: string) => void;
93
- }) {
94
- const t = useT();
95
- const installedDashboard = template.installedDashboards.find(
96
- (dashboard) => !dashboard.archivedAt,
97
- );
98
- const isInstalled = Boolean(installedDashboard);
99
- const metadata = [
100
- t("catalog.panels", { count: String(template.panelCount) }),
101
- ...template.dataSources.map(sourceLabel),
102
- ].join(" · ");
103
-
104
- return (
105
- <Card className="flex min-h-[250px] flex-col">
106
- <CardHeader className="gap-2 pb-4">
107
- <CardTitle className="text-base leading-6">{template.name}</CardTitle>
108
- <CardDescription className="text-sm leading-6">
109
- {template.description}
110
- </CardDescription>
111
- </CardHeader>
112
- <CardContent className="mt-auto">
113
- <p className="text-sm text-muted-foreground">{metadata}</p>
114
- </CardContent>
115
- <CardFooter className="gap-2">
116
- {isInstalled && installedDashboard ? (
117
- <Button
118
- variant="secondary"
119
- className="min-w-0 flex-1"
120
- onClick={() => onOpen(installedDashboard.id)}
121
- >
122
- {t("catalog.open")}
123
- </Button>
124
- ) : (
125
- <Button
126
- variant="secondary"
127
- className="min-w-0 flex-1"
128
- onClick={onInstall}
129
- disabled={installing}
130
- >
131
- {installing ? t("catalog.installing") : t("catalog.install")}
132
- </Button>
133
- )}
134
- </CardFooter>
135
- </Card>
136
- );
137
- }
138
-
139
- export default function TemplateCatalogRoute() {
140
- const t = useT();
141
- const [installingIds, setInstallingIds] = useState<Set<string>>(new Set());
142
- const navigate = useNavigate();
143
- const install = useActionMutation("install-dashboard-template");
144
- const { data, isLoading } = useActionQuery(
145
- "list-dashboard-templates",
146
- undefined,
147
- { staleTime: 30_000 },
148
- );
149
-
150
- const templates = useMemo(
151
- () => (data as DashboardTemplate[] | undefined) ?? [],
152
- [data],
153
- );
154
-
155
- async function installTemplate(template: DashboardTemplate) {
156
- setInstallingIds((prev) => new Set(prev).add(template.id));
157
- try {
158
- const result = (await install.mutateAsync({
159
- templateId: template.id,
160
- })) as {
161
- dashboardId?: string;
162
- name?: string;
163
- message?: string;
164
- alreadyInstalled?: boolean;
165
- };
166
- if (result.dashboardId) {
167
- toast.success(
168
- result.message ??
169
- t("catalog.installSuccess", { name: template.name }),
170
- );
171
- navigate(
172
- dashboardPathForTemplate(template, result.dashboardId, {
173
- intro: isDemoTemplate(template) && !result.alreadyInstalled,
174
- }),
175
- );
176
- }
177
- } catch (err) {
178
- toast.error(
179
- err instanceof Error
180
- ? t("catalog.installFailedWithMessage", {
181
- name: template.name,
182
- message: err.message,
183
- })
184
- : t("catalog.installFailed", { name: template.name }),
185
- );
186
- } finally {
187
- setInstallingIds((prev) => {
188
- const next = new Set(prev);
189
- next.delete(template.id);
190
- return next;
191
- });
192
- }
193
- }
194
-
195
- return (
196
- <div className="mx-auto flex w-full max-w-5xl flex-col gap-5">
197
- <div className="max-w-2xl">
198
- <p className="text-sm leading-6 text-muted-foreground">
199
- {t("catalog.description")}
200
- </p>
201
- </div>
202
-
203
- {isLoading ? (
204
- <div className="grid gap-4 md:grid-cols-2">
205
- {Array.from({ length: 4 }).map((_, index) => (
206
- <TemplateSkeleton key={index} />
207
- ))}
208
- </div>
209
- ) : templates.length === 0 ? (
210
- <Card className="border-dashed">
211
- <CardContent className="flex flex-col items-center justify-center gap-3 py-16 text-center">
212
- <IconLayoutDashboard className="h-8 w-8 text-muted-foreground" />
213
- <div>
214
- <h2 className="text-base font-semibold">
215
- {t("catalog.noTemplatesFound")}
216
- </h2>
217
- <p className="mt-1 text-sm text-muted-foreground">
218
- {t("catalog.tryDifferentCategory")}
219
- </p>
220
- </div>
221
- </CardContent>
222
- </Card>
223
- ) : (
224
- <div className="grid gap-4 md:grid-cols-2">
225
- {templates.map((template) => (
226
- <TemplateCard
227
- key={template.id}
228
- template={template}
229
- installing={installingIds.has(template.id)}
230
- onInstall={() => void installTemplate(template)}
231
- onOpen={(dashboardId) =>
232
- navigate(dashboardPathForTemplate(template, dashboardId))
233
- }
234
- />
235
- ))}
236
- </div>
237
- )}
238
- </div>
239
- );
240
- }