@aiscene/core 8.0.1 → 8.0.2

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 (192) hide show
  1. package/dist/es/agent/agent.mjs +3 -3
  2. package/dist/es/agent/agent.mjs.map +1 -1
  3. package/dist/es/agent/task-builder.mjs +3 -3
  4. package/dist/es/agent/task-builder.mjs.map +1 -1
  5. package/dist/es/agent/task-cache.mjs +4 -4
  6. package/dist/es/agent/task-cache.mjs.map +1 -1
  7. package/dist/es/agent/tasks.mjs +4 -4
  8. package/dist/es/agent/tasks.mjs.map +1 -1
  9. package/dist/es/agent/utils.mjs +5 -5
  10. package/dist/es/agent/utils.mjs.map +1 -1
  11. package/dist/es/ai-model/auto-glm/actions.mjs +1 -1
  12. package/dist/es/ai-model/auto-glm/actions.mjs.map +1 -1
  13. package/dist/es/ai-model/auto-glm/parser.mjs +1 -1
  14. package/dist/es/ai-model/auto-glm/parser.mjs.map +1 -1
  15. package/dist/es/ai-model/auto-glm/planning.mjs +1 -1
  16. package/dist/es/ai-model/auto-glm/planning.mjs.map +1 -1
  17. package/dist/es/ai-model/auto-glm/prompt.mjs.map +1 -1
  18. package/dist/es/ai-model/auto-glm/util.mjs.map +1 -1
  19. package/dist/es/ai-model/connectivity.mjs +1 -1
  20. package/dist/es/ai-model/connectivity.mjs.map +1 -1
  21. package/dist/es/ai-model/inspect.mjs +4 -4
  22. package/dist/es/ai-model/inspect.mjs.map +1 -1
  23. package/dist/es/ai-model/llm-planning.mjs +3 -3
  24. package/dist/es/ai-model/llm-planning.mjs.map +1 -1
  25. package/dist/es/ai-model/prompt/common.mjs.map +1 -1
  26. package/dist/es/ai-model/prompt/describe.mjs +1 -1
  27. package/dist/es/ai-model/prompt/describe.mjs.map +1 -1
  28. package/dist/es/ai-model/prompt/extraction.mjs +1 -1
  29. package/dist/es/ai-model/prompt/extraction.mjs.map +1 -1
  30. package/dist/es/ai-model/prompt/llm-locator.mjs +1 -1
  31. package/dist/es/ai-model/prompt/llm-locator.mjs.map +1 -1
  32. package/dist/es/ai-model/prompt/llm-planning.mjs +2 -2
  33. package/dist/es/ai-model/prompt/llm-planning.mjs.map +1 -1
  34. package/dist/es/ai-model/prompt/llm-section-locator.mjs +1 -1
  35. package/dist/es/ai-model/prompt/llm-section-locator.mjs.map +1 -1
  36. package/dist/es/ai-model/prompt/playwright-generator.mjs +1 -1
  37. package/dist/es/ai-model/prompt/playwright-generator.mjs.map +1 -1
  38. package/dist/es/ai-model/prompt/ui-tars-planning.mjs +1 -1
  39. package/dist/es/ai-model/prompt/ui-tars-planning.mjs.map +1 -1
  40. package/dist/es/ai-model/prompt/yaml-generator.mjs +1 -1
  41. package/dist/es/ai-model/prompt/yaml-generator.mjs.map +1 -1
  42. package/dist/es/ai-model/service-caller/codex-app-server.mjs +2 -2
  43. package/dist/es/ai-model/service-caller/codex-app-server.mjs.map +1 -1
  44. package/dist/es/ai-model/service-caller/image-detail.mjs.map +1 -1
  45. package/dist/es/ai-model/service-caller/index.mjs +3 -3
  46. package/dist/es/ai-model/service-caller/index.mjs.map +1 -1
  47. package/dist/es/ai-model/ui-tars-planning.mjs +3 -3
  48. package/dist/es/ai-model/ui-tars-planning.mjs.map +1 -1
  49. package/dist/es/common.mjs +4 -4
  50. package/dist/es/common.mjs.map +1 -1
  51. package/dist/es/device/index.mjs +1 -1
  52. package/dist/es/device/index.mjs.map +1 -1
  53. package/dist/es/dump/html-utils.mjs +1 -1
  54. package/dist/es/dump/html-utils.mjs.map +1 -1
  55. package/dist/es/index.mjs +1 -1
  56. package/dist/es/index.mjs.map +1 -1
  57. package/dist/es/report-generator.mjs +3 -3
  58. package/dist/es/report-generator.mjs.map +1 -1
  59. package/dist/es/report.mjs +2 -2
  60. package/dist/es/report.mjs.map +1 -1
  61. package/dist/es/screenshot-item.mjs +1 -1
  62. package/dist/es/screenshot-item.mjs.map +1 -1
  63. package/dist/es/service/index.mjs +3 -3
  64. package/dist/es/service/index.mjs.map +1 -1
  65. package/dist/es/service/utils.mjs +1 -1
  66. package/dist/es/service/utils.mjs.map +1 -1
  67. package/dist/es/skill/index.mjs +2 -2
  68. package/dist/es/skill/index.mjs.map +1 -1
  69. package/dist/es/task-runner.mjs +2 -2
  70. package/dist/es/task-runner.mjs.map +1 -1
  71. package/dist/es/task-timing.mjs +1 -1
  72. package/dist/es/task-timing.mjs.map +1 -1
  73. package/dist/es/tree.mjs +1 -1
  74. package/dist/es/tree.mjs.map +1 -1
  75. package/dist/es/types.mjs.map +1 -1
  76. package/dist/es/utils.mjs +4 -4
  77. package/dist/es/utils.mjs.map +1 -1
  78. package/dist/es/yaml/player.mjs +3 -3
  79. package/dist/es/yaml/player.mjs.map +1 -1
  80. package/dist/es/yaml/utils.mjs +2 -2
  81. package/dist/es/yaml/utils.mjs.map +1 -1
  82. package/dist/lib/agent/agent.js +3 -3
  83. package/dist/lib/agent/agent.js.map +1 -1
  84. package/dist/lib/agent/task-builder.js +3 -3
  85. package/dist/lib/agent/task-builder.js.map +1 -1
  86. package/dist/lib/agent/task-cache.js +4 -4
  87. package/dist/lib/agent/task-cache.js.map +1 -1
  88. package/dist/lib/agent/tasks.js +4 -4
  89. package/dist/lib/agent/tasks.js.map +1 -1
  90. package/dist/lib/agent/utils.js +5 -5
  91. package/dist/lib/agent/utils.js.map +1 -1
  92. package/dist/lib/ai-model/auto-glm/actions.js +1 -1
  93. package/dist/lib/ai-model/auto-glm/actions.js.map +1 -1
  94. package/dist/lib/ai-model/auto-glm/parser.js +1 -1
  95. package/dist/lib/ai-model/auto-glm/parser.js.map +1 -1
  96. package/dist/lib/ai-model/auto-glm/planning.js +1 -1
  97. package/dist/lib/ai-model/auto-glm/planning.js.map +1 -1
  98. package/dist/lib/ai-model/auto-glm/prompt.js.map +1 -1
  99. package/dist/lib/ai-model/auto-glm/util.js.map +1 -1
  100. package/dist/lib/ai-model/connectivity.js +1 -1
  101. package/dist/lib/ai-model/connectivity.js.map +1 -1
  102. package/dist/lib/ai-model/inspect.js +4 -4
  103. package/dist/lib/ai-model/inspect.js.map +1 -1
  104. package/dist/lib/ai-model/llm-planning.js +3 -3
  105. package/dist/lib/ai-model/llm-planning.js.map +1 -1
  106. package/dist/lib/ai-model/prompt/common.js.map +1 -1
  107. package/dist/lib/ai-model/prompt/describe.js +1 -1
  108. package/dist/lib/ai-model/prompt/describe.js.map +1 -1
  109. package/dist/lib/ai-model/prompt/extraction.js +1 -1
  110. package/dist/lib/ai-model/prompt/extraction.js.map +1 -1
  111. package/dist/lib/ai-model/prompt/llm-locator.js +1 -1
  112. package/dist/lib/ai-model/prompt/llm-locator.js.map +1 -1
  113. package/dist/lib/ai-model/prompt/llm-planning.js +2 -2
  114. package/dist/lib/ai-model/prompt/llm-planning.js.map +1 -1
  115. package/dist/lib/ai-model/prompt/llm-section-locator.js +1 -1
  116. package/dist/lib/ai-model/prompt/llm-section-locator.js.map +1 -1
  117. package/dist/lib/ai-model/prompt/playwright-generator.js +1 -1
  118. package/dist/lib/ai-model/prompt/playwright-generator.js.map +1 -1
  119. package/dist/lib/ai-model/prompt/ui-tars-planning.js +1 -1
  120. package/dist/lib/ai-model/prompt/ui-tars-planning.js.map +1 -1
  121. package/dist/lib/ai-model/prompt/yaml-generator.js +1 -1
  122. package/dist/lib/ai-model/prompt/yaml-generator.js.map +1 -1
  123. package/dist/lib/ai-model/service-caller/codex-app-server.js +2 -2
  124. package/dist/lib/ai-model/service-caller/codex-app-server.js.map +1 -1
  125. package/dist/lib/ai-model/service-caller/image-detail.js.map +1 -1
  126. package/dist/lib/ai-model/service-caller/index.js +3 -3
  127. package/dist/lib/ai-model/service-caller/index.js.map +1 -1
  128. package/dist/lib/ai-model/ui-tars-planning.js +3 -3
  129. package/dist/lib/ai-model/ui-tars-planning.js.map +1 -1
  130. package/dist/lib/common.js +4 -4
  131. package/dist/lib/common.js.map +1 -1
  132. package/dist/lib/device/index.js +1 -1
  133. package/dist/lib/device/index.js.map +1 -1
  134. package/dist/lib/dump/html-utils.js +1 -1
  135. package/dist/lib/dump/html-utils.js.map +1 -1
  136. package/dist/lib/index.js +1 -1
  137. package/dist/lib/index.js.map +1 -1
  138. package/dist/lib/report-generator.js +3 -3
  139. package/dist/lib/report-generator.js.map +1 -1
  140. package/dist/lib/report.js +2 -2
  141. package/dist/lib/report.js.map +1 -1
  142. package/dist/lib/screenshot-item.js +1 -1
  143. package/dist/lib/screenshot-item.js.map +1 -1
  144. package/dist/lib/service/index.js +3 -3
  145. package/dist/lib/service/index.js.map +1 -1
  146. package/dist/lib/service/utils.js +1 -1
  147. package/dist/lib/service/utils.js.map +1 -1
  148. package/dist/lib/skill/index.js +2 -2
  149. package/dist/lib/skill/index.js.map +1 -1
  150. package/dist/lib/task-runner.js +2 -2
  151. package/dist/lib/task-runner.js.map +1 -1
  152. package/dist/lib/task-timing.js +1 -1
  153. package/dist/lib/task-timing.js.map +1 -1
  154. package/dist/lib/tree.js +1 -1
  155. package/dist/lib/tree.js.map +1 -1
  156. package/dist/lib/types.js +2 -2
  157. package/dist/lib/types.js.map +1 -1
  158. package/dist/lib/utils.js +4 -4
  159. package/dist/lib/utils.js.map +1 -1
  160. package/dist/lib/yaml/player.js +3 -3
  161. package/dist/lib/yaml/player.js.map +1 -1
  162. package/dist/lib/yaml/utils.js +2 -2
  163. package/dist/lib/yaml/utils.js.map +1 -1
  164. package/dist/types/agent/agent.d.ts +2 -2
  165. package/dist/types/agent/task-builder.d.ts +1 -1
  166. package/dist/types/agent/task-cache.d.ts +1 -1
  167. package/dist/types/agent/tasks.d.ts +1 -1
  168. package/dist/types/agent/utils.d.ts +1 -1
  169. package/dist/types/ai-model/auto-glm/planning.d.ts +1 -1
  170. package/dist/types/ai-model/auto-glm/prompt.d.ts +1 -1
  171. package/dist/types/ai-model/auto-glm/util.d.ts +1 -1
  172. package/dist/types/ai-model/connectivity.d.ts +1 -1
  173. package/dist/types/ai-model/inspect.d.ts +2 -2
  174. package/dist/types/ai-model/llm-planning.d.ts +1 -1
  175. package/dist/types/ai-model/prompt/common.d.ts +1 -1
  176. package/dist/types/ai-model/prompt/llm-locator.d.ts +1 -1
  177. package/dist/types/ai-model/prompt/llm-planning.d.ts +1 -1
  178. package/dist/types/ai-model/prompt/llm-section-locator.d.ts +1 -1
  179. package/dist/types/ai-model/prompt/playwright-generator.d.ts +1 -1
  180. package/dist/types/ai-model/prompt/yaml-generator.d.ts +1 -1
  181. package/dist/types/ai-model/service-caller/codex-app-server.d.ts +1 -1
  182. package/dist/types/ai-model/service-caller/image-detail.d.ts +1 -1
  183. package/dist/types/ai-model/service-caller/index.d.ts +1 -1
  184. package/dist/types/ai-model/ui-tars-planning.d.ts +1 -1
  185. package/dist/types/common.d.ts +2 -2
  186. package/dist/types/device/index.d.ts +2 -2
  187. package/dist/types/dump/html-utils.d.ts +2 -2
  188. package/dist/types/index.d.ts +1 -1
  189. package/dist/types/service/index.d.ts +1 -1
  190. package/dist/types/tree.d.ts +3 -3
  191. package/dist/types/types.d.ts +4 -4
  192. package/package.json +1 -1
@@ -1,5 +1,5 @@
1
- import { CLIError, runToolsCLI } from "@midscene/shared/cli";
2
- import { BaseMidsceneTools } from "@midscene/shared/mcp";
1
+ import { CLIError, runToolsCLI } from "@aiscene/shared/cli";
2
+ import { BaseMidsceneTools } from "@aiscene/shared/mcp";
3
3
  import { Agent } from "../agent/agent.mjs";
