@athenaintel/react 0.39.0 → 0.40.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (126) hide show
  1. package/README.md +39 -3
  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/AthenaChat.cjs +24 -10
  20. package/dist/chat/AthenaChat.cjs.map +1 -1
  21. package/dist/chat/AthenaChat.js +25 -11
  22. package/dist/chat/AthenaChat.js.map +1 -1
  23. package/dist/chat/run-activity.cjs +29 -10
  24. package/dist/chat/run-activity.cjs.map +1 -1
  25. package/dist/chat/run-activity.d.ts +30 -4
  26. package/dist/chat/run-activity.js +29 -10
  27. package/dist/chat/run-activity.js.map +1 -1
  28. package/dist/chat/super-grouping/CollapsibleGroup.cjs +16 -8
  29. package/dist/chat/super-grouping/CollapsibleGroup.cjs.map +1 -1
  30. package/dist/chat/super-grouping/CollapsibleGroup.d.ts +26 -1
  31. package/dist/chat/super-grouping/CollapsibleGroup.js +16 -8
  32. package/dist/chat/super-grouping/CollapsibleGroup.js.map +1 -1
  33. package/dist/chat/super-grouping/GroupedMessageParts.cjs +59 -0
  34. package/dist/chat/super-grouping/GroupedMessageParts.cjs.map +1 -0
  35. package/dist/chat/super-grouping/GroupedMessageParts.d.ts +25 -0
  36. package/dist/chat/super-grouping/GroupedMessageParts.js +59 -0
  37. package/dist/chat/super-grouping/GroupedMessageParts.js.map +1 -0
  38. package/dist/chat/super-grouping/SuperGroupingCard.cjs +9 -5
  39. package/dist/chat/super-grouping/SuperGroupingCard.cjs.map +1 -1
  40. package/dist/chat/super-grouping/SuperGroupingCard.d.ts +2 -1
  41. package/dist/chat/super-grouping/SuperGroupingCard.js +13 -9
  42. package/dist/chat/super-grouping/SuperGroupingCard.js.map +1 -1
  43. package/dist/chat/super-grouping/SuperGroupingTranscript.cjs +10 -6
  44. package/dist/chat/super-grouping/SuperGroupingTranscript.cjs.map +1 -1
  45. package/dist/chat/super-grouping/SuperGroupingTranscript.js +14 -10
  46. package/dist/chat/super-grouping/SuperGroupingTranscript.js.map +1 -1
  47. package/dist/chat/super-grouping/grouped-part-keys.cjs +104 -0
  48. package/dist/chat/super-grouping/grouped-part-keys.cjs.map +1 -0
  49. package/dist/chat/super-grouping/grouped-part-keys.d.ts +37 -0
  50. package/dist/chat/super-grouping/grouped-part-keys.js +104 -0
  51. package/dist/chat/super-grouping/grouped-part-keys.js.map +1 -0
  52. package/dist/chat/super-grouping/index.cjs +2 -0
  53. package/dist/chat/super-grouping/index.cjs.map +1 -1
  54. package/dist/chat/super-grouping/index.d.ts +3 -1
  55. package/dist/chat/super-grouping/index.js +2 -0
  56. package/dist/chat/super-grouping/index.js.map +1 -1
  57. package/dist/chat-ui/composer/tiptap-composer.cjs +25 -19
  58. package/dist/chat-ui/composer/tiptap-composer.cjs.map +1 -1
  59. package/dist/chat-ui/composer/tiptap-composer.d.ts +5 -0
  60. package/dist/chat-ui/composer/tiptap-composer.js +26 -20
  61. package/dist/chat-ui/composer/tiptap-composer.js.map +1 -1
  62. package/dist/composer/clipboard.cjs +16 -0
  63. package/dist/composer/clipboard.cjs.map +1 -1
  64. package/dist/composer/clipboard.d.ts +9 -0
  65. package/dist/composer/clipboard.js +17 -1
  66. package/dist/composer/clipboard.js.map +1 -1
  67. package/dist/diagnostics/errors.cjs +6 -0
  68. package/dist/diagnostics/errors.cjs.map +1 -1
  69. package/dist/diagnostics/errors.d.ts +6 -0
  70. package/dist/diagnostics/errors.js +6 -0
  71. package/dist/diagnostics/errors.js.map +1 -1
  72. package/dist/index.cjs +23 -0
  73. package/dist/index.cjs.map +1 -1
  74. package/dist/index.d.ts +8 -1
  75. package/dist/index.js +23 -0
  76. package/dist/index.js.map +1 -1
  77. package/dist/lib/use-document-active.cjs +40 -0
  78. package/dist/lib/use-document-active.cjs.map +1 -0
  79. package/dist/lib/use-document-active.d.ts +9 -0
  80. package/dist/lib/use-document-active.js +40 -0
  81. package/dist/lib/use-document-active.js.map +1 -0
  82. package/dist/react/package.json.cjs +1 -1
  83. package/dist/react/package.json.js +1 -1
  84. package/dist/runtime/statewire-lifecycle.cjs +8 -0
  85. package/dist/runtime/statewire-lifecycle.cjs.map +1 -1
  86. package/dist/runtime/statewire-lifecycle.js +9 -1
  87. package/dist/runtime/statewire-lifecycle.js.map +1 -1
  88. package/dist/styles.css +1 -1
  89. package/dist/threads/Inbox.cjs +6 -2
  90. package/dist/threads/Inbox.cjs.map +1 -1
  91. package/dist/threads/Inbox.js +6 -2
  92. package/dist/threads/Inbox.js.map +1 -1
  93. package/dist/threads/mark-viewed-thread-read.cjs +97 -0
  94. package/dist/threads/mark-viewed-thread-read.cjs.map +1 -0
  95. package/dist/threads/mark-viewed-thread-read.d.ts +7 -0
  96. package/dist/threads/mark-viewed-thread-read.js +97 -0
  97. package/dist/threads/mark-viewed-thread-read.js.map +1 -0
  98. package/dist/tools/index.cjs +7 -0
  99. package/dist/tools/index.cjs.map +1 -1
  100. package/dist/tools/index.d.ts +2 -0
  101. package/dist/tools/index.js +7 -0
  102. package/dist/tools/index.js.map +1 -1
  103. package/dist/tools/inline-tools.cjs +10 -0
  104. package/dist/tools/inline-tools.cjs.map +1 -0
  105. package/dist/tools/inline-tools.d.ts +2 -0
  106. package/dist/tools/inline-tools.js +10 -0
  107. package/dist/tools/inline-tools.js.map +1 -0
  108. package/dist/tools/test-recording-report.cjs +175 -0
  109. package/dist/tools/test-recording-report.cjs.map +1 -0
  110. package/dist/tools/test-recording-report.d.ts +82 -0
  111. package/dist/tools/test-recording-report.js +175 -0
  112. package/dist/tools/test-recording-report.js.map +1 -0
  113. package/dist/tools/test-recording-tool-uis.cjs +498 -0
  114. package/dist/tools/test-recording-tool-uis.cjs.map +1 -0
  115. package/dist/tools/test-recording-tool-uis.d.ts +32 -0
  116. package/dist/tools/test-recording-tool-uis.js +498 -0
  117. package/dist/tools/test-recording-tool-uis.js.map +1 -0
  118. package/dist/tools/tool-summary.cjs +49 -1
  119. package/dist/tools/tool-summary.cjs.map +1 -1
  120. package/dist/tools/tool-summary.js +49 -1
  121. package/dist/tools/tool-summary.js.map +1 -1
  122. package/dist/tools/tool-uis.cjs +6 -1
  123. package/dist/tools/tool-uis.cjs.map +1 -1
  124. package/dist/tools/tool-uis.js +6 -1
  125. package/dist/tools/tool-uis.js.map +1 -1
  126. package/package.json +1 -1
