@agent-native/core 0.77.24 → 0.78.1

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 (212) hide show
  1. package/corpus/README.md +2 -2
  2. package/corpus/core/CHANGELOG.md +20 -0
  3. package/corpus/core/docs/content/template-analytics.md +5 -0
  4. package/corpus/core/docs/content/tracking.md +45 -0
  5. package/corpus/core/package.json +3 -1
  6. package/corpus/core/src/agent/types.ts +22 -0
  7. package/corpus/core/src/client/AgentPanel.tsx +34 -18
  8. package/corpus/core/src/client/AssistantChat.tsx +40 -0
  9. package/corpus/core/src/client/agent-chat.ts +173 -0
  10. package/corpus/core/src/client/analytics.ts +141 -5
  11. package/corpus/core/src/client/composer/TiptapComposer.tsx +316 -60
  12. package/corpus/core/src/client/composer/extensions/MentionReference.tsx +3 -0
  13. package/corpus/core/src/client/composer/types.ts +22 -0
  14. package/corpus/core/src/client/index.ts +14 -0
  15. package/corpus/core/src/client/session-replay.ts +800 -0
  16. package/corpus/core/src/db/schema.ts +12 -0
  17. package/corpus/core/src/deploy/build.ts +75 -0
  18. package/corpus/core/src/private-blob/index.ts +18 -0
  19. package/corpus/core/src/private-blob/registry.ts +241 -0
  20. package/corpus/core/src/private-blob/types.ts +55 -0
  21. package/corpus/core/src/scripts/utils.ts +34 -6
  22. package/corpus/core/src/server/agent-chat-plugin.ts +10 -0
  23. package/corpus/core/src/server/email.ts +5 -0
  24. package/corpus/core/src/server/index.ts +1 -0
  25. package/corpus/core/src/styles/agent-native.css +24 -0
  26. package/corpus/templates/analytics/.agents/skills/dashboard-management/SKILL.md +1 -1
  27. package/corpus/templates/analytics/.agents/skills/session-replay/SKILL.md +43 -0
  28. package/corpus/templates/analytics/AGENTS.md +32 -1
  29. package/corpus/templates/analytics/DEVELOPING.md +9 -1
  30. package/corpus/templates/analytics/actions/delete-dashboard-report-subscription.ts +24 -0
  31. package/corpus/templates/analytics/actions/get-session-replay-events.ts +50 -0
  32. package/corpus/templates/analytics/actions/get-session-replay-summary.ts +27 -0
  33. package/corpus/templates/analytics/actions/list-dashboard-report-subscriptions.ts +24 -0
  34. package/corpus/templates/analytics/actions/list-session-recordings.ts +59 -0
  35. package/corpus/templates/analytics/actions/navigate.ts +15 -5
  36. package/corpus/templates/analytics/actions/query-agent-native-analytics.ts +2 -2
  37. package/corpus/templates/analytics/actions/save-dashboard-report-subscription.ts +36 -0
  38. package/corpus/templates/analytics/actions/send-dashboard-report-now.ts +60 -0
  39. package/corpus/templates/analytics/actions/view-screen.ts +50 -2
  40. package/corpus/templates/analytics/app/components/dashboard/SqlChart.tsx +51 -2
  41. package/corpus/templates/analytics/app/components/layout/Header.tsx +4 -1
  42. package/corpus/templates/analytics/app/components/layout/Layout.tsx +14 -2
  43. package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +6 -6
  44. package/corpus/templates/analytics/app/hooks/use-navigation-state.ts +35 -6
  45. package/corpus/templates/analytics/app/i18n-data.ts +1119 -0
  46. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/EmailReportDialog.tsx +437 -0
  47. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/SqlChartCard.tsx +11 -12
  48. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/dashboard-layout.ts +43 -0
  49. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +136 -43
  50. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/report-filters.ts +26 -0
  51. package/corpus/templates/analytics/app/pages/sessions/SessionDetailPage.tsx +450 -0
  52. package/corpus/templates/analytics/app/pages/sessions/SessionsPage.tsx +419 -0
  53. package/corpus/templates/analytics/app/routes/overview.tsx +13 -6
  54. package/corpus/templates/analytics/app/routes/sessions.$recordingId.tsx +10 -0
  55. package/corpus/templates/analytics/app/routes/sessions._index.tsx +10 -0
  56. package/corpus/templates/analytics/changelog/2026-06-25-analytics-now-opens-directly-to-ask-instead-of-the-old-overv.md +6 -0
  57. package/corpus/templates/analytics/changelog/2026-06-25-dashboard-charts-now-show-reliable-blue-placement-lines-whil.md +6 -0
  58. package/corpus/templates/analytics/changelog/2026-06-26-dashboards-can-now-send-scheduled-daily-email-reports-from-t.md +6 -0
  59. package/corpus/templates/analytics/changelog/2026-06-26-session-replay-browser-sessions-can-now-be-reviewed-from.md +6 -0
  60. package/corpus/templates/analytics/package.json +5 -2
  61. package/corpus/templates/analytics/scripts/emit-netlify-dashboard-report-cron.ts +131 -0
  62. package/corpus/templates/analytics/scripts/seed-session-replay.ts +198 -0
  63. package/corpus/templates/analytics/seeds/dashboards/agent-native-templates-first-party.json +140 -27
  64. package/corpus/templates/analytics/server/db/index.ts +12 -0
  65. package/corpus/templates/analytics/server/db/schema.ts +131 -0
  66. package/corpus/templates/analytics/server/handlers/session-replay.ts +281 -0
  67. package/corpus/templates/analytics/server/handlers/sql-query.ts +8 -348
  68. package/corpus/templates/analytics/server/jobs/dashboard-report.ts +66 -0
  69. package/corpus/templates/analytics/server/jobs/session-replay-retention.ts +29 -0
  70. package/corpus/templates/analytics/server/lib/dashboard-panel-query.ts +365 -0
  71. package/corpus/templates/analytics/server/lib/dashboard-report-subscriptions.ts +473 -0
  72. package/corpus/templates/analytics/server/lib/dashboard-report.ts +405 -0
  73. package/corpus/templates/analytics/server/lib/first-party-analytics.ts +63 -18
  74. package/corpus/templates/analytics/server/lib/first-party-metric-catalog.ts +98 -26
  75. package/corpus/templates/analytics/server/lib/session-replay.ts +1645 -0
  76. package/corpus/templates/analytics/server/plugins/auth.ts +1 -1
  77. package/corpus/templates/analytics/server/plugins/dashboard-report-jobs.ts +47 -0
  78. package/corpus/templates/analytics/server/plugins/db.ts +200 -0
  79. package/corpus/templates/analytics/server/plugins/session-replay-retention-jobs.ts +41 -0
  80. package/corpus/templates/analytics/server/routes/api/analytics/replay.options.ts +1 -0
  81. package/corpus/templates/analytics/server/routes/api/analytics/replay.post.ts +1 -0
  82. package/corpus/templates/analytics/server/routes/api/dashboard-reports/run.post.ts +61 -0
  83. package/corpus/templates/analytics/server/routes/api/session-replay/recordings/[recordingId]/chunks/[seq].get.ts +1 -0
  84. package/corpus/templates/analytics/server/routes/api/session-replay/recordings/[recordingId]/events.get.ts +1 -0
  85. package/corpus/templates/analytics/server/routes/api/session-replay/recordings/[recordingId]/manifest.get.ts +1 -0
  86. package/corpus/templates/analytics/server/routes/api/session-replay/recordings/[recordingId].get.ts +1 -0
  87. package/corpus/templates/analytics/server/routes/api/session-replay/recordings.get.ts +1 -0
  88. package/corpus/templates/assets/.agents/skills/asset-generation/SKILL.md +10 -2
  89. package/corpus/templates/assets/.agents/skills/image-generation/SKILL.md +14 -7
  90. package/corpus/templates/assets/.agents/skills/library-management/SKILL.md +12 -0
  91. package/corpus/templates/assets/AGENTS.md +18 -3
  92. package/corpus/templates/assets/actions/_image-model-default.ts +24 -0
  93. package/corpus/templates/assets/actions/dismiss-variant-slots.ts +15 -22
  94. package/corpus/templates/assets/actions/generate-asset.ts +30 -23
  95. package/corpus/templates/assets/actions/generate-image-batch.ts +76 -29
  96. package/corpus/templates/assets/actions/generate-image.ts +46 -30
  97. package/corpus/templates/assets/actions/generate-video.ts +18 -3
  98. package/corpus/templates/assets/actions/list-assets.ts +39 -8
  99. package/corpus/templates/assets/actions/list-libraries.ts +24 -2
  100. package/corpus/templates/assets/actions/navigate.ts +1 -1
  101. package/corpus/templates/assets/actions/open-asset-picker.ts +38 -1
  102. package/corpus/templates/assets/actions/refresh-generation-run.ts +10 -0
  103. package/corpus/templates/assets/actions/save-generated-image.ts +21 -19
  104. package/corpus/templates/assets/actions/variant-slots.ts +131 -28
  105. package/corpus/templates/assets/actions/view-screen.ts +9 -0
  106. package/corpus/templates/assets/app/components/generation/GenerationResults.tsx +425 -0
  107. package/corpus/templates/assets/app/components/layout/Header.tsx +3 -1
  108. package/corpus/templates/assets/app/components/layout/Layout.tsx +5 -0
  109. package/corpus/templates/assets/app/components/layout/Sidebar.tsx +4 -6
  110. package/corpus/templates/assets/app/hooks/use-navigation-state.ts +47 -21
  111. package/corpus/templates/assets/app/i18n-data.ts +1815 -0
  112. package/corpus/templates/assets/app/lib/libraries.ts +6 -0
  113. package/corpus/templates/assets/app/lib/picker-chat-handoff.ts +71 -0
  114. package/corpus/templates/assets/app/routes/_index.tsx +4 -0
  115. package/corpus/templates/assets/app/routes/asset.$id.tsx +2 -2
  116. package/corpus/templates/assets/app/routes/audit.tsx +1 -1
  117. package/corpus/templates/assets/app/routes/brand-kits.$id.tsx +1557 -2164
  118. package/corpus/templates/assets/app/routes/brand-kits._index.tsx +7 -191
  119. package/corpus/templates/assets/app/routes/brand-kits.tsx +1 -5
  120. package/corpus/templates/assets/app/routes/libraries.tsx +4 -4
  121. package/corpus/templates/assets/app/routes/library.$id.tsx +9 -7
  122. package/corpus/templates/assets/app/routes/library.tsx +1454 -175
  123. package/corpus/templates/assets/server/db/index.ts +1 -1
  124. package/corpus/templates/assets/server/lib/generation.ts +86 -15
  125. package/corpus/templates/assets/server/plugins/agent-chat.ts +196 -0
  126. package/corpus/templates/assets/server/plugins/db.ts +5 -0
  127. package/corpus/templates/assets/shared/api.ts +6 -3
  128. package/dist/agent/types.d.ts +21 -0
  129. package/dist/agent/types.d.ts.map +1 -1
  130. package/dist/agent/types.js.map +1 -1
  131. package/dist/client/AgentPanel.d.ts +6 -2
  132. package/dist/client/AgentPanel.d.ts.map +1 -1
  133. package/dist/client/AgentPanel.js +10 -4
  134. package/dist/client/AgentPanel.js.map +1 -1
  135. package/dist/client/AssistantChat.d.ts +6 -0
  136. package/dist/client/AssistantChat.d.ts.map +1 -1
  137. package/dist/client/AssistantChat.js +15 -3
  138. package/dist/client/AssistantChat.js.map +1 -1
  139. package/dist/client/agent-chat.d.ts +32 -0
  140. package/dist/client/agent-chat.d.ts.map +1 -1
  141. package/dist/client/agent-chat.js +113 -0
  142. package/dist/client/agent-chat.js.map +1 -1
  143. package/dist/client/analytics.d.ts +22 -3
  144. package/dist/client/analytics.d.ts.map +1 -1
  145. package/dist/client/analytics.js +96 -4
  146. package/dist/client/analytics.js.map +1 -1
  147. package/dist/client/composer/TiptapComposer.d.ts +2 -1
  148. package/dist/client/composer/TiptapComposer.d.ts.map +1 -1
  149. package/dist/client/composer/TiptapComposer.js +228 -56
  150. package/dist/client/composer/TiptapComposer.js.map +1 -1
  151. package/dist/client/composer/extensions/MentionReference.d.ts.map +1 -1
  152. package/dist/client/composer/extensions/MentionReference.js +3 -0
  153. package/dist/client/composer/extensions/MentionReference.js.map +1 -1
  154. package/dist/client/composer/types.d.ts +21 -0
  155. package/dist/client/composer/types.d.ts.map +1 -1
  156. package/dist/client/composer/types.js.map +1 -1
  157. package/dist/client/index.d.ts +2 -2
  158. package/dist/client/index.d.ts.map +1 -1
  159. package/dist/client/index.js +2 -2
  160. package/dist/client/index.js.map +1 -1
  161. package/dist/client/session-replay.d.ts +41 -0
  162. package/dist/client/session-replay.d.ts.map +1 -0
  163. package/dist/client/session-replay.js +595 -0
  164. package/dist/client/session-replay.js.map +1 -0
  165. package/dist/collab/awareness.d.ts +2 -2
  166. package/dist/collab/awareness.d.ts.map +1 -1
  167. package/dist/collab/routes.d.ts +2 -2
  168. package/dist/db/schema.d.ts +3 -1
  169. package/dist/db/schema.d.ts.map +1 -1
  170. package/dist/db/schema.js +6 -2
  171. package/dist/db/schema.js.map +1 -1
  172. package/dist/deploy/build.d.ts +8 -0
  173. package/dist/deploy/build.d.ts.map +1 -1
  174. package/dist/deploy/build.js +66 -0
  175. package/dist/deploy/build.js.map +1 -1
  176. package/dist/file-upload/actions/upload-image.d.ts +2 -2
  177. package/dist/notifications/routes.d.ts +3 -3
  178. package/dist/observability/routes.d.ts +8 -8
  179. package/dist/private-blob/index.d.ts +3 -0
  180. package/dist/private-blob/index.d.ts.map +1 -0
  181. package/dist/private-blob/index.js +2 -0
  182. package/dist/private-blob/index.js.map +1 -0
  183. package/dist/private-blob/registry.d.ts +10 -0
  184. package/dist/private-blob/registry.d.ts.map +1 -0
  185. package/dist/private-blob/registry.js +152 -0
  186. package/dist/private-blob/registry.js.map +1 -0
  187. package/dist/private-blob/types.d.ts +51 -0
  188. package/dist/private-blob/types.d.ts.map +1 -0
  189. package/dist/private-blob/types.js +2 -0
  190. package/dist/private-blob/types.js.map +1 -0
  191. package/dist/resources/handlers.d.ts +3 -3
  192. package/dist/scripts/utils.d.ts +4 -4
  193. package/dist/scripts/utils.d.ts.map +1 -1
  194. package/dist/scripts/utils.js +30 -6
  195. package/dist/scripts/utils.js.map +1 -1
  196. package/dist/server/agent-chat-plugin.d.ts.map +1 -1
  197. package/dist/server/agent-chat-plugin.js +5 -0
  198. package/dist/server/agent-chat-plugin.js.map +1 -1
  199. package/dist/server/agent-engine-api-key-route.d.ts +2 -2
  200. package/dist/server/email.d.ts +2 -0
  201. package/dist/server/email.d.ts.map +1 -1
  202. package/dist/server/email.js +3 -0
  203. package/dist/server/email.js.map +1 -1
  204. package/dist/server/index.d.ts +1 -1
  205. package/dist/server/index.d.ts.map +1 -1
  206. package/dist/server/index.js.map +1 -1
  207. package/dist/server/transcribe-voice.d.ts +1 -1
  208. package/dist/styles/agent-native.css +24 -0
  209. package/docs/content/template-analytics.md +5 -0
  210. package/docs/content/tracking.md +45 -0
  211. package/package.json +3 -1
  212. package/corpus/templates/analytics/app/pages/overview/OverviewPage.tsx +0 -209