4
4
  function _define_property(obj, key, value) {
5
5
  if (key in obj) Object.defineProperty(obj, key, {
@@ -1 +1 @@
1
- {"version":3,"file":"skill/index.mjs","sources":["../../../src/skill/index.ts"],"sourcesContent":["import { CLIError, runToolsCLI } from '@midscene/shared/cli';\nimport { BaseMidsceneTools } from '@midscene/shared/mcp';\nimport type { BaseAgent, BaseDevice } from '@midscene/shared/mcp';\nimport { Agent } from '../agent/agent';\nimport type { AbstractInterface } from '../device';\n\ntype DeviceClass = new (...args: any[]) => AbstractInterface;\n\n/**\n * Skill tools manager that lazily creates Agent from a Device class.\n * Used by runSkillCLI for CLI / Agent Skills scenarios where no agent exists at startup.\n */\nclass SkillMidsceneTools extends BaseMidsceneTools<BaseAgent> {\n constructor(private DeviceClass: DeviceClass) {\n super();\n }\n\n protected createTemporaryDevice(): BaseDevice {\n return new this.DeviceClass() as unknown as BaseDevice;\n }\n\n protected async ensureAgent(): Promise<BaseAgent> {\n if (!this.agent) {\n const device = new this.DeviceClass();\n this.agent = new Agent(device) as unknown as BaseAgent;\n }\n return this.agent;\n }\n}\n\nexport interface SkillCLIOptions {\n scriptName: string;\n DeviceClass: DeviceClass;\n}\n\n/**\n * Launch a Skill CLI for a custom interface Device class.\n * This enables AI coding assistants (Claude Code, Cline, etc.) to control\n * your custom interface through CLI commands.\n *\n * @example\n * ```typescript\n * #!/usr/bin/env node\n * import { runSkillCLI } from '@midscene/core/skill';\n * import { SampleDevice } from './sample-device';\n *\n * runSkillCLI({\n * DeviceClass: SampleDevice,\n * scriptName: 'my-device',\n * });\n * ```\n */\nexport function runSkillCLI(options: SkillCLIOptions): Promise<void> {\n const tools = new SkillMidsceneTools(options.DeviceClass);\n return runToolsCLI(tools, options.scriptName).catch((e) => {\n if (!(e instanceof CLIError)) console.error(e);\n process.exit(e instanceof CLIError ? e.exitCode : 1);\n });\n}\n"],"names":["SkillMidsceneTools","BaseMidsceneTools","device","Agent","DeviceClass","runSkillCLI","options","tools","runToolsCLI","e","CLIError","console","process"],"mappings":";;;;;;;;;;;;;AAYA,MAAMA,2BAA2BC;IAKrB,wBAAoC;QAC5C,OAAO,IAAI,IAAI,CAAC,WAAW;IAC7B;IAEA,MAAgB,cAAkC;QAChD,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE;YACf,MAAMC,SAAS,IAAI,IAAI,CAAC,WAAW;YACnC,IAAI,CAAC,KAAK,GAAG,IAAIC,MAAMD;QACzB;QACA,OAAO,IAAI,CAAC,KAAK;IACnB;IAdA,YAAoBE,WAAwB,CAAE;QAC5C,KAAK,wDADaA,WAAW,GAAXA;IAEpB;AAaF;AAwBO,SAASC,YAAYC,OAAwB;IAClD,MAAMC,QAAQ,IAAIP,mBAAmBM,QAAQ,WAAW;IACxD,OAAOE,YAAYD,OAAOD,QAAQ,UAAU,EAAE,KAAK,CAAC,CAACG;QACnD,IAAI,CAAEA,CAAAA,aAAaC,QAAO,GAAIC,QAAQ,KAAK,CAACF;QAC5CG,QAAQ,IAAI,CAACH,aAAaC,WAAWD,EAAE,QAAQ,GAAG;IACpD;AACF"}
1
+ {"version":3,"file":"skill/index.mjs","sources":["../../../src/skill/index.ts"],"sourcesContent":["import { CLIError, runToolsCLI } from '@aiscene/shared/cli';\nimport { BaseMidsceneTools } from '@aiscene/shared/mcp';\nimport type { BaseAgent, BaseDevice } from '@aiscene/shared/mcp';\nimport { Agent } from '../agent/agent';\nimport type { AbstractInterface } from '../device';\n\ntype DeviceClass = new (...args: any[]) => AbstractInterface;\n\n/**\n * Skill tools manager that lazily creates Agent from a Device class.\n * Used by runSkillCLI for CLI / Agent Skills scenarios where no agent exists at startup.\n */\nclass SkillMidsceneTools extends BaseMidsceneTools<BaseAgent> {\n constructor(private DeviceClass: DeviceClass) {\n super();\n }\n\n protected createTemporaryDevice(): BaseDevice {\n return new this.DeviceClass() as unknown as BaseDevice;\n }\n\n protected async ensureAgent(): Promise<BaseAgent> {\n if (!this.agent) {\n const device = new this.DeviceClass();\n this.agent = new Agent(device) as unknown as BaseAgent;\n }\n return this.agent;\n }\n}\n\nexport interface SkillCLIOptions {\n scriptName: string;\n DeviceClass: DeviceClass;\n}\n\n/**\n * Launch a Skill CLI for a custom interface Device class.\n * This enables AI coding assistants (Claude Code, Cline, etc.) to control\n * your custom interface through CLI commands.\n *\n * @example\n * ```typescript\n * #!/usr/bin/env node\n * import { runSkillCLI } from '@midscene/core/skill';\n * import { SampleDevice } from './sample-device';\n *\n * runSkillCLI({\n * DeviceClass: SampleDevice,\n * scriptName: 'my-device',\n * });\n * ```\n */\nexport function runSkillCLI(options: SkillCLIOptions): Promise<void> {\n const tools = new SkillMidsceneTools(options.DeviceClass);\n return runToolsCLI(tools, options.scriptName).catch((e) => {\n if (!(e instanceof CLIError)) console.error(e);\n process.exit(e instanceof CLIError ? e.exitCode : 1);\n });\n}\n"],"names":["SkillMidsceneTools","BaseMidsceneTools","device","Agent","DeviceClass","runSkillCLI","options","tools","runToolsCLI","e","CLIError","console","process"],"mappings":";;;;;;;;;;;;;AAYA,MAAMA,2BAA2BC;IAKrB,wBAAoC;QAC5C,OAAO,IAAI,IAAI,CAAC,WAAW;IAC7B;IAEA,MAAgB,cAAkC;QAChD,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE;YACf,MAAMC,SAAS,IAAI,IAAI,CAAC,WAAW;YACnC,IAAI,CAAC,KAAK,GAAG,IAAIC,MAAMD;QACzB;QACA,OAAO,IAAI,CAAC,KAAK;IACnB;IAdA,YAAoBE,WAAwB,CAAE;QAC5C,KAAK,wDADaA,WAAW,GAAXA;IAEpB;AAaF;AAwBO,SAASC,YAAYC,OAAwB;IAClD,MAAMC,QAAQ,IAAIP,mBAAmBM,QAAQ,WAAW;IACxD,OAAOE,YAAYD,OAAOD,QAAQ,UAAU,EAAE,KAAK,CAAC,CAACG;QACnD,IAAI,CAAEA,CAAAA,aAAaC,QAAO,GAAIC,QAAQ,KAAK,CAACF;QAC5CG,QAAQ,IAAI,CAACH,aAAaC,WAAWD,EAAE,QAAQ,GAAG;IACpD;AACF"}
@@ -1,7 +1,7 @@
1
1
  import { setTimingFieldOnce } from "./task-timing.mjs";
2
2
  import { ExecutionDump } from "./types.mjs";
3
- import { getDebug } from "@midscene/shared/logger";
4
- import { assert, uuid } from "@midscene/shared/utils";
3
+ import { getDebug } from "@aiscene/shared/logger";
4
+ import { assert, uuid } from "@aiscene/shared/utils";
5
5
  function _define_property(obj, key, value) {
6
6
  if (key in obj) Object.defineProperty(obj, key, {
7
7
  value: value,
@@ -1 +1 @@
1
- {"version":3,"file":"task-runner.mjs","sources":["../../src/task-runner.ts"],"sourcesContent":["import type { ScreenshotItem } from '@/screenshot-item';\nimport { setTimingFieldOnce } from '@/task-timing';\nimport {\n ExecutionDump,\n type ExecutionRecorderItem,\n type ExecutionTask,\n type ExecutionTaskActionApply,\n type ExecutionTaskApply,\n type ExecutionTaskPlanningLocateOutput,\n type ExecutionTaskProgressOptions,\n type ExecutionTaskReturn,\n type ExecutorContext,\n type PlanningActionParamError,\n type UIContext,\n} from '@/types';\nimport { getDebug } from '@midscene/shared/logger';\nimport { assert, uuid } from '@midscene/shared/utils';\n\nconst debug = getDebug('task-runner');\nconst UI_CONTEXT_CACHE_TTL_MS = 300;\n\ntype TaskRunnerInitOptions = ExecutionTaskProgressOptions & {\n tasks?: ExecutionTaskApply[];\n onTaskUpdate?: (\n runner: TaskRunner,\n error?: TaskExecutionError,\n ) => Promise<void> | void;\n};\n\ntype TaskRunnerOperationOptions = {\n allowWhenError?: boolean;\n};\n\nexport class TaskRunner {\n readonly id: string;\n name: string;\n\n tasks: ExecutionTask[];\n\n // status of runner\n status: 'init' | 'pending' | 'running' | 'completed' | 'error';\n\n onTaskStart?: ExecutionTaskProgressOptions['onTaskStart'];\n\n private readonly uiContextBuilder: () => Promise<UIContext>;\n\n private readonly onTaskUpdate?:\n | ((runner: TaskRunner, error?: TaskExecutionError) => Promise<void> | void)\n | undefined;\n\n private readonly executionLogTime: number;\n\n constructor(\n name: string,\n uiContextBuilder: () => Promise<UIContext>,\n options?: TaskRunnerInitOptions,\n ) {\n this.id = uuid();\n this.status =\n options?.tasks && options.tasks.length > 0 ? 'pending' : 'init';\n this.name = name;\n this.tasks = (options?.tasks || []).map((item) =>\n this.markTaskAsPending(item),\n );\n this.onTaskStart = options?.onTaskStart;\n this.uiContextBuilder = uiContextBuilder;\n this.onTaskUpdate = options?.onTaskUpdate;\n this.executionLogTime = Date.now();\n }\n\n private async emitOnTaskUpdate(error?: TaskExecutionError): Promise<void> {\n if (!this.onTaskUpdate) {\n return;\n }\n await this.onTaskUpdate(this, error);\n }\n\n private lastUiContext?: {\n context: UIContext;\n capturedAt: number;\n };\n\n private async getUiContext(options?: { forceRefresh?: boolean }): Promise<\n UIContext | undefined\n > {\n const now = Date.now();\n const shouldReuse =\n !options?.forceRefresh &&\n this.lastUiContext &&\n now - this.lastUiContext.capturedAt <= UI_CONTEXT_CACHE_TTL_MS;\n\n if (shouldReuse && this.lastUiContext?.context) {\n debug(\n `reuse cached uiContext captured ${now - this.lastUiContext.capturedAt}ms ago`,\n );\n return this.lastUiContext?.context;\n }\n\n try {\n const uiContext = await this.uiContextBuilder();\n if (uiContext) {\n this.lastUiContext = {\n context: uiContext,\n capturedAt: Date.now(),\n };\n } else {\n this.lastUiContext = undefined;\n }\n return uiContext;\n } catch (error) {\n this.lastUiContext = undefined;\n throw error;\n }\n }\n\n private async captureScreenshot(): Promise<ScreenshotItem | undefined> {\n try {\n const uiContext = await this.getUiContext({ forceRefresh: true });\n return uiContext?.screenshot;\n } catch (error) {\n console.error('error while capturing screenshot', error);\n }\n return undefined;\n }\n\n private attachRecorderItem(\n task: ExecutionTask,\n screenshot: ScreenshotItem | undefined,\n phase: 'after-calling',\n ): void {\n if (!phase || !screenshot) {\n return;\n }\n\n const recorderItem: ExecutionRecorderItem = {\n type: 'screenshot',\n ts: Date.now(),\n screenshot,\n timing: phase,\n };\n\n if (!task.recorder) {\n task.recorder = [recorderItem];\n return;\n }\n task.recorder.push(recorderItem);\n }\n\n private markTaskAsPending(task: ExecutionTaskApply): ExecutionTask {\n return {\n taskId: uuid(),\n status: 'pending',\n ...task,\n };\n }\n\n private normalizeStatusFromError(\n options?: TaskRunnerOperationOptions,\n errorMessage?: string,\n ): void {\n if (this.status !== 'error') {\n return;\n }\n assert(\n options?.allowWhenError,\n errorMessage ||\n `task runner is in error state, cannot proceed\\nerror=${this.latestErrorTask()?.error}\\n${this.latestErrorTask()?.errorStack}`,\n );\n // reset runner state so new tasks can run\n this.status = this.tasks.length > 0 ? 'pending' : 'init';\n }\n\n async append(\n task: ExecutionTaskApply[] | ExecutionTaskApply,\n options?: TaskRunnerOperationOptions,\n ): Promise<void> {\n this.normalizeStatusFromError(\n options,\n `task runner is in error state, cannot append task\\nerror=${this.latestErrorTask()?.error}\\n${this.latestErrorTask()?.errorStack}`,\n );\n if (Array.isArray(task)) {\n this.tasks.push(...task.map((item) => this.markTaskAsPending(item)));\n } else {\n this.tasks.push(this.markTaskAsPending(task));\n }\n if (this.status !== 'running') {\n this.status = 'pending';\n }\n await this.emitOnTaskUpdate();\n }\n\n async appendAndFlush(\n task: ExecutionTaskApply[] | ExecutionTaskApply,\n options?: TaskRunnerOperationOptions,\n ): Promise<{ output: any; thought?: string } | undefined> {\n await this.append(task, options);\n return this.flush(options);\n }\n\n async flush(\n options?: TaskRunnerOperationOptions,\n ): Promise<{ output: any; thought?: string } | undefined> {\n if (this.status === 'init' && this.tasks.length > 0) {\n console.warn(\n 'illegal state for task runner, status is init but tasks are not empty',\n );\n }\n\n this.normalizeStatusFromError(options, 'task runner is in error state');\n assert(this.status !== 'running', 'task runner is already running');\n assert(this.status !== 'completed', 'task runner is already completed');\n\n const nextPendingIndex = this.tasks.findIndex(\n (task) => task.status === 'pending',\n );\n if (nextPendingIndex < 0) {\n // all tasks are completed\n return;\n }\n\n this.status = 'running';\n await this.emitOnTaskUpdate();\n let taskIndex = nextPendingIndex;\n let successfullyCompleted = true;\n\n let previousFindOutput: ExecutionTaskPlanningLocateOutput | undefined;\n\n while (taskIndex < this.tasks.length) {\n const task = this.tasks[taskIndex];\n assert(\n task.status === 'pending',\n `task status should be pending, but got: ${task.status}`,\n );\n task.timing = {\n start: Date.now(),\n };\n try {\n task.status = 'running';\n await this.emitOnTaskUpdate();\n try {\n if (this.onTaskStart) {\n await this.onTaskStart(task);\n }\n } catch (e) {\n console.error('error in onTaskStart', e);\n }\n assert(\n ['Insight', 'Action Space', 'Planning'].indexOf(task.type) >= 0,\n `unsupported task type: ${task.type}`,\n );\n\n const { executor, param } = task;\n assert(executor, `executor is required for task type: ${task.type}`);\n\n let returnValue;\n // For Insight tasks (Query/Assert/WaitFor), always get fresh context\n // to ensure we have the latest UI state after any preceding actions\n const forceRefresh = task.type === 'Insight';\n setTimingFieldOnce(task.timing, 'getUiContextStart');\n const uiContext = await this.getUiContext({ forceRefresh });\n setTimingFieldOnce(task.timing, 'getUiContextEnd');\n\n task.uiContext = uiContext;\n const executorContext: ExecutorContext = {\n task,\n element: previousFindOutput?.element,\n uiContext,\n };\n\n if (task.type === 'Insight') {\n assert(\n task.subType === 'Query' ||\n task.subType === 'Assert' ||\n task.subType === 'WaitFor' ||\n task.subType === 'Boolean' ||\n task.subType === 'Number' ||\n task.subType === 'String',\n `unsupported service subType: ${task.subType}`,\n );\n returnValue = await task.executor(param, executorContext);\n } else if (task.type === 'Planning') {\n returnValue = await task.executor(param, executorContext);\n if (task.subType === 'Locate') {\n previousFindOutput = (\n returnValue as ExecutionTaskReturn<ExecutionTaskPlanningLocateOutput>\n )?.output;\n }\n } else if (task.type === 'Action Space') {\n returnValue = await task.executor(param, executorContext);\n } else {\n console.warn(\n `unsupported task type: ${task.type}, will try to execute it directly`,\n );\n returnValue = await task.executor(param, executorContext);\n }\n\n const isLastTask = taskIndex === this.tasks.length - 1;\n\n if (isLastTask) {\n setTimingFieldOnce(task.timing, 'captureAfterCallingSnapshotStart');\n const screenshot = await this.captureScreenshot();\n this.attachRecorderItem(task, screenshot, 'after-calling');\n setTimingFieldOnce(task.timing, 'captureAfterCallingSnapshotEnd');\n }\n\n Object.assign(task, returnValue);\n task.status = 'finished';\n task.timing.end = Date.now();\n task.timing.cost = task.timing.end - task.timing.start;\n await this.emitOnTaskUpdate();\n taskIndex++;\n } catch (e: any) {\n successfullyCompleted = false;\n task.error = e;\n task.errorMessage =\n e?.message || (typeof e === 'string' ? e : 'error-without-message');\n task.errorStack = e.stack;\n\n task.status = 'failed';\n task.timing.end = Date.now();\n task.timing.cost = task.timing.end - task.timing.start;\n await this.emitOnTaskUpdate();\n break;\n }\n }\n\n // set all remaining tasks as cancelled\n for (let i = taskIndex + 1; i < this.tasks.length; i++) {\n this.tasks[i].status = 'cancelled';\n }\n if (taskIndex + 1 < this.tasks.length) {\n await this.emitOnTaskUpdate();\n }\n\n let finalizeError: TaskExecutionError | undefined;\n if (!successfullyCompleted) {\n this.status = 'error';\n const errorTask = this.latestErrorTask();\n const messageBase =\n errorTask?.errorMessage ||\n (errorTask?.error ? String(errorTask.error) : 'Task execution failed');\n const stack = errorTask?.errorStack;\n const message = stack ? `${messageBase}\\n${stack}` : messageBase;\n finalizeError = new TaskExecutionError(message, this, errorTask, {\n cause: errorTask?.error,\n });\n await this.emitOnTaskUpdate(finalizeError);\n } else {\n this.status = 'completed';\n await this.emitOnTaskUpdate();\n }\n\n if (finalizeError) {\n throw finalizeError;\n }\n\n if (this.tasks.length) {\n // return the last output\n const outputIndex = Math.min(taskIndex, this.tasks.length - 1);\n const { thought, output } = this.tasks[outputIndex];\n return {\n thought,\n output,\n };\n }\n }\n\n isInErrorState(): boolean {\n return this.status === 'error';\n }\n\n latestErrorTask(): ExecutionTask | null {\n if (this.status !== 'error') {\n return null;\n }\n // Find the LAST failed task (not the first one)\n // This is important when using allowWhenError to continue after errors\n for (let i = this.tasks.length - 1; i >= 0; i--) {\n if (this.tasks[i].status === 'failed') {\n return this.tasks[i];\n }\n }\n return null;\n }\n\n dump(): ExecutionDump {\n return new ExecutionDump({\n id: this.id,\n logTime: this.executionLogTime,\n name: this.name,\n tasks: this.tasks,\n });\n }\n\n async appendErrorPlan(errorMsg: string): Promise<{\n output: undefined;\n runner: TaskRunner;\n }> {\n const errorTask: ExecutionTaskActionApply<PlanningActionParamError> = {\n type: 'Action Space',\n subType: 'Error',\n param: {\n thought: errorMsg,\n },\n thought: errorMsg,\n executor: async () => {\n throw new Error(errorMsg || 'error without thought');\n },\n };\n await this.appendAndFlush(errorTask);\n\n return {\n output: undefined,\n runner: this,\n };\n }\n}\n\nexport class TaskExecutionError extends Error {\n runner: TaskRunner;\n\n errorTask: ExecutionTask | null;\n\n constructor(\n message: string,\n runner: TaskRunner,\n errorTask: ExecutionTask | null,\n options?: { cause?: unknown },\n ) {\n super(message, options);\n this.runner = runner;\n this.errorTask = errorTask;\n }\n}\n"],"names":["debug","getDebug","UI_CONTEXT_CACHE_TTL_MS","TaskRunner","error","options","now","Date","shouldReuse","uiContext","undefined","console","task","screenshot","phase","recorderItem","uuid","errorMessage","assert","Array","item","nextPendingIndex","taskIndex","successfullyCompleted","previousFindOutput","e","executor","param","returnValue","forceRefresh","setTimingFieldOnce","executorContext","isLastTask","Object","i","finalizeError","errorTask","messageBase","String","stack","message","TaskExecutionError","outputIndex","Math","thought","output","ExecutionDump","errorMsg","Error","name","uiContextBuilder","runner"],"mappings":";;;;;;;;;;;;;;AAkBA,MAAMA,QAAQC,SAAS;AACvB,MAAMC,0BAA0B;AAczB,MAAMC;IAqCX,MAAc,iBAAiBC,KAA0B,EAAiB;QACxE,IAAI,CAAC,IAAI,CAAC,YAAY,EACpB;QAEF,MAAM,IAAI,CAAC,YAAY,CAAC,IAAI,EAAEA;IAChC;IAOA,MAAc,aAAaC,OAAoC,EAE7D;QACA,MAAMC,MAAMC,KAAK,GAAG;QACpB,MAAMC,cACJ,CAACH,SAAS,gBACV,IAAI,CAAC,aAAa,IAClBC,MAAM,IAAI,CAAC,aAAa,CAAC,UAAU,IAAIJ;QAEzC,IAAIM,eAAe,IAAI,CAAC,aAAa,EAAE,SAAS;YAC9CR,MACE,CAAC,gCAAgC,EAAEM,MAAM,IAAI,CAAC,aAAa,CAAC,UAAU,CAAC,MAAM,CAAC;YAEhF,OAAO,IAAI,CAAC,aAAa,EAAE;QAC7B;QAEA,IAAI;YACF,MAAMG,YAAY,MAAM,IAAI,CAAC,gBAAgB;YAC7C,IAAIA,WACF,IAAI,CAAC,aAAa,GAAG;gBACnB,SAASA;gBACT,YAAYF,KAAK,GAAG;YACtB;iBAEA,IAAI,CAAC,aAAa,GAAGG;YAEvB,OAAOD;QACT,EAAE,OAAOL,OAAO;YACd,IAAI,CAAC,aAAa,GAAGM;YACrB,MAAMN;QACR;IACF;IAEA,MAAc,oBAAyD;QACrE,IAAI;YACF,MAAMK,YAAY,MAAM,IAAI,CAAC,YAAY,CAAC;gBAAE,cAAc;YAAK;YAC/D,OAAOA,WAAW;QACpB,EAAE,OAAOL,OAAO;YACdO,QAAQ,KAAK,CAAC,oCAAoCP;QACpD;IAEF;IAEQ,mBACNQ,IAAmB,EACnBC,UAAsC,EACtCC,KAAsB,EAChB;QACN,IAAI,CAACA,SAAS,CAACD,YACb;QAGF,MAAME,eAAsC;YAC1C,MAAM;YACN,IAAIR,KAAK,GAAG;YACZM;YACA,QAAQC;QACV;QAEA,IAAI,CAACF,KAAK,QAAQ,EAAE;YAClBA,KAAK,QAAQ,GAAG;gBAACG;aAAa;YAC9B;QACF;QACAH,KAAK,QAAQ,CAAC,IAAI,CAACG;IACrB;IAEQ,kBAAkBH,IAAwB,EAAiB;QACjE,OAAO;YACL,QAAQI;YACR,QAAQ;YACR,GAAGJ,IAAI;QACT;IACF;IAEQ,yBACNP,OAAoC,EACpCY,YAAqB,EACf;QACN,IAAI,AAAgB,YAAhB,IAAI,CAAC,MAAM,EACb;QAEFC,OACEb,SAAS,gBACTY,gBACE,CAAC,qDAAqD,EAAE,IAAI,CAAC,eAAe,IAAI,MAAM,EAAE,EAAE,IAAI,CAAC,eAAe,IAAI,YAAY;QAGlI,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,IAAI,YAAY;IACpD;IAEA,MAAM,OACJL,IAA+C,EAC/CP,OAAoC,EACrB;QACf,IAAI,CAAC,wBAAwB,CAC3BA,SACA,CAAC,yDAAyD,EAAE,IAAI,CAAC,eAAe,IAAI,MAAM,EAAE,EAAE,IAAI,CAAC,eAAe,IAAI,YAAY;QAEpI,IAAIc,MAAM,OAAO,CAACP,OAChB,IAAI,CAAC,KAAK,CAAC,IAAI,IAAIA,KAAK,GAAG,CAAC,CAACQ,OAAS,IAAI,CAAC,iBAAiB,CAACA;aAE7D,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,iBAAiB,CAACR;QAEzC,IAAI,AAAgB,cAAhB,IAAI,CAAC,MAAM,EACb,IAAI,CAAC,MAAM,GAAG;QAEhB,MAAM,IAAI,CAAC,gBAAgB;IAC7B;IAEA,MAAM,eACJA,IAA+C,EAC/CP,OAAoC,EACoB;QACxD,MAAM,IAAI,CAAC,MAAM,CAACO,MAAMP;QACxB,OAAO,IAAI,CAAC,KAAK,CAACA;IACpB;IAEA,MAAM,MACJA,OAAoC,EACoB;QACxD,IAAI,AAAgB,WAAhB,IAAI,CAAC,MAAM,IAAe,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,GAChDM,QAAQ,IAAI,CACV;QAIJ,IAAI,CAAC,wBAAwB,CAACN,SAAS;QACvCa,OAAO,AAAgB,cAAhB,IAAI,CAAC,MAAM,EAAgB;QAClCA,OAAO,AAAgB,gBAAhB,IAAI,CAAC,MAAM,EAAkB;QAEpC,MAAMG,mBAAmB,IAAI,CAAC,KAAK,CAAC,SAAS,CAC3C,CAACT,OAASA,AAAgB,cAAhBA,KAAK,MAAM;QAEvB,IAAIS,mBAAmB,GAErB;QAGF,IAAI,CAAC,MAAM,GAAG;QACd,MAAM,IAAI,CAAC,gBAAgB;QAC3B,IAAIC,YAAYD;QAChB,IAAIE,wBAAwB;QAE5B,IAAIC;QAEJ,MAAOF,YAAY,IAAI,CAAC,KAAK,CAAC,MAAM,CAAE;YACpC,MAAMV,OAAO,IAAI,CAAC,KAAK,CAACU,UAAU;YAClCJ,OACEN,AAAgB,cAAhBA,KAAK,MAAM,EACX,CAAC,wCAAwC,EAAEA,KAAK,MAAM,EAAE;YAE1DA,KAAK,MAAM,GAAG;gBACZ,OAAOL,KAAK,GAAG;YACjB;YACA,IAAI;gBACFK,KAAK,MAAM,GAAG;gBACd,MAAM,IAAI,CAAC,gBAAgB;gBAC3B,IAAI;oBACF,IAAI,IAAI,CAAC,WAAW,EAClB,MAAM,IAAI,CAAC,WAAW,CAACA;gBAE3B,EAAE,OAAOa,GAAG;oBACVd,QAAQ,KAAK,CAAC,wBAAwBc;gBACxC;gBACAP,OACE;oBAAC;oBAAW;oBAAgB;iBAAW,CAAC,OAAO,CAACN,KAAK,IAAI,KAAK,GAC9D,CAAC,uBAAuB,EAAEA,KAAK,IAAI,EAAE;gBAGvC,MAAM,EAAEc,QAAQ,EAAEC,KAAK,EAAE,GAAGf;gBAC5BM,OAAOQ,UAAU,CAAC,oCAAoC,EAAEd,KAAK,IAAI,EAAE;gBAEnE,IAAIgB;gBAGJ,MAAMC,eAAejB,AAAc,cAAdA,KAAK,IAAI;gBAC9BkB,mBAAmBlB,KAAK,MAAM,EAAE;gBAChC,MAAMH,YAAY,MAAM,IAAI,CAAC,YAAY,CAAC;oBAAEoB;gBAAa;gBACzDC,mBAAmBlB,KAAK,MAAM,EAAE;gBAEhCA,KAAK,SAAS,GAAGH;gBACjB,MAAMsB,kBAAmC;oBACvCnB;oBACA,SAASY,oBAAoB;oBAC7Bf;gBACF;gBAEA,IAAIG,AAAc,cAAdA,KAAK,IAAI,EAAgB;oBAC3BM,OACEN,AAAiB,YAAjBA,KAAK,OAAO,IACVA,AAAiB,aAAjBA,KAAK,OAAO,IACZA,AAAiB,cAAjBA,KAAK,OAAO,IACZA,AAAiB,cAAjBA,KAAK,OAAO,IACZA,AAAiB,aAAjBA,KAAK,OAAO,IACZA,AAAiB,aAAjBA,KAAK,OAAO,EACd,CAAC,6BAA6B,EAAEA,KAAK,OAAO,EAAE;oBAEhDgB,cAAc,MAAMhB,KAAK,QAAQ,CAACe,OAAOI;gBAC3C,OAAO,IAAInB,AAAc,eAAdA,KAAK,IAAI,EAAiB;oBACnCgB,cAAc,MAAMhB,KAAK,QAAQ,CAACe,OAAOI;oBACzC,IAAInB,AAAiB,aAAjBA,KAAK,OAAO,EACdY,qBACEI,aACC;gBAEP,OAAO,IAAIhB,AAAc,mBAAdA,KAAK,IAAI,EAClBgB,cAAc,MAAMhB,KAAK,QAAQ,CAACe,OAAOI;qBACpC;oBACLpB,QAAQ,IAAI,CACV,CAAC,uBAAuB,EAAEC,KAAK,IAAI,CAAC,iCAAiC,CAAC;oBAExEgB,cAAc,MAAMhB,KAAK,QAAQ,CAACe,OAAOI;gBAC3C;gBAEA,MAAMC,aAAaV,cAAc,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG;gBAErD,IAAIU,YAAY;oBACdF,mBAAmBlB,KAAK,MAAM,EAAE;oBAChC,MAAMC,aAAa,MAAM,IAAI,CAAC,iBAAiB;oBAC/C,IAAI,CAAC,kBAAkB,CAACD,MAAMC,YAAY;oBAC1CiB,mBAAmBlB,KAAK,MAAM,EAAE;gBAClC;gBAEAqB,OAAO,MAAM,CAACrB,MAAMgB;gBACpBhB,KAAK,MAAM,GAAG;gBACdA,KAAK,MAAM,CAAC,GAAG,GAAGL,KAAK,GAAG;gBAC1BK,KAAK,MAAM,CAAC,IAAI,GAAGA,KAAK,MAAM,CAAC,GAAG,GAAGA,KAAK,MAAM,CAAC,KAAK;gBACtD,MAAM,IAAI,CAAC,gBAAgB;gBAC3BU;YACF,EAAE,OAAOG,GAAQ;gBACfF,wBAAwB;gBACxBX,KAAK,KAAK,GAAGa;gBACbb,KAAK,YAAY,GACfa,GAAG,WAAY,CAAa,YAAb,OAAOA,IAAiBA,IAAI,uBAAsB;gBACnEb,KAAK,UAAU,GAAGa,EAAE,KAAK;gBAEzBb,KAAK,MAAM,GAAG;gBACdA,KAAK,MAAM,CAAC,GAAG,GAAGL,KAAK,GAAG;gBAC1BK,KAAK,MAAM,CAAC,IAAI,GAAGA,KAAK,MAAM,CAAC,GAAG,GAAGA,KAAK,MAAM,CAAC,KAAK;gBACtD,MAAM,IAAI,CAAC,gBAAgB;gBAC3B;YACF;QACF;QAGA,IAAK,IAAIsB,IAAIZ,YAAY,GAAGY,IAAI,IAAI,CAAC,KAAK,CAAC,MAAM,EAAEA,IACjD,IAAI,CAAC,KAAK,CAACA,EAAE,CAAC,MAAM,GAAG;QAEzB,IAAIZ,YAAY,IAAI,IAAI,CAAC,KAAK,CAAC,MAAM,EACnC,MAAM,IAAI,CAAC,gBAAgB;QAG7B,IAAIa;QACJ,IAAKZ,uBAYE;YACL,IAAI,CAAC,MAAM,GAAG;YACd,MAAM,IAAI,CAAC,gBAAgB;QAC7B,OAf4B;YAC1B,IAAI,CAAC,MAAM,GAAG;YACd,MAAMa,YAAY,IAAI,CAAC,eAAe;YACtC,MAAMC,cACJD,WAAW,gBACVA,CAAAA,WAAW,QAAQE,OAAOF,UAAU,KAAK,IAAI,uBAAsB;YACtE,MAAMG,QAAQH,WAAW;YACzB,MAAMI,UAAUD,QAAQ,GAAGF,YAAY,EAAE,EAAEE,OAAO,GAAGF;YACrDF,gBAAgB,IAAIM,mBAAmBD,SAAS,IAAI,EAAEJ,WAAW;gBAC/D,OAAOA,WAAW;YACpB;YACA,MAAM,IAAI,CAAC,gBAAgB,CAACD;QAC9B;QAKA,IAAIA,eACF,MAAMA;QAGR,IAAI,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE;YAErB,MAAMO,cAAcC,KAAK,GAAG,CAACrB,WAAW,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG;YAC5D,MAAM,EAAEsB,OAAO,EAAEC,MAAM,EAAE,GAAG,IAAI,CAAC,KAAK,CAACH,YAAY;YACnD,OAAO;gBACLE;gBACAC;YACF;QACF;IACF;IAEA,iBAA0B;QACxB,OAAO,AAAgB,YAAhB,IAAI,CAAC,MAAM;IACpB;IAEA,kBAAwC;QACtC,IAAI,AAAgB,YAAhB,IAAI,CAAC,MAAM,EACb,OAAO;QAIT,IAAK,IAAIX,IAAI,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,GAAGA,KAAK,GAAGA,IAC1C,IAAI,AAAyB,aAAzB,IAAI,CAAC,KAAK,CAACA,EAAE,CAAC,MAAM,EACtB,OAAO,IAAI,CAAC,KAAK,CAACA,EAAE;QAGxB,OAAO;IACT;IAEA,OAAsB;QACpB,OAAO,IAAIY,cAAc;YACvB,IAAI,IAAI,CAAC,EAAE;YACX,SAAS,IAAI,CAAC,gBAAgB;YAC9B,MAAM,IAAI,CAAC,IAAI;YACf,OAAO,IAAI,CAAC,KAAK;QACnB;IACF;IAEA,MAAM,gBAAgBC,QAAgB,EAGnC;QACD,MAAMX,YAAgE;YACpE,MAAM;YACN,SAAS;YACT,OAAO;gBACL,SAASW;YACX;YACA,SAASA;YACT,UAAU;gBACR,MAAM,IAAIC,MAAMD,YAAY;YAC9B;QACF;QACA,MAAM,IAAI,CAAC,cAAc,CAACX;QAE1B,OAAO;YACL,QAAQ1B;YACR,QAAQ,IAAI;QACd;IACF;IA3WA,YACEuC,IAAY,EACZC,gBAA0C,EAC1C7C,OAA+B,CAC/B;QAtBF,uBAAS,MAAT;QACA;QAEA;QAGA;QAEA;QAEA,uBAAiB,oBAAjB;QAEA,uBAAiB,gBAAjB;QAIA,uBAAiB,oBAAjB;QA2BA,uBAAQ,iBAAR;QApBE,IAAI,CAAC,EAAE,GAAGW;QACV,IAAI,CAAC,MAAM,GACTX,SAAS,SAASA,QAAQ,KAAK,CAAC,MAAM,GAAG,IAAI,YAAY;QAC3D,IAAI,CAAC,IAAI,GAAG4C;QACZ,IAAI,CAAC,KAAK,GAAI5C,AAAAA,CAAAA,SAAS,SAAS,EAAC,EAAG,GAAG,CAAC,CAACe,OACvC,IAAI,CAAC,iBAAiB,CAACA;QAEzB,IAAI,CAAC,WAAW,GAAGf,SAAS;QAC5B,IAAI,CAAC,gBAAgB,GAAG6C;QACxB,IAAI,CAAC,YAAY,GAAG7C,SAAS;QAC7B,IAAI,CAAC,gBAAgB,GAAGE,KAAK,GAAG;IAClC;AA4VF;AAEO,MAAMkC,2BAA2BO;IAKtC,YACER,OAAe,EACfW,MAAkB,EAClBf,SAA+B,EAC/B/B,OAA6B,CAC7B;QACA,KAAK,CAACmC,SAASnC,UAVjB,0CAEA;QASE,IAAI,CAAC,MAAM,GAAG8C;QACd,IAAI,CAAC,SAAS,GAAGf;IACnB;AACF"}
1
+ {"version":3,"file":"task-runner.mjs","sources":["../../src/task-runner.ts"],"sourcesContent":["import type { ScreenshotItem } from '@/screenshot-item';\nimport { setTimingFieldOnce } from '@/task-timing';\nimport {\n ExecutionDump,\n type ExecutionRecorderItem,\n type ExecutionTask,\n type ExecutionTaskActionApply,\n type ExecutionTaskApply,\n type ExecutionTaskPlanningLocateOutput,\n type ExecutionTaskProgressOptions,\n type ExecutionTaskReturn,\n type ExecutorContext,\n type PlanningActionParamError,\n type UIContext,\n} from '@/types';\nimport { getDebug } from '@aiscene/shared/logger';\nimport { assert, uuid } from '@aiscene/shared/utils';\n\nconst debug = getDebug('task-runner');\nconst UI_CONTEXT_CACHE_TTL_MS = 300;\n\ntype TaskRunnerInitOptions = ExecutionTaskProgressOptions & {\n tasks?: ExecutionTaskApply[];\n onTaskUpdate?: (\n runner: TaskRunner,\n error?: TaskExecutionError,\n ) => Promise<void> | void;\n};\n\ntype TaskRunnerOperationOptions = {\n allowWhenError?: boolean;\n};\n\nexport class TaskRunner {\n readonly id: string;\n name: string;\n\n tasks: ExecutionTask[];\n\n // status of runner\n status: 'init' | 'pending' | 'running' | 'completed' | 'error';\n\n onTaskStart?: ExecutionTaskProgressOptions['onTaskStart'];\n\n private readonly uiContextBuilder: () => Promise<UIContext>;\n\n private readonly onTaskUpdate?:\n | ((runner: TaskRunner, error?: TaskExecutionError) => Promise<void> | void)\n | undefined;\n\n private readonly executionLogTime: number;\n\n constructor(\n name: string,\n uiContextBuilder: () => Promise<UIContext>,\n options?: TaskRunnerInitOptions,\n ) {\n this.id = uuid();\n this.status =\n options?.tasks && options.tasks.length > 0 ? 'pending' : 'init';\n this.name = name;\n this.tasks = (options?.tasks || []).map((item) =>\n this.markTaskAsPending(item),\n );\n this.onTaskStart = options?.onTaskStart;\n this.uiContextBuilder = uiContextBuilder;\n this.onTaskUpdate = options?.onTaskUpdate;\n this.executionLogTime = Date.now();\n }\n\n private async emitOnTaskUpdate(error?: TaskExecutionError): Promise<void> {\n if (!this.onTaskUpdate) {\n return;\n }\n await this.onTaskUpdate(this, error);\n }\n\n private lastUiContext?: {\n context: UIContext;\n capturedAt: number;\n };\n\n private async getUiContext(options?: { forceRefresh?: boolean }): Promise<\n UIContext | undefined\n > {\n const now = Date.now();\n const shouldReuse =\n !options?.forceRefresh &&\n this.lastUiContext &&\n now - this.lastUiContext.capturedAt <= UI_CONTEXT_CACHE_TTL_MS;\n\n if (shouldReuse && this.lastUiContext?.context) {\n debug(\n `reuse cached uiContext captured ${now - this.lastUiContext.capturedAt}ms ago`,\n );\n return this.lastUiContext?.context;\n }\n\n try {\n const uiContext = await this.uiContextBuilder();\n if (uiContext) {\n this.lastUiContext = {\n context: uiContext,\n capturedAt: Date.now(),\n };\n } else {\n this.lastUiContext = undefined;\n }\n return uiContext;\n } catch (error) {\n this.lastUiContext = undefined;\n throw error;\n }\n }\n\n private async captureScreenshot(): Promise<ScreenshotItem | undefined> {\n try {\n const uiContext = await this.getUiContext({ forceRefresh: true });\n return uiContext?.screenshot;\n } catch (error) {\n console.error('error while capturing screenshot', error);\n }\n return undefined;\n }\n\n private attachRecorderItem(\n task: ExecutionTask,\n screenshot: ScreenshotItem | undefined,\n phase: 'after-calling',\n ): void {\n if (!phase || !screenshot) {\n return;\n }\n\n const recorderItem: ExecutionRecorderItem = {\n type: 'screenshot',\n ts: Date.now(),\n screenshot,\n timing: phase,\n };\n\n if (!task.recorder) {\n task.recorder = [recorderItem];\n return;\n }\n task.recorder.push(recorderItem);\n }\n\n private markTaskAsPending(task: ExecutionTaskApply): ExecutionTask {\n return {\n taskId: uuid(),\n status: 'pending',\n ...task,\n };\n }\n\n private normalizeStatusFromError(\n options?: TaskRunnerOperationOptions,\n errorMessage?: string,\n ): void {\n if (this.status !== 'error') {\n return;\n }\n assert(\n options?.allowWhenError,\n errorMessage ||\n `task runner is in error state, cannot proceed\\nerror=${this.latestErrorTask()?.error}\\n${this.latestErrorTask()?.errorStack}`,\n );\n // reset runner state so new tasks can run\n this.status = this.tasks.length > 0 ? 'pending' : 'init';\n }\n\n async append(\n task: ExecutionTaskApply[] | ExecutionTaskApply,\n options?: TaskRunnerOperationOptions,\n ): Promise<void> {\n this.normalizeStatusFromError(\n options,\n `task runner is in error state, cannot append task\\nerror=${this.latestErrorTask()?.error}\\n${this.latestErrorTask()?.errorStack}`,\n );\n if (Array.isArray(task)) {\n this.tasks.push(...task.map((item) => this.markTaskAsPending(item)));\n } else {\n this.tasks.push(this.markTaskAsPending(task));\n }\n if (this.status !== 'running') {\n this.status = 'pending';\n }\n await this.emitOnTaskUpdate();\n }\n\n async appendAndFlush(\n task: ExecutionTaskApply[] | ExecutionTaskApply,\n options?: TaskRunnerOperationOptions,\n ): Promise<{ output: any; thought?: string } | undefined> {\n await this.append(task, options);\n return this.flush(options);\n }\n\n async flush(\n options?: TaskRunnerOperationOptions,\n ): Promise<{ output: any; thought?: string } | undefined> {\n if (this.status === 'init' && this.tasks.length > 0) {\n console.warn(\n 'illegal state for task runner, status is init but tasks are not empty',\n );\n }\n\n this.normalizeStatusFromError(options, 'task runner is in error state');\n assert(this.status !== 'running', 'task runner is already running');\n assert(this.status !== 'completed', 'task runner is already completed');\n\n const nextPendingIndex = this.tasks.findIndex(\n (task) => task.status === 'pending',\n );\n if (nextPendingIndex < 0) {\n // all tasks are completed\n return;\n }\n\n this.status = 'running';\n await this.emitOnTaskUpdate();\n let taskIndex = nextPendingIndex;\n let successfullyCompleted = true;\n\n let previousFindOutput: ExecutionTaskPlanningLocateOutput | undefined;\n\n while (taskIndex < this.tasks.length) {\n const task = this.tasks[taskIndex];\n assert(\n task.status === 'pending',\n `task status should be pending, but got: ${task.status}`,\n );\n task.timing = {\n start: Date.now(),\n };\n try {\n task.status = 'running';\n await this.emitOnTaskUpdate();\n try {\n if (this.onTaskStart) {\n await this.onTaskStart(task);\n }\n } catch (e) {\n console.error('error in onTaskStart', e);\n }\n assert(\n ['Insight', 'Action Space', 'Planning'].indexOf(task.type) >= 0,\n `unsupported task type: ${task.type}`,\n );\n\n const { executor, param } = task;\n assert(executor, `executor is required for task type: ${task.type}`);\n\n let returnValue;\n // For Insight tasks (Query/Assert/WaitFor), always get fresh context\n // to ensure we have the latest UI state after any preceding actions\n const forceRefresh = task.type === 'Insight';\n setTimingFieldOnce(task.timing, 'getUiContextStart');\n const uiContext = await this.getUiContext({ forceRefresh });\n setTimingFieldOnce(task.timing, 'getUiContextEnd');\n\n task.uiContext = uiContext;\n const executorContext: ExecutorContext = {\n task,\n element: previousFindOutput?.element,\n uiContext,\n };\n\n if (task.type === 'Insight') {\n assert(\n task.subType === 'Query' ||\n task.subType === 'Assert' ||\n task.subType === 'WaitFor' ||\n task.subType === 'Boolean' ||\n task.subType === 'Number' ||\n task.subType === 'String',\n `unsupported service subType: ${task.subType}`,\n );\n returnValue = await task.executor(param, executorContext);\n } else if (task.type === 'Planning') {\n returnValue = await task.executor(param, executorContext);\n if (task.subType === 'Locate') {\n previousFindOutput = (\n returnValue as ExecutionTaskReturn<ExecutionTaskPlanningLocateOutput>\n )?.output;\n }\n } else if (task.type === 'Action Space') {\n returnValue = await task.executor(param, executorContext);\n } else {\n console.warn(\n `unsupported task type: ${task.type}, will try to execute it directly`,\n );\n returnValue = await task.executor(param, executorContext);\n }\n\n const isLastTask = taskIndex === this.tasks.length - 1;\n\n if (isLastTask) {\n setTimingFieldOnce(task.timing, 'captureAfterCallingSnapshotStart');\n const screenshot = await this.captureScreenshot();\n this.attachRecorderItem(task, screenshot, 'after-calling');\n setTimingFieldOnce(task.timing, 'captureAfterCallingSnapshotEnd');\n }\n\n Object.assign(task, returnValue);\n task.status = 'finished';\n task.timing.end = Date.now();\n task.timing.cost = task.timing.end - task.timing.start;\n await this.emitOnTaskUpdate();\n taskIndex++;\n } catch (e: any) {\n successfullyCompleted = false;\n task.error = e;\n task.errorMessage =\n e?.message || (typeof e === 'string' ? e : 'error-without-message');\n task.errorStack = e.stack;\n\n task.status = 'failed';\n task.timing.end = Date.now();\n task.timing.cost = task.timing.end - task.timing.start;\n await this.emitOnTaskUpdate();\n break;\n }\n }\n\n // set all remaining tasks as cancelled\n for (let i = taskIndex + 1; i < this.tasks.length; i++) {\n this.tasks[i].status = 'cancelled';\n }\n if (taskIndex + 1 < this.tasks.length) {\n await this.emitOnTaskUpdate();\n }\n\n let finalizeError: TaskExecutionError | undefined;\n if (!successfullyCompleted) {\n this.status = 'error';\n const errorTask = this.latestErrorTask();\n const messageBase =\n errorTask?.errorMessage ||\n (errorTask?.error ? String(errorTask.error) : 'Task execution failed');\n const stack = errorTask?.errorStack;\n const message = stack ? `${messageBase}\\n${stack}` : messageBase;\n finalizeError = new TaskExecutionError(message, this, errorTask, {\n cause: errorTask?.error,\n });\n await this.emitOnTaskUpdate(finalizeError);\n } else {\n this.status = 'completed';\n await this.emitOnTaskUpdate();\n }\n\n if (finalizeError) {\n throw finalizeError;\n }\n\n if (this.tasks.length) {\n // return the last output\n const outputIndex = Math.min(taskIndex, this.tasks.length - 1);\n const { thought, output } = this.tasks[outputIndex];\n return {\n thought,\n output,\n };\n }\n }\n\n isInErrorState(): boolean {\n return this.status === 'error';\n }\n\n latestErrorTask(): ExecutionTask | null {\n if (this.status !== 'error') {\n return null;\n }\n // Find the LAST failed task (not the first one)\n // This is important when using allowWhenError to continue after errors\n for (let i = this.tasks.length - 1; i >= 0; i--) {\n if (this.tasks[i].status === 'failed') {\n return this.tasks[i];\n }\n }\n return null;\n }\n\n dump(): ExecutionDump {\n return new ExecutionDump({\n id: this.id,\n logTime: this.executionLogTime,\n name: this.name,\n tasks: this.tasks,\n });\n }\n\n async appendErrorPlan(errorMsg: string): Promise<{\n output: undefined;\n runner: TaskRunner;\n }> {\n const errorTask: ExecutionTaskActionApply<PlanningActionParamError> = {\n type: 'Action Space',\n subType: 'Error',\n param: {\n thought: errorMsg,\n },\n thought: errorMsg,\n executor: async () => {\n throw new Error(errorMsg || 'error without thought');\n },\n };\n await this.appendAndFlush(errorTask);\n\n return {\n output: undefined,\n runner: this,\n };\n }\n}\n\nexport class TaskExecutionError extends Error {\n runner: TaskRunner;\n\n errorTask: ExecutionTask | null;\n\n constructor(\n message: string,\n runner: TaskRunner,\n errorTask: ExecutionTask | null,\n options?: { cause?: unknown },\n ) {\n super(message, options);\n this.runner = runner;\n this.errorTask = errorTask;\n }\n}\n"],"names":["debug","getDebug","UI_CONTEXT_CACHE_TTL_MS","TaskRunner","error","options","now","Date","shouldReuse","uiContext","undefined","console","task","screenshot","phase","recorderItem","uuid","errorMessage","assert","Array","item","nextPendingIndex","taskIndex","successfullyCompleted","previousFindOutput","e","executor","param","returnValue","forceRefresh","setTimingFieldOnce","executorContext","isLastTask","Object","i","finalizeError","errorTask","messageBase","String","stack","message","TaskExecutionError","outputIndex","Math","thought","output","ExecutionDump","errorMsg","Error","name","uiContextBuilder","runner"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { getDebug } from "@midscene/shared/logger";
1
+ import { getDebug } from "@aiscene/shared/logger";
2
2
  const debugTiming = getDebug('task-timing');
3
3
  function setTimingFieldOnce(timing, field) {
4
4
  if (!timing) return void debugTiming(`[warning] timing object missing, skip set. field=${field}`);
@@ -1 +1 @@
1
- {"version":3,"file":"task-timing.mjs","sources":["../../src/task-timing.ts"],"sourcesContent":["import type { ExecutionTask } from '@/types';\nimport { getDebug } from '@midscene/shared/logger';\n\nconst debugTiming = getDebug('task-timing');\n\ntype ExecutionTaskTiming = NonNullable<ExecutionTask['timing']>;\n\ntype NumericTimingField = {\n [K in keyof ExecutionTaskTiming]-?: ExecutionTaskTiming[K] extends\n | number\n | undefined\n ? K\n : never;\n}[keyof ExecutionTaskTiming];\n\nexport type TimingSettableField = Exclude<\n NumericTimingField,\n 'start' | 'end' | 'cost'\n>;\n\nexport function setTimingFieldOnce(\n timing: ExecutionTaskTiming | undefined,\n field: TimingSettableField,\n): void {\n if (!timing) {\n debugTiming(`[warning] timing object missing, skip set. field=${field}`);\n return;\n }\n\n const value = Date.now();\n const existingValue = timing[field];\n if (existingValue !== undefined) {\n debugTiming(\n `[warning] duplicate timing field set ignored. field=${field}, existing=${existingValue}, incoming=${value}`,\n );\n return;\n }\n\n timing[field] = value;\n}\n"],"names":["debugTiming","getDebug","setTimingFieldOnce","timing","field","value","Date","existingValue","undefined"],"mappings":";AAGA,MAAMA,cAAcC,SAAS;AAiBtB,SAASC,mBACdC,MAAuC,EACvCC,KAA0B;IAE1B,IAAI,CAACD,QAAQ,YACXH,YAAY,CAAC,iDAAiD,EAAEI,OAAO;IAIzE,MAAMC,QAAQC,KAAK,GAAG;IACtB,MAAMC,gBAAgBJ,MAAM,CAACC,MAAM;IACnC,IAAIG,AAAkBC,WAAlBD,eAA6B,YAC/BP,YACE,CAAC,oDAAoD,EAAEI,MAAM,WAAW,EAAEG,cAAc,WAAW,EAAEF,OAAO;IAKhHF,MAAM,CAACC,MAAM,GAAGC;AAClB"}
1
+ {"version":3,"file":"task-timing.mjs","sources":["../../src/task-timing.ts"],"sourcesContent":["import type { ExecutionTask } from '@/types';\nimport { getDebug } from '@aiscene/shared/logger';\n\nconst debugTiming = getDebug('task-timing');\n\ntype ExecutionTaskTiming = NonNullable<ExecutionTask['timing']>;\n\ntype NumericTimingField = {\n [K in keyof ExecutionTaskTiming]-?: ExecutionTaskTiming[K] extends\n | number\n | undefined\n ? K\n : never;\n}[keyof ExecutionTaskTiming];\n\nexport type TimingSettableField = Exclude<\n NumericTimingField,\n 'start' | 'end' | 'cost'\n>;\n\nexport function setTimingFieldOnce(\n timing: ExecutionTaskTiming | undefined,\n field: TimingSettableField,\n): void {\n if (!timing) {\n debugTiming(`[warning] timing object missing, skip set. field=${field}`);\n return;\n }\n\n const value = Date.now();\n const existingValue = timing[field];\n if (existingValue !== undefined) {\n debugTiming(\n `[warning] duplicate timing field set ignored. field=${field}, existing=${existingValue}, incoming=${value}`,\n );\n return;\n }\n\n timing[field] = value;\n}\n"],"names":["debugTiming","getDebug","setTimingFieldOnce","timing","field","value","Date","existingValue","undefined"],"mappings":";AAGA,MAAMA,cAAcC,SAAS;AAiBtB,SAASC,mBACdC,MAAuC,EACvCC,KAA0B;IAE1B,IAAI,CAACD,QAAQ,YACXH,YAAY,CAAC,iDAAiD,EAAEI,OAAO;IAIzE,MAAMC,QAAQC,KAAK,GAAG;IACtB,MAAMC,gBAAgBJ,MAAM,CAACC,MAAM;IACnC,IAAIG,AAAkBC,WAAlBD,eAA6B,YAC/BP,YACE,CAAC,oDAAoD,EAAEI,MAAM,WAAW,EAAEG,cAAc,WAAW,EAAEF,OAAO;IAKhHF,MAAM,CAACC,MAAM,GAAGC;AAClB"}
package/dist/es/tree.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { descriptionOfTree, treeToList, trimAttributes, truncateText } from "@midscene/shared/extractor";
1
+ import { descriptionOfTree, treeToList, trimAttributes, truncateText } from "@aiscene/shared/extractor";
2
2
  const ELEMENT_COUNT_WARNING_THRESHOLD = 5000;
3
3
  const TREE_SIZE_WARNING_MESSAGE = 'The number of elements is too large, it may cause the prompt to be too long, please use domIncluded: "visible-only" to reduce the number of elements';
4
4
  function tree_descriptionOfTree(tree, truncateTextLength, filterNonTextContent = false, visibleOnly = true) {
@@ -1 +1 @@
1
- {"version":3,"file":"tree.mjs","sources":["../../src/tree.ts"],"sourcesContent":["import type { BaseElement, ElementTreeNode } from '@midscene/shared/types';\n\nimport {\n descriptionOfTree as sharedDescriptionOfTree,\n treeToList,\n trimAttributes,\n truncateText,\n} from '@midscene/shared/extractor';\n\nconst ELEMENT_COUNT_WARNING_THRESHOLD = 5000;\nconst TREE_SIZE_WARNING_MESSAGE =\n 'The number of elements is too large, it may cause the prompt to be too long, please use domIncluded: \"visible-only\" to reduce the number of elements';\n\nexport { trimAttributes, truncateText };\n\nexport function descriptionOfTree<\n ElementType extends BaseElement = BaseElement,\n>(\n tree: ElementTreeNode<ElementType>,\n truncateTextLength?: number,\n filterNonTextContent = false,\n visibleOnly = true,\n) {\n if (!visibleOnly) {\n const flatElements = treeToList(tree);\n if (flatElements.length >= ELEMENT_COUNT_WARNING_THRESHOLD) {\n console.warn(TREE_SIZE_WARNING_MESSAGE);\n }\n }\n\n return sharedDescriptionOfTree(\n tree,\n truncateTextLength,\n filterNonTextContent,\n visibleOnly,\n );\n}\n"],"names":["ELEMENT_COUNT_WARNING_THRESHOLD","TREE_SIZE_WARNING_MESSAGE","descriptionOfTree","tree","truncateTextLength","filterNonTextContent","visibleOnly","flatElements","treeToList","console","sharedDescriptionOfTree"],"mappings":";AASA,MAAMA,kCAAkC;AACxC,MAAMC,4BACJ;AAIK,SAASC,uBAGdC,IAAkC,EAClCC,kBAA2B,EAC3BC,uBAAuB,KAAK,EAC5BC,cAAc,IAAI;IAElB,IAAI,CAACA,aAAa;QAChB,MAAMC,eAAeC,WAAWL;QAChC,IAAII,aAAa,MAAM,IAAIP,iCACzBS,QAAQ,IAAI,CAACR;IAEjB;IAEA,OAAOS,kBACLP,MACAC,oBACAC,sBACAC;AAEJ"}
1
+ {"version":3,"file":"tree.mjs","sources":["../../src/tree.ts"],"sourcesContent":["import type { BaseElement, ElementTreeNode } from '@aiscene/shared/types';\n\nimport {\n descriptionOfTree as sharedDescriptionOfTree,\n treeToList,\n trimAttributes,\n truncateText,\n} from '@aiscene/shared/extractor';\n\nconst ELEMENT_COUNT_WARNING_THRESHOLD = 5000;\nconst TREE_SIZE_WARNING_MESSAGE =\n 'The number of elements is too large, it may cause the prompt to be too long, please use domIncluded: \"visible-only\" to reduce the number of elements';\n\nexport { trimAttributes, truncateText };\n\nexport function descriptionOfTree<\n ElementType extends BaseElement = BaseElement,\n>(\n tree: ElementTreeNode<ElementType>,\n truncateTextLength?: number,\n filterNonTextContent = false,\n visibleOnly = true,\n) {\n if (!visibleOnly) {\n const flatElements = treeToList(tree);\n if (flatElements.length >= ELEMENT_COUNT_WARNING_THRESHOLD) {\n console.warn(TREE_SIZE_WARNING_MESSAGE);\n }\n }\n\n return sharedDescriptionOfTree(\n tree,\n truncateTextLength,\n filterNonTextContent,\n visibleOnly,\n );\n}\n"],"names":["ELEMENT_COUNT_WARNING_THRESHOLD","TREE_SIZE_WARNING_MESSAGE","descriptionOfTree","tree","truncateTextLength","filterNonTextContent","visibleOnly","flatElements","treeToList","console","sharedDescriptionOfTree"],"mappings":";AASA,MAAMA,kCAAkC;AACxC,MAAMC,4BACJ;AAIK,SAASC,uBAGdC,IAAkC,EAClCC,kBAA2B,EAC3BC,uBAAuB,KAAK,EAC5BC,cAAc,IAAI;IAElB,IAAI,CAACA,aAAa;QAChB,MAAMC,eAAeC,WAAWL;QAChC,IAAII,aAAa,MAAM,IAAIP,iCACzBS,QAAQ,IAAI,CAACR;IAEjB;IAEA,OAAOS,kBACLP,MACAC,oBACAC,sBACAC;AAEJ"}
@@ -1 +1 @@
1
- {"version":3,"file":"types.mjs","sources":["../../src/types.ts"],"sourcesContent":["/* eslint-disable @typescript-eslint/no-explicit-any */\n\nimport {\n existsSync,\n mkdirSync,\n readFileSync,\n rmSync,\n writeFileSync,\n} from 'node:fs';\nimport { join } from 'node:path';\nimport type { NodeType } from '@midscene/shared/constants';\nimport type { CreateOpenAIClientFn, TModelConfig } from '@midscene/shared/env';\nimport type {\n BaseElement,\n LocateResultElement,\n Rect,\n Size,\n} from '@midscene/shared/types';\nimport type { z } from 'zod';\nimport type { TUserPrompt } from './common';\nimport { restoreImageReferences } from './dump/screenshot-restoration';\nimport { ScreenshotStore } from './dump/screenshot-store';\nimport { ScreenshotItem } from './screenshot-item';\nimport type {\n DetailedLocateParam,\n MidsceneYamlFlowItem,\n ServiceExtractOption,\n} from './yaml';\n\nexport type {\n ElementTreeNode,\n BaseElement,\n Rect,\n Size,\n Point,\n} from '@midscene/shared/types';\nexport * from './yaml';\n\nexport type AIUsageInfo = Record<string, any> & {\n prompt_tokens: number | undefined;\n completion_tokens: number | undefined;\n total_tokens: number | undefined;\n cached_input: number | undefined;\n time_cost: number | undefined;\n model_name: string | undefined;\n model_description: string | undefined;\n intent: string | undefined;\n request_id: string | undefined;\n};\n\nexport type { LocateResultElement };\n\nexport type AISingleElementResponseByPosition = {\n position?: {\n x: number;\n y: number;\n };\n bbox?: [number, number, number, number];\n reason: string;\n text: string;\n};\n\nexport interface AIElementCoordinatesResponse {\n bbox: [number, number, number, number];\n errors?: string[];\n}\n\nexport type AIElementResponse = AIElementCoordinatesResponse;\n\nexport interface AIDataExtractionResponse<DataDemand> {\n data: DataDemand;\n errors?: string[];\n thought?: string;\n}\n\nexport interface AISectionLocatorResponse {\n bbox: [number, number, number, number];\n references_bbox?: [number, number, number, number][];\n error?: string;\n}\n\nexport interface AIAssertionResponse {\n pass: boolean;\n thought: string;\n}\n\nexport interface AIDescribeElementResponse {\n description: string;\n error?: string;\n}\n\nexport interface LocatorValidatorOption {\n centerDistanceThreshold?: number;\n}\n\nexport interface LocateValidatorResult {\n pass: boolean;\n rect: Rect;\n center: [number, number];\n centerDistance?: number;\n}\n\nexport interface AgentDescribeElementAtPointResult {\n prompt: string;\n deepLocate: boolean;\n verifyResult?: LocateValidatorResult;\n}\n\n/**\n * context\n */\n\nexport abstract class UIContext {\n /**\n * screenshot of the current UI state. which size is shotSize(be shrunk by screenshotShrinkFactor),\n */\n abstract screenshot: ScreenshotItem;\n\n /**\n * screenshot size after shrinking\n */\n abstract shotSize: Size;\n\n /**\n * The ratio for converting shrunk screenshot coordinates to logical coordinates.\n *\n * Example:\n * - Physical screen width: 3000px, dpr=6\n * - Logical width: 500px\n * - User-defined screenshotShrinkFactor: 2\n * - Actual shrunk screenshot width: 3000 / 2 = 1500px\n * - shrunkShotToLogicalRatio: dpr / screenshotShrinkFactor = 6 / 2 = 3\n * - To map back to logical coordinates: 1500 / shrunkShotToLogicalRatio = 500px\n */\n abstract shrunkShotToLogicalRatio: number;\n\n abstract _isFrozen?: boolean;\n\n // @deprecated - backward compatibility for aiLocate\n abstract deprecatedDpr?: number;\n}\n\nexport type EnsureObject<T> = { [K in keyof T]: any };\n\nexport type ServiceAction = 'locate' | 'extract' | 'assert' | 'describe';\n\nexport type ServiceExtractParam = string | Record<string, string>;\n\nexport type ElementCacheFeature = Record<string, unknown>;\n\nexport interface LocateResult {\n element: LocateResultElement | null;\n rect?: Rect;\n}\n\nexport type ThinkingLevel = 'off' | 'medium' | 'high';\n\nexport type DeepThinkOption = 'unset' | true | false;\n\nexport interface ServiceTaskInfo {\n durationMs: number;\n formatResponse?: string;\n rawResponse?: string;\n usage?: AIUsageInfo;\n searchArea?: Rect;\n searchAreaRawResponse?: string;\n searchAreaUsage?: AIUsageInfo;\n reasoning_content?: string;\n}\n\nexport interface DumpMeta {\n logTime: number;\n}\n\nexport type ReportAttributes = Record<\n string,\n string | number | boolean | null | undefined\n>;\n\nexport interface ReportDumpWithAttributes {\n dumpString: string;\n attributes?: ReportAttributes;\n}\n\nexport interface ServiceDump extends DumpMeta {\n type: 'locate' | 'extract' | 'assert';\n logId: string;\n userQuery: {\n element?: TUserPrompt;\n dataDemand?: ServiceExtractParam;\n assertion?: TUserPrompt;\n };\n matchedElement: LocateResultElement[];\n matchedRect?: Rect;\n deepLocate?: boolean;\n data: any;\n assertionPass?: boolean;\n assertionThought?: string;\n taskInfo: ServiceTaskInfo;\n error?: string;\n output?: any;\n}\n\nexport type PartialServiceDumpFromSDK = Omit<\n ServiceDump,\n 'logTime' | 'logId' | 'model_name'\n>;\n\nexport interface ServiceResultBase {\n dump: ServiceDump;\n}\n\nexport type LocateResultWithDump = LocateResult & ServiceResultBase;\n\nexport interface ServiceExtractResult<T> extends ServiceResultBase {\n data: T;\n thought?: string;\n usage?: AIUsageInfo;\n reasoning_content?: string;\n}\n\nexport class ServiceError extends Error {\n dump: ServiceDump;\n\n constructor(message: string, dump: ServiceDump) {\n super(message);\n this.name = 'ServiceError';\n this.dump = dump;\n }\n}\n\n// intermediate variables to optimize the return value by AI\nexport interface LiteUISection {\n name: string;\n description: string;\n sectionCharacteristics: string;\n textIds: string[];\n}\n\nexport type ElementById = (id: string) => BaseElement | null;\n\nexport type ServiceAssertionResponse = AIAssertionResponse & {\n usage?: AIUsageInfo;\n};\n\n/**\n * agent\n */\n\nexport type OnTaskStartTip = (tip: string) => Promise<void> | void;\n\nexport interface AgentWaitForOpt extends ServiceExtractOption {\n checkIntervalMs?: number;\n timeoutMs?: number;\n}\n\nexport interface AgentAssertOpt {\n keepRawResponse?: boolean;\n}\n\n/**\n * planning\n *\n */\n\nexport interface PlanningLocateParam extends DetailedLocateParam {\n bbox?: [number, number, number, number];\n}\n\nexport interface PlanningAction<ParamType = any> {\n thought?: string;\n log?: string; // a brief preamble to the user explaining what you’re about to do\n type: string;\n param: ParamType;\n}\n\nexport type SubGoalStatus = 'pending' | 'running' | 'finished';\n\nexport interface SubGoal {\n index: number;\n status: SubGoalStatus;\n description: string;\n logs?: string[];\n}\n\nexport interface RawResponsePlanningAIResponse {\n action: PlanningAction;\n thought?: string;\n log: string;\n memory?: string;\n error?: string;\n finalizeMessage?: string;\n finalizeSuccess?: boolean;\n updateSubGoals?: SubGoal[];\n markFinishedIndexes?: number[];\n}\n\nexport interface PlanningAIResponse\n extends Omit<RawResponsePlanningAIResponse, 'action'> {\n actions?: PlanningAction[];\n usage?: AIUsageInfo;\n rawResponse?: string;\n yamlFlow?: MidsceneYamlFlowItem[];\n yamlString?: string;\n error?: string;\n reasoning_content?: string;\n shouldContinuePlanning: boolean;\n output?: string; // Output message from <complete> tag (same as finalizeMessage)\n}\n\nexport interface PlanningActionParamSleep {\n timeMs: number;\n}\n\nexport interface PlanningActionParamError {\n thought: string;\n}\n\nexport type PlanningActionParamWaitFor = AgentWaitForOpt & {};\n\nexport interface LongPressParam {\n duration?: number;\n}\n\nexport interface PullParam {\n direction: 'up' | 'down';\n distance?: number;\n duration?: number;\n}\n/**\n * misc\n */\n\nexport interface Color {\n name: string;\n hex: string;\n}\n\nexport interface BaseAgentParserOpt {\n selector?: string;\n}\n// eslint-disable-next-line @typescript-eslint/no-empty-interface\nexport interface PuppeteerParserOpt extends BaseAgentParserOpt {}\n\n// eslint-disable-next-line @typescript-eslint/no-empty-interface\nexport interface PlaywrightParserOpt extends BaseAgentParserOpt {}\n\n/*\naction\n*/\nexport interface ExecutionTaskProgressOptions {\n onTaskStart?: (task: ExecutionTask) => Promise<void> | void;\n}\n\nexport interface ExecutionRecorderItem {\n type: 'screenshot';\n ts: number;\n screenshot?: ScreenshotItem;\n timing?: string;\n}\n\nexport type ExecutionTaskType = 'Planning' | 'Insight' | 'Action Space' | 'Log';\n\nexport interface ExecutorContext {\n task: ExecutionTask;\n element?: LocateResultElement | null;\n uiContext?: UIContext;\n}\n\nexport interface ExecutionTaskApply<\n Type extends ExecutionTaskType = any,\n TaskParam = any,\n TaskOutput = any,\n TaskLog = any,\n> {\n type: Type;\n subType?: string;\n param?: TaskParam;\n thought?: string;\n uiContext?: UIContext;\n executor: (\n param: TaskParam,\n context: ExecutorContext,\n ) => // biome-ignore lint/suspicious/noConfusingVoidType: void is intentionally allowed as some executors may not return a value\n | Promise<ExecutionTaskReturn<TaskOutput, TaskLog> | undefined | void>\n | undefined\n | void;\n}\n\nexport interface ExecutionTaskHitBy {\n from: string;\n context: Record<string, any>;\n}\n\nexport interface ExecutionTaskReturn<TaskOutput = unknown, TaskLog = unknown> {\n output?: TaskOutput;\n log?: TaskLog;\n recorder?: ExecutionRecorderItem[];\n hitBy?: ExecutionTaskHitBy;\n}\n\nexport type ExecutionTask<\n E extends ExecutionTaskApply<any, any, any> = ExecutionTaskApply<\n any,\n any,\n any\n >,\n> = E &\n ExecutionTaskReturn<\n E extends ExecutionTaskApply<any, any, infer TaskOutput, any>\n ? TaskOutput\n : unknown,\n E extends ExecutionTaskApply<any, any, any, infer TaskLog>\n ? TaskLog\n : unknown\n > & {\n taskId: string;\n status: 'pending' | 'running' | 'finished' | 'failed' | 'cancelled';\n error?: Error;\n errorMessage?: string;\n errorStack?: string;\n timing?: {\n start: number;\n getUiContextStart?: number;\n getUiContextEnd?: number;\n callAiStart?: number;\n callAiEnd?: number;\n beforeInvokeActionHookStart?: number;\n beforeInvokeActionHookEnd?: number;\n callActionStart?: number;\n callActionEnd?: number;\n afterInvokeActionHookStart?: number;\n afterInvokeActionHookEnd?: number;\n captureAfterCallingSnapshotStart?: number;\n captureAfterCallingSnapshotEnd?: number;\n end?: number;\n cost?: number;\n };\n usage?: AIUsageInfo;\n searchAreaUsage?: AIUsageInfo;\n reasoning_content?: string;\n };\n\nexport interface IExecutionDump extends DumpMeta {\n /** Stable unique identifier for this execution run */\n id?: string;\n name: string;\n description?: string;\n tasks: ExecutionTask[];\n aiActContext?: string;\n}\n\n/**\n * Replacer function for JSON serialization that handles Page, Browser objects and ScreenshotItem\n */\nfunction replacerForDumpSerialization(_key: string, value: any): any {\n if (value && value.constructor?.name === 'Page') {\n return '[Page object]';\n }\n if (value && value.constructor?.name === 'Browser') {\n return '[Browser object]';\n }\n // Handle ScreenshotItem serialization\n if (value && typeof value.toSerializable === 'function') {\n return value.toSerializable();\n }\n return value;\n}\n\n/**\n * Reviver function for JSON deserialization that keeps screenshot references\n * as plain objects. Resolution is handled lazily by restoreImageReferences.\n *\n * @param key - JSON key being processed\n * @param value - JSON value being processed\n * @returns Restored value\n */\nfunction reviverForDumpDeserialization(key: string, value: any): any {\n // Only process screenshot fields\n if (key !== 'screenshot' || typeof value !== 'object' || value === null) {\n return value;\n }\n\n if (ScreenshotItem.isSerialized(value)) {\n return value;\n }\n\n return value;\n}\n\n/**\n * ExecutionDump class for serializing and deserializing execution dumps\n */\nexport class ExecutionDump implements IExecutionDump {\n id?: string;\n logTime: number;\n name: string;\n description?: string;\n tasks: ExecutionTask[];\n aiActContext?: string;\n\n constructor(data: IExecutionDump) {\n this.id = data.id;\n this.logTime = data.logTime;\n this.name = data.name;\n this.description = data.description;\n this.tasks = data.tasks;\n this.aiActContext = data.aiActContext;\n }\n\n /**\n * Serialize the ExecutionDump to a JSON string\n */\n serialize(indents?: number): string {\n return JSON.stringify(this.toJSON(), replacerForDumpSerialization, indents);\n }\n\n /**\n * Convert to a plain object for JSON serialization\n */\n toJSON(): IExecutionDump {\n return {\n id: this.id,\n logTime: this.logTime,\n name: this.name,\n description: this.description,\n tasks: this.tasks.map((task) => ({\n ...task,\n recorder: task.recorder || [],\n })),\n aiActContext: this.aiActContext,\n };\n }\n\n /**\n * Create an ExecutionDump instance from a serialized JSON string\n */\n static fromSerializedString(serialized: string): ExecutionDump {\n const parsed = JSON.parse(\n serialized,\n reviverForDumpDeserialization,\n ) as IExecutionDump;\n return new ExecutionDump(parsed);\n }\n\n /**\n * Create an ExecutionDump instance from a plain object\n */\n static fromJSON(data: IExecutionDump): ExecutionDump {\n return new ExecutionDump(data);\n }\n\n /**\n * Collect all ScreenshotItem instances from tasks.\n * Scans through uiContext and recorder items to find screenshots.\n *\n * @returns Array of ScreenshotItem instances\n */\n collectScreenshots(): ScreenshotItem[] {\n const screenshots: ScreenshotItem[] = [];\n\n for (const task of this.tasks) {\n // Collect uiContext.screenshot if present\n if (task.uiContext?.screenshot instanceof ScreenshotItem) {\n screenshots.push(task.uiContext.screenshot);\n }\n\n // Collect recorder screenshots\n if (task.recorder) {\n for (const record of task.recorder) {\n if (record.screenshot instanceof ScreenshotItem) {\n screenshots.push(record.screenshot);\n }\n }\n }\n }\n\n return screenshots;\n }\n}\n\n/*\ntask - service-locate\n*/\nexport type ExecutionTaskInsightLocateParam = PlanningLocateParam;\n\nexport interface ExecutionTaskInsightLocateOutput {\n element: LocateResultElement | null;\n}\n\nexport type ExecutionTaskInsightDump = ServiceDump;\n\nexport type ExecutionTaskInsightLocateApply = ExecutionTaskApply<\n 'Insight',\n ExecutionTaskInsightLocateParam,\n ExecutionTaskInsightLocateOutput,\n ExecutionTaskInsightDump\n>;\n\nexport type ExecutionTaskInsightLocate =\n ExecutionTask<ExecutionTaskInsightLocateApply>;\n\n/*\ntask - service-query\n*/\nexport interface ExecutionTaskInsightQueryParam {\n dataDemand: ServiceExtractParam;\n domIncluded?: boolean | 'visible-only';\n}\n\nexport interface ExecutionTaskInsightQueryOutput {\n data: any;\n}\n\nexport type ExecutionTaskInsightQueryApply = ExecutionTaskApply<\n 'Insight',\n ExecutionTaskInsightQueryParam,\n any,\n ExecutionTaskInsightDump\n>;\n\nexport type ExecutionTaskInsightQuery =\n ExecutionTask<ExecutionTaskInsightQueryApply>;\n\n/*\ntask - assertion\n*/\nexport interface ExecutionTaskInsightAssertionParam {\n assertion: string;\n}\n\nexport type ExecutionTaskInsightAssertionApply = ExecutionTaskApply<\n 'Insight',\n ExecutionTaskInsightAssertionParam,\n ServiceAssertionResponse,\n ExecutionTaskInsightDump\n>;\n\nexport type ExecutionTaskInsightAssertion =\n ExecutionTask<ExecutionTaskInsightAssertionApply>;\n\n/*\ntask - action (i.e. interact) \n*/\nexport type ExecutionTaskActionApply<ActionParam = any> = ExecutionTaskApply<\n 'Action Space',\n ActionParam,\n void,\n void\n>;\n\nexport type ExecutionTaskAction = ExecutionTask<ExecutionTaskActionApply>;\n\n/*\ntask - Log\n*/\n\nexport type ExecutionTaskLogApply<\n LogParam = {\n content: string;\n },\n> = ExecutionTaskApply<'Log', LogParam, void, void>;\n\nexport type ExecutionTaskLog = ExecutionTask<ExecutionTaskLogApply>;\n\n/*\ntask - planning\n*/\n\nexport type ExecutionTaskPlanningApply = ExecutionTaskApply<\n 'Planning',\n {\n userInstruction: string;\n aiActContext?: string;\n },\n PlanningAIResponse\n>;\n\nexport type ExecutionTaskPlanning = ExecutionTask<ExecutionTaskPlanningApply>;\n\n/*\ntask - planning-locate\n*/\nexport type ExecutionTaskPlanningLocateParam = PlanningLocateParam;\n\nexport interface ExecutionTaskPlanningLocateOutput {\n element: LocateResultElement | null;\n}\n\nexport type ExecutionTaskPlanningDump = ServiceDump;\n\nexport type ExecutionTaskPlanningLocateApply = ExecutionTaskApply<\n 'Planning',\n ExecutionTaskPlanningLocateParam,\n ExecutionTaskPlanningLocateOutput,\n ExecutionTaskPlanningDump\n>;\n\nexport type ExecutionTaskPlanningLocate =\n ExecutionTask<ExecutionTaskPlanningLocateApply>;\n\n/*\nReport metadata - extracted from ReportActionDump for per-execution writes\n*/\nexport interface ReportMeta {\n groupName: string;\n groupDescription?: string;\n sdkVersion: string;\n modelBriefs: ModelBrief[];\n deviceType?: string;\n}\n\n// Backward-compatible aliases for existing external consumers.\nexport type GroupMeta = ReportMeta;\n\n/*\nReport dump\n*/\nexport interface IReportActionDump {\n sdkVersion: string;\n groupName: string;\n groupDescription?: string;\n modelBriefs: ModelBrief[];\n executions: IExecutionDump[];\n deviceType?: string;\n}\n\n// Backward-compatible aliases for existing external consumers.\nexport type IGroupedActionDump = IReportActionDump;\n\nexport interface ModelBrief {\n /**\n * The intent/category of the model call, for example \"planning\" or \"insight\".\n */\n intent?: string;\n\n /**\n * The model name returned by usage metadata, for example \"gpt-4o\".\n */\n name?: string;\n\n /**\n * Optional human-readable model description, for example \"qwen2.5-vl mode\".\n */\n modelDescription?: string;\n}\n\n/**\n * ReportActionDump class for serializing and deserializing report action dumps\n */\nexport class ReportActionDump implements IReportActionDump {\n sdkVersion: string;\n groupName: string;\n groupDescription?: string;\n modelBriefs: ModelBrief[];\n executions: ExecutionDump[];\n deviceType?: string;\n\n constructor(data: IReportActionDump) {\n this.sdkVersion = data.sdkVersion;\n this.groupName = data.groupName;\n this.groupDescription = data.groupDescription;\n this.modelBriefs = data.modelBriefs;\n this.executions = data.executions.map((exec) =>\n exec instanceof ExecutionDump ? exec : ExecutionDump.fromJSON(exec),\n );\n this.deviceType = data.deviceType;\n }\n\n /**\n * Serialize the ReportActionDump to a JSON string\n * Uses compact { $screenshot: id } format\n */\n serialize(indents?: number): string {\n return JSON.stringify(this.toJSON(), replacerForDumpSerialization, indents);\n }\n\n /**\n * Serialize the ReportActionDump with inline screenshots to a JSON string.\n * Each ScreenshotItem is replaced with { base64: \"...\", capturedAt }.\n */\n serializeWithInlineScreenshots(indents?: number): string {\n const processValue = (obj: unknown): unknown => {\n if (obj instanceof ScreenshotItem) {\n return { base64: obj.base64, capturedAt: obj.capturedAt };\n }\n if (Array.isArray(obj)) {\n return obj.map(processValue);\n }\n if (obj && typeof obj === 'object') {\n const entries = Object.entries(obj).map(([key, value]) => [\n key,\n processValue(value),\n ]);\n return Object.fromEntries(entries);\n }\n return obj;\n };\n\n const data = processValue(this.toJSON());\n return JSON.stringify(data, null, indents);\n }\n\n /**\n * Convert to a plain object for JSON serialization\n */\n toJSON(): IReportActionDump {\n return {\n sdkVersion: this.sdkVersion,\n groupName: this.groupName,\n groupDescription: this.groupDescription,\n modelBriefs: this.modelBriefs,\n executions: this.executions.map((exec) => exec.toJSON()),\n deviceType: this.deviceType,\n };\n }\n\n /**\n * Create a ReportActionDump instance from a serialized JSON string\n */\n static fromSerializedString(serialized: string): ReportActionDump {\n const parsed = JSON.parse(\n serialized,\n reviverForDumpDeserialization,\n ) as IReportActionDump;\n return new ReportActionDump(parsed);\n }\n\n /**\n * Create a ReportActionDump instance from a plain object\n */\n static fromJSON(data: IReportActionDump): ReportActionDump {\n return new ReportActionDump(data);\n }\n\n /**\n * Collect all ScreenshotItem instances from all executions.\n *\n * @returns Array of all ScreenshotItem instances across all executions\n */\n collectAllScreenshots(): ScreenshotItem[] {\n const screenshots: ScreenshotItem[] = [];\n for (const execution of this.executions) {\n screenshots.push(...execution.collectScreenshots());\n }\n return screenshots;\n }\n\n /**\n * Serialize the dump to files with screenshots as separate PNG files.\n * Creates:\n * - {basePath} - dump JSON with { $screenshot: id } references\n * - {basePath}.screenshots/ - PNG files\n *\n * @param basePath - Base path for the dump file\n */\n serializeToFiles(basePath: string): void {\n const screenshotsDir = `${basePath}.screenshots`;\n if (!existsSync(screenshotsDir)) {\n mkdirSync(screenshotsDir, { recursive: true });\n }\n\n const screenshots = this.collectAllScreenshots();\n\n for (const screenshot of screenshots) {\n const imagePath = join(\n screenshotsDir,\n `${screenshot.id}.${screenshot.extension}`,\n );\n if (existsSync(imagePath)) {\n continue;\n }\n\n const rawBase64 = screenshot.rawBase64;\n writeFileSync(imagePath, Buffer.from(rawBase64, 'base64'));\n }\n\n // Write dump JSON with references\n writeFileSync(basePath, this.serialize(), 'utf-8');\n }\n\n /**\n * Read dump from files and return JSON string with inline screenshots.\n * Reads the dump JSON and screenshot files, then inlines the base64 data.\n *\n * @param basePath - Base path for the dump file\n * @returns JSON string with inline screenshots ({ base64: \"...\" } format)\n */\n static fromFilesAsInlineJson(basePath: string): string {\n const dumpString = readFileSync(basePath, 'utf-8');\n const screenshotsDir = `${basePath}.screenshots`;\n\n const loadFromExecutionScreenshotDir = (id: string, mimeType: string) => {\n const ext = mimeType === 'image/jpeg' ? 'jpeg' : 'png';\n const filePath = join(screenshotsDir, `${id}.${ext}`);\n if (!existsSync(filePath)) {\n return '';\n }\n const data = readFileSync(filePath);\n return `data:image/${ext};base64,${data.toString('base64')}`;\n };\n\n // Restore image references\n const dumpData = JSON.parse(dumpString);\n const store = new ScreenshotStore({\n mode: 'directory',\n reportPath: basePath,\n });\n const processedData = restoreImageReferences(dumpData, (ref) => {\n const executionFileImage = loadFromExecutionScreenshotDir(\n ref.id,\n ref.mimeType,\n );\n if (executionFileImage) {\n return executionFileImage;\n }\n\n if (ref.storage === 'inline') {\n return '';\n }\n return store.loadBase64(ref);\n });\n return JSON.stringify(processedData);\n }\n\n /**\n * Clean up all files associated with a serialized dump.\n *\n * @param basePath - Base path for the dump file\n */\n static cleanupFiles(basePath: string): void {\n const filesToClean = [basePath, `${basePath}.screenshots`];\n\n for (const filePath of filesToClean) {\n try {\n rmSync(filePath, { force: true, recursive: true });\n } catch {\n // Ignore errors - file may already be deleted\n }\n }\n }\n\n /**\n * Get all file paths associated with a serialized dump.\n *\n * @param basePath - Base path for the dump file\n * @returns Array of all associated file paths\n */\n static getFilePaths(basePath: string): string[] {\n return [basePath, `${basePath}.screenshots`];\n }\n}\n\n// Backward-compatible aliases for existing external consumers.\nexport type GroupedActionDump = ReportActionDump;\nexport const GroupedActionDump = ReportActionDump;\n\nexport type InterfaceType =\n | 'puppeteer'\n | 'playwright'\n | 'static'\n | 'chrome-extension-proxy'\n | 'android'\n | string;\n\nexport interface StreamingCodeGenerationOptions {\n /** Whether to enable streaming output */\n stream?: boolean;\n /** Callback function to handle streaming chunks */\n onChunk?: StreamingCallback;\n /** Callback function to handle streaming completion */\n onComplete?: (finalCode: string) => void;\n /** Callback function to handle streaming errors */\n onError?: (error: Error) => void;\n}\n\nexport type StreamingCallback = (chunk: CodeGenerationChunk) => void;\n\nexport interface CodeGenerationChunk {\n /** The incremental content chunk */\n content: string;\n /** The reasoning content */\n reasoning_content: string;\n /** The accumulated content so far */\n accumulated: string;\n /** Whether this is the final chunk */\n isComplete: boolean;\n /** Token usage information if available */\n usage?: AIUsageInfo;\n}\n\nexport interface StreamingAIResponse {\n /** The final accumulated content */\n content: string;\n /** Token usage information */\n usage?: AIUsageInfo;\n /** Whether the response was streamed */\n isStreamed: boolean;\n}\n\nexport interface DeviceAction<TParam = any, TReturn = any> {\n name: string;\n description?: string;\n interfaceAlias?: string;\n paramSchema?: z.ZodType<TParam>;\n call: (param: TParam, context: ExecutorContext) => Promise<TReturn> | TReturn;\n delayAfterRunner?: number;\n /**\n * An example param object for this action.\n * Locate fields with { prompt } will automatically get bbox injected when needed.\n */\n sample?: { [K in keyof TParam]?: any };\n}\n\n/**\n * Type utilities for extracting types from DeviceAction definitions\n */\n\n/**\n * Extract parameter type from a DeviceAction\n */\nexport type ActionParam<Action extends DeviceAction<any, any>> =\n Action extends DeviceAction<infer P, any> ? P : never;\n\n/**\n * Extract return type from a DeviceAction\n */\nexport type ActionReturn<Action extends DeviceAction<any, any>> =\n Action extends DeviceAction<any, infer R> ? R : never;\n\n/**\n * Web-specific types\n */\nexport interface WebElementInfo extends BaseElement {\n id: string;\n attributes: {\n nodeType: NodeType;\n [key: string]: string;\n };\n}\n\n/**\n * Agent\n */\n\nexport type CacheConfig = {\n strategy?: 'read-only' | 'read-write' | 'write-only';\n id: string;\n};\n\nexport type Cache =\n | false // No read, no write\n | true // Will throw error at runtime - deprecated\n | CacheConfig; // Object configuration (requires explicit id)\n\nexport interface AgentOpt {\n // @deprecated Use `reportFileName` and `cache.id` instead.\n testId?: string;\n // @deprecated\n cacheId?: string; // Keep backward compatibility, but marked as deprecated\n groupName?: string;\n groupDescription?: string;\n /* if auto generate report, default true */\n generateReport?: boolean;\n /* if persist per-execution dump files next to the report, default false */\n persistExecutionDump?: boolean;\n /* if auto print report msg, default true */\n autoPrintReportMsg?: boolean;\n\n /**\n * Use directory-based report format with separate image files.\n *\n * When enabled:\n * - Screenshots are saved as PNG files in a `screenshots/` subdirectory\n * - Report is generated as `index.html` with relative image paths\n * - Reduces memory usage and report file size\n *\n * IMPORTANT: 'html-and-external-assets' reports must be served via HTTP server\n * (e.g., `npx serve ./report-dir`). The file:// protocol will not\n * work due to browser CORS restrictions.\n *\n * @default 'single-html'\n */\n outputFormat?: 'single-html' | 'html-and-external-assets';\n\n onTaskStartTip?: OnTaskStartTip;\n aiActContext?: string;\n aiActionContext?: string;\n /* custom report file name */\n reportFileName?: string;\n modelConfig?: TModelConfig;\n cache?: Cache;\n /**\n * Maximum number of replanning cycles for aiAct.\n * Defaults to 20 (40 for `vlm-ui-tars`) when not provided.\n * If omitted, the agent will also read `MIDSCENE_REPLANNING_CYCLE_LIMIT` for backward compatibility.\n */\n replanningCycleLimit?: number;\n\n /**\n * Wait time in milliseconds after each action execution.\n * This allows the UI to settle and stabilize before the next action.\n * Defaults to 300ms when not provided.\n */\n waitAfterAction?: number;\n\n /**\n * When set to true, Midscene will use the target device's time (Android/iOS)\n * instead of the system time. Useful when the device time differs from the\n * host machine. Default: false\n */\n useDeviceTimestamp?: boolean;\n\n /**\n * Custom screenshot shrink factor to reduce AI token usage.\n * When set, the screenshot will be scaled down by this factor from the physical resolution.\n *\n * Example:\n * - Physical screen width: 3000px, dpr=6\n * - Logical width: 500px\n * - screenshotShrinkFactor: 2\n * - Actual shrunk screenshot width: 3000 / 2 = 1500px\n * - AI analyzes the 1500px screenshot\n * - Coordinates are transformed back to logical (500px) before actions execute\n *\n * Benefits:\n * - Reduces token usage for high-resolution screenshots\n * - Maintains accuracy by scaling coordinates appropriately\n *\n * Must be >= 1 (shrinking only, enlarging is not supported).\n *\n * @default 1 (no shrinking, uses original physical screenshot)\n */\n screenshotShrinkFactor?: number;\n\n /**\n * Custom OpenAI client factory function\n *\n * If provided, this function will be called to create OpenAI client instances\n * for each AI call, allowing you to:\n * - Wrap clients with observability tools (langsmith, langfuse)\n * - Use custom OpenAI-compatible clients\n * - Apply different configurations based on intent\n *\n * @param config - Resolved model configuration\n * @returns OpenAI client instance (original or wrapped)\n *\n * @example\n * ```typescript\n * createOpenAIClient: async (openai, opts) => {\n * // Wrap with langsmith for planning tasks\n * if (opts.baseURL?.includes('planning')) {\n * return wrapOpenAI(openai, { metadata: { task: 'planning' } });\n * }\n *\n * return openai;\n * }\n * ```\n */\n createOpenAIClient?: CreateOpenAIClientFn;\n}\n\nexport type TestStatus =\n | 'passed'\n | 'failed'\n | 'timedOut'\n | 'skipped'\n | 'interrupted';\n\nexport interface ReportFileAttributes {\n testDuration: number;\n testStatus: TestStatus;\n testTitle: string;\n testId: string;\n testDescription: string;\n}\n\nexport type ReportFileWithAttributes =\n | {\n reportFilePath: string;\n reportAttributes: ReportFileAttributes;\n }\n | {\n reportFilePath?: string;\n reportAttributes: ReportFileAttributes & { testStatus: 'skipped' };\n };\n"],"names":["UIContext","ServiceError","Error","message","dump","replacerForDumpSerialization","_key","value","reviverForDumpDeserialization","key","ScreenshotItem","ExecutionDump","indents","JSON","task","serialized","parsed","data","screenshots","record","ReportActionDump","processValue","obj","Array","entries","Object","exec","execution","basePath","screenshotsDir","existsSync","mkdirSync","screenshot","imagePath","join","rawBase64","writeFileSync","Buffer","dumpString","readFileSync","loadFromExecutionScreenshotDir","id","mimeType","ext","filePath","dumpData","store","ScreenshotStore","processedData","restoreImageReferences","ref","executionFileImage","filesToClean","rmSync","GroupedActionDump"],"mappings":"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"}
1
+ {"version":3,"file":"types.mjs","sources":["../../src/types.ts"],"sourcesContent":["/* eslint-disable @typescript-eslint/no-explicit-any */\n\nimport {\n existsSync,\n mkdirSync,\n readFileSync,\n rmSync,\n writeFileSync,\n} from 'node:fs';\nimport { join } from 'node:path';\nimport type { NodeType } from '@aiscene/shared/constants';\nimport type { CreateOpenAIClientFn, TModelConfig } from '@aiscene/shared/env';\nimport type {\n BaseElement,\n LocateResultElement,\n Rect,\n Size,\n} from '@aiscene/shared/types';\nimport type { z } from 'zod';\nimport type { TUserPrompt } from './common';\nimport { restoreImageReferences } from './dump/screenshot-restoration';\nimport { ScreenshotStore } from './dump/screenshot-store';\nimport { ScreenshotItem } from './screenshot-item';\nimport type {\n DetailedLocateParam,\n MidsceneYamlFlowItem,\n ServiceExtractOption,\n} from './yaml';\n\nexport type {\n ElementTreeNode,\n BaseElement,\n Rect,\n Size,\n Point,\n} from '@aiscene/shared/types';\nexport * from './yaml';\n\nexport type AIUsageInfo = Record<string, any> & {\n prompt_tokens: number | undefined;\n completion_tokens: number | undefined;\n total_tokens: number | undefined;\n cached_input: number | undefined;\n time_cost: number | undefined;\n model_name: string | undefined;\n model_description: string | undefined;\n intent: string | undefined;\n request_id: string | undefined;\n};\n\nexport type { LocateResultElement };\n\nexport type AISingleElementResponseByPosition = {\n position?: {\n x: number;\n y: number;\n };\n bbox?: [number, number, number, number];\n reason: string;\n text: string;\n};\n\nexport interface AIElementCoordinatesResponse {\n bbox: [number, number, number, number];\n errors?: string[];\n}\n\nexport type AIElementResponse = AIElementCoordinatesResponse;\n\nexport interface AIDataExtractionResponse<DataDemand> {\n data: DataDemand;\n errors?: string[];\n thought?: string;\n}\n\nexport interface AISectionLocatorResponse {\n bbox: [number, number, number, number];\n references_bbox?: [number, number, number, number][];\n error?: string;\n}\n\nexport interface AIAssertionResponse {\n pass: boolean;\n thought: string;\n}\n\nexport interface AIDescribeElementResponse {\n description: string;\n error?: string;\n}\n\nexport interface LocatorValidatorOption {\n centerDistanceThreshold?: number;\n}\n\nexport interface LocateValidatorResult {\n pass: boolean;\n rect: Rect;\n center: [number, number];\n centerDistance?: number;\n}\n\nexport interface AgentDescribeElementAtPointResult {\n prompt: string;\n deepLocate: boolean;\n verifyResult?: LocateValidatorResult;\n}\n\n/**\n * context\n */\n\nexport abstract class UIContext {\n /**\n * screenshot of the current UI state. which size is shotSize(be shrunk by screenshotShrinkFactor),\n */\n abstract screenshot: ScreenshotItem;\n\n /**\n * screenshot size after shrinking\n */\n abstract shotSize: Size;\n\n /**\n * The ratio for converting shrunk screenshot coordinates to logical coordinates.\n *\n * Example:\n * - Physical screen width: 3000px, dpr=6\n * - Logical width: 500px\n * - User-defined screenshotShrinkFactor: 2\n * - Actual shrunk screenshot width: 3000 / 2 = 1500px\n * - shrunkShotToLogicalRatio: dpr / screenshotShrinkFactor = 6 / 2 = 3\n * - To map back to logical coordinates: 1500 / shrunkShotToLogicalRatio = 500px\n */\n abstract shrunkShotToLogicalRatio: number;\n\n abstract _isFrozen?: boolean;\n\n // @deprecated - backward compatibility for aiLocate\n abstract deprecatedDpr?: number;\n}\n\nexport type EnsureObject<T> = { [K in keyof T]: any };\n\nexport type ServiceAction = 'locate' | 'extract' | 'assert' | 'describe';\n\nexport type ServiceExtractParam = string | Record<string, string>;\n\nexport type ElementCacheFeature = Record<string, unknown>;\n\nexport interface LocateResult {\n element: LocateResultElement | null;\n rect?: Rect;\n}\n\nexport type ThinkingLevel = 'off' | 'medium' | 'high';\n\nexport type DeepThinkOption = 'unset' | true | false;\n\nexport interface ServiceTaskInfo {\n durationMs: number;\n formatResponse?: string;\n rawResponse?: string;\n usage?: AIUsageInfo;\n searchArea?: Rect;\n searchAreaRawResponse?: string;\n searchAreaUsage?: AIUsageInfo;\n reasoning_content?: string;\n}\n\nexport interface DumpMeta {\n logTime: number;\n}\n\nexport type ReportAttributes = Record<\n string,\n string | number | boolean | null | undefined\n>;\n\nexport interface ReportDumpWithAttributes {\n dumpString: string;\n attributes?: ReportAttributes;\n}\n\nexport interface ServiceDump extends DumpMeta {\n type: 'locate' | 'extract' | 'assert';\n logId: string;\n userQuery: {\n element?: TUserPrompt;\n dataDemand?: ServiceExtractParam;\n assertion?: TUserPrompt;\n };\n matchedElement: LocateResultElement[];\n matchedRect?: Rect;\n deepLocate?: boolean;\n data: any;\n assertionPass?: boolean;\n assertionThought?: string;\n taskInfo: ServiceTaskInfo;\n error?: string;\n output?: any;\n}\n\nexport type PartialServiceDumpFromSDK = Omit<\n ServiceDump,\n 'logTime' | 'logId' | 'model_name'\n>;\n\nexport interface ServiceResultBase {\n dump: ServiceDump;\n}\n\nexport type LocateResultWithDump = LocateResult & ServiceResultBase;\n\nexport interface ServiceExtractResult<T> extends ServiceResultBase {\n data: T;\n thought?: string;\n usage?: AIUsageInfo;\n reasoning_content?: string;\n}\n\nexport class ServiceError extends Error {\n dump: ServiceDump;\n\n constructor(message: string, dump: ServiceDump) {\n super(message);\n this.name = 'ServiceError';\n this.dump = dump;\n }\n}\n\n// intermediate variables to optimize the return value by AI\nexport interface LiteUISection {\n name: string;\n description: string;\n sectionCharacteristics: string;\n textIds: string[];\n}\n\nexport type ElementById = (id: string) => BaseElement | null;\n\nexport type ServiceAssertionResponse = AIAssertionResponse & {\n usage?: AIUsageInfo;\n};\n\n/**\n * agent\n */\n\nexport type OnTaskStartTip = (tip: string) => Promise<void> | void;\n\nexport interface AgentWaitForOpt extends ServiceExtractOption {\n checkIntervalMs?: number;\n timeoutMs?: number;\n}\n\nexport interface AgentAssertOpt {\n keepRawResponse?: boolean;\n}\n\n/**\n * planning\n *\n */\n\nexport interface PlanningLocateParam extends DetailedLocateParam {\n bbox?: [number, number, number, number];\n}\n\nexport interface PlanningAction<ParamType = any> {\n thought?: string;\n log?: string; // a brief preamble to the user explaining what you’re about to do\n type: string;\n param: ParamType;\n}\n\nexport type SubGoalStatus = 'pending' | 'running' | 'finished';\n\nexport interface SubGoal {\n index: number;\n status: SubGoalStatus;\n description: string;\n logs?: string[];\n}\n\nexport interface RawResponsePlanningAIResponse {\n action: PlanningAction;\n thought?: string;\n log: string;\n memory?: string;\n error?: string;\n finalizeMessage?: string;\n finalizeSuccess?: boolean;\n updateSubGoals?: SubGoal[];\n markFinishedIndexes?: number[];\n}\n\nexport interface PlanningAIResponse\n extends Omit<RawResponsePlanningAIResponse, 'action'> {\n actions?: PlanningAction[];\n usage?: AIUsageInfo;\n rawResponse?: string;\n yamlFlow?: MidsceneYamlFlowItem[];\n yamlString?: string;\n error?: string;\n reasoning_content?: string;\n shouldContinuePlanning: boolean;\n output?: string; // Output message from <complete> tag (same as finalizeMessage)\n}\n\nexport interface PlanningActionParamSleep {\n timeMs: number;\n}\n\nexport interface PlanningActionParamError {\n thought: string;\n}\n\nexport type PlanningActionParamWaitFor = AgentWaitForOpt & {};\n\nexport interface LongPressParam {\n duration?: number;\n}\n\nexport interface PullParam {\n direction: 'up' | 'down';\n distance?: number;\n duration?: number;\n}\n/**\n * misc\n */\n\nexport interface Color {\n name: string;\n hex: string;\n}\n\nexport interface BaseAgentParserOpt {\n selector?: string;\n}\n// eslint-disable-next-line @typescript-eslint/no-empty-interface\nexport interface PuppeteerParserOpt extends BaseAgentParserOpt {}\n\n// eslint-disable-next-line @typescript-eslint/no-empty-interface\nexport interface PlaywrightParserOpt extends BaseAgentParserOpt {}\n\n/*\naction\n*/\nexport interface ExecutionTaskProgressOptions {\n onTaskStart?: (task: ExecutionTask) => Promise<void> | void;\n}\n\nexport interface ExecutionRecorderItem {\n type: 'screenshot';\n ts: number;\n screenshot?: ScreenshotItem;\n timing?: string;\n}\n\nexport type ExecutionTaskType = 'Planning' | 'Insight' | 'Action Space' | 'Log';\n\nexport interface ExecutorContext {\n task: ExecutionTask;\n element?: LocateResultElement | null;\n uiContext?: UIContext;\n}\n\nexport interface ExecutionTaskApply<\n Type extends ExecutionTaskType = any,\n TaskParam = any,\n TaskOutput = any,\n TaskLog = any,\n> {\n type: Type;\n subType?: string;\n param?: TaskParam;\n thought?: string;\n uiContext?: UIContext;\n executor: (\n param: TaskParam,\n context: ExecutorContext,\n ) => // biome-ignore lint/suspicious/noConfusingVoidType: void is intentionally allowed as some executors may not return a value\n | Promise<ExecutionTaskReturn<TaskOutput, TaskLog> | undefined | void>\n | undefined\n | void;\n}\n\nexport interface ExecutionTaskHitBy {\n from: string;\n context: Record<string, any>;\n}\n\nexport interface ExecutionTaskReturn<TaskOutput = unknown, TaskLog = unknown> {\n output?: TaskOutput;\n log?: TaskLog;\n recorder?: ExecutionRecorderItem[];\n hitBy?: ExecutionTaskHitBy;\n}\n\nexport type ExecutionTask<\n E extends ExecutionTaskApply<any, any, any> = ExecutionTaskApply<\n any,\n any,\n any\n >,\n> = E &\n ExecutionTaskReturn<\n E extends ExecutionTaskApply<any, any, infer TaskOutput, any>\n ? TaskOutput\n : unknown,\n E extends ExecutionTaskApply<any, any, any, infer TaskLog>\n ? TaskLog\n : unknown\n > & {\n taskId: string;\n status: 'pending' | 'running' | 'finished' | 'failed' | 'cancelled';\n error?: Error;\n errorMessage?: string;\n errorStack?: string;\n timing?: {\n start: number;\n getUiContextStart?: number;\n getUiContextEnd?: number;\n callAiStart?: number;\n callAiEnd?: number;\n beforeInvokeActionHookStart?: number;\n beforeInvokeActionHookEnd?: number;\n callActionStart?: number;\n callActionEnd?: number;\n afterInvokeActionHookStart?: number;\n afterInvokeActionHookEnd?: number;\n captureAfterCallingSnapshotStart?: number;\n captureAfterCallingSnapshotEnd?: number;\n end?: number;\n cost?: number;\n };\n usage?: AIUsageInfo;\n searchAreaUsage?: AIUsageInfo;\n reasoning_content?: string;\n };\n\nexport interface IExecutionDump extends DumpMeta {\n /** Stable unique identifier for this execution run */\n id?: string;\n name: string;\n description?: string;\n tasks: ExecutionTask[];\n aiActContext?: string;\n}\n\n/**\n * Replacer function for JSON serialization that handles Page, Browser objects and ScreenshotItem\n */\nfunction replacerForDumpSerialization(_key: string, value: any): any {\n if (value && value.constructor?.name === 'Page') {\n return '[Page object]';\n }\n if (value && value.constructor?.name === 'Browser') {\n return '[Browser object]';\n }\n // Handle ScreenshotItem serialization\n if (value && typeof value.toSerializable === 'function') {\n return value.toSerializable();\n }\n return value;\n}\n\n/**\n * Reviver function for JSON deserialization that keeps screenshot references\n * as plain objects. Resolution is handled lazily by restoreImageReferences.\n *\n * @param key - JSON key being processed\n * @param value - JSON value being processed\n * @returns Restored value\n */\nfunction reviverForDumpDeserialization(key: string, value: any): any {\n // Only process screenshot fields\n if (key !== 'screenshot' || typeof value !== 'object' || value === null) {\n return value;\n }\n\n if (ScreenshotItem.isSerialized(value)) {\n return value;\n }\n\n return value;\n}\n\n/**\n * ExecutionDump class for serializing and deserializing execution dumps\n */\nexport class ExecutionDump implements IExecutionDump {\n id?: string;\n logTime: number;\n name: string;\n description?: string;\n tasks: ExecutionTask[];\n aiActContext?: string;\n\n constructor(data: IExecutionDump) {\n this.id = data.id;\n this.logTime = data.logTime;\n this.name = data.name;\n this.description = data.description;\n this.tasks = data.tasks;\n this.aiActContext = data.aiActContext;\n }\n\n /**\n * Serialize the ExecutionDump to a JSON string\n */\n serialize(indents?: number): string {\n return JSON.stringify(this.toJSON(), replacerForDumpSerialization, indents);\n }\n\n /**\n * Convert to a plain object for JSON serialization\n */\n toJSON(): IExecutionDump {\n return {\n id: this.id,\n logTime: this.logTime,\n name: this.name,\n description: this.description,\n tasks: this.tasks.map((task) => ({\n ...task,\n recorder: task.recorder || [],\n })),\n aiActContext: this.aiActContext,\n };\n }\n\n /**\n * Create an ExecutionDump instance from a serialized JSON string\n */\n static fromSerializedString(serialized: string): ExecutionDump {\n const parsed = JSON.parse(\n serialized,\n reviverForDumpDeserialization,\n ) as IExecutionDump;\n return new ExecutionDump(parsed);\n }\n\n /**\n * Create an ExecutionDump instance from a plain object\n */\n static fromJSON(data: IExecutionDump): ExecutionDump {\n return new ExecutionDump(data);\n }\n\n /**\n * Collect all ScreenshotItem instances from tasks.\n * Scans through uiContext and recorder items to find screenshots.\n *\n * @returns Array of ScreenshotItem instances\n */\n collectScreenshots(): ScreenshotItem[] {\n const screenshots: ScreenshotItem[] = [];\n\n for (const task of this.tasks) {\n // Collect uiContext.screenshot if present\n if (task.uiContext?.screenshot instanceof ScreenshotItem) {\n screenshots.push(task.uiContext.screenshot);\n }\n\n // Collect recorder screenshots\n if (task.recorder) {\n for (const record of task.recorder) {\n if (record.screenshot instanceof ScreenshotItem) {\n screenshots.push(record.screenshot);\n }\n }\n }\n }\n\n return screenshots;\n }\n}\n\n/*\ntask - service-locate\n*/\nexport type ExecutionTaskInsightLocateParam = PlanningLocateParam;\n\nexport interface ExecutionTaskInsightLocateOutput {\n element: LocateResultElement | null;\n}\n\nexport type ExecutionTaskInsightDump = ServiceDump;\n\nexport type ExecutionTaskInsightLocateApply = ExecutionTaskApply<\n 'Insight',\n ExecutionTaskInsightLocateParam,\n ExecutionTaskInsightLocateOutput,\n ExecutionTaskInsightDump\n>;\n\nexport type ExecutionTaskInsightLocate =\n ExecutionTask<ExecutionTaskInsightLocateApply>;\n\n/*\ntask - service-query\n*/\nexport interface ExecutionTaskInsightQueryParam {\n dataDemand: ServiceExtractParam;\n domIncluded?: boolean | 'visible-only';\n}\n\nexport interface ExecutionTaskInsightQueryOutput {\n data: any;\n}\n\nexport type ExecutionTaskInsightQueryApply = ExecutionTaskApply<\n 'Insight',\n ExecutionTaskInsightQueryParam,\n any,\n ExecutionTaskInsightDump\n>;\n\nexport type ExecutionTaskInsightQuery =\n ExecutionTask<ExecutionTaskInsightQueryApply>;\n\n/*\ntask - assertion\n*/\nexport interface ExecutionTaskInsightAssertionParam {\n assertion: string;\n}\n\nexport type ExecutionTaskInsightAssertionApply = ExecutionTaskApply<\n 'Insight',\n ExecutionTaskInsightAssertionParam,\n ServiceAssertionResponse,\n ExecutionTaskInsightDump\n>;\n\nexport type ExecutionTaskInsightAssertion =\n ExecutionTask<ExecutionTaskInsightAssertionApply>;\n\n/*\ntask - action (i.e. interact) \n*/\nexport type ExecutionTaskActionApply<ActionParam = any> = ExecutionTaskApply<\n 'Action Space',\n ActionParam,\n void,\n void\n>;\n\nexport type ExecutionTaskAction = ExecutionTask<ExecutionTaskActionApply>;\n\n/*\ntask - Log\n*/\n\nexport type ExecutionTaskLogApply<\n LogParam = {\n content: string;\n },\n> = ExecutionTaskApply<'Log', LogParam, void, void>;\n\nexport type ExecutionTaskLog = ExecutionTask<ExecutionTaskLogApply>;\n\n/*\ntask - planning\n*/\n\nexport type ExecutionTaskPlanningApply = ExecutionTaskApply<\n 'Planning',\n {\n userInstruction: string;\n aiActContext?: string;\n },\n PlanningAIResponse\n>;\n\nexport type ExecutionTaskPlanning = ExecutionTask<ExecutionTaskPlanningApply>;\n\n/*\ntask - planning-locate\n*/\nexport type ExecutionTaskPlanningLocateParam = PlanningLocateParam;\n\nexport interface ExecutionTaskPlanningLocateOutput {\n element: LocateResultElement | null;\n}\n\nexport type ExecutionTaskPlanningDump = ServiceDump;\n\nexport type ExecutionTaskPlanningLocateApply = ExecutionTaskApply<\n 'Planning',\n ExecutionTaskPlanningLocateParam,\n ExecutionTaskPlanningLocateOutput,\n ExecutionTaskPlanningDump\n>;\n\nexport type ExecutionTaskPlanningLocate =\n ExecutionTask<ExecutionTaskPlanningLocateApply>;\n\n/*\nReport metadata - extracted from ReportActionDump for per-execution writes\n*/\nexport interface ReportMeta {\n groupName: string;\n groupDescription?: string;\n sdkVersion: string;\n modelBriefs: ModelBrief[];\n deviceType?: string;\n}\n\n// Backward-compatible aliases for existing external consumers.\nexport type GroupMeta = ReportMeta;\n\n/*\nReport dump\n*/\nexport interface IReportActionDump {\n sdkVersion: string;\n groupName: string;\n groupDescription?: string;\n modelBriefs: ModelBrief[];\n executions: IExecutionDump[];\n deviceType?: string;\n}\n\n// Backward-compatible aliases for existing external consumers.\nexport type IGroupedActionDump = IReportActionDump;\n\nexport interface ModelBrief {\n /**\n * The intent/category of the model call, for example \"planning\" or \"insight\".\n */\n intent?: string;\n\n /**\n * The model name returned by usage metadata, for example \"gpt-4o\".\n */\n name?: string;\n\n /**\n * Optional human-readable model description, for example \"qwen2.5-vl mode\".\n */\n modelDescription?: string;\n}\n\n/**\n * ReportActionDump class for serializing and deserializing report action dumps\n */\nexport class ReportActionDump implements IReportActionDump {\n sdkVersion: string;\n groupName: string;\n groupDescription?: string;\n modelBriefs: ModelBrief[];\n executions: ExecutionDump[];\n deviceType?: string;\n\n constructor(data: IReportActionDump) {\n this.sdkVersion = data.sdkVersion;\n this.groupName = data.groupName;\n this.groupDescription = data.groupDescription;\n this.modelBriefs = data.modelBriefs;\n this.executions = data.executions.map((exec) =>\n exec instanceof ExecutionDump ? exec : ExecutionDump.fromJSON(exec),\n );\n this.deviceType = data.deviceType;\n }\n\n /**\n * Serialize the ReportActionDump to a JSON string\n * Uses compact { $screenshot: id } format\n */\n serialize(indents?: number): string {\n return JSON.stringify(this.toJSON(), replacerForDumpSerialization, indents);\n }\n\n /**\n * Serialize the ReportActionDump with inline screenshots to a JSON string.\n * Each ScreenshotItem is replaced with { base64: \"...\", capturedAt }.\n */\n serializeWithInlineScreenshots(indents?: number): string {\n const processValue = (obj: unknown): unknown => {\n if (obj instanceof ScreenshotItem) {\n return { base64: obj.base64, capturedAt: obj.capturedAt };\n }\n if (Array.isArray(obj)) {\n return obj.map(processValue);\n }\n if (obj && typeof obj === 'object') {\n const entries = Object.entries(obj).map(([key, value]) => [\n key,\n processValue(value),\n ]);\n return Object.fromEntries(entries);\n }\n return obj;\n };\n\n const data = processValue(this.toJSON());\n return JSON.stringify(data, null, indents);\n }\n\n /**\n * Convert to a plain object for JSON serialization\n */\n toJSON(): IReportActionDump {\n return {\n sdkVersion: this.sdkVersion,\n groupName: this.groupName,\n groupDescription: this.groupDescription,\n modelBriefs: this.modelBriefs,\n executions: this.executions.map((exec) => exec.toJSON()),\n deviceType: this.deviceType,\n };\n }\n\n /**\n * Create a ReportActionDump instance from a serialized JSON string\n */\n static fromSerializedString(serialized: string): ReportActionDump {\n const parsed = JSON.parse(\n serialized,\n reviverForDumpDeserialization,\n ) as IReportActionDump;\n return new ReportActionDump(parsed);\n }\n\n /**\n * Create a ReportActionDump instance from a plain object\n */\n static fromJSON(data: IReportActionDump): ReportActionDump {\n return new ReportActionDump(data);\n }\n\n /**\n * Collect all ScreenshotItem instances from all executions.\n *\n * @returns Array of all ScreenshotItem instances across all executions\n */\n collectAllScreenshots(): ScreenshotItem[] {\n const screenshots: ScreenshotItem[] = [];\n for (const execution of this.executions) {\n screenshots.push(...execution.collectScreenshots());\n }\n return screenshots;\n }\n\n /**\n * Serialize the dump to files with screenshots as separate PNG files.\n * Creates:\n * - {basePath} - dump JSON with { $screenshot: id } references\n * - {basePath}.screenshots/ - PNG files\n *\n * @param basePath - Base path for the dump file\n */\n serializeToFiles(basePath: string): void {\n const screenshotsDir = `${basePath}.screenshots`;\n if (!existsSync(screenshotsDir)) {\n mkdirSync(screenshotsDir, { recursive: true });\n }\n\n const screenshots = this.collectAllScreenshots();\n\n for (const screenshot of screenshots) {\n const imagePath = join(\n screenshotsDir,\n `${screenshot.id}.${screenshot.extension}`,\n );\n if (existsSync(imagePath)) {\n continue;\n }\n\n const rawBase64 = screenshot.rawBase64;\n writeFileSync(imagePath, Buffer.from(rawBase64, 'base64'));\n }\n\n // Write dump JSON with references\n writeFileSync(basePath, this.serialize(), 'utf-8');\n }\n\n /**\n * Read dump from files and return JSON string with inline screenshots.\n * Reads the dump JSON and screenshot files, then inlines the base64 data.\n *\n * @param basePath - Base path for the dump file\n * @returns JSON string with inline screenshots ({ base64: \"...\" } format)\n */\n static fromFilesAsInlineJson(basePath: string): string {\n const dumpString = readFileSync(basePath, 'utf-8');\n const screenshotsDir = `${basePath}.screenshots`;\n\n const loadFromExecutionScreenshotDir = (id: string, mimeType: string) => {\n const ext = mimeType === 'image/jpeg' ? 'jpeg' : 'png';\n const filePath = join(screenshotsDir, `${id}.${ext}`);\n if (!existsSync(filePath)) {\n return '';\n }\n const data = readFileSync(filePath);\n return `data:image/${ext};base64,${data.toString('base64')}`;\n };\n\n // Restore image references\n const dumpData = JSON.parse(dumpString);\n const store = new ScreenshotStore({\n mode: 'directory',\n reportPath: basePath,\n });\n const processedData = restoreImageReferences(dumpData, (ref) => {\n const executionFileImage = loadFromExecutionScreenshotDir(\n ref.id,\n ref.mimeType,\n );\n if (executionFileImage) {\n return executionFileImage;\n }\n\n if (ref.storage === 'inline') {\n return '';\n }\n return store.loadBase64(ref);\n });\n return JSON.stringify(processedData);\n }\n\n /**\n * Clean up all files associated with a serialized dump.\n *\n * @param basePath - Base path for the dump file\n */\n static cleanupFiles(basePath: string): void {\n const filesToClean = [basePath, `${basePath}.screenshots`];\n\n for (const filePath of filesToClean) {\n try {\n rmSync(filePath, { force: true, recursive: true });\n } catch {\n // Ignore errors - file may already be deleted\n }\n }\n }\n\n /**\n * Get all file paths associated with a serialized dump.\n *\n * @param basePath - Base path for the dump file\n * @returns Array of all associated file paths\n */\n static getFilePaths(basePath: string): string[] {\n return [basePath, `${basePath}.screenshots`];\n }\n}\n\n// Backward-compatible aliases for existing external consumers.\nexport type GroupedActionDump = ReportActionDump;\nexport const GroupedActionDump = ReportActionDump;\n\nexport type InterfaceType =\n | 'puppeteer'\n | 'playwright'\n | 'static'\n | 'chrome-extension-proxy'\n | 'android'\n | string;\n\nexport interface StreamingCodeGenerationOptions {\n /** Whether to enable streaming output */\n stream?: boolean;\n /** Callback function to handle streaming chunks */\n onChunk?: StreamingCallback;\n /** Callback function to handle streaming completion */\n onComplete?: (finalCode: string) => void;\n /** Callback function to handle streaming errors */\n onError?: (error: Error) => void;\n}\n\nexport type StreamingCallback = (chunk: CodeGenerationChunk) => void;\n\nexport interface CodeGenerationChunk {\n /** The incremental content chunk */\n content: string;\n /** The reasoning content */\n reasoning_content: string;\n /** The accumulated content so far */\n accumulated: string;\n /** Whether this is the final chunk */\n isComplete: boolean;\n /** Token usage information if available */\n usage?: AIUsageInfo;\n}\n\nexport interface StreamingAIResponse {\n /** The final accumulated content */\n content: string;\n /** Token usage information */\n usage?: AIUsageInfo;\n /** Whether the response was streamed */\n isStreamed: boolean;\n}\n\nexport interface DeviceAction<TParam = any, TReturn = any> {\n name: string;\n description?: string;\n interfaceAlias?: string;\n paramSchema?: z.ZodType<TParam>;\n call: (param: TParam, context: ExecutorContext) => Promise<TReturn> | TReturn;\n delayAfterRunner?: number;\n /**\n * An example param object for this action.\n * Locate fields with { prompt } will automatically get bbox injected when needed.\n */\n sample?: { [K in keyof TParam]?: any };\n}\n\n/**\n * Type utilities for extracting types from DeviceAction definitions\n */\n\n/**\n * Extract parameter type from a DeviceAction\n */\nexport type ActionParam<Action extends DeviceAction<any, any>> =\n Action extends DeviceAction<infer P, any> ? P : never;\n\n/**\n * Extract return type from a DeviceAction\n */\nexport type ActionReturn<Action extends DeviceAction<any, any>> =\n Action extends DeviceAction<any, infer R> ? R : never;\n\n/**\n * Web-specific types\n */\nexport interface WebElementInfo extends BaseElement {\n id: string;\n attributes: {\n nodeType: NodeType;\n [key: string]: string;\n };\n}\n\n/**\n * Agent\n */\n\nexport type CacheConfig = {\n strategy?: 'read-only' | 'read-write' | 'write-only';\n id: string;\n};\n\nexport type Cache =\n | false // No read, no write\n | true // Will throw error at runtime - deprecated\n | CacheConfig; // Object configuration (requires explicit id)\n\nexport interface AgentOpt {\n // @deprecated Use `reportFileName` and `cache.id` instead.\n testId?: string;\n // @deprecated\n cacheId?: string; // Keep backward compatibility, but marked as deprecated\n groupName?: string;\n groupDescription?: string;\n /* if auto generate report, default true */\n generateReport?: boolean;\n /* if persist per-execution dump files next to the report, default false */\n persistExecutionDump?: boolean;\n /* if auto print report msg, default true */\n autoPrintReportMsg?: boolean;\n\n /**\n * Use directory-based report format with separate image files.\n *\n * When enabled:\n * - Screenshots are saved as PNG files in a `screenshots/` subdirectory\n * - Report is generated as `index.html` with relative image paths\n * - Reduces memory usage and report file size\n *\n * IMPORTANT: 'html-and-external-assets' reports must be served via HTTP server\n * (e.g., `npx serve ./report-dir`). The file:// protocol will not\n * work due to browser CORS restrictions.\n *\n * @default 'single-html'\n */\n outputFormat?: 'single-html' | 'html-and-external-assets';\n\n onTaskStartTip?: OnTaskStartTip;\n aiActContext?: string;\n aiActionContext?: string;\n /* custom report file name */\n reportFileName?: string;\n modelConfig?: TModelConfig;\n cache?: Cache;\n /**\n * Maximum number of replanning cycles for aiAct.\n * Defaults to 20 (40 for `vlm-ui-tars`) when not provided.\n * If omitted, the agent will also read `MIDSCENE_REPLANNING_CYCLE_LIMIT` for backward compatibility.\n */\n replanningCycleLimit?: number;\n\n /**\n * Wait time in milliseconds after each action execution.\n * This allows the UI to settle and stabilize before the next action.\n * Defaults to 300ms when not provided.\n */\n waitAfterAction?: number;\n\n /**\n * When set to true, Midscene will use the target device's time (Android/iOS)\n * instead of the system time. Useful when the device time differs from the\n * host machine. Default: false\n */\n useDeviceTimestamp?: boolean;\n\n /**\n * Custom screenshot shrink factor to reduce AI token usage.\n * When set, the screenshot will be scaled down by this factor from the physical resolution.\n *\n * Example:\n * - Physical screen width: 3000px, dpr=6\n * - Logical width: 500px\n * - screenshotShrinkFactor: 2\n * - Actual shrunk screenshot width: 3000 / 2 = 1500px\n * - AI analyzes the 1500px screenshot\n * - Coordinates are transformed back to logical (500px) before actions execute\n *\n * Benefits:\n * - Reduces token usage for high-resolution screenshots\n * - Maintains accuracy by scaling coordinates appropriately\n *\n * Must be >= 1 (shrinking only, enlarging is not supported).\n *\n * @default 1 (no shrinking, uses original physical screenshot)\n */\n screenshotShrinkFactor?: number;\n\n /**\n * Custom OpenAI client factory function\n *\n * If provided, this function will be called to create OpenAI client instances\n * for each AI call, allowing you to:\n * - Wrap clients with observability tools (langsmith, langfuse)\n * - Use custom OpenAI-compatible clients\n * - Apply different configurations based on intent\n *\n * @param config - Resolved model configuration\n * @returns OpenAI client instance (original or wrapped)\n *\n * @example\n * ```typescript\n * createOpenAIClient: async (openai, opts) => {\n * // Wrap with langsmith for planning tasks\n * if (opts.baseURL?.includes('planning')) {\n * return wrapOpenAI(openai, { metadata: { task: 'planning' } });\n * }\n *\n * return openai;\n * }\n * ```\n */\n createOpenAIClient?: CreateOpenAIClientFn;\n}\n\nexport type TestStatus =\n | 'passed'\n | 'failed'\n | 'timedOut'\n | 'skipped'\n | 'interrupted';\n\nexport interface ReportFileAttributes {\n testDuration: number;\n testStatus: TestStatus;\n testTitle: string;\n testId: string;\n testDescription: string;\n}\n\nexport type ReportFileWithAttributes =\n | {\n reportFilePath: string;\n reportAttributes: ReportFileAttributes;\n }\n | {\n reportFilePath?: string;\n reportAttributes: ReportFileAttributes & { testStatus: 'skipped' };\n };\n"],"names":["UIContext","ServiceError","Error","message","dump","replacerForDumpSerialization","_key","value","reviverForDumpDeserialization","key","ScreenshotItem","ExecutionDump","indents","JSON","task","serialized","parsed","data","screenshots","record","ReportActionDump","processValue","obj","Array","entries","Object","exec","execution","basePath","screenshotsDir","existsSync","mkdirSync","screenshot","imagePath","join","rawBase64","writeFileSync","Buffer","dumpString","readFileSync","loadFromExecutionScreenshotDir","id","mimeType","ext","filePath","dumpData","store","ScreenshotStore","processedData","restoreImageReferences","ref","executionFileImage","filesToClean","rmSync","GroupedActionDump"],"mappings":"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"}
package/dist/es/utils.mjs CHANGED
@@ -3,10 +3,10 @@ import { appendFileSync, closeSync, existsSync, mkdirSync, openSync, readFileSyn
3
3
  import { tmpdir } from "node:os";
4
4
  import { join } from "node:path";
5
5
  import { promisify } from "node:util";
6
- import { defaultRunDirName, getMidsceneRunSubDir } from "@midscene/shared/common";
7
- import { MIDSCENE_CACHE, MIDSCENE_DEBUG_MODE, globalConfigManager } from "@midscene/shared/env";
8
- import { getRunningPkgInfo } from "@midscene/shared/node";
9
- import { assert, escapeScriptTag, ifInBrowser, ifInWorker, logMsg, uuid } from "@midscene/shared/utils";
6
+ import { defaultRunDirName, getMidsceneRunSubDir } from "@aiscene/shared/common";
7
+ import { MIDSCENE_CACHE, MIDSCENE_DEBUG_MODE, globalConfigManager } from "@aiscene/shared/env";
8
+ import { getRunningPkgInfo } from "@aiscene/shared/node";
9
+ import { assert, escapeScriptTag, ifInBrowser, ifInWorker, logMsg, uuid } from "@aiscene/shared/utils";
10
10
  let logEnvReady = false;
11
11
  const groupedActionDumpFileExt = 'web-dump.json';
12
12
  function processCacheConfig(cache, cacheId) {
@@ -1 +1 @@
1
- {"version":3,"file":"utils.mjs","sources":["../../src/utils.ts"],"sourcesContent":["import { execFile } from 'node:child_process';\nimport * as fs from 'node:fs';\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';\nimport { tmpdir } from 'node:os';\nimport * as path from 'node:path';\nimport { promisify } from 'node:util';\nimport {\n defaultRunDirName,\n getMidsceneRunSubDir,\n} from '@midscene/shared/common';\nimport {\n MIDSCENE_CACHE,\n MIDSCENE_DEBUG_MODE,\n globalConfigManager,\n} from '@midscene/shared/env';\nimport { getRunningPkgInfo } from '@midscene/shared/node';\nimport { assert, logMsg } from '@midscene/shared/utils';\nimport {\n escapeScriptTag,\n ifInBrowser,\n ifInWorker,\n uuid,\n} from '@midscene/shared/utils';\nimport type { Cache, Rect, ReportDumpWithAttributes } from './types';\n\nlet logEnvReady = false;\n\nexport { appendFileSync } from 'node:fs';\n\nexport const groupedActionDumpFileExt = 'web-dump.json';\n\n/**\n * Process cache configuration with environment variable support and backward compatibility.\n *\n * @param cache - The original cache configuration\n * @param cacheId - The cache ID to use as:\n * 1. Fallback ID when cache is true or cache object has no ID\n * 2. Legacy cacheId when cache is undefined (requires MIDSCENE_CACHE env var)\n * @returns Processed cache configuration\n */\nexport function processCacheConfig(\n cache: Cache | undefined,\n cacheId: string,\n): Cache | undefined {\n // 1. New cache object configuration (highest priority)\n if (cache !== undefined) {\n if (cache === false) {\n return undefined; // Completely disable cache\n }\n\n if (cache === true) {\n // Auto-generate ID using cacheId for CLI/YAML scenarios\n // Agent will validate and reject this later if needed\n return { id: cacheId };\n }\n\n // cache is object configuration\n if (typeof cache === 'object' && cache !== null) {\n // Auto-generate ID using cacheId when missing (for CLI/YAML scenarios)\n if (!cache.id) {\n return { ...cache, id: cacheId };\n }\n return cache;\n }\n }\n\n // 2. Backward compatibility: support old cacheId (requires environment variable)\n // When cache is undefined, check if legacy cacheId mode is enabled via env var\n const envEnabled = globalConfigManager.getEnvConfigInBoolean(MIDSCENE_CACHE);\n\n if (envEnabled && cacheId) {\n return { id: cacheId };\n }\n\n // 3. No cache configuration\n return undefined;\n}\n\nconst reportInitializedMap = new Map<string, boolean>();\nconst reportGroupIdMap = new Map<string, string>();\n\ndeclare const __DEV_REPORT_PATH__: string;\n\nexport function getReportTpl() {\n if (typeof __DEV_REPORT_PATH__ === 'string' && __DEV_REPORT_PATH__) {\n return fs.readFileSync(__DEV_REPORT_PATH__, 'utf-8');\n }\n const reportTpl = 'REPLACE_ME_WITH_REPORT_HTML';\n\n return reportTpl;\n}\n\n/**\n * Insert content before </html> in an HTML string.\n * Falls back to simple concatenation if </html> is not found.\n */\nexport function insertContentBeforeClosingHtml(\n html: string,\n content: string,\n): string {\n const htmlEndIdx = html.lastIndexOf('</html>');\n if (htmlEndIdx === -1) {\n return html + content;\n }\n return `${html.slice(0, htmlEndIdx)}${content}\\n${html.slice(htmlEndIdx)}`;\n}\n\n/**\n * high performance, insert script before </html> in HTML file\n * only truncate and append, no temporary file\n */\nexport function insertScriptBeforeClosingHtml(\n filePath: string,\n scriptContent: string,\n): void {\n const htmlEndTag = '</html>';\n const stat = fs.statSync(filePath);\n\n const readSize = Math.min(stat.size, 4096);\n const start = Math.max(0, stat.size - readSize);\n const buffer = Buffer.alloc(stat.size - start);\n const fd = fs.openSync(filePath, 'r');\n fs.readSync(fd, buffer, 0, buffer.length, start);\n fs.closeSync(fd);\n\n const tailStr = buffer.toString('utf8');\n const htmlEndIdx = tailStr.lastIndexOf(htmlEndTag);\n if (htmlEndIdx === -1) {\n throw new Error(`No </html> found in file:${filePath}`);\n }\n\n // calculate the correct byte position: char position to byte position\n const beforeHtmlInTail = tailStr.slice(0, htmlEndIdx);\n const htmlEndPos = start + Buffer.byteLength(beforeHtmlInTail, 'utf8');\n\n // truncate to </html> before\n fs.truncateSync(filePath, htmlEndPos);\n // append script and </html>\n fs.appendFileSync(filePath, `${scriptContent}\\n${htmlEndTag}\\n`);\n}\n\nexport function reportHTMLContent(\n dumpData: string | ReportDumpWithAttributes,\n reportPath?: string,\n appendReport?: boolean,\n withTpl = true, // whether return with report template, default = true\n): string {\n let tpl = '';\n if (withTpl) {\n tpl = getReportTpl();\n\n if (!tpl) {\n console.warn('reportTpl is not set, will not write report');\n return '';\n }\n }\n // if reportPath is set, it means we are in write to file mode\n const writeToFile = reportPath && !ifInBrowser;\n let dumpContent = '';\n\n const resolveAutoGroupId = (): string => {\n if (!reportPath || !appendReport) {\n return uuid();\n }\n\n const existingGroupId = reportGroupIdMap.get(reportPath);\n if (existingGroupId) {\n return existingGroupId;\n }\n\n const newGroupId = uuid();\n reportGroupIdMap.set(reportPath, newGroupId);\n return newGroupId;\n };\n\n if (typeof dumpData === 'string') {\n const groupId = resolveAutoGroupId();\n // do not use template string here, will cause bundle error\n dumpContent =\n // biome-ignore lint/style/useTemplate: <explanation>\n '<script type=\"midscene_web_dump\" type=\"application/json\" data-group-id=\"' +\n encodeURIComponent(groupId) +\n '\">\\n' +\n escapeScriptTag(dumpData) +\n '\\n</script>';\n } else {\n const { dumpString, attributes } = dumpData;\n const attributesArr = Object.entries(attributes || {})\n .filter((entry): entry is [string, string | number | boolean] => {\n return entry[1] !== undefined && entry[1] !== null;\n })\n .map(([key, value]) => {\n return `${key}=\"${encodeURIComponent(value)}\"`;\n });\n\n dumpContent =\n // do not use template string here, will cause bundle error\n // biome-ignore lint/style/useTemplate: <explanation>\n '<script type=\"midscene_web_dump\" type=\"application/json\" ' +\n attributesArr.join(' ') +\n '>\\n' +\n escapeScriptTag(dumpString) +\n '\\n</script>';\n }\n\n if (writeToFile) {\n if (!appendReport) {\n writeFileSync(\n reportPath!,\n insertContentBeforeClosingHtml(tpl, dumpContent),\n { flag: 'w' },\n );\n return reportPath!;\n }\n\n if (!reportInitializedMap.get(reportPath!)) {\n writeFileSync(reportPath!, tpl, { flag: 'w' });\n reportInitializedMap.set(reportPath!, true);\n }\n\n insertScriptBeforeClosingHtml(reportPath!, dumpContent);\n return reportPath!;\n }\n\n return insertContentBeforeClosingHtml(tpl, dumpContent);\n}\n\nexport function writeDumpReport(\n fileName: string,\n dumpData: string | ReportDumpWithAttributes,\n appendReport?: boolean,\n): string | null {\n if (ifInBrowser || ifInWorker) {\n console.log('will not write report in browser');\n return null;\n }\n\n const reportPath = path.join(\n getMidsceneRunSubDir('report'),\n `${fileName}.html`,\n );\n\n reportHTMLContent(dumpData, reportPath, appendReport);\n\n if (process.env.MIDSCENE_DEBUG_LOG_JSON) {\n const jsonPath = `${reportPath}.json`;\n let data;\n\n if (typeof dumpData === 'string') {\n data = JSON.parse(dumpData) as ReportDumpWithAttributes;\n } else {\n data = dumpData;\n }\n\n writeFileSync(jsonPath, JSON.stringify(data, null, 2), {\n flag: appendReport ? 'a' : 'w',\n });\n\n logMsg(`Midscene - dump file written: ${jsonPath}`);\n }\n\n return reportPath;\n}\n\nexport function writeLogFile(opts: {\n fileName: string;\n fileExt: string;\n fileContent: string | ReportDumpWithAttributes;\n type: 'dump' | 'cache' | 'report' | 'tmp';\n generateReport?: boolean;\n appendReport?: boolean;\n}) {\n if (ifInBrowser || ifInWorker) {\n return '/mock/report.html';\n }\n const { fileName, fileExt, fileContent, type = 'dump' } = opts;\n const targetDir = getMidsceneRunSubDir(type);\n // Ensure directory exists\n if (!logEnvReady) {\n assert(targetDir, 'logDir should be set before writing dump file');\n\n // gitIgnore in the parent directory\n const gitIgnorePath = path.join(targetDir, '../../.gitignore');\n const gitPath = path.join(targetDir, '../../.git');\n let gitIgnoreContent = '';\n\n if (existsSync(gitPath)) {\n // if the git path exists, we need to add the log folder to the git ignore file\n if (existsSync(gitIgnorePath)) {\n gitIgnoreContent = readFileSync(gitIgnorePath, 'utf-8');\n }\n\n // ignore the log folder\n if (!gitIgnoreContent.includes(`${defaultRunDirName}/`)) {\n writeFileSync(\n gitIgnorePath,\n `${gitIgnoreContent}\\n# Midscene.js dump files\\n${defaultRunDirName}/dump\\n${defaultRunDirName}/report\\n${defaultRunDirName}/tmp\\n${defaultRunDirName}/log\\n`,\n 'utf-8',\n );\n }\n }\n\n logEnvReady = true;\n }\n\n const filePath = path.join(targetDir, `${fileName}.${fileExt}`);\n\n if (type !== 'dump') {\n // do not write dump file any more\n writeFileSync(filePath, JSON.stringify(fileContent));\n }\n\n if (opts?.generateReport) {\n return writeDumpReport(fileName, fileContent, opts.appendReport);\n }\n\n return filePath;\n}\n\nexport function getTmpDir(): string | null {\n try {\n const runningPkgInfo = getRunningPkgInfo();\n if (!runningPkgInfo) {\n return null;\n }\n const { name } = runningPkgInfo;\n const tmpPath = path.join(tmpdir(), name);\n mkdirSync(tmpPath, { recursive: true });\n return tmpPath;\n } catch (e) {\n return null;\n }\n}\n\nexport function getTmpFile(fileExtWithoutDot: string): string | null {\n if (ifInBrowser || ifInWorker) {\n return null;\n }\n const tmpDir = getTmpDir();\n const filename = `${uuid()}.${fileExtWithoutDot}`;\n return path.join(tmpDir!, filename);\n}\n\nexport function overlapped(container: Rect, target: Rect) {\n // container and the target have some part overlapped\n return (\n container.left < target.left + target.width &&\n container.left + container.width > target.left &&\n container.top < target.top + target.height &&\n container.top + container.height > target.top\n );\n}\n\nexport async function sleep(ms: number) {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nexport function replacerForPageObject(_key: string, value: any) {\n if (value && value.constructor?.name === 'Page') {\n return '[Page object]';\n }\n if (value && value.constructor?.name === 'Browser') {\n return '[Browser object]';\n }\n // Handle ScreenshotItem serialization\n if (value && typeof value.toSerializable === 'function') {\n return value.toSerializable();\n }\n return value;\n}\n\nexport function stringifyDumpData(data: any, indents?: number) {\n return JSON.stringify(data, replacerForPageObject, indents);\n}\n\ndeclare const __VERSION__: string;\n\nexport function getVersion() {\n return __VERSION__;\n}\n\nfunction debugLog(...message: any[]) {\n // always read from process.env, and cannot be override by modelConfig, overrideAIConfig, etc.\n // also avoid circular dependency\n const debugMode = process.env[MIDSCENE_DEBUG_MODE];\n if (debugMode) {\n console.log('[Midscene]', ...message);\n }\n}\n\nlet gitInfoPromise: Promise<{ repoUrl: string; userEmail: string }> | null =\n null;\n\nfunction getGitInfoAsync(): Promise<{ repoUrl: string; userEmail: string }> {\n if (gitInfoPromise) return gitInfoPromise;\n\n const execFileAsync = promisify(execFile);\n\n gitInfoPromise = Promise.all([\n execFileAsync('git', ['config', '--get', 'remote.origin.url']).then(\n ({ stdout }) => stdout.trim(),\n () => '',\n ),\n execFileAsync('git', ['config', '--get', 'user.email']).then(\n ({ stdout }) => stdout.trim(),\n () => '',\n ),\n ]).then(([repoUrl, userEmail]) => ({ repoUrl, userEmail }));\n\n return gitInfoPromise;\n}\n\nlet lastReportedRepoUrl = '';\nexport async function uploadTestInfoToServer({\n testUrl,\n serverUrl,\n}: { testUrl: string; serverUrl?: string }) {\n if (!serverUrl) return;\n\n const { repoUrl, userEmail } = await getGitInfoAsync();\n\n // Only upload test info if:\n // 1. Server URL is configured AND\n // 2. Either:\n // - We have a repo URL that's different from last reported one (to avoid duplicate reports)\n // - OR we don't have a repo URL but have a test URL (for non-git environments)\n if (repoUrl ? repoUrl !== lastReportedRepoUrl : !!testUrl) {\n debugLog('Uploading test info to server', {\n serverUrl,\n repoUrl,\n testUrl,\n userEmail,\n });\n\n fetch(serverUrl, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n repo_url: repoUrl,\n test_url: testUrl,\n user_email: userEmail,\n }),\n })\n .then((response) => response.json())\n .then((data) => {\n debugLog('Successfully uploaded test info to server:', data);\n })\n .catch((error) =>\n debugLog('Failed to upload test info to server:', error),\n );\n lastReportedRepoUrl = repoUrl;\n }\n}\n"],"names":["logEnvReady","groupedActionDumpFileExt","processCacheConfig","cache","cacheId","undefined","envEnabled","globalConfigManager","MIDSCENE_CACHE","reportInitializedMap","Map","reportGroupIdMap","getReportTpl","reportTpl","insertContentBeforeClosingHtml","html","content","htmlEndIdx","insertScriptBeforeClosingHtml","filePath","scriptContent","htmlEndTag","stat","fs","readSize","Math","start","buffer","Buffer","fd","tailStr","Error","beforeHtmlInTail","htmlEndPos","reportHTMLContent","dumpData","reportPath","appendReport","withTpl","tpl","console","writeToFile","ifInBrowser","dumpContent","resolveAutoGroupId","uuid","existingGroupId","newGroupId","groupId","encodeURIComponent","escapeScriptTag","dumpString","attributes","attributesArr","Object","entry","key","value","writeFileSync","writeDumpReport","fileName","ifInWorker","path","getMidsceneRunSubDir","process","jsonPath","data","JSON","logMsg","writeLogFile","opts","fileExt","fileContent","type","targetDir","assert","gitIgnorePath","gitPath","gitIgnoreContent","existsSync","readFileSync","defaultRunDirName","getTmpDir","runningPkgInfo","getRunningPkgInfo","name","tmpPath","tmpdir","mkdirSync","e","getTmpFile","fileExtWithoutDot","tmpDir","filename","overlapped","container","target","sleep","ms","Promise","resolve","setTimeout","replacerForPageObject","_key","stringifyDumpData","indents","getVersion","__VERSION__","debugLog","message","debugMode","MIDSCENE_DEBUG_MODE","gitInfoPromise","getGitInfoAsync","execFileAsync","promisify","execFile","stdout","repoUrl","userEmail","lastReportedRepoUrl","uploadTestInfoToServer","testUrl","serverUrl","fetch","response","error"],"mappings":"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"}
1
+ {"version":3,"file":"utils.mjs","sources":["../../src/utils.ts"],"sourcesContent":["import { execFile } from 'node:child_process';\nimport * as fs from 'node:fs';\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';\nimport { tmpdir } from 'node:os';\nimport * as path from 'node:path';\nimport { promisify } from 'node:util';\nimport {\n defaultRunDirName,\n getMidsceneRunSubDir,\n} from '@aiscene/shared/common';\nimport {\n MIDSCENE_CACHE,\n MIDSCENE_DEBUG_MODE,\n globalConfigManager,\n} from '@aiscene/shared/env';\nimport { getRunningPkgInfo } from '@aiscene/shared/node';\nimport { assert, logMsg } from '@aiscene/shared/utils';\nimport {\n escapeScriptTag,\n ifInBrowser,\n ifInWorker,\n uuid,\n} from '@aiscene/shared/utils';\nimport type { Cache, Rect, ReportDumpWithAttributes } from './types';\n\nlet logEnvReady = false;\n\nexport { appendFileSync } from 'node:fs';\n\nexport const groupedActionDumpFileExt = 'web-dump.json';\n\n/**\n * Process cache configuration with environment variable support and backward compatibility.\n *\n * @param cache - The original cache configuration\n * @param cacheId - The cache ID to use as:\n * 1. Fallback ID when cache is true or cache object has no ID\n * 2. Legacy cacheId when cache is undefined (requires MIDSCENE_CACHE env var)\n * @returns Processed cache configuration\n */\nexport function processCacheConfig(\n cache: Cache | undefined,\n cacheId: string,\n): Cache | undefined {\n // 1. New cache object configuration (highest priority)\n if (cache !== undefined) {\n if (cache === false) {\n return undefined; // Completely disable cache\n }\n\n if (cache === true) {\n // Auto-generate ID using cacheId for CLI/YAML scenarios\n // Agent will validate and reject this later if needed\n return { id: cacheId };\n }\n\n // cache is object configuration\n if (typeof cache === 'object' && cache !== null) {\n // Auto-generate ID using cacheId when missing (for CLI/YAML scenarios)\n if (!cache.id) {\n return { ...cache, id: cacheId };\n }\n return cache;\n }\n }\n\n // 2. Backward compatibility: support old cacheId (requires environment variable)\n // When cache is undefined, check if legacy cacheId mode is enabled via env var\n const envEnabled = globalConfigManager.getEnvConfigInBoolean(MIDSCENE_CACHE);\n\n if (envEnabled && cacheId) {\n return { id: cacheId };\n }\n\n // 3. No cache configuration\n return undefined;\n}\n\nconst reportInitializedMap = new Map<string, boolean>();\nconst reportGroupIdMap = new Map<string, string>();\n\ndeclare const __DEV_REPORT_PATH__: string;\n\nexport function getReportTpl() {\n if (typeof __DEV_REPORT_PATH__ === 'string' && __DEV_REPORT_PATH__) {\n return fs.readFileSync(__DEV_REPORT_PATH__, 'utf-8');\n }\n const reportTpl = 'REPLACE_ME_WITH_REPORT_HTML';\n\n return reportTpl;\n}\n\n/**\n * Insert content before </html> in an HTML string.\n * Falls back to simple concatenation if </html> is not found.\n */\nexport function insertContentBeforeClosingHtml(\n html: string,\n content: string,\n): string {\n const htmlEndIdx = html.lastIndexOf('</html>');\n if (htmlEndIdx === -1) {\n return html + content;\n }\n return `${html.slice(0, htmlEndIdx)}${content}\\n${html.slice(htmlEndIdx)}`;\n}\n\n/**\n * high performance, insert script before </html> in HTML file\n * only truncate and append, no temporary file\n */\nexport function insertScriptBeforeClosingHtml(\n filePath: string,\n scriptContent: string,\n): void {\n const htmlEndTag = '</html>';\n const stat = fs.statSync(filePath);\n\n const readSize = Math.min(stat.size, 4096);\n const start = Math.max(0, stat.size - readSize);\n const buffer = Buffer.alloc(stat.size - start);\n const fd = fs.openSync(filePath, 'r');\n fs.readSync(fd, buffer, 0, buffer.length, start);\n fs.closeSync(fd);\n\n const tailStr = buffer.toString('utf8');\n const htmlEndIdx = tailStr.lastIndexOf(htmlEndTag);\n if (htmlEndIdx === -1) {\n throw new Error(`No </html> found in file:${filePath}`);\n }\n\n // calculate the correct byte position: char position to byte position\n const beforeHtmlInTail = tailStr.slice(0, htmlEndIdx);\n const htmlEndPos = start + Buffer.byteLength(beforeHtmlInTail, 'utf8');\n\n // truncate to </html> before\n fs.truncateSync(filePath, htmlEndPos);\n // append script and </html>\n fs.appendFileSync(filePath, `${scriptContent}\\n${htmlEndTag}\\n`);\n}\n\nexport function reportHTMLContent(\n dumpData: string | ReportDumpWithAttributes,\n reportPath?: string,\n appendReport?: boolean,\n withTpl = true, // whether return with report template, default = true\n): string {\n let tpl = '';\n if (withTpl) {\n tpl = getReportTpl();\n\n if (!tpl) {\n console.warn('reportTpl is not set, will not write report');\n return '';\n }\n }\n // if reportPath is set, it means we are in write to file mode\n const writeToFile = reportPath && !ifInBrowser;\n let dumpContent = '';\n\n const resolveAutoGroupId = (): string => {\n if (!reportPath || !appendReport) {\n return uuid();\n }\n\n const existingGroupId = reportGroupIdMap.get(reportPath);\n if (existingGroupId) {\n return existingGroupId;\n }\n\n const newGroupId = uuid();\n reportGroupIdMap.set(reportPath, newGroupId);\n return newGroupId;\n };\n\n if (typeof dumpData === 'string') {\n const groupId = resolveAutoGroupId();\n // do not use template string here, will cause bundle error\n dumpContent =\n // biome-ignore lint/style/useTemplate: <explanation>\n '<script type=\"midscene_web_dump\" type=\"application/json\" data-group-id=\"' +\n encodeURIComponent(groupId) +\n '\">\\n' +\n escapeScriptTag(dumpData) +\n '\\n</script>';\n } else {\n const { dumpString, attributes } = dumpData;\n const attributesArr = Object.entries(attributes || {})\n .filter((entry): entry is [string, string | number | boolean] => {\n return entry[1] !== undefined && entry[1] !== null;\n })\n .map(([key, value]) => {\n return `${key}=\"${encodeURIComponent(value)}\"`;\n });\n\n dumpContent =\n // do not use template string here, will cause bundle error\n // biome-ignore lint/style/useTemplate: <explanation>\n '<script type=\"midscene_web_dump\" type=\"application/json\" ' +\n attributesArr.join(' ') +\n '>\\n' +\n escapeScriptTag(dumpString) +\n '\\n</script>';\n }\n\n if (writeToFile) {\n if (!appendReport) {\n writeFileSync(\n reportPath!,\n insertContentBeforeClosingHtml(tpl, dumpContent),\n { flag: 'w' },\n );\n return reportPath!;\n }\n\n if (!reportInitializedMap.get(reportPath!)) {\n writeFileSync(reportPath!, tpl, { flag: 'w' });\n reportInitializedMap.set(reportPath!, true);\n }\n\n insertScriptBeforeClosingHtml(reportPath!, dumpContent);\n return reportPath!;\n }\n\n return insertContentBeforeClosingHtml(tpl, dumpContent);\n}\n\nexport function writeDumpReport(\n fileName: string,\n dumpData: string | ReportDumpWithAttributes,\n appendReport?: boolean,\n): string | null {\n if (ifInBrowser || ifInWorker) {\n console.log('will not write report in browser');\n return null;\n }\n\n const reportPath = path.join(\n getMidsceneRunSubDir('report'),\n `${fileName}.html`,\n );\n\n reportHTMLContent(dumpData, reportPath, appendReport);\n\n if (process.env.MIDSCENE_DEBUG_LOG_JSON) {\n const jsonPath = `${reportPath}.json`;\n let data;\n\n if (typeof dumpData === 'string') {\n data = JSON.parse(dumpData) as ReportDumpWithAttributes;\n } else {\n data = dumpData;\n }\n\n writeFileSync(jsonPath, JSON.stringify(data, null, 2), {\n flag: appendReport ? 'a' : 'w',\n });\n\n logMsg(`Midscene - dump file written: ${jsonPath}`);\n }\n\n return reportPath;\n}\n\nexport function writeLogFile(opts: {\n fileName: string;\n fileExt: string;\n fileContent: string | ReportDumpWithAttributes;\n type: 'dump' | 'cache' | 'report' | 'tmp';\n generateReport?: boolean;\n appendReport?: boolean;\n}) {\n if (ifInBrowser || ifInWorker) {\n return '/mock/report.html';\n }\n const { fileName, fileExt, fileContent, type = 'dump' } = opts;\n const targetDir = getMidsceneRunSubDir(type);\n // Ensure directory exists\n if (!logEnvReady) {\n assert(targetDir, 'logDir should be set before writing dump file');\n\n // gitIgnore in the parent directory\n const gitIgnorePath = path.join(targetDir, '../../.gitignore');\n const gitPath = path.join(targetDir, '../../.git');\n let gitIgnoreContent = '';\n\n if (existsSync(gitPath)) {\n // if the git path exists, we need to add the log folder to the git ignore file\n if (existsSync(gitIgnorePath)) {\n gitIgnoreContent = readFileSync(gitIgnorePath, 'utf-8');\n }\n\n // ignore the log folder\n if (!gitIgnoreContent.includes(`${defaultRunDirName}/`)) {\n writeFileSync(\n gitIgnorePath,\n `${gitIgnoreContent}\\n# Midscene.js dump files\\n${defaultRunDirName}/dump\\n${defaultRunDirName}/report\\n${defaultRunDirName}/tmp\\n${defaultRunDirName}/log\\n`,\n 'utf-8',\n );\n }\n }\n\n logEnvReady = true;\n }\n\n const filePath = path.join(targetDir, `${fileName}.${fileExt}`);\n\n if (type !== 'dump') {\n // do not write dump file any more\n writeFileSync(filePath, JSON.stringify(fileContent));\n }\n\n if (opts?.generateReport) {\n return writeDumpReport(fileName, fileContent, opts.appendReport);\n }\n\n return filePath;\n}\n\nexport function getTmpDir(): string | null {\n try {\n const runningPkgInfo = getRunningPkgInfo();\n if (!runningPkgInfo) {\n return null;\n }\n const { name } = runningPkgInfo;\n const tmpPath = path.join(tmpdir(), name);\n mkdirSync(tmpPath, { recursive: true });\n return tmpPath;\n } catch (e) {\n return null;\n }\n}\n\nexport function getTmpFile(fileExtWithoutDot: string): string | null {\n if (ifInBrowser || ifInWorker) {\n return null;\n }\n const tmpDir = getTmpDir();\n const filename = `${uuid()}.${fileExtWithoutDot}`;\n return path.join(tmpDir!, filename);\n}\n\nexport function overlapped(container: Rect, target: Rect) {\n // container and the target have some part overlapped\n return (\n container.left < target.left + target.width &&\n container.left + container.width > target.left &&\n container.top < target.top + target.height &&\n container.top + container.height > target.top\n );\n}\n\nexport async function sleep(ms: number) {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nexport function replacerForPageObject(_key: string, value: any) {\n if (value && value.constructor?.name === 'Page') {\n return '[Page object]';\n }\n if (value && value.constructor?.name === 'Browser') {\n return '[Browser object]';\n }\n // Handle ScreenshotItem serialization\n if (value && typeof value.toSerializable === 'function') {\n return value.toSerializable();\n }\n return value;\n}\n\nexport function stringifyDumpData(data: any, indents?: number) {\n return JSON.stringify(data, replacerForPageObject, indents);\n}\n\ndeclare const __VERSION__: string;\n\nexport function getVersion() {\n return __VERSION__;\n}\n\nfunction debugLog(...message: any[]) {\n // always read from process.env, and cannot be override by modelConfig, overrideAIConfig, etc.\n // also avoid circular dependency\n const debugMode = process.env[MIDSCENE_DEBUG_MODE];\n if (debugMode) {\n console.log('[Midscene]', ...message);\n }\n}\n\nlet gitInfoPromise: Promise<{ repoUrl: string; userEmail: string }> | null =\n null;\n\nfunction getGitInfoAsync(): Promise<{ repoUrl: string; userEmail: string }> {\n if (gitInfoPromise) return gitInfoPromise;\n\n const execFileAsync = promisify(execFile);\n\n gitInfoPromise = Promise.all([\n execFileAsync('git', ['config', '--get', 'remote.origin.url']).then(\n ({ stdout }) => stdout.trim(),\n () => '',\n ),\n execFileAsync('git', ['config', '--get', 'user.email']).then(\n ({ stdout }) => stdout.trim(),\n () => '',\n ),\n ]).then(([repoUrl, userEmail]) => ({ repoUrl, userEmail }));\n\n return gitInfoPromise;\n}\n\nlet lastReportedRepoUrl = '';\nexport async function uploadTestInfoToServer({\n testUrl,\n serverUrl,\n}: { testUrl: string; serverUrl?: string }) {\n if (!serverUrl) return;\n\n const { repoUrl, userEmail } = await getGitInfoAsync();\n\n // Only upload test info if:\n // 1. Server URL is configured AND\n // 2. Either:\n // - We have a repo URL that's different from last reported one (to avoid duplicate reports)\n // - OR we don't have a repo URL but have a test URL (for non-git environments)\n if (repoUrl ? repoUrl !== lastReportedRepoUrl : !!testUrl) {\n debugLog('Uploading test info to server', {\n serverUrl,\n repoUrl,\n testUrl,\n userEmail,\n });\n\n fetch(serverUrl, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n repo_url: repoUrl,\n test_url: testUrl,\n user_email: userEmail,\n }),\n })\n .then((response) => response.json())\n .then((data) => {\n debugLog('Successfully uploaded test info to server:', data);\n })\n .catch((error) =>\n debugLog('Failed to upload test info to server:', error),\n );\n lastReportedRepoUrl = repoUrl;\n }\n}\n"],"names":["logEnvReady","groupedActionDumpFileExt","processCacheConfig","cache","cacheId","undefined","envEnabled","globalConfigManager","MIDSCENE_CACHE","reportInitializedMap","Map","reportGroupIdMap","getReportTpl","reportTpl","insertContentBeforeClosingHtml","html","content","htmlEndIdx","insertScriptBeforeClosingHtml","filePath","scriptContent","htmlEndTag","stat","fs","readSize","Math","start","buffer","Buffer","fd","tailStr","Error","beforeHtmlInTail","htmlEndPos","reportHTMLContent","dumpData","reportPath","appendReport","withTpl","tpl","console","writeToFile","ifInBrowser","dumpContent","resolveAutoGroupId","uuid","existingGroupId","newGroupId","groupId","encodeURIComponent","escapeScriptTag","dumpString","attributes","attributesArr","Object","entry","key","value","writeFileSync","writeDumpReport","fileName","ifInWorker","path","getMidsceneRunSubDir","process","jsonPath","data","JSON","logMsg","writeLogFile","opts","fileExt","fileContent","type","targetDir","assert","gitIgnorePath","gitPath","gitIgnoreContent","existsSync","readFileSync","defaultRunDirName","getTmpDir","runningPkgInfo","getRunningPkgInfo","name","tmpPath","tmpdir","mkdirSync","e","getTmpFile","fileExtWithoutDot","tmpDir","filename","overlapped","container","target","sleep","ms","Promise","resolve","setTimeout","replacerForPageObject","_key","stringifyDumpData","indents","getVersion","__VERSION__","debugLog","message","debugMode","MIDSCENE_DEBUG_MODE","gitInfoPromise","getGitInfoAsync","execFileAsync","promisify","execFile","stdout","repoUrl","userEmail","lastReportedRepoUrl","uploadTestInfoToServer","testUrl","serverUrl","fetch","response","error"],"mappings":"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"}
@@ -1,9 +1,9 @@
1
1
  import { existsSync, mkdirSync, writeFileSync } from "node:fs";
2
2
  import { basename, dirname, join, resolve as external_node_path_resolve } from "node:path";
3
- import { assert, ifInBrowser, ifInWorker } from "@midscene/shared/utils";
3
+ import { assert, ifInBrowser, ifInWorker } from "@aiscene/shared/utils";
4
4
  import { z } from "zod";
5
- import { getMidsceneRunSubDir } from "@midscene/shared/common";
6
- import { getDebug } from "@midscene/shared/logger";
5
+ import { getMidsceneRunSubDir } from "@aiscene/shared/common";
6
+ import { getDebug } from "@aiscene/shared/logger";
7
7
  import { buildDetailedLocateParam, buildDetailedLocateParamAndRestParams } from "./utils.mjs";
8
8
  function _define_property(obj, key, value) {
9
9
  if (key in obj) Object.defineProperty(obj, key, {