@midscene/core 1.15.2-beta-20261012100626.0 → 1.15.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 (180) hide show
  1. package/dist/es/agent/action-readiness.mjs +6 -3
  2. package/dist/es/agent/action-readiness.mjs.map +1 -1
  3. package/dist/es/agent/agent.mjs +4 -2
  4. package/dist/es/agent/agent.mjs.map +1 -1
  5. package/dist/es/agent/execution-session.mjs.map +1 -1
  6. package/dist/es/agent/task-builder.mjs +35 -16
  7. package/dist/es/agent/task-builder.mjs.map +1 -1
  8. package/dist/es/agent/tasks.mjs +35 -12
  9. package/dist/es/agent/tasks.mjs.map +1 -1
  10. package/dist/es/agent/utils.mjs +1 -1
  11. package/dist/es/device/index.mjs +7 -6
  12. package/dist/es/device/index.mjs.map +1 -1
  13. package/dist/es/report-generator.mjs +30 -14
  14. package/dist/es/report-generator.mjs.map +1 -1
  15. package/dist/es/report-html-template.mjs +1 -1
  16. package/dist/es/report.mjs +121 -44
  17. package/dist/es/report.mjs.map +1 -1
  18. package/dist/es/service/index.mjs.map +1 -1
  19. package/dist/es/task-runner.mjs +22 -12
  20. package/dist/es/task-runner.mjs.map +1 -1
  21. package/dist/es/test-run-report.mjs.map +1 -1
  22. package/dist/es/test-runner/engine/case-runner.mjs +1 -1
  23. package/dist/es/test-runner/engine/case-runner.mjs.map +1 -1
  24. package/dist/es/test-runner/engine/document-runtime.mjs +42 -12
  25. package/dist/es/test-runner/engine/document-runtime.mjs.map +1 -1
  26. package/dist/es/test-runner/engine/execute-step.mjs +130 -12
  27. package/dist/es/test-runner/engine/execute-step.mjs.map +1 -1
  28. package/dist/es/test-runner/engine/index.mjs +1 -0
  29. package/dist/es/test-runner/engine/project-runtime.mjs +14 -5
  30. package/dist/es/test-runner/engine/project-runtime.mjs.map +1 -1
  31. package/dist/es/test-runner/engine/resource-operations.mjs +286 -35
  32. package/dist/es/test-runner/engine/resource-operations.mjs.map +1 -1
  33. package/dist/es/test-runner/engine/result-metadata.mjs +21 -0
  34. package/dist/es/test-runner/engine/result-metadata.mjs.map +1 -0
  35. package/dist/es/test-runner/engine/run-collected-case.mjs +30 -9
  36. package/dist/es/test-runner/engine/run-collected-case.mjs.map +1 -1
  37. package/dist/es/test-runner/engine/run-workflow-document.mjs +57 -10
  38. package/dist/es/test-runner/engine/run-workflow-document.mjs.map +1 -1
  39. package/dist/es/test-runner/errors.mjs +26 -13
  40. package/dist/es/test-runner/errors.mjs.map +1 -1
  41. package/dist/es/test-runner/execution-record.mjs +2 -0
  42. package/dist/es/test-runner/execution-record.mjs.map +1 -1
  43. package/dist/es/test-runner/index.mjs +3 -0
  44. package/dist/es/test-runner/reporting/agent-report.mjs +2 -7
  45. package/dist/es/test-runner/reporting/agent-report.mjs.map +1 -1
  46. package/dist/es/test-runner/reporting/merge-report.mjs +207 -6
  47. package/dist/es/test-runner/reporting/merge-report.mjs.map +1 -1
  48. package/dist/es/test-runner/reporting/metrics.mjs +77 -0
  49. package/dist/es/test-runner/reporting/metrics.mjs.map +1 -0
  50. package/dist/es/test-runner/reporting/sanitize.mjs +92 -16
  51. package/dist/es/test-runner/reporting/sanitize.mjs.map +1 -1
  52. package/dist/es/test-runner/reporting/test-plan.mjs +56 -0
  53. package/dist/es/test-runner/reporting/test-plan.mjs.map +1 -0
  54. package/dist/es/test-runner/reporting/test-run-report.mjs +208 -21
  55. package/dist/es/test-runner/reporting/test-run-report.mjs.map +1 -1
  56. package/dist/es/types.mjs.map +1 -1
  57. package/dist/es/utils.mjs +1 -1
  58. package/dist/es/yaml/legacy-yaml-runtime.mjs +3 -3
  59. package/dist/es/yaml/legacy-yaml-runtime.mjs.map +1 -1
  60. package/dist/lib/agent/action-readiness.js +6 -3
  61. package/dist/lib/agent/action-readiness.js.map +1 -1
  62. package/dist/lib/agent/agent.js +4 -2
  63. package/dist/lib/agent/agent.js.map +1 -1
  64. package/dist/lib/agent/execution-session.js.map +1 -1
  65. package/dist/lib/agent/task-builder.js +42 -23
  66. package/dist/lib/agent/task-builder.js.map +1 -1
  67. package/dist/lib/agent/tasks.js +35 -12
  68. package/dist/lib/agent/tasks.js.map +1 -1
  69. package/dist/lib/agent/utils.js +1 -1
  70. package/dist/lib/device/index.js +7 -6
  71. package/dist/lib/device/index.js.map +1 -1
  72. package/dist/lib/report-generator.js +30 -14
  73. package/dist/lib/report-generator.js.map +1 -1
  74. package/dist/lib/report-html-template.js +1 -1
  75. package/dist/lib/report.js +121 -44
  76. package/dist/lib/report.js.map +1 -1
  77. package/dist/lib/service/index.js.map +1 -1
  78. package/dist/lib/task-runner.js +22 -12
  79. package/dist/lib/task-runner.js.map +1 -1
  80. package/dist/lib/test-run-report.js.map +1 -1
  81. package/dist/lib/test-runner/engine/case-runner.js +1 -1
  82. package/dist/lib/test-runner/engine/case-runner.js.map +1 -1
  83. package/dist/lib/test-runner/engine/document-runtime.js +41 -11
  84. package/dist/lib/test-runner/engine/document-runtime.js.map +1 -1
  85. package/dist/lib/test-runner/engine/execute-step.js +131 -10
  86. package/dist/lib/test-runner/engine/execute-step.js.map +1 -1
  87. package/dist/lib/test-runner/engine/index.js +9 -2
  88. package/dist/lib/test-runner/engine/index.js.map +1 -1
  89. package/dist/lib/test-runner/engine/project-runtime.js +13 -4
  90. package/dist/lib/test-runner/engine/project-runtime.js.map +1 -1
  91. package/dist/lib/test-runner/engine/resource-operations.js +319 -38
  92. package/dist/lib/test-runner/engine/resource-operations.js.map +1 -1
  93. package/dist/lib/test-runner/engine/result-metadata.js +58 -0
  94. package/dist/lib/test-runner/engine/result-metadata.js.map +1 -0
  95. package/dist/lib/test-runner/engine/run-collected-case.js +29 -8
  96. package/dist/lib/test-runner/engine/run-collected-case.js.map +1 -1
  97. package/dist/lib/test-runner/engine/run-workflow-document.js +57 -10
  98. package/dist/lib/test-runner/engine/run-workflow-document.js.map +1 -1
  99. package/dist/lib/test-runner/errors.js +42 -23
  100. package/dist/lib/test-runner/errors.js.map +1 -1
  101. package/dist/lib/test-runner/execution-record.js +2 -0
  102. package/dist/lib/test-runner/execution-record.js.map +1 -1
  103. package/dist/lib/test-runner/index.js +88 -16
  104. package/dist/lib/test-runner/index.js.map +1 -1
  105. package/dist/lib/test-runner/node/index.js +2 -2
  106. package/dist/lib/test-runner/reporting/agent-report.js +2 -7
  107. package/dist/lib/test-runner/reporting/agent-report.js.map +1 -1
  108. package/dist/lib/test-runner/reporting/merge-report.js +207 -6
  109. package/dist/lib/test-runner/reporting/merge-report.js.map +1 -1
  110. package/dist/lib/test-runner/reporting/metrics.js +114 -0
  111. package/dist/lib/test-runner/reporting/metrics.js.map +1 -0
  112. package/dist/lib/test-runner/reporting/sanitize.js +94 -15
  113. package/dist/lib/test-runner/reporting/sanitize.js.map +1 -1
  114. package/dist/lib/test-runner/reporting/test-plan.js +90 -0
  115. package/dist/lib/test-runner/reporting/test-plan.js.map +1 -0
  116. package/dist/lib/test-runner/reporting/test-run-report.js +208 -21
  117. package/dist/lib/test-runner/reporting/test-run-report.js.map +1 -1
  118. package/dist/lib/types.js.map +1 -1
  119. package/dist/lib/utils.js +1 -1
  120. package/dist/lib/yaml/legacy-yaml-runtime.js +2 -2
  121. package/dist/lib/yaml/legacy-yaml-runtime.js.map +1 -1
  122. package/dist/types/agent/action-readiness.d.ts +1 -1
  123. package/dist/types/agent/action-readiness.d.ts.map +1 -1
  124. package/dist/types/agent/agent.d.ts +1 -1
  125. package/dist/types/agent/agent.d.ts.map +1 -1
  126. package/dist/types/agent/execution-session.d.ts +1 -1
  127. package/dist/types/agent/execution-session.d.ts.map +1 -1
  128. package/dist/types/agent/task-builder.d.ts +4 -2
  129. package/dist/types/agent/task-builder.d.ts.map +1 -1
  130. package/dist/types/agent/tasks.d.ts +3 -0
  131. package/dist/types/agent/tasks.d.ts.map +1 -1
  132. package/dist/types/device/index.d.ts +3 -3
  133. package/dist/types/device/index.d.ts.map +1 -1
  134. package/dist/types/report-generator.d.ts +4 -2
  135. package/dist/types/report-generator.d.ts.map +1 -1
  136. package/dist/types/report.d.ts.map +1 -1
  137. package/dist/types/service/index.d.ts +2 -2
  138. package/dist/types/service/index.d.ts.map +1 -1
  139. package/dist/types/task-runner.d.ts +1 -1
  140. package/dist/types/task-runner.d.ts.map +1 -1
  141. package/dist/types/test-run-report.d.ts +157 -8
  142. package/dist/types/test-run-report.d.ts.map +1 -1
  143. package/dist/types/test-runner/engine/document-runtime.d.ts.map +1 -1
  144. package/dist/types/test-runner/engine/execute-step.d.ts +1 -0
  145. package/dist/types/test-runner/engine/execute-step.d.ts.map +1 -1
  146. package/dist/types/test-runner/engine/index.d.ts +1 -0
  147. package/dist/types/test-runner/engine/index.d.ts.map +1 -1
  148. package/dist/types/test-runner/engine/project-runtime.d.ts.map +1 -1
  149. package/dist/types/test-runner/engine/resource-operations.d.ts +41 -3
  150. package/dist/types/test-runner/engine/resource-operations.d.ts.map +1 -1
  151. package/dist/types/test-runner/engine/result-metadata.d.ts +18 -0
  152. package/dist/types/test-runner/engine/result-metadata.d.ts.map +1 -0
  153. package/dist/types/test-runner/engine/run-collected-case.d.ts.map +1 -1
  154. package/dist/types/test-runner/engine/run-workflow-document.d.ts.map +1 -1
  155. package/dist/types/test-runner/engine/types.d.ts +47 -8
  156. package/dist/types/test-runner/engine/types.d.ts.map +1 -1
  157. package/dist/types/test-runner/errors.d.ts +7 -0
  158. package/dist/types/test-runner/errors.d.ts.map +1 -1
  159. package/dist/types/test-runner/execution-record.d.ts.map +1 -1
  160. package/dist/types/test-runner/index.d.ts +2 -0
  161. package/dist/types/test-runner/index.d.ts.map +1 -1
  162. package/dist/types/test-runner/node/types.d.ts +9 -1
  163. package/dist/types/test-runner/node/types.d.ts.map +1 -1
  164. package/dist/types/test-runner/parser/types.d.ts +5 -0
  165. package/dist/types/test-runner/parser/types.d.ts.map +1 -1
  166. package/dist/types/test-runner/reporting/agent-report.d.ts.map +1 -1
  167. package/dist/types/test-runner/reporting/merge-report.d.ts +1 -1
  168. package/dist/types/test-runner/reporting/merge-report.d.ts.map +1 -1
  169. package/dist/types/test-runner/reporting/metrics.d.ts +7 -0
  170. package/dist/types/test-runner/reporting/metrics.d.ts.map +1 -0
  171. package/dist/types/test-runner/reporting/sanitize.d.ts +1 -0
  172. package/dist/types/test-runner/reporting/sanitize.d.ts.map +1 -1
  173. package/dist/types/test-runner/reporting/test-plan.d.ts +8 -0
  174. package/dist/types/test-runner/reporting/test-plan.d.ts.map +1 -0
  175. package/dist/types/test-runner/reporting/test-run-report.d.ts.map +1 -1
  176. package/dist/types/test-runner/reporting/types.d.ts +6 -1
  177. package/dist/types/test-runner/reporting/types.d.ts.map +1 -1
  178. package/dist/types/types.d.ts +3 -1
  179. package/dist/types/types.d.ts.map +1 -1
  180. package/package.json +2 -2
