@godscene/core 1.7.11
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/LICENSE +21 -0
- package/README.md +9 -0
- package/dist/es/agent/agent.mjs +767 -0
- package/dist/es/agent/common.mjs +0 -0
- package/dist/es/agent/execution-session.mjs +39 -0
- package/dist/es/agent/index.mjs +6 -0
- package/dist/es/agent/task-builder.mjs +343 -0
- package/dist/es/agent/task-cache.mjs +212 -0
- package/dist/es/agent/tasks.mjs +428 -0
- package/dist/es/agent/ui-utils.mjs +101 -0
- package/dist/es/agent/utils.mjs +167 -0
- package/dist/es/ai-model/auto-glm/actions.mjs +237 -0
- package/dist/es/ai-model/auto-glm/index.mjs +6 -0
- package/dist/es/ai-model/auto-glm/parser.mjs +237 -0
- package/dist/es/ai-model/auto-glm/planning.mjs +69 -0
- package/dist/es/ai-model/auto-glm/prompt.mjs +220 -0
- package/dist/es/ai-model/auto-glm/util.mjs +7 -0
- package/dist/es/ai-model/connectivity.mjs +136 -0
- package/dist/es/ai-model/conversation-history.mjs +193 -0
- package/dist/es/ai-model/index.mjs +12 -0
- package/dist/es/ai-model/inspect.mjs +395 -0
- package/dist/es/ai-model/llm-planning.mjs +231 -0
- package/dist/es/ai-model/prompt/common.mjs +5 -0
- package/dist/es/ai-model/prompt/describe.mjs +64 -0
- package/dist/es/ai-model/prompt/extraction.mjs +129 -0
- package/dist/es/ai-model/prompt/llm-locator.mjs +49 -0
- package/dist/es/ai-model/prompt/llm-planning.mjs +584 -0
- package/dist/es/ai-model/prompt/llm-section-locator.mjs +42 -0
- package/dist/es/ai-model/prompt/order-sensitive-judge.mjs +33 -0
- package/dist/es/ai-model/prompt/playwright-generator.mjs +115 -0
- package/dist/es/ai-model/prompt/ui-tars-planning.mjs +34 -0
- package/dist/es/ai-model/prompt/util.mjs +57 -0
- package/dist/es/ai-model/prompt/yaml-generator.mjs +201 -0
- package/dist/es/ai-model/service-caller/codex-app-server.mjs +573 -0
- package/dist/es/ai-model/service-caller/image-detail.mjs +4 -0
- package/dist/es/ai-model/service-caller/index.mjs +648 -0
- package/dist/es/ai-model/service-caller/request-timeout.mjs +47 -0
- package/dist/es/ai-model/ui-tars-planning.mjs +247 -0
- package/dist/es/common.mjs +382 -0
- package/dist/es/device/device-options.mjs +0 -0
- package/dist/es/device/index.mjs +340 -0
- package/dist/es/dump/html-utils.mjs +290 -0
- package/dist/es/dump/index.mjs +3 -0
- package/dist/es/dump/screenshot-restoration.mjs +30 -0
- package/dist/es/dump/screenshot-store.mjs +125 -0
- package/dist/es/index.mjs +17 -0
- package/dist/es/report-cli.mjs +149 -0
- package/dist/es/report-generator.mjs +203 -0
- package/dist/es/report-markdown.mjs +216 -0
- package/dist/es/report.mjs +287 -0
- package/dist/es/screenshot-item.mjs +120 -0
- package/dist/es/service/index.mjs +272 -0
- package/dist/es/service/utils.mjs +13 -0
- package/dist/es/skill/index.mjs +35 -0
- package/dist/es/task-runner.mjs +261 -0
- package/dist/es/task-timing.mjs +10 -0
- package/dist/es/tree.mjs +11 -0
- package/dist/es/types.mjs +202 -0
- package/dist/es/utils.mjs +232 -0
- package/dist/es/yaml/builder.mjs +11 -0
- package/dist/es/yaml/index.mjs +4 -0
- package/dist/es/yaml/player.mjs +425 -0
- package/dist/es/yaml/utils.mjs +100 -0
- package/dist/es/yaml.mjs +0 -0
- package/dist/lib/agent/agent.js +815 -0
- package/dist/lib/agent/common.js +5 -0
- package/dist/lib/agent/execution-session.js +73 -0
- package/dist/lib/agent/index.js +76 -0
- package/dist/lib/agent/task-builder.js +380 -0
- package/dist/lib/agent/task-cache.js +264 -0
- package/dist/lib/agent/tasks.js +471 -0
- package/dist/lib/agent/ui-utils.js +153 -0
- package/dist/lib/agent/utils.js +238 -0
- package/dist/lib/ai-model/auto-glm/actions.js +271 -0
- package/dist/lib/ai-model/auto-glm/index.js +64 -0
- package/dist/lib/ai-model/auto-glm/parser.js +280 -0
- package/dist/lib/ai-model/auto-glm/planning.js +103 -0
- package/dist/lib/ai-model/auto-glm/prompt.js +257 -0
- package/dist/lib/ai-model/auto-glm/util.js +44 -0
- package/dist/lib/ai-model/connectivity.js +180 -0
- package/dist/lib/ai-model/conversation-history.js +227 -0
- package/dist/lib/ai-model/index.js +127 -0
- package/dist/lib/ai-model/inspect.js +441 -0
- package/dist/lib/ai-model/llm-planning.js +268 -0
- package/dist/lib/ai-model/prompt/common.js +39 -0
- package/dist/lib/ai-model/prompt/describe.js +98 -0
- package/dist/lib/ai-model/prompt/extraction.js +169 -0
- package/dist/lib/ai-model/prompt/llm-locator.js +86 -0
- package/dist/lib/ai-model/prompt/llm-planning.js +621 -0
- package/dist/lib/ai-model/prompt/llm-section-locator.js +79 -0
- package/dist/lib/ai-model/prompt/order-sensitive-judge.js +70 -0
- package/dist/lib/ai-model/prompt/playwright-generator.js +176 -0
- package/dist/lib/ai-model/prompt/ui-tars-planning.js +71 -0
- package/dist/lib/ai-model/prompt/util.js +103 -0
- package/dist/lib/ai-model/prompt/yaml-generator.js +262 -0
- package/dist/lib/ai-model/service-caller/codex-app-server.js +622 -0
- package/dist/lib/ai-model/service-caller/image-detail.js +38 -0
- package/dist/lib/ai-model/service-caller/index.js +716 -0
- package/dist/lib/ai-model/service-caller/request-timeout.js +93 -0
- package/dist/lib/ai-model/ui-tars-planning.js +281 -0
- package/dist/lib/common.js +491 -0
- package/dist/lib/device/device-options.js +18 -0
- package/dist/lib/device/index.js +467 -0
- package/dist/lib/dump/html-utils.js +366 -0
- package/dist/lib/dump/index.js +58 -0
- package/dist/lib/dump/screenshot-restoration.js +64 -0
- package/dist/lib/dump/screenshot-store.js +165 -0
- package/dist/lib/index.js +184 -0
- package/dist/lib/report-cli.js +189 -0
- package/dist/lib/report-generator.js +244 -0
- package/dist/lib/report-markdown.js +253 -0
- package/dist/lib/report.js +333 -0
- package/dist/lib/screenshot-item.js +154 -0
- package/dist/lib/service/index.js +306 -0
- package/dist/lib/service/utils.js +47 -0
- package/dist/lib/skill/index.js +69 -0
- package/dist/lib/task-runner.js +298 -0
- package/dist/lib/task-timing.js +44 -0
- package/dist/lib/tree.js +51 -0
- package/dist/lib/types.js +298 -0
- package/dist/lib/utils.js +314 -0
- package/dist/lib/yaml/builder.js +55 -0
- package/dist/lib/yaml/index.js +79 -0
- package/dist/lib/yaml/player.js +459 -0
- package/dist/lib/yaml/utils.js +153 -0
- package/dist/lib/yaml.js +18 -0
- package/dist/types/agent/agent.d.ts +220 -0
- package/dist/types/agent/common.d.ts +0 -0
- package/dist/types/agent/execution-session.d.ts +36 -0
- package/dist/types/agent/index.d.ts +9 -0
- package/dist/types/agent/task-builder.d.ts +34 -0
- package/dist/types/agent/task-cache.d.ts +49 -0
- package/dist/types/agent/tasks.d.ts +70 -0
- package/dist/types/agent/ui-utils.d.ts +14 -0
- package/dist/types/agent/utils.d.ts +25 -0
- package/dist/types/ai-model/auto-glm/actions.d.ts +78 -0
- package/dist/types/ai-model/auto-glm/index.d.ts +6 -0
- package/dist/types/ai-model/auto-glm/parser.d.ts +18 -0
- package/dist/types/ai-model/auto-glm/planning.d.ts +12 -0
- package/dist/types/ai-model/auto-glm/prompt.d.ts +27 -0
- package/dist/types/ai-model/auto-glm/util.d.ts +13 -0
- package/dist/types/ai-model/connectivity.d.ts +20 -0
- package/dist/types/ai-model/conversation-history.d.ts +105 -0
- package/dist/types/ai-model/index.d.ts +16 -0
- package/dist/types/ai-model/inspect.d.ts +67 -0
- package/dist/types/ai-model/llm-planning.d.ts +19 -0
- package/dist/types/ai-model/prompt/common.d.ts +2 -0
- package/dist/types/ai-model/prompt/describe.d.ts +1 -0
- package/dist/types/ai-model/prompt/extraction.d.ts +7 -0
- package/dist/types/ai-model/prompt/llm-locator.d.ts +3 -0
- package/dist/types/ai-model/prompt/llm-planning.d.ts +10 -0
- package/dist/types/ai-model/prompt/llm-section-locator.d.ts +3 -0
- package/dist/types/ai-model/prompt/order-sensitive-judge.d.ts +2 -0
- package/dist/types/ai-model/prompt/playwright-generator.d.ts +26 -0
- package/dist/types/ai-model/prompt/ui-tars-planning.d.ts +2 -0
- package/dist/types/ai-model/prompt/util.d.ts +33 -0
- package/dist/types/ai-model/prompt/yaml-generator.d.ts +102 -0
- package/dist/types/ai-model/service-caller/codex-app-server.d.ts +42 -0
- package/dist/types/ai-model/service-caller/image-detail.d.ts +2 -0
- package/dist/types/ai-model/service-caller/index.d.ts +60 -0
- package/dist/types/ai-model/service-caller/request-timeout.d.ts +32 -0
- package/dist/types/ai-model/ui-tars-planning.d.ts +72 -0
- package/dist/types/common.d.ts +288 -0
- package/dist/types/device/device-options.d.ts +155 -0
- package/dist/types/device/index.d.ts +2565 -0
- package/dist/types/dump/html-utils.d.ts +75 -0
- package/dist/types/dump/index.d.ts +5 -0
- package/dist/types/dump/screenshot-restoration.d.ts +8 -0
- package/dist/types/dump/screenshot-store.d.ts +49 -0
- package/dist/types/index.d.ts +21 -0
- package/dist/types/report-cli.d.ts +36 -0
- package/dist/types/report-generator.d.ts +88 -0
- package/dist/types/report-markdown.d.ts +24 -0
- package/dist/types/report.d.ts +52 -0
- package/dist/types/screenshot-item.d.ts +67 -0
- package/dist/types/service/index.d.ts +24 -0
- package/dist/types/service/utils.d.ts +2 -0
- package/dist/types/skill/index.d.ts +25 -0
- package/dist/types/task-runner.d.ts +50 -0
- package/dist/types/task-timing.d.ts +8 -0
- package/dist/types/tree.d.ts +4 -0
- package/dist/types/types.d.ts +684 -0
- package/dist/types/utils.d.ts +45 -0
- package/dist/types/yaml/builder.d.ts +2 -0
- package/dist/types/yaml/index.d.ts +4 -0
- package/dist/types/yaml/player.d.ts +34 -0
- package/dist/types/yaml/utils.d.ts +9 -0
- package/dist/types/yaml.d.ts +215 -0
- package/package.json +130 -0
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, rmSync } from "node:fs";
|
|
2
|
+
import { writeFile } from "node:fs/promises";
|
|
3
|
+
import { dirname, isAbsolute, join } from "node:path";
|
|
4
|
+
import { extractImageByIdSync } from "./html-utils.mjs";
|
|
5
|
+
function _define_property(obj, key, value) {
|
|
6
|
+
if (key in obj) Object.defineProperty(obj, key, {
|
|
7
|
+
value: value,
|
|
8
|
+
enumerable: true,
|
|
9
|
+
configurable: true,
|
|
10
|
+
writable: true
|
|
11
|
+
});
|
|
12
|
+
else obj[key] = value;
|
|
13
|
+
return obj;
|
|
14
|
+
}
|
|
15
|
+
function normalizeScreenshotRef(value) {
|
|
16
|
+
if ('object' != typeof value || null === value) return null;
|
|
17
|
+
const record = value;
|
|
18
|
+
if ('midscene_screenshot_ref' === record.type && 'string' == typeof record.id && 'number' == typeof record.capturedAt && ('inline' === record.storage || 'file' === record.storage) && ('image/png' === record.mimeType || 'image/jpeg' === record.mimeType)) {
|
|
19
|
+
if ('file' === record.storage && 'string' != typeof record.path) return null;
|
|
20
|
+
return record;
|
|
21
|
+
}
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
function extensionByMimeType(mimeType) {
|
|
25
|
+
return 'image/jpeg' === mimeType ? 'jpeg' : 'png';
|
|
26
|
+
}
|
|
27
|
+
function resolveScreenshotSource(refInput, options) {
|
|
28
|
+
const ref = normalizeScreenshotRef(refInput);
|
|
29
|
+
const id = ref?.id ?? options.fallbackId;
|
|
30
|
+
const mimeType = ref?.mimeType ?? options.fallbackMimeType;
|
|
31
|
+
if (!id || !mimeType) throw new Error('ScreenshotStore: screenshot id and mimeType are required to resolve screenshot');
|
|
32
|
+
const resolveReportRelativePath = (filePath)=>isAbsolute(filePath) ? filePath : join(dirname(options.reportPath), filePath);
|
|
33
|
+
if (ref?.storage === 'file') {
|
|
34
|
+
if (!ref.path) throw new Error(`ScreenshotStore: screenshot ref "${ref.id}" missing file path`);
|
|
35
|
+
const explicitFilePath = resolveReportRelativePath(ref.path);
|
|
36
|
+
if (existsSync(explicitFilePath)) return {
|
|
37
|
+
type: 'file',
|
|
38
|
+
id,
|
|
39
|
+
mimeType,
|
|
40
|
+
filePath: explicitFilePath
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
const inlineDataUri = extractImageByIdSync(options.reportPath, id);
|
|
44
|
+
if (inlineDataUri) return {
|
|
45
|
+
type: 'data-uri',
|
|
46
|
+
id,
|
|
47
|
+
mimeType,
|
|
48
|
+
dataUri: inlineDataUri
|
|
49
|
+
};
|
|
50
|
+
const siblingScreenshotPath = join(dirname(options.reportPath), 'screenshots', `${id}.${extensionByMimeType(mimeType)}`);
|
|
51
|
+
if (existsSync(siblingScreenshotPath)) return {
|
|
52
|
+
type: 'file',
|
|
53
|
+
id,
|
|
54
|
+
mimeType,
|
|
55
|
+
filePath: siblingScreenshotPath
|
|
56
|
+
};
|
|
57
|
+
throw new Error(`ScreenshotStore: cannot resolve screenshot "${id}" from ${options.reportPath}`);
|
|
58
|
+
}
|
|
59
|
+
class ScreenshotStore {
|
|
60
|
+
async persist(screenshot) {
|
|
61
|
+
const shouldWriteFileCopy = 'directory' === this.mode || this.alsoWriteFileCopy;
|
|
62
|
+
const fileRef = shouldWriteFileCopy ? await this.persistToSharedFileIfNeeded(screenshot, {
|
|
63
|
+
markAsPersisted: 'directory' === this.mode
|
|
64
|
+
}) : null;
|
|
65
|
+
if ('inline' === this.mode) {
|
|
66
|
+
if (!this.writeInlineImage) throw new Error('ScreenshotStore: writeInlineImage is required in inline mode');
|
|
67
|
+
if (!this.writtenInlineIds.has(screenshot.id)) {
|
|
68
|
+
await this.writeInlineImage(screenshot.id, screenshot.base64);
|
|
69
|
+
this.writtenInlineIds.add(screenshot.id);
|
|
70
|
+
}
|
|
71
|
+
return screenshot.markPersistedInline(this.reportPath);
|
|
72
|
+
}
|
|
73
|
+
if (!fileRef) throw new Error('ScreenshotStore: file persistence is required in directory mode');
|
|
74
|
+
return fileRef;
|
|
75
|
+
}
|
|
76
|
+
async persistToSharedFileIfNeeded(screenshot, options) {
|
|
77
|
+
const screenshotsDir = this.screenshotsDir;
|
|
78
|
+
if (!screenshotsDir) throw new Error('ScreenshotStore: screenshotsDir is required when file persistence is enabled');
|
|
79
|
+
if (!existsSync(screenshotsDir)) mkdirSync(screenshotsDir, {
|
|
80
|
+
recursive: true
|
|
81
|
+
});
|
|
82
|
+
const relativePath = `./screenshots/${screenshot.id}.${screenshot.extension}`;
|
|
83
|
+
const absolutePath = join(screenshotsDir, `${screenshot.id}.${screenshot.extension}`);
|
|
84
|
+
if (!this.writtenFileIds.has(screenshot.id)) {
|
|
85
|
+
const buffer = Buffer.from(screenshot.rawBase64, 'base64');
|
|
86
|
+
await writeFile(absolutePath, buffer);
|
|
87
|
+
this.writtenFileIds.add(screenshot.id);
|
|
88
|
+
}
|
|
89
|
+
if (options.markAsPersisted) return screenshot.markPersistedToPath(relativePath, absolutePath);
|
|
90
|
+
return screenshot.registerPersistedFileCopy(relativePath, absolutePath);
|
|
91
|
+
}
|
|
92
|
+
loadBase64(refInput) {
|
|
93
|
+
const ref = normalizeScreenshotRef(refInput);
|
|
94
|
+
if (!ref) throw new Error('ScreenshotStore: invalid screenshot reference');
|
|
95
|
+
const resolved = resolveScreenshotSource(ref, {
|
|
96
|
+
reportPath: this.reportPath
|
|
97
|
+
});
|
|
98
|
+
if ('data-uri' === resolved.type) return resolved.dataUri;
|
|
99
|
+
const data = readFileSync(resolved.filePath);
|
|
100
|
+
return `data:${resolved.mimeType};base64,${data.toString('base64')}`;
|
|
101
|
+
}
|
|
102
|
+
cleanup() {
|
|
103
|
+
if ('directory' === this.mode && this.screenshotsDir && existsSync(this.screenshotsDir)) rmSync(this.screenshotsDir, {
|
|
104
|
+
recursive: true,
|
|
105
|
+
force: true
|
|
106
|
+
});
|
|
107
|
+
this.writtenInlineIds.clear();
|
|
108
|
+
this.writtenFileIds.clear();
|
|
109
|
+
}
|
|
110
|
+
constructor(options){
|
|
111
|
+
_define_property(this, "mode", void 0);
|
|
112
|
+
_define_property(this, "reportPath", void 0);
|
|
113
|
+
_define_property(this, "screenshotsDir", void 0);
|
|
114
|
+
_define_property(this, "writeInlineImage", void 0);
|
|
115
|
+
_define_property(this, "alsoWriteFileCopy", void 0);
|
|
116
|
+
_define_property(this, "writtenInlineIds", new Set());
|
|
117
|
+
_define_property(this, "writtenFileIds", new Set());
|
|
118
|
+
this.mode = options.mode;
|
|
119
|
+
this.reportPath = options.reportPath;
|
|
120
|
+
this.screenshotsDir = options.screenshotsDir;
|
|
121
|
+
this.writeInlineImage = options.writeInlineImage;
|
|
122
|
+
this.alsoWriteFileCopy = options.alsoWriteFileCopy ?? options.ensureFileCopy ?? false;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
export { ScreenshotStore, normalizeScreenshotRef, resolveScreenshotSource };
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import service from "./service/index.mjs";
|
|
3
|
+
import { TaskRunner } from "./task-runner.mjs";
|
|
4
|
+
import { getVersion } from "./utils.mjs";
|
|
5
|
+
import { AiLocateElement, PointSchema, RectSchema, SizeSchema, TMultimodalPromptSchema, TUserPromptSchema, getMidsceneLocationSchema, plan, runConnectivityTest } from "./ai-model/index.mjs";
|
|
6
|
+
import { MIDSCENE_MODEL_NAME } from "@godscene/shared/env";
|
|
7
|
+
import { ExecutionDump, GroupedActionDump, ReportActionDump, ServiceError } from "./types.mjs";
|
|
8
|
+
import { Agent, createAgent } from "./agent/index.mjs";
|
|
9
|
+
import { escapeContent, generateDumpScriptTag, generateImageScriptTag, parseDumpScript, parseDumpScriptAttributes, parseImageScripts, restoreImageReferences, unescapeContent } from "./dump/index.mjs";
|
|
10
|
+
import { ReportGenerator, nullReportGenerator } from "./report-generator.mjs";
|
|
11
|
+
import { ReportMergingTool, collectDedupedExecutions, dedupeExecutionsKeepLatest, splitReportHtmlByExecution } from "./report.mjs";
|
|
12
|
+
import { createReportCliCommands, reportFileToMarkdown, splitReportFile } from "./report-cli.mjs";
|
|
13
|
+
import { ScreenshotItem } from "./screenshot-item.mjs";
|
|
14
|
+
import { ScreenshotStore } from "./dump/screenshot-store.mjs";
|
|
15
|
+
import { executionToMarkdown, reportToMarkdown } from "./report-markdown.mjs";
|
|
16
|
+
const src = service;
|
|
17
|
+
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, escapeContent, executionToMarkdown, generateDumpScriptTag, generateImageScriptTag, getMidsceneLocationSchema, getVersion, nullReportGenerator, parseDumpScript, parseDumpScriptAttributes, parseImageScripts, plan, reportFileToMarkdown, reportToMarkdown, restoreImageReferences, runConnectivityTest, splitReportFile, splitReportHtmlByExecution, unescapeContent, z };
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { copyFileSync, existsSync, mkdirSync, statSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { join, resolve } from "node:path";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import { resolveScreenshotSource } from "./dump/screenshot-store.mjs";
|
|
5
|
+
import { collectDedupedExecutions, splitReportHtmlByExecution } from "./report.mjs";
|
|
6
|
+
import { reportToMarkdown } from "./report-markdown.mjs";
|
|
7
|
+
import { ReportActionDump } from "./types.mjs";
|
|
8
|
+
function writeAttachmentFromReport(attachment, opts) {
|
|
9
|
+
const { suggestedFileName, id, mimeType } = attachment;
|
|
10
|
+
if (opts.writtenFiles.has(suggestedFileName)) return;
|
|
11
|
+
const absolutePath = join(opts.screenshotsDir, suggestedFileName);
|
|
12
|
+
const outputRelativePath = `./screenshots/${suggestedFileName}`;
|
|
13
|
+
const sourceRef = attachment.filePath !== outputRelativePath ? {
|
|
14
|
+
type: 'midscene_screenshot_ref',
|
|
15
|
+
id,
|
|
16
|
+
capturedAt: 0,
|
|
17
|
+
mimeType: mimeType || 'image/png',
|
|
18
|
+
storage: 'file',
|
|
19
|
+
path: attachment.filePath
|
|
20
|
+
} : null;
|
|
21
|
+
const resolved = resolveScreenshotSource(sourceRef, {
|
|
22
|
+
reportPath: opts.htmlPath,
|
|
23
|
+
fallbackId: id,
|
|
24
|
+
fallbackMimeType: mimeType || 'image/png'
|
|
25
|
+
});
|
|
26
|
+
if ('data-uri' === resolved.type) {
|
|
27
|
+
const rawBase64 = resolved.dataUri.replace(/^data:image\/[a-zA-Z+]+;base64,/, '');
|
|
28
|
+
writeFileSync(absolutePath, Buffer.from(rawBase64, 'base64'));
|
|
29
|
+
opts.writtenFiles.add(suggestedFileName);
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
if (!existsSync(resolved.filePath)) throw new Error(`Cannot resolve screenshot "${id}" for markdown attachment from ${opts.htmlPath}`);
|
|
33
|
+
copyFileSync(resolved.filePath, absolutePath);
|
|
34
|
+
opts.writtenFiles.add(suggestedFileName);
|
|
35
|
+
}
|
|
36
|
+
async function markdownFromReport(htmlPath, outputDir) {
|
|
37
|
+
const screenshotsDir = join(outputDir, 'screenshots');
|
|
38
|
+
mkdirSync(outputDir, {
|
|
39
|
+
recursive: true
|
|
40
|
+
});
|
|
41
|
+
mkdirSync(screenshotsDir, {
|
|
42
|
+
recursive: true
|
|
43
|
+
});
|
|
44
|
+
const { baseDump, executions } = collectDedupedExecutions(htmlPath);
|
|
45
|
+
const mergedReport = new ReportActionDump({
|
|
46
|
+
sdkVersion: baseDump.sdkVersion,
|
|
47
|
+
groupName: baseDump.groupName,
|
|
48
|
+
groupDescription: baseDump.groupDescription,
|
|
49
|
+
modelBriefs: baseDump.modelBriefs,
|
|
50
|
+
deviceType: baseDump.deviceType,
|
|
51
|
+
executions
|
|
52
|
+
});
|
|
53
|
+
const result = reportToMarkdown(mergedReport);
|
|
54
|
+
const markdownFiles = [];
|
|
55
|
+
const writtenScreenshots = new Set();
|
|
56
|
+
const mdPath = join(outputDir, 'report.md');
|
|
57
|
+
writeFileSync(mdPath, result.markdown, 'utf-8');
|
|
58
|
+
markdownFiles.push(mdPath);
|
|
59
|
+
for (const attachment of result.attachments)writeAttachmentFromReport(attachment, {
|
|
60
|
+
htmlPath,
|
|
61
|
+
screenshotsDir,
|
|
62
|
+
writtenFiles: writtenScreenshots
|
|
63
|
+
});
|
|
64
|
+
return {
|
|
65
|
+
markdownFiles,
|
|
66
|
+
screenshotFiles: Array.from(writtenScreenshots).sort().map((f)=>join(screenshotsDir, f))
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
function resolveReportHtmlPath(htmlPath) {
|
|
70
|
+
const normalizedPath = resolve(htmlPath);
|
|
71
|
+
if (!existsSync(normalizedPath)) throw new Error(`report-tool: --htmlPath does not exist: ${htmlPath}`);
|
|
72
|
+
const stats = statSync(normalizedPath);
|
|
73
|
+
if (!stats.isDirectory()) return normalizedPath;
|
|
74
|
+
const indexHtmlPath = join(normalizedPath, 'index.html');
|
|
75
|
+
if (!existsSync(indexHtmlPath)) throw new Error(`report-tool: "${htmlPath}" is not an HTML report file, and no index.html was found under this directory.`);
|
|
76
|
+
return indexHtmlPath;
|
|
77
|
+
}
|
|
78
|
+
function splitReportFile(options) {
|
|
79
|
+
const { htmlPath, outputDir } = options;
|
|
80
|
+
if (!htmlPath) throw new Error('splitReportFile: htmlPath is required');
|
|
81
|
+
if (!outputDir) throw new Error('splitReportFile: outputDir is required');
|
|
82
|
+
const resolvedHtmlPath = resolveReportHtmlPath(htmlPath);
|
|
83
|
+
return splitReportHtmlByExecution({
|
|
84
|
+
htmlPath: resolvedHtmlPath,
|
|
85
|
+
outputDir
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
async function reportFileToMarkdown(options) {
|
|
89
|
+
const { htmlPath, outputDir } = options;
|
|
90
|
+
if (!htmlPath) throw new Error('reportFileToMarkdown: htmlPath is required');
|
|
91
|
+
if (!outputDir) throw new Error('reportFileToMarkdown: outputDir is required');
|
|
92
|
+
const resolvedHtmlPath = resolveReportHtmlPath(htmlPath);
|
|
93
|
+
return markdownFromReport(resolvedHtmlPath, outputDir);
|
|
94
|
+
}
|
|
95
|
+
const reportCommandDefinition = {
|
|
96
|
+
name: 'report-tool',
|
|
97
|
+
description: 'Transform Midscene report artifacts, including splitting executions and converting to markdown.',
|
|
98
|
+
schema: {
|
|
99
|
+
action: z["enum"]([
|
|
100
|
+
'split',
|
|
101
|
+
'to-markdown'
|
|
102
|
+
]).optional().describe('Report action to run. Supports: split, to-markdown. Defaults to split.'),
|
|
103
|
+
htmlPath: z.string().optional().describe('Input report HTML path (e.g. ./report/index.html)'),
|
|
104
|
+
outputDir: z.string().optional().describe('Output directory for generated report artifacts')
|
|
105
|
+
},
|
|
106
|
+
handler: async (args)=>{
|
|
107
|
+
const { action = 'split', htmlPath, outputDir } = args;
|
|
108
|
+
if ('split' !== action && 'to-markdown' !== action) throw new Error(`report-tool: unsupported --action value "${action}". Currently supported: split, to-markdown`);
|
|
109
|
+
if (!htmlPath) throw new Error('report-tool: --htmlPath is required');
|
|
110
|
+
if (!outputDir) throw new Error('report-tool: --outputDir is required');
|
|
111
|
+
if ('to-markdown' === action) {
|
|
112
|
+
const result = await reportFileToMarkdown({
|
|
113
|
+
htmlPath,
|
|
114
|
+
outputDir
|
|
115
|
+
});
|
|
116
|
+
return {
|
|
117
|
+
isError: false,
|
|
118
|
+
content: [
|
|
119
|
+
{
|
|
120
|
+
type: 'text',
|
|
121
|
+
text: `Markdown export completed. Generated ${result.markdownFiles.length} markdown file(s) and ${result.screenshotFiles.length} screenshot(s). Output path: ${outputDir}`
|
|
122
|
+
}
|
|
123
|
+
]
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
const result = splitReportFile({
|
|
127
|
+
htmlPath,
|
|
128
|
+
outputDir
|
|
129
|
+
});
|
|
130
|
+
return {
|
|
131
|
+
isError: false,
|
|
132
|
+
content: [
|
|
133
|
+
{
|
|
134
|
+
type: 'text',
|
|
135
|
+
text: `Report split completed. Generated ${result.executionJsonFiles.length} execution JSON files and ${result.screenshotFiles.length} screenshots. Output path: ${outputDir}`
|
|
136
|
+
}
|
|
137
|
+
]
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
function createReportCliCommands() {
|
|
142
|
+
return [
|
|
143
|
+
{
|
|
144
|
+
name: 'report-tool',
|
|
145
|
+
def: reportCommandDefinition
|
|
146
|
+
}
|
|
147
|
+
];
|
|
148
|
+
}
|
|
149
|
+
export { createReportCliCommands, reportFileToMarkdown, splitReportFile };
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readdirSync } from "node:fs";
|
|
2
|
+
import { appendFile, writeFile } from "node:fs/promises";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
import { getMidsceneRunSubDir } from "@godscene/shared/common";
|
|
5
|
+
import { MIDSCENE_REPORT_QUIET, globalConfigManager } from "@godscene/shared/env";
|
|
6
|
+
import { ifInBrowser, logMsg, uuid } from "@godscene/shared/utils";
|
|
7
|
+
import { generateDumpScriptTag, generateImageScriptTag, getBaseUrlFixScript } from "./dump/html-utils.mjs";
|
|
8
|
+
import { ScreenshotStore } from "./dump/screenshot-store.mjs";
|
|
9
|
+
import { ReportActionDump } from "./types.mjs";
|
|
10
|
+
import { getReportTpl } from "./utils.mjs";
|
|
11
|
+
function _define_property(obj, key, value) {
|
|
12
|
+
if (key in obj) Object.defineProperty(obj, key, {
|
|
13
|
+
value: value,
|
|
14
|
+
enumerable: true,
|
|
15
|
+
configurable: true,
|
|
16
|
+
writable: true
|
|
17
|
+
});
|
|
18
|
+
else obj[key] = value;
|
|
19
|
+
return obj;
|
|
20
|
+
}
|
|
21
|
+
const nullReportGenerator = {
|
|
22
|
+
onExecutionUpdate: ()=>{},
|
|
23
|
+
flush: async ()=>{},
|
|
24
|
+
finalize: async ()=>void 0,
|
|
25
|
+
getReportPath: ()=>void 0
|
|
26
|
+
};
|
|
27
|
+
function assertReportGenerationOptions(opts) {
|
|
28
|
+
if (false === opts.generateReport && true === opts.persistExecutionDump) throw new Error('persistExecutionDump cannot be true when generateReport is false');
|
|
29
|
+
}
|
|
30
|
+
class ReportGenerator {
|
|
31
|
+
static create(reportFileName, opts) {
|
|
32
|
+
assertReportGenerationOptions(opts);
|
|
33
|
+
if (false === opts.generateReport) return nullReportGenerator;
|
|
34
|
+
if (ifInBrowser) return nullReportGenerator;
|
|
35
|
+
validateReportFileName(reportFileName);
|
|
36
|
+
const reportRootDir = getMidsceneRunSubDir('report');
|
|
37
|
+
const outputDir = join(reportRootDir, reportFileName);
|
|
38
|
+
const reportPath = 'html-and-external-assets' === opts.outputFormat ? join(outputDir, 'index.html') : join(reportRootDir, ensureHtmlFileName(reportFileName));
|
|
39
|
+
return new ReportGenerator({
|
|
40
|
+
reportPath,
|
|
41
|
+
screenshotMode: 'html-and-external-assets' === opts.outputFormat ? 'directory' : 'inline',
|
|
42
|
+
persistExecutionDump: opts.persistExecutionDump,
|
|
43
|
+
autoPrint: opts.autoPrintReportMsg,
|
|
44
|
+
reuseExistingReport: opts.reuseExistingReport
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
onExecutionUpdate(execution, reportMeta, attributes) {
|
|
48
|
+
this.lastExecution = execution;
|
|
49
|
+
this.lastReportMeta = reportMeta;
|
|
50
|
+
this.mergeReportAttributes(attributes);
|
|
51
|
+
this.writeQueue = this.writeQueue.then(async ()=>{
|
|
52
|
+
if (this.destroyed) return;
|
|
53
|
+
await this.doWriteExecution(execution, reportMeta);
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
async flush() {
|
|
57
|
+
await this.writeQueue;
|
|
58
|
+
}
|
|
59
|
+
async finalize() {
|
|
60
|
+
if (this.lastExecution && this.lastReportMeta) this.onExecutionUpdate(this.lastExecution, this.lastReportMeta);
|
|
61
|
+
await this.flush();
|
|
62
|
+
this.destroyed = true;
|
|
63
|
+
if (!this.initialized) return;
|
|
64
|
+
this.printReportPath('finalized');
|
|
65
|
+
return this.reportPath;
|
|
66
|
+
}
|
|
67
|
+
getReportPath() {
|
|
68
|
+
return this.reportPath;
|
|
69
|
+
}
|
|
70
|
+
printReportPath(verb) {
|
|
71
|
+
if (!this.autoPrint || !this.reportPath) return;
|
|
72
|
+
if (globalConfigManager.getEnvConfigInBoolean(MIDSCENE_REPORT_QUIET)) return;
|
|
73
|
+
'directory' === this.screenshotMode ? logMsg(`Midscene - report ${verb}: npx serve ${dirname(this.reportPath)}`) : logMsg(`Midscene - report ${verb}: ${this.reportPath}`);
|
|
74
|
+
}
|
|
75
|
+
async doWriteExecution(execution, reportMeta) {
|
|
76
|
+
const singleDump = this.wrapAsReportDump(execution, reportMeta);
|
|
77
|
+
if ('inline' === this.screenshotMode) await this.writeInlineExecution(execution, singleDump);
|
|
78
|
+
else await this.writeDirectoryExecution(execution, singleDump);
|
|
79
|
+
if (this.shouldPersistExecutionDump) await this.persistExecutionDumpToFile(execution, singleDump);
|
|
80
|
+
if (!this.firstWriteDone) {
|
|
81
|
+
this.firstWriteDone = true;
|
|
82
|
+
this.printReportPath('generated');
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
mergeReportAttributes(attributes) {
|
|
86
|
+
if (!attributes) return;
|
|
87
|
+
for (const [key, value] of Object.entries(attributes))if (null != value) this.reportAttributes[key] = String(value);
|
|
88
|
+
}
|
|
89
|
+
hydrateStateFromExistingReport() {
|
|
90
|
+
if (!existsSync(this.reportPath)) return;
|
|
91
|
+
this.initialized = true;
|
|
92
|
+
if (!this.shouldPersistExecutionDump) return;
|
|
93
|
+
const reportDir = dirname(this.reportPath);
|
|
94
|
+
const existingExecutionIndices = readdirSync(reportDir).map((name)=>/^(\d+)\.execution\.json$/.exec(name)?.[1]).filter((index)=>Boolean(index)).map((index)=>Number.parseInt(index, 10)).filter((index)=>Number.isFinite(index));
|
|
95
|
+
if (existingExecutionIndices.length > 0) this.executionLogIndex = Math.max(...existingExecutionIndices);
|
|
96
|
+
}
|
|
97
|
+
getDumpScriptAttributes() {
|
|
98
|
+
return {
|
|
99
|
+
'data-group-id': this.reportStreamId,
|
|
100
|
+
...this.reportAttributes
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
wrapAsReportDump(execution, reportMeta) {
|
|
104
|
+
return new ReportActionDump({
|
|
105
|
+
sdkVersion: reportMeta.sdkVersion,
|
|
106
|
+
groupName: reportMeta.groupName,
|
|
107
|
+
groupDescription: reportMeta.groupDescription,
|
|
108
|
+
modelBriefs: reportMeta.modelBriefs,
|
|
109
|
+
deviceType: reportMeta.deviceType,
|
|
110
|
+
executions: [
|
|
111
|
+
execution
|
|
112
|
+
]
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
async writeInlineExecution(execution, singleDump) {
|
|
116
|
+
const dir = dirname(this.reportPath);
|
|
117
|
+
if (!existsSync(dir)) mkdirSync(dir, {
|
|
118
|
+
recursive: true
|
|
119
|
+
});
|
|
120
|
+
if (!this.initialized) {
|
|
121
|
+
await writeFile(this.reportPath, getReportTpl());
|
|
122
|
+
this.initialized = true;
|
|
123
|
+
}
|
|
124
|
+
for (const screenshot of execution.collectScreenshots())await this.screenshotStore.persist(screenshot);
|
|
125
|
+
const serialized = singleDump.serialize();
|
|
126
|
+
await appendFile(this.reportPath, `\n${generateDumpScriptTag(serialized, this.getDumpScriptAttributes())}`);
|
|
127
|
+
}
|
|
128
|
+
async writeDirectoryExecution(execution, singleDump) {
|
|
129
|
+
const dir = dirname(this.reportPath);
|
|
130
|
+
if (!existsSync(dir)) mkdirSync(dir, {
|
|
131
|
+
recursive: true
|
|
132
|
+
});
|
|
133
|
+
for (const screenshot of execution.collectScreenshots())await this.screenshotStore.persist(screenshot);
|
|
134
|
+
const serialized = singleDump.serialize();
|
|
135
|
+
if (!this.initialized) {
|
|
136
|
+
await writeFile(this.reportPath, `${getReportTpl()}${getBaseUrlFixScript()}`);
|
|
137
|
+
this.initialized = true;
|
|
138
|
+
}
|
|
139
|
+
await appendFile(this.reportPath, `\n${generateDumpScriptTag(serialized, this.getDumpScriptAttributes())}`);
|
|
140
|
+
}
|
|
141
|
+
getExecutionLogKey(execution) {
|
|
142
|
+
if (!execution.id) throw new Error('ReportGenerator: execution.id is required for persisting execution dumps');
|
|
143
|
+
return `id:${execution.id}`;
|
|
144
|
+
}
|
|
145
|
+
async persistExecutionDumpToFile(execution, singleDump) {
|
|
146
|
+
const dir = dirname(this.reportPath);
|
|
147
|
+
if (!existsSync(dir)) mkdirSync(dir, {
|
|
148
|
+
recursive: true
|
|
149
|
+
});
|
|
150
|
+
const executionLogKey = this.getExecutionLogKey(execution);
|
|
151
|
+
let fileIndex = this.executionLogFileIndexByExecutionKey.get(executionLogKey);
|
|
152
|
+
if (!fileIndex) {
|
|
153
|
+
this.executionLogIndex += 1;
|
|
154
|
+
fileIndex = this.executionLogIndex;
|
|
155
|
+
this.executionLogFileIndexByExecutionKey.set(executionLogKey, fileIndex);
|
|
156
|
+
}
|
|
157
|
+
const fileName = `${fileIndex}.execution.json`;
|
|
158
|
+
const filePath = join(dirname(this.reportPath), fileName);
|
|
159
|
+
await writeFile(filePath, singleDump.serialize(2), 'utf-8');
|
|
160
|
+
}
|
|
161
|
+
constructor(options){
|
|
162
|
+
_define_property(this, "reportPath", void 0);
|
|
163
|
+
_define_property(this, "screenshotMode", void 0);
|
|
164
|
+
_define_property(this, "shouldPersistExecutionDump", void 0);
|
|
165
|
+
_define_property(this, "autoPrint", void 0);
|
|
166
|
+
_define_property(this, "firstWriteDone", false);
|
|
167
|
+
_define_property(this, "executionLogIndex", 0);
|
|
168
|
+
_define_property(this, "executionLogFileIndexByExecutionKey", new Map());
|
|
169
|
+
_define_property(this, "reportStreamId", void 0);
|
|
170
|
+
_define_property(this, "screenshotStore", void 0);
|
|
171
|
+
_define_property(this, "initialized", false);
|
|
172
|
+
_define_property(this, "lastExecution", void 0);
|
|
173
|
+
_define_property(this, "lastReportMeta", void 0);
|
|
174
|
+
_define_property(this, "reportAttributes", {});
|
|
175
|
+
_define_property(this, "writeQueue", Promise.resolve());
|
|
176
|
+
_define_property(this, "destroyed", false);
|
|
177
|
+
this.reportPath = options.reportPath;
|
|
178
|
+
this.screenshotMode = options.screenshotMode;
|
|
179
|
+
this.shouldPersistExecutionDump = options.persistExecutionDump ?? false;
|
|
180
|
+
this.autoPrint = options.autoPrint ?? true;
|
|
181
|
+
this.reportStreamId = uuid();
|
|
182
|
+
this.screenshotStore = new ScreenshotStore({
|
|
183
|
+
mode: 'inline' === this.screenshotMode ? 'inline' : 'directory',
|
|
184
|
+
reportPath: this.reportPath,
|
|
185
|
+
screenshotsDir: join(dirname(this.reportPath), 'screenshots'),
|
|
186
|
+
writeInlineImage: async (id, base64)=>{
|
|
187
|
+
await appendFile(this.reportPath, `\n${generateImageScriptTag(id, base64)}`);
|
|
188
|
+
},
|
|
189
|
+
alsoWriteFileCopy: this.shouldPersistExecutionDump
|
|
190
|
+
});
|
|
191
|
+
if (options.reuseExistingReport) this.hydrateStateFromExistingReport();
|
|
192
|
+
this.printReportPath('will be generated at');
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
function ensureHtmlFileName(reportFileName) {
|
|
196
|
+
return reportFileName.endsWith('.html') ? reportFileName : `${reportFileName}.html`;
|
|
197
|
+
}
|
|
198
|
+
function validateReportFileName(reportFileName) {
|
|
199
|
+
if (!reportFileName?.trim()) throw new Error('reportFileName must be a non-empty string');
|
|
200
|
+
if (/[\\/]/.test(reportFileName)) throw new Error('reportFileName must not contain path separators (`/` or `\\\\`)');
|
|
201
|
+
if (/[:*?"<>|]/.test(reportFileName)) throw new Error('reportFileName contains illegal filename characters: : * ? " < > |');
|
|
202
|
+
}
|
|
203
|
+
export { ReportGenerator, assertReportGenerationOptions, nullReportGenerator };
|