@aiscene/core 1.1.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 (299) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +9 -0
  3. package/dist/es/agent/agent.mjs +753 -0
  4. package/dist/es/agent/agent.mjs.map +1 -0
  5. package/dist/es/agent/common.mjs +0 -0
  6. package/dist/es/agent/execution-session.mjs +41 -0
  7. package/dist/es/agent/execution-session.mjs.map +1 -0
  8. package/dist/es/agent/index.mjs +6 -0
  9. package/dist/es/agent/task-builder.mjs +332 -0
  10. package/dist/es/agent/task-builder.mjs.map +1 -0
  11. package/dist/es/agent/task-cache.mjs +214 -0
  12. package/dist/es/agent/task-cache.mjs.map +1 -0
  13. package/dist/es/agent/tasks.mjs +423 -0
  14. package/dist/es/agent/tasks.mjs.map +1 -0
  15. package/dist/es/agent/ui-utils.mjs +91 -0
  16. package/dist/es/agent/ui-utils.mjs.map +1 -0
  17. package/dist/es/agent/utils.mjs +169 -0
  18. package/dist/es/agent/utils.mjs.map +1 -0
  19. package/dist/es/ai-model/auto-glm/actions.mjs +239 -0
  20. package/dist/es/ai-model/auto-glm/actions.mjs.map +1 -0
  21. package/dist/es/ai-model/auto-glm/index.mjs +6 -0
  22. package/dist/es/ai-model/auto-glm/parser.mjs +239 -0
  23. package/dist/es/ai-model/auto-glm/parser.mjs.map +1 -0
  24. package/dist/es/ai-model/auto-glm/planning.mjs +71 -0
  25. package/dist/es/ai-model/auto-glm/planning.mjs.map +1 -0
  26. package/dist/es/ai-model/auto-glm/prompt.mjs +222 -0
  27. package/dist/es/ai-model/auto-glm/prompt.mjs.map +1 -0
  28. package/dist/es/ai-model/auto-glm/util.mjs +9 -0
  29. package/dist/es/ai-model/auto-glm/util.mjs.map +1 -0
  30. package/dist/es/ai-model/connectivity.mjs +138 -0
  31. package/dist/es/ai-model/connectivity.mjs.map +1 -0
  32. package/dist/es/ai-model/conversation-history.mjs +195 -0
  33. package/dist/es/ai-model/conversation-history.mjs.map +1 -0
  34. package/dist/es/ai-model/index.mjs +12 -0
  35. package/dist/es/ai-model/inspect.mjs +397 -0
  36. package/dist/es/ai-model/inspect.mjs.map +1 -0
  37. package/dist/es/ai-model/llm-planning.mjs +233 -0
  38. package/dist/es/ai-model/llm-planning.mjs.map +1 -0
  39. package/dist/es/ai-model/prompt/common.mjs +7 -0
  40. package/dist/es/ai-model/prompt/common.mjs.map +1 -0
  41. package/dist/es/ai-model/prompt/describe.mjs +66 -0
  42. package/dist/es/ai-model/prompt/describe.mjs.map +1 -0
  43. package/dist/es/ai-model/prompt/extraction.mjs +131 -0
  44. package/dist/es/ai-model/prompt/extraction.mjs.map +1 -0
  45. package/dist/es/ai-model/prompt/llm-locator.mjs +51 -0
  46. package/dist/es/ai-model/prompt/llm-locator.mjs.map +1 -0
  47. package/dist/es/ai-model/prompt/llm-planning.mjs +568 -0
  48. package/dist/es/ai-model/prompt/llm-planning.mjs.map +1 -0
  49. package/dist/es/ai-model/prompt/llm-section-locator.mjs +44 -0
  50. package/dist/es/ai-model/prompt/llm-section-locator.mjs.map +1 -0
  51. package/dist/es/ai-model/prompt/order-sensitive-judge.mjs +35 -0
  52. package/dist/es/ai-model/prompt/order-sensitive-judge.mjs.map +1 -0
  53. package/dist/es/ai-model/prompt/playwright-generator.mjs +117 -0
  54. package/dist/es/ai-model/prompt/playwright-generator.mjs.map +1 -0
  55. package/dist/es/ai-model/prompt/ui-tars-planning.mjs +36 -0
  56. package/dist/es/ai-model/prompt/ui-tars-planning.mjs.map +1 -0
  57. package/dist/es/ai-model/prompt/util.mjs +59 -0
  58. package/dist/es/ai-model/prompt/util.mjs.map +1 -0
  59. package/dist/es/ai-model/prompt/yaml-generator.mjs +203 -0
  60. package/dist/es/ai-model/prompt/yaml-generator.mjs.map +1 -0
  61. package/dist/es/ai-model/service-caller/codex-app-server.mjs +575 -0
  62. package/dist/es/ai-model/service-caller/codex-app-server.mjs.map +1 -0
  63. package/dist/es/ai-model/service-caller/image-detail.mjs +6 -0
  64. package/dist/es/ai-model/service-caller/image-detail.mjs.map +1 -0
  65. package/dist/es/ai-model/service-caller/index.mjs +475 -0
  66. package/dist/es/ai-model/service-caller/index.mjs.map +1 -0
  67. package/dist/es/ai-model/ui-tars-planning.mjs +249 -0
  68. package/dist/es/ai-model/ui-tars-planning.mjs.map +1 -0
  69. package/dist/es/common.mjs +371 -0
  70. package/dist/es/common.mjs.map +1 -0
  71. package/dist/es/device/device-options.mjs +0 -0
  72. package/dist/es/device/index.mjs +341 -0
  73. package/dist/es/device/index.mjs.map +1 -0
  74. package/dist/es/dump/html-utils.mjs +292 -0
  75. package/dist/es/dump/html-utils.mjs.map +1 -0
  76. package/dist/es/dump/index.mjs +3 -0
  77. package/dist/es/dump/screenshot-restoration.mjs +32 -0
  78. package/dist/es/dump/screenshot-restoration.mjs.map +1 -0
  79. package/dist/es/dump/screenshot-store.mjs +126 -0
  80. package/dist/es/dump/screenshot-store.mjs.map +1 -0
  81. package/dist/es/index.mjs +19 -0
  82. package/dist/es/index.mjs.map +1 -0
  83. package/dist/es/report-cli.mjs +151 -0
  84. package/dist/es/report-cli.mjs.map +1 -0
  85. package/dist/es/report-generator.mjs +205 -0
  86. package/dist/es/report-generator.mjs.map +1 -0
  87. package/dist/es/report-markdown.mjs +218 -0
  88. package/dist/es/report-markdown.mjs.map +1 -0
  89. package/dist/es/report.mjs +270 -0
  90. package/dist/es/report.mjs.map +1 -0
  91. package/dist/es/screenshot-item.mjs +122 -0
  92. package/dist/es/screenshot-item.mjs.map +1 -0
  93. package/dist/es/service/index.mjs +274 -0
  94. package/dist/es/service/index.mjs.map +1 -0
  95. package/dist/es/service/utils.mjs +15 -0
  96. package/dist/es/service/utils.mjs.map +1 -0
  97. package/dist/es/skill/index.mjs +38 -0
  98. package/dist/es/skill/index.mjs.map +1 -0
  99. package/dist/es/task-runner.mjs +263 -0
  100. package/dist/es/task-runner.mjs.map +1 -0
  101. package/dist/es/task-timing.mjs +12 -0
  102. package/dist/es/task-timing.mjs.map +1 -0
  103. package/dist/es/tree.mjs +13 -0
  104. package/dist/es/tree.mjs.map +1 -0
  105. package/dist/es/types.mjs +204 -0
  106. package/dist/es/types.mjs.map +1 -0
  107. package/dist/es/utils.mjs +234 -0
  108. package/dist/es/utils.mjs.map +1 -0
  109. package/dist/es/yaml/builder.mjs +13 -0
  110. package/dist/es/yaml/builder.mjs.map +1 -0
  111. package/dist/es/yaml/index.mjs +4 -0
  112. package/dist/es/yaml/player.mjs +442 -0
  113. package/dist/es/yaml/player.mjs.map +1 -0
  114. package/dist/es/yaml/utils.mjs +102 -0
  115. package/dist/es/yaml/utils.mjs.map +1 -0
  116. package/dist/es/yaml.mjs +0 -0
  117. package/dist/lib/agent/agent.js +801 -0
  118. package/dist/lib/agent/agent.js.map +1 -0
  119. package/dist/lib/agent/common.js +5 -0
  120. package/dist/lib/agent/execution-session.js +75 -0
  121. package/dist/lib/agent/execution-session.js.map +1 -0
  122. package/dist/lib/agent/index.js +78 -0
  123. package/dist/lib/agent/index.js.map +1 -0
  124. package/dist/lib/agent/task-builder.js +369 -0
  125. package/dist/lib/agent/task-builder.js.map +1 -0
  126. package/dist/lib/agent/task-cache.js +266 -0
  127. package/dist/lib/agent/task-cache.js.map +1 -0
  128. package/dist/lib/agent/tasks.js +466 -0
  129. package/dist/lib/agent/tasks.js.map +1 -0
  130. package/dist/lib/agent/ui-utils.js +143 -0
  131. package/dist/lib/agent/ui-utils.js.map +1 -0
  132. package/dist/lib/agent/utils.js +240 -0
  133. package/dist/lib/agent/utils.js.map +1 -0
  134. package/dist/lib/ai-model/auto-glm/actions.js +273 -0
  135. package/dist/lib/ai-model/auto-glm/actions.js.map +1 -0
  136. package/dist/lib/ai-model/auto-glm/index.js +66 -0
  137. package/dist/lib/ai-model/auto-glm/index.js.map +1 -0
  138. package/dist/lib/ai-model/auto-glm/parser.js +282 -0
  139. package/dist/lib/ai-model/auto-glm/parser.js.map +1 -0
  140. package/dist/lib/ai-model/auto-glm/planning.js +105 -0
  141. package/dist/lib/ai-model/auto-glm/planning.js.map +1 -0
  142. package/dist/lib/ai-model/auto-glm/prompt.js +259 -0
  143. package/dist/lib/ai-model/auto-glm/prompt.js.map +1 -0
  144. package/dist/lib/ai-model/auto-glm/util.js +46 -0
  145. package/dist/lib/ai-model/auto-glm/util.js.map +1 -0
  146. package/dist/lib/ai-model/connectivity.js +182 -0
  147. package/dist/lib/ai-model/connectivity.js.map +1 -0
  148. package/dist/lib/ai-model/conversation-history.js +229 -0
  149. package/dist/lib/ai-model/conversation-history.js.map +1 -0
  150. package/dist/lib/ai-model/index.js +129 -0
  151. package/dist/lib/ai-model/index.js.map +1 -0
  152. package/dist/lib/ai-model/inspect.js +443 -0
  153. package/dist/lib/ai-model/inspect.js.map +1 -0
  154. package/dist/lib/ai-model/llm-planning.js +270 -0
  155. package/dist/lib/ai-model/llm-planning.js.map +1 -0
  156. package/dist/lib/ai-model/prompt/common.js +41 -0
  157. package/dist/lib/ai-model/prompt/common.js.map +1 -0
  158. package/dist/lib/ai-model/prompt/describe.js +100 -0
  159. package/dist/lib/ai-model/prompt/describe.js.map +1 -0
  160. package/dist/lib/ai-model/prompt/extraction.js +171 -0
  161. package/dist/lib/ai-model/prompt/extraction.js.map +1 -0
  162. package/dist/lib/ai-model/prompt/llm-locator.js +88 -0
  163. package/dist/lib/ai-model/prompt/llm-locator.js.map +1 -0
  164. package/dist/lib/ai-model/prompt/llm-planning.js +605 -0
  165. package/dist/lib/ai-model/prompt/llm-planning.js.map +1 -0
  166. package/dist/lib/ai-model/prompt/llm-section-locator.js +81 -0
  167. package/dist/lib/ai-model/prompt/llm-section-locator.js.map +1 -0
  168. package/dist/lib/ai-model/prompt/order-sensitive-judge.js +72 -0
  169. package/dist/lib/ai-model/prompt/order-sensitive-judge.js.map +1 -0
  170. package/dist/lib/ai-model/prompt/playwright-generator.js +178 -0
  171. package/dist/lib/ai-model/prompt/playwright-generator.js.map +1 -0
  172. package/dist/lib/ai-model/prompt/ui-tars-planning.js +73 -0
  173. package/dist/lib/ai-model/prompt/ui-tars-planning.js.map +1 -0
  174. package/dist/lib/ai-model/prompt/util.js +105 -0
  175. package/dist/lib/ai-model/prompt/util.js.map +1 -0
  176. package/dist/lib/ai-model/prompt/yaml-generator.js +264 -0
  177. package/dist/lib/ai-model/prompt/yaml-generator.js.map +1 -0
  178. package/dist/lib/ai-model/service-caller/codex-app-server.js +624 -0
  179. package/dist/lib/ai-model/service-caller/codex-app-server.js.map +1 -0
  180. package/dist/lib/ai-model/service-caller/image-detail.js +40 -0
  181. package/dist/lib/ai-model/service-caller/image-detail.js.map +1 -0
  182. package/dist/lib/ai-model/service-caller/index.js +540 -0
  183. package/dist/lib/ai-model/service-caller/index.js.map +1 -0
  184. package/dist/lib/ai-model/ui-tars-planning.js +283 -0
  185. package/dist/lib/ai-model/ui-tars-planning.js.map +1 -0
  186. package/dist/lib/common.js +480 -0
  187. package/dist/lib/common.js.map +1 -0
  188. package/dist/lib/device/device-options.js +20 -0
  189. package/dist/lib/device/device-options.js.map +1 -0
  190. package/dist/lib/device/index.js +468 -0
  191. package/dist/lib/device/index.js.map +1 -0
  192. package/dist/lib/dump/html-utils.js +368 -0
  193. package/dist/lib/dump/html-utils.js.map +1 -0
  194. package/dist/lib/dump/index.js +60 -0
  195. package/dist/lib/dump/index.js.map +1 -0
  196. package/dist/lib/dump/screenshot-restoration.js +66 -0
  197. package/dist/lib/dump/screenshot-restoration.js.map +1 -0
  198. package/dist/lib/dump/screenshot-store.js +166 -0
  199. package/dist/lib/dump/screenshot-store.js.map +1 -0
  200. package/dist/lib/index.js +186 -0
  201. package/dist/lib/index.js.map +1 -0
  202. package/dist/lib/report-cli.js +191 -0
  203. package/dist/lib/report-cli.js.map +1 -0
  204. package/dist/lib/report-generator.js +246 -0
  205. package/dist/lib/report-generator.js.map +1 -0
  206. package/dist/lib/report-markdown.js +255 -0
  207. package/dist/lib/report-markdown.js.map +1 -0
  208. package/dist/lib/report.js +316 -0
  209. package/dist/lib/report.js.map +1 -0
  210. package/dist/lib/screenshot-item.js +156 -0
  211. package/dist/lib/screenshot-item.js.map +1 -0
  212. package/dist/lib/service/index.js +308 -0
  213. package/dist/lib/service/index.js.map +1 -0
  214. package/dist/lib/service/utils.js +49 -0
  215. package/dist/lib/service/utils.js.map +1 -0
  216. package/dist/lib/skill/index.js +72 -0
  217. package/dist/lib/skill/index.js.map +1 -0
  218. package/dist/lib/task-runner.js +300 -0
  219. package/dist/lib/task-runner.js.map +1 -0
  220. package/dist/lib/task-timing.js +46 -0
  221. package/dist/lib/task-timing.js.map +1 -0
  222. package/dist/lib/tree.js +53 -0
  223. package/dist/lib/tree.js.map +1 -0
  224. package/dist/lib/types.js +300 -0
  225. package/dist/lib/types.js.map +1 -0
  226. package/dist/lib/utils.js +316 -0
  227. package/dist/lib/utils.js.map +1 -0
  228. package/dist/lib/yaml/builder.js +57 -0
  229. package/dist/lib/yaml/builder.js.map +1 -0
  230. package/dist/lib/yaml/index.js +81 -0
  231. package/dist/lib/yaml/index.js.map +1 -0
  232. package/dist/lib/yaml/player.js +476 -0
  233. package/dist/lib/yaml/player.js.map +1 -0
  234. package/dist/lib/yaml/utils.js +155 -0
  235. package/dist/lib/yaml/utils.js.map +1 -0
  236. package/dist/lib/yaml.js +20 -0
  237. package/dist/lib/yaml.js.map +1 -0
  238. package/dist/types/agent/agent.d.ts +216 -0
  239. package/dist/types/agent/common.d.ts +0 -0
  240. package/dist/types/agent/execution-session.d.ts +36 -0
  241. package/dist/types/agent/index.d.ts +9 -0
  242. package/dist/types/agent/task-builder.d.ts +34 -0
  243. package/dist/types/agent/task-cache.d.ts +49 -0
  244. package/dist/types/agent/tasks.d.ts +69 -0
  245. package/dist/types/agent/ui-utils.d.ts +14 -0
  246. package/dist/types/agent/utils.d.ts +25 -0
  247. package/dist/types/ai-model/auto-glm/actions.d.ts +78 -0
  248. package/dist/types/ai-model/auto-glm/index.d.ts +6 -0
  249. package/dist/types/ai-model/auto-glm/parser.d.ts +18 -0
  250. package/dist/types/ai-model/auto-glm/planning.d.ts +12 -0
  251. package/dist/types/ai-model/auto-glm/prompt.d.ts +27 -0
  252. package/dist/types/ai-model/auto-glm/util.d.ts +13 -0
  253. package/dist/types/ai-model/connectivity.d.ts +20 -0
  254. package/dist/types/ai-model/conversation-history.d.ts +105 -0
  255. package/dist/types/ai-model/index.d.ts +16 -0
  256. package/dist/types/ai-model/inspect.d.ts +67 -0
  257. package/dist/types/ai-model/llm-planning.d.ts +19 -0
  258. package/dist/types/ai-model/prompt/common.d.ts +2 -0
  259. package/dist/types/ai-model/prompt/describe.d.ts +1 -0
  260. package/dist/types/ai-model/prompt/extraction.d.ts +7 -0
  261. package/dist/types/ai-model/prompt/llm-locator.d.ts +3 -0
  262. package/dist/types/ai-model/prompt/llm-planning.d.ts +10 -0
  263. package/dist/types/ai-model/prompt/llm-section-locator.d.ts +3 -0
  264. package/dist/types/ai-model/prompt/order-sensitive-judge.d.ts +2 -0
  265. package/dist/types/ai-model/prompt/playwright-generator.d.ts +26 -0
  266. package/dist/types/ai-model/prompt/ui-tars-planning.d.ts +2 -0
  267. package/dist/types/ai-model/prompt/util.d.ts +33 -0
  268. package/dist/types/ai-model/prompt/yaml-generator.d.ts +102 -0
  269. package/dist/types/ai-model/service-caller/codex-app-server.d.ts +42 -0
  270. package/dist/types/ai-model/service-caller/image-detail.d.ts +2 -0
  271. package/dist/types/ai-model/service-caller/index.d.ts +49 -0
  272. package/dist/types/ai-model/ui-tars-planning.d.ts +72 -0
  273. package/dist/types/common.d.ts +288 -0
  274. package/dist/types/device/device-options.d.ts +145 -0
  275. package/dist/types/device/index.d.ts +2528 -0
  276. package/dist/types/dump/html-utils.d.ts +75 -0
  277. package/dist/types/dump/index.d.ts +5 -0
  278. package/dist/types/dump/screenshot-restoration.d.ts +8 -0
  279. package/dist/types/dump/screenshot-store.d.ts +49 -0
  280. package/dist/types/index.d.ts +21 -0
  281. package/dist/types/report-cli.d.ts +36 -0
  282. package/dist/types/report-generator.d.ts +81 -0
  283. package/dist/types/report-markdown.d.ts +24 -0
  284. package/dist/types/report.d.ts +52 -0
  285. package/dist/types/screenshot-item.d.ts +67 -0
  286. package/dist/types/service/index.d.ts +24 -0
  287. package/dist/types/service/utils.d.ts +2 -0
  288. package/dist/types/skill/index.d.ts +25 -0
  289. package/dist/types/task-runner.d.ts +50 -0
  290. package/dist/types/task-timing.d.ts +8 -0
  291. package/dist/types/tree.d.ts +4 -0
  292. package/dist/types/types.d.ts +681 -0
  293. package/dist/types/utils.d.ts +45 -0
  294. package/dist/types/yaml/builder.d.ts +2 -0
  295. package/dist/types/yaml/index.d.ts +4 -0
  296. package/dist/types/yaml/player.d.ts +34 -0
  297. package/dist/types/yaml/utils.d.ts +9 -0
  298. package/dist/types/yaml.d.ts +215 -0
  299. package/package.json +111 -0
