@agent-native/core 0.145.7 → 0.146.0

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 (186) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/analytics/.agents/skills/frontend-design/SKILL.md +5 -1
  3. package/corpus/templates/analytics/.agents/skills/turn-into-app/SKILL.md +37 -11
  4. package/corpus/templates/analytics/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
  5. package/corpus/templates/analytics/actions/get-sql-dashboard.ts +20 -4
  6. package/corpus/templates/analytics/actions/migrate-first-party-analytics-to-bigquery.ts +59 -24
  7. package/corpus/templates/analytics/app/pages/settings/AlertRulesSettingsCard.tsx +3 -2
  8. package/corpus/templates/analytics/changelog/2026-08-07-analytics-migrations-now-use-bounded-pressure-aware-backgrou.md +6 -0
  9. package/corpus/templates/analytics/changelog/2026-08-07-settings-skeleton-placeholders.md +6 -0
  10. package/corpus/templates/analytics/changelog/2026-08-07-template-activity-charts-now-show-only-core-agent-native-app.md +6 -0
  11. package/corpus/templates/analytics/netlify.toml +1 -1
  12. package/corpus/templates/analytics/package.json +1 -0
  13. package/corpus/templates/analytics/scripts/emit-netlify-dashboard-report-cron.ts +110 -0
  14. package/corpus/templates/analytics/scripts/migrate-production.ts +24 -0
  15. package/corpus/templates/analytics/seeds/dashboards/agent-native-templates-first-party.json +13 -13
  16. package/corpus/templates/analytics/server/jobs/analytics-bigquery-backfill.ts +448 -0
  17. package/corpus/templates/analytics/server/jobs/analytics-rollup-backfill.ts +11 -2
  18. package/corpus/templates/analytics/server/lib/analytics-rollup-lock.ts +4 -5
  19. package/corpus/templates/analytics/server/lib/canonical-first-party-dashboard-repair.ts +35 -0
  20. package/corpus/templates/analytics/server/lib/dashboard-catalog.ts +3 -5
  21. package/corpus/templates/analytics/server/lib/first-party-analytics-backend.ts +10 -4
  22. package/corpus/templates/analytics/server/lib/first-party-analytics-rollups.ts +4 -11
  23. package/corpus/templates/analytics/server/lib/first-party-metric-catalog.ts +74 -28
  24. package/corpus/templates/analytics/server/plugins/analytics-rollup-backfill-jobs.ts +2 -5
  25. package/corpus/templates/analytics/server/plugins/db.ts +56 -3
  26. package/corpus/templates/analytics/server/routes/api/analytics-bigquery/backfill.post.ts +55 -0
  27. package/corpus/templates/analytics/server/routes/api/dashboard-agent-context.json.get.ts +22 -4
  28. package/corpus/templates/assets/.agents/skills/frontend-design/SKILL.md +5 -1
  29. package/corpus/templates/assets/.agents/skills/turn-into-app/SKILL.md +37 -11
  30. package/corpus/templates/assets/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
  31. package/corpus/templates/brain/.agents/skills/frontend-design/SKILL.md +5 -1
  32. package/corpus/templates/brain/.agents/skills/turn-into-app/SKILL.md +37 -11
  33. package/corpus/templates/brain/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
  34. package/corpus/templates/brain/server/lib/connectors.ts +38 -14
  35. package/corpus/templates/calendar/.agents/skills/frontend-design/SKILL.md +5 -1
  36. package/corpus/templates/calendar/.agents/skills/turn-into-app/SKILL.md +37 -11
  37. package/corpus/templates/calendar/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
  38. package/corpus/templates/chat/.agents/skills/frontend-design/SKILL.md +5 -1
  39. package/corpus/templates/chat/.agents/skills/turn-into-app/SKILL.md +37 -11
  40. package/corpus/templates/chat/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
  41. package/corpus/templates/chat/AGENTS.md +3 -0
  42. package/corpus/templates/clips/.agents/skills/frontend-design/SKILL.md +5 -1
  43. package/corpus/templates/clips/.agents/skills/turn-into-app/SKILL.md +37 -11
  44. package/corpus/templates/clips/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
  45. package/corpus/templates/content/.agents/skills/frontend-design/SKILL.md +5 -1
  46. package/corpus/templates/content/.agents/skills/turn-into-app/SKILL.md +37 -11
  47. package/corpus/templates/content/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
  48. package/corpus/templates/crm/.agents/skills/turn-into-app/SKILL.md +37 -11
  49. package/corpus/templates/crm/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
  50. package/corpus/templates/crm/app/components/crm/IntelligenceSettings.tsx +21 -3
  51. package/corpus/templates/crm/app/components/crm/settings/ConnectionSettings.tsx +23 -5
  52. package/corpus/templates/crm/app/components/crm/settings/FieldsSettings.tsx +24 -5
  53. package/corpus/templates/crm/app/components/crm/settings/ListsSettings.tsx +23 -3
  54. package/corpus/templates/crm/changelog/2026-08-07-settings-loading-skeletons.md +6 -0
  55. package/corpus/templates/design/.agents/skills/frontend-design/SKILL.md +5 -1
  56. package/corpus/templates/design/.agents/skills/turn-into-app/SKILL.md +37 -11
  57. package/corpus/templates/design/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
  58. package/corpus/templates/dispatch/.agents/skills/frontend-design/SKILL.md +5 -1
  59. package/corpus/templates/dispatch/.agents/skills/turn-into-app/SKILL.md +37 -11
  60. package/corpus/templates/dispatch/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
  61. package/corpus/templates/factory/.agents/skills/frontend-design/SKILL.md +5 -1
  62. package/corpus/templates/factory/.agents/skills/turn-into-app/SKILL.md +37 -11
  63. package/corpus/templates/factory/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
  64. package/corpus/templates/forms/.agents/skills/frontend-design/SKILL.md +5 -1
  65. package/corpus/templates/forms/.agents/skills/turn-into-app/SKILL.md +37 -11
  66. package/corpus/templates/forms/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
  67. package/corpus/templates/macros/.agents/skills/frontend-design/SKILL.md +5 -1
  68. package/corpus/templates/macros/.agents/skills/turn-into-app/SKILL.md +37 -11
  69. package/corpus/templates/macros/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
  70. package/corpus/templates/mail/.agents/skills/frontend-design/SKILL.md +5 -1
  71. package/corpus/templates/mail/.agents/skills/turn-into-app/SKILL.md +37 -11
  72. package/corpus/templates/mail/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
  73. package/corpus/templates/plan/.agents/skills/frontend-design/SKILL.md +5 -1
  74. package/corpus/templates/plan/.agents/skills/turn-into-app/SKILL.md +37 -11
  75. package/corpus/templates/plan/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
  76. package/corpus/templates/slides/.agents/skills/frontend-design/SKILL.md +5 -1
  77. package/corpus/templates/slides/.agents/skills/turn-into-app/SKILL.md +37 -11
  78. package/corpus/templates/slides/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
  79. package/corpus/templates/slides/app/components/editor/EditorSidebar.tsx +13 -0
  80. package/corpus/templates/slides/app/components/editor/GeneratingSlidePreview.tsx +5 -0
  81. package/corpus/templates/slides/app/components/presentation/PresentationView.tsx +5 -0
  82. package/corpus/templates/slides/app/components/presentation/PresenterView.tsx +5 -0
  83. package/corpus/templates/slides/app/pages/DeckEditor.tsx +3 -0
  84. package/corpus/templates/slides/app/pages/Presentation.tsx +3 -0
  85. package/corpus/templates/slides/app/pages/SharedPresentation.tsx +5 -0
  86. package/corpus/templates/slides/app/routes/slide.tsx +10 -5
  87. package/corpus/templates/slides/changelog/2026-08-07-slide-previews-and-present-now-keep-deck-colors-consistent.md +6 -0
  88. package/corpus/templates/slides/server/db/schema.ts +1 -0
  89. package/corpus/templates/slides/server/handlers/share.ts +51 -3
  90. package/corpus/templates/slides/server/plugins/db.ts +5 -0
  91. package/corpus/templates/slides/shared/api.ts +2 -0
  92. package/corpus/templates/tasks/.agents/skills/frontend-design/SKILL.md +5 -1
  93. package/corpus/templates/tasks/.agents/skills/turn-into-app/SKILL.md +37 -11
  94. package/corpus/templates/tasks/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
  95. package/dist/cli/code-agent-executor.js +8 -3
  96. package/dist/client/ConnectBuilderCard.d.ts +7 -7
  97. package/dist/client/ConnectBuilderCard.js +45 -20
  98. package/dist/client/components/ApiKeySettings.js +2 -5
  99. package/dist/client/org/TeamPage.js +4 -2
  100. package/dist/client/settings/AccountSettingsCard.js +2 -1
  101. package/dist/client/settings/AgentsSection.js +2 -1
  102. package/dist/client/settings/SecretsSection.js +3 -2
  103. package/dist/client/settings/SettingsPanel.js +6 -12
  104. package/dist/client/settings/SettingsSkeleton.d.ts +12 -0
  105. package/dist/client/settings/SettingsSkeleton.js +12 -0
  106. package/dist/client/settings/VoiceTranscriptionSection.js +5 -4
  107. package/dist/client/settings/index.d.ts +1 -0
  108. package/dist/client/settings/index.js +1 -0
  109. package/dist/config.d.ts +9 -2
  110. package/dist/config.js +38 -2
  111. package/dist/db/client.d.ts +26 -8
  112. package/dist/db/client.js +200 -9
  113. package/dist/db/create-get-db.js +2 -2
  114. package/dist/db/ddl-guard.js +3 -1
  115. package/dist/db/ensure-additive-columns.d.ts +2 -0
  116. package/dist/db/ensure-additive-columns.js +5 -2
  117. package/dist/db/index.d.ts +2 -2
  118. package/dist/db/index.js +2 -2
  119. package/dist/db/migrations.d.ts +11 -54
  120. package/dist/db/migrations.js +70 -0
  121. package/dist/deploy/build.js +5 -1
  122. package/dist/jobs/index.d.ts +1 -0
  123. package/dist/jobs/index.js +1 -0
  124. package/dist/jobs/run-history.d.ts +4 -0
  125. package/dist/jobs/run-history.js +44 -0
  126. package/dist/provider-api/actions/provider-api.d.ts +4 -4
  127. package/dist/scripts/docs/search.js +3 -2
  128. package/dist/secrets/routes.d.ts +6 -6
  129. package/dist/server/agent-chat/prompt-resources.js +4 -3
  130. package/dist/server/agent-chat-plugin.js +6 -4
  131. package/dist/server/agents-bundle.d.ts +20 -3
  132. package/dist/server/agents-bundle.js +57 -30
  133. package/dist/server/auth-plugin.js +5 -0
  134. package/dist/server/better-auth-instance.js +2 -63
  135. package/dist/server/better-auth-migrations.d.ts +8 -0
  136. package/dist/server/better-auth-migrations.js +179 -0
  137. package/dist/server/edge.d.ts +2 -0
  138. package/dist/server/edge.js +2 -0
  139. package/dist/server/index.d.ts +2 -0
  140. package/dist/server/index.js +2 -0
  141. package/dist/server/release-migrations.d.ts +8 -0
  142. package/dist/server/release-migrations.js +24 -0
  143. package/dist/server/sweep-runtime.d.ts +1 -1
  144. package/dist/server/sweep-runtime.js +15 -0
  145. package/dist/shared/runtime-config.js +1 -1
  146. package/dist/templates/chat/.agents/skills/frontend-design/SKILL.md +5 -1
  147. package/dist/templates/chat/.agents/skills/turn-into-app/SKILL.md +37 -11
  148. package/dist/templates/chat/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
  149. package/dist/templates/chat/AGENTS.md +3 -0
  150. package/dist/templates/default/.agents/skills/frontend-design/SKILL.md +5 -1
  151. package/dist/templates/default/.agents/skills/turn-into-app/SKILL.md +37 -11
  152. package/dist/templates/default/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
  153. package/dist/templates/default/AGENTS.md +3 -0
  154. package/dist/templates/headless/.agents/skills/turn-into-app/SKILL.md +37 -11
  155. package/dist/templates/headless/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
  156. package/dist/templates/workspace-core/.agents/skills/frontend-design/SKILL.md +5 -1
  157. package/dist/templates/workspace-core/.agents/skills/turn-into-app/SKILL.md +37 -11
  158. package/dist/templates/workspace-core/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
  159. package/dist/templates/workspace-core/AGENTS.md +3 -0
  160. package/dist/templates/workspace-root/AGENTS.md +3 -0
  161. package/dist/vite/agent-native-config-loader.d.ts +10 -0
  162. package/dist/vite/agent-native-config-loader.js +56 -0
  163. package/dist/vite/agents-bundle-plugin.d.ts +5 -1
  164. package/dist/vite/agents-bundle-plugin.js +29 -6
  165. package/dist/vite/client.d.ts +4 -2
  166. package/dist/vite/client.js +4 -49
  167. package/dist/vite/index.d.ts +2 -2
  168. package/dist/vite/index.js +1 -1
  169. package/docs/content/agent-native-config.mdx +43 -17
  170. package/docs/content/environment-variables.mdx +1 -1
  171. package/package.json +3 -3
  172. package/src/templates/chat/.agents/skills/frontend-design/SKILL.md +5 -1
  173. package/src/templates/chat/.agents/skills/turn-into-app/SKILL.md +37 -11
  174. package/src/templates/chat/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
  175. package/src/templates/chat/AGENTS.md +3 -0
  176. package/src/templates/default/.agents/skills/frontend-design/SKILL.md +5 -1
  177. package/src/templates/default/.agents/skills/turn-into-app/SKILL.md +37 -11
  178. package/src/templates/default/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
  179. package/src/templates/default/AGENTS.md +3 -0
  180. package/src/templates/headless/.agents/skills/turn-into-app/SKILL.md +37 -11
  181. package/src/templates/headless/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
  182. package/src/templates/workspace-core/.agents/skills/frontend-design/SKILL.md +5 -1
  183. package/src/templates/workspace-core/.agents/skills/turn-into-app/SKILL.md +37 -11
  184. package/src/templates/workspace-core/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
  185. package/src/templates/workspace-core/AGENTS.md +3 -0
  186. package/src/templates/workspace-root/AGENTS.md +3 -0
