@midscene/core 1.17.6 → 1.17.7

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 (72) hide show
  1. package/dist/es/agent/agent-report-manager.mjs +1 -1
  2. package/dist/es/agent/agent-report-manager.mjs.map +1 -1
  3. package/dist/es/agent/utils.mjs +1 -1
  4. package/dist/es/ai-model/model-adapter/locate-prompt-order.mjs +80 -0
  5. package/dist/es/ai-model/model-adapter/locate-prompt-order.mjs.map +1 -0
  6. package/dist/es/ai-model/model-adapter/locate.mjs +2 -1
  7. package/dist/es/ai-model/model-adapter/locate.mjs.map +1 -1
  8. package/dist/es/ai-model/workflows/grounding/locate.mjs +1 -1
  9. package/dist/es/ai-model/workflows/grounding/locate.mjs.map +1 -1
  10. package/dist/es/ai-model/workflows/grounding/section-locate.mjs +1 -1
  11. package/dist/es/ai-model/workflows/grounding/section-locate.mjs.map +1 -1
  12. package/dist/es/ai-model/workflows/grounding/utils.mjs +8 -26
  13. package/dist/es/ai-model/workflows/grounding/utils.mjs.map +1 -1
  14. package/dist/es/dump/report-image-export.mjs +58 -0
  15. package/dist/es/dump/report-image-export.mjs.map +1 -0
  16. package/dist/es/dump/screenshot-store.mjs +5 -0
  17. package/dist/es/dump/screenshot-store.mjs.map +1 -1
  18. package/dist/es/report-cli.mjs +13 -29
  19. package/dist/es/report-cli.mjs.map +1 -1
  20. package/dist/es/report-generator.mjs +22 -8
  21. package/dist/es/report-generator.mjs.map +1 -1
  22. package/dist/es/report-html-template.mjs +1 -1
  23. package/dist/es/report-markdown.mjs +50 -53
  24. package/dist/es/report-markdown.mjs.map +1 -1
  25. package/dist/es/report.mjs +8 -24
  26. package/dist/es/report.mjs.map +1 -1
  27. package/dist/es/utils.mjs +1 -1
  28. package/dist/lib/agent/agent-report-manager.js +1 -1
  29. package/dist/lib/agent/agent-report-manager.js.map +1 -1
  30. package/dist/lib/agent/utils.js +1 -1
  31. package/dist/lib/ai-model/model-adapter/locate-prompt-order.js +117 -0
  32. package/dist/lib/ai-model/model-adapter/locate-prompt-order.js.map +1 -0
  33. package/dist/lib/ai-model/model-adapter/locate.js +2 -1
  34. package/dist/lib/ai-model/model-adapter/locate.js.map +1 -1
  35. package/dist/lib/ai-model/workflows/grounding/locate.js +1 -1
  36. package/dist/lib/ai-model/workflows/grounding/locate.js.map +1 -1
  37. package/dist/lib/ai-model/workflows/grounding/section-locate.js +1 -1
  38. package/dist/lib/ai-model/workflows/grounding/section-locate.js.map +1 -1
  39. package/dist/lib/ai-model/workflows/grounding/utils.js +8 -26
  40. package/dist/lib/ai-model/workflows/grounding/utils.js.map +1 -1
  41. package/dist/lib/dump/report-image-export.js +92 -0
  42. package/dist/lib/dump/report-image-export.js.map +1 -0
  43. package/dist/lib/dump/screenshot-store.js +5 -0
  44. package/dist/lib/dump/screenshot-store.js.map +1 -1
  45. package/dist/lib/report-cli.js +12 -28
  46. package/dist/lib/report-cli.js.map +1 -1
  47. package/dist/lib/report-generator.js +22 -8
  48. package/dist/lib/report-generator.js.map +1 -1
  49. package/dist/lib/report-html-template.js +1 -1
  50. package/dist/lib/report-markdown.js +50 -53
  51. package/dist/lib/report-markdown.js.map +1 -1
  52. package/dist/lib/report.js +6 -22
  53. package/dist/lib/report.js.map +1 -1
  54. package/dist/lib/utils.js +1 -1
  55. package/dist/types/ai-model/model-adapter/locate-prompt-order.d.ts +24 -0
  56. package/dist/types/ai-model/model-adapter/locate-prompt-order.d.ts.map +1 -0
  57. package/dist/types/ai-model/model-adapter/locate.d.ts.map +1 -1
  58. package/dist/types/ai-model/model-adapter/types.d.ts +9 -3
  59. package/dist/types/ai-model/model-adapter/types.d.ts.map +1 -1
  60. package/dist/types/ai-model/workflows/grounding/utils.d.ts +4 -9
  61. package/dist/types/ai-model/workflows/grounding/utils.d.ts.map +1 -1
  62. package/dist/types/dump/report-image-export.d.ts +12 -0
  63. package/dist/types/dump/report-image-export.d.ts.map +1 -0
  64. package/dist/types/dump/screenshot-store.d.ts +2 -0
  65. package/dist/types/dump/screenshot-store.d.ts.map +1 -1
  66. package/dist/types/report-cli.d.ts.map +1 -1
  67. package/dist/types/report-generator.d.ts +4 -2
  68. package/dist/types/report-generator.d.ts.map +1 -1
  69. package/dist/types/report-markdown.d.ts +4 -2
  70. package/dist/types/report-markdown.d.ts.map +1 -1
  71. package/dist/types/report.d.ts.map +1 -1
  72. package/package.json +2 -2
