@agent-native/core 0.131.9 → 0.132.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 +2 -2
- package/corpus/core/CHANGELOG.md +99 -0
- package/corpus/core/docs/content/durable-background-runs.mdx +15 -6
- package/corpus/core/docs/design/durable-agent-runs.md +15 -11
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/a2a/activity.ts +127 -10
- package/corpus/core/src/a2a/index.ts +3 -0
- package/corpus/core/src/agent/durable-background.ts +38 -28
- package/corpus/core/src/agent/harness/background.ts +12 -2
- package/corpus/core/src/agent/production-agent.ts +18 -15
- package/corpus/core/src/agent/run-store.ts +45 -3
- package/corpus/core/src/application-state/handlers.ts +45 -0
- package/corpus/core/src/application-state/index.ts +3 -0
- package/corpus/core/src/application-state/store.ts +28 -11
- package/corpus/core/src/audit/redact.ts +104 -18
- package/corpus/core/src/client/AgentPanel.tsx +2 -7
- package/corpus/core/src/client/MultiTabAssistantChat.tsx +3 -0
- package/corpus/core/src/client/RunStuckBanner.tsx +29 -6
- package/corpus/core/src/client/agent-chat.ts +4 -9
- package/corpus/core/src/client/application-state.ts +115 -12
- package/corpus/core/src/client/dynamic-suggestions.ts +4 -12
- package/corpus/core/src/client/guided-questions.tsx +12 -23
- package/corpus/core/src/db/client.ts +148 -28
- package/corpus/core/src/db/create-get-db.ts +39 -6
- package/corpus/core/src/db/runtime-diagnostics.ts +41 -6
- package/corpus/core/src/deploy/build.ts +26 -33
- package/corpus/core/src/feature-flags/store.ts +10 -5
- package/corpus/core/src/integrations/a2a-continuations-store.ts +11 -0
- package/corpus/core/src/integrations/config-store.ts +13 -0
- package/corpus/core/src/integrations/google-docs-poller.ts +26 -2
- package/corpus/core/src/mcp-client/routes.ts +33 -2
- package/corpus/core/src/onboarding/plugin.ts +31 -25
- package/corpus/core/src/org/accept-pending.ts +2 -0
- package/corpus/core/src/org/auto-join-domain.ts +2 -0
- package/corpus/core/src/org/context.ts +35 -10
- package/corpus/core/src/org/handlers.ts +6 -0
- package/corpus/core/src/org/request-org-cache.ts +71 -0
- package/corpus/core/src/resources/store.ts +10 -9
- package/corpus/core/src/scripts/runner.ts +6 -3
- package/corpus/core/src/secrets/storage.ts +97 -5
- package/corpus/core/src/server/action-change.ts +33 -0
- package/corpus/core/src/server/action-routes.ts +11 -9
- package/corpus/core/src/server/agent-teams.ts +10 -2
- package/corpus/core/src/server/better-auth-instance.ts +39 -12
- package/corpus/core/src/server/core-routes-plugin.ts +19 -2
- package/corpus/core/src/server/credential-provider.ts +57 -14
- package/corpus/core/src/server/http-response-telemetry.ts +1 -0
- package/corpus/core/src/server/poll.ts +95 -57
- package/corpus/core/src/server/voice-providers-status.ts +19 -4
- package/corpus/templates/analytics/.agents/skills/dashboard-management/SKILL.md +38 -0
- package/corpus/templates/analytics/actions/dashboard-mutation-api.ts +1 -1
- package/corpus/templates/analytics/actions/ensure-native-v2-dashboards.ts +299 -0
- package/corpus/templates/analytics/actions/list-native-v2-dashboard-templates.ts +34 -0
- package/corpus/templates/analytics/app/components/dashboard/SqlChart.tsx +82 -0
- package/corpus/templates/analytics/app/components/layout/NewDashboardDialog.tsx +2 -2
- package/corpus/templates/analytics/app/hooks/use-dashboard-chat-context.ts +40 -34
- package/corpus/templates/analytics/app/hooks/use-user-pref.ts +3 -3
- package/corpus/templates/analytics/app/i18n-data.ts +40 -0
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/PanelEditorDialog.tsx +18 -10
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +14 -0
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/types.ts +1 -0
- package/corpus/templates/analytics/app/routes/chart.tsx +3 -0
- package/corpus/templates/analytics/changelog/2026-07-30-analytics-dashboards-now-expose-native-funnel-heatmap-callou.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-30-analytics-now-ships-reusable-native-v2-dashboard-manifests-w.md +6 -0
- package/corpus/templates/analytics/server/lib/dashboard-report-render.ts +81 -1
- package/corpus/templates/analytics/server/lib/native-v2-dashboards.ts +457 -0
- package/corpus/templates/analytics/server/lib/report-chart-svg.ts +73 -2
- package/corpus/templates/analytics/server/plugins/agent-chat.ts +1 -1
- package/corpus/templates/analytics/shared/dashboard-funnel.ts +83 -0
- package/corpus/templates/content/changelog/2026-07-30-long-running-delegated-requests-now-continue-reliably.md +6 -0
- package/corpus/templates/content/netlify.toml +3 -0
- package/corpus/templates/content/server/plugins/agent-chat.ts +1 -0
- package/corpus/templates/slides/.agents/skills/create-deck/SKILL.md +2 -1
- package/corpus/templates/slides/actions/patch-deck.ts +25 -1
- package/corpus/templates/slides/app/components/deck/DeckCard.tsx +17 -4
- package/corpus/templates/slides/app/components/deck/SlideRenderer.tsx +9 -1
- package/corpus/templates/slides/app/components/layout/Layout.tsx +1 -1
- package/corpus/templates/slides/app/context/DeckContext.tsx +11 -3
- package/corpus/templates/slides/app/lib/sanitize-slide-html.ts +39 -11
- package/corpus/templates/slides/app/pages/Index.tsx +2 -2
- package/corpus/templates/slides/changelog/2026-07-30-slide-previews-keep-their-aspect-ratio-isolate-embedded-styl.md +6 -0
- package/corpus/templates/slides/changelog/2026-07-30-slides-no-longer-lose-decks-while-organization-access-is-loa.md +6 -0
- package/corpus/toolkit/CHANGELOG.md +8 -0
- package/corpus/toolkit/package.json +1 -1
- package/corpus/toolkit/src/composer/runtime-adapters.tsx +16 -11
- package/dist/a2a/activity.d.ts +22 -4
- package/dist/a2a/activity.d.ts.map +1 -1
- package/dist/a2a/activity.js +97 -11
- package/dist/a2a/activity.js.map +1 -1
- package/dist/a2a/index.d.ts +1 -1
- package/dist/a2a/index.d.ts.map +1 -1
- package/dist/a2a/index.js +1 -1
- package/dist/a2a/index.js.map +1 -1
- package/dist/agent/durable-background.d.ts +13 -8
- package/dist/agent/durable-background.d.ts.map +1 -1
- package/dist/agent/durable-background.js +36 -28
- package/dist/agent/durable-background.js.map +1 -1
- package/dist/agent/harness/background.d.ts.map +1 -1
- package/dist/agent/harness/background.js +12 -2
- package/dist/agent/harness/background.js.map +1 -1
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +18 -15
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/agent/run-store.d.ts +2 -0
- package/dist/agent/run-store.d.ts.map +1 -1
- package/dist/agent/run-store.js +44 -3
- package/dist/agent/run-store.js.map +1 -1
- package/dist/application-state/handlers.d.ts +15 -0
- package/dist/application-state/handlers.d.ts.map +1 -1
- package/dist/application-state/handlers.js +42 -2
- package/dist/application-state/handlers.js.map +1 -1
- package/dist/application-state/index.d.ts +2 -2
- package/dist/application-state/index.d.ts.map +1 -1
- package/dist/application-state/index.js +2 -2
- package/dist/application-state/index.js.map +1 -1
- package/dist/application-state/store.d.ts +11 -0
- package/dist/application-state/store.d.ts.map +1 -1
- package/dist/application-state/store.js +23 -10
- package/dist/application-state/store.js.map +1 -1
- package/dist/audit/redact.d.ts +25 -2
- package/dist/audit/redact.d.ts.map +1 -1
- package/dist/audit/redact.js +85 -21
- package/dist/audit/redact.js.map +1 -1
- package/dist/client/AgentPanel.d.ts.map +1 -1
- package/dist/client/AgentPanel.js +2 -7
- package/dist/client/AgentPanel.js.map +1 -1
- package/dist/client/MultiTabAssistantChat.d.ts.map +1 -1
- package/dist/client/MultiTabAssistantChat.js +1 -1
- package/dist/client/MultiTabAssistantChat.js.map +1 -1
- package/dist/client/RunStuckBanner.d.ts +11 -1
- package/dist/client/RunStuckBanner.d.ts.map +1 -1
- package/dist/client/RunStuckBanner.js +20 -7
- package/dist/client/RunStuckBanner.js.map +1 -1
- package/dist/client/agent-chat.d.ts.map +1 -1
- package/dist/client/agent-chat.js +4 -6
- package/dist/client/agent-chat.js.map +1 -1
- package/dist/client/application-state.d.ts +11 -0
- package/dist/client/application-state.d.ts.map +1 -1
- package/dist/client/application-state.js +87 -9
- package/dist/client/application-state.js.map +1 -1
- package/dist/client/dynamic-suggestions.d.ts.map +1 -1
- package/dist/client/dynamic-suggestions.js +4 -13
- package/dist/client/dynamic-suggestions.js.map +1 -1
- package/dist/client/guided-questions.d.ts.map +1 -1
- package/dist/client/guided-questions.js +6 -22
- package/dist/client/guided-questions.js.map +1 -1
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/awareness.d.ts.map +1 -1
- package/dist/db/client.d.ts +57 -10
- package/dist/db/client.d.ts.map +1 -1
- package/dist/db/client.js +122 -31
- package/dist/db/client.js.map +1 -1
- package/dist/db/create-get-db.d.ts.map +1 -1
- package/dist/db/create-get-db.js +31 -7
- package/dist/db/create-get-db.js.map +1 -1
- package/dist/db/runtime-diagnostics.d.ts +1 -0
- package/dist/db/runtime-diagnostics.d.ts.map +1 -1
- package/dist/db/runtime-diagnostics.js +31 -6
- package/dist/db/runtime-diagnostics.js.map +1 -1
- package/dist/deploy/build.d.ts +5 -20
- package/dist/deploy/build.d.ts.map +1 -1
- package/dist/deploy/build.js +18 -30
- package/dist/deploy/build.js.map +1 -1
- package/dist/feature-flags/store.d.ts.map +1 -1
- package/dist/feature-flags/store.js +10 -5
- package/dist/feature-flags/store.js.map +1 -1
- package/dist/integrations/a2a-continuations-store.d.ts.map +1 -1
- package/dist/integrations/a2a-continuations-store.js +12 -0
- package/dist/integrations/a2a-continuations-store.js.map +1 -1
- package/dist/integrations/config-store.d.ts +1 -0
- package/dist/integrations/config-store.d.ts.map +1 -1
- package/dist/integrations/config-store.js +11 -0
- package/dist/integrations/config-store.js.map +1 -1
- package/dist/integrations/google-docs-poller.d.ts.map +1 -1
- package/dist/integrations/google-docs-poller.js +19 -2
- package/dist/integrations/google-docs-poller.js.map +1 -1
- package/dist/mcp/screen-memory-stdio.d.ts +7 -7
- package/dist/mcp/screen-memory-stdio.d.ts.map +1 -1
- package/dist/mcp-client/routes.d.ts.map +1 -1
- package/dist/mcp-client/routes.js +30 -2
- package/dist/mcp-client/routes.js.map +1 -1
- package/dist/notifications/routes.d.ts +6 -6
- package/dist/observability/routes.d.ts +3 -3
- package/dist/onboarding/plugin.d.ts.map +1 -1
- package/dist/onboarding/plugin.js +12 -8
- package/dist/onboarding/plugin.js.map +1 -1
- package/dist/org/accept-pending.d.ts.map +1 -1
- package/dist/org/accept-pending.js +2 -0
- package/dist/org/accept-pending.js.map +1 -1
- package/dist/org/auto-join-domain.d.ts.map +1 -1
- package/dist/org/auto-join-domain.js +2 -0
- package/dist/org/auto-join-domain.js.map +1 -1
- package/dist/org/context.d.ts.map +1 -1
- package/dist/org/context.js +29 -9
- package/dist/org/context.js.map +1 -1
- package/dist/org/handlers.d.ts.map +1 -1
- package/dist/org/handlers.js +5 -0
- package/dist/org/handlers.js.map +1 -1
- package/dist/org/request-org-cache.d.ts +16 -0
- package/dist/org/request-org-cache.d.ts.map +1 -0
- package/dist/org/request-org-cache.js +62 -0
- package/dist/org/request-org-cache.js.map +1 -0
- package/dist/progress/routes.d.ts +1 -1
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/resources/store.d.ts.map +1 -1
- package/dist/resources/store.js +11 -9
- package/dist/resources/store.js.map +1 -1
- package/dist/scripts/runner.d.ts.map +1 -1
- package/dist/scripts/runner.js +3 -3
- package/dist/scripts/runner.js.map +1 -1
- package/dist/secrets/storage.d.ts.map +1 -1
- package/dist/secrets/storage.js +82 -5
- package/dist/secrets/storage.js.map +1 -1
- package/dist/server/action-change.d.ts +20 -0
- package/dist/server/action-change.d.ts.map +1 -1
- package/dist/server/action-change.js +29 -0
- package/dist/server/action-change.js.map +1 -1
- package/dist/server/action-routes.d.ts.map +1 -1
- package/dist/server/action-routes.js +7 -8
- package/dist/server/action-routes.js.map +1 -1
- package/dist/server/agent-teams.d.ts.map +1 -1
- package/dist/server/agent-teams.js +10 -2
- package/dist/server/agent-teams.js.map +1 -1
- package/dist/server/better-auth-instance.d.ts.map +1 -1
- package/dist/server/better-auth-instance.js +31 -14
- package/dist/server/better-auth-instance.js.map +1 -1
- package/dist/server/core-routes-plugin.d.ts +1 -0
- package/dist/server/core-routes-plugin.d.ts.map +1 -1
- package/dist/server/core-routes-plugin.js +18 -4
- package/dist/server/core-routes-plugin.js.map +1 -1
- package/dist/server/credential-provider.d.ts +16 -0
- package/dist/server/credential-provider.d.ts.map +1 -1
- package/dist/server/credential-provider.js +49 -13
- package/dist/server/credential-provider.js.map +1 -1
- package/dist/server/http-response-telemetry.d.ts.map +1 -1
- package/dist/server/http-response-telemetry.js +1 -0
- package/dist/server/http-response-telemetry.js.map +1 -1
- package/dist/server/poll.d.ts +6 -0
- package/dist/server/poll.d.ts.map +1 -1
- package/dist/server/poll.js +84 -47
- package/dist/server/poll.js.map +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/dist/server/voice-providers-status.d.ts.map +1 -1
- package/dist/server/voice-providers-status.js +12 -3
- package/dist/server/voice-providers-status.js.map +1 -1
- package/docs/content/durable-background-runs.mdx +15 -6
- package/docs/design/durable-agent-runs.md +15 -11
- package/package.json +2 -2
- package/src/a2a/activity.ts +127 -10
- package/src/a2a/index.ts +3 -0
- package/src/agent/durable-background.ts +38 -28
- package/src/agent/harness/background.ts +12 -2
- package/src/agent/production-agent.ts +18 -15
- package/src/agent/run-store.ts +45 -3
- package/src/application-state/handlers.ts +45 -0
- package/src/application-state/index.ts +3 -0
- package/src/application-state/store.ts +28 -11
- package/src/audit/redact.ts +104 -18
- package/src/client/AgentPanel.tsx +2 -7
- package/src/client/MultiTabAssistantChat.tsx +3 -0
- package/src/client/RunStuckBanner.tsx +29 -6
- package/src/client/agent-chat.ts +4 -9
- package/src/client/application-state.ts +115 -12
- package/src/client/dynamic-suggestions.ts +4 -12
- package/src/client/guided-questions.tsx +12 -23
- package/src/db/client.ts +148 -28
- package/src/db/create-get-db.ts +39 -6
- package/src/db/runtime-diagnostics.ts +41 -6
- package/src/deploy/build.ts +26 -33
- package/src/feature-flags/store.ts +10 -5
- package/src/integrations/a2a-continuations-store.ts +11 -0
- package/src/integrations/config-store.ts +13 -0
- package/src/integrations/google-docs-poller.ts +26 -2
- package/src/mcp-client/routes.ts +33 -2
- package/src/onboarding/plugin.ts +31 -25
- package/src/org/accept-pending.ts +2 -0
- package/src/org/auto-join-domain.ts +2 -0
- package/src/org/context.ts +35 -10
- package/src/org/handlers.ts +6 -0
- package/src/org/request-org-cache.ts +71 -0
- package/src/resources/store.ts +10 -9
- package/src/scripts/runner.ts +6 -3
- package/src/secrets/storage.ts +97 -5
- package/src/server/action-change.ts +33 -0
- package/src/server/action-routes.ts +11 -9
- package/src/server/agent-teams.ts +10 -2
- package/src/server/better-auth-instance.ts +39 -12
- package/src/server/core-routes-plugin.ts +19 -2
- package/src/server/credential-provider.ts +57 -14
- package/src/server/http-response-telemetry.ts +1 -0
- package/src/server/poll.ts +95 -57
- package/src/server/voice-providers-status.ts +19 -4
|
@@ -442,6 +442,12 @@ export class AppSyncState {
|
|
|
442
442
|
private lastExtensionsUpdatedAt: string | number | undefined;
|
|
443
443
|
private lastExtensionMarkerTs = 0;
|
|
444
444
|
private lastActionMarkerTs = 0;
|
|
445
|
+
/**
|
|
446
|
+
* Set by the seed when a durable action marker predates this process, so the
|
|
447
|
+
* first `doCheckExternalDbChanges` reads the marker rows even though the
|
|
448
|
+
* MAX(updated_at) probe cannot distinguish that row from the seeded max.
|
|
449
|
+
*/
|
|
450
|
+
private replayActionMarkerOnce = false;
|
|
445
451
|
|
|
446
452
|
/**
|
|
447
453
|
* Tracks the latest updated_at seen on the `__screen_refresh__` key.
|
|
@@ -1355,6 +1361,7 @@ export class AppSyncState {
|
|
|
1355
1361
|
// performed by a separate action process. Do not baseline past an existing
|
|
1356
1362
|
// marker on cold start, or the first poll after the action will miss it.
|
|
1357
1363
|
this.lastActionMarkerTs = 0;
|
|
1364
|
+
this.replayActionMarkerOnce = actionMarkerTs > 0;
|
|
1358
1365
|
this.lastScreenRefreshTs = refreshTs;
|
|
1359
1366
|
this.lastScreenRefreshTsBySession.clear();
|
|
1360
1367
|
for (const row of refreshResult.rows) {
|
|
@@ -1416,28 +1423,54 @@ export class AppSyncState {
|
|
|
1416
1423
|
// concurrently to shave stacked latency; results are still processed in
|
|
1417
1424
|
// the original sequential order, and conditional follow-up queries stay
|
|
1418
1425
|
// sequential within their branch.
|
|
1426
|
+
//
|
|
1427
|
+
// `lastAppStateTs` is maintained as MAX(updated_at) over the whole
|
|
1428
|
+
// `application_state` table (seeded in `seedVersionFromDb`, advanced by
|
|
1429
|
+
// the scan below over every key, not just the ones that emit events).
|
|
1430
|
+
// The application-state row scan and screen/extension marker reads only
|
|
1431
|
+
// report rows strictly newer than a watermark that is itself <=
|
|
1432
|
+
// `lastAppStateTs`, so an unadvanced MAX proves those reads are no-ops.
|
|
1433
|
+
// The action marker keeps its own max probe because its watermark must
|
|
1434
|
+
// remain independent under cross-process clock skew.
|
|
1419
1435
|
const [
|
|
1420
|
-
|
|
1436
|
+
appStateMaxTs,
|
|
1421
1437
|
actionMarkerTs,
|
|
1422
|
-
refreshResult,
|
|
1423
|
-
extensionMarkerTs,
|
|
1424
1438
|
settingsTs,
|
|
1425
1439
|
extensionsMaxUpdatedAt,
|
|
1426
1440
|
] = await Promise.all([
|
|
1427
|
-
db
|
|
1428
|
-
sql: "SELECT session_id, key, updated_at FROM application_state WHERE updated_at > ? ORDER BY updated_at ASC",
|
|
1429
|
-
args: [this.lastAppStateTs],
|
|
1430
|
-
}),
|
|
1441
|
+
readMaxUpdatedAt(db, "application_state"),
|
|
1431
1442
|
readActionMarkerMaxUpdatedAt(db),
|
|
1432
|
-
db.execute({
|
|
1433
|
-
sql: "SELECT session_id, updated_at, value FROM application_state WHERE key = ?",
|
|
1434
|
-
args: [SCREEN_REFRESH_KEY],
|
|
1435
|
-
}),
|
|
1436
|
-
readExtensionMarkerMaxUpdatedAt(db),
|
|
1437
1443
|
readMaxUpdatedAt(db, "settings"),
|
|
1438
1444
|
readMaxUpdatedAtRaw(db, "tools"),
|
|
1439
1445
|
]);
|
|
1440
1446
|
|
|
1447
|
+
// The seed deliberately rewinds `lastActionMarkerTs` to 0 so a marker
|
|
1448
|
+
// written before this process booted still reaches the first poll. That
|
|
1449
|
+
// replay is invisible to the MAX probe (the row is already counted in the
|
|
1450
|
+
// seeded max), so it gets one forced full read.
|
|
1451
|
+
const replayActionMarker = this.replayActionMarkerOnce;
|
|
1452
|
+
this.replayActionMarkerOnce = false;
|
|
1453
|
+
const appStateChanged =
|
|
1454
|
+
appStateMaxTs > this.lastAppStateTs || replayActionMarker;
|
|
1455
|
+
|
|
1456
|
+
const [appResult, refreshResult, extensionMarkerTs] = appStateChanged
|
|
1457
|
+
? await Promise.all([
|
|
1458
|
+
db.execute({
|
|
1459
|
+
sql: "SELECT session_id, key, updated_at FROM application_state WHERE updated_at > ? ORDER BY updated_at ASC",
|
|
1460
|
+
args: [this.lastAppStateTs],
|
|
1461
|
+
}),
|
|
1462
|
+
db.execute({
|
|
1463
|
+
sql: "SELECT session_id, updated_at, value FROM application_state WHERE key = ?",
|
|
1464
|
+
args: [SCREEN_REFRESH_KEY],
|
|
1465
|
+
}),
|
|
1466
|
+
readExtensionMarkerMaxUpdatedAt(db),
|
|
1467
|
+
])
|
|
1468
|
+
: ([
|
|
1469
|
+
{ rows: [] as Record<string, unknown>[] },
|
|
1470
|
+
null,
|
|
1471
|
+
this.lastExtensionMarkerTs,
|
|
1472
|
+
] as const);
|
|
1473
|
+
|
|
1441
1474
|
// Check application_state for external writes. Preserve the changed key so
|
|
1442
1475
|
// clients can invalidate one-shot command queries (`navigate`, `__set_url__`)
|
|
1443
1476
|
// only when those command rows actually change; noisy keys such as
|
|
@@ -1497,54 +1530,59 @@ export class AppSyncState {
|
|
|
1497
1530
|
// tool writes to application_state under a well-known key; when its
|
|
1498
1531
|
// updated_at bumps, emit a distinct event so the client invalidates
|
|
1499
1532
|
// all queries (not just the ones matching its default queryKey prefix).
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
)
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
)
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
// advanced, scoped with `owner` so canSeeChangeForUser delivers it only
|
|
1518
|
-
// to that user — not every authenticated poller.
|
|
1519
|
-
for (const row of refreshResult.rows) {
|
|
1520
|
-
const owner =
|
|
1521
|
-
typeof row.session_id === "string" ? row.session_id : undefined;
|
|
1522
|
-
if (!owner) continue;
|
|
1523
|
-
const rowTs = timestampValue(row.updated_at);
|
|
1524
|
-
if (rowTs <= (this.lastScreenRefreshTsBySession.get(owner) ?? 0)) {
|
|
1525
|
-
continue;
|
|
1533
|
+
// `refreshResult` is null exactly when the MAX probe proved no
|
|
1534
|
+
// application_state row moved, which is also exactly when this block
|
|
1535
|
+
// could not emit anything.
|
|
1536
|
+
if (refreshResult) {
|
|
1537
|
+
const refreshTs = refreshResult.rows.reduce(
|
|
1538
|
+
(max, row) => Math.max(max, timestampValue(row.updated_at)),
|
|
1539
|
+
0,
|
|
1540
|
+
);
|
|
1541
|
+
if (!this.screenRefreshInitialized) {
|
|
1542
|
+
this.lastScreenRefreshTs = refreshTs;
|
|
1543
|
+
for (const row of refreshResult.rows) {
|
|
1544
|
+
if (typeof row.session_id === "string") {
|
|
1545
|
+
this.lastScreenRefreshTsBySession.set(
|
|
1546
|
+
row.session_id,
|
|
1547
|
+
timestampValue(row.updated_at),
|
|
1548
|
+
);
|
|
1549
|
+
}
|
|
1526
1550
|
}
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1551
|
+
this.screenRefreshInitialized = true;
|
|
1552
|
+
} else if (refreshTs > this.lastScreenRefreshTs) {
|
|
1553
|
+
// Emit a per-user event only for the session(s) whose row actually
|
|
1554
|
+
// advanced, scoped with `owner` so canSeeChangeForUser delivers it only
|
|
1555
|
+
// to that user — not every authenticated poller.
|
|
1556
|
+
for (const row of refreshResult.rows) {
|
|
1557
|
+
const owner =
|
|
1558
|
+
typeof row.session_id === "string" ? row.session_id : undefined;
|
|
1559
|
+
if (!owner) continue;
|
|
1560
|
+
const rowTs = timestampValue(row.updated_at);
|
|
1561
|
+
if (rowTs <= (this.lastScreenRefreshTsBySession.get(owner) ?? 0)) {
|
|
1562
|
+
continue;
|
|
1533
1563
|
}
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1564
|
+
let scope: string | undefined;
|
|
1565
|
+
try {
|
|
1566
|
+
const raw = row.value;
|
|
1567
|
+
if (typeof raw === "string") {
|
|
1568
|
+
const parsed = JSON.parse(raw);
|
|
1569
|
+
if (typeof parsed?.scope === "string") scope = parsed.scope;
|
|
1570
|
+
}
|
|
1571
|
+
} catch {}
|
|
1572
|
+
this.recordChange(
|
|
1573
|
+
{
|
|
1574
|
+
source: "screen-refresh",
|
|
1575
|
+
type: "change",
|
|
1576
|
+
key: SCREEN_REFRESH_KEY,
|
|
1577
|
+
owner,
|
|
1578
|
+
...(scope ? { scope } : {}),
|
|
1579
|
+
},
|
|
1580
|
+
{ dedupeKey: `screen-refresh|${rowTs}` },
|
|
1581
|
+
);
|
|
1582
|
+
this.lastScreenRefreshTsBySession.set(owner, rowTs);
|
|
1583
|
+
}
|
|
1584
|
+
this.lastScreenRefreshTs = refreshTs;
|
|
1546
1585
|
}
|
|
1547
|
-
this.lastScreenRefreshTs = refreshTs;
|
|
1548
1586
|
}
|
|
1549
1587
|
|
|
1550
1588
|
// Extension mutations write a durable marker row so delete and hide/unhide
|
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
import { getOrgContext } from "../org/context.js";
|
|
23
23
|
import { getSession } from "./auth.js";
|
|
24
24
|
import {
|
|
25
|
+
prefetchSecrets,
|
|
25
26
|
resolveHasCompleteBuilderConnection,
|
|
26
27
|
resolveSecret,
|
|
27
28
|
} from "./credential-provider.js";
|
|
@@ -74,10 +75,14 @@ export function createVoiceProvidersStatusHandler() {
|
|
|
74
75
|
async function hasKey(key: string): Promise<boolean> {
|
|
75
76
|
try {
|
|
76
77
|
if (key === "GOOGLE_APPLICATION_CREDENTIALS") {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
78
|
+
// Same identity, passed explicitly — the context is here only so this
|
|
79
|
+
// scope sweep shares the prefetched per-request memo below.
|
|
80
|
+
const resolved = await withRequestContext(() =>
|
|
81
|
+
resolveGoogleRealtimeCredentials({
|
|
82
|
+
userEmail: session?.email,
|
|
83
|
+
orgId: orgCtx?.orgId ?? undefined,
|
|
84
|
+
}),
|
|
85
|
+
);
|
|
81
86
|
return typeof resolved === "string" && resolved.length > 0;
|
|
82
87
|
}
|
|
83
88
|
const resolved = await withRequestContext(() => resolveSecret(key));
|
|
@@ -87,6 +92,16 @@ export function createVoiceProvidersStatusHandler() {
|
|
|
87
92
|
}
|
|
88
93
|
}
|
|
89
94
|
|
|
95
|
+
// One read per scope for every key below, instead of one per key per scope.
|
|
96
|
+
await withRequestContext(() =>
|
|
97
|
+
prefetchSecrets([
|
|
98
|
+
"GEMINI_API_KEY",
|
|
99
|
+
"OPENAI_API_KEY",
|
|
100
|
+
"GROQ_API_KEY",
|
|
101
|
+
"GOOGLE_APPLICATION_CREDENTIALS",
|
|
102
|
+
]),
|
|
103
|
+
);
|
|
104
|
+
|
|
90
105
|
let builder = false;
|
|
91
106
|
try {
|
|
92
107
|
builder =
|
|
@@ -156,6 +156,43 @@ flattens into the baseline and reads as "no data."
|
|
|
156
156
|
- Scheduled email reports render the same two scales, so a dual-axis panel is
|
|
157
157
|
safe to put on a subscribed dashboard.
|
|
158
158
|
|
|
159
|
+
## Reusable Native Dashboard Patterns
|
|
160
|
+
|
|
161
|
+
The recent extension-backed dashboards in Builder Analytics cluster into a few
|
|
162
|
+
repeatable compositions. Prefer these native panels, with a real SQL or Data
|
|
163
|
+
Program result behind each one, when creating a replacement or a new dashboard:
|
|
164
|
+
|
|
165
|
+
| Pattern | Native composition |
|
|
166
|
+
| --- | --- |
|
|
167
|
+
| Customer ROI / value realization | `metric` KPI cards, `line` or `area` trends, `table` detail, and `callout` or `section` panels for the business narrative |
|
|
168
|
+
| Account engagement / outreach | `metric` coverage and adoption cards, a daily `line` trend, `heatmap` or `table` segmentation, and `callout` alerts |
|
|
169
|
+
| GTM pipeline / cross-sell | `funnel` for ordered stages, `metric` totals, `bar` or `line` trends, and a `table` for account-level follow-up |
|
|
170
|
+
| Win/loss analysis | `section` groups with `metric`, `table`, `bar`, `callout`, and trend panels; use a Data Program for provider joins and evidence rows |
|
|
171
|
+
|
|
172
|
+
Funnel panels use `config.xKey` for the stage label and `config.yKey` for a
|
|
173
|
+
non-negative count or value. The renderer preserves the SQL row order, shows
|
|
174
|
+
each stage's share of the first stage, and shows the change from the previous
|
|
175
|
+
stage. Keep the intended stage order in SQL with `ORDER BY`.
|
|
176
|
+
|
|
177
|
+
When a dashboard is being migrated from an extension, create a new v2 copy,
|
|
178
|
+
bind its panels to the real provider schema or Data Programs, and compare it
|
|
179
|
+
with the original before retiring the extension-backed version. Do not invent
|
|
180
|
+
customer-specific SQL, provider joins, cached rows, or extension ids in a
|
|
181
|
+
catalog template. Existing dashboards remain readable while the native
|
|
182
|
+
replacement is validated. Bespoke interaction flows, arbitrary layouts, and
|
|
183
|
+
visualizations outside these contracts may remain Custom Blocks.
|
|
184
|
+
|
|
185
|
+
The source tree ships four provider-free v2 manifests in
|
|
186
|
+
`server/lib/native-v2-dashboards.ts`: Customer ROI, Account Engagement,
|
|
187
|
+
Cross-sell, and Win / Loss. They intentionally contain no customer names,
|
|
188
|
+
provider ids, SQL, cached rows, or guessed joins. After deployment, an
|
|
189
|
+
organization owner or admin provisions them with `ensure-native-v2-dashboards`
|
|
190
|
+
by supplying one real Data Program per binding key. The action validates the
|
|
191
|
+
stored program output contract, shares the programs with the organization,
|
|
192
|
+
creates deterministic `native-*-v2-*` dashboard copies, and preserves the
|
|
193
|
+
extension-backed originals and any existing v2 edits. Do not add these to the
|
|
194
|
+
root demo bootstrap or silently auto-bind them to guessed provider schemas.
|
|
195
|
+
|
|
159
196
|
## When To Use An Extension Instead
|
|
160
197
|
|
|
161
198
|
Native Analytics dashboards are JSON configs rendered by the built-in dashboard
|
|
@@ -474,6 +511,7 @@ type PanelPatch = {
|
|
|
474
511
|
| "table"
|
|
475
512
|
| "pie"
|
|
476
513
|
| "section"
|
|
514
|
+
| "funnel"
|
|
477
515
|
| "heatmap"
|
|
478
516
|
| "callout"
|
|
479
517
|
| "extension";
|
|
@@ -49,7 +49,7 @@ type PanelPatch = {
|
|
|
49
49
|
title?: string;
|
|
50
50
|
sql?: string;
|
|
51
51
|
source?: "bigquery" | "ga4" | "amplitude" | "first-party" | "demo" | "prometheus" | "program";
|
|
52
|
-
chartType?: "line" | "area" | "bar" | "metric" | "table" | "pie" | "section" | "heatmap" | "callout" | "extension";
|
|
52
|
+
chartType?: "line" | "area" | "bar" | "metric" | "table" | "pie" | "funnel" | "section" | "heatmap" | "callout" | "extension";
|
|
53
53
|
width?: number;
|
|
54
54
|
columns?: number;
|
|
55
55
|
tab?: string;
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
|
|
3
|
+
import { defineAction, embedApp } from "@agent-native/core";
|
|
4
|
+
import { getDataProgram } from "@agent-native/core/data-programs";
|
|
5
|
+
import {
|
|
6
|
+
buildDeepLink,
|
|
7
|
+
getRequestOrgId,
|
|
8
|
+
getRequestUserEmail,
|
|
9
|
+
} from "@agent-native/core/server";
|
|
10
|
+
import { resolveAccess } from "@agent-native/core/sharing";
|
|
11
|
+
import setResourceVisibilityAction from "@agent-native/core/sharing/actions/set-resource-visibility";
|
|
12
|
+
import { z } from "zod";
|
|
13
|
+
|
|
14
|
+
import { getDashboard, upsertDashboard } from "../server/lib/dashboards-store";
|
|
15
|
+
import { requireAnalyticsAdminContext } from "../server/lib/db-admin-connections";
|
|
16
|
+
import {
|
|
17
|
+
compileNativeV2Dashboard,
|
|
18
|
+
getNativeV2DashboardManifest,
|
|
19
|
+
nativeV2DashboardManifests,
|
|
20
|
+
NATIVE_V2_DASHBOARD_IDS,
|
|
21
|
+
type NativeV2Binding,
|
|
22
|
+
type NativeV2BindingKey,
|
|
23
|
+
type NativeV2DashboardId,
|
|
24
|
+
} from "../server/lib/native-v2-dashboards";
|
|
25
|
+
|
|
26
|
+
const ANALYTICS_APP_ID = "analytics";
|
|
27
|
+
|
|
28
|
+
const bindingSchema = z.object({
|
|
29
|
+
programId: z.string().min(1),
|
|
30
|
+
params: z.record(z.string(), z.unknown()).optional(),
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
function orgDashboardId(
|
|
34
|
+
orgId: string,
|
|
35
|
+
templateId: NativeV2DashboardId,
|
|
36
|
+
): string {
|
|
37
|
+
const suffix = createHash("sha256").update(orgId).digest("hex").slice(0, 10);
|
|
38
|
+
return `native-${templateId}-${suffix}`;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function parseOutputColumns(value: string | null): Set<string> {
|
|
42
|
+
if (!value) return new Set();
|
|
43
|
+
try {
|
|
44
|
+
const parsed = JSON.parse(value);
|
|
45
|
+
if (!Array.isArray(parsed)) return new Set();
|
|
46
|
+
return new Set(
|
|
47
|
+
parsed.flatMap((column) =>
|
|
48
|
+
column && typeof column === "object" && typeof column.name === "string"
|
|
49
|
+
? [column.name]
|
|
50
|
+
: [],
|
|
51
|
+
),
|
|
52
|
+
);
|
|
53
|
+
} catch {
|
|
54
|
+
return new Set();
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function templateIdFromDashboard(
|
|
59
|
+
config: Record<string, unknown>,
|
|
60
|
+
): string | null {
|
|
61
|
+
const catalog = config.catalog;
|
|
62
|
+
if (!catalog || typeof catalog !== "object" || Array.isArray(catalog)) {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
const templateId = (catalog as Record<string, unknown>).templateId;
|
|
66
|
+
return typeof templateId === "string" ? templateId : null;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async function validateAndShareBindings(
|
|
70
|
+
bindings: Record<string, NativeV2Binding>,
|
|
71
|
+
requiredKeys: NativeV2BindingKey[],
|
|
72
|
+
requiredColumnsByBinding: Partial<Record<NativeV2BindingKey, string[]>>,
|
|
73
|
+
admin: { userEmail: string; orgId: string },
|
|
74
|
+
): Promise<void> {
|
|
75
|
+
const missing = requiredKeys.filter((key) => !bindings[key]?.programId);
|
|
76
|
+
if (missing.length > 0) {
|
|
77
|
+
throw new Error(`Missing required v2 bindings: ${missing.join(", ")}`);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const uniqueProgramIds = new Set(
|
|
81
|
+
requiredKeys.map((key) => bindings[key]?.programId).filter(Boolean),
|
|
82
|
+
);
|
|
83
|
+
const programsToShare: string[] = [];
|
|
84
|
+
for (const programId of uniqueProgramIds) {
|
|
85
|
+
const program = await getDataProgram(programId, ANALYTICS_APP_ID);
|
|
86
|
+
if (!program) {
|
|
87
|
+
throw new Error(`Data program not found: ${programId}`);
|
|
88
|
+
}
|
|
89
|
+
if (program.orgId && program.orgId !== admin.orgId) {
|
|
90
|
+
throw new Error(
|
|
91
|
+
`Data program belongs to another organization: ${programId}`,
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
if (program.archivedAt) {
|
|
95
|
+
throw new Error(`Data program is archived: ${programId}`);
|
|
96
|
+
}
|
|
97
|
+
const access = await resolveAccess("data_program", programId, {
|
|
98
|
+
userEmail: admin.userEmail,
|
|
99
|
+
orgId: admin.orgId,
|
|
100
|
+
});
|
|
101
|
+
if (!access) {
|
|
102
|
+
throw new Error(`No access to data program: ${programId}`);
|
|
103
|
+
}
|
|
104
|
+
const outputColumns = parseOutputColumns(program.outputColumns);
|
|
105
|
+
const missingColumns = [
|
|
106
|
+
...new Set(
|
|
107
|
+
requiredKeys.flatMap((key) =>
|
|
108
|
+
bindings[key]?.programId === programId
|
|
109
|
+
? (requiredColumnsByBinding[key] ?? [])
|
|
110
|
+
: [],
|
|
111
|
+
),
|
|
112
|
+
),
|
|
113
|
+
].filter((column) => !outputColumns.has(column));
|
|
114
|
+
if (missingColumns.length > 0) {
|
|
115
|
+
throw new Error(
|
|
116
|
+
`Data program ${programId} is missing required output columns: ${missingColumns.join(", ")}. Re-save it after a successful dry-run.`,
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (program.visibility !== "org" || program.orgId !== admin.orgId) {
|
|
121
|
+
programsToShare.push(programId);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
for (const programId of programsToShare) {
|
|
126
|
+
await setResourceVisibilityAction.run({
|
|
127
|
+
resourceType: "data_program",
|
|
128
|
+
resourceId: programId,
|
|
129
|
+
visibility: "org",
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export interface EnsureNativeV2DashboardsResult {
|
|
135
|
+
orgId: string;
|
|
136
|
+
dashboards: Array<{
|
|
137
|
+
templateId: NativeV2DashboardId;
|
|
138
|
+
dashboardId: string;
|
|
139
|
+
status: "created" | "updated" | "preserved";
|
|
140
|
+
requiredBindings: NativeV2BindingKey[];
|
|
141
|
+
}>;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export async function ensureNativeV2Dashboards(
|
|
145
|
+
args: {
|
|
146
|
+
templateIds?: NativeV2DashboardId[];
|
|
147
|
+
bindings: Record<string, NativeV2Binding>;
|
|
148
|
+
overwrite: boolean;
|
|
149
|
+
},
|
|
150
|
+
ctx?: { userEmail?: string; orgId?: string | null },
|
|
151
|
+
): Promise<EnsureNativeV2DashboardsResult> {
|
|
152
|
+
const admin = await requireAnalyticsAdminContext(ctx);
|
|
153
|
+
const templateIds = args.templateIds?.length
|
|
154
|
+
? args.templateIds
|
|
155
|
+
: [...NATIVE_V2_DASHBOARD_IDS];
|
|
156
|
+
const manifests = templateIds.map(getNativeV2DashboardManifest);
|
|
157
|
+
const allRequiredBindings = [
|
|
158
|
+
...new Set(manifests.flatMap((manifest) => manifest.requiredBindings)),
|
|
159
|
+
];
|
|
160
|
+
const requiredColumnsByBinding: Partial<
|
|
161
|
+
Record<NativeV2BindingKey, string[]>
|
|
162
|
+
> = Object.fromEntries(
|
|
163
|
+
manifests.flatMap((manifest) =>
|
|
164
|
+
manifest.panels
|
|
165
|
+
.filter(
|
|
166
|
+
(panel): panel is typeof panel & { bindingKey: NativeV2BindingKey } =>
|
|
167
|
+
Boolean(panel.bindingKey),
|
|
168
|
+
)
|
|
169
|
+
.map((panel) => [panel.bindingKey, panel.requiredColumns ?? []]),
|
|
170
|
+
),
|
|
171
|
+
);
|
|
172
|
+
|
|
173
|
+
await validateAndShareBindings(
|
|
174
|
+
args.bindings,
|
|
175
|
+
allRequiredBindings,
|
|
176
|
+
requiredColumnsByBinding,
|
|
177
|
+
admin,
|
|
178
|
+
);
|
|
179
|
+
|
|
180
|
+
const installedAt = new Date().toISOString();
|
|
181
|
+
const dashboards: EnsureNativeV2DashboardsResult["dashboards"] = [];
|
|
182
|
+
for (const manifest of manifests) {
|
|
183
|
+
const dashboardId = orgDashboardId(admin.orgId, manifest.id);
|
|
184
|
+
const existing = await getDashboard(dashboardId, {
|
|
185
|
+
email: admin.userEmail,
|
|
186
|
+
orgId: admin.orgId,
|
|
187
|
+
});
|
|
188
|
+
if (existing && templateIdFromDashboard(existing.config) !== manifest.id) {
|
|
189
|
+
throw new Error(
|
|
190
|
+
`Refusing to overwrite unrelated dashboard at deterministic id ${dashboardId}.`,
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (existing && !args.overwrite) {
|
|
195
|
+
dashboards.push({
|
|
196
|
+
templateId: manifest.id,
|
|
197
|
+
dashboardId,
|
|
198
|
+
status: "preserved",
|
|
199
|
+
requiredBindings: manifest.requiredBindings,
|
|
200
|
+
});
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const config = compileNativeV2Dashboard(
|
|
205
|
+
manifest,
|
|
206
|
+
args.bindings,
|
|
207
|
+
installedAt,
|
|
208
|
+
);
|
|
209
|
+
await upsertDashboard(
|
|
210
|
+
dashboardId,
|
|
211
|
+
"sql",
|
|
212
|
+
config as unknown as Record<string, unknown>,
|
|
213
|
+
{
|
|
214
|
+
email: admin.userEmail,
|
|
215
|
+
orgId: admin.orgId,
|
|
216
|
+
},
|
|
217
|
+
);
|
|
218
|
+
if (!existing) {
|
|
219
|
+
await setResourceVisibilityAction.run({
|
|
220
|
+
resourceType: "dashboard",
|
|
221
|
+
resourceId: dashboardId,
|
|
222
|
+
visibility: "org",
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
dashboards.push({
|
|
226
|
+
templateId: manifest.id,
|
|
227
|
+
dashboardId,
|
|
228
|
+
status: existing ? "updated" : "created",
|
|
229
|
+
requiredBindings: manifest.requiredBindings,
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
return { orgId: admin.orgId, dashboards };
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
export default defineAction({
|
|
237
|
+
description:
|
|
238
|
+
"Provision the four reusable native Analytics v2 dashboard patterns for the active organization from explicit Data Program bindings. " +
|
|
239
|
+
"This is an admin-only post-deploy action: it validates every program exists and exposes the manifest's required output columns, shares the bound programs with the organization, creates deterministic native-* dashboard copies, and never changes the legacy extension dashboards. " +
|
|
240
|
+
"Re-running preserves existing v2 dashboard edits unless overwrite=true.",
|
|
241
|
+
schema: z.object({
|
|
242
|
+
templateIds: z
|
|
243
|
+
.array(z.enum(NATIVE_V2_DASHBOARD_IDS))
|
|
244
|
+
.min(1)
|
|
245
|
+
.optional()
|
|
246
|
+
.describe("Optional subset of native v2 templates to provision."),
|
|
247
|
+
bindings: z
|
|
248
|
+
.record(z.string(), bindingSchema)
|
|
249
|
+
.describe(
|
|
250
|
+
'Binding key to stored Analytics Data Program, e.g. {"roi.kpi": {"programId": "dp_..."}}.',
|
|
251
|
+
),
|
|
252
|
+
overwrite: z
|
|
253
|
+
.boolean()
|
|
254
|
+
.optional()
|
|
255
|
+
.default(false)
|
|
256
|
+
.describe("Replace an existing v2 copy only when explicitly true."),
|
|
257
|
+
}),
|
|
258
|
+
mcpApp: {
|
|
259
|
+
compactCatalog: true,
|
|
260
|
+
resource: embedApp({
|
|
261
|
+
title: "Native Analytics v2 dashboards",
|
|
262
|
+
description: "Open the provisioned native v2 dashboard in Analytics.",
|
|
263
|
+
iframeTitle: "Agent-Native Analytics",
|
|
264
|
+
openLabel: "Open native v2 dashboard",
|
|
265
|
+
height: 680,
|
|
266
|
+
}),
|
|
267
|
+
},
|
|
268
|
+
run: async (args, ctx) => {
|
|
269
|
+
const userEmail = ctx?.userEmail ?? getRequestUserEmail();
|
|
270
|
+
const orgId = ctx?.orgId ?? getRequestOrgId() ?? null;
|
|
271
|
+
return ensureNativeV2Dashboards(
|
|
272
|
+
{
|
|
273
|
+
templateIds: args.templateIds,
|
|
274
|
+
bindings: args.bindings,
|
|
275
|
+
overwrite: args.overwrite,
|
|
276
|
+
},
|
|
277
|
+
{ userEmail, orgId },
|
|
278
|
+
);
|
|
279
|
+
},
|
|
280
|
+
link: ({ result }) => {
|
|
281
|
+
const dashboardId =
|
|
282
|
+
result && typeof result === "object"
|
|
283
|
+
? (result as EnsureNativeV2DashboardsResult).dashboards?.[0]
|
|
284
|
+
?.dashboardId
|
|
285
|
+
: null;
|
|
286
|
+
if (!dashboardId) return null;
|
|
287
|
+
return {
|
|
288
|
+
url: buildDeepLink({
|
|
289
|
+
app: "analytics",
|
|
290
|
+
view: "adhoc",
|
|
291
|
+
params: { dashboardId },
|
|
292
|
+
}),
|
|
293
|
+
label: "Open native v2 dashboard",
|
|
294
|
+
view: "adhoc",
|
|
295
|
+
};
|
|
296
|
+
},
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
export { nativeV2DashboardManifests };
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { defineAction } from "@agent-native/core";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
nativeV2DashboardManifests,
|
|
6
|
+
NATIVE_V2_DASHBOARD_VERSION,
|
|
7
|
+
} from "../server/lib/native-v2-dashboards";
|
|
8
|
+
|
|
9
|
+
export default defineAction({
|
|
10
|
+
description:
|
|
11
|
+
"List the source-controlled native Analytics v2 dashboard manifests, including required Data Program binding keys and output columns. These provider-free manifests are compiled into org dashboards by ensure-native-v2-dashboards.",
|
|
12
|
+
schema: z.object({}),
|
|
13
|
+
http: { method: "GET" },
|
|
14
|
+
readOnly: true,
|
|
15
|
+
publicAgent: { expose: true, readOnly: true, requiresAuth: true },
|
|
16
|
+
run: async () => ({
|
|
17
|
+
version: NATIVE_V2_DASHBOARD_VERSION,
|
|
18
|
+
templates: nativeV2DashboardManifests.map((manifest) => ({
|
|
19
|
+
id: manifest.id,
|
|
20
|
+
name: manifest.name,
|
|
21
|
+
description: manifest.description,
|
|
22
|
+
category: manifest.category,
|
|
23
|
+
tags: manifest.tags,
|
|
24
|
+
requiredBindings: manifest.requiredBindings,
|
|
25
|
+
panels: manifest.panels.map((panel) => ({
|
|
26
|
+
id: panel.id,
|
|
27
|
+
title: panel.title,
|
|
28
|
+
chartType: panel.chartType,
|
|
29
|
+
bindingKey: panel.bindingKey ?? null,
|
|
30
|
+
requiredColumns: panel.requiredColumns ?? [],
|
|
31
|
+
})),
|
|
32
|
+
})),
|
|
33
|
+
}),
|
|
34
|
+
});
|