@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.
Files changed (176) hide show
  1. package/corpus/README.md +2 -2
  2. package/corpus/core/CHANGELOG.md +11 -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/client/analytics.ts +195 -12
  14. package/corpus/core/src/client/chat/index.ts +1 -0
  15. package/corpus/core/src/client/chat/tool-call-display.tsx +5 -2
  16. package/corpus/core/src/client/chat/widgets/InlineExtensionWidget.tsx +139 -0
  17. package/corpus/core/src/client/chat/widgets/builtin-tool-renderers.tsx +27 -1
  18. package/corpus/core/src/client/conversation/AgentConversation.tsx +38 -0
  19. package/corpus/core/src/client/conversation/code-agent-transcript.ts +9 -0
  20. package/corpus/core/src/client/conversation/types.ts +4 -0
  21. package/corpus/core/src/client/extensions/EmbeddedExtension.tsx +27 -5
  22. package/corpus/core/src/client/extensions/ExtensionViewer.tsx +26 -5
  23. package/corpus/core/src/client/extensions/InlineExtensionFrame.tsx +654 -0
  24. package/corpus/core/src/client/extensions/iframe-bridge.ts +30 -0
  25. package/corpus/core/src/client/extensions/index.ts +5 -0
  26. package/corpus/core/src/client/index.ts +1 -0
  27. package/corpus/core/src/client/session-replay.ts +89 -14
  28. package/corpus/core/src/code-agents/transcript-normalizer.ts +14 -0
  29. package/corpus/core/src/extensions/actions.ts +218 -1
  30. package/corpus/core/src/extensions/html-shell.ts +70 -3
  31. package/corpus/core/src/extensions/routes.ts +12 -4
  32. package/corpus/core/src/index.browser.ts +1 -0
  33. package/corpus/core/src/index.ts +1 -0
  34. package/corpus/core/src/server/agent-chat-plugin.ts +21 -9
  35. package/corpus/core/src/templates/workspace-core/.agents/skills/extensions/SKILL.md +13 -0
  36. package/corpus/core/src/templates/workspace-core/.agents/skills/extensions/references/api.md +2 -0
  37. package/corpus/core/src/templates/workspace-core/.agents/skills/generative-ui/SKILL.md +115 -0
  38. package/corpus/templates/analytics/.agents/skills/dashboard-management/SKILL.md +198 -59
  39. package/corpus/templates/analytics/AGENTS.md +12 -0
  40. package/corpus/templates/analytics/actions/dashboard-mutation-api.ts +1105 -0
  41. package/corpus/templates/analytics/actions/dashboard-panel-order.ts +169 -0
  42. package/corpus/templates/analytics/actions/get-sql-dashboard.ts +89 -6
  43. package/corpus/templates/analytics/actions/mutate-dashboard.ts +333 -0
  44. package/corpus/templates/analytics/actions/reorder-dashboard-panels.ts +214 -0
  45. package/corpus/templates/analytics/actions/send-dashboard-report-now.ts +48 -21
  46. package/corpus/templates/analytics/actions/update-dashboard.ts +193 -100
  47. package/corpus/templates/analytics/app/components/dashboard/SqlChart.tsx +11 -0
  48. package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +1 -1
  49. package/corpus/templates/analytics/app/global.css +81 -10
  50. package/corpus/templates/analytics/app/i18n-data.ts +249 -4
  51. package/corpus/templates/analytics/app/pages/adhoc/explorer-dashboard/ChartCard.tsx +3 -3
  52. package/corpus/templates/analytics/app/pages/adhoc/explorer-dashboard/index.tsx +41 -1
  53. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/EmailReportDialog.tsx +1 -1
  54. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/PanelEditorDialog.tsx +4 -4
  55. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/SqlChartCard.tsx +48 -18
  56. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/dashboard-layout.ts +32 -0
  57. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +204 -70
  58. package/corpus/templates/analytics/app/pages/sessions/SessionDetailPage.tsx +1101 -227
  59. package/corpus/templates/analytics/app/pages/sessions/SessionsPage.tsx +160 -133
  60. package/corpus/templates/analytics/changelog/2026-06-26-agents-can-now-edit-dashboards-through-a-typed-mutation-scri.md +6 -0
  61. package/corpus/templates/analytics/changelog/2026-06-26-dashboard-bar-chart-hovers-stay-subtle-in-dark-mode.md +6 -0
  62. package/corpus/templates/analytics/changelog/2026-06-26-dashboard-chart-moves-are-now-handled-by-panel-id-making-sim.md +6 -0
  63. package/corpus/templates/analytics/changelog/2026-06-26-dashboard-charts-drag-smoothly-with-stable-cards-inactive-ch.md +6 -0
  64. package/corpus/templates/analytics/changelog/2026-06-26-dashboard-charts-show-a-refresh-spinner-while-keeping-existi.md +6 -0
  65. package/corpus/templates/analytics/changelog/2026-06-26-dashboard-edits-stay-stable-while-chart-order-and-deletions-.md +6 -0
  66. package/corpus/templates/analytics/changelog/2026-06-26-dashboard-email-reports-queue-immediate-sends.md +6 -0
  67. package/corpus/templates/analytics/changelog/2026-06-26-explorer-dashboards-now-drag-charts-with-a-stable-preview-an.md +6 -0
  68. package/corpus/templates/analytics/changelog/2026-06-26-session-replay-playback-can-now-be-toggled-by-clicking-the-r.md +6 -0
  69. package/corpus/templates/analytics/changelog/2026-06-26-session-replays-now-open-from-the-full-row-and-play-with-tim.md +6 -0
  70. package/corpus/templates/analytics/server/lib/dashboard-report-subscriptions.ts +21 -0
  71. package/corpus/templates/analytics/server/lib/dashboard-report.ts +41 -17
  72. package/corpus/templates/analytics/server/plugins/agent-chat.ts +5 -0
  73. package/dist/action-ui.d.ts +1 -0
  74. package/dist/action-ui.d.ts.map +1 -1
  75. package/dist/action-ui.js +1 -0
  76. package/dist/action-ui.js.map +1 -1
  77. package/dist/client/analytics.d.ts.map +1 -1
  78. package/dist/client/analytics.js +173 -12
  79. package/dist/client/analytics.js.map +1 -1
  80. package/dist/client/chat/index.d.ts +1 -1
  81. package/dist/client/chat/index.d.ts.map +1 -1
  82. package/dist/client/chat/index.js +1 -1
  83. package/dist/client/chat/index.js.map +1 -1
  84. package/dist/client/chat/tool-call-display.d.ts.map +1 -1
  85. package/dist/client/chat/tool-call-display.js +3 -1
  86. package/dist/client/chat/tool-call-display.js.map +1 -1
  87. package/dist/client/chat/widgets/InlineExtensionWidget.d.ts +17 -0
  88. package/dist/client/chat/widgets/InlineExtensionWidget.d.ts.map +1 -0
  89. package/dist/client/chat/widgets/InlineExtensionWidget.js +85 -0
  90. package/dist/client/chat/widgets/InlineExtensionWidget.js.map +1 -0
  91. package/dist/client/chat/widgets/builtin-tool-renderers.d.ts.map +1 -1
  92. package/dist/client/chat/widgets/builtin-tool-renderers.js +15 -2
  93. package/dist/client/chat/widgets/builtin-tool-renderers.js.map +1 -1
  94. package/dist/client/conversation/AgentConversation.d.ts.map +1 -1
  95. package/dist/client/conversation/AgentConversation.js +33 -0
  96. package/dist/client/conversation/AgentConversation.js.map +1 -1
  97. package/dist/client/conversation/code-agent-transcript.js +8 -0
  98. package/dist/client/conversation/code-agent-transcript.js.map +1 -1
  99. package/dist/client/conversation/types.d.ts +4 -0
  100. package/dist/client/conversation/types.d.ts.map +1 -1
  101. package/dist/client/conversation/types.js.map +1 -1
  102. package/dist/client/extensions/EmbeddedExtension.d.ts.map +1 -1
  103. package/dist/client/extensions/EmbeddedExtension.js +29 -1
  104. package/dist/client/extensions/EmbeddedExtension.js.map +1 -1
  105. package/dist/client/extensions/ExtensionViewer.d.ts.map +1 -1
  106. package/dist/client/extensions/ExtensionViewer.js +28 -1
  107. package/dist/client/extensions/ExtensionViewer.js.map +1 -1
  108. package/dist/client/extensions/InlineExtensionFrame.d.ts +25 -0
  109. package/dist/client/extensions/InlineExtensionFrame.d.ts.map +1 -0
  110. package/dist/client/extensions/InlineExtensionFrame.js +488 -0
  111. package/dist/client/extensions/InlineExtensionFrame.js.map +1 -0
  112. package/dist/client/extensions/iframe-bridge.d.ts +2 -0
  113. package/dist/client/extensions/iframe-bridge.d.ts.map +1 -1
  114. package/dist/client/extensions/iframe-bridge.js +23 -0
  115. package/dist/client/extensions/iframe-bridge.js.map +1 -1
  116. package/dist/client/extensions/index.d.ts +1 -0
  117. package/dist/client/extensions/index.d.ts.map +1 -1
  118. package/dist/client/extensions/index.js +1 -0
  119. package/dist/client/extensions/index.js.map +1 -1
  120. package/dist/client/index.d.ts +1 -1
  121. package/dist/client/index.d.ts.map +1 -1
  122. package/dist/client/index.js +1 -1
  123. package/dist/client/index.js.map +1 -1
  124. package/dist/client/session-replay.d.ts +11 -1
  125. package/dist/client/session-replay.d.ts.map +1 -1
  126. package/dist/client/session-replay.js +74 -14
  127. package/dist/client/session-replay.js.map +1 -1
  128. package/dist/code-agents/transcript-normalizer.d.ts +2 -0
  129. package/dist/code-agents/transcript-normalizer.d.ts.map +1 -1
  130. package/dist/code-agents/transcript-normalizer.js +8 -0
  131. package/dist/code-agents/transcript-normalizer.js.map +1 -1
  132. package/dist/collab/awareness.d.ts +2 -2
  133. package/dist/collab/awareness.d.ts.map +1 -1
  134. package/dist/collab/routes.d.ts +1 -1
  135. package/dist/extensions/actions.d.ts.map +1 -1
  136. package/dist/extensions/actions.js +200 -1
  137. package/dist/extensions/actions.js.map +1 -1
  138. package/dist/extensions/html-shell.d.ts.map +1 -1
  139. package/dist/extensions/html-shell.js +70 -3
  140. package/dist/extensions/html-shell.js.map +1 -1
  141. package/dist/extensions/routes.d.ts.map +1 -1
  142. package/dist/extensions/routes.js +11 -4
  143. package/dist/extensions/routes.js.map +1 -1
  144. package/dist/index.browser.d.ts +1 -1
  145. package/dist/index.browser.d.ts.map +1 -1
  146. package/dist/index.browser.js +1 -1
  147. package/dist/index.browser.js.map +1 -1
  148. package/dist/index.d.ts +1 -1
  149. package/dist/index.d.ts.map +1 -1
  150. package/dist/index.js +1 -1
  151. package/dist/index.js.map +1 -1
  152. package/dist/notifications/routes.d.ts +2 -2
  153. package/dist/observability/routes.d.ts +3 -3
  154. package/dist/progress/routes.d.ts +1 -1
  155. package/dist/provider-api/corpus-jobs.d.ts +2 -2
  156. package/dist/resources/handlers.d.ts +3 -3
  157. package/dist/server/agent-chat-plugin.d.ts.map +1 -1
  158. package/dist/server/agent-chat-plugin.js +21 -9
  159. package/dist/server/agent-chat-plugin.js.map +1 -1
  160. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  161. package/dist/server/transcribe-voice.d.ts +1 -1
  162. package/dist/templates/workspace-core/.agents/skills/extensions/SKILL.md +13 -0
  163. package/dist/templates/workspace-core/.agents/skills/extensions/references/api.md +2 -0
  164. package/dist/templates/workspace-core/.agents/skills/generative-ui/SKILL.md +115 -0
  165. package/docs/content/actions.md +4 -2
  166. package/docs/content/agent-surfaces.md +9 -0
  167. package/docs/content/extensions.md +10 -0
  168. package/docs/content/generative-ui.md +217 -0
  169. package/docs/content/key-concepts.md +2 -2
  170. package/docs/content/mcp-apps.md +3 -5
  171. package/docs/content/native-chat-ui.md +9 -5
  172. package/docs/content/tracking.md +9 -3
  173. package/package.json +1 -1
  174. package/src/templates/workspace-core/.agents/skills/extensions/SKILL.md +13 -0
  175. package/src/templates/workspace-core/.agents/skills/extensions/references/api.md +2 -0
  176. package/src/templates/workspace-core/.agents/skills/generative-ui/SKILL.md +115 -0
