@agent-native/core 0.78.9 → 0.79.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (180) hide show
  1. package/corpus/README.md +2 -2
  2. package/corpus/core/CHANGELOG.md +17 -0
  3. package/corpus/core/docs/content/actions.md +4 -2
  4. package/corpus/core/docs/content/agent-surfaces.md +9 -0
  5. package/corpus/core/docs/content/extensions.md +10 -0
  6. package/corpus/core/docs/content/generative-ui.md +217 -0
  7. package/corpus/core/docs/content/key-concepts.md +2 -2
  8. package/corpus/core/docs/content/mcp-apps.md +3 -5
  9. package/corpus/core/docs/content/native-chat-ui.md +9 -5
  10. package/corpus/core/docs/content/tracking.md +9 -3
  11. package/corpus/core/package.json +1 -1
  12. package/corpus/core/src/action-ui.ts +1 -0
  13. package/corpus/core/src/agent/production-agent.ts +8 -0
  14. package/corpus/core/src/client/analytics.ts +195 -12
  15. package/corpus/core/src/client/chat/index.ts +1 -0
  16. package/corpus/core/src/client/chat/tool-call-display.tsx +5 -2
  17. package/corpus/core/src/client/chat/widgets/InlineExtensionWidget.tsx +139 -0
  18. package/corpus/core/src/client/chat/widgets/builtin-tool-renderers.tsx +27 -1
  19. package/corpus/core/src/client/conversation/AgentConversation.tsx +38 -0
  20. package/corpus/core/src/client/conversation/code-agent-transcript.ts +9 -0
  21. package/corpus/core/src/client/conversation/types.ts +4 -0
  22. package/corpus/core/src/client/extensions/EmbeddedExtension.tsx +27 -5
  23. package/corpus/core/src/client/extensions/ExtensionViewer.tsx +26 -5
  24. package/corpus/core/src/client/extensions/InlineExtensionFrame.tsx +654 -0
  25. package/corpus/core/src/client/extensions/iframe-bridge.ts +30 -0
  26. package/corpus/core/src/client/extensions/index.ts +5 -0
  27. package/corpus/core/src/client/index.ts +1 -0
  28. package/corpus/core/src/client/session-replay.ts +89 -14
  29. package/corpus/core/src/code-agents/transcript-normalizer.ts +14 -0
  30. package/corpus/core/src/extensions/actions.ts +218 -1
  31. package/corpus/core/src/extensions/html-shell.ts +70 -3
  32. package/corpus/core/src/extensions/routes.ts +12 -4
  33. package/corpus/core/src/index.browser.ts +1 -0
  34. package/corpus/core/src/index.ts +1 -0
  35. package/corpus/core/src/server/agent-chat-plugin.ts +21 -9
  36. package/corpus/core/src/templates/workspace-core/.agents/skills/extensions/SKILL.md +13 -0
  37. package/corpus/core/src/templates/workspace-core/.agents/skills/extensions/references/api.md +2 -0
  38. package/corpus/core/src/templates/workspace-core/.agents/skills/generative-ui/SKILL.md +115 -0
  39. package/corpus/templates/analytics/.agents/skills/dashboard-management/SKILL.md +198 -59
  40. package/corpus/templates/analytics/AGENTS.md +12 -0
  41. package/corpus/templates/analytics/actions/dashboard-mutation-api.ts +1105 -0
  42. package/corpus/templates/analytics/actions/dashboard-panel-order.ts +169 -0
  43. package/corpus/templates/analytics/actions/get-sql-dashboard.ts +89 -6
  44. package/corpus/templates/analytics/actions/mutate-dashboard.ts +333 -0
  45. package/corpus/templates/analytics/actions/reorder-dashboard-panels.ts +214 -0
  46. package/corpus/templates/analytics/actions/send-dashboard-report-now.ts +48 -21
  47. package/corpus/templates/analytics/actions/update-dashboard.ts +193 -100
  48. package/corpus/templates/analytics/app/components/dashboard/SqlChart.tsx +11 -0
  49. package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +1 -1
  50. package/corpus/templates/analytics/app/global.css +81 -10
  51. package/corpus/templates/analytics/app/i18n-data.ts +249 -4
  52. package/corpus/templates/analytics/app/pages/adhoc/explorer-dashboard/ChartCard.tsx +3 -3
  53. package/corpus/templates/analytics/app/pages/adhoc/explorer-dashboard/index.tsx +41 -1
  54. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/EmailReportDialog.tsx +1 -1
  55. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/PanelEditorDialog.tsx +4 -4
  56. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/SqlChartCard.tsx +48 -18
  57. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/dashboard-layout.ts +32 -0
  58. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +204 -70
  59. package/corpus/templates/analytics/app/pages/sessions/SessionDetailPage.tsx +1101 -227
  60. package/corpus/templates/analytics/app/pages/sessions/SessionsPage.tsx +160 -133
  61. package/corpus/templates/analytics/changelog/2026-06-26-agents-can-now-edit-dashboards-through-a-typed-mutation-scri.md +6 -0
  62. package/corpus/templates/analytics/changelog/2026-06-26-dashboard-bar-chart-hovers-stay-subtle-in-dark-mode.md +6 -0
  63. package/corpus/templates/analytics/changelog/2026-06-26-dashboard-chart-moves-are-now-handled-by-panel-id-making-sim.md +6 -0
  64. package/corpus/templates/analytics/changelog/2026-06-26-dashboard-charts-drag-smoothly-with-stable-cards-inactive-ch.md +6 -0
  65. package/corpus/templates/analytics/changelog/2026-06-26-dashboard-charts-show-a-refresh-spinner-while-keeping-existi.md +6 -0
  66. package/corpus/templates/analytics/changelog/2026-06-26-dashboard-edits-stay-stable-while-chart-order-and-deletions-.md +6 -0
  67. package/corpus/templates/analytics/changelog/2026-06-26-dashboard-email-reports-queue-immediate-sends.md +6 -0
  68. package/corpus/templates/analytics/changelog/2026-06-26-explorer-dashboards-now-drag-charts-with-a-stable-preview-an.md +6 -0
  69. package/corpus/templates/analytics/changelog/2026-06-26-session-replay-playback-can-now-be-toggled-by-clicking-the-r.md +6 -0
  70. package/corpus/templates/analytics/changelog/2026-06-26-session-replays-now-open-from-the-full-row-and-play-with-tim.md +6 -0
  71. package/corpus/templates/analytics/server/lib/dashboard-report-subscriptions.ts +21 -0
  72. package/corpus/templates/analytics/server/lib/dashboard-report.ts +41 -17
  73. package/corpus/templates/analytics/server/plugins/agent-chat.ts +5 -0
  74. package/dist/action-ui.d.ts +1 -0
  75. package/dist/action-ui.d.ts.map +1 -1
  76. package/dist/action-ui.js +1 -0
  77. package/dist/action-ui.js.map +1 -1
  78. package/dist/agent/production-agent.d.ts.map +1 -1
  79. package/dist/agent/production-agent.js +6 -0
  80. package/dist/agent/production-agent.js.map +1 -1
  81. package/dist/client/analytics.d.ts.map +1 -1
  82. package/dist/client/analytics.js +173 -12
  83. package/dist/client/analytics.js.map +1 -1
  84. package/dist/client/chat/index.d.ts +1 -1
  85. package/dist/client/chat/index.d.ts.map +1 -1
  86. package/dist/client/chat/index.js +1 -1
  87. package/dist/client/chat/index.js.map +1 -1
  88. package/dist/client/chat/tool-call-display.d.ts.map +1 -1
  89. package/dist/client/chat/tool-call-display.js +3 -1
  90. package/dist/client/chat/tool-call-display.js.map +1 -1
  91. package/dist/client/chat/widgets/InlineExtensionWidget.d.ts +17 -0
  92. package/dist/client/chat/widgets/InlineExtensionWidget.d.ts.map +1 -0
  93. package/dist/client/chat/widgets/InlineExtensionWidget.js +85 -0
  94. package/dist/client/chat/widgets/InlineExtensionWidget.js.map +1 -0
  95. package/dist/client/chat/widgets/builtin-tool-renderers.d.ts.map +1 -1
  96. package/dist/client/chat/widgets/builtin-tool-renderers.js +15 -2
  97. package/dist/client/chat/widgets/builtin-tool-renderers.js.map +1 -1
  98. package/dist/client/conversation/AgentConversation.d.ts.map +1 -1
  99. package/dist/client/conversation/AgentConversation.js +33 -0
  100. package/dist/client/conversation/AgentConversation.js.map +1 -1
  101. package/dist/client/conversation/code-agent-transcript.js +8 -0
  102. package/dist/client/conversation/code-agent-transcript.js.map +1 -1
  103. package/dist/client/conversation/types.d.ts +4 -0
  104. package/dist/client/conversation/types.d.ts.map +1 -1
  105. package/dist/client/conversation/types.js.map +1 -1
  106. package/dist/client/extensions/EmbeddedExtension.d.ts.map +1 -1
  107. package/dist/client/extensions/EmbeddedExtension.js +29 -1
  108. package/dist/client/extensions/EmbeddedExtension.js.map +1 -1
  109. package/dist/client/extensions/ExtensionViewer.d.ts.map +1 -1
  110. package/dist/client/extensions/ExtensionViewer.js +28 -1
  111. package/dist/client/extensions/ExtensionViewer.js.map +1 -1
  112. package/dist/client/extensions/InlineExtensionFrame.d.ts +25 -0
  113. package/dist/client/extensions/InlineExtensionFrame.d.ts.map +1 -0
  114. package/dist/client/extensions/InlineExtensionFrame.js +488 -0
  115. package/dist/client/extensions/InlineExtensionFrame.js.map +1 -0
  116. package/dist/client/extensions/iframe-bridge.d.ts +2 -0
  117. package/dist/client/extensions/iframe-bridge.d.ts.map +1 -1
  118. package/dist/client/extensions/iframe-bridge.js +23 -0
  119. package/dist/client/extensions/iframe-bridge.js.map +1 -1
  120. package/dist/client/extensions/index.d.ts +1 -0
  121. package/dist/client/extensions/index.d.ts.map +1 -1
  122. package/dist/client/extensions/index.js +1 -0
  123. package/dist/client/extensions/index.js.map +1 -1
  124. package/dist/client/index.d.ts +1 -1
  125. package/dist/client/index.d.ts.map +1 -1
  126. package/dist/client/index.js +1 -1
  127. package/dist/client/index.js.map +1 -1
  128. package/dist/client/session-replay.d.ts +11 -1
  129. package/dist/client/session-replay.d.ts.map +1 -1
  130. package/dist/client/session-replay.js +74 -14
  131. package/dist/client/session-replay.js.map +1 -1
  132. package/dist/code-agents/transcript-normalizer.d.ts +2 -0
  133. package/dist/code-agents/transcript-normalizer.d.ts.map +1 -1
  134. package/dist/code-agents/transcript-normalizer.js +8 -0
  135. package/dist/code-agents/transcript-normalizer.js.map +1 -1
  136. package/dist/collab/awareness.d.ts +2 -2
  137. package/dist/collab/awareness.d.ts.map +1 -1
  138. package/dist/collab/routes.d.ts +1 -1
  139. package/dist/extensions/actions.d.ts.map +1 -1
  140. package/dist/extensions/actions.js +200 -1
  141. package/dist/extensions/actions.js.map +1 -1
  142. package/dist/extensions/html-shell.d.ts.map +1 -1
  143. package/dist/extensions/html-shell.js +70 -3
  144. package/dist/extensions/html-shell.js.map +1 -1
  145. package/dist/extensions/routes.d.ts.map +1 -1
  146. package/dist/extensions/routes.js +11 -4
  147. package/dist/extensions/routes.js.map +1 -1
  148. package/dist/index.browser.d.ts +1 -1
  149. package/dist/index.browser.d.ts.map +1 -1
  150. package/dist/index.browser.js +1 -1
  151. package/dist/index.browser.js.map +1 -1
  152. package/dist/index.d.ts +1 -1
  153. package/dist/index.d.ts.map +1 -1
  154. package/dist/index.js +1 -1
  155. package/dist/index.js.map +1 -1
  156. package/dist/notifications/routes.d.ts +3 -3
  157. package/dist/observability/routes.d.ts +3 -3
  158. package/dist/progress/routes.d.ts +1 -1
  159. package/dist/provider-api/corpus-jobs.d.ts +2 -2
  160. package/dist/resources/handlers.d.ts +3 -3
  161. package/dist/server/agent-chat-plugin.d.ts.map +1 -1
  162. package/dist/server/agent-chat-plugin.js +21 -9
  163. package/dist/server/agent-chat-plugin.js.map +1 -1
  164. package/dist/server/agent-engine-api-key-route.d.ts +2 -2
  165. package/dist/server/transcribe-voice.d.ts +1 -1
  166. package/dist/templates/workspace-core/.agents/skills/extensions/SKILL.md +13 -0
  167. package/dist/templates/workspace-core/.agents/skills/extensions/references/api.md +2 -0
  168. package/dist/templates/workspace-core/.agents/skills/generative-ui/SKILL.md +115 -0
  169. package/docs/content/actions.md +4 -2
  170. package/docs/content/agent-surfaces.md +9 -0
  171. package/docs/content/extensions.md +10 -0
  172. package/docs/content/generative-ui.md +217 -0
  173. package/docs/content/key-concepts.md +2 -2
  174. package/docs/content/mcp-apps.md +3 -5
  175. package/docs/content/native-chat-ui.md +9 -5
  176. package/docs/content/tracking.md +9 -3
  177. package/package.json +1 -1
  178. package/src/templates/workspace-core/.agents/skills/extensions/SKILL.md +13 -0
  179. package/src/templates/workspace-core/.agents/skills/extensions/references/api.md +2 -0
  180. package/src/templates/workspace-core/.agents/skills/generative-ui/SKILL.md +115 -0