@@ -1,255 +1,12 @@
1
1
  import { readBody } from "@agent-native/core/server";
2
2
  import { defineEventHandler, setResponseStatus } from "h3";
3
3
 
4
- import { getUserSegmentation, queryEvents } from "../lib/amplitude";
5
- import { runQuery } from "../lib/bigquery";
4
+ import { runApiHandlerWithContext } from "../lib/credentials";
6
5
  import {
7
- requireCredential,
8
- runApiHandlerWithContext,
9
- } from "../lib/credentials";
10
- import { runDemoPanel, serializeDemoDescriptorInput } from "../lib/demo-source";
11
- import { queryFirstPartyAnalytics } from "../lib/first-party-analytics";
12
- import { runReport } from "../lib/google-analytics";
13
- import {
14
- runPrometheusPanel,
15
- serializePanelDescriptorInput,
16
- } from "../lib/prometheus";
17
-
18
- /**
19
- * ga4 panels carry a JSON blob in `sql` describing the GA4 Data API call.
20
- * Shape: { metrics: string[]; dimensions?: string[]; days?: number;
21
- * startDate?: string; endDate?: string }. Dates are resolved from
22
- * `days` when startDate/endDate are omitted so seeded dashboards can use
23
- * the simpler `{"days": 30}` form.
24
- */
25
- async function runGa4Panel(raw: string): Promise<{
26
- rows: Record<string, unknown>[];
27
- schema: { name: string; type: string }[];
28
- }> {
29
- let parsed: {
30
- metrics?: unknown;
31
- dimensions?: unknown;
32
- days?: unknown;
33
- startDate?: unknown;
34
- endDate?: unknown;
35
- filter?: unknown;
36
- };
37
- try {
38
- parsed = JSON.parse(raw);
39
- } catch (err: any) {
40
- throw new Error(
41
- `ga4 panel sql must be a JSON object with metrics/dimensions/days: ${err?.message ?? err}`,
42
- );
43
- }
44
- if (!parsed || typeof parsed !== "object") {
45
- throw new Error("ga4 panel sql must be a JSON object");
46
- }
47
- const metrics = Array.isArray(parsed.metrics)
48
- ? parsed.metrics.filter((m): m is string => typeof m === "string")
49
- : [];
50
- if (metrics.length === 0) {
51
- throw new Error("ga4 panel requires at least one metric");
52
- }
53
- const dimensions = Array.isArray(parsed.dimensions)
54
- ? parsed.dimensions.filter((d): d is string => typeof d === "string")
55
- : [];
56
- const days =
57
- typeof parsed.days === "number" ? parsed.days : Number(parsed.days);
58
- const startDate =
59
- typeof parsed.startDate === "string" && parsed.startDate
60
- ? parsed.startDate
61
- : Number.isFinite(days) && days > 0
62
- ? `${days}daysAgo`
63
- : "7daysAgo";
64
- const endDate =
65
- typeof parsed.endDate === "string" && parsed.endDate
66
- ? parsed.endDate
67
- : "today";
68
-
69
- const dimensionFilter =
70
- parsed.filter && typeof parsed.filter === "object"
71
- ? (parsed.filter as Record<string, unknown>)
72
- : undefined;
73
-
74
- const report = await runReport(
75
- dimensions,
76
- metrics,
77
- { startDate, endDate },
78
- dimensionFilter,
79
- );
80
-
81
- // Flatten each GA4 row to { dimensionName: value, metricName: value } so
82
- // downstream chart renderers treat it identically to SQL rows. Metrics are
83
- // parsed to numbers since every chart type (metric card, bar, line, table)
84
- // relies on numeric typing for y-axis detection.
85
- const rows: Record<string, unknown>[] = (report.rows ?? []).map((row) => {
86
- const out: Record<string, unknown> = {};
87
- dimensions.forEach((name, i) => {
88
- out[name] = row.dimensionValues?.[i]?.value ?? "";
89
- });
90
- metrics.forEach((name, i) => {
91
- const raw = row.metricValues?.[i]?.value ?? "0";
92
- const num = Number(raw);
93
- out[name] = Number.isFinite(num) ? num : raw;
94
- });
95
- return out;
96
- });
97
-
98
- const schema = [
99
- ...dimensions.map((name) => ({ name, type: "string" })),
100
- ...metrics.map((name) => ({ name, type: "number" })),
101
- ];
102
- return { rows, schema };
103
- }
104
-
105
- /**
106
- * Amplitude panels carry a JSON blob in `sql` describing the segmentation
107
- * API call. Shape: { event: string; metric?: "totals"|"uniques";
108
- * groupBy?: string; days?: number; startDate?: string; endDate?: string }.
109
- */
110
- async function runAmplitudePanel(raw: string): Promise<{
111
- rows: Record<string, unknown>[];
112
- schema: { name: string; type: string }[];
113
- }> {
114
- let parsed: {
115
- event?: unknown;
116
- metric?: unknown;
117
- groupBy?: unknown;
118
- days?: unknown;
119
- startDate?: unknown;
120
- endDate?: unknown;
121
- };
122
- try {
123
- parsed = JSON.parse(raw);
124
- } catch (err: any) {
125
- throw new Error(
126
- `amplitude panel sql must be a JSON object: ${err?.message ?? err}`,
127
- );
128
- }
129
- if (!parsed || typeof parsed !== "object") {
130
- throw new Error("amplitude panel sql must be a JSON object");
131
- }
132
- if (typeof parsed.event !== "string" || !parsed.event.trim()) {
133
- throw new Error("amplitude panel requires an 'event' field");
134
- }
135
-
136
- const eventType = parsed.event.trim();
137
- const groupBy =
138
- typeof parsed.groupBy === "string" ? parsed.groupBy : undefined;
139
-
140
- const days =
141
- typeof parsed.days === "number" ? parsed.days : Number(parsed.days);
142
- const now = new Date();
143
- const startDate =
144
- Number.isFinite(days) && days > 0
145
- ? new Date(now.getTime() - days * 86_400_000)
146
- : new Date(now.getTime() - 30 * 86_400_000);
147
-
148
- const fmt = (d: Date) => d.toISOString().slice(0, 10).replace(/-/g, "");
149
- const startStr =
150
- typeof parsed.startDate === "string" && parsed.startDate
151
- ? parsed.startDate.replace(/-/g, "")
152
- : fmt(startDate);
153
- const endStr =
154
- typeof parsed.endDate === "string" && parsed.endDate
155
- ? parsed.endDate.replace(/-/g, "")
156
- : fmt(now);
157
-
158
- const response = groupBy
159
- ? await getUserSegmentation(eventType, startStr, endStr, groupBy)
160
- : await queryEvents(eventType, startStr, endStr);
161
-
162
- return flattenAmplitudeResponse(response, groupBy);
163
- }
164
-
165
- function flattenAmplitudeResponse(
166
- response: unknown,
167
- groupBy?: string,
168
- ): {
169
- rows: Record<string, unknown>[];
170
- schema: { name: string; type: string }[];
171
- } {
172
- const data = (response as any)?.data;
173
- if (!data) return { rows: [], schema: [] };
174
-
175
- const xValues: string[] = Array.isArray(data.xValues) ? data.xValues : [];
176
- const series = data.series;
177
-
178
- if (!series || (Array.isArray(series) && series.length === 0)) {
179
- return { rows: [], schema: [] };
180
- }
181
-
182
- // Normalize YYYYMMDD xValues → YYYY-MM-DD for chart rendering
183
- const normDate = (d: string) =>
184
- /^\d{8}$/.test(d)
185
- ? `${d.slice(0, 4)}-${d.slice(4, 6)}-${d.slice(6, 8)}`
186
- : d;
187
-
188
- if (!groupBy) {
189
- // No grouping — time series. series is [[n1, n2, ...]] or [{date: {value:n}}, ...]
190
- const firstSeries = Array.isArray(series) ? series[0] : series;
191
- const rows: Record<string, unknown>[] = xValues.map((dateStr, i) => {
192
- let count = 0;
193
- if (Array.isArray(firstSeries)) {
194
- count = typeof firstSeries[i] === "number" ? firstSeries[i] : 0;
195
- } else if (firstSeries && typeof firstSeries === "object") {
196
- const entry = (firstSeries as Record<string, any>)[dateStr];
197
- count = entry?.value ?? 0;
198
- }
199
- return { date: normDate(dateStr), count };
200
- });
201
- return {
202
- rows,
203
- schema: [
204
- { name: "date", type: "string" },
205
- { name: "count", type: "number" },
206
- ],
207
- };
208
- }
209
-
210
- // With groupBy — aggregate each series across dates into one row per group
211
- const seriesLabels: unknown[] = Array.isArray(data.seriesLabels)
212
- ? data.seriesLabels
213
- : [];
214
- const rows: Record<string, unknown>[] = [];
215
-
216
- if (Array.isArray(series)) {
217
- for (let i = 0; i < series.length; i++) {
218
- const s = series[i];
219
- let label = `Group ${i}`;
220
- if (seriesLabels[i]) {
221
- const entry = seriesLabels[i];
222
- if (Array.isArray(entry) && entry.length >= 2) {
223
- label = String(entry[entry.length - 1]);
224
- } else {
225
- label = String(entry);
226
- }
227
- }
228
-
229
- let total = 0;
230
- if (Array.isArray(s)) {
231
- for (const n of s) total += typeof n === "number" ? n : 0;
232
- } else if (s && typeof s === "object") {
233
- for (const val of Object.values(s as Record<string, any>)) {
234
- total += val?.value ?? (typeof val === "number" ? val : 0);
235
- }
236
- }
237
-
238
- rows.push({ [groupBy]: label, count: total });
239
- }
240
- }
241
-
242
- // Sort descending by count
243
- rows.sort((a, b) => (b.count as number) - (a.count as number));
244
-
245
- return {
246
- rows,
247
- schema: [
248
- { name: groupBy, type: "string" },
249
- { name: "count", type: "number" },
250
- ],
251
- };
252
- }
6
+ isDashboardPanelSource,
7
+ normalizeDashboardPanelQuery,
8
+ runDashboardPanelQuery,
9
+ } from "../lib/dashboard-panel-query";
253
10
 
