@agent-native/core 0.131.7 → 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 +123 -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 +38 -34
- package/corpus/core/src/audit/redact.ts +104 -18
- package/corpus/core/src/cli/create.ts +115 -16
- package/corpus/core/src/client/AgentPanel.tsx +2 -7
- package/corpus/core/src/client/AssistantChat.tsx +10 -2
- 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/frame-protocol.ts +5 -1
- 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/core/src/workspace-files/tool.ts +18 -11
- 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/clips/actions/generate-workflow.ts +6 -4
- package/corpus/templates/clips/actions/reconcile-workflow-generation.ts +257 -0
- package/corpus/templates/clips/app/hooks/use-auto-title.ts +166 -4
- package/corpus/templates/clips/app/routes/r.$recordingId.tsx +1 -1
- package/corpus/templates/clips/changelog/2026-07-27-stopped-agent-runs-no-longer-leave-generated-workflow-cards-.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-30-workflow-generation-now-stops-retrying-after-repeated-failur.md +6 -0
- package/corpus/templates/clips/shared/workflow.ts +5 -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 +37 -40
- 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/cli/create.d.ts +8 -1
- package/dist/cli/create.d.ts.map +1 -1
- package/dist/cli/create.js +98 -16
- package/dist/cli/create.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/AssistantChat.d.ts.map +1 -1
- package/dist/client/AssistantChat.js +10 -2
- package/dist/client/AssistantChat.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/frame-protocol.d.ts +1 -0
- package/dist/client/frame-protocol.d.ts.map +1 -1
- package/dist/client/frame-protocol.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/collab/routes.d.ts +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/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/realtime-token.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/dist/workspace-files/tool.d.ts.map +1 -1
- package/dist/workspace-files/tool.js +18 -11
- package/dist/workspace-files/tool.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 +3 -3
- 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 +38 -34
- package/src/audit/redact.ts +104 -18
- package/src/cli/create.ts +115 -16
- package/src/client/AgentPanel.tsx +2 -7
- package/src/client/AssistantChat.tsx +10 -2
- 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/frame-protocol.ts +5 -1
- 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
- package/src/workspace-files/tool.ts +18 -11
|
@@ -0,0 +1,457 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
SqlDashboardConfig,
|
|
3
|
+
SqlPanel,
|
|
4
|
+
} from "../../app/pages/adhoc/sql-dashboard/types";
|
|
5
|
+
|
|
6
|
+
export const NATIVE_V2_DASHBOARD_VERSION = "2026-07-30-v1";
|
|
7
|
+
|
|
8
|
+
export const NATIVE_V2_DASHBOARD_IDS = [
|
|
9
|
+
"customer-roi-v2",
|
|
10
|
+
"account-engagement-v2",
|
|
11
|
+
"cross-sell-v2",
|
|
12
|
+
"win-loss-v2",
|
|
13
|
+
] as const;
|
|
14
|
+
|
|
15
|
+
export type NativeV2DashboardId = (typeof NATIVE_V2_DASHBOARD_IDS)[number];
|
|
16
|
+
|
|
17
|
+
export type NativeV2BindingKey =
|
|
18
|
+
| "roi.kpi"
|
|
19
|
+
| "roi.trend"
|
|
20
|
+
| "roi.detail"
|
|
21
|
+
| "engagement.kpi"
|
|
22
|
+
| "engagement.trend"
|
|
23
|
+
| "engagement.heatmap"
|
|
24
|
+
| "engagement.alerts"
|
|
25
|
+
| "cross_sell.funnel"
|
|
26
|
+
| "cross_sell.kpi"
|
|
27
|
+
| "cross_sell.pipeline"
|
|
28
|
+
| "cross_sell.trend"
|
|
29
|
+
| "win_loss.summary"
|
|
30
|
+
| "win_loss.reasons"
|
|
31
|
+
| "win_loss.evidence"
|
|
32
|
+
| "win_loss.alerts";
|
|
33
|
+
|
|
34
|
+
export interface NativeV2Binding {
|
|
35
|
+
programId: string;
|
|
36
|
+
params?: Record<string, unknown>;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface NativeV2PanelManifest {
|
|
40
|
+
id: string;
|
|
41
|
+
title: string;
|
|
42
|
+
bindingKey?: NativeV2BindingKey;
|
|
43
|
+
chartType: SqlPanel["chartType"];
|
|
44
|
+
width: number;
|
|
45
|
+
columns?: number;
|
|
46
|
+
tab?: string;
|
|
47
|
+
config?: SqlPanel["config"];
|
|
48
|
+
requiredColumns?: string[];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface NativeV2DashboardManifest {
|
|
52
|
+
id: NativeV2DashboardId;
|
|
53
|
+
name: string;
|
|
54
|
+
description: string;
|
|
55
|
+
category: "Acquisition" | "Product" | "Operations";
|
|
56
|
+
tags: string[];
|
|
57
|
+
requiredBindings: NativeV2BindingKey[];
|
|
58
|
+
panels: NativeV2PanelManifest[];
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const section = (
|
|
62
|
+
id: string,
|
|
63
|
+
title: string,
|
|
64
|
+
tab: string,
|
|
65
|
+
columns = 6,
|
|
66
|
+
): NativeV2PanelManifest => ({
|
|
67
|
+
id,
|
|
68
|
+
title,
|
|
69
|
+
chartType: "section",
|
|
70
|
+
width: 1,
|
|
71
|
+
columns,
|
|
72
|
+
tab,
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
const boundPanel = (
|
|
76
|
+
panel: Omit<NativeV2PanelManifest, "bindingKey"> & {
|
|
77
|
+
bindingKey: NativeV2BindingKey;
|
|
78
|
+
requiredColumns: string[];
|
|
79
|
+
},
|
|
80
|
+
): NativeV2PanelManifest => panel;
|
|
81
|
+
|
|
82
|
+
export const nativeV2DashboardManifests: readonly NativeV2DashboardManifest[] =
|
|
83
|
+
[
|
|
84
|
+
{
|
|
85
|
+
id: "customer-roi-v2",
|
|
86
|
+
name: "Customer ROI v2",
|
|
87
|
+
description:
|
|
88
|
+
"Native value-realization dashboard: KPI, period trend, account detail, and evidence callouts.",
|
|
89
|
+
category: "Acquisition",
|
|
90
|
+
tags: ["native", "v2", "roi", "value-realization", "customer"],
|
|
91
|
+
requiredBindings: ["roi.kpi", "roi.trend", "roi.detail"],
|
|
92
|
+
panels: [
|
|
93
|
+
section("roi-overview", "Value realization", "Overview"),
|
|
94
|
+
boundPanel({
|
|
95
|
+
id: "roi-kpi",
|
|
96
|
+
title: "Realized value",
|
|
97
|
+
bindingKey: "roi.kpi",
|
|
98
|
+
chartType: "metric",
|
|
99
|
+
width: 1,
|
|
100
|
+
tab: "Overview",
|
|
101
|
+
config: {
|
|
102
|
+
yKey: "value",
|
|
103
|
+
yFormatter: "currency",
|
|
104
|
+
description: "The bound program's current realized-value KPI.",
|
|
105
|
+
},
|
|
106
|
+
requiredColumns: ["value"],
|
|
107
|
+
}),
|
|
108
|
+
boundPanel({
|
|
109
|
+
id: "roi-trend",
|
|
110
|
+
title: "Value over time",
|
|
111
|
+
bindingKey: "roi.trend",
|
|
112
|
+
chartType: "line",
|
|
113
|
+
width: 3,
|
|
114
|
+
tab: "Overview",
|
|
115
|
+
config: {
|
|
116
|
+
xKey: "period",
|
|
117
|
+
yKey: "value",
|
|
118
|
+
yFormatter: "currency",
|
|
119
|
+
legend: false,
|
|
120
|
+
},
|
|
121
|
+
requiredColumns: ["period", "value"],
|
|
122
|
+
}),
|
|
123
|
+
boundPanel({
|
|
124
|
+
id: "roi-detail",
|
|
125
|
+
title: "Account-level value evidence",
|
|
126
|
+
bindingKey: "roi.detail",
|
|
127
|
+
chartType: "table",
|
|
128
|
+
width: 3,
|
|
129
|
+
tab: "Accounts",
|
|
130
|
+
config: {
|
|
131
|
+
columns: [
|
|
132
|
+
{ key: "account", label: "Account" },
|
|
133
|
+
{ key: "period", label: "Period", format: "date" },
|
|
134
|
+
{ key: "investment", label: "Investment", format: "currency" },
|
|
135
|
+
{ key: "value", label: "Value", format: "currency" },
|
|
136
|
+
{ key: "roi", label: "ROI", format: "percent" },
|
|
137
|
+
{ key: "matched_by", label: "Matched by" },
|
|
138
|
+
{ key: "match_quality", label: "Match quality" },
|
|
139
|
+
],
|
|
140
|
+
limit: 100,
|
|
141
|
+
},
|
|
142
|
+
requiredColumns: [
|
|
143
|
+
"account",
|
|
144
|
+
"period",
|
|
145
|
+
"investment",
|
|
146
|
+
"value",
|
|
147
|
+
"roi",
|
|
148
|
+
"matched_by",
|
|
149
|
+
"match_quality",
|
|
150
|
+
],
|
|
151
|
+
}),
|
|
152
|
+
],
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
id: "account-engagement-v2",
|
|
156
|
+
name: "Account Engagement v2",
|
|
157
|
+
description:
|
|
158
|
+
"Native engagement coverage dashboard: headline coverage, daily trend, segment heatmap, and alerts.",
|
|
159
|
+
category: "Product",
|
|
160
|
+
tags: ["native", "v2", "engagement", "adoption", "customer"],
|
|
161
|
+
requiredBindings: [
|
|
162
|
+
"engagement.kpi",
|
|
163
|
+
"engagement.trend",
|
|
164
|
+
"engagement.heatmap",
|
|
165
|
+
"engagement.alerts",
|
|
166
|
+
],
|
|
167
|
+
panels: [
|
|
168
|
+
section("engagement-overview", "Account engagement", "Overview"),
|
|
169
|
+
boundPanel({
|
|
170
|
+
id: "engagement-kpi",
|
|
171
|
+
title: "Engaged accounts",
|
|
172
|
+
bindingKey: "engagement.kpi",
|
|
173
|
+
chartType: "metric",
|
|
174
|
+
width: 1,
|
|
175
|
+
tab: "Overview",
|
|
176
|
+
config: {
|
|
177
|
+
yKey: "value",
|
|
178
|
+
description: "The bound program's current engaged-account count.",
|
|
179
|
+
},
|
|
180
|
+
requiredColumns: ["value"],
|
|
181
|
+
}),
|
|
182
|
+
boundPanel({
|
|
183
|
+
id: "engagement-trend",
|
|
184
|
+
title: "Engagement over time",
|
|
185
|
+
bindingKey: "engagement.trend",
|
|
186
|
+
chartType: "line",
|
|
187
|
+
width: 3,
|
|
188
|
+
tab: "Overview",
|
|
189
|
+
config: {
|
|
190
|
+
xKey: "period",
|
|
191
|
+
yKey: "value",
|
|
192
|
+
legend: false,
|
|
193
|
+
},
|
|
194
|
+
requiredColumns: ["period", "value"],
|
|
195
|
+
}),
|
|
196
|
+
boundPanel({
|
|
197
|
+
id: "engagement-heatmap",
|
|
198
|
+
title: "Engagement by segment",
|
|
199
|
+
bindingKey: "engagement.heatmap",
|
|
200
|
+
chartType: "heatmap",
|
|
201
|
+
width: 3,
|
|
202
|
+
tab: "Segments",
|
|
203
|
+
config: {
|
|
204
|
+
xKey: "period",
|
|
205
|
+
yKey: "value",
|
|
206
|
+
color: "segment",
|
|
207
|
+
yFormatter: "percent",
|
|
208
|
+
},
|
|
209
|
+
requiredColumns: ["period", "segment", "value"],
|
|
210
|
+
}),
|
|
211
|
+
boundPanel({
|
|
212
|
+
id: "engagement-alerts",
|
|
213
|
+
title: "Engagement alerts",
|
|
214
|
+
bindingKey: "engagement.alerts",
|
|
215
|
+
chartType: "callout",
|
|
216
|
+
width: 3,
|
|
217
|
+
tab: "Alerts",
|
|
218
|
+
requiredColumns: ["severity", "message"],
|
|
219
|
+
}),
|
|
220
|
+
],
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
id: "cross-sell-v2",
|
|
224
|
+
name: "Cross-sell v2",
|
|
225
|
+
description:
|
|
226
|
+
"Native cross-sell dashboard: ordered opportunity funnel, pipeline value, account table, and trend.",
|
|
227
|
+
category: "Acquisition",
|
|
228
|
+
tags: ["native", "v2", "cross-sell", "pipeline", "gtm"],
|
|
229
|
+
requiredBindings: [
|
|
230
|
+
"cross_sell.funnel",
|
|
231
|
+
"cross_sell.kpi",
|
|
232
|
+
"cross_sell.pipeline",
|
|
233
|
+
"cross_sell.trend",
|
|
234
|
+
],
|
|
235
|
+
panels: [
|
|
236
|
+
section("cross-sell-overview", "Cross-sell pipeline", "Overview"),
|
|
237
|
+
boundPanel({
|
|
238
|
+
id: "cross-sell-funnel",
|
|
239
|
+
title: "Opportunity funnel",
|
|
240
|
+
bindingKey: "cross_sell.funnel",
|
|
241
|
+
chartType: "funnel",
|
|
242
|
+
width: 3,
|
|
243
|
+
tab: "Overview",
|
|
244
|
+
config: {
|
|
245
|
+
xKey: "stage",
|
|
246
|
+
yKey: "value",
|
|
247
|
+
yFormatter: "currency",
|
|
248
|
+
},
|
|
249
|
+
requiredColumns: ["stage", "value"],
|
|
250
|
+
}),
|
|
251
|
+
boundPanel({
|
|
252
|
+
id: "cross-sell-kpi",
|
|
253
|
+
title: "Pipeline value",
|
|
254
|
+
bindingKey: "cross_sell.kpi",
|
|
255
|
+
chartType: "metric",
|
|
256
|
+
width: 1,
|
|
257
|
+
tab: "Overview",
|
|
258
|
+
config: {
|
|
259
|
+
yKey: "value",
|
|
260
|
+
yFormatter: "currency",
|
|
261
|
+
},
|
|
262
|
+
requiredColumns: ["value"],
|
|
263
|
+
}),
|
|
264
|
+
boundPanel({
|
|
265
|
+
id: "cross-sell-pipeline",
|
|
266
|
+
title: "Account pipeline",
|
|
267
|
+
bindingKey: "cross_sell.pipeline",
|
|
268
|
+
chartType: "table",
|
|
269
|
+
width: 3,
|
|
270
|
+
tab: "Accounts",
|
|
271
|
+
config: {
|
|
272
|
+
columns: [
|
|
273
|
+
{ key: "account", label: "Account" },
|
|
274
|
+
{ key: "stage", label: "Stage" },
|
|
275
|
+
{ key: "value", label: "Value", format: "currency" },
|
|
276
|
+
{ key: "owner", label: "Owner" },
|
|
277
|
+
{ key: "matched_by", label: "Matched by" },
|
|
278
|
+
{ key: "match_quality", label: "Match quality" },
|
|
279
|
+
],
|
|
280
|
+
limit: 100,
|
|
281
|
+
},
|
|
282
|
+
requiredColumns: [
|
|
283
|
+
"account",
|
|
284
|
+
"stage",
|
|
285
|
+
"value",
|
|
286
|
+
"owner",
|
|
287
|
+
"matched_by",
|
|
288
|
+
"match_quality",
|
|
289
|
+
],
|
|
290
|
+
}),
|
|
291
|
+
boundPanel({
|
|
292
|
+
id: "cross-sell-trend",
|
|
293
|
+
title: "Pipeline over time",
|
|
294
|
+
bindingKey: "cross_sell.trend",
|
|
295
|
+
chartType: "area",
|
|
296
|
+
width: 3,
|
|
297
|
+
tab: "Trend",
|
|
298
|
+
config: {
|
|
299
|
+
xKey: "period",
|
|
300
|
+
yKey: "value",
|
|
301
|
+
yFormatter: "currency",
|
|
302
|
+
legend: false,
|
|
303
|
+
},
|
|
304
|
+
requiredColumns: ["period", "value"],
|
|
305
|
+
}),
|
|
306
|
+
],
|
|
307
|
+
},
|
|
308
|
+
{
|
|
309
|
+
id: "win-loss-v2",
|
|
310
|
+
name: "Win / Loss v2",
|
|
311
|
+
description:
|
|
312
|
+
"Native outcome dashboard: win/loss totals, reason breakdown, evidence table, and follow-up alerts.",
|
|
313
|
+
category: "Operations",
|
|
314
|
+
tags: ["native", "v2", "win-loss", "sales", "evidence"],
|
|
315
|
+
requiredBindings: [
|
|
316
|
+
"win_loss.summary",
|
|
317
|
+
"win_loss.reasons",
|
|
318
|
+
"win_loss.evidence",
|
|
319
|
+
"win_loss.alerts",
|
|
320
|
+
],
|
|
321
|
+
panels: [
|
|
322
|
+
section("win-loss-overview", "Win / loss outcomes", "Overview"),
|
|
323
|
+
boundPanel({
|
|
324
|
+
id: "win-loss-summary",
|
|
325
|
+
title: "Outcome totals",
|
|
326
|
+
bindingKey: "win_loss.summary",
|
|
327
|
+
chartType: "bar",
|
|
328
|
+
width: 3,
|
|
329
|
+
tab: "Overview",
|
|
330
|
+
config: {
|
|
331
|
+
xKey: "outcome",
|
|
332
|
+
yKey: "value",
|
|
333
|
+
yFormatter: "currency",
|
|
334
|
+
legend: false,
|
|
335
|
+
},
|
|
336
|
+
requiredColumns: ["outcome", "value"],
|
|
337
|
+
}),
|
|
338
|
+
boundPanel({
|
|
339
|
+
id: "win-loss-reasons",
|
|
340
|
+
title: "Reasons",
|
|
341
|
+
bindingKey: "win_loss.reasons",
|
|
342
|
+
chartType: "bar",
|
|
343
|
+
width: 3,
|
|
344
|
+
tab: "Reasons",
|
|
345
|
+
config: {
|
|
346
|
+
xKey: "reason",
|
|
347
|
+
yKey: "value",
|
|
348
|
+
legend: false,
|
|
349
|
+
},
|
|
350
|
+
requiredColumns: ["reason", "value"],
|
|
351
|
+
}),
|
|
352
|
+
boundPanel({
|
|
353
|
+
id: "win-loss-evidence",
|
|
354
|
+
title: "Outcome evidence",
|
|
355
|
+
bindingKey: "win_loss.evidence",
|
|
356
|
+
chartType: "table",
|
|
357
|
+
width: 3,
|
|
358
|
+
tab: "Evidence",
|
|
359
|
+
config: {
|
|
360
|
+
columns: [
|
|
361
|
+
{ key: "account", label: "Account" },
|
|
362
|
+
{ key: "outcome", label: "Outcome" },
|
|
363
|
+
{ key: "reason", label: "Reason" },
|
|
364
|
+
{ key: "value", label: "Value", format: "currency" },
|
|
365
|
+
{ key: "evidence", label: "Evidence" },
|
|
366
|
+
{ key: "match_quality", label: "Match quality" },
|
|
367
|
+
],
|
|
368
|
+
limit: 100,
|
|
369
|
+
},
|
|
370
|
+
requiredColumns: [
|
|
371
|
+
"account",
|
|
372
|
+
"outcome",
|
|
373
|
+
"reason",
|
|
374
|
+
"value",
|
|
375
|
+
"evidence",
|
|
376
|
+
"match_quality",
|
|
377
|
+
],
|
|
378
|
+
}),
|
|
379
|
+
boundPanel({
|
|
380
|
+
id: "win-loss-alerts",
|
|
381
|
+
title: "Follow-up alerts",
|
|
382
|
+
bindingKey: "win_loss.alerts",
|
|
383
|
+
chartType: "callout",
|
|
384
|
+
width: 3,
|
|
385
|
+
tab: "Alerts",
|
|
386
|
+
requiredColumns: ["severity", "message"],
|
|
387
|
+
}),
|
|
388
|
+
],
|
|
389
|
+
},
|
|
390
|
+
] as const;
|
|
391
|
+
|
|
392
|
+
export function getNativeV2DashboardManifest(
|
|
393
|
+
id: NativeV2DashboardId,
|
|
394
|
+
): NativeV2DashboardManifest {
|
|
395
|
+
const manifest = nativeV2DashboardManifests.find((entry) => entry.id === id);
|
|
396
|
+
if (!manifest) throw new Error(`Unknown native v2 dashboard: ${id}`);
|
|
397
|
+
return manifest;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
export function compileNativeV2Dashboard(
|
|
401
|
+
manifest: NativeV2DashboardManifest,
|
|
402
|
+
bindings: Partial<Record<NativeV2BindingKey, NativeV2Binding>>,
|
|
403
|
+
installedAt: string,
|
|
404
|
+
): SqlDashboardConfig {
|
|
405
|
+
const missing = manifest.requiredBindings.filter(
|
|
406
|
+
(bindingKey) => !bindings[bindingKey]?.programId.trim(),
|
|
407
|
+
);
|
|
408
|
+
if (missing.length > 0) {
|
|
409
|
+
throw new Error(
|
|
410
|
+
`${manifest.id} is missing required bindings: ${missing.join(", ")}`,
|
|
411
|
+
);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
return {
|
|
415
|
+
name: manifest.name,
|
|
416
|
+
description: manifest.description,
|
|
417
|
+
columns: 6,
|
|
418
|
+
catalog: {
|
|
419
|
+
templateId: manifest.id,
|
|
420
|
+
templateVersion: NATIVE_V2_DASHBOARD_VERSION,
|
|
421
|
+
installedAt,
|
|
422
|
+
},
|
|
423
|
+
panels: manifest.panels.map((panel) => {
|
|
424
|
+
if (!panel.bindingKey) {
|
|
425
|
+
return {
|
|
426
|
+
id: panel.id,
|
|
427
|
+
title: panel.title,
|
|
428
|
+
source: "first-party",
|
|
429
|
+
chartType: panel.chartType,
|
|
430
|
+
width: panel.width,
|
|
431
|
+
...(panel.columns ? { columns: panel.columns } : {}),
|
|
432
|
+
...(panel.tab ? { tab: panel.tab } : {}),
|
|
433
|
+
...(panel.config ? { config: panel.config } : {}),
|
|
434
|
+
sql: "",
|
|
435
|
+
} satisfies SqlPanel;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
const binding = bindings[panel.bindingKey];
|
|
439
|
+
if (!binding) {
|
|
440
|
+
throw new Error(`Missing binding: ${panel.bindingKey}`);
|
|
441
|
+
}
|
|
442
|
+
return {
|
|
443
|
+
id: panel.id,
|
|
444
|
+
title: panel.title,
|
|
445
|
+
source: "program",
|
|
446
|
+
chartType: panel.chartType,
|
|
447
|
+
width: panel.width,
|
|
448
|
+
...(panel.tab ? { tab: panel.tab } : {}),
|
|
449
|
+
...(panel.config ? { config: panel.config } : {}),
|
|
450
|
+
sql: JSON.stringify({
|
|
451
|
+
programId: binding.programId,
|
|
452
|
+
...(binding.params ? { params: binding.params } : {}),
|
|
453
|
+
}),
|
|
454
|
+
} satisfies SqlPanel;
|
|
455
|
+
}),
|
|
456
|
+
};
|
|
457
|
+
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { OG_FONT_FAMILY } from "@agent-native/core/server";
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
import type { DashboardFunnelItem } from "../../shared/dashboard-funnel";
|
|
4
|
+
|
|
5
|
+
export type ReportChartType = "bar" | "line" | "area" | "pie" | "funnel";
|
|
4
6
|
|
|
5
7
|
export type ReportChartAxis = "left" | "right";
|
|
6
8
|
|
|
@@ -784,6 +786,75 @@ function renderPieChartSvg({
|
|
|
784
786
|
</svg>`;
|
|
785
787
|
}
|
|
786
788
|
|
|
789
|
+
export function renderFunnelChartSvg({
|
|
790
|
+
title,
|
|
791
|
+
subtitle,
|
|
792
|
+
rows,
|
|
793
|
+
width,
|
|
794
|
+
height,
|
|
795
|
+
formatter,
|
|
796
|
+
colors,
|
|
797
|
+
}: {
|
|
798
|
+
title?: string;
|
|
799
|
+
subtitle?: string;
|
|
800
|
+
rows: DashboardFunnelItem[];
|
|
801
|
+
width: number;
|
|
802
|
+
height: number;
|
|
803
|
+
formatter?: ReportChartValueFormatter;
|
|
804
|
+
colors?: string[];
|
|
805
|
+
}): string {
|
|
806
|
+
const theme = CHART_THEMES.light;
|
|
807
|
+
const palette = CHART_PALETTES.light;
|
|
808
|
+
const safeWidth = clampSize(width, 360, 2000);
|
|
809
|
+
const safeHeight = clampSize(height, 240, 1200);
|
|
810
|
+
const header = renderChartHeader({
|
|
811
|
+
title: title ?? "",
|
|
812
|
+
subtitle: subtitle ?? "",
|
|
813
|
+
width: safeWidth,
|
|
814
|
+
theme,
|
|
815
|
+
fontFamily: REPORT_CHART_FONT_FAMILY,
|
|
816
|
+
fit: true,
|
|
817
|
+
});
|
|
818
|
+
const top = header.headerBottom + 30;
|
|
819
|
+
const bottom = safeHeight - 24;
|
|
820
|
+
const labelWidth = Math.min(190, Math.max(120, safeWidth * 0.2));
|
|
821
|
+
const valueWidth = 150;
|
|
822
|
+
const barLeft = labelWidth;
|
|
823
|
+
const barRight = safeWidth - valueWidth;
|
|
824
|
+
const barWidth = Math.max(1, barRight - barLeft);
|
|
825
|
+
const rowHeight = 31;
|
|
826
|
+
const maxRows = Math.max(1, Math.floor((bottom - top) / rowHeight));
|
|
827
|
+
const visible = rows.slice(0, maxRows);
|
|
828
|
+
const hidden = rows.length - visible.length;
|
|
829
|
+
const maxValue = Math.max(...rows.map((row) => row.value), 1);
|
|
830
|
+
const bars = visible
|
|
831
|
+
.map((row, index) => {
|
|
832
|
+
const y = top + index * rowHeight;
|
|
833
|
+
const fillWidth = row.value > 0 ? (row.value / maxValue) * barWidth : 0;
|
|
834
|
+
const color = safeColor(colors?.[index], palette[index % palette.length]);
|
|
835
|
+
const dropOff =
|
|
836
|
+
row.dropOffPercent === null
|
|
837
|
+
? ""
|
|
838
|
+
: ` ${row.dropOffPercent < 0 ? "↑" : "↓"}${Math.abs(row.dropOffPercent).toFixed(1)}%`;
|
|
839
|
+
return `<text x="${(labelWidth - 12).toFixed(1)}" y="${(y + 20).toFixed(1)}" text-anchor="end" font-size="12" fill="${theme.labelColor}">${escapeXml(truncate(row.label, 24))}</text><rect x="${barLeft.toFixed(1)}" y="${(y + 7).toFixed(1)}" width="${barWidth.toFixed(1)}" height="16" rx="4" fill="${theme.gridColor}"/><rect x="${barLeft.toFixed(1)}" y="${(y + 7).toFixed(1)}" width="${fillWidth.toFixed(1)}" height="16" rx="4" fill="${color}"/><text x="${(barRight + 12).toFixed(1)}" y="${(y + 19).toFixed(1)}" font-size="12" fill="${theme.labelColor}">${escapeXml(`${formatTick(row.value, formatter)} ${row.percentOfFirst.toFixed(1)}%${dropOff}`)}</text>`;
|
|
840
|
+
})
|
|
841
|
+
.join("");
|
|
842
|
+
const overflow =
|
|
843
|
+
hidden > 0
|
|
844
|
+
? `<text x="${barLeft.toFixed(1)}" y="${(top + visible.length * rowHeight + 18).toFixed(1)}" font-size="12" fill="${theme.tickColor}">${escapeXml(`+${hidden} more stages`)}</text>`
|
|
845
|
+
: "";
|
|
846
|
+
|
|
847
|
+
return `<svg xmlns="http://www.w3.org/2000/svg" width="${safeWidth}" height="${safeHeight}" viewBox="0 0 ${safeWidth} ${safeHeight}" font-family="${REPORT_CHART_FONT_FAMILY}" role="img" aria-label="${escapeXml(title ?? "Funnel chart")}">
|
|
848
|
+
<rect width="${safeWidth}" height="${safeHeight}" rx="8" fill="${theme.background}"/>
|
|
849
|
+
${header.titleMarkup}
|
|
850
|
+
${header.subtitleMarkup}
|
|
851
|
+
<g font-family="${REPORT_CHART_FONT_FAMILY}">
|
|
852
|
+
${bars}
|
|
853
|
+
${overflow}
|
|
854
|
+
</g>
|
|
855
|
+
</svg>`;
|
|
856
|
+
}
|
|
857
|
+
|
|
787
858
|
/**
|
|
788
859
|
* Browser-facing renderer: clamps every value to a non-negative number and uses
|
|
789
860
|
* a system font stack. Kept for `generate-chart`'s saved-artifact fallback; the
|
|
@@ -867,7 +938,7 @@ export function renderReportChartSvg({
|
|
|
867
938
|
subtitle?: string;
|
|
868
939
|
labels: string[];
|
|
869
940
|
series: ReportChartSeries[];
|
|
870
|
-
type: ReportChartType
|
|
941
|
+
type: Exclude<ReportChartType, "funnel">;
|
|
871
942
|
width: number;
|
|
872
943
|
height: number;
|
|
873
944
|
stacked?: boolean;
|
|
@@ -58,7 +58,7 @@ export const NON_ANALYTICS_REQUEST_GUIDANCE =
|
|
|
58
58
|
export const ANALYTICS_CUSTOM_BLOCK_GUIDANCE =
|
|
59
59
|
"<analytics-artifact-guidance>\n" +
|
|
60
60
|
"Analytics has one user-facing artifact type: dashboards. Build with native dashboard panels and Data Programs first. A sandboxed extension embedded in a dashboard is presented to users as a Custom Block, not as a separate Analytics artifact. " +
|
|
61
|
-
"Use native chart, table, metric, section, filter, and layout capabilities whenever they can represent the request faithfully. Use a Data Program when the durable need is reusable fetching, transformation, or computed data that native panels can render. Do not create a Custom Block merely because a request says custom, asks for a dashboard, or would take more effort with native components. " +
|
|
61
|
+
"Use native chart, table, metric, section, funnel, heatmap, callout, filter, and layout capabilities whenever they can represent the request faithfully. Reusable ROI, engagement, cross-sell, and win/loss dashboards should compose these native panels around real SQL or Data Program results. Use a Data Program when the durable need is reusable fetching, transformation, or computed data that native panels can render. Do not create a Custom Block merely because a request says custom, asks for a dashboard, or would take more effort with native components. " +
|
|
62
62
|
'Create a Custom Block only when the user explicitly asks for a genuinely bespoke or one-off visualization or interaction, the native dashboard model cannot represent it faithfully, and its intended scope is this dashboard. Create it with `create-extension`, immediately embed it as a `chartType: "extension"` panel with `config.extensionId`, and set `config.customBlock` to `{ authoredBy: "agent", intent: "one-off", scope: "dashboard", nativeGapReason: "custom-visualization" | "custom-interaction" | "custom-layout" | "other" }`. Choose the narrow categorical reason; never put prompt text, customer data, or other free text in this metadata. Use the host theme CSS variables and match the dashboard typography, card spacing, and density so the sandboxed content reads as an agent-authored patch to Analytics instead of a foreign mini-app. Describe it as a sandboxed, agent-authored dashboard patch. Never leave it standalone or direct the user to an Extensions page. ' +
|
|
63
63
|
"A Custom Block is a fast runtime patch, not the durable destination for reusable product behavior. If the request should work across dashboards or users, changes app chrome or business logic, adds a reusable chart type, needs native accessibility/export/governance, or explicitly asks for app code, a PR, or a native feature, call `connect-builder` with the request verbatim instead of creating a Custom Block. If scope is ambiguous, ask whether the user wants a one-off block for this dashboard or a reusable app feature before choosing. " +
|
|
64
64
|
"When the user chooses Promote to app code, preserve the existing Custom Block and pass its dashboard id, panel id, extension id, and requested native placement through `connect-builder`; do not delete or replace the block until the native implementation is reviewed and deployed. Legacy analyses and existing extension-backed dashboards remain readable and editable for compatibility.\n" +
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
export interface DashboardFunnelItem {
|
|
2
|
+
label: string;
|
|
3
|
+
value: number;
|
|
4
|
+
percentOfFirst: number;
|
|
5
|
+
dropOffPercent: number | null;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface DashboardFunnelRows {
|
|
9
|
+
labelKey: string;
|
|
10
|
+
valueKey: string;
|
|
11
|
+
items: DashboardFunnelItem[];
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function finiteNumber(value: unknown): number | null {
|
|
15
|
+
if (typeof value === "number") {
|
|
16
|
+
return Number.isFinite(value) ? value : null;
|
|
17
|
+
}
|
|
18
|
+
if (typeof value !== "string" || value.trim() === "") return null;
|
|
19
|
+
const parsed = Number(value);
|
|
20
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function hasNumericValue(
|
|
24
|
+
rows: Array<Record<string, unknown>>,
|
|
25
|
+
key: string,
|
|
26
|
+
): boolean {
|
|
27
|
+
return rows.some((row) => {
|
|
28
|
+
const value = finiteNumber(row[key]);
|
|
29
|
+
return value !== null && value >= 0;
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function resolveDashboardFunnelRows(
|
|
34
|
+
rows: Array<Record<string, unknown>>,
|
|
35
|
+
configuredLabelKey?: string,
|
|
36
|
+
configuredValueKey?: string,
|
|
37
|
+
): DashboardFunnelRows {
|
|
38
|
+
if (rows.length === 0) {
|
|
39
|
+
return { labelKey: "", valueKey: "", items: [] };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const columns = Object.keys(rows[0]);
|
|
43
|
+
const columnSet = new Set(columns);
|
|
44
|
+
const labelKey =
|
|
45
|
+
(configuredLabelKey && columnSet.has(configuredLabelKey)
|
|
46
|
+
? configuredLabelKey
|
|
47
|
+
: undefined) ||
|
|
48
|
+
columns.find((key) =>
|
|
49
|
+
rows.some((row) => typeof row[key] === "string" && row[key].trim()),
|
|
50
|
+
) ||
|
|
51
|
+
columns[0] ||
|
|
52
|
+
"";
|
|
53
|
+
const valueKey =
|
|
54
|
+
(configuredValueKey && columnSet.has(configuredValueKey)
|
|
55
|
+
? configuredValueKey
|
|
56
|
+
: undefined) ||
|
|
57
|
+
columns.find((key) => key !== labelKey && hasNumericValue(rows, key)) ||
|
|
58
|
+
"";
|
|
59
|
+
|
|
60
|
+
const parsed = rows.flatMap((row) => {
|
|
61
|
+
const label = String(row[labelKey] ?? "").trim();
|
|
62
|
+
const value = finiteNumber(row[valueKey]);
|
|
63
|
+
if (!label || value === null || value < 0) return [];
|
|
64
|
+
return [{ label, value }];
|
|
65
|
+
});
|
|
66
|
+
const firstValue = parsed[0]?.value ?? 0;
|
|
67
|
+
|
|
68
|
+
return {
|
|
69
|
+
labelKey,
|
|
70
|
+
valueKey,
|
|
71
|
+
items: parsed.map((item, index) => {
|
|
72
|
+
const previousValue = parsed[index - 1]?.value;
|
|
73
|
+
return {
|
|
74
|
+
...item,
|
|
75
|
+
percentOfFirst: firstValue > 0 ? (item.value / firstValue) * 100 : 0,
|
|
76
|
+
dropOffPercent:
|
|
77
|
+
previousValue && previousValue > 0
|
|
78
|
+
? ((previousValue - item.value) / previousValue) * 100
|
|
79
|
+
: null,
|
|
80
|
+
};
|
|
81
|
+
}),
|
|
82
|
+
};
|
|
83
|
+
}
|
|
@@ -25,6 +25,7 @@ import { z } from "zod";
|
|
|
25
25
|
|
|
26
26
|
import { getDb, schema } from "../server/db/index.js";
|
|
27
27
|
import { withFullVideoAiInstructions } from "../shared/clips-ai-prefs.js";
|
|
28
|
+
import { WorkflowKindSchema } from "../shared/workflow.js";
|
|
28
29
|
import { readIncludeFullVideoInAi } from "./lib/clips-ai-prefs.js";
|
|
29
30
|
|
|
30
31
|
const KIND_PROMPTS = {
|
|
@@ -64,7 +65,7 @@ export default defineAction({
|
|
|
64
65
|
"Ask the agent to generate a structured workflow doc (pr/sop/ticket/email) from this recording's transcript (and the full video when Include full video is enabled). The agent writes the result to clips-workflow-<recordingId> in application_state.",
|
|
65
66
|
schema: z.object({
|
|
66
67
|
recordingId: z.string().describe("Recording ID"),
|
|
67
|
-
kind:
|
|
68
|
+
kind: WorkflowKindSchema.describe("Workflow kind"),
|
|
68
69
|
}),
|
|
69
70
|
run: async (args) => {
|
|
70
71
|
await assertAccess("recording", args.recordingId, "viewer");
|
|
@@ -98,7 +99,7 @@ export default defineAction({
|
|
|
98
99
|
|
|
99
100
|
const includeFullVideoInAi = await readIncludeFullVideoInAi();
|
|
100
101
|
|
|
101
|
-
const existing = await readAppState(stateKey)
|
|
102
|
+
const existing = await readAppState(stateKey);
|
|
102
103
|
if (existing?.status === "generating") {
|
|
103
104
|
const requestedAt = Date.parse(String(existing.requestedAt ?? ""));
|
|
104
105
|
const isRecent =
|
|
@@ -115,13 +116,14 @@ export default defineAction({
|
|
|
115
116
|
}
|
|
116
117
|
}
|
|
117
118
|
|
|
119
|
+
const requestedAt = new Date().toISOString();
|
|
118
120
|
// Seed the output state with a "generating" placeholder so the UI can show
|
|
119
121
|
// a loading state immediately.
|
|
120
122
|
await writeAppState(stateKey, {
|
|
121
123
|
kind: args.kind,
|
|
122
124
|
status: "generating",
|
|
123
125
|
recordingId: args.recordingId,
|
|
124
|
-
requestedAt
|
|
126
|
+
requestedAt,
|
|
125
127
|
} as any);
|
|
126
128
|
|
|
127
129
|
const baseMessage =
|
|
@@ -136,7 +138,7 @@ export default defineAction({
|
|
|
136
138
|
kind: "generate-workflow" as const,
|
|
137
139
|
workflowKind: args.kind,
|
|
138
140
|
recordingId: args.recordingId,
|
|
139
|
-
requestedAt
|
|
141
|
+
requestedAt,
|
|
140
142
|
recordingTitle: rec.title,
|
|
141
143
|
recordingDescription: rec.description,
|
|
142
144
|
transcriptStatus: transcript?.status ?? "pending",
|