@midscene/test 1.12.4-beta-20260903111111.0 → 1.12.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/dist/es/android/index.mjs +50 -106
  2. package/dist/es/android/index.mjs.map +1 -1
  3. package/dist/es/cli/index.mjs +99 -149
  4. package/dist/es/cli/index.mjs.map +1 -1
  5. package/dist/es/harmony/index.mjs +119 -0
  6. package/dist/es/harmony/index.mjs.map +1 -0
  7. package/dist/es/index.mjs +36 -87
  8. package/dist/es/index.mjs.map +1 -1
  9. package/dist/es/ios/index.mjs +51 -101
  10. package/dist/es/ios/index.mjs.map +1 -1
  11. package/dist/es/midscene/index.mjs +37 -235
  12. package/dist/es/midscene/index.mjs.map +1 -1
  13. package/dist/es/playwright/index.mjs +14 -10
  14. package/dist/es/playwright/index.mjs.map +1 -1
  15. package/dist/es/test-cli.mjs +102 -150
  16. package/dist/es/test-cli.mjs.map +1 -1
  17. package/dist/lib/android/index.js +151 -178
  18. package/dist/lib/android/index.js.map +1 -1
  19. package/dist/lib/cli/index.js +99 -149
  20. package/dist/lib/cli/index.js.map +1 -1
  21. package/dist/lib/harmony/index.js +188 -0
  22. package/dist/lib/harmony/index.js.map +1 -0
  23. package/dist/lib/index.js +37 -100
  24. package/dist/lib/index.js.map +1 -1
  25. package/dist/lib/ios/index.js +152 -173
  26. package/dist/lib/ios/index.js.map +1 -1
  27. package/dist/lib/midscene/index.js +264 -412
  28. package/dist/lib/midscene/index.js.map +1 -1
  29. package/dist/lib/playwright/index.js +14 -10
  30. package/dist/lib/playwright/index.js.map +1 -1
  31. package/dist/lib/test-cli.js +102 -150
  32. package/dist/lib/test-cli.js.map +1 -1
  33. package/dist/types/android/index.d.ts +27 -101
  34. package/dist/types/cli/index.d.ts +9 -32
  35. package/dist/types/harmony/index.d.ts +124 -0
  36. package/dist/types/index.d.ts +18 -45
  37. package/dist/types/ios/index.d.ts +28 -107
  38. package/dist/types/midscene/index.d.ts +54 -153
  39. package/dist/types/playwright/index.d.ts +9 -63
  40. package/package.json +25 -6
