@agent-native/core 0.145.8 → 0.146.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/corpus/README.md +1 -1
- package/corpus/templates/analytics/.agents/skills/turn-into-app/SKILL.md +41 -11
- package/corpus/templates/analytics/.agents/skills/turn-into-app/references/fresh-project.md +21 -2
- package/corpus/templates/analytics/actions/compose-dashboard.ts +2 -27
- package/corpus/templates/analytics/actions/get-sql-dashboard.ts +18 -3
- package/corpus/templates/analytics/actions/migrate-first-party-analytics-to-bigquery.ts +75 -24
- package/corpus/templates/analytics/actions/mutate-dashboard.ts +16 -24
- package/corpus/templates/analytics/actions/rename-dashboard.ts +2 -24
- package/corpus/templates/analytics/actions/reorder-dashboard-panels.ts +2 -24
- package/corpus/templates/analytics/actions/restore-dashboard-revision.ts +2 -25
- package/corpus/templates/analytics/actions/update-dashboard.ts +4 -32
- package/corpus/templates/analytics/app/components/layout/CommandPalette.tsx +1 -40
- 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-language-selection-now-lives-in-settings-instead-of-the-comm.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 +7 -0
- package/corpus/templates/analytics/scripts/emit-netlify-dashboard-report-cron.ts +110 -0
- package/corpus/templates/analytics/server/jobs/analytics-bigquery-backfill.ts +450 -0
- package/corpus/templates/analytics/server/jobs/analytics-rollup-backfill.ts +2 -2
- package/corpus/templates/analytics/server/lib/analytics-rollup-lock.ts +4 -5
- package/corpus/templates/analytics/server/lib/dashboard-collab-sync.ts +77 -0
- package/corpus/templates/analytics/server/lib/demo-dashboards.ts +2 -23
- 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/plugins/analytics-rollup-backfill-jobs.ts +2 -5
- package/corpus/templates/analytics/server/plugins/db.ts +42 -0
- 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 +18 -4
- package/corpus/templates/assets/.agents/skills/turn-into-app/SKILL.md +41 -11
- package/corpus/templates/assets/.agents/skills/turn-into-app/references/fresh-project.md +21 -2
- package/corpus/templates/brain/.agents/skills/turn-into-app/SKILL.md +41 -11
- package/corpus/templates/brain/.agents/skills/turn-into-app/references/fresh-project.md +21 -2
- package/corpus/templates/brain/server/lib/connectors.ts +38 -14
- package/corpus/templates/calendar/.agents/skills/turn-into-app/SKILL.md +41 -11
- package/corpus/templates/calendar/.agents/skills/turn-into-app/references/fresh-project.md +21 -2
- package/corpus/templates/chat/.agents/skills/turn-into-app/SKILL.md +41 -11
- package/corpus/templates/chat/.agents/skills/turn-into-app/references/fresh-project.md +21 -2
- package/corpus/templates/clips/.agents/skills/turn-into-app/SKILL.md +41 -11
- package/corpus/templates/clips/.agents/skills/turn-into-app/references/fresh-project.md +21 -2
- package/corpus/templates/clips/app/components/player/video-player.tsx +1 -1
- package/corpus/templates/clips/changelog/2026-08-07-fixed-trimmed-clips-durations-so-the-main-player-display-mat.md +6 -0
- package/corpus/templates/clips/chrome-extension/STORE_DRAFT.md +1 -1
- package/corpus/templates/clips/chrome-extension/public/manifest.json +1 -1
- package/corpus/templates/content/.agents/skills/turn-into-app/SKILL.md +41 -11
- package/corpus/templates/content/.agents/skills/turn-into-app/references/fresh-project.md +21 -2
- package/corpus/templates/crm/.agents/skills/turn-into-app/SKILL.md +41 -11
- package/corpus/templates/crm/.agents/skills/turn-into-app/references/fresh-project.md +21 -2
- package/corpus/templates/design/.agents/skills/turn-into-app/SKILL.md +41 -11
- package/corpus/templates/design/.agents/skills/turn-into-app/references/fresh-project.md +21 -2
- package/corpus/templates/dispatch/.agents/skills/turn-into-app/SKILL.md +41 -11
- package/corpus/templates/dispatch/.agents/skills/turn-into-app/references/fresh-project.md +21 -2
- package/corpus/templates/dispatch/changelog/2026-08-07-language-selection-now-lives-in-settings-instead-of-the-disp.md +6 -0
- package/corpus/templates/factory/.agents/skills/turn-into-app/SKILL.md +41 -11
- package/corpus/templates/factory/.agents/skills/turn-into-app/references/fresh-project.md +21 -2
- package/corpus/templates/factory/server/plugins/factory-scheduler-job.ts +59 -22
- package/corpus/templates/forms/.agents/skills/turn-into-app/SKILL.md +41 -11
- package/corpus/templates/forms/.agents/skills/turn-into-app/references/fresh-project.md +21 -2
- package/corpus/templates/macros/.agents/skills/turn-into-app/SKILL.md +41 -11
- package/corpus/templates/macros/.agents/skills/turn-into-app/references/fresh-project.md +21 -2
- package/corpus/templates/mail/.agents/skills/turn-into-app/SKILL.md +41 -11
- package/corpus/templates/mail/.agents/skills/turn-into-app/references/fresh-project.md +21 -2
- package/corpus/templates/plan/.agents/skills/turn-into-app/SKILL.md +41 -11
- package/corpus/templates/plan/.agents/skills/turn-into-app/references/fresh-project.md +21 -2
- package/corpus/templates/slides/.agents/skills/image-generation-via-a2a/SKILL.md +10 -1
- package/corpus/templates/slides/.agents/skills/slide-images/SKILL.md +10 -3
- package/corpus/templates/slides/.agents/skills/turn-into-app/SKILL.md +41 -11
- package/corpus/templates/slides/.agents/skills/turn-into-app/references/fresh-project.md +21 -2
- package/corpus/templates/slides/actions/generate-image-api.ts +123 -0
- package/corpus/templates/slides/app/components/deck/DeckCard.tsx +10 -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/editor/ImageGenPanel.tsx +1 -1
- 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-deleting-a-deck-no-longer-freezes-the-slides-browser.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-07-slide-previews-and-present-now-keep-deck-colors-consistent.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-07-slides-verify-generated-images-are-persisted-on-the-target-s.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/lib/slide-image-insertion.ts +141 -0
- package/corpus/templates/slides/server/plugins/agent-chat.ts +2 -0
- 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/turn-into-app/SKILL.md +41 -11
- package/corpus/templates/tasks/.agents/skills/turn-into-app/references/fresh-project.md +21 -2
- package/dist/cli/code-agent-executor.js +8 -3
- package/dist/client/AgentPanel.d.ts +10 -4
- package/dist/client/AgentPanel.js +29 -82
- package/dist/client/ConnectBuilderCard.d.ts +7 -7
- package/dist/client/ConnectBuilderCard.js +45 -20
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/config.d.ts +9 -2
- package/dist/config.js +38 -2
- package/dist/db/client.d.ts +13 -1
- package/dist/db/client.js +76 -2
- package/dist/db/create-get-db.js +2 -2
- package/dist/db/migrations.js +22 -4
- package/dist/deploy/build.d.ts +1 -0
- package/dist/deploy/build.js +32 -23
- package/dist/notifications/routes.d.ts +3 -3
- package/dist/progress/routes.d.ts +1 -1
- package/dist/provider-api/actions/custom-provider-registration.d.ts +9 -9
- package/dist/provider-api/actions/provider-api.d.ts +8 -8
- package/dist/provider-api/corpus-jobs.d.ts +2 -2
- package/dist/scripts/docs/search.js +3 -2
- package/dist/server/agent-chat/prompt-resources.js +4 -3
- package/dist/server/agents-bundle.d.ts +20 -3
- package/dist/server/agents-bundle.js +57 -30
- package/dist/server/better-auth-instance.js +2 -2
- package/dist/server/builder-browser.d.ts +34 -0
- package/dist/server/builder-browser.js +179 -0
- package/dist/server/core-routes-plugin.d.ts +5 -0
- package/dist/server/core-routes-plugin.js +24 -8
- package/dist/server/index.d.ts +1 -1
- package/dist/server/index.js +1 -1
- package/dist/shared/cache-control.d.ts +2 -2
- package/dist/shared/cache-control.js +2 -2
- package/dist/shared/runtime-config.js +1 -1
- package/dist/templates/chat/.agents/skills/turn-into-app/SKILL.md +41 -11
- package/dist/templates/chat/.agents/skills/turn-into-app/references/fresh-project.md +21 -2
- package/dist/templates/default/.agents/skills/turn-into-app/SKILL.md +41 -11
- package/dist/templates/default/.agents/skills/turn-into-app/references/fresh-project.md +21 -2
- package/dist/templates/headless/.agents/skills/turn-into-app/SKILL.md +41 -11
- package/dist/templates/headless/.agents/skills/turn-into-app/references/fresh-project.md +21 -2
- package/dist/templates/workspace-core/.agents/skills/turn-into-app/SKILL.md +41 -11
- package/dist/templates/workspace-core/.agents/skills/turn-into-app/references/fresh-project.md +21 -2
- 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 +17 -50
- 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/drop-in-agent.mdx +3 -3
- package/docs/content/environment-variables.mdx +1 -1
- package/docs/content/getting-started-actions.mdx +1 -2
- package/docs/content/getting-started-pages.mdx +1 -2
- package/docs/content/getting-started.mdx +0 -4
- package/docs/content/what-is-agent-native.mdx +4 -4
- package/package.json +1 -1
- package/src/templates/chat/.agents/skills/turn-into-app/SKILL.md +41 -11
- package/src/templates/chat/.agents/skills/turn-into-app/references/fresh-project.md +21 -2
- package/src/templates/default/.agents/skills/turn-into-app/SKILL.md +41 -11
- package/src/templates/default/.agents/skills/turn-into-app/references/fresh-project.md +21 -2
- package/src/templates/headless/.agents/skills/turn-into-app/SKILL.md +41 -11
- package/src/templates/headless/.agents/skills/turn-into-app/references/fresh-project.md +21 -2
- package/src/templates/workspace-core/.agents/skills/turn-into-app/SKILL.md +41 -11
- package/src/templates/workspace-core/.agents/skills/turn-into-app/references/fresh-project.md +21 -2
- package/docs/content/getting-started-database.mdx +0 -173
package/corpus/README.md
CHANGED
|
@@ -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,36 @@ 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
|
+
- Connect the Agent-Native Dispatch MCP connector only. Dispatch uses the
|
|
31
|
+
authenticated Builder Projects API to reuse or provision the workspace
|
|
32
|
+
project before starting the Builder Cloud Agent; a separate Builder CMS MCP
|
|
33
|
+
connection is not required for this workflow.
|
|
34
|
+
- If `start-workspace-app-creation` is not available or Dispatch is not
|
|
35
|
+
authenticated, stop with the connector setup needed. Do not fall back to a
|
|
36
|
+
local build or claim that the app exists.
|
|
37
|
+
- In Claude Code, Codex Code, or another local code-agent runtime with a target
|
|
38
|
+
workspace, follow the local implementation steps below. The local agent may
|
|
39
|
+
scaffold, edit, run, and verify the app there.
|
|
40
|
+
|
|
16
41
|
## Default behavior
|
|
17
42
|
|
|
18
|
-
|
|
43
|
+
For a local code-agent runtime this is an end-to-end build skill, not a request
|
|
44
|
+
for an app proposal. For an online host, the end-to-end result is a verified
|
|
45
|
+
Builder handoff and the resulting workspace app, not code written in the host.
|
|
19
46
|
|
|
20
47
|
- With no argument, choose the source in this order: visible project context,
|
|
21
48
|
then the current thread. A fresh Claude or ChatGPT Project is a valid source
|
|
@@ -47,9 +74,12 @@ private web access, invent an importer, add fake OAuth, or scrape a logged-in
|
|
|
47
74
|
page. If the needed context is not visible, ask for an export, transcript, or
|
|
48
75
|
attachment and treat that artifact as imported source material.
|
|
49
76
|
|
|
50
|
-
|
|
51
|
-
buttons and agent handoffs, start its dev
|
|
52
|
-
continue through build/deployment handoff. Do
|
|
77
|
+
For local code-agent runtimes, deliver a fresh app in a new directory,
|
|
78
|
+
implement the repeatable workflow with buttons and agent handoffs, start its dev
|
|
79
|
+
server, verify the main path, and continue through build/deployment handoff. Do
|
|
80
|
+
not stop at a plan. For online hosts, call Dispatch first after the source brief,
|
|
81
|
+
then report the returned Builder branch/path and verify the workspace app through
|
|
82
|
+
Dispatch when it becomes available.
|
|
53
83
|
|
|
54
84
|
## Fresh project context mode
|
|
55
85
|
|
|
@@ -190,12 +220,12 @@ Do not use `create` for an existing workspace; it scaffolds a new standalone
|
|
|
190
220
|
workspace rather than adding an app to the current one.
|
|
191
221
|
|
|
192
222
|
When the source came from a fresh external Project and the target is a Builder
|
|
193
|
-
workspace,
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
223
|
+
workspace, the online host path is mandatory: after writing the source brief,
|
|
224
|
+
call `start-workspace-app-creation` with a concise prompt, the inferred app id,
|
|
225
|
+
and the brief's repeatable workflow. Pass selected workspace resource IDs when
|
|
226
|
+
they are available, rather than pasting entire knowledge files. Continue from
|
|
227
|
+
the returned workspace path or Builder branch handoff and report the actual
|
|
228
|
+
verification result. Do not run the local scaffold commands in this host mode.
|
|
199
229
|
|
|
200
230
|
Use a first-party template only when it materially fits the workflow. Keep the
|
|
201
231
|
new app independent from the source thread's working tree unless the user
|
|
@@ -50,14 +50,33 @@ skills. For a ChatGPT Project or any host that exposes only the MCP connector,
|
|
|
50
50
|
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
|
+
This single connector is enough: Dispatch provisions or reuses the Builder
|
|
54
|
+
project through the Builder Projects API, so do not ask the user to add a
|
|
55
|
+
separate Builder CMS MCP for app creation.
|
|
56
|
+
Builder's separate Fusion MCP is available as a custom remote connector at
|
|
57
|
+
`https://mcp.builder.io/mcp/fusion` for direct Builder work. It can run an
|
|
58
|
+
existing project, but it cannot provision the repo-backed Agent-Native
|
|
59
|
+
workspace project, so it is optional and not a replacement for the Dispatch
|
|
60
|
+
handoff in this skill. Do not add Builder's CMS MCP for this workflow.
|
|
61
|
+
|
|
62
|
+
For Claude Web, ChatGPT Web, and their web Projects, the host is an
|
|
63
|
+
orchestrator, not the build environment. After forming the source brief, call
|
|
64
|
+
the Dispatch `start-workspace-app-creation` action so Builder creates the app
|
|
65
|
+
in the connected workspace. Do not use the host's code interpreter, shell,
|
|
66
|
+
artifact editor, `create_workspace_app`, or local `pnpm`/`npm` commands. If the
|
|
67
|
+
handoff action is unavailable, stop and ask the user to authenticate Dispatch;
|
|
68
|
+
do not fall back to a local sandbox build. Claude Code and Codex Code are the
|
|
69
|
+
local-agent exception and may follow the implementation steps in the main
|
|
70
|
+
skill.
|
|
53
71
|
|
|
54
72
|
In a new Project chat, say:
|
|
55
73
|
|
|
56
74
|
```text
|
|
57
75
|
Turn this project into an app. Use the visible Project instructions,
|
|
58
76
|
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
|
|
77
|
+
app in the connected Agent-Native workspace through Dispatch and Builder, keep
|
|
78
|
+
the source brief bounded, and report the real Builder branch/path and
|
|
79
|
+
verification result. Do not build it in this chat's sandbox.
|
|
61
80
|
```
|
|
62
81
|
|
|
63
82
|
If the host does not show the Project instructions or files to the model, stop
|
|
@@ -1,9 +1,4 @@
|
|
|
1
1
|
import { defineAction, embedApp } from "@agent-native/core";
|
|
2
|
-
import {
|
|
3
|
-
hasCollabState,
|
|
4
|
-
applyText,
|
|
5
|
-
seedFromText,
|
|
6
|
-
} from "@agent-native/core/collab";
|
|
7
2
|
import {
|
|
8
3
|
getRequestUserEmail,
|
|
9
4
|
getRequestOrgId,
|
|
@@ -11,6 +6,7 @@ import {
|
|
|
11
6
|
} from "@agent-native/core/server";
|
|
12
7
|
import { z } from "zod";
|
|
13
8
|
|
|
9
|
+
import { queueDashboardCollabSync } from "../server/lib/dashboard-collab-sync";
|
|
14
10
|
import { validateFirstPartyDashboardTimeScope } from "../server/lib/dashboard-time-scope";
|
|
15
11
|
import {
|
|
16
12
|
getDashboard,
|
|
@@ -27,27 +23,6 @@ import {
|
|
|
27
23
|
usesFirstPartyDashboardFilters,
|
|
28
24
|
} from "../server/lib/first-party-metric-catalog";
|
|
29
25
|
|
|
30
|
-
/**
|
|
31
|
-
* Push the saved config through the collab layer so open dashboard editors get
|
|
32
|
-
* the change in real time (mirrors update-dashboard).
|
|
33
|
-
*/
|
|
34
|
-
async function syncToCollab(
|
|
35
|
-
dashboardId: string,
|
|
36
|
-
config: Record<string, unknown>,
|
|
37
|
-
): Promise<void> {
|
|
38
|
-
const docId = `dash-${dashboardId}`;
|
|
39
|
-
const configStr = JSON.stringify(config);
|
|
40
|
-
try {
|
|
41
|
-
if (await hasCollabState(docId)) {
|
|
42
|
-
await applyText(docId, configStr, "content", "agent");
|
|
43
|
-
} else {
|
|
44
|
-
await seedFromText(docId, configStr);
|
|
45
|
-
}
|
|
46
|
-
} catch {
|
|
47
|
-
// Collab sync is best-effort — the SQL write is the source of truth.
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
26
|
const WINDOWS = new Set<MetricWindow>(["30d", "90d", "all"]);
|
|
52
27
|
|
|
53
28
|
function normalizeWindow(raw: unknown): MetricWindow | undefined {
|
|
@@ -329,7 +304,7 @@ export default defineAction({
|
|
|
329
304
|
? (finalConfig.panels as unknown[]).length
|
|
330
305
|
: 0;
|
|
331
306
|
|
|
332
|
-
|
|
307
|
+
queueDashboardCollabSync(args.dashboardId, finalConfig, "agent");
|
|
333
308
|
|
|
334
309
|
const parts: string[] = [];
|
|
335
310
|
if (existing && !args.overwrite) {
|
|
@@ -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:
|
|
@@ -63,15 +65,28 @@ export default defineAction({
|
|
|
63
65
|
const dash = await getDashboard(args.id, ctx);
|
|
64
66
|
if (!dash || dash.kind !== "sql") {
|
|
65
67
|
const seed = loadDashboardSeed(args.id);
|
|
66
|
-
if (seed)
|
|
67
|
-
|
|
68
|
+
if (seed) {
|
|
69
|
+
const config =
|
|
70
|
+
args.id === FIRST_PARTY_DASHBOARD_ID
|
|
71
|
+
? repairCanonicalFirstPartyDashboardQueries(seed).config
|
|
72
|
+
: seed;
|
|
73
|
+
return buildDashboardSeedAgentContext(args.id, config, {
|
|
68
74
|
includeConfig: args.includeConfig === true,
|
|
69
75
|
});
|
|
76
|
+
}
|
|
70
77
|
throw Object.assign(new Error("Dashboard not found"), {
|
|
71
78
|
statusCode: 404,
|
|
72
79
|
});
|
|
73
80
|
}
|
|
74
|
-
|
|
81
|
+
const dashboard =
|
|
82
|
+
args.id === FIRST_PARTY_DASHBOARD_ID
|
|
83
|
+
? {
|
|
84
|
+
...dash,
|
|
85
|
+
config: repairCanonicalFirstPartyDashboardQueries(dash.config)
|
|
86
|
+
.config,
|
|
87
|
+
}
|
|
88
|
+
: dash;
|
|
89
|
+
return buildDashboardAgentContext(dashboard, {
|
|
75
90
|
includeConfig: args.includeConfig === true,
|
|
76
91
|
});
|
|
77
92
|
},
|
|
@@ -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,74 @@ 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
|
+
}
|
|
103
|
+
const initialCursor =
|
|
104
|
+
existingJob?.cursor ?? current.backfillCursor ?? null;
|
|
105
|
+
if (
|
|
106
|
+
!existingJob &&
|
|
107
|
+
current.sink === "dual" &&
|
|
108
|
+
!initialCursor &&
|
|
109
|
+
ready.rowCount > 0
|
|
110
|
+
) {
|
|
111
|
+
throw new Error(
|
|
112
|
+
"Refusing to restart an existing dual-write BigQuery backfill without its legacy cursor; recover the migration cursor before preparing again.",
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
const initialCompleted =
|
|
116
|
+
existingJob?.status === "completed" ||
|
|
117
|
+
current.backfillCompleted === true;
|
|
70
118
|
await saveFirstPartyAnalyticsBackend(scope, {
|
|
71
119
|
sink: "dual",
|
|
72
120
|
table: ready.table.fullyQualified,
|
|
73
|
-
backfillCursor:
|
|
74
|
-
backfillCompleted:
|
|
121
|
+
backfillCursor: initialCursor,
|
|
122
|
+
backfillCompleted: initialCompleted,
|
|
75
123
|
});
|
|
124
|
+
const job = await queueFirstPartyAnalyticsBigQueryBackfill(
|
|
125
|
+
scope,
|
|
126
|
+
ready.table.fullyQualified,
|
|
127
|
+
limit,
|
|
128
|
+
initialCursor,
|
|
129
|
+
);
|
|
76
130
|
return {
|
|
77
131
|
sink: "dual" as const,
|
|
78
132
|
table: ready.table.fullyQualified,
|
|
79
133
|
existingBigQueryRows: ready.rowCount,
|
|
134
|
+
job,
|
|
80
135
|
next: "backfill",
|
|
81
136
|
};
|
|
82
137
|
}
|
|
@@ -87,23 +142,18 @@ export default defineAction({
|
|
|
87
142
|
"Prepare the organization for dual-write before starting the BigQuery backfill.",
|
|
88
143
|
);
|
|
89
144
|
}
|
|
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
|
-
});
|
|
145
|
+
const job = await getFirstPartyAnalyticsBigQueryBackfillJob(scope);
|
|
146
|
+
if (!job) {
|
|
147
|
+
throw new Error(
|
|
148
|
+
"Prepare the organization before enqueueing the BigQuery backfill.",
|
|
149
|
+
);
|
|
150
|
+
}
|
|
102
151
|
return {
|
|
103
|
-
...result,
|
|
104
152
|
sink: "dual" as const,
|
|
105
153
|
table: current.table,
|
|
106
|
-
|
|
154
|
+
job,
|
|
155
|
+
next: job.status === "completed" ? "cutover" : "backfill",
|
|
156
|
+
queued: job.status !== "completed",
|
|
107
157
|
};
|
|
108
158
|
}
|
|
109
159
|
|
|
@@ -112,16 +162,17 @@ export default defineAction({
|
|
|
112
162
|
"Cutover requires confirm=true because it stops first-party event and rollup writes to Postgres for this organization.",
|
|
113
163
|
);
|
|
114
164
|
}
|
|
115
|
-
|
|
165
|
+
const job = await getFirstPartyAnalyticsBigQueryBackfillJob(scope);
|
|
166
|
+
if (current.sink !== "dual" || job?.status !== "completed") {
|
|
116
167
|
throw new Error(
|
|
117
|
-
"
|
|
168
|
+
"Wait for the durable dual-write backfill job to report completed before cutting over to BigQuery.",
|
|
118
169
|
);
|
|
119
170
|
}
|
|
120
171
|
const ready = await assertFirstPartyAnalyticsBigQueryReady(current.table);
|
|
121
172
|
await saveFirstPartyAnalyticsBackend(scope, {
|
|
122
173
|
sink: "bigquery",
|
|
123
174
|
table: ready.table.fullyQualified,
|
|
124
|
-
backfillCursor:
|
|
175
|
+
backfillCursor: job.cursor,
|
|
125
176
|
backfillCompleted: true,
|
|
126
177
|
});
|
|
127
178
|
return {
|
|
@@ -1,9 +1,4 @@
|
|
|
1
1
|
import { defineAction, embedApp } from "@agent-native/core";
|
|
2
|
-
import {
|
|
3
|
-
applyText,
|
|
4
|
-
hasCollabState,
|
|
5
|
-
seedFromText,
|
|
6
|
-
} from "@agent-native/core/collab";
|
|
7
2
|
import {
|
|
8
3
|
buildDeepLink,
|
|
9
4
|
getRequestOrgId,
|
|
@@ -11,6 +6,10 @@ import {
|
|
|
11
6
|
} from "@agent-native/core/server";
|
|
12
7
|
import { z } from "zod";
|
|
13
8
|
|
|
9
|
+
import {
|
|
10
|
+
DASHBOARD_COLLAB_SYNC_TIMEOUT_MS,
|
|
11
|
+
queueDashboardCollabSync,
|
|
12
|
+
} from "../server/lib/dashboard-collab-sync";
|
|
14
13
|
import {
|
|
15
14
|
getDashboard,
|
|
16
15
|
upsertDashboardWithRetry,
|
|
@@ -171,24 +170,6 @@ function cloneConfig(config: Record<string, unknown>): Record<string, unknown> {
|
|
|
171
170
|
return JSON.parse(JSON.stringify(config)) as Record<string, unknown>;
|
|
172
171
|
}
|
|
173
172
|
|
|
174
|
-
async function syncToCollab(
|
|
175
|
-
dashboardId: string,
|
|
176
|
-
config: Record<string, unknown>,
|
|
177
|
-
): Promise<void> {
|
|
178
|
-
const docId = `dash-${dashboardId}`;
|
|
179
|
-
const configStr = JSON.stringify(config);
|
|
180
|
-
try {
|
|
181
|
-
const exists = await hasCollabState(docId);
|
|
182
|
-
if (exists) {
|
|
183
|
-
await applyText(docId, configStr, "content", "agent");
|
|
184
|
-
} else {
|
|
185
|
-
await seedFromText(docId, configStr);
|
|
186
|
-
}
|
|
187
|
-
} catch {
|
|
188
|
-
// SQL remains the source of truth; live collab sync is best-effort.
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
|
|
192
173
|
function sqlValidationScope(
|
|
193
174
|
operations: DashboardMutationOperation[],
|
|
194
175
|
): ReadonlySet<string> | "all" | null {
|
|
@@ -399,7 +380,10 @@ export default defineAction({
|
|
|
399
380
|
// structurally identical to the winning attempt's `root`, but reflects
|
|
400
381
|
// exactly what was saved.
|
|
401
382
|
root = saved.config as Record<string, unknown>;
|
|
402
|
-
|
|
383
|
+
// SQL is the durable source of truth. Collab sync is a convenience for
|
|
384
|
+
// already-open editors, so it must never hold the saved mutation hostage
|
|
385
|
+
// behind an unavailable database or a stale per-document write lock.
|
|
386
|
+
queueDashboardCollabSync(dashboardId, root, "agent");
|
|
403
387
|
}
|
|
404
388
|
|
|
405
389
|
const compact = compactDashboardResult(root, movedPanelIdsFrom(operations));
|
|
@@ -421,6 +405,14 @@ export default defineAction({
|
|
|
421
405
|
insertedPanelIds: mutation.insertedPanelIds,
|
|
422
406
|
removedPanelIds: mutation.removedPanelIds,
|
|
423
407
|
dashboardFieldsChanged: mutation.dashboardFieldsChanged,
|
|
408
|
+
...(args.dryRun === true
|
|
409
|
+
? { collabSync: { status: "skipped" as const } }
|
|
410
|
+
: {
|
|
411
|
+
collabSync: {
|
|
412
|
+
status: "queued" as const,
|
|
413
|
+
timeoutMs: DASHBOARD_COLLAB_SYNC_TIMEOUT_MS,
|
|
414
|
+
},
|
|
415
|
+
}),
|
|
424
416
|
...(args.returnConfig === true ? { config: root } : {}),
|
|
425
417
|
...(args.returnTypes === true
|
|
426
418
|
? {
|
|
@@ -1,15 +1,11 @@
|
|
|
1
1
|
import { defineAction } from "@agent-native/core";
|
|
2
|
-
import {
|
|
3
|
-
hasCollabState,
|
|
4
|
-
applyText,
|
|
5
|
-
seedFromText,
|
|
6
|
-
} from "@agent-native/core/collab";
|
|
7
2
|
import {
|
|
8
3
|
getRequestUserEmail,
|
|
9
4
|
getRequestOrgId,
|
|
10
5
|
} from "@agent-native/core/server";
|
|
11
6
|
import { z } from "zod";
|
|
12
7
|
|
|
8
|
+
import { queueDashboardCollabSync } from "../server/lib/dashboard-collab-sync";
|
|
13
9
|
import { upsertDashboardWithRetry } from "../server/lib/dashboards-store";
|
|
14
10
|
|
|
15
11
|
function resolveScope() {
|
|
@@ -19,24 +15,6 @@ function resolveScope() {
|
|
|
19
15
|
return { orgId, email };
|
|
20
16
|
}
|
|
21
17
|
|
|
22
|
-
async function syncToCollab(
|
|
23
|
-
dashboardId: string,
|
|
24
|
-
config: Record<string, unknown>,
|
|
25
|
-
requestSource?: string,
|
|
26
|
-
): Promise<void> {
|
|
27
|
-
const docId = `dash-${dashboardId}`;
|
|
28
|
-
const configStr = JSON.stringify(config);
|
|
29
|
-
try {
|
|
30
|
-
if (await hasCollabState(docId)) {
|
|
31
|
-
await applyText(docId, configStr, "content", requestSource);
|
|
32
|
-
} else {
|
|
33
|
-
await seedFromText(docId, configStr);
|
|
34
|
-
}
|
|
35
|
-
} catch {
|
|
36
|
-
// Best-effort: SQL remains the source of truth.
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
18
|
export default defineAction({
|
|
41
19
|
description: "Rename a saved analytics dashboard by ID.",
|
|
42
20
|
schema: z.object({
|
|
@@ -54,7 +32,7 @@ export default defineAction({
|
|
|
54
32
|
const updated = await upsertDashboardWithRetry(args.id, ctx, (existing) => {
|
|
55
33
|
return { kind: existing.kind, body: { ...existing.config, name } };
|
|
56
34
|
});
|
|
57
|
-
|
|
35
|
+
queueDashboardCollabSync(
|
|
58
36
|
args.id,
|
|
59
37
|
updated.config,
|
|
60
38
|
actionContext?.caller === "frontend" ? undefined : "agent",
|
|
@@ -1,9 +1,4 @@
|
|
|
1
1
|
import { defineAction, embedApp } from "@agent-native/core";
|
|
2
|
-
import {
|
|
3
|
-
applyText,
|
|
4
|
-
hasCollabState,
|
|
5
|
-
seedFromText,
|
|
6
|
-
} from "@agent-native/core/collab";
|
|
7
2
|
import {
|
|
8
3
|
buildDeepLink,
|
|
9
4
|
getRequestOrgId,
|
|
@@ -11,6 +6,7 @@ import {
|
|
|
11
6
|
} from "@agent-native/core/server";
|
|
12
7
|
import { z } from "zod";
|
|
13
8
|
|
|
9
|
+
import { queueDashboardCollabSync } from "../server/lib/dashboard-collab-sync";
|
|
14
10
|
import { upsertDashboardWithRetry } from "../server/lib/dashboards-store";
|
|
15
11
|
import {
|
|
16
12
|
compactDashboardResult,
|
|
@@ -74,24 +70,6 @@ function resolveTarget(args: {
|
|
|
74
70
|
return { position: args.position ?? "top" };
|
|
75
71
|
}
|
|
76
72
|
|
|
77
|
-
async function syncToCollab(
|
|
78
|
-
dashboardId: string,
|
|
79
|
-
config: Record<string, unknown>,
|
|
80
|
-
): Promise<void> {
|
|
81
|
-
const docId = `dash-${dashboardId}`;
|
|
82
|
-
const configStr = JSON.stringify(config);
|
|
83
|
-
try {
|
|
84
|
-
const exists = await hasCollabState(docId);
|
|
85
|
-
if (exists) {
|
|
86
|
-
await applyText(docId, configStr, "content", "agent");
|
|
87
|
-
} else {
|
|
88
|
-
await seedFromText(docId, configStr);
|
|
89
|
-
}
|
|
90
|
-
} catch {
|
|
91
|
-
// SQL remains the source of truth; live collab sync is best-effort.
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
73
|
export default defineAction({
|
|
96
74
|
agentTool: false,
|
|
97
75
|
description:
|
|
@@ -172,7 +150,7 @@ export default defineAction({
|
|
|
172
150
|
},
|
|
173
151
|
);
|
|
174
152
|
const root = saved.config as Record<string, unknown>;
|
|
175
|
-
|
|
153
|
+
queueDashboardCollabSync(dashboardId, root, "agent");
|
|
176
154
|
|
|
177
155
|
const compact = compactDashboardResult(root, orderResult.movedPanelIds);
|
|
178
156
|
return {
|
|
@@ -1,15 +1,11 @@
|
|
|
1
1
|
import { defineAction } from "@agent-native/core";
|
|
2
|
-
import {
|
|
3
|
-
applyText,
|
|
4
|
-
hasCollabState,
|
|
5
|
-
seedFromText,
|
|
6
|
-
} from "@agent-native/core/collab";
|
|
7
2
|
import {
|
|
8
3
|
getRequestOrgId,
|
|
9
4
|
getRequestUserEmail,
|
|
10
5
|
} from "@agent-native/core/server";
|
|
11
6
|
import { z } from "zod";
|
|
12
7
|
|
|
8
|
+
import { queueDashboardCollabSync } from "../server/lib/dashboard-collab-sync";
|
|
13
9
|
import { restoreDashboardRevision } from "../server/lib/dashboards-store";
|
|
14
10
|
|
|
15
11
|
function resolveScope() {
|
|
@@ -19,25 +15,6 @@ function resolveScope() {
|
|
|
19
15
|
return { orgId, email };
|
|
20
16
|
}
|
|
21
17
|
|
|
22
|
-
async function syncToCollab(
|
|
23
|
-
dashboardId: string,
|
|
24
|
-
config: Record<string, unknown>,
|
|
25
|
-
requestSource?: string,
|
|
26
|
-
): Promise<void> {
|
|
27
|
-
const docId = `dash-${dashboardId}`;
|
|
28
|
-
const configStr = JSON.stringify(config);
|
|
29
|
-
try {
|
|
30
|
-
const exists = await hasCollabState(docId);
|
|
31
|
-
if (exists) {
|
|
32
|
-
await applyText(docId, configStr, "content", requestSource);
|
|
33
|
-
} else {
|
|
34
|
-
await seedFromText(docId, configStr);
|
|
35
|
-
}
|
|
36
|
-
} catch {
|
|
37
|
-
// SQL is the source of truth; open editors also refetch on the dashboards signal.
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
18
|
export default defineAction({
|
|
42
19
|
description:
|
|
43
20
|
"Restore a dashboard to a saved history revision, snapshotting the current dashboard first.",
|
|
@@ -63,7 +40,7 @@ export default defineAction({
|
|
|
63
40
|
);
|
|
64
41
|
}
|
|
65
42
|
const { dashboard, snapshotRevisionId } = restored;
|
|
66
|
-
|
|
43
|
+
queueDashboardCollabSync(
|
|
67
44
|
dashboard.id,
|
|
68
45
|
dashboard.config,
|
|
69
46
|
actionContext?.caller === "frontend" ? undefined : "agent",
|