package/corpus/README.md CHANGED
@@ -31,4 +31,4 @@ rg -n "defineAction|useActionQuery" node_modules/@agent-native/core/corpus
31
31
 
32
32
  ## Generated Counts
33
33
 
34
- - template files: 7888
34
+ - template files: 7896
@@ -199,7 +199,11 @@ as an operator would use it repeatedly:
199
199
  - Use full `Card` composition when the content has a title, description, content, or actions. Do not dump complex cards into a single `CardContent`.
200
200
  - Use `ToggleGroup` for small option sets, `Switch` for binary settings, `Checkbox` for multi-select, `RadioGroup` for one-of-many, and `Slider`/inputs for numeric values.
201
201
  - For forms, prefer the app's existing shadcn form pattern. If newer `Field`, `FieldGroup`, or `InputGroup` primitives are installed or appropriate to add, use them instead of raw layout divs.
202
- - Loading states use `Skeleton`, `Progress`, `Spinner`, or the app's existing loading primitives. Empty states should have one clear next action.
202
+ - Page and section data loading uses layout-matching `Skeleton` geometry. Do
203
+ not show generic "Loading..." text for content loads; reserve `Spinner` for
204
+ brief mutations, uploads, and progress actions. Use the app's existing
205
+ loading primitive when it is a genuine design-system adapter. Empty states
206
+ should have one clear next action.
203
207
 
