@agent-native/core 0.80.0 → 0.80.2
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 +28 -0
- package/corpus/core/docs/content/locales/ar-SA/skills-guide.mdx +5 -0
- package/corpus/core/docs/content/locales/ar-SA/template-design.mdx +14 -1
- package/corpus/core/docs/content/locales/de-DE/skills-guide.mdx +5 -0
- package/corpus/core/docs/content/locales/de-DE/template-design.mdx +15 -1
- package/corpus/core/docs/content/locales/es-ES/skills-guide.mdx +5 -0
- package/corpus/core/docs/content/locales/es-ES/template-design.mdx +15 -1
- package/corpus/core/docs/content/locales/fr-FR/skills-guide.mdx +5 -0
- package/corpus/core/docs/content/locales/fr-FR/template-design.mdx +15 -1
- package/corpus/core/docs/content/locales/hi-IN/skills-guide.mdx +5 -0
- package/corpus/core/docs/content/locales/hi-IN/template-design.mdx +15 -1
- package/corpus/core/docs/content/locales/ja-JP/skills-guide.mdx +5 -0
- package/corpus/core/docs/content/locales/ja-JP/template-design.mdx +16 -1
- package/corpus/core/docs/content/locales/ko-KR/skills-guide.mdx +5 -0
- package/corpus/core/docs/content/locales/ko-KR/template-design.mdx +14 -1
- package/corpus/core/docs/content/locales/pt-BR/skills-guide.mdx +5 -0
- package/corpus/core/docs/content/locales/pt-BR/template-design.mdx +14 -1
- package/corpus/core/docs/content/locales/zh-CN/skills-guide.mdx +5 -0
- package/corpus/core/docs/content/locales/zh-CN/template-design.mdx +14 -1
- package/corpus/core/docs/content/locales/zh-TW/skills-guide.mdx +5 -0
- package/corpus/core/docs/content/locales/zh-TW/template-design.mdx +14 -1
- package/corpus/core/docs/content/skills-guide.mdx +5 -0
- package/corpus/core/docs/content/template-design.mdx +14 -1
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/application-state/handlers.ts +1 -1
- package/corpus/core/src/brand-kit/index.ts +1 -1
- package/corpus/core/src/cli/skills.ts +18 -0
- package/corpus/core/src/client/AgentPanel.tsx +1 -1
- package/corpus/core/src/client/MultiTabAssistantChat.tsx +1 -1
- package/corpus/core/src/client/extensions/portable-extension.ts +1 -1
- package/corpus/core/src/code-agents/transcript-normalizer.ts +1 -1
- package/corpus/core/src/extensions/path.ts +1 -4
- package/corpus/core/src/integrations/a2a-continuation-processor.ts +1 -1
- package/corpus/core/src/mcp/build-server.ts +2 -2
- package/corpus/core/src/mcp/embed-app.ts +35 -1
- package/corpus/core/src/org/handlers.ts +5 -5
- package/corpus/core/src/org/plugin.ts +2 -2
- package/corpus/core/src/scripts/call-agent.ts +1 -1
- package/corpus/core/src/scripts/db/safety.ts +1 -1
- package/corpus/core/src/server/auth.ts +3 -0
- package/corpus/core/src/server/builder-design-systems.ts +308 -0
- package/corpus/core/src/server/core-routes-plugin.ts +29 -3
- package/corpus/core/src/server/index.ts +8 -0
- package/corpus/core/src/styles/agent-native.css +14 -7
- package/corpus/core/src/usage/store.ts +2 -2
- package/corpus/templates/analytics/actions/dashboard-mutation-api.ts +298 -15
- package/corpus/templates/analytics/actions/get-sql-dashboard.ts +9 -4
- package/corpus/templates/analytics/actions/mutate-dashboard.ts +18 -2
- package/corpus/templates/analytics/actions/query-agent-native-analytics.ts +12 -2
- package/corpus/templates/analytics/app/hooks/use-replay-storage-status.ts +66 -0
- package/corpus/templates/analytics/app/i18n/zh-TW.ts +37 -0
- package/corpus/templates/analytics/app/i18n-data.ts +404 -0
- package/corpus/templates/analytics/app/pages/Settings.tsx +333 -0
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/PanelEditorDialog.tsx +2 -1
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/dashboard-layout.ts +121 -3
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +49 -26
- package/corpus/templates/analytics/app/pages/sessions/SessionsPage.tsx +137 -36
- package/corpus/templates/analytics/changelog/2026-06-29-analytics-agents-can-now-place-new-charts-into-requested-das.md +6 -0
- package/corpus/templates/analytics/changelog/2026-06-29-analytics-queries-now-report-failed-source-query-errors-inst.md +6 -0
- package/corpus/templates/analytics/changelog/2026-06-29-charts-can-now-be-dropped-between-full-dashboard-rows-to-cre.md +6 -0
- package/corpus/templates/analytics/changelog/2026-06-29-dashboard-email-reports-include-the-full-dashboard-image-wit.md +6 -0
- package/corpus/templates/analytics/changelog/2026-06-29-main-surface-border-reaches-the-top.md +6 -0
- package/corpus/templates/analytics/changelog/2026-06-29-session-replay-now-records-playable-sessions-in-production-a.md +6 -0
- package/corpus/templates/analytics/changelog/2026-06-29-set-up-session-replay-storage-from-settings-connect-builder-.md +6 -0
- package/corpus/templates/analytics/scripts/cleanup-empty-session-recordings.ts +102 -0
- package/corpus/templates/analytics/server/lib/dashboard-report.ts +49 -26
- package/corpus/templates/analytics/server/lib/real-data-actions.ts +81 -20
- package/corpus/templates/analytics/server/lib/replay-storage.ts +79 -0
- package/corpus/templates/analytics/server/lib/s3-upload-provider.ts +255 -0
- package/corpus/templates/analytics/server/lib/session-replay.ts +29 -8
- package/corpus/templates/analytics/server/plugins/agent-chat.ts +11 -2
- package/corpus/templates/analytics/server/plugins/onboarding.ts +97 -0
- package/corpus/templates/clips/desktop/src/lib/recorder.ts +24 -0
- package/corpus/templates/clips/desktop/src/lib/transcription-capture.ts +105 -5
- package/corpus/templates/content/actions/set-image-alt-text.ts +1 -1
- package/corpus/templates/content/shared/document-export.ts +1 -1
- package/corpus/templates/design/.agents/skills/design-systems/SKILL.md +8 -18
- package/corpus/templates/design/.agents/skills/visual-edit/SKILL.md +17 -0
- package/corpus/templates/design/actions/add-localhost-screens.ts +32 -5
- package/corpus/templates/design/actions/index-design-system-with-builder.ts +259 -0
- package/corpus/templates/design/actions/update-file.ts +9 -2
- package/corpus/templates/design/app/components/design/CanvasContextMenu.tsx +33 -75
- package/corpus/templates/design/app/components/design/DesignCanvas.tsx +72 -9
- package/corpus/templates/design/app/components/design/DesignExtensionsPanel.tsx +41 -39
- package/corpus/templates/design/app/components/design/EditPanel.tsx +1029 -249
- package/corpus/templates/design/app/components/design/LayersPanel.tsx +450 -101
- package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +978 -154
- package/corpus/templates/design/app/components/design/TweaksPanel.tsx +7 -9
- package/corpus/templates/design/app/components/design/inspector/AlignmentMatrix.tsx +133 -66
- package/corpus/templates/design/app/components/design/inspector/AutoLayoutMatrix.tsx +1234 -344
- package/corpus/templates/design/app/components/design/inspector/ConstraintsWidget.tsx +144 -94
- package/corpus/templates/design/app/components/design/inspector/ExportSettingsPanel.tsx +301 -72
- package/corpus/templates/design/app/components/design/inspector/FigmaColorPicker.tsx +876 -286
- package/corpus/templates/design/app/components/design/inspector/GradientEditor.tsx +601 -0
- package/corpus/templates/design/app/components/design/inspector/ImageFillControls.tsx +242 -0
- package/corpus/templates/design/app/components/design/inspector/ScrubInput.tsx +30 -7
- package/corpus/templates/design/app/components/design/inspector/ShaderControls.tsx +212 -155
- package/corpus/templates/design/app/components/design/inspector/ShaderFillsPanel.tsx +423 -0
- package/corpus/templates/design/app/components/design/inspector/figma-icons.tsx +487 -107
- package/corpus/templates/design/app/components/design/inspector/index.ts +27 -0
- package/corpus/templates/design/app/global.css +29 -3
- package/corpus/templates/design/app/i18n/zh-TW.ts +13 -0
- package/corpus/templates/design/app/i18n-data.ts +239 -1
- package/corpus/templates/design/app/pages/DesignEditor.tsx +1023 -353
- package/corpus/templates/design/app/pages/DesignSystemSetup.tsx +57 -221
- package/corpus/templates/design/changelog/2026-06-29-canvas-selection-no-longer-flashes-broken-prototype-html-whe.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-command-menu-selections-are-clearer-in-dark-mode.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-design-mode-top-chrome-now-groups-collaborators-preview-shar.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-element-selection-now-stays-on-the-intended-layer-after-sele.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-screen-overview-full-view-controls-now-sit-above-frames-inst.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-screen-overview-tools-can-draw-shapes-create-screens-and-kee.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-the-pen-tool-now-creates-anchor-based-bezier-vectors-with-ha.md +6 -0
- package/corpus/templates/design/server/handlers/import-figma-system.ts +38 -85
- package/corpus/templates/design/server/lib/design-snapshot.ts +9 -1
- package/corpus/templates/design/shared/code-layer.ts +209 -9
- package/corpus/templates/design/shared/html-content.ts +21 -0
- package/corpus/templates/design/shared/pen-path.ts +207 -0
- package/corpus/templates/mail/app/lib/utils.ts +2 -2
- package/corpus/templates/slides/.agents/skills/design-systems/SKILL.md +9 -10
- package/corpus/templates/slides/AGENTS.md +3 -2
- package/corpus/templates/slides/actions/import-file.ts +26 -17
- package/corpus/templates/slides/actions/index-design-system-with-builder.ts +259 -0
- package/corpus/templates/slides/app/components/design-system/DesignSystemSetup.tsx +35 -167
- package/corpus/templates/slides/app/components/design-system/fig-import-response.ts +9 -13
- package/corpus/templates/slides/app/context/DeckContext.tsx +1 -1
- package/corpus/templates/slides/app/i18n/ar-SA.ts +9 -37
- package/corpus/templates/slides/app/i18n/de-DE.ts +10 -25
- package/corpus/templates/slides/app/i18n/en-US.ts +9 -26
- package/corpus/templates/slides/app/i18n/es-ES.ts +10 -28
- package/corpus/templates/slides/app/i18n/fr-FR.ts +10 -28
- package/corpus/templates/slides/app/i18n/hi-IN.ts +9 -26
- package/corpus/templates/slides/app/i18n/ja-JP.ts +10 -22
- package/corpus/templates/slides/app/i18n/ko-KR.ts +9 -23
- package/corpus/templates/slides/app/i18n/pt-BR.ts +10 -28
- package/corpus/templates/slides/app/i18n/zh-CN.ts +9 -22
- package/corpus/templates/slides/app/i18n/zh-TW.ts +9 -22
- package/corpus/templates/slides/server/handlers/import-figma-system.ts +44 -12
- package/corpus/templates/slides/server/lib/chat-attachments.ts +1 -1
- package/corpus/templates/videos/app/components/NewCompositionPopover.tsx +1 -1
- package/dist/application-state/handlers.js +1 -1
- package/dist/application-state/handlers.js.map +1 -1
- package/dist/brand-kit/index.d.ts +1 -1
- package/dist/brand-kit/index.js +1 -1
- package/dist/brand-kit/index.js.map +1 -1
- package/dist/cli/skills.d.ts.map +1 -1
- package/dist/cli/skills.js +18 -0
- package/dist/cli/skills.js.map +1 -1
- package/dist/client/AgentPanel.js +1 -1
- package/dist/client/AgentPanel.js.map +1 -1
- package/dist/client/MultiTabAssistantChat.js +1 -1
- package/dist/client/MultiTabAssistantChat.js.map +1 -1
- package/dist/client/extensions/portable-extension.d.ts +1 -1
- package/dist/client/extensions/portable-extension.d.ts.map +1 -1
- package/dist/client/extensions/portable-extension.js.map +1 -1
- package/dist/code-agents/transcript-normalizer.js +1 -1
- package/dist/code-agents/transcript-normalizer.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/extensions/path.d.ts +1 -1
- package/dist/extensions/path.d.ts.map +1 -1
- package/dist/extensions/path.js.map +1 -1
- package/dist/file-upload/actions/upload-image.d.ts +2 -2
- package/dist/integrations/a2a-continuation-processor.js +1 -1
- package/dist/integrations/a2a-continuation-processor.js.map +1 -1
- package/dist/mcp/build-server.d.ts +1 -1
- package/dist/mcp/build-server.d.ts.map +1 -1
- package/dist/mcp/build-server.js +1 -1
- package/dist/mcp/build-server.js.map +1 -1
- package/dist/mcp/embed-app.d.ts.map +1 -1
- package/dist/mcp/embed-app.js +35 -1
- package/dist/mcp/embed-app.js.map +1 -1
- package/dist/notifications/routes.d.ts +3 -3
- package/dist/observability/routes.d.ts +8 -8
- package/dist/org/handlers.js +5 -5
- package/dist/org/handlers.js.map +1 -1
- package/dist/org/plugin.js +2 -2
- package/dist/org/plugin.js.map +1 -1
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/scripts/call-agent.js +1 -1
- package/dist/scripts/call-agent.js.map +1 -1
- package/dist/scripts/db/safety.js +1 -1
- package/dist/scripts/db/safety.js.map +1 -1
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/auth.d.ts.map +1 -1
- package/dist/server/auth.js +3 -0
- package/dist/server/auth.js.map +1 -1
- package/dist/server/builder-design-systems.d.ts +28 -0
- package/dist/server/builder-design-systems.d.ts.map +1 -0
- package/dist/server/builder-design-systems.js +217 -0
- package/dist/server/builder-design-systems.js.map +1 -0
- 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 +20 -3
- package/dist/server/core-routes-plugin.js.map +1 -1
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +1 -0
- package/dist/server/index.js.map +1 -1
- package/dist/styles/agent-native.css +14 -7
- package/dist/usage/store.js +2 -2
- package/dist/usage/store.js.map +1 -1
- package/docs/content/locales/ar-SA/skills-guide.mdx +5 -0
- package/docs/content/locales/ar-SA/template-design.mdx +14 -1
- package/docs/content/locales/de-DE/skills-guide.mdx +5 -0
- package/docs/content/locales/de-DE/template-design.mdx +15 -1
- package/docs/content/locales/es-ES/skills-guide.mdx +5 -0
- package/docs/content/locales/es-ES/template-design.mdx +15 -1
- package/docs/content/locales/fr-FR/skills-guide.mdx +5 -0
- package/docs/content/locales/fr-FR/template-design.mdx +15 -1
- package/docs/content/locales/hi-IN/skills-guide.mdx +5 -0
- package/docs/content/locales/hi-IN/template-design.mdx +15 -1
- package/docs/content/locales/ja-JP/skills-guide.mdx +5 -0
- package/docs/content/locales/ja-JP/template-design.mdx +16 -1
- package/docs/content/locales/ko-KR/skills-guide.mdx +5 -0
- package/docs/content/locales/ko-KR/template-design.mdx +14 -1
- package/docs/content/locales/pt-BR/skills-guide.mdx +5 -0
- package/docs/content/locales/pt-BR/template-design.mdx +14 -1
- package/docs/content/locales/zh-CN/skills-guide.mdx +5 -0
- package/docs/content/locales/zh-CN/template-design.mdx +14 -1
- package/docs/content/locales/zh-TW/skills-guide.mdx +5 -0
- package/docs/content/locales/zh-TW/template-design.mdx +14 -1
- package/docs/content/skills-guide.mdx +5 -0
- package/docs/content/template-design.mdx +14 -1
- package/package.json +1 -1
- package/corpus/templates/design/actions/import-figma-file.ts +0 -166
- package/corpus/templates/slides/server/lib/fig-design-system.ts +0 -110
|
@@ -162,6 +162,17 @@
|
|
|
162
162
|
box-shadow 260ms cubic-bezier(0.32, 0.72, 0, 1);
|
|
163
163
|
}
|
|
164
164
|
|
|
165
|
+
.agent-layout-main-surface::before,
|
|
166
|
+
.agent-sidebar-main-surface::before {
|
|
167
|
+
content: "";
|
|
168
|
+
position: absolute;
|
|
169
|
+
inset: 0;
|
|
170
|
+
z-index: 20;
|
|
171
|
+
pointer-events: none;
|
|
172
|
+
border-radius: inherit;
|
|
173
|
+
box-shadow: var(--agent-native-raised-outline);
|
|
174
|
+
}
|
|
175
|
+
|
|
165
176
|
.agent-layout-shell
|
|
166
177
|
:where(.agent-layout-main-surface, .agent-sidebar-main-surface)
|
|
167
178
|
> :first-child
|
|
@@ -213,23 +224,19 @@
|
|
|
213
224
|
.agent-layout-shell > .agent-sidebar-shell > .agent-sidebar-main-surface {
|
|
214
225
|
border-start-start-radius: var(--agent-native-raised-radius);
|
|
215
226
|
border-end-start-radius: var(--agent-native-raised-radius);
|
|
216
|
-
box-shadow:
|
|
217
|
-
var(--agent-native-raised-outline), var(--agent-native-raised-shadow-left);
|
|
227
|
+
box-shadow: var(--agent-native-raised-shadow-left);
|
|
218
228
|
}
|
|
219
229
|
|
|
220
230
|
.agent-sidebar-main-surface[data-agent-sidebar-main-state="open"][data-agent-sidebar-main-position="right"] {
|
|
221
231
|
border-start-end-radius: var(--agent-native-raised-radius);
|
|
222
232
|
border-end-end-radius: var(--agent-native-raised-radius);
|
|
223
|
-
box-shadow:
|
|
224
|
-
var(--agent-native-raised-outline),
|
|
225
|
-
var(--agent-native-raised-shadow-right);
|
|
233
|
+
box-shadow: var(--agent-native-raised-shadow-right);
|
|
226
234
|
}
|
|
227
235
|
|
|
228
236
|
.agent-sidebar-main-surface[data-agent-sidebar-main-state="open"][data-agent-sidebar-main-position="left"] {
|
|
229
237
|
border-start-start-radius: var(--agent-native-raised-radius);
|
|
230
238
|
border-end-start-radius: var(--agent-native-raised-radius);
|
|
231
|
-
box-shadow:
|
|
232
|
-
var(--agent-native-raised-outline), var(--agent-native-raised-shadow-left);
|
|
239
|
+
box-shadow: var(--agent-native-raised-shadow-left);
|
|
233
240
|
}
|
|
234
241
|
}
|
|
235
242
|
|
|
@@ -112,12 +112,12 @@ const PRICING: Array<{ match: RegExp; pricing: ModelPricing }> = [
|
|
|
112
112
|
// ── Google Gemini ───────────────────────────────────────────────────────────
|
|
113
113
|
// Gemini 3 Pro / 3.1 Pro: ~$1.25/$10 per MTok (approximate; verify on billing)
|
|
114
114
|
{
|
|
115
|
-
match: /gemini-3[
|
|
115
|
+
match: /gemini-3[.\-]1-pro/i,
|
|
116
116
|
pricing: { input: 125, output: 1000, cacheRead: 31, cacheWrite: 0 },
|
|
117
117
|
},
|
|
118
118
|
// Gemini 3.5 Flash / 3 Flash: ~$0.15/$0.60 per MTok
|
|
119
119
|
{
|
|
120
|
-
match: /gemini-3[
|
|
120
|
+
match: /gemini-3[.\-][0-9]+-flash/i,
|
|
121
121
|
pricing: { input: 15, output: 60, cacheRead: 4, cacheWrite: 0 },
|
|
122
122
|
},
|
|
123
123
|
// Gemini 2.5 Pro/Flash (catch-all for older 2.5)
|
|
@@ -1,3 +1,14 @@
|
|
|
1
|
+
import {
|
|
2
|
+
buildDashboardPanelGroups,
|
|
3
|
+
columnExpansionForDropSlot,
|
|
4
|
+
movePanelToDropSlot,
|
|
5
|
+
type DashboardDropSlot,
|
|
6
|
+
} from "../app/pages/adhoc/sql-dashboard/dashboard-layout";
|
|
7
|
+
import {
|
|
8
|
+
clampDashboardColumns,
|
|
9
|
+
MAX_DASHBOARD_COLUMNS,
|
|
10
|
+
type SqlPanel,
|
|
11
|
+
} from "../app/pages/adhoc/sql-dashboard/types";
|
|
1
12
|
import { movePanelsById, type PanelOrderTarget } from "./dashboard-panel-order";
|
|
2
13
|
|
|
3
14
|
export const DASHBOARD_MUTATION_API_TYPES = `type DashboardScript = {
|
|
@@ -57,6 +68,14 @@ type PanelSelection = {
|
|
|
57
68
|
moveBefore(panelId: string): void;
|
|
58
69
|
moveAfter(panelId: string): void;
|
|
59
70
|
moveToIndex(index: number): void;
|
|
71
|
+
moveNextTo(panelId: string): void; // same visible row, after panelId; expands row columns when needed
|
|
72
|
+
nextTo(panelId: string): void; // alias for moveNextTo
|
|
73
|
+
moveToRow(rowNumber: number): void; // 1-based visible row number, end of row
|
|
74
|
+
moveToRowStart(rowNumber: number): void;
|
|
75
|
+
moveToRowEnd(rowNumber: number): void;
|
|
76
|
+
atRow(rowNumber: number): void; // alias for moveToRow
|
|
77
|
+
atRowStart(rowNumber: number): void;
|
|
78
|
+
atRowEnd(rowNumber: number): void;
|
|
60
79
|
remove(): void;
|
|
61
80
|
set(patch: PanelPatch): void;
|
|
62
81
|
setTitle(title: string): void;
|
|
@@ -77,6 +96,10 @@ type InsertedPanel = {
|
|
|
77
96
|
before(panelId: string): void;
|
|
78
97
|
after(panelId: string): void;
|
|
79
98
|
atIndex(index: number): void;
|
|
99
|
+
nextTo(panelId: string): void; // same visible row, after panelId; expands row columns when needed
|
|
100
|
+
atRow(rowNumber: number): void; // 1-based visible row number, end of row
|
|
101
|
+
atRowStart(rowNumber: number): void;
|
|
102
|
+
atRowEnd(rowNumber: number): void;
|
|
80
103
|
};`;
|
|
81
104
|
|
|
82
105
|
export const DASHBOARD_MUTATION_EXAMPLES = [
|
|
@@ -88,6 +111,8 @@ export const DASHBOARD_MUTATION_EXAMPLES = [
|
|
|
88
111
|
'dashboard.panel("retention").setConfigPath("yAxis.format","percent");',
|
|
89
112
|
'dashboard.section("retention-activity-section").append(["repeat-users","retention-over-time"]);',
|
|
90
113
|
'dashboard.insertPanel({"id":"new-kpi","title":"New KPI","source":"first-party","chartType":"metric","width":1,"sql":"SELECT COUNT(*) AS value FROM analytics_events"}).atTop();',
|
|
114
|
+
'dashboard.insertPanel({"id":"new-chart","title":"New Chart","source":"first-party","chartType":"line","width":1,"sql":"SELECT date, COUNT(*) AS value FROM analytics_events GROUP BY date ORDER BY date"}).nextTo("retention-over-time");',
|
|
115
|
+
'dashboard.insertPanel({"id":"row-chart","title":"Row Chart","source":"first-party","chartType":"bar","width":1,"sql":"SELECT name, COUNT(*) AS value FROM analytics_events GROUP BY name"}).atRow(2);',
|
|
91
116
|
] as const;
|
|
92
117
|
|
|
93
118
|
export type DashboardMutationOperation =
|
|
@@ -98,6 +123,9 @@ export type DashboardMutationOperation =
|
|
|
98
123
|
index?: number;
|
|
99
124
|
beforePanelId?: string;
|
|
100
125
|
afterPanelId?: string;
|
|
126
|
+
nextToPanelId?: string;
|
|
127
|
+
rowNumber?: number;
|
|
128
|
+
rowPosition?: "start" | "end";
|
|
101
129
|
}
|
|
102
130
|
| {
|
|
103
131
|
op: "removePanels";
|
|
@@ -121,6 +149,9 @@ export type DashboardMutationOperation =
|
|
|
121
149
|
index?: number;
|
|
122
150
|
beforePanelId?: string;
|
|
123
151
|
afterPanelId?: string;
|
|
152
|
+
nextToPanelId?: string;
|
|
153
|
+
rowNumber?: number;
|
|
154
|
+
rowPosition?: "start" | "end";
|
|
124
155
|
}
|
|
125
156
|
| {
|
|
126
157
|
op: "duplicatePanel";
|
|
@@ -131,6 +162,9 @@ export type DashboardMutationOperation =
|
|
|
131
162
|
index?: number;
|
|
132
163
|
beforePanelId?: string;
|
|
133
164
|
afterPanelId?: string;
|
|
165
|
+
nextToPanelId?: string;
|
|
166
|
+
rowNumber?: number;
|
|
167
|
+
rowPosition?: "start" | "end";
|
|
134
168
|
}
|
|
135
169
|
| {
|
|
136
170
|
op: "setDashboard";
|
|
@@ -156,8 +190,17 @@ type MutationTarget = {
|
|
|
156
190
|
index?: number;
|
|
157
191
|
beforePanelId?: string;
|
|
158
192
|
afterPanelId?: string;
|
|
193
|
+
nextToPanelId?: string;
|
|
194
|
+
rowNumber?: number;
|
|
195
|
+
rowPosition?: "start" | "end";
|
|
159
196
|
};
|
|
160
197
|
|
|
198
|
+
type VisualPlacementTarget =
|
|
199
|
+
| { nextToPanelId: string }
|
|
200
|
+
| { rowNumber: number; rowPosition?: "start" | "end" };
|
|
201
|
+
|
|
202
|
+
type DashboardPlacementTarget = PanelOrderTarget | VisualPlacementTarget;
|
|
203
|
+
|
|
161
204
|
const DASHBOARD_SUBJECTS = [
|
|
162
205
|
"set",
|
|
163
206
|
"panel",
|
|
@@ -173,6 +216,14 @@ const PANEL_METHODS = [
|
|
|
173
216
|
"moveBefore",
|
|
174
217
|
"moveAfter",
|
|
175
218
|
"moveToIndex",
|
|
219
|
+
"moveNextTo",
|
|
220
|
+
"nextTo",
|
|
221
|
+
"moveToRow",
|
|
222
|
+
"moveToRowStart",
|
|
223
|
+
"moveToRowEnd",
|
|
224
|
+
"atRow",
|
|
225
|
+
"atRowStart",
|
|
226
|
+
"atRowEnd",
|
|
176
227
|
"remove",
|
|
177
228
|
"set",
|
|
178
229
|
"setTitle",
|
|
@@ -189,11 +240,19 @@ const PLACEMENT_METHODS = [
|
|
|
189
240
|
"moveBefore",
|
|
190
241
|
"moveAfter",
|
|
191
242
|
"moveToIndex",
|
|
243
|
+
"moveNextTo",
|
|
244
|
+
"moveToRow",
|
|
245
|
+
"moveToRowStart",
|
|
246
|
+
"moveToRowEnd",
|
|
192
247
|
"atTop",
|
|
193
248
|
"atBottom",
|
|
194
249
|
"before",
|
|
195
250
|
"after",
|
|
196
251
|
"atIndex",
|
|
252
|
+
"nextTo",
|
|
253
|
+
"atRow",
|
|
254
|
+
"atRowStart",
|
|
255
|
+
"atRowEnd",
|
|
197
256
|
];
|
|
198
257
|
|
|
199
258
|
function panelsFromConfig(config: Record<string, unknown>) {
|
|
@@ -339,6 +398,14 @@ function assertNumber(value: unknown, label: string): number {
|
|
|
339
398
|
return value;
|
|
340
399
|
}
|
|
341
400
|
|
|
401
|
+
function assertPositiveInteger(value: unknown, label: string): number {
|
|
402
|
+
const number = assertNumber(value, label);
|
|
403
|
+
if (!Number.isInteger(number) || number < 1) {
|
|
404
|
+
throw new Error(`${label} must be a positive integer`);
|
|
405
|
+
}
|
|
406
|
+
return number;
|
|
407
|
+
}
|
|
408
|
+
|
|
342
409
|
function assertObject(value: unknown, label: string): Record<string, unknown> {
|
|
343
410
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
344
411
|
throw new Error(`${label} must be an object`);
|
|
@@ -359,23 +426,172 @@ function uniqueStrings(values: string[]): string[] {
|
|
|
359
426
|
|
|
360
427
|
function targetFromOperation(
|
|
361
428
|
op: MutationTarget & { op: string },
|
|
362
|
-
):
|
|
363
|
-
const
|
|
364
|
-
op.
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
429
|
+
): DashboardPlacementTarget {
|
|
430
|
+
const targetNames = [
|
|
431
|
+
op.position !== undefined ? "position" : null,
|
|
432
|
+
op.index !== undefined ? "index" : null,
|
|
433
|
+
op.beforePanelId ? "beforePanelId" : null,
|
|
434
|
+
op.afterPanelId ? "afterPanelId" : null,
|
|
435
|
+
op.nextToPanelId ? "nextToPanelId" : null,
|
|
436
|
+
op.rowNumber !== undefined ? "rowNumber" : null,
|
|
437
|
+
].filter((item): item is string => item !== null);
|
|
438
|
+
if (targetNames.length > 1) {
|
|
369
439
|
throw new Error(
|
|
370
|
-
`${op.op} accepts only one
|
|
440
|
+
`${op.op} accepts only one placement target (${targetNames.join(", ")})`,
|
|
371
441
|
);
|
|
372
442
|
}
|
|
443
|
+
if (op.rowPosition !== undefined && op.rowNumber === undefined) {
|
|
444
|
+
throw new Error(`${op.op} rowPosition requires rowNumber`);
|
|
445
|
+
}
|
|
446
|
+
if (op.nextToPanelId) return { nextToPanelId: op.nextToPanelId };
|
|
447
|
+
if (op.rowNumber !== undefined) {
|
|
448
|
+
if (!Number.isInteger(op.rowNumber) || op.rowNumber < 1) {
|
|
449
|
+
throw new Error(`${op.op} rowNumber must be a positive integer`);
|
|
450
|
+
}
|
|
451
|
+
return {
|
|
452
|
+
rowNumber: op.rowNumber,
|
|
453
|
+
rowPosition: op.rowPosition ?? "end",
|
|
454
|
+
};
|
|
455
|
+
}
|
|
373
456
|
if (op.beforePanelId) return { beforePanelId: op.beforePanelId };
|
|
374
457
|
if (op.afterPanelId) return { afterPanelId: op.afterPanelId };
|
|
375
458
|
if (op.index !== undefined) return { index: op.index };
|
|
376
459
|
return { position: op.position ?? "bottom" };
|
|
377
460
|
}
|
|
378
461
|
|
|
462
|
+
function isVisualPlacementTarget(
|
|
463
|
+
target: DashboardPlacementTarget,
|
|
464
|
+
): target is VisualPlacementTarget {
|
|
465
|
+
return "nextToPanelId" in target || "rowNumber" in target;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
function dashboardColumns(config: Record<string, unknown>): number {
|
|
469
|
+
return clampDashboardColumns(config.columns);
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
function panelsForLayout(config: Record<string, unknown>): SqlPanel[] {
|
|
473
|
+
return panelsFromConfig(config) as unknown as SqlPanel[];
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
function setGroupColumns(
|
|
477
|
+
config: Record<string, unknown>,
|
|
478
|
+
sectionPanelId: string | null,
|
|
479
|
+
columns: number,
|
|
480
|
+
): void {
|
|
481
|
+
if (sectionPanelId === null) {
|
|
482
|
+
config.columns = columns;
|
|
483
|
+
return;
|
|
484
|
+
}
|
|
485
|
+
const section = requirePanel(panelsFromConfig(config), sectionPanelId);
|
|
486
|
+
section.columns = columns;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
function resolveVisualDropSlot(
|
|
490
|
+
config: Record<string, unknown>,
|
|
491
|
+
target: VisualPlacementTarget,
|
|
492
|
+
): DashboardDropSlot {
|
|
493
|
+
const groups = buildDashboardPanelGroups(
|
|
494
|
+
panelsForLayout(config),
|
|
495
|
+
dashboardColumns(config),
|
|
496
|
+
);
|
|
497
|
+
|
|
498
|
+
if ("nextToPanelId" in target) {
|
|
499
|
+
for (const group of groups) {
|
|
500
|
+
for (let rowIndex = 0; rowIndex < group.rows.length; rowIndex++) {
|
|
501
|
+
const row = group.rows[rowIndex];
|
|
502
|
+
const columnIndex = row.panels.findIndex(
|
|
503
|
+
(panel) => panel.id === target.nextToPanelId,
|
|
504
|
+
);
|
|
505
|
+
if (columnIndex >= 0) {
|
|
506
|
+
return {
|
|
507
|
+
type: "column",
|
|
508
|
+
groupKey: group.key,
|
|
509
|
+
rowIndex,
|
|
510
|
+
columnIndex: columnIndex + 1,
|
|
511
|
+
};
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
throw new Error(
|
|
516
|
+
`target ${missingPanelHelp(panelsFromConfig(config), target.nextToPanelId)}`,
|
|
517
|
+
);
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
let rowNumber = 1;
|
|
521
|
+
for (const group of groups) {
|
|
522
|
+
for (let rowIndex = 0; rowIndex < group.rows.length; rowIndex++) {
|
|
523
|
+
const row = group.rows[rowIndex];
|
|
524
|
+
if (rowNumber === target.rowNumber) {
|
|
525
|
+
return {
|
|
526
|
+
type: "column",
|
|
527
|
+
groupKey: group.key,
|
|
528
|
+
rowIndex,
|
|
529
|
+
columnIndex: target.rowPosition === "start" ? 0 : row.panels.length,
|
|
530
|
+
};
|
|
531
|
+
}
|
|
532
|
+
rowNumber++;
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
throw new Error(
|
|
537
|
+
`rowNumber ${target.rowNumber} was not found; dashboard has ${rowNumber - 1} visible row(s)`,
|
|
538
|
+
);
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
function ensureVisualDropSlotCapacity(
|
|
542
|
+
config: Record<string, unknown>,
|
|
543
|
+
panelId: string,
|
|
544
|
+
slot: DashboardDropSlot,
|
|
545
|
+
): { columns: number; sectionPanelId: string | null } | null {
|
|
546
|
+
if (slot.type !== "column") return null;
|
|
547
|
+
const groups = buildDashboardPanelGroups(
|
|
548
|
+
panelsForLayout(config),
|
|
549
|
+
dashboardColumns(config),
|
|
550
|
+
);
|
|
551
|
+
const group = groups.find((item) => item.key === slot.groupKey);
|
|
552
|
+
const row = group?.rows[slot.rowIndex];
|
|
553
|
+
if (!group || !row) return null;
|
|
554
|
+
const rowContainsPanel = row.panels.some((panel) => panel.id === panelId);
|
|
555
|
+
const requiredColumns = rowContainsPanel
|
|
556
|
+
? row.panels.length
|
|
557
|
+
: row.panels.length + 1;
|
|
558
|
+
if (requiredColumns > MAX_DASHBOARD_COLUMNS) {
|
|
559
|
+
throw new Error(
|
|
560
|
+
`row ${slot.rowIndex + 1} already has ${MAX_DASHBOARD_COLUMNS} panels; cannot place another panel in that row`,
|
|
561
|
+
);
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
return columnExpansionForDropSlot(groups, panelId, slot);
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
function moveSinglePanelToVisualTarget(
|
|
568
|
+
config: Record<string, unknown>,
|
|
569
|
+
panelId: string,
|
|
570
|
+
target: VisualPlacementTarget,
|
|
571
|
+
): number {
|
|
572
|
+
if ("nextToPanelId" in target && target.nextToPanelId === panelId) {
|
|
573
|
+
throw new Error("nextTo target cannot be the moving panel itself");
|
|
574
|
+
}
|
|
575
|
+
requirePanel(panelsFromConfig(config), panelId);
|
|
576
|
+
const slot = resolveVisualDropSlot(config, target);
|
|
577
|
+
const expansion = ensureVisualDropSlotCapacity(config, panelId, slot);
|
|
578
|
+
const currentPanels = panelsForLayout(config);
|
|
579
|
+
const nextPanels = movePanelToDropSlot(
|
|
580
|
+
currentPanels,
|
|
581
|
+
panelId,
|
|
582
|
+
slot,
|
|
583
|
+
dashboardColumns(config),
|
|
584
|
+
);
|
|
585
|
+
if (nextPanels === currentPanels) {
|
|
586
|
+
throw new Error("visual placement did not change the dashboard layout");
|
|
587
|
+
}
|
|
588
|
+
config.panels = nextPanels;
|
|
589
|
+
if (expansion) {
|
|
590
|
+
setGroupColumns(config, expansion.sectionPanelId, expansion.columns);
|
|
591
|
+
}
|
|
592
|
+
return findPanelIndex(panelsFromConfig(config), panelId);
|
|
593
|
+
}
|
|
594
|
+
|
|
379
595
|
function findPanelIndex(
|
|
380
596
|
panels: Array<Record<string, unknown>>,
|
|
381
597
|
id: string,
|
|
@@ -418,7 +634,7 @@ function enhancePanelError(
|
|
|
418
634
|
function insertPanel(
|
|
419
635
|
config: Record<string, unknown>,
|
|
420
636
|
panel: Record<string, unknown>,
|
|
421
|
-
target:
|
|
637
|
+
target: DashboardPlacementTarget,
|
|
422
638
|
): number {
|
|
423
639
|
const panels = panelsFromConfig(config);
|
|
424
640
|
const id = assertString(panel.id, "panel.id");
|
|
@@ -426,6 +642,28 @@ function insertPanel(
|
|
|
426
642
|
throw new Error(`panel "${id}" already exists`);
|
|
427
643
|
}
|
|
428
644
|
const placeholderId = `__agent_native_insert_${id}`;
|
|
645
|
+
if (isVisualPlacementTarget(target)) {
|
|
646
|
+
const slot = resolveVisualDropSlot(config, target);
|
|
647
|
+
config.panels = [...panels, { ...panel, id: placeholderId }];
|
|
648
|
+
const expansion = ensureVisualDropSlotCapacity(config, placeholderId, slot);
|
|
649
|
+
const currentPanels = panelsForLayout(config);
|
|
650
|
+
const nextPanels = movePanelToDropSlot(
|
|
651
|
+
currentPanels,
|
|
652
|
+
placeholderId,
|
|
653
|
+
slot,
|
|
654
|
+
dashboardColumns(config),
|
|
655
|
+
);
|
|
656
|
+
if (nextPanels === currentPanels) {
|
|
657
|
+
throw new Error("visual placement did not change the dashboard layout");
|
|
658
|
+
}
|
|
659
|
+
config.panels = nextPanels;
|
|
660
|
+
if (expansion) {
|
|
661
|
+
setGroupColumns(config, expansion.sectionPanelId, expansion.columns);
|
|
662
|
+
}
|
|
663
|
+
const inserted = requirePanel(panelsFromConfig(config), placeholderId);
|
|
664
|
+
inserted.id = id;
|
|
665
|
+
return findPanelIndex(panelsFromConfig(config), id);
|
|
666
|
+
}
|
|
429
667
|
config.panels = [...panels, { ...panel, id: placeholderId }];
|
|
430
668
|
const result = movePanelsById(config, [placeholderId], target);
|
|
431
669
|
const nextPanels = panelsFromConfig(config);
|
|
@@ -520,7 +758,7 @@ function duplicatePanel(
|
|
|
520
758
|
panelIdToCopy: string,
|
|
521
759
|
newPanelId: string,
|
|
522
760
|
patch: Record<string, unknown>,
|
|
523
|
-
target:
|
|
761
|
+
target: DashboardPlacementTarget,
|
|
524
762
|
): number {
|
|
525
763
|
const panels = panelsFromConfig(config);
|
|
526
764
|
const source = requirePanel(panels, panelIdToCopy);
|
|
@@ -552,13 +790,25 @@ export function applyDashboardMutationOperations(
|
|
|
552
790
|
try {
|
|
553
791
|
switch (op.op) {
|
|
554
792
|
case "movePanels": {
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
793
|
+
const target = targetFromOperation(op);
|
|
794
|
+
if (isVisualPlacementTarget(target)) {
|
|
795
|
+
if (op.panelIds.length !== 1) {
|
|
796
|
+
throw new Error(
|
|
797
|
+
"row-aware visual placement supports one moving panel at a time",
|
|
798
|
+
);
|
|
799
|
+
}
|
|
800
|
+
const index = moveSinglePanelToVisualTarget(
|
|
558
801
|
config,
|
|
559
|
-
op.panelIds,
|
|
560
|
-
|
|
802
|
+
op.panelIds[0],
|
|
803
|
+
target,
|
|
561
804
|
);
|
|
805
|
+
changedPanelIds.add(op.panelIds[0]);
|
|
806
|
+
commandLog.push(`movePanels(${op.panelIds[0]}) -> index ${index}`);
|
|
807
|
+
break;
|
|
808
|
+
}
|
|
809
|
+
let result;
|
|
810
|
+
try {
|
|
811
|
+
result = movePanelsById(config, op.panelIds, target);
|
|
562
812
|
} catch (err) {
|
|
563
813
|
enhancePanelError(config, err);
|
|
564
814
|
}
|
|
@@ -887,6 +1137,31 @@ function targetFromChainCall(call: ParsedCall): MutationTarget {
|
|
|
887
1137
|
case "moveToIndex":
|
|
888
1138
|
case "atIndex":
|
|
889
1139
|
return { index: assertNumber(call.args[0], `${call.name} index`) };
|
|
1140
|
+
case "moveNextTo":
|
|
1141
|
+
case "nextTo":
|
|
1142
|
+
return {
|
|
1143
|
+
nextToPanelId: assertString(call.args[0], `${call.name} panelId`),
|
|
1144
|
+
};
|
|
1145
|
+
case "moveToRow":
|
|
1146
|
+
case "moveToRowEnd":
|
|
1147
|
+
case "atRow":
|
|
1148
|
+
case "atRowEnd":
|
|
1149
|
+
return {
|
|
1150
|
+
rowNumber: assertPositiveInteger(
|
|
1151
|
+
call.args[0],
|
|
1152
|
+
`${call.name} rowNumber`,
|
|
1153
|
+
),
|
|
1154
|
+
rowPosition: "end",
|
|
1155
|
+
};
|
|
1156
|
+
case "moveToRowStart":
|
|
1157
|
+
case "atRowStart":
|
|
1158
|
+
return {
|
|
1159
|
+
rowNumber: assertPositiveInteger(
|
|
1160
|
+
call.args[0],
|
|
1161
|
+
`${call.name} rowNumber`,
|
|
1162
|
+
),
|
|
1163
|
+
rowPosition: "start",
|
|
1164
|
+
};
|
|
890
1165
|
default:
|
|
891
1166
|
throw new Error(
|
|
892
1167
|
`unsupported placement method "${call.name}".${methodHelp(call.name, PLACEMENT_METHODS)}`,
|
|
@@ -904,6 +1179,14 @@ function operationFromPanelCommand(
|
|
|
904
1179
|
case "moveBefore":
|
|
905
1180
|
case "moveAfter":
|
|
906
1181
|
case "moveToIndex":
|
|
1182
|
+
case "moveNextTo":
|
|
1183
|
+
case "nextTo":
|
|
1184
|
+
case "moveToRow":
|
|
1185
|
+
case "moveToRowStart":
|
|
1186
|
+
case "moveToRowEnd":
|
|
1187
|
+
case "atRow":
|
|
1188
|
+
case "atRowStart":
|
|
1189
|
+
case "atRowEnd":
|
|
907
1190
|
return [
|
|
908
1191
|
{
|
|
909
1192
|
op: "movePanels",
|
|
@@ -22,6 +22,7 @@ function dashboardLayoutSummary(config: Record<string, unknown>) {
|
|
|
22
22
|
const columns = clampDashboardColumns(config.columns);
|
|
23
23
|
const groups = buildDashboardPanelGroups(panels, columns);
|
|
24
24
|
const panelOrder = getPanelOrder(config);
|
|
25
|
+
let visibleRowNumber = 1;
|
|
25
26
|
|
|
26
27
|
return {
|
|
27
28
|
panelCount: panelOrder.length,
|
|
@@ -32,10 +33,14 @@ function dashboardLayoutSummary(config: Record<string, unknown>) {
|
|
|
32
33
|
sectionId: group.section?.id ?? null,
|
|
33
34
|
sectionTitle: group.section?.title ?? null,
|
|
34
35
|
columns: group.columns,
|
|
35
|
-
rows: group.rows.map((row, rowIndex) =>
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
36
|
+
rows: group.rows.map((row, rowIndex) => {
|
|
37
|
+
const rowNumber = visibleRowNumber++;
|
|
38
|
+
return {
|
|
39
|
+
rowNumber,
|
|
40
|
+
rowIndex,
|
|
41
|
+
panelIds: row.panels.map((panel) => panel.id),
|
|
42
|
+
};
|
|
43
|
+
}),
|
|
39
44
|
})),
|
|
40
45
|
};
|
|
41
46
|
}
|
|
@@ -27,6 +27,20 @@ const mutationTargetSchema = {
|
|
|
27
27
|
index: z.number().int().nonnegative().optional(),
|
|
28
28
|
beforePanelId: z.string().optional(),
|
|
29
29
|
afterPanelId: z.string().optional(),
|
|
30
|
+
nextToPanelId: z
|
|
31
|
+
.string()
|
|
32
|
+
.optional()
|
|
33
|
+
.describe("Place the panel in the same visible row, after this panel id."),
|
|
34
|
+
rowNumber: z
|
|
35
|
+
.number()
|
|
36
|
+
.int()
|
|
37
|
+
.positive()
|
|
38
|
+
.optional()
|
|
39
|
+
.describe("1-based visible row number for row-aware placement."),
|
|
40
|
+
rowPosition: z
|
|
41
|
+
.enum(["start", "end"])
|
|
42
|
+
.optional()
|
|
43
|
+
.describe("Where in rowNumber to place the panel. Defaults to end."),
|
|
30
44
|
};
|
|
31
45
|
|
|
32
46
|
const mutationOperationSchema = z.discriminatedUnion("op", [
|
|
@@ -101,8 +115,9 @@ const apiHelp =
|
|
|
101
115
|
"Constrained TypeScript-like dashboard mutation script. The server parses only calls on `dashboard`; it does not execute arbitrary JavaScript. " +
|
|
102
116
|
"No variables, imports, loops, functions, templates, network, filesystem, or DB access. Arguments must be JSON-compatible literals, so quote object keys. " +
|
|
103
117
|
"Subjects: dashboard.set, dashboard.panel, dashboard.panels, dashboard.panelsMatching, dashboard.section, dashboard.insertPanel. " +
|
|
104
|
-
"Selection methods: moveToTop, moveToBottom, moveBefore, moveAfter, moveToIndex, remove, set, setTitle, setSql, setWidth, setConfig, setConfigPath, duplicate. " +
|
|
105
|
-
|
|
118
|
+
"Selection methods: moveToTop, moveToBottom, moveBefore, moveAfter, moveToIndex, moveNextTo, moveToRow, remove, set, setTitle, setSql, setWidth, setConfig, setConfigPath, duplicate. " +
|
|
119
|
+
"Inserted panels support atTop, atBottom, before, after, atIndex, nextTo, atRow, atRowStart, and atRowEnd. Use nextTo(panelId) or atRow(rowNumber) for visible row placement. " +
|
|
120
|
+
`Examples: ${DASHBOARD_MUTATION_EXAMPLES.slice(0, 5).join(" ")}`;
|
|
106
121
|
|
|
107
122
|
function resolveScope() {
|
|
108
123
|
const orgId = getRequestOrgId() || null;
|
|
@@ -177,6 +192,7 @@ export default defineAction({
|
|
|
177
192
|
description:
|
|
178
193
|
"Apply general SQL dashboard edits through a small typed mutation API in ONE atomic save. " +
|
|
179
194
|
"Prefer this for dashboard layout and panel edits: move panels by id, edit titles/SQL/width/config, remove panels, duplicate panels, insert panels, or patch dashboard fields. " +
|
|
195
|
+
"For user placement requests like 'second row' or 'next to return rates', use row-aware placement such as `dashboard.insertPanel(...).nextTo(\"retention-over-time\")` or `.atRow(2)`, then verify rendered rows from `get-sql-dashboard.layout.groups`. " +
|
|
180
196
|
"This is code-shaped but not arbitrary code execution: the server parses the allowed dashboard methods, validates the resulting config with the same invariants as update-dashboard, saves once, syncs collab, and returns compact proof. " +
|
|
181
197
|
"The main code argument is a string, so it avoids brittle JSON-pointer indexes and native-array serialization issues. " +
|
|
182
198
|
`Common example: ${DASHBOARD_MUTATION_EXAMPLES[0]}`,
|
|
@@ -15,14 +15,24 @@ function resolveScope() {
|
|
|
15
15
|
|
|
16
16
|
export default defineAction({
|
|
17
17
|
description:
|
|
18
|
-
"Query first-party analytics events recorded through this app's analytics collector endpoint (/track) and session replay summaries recorded through /api/analytics/replay. Use this for questions about app/site traffic, product events, template/app usage, conversions, session recordings, and other first-party data collected by this analytics app. Use source-specific actions such as BigQuery, GA4, Mixpanel, PostHog, or Amplitude when the user asks for those sources or the relevant data lives there. SQL may read analytics_events and session_recordings only; session_replay_chunks is intentionally unavailable, and reads are automatically scoped to the current user/org.",
|
|
18
|
+
"Query first-party analytics events recorded through this app's analytics collector endpoint (/track) and session replay summaries recorded through /api/analytics/replay. Use this for questions about app/site traffic, product events, template/app usage, conversions, session recordings, and other first-party data collected by this analytics app. Use source-specific actions such as BigQuery, GA4, Mixpanel, PostHog, or Amplitude when the user asks for those sources or the relevant data lives there. SQL may read analytics_events and session_recordings only; session_replay_chunks is intentionally unavailable, and reads are automatically scoped to the current user/org. analytics_events columns include event_name, timestamp, event_date, user_id, anonymous_id, user_key, session_id, app, template, signed_in, url, path, hostname, referrer, properties, and context. session_recordings columns include id, session_id, user_id, anonymous_id, user_key, started_at, ended_at, duration_ms, chunk_count, event_count, page_count, error_count, rage_click_count, app, template, status, first_url, last_url, path, hostname, referrer, and metadata.",
|
|
19
19
|
schema: z.object({
|
|
20
20
|
sql: z
|
|
21
21
|
.string()
|
|
22
22
|
.describe(
|
|
23
|
-
"Read-only SQL over analytics_events and session_recordings,
|
|
23
|
+
"Read-only SQL over analytics_events and session_recordings. Use literal values, not bind placeholders. Example: SELECT event_name, COUNT(*) AS events FROM analytics_events WHERE timestamp >= '2026-05-01T04:00:00Z' AND timestamp < '2026-05-02T04:00:00Z' GROUP BY event_name ORDER BY events DESC",
|
|
24
24
|
),
|
|
25
25
|
}),
|
|
26
|
+
outputSchema: z.object({
|
|
27
|
+
rows: z.array(z.record(z.string(), z.unknown())),
|
|
28
|
+
schema: z.array(
|
|
29
|
+
z.object({
|
|
30
|
+
name: z.string(),
|
|
31
|
+
type: z.string(),
|
|
32
|
+
}),
|
|
33
|
+
),
|
|
34
|
+
}),
|
|
35
|
+
readOnly: true,
|
|
26
36
|
http: false,
|
|
27
37
|
run: async (args) => {
|
|
28
38
|
return queryFirstPartyAnalytics(args.sql, resolveScope());
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { agentNativePath } from "@agent-native/core/client";
|
|
2
|
+
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
|
3
|
+
import { useEffect } from "react";
|
|
4
|
+
|
|
5
|
+
export interface ReplayStorageStatus {
|
|
6
|
+
configured: boolean;
|
|
7
|
+
activeProvider?: { id: string; name: string } | null;
|
|
8
|
+
builderConfigured?: boolean;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const REPLAY_STORAGE_STATUS_KEY = [
|
|
12
|
+
"analytics",
|
|
13
|
+
"replay-storage-status",
|
|
14
|
+
] as const;
|
|
15
|
+
|
|
16
|
+
export async function fetchReplayStorageStatus(): Promise<ReplayStorageStatus> {
|
|
17
|
+
let uploadStatus: ReplayStorageStatus | null = null;
|
|
18
|
+
try {
|
|
19
|
+
const r = await fetch(agentNativePath("/_agent-native/file-upload/status"));
|
|
20
|
+
uploadStatus = r.ok ? ((await r.json()) as ReplayStorageStatus) : null;
|
|
21
|
+
if (uploadStatus?.configured) return uploadStatus;
|
|
22
|
+
} catch {
|
|
23
|
+
// Fall through to the Builder status check.
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
try {
|
|
27
|
+
const r = await fetch(agentNativePath("/_agent-native/builder/status"));
|
|
28
|
+
const builderStatus = r.ok
|
|
29
|
+
? ((await r.json()) as { configured?: boolean })
|
|
30
|
+
: null;
|
|
31
|
+
if (builderStatus?.configured) {
|
|
32
|
+
return {
|
|
33
|
+
configured: true,
|
|
34
|
+
activeProvider: { id: "builder", name: "Builder.io" },
|
|
35
|
+
builderConfigured: true,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
} catch {
|
|
39
|
+
// Treat an unreachable status route as not configured.
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return {
|
|
43
|
+
configured: false,
|
|
44
|
+
activeProvider: uploadStatus?.activeProvider ?? null,
|
|
45
|
+
builderConfigured: uploadStatus?.builderConfigured ?? false,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function useReplayStorageStatus() {
|
|
50
|
+
return useQuery({
|
|
51
|
+
queryKey: REPLAY_STORAGE_STATUS_KEY,
|
|
52
|
+
queryFn: fetchReplayStorageStatus,
|
|
53
|
+
staleTime: 60_000,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function usePrefetchReplayStorageStatus() {
|
|
58
|
+
const qc = useQueryClient();
|
|
59
|
+
useEffect(() => {
|
|
60
|
+
qc.prefetchQuery({
|
|
61
|
+
queryKey: REPLAY_STORAGE_STATUS_KEY,
|
|
62
|
+
queryFn: fetchReplayStorageStatus,
|
|
63
|
+
staleTime: 60_000,
|
|
64
|
+
});
|
|
65
|
+
}, [qc]);
|
|
66
|
+
}
|