@agent-native/core 0.77.16 → 0.77.18

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 (63) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +19 -0
  3. package/corpus/core/package.json +1 -1
  4. package/corpus/core/src/agent/production-agent.ts +21 -5
  5. package/corpus/core/src/client/AgentPanel.tsx +4 -0
  6. package/corpus/core/src/client/org/OrgSwitcher.tsx +4 -0
  7. package/corpus/core/src/client/use-db-sync.ts +19 -2
  8. package/corpus/core/src/server/auth-marketing.ts +15 -0
  9. package/corpus/core/src/server/onboarding-html.ts +47 -1
  10. package/corpus/templates/analytics/.agents/skills/dashboard-management/SKILL.md +76 -0
  11. package/corpus/templates/analytics/actions/update-dashboard.ts +1 -0
  12. package/corpus/templates/analytics/app/components/dashboard/SqlChart.tsx +13 -2
  13. package/corpus/templates/analytics/app/i18n-data.ts +10 -0
  14. package/corpus/templates/analytics/app/lib/sql-query.ts +97 -21
  15. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/SqlChartCard.tsx +22 -1
  16. package/corpus/templates/analytics/app/root.tsx +15 -6
  17. package/corpus/templates/analytics/changelog/2026-06-25-dashboard-charts-no-longer-refresh-in-the-background-during-.md +6 -0
  18. package/corpus/templates/analytics/changelog/2026-06-25-first-party-dashboards-use-indexed-event-dates-for-faster-da.md +6 -0
  19. package/corpus/templates/analytics/changelog/2026-06-25-retention-and-active-user-dashboard-panels-now-count-account.md +6 -0
  20. package/corpus/templates/analytics/seeds/dashboards/agent-native-templates-first-party.json +58 -58
  21. package/corpus/templates/analytics/server/db/schema.ts +2 -0
  22. package/corpus/templates/analytics/server/handlers/sql-query.ts +1 -1
  23. package/corpus/templates/analytics/server/lib/dashboard-catalog.ts +1 -1
  24. package/corpus/templates/analytics/server/lib/first-party-analytics.ts +16 -6
  25. package/corpus/templates/analytics/server/lib/first-party-metric-catalog.ts +88 -56
  26. package/corpus/templates/analytics/server/plugins/db.ts +73 -0
  27. package/corpus/templates/clips/AGENTS.md +5 -0
  28. package/corpus/templates/clips/changelog/2026-06-25-github-issue-and-pull-request-pages-can-now-preview-playable.md +6 -0
  29. package/corpus/templates/clips/chrome-extension/PERMISSIONS.md +8 -2
  30. package/corpus/templates/clips/chrome-extension/public/manifest.json +14 -2
  31. package/corpus/templates/clips/chrome-extension/src/github-preview-content.ts +233 -0
  32. package/corpus/templates/clips/chrome-extension/src/github-preview.html +12 -0
  33. package/corpus/templates/clips/chrome-extension/src/github-preview.ts +414 -0
  34. package/corpus/templates/clips/chrome-extension/vite.config.ts +5 -0
  35. package/corpus/templates/mail/actions/archive-email.ts +13 -3
  36. package/corpus/templates/mail/app/hooks/use-emails.ts +5 -2
  37. package/corpus/templates/mail/changelog/2026-06-25-archive-failures-now-explain-when-gmail-needs-reconnecting-p.md +6 -0
  38. package/corpus/templates/mail/shared/archive-errors.ts +137 -0
  39. package/corpus/templates/plan/changelog/2026-06-25-hosted-signup-now-shows-how-to-switch-visual-plan-to-local-files.md +6 -0
  40. package/dist/agent/production-agent.d.ts.map +1 -1
  41. package/dist/agent/production-agent.js +19 -5
  42. package/dist/agent/production-agent.js.map +1 -1
  43. package/dist/client/AgentPanel.d.ts.map +1 -1
  44. package/dist/client/AgentPanel.js +2 -1
  45. package/dist/client/AgentPanel.js.map +1 -1
  46. package/dist/client/org/OrgSwitcher.d.ts.map +1 -1
  47. package/dist/client/org/OrgSwitcher.js +3 -1
  48. package/dist/client/org/OrgSwitcher.js.map +1 -1
  49. package/dist/client/use-db-sync.d.ts +9 -0
  50. package/dist/client/use-db-sync.d.ts.map +1 -1
  51. package/dist/client/use-db-sync.js +8 -1
  52. package/dist/client/use-db-sync.js.map +1 -1
  53. package/dist/file-upload/actions/upload-image.d.ts +2 -2
  54. package/dist/notifications/routes.d.ts +2 -2
  55. package/dist/observability/routes.d.ts +6 -6
  56. package/dist/server/auth-marketing.d.ts +4 -0
  57. package/dist/server/auth-marketing.d.ts.map +1 -1
  58. package/dist/server/auth-marketing.js +9 -0
  59. package/dist/server/auth-marketing.js.map +1 -1
  60. package/dist/server/onboarding-html.d.ts.map +1 -1
  61. package/dist/server/onboarding-html.js +46 -1
  62. package/dist/server/onboarding-html.js.map +1 -1
  63. package/package.json +1 -1