204
208
  ## Anti-Patterns
205
209
 
@@ -4,7 +4,7 @@ description: >-
4
4
  Turn visible project context, a proven thread, skill, or workflow into a
5
5
  runnable Agent-Native app with simple buttons, visible agent steps, preview,
6
6
  and deployment handoff. Use when a user invokes `/turn-into-app` or asks to
7
- make a workflow into an app.
7
+ make a workflow into an app, including from Claude or ChatGPT on the web.
8
8
  user-invocable: true
9
9
  scope: both
10
10
  metadata:
@@ -13,9 +13,32 @@ metadata:
13
13
 
14
14
  # Turn Into App
15
15
 
16
+ ## Host execution boundary
17
+
18
+ The build location depends on the host. Treat this as a hard routing rule:
19
+
20
+ - In Claude Web, ChatGPT Web, or a Claude/ChatGPT Project on the web, you are
21
+ the source analyst and handoff orchestrator. You must not build the app in
22
+ the host sandbox. Do not run `npm`, `pnpm`, `npx`, `agent-native create`, or
23
+ `add-app`; do not edit files, create artifacts, or start a local dev server.
24
+ After writing the bounded source brief, call the connected Dispatch action
25
+ `start-workspace-app-creation`. Pass the brief and repeatable workflow in
26
+ `prompt`, plus the inferred `appId`, `description`, `template`, and selected
27
+ `resourceIds` when available. This is the Builder handoff.
28
+ - Do not substitute the generic `create_workspace_app` MCP tool in an online
29
+ host. That tool is a local workspace scaffolder, not the Builder handoff.
30
+ - If `start-workspace-app-creation` is not available or Dispatch is not
31
+ authenticated, stop with the connector setup needed. Do not fall back to a
32
+ local build or claim that the app exists.
33
+ - In Claude Code, Codex Code, or another local code-agent runtime with a target
34
+ workspace, follow the local implementation steps below. The local agent may
35
+ scaffold, edit, run, and verify the app there.
36
+
16
37
  ## Default behavior
