@athenaintel/react 0.39.0 → 0.40.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 (103) hide show
  1. package/README.md +9 -1
  2. package/dist/agent-runtime/src/errors.cjs +8 -0
  3. package/dist/agent-runtime/src/errors.cjs.map +1 -1
  4. package/dist/agent-runtime/src/errors.js +8 -0
  5. package/dist/agent-runtime/src/errors.js.map +1 -1
  6. package/dist/agent-runtime/src/messages.cjs +76 -16
  7. package/dist/agent-runtime/src/messages.cjs.map +1 -1
  8. package/dist/agent-runtime/src/messages.js +76 -16
  9. package/dist/agent-runtime/src/messages.js.map +1 -1
  10. package/dist/agent-runtime/src/tool-metadata.cjs +15 -0
  11. package/dist/agent-runtime/src/tool-metadata.cjs.map +1 -1
  12. package/dist/agent-runtime/src/tool-metadata.js +15 -0
  13. package/dist/agent-runtime/src/tool-metadata.js.map +1 -1
  14. package/dist/assets/use-asset-content.cjs +127 -0
  15. package/dist/assets/use-asset-content.cjs.map +1 -0
  16. package/dist/assets/use-asset-content.d.ts +60 -0
  17. package/dist/assets/use-asset-content.js +127 -0
  18. package/dist/assets/use-asset-content.js.map +1 -0
  19. package/dist/chat/super-grouping/CollapsibleGroup.cjs +16 -8
  20. package/dist/chat/super-grouping/CollapsibleGroup.cjs.map +1 -1
  21. package/dist/chat/super-grouping/CollapsibleGroup.d.ts +26 -1
  22. package/dist/chat/super-grouping/CollapsibleGroup.js +16 -8
  23. package/dist/chat/super-grouping/CollapsibleGroup.js.map +1 -1
  24. package/dist/chat/super-grouping/GroupedMessageParts.cjs +59 -0
  25. package/dist/chat/super-grouping/GroupedMessageParts.cjs.map +1 -0
  26. package/dist/chat/super-grouping/GroupedMessageParts.d.ts +25 -0
  27. package/dist/chat/super-grouping/GroupedMessageParts.js +59 -0
  28. package/dist/chat/super-grouping/GroupedMessageParts.js.map +1 -0
  29. package/dist/chat/super-grouping/SuperGroupingCard.cjs +9 -5
  30. package/dist/chat/super-grouping/SuperGroupingCard.cjs.map +1 -1
  31. package/dist/chat/super-grouping/SuperGroupingCard.d.ts +2 -1
  32. package/dist/chat/super-grouping/SuperGroupingCard.js +13 -9
  33. package/dist/chat/super-grouping/SuperGroupingCard.js.map +1 -1
  34. package/dist/chat/super-grouping/SuperGroupingTranscript.cjs +10 -6
  35. package/dist/chat/super-grouping/SuperGroupingTranscript.cjs.map +1 -1
  36. package/dist/chat/super-grouping/SuperGroupingTranscript.js +14 -10
  37. package/dist/chat/super-grouping/SuperGroupingTranscript.js.map +1 -1
  38. package/dist/chat/super-grouping/grouped-part-keys.cjs +104 -0
  39. package/dist/chat/super-grouping/grouped-part-keys.cjs.map +1 -0
  40. package/dist/chat/super-grouping/grouped-part-keys.d.ts +37 -0
  41. package/dist/chat/super-grouping/grouped-part-keys.js +104 -0
  42. package/dist/chat/super-grouping/grouped-part-keys.js.map +1 -0
  43. package/dist/chat/super-grouping/index.cjs +2 -0
  44. package/dist/chat/super-grouping/index.cjs.map +1 -1
  45. package/dist/chat/super-grouping/index.d.ts +3 -1
  46. package/dist/chat/super-grouping/index.js +2 -0
  47. package/dist/chat/super-grouping/index.js.map +1 -1
  48. package/dist/chat-ui/composer/tiptap-composer.cjs +25 -19
  49. package/dist/chat-ui/composer/tiptap-composer.cjs.map +1 -1
  50. package/dist/chat-ui/composer/tiptap-composer.d.ts +5 -0
  51. package/dist/chat-ui/composer/tiptap-composer.js +26 -20
  52. package/dist/chat-ui/composer/tiptap-composer.js.map +1 -1
  53. package/dist/composer/clipboard.cjs +16 -0
  54. package/dist/composer/clipboard.cjs.map +1 -1
  55. package/dist/composer/clipboard.d.ts +9 -0
  56. package/dist/composer/clipboard.js +17 -1
  57. package/dist/composer/clipboard.js.map +1 -1
  58. package/dist/diagnostics/errors.cjs +6 -0
  59. package/dist/diagnostics/errors.cjs.map +1 -1
  60. package/dist/diagnostics/errors.d.ts +6 -0
  61. package/dist/diagnostics/errors.js +6 -0
  62. package/dist/diagnostics/errors.js.map +1 -1
  63. package/dist/index.cjs +23 -0
  64. package/dist/index.cjs.map +1 -1
  65. package/dist/index.d.ts +8 -1
  66. package/dist/index.js +23 -0
  67. package/dist/index.js.map +1 -1
  68. package/dist/react/package.json.cjs +1 -1
  69. package/dist/react/package.json.js +1 -1
  70. package/dist/runtime/statewire-lifecycle.cjs +8 -0
  71. package/dist/runtime/statewire-lifecycle.cjs.map +1 -1
  72. package/dist/runtime/statewire-lifecycle.js +9 -1
  73. package/dist/runtime/statewire-lifecycle.js.map +1 -1
  74. package/dist/styles.css +1 -1
  75. package/dist/tools/index.cjs +7 -0
  76. package/dist/tools/index.cjs.map +1 -1
  77. package/dist/tools/index.d.ts +2 -0
  78. package/dist/tools/index.js +7 -0
  79. package/dist/tools/index.js.map +1 -1
  80. package/dist/tools/inline-tools.cjs +10 -0
  81. package/dist/tools/inline-tools.cjs.map +1 -0
  82. package/dist/tools/inline-tools.d.ts +2 -0
  83. package/dist/tools/inline-tools.js +10 -0
  84. package/dist/tools/inline-tools.js.map +1 -0
  85. package/dist/tools/test-recording-report.cjs +175 -0
  86. package/dist/tools/test-recording-report.cjs.map +1 -0
  87. package/dist/tools/test-recording-report.d.ts +82 -0
  88. package/dist/tools/test-recording-report.js +175 -0
  89. package/dist/tools/test-recording-report.js.map +1 -0
  90. package/dist/tools/test-recording-tool-uis.cjs +498 -0
  91. package/dist/tools/test-recording-tool-uis.cjs.map +1 -0
  92. package/dist/tools/test-recording-tool-uis.d.ts +32 -0
  93. package/dist/tools/test-recording-tool-uis.js +498 -0
  94. package/dist/tools/test-recording-tool-uis.js.map +1 -0
  95. package/dist/tools/tool-summary.cjs +49 -1
  96. package/dist/tools/tool-summary.cjs.map +1 -1
  97. package/dist/tools/tool-summary.js +49 -1
  98. package/dist/tools/tool-summary.js.map +1 -1
  99. package/dist/tools/tool-uis.cjs +6 -1
  100. package/dist/tools/tool-uis.cjs.map +1 -1
  101. package/dist/tools/tool-uis.js +6 -1
  102. package/dist/tools/tool-uis.js.map +1 -1
  103. package/package.json +1 -1
