{"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  writeFile as writeFileAsync,\n} from 'node:fs/promises';\nimport { dirname, join } from 'node:path';\nimport { getMidsceneRunSubDir } from '@midscene/shared/common';\nimport {\n  MIDSCENE_REPORT_QUIET,\n  globalConfigManager,\n} from '@midscene/shared/env';\nimport { ifInBrowser, logMsg, uuid } from '@midscene/shared/utils';\nimport {\n  generateDumpScriptTag,\n  generateImageScriptTag,\n  getBaseUrlFixScript,\n} from './dump/html-utils';\nimport { ScreenshotStore } from './dump/screenshot-store';\nimport {\n  type ExecutionDump,\n  ReportActionDump,\n  type ReportAttributes,\n  type ReportMeta,\n} from './types';\nimport { getReportTpl } from './utils';\n\nexport interface IReportGenerator {\n  /**\n   * Write or update a single execution.\n   * Each call appends a new dump script tag. The frontend deduplicates\n   * executions with the same id/name, keeping only the last one.\n   *\n   * @param execution  Current execution's full data\n   * @param reportMeta  Report-level metadata (groupName, sdkVersion, etc.)\n   */\n  onExecutionUpdate(\n    execution: ExecutionDump,\n    reportMeta: ReportMeta,\n    attributes?: ReportAttributes,\n  ): void;\n\n  /**\n   * @deprecated Use onExecutionUpdate instead. Kept for backward compatibility.\n   */\n  onDumpUpdate?(dump: ReportActionDump): void;\n\n  /**\n   * Wait for all queued write operations to complete.\n   */\n  flush(): Promise<void>;\n\n  /**\n   * Finalize the report. Calls flush() internally.\n   */\n  finalize(): Promise<string | undefined>;\n\n  getReportPath(): string | undefined;\n}\n\nexport const nullReportGenerator: IReportGenerator = {\n  onExecutionUpdate: () => {},\n  flush: async () => {},\n  finalize: async () => undefined,\n  getReportPath: () => undefined,\n};\n\nexport function assertReportGenerationOptions(opts: {\n  generateReport?: boolean;\n  persistExecutionDump?: boolean;\n}): void {\n  if (opts.generateReport === false && opts.persistExecutionDump === true) {\n    throw new Error(\n      'persistExecutionDump cannot be true when generateReport is false',\n    );\n  }\n}\n\nexport class ReportGenerator implements IReportGenerator {\n  private reportPath: string;\n  private screenshotMode: 'inline' | 'directory';\n  private shouldPersistExecutionDump: boolean;\n  private autoPrint: boolean;\n  private firstWriteDone = false;\n  private executionLogIndex = 0;\n  private executionLogFileIndexByExecutionKey = new Map<string, number>();\n\n  // Unique identifier for this report stream — used as data-group-id\n  private readonly reportStreamId: string;\n\n  // Tracks screenshots already written to disk (by id) to avoid duplicates\n  private screenshotStore: ScreenshotStore;\n  private initialized = false;\n\n  // Tracks the last execution + groupMeta for re-writing on finalize\n  private lastExecution?: ExecutionDump;\n  private lastReportMeta?: ReportMeta;\n  private reportAttributes: Record<string, string> = {};\n\n  // write queue for serial execution\n  private writeQueue: Promise<void> = Promise.resolve();\n  private destroyed = false;\n\n  constructor(options: {\n    reportPath: string;\n    screenshotMode: 'inline' | 'directory';\n    persistExecutionDump?: boolean;\n    autoPrint?: boolean;\n    reuseExistingReport?: boolean;\n  }) {\n    this.reportPath = options.reportPath;\n    this.screenshotMode = options.screenshotMode;\n    this.shouldPersistExecutionDump = options.persistExecutionDump ?? false;\n    this.autoPrint = options.autoPrint ?? true;\n    this.reportStreamId = uuid();\n    this.screenshotStore = new ScreenshotStore({\n      mode: this.screenshotMode === 'inline' ? 'inline' : 'directory',\n      reportPath: this.reportPath,\n      screenshotsDir: join(dirname(this.reportPath), 'screenshots'),\n      writeInlineImage: async (id, base64) => {\n        await appendFileAsync(\n          this.reportPath,\n          `\\n${generateImageScriptTag(id, base64)}`,\n        );\n      },\n      alsoWriteFileCopy: this.shouldPersistExecutionDump,\n    });\n    if (options.reuseExistingReport) {\n      this.hydrateStateFromExistingReport();\n    }\n  }\n\n  static create(\n    reportFileName: string,\n    opts: {\n      generateReport?: boolean;\n      persistExecutionDump?: boolean;\n      outputFormat?: 'single-html' | 'html-and-external-assets';\n      autoPrintReportMsg?: boolean;\n      reuseExistingReport?: boolean;\n    },\n  ): IReportGenerator {\n    assertReportGenerationOptions(opts);\n    validateReportFileName(reportFileName);\n    if (opts.generateReport === false) return nullReportGenerator;\n\n    // In browser environment, file system is not available\n    if (ifInBrowser) return nullReportGenerator;\n\n    const reportRootDir = getMidsceneRunSubDir('report');\n    const outputDir = join(reportRootDir, reportFileName);\n    const reportPath =\n      opts.outputFormat === 'html-and-external-assets'\n        ? join(outputDir, 'index.html')\n        : join(reportRootDir, ensureHtmlFileName(reportFileName));\n    return new ReportGenerator({\n      reportPath,\n      screenshotMode:\n        opts.outputFormat === 'html-and-external-assets'\n          ? 'directory'\n          : 'inline',\n      persistExecutionDump: opts.persistExecutionDump,\n      autoPrint: opts.autoPrintReportMsg,\n      reuseExistingReport: opts.reuseExistingReport,\n    });\n  }\n\n  onExecutionUpdate(\n    execution: ExecutionDump,\n    reportMeta: ReportMeta,\n    attributes?: ReportAttributes,\n  ): void {\n    this.lastExecution = execution;\n    this.lastReportMeta = reportMeta;\n    this.mergeReportAttributes(attributes);\n    this.writeQueue = this.writeQueue.then(async () => {\n      if (this.destroyed) return;\n      await this.doWriteExecution(execution, reportMeta);\n    });\n  }\n\n  async flush(): Promise<void> {\n    await this.writeQueue;\n  }\n\n  async finalize(): Promise<string | undefined> {\n    // Re-write the last execution to capture any final state changes\n    if (this.lastExecution && this.lastReportMeta) {\n      this.onExecutionUpdate(this.lastExecution, this.lastReportMeta);\n    }\n    await this.flush();\n    this.destroyed = true;\n\n    if (!this.initialized) {\n      // No executions were ever written — no file exists\n      return undefined;\n    }\n\n    return this.reportPath;\n  }\n\n  getReportPath(): string | undefined {\n    return this.reportPath;\n  }\n\n  private printReportPath(): void {\n    if (!this.autoPrint || !this.reportPath) return;\n    if (globalConfigManager.getEnvConfigInBoolean(MIDSCENE_REPORT_QUIET))\n      return;\n\n    if (this.screenshotMode === 'directory') {\n      logMsg(\n        `Midscene - report file updated: npx serve ${dirname(this.reportPath)}`,\n      );\n    } else {\n      logMsg(`Midscene - report file updated: ${this.reportPath}`);\n    }\n  }\n\n  private async doWriteExecution(\n    execution: ExecutionDump,\n    reportMeta: ReportMeta,\n  ): Promise<void> {\n    const singleDump = this.wrapAsReportDump(execution, reportMeta);\n\n    if (this.screenshotMode === 'inline') {\n      await this.writeInlineExecution(execution, singleDump);\n    } else {\n      await this.writeDirectoryExecution(execution, singleDump);\n    }\n\n    if (this.shouldPersistExecutionDump) {\n      await this.persistExecutionDumpToFile(execution, singleDump);\n    }\n\n    if (!this.firstWriteDone) {\n      this.firstWriteDone = true;\n      this.printReportPath();\n    }\n  }\n\n  private mergeReportAttributes(attributes?: ReportAttributes): void {\n    if (!attributes) {\n      return;\n    }\n\n    for (const [key, value] of Object.entries(attributes)) {\n      if (value === undefined || value === null) {\n        continue;\n      }\n      this.reportAttributes[key] = String(value);\n    }\n  }\n\n  private hydrateStateFromExistingReport(): void {\n    if (!existsSync(this.reportPath)) {\n      return;\n    }\n\n    // Reuse existing report file and append new updates instead of rewriting.\n    this.initialized = true;\n\n    if (!this.shouldPersistExecutionDump) {\n      return;\n    }\n\n    const reportDir = dirname(this.reportPath);\n    const existingExecutionIndices = readdirSync(reportDir)\n      .map((name) => /^(\\d+)\\.execution\\.json$/.exec(name)?.[1])\n      .filter((index): index is string => Boolean(index))\n      .map((index) => Number.parseInt(index, 10))\n      .filter((index) => Number.isFinite(index));\n\n    if (existingExecutionIndices.length > 0) {\n      this.executionLogIndex = Math.max(...existingExecutionIndices);\n    }\n  }\n\n  private getDumpScriptAttributes(): Record<string, string> {\n    return {\n      'data-group-id': this.reportStreamId,\n      ...this.reportAttributes,\n    };\n  }\n\n  /**\n   * Wrap an ExecutionDump + ReportMeta into a single-execution ReportActionDump.\n   */\n  private wrapAsReportDump(\n    execution: ExecutionDump,\n    reportMeta: ReportMeta,\n  ): ReportActionDump {\n    return new ReportActionDump({\n      sdkVersion: reportMeta.sdkVersion,\n      groupName: reportMeta.groupName,\n      groupDescription: reportMeta.groupDescription,\n      modelBriefs: reportMeta.modelBriefs,\n      deviceType: reportMeta.deviceType,\n      executions: [execution],\n    });\n  }\n\n  /**\n   * Append-only inline mode: write new screenshots and a dump tag on every call.\n   * The frontend deduplicates executions with the same id/name (keeps last).\n   * Duplicate dump JSON is acceptable; only screenshots are deduplicated.\n   *\n   * All writes go through `fs/promises` so they run on libuv's thread pool\n   * rather than blocking the Node event loop. A long agent run previously\n   * appended multi-MB dumps (screenshots + serialized tasks) per progress\n   * tick on the main thread, starving IPC and UI for >10s per stall.\n   */\n  private async writeInlineExecution(\n    execution: ExecutionDump,\n    singleDump: ReportActionDump,\n  ): Promise<void> {\n    const dir = dirname(this.reportPath);\n    if (!existsSync(dir)) {\n      mkdirSync(dir, { recursive: true });\n    }\n\n    // Initialize: write HTML template once\n    if (!this.initialized) {\n      await writeFileAsync(this.reportPath, getReportTpl());\n      this.initialized = true;\n    }\n\n    // Append new screenshots (skip already-written ones)\n    for (const screenshot of execution.collectScreenshots()) {\n      await this.screenshotStore.persist(screenshot);\n    }\n\n    // Append dump tag (always — frontend keeps only last per execution id)\n    const serialized = singleDump.serialize();\n    await appendFileAsync(\n      this.reportPath,\n      `\\n${generateDumpScriptTag(serialized, this.getDumpScriptAttributes())}`,\n    );\n  }\n\n  private async writeDirectoryExecution(\n    execution: ExecutionDump,\n    singleDump: ReportActionDump,\n  ): Promise<void> {\n    const dir = dirname(this.reportPath);\n    if (!existsSync(dir)) {\n      mkdirSync(dir, { recursive: true });\n    }\n\n    for (const screenshot of execution.collectScreenshots()) {\n      await this.screenshotStore.persist(screenshot);\n    }\n\n    // 2. Append dump tag (always — frontend keeps only last per execution id)\n    const serialized = singleDump.serialize();\n\n    if (!this.initialized) {\n      await writeFileAsync(\n        this.reportPath,\n        `${getReportTpl()}${getBaseUrlFixScript()}`,\n      );\n      this.initialized = true;\n    }\n\n    await appendFileAsync(\n      this.reportPath,\n      `\\n${generateDumpScriptTag(serialized, this.getDumpScriptAttributes())}`,\n    );\n  }\n\n  private getExecutionLogKey(execution: ExecutionDump): string {\n    if (!execution.id) {\n      throw new Error(\n        'ReportGenerator: execution.id is required for persisting execution dumps',\n      );\n    }\n    return `id:${execution.id}`;\n  }\n\n  private async persistExecutionDumpToFile(\n    execution: ExecutionDump,\n    singleDump: ReportActionDump,\n  ): Promise<void> {\n    const dir = dirname(this.reportPath);\n    if (!existsSync(dir)) {\n      mkdirSync(dir, { recursive: true });\n    }\n\n    const executionLogKey = this.getExecutionLogKey(execution);\n    let fileIndex =\n      this.executionLogFileIndexByExecutionKey.get(executionLogKey);\n    if (!fileIndex) {\n      this.executionLogIndex += 1;\n      fileIndex = this.executionLogIndex;\n      this.executionLogFileIndexByExecutionKey.set(executionLogKey, fileIndex);\n    }\n\n    const fileName = `${fileIndex}.execution.json`;\n    const filePath = join(dirname(this.reportPath), fileName);\n    await writeFileAsync(filePath, singleDump.serialize(2), 'utf-8');\n  }\n}\n\nfunction ensureHtmlFileName(reportFileName: string): string {\n  return reportFileName.endsWith('.html')\n    ? reportFileName\n    : `${reportFileName}.html`;\n}\n\nfunction validateReportFileName(reportFileName: string): void {\n  if (!reportFileName?.trim()) {\n    throw new Error('reportFileName must be a non-empty string');\n  }\n\n  if (/[\\\\/]/.test(reportFileName)) {\n    throw new Error(\n      'reportFileName must not contain path separators (`/` or `\\\\\\\\`)',\n    );\n  }\n\n  if (/[:*?\"<>|]/.test(reportFileName)) {\n    throw new Error(\n      'reportFileName contains illegal filename characters: : * ? \" < > |',\n    );\n  }\n}\n"],"names":["nullReportGenerator","undefined","assertReportGenerationOptions","opts","Error","ReportGenerator","reportFileName","validateReportFileName","ifInBrowser","reportRootDir","getMidsceneRunSubDir","outputDir","join","reportPath","ensureHtmlFileName","execution","reportMeta","attributes","globalConfigManager","MIDSCENE_REPORT_QUIET","logMsg","dirname","singleDump","key","value","Object","String","existsSync","reportDir","existingExecutionIndices","readdirSync","name","index","Boolean","Number","Math","ReportActionDump","dir","mkdirSync","writeFileAsync","getReportTpl","screenshot","serialized","appendFileAsync","generateDumpScriptTag","getBaseUrlFixScript","executionLogKey","fileIndex","fileName","filePath","options","Map","Promise","uuid","ScreenshotStore","id","base64","generateImageScriptTag"],"mappings":"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"}