@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.
- package/dist/es/agent/action-readiness.mjs +6 -3
- package/dist/es/agent/action-readiness.mjs.map +1 -1
- package/dist/es/agent/agent.mjs +4 -2
- package/dist/es/agent/agent.mjs.map +1 -1
- package/dist/es/agent/execution-session.mjs.map +1 -1
- package/dist/es/agent/task-builder.mjs +35 -16
- package/dist/es/agent/task-builder.mjs.map +1 -1
- package/dist/es/agent/tasks.mjs +35 -12
- package/dist/es/agent/tasks.mjs.map +1 -1
- package/dist/es/agent/utils.mjs +1 -1
- package/dist/es/device/index.mjs +7 -6
- package/dist/es/device/index.mjs.map +1 -1
- package/dist/es/report-generator.mjs +30 -14
- package/dist/es/report-generator.mjs.map +1 -1
- package/dist/es/report-html-template.mjs +1 -1
- package/dist/es/report.mjs +121 -44
- package/dist/es/report.mjs.map +1 -1
- package/dist/es/service/index.mjs.map +1 -1
- package/dist/es/task-runner.mjs +22 -12
- package/dist/es/task-runner.mjs.map +1 -1
- package/dist/es/test-run-report.mjs.map +1 -1
- package/dist/es/test-runner/engine/case-runner.mjs +1 -1
- package/dist/es/test-runner/engine/case-runner.mjs.map +1 -1
- package/dist/es/test-runner/engine/document-runtime.mjs +42 -12
- package/dist/es/test-runner/engine/document-runtime.mjs.map +1 -1
- package/dist/es/test-runner/engine/execute-step.mjs +130 -12
- package/dist/es/test-runner/engine/execute-step.mjs.map +1 -1
- package/dist/es/test-runner/engine/index.mjs +1 -0
- package/dist/es/test-runner/engine/project-runtime.mjs +14 -5
- package/dist/es/test-runner/engine/project-runtime.mjs.map +1 -1
- package/dist/es/test-runner/engine/resource-operations.mjs +286 -35
- package/dist/es/test-runner/engine/resource-operations.mjs.map +1 -1
- package/dist/es/test-runner/engine/result-metadata.mjs +21 -0
- package/dist/es/test-runner/engine/result-metadata.mjs.map +1 -0
- package/dist/es/test-runner/engine/run-collected-case.mjs +30 -9
- package/dist/es/test-runner/engine/run-collected-case.mjs.map +1 -1
- package/dist/es/test-runner/engine/run-workflow-document.mjs +57 -10
- package/dist/es/test-runner/engine/run-workflow-document.mjs.map +1 -1
- package/dist/es/test-runner/errors.mjs +26 -13
- package/dist/es/test-runner/errors.mjs.map +1 -1
- package/dist/es/test-runner/execution-record.mjs +2 -0
- package/dist/es/test-runner/execution-record.mjs.map +1 -1
- package/dist/es/test-runner/index.mjs +3 -0
- package/dist/es/test-runner/reporting/agent-report.mjs +2 -7
- package/dist/es/test-runner/reporting/agent-report.mjs.map +1 -1
- package/dist/es/test-runner/reporting/merge-report.mjs +207 -6
- package/dist/es/test-runner/reporting/merge-report.mjs.map +1 -1
- package/dist/es/test-runner/reporting/metrics.mjs +77 -0
- package/dist/es/test-runner/reporting/metrics.mjs.map +1 -0
- package/dist/es/test-runner/reporting/sanitize.mjs +92 -16
- package/dist/es/test-runner/reporting/sanitize.mjs.map +1 -1
- package/dist/es/test-runner/reporting/test-plan.mjs +56 -0
- package/dist/es/test-runner/reporting/test-plan.mjs.map +1 -0
- package/dist/es/test-runner/reporting/test-run-report.mjs +208 -21
- package/dist/es/test-runner/reporting/test-run-report.mjs.map +1 -1
- package/dist/es/types.mjs.map +1 -1
- package/dist/es/utils.mjs +1 -1
- package/dist/es/yaml/legacy-yaml-runtime.mjs +3 -3
- package/dist/es/yaml/legacy-yaml-runtime.mjs.map +1 -1
- package/dist/lib/agent/action-readiness.js +6 -3
- package/dist/lib/agent/action-readiness.js.map +1 -1
- package/dist/lib/agent/agent.js +4 -2
- package/dist/lib/agent/agent.js.map +1 -1
- package/dist/lib/agent/execution-session.js.map +1 -1
- package/dist/lib/agent/task-builder.js +42 -23
- package/dist/lib/agent/task-builder.js.map +1 -1
- package/dist/lib/agent/tasks.js +35 -12
- package/dist/lib/agent/tasks.js.map +1 -1
- package/dist/lib/agent/utils.js +1 -1
- package/dist/lib/device/index.js +7 -6
- package/dist/lib/device/index.js.map +1 -1
- package/dist/lib/report-generator.js +30 -14
- package/dist/lib/report-generator.js.map +1 -1
- package/dist/lib/report-html-template.js +1 -1
- package/dist/lib/report.js +121 -44
- package/dist/lib/report.js.map +1 -1
- package/dist/lib/service/index.js.map +1 -1
- package/dist/lib/task-runner.js +22 -12
- package/dist/lib/task-runner.js.map +1 -1
- package/dist/lib/test-run-report.js.map +1 -1
- package/dist/lib/test-runner/engine/case-runner.js +1 -1
- package/dist/lib/test-runner/engine/case-runner.js.map +1 -1
- package/dist/lib/test-runner/engine/document-runtime.js +41 -11
- package/dist/lib/test-runner/engine/document-runtime.js.map +1 -1
- package/dist/lib/test-runner/engine/execute-step.js +131 -10
- package/dist/lib/test-runner/engine/execute-step.js.map +1 -1
- package/dist/lib/test-runner/engine/index.js +9 -2
- package/dist/lib/test-runner/engine/index.js.map +1 -1
- package/dist/lib/test-runner/engine/project-runtime.js +13 -4
- package/dist/lib/test-runner/engine/project-runtime.js.map +1 -1
- package/dist/lib/test-runner/engine/resource-operations.js +319 -38
- package/dist/lib/test-runner/engine/resource-operations.js.map +1 -1
- package/dist/lib/test-runner/engine/result-metadata.js +58 -0
- package/dist/lib/test-runner/engine/result-metadata.js.map +1 -0
- package/dist/lib/test-runner/engine/run-collected-case.js +29 -8
- package/dist/lib/test-runner/engine/run-collected-case.js.map +1 -1
- package/dist/lib/test-runner/engine/run-workflow-document.js +57 -10
- package/dist/lib/test-runner/engine/run-workflow-document.js.map +1 -1
- package/dist/lib/test-runner/errors.js +42 -23
- package/dist/lib/test-runner/errors.js.map +1 -1
- package/dist/lib/test-runner/execution-record.js +2 -0
- package/dist/lib/test-runner/execution-record.js.map +1 -1
- package/dist/lib/test-runner/index.js +88 -16
- package/dist/lib/test-runner/index.js.map +1 -1
- package/dist/lib/test-runner/node/index.js +2 -2
- package/dist/lib/test-runner/reporting/agent-report.js +2 -7
- package/dist/lib/test-runner/reporting/agent-report.js.map +1 -1
- package/dist/lib/test-runner/reporting/merge-report.js +207 -6
- package/dist/lib/test-runner/reporting/merge-report.js.map +1 -1
- package/dist/lib/test-runner/reporting/metrics.js +114 -0
- package/dist/lib/test-runner/reporting/metrics.js.map +1 -0
- package/dist/lib/test-runner/reporting/sanitize.js +94 -15
- package/dist/lib/test-runner/reporting/sanitize.js.map +1 -1
- package/dist/lib/test-runner/reporting/test-plan.js +90 -0
- package/dist/lib/test-runner/reporting/test-plan.js.map +1 -0
- package/dist/lib/test-runner/reporting/test-run-report.js +208 -21
- package/dist/lib/test-runner/reporting/test-run-report.js.map +1 -1
- package/dist/lib/types.js.map +1 -1
- package/dist/lib/utils.js +1 -1
- package/dist/lib/yaml/legacy-yaml-runtime.js +2 -2
- package/dist/lib/yaml/legacy-yaml-runtime.js.map +1 -1
- package/dist/types/agent/action-readiness.d.ts +1 -1
- package/dist/types/agent/action-readiness.d.ts.map +1 -1
- package/dist/types/agent/agent.d.ts +1 -1
- package/dist/types/agent/agent.d.ts.map +1 -1
- package/dist/types/agent/execution-session.d.ts +1 -1
- package/dist/types/agent/execution-session.d.ts.map +1 -1
- package/dist/types/agent/task-builder.d.ts +4 -2
- package/dist/types/agent/task-builder.d.ts.map +1 -1
- package/dist/types/agent/tasks.d.ts +3 -0
- package/dist/types/agent/tasks.d.ts.map +1 -1
- package/dist/types/device/index.d.ts +3 -3
- package/dist/types/device/index.d.ts.map +1 -1
- package/dist/types/report-generator.d.ts +4 -2
- package/dist/types/report-generator.d.ts.map +1 -1
- package/dist/types/report.d.ts.map +1 -1
- package/dist/types/service/index.d.ts +2 -2
- package/dist/types/service/index.d.ts.map +1 -1
- package/dist/types/task-runner.d.ts +1 -1
- package/dist/types/task-runner.d.ts.map +1 -1
- package/dist/types/test-run-report.d.ts +157 -8
- package/dist/types/test-run-report.d.ts.map +1 -1
- package/dist/types/test-runner/engine/document-runtime.d.ts.map +1 -1
- package/dist/types/test-runner/engine/execute-step.d.ts +1 -0
- package/dist/types/test-runner/engine/execute-step.d.ts.map +1 -1
- package/dist/types/test-runner/engine/index.d.ts +1 -0
- package/dist/types/test-runner/engine/index.d.ts.map +1 -1
- package/dist/types/test-runner/engine/project-runtime.d.ts.map +1 -1
- package/dist/types/test-runner/engine/resource-operations.d.ts +41 -3
- package/dist/types/test-runner/engine/resource-operations.d.ts.map +1 -1
- package/dist/types/test-runner/engine/result-metadata.d.ts +18 -0
- package/dist/types/test-runner/engine/result-metadata.d.ts.map +1 -0
- package/dist/types/test-runner/engine/run-collected-case.d.ts.map +1 -1
- package/dist/types/test-runner/engine/run-workflow-document.d.ts.map +1 -1
- package/dist/types/test-runner/engine/types.d.ts +47 -8
- package/dist/types/test-runner/engine/types.d.ts.map +1 -1
- package/dist/types/test-runner/errors.d.ts +7 -0
- package/dist/types/test-runner/errors.d.ts.map +1 -1
- package/dist/types/test-runner/execution-record.d.ts.map +1 -1
- package/dist/types/test-runner/index.d.ts +2 -0
- package/dist/types/test-runner/index.d.ts.map +1 -1
- package/dist/types/test-runner/node/types.d.ts +9 -1
- package/dist/types/test-runner/node/types.d.ts.map +1 -1
- package/dist/types/test-runner/parser/types.d.ts +5 -0
- package/dist/types/test-runner/parser/types.d.ts.map +1 -1
- package/dist/types/test-runner/reporting/agent-report.d.ts.map +1 -1
- package/dist/types/test-runner/reporting/merge-report.d.ts +1 -1
- package/dist/types/test-runner/reporting/merge-report.d.ts.map +1 -1
- package/dist/types/test-runner/reporting/metrics.d.ts +7 -0
- package/dist/types/test-runner/reporting/metrics.d.ts.map +1 -0
- package/dist/types/test-runner/reporting/sanitize.d.ts +1 -0
- package/dist/types/test-runner/reporting/sanitize.d.ts.map +1 -1
- package/dist/types/test-runner/reporting/test-plan.d.ts +8 -0
- package/dist/types/test-runner/reporting/test-plan.d.ts.map +1 -0
- package/dist/types/test-runner/reporting/test-run-report.d.ts.map +1 -1
- package/dist/types/test-runner/reporting/types.d.ts +6 -1
- package/dist/types/test-runner/reporting/types.d.ts.map +1 -1
- package/dist/types/types.d.ts +3 -1
- package/dist/types/types.d.ts.map +1 -1
- 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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"}
|
package/dist/es/task-runner.mjs
CHANGED
|
@@ -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
|
-
|
|
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
|
|
213
|
-
|
|
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)=>'
|
|
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
|
|
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)=>'
|
|
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
|
-
|
|
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 ('
|
|
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)=>'
|
|
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:
|
|
239
|
+
error: failure
|
|
210
240
|
});
|
|
211
|
-
executionErrors.push(
|
|
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
|
-
|
|
252
|
+
{
|
|
223
253
|
const errors = [
|
|
224
254
|
...cleanupExecutionErrors,
|
|
225
255
|
...teardownErrors.slice(priorTeardownErrors)
|
|
226
256
|
];
|
|
227
|
-
if (errors.length) throw new AggregateError(errors, '
|
|
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',
|