@midscene/core 1.10.2-beta-20260706032158.0 → 1.10.3

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 (161) hide show
  1. package/dist/es/agent/agent.mjs +40 -1
  2. package/dist/es/agent/agent.mjs.map +1 -1
  3. package/dist/es/agent/metrics.mjs +81 -0
  4. package/dist/es/agent/metrics.mjs.map +1 -0
  5. package/dist/es/agent/task-builder.mjs +1 -1
  6. package/dist/es/agent/task-builder.mjs.map +1 -1
  7. package/dist/es/agent/utils.mjs +1 -1
  8. package/dist/es/ai-model/connectivity/run-connectivity-test.mjs +9 -3
  9. package/dist/es/ai-model/connectivity/run-connectivity-test.mjs.map +1 -1
  10. package/dist/es/ai-model/inspect.mjs +3 -3
  11. package/dist/es/ai-model/inspect.mjs.map +1 -1
  12. package/dist/es/ai-model/llm-planning.mjs +1 -1
  13. package/dist/es/ai-model/llm-planning.mjs.map +1 -1
  14. package/dist/es/ai-model/model-adapter/resolve.mjs +2 -2
  15. package/dist/es/ai-model/model-adapter/resolve.mjs.map +1 -1
  16. package/dist/es/ai-model/models/default.mjs +2 -1
  17. package/dist/es/ai-model/models/default.mjs.map +1 -1
  18. package/dist/es/ai-model/models/doubao.mjs +38 -60
  19. package/dist/es/ai-model/models/doubao.mjs.map +1 -1
  20. package/dist/es/ai-model/models/gemini.mjs +4 -15
  21. package/dist/es/ai-model/models/gemini.mjs.map +1 -1
  22. package/dist/es/ai-model/models/glm.mjs +4 -0
  23. package/dist/es/ai-model/models/glm.mjs.map +1 -1
  24. package/dist/es/ai-model/models/gpt.mjs +5 -1
  25. package/dist/es/ai-model/models/gpt.mjs.map +1 -1
  26. package/dist/es/ai-model/models/kimi.mjs +4 -0
  27. package/dist/es/ai-model/models/kimi.mjs.map +1 -1
  28. package/dist/es/ai-model/models/mimo.mjs +3 -2
  29. package/dist/es/ai-model/models/mimo.mjs.map +1 -1
  30. package/dist/es/ai-model/models/qwen.mjs.map +1 -1
  31. package/dist/es/ai-model/models/ui-tars/adapter.mjs +2 -41
  32. package/dist/es/ai-model/models/ui-tars/adapter.mjs.map +1 -1
  33. package/dist/es/ai-model/models/utils/intent.mjs +6 -0
  34. package/dist/es/ai-model/models/utils/intent.mjs.map +1 -0
  35. package/dist/es/ai-model/prompt/describe.mjs +31 -34
  36. package/dist/es/ai-model/prompt/describe.mjs.map +1 -1
  37. package/dist/es/ai-model/prompt/extraction.mjs +15 -9
  38. package/dist/es/ai-model/prompt/extraction.mjs.map +1 -1
  39. package/dist/es/ai-model/prompt/llm-planning.mjs +19 -19
  40. package/dist/es/ai-model/prompt/llm-planning.mjs.map +1 -1
  41. package/dist/es/ai-model/prompt/locate-grounding-rules.mjs +7 -3
  42. package/dist/es/ai-model/prompt/locate-grounding-rules.mjs.map +1 -1
  43. package/dist/es/ai-model/prompt/recorder-metadata-generator.mjs +2 -1
  44. package/dist/es/ai-model/prompt/recorder-metadata-generator.mjs.map +1 -1
  45. package/dist/es/ai-model/service-caller/index.mjs +40 -19
  46. package/dist/es/ai-model/service-caller/index.mjs.map +1 -1
  47. package/dist/es/ai-model/service-caller/json.mjs +31 -28
  48. package/dist/es/ai-model/service-caller/json.mjs.map +1 -1
  49. package/dist/es/device/index.mjs +2 -4
  50. package/dist/es/device/index.mjs.map +1 -1
  51. package/dist/es/dump/html-utils.mjs +63 -1
  52. package/dist/es/dump/html-utils.mjs.map +1 -1
  53. package/dist/es/dump/screenshot-restoration.mjs +6 -0
  54. package/dist/es/dump/screenshot-restoration.mjs.map +1 -1
  55. package/dist/es/element-describer.mjs +36 -18
  56. package/dist/es/element-describer.mjs.map +1 -1
  57. package/dist/es/index.mjs +2 -2
  58. package/dist/es/index.mjs.map +1 -1
  59. package/dist/es/report-generator.mjs +28 -1
  60. package/dist/es/report-generator.mjs.map +1 -1
  61. package/dist/es/report-markdown.mjs +300 -35
  62. package/dist/es/report-markdown.mjs.map +1 -1
  63. package/dist/es/report.mjs +29 -1
  64. package/dist/es/report.mjs.map +1 -1
  65. package/dist/es/service/index.mjs +101 -25
  66. package/dist/es/service/index.mjs.map +1 -1
  67. package/dist/es/service/utils.mjs +65 -1
  68. package/dist/es/service/utils.mjs.map +1 -1
  69. package/dist/es/types.mjs.map +1 -1
  70. package/dist/es/utils.mjs +2 -2
  71. package/dist/lib/agent/agent.js +40 -1
  72. package/dist/lib/agent/agent.js.map +1 -1
  73. package/dist/lib/agent/metrics.js +115 -0
  74. package/dist/lib/agent/metrics.js.map +1 -0
  75. package/dist/lib/agent/task-builder.js +1 -1
  76. package/dist/lib/agent/task-builder.js.map +1 -1
  77. package/dist/lib/agent/utils.js +1 -1
  78. package/dist/lib/ai-model/connectivity/run-connectivity-test.js +9 -3
  79. package/dist/lib/ai-model/connectivity/run-connectivity-test.js.map +1 -1
  80. package/dist/lib/ai-model/inspect.js +3 -3
  81. package/dist/lib/ai-model/inspect.js.map +1 -1
  82. package/dist/lib/ai-model/llm-planning.js +1 -1
  83. package/dist/lib/ai-model/llm-planning.js.map +1 -1
  84. package/dist/lib/ai-model/model-adapter/resolve.js +1 -1
  85. package/dist/lib/ai-model/model-adapter/resolve.js.map +1 -1
  86. package/dist/lib/ai-model/models/default.js +2 -1
  87. package/dist/lib/ai-model/models/default.js.map +1 -1
  88. package/dist/lib/ai-model/models/doubao.js +38 -69
  89. package/dist/lib/ai-model/models/doubao.js.map +1 -1
  90. package/dist/lib/ai-model/models/gemini.js +4 -15
  91. package/dist/lib/ai-model/models/gemini.js.map +1 -1
  92. package/dist/lib/ai-model/models/glm.js +4 -0
  93. package/dist/lib/ai-model/models/glm.js.map +1 -1
  94. package/dist/lib/ai-model/models/gpt.js +5 -1
  95. package/dist/lib/ai-model/models/gpt.js.map +1 -1
  96. package/dist/lib/ai-model/models/kimi.js +4 -0
  97. package/dist/lib/ai-model/models/kimi.js.map +1 -1
  98. package/dist/lib/ai-model/models/mimo.js +3 -2
  99. package/dist/lib/ai-model/models/mimo.js.map +1 -1
  100. package/dist/lib/ai-model/models/qwen.js.map +1 -1
  101. package/dist/lib/ai-model/models/ui-tars/adapter.js +1 -40
  102. package/dist/lib/ai-model/models/ui-tars/adapter.js.map +1 -1
  103. package/dist/lib/ai-model/models/utils/intent.js +40 -0
  104. package/dist/lib/ai-model/models/utils/intent.js.map +1 -0
  105. package/dist/lib/ai-model/prompt/describe.js +31 -34
  106. package/dist/lib/ai-model/prompt/describe.js.map +1 -1
  107. package/dist/lib/ai-model/prompt/extraction.js +14 -8
  108. package/dist/lib/ai-model/prompt/extraction.js.map +1 -1
  109. package/dist/lib/ai-model/prompt/llm-planning.js +19 -19
  110. package/dist/lib/ai-model/prompt/llm-planning.js.map +1 -1
  111. package/dist/lib/ai-model/prompt/locate-grounding-rules.js +7 -3
  112. package/dist/lib/ai-model/prompt/locate-grounding-rules.js.map +1 -1
  113. package/dist/lib/ai-model/prompt/recorder-metadata-generator.js +4 -3
  114. package/dist/lib/ai-model/prompt/recorder-metadata-generator.js.map +1 -1
  115. package/dist/lib/ai-model/service-caller/index.js +44 -23
  116. package/dist/lib/ai-model/service-caller/index.js.map +1 -1
  117. package/dist/lib/ai-model/service-caller/json.js +33 -33
  118. package/dist/lib/ai-model/service-caller/json.js.map +1 -1
  119. package/dist/lib/device/index.js +2 -4
  120. package/dist/lib/device/index.js.map +1 -1
  121. package/dist/lib/dump/html-utils.js +65 -0
  122. package/dist/lib/dump/html-utils.js.map +1 -1
  123. package/dist/lib/dump/screenshot-restoration.js +6 -0
  124. package/dist/lib/dump/screenshot-restoration.js.map +1 -1
  125. package/dist/lib/element-describer.js +37 -22
  126. package/dist/lib/element-describer.js.map +1 -1
  127. package/dist/lib/index.js +1 -4
  128. package/dist/lib/index.js.map +1 -1
  129. package/dist/lib/report-generator.js +27 -0
  130. package/dist/lib/report-generator.js.map +1 -1
  131. package/dist/lib/report-markdown.js +300 -35
  132. package/dist/lib/report-markdown.js.map +1 -1
  133. package/dist/lib/report.js +28 -0
  134. package/dist/lib/report.js.map +1 -1
  135. package/dist/lib/service/index.js +99 -23
  136. package/dist/lib/service/index.js.map +1 -1
  137. package/dist/lib/service/utils.js +98 -1
  138. package/dist/lib/service/utils.js.map +1 -1
  139. package/dist/lib/types.js.map +1 -1
  140. package/dist/lib/utils.js +2 -2
  141. package/dist/types/agent/agent.d.ts +15 -2
  142. package/dist/types/agent/index.d.ts +1 -0
  143. package/dist/types/agent/metrics.d.ts +48 -0
  144. package/dist/types/ai-model/model-adapter/types.d.ts +8 -0
  145. package/dist/types/ai-model/models/doubao.d.ts +1 -4
  146. package/dist/types/ai-model/models/gemini.d.ts +1 -1
  147. package/dist/types/ai-model/models/registry.d.ts +1 -1
  148. package/dist/types/ai-model/models/utils/intent.d.ts +2 -0
  149. package/dist/types/ai-model/service-caller/index.d.ts +8 -3
  150. package/dist/types/ai-model/service-caller/json.d.ts +19 -2
  151. package/dist/types/device/device-options.d.ts +0 -28
  152. package/dist/types/device/index.d.ts +0 -5
  153. package/dist/types/dump/html-utils.d.ts +2 -0
  154. package/dist/types/dump/screenshot-restoration.d.ts +1 -1
  155. package/dist/types/element-describer.d.ts +3 -6
  156. package/dist/types/index.d.ts +2 -2
  157. package/dist/types/report-generator.d.ts +6 -0
  158. package/dist/types/service/index.d.ts +1 -1
  159. package/dist/types/service/utils.d.ts +21 -2
  160. package/dist/types/types.d.ts +12 -2
  161. package/package.json +3 -3