@@ -0,0 +1,498 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const jsxRuntime = require("react/jsx-runtime");
4
+ const lucideReact = require("lucide-react");
5
+ const react = require("react");
6
+ const useAssetContent = require("../assets/use-asset-content.cjs");
7
+ const AthenaContext = require("../provider/AthenaContext.cjs");
8
+ const primitives = require("./primitives.cjs");
9
+ const testRecordingReport = require("./test-recording-report.cjs");
10
+ const cnfast = require("cnfast");
11
+ function argsRecord(args) {
12
+ return typeof args === "object" && args !== null && !Array.isArray(args) ? args : void 0;
13
+ }
14
+ function argString(args, key) {
15
+ const value = args == null ? void 0 : args[key];
16
+ return typeof value === "string" && value.trim() !== "" ? value.trim() : void 0;
17
+ }
18
+ function incompleteMessage(status, fallback) {
19
+ if ((status == null ? void 0 : status.type) !== "incomplete") return null;
20
+ if (status.reason === "cancelled") return "Cancelled";
21
+ if (typeof status.error === "string" && status.error.trim() !== "") return status.error;
22
+ return fallback;
23
+ }
24
+ const IDLE_PLAYBACK = { requested: false, seek: null };
25
+ const HAVE_METADATA = 1;
26
+ function playbackErrorMessage(error) {
27
+ const status = error.status;
28
+ if (status === 401 || status === 403) return "You don't have access to this recording.";
29
+ if (status === 404) return "The recording's video was not found.";
30
+ return "The recording could not be loaded.";
31
+ }
32
+ function TestRecordingPlayer({
33
+ assetId,
34
+ title,
35
+ durationS,
36
+ url,
37
+ playback,
38
+ onPlay
39
+ }) {
40
+ var _a, _b;
41
+ const content = useAssetContent.useAthenaAssetContent({ assetId, enabled: playback.requested });
42
+ const src = useAssetContent.useObjectUrl(playback.requested ? content.data : void 0);
43
+ const { refetch } = content;
44
+ const retry = react.useCallback(() => {
45
+ void refetch();
46
+ }, [refetch]);
47
+ const videoRef = react.useRef(null);
48
+ const pendingSeekRef = react.useRef(null);
49
+ const seekTime = (_a = playback.seek) == null ? void 0 : _a.time;
50
+ const seekNonce = ((_b = playback.seek) == null ? void 0 : _b.nonce) ?? 0;
51
+ react.useEffect(() => {
52
+ var _a2;
53
+ const video = videoRef.current;
54
+ if (!video || !src) return;
55
+ if (seekTime !== void 0) {
56
+ video.currentTime = seekTime;
57
+ pendingSeekRef.current = video.readyState < HAVE_METADATA ? seekTime : null;
58
+ }
59
+ (_a2 = video.play()) == null ? void 0 : _a2.catch(() => {
60
+ });
61
+ }, [src, seekNonce]);
62
+ const handleLoadedMetadata = react.useCallback((event) => {
63
+ const pending = pendingSeekRef.current;
64
+ pendingSeekRef.current = null;
65
+ if (pending !== null) event.currentTarget.currentTime = pending;
66
+ }, []);
67
+ if (src) {
68
+ return /* @__PURE__ */ jsxRuntime.jsx(
69
+ "video",
70
+ {
71
+ ref: videoRef,
72
+ "data-testid": "test-recording-video",
73
+ src,
74
+ controls: true,
75
+ playsInline: true,
76
+ preload: "auto",
77
+ "aria-label": `Recording: ${title}`,
78
+ onLoadedMetadata: handleLoadedMetadata,
79
+ className: "block max-h-[480px] w-full bg-black"
80
+ }
81
+ );
82
+ }
83
+ if (content.isError && !content.isFetching) {
84
+ return /* @__PURE__ */ jsxRuntime.jsxs(
85
+ "div",
86
+ {
87
+ role: "alert",
88
+ "data-testid": "test-recording-video-error",
89
+ className: "flex aspect-video w-full flex-col items-center justify-center gap-2 bg-muted/40 px-6 text-center",
90
+ children: [
91
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.AlertCircleIcon, { className: "size-5 text-destructive", "aria-hidden": true }),
92
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[12.5px] text-foreground", children: playbackErrorMessage(content.error) }),
93
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
94
+ /* @__PURE__ */ jsxRuntime.jsx(
95
+ "button",
96
+ {
97
+ type: "button",
98
+ onClick: retry,
99
+ className: "rounded-md border border-border bg-background px-2.5 py-1 text-[12px] font-medium text-foreground transition-colors hover:bg-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
100
+ children: "Try again"
101
+ }
102
+ ),
103
+ url ? /* @__PURE__ */ jsxRuntime.jsx(
104
+ "a",
105
+ {
106
+ href: url,
107
+ target: "_blank",
108
+ rel: "noopener noreferrer",
109
+ className: "text-[12px] font-medium text-primary hover:underline",
110
+ children: "Watch in Athena"
111
+ }
112
+ ) : null
113
+ ] })
114
+ ]
115
+ }
116
+ );
117
+ }
118
+ if (playback.requested) {
119
+ return /* @__PURE__ */ jsxRuntime.jsxs(
120
+ "output",
121
+ {
122
+ "data-testid": "test-recording-video-loading",
123
+ className: "flex aspect-video w-full items-center justify-center gap-2 bg-zinc-950 text-[12px] text-white/80",
124
+ children: [
125
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.LoaderIcon, { className: "size-4 animate-spin", "aria-hidden": true }),
126
+ "Loading recording…"
127
+ ]
128
+ }
129
+ );
130
+ }
131
+ return /* @__PURE__ */ jsxRuntime.jsxs(
132
+ "button",
133
+ {
134
+ type: "button",
135
+ onClick: onPlay,
136
+ "aria-label": `Play recording: ${title}`,
137
+ className: "group relative flex aspect-video w-full items-center justify-center bg-zinc-950 text-white focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-inset",
138
+ children: [
139
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex size-12 items-center justify-center rounded-full bg-white/15 transition-colors group-hover:bg-white/25", children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.PlayIcon, { className: "ml-0.5 size-5 fill-current", "aria-hidden": true }) }),
140
+ durationS !== null ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "absolute right-3 bottom-2 text-[11px] tabular-nums text-white/80", children: testRecordingReport.formatRecordingClock(durationS) }) : null
141
+ ]
142
+ }
143
+ );
144
+ }
145
+ function ChapterButton({
146
+ time,
147
+ label,
148
+ onSeek
149
+ }) {
150
+ const clock = testRecordingReport.formatRecordingClock(time);
151
+ return /* @__PURE__ */ jsxRuntime.jsx(
152
+ "button",
153
+ {
154
+ type: "button",
155
+ "data-testid": "test-recording-chapter",
156
+ onClick: () => onSeek(time),
157
+ "aria-label": `Play from ${clock}: ${label}`,
158
+ className: "shrink-0 rounded px-1.5 py-0.5 font-mono text-[11px] tabular-nums text-primary transition-colors hover:bg-primary/10 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
159
+ children: clock
160
+ }
161
+ );
162
+ }
163
+ const OUTCOME_LABEL = {
164
+ passed: "Passed",
165
+ failed: "Failed",
166
+ untested: "Not checked"
167
+ };
168
+ function OutcomeIcon({ result }) {
169
+ if (result === "passed") {
170
+ return /* @__PURE__ */ jsxRuntime.jsx("span", { className: "mt-px flex size-4 shrink-0 items-center justify-center rounded-full bg-emerald-500 text-white", children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.CheckIcon, { className: "size-2.5", strokeWidth: 3.5, "aria-hidden": true }) });
171
+ }
172
+ if (result === "failed") {
173
+ return /* @__PURE__ */ jsxRuntime.jsx("span", { className: "mt-px flex size-4 shrink-0 items-center justify-center rounded-full bg-destructive text-white", children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.XIcon, { className: "size-2.5", strokeWidth: 3.5, "aria-hidden": true }) });
174
+ }
175
+ return /* @__PURE__ */ jsxRuntime.jsx(lucideReact.CircleDashedIcon, { className: "mt-px size-4 shrink-0 text-muted-foreground", "aria-hidden": true });
176
+ }
177
+ function AssertionRow({
178
+ assertion,
179
+ onSeek
180
+ }) {
181
+ const failed = assertion.result === "failed";
182
+ const text = assertion.text || (failed ? "Failed" : "Passed");
183
+ return /* @__PURE__ */ jsxRuntime.jsxs(
184
+ "li",
185
+ {
186
+ "data-testid": "test-recording-assertion",
187
+ "data-result": assertion.result,
188
+ className: cnfast.cn(
189
+ "flex items-start gap-1.5 rounded-md px-1.5 py-0.5 text-[12px] leading-snug",
190
+ failed ? "bg-destructive/10 text-destructive" : "text-muted-foreground"
191
+ ),
192
+ children: [
193
+ failed ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.XIcon, { className: "mt-0.5 size-3 shrink-0", strokeWidth: 3, "aria-hidden": true }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.CheckIcon, { className: "mt-0.5 size-3 shrink-0 text-emerald-600", strokeWidth: 3, "aria-hidden": true }),
194
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "min-w-0 flex-1 break-words", children: [
195
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "sr-only", children: failed ? "Failed: " : "Passed: " }),
196
+ text
197
+ ] }),
198
+ onSeek && assertion.editedTimeS !== null ? /* @__PURE__ */ jsxRuntime.jsx(ChapterButton, { time: assertion.editedTimeS, label: text, onSeek }) : null
199
+ ]
200
+ }
201
+ );
202
+ }
203
+ function TestRow({ test, onSeek }) {
204
+ return /* @__PURE__ */ jsxRuntime.jsxs("li", { "data-testid": "test-recording-test", "data-result": test.result, className: "px-4 py-2.5", children: [
205
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start gap-2", children: [
206
+ /* @__PURE__ */ jsxRuntime.jsx(OutcomeIcon, { result: test.result }),
207
+ /* @__PURE__ */ jsxRuntime.jsxs(
208
+ "span",
209
+ {
210
+ className: cnfast.cn(
211
+ "min-w-0 flex-1 break-words text-[12.5px] font-medium leading-snug",
212
+ test.result === "failed" && "text-destructive",
213
+ test.result === "passed" && "text-foreground",
214
+ test.result === "untested" && "text-muted-foreground"
215
+ ),
216
+ children: [
217
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "sr-only", children: `${OUTCOME_LABEL[test.result]}: ` }),
218
+ test.name
219
+ ]
220
+ }
221
+ ),
222
+ test.result === "untested" ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "shrink-0 rounded-full bg-muted px-1.5 py-0.5 text-[10px] font-medium text-muted-foreground", children: "Not checked" }) : null,
223
+ onSeek && test.startTimeS !== null ? /* @__PURE__ */ jsxRuntime.jsx(ChapterButton, { time: test.startTimeS, label: test.name, onSeek }) : null
224
+ ] }),
225
+ test.assertions.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx("ul", { className: "mt-1 flex flex-col gap-0.5 pl-6", children: test.assertions.map((assertion, index) => /* @__PURE__ */ jsxRuntime.jsx(
226
+ AssertionRow,
227
+ {
228
+ assertion,
229
+ onSeek
230
+ },
231
+ index
232
+ )) }) : null
233
+ ] });
234
+ }
235
+ function TestRecordingReportCard({ report, className }) {
236
+ const [playback, setPlayback] = react.useState(IDLE_PLAYBACK);
237
+ const play = react.useCallback(() => {
238
+ setPlayback((previous) => ({ ...previous, requested: true }));
239
+ }, []);
240
+ const seek = react.useCallback((time) => {
241
+ setPlayback((previous) => {
242
+ var _a;
243
+ return {
244
+ requested: true,
245
+ seek: { time, nonce: (((_a = previous.seek) == null ? void 0 : _a.nonce) ?? 0) + 1 }
246
+ };
247
+ });
248
+ }, []);
249
+ const athenaConfig = react.useContext(AthenaContext.AthenaContext);
250
+ const playableAssetId = athenaConfig ? report.assetId : null;
251
+ const onSeek = playableAssetId ? seek : null;
252
+ const hasFailures = report.failed > 0;
253
+ return /* @__PURE__ */ jsxRuntime.jsxs(
254
+ "section",
255
+ {
256
+ "data-testid": "test-recording-report",
257
+ "data-has-failures": hasFailures ? "true" : "false",
258
+ "aria-label": `Test recording: ${report.title}`,
259
+ className: cnfast.cn(
260
+ "my-3 w-full overflow-hidden rounded-xl border border-border/60 bg-background shadow-sm",
261
+ className
262
+ ),
263
+ children: [
264
+ /* @__PURE__ */ jsxRuntime.jsxs("header", { className: "flex items-start gap-3 px-4 py-3", children: [
265
+ /* @__PURE__ */ jsxRuntime.jsx(
266
+ "span",
267
+ {
268
+ className: cnfast.cn(
269
+ "flex size-8 shrink-0 items-center justify-center rounded-lg",
270
+ hasFailures ? "bg-destructive/10 text-destructive" : "bg-emerald-50 text-emerald-600"
271
+ ),
272
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ClapperboardIcon, { className: "size-4", "aria-hidden": true })
273
+ }
274
+ ),
275
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0 flex-1", children: [
276
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "break-words text-[13px] font-medium leading-snug text-foreground", children: report.title }),
277
+ /* @__PURE__ */ jsxRuntime.jsxs("p", { "data-testid": "test-recording-counts", className: "text-[12px] tabular-nums text-muted-foreground", children: [
278
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: cnfast.cn(report.passed > 0 && "text-emerald-700"), children: [
279
+ report.passed,
280
+ " passed"
281
+ ] }),
282
+ " · ",
283
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: cnfast.cn(hasFailures && "font-medium text-destructive"), children: [
284
+ report.failed,
285
+ " failed"
286
+ ] }),
287
+ report.untested > 0 ? ` · ${report.untested} untested` : null
288
+ ] })
289
+ ] }),
290
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "shrink-0 rounded-md bg-muted/60 px-1.5 py-0.5 text-[10px] font-medium text-muted-foreground", children: "UI test" })
291
+ ] }),
292
+ playableAssetId ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "border-t border-border/40", children: /* @__PURE__ */ jsxRuntime.jsx(
293
+ TestRecordingPlayer,
294
+ {
295
+ assetId: playableAssetId,
296
+ title: report.title,
297
+ durationS: report.editedDurationS,
298
+ url: report.url,
299
+ playback,
300
+ onPlay: play
301
+ }
302
+ ) }) : null,
303
+ report.tests.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx("ol", { "aria-label": "Tests", className: "flex flex-col divide-y divide-border/40 border-t border-border/40", children: report.tests.map((test, index) => (
304
+ // Tests are an ordered log, never reordered; two may share a name.
305
+ /* @__PURE__ */ jsxRuntime.jsx(TestRow, { test, onSeek }, index)
306
+ )) }) : /* @__PURE__ */ jsxRuntime.jsx("p", { className: "border-t border-border/40 px-4 py-2.5 text-[12px] text-muted-foreground", children: "No tests were marked in this recording." }),
307
+ report.summary ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "border-t border-border/40 px-4 py-2.5", children: [
308
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[11px] font-medium text-muted-foreground", children: "Summary" }),
309
+ /* @__PURE__ */ jsxRuntime.jsx(
310
+ "p",
311
+ {
312
+ "data-testid": "test-recording-summary",
313
+ className: "mt-0.5 whitespace-pre-wrap break-words text-[12.5px] leading-relaxed text-foreground",
314
+ children: report.summary
315
+ }
316
+ )
317
+ ] }) : null,
318
+ report.url ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex justify-end border-t border-border/40 px-4 py-2", children: /* @__PURE__ */ jsxRuntime.jsxs(
319
+ "a",
320
+ {
321
+ href: report.url,
322
+ target: "_blank",
323
+ rel: "noopener noreferrer",
324
+ className: "flex items-center gap-1.5 rounded-md px-2 py-1 text-[12px] font-medium text-muted-foreground transition-colors hover:bg-muted/50 hover:text-foreground",
325
+ children: [
326
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ExternalLinkIcon, { className: "size-3", "aria-hidden": true }),
327
+ "Watch in Athena"
328
+ ]
329
+ }
330
+ ) }) : null
331
+ ]
332
+ }
333
+ );
334
+ }
335
+ const TestRecordingStopToolUIImpl = ({
336
+ toolName,
337
+ args,
338
+ result,
339
+ status
340
+ }) => {
341
+ const typedArgs = argsRecord(args);
342
+ const titleArg = argString(typedArgs, "title");
343
+ const parsed = react.useMemo(
344
+ () => testRecordingReport.parseTestRecordingStopResult(result, { fallbackTitle: titleArg }),
345
+ [result, titleArg]
346
+ );
347
+ if (parsed.kind === "saved") return /* @__PURE__ */ jsxRuntime.jsx(TestRecordingReportCard, { report: parsed.report });
348
+ const isRunning = (status == null ? void 0 : status.type) === "running";
349
+ const error = incompleteMessage(status, "The test recording could not be saved.") ?? (parsed.kind === "error" ? parsed.message : null);
350
+ const cardStatus = error ? "incomplete" : isRunning ? "running" : "complete";
351
+ return /* @__PURE__ */ jsxRuntime.jsx(
352
+ primitives.ToolCard,
353
+ {
354
+ icon: lucideReact.ClapperboardIcon,
355
+ status: cardStatus,
356
+ title: error ? "Couldn't save the test recording" : isRunning ? "Saving test recording…" : "Test recording",
357
+ subtitle: titleArg,
358
+ toolName,
359
+ error,
360
+ args: typedArgs,
361
+ result,
362
+ children: cardStatus === "complete" ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "border-t border-border/40 px-4 py-2.5 text-[12px] text-muted-foreground", children: "The recording's report could not be read." }) : null
363
+ }
364
+ );
365
+ };
366
+ const TestRecordingStopToolUI = react.memo(
367
+ TestRecordingStopToolUIImpl
368
+ );
369
+ TestRecordingStopToolUI.displayName = "TestRecordingStopToolUI";
370
+ function TestRecordingRow({
371
+ testId,
372
+ kind,
373
+ icon: Icon,
374
+ iconClassName,
375
+ label,
376
+ labelClassName,
377
+ text,
378
+ time,
379
+ error
380
+ }) {
381
+ return /* @__PURE__ */ jsxRuntime.jsxs(
382
+ "div",
383
+ {
384
+ "data-testid": testId,
385
+ "data-kind": kind,
386
+ "data-failed": error ? "true" : "false",
387
+ className: cnfast.cn(
388
+ "my-1 flex items-start gap-2 rounded-lg px-2 py-1 text-[12.5px] leading-snug",
389
+ error && "bg-destructive/5"
390
+ ),
391
+ children: [
392
+ /* @__PURE__ */ jsxRuntime.jsx(Icon, { className: cnfast.cn("mt-0.5 size-3.5 shrink-0", iconClassName), "aria-hidden": true }),
393
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0 flex-1", children: [
394
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: cnfast.cn("mr-1.5 font-medium", labelClassName), children: label }),
395
+ text ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "break-words text-foreground", children: text }) : null,
396
+ error ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-0.5 break-words text-[12px] text-destructive", children: error }) : null
397
+ ] }),
398
+ time ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "shrink-0 font-mono text-[11px] tabular-nums text-muted-foreground", children: time }) : null
399
+ ]
400
+ }
401
+ );
402
+ }
403
+ const TestRecordingStartToolUIImpl = ({ args, result, status }) => {
404
+ const start = react.useMemo(
405
+ () => testRecordingReport.describeTestRecordingStart({ args: argsRecord(args), result }),
406
+ [args, result]
407
+ );
408
+ const error = incompleteMessage(status, "The recording could not start.") ?? start.error;
409
+ const isRunning = !error && (status == null ? void 0 : status.type) === "running";
410
+ return /* @__PURE__ */ jsxRuntime.jsx(
411
+ TestRecordingRow,
412
+ {
413
+ testId: "test-recording-start",
414
+ icon: error ? lucideReact.AlertCircleIcon : isRunning ? lucideReact.LoaderIcon : lucideReact.CircleDotIcon,
415
+ iconClassName: cnfast.cn(
416
+ error && "text-destructive",
417
+ isRunning && "animate-spin text-muted-foreground",
418
+ !error && !isRunning && "text-red-500"
419
+ ),
420
+ label: error ? "Couldn't start recording" : isRunning ? "Starting recording" : "Recording started",
421
+ labelClassName: error ? "text-destructive" : "text-muted-foreground",
422
+ text: start.title,
423
+ error
424
+ }
425
+ );
426
+ };
427
+ const TestRecordingStartToolUI = react.memo(
428
+ TestRecordingStartToolUIImpl
429
+ );
430
+ TestRecordingStartToolUI.displayName = "TestRecordingStartToolUI";
431
+ const MARK_ROW = {
432
+ test: {
433
+ label: "Test",
434
+ icon: lucideReact.FlaskConicalIcon,
435
+ iconClassName: "text-primary",
436
+ labelClassName: "text-primary"
437
+ },
438
+ pass: {
439
+ label: "Pass",
440
+ icon: lucideReact.CheckIcon,
441
+ iconClassName: "text-emerald-600",
442
+ labelClassName: "text-emerald-700"
443
+ },
444
+ fail: {
445
+ label: "Fail",
446
+ icon: lucideReact.XIcon,
447
+ iconClassName: "text-destructive",
448
+ labelClassName: "text-destructive"
449
+ },
450
+ note: {
451
+ label: "Note",
452
+ icon: lucideReact.StickyNoteIcon,
453
+ iconClassName: "text-muted-foreground",
454
+ labelClassName: "text-muted-foreground"
455
+ }
456
+ };
457
+ const UNKNOWN_MARK_ROW = {
458
+ label: "Mark",
459
+ icon: lucideReact.StickyNoteIcon,
460
+ iconClassName: "text-muted-foreground",
461
+ labelClassName: "text-muted-foreground"
462
+ };
463
+ const TestRecordingMarkToolUIImpl = ({ args, result, status }) => {
464
+ const mark = react.useMemo(
465
+ () => testRecordingReport.describeTestRecordingMark({ args: argsRecord(args), result }),
466
+ [args, result]
467
+ );
468
+ const error = incompleteMessage(status, "The mark was not recorded.") ?? mark.error;
469
+ const isRunning = !error && (status == null ? void 0 : status.type) === "running";
470
+ const row = mark.kind ? MARK_ROW[mark.kind] : UNKNOWN_MARK_ROW;
471
+ return /* @__PURE__ */ jsxRuntime.jsx(
472
+ TestRecordingRow,
473
+ {
474
+ testId: "test-recording-mark",
475
+ kind: mark.kind ?? void 0,
476
+ icon: error ? lucideReact.AlertCircleIcon : isRunning ? lucideReact.LoaderIcon : row.icon,
477
+ iconClassName: cnfast.cn(
478
+ error && "text-destructive",
479
+ isRunning && "animate-spin text-muted-foreground",
480
+ !error && !isRunning && row.iconClassName
481
+ ),
482
+ label: error ? `Couldn't mark ${row.label.toLowerCase()}` : row.label,
483
+ labelClassName: error ? "text-destructive" : row.labelClassName,
484
+ text: mark.text,
485
+ time: mark.elapsedS !== null ? testRecordingReport.formatRecordingClock(mark.elapsedS) : null,
486
+ error
487
+ }
488
+ );
489
+ };
490
+ const TestRecordingMarkToolUI = react.memo(
491
+ TestRecordingMarkToolUIImpl
492
+ );
493
+ TestRecordingMarkToolUI.displayName = "TestRecordingMarkToolUI";
494
+ exports.TestRecordingMarkToolUI = TestRecordingMarkToolUI;
495
+ exports.TestRecordingReportCard = TestRecordingReportCard;
496
+ exports.TestRecordingStartToolUI = TestRecordingStartToolUI;
497
+ exports.TestRecordingStopToolUI = TestRecordingStopToolUI;
498
+ //# sourceMappingURL=test-recording-tool-uis.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"test-recording-tool-uis.cjs","sources":["../../src/tools/test-recording-tool-uis.tsx"],"sourcesContent":["\"use client\";\n\n/**\n * The UI Testing toolkit's cards: an agent records itself checking a UI\n * change on its computer and hands back an annotated video.\n *\n * - `test_recording_stop` — the report: the recording's title, \"N passed · M\n * failed\", each test with its ✓ / ✗ assertions (what the agent saw), the\n * summary, and the video, with every test's start as a chapter that plays\n * from there. It stays in the conversation instead of folding into the\n * turn's steps (`isSdkInlineTool`).\n * - `test_recording_start` / `test_recording_mark` — one compact row each.\n *\n * The video is an Athena asset the signed-in user owns. It is read with the\n * user's own credential through `useAthenaAssetContent` (a permission-checked\n * read of the stored file) and played from a page-local object URL, only when\n * the person presses play or picks a chapter; no shareable link is minted.\n */\nimport type { ToolCallMessagePartComponent } from \"@assistant-ui/react\";\nimport {\n AlertCircleIcon,\n CheckIcon,\n CircleDashedIcon,\n CircleDotIcon,\n ClapperboardIcon,\n ExternalLinkIcon,\n FlaskConicalIcon,\n LoaderIcon,\n PlayIcon,\n StickyNoteIcon,\n XIcon,\n} from \"lucide-react\";\nimport {\n type ElementType,\n memo,\n type SyntheticEvent,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from \"react\";\nimport { useAthenaAssetContent, useObjectUrl } from \"../assets/use-asset-content\";\nimport { cn } from \"../lib/utils\";\nimport { AthenaContext } from \"../provider/AthenaContext\";\nimport { ToolCard, type ToolStatus } from \"./primitives\";\nimport {\n describeTestRecordingMark,\n describeTestRecordingStart,\n formatRecordingClock,\n parseTestRecordingStopResult,\n type TestRecordingAssertion,\n type TestRecordingMarkKind,\n type TestRecordingOutcome,\n type TestRecordingReport,\n type TestRecordingTest,\n} from \"./test-recording-report\";\n\n// ─── Helpers ─────────────────────────────────────────────────────────\n\nfunction argsRecord(args: unknown): Record<string, unknown> | undefined {\n return typeof args === \"object\" && args !== null && !Array.isArray(args)\n ? (args as Record<string, unknown>)\n : undefined;\n}\n\nfunction argString(args: Record<string, unknown> | undefined, key: string): string | undefined {\n const value = args?.[key];\n return typeof value === \"string\" && value.trim() !== \"\" ? value.trim() : undefined;\n}\n\n/** The message an `incomplete` tool status carries, or `null` for any other status. */\nfunction incompleteMessage(\n status: { type: string; reason?: string; error?: unknown } | undefined,\n fallback: string,\n): string | null {\n if (status?.type !== \"incomplete\") return null;\n if (status.reason === \"cancelled\") return \"Cancelled\";\n if (typeof status.error === \"string\" && status.error.trim() !== \"\") return status.error;\n return fallback;\n}\n\n// ─── Playback ────────────────────────────────────────────────────────\n\ninterface PlaybackRequest {\n /** The person asked to play: the video is read from then on. */\n requested: boolean;\n /** The last chapter picked; `nonce` makes picking the same chapter twice seek twice. */\n seek: { time: number; nonce: number } | null;\n}\n\nconst IDLE_PLAYBACK: PlaybackRequest = { requested: false, seek: null };\n\n/** `HTMLMediaElement.HAVE_METADATA`: below it a seek waits for the video's metadata. */\nconst HAVE_METADATA = 1;\n\nfunction playbackErrorMessage(error: Error): string {\n const status = (error as { status?: unknown }).status;\n if (status === 401 || status === 403) return \"You don't have access to this recording.\";\n if (status === 404) return \"The recording's video was not found.\";\n return \"The recording could not be loaded.\";\n}\n\nfunction TestRecordingPlayer({\n assetId,\n title,\n durationS,\n url,\n playback,\n onPlay,\n}: {\n assetId: string;\n title: string;\n durationS: number | null;\n url: string | null;\n playback: PlaybackRequest;\n onPlay: () => void;\n}) {\n const content = useAthenaAssetContent({ assetId, enabled: playback.requested });\n // Bytes a query already holds still wait for the person to press play.\n const src = useObjectUrl(playback.requested ? content.data : undefined);\n const { refetch } = content;\n const retry = useCallback(() => {\n void refetch();\n }, [refetch]);\n const videoRef = useRef<HTMLVideoElement | null>(null);\n const pendingSeekRef = useRef<number | null>(null);\n const seekTime = playback.seek?.time;\n const seekNonce = playback.seek?.nonce ?? 0;\n\n // Play once the bytes are here, and again from each chapter picked (the\n // nonce is the pick's change signal; picking one chapter twice seeks twice).\n // A seek before the metadata loads sets the start position and is applied\n // again once it has (`onLoadedMetadata`).\n // eslint-disable-next-line react-hooks/exhaustive-deps\n useEffect(() => {\n const video = videoRef.current;\n if (!video || !src) return;\n if (seekTime !== undefined) {\n video.currentTime = seekTime;\n pendingSeekRef.current = video.readyState < HAVE_METADATA ? seekTime : null;\n }\n video.play()?.catch(() => {\n // Autoplay refused (no recent user gesture): the controls stay to press play.\n });\n }, [src, seekNonce]);\n\n const handleLoadedMetadata = useCallback((event: SyntheticEvent<HTMLVideoElement>) => {\n const pending = pendingSeekRef.current;\n pendingSeekRef.current = null;\n if (pending !== null) event.currentTarget.currentTime = pending;\n }, []);\n\n if (src) {\n return (\n <video\n ref={videoRef}\n data-testid=\"test-recording-video\"\n src={src}\n controls\n playsInline\n preload=\"auto\"\n aria-label={`Recording: ${title}`}\n onLoadedMetadata={handleLoadedMetadata}\n className=\"block max-h-[480px] w-full bg-black\"\n />\n );\n }\n\n if (content.isError && !content.isFetching) {\n return (\n <div\n role=\"alert\"\n data-testid=\"test-recording-video-error\"\n className=\"flex aspect-video w-full flex-col items-center justify-center gap-2 bg-muted/40 px-6 text-center\"\n >\n <AlertCircleIcon className=\"size-5 text-destructive\" aria-hidden />\n <p className=\"text-[12.5px] text-foreground\">{playbackErrorMessage(content.error)}</p>\n <div className=\"flex items-center gap-3\">\n {/* An explicit retry, refusals included: access may have changed since. */}\n <button\n type=\"button\"\n onClick={retry}\n className=\"rounded-md border border-border bg-background px-2.5 py-1 text-[12px] font-medium text-foreground transition-colors hover:bg-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring\"\n >\n Try again\n </button>\n {url ? (\n <a\n href={url}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"text-[12px] font-medium text-primary hover:underline\"\n >\n Watch in Athena\n </a>\n ) : null}\n </div>\n </div>\n );\n }\n\n if (playback.requested) {\n return (\n <output\n data-testid=\"test-recording-video-loading\"\n className=\"flex aspect-video w-full items-center justify-center gap-2 bg-zinc-950 text-[12px] text-white/80\"\n >\n <LoaderIcon className=\"size-4 animate-spin\" aria-hidden />\n Loading recording…\n </output>\n );\n }\n\n return (\n <button\n type=\"button\"\n onClick={onPlay}\n aria-label={`Play recording: ${title}`}\n className=\"group relative flex aspect-video w-full items-center justify-center bg-zinc-950 text-white focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-inset\"\n >\n <span className=\"flex size-12 items-center justify-center rounded-full bg-white/15 transition-colors group-hover:bg-white/25\">\n <PlayIcon className=\"ml-0.5 size-5 fill-current\" aria-hidden />\n </span>\n {durationS !== null ? (\n <span className=\"absolute right-3 bottom-2 text-[11px] tabular-nums text-white/80\">\n {formatRecordingClock(durationS)}\n </span>\n ) : null}\n </button>\n );\n}\n\n/** A time that plays the recording from there: a test's chapter or an assertion's moment. */\nfunction ChapterButton({\n time,\n label,\n onSeek,\n}: {\n time: number;\n label: string;\n onSeek: (time: number) => void;\n}) {\n const clock = formatRecordingClock(time);\n return (\n <button\n type=\"button\"\n data-testid=\"test-recording-chapter\"\n onClick={() => onSeek(time)}\n aria-label={`Play from ${clock}: ${label}`}\n className=\"shrink-0 rounded px-1.5 py-0.5 font-mono text-[11px] tabular-nums text-primary transition-colors hover:bg-primary/10 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring\"\n >\n {clock}\n </button>\n );\n}\n\n// ─── Report card ─────────────────────────────────────────────────────\n\nconst OUTCOME_LABEL: Record<TestRecordingOutcome, string> = {\n passed: \"Passed\",\n failed: \"Failed\",\n untested: \"Not checked\",\n};\n\nfunction OutcomeIcon({ result }: { result: TestRecordingOutcome }) {\n if (result === \"passed\") {\n return (\n <span className=\"mt-px flex size-4 shrink-0 items-center justify-center rounded-full bg-emerald-500 text-white\">\n <CheckIcon className=\"size-2.5\" strokeWidth={3.5} aria-hidden />\n </span>\n );\n }\n if (result === \"failed\") {\n return (\n <span className=\"mt-px flex size-4 shrink-0 items-center justify-center rounded-full bg-destructive text-white\">\n <XIcon className=\"size-2.5\" strokeWidth={3.5} aria-hidden />\n </span>\n );\n }\n return <CircleDashedIcon className=\"mt-px size-4 shrink-0 text-muted-foreground\" aria-hidden />;\n}\n\nfunction AssertionRow({\n assertion,\n onSeek,\n}: {\n assertion: TestRecordingAssertion;\n onSeek: ((time: number) => void) | null;\n}) {\n const failed = assertion.result === \"failed\";\n const text = assertion.text || (failed ? \"Failed\" : \"Passed\");\n return (\n <li\n data-testid=\"test-recording-assertion\"\n data-result={assertion.result}\n className={cn(\n \"flex items-start gap-1.5 rounded-md px-1.5 py-0.5 text-[12px] leading-snug\",\n failed ? \"bg-destructive/10 text-destructive\" : \"text-muted-foreground\",\n )}\n >\n {failed ? (\n <XIcon className=\"mt-0.5 size-3 shrink-0\" strokeWidth={3} aria-hidden />\n ) : (\n <CheckIcon className=\"mt-0.5 size-3 shrink-0 text-emerald-600\" strokeWidth={3} aria-hidden />\n )}\n <span className=\"min-w-0 flex-1 break-words\">\n <span className=\"sr-only\">{failed ? \"Failed: \" : \"Passed: \"}</span>\n {text}\n </span>\n {onSeek && assertion.editedTimeS !== null ? (\n <ChapterButton time={assertion.editedTimeS} label={text} onSeek={onSeek} />\n ) : null}\n </li>\n );\n}\n\nfunction TestRow({ test, onSeek }: { test: TestRecordingTest; onSeek: ((time: number) => void) | null }) {\n return (\n <li data-testid=\"test-recording-test\" data-result={test.result} className=\"px-4 py-2.5\">\n <div className=\"flex items-start gap-2\">\n <OutcomeIcon result={test.result} />\n <span\n className={cn(\n \"min-w-0 flex-1 break-words text-[12.5px] font-medium leading-snug\",\n test.result === \"failed\" && \"text-destructive\",\n test.result === \"passed\" && \"text-foreground\",\n test.result === \"untested\" && \"text-muted-foreground\",\n )}\n >\n <span className=\"sr-only\">{`${OUTCOME_LABEL[test.result]}: `}</span>\n {test.name}\n </span>\n {test.result === \"untested\" ? (\n <span className=\"shrink-0 rounded-full bg-muted px-1.5 py-0.5 text-[10px] font-medium text-muted-foreground\">\n Not checked\n </span>\n ) : null}\n {onSeek && test.startTimeS !== null ? (\n <ChapterButton time={test.startTimeS} label={test.name} onSeek={onSeek} />\n ) : null}\n </div>\n {test.assertions.length > 0 ? (\n <ul className=\"mt-1 flex flex-col gap-0.5 pl-6\">\n {test.assertions.map((assertion, index) => (\n <AssertionRow\n // Assertions are an ordered log with no id of their own, never reordered.\n key={index}\n assertion={assertion}\n onSeek={onSeek}\n />\n ))}\n </ul>\n ) : null}\n </li>\n );\n}\n\nexport interface TestRecordingReportCardProps {\n report: TestRecordingReport;\n className?: string;\n}\n\n/**\n * A saved test recording: the title and counts, the video (read with the\n * signed-in user's credential when the person presses play), each test with\n * its assertions and chapter times, the summary, and a link to the asset in\n * Athena.\n */\nexport function TestRecordingReportCard({ report, className }: TestRecordingReportCardProps) {\n const [playback, setPlayback] = useState<PlaybackRequest>(IDLE_PLAYBACK);\n const play = useCallback(() => {\n setPlayback((previous) => ({ ...previous, requested: true }));\n }, []);\n const seek = useCallback((time: number) => {\n setPlayback((previous) => ({\n requested: true,\n seek: { time, nonce: (previous.seek?.nonce ?? 0) + 1 },\n }));\n }, []);\n // The video is read with the provider's credentials; a host that renders\n // the card outside `<AthenaProvider>` gets the report and the link only.\n const athenaConfig = useContext(AthenaContext);\n const playableAssetId = athenaConfig ? report.assetId : null;\n const onSeek = playableAssetId ? seek : null;\n const hasFailures = report.failed > 0;\n\n return (\n <section\n data-testid=\"test-recording-report\"\n data-has-failures={hasFailures ? \"true\" : \"false\"}\n aria-label={`Test recording: ${report.title}`}\n className={cn(\n \"my-3 w-full overflow-hidden rounded-xl border border-border/60 bg-background shadow-sm\",\n className,\n )}\n >\n <header className=\"flex items-start gap-3 px-4 py-3\">\n <span\n className={cn(\n \"flex size-8 shrink-0 items-center justify-center rounded-lg\",\n hasFailures ? \"bg-destructive/10 text-destructive\" : \"bg-emerald-50 text-emerald-600\",\n )}\n >\n <ClapperboardIcon className=\"size-4\" aria-hidden />\n </span>\n <div className=\"min-w-0 flex-1\">\n <h3 className=\"break-words text-[13px] font-medium leading-snug text-foreground\">\n {report.title}\n </h3>\n <p data-testid=\"test-recording-counts\" className=\"text-[12px] tabular-nums text-muted-foreground\">\n <span className={cn(report.passed > 0 && \"text-emerald-700\")}>{report.passed} passed</span>\n {\" · \"}\n <span className={cn(hasFailures && \"font-medium text-destructive\")}>{report.failed} failed</span>\n {report.untested > 0 ? ` · ${report.untested} untested` : null}\n </p>\n </div>\n <span className=\"shrink-0 rounded-md bg-muted/60 px-1.5 py-0.5 text-[10px] font-medium text-muted-foreground\">\n UI test\n </span>\n </header>\n\n {playableAssetId ? (\n <div className=\"border-t border-border/40\">\n <TestRecordingPlayer\n assetId={playableAssetId}\n title={report.title}\n durationS={report.editedDurationS}\n url={report.url}\n playback={playback}\n onPlay={play}\n />\n </div>\n ) : null}\n\n {report.tests.length > 0 ? (\n <ol aria-label=\"Tests\" className=\"flex flex-col divide-y divide-border/40 border-t border-border/40\">\n {report.tests.map((test, index) => (\n // Tests are an ordered log, never reordered; two may share a name.\n <TestRow key={index} test={test} onSeek={onSeek} />\n ))}\n </ol>\n ) : (\n <p className=\"border-t border-border/40 px-4 py-2.5 text-[12px] text-muted-foreground\">\n No tests were marked in this recording.\n </p>\n )}\n\n {report.summary ? (\n <div className=\"border-t border-border/40 px-4 py-2.5\">\n <p className=\"text-[11px] font-medium text-muted-foreground\">Summary</p>\n <p\n data-testid=\"test-recording-summary\"\n className=\"mt-0.5 whitespace-pre-wrap break-words text-[12.5px] leading-relaxed text-foreground\"\n >\n {report.summary}\n </p>\n </div>\n ) : null}\n\n {report.url ? (\n <div className=\"flex justify-end border-t border-border/40 px-4 py-2\">\n <a\n href={report.url}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"flex items-center gap-1.5 rounded-md px-2 py-1 text-[12px] font-medium text-muted-foreground transition-colors hover:bg-muted/50 hover:text-foreground\"\n >\n <ExternalLinkIcon className=\"size-3\" aria-hidden />\n Watch in Athena\n </a>\n </div>\n ) : null}\n </section>\n );\n}\n\n// ─── test_recording_stop ─────────────────────────────────────────────\n\nconst TestRecordingStopToolUIImpl: ToolCallMessagePartComponent = ({\n toolName,\n args,\n result,\n status,\n}) => {\n const typedArgs = argsRecord(args);\n const titleArg = argString(typedArgs, \"title\");\n const parsed = useMemo(\n () => parseTestRecordingStopResult(result, { fallbackTitle: titleArg }),\n [result, titleArg],\n );\n\n if (parsed.kind === \"saved\") return <TestRecordingReportCard report={parsed.report} />;\n\n const isRunning = status?.type === \"running\";\n const error =\n incompleteMessage(status, \"The test recording could not be saved.\") ??\n (parsed.kind === \"error\" ? parsed.message : null);\n const cardStatus: ToolStatus = error ? \"incomplete\" : isRunning ? \"running\" : \"complete\";\n return (\n <ToolCard\n icon={ClapperboardIcon}\n status={cardStatus}\n title={\n error ? \"Couldn't save the test recording\" : isRunning ? \"Saving test recording…\" : \"Test recording\"\n }\n subtitle={titleArg}\n toolName={toolName}\n error={error}\n args={typedArgs}\n result={result}\n >\n {cardStatus === \"complete\" ? (\n <p className=\"border-t border-border/40 px-4 py-2.5 text-[12px] text-muted-foreground\">\n The recording's report could not be read.\n </p>\n ) : null}\n </ToolCard>\n );\n};\n\nexport const TestRecordingStopToolUI = memo(\n TestRecordingStopToolUIImpl,\n) as unknown as ToolCallMessagePartComponent;\nTestRecordingStopToolUI.displayName = \"TestRecordingStopToolUI\";\n\n// ─── Compact rows: test_recording_start / test_recording_mark ────────\n\nfunction TestRecordingRow({\n testId,\n kind,\n icon: Icon,\n iconClassName,\n label,\n labelClassName,\n text,\n time,\n error,\n}: {\n testId: string;\n kind?: string;\n icon: ElementType;\n iconClassName?: string;\n label: string;\n labelClassName?: string;\n text?: string;\n time?: string | null;\n error?: string | null;\n}) {\n return (\n <div\n data-testid={testId}\n data-kind={kind}\n data-failed={error ? \"true\" : \"false\"}\n className={cn(\n \"my-1 flex items-start gap-2 rounded-lg px-2 py-1 text-[12.5px] leading-snug\",\n error && \"bg-destructive/5\",\n )}\n >\n <Icon className={cn(\"mt-0.5 size-3.5 shrink-0\", iconClassName)} aria-hidden />\n <div className=\"min-w-0 flex-1\">\n <span className={cn(\"mr-1.5 font-medium\", labelClassName)}>{label}</span>\n {text ? <span className=\"break-words text-foreground\">{text}</span> : null}\n {error ? <p className=\"mt-0.5 break-words text-[12px] text-destructive\">{error}</p> : null}\n </div>\n {time ? (\n <span className=\"shrink-0 font-mono text-[11px] tabular-nums text-muted-foreground\">{time}</span>\n ) : null}\n </div>\n );\n}\n\nconst TestRecordingStartToolUIImpl: ToolCallMessagePartComponent = ({ args, result, status }) => {\n const start = useMemo(\n () => describeTestRecordingStart({ args: argsRecord(args), result }),\n [args, result],\n );\n const error = incompleteMessage(status, \"The recording could not start.\") ?? start.error;\n const isRunning = !error && status?.type === \"running\";\n return (\n <TestRecordingRow\n testId=\"test-recording-start\"\n icon={error ? AlertCircleIcon : isRunning ? LoaderIcon : CircleDotIcon}\n iconClassName={cn(\n error && \"text-destructive\",\n isRunning && \"animate-spin text-muted-foreground\",\n !error && !isRunning && \"text-red-500\",\n )}\n label={error ? \"Couldn't start recording\" : isRunning ? \"Starting recording\" : \"Recording started\"}\n labelClassName={error ? \"text-destructive\" : \"text-muted-foreground\"}\n text={start.title}\n error={error}\n />\n );\n};\n\nexport const TestRecordingStartToolUI = memo(\n TestRecordingStartToolUIImpl,\n) as unknown as ToolCallMessagePartComponent;\nTestRecordingStartToolUI.displayName = \"TestRecordingStartToolUI\";\n\nconst MARK_ROW: Record<\n TestRecordingMarkKind,\n { label: string; icon: ElementType; iconClassName: string; labelClassName: string }\n> = {\n test: {\n label: \"Test\",\n icon: FlaskConicalIcon,\n iconClassName: \"text-primary\",\n labelClassName: \"text-primary\",\n },\n pass: {\n label: \"Pass\",\n icon: CheckIcon,\n iconClassName: \"text-emerald-600\",\n labelClassName: \"text-emerald-700\",\n },\n fail: {\n label: \"Fail\",\n icon: XIcon,\n iconClassName: \"text-destructive\",\n labelClassName: \"text-destructive\",\n },\n note: {\n label: \"Note\",\n icon: StickyNoteIcon,\n iconClassName: \"text-muted-foreground\",\n labelClassName: \"text-muted-foreground\",\n },\n};\n\n/** A mark whose kind has not streamed in yet (or is one this SDK does not know). */\nconst UNKNOWN_MARK_ROW = {\n label: \"Mark\",\n icon: StickyNoteIcon,\n iconClassName: \"text-muted-foreground\",\n labelClassName: \"text-muted-foreground\",\n} as const;\n\nconst TestRecordingMarkToolUIImpl: ToolCallMessagePartComponent = ({ args, result, status }) => {\n const mark = useMemo(\n () => describeTestRecordingMark({ args: argsRecord(args), result }),\n [args, result],\n );\n const error = incompleteMessage(status, \"The mark was not recorded.\") ?? mark.error;\n const isRunning = !error && status?.type === \"running\";\n const row = mark.kind ? MARK_ROW[mark.kind] : UNKNOWN_MARK_ROW;\n return (\n <TestRecordingRow\n testId=\"test-recording-mark\"\n kind={mark.kind ?? undefined}\n icon={error ? AlertCircleIcon : isRunning ? LoaderIcon : row.icon}\n iconClassName={cn(\n error && \"text-destructive\",\n isRunning && \"animate-spin text-muted-foreground\",\n !error && !isRunning && row.iconClassName,\n )}\n label={error ? `Couldn't mark ${row.label.toLowerCase()}` : row.label}\n labelClassName={error ? \"text-destructive\" : row.labelClassName}\n text={mark.text}\n time={mark.elapsedS !== null ? formatRecordingClock(mark.elapsedS) : null}\n error={error}\n />\n );\n};\n\nexport const TestRecordingMarkToolUI = memo(\n TestRecordingMarkToolUIImpl,\n) as unknown as ToolCallMessagePartComponent;\nTestRecordingMarkToolUI.displayName = \"TestRecordingMarkToolUI\";\n"],"names":["useAthenaAssetContent","useObjectUrl","useCallback","useRef","useEffect","_a","jsx","jsxs","AlertCircleIcon","LoaderIcon","PlayIcon","formatRecordingClock","CheckIcon","XIcon","CircleDashedIcon","cn","useState","useContext","AthenaContext","ClapperboardIcon","ExternalLinkIcon","useMemo","parseTestRecordingStopResult","ToolCard","memo","describeTestRecordingStart","CircleDotIcon","FlaskConicalIcon","StickyNoteIcon","describeTestRecordingMark"],"mappings":"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"}
@@ -0,0 +1,32 @@
1
+ /**
2
+ * The UI Testing toolkit's cards: an agent records itself checking a UI
3
+ * change on its computer and hands back an annotated video.
4
+ *
5
+ * - `test_recording_stop` — the report: the recording's title, "N passed · M
6
+ * failed", each test with its ✓ / ✗ assertions (what the agent saw), the
7
+ * summary, and the video, with every test's start as a chapter that plays
8
+ * from there. It stays in the conversation instead of folding into the
9
+ * turn's steps (`isSdkInlineTool`).
10
+ * - `test_recording_start` / `test_recording_mark` — one compact row each.
11
+ *
12
+ * The video is an Athena asset the signed-in user owns. It is read with the
13
+ * user's own credential through `useAthenaAssetContent` (a permission-checked
14
+ * read of the stored file) and played from a page-local object URL, only when
15
+ * the person presses play or picks a chapter; no shareable link is minted.
16
+ */
17
+ import type { ToolCallMessagePartComponent } from "@assistant-ui/react";
18
+ import { type TestRecordingReport } from "./test-recording-report";
19
+ export interface TestRecordingReportCardProps {
20
+ report: TestRecordingReport;
21
+ className?: string;
22
+ }
23
+ /**
24
+ * A saved test recording: the title and counts, the video (read with the
25
+ * signed-in user's credential when the person presses play), each test with
26
+ * its assertions and chapter times, the summary, and a link to the asset in
27
+ * Athena.
28
+ */
29
+ export declare function TestRecordingReportCard({ report, className }: TestRecordingReportCardProps): import("react").JSX.Element;
30
+ export declare const TestRecordingStopToolUI: ToolCallMessagePartComponent;
31
+ export declare const TestRecordingStartToolUI: ToolCallMessagePartComponent;
32
+ export declare const TestRecordingMarkToolUI: ToolCallMessagePartComponent;