@@ -1 +1 @@
1
- {"version":3,"file":"service/index.mjs","sources":["../../../src/service/index.ts"],"sourcesContent":["import type { ModelRuntime } from '@/ai-model/models';\nimport { elementDescriberInstruction } from '@/ai-model/prompt/describe';\nimport {\n AIResponseParseError,\n callAIWithObjectResponse,\n} from '@/ai-model/service-caller';\nimport type { ModelCallMessages } from '@/ai-model/service-caller/types';\nimport type { MessageContent } from '@/ai-model/service-caller/types';\nimport { defaultModelFamilyRequiredForLocateMessage } from '@/ai-model/shared/model-locate-result/errors';\nimport {\n AiLocateElement,\n AiLocateSection,\n buildSearchAreaConfig,\n} from '@/ai-model/workflows/grounding';\nimport { mergeSearchAreaResults } from '@/ai-model/workflows/grounding/search-area';\nimport type { SearchAreaConfig } from '@/ai-model/workflows/grounding/types';\nimport { AiExtractElementInfo } from '@/ai-model/workflows/insight';\nimport type { RawAssistantOutput } from '@/types';\nimport type {\n AIDescribeElementResponse,\n AIUsageInfo,\n DetailedLocateParam,\n LocateResultElement,\n LocateResultWithDump,\n PartialServiceDumpFromSDK,\n Rect,\n ServiceExtractOption,\n ServiceExtractParam,\n ServiceExtractResult,\n ServiceTaskInfo,\n UIContext,\n} from '@/types';\nimport { ServiceError } from '@/types';\nimport {\n compositeElementInfoImg,\n compositePointMarkerImg,\n cropByRect,\n resizeBase64ImageToJpeg,\n} from '@midscene/shared/img';\nimport { getDebug } from '@midscene/shared/logger';\nimport { assert } from '@midscene/shared/utils';\nimport type { TMultimodalPrompt, TUserPrompt } from '../common';\nimport {\n createServiceDump,\n getDescribeDeepContextAreas,\n getDescribeDeepLocateResizeSize,\n getDescribeMarkerBorderThickness,\n getDescribeMarkerRect,\n getRectInCrop,\n recoverDescribeResponseFromParseError,\n} from './utils';\n\nexport interface LocateOpts {\n context?: UIContext;\n planLocatedElement?: LocateResultElement;\n}\n\nexport type AnyValue<T> = {\n [K in keyof T]: unknown extends T[K] ? any : T[K];\n};\n\ninterface ServiceOptions {\n taskInfo?: Omit<ServiceTaskInfo, 'durationMs'>;\n}\n\ninterface LocateSearchAreaResult {\n config?: SearchAreaConfig;\n error?: string;\n trace: {\n sourceRect?: Rect;\n rawResponse?: string;\n rawAssistantOutput?: RawAssistantOutput;\n usage?: AIUsageInfo;\n };\n}\n\nconst debug = getDebug('ai:service');\n\nexport default class Service {\n contextRetrieverFn: () => Promise<UIContext> | UIContext;\n\n taskInfo?: Omit<ServiceTaskInfo, 'durationMs'>;\n\n constructor(\n context: UIContext | (() => Promise<UIContext> | UIContext),\n opt?: ServiceOptions,\n ) {\n assert(context, 'context is required for Service');\n if (typeof context === 'function') {\n this.contextRetrieverFn = context;\n } else {\n this.contextRetrieverFn = () => Promise.resolve(context);\n }\n\n if (typeof opt?.taskInfo !== 'undefined') {\n this.taskInfo = opt.taskInfo;\n }\n }\n\n async locate(\n query: DetailedLocateParam,\n opt: LocateOpts,\n modelRuntime: ModelRuntime,\n abortSignal?: AbortSignal,\n ): Promise<LocateResultWithDump> {\n const { config: modelConfig } = modelRuntime;\n const queryPrompt = query.prompt;\n assert(queryPrompt, 'query is required for locate');\n\n if (!modelConfig.modelFamily) {\n throw new Error(defaultModelFamilyRequiredForLocateMessage);\n }\n\n const context = opt?.context || (await this.contextRetrieverFn());\n\n const searchAreaStartTime = Date.now();\n const searchArea = await this.resolveLocateSearchArea({\n query,\n queryPrompt,\n opt,\n context,\n modelRuntime,\n abortSignal,\n });\n\n if (!searchArea.config && searchArea.error) {\n const errorMessage = `cannot find search area for \"${queryPrompt}\": ${searchArea.error}`;\n const taskInfo: ServiceTaskInfo = {\n ...(this.taskInfo ? this.taskInfo : {}),\n durationMs: Date.now() - searchAreaStartTime,\n searchAreaRawResponse: searchArea.trace.rawResponse,\n searchAreaRawChoiceMessage: searchArea.trace.rawAssistantOutput,\n searchAreaUsage: searchArea.trace.usage,\n };\n const dump = createServiceDump({\n type: 'locate',\n userQuery: { element: queryPrompt },\n matchedElement: [],\n data: null,\n taskInfo,\n deepLocate: true,\n error: errorMessage,\n });\n throw new ServiceError(errorMessage, dump);\n }\n\n const startTime = Date.now();\n const {\n parseResult,\n rawResponse,\n rawAssistantOutput,\n usage,\n reasoning_content,\n } = await AiLocateElement({\n context,\n targetElementDescription: queryPrompt,\n searchConfig: searchArea.config,\n modelRuntime,\n abortSignal,\n });\n\n const timeCost = Date.now() - startTime;\n const taskInfo: ServiceTaskInfo = {\n ...(this.taskInfo ? this.taskInfo : {}),\n durationMs: timeCost,\n rawResponse,\n rawAssistantOutput,\n formatResponse: parseResult,\n usage,\n searchArea: searchArea.trace.sourceRect,\n searchAreaRawResponse: searchArea.trace.rawResponse,\n searchAreaRawChoiceMessage: searchArea.trace.rawAssistantOutput,\n searchAreaUsage: searchArea.trace.usage,\n reasoning_content,\n };\n\n let errorLog: string | undefined;\n if (parseResult.errors?.length) {\n errorLog = `failed to locate element: \\n${parseResult.errors.join('\\n')}`;\n }\n\n const dumpData: PartialServiceDumpFromSDK = {\n type: 'locate',\n userQuery: {\n element: queryPrompt,\n },\n data: null,\n taskInfo,\n deepLocate: !!searchArea.trace.sourceRect,\n error: errorLog,\n };\n\n const element = parseResult.element;\n\n const dump = createServiceDump({\n ...dumpData,\n matchedElement: element ? [element] : [],\n });\n\n if (errorLog) {\n throw new ServiceError(errorLog, dump);\n }\n\n if (element) {\n return {\n element: {\n center: element.center,\n description: element.description,\n ...(element.rect ? { rect: element.rect } : {}),\n },\n dump,\n };\n }\n\n return {\n element: null,\n dump,\n };\n }\n\n private async resolveLocateSearchArea(options: {\n query: DetailedLocateParam;\n queryPrompt: TUserPrompt;\n opt: LocateOpts;\n context: UIContext;\n modelRuntime: ModelRuntime;\n abortSignal?: AbortSignal;\n }): Promise<LocateSearchAreaResult> {\n const { query, queryPrompt, opt, context, modelRuntime, abortSignal } =\n options;\n const { adapter } = modelRuntime;\n const hasPlanLocatedElement = !!opt?.planLocatedElement?.center;\n\n if (!query.deepLocate) {\n return { trace: {} };\n }\n\n // TODO: Make the plan, section-locate, and element-locate fallback paths\n // resolve only the source rect, then build the search-area config once in\n // this orchestration layer instead of building it inside AiLocateSection.\n if (hasPlanLocatedElement) {\n const config = await buildSearchAreaConfig({\n context,\n baseRect: mergeSearchAreaResults(opt.planLocatedElement!),\n });\n\n return {\n config,\n trace: {\n sourceRect: config.sourceRect,\n rawResponse: JSON.stringify({\n source: 'plan-located-element',\n center: opt.planLocatedElement!.center,\n rect: opt.planLocatedElement!.rect,\n }),\n },\n };\n }\n\n if (adapter.locate.kind === 'standard' && adapter.locate.searchArea) {\n const searchAreaResponse = await AiLocateSection({\n context,\n sectionDescription: queryPrompt,\n modelRuntime,\n abortSignal,\n });\n const { searchAreaConfig } = searchAreaResponse;\n if (!searchAreaConfig) {\n return {\n error: searchAreaResponse.error || 'unknown search area error',\n trace: {\n rawResponse: searchAreaResponse.rawResponse,\n rawAssistantOutput: searchAreaResponse.rawAssistantOutput,\n usage: searchAreaResponse.usage,\n },\n };\n }\n\n return {\n config: searchAreaConfig,\n trace: {\n sourceRect: searchAreaConfig.sourceRect,\n rawResponse: searchAreaResponse.rawResponse,\n rawAssistantOutput: searchAreaResponse.rawAssistantOutput,\n usage: searchAreaResponse.usage,\n },\n };\n }\n\n const firstPassLocateResult = await AiLocateElement({\n context,\n targetElementDescription: queryPrompt,\n modelRuntime,\n abortSignal,\n });\n assert(\n firstPassLocateResult.parseResult.element?.center,\n `cannot find search area for \"${queryPrompt}\"${\n firstPassLocateResult.parseResult.errors?.length\n ? `: ${firstPassLocateResult.parseResult.errors.join('\\n')}`\n : ''\n }`,\n );\n\n const config = await buildSearchAreaConfig({\n context,\n baseRect: mergeSearchAreaResults(\n firstPassLocateResult.parseResult.element!,\n ),\n });\n\n return {\n config,\n trace: {\n sourceRect: config.sourceRect,\n rawResponse: JSON.stringify({\n source: 'deep-locate-first-pass',\n center: firstPassLocateResult.parseResult.element?.center,\n rect: firstPassLocateResult.parseResult.element?.rect,\n rawResponse: firstPassLocateResult.rawResponse,\n }),\n rawAssistantOutput: firstPassLocateResult.rawAssistantOutput,\n usage: firstPassLocateResult.usage,\n },\n };\n }\n\n async extract<T>(\n dataDemand: ServiceExtractParam,\n modelRuntime: ModelRuntime,\n opt?: ServiceExtractOption,\n pageDescription?: string,\n multimodalPrompt?: TMultimodalPrompt,\n context?: UIContext,\n executionOptions?: {\n abortSignal?: AbortSignal;\n },\n ): Promise<ServiceExtractResult<T>> {\n assert(context, 'context is required for extract');\n assert(\n typeof dataDemand === 'object' || typeof dataDemand === 'string',\n `dataDemand should be object or string, but get ${typeof dataDemand}`,\n );\n\n const startTime = Date.now();\n\n let parseResult: Awaited<\n ReturnType<typeof AiExtractElementInfo<T>>\n >['parseResult'];\n let rawResponse: string;\n let rawAssistantOutput: RawAssistantOutput | undefined;\n let usage: Awaited<ReturnType<typeof AiExtractElementInfo<T>>>['usage'];\n let reasoning_content: string | undefined;\n\n try {\n const result = await AiExtractElementInfo<T>({\n context,\n dataQuery: dataDemand,\n multimodalPrompt,\n extractOption: opt,\n modelRuntime,\n pageDescription,\n abortSignal: executionOptions?.abortSignal,\n });\n parseResult = result.parseResult;\n rawResponse = result.rawResponse;\n rawAssistantOutput = result.rawAssistantOutput;\n usage = result.usage;\n reasoning_content = result.reasoning_content;\n } catch (error) {\n if (error instanceof AIResponseParseError) {\n // Create dump with usage and rawResponse from the error\n const timeCost = Date.now() - startTime;\n const taskInfo: ServiceTaskInfo = {\n ...(this.taskInfo ? this.taskInfo : {}),\n durationMs: timeCost,\n rawResponse: error.rawResponse,\n rawAssistantOutput: error.rawAssistantOutput,\n usage: error.usage,\n };\n const dump = createServiceDump({\n type: 'extract',\n userQuery: { dataDemand },\n data: null,\n taskInfo,\n error: error.message,\n });\n throw new ServiceError(error.message, dump);\n }\n throw error;\n }\n\n const timeCost = Date.now() - startTime;\n const taskInfo: ServiceTaskInfo = {\n ...(this.taskInfo ? this.taskInfo : {}),\n durationMs: timeCost,\n rawResponse,\n rawAssistantOutput,\n formatResponse: parseResult,\n usage,\n reasoning_content,\n };\n\n let errorLog: string | undefined;\n if (parseResult.errors?.length) {\n errorLog = `AI response error: \\n${parseResult.errors.join('\\n')}`;\n }\n\n const dumpData: PartialServiceDumpFromSDK = {\n type: 'extract',\n userQuery: {\n dataDemand,\n },\n data: null,\n taskInfo,\n error: errorLog,\n };\n\n const { data, thought } = parseResult || {};\n\n const dump = createServiceDump({\n ...dumpData,\n data,\n });\n\n if (errorLog && !data) {\n throw new ServiceError(errorLog, dump);\n }\n\n return {\n data,\n thought,\n usage,\n reasoning_content,\n dump,\n };\n }\n\n async describe(\n target: Rect | [number, number],\n modelRuntime: ModelRuntime,\n opt?: {\n deepDescribe?: boolean;\n context?: UIContext;\n },\n ): Promise<Pick<AIDescribeElementResponse, 'description'>> {\n assert(target, 'target is required for service.describe');\n const context = opt?.context || (await this.contextRetrieverFn());\n const { shotSize } = context;\n const screenshotBase64 = context.screenshot.base64;\n assert(screenshotBase64, 'screenshot is required for service.describe');\n const systemPrompt = elementDescriberInstruction();\n\n // Convert [x,y] center point to Rect if needed\n const defaultRectSize = 30;\n const targetFromPoint = Array.isArray(target);\n const targetRect: Rect = targetFromPoint\n ? {\n left: Math.floor(target[0] - defaultRectSize / 2),\n top: Math.floor(target[1] - defaultRectSize / 2),\n width: defaultRectSize,\n height: defaultRectSize,\n }\n : target;\n const targetPoint = targetFromPoint\n ? {\n x: target[0],\n y: target[1],\n }\n : undefined;\n\n const usePointMarker = targetFromPoint;\n const imagePayload = usePointMarker\n ? await compositePointMarkerImg({\n inputImgBase64: screenshotBase64,\n size: shotSize,\n point: targetPoint!,\n })\n : await compositeElementInfoImg({\n inputImgBase64: screenshotBase64,\n size: shotSize,\n elementsPositionInfo: [\n {\n rect: getDescribeMarkerRect(targetRect),\n },\n ],\n borderThickness: getDescribeMarkerBorderThickness(targetRect),\n centerPoint: true,\n });\n\n const shouldDeepDescribe = opt?.deepDescribe;\n let imageContent: MessageContent[];\n if (shouldDeepDescribe) {\n const contextAreas = getDescribeDeepContextAreas(targetRect, shotSize);\n const contextImages = await Promise.all(\n contextAreas.map(async (area) => {\n debug('describe: cropping deep context area', area);\n const croppedResult = await cropByRect(screenshotBase64, area.rect);\n const cropSize = {\n width: croppedResult.width,\n height: croppedResult.height,\n };\n const targetInCrop = getRectInCrop(targetRect, area.rect, cropSize);\n const markedCropPayload = targetFromPoint\n ? await compositePointMarkerImg({\n inputImgBase64: croppedResult.imageBase64,\n size: cropSize,\n point: {\n x: targetPoint!.x - area.rect.left,\n y: targetPoint!.y - area.rect.top,\n },\n })\n : await compositeElementInfoImg({\n inputImgBase64: croppedResult.imageBase64,\n size: cropSize,\n elementsPositionInfo: [\n {\n rect: getDescribeMarkerRect(targetInCrop),\n },\n ],\n borderThickness: getDescribeMarkerBorderThickness(targetInCrop),\n centerPoint: true,\n });\n const resizeSize = getDescribeDeepLocateResizeSize(croppedResult);\n return {\n kind: area.kind,\n imageBase64: resizeSize\n ? await resizeBase64ImageToJpeg(markedCropPayload, {\n sourceSize: cropSize,\n targetSize: resizeSize,\n })\n : markedCropPayload,\n };\n }),\n );\n const contextImageContent = contextImages.flatMap<MessageContent>(\n (item, index) => [\n {\n type: 'text',\n text: `Image ${index + 2}: focused detail crop around the target, for reading text, icon shape, and exact local boundaries.`,\n },\n {\n type: 'image',\n url: item.imageBase64,\n },\n ],\n );\n\n imageContent = [\n {\n type: 'text' as const,\n text: 'Use these images together to describe the real UI target marked by the temporary callout. Do not describe the marker itself.',\n },\n {\n type: 'text' as const,\n text: 'Image 1: full screenshot overview with the target marker, for page position and ownership context.',\n },\n {\n type: 'image' as const,\n url: imagePayload,\n },\n ...contextImageContent,\n ];\n } else {\n imageContent = [\n {\n type: 'text' as const,\n text: 'Full screenshot with a temporary callout marking the target:',\n },\n {\n type: 'image' as const,\n url: imagePayload,\n },\n ];\n }\n\n const msgs: ModelCallMessages = [\n { role: 'system', content: systemPrompt },\n {\n role: 'user',\n content: imageContent,\n },\n ];\n\n let res: Awaited<\n ReturnType<typeof callAIWithObjectResponse<AIDescribeElementResponse>>\n >;\n try {\n res = await callAIWithObjectResponse<AIDescribeElementResponse>(\n msgs,\n modelRuntime,\n );\n } catch (error) {\n const recoveredResponse = recoverDescribeResponseFromParseError(error);\n if (!recoveredResponse) {\n throw error;\n }\n debug('describe: recovered malformed description JSON response');\n return recoveredResponse;\n }\n\n const { content } = res;\n assert(!content.error, `describe failed: ${content.error}`);\n assert(content.description, 'failed to describe the element');\n return content;\n }\n}\n"],"names":["debug","getDebug","Service","query","opt","modelRuntime","abortSignal","modelConfig","queryPrompt","assert","Error","defaultModelFamilyRequiredForLocateMessage","context","searchAreaStartTime","Date","searchArea","errorMessage","taskInfo","dump","createServiceDump","ServiceError","startTime","parseResult","rawResponse","rawAssistantOutput","usage","reasoning_content","AiLocateElement","timeCost","errorLog","dumpData","element","options","adapter","hasPlanLocatedElement","config","buildSearchAreaConfig","mergeSearchAreaResults","JSON","searchAreaResponse","AiLocateSection","searchAreaConfig","firstPassLocateResult","dataDemand","pageDescription","multimodalPrompt","executionOptions","result","AiExtractElementInfo","error","AIResponseParseError","data","thought","target","shotSize","screenshotBase64","systemPrompt","elementDescriberInstruction","defaultRectSize","targetFromPoint","Array","targetRect","Math","targetPoint","undefined","usePointMarker","imagePayload","compositePointMarkerImg","compositeElementInfoImg","getDescribeMarkerRect","getDescribeMarkerBorderThickness","shouldDeepDescribe","imageContent","contextAreas","getDescribeDeepContextAreas","contextImages","Promise","area","croppedResult","cropByRect","cropSize","targetInCrop","getRectInCrop","markedCropPayload","resizeSize","getDescribeDeepLocateResizeSize","resizeBase64ImageToJpeg","contextImageContent","item","index","msgs","res","callAIWithObjectResponse","recoveredResponse","recoverDescribeResponseFromParseError","content"],"mappings":"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"}
1
+ {"version":3,"file":"service/index.mjs","sources":["../../../src/service/index.ts"],"sourcesContent":["import type { ModelRuntime } from '@/ai-model/models';\nimport { elementDescriberInstruction } from '@/ai-model/prompt/describe';\nimport {\n AIResponseParseError,\n callAIWithObjectResponse,\n} from '@/ai-model/service-caller';\nimport type { ModelCallMessages } from '@/ai-model/service-caller/types';\nimport type { MessageContent } from '@/ai-model/service-caller/types';\nimport { defaultModelFamilyRequiredForLocateMessage } from '@/ai-model/shared/model-locate-result/errors';\nimport {\n AiLocateElement,\n AiLocateSection,\n buildSearchAreaConfig,\n} from '@/ai-model/workflows/grounding';\nimport { mergeSearchAreaResults } from '@/ai-model/workflows/grounding/search-area';\nimport type { SearchAreaConfig } from '@/ai-model/workflows/grounding/types';\nimport { AiExtractElementInfo } from '@/ai-model/workflows/insight';\nimport type { RawAssistantOutput } from '@/types';\nimport type {\n AIDescribeElementResponse,\n AIUsageInfo,\n DetailedLocateParam,\n LocateResultElement,\n LocateResultWithDump,\n PartialServiceDumpFromSDK,\n Rect,\n ServiceExtractOption,\n ServiceExtractParam,\n ServiceExtractResult,\n ServiceTaskInfo,\n UIContext,\n} from '@/types';\nimport { ServiceError } from '@/types';\nimport {\n compositeElementInfoImg,\n compositePointMarkerImg,\n cropByRect,\n resizeBase64ImageToJpeg,\n} from '@midscene/shared/img';\nimport { getDebug } from '@midscene/shared/logger';\nimport { assert } from '@midscene/shared/utils';\nimport type { TMultimodalPrompt, TUserPrompt } from '../common';\nimport {\n createServiceDump,\n getDescribeDeepContextAreas,\n getDescribeDeepLocateResizeSize,\n getDescribeMarkerBorderThickness,\n getDescribeMarkerRect,\n getRectInCrop,\n recoverDescribeResponseFromParseError,\n} from './utils';\n\nexport interface LocateOpts {\n context?: UIContext;\n planLocatedElement?: LocateResultElement;\n}\n\nexport type AnyValue<T> = {\n [K in keyof T]: unknown extends T[K] ? any : T[K];\n};\n\ninterface ServiceOptions {\n taskInfo?: Omit<ServiceTaskInfo, 'durationMs'>;\n}\n\ninterface LocateSearchAreaResult {\n config?: SearchAreaConfig;\n error?: string;\n trace: {\n sourceRect?: Rect;\n rawResponse?: string;\n rawAssistantOutput?: RawAssistantOutput;\n usage?: AIUsageInfo;\n };\n}\n\nconst debug = getDebug('ai:service');\n\nexport default class Service {\n contextRetrieverFn: (signal?: AbortSignal) => Promise<UIContext> | UIContext;\n\n taskInfo?: Omit<ServiceTaskInfo, 'durationMs'>;\n\n constructor(\n context:\n | UIContext\n | ((signal?: AbortSignal) => Promise<UIContext> | UIContext),\n opt?: ServiceOptions,\n ) {\n assert(context, 'context is required for Service');\n if (typeof context === 'function') {\n this.contextRetrieverFn = context;\n } else {\n this.contextRetrieverFn = () => Promise.resolve(context);\n }\n\n if (typeof opt?.taskInfo !== 'undefined') {\n this.taskInfo = opt.taskInfo;\n }\n }\n\n async locate(\n query: DetailedLocateParam,\n opt: LocateOpts,\n modelRuntime: ModelRuntime,\n abortSignal?: AbortSignal,\n ): Promise<LocateResultWithDump> {\n const { config: modelConfig } = modelRuntime;\n const queryPrompt = query.prompt;\n assert(queryPrompt, 'query is required for locate');\n\n if (!modelConfig.modelFamily) {\n throw new Error(defaultModelFamilyRequiredForLocateMessage);\n }\n\n const context = opt?.context || (await this.contextRetrieverFn());\n\n const searchAreaStartTime = Date.now();\n const searchArea = await this.resolveLocateSearchArea({\n query,\n queryPrompt,\n opt,\n context,\n modelRuntime,\n abortSignal,\n });\n\n if (!searchArea.config && searchArea.error) {\n const errorMessage = `cannot find search area for \"${queryPrompt}\": ${searchArea.error}`;\n const taskInfo: ServiceTaskInfo = {\n ...(this.taskInfo ? this.taskInfo : {}),\n durationMs: Date.now() - searchAreaStartTime,\n searchAreaRawResponse: searchArea.trace.rawResponse,\n searchAreaRawChoiceMessage: searchArea.trace.rawAssistantOutput,\n searchAreaUsage: searchArea.trace.usage,\n };\n const dump = createServiceDump({\n type: 'locate',\n userQuery: { element: queryPrompt },\n matchedElement: [],\n data: null,\n taskInfo,\n deepLocate: true,\n error: errorMessage,\n });\n throw new ServiceError(errorMessage, dump);\n }\n\n const startTime = Date.now();\n const {\n parseResult,\n rawResponse,\n rawAssistantOutput,\n usage,\n reasoning_content,\n } = await AiLocateElement({\n context,\n targetElementDescription: queryPrompt,\n searchConfig: searchArea.config,\n modelRuntime,\n abortSignal,\n });\n\n const timeCost = Date.now() - startTime;\n const taskInfo: ServiceTaskInfo = {\n ...(this.taskInfo ? this.taskInfo : {}),\n durationMs: timeCost,\n rawResponse,\n rawAssistantOutput,\n formatResponse: parseResult,\n usage,\n searchArea: searchArea.trace.sourceRect,\n searchAreaRawResponse: searchArea.trace.rawResponse,\n searchAreaRawChoiceMessage: searchArea.trace.rawAssistantOutput,\n searchAreaUsage: searchArea.trace.usage,\n reasoning_content,\n };\n\n let errorLog: string | undefined;\n if (parseResult.errors?.length) {\n errorLog = `failed to locate element: \\n${parseResult.errors.join('\\n')}`;\n }\n\n const dumpData: PartialServiceDumpFromSDK = {\n type: 'locate',\n userQuery: {\n element: queryPrompt,\n },\n data: null,\n taskInfo,\n deepLocate: !!searchArea.trace.sourceRect,\n error: errorLog,\n };\n\n const element = parseResult.element;\n\n const dump = createServiceDump({\n ...dumpData,\n matchedElement: element ? [element] : [],\n });\n\n if (errorLog) {\n throw new ServiceError(errorLog, dump);\n }\n\n if (element) {\n return {\n element: {\n center: element.center,\n description: element.description,\n ...(element.rect ? { rect: element.rect } : {}),\n },\n dump,\n };\n }\n\n return {\n element: null,\n dump,\n };\n }\n\n private async resolveLocateSearchArea(options: {\n query: DetailedLocateParam;\n queryPrompt: TUserPrompt;\n opt: LocateOpts;\n context: UIContext;\n modelRuntime: ModelRuntime;\n abortSignal?: AbortSignal;\n }): Promise<LocateSearchAreaResult> {\n const { query, queryPrompt, opt, context, modelRuntime, abortSignal } =\n options;\n const { adapter } = modelRuntime;\n const hasPlanLocatedElement = !!opt?.planLocatedElement?.center;\n\n if (!query.deepLocate) {\n return { trace: {} };\n }\n\n // TODO: Make the plan, section-locate, and element-locate fallback paths\n // resolve only the source rect, then build the search-area config once in\n // this orchestration layer instead of building it inside AiLocateSection.\n if (hasPlanLocatedElement) {\n const config = await buildSearchAreaConfig({\n context,\n baseRect: mergeSearchAreaResults(opt.planLocatedElement!),\n });\n\n return {\n config,\n trace: {\n sourceRect: config.sourceRect,\n rawResponse: JSON.stringify({\n source: 'plan-located-element',\n center: opt.planLocatedElement!.center,\n rect: opt.planLocatedElement!.rect,\n }),\n },\n };\n }\n\n if (adapter.locate.kind === 'standard' && adapter.locate.searchArea) {\n const searchAreaResponse = await AiLocateSection({\n context,\n sectionDescription: queryPrompt,\n modelRuntime,\n abortSignal,\n });\n const { searchAreaConfig } = searchAreaResponse;\n if (!searchAreaConfig) {\n return {\n error: searchAreaResponse.error || 'unknown search area error',\n trace: {\n rawResponse: searchAreaResponse.rawResponse,\n rawAssistantOutput: searchAreaResponse.rawAssistantOutput,\n usage: searchAreaResponse.usage,\n },\n };\n }\n\n return {\n config: searchAreaConfig,\n trace: {\n sourceRect: searchAreaConfig.sourceRect,\n rawResponse: searchAreaResponse.rawResponse,\n rawAssistantOutput: searchAreaResponse.rawAssistantOutput,\n usage: searchAreaResponse.usage,\n },\n };\n }\n\n const firstPassLocateResult = await AiLocateElement({\n context,\n targetElementDescription: queryPrompt,\n modelRuntime,\n abortSignal,\n });\n assert(\n firstPassLocateResult.parseResult.element?.center,\n `cannot find search area for \"${queryPrompt}\"${\n firstPassLocateResult.parseResult.errors?.length\n ? `: ${firstPassLocateResult.parseResult.errors.join('\\n')}`\n : ''\n }`,\n );\n\n const config = await buildSearchAreaConfig({\n context,\n baseRect: mergeSearchAreaResults(\n firstPassLocateResult.parseResult.element!,\n ),\n });\n\n return {\n config,\n trace: {\n sourceRect: config.sourceRect,\n rawResponse: JSON.stringify({\n source: 'deep-locate-first-pass',\n center: firstPassLocateResult.parseResult.element?.center,\n rect: firstPassLocateResult.parseResult.element?.rect,\n rawResponse: firstPassLocateResult.rawResponse,\n }),\n rawAssistantOutput: firstPassLocateResult.rawAssistantOutput,\n usage: firstPassLocateResult.usage,\n },\n };\n }\n\n async extract<T>(\n dataDemand: ServiceExtractParam,\n modelRuntime: ModelRuntime,\n opt?: ServiceExtractOption,\n pageDescription?: string,\n multimodalPrompt?: TMultimodalPrompt,\n context?: UIContext,\n executionOptions?: {\n abortSignal?: AbortSignal;\n },\n ): Promise<ServiceExtractResult<T>> {\n assert(context, 'context is required for extract');\n assert(\n typeof dataDemand === 'object' || typeof dataDemand === 'string',\n `dataDemand should be object or string, but get ${typeof dataDemand}`,\n );\n\n const startTime = Date.now();\n\n let parseResult: Awaited<\n ReturnType<typeof AiExtractElementInfo<T>>\n >['parseResult'];\n let rawResponse: string;\n let rawAssistantOutput: RawAssistantOutput | undefined;\n let usage: Awaited<ReturnType<typeof AiExtractElementInfo<T>>>['usage'];\n let reasoning_content: string | undefined;\n\n try {\n const result = await AiExtractElementInfo<T>({\n context,\n dataQuery: dataDemand,\n multimodalPrompt,\n extractOption: opt,\n modelRuntime,\n pageDescription,\n abortSignal: executionOptions?.abortSignal,\n });\n parseResult = result.parseResult;\n rawResponse = result.rawResponse;\n rawAssistantOutput = result.rawAssistantOutput;\n usage = result.usage;\n reasoning_content = result.reasoning_content;\n } catch (error) {\n if (error instanceof AIResponseParseError) {\n // Create dump with usage and rawResponse from the error\n const timeCost = Date.now() - startTime;\n const taskInfo: ServiceTaskInfo = {\n ...(this.taskInfo ? this.taskInfo : {}),\n durationMs: timeCost,\n rawResponse: error.rawResponse,\n rawAssistantOutput: error.rawAssistantOutput,\n usage: error.usage,\n };\n const dump = createServiceDump({\n type: 'extract',\n userQuery: { dataDemand },\n data: null,\n taskInfo,\n error: error.message,\n });\n throw new ServiceError(error.message, dump);\n }\n throw error;\n }\n\n const timeCost = Date.now() - startTime;\n const taskInfo: ServiceTaskInfo = {\n ...(this.taskInfo ? this.taskInfo : {}),\n durationMs: timeCost,\n rawResponse,\n rawAssistantOutput,\n formatResponse: parseResult,\n usage,\n reasoning_content,\n };\n\n let errorLog: string | undefined;\n if (parseResult.errors?.length) {\n errorLog = `AI response error: \\n${parseResult.errors.join('\\n')}`;\n }\n\n const dumpData: PartialServiceDumpFromSDK = {\n type: 'extract',\n userQuery: {\n dataDemand,\n },\n data: null,\n taskInfo,\n error: errorLog,\n };\n\n const { data, thought } = parseResult || {};\n\n const dump = createServiceDump({\n ...dumpData,\n data,\n });\n\n if (errorLog && !data) {\n throw new ServiceError(errorLog, dump);\n }\n\n return {\n data,\n thought,\n usage,\n reasoning_content,\n dump,\n };\n }\n\n async describe(\n target: Rect | [number, number],\n modelRuntime: ModelRuntime,\n opt?: {\n deepDescribe?: boolean;\n context?: UIContext;\n },\n ): Promise<Pick<AIDescribeElementResponse, 'description'>> {\n assert(target, 'target is required for service.describe');\n const context = opt?.context || (await this.contextRetrieverFn());\n const { shotSize } = context;\n const screenshotBase64 = context.screenshot.base64;\n assert(screenshotBase64, 'screenshot is required for service.describe');\n const systemPrompt = elementDescriberInstruction();\n\n // Convert [x,y] center point to Rect if needed\n const defaultRectSize = 30;\n const targetFromPoint = Array.isArray(target);\n const targetRect: Rect = targetFromPoint\n ? {\n left: Math.floor(target[0] - defaultRectSize / 2),\n top: Math.floor(target[1] - defaultRectSize / 2),\n width: defaultRectSize,\n height: defaultRectSize,\n }\n : target;\n const targetPoint = targetFromPoint\n ? {\n x: target[0],\n y: target[1],\n }\n : undefined;\n\n const usePointMarker = targetFromPoint;\n const imagePayload = usePointMarker\n ? await compositePointMarkerImg({\n inputImgBase64: screenshotBase64,\n size: shotSize,\n point: targetPoint!,\n })\n : await compositeElementInfoImg({\n inputImgBase64: screenshotBase64,\n size: shotSize,\n elementsPositionInfo: [\n {\n rect: getDescribeMarkerRect(targetRect),\n },\n ],\n borderThickness: getDescribeMarkerBorderThickness(targetRect),\n centerPoint: true,\n });\n\n const shouldDeepDescribe = opt?.deepDescribe;\n let imageContent: MessageContent[];\n if (shouldDeepDescribe) {\n const contextAreas = getDescribeDeepContextAreas(targetRect, shotSize);\n const contextImages = await Promise.all(\n contextAreas.map(async (area) => {\n debug('describe: cropping deep context area', area);\n const croppedResult = await cropByRect(screenshotBase64, area.rect);\n const cropSize = {\n width: croppedResult.width,\n height: croppedResult.height,\n };\n const targetInCrop = getRectInCrop(targetRect, area.rect, cropSize);\n const markedCropPayload = targetFromPoint\n ? await compositePointMarkerImg({\n inputImgBase64: croppedResult.imageBase64,\n size: cropSize,\n point: {\n x: targetPoint!.x - area.rect.left,\n y: targetPoint!.y - area.rect.top,\n },\n })\n : await compositeElementInfoImg({\n inputImgBase64: croppedResult.imageBase64,\n size: cropSize,\n elementsPositionInfo: [\n {\n rect: getDescribeMarkerRect(targetInCrop),\n },\n ],\n borderThickness: getDescribeMarkerBorderThickness(targetInCrop),\n centerPoint: true,\n });\n const resizeSize = getDescribeDeepLocateResizeSize(croppedResult);\n return {\n kind: area.kind,\n imageBase64: resizeSize\n ? await resizeBase64ImageToJpeg(markedCropPayload, {\n sourceSize: cropSize,\n targetSize: resizeSize,\n })\n : markedCropPayload,\n };\n }),\n );\n const contextImageContent = contextImages.flatMap<MessageContent>(\n (item, index) => [\n {\n type: 'text',\n text: `Image ${index + 2}: focused detail crop around the target, for reading text, icon shape, and exact local boundaries.`,\n },\n {\n type: 'image',\n url: item.imageBase64,\n },\n ],\n );\n\n imageContent = [\n {\n type: 'text' as const,\n text: 'Use these images together to describe the real UI target marked by the temporary callout. Do not describe the marker itself.',\n },\n {\n type: 'text' as const,\n text: 'Image 1: full screenshot overview with the target marker, for page position and ownership context.',\n },\n {\n type: 'image' as const,\n url: imagePayload,\n },\n ...contextImageContent,\n ];\n } else {\n imageContent = [\n {\n type: 'text' as const,\n text: 'Full screenshot with a temporary callout marking the target:',\n },\n {\n type: 'image' as const,\n url: imagePayload,\n },\n ];\n }\n\n const msgs: ModelCallMessages = [\n { role: 'system', content: systemPrompt },\n {\n role: 'user',\n content: imageContent,\n },\n ];\n\n let res: Awaited<\n ReturnType<typeof callAIWithObjectResponse<AIDescribeElementResponse>>\n >;\n try {\n res = await callAIWithObjectResponse<AIDescribeElementResponse>(\n msgs,\n modelRuntime,\n );\n } catch (error) {\n const recoveredResponse = recoverDescribeResponseFromParseError(error);\n if (!recoveredResponse) {\n throw error;\n }\n debug('describe: recovered malformed description JSON response');\n return recoveredResponse;\n }\n\n const { content } = res;\n assert(!content.error, `describe failed: ${content.error}`);\n assert(content.description, 'failed to describe the element');\n return content;\n }\n}\n"],"names":["debug","getDebug","Service","query","opt","modelRuntime","abortSignal","modelConfig","queryPrompt","assert","Error","defaultModelFamilyRequiredForLocateMessage","context","searchAreaStartTime","Date","searchArea","errorMessage","taskInfo","dump","createServiceDump","ServiceError","startTime","parseResult","rawResponse","rawAssistantOutput","usage","reasoning_content","AiLocateElement","timeCost","errorLog","dumpData","element","options","adapter","hasPlanLocatedElement","config","buildSearchAreaConfig","mergeSearchAreaResults","JSON","searchAreaResponse","AiLocateSection","searchAreaConfig","firstPassLocateResult","dataDemand","pageDescription","multimodalPrompt","executionOptions","result","AiExtractElementInfo","error","AIResponseParseError","data","thought","target","shotSize","screenshotBase64","systemPrompt","elementDescriberInstruction","defaultRectSize","targetFromPoint","Array","targetRect","Math","targetPoint","undefined","usePointMarker","imagePayload","compositePointMarkerImg","compositeElementInfoImg","getDescribeMarkerRect","getDescribeMarkerBorderThickness","shouldDeepDescribe","imageContent","contextAreas","getDescribeDeepContextAreas","contextImages","Promise","area","croppedResult","cropByRect","cropSize","targetInCrop","getRectInCrop","markedCropPayload","resizeSize","getDescribeDeepLocateResizeSize","resizeBase64ImageToJpeg","contextImageContent","item","index","msgs","res","callAIWithObjectResponse","recoveredResponse","recoverDescribeResponseFromParseError","content"],"mappings":"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"}
@@ -50,6 +50,7 @@ class TaskRunner {
50
50
  }