@@ -1 +1 @@
1
- {"version":3,"file":"report-markdown.mjs","sources":["../../src/report-markdown.ts"],"sourcesContent":["import { basename } from 'node:path';\nimport { extractInsightParam, paramStr, typeStr } from '@/agent/ui-utils';\nimport { ScreenshotItem } from '@/screenshot-item';\nimport type {\n ExecutionDump,\n ExecutionRecorderItem,\n ExecutionTask,\n IExecutionDump,\n IReportActionDump,\n ReportActionDump,\n} from '@/types';\nimport type { ScreenshotRef } from './dump/screenshot-store';\nimport { normalizeScreenshotRef } from './dump/screenshot-store';\n\nexport interface MarkdownAttachment {\n id: string;\n /**\n * Stable, prefixed file name for the exported copy. The markdown image link\n * always points at `${screenshotBaseDir}/${suggestedFileName}`, so consumers\n * write the screenshot under this name to keep links in sync. See #2392.\n */\n suggestedFileName: string;\n mimeType?: string;\n /**\n * Reference to the screenshot in the source report, used to locate the\n * original bytes when copying them to the exported name. Absent for in-memory\n * screenshots, which carry their data in `base64Data` instead.\n */\n sourceRef?: ScreenshotRef;\n executionIndex: number;\n taskIndex: number;\n /** Populated when screenshot data is available in memory (e.g. browser context). */\n base64Data?: string;\n}\n\nexport interface ExecutionMarkdownOptions {\n screenshotBaseDir?: string;\n}\n\nexport interface ExecutionMarkdownResult {\n markdown: string;\n attachments: MarkdownAttachment[];\n}\n\nexport interface ReportMarkdownResult {\n markdown: string;\n attachments: MarkdownAttachment[];\n}\n\nfunction toExecutionDump(\n execution: ExecutionDump | IExecutionDump,\n): IExecutionDump {\n if (!execution || typeof execution !== 'object') {\n throw new Error('executionToMarkdown: execution is required');\n }\n\n if (!Array.isArray(execution.tasks)) {\n throw new Error('executionToMarkdown: execution.tasks must be an array');\n }\n\n if (!execution.name) {\n throw new Error('executionToMarkdown: execution.name is required');\n }\n\n return execution;\n}\n\nfunction toReportDump(\n report: ReportActionDump | IReportActionDump,\n): IReportActionDump {\n if (!report || typeof report !== 'object') {\n throw new Error('reportToMarkdown: report is required');\n }\n\n if (!Array.isArray(report.executions)) {\n throw new Error('reportToMarkdown: report.executions must be an array');\n }\n\n return report;\n}\n\nfunction formatTime(ts?: number): string {\n if (typeof ts !== 'number' || Number.isNaN(ts)) {\n return 'N/A';\n }\n return new Date(ts).toISOString();\n}\n\nfunction resolveTaskTiming(task: ExecutionTask): {\n start?: number;\n end?: number;\n cost?: number;\n} {\n const timing = task.timing;\n if (!timing) {\n return {};\n }\n\n const start = timing.start ?? timing.callAiStart ?? timing.callActionStart;\n const end =\n timing.end ??\n timing.callAiEnd ??\n timing.callActionEnd ??\n timing.captureAfterCallingSnapshotEnd;\n const cost =\n timing.cost ??\n (typeof start === 'number' && typeof end === 'number'\n ? end - start\n : undefined);\n\n return { start, end, cost };\n}\n\nfunction safeTaskParam(task: ExecutionTask): string {\n const readable = paramStr(task);\n if (readable) {\n return readable;\n }\n\n if (task.type === 'Insight') {\n return extractInsightParam((task as any).param).content;\n }\n\n return '';\n}\n\nfunction formatSize(\n size?: { width?: number; height?: number } | null,\n): string | undefined {\n if (\n !size ||\n typeof size.width !== 'number' ||\n typeof size.height !== 'number' ||\n Number.isNaN(size.width) ||\n Number.isNaN(size.height)\n ) {\n return undefined;\n }\n\n return `${size.width} x ${size.height}`;\n}\n\nfunction extractLocateCenter(\n task: ExecutionTask,\n): [number, number] | undefined {\n const outputCenter = (task.output as { element?: { center?: unknown } })\n ?.element?.center;\n if (\n Array.isArray(outputCenter) &&\n outputCenter.length >= 2 &&\n typeof outputCenter[0] === 'number' &&\n typeof outputCenter[1] === 'number'\n ) {\n return [outputCenter[0], outputCenter[1]];\n }\n\n const paramLocateCenter = (task.param as { locate?: { center?: unknown } })\n ?.locate?.center;\n if (\n Array.isArray(paramLocateCenter) &&\n paramLocateCenter.length >= 2 &&\n typeof paramLocateCenter[0] === 'number' &&\n typeof paramLocateCenter[1] === 'number'\n ) {\n return [paramLocateCenter[0], paramLocateCenter[1]];\n }\n\n const paramCenter = (task.param as { center?: unknown })?.center;\n if (\n Array.isArray(paramCenter) &&\n paramCenter.length >= 2 &&\n typeof paramCenter[0] === 'number' &&\n typeof paramCenter[1] === 'number'\n ) {\n return [paramCenter[0], paramCenter[1]];\n }\n\n return undefined;\n}\n\nfunction tryExtractBase64(screenshot: unknown): string | undefined {\n if (!screenshot || typeof screenshot !== 'object') return undefined;\n const s = screenshot as Record<string, unknown>;\n if (typeof s.base64 === 'string' && s.base64.length > 0) {\n return s.base64;\n }\n return undefined;\n}\n\nfunction screenshotAttachment(\n screenshot: unknown,\n screenshotBaseDir: string,\n executionIndex: number,\n taskIndex: number,\n): { markdown: string; attachment: MarkdownAttachment } {\n if (screenshot instanceof ScreenshotItem) {\n const ext = screenshot.extension;\n const suggestedFileName = `execution-${executionIndex + 1}-task-${taskIndex + 1}-${screenshot.id}.${ext}`;\n return {\n markdown: `\\n![task-${taskIndex + 1}](${screenshotBaseDir}/${suggestedFileName})`,\n attachment: {\n id: screenshot.id,\n suggestedFileName,\n mimeType: `image/${ext === 'jpeg' ? 'jpeg' : 'png'}`,\n executionIndex,\n taskIndex,\n base64Data: tryExtractBase64(screenshot),\n },\n };\n }\n\n const ref = normalizeScreenshotRef(screenshot);\n if (ref) {\n const ext = ref.mimeType === 'image/jpeg' ? 'jpeg' : 'png';\n const suggestedFileName = `execution-${executionIndex + 1}-task-${taskIndex + 1}-${ref.id}.${ext}`;\n return {\n markdown: `\\n![task-${taskIndex + 1}](${screenshotBaseDir}/${suggestedFileName})`,\n attachment: {\n id: ref.id,\n suggestedFileName,\n sourceRef: ref,\n mimeType: ref.mimeType,\n executionIndex,\n taskIndex,\n base64Data: tryExtractBase64(screenshot),\n },\n };\n }\n\n const base64 = tryExtractBase64(screenshot);\n if (base64) {\n const ext = base64.startsWith('data:image/jpeg') ? 'jpeg' : 'png';\n const id = `restored-${executionIndex + 1}-${taskIndex + 1}`;\n const suggestedFileName = `execution-${executionIndex + 1}-task-${taskIndex + 1}-${id}.${ext}`;\n return {\n markdown: `\\n![task-${taskIndex + 1}](${screenshotBaseDir}/${suggestedFileName})`,\n attachment: {\n id,\n suggestedFileName,\n mimeType: `image/${ext}`,\n executionIndex,\n taskIndex,\n base64Data: base64,\n },\n };\n }\n\n throw new Error(\n `executionToMarkdown: missing screenshot for execution #${executionIndex + 1} task #${taskIndex + 1}`,\n );\n}\n\nfunction recorderMarkdownSection(\n recorder: ExecutionRecorderItem[] | undefined,\n screenshotBaseDir: string,\n executionIndex: number,\n taskIndex: number,\n): { lines: string[]; attachments: MarkdownAttachment[] } {\n if (!recorder?.length) {\n return { lines: [], attachments: [] };\n }\n\n const lines: string[] = ['', '### Recorder'];\n const attachments: MarkdownAttachment[] = [];\n\n recorder.forEach((item, recorderIndex) => {\n const descriptionText = item.description\n ? `, description=${item.description}`\n : '';\n lines.push(\n `- #${recorderIndex + 1} type=${item.type}, ts=${formatTime(item.ts)}, timing=${item.timing || 'N/A'}${descriptionText}`,\n );\n\n if (!item.screenshot) {\n return;\n }\n\n const imageResult = screenshotAttachment(\n item.screenshot,\n screenshotBaseDir,\n executionIndex,\n taskIndex,\n );\n\n lines.push(imageResult.markdown);\n attachments.push(imageResult.attachment);\n });\n\n return { lines, attachments };\n}\n\nfunction renderExecution(\n executionRaw: ExecutionDump | IExecutionDump,\n executionIndex: number,\n options?: ExecutionMarkdownOptions,\n): ExecutionMarkdownResult {\n const execution = toExecutionDump(executionRaw);\n const screenshotBaseDir = options?.screenshotBaseDir ?? './screenshots';\n\n const lines: string[] = [];\n const attachments: MarkdownAttachment[] = [];\n\n lines.push(`# ${execution.name}`);\n if (execution.description) {\n lines.push('', execution.description);\n }\n\n lines.push('', `- Execution start: ${formatTime(execution.logTime)}`);\n lines.push(`- Task count: ${execution.tasks.length}`);\n\n execution.tasks.forEach((task, taskIndex) => {\n const title = typeStr(task);\n const detail = safeTaskParam(task);\n const time = resolveTaskTiming(task);\n\n lines.push(\n '',\n `## ${taskIndex + 1}. ${title}${detail ? ` - ${detail}` : ''}`,\n );\n lines.push(`- Status: ${task.status || 'unknown'}`);\n lines.push(`- Start: ${formatTime(time.start)}`);\n lines.push(`- End: ${formatTime(time.end)}`);\n lines.push(\n `- Cost(ms): ${typeof time.cost === 'number' ? time.cost : 'N/A'}`,\n );\n lines.push(\n `- Screen size: ${formatSize(task.uiContext?.shotSize) || 'N/A'}`,\n );\n\n if (task.subType === 'Locate') {\n const locateCenter = extractLocateCenter(task);\n if (locateCenter) {\n lines.push(`- Locate center: (${locateCenter[0]}, ${locateCenter[1]})`);\n }\n }\n\n if (task.errorMessage) {\n lines.push(`- Error: ${task.errorMessage}`);\n }\n\n if (task.uiContext?.screenshot) {\n const imageResult = screenshotAttachment(\n task.uiContext.screenshot,\n screenshotBaseDir,\n executionIndex,\n taskIndex,\n );\n\n lines.push(imageResult.markdown);\n attachments.push(imageResult.attachment);\n }\n\n const recorderSection = recorderMarkdownSection(\n task.recorder,\n screenshotBaseDir,\n executionIndex,\n taskIndex,\n );\n if (recorderSection.lines.length) {\n lines.push(...recorderSection.lines);\n attachments.push(...recorderSection.attachments);\n }\n });\n\n return {\n markdown: lines.join('\\n'),\n attachments,\n };\n}\n\nfunction reportFileName(\n execution: IExecutionDump,\n executionIndex: number,\n): string {\n const safeName =\n execution.name\n .trim()\n .replace(/\\s+/g, '-')\n .replace(/[^a-zA-Z0-9-_]/g, '') || `execution-${executionIndex + 1}`;\n return `${executionIndex + 1}-${basename(safeName)}.md`;\n}\n\nexport function executionToMarkdown(\n execution: ExecutionDump | IExecutionDump,\n options?: ExecutionMarkdownOptions,\n): ExecutionMarkdownResult {\n return renderExecution(execution, 0, options);\n}\n\nexport function reportToMarkdown(\n report: ReportActionDump | IReportActionDump,\n): ReportMarkdownResult {\n const reportDump = toReportDump(report);\n\n const executionResults = reportDump.executions.map((execution, index) => {\n const rendered = renderExecution(execution, index);\n return {\n executionIndex: index,\n executionName: execution.name,\n markdown: rendered.markdown,\n attachments: rendered.attachments,\n suggestedFileName: reportFileName(execution, index),\n };\n });\n\n const attachments = executionResults.flatMap((item) => item.attachments);\n\n const header = [\n `# ${reportDump.groupName}`,\n reportDump.groupDescription ? `\\n${reportDump.groupDescription}` : '',\n `\\n- SDK Version: ${reportDump.sdkVersion}`,\n `- Execution count: ${reportDump.executions.length}`,\n '\\n## Suggested execution markdown files',\n ...executionResults.map(\n (item) => `- ${item.suggestedFileName} (${item.executionName})`,\n ),\n ]\n .filter(Boolean)\n .join('\\n');\n\n return {\n markdown: `${header}\\n\\n${executionResults.map((item) => item.markdown).join('\\n\\n---\\n\\n')}`,\n attachments,\n };\n}\n"],"names":["toExecutionDump","execution","Error","Array","toReportDump","report","formatTime","ts","Number","Date","resolveTaskTiming","task","timing","start","end","cost","undefined","safeTaskParam","readable","paramStr","extractInsightParam","formatSize","size","extractLocateCenter","outputCenter","paramLocateCenter","paramCenter","tryExtractBase64","screenshot","s","screenshotAttachment","screenshotBaseDir","executionIndex","taskIndex","ScreenshotItem","ext","suggestedFileName","ref","normalizeScreenshotRef","base64","id","recorderMarkdownSection","recorder","lines","attachments","item","recorderIndex","descriptionText","imageResult","renderExecution","executionRaw","options","title","typeStr","detail","time","locateCenter","recorderSection","reportFileName","safeName","basename","executionToMarkdown","reportToMarkdown","reportDump","executionResults","index","rendered","header","Boolean"],"mappings":"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"}
1
+ {"version":3,"file":"report-markdown.mjs","sources":["../../src/report-markdown.ts"],"sourcesContent":["import { extractInsightParam, paramStr, typeStr } from '@/agent/ui-utils';\nimport { ScreenshotItem } from '@/screenshot-item';\nimport type {\n AIUsageInfo,\n ExecutionDump,\n ExecutionTask,\n IExecutionDump,\n IReportActionDump,\n ModelBrief,\n ReportActionDump,\n} from '@/types';\nimport type { ScreenshotRef } from './dump/screenshot-store';\nimport { normalizeScreenshotRef } from './dump/screenshot-store';\n\nconst screenshotDataUrlPattern =\n /^data:image\\/(png|jpeg|jpg);base64,([\\s\\S]*)$/i;\nconst rawBase64BodyPattern = /^[a-zA-Z0-9+/=\\s]+$/;\nconst jsonContextMaxStringLength = 12_000;\n\ntype ExecutionTaskWithExtraUsage = ExecutionTask & {\n searchAreaUsage?: AIUsageInfo;\n reasoning_content?: string;\n};\n\ninterface UsageTotals {\n calls: number;\n prompt: number;\n cachedInput: number;\n completion: number;\n total: number;\n timeCost: number;\n}\n\nexport interface MarkdownAttachment {\n id: string;\n /**\n * Stable, prefixed file name for the exported copy. The markdown image link\n * always points at `${screenshotBaseDir}/${suggestedFileName}`, so consumers\n * write the screenshot under this name to keep links in sync. See #2392.\n */\n suggestedFileName: string;\n mimeType?: string;\n /**\n * Reference to the screenshot in the source report, used to locate the\n * original bytes when copying them to the exported name. Absent for in-memory\n * screenshots, which carry their data in `base64Data` instead.\n */\n sourceRef?: ScreenshotRef;\n executionIndex: number;\n taskIndex: number;\n /** Populated when screenshot data is available in memory (e.g. browser context). */\n base64Data?: string;\n}\n\nexport interface ExecutionMarkdownOptions {\n screenshotBaseDir?: string;\n}\n\nexport interface ExecutionMarkdownResult {\n markdown: string;\n attachments: MarkdownAttachment[];\n}\n\nexport interface ReportMarkdownResult {\n markdown: string;\n attachments: MarkdownAttachment[];\n}\n\nfunction toExecutionDump(\n execution: ExecutionDump | IExecutionDump,\n): IExecutionDump {\n if (!execution || typeof execution !== 'object') {\n throw new Error('executionToMarkdown: execution is required');\n }\n\n if (!Array.isArray(execution.tasks)) {\n throw new Error('executionToMarkdown: execution.tasks must be an array');\n }\n\n if (!execution.name) {\n throw new Error('executionToMarkdown: execution.name is required');\n }\n\n return execution;\n}\n\nfunction toReportDump(\n report: ReportActionDump | IReportActionDump,\n): IReportActionDump {\n if (!report || typeof report !== 'object') {\n throw new Error('reportToMarkdown: report is required');\n }\n\n if (!Array.isArray(report.executions)) {\n throw new Error('reportToMarkdown: report.executions must be an array');\n }\n\n return report;\n}\n\nfunction formatTime(ts?: number): string {\n if (typeof ts !== 'number' || Number.isNaN(ts)) {\n return 'N/A';\n }\n return new Date(ts).toISOString();\n}\n\nfunction formatValue(value: unknown): string {\n if (value === undefined || value === null || value === '') {\n return 'N/A';\n }\n if (typeof value === 'number') {\n return Number.isFinite(value) ? String(value) : 'N/A';\n }\n return String(value);\n}\n\nfunction escapeTableCell(value: unknown): string {\n return formatValue(value).replace(/\\|/g, '\\\\|').replace(/\\n/g, '<br>');\n}\n\nfunction markdownTable(headers: string[], rows: unknown[][]): string[] {\n return [\n `| ${headers.map(escapeTableCell).join(' | ')} |`,\n `| ${headers.map(() => '---').join(' | ')} |`,\n ...rows.map((row) => `| ${row.map(escapeTableCell).join(' | ')} |`),\n ];\n}\n\nfunction modelBriefRows(modelBriefs: ModelBrief[]): unknown[][] {\n return modelBriefs.map((brief) => [\n brief.intent || 'default',\n brief.name || 'N/A',\n brief.modelDescription || 'N/A',\n ]);\n}\n\nfunction modelRowsFromUsage(report: IReportActionDump): unknown[][] {\n const rows = new Map<string, unknown[]>();\n const addUsage = (source: string, usage?: AIUsageInfo) => {\n if (!hasUsage(usage)) return;\n const intent = usage.intent || source;\n const model = usage.model_name || usage.response_model_name;\n const description = usage.model_description;\n const key = `${intent}\\0${model || ''}\\0${description || ''}`;\n if (rows.has(key)) {\n return;\n }\n rows.set(key, [intent, model || 'N/A', description || 'N/A']);\n };\n\n for (const execution of report.executions) {\n for (const task of execution.tasks) {\n const taskWithUsage = task as ExecutionTaskWithExtraUsage;\n addUsage('main', taskWithUsage.usage);\n addUsage('searchArea', taskWithUsage.searchAreaUsage);\n }\n }\n\n return Array.from(rows.values());\n}\n\nfunction usageValue(usage: AIUsageInfo | undefined, key: string): number {\n const value = usage?.[key];\n return typeof value === 'number' && Number.isFinite(value) ? value : 0;\n}\n\nfunction hasUsage(usage: AIUsageInfo | undefined): usage is AIUsageInfo {\n if (!usage) return false;\n return [\n 'intent',\n 'model_name',\n 'model_description',\n 'prompt_tokens',\n 'cached_input',\n 'completion_tokens',\n 'total_tokens',\n 'time_cost',\n 'request_id',\n ].some((key) => usage[key] !== undefined && usage[key] !== null);\n}\n\nfunction usageTotalTokens(usage: AIUsageInfo): number {\n const explicitTotal = usageValue(usage, 'total_tokens');\n if (explicitTotal > 0) {\n return explicitTotal;\n }\n return (\n usageValue(usage, 'prompt_tokens') + usageValue(usage, 'completion_tokens')\n );\n}\n\nfunction usageModelName(usage: AIUsageInfo): string {\n return (\n usage.model_name ||\n usage.response_model_name ||\n usage.model_description ||\n usage.intent ||\n 'Unknown'\n );\n}\n\nfunction usageRowsForTask(task: ExecutionTask): unknown[][] {\n const taskWithUsage = task as ExecutionTaskWithExtraUsage;\n const rows: unknown[][] = [];\n const appendUsage = (source: string, usage?: AIUsageInfo) => {\n if (!hasUsage(usage)) return;\n rows.push([\n source,\n usage.intent,\n usage.model_name || usage.response_model_name,\n usage.model_description,\n usageValue(usage, 'prompt_tokens'),\n usageValue(usage, 'cached_input'),\n usageValue(usage, 'completion_tokens'),\n usageTotalTokens(usage),\n usageValue(usage, 'time_cost'),\n usage.request_id,\n ]);\n };\n\n appendUsage('main', taskWithUsage.usage);\n appendUsage('searchArea', taskWithUsage.searchAreaUsage);\n return rows;\n}\n\nfunction collectUsageTotals(\n report: IReportActionDump,\n): Map<string, UsageTotals> {\n const totalsByModel = new Map<string, UsageTotals>();\n const addUsage = (usage?: AIUsageInfo) => {\n if (!hasUsage(usage)) return;\n const modelName = usageModelName(usage);\n const current = totalsByModel.get(modelName) ?? {\n calls: 0,\n prompt: 0,\n cachedInput: 0,\n completion: 0,\n total: 0,\n timeCost: 0,\n };\n\n totalsByModel.set(modelName, {\n calls: current.calls + 1,\n prompt: current.prompt + usageValue(usage, 'prompt_tokens'),\n cachedInput: current.cachedInput + usageValue(usage, 'cached_input'),\n completion: current.completion + usageValue(usage, 'completion_tokens'),\n total: current.total + usageTotalTokens(usage),\n timeCost: current.timeCost + usageValue(usage, 'time_cost'),\n });\n };\n\n for (const execution of report.executions) {\n for (const task of execution.tasks) {\n const taskWithUsage = task as ExecutionTaskWithExtraUsage;\n addUsage(taskWithUsage.usage);\n addUsage(taskWithUsage.searchAreaUsage);\n }\n }\n\n return totalsByModel;\n}\n\nfunction sanitizeJsonForMarkdown(\n value: unknown,\n seen = new WeakSet<object>(),\n): unknown {\n if (typeof value === 'string') {\n if (\n screenshotDataUrlPattern.test(value) ||\n value.length > 1_000_000 ||\n /^[a-zA-Z0-9+/=\\s]{100000,}$/.test(value)\n ) {\n return `[omitted image/base64 string, length=${value.length}]`;\n }\n if (value.length > jsonContextMaxStringLength) {\n return `${value.slice(0, jsonContextMaxStringLength)}\\n[truncated, original length=${value.length}]`;\n }\n return value;\n }\n\n if (\n value === null ||\n typeof value === 'number' ||\n typeof value === 'boolean'\n ) {\n return value;\n }\n\n if (typeof value === 'function') {\n return '[omitted function]';\n }\n\n if (typeof value !== 'object' || value === undefined) {\n return undefined;\n }\n\n if (seen.has(value)) {\n return '[omitted circular reference]';\n }\n seen.add(value);\n\n if (Array.isArray(value)) {\n return value.map((item) => sanitizeJsonForMarkdown(item, seen));\n }\n\n const input = value as Record<string, unknown>;\n const output: Record<string, unknown> = {};\n for (const [key, entry] of Object.entries(input)) {\n if (entry === undefined || typeof entry === 'function') {\n continue;\n }\n if (\n typeof entry === 'string' &&\n /(^|_|-)(base64|screenshotBase64|imageBase64)$/i.test(key)\n ) {\n output[key] = `[omitted base64 string, length=${entry.length}]`;\n continue;\n }\n output[key] = sanitizeJsonForMarkdown(entry, seen);\n }\n seen.delete(value);\n return output;\n}\n\nfunction appendJsonSection(\n lines: string[],\n title: string,\n value: unknown,\n): void {\n if (value === undefined || value === null || value === '') {\n return;\n }\n const sanitized = sanitizeJsonForMarkdown(value);\n if (sanitized === undefined) {\n return;\n }\n\n lines.push('', `### ${title}`, '', '```json');\n lines.push(JSON.stringify(sanitized, null, 2));\n lines.push('```');\n}\n\nfunction appendTextSection(\n lines: string[],\n title: string,\n value: unknown,\n): void {\n if (typeof value !== 'string' || !value.trim()) {\n return;\n }\n lines.push('', `### ${title}`, '', '```text');\n lines.push(value.replace(/```/g, '` ` `'));\n lines.push('```');\n}\n\nfunction resolveTaskTiming(task: ExecutionTask): {\n start?: number;\n end?: number;\n cost?: number;\n} {\n const timing = task.timing;\n if (!timing) {\n return {};\n }\n\n const start = timing.start ?? timing.callAiStart ?? timing.callActionStart;\n const end =\n timing.end ??\n timing.callAiEnd ??\n timing.callActionEnd ??\n timing.captureAfterCallingSnapshotEnd;\n const cost =\n timing.cost ??\n (typeof start === 'number' && typeof end === 'number'\n ? end - start\n : undefined);\n\n return { start, end, cost };\n}\n\nfunction safeTaskParam(task: ExecutionTask): string {\n const readable = paramStr(task);\n if (readable) {\n return readable;\n }\n\n if (task.type === 'Insight') {\n return extractInsightParam((task as any).param).content;\n }\n\n return '';\n}\n\nfunction formatSize(\n size?: { width?: number; height?: number } | null,\n): string | undefined {\n if (\n !size ||\n typeof size.width !== 'number' ||\n typeof size.height !== 'number' ||\n Number.isNaN(size.width) ||\n Number.isNaN(size.height)\n ) {\n return undefined;\n }\n\n return `${size.width} x ${size.height}`;\n}\n\nfunction extractLocateCenter(\n task: ExecutionTask,\n): [number, number] | undefined {\n const outputCenter = (task.output as { element?: { center?: unknown } })\n ?.element?.center;\n if (\n Array.isArray(outputCenter) &&\n outputCenter.length >= 2 &&\n typeof outputCenter[0] === 'number' &&\n typeof outputCenter[1] === 'number'\n ) {\n return [outputCenter[0], outputCenter[1]];\n }\n\n const paramLocateCenter = (task.param as { locate?: { center?: unknown } })\n ?.locate?.center;\n if (\n Array.isArray(paramLocateCenter) &&\n paramLocateCenter.length >= 2 &&\n typeof paramLocateCenter[0] === 'number' &&\n typeof paramLocateCenter[1] === 'number'\n ) {\n return [paramLocateCenter[0], paramLocateCenter[1]];\n }\n\n const paramCenter = (task.param as { center?: unknown })?.center;\n if (\n Array.isArray(paramCenter) &&\n paramCenter.length >= 2 &&\n typeof paramCenter[0] === 'number' &&\n typeof paramCenter[1] === 'number'\n ) {\n return [paramCenter[0], paramCenter[1]];\n }\n\n return undefined;\n}\n\nfunction tryExtractBase64(screenshot: unknown): string | undefined {\n if (typeof screenshot === 'string') {\n const trimmedScreenshot = screenshot.trim();\n const dataUrlMatch = trimmedScreenshot.match(screenshotDataUrlPattern);\n if (dataUrlMatch) {\n const format = dataUrlMatch[1].toLowerCase() === 'jpg' ? 'jpeg' : 'png';\n const base64Body = dataUrlMatch[2].replace(/\\s/g, '');\n if (!base64Body) {\n return undefined;\n }\n return `data:image/${format};base64,${base64Body}`;\n }\n\n if (\n trimmedScreenshot.startsWith('data:') ||\n !rawBase64BodyPattern.test(trimmedScreenshot)\n ) {\n return undefined;\n }\n\n const base64Body = trimmedScreenshot.replace(/\\s/g, '');\n return base64Body ? `data:image/png;base64,${base64Body}` : undefined;\n }\n\n if (!screenshot || typeof screenshot !== 'object') return undefined;\n const s = screenshot as Record<string, unknown>;\n if (typeof s.base64 === 'string' && s.base64.length > 0) {\n return s.base64;\n }\n return undefined;\n}\n\nfunction restoredSourceRef(screenshot: unknown): ScreenshotRef | undefined {\n if (!screenshot || typeof screenshot !== 'object') {\n return undefined;\n }\n\n const sourceRef = (screenshot as Record<string, unknown>).sourceRef;\n return normalizeScreenshotRef(sourceRef) ?? undefined;\n}\n\nfunction screenshotAttachment(\n screenshot: unknown,\n screenshotBaseDir: string,\n executionIndex: number,\n taskIndex: number,\n options?: {\n fallbackIdSuffix?: string;\n label?: string;\n },\n): { markdown: string; attachment: MarkdownAttachment } {\n const markdownLabel = options?.label || `task-${taskIndex + 1}`;\n\n if (screenshot instanceof ScreenshotItem) {\n const ext = screenshot.extension;\n const suggestedFileName = `execution-${executionIndex + 1}-task-${taskIndex + 1}-${screenshot.id}.${ext}`;\n return {\n markdown: `\\n![${markdownLabel}](${screenshotBaseDir}/${suggestedFileName})`,\n attachment: {\n id: screenshot.id,\n suggestedFileName,\n mimeType: `image/${ext === 'jpeg' ? 'jpeg' : 'png'}`,\n executionIndex,\n taskIndex,\n base64Data: tryExtractBase64(screenshot),\n },\n };\n }\n\n const ref = normalizeScreenshotRef(screenshot);\n if (ref) {\n const ext = ref.mimeType === 'image/jpeg' ? 'jpeg' : 'png';\n const suggestedFileName = `execution-${executionIndex + 1}-task-${taskIndex + 1}-${ref.id}.${ext}`;\n return {\n markdown: `\\n![${markdownLabel}](${screenshotBaseDir}/${suggestedFileName})`,\n attachment: {\n id: ref.id,\n suggestedFileName,\n sourceRef: ref,\n mimeType: ref.mimeType,\n executionIndex,\n taskIndex,\n base64Data: tryExtractBase64(screenshot),\n },\n };\n }\n\n const sourceRef = restoredSourceRef(screenshot);\n if (sourceRef) {\n const ext = sourceRef.mimeType === 'image/jpeg' ? 'jpeg' : 'png';\n const suggestedFileName = `execution-${executionIndex + 1}-task-${taskIndex + 1}-${sourceRef.id}.${ext}`;\n return {\n markdown: `\\n![${markdownLabel}](${screenshotBaseDir}/${suggestedFileName})`,\n attachment: {\n id: sourceRef.id,\n suggestedFileName,\n sourceRef,\n mimeType: sourceRef.mimeType,\n executionIndex,\n taskIndex,\n base64Data: tryExtractBase64(screenshot),\n },\n };\n }\n\n const base64 = tryExtractBase64(screenshot);\n if (base64) {\n const ext = base64.startsWith('data:image/jpeg') ? 'jpeg' : 'png';\n const idSuffix = options?.fallbackIdSuffix\n ? `-${options.fallbackIdSuffix}`\n : '';\n const id = `restored-${executionIndex + 1}-${taskIndex + 1}${idSuffix}`;\n const suggestedFileName = `execution-${executionIndex + 1}-task-${taskIndex + 1}-${id}.${ext}`;\n return {\n markdown: `\\n![${markdownLabel}](${screenshotBaseDir}/${suggestedFileName})`,\n attachment: {\n id,\n suggestedFileName,\n mimeType: `image/${ext}`,\n executionIndex,\n taskIndex,\n base64Data: base64,\n },\n };\n }\n\n throw new Error(\n `executionToMarkdown: missing screenshot for execution #${executionIndex + 1} task #${taskIndex + 1}`,\n );\n}\n\nfunction imageLabelSuffix(value: string): string {\n return (\n value\n .toLowerCase()\n .replace(/[^a-z0-9._-]+/g, '-')\n .replace(/-+/g, '-')\n .replace(/^-|-$/g, '') || 'screenshot'\n );\n}\n\nfunction screenshotsMarkdownSection(\n task: ExecutionTask,\n screenshotBaseDir: string,\n executionIndex: number,\n taskIndex: number,\n): { lines: string[]; attachments: MarkdownAttachment[] } {\n const lines: string[] = ['', '### Screenshots'];\n const attachments: MarkdownAttachment[] = [];\n let screenshotIndex = 0;\n\n if (task.uiContext?.screenshot) {\n const imageResult = screenshotAttachment(\n task.uiContext.screenshot,\n screenshotBaseDir,\n executionIndex,\n taskIndex,\n {\n fallbackIdSuffix: 'ui-context',\n label: `task-${taskIndex + 1}-ui-context`,\n },\n );\n const time = resolveTaskTiming(task);\n screenshotIndex += 1;\n lines.push(\n `- #${screenshotIndex} type=ui-context, ts=${formatTime(time.start)}, timing=ui-context`,\n );\n lines.push(imageResult.markdown);\n attachments.push(imageResult.attachment);\n }\n\n task.recorder?.forEach((item, recorderIndex) => {\n if (!item.screenshot) {\n return;\n }\n\n const descriptionText = item.description\n ? `, description=${item.description}`\n : '';\n const timing = item.timing || 'N/A';\n screenshotIndex += 1;\n lines.push(\n `- #${screenshotIndex} type=${item.type}, ts=${formatTime(item.ts)}, timing=${timing}${descriptionText}`,\n );\n\n const imageResult = screenshotAttachment(\n item.screenshot,\n screenshotBaseDir,\n executionIndex,\n taskIndex,\n {\n fallbackIdSuffix: `recorder-${recorderIndex + 1}`,\n label: `task-${taskIndex + 1}-${imageLabelSuffix(timing)}`,\n },\n );\n\n lines.push(imageResult.markdown);\n attachments.push(imageResult.attachment);\n });\n\n return screenshotIndex > 0\n ? { lines, attachments }\n : { lines: [], attachments };\n}\n\nfunction renderExecution(\n executionRaw: ExecutionDump | IExecutionDump,\n executionIndex: number,\n options?: ExecutionMarkdownOptions,\n): ExecutionMarkdownResult {\n const execution = toExecutionDump(executionRaw);\n const screenshotBaseDir = options?.screenshotBaseDir ?? './screenshots';\n\n const lines: string[] = [];\n const attachments: MarkdownAttachment[] = [];\n\n lines.push(`# ${execution.name}`);\n if (execution.description) {\n lines.push('', execution.description);\n }\n\n lines.push('', `- Execution start: ${formatTime(execution.logTime)}`);\n lines.push(`- Task count: ${execution.tasks.length}`);\n if (execution.aiActContext) {\n appendTextSection(lines, 'AI Action Context', execution.aiActContext);\n }\n\n execution.tasks.forEach((task, taskIndex) => {\n const title = typeStr(task);\n const detail = safeTaskParam(task);\n const time = resolveTaskTiming(task);\n\n lines.push(\n '',\n `## ${taskIndex + 1}. ${title}${detail ? ` - ${detail}` : ''}`,\n );\n lines.push(`- Task ID: ${task.taskId || 'N/A'}`);\n lines.push(`- Type: ${task.type || 'N/A'}`);\n lines.push(`- SubType: ${task.subType || 'N/A'}`);\n lines.push(`- Status: ${task.status || 'unknown'}`);\n lines.push(`- Start: ${formatTime(time.start)}`);\n lines.push(`- End: ${formatTime(time.end)}`);\n lines.push(\n `- Cost(ms): ${typeof time.cost === 'number' ? time.cost : 'N/A'}`,\n );\n lines.push(\n `- Screen size: ${formatSize(task.uiContext?.shotSize) || 'N/A'}`,\n );\n\n if (task.subType === 'Locate') {\n const locateCenter = extractLocateCenter(task);\n if (locateCenter) {\n lines.push(`- Locate center: (${locateCenter[0]}, ${locateCenter[1]})`);\n }\n }\n\n if (task.errorMessage) {\n lines.push(`- Error: ${task.errorMessage}`);\n }\n\n const usageRows = usageRowsForTask(task);\n if (usageRows.length) {\n lines.push('', '### Model Usage');\n lines.push(\n ...markdownTable(\n [\n 'Source',\n 'Intent',\n 'Model',\n 'Description',\n 'Prompt Tokens',\n 'Cached Input',\n 'Completion Tokens',\n 'Total Tokens',\n 'Time Cost(ms)',\n 'Request ID',\n ],\n usageRows,\n ),\n );\n }\n\n appendJsonSection(lines, 'Param', task.param);\n appendJsonSection(lines, 'Output', task.output);\n appendJsonSection(lines, 'Log', task.log);\n appendTextSection(lines, 'Thought', task.thought);\n appendTextSection(\n lines,\n 'Reasoning Content',\n (task as ExecutionTaskWithExtraUsage).reasoning_content,\n );\n\n const screenshotsSection = screenshotsMarkdownSection(\n task,\n screenshotBaseDir,\n executionIndex,\n taskIndex,\n );\n if (screenshotsSection.lines.length) {\n lines.push(...screenshotsSection.lines);\n attachments.push(...screenshotsSection.attachments);\n }\n });\n\n return {\n markdown: lines.join('\\n'),\n attachments,\n };\n}\n\nexport function executionToMarkdown(\n execution: ExecutionDump | IExecutionDump,\n options?: ExecutionMarkdownOptions,\n): ExecutionMarkdownResult {\n return renderExecution(execution, 0, options);\n}\n\nexport function reportToMarkdown(\n report: ReportActionDump | IReportActionDump,\n): ReportMarkdownResult {\n const reportDump = toReportDump(report);\n\n const executionResults = reportDump.executions.map((execution, index) => {\n const rendered = renderExecution(execution, index);\n return {\n markdown: rendered.markdown,\n attachments: rendered.attachments,\n };\n });\n\n const attachments = executionResults.flatMap((item) => item.attachments);\n const usageTotals = collectUsageTotals(reportDump);\n const modelRows = reportDump.modelBriefs?.length\n ? modelBriefRows(reportDump.modelBriefs)\n : modelRowsFromUsage(reportDump);\n\n const modelInfoLines = [\n '\\n## Model Info',\n ...(modelRows.length\n ? markdownTable(['Intent', 'Model', 'Description'], modelRows)\n : ['- No model metadata recorded.']),\n ];\n\n const tokenSummaryLines = [\n '\\n## Token Usage Summary',\n ...(usageTotals.size\n ? markdownTable(\n [\n 'Model',\n 'Calls',\n 'Prompt Tokens',\n 'Cached Input',\n 'Completion Tokens',\n 'Total Tokens',\n 'Time Cost(ms)',\n ],\n Array.from(usageTotals.entries()).map(([modelName, totals]) => [\n modelName,\n totals.calls,\n totals.prompt,\n totals.cachedInput,\n totals.completion,\n totals.total,\n totals.timeCost,\n ]),\n )\n : ['- No token usage recorded.']),\n ];\n\n const header = [\n `# ${reportDump.groupName}`,\n reportDump.groupDescription ? `\\n${reportDump.groupDescription}` : '',\n `\\n- SDK Version: ${reportDump.sdkVersion}`,\n reportDump.deviceType ? `- Device Type: ${reportDump.deviceType}` : '',\n `- Execution count: ${reportDump.executions.length}`,\n ...modelInfoLines,\n ...tokenSummaryLines,\n ]\n .filter(Boolean)\n .join('\\n');\n\n return {\n markdown: `${header}\\n\\n${executionResults.map((item) => item.markdown).join('\\n\\n---\\n\\n')}`,\n attachments,\n };\n}\n"],"names":["screenshotDataUrlPattern","rawBase64BodyPattern","jsonContextMaxStringLength","toExecutionDump","execution","Error","Array","toReportDump","report","formatTime","ts","Number","Date","formatValue","value","String","escapeTableCell","markdownTable","headers","rows","row","modelBriefRows","modelBriefs","brief","modelRowsFromUsage","Map","addUsage","source","usage","hasUsage","intent","model","description","key","task","taskWithUsage","usageValue","undefined","usageTotalTokens","explicitTotal","usageModelName","usageRowsForTask","appendUsage","collectUsageTotals","totalsByModel","modelName","current","sanitizeJsonForMarkdown","seen","WeakSet","item","input","output","entry","Object","appendJsonSection","lines","title","sanitized","JSON","appendTextSection","resolveTaskTiming","timing","start","end","cost","safeTaskParam","readable","paramStr","extractInsightParam","formatSize","size","extractLocateCenter","outputCenter","paramLocateCenter","paramCenter","tryExtractBase64","screenshot","trimmedScreenshot","dataUrlMatch","format","base64Body","s","restoredSourceRef","sourceRef","normalizeScreenshotRef","screenshotAttachment","screenshotBaseDir","executionIndex","taskIndex","options","markdownLabel","ScreenshotItem","ext","suggestedFileName","ref","base64","idSuffix","id","imageLabelSuffix","screenshotsMarkdownSection","attachments","screenshotIndex","imageResult","time","recorderIndex","descriptionText","renderExecution","executionRaw","typeStr","detail","locateCenter","usageRows","screenshotsSection","executionToMarkdown","reportToMarkdown","reportDump","executionResults","index","rendered","usageTotals","modelRows","modelInfoLines","tokenSummaryLines","totals","header","Boolean"],"mappings":"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"}
@@ -3,7 +3,7 @@ import { basename, dirname, join, resolve } from "node:path";
3
3
  import { getMidsceneRunSubDir } from "@midscene/shared/common";