254
11
  export const handleSqlQuery = defineEventHandler(async (event) => {
255
12
  return runApiHandlerWithContext(event, async (ctx) => {
@@ -258,17 +15,7 @@ export const handleSqlQuery = defineEventHandler(async (event) => {
258
15
  source?: unknown;
259
16
  };
260
17
 
261
- if (
262
- typeof source !== "string" ||
263
- ![
264
- "bigquery",
265
- "ga4",
266
- "amplitude",
267
- "first-party",
268
- "demo",
269
- "prometheus",
270
- ].includes(source)
271
- ) {
18
+ if (!isDashboardPanelSource(source)) {
272
19
  setResponseStatus(event, 400);
273
20
  return {
274
21
  error:
@@ -276,96 +23,9 @@ export const handleSqlQuery = defineEventHandler(async (event) => {
276
23
  };
277
24
  }
278
25
 
279
- let query: string;
280
- if (source === "prometheus" || source === "demo") {
281
- if (rawQuery === undefined || rawQuery === null || rawQuery === "") {
282
- setResponseStatus(event, 400);
283
- return { error: "Missing or invalid query" };
284
- }
285
- try {
286
- query =
287
- source === "demo"
288
- ? serializeDemoDescriptorInput(rawQuery)
289
- : serializePanelDescriptorInput(rawQuery);
290
- } catch (err: any) {
291
- setResponseStatus(event, 400);
292
- return { error: err?.message || "Missing or invalid query" };
293
- }
294
- } else {
295
- if (!rawQuery || typeof rawQuery !== "string") {
296
- setResponseStatus(event, 400);
297
- return { error: "Missing or invalid query" };
298
- }
299
- query = rawQuery;
300
- }
301
-
302
26
  try {
303
- if (source === "bigquery") {
304
- const missing = await requireCredential(
305
- event,
306
- "BIGQUERY_PROJECT_ID",
307
- "BigQuery",
308
- );
309
- if (missing) return missing;
310
- const result = await runQuery(query);
311
- return result;
312
- }
313
-
314
- if (source === "ga4") {
315
- const missingProp = await requireCredential(
316
- event,
317
- "GA4_PROPERTY_ID",
318
- "Google Analytics",
319
- );
320
- if (missingProp) return missingProp;
321
- const missingCreds = await requireCredential(
322
- event,
323
- "GOOGLE_APPLICATION_CREDENTIALS_JSON",
324
- "Google Analytics",
325
- );
326
- if (missingCreds) return missingCreds;
327
- return await runGa4Panel(query);
328
- }
329
-
330
- if (source === "amplitude") {
331
- const missingKey = await requireCredential(
332
- event,
333
- "AMPLITUDE_API_KEY",
334
- "Amplitude",
335
- );
336
- if (missingKey) return missingKey;
337
- const missingSecret = await requireCredential(
338
- event,
339
- "AMPLITUDE_SECRET_KEY",
340
- "Amplitude",
341
- );
342
- if (missingSecret) return missingSecret;
343
- return await runAmplitudePanel(query);
344
- }
345
-
346
- if (source === "first-party") {
347
- return await queryFirstPartyAnalytics(query, {
348
- userEmail: ctx.userEmail,
349
- orgId: ctx.orgId ?? null,
350
- });
351
- }
352
-
353
- if (source === "demo") {
354
- return await runDemoPanel(query);
355
- }
356
-
357
- if (source === "prometheus") {
358
- const missingUrl = await requireCredential(
359
- event,
360
- "PROMETHEUS_URL",
361
- "Prometheus",
362
- );
363
- if (missingUrl) return missingUrl;
364
- return await runPrometheusPanel(query);
365
- }
366
-
367
- setResponseStatus(event, 400);
368
- return { error: "Unsupported source" };
27
+ const query = normalizeDashboardPanelQuery(source, rawQuery);
28
+ return await runDashboardPanelQuery({ source, query, ctx });
369
29
  } catch (error: any) {
370
30
  const message = error?.message || String(error);
371
31
  console.error(`SQL query error (${source}):`, message);
@@ -0,0 +1,66 @@
1
+ import { runWithRequestContext } from "@agent-native/core/server/request-context";
2
+
3
+ import { sendDashboardReportSubscription } from "../lib/dashboard-report";
4
+ import {
5
+ claimDueDashboardReportSubscriptions,
6
+ markDashboardReportResult,
7
+ } from "../lib/dashboard-report-subscriptions";
8
+
9
+ let running = false;
10
+ const DEFAULT_MAX_REPORTS_PER_SWEEP = 5;
11
+
12
+ function maxReportsPerSweep(): number {
13
+ const raw = process.env.DASHBOARD_REPORT_SWEEP_LIMIT?.trim();
14
+ if (!raw) return DEFAULT_MAX_REPORTS_PER_SWEEP;
15
+ const parsed = Number.parseInt(raw, 10);
16
+ return Number.isFinite(parsed) && parsed > 0
17
+ ? parsed
18
+ : DEFAULT_MAX_REPORTS_PER_SWEEP;
19
+ }
20
+
21
+ /**
22
+ * Run one dashboard report sweep. Exported for deployment-specific scheduled
23
+ * functions that should not rely on a long-lived Node process.
24
+ */
25
+ export async function runDashboardReportsOnce(): Promise<{
26
+ processed: number;
27
+ failed: number;
28
+ remaining: number;
29
+ }> {
30
+ if (running) return { processed: 0, failed: 0, remaining: 0 };
31
+ running = true;
32
+ let processed = 0;
33
+ let failed = 0;
34
+ let remaining = 0;
35
+
36
+ try {
37
+ const sweepLimit = maxReportsPerSweep();
38
+ const batch = await claimDueDashboardReportSubscriptions(sweepLimit);
39
+ remaining = batch.length >= sweepLimit ? 1 : 0;
40
+ for (const sub of batch) {
41
+ processed++;
42
+ try {
43
+ await runWithRequestContext(
44
+ {
45
+ userEmail: sub.ownerEmail,
46
+ orgId: sub.orgId ?? undefined,
47
+ },
48
+ () => sendDashboardReportSubscription(sub),
49
+ );
50
+ await markDashboardReportResult(sub, "success");
51
+ } catch (err: any) {
52
+ failed++;
53
+ const message = err?.message ?? String(err);
54
+ console.error(
55
+ `[dashboard-report] Subscription ${sub.id} failed:`,
56
+ message,
57
+ );
58
+ await markDashboardReportResult(sub, "error", message);
59
+ }
60
+ }
61
+ } finally {
62
+ running = false;
63
+ }
64
+
65
+ return { processed, failed, remaining };
66
+ }
@@ -0,0 +1,29 @@
1
+ import { runSessionReplayRetentionSweep } from "../lib/session-replay";
2
+
3
+ let running = false;
4
+
5
+ /**
6
+ * Run one session replay retention sweep. Exported for deployment-specific
7
+ * scheduled functions that should not rely on a long-lived Node process.
8
+ */
9
+ export async function runSessionReplayRetentionOnce(): Promise<{
10
+ finalized: number;
11
+ expired: number;
12
+ chunks: number;
13
+ blobDeleteFailures: number;
14
+ }> {
15
+ if (running) {
16
+ return {
17
+ finalized: 0,
18
+ expired: 0,
19
+ chunks: 0,
20
+ blobDeleteFailures: 0,
21
+ };
22
+ }
23
+ running = true;
24
+ try {
25
+ return await runSessionReplayRetentionSweep();
26
+ } finally {
27
+ running = false;
28
+ }
29
+ }