@@ -1 +1 @@
1
- {"version":3,"file":"dump/screenshot-store.mjs","sources":["../../../src/dump/screenshot-store.ts"],"sourcesContent":["import { createHash } from 'node:crypto';\nimport { existsSync, mkdirSync, readFileSync, rmSync } from 'node:fs';\nimport { dirname, isAbsolute, join } from 'node:path';\nimport type { ScreenshotItem } from '../screenshot-item';\nimport { collectImageScriptIds, extractImageByIdSync } from './html-utils';\nimport {\n type ImageUrlRef,\n type ScreenshotRef,\n type StoredImageRef,\n imageFileExtensionForMimeType,\n imageRefFileExtension,\n normalizeImageUrlRef,\n normalizeScreenshotRef,\n normalizeStoredImageRef,\n} from './image-reference';\nimport { writeReportArtifactFile } from './report-artifact-store';\n\nexport type {\n ImageUrlRef,\n ScreenshotRef,\n StoredImageRef,\n} from './image-reference';\nexport {\n imageRefFileExtension,\n normalizeImageUrlRef,\n normalizeScreenshotRef,\n normalizeStoredImageRef,\n} from './image-reference';\n\nconst base64ImageDataUrlPattern =\n /^data:(image\\/[a-z0-9.+-]+)(?:;[^,]*)?;base64,([a-z0-9+/=\\s]+)$/i;\ntype ResolvedImageSource =\n | {\n type: 'data-uri';\n id: string;\n mimeType: StoredImageRef['mimeType'];\n dataUri: string;\n }\n | {\n type: 'file';\n id: string;\n mimeType: StoredImageRef['mimeType'];\n filePath: string;\n };\n\n/** Parse and normalize a base64 image data URL for report persistence. */\nexport function parseBase64ImageDataUrl(imageUrl: string): {\n mimeType: `image/${string}`;\n extension: string;\n rawBase64: string;\n} {\n const match = imageUrl.match(base64ImageDataUrlPattern);\n if (!match) {\n throw new Error(\n 'ReportImageStore: reference image must be a valid base64 image data URL',\n );\n }\n const mimeType = match[1].toLowerCase() as `image/${string}`;\n return {\n mimeType,\n extension: imageFileExtensionForMimeType(mimeType),\n rawBase64: match[2].replace(/\\s/g, ''),\n };\n}\n\nexport function isBase64ImageDataUrl(value: string): boolean {\n return base64ImageDataUrlPattern.test(value);\n}\n\n/** Stable asset names allow different observations to share the same bytes. */\nexport function reportImageFileName(\n extension: string,\n bytes: Uint8Array,\n): string {\n const hash = createHash('sha256')\n .update(extension)\n .update('\\0')\n .update(bytes)\n .digest('hex');\n return `${hash}.${extension}`;\n}\n\nexport function resolveImageSource(\n refInput: unknown,\n options: {\n reportPath: string;\n imageDirectory?: string;\n fallbackId?: string;\n fallbackMimeType?: ScreenshotRef['mimeType'];\n },\n): ResolvedImageSource {\n const ref = normalizeStoredImageRef(refInput);\n const id = ref?.id ?? options.fallbackId;\n const mimeType = ref?.mimeType ?? options.fallbackMimeType;\n\n if (!id || !mimeType) {\n throw new Error(\n 'ReportImageStore: image id and mimeType are required to resolve an image',\n );\n }\n\n const resolveReportRelativePath = (filePath: string): string =>\n isAbsolute(filePath)\n ? filePath\n : join(dirname(options.reportPath), filePath);\n\n if (ref?.storage === 'file') {\n if (!ref.path) {\n throw new Error(\n `ReportImageStore: image ref \"${ref.id}\" missing file path`,\n );\n }\n\n const explicitFilePath = resolveReportRelativePath(ref.path);\n if (existsSync(explicitFilePath)) {\n return {\n type: 'file',\n id,\n mimeType,\n filePath: explicitFilePath,\n };\n }\n }\n\n const inlineDataUri = extractImageByIdSync(options.reportPath, id);\n if (inlineDataUri) {\n return {\n type: 'data-uri',\n id,\n mimeType,\n dataUri: inlineDataUri,\n };\n }\n\n const extension = ref\n ? imageRefFileExtension(ref)\n : imageFileExtensionForMimeType(options.fallbackMimeType!);\n if (options.imageDirectory) {\n const companionImagePath = join(\n options.imageDirectory,\n `${id}.${extension}`,\n );\n if (existsSync(companionImagePath)) {\n return {\n type: 'file',\n id,\n mimeType,\n filePath: companionImagePath,\n };\n }\n }\n const siblingImagePath = join(\n dirname(options.reportPath),\n 'screenshots',\n `${id}.${extension}`,\n );\n if (existsSync(siblingImagePath)) {\n return {\n type: 'file',\n id,\n mimeType,\n filePath: siblingImagePath,\n };\n }\n\n throw new Error(\n `ReportImageStore: cannot resolve image \"${id}\" from ${options.reportPath}`,\n );\n}\n\nexport function resolveScreenshotSource(\n refInput: unknown,\n options: {\n reportPath: string;\n imageDirectory?: string;\n fallbackId?: string;\n fallbackMimeType?: ScreenshotRef['mimeType'];\n },\n): ResolvedImageSource {\n const ref = normalizeScreenshotRef(refInput);\n return resolveImageSource(ref, options);\n}\n\n/** Persists and restores every image asset referenced by a report. */\nexport class ReportImageStore {\n private readonly mode: 'inline' | 'directory';\n private readonly reportPath: string;\n private readonly screenshotsDir?: string;\n private readonly writeInlineImage?: (\n id: string,\n base64: string,\n ) => void | Promise<void>;\n private alsoWriteFileCopy: boolean;\n private readonly writtenInlineIds = new Set<string>();\n private readonly writtenFileIds = new Set<string>();\n private readonly fileLocations = new Map<\n string,\n { relativePath: string; absolutePath: string }\n >();\n private readonly pendingFiles = new Map<\n string,\n Promise<{ relativePath: string; absolutePath: string }>\n >();\n private readonly shouldReuseExistingInlineReport: boolean;\n private existingInlineImageIdsPromise?: Promise<void>;\n\n constructor(options: {\n mode: 'inline' | 'directory';\n reportPath: string;\n screenshotsDir?: string;\n writeInlineImage?: (id: string, base64: string) => void | Promise<void>;\n alsoWriteFileCopy?: boolean;\n /** @deprecated Use alsoWriteFileCopy instead. */\n ensureFileCopy?: boolean;\n /** Reuse inline image assets already present in reportPath. */\n reuseExistingReport?: boolean;\n }) {\n this.mode = options.mode;\n this.reportPath = options.reportPath;\n this.screenshotsDir = options.screenshotsDir;\n this.writeInlineImage = options.writeInlineImage;\n this.alsoWriteFileCopy =\n options.alsoWriteFileCopy ?? options.ensureFileCopy ?? false;\n this.shouldReuseExistingInlineReport = Boolean(\n options.reuseExistingReport && this.mode === 'inline',\n );\n }\n\n enableFileCopies(): void {\n this.alsoWriteFileCopy = true;\n }\n\n /** Resolve an observation/reference ID to its content-addressed asset. */\n getFilePath(id: string): string {\n const file = this.fileLocations.get(id);\n if (!file)\n throw new Error(`ReportImageStore: image ${id} has no persisted file`);\n return file.relativePath;\n }\n\n async persist(screenshot: ScreenshotItem): Promise<ScreenshotRef> {\n await this.ensureExistingInlineImageIdsLoaded();\n const shouldWriteFileCopy =\n this.mode === 'directory' || this.alsoWriteFileCopy;\n const fileRef = shouldWriteFileCopy\n ? await this.persistToSharedFileIfNeeded(screenshot, {\n markAsPersisted: this.mode === 'directory',\n })\n : null;\n\n if (this.mode === 'inline') {\n if (!this.writeInlineImage) {\n throw new Error(\n 'ReportImageStore: writeInlineImage is required in inline mode',\n );\n }\n if (!this.writtenInlineIds.has(screenshot.id)) {\n await this.writeInlineImage(screenshot.id, screenshot.base64);\n this.writtenInlineIds.add(screenshot.id);\n }\n return screenshot.markPersistedInline(this.reportPath);\n }\n\n if (!fileRef) {\n throw new Error(\n 'ReportImageStore: file persistence is required in directory mode',\n );\n }\n return fileRef;\n }\n\n /** Persist a base64 multimodal prompt image and return its content-addressed ref. */\n async persistReferenceImage(imageUrl: string): Promise<ImageUrlRef> {\n await this.ensureExistingInlineImageIdsLoaded();\n const { mimeType, extension, rawBase64 } =\n parseBase64ImageDataUrl(imageUrl);\n const id = `reference-${createHash('sha256')\n .update(mimeType)\n .update('\\0')\n .update(Buffer.from(rawBase64, 'base64'))\n .digest('hex')}`;\n const shouldWriteFileCopy =\n this.mode === 'directory' || this.alsoWriteFileCopy;\n const fileLocation = shouldWriteFileCopy\n ? await this.writeImageFileIfNeeded({ id, extension, rawBase64 })\n : null;\n\n let ref: ImageUrlRef;\n if (this.mode === 'inline') {\n if (!this.writeInlineImage) {\n throw new Error(\n 'ReportImageStore: writeInlineImage is required in inline mode',\n );\n }\n if (!this.writtenInlineIds.has(id)) {\n await this.writeInlineImage(id, imageUrl);\n this.writtenInlineIds.add(id);\n }\n ref = {\n type: 'midscene_image_url_ref',\n id,\n mimeType,\n storage: 'inline',\n };\n } else {\n if (!fileLocation) {\n throw new Error(\n 'ReportImageStore: file persistence is required in directory mode',\n );\n }\n ref = {\n type: 'midscene_image_url_ref',\n id,\n mimeType,\n storage: 'file',\n path: fileLocation.relativePath,\n };\n }\n return ref;\n }\n\n private async ensureExistingInlineImageIdsLoaded(): Promise<void> {\n if (!this.shouldReuseExistingInlineReport) return;\n if (!this.existingInlineImageIdsPromise) {\n this.existingInlineImageIdsPromise = (async () => {\n try {\n const imageIds = await collectImageScriptIds(this.reportPath);\n for (const imageId of imageIds) this.writtenInlineIds.add(imageId);\n } catch (error) {\n if (\n typeof error === 'object' &&\n error !== null &&\n 'code' in error &&\n error.code === 'ENOENT'\n ) {\n return;\n }\n throw new Error(\n `ReportImageStore: failed to index existing inline images from ${this.reportPath}`,\n { cause: error },\n );\n }\n })();\n }\n await this.existingInlineImageIdsPromise;\n }\n\n private async persistToSharedFileIfNeeded(\n screenshot: ScreenshotItem,\n options: {\n markAsPersisted: boolean;\n },\n ): Promise<ScreenshotRef> {\n const { relativePath, absolutePath } =\n this.fileLocations.get(screenshot.id) ??\n (await this.writeImageFileIfNeeded({\n id: screenshot.id,\n extension: screenshot.extension,\n rawBase64: screenshot.rawBase64,\n }));\n\n if (options.markAsPersisted) {\n return screenshot.markPersistedToPath(relativePath, absolutePath);\n }\n\n return screenshot.registerPersistedFileCopy(relativePath, absolutePath);\n }\n\n private async writeImageFileIfNeeded(image: {\n id: string;\n extension: string;\n rawBase64: string;\n }): Promise<{ relativePath: string; absolutePath: string }> {\n const screenshotsDir = this.screenshotsDir;\n if (!screenshotsDir) {\n throw new Error(\n 'ReportImageStore: screenshotsDir is required when file persistence is enabled',\n );\n }\n if (!existsSync(screenshotsDir)) {\n mkdirSync(screenshotsDir, { recursive: true });\n }\n\n const known = this.fileLocations.get(image.id);\n if (known) return known;\n const bytes = Buffer.from(image.rawBase64, 'base64');\n const fileName = reportImageFileName(image.extension, bytes);\n const relativePath = `./screenshots/${fileName}`;\n const absolutePath = join(screenshotsDir, fileName);\n let publication = this.pendingFiles.get(fileName);\n if (!publication) {\n publication = (async () => {\n if (!this.writtenFileIds.has(fileName)) {\n await writeReportArtifactFile(absolutePath, bytes);\n this.writtenFileIds.add(fileName);\n }\n return { relativePath, absolutePath };\n })();\n this.pendingFiles.set(fileName, publication);\n }\n try {\n const location = await publication;\n this.fileLocations.set(image.id, location);\n return location;\n } finally {\n this.pendingFiles.delete(fileName);\n }\n }\n\n /** Resolve any stored report image reference to a data URI. */\n loadDataUri(refInput: unknown): string {\n const ref = normalizeStoredImageRef(refInput);\n if (!ref) {\n throw new Error('ReportImageStore: invalid image reference');\n }\n\n const resolved = resolveImageSource(ref, {\n reportPath: this.reportPath,\n imageDirectory: this.screenshotsDir,\n });\n\n if (resolved.type === 'data-uri') {\n return resolved.dataUri;\n }\n\n const data = readFileSync(resolved.filePath);\n return `data:${resolved.mimeType};base64,${data.toString('base64')}`;\n }\n\n /** @deprecated Use loadDataUri. */\n loadBase64(refInput: unknown): string {\n const screenshotRef = normalizeScreenshotRef(refInput);\n if (!screenshotRef) {\n throw new Error('ReportImageStore: invalid screenshot reference');\n }\n return this.loadDataUri(screenshotRef);\n }\n\n cleanup(): void {\n if (\n this.mode === 'directory' &&\n this.screenshotsDir &&\n existsSync(this.screenshotsDir)\n ) {\n rmSync(this.screenshotsDir, { recursive: true, force: true });\n }\n this.writtenInlineIds.clear();\n this.writtenFileIds.clear();\n this.fileLocations.clear();\n this.pendingFiles.clear();\n this.existingInlineImageIdsPromise = undefined;\n }\n}\n\n/** @deprecated Use ReportImageStore. */\nexport { ReportImageStore as ScreenshotStore };\n"],"names":["base64ImageDataUrlPattern","parseBase64ImageDataUrl","imageUrl","match","Error","mimeType","imageFileExtensionForMimeType","isBase64ImageDataUrl","value","reportImageFileName","extension","bytes","hash","createHash","resolveImageSource","refInput","options","ref","normalizeStoredImageRef","id","resolveReportRelativePath","filePath","isAbsolute","join","dirname","explicitFilePath","existsSync","inlineDataUri","extractImageByIdSync","imageRefFileExtension","companionImagePath","siblingImagePath","resolveScreenshotSource","normalizeScreenshotRef","ReportImageStore","file","screenshot","shouldWriteFileCopy","fileRef","rawBase64","Buffer","fileLocation","imageIds","collectImageScriptIds","imageId","error","relativePath","absolutePath","image","screenshotsDir","mkdirSync","known","fileName","publication","writeReportArtifactFile","location","resolved","data","readFileSync","screenshotRef","rmSync","undefined","Set","Map","Boolean"],"mappings":";;;;;;;;;;;;;;;;AA6BA,MAAMA,4BACJ;AAgBK,SAASC,wBAAwBC,QAAgB;IAKtD,MAAMC,QAAQD,SAAS,KAAK,CAACF;IAC7B,IAAI,CAACG,OACH,MAAM,IAAIC,MACR;IAGJ,MAAMC,WAAWF,KAAK,CAAC,EAAE,CAAC,WAAW;IACrC,OAAO;QACLE;QACA,WAAWC,8BAA8BD;QACzC,WAAWF,KAAK,CAAC,EAAE,CAAC,OAAO,CAAC,OAAO;IACrC;AACF;AAEO,SAASI,qBAAqBC,KAAa;IAChD,OAAOR,0BAA0B,IAAI,CAACQ;AACxC;AAGO,SAASC,oBACdC,SAAiB,EACjBC,KAAiB;IAEjB,MAAMC,OAAOC,WAAW,UACrB,MAAM,CAACH,WACP,MAAM,CAAC,MACP,MAAM,CAACC,OACP,MAAM,CAAC;IACV,OAAO,GAAGC,KAAK,CAAC,EAAEF,WAAW;AAC/B;AAEO,SAASI,mBACdC,QAAiB,EACjBC,OAKC;IAED,MAAMC,MAAMC,wBAAwBH;IACpC,MAAMI,KAAKF,KAAK,MAAMD,QAAQ,UAAU;IACxC,MAAMX,WAAWY,KAAK,YAAYD,QAAQ,gBAAgB;IAE1D,IAAI,CAACG,MAAM,CAACd,UACV,MAAM,IAAID,MACR;IAIJ,MAAMgB,4BAA4B,CAACC,WACjCC,WAAWD,YACPA,WACAE,KAAKC,QAAQR,QAAQ,UAAU,GAAGK;IAExC,IAAIJ,KAAK,YAAY,QAAQ;QAC3B,IAAI,CAACA,IAAI,IAAI,EACX,MAAM,IAAIb,MACR,CAAC,6BAA6B,EAAEa,IAAI,EAAE,CAAC,mBAAmB,CAAC;QAI/D,MAAMQ,mBAAmBL,0BAA0BH,IAAI,IAAI;QAC3D,IAAIS,WAAWD,mBACb,OAAO;YACL,MAAM;YACNN;YACAd;YACA,UAAUoB;QACZ;IAEJ;IAEA,MAAME,gBAAgBC,qBAAqBZ,QAAQ,UAAU,EAAEG;IAC/D,IAAIQ,eACF,OAAO;QACL,MAAM;QACNR;QACAd;QACA,SAASsB;IACX;IAGF,MAAMjB,YAAYO,MACdY,sBAAsBZ,OACtBX,8BAA8BU,QAAQ,gBAAgB;IAC1D,IAAIA,QAAQ,cAAc,EAAE;QAC1B,MAAMc,qBAAqBP,KACzBP,QAAQ,cAAc,EACtB,GAAGG,GAAG,CAAC,EAAET,WAAW;QAEtB,IAAIgB,WAAWI,qBACb,OAAO;YACL,MAAM;YACNX;YACAd;YACA,UAAUyB;QACZ;IAEJ;IACA,MAAMC,mBAAmBR,KACvBC,QAAQR,QAAQ,UAAU,GAC1B,eACA,GAAGG,GAAG,CAAC,EAAET,WAAW;IAEtB,IAAIgB,WAAWK,mBACb,OAAO;QACL,MAAM;QACNZ;QACAd;QACA,UAAU0B;IACZ;IAGF,MAAM,IAAI3B,MACR,CAAC,wCAAwC,EAAEe,GAAG,OAAO,EAAEH,QAAQ,UAAU,EAAE;AAE/E;AAEO,SAASgB,wBACdjB,QAAiB,EACjBC,OAKC;IAED,MAAMC,MAAMgB,uBAAuBlB;IACnC,OAAOD,mBAAmBG,KAAKD;AACjC;AAGO,MAAMkB;IA4CX,mBAAyB;QACvB,IAAI,CAAC,iBAAiB,GAAG;IAC3B;IAGA,YAAYf,EAAU,EAAU;QAC9B,MAAMgB,OAAO,IAAI,CAAC,aAAa,CAAC,GAAG,CAAChB;QACpC,IAAI,CAACgB,MACH,MAAM,IAAI/B,MAAM,CAAC,wBAAwB,EAAEe,GAAG,sBAAsB,CAAC;QACvE,OAAOgB,KAAK,YAAY;IAC1B;IAEA,MAAM,QAAQC,UAA0B,EAA0B;QAChE,MAAM,IAAI,CAAC,kCAAkC;QAC7C,MAAMC,sBACJ,AAAc,gBAAd,IAAI,CAAC,IAAI,IAAoB,IAAI,CAAC,iBAAiB;QACrD,MAAMC,UAAUD,sBACZ,MAAM,IAAI,CAAC,2BAA2B,CAACD,YAAY;YACjD,iBAAiB,AAAc,gBAAd,IAAI,CAAC,IAAI;QAC5B,KACA;QAEJ,IAAI,AAAc,aAAd,IAAI,CAAC,IAAI,EAAe;YAC1B,IAAI,CAAC,IAAI,CAAC,gBAAgB,EACxB,MAAM,IAAIhC,MACR;YAGJ,IAAI,CAAC,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAACgC,WAAW,EAAE,GAAG;gBAC7C,MAAM,IAAI,CAAC,gBAAgB,CAACA,WAAW,EAAE,EAAEA,WAAW,MAAM;gBAC5D,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAACA,WAAW,EAAE;YACzC;YACA,OAAOA,WAAW,mBAAmB,CAAC,IAAI,CAAC,UAAU;QACvD;QAEA,IAAI,CAACE,SACH,MAAM,IAAIlC,MACR;QAGJ,OAAOkC;IACT;IAGA,MAAM,sBAAsBpC,QAAgB,EAAwB;QAClE,MAAM,IAAI,CAAC,kCAAkC;QAC7C,MAAM,EAAEG,QAAQ,EAAEK,SAAS,EAAE6B,SAAS,EAAE,GACtCtC,wBAAwBC;QAC1B,MAAMiB,KAAK,CAAC,UAAU,EAAEN,WAAW,UAChC,MAAM,CAACR,UACP,MAAM,CAAC,MACP,MAAM,CAACmC,OAAO,IAAI,CAACD,WAAW,WAC9B,MAAM,CAAC,QAAQ;QAClB,MAAMF,sBACJ,AAAc,gBAAd,IAAI,CAAC,IAAI,IAAoB,IAAI,CAAC,iBAAiB;QACrD,MAAMI,eAAeJ,sBACjB,MAAM,IAAI,CAAC,sBAAsB,CAAC;YAAElB;YAAIT;YAAW6B;QAAU,KAC7D;QAEJ,IAAItB;QACJ,IAAI,AAAc,aAAd,IAAI,CAAC,IAAI,EAAe;YAC1B,IAAI,CAAC,IAAI,CAAC,gBAAgB,EACxB,MAAM,IAAIb,MACR;YAGJ,IAAI,CAAC,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAACe,KAAK;gBAClC,MAAM,IAAI,CAAC,gBAAgB,CAACA,IAAIjB;gBAChC,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAACiB;YAC5B;YACAF,MAAM;gBACJ,MAAM;gBACNE;gBACAd;gBACA,SAAS;YACX;QACF,OAAO;YACL,IAAI,CAACoC,cACH,MAAM,IAAIrC,MACR;YAGJa,MAAM;gBACJ,MAAM;gBACNE;gBACAd;gBACA,SAAS;gBACT,MAAMoC,aAAa,YAAY;YACjC;QACF;QACA,OAAOxB;IACT;IAEA,MAAc,qCAAoD;QAChE,IAAI,CAAC,IAAI,CAAC,+BAA+B,EAAE;QAC3C,IAAI,CAAC,IAAI,CAAC,6BAA6B,EACrC,IAAI,CAAC,6BAA6B,GAAI;YACpC,IAAI;gBACF,MAAMyB,WAAW,MAAMC,sBAAsB,IAAI,CAAC,UAAU;gBAC5D,KAAK,MAAMC,WAAWF,SAAU,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAACE;YAC5D,EAAE,OAAOC,OAAO;gBACd,IACE,AAAiB,YAAjB,OAAOA,SACPA,AAAU,SAAVA,SACA,UAAUA,SACVA,AAAe,aAAfA,MAAM,IAAI,EAEV;gBAEF,MAAM,IAAIzC,MACR,CAAC,8DAA8D,EAAE,IAAI,CAAC,UAAU,EAAE,EAClF;oBAAE,OAAOyC;gBAAM;YAEnB;QACF;QAEF,MAAM,IAAI,CAAC,6BAA6B;IAC1C;IAEA,MAAc,4BACZT,UAA0B,EAC1BpB,OAEC,EACuB;QACxB,MAAM,EAAE8B,YAAY,EAAEC,YAAY,EAAE,GAClC,IAAI,CAAC,aAAa,CAAC,GAAG,CAACX,WAAW,EAAE,KACnC,MAAM,IAAI,CAAC,sBAAsB,CAAC;YACjC,IAAIA,WAAW,EAAE;YACjB,WAAWA,WAAW,SAAS;YAC/B,WAAWA,WAAW,SAAS;QACjC;QAEF,IAAIpB,QAAQ,eAAe,EACzB,OAAOoB,WAAW,mBAAmB,CAACU,cAAcC;QAGtD,OAAOX,WAAW,yBAAyB,CAACU,cAAcC;IAC5D;IAEA,MAAc,uBAAuBC,KAIpC,EAA2D;QAC1D,MAAMC,iBAAiB,IAAI,CAAC,cAAc;QAC1C,IAAI,CAACA,gBACH,MAAM,IAAI7C,MACR;QAGJ,IAAI,CAACsB,WAAWuB,iBACdC,UAAUD,gBAAgB;YAAE,WAAW;QAAK;QAG9C,MAAME,QAAQ,IAAI,CAAC,aAAa,CAAC,GAAG,CAACH,MAAM,EAAE;QAC7C,IAAIG,OAAO,OAAOA;QAClB,MAAMxC,QAAQ6B,OAAO,IAAI,CAACQ,MAAM,SAAS,EAAE;QAC3C,MAAMI,WAAW3C,oBAAoBuC,MAAM,SAAS,EAAErC;QACtD,MAAMmC,eAAe,CAAC,cAAc,EAAEM,UAAU;QAChD,MAAML,eAAexB,KAAK0B,gBAAgBG;QAC1C,IAAIC,cAAc,IAAI,CAAC,YAAY,CAAC,GAAG,CAACD;QACxC,IAAI,CAACC,aAAa;YAChBA,cAAe;gBACb,IAAI,CAAC,IAAI,CAAC,cAAc,CAAC,GAAG,CAACD,WAAW;oBACtC,MAAME,wBAAwBP,cAAcpC;oBAC5C,IAAI,CAAC,cAAc,CAAC,GAAG,CAACyC;gBAC1B;gBACA,OAAO;oBAAEN;oBAAcC;gBAAa;YACtC;YACA,IAAI,CAAC,YAAY,CAAC,GAAG,CAACK,UAAUC;QAClC;QACA,IAAI;YACF,MAAME,WAAW,MAAMF;YACvB,IAAI,CAAC,aAAa,CAAC,GAAG,CAACL,MAAM,EAAE,EAAEO;YACjC,OAAOA;QACT,SAAU;YACR,IAAI,CAAC,YAAY,CAAC,MAAM,CAACH;QAC3B;IACF;IAGA,YAAYrC,QAAiB,EAAU;QACrC,MAAME,MAAMC,wBAAwBH;QACpC,IAAI,CAACE,KACH,MAAM,IAAIb,MAAM;QAGlB,MAAMoD,WAAW1C,mBAAmBG,KAAK;YACvC,YAAY,IAAI,CAAC,UAAU;YAC3B,gBAAgB,IAAI,CAAC,cAAc;QACrC;QAEA,IAAIuC,AAAkB,eAAlBA,SAAS,IAAI,EACf,OAAOA,SAAS,OAAO;QAGzB,MAAMC,OAAOC,aAAaF,SAAS,QAAQ;QAC3C,OAAO,CAAC,KAAK,EAAEA,SAAS,QAAQ,CAAC,QAAQ,EAAEC,KAAK,QAAQ,CAAC,WAAW;IACtE;IAGA,WAAW1C,QAAiB,EAAU;QACpC,MAAM4C,gBAAgB1B,uBAAuBlB;QAC7C,IAAI,CAAC4C,eACH,MAAM,IAAIvD,MAAM;QAElB,OAAO,IAAI,CAAC,WAAW,CAACuD;IAC1B;IAEA,UAAgB;QACd,IACE,AAAc,gBAAd,IAAI,CAAC,IAAI,IACT,IAAI,CAAC,cAAc,IACnBjC,WAAW,IAAI,CAAC,cAAc,GAE9BkC,OAAO,IAAI,CAAC,cAAc,EAAE;YAAE,WAAW;YAAM,OAAO;QAAK;QAE7D,IAAI,CAAC,gBAAgB,CAAC,KAAK;QAC3B,IAAI,CAAC,cAAc,CAAC,KAAK;QACzB,IAAI,CAAC,aAAa,CAAC,KAAK;QACxB,IAAI,CAAC,YAAY,CAAC,KAAK;QACvB,IAAI,CAAC,6BAA6B,GAAGC;IACvC;IArPA,YAAY7C,OAUX,CAAE;QA/BH,uBAAiB,QAAjB;QACA,uBAAiB,cAAjB;QACA,uBAAiB,kBAAjB;QACA,uBAAiB,oBAAjB;QAIA,uBAAQ,qBAAR;QACA,uBAAiB,oBAAmB,IAAI8C;QACxC,uBAAiB,kBAAiB,IAAIA;QACtC,uBAAiB,iBAAgB,IAAIC;QAIrC,uBAAiB,gBAAe,IAAIA;QAIpC,uBAAiB,mCAAjB;QACA,uBAAQ,iCAAR;QAaE,IAAI,CAAC,IAAI,GAAG/C,QAAQ,IAAI;QACxB,IAAI,CAAC,UAAU,GAAGA,QAAQ,UAAU;QACpC,IAAI,CAAC,cAAc,GAAGA,QAAQ,cAAc;QAC5C,IAAI,CAAC,gBAAgB,GAAGA,QAAQ,gBAAgB;QAChD,IAAI,CAAC,iBAAiB,GACpBA,QAAQ,iBAAiB,IAAIA,QAAQ,cAAc,IAAI;QACzD,IAAI,CAAC,+BAA+B,GAAGgD,QACrChD,QAAQ,mBAAmB,IAAI,AAAc,aAAd,IAAI,CAAC,IAAI;IAE5C;AAkOF"}
1
+ {"version":3,"file":"dump/screenshot-store.mjs","sources":["../../../src/dump/screenshot-store.ts"],"sourcesContent":["import { createHash } from 'node:crypto';\nimport { existsSync, mkdirSync, readFileSync, rmSync } from 'node:fs';\nimport { dirname, isAbsolute, join } from 'node:path';\nimport type { ScreenshotItem } from '../screenshot-item';\nimport { collectImageScriptIds, extractImageByIdSync } from './html-utils';\nimport {\n type ImageUrlRef,\n type ScreenshotRef,\n type StoredImageRef,\n imageFileExtensionForMimeType,\n imageRefFileExtension,\n normalizeImageUrlRef,\n normalizeScreenshotRef,\n normalizeStoredImageRef,\n} from './image-reference';\nimport { writeReportArtifactFile } from './report-artifact-store';\n\nexport type {\n ImageUrlRef,\n ScreenshotRef,\n StoredImageRef,\n} from './image-reference';\nexport {\n imageRefFileExtension,\n normalizeImageUrlRef,\n normalizeScreenshotRef,\n normalizeStoredImageRef,\n} from './image-reference';\n\nconst base64ImageDataUrlPattern =\n /^data:(image\\/[a-z0-9.+-]+)(?:;[^,]*)?;base64,([a-z0-9+/=\\s]+)$/i;\ntype ResolvedImageSource =\n | {\n type: 'data-uri';\n id: string;\n mimeType: StoredImageRef['mimeType'];\n dataUri: string;\n }\n | {\n type: 'file';\n id: string;\n mimeType: StoredImageRef['mimeType'];\n filePath: string;\n };\n\n/** Parse and normalize a base64 image data URL for report persistence. */\nexport function parseBase64ImageDataUrl(imageUrl: string): {\n mimeType: `image/${string}`;\n extension: string;\n rawBase64: string;\n} {\n const match = imageUrl.match(base64ImageDataUrlPattern);\n if (!match) {\n throw new Error(\n 'ReportImageStore: reference image must be a valid base64 image data URL',\n );\n }\n const mimeType = match[1].toLowerCase() as `image/${string}`;\n return {\n mimeType,\n extension: imageFileExtensionForMimeType(mimeType),\n rawBase64: match[2].replace(/\\s/g, ''),\n };\n}\n\nexport function isBase64ImageDataUrl(value: string): boolean {\n return base64ImageDataUrlPattern.test(value);\n}\n\n/** Stable asset names allow different observations to share the same bytes. */\nexport function reportImageFileName(\n extension: string,\n bytes: Uint8Array,\n): string {\n const hash = createHash('sha256')\n .update(extension)\n .update('\\0')\n .update(bytes)\n .digest('hex');\n return `${hash}.${extension}`;\n}\n\nexport function resolveImageSource(\n refInput: unknown,\n options: {\n reportPath: string;\n imageDirectory?: string;\n fallbackId?: string;\n fallbackMimeType?: ScreenshotRef['mimeType'];\n },\n): ResolvedImageSource {\n const ref = normalizeStoredImageRef(refInput);\n const id = ref?.id ?? options.fallbackId;\n const mimeType = ref?.mimeType ?? options.fallbackMimeType;\n\n if (!id || !mimeType) {\n throw new Error(\n 'ReportImageStore: image id and mimeType are required to resolve an image',\n );\n }\n\n const resolveReportRelativePath = (filePath: string): string =>\n isAbsolute(filePath)\n ? filePath\n : join(dirname(options.reportPath), filePath);\n\n if (ref?.storage === 'file') {\n if (!ref.path) {\n throw new Error(\n `ReportImageStore: image ref \"${ref.id}\" missing file path`,\n );\n }\n\n const explicitFilePath = resolveReportRelativePath(ref.path);\n if (existsSync(explicitFilePath)) {\n return {\n type: 'file',\n id,\n mimeType,\n filePath: explicitFilePath,\n };\n }\n }\n\n const inlineDataUri = extractImageByIdSync(options.reportPath, id);\n if (inlineDataUri) {\n return {\n type: 'data-uri',\n id,\n mimeType,\n dataUri: inlineDataUri,\n };\n }\n\n const extension = ref\n ? imageRefFileExtension(ref)\n : imageFileExtensionForMimeType(options.fallbackMimeType!);\n if (options.imageDirectory) {\n const companionImagePath = join(\n options.imageDirectory,\n `${id}.${extension}`,\n );\n if (existsSync(companionImagePath)) {\n return {\n type: 'file',\n id,\n mimeType,\n filePath: companionImagePath,\n };\n }\n }\n const siblingImagePath = join(\n dirname(options.reportPath),\n 'screenshots',\n `${id}.${extension}`,\n );\n if (existsSync(siblingImagePath)) {\n return {\n type: 'file',\n id,\n mimeType,\n filePath: siblingImagePath,\n };\n }\n\n throw new Error(\n `ReportImageStore: cannot resolve image \"${id}\" from ${options.reportPath}`,\n );\n}\n\nexport function resolveScreenshotSource(\n refInput: unknown,\n options: {\n reportPath: string;\n imageDirectory?: string;\n fallbackId?: string;\n fallbackMimeType?: ScreenshotRef['mimeType'];\n },\n): ResolvedImageSource {\n const ref = normalizeScreenshotRef(refInput);\n return resolveImageSource(ref, options);\n}\n\n/** Persists and restores every image asset referenced by a report. */\nexport class ReportImageStore {\n private readonly mode: 'inline' | 'directory';\n private readonly reportPath: string;\n private readonly screenshotsDir?: string;\n private readonly writeInlineImage?: (\n id: string,\n base64: string,\n ) => void | Promise<void>;\n private alsoWriteFileCopy: boolean;\n private readonly writtenInlineIds = new Set<string>();\n private readonly writtenFileIds = new Set<string>();\n private readonly fileLocations = new Map<\n string,\n { relativePath: string; absolutePath: string }\n >();\n private readonly pendingFiles = new Map<\n string,\n Promise<{ relativePath: string; absolutePath: string }>\n >();\n private readonly shouldReuseExistingInlineReport: boolean;\n private existingInlineImageIdsPromise?: Promise<void>;\n\n constructor(options: {\n mode: 'inline' | 'directory';\n reportPath: string;\n screenshotsDir?: string;\n writeInlineImage?: (id: string, base64: string) => void | Promise<void>;\n alsoWriteFileCopy?: boolean;\n /** @deprecated Use alsoWriteFileCopy instead. */\n ensureFileCopy?: boolean;\n /** Reuse inline image assets already present in reportPath. */\n reuseExistingReport?: boolean;\n }) {\n this.mode = options.mode;\n this.reportPath = options.reportPath;\n this.screenshotsDir = options.screenshotsDir;\n this.writeInlineImage = options.writeInlineImage;\n this.alsoWriteFileCopy =\n options.alsoWriteFileCopy ?? options.ensureFileCopy ?? false;\n this.shouldReuseExistingInlineReport = Boolean(\n options.reuseExistingReport && this.mode === 'inline',\n );\n }\n\n enableFileCopies(): void {\n this.alsoWriteFileCopy = true;\n }\n\n /** Resolve an observation/reference ID to its content-addressed asset. */\n getFilePath(id: string): string {\n const file = this.fileLocations.get(id);\n if (!file)\n throw new Error(`ReportImageStore: image ${id} has no persisted file`);\n return file.relativePath;\n }\n\n /** Keep diagnostic bytes without changing the screenshot's report location. */\n persistFileCopy(screenshot: ScreenshotItem): Promise<ScreenshotRef> {\n return this.persistToSharedFileIfNeeded(screenshot, {\n markAsPersisted: false,\n });\n }\n\n async persist(screenshot: ScreenshotItem): Promise<ScreenshotRef> {\n await this.ensureExistingInlineImageIdsLoaded();\n const shouldWriteFileCopy =\n this.mode === 'directory' || this.alsoWriteFileCopy;\n const fileRef = shouldWriteFileCopy\n ? await this.persistToSharedFileIfNeeded(screenshot, {\n markAsPersisted: this.mode === 'directory',\n })\n : null;\n\n if (this.mode === 'inline') {\n if (!this.writeInlineImage) {\n throw new Error(\n 'ReportImageStore: writeInlineImage is required in inline mode',\n );\n }\n if (!this.writtenInlineIds.has(screenshot.id)) {\n await this.writeInlineImage(screenshot.id, screenshot.base64);\n this.writtenInlineIds.add(screenshot.id);\n }\n return screenshot.markPersistedInline(this.reportPath);\n }\n\n if (!fileRef) {\n throw new Error(\n 'ReportImageStore: file persistence is required in directory mode',\n );\n }\n return fileRef;\n }\n\n /** Persist a base64 multimodal prompt image and return its content-addressed ref. */\n async persistReferenceImage(imageUrl: string): Promise<ImageUrlRef> {\n await this.ensureExistingInlineImageIdsLoaded();\n const { mimeType, extension, rawBase64 } =\n parseBase64ImageDataUrl(imageUrl);\n const id = `reference-${createHash('sha256')\n .update(mimeType)\n .update('\\0')\n .update(Buffer.from(rawBase64, 'base64'))\n .digest('hex')}`;\n const shouldWriteFileCopy =\n this.mode === 'directory' || this.alsoWriteFileCopy;\n const fileLocation = shouldWriteFileCopy\n ? await this.writeImageFileIfNeeded({ id, extension, rawBase64 })\n : null;\n\n let ref: ImageUrlRef;\n if (this.mode === 'inline') {\n if (!this.writeInlineImage) {\n throw new Error(\n 'ReportImageStore: writeInlineImage is required in inline mode',\n );\n }\n if (!this.writtenInlineIds.has(id)) {\n await this.writeInlineImage(id, imageUrl);\n this.writtenInlineIds.add(id);\n }\n ref = {\n type: 'midscene_image_url_ref',\n id,\n mimeType,\n storage: 'inline',\n };\n } else {\n if (!fileLocation) {\n throw new Error(\n 'ReportImageStore: file persistence is required in directory mode',\n );\n }\n ref = {\n type: 'midscene_image_url_ref',\n id,\n mimeType,\n storage: 'file',\n path: fileLocation.relativePath,\n };\n }\n return ref;\n }\n\n private async ensureExistingInlineImageIdsLoaded(): Promise<void> {\n if (!this.shouldReuseExistingInlineReport) return;\n if (!this.existingInlineImageIdsPromise) {\n this.existingInlineImageIdsPromise = (async () => {\n try {\n const imageIds = await collectImageScriptIds(this.reportPath);\n for (const imageId of imageIds) this.writtenInlineIds.add(imageId);\n } catch (error) {\n if (\n typeof error === 'object' &&\n error !== null &&\n 'code' in error &&\n error.code === 'ENOENT'\n ) {\n return;\n }\n throw new Error(\n `ReportImageStore: failed to index existing inline images from ${this.reportPath}`,\n { cause: error },\n );\n }\n })();\n }\n await this.existingInlineImageIdsPromise;\n }\n\n private async persistToSharedFileIfNeeded(\n screenshot: ScreenshotItem,\n options: {\n markAsPersisted: boolean;\n },\n ): Promise<ScreenshotRef> {\n const { relativePath, absolutePath } =\n this.fileLocations.get(screenshot.id) ??\n (await this.writeImageFileIfNeeded({\n id: screenshot.id,\n extension: screenshot.extension,\n rawBase64: screenshot.rawBase64,\n }));\n\n if (options.markAsPersisted) {\n return screenshot.markPersistedToPath(relativePath, absolutePath);\n }\n\n return screenshot.registerPersistedFileCopy(relativePath, absolutePath);\n }\n\n private async writeImageFileIfNeeded(image: {\n id: string;\n extension: string;\n rawBase64: string;\n }): Promise<{ relativePath: string; absolutePath: string }> {\n const screenshotsDir = this.screenshotsDir;\n if (!screenshotsDir) {\n throw new Error(\n 'ReportImageStore: screenshotsDir is required when file persistence is enabled',\n );\n }\n if (!existsSync(screenshotsDir)) {\n mkdirSync(screenshotsDir, { recursive: true });\n }\n\n const known = this.fileLocations.get(image.id);\n if (known) return known;\n const bytes = Buffer.from(image.rawBase64, 'base64');\n const fileName = reportImageFileName(image.extension, bytes);\n const relativePath = `./screenshots/${fileName}`;\n const absolutePath = join(screenshotsDir, fileName);\n let publication = this.pendingFiles.get(fileName);\n if (!publication) {\n publication = (async () => {\n if (!this.writtenFileIds.has(fileName)) {\n await writeReportArtifactFile(absolutePath, bytes);\n this.writtenFileIds.add(fileName);\n }\n return { relativePath, absolutePath };\n })();\n this.pendingFiles.set(fileName, publication);\n }\n try {\n const location = await publication;\n this.fileLocations.set(image.id, location);\n return location;\n } finally {\n this.pendingFiles.delete(fileName);\n }\n }\n\n /** Resolve any stored report image reference to a data URI. */\n loadDataUri(refInput: unknown): string {\n const ref = normalizeStoredImageRef(refInput);\n if (!ref) {\n throw new Error('ReportImageStore: invalid image reference');\n }\n\n const resolved = resolveImageSource(ref, {\n reportPath: this.reportPath,\n imageDirectory: this.screenshotsDir,\n });\n\n if (resolved.type === 'data-uri') {\n return resolved.dataUri;\n }\n\n const data = readFileSync(resolved.filePath);\n return `data:${resolved.mimeType};base64,${data.toString('base64')}`;\n }\n\n /** @deprecated Use loadDataUri. */\n loadBase64(refInput: unknown): string {\n const screenshotRef = normalizeScreenshotRef(refInput);\n if (!screenshotRef) {\n throw new Error('ReportImageStore: invalid screenshot reference');\n }\n return this.loadDataUri(screenshotRef);\n }\n\n cleanup(): void {\n if (\n this.mode === 'directory' &&\n this.screenshotsDir &&\n existsSync(this.screenshotsDir)\n ) {\n rmSync(this.screenshotsDir, { recursive: true, force: true });\n }\n this.writtenInlineIds.clear();\n this.writtenFileIds.clear();\n this.fileLocations.clear();\n this.pendingFiles.clear();\n this.existingInlineImageIdsPromise = undefined;\n }\n}\n\n/** @deprecated Use ReportImageStore. */\nexport { ReportImageStore as ScreenshotStore };\n"],"names":["base64ImageDataUrlPattern","parseBase64ImageDataUrl","imageUrl","match","Error","mimeType","imageFileExtensionForMimeType","isBase64ImageDataUrl","value","reportImageFileName","extension","bytes","hash","createHash","resolveImageSource","refInput","options","ref","normalizeStoredImageRef","id","resolveReportRelativePath","filePath","isAbsolute","join","dirname","explicitFilePath","existsSync","inlineDataUri","extractImageByIdSync","imageRefFileExtension","companionImagePath","siblingImagePath","resolveScreenshotSource","normalizeScreenshotRef","ReportImageStore","file","screenshot","shouldWriteFileCopy","fileRef","rawBase64","Buffer","fileLocation","imageIds","collectImageScriptIds","imageId","error","relativePath","absolutePath","image","screenshotsDir","mkdirSync","known","fileName","publication","writeReportArtifactFile","location","resolved","data","readFileSync","screenshotRef","rmSync","undefined","Set","Map","Boolean"],"mappings":"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"}
@@ -1,32 +1,13 @@
1
- import { copyFileSync, existsSync, mkdirSync, statSync, writeFileSync } from "node:fs";
1
+ import { existsSync, mkdirSync, statSync, writeFileSync } from "node:fs";
2
2
  import { basename, dirname, extname, join, resolve } from "node:path";