17
38
 
18
- This is an end-to-end build skill, not a request for an app proposal.
39
+ For a local code-agent runtime this is an end-to-end build skill, not a request
40
+ for an app proposal. For an online host, the end-to-end result is a verified
41
+ Builder handoff and the resulting workspace app, not code written in the host.
19
42
 
20
43
  - With no argument, choose the source in this order: visible project context,
21
44
  then the current thread. A fresh Claude or ChatGPT Project is a valid source
@@ -47,9 +70,12 @@ private web access, invent an importer, add fake OAuth, or scrape a logged-in
47
70
  page. If the needed context is not visible, ask for an export, transcript, or
48
71
  attachment and treat that artifact as imported source material.
49
72
 
50
- Deliver a fresh app in a new directory, implement the repeatable workflow with
51
- buttons and agent handoffs, start its dev server, verify the main path, and
52
- continue through build/deployment handoff. Do not stop at a plan.
73
+ For local code-agent runtimes, deliver a fresh app in a new directory,
74
+ implement the repeatable workflow with buttons and agent handoffs, start its dev
75
+ server, verify the main path, and continue through build/deployment handoff. Do
76
+ not stop at a plan. For online hosts, call Dispatch first after the source brief,
77
+ then report the returned Builder branch/path and verify the workspace app through
78
+ Dispatch when it becomes available.
53
79
 
54
80
  ## Fresh project context mode
55
81
 
@@ -190,12 +216,12 @@ Do not use `create` for an existing workspace; it scaffolds a new standalone
190
216
  workspace rather than adding an app to the current one.
191
217
 
192
218
  When the source came from a fresh external Project and the target is a Builder
193
- workspace, prefer the available Dispatch MCP workspace handoff after writing
194
- the source brief. Call `start-workspace-app-creation` with a concise prompt,
195
- the inferred app id, and the brief's repeatable workflow. Pass selected
196
- workspace resource IDs when they are available, rather than pasting entire
197
- knowledge files. Continue from the returned workspace path or Builder handoff
198
- and report the actual verification result.
219
+ workspace, the online host path is mandatory: after writing the source brief,
220
+ call `start-workspace-app-creation` with a concise prompt, the inferred app id,
221
+ and the brief's repeatable workflow. Pass selected workspace resource IDs when
222
+ they are available, rather than pasting entire knowledge files. Continue from
223
+ the returned workspace path or Builder branch handoff and report the actual
224
+ verification result. Do not run the local scaffold commands in this host mode.
199
225
 
200
226
  Use a first-party template only when it materially fits the workflow. Keep the
201
227
  new app independent from the source thread's working tree unless the user
@@ -51,13 +51,24 @@ also place the skill instructions or this reference in the Project
51
51
  instructions/knowledge files. Then add and authenticate the Dispatch MCP
52
52
  connector from the packaged `adapters/chatgpt-mcp/connector.json`.
53
53
 
54
+ For Claude Web, ChatGPT Web, and their web Projects, the host is an
55
+ orchestrator, not the build environment. After forming the source brief, call
56
+ the Dispatch `start-workspace-app-creation` action so Builder creates the app
57
+ in the connected workspace. Do not use the host's code interpreter, shell,
58
+ artifact editor, `create_workspace_app`, or local `pnpm`/`npm` commands. If the
59
+ handoff action is unavailable, stop and ask the user to authenticate Dispatch;
60
+ do not fall back to a local sandbox build. Claude Code and Codex Code are the
61
+ local-agent exception and may follow the implementation steps in the main
62
+ skill.
63
+
54
64
  In a new Project chat, say:
55
65
 
