@agent-native/core 0.78.8 → 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 (181) hide show
  1. package/corpus/README.md +2 -2
  2. package/corpus/core/CHANGELOG.md +25 -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/mcp/build-server.ts +1 -1
  35. package/corpus/core/src/mcp/embed-app.ts +11 -1
  36. package/corpus/core/src/server/agent-chat-plugin.ts +21 -9
  37. package/corpus/core/src/templates/workspace-core/.agents/skills/extensions/SKILL.md +13 -0
  38. package/corpus/core/src/templates/workspace-core/.agents/skills/extensions/references/api.md +2 -0
  39. package/corpus/core/src/templates/workspace-core/.agents/skills/generative-ui/SKILL.md +115 -0
  40. package/corpus/templates/analytics/.agents/skills/dashboard-management/SKILL.md +198 -59
  41. package/corpus/templates/analytics/AGENTS.md +12 -0
  42. package/corpus/templates/analytics/actions/dashboard-mutation-api.ts +1105 -0
  43. package/corpus/templates/analytics/actions/dashboard-panel-order.ts +169 -0
  44. package/corpus/templates/analytics/actions/get-sql-dashboard.ts +89 -6
  45. package/corpus/templates/analytics/actions/mutate-dashboard.ts +333 -0
  46. package/corpus/templates/analytics/actions/reorder-dashboard-panels.ts +214 -0
  47. package/corpus/templates/analytics/actions/send-dashboard-report-now.ts +48 -21
  48. package/corpus/templates/analytics/actions/update-dashboard.ts +193 -100
  49. package/corpus/templates/analytics/app/components/dashboard/SqlChart.tsx +11 -0
  50. package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +1 -1
  51. package/corpus/templates/analytics/app/global.css +81 -10
  52. package/corpus/templates/analytics/app/i18n-data.ts +249 -4
  53. package/corpus/templates/analytics/app/pages/adhoc/explorer-dashboard/ChartCard.tsx +3 -3
  54. package/corpus/templates/analytics/app/pages/adhoc/explorer-dashboard/index.tsx +41 -1
  55. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/EmailReportDialog.tsx +1 -1
  56. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/PanelEditorDialog.tsx +4 -4
  57. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/SqlChartCard.tsx +48 -18
  58. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/dashboard-layout.ts +32 -0
  59. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +204 -70
  60. package/corpus/templates/analytics/app/pages/sessions/SessionDetailPage.tsx +1101 -227
  61. package/corpus/templates/analytics/app/pages/sessions/SessionsPage.tsx +160 -133
  62. package/corpus/templates/analytics/changelog/2026-06-26-agents-can-now-edit-dashboards-through-a-typed-mutation-scri.md +6 -0
  63. package/corpus/templates/analytics/changelog/2026-06-26-dashboard-bar-chart-hovers-stay-subtle-in-dark-mode.md +6 -0
  64. package/corpus/templates/analytics/changelog/2026-06-26-dashboard-chart-moves-are-now-handled-by-panel-id-making-sim.md +6 -0
  65. package/corpus/templates/analytics/changelog/2026-06-26-dashboard-charts-drag-smoothly-with-stable-cards-inactive-ch.md +6 -0
  66. package/corpus/templates/analytics/changelog/2026-06-26-dashboard-charts-show-a-refresh-spinner-while-keeping-existi.md +6 -0
  67. package/corpus/templates/analytics/changelog/2026-06-26-dashboard-edits-stay-stable-while-chart-order-and-deletions-.md +6 -0
  68. package/corpus/templates/analytics/changelog/2026-06-26-dashboard-email-reports-queue-immediate-sends.md +6 -0
  69. package/corpus/templates/analytics/changelog/2026-06-26-explorer-dashboards-now-drag-charts-with-a-stable-preview-an.md +6 -0
  70. package/corpus/templates/analytics/changelog/2026-06-26-session-replay-playback-can-now-be-toggled-by-clicking-the-r.md +6 -0
  71. package/corpus/templates/analytics/changelog/2026-06-26-session-replays-now-open-from-the-full-row-and-play-with-tim.md +6 -0
  72. package/corpus/templates/analytics/server/lib/dashboard-report-subscriptions.ts +21 -0
  73. package/corpus/templates/analytics/server/lib/dashboard-report.ts +41 -17
  74. package/corpus/templates/analytics/server/plugins/agent-chat.ts +5 -0
  75. package/dist/action-ui.d.ts +1 -0
  76. package/dist/action-ui.d.ts.map +1 -1
  77. package/dist/action-ui.js +1 -0
  78. package/dist/action-ui.js.map +1 -1
  79. package/dist/client/analytics.d.ts.map +1 -1
  80. package/dist/client/analytics.js +173 -12
  81. package/dist/client/analytics.js.map +1 -1
  82. package/dist/client/chat/index.d.ts +1 -1
  83. package/dist/client/chat/index.d.ts.map +1 -1
  84. package/dist/client/chat/index.js +1 -1
  85. package/dist/client/chat/index.js.map +1 -1
  86. package/dist/client/chat/tool-call-display.d.ts.map +1 -1
  87. package/dist/client/chat/tool-call-display.js +3 -1
  88. package/dist/client/chat/tool-call-display.js.map +1 -1
  89. package/dist/client/chat/widgets/InlineExtensionWidget.d.ts +17 -0
  90. package/dist/client/chat/widgets/InlineExtensionWidget.d.ts.map +1 -0
  91. package/dist/client/chat/widgets/InlineExtensionWidget.js +85 -0
  92. package/dist/client/chat/widgets/InlineExtensionWidget.js.map +1 -0
  93. package/dist/client/chat/widgets/builtin-tool-renderers.d.ts.map +1 -1
  94. package/dist/client/chat/widgets/builtin-tool-renderers.js +15 -2
  95. package/dist/client/chat/widgets/builtin-tool-renderers.js.map +1 -1
  96. package/dist/client/conversation/AgentConversation.d.ts.map +1 -1
  97. package/dist/client/conversation/AgentConversation.js +33 -0
  98. package/dist/client/conversation/AgentConversation.js.map +1 -1
  99. package/dist/client/conversation/code-agent-transcript.js +8 -0
  100. package/dist/client/conversation/code-agent-transcript.js.map +1 -1
  101. package/dist/client/conversation/types.d.ts +4 -0
  102. package/dist/client/conversation/types.d.ts.map +1 -1
  103. package/dist/client/conversation/types.js.map +1 -1
  104. package/dist/client/extensions/EmbeddedExtension.d.ts.map +1 -1
  105. package/dist/client/extensions/EmbeddedExtension.js +29 -1
  106. package/dist/client/extensions/EmbeddedExtension.js.map +1 -1
  107. package/dist/client/extensions/ExtensionViewer.d.ts.map +1 -1
  108. package/dist/client/extensions/ExtensionViewer.js +28 -1
  109. package/dist/client/extensions/ExtensionViewer.js.map +1 -1
  110. package/dist/client/extensions/InlineExtensionFrame.d.ts +25 -0
  111. package/dist/client/extensions/InlineExtensionFrame.d.ts.map +1 -0
  112. package/dist/client/extensions/InlineExtensionFrame.js +488 -0
  113. package/dist/client/extensions/InlineExtensionFrame.js.map +1 -0
  114. package/dist/client/extensions/iframe-bridge.d.ts +2 -0
  115. package/dist/client/extensions/iframe-bridge.d.ts.map +1 -1
  116. package/dist/client/extensions/iframe-bridge.js +23 -0
  117. package/dist/client/extensions/iframe-bridge.js.map +1 -1
  118. package/dist/client/extensions/index.d.ts +1 -0
  119. package/dist/client/extensions/index.d.ts.map +1 -1
  120. package/dist/client/extensions/index.js +1 -0
  121. package/dist/client/extensions/index.js.map +1 -1
  122. package/dist/client/index.d.ts +1 -1
  123. package/dist/client/index.d.ts.map +1 -1
  124. package/dist/client/index.js +1 -1
  125. package/dist/client/index.js.map +1 -1
  126. package/dist/client/session-replay.d.ts +11 -1
  127. package/dist/client/session-replay.d.ts.map +1 -1
  128. package/dist/client/session-replay.js +74 -14
  129. package/dist/client/session-replay.js.map +1 -1
  130. package/dist/code-agents/transcript-normalizer.d.ts +2 -0
  131. package/dist/code-agents/transcript-normalizer.d.ts.map +1 -1
  132. package/dist/code-agents/transcript-normalizer.js +8 -0
  133. package/dist/code-agents/transcript-normalizer.js.map +1 -1
  134. package/dist/collab/awareness.d.ts +2 -2
  135. package/dist/collab/awareness.d.ts.map +1 -1
  136. package/dist/collab/routes.d.ts +2 -2
  137. package/dist/extensions/actions.d.ts.map +1 -1
  138. package/dist/extensions/actions.js +200 -1
  139. package/dist/extensions/actions.js.map +1 -1
  140. package/dist/extensions/html-shell.d.ts.map +1 -1
  141. package/dist/extensions/html-shell.js +70 -3
  142. package/dist/extensions/html-shell.js.map +1 -1
  143. package/dist/extensions/routes.d.ts.map +1 -1
  144. package/dist/extensions/routes.js +11 -4
  145. package/dist/extensions/routes.js.map +1 -1
  146. package/dist/file-upload/actions/upload-image.d.ts +2 -2
  147. package/dist/index.browser.d.ts +1 -1
  148. package/dist/index.browser.d.ts.map +1 -1
  149. package/dist/index.browser.js +1 -1
  150. package/dist/index.browser.js.map +1 -1
  151. package/dist/index.d.ts +1 -1
  152. package/dist/index.d.ts.map +1 -1
  153. package/dist/index.js +1 -1
  154. package/dist/index.js.map +1 -1
  155. package/dist/mcp/build-server.js +1 -1
  156. package/dist/mcp/build-server.js.map +1 -1
  157. package/dist/mcp/embed-app.d.ts.map +1 -1
  158. package/dist/mcp/embed-app.js +11 -1
  159. package/dist/mcp/embed-app.js.map +1 -1
  160. package/dist/observability/routes.d.ts +7 -7
  161. package/dist/provider-api/corpus-jobs.d.ts +2 -2
  162. package/dist/resources/handlers.d.ts +2 -2
  163. package/dist/server/agent-chat-plugin.d.ts.map +1 -1
  164. package/dist/server/agent-chat-plugin.js +21 -9
  165. package/dist/server/agent-chat-plugin.js.map +1 -1
  166. package/dist/server/agent-engine-api-key-route.d.ts +2 -2
  167. package/dist/templates/workspace-core/.agents/skills/extensions/SKILL.md +13 -0
  168. package/dist/templates/workspace-core/.agents/skills/extensions/references/api.md +2 -0
  169. package/dist/templates/workspace-core/.agents/skills/generative-ui/SKILL.md +115 -0
  170. package/docs/content/actions.md +4 -2
  171. package/docs/content/agent-surfaces.md +9 -0
  172. package/docs/content/extensions.md +10 -0
  173. package/docs/content/generative-ui.md +217 -0
  174. package/docs/content/key-concepts.md +2 -2
  175. package/docs/content/mcp-apps.md +3 -5
  176. package/docs/content/native-chat-ui.md +9 -5
  177. package/docs/content/tracking.md +9 -3
  178. package/package.json +1 -1
  179. package/src/templates/workspace-core/.agents/skills/extensions/SKILL.md +13 -0
  180. package/src/templates/workspace-core/.agents/skills/extensions/references/api.md +2 -0
  181. package/src/templates/workspace-core/.agents/skills/generative-ui/SKILL.md +115 -0
