@midscene/core 1.10.2-beta-20260706032158.0 → 1.10.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/es/agent/agent.mjs +40 -1
- package/dist/es/agent/agent.mjs.map +1 -1
- package/dist/es/agent/metrics.mjs +81 -0
- package/dist/es/agent/metrics.mjs.map +1 -0
- package/dist/es/agent/task-builder.mjs +1 -1
- package/dist/es/agent/task-builder.mjs.map +1 -1
- package/dist/es/agent/utils.mjs +1 -1
- package/dist/es/ai-model/connectivity/run-connectivity-test.mjs +9 -3
- package/dist/es/ai-model/connectivity/run-connectivity-test.mjs.map +1 -1
- package/dist/es/ai-model/inspect.mjs +3 -3
- package/dist/es/ai-model/inspect.mjs.map +1 -1
- package/dist/es/ai-model/llm-planning.mjs +1 -1
- package/dist/es/ai-model/llm-planning.mjs.map +1 -1
- package/dist/es/ai-model/model-adapter/resolve.mjs +2 -2
- package/dist/es/ai-model/model-adapter/resolve.mjs.map +1 -1
- package/dist/es/ai-model/models/default.mjs +2 -1
- package/dist/es/ai-model/models/default.mjs.map +1 -1
- package/dist/es/ai-model/models/doubao.mjs +38 -60
- package/dist/es/ai-model/models/doubao.mjs.map +1 -1
- package/dist/es/ai-model/models/gemini.mjs +4 -15
- package/dist/es/ai-model/models/gemini.mjs.map +1 -1
- package/dist/es/ai-model/models/glm.mjs +4 -0
- package/dist/es/ai-model/models/glm.mjs.map +1 -1
- package/dist/es/ai-model/models/gpt.mjs +5 -1
- package/dist/es/ai-model/models/gpt.mjs.map +1 -1
- package/dist/es/ai-model/models/kimi.mjs +4 -0
- package/dist/es/ai-model/models/kimi.mjs.map +1 -1
- package/dist/es/ai-model/models/mimo.mjs +3 -2
- package/dist/es/ai-model/models/mimo.mjs.map +1 -1
- package/dist/es/ai-model/models/qwen.mjs.map +1 -1
- package/dist/es/ai-model/models/ui-tars/adapter.mjs +2 -41
- package/dist/es/ai-model/models/ui-tars/adapter.mjs.map +1 -1
- package/dist/es/ai-model/models/utils/intent.mjs +6 -0
- package/dist/es/ai-model/models/utils/intent.mjs.map +1 -0
- package/dist/es/ai-model/prompt/describe.mjs +31 -34
- package/dist/es/ai-model/prompt/describe.mjs.map +1 -1
- package/dist/es/ai-model/prompt/extraction.mjs +15 -9
- package/dist/es/ai-model/prompt/extraction.mjs.map +1 -1
- package/dist/es/ai-model/prompt/llm-planning.mjs +19 -19
- package/dist/es/ai-model/prompt/llm-planning.mjs.map +1 -1
- package/dist/es/ai-model/prompt/locate-grounding-rules.mjs +7 -3
- package/dist/es/ai-model/prompt/locate-grounding-rules.mjs.map +1 -1
- package/dist/es/ai-model/prompt/recorder-metadata-generator.mjs +2 -1
- package/dist/es/ai-model/prompt/recorder-metadata-generator.mjs.map +1 -1
- package/dist/es/ai-model/service-caller/index.mjs +40 -19
- package/dist/es/ai-model/service-caller/index.mjs.map +1 -1
- package/dist/es/ai-model/service-caller/json.mjs +31 -28
- package/dist/es/ai-model/service-caller/json.mjs.map +1 -1
- package/dist/es/device/index.mjs +2 -4
- package/dist/es/device/index.mjs.map +1 -1
- package/dist/es/dump/html-utils.mjs +63 -1
- package/dist/es/dump/html-utils.mjs.map +1 -1
- package/dist/es/dump/screenshot-restoration.mjs +6 -0
- package/dist/es/dump/screenshot-restoration.mjs.map +1 -1
- package/dist/es/element-describer.mjs +36 -18
- package/dist/es/element-describer.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 +28 -1
- package/dist/es/report-generator.mjs.map +1 -1
- package/dist/es/report-markdown.mjs +300 -35
- package/dist/es/report-markdown.mjs.map +1 -1
- package/dist/es/report.mjs +29 -1
- package/dist/es/report.mjs.map +1 -1
- package/dist/es/service/index.mjs +101 -25
- package/dist/es/service/index.mjs.map +1 -1
- package/dist/es/service/utils.mjs +65 -1
- package/dist/es/service/utils.mjs.map +1 -1
- package/dist/es/types.mjs.map +1 -1
- package/dist/es/utils.mjs +2 -2
- package/dist/lib/agent/agent.js +40 -1
- package/dist/lib/agent/agent.js.map +1 -1
- package/dist/lib/agent/metrics.js +115 -0
- package/dist/lib/agent/metrics.js.map +1 -0
- package/dist/lib/agent/task-builder.js +1 -1
- package/dist/lib/agent/task-builder.js.map +1 -1
- package/dist/lib/agent/utils.js +1 -1
- package/dist/lib/ai-model/connectivity/run-connectivity-test.js +9 -3
- package/dist/lib/ai-model/connectivity/run-connectivity-test.js.map +1 -1
- package/dist/lib/ai-model/inspect.js +3 -3
- package/dist/lib/ai-model/inspect.js.map +1 -1
- package/dist/lib/ai-model/llm-planning.js +1 -1
- package/dist/lib/ai-model/llm-planning.js.map +1 -1
- package/dist/lib/ai-model/model-adapter/resolve.js +1 -1
- package/dist/lib/ai-model/model-adapter/resolve.js.map +1 -1
- package/dist/lib/ai-model/models/default.js +2 -1
- package/dist/lib/ai-model/models/default.js.map +1 -1
- package/dist/lib/ai-model/models/doubao.js +38 -69
- package/dist/lib/ai-model/models/doubao.js.map +1 -1
- package/dist/lib/ai-model/models/gemini.js +4 -15
- package/dist/lib/ai-model/models/gemini.js.map +1 -1
- package/dist/lib/ai-model/models/glm.js +4 -0
- package/dist/lib/ai-model/models/glm.js.map +1 -1
- package/dist/lib/ai-model/models/gpt.js +5 -1
- package/dist/lib/ai-model/models/gpt.js.map +1 -1
- package/dist/lib/ai-model/models/kimi.js +4 -0
- package/dist/lib/ai-model/models/kimi.js.map +1 -1
- package/dist/lib/ai-model/models/mimo.js +3 -2
- package/dist/lib/ai-model/models/mimo.js.map +1 -1
- package/dist/lib/ai-model/models/qwen.js.map +1 -1
- package/dist/lib/ai-model/models/ui-tars/adapter.js +1 -40
- package/dist/lib/ai-model/models/ui-tars/adapter.js.map +1 -1
- package/dist/lib/ai-model/models/utils/intent.js +40 -0
- package/dist/lib/ai-model/models/utils/intent.js.map +1 -0
- package/dist/lib/ai-model/prompt/describe.js +31 -34
- package/dist/lib/ai-model/prompt/describe.js.map +1 -1
- package/dist/lib/ai-model/prompt/extraction.js +14 -8
- package/dist/lib/ai-model/prompt/extraction.js.map +1 -1
- package/dist/lib/ai-model/prompt/llm-planning.js +19 -19
- package/dist/lib/ai-model/prompt/llm-planning.js.map +1 -1
- package/dist/lib/ai-model/prompt/locate-grounding-rules.js +7 -3
- package/dist/lib/ai-model/prompt/locate-grounding-rules.js.map +1 -1
- package/dist/lib/ai-model/prompt/recorder-metadata-generator.js +4 -3
- package/dist/lib/ai-model/prompt/recorder-metadata-generator.js.map +1 -1
- package/dist/lib/ai-model/service-caller/index.js +44 -23
- package/dist/lib/ai-model/service-caller/index.js.map +1 -1
- package/dist/lib/ai-model/service-caller/json.js +33 -33
- package/dist/lib/ai-model/service-caller/json.js.map +1 -1
- package/dist/lib/device/index.js +2 -4
- package/dist/lib/device/index.js.map +1 -1
- package/dist/lib/dump/html-utils.js +65 -0
- package/dist/lib/dump/html-utils.js.map +1 -1
- package/dist/lib/dump/screenshot-restoration.js +6 -0
- package/dist/lib/dump/screenshot-restoration.js.map +1 -1
- package/dist/lib/element-describer.js +37 -22
- package/dist/lib/element-describer.js.map +1 -1
- package/dist/lib/index.js +1 -4
- package/dist/lib/index.js.map +1 -1
- package/dist/lib/report-generator.js +27 -0
- package/dist/lib/report-generator.js.map +1 -1
- package/dist/lib/report-markdown.js +300 -35
- package/dist/lib/report-markdown.js.map +1 -1
- package/dist/lib/report.js +28 -0
- package/dist/lib/report.js.map +1 -1
- package/dist/lib/service/index.js +99 -23
- package/dist/lib/service/index.js.map +1 -1
- package/dist/lib/service/utils.js +98 -1
- package/dist/lib/service/utils.js.map +1 -1
- package/dist/lib/types.js.map +1 -1
- package/dist/lib/utils.js +2 -2
- package/dist/types/agent/agent.d.ts +15 -2
- package/dist/types/agent/index.d.ts +1 -0
- package/dist/types/agent/metrics.d.ts +48 -0
- package/dist/types/ai-model/model-adapter/types.d.ts +8 -0
- package/dist/types/ai-model/models/doubao.d.ts +1 -4
- package/dist/types/ai-model/models/gemini.d.ts +1 -1
- package/dist/types/ai-model/models/registry.d.ts +1 -1
- package/dist/types/ai-model/models/utils/intent.d.ts +2 -0
- package/dist/types/ai-model/service-caller/index.d.ts +8 -3
- package/dist/types/ai-model/service-caller/json.d.ts +19 -2
- package/dist/types/device/device-options.d.ts +0 -28
- package/dist/types/device/index.d.ts +0 -5
- package/dist/types/dump/html-utils.d.ts +2 -0
- package/dist/types/dump/screenshot-restoration.d.ts +1 -1
- package/dist/types/element-describer.d.ts +3 -6
- package/dist/types/index.d.ts +2 -2
- package/dist/types/report-generator.d.ts +6 -0
- package/dist/types/service/index.d.ts +1 -1
- package/dist/types/service/utils.d.ts +21 -2
- package/dist/types/types.d.ts +12 -2
- package/package.json +3 -3
|
@@ -18,6 +18,68 @@ function __webpack_require__(moduleId) {
|
|
|
18
18
|
var external_node_fs_ = __webpack_require__("node:fs");
|
|
19
19
|
const escapeContent = escapeScriptTag;
|
|
20
20
|
const unescapeContent = antiEscapeScriptTag;
|
|
21
|
+
function cleanHtmlCommentValue(value) {
|
|
22
|
+
return String(value ?? 'N/A').replace(/\0/g, '').replace(/--/g, '- -');
|
|
23
|
+
}
|
|
24
|
+
function formatModelBriefForAgent(brief) {
|
|
25
|
+
const intent = brief.intent || 'default';
|
|
26
|
+
const model = brief.name || 'unknown';
|
|
27
|
+
const description = brief.modelDescription ? ` (${brief.modelDescription})` : '';
|
|
28
|
+
return `${intent}: ${model}${description}`;
|
|
29
|
+
}
|
|
30
|
+
function hasUsageModelInfo(usage) {
|
|
31
|
+
return Boolean(usage && (usage.model_name || usage.response_model_name || usage.model_description || usage.intent));
|
|
32
|
+
}
|
|
33
|
+
function formatUsageModelForAgent(source, usage) {
|
|
34
|
+
const intent = usage.intent || source;
|
|
35
|
+
const model = usage.model_name || usage.response_model_name || 'unknown';
|
|
36
|
+
const description = usage.model_description ? ` (${usage.model_description})` : '';
|
|
37
|
+
return `${intent}: ${model}${description}`;
|
|
38
|
+
}
|
|
39
|
+
function collectUsageModelsForAgent(report) {
|
|
40
|
+
const models = new Map();
|
|
41
|
+
const executions = Array.isArray(report.executions) ? report.executions : [];
|
|
42
|
+
for (const execution of executions){
|
|
43
|
+
const tasks = Array.isArray(execution.tasks) ? execution.tasks : [];
|
|
44
|
+
for (const task of tasks){
|
|
45
|
+
const taskWithUsage = task;
|
|
46
|
+
const usages = [
|
|
47
|
+
[
|
|
48
|
+
'main',
|
|
49
|
+
taskWithUsage.usage
|
|
50
|
+
],
|
|
51
|
+
[
|
|
52
|
+
'searchArea',
|
|
53
|
+
taskWithUsage.searchAreaUsage
|
|
54
|
+
]
|
|
55
|
+
];
|
|
56
|
+
for (const [source, usage] of usages){
|
|
57
|
+
if (!hasUsageModelInfo(usage)) continue;
|
|
58
|
+
const formatted = formatUsageModelForAgent(source, usage);
|
|
59
|
+
models.set(formatted, formatted);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return Array.from(models.values()).join('; ');
|
|
64
|
+
}
|
|
65
|
+
function generateAgentReportComment(report) {
|
|
66
|
+
const executions = Array.isArray(report.executions) ? report.executions : [];
|
|
67
|
+
const taskCount = executions.reduce((sum, execution)=>sum + (Array.isArray(execution.tasks) ? execution.tasks.length : 0), 0);
|
|
68
|
+
const modelBriefs = report.modelBriefs?.length ? report.modelBriefs.map(formatModelBriefForAgent).join('; ') : collectUsageModelsForAgent(report) || 'No model metadata recorded';
|
|
69
|
+
const lines = [
|
|
70
|
+
'For Agent Analysis:',
|
|
71
|
+
`Report: ${cleanHtmlCommentValue(report.groupName)}`,
|
|
72
|
+
`SDK: ${cleanHtmlCommentValue(report.sdkVersion)}`,
|
|
73
|
+
`Device: ${cleanHtmlCommentValue(report.deviceType)}`,
|
|
74
|
+
`Executions: ${executions.length}; Tasks: ${taskCount}`,
|
|
75
|
+
`Models: ${cleanHtmlCommentValue(modelBriefs)}`,
|
|
76
|
+
'Structured report JSON is stored in script[type="midscene_web_dump"] tags near this comment.',
|
|
77
|
+
'Screenshots are stored as script[type="midscene-image"] tags or files referenced by screenshot refs.',
|
|
78
|
+
'For AI analysis, inspect each execution task for type, status, timing, param, output, usage, searchAreaUsage, recorder, and screenshot refs.',
|
|
79
|
+
'Use the Markdown export when available; it contains the same report context plus image links for agent review.'
|
|
80
|
+
];
|
|
81
|
+
return `\n<!--\n${lines.join('\n')}\n-->\n`;
|
|
82
|
+
}
|
|
21
83
|
function htmlScriptCloseTag() {
|
|
22
84
|
return String.fromCharCode(60) + "/script>";
|
|
23
85
|
}
|
|
@@ -293,6 +355,6 @@ function generateDumpScriptTag(json, attributes) {
|
|
|
293
355
|
if (attributes && Object.keys(attributes).length > 0) attrString = ' ' + Object.entries(attributes).map(([k, v])=>k + '="' + encodeURIComponent(v) + '"').join(' ');
|
|
294
356
|
return '<script type="midscene_web_dump"' + attrString + '>' + escapeContent(json) + closeTag;
|
|
295
357
|
}
|
|
296
|
-
export { DATA_SCREENSHOT_MODE_ATTR, STREAMING_CHUNK_SIZE, escapeContent, extractAllDumpScriptsSync, extractImageByIdSync, extractLastDumpScriptSync, generateDumpScriptTag, generateImageScriptTag, getBaseUrlFixScript, parseDumpScript, parseDumpScriptAttributes, parseImageScripts, streamDumpScriptsSync, streamImageScriptsToFile, streamScanTags, unescapeContent };
|
|
358
|
+
export { DATA_SCREENSHOT_MODE_ATTR, STREAMING_CHUNK_SIZE, escapeContent, extractAllDumpScriptsSync, extractImageByIdSync, extractLastDumpScriptSync, generateAgentReportComment, generateDumpScriptTag, generateImageScriptTag, getBaseUrlFixScript, parseDumpScript, parseDumpScriptAttributes, parseImageScripts, streamDumpScriptsSync, streamImageScriptsToFile, streamScanTags, unescapeContent };
|
|
297
359
|
|
|
298
360
|
//# 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';\n\nexport const escapeContent = escapeScriptTag;\nexport const unescapeContent = antiEscapeScriptTag;\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","htmlScriptCloseTag","String","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","value","decodeURIComponent","generateImageScriptTag","data","_baseUrlFixScript","getBaseUrlFixScript","close","DATA_SCREENSHOT_MODE_ATTR","generateDumpScriptTag","json","Object","k","v","encodeURIComponent"],"mappings":";;;;;;;;;;;;;;;;;;AAGO,MAAMA,gBAAgBC;AACtB,MAAMC,kBAAkBC;AAE/B,SAASC;IAEP,OAAOC,OAAO,YAAY,CAAC,MAAM;AACnC;AAGO,MAAMC,uBAAuB;AAkB7B,SAASC,eACdC,QAAgB,EAChBC,OAAe,EACfC,QAAgB,EAChBC,OAAyB;IAEzB,MAAMC,KAAKC,AAAAA,IAAAA,kBAAAA,QAAAA,AAAAA,EAASL,UAAU;IAC9B,MAAMM,WAAWC,AAAAA,IAAAA,kBAAAA,QAAAA,AAAAA,EAASP,UAAU,IAAI;IACxC,MAAMQ,SAASC,OAAO,KAAK,CAACX;IAE5B,IAAIY,WAAW;IACf,IAAIC,WAAW;IACf,IAAIC,YAAY;IAChB,IAAIC,iBAAiB;IAErB,IAAI;QACF,MAAOH,WAAWJ,SAAU;YAC1B,MAAMQ,YAAYC,AAAAA,IAAAA,kBAAAA,QAAAA,AAAAA,EAASX,IAAII,QAAQ,GAAGV,sBAAsBY;YAChE,MAAMM,QAAQL,WAAWH,OAAO,QAAQ,CAAC,SAAS,GAAGM;YACrDJ,YAAYI;YAEZ,IAAIG,cAAc;YAElB,MAAOA,cAAcD,MAAM,MAAM,CAC/B,IAAKJ,WAsBE;gBACL,MAAMM,SAASF,MAAM,OAAO,CAACd,UAAUe;gBACvC,IAAIC,AAAW,OAAXA,QAAe;oBACjBL,kBAAkBG,MAAM,KAAK,CAACC,aAAaC;oBAC3C,MAAMC,aAAahB,QAAQU;oBAC3B,IAAIM,YAAY;oBAChBP,YAAY;oBACZC,iBAAiB;oBACjBI,cAAcC,SAAShB,SAAS,MAAM;gBACxC,OAAO;oBACLW,kBAAkBG,MAAM,KAAK,CAACC,aAAa,CAACf,SAAS,MAAM;oBAC3DS,WAAWK,MAAM,KAAK,CAAC,CAACd,SAAS,MAAM;oBACvC;gBACF;YACF,OApCgB;gBACd,MAAMkB,WAAWJ,MAAM,OAAO,CAACf,SAASgB;gBACxC,IAAIG,AAAa,OAAbA,UAAiB;oBACnBR,YAAY;oBACZC,iBAAiBG,MAAM,KAAK,CAACI,WAAWnB,QAAQ,MAAM;oBACtD,MAAMiB,SAASL,eAAe,OAAO,CAACX;oBACtC,IAAIgB,AAAW,OAAXA,QAAe;wBACjB,MAAMC,aAAahB,QAAQU,eAAe,KAAK,CAAC,GAAGK;wBACnD,IAAIC,YAAY;wBAChBP,YAAY;wBACZC,iBAAiB;wBACjBI,cACEG,WAAWnB,QAAQ,MAAM,GAAGiB,SAAShB,SAAS,MAAM;oBACxD,OAAO;wBACLS,WAAWE,eAAe,KAAK,CAAC,CAACX,SAAS,MAAM;wBAChDW,iBAAiBA,eAAe,KAAK,CAAC,GAAG,CAACX,SAAS,MAAM;wBACzD;oBACF;gBACF,OAAO;oBACLS,WAAWK,MAAM,KAAK,CAAC,CAACf,QAAQ,MAAM;oBACtC;gBACF;YACF;QAgBJ;IACF,SAAU;QACRoB,IAAAA,kBAAAA,SAAAA,AAAAA,EAAUjB;IACZ;AACF;AAUO,SAASkB,qBACdC,QAAgB,EAChBC,OAAe;IAEf,MAAMC,YAAY,CAAC,uCAAuC,EAAED,QAAQ,EAAE,CAAC;IACvE,MAAMtB,WAAWN;IAEjB,IAAI8B,SAAwB;IAE5B3B,eAAewB,UAAUE,WAAWvB,UAAU,CAACyB;QAC7CD,SAAShC,gBAAgBiC;QACzB,OAAO;IACT;IAEA,OAAOD;AACT;AASO,SAASE,yBACdC,WAAmB,EACnBC,YAAoB;IAEpB,MAAM,EAAEC,cAAc,EAAE,GAAGC,oBAAQ;IACnC,MAAM/B,UAAU;IAChB,MAAMC,WAAWN;IAEjBG,eAAe8B,aAAa5B,SAASC,UAAU,CAACyB;QAE9CI,eAAeD,cAAc,GAAG7B,UAAU0B,UAAUzB,SAAS,EAAE,CAAC;QAChE,OAAO;IACT;AACF;AASO,SAAS+B,0BAA0BjC,QAAgB;IACxD,MAAMkC,gBAAgB;IACtB,MAAMhC,WAAWN;IAEjB,IAAIuC,cAAc;IAGlB,MAAM/B,KAAKC,AAAAA,IAAAA,kBAAAA,QAAAA,AAAAA,EAASL,UAAU;IAC9B,MAAMM,WAAWC,AAAAA,IAAAA,kBAAAA,QAAAA,AAAAA,EAASP,UAAU,IAAI;IACxC,MAAMQ,SAASC,OAAO,KAAK,CAACX;IAE5B,IAAIY,WAAW;IACf,IAAIC,WAAW;IACf,IAAIC,YAAY;IAChB,IAAIC,iBAAiB;IAErB,IAAI;QACF,MAAOH,WAAWJ,SAAU;YAC1B,MAAMQ,YAAYC,AAAAA,IAAAA,kBAAAA,QAAAA,AAAAA,EAASX,IAAII,QAAQ,GAAGV,sBAAsBY;YAChE,MAAMM,QAAQL,WAAWH,OAAO,QAAQ,CAAC,SAAS,GAAGM;YACrDJ,YAAYI;YAEZ,IAAIG,cAAc;YAElB,MAAOA,cAAcD,MAAM,MAAM,CAC/B,IAAKJ,WA2BE;gBACL,MAAMM,SAASF,MAAM,OAAO,CAACd,UAAUe;gBACvC,IAAIC,AAAW,OAAXA,QAAe;oBACjBL,kBAAkBG,MAAM,KAAK,CAACC,aAAaC;oBAC3CiB,cAActB,eAAe,IAAI;oBACjCD,YAAY;oBACZC,iBAAiB;oBACjBI,cAAcC,SAAShB,SAAS,MAAM;gBACxC,OAAO;oBACLW,kBAAkBG,MAAM,KAAK,CAACC,aAAa,CAACf,SAAS,MAAM;oBAC3DS,WAAWK,MAAM,KAAK,CAAC,CAACd,SAAS,MAAM;oBACvC;gBACF;YACF,OAxCgB;gBACd,MAAMkB,WAAWJ,MAAM,OAAO,CAACkB,eAAejB;gBAC9C,IAAIG,AAAa,OAAbA,UAAiB;oBAEnB,MAAMgB,YAAYpB,MAAM,OAAO,CAAC,KAAKI;oBACrC,IAAIgB,AAAc,OAAdA,WAAkB;wBACpBxB,YAAY;wBACZC,iBAAiBG,MAAM,KAAK,CAACoB,YAAY;wBACzC,MAAMlB,SAASL,eAAe,OAAO,CAACX;wBACtC,IAAIgB,AAAW,OAAXA,QAAe;4BACjBiB,cAActB,eAAe,KAAK,CAAC,GAAGK,QAAQ,IAAI;4BAClDN,YAAY;4BACZC,iBAAiB;4BACjBI,cAAcmB,YAAY,IAAIlB,SAAShB,SAAS,MAAM;wBACxD,OAAO;4BACLS,WAAWE,eAAe,KAAK,CAAC,CAACX,SAAS,MAAM;4BAChDW,iBAAiBA,eAAe,KAAK,CAAC,GAAG,CAACX,SAAS,MAAM;4BACzD;wBACF;oBACF,OAAO;wBACLS,WAAWK,MAAM,KAAK,CAACI;wBACvB;oBACF;gBACF,OAAO;oBACLT,WAAWK,MAAM,KAAK,CAAC,CAACkB,cAAc,MAAM;oBAC5C;gBACF;YACF;QAeJ;IACF,SAAU;QACRb,IAAAA,kBAAAA,SAAAA,AAAAA,EAAUjB;IACZ;IAEA,OAAO+B;AACT;AASO,SAASE,0BACdrC,QAAgB;IAEhB,MAAMsC,UAAkD,EAAE;IAC1DC,sBAAsBvC,UAAU,CAACwC;QAC/BF,QAAQ,IAAI,CAACE;QACb,OAAO;IACT;IACA,OAAOF;AACT;AAUO,SAASC,sBACdvC,QAAgB,EAChBG,OAAsE;IAEtE,MAAM+B,gBAAgB;IACtB,MAAMhC,WAAWN;IAEjB,MAAMQ,KAAKC,AAAAA,IAAAA,kBAAAA,QAAAA,AAAAA,EAASL,UAAU;IAC9B,MAAMM,WAAWC,AAAAA,IAAAA,kBAAAA,QAAAA,AAAAA,EAASP,UAAU,IAAI;IACxC,MAAMQ,SAASC,OAAO,KAAK,CAACX;IAE5B,IAAIY,WAAW;IACf,IAAIC,WAAW;IACf,IAAIC,YAAY;IAChB,IAAIC,iBAAiB;IACrB,IAAI4B,iBAAiB;IAErB,IAAI;QACF,MAAO/B,WAAWJ,SAAU;YAC1B,MAAMQ,YAAYC,AAAAA,IAAAA,kBAAAA,QAAAA,AAAAA,EAASX,IAAII,QAAQ,GAAGV,sBAAsBY;YAChE,MAAMM,QAAQL,WAAWH,OAAO,QAAQ,CAAC,SAAS,GAAGM;YACrDJ,YAAYI;YAEZ,IAAIG,cAAc;YAElB,MAAOA,cAAcD,MAAM,MAAM,CAC/B,IAAKJ,WAgCE;gBACL,MAAMM,SAASF,MAAM,OAAO,CAACd,UAAUe;gBACvC,IAAIC,AAAW,OAAXA,QAAe;oBACjBL,kBAAkBG,MAAM,KAAK,CAACC,aAAaC;oBAC3C,MAAMC,aAAahB,QAAQ;wBACzB,SAASsC;wBACT,SAAS5B,eAAe,IAAI;oBAC9B;oBACA,IAAIM,YAAY;oBAChBP,YAAY;oBACZC,iBAAiB;oBACjB4B,iBAAiB;oBACjBxB,cAAcC,SAAShB,SAAS,MAAM;gBACxC,OAAO;oBACLW,kBAAkBG,MAAM,KAAK,CAACC,aAAa,CAACf,SAAS,MAAM;oBAC3DS,WAAWK,MAAM,KAAK,CAAC,CAACd,SAAS,MAAM;oBACvC;gBACF;YACF,OAlDgB;gBACd,MAAMkB,WAAWJ,MAAM,OAAO,CAACkB,eAAejB;gBAC9C,IAAIG,AAAa,OAAbA,UAAiB;oBACnB,MAAMgB,YAAYpB,MAAM,OAAO,CAAC,KAAKI;oBACrC,IAAIgB,AAAc,OAAdA,WAAkB;wBACpBxB,YAAY;wBACZ6B,iBAAiBzB,MAAM,KAAK,CAACI,UAAUgB,YAAY;wBACnDvB,iBAAiBG,MAAM,KAAK,CAACoB,YAAY;wBACzC,MAAMlB,SAASL,eAAe,OAAO,CAACX;wBACtC,IAAIgB,AAAW,OAAXA,QAAe;4BACjB,MAAMC,aAAahB,QAAQ;gCACzB,SAASsC;gCACT,SAAS5B,eAAe,KAAK,CAAC,GAAGK,QAAQ,IAAI;4BAC/C;4BACA,IAAIC,YAAY;4BAChBP,YAAY;4BACZC,iBAAiB;4BACjB4B,iBAAiB;4BACjBxB,cAAcmB,YAAY,IAAIlB,SAAShB,SAAS,MAAM;wBACxD,OAAO;4BACLS,WAAWE,eAAe,KAAK,CAAC,CAACX,SAAS,MAAM;4BAChDW,iBAAiBA,eAAe,KAAK,CAAC,GAAG,CAACX,SAAS,MAAM;4BACzD;wBACF;oBACF,OAAO;wBACLS,WAAWK,MAAM,KAAK,CAACI;wBACvB;oBACF;gBACF,OAAO;oBACLT,WAAWK,MAAM,KAAK,CAAC,CAACkB,cAAc,MAAM;oBAC5C;gBACF;YACF;QAoBJ;IACF,SAAU;QACRb,IAAAA,kBAAAA,SAAAA,AAAAA,EAAUjB;IACZ;AACF;AAEO,SAASsC,kBAAkBC,IAAY;IAC5C,MAAMC,WAAmC,CAAC;IAC1C,MAAMC,QACJ;IAEF,KAAK,MAAMC,SAASH,KAAK,QAAQ,CAACE,OAAQ;QACxC,MAAM,GAAGE,IAAIpB,QAAQ,GAAGmB;QACxBF,QAAQ,CAACG,GAAG,GAAGrD,gBAAgBiC;IACjC;IAEA,OAAOiB;AACT;AAEO,SAASI,gBAAgBL,IAAY;IAG1C,MAAMM,gBAAgB;IACtB,MAAM/C,WAAWN;IAGjB,MAAMsD,gBAAgBP,KAAK,WAAW,CAACM;IACvC,IAAIC,AAAkB,OAAlBA,eACF,MAAM,IAAIC,MAAM;IAIlB,MAAMC,cAAcT,KAAK,OAAO,CAAC,KAAKO;IACtC,IAAIE,AAAgB,OAAhBA,aACF,MAAM,IAAID,MAAM;IAIlB,MAAME,aAAaV,KAAK,OAAO,CAACzC,UAAUkD;IAC1C,IAAIC,AAAe,OAAfA,YACF,MAAM,IAAIF,MAAM;IAIlB,MAAMxB,UAAUgB,KAAK,SAAS,CAACS,cAAc,GAAGC;IAChD,OAAO3D,gBAAgBiC;AACzB;AAEO,SAAS2B,0BACdX,IAAY;IAEZ,MAAME,QAAQ;IACd,MAAMC,QAAQD,MAAM,IAAI,CAACF;IAEzB,IAAI,CAACG,OACH,OAAO,CAAC;IAGV,MAAMS,aAAaT,KAAK,CAAC,EAAE;IAC3B,MAAMU,aAAqC,CAAC;IAC5C,MAAMC,YAAY;IAElB,KAAK,MAAMC,aAAaH,WAAW,QAAQ,CAACE,WAAY;QACtD,MAAM,GAAGE,KAAKC,MAAM,GAAGF;QACvB,IAAIC,AAAQ,WAARA,KACFH,UAAU,CAACG,IAAI,GAAGE,mBAAmBD;IAEzC;IAEA,OAAOJ;AACT;AAEO,SAASM,uBAAuBf,EAAU,EAAEgB,IAAY;IAE7D,MAAM7D,WAAWN;IACjB,OAEE,4CACAmD,KACA,OACAvD,cAAcuE,QACd7D;AAEJ;AAoBA,IAAI8D;AACG,SAASC;IACd,IAAI,CAACD,mBAAmB;QACtB,MAAME,QAAQtE;QACdoE,oBAEE,oNAOAE,QACA;IACJ;IACA,OAAOF;AACT;AAQO,MAAMG,4BAA4B;AAElC,SAASC,sBACdC,IAAY,EACZb,UAAsD;IAEtD,MAAMtD,WAAWN;IACjB,IAAI2D,aAAa;IACjB,IAAIC,cAAcc,OAAO,IAAI,CAACd,YAAY,MAAM,GAAG,GAEjDD,aAEE,MACAe,OAAO,OAAO,CAACd,YAEZ,GAAG,CAAC,CAAC,CAACe,GAAGC,EAAE,GAAKD,IAAI,OAAOE,mBAAmBD,KAAK,KACnD,IAAI,CAAC;IAIZ,OAEE,qCACAjB,aACA,MACA/D,cAAc6E,QACdnE;AAEJ"}
|
|
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 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dump/screenshot-restoration.mjs","sources":["../../../src/dump/screenshot-restoration.ts"],"sourcesContent":["import { type ScreenshotRef, normalizeScreenshotRef } from './screenshot-store';\n\n/**\n * Recursively restore image references in parsed data.\n * Replaces ScreenshotRef with lazy\n * { get base64() {...}, capturedAt } objects.\n * The resolver is only called when .base64 is first accessed.\n */\nexport function restoreImageReferences<T>(\n data: T,\n resolveImage: (ref: ScreenshotRef) => 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 refLike = normalizeScreenshotRef(data);\n if (refLike) {\n let resolved: string | null = null;\n const lazy: {
|
|
1
|
+
{"version":3,"file":"dump/screenshot-restoration.mjs","sources":["../../../src/dump/screenshot-restoration.ts"],"sourcesContent":["import { type ScreenshotRef, normalizeScreenshotRef } from './screenshot-store';\n\n/**\n * Recursively restore image references in parsed data.\n * Replaces ScreenshotRef with lazy\n * { get base64() {...}, capturedAt, sourceRef } objects.\n * The resolver is only called when .base64 is first accessed.\n */\nexport function restoreImageReferences<T>(\n data: T,\n resolveImage: (ref: ScreenshotRef) => 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 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","refLike","normalizeScreenshotRef","resolved","lazy","Object","result","key","value"],"mappings":";AAQO,SAASA,uBACdC,IAAO,EACPC,YAA4C;IAE5C,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,UAAUC,uBAAuBL;QACvC,IAAII,SAAS;YACX,IAAIE,WAA0B;YAC9B,MAAMC,OAIFC,OAAO,gBAAgB,CACzB,CAAC,GAKD;gBACE,QAAQ;oBACN;wBACE,IAAIF,AAAa,SAAbA,UACFA,WAAWL,aAAaG;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,CAACR,MACxCS,MAAM,CAACC,IAAI,GAAGX,uBAAuBY,OAAOV;QAE9C,OAAOQ;IACT;IAEA,OAAOT;AACT"}
|
|
@@ -49,6 +49,7 @@ const createScreenshotBoundLocatorContext = async (center, opt)=>{
|
|
|
49
49
|
async function verifyLocator(runtime, prompt, locateOpt, expectCenter, verifyLocateOption) {
|
|
50
50
|
return locateAndVerify(runtime, prompt, expectCenter, {
|
|
51
51
|
centerDistanceThreshold: verifyLocateOption?.centerDistanceThreshold,
|
|
52
|
+
deepLocate: verifyLocateOption?.deepLocate,
|
|
52
53
|
uiContext: locateOpt?.uiContext
|
|
53
54
|
});
|
|
54
55
|
}
|
|
@@ -70,25 +71,37 @@ async function describeElementAtPoint(runtime, center, opt) {
|
|
|
70
71
|
let success = false;
|
|
71
72
|
let retryCount = 0;
|
|
72
73
|
let resultPrompt = '';
|
|
74
|
+
const autoRetryDeepDescribe = opt?.deepDescribe === void 0;
|
|
75
|
+
const autoRetryDeepLocate = opt?.deepLocate === void 0;
|
|
76
|
+
let deepDescribe = opt?.deepDescribe || false;
|
|
73
77
|
let deepLocate = opt?.deepLocate || false;
|
|
74
78
|
let verifyResult;
|
|
75
79
|
let lastError;
|
|
76
80
|
let failureStage;
|
|
77
81
|
while(!success && retryCount < retryLimit){
|
|
78
|
-
if (retryCount >=
|
|
82
|
+
if (retryCount >= 1 && autoRetryDeepDescribe) deepDescribe = true;
|
|
83
|
+
if (retryCount >= 1 && autoRetryDeepLocate) deepLocate = true;
|
|
84
|
+
const describeModelRuntime = runtime.describeModelRuntime;
|
|
85
|
+
const locateModelRuntime = runtime.locateModelRuntime;
|
|
86
|
+
const retryRuntime = {
|
|
87
|
+
...runtime,
|
|
88
|
+
describeModelRuntime,
|
|
89
|
+
locateModelRuntime
|
|
90
|
+
};
|
|
79
91
|
const describeOpt = screenshotContext ? {
|
|
80
|
-
|
|
92
|
+
deepDescribe,
|
|
81
93
|
context: screenshotContext
|
|
82
94
|
} : {
|
|
83
|
-
|
|
95
|
+
deepDescribe
|
|
84
96
|
};
|
|
85
97
|
let text;
|
|
86
98
|
try {
|
|
87
|
-
text = await
|
|
99
|
+
text = await retryRuntime.service.describe(targetCenter, retryRuntime.describeModelRuntime, describeOpt);
|
|
88
100
|
} catch (error) {
|
|
89
101
|
return {
|
|
90
102
|
prompt: resultPrompt,
|
|
91
103
|
deepLocate,
|
|
104
|
+
deepDescribe,
|
|
92
105
|
verifyResult,
|
|
93
106
|
success: false,
|
|
94
107
|
error: errorMessage(error),
|
|
@@ -98,6 +111,7 @@ async function describeElementAtPoint(runtime, center, opt) {
|
|
|
98
111
|
if (!text.description) return {
|
|
99
112
|
prompt: resultPrompt,
|
|
100
113
|
deepLocate,
|
|
114
|
+
deepDescribe,
|
|
101
115
|
verifyResult,
|
|
102
116
|
success: false,
|
|
103
117
|
error: `failed to describe element at [${targetCenter}]`,
|
|
@@ -107,44 +121,50 @@ async function describeElementAtPoint(runtime, center, opt) {
|
|
|
107
121
|
if (!verifyPrompt) {
|
|
108
122
|
opt?.onProgress?.({
|
|
109
123
|
prompt: resultPrompt,
|
|
124
|
+
deepDescribe,
|
|
110
125
|
deepLocate
|
|
111
126
|
});
|
|
112
127
|
success = true;
|
|
113
128
|
break;
|
|
114
129
|
}
|
|
115
130
|
try {
|
|
116
|
-
|
|
131
|
+
const candidateVerifyResult = await verifyLocator(retryRuntime, resultPrompt, locateOpt, targetCenter, {
|
|
132
|
+
...opt,
|
|
133
|
+
deepLocate
|
|
134
|
+
});
|
|
117
135
|
verifyResult = applyLocatorVerifyFn(opt?.locatorVerifyFn, {
|
|
118
136
|
prompt: resultPrompt,
|
|
119
137
|
expectCenter: targetCenter,
|
|
120
|
-
deepLocate,
|
|
121
138
|
retryCount,
|
|
122
|
-
verifyResult
|
|
139
|
+
verifyResult: candidateVerifyResult
|
|
123
140
|
});
|
|
124
141
|
opt?.onProgress?.({
|
|
125
142
|
prompt: resultPrompt,
|
|
143
|
+
deepDescribe,
|
|
126
144
|
deepLocate,
|
|
127
145
|
verifyResult
|
|
128
146
|
});
|
|
129
|
-
if (verifyResult.pass)
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
failureStage = 'verify';
|
|
133
|
-
retryCount++;
|
|
147
|
+
if (verifyResult.pass) {
|
|
148
|
+
success = true;
|
|
149
|
+
break;
|
|
134
150
|
}
|
|
151
|
+
lastError = void 0;
|
|
152
|
+
failureStage = 'verify';
|
|
135
153
|
} catch (error) {
|
|
136
154
|
lastError = errorMessage(error);
|
|
137
155
|
failureStage = 'verify';
|
|
138
156
|
opt?.onProgress?.({
|
|
139
157
|
prompt: resultPrompt,
|
|
158
|
+
deepDescribe,
|
|
140
159
|
deepLocate
|
|
141
160
|
});
|
|
142
|
-
retryCount++;
|
|
143
161
|
}
|
|
162
|
+
retryCount++;
|
|
144
163
|
}
|
|
145
164
|
return {
|
|
146
165
|
prompt: resultPrompt,
|
|
147
166
|
deepLocate,
|
|
167
|
+
deepDescribe,
|
|
148
168
|
verifyResult,
|
|
149
169
|
success,
|
|
150
170
|
error: success || !verifyPrompt ? void 0 : lastError || 'describeElementAtPoint verify failed',
|
|
@@ -156,9 +176,6 @@ async function verifyElementDescriptionAtPoint(runtime, description, center, opt
|
|
|
156
176
|
const { locateOpt, targetCenter } = await createScreenshotBoundLocatorContext(center, opt);
|
|
157
177
|
return verifyLocator(runtime, description, locateOpt, targetCenter, opt);
|
|
158
178
|
}
|
|
159
|
-
async function verifyElementByServiceLocate(runtime, description, center, opt) {
|
|
160
|
-
return locateAndVerify(runtime, description, center, opt);
|
|
161
|
-
}
|
|
162
179
|
async function locateAndVerify(runtime, description, center, opt) {
|
|
163
180
|
assert(description?.trim(), "description must not be empty");
|
|
164
181
|
const { screenshotContext, targetCenter } = await createScreenshotBoundLocatorContext(center, opt);
|
|
@@ -173,8 +190,9 @@ async function locateAndVerify(runtime, description, center, opt) {
|
|
|
173
190
|
context
|
|
174
191
|
} : {}, runtime.locateModelRuntime, opt?.abortSignal);
|
|
175
192
|
assert(locateResult.element, `Element not found: ${description}`);
|
|
176
|
-
|
|
193
|
+
const verifyResult = buildLocateValidatorResult(targetCenter, locateResult.element, opt);
|
|
194
|
+
return verifyResult;
|
|
177
195
|
}
|
|
178
|
-
export { describeElementAtPoint,
|
|
196
|
+
export { describeElementAtPoint, verifyElementDescriptionAtPoint, verifyLocator };
|
|
179
197
|
|
|
180
198
|
//# sourceMappingURL=element-describer.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"element-describer.mjs","sources":["../../src/element-describer.ts"],"sourcesContent":["import type { ModelRuntime } from '@/ai-model/models';\nimport { assert } from '@midscene/shared/utils';\nimport { createScreenshotBoundUIContext } from './agent/utils';\nimport type Service from './service';\nimport type {\n AgentDescribeElementAtPointResult,\n LocateOption,\n LocateResultElement,\n LocateValidatorResult,\n LocatorValidatorOption,\n Rect,\n Size,\n UIContext,\n} from './types';\nimport { buildDetailedLocateParam } from './yaml/utils';\n\nexport type DescribeElementCoordinateSpace = 'screenshot' | 'logical';\n\nexport type LocatorVerifyFn = (input: {\n prompt: string;\n expectCenter: [number, number];\n deepLocate: boolean;\n retryCount: number;\n verifyResult: LocateValidatorResult;\n}) => LocateValidatorResult | boolean;\n\nexport type DescribeElementAtPointOptions = {\n verifyPrompt?: boolean;\n retryLimit?: number;\n deepLocate?: boolean;\n locatorVerifyFn?: LocatorVerifyFn;\n screenshotBase64?: string;\n screenshotSize?: Size;\n coordinateSpace?: DescribeElementCoordinateSpace;\n logicalSize?: Size;\n onProgress?: (progress: {\n prompt?: string;\n deepLocate?: boolean;\n verifyResult?: LocateValidatorResult;\n }) => void;\n} & LocatorValidatorOption;\n\ntype ScreenshotBoundContextOptions = {\n screenshotBase64?: string;\n screenshotSize?: Size;\n coordinateSpace?: DescribeElementCoordinateSpace;\n logicalSize?: Size;\n};\n\nexport type VerifyElementDescriptionAtPointOptions =\n ScreenshotBoundContextOptions & LocatorValidatorOption;\n\nexport type VerifyElementByServiceLocateOptions =\n VerifyElementDescriptionAtPointOptions &\n Pick<LocateOption, 'cacheable' | 'deepLocate' | 'xpath'> & {\n abortSignal?: AbortSignal;\n };\n\nexport type ElementDescriberRuntime = {\n service: Pick<Service, 'describe' | 'locate'>;\n describeModelRuntime: ModelRuntime;\n locateModelRuntime: ModelRuntime;\n};\n\nconst distanceOfTwoPoints = (p1: [number, number], p2: [number, number]) => {\n const [x1, y1] = p1;\n const [x2, y2] = p2;\n return Math.round(Math.sqrt((x1 - x2) ** 2 + (y1 - y2) ** 2));\n};\n\nconst includedInRect = (point: [number, number], rect: Rect) => {\n const [x, y] = point;\n const { left, top, width, height } = rect;\n return x >= left && x <= left + width && y >= top && y <= top + height;\n};\n\nconst buildLocateValidatorResult = (\n expectCenter: [number, number],\n located: Pick<LocateResultElement, 'center' | 'rect'>,\n verifyLocateOption?: LocatorValidatorOption,\n): LocateValidatorResult => {\n const distance = distanceOfTwoPoints(expectCenter, located.center);\n const included = includedInRect(expectCenter, located.rect);\n const pass =\n distance <= (verifyLocateOption?.centerDistanceThreshold || 20) || included;\n return {\n pass,\n rect: located.rect,\n center: located.center,\n centerDistance: distance,\n includedInRect: included,\n };\n};\n\nfunction assertPositiveSize(\n size: Size | undefined,\n label: string,\n): asserts size is Size {\n assert(\n size &&\n Number.isFinite(size.width) &&\n Number.isFinite(size.height) &&\n size.width > 0 &&\n size.height > 0,\n `${label} must include positive width and height`,\n );\n}\n\nconst mapPointToScreenshotSpace = (\n center: [number, number],\n screenshotSize: Size,\n opt: ScreenshotBoundContextOptions,\n): [number, number] => {\n const coordinateSpace = opt.coordinateSpace || 'screenshot';\n if (coordinateSpace === 'screenshot') {\n return center;\n }\n\n assertPositiveSize(\n opt.logicalSize,\n 'logicalSize is required when coordinateSpace is logical',\n );\n return [\n (center[0] * screenshotSize.width) / opt.logicalSize.width,\n (center[1] * screenshotSize.height) / opt.logicalSize.height,\n ];\n};\n\nconst createScreenshotBoundLocatorContext = async (\n center: [number, number],\n opt?: ScreenshotBoundContextOptions,\n): Promise<{\n screenshotContext?: UIContext;\n locateOpt?: LocateOption;\n targetCenter: [number, number];\n}> => {\n const screenshotContext = opt?.screenshotBase64\n ? await createScreenshotBoundUIContext(opt.screenshotBase64, opt)\n : undefined;\n const targetCenter = screenshotContext\n ? mapPointToScreenshotSpace(center, screenshotContext.shotSize, opt || {})\n : center;\n return {\n screenshotContext,\n locateOpt: screenshotContext ? { uiContext: screenshotContext } : undefined,\n targetCenter,\n };\n};\n\nexport async function verifyLocator(\n runtime: Pick<ElementDescriberRuntime, 'service' | 'locateModelRuntime'>,\n prompt: string,\n locateOpt: LocateOption | undefined,\n expectCenter: [number, number],\n verifyLocateOption?: LocatorValidatorOption,\n): Promise<LocateValidatorResult> {\n return locateAndVerify(runtime, prompt, expectCenter, {\n centerDistanceThreshold: verifyLocateOption?.centerDistanceThreshold,\n uiContext: locateOpt?.uiContext,\n });\n}\n\nfunction applyLocatorVerifyFn(\n locatorVerifyFn: LocatorVerifyFn | undefined,\n input: Parameters<LocatorVerifyFn>[0],\n): LocateValidatorResult {\n if (!locatorVerifyFn) {\n return input.verifyResult;\n }\n const customResult = locatorVerifyFn(input);\n if (typeof customResult === 'boolean') {\n return {\n ...input.verifyResult,\n pass: customResult,\n };\n }\n return customResult;\n}\n\nfunction errorMessage(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n\nexport async function describeElementAtPoint(\n runtime: ElementDescriberRuntime,\n center: [number, number],\n opt?: DescribeElementAtPointOptions,\n): Promise<AgentDescribeElementAtPointResult> {\n const { verifyPrompt = true, retryLimit = 3 } = opt || {};\n const { screenshotContext, locateOpt, targetCenter } =\n await createScreenshotBoundLocatorContext(center, opt);\n\n let success = false;\n let retryCount = 0;\n let resultPrompt = '';\n let deepLocate = opt?.deepLocate || false;\n let verifyResult: LocateValidatorResult | undefined;\n let lastError: string | undefined;\n let failureStage: AgentDescribeElementAtPointResult['failureStage'];\n\n while (!success && retryCount < retryLimit) {\n if (retryCount >= 2) {\n deepLocate = true;\n }\n const describeOpt = screenshotContext\n ? { deepLocate, context: screenshotContext }\n : { deepLocate };\n\n let text: Awaited<\n ReturnType<ElementDescriberRuntime['service']['describe']>\n >;\n try {\n text = await runtime.service.describe(\n targetCenter,\n runtime.describeModelRuntime,\n describeOpt,\n );\n } catch (error) {\n return {\n prompt: resultPrompt,\n deepLocate,\n verifyResult,\n success: false,\n error: errorMessage(error),\n failureStage: 'describe',\n };\n }\n if (!text.description) {\n return {\n prompt: resultPrompt,\n deepLocate,\n verifyResult,\n success: false,\n error: `failed to describe element at [${targetCenter}]`,\n failureStage: 'describe',\n };\n }\n resultPrompt = text.description;\n\n if (!verifyPrompt) {\n opt?.onProgress?.({ prompt: resultPrompt, deepLocate });\n success = true;\n break;\n }\n\n try {\n verifyResult = await verifyLocator(\n runtime,\n resultPrompt,\n locateOpt,\n targetCenter,\n opt,\n );\n verifyResult = applyLocatorVerifyFn(opt?.locatorVerifyFn, {\n prompt: resultPrompt,\n expectCenter: targetCenter,\n deepLocate,\n retryCount,\n verifyResult,\n });\n opt?.onProgress?.({ prompt: resultPrompt, deepLocate, verifyResult });\n if (verifyResult.pass) {\n success = true;\n } else {\n lastError = undefined;\n failureStage = 'verify';\n retryCount++;\n }\n } catch (error) {\n lastError = errorMessage(error);\n failureStage = 'verify';\n opt?.onProgress?.({ prompt: resultPrompt, deepLocate });\n retryCount++;\n }\n }\n\n return {\n prompt: resultPrompt,\n deepLocate,\n verifyResult,\n success,\n error:\n success || !verifyPrompt\n ? undefined\n : lastError || 'describeElementAtPoint verify failed',\n failureStage: success ? undefined : failureStage,\n };\n}\n\nexport async function verifyElementDescriptionAtPoint(\n runtime: ElementDescriberRuntime,\n description: string,\n center: [number, number],\n opt?: VerifyElementDescriptionAtPointOptions,\n): Promise<LocateValidatorResult> {\n assert(description?.trim(), 'description must not be empty');\n const { locateOpt, targetCenter } = await createScreenshotBoundLocatorContext(\n center,\n opt,\n );\n\n return verifyLocator(runtime, description, locateOpt, targetCenter, opt);\n}\n\nexport async function verifyElementByServiceLocate(\n runtime: Pick<ElementDescriberRuntime, 'service' | 'locateModelRuntime'>,\n description: string,\n center: [number, number],\n opt?: VerifyElementByServiceLocateOptions,\n): Promise<LocateValidatorResult> {\n return locateAndVerify(runtime, description, center, opt);\n}\n\nasync function locateAndVerify(\n runtime: Pick<ElementDescriberRuntime, 'service' | 'locateModelRuntime'>,\n description: string,\n center: [number, number],\n opt?: VerifyElementByServiceLocateOptions & { uiContext?: UIContext },\n): Promise<LocateValidatorResult> {\n assert(description?.trim(), 'description must not be empty');\n const { screenshotContext, targetCenter } =\n await createScreenshotBoundLocatorContext(center, opt);\n const context = opt?.uiContext || screenshotContext;\n const locateParam = buildDetailedLocateParam(description, {\n cacheable: opt?.cacheable,\n deepLocate: opt?.deepLocate,\n xpath: opt?.xpath,\n });\n assert(locateParam, 'cannot get locate param for service locate');\n\n const locateResult = await runtime.service.locate(\n locateParam,\n context ? { context } : {},\n runtime.locateModelRuntime,\n opt?.abortSignal,\n );\n assert(locateResult.element, `Element not found: ${description}`);\n return buildLocateValidatorResult(targetCenter, locateResult.element, opt);\n}\n"],"names":["distanceOfTwoPoints","p1","p2","x1","y1","x2","y2","Math","includedInRect","point","rect","x","y","left","top","width","height","buildLocateValidatorResult","expectCenter","located","verifyLocateOption","distance","included","pass","assertPositiveSize","size","label","assert","Number","mapPointToScreenshotSpace","center","screenshotSize","opt","coordinateSpace","createScreenshotBoundLocatorContext","screenshotContext","createScreenshotBoundUIContext","undefined","targetCenter","verifyLocator","runtime","prompt","locateOpt","locateAndVerify","applyLocatorVerifyFn","locatorVerifyFn","input","customResult","errorMessage","error","Error","String","describeElementAtPoint","verifyPrompt","retryLimit","success","retryCount","resultPrompt","deepLocate","verifyResult","lastError","failureStage","describeOpt","text","verifyElementDescriptionAtPoint","description","verifyElementByServiceLocate","context","locateParam","buildDetailedLocateParam","locateResult"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"element-describer.mjs","sources":["../../src/element-describer.ts"],"sourcesContent":["import type { ModelRuntime } from '@/ai-model/models';\nimport { assert } from '@midscene/shared/utils';\nimport { createScreenshotBoundUIContext } from './agent/utils';\nimport type Service from './service';\nimport type {\n AgentDescribeElementAtPointResult,\n LocateOption,\n LocateResultElement,\n LocateValidatorResult,\n LocatorValidatorOption,\n Rect,\n Size,\n UIContext,\n} from './types';\nimport { buildDetailedLocateParam } from './yaml/utils';\n\nexport type DescribeElementCoordinateSpace = 'screenshot' | 'logical';\n\nexport type LocatorVerifyFn = (input: {\n prompt: string;\n expectCenter: [number, number];\n retryCount: number;\n verifyResult: LocateValidatorResult;\n}) => LocateValidatorResult | boolean;\n\nexport type DescribeElementAtPointOptions = {\n verifyPrompt?: boolean;\n retryLimit?: number;\n deepDescribe?: boolean;\n deepLocate?: boolean;\n locatorVerifyFn?: LocatorVerifyFn;\n screenshotBase64?: string;\n screenshotSize?: Size;\n coordinateSpace?: DescribeElementCoordinateSpace;\n logicalSize?: Size;\n onProgress?: (progress: {\n prompt?: string;\n deepDescribe?: boolean;\n deepLocate?: boolean;\n verifyResult?: LocateValidatorResult;\n }) => void;\n} & LocatorValidatorOption;\n\ntype ScreenshotBoundContextOptions = {\n screenshotBase64?: string;\n screenshotSize?: Size;\n coordinateSpace?: DescribeElementCoordinateSpace;\n logicalSize?: Size;\n};\n\nexport type VerifyElementDescriptionAtPointOptions =\n ScreenshotBoundContextOptions & LocatorValidatorOption;\n\ntype LocateAndVerifyOptions = VerifyElementDescriptionAtPointOptions &\n Pick<LocateOption, 'cacheable' | 'deepLocate' | 'xpath'> & {\n abortSignal?: AbortSignal;\n };\n\nexport type ElementDescriberRuntime = {\n service: Pick<Service, 'describe' | 'locate'>;\n describeModelRuntime: ModelRuntime;\n locateModelRuntime: ModelRuntime;\n};\n\ntype ServiceDescribeOptions = NonNullable<\n Parameters<ElementDescriberRuntime['service']['describe']>[2]\n>;\n\nconst distanceOfTwoPoints = (p1: [number, number], p2: [number, number]) => {\n const [x1, y1] = p1;\n const [x2, y2] = p2;\n return Math.round(Math.sqrt((x1 - x2) ** 2 + (y1 - y2) ** 2));\n};\n\nconst includedInRect = (point: [number, number], rect: Rect) => {\n const [x, y] = point;\n const { left, top, width, height } = rect;\n return x >= left && x <= left + width && y >= top && y <= top + height;\n};\n\nconst buildLocateValidatorResult = (\n expectCenter: [number, number],\n located: Pick<LocateResultElement, 'center' | 'rect'>,\n verifyLocateOption?: LocatorValidatorOption,\n): LocateValidatorResult => {\n const distance = distanceOfTwoPoints(expectCenter, located.center);\n const included = includedInRect(expectCenter, located.rect);\n const pass =\n distance <= (verifyLocateOption?.centerDistanceThreshold || 20) || included;\n return {\n pass,\n rect: located.rect,\n center: located.center,\n centerDistance: distance,\n includedInRect: included,\n };\n};\n\nfunction assertPositiveSize(\n size: Size | undefined,\n label: string,\n): asserts size is Size {\n assert(\n size &&\n Number.isFinite(size.width) &&\n Number.isFinite(size.height) &&\n size.width > 0 &&\n size.height > 0,\n `${label} must include positive width and height`,\n );\n}\n\nconst mapPointToScreenshotSpace = (\n center: [number, number],\n screenshotSize: Size,\n opt: ScreenshotBoundContextOptions,\n): [number, number] => {\n const coordinateSpace = opt.coordinateSpace || 'screenshot';\n if (coordinateSpace === 'screenshot') {\n return center;\n }\n\n assertPositiveSize(\n opt.logicalSize,\n 'logicalSize is required when coordinateSpace is logical',\n );\n return [\n (center[0] * screenshotSize.width) / opt.logicalSize.width,\n (center[1] * screenshotSize.height) / opt.logicalSize.height,\n ];\n};\n\nconst createScreenshotBoundLocatorContext = async (\n center: [number, number],\n opt?: ScreenshotBoundContextOptions,\n): Promise<{\n screenshotContext?: UIContext;\n locateOpt?: LocateOption;\n targetCenter: [number, number];\n}> => {\n const screenshotContext = opt?.screenshotBase64\n ? await createScreenshotBoundUIContext(opt.screenshotBase64, opt)\n : undefined;\n const targetCenter = screenshotContext\n ? mapPointToScreenshotSpace(center, screenshotContext.shotSize, opt || {})\n : center;\n return {\n screenshotContext,\n locateOpt: screenshotContext ? { uiContext: screenshotContext } : undefined,\n targetCenter,\n };\n};\n\nexport async function verifyLocator(\n runtime: Pick<ElementDescriberRuntime, 'service' | 'locateModelRuntime'>,\n prompt: string,\n locateOpt: LocateOption | undefined,\n expectCenter: [number, number],\n verifyLocateOption?: LocatorValidatorOption &\n Pick<LocateOption, 'deepLocate'>,\n): Promise<LocateValidatorResult> {\n return locateAndVerify(runtime, prompt, expectCenter, {\n centerDistanceThreshold: verifyLocateOption?.centerDistanceThreshold,\n deepLocate: verifyLocateOption?.deepLocate,\n uiContext: locateOpt?.uiContext,\n });\n}\n\nfunction applyLocatorVerifyFn(\n locatorVerifyFn: LocatorVerifyFn | undefined,\n input: Parameters<LocatorVerifyFn>[0],\n): LocateValidatorResult {\n if (!locatorVerifyFn) {\n return input.verifyResult;\n }\n const customResult = locatorVerifyFn(input);\n if (typeof customResult === 'boolean') {\n return {\n ...input.verifyResult,\n pass: customResult,\n };\n }\n return customResult;\n}\n\nfunction errorMessage(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n\nexport async function describeElementAtPoint(\n runtime: ElementDescriberRuntime,\n center: [number, number],\n opt?: DescribeElementAtPointOptions,\n): Promise<AgentDescribeElementAtPointResult> {\n const { verifyPrompt = true, retryLimit = 3 } = opt || {};\n const { screenshotContext, locateOpt, targetCenter } =\n await createScreenshotBoundLocatorContext(center, opt);\n\n let success = false;\n let retryCount = 0;\n let resultPrompt = '';\n const autoRetryDeepDescribe = opt?.deepDescribe === undefined;\n const autoRetryDeepLocate = opt?.deepLocate === undefined;\n let deepDescribe = opt?.deepDescribe || false;\n let deepLocate = opt?.deepLocate || false;\n let verifyResult: LocateValidatorResult | undefined;\n let lastError: string | undefined;\n let failureStage: AgentDescribeElementAtPointResult['failureStage'];\n\n while (!success && retryCount < retryLimit) {\n if (retryCount >= 1 && autoRetryDeepDescribe) {\n deepDescribe = true;\n }\n if (retryCount >= 1 && autoRetryDeepLocate) {\n deepLocate = true;\n }\n const describeModelRuntime = runtime.describeModelRuntime;\n const locateModelRuntime = runtime.locateModelRuntime;\n const retryRuntime: ElementDescriberRuntime = {\n ...runtime,\n describeModelRuntime,\n locateModelRuntime,\n };\n const describeOpt: ServiceDescribeOptions = screenshotContext\n ? {\n deepDescribe,\n context: screenshotContext,\n }\n : {\n deepDescribe,\n };\n let text: Awaited<\n ReturnType<ElementDescriberRuntime['service']['describe']>\n >;\n try {\n text = await retryRuntime.service.describe(\n targetCenter,\n retryRuntime.describeModelRuntime,\n describeOpt,\n );\n } catch (error) {\n return {\n prompt: resultPrompt,\n deepLocate,\n deepDescribe,\n verifyResult,\n success: false,\n error: errorMessage(error),\n failureStage: 'describe',\n };\n }\n if (!text.description) {\n return {\n prompt: resultPrompt,\n deepLocate,\n deepDescribe,\n verifyResult,\n success: false,\n error: `failed to describe element at [${targetCenter}]`,\n failureStage: 'describe',\n };\n }\n resultPrompt = text.description;\n if (!verifyPrompt) {\n opt?.onProgress?.({\n prompt: resultPrompt,\n deepDescribe,\n deepLocate,\n });\n success = true;\n break;\n }\n\n try {\n const candidateVerifyResult = await verifyLocator(\n retryRuntime,\n resultPrompt,\n locateOpt,\n targetCenter,\n {\n ...opt,\n deepLocate,\n },\n );\n verifyResult = applyLocatorVerifyFn(opt?.locatorVerifyFn, {\n prompt: resultPrompt,\n expectCenter: targetCenter,\n retryCount,\n verifyResult: candidateVerifyResult,\n });\n opt?.onProgress?.({\n prompt: resultPrompt,\n deepDescribe,\n deepLocate,\n verifyResult,\n });\n if (verifyResult.pass) {\n success = true;\n break;\n }\n lastError = undefined;\n failureStage = 'verify';\n } catch (error) {\n lastError = errorMessage(error);\n failureStage = 'verify';\n opt?.onProgress?.({\n prompt: resultPrompt,\n deepDescribe,\n deepLocate,\n });\n }\n retryCount++;\n }\n\n return {\n prompt: resultPrompt,\n deepLocate,\n deepDescribe,\n verifyResult,\n success,\n error:\n success || !verifyPrompt\n ? undefined\n : lastError || 'describeElementAtPoint verify failed',\n failureStage: success ? undefined : failureStage,\n };\n}\n\nexport async function verifyElementDescriptionAtPoint(\n runtime: ElementDescriberRuntime,\n description: string,\n center: [number, number],\n opt?: VerifyElementDescriptionAtPointOptions,\n): Promise<LocateValidatorResult> {\n assert(description?.trim(), 'description must not be empty');\n const { locateOpt, targetCenter } = await createScreenshotBoundLocatorContext(\n center,\n opt,\n );\n\n return verifyLocator(runtime, description, locateOpt, targetCenter, opt);\n}\n\nasync function locateAndVerify(\n runtime: Pick<ElementDescriberRuntime, 'service' | 'locateModelRuntime'>,\n description: string,\n center: [number, number],\n opt?: LocateAndVerifyOptions & {\n uiContext?: UIContext;\n },\n): Promise<LocateValidatorResult> {\n assert(description?.trim(), 'description must not be empty');\n const { screenshotContext, targetCenter } =\n await createScreenshotBoundLocatorContext(center, opt);\n const context = opt?.uiContext || screenshotContext;\n const locateParam = buildDetailedLocateParam(description, {\n cacheable: opt?.cacheable,\n deepLocate: opt?.deepLocate,\n xpath: opt?.xpath,\n });\n assert(locateParam, 'cannot get locate param for service locate');\n\n const locateResult = await runtime.service.locate(\n locateParam,\n context ? { context } : {},\n runtime.locateModelRuntime,\n opt?.abortSignal,\n );\n assert(locateResult.element, `Element not found: ${description}`);\n const verifyResult = buildLocateValidatorResult(\n targetCenter,\n locateResult.element,\n opt,\n );\n return verifyResult;\n}\n"],"names":["distanceOfTwoPoints","p1","p2","x1","y1","x2","y2","Math","includedInRect","point","rect","x","y","left","top","width","height","buildLocateValidatorResult","expectCenter","located","verifyLocateOption","distance","included","pass","assertPositiveSize","size","label","assert","Number","mapPointToScreenshotSpace","center","screenshotSize","opt","coordinateSpace","createScreenshotBoundLocatorContext","screenshotContext","createScreenshotBoundUIContext","undefined","targetCenter","verifyLocator","runtime","prompt","locateOpt","locateAndVerify","applyLocatorVerifyFn","locatorVerifyFn","input","customResult","errorMessage","error","Error","String","describeElementAtPoint","verifyPrompt","retryLimit","success","retryCount","resultPrompt","autoRetryDeepDescribe","autoRetryDeepLocate","deepDescribe","deepLocate","verifyResult","lastError","failureStage","describeModelRuntime","locateModelRuntime","retryRuntime","describeOpt","text","candidateVerifyResult","verifyElementDescriptionAtPoint","description","context","locateParam","buildDetailedLocateParam","locateResult"],"mappings":"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"}
|
package/dist/es/index.mjs
CHANGED
|
@@ -6,7 +6,7 @@ import { AiLocateElement, PointSchema, RectSchema, SizeSchema, TMultimodalPrompt
|
|
|
6
6
|
import { MIDSCENE_MODEL_NAME } from "@midscene/shared/env";
|
|
7
7
|
import { ExecutionDump, GroupedActionDump, ReportActionDump, ServiceError } from "./types.mjs";
|
|
8
8
|
import { Agent, createAgent } from "./agent/index.mjs";
|
|
9
|
-
import { describeElementAtPoint,
|
|
9
|
+
import { describeElementAtPoint, verifyElementDescriptionAtPoint, verifyLocator } from "./element-describer.mjs";
|
|
10
10
|
import { deriveCaseStatus, deriveTaskStatus, escapeContent, generateDumpScriptTag, generateImageScriptTag, parseDumpScript, parseDumpScriptAttributes, parseImageScripts, restoreImageReferences, unescapeContent } from "./dump/index.mjs";
|
|
11
11
|
import { getTaskSearchArea, getTaskServiceDump } from "./dump/task-service-dump.mjs";
|
|
12
12
|
import { ReportGenerator, nullReportGenerator } from "./report-generator.mjs";
|
|
@@ -16,6 +16,6 @@ import { ScreenshotItem } from "./screenshot-item.mjs";
|
|
|
16
16
|
import { ScreenshotStore } from "./dump/screenshot-store.mjs";
|
|
17
17
|
import { executionToMarkdown, reportToMarkdown } from "./report-markdown.mjs";
|
|
18
18
|
const src = service;
|
|
19
|
-
export { Agent, AiLocateElement, ExecutionDump, GroupedActionDump, MIDSCENE_MODEL_NAME, PointSchema, RectSchema, ReportActionDump, ReportGenerator, ReportMergingTool, ScreenshotItem, ScreenshotStore, service as Service, ServiceError, SizeSchema, TMultimodalPromptSchema, TUserPromptSchema, TaskRunner, collectDedupedExecutions, createAgent, createReportCliCommands, dedupeExecutionsKeepLatest, src as default, deriveCaseStatus, deriveTaskStatus, describeElementAtPoint, escapeContent, executionToMarkdown, generateDumpScriptTag, generateImageScriptTag, getMidsceneLocationSchema, getTaskSearchArea, getTaskServiceDump, getVersion, mergeReportFiles, nullReportGenerator, parseDumpScript, parseDumpScriptAttributes, parseImageScripts, plan, reportFileToMarkdown, reportToMarkdown, restoreImageReferences, runConnectivityTest, splitReportFile, splitReportHtmlByExecution, unescapeContent,
|
|
19
|
+
export { Agent, AiLocateElement, ExecutionDump, GroupedActionDump, MIDSCENE_MODEL_NAME, PointSchema, RectSchema, ReportActionDump, ReportGenerator, ReportMergingTool, ScreenshotItem, ScreenshotStore, service as Service, ServiceError, SizeSchema, TMultimodalPromptSchema, TUserPromptSchema, TaskRunner, collectDedupedExecutions, createAgent, createReportCliCommands, dedupeExecutionsKeepLatest, src as default, deriveCaseStatus, deriveTaskStatus, describeElementAtPoint, escapeContent, executionToMarkdown, generateDumpScriptTag, generateImageScriptTag, getMidsceneLocationSchema, getTaskSearchArea, getTaskServiceDump, getVersion, mergeReportFiles, nullReportGenerator, parseDumpScript, parseDumpScriptAttributes, parseImageScripts, plan, reportFileToMarkdown, reportToMarkdown, restoreImageReferences, runConnectivityTest, splitReportFile, splitReportHtmlByExecution, unescapeContent, verifyElementDescriptionAtPoint, verifyLocator, z };
|
|
20
20
|
|
|
21
21
|
//# sourceMappingURL=index.mjs.map
|
package/dist/es/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","sources":["../../src/index.ts"],"sourcesContent":["import { z } from 'zod';\nimport Service from './service/index';\nimport { TaskRunner } from './task-runner';\nimport { getVersion } from './utils';\n\nexport {\n plan,\n AiLocateElement,\n runConnectivityTest,\n getMidsceneLocationSchema,\n PointSchema,\n SizeSchema,\n RectSchema,\n TMultimodalPromptSchema,\n TUserPromptSchema,\n type TMultimodalPrompt,\n type TUserPrompt,\n type ConnectivityTestConfig,\n type ConnectivityTestResult,\n} from './ai-model/index';\n\nexport {\n MIDSCENE_MODEL_NAME,\n type CreateOpenAIClientFn,\n} from '@midscene/shared/env';\n\nexport type * from './types';\nexport {\n ServiceError,\n ExecutionDump,\n ReportActionDump,\n GroupedActionDump,\n type IExecutionDump,\n type IReportActionDump,\n type IGroupedActionDump,\n type ReportMeta,\n type GroupMeta,\n} from './types';\n\nexport { z };\n\nexport default Service;\nexport { TaskRunner, Service, getVersion };\n\nexport type {\n MidsceneYamlScript,\n MidsceneYamlTask,\n MidsceneYamlFlowItem,\n MidsceneYamlConfigResult,\n MidsceneYamlConfig,\n MidsceneYamlScriptWebEnv,\n MidsceneYamlScriptAndroidEnv,\n MidsceneYamlScriptIOSEnv,\n MidsceneYamlScriptEnv,\n LocateOption,\n DetailedLocateParam,\n} from './yaml';\n\nexport {\n Agent,\n type AgentOpt,\n type AiActOptions,\n type GherkinStepKeyword,\n type RunGherkinScenarioOptions,\n createAgent,\n} from './agent';\nexport {\n describeElementAtPoint,\n verifyElementDescriptionAtPoint,\n
|
|
1
|
+
{"version":3,"file":"index.mjs","sources":["../../src/index.ts"],"sourcesContent":["import { z } from 'zod';\nimport Service from './service/index';\nimport { TaskRunner } from './task-runner';\nimport { getVersion } from './utils';\n\nexport {\n plan,\n AiLocateElement,\n runConnectivityTest,\n getMidsceneLocationSchema,\n PointSchema,\n SizeSchema,\n RectSchema,\n TMultimodalPromptSchema,\n TUserPromptSchema,\n type TMultimodalPrompt,\n type TUserPrompt,\n type ConnectivityTestConfig,\n type ConnectivityTestResult,\n} from './ai-model/index';\n\nexport {\n MIDSCENE_MODEL_NAME,\n type CreateOpenAIClientFn,\n} from '@midscene/shared/env';\n\nexport type * from './types';\nexport {\n ServiceError,\n ExecutionDump,\n ReportActionDump,\n GroupedActionDump,\n type IExecutionDump,\n type IReportActionDump,\n type IGroupedActionDump,\n type ReportMeta,\n type GroupMeta,\n} from './types';\n\nexport { z };\n\nexport default Service;\nexport { TaskRunner, Service, getVersion };\n\nexport type {\n MidsceneYamlScript,\n MidsceneYamlTask,\n MidsceneYamlFlowItem,\n MidsceneYamlConfigResult,\n MidsceneYamlConfig,\n MidsceneYamlScriptWebEnv,\n MidsceneYamlScriptAndroidEnv,\n MidsceneYamlScriptIOSEnv,\n MidsceneYamlScriptEnv,\n LocateOption,\n DetailedLocateParam,\n} from './yaml';\n\nexport {\n Agent,\n type AgentOpt,\n type AiActOptions,\n type GherkinStepKeyword,\n type MidsceneUsageMetrics,\n type RunGherkinScenarioOptions,\n type UsageBucket,\n createAgent,\n} from './agent';\nexport {\n describeElementAtPoint,\n verifyElementDescriptionAtPoint,\n verifyLocator,\n type DescribeElementAtPointOptions,\n type DescribeElementCoordinateSpace,\n type ElementDescriberRuntime,\n type VerifyElementDescriptionAtPointOptions,\n} from './element-describer';\n\n// Dump utilities\nexport {\n restoreImageReferences,\n escapeContent,\n unescapeContent,\n parseImageScripts,\n parseDumpScript,\n parseDumpScriptAttributes,\n generateImageScriptTag,\n generateDumpScriptTag,\n deriveTaskStatus,\n deriveCaseStatus,\n} from './dump';\nexport type { TaskStatusFields, DerivedTaskStatus } from './dump';\nexport {\n getTaskSearchArea,\n getTaskServiceDump,\n} from './dump/task-service-dump';\n\n// Report generator\nexport type { IReportGenerator } from './report-generator';\nexport { ReportGenerator, nullReportGenerator } from './report-generator';\nexport {\n collectDedupedExecutions,\n ReportMergingTool,\n dedupeExecutionsKeepLatest,\n splitReportHtmlByExecution,\n} from './report';\nexport {\n createReportCliCommands,\n reportFileToMarkdown,\n splitReportFile,\n mergeReportFiles,\n type ConsumeReportFileAction,\n type ReportFileToMarkdownOptions,\n type ReportCliCommandDefinition,\n type ReportCliCommandEntry,\n type SplitReportFileOptions,\n type MergeReportFilesOptions,\n type MergeReportFilesResult,\n} from './report-cli';\n\n// ScreenshotItem\nexport { ScreenshotItem } from './screenshot-item';\nexport { ScreenshotStore, type ScreenshotRef } from './dump/screenshot-store';\n\nexport {\n executionToMarkdown,\n reportToMarkdown,\n type ExecutionMarkdownOptions,\n type ExecutionMarkdownResult,\n type ReportMarkdownResult,\n type MarkdownAttachment,\n} from './report-markdown';\n"],"names":["Service"],"mappings":";;;;;;;;;;;;;;;;;AAyCA,YAAeA"}
|
|
@@ -4,7 +4,7 @@ import { dirname, join } from "node:path";
|
|
|
4
4
|
import { getMidsceneRunSubDir } from "@midscene/shared/common";
|
|
5
5
|
import { MIDSCENE_REPORT_QUIET, globalConfigManager } from "@midscene/shared/env";
|
|
6
6
|
import { ifInBrowser, logMsg, uuid } from "@midscene/shared/utils";
|
|
7
|
-
import { DATA_SCREENSHOT_MODE_ATTR, generateDumpScriptTag, generateImageScriptTag, getBaseUrlFixScript } from "./dump/html-utils.mjs";
|
|
7
|
+
import { DATA_SCREENSHOT_MODE_ATTR, generateAgentReportComment, generateDumpScriptTag, generateImageScriptTag, getBaseUrlFixScript } from "./dump/html-utils.mjs";
|
|
8
8
|
import { ScreenshotStore } from "./dump/screenshot-store.mjs";
|
|
9
9
|
import { ReportActionDump } from "./types.mjs";
|
|
10
10
|
import { getReportTpl } from "./utils.mjs";
|
|
@@ -47,6 +47,7 @@ class ReportGenerator {
|
|
|
47
47
|
onExecutionUpdate(execution, reportMeta, attributes) {
|
|
48
48
|
this.lastExecution = execution;
|
|
49
49
|
this.lastReportMeta = reportMeta;
|
|
50
|
+
this.executionsByKey.set(this.getExecutionCommentKey(execution), execution);
|
|
50
51
|
this.mergeReportAttributes(attributes);
|
|
51
52
|
this.writeQueue = this.writeQueue.then(async ()=>{
|
|
52
53
|
if (this.destroyed) return;
|
|
@@ -61,6 +62,7 @@ class ReportGenerator {
|
|
|
61
62
|
await this.flush();
|
|
62
63
|
this.destroyed = true;
|
|
63
64
|
if (!this.initialized) return;
|
|
65
|
+
await this.appendAgentReportComment();
|
|
64
66
|
return this.reportPath;
|
|
65
67
|
}
|
|
66
68
|
getReportPath() {
|
|
@@ -138,10 +140,31 @@ class ReportGenerator {
|
|
|
138
140
|
}
|
|
139
141
|
await appendFile(this.reportPath, `\n${generateDumpScriptTag(serialized, this.getDumpScriptAttributes())}`);
|
|
140
142
|
}
|
|
143
|
+
async appendAgentReportComment() {
|
|
144
|
+
if (this.agentCommentWritten || !this.lastReportMeta || 0 === this.executionsByKey.size) return;
|
|
145
|
+
const reportDump = new ReportActionDump({
|
|
146
|
+
sdkVersion: this.lastReportMeta.sdkVersion,
|
|
147
|
+
groupName: this.lastReportMeta.groupName,
|
|
148
|
+
groupDescription: this.lastReportMeta.groupDescription,
|
|
149
|
+
modelBriefs: this.lastReportMeta.modelBriefs,
|
|
150
|
+
deviceType: this.lastReportMeta.deviceType,
|
|
151
|
+
executions: Array.from(this.executionsByKey.values())
|
|
152
|
+
});
|
|
153
|
+
await appendFile(this.reportPath, `\n${generateAgentReportComment(reportDump)}`);
|
|
154
|
+
this.agentCommentWritten = true;
|
|
155
|
+
}
|
|
141
156
|
getExecutionLogKey(execution) {
|
|
142
157
|
if (!execution.id) throw new Error('ReportGenerator: execution.id is required for persisting execution dumps');
|
|
143
158
|
return `id:${execution.id}`;
|
|
144
159
|
}
|
|
160
|
+
getExecutionCommentKey(execution) {
|
|
161
|
+
if (execution.id) return `id:${execution.id}`;
|
|
162
|
+
const existingKey = this.executionCommentKeyByObject.get(execution);
|
|
163
|
+
if (existingKey) return existingKey;
|
|
164
|
+
const key = `no-id:${this.executionCommentKeyIndex++}`;
|
|
165
|
+
this.executionCommentKeyByObject.set(execution, key);
|
|
166
|
+
return key;
|
|
167
|
+
}
|
|
145
168
|
async persistExecutionDumpToFile(execution, singleDump) {
|
|
146
169
|
const dir = dirname(this.reportPath);
|
|
147
170
|
if (!existsSync(dir)) mkdirSync(dir, {
|
|
@@ -171,7 +194,11 @@ class ReportGenerator {
|
|
|
171
194
|
_define_property(this, "initialized", false);
|
|
172
195
|
_define_property(this, "lastExecution", void 0);
|
|
173
196
|
_define_property(this, "lastReportMeta", void 0);
|
|
197
|
+
_define_property(this, "executionsByKey", new Map());
|
|
198
|
+
_define_property(this, "executionCommentKeyByObject", new WeakMap());
|
|
199
|
+
_define_property(this, "executionCommentKeyIndex", 0);
|
|
174
200
|
_define_property(this, "reportAttributes", {});
|
|
201
|
+
_define_property(this, "agentCommentWritten", false);
|
|
175
202
|
_define_property(this, "writeQueue", Promise.resolve());
|
|
176
203
|
_define_property(this, "destroyed", false);
|
|
177
204
|
this.reportPath = options.reportPath;
|