@@ -104,6 +104,7 @@ export {
104
104
  ACTION_CHAT_UI_DATA_INSIGHTS_RENDERER,
105
105
  ACTION_CHAT_UI_DATA_TABLE_RENDERER,
106
106
  ACTION_CHAT_UI_DATA_WIDGET_RENDERER,
107
+ ACTION_CHAT_UI_INLINE_EXTENSION_RENDERER,
107
108
  type ActionChatUIConfig,
108
109
  } from "./action-ui.js";
109
110
  export {
@@ -58,6 +58,7 @@ export {
58
58
  ACTION_CHAT_UI_DATA_INSIGHTS_RENDERER,
59
59
  ACTION_CHAT_UI_DATA_TABLE_RENDERER,
60
60
  ACTION_CHAT_UI_DATA_WIDGET_RENDERER,
61
+ ACTION_CHAT_UI_INLINE_EXTENSION_RENDERER,
61
62
  type ActionChatUIConfig,
62
63
  } from "./action-ui.js";
63
64
  export {
@@ -3036,21 +3036,29 @@ function buildFrameworkPrompts(
3036
3036
  ? "source-code handoffs and app-created artifacts such as extensions, widgets, dashboards, calculators, mini-apps, documents, designs, slides, or videos"
3037
3037
  : "source-code handoffs and app-created artifacts such as documents, designs, slides, or videos";
3038
3038
  const planModeBlockedTools = extensionToolsEnabled
3039
- ? "`create-extension`, `update-extension`, `connect-builder`, or any action that creates, updates, deletes, sends, publishes, or persists data"
3039
+ ? "`render-inline-extension`, `create-extension`, `update-extension`, `connect-builder`, or any action that creates, updates, deletes, sends, publishes, or persists data"
3040
3040
  : "`connect-builder`, or any action that creates, updates, deletes, sends, publishes, or persists data";
3041
3041
  const extensionConnectBuilderGuard = extensionToolsEnabled
3042
- ? "If the request matches the Extensions section above, use `create-extension` or `update-extension` instead — do NOT route it to `connect-builder`."
3042
+ ? "If the request matches the Extensions section above, use `render-inline-extension`, `create-extension`, `show-extension-inline`, or `update-extension` instead — do NOT route it to `connect-builder`."
3043
3043
  : "Because extension tools are disabled, do NOT invent an extension workflow. Only use `connect-builder` when the request genuinely requires changing the host app's source code.";
3044
3044
  const extensionInstructionsFull = extensionToolsEnabled
3045
- ? `### Extensions (Mini-Apps) — Use \`create-extension\` for extensions / widgets / dashboards
3045
+ ? `### Generative UI and Extensions (Mini-Apps)
3046
3046
 
3047
- In Act mode, if the user asks you to create, build, or make an **extension**, **widget**, **dashboard**, **calculator**, **mini-app**, or any small self-contained interactive utility — call \`create-extension\` immediately with a self-contained Alpine.js HTML body. This is **NOT** a code change and does **NOT** go through \`connect-builder\`. Extensions are sandboxed mini-apps stored in the database — no source files are touched, no PR is opened, no build is required. The extension appears in the Extensions view and can be edited later via \`update-extension\`.
3047
+ In Act mode, if the user asks for generated interactive UI in chat, choose the smallest extension action that matches the lifetime:
3048
+
3049
+ - For a **one-time inline UI** that answers the current chat turn (knobs, controls, pickers, calculators, temporary dashboards, visualizers), call \`render-inline-extension\` immediately with a self-contained Alpine.js HTML body. It renders inside the transcript and is not saved.
3050
+ - For a **reusable or saved UI** (an extension/widget/dashboard/calculator/mini-app the user can reopen from Extensions), call \`create-extension\` with a self-contained Alpine.js HTML body. It saves to the Extensions view and also renders inline in chat.
3051
+ - To **reuse an existing saved extension inline**, call \`show-extension-inline\` with its id, or a search string when the id is unknown.
3052
+
3053
+ These are **NOT** source-code changes and do **NOT** go through \`connect-builder\`. Extensions are sandboxed mini-apps — no source files are touched, no PR is opened, no build is required. Saved extensions can be edited later via \`update-extension\`.
3048
3054
 
3049
3055
  Keep \`create-extension\` payloads compact enough to finish quickly. For complex extensions, create a useful working v1 first, then call \`update-extension\` with focused edits for refinements instead of trying to assemble one enormous initial tool input.
3050
3056
 
3057
+ Generated UI content can use appAction(), appFetch(), dbQuery(), dbExec(), extensionFetch(), extensionData, agentNative.ui.output(value, opts?), and agentNative.chat.send(...)/sendToAgentChat(...). It can receive chat inputs through slotContext/window.onSlotContext. Use agentNative.ui.output for passive current values from knobs, sliders, selections, and controls; it writes application state at \`inline-ui:<extensionId>:output\` scoped to the inline extension id returned by \`render-inline-extension\` or \`show-extension-inline\`. When the user later says "use that value", "apply the current setting", or similar, read it with \`readAppState("inline-ui:<id>:output")\` instead of asking them to send it again. Use agentNative.chat.send for visible submit/apply actions that should put a message into chat. Transient extensionData is browser-local and not agent-readable, synced, promoted, or garbage-collected; use application_state/appFetch, appAction, ui.output, or chat.send for anything the agent or app must observe. Use semantic Tailwind classes like bg-background, text-foreground, bg-primary, border-border, and text-muted-foreground so the UI inherits the parent app theme.
3058
+
3051
3059
  If the user asks to change, edit, fix, style, rename, or add behavior to an existing extension/widget/dashboard/calculator/mini-app, use the current extension id from \`<current-screen>\` or \`<current-url>\` when present. Call \`get-extension\` only if you need to inspect its content, then \`update-extension\` with that id. Use \`list-extensions\` only when no current id/name is available. Existing extension edits are SQL data updates, not source-code changes, even when the request says "change the UI" or "fix this". Do **NOT** call \`connect-builder\` for existing extension edits.
3052
3060
 
3053
- In Act mode, when in doubt — if the request mentions creating an extension, widget, dashboard, calculator, or asks for a new small interactive utility choose \`create-extension\`. If it references an existing one or the current extension page, choose \`update-extension\`. Do **not** preface the call with planning text like "let me build the dashboard…" — just call the right extension action directly.
3061
+ In Act mode, when in doubt — if the request asks for a new small interactive utility and does not need reuse, choose \`render-inline-extension\`; if it mentions saving/reuse or asks for an extension/widget/dashboard/calculator/mini-app, choose \`create-extension\`. If it references an existing one or the current extension page, choose \`update-extension\`. Do **not** preface the call with planning text like "let me build the dashboard…" — just call the right extension action directly.
3054
3062
 
3055
3063
  Note: "extension" is the user-facing primitive (the sandboxed Alpine.js mini-app). Don't confuse it with the LLM concept of "tools" (function calls) — those are how you invoke ANY action, including \`create-extension\` itself.
3056
3064
 
@@ -3063,10 +3071,12 @@ Route by what the request changes, not how it is phrased. Extensions render in t
3063
3071
  <routing>
3064
3072
  | The request is for… | Path |
3065
3073
  | ---------------------------------------------------------------- | ----------------------------- |
3074
+ | A one-off interactive answer inside chat (controls, picker, calculator, temporary visualizer) | \`render-inline-extension\` — inline only |
3066
3075
  | A new self-contained surface (widget, dashboard, calculator, viewer, list, tracker) | \`create-extension\` — ships instantly, no PR |
3076
+ | Loading a saved extension inside chat | \`show-extension-inline\` |
3067
3077
  | Editing an existing extension (fix, restyle, rename, add behavior) | \`update-extension\` |
3068
3078
  | The host app's own chrome (nav bar, sidebar, layout, routes, shipped components, existing styles, business logic) | \`connect-builder\` — a real source-code change |
3069
- | Ambiguous, satisfiable either way (e.g. "give me an unread view") | \`create-extension\` (prefer the instant path) |
3079
+ | Ambiguous, satisfiable either way (e.g. "give me an unread view") | \`render-inline-extension\` for chat-only, \`create-extension\` for reusable |
3070
3080
  </routing>
3071
3081
 
3072
3082
  Worked examples: "a widget showing unread emails grouped by sender", "a dashboard summarizing my pipeline", "a tracker for my newsletter subscriptions" → \`create-extension\`. "Add an Unread tab to the left navigation", "make the subject lines wrap", "change the inbox grouping logic", "add a field to the compose form" → \`connect-builder\`.`
@@ -3074,19 +3084,21 @@ Worked examples: "a widget showing unread emails grouped by sender", "a dashboar
3074
3084
 
3075
3085
  Extension creation and management tools are disabled for this app. Do not claim you can create, edit, hide, or delete Agent-Native extensions unless the template exposes its own typed action for that workflow. For requests that would otherwise be handled as an extension/widget/dashboard/calculator mini-app, explain that this app has disabled extension tools and use the app's available actions instead.`;
3076
3086
  const extensionInstructionsCompact = extensionToolsEnabled
3077
- ? `### Extensions (Mini-Apps) — Use \`create-extension\`
3087
+ ? `### Generative UI and Extensions (Mini-Apps)
3078
3088
 
3079
- In Act mode, if the user asks for an **extension**, **widget**, **dashboard**, **calculator**, or **mini-app**, call \`create-extension\` immediately with a self-contained Alpine.js HTML body. This is NOT a code change — extensions are sandboxed mini-apps stored in the database. Do not preface with "let me build…" — just call \`create-extension\`.
3089
+ In Act mode, if the user asks for generated interactive UI in chat, call \`render-inline-extension\` for one-time inline controls/knobs/calculators/visualizers that do not need saving. If the user asks for an **extension**, **widget**, **dashboard**, **calculator**, or **mini-app** that should be reusable or saved, call \`create-extension\` with a self-contained Alpine.js HTML body. To load a saved extension inline, call \`show-extension-inline\`. These are NOT code changes — extensions are sandboxed mini-apps. Do not preface with "let me build…" — just call the right extension action.
3080
3090
 
3081
3091
  Keep the first \`create-extension\` call compact and working. If the request is complex, create the v1 first and then refine with focused \`update-extension\` edits.
3082
3092
 
3093
+ Generated UI can read chat inputs from slotContext/window.onSlotContext, see/update app state through appFetch/appAction, use extensionData, record passive current values through agentNative.ui.output(value, opts?), and send visible results through agentNative.chat.send(...) or sendToAgentChat(...). ui.output writes \`inline-ui:<extensionId>:output\` in application state; when the user asks to use the current slider/selection/value, read \`readAppState("inline-ui:<id>:output")\`. Transient extensionData is browser-local only, so do not rely on it for values the agent or app must observe. Use semantic Tailwind theme classes.
3094
+
3083
3095
  If the user asks to change, edit, fix, style, rename, or add behavior to an existing extension/widget/dashboard/calculator/mini-app, use the current extension id from \`<current-screen>\` or \`<current-url>\` when present. Call \`get-extension\` only if you need to inspect its content, then \`update-extension\` with that id. Use \`list-extensions\` only when no current id/name is available. Existing extension edits are SQL data updates, not source-code changes. Do NOT call \`connect-builder\` for them.
3084
3096
 
3085
3097
  For existing extensions, use \`get-extension\` or \`update-extension\` directly when \`<current-screen>\` or \`<current-url>\` provides an \`extensionId\`. Use \`list-extensions\` only to browse or resolve an unknown name. Use \`hide-extension\` when the user wants a shared extension removed only from their own view. Do not query the legacy \`tools\` table directly.
3086
3098
 
3087
3099
  ### Extensions vs. Code Changes — Pick the Right Path
3088
3100
 
3089
- If the user wants a **new self-contained surface** (custom widget, dashboard, list, viewer, calculator), use \`create-extension\` — extensions ship instantly without a PR. Use \`connect-builder\` only when the request **modifies the host app's existing chrome** (nav bar, sidebar, current components, layout, styles, routes). Extensions cannot change the host nav or restyle existing components.`
3101
+ If the user wants a **one-off interactive answer in chat**, use \`render-inline-extension\`. If they want a **new reusable self-contained surface** (custom widget, dashboard, list, viewer, calculator), use \`create-extension\` — extensions ship instantly without a PR. Use \`connect-builder\` only when the request **modifies the host app's existing chrome** (nav bar, sidebar, current components, layout, styles, routes). Extensions cannot change the host nav or restyle existing components.`
3090
3102
  : `### Extensions Disabled
3091
3103
 
3092
3104
  Extension creation and management tools are disabled for this app. Do not claim you can create, edit, hide, or delete Agent-Native extensions unless the template exposes its own typed action for that workflow.`;
@@ -73,6 +73,19 @@ Extensions have full access to app data via helpers injected into the iframe
73
73
  per-extension (supports `{ scope: 'user' | 'org' | 'all' }` option). Legacy
74
74
  alias: `toolData` — kept for back-compat; both names refer to the same
75
75
  store.
76
+ - `agentNative.ui.output(value, opts?)` — when an extension is rendered inline
77
+ in chat, record passive control/selection output at
78
+ `inline-ui:<extensionId>:output` in application state so the agent can read it
79
+ later with `readAppState`.
80
+ - `agentNative.chat.send(message, opts?)` — send a visible prompt or selected
81
+ value back into the current agent chat.
82
+
83
+ For transient inline generative UI, `extensionData` is host-browser
84
+ `localStorage`: the agent cannot read it, it does not sync across devices, it
85
+ does not migrate when the UI is saved later, and the server does not garbage
86
+ collect it. Use it only for throwaway local UI state. Use application state,
87
+ `agentNative.ui.output`, `appAction`, or `agentNative.chat.send` for anything
88
+ the agent or app must observe.
76
89
 
77
90
  ## Data Persistence is Built In
78
91
 
@@ -105,6 +105,8 @@ await dbExec("UPDATE notes SET title = 'Updated Title' WHERE id = 'abc'");
105
105
  | `extensionData.list(collection, opts?)` | List items in a collection | `extensionData.list('todos', { scope: 'all' })` |
106
106
  | `extensionData.get(collection, id, opts?)` | Get a single item by id | `extensionData.get('todos', 'todo-1')` |
107
107
  | `extensionData.remove(collection, id, opts?)` | Delete an item | `extensionData.remove('todos', 'todo-1')` |
108
+ | `agentNative.ui.output(value, opts?)` | Record passive inline UI output in application state | `agentNative.ui.output({ threshold })` |
109
+ | `agentNative.chat.send(message, opts?)` | Send a visible prompt or selected value back to chat | `agentNative.chat.send('Use Q2', { context: { q: 2 } })` |
108
110
 
109
111
  ## Persisting Custom Data
110
112
 
@@ -0,0 +1,115 @@
1
+ ---
2
+ name: generative-ui
3
+ description: >-
4
+ Generate transient or saved inline chat UI with Alpine/Tailwind controls,
5
+ outputs, app state, and extensions. Use for knobs, pickers, calculators,
6
+ dashboards, widgets, or reusable mini-apps.
7
+ ---
8
+
9
+ # Generative UI
10
+
11
+ Generative UI is sandboxed Alpine.js HTML rendered inline in chat. It is not a
12
+ source-code change and does not go through Builder.
13
+
14
+ ## Pick the Lifetime
15
+
16
+ - Use `render-inline-extension` for one-off UI that belongs only in the current
17
+ chat: knobs, controls, pickers, calculators, temporary charts, and previews.
18
+ - Use `create-extension` when the UI should be saved, reusable, or visible in
19
+ the Extensions view. It also renders inline after creation.
20
+ - Use `show-extension-inline` to reopen a saved extension inside chat.
21
+ - Use `update-extension` to edit an existing saved extension.
22
+
23
+ ## Inputs From Chat
24
+
25
+ Pass initial values through the action `context` argument. The iframe reads them
26
+ from `window.slotContext` and can subscribe to changes with
27
+ `window.onSlotContext(fn)`.
28
+
29
+ ```html
30
+ <script>
31
+ Alpine.data("controls", () => ({
32
+ threshold: 50,
33
+ init() {
34
+ this.threshold = Number(window.slotContext?.threshold ?? 50);
35
+ window.onSlotContext?.((ctx) => {
36
+ if (ctx.threshold !== undefined) {
37
+ this.threshold = Number(ctx.threshold);
38
+ }
39
+ });
40
+ },
41
+ }));
42
+ </script>
43
+ ```
44
+
45
+ ## Outputs To The Agent
46
+
47
+ Use passive output when a control's current value should be visible to the agent
48
+ on the next turn without requiring a submit click:
49
+
50
+ ```html
51
+ <input
52
+ type="range"
53
+ min="0"
54
+ max="100"
55
+ x-model.number="threshold"
56
+ @input="agentNative.ui.output({ threshold }, { label: 'Threshold' })"
57
+ />
58
+ ```
59
+
60
+ `agentNative.ui.output(value, opts?)` writes application state at:
61
+
62
+ ```txt
63
+ inline-ui:<extensionId>:output
64
+ ```
65
+
66
+ The id is scoped automatically; the HTML author must not pass it. When the user
67
+ says "use that value", "apply the current setting", or "run it with the current
68
+ selection", read the value with:
69
+
70
+ ```ts
71
+ const output = await readAppState("inline-ui:<id>:output");
72
+ ```
73
+
74
+ The stored payload includes `value`, `updatedAt`, `extensionId`, and
75
+ `source: "inline-ui"`, plus optional `label`, `context`, or `meta`.
76
+
77
+ Use `agentNative.chat.send(message, opts?)` or `sendToAgentChat(message, opts?)`
78
+ for a visible Apply/Submit button that should send a prompt or selected value
79
+ into the chat.
80
+
81
+ ## App Data And State
82
+
83
+ - Prefer `appAction(name, params)` for template data and operations.
84
+ - Use `appFetch(path, options)` only for allowed framework endpoints under
85
+ `/_agent-native/*`, including application-state reads and writes.
86
+ - Do not call template `/api/*` routes from generated UI.
87
+ - Use `dbQuery`/`dbExec` only for known existing SQL tables when no action fits.
88
+ - Use `extensionFetch` for external APIs through the sandbox proxy and secrets.
89
+
90
+ ## Transient extensionData Boundary
91
+
92
+ For saved extensions, `extensionData` uses the authenticated database-backed
93
+ extension data API.
94
+
95
+ For transient inline UIs, `extensionData` is host `localStorage`. The agent
96
+ cannot read it, it does not sync across devices, it does not migrate when a UI
97
+ is promoted to a saved Extension, and the server does not garbage-collect it.
98
+ Use it only for throwaway local UI state.
99
+
100
+ For anything the agent or app must observe, use `agentNative.ui.output`,
101
+ application state through `appFetch`, an `appAction`, or
102
+ `agentNative.chat.send`.
103
+
104
+ ## Styling
105
+
106
+ Use semantic Tailwind classes so the iframe inherits the parent app theme:
107
+
108
+ - `bg-background`, `text-foreground`, `border-border`
109
+ - `bg-card`, `text-card-foreground`
110
+ - `bg-primary`, `text-primary-foreground`
111
+ - `text-muted-foreground`, `bg-accent`
112
+
113
+ Do not hardcode provider tokens, private data, or internal secrets in generated
114
+ HTML. Do not call an LLM directly from the iframe; route AI work through the
115
+ agent chat.
@@ -23,13 +23,17 @@ Current storage:
23
23
 
24
24
  Legacy settings keys such as `u:<email>:dashboard-*`, `u:<email>:sql-dashboard-*`, `o:<orgId>:sql-dashboard-*`, and `adhoc-analysis-*` are still read as a fallback and copied into SQL on access. Do not create new dashboard settings rows.
25
25
 
26
- Use `update-dashboard` for dashboard edits. It resolves the current user/org context, validates the config, applies JSON-pointer operations when provided, writes the SQL-backed record, and preserves sharing semantics.
26
+ Use `mutate-dashboard` for existing dashboard edits. It resolves the current
27
+ user/org context, validates the resulting config, writes the SQL-backed record,
28
+ syncs collab, and returns compact proof. Use `update-dashboard` for new
29
+ full-config saves, UI full-config saves, or explicitly requested low-level
30
+ JSON-pointer edits.
27
31
 
28
32
  Never use `db-patch`, raw SQL, or settings-key edits to create or modify a
29
33
  dashboard config. Those bypass the dashboard action's access checks, SQL
30
- validation, collab sync, and proof-of-done return. If an `update-dashboard`
31
- call fails because the argument shape was wrong, fix the `update-dashboard`
32
- arguments and retry once — do not switch tools.
34
+ validation, collab sync, and proof-of-done return. If a dashboard action fails
35
+ because the argument shape was wrong, fix that action's arguments and retry
36
+ once — do not switch to db-patch or raw SQL.
33
37
 
34
38
  ## Valid Panel Sources
35
39
 
@@ -68,7 +72,7 @@ The save path dry-runs BigQuery panels before persisting. If validation returns
68
72
  ## When To Use An Extension Instead
69
73
 
70
74
  Native Analytics dashboards are JSON configs rendered by the built-in dashboard
71
- components. Use `update-dashboard` only when the request fits that model:
75
+ components. Use native dashboard actions only when the request fits that model:
72
76
  standard panels, supported chart types, filters, variables, sections, and grid
73
77
  layout.
74
78
 
@@ -167,71 +171,196 @@ Filters auto-apply on change — there is no Apply button. Each filter change wr
167
171
 
168
172
  ## Modifying A Dashboard
169
173
 
170
- Preferred patterns:
174
+ For existing dashboard edits, default to `mutate-dashboard`. It gives the
175
+ agent a small typed script API without exposing arbitrary JavaScript execution.
176
+ The main action payload is a string, so it avoids native-array serialization
177
+ traps in tool calls while still giving the agent a code-like editing surface.
178
+ The server parses only documented `dashboard.*` method calls, applies the
179
+ resulting operations in memory, validates the final dashboard config, writes
180
+ SQL once, syncs collab, and returns compact proof.
181
+
182
+ Arguments must be JSON-compatible literals, so quote object keys. Variables,
183
+ imports, loops, functions, templates, network, filesystem, DB access, and
184
+ calling other actions from the script are not available.
185
+
186
+ ```ts
187
+ type DashboardMutationApi = {
188
+ dashboard: {
189
+ set(patch: DashboardPatch): void;
190
+ panel(id: string): PanelSelection;
191
+ section(id: string): SectionSelection;
192
+ panels(ids: string[]): PanelSelection;
193
+ panelsMatching(filter: PanelFilter): PanelSelection;
194
+ insertPanel(panel: PanelInput): InsertedPanel;
195
+ };
196
+ };
197
+
198
+ type DashboardPatch = {
199
+ name?: string;
200
+ description?: string;
201
+ columns?: number;
202
+ filters?: unknown[];
203
+ variables?: Record<string, string>;
204
+ };
205
+
206
+ type PanelPatch = {
207
+ title?: string;
208
+ sql?: string;
209
+ source?: "bigquery" | "ga4" | "amplitude" | "first-party" | "demo" | "prometheus";
210
+ chartType?: "line" | "area" | "bar" | "metric" | "table" | "pie" | "section" | "heatmap" | "callout";
211
+ width?: number;
212
+ columns?: number;
213
+ tab?: string;
214
+ config?: Record<string, unknown>;
215
+ description?: string;
216
+ };
217
+
218
+ type PanelInput = PanelPatch & {
219
+ id: string;
220
+ title: string;
221
+ chartType: NonNullable<PanelPatch["chartType"]>;
222
+ source?: PanelPatch["source"]; // required for non-section panels
223
+ sql?: string; // required for non-section panels
224
+ };
225
+
226
+ type PanelFilter = {
227
+ id?: string;
228
+ ids?: string[];
229
+ idIncludes?: string;
230
+ title?: string;
231
+ titleIncludes?: string;
232
+ chartType?: PanelPatch["chartType"];
233
+ source?: PanelPatch["source"];
234
+ tab?: string;
235
+ isSection?: boolean;
236
+ };
237
+
238
+ type PanelSelection = {
239
+ moveToTop(): void;
240
+ moveToBottom(): void;
241
+ moveBefore(panelId: string): void;
242
+ moveAfter(panelId: string): void;
243
+ moveToIndex(index: number): void;
244
+ remove(): void;
245
+ set(patch: PanelPatch): void;
246
+ setTitle(title: string): void;
247
+ setSql(sql: string): void;
248
+ setWidth(width: number): void;
249
+ setConfig(patch: Record<string, unknown>): void;
250
+ setConfigPath(path: string, value: unknown): void;
251
+ duplicate(newPanelId: string, patch?: PanelPatch): void;
252
+ };
253
+
254
+ type SectionSelection = PanelSelection & {
255
+ append(panelIds: string[]): void;
256
+ };
257
+
258
+ type InsertedPanel = {
259
+ atTop(): void;
260
+ atBottom(): void;
261
+ before(panelId: string): void;
262
+ after(panelId: string): void;
263
+ atIndex(index: number): void;
264
+ };
265
+ ```
171
266
 
172
- ```bash
173
- # JSON-pointer style patch
174
- pnpm action update-dashboard --dashboardId weekly-metrics \
175
- --ops '[{"op":"replace","path":"/panels/0/title","value":"Events by Day"}]'
267
+ Examples:
268
+
269
+ ```ts
270
+ dashboard.panels(["dau-over-time", "wau-over-time"]).moveToTop();
271
+ dashboard.panel("top-referrers").setTitle("Top Referrers by Domain");
272
+ dashboard.panel("retention").set({
273
+ "width": 2,
274
+ "config": { "description": "Updated definition." }
275
+ });
276
+ dashboard.panelsMatching({ "source": "first-party" }).setWidth(2);
277
+ dashboard.panelsMatching({ "titleIncludes": "Revenue" }).setConfigPath(
278
+ "yAxis.format",
279
+ "currency"
280
+ );
281
+ dashboard.panelsMatching({ "titleIncludes": "Signed-In" }).moveToTop();
282
+ dashboard.section("retention-activity-section").append([
283
+ "repeat-users",
284
+ "retention-over-time"
285
+ ]);
286
+ dashboard.insertPanel({
287
+ "id": "new-kpi",
288
+ "title": "New KPI",
289
+ "source": "first-party",
290
+ "chartType": "metric",
291
+ "width": 1,
292
+ "sql": "SELECT COUNT(*) AS value FROM analytics_events"
293
+ }).atTop();
294
+ ```
176
295
 
177
- # Full config replacement
178
- pnpm action update-dashboard --dashboardId weekly-metrics --config '<full json>'
296
+ Native tool call:
297
+
298
+ ```json
299
+ {
300
+ "dashboardId": "weekly-metrics",
301
+ "code": "dashboard.panels([\"dau-over-time\",\"wau-over-time\"]).moveToTop();"
302
+ }
179
303
  ```
180
304
 
181
- After a mutation, navigate to the dashboard if the user is elsewhere. The app syncs through the framework's polling/query invalidation path.
305
+ Use `update-dashboard` only for new full-config saves, UI full-config saves, or
306
+ when the user specifically requests low-level JSON-pointer edits.
182
307
 
183
- ### Existing Dashboard Edits
308
+ `get-sql-dashboard` is compact by default. It returns panel summaries, ids,
309
+ titles, chart types, sources, layout groups, `layout.panelOrder`, and
310
+ `layout.firstPanelIds` without embedding every panel's full SQL. Use that
311
+ compact result to find panel ids and verify order. Pass `includeConfig: true`
312
+ only when you need full panel SQL/config for a detailed edit.
184
313
 
185
- When the user asks to change existing panels:
314
+ After a mutation, navigate to the dashboard if the user is elsewhere. The app syncs through the framework's polling/query invalidation path.
186
315
 
187
- 1. Read the current dashboard config through the dashboard/action surface.
188
- 2. Find panel indexes by `panel.id` from the current config you just read. Do
189
- not rely on seed-file order, stale memory, or screenshots.
190
- 3. Build one `ops` array that includes every change.
191
- 4. Call `update-dashboard` once.
192
- 5. Verify the returned `panelCount`, `appliedOps`, and `summary`. If possible,
193
- read the affected panels back and confirm the exact fields changed.
316
+ ### Reordering Panels
194
317
 
195
- For native production tools, pass `ops` as a native array:
318
+ For simple "move this chart/section" requests, prefer `mutate-dashboard` with a
319
+ string script:
196
320
 
197
321
  ```json
198
322
  {
199
323
  "dashboardId": "weekly-metrics",
200
- "ops": [
201
- {
202
- "op": "replace",
203
- "path": "/panels/3/sql",
204
- "value": "SELECT COUNT(*) AS value FROM analytics_events"
205
- },
206
- {
207
- "op": "replace",
208
- "path": "/panels/3/config/description",
209
- "value": "Updated definition."
210
- }
211
- ]
324
+ "code": "dashboard.panels([\"dau-over-time\",\"wau-over-time\"]).moveToTop();"
212
325
  }
213
326
  ```
214
327
 
215
- The quoted JSON examples in this skill are for shell commands only. In native
216
- tool calls, do not pass `ops` as a string. If the tool complains about the
217
- shape, retry `update-dashboard` with a native array and continue from the same
218
- dashboard config.
328
+ Do not do index arithmetic with `/panels/<index>` unless the user specifically
329
+ asks for a low-level JSON-pointer edit. Use `moveBefore`, `moveAfter`,
330
+ `moveToTop`, `moveToBottom`, or `moveToIndex` against panel ids instead.
331
+
332
+ `get-sql-dashboard` returns `layout.panelOrder`, `layout.firstPanelIds`, and
333
+ row/group summaries. Use those fields for orientation and verification instead
334
+ of re-reading stale screenshots or counting positions from memory.
335
+
336
+ ### Existing Dashboard Edits
337
+
338
+ When the user asks to change existing panels:
219
339
 
220
- For SQL-only panel edits, replacing `/panels/<index>/sql` is enough. If the
221
- metric semantics changed, also replace `/panels/<index>/config/description` so
222
- the visible dashboard explains the new definition. If the title, source, chart
223
- type, width, or config shape changes together, replace the whole panel object at
224
- `/panels/<index>` in the same `ops` array.
340
+ 1. Read the current dashboard with `get-sql-dashboard` compact mode unless full
341
+ SQL/config is required.
342
+ 2. Call `mutate-dashboard` once with every change in one script. Use
343
+ `panel(...)`, `panels([...])`, or `panelsMatching({...})` selectors by id or
344
+ metadata; do not compute shifted array indexes.
345
+ 3. Verify the returned `panelCount`, `appliedOps`, `firstPanelIds`, and
346
+ `summary`. If possible, read the affected panels back and confirm the exact
347
+ fields changed.
348
+
349
+ For SQL-only panel edits, use `dashboard.panel("id").setSql("...")`. If the
350
+ metric semantics changed, also update the visible definition with
351
+ `setConfigPath("description", "...")` or `set({ "description": "..." })`. If
352
+ the title, source, chart type, width, or config shape changes together, put them
353
+ in the same `set({...})` call.
225
354
 
226
355
  ### First-Party User Metrics
227
356
 
228
357
  For first-party `/track` events, be precise about identity:
229
358
 
230
- | Metric intent | Identity expression |
231
- | -------------------------------------------- | -------------------------------------------------------- |
232
- | Account users, DAU, WAU, retention, cohorts | `NULLIF(user_id, '')` plus `NULLIF(user_id, '') IS NOT NULL`, but only on events that actually represent the activity being measured |
359
+ | Metric intent | Identity expression |
360
+ | -------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
361
+ | Account users, DAU, WAU, retention, cohorts | `NULLIF(user_id, '')` plus `NULLIF(user_id, '') IS NOT NULL`, but only on events that actually represent the activity being measured |
233
362
  | Signed-in visitor activity | `event_name = 'session status' AND signed_in = 'true'` keyed by `COALESCE(NULLIF(user_id, ''), NULLIF(anonymous_id, ''))`, labeled as signed-in visitors rather than account users |
234
- | Public traffic, visitors, clip/share viewers | `COALESCE(NULLIF(user_id, ''), NULLIF(anonymous_id, ''))` |
363
+ | Public traffic, visitors, clip/share viewers | `COALESCE(NULLIF(user_id, ''), NULLIF(anonymous_id, ''))` |
235
364
 
236
365
  Do not call anonymous visitors "users" in dashboard labels or descriptions.
237
366
  When a user asks for DAU, WAU, retention, repeat users, or account cohorts,
@@ -272,18 +401,26 @@ This is the dashboard-specific application of the framework-wide `reliable-mutat
272
401
  Hosted agent runs have a **~40s budget**. Many sequential `update-dashboard` calls (one per panel, plus schema-discovery calls) will blow that budget and leave the dashboard in a partial state — earlier inserts looked like they succeeded (✓), but nothing actually persisted. Avoid this:
273
402
 
274
403
  - **For a large first-party dashboard, use `compose-dashboard`** (see the section above): name the metrics, the server generates the panels in one call. Do not hand-author the big config.
275
- - **Batch ALL changes into ONE `update-dashboard` call.** A single `update-dashboard` is atomic: it applies every op to an in-memory config, validates all panel SQL, then upserts once. Never loop the action.
276
- - To add N panels, pass N ops in one call: `ops: [{op:"insert", path:"/panels/-", value:<panel>}, ]` (`/panels/-` appends to the end).
277
- - The `ops` format needs no discovery: each op is `{ op, path, from?, value? }`, `op ∈ set | replace | remove | insert | move | move-before`, and `path` is a JSON Pointer (e.g. `/panels/3`, `/panels/3/title`, `/name`).
278
- - In native tool calls, `ops` is an array, not a JSON string. Shell commands quote JSON only because shells need strings.
404
+ - **Batch ALL edits into ONE `mutate-dashboard` call.** One script can move,
405
+ insert, remove, duplicate, and update many panels. Never loop dashboard edit
406
+ actions panel-by-panel.
407
+ - To bulk edit existing panels, use selectors:
408
+ `dashboard.panelsMatching({"source":"first-party"}).setWidth(2);`
409
+ - To make nested config edits, use
410
+ `setConfigPath("yAxis.format", "percent")` instead of resending/clobbering
411
+ the whole nested object.
279
412
  - **To add a shipped template's panels, prefer `install-dashboard-template` with `mergePanels: true`** and the existing `dashboardId`. It appends only the template panels whose id is not already present (preserving existing panels and order) in one atomic save — you don't author each panel yourself.
280
- - **Always verify the returned proof-of-done and report it.** `update-dashboard` returns `panelCount`, `appliedOps`, and a `summary` string; `install-dashboard-template --mergePanels` returns `addedPanelIds`, `skippedExistingIds`, and `panelCount`. Tell the user the resulting panel count instead of assuming success.
413
+ - **Always verify the returned proof-of-done and report it.**
414
+ `mutate-dashboard` returns `panelCount`, `appliedOps`, `panelOrder`,
415
+ `firstPanelIds`, `changedPanelIds`, `commandLog`, and a `summary` string.
416
+ `install-dashboard-template --mergePanels` returns `addedPanelIds`,
417
+ `skippedExistingIds`, and `panelCount`. Tell the user the resulting panel
418
+ count instead of assuming success.
281
419
 
282
420
  ```bash
283
- # Add several panels in ONE atomic call (never one call per panel)
284
- pnpm action update-dashboard --dashboardId weekly-metrics \
285
- --ops '[{"op":"insert","path":"/panels/-","value":{"id":"p1","title":"A","source":"first-party","chartType":"metric","width":1,"sql":"SELECT COUNT(*) AS value FROM analytics_events"}},
286
- {"op":"insert","path":"/panels/-","value":{"id":"p2","title":"B","source":"first-party","chartType":"metric","width":1,"sql":"SELECT COUNT(DISTINCT user_id) AS value FROM analytics_events"}}]'
421
+ # Add or edit several panels in ONE atomic call (never one call per panel)
422
+ pnpm action mutate-dashboard --dashboardId weekly-metrics \
423
+ --code 'dashboard.panelsMatching({"source":"first-party"}).setWidth(2);'
287
424
 
288
425
  # Append a template's panels to an existing dashboard in one call
289
426
  pnpm action install-dashboard-template --templateId skills-cli-funnel --dashboardId weekly-metrics --mergePanels true
@@ -318,7 +455,9 @@ Writes require editor access; deletes require admin access. Owners always satisf
318
455
  - Never fabricate data or create a dashboard from guessed schema. A panel's SQL must hit a real source; do not present figures you did not actually query.
319
456
  - Never write dashboard configs into the settings table.
320
457
  - Never use `db-patch` as a fallback for dashboard config edits. Use
321
- `update-dashboard` and fix the action arguments.
458
+ `mutate-dashboard` for existing edits, or `update-dashboard` for new/full
459
+ config saves, and fix the action arguments.
322
460
  - Never set `panel.source` to a table name or unsupported backend.
323
461
  - Use `first-party` for `/track` data and `query-agent-native-analytics` for ad-hoc first-party event questions.
324
- - Use `update-dashboard` for creates and edits.
462
+ - Use `update-dashboard` for new dashboard config saves and full config
463
+ replacements. Use `mutate-dashboard` for existing dashboard edits.
@@ -65,6 +65,18 @@ details live in `.agents/skills/`.
65
65
  - For shipped dashboard templates, call `list-dashboard-templates` first, then
66
66
  `install-dashboard-template` with the selected `templateId`. Do not recreate a
67
67
  catalog template by hand unless the user asks for a custom variant.
68
+ - For dashboard edits, default to `mutate-dashboard` with its typed
69
+ `dashboard.*` script API. It supports id-based panel moves, title/SQL/config
70
+ edits, inserts, duplication, removal, and dashboard field patches in one
71
+ atomic save. The main payload is a string, so it avoids native-array
72
+ serialization traps. The script is constrained: only documented dashboard
73
+ method calls with JSON-compatible arguments are parsed; variables, imports,
74
+ loops, functions, network, filesystem, and DB access are not available.
75
+ - Do not count shifting `/panels/<index>` values for ordinary dashboard edit
76
+ requests. Use low-level JSON-pointer edits only when explicitly requested.
77
+ - `get-sql-dashboard` is compact by default for agents. Use its `panels`
78
+ summaries and `layout.panelOrder` / `layout.firstPanelIds` for orientation and
79
+ proof. Pass `includeConfig: true` only when full panel SQL/config is needed.
68
80
  - Native dashboards and saved analyses are constrained artifacts. If a requested
69
81
  dashboard, analysis surface, visualization, interaction model, custom layout,
70
82
  or bespoke workflow cannot be done faithfully with the built-in dashboard JSON