@@ -0,0 +1,10 @@
1
+ import { TEST_RECORDING_STOP_TOOL } from "./test-recording-report.js";
2
+ import { isInlineTool } from "../agent-runtime/src/tool-metadata.js";
3
+ const SDK_INLINE_TOOLS = /* @__PURE__ */ new Set([TEST_RECORDING_STOP_TOOL]);
4
+ function isSdkInlineTool(toolName) {
5
+ return isInlineTool(toolName) || SDK_INLINE_TOOLS.has(toolName);
6
+ }
7
+ export {
8
+ isSdkInlineTool
9
+ };
10
+ //# sourceMappingURL=inline-tools.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inline-tools.js","sources":["../../src/tools/inline-tools.ts"],"sourcesContent":["/**\n * Which tool calls keep their card in the conversation instead of folding\n * into a grouped turn's collapsed steps: the shared registry's `inline` tools\n * (an editable draft, a draw.io diagram) plus the SDK cards that are the\n * answer a person reads in place and that the registry cannot list yet.\n *\n * `test_recording_stop` (the UI test report and its video) belongs in the\n * registry with `inline: true`, but the registry may only name tools agora\n * defines (`scripts/tool-registry-parity.test.ts`); it moves there once\n * agora's UI Testing toolkit lands, and this list empties.\n */\nimport { isInlineTool as isRegistryInlineTool } from \"@athenaintel/agent-runtime\";\nimport { TEST_RECORDING_STOP_TOOL } from \"./test-recording-report\";\n\nconst SDK_INLINE_TOOLS: ReadonlySet<string> = new Set([TEST_RECORDING_STOP_TOOL]);\n\n/** True when the tool's card renders in the conversation rather than behind collapsed details. */\nexport function isSdkInlineTool(toolName: string): boolean {\n return isRegistryInlineTool(toolName) || SDK_INLINE_TOOLS.has(toolName);\n}\n"],"names":["isRegistryInlineTool"],"mappings":";;AAcA,MAAM,mBAAwC,oBAAI,IAAI,CAAC,wBAAwB,CAAC;AAGzE,SAAS,gBAAgB,UAA2B;AACzD,SAAOA,aAAqB,QAAQ,KAAK,iBAAiB,IAAI,QAAQ;AACxE;"}
@@ -0,0 +1,175 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const toolResult = require("../agent-runtime/src/tool-result.cjs");
4
+ const TEST_RECORDING_START_TOOL = "test_recording_start";
5
+ const TEST_RECORDING_MARK_TOOL = "test_recording_mark";
6
+ const TEST_RECORDING_STOP_TOOL = "test_recording_stop";
7
+ const MARK_KINDS = ["test", "pass", "fail", "note"];
8
+ function str(value) {
9
+ return typeof value === "string" && value.trim() !== "" ? value.trim() : void 0;
10
+ }
11
+ function seconds(value) {
12
+ return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : null;
13
+ }
14
+ function count(value) {
15
+ return typeof value === "number" && Number.isInteger(value) && value >= 0 ? value : null;
16
+ }
17
+ function asRecord(value) {
18
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
19
+ }
20
+ function parseRecord(text) {
21
+ try {
22
+ return asRecord(JSON.parse(text));
23
+ } catch {
24
+ return null;
25
+ }
26
+ }
27
+ function resultRecord(result) {
28
+ const record = typeof result === "string" ? parseRecord(result) : asRecord(result);
29
+ if (record && typeof record.result === "string") return parseRecord(record.result) ?? record;
30
+ return record;
31
+ }
32
+ function safeHttpUrl(value) {
33
+ const raw = str(value);
34
+ if (!raw) return null;
35
+ try {
36
+ const url = new URL(raw);
37
+ return url.protocol === "https:" || url.protocol === "http:" ? raw : null;
38
+ } catch {
39
+ return null;
40
+ }
41
+ }
42
+ function assertionResult(value) {
43
+ if (value === "passed" || value === "pass") return "passed";
44
+ if (value === "failed" || value === "fail") return "failed";
45
+ return null;
46
+ }
47
+ function parseAssertions(value) {
48
+ if (!Array.isArray(value)) return [];
49
+ const assertions = [];
50
+ for (const raw of value) {
51
+ const record = asRecord(raw);
52
+ const result = assertionResult(record == null ? void 0 : record.result);
53
+ if (!record || !result) continue;
54
+ assertions.push({
55
+ text: str(record.text) ?? "",
56
+ result,
57
+ editedTimeS: seconds(record.edited_time_s)
58
+ });
59
+ }
60
+ return assertions;
61
+ }
62
+ function outcomeOf(assertions) {
63
+ if (assertions.some((assertion) => assertion.result === "failed")) return "failed";
64
+ return assertions.length > 0 ? "passed" : "untested";
65
+ }
66
+ function parseTests(value) {
67
+ if (!Array.isArray(value)) return [];
68
+ const tests = [];
69
+ for (const raw of value) {
70
+ const record = asRecord(raw);
71
+ const name = str(record == null ? void 0 : record.name);
72
+ if (!record || !name) continue;
73
+ const assertions = parseAssertions(record.assertions);
74
+ const declared = record.result;
75
+ tests.push({
76
+ name,
77
+ result: declared === "passed" || declared === "failed" || declared === "untested" ? declared : outcomeOf(assertions),
78
+ startTimeS: seconds(record.start_time_s),
79
+ assertions
80
+ });
81
+ }
82
+ return tests;
83
+ }
84
+ function parseTestRecordingStopResult(result, { fallbackTitle } = {}) {
85
+ if (result === void 0 || result === null || result === "") return { kind: "unreadable" };
86
+ const record = resultRecord(result);
87
+ if (!record) {
88
+ const failure = toolResult.toolFailure({ result });
89
+ return failure ? { kind: "error", message: failure.message } : { kind: "unreadable" };
90
+ }
91
+ if (record.status === "error" || str(record.error)) {
92
+ return {
93
+ kind: "error",
94
+ message: str(record.error) ?? "The test recording could not be saved."
95
+ };
96
+ }
97
+ if (record.status !== void 0 && record.status !== "saved") return { kind: "unreadable" };
98
+ const tests = parseTests(record.tests);
99
+ const assetId = str(record.asset_id) ?? null;
100
+ if (record.status !== "saved" && !assetId && tests.length === 0) return { kind: "unreadable" };
101
+ const title = str(record.title) ?? str(fallbackTitle) ?? "";
102
+ return {
103
+ kind: "saved",
104
+ report: {
105
+ assetId,
106
+ url: safeHttpUrl(record.url),
107
+ title: title || "UI test recording",
108
+ summary: str(record.summary) ?? "",
109
+ passed: count(record.passed) ?? tests.filter((test) => test.result === "passed").length,
110
+ failed: count(record.failed) ?? tests.filter((test) => test.result === "failed").length,
111
+ untested: tests.filter((test) => test.result === "untested").length,
112
+ tests,
113
+ sourceDurationS: seconds(record.source_duration_s),
114
+ editedDurationS: seconds(record.edited_duration_s)
115
+ }
116
+ };
117
+ }
118
+ function callError(result) {
119
+ var _a;
120
+ const record = resultRecord(result);
121
+ if (record) {
122
+ if (record.status === "error" || str(record.error)) {
123
+ return str(record.error) ?? "The call failed.";
124
+ }
125
+ return null;
126
+ }
127
+ return ((_a = toolResult.toolFailure({ result })) == null ? void 0 : _a.message) ?? null;
128
+ }
129
+ function describeTestRecordingMark({
130
+ args,
131
+ result
132
+ }) {
133
+ const record = resultRecord(result);
134
+ const rawKind = (args == null ? void 0 : args.kind) ?? (record == null ? void 0 : record.kind);
135
+ const kind = MARK_KINDS.find((candidate) => candidate === rawKind) ?? null;
136
+ return {
137
+ kind,
138
+ text: str(args == null ? void 0 : args.text) ?? "",
139
+ elapsedS: seconds(record == null ? void 0 : record.elapsed_s),
140
+ error: callError(result)
141
+ };
142
+ }
143
+ function describeTestRecordingStart({
144
+ args,
145
+ result
146
+ }) {
147
+ const record = resultRecord(result);
148
+ return {
149
+ title: str(record == null ? void 0 : record.title) ?? str(args == null ? void 0 : args.title) ?? "",
150
+ recordingId: str(record == null ? void 0 : record.recording_id) ?? null,
151
+ error: callError(result)
152
+ };
153
+ }
154
+ function formatRecordingClock(totalSeconds) {
155
+ const whole = Math.max(0, Math.floor(Number.isFinite(totalSeconds) ? totalSeconds : 0));
156
+ const hours = Math.floor(whole / 3600);
157
+ const minutes = Math.floor(whole % 3600 / 60);
158
+ const secs = String(whole % 60).padStart(2, "0");
159
+ return hours > 0 ? `${hours}:${String(minutes).padStart(2, "0")}:${secs}` : `${minutes}:${secs}`;
160
+ }
161
+ function formatTestRecordingCounts(report) {
162
+ const parts = [`${report.passed} passed`, `${report.failed} failed`];
163
+ if (report.untested > 0) parts.push(`${report.untested} untested`);
164
+ return parts.join(" · ");
165
+ }
166
+ exports.TEST_RECORDING_MARK_TOOL = TEST_RECORDING_MARK_TOOL;
167
+ exports.TEST_RECORDING_START_TOOL = TEST_RECORDING_START_TOOL;
168
+ exports.TEST_RECORDING_STOP_TOOL = TEST_RECORDING_STOP_TOOL;
169
+ exports.describeTestRecordingMark = describeTestRecordingMark;
170
+ exports.describeTestRecordingStart = describeTestRecordingStart;
171
+ exports.formatRecordingClock = formatRecordingClock;
172
+ exports.formatTestRecordingCounts = formatTestRecordingCounts;
173
+ exports.parseTestRecordingStopResult = parseTestRecordingStopResult;
174
+ exports.safeHttpUrl = safeHttpUrl;
175
+ //# sourceMappingURL=test-recording-report.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"test-recording-report.cjs","sources":["../../src/tools/test-recording-report.ts"],"sourcesContent":["/**\n * The UI Testing toolkit's three tools as the SDK's cards and step rows read\n * them: `test_recording_start` (a recorder starts on the agent's computer),\n * `test_recording_mark` (a test begins, an assertion passes or fails, a note)\n * and `test_recording_stop` (the edited video is saved as an asset, with the\n * test report). Each tool message's content is its result JSON as a string;\n * a failed call answers `{ \"status\": \"error\", \"error\": \"…\" }`.\n *\n * Every reader here is total: a result that is missing, still streaming,\n * truncated or shaped differently reads as \"nothing to show yet\" or as the\n * parts it does carry, never as a throw. Pure and React-free.\n */\nimport { toolFailure } from \"@athenaintel/agent-runtime\";\n\nexport const TEST_RECORDING_START_TOOL = \"test_recording_start\";\nexport const TEST_RECORDING_MARK_TOOL = \"test_recording_mark\";\nexport const TEST_RECORDING_STOP_TOOL = \"test_recording_stop\";\n\n/** A test's outcome: any failed assertion fails it; one with no assertion was not checked. */\nexport type TestRecordingOutcome = \"passed\" | \"failed\" | \"untested\";\n\nexport interface TestRecordingAssertion {\n /** What the agent actually observed. */\n text: string;\n result: \"passed\" | \"failed\";\n /** Where the assertion was marked in the edited video, in seconds. */\n editedTimeS: number | null;\n}\n\nexport interface TestRecordingTest {\n /** \"It should …\". */\n name: string;\n result: TestRecordingOutcome;\n /** Where the test starts in the edited video, in seconds: the chapter. */\n startTimeS: number | null;\n assertions: TestRecordingAssertion[];\n}\n\nexport interface TestRecordingReport {\n /** The saved video asset. */\n assetId: string | null;\n /** The asset in Athena (an `http(s)` URL only). */\n url: string | null;\n title: string;\n summary: string;\n passed: number;\n failed: number;\n untested: number;\n tests: TestRecordingTest[];\n sourceDurationS: number | null;\n editedDurationS: number | null;\n}\n\nexport type TestRecordingStopResult =\n | { kind: \"saved\"; report: TestRecordingReport }\n | { kind: \"error\"; message: string }\n /** No result yet, or one that is not a report (partial JSON while it streams). */\n | { kind: \"unreadable\" };\n\nexport type TestRecordingMarkKind = \"test\" | \"pass\" | \"fail\" | \"note\";\n\nexport interface TestRecordingMark {\n /** `null` while the arguments are still streaming. */\n kind: TestRecordingMarkKind | null;\n text: string;\n /** Seconds since the recording started, once the mark is in. */\n elapsedS: number | null;\n error: string | null;\n}\n\nexport interface TestRecordingStart {\n title: string;\n recordingId: string | null;\n error: string | null;\n}\n\nconst MARK_KINDS: readonly TestRecordingMarkKind[] = [\"test\", \"pass\", \"fail\", \"note\"];\n\nfunction str(value: unknown): string | undefined {\n return typeof value === \"string\" && value.trim() !== \"\" ? value.trim() : undefined;\n}\n\nfunction seconds(value: unknown): number | null {\n return typeof value === \"number\" && Number.isFinite(value) && value >= 0 ? value : null;\n}\n\nfunction count(value: unknown): number | null {\n return typeof value === \"number\" && Number.isInteger(value) && value >= 0 ? value : null;\n}\n\nfunction asRecord(value: unknown): Record<string, unknown> | null {\n return typeof value === \"object\" && value !== null && !Array.isArray(value)\n ? (value as Record<string, unknown>)\n : null;\n}\n\nfunction parseRecord(text: string): Record<string, unknown> | null {\n try {\n return asRecord(JSON.parse(text));\n } catch {\n return null;\n }\n}\n\n/**\n * A tool result as a record: the JSON string the tool message carries, an\n * object, or either wrapped in `{ result }` (as `normalizeResult` reads it).\n * `null` for anything else, a JSON string cut off mid-stream included.\n */\nfunction resultRecord(result: unknown): Record<string, unknown> | null {\n const record = typeof result === \"string\" ? parseRecord(result) : asRecord(result);\n if (record && typeof record.result === \"string\") return parseRecord(record.result) ?? record;\n return record;\n}\n\n/** An `http(s)` URL as given, else `null` (never a `javascript:` or other scheme from a result). */\nexport function safeHttpUrl(value: unknown): string | null {\n const raw = str(value);\n if (!raw) return null;\n try {\n const url = new URL(raw);\n return url.protocol === \"https:\" || url.protocol === \"http:\" ? raw : null;\n } catch {\n return null;\n }\n}\n\nfunction assertionResult(value: unknown): \"passed\" | \"failed\" | null {\n if (value === \"passed\" || value === \"pass\") return \"passed\";\n if (value === \"failed\" || value === \"fail\") return \"failed\";\n return null;\n}\n\nfunction parseAssertions(value: unknown): TestRecordingAssertion[] {\n if (!Array.isArray(value)) return [];\n const assertions: TestRecordingAssertion[] = [];\n for (const raw of value) {\n const record = asRecord(raw);\n const result = assertionResult(record?.result);\n if (!record || !result) continue;\n assertions.push({\n text: str(record.text) ?? \"\",\n result,\n editedTimeS: seconds(record.edited_time_s),\n });\n }\n return assertions;\n}\n\n/** The contract's rule, for a test whose own `result` is missing or unknown. */\nfunction outcomeOf(assertions: readonly TestRecordingAssertion[]): TestRecordingOutcome {\n if (assertions.some((assertion) => assertion.result === \"failed\")) return \"failed\";\n return assertions.length > 0 ? \"passed\" : \"untested\";\n}\n\nfunction parseTests(value: unknown): TestRecordingTest[] {\n if (!Array.isArray(value)) return [];\n const tests: TestRecordingTest[] = [];\n for (const raw of value) {\n const record = asRecord(raw);\n const name = str(record?.name);\n if (!record || !name) continue;\n const assertions = parseAssertions(record.assertions);\n const declared = record.result;\n tests.push({\n name,\n result:\n declared === \"passed\" || declared === \"failed\" || declared === \"untested\"\n ? declared\n : outcomeOf(assertions),\n startTimeS: seconds(record.start_time_s),\n assertions,\n });\n }\n return tests;\n}\n\n/**\n * Read a `test_recording_stop` result. `fallbackTitle` is the call's own\n * `title` argument, for a report that names none.\n */\nexport function parseTestRecordingStopResult(\n result: unknown,\n { fallbackTitle }: { fallbackTitle?: string } = {},\n): TestRecordingStopResult {\n if (result === undefined || result === null || result === \"\") return { kind: \"unreadable\" };\n const record = resultRecord(result);\n if (!record) {\n const failure = toolFailure({ result });\n return failure ? { kind: \"error\", message: failure.message } : { kind: \"unreadable\" };\n }\n if (record.status === \"error\" || str(record.error)) {\n return {\n kind: \"error\",\n message: str(record.error) ?? \"The test recording could not be saved.\",\n };\n }\n if (record.status !== undefined && record.status !== \"saved\") return { kind: \"unreadable\" };\n\n const tests = parseTests(record.tests);\n const assetId = str(record.asset_id) ?? null;\n // Only the result proves a save (its status, the video, or the tests); the\n // call's own title never does.\n if (record.status !== \"saved\" && !assetId && tests.length === 0) return { kind: \"unreadable\" };\n const title = str(record.title) ?? str(fallbackTitle) ?? \"\";\n\n return {\n kind: \"saved\",\n report: {\n assetId,\n url: safeHttpUrl(record.url),\n title: title || \"UI test recording\",\n summary: str(record.summary) ?? \"\",\n passed: count(record.passed) ?? tests.filter((test) => test.result === \"passed\").length,\n failed: count(record.failed) ?? tests.filter((test) => test.result === \"failed\").length,\n untested: tests.filter((test) => test.result === \"untested\").length,\n tests,\n sourceDurationS: seconds(record.source_duration_s),\n editedDurationS: seconds(record.edited_duration_s),\n },\n };\n}\n\n/** The failure a start or mark call answered, or `null`. */\nfunction callError(result: unknown): string | null {\n const record = resultRecord(result);\n if (record) {\n if (record.status === \"error\" || str(record.error)) {\n return str(record.error) ?? \"The call failed.\";\n }\n return null;\n }\n return toolFailure({ result })?.message ?? null;\n}\n\n/** Read a `test_recording_mark` call from its arguments and result. */\nexport function describeTestRecordingMark({\n args,\n result,\n}: {\n args: Record<string, unknown> | undefined;\n result: unknown;\n}): TestRecordingMark {\n const record = resultRecord(result);\n const rawKind = args?.kind ?? record?.kind;\n const kind = MARK_KINDS.find((candidate) => candidate === rawKind) ?? null;\n return {\n kind,\n text: str(args?.text) ?? \"\",\n elapsedS: seconds(record?.elapsed_s),\n error: callError(result),\n };\n}\n\n/** Read a `test_recording_start` call from its arguments and result. */\nexport function describeTestRecordingStart({\n args,\n result,\n}: {\n args: Record<string, unknown> | undefined;\n result: unknown;\n}): TestRecordingStart {\n const record = resultRecord(result);\n return {\n title: str(record?.title) ?? str(args?.title) ?? \"\",\n recordingId: str(record?.recording_id) ?? null,\n error: callError(result),\n };\n}\n\n/** A video position as a clock: `5.8` → \"0:05\", `62` → \"1:02\", `3725` → \"1:02:05\". */\nexport function formatRecordingClock(totalSeconds: number): string {\n const whole = Math.max(0, Math.floor(Number.isFinite(totalSeconds) ? totalSeconds : 0));\n const hours = Math.floor(whole / 3600);\n const minutes = Math.floor((whole % 3600) / 60);\n const secs = String(whole % 60).padStart(2, \"0\");\n return hours > 0 ? `${hours}:${String(minutes).padStart(2, \"0\")}:${secs}` : `${minutes}:${secs}`;\n}\n\n/** \"2 passed · 1 failed\", with \"· 1 untested\" when a test was never checked. */\nexport function formatTestRecordingCounts(\n report: Pick<TestRecordingReport, \"passed\" | \"failed\" | \"untested\">,\n): string {\n const parts = [`${report.passed} passed`, `${report.failed} failed`];\n if (report.untested > 0) parts.push(`${report.untested} untested`);\n return parts.join(\" · \");\n}\n"],"names":["toolFailure"],"mappings":"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"}
@@ -0,0 +1,82 @@
1
+ export declare const TEST_RECORDING_START_TOOL = "test_recording_start";
2
+ export declare const TEST_RECORDING_MARK_TOOL = "test_recording_mark";
3
+ export declare const TEST_RECORDING_STOP_TOOL = "test_recording_stop";
4
+ /** A test's outcome: any failed assertion fails it; one with no assertion was not checked. */
5
+ export type TestRecordingOutcome = "passed" | "failed" | "untested";
6
+ export interface TestRecordingAssertion {
7
+ /** What the agent actually observed. */
8
+ text: string;
9
+ result: "passed" | "failed";
10
+ /** Where the assertion was marked in the edited video, in seconds. */
11
+ editedTimeS: number | null;
12
+ }
13
+ export interface TestRecordingTest {
14
+ /** "It should …". */
15
+ name: string;
16
+ result: TestRecordingOutcome;
17
+ /** Where the test starts in the edited video, in seconds: the chapter. */
18
+ startTimeS: number | null;
19
+ assertions: TestRecordingAssertion[];
20
+ }
21
+ export interface TestRecordingReport {
22
+ /** The saved video asset. */
23
+ assetId: string | null;
24
+ /** The asset in Athena (an `http(s)` URL only). */
25
+ url: string | null;
26
+ title: string;
27
+ summary: string;
28
+ passed: number;
29
+ failed: number;
30
+ untested: number;
31
+ tests: TestRecordingTest[];
32
+ sourceDurationS: number | null;
33
+ editedDurationS: number | null;
34
+ }
35
+ export type TestRecordingStopResult = {
36
+ kind: "saved";
37
+ report: TestRecordingReport;
38
+ } | {
39
+ kind: "error";
40
+ message: string;
41
+ }
42
+ /** No result yet, or one that is not a report (partial JSON while it streams). */
43
+ | {
44
+ kind: "unreadable";
45
+ };
46
+ export type TestRecordingMarkKind = "test" | "pass" | "fail" | "note";
47
+ export interface TestRecordingMark {
48
+ /** `null` while the arguments are still streaming. */
49
+ kind: TestRecordingMarkKind | null;
50
+ text: string;
51
+ /** Seconds since the recording started, once the mark is in. */
52
+ elapsedS: number | null;
53
+ error: string | null;
54
+ }
55
+ export interface TestRecordingStart {
56
+ title: string;
57
+ recordingId: string | null;
58
+ error: string | null;
59
+ }
60
+ /** An `http(s)` URL as given, else `null` (never a `javascript:` or other scheme from a result). */
61
+ export declare function safeHttpUrl(value: unknown): string | null;
62
+ /**
63
+ * Read a `test_recording_stop` result. `fallbackTitle` is the call's own
64
+ * `title` argument, for a report that names none.
65
+ */
66
+ export declare function parseTestRecordingStopResult(result: unknown, { fallbackTitle }?: {
67
+ fallbackTitle?: string;
68
+ }): TestRecordingStopResult;
69
+ /** Read a `test_recording_mark` call from its arguments and result. */
70
+ export declare function describeTestRecordingMark({ args, result, }: {
71
+ args: Record<string, unknown> | undefined;
72
+ result: unknown;
73
+ }): TestRecordingMark;
74
+ /** Read a `test_recording_start` call from its arguments and result. */
75
+ export declare function describeTestRecordingStart({ args, result, }: {
76
+ args: Record<string, unknown> | undefined;
77
+ result: unknown;
78
+ }): TestRecordingStart;
79
+ /** A video position as a clock: `5.8` → "0:05", `62` → "1:02", `3725` → "1:02:05". */
80
+ export declare function formatRecordingClock(totalSeconds: number): string;
81
+ /** "2 passed · 1 failed", with "· 1 untested" when a test was never checked. */
82
+ export declare function formatTestRecordingCounts(report: Pick<TestRecordingReport, "passed" | "failed" | "untested">): string;
@@ -0,0 +1,175 @@
1
+ import { toolFailure } from "../agent-runtime/src/tool-result.js";
2
+ const TEST_RECORDING_START_TOOL = "test_recording_start";
3
+ const TEST_RECORDING_MARK_TOOL = "test_recording_mark";
4
+ const TEST_RECORDING_STOP_TOOL = "test_recording_stop";
5
+ const MARK_KINDS = ["test", "pass", "fail", "note"];
6
+ function str(value) {
7
+ return typeof value === "string" && value.trim() !== "" ? value.trim() : void 0;
8
+ }
9
+ function seconds(value) {
10
+ return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : null;
11
+ }
12
+ function count(value) {
13
+ return typeof value === "number" && Number.isInteger(value) && value >= 0 ? value : null;
14
+ }
15
+ function asRecord(value) {
16
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
17
+ }
18
+ function parseRecord(text) {
19
+ try {
20
+ return asRecord(JSON.parse(text));
21
+ } catch {
22
+ return null;
23
+ }
24
+ }
25
+ function resultRecord(result) {
26
+ const record = typeof result === "string" ? parseRecord(result) : asRecord(result);
27
+ if (record && typeof record.result === "string") return parseRecord(record.result) ?? record;
28
+ return record;
29
+ }
30
+ function safeHttpUrl(value) {
31
+ const raw = str(value);
32
+ if (!raw) return null;
33
+ try {
34
+ const url = new URL(raw);
35
+ return url.protocol === "https:" || url.protocol === "http:" ? raw : null;
36
+ } catch {
37
+ return null;
38
+ }
39
+ }
40
+ function assertionResult(value) {
41
+ if (value === "passed" || value === "pass") return "passed";
42
+ if (value === "failed" || value === "fail") return "failed";
43
+ return null;
44
+ }
45
+ function parseAssertions(value) {
46
+ if (!Array.isArray(value)) return [];
47
+ const assertions = [];
48
+ for (const raw of value) {
49
+ const record = asRecord(raw);
50
+ const result = assertionResult(record == null ? void 0 : record.result);
51
+ if (!record || !result) continue;
52
+ assertions.push({
53
+ text: str(record.text) ?? "",
54
+ result,
55
+ editedTimeS: seconds(record.edited_time_s)
56
+ });
57
+ }
58
+ return assertions;
59
+ }
60
+ function outcomeOf(assertions) {
61
+ if (assertions.some((assertion) => assertion.result === "failed")) return "failed";
62
+ return assertions.length > 0 ? "passed" : "untested";
63
+ }
64
+ function parseTests(value) {
65
+ if (!Array.isArray(value)) return [];
66
+ const tests = [];
67
+ for (const raw of value) {
68
+ const record = asRecord(raw);
69
+ const name = str(record == null ? void 0 : record.name);
70
+ if (!record || !name) continue;
71
+ const assertions = parseAssertions(record.assertions);
72
+ const declared = record.result;
73
+ tests.push({
74
+ name,
75
+ result: declared === "passed" || declared === "failed" || declared === "untested" ? declared : outcomeOf(assertions),
76
+ startTimeS: seconds(record.start_time_s),
77
+ assertions
78
+ });
79
+ }
80
+ return tests;
81
+ }
82
+ function parseTestRecordingStopResult(result, { fallbackTitle } = {}) {
83
+ if (result === void 0 || result === null || result === "") return { kind: "unreadable" };
84
+ const record = resultRecord(result);
85
+ if (!record) {
86
+ const failure = toolFailure({ result });
87
+ return failure ? { kind: "error", message: failure.message } : { kind: "unreadable" };
88
+ }
89
+ if (record.status === "error" || str(record.error)) {
90
+ return {
91
+ kind: "error",
92
+ message: str(record.error) ?? "The test recording could not be saved."
93
+ };
94
+ }
95
+ if (record.status !== void 0 && record.status !== "saved") return { kind: "unreadable" };
96
+ const tests = parseTests(record.tests);
97
+ const assetId = str(record.asset_id) ?? null;
98
+ if (record.status !== "saved" && !assetId && tests.length === 0) return { kind: "unreadable" };
99
+ const title = str(record.title) ?? str(fallbackTitle) ?? "";
100
+ return {
101
+ kind: "saved",
102
+ report: {
103
+ assetId,
104
+ url: safeHttpUrl(record.url),
105
+ title: title || "UI test recording",
106
+ summary: str(record.summary) ?? "",
107
+ passed: count(record.passed) ?? tests.filter((test) => test.result === "passed").length,
108
+ failed: count(record.failed) ?? tests.filter((test) => test.result === "failed").length,
109
+ untested: tests.filter((test) => test.result === "untested").length,
110
+ tests,
111
+ sourceDurationS: seconds(record.source_duration_s),
112
+ editedDurationS: seconds(record.edited_duration_s)
113
+ }
114
+ };
115
+ }
116
+ function callError(result) {
117
+ var _a;
118
+ const record = resultRecord(result);
119
+ if (record) {
120
+ if (record.status === "error" || str(record.error)) {
121
+ return str(record.error) ?? "The call failed.";
122
+ }
123
+ return null;
124
+ }
125
+ return ((_a = toolFailure({ result })) == null ? void 0 : _a.message) ?? null;
126
+ }
127
+ function describeTestRecordingMark({
128
+ args,
129
+ result
130
+ }) {
131
+ const record = resultRecord(result);
132
+ const rawKind = (args == null ? void 0 : args.kind) ?? (record == null ? void 0 : record.kind);
133
+ const kind = MARK_KINDS.find((candidate) => candidate === rawKind) ?? null;
134
+ return {
135
+ kind,
136
+ text: str(args == null ? void 0 : args.text) ?? "",
137
+ elapsedS: seconds(record == null ? void 0 : record.elapsed_s),
138
+ error: callError(result)
139
+ };
140
+ }
141
+ function describeTestRecordingStart({
142
+ args,
143
+ result
144
+ }) {
145
+ const record = resultRecord(result);
146
+ return {
147
+ title: str(record == null ? void 0 : record.title) ?? str(args == null ? void 0 : args.title) ?? "",
148
+ recordingId: str(record == null ? void 0 : record.recording_id) ?? null,
149
+ error: callError(result)
150
+ };
151
+ }
152
+ function formatRecordingClock(totalSeconds) {
153
+ const whole = Math.max(0, Math.floor(Number.isFinite(totalSeconds) ? totalSeconds : 0));
154
+ const hours = Math.floor(whole / 3600);
155
+ const minutes = Math.floor(whole % 3600 / 60);
156
+ const secs = String(whole % 60).padStart(2, "0");
157
+ return hours > 0 ? `${hours}:${String(minutes).padStart(2, "0")}:${secs}` : `${minutes}:${secs}`;
158
+ }
159
+ function formatTestRecordingCounts(report) {
160
+ const parts = [`${report.passed} passed`, `${report.failed} failed`];
161
+ if (report.untested > 0) parts.push(`${report.untested} untested`);
162
+ return parts.join(" · ");
163
+ }
164
+ export {
165
+ TEST_RECORDING_MARK_TOOL,
166
+ TEST_RECORDING_START_TOOL,
167
+ TEST_RECORDING_STOP_TOOL,
168
+ describeTestRecordingMark,
169
+ describeTestRecordingStart,
170
+ formatRecordingClock,
171
+ formatTestRecordingCounts,
172
+ parseTestRecordingStopResult,
173
+ safeHttpUrl
174
+ };
175
+ //# sourceMappingURL=test-recording-report.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"test-recording-report.js","sources":["../../src/tools/test-recording-report.ts"],"sourcesContent":["/**\n * The UI Testing toolkit's three tools as the SDK's cards and step rows read\n * them: `test_recording_start` (a recorder starts on the agent's computer),\n * `test_recording_mark` (a test begins, an assertion passes or fails, a note)\n * and `test_recording_stop` (the edited video is saved as an asset, with the\n * test report). Each tool message's content is its result JSON as a string;\n * a failed call answers `{ \"status\": \"error\", \"error\": \"…\" }`.\n *\n * Every reader here is total: a result that is missing, still streaming,\n * truncated or shaped differently reads as \"nothing to show yet\" or as the\n * parts it does carry, never as a throw. Pure and React-free.\n */\nimport { toolFailure } from \"@athenaintel/agent-runtime\";\n\nexport const TEST_RECORDING_START_TOOL = \"test_recording_start\";\nexport const TEST_RECORDING_MARK_TOOL = \"test_recording_mark\";\nexport const TEST_RECORDING_STOP_TOOL = \"test_recording_stop\";\n\n/** A test's outcome: any failed assertion fails it; one with no assertion was not checked. */\nexport type TestRecordingOutcome = \"passed\" | \"failed\" | \"untested\";\n\nexport interface TestRecordingAssertion {\n /** What the agent actually observed. */\n text: string;\n result: \"passed\" | \"failed\";\n /** Where the assertion was marked in the edited video, in seconds. */\n editedTimeS: number | null;\n}\n\nexport interface TestRecordingTest {\n /** \"It should …\". */\n name: string;\n result: TestRecordingOutcome;\n /** Where the test starts in the edited video, in seconds: the chapter. */\n startTimeS: number | null;\n assertions: TestRecordingAssertion[];\n}\n\nexport interface TestRecordingReport {\n /** The saved video asset. */\n assetId: string | null;\n /** The asset in Athena (an `http(s)` URL only). */\n url: string | null;\n title: string;\n summary: string;\n passed: number;\n failed: number;\n untested: number;\n tests: TestRecordingTest[];\n sourceDurationS: number | null;\n editedDurationS: number | null;\n}\n\nexport type TestRecordingStopResult =\n | { kind: \"saved\"; report: TestRecordingReport }\n | { kind: \"error\"; message: string }\n /** No result yet, or one that is not a report (partial JSON while it streams). */\n | { kind: \"unreadable\" };\n\nexport type TestRecordingMarkKind = \"test\" | \"pass\" | \"fail\" | \"note\";\n\nexport interface TestRecordingMark {\n /** `null` while the arguments are still streaming. */\n kind: TestRecordingMarkKind | null;\n text: string;\n /** Seconds since the recording started, once the mark is in. */\n elapsedS: number | null;\n error: string | null;\n}\n\nexport interface TestRecordingStart {\n title: string;\n recordingId: string | null;\n error: string | null;\n}\n\nconst MARK_KINDS: readonly TestRecordingMarkKind[] = [\"test\", \"pass\", \"fail\", \"note\"];\n\nfunction str(value: unknown): string | undefined {\n return typeof value === \"string\" && value.trim() !== \"\" ? value.trim() : undefined;\n}\n\nfunction seconds(value: unknown): number | null {\n return typeof value === \"number\" && Number.isFinite(value) && value >= 0 ? value : null;\n}\n\nfunction count(value: unknown): number | null {\n return typeof value === \"number\" && Number.isInteger(value) && value >= 0 ? value : null;\n}\n\nfunction asRecord(value: unknown): Record<string, unknown> | null {\n return typeof value === \"object\" && value !== null && !Array.isArray(value)\n ? (value as Record<string, unknown>)\n : null;\n}\n\nfunction parseRecord(text: string): Record<string, unknown> | null {\n try {\n return asRecord(JSON.parse(text));\n } catch {\n return null;\n }\n}\n\n/**\n * A tool result as a record: the JSON string the tool message carries, an\n * object, or either wrapped in `{ result }` (as `normalizeResult` reads it).\n * `null` for anything else, a JSON string cut off mid-stream included.\n */\nfunction resultRecord(result: unknown): Record<string, unknown> | null {\n const record = typeof result === \"string\" ? parseRecord(result) : asRecord(result);\n if (record && typeof record.result === \"string\") return parseRecord(record.result) ?? record;\n return record;\n}\n\n/** An `http(s)` URL as given, else `null` (never a `javascript:` or other scheme from a result). */\nexport function safeHttpUrl(value: unknown): string | null {\n const raw = str(value);\n if (!raw) return null;\n try {\n const url = new URL(raw);\n return url.protocol === \"https:\" || url.protocol === \"http:\" ? raw : null;\n } catch {\n return null;\n }\n}\n\nfunction assertionResult(value: unknown): \"passed\" | \"failed\" | null {\n if (value === \"passed\" || value === \"pass\") return \"passed\";\n if (value === \"failed\" || value === \"fail\") return \"failed\";\n return null;\n}\n\nfunction parseAssertions(value: unknown): TestRecordingAssertion[] {\n if (!Array.isArray(value)) return [];\n const assertions: TestRecordingAssertion[] = [];\n for (const raw of value) {\n const record = asRecord(raw);\n const result = assertionResult(record?.result);\n if (!record || !result) continue;\n assertions.push({\n text: str(record.text) ?? \"\",\n result,\n editedTimeS: seconds(record.edited_time_s),\n });\n }\n return assertions;\n}\n\n/** The contract's rule, for a test whose own `result` is missing or unknown. */\nfunction outcomeOf(assertions: readonly TestRecordingAssertion[]): TestRecordingOutcome {\n if (assertions.some((assertion) => assertion.result === \"failed\")) return \"failed\";\n return assertions.length > 0 ? \"passed\" : \"untested\";\n}\n\nfunction parseTests(value: unknown): TestRecordingTest[] {\n if (!Array.isArray(value)) return [];\n const tests: TestRecordingTest[] = [];\n for (const raw of value) {\n const record = asRecord(raw);\n const name = str(record?.name);\n if (!record || !name) continue;\n const assertions = parseAssertions(record.assertions);\n const declared = record.result;\n tests.push({\n name,\n result:\n declared === \"passed\" || declared === \"failed\" || declared === \"untested\"\n ? declared\n : outcomeOf(assertions),\n startTimeS: seconds(record.start_time_s),\n assertions,\n });\n }\n return tests;\n}\n\n/**\n * Read a `test_recording_stop` result. `fallbackTitle` is the call's own\n * `title` argument, for a report that names none.\n */\nexport function parseTestRecordingStopResult(\n result: unknown,\n { fallbackTitle }: { fallbackTitle?: string } = {},\n): TestRecordingStopResult {\n if (result === undefined || result === null || result === \"\") return { kind: \"unreadable\" };\n const record = resultRecord(result);\n if (!record) {\n const failure = toolFailure({ result });\n return failure ? { kind: \"error\", message: failure.message } : { kind: \"unreadable\" };\n }\n if (record.status === \"error\" || str(record.error)) {\n return {\n kind: \"error\",\n message: str(record.error) ?? \"The test recording could not be saved.\",\n };\n }\n if (record.status !== undefined && record.status !== \"saved\") return { kind: \"unreadable\" };\n\n const tests = parseTests(record.tests);\n const assetId = str(record.asset_id) ?? null;\n // Only the result proves a save (its status, the video, or the tests); the\n // call's own title never does.\n if (record.status !== \"saved\" && !assetId && tests.length === 0) return { kind: \"unreadable\" };\n const title = str(record.title) ?? str(fallbackTitle) ?? \"\";\n\n return {\n kind: \"saved\",\n report: {\n assetId,\n url: safeHttpUrl(record.url),\n title: title || \"UI test recording\",\n summary: str(record.summary) ?? \"\",\n passed: count(record.passed) ?? tests.filter((test) => test.result === \"passed\").length,\n failed: count(record.failed) ?? tests.filter((test) => test.result === \"failed\").length,\n untested: tests.filter((test) => test.result === \"untested\").length,\n tests,\n sourceDurationS: seconds(record.source_duration_s),\n editedDurationS: seconds(record.edited_duration_s),\n },\n };\n}\n\n/** The failure a start or mark call answered, or `null`. */\nfunction callError(result: unknown): string | null {\n const record = resultRecord(result);\n if (record) {\n if (record.status === \"error\" || str(record.error)) {\n return str(record.error) ?? \"The call failed.\";\n }\n return null;\n }\n return toolFailure({ result })?.message ?? null;\n}\n\n/** Read a `test_recording_mark` call from its arguments and result. */\nexport function describeTestRecordingMark({\n args,\n result,\n}: {\n args: Record<string, unknown> | undefined;\n result: unknown;\n}): TestRecordingMark {\n const record = resultRecord(result);\n const rawKind = args?.kind ?? record?.kind;\n const kind = MARK_KINDS.find((candidate) => candidate === rawKind) ?? null;\n return {\n kind,\n text: str(args?.text) ?? \"\",\n elapsedS: seconds(record?.elapsed_s),\n error: callError(result),\n };\n}\n\n/** Read a `test_recording_start` call from its arguments and result. */\nexport function describeTestRecordingStart({\n args,\n result,\n}: {\n args: Record<string, unknown> | undefined;\n result: unknown;\n}): TestRecordingStart {\n const record = resultRecord(result);\n return {\n title: str(record?.title) ?? str(args?.title) ?? \"\",\n recordingId: str(record?.recording_id) ?? null,\n error: callError(result),\n };\n}\n\n/** A video position as a clock: `5.8` → \"0:05\", `62` → \"1:02\", `3725` → \"1:02:05\". */\nexport function formatRecordingClock(totalSeconds: number): string {\n const whole = Math.max(0, Math.floor(Number.isFinite(totalSeconds) ? totalSeconds : 0));\n const hours = Math.floor(whole / 3600);\n const minutes = Math.floor((whole % 3600) / 60);\n const secs = String(whole % 60).padStart(2, \"0\");\n return hours > 0 ? `${hours}:${String(minutes).padStart(2, \"0\")}:${secs}` : `${minutes}:${secs}`;\n}\n\n/** \"2 passed · 1 failed\", with \"· 1 untested\" when a test was never checked. */\nexport function formatTestRecordingCounts(\n report: Pick<TestRecordingReport, \"passed\" | \"failed\" | \"untested\">,\n): string {\n const parts = [`${report.passed} passed`, `${report.failed} failed`];\n if (report.untested > 0) parts.push(`${report.untested} untested`);\n return parts.join(\" · \");\n}\n"],"names":[],"mappings":"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"}