@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.
- package/dist/es/agent/agent.mjs +40 -1
- package/dist/es/agent/agent.mjs.map +1 -1
- package/dist/es/agent/metrics.mjs +81 -0
- package/dist/es/agent/metrics.mjs.map +1 -0
- package/dist/es/agent/task-builder.mjs +1 -1
- package/dist/es/agent/task-builder.mjs.map +1 -1
- package/dist/es/agent/utils.mjs +1 -1
- package/dist/es/ai-model/connectivity/run-connectivity-test.mjs +9 -3
- package/dist/es/ai-model/connectivity/run-connectivity-test.mjs.map +1 -1
- package/dist/es/ai-model/inspect.mjs +3 -3
- package/dist/es/ai-model/inspect.mjs.map +1 -1
- package/dist/es/ai-model/model-adapter/resolve.mjs +2 -2
- package/dist/es/ai-model/model-adapter/resolve.mjs.map +1 -1
- package/dist/es/ai-model/models/doubao.mjs +38 -60
- package/dist/es/ai-model/models/doubao.mjs.map +1 -1
- package/dist/es/ai-model/models/glm.mjs +4 -0
- package/dist/es/ai-model/models/glm.mjs.map +1 -1
- package/dist/es/ai-model/models/gpt.mjs +5 -1
- package/dist/es/ai-model/models/gpt.mjs.map +1 -1
- package/dist/es/ai-model/models/kimi.mjs +4 -0
- package/dist/es/ai-model/models/kimi.mjs.map +1 -1
- package/dist/es/ai-model/models/mimo.mjs +3 -2
- package/dist/es/ai-model/models/mimo.mjs.map +1 -1
- package/dist/es/ai-model/models/qwen.mjs.map +1 -1
- package/dist/es/ai-model/models/ui-tars/adapter.mjs +2 -41
- package/dist/es/ai-model/models/ui-tars/adapter.mjs.map +1 -1
- package/dist/es/ai-model/models/utils/intent.mjs +6 -0
- package/dist/es/ai-model/models/utils/intent.mjs.map +1 -0
- package/dist/es/ai-model/prompt/extraction.mjs +9 -3
- package/dist/es/ai-model/prompt/extraction.mjs.map +1 -1
- package/dist/es/ai-model/prompt/recorder-metadata-generator.mjs +2 -1
- package/dist/es/ai-model/prompt/recorder-metadata-generator.mjs.map +1 -1
- package/dist/es/ai-model/service-caller/index.mjs +40 -19
- package/dist/es/ai-model/service-caller/index.mjs.map +1 -1
- package/dist/es/ai-model/service-caller/json.mjs +31 -28
- package/dist/es/ai-model/service-caller/json.mjs.map +1 -1
- package/dist/es/dump/html-utils.mjs +63 -1
- package/dist/es/dump/html-utils.mjs.map +1 -1
- package/dist/es/dump/screenshot-restoration.mjs +6 -0
- package/dist/es/dump/screenshot-restoration.mjs.map +1 -1
- package/dist/es/index.mjs.map +1 -1
- package/dist/es/report-generator.mjs +28 -1
- package/dist/es/report-generator.mjs.map +1 -1
- package/dist/es/report-markdown.mjs +300 -35
- package/dist/es/report-markdown.mjs.map +1 -1
- package/dist/es/report.mjs +29 -1
- package/dist/es/report.mjs.map +1 -1
- package/dist/es/service/index.mjs +3 -3
- package/dist/es/service/index.mjs.map +1 -1
- package/dist/es/types.mjs.map +1 -1
- package/dist/es/utils.mjs +2 -2
- package/dist/lib/agent/agent.js +40 -1
- package/dist/lib/agent/agent.js.map +1 -1
- package/dist/lib/agent/metrics.js +115 -0
- package/dist/lib/agent/metrics.js.map +1 -0
- package/dist/lib/agent/task-builder.js +1 -1
- package/dist/lib/agent/task-builder.js.map +1 -1
- package/dist/lib/agent/utils.js +1 -1
- package/dist/lib/ai-model/connectivity/run-connectivity-test.js +9 -3
- package/dist/lib/ai-model/connectivity/run-connectivity-test.js.map +1 -1
- package/dist/lib/ai-model/inspect.js +3 -3
- package/dist/lib/ai-model/inspect.js.map +1 -1
- package/dist/lib/ai-model/model-adapter/resolve.js +1 -1
- package/dist/lib/ai-model/model-adapter/resolve.js.map +1 -1
- package/dist/lib/ai-model/models/doubao.js +38 -69
- package/dist/lib/ai-model/models/doubao.js.map +1 -1
- package/dist/lib/ai-model/models/glm.js +4 -0
- package/dist/lib/ai-model/models/glm.js.map +1 -1
- package/dist/lib/ai-model/models/gpt.js +5 -1
- package/dist/lib/ai-model/models/gpt.js.map +1 -1
- package/dist/lib/ai-model/models/kimi.js +4 -0
- package/dist/lib/ai-model/models/kimi.js.map +1 -1
- package/dist/lib/ai-model/models/mimo.js +3 -2
- package/dist/lib/ai-model/models/mimo.js.map +1 -1
- package/dist/lib/ai-model/models/qwen.js.map +1 -1
- package/dist/lib/ai-model/models/ui-tars/adapter.js +1 -40
- package/dist/lib/ai-model/models/ui-tars/adapter.js.map +1 -1
- package/dist/lib/ai-model/models/utils/intent.js +40 -0
- package/dist/lib/ai-model/models/utils/intent.js.map +1 -0
- package/dist/lib/ai-model/prompt/extraction.js +8 -2
- package/dist/lib/ai-model/prompt/extraction.js.map +1 -1
- package/dist/lib/ai-model/prompt/recorder-metadata-generator.js +4 -3
- package/dist/lib/ai-model/prompt/recorder-metadata-generator.js.map +1 -1
- package/dist/lib/ai-model/service-caller/index.js +44 -23
- package/dist/lib/ai-model/service-caller/index.js.map +1 -1
- package/dist/lib/ai-model/service-caller/json.js +33 -33
- package/dist/lib/ai-model/service-caller/json.js.map +1 -1
- package/dist/lib/dump/html-utils.js +65 -0
- package/dist/lib/dump/html-utils.js.map +1 -1
- package/dist/lib/dump/screenshot-restoration.js +6 -0
- package/dist/lib/dump/screenshot-restoration.js.map +1 -1
- package/dist/lib/index.js.map +1 -1
- package/dist/lib/report-generator.js +27 -0
- package/dist/lib/report-generator.js.map +1 -1
- package/dist/lib/report-markdown.js +300 -35
- package/dist/lib/report-markdown.js.map +1 -1
- package/dist/lib/report.js +28 -0
- package/dist/lib/report.js.map +1 -1
- package/dist/lib/service/index.js +3 -3
- package/dist/lib/service/index.js.map +1 -1
- package/dist/lib/types.js.map +1 -1
- package/dist/lib/utils.js +2 -2
- package/dist/types/agent/agent.d.ts +15 -0
- package/dist/types/agent/index.d.ts +1 -0
- package/dist/types/agent/metrics.d.ts +48 -0
- package/dist/types/ai-model/model-adapter/types.d.ts +8 -0
- package/dist/types/ai-model/models/doubao.d.ts +1 -4
- package/dist/types/ai-model/models/utils/intent.d.ts +2 -0
- package/dist/types/ai-model/service-caller/index.d.ts +8 -3
- package/dist/types/ai-model/service-caller/json.d.ts +19 -2
- package/dist/types/dump/html-utils.d.ts +2 -0
- package/dist/types/dump/screenshot-restoration.d.ts +1 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/types/report-generator.d.ts +6 -0
- package/dist/types/types.d.ts +11 -2
- 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
|
|
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`,
|
|
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`,
|
|
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`,
|
|
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
|
|
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`,
|
|
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
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
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
|
-
'###
|
|
349
|
+
'### Screenshots'
|
|
153
350
|
];
|
|
154
351
|
const attachments = [];
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
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
|
|
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
|
-
|
|
193
|
-
|
|
194
|
-
lines.push(
|
|
195
|
-
|
|
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
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
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
|
-
|
|
234
|
-
...
|
|
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')}`,
|