@agent-native/core 0.78.9 → 0.79.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 +11 -0
- package/corpus/core/docs/content/actions.md +4 -2
- package/corpus/core/docs/content/agent-surfaces.md +9 -0
- package/corpus/core/docs/content/extensions.md +10 -0
- package/corpus/core/docs/content/generative-ui.md +217 -0
- package/corpus/core/docs/content/key-concepts.md +2 -2
- package/corpus/core/docs/content/mcp-apps.md +3 -5
- package/corpus/core/docs/content/native-chat-ui.md +9 -5
- package/corpus/core/docs/content/tracking.md +9 -3
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/action-ui.ts +1 -0
- package/corpus/core/src/client/analytics.ts +195 -12
- package/corpus/core/src/client/chat/index.ts +1 -0
- package/corpus/core/src/client/chat/tool-call-display.tsx +5 -2
- package/corpus/core/src/client/chat/widgets/InlineExtensionWidget.tsx +139 -0
- package/corpus/core/src/client/chat/widgets/builtin-tool-renderers.tsx +27 -1
- package/corpus/core/src/client/conversation/AgentConversation.tsx +38 -0
- package/corpus/core/src/client/conversation/code-agent-transcript.ts +9 -0
- package/corpus/core/src/client/conversation/types.ts +4 -0
- package/corpus/core/src/client/extensions/EmbeddedExtension.tsx +27 -5
- package/corpus/core/src/client/extensions/ExtensionViewer.tsx +26 -5
- package/corpus/core/src/client/extensions/InlineExtensionFrame.tsx +654 -0
- package/corpus/core/src/client/extensions/iframe-bridge.ts +30 -0
- package/corpus/core/src/client/extensions/index.ts +5 -0
- package/corpus/core/src/client/index.ts +1 -0
- package/corpus/core/src/client/session-replay.ts +89 -14
- package/corpus/core/src/code-agents/transcript-normalizer.ts +14 -0
- package/corpus/core/src/extensions/actions.ts +218 -1
- package/corpus/core/src/extensions/html-shell.ts +70 -3
- package/corpus/core/src/extensions/routes.ts +12 -4
- package/corpus/core/src/index.browser.ts +1 -0
- package/corpus/core/src/index.ts +1 -0
- package/corpus/core/src/server/agent-chat-plugin.ts +21 -9
- package/corpus/core/src/templates/workspace-core/.agents/skills/extensions/SKILL.md +13 -0
- package/corpus/core/src/templates/workspace-core/.agents/skills/extensions/references/api.md +2 -0
- package/corpus/core/src/templates/workspace-core/.agents/skills/generative-ui/SKILL.md +115 -0
- package/corpus/templates/analytics/.agents/skills/dashboard-management/SKILL.md +198 -59
- package/corpus/templates/analytics/AGENTS.md +12 -0
- package/corpus/templates/analytics/actions/dashboard-mutation-api.ts +1105 -0
- package/corpus/templates/analytics/actions/dashboard-panel-order.ts +169 -0
- package/corpus/templates/analytics/actions/get-sql-dashboard.ts +89 -6
- package/corpus/templates/analytics/actions/mutate-dashboard.ts +333 -0
- package/corpus/templates/analytics/actions/reorder-dashboard-panels.ts +214 -0
- package/corpus/templates/analytics/actions/send-dashboard-report-now.ts +48 -21
- package/corpus/templates/analytics/actions/update-dashboard.ts +193 -100
- package/corpus/templates/analytics/app/components/dashboard/SqlChart.tsx +11 -0
- package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +1 -1
- package/corpus/templates/analytics/app/global.css +81 -10
- package/corpus/templates/analytics/app/i18n-data.ts +249 -4
- package/corpus/templates/analytics/app/pages/adhoc/explorer-dashboard/ChartCard.tsx +3 -3
- package/corpus/templates/analytics/app/pages/adhoc/explorer-dashboard/index.tsx +41 -1
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/EmailReportDialog.tsx +1 -1
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/PanelEditorDialog.tsx +4 -4
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/SqlChartCard.tsx +48 -18
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/dashboard-layout.ts +32 -0
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +204 -70
- package/corpus/templates/analytics/app/pages/sessions/SessionDetailPage.tsx +1101 -227
- package/corpus/templates/analytics/app/pages/sessions/SessionsPage.tsx +160 -133
- package/corpus/templates/analytics/changelog/2026-06-26-agents-can-now-edit-dashboards-through-a-typed-mutation-scri.md +6 -0
- package/corpus/templates/analytics/changelog/2026-06-26-dashboard-bar-chart-hovers-stay-subtle-in-dark-mode.md +6 -0
- package/corpus/templates/analytics/changelog/2026-06-26-dashboard-chart-moves-are-now-handled-by-panel-id-making-sim.md +6 -0
- package/corpus/templates/analytics/changelog/2026-06-26-dashboard-charts-drag-smoothly-with-stable-cards-inactive-ch.md +6 -0
- package/corpus/templates/analytics/changelog/2026-06-26-dashboard-charts-show-a-refresh-spinner-while-keeping-existi.md +6 -0
- package/corpus/templates/analytics/changelog/2026-06-26-dashboard-edits-stay-stable-while-chart-order-and-deletions-.md +6 -0
- package/corpus/templates/analytics/changelog/2026-06-26-dashboard-email-reports-queue-immediate-sends.md +6 -0
- package/corpus/templates/analytics/changelog/2026-06-26-explorer-dashboards-now-drag-charts-with-a-stable-preview-an.md +6 -0
- package/corpus/templates/analytics/changelog/2026-06-26-session-replay-playback-can-now-be-toggled-by-clicking-the-r.md +6 -0
- package/corpus/templates/analytics/changelog/2026-06-26-session-replays-now-open-from-the-full-row-and-play-with-tim.md +6 -0
- package/corpus/templates/analytics/server/lib/dashboard-report-subscriptions.ts +21 -0
- package/corpus/templates/analytics/server/lib/dashboard-report.ts +41 -17
- package/corpus/templates/analytics/server/plugins/agent-chat.ts +5 -0
- package/dist/action-ui.d.ts +1 -0
- package/dist/action-ui.d.ts.map +1 -1
- package/dist/action-ui.js +1 -0
- package/dist/action-ui.js.map +1 -1
- package/dist/client/analytics.d.ts.map +1 -1
- package/dist/client/analytics.js +173 -12
- package/dist/client/analytics.js.map +1 -1
- package/dist/client/chat/index.d.ts +1 -1
- package/dist/client/chat/index.d.ts.map +1 -1
- package/dist/client/chat/index.js +1 -1
- package/dist/client/chat/index.js.map +1 -1
- package/dist/client/chat/tool-call-display.d.ts.map +1 -1
- package/dist/client/chat/tool-call-display.js +3 -1
- package/dist/client/chat/tool-call-display.js.map +1 -1
- package/dist/client/chat/widgets/InlineExtensionWidget.d.ts +17 -0
- package/dist/client/chat/widgets/InlineExtensionWidget.d.ts.map +1 -0
- package/dist/client/chat/widgets/InlineExtensionWidget.js +85 -0
- package/dist/client/chat/widgets/InlineExtensionWidget.js.map +1 -0
- package/dist/client/chat/widgets/builtin-tool-renderers.d.ts.map +1 -1
- package/dist/client/chat/widgets/builtin-tool-renderers.js +15 -2
- package/dist/client/chat/widgets/builtin-tool-renderers.js.map +1 -1
- package/dist/client/conversation/AgentConversation.d.ts.map +1 -1
- package/dist/client/conversation/AgentConversation.js +33 -0
- package/dist/client/conversation/AgentConversation.js.map +1 -1
- package/dist/client/conversation/code-agent-transcript.js +8 -0
- package/dist/client/conversation/code-agent-transcript.js.map +1 -1
- package/dist/client/conversation/types.d.ts +4 -0
- package/dist/client/conversation/types.d.ts.map +1 -1
- package/dist/client/conversation/types.js.map +1 -1
- package/dist/client/extensions/EmbeddedExtension.d.ts.map +1 -1
- package/dist/client/extensions/EmbeddedExtension.js +29 -1
- package/dist/client/extensions/EmbeddedExtension.js.map +1 -1
- package/dist/client/extensions/ExtensionViewer.d.ts.map +1 -1
- package/dist/client/extensions/ExtensionViewer.js +28 -1
- package/dist/client/extensions/ExtensionViewer.js.map +1 -1
- package/dist/client/extensions/InlineExtensionFrame.d.ts +25 -0
- package/dist/client/extensions/InlineExtensionFrame.d.ts.map +1 -0
- package/dist/client/extensions/InlineExtensionFrame.js +488 -0
- package/dist/client/extensions/InlineExtensionFrame.js.map +1 -0
- package/dist/client/extensions/iframe-bridge.d.ts +2 -0
- package/dist/client/extensions/iframe-bridge.d.ts.map +1 -1
- package/dist/client/extensions/iframe-bridge.js +23 -0
- package/dist/client/extensions/iframe-bridge.js.map +1 -1
- package/dist/client/extensions/index.d.ts +1 -0
- package/dist/client/extensions/index.d.ts.map +1 -1
- package/dist/client/extensions/index.js +1 -0
- package/dist/client/extensions/index.js.map +1 -1
- package/dist/client/index.d.ts +1 -1
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +1 -1
- package/dist/client/index.js.map +1 -1
- package/dist/client/session-replay.d.ts +11 -1
- package/dist/client/session-replay.d.ts.map +1 -1
- package/dist/client/session-replay.js +74 -14
- package/dist/client/session-replay.js.map +1 -1
- package/dist/code-agents/transcript-normalizer.d.ts +2 -0
- package/dist/code-agents/transcript-normalizer.d.ts.map +1 -1
- package/dist/code-agents/transcript-normalizer.js +8 -0
- 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/actions.d.ts.map +1 -1
- package/dist/extensions/actions.js +200 -1
- package/dist/extensions/actions.js.map +1 -1
- package/dist/extensions/html-shell.d.ts.map +1 -1
- package/dist/extensions/html-shell.js +70 -3
- package/dist/extensions/html-shell.js.map +1 -1
- package/dist/extensions/routes.d.ts.map +1 -1
- package/dist/extensions/routes.js +11 -4
- package/dist/extensions/routes.js.map +1 -1
- package/dist/index.browser.d.ts +1 -1
- package/dist/index.browser.d.ts.map +1 -1
- package/dist/index.browser.js +1 -1
- package/dist/index.browser.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/notifications/routes.d.ts +2 -2
- package/dist/observability/routes.d.ts +3 -3
- package/dist/progress/routes.d.ts +1 -1
- package/dist/provider-api/corpus-jobs.d.ts +2 -2
- package/dist/resources/handlers.d.ts +3 -3
- package/dist/server/agent-chat-plugin.d.ts.map +1 -1
- package/dist/server/agent-chat-plugin.js +21 -9
- package/dist/server/agent-chat-plugin.js.map +1 -1
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/dist/templates/workspace-core/.agents/skills/extensions/SKILL.md +13 -0
- package/dist/templates/workspace-core/.agents/skills/extensions/references/api.md +2 -0
- package/dist/templates/workspace-core/.agents/skills/generative-ui/SKILL.md +115 -0
- package/docs/content/actions.md +4 -2
- package/docs/content/agent-surfaces.md +9 -0
- package/docs/content/extensions.md +10 -0
- package/docs/content/generative-ui.md +217 -0
- package/docs/content/key-concepts.md +2 -2
- package/docs/content/mcp-apps.md +3 -5
- package/docs/content/native-chat-ui.md +9 -5
- package/docs/content/tracking.md +9 -3
- package/package.json +1 -1
- package/src/templates/workspace-core/.agents/skills/extensions/SKILL.md +13 -0
- package/src/templates/workspace-core/.agents/skills/extensions/references/api.md +2 -0
- package/src/templates/workspace-core/.agents/skills/generative-ui/SKILL.md +115 -0
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
export type PanelOrderTarget =
|
|
2
|
+
| { position: "top" | "bottom" }
|
|
3
|
+
| { index: number }
|
|
4
|
+
| { beforePanelId: string }
|
|
5
|
+
| { afterPanelId: string };
|
|
6
|
+
|
|
7
|
+
export interface PanelOrderResult {
|
|
8
|
+
panelCount: number;
|
|
9
|
+
panelOrder: string[];
|
|
10
|
+
firstPanelIds: string[];
|
|
11
|
+
movedPanelIds: string[];
|
|
12
|
+
missingPanelIds: string[];
|
|
13
|
+
skippedDuplicatePanelIds: string[];
|
|
14
|
+
insertIndex: number;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function panelsFromConfig(config: Record<string, unknown>) {
|
|
18
|
+
const panels = config.panels;
|
|
19
|
+
if (!Array.isArray(panels)) {
|
|
20
|
+
throw new Error("config.panels must be an array");
|
|
21
|
+
}
|
|
22
|
+
return panels as Array<Record<string, unknown>>;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function panelId(panel: Record<string, unknown>): string {
|
|
26
|
+
return typeof panel.id === "string" ? panel.id : "";
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function getPanelOrder(config: Record<string, unknown>): string[] {
|
|
30
|
+
return panelsFromConfig(config).map(panelId).filter(Boolean);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function getFirstPanelIds(
|
|
34
|
+
config: Record<string, unknown>,
|
|
35
|
+
count = 10,
|
|
36
|
+
): string[] {
|
|
37
|
+
return getPanelOrder(config).slice(0, count);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function uniquePanelIds(ids: string[]): {
|
|
41
|
+
ids: string[];
|
|
42
|
+
duplicates: string[];
|
|
43
|
+
} {
|
|
44
|
+
const seen = new Set<string>();
|
|
45
|
+
const out: string[] = [];
|
|
46
|
+
const duplicates: string[] = [];
|
|
47
|
+
for (const raw of ids) {
|
|
48
|
+
const id = raw.trim();
|
|
49
|
+
if (!id) continue;
|
|
50
|
+
if (seen.has(id)) {
|
|
51
|
+
duplicates.push(id);
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
seen.add(id);
|
|
55
|
+
out.push(id);
|
|
56
|
+
}
|
|
57
|
+
return { ids: out, duplicates };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function resolveInsertIndex(
|
|
61
|
+
remainingPanels: Array<Record<string, unknown>>,
|
|
62
|
+
target: PanelOrderTarget,
|
|
63
|
+
): number {
|
|
64
|
+
if ("position" in target) {
|
|
65
|
+
return target.position === "bottom" ? remainingPanels.length : 0;
|
|
66
|
+
}
|
|
67
|
+
if ("index" in target) {
|
|
68
|
+
const index = target.index;
|
|
69
|
+
if (
|
|
70
|
+
!Number.isInteger(index) ||
|
|
71
|
+
index < 0 ||
|
|
72
|
+
index > remainingPanels.length
|
|
73
|
+
) {
|
|
74
|
+
throw new Error(
|
|
75
|
+
`index must be an integer between 0 and ${remainingPanels.length}`,
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
return index;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const targetId =
|
|
82
|
+
"beforePanelId" in target ? target.beforePanelId : target.afterPanelId;
|
|
83
|
+
const index = remainingPanels.findIndex(
|
|
84
|
+
(panel) => panelId(panel) === targetId,
|
|
85
|
+
);
|
|
86
|
+
if (index < 0) {
|
|
87
|
+
throw new Error(`target panel "${targetId}" was not found`);
|
|
88
|
+
}
|
|
89
|
+
return "beforePanelId" in target ? index : index + 1;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function movePanelsById(
|
|
93
|
+
config: Record<string, unknown>,
|
|
94
|
+
panelIds: string[],
|
|
95
|
+
target: PanelOrderTarget = { position: "top" },
|
|
96
|
+
): PanelOrderResult {
|
|
97
|
+
const panels = panelsFromConfig(config);
|
|
98
|
+
const { ids, duplicates } = uniquePanelIds(panelIds);
|
|
99
|
+
if (ids.length === 0) {
|
|
100
|
+
throw new Error("panelIds must include at least one panel id");
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const wanted = new Set(ids);
|
|
104
|
+
const found = new Set<string>();
|
|
105
|
+
const moving: Array<Record<string, unknown>> = [];
|
|
106
|
+
const remaining: Array<Record<string, unknown>> = [];
|
|
107
|
+
|
|
108
|
+
for (const panel of panels) {
|
|
109
|
+
const id = panelId(panel);
|
|
110
|
+
if (wanted.has(id)) {
|
|
111
|
+
found.add(id);
|
|
112
|
+
moving.push(panel);
|
|
113
|
+
} else {
|
|
114
|
+
remaining.push(panel);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const missing = ids.filter((id) => !found.has(id));
|
|
119
|
+
if (missing.length > 0) {
|
|
120
|
+
throw new Error(`panel id(s) not found: ${missing.join(", ")}`);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const orderedMoving = ids.map(
|
|
124
|
+
(id) => moving.find((panel) => panelId(panel) === id)!,
|
|
125
|
+
);
|
|
126
|
+
const insertIndex = resolveInsertIndex(remaining, target);
|
|
127
|
+
const nextPanels = [
|
|
128
|
+
...remaining.slice(0, insertIndex),
|
|
129
|
+
...orderedMoving,
|
|
130
|
+
...remaining.slice(insertIndex),
|
|
131
|
+
];
|
|
132
|
+
|
|
133
|
+
config.panels = nextPanels;
|
|
134
|
+
const order = getPanelOrder(config);
|
|
135
|
+
return {
|
|
136
|
+
panelCount: order.length,
|
|
137
|
+
panelOrder: order,
|
|
138
|
+
firstPanelIds: order.slice(0, 10),
|
|
139
|
+
movedPanelIds: ids,
|
|
140
|
+
missingPanelIds: [],
|
|
141
|
+
skippedDuplicatePanelIds: duplicates,
|
|
142
|
+
insertIndex,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export function applyPanelOrder(
|
|
147
|
+
config: Record<string, unknown>,
|
|
148
|
+
requestedPanelIds: string[],
|
|
149
|
+
): PanelOrderResult {
|
|
150
|
+
return movePanelsById(config, requestedPanelIds, { position: "top" });
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export function compactDashboardResult(
|
|
154
|
+
config: Record<string, unknown>,
|
|
155
|
+
movedPanelIds: string[] = [],
|
|
156
|
+
): {
|
|
157
|
+
panelCount: number;
|
|
158
|
+
panelOrder: string[];
|
|
159
|
+
firstPanelIds: string[];
|
|
160
|
+
movedPanelIds: string[];
|
|
161
|
+
} {
|
|
162
|
+
const order = getPanelOrder(config);
|
|
163
|
+
return {
|
|
164
|
+
panelCount: order.length,
|
|
165
|
+
panelOrder: order,
|
|
166
|
+
firstPanelIds: order.slice(0, 10),
|
|
167
|
+
movedPanelIds,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
@@ -6,16 +6,83 @@ import {
|
|
|
6
6
|
} from "@agent-native/core/server";
|
|
7
7
|
import { z } from "zod";
|
|
8
8
|
|
|
9
|
+
import { buildDashboardPanelGroups } from "../app/pages/adhoc/sql-dashboard/dashboard-layout";
|
|
10
|
+
import {
|
|
11
|
+
clampDashboardColumns,
|
|
12
|
+
type SqlPanel,
|
|
13
|
+
} from "../app/pages/adhoc/sql-dashboard/types";
|
|
9
14
|
import { loadDashboardSeed } from "../server/lib/dashboard-seeds";
|
|
10
15
|
import { getDashboard } from "../server/lib/dashboards-store";
|
|
16
|
+
import { getPanelOrder } from "./dashboard-panel-order";
|
|
17
|
+
|
|
18
|
+
function dashboardLayoutSummary(config: Record<string, unknown>) {
|
|
19
|
+
const panels = Array.isArray(config.panels)
|
|
20
|
+
? (config.panels as SqlPanel[])
|
|
21
|
+
: [];
|
|
22
|
+
const columns = clampDashboardColumns(config.columns);
|
|
23
|
+
const groups = buildDashboardPanelGroups(panels, columns);
|
|
24
|
+
const panelOrder = getPanelOrder(config);
|
|
25
|
+
|
|
26
|
+
return {
|
|
27
|
+
panelCount: panelOrder.length,
|
|
28
|
+
panelOrder,
|
|
29
|
+
firstPanelIds: panelOrder.slice(0, 10),
|
|
30
|
+
groups: groups.map((group) => ({
|
|
31
|
+
key: group.key,
|
|
32
|
+
sectionId: group.section?.id ?? null,
|
|
33
|
+
sectionTitle: group.section?.title ?? null,
|
|
34
|
+
columns: group.columns,
|
|
35
|
+
rows: group.rows.map((row, rowIndex) => ({
|
|
36
|
+
rowIndex,
|
|
37
|
+
panelIds: row.panels.map((panel) => panel.id),
|
|
38
|
+
})),
|
|
39
|
+
})),
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function panelSummaries(config: Record<string, unknown>) {
|
|
44
|
+
const panels = Array.isArray(config.panels)
|
|
45
|
+
? (config.panels as Array<Record<string, unknown>>)
|
|
46
|
+
: [];
|
|
47
|
+
return panels.map((panel, index) => {
|
|
48
|
+
const panelConfig =
|
|
49
|
+
panel.config &&
|
|
50
|
+
typeof panel.config === "object" &&
|
|
51
|
+
!Array.isArray(panel.config)
|
|
52
|
+
? (panel.config as Record<string, unknown>)
|
|
53
|
+
: {};
|
|
54
|
+
return {
|
|
55
|
+
index,
|
|
56
|
+
id: typeof panel.id === "string" ? panel.id : "",
|
|
57
|
+
title: typeof panel.title === "string" ? panel.title : "",
|
|
58
|
+
chartType: typeof panel.chartType === "string" ? panel.chartType : "",
|
|
59
|
+
source: typeof panel.source === "string" ? panel.source : undefined,
|
|
60
|
+
width: typeof panel.width === "number" ? panel.width : undefined,
|
|
61
|
+
columns: typeof panel.columns === "number" ? panel.columns : undefined,
|
|
62
|
+
tab: typeof panel.tab === "string" ? panel.tab : undefined,
|
|
63
|
+
description:
|
|
64
|
+
typeof panelConfig.description === "string"
|
|
65
|
+
? panelConfig.description
|
|
66
|
+
: undefined,
|
|
67
|
+
};
|
|
68
|
+
});
|
|
69
|
+
}
|
|
11
70
|
|
|
12
71
|
function seededResponse(
|
|
13
72
|
id: string,
|
|
14
73
|
seed: Record<string, unknown>,
|
|
74
|
+
includeConfig: boolean,
|
|
15
75
|
): Record<string, unknown> {
|
|
16
|
-
|
|
76
|
+
const base = {
|
|
17
77
|
id,
|
|
18
|
-
|
|
78
|
+
name: typeof seed.name === "string" ? seed.name : id,
|
|
79
|
+
description:
|
|
80
|
+
typeof seed.description === "string" ? seed.description : undefined,
|
|
81
|
+
filters: seed.filters,
|
|
82
|
+
variables: seed.variables,
|
|
83
|
+
columns: seed.columns,
|
|
84
|
+
panels: panelSummaries(seed),
|
|
85
|
+
layout: dashboardLayoutSummary(seed),
|
|
19
86
|
ownerEmail: null,
|
|
20
87
|
orgId: null,
|
|
21
88
|
visibility: "org",
|
|
@@ -23,13 +90,20 @@ function seededResponse(
|
|
|
23
90
|
hiddenAt: null,
|
|
24
91
|
hiddenBy: null,
|
|
25
92
|
};
|
|
93
|
+
return includeConfig ? { ...base, ...seed } : base;
|
|
26
94
|
}
|
|
27
95
|
|
|
28
96
|
export default defineAction({
|
|
29
97
|
description:
|
|
30
|
-
"Get a SQL analytics dashboard by ID
|
|
98
|
+
"Get a SQL analytics dashboard by ID. By default this returns compact panel summaries and layout/order fields without giant SQL strings; use includeConfig=true only when you need the full dashboard config for a detailed SQL/config edit.",
|
|
31
99
|
schema: z.object({
|
|
32
100
|
id: z.string().describe("The dashboard ID"),
|
|
101
|
+
includeConfig: z
|
|
102
|
+
.boolean()
|
|
103
|
+
.optional()
|
|
104
|
+
.describe(
|
|
105
|
+
"If true, include the full dashboard config including panel SQL. Defaults to false to keep agent context compact.",
|
|
106
|
+
),
|
|
33
107
|
}),
|
|
34
108
|
http: { method: "GET" },
|
|
35
109
|
readOnly: true,
|
|
@@ -69,15 +143,23 @@ export default defineAction({
|
|
|
69
143
|
const dash = await getDashboard(args.id, ctx);
|
|
70
144
|
if (!dash || dash.kind !== "sql") {
|
|
71
145
|
const seed = loadDashboardSeed(args.id);
|
|
72
|
-
if (seed)
|
|
146
|
+
if (seed)
|
|
147
|
+
return seededResponse(args.id, seed, args.includeConfig === true);
|
|
73
148
|
throw Object.assign(new Error("Dashboard not found"), {
|
|
74
149
|
statusCode: 404,
|
|
75
150
|
});
|
|
76
151
|
}
|
|
77
152
|
const config = dash.config as Record<string, unknown>;
|
|
78
|
-
|
|
153
|
+
const base = {
|
|
79
154
|
id: args.id,
|
|
80
|
-
|
|
155
|
+
name: typeof config.name === "string" ? config.name : args.id,
|
|
156
|
+
description:
|
|
157
|
+
typeof config.description === "string" ? config.description : undefined,
|
|
158
|
+
filters: config.filters,
|
|
159
|
+
variables: config.variables,
|
|
160
|
+
columns: config.columns,
|
|
161
|
+
panels: panelSummaries(config),
|
|
162
|
+
layout: dashboardLayoutSummary(config),
|
|
81
163
|
ownerEmail: dash.ownerEmail,
|
|
82
164
|
orgId: dash.orgId,
|
|
83
165
|
visibility: dash.visibility,
|
|
@@ -90,5 +172,6 @@ export default defineAction({
|
|
|
90
172
|
createdAt: dash.createdAt,
|
|
91
173
|
updatedAt: dash.updatedAt,
|
|
92
174
|
};
|
|
175
|
+
return args.includeConfig === true ? { ...base, ...config } : base;
|
|
93
176
|
},
|
|
94
177
|
});
|
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
import { defineAction, embedApp } from "@agent-native/core";
|
|
2
|
+
import {
|
|
3
|
+
applyText,
|
|
4
|
+
hasCollabState,
|
|
5
|
+
seedFromText,
|
|
6
|
+
} from "@agent-native/core/collab";
|
|
7
|
+
import {
|
|
8
|
+
buildDeepLink,
|
|
9
|
+
getRequestOrgId,
|
|
10
|
+
getRequestUserEmail,
|
|
11
|
+
} from "@agent-native/core/server";
|
|
12
|
+
import { z } from "zod";
|
|
13
|
+
|
|
14
|
+
import { getDashboard, upsertDashboard } from "../server/lib/dashboards-store";
|
|
15
|
+
import {
|
|
16
|
+
applyDashboardMutationOperations,
|
|
17
|
+
DASHBOARD_MUTATION_API_TYPES,
|
|
18
|
+
DASHBOARD_MUTATION_EXAMPLES,
|
|
19
|
+
parseDashboardMutationScript,
|
|
20
|
+
type DashboardMutationOperation,
|
|
21
|
+
} from "./dashboard-mutation-api";
|
|
22
|
+
import { compactDashboardResult } from "./dashboard-panel-order";
|
|
23
|
+
import { validateDashboardConfig, validatePanelSql } from "./update-dashboard";
|
|
24
|
+
|
|
25
|
+
const mutationTargetSchema = {
|
|
26
|
+
position: z.enum(["top", "bottom"]).optional(),
|
|
27
|
+
index: z.number().int().nonnegative().optional(),
|
|
28
|
+
beforePanelId: z.string().optional(),
|
|
29
|
+
afterPanelId: z.string().optional(),
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const mutationOperationSchema = z.discriminatedUnion("op", [
|
|
33
|
+
z.object({
|
|
34
|
+
op: z.literal("movePanels"),
|
|
35
|
+
panelIds: z.array(z.string()).min(1),
|
|
36
|
+
...mutationTargetSchema,
|
|
37
|
+
}),
|
|
38
|
+
z.object({
|
|
39
|
+
op: z.literal("removePanels"),
|
|
40
|
+
panelIds: z.array(z.string()).min(1),
|
|
41
|
+
}),
|
|
42
|
+
z.object({
|
|
43
|
+
op: z.literal("updatePanel"),
|
|
44
|
+
panelId: z.string(),
|
|
45
|
+
patch: z.record(z.string(), z.unknown()),
|
|
46
|
+
}),
|
|
47
|
+
z.object({
|
|
48
|
+
op: z.literal("updatePanelPath"),
|
|
49
|
+
panelId: z.string(),
|
|
50
|
+
path: z.string(),
|
|
51
|
+
value: z.unknown(),
|
|
52
|
+
}),
|
|
53
|
+
z.object({
|
|
54
|
+
op: z.literal("insertPanel"),
|
|
55
|
+
panel: z.record(z.string(), z.unknown()),
|
|
56
|
+
...mutationTargetSchema,
|
|
57
|
+
}),
|
|
58
|
+
z.object({
|
|
59
|
+
op: z.literal("duplicatePanel"),
|
|
60
|
+
panelId: z.string(),
|
|
61
|
+
newPanelId: z.string(),
|
|
62
|
+
patch: z.record(z.string(), z.unknown()).optional(),
|
|
63
|
+
...mutationTargetSchema,
|
|
64
|
+
}),
|
|
65
|
+
z.object({
|
|
66
|
+
op: z.literal("setDashboard"),
|
|
67
|
+
patch: z.record(z.string(), z.unknown()),
|
|
68
|
+
}),
|
|
69
|
+
]);
|
|
70
|
+
|
|
71
|
+
function parseJsonArrayString(
|
|
72
|
+
value: string,
|
|
73
|
+
fieldName: string,
|
|
74
|
+
): DashboardMutationOperation[] {
|
|
75
|
+
let parsed: unknown;
|
|
76
|
+
try {
|
|
77
|
+
parsed = JSON.parse(value);
|
|
78
|
+
} catch (err: any) {
|
|
79
|
+
throw new Error(`${fieldName} must be a JSON array: ${err.message}`);
|
|
80
|
+
}
|
|
81
|
+
if (!Array.isArray(parsed)) {
|
|
82
|
+
throw new Error(`${fieldName} must be a JSON array`);
|
|
83
|
+
}
|
|
84
|
+
return parsed.map((op, index) => {
|
|
85
|
+
try {
|
|
86
|
+
return mutationOperationSchema.parse(op) as DashboardMutationOperation;
|
|
87
|
+
} catch (err: any) {
|
|
88
|
+
throw new Error(`${fieldName}[${index}] is invalid: ${err.message}`);
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const operationsInputSchema = z
|
|
94
|
+
.union([
|
|
95
|
+
z.array(mutationOperationSchema),
|
|
96
|
+
z.string().transform((value) => parseJsonArrayString(value, "operations")),
|
|
97
|
+
])
|
|
98
|
+
.optional();
|
|
99
|
+
|
|
100
|
+
const apiHelp =
|
|
101
|
+
"Constrained TypeScript-like dashboard mutation script. The server parses only calls on `dashboard`; it does not execute arbitrary JavaScript. " +
|
|
102
|
+
"No variables, imports, loops, functions, templates, network, filesystem, or DB access. Arguments must be JSON-compatible literals, so quote object keys. " +
|
|
103
|
+
"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
|
+
`Examples: ${DASHBOARD_MUTATION_EXAMPLES.slice(0, 3).join(" ")}`;
|
|
106
|
+
|
|
107
|
+
function resolveScope() {
|
|
108
|
+
const orgId = getRequestOrgId() || null;
|
|
109
|
+
const email = getRequestUserEmail();
|
|
110
|
+
if (!email) throw new Error("no authenticated user");
|
|
111
|
+
return { orgId, email };
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function resolveDashboardId(args: { dashboardId?: string; id?: string }) {
|
|
115
|
+
const dashboardId = args.dashboardId || args.id;
|
|
116
|
+
if (!dashboardId) {
|
|
117
|
+
throw new Error("provide `dashboardId` (or legacy `id`).");
|
|
118
|
+
}
|
|
119
|
+
return dashboardId;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function cloneConfig(config: Record<string, unknown>): Record<string, unknown> {
|
|
123
|
+
return JSON.parse(JSON.stringify(config)) as Record<string, unknown>;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
async function syncToCollab(
|
|
127
|
+
dashboardId: string,
|
|
128
|
+
config: Record<string, unknown>,
|
|
129
|
+
): Promise<void> {
|
|
130
|
+
const docId = `dash-${dashboardId}`;
|
|
131
|
+
const configStr = JSON.stringify(config);
|
|
132
|
+
try {
|
|
133
|
+
const exists = await hasCollabState(docId);
|
|
134
|
+
if (exists) {
|
|
135
|
+
await applyText(docId, configStr, "content", "agent");
|
|
136
|
+
} else {
|
|
137
|
+
await seedFromText(docId, configStr);
|
|
138
|
+
}
|
|
139
|
+
} catch {
|
|
140
|
+
// SQL remains the source of truth; live collab sync is best-effort.
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function mutationCanChangePanelSql(op: DashboardMutationOperation): boolean {
|
|
145
|
+
if (op.op === "insertPanel" || op.op === "duplicatePanel") return true;
|
|
146
|
+
if (op.op === "setDashboard") {
|
|
147
|
+
return Object.keys(op.patch).some((key) =>
|
|
148
|
+
["filters", "variables"].includes(key),
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
if (op.op !== "updatePanel") return false;
|
|
152
|
+
return Object.keys(op.patch).some((key) =>
|
|
153
|
+
["sql", "source", "chartType"].includes(key),
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function movedPanelIdsFrom(operations: DashboardMutationOperation[]): string[] {
|
|
158
|
+
const moved = new Set<string>();
|
|
159
|
+
for (const op of operations) {
|
|
160
|
+
if (op.op !== "movePanels") continue;
|
|
161
|
+
for (const id of op.panelIds) moved.add(id);
|
|
162
|
+
}
|
|
163
|
+
return Array.from(moved);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function helpResult() {
|
|
167
|
+
return {
|
|
168
|
+
mutationApiVersion: 1,
|
|
169
|
+
apiTypes: DASHBOARD_MUTATION_API_TYPES,
|
|
170
|
+
examples: DASHBOARD_MUTATION_EXAMPLES,
|
|
171
|
+
summary:
|
|
172
|
+
"Use `code` for constrained dashboard mutation scripts, or `operations` for the equivalent structured ops.",
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export default defineAction({
|
|
177
|
+
description:
|
|
178
|
+
"Apply general SQL dashboard edits through a small typed mutation API in ONE atomic save. " +
|
|
179
|
+
"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. " +
|
|
180
|
+
"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
|
+
"The main code argument is a string, so it avoids brittle JSON-pointer indexes and native-array serialization issues. " +
|
|
182
|
+
`Common example: ${DASHBOARD_MUTATION_EXAMPLES[0]}`,
|
|
183
|
+
schema: z.object({
|
|
184
|
+
dashboardId: z
|
|
185
|
+
.string()
|
|
186
|
+
.optional()
|
|
187
|
+
.describe("Dashboard id, e.g. 'agent-native-templates-first-party'."),
|
|
188
|
+
id: z
|
|
189
|
+
.string()
|
|
190
|
+
.optional()
|
|
191
|
+
.describe("Legacy alias for dashboardId. Prefer dashboardId."),
|
|
192
|
+
code: z.string().optional().describe(apiHelp),
|
|
193
|
+
operations: operationsInputSchema.describe(
|
|
194
|
+
"Structured equivalent of the typed script. Native callers should pass an array of mutation ops; shell/legacy callers may pass a JSON string. " +
|
|
195
|
+
"Supported ops: movePanels, removePanels, updatePanel, updatePanelPath, insertPanel, duplicatePanel, setDashboard.",
|
|
196
|
+
),
|
|
197
|
+
dryRun: z
|
|
198
|
+
.boolean()
|
|
199
|
+
.optional()
|
|
200
|
+
.describe(
|
|
201
|
+
"If true, validate and return the resulting compact proof without saving.",
|
|
202
|
+
),
|
|
203
|
+
returnConfig: z
|
|
204
|
+
.boolean()
|
|
205
|
+
.optional()
|
|
206
|
+
.describe(
|
|
207
|
+
"If true, include the full resulting dashboard config. Defaults to false to keep tool output compact.",
|
|
208
|
+
),
|
|
209
|
+
returnTypes: z
|
|
210
|
+
.boolean()
|
|
211
|
+
.optional()
|
|
212
|
+
.describe("If true, include the allowed TypeScript API and examples."),
|
|
213
|
+
}),
|
|
214
|
+
http: { method: "POST" },
|
|
215
|
+
mcpApp: {
|
|
216
|
+
compactCatalog: true,
|
|
217
|
+
resource: embedApp({
|
|
218
|
+
title: "Dashboard preview",
|
|
219
|
+
description: "Open the mutated dashboard in the real Analytics UI.",
|
|
220
|
+
iframeTitle: "Agent-Native Analytics",
|
|
221
|
+
openLabel: "Open dashboard",
|
|
222
|
+
height: 680,
|
|
223
|
+
}),
|
|
224
|
+
},
|
|
225
|
+
run: async (args) => {
|
|
226
|
+
const wantsHelpOnly =
|
|
227
|
+
args.returnTypes === true &&
|
|
228
|
+
!args.dashboardId &&
|
|
229
|
+
!args.id &&
|
|
230
|
+
!args.code &&
|
|
231
|
+
!args.operations;
|
|
232
|
+
if (wantsHelpOnly) return helpResult();
|
|
233
|
+
|
|
234
|
+
const dashboardId = resolveDashboardId(args);
|
|
235
|
+
const suppliedModes = [args.code, args.operations].filter(
|
|
236
|
+
(value) => value !== undefined,
|
|
237
|
+
).length;
|
|
238
|
+
if (suppliedModes === 0) {
|
|
239
|
+
throw new Error("provide `code` or `operations`.");
|
|
240
|
+
}
|
|
241
|
+
if (suppliedModes > 1) {
|
|
242
|
+
throw new Error("provide only one of `code` or `operations`.");
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const scope = resolveScope();
|
|
246
|
+
const ctx = { email: scope.email, orgId: scope.orgId };
|
|
247
|
+
const existing = await getDashboard(dashboardId, ctx);
|
|
248
|
+
if (!existing) {
|
|
249
|
+
throw new Error(
|
|
250
|
+
`dashboard "${dashboardId}" not found (or you don't have access).`,
|
|
251
|
+
);
|
|
252
|
+
}
|
|
253
|
+
if (existing.kind !== "sql") {
|
|
254
|
+
throw new Error(
|
|
255
|
+
`mutate-dashboard only supports SQL dashboards; "${dashboardId}" is ${existing.kind}.`,
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const root = cloneConfig(existing.config as Record<string, unknown>);
|
|
260
|
+
const operations = args.operations
|
|
261
|
+
? (args.operations as DashboardMutationOperation[])
|
|
262
|
+
: parseDashboardMutationScript(root, args.code!);
|
|
263
|
+
const mutation = applyDashboardMutationOperations(root, operations);
|
|
264
|
+
|
|
265
|
+
const validation = validateDashboardConfig(root);
|
|
266
|
+
if (validation) throw new Error(validation);
|
|
267
|
+
if (operations.some(mutationCanChangePanelSql)) {
|
|
268
|
+
const sqlError = await validatePanelSql(root);
|
|
269
|
+
if (sqlError) throw new Error(sqlError);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
if (args.dryRun !== true) {
|
|
273
|
+
await upsertDashboard(dashboardId, existing.kind, root, ctx);
|
|
274
|
+
await syncToCollab(dashboardId, root);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const compact = compactDashboardResult(root, movedPanelIdsFrom(operations));
|
|
278
|
+
const summary =
|
|
279
|
+
`${args.dryRun === true ? "Dry-ran" : "Applied"} ${operations.length} dashboard mutation op(s) for "${dashboardId}". ` +
|
|
280
|
+
`First panels: ${compact.firstPanelIds.join(", ")}.`;
|
|
281
|
+
|
|
282
|
+
return {
|
|
283
|
+
id: dashboardId,
|
|
284
|
+
dashboardId,
|
|
285
|
+
name: typeof root.name === "string" ? root.name : dashboardId,
|
|
286
|
+
mutationApiVersion: 1,
|
|
287
|
+
saved: args.dryRun !== true,
|
|
288
|
+
dryRun: args.dryRun === true,
|
|
289
|
+
appliedOps: operations.length,
|
|
290
|
+
...compact,
|
|
291
|
+
commandLog: mutation.commandLog,
|
|
292
|
+
changedPanelIds: mutation.changedPanelIds,
|
|
293
|
+
insertedPanelIds: mutation.insertedPanelIds,
|
|
294
|
+
removedPanelIds: mutation.removedPanelIds,
|
|
295
|
+
dashboardFieldsChanged: mutation.dashboardFieldsChanged,
|
|
296
|
+
...(args.returnConfig === true ? { config: root } : {}),
|
|
297
|
+
...(args.returnTypes === true
|
|
298
|
+
? {
|
|
299
|
+
apiTypes: DASHBOARD_MUTATION_API_TYPES,
|
|
300
|
+
examples: DASHBOARD_MUTATION_EXAMPLES,
|
|
301
|
+
}
|
|
302
|
+
: {}),
|
|
303
|
+
summary,
|
|
304
|
+
urlPath: `/dashboards/${dashboardId}`,
|
|
305
|
+
deepLink: buildDeepLink({
|
|
306
|
+
app: "analytics",
|
|
307
|
+
view: "adhoc",
|
|
308
|
+
params: { dashboardId },
|
|
309
|
+
}),
|
|
310
|
+
message:
|
|
311
|
+
`${summary} ` +
|
|
312
|
+
(args.returnConfig === true
|
|
313
|
+
? ""
|
|
314
|
+
: "Full config omitted; call get-sql-dashboard with includeConfig=true only if full SQL/config is needed."),
|
|
315
|
+
};
|
|
316
|
+
},
|
|
317
|
+
link: ({ result }) => {
|
|
318
|
+
const dashboardId =
|
|
319
|
+
result && typeof result === "object"
|
|
320
|
+
? (result as { dashboardId?: string }).dashboardId
|
|
321
|
+
: undefined;
|
|
322
|
+
if (!dashboardId) return null;
|
|
323
|
+
return {
|
|
324
|
+
url: buildDeepLink({
|
|
325
|
+
app: "analytics",
|
|
326
|
+
view: "adhoc",
|
|
327
|
+
params: { dashboardId },
|
|
328
|
+
}),
|
|
329
|
+
label: "Open dashboard in Analytics",
|
|
330
|
+
view: "adhoc",
|
|
331
|
+
};
|
|
332
|
+
},
|
|
333
|
+
});
|