@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.
- package/corpus/README.md +1 -1
- package/corpus/templates/analytics/.agents/skills/frontend-design/SKILL.md +5 -1
- package/corpus/templates/analytics/.agents/skills/turn-into-app/SKILL.md +37 -11
- package/corpus/templates/analytics/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
- package/corpus/templates/analytics/actions/get-sql-dashboard.ts +20 -4
- package/corpus/templates/analytics/actions/migrate-first-party-analytics-to-bigquery.ts +59 -24
- package/corpus/templates/analytics/app/pages/settings/AlertRulesSettingsCard.tsx +3 -2
- package/corpus/templates/analytics/changelog/2026-08-07-analytics-migrations-now-use-bounded-pressure-aware-backgrou.md +6 -0
- package/corpus/templates/analytics/changelog/2026-08-07-settings-skeleton-placeholders.md +6 -0
- package/corpus/templates/analytics/changelog/2026-08-07-template-activity-charts-now-show-only-core-agent-native-app.md +6 -0
- package/corpus/templates/analytics/netlify.toml +1 -1
- package/corpus/templates/analytics/package.json +1 -0
- package/corpus/templates/analytics/scripts/emit-netlify-dashboard-report-cron.ts +110 -0
- package/corpus/templates/analytics/scripts/migrate-production.ts +24 -0
- package/corpus/templates/analytics/seeds/dashboards/agent-native-templates-first-party.json +13 -13
- package/corpus/templates/analytics/server/jobs/analytics-bigquery-backfill.ts +448 -0
- package/corpus/templates/analytics/server/jobs/analytics-rollup-backfill.ts +11 -2
- package/corpus/templates/analytics/server/lib/analytics-rollup-lock.ts +4 -5
- package/corpus/templates/analytics/server/lib/canonical-first-party-dashboard-repair.ts +35 -0
- package/corpus/templates/analytics/server/lib/dashboard-catalog.ts +3 -5
- package/corpus/templates/analytics/server/lib/first-party-analytics-backend.ts +10 -4
- package/corpus/templates/analytics/server/lib/first-party-analytics-rollups.ts +4 -11
- package/corpus/templates/analytics/server/lib/first-party-metric-catalog.ts +74 -28
- package/corpus/templates/analytics/server/plugins/analytics-rollup-backfill-jobs.ts +2 -5
- package/corpus/templates/analytics/server/plugins/db.ts +56 -3
- package/corpus/templates/analytics/server/routes/api/analytics-bigquery/backfill.post.ts +55 -0
- package/corpus/templates/analytics/server/routes/api/dashboard-agent-context.json.get.ts +22 -4
- package/corpus/templates/assets/.agents/skills/frontend-design/SKILL.md +5 -1
- package/corpus/templates/assets/.agents/skills/turn-into-app/SKILL.md +37 -11
- package/corpus/templates/assets/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
- package/corpus/templates/brain/.agents/skills/frontend-design/SKILL.md +5 -1
- package/corpus/templates/brain/.agents/skills/turn-into-app/SKILL.md +37 -11
- package/corpus/templates/brain/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
- package/corpus/templates/brain/server/lib/connectors.ts +38 -14
- package/corpus/templates/calendar/.agents/skills/frontend-design/SKILL.md +5 -1
- package/corpus/templates/calendar/.agents/skills/turn-into-app/SKILL.md +37 -11
- package/corpus/templates/calendar/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
- package/corpus/templates/chat/.agents/skills/frontend-design/SKILL.md +5 -1
- package/corpus/templates/chat/.agents/skills/turn-into-app/SKILL.md +37 -11
- package/corpus/templates/chat/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
- package/corpus/templates/chat/AGENTS.md +3 -0
- package/corpus/templates/clips/.agents/skills/frontend-design/SKILL.md +5 -1
- package/corpus/templates/clips/.agents/skills/turn-into-app/SKILL.md +37 -11
- package/corpus/templates/clips/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
- package/corpus/templates/content/.agents/skills/frontend-design/SKILL.md +5 -1
- package/corpus/templates/content/.agents/skills/turn-into-app/SKILL.md +37 -11
- package/corpus/templates/content/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
- package/corpus/templates/crm/.agents/skills/turn-into-app/SKILL.md +37 -11
- package/corpus/templates/crm/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
- package/corpus/templates/crm/app/components/crm/IntelligenceSettings.tsx +21 -3
- package/corpus/templates/crm/app/components/crm/settings/ConnectionSettings.tsx +23 -5
- package/corpus/templates/crm/app/components/crm/settings/FieldsSettings.tsx +24 -5
- package/corpus/templates/crm/app/components/crm/settings/ListsSettings.tsx +23 -3
- package/corpus/templates/crm/changelog/2026-08-07-settings-loading-skeletons.md +6 -0
- package/corpus/templates/design/.agents/skills/frontend-design/SKILL.md +5 -1
- package/corpus/templates/design/.agents/skills/turn-into-app/SKILL.md +37 -11
- package/corpus/templates/design/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
- package/corpus/templates/dispatch/.agents/skills/frontend-design/SKILL.md +5 -1
- package/corpus/templates/dispatch/.agents/skills/turn-into-app/SKILL.md +37 -11
- package/corpus/templates/dispatch/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
- package/corpus/templates/factory/.agents/skills/frontend-design/SKILL.md +5 -1
- package/corpus/templates/factory/.agents/skills/turn-into-app/SKILL.md +37 -11
- package/corpus/templates/factory/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
- package/corpus/templates/forms/.agents/skills/frontend-design/SKILL.md +5 -1
- package/corpus/templates/forms/.agents/skills/turn-into-app/SKILL.md +37 -11
- package/corpus/templates/forms/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
- package/corpus/templates/macros/.agents/skills/frontend-design/SKILL.md +5 -1
- package/corpus/templates/macros/.agents/skills/turn-into-app/SKILL.md +37 -11
- package/corpus/templates/macros/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
- package/corpus/templates/mail/.agents/skills/frontend-design/SKILL.md +5 -1
- package/corpus/templates/mail/.agents/skills/turn-into-app/SKILL.md +37 -11
- package/corpus/templates/mail/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
- package/corpus/templates/plan/.agents/skills/frontend-design/SKILL.md +5 -1
- package/corpus/templates/plan/.agents/skills/turn-into-app/SKILL.md +37 -11
- package/corpus/templates/plan/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
- package/corpus/templates/slides/.agents/skills/frontend-design/SKILL.md +5 -1
- package/corpus/templates/slides/.agents/skills/turn-into-app/SKILL.md +37 -11
- package/corpus/templates/slides/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
- package/corpus/templates/slides/app/components/editor/EditorSidebar.tsx +13 -0
- package/corpus/templates/slides/app/components/editor/GeneratingSlidePreview.tsx +5 -0
- package/corpus/templates/slides/app/components/presentation/PresentationView.tsx +5 -0
- package/corpus/templates/slides/app/components/presentation/PresenterView.tsx +5 -0
- package/corpus/templates/slides/app/pages/DeckEditor.tsx +3 -0
- package/corpus/templates/slides/app/pages/Presentation.tsx +3 -0
- package/corpus/templates/slides/app/pages/SharedPresentation.tsx +5 -0
- package/corpus/templates/slides/app/routes/slide.tsx +10 -5
- package/corpus/templates/slides/changelog/2026-08-07-slide-previews-and-present-now-keep-deck-colors-consistent.md +6 -0
- package/corpus/templates/slides/server/db/schema.ts +1 -0
- package/corpus/templates/slides/server/handlers/share.ts +51 -3
- package/corpus/templates/slides/server/plugins/db.ts +5 -0
- package/corpus/templates/slides/shared/api.ts +2 -0
- package/corpus/templates/tasks/.agents/skills/frontend-design/SKILL.md +5 -1
- package/corpus/templates/tasks/.agents/skills/turn-into-app/SKILL.md +37 -11
- package/corpus/templates/tasks/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
- package/dist/cli/code-agent-executor.js +8 -3
- package/dist/client/ConnectBuilderCard.d.ts +7 -7
- package/dist/client/ConnectBuilderCard.js +45 -20
- package/dist/client/components/ApiKeySettings.js +2 -5
- package/dist/client/org/TeamPage.js +4 -2
- package/dist/client/settings/AccountSettingsCard.js +2 -1
- package/dist/client/settings/AgentsSection.js +2 -1
- package/dist/client/settings/SecretsSection.js +3 -2
- package/dist/client/settings/SettingsPanel.js +6 -12
- package/dist/client/settings/SettingsSkeleton.d.ts +12 -0
- package/dist/client/settings/SettingsSkeleton.js +12 -0
- package/dist/client/settings/VoiceTranscriptionSection.js +5 -4
- package/dist/client/settings/index.d.ts +1 -0
- package/dist/client/settings/index.js +1 -0
- package/dist/config.d.ts +9 -2
- package/dist/config.js +38 -2
- package/dist/db/client.d.ts +26 -8
- package/dist/db/client.js +200 -9
- package/dist/db/create-get-db.js +2 -2
- package/dist/db/ddl-guard.js +3 -1
- package/dist/db/ensure-additive-columns.d.ts +2 -0
- package/dist/db/ensure-additive-columns.js +5 -2
- package/dist/db/index.d.ts +2 -2
- package/dist/db/index.js +2 -2
- package/dist/db/migrations.d.ts +11 -54
- package/dist/db/migrations.js +70 -0
- package/dist/deploy/build.js +5 -1
- package/dist/jobs/index.d.ts +1 -0
- package/dist/jobs/index.js +1 -0
- package/dist/jobs/run-history.d.ts +4 -0
- package/dist/jobs/run-history.js +44 -0
- package/dist/provider-api/actions/provider-api.d.ts +4 -4
- package/dist/scripts/docs/search.js +3 -2
- package/dist/secrets/routes.d.ts +6 -6
- package/dist/server/agent-chat/prompt-resources.js +4 -3
- package/dist/server/agent-chat-plugin.js +6 -4
- package/dist/server/agents-bundle.d.ts +20 -3
- package/dist/server/agents-bundle.js +57 -30
- package/dist/server/auth-plugin.js +5 -0
- package/dist/server/better-auth-instance.js +2 -63
- package/dist/server/better-auth-migrations.d.ts +8 -0
- package/dist/server/better-auth-migrations.js +179 -0
- package/dist/server/edge.d.ts +2 -0
- package/dist/server/edge.js +2 -0
- package/dist/server/index.d.ts +2 -0
- package/dist/server/index.js +2 -0
- package/dist/server/release-migrations.d.ts +8 -0
- package/dist/server/release-migrations.js +24 -0
- package/dist/server/sweep-runtime.d.ts +1 -1
- package/dist/server/sweep-runtime.js +15 -0
- package/dist/shared/runtime-config.js +1 -1
- package/dist/templates/chat/.agents/skills/frontend-design/SKILL.md +5 -1
- package/dist/templates/chat/.agents/skills/turn-into-app/SKILL.md +37 -11
- package/dist/templates/chat/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
- package/dist/templates/chat/AGENTS.md +3 -0
- package/dist/templates/default/.agents/skills/frontend-design/SKILL.md +5 -1
- package/dist/templates/default/.agents/skills/turn-into-app/SKILL.md +37 -11
- package/dist/templates/default/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
- package/dist/templates/default/AGENTS.md +3 -0
- package/dist/templates/headless/.agents/skills/turn-into-app/SKILL.md +37 -11
- package/dist/templates/headless/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
- package/dist/templates/workspace-core/.agents/skills/frontend-design/SKILL.md +5 -1
- package/dist/templates/workspace-core/.agents/skills/turn-into-app/SKILL.md +37 -11
- package/dist/templates/workspace-core/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
- package/dist/templates/workspace-core/AGENTS.md +3 -0
- package/dist/templates/workspace-root/AGENTS.md +3 -0
- package/dist/vite/agent-native-config-loader.d.ts +10 -0
- package/dist/vite/agent-native-config-loader.js +56 -0
- package/dist/vite/agents-bundle-plugin.d.ts +5 -1
- package/dist/vite/agents-bundle-plugin.js +29 -6
- package/dist/vite/client.d.ts +4 -2
- package/dist/vite/client.js +4 -49
- package/dist/vite/index.d.ts +2 -2
- package/dist/vite/index.js +1 -1
- package/docs/content/agent-native-config.mdx +43 -17
- package/docs/content/environment-variables.mdx +1 -1
- package/package.json +3 -3
- package/src/templates/chat/.agents/skills/frontend-design/SKILL.md +5 -1
- package/src/templates/chat/.agents/skills/turn-into-app/SKILL.md +37 -11
- package/src/templates/chat/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
- package/src/templates/chat/AGENTS.md +3 -0
- package/src/templates/default/.agents/skills/frontend-design/SKILL.md +5 -1
- package/src/templates/default/.agents/skills/turn-into-app/SKILL.md +37 -11
- package/src/templates/default/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
- package/src/templates/default/AGENTS.md +3 -0
- package/src/templates/headless/.agents/skills/turn-into-app/SKILL.md +37 -11
- package/src/templates/headless/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
- package/src/templates/workspace-core/.agents/skills/frontend-design/SKILL.md +5 -1
- package/src/templates/workspace-core/.agents/skills/turn-into-app/SKILL.md +37 -11
- package/src/templates/workspace-core/.agents/skills/turn-into-app/references/fresh-project.md +13 -2
- package/src/templates/workspace-core/AGENTS.md +3 -0
- package/src/templates/workspace-root/AGENTS.md +3 -0
package/corpus/README.md
CHANGED
|
@@ -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
|
-
-
|
|
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
|
-
|
|
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
|
-
|
|
51
|
-
buttons and agent handoffs, start its dev
|
|
52
|
-
continue through build/deployment handoff. Do
|
|
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,
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
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
|
|
60
|
-
and report the real path and
|
|
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(
|
|
68
|
-
|
|
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
|
-
|
|
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
|
-
|
|
39
|
-
|
|
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
|
-
"
|
|
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,
|
|
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:
|
|
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
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
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
|
-
|
|
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
|
-
|
|
149
|
+
const job = await getFirstPartyAnalyticsBigQueryBackfillJob(scope);
|
|
150
|
+
if (current.sink !== "dual" || job?.status !== "completed") {
|
|
116
151
|
throw new Error(
|
|
117
|
-
"
|
|
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:
|
|
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
|
-
<
|
|
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>
|
|
@@ -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
|
|
|
@@ -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
|
+
}
|