@midscene/core 1.10.2 → 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 (116) 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/model-adapter/resolve.mjs +2 -2
  13. package/dist/es/ai-model/model-adapter/resolve.mjs.map +1 -1
  14. package/dist/es/ai-model/models/doubao.mjs +38 -60
  15. package/dist/es/ai-model/models/doubao.mjs.map +1 -1
  16. package/dist/es/ai-model/models/glm.mjs +4 -0
  17. package/dist/es/ai-model/models/glm.mjs.map +1 -1
  18. package/dist/es/ai-model/models/gpt.mjs +5 -1
  19. package/dist/es/ai-model/models/gpt.mjs.map +1 -1
  20. package/dist/es/ai-model/models/kimi.mjs +4 -0
  21. package/dist/es/ai-model/models/kimi.mjs.map +1 -1
  22. package/dist/es/ai-model/models/mimo.mjs +3 -2
  23. package/dist/es/ai-model/models/mimo.mjs.map +1 -1
  24. package/dist/es/ai-model/models/qwen.mjs.map +1 -1
  25. package/dist/es/ai-model/models/ui-tars/adapter.mjs +2 -41
  26. package/dist/es/ai-model/models/ui-tars/adapter.mjs.map +1 -1
  27. package/dist/es/ai-model/models/utils/intent.mjs +6 -0
  28. package/dist/es/ai-model/models/utils/intent.mjs.map +1 -0
  29. package/dist/es/ai-model/prompt/extraction.mjs +9 -3
  30. package/dist/es/ai-model/prompt/extraction.mjs.map +1 -1
  31. package/dist/es/ai-model/prompt/recorder-metadata-generator.mjs +2 -1
  32. package/dist/es/ai-model/prompt/recorder-metadata-generator.mjs.map +1 -1
  33. package/dist/es/ai-model/service-caller/index.mjs +40 -19
  34. package/dist/es/ai-model/service-caller/index.mjs.map +1 -1
  35. package/dist/es/ai-model/service-caller/json.mjs +31 -28
  36. package/dist/es/ai-model/service-caller/json.mjs.map +1 -1
  37. package/dist/es/dump/html-utils.mjs +63 -1
  38. package/dist/es/dump/html-utils.mjs.map +1 -1
  39. package/dist/es/dump/screenshot-restoration.mjs +6 -0
  40. package/dist/es/dump/screenshot-restoration.mjs.map +1 -1
  41. package/dist/es/index.mjs.map +1 -1
  42. package/dist/es/report-generator.mjs +28 -1
  43. package/dist/es/report-generator.mjs.map +1 -1
  44. package/dist/es/report-markdown.mjs +300 -35
  45. package/dist/es/report-markdown.mjs.map +1 -1
  46. package/dist/es/report.mjs +29 -1
  47. package/dist/es/report.mjs.map +1 -1
  48. package/dist/es/service/index.mjs +3 -3
  49. package/dist/es/service/index.mjs.map +1 -1
  50. package/dist/es/types.mjs.map +1 -1
  51. package/dist/es/utils.mjs +2 -2
  52. package/dist/lib/agent/agent.js +40 -1
  53. package/dist/lib/agent/agent.js.map +1 -1
  54. package/dist/lib/agent/metrics.js +115 -0
  55. package/dist/lib/agent/metrics.js.map +1 -0
  56. package/dist/lib/agent/task-builder.js +1 -1
  57. package/dist/lib/agent/task-builder.js.map +1 -1
  58. package/dist/lib/agent/utils.js +1 -1
  59. package/dist/lib/ai-model/connectivity/run-connectivity-test.js +9 -3
  60. package/dist/lib/ai-model/connectivity/run-connectivity-test.js.map +1 -1
  61. package/dist/lib/ai-model/inspect.js +3 -3
  62. package/dist/lib/ai-model/inspect.js.map +1 -1
  63. package/dist/lib/ai-model/model-adapter/resolve.js +1 -1
  64. package/dist/lib/ai-model/model-adapter/resolve.js.map +1 -1
  65. package/dist/lib/ai-model/models/doubao.js +38 -69
  66. package/dist/lib/ai-model/models/doubao.js.map +1 -1
  67. package/dist/lib/ai-model/models/glm.js +4 -0
  68. package/dist/lib/ai-model/models/glm.js.map +1 -1
  69. package/dist/lib/ai-model/models/gpt.js +5 -1
  70. package/dist/lib/ai-model/models/gpt.js.map +1 -1
  71. package/dist/lib/ai-model/models/kimi.js +4 -0
  72. package/dist/lib/ai-model/models/kimi.js.map +1 -1
  73. package/dist/lib/ai-model/models/mimo.js +3 -2
  74. package/dist/lib/ai-model/models/mimo.js.map +1 -1
  75. package/dist/lib/ai-model/models/qwen.js.map +1 -1
  76. package/dist/lib/ai-model/models/ui-tars/adapter.js +1 -40
  77. package/dist/lib/ai-model/models/ui-tars/adapter.js.map +1 -1
  78. package/dist/lib/ai-model/models/utils/intent.js +40 -0
  79. package/dist/lib/ai-model/models/utils/intent.js.map +1 -0
  80. package/dist/lib/ai-model/prompt/extraction.js +8 -2
  81. package/dist/lib/ai-model/prompt/extraction.js.map +1 -1
  82. package/dist/lib/ai-model/prompt/recorder-metadata-generator.js +4 -3
  83. package/dist/lib/ai-model/prompt/recorder-metadata-generator.js.map +1 -1
  84. package/dist/lib/ai-model/service-caller/index.js +44 -23
  85. package/dist/lib/ai-model/service-caller/index.js.map +1 -1
  86. package/dist/lib/ai-model/service-caller/json.js +33 -33
  87. package/dist/lib/ai-model/service-caller/json.js.map +1 -1
  88. package/dist/lib/dump/html-utils.js +65 -0
  89. package/dist/lib/dump/html-utils.js.map +1 -1
  90. package/dist/lib/dump/screenshot-restoration.js +6 -0
  91. package/dist/lib/dump/screenshot-restoration.js.map +1 -1
  92. package/dist/lib/index.js.map +1 -1
  93. package/dist/lib/report-generator.js +27 -0
  94. package/dist/lib/report-generator.js.map +1 -1
  95. package/dist/lib/report-markdown.js +300 -35
  96. package/dist/lib/report-markdown.js.map +1 -1
  97. package/dist/lib/report.js +28 -0
  98. package/dist/lib/report.js.map +1 -1
  99. package/dist/lib/service/index.js +3 -3
  100. package/dist/lib/service/index.js.map +1 -1
  101. package/dist/lib/types.js.map +1 -1
  102. package/dist/lib/utils.js +2 -2
  103. package/dist/types/agent/agent.d.ts +15 -0
  104. package/dist/types/agent/index.d.ts +1 -0
  105. package/dist/types/agent/metrics.d.ts +48 -0
  106. package/dist/types/ai-model/model-adapter/types.d.ts +8 -0
  107. package/dist/types/ai-model/models/doubao.d.ts +1 -4
  108. package/dist/types/ai-model/models/utils/intent.d.ts +2 -0
  109. package/dist/types/ai-model/service-caller/index.d.ts +8 -3
  110. package/dist/types/ai-model/service-caller/json.d.ts +19 -2
  111. package/dist/types/dump/html-utils.d.ts +2 -0
  112. package/dist/types/dump/screenshot-restoration.d.ts +1 -1
  113. package/dist/types/index.d.ts +1 -1
  114. package/dist/types/report-generator.d.ts +6 -0
  115. package/dist/types/types.d.ts +11 -2
  116. package/package.json +3 -3
