@midscene/core 1.12.0 → 1.12.1-beta-20260824081858.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/es/agent/utils.mjs +1 -1
- package/dist/es/dump/html-utils.mjs +20 -2
- package/dist/es/dump/html-utils.mjs.map +1 -1
- package/dist/es/dump/index.mjs +2 -1
- package/dist/es/dump/report-action-dump.mjs +53 -15
- package/dist/es/dump/report-action-dump.mjs.map +1 -1
- package/dist/es/dump/screenshot-restoration.mjs +3 -1
- package/dist/es/dump/screenshot-restoration.mjs.map +1 -1
- package/dist/es/dump/screenshot-store.mjs +132 -27
- package/dist/es/dump/screenshot-store.mjs.map +1 -1
- package/dist/es/index.mjs +2 -2
- package/dist/es/index.mjs.map +1 -1
- package/dist/es/report-generator.mjs +22 -10
- package/dist/es/report-generator.mjs.map +1 -1
- package/dist/es/report-html-template.mjs +1 -1
- package/dist/es/report.mjs +9 -13
- package/dist/es/report.mjs.map +1 -1
- package/dist/es/types.mjs.map +1 -1
- package/dist/es/utils.mjs +1 -1
- package/dist/lib/agent/utils.js +1 -1
- package/dist/lib/dump/html-utils.js +23 -2
- package/dist/lib/dump/html-utils.js.map +1 -1
- package/dist/lib/dump/index.js +8 -1
- package/dist/lib/dump/report-action-dump.js +52 -14
- package/dist/lib/dump/report-action-dump.js.map +1 -1
- package/dist/lib/dump/screenshot-restoration.js +2 -0
- package/dist/lib/dump/screenshot-restoration.js.map +1 -1
- package/dist/lib/dump/screenshot-store.js +153 -27
- package/dist/lib/dump/screenshot-store.js.map +1 -1
- package/dist/lib/index.js +4 -1
- package/dist/lib/index.js.map +1 -1
- package/dist/lib/report-generator.js +21 -9
- package/dist/lib/report-generator.js.map +1 -1
- package/dist/lib/report-html-template.js +1 -1
- package/dist/lib/report.js +8 -12
- package/dist/lib/report.js.map +1 -1
- package/dist/lib/types.js.map +1 -1
- package/dist/lib/utils.js +1 -1
- package/dist/types/device/device-options.d.ts +4 -0
- package/dist/types/dump/html-utils.d.ts +4 -1
- package/dist/types/dump/index.d.ts +2 -0
- package/dist/types/dump/report-action-dump.d.ts +19 -0
- package/dist/types/dump/screenshot-restoration.d.ts +5 -5
- package/dist/types/dump/screenshot-store.d.ts +40 -6
- package/dist/types/index.d.ts +1 -1
- package/dist/types/report-generator.d.ts +1 -0
- package/dist/types/types.d.ts +1 -1
- package/package.json +2 -2
package/dist/es/agent/utils.mjs
CHANGED
|
@@ -154,7 +154,7 @@ async function matchElementFromCache(context, cacheEntry, cachePrompt, cacheable
|
|
|
154
154
|
return;
|
|
155
155
|
}
|
|
156
156
|
}
|
|
157
|
-
const getMidsceneVersion = ()=>"1.12.0";
|
|
157
|
+
const getMidsceneVersion = ()=>"1.12.1-beta-20260824081858.0";
|
|
158
158
|
const parsePrompt = (prompt)=>{
|
|
159
159
|
if ('string' == typeof prompt) return {
|
|
160
160
|
textPrompt: prompt,
|
|
@@ -149,15 +149,33 @@ function extractImageByIdSync(htmlPath, imageId) {
|
|
|
149
149
|
});
|
|
150
150
|
return result;
|
|
151
151
|
}
|
|
152
|
-
function streamImageScriptsToFile(srcFilePath, destFilePath) {
|
|
152
|
+
function streamImageScriptsToFile(srcFilePath, destFilePath, writtenImageIds = new Set()) {
|
|
153
153
|
const { appendFileSync } = __webpack_require__("node:fs");
|
|
154
154
|
const openTag = '<script type="midscene-image"';
|
|
155
155
|
const closeTag = htmlScriptCloseTag();
|
|
156
156
|
streamScanTags(srcFilePath, openTag, closeTag, (content)=>{
|
|
157
|
+
const imageId = parseImageScriptId(content);
|
|
158
|
+
if (!imageId || writtenImageIds.has(imageId)) return false;
|
|
157
159
|
appendFileSync(destFilePath, `${openTag}${content}${closeTag}\n`);
|
|
160
|
+
writtenImageIds.add(imageId);
|
|
158
161
|
return false;
|
|
159
162
|
});
|
|
160
163
|
}
|
|
164
|
+
function parseImageScriptId(contentAfterType) {
|
|
165
|
+
const match = /^ data-id="([a-z0-9_-]{1,128})">/i.exec(contentAfterType);
|
|
166
|
+
return match?.[1] ?? null;
|
|
167
|
+
}
|
|
168
|
+
function collectImageScriptIdsSync(filePath) {
|
|
169
|
+
const imageIds = new Set();
|
|
170
|
+
const openTag = '<script type="midscene-image"';
|
|
171
|
+
const closeTag = htmlScriptCloseTag();
|
|
172
|
+
streamScanTags(filePath, openTag, closeTag, (content)=>{
|
|
173
|
+
const imageId = parseImageScriptId(content);
|
|
174
|
+
if (imageId) imageIds.add(imageId);
|
|
175
|
+
return false;
|
|
176
|
+
});
|
|
177
|
+
return imageIds;
|
|
178
|
+
}
|
|
161
179
|
function extractLastDumpScriptSync(filePath) {
|
|
162
180
|
const openTagPrefix = '<script type="midscene_web_dump"';
|
|
163
181
|
const closeTag = htmlScriptCloseTag();
|
|
@@ -355,6 +373,6 @@ function generateDumpScriptTag(json, attributes) {
|
|
|
355
373
|
if (attributes && Object.keys(attributes).length > 0) attrString = ' ' + Object.entries(attributes).map(([k, v])=>k + '="' + encodeURIComponent(v) + '"').join(' ');
|
|
356
374
|
return '<script type="midscene_web_dump"' + attrString + '>' + escapeContent(json) + closeTag;
|
|
357
375
|
}
|
|
358
|
-
export { DATA_SCREENSHOT_MODE_ATTR, STREAMING_CHUNK_SIZE, escapeContent, extractAllDumpScriptsSync, extractImageByIdSync, extractLastDumpScriptSync, generateAgentReportComment, generateDumpScriptTag, generateImageScriptTag, getBaseUrlFixScript, parseDumpScript, parseDumpScriptAttributes, parseImageScripts, streamDumpScriptsSync, streamImageScriptsToFile, streamScanTags, unescapeContent };
|
|
376
|
+
export { DATA_SCREENSHOT_MODE_ATTR, STREAMING_CHUNK_SIZE, collectImageScriptIdsSync, escapeContent, extractAllDumpScriptsSync, extractImageByIdSync, extractLastDumpScriptSync, generateAgentReportComment, generateDumpScriptTag, generateImageScriptTag, getBaseUrlFixScript, parseDumpScript, parseDumpScriptAttributes, parseImageScripts, streamDumpScriptsSync, streamImageScriptsToFile, streamScanTags, unescapeContent };
|
|
359
377
|
|
|
360
378
|
//# sourceMappingURL=html-utils.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dump/html-utils.mjs","sources":["../../../src/dump/html-utils.ts"],"sourcesContent":["import { closeSync, openSync, readSync, statSync } from 'node:fs';\nimport { antiEscapeScriptTag, escapeScriptTag } from '@midscene/shared/utils';\nimport type { AIUsageInfo, IReportActionDump, ModelBrief } from '../types';\n\nexport const escapeContent = escapeScriptTag;\nexport const unescapeContent = antiEscapeScriptTag;\n\nfunction cleanHtmlCommentValue(value: unknown): string {\n return String(value ?? 'N/A')\n .replace(/\\0/g, '')\n .replace(/--/g, '- -');\n}\n\nfunction formatModelBriefForAgent(brief: ModelBrief): string {\n const intent = brief.intent || 'default';\n const model = brief.name || 'unknown';\n const description = brief.modelDescription\n ? ` (${brief.modelDescription})`\n : '';\n return `${intent}: ${model}${description}`;\n}\n\nfunction hasUsageModelInfo(usage?: AIUsageInfo): usage is AIUsageInfo {\n return Boolean(\n usage &&\n (usage.model_name ||\n usage.response_model_name ||\n usage.model_description ||\n usage.intent),\n );\n}\n\nfunction formatUsageModelForAgent(source: string, usage: AIUsageInfo): string {\n const intent = usage.intent || source;\n const model = usage.model_name || usage.response_model_name || 'unknown';\n const description = usage.model_description\n ? ` (${usage.model_description})`\n : '';\n return `${intent}: ${model}${description}`;\n}\n\nfunction collectUsageModelsForAgent(report: IReportActionDump): string {\n const models = new Map<string, string>();\n const executions = Array.isArray(report.executions) ? report.executions : [];\n for (const execution of executions) {\n const tasks = Array.isArray(execution.tasks) ? execution.tasks : [];\n for (const task of tasks) {\n const taskWithUsage = task as {\n usage?: AIUsageInfo;\n searchAreaUsage?: AIUsageInfo;\n };\n const usages: Array<[string, AIUsageInfo | undefined]> = [\n ['main', taskWithUsage.usage],\n ['searchArea', taskWithUsage.searchAreaUsage],\n ];\n\n for (const [source, usage] of usages) {\n if (!hasUsageModelInfo(usage)) {\n continue;\n }\n const formatted = formatUsageModelForAgent(source, usage);\n models.set(formatted, formatted);\n }\n }\n }\n\n return Array.from(models.values()).join('; ');\n}\n\nexport function generateAgentReportComment(report: IReportActionDump): string {\n const executions = Array.isArray(report.executions) ? report.executions : [];\n const taskCount = executions.reduce(\n (sum, execution) =>\n sum + (Array.isArray(execution.tasks) ? execution.tasks.length : 0),\n 0,\n );\n const modelBriefs = report.modelBriefs?.length\n ? report.modelBriefs.map(formatModelBriefForAgent).join('; ')\n : collectUsageModelsForAgent(report) || 'No model metadata recorded';\n\n const lines = [\n 'For Agent Analysis:',\n `Report: ${cleanHtmlCommentValue(report.groupName)}`,\n `SDK: ${cleanHtmlCommentValue(report.sdkVersion)}`,\n `Device: ${cleanHtmlCommentValue(report.deviceType)}`,\n `Executions: ${executions.length}; Tasks: ${taskCount}`,\n `Models: ${cleanHtmlCommentValue(modelBriefs)}`,\n 'Structured report JSON is stored in script[type=\"midscene_web_dump\"] tags near this comment.',\n 'Screenshots are stored as script[type=\"midscene-image\"] tags or files referenced by screenshot refs.',\n 'For AI analysis, inspect each execution task for type, status, timing, param, output, usage, searchAreaUsage, recorder, and screenshot refs.',\n 'Use the Markdown export when available; it contains the same report context plus image links for agent review.',\n ];\n\n return `\\n<!--\\n${lines.join('\\n')}\\n-->\\n`;\n}\n\nfunction htmlScriptCloseTag(): string {\n // biome-ignore lint/style/useTemplate: keep this token runtime-built for inline report bundles\n return String.fromCharCode(60) + '/script>';\n}\n\n/** Chunk size for streaming file operations (64KB) */\nexport const STREAMING_CHUNK_SIZE = 64 * 1024;\n\n/**\n * Callback for processing matched tags during streaming.\n * @param content - The content between open and close tags\n * @returns true to stop scanning, false to continue\n */\ntype TagMatchCallback = (content: string) => boolean;\n\n/**\n * Stream through a file and find tags matching the pattern.\n * Memory usage: O(chunk_size + tag_size), not O(file_size).\n *\n * @param filePath - Absolute path to the file\n * @param openTag - Opening tag to search for\n * @param closeTag - Closing tag\n * @param onMatch - Callback for each matched tag content\n */\nexport function streamScanTags(\n filePath: string,\n openTag: string,\n closeTag: string,\n onMatch: TagMatchCallback,\n): void {\n const fd = openSync(filePath, 'r');\n const fileSize = statSync(filePath).size;\n const buffer = Buffer.alloc(STREAMING_CHUNK_SIZE);\n\n let position = 0;\n let leftover = '';\n let capturing = false;\n let currentContent = '';\n\n try {\n while (position < fileSize) {\n const bytesRead = readSync(fd, buffer, 0, STREAMING_CHUNK_SIZE, position);\n const chunk = leftover + buffer.toString('utf-8', 0, bytesRead);\n position += bytesRead;\n\n let searchStart = 0;\n\n while (searchStart < chunk.length) {\n if (!capturing) {\n const startIdx = chunk.indexOf(openTag, searchStart);\n if (startIdx !== -1) {\n capturing = true;\n currentContent = chunk.slice(startIdx + openTag.length);\n const endIdx = currentContent.indexOf(closeTag);\n if (endIdx !== -1) {\n const shouldStop = onMatch(currentContent.slice(0, endIdx));\n if (shouldStop) return;\n capturing = false;\n currentContent = '';\n searchStart =\n startIdx + openTag.length + endIdx + closeTag.length;\n } else {\n leftover = currentContent.slice(-closeTag.length);\n currentContent = currentContent.slice(0, -closeTag.length);\n break;\n }\n } else {\n leftover = chunk.slice(-openTag.length);\n break;\n }\n } else {\n const endIdx = chunk.indexOf(closeTag, searchStart);\n if (endIdx !== -1) {\n currentContent += chunk.slice(searchStart, endIdx);\n const shouldStop = onMatch(currentContent);\n if (shouldStop) return;\n capturing = false;\n currentContent = '';\n searchStart = endIdx + closeTag.length;\n } else {\n currentContent += chunk.slice(searchStart, -closeTag.length);\n leftover = chunk.slice(-closeTag.length);\n break;\n }\n }\n }\n }\n } finally {\n closeSync(fd);\n }\n}\n\n/**\n * Synchronously extract a specific image's base64 data from an HTML file by its id.\n * Uses streaming to avoid loading the entire file into memory.\n *\n * @param htmlPath - Absolute path to the HTML file\n * @param imageId - The id of the image to extract\n * @returns The base64 data string, or null if not found\n */\nexport function extractImageByIdSync(\n htmlPath: string,\n imageId: string,\n): string | null {\n const targetTag = `<script type=\"midscene-image\" data-id=\"${imageId}\">`;\n const closeTag = htmlScriptCloseTag();\n\n let result: string | null = null;\n\n streamScanTags(htmlPath, targetTag, closeTag, (content) => {\n result = unescapeContent(content);\n return true; // Stop after first match\n });\n\n return result;\n}\n\n/**\n * Stream image script tags from source file directly to output file.\n * Memory usage: O(single_image_size), not O(all_images_size).\n *\n * @param srcFilePath - Source HTML file path\n * @param destFilePath - Destination file path to append to\n */\nexport function streamImageScriptsToFile(\n srcFilePath: string,\n destFilePath: string,\n): void {\n const { appendFileSync } = require('node:fs');\n const openTag = '<script type=\"midscene-image\"';\n const closeTag = htmlScriptCloseTag();\n\n streamScanTags(srcFilePath, openTag, closeTag, (content) => {\n // Write complete tag immediately to destination, don't accumulate\n appendFileSync(destFilePath, `${openTag}${content}${closeTag}\\n`);\n return false; // Continue scanning for more tags\n });\n}\n\n/**\n * Extract the LAST dump script content from HTML file using streaming.\n * Memory usage: O(dump_size), not O(file_size).\n *\n * @param filePath - Absolute path to the HTML file\n * @returns The dump script content (trimmed), or empty string if not found\n */\nexport function extractLastDumpScriptSync(filePath: string): string {\n const openTagPrefix = '<script type=\"midscene_web_dump\"';\n const closeTag = htmlScriptCloseTag();\n\n let lastContent = '';\n\n // Custom streaming to handle the special case where open tag has variable attributes\n const fd = openSync(filePath, 'r');\n const fileSize = statSync(filePath).size;\n const buffer = Buffer.alloc(STREAMING_CHUNK_SIZE);\n\n let position = 0;\n let leftover = '';\n let capturing = false;\n let currentContent = '';\n\n try {\n while (position < fileSize) {\n const bytesRead = readSync(fd, buffer, 0, STREAMING_CHUNK_SIZE, position);\n const chunk = leftover + buffer.toString('utf-8', 0, bytesRead);\n position += bytesRead;\n\n let searchStart = 0;\n\n while (searchStart < chunk.length) {\n if (!capturing) {\n const startIdx = chunk.indexOf(openTagPrefix, searchStart);\n if (startIdx !== -1) {\n // Find the end of the opening tag (the '>' character)\n const tagEndIdx = chunk.indexOf('>', startIdx);\n if (tagEndIdx !== -1) {\n capturing = true;\n currentContent = chunk.slice(tagEndIdx + 1);\n const endIdx = currentContent.indexOf(closeTag);\n if (endIdx !== -1) {\n lastContent = currentContent.slice(0, endIdx).trim();\n capturing = false;\n currentContent = '';\n searchStart = tagEndIdx + 1 + endIdx + closeTag.length;\n } else {\n leftover = currentContent.slice(-closeTag.length);\n currentContent = currentContent.slice(0, -closeTag.length);\n break;\n }\n } else {\n leftover = chunk.slice(startIdx);\n break;\n }\n } else {\n leftover = chunk.slice(-openTagPrefix.length);\n break;\n }\n } else {\n const endIdx = chunk.indexOf(closeTag, searchStart);\n if (endIdx !== -1) {\n currentContent += chunk.slice(searchStart, endIdx);\n lastContent = currentContent.trim();\n capturing = false;\n currentContent = '';\n searchStart = endIdx + closeTag.length;\n } else {\n currentContent += chunk.slice(searchStart, -closeTag.length);\n leftover = chunk.slice(-closeTag.length);\n break;\n }\n }\n }\n }\n } finally {\n closeSync(fd);\n }\n\n return lastContent;\n}\n\n/**\n * Extract ALL dump script contents from an HTML file using streaming.\n * Each entry includes the full opening tag (for attribute extraction) and the content.\n *\n * @param filePath - Absolute path to the HTML file\n * @returns Array of { openTag, content } for each dump script found\n */\nexport function extractAllDumpScriptsSync(\n filePath: string,\n): { openTag: string; content: string }[] {\n const results: { openTag: string; content: string }[] = [];\n streamDumpScriptsSync(filePath, (dumpScript) => {\n results.push(dumpScript);\n return false;\n });\n return results;\n}\n\n/**\n * Stream ALL dump scripts from an HTML file.\n * Calls onMatch for each dump script and keeps memory bounded to a single\n * dump script payload instead of accumulating every dump in memory.\n *\n * @param filePath - Absolute path to the HTML file\n * @param onMatch - Callback for each dump script; return true to stop early\n */\nexport function streamDumpScriptsSync(\n filePath: string,\n onMatch: (dumpScript: { openTag: string; content: string }) => boolean,\n): void {\n const openTagPrefix = '<script type=\"midscene_web_dump\"';\n const closeTag = htmlScriptCloseTag();\n\n const fd = openSync(filePath, 'r');\n const fileSize = statSync(filePath).size;\n const buffer = Buffer.alloc(STREAMING_CHUNK_SIZE);\n\n let position = 0;\n let leftover = '';\n let capturing = false;\n let currentContent = '';\n let currentOpenTag = '';\n\n try {\n while (position < fileSize) {\n const bytesRead = readSync(fd, buffer, 0, STREAMING_CHUNK_SIZE, position);\n const chunk = leftover + buffer.toString('utf-8', 0, bytesRead);\n position += bytesRead;\n\n let searchStart = 0;\n\n while (searchStart < chunk.length) {\n if (!capturing) {\n const startIdx = chunk.indexOf(openTagPrefix, searchStart);\n if (startIdx !== -1) {\n const tagEndIdx = chunk.indexOf('>', startIdx);\n if (tagEndIdx !== -1) {\n capturing = true;\n currentOpenTag = chunk.slice(startIdx, tagEndIdx + 1);\n currentContent = chunk.slice(tagEndIdx + 1);\n const endIdx = currentContent.indexOf(closeTag);\n if (endIdx !== -1) {\n const shouldStop = onMatch({\n openTag: currentOpenTag,\n content: currentContent.slice(0, endIdx).trim(),\n });\n if (shouldStop) return;\n capturing = false;\n currentContent = '';\n currentOpenTag = '';\n searchStart = tagEndIdx + 1 + endIdx + closeTag.length;\n } else {\n leftover = currentContent.slice(-closeTag.length);\n currentContent = currentContent.slice(0, -closeTag.length);\n break;\n }\n } else {\n leftover = chunk.slice(startIdx);\n break;\n }\n } else {\n leftover = chunk.slice(-openTagPrefix.length);\n break;\n }\n } else {\n const endIdx = chunk.indexOf(closeTag, searchStart);\n if (endIdx !== -1) {\n currentContent += chunk.slice(searchStart, endIdx);\n const shouldStop = onMatch({\n openTag: currentOpenTag,\n content: currentContent.trim(),\n });\n if (shouldStop) return;\n capturing = false;\n currentContent = '';\n currentOpenTag = '';\n searchStart = endIdx + closeTag.length;\n } else {\n currentContent += chunk.slice(searchStart, -closeTag.length);\n leftover = chunk.slice(-closeTag.length);\n break;\n }\n }\n }\n }\n } finally {\n closeSync(fd);\n }\n}\n\nexport function parseImageScripts(html: string): Record<string, string> {\n const imageMap: Record<string, string> = {};\n const regex =\n /<script type=\"midscene-image\" data-id=\"([^\"]+)\">([\\s\\S]*?)<\\/script>/g;\n\n for (const match of html.matchAll(regex)) {\n const [, id, content] = match;\n imageMap[id] = unescapeContent(content);\n }\n\n return imageMap;\n}\n\nexport function parseDumpScript(html: string): string {\n // Use string search instead of regex to avoid ReDoS vulnerability\n // Find the LAST dump script tag (template may contain similar patterns in bundled JS)\n const scriptOpenTag = '<script type=\"midscene_web_dump\"';\n const closeTag = htmlScriptCloseTag();\n\n // Find the last occurrence of the opening tag\n const lastOpenIndex = html.lastIndexOf(scriptOpenTag);\n if (lastOpenIndex === -1) {\n throw new Error('No dump script found in HTML');\n }\n\n // Find the end of the opening tag (the '>' character)\n const tagEndIndex = html.indexOf('>', lastOpenIndex);\n if (tagEndIndex === -1) {\n throw new Error('No dump script found in HTML');\n }\n\n // Find the closing tag after the opening tag\n const closeIndex = html.indexOf(closeTag, tagEndIndex);\n if (closeIndex === -1) {\n throw new Error('No dump script found in HTML');\n }\n\n // Extract content between opening and closing tags\n const content = html.substring(tagEndIndex + 1, closeIndex);\n return unescapeContent(content);\n}\n\nexport function parseDumpScriptAttributes(\n html: string,\n): Record<string, string> {\n const regex = /<script type=\"midscene_web_dump\"([^>]*)>/;\n const match = regex.exec(html);\n\n if (!match) {\n return {};\n }\n\n const attrString = match[1];\n const attributes: Record<string, string> = {};\n const attrRegex = /(\\w+)=\"([^\"]*)\"/g;\n\n for (const attrMatch of attrString.matchAll(attrRegex)) {\n const [, key, value] = attrMatch;\n if (key !== 'type') {\n attributes[key] = decodeURIComponent(value);\n }\n }\n\n return attributes;\n}\n\nexport function generateImageScriptTag(id: string, data: string): string {\n // Do not use template string here, will cause bundle error with <script\n const closeTag = htmlScriptCloseTag();\n return (\n // biome-ignore lint/style/useTemplate: <explanation>\n '<script type=\"midscene-image\" data-id=\"' +\n id +\n '\">' +\n escapeContent(data) +\n closeTag\n );\n}\n\n/**\n * Inline script that fixes relative URL resolution for directory-mode reports.\n *\n * Problem: when a static server (e.g. `npx serve`) serves `name/index.html`\n * at URL `/name` (without trailing slash), relative paths like\n * `./screenshots/xxx.png` resolve to `/screenshots/xxx.png` instead of\n * `/name/screenshots/xxx.png`.\n *\n * Fix: dynamically insert a <base> tag so relative URLs resolve correctly.\n */\n// Do not use template string here, will cause bundle error with <script\n//\n// The closing script tag is built at runtime so bundlers cannot inline the\n// token that would prematurely close the report template's inline app bundle.\n//\n// Do NOT replace this with a string constant, hex escape (\\x3c), or literal\n// string concatenation. Bundlers may optimise those forms back to the unsafe\n// token.\nlet _baseUrlFixScript: string;\nexport function getBaseUrlFixScript(): string {\n if (!_baseUrlFixScript) {\n const close = htmlScriptCloseTag();\n _baseUrlFixScript =\n // biome-ignore lint/style/useTemplate: see above\n '\\n<script>(function(){' +\n 'var p=window.location.pathname;' +\n 'if(p.endsWith(\"/\")||/\\\\.\\\\w+$/.test(p))return;' +\n 'var b=document.createElement(\"base\");' +\n 'b.href=p+\"/\";' +\n 'document.head.insertBefore(b,document.head.firstChild)' +\n '})()' +\n close +\n '\\n';\n }\n return _baseUrlFixScript;\n}\n\n/**\n * Dump-script attribute that records how the report file stores screenshots.\n * Written by the report generator and the merger, read back when deciding\n * whether a report is in directory mode. Kept here, next to dump-tag\n * generation, so the writer and reader share one source of truth.\n */\nexport const DATA_SCREENSHOT_MODE_ATTR = 'data-screenshot-mode';\n\nexport function generateDumpScriptTag(\n json: string,\n attributes?: Record<string, string | number | boolean>,\n): string {\n const closeTag = htmlScriptCloseTag();\n let attrString = '';\n if (attributes && Object.keys(attributes).length > 0) {\n // Do not use template string here, will cause bundle error with <script\n attrString =\n // biome-ignore lint/style/useTemplate: <explanation>\n ' ' +\n Object.entries(attributes)\n // biome-ignore lint/style/useTemplate: <explanation>\n .map(([k, v]) => k + '=\"' + encodeURIComponent(v) + '\"')\n .join(' ');\n }\n\n // Do not use template string here, will cause bundle error with <script\n return (\n // biome-ignore lint/style/useTemplate: <explanation>\n '<script type=\"midscene_web_dump\"' +\n attrString +\n '>' +\n escapeContent(json) +\n closeTag\n );\n}\n"],"names":["escapeContent","escapeScriptTag","unescapeContent","antiEscapeScriptTag","cleanHtmlCommentValue","value","String","formatModelBriefForAgent","brief","intent","model","description","hasUsageModelInfo","usage","Boolean","formatUsageModelForAgent","source","collectUsageModelsForAgent","report","models","Map","executions","Array","execution","tasks","task","taskWithUsage","usages","formatted","generateAgentReportComment","taskCount","sum","modelBriefs","lines","htmlScriptCloseTag","STREAMING_CHUNK_SIZE","streamScanTags","filePath","openTag","closeTag","onMatch","fd","openSync","fileSize","statSync","buffer","Buffer","position","leftover","capturing","currentContent","bytesRead","readSync","chunk","searchStart","endIdx","shouldStop","startIdx","closeSync","extractImageByIdSync","htmlPath","imageId","targetTag","result","content","streamImageScriptsToFile","srcFilePath","destFilePath","appendFileSync","require","extractLastDumpScriptSync","openTagPrefix","lastContent","tagEndIdx","extractAllDumpScriptsSync","results","streamDumpScriptsSync","dumpScript","currentOpenTag","parseImageScripts","html","imageMap","regex","match","id","parseDumpScript","scriptOpenTag","lastOpenIndex","Error","tagEndIndex","closeIndex","parseDumpScriptAttributes","attrString","attributes","attrRegex","attrMatch","key","decodeURIComponent","generateImageScriptTag","data","_baseUrlFixScript","getBaseUrlFixScript","close","DATA_SCREENSHOT_MODE_ATTR","generateDumpScriptTag","json","Object","k","v","encodeURIComponent"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"dump/html-utils.mjs","sources":["../../../src/dump/html-utils.ts"],"sourcesContent":["import { closeSync, openSync, readSync, statSync } from 'node:fs';\nimport { antiEscapeScriptTag, escapeScriptTag } from '@midscene/shared/utils';\nimport type { AIUsageInfo, IReportActionDump, ModelBrief } from '../types';\n\nexport const escapeContent = escapeScriptTag;\nexport const unescapeContent = antiEscapeScriptTag;\n\nfunction cleanHtmlCommentValue(value: unknown): string {\n return String(value ?? 'N/A')\n .replace(/\\0/g, '')\n .replace(/--/g, '- -');\n}\n\nfunction formatModelBriefForAgent(brief: ModelBrief): string {\n const intent = brief.intent || 'default';\n const model = brief.name || 'unknown';\n const description = brief.modelDescription\n ? ` (${brief.modelDescription})`\n : '';\n return `${intent}: ${model}${description}`;\n}\n\nfunction hasUsageModelInfo(usage?: AIUsageInfo): usage is AIUsageInfo {\n return Boolean(\n usage &&\n (usage.model_name ||\n usage.response_model_name ||\n usage.model_description ||\n usage.intent),\n );\n}\n\nfunction formatUsageModelForAgent(source: string, usage: AIUsageInfo): string {\n const intent = usage.intent || source;\n const model = usage.model_name || usage.response_model_name || 'unknown';\n const description = usage.model_description\n ? ` (${usage.model_description})`\n : '';\n return `${intent}: ${model}${description}`;\n}\n\nfunction collectUsageModelsForAgent(report: IReportActionDump): string {\n const models = new Map<string, string>();\n const executions = Array.isArray(report.executions) ? report.executions : [];\n for (const execution of executions) {\n const tasks = Array.isArray(execution.tasks) ? execution.tasks : [];\n for (const task of tasks) {\n const taskWithUsage = task as {\n usage?: AIUsageInfo;\n searchAreaUsage?: AIUsageInfo;\n };\n const usages: Array<[string, AIUsageInfo | undefined]> = [\n ['main', taskWithUsage.usage],\n ['searchArea', taskWithUsage.searchAreaUsage],\n ];\n\n for (const [source, usage] of usages) {\n if (!hasUsageModelInfo(usage)) {\n continue;\n }\n const formatted = formatUsageModelForAgent(source, usage);\n models.set(formatted, formatted);\n }\n }\n }\n\n return Array.from(models.values()).join('; ');\n}\n\nexport function generateAgentReportComment(report: IReportActionDump): string {\n const executions = Array.isArray(report.executions) ? report.executions : [];\n const taskCount = executions.reduce(\n (sum, execution) =>\n sum + (Array.isArray(execution.tasks) ? execution.tasks.length : 0),\n 0,\n );\n const modelBriefs = report.modelBriefs?.length\n ? report.modelBriefs.map(formatModelBriefForAgent).join('; ')\n : collectUsageModelsForAgent(report) || 'No model metadata recorded';\n\n const lines = [\n 'For Agent Analysis:',\n `Report: ${cleanHtmlCommentValue(report.groupName)}`,\n `SDK: ${cleanHtmlCommentValue(report.sdkVersion)}`,\n `Device: ${cleanHtmlCommentValue(report.deviceType)}`,\n `Executions: ${executions.length}; Tasks: ${taskCount}`,\n `Models: ${cleanHtmlCommentValue(modelBriefs)}`,\n 'Structured report JSON is stored in script[type=\"midscene_web_dump\"] tags near this comment.',\n 'Screenshots are stored as script[type=\"midscene-image\"] tags or files referenced by screenshot refs.',\n 'For AI analysis, inspect each execution task for type, status, timing, param, output, usage, searchAreaUsage, recorder, and screenshot refs.',\n 'Use the Markdown export when available; it contains the same report context plus image links for agent review.',\n ];\n\n return `\\n<!--\\n${lines.join('\\n')}\\n-->\\n`;\n}\n\nfunction htmlScriptCloseTag(): string {\n // biome-ignore lint/style/useTemplate: keep this token runtime-built for inline report bundles\n return String.fromCharCode(60) + '/script>';\n}\n\n/** Chunk size for streaming file operations (64KB) */\nexport const STREAMING_CHUNK_SIZE = 64 * 1024;\n\n/**\n * Callback for processing matched tags during streaming.\n * @param content - The content between open and close tags\n * @returns true to stop scanning, false to continue\n */\ntype TagMatchCallback = (content: string) => boolean;\n\n/**\n * Stream through a file and find tags matching the pattern.\n * Memory usage: O(chunk_size + tag_size), not O(file_size).\n *\n * @param filePath - Absolute path to the file\n * @param openTag - Opening tag to search for\n * @param closeTag - Closing tag\n * @param onMatch - Callback for each matched tag content\n */\nexport function streamScanTags(\n filePath: string,\n openTag: string,\n closeTag: string,\n onMatch: TagMatchCallback,\n): void {\n const fd = openSync(filePath, 'r');\n const fileSize = statSync(filePath).size;\n const buffer = Buffer.alloc(STREAMING_CHUNK_SIZE);\n\n let position = 0;\n let leftover = '';\n let capturing = false;\n let currentContent = '';\n\n try {\n while (position < fileSize) {\n const bytesRead = readSync(fd, buffer, 0, STREAMING_CHUNK_SIZE, position);\n const chunk = leftover + buffer.toString('utf-8', 0, bytesRead);\n position += bytesRead;\n\n let searchStart = 0;\n\n while (searchStart < chunk.length) {\n if (!capturing) {\n const startIdx = chunk.indexOf(openTag, searchStart);\n if (startIdx !== -1) {\n capturing = true;\n currentContent = chunk.slice(startIdx + openTag.length);\n const endIdx = currentContent.indexOf(closeTag);\n if (endIdx !== -1) {\n const shouldStop = onMatch(currentContent.slice(0, endIdx));\n if (shouldStop) return;\n capturing = false;\n currentContent = '';\n searchStart =\n startIdx + openTag.length + endIdx + closeTag.length;\n } else {\n leftover = currentContent.slice(-closeTag.length);\n currentContent = currentContent.slice(0, -closeTag.length);\n break;\n }\n } else {\n leftover = chunk.slice(-openTag.length);\n break;\n }\n } else {\n const endIdx = chunk.indexOf(closeTag, searchStart);\n if (endIdx !== -1) {\n currentContent += chunk.slice(searchStart, endIdx);\n const shouldStop = onMatch(currentContent);\n if (shouldStop) return;\n capturing = false;\n currentContent = '';\n searchStart = endIdx + closeTag.length;\n } else {\n currentContent += chunk.slice(searchStart, -closeTag.length);\n leftover = chunk.slice(-closeTag.length);\n break;\n }\n }\n }\n }\n } finally {\n closeSync(fd);\n }\n}\n\n/**\n * Synchronously extract a specific image's base64 data from an HTML file by its id.\n * Uses streaming to avoid loading the entire file into memory.\n *\n * @param htmlPath - Absolute path to the HTML file\n * @param imageId - The id of the image to extract\n * @returns The base64 data string, or null if not found\n */\nexport function extractImageByIdSync(\n htmlPath: string,\n imageId: string,\n): string | null {\n const targetTag = `<script type=\"midscene-image\" data-id=\"${imageId}\">`;\n const closeTag = htmlScriptCloseTag();\n\n let result: string | null = null;\n\n streamScanTags(htmlPath, targetTag, closeTag, (content) => {\n result = unescapeContent(content);\n return true; // Stop after first match\n });\n\n return result;\n}\n\n/**\n * Stream image script tags from source file directly to output file.\n * Memory usage: O(single_image_size), not O(all_images_size).\n *\n * @param srcFilePath - Source HTML file path\n * @param destFilePath - Destination file path to append to\n * @param writtenImageIds - IDs already written to the destination\n */\nexport function streamImageScriptsToFile(\n srcFilePath: string,\n destFilePath: string,\n writtenImageIds: Set<string> = new Set(),\n): void {\n const { appendFileSync } = require('node:fs');\n const openTag = '<script type=\"midscene-image\"';\n const closeTag = htmlScriptCloseTag();\n\n streamScanTags(srcFilePath, openTag, closeTag, (content) => {\n const imageId = parseImageScriptId(content);\n if (!imageId || writtenImageIds.has(imageId)) {\n return false;\n }\n // Write complete tag immediately to destination, don't accumulate\n appendFileSync(destFilePath, `${openTag}${content}${closeTag}\\n`);\n writtenImageIds.add(imageId);\n return false; // Continue scanning for more tags\n });\n}\n\nfunction parseImageScriptId(contentAfterType: string): string | null {\n const match = /^ data-id=\"([a-z0-9_-]{1,128})\">/i.exec(contentAfterType);\n return match?.[1] ?? null;\n}\n\n/** Collect IDs of real inline image script tags without loading the report. */\nexport function collectImageScriptIdsSync(filePath: string): Set<string> {\n const imageIds = new Set<string>();\n const openTag = '<script type=\"midscene-image\"';\n const closeTag = htmlScriptCloseTag();\n\n streamScanTags(filePath, openTag, closeTag, (content) => {\n const imageId = parseImageScriptId(content);\n if (imageId) imageIds.add(imageId);\n return false;\n });\n return imageIds;\n}\n\n/**\n * Extract the LAST dump script content from HTML file using streaming.\n * Memory usage: O(dump_size), not O(file_size).\n *\n * @param filePath - Absolute path to the HTML file\n * @returns The dump script content (trimmed), or empty string if not found\n */\nexport function extractLastDumpScriptSync(filePath: string): string {\n const openTagPrefix = '<script type=\"midscene_web_dump\"';\n const closeTag = htmlScriptCloseTag();\n\n let lastContent = '';\n\n // Custom streaming to handle the special case where open tag has variable attributes\n const fd = openSync(filePath, 'r');\n const fileSize = statSync(filePath).size;\n const buffer = Buffer.alloc(STREAMING_CHUNK_SIZE);\n\n let position = 0;\n let leftover = '';\n let capturing = false;\n let currentContent = '';\n\n try {\n while (position < fileSize) {\n const bytesRead = readSync(fd, buffer, 0, STREAMING_CHUNK_SIZE, position);\n const chunk = leftover + buffer.toString('utf-8', 0, bytesRead);\n position += bytesRead;\n\n let searchStart = 0;\n\n while (searchStart < chunk.length) {\n if (!capturing) {\n const startIdx = chunk.indexOf(openTagPrefix, searchStart);\n if (startIdx !== -1) {\n // Find the end of the opening tag (the '>' character)\n const tagEndIdx = chunk.indexOf('>', startIdx);\n if (tagEndIdx !== -1) {\n capturing = true;\n currentContent = chunk.slice(tagEndIdx + 1);\n const endIdx = currentContent.indexOf(closeTag);\n if (endIdx !== -1) {\n lastContent = currentContent.slice(0, endIdx).trim();\n capturing = false;\n currentContent = '';\n searchStart = tagEndIdx + 1 + endIdx + closeTag.length;\n } else {\n leftover = currentContent.slice(-closeTag.length);\n currentContent = currentContent.slice(0, -closeTag.length);\n break;\n }\n } else {\n leftover = chunk.slice(startIdx);\n break;\n }\n } else {\n leftover = chunk.slice(-openTagPrefix.length);\n break;\n }\n } else {\n const endIdx = chunk.indexOf(closeTag, searchStart);\n if (endIdx !== -1) {\n currentContent += chunk.slice(searchStart, endIdx);\n lastContent = currentContent.trim();\n capturing = false;\n currentContent = '';\n searchStart = endIdx + closeTag.length;\n } else {\n currentContent += chunk.slice(searchStart, -closeTag.length);\n leftover = chunk.slice(-closeTag.length);\n break;\n }\n }\n }\n }\n } finally {\n closeSync(fd);\n }\n\n return lastContent;\n}\n\n/**\n * Extract ALL dump script contents from an HTML file using streaming.\n * Each entry includes the full opening tag (for attribute extraction) and the content.\n *\n * @param filePath - Absolute path to the HTML file\n * @returns Array of { openTag, content } for each dump script found\n */\nexport function extractAllDumpScriptsSync(\n filePath: string,\n): { openTag: string; content: string }[] {\n const results: { openTag: string; content: string }[] = [];\n streamDumpScriptsSync(filePath, (dumpScript) => {\n results.push(dumpScript);\n return false;\n });\n return results;\n}\n\n/**\n * Stream ALL dump scripts from an HTML file.\n * Calls onMatch for each dump script and keeps memory bounded to a single\n * dump script payload instead of accumulating every dump in memory.\n *\n * @param filePath - Absolute path to the HTML file\n * @param onMatch - Callback for each dump script; return true to stop early\n */\nexport function streamDumpScriptsSync(\n filePath: string,\n onMatch: (dumpScript: { openTag: string; content: string }) => boolean,\n): void {\n const openTagPrefix = '<script type=\"midscene_web_dump\"';\n const closeTag = htmlScriptCloseTag();\n\n const fd = openSync(filePath, 'r');\n const fileSize = statSync(filePath).size;\n const buffer = Buffer.alloc(STREAMING_CHUNK_SIZE);\n\n let position = 0;\n let leftover = '';\n let capturing = false;\n let currentContent = '';\n let currentOpenTag = '';\n\n try {\n while (position < fileSize) {\n const bytesRead = readSync(fd, buffer, 0, STREAMING_CHUNK_SIZE, position);\n const chunk = leftover + buffer.toString('utf-8', 0, bytesRead);\n position += bytesRead;\n\n let searchStart = 0;\n\n while (searchStart < chunk.length) {\n if (!capturing) {\n const startIdx = chunk.indexOf(openTagPrefix, searchStart);\n if (startIdx !== -1) {\n const tagEndIdx = chunk.indexOf('>', startIdx);\n if (tagEndIdx !== -1) {\n capturing = true;\n currentOpenTag = chunk.slice(startIdx, tagEndIdx + 1);\n currentContent = chunk.slice(tagEndIdx + 1);\n const endIdx = currentContent.indexOf(closeTag);\n if (endIdx !== -1) {\n const shouldStop = onMatch({\n openTag: currentOpenTag,\n content: currentContent.slice(0, endIdx).trim(),\n });\n if (shouldStop) return;\n capturing = false;\n currentContent = '';\n currentOpenTag = '';\n searchStart = tagEndIdx + 1 + endIdx + closeTag.length;\n } else {\n leftover = currentContent.slice(-closeTag.length);\n currentContent = currentContent.slice(0, -closeTag.length);\n break;\n }\n } else {\n leftover = chunk.slice(startIdx);\n break;\n }\n } else {\n leftover = chunk.slice(-openTagPrefix.length);\n break;\n }\n } else {\n const endIdx = chunk.indexOf(closeTag, searchStart);\n if (endIdx !== -1) {\n currentContent += chunk.slice(searchStart, endIdx);\n const shouldStop = onMatch({\n openTag: currentOpenTag,\n content: currentContent.trim(),\n });\n if (shouldStop) return;\n capturing = false;\n currentContent = '';\n currentOpenTag = '';\n searchStart = endIdx + closeTag.length;\n } else {\n currentContent += chunk.slice(searchStart, -closeTag.length);\n leftover = chunk.slice(-closeTag.length);\n break;\n }\n }\n }\n }\n } finally {\n closeSync(fd);\n }\n}\n\nexport function parseImageScripts(html: string): Record<string, string> {\n const imageMap: Record<string, string> = {};\n const regex =\n /<script type=\"midscene-image\" data-id=\"([^\"]+)\">([\\s\\S]*?)<\\/script>/g;\n\n for (const match of html.matchAll(regex)) {\n const [, id, content] = match;\n imageMap[id] = unescapeContent(content);\n }\n\n return imageMap;\n}\n\nexport function parseDumpScript(html: string): string {\n // Use string search instead of regex to avoid ReDoS vulnerability\n // Find the LAST dump script tag (template may contain similar patterns in bundled JS)\n const scriptOpenTag = '<script type=\"midscene_web_dump\"';\n const closeTag = htmlScriptCloseTag();\n\n // Find the last occurrence of the opening tag\n const lastOpenIndex = html.lastIndexOf(scriptOpenTag);\n if (lastOpenIndex === -1) {\n throw new Error('No dump script found in HTML');\n }\n\n // Find the end of the opening tag (the '>' character)\n const tagEndIndex = html.indexOf('>', lastOpenIndex);\n if (tagEndIndex === -1) {\n throw new Error('No dump script found in HTML');\n }\n\n // Find the closing tag after the opening tag\n const closeIndex = html.indexOf(closeTag, tagEndIndex);\n if (closeIndex === -1) {\n throw new Error('No dump script found in HTML');\n }\n\n // Extract content between opening and closing tags\n const content = html.substring(tagEndIndex + 1, closeIndex);\n return unescapeContent(content);\n}\n\nexport function parseDumpScriptAttributes(\n html: string,\n): Record<string, string> {\n const regex = /<script type=\"midscene_web_dump\"([^>]*)>/;\n const match = regex.exec(html);\n\n if (!match) {\n return {};\n }\n\n const attrString = match[1];\n const attributes: Record<string, string> = {};\n const attrRegex = /(\\w+)=\"([^\"]*)\"/g;\n\n for (const attrMatch of attrString.matchAll(attrRegex)) {\n const [, key, value] = attrMatch;\n if (key !== 'type') {\n attributes[key] = decodeURIComponent(value);\n }\n }\n\n return attributes;\n}\n\nexport function generateImageScriptTag(id: string, data: string): string {\n // Do not use template string here, will cause bundle error with <script\n const closeTag = htmlScriptCloseTag();\n return (\n // biome-ignore lint/style/useTemplate: <explanation>\n '<script type=\"midscene-image\" data-id=\"' +\n id +\n '\">' +\n escapeContent(data) +\n closeTag\n );\n}\n\n/**\n * Inline script that fixes relative URL resolution for directory-mode reports.\n *\n * Problem: when a static server (e.g. `npx serve`) serves `name/index.html`\n * at URL `/name` (without trailing slash), relative paths like\n * `./screenshots/xxx.png` resolve to `/screenshots/xxx.png` instead of\n * `/name/screenshots/xxx.png`.\n *\n * Fix: dynamically insert a <base> tag so relative URLs resolve correctly.\n */\n// Do not use template string here, will cause bundle error with <script\n//\n// The closing script tag is built at runtime so bundlers cannot inline the\n// token that would prematurely close the report template's inline app bundle.\n//\n// Do NOT replace this with a string constant, hex escape (\\x3c), or literal\n// string concatenation. Bundlers may optimise those forms back to the unsafe\n// token.\nlet _baseUrlFixScript: string;\nexport function getBaseUrlFixScript(): string {\n if (!_baseUrlFixScript) {\n const close = htmlScriptCloseTag();\n _baseUrlFixScript =\n // biome-ignore lint/style/useTemplate: see above\n '\\n<script>(function(){' +\n 'var p=window.location.pathname;' +\n 'if(p.endsWith(\"/\")||/\\\\.\\\\w+$/.test(p))return;' +\n 'var b=document.createElement(\"base\");' +\n 'b.href=p+\"/\";' +\n 'document.head.insertBefore(b,document.head.firstChild)' +\n '})()' +\n close +\n '\\n';\n }\n return _baseUrlFixScript;\n}\n\n/**\n * Dump-script attribute that records how the report file stores screenshots.\n * Written by the report generator and the merger, read back when deciding\n * whether a report is in directory mode. Kept here, next to dump-tag\n * generation, so the writer and reader share one source of truth.\n */\nexport const DATA_SCREENSHOT_MODE_ATTR = 'data-screenshot-mode';\n\nexport function generateDumpScriptTag(\n json: string,\n attributes?: Record<string, string | number | boolean>,\n): string {\n const closeTag = htmlScriptCloseTag();\n let attrString = '';\n if (attributes && Object.keys(attributes).length > 0) {\n // Do not use template string here, will cause bundle error with <script\n attrString =\n // biome-ignore lint/style/useTemplate: <explanation>\n ' ' +\n Object.entries(attributes)\n // biome-ignore lint/style/useTemplate: <explanation>\n .map(([k, v]) => k + '=\"' + encodeURIComponent(v) + '\"')\n .join(' ');\n }\n\n // Do not use template string here, will cause bundle error with <script\n return (\n // biome-ignore lint/style/useTemplate: <explanation>\n '<script type=\"midscene_web_dump\"' +\n attrString +\n '>' +\n escapeContent(json) +\n closeTag\n );\n}\n"],"names":["escapeContent","escapeScriptTag","unescapeContent","antiEscapeScriptTag","cleanHtmlCommentValue","value","String","formatModelBriefForAgent","brief","intent","model","description","hasUsageModelInfo","usage","Boolean","formatUsageModelForAgent","source","collectUsageModelsForAgent","report","models","Map","executions","Array","execution","tasks","task","taskWithUsage","usages","formatted","generateAgentReportComment","taskCount","sum","modelBriefs","lines","htmlScriptCloseTag","STREAMING_CHUNK_SIZE","streamScanTags","filePath","openTag","closeTag","onMatch","fd","openSync","fileSize","statSync","buffer","Buffer","position","leftover","capturing","currentContent","bytesRead","readSync","chunk","searchStart","endIdx","shouldStop","startIdx","closeSync","extractImageByIdSync","htmlPath","imageId","targetTag","result","content","streamImageScriptsToFile","srcFilePath","destFilePath","writtenImageIds","Set","appendFileSync","require","parseImageScriptId","contentAfterType","match","collectImageScriptIdsSync","imageIds","extractLastDumpScriptSync","openTagPrefix","lastContent","tagEndIdx","extractAllDumpScriptsSync","results","streamDumpScriptsSync","dumpScript","currentOpenTag","parseImageScripts","html","imageMap","regex","id","parseDumpScript","scriptOpenTag","lastOpenIndex","Error","tagEndIndex","closeIndex","parseDumpScriptAttributes","attrString","attributes","attrRegex","attrMatch","key","decodeURIComponent","generateImageScriptTag","data","_baseUrlFixScript","getBaseUrlFixScript","close","DATA_SCREENSHOT_MODE_ATTR","generateDumpScriptTag","json","Object","k","v","encodeURIComponent"],"mappings":"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"}
|
package/dist/es/dump/index.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { restoreImageReferences } from "./screenshot-restoration.mjs";
|
|
2
|
+
import { ReportImageStore, ScreenshotStore } from "./screenshot-store.mjs";
|
|
2
3
|
import { escapeContent, generateDumpScriptTag, generateImageScriptTag, parseDumpScript, parseDumpScriptAttributes, parseImageScripts, unescapeContent } from "./html-utils.mjs";
|
|
3
4
|
import { getTaskSearchArea, getTaskServiceDump } from "./task-service-dump.mjs";
|
|
4
5
|
import { deriveCaseStatus, deriveTaskStatus } from "./task-status.mjs";
|
|
5
|
-
export { deriveCaseStatus, deriveTaskStatus, escapeContent, generateDumpScriptTag, generateImageScriptTag, getTaskSearchArea, getTaskServiceDump, parseDumpScript, parseDumpScriptAttributes, parseImageScripts, restoreImageReferences, unescapeContent };
|
|
6
|
+
export { ReportImageStore, ScreenshotStore, deriveCaseStatus, deriveTaskStatus, escapeContent, generateDumpScriptTag, generateImageScriptTag, getTaskSearchArea, getTaskServiceDump, parseDumpScript, parseDumpScriptAttributes, parseImageScripts, restoreImageReferences, unescapeContent };
|
|
@@ -2,7 +2,7 @@ import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node
|
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { ScreenshotItem } from "../screenshot-item.mjs";
|
|
4
4
|
import { restoreImageReferences } from "./screenshot-restoration.mjs";
|
|
5
|
-
import {
|
|
5
|
+
import { imageRefFileExtension, isBase64ImageDataUrl, resolveImageSource } from "./screenshot-store.mjs";
|
|
6
6
|
function _define_property(obj, key, value) {
|
|
7
7
|
if (key in obj) Object.defineProperty(obj, key, {
|
|
8
8
|
value: value,
|
|
@@ -13,13 +13,27 @@ function _define_property(obj, key, value) {
|
|
|
13
13
|
else obj[key] = value;
|
|
14
14
|
return obj;
|
|
15
15
|
}
|
|
16
|
-
function
|
|
17
|
-
if ('
|
|
16
|
+
function isReferenceImageDescriptor(value) {
|
|
17
|
+
if ('object' != typeof value || null === value) return false;
|
|
18
|
+
const record = value;
|
|
19
|
+
return 'string' == typeof record.name && 'string' == typeof record.url;
|
|
20
|
+
}
|
|
21
|
+
function replacerForDumpSerialization(holder, key, value, referenceImageRefs) {
|
|
22
|
+
if ('screenshotSequence' === key) return;
|
|
23
|
+
if ('url' === key && 'string' == typeof value && isReferenceImageDescriptor(holder)) {
|
|
24
|
+
const referenceImageRef = referenceImageRefs?.get(value);
|
|
25
|
+
if (referenceImageRef) return referenceImageRef;
|
|
26
|
+
}
|
|
18
27
|
if (value && value.constructor?.name === 'Page') return '[Page object]';
|
|
19
28
|
if (value && value.constructor?.name === 'Browser') return '[Browser object]';
|
|
20
29
|
if (value && 'function' == typeof value.toSerializable) return value.toSerializable();
|
|
21
30
|
return value;
|
|
22
31
|
}
|
|
32
|
+
function stringifyDump(data, indents, referenceImageRefs) {
|
|
33
|
+
return JSON.stringify(data, function(key, value) {
|
|
34
|
+
return replacerForDumpSerialization(this, key, value, referenceImageRefs);
|
|
35
|
+
}, indents);
|
|
36
|
+
}
|
|
23
37
|
function reviverForDumpDeserialization(key, value) {
|
|
24
38
|
if ('screenshot' !== key || 'object' != typeof value || null === value) return value;
|
|
25
39
|
ScreenshotItem.isSerialized(value);
|
|
@@ -27,7 +41,7 @@ function reviverForDumpDeserialization(key, value) {
|
|
|
27
41
|
}
|
|
28
42
|
class ExecutionDump {
|
|
29
43
|
serialize(indents) {
|
|
30
|
-
return
|
|
44
|
+
return stringifyDump(this.toJSON(), indents);
|
|
31
45
|
}
|
|
32
46
|
toJSON() {
|
|
33
47
|
return {
|
|
@@ -59,6 +73,27 @@ class ExecutionDump {
|
|
|
59
73
|
}
|
|
60
74
|
return screenshots;
|
|
61
75
|
}
|
|
76
|
+
collectReferenceImageUrls() {
|
|
77
|
+
const imageUrls = new Set();
|
|
78
|
+
const visited = new WeakSet();
|
|
79
|
+
const visit = (value)=>{
|
|
80
|
+
if (Array.isArray(value)) {
|
|
81
|
+
for (const item of value)visit(item);
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
if ('object' != typeof value || null === value || visited.has(value)) return;
|
|
85
|
+
visited.add(value);
|
|
86
|
+
const record = value;
|
|
87
|
+
if (Array.isArray(record.images)) for (const image of record.images){
|
|
88
|
+
if (!isReferenceImageDescriptor(image)) continue;
|
|
89
|
+
const imageUrl = image.url;
|
|
90
|
+
if (isBase64ImageDataUrl(imageUrl)) imageUrls.add(imageUrl);
|
|
91
|
+
}
|
|
92
|
+
for (const nestedValue of Object.values(record))visit(nestedValue);
|
|
93
|
+
};
|
|
94
|
+
for (const task of this.tasks)visit(task.param);
|
|
95
|
+
return Array.from(imageUrls);
|
|
96
|
+
}
|
|
62
97
|
constructor(data){
|
|
63
98
|
_define_property(this, "id", void 0);
|
|
64
99
|
_define_property(this, "logTime", void 0);
|
|
@@ -76,7 +111,10 @@ class ExecutionDump {
|
|
|
76
111
|
}
|
|
77
112
|
class ReportActionDump {
|
|
78
113
|
serialize(indents) {
|
|
79
|
-
return
|
|
114
|
+
return stringifyDump(this.toJSON(), indents);
|
|
115
|
+
}
|
|
116
|
+
serializeWithReferenceImages(referenceImageRefs, indents) {
|
|
117
|
+
return stringifyDump(this.toJSON(), indents, referenceImageRefs);
|
|
80
118
|
}
|
|
81
119
|
serializeWithInlineScreenshots(indents) {
|
|
82
120
|
const processValue = (obj)=>{
|
|
@@ -136,23 +174,23 @@ class ReportActionDump {
|
|
|
136
174
|
static fromFilesAsInlineJson(basePath) {
|
|
137
175
|
const dumpString = readFileSync(basePath, 'utf-8');
|
|
138
176
|
const screenshotsDir = `${basePath}.screenshots`;
|
|
139
|
-
const loadFromExecutionScreenshotDir = (id, mimeType)=>{
|
|
140
|
-
const
|
|
141
|
-
const filePath = join(screenshotsDir, `${id}.${ext}`);
|
|
177
|
+
const loadFromExecutionScreenshotDir = (id, mimeType, extension)=>{
|
|
178
|
+
const filePath = join(screenshotsDir, `${id}.${extension}`);
|
|
142
179
|
if (!existsSync(filePath)) return '';
|
|
143
180
|
const data = readFileSync(filePath);
|
|
144
|
-
return `data
|
|
181
|
+
return `data:${mimeType};base64,${data.toString('base64')}`;
|
|
145
182
|
};
|
|
146
183
|
const dumpData = JSON.parse(dumpString);
|
|
147
|
-
const store = new ScreenshotStore({
|
|
148
|
-
mode: 'directory',
|
|
149
|
-
reportPath: basePath
|
|
150
|
-
});
|
|
151
184
|
const processedData = restoreImageReferences(dumpData, (ref)=>{
|
|
152
|
-
const executionFileImage = loadFromExecutionScreenshotDir(ref.id, ref.mimeType);
|
|
185
|
+
const executionFileImage = loadFromExecutionScreenshotDir(ref.id, ref.mimeType, imageRefFileExtension(ref));
|
|
153
186
|
if (executionFileImage) return executionFileImage;
|
|
154
187
|
if ('inline' === ref.storage) return '';
|
|
155
|
-
|
|
188
|
+
const resolved = resolveImageSource(ref, {
|
|
189
|
+
reportPath: basePath
|
|
190
|
+
});
|
|
191
|
+
if ('data-uri' === resolved.type) return resolved.dataUri;
|
|
192
|
+
const data = readFileSync(resolved.filePath);
|
|
193
|
+
return `data:${resolved.mimeType};base64,${data.toString('base64')}`;
|
|
156
194
|
});
|
|
157
195
|
return JSON.stringify(processedData);
|
|
158
196
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dump/report-action-dump.mjs","sources":["../../../src/dump/report-action-dump.ts"],"sourcesContent":["import {\n existsSync,\n mkdirSync,\n readFileSync,\n rmSync,\n writeFileSync,\n} from 'node:fs';\nimport { join } from 'node:path';\nimport { ScreenshotItem } from '../screenshot-item';\nimport type {\n ExecutionTask,\n IExecutionDump,\n IReportActionDump,\n} from '../types';\nimport { restoreImageReferences } from './screenshot-restoration';\nimport { ScreenshotStore } from './screenshot-store';\n\n/**\n * Replacer function for JSON serialization that handles Page, Browser objects and ScreenshotItem\n */\nfunction replacerForDumpSerialization(_key: string, value: any): any {\n // screenshotSequence is a transient model input (multi-frame capture). Its\n // frames are not persisted by collectScreenshots, so serializing them would\n // emit dangling screenshot refs. The representative `screenshot` is kept.\n if (_key === 'screenshotSequence') {\n return undefined;\n }\n if (value && value.constructor?.name === 'Page') {\n return '[Page object]';\n }\n if (value && value.constructor?.name === 'Browser') {\n return '[Browser object]';\n }\n // Handle ScreenshotItem serialization\n if (value && typeof value.toSerializable === 'function') {\n return value.toSerializable();\n }\n return value;\n}\n\n/**\n * Reviver function for JSON deserialization that keeps screenshot references\n * as plain objects. Resolution is handled lazily by restoreImageReferences.\n *\n * @param key - JSON key being processed\n * @param value - JSON value being processed\n * @returns Restored value\n */\nfunction reviverForDumpDeserialization(key: string, value: any): any {\n // Only process screenshot fields\n if (key !== 'screenshot' || typeof value !== 'object' || value === null) {\n return value;\n }\n\n if (ScreenshotItem.isSerialized(value)) {\n return value;\n }\n\n return value;\n}\n\n/**\n * ExecutionDump class for serializing and deserializing execution dumps\n */\nexport class ExecutionDump implements IExecutionDump {\n id?: string;\n logTime: number;\n name: string;\n description?: string;\n tasks: ExecutionTask[];\n aiActContext?: string;\n\n constructor(data: IExecutionDump) {\n this.id = data.id;\n this.logTime = data.logTime;\n this.name = data.name;\n this.description = data.description;\n this.tasks = data.tasks;\n this.aiActContext = data.aiActContext;\n }\n\n /**\n * Serialize the ExecutionDump to a JSON string\n */\n serialize(indents?: number): string {\n return JSON.stringify(this.toJSON(), replacerForDumpSerialization, indents);\n }\n\n /**\n * Convert to a plain object for JSON serialization\n */\n toJSON(): IExecutionDump {\n return {\n id: this.id,\n logTime: this.logTime,\n name: this.name,\n description: this.description,\n tasks: this.tasks.map((task) => ({\n ...task,\n recorder: task.recorder || [],\n })),\n aiActContext: this.aiActContext,\n };\n }\n\n /**\n * Create an ExecutionDump instance from a serialized JSON string\n */\n static fromSerializedString(serialized: string): ExecutionDump {\n const parsed = JSON.parse(\n serialized,\n reviverForDumpDeserialization,\n ) as IExecutionDump;\n return new ExecutionDump(parsed);\n }\n\n /**\n * Create an ExecutionDump instance from a plain object\n */\n static fromJSON(data: IExecutionDump): ExecutionDump {\n return new ExecutionDump(data);\n }\n\n /**\n * Collect all ScreenshotItem instances from tasks.\n * Scans through uiContext and recorder items to find screenshots.\n *\n * @returns Array of ScreenshotItem instances\n */\n collectScreenshots(): ScreenshotItem[] {\n const screenshots: ScreenshotItem[] = [];\n\n for (const task of this.tasks) {\n // Collect uiContext.screenshot if present\n if (task.uiContext?.screenshot instanceof ScreenshotItem) {\n screenshots.push(task.uiContext.screenshot);\n }\n\n // Collect recorder screenshots\n if (task.recorder) {\n for (const record of task.recorder) {\n if (record.screenshot instanceof ScreenshotItem) {\n screenshots.push(record.screenshot);\n }\n }\n }\n }\n\n return screenshots;\n }\n}\n\n/**\n * ReportActionDump class for serializing and deserializing report action dumps\n */\nexport class ReportActionDump implements IReportActionDump {\n sdkVersion: string;\n groupName: string;\n groupDescription?: string;\n modelBriefs: IReportActionDump['modelBriefs'];\n executions: ExecutionDump[];\n deviceType?: string;\n\n constructor(data: IReportActionDump) {\n this.sdkVersion = data.sdkVersion;\n this.groupName = data.groupName;\n this.groupDescription = data.groupDescription;\n this.modelBriefs = data.modelBriefs;\n this.executions = data.executions.map((exec) =>\n exec instanceof ExecutionDump ? exec : ExecutionDump.fromJSON(exec),\n );\n this.deviceType = data.deviceType;\n }\n\n /**\n * Serialize the ReportActionDump to a JSON string\n * Uses compact { $screenshot: id } format\n */\n serialize(indents?: number): string {\n return JSON.stringify(this.toJSON(), replacerForDumpSerialization, indents);\n }\n\n /**\n * Serialize the ReportActionDump with inline screenshots to a JSON string.\n * Each ScreenshotItem is replaced with { base64: \"...\", capturedAt }.\n */\n serializeWithInlineScreenshots(indents?: number): string {\n const processValue = (obj: unknown): unknown => {\n if (obj instanceof ScreenshotItem) {\n return { base64: obj.base64, capturedAt: obj.capturedAt };\n }\n if (Array.isArray(obj)) {\n return obj.map(processValue);\n }\n if (obj && typeof obj === 'object') {\n const entries = Object.entries(obj)\n // screenshotSequence is a transient multi-frame model input whose\n // frames are not persisted; skip it to avoid inlining large base64.\n .filter(([key]) => key !== 'screenshotSequence')\n .map(([key, value]) => [key, processValue(value)]);\n return Object.fromEntries(entries);\n }\n return obj;\n };\n\n const data = processValue(this.toJSON());\n return JSON.stringify(data, null, indents);\n }\n\n /**\n * Convert to a plain object for JSON serialization\n */\n toJSON(): IReportActionDump {\n return {\n sdkVersion: this.sdkVersion,\n groupName: this.groupName,\n groupDescription: this.groupDescription,\n modelBriefs: this.modelBriefs,\n executions: this.executions.map((exec) => exec.toJSON()),\n deviceType: this.deviceType,\n };\n }\n\n /**\n * Create a ReportActionDump instance from a serialized JSON string\n */\n static fromSerializedString(serialized: string): ReportActionDump {\n const parsed = JSON.parse(\n serialized,\n reviverForDumpDeserialization,\n ) as IReportActionDump;\n return new ReportActionDump(parsed);\n }\n\n /**\n * Create a ReportActionDump instance from a plain object\n */\n static fromJSON(data: IReportActionDump): ReportActionDump {\n return new ReportActionDump(data);\n }\n\n /**\n * Collect all ScreenshotItem instances from all executions.\n *\n * @returns Array of all ScreenshotItem instances across all executions\n */\n collectAllScreenshots(): ScreenshotItem[] {\n const screenshots: ScreenshotItem[] = [];\n for (const execution of this.executions) {\n screenshots.push(...execution.collectScreenshots());\n }\n return screenshots;\n }\n\n /**\n * Serialize the dump to files with screenshots as separate PNG files.\n * Creates:\n * - {basePath} - dump JSON with { $screenshot: id } references\n * - {basePath}.screenshots/ - PNG files\n *\n * @param basePath - Base path for the dump file\n */\n serializeToFiles(basePath: string): void {\n const screenshotsDir = `${basePath}.screenshots`;\n if (!existsSync(screenshotsDir)) {\n mkdirSync(screenshotsDir, { recursive: true });\n }\n\n const screenshots = this.collectAllScreenshots();\n\n for (const screenshot of screenshots) {\n const imagePath = join(\n screenshotsDir,\n `${screenshot.id}.${screenshot.extension}`,\n );\n if (existsSync(imagePath)) {\n continue;\n }\n\n const rawBase64 = screenshot.rawBase64;\n writeFileSync(imagePath, Buffer.from(rawBase64, 'base64'));\n }\n\n // Write dump JSON with references\n writeFileSync(basePath, this.serialize(), 'utf-8');\n }\n\n /**\n * Read dump from files and return JSON string with inline screenshots.\n * Reads the dump JSON and screenshot files, then inlines the base64 data.\n *\n * @param basePath - Base path for the dump file\n * @returns JSON string with inline screenshots ({ base64: \"...\" } format)\n */\n static fromFilesAsInlineJson(basePath: string): string {\n const dumpString = readFileSync(basePath, 'utf-8');\n const screenshotsDir = `${basePath}.screenshots`;\n\n const loadFromExecutionScreenshotDir = (id: string, mimeType: string) => {\n const ext = mimeType === 'image/jpeg' ? 'jpeg' : 'png';\n const filePath = join(screenshotsDir, `${id}.${ext}`);\n if (!existsSync(filePath)) {\n return '';\n }\n const data = readFileSync(filePath);\n return `data:image/${ext};base64,${data.toString('base64')}`;\n };\n\n // Restore image references\n const dumpData = JSON.parse(dumpString);\n const store = new ScreenshotStore({\n mode: 'directory',\n reportPath: basePath,\n });\n const processedData = restoreImageReferences(dumpData, (ref) => {\n const executionFileImage = loadFromExecutionScreenshotDir(\n ref.id,\n ref.mimeType,\n );\n if (executionFileImage) {\n return executionFileImage;\n }\n\n if (ref.storage === 'inline') {\n return '';\n }\n return store.loadBase64(ref);\n });\n return JSON.stringify(processedData);\n }\n\n /**\n * Clean up all files associated with a serialized dump.\n *\n * @param basePath - Base path for the dump file\n */\n static cleanupFiles(basePath: string): void {\n const filesToClean = [basePath, `${basePath}.screenshots`];\n\n for (const filePath of filesToClean) {\n try {\n rmSync(filePath, { force: true, recursive: true });\n } catch {\n // Ignore errors - file may already be deleted\n }\n }\n }\n\n /**\n * Get all file paths associated with a serialized dump.\n *\n * @param basePath - Base path for the dump file\n * @returns Array of all associated file paths\n */\n static getFilePaths(basePath: string): string[] {\n return [basePath, `${basePath}.screenshots`];\n }\n}\n\n// Backward-compatible aliases for existing external consumers.\nexport type GroupedActionDump = ReportActionDump;\nexport const GroupedActionDump = ReportActionDump;\n"],"names":["replacerForDumpSerialization","_key","value","reviverForDumpDeserialization","key","ScreenshotItem","ExecutionDump","indents","JSON","task","serialized","parsed","data","screenshots","record","ReportActionDump","processValue","obj","Array","entries","Object","exec","execution","basePath","screenshotsDir","existsSync","mkdirSync","screenshot","imagePath","join","rawBase64","writeFileSync","Buffer","dumpString","readFileSync","loadFromExecutionScreenshotDir","id","mimeType","ext","filePath","dumpData","store","ScreenshotStore","processedData","restoreImageReferences","ref","executionFileImage","filesToClean","rmSync","GroupedActionDump"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"dump/report-action-dump.mjs","sources":["../../../src/dump/report-action-dump.ts"],"sourcesContent":["import {\n existsSync,\n mkdirSync,\n readFileSync,\n rmSync,\n writeFileSync,\n} from 'node:fs';\nimport { join } from 'node:path';\nimport { ScreenshotItem } from '../screenshot-item';\nimport type {\n ExecutionTask,\n IExecutionDump,\n IReportActionDump,\n} from '../types';\nimport { restoreImageReferences } from './screenshot-restoration';\nimport {\n type ImageUrlRef,\n imageRefFileExtension,\n isBase64ImageDataUrl,\n resolveImageSource,\n} from './screenshot-store';\n\n/** Maps original prompt image data URLs to their persisted report assets. */\nexport type ReferenceImageRefs = ReadonlyMap<string, ImageUrlRef>;\n\nfunction isReferenceImageDescriptor(value: unknown): boolean {\n if (typeof value !== 'object' || value === null) return false;\n const record = value as Record<string, unknown>;\n return typeof record.name === 'string' && typeof record.url === 'string';\n}\n\n/**\n * Replacer function for JSON serialization that handles Page, Browser objects and ScreenshotItem\n */\nfunction replacerForDumpSerialization(\n holder: unknown,\n key: string,\n value: any,\n referenceImageRefs?: ReferenceImageRefs,\n): any {\n // screenshotSequence is a transient model input (multi-frame capture). Its\n // frames are not persisted by collectScreenshots, so serializing them would\n // emit dangling screenshot refs. The representative `screenshot` is kept.\n if (key === 'screenshotSequence') {\n return undefined;\n }\n if (\n key === 'url' &&\n typeof value === 'string' &&\n isReferenceImageDescriptor(holder)\n ) {\n const referenceImageRef = referenceImageRefs?.get(value);\n if (referenceImageRef) {\n return referenceImageRef;\n }\n }\n if (value && value.constructor?.name === 'Page') {\n return '[Page object]';\n }\n if (value && value.constructor?.name === 'Browser') {\n return '[Browser object]';\n }\n // Handle ScreenshotItem serialization\n if (value && typeof value.toSerializable === 'function') {\n return value.toSerializable();\n }\n return value;\n}\n\nfunction stringifyDump(\n data: IExecutionDump | IReportActionDump,\n indents?: number,\n referenceImageRefs?: ReferenceImageRefs,\n): string {\n return JSON.stringify(\n data,\n function (key, value) {\n return replacerForDumpSerialization(this, key, value, referenceImageRefs);\n },\n indents,\n );\n}\n\n/**\n * Reviver function for JSON deserialization that keeps screenshot references\n * as plain objects. Resolution is handled lazily by restoreImageReferences.\n *\n * @param key - JSON key being processed\n * @param value - JSON value being processed\n * @returns Restored value\n */\nfunction reviverForDumpDeserialization(key: string, value: any): any {\n // Only process screenshot fields\n if (key !== 'screenshot' || typeof value !== 'object' || value === null) {\n return value;\n }\n\n if (ScreenshotItem.isSerialized(value)) {\n return value;\n }\n\n return value;\n}\n\n/**\n * ExecutionDump class for serializing and deserializing execution dumps\n */\nexport class ExecutionDump implements IExecutionDump {\n id?: string;\n logTime: number;\n name: string;\n description?: string;\n tasks: ExecutionTask[];\n aiActContext?: string;\n\n constructor(data: IExecutionDump) {\n this.id = data.id;\n this.logTime = data.logTime;\n this.name = data.name;\n this.description = data.description;\n this.tasks = data.tasks;\n this.aiActContext = data.aiActContext;\n }\n\n /**\n * Serialize the ExecutionDump to a JSON string\n */\n serialize(indents?: number): string {\n return stringifyDump(this.toJSON(), indents);\n }\n\n /**\n * Convert to a plain object for JSON serialization\n */\n toJSON(): IExecutionDump {\n return {\n id: this.id,\n logTime: this.logTime,\n name: this.name,\n description: this.description,\n tasks: this.tasks.map((task) => ({\n ...task,\n recorder: task.recorder || [],\n })),\n aiActContext: this.aiActContext,\n };\n }\n\n /**\n * Create an ExecutionDump instance from a serialized JSON string\n */\n static fromSerializedString(serialized: string): ExecutionDump {\n const parsed = JSON.parse(\n serialized,\n reviverForDumpDeserialization,\n ) as IExecutionDump;\n return new ExecutionDump(parsed);\n }\n\n /**\n * Create an ExecutionDump instance from a plain object\n */\n static fromJSON(data: IExecutionDump): ExecutionDump {\n return new ExecutionDump(data);\n }\n\n /**\n * Collect all ScreenshotItem instances from tasks.\n * Scans through uiContext and recorder items to find screenshots.\n *\n * @returns Array of ScreenshotItem instances\n */\n collectScreenshots(): ScreenshotItem[] {\n const screenshots: ScreenshotItem[] = [];\n\n for (const task of this.tasks) {\n // Collect uiContext.screenshot if present\n if (task.uiContext?.screenshot instanceof ScreenshotItem) {\n screenshots.push(task.uiContext.screenshot);\n }\n\n // Collect recorder screenshots\n if (task.recorder) {\n for (const record of task.recorder) {\n if (record.screenshot instanceof ScreenshotItem) {\n screenshots.push(record.screenshot);\n }\n }\n }\n }\n\n return screenshots;\n }\n\n /**\n * Collect unique base64 image URLs from multimodal prompt descriptors.\n * Traversal is cycle-safe because task params may contain consumer objects.\n *\n * @returns The unique image data URLs in first-seen order.\n */\n collectReferenceImageUrls(): string[] {\n const imageUrls = new Set<string>();\n const visited = new WeakSet<object>();\n\n const visit = (value: unknown): void => {\n if (Array.isArray(value)) {\n for (const item of value) visit(item);\n return;\n }\n if (typeof value !== 'object' || value === null || visited.has(value)) {\n return;\n }\n visited.add(value);\n\n const record = value as Record<string, unknown>;\n if (Array.isArray(record.images)) {\n for (const image of record.images) {\n if (!isReferenceImageDescriptor(image)) continue;\n const imageUrl = (image as { url: string }).url;\n if (isBase64ImageDataUrl(imageUrl)) imageUrls.add(imageUrl);\n }\n }\n\n for (const nestedValue of Object.values(record)) visit(nestedValue);\n };\n\n for (const task of this.tasks) visit(task.param);\n return Array.from(imageUrls);\n }\n}\n\n/**\n * ReportActionDump class for serializing and deserializing report action dumps\n */\nexport class ReportActionDump implements IReportActionDump {\n sdkVersion: string;\n groupName: string;\n groupDescription?: string;\n modelBriefs: IReportActionDump['modelBriefs'];\n executions: ExecutionDump[];\n deviceType?: string;\n\n constructor(data: IReportActionDump) {\n this.sdkVersion = data.sdkVersion;\n this.groupName = data.groupName;\n this.groupDescription = data.groupDescription;\n this.modelBriefs = data.modelBriefs;\n this.executions = data.executions.map((exec) =>\n exec instanceof ExecutionDump ? exec : ExecutionDump.fromJSON(exec),\n );\n this.deviceType = data.deviceType;\n }\n\n /**\n * Serialize the ReportActionDump to a JSON string\n * Uses compact { $screenshot: id } format\n */\n serialize(indents?: number): string {\n return stringifyDump(this.toJSON(), indents);\n }\n\n /**\n * Serialize a report dump while replacing persisted multimodal prompt image\n * URLs with their compact report asset references.\n *\n * @param referenceImageRefs Maps each original data URL to its stored asset.\n * @param indents Optional JSON indentation.\n * @returns The serialized report dump.\n */\n serializeWithReferenceImages(\n referenceImageRefs: ReferenceImageRefs,\n indents?: number,\n ): string {\n return stringifyDump(this.toJSON(), indents, referenceImageRefs);\n }\n\n /**\n * Serialize the ReportActionDump with inline screenshots to a JSON string.\n * Each ScreenshotItem is replaced with { base64: \"...\", capturedAt }.\n */\n serializeWithInlineScreenshots(indents?: number): string {\n const processValue = (obj: unknown): unknown => {\n if (obj instanceof ScreenshotItem) {\n return { base64: obj.base64, capturedAt: obj.capturedAt };\n }\n if (Array.isArray(obj)) {\n return obj.map(processValue);\n }\n if (obj && typeof obj === 'object') {\n const entries = Object.entries(obj)\n // screenshotSequence is a transient multi-frame model input whose\n // frames are not persisted; skip it to avoid inlining large base64.\n .filter(([key]) => key !== 'screenshotSequence')\n .map(([key, value]) => [key, processValue(value)]);\n return Object.fromEntries(entries);\n }\n return obj;\n };\n\n const data = processValue(this.toJSON());\n return JSON.stringify(data, null, indents);\n }\n\n /**\n * Convert to a plain object for JSON serialization\n */\n toJSON(): IReportActionDump {\n return {\n sdkVersion: this.sdkVersion,\n groupName: this.groupName,\n groupDescription: this.groupDescription,\n modelBriefs: this.modelBriefs,\n executions: this.executions.map((exec) => exec.toJSON()),\n deviceType: this.deviceType,\n };\n }\n\n /**\n * Create a ReportActionDump instance from a serialized JSON string\n */\n static fromSerializedString(serialized: string): ReportActionDump {\n const parsed = JSON.parse(\n serialized,\n reviverForDumpDeserialization,\n ) as IReportActionDump;\n return new ReportActionDump(parsed);\n }\n\n /**\n * Create a ReportActionDump instance from a plain object\n */\n static fromJSON(data: IReportActionDump): ReportActionDump {\n return new ReportActionDump(data);\n }\n\n /**\n * Collect all ScreenshotItem instances from all executions.\n *\n * @returns Array of all ScreenshotItem instances across all executions\n */\n collectAllScreenshots(): ScreenshotItem[] {\n const screenshots: ScreenshotItem[] = [];\n for (const execution of this.executions) {\n screenshots.push(...execution.collectScreenshots());\n }\n return screenshots;\n }\n\n /**\n * Serialize the dump to files with screenshots as separate PNG files.\n * Creates:\n * - {basePath} - dump JSON with { $screenshot: id } references\n * - {basePath}.screenshots/ - PNG files\n *\n * @param basePath - Base path for the dump file\n */\n serializeToFiles(basePath: string): void {\n const screenshotsDir = `${basePath}.screenshots`;\n if (!existsSync(screenshotsDir)) {\n mkdirSync(screenshotsDir, { recursive: true });\n }\n\n const screenshots = this.collectAllScreenshots();\n\n for (const screenshot of screenshots) {\n const imagePath = join(\n screenshotsDir,\n `${screenshot.id}.${screenshot.extension}`,\n );\n if (existsSync(imagePath)) {\n continue;\n }\n\n const rawBase64 = screenshot.rawBase64;\n writeFileSync(imagePath, Buffer.from(rawBase64, 'base64'));\n }\n\n // Write dump JSON with references\n writeFileSync(basePath, this.serialize(), 'utf-8');\n }\n\n /**\n * Read dump from files and return JSON string with inline screenshots.\n * Reads the dump JSON and screenshot files, then inlines the base64 data.\n *\n * @param basePath - Base path for the dump file\n * @returns JSON string with inline screenshots ({ base64: \"...\" } format)\n */\n static fromFilesAsInlineJson(basePath: string): string {\n const dumpString = readFileSync(basePath, 'utf-8');\n const screenshotsDir = `${basePath}.screenshots`;\n\n const loadFromExecutionScreenshotDir = (\n id: string,\n mimeType: string,\n extension: string,\n ) => {\n const filePath = join(screenshotsDir, `${id}.${extension}`);\n if (!existsSync(filePath)) {\n return '';\n }\n const data = readFileSync(filePath);\n return `data:${mimeType};base64,${data.toString('base64')}`;\n };\n\n // Restore image references\n const dumpData = JSON.parse(dumpString);\n const processedData = restoreImageReferences(dumpData, (ref) => {\n const executionFileImage = loadFromExecutionScreenshotDir(\n ref.id,\n ref.mimeType,\n imageRefFileExtension(ref),\n );\n if (executionFileImage) {\n return executionFileImage;\n }\n\n if (ref.storage === 'inline') {\n return '';\n }\n const resolved = resolveImageSource(ref, { reportPath: basePath });\n if (resolved.type === 'data-uri') return resolved.dataUri;\n const data = readFileSync(resolved.filePath);\n return `data:${resolved.mimeType};base64,${data.toString('base64')}`;\n });\n return JSON.stringify(processedData);\n }\n\n /**\n * Clean up all files associated with a serialized dump.\n *\n * @param basePath - Base path for the dump file\n */\n static cleanupFiles(basePath: string): void {\n const filesToClean = [basePath, `${basePath}.screenshots`];\n\n for (const filePath of filesToClean) {\n try {\n rmSync(filePath, { force: true, recursive: true });\n } catch {\n // Ignore errors - file may already be deleted\n }\n }\n }\n\n /**\n * Get all file paths associated with a serialized dump.\n *\n * @param basePath - Base path for the dump file\n * @returns Array of all associated file paths\n */\n static getFilePaths(basePath: string): string[] {\n return [basePath, `${basePath}.screenshots`];\n }\n}\n\n// Backward-compatible aliases for existing external consumers.\nexport type GroupedActionDump = ReportActionDump;\nexport const GroupedActionDump = ReportActionDump;\n"],"names":["isReferenceImageDescriptor","value","record","replacerForDumpSerialization","holder","key","referenceImageRefs","referenceImageRef","stringifyDump","data","indents","JSON","reviverForDumpDeserialization","ScreenshotItem","ExecutionDump","task","serialized","parsed","screenshots","imageUrls","Set","visited","WeakSet","visit","Array","item","image","imageUrl","isBase64ImageDataUrl","nestedValue","Object","ReportActionDump","processValue","obj","entries","exec","execution","basePath","screenshotsDir","existsSync","mkdirSync","screenshot","imagePath","join","rawBase64","writeFileSync","Buffer","dumpString","readFileSync","loadFromExecutionScreenshotDir","id","mimeType","extension","filePath","dumpData","processedData","restoreImageReferences","ref","executionFileImage","imageRefFileExtension","resolved","resolveImageSource","filesToClean","rmSync","GroupedActionDump"],"mappings":"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"}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
import { normalizeScreenshotRef } from "./screenshot-store.mjs";
|
|
1
|
+
import { normalizeImageUrlRef, normalizeScreenshotRef } from "./screenshot-store.mjs";
|
|
2
2
|
function restoreImageReferences(data, resolveImage) {
|
|
3
3
|
if ('string' == typeof data) return data;
|
|
4
4
|
if (Array.isArray(data)) return data.map((item)=>restoreImageReferences(item, resolveImage));
|
|
5
5
|
if ('object' == typeof data && null !== data) {
|
|
6
|
+
const imageUrlRef = normalizeImageUrlRef(data);
|
|
7
|
+
if (imageUrlRef) return resolveImage(imageUrlRef);
|
|
6
8
|
const refLike = normalizeScreenshotRef(data);
|
|
7
9
|
if (refLike) {
|
|
8
10
|
let resolved = null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dump/screenshot-restoration.mjs","sources":["../../../src/dump/screenshot-restoration.ts"],"sourcesContent":["import {
|
|
1
|
+
{"version":3,"file":"dump/screenshot-restoration.mjs","sources":["../../../src/dump/screenshot-restoration.ts"],"sourcesContent":["import {\n type ScreenshotRef,\n type StoredImageRef,\n normalizeImageUrlRef,\n normalizeScreenshotRef,\n} from './screenshot-store';\n\n/**\n * Recursively restore image references in parsed data.\n * Replaces screenshot refs with lazy\n * { get base64() {...}, capturedAt, sourceRef } objects, and reference-image\n * URL refs with their URL strings. Screenshot refs are resolved on first use.\n */\nexport function restoreImageReferences<T>(\n data: T,\n resolveImage: (ref: StoredImageRef) => string,\n): T {\n if (typeof data === 'string') {\n return data;\n }\n\n if (Array.isArray(data)) {\n return data.map((item) => restoreImageReferences(item, resolveImage)) as T;\n }\n\n if (typeof data === 'object' && data !== null) {\n const imageUrlRef = normalizeImageUrlRef(data);\n if (imageUrlRef) {\n return resolveImage(imageUrlRef) as T;\n }\n\n const refLike = normalizeScreenshotRef(data);\n if (refLike) {\n let resolved: string | null = null;\n const lazy: {\n base64: string;\n capturedAt?: number;\n sourceRef: ScreenshotRef;\n } = Object.defineProperties(\n {} as {\n base64: string;\n capturedAt?: number;\n sourceRef: ScreenshotRef;\n },\n {\n base64: {\n get() {\n if (resolved === null) {\n resolved = resolveImage(refLike);\n }\n return resolved;\n },\n enumerable: true,\n },\n capturedAt: { value: refLike.capturedAt, enumerable: true },\n sourceRef: { value: { ...refLike }, enumerable: true },\n },\n );\n return lazy as T;\n }\n\n const result: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(data)) {\n result[key] = restoreImageReferences(value, resolveImage);\n }\n return result as T;\n }\n\n return data;\n}\n"],"names":["restoreImageReferences","data","resolveImage","Array","item","imageUrlRef","normalizeImageUrlRef","refLike","normalizeScreenshotRef","resolved","lazy","Object","result","key","value"],"mappings":";AAaO,SAASA,uBACdC,IAAO,EACPC,YAA6C;IAE7C,IAAI,AAAgB,YAAhB,OAAOD,MACT,OAAOA;IAGT,IAAIE,MAAM,OAAO,CAACF,OAChB,OAAOA,KAAK,GAAG,CAAC,CAACG,OAASJ,uBAAuBI,MAAMF;IAGzD,IAAI,AAAgB,YAAhB,OAAOD,QAAqBA,AAAS,SAATA,MAAe;QAC7C,MAAMI,cAAcC,qBAAqBL;QACzC,IAAII,aACF,OAAOH,aAAaG;QAGtB,MAAME,UAAUC,uBAAuBP;QACvC,IAAIM,SAAS;YACX,IAAIE,WAA0B;YAC9B,MAAMC,OAIFC,OAAO,gBAAgB,CACzB,CAAC,GAKD;gBACE,QAAQ;oBACN;wBACE,IAAIF,AAAa,SAAbA,UACFA,WAAWP,aAAaK;wBAE1B,OAAOE;oBACT;oBACA,YAAY;gBACd;gBACA,YAAY;oBAAE,OAAOF,QAAQ,UAAU;oBAAE,YAAY;gBAAK;gBAC1D,WAAW;oBAAE,OAAO;wBAAE,GAAGA,OAAO;oBAAC;oBAAG,YAAY;gBAAK;YACvD;YAEF,OAAOG;QACT;QAEA,MAAME,SAAkC,CAAC;QACzC,KAAK,MAAM,CAACC,KAAKC,MAAM,IAAIH,OAAO,OAAO,CAACV,MACxCW,MAAM,CAACC,IAAI,GAAGb,uBAAuBc,OAAOZ;QAE9C,OAAOU;IACT;IAEA,OAAOX;AACT"}
|