3
3
  import { z } from "zod";
4
4
  import { readReportRunnerDump, streamReportDumpScripts } from "./dump/report-artifact-reader.mjs";
5
- import { resolveScreenshotSource } from "./dump/screenshot-store.mjs";
5
+ import { ReportImageExporter } from "./dump/report-image-export.mjs";
6
6
  import { deriveCaseStatus } from "./dump/task-status.mjs";
7
7
  import { ReportMergingTool, collectDedupedExecutions, exportReportArtifact, splitReportHtmlByExecution } from "./report.mjs";
8
8
  import { reportToMarkdown } from "./report-markdown.mjs";
9
9
  import { collectReportSummary } from "./report-stats.mjs";
10
10
  import { ReportActionDump } from "./types.mjs";
11
- function writeAttachmentFromReport(attachment, opts) {
12
- const { suggestedFileName, id, mimeType } = attachment;
13
- if (opts.writtenFiles.has(suggestedFileName)) return;
14
- const absolutePath = join(opts.screenshotsDir, suggestedFileName);
15
- const resolved = resolveScreenshotSource(attachment.sourceRef ?? null, {
16
- reportPath: opts.htmlPath,
17
- fallbackId: id,
18
- fallbackMimeType: mimeType || 'image/png'
19
- });
20
- if ('data-uri' === resolved.type) {
21
- const rawBase64 = resolved.dataUri.replace(/^data:image\/[a-zA-Z+]+;base64,/, '');
22
- writeFileSync(absolutePath, Buffer.from(rawBase64, 'base64'));
23
- opts.writtenFiles.add(suggestedFileName);
24
- return;
25
- }
26
- if (!existsSync(resolved.filePath)) throw new Error(`Cannot resolve screenshot "${id}" for markdown attachment from ${opts.htmlPath}`);
27
- copyFileSync(resolved.filePath, absolutePath);
28
- opts.writtenFiles.add(suggestedFileName);
29
- }
30
11
  async function markdownFromReport(htmlPath, outputDir) {
31
12
  const screenshotsDir = join(outputDir, 'screenshots');
32
13
  mkdirSync(outputDir, {
@@ -44,20 +25,23 @@ async function markdownFromReport(htmlPath, outputDir) {
44
25
  deviceType: baseDump.deviceType,
45
26
  executions
46
27
  });
47
- const result = reportToMarkdown(mergedReport);
28
+ const images = new ReportImageExporter(htmlPath, screenshotsDir);
29
+ const result = reportToMarkdown(mergedReport, {
30
+ resolveScreenshotFileName: (attachment)=>images.write(attachment.sourceRef ?? {
31
+ type: 'midscene_screenshot_ref',
32
+ id: attachment.id,
33
+ capturedAt: 0,
34
+ mimeType: attachment.mimeType ?? 'image/png',
35
+ storage: 'inline'
36
+ }, attachment.base64Data)
37
+ });
48
38
  const markdownFiles = [];
49
- const writtenScreenshots = new Set();
50
39
  const mdPath = join(outputDir, 'report.md');
51
40
  writeFileSync(mdPath, result.markdown, 'utf-8');
52
41
  markdownFiles.push(mdPath);
53
- for (const attachment of result.attachments)writeAttachmentFromReport(attachment, {
54
- htmlPath,
55
- screenshotsDir,
56
- writtenFiles: writtenScreenshots
57
- });
58
42
  return {
59
43
  markdownFiles,
60
- screenshotFiles: Array.from(writtenScreenshots).sort().map((f)=>join(screenshotsDir, f))
44
+ screenshotFiles: images.files
61
45
  };
62
46
  }
63
47
  function resolveReportHtmlPath(htmlPath) {
@@ -1 +1 @@
1
- {"version":3,"file":"report-cli.mjs","sources":["../../src/report-cli.ts"],"sourcesContent":["import {\n copyFileSync,\n existsSync,\n mkdirSync,\n statSync,\n writeFileSync,\n} from 'node:fs';\nimport * as path from 'node:path';\nimport { z } from 'zod';\nimport {\n readReportRunnerDump,\n streamReportDumpScripts,\n} from './dump/report-artifact-reader';\nimport { resolveScreenshotSource } from './dump/screenshot-store';\nimport { deriveCaseStatus } from './dump/task-status';\nimport {\n ReportMergingTool,\n collectDedupedExecutions,\n exportReportArtifact,\n splitReportHtmlByExecution,\n} from './report';\nimport { reportToMarkdown } from './report-markdown';\nimport type { MarkdownAttachment } from './report-markdown';\nimport { collectReportSummary } from './report-stats';\nimport type { ReportFileAttributes, TestStatus } from './types';\nimport { ReportActionDump } from './types';\n\ntype ReportCliToolResult = {\n isError: boolean;\n content: Array<{\n type: 'text';\n text: string;\n }>;\n};\n\ntype ReportCliSchema = Record<string, z.ZodTypeAny>;\n\nexport interface ReportCliCommandDefinition {\n name: string;\n description: string;\n schema: ReportCliSchema;\n handler: (args: Record<string, unknown>) => Promise<ReportCliToolResult>;\n}\n\nexport interface ReportCliCommandEntry {\n name: string;\n def: ReportCliCommandDefinition;\n}\n\nexport type ConsumeReportFileAction =\n | 'split'\n | 'to-markdown'\n | 'merge-html'\n | 'export';\n\nexport interface ConsumeReportFileOptions {\n htmlPath: string;\n outputDir: string;\n}\n\nexport type SplitReportFileOptions = ConsumeReportFileOptions;\nexport type ReportFileToMarkdownOptions = ConsumeReportFileOptions;\n\nexport interface MergeReportFilesOptions {\n htmlPaths: string[];\n outputDir?: string;\n outputName?: string;\n outputFormat?: 'single-html' | 'html-and-external-assets';\n overwrite?: boolean;\n}\n\nexport interface MergeReportFilesResult {\n mergedReportPath: string;\n}\n\nfunction writeAttachmentFromReport(\n attachment: MarkdownAttachment,\n opts: {\n htmlPath: string;\n screenshotsDir: string;\n writtenFiles: Set<string>;\n },\n): void {\n const { suggestedFileName, id, mimeType } = attachment;\n if (opts.writtenFiles.has(suggestedFileName)) return;\n\n const absolutePath = path.join(opts.screenshotsDir, suggestedFileName);\n\n const resolved = resolveScreenshotSource(attachment.sourceRef ?? null, {\n reportPath: opts.htmlPath,\n fallbackId: id,\n fallbackMimeType: (mimeType || 'image/png') as 'image/png' | 'image/jpeg',\n });\n\n if (resolved.type === 'data-uri') {\n const rawBase64 = resolved.dataUri.replace(\n /^data:image\\/[a-zA-Z+]+;base64,/,\n '',\n );\n writeFileSync(absolutePath, Buffer.from(rawBase64, 'base64'));\n opts.writtenFiles.add(suggestedFileName);\n return;\n }\n\n if (!existsSync(resolved.filePath)) {\n throw new Error(\n `Cannot resolve screenshot \"${id}\" for markdown attachment from ${opts.htmlPath}`,\n );\n }\n\n copyFileSync(resolved.filePath, absolutePath);\n opts.writtenFiles.add(suggestedFileName);\n}\n\nasync function markdownFromReport(\n htmlPath: string,\n outputDir: string,\n): Promise<{ markdownFiles: string[]; screenshotFiles: string[] }> {\n const screenshotsDir = path.join(outputDir, 'screenshots');\n\n mkdirSync(outputDir, { recursive: true });\n mkdirSync(screenshotsDir, { recursive: true });\n\n const { baseDump, executions } = collectDedupedExecutions(htmlPath);\n\n const mergedReport = new ReportActionDump({\n sdkVersion: baseDump.sdkVersion,\n groupName: baseDump.groupName,\n groupDescription: baseDump.groupDescription,\n modelBriefs: baseDump.modelBriefs,\n deviceType: baseDump.deviceType,\n executions,\n });\n\n const result = reportToMarkdown(mergedReport);\n\n const markdownFiles: string[] = [];\n const writtenScreenshots = new Set<string>();\n\n const mdPath = path.join(outputDir, 'report.md');\n writeFileSync(mdPath, result.markdown, 'utf-8');\n markdownFiles.push(mdPath);\n\n for (const attachment of result.attachments) {\n writeAttachmentFromReport(attachment, {\n htmlPath,\n screenshotsDir,\n writtenFiles: writtenScreenshots,\n });\n }\n\n return {\n markdownFiles,\n screenshotFiles: Array.from(writtenScreenshots)\n .sort()\n .map((f) => path.join(screenshotsDir, f)),\n };\n}\n\nfunction resolveReportHtmlPath(htmlPath: string): string {\n const normalizedPath = path.resolve(htmlPath);\n\n if (!existsSync(normalizedPath)) {\n throw new Error(`report-tool: --htmlPath does not exist: ${htmlPath}`);\n }\n\n const stats = statSync(normalizedPath);\n if (!stats.isDirectory()) {\n return normalizedPath;\n }\n\n const indexHtmlPath = path.join(normalizedPath, 'index.html');\n if (!existsSync(indexHtmlPath)) {\n throw new Error(\n `report-tool: \"${htmlPath}\" is not an HTML report file, and no index.html was found under this directory.`,\n );\n }\n\n return indexHtmlPath;\n}\n\nexport function splitReportFile(options: SplitReportFileOptions): {\n executionJsonFiles: string[];\n screenshotFiles: string[];\n} {\n const { htmlPath, outputDir } = options;\n if (!htmlPath) {\n throw new Error('splitReportFile: htmlPath is required');\n }\n\n if (!outputDir) {\n throw new Error('splitReportFile: outputDir is required');\n }\n\n const resolvedHtmlPath = resolveReportHtmlPath(htmlPath);\n return splitReportHtmlByExecution({\n htmlPath: resolvedHtmlPath,\n outputDir,\n });\n}\n\nexport async function reportFileToMarkdown(\n options: ReportFileToMarkdownOptions,\n): Promise<{ markdownFiles: string[]; screenshotFiles: string[] }> {\n const { htmlPath, outputDir } = options;\n if (!htmlPath) {\n throw new Error('reportFileToMarkdown: htmlPath is required');\n }\n\n if (!outputDir) {\n throw new Error('reportFileToMarkdown: outputDir is required');\n }\n\n const resolvedHtmlPath = resolveReportHtmlPath(htmlPath);\n return markdownFromReport(resolvedHtmlPath, outputDir);\n}\n\nconst TEST_STATUS_VALUES: ReadonlySet<TestStatus> = new Set<TestStatus>([\n 'passed',\n 'failed',\n 'timedOut',\n 'skipped',\n 'interrupted',\n]);\n\nconst FAILING_TEST_STATUSES: ReadonlySet<TestStatus> = new Set<TestStatus>([\n 'failed',\n 'timedOut',\n 'interrupted',\n]);\n\n/**\n * Reuse a `playwright_test_status` already recorded on the source report's dump\n * scripts (e.g. a Playwright-generated report carries the precise\n * timedOut/skipped/interrupted status). Returns undefined when the source has\n * no such attribute, so the caller can fall back to deriving from the dump.\n *\n * A single source report may bundle several executions (e.g. an already-merged\n * report), so surface a failing status if any execution failed — mirroring\n * `deriveCaseStatus`'s any-failure-wins semantics rather than taking the first\n * script's status and masking later failures.\n */\nfunction readSourceTestStatus(htmlPath: string): TestStatus | undefined {\n const recorded: TestStatus[] = [];\n streamReportDumpScripts(htmlPath, ({ openTag }) => {\n const match = openTag.match(/playwright_test_status=\"([^\"]*)\"/);\n if (!match) return false;\n const status = decodeURIComponent(match[1]) as TestStatus;\n if (TEST_STATUS_VALUES.has(status)) {\n recorded.push(status);\n }\n return false;\n });\n if (recorded.length === 0) return undefined;\n return recorded.find((s) => FAILING_TEST_STATUSES.has(s)) ?? recorded[0];\n}\n\nfunction deriveReportAttributesFromHtml(\n htmlPath: string,\n index: number,\n): ReportFileAttributes {\n const fallbackId = `${path.basename(path.dirname(htmlPath)) || path.basename(htmlPath, path.extname(htmlPath))}-${index + 1}`;\n const runnerDump = readReportRunnerDump(htmlPath);\n const recordedStatus = readSourceTestStatus(htmlPath);\n try {\n const { baseDump, executions } = collectDedupedExecutions(htmlPath);\n // Prefer a status the source report already recorded (keeps Playwright's\n // precise timedOut/skipped/interrupted); otherwise infer it from the dump\n // so a failing case is not silently merged in as passed.\n const testStatus =\n recordedStatus && FAILING_TEST_STATUSES.has(recordedStatus)\n ? recordedStatus\n : runnerDump?.status === 'failed'\n ? 'failed'\n : (recordedStatus ??\n (runnerDump ? 'passed' : deriveCaseStatus(executions)));\n return {\n testId: fallbackId,\n testTitle: baseDump.groupName || fallbackId,\n testDescription: baseDump.groupDescription ?? '',\n testDuration:\n runnerDump?.durationMs ??\n collectReportSummary({ executions }).timing.wallTimeMs ??\n 0,\n testStatus,\n };\n } catch {\n return {\n testId: fallbackId,\n testTitle: fallbackId,\n testDescription: '',\n testDuration: 0,\n testStatus:\n recordedStatus && FAILING_TEST_STATUSES.has(recordedStatus)\n ? recordedStatus\n : runnerDump?.status === 'failed'\n ? 'failed'\n : (recordedStatus ?? 'passed'),\n };\n }\n}\n\nexport function mergeReportFiles(\n options: MergeReportFilesOptions,\n): MergeReportFilesResult {\n const {\n htmlPaths,\n outputDir,\n outputName,\n outputFormat,\n overwrite = false,\n } = options;\n if (!htmlPaths || htmlPaths.length === 0) {\n throw new Error('mergeReportFiles: htmlPaths is required');\n }\n\n const resolvedPaths = htmlPaths.map((p) => resolveReportHtmlPath(p));\n\n const tool = new ReportMergingTool();\n resolvedPaths.forEach((htmlPath, index) => {\n tool.append({\n reportFilePath: htmlPath,\n reportAttributes: deriveReportAttributesFromHtml(htmlPath, index),\n });\n });\n\n const mergedReportPath = tool.mergeReports(outputName ?? 'AUTO', {\n overwrite,\n outputDir,\n outputFormat,\n });\n\n if (!mergedReportPath) {\n throw new Error('mergeReportFiles: failed to produce a merged report');\n }\n\n return { mergedReportPath };\n}\n\nfunction normalizeHtmlReportArg(raw: unknown): string[] | undefined {\n if (raw === undefined || raw === null) return undefined;\n if (Array.isArray(raw)) {\n return raw.filter(\n (p): p is string => typeof p === 'string' && p.length > 0,\n );\n }\n if (typeof raw === 'string') {\n const trimmed = raw.trim();\n return trimmed ? [trimmed] : [];\n }\n return undefined;\n}\n\nconst reportCommandDefinition: ReportCliCommandDefinition = {\n name: 'report-tool',\n description:\n 'Transform Midscene report artifacts, including splitting executions, converting to markdown, merging multiple reports, and exporting portable reports in either storage format.',\n schema: {\n action: z\n .enum(['split', 'to-markdown', 'merge-html', 'export'])\n .optional()\n .describe(\n 'Report action to run. Supports: split, to-markdown, merge-html, export. Defaults to split.',\n ),\n htmlPath: z\n .string()\n .optional()\n .describe(\n 'Input report HTML path (e.g. ./report/index.html). Used by split, to-markdown, and export.',\n ),\n htmlReport: z\n .union([z.string(), z.array(z.string())])\n .optional()\n .describe(\n 'Input report HTML path for the merge action. Repeat the flag to merge multiple reports (e.g. --htmlReport ./a/index.html --htmlReport ./b.html).',\n ),\n outputDir: z\n .string()\n .optional()\n .describe(\n 'Output directory for generated report artifacts. For merge, defaults to the Midscene report directory.',\n ),\n outputName: z\n .string()\n .optional()\n .describe(\n 'Output report file/directory name (without .html). Merge generates a name; export uses the input name by default.',\n ),\n overwrite: z\n .union([z.boolean(), z.string()])\n .optional()\n .describe('Overwrite an existing output report for merge or export.'),\n outputFormat: z\n .enum(['single-html', 'html-and-external-assets'])\n .optional()\n .describe(\n 'Final delivery format for merge and export. Defaults to single-html.',\n ),\n },\n handler: async (args) => {\n const {\n action = 'split',\n htmlPath,\n htmlReport,\n outputDir,\n outputName,\n overwrite,\n outputFormat,\n } = args as {\n action?: string;\n htmlPath?: string;\n htmlReport?: unknown;\n outputDir?: string;\n outputName?: string;\n overwrite?: unknown;\n outputFormat?: 'single-html' | 'html-and-external-assets';\n };\n if (\n action !== 'split' &&\n action !== 'to-markdown' &&\n action !== 'merge-html' &&\n action !== 'export'\n ) {\n throw new Error(\n `report-tool: unsupported --action value \"${action}\". Currently supported: split, to-markdown, merge-html, export`,\n );\n }\n\n if (action === 'merge-html') {\n const paths = normalizeHtmlReportArg(htmlReport);\n if (!paths || paths.length === 0) {\n throw new Error(\n 'report-tool: --htmlReport is required for action \"merge-html\". Repeat --htmlReport for each report (e.g. --htmlReport ./a/index.html --htmlReport ./b.html).',\n );\n }\n\n const overwriteFlag =\n overwrite === true || overwrite === 'true' || overwrite === '1';\n\n const result = mergeReportFiles({\n htmlPaths: paths,\n outputDir,\n outputName,\n outputFormat,\n overwrite: overwriteFlag,\n });\n\n return {\n isError: false,\n content: [\n {\n type: 'text',\n text: `Merged ${paths.length} report(s) into ${result.mergedReportPath}`,\n },\n ],\n };\n }\n\n if (!htmlPath) {\n throw new Error('report-tool: --htmlPath is required');\n }\n\n if (!outputDir) {\n throw new Error('report-tool: --outputDir is required');\n }\n\n if (action === 'export') {\n const input = resolveReportHtmlPath(htmlPath);\n const name =\n outputName ??\n (path.basename(input) === 'index.html'\n ? path.basename(path.dirname(input))\n : path.basename(input, '.html'));\n const reportPath = await exportReportArtifact({\n reportPath: input,\n outputDir,\n reportFileName: name,\n outputFormat,\n overwrite:\n overwrite === true || overwrite === 'true' || overwrite === '1',\n });\n return {\n isError: false,\n content: [{ type: 'text', text: `Report exported to ${reportPath}` }],\n };\n }\n\n if (action === 'to-markdown') {\n const result = await reportFileToMarkdown({\n htmlPath,\n outputDir,\n });\n return {\n isError: false,\n content: [\n {\n type: 'text',\n text: `Markdown export completed. Generated ${result.markdownFiles.length} markdown file(s) and ${result.screenshotFiles.length} screenshot(s). Output path: ${outputDir}`,\n },\n ],\n };\n }\n\n const result = splitReportFile({\n htmlPath,\n outputDir,\n });\n\n return {\n isError: false,\n content: [\n {\n type: 'text',\n text: `Report split completed. Generated ${result.executionJsonFiles.length} execution JSON files and ${result.screenshotFiles.length} screenshots. Output path: ${outputDir}`,\n },\n ],\n };\n },\n};\n\nexport function createReportCliCommands(): ReportCliCommandEntry[] {\n return [\n {\n name: 'report-tool',\n def: reportCommandDefinition,\n },\n ];\n}\n"],"names":["writeAttachmentFromReport","attachment","opts","suggestedFileName","id","mimeType","absolutePath","path","resolved","resolveScreenshotSource","rawBase64","writeFileSync","Buffer","existsSync","Error","copyFileSync","markdownFromReport","htmlPath","outputDir","screenshotsDir","mkdirSync","baseDump","executions","collectDedupedExecutions","mergedReport","ReportActionDump","result","reportToMarkdown","markdownFiles","writtenScreenshots","Set","mdPath","Array","f","resolveReportHtmlPath","normalizedPath","stats","statSync","indexHtmlPath","splitReportFile","options","resolvedHtmlPath","splitReportHtmlByExecution","reportFileToMarkdown","TEST_STATUS_VALUES","FAILING_TEST_STATUSES","readSourceTestStatus","recorded","streamReportDumpScripts","openTag","match","status","decodeURIComponent","s","deriveReportAttributesFromHtml","index","fallbackId","runnerDump","readReportRunnerDump","recordedStatus","testStatus","deriveCaseStatus","collectReportSummary","mergeReportFiles","htmlPaths","outputName","outputFormat","overwrite","resolvedPaths","p","tool","ReportMergingTool","mergedReportPath","normalizeHtmlReportArg","raw","trimmed","reportCommandDefinition","z","args","action","htmlReport","paths","overwriteFlag","input","name","reportPath","exportReportArtifact","createReportCliCommands"],"mappings":"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"}
1
+ {"version":3,"file":"report-cli.mjs","sources":["../../src/report-cli.ts"],"sourcesContent":["import { existsSync, mkdirSync, statSync, writeFileSync } from 'node:fs';\nimport * as path from 'node:path';\nimport { z } from 'zod';\nimport {\n readReportRunnerDump,\n streamReportDumpScripts,\n} from './dump/report-artifact-reader';\nimport { ReportImageExporter } from './dump/report-image-export';\nimport { deriveCaseStatus } from './dump/task-status';\nimport {\n ReportMergingTool,\n collectDedupedExecutions,\n exportReportArtifact,\n splitReportHtmlByExecution,\n} from './report';\nimport { reportToMarkdown } from './report-markdown';\nimport { collectReportSummary } from './report-stats';\nimport type { ReportFileAttributes, TestStatus } from './types';\nimport { ReportActionDump } from './types';\n\ntype ReportCliToolResult = {\n isError: boolean;\n content: Array<{\n type: 'text';\n text: string;\n }>;\n};\n\ntype ReportCliSchema = Record<string, z.ZodTypeAny>;\n\nexport interface ReportCliCommandDefinition {\n name: string;\n description: string;\n schema: ReportCliSchema;\n handler: (args: Record<string, unknown>) => Promise<ReportCliToolResult>;\n}\n\nexport interface ReportCliCommandEntry {\n name: string;\n def: ReportCliCommandDefinition;\n}\n\nexport type ConsumeReportFileAction =\n | 'split'\n | 'to-markdown'\n | 'merge-html'\n | 'export';\n\nexport interface ConsumeReportFileOptions {\n htmlPath: string;\n outputDir: string;\n}\n\nexport type SplitReportFileOptions = ConsumeReportFileOptions;\nexport type ReportFileToMarkdownOptions = ConsumeReportFileOptions;\n\nexport interface MergeReportFilesOptions {\n htmlPaths: string[];\n outputDir?: string;\n outputName?: string;\n outputFormat?: 'single-html' | 'html-and-external-assets';\n overwrite?: boolean;\n}\n\nexport interface MergeReportFilesResult {\n mergedReportPath: string;\n}\n\nasync function markdownFromReport(\n htmlPath: string,\n outputDir: string,\n): Promise<{ markdownFiles: string[]; screenshotFiles: string[] }> {\n const screenshotsDir = path.join(outputDir, 'screenshots');\n\n mkdirSync(outputDir, { recursive: true });\n mkdirSync(screenshotsDir, { recursive: true });\n\n const { baseDump, executions } = collectDedupedExecutions(htmlPath);\n\n const mergedReport = new ReportActionDump({\n sdkVersion: baseDump.sdkVersion,\n groupName: baseDump.groupName,\n groupDescription: baseDump.groupDescription,\n modelBriefs: baseDump.modelBriefs,\n deviceType: baseDump.deviceType,\n executions,\n });\n\n const images = new ReportImageExporter(htmlPath, screenshotsDir);\n const result = reportToMarkdown(mergedReport, {\n resolveScreenshotFileName: (attachment) =>\n images.write(\n attachment.sourceRef ?? {\n type: 'midscene_screenshot_ref',\n id: attachment.id,\n capturedAt: 0,\n mimeType: (attachment.mimeType ?? 'image/png') as\n | 'image/png'\n | 'image/jpeg',\n storage: 'inline',\n },\n attachment.base64Data,\n ),\n });\n\n const markdownFiles: string[] = [];\n\n const mdPath = path.join(outputDir, 'report.md');\n writeFileSync(mdPath, result.markdown, 'utf-8');\n markdownFiles.push(mdPath);\n\n return {\n markdownFiles,\n screenshotFiles: images.files,\n };\n}\n\nfunction resolveReportHtmlPath(htmlPath: string): string {\n const normalizedPath = path.resolve(htmlPath);\n\n if (!existsSync(normalizedPath)) {\n throw new Error(`report-tool: --htmlPath does not exist: ${htmlPath}`);\n }\n\n const stats = statSync(normalizedPath);\n if (!stats.isDirectory()) {\n return normalizedPath;\n }\n\n const indexHtmlPath = path.join(normalizedPath, 'index.html');\n if (!existsSync(indexHtmlPath)) {\n throw new Error(\n `report-tool: \"${htmlPath}\" is not an HTML report file, and no index.html was found under this directory.`,\n );\n }\n\n return indexHtmlPath;\n}\n\nexport function splitReportFile(options: SplitReportFileOptions): {\n executionJsonFiles: string[];\n screenshotFiles: string[];\n} {\n const { htmlPath, outputDir } = options;\n if (!htmlPath) {\n throw new Error('splitReportFile: htmlPath is required');\n }\n\n if (!outputDir) {\n throw new Error('splitReportFile: outputDir is required');\n }\n\n const resolvedHtmlPath = resolveReportHtmlPath(htmlPath);\n return splitReportHtmlByExecution({\n htmlPath: resolvedHtmlPath,\n outputDir,\n });\n}\n\nexport async function reportFileToMarkdown(\n options: ReportFileToMarkdownOptions,\n): Promise<{ markdownFiles: string[]; screenshotFiles: string[] }> {\n const { htmlPath, outputDir } = options;\n if (!htmlPath) {\n throw new Error('reportFileToMarkdown: htmlPath is required');\n }\n\n if (!outputDir) {\n throw new Error('reportFileToMarkdown: outputDir is required');\n }\n\n const resolvedHtmlPath = resolveReportHtmlPath(htmlPath);\n return markdownFromReport(resolvedHtmlPath, outputDir);\n}\n\nconst TEST_STATUS_VALUES: ReadonlySet<TestStatus> = new Set<TestStatus>([\n 'passed',\n 'failed',\n 'timedOut',\n 'skipped',\n 'interrupted',\n]);\n\nconst FAILING_TEST_STATUSES: ReadonlySet<TestStatus> = new Set<TestStatus>([\n 'failed',\n 'timedOut',\n 'interrupted',\n]);\n\n/**\n * Reuse a `playwright_test_status` already recorded on the source report's dump\n * scripts (e.g. a Playwright-generated report carries the precise\n * timedOut/skipped/interrupted status). Returns undefined when the source has\n * no such attribute, so the caller can fall back to deriving from the dump.\n *\n * A single source report may bundle several executions (e.g. an already-merged\n * report), so surface a failing status if any execution failed — mirroring\n * `deriveCaseStatus`'s any-failure-wins semantics rather than taking the first\n * script's status and masking later failures.\n */\nfunction readSourceTestStatus(htmlPath: string): TestStatus | undefined {\n const recorded: TestStatus[] = [];\n streamReportDumpScripts(htmlPath, ({ openTag }) => {\n const match = openTag.match(/playwright_test_status=\"([^\"]*)\"/);\n if (!match) return false;\n const status = decodeURIComponent(match[1]) as TestStatus;\n if (TEST_STATUS_VALUES.has(status)) {\n recorded.push(status);\n }\n return false;\n });\n if (recorded.length === 0) return undefined;\n return recorded.find((s) => FAILING_TEST_STATUSES.has(s)) ?? recorded[0];\n}\n\nfunction deriveReportAttributesFromHtml(\n htmlPath: string,\n index: number,\n): ReportFileAttributes {\n const fallbackId = `${path.basename(path.dirname(htmlPath)) || path.basename(htmlPath, path.extname(htmlPath))}-${index + 1}`;\n const runnerDump = readReportRunnerDump(htmlPath);\n const recordedStatus = readSourceTestStatus(htmlPath);\n try {\n const { baseDump, executions } = collectDedupedExecutions(htmlPath);\n // Prefer a status the source report already recorded (keeps Playwright's\n // precise timedOut/skipped/interrupted); otherwise infer it from the dump\n // so a failing case is not silently merged in as passed.\n const testStatus =\n recordedStatus && FAILING_TEST_STATUSES.has(recordedStatus)\n ? recordedStatus\n : runnerDump?.status === 'failed'\n ? 'failed'\n : (recordedStatus ??\n (runnerDump ? 'passed' : deriveCaseStatus(executions)));\n return {\n testId: fallbackId,\n testTitle: baseDump.groupName || fallbackId,\n testDescription: baseDump.groupDescription ?? '',\n testDuration:\n runnerDump?.durationMs ??\n collectReportSummary({ executions }).timing.wallTimeMs ??\n 0,\n testStatus,\n };\n } catch {\n return {\n testId: fallbackId,\n testTitle: fallbackId,\n testDescription: '',\n testDuration: 0,\n testStatus:\n recordedStatus && FAILING_TEST_STATUSES.has(recordedStatus)\n ? recordedStatus\n : runnerDump?.status === 'failed'\n ? 'failed'\n : (recordedStatus ?? 'passed'),\n };\n }\n}\n\nexport function mergeReportFiles(\n options: MergeReportFilesOptions,\n): MergeReportFilesResult {\n const {\n htmlPaths,\n outputDir,\n outputName,\n outputFormat,\n overwrite = false,\n } = options;\n if (!htmlPaths || htmlPaths.length === 0) {\n throw new Error('mergeReportFiles: htmlPaths is required');\n }\n\n const resolvedPaths = htmlPaths.map((p) => resolveReportHtmlPath(p));\n\n const tool = new ReportMergingTool();\n resolvedPaths.forEach((htmlPath, index) => {\n tool.append({\n reportFilePath: htmlPath,\n reportAttributes: deriveReportAttributesFromHtml(htmlPath, index),\n });\n });\n\n const mergedReportPath = tool.mergeReports(outputName ?? 'AUTO', {\n overwrite,\n outputDir,\n outputFormat,\n });\n\n if (!mergedReportPath) {\n throw new Error('mergeReportFiles: failed to produce a merged report');\n }\n\n return { mergedReportPath };\n}\n\nfunction normalizeHtmlReportArg(raw: unknown): string[] | undefined {\n if (raw === undefined || raw === null) return undefined;\n if (Array.isArray(raw)) {\n return raw.filter(\n (p): p is string => typeof p === 'string' && p.length > 0,\n );\n }\n if (typeof raw === 'string') {\n const trimmed = raw.trim();\n return trimmed ? [trimmed] : [];\n }\n return undefined;\n}\n\nconst reportCommandDefinition: ReportCliCommandDefinition = {\n name: 'report-tool',\n description:\n 'Transform Midscene report artifacts, including splitting executions, converting to markdown, merging multiple reports, and exporting portable reports in either storage format.',\n schema: {\n action: z\n .enum(['split', 'to-markdown', 'merge-html', 'export'])\n .optional()\n .describe(\n 'Report action to run. Supports: split, to-markdown, merge-html, export. Defaults to split.',\n ),\n htmlPath: z\n .string()\n .optional()\n .describe(\n 'Input report HTML path (e.g. ./report/index.html). Used by split, to-markdown, and export.',\n ),\n htmlReport: z\n .union([z.string(), z.array(z.string())])\n .optional()\n .describe(\n 'Input report HTML path for the merge action. Repeat the flag to merge multiple reports (e.g. --htmlReport ./a/index.html --htmlReport ./b.html).',\n ),\n outputDir: z\n .string()\n .optional()\n .describe(\n 'Output directory for generated report artifacts. For merge, defaults to the Midscene report directory.',\n ),\n outputName: z\n .string()\n .optional()\n .describe(\n 'Output report file/directory name (without .html). Merge generates a name; export uses the input name by default.',\n ),\n overwrite: z\n .union([z.boolean(), z.string()])\n .optional()\n .describe('Overwrite an existing output report for merge or export.'),\n outputFormat: z\n .enum(['single-html', 'html-and-external-assets'])\n .optional()\n .describe(\n 'Final delivery format for merge and export. Defaults to single-html.',\n ),\n },\n handler: async (args) => {\n const {\n action = 'split',\n htmlPath,\n htmlReport,\n outputDir,\n outputName,\n overwrite,\n outputFormat,\n } = args as {\n action?: string;\n htmlPath?: string;\n htmlReport?: unknown;\n outputDir?: string;\n outputName?: string;\n overwrite?: unknown;\n outputFormat?: 'single-html' | 'html-and-external-assets';\n };\n if (\n action !== 'split' &&\n action !== 'to-markdown' &&\n action !== 'merge-html' &&\n action !== 'export'\n ) {\n throw new Error(\n `report-tool: unsupported --action value \"${action}\". Currently supported: split, to-markdown, merge-html, export`,\n );\n }\n\n if (action === 'merge-html') {\n const paths = normalizeHtmlReportArg(htmlReport);\n if (!paths || paths.length === 0) {\n throw new Error(\n 'report-tool: --htmlReport is required for action \"merge-html\". Repeat --htmlReport for each report (e.g. --htmlReport ./a/index.html --htmlReport ./b.html).',\n );\n }\n\n const overwriteFlag =\n overwrite === true || overwrite === 'true' || overwrite === '1';\n\n const result = mergeReportFiles({\n htmlPaths: paths,\n outputDir,\n outputName,\n outputFormat,\n overwrite: overwriteFlag,\n });\n\n return {\n isError: false,\n content: [\n {\n type: 'text',\n text: `Merged ${paths.length} report(s) into ${result.mergedReportPath}`,\n },\n ],\n };\n }\n\n if (!htmlPath) {\n throw new Error('report-tool: --htmlPath is required');\n }\n\n if (!outputDir) {\n throw new Error('report-tool: --outputDir is required');\n }\n\n if (action === 'export') {\n const input = resolveReportHtmlPath(htmlPath);\n const name =\n outputName ??\n (path.basename(input) === 'index.html'\n ? path.basename(path.dirname(input))\n : path.basename(input, '.html'));\n const reportPath = await exportReportArtifact({\n reportPath: input,\n outputDir,\n reportFileName: name,\n outputFormat,\n overwrite:\n overwrite === true || overwrite === 'true' || overwrite === '1',\n });\n return {\n isError: false,\n content: [{ type: 'text', text: `Report exported to ${reportPath}` }],\n };\n }\n\n if (action === 'to-markdown') {\n const result = await reportFileToMarkdown({\n htmlPath,\n outputDir,\n });\n return {\n isError: false,\n content: [\n {\n type: 'text',\n text: `Markdown export completed. Generated ${result.markdownFiles.length} markdown file(s) and ${result.screenshotFiles.length} screenshot(s). Output path: ${outputDir}`,\n },\n ],\n };\n }\n\n const result = splitReportFile({\n htmlPath,\n outputDir,\n });\n\n return {\n isError: false,\n content: [\n {\n type: 'text',\n text: `Report split completed. Generated ${result.executionJsonFiles.length} execution JSON files and ${result.screenshotFiles.length} screenshots. Output path: ${outputDir}`,\n },\n ],\n };\n },\n};\n\nexport function createReportCliCommands(): ReportCliCommandEntry[] {\n return [\n {\n name: 'report-tool',\n def: reportCommandDefinition,\n },\n ];\n}\n"],"names":["markdownFromReport","htmlPath","outputDir","screenshotsDir","path","mkdirSync","baseDump","executions","collectDedupedExecutions","mergedReport","ReportActionDump","images","ReportImageExporter","result","reportToMarkdown","attachment","markdownFiles","mdPath","writeFileSync","resolveReportHtmlPath","normalizedPath","existsSync","Error","stats","statSync","indexHtmlPath","splitReportFile","options","resolvedHtmlPath","splitReportHtmlByExecution","reportFileToMarkdown","TEST_STATUS_VALUES","Set","FAILING_TEST_STATUSES","readSourceTestStatus","recorded","streamReportDumpScripts","openTag","match","status","decodeURIComponent","s","deriveReportAttributesFromHtml","index","fallbackId","runnerDump","readReportRunnerDump","recordedStatus","testStatus","deriveCaseStatus","collectReportSummary","mergeReportFiles","htmlPaths","outputName","outputFormat","overwrite","resolvedPaths","p","tool","ReportMergingTool","mergedReportPath","normalizeHtmlReportArg","raw","Array","trimmed","reportCommandDefinition","z","args","action","htmlReport","paths","overwriteFlag","input","name","reportPath","exportReportArtifact","createReportCliCommands"],"mappings":"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"}
@@ -119,10 +119,11 @@ class ReportGenerator {
119
119
  sourcePath: this.reportPath
120
120
  });
121
121
  for (const diagnostic of dump.diagnostics ?? [])warnReport(diagnostic.message);
122
+ this.enableSourceSnapshots(scopeId);
122
123
  const sourceDump = await this.serializeSourceDump();
123
124
  const images = [];
124
125
  if ('inline' === this.screenshotMode) for (const ref of sourceDump.images.values()){
125
- const bytes = await readFile(join(dirname(this.reportPath), ref.path));
126
+ const bytes = await readFile(join(this.sourceDirectory, ref.path));
126
127
  images.push(generateImageScriptTag(ref.id, `data:${ref.mimeType};base64,${bytes.toString('base64')}`));
127
128
  }
128
129
  if (this.artifactStore) {
@@ -176,19 +177,27 @@ ${generateAgentReportComment(agentDump)}`;
176
177
  await this.flush();
177
178
  return this.reportPath;
178
179
  }
179
- enableSourceSnapshots() {
180
- this.screenshotStore.enableFileCopies();
180
+ enableSourceSnapshots(scopeId = this.reportStreamId) {
181
+ if (!scopeId || '.' === scopeId || '..' === scopeId || /[/\\]/.test(scopeId)) throw new Error('Invalid report scope ID');
182
+ if (this.sourceStore) return;
183
+ this.sourceDirectory = join(getMidsceneRunSubDir('test-results'), scopeId, 'records');
184
+ this.sourceStore = new ReportImageStore({
185
+ mode: 'directory',
186
+ reportPath: join(this.sourceDirectory, 'source.json'),
187
+ screenshotsDir: join(this.sourceDirectory, 'screenshots')
188
+ });
181
189
  }
182
190
  async serializeSourceDump(snapshot) {
183
191
  this.enableSourceSnapshots();
192
+ const store = this.sourceStore;
184
193
  const captured = snapshot ?? this.recorder.capture(void 0, this.lastReportMeta ?? {
185
194
  sdkVersion: '',
186
195
  groupName: 'YAML execution',
187
196
  modelBriefs: []
188
197
  });
189
- for (const screenshot of captured.screenshots)await this.screenshotStore.persist(screenshot);
198
+ for (const screenshot of captured.screenshots)await store.persistFileCopy(screenshot);
190
199
  const references = new Map();
191
- for (const url of captured.referenceImageUrls)references.set(url, await this.screenshotStore.persistReferenceImage(url));
200
+ for (const url of captured.referenceImageUrls)references.set(url, await store.persistReferenceImage(url));
192
201
  const images = new Map();
193
202
  const serialized = JSON.stringify(JSON.parse(captured.dump.serializeWithReferenceImages(references)), (_key, value)=>{
194
203
  const ref = normalizeStoredImageRef(value);
@@ -196,7 +205,7 @@ ${generateAgentReportComment(agentDump)}`;
196
205
  const stored = {
197
206
  ...ref,
198
207
  storage: 'file',
199
- path: 'file' === ref.storage ? ref.path : this.screenshotStore.getFilePath(ref.id)
208
+ path: store.getFilePath(ref.id)
200
209
  };
201
210
  images.set(ref.id, stored);
202
211
  return stored;
@@ -216,7 +225,7 @@ ${generateAgentReportComment(agentDump)}`;
216
225
  });
217
226
  const publication = (async ()=>{
218
227
  const { serialized } = await this.serializeSourceDump(snapshot);
219
- const dumpPath = `${this.reportPath}.${scopeId}.${uuid()}.json`;
228
+ const dumpPath = join(this.sourceDirectory, `${this.reportStreamId}.${scopeId}.${uuid()}.json`);
220
229
  mkdirSync(dirname(dumpPath), {
221
230
  recursive: true
222
231
  });
@@ -297,7 +306,10 @@ ${generateAgentReportComment(agentDump)}`;
297
306
  await writeFile(this.reportPath, `${getReportTpl()}${baseUrlFix}${marker}`);
298
307
  this.initialized = true;
299
308
  }
300
- for (const screenshot of execution.collectScreenshots())await this.screenshotStore.persist(screenshot);
309
+ for (const screenshot of execution.collectScreenshots()){
310
+ await this.sourceStore?.persistFileCopy(screenshot);
311
+ await this.screenshotStore.persist(screenshot);
312
+ }
301
313
  const referenceImageRefs = await this.persistReferenceImages(execution);
302
314
  const serialized = singleDump.serializeWithReferenceImages(referenceImageRefs);
303
315
  if (this.artifactStore) await this.artifactStore.writeExecution(this.getExecutionCommentKey(execution), serialized, this.getDumpScriptAttributes());
@@ -375,6 +387,8 @@ ${generateAgentReportComment(agentDump)}`;
375
387
  _define_property(this, "executionLogFileIndexByExecutionKey", new Map());
376
388
  _define_property(this, "reportStreamId", void 0);
377
389
  _define_property(this, "screenshotStore", void 0);
390
+ _define_property(this, "sourceStore", void 0);
391
+ _define_property(this, "sourceDirectory", void 0);
378
392
  _define_property(this, "initialized", false);
379
393
  _define_property(this, "lastExecution", void 0);
380
394
  _define_property(this, "lastReportMeta", void 0);
@@ -1 +1 @@
1
- {"version":3,"file":"report-generator.mjs","sources":["../../src/report-generator.ts"],"sourcesContent":["/*\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 readFile as readFileAsync,\n rename as renameAsync,\n unlink as unlinkAsync,\n writeFile as writeFileAsync,\n} from 'node:fs/promises';\nimport { basename, dirname, join } from 'node:path';\nimport { getMidsceneRunSubDir } from '@midscene/shared/common';\nimport {\n MIDSCENE_REPORT_QUIET,\n globalConfigManager,\n} from '@midscene/shared/env';\nimport { getDebug, logReport } from '@midscene/shared/logger';\nimport { ifInBrowser, uuid } from '@midscene/shared/utils';\nimport {\n type ExecutionRecordSnapshot,\n ExecutionRecorder,\n} from './dump/execution-recorder';\nimport {\n DATA_SCREENSHOT_MODE_ATTR,\n generateAgentReportComment,\n generateDumpScriptTag,\n generateImageScriptTag,\n generateReportArtifactScriptTag,\n generateTestRunReportScriptTag,\n getBaseUrlFixScript,\n} from './dump/html-utils';\nimport { normalizeStoredImageRef } from './dump/image-reference';\nimport { readReportArtifactManifest } from './dump/report-artifact-reader';\nimport { ReportArtifactStore } from './dump/report-artifact-store';\nimport { compactReportDumps } from './dump/report-dump-compactor';\nimport { type ImageUrlRef, ReportImageStore } from './dump/screenshot-store';\nimport type { WorkflowReportSource } from './test-runner/engine/types';\nimport { buildAgentTestRunReportDump } from './test-runner/reporting/agent-report';\nimport type { RunReportInput } from './test-runner/reporting/types';\nimport {\n type ExecutionDump,\n ReportActionDump,\n type ReportAttributes,\n type ReportMeta,\n type ScreenshotMode,\n} from './types';\nimport { getReportTpl } from './utils';\n\nconst warnReport = getDebug('report-generator', { console: true });\nconst maxReportFilenameBytes = 255;\n\nexport interface IReportGenerator {\n /**\n * Write or update a single execution.\n * Inline reports append dump tags; directory reports replace the execution's\n * data file. Both retain the latest state for each stable execution ID.\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 enableSourceSnapshots?(): void;\n createSourceSnapshot?(\n scopeId: string,\n executionIds?: readonly string[],\n ): Promise<WorkflowReportSource | undefined>;\n\n /**\n * Finalize the report. Calls flush() internally.\n */\n finalize(): Promise<string | undefined>;\n\n getReportPath(): string | undefined;\n /** Publish a single root Runner snapshot into this Agent's existing report. */\n writeRunnerReport?(\n input: RunReportInput,\n scopeId: string,\n meta: ReportMeta,\n ): Promise<string | undefined>;\n}\n\nexport const nullReportGenerator: IReportGenerator = {\n onExecutionUpdate: () => {},\n flush: async () => {},\n finalize: async () => undefined,\n getReportPath: () => undefined,\n writeRunnerReport: async () => 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: ReportImageStore;\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 readonly recorder = new ExecutionRecorder();\n private readonly artifactStore?: ReportArtifactStore;\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 finalization?: Promise<string | undefined>;\n private sourcePublications = new Set<Promise<unknown>>();\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 if (this.screenshotMode === 'directory') {\n this.artifactStore = new ReportArtifactStore(\n dirname(this.reportPath),\n this.reportStreamId,\n 'agent',\n options.reuseExistingReport,\n basename(this.reportPath),\n );\n }\n this.screenshotStore = new ReportImageStore({\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 reuseExistingReport: options.reuseExistingReport,\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, opts.outputFormat);\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.assertOpen();\n this.lastExecution = execution;\n this.lastReportMeta = reportMeta;\n this.recorder.record(execution, reportMeta);\n this.mergeReportAttributes(attributes);\n this.writeQueue = this.writeQueue.then(async () => {\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 if (!this.finalization) {\n // Enqueue the final state before sealing the generator to new writes.\n if (this.lastExecution && this.lastReportMeta) {\n this.onExecutionUpdate(this.lastExecution, this.lastReportMeta);\n }\n this.finalization = this.finishPublication();\n }\n return this.finalization;\n }\n\n private async finishPublication(): Promise<string | undefined> {\n // Snapshots freeze independently of the HTML queue, but both must be on\n // disk before their backing executions and diagnostic data are released.\n await Promise.all([this.flush(), ...this.sourcePublications]);\n if (this.initialized) {\n if (this.artifactStore) {\n await this.artifactStore.finalize();\n } else {\n await this.appendAgentReportComment();\n try {\n await compactReportDumps(this.reportPath);\n } catch (error) {\n warnReport(\n `Failed to compact report ${this.reportPath}; keeping the uncompressed report: ${String(error)}`,\n );\n }\n }\n }\n this.recorder.clear();\n this.lastExecution = undefined;\n this.lastReportMeta = undefined;\n this.executionCommentKeyByObject = new WeakMap();\n this.executionLogFileIndexByExecutionKey.clear();\n return this.initialized ? this.reportPath : undefined;\n }\n\n private assertOpen(): void {\n if (this.finalization) throw new Error('The report has been finalized.');\n }\n\n getReportPath(): string | undefined {\n return this.reportPath;\n }\n\n async writeRunnerReport(\n input: RunReportInput,\n scopeId: string,\n meta: ReportMeta,\n ): Promise<string> {\n this.assertOpen();\n this.writeQueue = this.writeQueue.then(async () => {\n // This runs at YAML invocation completion, never on progress ticks. Keep\n // all file I/O asynchronous and publish atomically for an open viewer.\n const reportMeta = this.lastReportMeta ?? meta;\n const executions = this.recorder.executions;\n const agentDump = new ReportActionDump({ ...reportMeta, executions });\n const reportId =\n this.reportAttributes['data-report-id'] ??\n this.reportAttributes['data-group-id'] ??\n this.reportStreamId;\n const dump = buildAgentTestRunReportDump(input, agentDump, {\n reportId,\n scopeId,\n sourcePath: this.reportPath,\n });\n for (const diagnostic of dump.diagnostics ?? [])\n warnReport(diagnostic.message);\n const sourceDump = await this.serializeSourceDump();\n const images: string[] = [];\n if (this.screenshotMode === 'inline') {\n for (const ref of sourceDump.images.values()) {\n const bytes = await readFileAsync(\n join(dirname(this.reportPath), ref.path!),\n );\n images.push(\n generateImageScriptTag(\n ref.id,\n `data:${ref.mimeType};base64,${bytes.toString('base64')}`,\n ),\n );\n }\n }\n if (this.artifactStore) {\n for (const execution of executions) {\n await this.writeExecution(\n execution,\n this.wrapAsReportDump(execution, reportMeta),\n );\n }\n if (!executions.length) {\n // An empty execution group still carries setup/collection diagnostics.\n await this.artifactStore.writeExecution(\n 'empty',\n sourceDump.serialized,\n this.getDumpScriptAttributes(),\n );\n if (!this.initialized) {\n const marker = generateReportArtifactScriptTag('report.json');\n await writeFileAsync(\n this.reportPath,\n `${getReportTpl()}${getBaseUrlFixScript()}\\n${marker}`,\n );\n this.initialized = true;\n }\n }\n await this.artifactStore.writeRunner(JSON.stringify(dump));\n if (!this.firstWriteDone) {\n this.firstWriteDone = true;\n this.printReportPath();\n }\n return;\n }\n // Rebuild from the owned dump and image index, never parse this HTML back.\n const renderedDump =\n this.screenshotMode === 'inline'\n ? JSON.stringify(JSON.parse(sourceDump.serialized), (_key, value) => {\n const ref = normalizeStoredImageRef(value);\n return ref\n ? { ...ref, storage: 'inline', path: undefined }\n : value;\n })\n : sourceDump.serialized;\n const contents = `${getReportTpl()}${this.screenshotMode === 'directory' ? getBaseUrlFixScript() : ''}\n${generateDumpScriptTag(renderedDump, this.getDumpScriptAttributes())}\n${images.join('\\n')}\n${generateTestRunReportScriptTag(dump)}\n${generateAgentReportComment(agentDump)}`;\n mkdirSync(dirname(this.reportPath), { recursive: true });\n const temporaryPath = `${this.reportPath}.runner-${uuid()}.tmp`;\n try {\n await writeFileAsync(temporaryPath, contents);\n await renameAsync(temporaryPath, this.reportPath);\n } finally {\n await unlinkAsync(temporaryPath).catch((error) => {\n if (error.code !== 'ENOENT') throw error;\n });\n }\n this.initialized = true;\n if (!this.firstWriteDone) {\n this.firstWriteDone = true;\n this.printReportPath();\n }\n });\n await this.flush();\n return this.reportPath;\n }\n\n enableSourceSnapshots(): void {\n this.screenshotStore.enableFileCopies();\n }\n\n private async serializeSourceDump(snapshot?: ExecutionRecordSnapshot) {\n this.enableSourceSnapshots();\n const captured =\n snapshot ??\n this.recorder.capture(\n undefined,\n this.lastReportMeta ?? {\n sdkVersion: '',\n groupName: 'YAML execution',\n modelBriefs: [],\n },\n );\n for (const screenshot of captured.screenshots)\n await this.screenshotStore.persist(screenshot);\n const references = new Map<string, ImageUrlRef>();\n for (const url of captured.referenceImageUrls) {\n references.set(\n url,\n await this.screenshotStore.persistReferenceImage(url),\n );\n }\n const images = new Map<\n string,\n ReturnType<typeof normalizeStoredImageRef> & {}\n >();\n const serialized = JSON.stringify(\n JSON.parse(captured.dump.serializeWithReferenceImages(references)),\n (_key, value) => {\n const ref = normalizeStoredImageRef(value);\n if (!ref) return value;\n const stored = {\n ...ref,\n storage: 'file' as const,\n path:\n ref.storage === 'file'\n ? ref.path\n : this.screenshotStore.getFilePath(ref.id),\n };\n images.set(ref.id, stored);\n return stored;\n },\n );\n return { serialized, images };\n }\n\n async createSourceSnapshot(\n scopeId: string,\n executionIds?: readonly string[],\n ): Promise<WorkflowReportSource & { dumpPath: string }> {\n this.assertOpen();\n if (!scopeId || basename(scopeId) !== scopeId)\n throw new Error('Invalid report scope ID');\n const snapshot = this.recorder.capture(\n executionIds,\n this.lastReportMeta ?? {\n sdkVersion: '',\n groupName: 'YAML execution',\n modelBriefs: [],\n },\n );\n const publication = (async () => {\n const { serialized } = await this.serializeSourceDump(snapshot);\n const dumpPath = `${this.reportPath}.${scopeId}.${uuid()}.json`;\n mkdirSync(dirname(dumpPath), { recursive: true });\n await writeFileAsync(dumpPath, serialized, { flag: 'wx' });\n return {\n sourcePath: this.reportPath,\n dumpPath,\n collection: {\n status: 'complete' as const,\n capturedAt: snapshot.capturedAt,\n },\n };\n })();\n this.sourcePublications.add(publication);\n try {\n return await publication;\n } finally {\n this.sourcePublications.delete(publication);\n }\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 logReport(\n `Midscene - report file updated: npx serve ${dirname(this.reportPath)}`,\n );\n } else {\n logReport(`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 const referenceImageRefs = await this.writeExecution(execution, singleDump);\n\n if (this.shouldPersistExecutionDump && !this.artifactStore) {\n await this.persistExecutionDumpToFile(\n execution,\n singleDump,\n referenceImageRefs,\n );\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 if (this.artifactStore && !readReportArtifactManifest(this.reportPath)) {\n throw new Error(\n 'Cannot reuse an incompatible directory report. Choose a new reportFileName or regenerate the report.',\n );\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 if (!existsSync(this.executionDumpDir)) return;\n const existingExecutionIndices = readdirSync(this.executionDumpDir)\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 'data-report-kind': 'agent',\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 assets and a dump tag without duplicating mode-specific workflows.\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 writeExecution(\n execution: ExecutionDump,\n singleDump: ReportActionDump,\n ): Promise<Map<string, ImageUrlRef>> {\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 const baseUrlFix =\n this.screenshotMode === 'directory' ? getBaseUrlFixScript() : '';\n const marker = this.artifactStore\n ? `\\n${generateReportArtifactScriptTag('report.json')}`\n : '';\n await writeFileAsync(\n this.reportPath,\n `${getReportTpl()}${baseUrlFix}${marker}`,\n );\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 const referenceImageRefs = await this.persistReferenceImages(execution);\n\n // Publish the latest facts using the selected storage format.\n const serialized =\n singleDump.serializeWithReferenceImages(referenceImageRefs);\n if (this.artifactStore) {\n await this.artifactStore.writeExecution(\n this.getExecutionCommentKey(execution),\n serialized,\n this.getDumpScriptAttributes(),\n );\n } else {\n await appendFileAsync(\n this.reportPath,\n `\\n${generateDumpScriptTag(serialized, this.getDumpScriptAttributes())}`,\n );\n }\n return referenceImageRefs;\n }\n\n private async persistReferenceImages(\n execution: ExecutionDump,\n ): Promise<Map<string, ImageUrlRef>> {\n const refs = new Map<string, ImageUrlRef>();\n for (const imageUrl of execution.getReferenceImageUrls()) {\n const ref = await this.screenshotStore.persistReferenceImage(imageUrl);\n refs.set(imageUrl, ref);\n }\n return refs;\n }\n\n private async appendAgentReportComment(): Promise<void> {\n if (\n this.agentCommentWritten ||\n !this.lastReportMeta ||\n this.recorder.executions.length === 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: this.recorder.executions,\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 get executionDumpDir(): string {\n // Inline HTML files share their parent directory. Each needs its own\n // numbered JSON namespace; external-asset reports already have one.\n return this.screenshotMode === 'inline'\n ? join(\n dirname(this.reportPath),\n '.execution-dumps',\n basename(this.reportPath),\n )\n : dirname(this.reportPath);\n }\n\n private async persistExecutionDumpToFile(\n execution: ExecutionDump,\n singleDump: ReportActionDump,\n referenceImageRefs: ReadonlyMap<string, ImageUrlRef>,\n ): Promise<void> {\n const dir = this.executionDumpDir;\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(dir, fileName);\n let serialized = singleDump.serializeWithReferenceImages(\n referenceImageRefs,\n 2,\n );\n if (this.screenshotMode === 'inline') {\n // JSON is two directories below the HTML. Point to the existing file\n // copies so it can be consumed without resolving inline HTML assets.\n serialized = JSON.stringify(\n JSON.parse(serialized),\n (_key, value) => {\n const ref = normalizeStoredImageRef(value);\n return ref\n ? {\n ...ref,\n storage: 'file',\n path: `../../${this.screenshotStore.getFilePath(ref.id).replace(/^\\.\\//, '')}`,\n }\n : value;\n },\n 2,\n );\n }\n await writeFileAsync(filePath, serialized, 'utf-8');\n }\n}\n\nfunction ensureHtmlFileName(reportFileName: string): string {\n return reportFileName.endsWith('.html')\n ? reportFileName\n : `${reportFileName}.html`;\n}\n\nfunction validateReportFileName(\n reportFileName: string,\n outputFormat?: 'single-html' | 'html-and-external-assets',\n): 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 const filenameComponent =\n outputFormat === 'html-and-external-assets'\n ? reportFileName\n : ensureHtmlFileName(reportFileName);\n const filenameBytes = new TextEncoder().encode(filenameComponent).byteLength;\n if (filenameBytes > maxReportFilenameBytes) {\n throw new Error(\n `reportFileName produces a filename component of ${filenameBytes} UTF-8 bytes; maximum is ${maxReportFilenameBytes}`,\n );\n }\n}\n"],"names":["warnReport","getDebug","maxReportFilenameBytes","nullReportGenerator","undefined","assertReportGenerationOptions","opts","Error","ReportGenerator","reportFileName","validateReportFileName","ifInBrowser","reportRootDir","getMidsceneRunSubDir","outputDir","join","reportPath","ensureHtmlFileName","execution","reportMeta","attributes","Promise","compactReportDumps","error","String","WeakMap","input","scopeId","meta","executions","agentDump","ReportActionDump","reportId","dump","buildAgentTestRunReportDump","diagnostic","sourceDump","images","ref","bytes","readFileAsync","dirname","generateImageScriptTag","marker","generateReportArtifactScriptTag","writeFileAsync","getReportTpl","getBaseUrlFixScript","JSON","renderedDump","_key","value","normalizeStoredImageRef","contents","generateDumpScriptTag","generateTestRunReportScriptTag","generateAgentReportComment","mkdirSync","temporaryPath","uuid","renameAsync","unlinkAsync","snapshot","captured","screenshot","references","Map","url","serialized","stored","executionIds","basename","publication","dumpPath","globalConfigManager","MIDSCENE_REPORT_QUIET","logReport","singleDump","referenceImageRefs","key","Object","existsSync","readReportArtifactManifest","existingExecutionIndices","readdirSync","name","index","Boolean","Number","Math","DATA_SCREENSHOT_MODE_ATTR","dir","baseUrlFix","appendFileAsync","refs","imageUrl","reportDump","existingKey","executionLogKey","fileIndex","fileName","filePath","options","ExecutionRecorder","Set","ReportArtifactStore","ReportImageStore","id","base64","outputFormat","filenameComponent","filenameBytes","TextEncoder"],"mappings":"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"}
1
+ {"version":3,"file":"report-generator.mjs","sources":["../../src/report-generator.ts"],"sourcesContent":["/*\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 readFile as readFileAsync,\n rename as renameAsync,\n unlink as unlinkAsync,\n writeFile as writeFileAsync,\n} from 'node:fs/promises';\nimport { basename, dirname, join } from 'node:path';\nimport { getMidsceneRunSubDir } from '@midscene/shared/common';\nimport {\n MIDSCENE_REPORT_QUIET,\n globalConfigManager,\n} from '@midscene/shared/env';\nimport { getDebug, logReport } from '@midscene/shared/logger';\nimport { ifInBrowser, uuid } from '@midscene/shared/utils';\nimport {\n type ExecutionRecordSnapshot,\n ExecutionRecorder,\n} from './dump/execution-recorder';\nimport {\n DATA_SCREENSHOT_MODE_ATTR,\n generateAgentReportComment,\n generateDumpScriptTag,\n generateImageScriptTag,\n generateReportArtifactScriptTag,\n generateTestRunReportScriptTag,\n getBaseUrlFixScript,\n} from './dump/html-utils';\nimport { normalizeStoredImageRef } from './dump/image-reference';\nimport { readReportArtifactManifest } from './dump/report-artifact-reader';\nimport { ReportArtifactStore } from './dump/report-artifact-store';\nimport { compactReportDumps } from './dump/report-dump-compactor';\nimport { type ImageUrlRef, ReportImageStore } from './dump/screenshot-store';\nimport type { WorkflowReportSource } from './test-runner/engine/types';\nimport { buildAgentTestRunReportDump } from './test-runner/reporting/agent-report';\nimport type { RunReportInput } from './test-runner/reporting/types';\nimport {\n type ExecutionDump,\n ReportActionDump,\n type ReportAttributes,\n type ReportMeta,\n type ScreenshotMode,\n} from './types';\nimport { getReportTpl } from './utils';\n\nconst warnReport = getDebug('report-generator', { console: true });\nconst maxReportFilenameBytes = 255;\n\nexport interface IReportGenerator {\n /**\n * Write or update a single execution.\n * Inline reports append dump tags; directory reports replace the execution's\n * data file. Both retain the latest state for each stable execution ID.\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 enableSourceSnapshots?(scopeId?: string): void;\n createSourceSnapshot?(\n scopeId: string,\n executionIds?: readonly string[],\n ): Promise<WorkflowReportSource | undefined>;\n\n /**\n * Finalize the report. Calls flush() internally.\n */\n finalize(): Promise<string | undefined>;\n\n getReportPath(): string | undefined;\n /** Publish a single root Runner snapshot into this Agent's existing report. */\n writeRunnerReport?(\n input: RunReportInput,\n scopeId: string,\n meta: ReportMeta,\n ): Promise<string | undefined>;\n}\n\nexport const nullReportGenerator: IReportGenerator = {\n onExecutionUpdate: () => {},\n flush: async () => {},\n finalize: async () => undefined,\n getReportPath: () => undefined,\n writeRunnerReport: async () => 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: ReportImageStore;\n private sourceStore?: ReportImageStore;\n private sourceDirectory?: string;\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 readonly recorder = new ExecutionRecorder();\n private readonly artifactStore?: ReportArtifactStore;\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 finalization?: Promise<string | undefined>;\n private sourcePublications = new Set<Promise<unknown>>();\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 if (this.screenshotMode === 'directory') {\n this.artifactStore = new ReportArtifactStore(\n dirname(this.reportPath),\n this.reportStreamId,\n 'agent',\n options.reuseExistingReport,\n basename(this.reportPath),\n );\n }\n this.screenshotStore = new ReportImageStore({\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 reuseExistingReport: options.reuseExistingReport,\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, opts.outputFormat);\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.assertOpen();\n this.lastExecution = execution;\n this.lastReportMeta = reportMeta;\n this.recorder.record(execution, reportMeta);\n this.mergeReportAttributes(attributes);\n this.writeQueue = this.writeQueue.then(async () => {\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 if (!this.finalization) {\n // Enqueue the final state before sealing the generator to new writes.\n if (this.lastExecution && this.lastReportMeta) {\n this.onExecutionUpdate(this.lastExecution, this.lastReportMeta);\n }\n this.finalization = this.finishPublication();\n }\n return this.finalization;\n }\n\n private async finishPublication(): Promise<string | undefined> {\n // Snapshots freeze independently of the HTML queue, but both must be on\n // disk before their backing executions and diagnostic data are released.\n await Promise.all([this.flush(), ...this.sourcePublications]);\n if (this.initialized) {\n if (this.artifactStore) {\n await this.artifactStore.finalize();\n } else {\n await this.appendAgentReportComment();\n try {\n await compactReportDumps(this.reportPath);\n } catch (error) {\n warnReport(\n `Failed to compact report ${this.reportPath}; keeping the uncompressed report: ${String(error)}`,\n );\n }\n }\n }\n this.recorder.clear();\n this.lastExecution = undefined;\n this.lastReportMeta = undefined;\n this.executionCommentKeyByObject = new WeakMap();\n this.executionLogFileIndexByExecutionKey.clear();\n return this.initialized ? this.reportPath : undefined;\n }\n\n private assertOpen(): void {\n if (this.finalization) throw new Error('The report has been finalized.');\n }\n\n getReportPath(): string | undefined {\n return this.reportPath;\n }\n\n async writeRunnerReport(\n input: RunReportInput,\n scopeId: string,\n meta: ReportMeta,\n ): Promise<string> {\n this.assertOpen();\n this.writeQueue = this.writeQueue.then(async () => {\n // This runs at YAML invocation completion, never on progress ticks. Keep\n // all file I/O asynchronous and publish atomically for an open viewer.\n const reportMeta = this.lastReportMeta ?? meta;\n const executions = this.recorder.executions;\n const agentDump = new ReportActionDump({ ...reportMeta, executions });\n const reportId =\n this.reportAttributes['data-report-id'] ??\n this.reportAttributes['data-group-id'] ??\n this.reportStreamId;\n const dump = buildAgentTestRunReportDump(input, agentDump, {\n reportId,\n scopeId,\n sourcePath: this.reportPath,\n });\n for (const diagnostic of dump.diagnostics ?? [])\n warnReport(diagnostic.message);\n this.enableSourceSnapshots(scopeId);\n const sourceDump = await this.serializeSourceDump();\n const images: string[] = [];\n if (this.screenshotMode === 'inline') {\n for (const ref of sourceDump.images.values()) {\n const bytes = await readFileAsync(\n join(this.sourceDirectory!, ref.path!),\n );\n images.push(\n generateImageScriptTag(\n ref.id,\n `data:${ref.mimeType};base64,${bytes.toString('base64')}`,\n ),\n );\n }\n }\n if (this.artifactStore) {\n for (const execution of executions) {\n await this.writeExecution(\n execution,\n this.wrapAsReportDump(execution, reportMeta),\n );\n }\n if (!executions.length) {\n // An empty execution group still carries setup/collection diagnostics.\n await this.artifactStore.writeExecution(\n 'empty',\n sourceDump.serialized,\n this.getDumpScriptAttributes(),\n );\n if (!this.initialized) {\n const marker = generateReportArtifactScriptTag('report.json');\n await writeFileAsync(\n this.reportPath,\n `${getReportTpl()}${getBaseUrlFixScript()}\\n${marker}`,\n );\n this.initialized = true;\n }\n }\n await this.artifactStore.writeRunner(JSON.stringify(dump));\n if (!this.firstWriteDone) {\n this.firstWriteDone = true;\n this.printReportPath();\n }\n return;\n }\n // Rebuild from the owned dump and image index, never parse this HTML back.\n const renderedDump =\n this.screenshotMode === 'inline'\n ? JSON.stringify(JSON.parse(sourceDump.serialized), (_key, value) => {\n const ref = normalizeStoredImageRef(value);\n return ref\n ? { ...ref, storage: 'inline', path: undefined }\n : value;\n })\n : sourceDump.serialized;\n const contents = `${getReportTpl()}${this.screenshotMode === 'directory' ? getBaseUrlFixScript() : ''}\n${generateDumpScriptTag(renderedDump, this.getDumpScriptAttributes())}\n${images.join('\\n')}\n${generateTestRunReportScriptTag(dump)}\n${generateAgentReportComment(agentDump)}`;\n mkdirSync(dirname(this.reportPath), { recursive: true });\n const temporaryPath = `${this.reportPath}.runner-${uuid()}.tmp`;\n try {\n await writeFileAsync(temporaryPath, contents);\n await renameAsync(temporaryPath, this.reportPath);\n } finally {\n await unlinkAsync(temporaryPath).catch((error) => {\n if (error.code !== 'ENOENT') throw error;\n });\n }\n this.initialized = true;\n if (!this.firstWriteDone) {\n this.firstWriteDone = true;\n this.printReportPath();\n }\n });\n await this.flush();\n return this.reportPath;\n }\n\n enableSourceSnapshots(scopeId = this.reportStreamId): void {\n if (\n !scopeId ||\n scopeId === '.' ||\n scopeId === '..' ||\n /[/\\\\]/.test(scopeId)\n )\n throw new Error('Invalid report scope ID');\n if (this.sourceStore) return;\n this.sourceDirectory = join(\n getMidsceneRunSubDir('test-results'),\n scopeId,\n 'records',\n );\n this.sourceStore = new ReportImageStore({\n mode: 'directory',\n reportPath: join(this.sourceDirectory, 'source.json'),\n screenshotsDir: join(this.sourceDirectory, 'screenshots'),\n });\n }\n\n private async serializeSourceDump(snapshot?: ExecutionRecordSnapshot) {\n this.enableSourceSnapshots();\n const store = this.sourceStore!;\n const captured =\n snapshot ??\n this.recorder.capture(\n undefined,\n this.lastReportMeta ?? {\n sdkVersion: '',\n groupName: 'YAML execution',\n modelBriefs: [],\n },\n );\n for (const screenshot of captured.screenshots)\n await store.persistFileCopy(screenshot);\n const references = new Map<string, ImageUrlRef>();\n for (const url of captured.referenceImageUrls) {\n references.set(url, await store.persistReferenceImage(url));\n }\n const images = new Map<\n string,\n ReturnType<typeof normalizeStoredImageRef> & {}\n >();\n const serialized = JSON.stringify(\n JSON.parse(captured.dump.serializeWithReferenceImages(references)),\n (_key, value) => {\n const ref = normalizeStoredImageRef(value);\n if (!ref) return value;\n const stored = {\n ...ref,\n storage: 'file' as const,\n path: store.getFilePath(ref.id),\n };\n images.set(ref.id, stored);\n return stored;\n },\n );\n return { serialized, images };\n }\n\n async createSourceSnapshot(\n scopeId: string,\n executionIds?: readonly string[],\n ): Promise<WorkflowReportSource & { dumpPath: string }> {\n this.assertOpen();\n if (!scopeId || basename(scopeId) !== scopeId)\n throw new Error('Invalid report scope ID');\n const snapshot = this.recorder.capture(\n executionIds,\n this.lastReportMeta ?? {\n sdkVersion: '',\n groupName: 'YAML execution',\n modelBriefs: [],\n },\n );\n const publication = (async () => {\n const { serialized } = await this.serializeSourceDump(snapshot);\n const dumpPath = join(\n this.sourceDirectory!,\n `${this.reportStreamId}.${scopeId}.${uuid()}.json`,\n );\n mkdirSync(dirname(dumpPath), { recursive: true });\n await writeFileAsync(dumpPath, serialized, { flag: 'wx' });\n return {\n sourcePath: this.reportPath,\n dumpPath,\n collection: {\n status: 'complete' as const,\n capturedAt: snapshot.capturedAt,\n },\n };\n })();\n this.sourcePublications.add(publication);\n try {\n return await publication;\n } finally {\n this.sourcePublications.delete(publication);\n }\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 logReport(\n `Midscene - report file updated: npx serve ${dirname(this.reportPath)}`,\n );\n } else {\n logReport(`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 const referenceImageRefs = await this.writeExecution(execution, singleDump);\n\n if (this.shouldPersistExecutionDump && !this.artifactStore) {\n await this.persistExecutionDumpToFile(\n execution,\n singleDump,\n referenceImageRefs,\n );\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 if (this.artifactStore && !readReportArtifactManifest(this.reportPath)) {\n throw new Error(\n 'Cannot reuse an incompatible directory report. Choose a new reportFileName or regenerate the report.',\n );\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 if (!existsSync(this.executionDumpDir)) return;\n const existingExecutionIndices = readdirSync(this.executionDumpDir)\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 'data-report-kind': 'agent',\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 assets and a dump tag without duplicating mode-specific workflows.\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 writeExecution(\n execution: ExecutionDump,\n singleDump: ReportActionDump,\n ): Promise<Map<string, ImageUrlRef>> {\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 const baseUrlFix =\n this.screenshotMode === 'directory' ? getBaseUrlFixScript() : '';\n const marker = this.artifactStore\n ? `\\n${generateReportArtifactScriptTag('report.json')}`\n : '';\n await writeFileAsync(\n this.reportPath,\n `${getReportTpl()}${baseUrlFix}${marker}`,\n );\n this.initialized = true;\n }\n\n // Append new screenshots (skip already-written ones)\n for (const screenshot of execution.collectScreenshots()) {\n await this.sourceStore?.persistFileCopy(screenshot);\n await this.screenshotStore.persist(screenshot);\n }\n const referenceImageRefs = await this.persistReferenceImages(execution);\n\n // Publish the latest facts using the selected storage format.\n const serialized =\n singleDump.serializeWithReferenceImages(referenceImageRefs);\n if (this.artifactStore) {\n await this.artifactStore.writeExecution(\n this.getExecutionCommentKey(execution),\n serialized,\n this.getDumpScriptAttributes(),\n );\n } else {\n await appendFileAsync(\n this.reportPath,\n `\\n${generateDumpScriptTag(serialized, this.getDumpScriptAttributes())}`,\n );\n }\n return referenceImageRefs;\n }\n\n private async persistReferenceImages(\n execution: ExecutionDump,\n ): Promise<Map<string, ImageUrlRef>> {\n const refs = new Map<string, ImageUrlRef>();\n for (const imageUrl of execution.getReferenceImageUrls()) {\n const ref = await this.screenshotStore.persistReferenceImage(imageUrl);\n refs.set(imageUrl, ref);\n }\n return refs;\n }\n\n private async appendAgentReportComment(): Promise<void> {\n if (\n this.agentCommentWritten ||\n !this.lastReportMeta ||\n this.recorder.executions.length === 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: this.recorder.executions,\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 get executionDumpDir(): string {\n // Inline HTML files share their parent directory. Each needs its own\n // numbered JSON namespace; external-asset reports already have one.\n return this.screenshotMode === 'inline'\n ? join(\n dirname(this.reportPath),\n '.execution-dumps',\n basename(this.reportPath),\n )\n : dirname(this.reportPath);\n }\n\n private async persistExecutionDumpToFile(\n execution: ExecutionDump,\n singleDump: ReportActionDump,\n referenceImageRefs: ReadonlyMap<string, ImageUrlRef>,\n ): Promise<void> {\n const dir = this.executionDumpDir;\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(dir, fileName);\n let serialized = singleDump.serializeWithReferenceImages(\n referenceImageRefs,\n 2,\n );\n if (this.screenshotMode === 'inline') {\n // JSON is two directories below the HTML. Point to the existing file\n // copies so it can be consumed without resolving inline HTML assets.\n serialized = JSON.stringify(\n JSON.parse(serialized),\n (_key, value) => {\n const ref = normalizeStoredImageRef(value);\n return ref\n ? {\n ...ref,\n storage: 'file',\n path: `../../${this.screenshotStore.getFilePath(ref.id).replace(/^\\.\\//, '')}`,\n }\n : value;\n },\n 2,\n );\n }\n await writeFileAsync(filePath, serialized, 'utf-8');\n }\n}\n\nfunction ensureHtmlFileName(reportFileName: string): string {\n return reportFileName.endsWith('.html')\n ? reportFileName\n : `${reportFileName}.html`;\n}\n\nfunction validateReportFileName(\n reportFileName: string,\n outputFormat?: 'single-html' | 'html-and-external-assets',\n): 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 const filenameComponent =\n outputFormat === 'html-and-external-assets'\n ? reportFileName\n : ensureHtmlFileName(reportFileName);\n const filenameBytes = new TextEncoder().encode(filenameComponent).byteLength;\n if (filenameBytes > maxReportFilenameBytes) {\n throw new Error(\n `reportFileName produces a filename component of ${filenameBytes} UTF-8 bytes; maximum is ${maxReportFilenameBytes}`,\n );\n }\n}\n"],"names":["warnReport","getDebug","maxReportFilenameBytes","nullReportGenerator","undefined","assertReportGenerationOptions","opts","Error","ReportGenerator","reportFileName","validateReportFileName","ifInBrowser","reportRootDir","getMidsceneRunSubDir","outputDir","join","reportPath","ensureHtmlFileName","execution","reportMeta","attributes","Promise","compactReportDumps","error","String","WeakMap","input","scopeId","meta","executions","agentDump","ReportActionDump","reportId","dump","buildAgentTestRunReportDump","diagnostic","sourceDump","images","ref","bytes","readFileAsync","generateImageScriptTag","marker","generateReportArtifactScriptTag","writeFileAsync","getReportTpl","getBaseUrlFixScript","JSON","renderedDump","_key","value","normalizeStoredImageRef","contents","generateDumpScriptTag","generateTestRunReportScriptTag","generateAgentReportComment","mkdirSync","dirname","temporaryPath","uuid","renameAsync","unlinkAsync","ReportImageStore","snapshot","store","captured","screenshot","references","Map","url","serialized","stored","executionIds","basename","publication","dumpPath","globalConfigManager","MIDSCENE_REPORT_QUIET","logReport","singleDump","referenceImageRefs","key","Object","existsSync","readReportArtifactManifest","existingExecutionIndices","readdirSync","name","index","Boolean","Number","Math","DATA_SCREENSHOT_MODE_ATTR","dir","baseUrlFix","appendFileAsync","refs","imageUrl","reportDump","existingKey","executionLogKey","fileIndex","fileName","filePath","options","ExecutionRecorder","Set","ReportArtifactStore","id","base64","outputFormat","filenameComponent","filenameBytes","TextEncoder"],"mappings":"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"}