@@ -1 +1 @@
1
- {"version":3,"file":"report-generator.js","sources":["webpack/runtime/define_property_getters","webpack/runtime/has_own_property","webpack/runtime/make_namespace_object","../../src/report-generator.ts"],"sourcesContent":["__webpack_require__.d = (exports, definition) => {\n\tfor(var key in definition) {\n if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n }\n }\n};","__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))","// define __esModule on exports\n__webpack_require__.r = (exports) => {\n\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n\t}\n\tObject.defineProperty(exports, '__esModule', { value: true });\n};","/*\n * PERF INVARIANT — DO NOT reintroduce sync fs APIs (writeFileSync /\n * appendFileSync) in this file's write paths. `ReportGenerator` runs on\n * the Electron main event loop during agent execution, and a single\n * progress tick appends a multi-MB ExecutionDump payload. Sync I/O here\n * blocked the loop for 20+ seconds per run, freezing IPC, scrcpy and\n * every renderer round-trip. Always use `fs/promises`. See commit\n * 6a25e05c and `report-generator-async-contract.test.ts`.\n */\nimport { existsSync, mkdirSync, readdirSync } from 'node:fs';\nimport {\n appendFile as appendFileAsync,\n writeFile as writeFileAsync,\n} from 'node:fs/promises';\nimport { dirname, join } from 'node:path';\nimport { getMidsceneRunSubDir } from '@midscene/shared/common';\nimport {\n MIDSCENE_REPORT_QUIET,\n globalConfigManager,\n} from '@midscene/shared/env';\nimport { ifInBrowser, logMsg, uuid } from '@midscene/shared/utils';\nimport {\n DATA_SCREENSHOT_MODE_ATTR,\n generateDumpScriptTag,\n generateImageScriptTag,\n getBaseUrlFixScript,\n} from './dump/html-utils';\nimport { ScreenshotStore } from './dump/screenshot-store';\nimport {\n type ExecutionDump,\n ReportActionDump,\n type ReportAttributes,\n type ReportMeta,\n type ScreenshotMode,\n} from './types';\nimport { getReportTpl } from './utils';\n\nexport interface IReportGenerator {\n /**\n * Write or update a single execution.\n * Each call appends a new dump script tag. The frontend deduplicates\n * executions with the same id/name, keeping only the last one.\n *\n * @param execution Current execution's full data\n * @param reportMeta Report-level metadata (groupName, sdkVersion, etc.)\n */\n onExecutionUpdate(\n execution: ExecutionDump,\n reportMeta: ReportMeta,\n attributes?: ReportAttributes,\n ): void;\n\n /**\n * @deprecated Use onExecutionUpdate instead. Kept for backward compatibility.\n */\n onDumpUpdate?(dump: ReportActionDump): void;\n\n /**\n * Wait for all queued write operations to complete.\n */\n flush(): Promise<void>;\n\n /**\n * Finalize the report. Calls flush() internally.\n */\n finalize(): Promise<string | undefined>;\n\n getReportPath(): string | undefined;\n}\n\nexport const nullReportGenerator: IReportGenerator = {\n onExecutionUpdate: () => {},\n flush: async () => {},\n finalize: async () => undefined,\n getReportPath: () => undefined,\n};\n\nexport function assertReportGenerationOptions(opts: {\n generateReport?: boolean;\n persistExecutionDump?: boolean;\n}): void {\n if (opts.generateReport === false && opts.persistExecutionDump === true) {\n throw new Error(\n 'persistExecutionDump cannot be true when generateReport is false',\n );\n }\n}\n\nexport class ReportGenerator implements IReportGenerator {\n private reportPath: string;\n private screenshotMode: ScreenshotMode;\n private shouldPersistExecutionDump: boolean;\n private autoPrint: boolean;\n private firstWriteDone = false;\n private executionLogIndex = 0;\n private executionLogFileIndexByExecutionKey = new Map<string, number>();\n\n // Unique identifier for this report stream — used as data-group-id\n private readonly reportStreamId: string;\n\n // Tracks screenshots already written to disk (by id) to avoid duplicates\n private screenshotStore: ScreenshotStore;\n private initialized = false;\n\n // Tracks the last execution + groupMeta for re-writing on finalize\n private lastExecution?: ExecutionDump;\n private lastReportMeta?: ReportMeta;\n private reportAttributes: Record<string, string> = {};\n\n // write queue for serial execution\n private writeQueue: Promise<void> = Promise.resolve();\n private destroyed = false;\n\n constructor(options: {\n reportPath: string;\n screenshotMode: ScreenshotMode;\n persistExecutionDump?: boolean;\n autoPrint?: boolean;\n reuseExistingReport?: boolean;\n }) {\n this.reportPath = options.reportPath;\n this.screenshotMode = options.screenshotMode;\n this.shouldPersistExecutionDump = options.persistExecutionDump ?? false;\n this.autoPrint = options.autoPrint ?? true;\n this.reportStreamId = uuid();\n this.screenshotStore = new ScreenshotStore({\n mode: this.screenshotMode === 'inline' ? 'inline' : 'directory',\n reportPath: this.reportPath,\n screenshotsDir: join(dirname(this.reportPath), 'screenshots'),\n writeInlineImage: async (id, base64) => {\n await appendFileAsync(\n this.reportPath,\n `\\n${generateImageScriptTag(id, base64)}`,\n );\n },\n alsoWriteFileCopy: this.shouldPersistExecutionDump,\n });\n if (options.reuseExistingReport) {\n this.hydrateStateFromExistingReport();\n }\n }\n\n static create(\n reportFileName: string,\n opts: {\n generateReport?: boolean;\n persistExecutionDump?: boolean;\n outputFormat?: 'single-html' | 'html-and-external-assets';\n autoPrintReportMsg?: boolean;\n reuseExistingReport?: boolean;\n },\n ): IReportGenerator {\n assertReportGenerationOptions(opts);\n validateReportFileName(reportFileName);\n if (opts.generateReport === false) return nullReportGenerator;\n\n // In browser environment, file system is not available\n if (ifInBrowser) return nullReportGenerator;\n\n const reportRootDir = getMidsceneRunSubDir('report');\n const outputDir = join(reportRootDir, reportFileName);\n const reportPath =\n opts.outputFormat === 'html-and-external-assets'\n ? join(outputDir, 'index.html')\n : join(reportRootDir, ensureHtmlFileName(reportFileName));\n return new ReportGenerator({\n reportPath,\n screenshotMode:\n opts.outputFormat === 'html-and-external-assets'\n ? 'directory'\n : 'inline',\n persistExecutionDump: opts.persistExecutionDump,\n autoPrint: opts.autoPrintReportMsg,\n reuseExistingReport: opts.reuseExistingReport,\n });\n }\n\n onExecutionUpdate(\n execution: ExecutionDump,\n reportMeta: ReportMeta,\n attributes?: ReportAttributes,\n ): void {\n this.lastExecution = execution;\n this.lastReportMeta = reportMeta;\n this.mergeReportAttributes(attributes);\n this.writeQueue = this.writeQueue.then(async () => {\n if (this.destroyed) return;\n await this.doWriteExecution(execution, reportMeta);\n });\n }\n\n async flush(): Promise<void> {\n await this.writeQueue;\n }\n\n async finalize(): Promise<string | undefined> {\n // Re-write the last execution to capture any final state changes\n if (this.lastExecution && this.lastReportMeta) {\n this.onExecutionUpdate(this.lastExecution, this.lastReportMeta);\n }\n await this.flush();\n this.destroyed = true;\n\n if (!this.initialized) {\n // No executions were ever written — no file exists\n return undefined;\n }\n\n return this.reportPath;\n }\n\n getReportPath(): string | undefined {\n return this.reportPath;\n }\n\n private printReportPath(): void {\n if (!this.autoPrint || !this.reportPath) return;\n if (globalConfigManager.getEnvConfigInBoolean(MIDSCENE_REPORT_QUIET))\n return;\n\n if (this.screenshotMode === 'directory') {\n logMsg(\n `Midscene - report file updated: npx serve ${dirname(this.reportPath)}`,\n );\n } else {\n logMsg(`Midscene - report file updated: ${this.reportPath}`);\n }\n }\n\n private async doWriteExecution(\n execution: ExecutionDump,\n reportMeta: ReportMeta,\n ): Promise<void> {\n const singleDump = this.wrapAsReportDump(execution, reportMeta);\n\n if (this.screenshotMode === 'inline') {\n await this.writeInlineExecution(execution, singleDump);\n } else {\n await this.writeDirectoryExecution(execution, singleDump);\n }\n\n if (this.shouldPersistExecutionDump) {\n await this.persistExecutionDumpToFile(execution, singleDump);\n }\n\n if (!this.firstWriteDone) {\n this.firstWriteDone = true;\n this.printReportPath();\n }\n }\n\n private mergeReportAttributes(attributes?: ReportAttributes): void {\n if (!attributes) {\n return;\n }\n\n for (const [key, value] of Object.entries(attributes)) {\n if (value === undefined || value === null) {\n continue;\n }\n this.reportAttributes[key] = String(value);\n }\n }\n\n private hydrateStateFromExistingReport(): void {\n if (!existsSync(this.reportPath)) {\n return;\n }\n\n // Reuse existing report file and append new updates instead of rewriting.\n this.initialized = true;\n\n if (!this.shouldPersistExecutionDump) {\n return;\n }\n\n const reportDir = dirname(this.reportPath);\n const existingExecutionIndices = readdirSync(reportDir)\n .map((name) => /^(\\d+)\\.execution\\.json$/.exec(name)?.[1])\n .filter((index): index is string => Boolean(index))\n .map((index) => Number.parseInt(index, 10))\n .filter((index) => Number.isFinite(index));\n\n if (existingExecutionIndices.length > 0) {\n this.executionLogIndex = Math.max(...existingExecutionIndices);\n }\n }\n\n private getDumpScriptAttributes(): Record<string, string> {\n return {\n 'data-group-id': this.reportStreamId,\n // Self-describe how this report file stores screenshots so consumers\n // (merge/delete) never have to guess the mode from the filesystem.\n [DATA_SCREENSHOT_MODE_ATTR]: this.screenshotMode,\n ...this.reportAttributes,\n };\n }\n\n /**\n * Wrap an ExecutionDump + ReportMeta into a single-execution ReportActionDump.\n */\n private wrapAsReportDump(\n execution: ExecutionDump,\n reportMeta: ReportMeta,\n ): ReportActionDump {\n return new ReportActionDump({\n sdkVersion: reportMeta.sdkVersion,\n groupName: reportMeta.groupName,\n groupDescription: reportMeta.groupDescription,\n modelBriefs: reportMeta.modelBriefs,\n deviceType: reportMeta.deviceType,\n executions: [execution],\n });\n }\n\n /**\n * Append-only inline mode: write new screenshots and a dump tag on every call.\n * The frontend deduplicates executions with the same id/name (keeps last).\n * Duplicate dump JSON is acceptable; only screenshots are deduplicated.\n *\n * All writes go through `fs/promises` so they run on libuv's thread pool\n * rather than blocking the Node event loop. A long agent run previously\n * appended multi-MB dumps (screenshots + serialized tasks) per progress\n * tick on the main thread, starving IPC and UI for >10s per stall.\n */\n private async writeInlineExecution(\n execution: ExecutionDump,\n singleDump: ReportActionDump,\n ): Promise<void> {\n const dir = dirname(this.reportPath);\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true });\n }\n\n // Initialize: write HTML template once\n if (!this.initialized) {\n await writeFileAsync(this.reportPath, getReportTpl());\n this.initialized = true;\n }\n\n // Append new screenshots (skip already-written ones)\n for (const screenshot of execution.collectScreenshots()) {\n await this.screenshotStore.persist(screenshot);\n }\n\n // Append dump tag (always — frontend keeps only last per execution id)\n const serialized = singleDump.serialize();\n await appendFileAsync(\n this.reportPath,\n `\\n${generateDumpScriptTag(serialized, this.getDumpScriptAttributes())}`,\n );\n }\n\n private async writeDirectoryExecution(\n execution: ExecutionDump,\n singleDump: ReportActionDump,\n ): Promise<void> {\n const dir = dirname(this.reportPath);\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true });\n }\n\n for (const screenshot of execution.collectScreenshots()) {\n await this.screenshotStore.persist(screenshot);\n }\n\n // 2. Append dump tag (always — frontend keeps only last per execution id)\n const serialized = singleDump.serialize();\n\n if (!this.initialized) {\n await writeFileAsync(\n this.reportPath,\n `${getReportTpl()}${getBaseUrlFixScript()}`,\n );\n this.initialized = true;\n }\n\n await appendFileAsync(\n this.reportPath,\n `\\n${generateDumpScriptTag(serialized, this.getDumpScriptAttributes())}`,\n );\n }\n\n private getExecutionLogKey(execution: ExecutionDump): string {\n if (!execution.id) {\n throw new Error(\n 'ReportGenerator: execution.id is required for persisting execution dumps',\n );\n }\n return `id:${execution.id}`;\n }\n\n private async persistExecutionDumpToFile(\n execution: ExecutionDump,\n singleDump: ReportActionDump,\n ): Promise<void> {\n const dir = dirname(this.reportPath);\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true });\n }\n\n const executionLogKey = this.getExecutionLogKey(execution);\n let fileIndex =\n this.executionLogFileIndexByExecutionKey.get(executionLogKey);\n if (!fileIndex) {\n this.executionLogIndex += 1;\n fileIndex = this.executionLogIndex;\n this.executionLogFileIndexByExecutionKey.set(executionLogKey, fileIndex);\n }\n\n const fileName = `${fileIndex}.execution.json`;\n const filePath = join(dirname(this.reportPath), fileName);\n await writeFileAsync(filePath, singleDump.serialize(2), 'utf-8');\n }\n}\n\nfunction ensureHtmlFileName(reportFileName: string): string {\n return reportFileName.endsWith('.html')\n ? reportFileName\n : `${reportFileName}.html`;\n}\n\nfunction validateReportFileName(reportFileName: string): void {\n if (!reportFileName?.trim()) {\n throw new Error('reportFileName must be a non-empty string');\n }\n\n if (/[\\\\/]/.test(reportFileName)) {\n throw new Error(\n 'reportFileName must not contain path separators (`/` or `\\\\\\\\`)',\n );\n }\n\n if (/[:*?\"<>|]/.test(reportFileName)) {\n throw new Error(\n 'reportFileName contains illegal filename characters: : * ? \" < > |',\n );\n }\n}\n"],"names":["__webpack_require__","definition","key","Object","obj","prop","Symbol","nullReportGenerator","undefined","assertReportGenerationOptions","opts","Error","ReportGenerator","reportFileName","validateReportFileName","ifInBrowser","reportRootDir","getMidsceneRunSubDir","outputDir","join","reportPath","ensureHtmlFileName","execution","reportMeta","attributes","globalConfigManager","MIDSCENE_REPORT_QUIET","logMsg","dirname","singleDump","value","String","existsSync","reportDir","existingExecutionIndices","readdirSync","name","index","Boolean","Number","Math","DATA_SCREENSHOT_MODE_ATTR","ReportActionDump","dir","mkdirSync","writeFileAsync","getReportTpl","screenshot","serialized","appendFileAsync","generateDumpScriptTag","getBaseUrlFixScript","executionLogKey","fileIndex","fileName","filePath","options","Map","Promise","uuid","ScreenshotStore","id","base64","generateImageScriptTag"],"mappings":"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"}
1
+ {"version":3,"file":"report-generator.js","sources":["webpack/runtime/define_property_getters","webpack/runtime/has_own_property","webpack/runtime/make_namespace_object","../../src/report-generator.ts"],"sourcesContent":["__webpack_require__.d = (exports, definition) => {\n\tfor(var key in definition) {\n if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n }\n }\n};","__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))","// define __esModule on exports\n__webpack_require__.r = (exports) => {\n\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n\t}\n\tObject.defineProperty(exports, '__esModule', { value: true });\n};","/*\n * PERF INVARIANT — DO NOT reintroduce sync fs APIs (writeFileSync /\n * appendFileSync) in this file's write paths. `ReportGenerator` runs on\n * the Electron main event loop during agent execution, and a single\n * progress tick appends a multi-MB ExecutionDump payload. Sync I/O here\n * blocked the loop for 20+ seconds per run, freezing IPC, scrcpy and\n * every renderer round-trip. Always use `fs/promises`. See commit\n * 6a25e05c and `report-generator-async-contract.test.ts`.\n */\nimport { existsSync, mkdirSync, readdirSync } from 'node:fs';\nimport {\n appendFile as appendFileAsync,\n writeFile as writeFileAsync,\n} from 'node:fs/promises';\nimport { dirname, join } from 'node:path';\nimport { getMidsceneRunSubDir } from '@midscene/shared/common';\nimport {\n MIDSCENE_REPORT_QUIET,\n globalConfigManager,\n} from '@midscene/shared/env';\nimport { ifInBrowser, logMsg, uuid } from '@midscene/shared/utils';\nimport {\n DATA_SCREENSHOT_MODE_ATTR,\n generateAgentReportComment,\n generateDumpScriptTag,\n generateImageScriptTag,\n getBaseUrlFixScript,\n} from './dump/html-utils';\nimport { ScreenshotStore } from './dump/screenshot-store';\nimport {\n type ExecutionDump,\n ReportActionDump,\n type ReportAttributes,\n type ReportMeta,\n type ScreenshotMode,\n} from './types';\nimport { getReportTpl } from './utils';\n\nexport interface IReportGenerator {\n /**\n * Write or update a single execution.\n * Each call appends a new dump script tag. The frontend deduplicates\n * executions with the same id/name, keeping only the last one.\n *\n * @param execution Current execution's full data\n * @param reportMeta Report-level metadata (groupName, sdkVersion, etc.)\n */\n onExecutionUpdate(\n execution: ExecutionDump,\n reportMeta: ReportMeta,\n attributes?: ReportAttributes,\n ): void;\n\n /**\n * @deprecated Use onExecutionUpdate instead. Kept for backward compatibility.\n */\n onDumpUpdate?(dump: ReportActionDump): void;\n\n /**\n * Wait for all queued write operations to complete.\n */\n flush(): Promise<void>;\n\n /**\n * Finalize the report. Calls flush() internally.\n */\n finalize(): Promise<string | undefined>;\n\n getReportPath(): string | undefined;\n}\n\nexport const nullReportGenerator: IReportGenerator = {\n onExecutionUpdate: () => {},\n flush: async () => {},\n finalize: async () => undefined,\n getReportPath: () => undefined,\n};\n\nexport function assertReportGenerationOptions(opts: {\n generateReport?: boolean;\n persistExecutionDump?: boolean;\n}): void {\n if (opts.generateReport === false && opts.persistExecutionDump === true) {\n throw new Error(\n 'persistExecutionDump cannot be true when generateReport is false',\n );\n }\n}\n\nexport class ReportGenerator implements IReportGenerator {\n private reportPath: string;\n private screenshotMode: ScreenshotMode;\n private shouldPersistExecutionDump: boolean;\n private autoPrint: boolean;\n private firstWriteDone = false;\n private executionLogIndex = 0;\n private executionLogFileIndexByExecutionKey = new Map<string, number>();\n\n // Unique identifier for this report stream — used as data-group-id\n private readonly reportStreamId: string;\n\n // Tracks screenshots already written to disk (by id) to avoid duplicates\n private screenshotStore: ScreenshotStore;\n private initialized = false;\n\n // Tracks the last execution + groupMeta for re-writing on finalize\n private lastExecution?: ExecutionDump;\n private lastReportMeta?: ReportMeta;\n private executionsByKey = new Map<string, ExecutionDump>();\n private executionCommentKeyByObject = new WeakMap<ExecutionDump, string>();\n private executionCommentKeyIndex = 0;\n private reportAttributes: Record<string, string> = {};\n private agentCommentWritten = false;\n\n // write queue for serial execution\n private writeQueue: Promise<void> = Promise.resolve();\n private destroyed = false;\n\n constructor(options: {\n reportPath: string;\n screenshotMode: ScreenshotMode;\n persistExecutionDump?: boolean;\n autoPrint?: boolean;\n reuseExistingReport?: boolean;\n }) {\n this.reportPath = options.reportPath;\n this.screenshotMode = options.screenshotMode;\n this.shouldPersistExecutionDump = options.persistExecutionDump ?? false;\n this.autoPrint = options.autoPrint ?? true;\n this.reportStreamId = uuid();\n this.screenshotStore = new ScreenshotStore({\n mode: this.screenshotMode === 'inline' ? 'inline' : 'directory',\n reportPath: this.reportPath,\n screenshotsDir: join(dirname(this.reportPath), 'screenshots'),\n writeInlineImage: async (id, base64) => {\n await appendFileAsync(\n this.reportPath,\n `\\n${generateImageScriptTag(id, base64)}`,\n );\n },\n alsoWriteFileCopy: this.shouldPersistExecutionDump,\n });\n if (options.reuseExistingReport) {\n this.hydrateStateFromExistingReport();\n }\n }\n\n static create(\n reportFileName: string,\n opts: {\n generateReport?: boolean;\n persistExecutionDump?: boolean;\n outputFormat?: 'single-html' | 'html-and-external-assets';\n autoPrintReportMsg?: boolean;\n reuseExistingReport?: boolean;\n },\n ): IReportGenerator {\n assertReportGenerationOptions(opts);\n validateReportFileName(reportFileName);\n if (opts.generateReport === false) return nullReportGenerator;\n\n // In browser environment, file system is not available\n if (ifInBrowser) return nullReportGenerator;\n\n const reportRootDir = getMidsceneRunSubDir('report');\n const outputDir = join(reportRootDir, reportFileName);\n const reportPath =\n opts.outputFormat === 'html-and-external-assets'\n ? join(outputDir, 'index.html')\n : join(reportRootDir, ensureHtmlFileName(reportFileName));\n return new ReportGenerator({\n reportPath,\n screenshotMode:\n opts.outputFormat === 'html-and-external-assets'\n ? 'directory'\n : 'inline',\n persistExecutionDump: opts.persistExecutionDump,\n autoPrint: opts.autoPrintReportMsg,\n reuseExistingReport: opts.reuseExistingReport,\n });\n }\n\n onExecutionUpdate(\n execution: ExecutionDump,\n reportMeta: ReportMeta,\n attributes?: ReportAttributes,\n ): void {\n this.lastExecution = execution;\n this.lastReportMeta = reportMeta;\n this.executionsByKey.set(this.getExecutionCommentKey(execution), execution);\n this.mergeReportAttributes(attributes);\n this.writeQueue = this.writeQueue.then(async () => {\n if (this.destroyed) return;\n await this.doWriteExecution(execution, reportMeta);\n });\n }\n\n async flush(): Promise<void> {\n await this.writeQueue;\n }\n\n async finalize(): Promise<string | undefined> {\n // Re-write the last execution to capture any final state changes\n if (this.lastExecution && this.lastReportMeta) {\n this.onExecutionUpdate(this.lastExecution, this.lastReportMeta);\n }\n await this.flush();\n this.destroyed = true;\n\n if (!this.initialized) {\n // No executions were ever written — no file exists\n return undefined;\n }\n\n await this.appendAgentReportComment();\n return this.reportPath;\n }\n\n getReportPath(): string | undefined {\n return this.reportPath;\n }\n\n private printReportPath(): void {\n if (!this.autoPrint || !this.reportPath) return;\n if (globalConfigManager.getEnvConfigInBoolean(MIDSCENE_REPORT_QUIET))\n return;\n\n if (this.screenshotMode === 'directory') {\n logMsg(\n `Midscene - report file updated: npx serve ${dirname(this.reportPath)}`,\n );\n } else {\n logMsg(`Midscene - report file updated: ${this.reportPath}`);\n }\n }\n\n private async doWriteExecution(\n execution: ExecutionDump,\n reportMeta: ReportMeta,\n ): Promise<void> {\n const singleDump = this.wrapAsReportDump(execution, reportMeta);\n\n if (this.screenshotMode === 'inline') {\n await this.writeInlineExecution(execution, singleDump);\n } else {\n await this.writeDirectoryExecution(execution, singleDump);\n }\n\n if (this.shouldPersistExecutionDump) {\n await this.persistExecutionDumpToFile(execution, singleDump);\n }\n\n if (!this.firstWriteDone) {\n this.firstWriteDone = true;\n this.printReportPath();\n }\n }\n\n private mergeReportAttributes(attributes?: ReportAttributes): void {\n if (!attributes) {\n return;\n }\n\n for (const [key, value] of Object.entries(attributes)) {\n if (value === undefined || value === null) {\n continue;\n }\n this.reportAttributes[key] = String(value);\n }\n }\n\n private hydrateStateFromExistingReport(): void {\n if (!existsSync(this.reportPath)) {\n return;\n }\n\n // Reuse existing report file and append new updates instead of rewriting.\n this.initialized = true;\n\n if (!this.shouldPersistExecutionDump) {\n return;\n }\n\n const reportDir = dirname(this.reportPath);\n const existingExecutionIndices = readdirSync(reportDir)\n .map((name) => /^(\\d+)\\.execution\\.json$/.exec(name)?.[1])\n .filter((index): index is string => Boolean(index))\n .map((index) => Number.parseInt(index, 10))\n .filter((index) => Number.isFinite(index));\n\n if (existingExecutionIndices.length > 0) {\n this.executionLogIndex = Math.max(...existingExecutionIndices);\n }\n }\n\n private getDumpScriptAttributes(): Record<string, string> {\n return {\n 'data-group-id': this.reportStreamId,\n // Self-describe how this report file stores screenshots so consumers\n // (merge/delete) never have to guess the mode from the filesystem.\n [DATA_SCREENSHOT_MODE_ATTR]: this.screenshotMode,\n ...this.reportAttributes,\n };\n }\n\n /**\n * Wrap an ExecutionDump + ReportMeta into a single-execution ReportActionDump.\n */\n private wrapAsReportDump(\n execution: ExecutionDump,\n reportMeta: ReportMeta,\n ): ReportActionDump {\n return new ReportActionDump({\n sdkVersion: reportMeta.sdkVersion,\n groupName: reportMeta.groupName,\n groupDescription: reportMeta.groupDescription,\n modelBriefs: reportMeta.modelBriefs,\n deviceType: reportMeta.deviceType,\n executions: [execution],\n });\n }\n\n /**\n * Append-only inline mode: write new screenshots and a dump tag on every call.\n * The frontend deduplicates executions with the same id/name (keeps last).\n * Duplicate dump JSON is acceptable; only screenshots are deduplicated.\n *\n * All writes go through `fs/promises` so they run on libuv's thread pool\n * rather than blocking the Node event loop. A long agent run previously\n * appended multi-MB dumps (screenshots + serialized tasks) per progress\n * tick on the main thread, starving IPC and UI for >10s per stall.\n */\n private async writeInlineExecution(\n execution: ExecutionDump,\n singleDump: ReportActionDump,\n ): Promise<void> {\n const dir = dirname(this.reportPath);\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true });\n }\n\n // Initialize: write HTML template once\n if (!this.initialized) {\n await writeFileAsync(this.reportPath, getReportTpl());\n this.initialized = true;\n }\n\n // Append new screenshots (skip already-written ones)\n for (const screenshot of execution.collectScreenshots()) {\n await this.screenshotStore.persist(screenshot);\n }\n\n // Append dump tag (always — frontend keeps only last per execution id)\n const serialized = singleDump.serialize();\n await appendFileAsync(\n this.reportPath,\n `\\n${generateDumpScriptTag(serialized, this.getDumpScriptAttributes())}`,\n );\n }\n\n private async writeDirectoryExecution(\n execution: ExecutionDump,\n singleDump: ReportActionDump,\n ): Promise<void> {\n const dir = dirname(this.reportPath);\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true });\n }\n\n for (const screenshot of execution.collectScreenshots()) {\n await this.screenshotStore.persist(screenshot);\n }\n\n // 2. Append dump tag (always — frontend keeps only last per execution id)\n const serialized = singleDump.serialize();\n\n if (!this.initialized) {\n await writeFileAsync(\n this.reportPath,\n `${getReportTpl()}${getBaseUrlFixScript()}`,\n );\n this.initialized = true;\n }\n\n await appendFileAsync(\n this.reportPath,\n `\\n${generateDumpScriptTag(serialized, this.getDumpScriptAttributes())}`,\n );\n }\n\n private async appendAgentReportComment(): Promise<void> {\n if (\n this.agentCommentWritten ||\n !this.lastReportMeta ||\n this.executionsByKey.size === 0\n ) {\n return;\n }\n\n const reportDump = new ReportActionDump({\n sdkVersion: this.lastReportMeta.sdkVersion,\n groupName: this.lastReportMeta.groupName,\n groupDescription: this.lastReportMeta.groupDescription,\n modelBriefs: this.lastReportMeta.modelBriefs,\n deviceType: this.lastReportMeta.deviceType,\n executions: Array.from(this.executionsByKey.values()),\n });\n await appendFileAsync(\n this.reportPath,\n `\\n${generateAgentReportComment(reportDump)}`,\n );\n this.agentCommentWritten = true;\n }\n\n private getExecutionLogKey(execution: ExecutionDump): string {\n if (!execution.id) {\n throw new Error(\n 'ReportGenerator: execution.id is required for persisting execution dumps',\n );\n }\n return `id:${execution.id}`;\n }\n\n private getExecutionCommentKey(execution: ExecutionDump): string {\n if (execution.id) {\n return `id:${execution.id}`;\n }\n\n const existingKey = this.executionCommentKeyByObject.get(execution);\n if (existingKey) {\n return existingKey;\n }\n\n const key = `no-id:${this.executionCommentKeyIndex++}`;\n this.executionCommentKeyByObject.set(execution, key);\n return key;\n }\n\n private async persistExecutionDumpToFile(\n execution: ExecutionDump,\n singleDump: ReportActionDump,\n ): Promise<void> {\n const dir = dirname(this.reportPath);\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true });\n }\n\n const executionLogKey = this.getExecutionLogKey(execution);\n let fileIndex =\n this.executionLogFileIndexByExecutionKey.get(executionLogKey);\n if (!fileIndex) {\n this.executionLogIndex += 1;\n fileIndex = this.executionLogIndex;\n this.executionLogFileIndexByExecutionKey.set(executionLogKey, fileIndex);\n }\n\n const fileName = `${fileIndex}.execution.json`;\n const filePath = join(dirname(this.reportPath), fileName);\n await writeFileAsync(filePath, singleDump.serialize(2), 'utf-8');\n }\n}\n\nfunction ensureHtmlFileName(reportFileName: string): string {\n return reportFileName.endsWith('.html')\n ? reportFileName\n : `${reportFileName}.html`;\n}\n\nfunction validateReportFileName(reportFileName: string): void {\n if (!reportFileName?.trim()) {\n throw new Error('reportFileName must be a non-empty string');\n }\n\n if (/[\\\\/]/.test(reportFileName)) {\n throw new Error(\n 'reportFileName must not contain path separators (`/` or `\\\\\\\\`)',\n );\n }\n\n if (/[:*?\"<>|]/.test(reportFileName)) {\n throw new Error(\n 'reportFileName contains illegal filename characters: : * ? \" < > |',\n );\n }\n}\n"],"names":["__webpack_require__","definition","key","Object","obj","prop","Symbol","nullReportGenerator","undefined","assertReportGenerationOptions","opts","Error","ReportGenerator","reportFileName","validateReportFileName","ifInBrowser","reportRootDir","getMidsceneRunSubDir","outputDir","join","reportPath","ensureHtmlFileName","execution","reportMeta","attributes","globalConfigManager","MIDSCENE_REPORT_QUIET","logMsg","dirname","singleDump","value","String","existsSync","reportDir","existingExecutionIndices","readdirSync","name","index","Boolean","Number","Math","DATA_SCREENSHOT_MODE_ATTR","ReportActionDump","dir","mkdirSync","writeFileAsync","getReportTpl","screenshot","serialized","appendFileAsync","generateDumpScriptTag","getBaseUrlFixScript","reportDump","Array","generateAgentReportComment","existingKey","executionLogKey","fileIndex","fileName","filePath","options","Map","WeakMap","Promise","uuid","ScreenshotStore","id","base64","generateImageScriptTag"],"mappings":"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"}
@@ -27,10 +27,12 @@ __webpack_require__.d(__webpack_exports__, {
27
27
  executionToMarkdown: ()=>executionToMarkdown,
28
28
  reportToMarkdown: ()=>reportToMarkdown
29
29
  });