51
51
  }
52
52
  async getUiContext(options) {
53
+ this.abortSignal?.throwIfAborted();
53
54
  const now = Date.now();
54
55
  const shouldReuse = !options?.forceRefresh && this.lastUiContext && now - this.lastUiContext.capturedAt <= UI_CONTEXT_CACHE_TTL_MS;
55
56
  if (shouldReuse && this.lastUiContext?.context) {
@@ -57,7 +58,8 @@ class TaskRunner {
57
58
  return this.lastUiContext?.context;
58
59
  }
59
60
  try {
60
- const uiContext = await this.uiContextBuilder();
61
+ const uiContext = await this.uiContextBuilder(this.abortSignal);
62
+ this.abortSignal?.throwIfAborted();
61
63
  if (uiContext) this.lastUiContext = {
62
64
  context: uiContext,
63
65
  capturedAt: Date.now()
@@ -108,6 +110,7 @@ class TaskRunner {
108
110
  this.status = this.tasks.length > 0 ? 'pending' : 'init';
109
111
  }
110
112
  async append(task, options) {
113
+ this.abortSignal?.throwIfAborted();
111
114
  this.normalizeStatusFromError(options, `task runner is in error state, cannot append task\nerror=${this.latestErrorTask()?.errorMessage}\n${this.latestErrorTask()?.errorStack}`);
112
115
  const appended = [];
113
116
  if (Array.isArray(task)) appended.push(...task.map((item)=>this.markTaskAsPending(item)));
@@ -136,16 +139,19 @@ class TaskRunner {
136
139
  while(taskIndex < this.tasks.length){
137
140
  const task = this.tasks[taskIndex];
138
141
  assert('pending' === task.status, `task status should be pending, but got: ${task.status}`);
139
- task.timing = {
140
- start: Date.now()
141
- };
142
+ let taskStarted = false;
142
143
  try {
143
144
  this.abortSignal?.throwIfAborted();
145
+ taskStarted = true;
146
+ task.timing = {
147
+ start: Date.now()
148
+ };
144
149
  task.status = 'running';
145
150
  await this.emitSnapshotChange();
146
151
  await this.emitTaskEvent('start', task);
152
+ this.abortSignal?.throwIfAborted();
147
153
  try {
148
- if (this.onTaskStart) await this.onTaskStart(task);
154
+ if (this.onTaskStart) await this.onTaskStart(task, this.abortSignal);
149
155
  } catch (e) {
150
156
  console.error('error in onTaskStart', e);
151
157
  }
@@ -169,7 +175,8 @@ class TaskRunner {
169
175
  const executorContext = {
170
176
  task,
171
177
  element: previousFindOutput?.element,
172
- uiContext
178
+ uiContext,
179
+ signal: this.abortSignal
173
180
  };
174
181
  if ('Insight' === task.type) {
175
182
  assert('Query' === task.subType || 'Assert' === task.subType || 'WaitFor' === task.subType || 'Boolean' === task.subType || 'Number' === task.subType || 'String' === task.subType, `unsupported service subType: ${task.subType}`);
@@ -191,6 +198,7 @@ class TaskRunner {
191
198
  if (isLastTask) {
192
199
  setTimingFieldOnce(task.timing, 'captureAfterCallingSnapshotStart');
193
200
  const screenshot = await this.captureScreenshot();
201
+ this.abortSignal?.throwIfAborted();
194
202
  this.attachRecorderItem(task, screenshot, 'after-calling');
195
203
  setTimingFieldOnce(task.timing, 'captureAfterCallingSnapshotEnd');
196
204
  }
@@ -208,11 +216,13 @@ class TaskRunner {
208
216
  task.error = serializedError;
209
217
  task.errorMessage = serializedError.message;
210
218
  task.errorStack = serializedError.stack;
211
- task.status = 'failed';
212
- task.timing.end = Date.now();
213
- task.timing.cost = task.timing.end - task.timing.start;
219
+ task.status = taskStarted ? 'failed' : 'cancelled';
220
+ if (taskStarted && task.timing) {
221
+ task.timing.end = Date.now();
222
+ task.timing.cost = task.timing.end - task.timing.start;
223
+ }
214
224
  await this.emitSnapshotChange();
215
- await this.emitTaskEvent('error', task);
225
+ await this.emitTaskEvent(taskStarted ? 'error' : 'cancel', task);
216
226
  break;
217
227
  }
218
228
  }
@@ -232,10 +242,10 @@ class TaskRunner {
232
242
  } else {
233
243
  this.status = 'error';
234
244
  const errorTask = this.latestErrorTask();
235
- const error = errorTask?.error ?? {
245
+ const error = errorTask?.error ?? (this.abortSignal?.aborted ? serializeError(this.abortSignal.reason) : {
236
246
  name: 'Error',
237
247
  message: 'Task execution failed'
238
- };
248
+ });
239
249
  finalizeError = new TaskExecutionError(error, serializeErrorTask(errorTask));
240
250
  await this.emitSnapshotChange(finalizeError);
241
251
  }
@@ -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 {\n type SerializedError,\n serializeError,\n truncateSerializedErrorString,\n} from '@midscene/shared/agent-tools/error-formatter';\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\n/**\n * A native, per-task lifecycle notification. The runner is the single source\n * of truth for when a task actually transitions, so it reports each transition\n * exactly once with the task that changed. Consumers no longer have to re-scan\n * the task list and diff it against remembered keys to reconstruct this stream.\n */\nexport type TaskRunnerEventKind =\n | 'append'\n | 'start'\n | 'finish'\n | 'error'\n | 'cancel';\n\nexport interface TaskRunnerEvent {\n kind: TaskRunnerEventKind;\n task: ExecutionTask;\n runner: TaskRunner;\n}\n\nexport interface SerializedErrorTask {\n taskId: string;\n type: ExecutionTask['type'];\n subType?: string;\n status: ExecutionTask['status'];\n thought?: string;\n errorMessage?: string;\n}\n\nexport interface SerializedTaskExecutionError extends SerializedError {\n code: 'TASK_EXECUTION_FAILED';\n cause: SerializedError;\n task?: SerializedErrorTask;\n}\n\nfunction serializeErrorTask(\n task: ExecutionTask | null,\n): SerializedErrorTask | undefined {\n if (!task) {\n return undefined;\n }\n\n return {\n taskId: truncateSerializedErrorString(task.taskId),\n type: task.type,\n ...(task.subType === undefined\n ? {}\n : { subType: truncateSerializedErrorString(task.subType) }),\n status: task.status,\n ...(task.thought === undefined\n ? {}\n : { thought: truncateSerializedErrorString(task.thought) }),\n ...(task.errorMessage === undefined\n ? {}\n : {\n errorMessage: truncateSerializedErrorString(task.errorMessage),\n }),\n };\n}\n\nexport type TaskRunnerEventListener = (\n event: TaskRunnerEvent,\n) => Promise<void> | void;\n\n/** Minimal prompt-image shape retained as report asset metadata. */\nexport interface ExecutionReferenceImage {\n readonly url: string;\n}\n\ntype TaskRunnerInitOptions = ExecutionTaskProgressOptions & {\n tasks?: ExecutionTaskApply[];\n abortSignal?: AbortSignal;\n referenceImages?: readonly ExecutionReferenceImage[];\n /**\n * Coarse \"the execution snapshot changed\" signal. Fires on any state change\n * (append, status flips, completion) with the whole runner, so consumers can\n * re-dump/re-render the current snapshot. Deliberately batch-granular, unlike\n * the per-task {@link onTaskEvent} stream.\n */\n onSnapshotChange?: (\n runner: TaskRunner,\n error?: TaskExecutionError,\n ) => Promise<void> | void;\n onTaskEvent?: TaskRunnerEventListener;\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 private readonly abortSignal?: AbortSignal;\n\n private readonly onSnapshotChange?:\n | ((runner: TaskRunner, error?: TaskExecutionError) => Promise<void> | void)\n | undefined;\n\n private readonly onTaskEvent?: TaskRunnerEventListener | undefined;\n\n private readonly executionLogTime: number;\n\n private readonly referenceImageUrls = new Set<string>();\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.abortSignal = options?.abortSignal;\n this.onSnapshotChange = options?.onSnapshotChange;\n this.onTaskEvent = options?.onTaskEvent;\n this.executionLogTime = Date.now();\n for (const image of options?.referenceImages ?? []) {\n this.referenceImageUrls.add(image.url);\n }\n }\n\n private async emitSnapshotChange(error?: TaskExecutionError): Promise<void> {\n if (!this.onSnapshotChange) {\n return;\n }\n await this.onSnapshotChange(this, error);\n }\n\n private async emitTaskEvent(\n kind: TaskRunnerEventKind,\n task: ExecutionTask,\n ): Promise<void> {\n if (!this.onTaskEvent) {\n return;\n }\n try {\n await this.onTaskEvent({ kind, task, runner: this });\n } catch (error) {\n console.error('Error in onTaskEvent listener', error);\n }\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()?.errorMessage}\\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()?.errorMessage}\\n${this.latestErrorTask()?.errorStack}`,\n );\n const appended: ExecutionTask[] = [];\n if (Array.isArray(task)) {\n appended.push(...task.map((item) => this.markTaskAsPending(item)));\n } else {\n appended.push(this.markTaskAsPending(task));\n }\n this.tasks.push(...appended);\n if (this.status !== 'running') {\n this.status = 'pending';\n }\n await this.emitSnapshotChange();\n for (const appendedTask of appended) {\n await this.emitTaskEvent('append', appendedTask);\n }\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.emitSnapshotChange();\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 this.abortSignal?.throwIfAborted();\n task.status = 'running';\n await this.emitSnapshotChange();\n await this.emitTaskEvent('start', task);\n try {\n if (this.onTaskStart) {\n await this.onTaskStart(task);\n }\n } catch (e) {\n console.error('error in onTaskStart', e);\n }\n this.abortSignal?.throwIfAborted();\n assert(\n ['Insight', 'Action Space', 'Planning'].indexOf(task.type) >= 0,\n `unsupported task type: ${task.type}`,\n );\n\n const { executor } = 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 this.abortSignal?.throwIfAborted();\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(executorContext);\n } else if (task.type === 'Planning') {\n returnValue = await task.executor(executorContext);\n if (task.subType === 'Locate') {\n previousFindOutput = (\n returnValue as ExecutionTaskReturn<ExecutionTaskPlanningLocateOutput>\n )?.output;\n }\n } else if (task.type === 'Action Space') {\n try {\n returnValue = await task.executor(executorContext);\n } finally {\n // The short TTL still lets planning and the action itself share one\n // context. Once an action settles, time alone can no longer prove\n // that the cached context represents the current UI.\n this.lastUiContext = undefined;\n }\n } else {\n console.warn(\n `unsupported task type: ${task.type}, will try to execute it directly`,\n );\n returnValue = await task.executor(executorContext);\n }\n\n this.abortSignal?.throwIfAborted();\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 // Cancellation may arrive during the report screenshot, after planning\n // has returned but before the next batch of actions is built.\n this.abortSignal?.throwIfAborted();\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.emitSnapshotChange();\n await this.emitTaskEvent('finish', task);\n taskIndex++;\n } catch (error) {\n successfullyCompleted = false;\n const serializedError = serializeError(error);\n task.error = serializedError;\n task.errorMessage = serializedError.message;\n task.errorStack = serializedError.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.emitSnapshotChange();\n await this.emitTaskEvent('error', task);\n break;\n }\n }\n\n // set all remaining tasks as cancelled\n const cancelledTasks: ExecutionTask[] = [];\n for (let i = taskIndex + 1; i < this.tasks.length; i++) {\n this.tasks[i].status = 'cancelled';\n cancelledTasks.push(this.tasks[i]);\n }\n if (cancelledTasks.length > 0) {\n await this.emitSnapshotChange();\n for (const cancelledTask of cancelledTasks) {\n await this.emitTaskEvent('cancel', cancelledTask);\n }\n }\n\n let finalizeError: TaskExecutionError | undefined;\n if (!successfullyCompleted) {\n this.status = 'error';\n const errorTask = this.latestErrorTask();\n const error = errorTask?.error ?? {\n name: 'Error',\n message: 'Task execution failed',\n };\n finalizeError = new TaskExecutionError(\n error,\n serializeErrorTask(errorTask),\n );\n await this.emitSnapshotChange(finalizeError);\n } else {\n this.status = 'completed';\n await this.emitSnapshotChange();\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 {\n id: this.id,\n logTime: this.executionLogTime,\n name: this.name,\n tasks: this.tasks,\n },\n { referenceImageUrls: [...this.referenceImageUrls] },\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 readonly code = 'TASK_EXECUTION_FAILED' as const;\n\n override readonly cause: SerializedError;\n\n readonly task?: SerializedErrorTask;\n\n constructor(error: SerializedError, task?: SerializedErrorTask) {\n super(error.message, { cause: error });\n this.name = 'TaskExecutionError';\n this.cause = error;\n this.task = task;\n if (this.stack) {\n this.stack = truncateSerializedErrorString(this.stack);\n }\n }\n\n /**\n * Return the same bounded fields already stored on the live error. The\n * wrapper stack identifies where TaskExecutionError was created; cause.stack\n * preserves the executor's original stack.\n */\n toJSON(): SerializedTaskExecutionError {\n return {\n name: this.name,\n code: this.code,\n message: this.message,\n ...(this.stack === undefined ? {} : { stack: this.stack }),\n cause: this.cause,\n ...(this.task === undefined ? {} : { task: this.task }),\n };\n }\n}\n"],"names":["debug","getDebug","UI_CONTEXT_CACHE_TTL_MS","serializeErrorTask","task","truncateSerializedErrorString","undefined","TaskRunner","error","kind","console","options","now","Date","shouldReuse","uiContext","screenshot","phase","recorderItem","uuid","errorMessage","assert","appended","Array","item","appendedTask","nextPendingIndex","taskIndex","successfullyCompleted","previousFindOutput","e","executor","returnValue","forceRefresh","setTimingFieldOnce","executorContext","isLastTask","Object","serializedError","serializeError","cancelledTasks","i","cancelledTask","finalizeError","errorTask","TaskExecutionError","outputIndex","Math","thought","output","ExecutionDump","errorMsg","Error","name","uiContextBuilder","Set","image"],"mappings":"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"}
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 {\n type SerializedError,\n serializeError,\n truncateSerializedErrorString,\n} from '@midscene/shared/agent-tools/error-formatter';\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\n/**\n * A native, per-task lifecycle notification. The runner is the single source\n * of truth for when a task actually transitions, so it reports each transition\n * exactly once with the task that changed. Consumers no longer have to re-scan\n * the task list and diff it against remembered keys to reconstruct this stream.\n */\nexport type TaskRunnerEventKind =\n | 'append'\n | 'start'\n | 'finish'\n | 'error'\n | 'cancel';\n\nexport interface TaskRunnerEvent {\n kind: TaskRunnerEventKind;\n task: ExecutionTask;\n runner: TaskRunner;\n}\n\nexport interface SerializedErrorTask {\n taskId: string;\n type: ExecutionTask['type'];\n subType?: string;\n status: ExecutionTask['status'];\n thought?: string;\n errorMessage?: string;\n}\n\nexport interface SerializedTaskExecutionError extends SerializedError {\n code: 'TASK_EXECUTION_FAILED';\n cause: SerializedError;\n task?: SerializedErrorTask;\n}\n\nfunction serializeErrorTask(\n task: ExecutionTask | null,\n): SerializedErrorTask | undefined {\n if (!task) {\n return undefined;\n }\n\n return {\n taskId: truncateSerializedErrorString(task.taskId),\n type: task.type,\n ...(task.subType === undefined\n ? {}\n : { subType: truncateSerializedErrorString(task.subType) }),\n status: task.status,\n ...(task.thought === undefined\n ? {}\n : { thought: truncateSerializedErrorString(task.thought) }),\n ...(task.errorMessage === undefined\n ? {}\n : {\n errorMessage: truncateSerializedErrorString(task.errorMessage),\n }),\n };\n}\n\nexport type TaskRunnerEventListener = (\n event: TaskRunnerEvent,\n) => Promise<void> | void;\n\n/** Minimal prompt-image shape retained as report asset metadata. */\nexport interface ExecutionReferenceImage {\n readonly url: string;\n}\n\ntype TaskRunnerInitOptions = ExecutionTaskProgressOptions & {\n tasks?: ExecutionTaskApply[];\n abortSignal?: AbortSignal;\n referenceImages?: readonly ExecutionReferenceImage[];\n /**\n * Coarse \"the execution snapshot changed\" signal. Fires on any state change\n * (append, status flips, completion) with the whole runner, so consumers can\n * re-dump/re-render the current snapshot. Deliberately batch-granular, unlike\n * the per-task {@link onTaskEvent} stream.\n */\n onSnapshotChange?: (\n runner: TaskRunner,\n error?: TaskExecutionError,\n ) => Promise<void> | void;\n onTaskEvent?: TaskRunnerEventListener;\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: (\n signal?: AbortSignal,\n ) => Promise<UIContext>;\n private readonly abortSignal?: AbortSignal;\n\n private readonly onSnapshotChange?:\n | ((runner: TaskRunner, error?: TaskExecutionError) => Promise<void> | void)\n | undefined;\n\n private readonly onTaskEvent?: TaskRunnerEventListener | undefined;\n\n private readonly executionLogTime: number;\n\n private readonly referenceImageUrls = new Set<string>();\n\n constructor(\n name: string,\n uiContextBuilder: (signal?: AbortSignal) => 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.abortSignal = options?.abortSignal;\n this.onSnapshotChange = options?.onSnapshotChange;\n this.onTaskEvent = options?.onTaskEvent;\n this.executionLogTime = Date.now();\n for (const image of options?.referenceImages ?? []) {\n this.referenceImageUrls.add(image.url);\n }\n }\n\n private async emitSnapshotChange(error?: TaskExecutionError): Promise<void> {\n if (!this.onSnapshotChange) {\n return;\n }\n await this.onSnapshotChange(this, error);\n }\n\n private async emitTaskEvent(\n kind: TaskRunnerEventKind,\n task: ExecutionTask,\n ): Promise<void> {\n if (!this.onTaskEvent) {\n return;\n }\n try {\n await this.onTaskEvent({ kind, task, runner: this });\n } catch (error) {\n console.error('Error in onTaskEvent listener', error);\n }\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 this.abortSignal?.throwIfAborted();\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(this.abortSignal);\n this.abortSignal?.throwIfAborted();\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()?.errorMessage}\\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.abortSignal?.throwIfAborted();\n this.normalizeStatusFromError(\n options,\n `task runner is in error state, cannot append task\\nerror=${this.latestErrorTask()?.errorMessage}\\n${this.latestErrorTask()?.errorStack}`,\n );\n const appended: ExecutionTask[] = [];\n if (Array.isArray(task)) {\n appended.push(...task.map((item) => this.markTaskAsPending(item)));\n } else {\n appended.push(this.markTaskAsPending(task));\n }\n this.tasks.push(...appended);\n if (this.status !== 'running') {\n this.status = 'pending';\n }\n await this.emitSnapshotChange();\n for (const appendedTask of appended) {\n await this.emitTaskEvent('append', appendedTask);\n }\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.emitSnapshotChange();\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 let taskStarted = false;\n try {\n this.abortSignal?.throwIfAborted();\n taskStarted = true;\n task.timing = { start: Date.now() };\n task.status = 'running';\n await this.emitSnapshotChange();\n await this.emitTaskEvent('start', task);\n this.abortSignal?.throwIfAborted();\n try {\n if (this.onTaskStart) {\n await this.onTaskStart(task, this.abortSignal);\n }\n } catch (e) {\n console.error('error in onTaskStart', e);\n }\n this.abortSignal?.throwIfAborted();\n assert(\n ['Insight', 'Action Space', 'Planning'].indexOf(task.type) >= 0,\n `unsupported task type: ${task.type}`,\n );\n\n const { executor } = 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 this.abortSignal?.throwIfAborted();\n\n task.uiContext = uiContext;\n const executorContext: ExecutorContext = {\n task,\n element: previousFindOutput?.element,\n uiContext,\n signal: this.abortSignal,\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(executorContext);\n } else if (task.type === 'Planning') {\n returnValue = await task.executor(executorContext);\n if (task.subType === 'Locate') {\n previousFindOutput = (\n returnValue as ExecutionTaskReturn<ExecutionTaskPlanningLocateOutput>\n )?.output;\n }\n } else if (task.type === 'Action Space') {\n try {\n returnValue = await task.executor(executorContext);\n } finally {\n // The short TTL still lets planning and the action itself share one\n // context. Once an action settles, time alone can no longer prove\n // that the cached context represents the current UI.\n this.lastUiContext = undefined;\n }\n } else {\n console.warn(\n `unsupported task type: ${task.type}, will try to execute it directly`,\n );\n returnValue = await task.executor(executorContext);\n }\n\n this.abortSignal?.throwIfAborted();\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.abortSignal?.throwIfAborted();\n this.attachRecorderItem(task, screenshot, 'after-calling');\n setTimingFieldOnce(task.timing, 'captureAfterCallingSnapshotEnd');\n }\n // Cancellation may arrive during the report screenshot, after planning\n // has returned but before the next batch of actions is built.\n this.abortSignal?.throwIfAborted();\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.emitSnapshotChange();\n await this.emitTaskEvent('finish', task);\n taskIndex++;\n } catch (error) {\n successfullyCompleted = false;\n const serializedError = serializeError(error);\n task.error = serializedError;\n task.errorMessage = serializedError.message;\n task.errorStack = serializedError.stack;\n\n task.status = taskStarted ? 'failed' : 'cancelled';\n if (taskStarted && task.timing) {\n task.timing.end = Date.now();\n task.timing.cost = task.timing.end - task.timing.start;\n }\n await this.emitSnapshotChange();\n await this.emitTaskEvent(taskStarted ? 'error' : 'cancel', task);\n break;\n }\n }\n\n // set all remaining tasks as cancelled\n const cancelledTasks: ExecutionTask[] = [];\n for (let i = taskIndex + 1; i < this.tasks.length; i++) {\n this.tasks[i].status = 'cancelled';\n cancelledTasks.push(this.tasks[i]);\n }\n if (cancelledTasks.length > 0) {\n await this.emitSnapshotChange();\n for (const cancelledTask of cancelledTasks) {\n await this.emitTaskEvent('cancel', cancelledTask);\n }\n }\n\n let finalizeError: TaskExecutionError | undefined;\n if (!successfullyCompleted) {\n this.status = 'error';\n const errorTask = this.latestErrorTask();\n const error =\n errorTask?.error ??\n (this.abortSignal?.aborted\n ? serializeError(this.abortSignal.reason)\n : {\n name: 'Error',\n message: 'Task execution failed',\n });\n finalizeError = new TaskExecutionError(\n error,\n serializeErrorTask(errorTask),\n );\n await this.emitSnapshotChange(finalizeError);\n } else {\n this.status = 'completed';\n await this.emitSnapshotChange();\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 {\n id: this.id,\n logTime: this.executionLogTime,\n name: this.name,\n tasks: this.tasks,\n },\n { referenceImageUrls: [...this.referenceImageUrls] },\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 readonly code = 'TASK_EXECUTION_FAILED' as const;\n\n override readonly cause: SerializedError;\n\n readonly task?: SerializedErrorTask;\n\n constructor(error: SerializedError, task?: SerializedErrorTask) {\n super(error.message, { cause: error });\n this.name = 'TaskExecutionError';\n this.cause = error;\n this.task = task;\n if (this.stack) {\n this.stack = truncateSerializedErrorString(this.stack);\n }\n }\n\n /**\n * Return the same bounded fields already stored on the live error. The\n * wrapper stack identifies where TaskExecutionError was created; cause.stack\n * preserves the executor's original stack.\n */\n toJSON(): SerializedTaskExecutionError {\n return {\n name: this.name,\n code: this.code,\n message: this.message,\n ...(this.stack === undefined ? {} : { stack: this.stack }),\n cause: this.cause,\n ...(this.task === undefined ? {} : { task: this.task }),\n };\n }\n}\n"],"names":["debug","getDebug","UI_CONTEXT_CACHE_TTL_MS","serializeErrorTask","task","truncateSerializedErrorString","undefined","TaskRunner","error","kind","console","options","now","Date","shouldReuse","uiContext","screenshot","phase","recorderItem","uuid","errorMessage","assert","appended","Array","item","appendedTask","nextPendingIndex","taskIndex","successfullyCompleted","previousFindOutput","taskStarted","e","executor","returnValue","forceRefresh","setTimingFieldOnce","executorContext","isLastTask","Object","serializedError","serializeError","cancelledTasks","i","cancelledTask","finalizeError","errorTask","TaskExecutionError","outputIndex","Math","thought","output","ExecutionDump","errorMsg","Error","name","uiContextBuilder","Set","image"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"test-run-report.mjs","sources":["../../src/test-run-report.ts"],"sourcesContent":["export const TEST_RUN_REPORT_SCRIPT_TYPE = 'midscene_test_run_dump';\n\nexport type TestRunReportRunStatus = 'success' | 'failed';\nexport type TestRunReportCaseStatus = TestRunReportRunStatus | 'not-run';\nexport type TestRunReportStepPhase =\n | 'beforeAll'\n | 'beforeEach'\n | 'steps'\n | 'afterEach'\n | 'afterAll';\n\n/**\n * A JSON-safe value prepared for display in a Midscene Test report.\n *\n * Paths use JSONPath-like notation rooted at `$`. The path arrays are kept\n * beside the value so the UI never presents redacted or truncated data as if\n * it were complete.\n */\nexport interface TestRunReportValue {\n value: unknown;\n truncatedPaths?: string[];\n redactedPaths?: string[];\n}\n\nexport interface TestRunReportError {\n name: string;\n message: string;\n code?: string;\n details?: TestRunReportValue;\n}\n\nexport interface TestRunReportHostError {\n phase:\n | 'setup'\n | 'execution'\n | 'cleanup'\n | 'report'\n | 'publication'\n | 'observer';\n error: TestRunReportError;\n}\n\nexport interface TestRunReportDiagnostic {\n level: 'warning' | 'error';\n code:\n | 'agent-detail-unresolved'\n | 'agent-detail-unavailable'\n | 'large-report'\n | 'run-infrastructure-error'\n | 'source-without-executions';\n message: string;\n scopeId?: string;\n executionId?: string;\n sourcePaths?: string[];\n}\n\nexport interface TestRunReportMetrics {\n modelCallCount: number;\n modelTimeMs: number;\n promptTokens: number;\n cachedInputTokens: number;\n completionTokens: number;\n totalTokens: number;\n}\n\nexport interface TestRunReportAgentDetail {\n reportId: string;\n executionId: string;\n}\n\nexport interface TestRunReportStep {\n id: string;\n phase: TestRunReportStepPhase;\n stepIndex: number;\n node: string;\n title?: string;\n status: TestRunReportRunStatus;\n continuedAfterError: boolean;\n startedAt: string;\n endedAt: string;\n durationMs: number;\n input?: TestRunReportValue;\n output?: {\n summary?: string;\n data?: TestRunReportValue;\n };\n error?: TestRunReportError;\n agentDetails?: TestRunReportAgentDetail[];\n agentDetailDiagnostic?: string;\n}\n\nexport interface TestRunReportAttempt {\n attemptId: string;\n attemptIndex: number;\n status: TestRunReportRunStatus;\n startedAt: string;\n endedAt: string;\n durationMs: number;\n beforeEach: TestRunReportStep[];\n steps: TestRunReportStep[];\n afterEach: TestRunReportStep[];\n teardownErrors?: TestRunReportError[];\n hostErrors?: TestRunReportHostError[];\n /** Report groups available for scope-level fallback browsing. */\n scopeReportIds?: string[];\n}\n\nexport interface TestRunReportCase {\n caseId: string;\n name: string;\n caseIndex: number;\n status: TestRunReportCaseStatus;\n notRunReason?: string;\n attempts: TestRunReportAttempt[];\n}\n\nexport interface TestRunReportDocument {\n documentId: string;\n /** Logical file identity when separate invocations need distinct UI IDs. */\n logicalDocumentId?: string;\n documentRunId?: string;\n attemptIndex?: number;\n sourcePath: string;\n status: TestRunReportRunStatus;\n startedAt?: string;\n endedAt?: string;\n durationMs?: number;\n beforeAll: TestRunReportStep[];\n cases: TestRunReportCase[];\n afterAll: TestRunReportStep[];\n teardownErrors?: TestRunReportError[];\n hostErrors?: TestRunReportHostError[];\n /** Report groups available for scope-level fallback browsing. */\n scopeReportIds?: string[];\n /** Whole-file invocation history; Cases aggregate their own attempts. */\n attempts?: Omit<TestRunReportDocument, 'cases' | 'attempts'>[];\n}\n\nexport interface TestRunReportCollectionError {\n sourcePath: string;\n error: TestRunReportError;\n}\n\nexport interface TestRunReportProjectLifecycle {\n status: TestRunReportRunStatus;\n startedAt: string;\n endedAt: string;\n durationMs: number;\n setupError?: TestRunReportError;\n teardownErrors?: TestRunReportError[];\n}\n\nexport interface TestRunReportProject {\n projectId: string;\n name: string;\n /** Display identity, also supports legacy custom interface adapters. */\n platform?: string;\n status: TestRunReportRunStatus;\n retry: number;\n lifecycle?: TestRunReportProjectLifecycle;\n documents: TestRunReportDocument[];\n collectionErrors: TestRunReportCollectionError[];\n}\n\nexport interface TestRunReportSummary {\n total: number;\n passed: number;\n failed: number;\n notRun: number;\n filtered: number;\n collectionErrors: number;\n documentFailures: number;\n projectFailures: number;\n}\n\nexport interface TestRunReportDump {\n schemaVersion: 1;\n kind: 'test-runner';\n runId: string;\n status: TestRunReportRunStatus;\n startedAt: string;\n endedAt: string;\n durationMs: number;\n summary: TestRunReportSummary;\n metrics: TestRunReportMetrics;\n projects: TestRunReportProject[];\n diagnostics?: TestRunReportDiagnostic[];\n}\n\nexport interface TestRunReportSource {\n /** Resource/report scope that owns this finalized Midscene Agent report. */\n scopeId: string;\n /** Absolute path to a finalized Midscene Agent report. */\n sourcePath: string;\n /** Current Agent dump JSON; when present, HTML is not parsed for execution data. */\n dumpPath?: string;\n}\n\nexport interface IndexedTestRunReportSource {\n reportId: string;\n scopeId: string;\n sourcePath: string;\n executionIds: string[];\n}\n\nexport interface TestRunReportSourceIndex {\n sources: IndexedTestRunReportSource[];\n metrics: TestRunReportMetrics;\n}\n\nexport interface AssembleTestRunReportOptions {\n outputDir: string;\n /** File or directory name without an extension. */\n reportFileName: string;\n sources: readonly TestRunReportSource[];\n buildRunnerDump(index: TestRunReportSourceIndex): TestRunReportDump;\n overwrite?: boolean;\n}\n"],"names":["TEST_RUN_REPORT_SCRIPT_TYPE"],"mappings":"AAAO,MAAMA,8BAA8B"}
1
+ {"version":3,"file":"test-run-report.mjs","sources":["../../src/test-run-report.ts"],"sourcesContent":["export const TEST_RUN_REPORT_SCRIPT_TYPE = 'midscene_test_run_dump';\n\nexport type TestRunReportRunStatus = 'success' | 'failed';\nexport type TestRunReportCaseStatus = TestRunReportRunStatus | 'not-run';\nexport type TestRunReportStepPhase =\n | 'beforeAll'\n | 'beforeEach'\n | 'steps'\n | 'afterEach'\n | 'afterAll';\n\n/**\n * A JSON-safe value prepared for display in a Midscene Test report.\n *\n * Paths use JSONPath-like notation rooted at `$`. The path arrays are kept\n * beside the value so the UI never presents redacted or truncated data as if\n * it were complete.\n */\nexport interface TestRunReportValue {\n value: unknown;\n truncatedPaths?: string[];\n redactedPaths?: string[];\n}\n\nexport interface TestRunReportError {\n format?: 'midscene-error';\n name: string;\n message: string;\n code?: string;\n details?: TestRunReportValue;\n cause?: TestRunReportError;\n errors?: TestRunReportError[];\n truncated?: boolean;\n}\n\nexport interface TestRunReportOperationReference {\n operationId: string;\n scopeId?: string;\n stepId?: string;\n node?: string;\n}\n\n/** Immutable observation; late completion remains in the operation journal. */\nexport interface TestRunReportCleanup {\n status: 'not-required' | 'completed' | 'pending' | 'failed';\n capturedAt: string;\n operationId?: string;\n blockingOperations?: TestRunReportOperationReference[];\n error?: TestRunReportError;\n}\n\nexport interface TestRunReportRetryDecision {\n scope: 'case' | 'document';\n retryCount: number;\n attemptIndex: number;\n decision: 'retry' | 'stop';\n reason:\n | 'passed'\n | 'attempt-failed'\n | 'budget-exhausted'\n | 'cleanup-pending'\n | 'cleanup-failed'\n | 'operation-pending'\n | 'fatal-error'\n | 'interrupted'\n | 'bail'\n | 'infrastructure-error';\n}\n\nexport interface TestRunReportPhaseResult {\n status: 'success' | 'failed' | 'not-run' | 'not-required';\n plannedStepCount: number;\n executedStepCount: number;\n}\n\nexport interface TestRunReportHostError {\n phase:\n | 'setup'\n | 'execution'\n | 'cleanup'\n | 'report'\n | 'publication'\n | 'observer';\n error: TestRunReportError;\n}\n\nexport interface TestRunReportDiagnostic {\n level: 'warning' | 'error';\n code:\n | 'agent-detail-unresolved'\n | 'agent-detail-unavailable'\n | 'large-report'\n | 'run-infrastructure-error'\n | 'agent-source-unavailable'\n | 'source-without-executions';\n message: string;\n scopeId?: string;\n executionId?: string;\n sourcePaths?: string[];\n stepId?: string;\n error?: TestRunReportError;\n}\n\nexport interface TestRunReportMetrics {\n modelCallCount: number;\n modelTimeMs: number;\n promptTokens: number;\n cachedInputTokens: number;\n completionTokens: number;\n totalTokens: number;\n collection?: {\n status: 'complete' | 'partial' | 'unknown';\n expectedExecutionCount: number;\n capturedExecutionCount: number;\n reasons: string[];\n fields?: Record<\n 'modelCallCount' | 'modelTimeMs' | 'totalTokens',\n 'complete' | 'partial' | 'unknown'\n >;\n };\n}\n\nexport interface TestRunReportSnapshotCollection {\n status: 'complete' | 'partial';\n /** Native dump cutoff before I/O; observation time for partial live sources. */\n capturedAt: string;\n reason?: string;\n}\n\nexport interface TestRunReportAgentDetail {\n reportId: string;\n executionId: string;\n}\n\n/** Operation facts are independent of Agent dump IDs and report snapshots. */\nexport interface TestRunReportOperationEvent {\n operationId: string;\n operationKind: 'node' | 'resource' | 'acquisition' | 'cleanup' | 'readiness';\n kind:\n | 'started'\n | 'cancel-requested'\n | 'settled'\n | 'runner-returned'\n | 'cleanup-deferred';\n at: string;\n stepId?: string;\n scopeId?: string;\n node?: string;\n status?: 'success' | 'failed' | 'not-run';\n /** True only when the actual operation has not settled at Runner return. */\n pending?: boolean;\n error?: TestRunReportError;\n blockingOperations?: TestRunReportOperationReference[];\n}\n\nexport interface TestRunReportPlannedStep {\n id: string;\n phase: TestRunReportStepPhase;\n stepIndex: number;\n node: string;\n title?: string;\n input: TestRunReportValue;\n inputMode: 'template' | 'resolved';\n timeoutMs?: number;\n continueOnError: boolean;\n}\n\nexport interface TestRunReportCasePlan {\n caseId: string;\n name: string;\n caseIndex: number;\n selected: boolean;\n tags: string[];\n onFailure?: 'continue' | 'stop-document';\n beforeEach: TestRunReportPlannedStep[];\n steps: TestRunReportPlannedStep[];\n afterEach: TestRunReportPlannedStep[];\n}\n\n/** A safe snapshot collected before resource acquisition, independent of attempts. */\nexport interface TestRunReportDocumentPlan {\n documentId: string;\n sourcePath: string;\n sourceYaml?: TestRunReportValue;\n retry: { scope: 'case' | 'document'; count: number };\n beforeAll: TestRunReportPlannedStep[];\n cases: TestRunReportCasePlan[];\n afterAll: TestRunReportPlannedStep[];\n}\n\ninterface TestRunReportStepBase {\n id: string;\n planStepId?: string;\n phase: TestRunReportStepPhase;\n stepIndex: number;\n node: string;\n title?: string;\n continuedAfterError: boolean;\n /** Time spent waiting for a cancelled operation before this Step started. */\n waitForPreviousOperationMs?: number;\n input?: TestRunReportValue;\n output?: {\n summary?: string;\n data?: TestRunReportValue;\n };\n error?: TestRunReportError;\n agentDetails?: TestRunReportAgentDetail[];\n agentDetailDiagnostic?: string;\n /** Usage survives even when screenshots or execution detail storage fails. */\n metrics?: TestRunReportMetrics;\n evidenceCollection?: {\n status: 'complete' | 'partial' | 'unknown';\n /** The missing source also prevented independent usage recovery. */\n modelUsageUnavailable?: boolean;\n capturedAt?: string;\n errors?: TestRunReportError[];\n };\n operations?: { capturedAt: string; events: TestRunReportOperationEvent[] };\n}\n\nexport type TestRunReportStep = TestRunReportStepBase &\n (\n | {\n status: TestRunReportRunStatus;\n startedAt: string;\n endedAt: string;\n durationMs: number;\n notRunReason?: never;\n blockedBy?: never;\n }\n | {\n status: 'not-run';\n notRunReason: string;\n blockedBy?: string;\n startedAt?: never;\n endedAt?: never;\n durationMs?: never;\n }\n );\n\nexport interface TestRunReportAttempt {\n attemptId: string;\n attemptIndex: number;\n status: TestRunReportRunStatus;\n executionStatus?: TestRunReportCaseStatus;\n phases?: Partial<Record<TestRunReportStepPhase, TestRunReportPhaseResult>>;\n cleanup?: TestRunReportCleanup;\n pendingOperations?: TestRunReportOperationReference[];\n retryDecision?: TestRunReportRetryDecision;\n startedAt: string;\n endedAt: string;\n durationMs: number;\n beforeEach: TestRunReportStep[];\n steps: TestRunReportStep[];\n afterEach: TestRunReportStep[];\n teardownErrors?: TestRunReportError[];\n hostErrors?: TestRunReportHostError[];\n /** Report groups available for scope-level fallback browsing. */\n scopeReportIds?: string[];\n}\n\nexport interface TestRunReportCase {\n plan?: TestRunReportCasePlan;\n caseId: string;\n name: string;\n caseIndex: number;\n status: TestRunReportCaseStatus;\n notRunReason?: string;\n attempts: TestRunReportAttempt[];\n}\n\nexport interface TestRunReportDocument {\n plan?: TestRunReportDocumentPlan;\n documentId: string;\n /** Logical file identity when separate invocations need distinct UI IDs. */\n logicalDocumentId?: string;\n documentRunId?: string;\n attemptIndex?: number;\n sourcePath: string;\n status: TestRunReportRunStatus;\n executionStatus?: TestRunReportCaseStatus;\n phases?: Partial<Record<TestRunReportStepPhase, TestRunReportPhaseResult>>;\n cleanup?: TestRunReportCleanup;\n pendingOperations?: TestRunReportOperationReference[];\n retryDecision?: TestRunReportRetryDecision;\n startedAt?: string;\n endedAt?: string;\n durationMs?: number;\n beforeAll: TestRunReportStep[];\n cases: TestRunReportCase[];\n afterAll: TestRunReportStep[];\n teardownErrors?: TestRunReportError[];\n hostErrors?: TestRunReportHostError[];\n /** Report groups available for scope-level fallback browsing. */\n scopeReportIds?: string[];\n /** Whole-file invocation history; Cases aggregate their own attempts. */\n attempts?: Omit<TestRunReportDocument, 'cases' | 'attempts'>[];\n}\n\nexport interface TestRunReportCollectionError {\n sourcePath: string;\n error: TestRunReportError;\n}\n\nexport interface TestRunReportProjectLifecycle {\n status: TestRunReportRunStatus;\n startedAt: string;\n endedAt: string;\n durationMs: number;\n setupError?: TestRunReportError;\n teardownErrors?: TestRunReportError[];\n cleanup?: TestRunReportCleanup;\n pendingOperations?: TestRunReportOperationReference[];\n}\n\nexport interface TestRunReportProject {\n plans?: readonly TestRunReportDocumentPlan[];\n projectId: string;\n name: string;\n /** Display identity, also supports legacy custom interface adapters. */\n platform?: string;\n status: TestRunReportRunStatus;\n retry: number;\n lifecycle?: TestRunReportProjectLifecycle;\n documents: TestRunReportDocument[];\n collectionErrors: TestRunReportCollectionError[];\n}\n\nexport interface TestRunReportSummary {\n total: number;\n passed: number;\n failed: number;\n notRun: number;\n filtered: number;\n collectionErrors: number;\n documentFailures: number;\n projectFailures: number;\n}\n\nexport interface TestRunReportDump {\n schemaVersion: 1;\n kind: 'test-runner';\n runId: string;\n status: TestRunReportRunStatus;\n startedAt: string;\n endedAt: string;\n durationMs: number;\n summary: TestRunReportSummary;\n metrics: TestRunReportMetrics;\n executionMetrics?: Record<string, TestRunReportMetrics>;\n projects: TestRunReportProject[];\n diagnostics?: TestRunReportDiagnostic[];\n operationLogs?: { path: string; snapshotAt: string }[];\n}\n\nexport interface TestRunReportSource {\n /** Resource/report scope that owns this Midscene Agent source. */\n scopeId: string;\n /** Absolute path identifying the Midscene Agent report. */\n sourcePath: string;\n /** Current Agent dump JSON; when present, HTML is not parsed for execution data. */\n dumpPath?: string;\n collection?: TestRunReportSnapshotCollection;\n}\n\nexport interface IndexedTestRunReportSource {\n reportId: string;\n scopeId: string;\n sourcePath: string;\n executionIds: string[];\n collection?: TestRunReportSnapshotCollection;\n}\n\nexport interface TestRunReportSourceIndex {\n sources: IndexedTestRunReportSource[];\n metrics: TestRunReportMetrics;\n executionMetrics?: Record<string, TestRunReportMetrics>;\n /** Usage from readable sources, before applying source-availability diagnostics. */\n capturedMetrics?: TestRunReportMetrics;\n diagnostics?: TestRunReportDiagnostic[];\n}\n\nexport interface AssembleTestRunReportOptions {\n outputDir: string;\n /** File or directory name without an extension. */\n reportFileName: string;\n sources: readonly TestRunReportSource[];\n buildRunnerDump(index: TestRunReportSourceIndex): TestRunReportDump;\n overwrite?: boolean;\n}\n"],"names":["TEST_RUN_REPORT_SCRIPT_TYPE"],"mappings":"AAAO,MAAMA,8BAA8B"}
@@ -33,7 +33,7 @@ class CaseRunner {
33
33
  context: this.context,
34
34
  createRunId: ()=>'standalone-case'
35
35
  });
36
- const fatalStepError = result.steps.find((step)=>'failed' === step.status && !step.continuedAfterError)?.error;
36
+ const fatalStepError = result.steps.find((step)=>'success' !== step.status && !step.continuedAfterError)?.error;
37
37
  if (fatalStepError) throw fatalStepError;
38
38
  return result;
39
39
  }
@@ -1 +1 @@
1
- {"version":3,"file":"test-runner/engine/case-runner.mjs","sources":["../../../../src/test-runner/engine/case-runner.ts"],"sourcesContent":["import type { NodeDefinition } from '../node/types';\nimport { normalizeSteps } from '../parser/normalize';\nimport type { CaseInput, CollectedCase } from '../parser/types';\nimport { NodeRegistry } from './registry';\nimport { runCollectedCase } from './run-collected-case';\nimport type { CaseRunResult, CaseRunnerOptions } from './types';\n\nexport class CaseRunner<TContext = undefined> {\n readonly registry: NodeRegistry;\n readonly context: TContext;\n\n constructor(options: CaseRunnerOptions<TContext> = {}) {\n this.registry = new NodeRegistry(options.nodes);\n this.context = options.context as TContext;\n }\n\n register<TInput, TData>(node: NodeDefinition<TInput, TData, TContext>): this {\n this.registry.register(node);\n return this;\n }\n\n async run(input: CaseInput): Promise<CaseRunResult> {\n const collectedCase: CollectedCase = {\n caseId: 'standalone-case',\n projectId: 'standalone-project',\n sourcePath: '',\n caseIndex: 0,\n definition: {\n name: input.name ?? 'case',\n tags: input.tags ?? [],\n steps: normalizeSteps(input.steps, (name) => this.registry.get(name)),\n },\n };\n const result = await runCollectedCase(collectedCase, {\n resolveNode: (name) =>\n this.registry.require(name) as NodeDefinition<any, any, TContext>,\n context: this.context,\n createRunId: () => 'standalone-case',\n });\n\n const fatalStepError = result.steps.find(\n (step) => step.status === 'failed' && !step.continuedAfterError,\n )?.error;\n if (fatalStepError) throw fatalStepError;\n return result;\n }\n}\n\nexport function createCaseRunner<TContext = undefined>(\n options: CaseRunnerOptions<TContext> = {},\n): CaseRunner<TContext> {\n return new CaseRunner(options);\n}\n"],"names":["CaseRunner","node","input","collectedCase","normalizeSteps","name","result","runCollectedCase","fatalStepError","step","options","NodeRegistry","createCaseRunner"],"mappings":";;;;;;;;;;;;;AAOO,MAAMA;IASX,SAAwBC,IAA6C,EAAQ;QAC3E,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAACA;QACvB,OAAO,IAAI;IACb;IAEA,MAAM,IAAIC,KAAgB,EAA0B;QAClD,MAAMC,gBAA+B;YACnC,QAAQ;YACR,WAAW;YACX,YAAY;YACZ,WAAW;YACX,YAAY;gBACV,MAAMD,MAAM,IAAI,IAAI;gBACpB,MAAMA,MAAM,IAAI,IAAI,EAAE;gBACtB,OAAOE,eAAeF,MAAM,KAAK,EAAE,CAACG,OAAS,IAAI,CAAC,QAAQ,CAAC,GAAG,CAACA;YACjE;QACF;QACA,MAAMC,SAAS,MAAMC,iBAAiBJ,eAAe;YACnD,aAAa,CAACE,OACZ,IAAI,CAAC,QAAQ,CAAC,OAAO,CAACA;YACxB,SAAS,IAAI,CAAC,OAAO;YACrB,aAAa,IAAM;QACrB;QAEA,MAAMG,iBAAiBF,OAAO,KAAK,CAAC,IAAI,CACtC,CAACG,OAASA,AAAgB,aAAhBA,KAAK,MAAM,IAAiB,CAACA,KAAK,mBAAmB,GAC9D;QACH,IAAID,gBAAgB,MAAMA;QAC1B,OAAOF;IACT;IAlCA,YAAYI,UAAuC,CAAC,CAAC,CAAE;QAHvD,uBAAS,YAAT;QACA,uBAAS,WAAT;QAGE,IAAI,CAAC,QAAQ,GAAG,IAAIC,aAAaD,QAAQ,KAAK;QAC9C,IAAI,CAAC,OAAO,GAAGA,QAAQ,OAAO;IAChC;AAgCF;AAEO,SAASE,iBACdF,UAAuC,CAAC,CAAC;IAEzC,OAAO,IAAIV,WAAWU;AACxB"}
1
+ {"version":3,"file":"test-runner/engine/case-runner.mjs","sources":["../../../../src/test-runner/engine/case-runner.ts"],"sourcesContent":["import type { NodeDefinition } from '../node/types';\nimport { normalizeSteps } from '../parser/normalize';\nimport type { CaseInput, CollectedCase } from '../parser/types';\nimport { NodeRegistry } from './registry';\nimport { runCollectedCase } from './run-collected-case';\nimport type { CaseRunResult, CaseRunnerOptions } from './types';\n\nexport class CaseRunner<TContext = undefined> {\n readonly registry: NodeRegistry;\n readonly context: TContext;\n\n constructor(options: CaseRunnerOptions<TContext> = {}) {\n this.registry = new NodeRegistry(options.nodes);\n this.context = options.context as TContext;\n }\n\n register<TInput, TData>(node: NodeDefinition<TInput, TData, TContext>): this {\n this.registry.register(node);\n return this;\n }\n\n async run(input: CaseInput): Promise<CaseRunResult> {\n const collectedCase: CollectedCase = {\n caseId: 'standalone-case',\n projectId: 'standalone-project',\n sourcePath: '',\n caseIndex: 0,\n definition: {\n name: input.name ?? 'case',\n tags: input.tags ?? [],\n steps: normalizeSteps(input.steps, (name) => this.registry.get(name)),\n },\n };\n const result = await runCollectedCase(collectedCase, {\n resolveNode: (name) =>\n this.registry.require(name) as NodeDefinition<any, any, TContext>,\n context: this.context,\n createRunId: () => 'standalone-case',\n });\n\n const fatalStepError = result.steps.find(\n (step) => step.status !== 'success' && !step.continuedAfterError,\n )?.error;\n if (fatalStepError) throw fatalStepError;\n return result;\n }\n}\n\nexport function createCaseRunner<TContext = undefined>(\n options: CaseRunnerOptions<TContext> = {},\n): CaseRunner<TContext> {\n return new CaseRunner(options);\n}\n"],"names":["CaseRunner","node","input","collectedCase","normalizeSteps","name","result","runCollectedCase","fatalStepError","step","options","NodeRegistry","createCaseRunner"],"mappings":";;;;;;;;;;;;;AAOO,MAAMA;IASX,SAAwBC,IAA6C,EAAQ;QAC3E,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAACA;QACvB,OAAO,IAAI;IACb;IAEA,MAAM,IAAIC,KAAgB,EAA0B;QAClD,MAAMC,gBAA+B;YACnC,QAAQ;YACR,WAAW;YACX,YAAY;YACZ,WAAW;YACX,YAAY;gBACV,MAAMD,MAAM,IAAI,IAAI;gBACpB,MAAMA,MAAM,IAAI,IAAI,EAAE;gBACtB,OAAOE,eAAeF,MAAM,KAAK,EAAE,CAACG,OAAS,IAAI,CAAC,QAAQ,CAAC,GAAG,CAACA;YACjE;QACF;QACA,MAAMC,SAAS,MAAMC,iBAAiBJ,eAAe;YACnD,aAAa,CAACE,OACZ,IAAI,CAAC,QAAQ,CAAC,OAAO,CAACA;YACxB,SAAS,IAAI,CAAC,OAAO;YACrB,aAAa,IAAM;QACrB;QAEA,MAAMG,iBAAiBF,OAAO,KAAK,CAAC,IAAI,CACtC,CAACG,OAASA,AAAgB,cAAhBA,KAAK,MAAM,IAAkB,CAACA,KAAK,mBAAmB,GAC/D;QACH,IAAID,gBAAgB,MAAMA;QAC1B,OAAOF;IACT;IAlCA,YAAYI,UAAuC,CAAC,CAAC,CAAE;QAHvD,uBAAS,YAAT;QACA,uBAAS,WAAT;QAGE,IAAI,CAAC,QAAQ,GAAG,IAAIC,aAAaD,QAAQ,KAAK;QAC9C,IAAI,CAAC,OAAO,GAAGA,QAAQ,OAAO;IAChC;AAgCF;AAEO,SAASE,iBACdF,UAAuC,CAAC,CAAC;IAEzC,OAAO,IAAIV,WAAWU;AACxB"}
@@ -1,12 +1,21 @@
1
1
  import { getDebug } from "@midscene/shared/logger";
2
2
  import { uuid } from "@midscene/shared/utils";
3
3
  import { NodeScopeTeardownError, WorkflowLifecycleError } from "../errors.mjs";
4
+ import { buildTestRunReportPlan } from "../reporting/test-plan.mjs";
4
5
  import { executeStep } from "./execute-step.mjs";
5
6
  import { WorkflowExecutionFailure, asExecutionError } from "./execution-failure.mjs";
6
- import { ResourceCleanupDeferredError, cleanupScopeResources, createResourceScope, recoverCancelledScopeResources } from "./resource-operations.mjs";
7
+ import { ResourceCleanupDeferredError, cleanupScopeResources, createResourceScope, getPendingScopeOperations, getResourceCleanupSnapshot, recoverCancelledScopeResources, setResourceOperationIdentity } from "./resource-operations.mjs";
8
+ import { phaseResult } from "./result-metadata.mjs";
7
9
  import { reportPathsFromTeardown } from "./scope-teardown.mjs";
8
10
  function createDocumentRuntime(document, options) {
9
11
  const documentRunId = options.createDocumentRunId?.() ?? uuid();
12
+ const plan = options.plan ?? buildTestRunReportPlan(document, {
13
+ defaultTimeoutMs: options.defaultTimeoutMs,
14
+ retry: {
15
+ scope: 'case',
16
+ count: options.project?.retry ?? 0
17
+ }
18
+ });
10
19
  const startedAt = new Date();
11
20
  const beforeAll = [];
12
21
  const afterAll = [];
@@ -22,6 +31,9 @@ function createDocumentRuntime(document, options) {
22
31
  let context = options.projectContext;
23
32
  const resourceScope = createResourceScope(options.signal);
24
33
  const signal = resourceScope.signal;
34
+ setResourceOperationIdentity(signal, {
35
+ scopeId: documentRunId
36
+ });
25
37
  const nodeTeardownStack = [];
26
38
  let acceptingNodeTeardowns = true;
27
39
  let started = false;
@@ -29,12 +41,20 @@ function createDocumentRuntime(document, options) {
29
41
  let finishPromise;
30
42
  const reportPaths = new Set();
31
43
  const reportSources = [];
44
+ const capturedSources = new Map();
32
45
  const executionErrors = [];
33
46
  const hostErrors = [];
34
47
  let acceptingSetupTeardowns = false;
35
48
  const createResult = (teardownErrors = [])=>{
36
49
  const endedAt = new Date();
50
+ const cleanup = getResourceCleanupSnapshot(signal);
51
+ const pendingOperations = getPendingScopeOperations(signal);
52
+ const phaseResults = {
53
+ beforeAll: phaseResult(document.lifecycle.beforeAll.length, beforeAll),
54
+ afterAll: phaseResult(document.lifecycle.afterAll.length, afterAll)
55
+ };
37
56
  return {
57
+ plan,
38
58
  documentId: document.documentId,
39
59
  documentRunId,
40
60
  ...void 0 === options.documentAttemptIndex ? {} : {
@@ -43,7 +63,13 @@ function createDocumentRuntime(document, options) {
43
63
  projectId: document.projectId,
44
64
  projectName: project.name,
45
65
  sourcePath: document.sourcePath,
46
- status: executionErrors.length > 0 || hostErrors.length > 0 || beforeAll.some((step)=>'failed' === step.status) || afterAll.some((step)=>'failed' === step.status) || teardownErrors.length > 0 ? 'failed' : 'success',
66
+ status: executionErrors.length > 0 || hostErrors.length > 0 || beforeAll.some((step)=>'success' !== step.status) || afterAll.some((step)=>'success' !== step.status) || 'pending' === cleanup.status || 'failed' === cleanup.status || pendingOperations.length > 0 || teardownErrors.length > 0 ? 'failed' : 'success',
67
+ executionStatus: Object.values(phaseResults).some((phase)=>'failed' === phase.status) ? 'failed' : Object.values(phaseResults).some((phase)=>'not-run' === phase.status) || hostErrors.some(({ phase })=>'setup' === phase) || signal.aborted && !beforeAll.length && !afterAll.length ? 'not-run' : 'success',
68
+ phases: phaseResults,
69
+ cleanup,
70
+ ...pendingOperations.length ? {
71
+ pendingOperations
72
+ } : {},
47
73
  startedAt: startedAt.toISOString(),
48
74
  endedAt: endedAt.toISOString(),
49
75
  durationMs: Math.max(0, endedAt.getTime() - startedAt.getTime()),
@@ -54,7 +80,7 @@ function createDocumentRuntime(document, options) {
54
80
  ...afterAll
55
81
  ],
56
82
  ...executionErrors.length ? {
57
- executionErrors: executionErrors.map(asExecutionError)
83
+ executionErrors: executionErrors.filter((error)=>!hostErrors.some((host)=>host.error === error)).map(asExecutionError)
58
84
  } : {},
59
85
  ...hostErrors.length ? {
60
86
  hostErrors: [
@@ -71,8 +97,9 @@ function createDocumentRuntime(document, options) {
71
97
  ...reportPaths
72
98
  ]
73
99
  } : {},
74
- ...reportSources.length ? {
100
+ ...capturedSources.size || reportSources.length ? {
75
101
  reportSources: [
102
+ ...capturedSources.values(),
76
103
  ...reportSources
77
104
  ]
78
105
  } : {}
@@ -80,7 +107,7 @@ function createDocumentRuntime(document, options) {
80
107
  };
81
108
  const runPhase = async (phase, results, executionSignal = signal)=>{
82
109
  for (const [stepIndex, step] of document.lifecycle[phase].entries()){
83
- if (stepIndex > 0) await recoverCancelledScopeResources(executionSignal);
110
+ const waitForPreviousOperationMs = stepIndex > 0 ? await recoverCancelledScopeResources(executionSignal) : 0;
84
111
  const documentContext = {
85
112
  documentId: document.documentId,
86
113
  documentRunId,
@@ -121,9 +148,11 @@ function createDocumentRuntime(document, options) {
121
148
  });
122
149
  }
123
150
  });
151
+ if (waitForPreviousOperationMs > 0) result.waitForPreviousOperationMs = waitForPreviousOperationMs;
124
152
  results.push(result);
153
+ for (const source of result.report?.sources ?? [])capturedSources.set(`${source.sourcePath}\u0000${source.dumpPath ?? ''}`, source);
125
154
  await options.onStepResult?.(stepInfo, result);
126
- if ('failed' === result.status && !result.continuedAfterError) break;
155
+ if ('success' !== result.status && !result.continuedAfterError) break;
127
156
  }
128
157
  };
129
158
  return {
@@ -132,7 +161,7 @@ function createDocumentRuntime(document, options) {
132
161
  return context;
133
162
  },
134
163
  get canRunCases () {
135
- return started && 0 === executionErrors.length && 0 === hostErrors.length && !signal.aborted && !beforeAll.some((step)=>'failed' === step.status);
164
+ return started && 0 === executionErrors.length && 0 === hostErrors.length && !signal.aborted && !beforeAll.some((step)=>'success' !== step.status);
136
165
  },
137
166
  async start () {
138
167
  if (started) throw new WorkflowLifecycleError('Workflow document runtime has already started.', {
@@ -204,11 +233,12 @@ function createDocumentRuntime(document, options) {
204
233
  for (const path of reportPathsFromTeardown(released))reportPaths.add(path);
205
234
  } catch (error) {
206
235
  if (host) {
236
+ const failure = asExecutionError(error);
207
237
  hostErrors.push({
208
238
  phase: 'cleanup',
209
- error: asExecutionError(error)
239
+ error: failure
210
240
  });
211
- executionErrors.push(error);
241
+ executionErrors.push(failure);
212
242
  cleanupExecutionErrors.push(error);
213
243
  continue;
214
244
  }
@@ -219,12 +249,12 @@ function createDocumentRuntime(document, options) {
219
249
  registrationIndex
220
250
  }));
221
251
  }
222
- if (deferredCleanup) {
252
+ {
223
253
  const errors = [
224
254
  ...cleanupExecutionErrors,
225
255
  ...teardownErrors.slice(priorTeardownErrors)
226
256
  ];
227
- if (errors.length) throw new AggregateError(errors, 'Deferred document cleanup failed');
257
+ if (errors.length) throw new AggregateError(errors, 'Document cleanup failed');
228
258
  }
229
259
  };
230
260
  try {
@@ -245,7 +275,7 @@ function createDocumentRuntime(document, options) {
245
275
  } catch (error) {
246
276
  deferredCleanup = error instanceof ResourceCleanupDeferredError;
247
277
  if (!deferredCleanup) acceptingNodeTeardowns = false;
248
- teardownErrors.push(new NodeScopeTeardownError(error, {
278
+ if (!deferredCleanup && !teardownErrors.length && !cleanupExecutionErrors.length) teardownErrors.push(new NodeScopeTeardownError(error, {
249
279
  scope: 'document',
250
280
  scopeId: documentRunId,
251
281
  node: 'resource-drain',