@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.
Files changed (189) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +9 -0
  3. package/dist/es/agent/agent.mjs +767 -0
  4. package/dist/es/agent/common.mjs +0 -0
  5. package/dist/es/agent/execution-session.mjs +39 -0
  6. package/dist/es/agent/index.mjs +6 -0
  7. package/dist/es/agent/task-builder.mjs +343 -0
  8. package/dist/es/agent/task-cache.mjs +212 -0
  9. package/dist/es/agent/tasks.mjs +428 -0
  10. package/dist/es/agent/ui-utils.mjs +101 -0
  11. package/dist/es/agent/utils.mjs +167 -0
  12. package/dist/es/ai-model/auto-glm/actions.mjs +237 -0
  13. package/dist/es/ai-model/auto-glm/index.mjs +6 -0
  14. package/dist/es/ai-model/auto-glm/parser.mjs +237 -0
  15. package/dist/es/ai-model/auto-glm/planning.mjs +69 -0
  16. package/dist/es/ai-model/auto-glm/prompt.mjs +220 -0
  17. package/dist/es/ai-model/auto-glm/util.mjs +7 -0
  18. package/dist/es/ai-model/connectivity.mjs +136 -0
  19. package/dist/es/ai-model/conversation-history.mjs +193 -0
  20. package/dist/es/ai-model/index.mjs +12 -0
  21. package/dist/es/ai-model/inspect.mjs +395 -0
  22. package/dist/es/ai-model/llm-planning.mjs +231 -0
  23. package/dist/es/ai-model/prompt/common.mjs +5 -0
  24. package/dist/es/ai-model/prompt/describe.mjs +64 -0
  25. package/dist/es/ai-model/prompt/extraction.mjs +129 -0
  26. package/dist/es/ai-model/prompt/llm-locator.mjs +49 -0
  27. package/dist/es/ai-model/prompt/llm-planning.mjs +584 -0
  28. package/dist/es/ai-model/prompt/llm-section-locator.mjs +42 -0
  29. package/dist/es/ai-model/prompt/order-sensitive-judge.mjs +33 -0
  30. package/dist/es/ai-model/prompt/playwright-generator.mjs +115 -0
  31. package/dist/es/ai-model/prompt/ui-tars-planning.mjs +34 -0
  32. package/dist/es/ai-model/prompt/util.mjs +57 -0
  33. package/dist/es/ai-model/prompt/yaml-generator.mjs +201 -0
  34. package/dist/es/ai-model/service-caller/codex-app-server.mjs +573 -0
  35. package/dist/es/ai-model/service-caller/image-detail.mjs +4 -0
  36. package/dist/es/ai-model/service-caller/index.mjs +648 -0
  37. package/dist/es/ai-model/service-caller/request-timeout.mjs +47 -0
  38. package/dist/es/ai-model/ui-tars-planning.mjs +247 -0
  39. package/dist/es/common.mjs +382 -0
  40. package/dist/es/device/device-options.mjs +0 -0
  41. package/dist/es/device/index.mjs +340 -0
  42. package/dist/es/dump/html-utils.mjs +290 -0
  43. package/dist/es/dump/index.mjs +3 -0
  44. package/dist/es/dump/screenshot-restoration.mjs +30 -0
  45. package/dist/es/dump/screenshot-store.mjs +125 -0
  46. package/dist/es/index.mjs +17 -0
  47. package/dist/es/report-cli.mjs +149 -0
  48. package/dist/es/report-generator.mjs +203 -0
  49. package/dist/es/report-markdown.mjs +216 -0
  50. package/dist/es/report.mjs +287 -0
  51. package/dist/es/screenshot-item.mjs +120 -0
  52. package/dist/es/service/index.mjs +272 -0
  53. package/dist/es/service/utils.mjs +13 -0
  54. package/dist/es/skill/index.mjs +35 -0
  55. package/dist/es/task-runner.mjs +261 -0
  56. package/dist/es/task-timing.mjs +10 -0
  57. package/dist/es/tree.mjs +11 -0
  58. package/dist/es/types.mjs +202 -0
  59. package/dist/es/utils.mjs +232 -0
  60. package/dist/es/yaml/builder.mjs +11 -0
  61. package/dist/es/yaml/index.mjs +4 -0
  62. package/dist/es/yaml/player.mjs +425 -0
  63. package/dist/es/yaml/utils.mjs +100 -0
  64. package/dist/es/yaml.mjs +0 -0
  65. package/dist/lib/agent/agent.js +815 -0
  66. package/dist/lib/agent/common.js +5 -0
  67. package/dist/lib/agent/execution-session.js +73 -0
  68. package/dist/lib/agent/index.js +76 -0
  69. package/dist/lib/agent/task-builder.js +380 -0
  70. package/dist/lib/agent/task-cache.js +264 -0
  71. package/dist/lib/agent/tasks.js +471 -0
  72. package/dist/lib/agent/ui-utils.js +153 -0
  73. package/dist/lib/agent/utils.js +238 -0
  74. package/dist/lib/ai-model/auto-glm/actions.js +271 -0
  75. package/dist/lib/ai-model/auto-glm/index.js +64 -0
  76. package/dist/lib/ai-model/auto-glm/parser.js +280 -0
  77. package/dist/lib/ai-model/auto-glm/planning.js +103 -0
  78. package/dist/lib/ai-model/auto-glm/prompt.js +257 -0
  79. package/dist/lib/ai-model/auto-glm/util.js +44 -0
  80. package/dist/lib/ai-model/connectivity.js +180 -0
  81. package/dist/lib/ai-model/conversation-history.js +227 -0
  82. package/dist/lib/ai-model/index.js +127 -0
  83. package/dist/lib/ai-model/inspect.js +441 -0
  84. package/dist/lib/ai-model/llm-planning.js +268 -0
  85. package/dist/lib/ai-model/prompt/common.js +39 -0
  86. package/dist/lib/ai-model/prompt/describe.js +98 -0
  87. package/dist/lib/ai-model/prompt/extraction.js +169 -0
  88. package/dist/lib/ai-model/prompt/llm-locator.js +86 -0
  89. package/dist/lib/ai-model/prompt/llm-planning.js +621 -0
  90. package/dist/lib/ai-model/prompt/llm-section-locator.js +79 -0
  91. package/dist/lib/ai-model/prompt/order-sensitive-judge.js +70 -0
  92. package/dist/lib/ai-model/prompt/playwright-generator.js +176 -0
  93. package/dist/lib/ai-model/prompt/ui-tars-planning.js +71 -0
  94. package/dist/lib/ai-model/prompt/util.js +103 -0
  95. package/dist/lib/ai-model/prompt/yaml-generator.js +262 -0
  96. package/dist/lib/ai-model/service-caller/codex-app-server.js +622 -0
  97. package/dist/lib/ai-model/service-caller/image-detail.js +38 -0
  98. package/dist/lib/ai-model/service-caller/index.js +716 -0
  99. package/dist/lib/ai-model/service-caller/request-timeout.js +93 -0
  100. package/dist/lib/ai-model/ui-tars-planning.js +281 -0
  101. package/dist/lib/common.js +491 -0
  102. package/dist/lib/device/device-options.js +18 -0
  103. package/dist/lib/device/index.js +467 -0
  104. package/dist/lib/dump/html-utils.js +366 -0
  105. package/dist/lib/dump/index.js +58 -0
  106. package/dist/lib/dump/screenshot-restoration.js +64 -0
  107. package/dist/lib/dump/screenshot-store.js +165 -0
  108. package/dist/lib/index.js +184 -0
  109. package/dist/lib/report-cli.js +189 -0
  110. package/dist/lib/report-generator.js +244 -0
  111. package/dist/lib/report-markdown.js +253 -0
  112. package/dist/lib/report.js +333 -0
  113. package/dist/lib/screenshot-item.js +154 -0
  114. package/dist/lib/service/index.js +306 -0
  115. package/dist/lib/service/utils.js +47 -0
  116. package/dist/lib/skill/index.js +69 -0
  117. package/dist/lib/task-runner.js +298 -0
  118. package/dist/lib/task-timing.js +44 -0
  119. package/dist/lib/tree.js +51 -0
  120. package/dist/lib/types.js +298 -0
  121. package/dist/lib/utils.js +314 -0
  122. package/dist/lib/yaml/builder.js +55 -0
  123. package/dist/lib/yaml/index.js +79 -0
  124. package/dist/lib/yaml/player.js +459 -0
  125. package/dist/lib/yaml/utils.js +153 -0
  126. package/dist/lib/yaml.js +18 -0
  127. package/dist/types/agent/agent.d.ts +220 -0
  128. package/dist/types/agent/common.d.ts +0 -0
  129. package/dist/types/agent/execution-session.d.ts +36 -0
  130. package/dist/types/agent/index.d.ts +9 -0
  131. package/dist/types/agent/task-builder.d.ts +34 -0
  132. package/dist/types/agent/task-cache.d.ts +49 -0
  133. package/dist/types/agent/tasks.d.ts +70 -0
  134. package/dist/types/agent/ui-utils.d.ts +14 -0
  135. package/dist/types/agent/utils.d.ts +25 -0
  136. package/dist/types/ai-model/auto-glm/actions.d.ts +78 -0
  137. package/dist/types/ai-model/auto-glm/index.d.ts +6 -0
  138. package/dist/types/ai-model/auto-glm/parser.d.ts +18 -0
  139. package/dist/types/ai-model/auto-glm/planning.d.ts +12 -0
  140. package/dist/types/ai-model/auto-glm/prompt.d.ts +27 -0
  141. package/dist/types/ai-model/auto-glm/util.d.ts +13 -0
  142. package/dist/types/ai-model/connectivity.d.ts +20 -0
  143. package/dist/types/ai-model/conversation-history.d.ts +105 -0
  144. package/dist/types/ai-model/index.d.ts +16 -0
  145. package/dist/types/ai-model/inspect.d.ts +67 -0
  146. package/dist/types/ai-model/llm-planning.d.ts +19 -0
  147. package/dist/types/ai-model/prompt/common.d.ts +2 -0
  148. package/dist/types/ai-model/prompt/describe.d.ts +1 -0
  149. package/dist/types/ai-model/prompt/extraction.d.ts +7 -0
  150. package/dist/types/ai-model/prompt/llm-locator.d.ts +3 -0
  151. package/dist/types/ai-model/prompt/llm-planning.d.ts +10 -0
  152. package/dist/types/ai-model/prompt/llm-section-locator.d.ts +3 -0
  153. package/dist/types/ai-model/prompt/order-sensitive-judge.d.ts +2 -0
  154. package/dist/types/ai-model/prompt/playwright-generator.d.ts +26 -0
  155. package/dist/types/ai-model/prompt/ui-tars-planning.d.ts +2 -0
  156. package/dist/types/ai-model/prompt/util.d.ts +33 -0
  157. package/dist/types/ai-model/prompt/yaml-generator.d.ts +102 -0
  158. package/dist/types/ai-model/service-caller/codex-app-server.d.ts +42 -0
  159. package/dist/types/ai-model/service-caller/image-detail.d.ts +2 -0
  160. package/dist/types/ai-model/service-caller/index.d.ts +60 -0
  161. package/dist/types/ai-model/service-caller/request-timeout.d.ts +32 -0
  162. package/dist/types/ai-model/ui-tars-planning.d.ts +72 -0
  163. package/dist/types/common.d.ts +288 -0
  164. package/dist/types/device/device-options.d.ts +155 -0
  165. package/dist/types/device/index.d.ts +2565 -0
  166. package/dist/types/dump/html-utils.d.ts +75 -0
  167. package/dist/types/dump/index.d.ts +5 -0
  168. package/dist/types/dump/screenshot-restoration.d.ts +8 -0
  169. package/dist/types/dump/screenshot-store.d.ts +49 -0
  170. package/dist/types/index.d.ts +21 -0
  171. package/dist/types/report-cli.d.ts +36 -0
  172. package/dist/types/report-generator.d.ts +88 -0
  173. package/dist/types/report-markdown.d.ts +24 -0
  174. package/dist/types/report.d.ts +52 -0
  175. package/dist/types/screenshot-item.d.ts +67 -0
  176. package/dist/types/service/index.d.ts +24 -0
  177. package/dist/types/service/utils.d.ts +2 -0
  178. package/dist/types/skill/index.d.ts +25 -0
  179. package/dist/types/task-runner.d.ts +50 -0
  180. package/dist/types/task-timing.d.ts +8 -0
  181. package/dist/types/tree.d.ts +4 -0
  182. package/dist/types/types.d.ts +684 -0
  183. package/dist/types/utils.d.ts +45 -0
  184. package/dist/types/yaml/builder.d.ts +2 -0
  185. package/dist/types/yaml/index.d.ts +4 -0
  186. package/dist/types/yaml/player.d.ts +34 -0
  187. package/dist/types/yaml/utils.d.ts +9 -0
  188. package/dist/types/yaml.d.ts +215 -0
  189. 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 };