4
4
  import { antiEscapeScriptTag, logMsg } from "@midscene/shared/utils";
5
5
  import { getReportFileName } from "./agent/index.mjs";
6
- import { DATA_SCREENSHOT_MODE_ATTR, extractAllDumpScriptsSync, extractLastDumpScriptSync, getBaseUrlFixScript, streamDumpScriptsSync, streamImageScriptsToFile } from "./dump/html-utils.mjs";
6
+ import { DATA_SCREENSHOT_MODE_ATTR, extractAllDumpScriptsSync, extractLastDumpScriptSync, generateAgentReportComment, getBaseUrlFixScript, streamDumpScriptsSync, streamImageScriptsToFile } from "./dump/html-utils.mjs";
7
7
  import { normalizeScreenshotRef, resolveScreenshotSource } from "./dump/screenshot-store.mjs";
8
8
  import { ReportActionDump } from "./types.mjs";
9
9
  import { getReportTpl, getVersion, reportHTMLContent } from "./utils.mjs";
@@ -59,6 +59,29 @@ function peekReportSdkVersion(reportFilePath) {
59
59
  }
60
60
  }
61
61
  const warnedMismatchedVersions = new Set();
62
+ function tryParseAgentReportDump(dumpString) {
63
+ const trimmed = dumpString.trimStart();
64
+ if (!trimmed.startsWith('{') || !trimmed.includes('"executions"')) return null;
65
+ try {
66
+ return ReportActionDump.fromSerializedString(trimmed);
67
+ } catch {
68
+ return null;
69
+ }
70
+ }
71
+ function mergedAgentReportComment(reports) {
72
+ if (0 === reports.length) return '';
73
+ if (1 === reports.length) return generateAgentReportComment(reports[0]);
74
+ const deviceTypes = Array.from(new Set(reports.map((report)=>report.deviceType).filter(Boolean)));
75
+ const mergedReport = new ReportActionDump({
76
+ sdkVersion: reports[0].sdkVersion || getVersion(),
77
+ groupName: 'Merged Midscene Report',
78
+ groupDescription: 'Agent-readable summary for merged report HTML',
79
+ modelBriefs: reports.flatMap((report)=>report.modelBriefs ?? []),
80
+ deviceType: 1 === deviceTypes.length ? deviceTypes[0] : 'mixed',
81
+ executions: reports.flatMap((report)=>report.executions ?? [])
82
+ });
83
+ return generateAgentReportComment(mergedReport);
84
+ }
62
85
  class ReportMergingTool {
63
86
  createEmptyDumpString(groupName, groupDescription) {
64
87
  return new ReportActionDump({
@@ -131,6 +154,7 @@ class ReportMergingTool {
131
154
  const tplWithoutClose = -1 !== htmlEndIdx ? tpl.slice(0, htmlEndIdx) : tpl;
132
155
  appendFileSync(outputFilePath, tplWithoutClose);
133
156
  if (hasDirectoryModeReport) appendFileSync(outputFilePath, getBaseUrlFixScript());
157
+ const agentReports = [];
134
158
  for(let i = 0; i < this.reportInfos.length; i++){
135
159
  const reportInfo = this.reportInfos[i];
136
160
  logMsg(`Processing report ${i + 1}/${this.reportInfos.length}`);
@@ -159,6 +183,8 @@ class ReportMergingTool {
159
183
  const extractedDumpString = allDumps.length > 0 ? this.mergeDumpScripts(allDumps.map((d)=>d.content)) : extractLastDumpScriptSync(reportInfo.reportFilePath);
160
184
  if (extractedDumpString) dumpString = extractedDumpString;
161
185
  }
186
+ const agentReport = tryParseAgentReportDump(dumpString);
187
+ if (agentReport) agentReports.push(agentReport);
162
188
  const reportHtmlStr = `${reportHTMLContent({
163
189
  dumpString,
164
190
  attributes: {
@@ -174,6 +200,8 @@ class ReportMergingTool {
174
200
  }, void 0, void 0, false)}\n`;
175
201
  appendFileSync(outputFilePath, reportHtmlStr);
176
202
  }
203
+ const agentComment = mergedAgentReportComment(agentReports);
204
+ if (agentComment) appendFileSync(outputFilePath, agentComment);
177
205
  appendFileSync(outputFilePath, `${htmlEndTag}\n`);
178
206
  logMsg(`Successfully merged new report: ${outputFilePath}`);
179
207
  if (rmOriginalReports) {
@@ -1 +1 @@
1
- {"version":3,"file":"report.mjs","sources":["../../src/report.ts"],"sourcesContent":["import {\n appendFileSync,\n copyFileSync,\n existsSync,\n mkdirSync,\n readdirSync,\n rmSync,\n unlinkSync,\n writeFileSync,\n} from 'node:fs';\nimport * as path from 'node:path';\nimport { getMidsceneRunSubDir } from '@midscene/shared/common';\nimport { antiEscapeScriptTag, logMsg } from '@midscene/shared/utils';\nimport { getReportFileName } from './agent';\nimport {\n DATA_SCREENSHOT_MODE_ATTR,\n extractAllDumpScriptsSync,\n extractLastDumpScriptSync,\n getBaseUrlFixScript,\n streamDumpScriptsSync,\n streamImageScriptsToFile,\n} from './dump/html-utils';\nimport {\n normalizeScreenshotRef,\n resolveScreenshotSource,\n} from './dump/screenshot-store';\nimport {\n type ExecutionDump,\n type IExecutionDump,\n ReportActionDump,\n type ScreenshotMode,\n} from './types';\nimport type { ReportFileWithAttributes } from './types';\nimport { getReportTpl, getVersion, reportHTMLContent } from './utils';\n\n/**\n * Read the screenshot storage mode a report declared at generation time.\n *\n * Reports written by current versions stamp `data-screenshot-mode` onto every\n * dump script tag, so we only need to peek at the first real dump script (the\n * template's bundled JS also references the dump type string, hence the\n * `data-group-id` filter) and can stop streaming immediately.\n *\n * Returns undefined for legacy reports that predate the attribute or for\n * unreadable files, letting the caller fall back to a filesystem heuristic.\n */\nconst screenshotModeAttrRegExp = new RegExp(\n `${DATA_SCREENSHOT_MODE_ATTR}=\"(inline|directory)\"`,\n);\n\nfunction readDeclaredScreenshotMode(\n reportFilePath: string,\n): ScreenshotMode | undefined {\n let mode: ScreenshotMode | undefined;\n try {\n streamDumpScriptsSync(reportFilePath, ({ openTag }) => {\n // Skip false matches from the template's bundled JS code.\n if (!openTag.includes('data-group-id')) return false;\n const match = openTag.match(screenshotModeAttrRegExp);\n mode = match?.[1] as ScreenshotMode | undefined;\n return true; // the first real dump script decides the mode\n });\n } catch {\n // Unreadable / non-existent file — let the caller fall back.\n }\n return mode;\n}\n\n/**\n * Check if a report is in directory mode (html-and-external-assets).\n * Directory mode reports: {name}/index.html + {name}/screenshots/\n *\n * The mode is read from the report's own `data-screenshot-mode` metadata, which\n * is authoritative regardless of whether the run happened to capture any\n * screenshots. For legacy reports without the attribute we fall back to the old\n * filesystem heuristic (an `index.html` that has a sibling `screenshots/` dir).\n */\nexport function isDirectoryModeReport(reportFilePath: string): boolean {\n // Directory-mode reports are always written as `{name}/index.html`, so any\n // other filename is inline. Short-circuit before touching the file so the\n // common inline case (`{name}.html`) costs nothing.\n if (path.basename(reportFilePath) !== 'index.html') return false;\n\n const declared = readDeclaredScreenshotMode(reportFilePath);\n if (declared) return declared === 'directory';\n\n // Legacy fallback for reports generated before screenshotMode was embedded.\n return existsSync(path.join(path.dirname(reportFilePath), 'screenshots'));\n}\n\n/**\n * Whether a report lives in its own dedicated directory (`{name}/index.html`)\n * rather than being a single standalone file (`{name}.html`).\n *\n * This is a structural fact about *where the report file sits*, independent of\n * how it stores screenshots: a directory report can keep screenshots external\n * (a `screenshots/` sibling) OR inline them into `index.html`. Deletion needs\n * this — not the screenshot mode — to decide whether to remove the whole\n * directory or just unlink a file, so that an inline-screenshot report nested in\n * its own folder still has the folder removed instead of being orphaned.\n */\nfunction isDirectoryBasedReport(reportFilePath: string): boolean {\n return path.basename(reportFilePath) === 'index.html';\n}\n\n/**\n * Deduplicate executions by stable id, keeping only the last occurrence.\n * Old-format executions without id are always preserved.\n */\nexport function dedupeExecutionsKeepLatest<T extends Pick<ExecutionDump, 'id'>>(\n executions: T[],\n): T[] {\n let noIdCounter = 0;\n const deduped = new Map<string, T>();\n for (const exec of executions) {\n const key = exec.id || `__no_id_${noIdCounter++}`;\n deduped.set(key, exec);\n }\n return Array.from(deduped.values());\n}\n/**\n * Peek at the first `sdkVersion` field embedded in a midscene_web_dump\n * script tag inside the given report file. Returns undefined if no\n * recognizable tag or sdkVersion is present.\n */\nfunction peekReportSdkVersion(reportFilePath: string): string | undefined {\n try {\n const dump = extractLastDumpScriptSync(reportFilePath);\n if (!dump) return undefined;\n const match = dump.match(/\"sdkVersion\"\\s*:\\s*\"([^\"]+)\"/);\n return match?.[1];\n } catch {\n return undefined;\n }\n}\n\nconst warnedMismatchedVersions = new Set<string>();\n\nexport class ReportMergingTool {\n private reportInfos: ReportFileWithAttributes[] = [];\n\n private createEmptyDumpString(groupName: string, groupDescription?: string) {\n return new ReportActionDump({\n sdkVersion: '',\n groupName,\n groupDescription,\n modelBriefs: [],\n executions: [],\n }).serialize();\n }\n\n public append(reportInfo: ReportFileWithAttributes) {\n if (reportInfo.reportFilePath) {\n const sourceVersion = peekReportSdkVersion(reportInfo.reportFilePath);\n const currentVersion = getVersion();\n if (\n sourceVersion &&\n currentVersion &&\n sourceVersion !== currentVersion &&\n !warnedMismatchedVersions.has(sourceVersion)\n ) {\n warnedMismatchedVersions.add(sourceVersion);\n logMsg(\n `[@midscene/core] ReportMergingTool version mismatch: source report was written by @midscene/core@${sourceVersion} but the merger is @midscene/core@${currentVersion}. This commonly means @midscene/core and the device package (e.g. @midscene/android) resolve to different versions in node_modules. Merged output may silently drop intermediate steps. Align the versions and reinstall (rm -rf node_modules package-lock.json && npm install).`,\n );\n }\n }\n this.reportInfos.push(reportInfo);\n }\n public clear() {\n this.reportInfos = [];\n }\n\n /**\n * Merge multiple dump script contents (from the same source report)\n * into a single serialized ReportActionDump string.\n * If there's only one dump, return it as-is. If multiple, merge\n * all executions into the first dump's group structure.\n */\n private mergeDumpScripts(contents: string[]): string {\n const unescaped = contents\n .map((c) => antiEscapeScriptTag(c))\n .filter((c) => c.length > 0);\n if (unescaped.length === 0) return '';\n if (unescaped.length === 1) return unescaped[0];\n\n // Parse all dumps and collect executions, deduplicating by id (keep last).\n // Only executions with a stable id are deduped; old-format entries without\n // id are always kept (they may be distinct despite sharing the same name).\n const base = ReportActionDump.fromSerializedString(unescaped[0]);\n const allExecutions = [...base.executions];\n for (let i = 1; i < unescaped.length; i++) {\n const other = ReportActionDump.fromSerializedString(unescaped[i]);\n allExecutions.push(...other.executions);\n }\n base.executions = dedupeExecutionsKeepLatest(allExecutions);\n return base.serialize();\n }\n\n public mergeReports(\n reportFileName: 'AUTO' | string = 'AUTO',\n opts?: {\n rmOriginalReports?: boolean;\n overwrite?: boolean;\n outputDir?: string;\n },\n ): string | null {\n const {\n rmOriginalReports = false,\n overwrite = false,\n outputDir,\n } = opts ?? {};\n\n if (this.reportInfos.length === 0) {\n logMsg('No reports to merge');\n return null;\n }\n\n const targetDir = outputDir\n ? path.resolve(outputDir)\n : getMidsceneRunSubDir('report');\n if (outputDir) {\n mkdirSync(targetDir, { recursive: true });\n }\n\n // Resolve each report's screenshot mode exactly once. isDirectoryModeReport\n // reads the file to find the authoritative metadata, so recomputing it for\n // both the output-path decision and the per-report merge loop would re-scan\n // every report twice.\n const isDirModeByIndex = this.reportInfos.map((info) =>\n Boolean(\n info.reportFilePath && isDirectoryModeReport(info.reportFilePath),\n ),\n );\n const hasDirectoryModeReport = isDirModeByIndex.some(Boolean);\n\n const resolvedName =\n reportFileName === 'AUTO'\n ? getReportFileName('merged-report')\n : reportFileName;\n\n // Directory mode: output as {name}/index.html to keep relative paths working\n // Inline mode: output as {name}.html (single file)\n const outputFilePath = hasDirectoryModeReport\n ? path.resolve(targetDir, resolvedName, 'index.html')\n : path.resolve(targetDir, `${resolvedName}.html`);\n\n if (reportFileName !== 'AUTO' && existsSync(outputFilePath)) {\n if (!overwrite) {\n throw new Error(\n `Report file already exists: ${outputFilePath}\\nSet overwrite to true to overwrite this file.`,\n );\n }\n if (hasDirectoryModeReport) {\n rmSync(path.dirname(outputFilePath), { recursive: true, force: true });\n } else {\n unlinkSync(outputFilePath);\n }\n }\n\n if (hasDirectoryModeReport) {\n mkdirSync(path.dirname(outputFilePath), { recursive: true });\n }\n\n logMsg(\n `Start merging ${this.reportInfos.length} reports...\\nCreating template file...`,\n );\n\n try {\n // Write template without closing </html> tag so we can append\n // dump scripts before it. The closing tag is added at the end.\n const htmlEndTag = '</html>';\n const tpl = getReportTpl();\n const htmlEndIdx = tpl.lastIndexOf(htmlEndTag);\n const tplWithoutClose =\n htmlEndIdx !== -1 ? tpl.slice(0, htmlEndIdx) : tpl;\n appendFileSync(outputFilePath, tplWithoutClose);\n\n // For directory-mode output, inject base URL fix script\n if (hasDirectoryModeReport) {\n appendFileSync(outputFilePath, getBaseUrlFixScript());\n }\n\n // Process all reports one by one\n for (let i = 0; i < this.reportInfos.length; i++) {\n const reportInfo = this.reportInfos[i];\n logMsg(`Processing report ${i + 1}/${this.reportInfos.length}`);\n\n const { reportAttributes } = reportInfo;\n let dumpString = this.createEmptyDumpString(\n reportAttributes.testTitle,\n reportAttributes.testDescription,\n );\n let mergedGroupId = `merged-group-${i}`;\n\n if (reportInfo.reportFilePath) {\n if (isDirModeByIndex[i]) {\n // Directory mode: copy external screenshot files. A directory-mode\n // report can legitimately have no screenshots/ dir (a run that\n // captured nothing), so only copy when the source dir exists.\n const reportDir = path.dirname(reportInfo.reportFilePath);\n const screenshotsDir = path.join(reportDir, 'screenshots');\n if (existsSync(screenshotsDir)) {\n const mergedScreenshotsDir = path.join(\n path.dirname(outputFilePath),\n 'screenshots',\n );\n mkdirSync(mergedScreenshotsDir, { recursive: true });\n for (const file of readdirSync(screenshotsDir)) {\n const src = path.join(screenshotsDir, file);\n const dest = path.join(mergedScreenshotsDir, file);\n copyFileSync(src, dest);\n }\n }\n } else {\n // Inline mode: stream image scripts to output file\n streamImageScriptsToFile(reportInfo.reportFilePath, outputFilePath);\n }\n\n // Extract all dump scripts from the source report.\n // After the per-execution append refactor, a single source report\n // may contain multiple <script type=\"midscene_web_dump\"> tags\n // (one per execution). We merge them into a single ReportActionDump.\n // Filter by data-group-id to exclude false matches from the template's\n // bundled JS code, which also references the midscene_web_dump type string.\n const allDumps = extractAllDumpScriptsSync(\n reportInfo.reportFilePath,\n ).filter((d) => d.openTag.includes('data-group-id'));\n const groupIdMatch = allDumps[0]?.openTag.match(\n /data-group-id=\"([^\"]+)\"/,\n );\n if (groupIdMatch) {\n mergedGroupId = decodeURIComponent(groupIdMatch[1]);\n }\n const extractedDumpString =\n allDumps.length > 0\n ? this.mergeDumpScripts(allDumps.map((d) => d.content))\n : extractLastDumpScriptSync(reportInfo.reportFilePath);\n if (extractedDumpString) {\n dumpString = extractedDumpString;\n }\n }\n\n const reportHtmlStr = `${reportHTMLContent(\n {\n dumpString,\n attributes: {\n 'data-group-id': mergedGroupId,\n [DATA_SCREENSHOT_MODE_ATTR]: hasDirectoryModeReport\n ? 'directory'\n : 'inline',\n playwright_test_duration: reportAttributes.testDuration,\n playwright_test_status: reportAttributes.testStatus,\n playwright_test_title: reportAttributes.testTitle,\n playwright_test_id: reportAttributes.testId,\n playwright_test_description: reportAttributes.testDescription,\n is_merged: true,\n },\n },\n undefined,\n undefined,\n false,\n )}\\n`;\n\n appendFileSync(outputFilePath, reportHtmlStr);\n }\n\n // Close the HTML document\n appendFileSync(outputFilePath, `${htmlEndTag}\\n`);\n\n logMsg(`Successfully merged new report: ${outputFilePath}`);\n\n // Remove original reports if needed\n if (rmOriginalReports) {\n for (const info of this.reportInfos) {\n if (!info.reportFilePath) continue;\n try {\n if (isDirectoryBasedReport(info.reportFilePath)) {\n // The report owns its directory (`{name}/index.html`) — remove the\n // whole folder, whether screenshots are external or inlined.\n const reportDir = path.dirname(info.reportFilePath);\n rmSync(reportDir, { recursive: true, force: true });\n } else {\n unlinkSync(info.reportFilePath);\n }\n } catch (error) {\n logMsg(`Error deleting report ${info.reportFilePath}: ${error}`);\n }\n }\n logMsg(`Removed ${this.reportInfos.length} original reports`);\n }\n return outputFilePath;\n } catch (error) {\n logMsg(`Error in mergeReports: ${error}`);\n throw error;\n }\n }\n}\n\nexport interface SplitReportHtmlOptions {\n htmlPath: string;\n outputDir: string;\n}\n\nexport interface SplitReportHtmlResult {\n executionJsonFiles: string[];\n screenshotFiles: string[];\n}\n\nexport interface CollectedReportExecutions {\n baseDump: ReportActionDump;\n executions: IExecutionDump[];\n}\n\n/**\n * Collect executions from a report HTML, deduplicating by stable id while\n * keeping only the latest occurrence. Old-format executions without id are\n * always preserved.\n */\nexport function collectDedupedExecutions(\n htmlPath: string,\n): CollectedReportExecutions {\n let baseDump: ReportActionDump | null = null;\n let executionSerial = 0;\n const latestSerialByExecutionId = new Map<string, number>();\n\n streamDumpScriptsSync(htmlPath, (dumpScript) => {\n if (!dumpScript.openTag.includes('data-group-id')) {\n return false;\n }\n const groupedDump = ReportActionDump.fromSerializedString(\n antiEscapeScriptTag(dumpScript.content),\n );\n for (const execution of groupedDump.executions) {\n executionSerial += 1;\n if (execution.id) {\n latestSerialByExecutionId.set(execution.id, executionSerial);\n }\n }\n return false;\n });\n\n const executions: IExecutionDump[] = [];\n executionSerial = 0;\n streamDumpScriptsSync(htmlPath, (dumpScript) => {\n if (!dumpScript.openTag.includes('data-group-id')) {\n return false;\n }\n\n const groupedDump = ReportActionDump.fromSerializedString(\n antiEscapeScriptTag(dumpScript.content),\n );\n if (!baseDump) {\n baseDump = groupedDump;\n }\n\n for (const execution of groupedDump.executions) {\n executionSerial += 1;\n if (\n execution.id &&\n latestSerialByExecutionId.get(execution.id) !== executionSerial\n ) {\n continue;\n }\n executions.push(execution);\n }\n\n return false;\n });\n\n if (!baseDump) {\n throw new Error(`No report dump scripts found in ${htmlPath}`);\n }\n\n return {\n baseDump,\n executions,\n };\n}\n\nfunction extensionByMimeType(mimeType: string): 'png' | 'jpeg' {\n if (mimeType === 'image/png') return 'png';\n if (mimeType === 'image/jpeg') return 'jpeg';\n throw new Error(`Unsupported screenshot mime type: ${mimeType}`);\n}\n\nfunction externalizeScreenshotsInExecution(\n execution: IExecutionDump,\n opts: {\n htmlPath: string;\n screenshotsDir: string;\n writtenFiles: Set<string>;\n },\n): void {\n const visit = (node: unknown): void => {\n if (Array.isArray(node)) {\n for (const item of node) {\n visit(item);\n }\n return;\n }\n\n if (typeof node !== 'object' || node === null) return;\n\n const ref = normalizeScreenshotRef(node);\n if (ref) {\n const ext = extensionByMimeType(ref.mimeType);\n const fileName = `${ref.id}.${ext}`;\n const relativePath = `./screenshots/${fileName}`;\n const absolutePath = path.join(opts.screenshotsDir, fileName);\n\n if (!opts.writtenFiles.has(fileName)) {\n const resolved = resolveScreenshotSource(ref, {\n reportPath: opts.htmlPath,\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 } else {\n copyFileSync(resolved.filePath, absolutePath);\n }\n opts.writtenFiles.add(fileName);\n }\n\n ref.storage = 'file';\n ref.path = relativePath;\n return;\n }\n\n for (const value of Object.values(node)) {\n visit(value);\n }\n };\n\n visit(execution);\n}\n\n/**\n * Reverse parse a Midscene report HTML into per-execution JSON files and\n * externalized screenshots.\n */\nexport function splitReportHtmlByExecution(\n options: SplitReportHtmlOptions,\n): SplitReportHtmlResult {\n const { htmlPath, outputDir } = options;\n const screenshotsDir = path.join(outputDir, 'screenshots');\n\n mkdirSync(outputDir, { recursive: true });\n mkdirSync(screenshotsDir, { recursive: true });\n\n const executionJsonFiles: string[] = [];\n const writtenScreenshotFiles = new Set<string>();\n const { baseDump, executions } = collectDedupedExecutions(htmlPath);\n\n let fileIndex = 0;\n for (const execution of executions) {\n fileIndex += 1;\n externalizeScreenshotsInExecution(execution, {\n htmlPath,\n screenshotsDir,\n writtenFiles: writtenScreenshotFiles,\n });\n const singleExecutionDump = new ReportActionDump({\n sdkVersion: baseDump.sdkVersion,\n groupName: baseDump.groupName,\n groupDescription: baseDump.groupDescription,\n modelBriefs: baseDump.modelBriefs,\n deviceType: baseDump.deviceType,\n executions: [execution],\n });\n\n const jsonFilePath = path.join(outputDir, `${fileIndex}.execution.json`);\n writeFileSync(jsonFilePath, singleExecutionDump.serialize(2), 'utf-8');\n executionJsonFiles.push(jsonFilePath);\n }\n\n return {\n executionJsonFiles,\n screenshotFiles: Array.from(writtenScreenshotFiles)\n .sort()\n .map((fileName) => path.join(screenshotsDir, fileName)),\n };\n}\n"],"names":["screenshotModeAttrRegExp","RegExp","DATA_SCREENSHOT_MODE_ATTR","readDeclaredScreenshotMode","reportFilePath","mode","streamDumpScriptsSync","openTag","match","isDirectoryModeReport","path","declared","existsSync","isDirectoryBasedReport","dedupeExecutionsKeepLatest","executions","noIdCounter","deduped","Map","exec","key","Array","peekReportSdkVersion","dump","extractLastDumpScriptSync","warnedMismatchedVersions","Set","ReportMergingTool","groupName","groupDescription","ReportActionDump","reportInfo","sourceVersion","currentVersion","getVersion","logMsg","contents","unescaped","c","antiEscapeScriptTag","base","allExecutions","i","other","reportFileName","opts","rmOriginalReports","overwrite","outputDir","targetDir","getMidsceneRunSubDir","mkdirSync","isDirModeByIndex","info","Boolean","hasDirectoryModeReport","resolvedName","getReportFileName","outputFilePath","Error","rmSync","unlinkSync","htmlEndTag","tpl","getReportTpl","htmlEndIdx","tplWithoutClose","appendFileSync","getBaseUrlFixScript","reportAttributes","dumpString","mergedGroupId","reportDir","screenshotsDir","mergedScreenshotsDir","file","readdirSync","src","dest","copyFileSync","streamImageScriptsToFile","allDumps","extractAllDumpScriptsSync","d","groupIdMatch","decodeURIComponent","extractedDumpString","reportHtmlStr","reportHTMLContent","undefined","error","collectDedupedExecutions","htmlPath","baseDump","executionSerial","latestSerialByExecutionId","dumpScript","groupedDump","execution","extensionByMimeType","mimeType","externalizeScreenshotsInExecution","visit","node","item","ref","normalizeScreenshotRef","ext","fileName","relativePath","absolutePath","resolved","resolveScreenshotSource","rawBase64","writeFileSync","Buffer","value","Object","splitReportHtmlByExecution","options","executionJsonFiles","writtenScreenshotFiles","fileIndex","singleExecutionDump","jsonFilePath"],"mappings":"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"}
1
+ {"version":3,"file":"report.mjs","sources":["../../src/report.ts"],"sourcesContent":["import {\n appendFileSync,\n copyFileSync,\n existsSync,\n mkdirSync,\n readdirSync,\n rmSync,\n unlinkSync,\n writeFileSync,\n} from 'node:fs';\nimport * as path from 'node:path';\nimport { getMidsceneRunSubDir } from '@midscene/shared/common';\nimport { antiEscapeScriptTag, logMsg } from '@midscene/shared/utils';\nimport { getReportFileName } from './agent';\nimport {\n DATA_SCREENSHOT_MODE_ATTR,\n extractAllDumpScriptsSync,\n extractLastDumpScriptSync,\n generateAgentReportComment,\n getBaseUrlFixScript,\n streamDumpScriptsSync,\n streamImageScriptsToFile,\n} from './dump/html-utils';\nimport {\n normalizeScreenshotRef,\n resolveScreenshotSource,\n} from './dump/screenshot-store';\nimport {\n type ExecutionDump,\n type IExecutionDump,\n ReportActionDump,\n type ScreenshotMode,\n} from './types';\nimport type { ReportFileWithAttributes } from './types';\nimport { getReportTpl, getVersion, reportHTMLContent } from './utils';\n\n/**\n * Read the screenshot storage mode a report declared at generation time.\n *\n * Reports written by current versions stamp `data-screenshot-mode` onto every\n * dump script tag, so we only need to peek at the first real dump script (the\n * template's bundled JS also references the dump type string, hence the\n * `data-group-id` filter) and can stop streaming immediately.\n *\n * Returns undefined for legacy reports that predate the attribute or for\n * unreadable files, letting the caller fall back to a filesystem heuristic.\n */\nconst screenshotModeAttrRegExp = new RegExp(\n `${DATA_SCREENSHOT_MODE_ATTR}=\"(inline|directory)\"`,\n);\n\nfunction readDeclaredScreenshotMode(\n reportFilePath: string,\n): ScreenshotMode | undefined {\n let mode: ScreenshotMode | undefined;\n try {\n streamDumpScriptsSync(reportFilePath, ({ openTag }) => {\n // Skip false matches from the template's bundled JS code.\n if (!openTag.includes('data-group-id')) return false;\n const match = openTag.match(screenshotModeAttrRegExp);\n mode = match?.[1] as ScreenshotMode | undefined;\n return true; // the first real dump script decides the mode\n });\n } catch {\n // Unreadable / non-existent file — let the caller fall back.\n }\n return mode;\n}\n\n/**\n * Check if a report is in directory mode (html-and-external-assets).\n * Directory mode reports: {name}/index.html + {name}/screenshots/\n *\n * The mode is read from the report's own `data-screenshot-mode` metadata, which\n * is authoritative regardless of whether the run happened to capture any\n * screenshots. For legacy reports without the attribute we fall back to the old\n * filesystem heuristic (an `index.html` that has a sibling `screenshots/` dir).\n */\nexport function isDirectoryModeReport(reportFilePath: string): boolean {\n // Directory-mode reports are always written as `{name}/index.html`, so any\n // other filename is inline. Short-circuit before touching the file so the\n // common inline case (`{name}.html`) costs nothing.\n if (path.basename(reportFilePath) !== 'index.html') return false;\n\n const declared = readDeclaredScreenshotMode(reportFilePath);\n if (declared) return declared === 'directory';\n\n // Legacy fallback for reports generated before screenshotMode was embedded.\n return existsSync(path.join(path.dirname(reportFilePath), 'screenshots'));\n}\n\n/**\n * Whether a report lives in its own dedicated directory (`{name}/index.html`)\n * rather than being a single standalone file (`{name}.html`).\n *\n * This is a structural fact about *where the report file sits*, independent of\n * how it stores screenshots: a directory report can keep screenshots external\n * (a `screenshots/` sibling) OR inline them into `index.html`. Deletion needs\n * this — not the screenshot mode — to decide whether to remove the whole\n * directory or just unlink a file, so that an inline-screenshot report nested in\n * its own folder still has the folder removed instead of being orphaned.\n */\nfunction isDirectoryBasedReport(reportFilePath: string): boolean {\n return path.basename(reportFilePath) === 'index.html';\n}\n\n/**\n * Deduplicate executions by stable id, keeping only the last occurrence.\n * Old-format executions without id are always preserved.\n */\nexport function dedupeExecutionsKeepLatest<T extends Pick<ExecutionDump, 'id'>>(\n executions: T[],\n): T[] {\n let noIdCounter = 0;\n const deduped = new Map<string, T>();\n for (const exec of executions) {\n const key = exec.id || `__no_id_${noIdCounter++}`;\n deduped.set(key, exec);\n }\n return Array.from(deduped.values());\n}\n/**\n * Peek at the first `sdkVersion` field embedded in a midscene_web_dump\n * script tag inside the given report file. Returns undefined if no\n * recognizable tag or sdkVersion is present.\n */\nfunction peekReportSdkVersion(reportFilePath: string): string | undefined {\n try {\n const dump = extractLastDumpScriptSync(reportFilePath);\n if (!dump) return undefined;\n const match = dump.match(/\"sdkVersion\"\\s*:\\s*\"([^\"]+)\"/);\n return match?.[1];\n } catch {\n return undefined;\n }\n}\n\nconst warnedMismatchedVersions = new Set<string>();\n\nfunction tryParseAgentReportDump(dumpString: string): ReportActionDump | null {\n const trimmed = dumpString.trimStart();\n if (!trimmed.startsWith('{') || !trimmed.includes('\"executions\"')) {\n return null;\n }\n\n try {\n return ReportActionDump.fromSerializedString(trimmed);\n } catch {\n return null;\n }\n}\n\nfunction mergedAgentReportComment(reports: ReportActionDump[]): string {\n if (reports.length === 0) {\n return '';\n }\n if (reports.length === 1) {\n return generateAgentReportComment(reports[0]);\n }\n\n const deviceTypes = Array.from(\n new Set(reports.map((report) => report.deviceType).filter(Boolean)),\n );\n const mergedReport = new ReportActionDump({\n sdkVersion: reports[0].sdkVersion || getVersion(),\n groupName: 'Merged Midscene Report',\n groupDescription: 'Agent-readable summary for merged report HTML',\n modelBriefs: reports.flatMap((report) => report.modelBriefs ?? []),\n deviceType: deviceTypes.length === 1 ? deviceTypes[0] : 'mixed',\n executions: reports.flatMap((report) => report.executions ?? []),\n });\n return generateAgentReportComment(mergedReport);\n}\n\nexport class ReportMergingTool {\n private reportInfos: ReportFileWithAttributes[] = [];\n\n private createEmptyDumpString(groupName: string, groupDescription?: string) {\n return new ReportActionDump({\n sdkVersion: '',\n groupName,\n groupDescription,\n modelBriefs: [],\n executions: [],\n }).serialize();\n }\n\n public append(reportInfo: ReportFileWithAttributes) {\n if (reportInfo.reportFilePath) {\n const sourceVersion = peekReportSdkVersion(reportInfo.reportFilePath);\n const currentVersion = getVersion();\n if (\n sourceVersion &&\n currentVersion &&\n sourceVersion !== currentVersion &&\n !warnedMismatchedVersions.has(sourceVersion)\n ) {\n warnedMismatchedVersions.add(sourceVersion);\n logMsg(\n `[@midscene/core] ReportMergingTool version mismatch: source report was written by @midscene/core@${sourceVersion} but the merger is @midscene/core@${currentVersion}. This commonly means @midscene/core and the device package (e.g. @midscene/android) resolve to different versions in node_modules. Merged output may silently drop intermediate steps. Align the versions and reinstall (rm -rf node_modules package-lock.json && npm install).`,\n );\n }\n }\n this.reportInfos.push(reportInfo);\n }\n public clear() {\n this.reportInfos = [];\n }\n\n /**\n * Merge multiple dump script contents (from the same source report)\n * into a single serialized ReportActionDump string.\n * If there's only one dump, return it as-is. If multiple, merge\n * all executions into the first dump's group structure.\n */\n private mergeDumpScripts(contents: string[]): string {\n const unescaped = contents\n .map((c) => antiEscapeScriptTag(c))\n .filter((c) => c.length > 0);\n if (unescaped.length === 0) return '';\n if (unescaped.length === 1) return unescaped[0];\n\n // Parse all dumps and collect executions, deduplicating by id (keep last).\n // Only executions with a stable id are deduped; old-format entries without\n // id are always kept (they may be distinct despite sharing the same name).\n const base = ReportActionDump.fromSerializedString(unescaped[0]);\n const allExecutions = [...base.executions];\n for (let i = 1; i < unescaped.length; i++) {\n const other = ReportActionDump.fromSerializedString(unescaped[i]);\n allExecutions.push(...other.executions);\n }\n base.executions = dedupeExecutionsKeepLatest(allExecutions);\n return base.serialize();\n }\n\n public mergeReports(\n reportFileName: 'AUTO' | string = 'AUTO',\n opts?: {\n rmOriginalReports?: boolean;\n overwrite?: boolean;\n outputDir?: string;\n },\n ): string | null {\n const {\n rmOriginalReports = false,\n overwrite = false,\n outputDir,\n } = opts ?? {};\n\n if (this.reportInfos.length === 0) {\n logMsg('No reports to merge');\n return null;\n }\n\n const targetDir = outputDir\n ? path.resolve(outputDir)\n : getMidsceneRunSubDir('report');\n if (outputDir) {\n mkdirSync(targetDir, { recursive: true });\n }\n\n // Resolve each report's screenshot mode exactly once. isDirectoryModeReport\n // reads the file to find the authoritative metadata, so recomputing it for\n // both the output-path decision and the per-report merge loop would re-scan\n // every report twice.\n const isDirModeByIndex = this.reportInfos.map((info) =>\n Boolean(\n info.reportFilePath && isDirectoryModeReport(info.reportFilePath),\n ),\n );\n const hasDirectoryModeReport = isDirModeByIndex.some(Boolean);\n\n const resolvedName =\n reportFileName === 'AUTO'\n ? getReportFileName('merged-report')\n : reportFileName;\n\n // Directory mode: output as {name}/index.html to keep relative paths working\n // Inline mode: output as {name}.html (single file)\n const outputFilePath = hasDirectoryModeReport\n ? path.resolve(targetDir, resolvedName, 'index.html')\n : path.resolve(targetDir, `${resolvedName}.html`);\n\n if (reportFileName !== 'AUTO' && existsSync(outputFilePath)) {\n if (!overwrite) {\n throw new Error(\n `Report file already exists: ${outputFilePath}\\nSet overwrite to true to overwrite this file.`,\n );\n }\n if (hasDirectoryModeReport) {\n rmSync(path.dirname(outputFilePath), { recursive: true, force: true });\n } else {\n unlinkSync(outputFilePath);\n }\n }\n\n if (hasDirectoryModeReport) {\n mkdirSync(path.dirname(outputFilePath), { recursive: true });\n }\n\n logMsg(\n `Start merging ${this.reportInfos.length} reports...\\nCreating template file...`,\n );\n\n try {\n // Write template without closing </html> tag so we can append\n // dump scripts before it. The closing tag is added at the end.\n const htmlEndTag = '</html>';\n const tpl = getReportTpl();\n const htmlEndIdx = tpl.lastIndexOf(htmlEndTag);\n const tplWithoutClose =\n htmlEndIdx !== -1 ? tpl.slice(0, htmlEndIdx) : tpl;\n appendFileSync(outputFilePath, tplWithoutClose);\n\n // For directory-mode output, inject base URL fix script\n if (hasDirectoryModeReport) {\n appendFileSync(outputFilePath, getBaseUrlFixScript());\n }\n\n const agentReports: ReportActionDump[] = [];\n\n // Process all reports one by one\n for (let i = 0; i < this.reportInfos.length; i++) {\n const reportInfo = this.reportInfos[i];\n logMsg(`Processing report ${i + 1}/${this.reportInfos.length}`);\n\n const { reportAttributes } = reportInfo;\n let dumpString = this.createEmptyDumpString(\n reportAttributes.testTitle,\n reportAttributes.testDescription,\n );\n let mergedGroupId = `merged-group-${i}`;\n\n if (reportInfo.reportFilePath) {\n if (isDirModeByIndex[i]) {\n // Directory mode: copy external screenshot files. A directory-mode\n // report can legitimately have no screenshots/ dir (a run that\n // captured nothing), so only copy when the source dir exists.\n const reportDir = path.dirname(reportInfo.reportFilePath);\n const screenshotsDir = path.join(reportDir, 'screenshots');\n if (existsSync(screenshotsDir)) {\n const mergedScreenshotsDir = path.join(\n path.dirname(outputFilePath),\n 'screenshots',\n );\n mkdirSync(mergedScreenshotsDir, { recursive: true });\n for (const file of readdirSync(screenshotsDir)) {\n const src = path.join(screenshotsDir, file);\n const dest = path.join(mergedScreenshotsDir, file);\n copyFileSync(src, dest);\n }\n }\n } else {\n // Inline mode: stream image scripts to output file\n streamImageScriptsToFile(reportInfo.reportFilePath, outputFilePath);\n }\n\n // Extract all dump scripts from the source report.\n // After the per-execution append refactor, a single source report\n // may contain multiple <script type=\"midscene_web_dump\"> tags\n // (one per execution). We merge them into a single ReportActionDump.\n // Filter by data-group-id to exclude false matches from the template's\n // bundled JS code, which also references the midscene_web_dump type string.\n const allDumps = extractAllDumpScriptsSync(\n reportInfo.reportFilePath,\n ).filter((d) => d.openTag.includes('data-group-id'));\n const groupIdMatch = allDumps[0]?.openTag.match(\n /data-group-id=\"([^\"]+)\"/,\n );\n if (groupIdMatch) {\n mergedGroupId = decodeURIComponent(groupIdMatch[1]);\n }\n const extractedDumpString =\n allDumps.length > 0\n ? this.mergeDumpScripts(allDumps.map((d) => d.content))\n : extractLastDumpScriptSync(reportInfo.reportFilePath);\n if (extractedDumpString) {\n dumpString = extractedDumpString;\n }\n }\n\n const agentReport = tryParseAgentReportDump(dumpString);\n if (agentReport) {\n agentReports.push(agentReport);\n }\n\n const reportHtmlStr = `${reportHTMLContent(\n {\n dumpString,\n attributes: {\n 'data-group-id': mergedGroupId,\n [DATA_SCREENSHOT_MODE_ATTR]: hasDirectoryModeReport\n ? 'directory'\n : 'inline',\n playwright_test_duration: reportAttributes.testDuration,\n playwright_test_status: reportAttributes.testStatus,\n playwright_test_title: reportAttributes.testTitle,\n playwright_test_id: reportAttributes.testId,\n playwright_test_description: reportAttributes.testDescription,\n is_merged: true,\n },\n },\n undefined,\n undefined,\n false,\n )}\\n`;\n\n appendFileSync(outputFilePath, reportHtmlStr);\n }\n\n const agentComment = mergedAgentReportComment(agentReports);\n if (agentComment) {\n appendFileSync(outputFilePath, agentComment);\n }\n\n // Close the HTML document\n appendFileSync(outputFilePath, `${htmlEndTag}\\n`);\n\n logMsg(`Successfully merged new report: ${outputFilePath}`);\n\n // Remove original reports if needed\n if (rmOriginalReports) {\n for (const info of this.reportInfos) {\n if (!info.reportFilePath) continue;\n try {\n if (isDirectoryBasedReport(info.reportFilePath)) {\n // The report owns its directory (`{name}/index.html`) — remove the\n // whole folder, whether screenshots are external or inlined.\n const reportDir = path.dirname(info.reportFilePath);\n rmSync(reportDir, { recursive: true, force: true });\n } else {\n unlinkSync(info.reportFilePath);\n }\n } catch (error) {\n logMsg(`Error deleting report ${info.reportFilePath}: ${error}`);\n }\n }\n logMsg(`Removed ${this.reportInfos.length} original reports`);\n }\n return outputFilePath;\n } catch (error) {\n logMsg(`Error in mergeReports: ${error}`);\n throw error;\n }\n }\n}\n\nexport interface SplitReportHtmlOptions {\n htmlPath: string;\n outputDir: string;\n}\n\nexport interface SplitReportHtmlResult {\n executionJsonFiles: string[];\n screenshotFiles: string[];\n}\n\nexport interface CollectedReportExecutions {\n baseDump: ReportActionDump;\n executions: IExecutionDump[];\n}\n\n/**\n * Collect executions from a report HTML, deduplicating by stable id while\n * keeping only the latest occurrence. Old-format executions without id are\n * always preserved.\n */\nexport function collectDedupedExecutions(\n htmlPath: string,\n): CollectedReportExecutions {\n let baseDump: ReportActionDump | null = null;\n let executionSerial = 0;\n const latestSerialByExecutionId = new Map<string, number>();\n\n streamDumpScriptsSync(htmlPath, (dumpScript) => {\n if (!dumpScript.openTag.includes('data-group-id')) {\n return false;\n }\n const groupedDump = ReportActionDump.fromSerializedString(\n antiEscapeScriptTag(dumpScript.content),\n );\n for (const execution of groupedDump.executions) {\n executionSerial += 1;\n if (execution.id) {\n latestSerialByExecutionId.set(execution.id, executionSerial);\n }\n }\n return false;\n });\n\n const executions: IExecutionDump[] = [];\n executionSerial = 0;\n streamDumpScriptsSync(htmlPath, (dumpScript) => {\n if (!dumpScript.openTag.includes('data-group-id')) {\n return false;\n }\n\n const groupedDump = ReportActionDump.fromSerializedString(\n antiEscapeScriptTag(dumpScript.content),\n );\n if (!baseDump) {\n baseDump = groupedDump;\n }\n\n for (const execution of groupedDump.executions) {\n executionSerial += 1;\n if (\n execution.id &&\n latestSerialByExecutionId.get(execution.id) !== executionSerial\n ) {\n continue;\n }\n executions.push(execution);\n }\n\n return false;\n });\n\n if (!baseDump) {\n throw new Error(`No report dump scripts found in ${htmlPath}`);\n }\n\n return {\n baseDump,\n executions,\n };\n}\n\nfunction extensionByMimeType(mimeType: string): 'png' | 'jpeg' {\n if (mimeType === 'image/png') return 'png';\n if (mimeType === 'image/jpeg') return 'jpeg';\n throw new Error(`Unsupported screenshot mime type: ${mimeType}`);\n}\n\nfunction externalizeScreenshotsInExecution(\n execution: IExecutionDump,\n opts: {\n htmlPath: string;\n screenshotsDir: string;\n writtenFiles: Set<string>;\n },\n): void {\n const visit = (node: unknown): void => {\n if (Array.isArray(node)) {\n for (const item of node) {\n visit(item);\n }\n return;\n }\n\n if (typeof node !== 'object' || node === null) return;\n\n const ref = normalizeScreenshotRef(node);\n if (ref) {\n const ext = extensionByMimeType(ref.mimeType);\n const fileName = `${ref.id}.${ext}`;\n const relativePath = `./screenshots/${fileName}`;\n const absolutePath = path.join(opts.screenshotsDir, fileName);\n\n if (!opts.writtenFiles.has(fileName)) {\n const resolved = resolveScreenshotSource(ref, {\n reportPath: opts.htmlPath,\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 } else {\n copyFileSync(resolved.filePath, absolutePath);\n }\n opts.writtenFiles.add(fileName);\n }\n\n ref.storage = 'file';\n ref.path = relativePath;\n return;\n }\n\n for (const value of Object.values(node)) {\n visit(value);\n }\n };\n\n visit(execution);\n}\n\n/**\n * Reverse parse a Midscene report HTML into per-execution JSON files and\n * externalized screenshots.\n */\nexport function splitReportHtmlByExecution(\n options: SplitReportHtmlOptions,\n): SplitReportHtmlResult {\n const { htmlPath, outputDir } = options;\n const screenshotsDir = path.join(outputDir, 'screenshots');\n\n mkdirSync(outputDir, { recursive: true });\n mkdirSync(screenshotsDir, { recursive: true });\n\n const executionJsonFiles: string[] = [];\n const writtenScreenshotFiles = new Set<string>();\n const { baseDump, executions } = collectDedupedExecutions(htmlPath);\n\n let fileIndex = 0;\n for (const execution of executions) {\n fileIndex += 1;\n externalizeScreenshotsInExecution(execution, {\n htmlPath,\n screenshotsDir,\n writtenFiles: writtenScreenshotFiles,\n });\n const singleExecutionDump = new ReportActionDump({\n sdkVersion: baseDump.sdkVersion,\n groupName: baseDump.groupName,\n groupDescription: baseDump.groupDescription,\n modelBriefs: baseDump.modelBriefs,\n deviceType: baseDump.deviceType,\n executions: [execution],\n });\n\n const jsonFilePath = path.join(outputDir, `${fileIndex}.execution.json`);\n writeFileSync(jsonFilePath, singleExecutionDump.serialize(2), 'utf-8');\n executionJsonFiles.push(jsonFilePath);\n }\n\n return {\n executionJsonFiles,\n screenshotFiles: Array.from(writtenScreenshotFiles)\n .sort()\n .map((fileName) => path.join(screenshotsDir, fileName)),\n };\n}\n"],"names":["screenshotModeAttrRegExp","RegExp","DATA_SCREENSHOT_MODE_ATTR","readDeclaredScreenshotMode","reportFilePath","mode","streamDumpScriptsSync","openTag","match","isDirectoryModeReport","path","declared","existsSync","isDirectoryBasedReport","dedupeExecutionsKeepLatest","executions","noIdCounter","deduped","Map","exec","key","Array","peekReportSdkVersion","dump","extractLastDumpScriptSync","warnedMismatchedVersions","Set","tryParseAgentReportDump","dumpString","trimmed","ReportActionDump","mergedAgentReportComment","reports","generateAgentReportComment","deviceTypes","report","Boolean","mergedReport","getVersion","ReportMergingTool","groupName","groupDescription","reportInfo","sourceVersion","currentVersion","logMsg","contents","unescaped","c","antiEscapeScriptTag","base","allExecutions","i","other","reportFileName","opts","rmOriginalReports","overwrite","outputDir","targetDir","getMidsceneRunSubDir","mkdirSync","isDirModeByIndex","info","hasDirectoryModeReport","resolvedName","getReportFileName","outputFilePath","Error","rmSync","unlinkSync","htmlEndTag","tpl","getReportTpl","htmlEndIdx","tplWithoutClose","appendFileSync","getBaseUrlFixScript","agentReports","reportAttributes","mergedGroupId","reportDir","screenshotsDir","mergedScreenshotsDir","file","readdirSync","src","dest","copyFileSync","streamImageScriptsToFile","allDumps","extractAllDumpScriptsSync","d","groupIdMatch","decodeURIComponent","extractedDumpString","agentReport","reportHtmlStr","reportHTMLContent","undefined","agentComment","error","collectDedupedExecutions","htmlPath","baseDump","executionSerial","latestSerialByExecutionId","dumpScript","groupedDump","execution","extensionByMimeType","mimeType","externalizeScreenshotsInExecution","visit","node","item","ref","normalizeScreenshotRef","ext","fileName","relativePath","absolutePath","resolved","resolveScreenshotSource","rawBase64","writeFileSync","Buffer","value","Object","splitReportHtmlByExecution","options","executionJsonFiles","writtenScreenshotFiles","fileIndex","singleExecutionDump","jsonFilePath"],"mappings":"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"}
@@ -2,12 +2,11 @@ import { defaultModelFamilyRequiredForLocateMessage } from "../ai-model/errors.m
2
2
  import { AiExtractElementInfo, AiLocateElement, AiLocateSection, buildSearchAreaConfig } from "../ai-model/inspect.mjs";
3
3
  import { elementDescriberInstruction } from "../ai-model/prompt/describe.mjs";
4
4
  import { AIResponseParseError, callAIWithObjectResponse } from "../ai-model/service-caller/index.mjs";
5
- import { expandSearchArea } from "../common.mjs";
6
5
  import { ServiceError } from "../types.mjs";
7
- import { compositeElementInfoImg, cropByRect } from "@midscene/shared/img";
6
+ import { compositeElementInfoImg, compositePointMarkerImg, cropByRect, resizeImgBase64 } from "@midscene/shared/img";
8
7
  import { getDebug } from "@midscene/shared/logger";
9
8
  import { assert } from "@midscene/shared/utils";
10
- import { createServiceDump, recoverDescribeResponseFromParseError } from "./utils.mjs";
9
+ import { createServiceDump, getDescribeDeepContextAreas, getDescribeDeepLocateResizeSize, getDescribeMarkerBorderThickness, getDescribeMarkerRect, getRectInCrop, recoverDescribeResponseFromParseError } from "./utils.mjs";
11
10
  function _define_property(obj, key, value) {
12
11
  if (key in obj) Object.defineProperty(obj, key, {
13
12
  value: value,
@@ -47,9 +46,9 @@ class Service {
47
46
  const taskInfo = {
48
47
  ...this.taskInfo ? this.taskInfo : {},
49
48
  durationMs: timeCost,
50
- rawResponse: JSON.stringify(rawResponse),
49
+ rawResponse,
51
50
  rawChoiceMessage,
52
- formatResponse: JSON.stringify(parseResult),
51
+ formatResponse: parseResult,
53
52
  usage,
54
53
  searchArea: searchArea.trace.sourceRect,
55
54
  searchAreaRawResponse: searchArea.trace.rawResponse,
@@ -213,7 +212,7 @@ class Service {
213
212
  durationMs: timeCost,
214
213
  rawResponse,
215
214
  rawChoiceMessage,
216
- formatResponse: JSON.stringify(parseResult),
215
+ formatResponse: parseResult,
217
216
  usage,
218
217
  reasoning_content
219
218
  };
@@ -250,28 +249,113 @@ class Service {
250
249
  assert(screenshotBase64, 'screenshot is required for service.describe');
251
250
  const systemPrompt = elementDescriberInstruction();
252
251
  const defaultRectSize = 30;
253
- const targetRect = Array.isArray(target) ? {
252
+ const targetFromPoint = Array.isArray(target);
253
+ const targetRect = targetFromPoint ? {
254
254
  left: Math.floor(target[0] - defaultRectSize / 2),
255
255
  top: Math.floor(target[1] - defaultRectSize / 2),
256
256
  width: defaultRectSize,
257
257
  height: defaultRectSize
258
258
  } : target;
259
- let imagePayload = await compositeElementInfoImg({
259
+ const targetPoint = targetFromPoint ? {
260
+ x: target[0],
261
+ y: target[1]
262
+ } : void 0;
263
+ const usePointMarker = targetFromPoint;
264
+ const imagePayload = usePointMarker ? await compositePointMarkerImg({
265
+ inputImgBase64: screenshotBase64,
266
+ size: shotSize,
267
+ point: targetPoint
268
+ }) : await compositeElementInfoImg({
260
269
  inputImgBase64: screenshotBase64,
261
270
  size: shotSize,
262
271
  elementsPositionInfo: [
263
272
  {
264
- rect: targetRect
273
+ rect: getDescribeMarkerRect(targetRect)
265
274
  }
266
275
  ],
267
- borderThickness: 3
276
+ borderThickness: getDescribeMarkerBorderThickness(targetRect),
277
+ centerPoint: true
268
278
  });
269
- if (opt?.deepLocate) {
270
- const searchArea = expandSearchArea(targetRect, shotSize);
271
- debug('describe: cropping to searchArea', searchArea);
272
- const croppedResult = await cropByRect(imagePayload, searchArea);
273
- imagePayload = croppedResult.imageBase64;
274
- }
279
+ const shouldDeepDescribe = opt?.deepDescribe;
280
+ let imageContent;
281
+ if (shouldDeepDescribe) {
282
+ const contextAreas = getDescribeDeepContextAreas(targetRect, shotSize);
283
+ const contextImages = await Promise.all(contextAreas.map(async (area)=>{
284
+ debug('describe: cropping deep context area', area);
285
+ const croppedResult = await cropByRect(screenshotBase64, area.rect);
286
+ const cropSize = {
287
+ width: croppedResult.width,
288
+ height: croppedResult.height
289
+ };
290
+ const targetInCrop = getRectInCrop(targetRect, area.rect, cropSize);
291
+ const markedCropPayload = targetFromPoint ? await compositePointMarkerImg({
292
+ inputImgBase64: croppedResult.imageBase64,
293
+ size: cropSize,
294
+ point: {
295
+ x: targetPoint.x - area.rect.left,
296
+ y: targetPoint.y - area.rect.top
297
+ }
298
+ }) : await compositeElementInfoImg({
299
+ inputImgBase64: croppedResult.imageBase64,
300
+ size: cropSize,
301
+ elementsPositionInfo: [
302
+ {
303
+ rect: getDescribeMarkerRect(targetInCrop)
304
+ }
305
+ ],
306
+ borderThickness: getDescribeMarkerBorderThickness(targetInCrop),
307
+ centerPoint: true
308
+ });
309
+ const resizeSize = getDescribeDeepLocateResizeSize(croppedResult);
310
+ return {
311
+ kind: area.kind,
312
+ imageBase64: resizeSize ? await resizeImgBase64(markedCropPayload, resizeSize) : markedCropPayload
313
+ };
314
+ }));
315
+ const contextImageContent = contextImages.flatMap((item, index)=>[
316
+ {
317
+ type: 'text',
318
+ text: `Image ${index + 2}: focused detail crop around the target, for reading text, icon shape, and exact local boundaries.`
319
+ },
320
+ {
321
+ type: 'image_url',
322
+ image_url: {
323
+ url: item.imageBase64,
324
+ detail: 'high'
325
+ }
326
+ }
327
+ ]);
328
+ imageContent = [
329
+ {
330
+ type: 'text',
331
+ text: 'Use these images together to describe the real UI target marked by the temporary callout. Do not describe the marker itself.'
332
+ },
333
+ {
334
+ type: 'text',
335
+ text: 'Image 1: full screenshot overview with the target marker, for page position and ownership context.'
336
+ },
337
+ {
338
+ type: 'image_url',
339
+ image_url: {
340
+ url: imagePayload,
341
+ detail: 'high'
342
+ }
343
+ },
344
+ ...contextImageContent
345
+ ];
346
+ } else imageContent = [
347
+ {
348
+ type: 'text',
349
+ text: 'Full screenshot with a temporary callout marking the target:'
350
+ },
351
+ {
352
+ type: 'image_url',
353
+ image_url: {
354
+ url: imagePayload,
355
+ detail: 'high'
356
+ }
357
+ }
358
+ ];
275
359
  const msgs = [
276
360
  {
277
361
  role: 'system',
@@ -279,15 +363,7 @@ class Service {
279
363
  },
280
364
  {
281
365
  role: 'user',
282
- content: [
283
- {
284
- type: 'image_url',
285
- image_url: {
286
- url: imagePayload,
287
- detail: 'high'
288
- }
289
- }
290
- ]
366
+ content: imageContent
291
367
  }
292
368
  ];
293
369
  let res;