30
- const external_node_path_namespaceObject = require("node:path");
31
30
  const ui_utils_js_namespaceObject = require("./agent/ui-utils.js");
32
31
  const external_screenshot_item_js_namespaceObject = require("./screenshot-item.js");
33
32
  const screenshot_store_js_namespaceObject = require("./dump/screenshot-store.js");
33
+ const screenshotDataUrlPattern = /^data:image\/(png|jpeg|jpg);base64,([\s\S]*)$/i;
34
+ const rawBase64BodyPattern = /^[a-zA-Z0-9+/=\s]+$/;
35
+ const jsonContextMaxStringLength = 12000;
34
36
  function toExecutionDump(execution) {
35
37
  if (!execution || 'object' != typeof execution) throw new Error('executionToMarkdown: execution is required');
36
38
  if (!Array.isArray(execution.tasks)) throw new Error('executionToMarkdown: execution.tasks must be an array');
@@ -46,6 +48,165 @@ function formatTime(ts) {
46
48
  if ('number' != typeof ts || Number.isNaN(ts)) return 'N/A';
47
49
  return new Date(ts).toISOString();
48
50
  }
51
+ function formatValue(value) {
52
+ if (null == value || '' === value) return 'N/A';
53
+ if ('number' == typeof value) return Number.isFinite(value) ? String(value) : 'N/A';
54
+ return String(value);
55
+ }
56
+ function escapeTableCell(value) {
57
+ return formatValue(value).replace(/\|/g, '\\|').replace(/\n/g, '<br>');
58
+ }
59
+ function markdownTable(headers, rows) {
60
+ return [
61
+ `| ${headers.map(escapeTableCell).join(' | ')} |`,
62
+ `| ${headers.map(()=>'---').join(' | ')} |`,
63
+ ...rows.map((row)=>`| ${row.map(escapeTableCell).join(' | ')} |`)
64
+ ];
65
+ }
66
+ function modelBriefRows(modelBriefs) {
67
+ return modelBriefs.map((brief)=>[
68
+ brief.intent || 'default',
69
+ brief.name || 'N/A',
70
+ brief.modelDescription || 'N/A'
71
+ ]);
72
+ }
73
+ function modelRowsFromUsage(report) {
74
+ const rows = new Map();
75
+ const addUsage = (source, usage)=>{
76
+ if (!hasUsage(usage)) return;
77
+ const intent = usage.intent || source;
78
+ const model = usage.model_name || usage.response_model_name;
79
+ const description = usage.model_description;
80
+ const key = `${intent}\0${model || ''}\0${description || ''}`;
81
+ if (rows.has(key)) return;
82
+ rows.set(key, [
83
+ intent,
84
+ model || 'N/A',
85
+ description || 'N/A'
86
+ ]);
87
+ };
88
+ for (const execution of report.executions)for (const task of execution.tasks){
89
+ const taskWithUsage = task;
90
+ addUsage('main', taskWithUsage.usage);
91
+ addUsage('searchArea', taskWithUsage.searchAreaUsage);
92
+ }
93
+ return Array.from(rows.values());
94
+ }
95
+ function usageValue(usage, key) {
96
+ const value = usage?.[key];
97
+ return 'number' == typeof value && Number.isFinite(value) ? value : 0;
98
+ }
99
+ function hasUsage(usage) {
100
+ if (!usage) return false;
101
+ return [
102
+ 'intent',
103
+ 'model_name',
104
+ "model_description",
105
+ 'prompt_tokens',
106
+ 'cached_input',
107
+ 'completion_tokens',
108
+ 'total_tokens',
109
+ 'time_cost',
110
+ 'request_id'
111
+ ].some((key)=>void 0 !== usage[key] && null !== usage[key]);
112
+ }
113
+ function usageTotalTokens(usage) {
114
+ const explicitTotal = usageValue(usage, 'total_tokens');
115
+ if (explicitTotal > 0) return explicitTotal;
116
+ return usageValue(usage, 'prompt_tokens') + usageValue(usage, 'completion_tokens');
117
+ }
118
+ function usageModelName(usage) {
119
+ return usage.model_name || usage.response_model_name || usage.model_description || usage.intent || 'Unknown';
120
+ }
121
+ function usageRowsForTask(task) {
122
+ const taskWithUsage = task;
123
+ const rows = [];
124
+ const appendUsage = (source, usage)=>{
125
+ if (!hasUsage(usage)) return;
126
+ rows.push([
127
+ source,
128
+ usage.intent,
129
+ usage.model_name || usage.response_model_name,
130
+ usage.model_description,
131
+ usageValue(usage, 'prompt_tokens'),
132
+ usageValue(usage, 'cached_input'),
133
+ usageValue(usage, 'completion_tokens'),
134
+ usageTotalTokens(usage),
135
+ usageValue(usage, 'time_cost'),
136
+ usage.request_id
137
+ ]);
138
+ };
139
+ appendUsage('main', taskWithUsage.usage);
140
+ appendUsage('searchArea', taskWithUsage.searchAreaUsage);
141
+ return rows;
142
+ }
143
+ function collectUsageTotals(report) {
144
+ const totalsByModel = new Map();
145
+ const addUsage = (usage)=>{
146
+ if (!hasUsage(usage)) return;
147
+ const modelName = usageModelName(usage);
148
+ const current = totalsByModel.get(modelName) ?? {
149
+ calls: 0,
150
+ prompt: 0,
151
+ cachedInput: 0,
152
+ completion: 0,
153
+ total: 0,
154
+ timeCost: 0
155
+ };
156
+ totalsByModel.set(modelName, {
157
+ calls: current.calls + 1,
158
+ prompt: current.prompt + usageValue(usage, 'prompt_tokens'),
159
+ cachedInput: current.cachedInput + usageValue(usage, 'cached_input'),
160
+ completion: current.completion + usageValue(usage, 'completion_tokens'),
161
+ total: current.total + usageTotalTokens(usage),
162
+ timeCost: current.timeCost + usageValue(usage, 'time_cost')
163
+ });
164
+ };
165
+ for (const execution of report.executions)for (const task of execution.tasks){
166
+ const taskWithUsage = task;
167
+ addUsage(taskWithUsage.usage);
168
+ addUsage(taskWithUsage.searchAreaUsage);
169
+ }
170
+ return totalsByModel;
171
+ }
172
+ function sanitizeJsonForMarkdown(value, seen = new WeakSet()) {
173
+ if ('string' == typeof value) {
174
+ if (screenshotDataUrlPattern.test(value) || value.length > 1000000 || /^[a-zA-Z0-9+/=\s]{100000,}$/.test(value)) return `[omitted image/base64 string, length=${value.length}]`;
175
+ if (value.length > jsonContextMaxStringLength) return `${value.slice(0, jsonContextMaxStringLength)}\n[truncated, original length=${value.length}]`;
176
+ return value;
177
+ }
178
+ if (null === value || 'number' == typeof value || 'boolean' == typeof value) return value;
179
+ if ('function' == typeof value) return '[omitted function]';
180
+ if ('object' != typeof value || void 0 === value) return;
181
+ if (seen.has(value)) return '[omitted circular reference]';
182
+ seen.add(value);
183
+ if (Array.isArray(value)) return value.map((item)=>sanitizeJsonForMarkdown(item, seen));
184
+ const input = value;
185
+ const output = {};
186
+ for (const [key, entry] of Object.entries(input))if (void 0 !== entry && 'function' != typeof entry) {
187
+ if ('string' == typeof entry && /(^|_|-)(base64|screenshotBase64|imageBase64)$/i.test(key)) {
188
+ output[key] = `[omitted base64 string, length=${entry.length}]`;
189
+ continue;
190
+ }
191
+ output[key] = sanitizeJsonForMarkdown(entry, seen);
192
+ }
193
+ seen.delete(value);
194
+ return output;
195
+ }
196
+ function appendJsonSection(lines, title, value) {
197
+ if (null == value || '' === value) return;
198
+ const sanitized = sanitizeJsonForMarkdown(value);
199
+ if (void 0 === sanitized) return;
200
+ lines.push('', `### ${title}`, '', '```json');
201
+ lines.push(JSON.stringify(sanitized, null, 2));
202
+ lines.push('```');
203
+ }
204
+ function appendTextSection(lines, title, value) {
205
+ if ('string' != typeof value || !value.trim()) return;
206
+ lines.push('', `### ${title}`, '', '```text');
207
+ lines.push(value.replace(/```/g, '` ` `'));
208
+ lines.push('```');
209
+ }
49
210
  function resolveTaskTiming(task) {
50
211
  const timing = task.timing;
51
212
  if (!timing) return {};
@@ -86,16 +247,35 @@ function extractLocateCenter(task) {
86
247
  ];
87
248
  }
88
249
  function tryExtractBase64(screenshot) {
250
+ if ('string' == typeof screenshot) {
251
+ const trimmedScreenshot = screenshot.trim();
252
+ const dataUrlMatch = trimmedScreenshot.match(screenshotDataUrlPattern);
253
+ if (dataUrlMatch) {
254
+ const format = 'jpg' === dataUrlMatch[1].toLowerCase() ? 'jpeg' : 'png';
255
+ const base64Body = dataUrlMatch[2].replace(/\s/g, '');
256
+ if (!base64Body) return;
257
+ return `data:image/${format};base64,${base64Body}`;
258
+ }
259
+ if (trimmedScreenshot.startsWith('data:') || !rawBase64BodyPattern.test(trimmedScreenshot)) return;
260
+ const base64Body = trimmedScreenshot.replace(/\s/g, '');
261
+ return base64Body ? `data:image/png;base64,${base64Body}` : void 0;
262
+ }
89
263
  if (!screenshot || 'object' != typeof screenshot) return;
90
264
  const s = screenshot;
91
265
  if ('string' == typeof s.base64 && s.base64.length > 0) return s.base64;
92
266
  }
93
- function screenshotAttachment(screenshot, screenshotBaseDir, executionIndex, taskIndex) {
267
+ function restoredSourceRef(screenshot) {
268
+ if (!screenshot || 'object' != typeof screenshot) return;
269
+ const sourceRef = screenshot.sourceRef;
270
+ return (0, screenshot_store_js_namespaceObject.normalizeScreenshotRef)(sourceRef) ?? void 0;
271
+ }
272
+ function screenshotAttachment(screenshot, screenshotBaseDir, executionIndex, taskIndex, options) {
273
+ const markdownLabel = options?.label || `task-${taskIndex + 1}`;
94
274
  if (screenshot instanceof external_screenshot_item_js_namespaceObject.ScreenshotItem) {
95
275
  const ext = screenshot.extension;
96
276
  const suggestedFileName = `execution-${executionIndex + 1}-task-${taskIndex + 1}-${screenshot.id}.${ext}`;
97
277
  return {
98
- markdown: `\n![task-${taskIndex + 1}](${screenshotBaseDir}/${suggestedFileName})`,
278
+ markdown: `\n![${markdownLabel}](${screenshotBaseDir}/${suggestedFileName})`,
99
279
  attachment: {
100
280
  id: screenshot.id,
101
281
  suggestedFileName,
@@ -111,7 +291,7 @@ function screenshotAttachment(screenshot, screenshotBaseDir, executionIndex, tas
111
291
  const ext = 'image/jpeg' === ref.mimeType ? 'jpeg' : 'png';
112
292
  const suggestedFileName = `execution-${executionIndex + 1}-task-${taskIndex + 1}-${ref.id}.${ext}`;
113
293
  return {
114
- markdown: `\n![task-${taskIndex + 1}](${screenshotBaseDir}/${suggestedFileName})`,
294
+ markdown: `\n![${markdownLabel}](${screenshotBaseDir}/${suggestedFileName})`,
115
295
  attachment: {
116
296
  id: ref.id,
117
297
  suggestedFileName,
@@ -123,13 +303,31 @@ function screenshotAttachment(screenshot, screenshotBaseDir, executionIndex, tas
123
303
  }
124
304
  };
125
305
  }
306
+ const sourceRef = restoredSourceRef(screenshot);
307
+ if (sourceRef) {
308
+ const ext = 'image/jpeg' === sourceRef.mimeType ? 'jpeg' : 'png';
309
+ const suggestedFileName = `execution-${executionIndex + 1}-task-${taskIndex + 1}-${sourceRef.id}.${ext}`;
310
+ return {
311
+ markdown: `\n![${markdownLabel}](${screenshotBaseDir}/${suggestedFileName})`,
312
+ attachment: {
313
+ id: sourceRef.id,
314
+ suggestedFileName,
315
+ sourceRef,
316
+ mimeType: sourceRef.mimeType,
317
+ executionIndex,
318
+ taskIndex,
319
+ base64Data: tryExtractBase64(screenshot)
320
+ }
321
+ };
322
+ }
126
323
  const base64 = tryExtractBase64(screenshot);
127
324
  if (base64) {
128
325
  const ext = base64.startsWith('data:image/jpeg') ? 'jpeg' : 'png';
129
- const id = `restored-${executionIndex + 1}-${taskIndex + 1}`;
326
+ const idSuffix = options?.fallbackIdSuffix ? `-${options.fallbackIdSuffix}` : '';
327
+ const id = `restored-${executionIndex + 1}-${taskIndex + 1}${idSuffix}`;
130
328
  const suggestedFileName = `execution-${executionIndex + 1}-task-${taskIndex + 1}-${id}.${ext}`;
131
329
  return {
132
- markdown: `\n![task-${taskIndex + 1}](${screenshotBaseDir}/${suggestedFileName})`,
330
+ markdown: `\n![${markdownLabel}](${screenshotBaseDir}/${suggestedFileName})`,
133
331
  attachment: {
134
332
  id,
135
333
  suggestedFileName,
@@ -142,27 +340,46 @@ function screenshotAttachment(screenshot, screenshotBaseDir, executionIndex, tas
142
340
  }
143
341
  throw new Error(`executionToMarkdown: missing screenshot for execution #${executionIndex + 1} task #${taskIndex + 1}`);
144
342
  }
145
- function recorderMarkdownSection(recorder, screenshotBaseDir, executionIndex, taskIndex) {
146
- if (!recorder?.length) return {
147
- lines: [],
148
- attachments: []
149
- };
343
+ function imageLabelSuffix(value) {
344
+ return value.toLowerCase().replace(/[^a-z0-9._-]+/g, '-').replace(/-+/g, '-').replace(/^-|-$/g, '') || 'screenshot';
345
+ }
346
+ function screenshotsMarkdownSection(task, screenshotBaseDir, executionIndex, taskIndex) {
150
347
  const lines = [
151
348
  '',
152
- '### Recorder'
349
+ '### Screenshots'
153
350
  ];
154
351
  const attachments = [];
155
- recorder.forEach((item, recorderIndex)=>{
156
- const descriptionText = item.description ? `, description=${item.description}` : '';
157
- lines.push(`- #${recorderIndex + 1} type=${item.type}, ts=${formatTime(item.ts)}, timing=${item.timing || 'N/A'}${descriptionText}`);
352
+ let screenshotIndex = 0;
353
+ if (task.uiContext?.screenshot) {
354
+ const imageResult = screenshotAttachment(task.uiContext.screenshot, screenshotBaseDir, executionIndex, taskIndex, {
355
+ fallbackIdSuffix: 'ui-context',
356
+ label: `task-${taskIndex + 1}-ui-context`
357
+ });
358
+ const time = resolveTaskTiming(task);
359
+ screenshotIndex += 1;
360
+ lines.push(`- #${screenshotIndex} type=ui-context, ts=${formatTime(time.start)}, timing=ui-context`);
361
+ lines.push(imageResult.markdown);
362
+ attachments.push(imageResult.attachment);
363
+ }
364
+ task.recorder?.forEach((item, recorderIndex)=>{
158
365
  if (!item.screenshot) return;
159
- const imageResult = screenshotAttachment(item.screenshot, screenshotBaseDir, executionIndex, taskIndex);
366
+ const descriptionText = item.description ? `, description=${item.description}` : '';
367
+ const timing = item.timing || 'N/A';
368
+ screenshotIndex += 1;
369
+ lines.push(`- #${screenshotIndex} type=${item.type}, ts=${formatTime(item.ts)}, timing=${timing}${descriptionText}`);
370
+ const imageResult = screenshotAttachment(item.screenshot, screenshotBaseDir, executionIndex, taskIndex, {
371
+ fallbackIdSuffix: `recorder-${recorderIndex + 1}`,
372
+ label: `task-${taskIndex + 1}-${imageLabelSuffix(timing)}`
373
+ });
160
374
  lines.push(imageResult.markdown);
161
375
  attachments.push(imageResult.attachment);
162
376
  });
163
- return {
377
+ return screenshotIndex > 0 ? {
164
378
  lines,
165
379
  attachments
380
+ } : {
381
+ lines: [],
382
+ attachments
166
383
  };
167
384
  }
168
385
  function renderExecution(executionRaw, executionIndex, options) {
@@ -174,11 +391,15 @@ function renderExecution(executionRaw, executionIndex, options) {
174
391
  if (execution.description) lines.push('', execution.description);
175
392
  lines.push('', `- Execution start: ${formatTime(execution.logTime)}`);
176
393
  lines.push(`- Task count: ${execution.tasks.length}`);
394
+ if (execution.aiActContext) appendTextSection(lines, 'AI Action Context', execution.aiActContext);
177
395
  execution.tasks.forEach((task, taskIndex)=>{
178
396
  const title = (0, ui_utils_js_namespaceObject.typeStr)(task);
179
397
  const detail = safeTaskParam(task);
180
398
  const time = resolveTaskTiming(task);
181
399
  lines.push('', `## ${taskIndex + 1}. ${title}${detail ? ` - ${detail}` : ''}`);
400
+ lines.push(`- Task ID: ${task.taskId || 'N/A'}`);
401
+ lines.push(`- Type: ${task.type || 'N/A'}`);
402
+ lines.push(`- SubType: ${task.subType || 'N/A'}`);
182
403
  lines.push(`- Status: ${task.status || 'unknown'}`);
183
404
  lines.push(`- Start: ${formatTime(time.start)}`);
184
405
  lines.push(`- End: ${formatTime(time.end)}`);
@@ -189,15 +410,31 @@ function renderExecution(executionRaw, executionIndex, options) {
189
410
  if (locateCenter) lines.push(`- Locate center: (${locateCenter[0]}, ${locateCenter[1]})`);
190
411
  }
191
412
  if (task.errorMessage) lines.push(`- Error: ${task.errorMessage}`);
192
- if (task.uiContext?.screenshot) {
193
- const imageResult = screenshotAttachment(task.uiContext.screenshot, screenshotBaseDir, executionIndex, taskIndex);
194
- lines.push(imageResult.markdown);
195
- attachments.push(imageResult.attachment);
413
+ const usageRows = usageRowsForTask(task);
414
+ if (usageRows.length) {
415
+ lines.push('', '### Model Usage');
416
+ lines.push(...markdownTable([
417
+ 'Source',
418
+ 'Intent',
419
+ 'Model',
420
+ "Description",
421
+ 'Prompt Tokens',
422
+ 'Cached Input',
423
+ 'Completion Tokens',
424
+ 'Total Tokens',
425
+ 'Time Cost(ms)',
426
+ 'Request ID'
427
+ ], usageRows));
196
428
  }
197
- const recorderSection = recorderMarkdownSection(task.recorder, screenshotBaseDir, executionIndex, taskIndex);
198
- if (recorderSection.lines.length) {
199
- lines.push(...recorderSection.lines);
200
- attachments.push(...recorderSection.attachments);
429
+ appendJsonSection(lines, 'Param', task.param);
430
+ appendJsonSection(lines, 'Output', task.output);
431
+ appendJsonSection(lines, 'Log', task.log);
432
+ appendTextSection(lines, 'Thought', task.thought);
433
+ appendTextSection(lines, 'Reasoning Content', task.reasoning_content);
434
+ const screenshotsSection = screenshotsMarkdownSection(task, screenshotBaseDir, executionIndex, taskIndex);
435
+ if (screenshotsSection.lines.length) {
436
+ lines.push(...screenshotsSection.lines);
437
+ attachments.push(...screenshotsSection.attachments);
201
438
  }
202
439
  });
203
440
  return {
@@ -205,10 +442,6 @@ function renderExecution(executionRaw, executionIndex, options) {
205
442
  attachments
206
443
  };
207
444
  }
208
- function reportFileName(execution, executionIndex) {
209
- const safeName = execution.name.trim().replace(/\s+/g, '-').replace(/[^a-zA-Z0-9-_]/g, '') || `execution-${executionIndex + 1}`;
210
- return `${executionIndex + 1}-${(0, external_node_path_namespaceObject.basename)(safeName)}.md`;
211
- }
212
445
  function executionToMarkdown(execution, options) {
213
446
  return renderExecution(execution, 0, options);
214
447
  }
@@ -217,21 +450,53 @@ function reportToMarkdown(report) {
217
450
  const executionResults = reportDump.executions.map((execution, index)=>{
218
451
  const rendered = renderExecution(execution, index);
219
452
  return {
220
- executionIndex: index,
221
- executionName: execution.name,
222
453
  markdown: rendered.markdown,
223
- attachments: rendered.attachments,
224
- suggestedFileName: reportFileName(execution, index)
454
+ attachments: rendered.attachments
225
455
  };
226
456
  });
227
457
  const attachments = executionResults.flatMap((item)=>item.attachments);
458
+ const usageTotals = collectUsageTotals(reportDump);
459
+ const modelRows = reportDump.modelBriefs?.length ? modelBriefRows(reportDump.modelBriefs) : modelRowsFromUsage(reportDump);
460
+ const modelInfoLines = [
461
+ '\n## Model Info',
462
+ ...modelRows.length ? markdownTable([
463
+ 'Intent',
464
+ 'Model',
465
+ "Description"
466
+ ], modelRows) : [
467
+ '- No model metadata recorded.'
468
+ ]
469
+ ];
470
+ const tokenSummaryLines = [
471
+ '\n## Token Usage Summary',
472
+ ...usageTotals.size ? markdownTable([
473
+ 'Model',
474
+ 'Calls',
475
+ 'Prompt Tokens',
476
+ 'Cached Input',
477
+ 'Completion Tokens',
478
+ 'Total Tokens',
479
+ 'Time Cost(ms)'
480
+ ], Array.from(usageTotals.entries()).map(([modelName, totals])=>[
481
+ modelName,
482
+ totals.calls,
483
+ totals.prompt,
484
+ totals.cachedInput,
485
+ totals.completion,
486
+ totals.total,
487
+ totals.timeCost
488
+ ])) : [
489
+ '- No token usage recorded.'
490
+ ]
491
+ ];
228
492
  const header = [
229
493
  `# ${reportDump.groupName}`,
230
494
  reportDump.groupDescription ? `\n${reportDump.groupDescription}` : '',
231
495
  `\n- SDK Version: ${reportDump.sdkVersion}`,
496
+ reportDump.deviceType ? `- Device Type: ${reportDump.deviceType}` : '',
232
497
  `- Execution count: ${reportDump.executions.length}`,
233
- '\n## Suggested execution markdown files',
234
- ...executionResults.map((item)=>`- ${item.suggestedFileName} (${item.executionName})`)
498
+ ...modelInfoLines,
499
+ ...tokenSummaryLines
235
500
  ].filter(Boolean).join('\n');
236
501
  return {
237
502
  markdown: `${header}\n\n${executionResults.map((item)=>item.markdown).join('\n\n---\n\n')}`,