56
66
  ```text
57
67
  Turn this project into an app. Use the visible Project instructions,
58
68
  knowledge files, and any selected successful runs as the source. Create the
59
- app in the connected Agent-Native workspace, keep the source brief bounded,
60
- and report the real path and verification result.
69
+ app in the connected Agent-Native workspace through Dispatch and Builder, keep
70
+ the source brief bounded, and report the real Builder branch/path and
71
+ verification result. Do not build it in this chat's sandbox.
61
72
  ```
62
73
 
63
74
  If the host does not show the Project instructions or files to the model, stop
@@ -10,8 +10,10 @@ import {
10
10
  buildDashboardAgentContext,
11
11
  buildDashboardSeedAgentContext,
12
12
  } from "../server/lib/agent-readable-resource-context";
13
+ import { repairCanonicalFirstPartyDashboardQueries } from "../server/lib/canonical-first-party-dashboard-repair";
13
14
  import { loadDashboardSeed } from "../server/lib/dashboard-seeds";
14
15
  import { getDashboard } from "../server/lib/dashboards-store";
16
+ import { FIRST_PARTY_DASHBOARD_ID } from "../server/lib/first-party-metric-catalog";
15
17
 
16
18
  export default defineAction({
17
19
  description:
@@ -64,14 +66,28 @@ export default defineAction({
64
66
  if (!dash || dash.kind !== "sql") {
65
67
  const seed = loadDashboardSeed(args.id);
66
68
  if (seed)
67
- return buildDashboardSeedAgentContext(args.id, seed, {
68
- includeConfig: args.includeConfig === true,
69
- });
69
+ return buildDashboardSeedAgentContext(
70
+ args.id,
71
+ args.id === FIRST_PARTY_DASHBOARD_ID
72
+ ? repairCanonicalFirstPartyDashboardQueries(seed).config
73
+ : seed,
74
+ {
75
+ includeConfig: args.includeConfig === true,
76
+ },
77
+ );
70
78
  throw Object.assign(new Error("Dashboard not found"), {
71
79
  statusCode: 404,
72
80
  });
73
81
  }
74
- return buildDashboardAgentContext(dash, {
82
+ const dashboard =
83
+ args.id === FIRST_PARTY_DASHBOARD_ID
84
+ ? {
85
+ ...dash,
86
+ config: repairCanonicalFirstPartyDashboardQueries(dash.config)
87
+ .config,
88
+ }
89
+ : dash;
90
+ return buildDashboardAgentContext(dashboard, {
75
91
  includeConfig: args.includeConfig === true,
76
92
  });
77
93
  },
@@ -5,10 +5,13 @@ import {
5
5
  } from "@agent-native/core/server";
6
6
  import { z } from "zod";
7
7
 
8
+ import {
9
+ getFirstPartyAnalyticsBigQueryBackfillJob,
10
+ queueFirstPartyAnalyticsBigQueryBackfill,
11
+ } from "../server/jobs/analytics-bigquery-backfill.js";
8
12
  import { requireAnalyticsAdminContext } from "../server/lib/db-admin-connections.js";
9
13
  import {
10
14
  assertFirstPartyAnalyticsBigQueryReady,
11
- backfillFirstPartyAnalyticsBatch,
12
15
  getFirstPartyAnalyticsBackend,
13
16
  saveFirstPartyAnalyticsBackend,
14
17
  } from "../server/lib/first-party-analytics-backend.js";
@@ -35,18 +38,23 @@ const migrationSchema = z.object({
35
38
  .describe(
36
39
  "Optional BigQuery table reference as dataset.table or project.dataset.table. The default is <BIGQUERY_PROJECT_ID>.analytics.first_party_analytics_events_raw.",
37
40
  ),
38
- cursor: z.string().trim().optional(),
39
- limit: z.number().int().min(1).max(5_000).optional(),
41
+ limit: z
42
+ .number()
43
+ .int()
44
+ .min(1)
45
+ .max(750)
46
+ .optional()
47
+ .describe("Optional per-page limit used when mode is prepare."),
40
48
  confirm: z.boolean().optional(),
41
49
  });
42
50
 
43
51
  export default defineAction({
44
52
  description:
45
- "UI-only production migration for the current Analytics organization. Run prepare to validate the configured BigQuery table and enter dual-write mode, call backfill repeatedly with the returned cursor until complete, then call cutover with confirm=true. New /track events stay in Postgres during dual-write, so a BigQuery outage does not silently lose live data. Cutover is the only step that stops analytics event and rollup writes to Postgres; public-key metadata, derived exception issues, and session-replay data remain in the SQL store.",
53
+ "Production migration for the current Analytics organization. Run prepare to validate the configured BigQuery table, enter dual-write mode, and enqueue a durable single-flight worker. The worker uses bounded pages, a persisted cursor, a lease, and a database-pressure gate; it pauses instead of competing with live traffic. Call status or backfill to inspect progress, then call cutover with confirm=true only after the worker reports completed. New /track events stay in Postgres during dual-write, so a BigQuery outage does not silently lose live data. Cutover is the only step that stops first-party event and rollup writes to Postgres; public-key metadata, derived exception issues, and session-replay data remain in the SQL store.",
46
54
  schema: migrationSchema,
47
55
  agentTool: false,
48
56
  needsApproval: ({ mode }) => mode === "cutover",
49
- run: async ({ mode, table, cursor, limit, confirm }) => {
57
+ run: async ({ mode, table, limit, confirm }) => {
50
58
  const scope = await resolveScope();
51
59
  const current = await getFirstPartyAnalyticsBackend(scope);
52
60
  const configuredTable = table ?? current.table;
@@ -56,27 +64,58 @@ export default defineAction({
56
64
  current.sink === "postgres"
57
65
  ? null
58
66
  : await assertFirstPartyAnalyticsBigQueryReady(current.table);
67
+ const job =
68
+ current.sink === "postgres"
69
+ ? null
70
+ : await getFirstPartyAnalyticsBigQueryBackfillJob(scope);
59
71
  return {
60
72
  ...current,
73
+ backfillCursor:
74
+ current.sink === "dual"
75
+ ? (job?.cursor ?? null)
76
+ : current.backfillCursor,
77
+ backfillCompleted:
78
+ current.sink === "dual"
79
+ ? job?.status === "completed"
80
+ : current.backfillCompleted,
61
81
  ready: Boolean(ready),
62
82
  rowCount: ready?.rowCount ?? null,
63
83
  table: ready?.table.fullyQualified ?? current.table,
84
+ job,
64
85
  };
65
86
  }
66
87
 
67
88
  if (mode === "prepare") {
89
+ if (current.sink === "bigquery") {
90
+ throw new Error(
91
+ "This organization has already cut over to BigQuery; preparing it again is not supported.",
92
+ );
93
+ }
68
94
  const ready =
69
95
  await assertFirstPartyAnalyticsBigQueryReady(configuredTable);
96
+ const existingJob =
97
+ await getFirstPartyAnalyticsBigQueryBackfillJob(scope);
98
+ if (existingJob && existingJob.table !== ready.table.fullyQualified) {
99
+ throw new Error(
100
+ `BigQuery backfill already targets ${existingJob.table}; prepare the existing migration before changing tables.`,
101
+ );
102
+ }
70
103
  await saveFirstPartyAnalyticsBackend(scope, {
71
104
  sink: "dual",
72
105
  table: ready.table.fullyQualified,
73
- backfillCursor: null,
74
- backfillCompleted: false,
106
+ backfillCursor: existingJob?.cursor ?? null,
107
+ backfillCompleted: existingJob?.status === "completed",
75
108
  });
109
+ const job = await queueFirstPartyAnalyticsBigQueryBackfill(
110
+ scope,
111
+ ready.table.fullyQualified,
112
+ limit,
113
+ );
76
114
  return {
77
115
  sink: "dual" as const,
78
116
  table: ready.table.fullyQualified,
79
117
  existingBigQueryRows: ready.rowCount,
118
+ job,
80
119
  next: "backfill",
81
120
  };
82
121
  }
@@ -87,23 +126,18 @@ export default defineAction({
87
126
  "Prepare the organization for dual-write before starting the BigQuery backfill.",
88
127
  );
89
128
  }
90
- const result = await backfillFirstPartyAnalyticsBatch(
91
- scope,
92
- cursor ?? current.backfillCursor ?? null,
93
- limit ?? 500,
94
- current.table,
95
- );
96
- await saveFirstPartyAnalyticsBackend(scope, {
97
- sink: "dual",
98
- table: current.table,
99
- backfillCursor: result.nextCursor,
100
- backfillCompleted: result.complete,
101
- });
129
+ const job = await getFirstPartyAnalyticsBigQueryBackfillJob(scope);
130
+ if (!job) {
131
+ throw new Error(
132
+ "Prepare the organization before enqueueing the BigQuery backfill.",
133
+ );
134
+ }
102
135
  return {
103
- ...result,
104
136
  sink: "dual" as const,
105
137
  table: current.table,
106
- next: result.complete ? "cutover" : "backfill",
138
+ job,
139
+ next: job.status === "completed" ? "cutover" : "backfill",
140
+ queued: job.status !== "completed",
107
141
  };
108
142
  }
109
143
 
@@ -112,16 +146,17 @@ export default defineAction({
112
146
  "Cutover requires confirm=true because it stops first-party event and rollup writes to Postgres for this organization.",
113
147
  );
114
148
  }
115
- if (current.sink !== "dual" || current.backfillCompleted !== true) {
149
+ const job = await getFirstPartyAnalyticsBigQueryBackfillJob(scope);
150
+ if (current.sink !== "dual" || job?.status !== "completed") {
116
151
  throw new Error(
117
- "Complete the dual-write backfill before cutting over to BigQuery.",
152
+ "Wait for the durable dual-write backfill job to report completed before cutting over to BigQuery.",
118
153
  );
119
154
  }
120
155
  const ready = await assertFirstPartyAnalyticsBigQueryReady(current.table);
121
156
  await saveFirstPartyAnalyticsBackend(scope, {
122
157
  sink: "bigquery",
123
158
  table: ready.table.fullyQualified,
124
- backfillCursor: current.backfillCursor,
159
+ backfillCursor: job.cursor,
125
160
  backfillCompleted: true,
126
161
  });
127
162
  return {
@@ -58,6 +58,7 @@ import {
58
58
  SelectTrigger,
59
59
  SelectValue,
60
60
  } from "@/components/ui/select";
61
+ import { Skeleton } from "@/components/ui/skeleton";
61
62
  import { Switch } from "@/components/ui/switch";
62
63
  import { Textarea } from "@/components/ui/textarea";
63
64
  import { cn } from "@/lib/utils";
@@ -503,9 +504,9 @@ export function AlertRulesSettingsCard() {
503
504
  {isLoading ? (
504
505
  <div className="space-y-2">
505
506
  {[0, 1, 2].map((item) => (
506
- <div
507
+ <Skeleton
507
508
  key={item}
508
- className="h-14 rounded-md border border-border bg-muted/30"
509
+ className="h-14 w-full rounded-md border border-border bg-muted/30"
509
510
  />
510
511
  ))}
511
512
  </div>
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-08-07
4
+ ---
5
+
6
+ Analytics migrations now use bounded, pressure-aware background jobs
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: improved
3
+ date: 2026-08-07
4
+ ---
5
+
6
+ Settings show stable skeleton placeholders while alert rules load.
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-08-07
4
+ ---
5
+
6
+ Template activity charts now show only core Agent Native apps
@@ -4,7 +4,7 @@ ignore = "node ./scripts/netlify-ignore-build.mjs analytics"
4
4
  # core explicitly here so a cached Netlify install cannot bundle an older agent
5
5
  # loop into Analytics' server functions (including A2A/MCP final-response
6
6
  # guards).
7
- command = "export DATABASE_URL=${NETLIFY_DATABASE_URL:-$DATABASE_URL} && pnpm install && pnpm --filter @agent-native/core build && NITRO_PRESET=netlify pnpm --filter analytics build"
7
+ command = "export DATABASE_URL=${NETLIFY_DATABASE_URL:-$DATABASE_URL} && pnpm install && pnpm --filter @agent-native/core build && NITRO_PRESET=netlify pnpm --filter analytics build && if [ \"${CONTEXT:-}\" = \"production\" ]; then pnpm --filter analytics migrate:production; fi"
8
8
  publish = "templates/analytics/dist"
9
9
  functions = "templates/analytics/.netlify/functions-internal"
10
10
 
@@ -10,6 +10,7 @@
10
10
  "test": "vitest --run",
11
11
  "format.fix": "oxfmt --write .",
12
12
  "typecheck": "agent-native typecheck",
13
+ "migrate:production": "tsx scripts/migrate-production.ts",
13
14
  "action": "agent-native action",
14
15
  "script": "agent-native script"
15
16
  },
@@ -26,6 +26,10 @@ const ROLLUP_SCHEDULED_NAME = "analytics-rollup-backfill-cron";
26
26
  const ROLLUP_WORKER_NAME = "analytics-rollup-backfill-background";
27
27
  const ROLLUP_ROUTE_PATH = "/api/analytics-rollups/backfill";
28
28
  const ROLLUP_SCHEDULE = "*/15 * * * *";
29
+ const BIGQUERY_SCHEDULED_NAME = "analytics-bigquery-backfill-cron";
30
+ const BIGQUERY_WORKER_NAME = "analytics-bigquery-backfill-background";
31
+ const BIGQUERY_ROUTE_PATH = "/api/analytics-bigquery/backfill";
32
+ const BIGQUERY_SCHEDULE = "* * * * *";
29
33
  const RETENTION_SCHEDULED_NAME = "session-replay-retention-cron";
30
34
  const RETENTION_WORKER_NAME = "session-replay-retention-sweep-background";
31
35
  const RETENTION_ROUTE_PATH = "/api/session-replay/retention";
@@ -499,6 +503,106 @@ export const config = {
499
503
  writeFileSync(path.join(dest, `${ROLLUP_WORKER_NAME}.mjs`), source);
500
504
  }
501
505
 
506
+ function emitBigQueryScheduledTrigger(token: string) {
507
+ const dest = path.join(FUNCTIONS_DIR, BIGQUERY_SCHEDULED_NAME);
508
+ rmSync(dest, { recursive: true, force: true });
509
+ ensureDir(dest);
510
+
511
+ const source = `const WORKER_PATH = "/.netlify/functions/${BIGQUERY_WORKER_NAME}";
512
+ const CRON_TOKEN = ${JSON.stringify(token)};
513
+
514
+ function siteOrigin(request) {
515
+ const configured = process.env.URL || process.env.DEPLOY_URL;
516
+ if (configured) return configured;
517
+ const url = new URL(request.url);
518
+ return url.origin;
519
+ }
520
+
521
+ export default async function handler(request) {
522
+ const url = new URL(WORKER_PATH, siteOrigin(request));
523
+ const response = await fetch(url.toString(), {
524
+ method: "POST",
525
+ headers: {
526
+ "content-type": "application/json",
527
+ "x-agent-native-analytics-bigquery-cron": CRON_TOKEN,
528
+ },
529
+ body: JSON.stringify({ scheduled: true }),
530
+ });
531
+
532
+ if (!response.ok && response.status !== 202) {
533
+ console.error(
534
+ "[analytics-bigquery-backfill-cron] Background backfill trigger failed:",
535
+ response.status,
536
+ );
537
+ }
538
+ }
539
+
540
+ export const config = {
541
+ name: "analytics BigQuery backfill cron trigger",
542
+ generator: "agent-native analytics build",
543
+ schedule: ${JSON.stringify(BIGQUERY_SCHEDULE)},
544
+ };
545
+ `;
546
+
547
+ writeFileSync(path.join(dest, `${BIGQUERY_SCHEDULED_NAME}.mjs`), source);
548
+ }
549
+
550
+ function emitBigQueryBackgroundWorker(token: string) {
551
+ const dest = path.join(FUNCTIONS_DIR, BIGQUERY_WORKER_NAME);
552
+ rmSync(dest, { recursive: true, force: true });
553
+ cloneServerBundleForFunction(SERVER_DIR, dest);
554
+ rmSync(path.join(dest, "server.mjs"), { force: true });
555
+
556
+ const source = `globalThis.__AGENT_NATIVE_BACKGROUND_RUNTIME__ = true;
557
+ globalThis.__AGENT_NATIVE_LOW_CONNECTION_BACKGROUND_RUNTIME__ = true;
558
+ globalThis.__AGENT_NATIVE_ANALYTICS_BIGQUERY_BACKFILL_SCHEDULED_RUNTIME__ = true;
559
+
560
+ const CRON_TOKEN = ${JSON.stringify(token)};
561
+ const ROUTE_PATH = ${JSON.stringify(BIGQUERY_ROUTE_PATH)};
562
+ let cachedHandler;
563
+
564
+ function timingSafeEquals(a, b) {
565
+ if (a.length !== b.length) return false;
566
+ let diff = 0;
567
+ for (let i = 0; i < a.length; i += 1) {
568
+ diff |= a.charCodeAt(i) ^ b.charCodeAt(i);
569
+ }
570
+ return diff === 0;
571
+ }
572
+
573
+ export default async function handler(request, context) {
574
+ const token = request.headers.get("x-agent-native-analytics-bigquery-cron") || "";
575
+ if (!timingSafeEquals(token, CRON_TOKEN)) {
576
+ return new Response("Unauthorized", { status: 401 });
577
+ }
578
+
579
+ cachedHandler ??= (await import("./main.mjs")).default;
580
+ const url = new URL(request.url);
581
+ url.pathname = ROUTE_PATH;
582
+ url.search = "";
583
+
584
+ const rewritten = new Request(url.toString(), {
585
+ method: "POST",
586
+ headers: new Headers({ "content-type": "application/json" }),
587
+ body: JSON.stringify({ scheduled: true }),
588
+ });
589
+
590
+ return await cachedHandler(rewritten, context);
591
+ }
592
+
593
+ export const config = {
594
+ name: "analytics BigQuery backfill background sweep",
595
+ generator: "agent-native analytics build",
596
+ background: true,
597
+ nodeBundler: "none",
598
+ includedFiles: ["**"],
599
+ preferStatic: false,
600
+ };
601
+ `;
602
+
603
+ writeFileSync(path.join(dest, `${BIGQUERY_WORKER_NAME}.mjs`), source);
604
+ }
605
+
502
606
  function emitRetentionScheduledTrigger(token: string) {
503
607
  const dest = path.join(FUNCTIONS_DIR, RETENTION_SCHEDULED_NAME);
504
608
  rmSync(dest, { recursive: true, force: true });
@@ -621,6 +725,7 @@ function main(): void {
621
725
  const alertToken = randomBytes(32).toString("hex");
622
726
  const uptimeToken = randomBytes(32).toString("hex");
623
727
  const rollupToken = randomBytes(32).toString("hex");
728
+ const bigQueryToken = randomBytes(32).toString("hex");
624
729
  const retentionToken = randomBytes(32).toString("hex");
625
730
  emitScheduledTrigger(token);
626
731
  emitBackgroundWorker(token);
@@ -630,6 +735,8 @@ function main(): void {
630
735
  emitUptimeBackgroundWorker(uptimeToken);
631
736
  emitRollupScheduledTrigger(rollupToken);
632
737
  emitRollupBackgroundWorker(rollupToken);
738
+ emitBigQueryScheduledTrigger(bigQueryToken);
739
+ emitBigQueryBackgroundWorker(bigQueryToken);
633
740
  emitRetentionScheduledTrigger(retentionToken);
634
741
  emitRetentionBackgroundWorker(retentionToken);
635
742
  console.log(
@@ -644,6 +751,9 @@ function main(): void {
644
751
  console.log(
645
752
  `[analytics-rollup-backfill-cron] Emitted Netlify scheduled trigger "${ROLLUP_SCHEDULED_NAME}" (${ROLLUP_SCHEDULE}) and background worker "${ROLLUP_WORKER_NAME}".`,
646
753
  );
754
+ console.log(
755
+ `[analytics-bigquery-backfill-cron] Emitted Netlify scheduled trigger "${BIGQUERY_SCHEDULED_NAME}" (${BIGQUERY_SCHEDULE}) and background worker "${BIGQUERY_WORKER_NAME}".`,
756
+ );
647
757
  console.log(
648
758
  `[session-replay-retention-cron] Emitted Netlify scheduled trigger "${RETENTION_SCHEDULED_NAME}" (${RETENTION_SCHEDULE}) and background worker "${RETENTION_WORKER_NAME}".`,
649
759
  );
@@ -0,0 +1,24 @@
1
+ import { closeDbExec, withMigrationRuntime } from "@agent-native/core/db";
2
+ import { runFrameworkReleaseMigrations } from "@agent-native/core/server";
3
+
4
+ import { runAnalyticsMigrations } from "../server/plugins/db.js";
5
+
6
+ /**
7
+ * Release-time schema entrypoint for Analytics.
8
+ *
9
+ * This script is the production owner of schema changes. It runs against the
10
+ * direct migration endpoint selected by core, while request functions skip
11
+ * all migration and ensure-table work automatically.
12
+ */
13
+ async function main(): Promise<void> {
14
+ await withMigrationRuntime(async () => {
15
+ await runFrameworkReleaseMigrations(null);
16
+ await runAnalyticsMigrations(null);
17
+ });
18
+ }
19
+
20
+ try {
21
+ await main();
22
+ } finally {
23
+ await closeDbExec();
24
+ }