@@ -0,0 +1,151 @@
1
+ import { copyFileSync, existsSync, mkdirSync, statSync, writeFileSync } from "node:fs";
2
+ import { join, resolve } from "node:path";
3
+ import { z } from "zod";
4
+ import { resolveScreenshotSource } from "./dump/screenshot-store.mjs";
5
+ import { collectDedupedExecutions, splitReportHtmlByExecution } from "./report.mjs";
6
+ import { reportToMarkdown } from "./report-markdown.mjs";
7
+ import { ReportActionDump } from "./types.mjs";
8
+ function writeAttachmentFromReport(attachment, opts) {
9
+ const { suggestedFileName, id, mimeType } = attachment;
10
+ if (opts.writtenFiles.has(suggestedFileName)) return;
11
+ const absolutePath = join(opts.screenshotsDir, suggestedFileName);
12
+ const outputRelativePath = `./screenshots/${suggestedFileName}`;
13
+ const sourceRef = attachment.filePath !== outputRelativePath ? {
14
+ type: 'midscene_screenshot_ref',
15
+ id,
16
+ capturedAt: 0,
17
+ mimeType: mimeType || 'image/png',
18
+ storage: 'file',
19
+ path: attachment.filePath
20
+ } : null;
21
+ const resolved = resolveScreenshotSource(sourceRef, {
22
+ reportPath: opts.htmlPath,
23
+ fallbackId: id,
24
+ fallbackMimeType: mimeType || 'image/png'
25
+ });
26
+ if ('data-uri' === resolved.type) {
27
+ const rawBase64 = resolved.dataUri.replace(/^data:image\/[a-zA-Z+]+;base64,/, '');
28
+ writeFileSync(absolutePath, Buffer.from(rawBase64, 'base64'));
29
+ opts.writtenFiles.add(suggestedFileName);
30
+ return;
31
+ }
32
+ if (!existsSync(resolved.filePath)) throw new Error(`Cannot resolve screenshot "${id}" for markdown attachment from ${opts.htmlPath}`);
33
+ copyFileSync(resolved.filePath, absolutePath);
34
+ opts.writtenFiles.add(suggestedFileName);
35
+ }
36
+ async function markdownFromReport(htmlPath, outputDir) {
37
+ const screenshotsDir = join(outputDir, 'screenshots');
38
+ mkdirSync(outputDir, {
39
+ recursive: true
40
+ });
41
+ mkdirSync(screenshotsDir, {
42
+ recursive: true
43
+ });
44
+ const { baseDump, executions } = collectDedupedExecutions(htmlPath);
45
+ const mergedReport = new ReportActionDump({
46
+ sdkVersion: baseDump.sdkVersion,
47
+ groupName: baseDump.groupName,
48
+ groupDescription: baseDump.groupDescription,
49
+ modelBriefs: baseDump.modelBriefs,
50
+ deviceType: baseDump.deviceType,
51
+ executions
52
+ });
53
+ const result = reportToMarkdown(mergedReport);
54
+ const markdownFiles = [];
55
+ const writtenScreenshots = new Set();
56
+ const mdPath = join(outputDir, 'report.md');
57
+ writeFileSync(mdPath, result.markdown, 'utf-8');
58
+ markdownFiles.push(mdPath);
59
+ for (const attachment of result.attachments)writeAttachmentFromReport(attachment, {
60
+ htmlPath,
61
+ screenshotsDir,
62
+ writtenFiles: writtenScreenshots
63
+ });
64
+ return {
65
+ markdownFiles,
66
+ screenshotFiles: Array.from(writtenScreenshots).sort().map((f)=>join(screenshotsDir, f))
67
+ };
68
+ }
69
+ function resolveReportHtmlPath(htmlPath) {
70
+ const normalizedPath = resolve(htmlPath);
71
+ if (!existsSync(normalizedPath)) throw new Error(`report-tool: --htmlPath does not exist: ${htmlPath}`);
72
+ const stats = statSync(normalizedPath);
73
+ if (!stats.isDirectory()) return normalizedPath;
74
+ const indexHtmlPath = join(normalizedPath, 'index.html');
75
+ if (!existsSync(indexHtmlPath)) throw new Error(`report-tool: "${htmlPath}" is not an HTML report file, and no index.html was found under this directory.`);
76
+ return indexHtmlPath;
77
+ }
78
+ function splitReportFile(options) {
79
+ const { htmlPath, outputDir } = options;
80
+ if (!htmlPath) throw new Error('splitReportFile: htmlPath is required');
81
+ if (!outputDir) throw new Error('splitReportFile: outputDir is required');
82
+ const resolvedHtmlPath = resolveReportHtmlPath(htmlPath);
83
+ return splitReportHtmlByExecution({
84
+ htmlPath: resolvedHtmlPath,
85
+ outputDir
86
+ });
87
+ }
88
+ async function reportFileToMarkdown(options) {
89
+ const { htmlPath, outputDir } = options;
90
+ if (!htmlPath) throw new Error('reportFileToMarkdown: htmlPath is required');
91
+ if (!outputDir) throw new Error('reportFileToMarkdown: outputDir is required');
92
+ const resolvedHtmlPath = resolveReportHtmlPath(htmlPath);
93
+ return markdownFromReport(resolvedHtmlPath, outputDir);
94
+ }
95
+ const reportCommandDefinition = {
96
+ name: 'report-tool',
97
+ description: 'Transform Midscene report artifacts, including splitting executions and converting to markdown.',
98
+ schema: {
99
+ action: z["enum"]([
100
+ 'split',
101
+ 'to-markdown'
102
+ ]).optional().describe('Report action to run. Supports: split, to-markdown. Defaults to split.'),
103
+ htmlPath: z.string().optional().describe('Input report HTML path (e.g. ./report/index.html)'),
104
+ outputDir: z.string().optional().describe('Output directory for generated report artifacts')
105
+ },
106
+ handler: async (args)=>{
107
+ const { action = 'split', htmlPath, outputDir } = args;
108
+ if ('split' !== action && 'to-markdown' !== action) throw new Error(`report-tool: unsupported --action value "${action}". Currently supported: split, to-markdown`);
109
+ if (!htmlPath) throw new Error('report-tool: --htmlPath is required');
110
+ if (!outputDir) throw new Error('report-tool: --outputDir is required');
111
+ if ('to-markdown' === action) {
112
+ const result = await reportFileToMarkdown({
113
+ htmlPath,
114
+ outputDir
115
+ });
116
+ return {
117
+ isError: false,
118
+ content: [
119
+ {
120
+ type: 'text',
121
+ text: `Markdown export completed. Generated ${result.markdownFiles.length} markdown file(s) and ${result.screenshotFiles.length} screenshot(s). Output path: ${outputDir}`
122
+ }
123
+ ]
124
+ };
125
+ }
126
+ const result = splitReportFile({
127
+ htmlPath,
128
+ outputDir
129
+ });
130
+ return {
131
+ isError: false,
132
+ content: [
133
+ {
134
+ type: 'text',
135
+ text: `Report split completed. Generated ${result.executionJsonFiles.length} execution JSON files and ${result.screenshotFiles.length} screenshots. Output path: ${outputDir}`
136
+ }
137
+ ]
138
+ };
139
+ }
140
+ };
141
+ function createReportCliCommands() {
142
+ return [
143
+ {
144
+ name: 'report-tool',
145
+ def: reportCommandDefinition
146
+ }
147
+ ];
148
+ }
149
+ export { createReportCliCommands, reportFileToMarkdown, splitReportFile };
150
+
151
+ //# sourceMappingURL=report-cli.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"report-cli.mjs","sources":["../../src/report-cli.ts"],"sourcesContent":["import {\n copyFileSync,\n existsSync,\n mkdirSync,\n statSync,\n writeFileSync,\n} from 'node:fs';\nimport * as path from 'node:path';\nimport { z } from 'zod';\nimport { resolveScreenshotSource } from './dump/screenshot-store';\nimport { collectDedupedExecutions, splitReportHtmlByExecution } from './report';\nimport { reportToMarkdown } from './report-markdown';\nimport type { MarkdownAttachment } from './report-markdown';\nimport { ReportActionDump } from './types';\n\ntype ReportCliToolResult = {\n isError: boolean;\n content: Array<{\n type: 'text';\n text: string;\n }>;\n};\n\ntype ReportCliSchema = Record<string, z.ZodTypeAny>;\n\nexport interface ReportCliCommandDefinition {\n name: string;\n description: string;\n schema: ReportCliSchema;\n handler: (args: Record<string, unknown>) => Promise<ReportCliToolResult>;\n}\n\nexport interface ReportCliCommandEntry {\n name: string;\n def: ReportCliCommandDefinition;\n}\n\nexport type ConsumeReportFileAction = 'split' | 'to-markdown';\n\nexport interface ConsumeReportFileOptions {\n htmlPath: string;\n outputDir: string;\n}\n\nexport type SplitReportFileOptions = ConsumeReportFileOptions;\nexport type ReportFileToMarkdownOptions = ConsumeReportFileOptions;\n\nfunction writeAttachmentFromReport(\n attachment: MarkdownAttachment,\n opts: {\n htmlPath: string;\n screenshotsDir: string;\n writtenFiles: Set<string>;\n },\n): void {\n const { suggestedFileName, id, mimeType } = attachment;\n if (opts.writtenFiles.has(suggestedFileName)) return;\n\n const absolutePath = path.join(opts.screenshotsDir, suggestedFileName);\n\n const outputRelativePath = `./screenshots/${suggestedFileName}`;\n const sourceRef =\n attachment.filePath !== outputRelativePath\n ? {\n type: 'midscene_screenshot_ref' as const,\n id,\n capturedAt: 0,\n mimeType: (mimeType || 'image/png') as 'image/png' | 'image/jpeg',\n storage: 'file' as const,\n path: attachment.filePath,\n }\n : null;\n\n const resolved = resolveScreenshotSource(sourceRef, {\n reportPath: opts.htmlPath,\n fallbackId: id,\n fallbackMimeType: (mimeType || 'image/png') as 'image/png' | 'image/jpeg',\n });\n\n if (resolved.type === 'data-uri') {\n const rawBase64 = resolved.dataUri.replace(\n /^data:image\\/[a-zA-Z+]+;base64,/,\n '',\n );\n writeFileSync(absolutePath, Buffer.from(rawBase64, 'base64'));\n opts.writtenFiles.add(suggestedFileName);\n return;\n }\n\n if (!existsSync(resolved.filePath)) {\n throw new Error(\n `Cannot resolve screenshot \"${id}\" for markdown attachment from ${opts.htmlPath}`,\n );\n }\n\n copyFileSync(resolved.filePath, absolutePath);\n opts.writtenFiles.add(suggestedFileName);\n}\n\nasync function markdownFromReport(\n htmlPath: string,\n outputDir: string,\n): Promise<{ markdownFiles: string[]; screenshotFiles: string[] }> {\n const screenshotsDir = path.join(outputDir, 'screenshots');\n\n mkdirSync(outputDir, { recursive: true });\n mkdirSync(screenshotsDir, { recursive: true });\n\n const { baseDump, executions } = collectDedupedExecutions(htmlPath);\n\n const mergedReport = new ReportActionDump({\n sdkVersion: baseDump.sdkVersion,\n groupName: baseDump.groupName,\n groupDescription: baseDump.groupDescription,\n modelBriefs: baseDump.modelBriefs,\n deviceType: baseDump.deviceType,\n executions,\n });\n\n const result = reportToMarkdown(mergedReport);\n\n const markdownFiles: string[] = [];\n const writtenScreenshots = new Set<string>();\n\n const mdPath = path.join(outputDir, 'report.md');\n writeFileSync(mdPath, result.markdown, 'utf-8');\n markdownFiles.push(mdPath);\n\n for (const attachment of result.attachments) {\n writeAttachmentFromReport(attachment, {\n htmlPath,\n screenshotsDir,\n writtenFiles: writtenScreenshots,\n });\n }\n\n return {\n markdownFiles,\n screenshotFiles: Array.from(writtenScreenshots)\n .sort()\n .map((f) => path.join(screenshotsDir, f)),\n };\n}\n\nfunction resolveReportHtmlPath(htmlPath: string): string {\n const normalizedPath = path.resolve(htmlPath);\n\n if (!existsSync(normalizedPath)) {\n throw new Error(`report-tool: --htmlPath does not exist: ${htmlPath}`);\n }\n\n const stats = statSync(normalizedPath);\n if (!stats.isDirectory()) {\n return normalizedPath;\n }\n\n const indexHtmlPath = path.join(normalizedPath, 'index.html');\n if (!existsSync(indexHtmlPath)) {\n throw new Error(\n `report-tool: \"${htmlPath}\" is not an HTML report file, and no index.html was found under this directory.`,\n );\n }\n\n return indexHtmlPath;\n}\n\nexport function splitReportFile(options: SplitReportFileOptions): {\n executionJsonFiles: string[];\n screenshotFiles: string[];\n} {\n const { htmlPath, outputDir } = options;\n if (!htmlPath) {\n throw new Error('splitReportFile: htmlPath is required');\n }\n\n if (!outputDir) {\n throw new Error('splitReportFile: outputDir is required');\n }\n\n const resolvedHtmlPath = resolveReportHtmlPath(htmlPath);\n return splitReportHtmlByExecution({\n htmlPath: resolvedHtmlPath,\n outputDir,\n });\n}\n\nexport async function reportFileToMarkdown(\n options: ReportFileToMarkdownOptions,\n): Promise<{ markdownFiles: string[]; screenshotFiles: string[] }> {\n const { htmlPath, outputDir } = options;\n if (!htmlPath) {\n throw new Error('reportFileToMarkdown: htmlPath is required');\n }\n\n if (!outputDir) {\n throw new Error('reportFileToMarkdown: outputDir is required');\n }\n\n const resolvedHtmlPath = resolveReportHtmlPath(htmlPath);\n return markdownFromReport(resolvedHtmlPath, outputDir);\n}\n\nconst reportCommandDefinition: ReportCliCommandDefinition = {\n name: 'report-tool',\n description:\n 'Transform Midscene report artifacts, including splitting executions and converting to markdown.',\n schema: {\n action: z\n .enum(['split', 'to-markdown'])\n .optional()\n .describe(\n 'Report action to run. Supports: split, to-markdown. Defaults to split.',\n ),\n htmlPath: z\n .string()\n .optional()\n .describe('Input report HTML path (e.g. ./report/index.html)'),\n outputDir: z\n .string()\n .optional()\n .describe('Output directory for generated report artifacts'),\n },\n handler: async (args) => {\n const {\n action = 'split',\n htmlPath,\n outputDir,\n } = args as {\n action?: string;\n htmlPath?: string;\n outputDir?: string;\n };\n if (action !== 'split' && action !== 'to-markdown') {\n throw new Error(\n `report-tool: unsupported --action value \"${action}\". Currently supported: split, to-markdown`,\n );\n }\n\n if (!htmlPath) {\n throw new Error('report-tool: --htmlPath is required');\n }\n\n if (!outputDir) {\n throw new Error('report-tool: --outputDir is required');\n }\n\n if (action === 'to-markdown') {\n const result = await reportFileToMarkdown({\n htmlPath,\n outputDir,\n });\n return {\n isError: false,\n content: [\n {\n type: 'text',\n text: `Markdown export completed. Generated ${result.markdownFiles.length} markdown file(s) and ${result.screenshotFiles.length} screenshot(s). Output path: ${outputDir}`,\n },\n ],\n };\n }\n\n const result = splitReportFile({\n htmlPath,\n outputDir,\n });\n\n return {\n isError: false,\n content: [\n {\n type: 'text',\n text: `Report split completed. Generated ${result.executionJsonFiles.length} execution JSON files and ${result.screenshotFiles.length} screenshots. Output path: ${outputDir}`,\n },\n ],\n };\n },\n};\n\nexport function createReportCliCommands(): ReportCliCommandEntry[] {\n return [\n {\n name: 'report-tool',\n def: reportCommandDefinition,\n },\n ];\n}\n"],"names":["writeAttachmentFromReport","attachment","opts","suggestedFileName","id","mimeType","absolutePath","path","outputRelativePath","sourceRef","resolved","resolveScreenshotSource","rawBase64","writeFileSync","Buffer","existsSync","Error","copyFileSync","markdownFromReport","htmlPath","outputDir","screenshotsDir","mkdirSync","baseDump","executions","collectDedupedExecutions","mergedReport","ReportActionDump","result","reportToMarkdown","markdownFiles","writtenScreenshots","Set","mdPath","Array","f","resolveReportHtmlPath","normalizedPath","stats","statSync","indexHtmlPath","splitReportFile","options","resolvedHtmlPath","splitReportHtmlByExecution","reportFileToMarkdown","reportCommandDefinition","z","args","action","createReportCliCommands"],"mappings":"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"}
@@ -0,0 +1,205 @@
1
+ import { existsSync, mkdirSync, readdirSync, writeFileSync } from "node:fs";
2
+ import { dirname, join } from "node:path";
3
+ import { getMidsceneRunSubDir } from "@midscene/shared/common";
4
+ import { MIDSCENE_REPORT_QUIET, globalConfigManager } from "@midscene/shared/env";
5
+ import { ifInBrowser, logMsg, uuid } from "@midscene/shared/utils";
6
+ import { generateDumpScriptTag, generateImageScriptTag, getBaseUrlFixScript } from "./dump/html-utils.mjs";
7
+ import { ScreenshotStore } from "./dump/screenshot-store.mjs";
8
+ import { ReportActionDump } from "./types.mjs";
9
+ import { appendFileSync, getReportTpl } from "./utils.mjs";
10
+ function _define_property(obj, key, value) {
11
+ if (key in obj) Object.defineProperty(obj, key, {
12
+ value: value,
13
+ enumerable: true,
14
+ configurable: true,
15
+ writable: true
16
+ });
17
+ else obj[key] = value;
18
+ return obj;
19
+ }
20
+ const nullReportGenerator = {
21
+ onExecutionUpdate: ()=>{},
22
+ flush: async ()=>{},
23
+ finalize: async ()=>void 0,
24
+ getReportPath: ()=>void 0
25
+ };
26
+ function assertReportGenerationOptions(opts) {
27
+ if (false === opts.generateReport && true === opts.persistExecutionDump) throw new Error('persistExecutionDump cannot be true when generateReport is false');
28
+ }
29
+ class ReportGenerator {
30
+ static create(reportFileName, opts) {
31
+ assertReportGenerationOptions(opts);
32
+ if (false === opts.generateReport) return nullReportGenerator;
33
+ if (ifInBrowser) return nullReportGenerator;
34
+ validateReportFileName(reportFileName);
35
+ const reportRootDir = getMidsceneRunSubDir('report');
36
+ const outputDir = join(reportRootDir, reportFileName);
37
+ const reportPath = 'html-and-external-assets' === opts.outputFormat ? join(outputDir, 'index.html') : join(reportRootDir, ensureHtmlFileName(reportFileName));
38
+ return new ReportGenerator({
39
+ reportPath,
40
+ screenshotMode: 'html-and-external-assets' === opts.outputFormat ? 'directory' : 'inline',
41
+ persistExecutionDump: opts.persistExecutionDump,
42
+ autoPrint: opts.autoPrintReportMsg
43
+ });
44
+ }
45
+ onExecutionUpdate(execution, reportMeta, attributes) {
46
+ this.lastExecution = execution;
47
+ this.lastReportMeta = reportMeta;
48
+ this.mergeReportAttributes(attributes);
49
+ this.writeQueue = this.writeQueue.then(()=>{
50
+ if (this.destroyed) return;
51
+ this.doWriteExecution(execution, reportMeta);
52
+ });
53
+ }
54
+ async flush() {
55
+ await this.writeQueue;
56
+ }
57
+ async finalize() {
58
+ if (this.lastExecution && this.lastReportMeta) this.onExecutionUpdate(this.lastExecution, this.lastReportMeta);
59
+ await this.flush();
60
+ this.destroyed = true;
61
+ if (!this.initialized) return;
62
+ this.printReportPath('finalized');
63
+ return this.reportPath;
64
+ }
65
+ getReportPath() {
66
+ return this.reportPath;
67
+ }
68
+ printReportPath(verb) {
69
+ if (!this.autoPrint || !this.reportPath) return;
70
+ if (globalConfigManager.getEnvConfigInBoolean(MIDSCENE_REPORT_QUIET)) return;
71
+ 'directory' === this.screenshotMode ? logMsg(`Midscene - report ${verb}: npx serve ${dirname(this.reportPath)}`) : logMsg(`Midscene - report ${verb}: ${this.reportPath}`);
72
+ }
73
+ doWriteExecution(execution, reportMeta) {
74
+ const singleDump = this.wrapAsReportDump(execution, reportMeta);
75
+ if ('inline' === this.screenshotMode) this.writeInlineExecution(execution, singleDump);
76
+ else this.writeDirectoryExecution(execution, singleDump);
77
+ if (this.shouldPersistExecutionDump) this.persistExecutionDumpToFile(execution, singleDump);
78
+ if (!this.firstWriteDone) {
79
+ this.firstWriteDone = true;
80
+ this.printReportPath('generated');
81
+ }
82
+ }
83
+ mergeReportAttributes(attributes) {
84
+ if (!attributes) return;
85
+ for (const [key, value] of Object.entries(attributes))if (null != value) {
86
+ if ('data-group-id' !== key) this.reportAttributes[key] = String(value);
87
+ }
88
+ }
89
+ hydrateStateFromExistingReport() {
90
+ if (!existsSync(this.reportPath)) return;
91
+ this.initialized = true;
92
+ if (!this.shouldPersistExecutionDump) return;
93
+ const reportDir = dirname(this.reportPath);
94
+ const existingExecutionIndices = readdirSync(reportDir).map((name)=>/^(\d+)\.execution\.json$/.exec(name)?.[1]).filter((index)=>Boolean(index)).map((index)=>Number.parseInt(index, 10)).filter((index)=>Number.isFinite(index));
95
+ if (existingExecutionIndices.length > 0) this.executionLogIndex = Math.max(...existingExecutionIndices);
96
+ }
97
+ getDumpScriptAttributes() {
98
+ return {
99
+ 'data-group-id': this.reportStreamId,
100
+ ...this.reportAttributes
101
+ };
102
+ }
103
+ wrapAsReportDump(execution, reportMeta) {
104
+ return new ReportActionDump({
105
+ sdkVersion: reportMeta.sdkVersion,
106
+ groupName: reportMeta.groupName,
107
+ groupDescription: reportMeta.groupDescription,
108
+ modelBriefs: reportMeta.modelBriefs,
109
+ deviceType: reportMeta.deviceType,
110
+ executions: [
111
+ execution
112
+ ]
113
+ });
114
+ }
115
+ writeInlineExecution(execution, singleDump) {
116
+ const dir = dirname(this.reportPath);
117
+ if (!existsSync(dir)) mkdirSync(dir, {
118
+ recursive: true
119
+ });
120
+ if (!this.initialized) {
121
+ writeFileSync(this.reportPath, getReportTpl());
122
+ this.initialized = true;
123
+ }
124
+ for (const screenshot of execution.collectScreenshots())this.screenshotStore.persist(screenshot);
125
+ const serialized = singleDump.serialize();
126
+ appendFileSync(this.reportPath, `\n${generateDumpScriptTag(serialized, this.getDumpScriptAttributes())}`);
127
+ }
128
+ writeDirectoryExecution(execution, singleDump) {
129
+ const dir = dirname(this.reportPath);
130
+ if (!existsSync(dir)) mkdirSync(dir, {
131
+ recursive: true
132
+ });
133
+ for (const screenshot of execution.collectScreenshots())this.screenshotStore.persist(screenshot);
134
+ const serialized = singleDump.serialize();
135
+ if (!this.initialized) {
136
+ writeFileSync(this.reportPath, `${getReportTpl()}${getBaseUrlFixScript()}`);
137
+ this.initialized = true;
138
+ }
139
+ appendFileSync(this.reportPath, `\n${generateDumpScriptTag(serialized, this.getDumpScriptAttributes())}`);
140
+ }
141
+ getExecutionLogKey(execution) {
142
+ if (!execution.id) throw new Error('ReportGenerator: execution.id is required for persisting execution dumps');
143
+ return `id:${execution.id}`;
144
+ }
145
+ persistExecutionDumpToFile(execution, singleDump) {
146
+ const dir = dirname(this.reportPath);
147
+ if (!existsSync(dir)) mkdirSync(dir, {
148
+ recursive: true
149
+ });
150
+ const executionLogKey = this.getExecutionLogKey(execution);
151
+ let fileIndex = this.executionLogFileIndexByExecutionKey.get(executionLogKey);
152
+ if (!fileIndex) {
153
+ this.executionLogIndex += 1;
154
+ fileIndex = this.executionLogIndex;
155
+ this.executionLogFileIndexByExecutionKey.set(executionLogKey, fileIndex);
156
+ }
157
+ const fileName = `${fileIndex}.execution.json`;
158
+ const filePath = join(dirname(this.reportPath), fileName);
159
+ writeFileSync(filePath, singleDump.serialize(2), 'utf-8');
160
+ }
161
+ constructor(options){
162
+ _define_property(this, "reportPath", void 0);
163
+ _define_property(this, "screenshotMode", void 0);
164
+ _define_property(this, "shouldPersistExecutionDump", void 0);
165
+ _define_property(this, "autoPrint", void 0);
166
+ _define_property(this, "firstWriteDone", false);
167
+ _define_property(this, "executionLogIndex", 0);
168
+ _define_property(this, "executionLogFileIndexByExecutionKey", new Map());
169
+ _define_property(this, "reportStreamId", void 0);
170
+ _define_property(this, "screenshotStore", void 0);
171
+ _define_property(this, "initialized", false);
172
+ _define_property(this, "lastExecution", void 0);
173
+ _define_property(this, "lastReportMeta", void 0);
174
+ _define_property(this, "reportAttributes", {});
175
+ _define_property(this, "writeQueue", Promise.resolve());
176
+ _define_property(this, "destroyed", false);
177
+ this.reportPath = options.reportPath;
178
+ this.screenshotMode = options.screenshotMode;
179
+ this.shouldPersistExecutionDump = options.persistExecutionDump ?? false;
180
+ this.autoPrint = options.autoPrint ?? true;
181
+ this.reportStreamId = uuid();
182
+ this.screenshotStore = new ScreenshotStore({
183
+ mode: 'inline' === this.screenshotMode ? 'inline' : 'directory',
184
+ reportPath: this.reportPath,
185
+ screenshotsDir: join(dirname(this.reportPath), 'screenshots'),
186
+ writeInlineImage: (id, base64)=>{
187
+ appendFileSync(this.reportPath, `\n${generateImageScriptTag(id, base64)}`);
188
+ },
189
+ alsoWriteFileCopy: this.shouldPersistExecutionDump
190
+ });
191
+ this.hydrateStateFromExistingReport();
192
+ this.printReportPath('will be generated at');
193
+ }
194
+ }
195
+ function ensureHtmlFileName(reportFileName) {
196
+ return reportFileName.endsWith('.html') ? reportFileName : `${reportFileName}.html`;
197
+ }
198
+ function validateReportFileName(reportFileName) {
199
+ if (!reportFileName?.trim()) throw new Error('reportFileName must be a non-empty string');
200
+ if (/[\\/]/.test(reportFileName)) throw new Error('reportFileName must not contain path separators (`/` or `\\\\`)');
201
+ if (/[:*?"<>|]/.test(reportFileName)) throw new Error('reportFileName contains illegal filename characters: : * ? " < > |');
202
+ }
203
+ export { ReportGenerator, assertReportGenerationOptions, nullReportGenerator };
204
+
205
+ //# sourceMappingURL=report-generator.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"report-generator.mjs","sources":["../../src/report-generator.ts"],"sourcesContent":["import { existsSync, mkdirSync, readdirSync, writeFileSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport { getMidsceneRunSubDir } from '@midscene/shared/common';\nimport {\n MIDSCENE_REPORT_QUIET,\n globalConfigManager,\n} from '@midscene/shared/env';\nimport { ifInBrowser, logMsg, uuid } from '@midscene/shared/utils';\nimport {\n generateDumpScriptTag,\n generateImageScriptTag,\n getBaseUrlFixScript,\n} from './dump/html-utils';\nimport { ScreenshotStore } from './dump/screenshot-store';\nimport {\n type ExecutionDump,\n ReportActionDump,\n type ReportAttributes,\n type ReportMeta,\n} from './types';\nimport { appendFileSync, getReportTpl } from './utils';\n\nexport interface IReportGenerator {\n /**\n * Write or update a single execution.\n * Each call appends a new dump script tag. The frontend deduplicates\n * executions with the same id/name, keeping only the last one.\n *\n * @param execution Current execution's full data\n * @param reportMeta Report-level metadata (groupName, sdkVersion, etc.)\n */\n onExecutionUpdate(\n execution: ExecutionDump,\n reportMeta: ReportMeta,\n attributes?: ReportAttributes,\n ): void;\n\n /**\n * @deprecated Use onExecutionUpdate instead. Kept for backward compatibility.\n */\n onDumpUpdate?(dump: ReportActionDump): void;\n\n /**\n * Wait for all queued write operations to complete.\n */\n flush(): Promise<void>;\n\n /**\n * Finalize the report. Calls flush() internally.\n */\n finalize(): Promise<string | undefined>;\n\n getReportPath(): string | undefined;\n}\n\nexport const nullReportGenerator: IReportGenerator = {\n onExecutionUpdate: () => {},\n flush: async () => {},\n finalize: async () => undefined,\n getReportPath: () => undefined,\n};\n\nexport function assertReportGenerationOptions(opts: {\n generateReport?: boolean;\n persistExecutionDump?: boolean;\n}): void {\n if (opts.generateReport === false && opts.persistExecutionDump === true) {\n throw new Error(\n 'persistExecutionDump cannot be true when generateReport is false',\n );\n }\n}\n\nexport class ReportGenerator implements IReportGenerator {\n private reportPath: string;\n private screenshotMode: 'inline' | 'directory';\n private shouldPersistExecutionDump: boolean;\n private autoPrint: boolean;\n private firstWriteDone = false;\n private executionLogIndex = 0;\n private executionLogFileIndexByExecutionKey = new Map<string, number>();\n\n // Unique identifier for this report stream — used as data-group-id\n private readonly reportStreamId: string;\n\n // Tracks screenshots already written to disk (by id) to avoid duplicates\n private screenshotStore: ScreenshotStore;\n private initialized = false;\n\n // Tracks the last execution + groupMeta for re-writing on finalize\n private lastExecution?: ExecutionDump;\n private lastReportMeta?: ReportMeta;\n private reportAttributes: Record<string, string> = {};\n\n // write queue for serial execution\n private writeQueue: Promise<void> = Promise.resolve();\n private destroyed = false;\n\n constructor(options: {\n reportPath: string;\n screenshotMode: 'inline' | 'directory';\n persistExecutionDump?: boolean;\n autoPrint?: boolean;\n }) {\n this.reportPath = options.reportPath;\n this.screenshotMode = options.screenshotMode;\n this.shouldPersistExecutionDump = options.persistExecutionDump ?? false;\n this.autoPrint = options.autoPrint ?? true;\n this.reportStreamId = uuid();\n this.screenshotStore = new ScreenshotStore({\n mode: this.screenshotMode === 'inline' ? 'inline' : 'directory',\n reportPath: this.reportPath,\n screenshotsDir: join(dirname(this.reportPath), 'screenshots'),\n writeInlineImage: (id, base64) => {\n appendFileSync(\n this.reportPath,\n `\\n${generateImageScriptTag(id, base64)}`,\n );\n },\n alsoWriteFileCopy: this.shouldPersistExecutionDump,\n });\n this.hydrateStateFromExistingReport();\n this.printReportPath('will be generated at');\n }\n\n static create(\n reportFileName: string,\n opts: {\n generateReport?: boolean;\n persistExecutionDump?: boolean;\n outputFormat?: 'single-html' | 'html-and-external-assets';\n autoPrintReportMsg?: boolean;\n },\n ): IReportGenerator {\n assertReportGenerationOptions(opts);\n if (opts.generateReport === false) return nullReportGenerator;\n\n // In browser environment, file system is not available\n if (ifInBrowser) return nullReportGenerator;\n validateReportFileName(reportFileName);\n\n const reportRootDir = getMidsceneRunSubDir('report');\n const outputDir = join(reportRootDir, reportFileName);\n const reportPath =\n opts.outputFormat === 'html-and-external-assets'\n ? join(outputDir, 'index.html')\n : join(reportRootDir, ensureHtmlFileName(reportFileName));\n return new ReportGenerator({\n reportPath,\n screenshotMode:\n opts.outputFormat === 'html-and-external-assets'\n ? 'directory'\n : 'inline',\n persistExecutionDump: opts.persistExecutionDump,\n autoPrint: opts.autoPrintReportMsg,\n });\n }\n\n onExecutionUpdate(\n execution: ExecutionDump,\n reportMeta: ReportMeta,\n attributes?: ReportAttributes,\n ): void {\n this.lastExecution = execution;\n this.lastReportMeta = reportMeta;\n this.mergeReportAttributes(attributes);\n this.writeQueue = this.writeQueue.then(() => {\n if (this.destroyed) return;\n this.doWriteExecution(execution, reportMeta);\n });\n }\n\n async flush(): Promise<void> {\n await this.writeQueue;\n }\n\n async finalize(): Promise<string | undefined> {\n // Re-write the last execution to capture any final state changes\n if (this.lastExecution && this.lastReportMeta) {\n this.onExecutionUpdate(this.lastExecution, this.lastReportMeta);\n }\n await this.flush();\n this.destroyed = true;\n\n if (!this.initialized) {\n // No executions were ever written — no file exists\n return undefined;\n }\n\n this.printReportPath('finalized');\n return this.reportPath;\n }\n\n getReportPath(): string | undefined {\n return this.reportPath;\n }\n\n private printReportPath(verb: string): void {\n if (!this.autoPrint || !this.reportPath) return;\n if (globalConfigManager.getEnvConfigInBoolean(MIDSCENE_REPORT_QUIET))\n return;\n\n if (this.screenshotMode === 'directory') {\n logMsg(\n `Midscene - report ${verb}: npx serve ${dirname(this.reportPath)}`,\n );\n } else {\n logMsg(`Midscene - report ${verb}: ${this.reportPath}`);\n }\n }\n\n private doWriteExecution(\n execution: ExecutionDump,\n reportMeta: ReportMeta,\n ): void {\n const singleDump = this.wrapAsReportDump(execution, reportMeta);\n\n if (this.screenshotMode === 'inline') {\n this.writeInlineExecution(execution, singleDump);\n } else {\n this.writeDirectoryExecution(execution, singleDump);\n }\n\n if (this.shouldPersistExecutionDump) {\n this.persistExecutionDumpToFile(execution, singleDump);\n }\n\n if (!this.firstWriteDone) {\n this.firstWriteDone = true;\n this.printReportPath('generated');\n }\n }\n\n private mergeReportAttributes(attributes?: ReportAttributes): void {\n if (!attributes) {\n return;\n }\n\n for (const [key, value] of Object.entries(attributes)) {\n if (value === undefined || value === null) {\n continue;\n }\n if (key === 'data-group-id') {\n continue;\n }\n this.reportAttributes[key] = String(value);\n }\n }\n\n private hydrateStateFromExistingReport(): void {\n if (!existsSync(this.reportPath)) {\n return;\n }\n\n // Reuse existing report file and append new updates instead of rewriting.\n this.initialized = true;\n\n if (!this.shouldPersistExecutionDump) {\n return;\n }\n\n const reportDir = dirname(this.reportPath);\n const existingExecutionIndices = readdirSync(reportDir)\n .map((name) => /^(\\d+)\\.execution\\.json$/.exec(name)?.[1])\n .filter((index): index is string => Boolean(index))\n .map((index) => Number.parseInt(index, 10))\n .filter((index) => Number.isFinite(index));\n\n if (existingExecutionIndices.length > 0) {\n this.executionLogIndex = Math.max(...existingExecutionIndices);\n }\n }\n\n private getDumpScriptAttributes(): Record<string, string> {\n return {\n 'data-group-id': this.reportStreamId,\n ...this.reportAttributes,\n };\n }\n\n /**\n * Wrap an ExecutionDump + ReportMeta into a single-execution ReportActionDump.\n */\n private wrapAsReportDump(\n execution: ExecutionDump,\n reportMeta: ReportMeta,\n ): ReportActionDump {\n return new ReportActionDump({\n sdkVersion: reportMeta.sdkVersion,\n groupName: reportMeta.groupName,\n groupDescription: reportMeta.groupDescription,\n modelBriefs: reportMeta.modelBriefs,\n deviceType: reportMeta.deviceType,\n executions: [execution],\n });\n }\n\n /**\n * Append-only inline mode: write new screenshots and a dump tag on every call.\n * The frontend deduplicates executions with the same id/name (keeps last).\n * Duplicate dump JSON is acceptable; only screenshots are deduplicated.\n */\n private writeInlineExecution(\n execution: ExecutionDump,\n singleDump: ReportActionDump,\n ): void {\n const dir = dirname(this.reportPath);\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true });\n }\n\n // Initialize: write HTML template once\n if (!this.initialized) {\n writeFileSync(this.reportPath, getReportTpl());\n this.initialized = true;\n }\n\n // Append new screenshots (skip already-written ones)\n for (const screenshot of execution.collectScreenshots()) {\n this.screenshotStore.persist(screenshot);\n }\n\n // Append dump tag (always — frontend keeps only last per execution id)\n const serialized = singleDump.serialize();\n appendFileSync(\n this.reportPath,\n `\\n${generateDumpScriptTag(serialized, this.getDumpScriptAttributes())}`,\n );\n }\n\n private writeDirectoryExecution(\n execution: ExecutionDump,\n singleDump: ReportActionDump,\n ): void {\n const dir = dirname(this.reportPath);\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true });\n }\n\n for (const screenshot of execution.collectScreenshots()) {\n this.screenshotStore.persist(screenshot);\n }\n\n // 2. Append dump tag (always — frontend keeps only last per execution id)\n const serialized = singleDump.serialize();\n\n if (!this.initialized) {\n writeFileSync(\n this.reportPath,\n `${getReportTpl()}${getBaseUrlFixScript()}`,\n );\n this.initialized = true;\n }\n\n appendFileSync(\n this.reportPath,\n `\\n${generateDumpScriptTag(serialized, this.getDumpScriptAttributes())}`,\n );\n }\n\n private getExecutionLogKey(execution: ExecutionDump): string {\n if (!execution.id) {\n throw new Error(\n 'ReportGenerator: execution.id is required for persisting execution dumps',\n );\n }\n return `id:${execution.id}`;\n }\n\n private persistExecutionDumpToFile(\n execution: ExecutionDump,\n singleDump: ReportActionDump,\n ): void {\n const dir = dirname(this.reportPath);\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true });\n }\n\n const executionLogKey = this.getExecutionLogKey(execution);\n let fileIndex =\n this.executionLogFileIndexByExecutionKey.get(executionLogKey);\n if (!fileIndex) {\n this.executionLogIndex += 1;\n fileIndex = this.executionLogIndex;\n this.executionLogFileIndexByExecutionKey.set(executionLogKey, fileIndex);\n }\n\n const fileName = `${fileIndex}.execution.json`;\n const filePath = join(dirname(this.reportPath), fileName);\n writeFileSync(filePath, singleDump.serialize(2), 'utf-8');\n }\n}\n\nfunction ensureHtmlFileName(reportFileName: string): string {\n return reportFileName.endsWith('.html')\n ? reportFileName\n : `${reportFileName}.html`;\n}\n\nfunction validateReportFileName(reportFileName: string): void {\n if (!reportFileName?.trim()) {\n throw new Error('reportFileName must be a non-empty string');\n }\n\n if (/[\\\\/]/.test(reportFileName)) {\n throw new Error(\n 'reportFileName must not contain path separators (`/` or `\\\\\\\\`)',\n );\n }\n\n if (/[:*?\"<>|]/.test(reportFileName)) {\n throw new Error(\n 'reportFileName contains illegal filename characters: : * ? \" < > |',\n );\n }\n}\n"],"names":["nullReportGenerator","undefined","assertReportGenerationOptions","opts","Error","ReportGenerator","reportFileName","ifInBrowser","validateReportFileName","reportRootDir","getMidsceneRunSubDir","outputDir","join","reportPath","ensureHtmlFileName","execution","reportMeta","attributes","verb","globalConfigManager","MIDSCENE_REPORT_QUIET","logMsg","dirname","singleDump","key","value","Object","String","existsSync","reportDir","existingExecutionIndices","readdirSync","name","index","Boolean","Number","Math","ReportActionDump","dir","mkdirSync","writeFileSync","getReportTpl","screenshot","serialized","appendFileSync","generateDumpScriptTag","getBaseUrlFixScript","executionLogKey","fileIndex","fileName","filePath","options","Map","Promise","uuid","ScreenshotStore","id","base64","generateImageScriptTag"],"mappings":";;;;;;;;;;;;;;;;;;;AAuDO,MAAMA,sBAAwC;IACnD,mBAAmB,KAAO;IAC1B,OAAO,WAAa;IACpB,UAAU,UAAYC;IACtB,eAAe,IAAMA;AACvB;AAEO,SAASC,8BAA8BC,IAG7C;IACC,IAAIA,AAAwB,UAAxBA,KAAK,cAAc,IAAcA,AAA8B,SAA9BA,KAAK,oBAAoB,EAC5D,MAAM,IAAIC,MACR;AAGN;AAEO,MAAMC;IAoDX,OAAO,OACLC,cAAsB,EACtBH,IAKC,EACiB;QAClBD,8BAA8BC;QAC9B,IAAIA,AAAwB,UAAxBA,KAAK,cAAc,EAAY,OAAOH;QAG1C,IAAIO,aAAa,OAAOP;QACxBQ,uBAAuBF;QAEvB,MAAMG,gBAAgBC,qBAAqB;QAC3C,MAAMC,YAAYC,KAAKH,eAAeH;QACtC,MAAMO,aACJV,AAAsB,+BAAtBA,KAAK,YAAY,GACbS,KAAKD,WAAW,gBAChBC,KAAKH,eAAeK,mBAAmBR;QAC7C,OAAO,IAAID,gBAAgB;YACzBQ;YACA,gBACEV,AAAsB,+BAAtBA,KAAK,YAAY,GACb,cACA;YACN,sBAAsBA,KAAK,oBAAoB;YAC/C,WAAWA,KAAK,kBAAkB;QACpC;IACF;IAEA,kBACEY,SAAwB,EACxBC,UAAsB,EACtBC,UAA6B,EACvB;QACN,IAAI,CAAC,aAAa,GAAGF;QACrB,IAAI,CAAC,cAAc,GAAGC;QACtB,IAAI,CAAC,qBAAqB,CAACC;QAC3B,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;YACrC,IAAI,IAAI,CAAC,SAAS,EAAE;YACpB,IAAI,CAAC,gBAAgB,CAACF,WAAWC;QACnC;IACF;IAEA,MAAM,QAAuB;QAC3B,MAAM,IAAI,CAAC,UAAU;IACvB;IAEA,MAAM,WAAwC;QAE5C,IAAI,IAAI,CAAC,aAAa,IAAI,IAAI,CAAC,cAAc,EAC3C,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC,aAAa,EAAE,IAAI,CAAC,cAAc;QAEhE,MAAM,IAAI,CAAC,KAAK;QAChB,IAAI,CAAC,SAAS,GAAG;QAEjB,IAAI,CAAC,IAAI,CAAC,WAAW,EAEnB;QAGF,IAAI,CAAC,eAAe,CAAC;QACrB,OAAO,IAAI,CAAC,UAAU;IACxB;IAEA,gBAAoC;QAClC,OAAO,IAAI,CAAC,UAAU;IACxB;IAEQ,gBAAgBE,IAAY,EAAQ;QAC1C,IAAI,CAAC,IAAI,CAAC,SAAS,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE;QACzC,IAAIC,oBAAoB,qBAAqB,CAACC,wBAC5C;QAE0B,gBAAxB,IAAI,CAAC,cAAc,GACrBC,OACE,CAAC,kBAAkB,EAAEH,KAAK,YAAY,EAAEI,QAAQ,IAAI,CAAC,UAAU,GAAG,IAGpED,OAAO,CAAC,kBAAkB,EAAEH,KAAK,EAAE,EAAE,IAAI,CAAC,UAAU,EAAE;IAE1D;IAEQ,iBACNH,SAAwB,EACxBC,UAAsB,EAChB;QACN,MAAMO,aAAa,IAAI,CAAC,gBAAgB,CAACR,WAAWC;QAEpD,IAAI,AAAwB,aAAxB,IAAI,CAAC,cAAc,EACrB,IAAI,CAAC,oBAAoB,CAACD,WAAWQ;aAErC,IAAI,CAAC,uBAAuB,CAACR,WAAWQ;QAG1C,IAAI,IAAI,CAAC,0BAA0B,EACjC,IAAI,CAAC,0BAA0B,CAACR,WAAWQ;QAG7C,IAAI,CAAC,IAAI,CAAC,cAAc,EAAE;YACxB,IAAI,CAAC,cAAc,GAAG;YACtB,IAAI,CAAC,eAAe,CAAC;QACvB;IACF;IAEQ,sBAAsBN,UAA6B,EAAQ;QACjE,IAAI,CAACA,YACH;QAGF,KAAK,MAAM,CAACO,KAAKC,MAAM,IAAIC,OAAO,OAAO,CAACT,YACxC,IAAIQ,QAAAA,OAGJ;YAAA,IAAID,AAAQ,oBAARA,KAGJ,IAAI,CAAC,gBAAgB,CAACA,IAAI,GAAGG,OAAOF;QADpC;IAGJ;IAEQ,iCAAuC;QAC7C,IAAI,CAACG,WAAW,IAAI,CAAC,UAAU,GAC7B;QAIF,IAAI,CAAC,WAAW,GAAG;QAEnB,IAAI,CAAC,IAAI,CAAC,0BAA0B,EAClC;QAGF,MAAMC,YAAYP,QAAQ,IAAI,CAAC,UAAU;QACzC,MAAMQ,2BAA2BC,YAAYF,WAC1C,GAAG,CAAC,CAACG,OAAS,2BAA2B,IAAI,CAACA,OAAO,CAAC,EAAE,EACxD,MAAM,CAAC,CAACC,QAA2BC,QAAQD,QAC3C,GAAG,CAAC,CAACA,QAAUE,OAAO,QAAQ,CAACF,OAAO,KACtC,MAAM,CAAC,CAACA,QAAUE,OAAO,QAAQ,CAACF;QAErC,IAAIH,yBAAyB,MAAM,GAAG,GACpC,IAAI,CAAC,iBAAiB,GAAGM,KAAK,GAAG,IAAIN;IAEzC;IAEQ,0BAAkD;QACxD,OAAO;YACL,iBAAiB,IAAI,CAAC,cAAc;YACpC,GAAG,IAAI,CAAC,gBAAgB;QAC1B;IACF;IAKQ,iBACNf,SAAwB,EACxBC,UAAsB,EACJ;QAClB,OAAO,IAAIqB,iBAAiB;YAC1B,YAAYrB,WAAW,UAAU;YACjC,WAAWA,WAAW,SAAS;YAC/B,kBAAkBA,WAAW,gBAAgB;YAC7C,aAAaA,WAAW,WAAW;YACnC,YAAYA,WAAW,UAAU;YACjC,YAAY;gBAACD;aAAU;QACzB;IACF;IAOQ,qBACNA,SAAwB,EACxBQ,UAA4B,EACtB;QACN,MAAMe,MAAMhB,QAAQ,IAAI,CAAC,UAAU;QACnC,IAAI,CAACM,WAAWU,MACdC,UAAUD,KAAK;YAAE,WAAW;QAAK;QAInC,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE;YACrBE,cAAc,IAAI,CAAC,UAAU,EAAEC;YAC/B,IAAI,CAAC,WAAW,GAAG;QACrB;QAGA,KAAK,MAAMC,cAAc3B,UAAU,kBAAkB,GACnD,IAAI,CAAC,eAAe,CAAC,OAAO,CAAC2B;QAI/B,MAAMC,aAAapB,WAAW,SAAS;QACvCqB,eACE,IAAI,CAAC,UAAU,EACf,CAAC,EAAE,EAAEC,sBAAsBF,YAAY,IAAI,CAAC,uBAAuB,KAAK;IAE5E;IAEQ,wBACN5B,SAAwB,EACxBQ,UAA4B,EACtB;QACN,MAAMe,MAAMhB,QAAQ,IAAI,CAAC,UAAU;QACnC,IAAI,CAACM,WAAWU,MACdC,UAAUD,KAAK;YAAE,WAAW;QAAK;QAGnC,KAAK,MAAMI,cAAc3B,UAAU,kBAAkB,GACnD,IAAI,CAAC,eAAe,CAAC,OAAO,CAAC2B;QAI/B,MAAMC,aAAapB,WAAW,SAAS;QAEvC,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE;YACrBiB,cACE,IAAI,CAAC,UAAU,EACf,GAAGC,iBAAiBK,uBAAuB;YAE7C,IAAI,CAAC,WAAW,GAAG;QACrB;QAEAF,eACE,IAAI,CAAC,UAAU,EACf,CAAC,EAAE,EAAEC,sBAAsBF,YAAY,IAAI,CAAC,uBAAuB,KAAK;IAE5E;IAEQ,mBAAmB5B,SAAwB,EAAU;QAC3D,IAAI,CAACA,UAAU,EAAE,EACf,MAAM,IAAIX,MACR;QAGJ,OAAO,CAAC,GAAG,EAAEW,UAAU,EAAE,EAAE;IAC7B;IAEQ,2BACNA,SAAwB,EACxBQ,UAA4B,EACtB;QACN,MAAMe,MAAMhB,QAAQ,IAAI,CAAC,UAAU;QACnC,IAAI,CAACM,WAAWU,MACdC,UAAUD,KAAK;YAAE,WAAW;QAAK;QAGnC,MAAMS,kBAAkB,IAAI,CAAC,kBAAkB,CAAChC;QAChD,IAAIiC,YACF,IAAI,CAAC,mCAAmC,CAAC,GAAG,CAACD;QAC/C,IAAI,CAACC,WAAW;YACd,IAAI,CAAC,iBAAiB,IAAI;YAC1BA,YAAY,IAAI,CAAC,iBAAiB;YAClC,IAAI,CAAC,mCAAmC,CAAC,GAAG,CAACD,iBAAiBC;QAChE;QAEA,MAAMC,WAAW,GAAGD,UAAU,eAAe,CAAC;QAC9C,MAAME,WAAWtC,KAAKU,QAAQ,IAAI,CAAC,UAAU,GAAG2B;QAChDT,cAAcU,UAAU3B,WAAW,SAAS,CAAC,IAAI;IACnD;IApSA,YAAY4B,OAKX,CAAE;QA7BH,uBAAQ,cAAR;QACA,uBAAQ,kBAAR;QACA,uBAAQ,8BAAR;QACA,uBAAQ,aAAR;QACA,uBAAQ,kBAAiB;QACzB,uBAAQ,qBAAoB;QAC5B,uBAAQ,uCAAsC,IAAIC;QAGlD,uBAAiB,kBAAjB;QAGA,uBAAQ,mBAAR;QACA,uBAAQ,eAAc;QAGtB,uBAAQ,iBAAR;QACA,uBAAQ,kBAAR;QACA,uBAAQ,oBAA2C,CAAC;QAGpD,uBAAQ,cAA4BC,QAAQ,OAAO;QACnD,uBAAQ,aAAY;QAQlB,IAAI,CAAC,UAAU,GAAGF,QAAQ,UAAU;QACpC,IAAI,CAAC,cAAc,GAAGA,QAAQ,cAAc;QAC5C,IAAI,CAAC,0BAA0B,GAAGA,QAAQ,oBAAoB,IAAI;QAClE,IAAI,CAAC,SAAS,GAAGA,QAAQ,SAAS,IAAI;QACtC,IAAI,CAAC,cAAc,GAAGG;QACtB,IAAI,CAAC,eAAe,GAAG,IAAIC,gBAAgB;YACzC,MAAM,AAAwB,aAAxB,IAAI,CAAC,cAAc,GAAgB,WAAW;YACpD,YAAY,IAAI,CAAC,UAAU;YAC3B,gBAAgB3C,KAAKU,QAAQ,IAAI,CAAC,UAAU,GAAG;YAC/C,kBAAkB,CAACkC,IAAIC;gBACrBb,eACE,IAAI,CAAC,UAAU,EACf,CAAC,EAAE,EAAEc,uBAAuBF,IAAIC,SAAS;YAE7C;YACA,mBAAmB,IAAI,CAAC,0BAA0B;QACpD;QACA,IAAI,CAAC,8BAA8B;QACnC,IAAI,CAAC,eAAe,CAAC;IACvB;AA4QF;AAEA,SAAS3C,mBAAmBR,cAAsB;IAChD,OAAOA,eAAe,QAAQ,CAAC,WAC3BA,iBACA,GAAGA,eAAe,KAAK,CAAC;AAC9B;AAEA,SAASE,uBAAuBF,cAAsB;IACpD,IAAI,CAACA,gBAAgB,QACnB,MAAM,IAAIF,MAAM;IAGlB,IAAI,QAAQ,IAAI,CAACE,iBACf,MAAM,IAAIF,MACR;IAIJ,IAAI,YAAY,IAAI,CAACE,iBACnB,MAAM,IAAIF,MACR;AAGN"}
@@ -0,0 +1,218 @@
1
+ import { basename } from "node:path";
2
+ import { extractInsightParam, paramStr, typeStr } from "./agent/ui-utils.mjs";
3
+ import { ScreenshotItem } from "./screenshot-item.mjs";
4
+ import { normalizeScreenshotRef } from "./dump/screenshot-store.mjs";
5
+ function toExecutionDump(execution) {
6
+ if (!execution || 'object' != typeof execution) throw new Error('executionToMarkdown: execution is required');
7
+ if (!Array.isArray(execution.tasks)) throw new Error('executionToMarkdown: execution.tasks must be an array');
8
+ if (!execution.name) throw new Error('executionToMarkdown: execution.name is required');
9
+ return execution;
10
+ }
11
+ function toReportDump(report) {
12
+ if (!report || 'object' != typeof report) throw new Error('reportToMarkdown: report is required');
13
+ if (!Array.isArray(report.executions)) throw new Error('reportToMarkdown: report.executions must be an array');
14
+ return report;
15
+ }
16
+ function formatTime(ts) {
17
+ if ('number' != typeof ts || Number.isNaN(ts)) return 'N/A';
18
+ return new Date(ts).toISOString();
19
+ }
20
+ function resolveTaskTiming(task) {
21
+ const timing = task.timing;
22
+ if (!timing) return {};
23
+ const start = timing.start ?? timing.callAiStart ?? timing.callActionStart;
24
+ const end = timing.end ?? timing.callAiEnd ?? timing.callActionEnd ?? timing.captureAfterCallingSnapshotEnd;
25
+ const cost = timing.cost ?? ('number' == typeof start && 'number' == typeof end ? end - start : void 0);
26
+ return {
27
+ start,
28
+ end,
29
+ cost
30
+ };
31
+ }
32
+ function safeTaskParam(task) {
33
+ const readable = paramStr(task);
34
+ if (readable) return readable;
35
+ if ('Insight' === task.type) return extractInsightParam(task.param).content;
36
+ return '';
37
+ }
38
+ function formatSize(size) {
39
+ if (!size || 'number' != typeof size.width || 'number' != typeof size.height || Number.isNaN(size.width) || Number.isNaN(size.height)) return;
40
+ return `${size.width} x ${size.height}`;
41
+ }
42
+ function extractLocateCenter(task) {
43
+ const outputCenter = task.output?.element?.center;
44
+ if (Array.isArray(outputCenter) && outputCenter.length >= 2 && 'number' == typeof outputCenter[0] && 'number' == typeof outputCenter[1]) return [
45
+ outputCenter[0],
46
+ outputCenter[1]
47
+ ];
48
+ const paramLocateCenter = task.param?.locate?.center;
49
+ if (Array.isArray(paramLocateCenter) && paramLocateCenter.length >= 2 && 'number' == typeof paramLocateCenter[0] && 'number' == typeof paramLocateCenter[1]) return [
50
+ paramLocateCenter[0],
51
+ paramLocateCenter[1]
52
+ ];
53
+ const paramCenter = task.param?.center;
54
+ if (Array.isArray(paramCenter) && paramCenter.length >= 2 && 'number' == typeof paramCenter[0] && 'number' == typeof paramCenter[1]) return [
55
+ paramCenter[0],
56
+ paramCenter[1]
57
+ ];
58
+ }
59
+ function tryExtractBase64(screenshot) {
60
+ if (!screenshot || 'object' != typeof screenshot) return;
61
+ const s = screenshot;
62
+ if ('string' == typeof s.base64 && s.base64.length > 0) return s.base64;
63
+ }
64
+ function screenshotAttachment(screenshot, screenshotBaseDir, executionIndex, taskIndex) {
65
+ if (screenshot instanceof ScreenshotItem) {
66
+ const ext = screenshot.extension;
67
+ const suggestedFileName = `execution-${executionIndex + 1}-task-${taskIndex + 1}-${screenshot.id}.${ext}`;
68
+ const filePath = `${screenshotBaseDir}/${suggestedFileName}`;
69
+ return {
70
+ markdown: `\n![task-${taskIndex + 1}](${filePath})`,
71
+ attachment: {
72
+ id: screenshot.id,
73
+ suggestedFileName,
74
+ filePath,
75
+ mimeType: `image/${'jpeg' === ext ? 'jpeg' : 'png'}`,
76
+ executionIndex,
77
+ taskIndex,
78
+ base64Data: tryExtractBase64(screenshot)
79
+ }
80
+ };
81
+ }
82
+ const ref = normalizeScreenshotRef(screenshot);
83
+ if (ref) {
84
+ const ext = 'image/jpeg' === ref.mimeType ? 'jpeg' : 'png';
85
+ const suggestedFileName = `execution-${executionIndex + 1}-task-${taskIndex + 1}-${ref.id}.${ext}`;
86
+ const filePath = ref.path || `${screenshotBaseDir}/${suggestedFileName}`;
87
+ return {
88
+ markdown: `\n![task-${taskIndex + 1}](${filePath})`,
89
+ attachment: {
90
+ id: ref.id,
91
+ suggestedFileName,
92
+ filePath,
93
+ mimeType: ref.mimeType,
94
+ executionIndex,
95
+ taskIndex,
96
+ base64Data: tryExtractBase64(screenshot)
97
+ }
98
+ };
99
+ }
100
+ const base64 = tryExtractBase64(screenshot);
101
+ if (base64) {
102
+ const ext = base64.startsWith('data:image/jpeg') ? 'jpeg' : 'png';
103
+ const id = `restored-${executionIndex + 1}-${taskIndex + 1}`;
104
+ const suggestedFileName = `execution-${executionIndex + 1}-task-${taskIndex + 1}-${id}.${ext}`;
105
+ const filePath = `${screenshotBaseDir}/${suggestedFileName}`;
106
+ return {
107
+ markdown: `\n![task-${taskIndex + 1}](${filePath})`,
108
+ attachment: {
109
+ id,
110
+ suggestedFileName,
111
+ filePath,
112
+ mimeType: `image/${ext}`,
113
+ executionIndex,
114
+ taskIndex,
115
+ base64Data: base64
116
+ }
117
+ };
118
+ }
119
+ throw new Error(`executionToMarkdown: missing screenshot for execution #${executionIndex + 1} task #${taskIndex + 1}`);
120
+ }
121
+ function recorderMarkdownSection(recorder, screenshotBaseDir, executionIndex, taskIndex) {
122
+ if (!recorder?.length) return {
123
+ lines: [],
124
+ attachments: []
125
+ };
126
+ const lines = [
127
+ '',
128
+ '### Recorder'
129
+ ];
130
+ const attachments = [];
131
+ recorder.forEach((item, recorderIndex)=>{
132
+ lines.push(`- #${recorderIndex + 1} type=${item.type}, ts=${formatTime(item.ts)}, timing=${item.timing || 'N/A'}`);
133
+ if (!item.screenshot) return;
134
+ const imageResult = screenshotAttachment(item.screenshot, screenshotBaseDir, executionIndex, taskIndex);
135
+ lines.push(imageResult.markdown);
136
+ attachments.push(imageResult.attachment);
137
+ });
138
+ return {
139
+ lines,
140
+ attachments
141
+ };
142
+ }
143
+ function renderExecution(executionRaw, executionIndex, options) {
144
+ const execution = toExecutionDump(executionRaw);
145
+ const screenshotBaseDir = options?.screenshotBaseDir ?? './screenshots';
146
+ const lines = [];
147
+ const attachments = [];
148
+ lines.push(`# ${execution.name}`);
149
+ if (execution.description) lines.push('', execution.description);
150
+ lines.push('', `- Execution start: ${formatTime(execution.logTime)}`);
151
+ lines.push(`- Task count: ${execution.tasks.length}`);
152
+ execution.tasks.forEach((task, taskIndex)=>{
153
+ const title = typeStr(task);
154
+ const detail = safeTaskParam(task);
155
+ const time = resolveTaskTiming(task);
156
+ lines.push('', `## ${taskIndex + 1}. ${title}${detail ? ` - ${detail}` : ''}`);
157
+ lines.push(`- Status: ${task.status || 'unknown'}`);
158
+ lines.push(`- Start: ${formatTime(time.start)}`);
159
+ lines.push(`- End: ${formatTime(time.end)}`);
160
+ lines.push(`- Cost(ms): ${'number' == typeof time.cost ? time.cost : 'N/A'}`);
161
+ lines.push(`- Screen size: ${formatSize(task.uiContext?.shotSize) || 'N/A'}`);
162
+ if ('Locate' === task.subType) {
163
+ const locateCenter = extractLocateCenter(task);
164
+ if (locateCenter) lines.push(`- Locate center: (${locateCenter[0]}, ${locateCenter[1]})`);
165
+ }
166
+ if (task.errorMessage) lines.push(`- Error: ${task.errorMessage}`);
167
+ if (task.uiContext?.screenshot) {
168
+ const imageResult = screenshotAttachment(task.uiContext.screenshot, screenshotBaseDir, executionIndex, taskIndex);
169
+ lines.push(imageResult.markdown);
170
+ attachments.push(imageResult.attachment);
171
+ }
172
+ const recorderSection = recorderMarkdownSection(task.recorder, screenshotBaseDir, executionIndex, taskIndex);
173
+ if (recorderSection.lines.length) {
174
+ lines.push(...recorderSection.lines);
175
+ attachments.push(...recorderSection.attachments);
176
+ }
177
+ });
178
+ return {
179
+ markdown: lines.join('\n'),
180
+ attachments
181
+ };
182
+ }
183
+ function reportFileName(execution, executionIndex) {
184
+ const safeName = execution.name.trim().replace(/\s+/g, '-').replace(/[^a-zA-Z0-9-_]/g, '') || `execution-${executionIndex + 1}`;
185
+ return `${executionIndex + 1}-${basename(safeName)}.md`;
186
+ }
187
+ function executionToMarkdown(execution, options) {
188
+ return renderExecution(execution, 0, options);
189
+ }
190
+ function reportToMarkdown(report) {
191
+ const reportDump = toReportDump(report);
192
+ const executionResults = reportDump.executions.map((execution, index)=>{
193
+ const rendered = renderExecution(execution, index);
194
+ return {
195
+ executionIndex: index,
196
+ executionName: execution.name,
197
+ markdown: rendered.markdown,
198
+ attachments: rendered.attachments,
199
+ suggestedFileName: reportFileName(execution, index)
200
+ };
201
+ });
202
+ const attachments = executionResults.flatMap((item)=>item.attachments);
203
+ const header = [
204
+ `# ${reportDump.groupName}`,
205
+ reportDump.groupDescription ? `\n${reportDump.groupDescription}` : '',
206
+ `\n- SDK Version: ${reportDump.sdkVersion}`,
207
+ `- Execution count: ${reportDump.executions.length}`,
208
+ '\n## Suggested execution markdown files',
209
+ ...executionResults.map((item)=>`- ${item.suggestedFileName} (${item.executionName})`)
210
+ ].filter(Boolean).join('\n');
211
+ return {
212
+ markdown: `${header}\n\n${executionResults.map((item)=>item.markdown).join('\n\n---\n\n')}`,
213
+ attachments
214
+ };
215
+ }
216
+ export { executionToMarkdown, reportToMarkdown };
217
+
218
+ //# sourceMappingURL=report-markdown.mjs.map