@@ -12,40 +12,114 @@ export interface SqlQueryResult {
12
12
  schema?: { name: string; type: string }[];
13
13
  }
14
14
 
15
+ const MAX_CONCURRENT_SQL_QUERIES = 4;
16
+
17
+ type PendingSqlQuerySlot = {
18
+ resolve: (release: () => void) => void;
19
+ reject: (reason: unknown) => void;
20
+ signal?: AbortSignal;
21
+ onAbort: () => void;
22
+ };
23
+
24
+ let activeSqlQueries = 0;
25
+ const pendingSqlQuerySlots: PendingSqlQuerySlot[] = [];
26
+
27
+ function createAbortError(): Error {
28
+ if (typeof DOMException !== "undefined") {
29
+ return new DOMException("SQL query aborted", "AbortError");
30
+ }
31
+ const error = new Error("SQL query aborted");
32
+ error.name = "AbortError";
33
+ return error;
34
+ }
35
+
36
+ function createSqlQueryRelease(): () => void {
37
+ let released = false;
38
+ return () => {
39
+ if (released) return;
40
+ released = true;
41
+ activeSqlQueries = Math.max(0, activeSqlQueries - 1);
42
+ drainSqlQuerySlots();
43
+ };
44
+ }
45
+
46
+ function drainSqlQuerySlots(): void {
47
+ while (
48
+ activeSqlQueries < MAX_CONCURRENT_SQL_QUERIES &&
49
+ pendingSqlQuerySlots.length > 0
50
+ ) {
51
+ const pending = pendingSqlQuerySlots.shift();
52
+ if (!pending) return;
53
+ pending.signal?.removeEventListener("abort", pending.onAbort);
54
+ if (pending.signal?.aborted) {
55
+ pending.reject(createAbortError());
56
+ continue;
57
+ }
58
+ activeSqlQueries += 1;
59
+ pending.resolve(createSqlQueryRelease());
60
+ }
61
+ }
62
+
63
+ async function acquireSqlQuerySlot(signal?: AbortSignal): Promise<() => void> {
64
+ if (signal?.aborted) throw createAbortError();
65
+ return new Promise((resolve, reject) => {
66
+ const pending: PendingSqlQuerySlot = {
67
+ resolve,
68
+ reject,
69
+ signal,
70
+ onAbort: () => {
71
+ const index = pendingSqlQuerySlots.indexOf(pending);
72
+ if (index >= 0) pendingSqlQuerySlots.splice(index, 1);
73
+ reject(createAbortError());
74
+ },
75
+ };
76
+ signal?.addEventListener("abort", pending.onAbort, { once: true });
77
+ pendingSqlQuerySlots.push(pending);
78
+ drainSqlQuerySlots();
79
+ });
80
+ }
81
+
82
+ async function readSqlQueryError(res: Response): Promise<string> {
83
+ const body = await res.json().catch(() => ({}));
84
+ return typeof body?.error === "string"
85
+ ? body.error
86
+ : `Query failed (${res.status})`;
87
+ }
88
+
15
89
  export async function executeSqlQuery(
16
90
  sql: string,
17
91
  source: DataSourceType,
18
92
  signal?: AbortSignal,
19
93
  ): Promise<SqlQueryResult> {
20
94
  const token = await getIdToken();
21
- const res = await fetch(appApiPath("/api/sql-query"), {
22
- method: "POST",
23
- signal,
24
- headers: {
25
- "Content-Type": "application/json",
26
- ...(token && { Authorization: `Bearer ${token}` }),
27
- },
28
- body: JSON.stringify({ query: sql, source }),
29
- });
95
+ const release = await acquireSqlQuerySlot(signal);
96
+ let res: Response;
97
+ try {
98
+ res = await fetch(appApiPath("/api/sql-query"), {
99
+ method: "POST",
100
+ signal,
101
+ headers: {
102
+ "Content-Type": "application/json",
103
+ ...(token && { Authorization: `Bearer ${token}` }),
104
+ },
105
+ body: JSON.stringify({ query: sql, source }),
106
+ });
107
+ } finally {
108
+ release();
109
+ }
30
110
 
31
111
  if (!res.ok) {
32
- const body = await res.json().catch(() => ({}));
33
- return {
34
- rows: [],
35
- error: body.error || `Query failed (${res.status})`,
36
- };
112
+ throw new Error(await readSqlQueryError(res));
37
113
  }
38
114
 
39
115
  const data = await res.json();
40
116
 
41
117
  if (typeof data?.error === "string") {
42
- return {
43
- rows: [],
44
- error:
45
- typeof data.message === "string" && data.message
46
- ? data.message
47
- : data.error,
48
- };
118
+ throw new Error(
119
+ typeof data.message === "string" && data.message
120
+ ? data.message
121
+ : data.error,
122
+ );
49
123
  }
50
124
 
51
125
  if (data.bytesProcessed) {
@@ -68,6 +142,7 @@ export function useSqlQuery(
68
142
  refetchOnMount?: boolean | "always";
69
143
  refetchOnReconnect?: boolean | "always";
70
144
  refetchOnWindowFocus?: boolean | "always";
145
+ retry?: boolean | number;
71
146
  staleTime?: number;
72
147
  },
73
148
  ) {
@@ -79,6 +154,7 @@ export function useSqlQuery(
79
154
  refetchOnMount: options?.refetchOnMount ?? false,
80
155
  refetchOnReconnect: options?.refetchOnReconnect ?? false,
81
156
  refetchOnWindowFocus: options?.refetchOnWindowFocus ?? false,
157
+ retry: options?.retry ?? false,
82
158
  staleTime: options?.staleTime ?? 5 * 60 * 1000,
83
159
  });
84
160
  }