@@ -16,6 +16,10 @@ import { dryRunQuery } from "../server/lib/bigquery";
16
16
  import { getDashboard, upsertDashboard } from "../server/lib/dashboards-store";
17
17
  import { parseDemoDescriptor } from "../server/lib/demo-source";
18
18
  import { validateFirstPartyAnalyticsSql } from "../server/lib/first-party-analytics.js";
19
+ import {
20
+ applyPanelOrder,
21
+ compactDashboardResult,
22
+ } from "./dashboard-panel-order";
19
23
 
20
24
  /**
21
25
  * Same validation shape used in the sql-dashboard save path.
@@ -80,6 +84,62 @@ type JsonOp = {
80
84
  value?: unknown;
81
85
  };
82
86
 
87
+ const jsonOpSchema = z.object({
88
+ op: z.enum(["set", "replace", "remove", "move", "move-before", "insert"]),
89
+ path: z.string().optional(),
90
+ from: z.string().optional(),
91
+ value: z.unknown().optional(),
92
+ });
93
+
94
+ function parseJsonArrayString(value: string, fieldName: string): unknown[] {
95
+ let parsed: unknown;
96
+ try {
97
+ parsed = JSON.parse(value);
98
+ } catch (err: any) {
99
+ throw new Error(`${fieldName} must be a JSON array: ${err.message}`);
100
+ }
101
+ if (!Array.isArray(parsed)) {
102
+ throw new Error(`${fieldName} must be a JSON array`);
103
+ }
104
+ return parsed;
105
+ }
106
+
107
+ const jsonOpsInputSchema = z
108
+ .union([
109
+ z.array(jsonOpSchema),
110
+ z.string().transform((value) => parseJsonArrayString(value, "ops")),
111
+ ])
112
+ .optional();
113
+
114
+ const panelOrderInputSchema = z
115
+ .union([
116
+ z.array(z.string()),
117
+ z
118
+ .string()
119
+ .transform((value) =>
120
+ parseJsonArrayString(value, "panelOrder").map((item) => String(item)),
121
+ ),
122
+ ])
123
+ .optional();
124
+
125
+ const configInputSchema = z
126
+ .union([
127
+ z.record(z.string(), z.unknown()),
128
+ z.string().transform((value) => {
129
+ let parsed: unknown;
130
+ try {
131
+ parsed = JSON.parse(value);
132
+ } catch (err: any) {
133
+ throw new Error(`config must be a JSON object: ${err.message}`);
134
+ }
135
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
136
+ throw new Error("config must be a JSON object");
137
+ }
138
+ return parsed as Record<string, unknown>;
139
+ }),
140
+ ])
141
+ .optional();
142
+
83
143
  function parsePointer(pointer: string): string[] {
84
144
  if (pointer === "" || pointer === "/") return [];
85
145
  if (!pointer.startsWith("/")) {
@@ -208,7 +268,7 @@ function applyJsonOp(root: any, op: JsonOp): string {
208
268
  * Returns a human-readable error string, or `null` when the config passes.
209
269
  * Mirrors the shape required by `app/pages/adhoc/sql-dashboard/types.ts`.
210
270
  */