@@ -0,0 +1,119 @@
1
+ import { harmonyAgentTestRunnerNodeDefinitions, launchInputSchema, runHdcShellInputSchema, terminateInputSchema } from "@midscene/harmony/test-runner";
2
+ import "@midscene/core/agent/test-runner";
3
+ import { z } from "zod/v4";
4
+ function _define_property(obj, key, value) {
5
+ if (key in obj) Object.defineProperty(obj, key, {
6
+ value: value,
7
+ enumerable: true,
8
+ configurable: true,
9
+ writable: true
10
+ });
11
+ else obj[key] = value;
12
+ return obj;
13
+ }
14
+ class WorkflowError extends Error {
15
+ toJSON() {
16
+ return {
17
+ name: this.name,
18
+ message: this.message,
19
+ code: this.code,
20
+ ...void 0 === this.details ? {} : {
21
+ details: this.details
22
+ }
23
+ };
24
+ }
25
+ constructor(message, options = {}){
26
+ super(message, {
27
+ cause: options.cause
28
+ }), _define_property(this, "code", void 0), _define_property(this, "details", void 0);
29
+ this.name = new.target.name;
30
+ this.code = options.code ?? 'WORKFLOW_ERROR';
31
+ this.details = options.details;
32
+ }
33
+ }
34
+ class errors_NodeDefinitionError extends WorkflowError {
35
+ constructor(message, details){
36
+ super(message, {
37
+ code: 'NODE_DEFINITION_ERROR',
38
+ details
39
+ });
40
+ }
41
+ }
42
+ const validateOptionalText = (value, field, node)=>{
43
+ if (void 0 !== value && ('string' != typeof value || 0 === value.trim().length)) throw new errors_NodeDefinitionError(`Node "${node}" ${field} must be a non-empty string.`, {
44
+ node,
45
+ field
46
+ });
47
+ };
48
+ const validateInputSchema = (schema, node)=>{
49
+ if (void 0 === schema) return;
50
+ if (!(schema instanceof z.ZodObject)) throw new errors_NodeDefinitionError(`Node "${node}" inputSchema must be a Zod object schema.`, {
51
+ node,
52
+ field: 'inputSchema'
53
+ });
54
+ if ('$' in schema.shape) throw new errors_NodeDefinitionError(`Node "${node}" inputSchema must not declare "$" as an input property.`, {
55
+ node,
56
+ field: 'inputSchema.$'
57
+ });
58
+ };
59
+ const validateDefinition = (options)=>{
60
+ if (!options || 'object' != typeof options) throw new errors_NodeDefinitionError('Node definition must be an object.');
61
+ if ('string' != typeof options.name || 0 === options.name.trim().length) throw new errors_NodeDefinitionError('Node name must be a non-empty string.');
62
+ validateOptionalText(options.title, 'title', options.name);
63
+ validateOptionalText(options.description, "description", options.name);
64
+ if (void 0 !== options.stringInputKey && false !== options.stringInputKey && ('string' != typeof options.stringInputKey || 0 === options.stringInputKey.trim().length)) throw new errors_NodeDefinitionError(`Node "${options.name}" stringInputKey must be a non-empty string or false.`, {
65
+ node: options.name,
66
+ field: 'stringInputKey'
67
+ });
68
+ validateInputSchema(options.inputSchema, options.name);
69
+ if ('string' == typeof options.stringInputKey && options.inputSchema instanceof z.ZodObject && !(options.stringInputKey in options.inputSchema.shape)) throw new errors_NodeDefinitionError(`Node "${options.name}" stringInputKey must reference a field in inputSchema.`, {
70
+ node: options.name,
71
+ field: 'stringInputKey'
72
+ });
73
+ if ('function' != typeof options.execute) throw new errors_NodeDefinitionError(`Node "${options.name}" must provide an execute function.`, {
74
+ node: options.name
75
+ });
76
+ };
77
+ function define_node_defineNode(options) {
78
+ validateDefinition(options);
79
+ return options;
80
+ }
81
+ const nonBlankPrompt = (description)=>z.string().regex(/\S/, 'prompt must contain a non-whitespace character').describe(description);
82
+ z.strictObject({
83
+ duration: z.number().positive().describe('How long to wait.'),
84
+ unit: z["enum"]([
85
+ 'ms',
86
+ 's',
87
+ 'min'
88
+ ]).default('ms').describe('Duration unit: milliseconds, seconds, or minutes.')
89
+ });
90
+ z.strictObject({
91
+ prompt: nonBlankPrompt('A self-contained task, including allowed tools and success conditions.')
92
+ });
93
+ const createAgentTestRunnerNodes = (definitions, getAgent)=>definitions.map((definition)=>define_node_defineNode({
94
+ name: definition.name,
95
+ ...void 0 === definition.title ? {} : {
96
+ title: definition.title
97
+ },
98
+ ...void 0 === definition.description ? {} : {
99
+ description: definition.description
100
+ },
101
+ ...void 0 === definition.stringInputKey ? {} : {
102
+ stringInputKey: definition.stringInputKey
103
+ },
104
+ inputSchema: definition.inputSchema,
105
+ async execute (ctx) {
106
+ const agent = await getAgent(ctx);
107
+ return definition.execute(agent, ctx.input, {
108
+ signal: ctx.signal
109
+ });
110
+ }
111
+ }));
112
+ function createHarmonyNodes(options) {
113
+ if (!options || 'object' != typeof options) throw new errors_NodeDefinitionError('createHarmonyNodes() options must be an object.');
114
+ if ('function' != typeof options.getAgent) throw new errors_NodeDefinitionError('createHarmonyNodes() requires getAgent().');
115
+ return createAgentTestRunnerNodes(harmonyAgentTestRunnerNodeDefinitions, options.getAgent);
116
+ }
117
+ export { createHarmonyNodes, launchInputSchema, runHdcShellInputSchema, terminateInputSchema };
118
+
119
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"harmony/index.mjs","sources":["../../../src/errors.ts","../../../src/node/define-node.ts","../../../src/midscene/index.ts","../../../src/harmony/index.ts"],"sourcesContent":["import type { z } from 'zod/v4';\n\nexport interface WorkflowErrorOptions {\n code?: string;\n details?: unknown;\n cause?: unknown;\n}\n\nexport class WorkflowError extends Error {\n readonly code: string;\n readonly details?: unknown;\n\n constructor(message: string, options: WorkflowErrorOptions = {}) {\n super(message, { cause: options.cause });\n this.name = new.target.name;\n this.code = options.code ?? 'WORKFLOW_ERROR';\n this.details = options.details;\n }\n\n toJSON(): Record<string, unknown> {\n return {\n name: this.name,\n message: this.message,\n code: this.code,\n ...(this.details === undefined ? {} : { details: this.details }),\n };\n }\n}\n\nexport class WorkflowParseError extends WorkflowError {\n constructor(message: string, details?: unknown, cause?: unknown) {\n super(message, { code: 'WORKFLOW_PARSE_ERROR', details, cause });\n }\n}\n\nexport class NodeDefinitionError extends WorkflowError {\n constructor(message: string, details?: unknown) {\n super(message, { code: 'NODE_DEFINITION_ERROR', details });\n }\n}\n\nexport class DuplicateNodeError extends WorkflowError {\n readonly node: string;\n\n constructor(node: string) {\n super(`Node \"${node}\" is already registered.`, {\n code: 'DUPLICATE_NODE',\n details: { node },\n });\n this.node = node;\n }\n}\n\nexport class NodeNotFoundError extends WorkflowError {\n readonly node: string;\n\n constructor(node: string) {\n super(`Node \"${node}\" is not registered.`, {\n code: 'NODE_NOT_FOUND',\n details: { node },\n });\n this.node = node;\n }\n}\n\nexport class NodeInputValidationError extends WorkflowError {\n constructor(message: string, details?: unknown) {\n super(message, { code: 'NODE_INPUT_VALIDATION_ERROR', details });\n }\n\n static fromZod(node: string, error: z.ZodError): NodeInputValidationError {\n const issues = error.issues.map((issue) => ({\n code: issue.code,\n path: issue.path.map(String).join('.'),\n message: issue.message,\n }));\n const firstIssue = issues[0];\n const path = firstIssue?.path || '<root>';\n const message = firstIssue?.message ?? 'invalid input';\n return new NodeInputValidationError(\n `Node \"${node}\" input validation failed at \"${path}\": ${message}`,\n { node, issues },\n );\n }\n}\n\nexport class StepTimeoutError extends WorkflowError {\n readonly timeoutMs: number;\n readonly node?: string;\n\n constructor(timeoutMs: number, node?: string) {\n super(\n node\n ? `Node \"${node}\" timed out after ${timeoutMs}ms.`\n : `Step timed out after ${timeoutMs}ms.`,\n {\n code: 'STEP_TIMEOUT',\n details: { timeoutMs, ...(node === undefined ? {} : { node }) },\n },\n );\n this.timeoutMs = timeoutMs;\n this.node = node;\n }\n}\n\nexport class NodeExecutionError extends WorkflowError {\n readonly node: string;\n\n constructor(node: string, cause: unknown) {\n const causeMessage =\n cause instanceof Error ? cause.message : String(cause ?? 'Unknown error');\n super(`Node \"${node}\" failed: ${causeMessage}`, {\n code: 'NODE_EXECUTION_ERROR',\n details: { node },\n cause,\n });\n this.node = node;\n }\n}\n\nexport class WorkflowLifecycleError extends WorkflowError {\n constructor(message: string, details?: unknown) {\n super(message, { code: 'WORKFLOW_LIFECYCLE_ERROR', details });\n }\n}\n\nexport class ProjectSetupError extends WorkflowError {\n constructor(cause: unknown, details: { projectName: string }) {\n const causeMessage =\n cause instanceof Error ? cause.message : String(cause ?? 'Unknown error');\n super(`Project \"${details.projectName}\" setup failed: ${causeMessage}`, {\n code: 'PROJECT_SETUP_ERROR',\n details,\n cause,\n });\n }\n}\n\nexport class ProjectTeardownError extends WorkflowError {\n constructor(\n cause: unknown,\n details: { projectName: string; registrationIndex: number },\n ) {\n const causeMessage =\n cause instanceof Error ? cause.message : String(cause ?? 'Unknown error');\n super(`Project \"${details.projectName}\" teardown failed: ${causeMessage}`, {\n code: 'PROJECT_TEARDOWN_ERROR',\n details,\n cause,\n });\n }\n}\n\nexport class NodeScopeTeardownError extends WorkflowError {\n constructor(\n cause: unknown,\n details: {\n scope: 'case' | 'document';\n scopeId: string;\n node: string;\n registrationIndex: number;\n },\n ) {\n const causeMessage =\n cause instanceof Error ? cause.message : String(cause ?? 'Unknown error');\n super(\n `${details.scope === 'case' ? 'Case attempt' : 'Workflow document'} node teardown failed for \"${details.node}\": ${causeMessage}`,\n { code: 'NODE_SCOPE_TEARDOWN_ERROR', details, cause },\n );\n }\n}\n\nexport class FatalDeviceError extends WorkflowError {\n constructor(message: string, cause?: unknown) {\n super(message, { code: 'FATAL_DEVICE_ERROR', cause });\n }\n}\n\nexport const isFatalDeviceError = (error: unknown): boolean => {\n if (error instanceof FatalDeviceError) return true;\n if (error instanceof WorkflowError && error.code === 'FATAL_DEVICE_ERROR') {\n return true;\n }\n if (\n error instanceof Error &&\n /device offline|device not found|(?:adb|bdc|device) connection (?:was )?closed/i.test(\n error.message,\n )\n ) {\n return true;\n }\n return error instanceof Error && error.cause !== undefined\n ? isFatalDeviceError(error.cause)\n : false;\n};\n\nexport class CaseExecutionError extends WorkflowError {\n readonly result: import('./engine/types').CaseRunResult;\n\n constructor(result: import('./engine/types').CaseRunResult) {\n super(`Case \"${result.name}\" failed.`, {\n code: 'CASE_EXECUTION_FAILED',\n details: { caseId: result.caseId, runId: result.runId },\n });\n this.result = result;\n }\n}\n\nexport class WorkflowDocumentExecutionError extends WorkflowError {\n readonly result: import('./engine/types').WorkflowDocumentRunResult;\n\n constructor(result: import('./engine/types').WorkflowDocumentRunResult) {\n super(`Workflow document \"${result.sourcePath}\" failed.`, {\n code: 'WORKFLOW_DOCUMENT_EXECUTION_FAILED',\n details: {\n documentId: result.documentId,\n documentRunId: result.documentRunId,\n },\n });\n this.result = result;\n }\n}\n\nexport function normalizeNodeExecutionError(\n error: unknown,\n node: string,\n): WorkflowError {\n return error instanceof WorkflowError\n ? error\n : new NodeExecutionError(node, error);\n}\n","import { z } from 'zod/v4';\nimport { NodeDefinitionError } from '../errors';\nimport type {\n DefineNodeOptions,\n DefineNodeWithSchemaOptions,\n NodeDefinition,\n NodeDefinitionWithSchema,\n NodeInputSchema,\n} from './types';\n\nconst validateOptionalText = (\n value: unknown,\n field: 'title' | 'description',\n node: string,\n): void => {\n if (\n value !== undefined &&\n (typeof value !== 'string' || value.trim().length === 0)\n ) {\n throw new NodeDefinitionError(\n `Node \"${node}\" ${field} must be a non-empty string.`,\n { node, field },\n );\n }\n};\n\nconst validateInputSchema = (schema: unknown, node: string): void => {\n if (schema === undefined) return;\n if (!(schema instanceof z.ZodObject)) {\n throw new NodeDefinitionError(\n `Node \"${node}\" inputSchema must be a Zod object schema.`,\n { node, field: 'inputSchema' },\n );\n }\n if ('$' in schema.shape) {\n throw new NodeDefinitionError(\n `Node \"${node}\" inputSchema must not declare \"$\" as an input property.`,\n { node, field: 'inputSchema.$' },\n );\n }\n};\n\nconst validateDefinition = (options: {\n name: string;\n title?: unknown;\n description?: unknown;\n stringInputKey?: unknown;\n inputSchema?: unknown;\n execute: unknown;\n}): void => {\n if (!options || typeof options !== 'object') {\n throw new NodeDefinitionError('Node definition must be an object.');\n }\n\n if (typeof options.name !== 'string' || options.name.trim().length === 0) {\n throw new NodeDefinitionError('Node name must be a non-empty string.');\n }\n\n validateOptionalText(options.title, 'title', options.name);\n validateOptionalText(options.description, 'description', options.name);\n if (\n options.stringInputKey !== undefined &&\n options.stringInputKey !== false &&\n (typeof options.stringInputKey !== 'string' ||\n options.stringInputKey.trim().length === 0)\n ) {\n throw new NodeDefinitionError(\n `Node \"${options.name}\" stringInputKey must be a non-empty string or false.`,\n { node: options.name, field: 'stringInputKey' },\n );\n }\n validateInputSchema(options.inputSchema, options.name);\n\n if (\n typeof options.stringInputKey === 'string' &&\n options.inputSchema instanceof z.ZodObject &&\n !(options.stringInputKey in options.inputSchema.shape)\n ) {\n throw new NodeDefinitionError(\n `Node \"${options.name}\" stringInputKey must reference a field in inputSchema.`,\n { node: options.name, field: 'stringInputKey' },\n );\n }\n\n if (typeof options.execute !== 'function') {\n throw new NodeDefinitionError(\n `Node \"${options.name}\" must provide an execute function.`,\n { node: options.name },\n );\n }\n};\n\nexport function defineNode<\n TSchema extends NodeInputSchema,\n TData = unknown,\n TContext = unknown,\n>(\n options: DefineNodeWithSchemaOptions<TSchema, TData, TContext>,\n): NodeDefinitionWithSchema<TSchema, TData, TContext>;\n\nexport function defineNode<\n TInput = unknown,\n TData = unknown,\n TContext = unknown,\n>(\n options: DefineNodeOptions<TInput, TData, TContext>,\n): NodeDefinition<TInput, TData, TContext>;\n\nexport function defineNode(\n options: DefineNodeOptions<any, any, any>,\n): NodeDefinition<any, any, any> {\n validateDefinition(options);\n return options;\n}\n","import {\n type AgentTestRunnerNodeDefinition,\n type AgentTestRunnerNodeProvider,\n type CommonAgentTestRunnerApi,\n aiActInputSchema,\n aiActOptionsInputSchema,\n aiAssertInputSchema,\n aiAssertOptionsInputSchema,\n aiTapInputSchema,\n insightInputSchema,\n insightOptionsInputSchema,\n locateOptionsInputSchema,\n promptImageInputSchema,\n recordToReportInputSchema,\n recordToReportOptionsInputSchema,\n reportScreenshotInputSchema,\n structuredUserPromptInputSchema,\n userPromptInputSchema,\n} from '@midscene/core/agent/test-runner';\nimport { z } from 'zod/v4';\nimport type { Awaitable } from '../engine/types';\nimport { NodeDefinitionError, NodeExecutionError } from '../errors';\nimport { defineNode } from '../node/define-node';\nimport type {\n NodeDefinition,\n NodeExecutionContext,\n NodeResult,\n} from '../node/types';\n\nexport type MidsceneUIAgent = CommonAgentTestRunnerApi;\n\nexport {\n aiActInputSchema,\n aiActOptionsInputSchema,\n aiAssertInputSchema,\n aiAssertOptionsInputSchema,\n aiTapInputSchema,\n insightInputSchema,\n insightOptionsInputSchema,\n locateOptionsInputSchema,\n promptImageInputSchema,\n recordToReportInputSchema,\n recordToReportOptionsInputSchema,\n reportScreenshotInputSchema,\n structuredUserPromptInputSchema,\n userPromptInputSchema,\n};\n\nexport interface AgentProvider<TContext> {\n getAgent(\n runId: string,\n ctx: NodeExecutionContext<unknown, TContext>,\n ): Awaitable<MidsceneUIAgent>;\n // biome-ignore lint/suspicious/noConfusingVoidType: providers without a report intentionally return void.\n releaseAgent?(runId: string): Awaitable<AgentReleaseResult | void>;\n dispose?(): Awaitable<void>;\n}\n\nexport interface AgentReleaseResult {\n /** Absolute path to the finalized report for this Agent scope. */\n reportPath?: string;\n}\n\nexport interface AgentExecutorInput<TContext> {\n prompt: string;\n context: TContext;\n signal: AbortSignal;\n execution:\n | { scope: 'case'; runId: string }\n | { scope: 'document'; runId: string };\n}\n\nexport interface AgentExecutor<TContext> {\n // biome-ignore lint/suspicious/noConfusingVoidType: executors may perform side effects without returning a summary.\n execute(input: AgentExecutorInput<TContext>): Awaitable<NodeResult | void>;\n}\n\nconst nonBlankPrompt = (description: string) =>\n z\n .string()\n .regex(/\\S/, 'prompt must contain a non-whitespace character')\n .describe(description);\n\nexport const waitInputSchema = z.strictObject({\n duration: z.number().positive().describe('How long to wait.'),\n unit: z\n .enum(['ms', 's', 'min'])\n .default('ms')\n .describe('Duration unit: milliseconds, seconds, or minutes.'),\n});\n\nexport const agentInputSchema = z.strictObject({\n prompt: nonBlankPrompt(\n 'A self-contained task, including allowed tools and success conditions.',\n ),\n});\n\nexport type AiActNodeInput = z.infer<typeof aiActInputSchema>;\nexport type AiAssertNodeInput = z.infer<typeof aiAssertInputSchema>;\nexport type AiTapNodeInput = z.infer<typeof aiTapInputSchema>;\nexport type InsightNodeInput = z.infer<typeof insightInputSchema>;\nexport type RecordToReportNodeInput = z.infer<typeof recordToReportInputSchema>;\nexport type WaitNodeInput = z.infer<typeof waitInputSchema>;\nexport type AgentNodeInput = z.infer<typeof agentInputSchema>;\n\nexport interface CreateMidsceneNodesOptions<TContext> {\n getAgent?(\n ctx: NodeExecutionContext<unknown, TContext>,\n ): Awaitable<MidsceneUIAgent>;\n agentProvider?: AgentProvider<TContext>;\n /** Agent class that declares the Agent-backed Nodes to register. */\n agentClass: AgentTestRunnerNodeProvider;\n agentExecutor?: AgentExecutor<TContext>;\n}\n\nconst waitFor = async (durationMs: number, signal: AbortSignal) => {\n if (signal.aborted) {\n throw signal.reason ?? new Error('Wait aborted.');\n }\n await new Promise<void>((resolve, reject) => {\n const timeout = setTimeout(() => {\n signal.removeEventListener('abort', abort);\n resolve();\n }, durationMs);\n const abort = () => {\n clearTimeout(timeout);\n reject(signal.reason ?? new Error('Wait aborted.'));\n };\n signal.addEventListener('abort', abort, { once: true });\n });\n};\n\n/** Adapt Agent-owned descriptions to executable Test Runner Nodes. */\nexport const createAgentTestRunnerNodes = <TContext>(\n definitions: readonly AgentTestRunnerNodeDefinition[],\n getAgent: (\n ctx: NodeExecutionContext<unknown, TContext>,\n ) => Awaitable<unknown>,\n): readonly NodeDefinition<any, any, TContext>[] =>\n definitions.map((definition) =>\n defineNode({\n name: definition.name,\n ...(definition.title === undefined ? {} : { title: definition.title }),\n ...(definition.description === undefined\n ? {}\n : { description: definition.description }),\n ...(definition.stringInputKey === undefined\n ? {}\n : { stringInputKey: definition.stringInputKey }),\n inputSchema: definition.inputSchema,\n async execute(ctx) {\n const agent = await getAgent(ctx);\n return definition.execute(agent, ctx.input, {\n signal: ctx.signal,\n });\n },\n }),\n );\n\nexport function createMidsceneNodes<TContext>(\n options: CreateMidsceneNodesOptions<TContext>,\n): readonly NodeDefinition<any, any, TContext>[] {\n if (!options || typeof options !== 'object') {\n throw new NodeDefinitionError(\n 'createMidsceneNodes() options must be an object.',\n );\n }\n if (\n typeof options.agentProvider?.getAgent !== 'function' &&\n typeof options.getAgent !== 'function'\n ) {\n throw new NodeDefinitionError(\n 'createMidsceneNodes() requires getAgent or agentProvider.getAgent.',\n );\n }\n if (\n !options.agentClass ||\n typeof options.agentClass.getTestRunnerNodeDefinitions !== 'function'\n ) {\n throw new NodeDefinitionError(\n 'createMidsceneNodes() requires agentClass.getTestRunnerNodeDefinitions().',\n );\n }\n\n const registeredAgentScopes = new Set<string>();\n const getExecutionId = (ctx: NodeExecutionContext<unknown, TContext>) =>\n ctx.scope === 'case' ? ctx.case.runId : ctx.document.documentRunId;\n const getAgent = async (\n ctx: NodeExecutionContext<unknown, TContext>,\n ): Promise<MidsceneUIAgent> => {\n if (!options.agentProvider) return options.getAgent!(ctx);\n const runId = getExecutionId(ctx);\n if (\n options.agentProvider.releaseAgent &&\n !registeredAgentScopes.has(runId)\n ) {\n registeredAgentScopes.add(runId);\n ctx.onTeardown(async () => {\n try {\n const released = await options.agentProvider!.releaseAgent!(runId);\n return released?.reportPath\n ? { reportPaths: [released.reportPath] }\n : undefined;\n } finally {\n registeredAgentScopes.delete(runId);\n }\n });\n }\n return options.agentProvider.getAgent(runId, ctx);\n };\n\n const agentDefinitions = options.agentClass.getTestRunnerNodeDefinitions();\n\n return [\n ...createAgentTestRunnerNodes(agentDefinitions, getAgent),\n defineNode<typeof waitInputSchema, unknown, TContext>({\n name: 'wait',\n description: 'Wait for a fixed duration while honoring cancellation.',\n stringInputKey: false,\n inputSchema: waitInputSchema,\n async execute(ctx) {\n const multiplier =\n ctx.input.unit === 'min'\n ? 60_000\n : ctx.input.unit === 's'\n ? 1_000\n : 1;\n const durationMs = ctx.input.duration * multiplier;\n await waitFor(durationMs, ctx.signal);\n return { summary: `Waited ${durationMs}ms` };\n },\n }),\n defineNode<typeof agentInputSchema, unknown, TContext>({\n name: 'agent',\n description:\n 'Execute one self-contained natural-language task with an injected Agent executor.',\n stringInputKey: 'prompt',\n inputSchema: agentInputSchema,\n async execute(ctx) {\n if (!options.agentExecutor) {\n throw new NodeExecutionError(\n 'agent',\n new TypeError('createMidsceneNodes() requires an agentExecutor.'),\n );\n }\n const execution =\n ctx.scope === 'case'\n ? { scope: 'case' as const, runId: ctx.case.runId }\n : {\n scope: 'document' as const,\n runId: ctx.document.documentRunId,\n };\n const result = await options.agentExecutor.execute({\n prompt: ctx.input.prompt,\n context: ctx.context,\n signal: ctx.signal,\n execution,\n });\n return result ?? { summary: 'Agent task completed.' };\n },\n }),\n ];\n}\n","import type { HarmonyAgent } from '@midscene/harmony';\nimport {\n harmonyAgentTestRunnerNodeDefinitions,\n launchInputSchema,\n runHdcShellInputSchema,\n terminateInputSchema,\n} from '@midscene/harmony/test-runner';\nexport type {\n LaunchNodeInput,\n RunHdcShellNodeInput,\n TerminateNodeInput,\n} from '@midscene/harmony/test-runner';\nimport type { Awaitable } from '../engine/types';\nimport { NodeDefinitionError } from '../errors';\nimport { createAgentTestRunnerNodes } from '../midscene';\nimport type { NodeDefinition, NodeExecutionContext } from '../node/types';\n\nexport { launchInputSchema, runHdcShellInputSchema, terminateInputSchema };\n\nexport type HarmonyRunnerAgent = Pick<\n HarmonyAgent,\n 'launch' | 'terminate' | 'runHdcShell' | 'back' | 'home' | 'recentApps'\n>;\n\nexport interface CreateHarmonyNodesOptions<TContext> {\n getAgent(\n ctx: NodeExecutionContext<unknown, TContext>,\n ): Awaitable<HarmonyRunnerAgent>;\n}\n\nexport function createHarmonyNodes<TContext>(\n options: CreateHarmonyNodesOptions<TContext>,\n): readonly NodeDefinition<any, any, TContext>[] {\n if (!options || typeof options !== 'object') {\n throw new NodeDefinitionError(\n 'createHarmonyNodes() options must be an object.',\n );\n }\n if (typeof options.getAgent !== 'function') {\n throw new NodeDefinitionError('createHarmonyNodes() requires getAgent().');\n }\n\n return createAgentTestRunnerNodes(\n harmonyAgentTestRunnerNodeDefinitions,\n options.getAgent,\n );\n}\n"],"names":["WorkflowError","Error","undefined","message","options","NodeDefinitionError","details","validateOptionalText","value","field","node","validateInputSchema","schema","z","validateDefinition","defineNode","nonBlankPrompt","description","createAgentTestRunnerNodes","definitions","getAgent","definition","ctx","agent","createHarmonyNodes","harmonyAgentTestRunnerNodeDefinitions"],"mappings":"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"}
package/dist/es/index.mjs CHANGED
@@ -239,13 +239,7 @@ function normalizeMeta(value, index) {
239
239
  continueOnError: continueOnError ?? false
240
240
  };
