@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
@@ -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
@@ -1,165 +0,0 @@
1
- import { useSendToAgentChat } from "@agent-native/core/client/agent-chat";
2
- import { PromptComposer } from "@agent-native/core/client/composer";
3
- import { useActionQuery } from "@agent-native/core/client/hooks";
4
- import { useT } from "@agent-native/core/client/i18n";
5
- import {
6
- IconAlertCircle,
7
- IconCheck,
8
- IconDatabase,
9
- IconLoader2,
10
- IconPlus,
11
- } from "@tabler/icons-react";
12
- import { useMemo, useState } from "react";
13
- import { Link } from "react-router";
14
-
15
- import { Badge } from "@/components/ui/badge";
16
- import {
17
- Popover,
18
- PopoverContent,
19
- PopoverTrigger,
20
- } from "@/components/ui/popover";
21
- import {
22
- credentialRowsFromStatus,
23
- getConfiguredDataSources,
24
- type DataSourceStatusResponse,
25
- } from "@/lib/data-source-status";
26
- import { cn } from "@/lib/utils";
27
-
28
- const ANALYSIS_CONTEXT =
29
- "The user wants to kick off a new ad-hoc analysis. " +
30
- "REAL_DATA_REQUIRED: before saving or answering, run at least one real data-source query action; `data-source-status`, `list-data-dictionary`, `generate-chart`, and `save-analysis` do not count as data queries. " +
31
- "The built-in `demo` dashboard source is a demo-environment Prometheus source and never satisfies REAL_DATA_REQUIRED for user analytics answers or saved analyses unless the user explicitly asks to analyze the demo dashboard itself. " +
32
- "If no source can answer, report the exact unavailable/error result instead of saving a guessed analysis. " +
33
- "Read the `adhoc-analysis` skill first. Then: gather data from relevant sources, " +
34
- "synthesize findings, and save via `save-analysis` with --id, --name, --question, " +
35
- "--description, --instructions (markdown recipe for re-running), --resultMarkdown (polished writeup), " +
36
- "--dataSources (JSON array of data sources used), and --resultData (structured raw query results and metrics from the successful data-source actions). " +
37
- "After saving, call `navigate --view=analyses --analysisId=<id>` so the user sees it. " +
38
- "No code files to create — analyses are persisted settings data.";
39
-
40
- function buildAnalysisContext(configuredSourceNames: string[]): string {
41
- const sourceContext = configuredSourceNames.length
42
- ? `Call \`data-source-status\` first to verify source availability. Current credential status shows these configured data sources: ${configuredSourceNames.join(", ")}. Prefer those sources unless the user names a different provider. The data dictionary can describe metrics, but it is not a live data source by itself. The demo source is not evidence for user analytics.`
43
- : "Current credential status shows no configured data sources. Check `data-source-status` before analysis. If the requested provider is unavailable or missing credentials, report the exact connection problem and direct the user to Data Sources instead of saving guessed results. The data dictionary can describe metrics, but it is not a live data source by itself. The demo source is not evidence for user analytics.";
44
- return `${ANALYSIS_CONTEXT} ${sourceContext}`;
45
- }
46
-
47
- export function NewAnalysisDialog() {
48
- const t = useT();
49
- const [open, setOpen] = useState(false);
50
- const { send, isGenerating } = useSendToAgentChat();
51
- const { data: statusData, isLoading: isStatusLoading } = useActionQuery(
52
- "data-source-status",
53
- undefined,
54
- {
55
- staleTime: 10_000,
56
- },
57
- );
58
- const status = statusData as DataSourceStatusResponse | undefined;
59
- const envStatus = credentialRowsFromStatus(status);
60
- const configuredSources = useMemo(
61
- () => getConfiguredDataSources(envStatus, status),
62
- [envStatus, status],
63
- );
64
- const configuredSourceNames = configuredSources.map((source) => source.name);
65
- const statusMessage = status?.message || status?.error;
66
-
67
- function handleSubmit(text: string) {
68
- const trimmed = text.trim();
69
- if (!trimmed || isGenerating) return;
70
- send({
71
- message: trimmed,
72
- context: buildAnalysisContext(configuredSourceNames),
73
- submit: true,
74
- });
75
- setOpen(false);
76
- }
77
-
78
- return (
79
- <Popover open={open} onOpenChange={setOpen}>
80
- <PopoverTrigger asChild>
81
- <button className="flex w-full cursor-pointer items-center gap-2 rounded-lg px-3 py-1.5 text-xs text-muted-foreground/60 hover:bg-sidebar-accent/50 hover:text-primary">
82
- <IconPlus className="h-3 w-3" />
83
- {t("dialogs.newAnalysis")}
84
- </button>
85
- </PopoverTrigger>
86
- <PopoverContent
87
- className="w-[calc(100vw-2rem)] p-3 sm:w-[420px]"
88
- side="right"
89
- align="start"
90
- >
91
- <p className="px-1 pb-2 text-sm font-semibold text-foreground">
92
- {t("dialogs.newAnalysisTitle")}
93
- </p>
94
- <div
95
- className={cn(
96
- "mb-3 rounded-md border px-3 py-2 text-xs",
97
- configuredSources.length > 0
98
- ? "border-emerald-500/25 bg-emerald-500/10"
99
- : "border-amber-500/25 bg-amber-500/10",
100
- )}
101
- >
102
- <div className="flex gap-2">
103
- {isStatusLoading ? (
104
- <IconLoader2 className="mt-0.5 h-3.5 w-3.5 shrink-0 animate-spin text-muted-foreground" />
105
- ) : configuredSources.length > 0 ? (
106
- <IconCheck className="mt-0.5 h-3.5 w-3.5 shrink-0 text-emerald-500" />
107
- ) : (
108
- <IconAlertCircle className="mt-0.5 h-3.5 w-3.5 shrink-0 text-amber-500" />
109
- )}
110
- <div className="min-w-0 flex-1">
111
- <div className="flex items-center gap-1.5 font-medium text-foreground">
112
- <IconDatabase className="h-3.5 w-3.5 text-muted-foreground" />
113
- {isStatusLoading
114
- ? t("dialogs.checkingDataSources")
115
- : configuredSources.length > 0
116
- ? t("dialogs.sourcesConfigured", {
117
- count: configuredSources.length,
118
- plural: configuredSources.length === 1 ? "" : "s",
119
- })
120
- : t("dialogs.noDataSourcesConfigured")}
121
- </div>
122
- {configuredSources.length > 0 ? (
123
- <div className="mt-2 flex flex-wrap gap-1">
124
- {configuredSources.slice(0, 4).map((source) => (
125
- <Badge
126
- key={source.id}
127
- variant="secondary"
128
- className="px-1.5 py-0 text-[10px]"
129
- >
130
- {source.name}
131
- </Badge>
132
- ))}
133
- {configuredSources.length > 4 && (
134
- <Badge
135
- variant="outline"
136
- className="px-1.5 py-0 text-[10px]"
137
- >
138
- {t("dialogs.more", {
139
- count: configuredSources.length - 4,
140
- })}
141
- </Badge>
142
- )}
143
- </div>
144
- ) : (
145
- <p className="mt-1 text-muted-foreground">
146
- {statusMessage || t("dialogs.connectSourceFirst")}{" "}
147
- <Link to="/data-sources" className="text-primary underline">
148
- {t("dialogs.manageSources")}
149
- </Link>
150
- </p>
151
- )}
152
- </div>
153
- </div>
154
- </div>
155
- <PromptComposer
156
- autoFocus
157
- disabled={isGenerating}
158
- placeholder={t("dialogs.newAnalysisPlaceholder")}
159
- draftScope="analytics:new-analysis"
160
- onSubmit={handleSubmit}
161
- />
162
- </PopoverContent>
163
- </Popover>
164
- );
165
- }
@@ -1,175 +0,0 @@
1
- import { useSendToAgentChat } from "@agent-native/core/client/agent-chat";
2
- import { callAction, useChangeVersions } from "@agent-native/core/client/hooks";
3
- import { useT } from "@agent-native/core/client/i18n";
4
- import { IconFlask, IconClock, IconSearch } from "@tabler/icons-react";
5
- import { useQuery } from "@tanstack/react-query";
6
- import { useState } from "react";
7
- import { Link } from "react-router";
8
-
9
- import { Badge } from "@/components/ui/badge";
10
- import {
11
- Card,
12
- CardContent,
13
- CardDescription,
14
- CardHeader,
15
- CardTitle,
16
- } from "@/components/ui/card";
17
- import { Skeleton } from "@/components/ui/skeleton";
18
-
19
- interface AnalysisSummary {
20
- id: string;
21
- name: string;
22
- description: string;
23
- dataSources: string[];
24
- createdAt: string;
25
- updatedAt: string;
26
- author: string;
27
- }
28
-
29
- async function fetchAnalyses(): Promise<AnalysisSummary[]> {
30
- const rows = await callAction("list-analyses", {}, { method: "GET" });
31
- return (Array.isArray(rows) ? rows : []) as AnalysisSummary[];
32
- }
33
-
34
- function formatRelativeDate(iso: string): string {
35
- const date = new Date(iso);
36
- const now = new Date();
37
- const diffMs = now.getTime() - date.getTime();
38
- const diffMins = Math.floor(diffMs / 60000);
39
- const diffHours = Math.floor(diffMins / 60);
40
- const diffDays = Math.floor(diffHours / 24);
41
-
42
- if (diffMins < 1) return "just now";
43
- if (diffMins < 60) return `${diffMins}m ago`;
44
- if (diffHours < 24) return `${diffHours}h ago`;
45
- if (diffDays < 7) return `${diffDays}d ago`;
46
- return date.toLocaleDateString();
47
- }
48
-
49
- export default function AnalysesList() {
50
- const t = useT();
51
- const analysesSync = useChangeVersions(["analyses", "action"]);
52
- const { data: analyses, isLoading } = useQuery({
53
- queryKey: ["analyses-list", analysesSync],
54
- queryFn: fetchAnalyses,
55
- staleTime: 10_000,
56
- placeholderData: (prev) => prev,
57
- });
58
-
59
- const { send, codeRequiredDialog } = useSendToAgentChat();
60
- const [searchQuery, setSearchQuery] = useState("");
61
-
62
- const filteredAnalyses = analyses?.filter((a) => {
63
- if (!searchQuery.trim()) return true;
64
- const q = searchQuery.toLowerCase();
65
- return (
66
- a.name.toLowerCase().includes(q) ||
67
- (a.description && a.description.toLowerCase().includes(q))
68
- );
69
- });
70
-
71
- return (
72
- <>
73
- {codeRequiredDialog}
74
- <div className="space-y-6">
75
- <div className="flex items-center gap-3">
76
- <p className="text-sm text-muted-foreground flex-1">
77
- {t("analyses.description")}
78
- </p>
79
- {analyses && analyses.length > 0 && (
80
- <div className="relative">
81
- <IconSearch className="absolute start-2.5 top-1/2 h-3.5 w-3.5 -translate-y-1/2 text-muted-foreground/60 pointer-events-none" />
82
- <input
83
- type="search"
84
- placeholder={t("analyses.searchPlaceholder")}
85
- value={searchQuery}
86
- onChange={(e) => setSearchQuery(e.target.value)}
87
- className="h-8 rounded-md border border-input bg-background ps-8 pe-3 text-xs placeholder:text-muted-foreground/60 focus:outline-none focus:ring-2 focus:ring-ring"
88
- />
89
- </div>
90
- )}
91
- </div>
92
-
93
- {isLoading ? (
94
- <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
95
- {Array.from({ length: 3 }).map((_, i) => (
96
- <Skeleton key={i} className="h-40 w-full rounded-xl" />
97
- ))}
98
- </div>
99
- ) : !analyses?.length || filteredAnalyses?.length === 0 ? (
100
- <Card className="border-dashed">
101
- <CardContent className="flex flex-col items-center justify-center py-16 text-center">
102
- <div className="flex h-14 w-14 items-center justify-center rounded-2xl bg-primary/10 mb-4">
103
- <IconFlask className="h-7 w-7 text-primary" />
104
- </div>
105
- <h3 className="text-lg font-semibold mb-2">
106
- {searchQuery
107
- ? t("analyses.noResults")
108
- : t("analyses.noAnalysesYet")}
109
- </h3>
110
- <p className="text-sm text-muted-foreground max-w-sm mb-4">
111
- {searchQuery
112
- ? t("analyses.noResultsForQuery", { query: searchQuery })
113
- : t("analyses.emptyDescription")}
114
- </p>
115
- {!searchQuery && (
116
- <button
117
- onClick={() =>
118
- send({
119
- message: t("analyses.examplePrompt"),
120
- submit: false,
121
- })
122
- }
123
- className="text-sm text-primary hover:underline"
124
- >
125
- {t("analyses.tryExamplePrompt")}
126
- </button>
127
- )}
128
- </CardContent>
129
- </Card>
130
- ) : (
131
- <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
132
- {(filteredAnalyses ?? analyses ?? []).map((a) => (
133
- <Link key={a.id} to={`/analyses/${a.id}`} className="block">
134
- <Card className="h-full hover:border-primary/40 transition-colors cursor-pointer">
135
- <CardHeader className="pb-3">
136
- <CardTitle className="text-base leading-snug">
137
- {a.name}
138
- </CardTitle>
139
- {a.description && (
140
- <CardDescription className="line-clamp-2 text-xs">
141
- {a.description}
142
- </CardDescription>
143
- )}
144
- </CardHeader>
145
- <CardContent className="pt-0">
146
- <div className="flex flex-wrap gap-1.5 mb-3">
147
- {a.dataSources?.map((ds) => (
148
- <Badge
149
- key={ds}
150
- variant="secondary"
151
- className="text-[10px] px-1.5 py-0"
152
- >
153
- {ds}
154
- </Badge>
155
- ))}
156
- </div>
157
- <div className="flex items-center gap-3 text-[11px] text-muted-foreground">
158
- <span className="flex items-center gap-1">
159
- <IconClock className="h-3 w-3" />
160
- {formatRelativeDate(a.updatedAt)}
161
- </span>
162
- {a.author && (
163
- <span className="truncate">by {a.author}</span>
164
- )}
165
- </div>
166
- </CardContent>
167
- </Card>
168
- </Link>
169
- ))}
170
- </div>
171
- )}
172
- </div>
173
- </>
174
- );
175
- }