211
- function validateDashboardConfig(
271
+ export function validateDashboardConfig(
212
272
  config: Record<string, unknown>,
213
273
  ): string | null {
214
274
  if (!config || typeof config !== "object") {
@@ -306,7 +366,7 @@ function validateDashboardConfig(
306
366
  * mismatches fail here, with the full BigQuery error text, rather than
307
367
  * silently saving a broken dashboard that crashes on render.
308
368
  */
309
- async function validatePanelSql(
369
+ export async function validatePanelSql(
310
370
  config: Record<string, unknown>,
311
371
  ): Promise<string | null> {
312
372
  const panels = config.panels;
@@ -383,6 +443,55 @@ function countPanels(config: Record<string, unknown>): number {
383
443
  return Array.isArray(config.panels) ? config.panels.length : 0;
384
444
  }
385
445
 
446
+ function resolveDashboardId(args: { dashboardId?: string; id?: string }) {
447
+ const dashboardId = args.dashboardId || args.id;
448
+ if (!dashboardId) {
449
+ throw new Error("provide `dashboardId` (or legacy `id`).");
450
+ }
451
+ return dashboardId;
452
+ }
453
+
454
+ function dashboardResult(
455
+ dashboardId: string,
456
+ config: Record<string, unknown>,
457
+ appliedOps: number,
458
+ summary: string,
459
+ movedPanelIds: string[] = [],
460
+ returnConfig = false,
461
+ ) {
462
+ const compact = compactDashboardResult(config, movedPanelIds);
463
+ return {
464
+ id: dashboardId,
465
+ dashboardId,
466
+ name: typeof config.name === "string" ? config.name : dashboardId,
467
+ ...compact,
468
+ appliedOps,
469
+ summary,
470
+ ...(returnConfig ? { config } : {}),
471
+ urlPath: `/dashboards/${dashboardId}`,
472
+ deepLink: buildDeepLink({
473
+ app: "analytics",
474
+ view: "adhoc",
475
+ params: { dashboardId },
476
+ }),
477
+ message:
478
+ `${summary} First panels: ${compact.firstPanelIds.join(", ")}.` +
479
+ (returnConfig
480
+ ? ""
481
+ : " Full config omitted; call get-sql-dashboard with includeConfig=true only if full SQL/config is needed."),
482
+ };
483
+ }
484
+
485
+ function opCanChangePanelSql(op: JsonOp): boolean {
486
+ if (op.op === "move" || op.op === "move-before" || op.op === "remove") {
487
+ return false;
488
+ }
489
+ if (!op.path) return true;
490
+ return (
491
+ op.path === "/panels" || /^\/panels\/(?:-|[0-9]+)(?:\/|$)/.test(op.path)
492
+ );
493
+ }
494
+
386
495
  /**
387
496
  * Push a config update through the collab layer so open dashboard editors
388
497
  * receive the change in real time. Seeds the collab state if it doesn't
@@ -412,61 +521,40 @@ async function syncToCollab(
412
521
 
413
522
  export default defineAction({
414
523
  description:
415
- "Edit a SQL dashboard config (scope-aware) atomically in ONE call. Prefer this over raw db-patch on the settings table — " +
416
- "it resolves org vs. user scope correctly so the edit lands on the row the UI actually renders. " +
417
- "BATCH ALL EDITS INTO A SINGLE CALL. Never call this action repeatedly in a loop: hosted agent runs have a ~40s budget, and many sequential update-dashboard calls time out mid-way and leave the dashboard in a partial state even though earlier calls looked like they succeeded. Put every change you want to make into one `ops` array (or one `config`). " +
418
- "`ops` is an array of { op, path, from?, value? } applied in order in a single atomic save. " +
419
- "When calling this as a native tool, pass `ops` as a real array, not a JSON string; the shell examples quote JSON only for CLI parsing. If a call fails because `ops` was stringified, retry this action with a native array instead of switching to db-patch or settings writes. " +
420
- "`op` is one of: set | replace | remove | insert | move | move-before. " +
421
- "`path` is a JSON Pointer into the config (e.g. `/panels/3` is the 4th panel, `/panels/3/title` is its title, `/name` is the dashboard name). The special index `-` means the end of an array: `/panels/-` appends. " +
422
- "`value` is the panel or object to set/insert. `from` is the source JSON Pointer for move / move-before. " +
423
- "To ADD N panels in one call, pass N entries of { op: 'insert', path: '/panels/-', value: <panel> }. " +
424
- "To reorder: [{ op: 'move', from: '/panels/2', path: '/panels/0' }]. " +
425
- "To edit a field: [{ op: 'replace', path: '/panels/0/title', value: 'Events by Day' }]. " +
426
- "Use `config` to replace the entire dashboard config in one call. Provide `ops` OR `config`, not both. " +
524
+ "Save or replace a SQL dashboard full config (scope-aware) atomically in ONE call. " +
525
+ "For existing dashboard panel/layout edits, use `mutate-dashboard` instead; it has the typed `dashboard.*` API for moves, inserts, deletes, duplicates, SQL/title/config edits, and bulk edits without JSON-pointer index math. " +
526
+ "Use this action when creating a brand-new dashboard from a complete config, for the UI full-config save path, or for an explicitly requested low-level JSON-pointer compatibility edit. " +
527
+ "Do not use `ops` or `panelOrder` for ordinary agent edits like moving charts, adding panels to an existing dashboard, changing widths, or updating panel config; call `mutate-dashboard` once with the full edit script. " +
528
+ "When this action is appropriate, provide only one of `ops`, `panelOrder`, or `config`; `config` replaces the whole dashboard config. " +
427
529
  "To add a shipped catalog template's panels to an existing dashboard, prefer `install-dashboard-template` with `mergePanels: true` — it appends the template's panels in one call without you having to author each panel. " +
428
- "The result includes `panelCount` (resulting number of panels), `appliedOps` (count), and a `summary` string use them as proof-of-done and report the new panel count back to the user instead of assuming success. " +
530
+ "The result is compact by default: `panelCount`, `appliedOps`, `panelOrder`, `firstPanelIds`, and `summary`. Set `returnConfig: true` only when you truly need the full config in the tool result. " +
429
531
  "The UI auto-refreshes after this action — do NOT call `refresh-screen`.",
430
532
  schema: z.object({
431
533
  dashboardId: z
432
534
  .string()
535
+ .optional()
433
536
  .describe(
434
537
  "Dashboard id (without the `sql-dashboard-` prefix). e.g. 'devrel-leaderboard'",
435
538
  ),
436
- ops: z
437
- .preprocess(
438
- (v) => (typeof v === "string" ? JSON.parse(v) : v),
439
- z.array(
440
- z.object({
441
- op: z.enum([
442
- "set",
443
- "replace",
444
- "remove",
445
- "move",
446
- "move-before",
447
- "insert",
448
- ]),
449
- path: z.string().optional(),
450
- from: z.string().optional(),
451
- value: z.unknown().optional(),
452
- }),
453
- ),
454
- )
539
+ id: z
540
+ .string()
541
+ .optional()
542
+ .describe("Legacy alias for dashboardId. Prefer dashboardId."),
543
+ ops: jsonOpsInputSchema.describe(
544
+ "Legacy low-level JSON-pointer compatibility ops. Agents should use mutate-dashboard for existing dashboard edits; only use this when the user explicitly requests raw JSON-pointer operations.",
545
+ ),
546
+ panelOrder: panelOrderInputSchema.describe(
547
+ "Legacy compatibility reorder input. Agents should use mutate-dashboard id-based move methods for ordinary chart/section moves.",
548
+ ),
549
+ config: configInputSchema.describe(
550
+ "Replace the whole dashboard config (or a JSON string).",
551
+ ),
552
+ returnConfig: z
553
+ .boolean()
455
554
  .optional()
456
555
  .describe(
457
- "Array of JSON-patch-style ops applied in order in ONE atomic save (or a JSON string). " +
458
- "Each op is { op, path, from?, value? }. op ∈ set|replace|remove|insert|move|move-before. " +
459
- "path is a JSON Pointer (e.g. '/panels/3', and '/panels/-' appends to the end). " +
460
- "Add N panels at once with N {op:'insert', path:'/panels/-', value:<panel>} entries. " +
461
- "Example reorder: [{op:'move', from:'/panels/2', path:'/panels/0'}].",
556
+ "If true, include the full dashboard config in the result. Defaults to false to keep tool output compact.",
462
557
  ),
463
- config: z
464
- .preprocess(
465
- (v) => (typeof v === "string" ? JSON.parse(v) : v),
466
- z.record(z.string(), z.unknown()),
467
- )
468
- .optional()
469
- .describe("Replace the whole dashboard config (or a JSON string)."),
470
558
  }),
471
559
  // The SQL dashboard editor persists user edits through callAction(), which
472
560
  // needs this action mounted under /_agent-native/actions/update-dashboard.
@@ -482,13 +570,18 @@ export default defineAction({
482
570
  }),
483
571
  },
484
572
  run: async (args) => {
485
- if (!args.ops && !args.config) {
573
+ const dashboardId = resolveDashboardId(args);
574
+ const modeCount = [args.ops, args.panelOrder, args.config].filter(
575
+ (value) => value !== undefined,
576
+ ).length;
577
+
578
+ if (modeCount === 0) {
486
579
  throw new Error(
487
- "provide either `ops` (for surgical edits) or `config` (for full replace).",
580
+ "provide `ops` (surgical edits), `panelOrder` (id reorder), or `config` (full replace).",
488
581
  );
489
582
  }
490
- if (args.ops && args.config) {
491
- throw new Error("provide `ops` OR `config`, not both.");
583
+ if (modeCount > 1) {
584
+ throw new Error("provide only one of `ops`, `panelOrder`, or `config`.");
492
585
  }
493
586
 
494
587
  const scope = resolveScope();
@@ -499,40 +592,49 @@ export default defineAction({
499
592
  if (validation) throw new Error(validation);
500
593
  const sqlError = await validatePanelSql(args.config);
501
594
  if (sqlError) throw new Error(sqlError);
502
- await upsertDashboard(args.dashboardId, "sql", args.config, ctx);
503
- await syncToCollab(args.dashboardId, args.config);
595
+ await upsertDashboard(dashboardId, "sql", args.config, ctx);
596
+ await syncToCollab(dashboardId, args.config);
504
597
  const panelCount = countPanels(args.config);
505
- return {
506
- id: args.dashboardId,
507
- dashboardId: args.dashboardId,
508
- name:
509
- typeof args.config.name === "string"
510
- ? args.config.name
511
- : args.dashboardId,
512
- // Proof-of-done: lead with verifiable state so the agent/UI can
513
- // confirm the new shape cheaply instead of assuming success.
514
- panelCount,
515
- appliedOps: 0,
516
- summary: `Replaced dashboard "${args.dashboardId}"; it now has ${panelCount} panel(s).`,
517
- config: args.config,
518
- urlPath: `/dashboards/${args.dashboardId}`,
519
- deepLink: buildDeepLink({
520
- app: "analytics",
521
- view: "adhoc",
522
- params: { dashboardId: args.dashboardId },
523
- }),
524
- message: `Dashboard "${args.dashboardId}" replaced.`,
525
- };
598
+ return dashboardResult(
599
+ dashboardId,
600
+ args.config,
601
+ 0,
602
+ `Replaced dashboard "${dashboardId}"; it now has ${panelCount} panel(s).`,
603
+ [],
604
+ args.returnConfig === true,
605
+ );
526
606
  }
527
607
 
528
- const existing = await getDashboard(args.dashboardId, ctx);
608
+ const existing = await getDashboard(dashboardId, ctx);
529
609
  if (!existing) {
530
610
  throw new Error(
531
- `dashboard "${args.dashboardId}" not found (or you don't have access).`,
611
+ `dashboard "${dashboardId}" not found (or you don't have access).`,
532
612
  );
533
613
  }
534
614
 
535
615
  const root = existing.config as any;
616
+
617
+ if (args.panelOrder) {
618
+ const orderDetails = applyPanelOrder(root, args.panelOrder);
619
+ const validation = validateDashboardConfig(root);
620
+ if (validation) throw new Error(validation);
621
+ await upsertDashboard(
622
+ dashboardId,
623
+ existing.kind,
624
+ root as Record<string, unknown>,
625
+ ctx,
626
+ );
627
+ await syncToCollab(dashboardId, root as Record<string, unknown>);
628
+ return dashboardResult(
629
+ dashboardId,
630
+ root as Record<string, unknown>,
631
+ 1,
632
+ `Moved ${orderDetails.movedPanelIds.length} panel id(s) to the front of dashboard "${dashboardId}"; it now has ${orderDetails.panelCount} panel(s).`,
633
+ orderDetails.movedPanelIds,
634
+ args.returnConfig === true,
635
+ );
636
+ }
637
+
536
638
  const details: string[] = [];
537
639
  for (const op of args.ops!) {
538
640
  try {
@@ -542,39 +644,30 @@ export default defineAction({
542
644
  }
543
645
  }
544
646
 
545
- const sqlError = await validatePanelSql(root);
546
- if (sqlError) throw new Error(sqlError);
647
+ const validation = validateDashboardConfig(root);
648
+ if (validation) throw new Error(validation);
649
+ if (args.ops!.some((op) => opCanChangePanelSql(op as JsonOp))) {
650
+ const sqlError = await validatePanelSql(root);
651
+ if (sqlError) throw new Error(sqlError);
652
+ }
547
653
 
548
654
  await upsertDashboard(
549
- args.dashboardId,
655
+ dashboardId,
550
656
  existing.kind,
551
657
  root as Record<string, unknown>,
552
658
  ctx,
553
659
  );
554
- await syncToCollab(args.dashboardId, root as Record<string, unknown>);
660
+ await syncToCollab(dashboardId, root as Record<string, unknown>);
555
661
 
556
662
  const panelCount = countPanels(root as Record<string, unknown>);
557
- return {
558
- id: args.dashboardId,
559
- dashboardId: args.dashboardId,
560
- name: typeof root.name === "string" ? root.name : args.dashboardId,
561
- // Proof-of-done: lead with verifiable state (op count + resulting panel
562
- // count) so the agent/UI can confirm success without re-fetching.
563
- panelCount,
564
- appliedOps: details.length,
565
- summary: `Applied ${details.length} op(s); dashboard "${args.dashboardId}" now has ${panelCount} panel(s).`,
566
- config: root,
567
- urlPath: `/dashboards/${args.dashboardId}`,
568
- deepLink: buildDeepLink({
569
- app: "analytics",
570
- view: "adhoc",
571
- params: { dashboardId: args.dashboardId },
572
- }),
573
- message:
574
- `Dashboard "${args.dashboardId}" updated. ` +
575
- `Applied ${details.length} op(s): ${details.join("; ")}. ` +
576
- `Now ${panelCount} panel(s).`,
577
- };
663
+ return dashboardResult(
664
+ dashboardId,
665
+ root as Record<string, unknown>,
666
+ details.length,
667
+ `Applied ${details.length} op(s); dashboard "${dashboardId}" now has ${panelCount} panel(s).`,
668
+ [],
669
+ args.returnConfig === true,
670
+ );
578
671
  },
579
672
  link: ({ result }) => {
580
673
  const dashboardId =
@@ -89,6 +89,16 @@ const CHART_TOOLTIP_PROPS = {
89
89
  wrapperStyle: CHART_TOOLTIP_WRAPPER_STYLE,
90
90
  } as const;
91
91
 
92
+ const BAR_TOOLTIP_CURSOR_PROPS = {
93
+ fill: "hsl(var(--muted))",
94
+ fillOpacity: 0.32,
95
+ stroke: "hsl(var(--border))",
96
+ strokeOpacity: 0.5,
97
+ strokeWidth: 1,
98
+ rx: 4,
99
+ ry: 4,
100
+ } as const;
101
+
92
102
  const CHART_LEGEND_WRAPPER_STYLE: CSSProperties = {
93
103
  fontSize: 11,
94
104
  paddingTop: 8,
@@ -1536,6 +1546,7 @@ function BarRenderer({
1536
1546
  />
1537
1547
  <Tooltip
1538
1548
  {...CHART_TOOLTIP_PROPS}
1549
+ cursor={BAR_TOOLTIP_CURSOR_PROPS}
1539
1550
  labelFormatter={xLabelFormatter}
1540
1551
  content={
1541
1552
  <ChartTooltip
@@ -1112,7 +1112,7 @@ async function fetchSqlDashboardForPrefetch(
1112
1112
  try {
1113
1113
  const data: any = await callAction(
1114
1114
  "get-sql-dashboard",
1115
- { id },
1115
+ { id, includeConfig: true },
1116
1116
  { method: "GET" },
1117
1117
  );
1118
1118
  if (!data || data.error) return null;
@@ -193,6 +193,48 @@
193
193
  box-shadow: none;
194
194
  }
195
195
 
196
+ .an-replay-stage-root iframe {
197
+ display: block;
198
+ border: 0;
199
+ }
200
+
201
+ .an-replay-stage-root .replayer-wrapper {
202
+ transform-origin: top left;
203
+ }
204
+
205
+ .an-replay-scrub {
206
+ height: 0.25rem;
207
+ appearance: none;
208
+ border-radius: 9999px;
209
+ outline: none;
210
+ background: hsl(var(--muted));
211
+ cursor: pointer;
212
+ }
213
+
214
+ .an-replay-scrub:disabled {
215
+ cursor: not-allowed;
216
+ opacity: 0.55;
217
+ }
218
+
219
+ .an-replay-scrub::-webkit-slider-thumb {
220
+ width: 0.75rem;
221
+ height: 0.75rem;
222
+ appearance: none;
223
+ border: 2px solid hsl(var(--background));
224
+ border-radius: 9999px;
225
+ background: hsl(var(--primary));
226
+ box-shadow: 0 0 0 1px hsl(var(--border));
227
+ }
228
+
229
+ .an-replay-scrub::-moz-range-thumb {
230
+ width: 0.75rem;
231
+ height: 0.75rem;
232
+ border: 2px solid hsl(var(--background));
233
+ border-radius: 9999px;
234
+ background: hsl(var(--primary));
235
+ box-shadow: 0 0 0 1px hsl(var(--border));
236
+ }
237
+
196
238
  .dark
197
239
  .analytics-chat-panel
198
240
  [data-agent-empty-state="centered"]
@@ -317,12 +359,12 @@
317
359
  when the agent sidebar is open — not the viewport. Rows are rendered
318
360
  explicitly so deleting a card only expands the remaining cards in that row. */
319
361
  .dashboard-grid-container {
362
+ --dashboard-drop-line: hsl(217 91% 60%);
320
363
  container: dashboard-grid / inline-size;
321
364
  }
322
365
 
323
366
  .dashboard-grid {
324
367
  --dashboard-grid-gap: 1rem;
325
- --dashboard-drop-line: hsl(217 91% 60%);
326
368
  display: flex;
327
369
  flex-direction: column;
328
370
  align-items: stretch;
@@ -338,17 +380,17 @@
338
380
  .dashboard-column-drop-slot {
339
381
  position: relative;
340
382
  z-index: 30;
341
- flex: 0 0 1rem;
383
+ flex: 0 0 1.5rem;
342
384
  }
343
385
 
344
386
  .dashboard-row-drop-slot {
345
- height: 1rem;
346
- margin-block: -0.125rem;
387
+ height: 1.5rem;
388
+ margin-block: -0.25rem;
347
389
  }
348
390
 
349
391
  .dashboard-column-drop-slot {
350
- height: 1rem;
351
- margin-block: -0.5rem;
392
+ height: 1.5rem;
393
+ margin-block: -0.75rem;
352
394
  }
353
395
 
354
396
  .dashboard-row-drop-slot::after,
@@ -367,7 +409,7 @@
367
409
  left: 0;
368
410
  right: 0;
369
411
  top: 50%;
370
- height: 2px;
412
+ height: 3px;
371
413
  transform: translateY(-50%);
372
414
  }
373
415
 
@@ -376,6 +418,35 @@
376
418
  opacity: 1;
377
419
  }
378
420
 
421
+ .dashboard-chart-card[data-dragging="true"],
422
+ .dashboard-section-card[data-dragging="true"] {
423
+ opacity: 0.55;
424
+ }
425
+
426
+ .dashboard-grid-container[data-dashboard-dragging="true"] .dashboard-grid-cell,
427
+ .dashboard-grid-container[data-dashboard-dragging="true"]
428
+ .dashboard-section-cell,
429
+ .dashboard-grid-container[data-dashboard-dragging="true"]
430
+ .dashboard-chart-content {
431
+ pointer-events: none;
432
+ }
433
+
434
+ .dashboard-grid-container[data-dashboard-dragging="true"]
435
+ .dashboard-row-drop-slot,
436
+ .dashboard-grid-container[data-dashboard-dragging="true"]
437
+ .dashboard-column-drop-slot {
438
+ pointer-events: auto;
439
+ }
440
+
441
+ .explorer-dashboard-grid [data-dragging="true"] {
442
+ opacity: 0.55;
443
+ }
444
+
445
+ .explorer-dashboard-grid[data-dashboard-dragging="true"]
446
+ .explorer-dashboard-chart-content {
447
+ pointer-events: none;
448
+ }
449
+
379
450
  @container dashboard-grid (min-width: 768px) {
380
451
  .dashboard-grid {
381
452
  flex-direction: row;
@@ -389,10 +460,10 @@
389
460
 
390
461
  .dashboard-column-drop-slot {
391
462
  align-self: stretch;
392
- width: 1rem;
463
+ width: 1.5rem;
393
464
  height: auto;
394
465
  margin-block: 0;
395
- margin-inline: -0.5rem;
466
+ margin-inline: -0.75rem;
396
467
  }
397
468
 
398
469
  .dashboard-column-drop-slot::after {
@@ -400,7 +471,7 @@
400
471
  bottom: 0;
401
472
  left: 50%;
402
473
  right: auto;
403
- width: 2px;
474
+ width: 3px;
404
475
  height: auto;
405
476
  transform: translateX(-50%);
406
477
  }