@@ -1,24 +1,19 @@
1
1
  import { CodeSurface } from "@agent-native/core/blocks";
2
2
  import { useActionQuery, useT } from "@agent-native/core/client";
3
3
  import {
4
+ IconChevronDown,
4
5
  IconCode,
5
6
  IconFilter,
6
7
  IconPlayerPlay,
7
8
  IconRefresh,
8
9
  IconSearch,
9
10
  } from "@tabler/icons-react";
10
- import { useMemo } from "react";
11
- import { Link, useSearchParams } from "react-router";
11
+ import { useMemo, useState } from "react";
12
+ import { useNavigate, useSearchParams } from "react-router";
12
13
 
13
14
  import { Badge } from "@/components/ui/badge";
14
15
  import { Button } from "@/components/ui/button";
15
- import {
16
- Card,
17
- CardContent,
18
- CardDescription,
19
- CardHeader,
20
- CardTitle,
21
- } from "@/components/ui/card";
16
+ import { Card, CardContent } from "@/components/ui/card";
22
17
  import { Input } from "@/components/ui/input";
23
18
  import {
24
19
  Select,
@@ -28,14 +23,6 @@ import {
28
23
  SelectValue,
29
24
  } from "@/components/ui/select";
30
25
  import { Skeleton } from "@/components/ui/skeleton";
31
- import {
32
- Table,
33
- TableBody,
34
- TableCell,
35
- TableHead,
36
- TableHeader,
37
- TableRow,
38
- } from "@/components/ui/table";
39
26
  import { cn } from "@/lib/utils";
40
27
 
41
28
  type ReplayRange = "24h" | "7d" | "30d" | "90d" | "all";
@@ -74,7 +61,9 @@ const RANGE_OPTIONS: ReplayRange[] = ["24h", "7d", "30d", "90d", "all"];
74
61
 
75
62
  export default function SessionsPage() {
76
63
  const t = useT();
64
+ const navigate = useNavigate();
77
65
  const [searchParams, setSearchParams] = useSearchParams();
66
+ const [filtersOpen, setFiltersOpen] = useState(false);
78
67
  const range = readRange(searchParams.get("range"));
79
68
  const app = searchParams.get("app") ?? "";
80
69
  const query = searchParams.get("q") ?? "";
@@ -105,76 +94,39 @@ export default function SessionsPage() {
105
94
  }
106
95
 
107
96
  return (
108
- <div className="mx-auto flex w-full max-w-7xl flex-col gap-6 px-4 py-6 md:px-6">
109
- <div className="flex flex-col gap-4 md:flex-row md:items-start md:justify-between">
110
- <div className="min-w-0 space-y-2">
97
+ <div className="mx-auto flex w-full max-w-7xl flex-col gap-4 px-4 py-5 md:px-6">
98
+ <div className="flex items-center">
99
+ <div className="min-w-0">
111
100
  <div className="flex items-center gap-2">
112
101
  <IconPlayerPlay className="h-5 w-5 text-primary" />
113
- <h1 className="text-2xl font-semibold tracking-normal">
102
+ <h1 className="text-xl font-semibold tracking-normal">
114
103
  {t("sessions.title")}
115
104
  </h1>
116
105
  </div>
117
- <p className="max-w-3xl text-sm text-muted-foreground">
118
- {t("sessions.description")}
119
- </p>
120
106
  </div>
121
- <Button
122
- type="button"
123
- variant="outline"
124
- onClick={() => void refetch()}
125
- disabled={isFetching}
126
- >
127
- <IconRefresh
128
- className={cn("h-4 w-4", isFetching && "animate-spin")}
129
- />
130
- {t("sessions.refresh")}
131
- </Button>
132
107
  </div>
133
108
 
134
109
  <Card>
135
- <CardHeader className="pb-4">
136
- <div className="flex flex-col gap-3 lg:flex-row lg:items-center lg:justify-between">
137
- <div>
138
- <CardTitle className="flex items-center gap-2 text-base">
139
- <IconFilter className="h-4 w-4" />
140
- {t("sessions.filters")}
141
- </CardTitle>
142
- <CardDescription>
143
- {t("sessions.filtersDescription")}
144
- </CardDescription>
145
- </div>
146
- <div className="flex flex-wrap items-center gap-2 text-xs text-muted-foreground">
147
- {data ? (
148
- <Badge variant="outline">
149
- {t("sessions.showing", {
150
- count: String(recordings.length),
151
- })}
152
- </Badge>
153
- ) : null}
110
+ <CardContent className="p-3">
111
+ <div className="grid gap-2 lg:grid-cols-[150px_minmax(260px,1fr)_160px_auto_auto] lg:items-center">
112
+ <div className="flex items-center gap-2 px-1 text-sm font-medium">
113
+ <IconFilter className="h-4 w-4 text-muted-foreground" />
114
+ {t("sessions.segmentFilters")}
154
115
  </div>
155
- </div>
156
- </CardHeader>
157
- <CardContent>
158
- <div className="grid gap-3 md:grid-cols-[minmax(0,1.4fr)_minmax(160px,0.7fr)_160px]">
159
116
  <div className="relative">
160
117
  <IconSearch className="pointer-events-none absolute start-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
161
118
  <Input
162
119
  value={query}
163
120
  onChange={(event) => updateFilter("q", event.target.value)}
164
121
  placeholder={t("sessions.searchPlaceholder")}
165
- className="ps-9"
122
+ className="h-9 ps-9"
166
123
  />
167
124
  </div>
168
- <Input
169
- value={app}
170
- onChange={(event) => updateFilter("app", event.target.value)}
171
- placeholder={t("sessions.appPlaceholder")}
172
- />
173
125
  <Select
174
126
  value={range}
175
127
  onValueChange={(value) => updateFilter("range", value)}
176
128
  >
177
- <SelectTrigger aria-label={t("sessions.range")}>
129
+ <SelectTrigger className="h-9" aria-label={t("sessions.range")}>
178
130
  <SelectValue />
179
131
  </SelectTrigger>
180
132
  <SelectContent>
@@ -185,7 +137,59 @@ export default function SessionsPage() {
185
137
  ))}
186
138
  </SelectContent>
187
139
  </Select>
140
+ <Button
141
+ type="button"
142
+ variant="outline"
143
+ size="sm"
144
+ className="h-9 justify-between gap-2"
145
+ onClick={() => setFiltersOpen((value) => !value)}
146
+ aria-expanded={filtersOpen}
147
+ >
148
+ {t("sessions.filters")}
149
+ <IconChevronDown
150
+ className={cn(
151
+ "h-4 w-4 transition-transform",
152
+ filtersOpen && "rotate-180",
153
+ )}
154
+ />
155
+ </Button>
156
+ <Button
157
+ type="button"
158
+ variant="ghost"
159
+ size="icon"
160
+ className="h-9 w-9"
161
+ onClick={() => void refetch()}
162
+ disabled={isFetching}
163
+ aria-label={t("sessions.refresh")}
164
+ >
165
+ <IconRefresh
166
+ className={cn("h-4 w-4", isFetching && "animate-spin")}
167
+ />
168
+ </Button>
188
169
  </div>
170
+ {filtersOpen ? (
171
+ <div className="mt-3 grid gap-3 border-t pt-3 lg:grid-cols-2">
172
+ <div className="rounded-md border bg-muted/20 p-3">
173
+ <div className="mb-2 text-[11px] font-semibold uppercase text-muted-foreground">
174
+ {t("sessions.userFilters")}
175
+ </div>
176
+ <Input
177
+ value={app}
178
+ onChange={(event) => updateFilter("app", event.target.value)}
179
+ placeholder={t("sessions.appPlaceholder")}
180
+ className="h-9"
181
+ />
182
+ </div>
183
+ <div className="rounded-md border bg-muted/20 p-3">
184
+ <div className="mb-2 text-[11px] font-semibold uppercase text-muted-foreground">
185
+ {t("sessions.eventFilters")}
186
+ </div>
187
+ <div className="flex h-9 items-center rounded-md border bg-background px-3 text-sm text-muted-foreground">
188
+ {t("sessions.anyActivity")}
189
+ </div>
190
+ </div>
191
+ </div>
192
+ ) : null}
189
193
  </CardContent>
190
194
  </Card>
191
195
 
@@ -200,40 +204,65 @@ export default function SessionsPage() {
200
204
  ) : recordings.length === 0 ? (
201
205
  <EmptySessionsState />
202
206
  ) : (
203
- <div className="overflow-x-auto">
204
- <Table>
205
- <TableHeader>
206
- <TableRow>
207
- <TableHead>{t("sessions.session")}</TableHead>
208
- <TableHead>{t("sessions.app")}</TableHead>
209
- <TableHead>{t("sessions.visitor")}</TableHead>
210
- <TableHead>{t("sessions.lastSeen")}</TableHead>
211
- <TableHead>{t("sessions.duration")}</TableHead>
212
- <TableHead className="text-end">
213
- {t("sessions.events")}
214
- </TableHead>
215
- <TableHead className="text-end">
216
- {t("sessions.chunks")}
217
- </TableHead>
218
- <TableHead className="text-end">
219
- {t("sessions.pages")}
220
- </TableHead>
221
- </TableRow>
222
- </TableHeader>
223
- <TableBody>
224
- {recordings.map((recording) => (
225
- <TableRow key={recording.id}>
226
- <TableCell className="min-w-[240px]">
227
- <Link
228
- to={`/sessions/${encodeURIComponent(recording.id)}`}
229
- className="font-mono text-xs font-medium text-primary hover:underline"
230
- >
231
- {shortId(recording.sessionId)}
232
- </Link>
233
- <div className="mt-1 flex flex-wrap gap-1.5">
234
- <Badge className="bg-emerald-500/15 text-emerald-700 hover:bg-emerald-500/20 dark:text-emerald-300">
235
- {t("sessions.replayReady")}
236
- </Badge>
207
+ <div>
208
+ <div className="flex items-center justify-between border-b px-4 py-3">
209
+ <div className="text-sm font-medium">
210
+ {t("sessions.sessionPlaylist")}
211
+ </div>
212
+ <div className="text-xs text-muted-foreground">
213
+ {t("sessions.showing", {
214
+ count: String(recordings.length),
215
+ })}
216
+ </div>
217
+ </div>
218
+ <div className="divide-y">
219
+ {recordings.map((recording) => {
220
+ const href = `/sessions/${encodeURIComponent(recording.id)}`;
221
+ const lastSeen =
222
+ recording.endedAt ??
223
+ recording.lastIngestedAt ??
224
+ recording.startedAt;
225
+ return (
226
+ <button
227
+ key={recording.id}
228
+ type="button"
229
+ className="grid w-full gap-3 px-4 py-3 text-left transition-colors hover:bg-muted/35 focus-visible:bg-muted/35 focus-visible:outline-none md:grid-cols-[104px_minmax(170px,0.85fr)_minmax(260px,1.25fr)_minmax(130px,0.55fr)] md:items-center"
230
+ onClick={() => navigate(href)}
231
+ aria-label={t("sessions.watchReplay")}
232
+ >
233
+ <span className="inline-flex h-10 w-[92px] items-center justify-center gap-2 rounded-md bg-primary/10 font-medium text-primary">
234
+ <IconPlayerPlay className="h-4 w-4 fill-current" />
235
+ {formatDuration(recording.durationMs)}
236
+ </span>
237
+ <span className="min-w-0">
238
+ <span className="block truncate text-sm font-medium text-foreground">
239
+ {visitorLabel(recording, t)}
240
+ </span>
241
+ <span className="mt-0.5 block truncate text-xs text-muted-foreground">
242
+ {formatDateTime(lastSeen)} ·{" "}
243
+ {t("sessions.eventCountCompact", {
244
+ count: formatNumber(recording.eventCount),
245
+ })}
246
+ </span>
247
+ </span>
248
+ <span className="min-w-0">
249
+ <span className="block truncate text-sm font-medium text-primary">
250
+ {pathLabel(recording)}
251
+ </span>
252
+ <span className="mt-0.5 block truncate text-xs text-muted-foreground">
253
+ {recording.hostname ||
254
+ recording.app ||
255
+ recording.template ||
256
+ shortId(recording.sessionId)}
257
+ </span>
258
+ </span>
259
+ <span className="min-w-0 text-left md:text-right">
260
+ <span className="block truncate text-sm text-muted-foreground">
261
+ {recording.app ||
262
+ recording.template ||
263
+ t("sessions.unknownApp")}
264
+ </span>
265
+ <span className="mt-1 flex flex-wrap gap-1.5 md:justify-end">
237
266
  {recording.errorCount > 0 ? (
238
267
  <Badge variant="destructive">
239
268
  {t("sessions.errorCount", {
@@ -248,39 +277,20 @@ export default function SessionsPage() {
248
277
  })}
249
278
  </Badge>
250
279
  ) : null}
251
- </div>
252
- </TableCell>
253
- <TableCell className="min-w-[130px]">
254
- {recording.app || recording.template || "-"}
255
- </TableCell>
256
- <TableCell className="min-w-[160px]">
257
- <div className="truncate">
258
- {visitorLabel(recording, t)}
259
- </div>
260
- </TableCell>
261
- <TableCell className="min-w-[140px]">
262
- {formatDateTime(
263
- recording.endedAt ??
264
- recording.lastIngestedAt ??
265
- recording.startedAt,
266
- )}
267
- </TableCell>
268
- <TableCell>
269
- {formatDuration(recording.durationMs)}
270
- </TableCell>
271
- <TableCell className="text-end">
272
- {formatNumber(recording.eventCount)}
273
- </TableCell>
274
- <TableCell className="text-end">
275
- {formatNumber(recording.chunkCount)}
276
- </TableCell>
277
- <TableCell className="text-end">
278
- {formatNumber(recording.pageCount)}
279
- </TableCell>
280
- </TableRow>
281
- ))}
282
- </TableBody>
283
- </Table>
280
+ {recording.errorCount === 0 &&
281
+ recording.rageClickCount === 0 ? (
282
+ <span className="text-xs text-muted-foreground">
283
+ {t("sessions.pageCountCompact", {
284
+ count: formatNumber(recording.pageCount),
285
+ })}
286
+ </span>
287
+ ) : null}
288
+ </span>
289
+ </span>
290
+ </button>
291
+ );
292
+ })}
293
+ </div>
284
294
  </div>
285
295
  )}
286
296
  </CardContent>
@@ -374,6 +384,22 @@ function shortId(value: string): string {
374
384
  : value;
375
385
  }
376
386
 
387
+ function pathLabel(recording: SessionRecordingSummary): string {
388
+ if (recording.path) return recording.path;
389
+ if (recording.lastUrl) return safePathFromUrl(recording.lastUrl);
390
+ if (recording.firstUrl) return safePathFromUrl(recording.firstUrl);
391
+ return shortId(recording.sessionId);
392
+ }
393
+
394
+ function safePathFromUrl(value: string): string {
395
+ try {
396
+ const url = new URL(value);
397
+ return `${url.pathname}${url.search}` || url.hostname;
398
+ } catch {
399
+ return value;
400
+ }
401
+ }
402
+
377
403
  function formatDateTime(value: string): string {
378
404
  const date = new Date(value);
379
405
  if (!Number.isFinite(date.getTime())) return value;
@@ -409,7 +435,8 @@ configureTracking({
409
435
  endpoint: "https://analytics.example.com/api/analytics/track",
410
436
  sessionReplay: {
411
437
  enabled: true,
412
- sampleRate: 1,
438
+ requireSignedInUser: true,
439
+ sampleRate: 0.1,
413
440
  },
414
441
  getDefaultProps: (_event, props) => ({
415
442
  ...props,
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: improved
3
+ date: 2026-06-26
4
+ ---
5
+
6
+ Agents can now edit dashboards through a typed mutation script API for faster, more reliable multi-step changes.
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-06-26
4
+ ---
5
+
6
+ Dashboard bar chart hovers stay subtle in dark mode.
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: improved
3
+ date: 2026-06-26
4
+ ---
5
+
6
+ Dashboard chart moves are now handled by panel id, making simple reorders faster and more reliable for the agent.
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-06-26
4
+ ---
5
+
6
+ Dashboard charts drag smoothly with stable cards, inactive chart hovers, and reliable blue placement lines.
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: improved
3
+ date: 2026-06-26
4
+ ---
5
+
6
+ Dashboard charts show a refresh spinner while keeping existing data visible.
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-06-26
4
+ ---
5
+
6
+ Dashboard edits stay stable while chart order and deletions save.
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: improved
3
+ date: 2026-06-26
4
+ ---
5
+
6
+ Dashboard email reports queue immediate sends through the background sender for more reliable delivery.
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: improved
3
+ date: 2026-06-26
4
+ ---
5
+
6
+ Explorer dashboards now drag charts with a stable preview and smoother placement.
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: improved
3
+ date: 2026-06-26
4
+ ---
5
+
6
+ Session replay playback can now be toggled by clicking the replay preview.
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: improved
3
+ date: 2026-06-26
4
+ ---
5
+
6
+ Session replays now open from the full row and play with timeline controls, speed settings, inactivity skipping, and an agent prompt popover.
@@ -452,6 +452,27 @@ export async function claimDashboardReportSubscription(
452
452
  return rows[0] ? rowToSubscription(rows[0]) : null;
453
453
  }
454
454
 
455
+ export async function queueDashboardReportSubscriptionNow(
456
+ id: string,
457
+ ctx: AccessCtx,
458
+ now: Date = new Date(),
459
+ ): Promise<DashboardReportSubscription | null> {
460
+ const db = getDb() as any;
461
+ const queuedAt = now.toISOString();
462
+ const rows = await db
463
+ .update(schema.dashboardReportSubscriptions)
464
+ .set({
465
+ enabled: true,
466
+ nextRunAt: queuedAt,
467
+ lastStatus: null,
468
+ lastError: null,
469
+ updatedAt: queuedAt,
470
+ })
471
+ .where(ownerWhere(ctx, undefined, id))
472
+ .returning();
473
+ return rows[0] ? rowToSubscription(rows[0]) : null;
474
+ }
475
+
455
476
  export async function markDashboardReportResult(
456
477
  sub: DashboardReportSubscription,
457
478
  status: "success" | "error",
@@ -205,11 +205,17 @@ async function launchScreenshotBrowser() {
205
205
 
206
206
  if (isServerlessBrowserRuntime()) {
207
207
  const { default: chromium } = await import("@sparticuz/chromium-min");
208
+ chromium.setGraphicsMode = false;
208
209
  const packUrl =
209
210
  process.env.DASHBOARD_REPORT_CHROMIUM_PACK_URL?.trim() ||
210
211
  DEFAULT_SERVERLESS_CHROMIUM_PACK_URL;
211
212
  return playwright.launch({
212
- args: chromium.args,
213
+ args: [
214
+ ...chromium.args,
215
+ "--disable-dev-shm-usage",
216
+ "--disable-gpu",
217
+ "--hide-scrollbars",
218
+ ],
213
219
  executablePath: await chromium.executablePath(packUrl),
214
220
  headless: true,
215
221
  });
@@ -219,22 +225,40 @@ async function launchScreenshotBrowser() {
219
225
  }
220
226
 
221
227
  async function waitForDashboardReportReady(page: any): Promise<void> {
222
- await page.waitForFunction(
223
- `(() => {
224
- const root = document.querySelector("[data-dashboard-report-capture]");
225
- if (!root) return false;
226
- if (root.getAttribute("data-dashboard-report-ready") !== "true") {
227
- return false;
228
- }
229
- return !root.querySelector("[data-dashboard-report-loading='true']");
230
- })()`,
231
- undefined,
232
- { timeout: 90_000 },
233
- );
234
- await page.evaluate(`(async () => {
235
- await document.fonts?.ready;
236
- })()`);
237
- await page.waitForTimeout(750);
228
+ try {
229
+ await page.waitForFunction(
230
+ `(() => {
231
+ const root = document.querySelector("[data-dashboard-report-capture]");
232
+ if (!root) return false;
233
+ if (root.getAttribute("data-dashboard-report-ready") !== "true") {
234
+ return false;
235
+ }
236
+ return !root.querySelector("[data-dashboard-report-loading='true']");
237
+ })()`,
238
+ undefined,
239
+ { timeout: 90_000 },
240
+ );
241
+ await page.evaluate(`(async () => {
242
+ await document.fonts?.ready;
243
+ })()`);
244
+ await page.waitForTimeout(750);
245
+ } catch (err: any) {
246
+ const detail = await page
247
+ .evaluate(`(() => {
248
+ const root = document.querySelector("[data-dashboard-report-capture]");
249
+ return {
250
+ ready: root?.getAttribute("data-dashboard-report-ready") ?? null,
251
+ loadingCount: root?.querySelectorAll("[data-dashboard-report-loading='true']").length ?? null,
252
+ text: document.body?.innerText?.slice(0, 1000) ?? "",
253
+ url: location.href,
254
+ };
255
+ })()`)
256
+ .catch(() => null);
257
+ const message = detail
258
+ ? `${err?.message ?? String(err)}; dashboard state: ${JSON.stringify(detail)}`
259
+ : `${err?.message ?? String(err)}; dashboard page was not inspectable`;
260
+ throw new Error(message);
261
+ }
238
262
  }
239
263
 
240
264
  async function scrollDashboardForLazyRendering(page: any): Promise<void> {
@@ -36,6 +36,8 @@ const INITIAL_TOOL_NAMES = [
36
36
  "save-analysis",
37
37
  "rename-analysis",
38
38
  "delete-analysis",
39
+ "get-sql-dashboard",
40
+ "mutate-dashboard",
39
41
  "generate-chart",
40
42
  "bigquery",
41
43
  "search-bigquery-schema",
@@ -172,6 +174,9 @@ export default createAgentChatPlugin({
172
174
  "Apply real-data requirements only when presenting analytics results, source records, or derived metrics. Do not call data-source tools for workflow migration, recurring-job setup, UI/code fixes, settings help, conceptual planning, or other non-data tasks unless the user explicitly asks for data. " +
173
175
  "SURFACE DIFFERENTIATION — You are the analytics assistant for definitions, deep-dive analysis, and action. For questions about what a metric, model, or table means, use the Data Dictionary and configured schema tools first. For trends, comparisons, anomalies, current data, or anything that requires querying live data, answer directly in chat with the relevant provider query, dashboard analysis, and inline charts when useful. " +
174
176
  "DASHBOARD CREATION RULE — You may create dashboards, analyses, SQL panels, or other resources only when the user explicitly asks you to (e.g. 'build me a dashboard for...', 'create a new analysis', 'add a chart for...'). Never create any resource proactively during research, trend analysis, or answering questions. If you think a dashboard would be useful, suggest it and wait for explicit confirmation before creating anything. Never add new items to the sidebar or modify existing dashboards without an explicit user directive. " +
177
+ "DASHBOARD MUTATION RULE — For dashboard edits, default to `mutate-dashboard` with the typed `dashboard.*` script API so the main payload is a string and avoids native-array serialization traps. It can move panels by id, edit titles/SQL/config, insert, duplicate, remove, and patch dashboard fields in one atomic save. The script API is constrained: no variables/imports/loops/functions, only JSON-compatible arguments on documented dashboard methods. Do not count shifting `/panels/<index>` positions for ordinary dashboard edits unless the user specifically asks for low-level JSON-pointer operations. " +
178
+ "DASHBOARD READ RULE — `get-sql-dashboard` is compact by default: use its `panels` summaries plus `layout.panelOrder` and `layout.firstPanelIds` for orientation and verification. Pass `includeConfig: true` only when you truly need full panel SQL/config. " +
179
+ 'DASHBOARD REORDER RULE — For simple chart/section moves, use `mutate-dashboard` code such as `dashboard.panels(["panel-a","panel-b"]).moveToTop();`. Never count shifting `/panels/<index>` positions for ordinary \'move this chart\' requests. Use `get-sql-dashboard.layout.firstPanelIds` or `mutate-dashboard.panelOrder` as proof of placement. ' +
175
180
  "Use configured data sources and actions only. Call `data-source-status` when you need to know which providers are connected, and treat provider actions as unavailable for analysis if they return missing credentials, permission, syntax, quota, or network errors. " +
176
181
  "The built-in `demo` dashboard source is a demo-environment Prometheus source reserved for the Node Exporter demo. It must never satisfy REAL_DATA_REQUIRED or be cited as user analytics evidence unless the user explicitly asks to inspect the demo dashboard. " +
177
182
  "When the user names a provider or tool such as Jira, Pylon, HubSpot, Gong, Slack, Sentry, GA4, or BigQuery, that named source is authoritative for the turn: use that provider's real tool/API surface, not a warehouse or different-provider substitute, unless the user explicitly asks for the copy/fallback. For bounded lookups where a first-class action fully models the requested source, object, filter, and pagination need, that shortcut is fine. For broad provider searches, cross-source joins, corpus-wide counts, exact cohort coverage, or any answer where absence matters, do not start and stop with shortcuts; use the broad provider API/MCP and corpus/code workflow as the primary path. " +
@@ -2,6 +2,7 @@ export declare const ACTION_CHAT_UI_DATA_TABLE_RENDERER = "core.data-table";
2
2
  export declare const ACTION_CHAT_UI_DATA_CHART_RENDERER = "core.data-chart";
3
3
  export declare const ACTION_CHAT_UI_DATA_INSIGHTS_RENDERER = "core.data-insights";
4
4
  export declare const ACTION_CHAT_UI_DATA_WIDGET_RENDERER = "core.data-widget";
5
+ export declare const ACTION_CHAT_UI_INLINE_EXTENSION_RENDERER = "core.inline-extension";
5
6
  export interface ActionChatUIConfig {
6
7
  /**
7
8
  * Exact renderer id to use in Agent-Native chat. This is native first-party
@@ -1 +1 @@
1
- {"version":3,"file":"action-ui.d.ts","sourceRoot":"","sources":["../src/action-ui.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,kCAAkC,oBAAoB,CAAC;AACpE,eAAO,MAAM,kCAAkC,oBAAoB,CAAC;AACpE,eAAO,MAAM,qCAAqC,uBAAuB,CAAC;AAC1E,eAAO,MAAM,mCAAmC,qBAAqB,CAAC;AAEtE,MAAM,WAAW,kBAAkB;IACjC;;;OAGG;IACH,QAAQ,EAAE,MAAM,CAAC;IACjB,oEAAoE;IACpE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,+DAA+D;IAC/D,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,wBAAgB,2BAA2B,CACzC,KAAK,EAAE,OAAO,GACb,kBAAkB,GAAG,SAAS,CAiBhC"}
1
+ {"version":3,"file":"action-ui.d.ts","sourceRoot":"","sources":["../src/action-ui.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,kCAAkC,oBAAoB,CAAC;AACpE,eAAO,MAAM,kCAAkC,oBAAoB,CAAC;AACpE,eAAO,MAAM,qCAAqC,uBAAuB,CAAC;AAC1E,eAAO,MAAM,mCAAmC,qBAAqB,CAAC;AACtE,eAAO,MAAM,wCAAwC,0BAA0B,CAAC;AAEhF,MAAM,WAAW,kBAAkB;IACjC;;;OAGG;IACH,QAAQ,EAAE,MAAM,CAAC;IACjB,oEAAoE;IACpE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,+DAA+D;IAC/D,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,wBAAgB,2BAA2B,CACzC,KAAK,EAAE,OAAO,GACb,kBAAkB,GAAG,SAAS,CAiBhC"}
package/dist/action-ui.js CHANGED
@@ -2,6 +2,7 @@ export const ACTION_CHAT_UI_DATA_TABLE_RENDERER = "core.data-table";
2
2
  export const ACTION_CHAT_UI_DATA_CHART_RENDERER = "core.data-chart";
3
3
  export const ACTION_CHAT_UI_DATA_INSIGHTS_RENDERER = "core.data-insights";
4
4
  export const ACTION_CHAT_UI_DATA_WIDGET_RENDERER = "core.data-widget";
5
+ export const ACTION_CHAT_UI_INLINE_EXTENSION_RENDERER = "core.inline-extension";
5
6
  export function normalizeActionChatUIConfig(value) {
6
7
  if (!value || typeof value !== "object" || Array.isArray(value)) {
7
8
  return undefined;
@@ -1 +1 @@
1
- {"version":3,"file":"action-ui.js","sourceRoot":"","sources":["../src/action-ui.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,kCAAkC,GAAG,iBAAiB,CAAC;AACpE,MAAM,CAAC,MAAM,kCAAkC,GAAG,iBAAiB,CAAC;AACpE,MAAM,CAAC,MAAM,qCAAqC,GAAG,oBAAoB,CAAC;AAC1E,MAAM,CAAC,MAAM,mCAAmC,GAAG,kBAAkB,CAAC;AActE,MAAM,UAAU,2BAA2B,CACzC,KAAc;IAEd,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QAChE,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,MAAM,MAAM,GAAG,KAAgC,CAAC;IAChD,IAAI,OAAO,MAAM,CAAC,QAAQ,KAAK,QAAQ,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE,EAAE,CAAC;QACnE,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,OAAO;QACL,QAAQ,EAAE,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE;QAChC,GAAG,CAAC,OAAO,MAAM,CAAC,KAAK,KAAK,QAAQ,IAAI,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE;YACzD,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE;YACzB,CAAC,CAAC,EAAE,CAAC;QACP,GAAG,CAAC,OAAO,MAAM,CAAC,WAAW,KAAK,QAAQ,IAAI,MAAM,CAAC,WAAW,CAAC,IAAI,EAAE;YACrE,CAAC,CAAC,EAAE,WAAW,EAAE,MAAM,CAAC,WAAW,EAAE;YACrC,CAAC,CAAC,EAAE,CAAC;KACR,CAAC;AACJ,CAAC","sourcesContent":["export const ACTION_CHAT_UI_DATA_TABLE_RENDERER = \"core.data-table\";\nexport const ACTION_CHAT_UI_DATA_CHART_RENDERER = \"core.data-chart\";\nexport const ACTION_CHAT_UI_DATA_INSIGHTS_RENDERER = \"core.data-insights\";\nexport const ACTION_CHAT_UI_DATA_WIDGET_RENDERER = \"core.data-widget\";\n\nexport interface ActionChatUIConfig {\n /**\n * Exact renderer id to use in Agent-Native chat. This is native first-party\n * React UI, distinct from MCP Apps resources for external hosts.\n */\n renderer: string;\n /** Optional label for catalogs, docs, or custom renderer chrome. */\n title?: string;\n /** Optional developer-facing description for catalogs/docs. */\n description?: string;\n}\n\nexport function normalizeActionChatUIConfig(\n value: unknown,\n): ActionChatUIConfig | undefined {\n if (!value || typeof value !== \"object\" || Array.isArray(value)) {\n return undefined;\n }\n const record = value as Record<string, unknown>;\n if (typeof record.renderer !== \"string\" || !record.renderer.trim()) {\n return undefined;\n }\n return {\n renderer: record.renderer.trim(),\n ...(typeof record.title === \"string\" && record.title.trim()\n ? { title: record.title }\n : {}),\n ...(typeof record.description === \"string\" && record.description.trim()\n ? { description: record.description }\n : {}),\n };\n}\n"]}
1
+ {"version":3,"file":"action-ui.js","sourceRoot":"","sources":["../src/action-ui.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,kCAAkC,GAAG,iBAAiB,CAAC;AACpE,MAAM,CAAC,MAAM,kCAAkC,GAAG,iBAAiB,CAAC;AACpE,MAAM,CAAC,MAAM,qCAAqC,GAAG,oBAAoB,CAAC;AAC1E,MAAM,CAAC,MAAM,mCAAmC,GAAG,kBAAkB,CAAC;AACtE,MAAM,CAAC,MAAM,wCAAwC,GAAG,uBAAuB,CAAC;AAchF,MAAM,UAAU,2BAA2B,CACzC,KAAc;IAEd,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QAChE,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,MAAM,MAAM,GAAG,KAAgC,CAAC;IAChD,IAAI,OAAO,MAAM,CAAC,QAAQ,KAAK,QAAQ,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE,EAAE,CAAC;QACnE,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,OAAO;QACL,QAAQ,EAAE,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE;QAChC,GAAG,CAAC,OAAO,MAAM,CAAC,KAAK,KAAK,QAAQ,IAAI,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE;YACzD,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE;YACzB,CAAC,CAAC,EAAE,CAAC;QACP,GAAG,CAAC,OAAO,MAAM,CAAC,WAAW,KAAK,QAAQ,IAAI,MAAM,CAAC,WAAW,CAAC,IAAI,EAAE;YACrE,CAAC,CAAC,EAAE,WAAW,EAAE,MAAM,CAAC,WAAW,EAAE;YACrC,CAAC,CAAC,EAAE,CAAC;KACR,CAAC;AACJ,CAAC","sourcesContent":["export const ACTION_CHAT_UI_DATA_TABLE_RENDERER = \"core.data-table\";\nexport const ACTION_CHAT_UI_DATA_CHART_RENDERER = \"core.data-chart\";\nexport const ACTION_CHAT_UI_DATA_INSIGHTS_RENDERER = \"core.data-insights\";\nexport const ACTION_CHAT_UI_DATA_WIDGET_RENDERER = \"core.data-widget\";\nexport const ACTION_CHAT_UI_INLINE_EXTENSION_RENDERER = \"core.inline-extension\";\n\nexport interface ActionChatUIConfig {\n /**\n * Exact renderer id to use in Agent-Native chat. This is native first-party\n * React UI, distinct from MCP Apps resources for external hosts.\n */\n renderer: string;\n /** Optional label for catalogs, docs, or custom renderer chrome. */\n title?: string;\n /** Optional developer-facing description for catalogs/docs. */\n description?: string;\n}\n\nexport function normalizeActionChatUIConfig(\n value: unknown,\n): ActionChatUIConfig | undefined {\n if (!value || typeof value !== \"object\" || Array.isArray(value)) {\n return undefined;\n }\n const record = value as Record<string, unknown>;\n if (typeof record.renderer !== \"string\" || !record.renderer.trim()) {\n return undefined;\n }\n return {\n renderer: record.renderer.trim(),\n ...(typeof record.title === \"string\" && record.title.trim()\n ? { title: record.title }\n : {}),\n ...(typeof record.description === \"string\" && record.description.trim()\n ? { description: record.description }\n : {}),\n };\n}\n"]}