241
241
  }
242
- function validateCommonNodeInput(input, index) {
243
- if (void 0 !== input.prompt && 'string' != typeof input.prompt) throw new WorkflowParseError(`${formatStep(index)} "prompt" must be a string.`, {
244
- index,
245
- prompt: input.prompt
246
- });
247
- }
248
- function normalizeStep(value, index = 0) {
242
+ function normalizeStep(value, index = 0, resolveNode) {
249
243
  if (!isMapping(value)) throw new WorkflowParseError(`${formatStep(index)} must be a mapping.`, {
250
244
  index
251
245
  });
@@ -258,30 +252,37 @@ function normalizeStep(value, index = 0) {
258
252
  if (0 === node.trim().length) throw new WorkflowParseError(`${formatStep(index)} node name must not be empty.`, {
259
253
  index
260
254
  });
261
- if ('string' == typeof rawValue) return {
262
- node,
263
- input: {
264
- prompt: rawValue
265
- },
266
- meta: {
267
- continueOnError: false
268
- }
269
- };
255
+ if ('string' == typeof rawValue) {
256
+ const resolvedNode = resolveNode?.(node);
257
+ const stringInputKey = resolvedNode ? resolvedNode.stringInputKey : 'prompt';
258
+ if (false === stringInputKey || void 0 === stringInputKey) throw new WorkflowParseError(`${formatStep(index)} node "${node}" does not accept string shorthand.`, {
259
+ index,
260
+ node
261
+ });
262
+ return {
263
+ node,
264
+ input: {
265
+ [stringInputKey]: rawValue
266
+ },
267
+ meta: {
268
+ continueOnError: false
269
+ }
270
+ };
271
+ }
270
272
  if (!isMapping(rawValue)) throw new WorkflowParseError(`${formatStep(index)} value must be a string shorthand or a mapping.`, {
271
273
  index,
272
274
  node
273
275
  });
274
276
  const { $: rawMeta, ...input } = rawValue;
275
- validateCommonNodeInput(input, index);
276
277
  return {
277
278
  node,
278
279
  input,
279
280
  meta: normalizeMeta(rawMeta, index)
280
281
  };
281
282
  }
282
- function normalizeSteps(steps) {
283
+ function normalizeSteps(steps, resolveNode) {
283
284
  if (!Array.isArray(steps)) throw new WorkflowParseError('Steps must be an array.');
284
- return steps.map((step, index)=>normalizeStep(step, index));
285
+ return steps.map((step, index)=>normalizeStep(step, index, resolveNode));
285
286
  }
286
287
  const validateOptionalText = (value, field, node)=>{
287
288
  if (void 0 !== value && ('string' != typeof value || 0 === value.trim().length)) throw new NodeDefinitionError(`Node "${node}" ${field} must be a non-empty string.`, {
@@ -305,7 +306,15 @@ const validateDefinition = (options)=>{
305
306
  if ('string' != typeof options.name || 0 === options.name.trim().length) throw new NodeDefinitionError('Node name must be a non-empty string.');
306
307
  validateOptionalText(options.title, 'title', options.name);
307
308
  validateOptionalText(options.description, "description", options.name);
309
+ if (void 0 !== options.stringInputKey && false !== options.stringInputKey && ('string' != typeof options.stringInputKey || 0 === options.stringInputKey.trim().length)) throw new NodeDefinitionError(`Node "${options.name}" stringInputKey must be a non-empty string or false.`, {
310
+ node: options.name,
311
+ field: 'stringInputKey'
312
+ });
308
313
  validateInputSchema(options.inputSchema, options.name);
314
+ if ('string' == typeof options.stringInputKey && options.inputSchema instanceof z.ZodObject && !(options.stringInputKey in options.inputSchema.shape)) throw new NodeDefinitionError(`Node "${options.name}" stringInputKey must reference a field in inputSchema.`, {
315
+ node: options.name,
316
+ field: 'stringInputKey'
317
+ });
309
318
  if ('function' != typeof options.execute) throw new NodeDefinitionError(`Node "${options.name}" must provide an execute function.`, {
310
319
  node: options.name
311
320
  });
@@ -392,45 +401,6 @@ const assertJsonSerializable = (value, label, node)=>{
392
401
  throw new NodeExecutionError(node, error);
393
402
  }
394
403
  };
395
- const cloneAndFreezeJson = (value)=>{
396
- if (Array.isArray(value)) return Object.freeze(value.map(cloneAndFreezeJson));
397
- if ('object' == typeof value && null !== value) return Object.freeze(Object.fromEntries(Object.entries(value).map(([key, child])=>[
398
- key,
399
- cloneAndFreezeJson(child)
400
- ])));
401
- return value;
402
- };
403
- const toHistoryEntry = (result, scope)=>{
404
- assertJsonSerializable(result.input, 'input', result.node);
405
- if (result.output?.data !== void 0) assertJsonSerializable(result.output.data, 'output.data', result.node);
406
- return Object.freeze({
407
- scope,
408
- phase: result.phase,
409
- stepIndex: result.stepIndex,
410
- node: result.node,
411
- input: cloneAndFreezeJson(result.input),
412
- ...'string' == typeof result.input?.prompt ? {
413
- intent: result.input.prompt
414
- } : {},
415
- status: 'success' === result.status ? 'passed' : 'failed',
416
- ...result.output?.summary === void 0 ? {} : {
417
- summary: result.output.summary
418
- },
419
- ...result.output?.data === void 0 ? {} : {
420
- data: cloneAndFreezeJson(result.output.data)
421
- },
422
- ...result.error ? {
423
- error: Object.freeze({
424
- name: result.error.name,
425
- message: result.error.message
426
- })
427
- } : {}
428
- });
429
- };
430
- const createHistory = (prefix, results, scope)=>Object.freeze([
431
- ...prefix,
432
- ...results.map((result)=>toHistoryEntry(result, scope))
433
- ]);
434
404
  function validateNodeOutput(output, node) {
435
405
  if (void 0 === output) return;
436
406
  if ('object' != typeof output || null === output || Array.isArray(output)) throw new NodeExecutionError(node, new TypeError('Node output must be an object or undefined.'));
@@ -452,7 +422,6 @@ function createStepResultBase(step, startedAt, phase, stepIndex) {
452
422
  };
453
423
  }
454
424
  async function executeNode(step, execute, phase, stepIndex, options) {
455
- validateCommonNodeInput(step.input, stepIndex);
456
425
  const startedAt = new Date();
457
426
  const abortController = new AbortController();
458
427
  const parentSignal = options.parentSignal;
@@ -536,7 +505,6 @@ async function executeStep(step, node, target, context, execution = {}) {
536
505
  $: step.meta,
537
506
  signal,
538
507
  context,
539
- history: execution.history ?? Object.freeze([]),
540
508
  onTeardown: (teardown)=>{
541
509
  if (!execution.onTeardown) throw new NodeExecutionError(step.node, new Error('The current execution scope cannot register teardown.'));
542
510
  execution.onTeardown(step.node, teardown);
@@ -584,7 +552,6 @@ async function runCollectedCase(collectedCase, options) {
584
552
  const beforeEach = [];
585
553
  const steps = [];
586
554
  const afterEach = [];
587
- const completedNodes = [];
588
555
  const teardownStack = [];
589
556
  let acceptingTeardowns = true;
590
557
  const onTeardown = (node, teardown)=>{
@@ -613,13 +580,7 @@ async function runCollectedCase(collectedCase, options) {
613
580
  sourcePath: collectedCase.sourcePath,
614
581
  caseIndex: collectedCase.caseIndex,
615
582
  phase,
616
- stepIndex,
617
- completedSteps: Object.freeze([
618
- ...steps
619
- ]),
620
- completedNodes: Object.freeze([
621
- ...completedNodes
622
- ])
583
+ stepIndex
623
584
  };
624
585
  const stepInfo = {
625
586
  scope: 'case',
@@ -632,14 +593,12 @@ async function runCollectedCase(collectedCase, options) {
632
593
  scope: 'case',
633
594
  case: caseContext
634
595
  }, options.context, {
635
- history: createHistory(options.documentHistory ?? [], completedNodes, 'case'),
636
596
  signal,
637
597
  defaultTimeoutMs: options.defaultTimeoutMs,
638
598
  onTeardown
639
599
  });
640
600
  await options.onStepResult?.(stepInfo, result);
641
601
  results.push(result);
642
- completedNodes.push(result);
643
602
  if ('failed' === result.status && !result.continuedAfterError) break;
644
603
  }
645
604
  };
@@ -679,7 +638,7 @@ async function runCollectedCase(collectedCase, options) {
679
638
  name: collectedCase.definition.name,
680
639
  sourcePath: collectedCase.sourcePath,
681
640
  caseIndex: collectedCase.caseIndex,
682
- status: completedNodes.some((step)=>'failed' === step.status) || teardownErrors.length > 0 ? 'failed' : 'success',
641
+ status: beforeEach.some((step)=>'failed' === step.status) || steps.some((step)=>'failed' === step.status) || afterEach.some((step)=>'failed' === step.status) || teardownErrors.length > 0 ? 'failed' : 'success',
683
642
  beforeEach,
684
643
  steps,
685
644
  afterEach,
@@ -722,7 +681,7 @@ class CaseRunner {
722
681
  definition: {
723
682
  name: input.name ?? 'case',
724
683
  tags: input.tags ?? [],
725
- steps: normalizeSteps(input.steps)
684
+ steps: normalizeSteps(input.steps, (name)=>this.registry.get(name))
726
685
  }
727
686
  };
728
687
  const result = await runCollectedCase(collectedCase, {
@@ -749,7 +708,6 @@ function createDocumentRuntime(document, options) {
749
708
  const startedAt = new Date();
750
709
  const beforeAll = [];
751
710
  const afterAll = [];
752
- const completedNodes = [];
753
711
  const nodes = {
754
712
  beforeAll: document.lifecycle.beforeAll.map((step)=>options.resolveNode(step.node)),
755
713
  afterAll: document.lifecycle.afterAll.map((step)=>options.resolveNode(step.node))
@@ -780,7 +738,7 @@ function createDocumentRuntime(document, options) {
780
738
  projectId: document.projectId,
781
739
  projectName: project.name,
782
740
  sourcePath: document.sourcePath,
783
- status: completedNodes.some((step)=>'failed' === step.status) || teardownErrors.length > 0 ? 'failed' : 'success',
741
+ status: beforeAll.some((step)=>'failed' === step.status) || afterAll.some((step)=>'failed' === step.status) || teardownErrors.length > 0 ? 'failed' : 'success',
784
742
  startedAt: startedAt.toISOString(),
785
743
  endedAt: endedAt.toISOString(),
786
744
  durationMs: Math.max(0, endedAt.getTime() - startedAt.getTime()),
@@ -809,10 +767,7 @@ function createDocumentRuntime(document, options) {
809
767
  projectName: project.name,
810
768
  sourcePath: document.sourcePath,
811
769
  phase,
812
- stepIndex,
813
- completedNodes: Object.freeze([
814
- ...completedNodes
815
- ])
770
+ stepIndex
816
771
  };
817
772
  const stepInfo = {
818
773
  scope: 'document',
@@ -825,7 +780,6 @@ function createDocumentRuntime(document, options) {
825
780
  scope: 'document',
826
781
  document: documentContext
827
782
  }, context, {
828
- history: createHistory([], completedNodes, 'document'),
829
783
  signal: executionSignal,
830
784
  defaultTimeoutMs: options.defaultTimeoutMs,
831
785
  onTeardown (node, teardown) {
@@ -848,7 +802,6 @@ function createDocumentRuntime(document, options) {
848
802
  });
849
803
  await options.onStepResult?.(stepInfo, result);
850
804
  results.push(result);
851
- completedNodes.push(result);
852
805
  if ('failed' === result.status && !result.continuedAfterError) break;
853
806
  }
854
807
  };
@@ -856,9 +809,6 @@ function createDocumentRuntime(document, options) {
856
809
  get context () {
857
810
  return context;
858
811
  },
859
- get history () {
860
- return createHistory([], completedNodes, 'document');
861
- },
862
812
  get canRunCases () {
863
813
  return started && !signal.aborted && !beforeAll.some((step)=>'failed' === step.status);
864
814
  },
@@ -953,7 +903,6 @@ async function runWorkflowDocument(document, options) {
953
903
  context: runtime.context,
954
904
  projectName,
955
905
  attemptIndex,
956
- documentHistory: runtime.history,
957
906
  signal: options.signal,
958
907
  defaultTimeoutMs: options.defaultTimeoutMs,
959
908
  onStepStart: options.onStepStart,
@@ -1214,7 +1163,7 @@ function collectWorkflowDocument(source, options) {
1214
1163
  if (!Array.isArray(value)) throw new WorkflowParseError(`Workflow document ${phase} must be an array.`, {
1215
1164
  phase
1216
1165
  });
1217
- return normalizeSteps(value).map((normalized, stepIndex)=>{
1166
+ return normalizeSteps(value, options.resolveNode).map((normalized, stepIndex)=>{
1218
1167
  const resolved = options.resolveNode(normalized.node);
1219
1168
  if (!resolved) throw new WorkflowParseError(`Workflow document ${phase} step ${stepIndex + 1} references unknown node "${normalized.node}".`, {
1220
1169
  phase,
@@ -1250,7 +1199,7 @@ function collectWorkflowDocument(source, options) {
1250
1199
  if (!Array.isArray(tags) || tags.some((tag)=>'string' != typeof tag || 0 === tag.trim().length)) throw new WorkflowParseError(`Case ${caseIndex + 1} tags must be an array of non-empty strings.`, {
1251
1200
  caseIndex
1252
1201
  });
1253
- const steps = normalizeSteps(definition.steps).map((normalized, stepIndex)=>{
1202
+ const steps = normalizeSteps(definition.steps, options.resolveNode).map((normalized, stepIndex)=>{
1254
1203
  if (!options.resolveNode(normalized.node)) throw new WorkflowParseError(`Case ${caseIndex + 1} step ${stepIndex + 1} references unknown node "${normalized.node}".`, {
1255
1204
  caseIndex,
1256
1205
  stepIndex,
@@ -1283,6 +1232,6 @@ function collectWorkflowDocument(source, options) {
1283
1232
  cases
1284
1233
  };
1285
1234
  }
1286
- export { CaseExecutionError, CaseRunner, DuplicateNodeError, FatalDeviceError, NodeDefinitionError, NodeExecutionError, NodeInputValidationError, NodeNotFoundError, NodeRegistry, NodeScopeTeardownError, ProjectSetupError, ProjectTeardownError, StepTimeoutError, WorkflowDocumentExecutionError, WorkflowError, WorkflowLifecycleError, WorkflowParseError, assertJsonSerializable, collectWorkflowDocument, createCaseId, createCaseRunner, createDocumentRuntime, createHistory, createProjectRuntime, createWorkflowDocumentId, defineNode, isFatalDeviceError, normalizeNodeExecutionError, normalizeStep, normalizeSteps, resolveWorkflowVariables, runWorkflowDocument, toHistoryEntry, validateCommonNodeInput, z };
1235
+ export { CaseExecutionError, CaseRunner, DuplicateNodeError, FatalDeviceError, NodeDefinitionError, NodeExecutionError, NodeInputValidationError, NodeNotFoundError, NodeRegistry, NodeScopeTeardownError, ProjectSetupError, ProjectTeardownError, StepTimeoutError, WorkflowDocumentExecutionError, WorkflowError, WorkflowLifecycleError, WorkflowParseError, collectWorkflowDocument, createCaseId, createCaseRunner, createDocumentRuntime, createProjectRuntime, createWorkflowDocumentId, defineNode, isFatalDeviceError, normalizeNodeExecutionError, normalizeStep, normalizeSteps, resolveWorkflowVariables, runWorkflowDocument, z };
1287
1236
 
1288
1237
  //# sourceMappingURL=index.mjs.map