@@ -6,10 +6,12 @@ import {
6
6
  IconDotsVertical,
7
7
  IconMaximize,
8
8
  IconPencil,
9
+ IconRefresh,
9
10
  IconTrash,
10
11
  IconCode,
11
12
  IconDownload,
12
13
  } from "@tabler/icons-react";
14
+ import { useQueryClient } from "@tanstack/react-query";
13
15
  import { useCallback, useEffect, useRef, useState } from "react";
14
16
 
15
17
  import { ChartFillHeight, SqlChart } from "@/components/dashboard/SqlChart";
@@ -43,6 +45,7 @@ import {
43
45
  TooltipTrigger,
44
46
  } from "@/components/ui/tooltip";
45
47
 
48
+ import { serializePanelSql } from "./panel-sql";
46
49
  import type { SqlPanel } from "./types";
47
50
  import { ViewSqlPopover } from "./ViewSqlPopover";
48
51
 
@@ -66,6 +69,7 @@ export function SqlChartCard({
66
69
  editable = true,
67
70
  }: SqlChartCardProps) {
68
71
  const t = useT();
72
+ const queryClient = useQueryClient();
69
73
  const {
70
74
  attributes,
71
75
  listeners,
@@ -95,6 +99,18 @@ export function SqlChartCard({
95
99
  setExportCsv(handler ? () => handler : null);
96
100
  }, []);
97
101
 
102
+ const handleRefresh = useCallback(() => {
103
+ setShouldLoadData(true);
104
+ void queryClient.invalidateQueries({
105
+ queryKey: [
106
+ "sql-chart",
107
+ panel.id,
108
+ serializePanelSql(resolvedSql ?? panel.sql),
109
+ panel.source,
110
+ ],
111
+ });
112
+ }, [panel.id, panel.source, panel.sql, queryClient, resolvedSql]);
113
+
98
114
  useEffect(() => {
99
115
  if (panel.chartType === "section") {
100
116
  setShouldLoadData(true);
@@ -116,7 +132,7 @@ export function SqlChartCard({
116
132
  }
117
133
  },
118
134
  {
119
- rootMargin: "800px 0px",
135
+ rootMargin: "320px 0px",
120
136
  threshold: 0.01,
121
137
  },
122
138
  );
@@ -310,6 +326,11 @@ export function SqlChartCard({
310
326
  {t("sidebar.edit")}
311
327
  </DropdownMenuItem>
312
328
  )}
329
+ {!editable ? <DropdownMenuSeparator /> : null}
330
+ <DropdownMenuItem onSelect={handleRefresh}>
331
+ <IconRefresh className="h-4 w-4 mr-2" />
332
+ {t("sqlDashboard.refresh")}
333
+ </DropdownMenuItem>
313
334
  {editable ? (
314
335
  <DropdownMenuItem
315
336
  onSelect={(e) => {
@@ -8,7 +8,7 @@ import {
8
8
  } from "@agent-native/core/client";
9
9
  import { configureTracking } from "@agent-native/core/client";
10
10
  import { useQueryClient } from "@tanstack/react-query";
11
- import { useState } from "react";
11
+ import { useCallback, useState } from "react";
12
12
  import { Links, Meta, Outlet, Scripts, ScrollRestoration } from "react-router";
13
13
  import type { LinksFunction } from "react-router";
14
14
 
@@ -79,12 +79,21 @@ export function Layout({ children }: { children: React.ReactNode }) {
79
79
 
80
80
  function DbSyncBridge() {
81
81
  // Invalidate react-query caches on DB changes (agent edits, other tabs,
82
- // cron jobs). The hook invalidates every active query on any non-own
83
- // change event, so we no longer need to enumerate dashboard / analysis
84
- // / explorer keys here. Screen-refresh is handled automatically inside
85
- // AgentSidebar.
82
+ // cron jobs). SQL chart queries can be expensive, so they stay on explicit
83
+ // refresh/filter semantics instead of joining the broad action fallback.
84
+ // Screen-refresh is handled automatically inside AgentSidebar.
86
85
  const queryClient = useQueryClient();
87
- useDbSync({ queryClient, ignoreSource: TAB_ID });
86
+ const shouldInvalidateForAction = useCallback(
87
+ (query: { queryKey: readonly unknown[] }) => {
88
+ return query.queryKey[0] !== "sql-chart";
89
+ },
90
+ [],
91
+ );
92
+ useDbSync({
93
+ queryClient,
94
+ ignoreSource: TAB_ID,
95
+ actionInvalidatePredicate: shouldInvalidateForAction,
96
+ });
88
97
  return null;
89
98
  }
90
99
 
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-06-25
4
+ ---
5
+
6
+ Dashboard charts no longer refresh in the background during agent activity and load more steadily on large dashboards.
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: improved
3
+ date: 2026-06-25
4
+ ---
5
+
6
+ First-party dashboards use indexed event dates for faster date-range charts.
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-06-25
4
+ ---
5
+
6
+ Retention and active-user dashboard panels now exclude docs traffic, smooth retention cohorts, and use clearer signed-in visitor labels.