android2harmony 0.1.3 → 0.1.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.
package/dist/index.js.map CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
- "sources": ["../src/index.ts", "../src/paths.ts", "../src/discovery.ts", "../src/env.ts", "../src/tools/a2h-app-feature-verify.ts", "../src/tools/a2h-autotest-resolve-metadata.ts", "../src/tools/autotest-shared.ts", "../src/tools/a2h-autotest-testcases.ts", "../src/tools/a2h-autotest-report.ts", "../src/tools/a2h-autotest-selftest.ts"],
4
- "sourcesContent": ["import path from \"node:path\"\r\nimport type { A2HHostDeps, Plugin, PluginInput, PluginHooks } from \"./types\"\r\nimport { PKG_ROOT } from \"./paths\"\r\nimport { discoverA2HAgents } from \"./discovery\"\r\nimport { buildA2HEnvBase } from \"./env\"\r\nimport { makeA2hAppFeatureVerifyTool } from \"./tools/a2h-app-feature-verify\"\r\nimport { makeA2hAutotestResolveMetadataTool } from \"./tools/a2h-autotest-resolve-metadata\"\r\nimport { makeA2hAutotestTestcasesTool } from \"./tools/a2h-autotest-testcases\"\r\nimport { makeA2hAutotestReportTool } from \"./tools/a2h-autotest-report\"\r\nimport { makeA2hAutotestSelftestTool } from \"./tools/a2h-autotest-selftest\"\r\n\r\n// Public re-exports \u2014 the package's external surface (dev eco code loads\r\n// dist/index.js at runtime and calls `createServer`; `A2HHostDeps` /\r\n// `PLUGIN_ID` are kept exported for any TS consumers).\r\nexport type { A2HHostDeps } from \"./types\"\r\nexport { PLUGIN_ID } from \"./paths\"\r\n\r\n/**\r\n * Create the A2H DevEco Code plugin.\r\n *\r\n * This is a self-contained plugin - skills/, agents/, and tools/\r\n * are bundled inside the package, NOT referenced from HomeTrans.\r\n * A2H-Plugin is the independently publishable \"plugin edition\" of HomeTrans.\r\n *\r\n * @param deps - host dependencies injected by DevEco Code's thin bridge.\r\n * The A2H Plugin defines what it needs (A2HHostDeps);\r\n * DevEco Code provides how to resolve it.\r\n * @returns A Plugin function ready to be registered as a built-in plugin.\r\n */\r\nexport function createServer(deps: A2HHostDeps): Plugin {\r\n return async (input: PluginInput): Promise<PluginHooks> => {\r\n // All A2H assets live inside this package (self-contained, no HomeTrans dep)\r\n const skillsRoot = path.join(PKG_ROOT, \"skills\")\r\n const agentsRoot = path.join(PKG_ROOT, \"agents\")\r\n\r\n // Auto-discover agents: scan top-level agents/*.md, parse `name:` from\r\n // frontmatter, strip frontmatter for prompt body. Adding/removing/renaming\r\n // an agent is just a file drop \u2014 no code change, no rebuild.\r\n const agentPrompts = discoverA2HAgents(agentsRoot)\r\n\r\n return {\r\n dispose: async () => {\r\n agentPrompts.clear()\r\n },\r\n\r\n // \u2500\u2500 config hook: register skills search paths and agents \u2500\u2500\r\n config: async (cfg: Record<string, unknown>) => {\r\n // --- Skills ---\r\n const skills = (cfg.skills as Record<string, unknown>) || {}\r\n cfg.skills = skills\r\n\r\n // Register the bundled skills/ directory as an additional search path.\r\n // All bundled skills are user-visible \u2014 there is no partial-visibility\r\n // (hidden) layer anymore, so we only register the search path here.\r\n const paths = (skills.paths as string[]) || []\r\n skills.paths = paths\r\n if (!paths.includes(skillsRoot)) {\r\n paths.push(skillsRoot)\r\n }\r\n\r\n // --- Agents ---\r\n const agent = (cfg.agent as Record<string, Record<string, unknown>>) || {}\r\n cfg.agent = agent\r\n\r\n for (const [name, prompt] of agentPrompts) {\r\n // Don't overwrite if the user already configured this agent\r\n if (!agent[name]) {\r\n agent[name] = {\r\n mode: \"subagent\",\r\n hidden: true,\r\n prompt,\r\n }\r\n }\r\n }\r\n\r\n },\r\n\r\n // \u2500\u2500 shell.env hook: inject DevEco Studio paths + multimodal model \u2500\u2500\r\n // params (from A2HHostDeps.resolveModelParams) as env vars, so the\r\n // UI-alignment scripts (app_feature_verify.ts) can call the model\r\n // without falling back to ~/.hometrans/config.json.\r\n \"shell.env\": async (\r\n shellInput: { cwd: string; sessionID?: string; callID?: string },\r\n output: { env: Record<string, string> },\r\n ) => {\r\n const a2hEnv = await buildA2HEnvBase(deps)\r\n Object.assign(output.env, a2hEnv)\r\n },\r\n\r\n // \u2500\u2500 tool hook: run the UI-verification phone agent without leaking the\r\n // model apiKey into env/argv \u2014 it is resolved from the host and piped to\r\n // the script over stdin. See src/tools/a2h-app-feature-verify.ts.\r\n //\r\n // The autotest_* tools wrap the engine scripts under tools/autotest/\r\n // (vendored from HomeTrans). They are grouped by noun \u2014 each tool\r\n // dispatches on an `action` enum (e.g. `a2h_autotest_selftest` covers\r\n // run|status|kill|check_hap|check_inputs|check_pre), so 11 engine\r\n // subcommands surface as 4 plugin tools. Only `a2h_autotest_selftest`\r\n // with `action:\"run\"` needs a model, resolved via deps.resolveModelParams\r\n // and piped over stdin with the same secret-handling discipline; the\r\n // rest are pure JSON/Markdown transforms or state probes with no model\r\n // and no device.\r\n tool: {\r\n a2h_app_feature_verify: makeA2hAppFeatureVerifyTool(deps),\r\n a2h_autotest_resolve_metadata: makeA2hAutotestResolveMetadataTool(deps),\r\n a2h_autotest_testcases: makeA2hAutotestTestcasesTool(deps),\r\n a2h_autotest_report: makeA2hAutotestReportTool(deps),\r\n a2h_autotest_selftest: makeA2hAutotestSelftestTool(deps),\r\n },\r\n }\r\n }\r\n}\r\n", "import path from \"node:path\"\r\nimport { fileURLToPath } from \"node:url\"\r\n\r\n// Resolve package root (one directory above dist/ or src/). Computed once here\r\n// and imported everywhere else so there's a single source of truth. After\r\n// esbuild bundling, import.meta.url points at dist/index.js, so this still\r\n// resolves to the package root at runtime.\r\nconst __filename = fileURLToPath(import.meta.url)\r\nconst __dirname = path.dirname(__filename)\r\n\r\n/** Absolute path to the A2H-Plugin package root. */\r\nexport const PKG_ROOT = path.resolve(__dirname, \"..\")\r\n\r\n/** Public plugin id. */\r\nexport const PLUGIN_ID = \"android2harmony\"\r\n", "import path from \"node:path\"\r\nimport fs from \"node:fs\"\r\n\r\n/**\r\n * Strip YAML frontmatter (--- ... ---) from agent .md files.\r\n * Agent .md files have metadata in frontmatter; only the body\r\n * should be used as the LLM prompt.\r\n */\r\nexport function stripFrontmatter(content: string): string {\r\n const match = content.match(/^---\\s*\\n([\\s\\S]*?)\\n---\\s*\\n/)\r\n return match ? content.slice(match[0].length).trim() : content\r\n}\r\n\r\n/**\r\n * Minimal YAML frontmatter extractor for A2H-owned `.md` files (SKILL.md\r\n * and agent `.md`). A2H controls the frontmatter format, so we only need\r\n * the top-level `name` (a scalar). This avoids pulling a YAML dependency\r\n * into the plugin.\r\n *\r\n * Returns the parsed `name`, or null when the file has no usable frontmatter\r\n * (e.g. a README dropped into a directory that's also scanned for agents).\r\n */\r\nexport function parseFrontmatter(content: string): string | null {\r\n const match = content.match(/^---\\s*\\r?\\n([\\s\\S]*?)\\r?\\n---/)\r\n if (!match) return null\r\n const lines = match[1].split(/\\r?\\n/)\r\n\r\n for (const line of lines) {\r\n if (!line.trim() || line.trim().startsWith(\"#\")) continue\r\n if (line.length - line.trimStart().length !== 0) continue // skip indented (e.g. metadata block) lines\r\n\r\n const kv = line.trim().match(/^([a-zA-Z_][a-zA-Z0-9_-]*):\\s*(.*)$/)\r\n if (!kv) continue\r\n if (kv[1] === \"name\") {\r\n return kv[2].trim().replace(/^[\"']|[\"']$/g, \"\")\r\n }\r\n }\r\n\r\n return null\r\n}\r\n\r\n/**\r\n * Scan the bundled agents/ directory and return a `name \u2192 prompt body` map.\r\n *\r\n * Only top-level `.md` files are considered (subdirectories like `scripts/`\r\n * are skipped via `isFile()`). Files without a `name:` field in frontmatter\r\n * (e.g. a stray README) are silently skipped \u2014 the frontmatter name is the\r\n * source of truth, not the file basename. The body (frontmatter stripped)\r\n * becomes the LLM prompt.\r\n *\r\n * Adding a new agent is therefore a zero-code, zero-rebuild change: just\r\n * drop an `.md` with `name:` frontmatter into `agents/`.\r\n */\r\nexport function discoverA2HAgents(agentsRoot: string): Map<string, string> {\r\n const result = new Map<string, string>()\r\n let entries: fs.Dirent[]\r\n try {\r\n entries = fs.readdirSync(agentsRoot, { withFileTypes: true })\r\n } catch {\r\n return result\r\n }\r\n for (const entry of entries) {\r\n if (!entry.isFile() || !entry.name.endsWith(\".md\")) continue\r\n const file = path.join(agentsRoot, entry.name)\r\n let raw: string\r\n try {\r\n raw = fs.readFileSync(file, \"utf-8\")\r\n } catch {\r\n continue\r\n }\r\n const name = parseFrontmatter(raw)\r\n if (!name) continue // not a valid agent file (no `name:` frontmatter)\r\n result.set(name, stripFrontmatter(raw))\r\n }\r\n return result\r\n}\r\n", "import path from \"node:path\"\nimport type { A2HHostDeps } from \"./types\"\n\n/**\n * Build the A2H env var set from host-injected deps. Shared by the `shell.env`\n * hook (so every shell session sees these). Env vars are the single source of\n * truth in the plugin context for non-secret paths.\n *\n * Contains only DevEco/SDK path vars. The multimodal model apiKey is NOT\n * injected here anymore \u2014 putting it in `process.env` leaked it to every\n * descendant process (adb/hdc, sub-agents), to `/proc/<pid>/environ`, crash\n * dumps, and any `env`/`printenv` output. Instead the `a2h_app_feature_verify`\n * plugin tool resolves the params via `A2HHostDeps.resolveModelParams` and pipes\n * them to the script over stdin (`--model-stdin`), so the key stays in process\n * memory + the stdin pipe and never enters env/argv/disk.\n *\n * Nothing secret is present in this env set, but it is still good hygiene not\n * to dump it into logs verbatim.\n */\nexport async function buildA2HEnvBase(\n deps: A2HHostDeps,\n): Promise<Record<string, string>> {\n const env: Record<string, string> = {}\n\n const home = await deps.resolveDevEcoHome()\n if (home) {\n const sdk = deps.resolveSdkPath(home)\n env[\"DEVECO_HOME\"] = home\n env[\"DEVECO_SDK_HOME\"] = sdk\n env[\"OHOS_SDK_PATH\"] = path.join(sdk, \"default\", \"openharmony\", \"ets\")\n env[\"HMS_SDK_PATH\"] = path.join(sdk, \"default\", \"hms\", \"ets\")\n }\n\n return env\n}\n", "import { spawn } from \"node:child_process\"\nimport path from \"node:path\"\nimport { PKG_ROOT } from \"../paths\"\nimport type { A2HHostDeps, ToolContext, ToolDefinition } from \"../types\"\n\n// The bundled UI-feature-verification script (TypeScript, run directly by\n// Node \u2265 22.18 which strips types natively \u2014 same as when the agent ran\n// `node app_feature_verify.ts` via the shell).\nconst SCRIPT_PATH = path.join(\n PKG_ROOT,\n \"skills\",\n \"hmos-incremental-ui-align\",\n \"scripts\",\n \"app_feature_verify.ts\",\n)\n\n/**\n * Env vars that may carry a model apiKey. Stripped from the child's env so the\n * key is never inherited by the spawned script or its adb/hdc descendants. The\n * script instead receives the key via the stdin pipe (`--model-stdin`).\n */\nconst SECRET_ENV = [\n \"HOMETRANS_MODEL_API_KEY\",\n \"HOMETRANS_MODEL_NAME\",\n \"HOMETRANS_MODEL_BASE_URL\",\n \"GLM_API_KEY\",\n \"TEST_API_KEY\",\n] as const\n\n/** Cap stdout returned to the host so a runaway loop can't overflow the chat. */\nfunction cap(s: string, max = 50000): string {\n if (s.length <= max) return s\n const half = Math.floor(max / 2)\n return s.slice(0, half) + `\\n\u2026[truncated ${s.length - max} chars]\u2026\\n` + s.slice(-half)\n}\n\n/**\n * Build the `a2h_app_feature_verify` plugin tool.\n *\n * Spawns app_feature_verify.ts with the model config piped to its stdin, so the\n * apiKey travels only through the OS pipe + process memory \u2014 never env, argv,\n * or a plugin-written file. The host resolves the params (apiKey) via\n * `deps.resolveModelParams`; the tool is the only thing that ever holds it.\n */\nexport function makeA2hAppFeatureVerifyTool(deps: A2HHostDeps): ToolDefinition {\n return {\n description:\n \"Run the a2h UI feature-verification phone agent (app_feature_verify.ts) to navigate an app and verify a feature path on a connected device. The multimodal model apiKey is resolved from the host and piped to the script over stdin \u2014 it is never placed in env or argv, so it is not visible to the spawned adb/hdc subprocesses. Use this instead of running `node app_feature_verify.ts` directly. The script runs its internal agent loop (screenshot \u2192 model \u2192 action) up to maxSteps and prints per-step thinking + actions; the full stdout is returned on success. One of task/prompt/feature is required; app and package are required.\",\n args: {\n type: \"object\",\n properties: {\n app: { type: \"string\", description: \"\u76EE\u6807 app \u663E\u793A\u540D\uFF08required\uFF09\" },\n package: { type: \"string\", description: \"\u76EE\u6807 app \u5305\u540D\uFF08required\uFF09\" },\n device: {\n type: \"string\",\n enum: [\"hdc\", \"adb\"],\n default: \"hdc\",\n description: \"\u8BBE\u5907\u9A71\u52A8\uFF1Ahdc(HarmonyOS) / adb(Android)\",\n },\n task: {\n type: \"string\",\n description: \"L1/L2/L3 \u4EFB\u52A1\u8DEF\u5F84\uFF0C\u5982 L1\u76F8\u518C\u8BE6\u60C5L2\u66F4\u591A\uFF08\u4E0E prompt/feature \u4E09\u9009\u4E00\uFF09\",\n },\n prompt: { type: \"string\", description: \"\u81EA\u7531 prompt\uFF08\u4E0E task/feature \u4E09\u9009\u4E00\uFF09\" },\n feature: { type: \"string\", description: \".md \u529F\u80FD\u6E05\u5355\u8DEF\u5F84\uFF08\u4E0E task/prompt \u4E09\u9009\u4E00\uFF09\" },\n maxSteps: { type: \"number\", default: 30, description: \"\u6700\u5927 agent \u6B65\u6570\" },\n appHints: { type: \"string\", description: \"\u5E94\u7528\u5BFC\u822A\u63D0\u793A\" },\n serial: { type: \"string\", description: \"\u8BBE\u5907\u5E8F\u5217\u53F7\" },\n limit: { type: \"number\", description: \"--feature \u6A21\u5F0F\u4E0B\u53D6\u524D N \u6761\" },\n offset: { type: \"number\", default: 1, description: \"--feature \u6A21\u5F0F\u4E0B\u504F\u79FB\" },\n quiet: { type: \"boolean\", default: false, description: \"\u6291\u5236\u63D0\u793A\u6027\u8F93\u51FA\" },\n },\n required: [\"app\", \"package\"],\n },\n execute: async (args: Record<string, unknown>, context: ToolContext) => {\n const app = String(args.app ?? \"\")\n const pkg = String(args.package ?? \"\")\n if (!app || !pkg) {\n throw new Error(\"a2h_app_feature_verify: `app` and `package` are required\")\n }\n const task = args.task != null ? String(args.task) : null\n const prompt = args.prompt != null ? String(args.prompt) : null\n const feature = args.feature != null ? String(args.feature) : null\n if (!task && !prompt && !feature) {\n throw new Error(\"a2h_app_feature_verify: one of `task` / `prompt` / `feature` is required\")\n }\n\n // Resolve host model params \u2014 the apiKey lives only in this process's\n // memory from here on.\n const model = await deps.resolveModelParams(context.worktree)\n if (!model || !model.apiKey) {\n throw new Error(\"a2h_app_feature_verify: host did not provide model params (apiKey missing)\")\n }\n\n // Assemble argv from provided args; append --model-stdin so the script\n // reads model config from stdin rather than env/argv.\n const argv: string[] = [\"--app\", app, \"--package\", pkg, \"--device\", String(args.device ?? \"hdc\")]\n if (task) argv.push(\"--task\", task)\n if (prompt) argv.push(\"--prompt\", prompt)\n if (feature) argv.push(\"--feature\", feature)\n if (args.maxSteps != null) argv.push(\"--max-steps\", String(args.maxSteps))\n if (args.appHints != null) argv.push(\"--app-hints\", String(args.appHints))\n if (args.serial != null) argv.push(\"--serial\", String(args.serial))\n if (args.limit != null) argv.push(\"--limit\", String(args.limit))\n if (args.offset != null) argv.push(\"--offset\", String(args.offset))\n if (args.quiet === true) argv.push(\"--quiet\")\n argv.push(\"--model-stdin\")\n\n // Sanitize env: strip every var that could carry a model apiKey so the\n // child (and its adb/hdc descendants) cannot read it from the environment.\n const env: NodeJS.ProcessEnv = { ...process.env }\n for (const k of SECRET_ENV) delete env[k]\n\n // Spawn via PATH `node` (same binary the shell used to run the .ts script\n // \u2014 guaranteed \u2265 22.18 for native TS). process.execPath might be an older\n // host node, so prefer PATH resolution to match prior working behavior.\n const child = spawn(\"node\", [SCRIPT_PATH, ...argv], {\n cwd: context.directory,\n env,\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\n })\n\n // Pipe the model config to the child's stdin and close it \u2014 the key\n // traverses only the OS pipe, never env/argv/disk.\n child.stdin.end(\n JSON.stringify({ apiKey: model.apiKey, modelName: model.modelName, baseURL: model.baseURL }),\n )\n\n let stdout = \"\"\n let stderr = \"\"\n let spawnError = \"\"\n const stepRe = /\u6267\u884C\u52A8\u4F5C:\\s*(.*)/g\n child.stdout.on(\"data\", (chunk: Buffer) => {\n const text = chunk.toString(\"utf8\")\n stdout += text\n // Surface the latest step's action as the tool's running title.\n const matches = [...text.matchAll(stepRe)]\n if (matches.length) {\n const last = matches[matches.length - 1][1].trim()\n try {\n context.metadata({ title: `UI verify: ${last.slice(0, 80)}` })\n } catch {\n /* host may not implement metadata \u2014 non-essential */\n }\n }\n })\n child.stderr.on(\"data\", (chunk: Buffer) => {\n stderr += chunk.toString(\"utf8\")\n })\n\n // Honor abort: kill the child if the host cancels the tool call.\n const onAbort = () => {\n if (!child.killed) child.kill(\"SIGTERM\")\n }\n context.abort.addEventListener(\"abort\", onAbort)\n\n const code: number = await new Promise((resolve) => {\n child.on(\"close\", resolve)\n child.on(\"error\", (err: Error) => {\n spawnError = err.message\n resolve(-1)\n })\n })\n context.abort.removeEventListener(\"abort\", onAbort)\n\n if (code !== 0) {\n if (spawnError) {\n throw new Error(`a2h_app_feature_verify: failed to spawn node \u2014 ${spawnError}`)\n }\n const tail = stderr.slice(-2000)\n throw new Error(`a2h_app_feature_verify failed (exit ${code}): ${tail || \"(no stderr)\"}`)\n }\n return { ok: true, exitCode: code, output: cap(stdout) }\n },\n }\n}\n", "import path from \"node:path\"\nimport type { A2HHostDeps, ToolContext, ToolDefinition } from \"../types\"\nimport { AUTOTEST_ROOT, autotestToolError, runAutotestScript } from \"./autotest-shared\"\n\nconst SCRIPT_REL = path.join(\"resolve-metadata-tool.ts\")\nconst TOOL_ID = \"a2h_autotest_resolve_metadata\"\n\n/**\n * Build the `a2h_autotest_resolve_metadata` plugin tool.\n *\n * Wraps `tools/autotest/resolve-metadata-tool.ts`: deterministically resolves\n * `bundle_name` / `app_name` from a HarmonyOS project's `AppScope/app.json5`\n * (resolving `$string:xxx` refs) and writes `app-metadata.json`. No model, no\n * device \u2014 pure metadata extraction.\n */\nexport function makeA2hAutotestResolveMetadataTool(_deps: A2HHostDeps): ToolDefinition {\n return {\n description:\n \"Resolve bundle_name / app_name from a HarmonyOS project (reads AppScope/app.json5 and resolves $string refs) and writes app-metadata.json. Pure metadata extraction \u2014 no model, no device. Use this instead of running `node resolve-metadata-tool.ts` directly. Returns the written metadata JSON path + stdout on success.\",\n args: {\n type: \"object\",\n properties: {\n projectDir: {\n type: \"string\",\n description: \"HarmonyOS \u5DE5\u7A0B\u6839\u76EE\u5F55\uFF08\u542B AppScope/app.json5\uFF09\",\n },\n output: {\n type: \"string\",\n description: \"app-metadata.json \u8F93\u51FA\u8DEF\u5F84\",\n },\n },\n required: [\"projectDir\", \"output\"],\n },\n execute: async (args: Record<string, unknown>, context: ToolContext) => {\n const projectDir = String(args.projectDir ?? \"\")\n const output = String(args.output ?? \"\")\n if (!projectDir || !output) {\n throw new Error(`${TOOL_ID}: \\`projectDir\\` and \\`output\\` are required`)\n }\n const argv = [\"--project-dir\", projectDir, \"--output\", output]\n const r = await runAutotestScript({\n scriptRel: SCRIPT_REL,\n argv,\n cwd: context.directory,\n abort: context.abort,\n })\n if (!r.ok) throw autotestToolError(TOOL_ID, r)\n return { ok: true, exitCode: r.exitCode, output: r.stdout }\n },\n }\n}\n", "import { spawn } from \"node:child_process\"\nimport path from \"node:path\"\nimport { PKG_ROOT } from \"../paths\"\n\n/**\n * Shared spawn harness for the a2h autotest plugin tools. Each tool wraps a\n * `node <tools/autotest/...>.ts` engine script; this centralizes the\n * boilerplate (PATH `node` for \u226522.18 native TS, secret-env strip, captured\n * stdio, abort, output cap) so the 4 tool files stay thin.\n */\nexport const AUTOTEST_ROOT = path.join(PKG_ROOT, \"tools\", \"autotest\")\n\n/**\n * Env vars that may carry a model apiKey. Stripped from the child's env so the\n * key is never inherited by the spawned script or its hdc/adb descendants. The\n * selftest `run` script instead receives the key via the stdin pipe\n * (`--model-stdin`).\n */\nconst SECRET_ENV = [\n \"HOMETRANS_MODEL_API_KEY\",\n \"HOMETRANS_MODEL_NAME\",\n \"HOMETRANS_MODEL_BASE_URL\",\n \"GLM_API_KEY\",\n \"TEST_API_KEY\",\n] as const\n\n/** Cap stdout returned to the host so a runaway loop can't overflow the chat. */\nexport function cap(s: string, max = 50000): string {\n if (s.length <= max) return s\n const half = Math.floor(max / 2)\n return s.slice(0, half) + `\\n\u2026[truncated ${s.length - max} chars]\u2026\\n` + s.slice(-half)\n}\n\nexport interface RunOpts {\n /** Path under tools/autotest/ to the engine .ts script, e.g. \"engine/self-test-runner.ts\". */\n scriptRel: string\n /** Argv after the script path (subcommand + flags). */\n argv: string[]\n /** Working directory (typically context.directory). */\n cwd: string\n /** Abort signal from the tool context. */\n abort: AbortSignal\n /** Optional stdin payload (e.g. model config JSON) \u2014 written then closed. */\n stdin?: string\n /** Optional progress hook for stdout chunks (surfaces a running title). */\n onStdoutChunk?: (text: string) => void\n}\n\nexport interface RunResult {\n ok: boolean\n exitCode: number\n stdout: string\n stderr: string\n spawnError: string\n}\n\n/**\n * Spawn `node <tools/autotest/...> argv` with secret env stripped, captured\n * stdio, and abort \u2192 SIGTERM the child. Resolves with a RunResult (never\n * rejects); callers decide ok/fail from `exitCode`).\n */\nexport function runAutotestScript(opts: RunOpts): Promise<RunResult> {\n return new Promise((resolve) => {\n const scriptPath = path.join(AUTOTEST_ROOT, opts.scriptRel)\n const env: NodeJS.ProcessEnv = { ...process.env }\n for (const k of SECRET_ENV) delete env[k]\n\n // PATH `node` (guaranteed \u2265 22.18 for native TS type-stripping); process.execPath\n // may be an older host node, so prefer PATH resolution (matches a2h_app_feature_verify).\n const child = spawn(\"node\", [scriptPath, ...opts.argv], {\n cwd: opts.cwd,\n env,\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\n })\n\n let stdout = \"\"\n let stderr = \"\"\n let spawnError = \"\"\n\n child.stdout.on(\"data\", (chunk: Buffer) => {\n const text = chunk.toString(\"utf8\")\n stdout += text\n opts.onStdoutChunk?.(text)\n })\n child.stderr.on(\"data\", (chunk: Buffer) => {\n stderr += chunk.toString(\"utf8\")\n })\n\n // Write the stdin payload (or just close stdin) \u2014 the key travels only\n // through the OS pipe, never env/argv/disk.\n child.stdin.end(opts.stdin ?? \"\")\n\n const onAbort = () => {\n if (!child.killed) child.kill(\"SIGTERM\")\n }\n opts.abort.addEventListener(\"abort\", onAbort)\n\n child.on(\"close\", (code: number | null) => {\n opts.abort.removeEventListener(\"abort\", onAbort)\n resolve({ ok: code === 0, exitCode: code ?? -1, stdout: cap(stdout), stderr, spawnError })\n })\n child.on(\"error\", (err: Error) => {\n opts.abort.removeEventListener(\"abort\", onAbort)\n spawnError = err.message\n resolve({ ok: false, exitCode: -1, stdout: cap(stdout), stderr, spawnError })\n })\n })\n}\n\n/** Build a tool-shaped Error from a non-zero run result. */\nexport function autotestToolError(toolId: string, r: RunResult): Error {\n if (r.spawnError) return new Error(`${toolId}: failed to spawn node \u2014 ${r.spawnError}`)\n const tail = r.stderr.slice(-2000)\n return new Error(`${toolId} failed (exit ${r.exitCode}): ${tail || \"(no stderr)\"}`)\n}\n", "import path from \"node:path\"\nimport type { A2HHostDeps, ToolContext, ToolDefinition } from \"../types\"\nimport { autotestToolError, runAutotestScript } from \"./autotest-shared\"\n\nconst SCRIPT_REL = path.join(\"engine\", \"testcases-tool.ts\")\nconst TOOL_ID = \"a2h_autotest_testcases\"\n\n/**\n * Build the `a2h_autotest_testcases` plugin tool \u2014 the grouped entry point for\n * the testcases engine (`tools/autotest/engine/testcases-tool.ts`).\n *\n * Two actions, dispatched on `action`:\n * - `generate`: build a schema-compliant testcases.json from extractor output\n * ({ bundle_name, app_name, cases:[{case_name,actions,expected_results}] }).\n * Optional `validate` validates the generated file in place.\n * - `validate`: check an existing testcases.json against the schema.\n *\n * No model, no device. Transform convention \u2014 a non-zero exit is a real\n * failure: `autotestToolError` is thrown (only `generate`/`validate` are\n * transform ops, never state probes).\n */\nexport function makeA2hAutotestTestcasesTool(_deps: A2HHostDeps): ToolDefinition {\n return {\n description:\n \"HarmonyOS self-test testcases.json engine \u2014 grouped by noun, dispatched on `action`. `generate`: build a schema-compliant testcases.json from extractor output ({ bundle_name, app_name, cases:[{case_name,actions,expected_results}] }); pass `validate` to validate in place. `validate`: check an existing testcases.json against the schema. No model, no device. Non-zero exit is a failure (thrown). Use this instead of running `node testcases-tool.ts` directly.\",\n args: {\n type: \"object\",\n properties: {\n action: {\n type: \"string\",\n enum: [\"generate\", \"validate\"],\n description: \"generate=\u4ECE\u62BD\u53D6\u8F93\u51FA\u5EFA testcases.json\uFF1Bvalidate=\u6821\u9A8C\u5DF2\u6709 testcases.json\",\n },\n input: { type: \"string\", description: \"[generate] \u62BD\u53D6\u8F93\u51FA JSON \u8DEF\u5F84\" },\n output: { type: \"string\", description: \"[generate] testcases.json \u8F93\u51FA\u8DEF\u5F84\" },\n validate: { type: \"boolean\", default: false, description: \"[generate] \u5199\u5B8C\u540E\u5C31\u5730\u6821\u9A8C\" },\n path: { type: \"string\", description: \"[validate] \u8981\u6821\u9A8C\u7684 testcases.json \u8DEF\u5F84\" },\n },\n required: [\"action\"],\n },\n execute: async (args: Record<string, unknown>, context: ToolContext) => {\n const action = String(args.action ?? \"\")\n if (action === \"generate\") {\n const input = String(args.input ?? \"\")\n const output = String(args.output ?? \"\")\n if (!input || !output) {\n throw new Error(`${TOOL_ID}: \\`action:\"generate\"\\` requires \\`input\\` and \\`output\\``)\n }\n const argv = [\"generate\", input, output]\n if (args.validate === true) argv.push(\"--validate\")\n const r = await runAutotestScript({\n scriptRel: SCRIPT_REL,\n argv,\n cwd: context.directory,\n abort: context.abort,\n })\n if (!r.ok) throw autotestToolError(TOOL_ID, r)\n return { ok: true, exitCode: r.exitCode, output: r.stdout }\n }\n if (action === \"validate\") {\n const p = String(args.path ?? \"\")\n if (!p) {\n throw new Error(`${TOOL_ID}: \\`action:\"validate\"\\` requires \\`path\\``)\n }\n const argv = [\"validate\", p]\n const r = await runAutotestScript({\n scriptRel: SCRIPT_REL,\n argv,\n cwd: context.directory,\n abort: context.abort,\n })\n if (!r.ok) throw autotestToolError(TOOL_ID, r)\n return { ok: true, exitCode: r.exitCode, output: r.stdout }\n }\n throw new Error(`${TOOL_ID}: unknown \\`action\\` \"${action}\" (expected generate|validate)`)\n },\n }\n}\n", "import path from \"node:path\"\nimport type { A2HHostDeps, ToolContext, ToolDefinition } from \"../types\"\nimport { autotestToolError, runAutotestScript } from \"./autotest-shared\"\n\nconst SCRIPT_REL = path.join(\"engine\", \"report-tool.ts\")\nconst TOOL_ID = \"a2h_autotest_report\"\n\n/**\n * Build the `a2h_autotest_report` plugin tool \u2014 the grouped entry point for\n * the report engine (`tools/autotest/engine/report-tool.ts`).\n *\n * Two actions, dispatched on `action`:\n * - `generate`: render self-test-report.md from a task_<ts>/ directory\n * (summary.json + task_results.jsonl + per-case judgment logs). Optional\n * `validate` validates the rendered report in place.\n * - `validate`: check an existing self-test-report.md's layout.\n *\n * No model, no device. Transform convention \u2014 non-zero exit is a real failure\n * (`autotestToolError` thrown).\n */\nexport function makeA2hAutotestReportTool(_deps: A2HHostDeps): ToolDefinition {\n return {\n description:\n \"HarmonyOS self-test report engine \u2014 grouped by noun, dispatched on `action`. `generate`: render self-test-report.md from a task_<ts>/ directory (summary.json + task_results.jsonl + per-case judgment logs): overview, pre-cases, regular-cases, summary table, suggestions; pass `validate` to validate in place. `validate`: check an existing report.md's layout. No model, no device. Non-zero exit is a failure (thrown). Use this instead of running `node report-tool.ts` directly.\",\n args: {\n type: \"object\",\n properties: {\n action: {\n type: \"string\",\n enum: [\"generate\", \"validate\"],\n description: \"generate=\u4ECE task \u76EE\u5F55\u6E32\u67D3 self-test-report.md\uFF1Bvalidate=\u6821\u9A8C\u5DF2\u6709 report.md \u5E03\u5C40\",\n },\n taskSubdir: { type: \"string\", description: \"[generate] task_<ts>/ \u76EE\u5F55\u8DEF\u5F84\" },\n appMetadata: { type: \"string\", description: \"[generate] app-metadata.json \u8DEF\u5F84\" },\n hap: { type: \"string\", description: \"[generate] HAP \u6587\u4EF6\u8DEF\u5F84\" },\n device: { type: \"string\", description: \"[generate] \u8BBE\u5907\u6807\u8BC6\uFF08\u5982 127.0.0.1:5555\uFF09\" },\n suite: { type: \"string\", description: \"[generate] \u6D4B\u8BD5\u5957\u4EF6\u6807\u9898\" },\n out: { type: \"string\", description: \"[generate] self-test-report.md \u8F93\u51FA\u8DEF\u5F84\" },\n validate: { type: \"boolean\", default: false, description: \"[generate] \u5199\u5B8C\u540E\u5C31\u5730\u6821\u9A8C\" },\n path: { type: \"string\", description: \"[validate] \u8981\u6821\u9A8C\u7684 report.md \u8DEF\u5F84\" },\n },\n required: [\"action\"],\n },\n execute: async (args: Record<string, unknown>, context: ToolContext) => {\n const action = String(args.action ?? \"\")\n if (action === \"generate\") {\n const taskSubdir = String(args.taskSubdir ?? \"\")\n const appMetadata = String(args.appMetadata ?? \"\")\n const hap = String(args.hap ?? \"\")\n const device = String(args.device ?? \"\")\n const suite = String(args.suite ?? \"\")\n const out = String(args.out ?? \"\")\n if (!taskSubdir || !appMetadata || !hap || !device || !suite || !out) {\n throw new Error(\n `${TOOL_ID}: \\`action:\"generate\"\\` requires \\`taskSubdir\\`, \\`appMetadata\\`, \\`hap\\`, \\`device\\`, \\`suite\\`, \\`out\\``,\n )\n }\n const argv = [\n \"generate\",\n \"--task-subdir\", taskSubdir,\n \"--app-metadata\", appMetadata,\n \"--hap\", hap,\n \"--device\", device,\n \"--suite\", suite,\n \"--out\", out,\n ]\n if (args.validate === true) argv.push(\"--validate\")\n const r = await runAutotestScript({\n scriptRel: SCRIPT_REL,\n argv,\n cwd: context.directory,\n abort: context.abort,\n })\n if (!r.ok) throw autotestToolError(TOOL_ID, r)\n return { ok: true, exitCode: r.exitCode, output: r.stdout }\n }\n if (action === \"validate\") {\n const p = String(args.path ?? \"\")\n if (!p) {\n throw new Error(`${TOOL_ID}: \\`action:\"validate\"\\` requires \\`path\\``)\n }\n const argv = [\"validate\", p]\n const r = await runAutotestScript({\n scriptRel: SCRIPT_REL,\n argv,\n cwd: context.directory,\n abort: context.abort,\n })\n if (!r.ok) throw autotestToolError(TOOL_ID, r)\n return { ok: true, exitCode: r.exitCode, output: r.stdout }\n }\n throw new Error(`${TOOL_ID}: unknown \\`action\\` \"${action}\" (expected generate|validate)`)\n },\n }\n}\n", "import { spawn } from \"node:child_process\"\nimport path from \"node:path\"\nimport type { A2HHostDeps, ToolContext, ToolDefinition } from \"../types\"\nimport { AUTOTEST_ROOT, runAutotestScript } from \"./autotest-shared\"\n\nconst SCRIPT_REL = path.join(\"engine\", \"self-test-runner.ts\")\nconst TOOL_ID = \"a2h_autotest_selftest\"\n\n/**\n * Build the `a2h_autotest_selftest` plugin tool \u2014 the grouped entry point for\n * the self-test engine (`tools/autotest/engine/self-test-runner.ts`).\n *\n * Six actions, dispatched on `action`:\n * - `run`: hdc uninstall+install of the HAP, then spawn a detached\n * `@autotest/agent` batch. The multimodal model is resolved via the host's\n * `deps.resolveModelParams(worktree)` and piped through stdin\n * (`--model-stdin`) \u2014 the apiKey never touches env/argv/disk. Without\n * `timeout`, returns RUNNING JSON immediately (exit 2); with `timeout`,\n * blocks until terminal or timeout (0 COMPLETED / 1 FAILED / 3 CRASHED /\n * 4 NOT_STARTED / 5 TIMEOUT). Abort fires a detached `kill --task-dir`.\n * - `status`: probe on-disk state (summary.json / task_results.jsonl / pid\n * liveness). 0 COMPLETED / 2 RUNNING / 3 CRASHED / 4 NOT_STARTED.\n * - `kill`: terminate the batch tree via the pid file. NO_PID_FILE /\n * ALREADY_DEAD / KILLED.\n * - `check_hap`: validate a hap_path (every entry exists + \u22651 entry HAP).\n * - `check_inputs`: validate testcases.json parses to a non-empty array +\n * app-metadata.json has required fields.\n * - `check_pre`: assert [PRE]-prefixed cases are contiguous from index 0.\n *\n * All six use the STATE convention \u2014 exit codes are states/check results, NOT\n * failures: only a spawn error throws; otherwise the stdout JSON + exitCode\n * are returned for the caller to branch on (non-zero = a state or a failed\n * check, reason in stdout).\n *\n * Only `run` needs a model. The factory takes `deps` so the `run` branch can\n * call `deps.resolveModelParams`; the other five branches ignore `deps`.\n */\nexport function makeA2hAutotestSelftestTool(deps: A2HHostDeps): ToolDefinition {\n return {\n description:\n \"HarmonyOS on-device self-test engine \u2014 grouped by noun, dispatched on `action`. `run`: hdc uninstall+install of HAP, then a detached @autotest/agent batch (multimodal model resolved by the host, piped via stdin; never env/argv/disk). Without `timeout` returns RUNNING immediately (exit 2); with `timeout` blocks until terminal (0 COMPLETED / 1 FAILED / 3 CRASHED / 4 NOT_STARTED / 5 TIMEOUT). `status`: probe on-disk state (0/2/3/4). `kill`: tear down the batch tree. `check_hap`/`check_inputs`/`check_pre`: pre-flight validators (0 = passed). Exit codes are states, not errors \u2014 branch on `status` in the returned JSON. Only `run` needs a model. Use this instead of running `node self-test-runner.ts` directly.\",\n args: {\n type: \"object\",\n properties: {\n action: {\n type: \"string\",\n enum: [\"run\", \"status\", \"kill\", \"check_hap\", \"check_inputs\", \"check_pre\"],\n description:\n \"run=\u88C5 HAP + \u8DD1 batch\uFF08\u9700\u6A21\u578B\uFF09\uFF1Bstatus=\u63A2\u72B6\u6001\uFF1Bkill=\u7EC8\u6B62 batch\uFF1Bcheck_hap=\u6821\u9A8C hap_path\uFF1Bcheck_inputs=\u6821\u9A8C testcases+metadata\uFF1Bcheck_pre=\u6821\u9A8C [PRE] \u8FDE\u7EED\",\n },\n // \u2500\u2500 run \u2500\u2500\n testcases: { type: \"string\", description: \"[run/check_inputs/check_pre] \u6D4B\u8BD5\u7528\u4F8B\u6587\u4EF6\uFF08JSON \u6570\u7EC4\u6216 JSONL\uFF09\" },\n hap: { type: \"string\", description: \"[run/check_hap] \u7B7E\u540D\u5305\uFF08.hap/.hsp \u6587\u4EF6\u3001\u76EE\u5F55\uFF0C\u6216\u9017\u53F7\u5206\u9694\u591A\u9879\uFF09\" },\n bundleName: { type: \"string\", description: \"[run] \u5305\u540D\uFF08bundle_name\uFF09\" },\n taskDir: {\n type: \"string\",\n description: \"[run/status/kill] \u4EFB\u52A1\u76EE\u5F55\uFF08run \u5185\u90E8\u5EFA task_<ts> \u5B50\u76EE\u5F55\uFF1B\u4E0D\u4F20\u5219\u9ED8\u8BA4 'task'\uFF09\u3002status/kill \u636E\u6B64\u627E batch.pid\",\n },\n outputDir: { type: \"string\", description: \"[run/status] \u65E5\u5FD7\u76EE\u5F55\uFF08\u4E0D\u4F20\u5219\u540C taskDir\uFF09\" },\n category: { type: \"string\", description: \"[run] \u6D4B\u8BD5\u5206\u7C7B\u540D\uFF0C\u4F20\u7ED9 batch-launcher\" },\n timeout: {\n type: [\"number\", \"string\"],\n description: \"[run] \u963B\u585E\u81F3\u7EC8\u6001\u6216\u8D85\u65F6\u3002\u6570\u503C=\u79D2\u6570\uFF1B\\\"auto\\\"=\u5F15\u64CE\u89E3\u6790 testcases \u540E\u6309\u7528\u4F8B\u6570\u00D7720s \u81EA\u52A8\u63A8\u5BFC\uFF08\u6BCF\u4F8B 10min + 20% \u4F59\u91CF\uFF09\u3002\u4E0D\u4F20\u5219\u7ACB\u5373\u8FD4\u56DE RUNNING\uFF1B\u8D85\u65F6\u540E\u81EA\u52A8 kill batch \u5E76\u8FD4\u56DE TIMEOUT\uFF08exit 5\uFF09\",\n },\n deviceSn: { type: \"string\", description: \"[run] \u8BBE\u5907\u5E8F\u5217\u53F7\uFF08\u2192 AutoTestAgent.deviceSn\uFF09\" },\n ip: { type: \"string\", description: \"[run] \u8BBE\u5907 IP\uFF08\u2192 AutoTestAgent.ip\uFF09\" },\n port: { type: \"string\", description: \"[run] \u8BBE\u5907\u7AEF\u53E3\uFF08\u2192 AutoTestAgent.port\uFF09\" },\n agentMode: { type: \"string\", description: \"[run] agent \u6A21\u5F0F\uFF08\u9ED8\u8BA4 single\uFF09\" },\n maxSteps: { type: \"number\", description: \"[run] \u5355\u7528\u4F8B\u6700\u5927\u6B65\u6570\" },\n promptVersion: { type: \"string\", description: \"[run] prompt \u7248\u672C\uFF08\u9ED8\u8BA4 v2.0\uFF09\" },\n // \u2500\u2500 check_inputs \u2500\u2500\n metadata: { type: \"string\", description: \"[check_inputs] app-metadata.json \u8DEF\u5F84\" },\n },\n required: [\"action\"],\n },\n execute: async (args: Record<string, unknown>, context: ToolContext) => {\n const action = String(args.action ?? \"\")\n\n // \u2500\u2500 run \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n if (action === \"run\") {\n const testcases = String(args.testcases ?? \"\")\n const hap = String(args.hap ?? \"\")\n const bundleName = String(args.bundleName ?? \"\")\n if (!testcases || !hap || !bundleName) {\n throw new Error(`${TOOL_ID}: \\`action:\"run\"\\` requires \\`testcases\\`, \\`hap\\`, \\`bundleName\\``)\n }\n // Effective taskDir drives both argv and the abort-cleanup kill spawn.\n // self-test-runner defaults to 'task' when --task-dir is absent, so mirror that.\n const taskDir = String(args.taskDir ?? \"task\")\n\n const argv = [\n \"run\",\n \"--testcases\", testcases,\n \"--hap\", hap,\n \"--bundle-name\", bundleName,\n \"--task-dir\", taskDir,\n \"--model-stdin\",\n ]\n if (args.outputDir) argv.push(\"--output-dir\", String(args.outputDir))\n if (args.category) argv.push(\"--category\", String(args.category))\n if (args.deviceSn) argv.push(\"--device-sn\", String(args.deviceSn))\n if (args.ip) argv.push(\"--ip\", String(args.ip))\n if (args.port) argv.push(\"--port\", String(args.port))\n if (args.agentMode) argv.push(\"--agent-mode\", String(args.agentMode))\n if (args.maxSteps !== undefined && args.maxSteps !== null) {\n argv.push(\"--max-steps\", String(args.maxSteps))\n }\n if (args.promptVersion) argv.push(\"--prompt-version\", String(args.promptVersion))\n if (args.timeout !== undefined && args.timeout !== null) {\n argv.push(\"--timeout\", String(args.timeout))\n }\n\n // Resolve the model via the host; null = host can't supply one \u2014 fail\n // fast rather than spawning a batch that dies mid-flight for lack of creds.\n const model = await deps.resolveModelParams(context.worktree)\n if (!model) {\n throw new Error(\n `${TOOL_ID}: \\`action:\"run\"\\` needs a model but deps.resolveModelParams returned null ` +\n `for worktree ${context.worktree ?? \"(none)\"} \u2014 configure the host's model provider`,\n )\n }\n const stdin = JSON.stringify({\n apiKey: model.apiKey,\n modelName: model.modelName,\n baseURL: model.baseURL,\n })\n\n // Abort cleanup: the detached batch survives SIGTERM of the\n // self-test-runner child, so on abort also fire `kill --task-dir`.\n // Fire-and-forget; best-effort.\n const onAbortKill = (): void => {\n try {\n const env: NodeJS.ProcessEnv = { ...process.env }\n // strip secrets defensively (kill needs no model)\n delete env.HOMETRANS_MODEL_API_KEY\n delete env.HOMETRANS_MODEL_NAME\n delete env.HOMETRANS_MODEL_BASE_URL\n delete env.GLM_API_KEY\n delete env.TEST_API_KEY\n const k = spawn(\n \"node\",\n [path.join(AUTOTEST_ROOT, SCRIPT_REL), \"kill\", \"--task-dir\", taskDir],\n { cwd: context.directory, env, stdio: \"ignore\", detached: true, windowsHide: true },\n )\n k.on(\"error\", () => { /* best effort \u2014 swallow */ })\n k.unref()\n } catch {\n // best effort\n }\n }\n if (!context.abort.aborted) {\n context.abort.addEventListener(\"abort\", onAbortKill)\n }\n\n const r = await runAutotestScript({\n scriptRel: SCRIPT_REL,\n argv,\n cwd: context.directory,\n abort: context.abort,\n stdin,\n })\n\n // Remove our abort listener; the shared harness removes its own on close.\n context.abort.removeEventListener(\"abort\", onAbortKill)\n\n if (r.spawnError) {\n throw new Error(`${TOOL_ID}: failed to spawn node \u2014 ${r.spawnError}`)\n }\n // Exit codes are states, not failures \u2014 surface the JSON + code.\n return { ok: r.exitCode === 0, exitCode: r.exitCode, output: r.stdout }\n }\n\n // \u2500\u2500 status \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n if (action === \"status\") {\n const taskDir = String(args.taskDir ?? \"\")\n if (!taskDir) {\n throw new Error(`${TOOL_ID}: \\`action:\"status\"\\` requires \\`taskDir\\``)\n }\n const argv = [\"status\", \"--task-dir\", taskDir]\n if (args.outputDir) argv.push(\"--output-dir\", String(args.outputDir))\n const r = await runAutotestScript({\n scriptRel: SCRIPT_REL,\n argv,\n cwd: context.directory,\n abort: context.abort,\n })\n if (r.spawnError) {\n throw new Error(`${TOOL_ID}: failed to spawn node \u2014 ${r.spawnError}`)\n }\n return { ok: r.exitCode === 0, exitCode: r.exitCode, output: r.stdout }\n }\n\n // \u2500\u2500 kill \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n if (action === \"kill\") {\n const taskDir = String(args.taskDir ?? \"\")\n if (!taskDir) {\n throw new Error(`${TOOL_ID}: \\`action:\"kill\"\\` requires \\`taskDir\\``)\n }\n const argv = [\"kill\", \"--task-dir\", taskDir]\n const r = await runAutotestScript({\n scriptRel: SCRIPT_REL,\n argv,\n cwd: context.directory,\n abort: context.abort,\n })\n if (r.spawnError) {\n throw new Error(`${TOOL_ID}: failed to spawn node \u2014 ${r.spawnError}`)\n }\n return { ok: r.exitCode === 0, exitCode: r.exitCode, output: r.stdout }\n }\n\n // \u2500\u2500 check_hap \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n if (action === \"check_hap\") {\n const hap = String(args.hap ?? \"\")\n if (!hap) {\n throw new Error(`${TOOL_ID}: \\`action:\"check_hap\"\\` requires \\`hap\\``)\n }\n const argv = [\"check-hap\", \"--hap\", hap]\n const r = await runAutotestScript({\n scriptRel: SCRIPT_REL,\n argv,\n cwd: context.directory,\n abort: context.abort,\n })\n if (r.spawnError) {\n throw new Error(`${TOOL_ID}: failed to spawn node \u2014 ${r.spawnError}`)\n }\n return { ok: r.exitCode === 0, exitCode: r.exitCode, output: r.stdout }\n }\n\n // \u2500\u2500 check_inputs \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n if (action === \"check_inputs\") {\n const testcases = String(args.testcases ?? \"\")\n const metadata = String(args.metadata ?? \"\")\n if (!testcases || !metadata) {\n throw new Error(`${TOOL_ID}: \\`action:\"check_inputs\"\\` requires \\`testcases\\` and \\`metadata\\``)\n }\n const argv = [\"check-inputs\", \"--testcases\", testcases, \"--metadata\", metadata]\n const r = await runAutotestScript({\n scriptRel: SCRIPT_REL,\n argv,\n cwd: context.directory,\n abort: context.abort,\n })\n if (r.spawnError) {\n throw new Error(`${TOOL_ID}: failed to spawn node \u2014 ${r.spawnError}`)\n }\n return { ok: r.exitCode === 0, exitCode: r.exitCode, output: r.stdout }\n }\n\n // \u2500\u2500 check_pre \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n if (action === \"check_pre\") {\n const testcases = String(args.testcases ?? \"\")\n if (!testcases) {\n throw new Error(`${TOOL_ID}: \\`action:\"check_pre\"\\` requires \\`testcases\\``)\n }\n const argv = [\"check-pre\", \"--testcases\", testcases]\n const r = await runAutotestScript({\n scriptRel: SCRIPT_REL,\n argv,\n cwd: context.directory,\n abort: context.abort,\n })\n if (r.spawnError) {\n throw new Error(`${TOOL_ID}: failed to spawn node \u2014 ${r.spawnError}`)\n }\n return { ok: r.exitCode === 0, exitCode: r.exitCode, output: r.stdout }\n }\n\n throw new Error(\n `${TOOL_ID}: unknown \\`action\\` \"${action}\" (expected run|status|kill|check_hap|check_inputs|check_pre)`,\n )\n },\n }\n}\n"],
5
- "mappings": "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",
6
- "names": ["path", "path", "path", "path", "path", "path", "spawn", "path", "path", "SECRET_ENV", "cap", "spawn", "path", "path", "SCRIPT_REL", "path", "TOOL_ID", "path", "SCRIPT_REL", "path", "TOOL_ID", "spawn", "path", "SCRIPT_REL", "path", "TOOL_ID", "spawn", "path"]
3
+ "sources": ["../src/index.ts", "../src/paths.ts", "../src/discovery.ts", "../src/env.ts", "../src/tools/a2h-app-feature-verify.ts"],
4
+ "sourcesContent": ["import path from \"node:path\"\r\nimport type { A2HHostDeps, Plugin, PluginInput, PluginHooks } from \"./types\"\r\nimport { PKG_ROOT } from \"./paths\"\r\nimport { discoverA2HAgents } from \"./discovery\"\r\nimport { buildA2HEnvBase } from \"./env\"\r\nimport { makeA2hAppFeatureVerifyTool } from \"./tools/a2h-app-feature-verify\"\r\n\r\n// Public re-exports \u2014 the package's external surface (dev eco code loads\r\n// dist/index.js at runtime and calls `createServer`; `A2HHostDeps` /\r\n// `PLUGIN_ID` are kept exported for any TS consumers).\r\nexport type { A2HHostDeps } from \"./types\"\r\nexport { PLUGIN_ID } from \"./paths\"\r\n\r\n/**\r\n * Create the A2H DevEco Code plugin.\r\n *\r\n * This is a self-contained plugin - skills/, agents/, and tools/\r\n * are bundled inside the package, NOT referenced from HomeTrans.\r\n * A2H-Plugin is the independently publishable \"plugin edition\" of HomeTrans.\r\n *\r\n * @param deps - host dependencies injected by DevEco Code's thin bridge.\r\n * The A2H Plugin defines what it needs (A2HHostDeps);\r\n * DevEco Code provides how to resolve it.\r\n * @returns A Plugin function ready to be registered as a built-in plugin.\r\n */\r\nexport function createServer(deps: A2HHostDeps): Plugin {\r\n return async (input: PluginInput): Promise<PluginHooks> => {\r\n // All A2H assets live inside this package (self-contained, no HomeTrans dep)\r\n const skillsRoot = path.join(PKG_ROOT, \"skills\")\r\n const agentsRoot = path.join(PKG_ROOT, \"agents\")\r\n\r\n // Auto-discover agents: scan top-level agents/*.md, parse `name:` from\r\n // frontmatter, strip frontmatter for prompt body. Adding/removing/renaming\r\n // an agent is just a file drop \u2014 no code change, no rebuild.\r\n const agentPrompts = discoverA2HAgents(agentsRoot)\r\n\r\n return {\r\n dispose: async () => {\r\n agentPrompts.clear()\r\n },\r\n\r\n // \u2500\u2500 config hook: register skills search paths and agents \u2500\u2500\r\n config: async (cfg: Record<string, unknown>) => {\r\n // --- Skills ---\r\n const skills = (cfg.skills as Record<string, unknown>) || {}\r\n cfg.skills = skills\r\n\r\n // Register the bundled skills/ directory as an additional search path.\r\n // All bundled skills are user-visible \u2014 there is no partial-visibility\r\n // (hidden) layer anymore, so we only register the search path here.\r\n const paths = (skills.paths as string[]) || []\r\n skills.paths = paths\r\n if (!paths.includes(skillsRoot)) {\r\n paths.push(skillsRoot)\r\n }\r\n\r\n // --- Agents ---\r\n const agent = (cfg.agent as Record<string, Record<string, unknown>>) || {}\r\n cfg.agent = agent\r\n\r\n for (const [name, prompt] of agentPrompts) {\r\n // Don't overwrite if the user already configured this agent\r\n if (!agent[name]) {\r\n agent[name] = {\r\n mode: \"subagent\",\r\n hidden: true,\r\n prompt,\r\n }\r\n }\r\n }\r\n\r\n },\r\n\r\n // \u2500\u2500 shell.env hook: inject DevEco Studio / SDK path vars only. The \u2500\u2500\r\n // multimodal model apiKey is deliberately NOT put in env (it would leak\r\n // to every adb/hdc descendant); the a2h_app_feature_verify tool pipes it\r\n // to the script over stdin instead. See src/env.ts.\r\n \"shell.env\": async (\r\n shellInput: { cwd: string; sessionID?: string; callID?: string },\r\n output: { env: Record<string, string> },\r\n ) => {\r\n const a2hEnv = await buildA2HEnvBase(deps)\r\n Object.assign(output.env, a2hEnv)\r\n },\r\n\r\n // \u2500\u2500 tool hook: run the UI-verification phone agent without leaking the\r\n // model apiKey into env/argv \u2014 it is resolved from the host and piped to\r\n // the script over stdin. See src/tools/a2h-app-feature-verify.ts.\r\n //\r\n // The AutoTest engine (selftest / testcases / report / resolve-metadata)\r\n // is no longer vendored here \u2014 it lives in the HomeTrans package and is\r\n // invoked directly via `node` by agents/self-tester.md (see that file's\r\n // \"Resolving the AutoTest Tools Directory\" section). a2h therefore ships\r\n // only this one plugin tool and carries no autotest runtime dependency.\r\n tool: {\r\n a2h_app_feature_verify: makeA2hAppFeatureVerifyTool(deps),\r\n },\r\n }\r\n }\r\n}\r\n", "import path from \"node:path\"\r\nimport { fileURLToPath } from \"node:url\"\r\n\r\n// Resolve package root (one directory above dist/ or src/). Computed once here\r\n// and imported everywhere else so there's a single source of truth. After\r\n// esbuild bundling, import.meta.url points at dist/index.js, so this still\r\n// resolves to the package root at runtime.\r\nconst __filename = fileURLToPath(import.meta.url)\r\nconst __dirname = path.dirname(__filename)\r\n\r\n/** Absolute path to the A2H-Plugin package root. */\r\nexport const PKG_ROOT = path.resolve(__dirname, \"..\")\r\n\r\n/** Public plugin id. */\r\nexport const PLUGIN_ID = \"android2harmony\"\r\n", "import path from \"node:path\"\r\nimport fs from \"node:fs\"\r\n\r\n/**\r\n * Strip YAML frontmatter (--- ... ---) from agent .md files.\r\n * Agent .md files have metadata in frontmatter; only the body\r\n * should be used as the LLM prompt.\r\n */\r\nexport function stripFrontmatter(content: string): string {\r\n const match = content.match(/^---\\s*\\n([\\s\\S]*?)\\n---\\s*\\n/)\r\n return match ? content.slice(match[0].length).trim() : content\r\n}\r\n\r\n/**\r\n * Minimal YAML frontmatter extractor for A2H-owned `.md` files (SKILL.md\r\n * and agent `.md`). A2H controls the frontmatter format, so we only need\r\n * the top-level `name` (a scalar). This avoids pulling a YAML dependency\r\n * into the plugin.\r\n *\r\n * Returns the parsed `name`, or null when the file has no usable frontmatter\r\n * (e.g. a README dropped into a directory that's also scanned for agents).\r\n */\r\nexport function parseFrontmatter(content: string): string | null {\r\n const match = content.match(/^---\\s*\\r?\\n([\\s\\S]*?)\\r?\\n---/)\r\n if (!match) return null\r\n const lines = match[1].split(/\\r?\\n/)\r\n\r\n for (const line of lines) {\r\n if (!line.trim() || line.trim().startsWith(\"#\")) continue\r\n if (line.length - line.trimStart().length !== 0) continue // skip indented (e.g. metadata block) lines\r\n\r\n const kv = line.trim().match(/^([a-zA-Z_][a-zA-Z0-9_-]*):\\s*(.*)$/)\r\n if (!kv) continue\r\n if (kv[1] === \"name\") {\r\n return kv[2].trim().replace(/^[\"']|[\"']$/g, \"\")\r\n }\r\n }\r\n\r\n return null\r\n}\r\n\r\n/**\r\n * Scan the bundled agents/ directory and return a `name \u2192 prompt body` map.\r\n *\r\n * Only top-level `.md` files are considered (subdirectories like `scripts/`\r\n * are skipped via `isFile()`). Files without a `name:` field in frontmatter\r\n * (e.g. a stray README) are silently skipped \u2014 the frontmatter name is the\r\n * source of truth, not the file basename. The body (frontmatter stripped)\r\n * becomes the LLM prompt.\r\n *\r\n * Adding a new agent is therefore a zero-code, zero-rebuild change: just\r\n * drop an `.md` with `name:` frontmatter into `agents/`.\r\n */\r\nexport function discoverA2HAgents(agentsRoot: string): Map<string, string> {\r\n const result = new Map<string, string>()\r\n let entries: fs.Dirent[]\r\n try {\r\n entries = fs.readdirSync(agentsRoot, { withFileTypes: true })\r\n } catch {\r\n return result\r\n }\r\n for (const entry of entries) {\r\n if (!entry.isFile() || !entry.name.endsWith(\".md\")) continue\r\n const file = path.join(agentsRoot, entry.name)\r\n let raw: string\r\n try {\r\n raw = fs.readFileSync(file, \"utf-8\")\r\n } catch {\r\n continue\r\n }\r\n const name = parseFrontmatter(raw)\r\n if (!name) continue // not a valid agent file (no `name:` frontmatter)\r\n result.set(name, stripFrontmatter(raw))\r\n }\r\n return result\r\n}\r\n", "import path from \"node:path\"\nimport type { A2HHostDeps } from \"./types\"\n\n/**\n * Build the A2H env var set from host-injected deps. Shared by the `shell.env`\n * hook (so every shell session sees these). Env vars are the single source of\n * truth in the plugin context for non-secret paths.\n *\n * Contains only DevEco/SDK path vars. The multimodal model apiKey is NOT\n * injected here anymore \u2014 putting it in `process.env` leaked it to every\n * descendant process (adb/hdc, sub-agents), to `/proc/<pid>/environ`, crash\n * dumps, and any `env`/`printenv` output. Instead the `a2h_app_feature_verify`\n * plugin tool resolves the params via `A2HHostDeps.resolveModelParams` and pipes\n * them to the script over stdin (`--model-stdin`), so the key stays in process\n * memory + the stdin pipe and never enters env/argv/disk.\n *\n * Nothing secret is present in this env set, but it is still good hygiene not\n * to dump it into logs verbatim.\n */\nexport async function buildA2HEnvBase(\n deps: A2HHostDeps,\n): Promise<Record<string, string>> {\n const env: Record<string, string> = {}\n\n const home = await deps.resolveDevEcoHome()\n if (home) {\n const sdk = deps.resolveSdkPath(home)\n env[\"DEVECO_HOME\"] = home\n env[\"DEVECO_SDK_HOME\"] = sdk\n env[\"OHOS_SDK_PATH\"] = path.join(sdk, \"default\", \"openharmony\", \"ets\")\n env[\"HMS_SDK_PATH\"] = path.join(sdk, \"default\", \"hms\", \"ets\")\n }\n\n return env\n}\n", "import { spawn } from \"node:child_process\"\nimport path from \"node:path\"\nimport { PKG_ROOT } from \"../paths\"\nimport type { A2HHostDeps, ToolContext, ToolDefinition } from \"../types\"\n\n// The bundled UI-feature-verification script (TypeScript, run directly by\n// Node \u2265 22.18 which strips types natively \u2014 same as when the agent ran\n// `node app_feature_verify.ts` via the shell).\nconst SCRIPT_PATH = path.join(\n PKG_ROOT,\n \"skills\",\n \"hmos-incremental-ui-align\",\n \"scripts\",\n \"app_feature_verify.ts\",\n)\n\n/**\n * Env vars that may carry a model apiKey. Stripped from the child's env so the\n * key is never inherited by the spawned script or its adb/hdc descendants. The\n * script instead receives the key via the stdin pipe (`--model-stdin`).\n */\nconst SECRET_ENV = [\n \"HOMETRANS_MODEL_API_KEY\",\n \"HOMETRANS_MODEL_NAME\",\n \"HOMETRANS_MODEL_BASE_URL\",\n \"GLM_API_KEY\",\n \"TEST_API_KEY\",\n] as const\n\n/** Cap stdout returned to the host so a runaway loop can't overflow the chat. */\nfunction cap(s: string, max = 50000): string {\n if (s.length <= max) return s\n const half = Math.floor(max / 2)\n return s.slice(0, half) + `\\n\u2026[truncated ${s.length - max} chars]\u2026\\n` + s.slice(-half)\n}\n\n/**\n * Build the `a2h_app_feature_verify` plugin tool.\n *\n * Spawns app_feature_verify.ts with the model config piped to its stdin, so the\n * apiKey travels only through the OS pipe + process memory \u2014 never env, argv,\n * or a plugin-written file. The host resolves the params (apiKey) via\n * `deps.resolveModelParams`; the tool is the only thing that ever holds it.\n */\nexport function makeA2hAppFeatureVerifyTool(deps: A2HHostDeps): ToolDefinition {\n return {\n description:\n \"Run the a2h UI feature-verification phone agent (app_feature_verify.ts) to navigate an app and verify a feature path on a connected device. The multimodal model apiKey is resolved from the host and piped to the script over stdin \u2014 it is never placed in env or argv, so it is not visible to the spawned adb/hdc subprocesses. Use this instead of running `node app_feature_verify.ts` directly. The script runs its internal agent loop (screenshot \u2192 model \u2192 action) up to maxSteps and prints per-step thinking + actions; the full stdout is returned on success. One of task/prompt/feature is required; app and package are required.\",\n args: {\n type: \"object\",\n properties: {\n app: { type: \"string\", description: \"\u76EE\u6807 app \u663E\u793A\u540D\uFF08required\uFF09\" },\n package: { type: \"string\", description: \"\u76EE\u6807 app \u5305\u540D\uFF08required\uFF09\" },\n device: {\n type: \"string\",\n enum: [\"hdc\", \"adb\"],\n default: \"hdc\",\n description: \"\u8BBE\u5907\u9A71\u52A8\uFF1Ahdc(HarmonyOS) / adb(Android)\",\n },\n task: {\n type: \"string\",\n description: \"L1/L2/L3 \u4EFB\u52A1\u8DEF\u5F84\uFF0C\u5982 L1\u76F8\u518C\u8BE6\u60C5L2\u66F4\u591A\uFF08\u4E0E prompt/feature \u4E09\u9009\u4E00\uFF09\",\n },\n prompt: { type: \"string\", description: \"\u81EA\u7531 prompt\uFF08\u4E0E task/feature \u4E09\u9009\u4E00\uFF09\" },\n feature: { type: \"string\", description: \".md \u529F\u80FD\u6E05\u5355\u8DEF\u5F84\uFF08\u4E0E task/prompt \u4E09\u9009\u4E00\uFF09\" },\n maxSteps: { type: \"number\", default: 30, description: \"\u6700\u5927 agent \u6B65\u6570\" },\n appHints: { type: \"string\", description: \"\u5E94\u7528\u5BFC\u822A\u63D0\u793A\" },\n serial: { type: \"string\", description: \"\u8BBE\u5907\u5E8F\u5217\u53F7\" },\n limit: { type: \"number\", description: \"--feature \u6A21\u5F0F\u4E0B\u53D6\u524D N \u6761\" },\n offset: { type: \"number\", default: 1, description: \"--feature \u6A21\u5F0F\u4E0B\u504F\u79FB\" },\n quiet: { type: \"boolean\", default: false, description: \"\u6291\u5236\u63D0\u793A\u6027\u8F93\u51FA\" },\n },\n required: [\"app\", \"package\"],\n },\n execute: async (args: Record<string, unknown>, context: ToolContext) => {\n const app = String(args.app ?? \"\")\n const pkg = String(args.package ?? \"\")\n if (!app || !pkg) {\n throw new Error(\"a2h_app_feature_verify: `app` and `package` are required\")\n }\n const task = args.task != null ? String(args.task) : null\n const prompt = args.prompt != null ? String(args.prompt) : null\n const feature = args.feature != null ? String(args.feature) : null\n if (!task && !prompt && !feature) {\n throw new Error(\"a2h_app_feature_verify: one of `task` / `prompt` / `feature` is required\")\n }\n\n // Resolve host model params \u2014 the apiKey lives only in this process's\n // memory from here on.\n const model = await deps.resolveModelParams(context.worktree)\n if (!model || !model.apiKey) {\n throw new Error(\"a2h_app_feature_verify: host did not provide model params (apiKey missing)\")\n }\n\n // Assemble argv from provided args; append --model-stdin so the script\n // reads model config from stdin rather than env/argv.\n const argv: string[] = [\"--app\", app, \"--package\", pkg, \"--device\", String(args.device ?? \"hdc\")]\n if (task) argv.push(\"--task\", task)\n if (prompt) argv.push(\"--prompt\", prompt)\n if (feature) argv.push(\"--feature\", feature)\n if (args.maxSteps != null) argv.push(\"--max-steps\", String(args.maxSteps))\n if (args.appHints != null) argv.push(\"--app-hints\", String(args.appHints))\n if (args.serial != null) argv.push(\"--serial\", String(args.serial))\n if (args.limit != null) argv.push(\"--limit\", String(args.limit))\n if (args.offset != null) argv.push(\"--offset\", String(args.offset))\n if (args.quiet === true) argv.push(\"--quiet\")\n argv.push(\"--model-stdin\")\n\n // Sanitize env: strip every var that could carry a model apiKey so the\n // child (and its adb/hdc descendants) cannot read it from the environment.\n const env: NodeJS.ProcessEnv = { ...process.env }\n for (const k of SECRET_ENV) delete env[k]\n\n // Spawn via PATH `node` (same binary the shell used to run the .ts script\n // \u2014 guaranteed \u2265 22.18 for native TS). process.execPath might be an older\n // host node, so prefer PATH resolution to match prior working behavior.\n const child = spawn(\"node\", [SCRIPT_PATH, ...argv], {\n cwd: context.directory,\n env,\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\n })\n\n // Pipe the model config to the child's stdin and close it \u2014 the key\n // traverses only the OS pipe, never env/argv/disk.\n child.stdin.end(\n JSON.stringify({ apiKey: model.apiKey, modelName: model.modelName, baseURL: model.baseURL }),\n )\n\n let stdout = \"\"\n let stderr = \"\"\n let spawnError = \"\"\n const stepRe = /\u6267\u884C\u52A8\u4F5C:\\s*(.*)/g\n child.stdout.on(\"data\", (chunk: Buffer) => {\n const text = chunk.toString(\"utf8\")\n stdout += text\n // Surface the latest step's action as the tool's running title.\n const matches = [...text.matchAll(stepRe)]\n if (matches.length) {\n const last = matches[matches.length - 1][1].trim()\n try {\n context.metadata({ title: `UI verify: ${last.slice(0, 80)}` })\n } catch {\n /* host may not implement metadata \u2014 non-essential */\n }\n }\n })\n child.stderr.on(\"data\", (chunk: Buffer) => {\n stderr += chunk.toString(\"utf8\")\n })\n\n // Honor abort: kill the child if the host cancels the tool call.\n const onAbort = () => {\n if (!child.killed) child.kill(\"SIGTERM\")\n }\n context.abort.addEventListener(\"abort\", onAbort)\n\n const code: number = await new Promise((resolve) => {\n child.on(\"close\", resolve)\n child.on(\"error\", (err: Error) => {\n spawnError = err.message\n resolve(-1)\n })\n })\n context.abort.removeEventListener(\"abort\", onAbort)\n\n if (code !== 0) {\n if (spawnError) {\n throw new Error(`a2h_app_feature_verify: failed to spawn node \u2014 ${spawnError}`)\n }\n const tail = stderr.slice(-2000)\n throw new Error(`a2h_app_feature_verify failed (exit ${code}): ${tail || \"(no stderr)\"}`)\n }\n return { ok: true, exitCode: code, output: cap(stdout) }\n },\n }\n}\n"],
5
+ "mappings": ";AAAA,OAAOA,WAAU;;;ACAjB,OAAO,UAAU;AACjB,SAAS,qBAAqB;AAM9B,IAAM,aAAa,cAAc,YAAY,GAAG;AAChD,IAAM,YAAY,KAAK,QAAQ,UAAU;AAGlC,IAAM,WAAW,KAAK,QAAQ,WAAW,IAAI;AAG7C,IAAM,YAAY;;;ACdzB,OAAOC,WAAU;AACjB,OAAO,QAAQ;AAOR,SAAS,iBAAiB,SAAyB;AACxD,QAAM,QAAQ,QAAQ,MAAM,+BAA+B;AAC3D,SAAO,QAAQ,QAAQ,MAAM,MAAM,CAAC,EAAE,MAAM,EAAE,KAAK,IAAI;AACzD;AAWO,SAAS,iBAAiB,SAAgC;AAC/D,QAAM,QAAQ,QAAQ,MAAM,gCAAgC;AAC5D,MAAI,CAAC,MAAO,QAAO;AACnB,QAAM,QAAQ,MAAM,CAAC,EAAE,MAAM,OAAO;AAEpC,aAAW,QAAQ,OAAO;AACxB,QAAI,CAAC,KAAK,KAAK,KAAK,KAAK,KAAK,EAAE,WAAW,GAAG,EAAG;AACjD,QAAI,KAAK,SAAS,KAAK,UAAU,EAAE,WAAW,EAAG;AAEjD,UAAM,KAAK,KAAK,KAAK,EAAE,MAAM,qCAAqC;AAClE,QAAI,CAAC,GAAI;AACT,QAAI,GAAG,CAAC,MAAM,QAAQ;AACpB,aAAO,GAAG,CAAC,EAAE,KAAK,EAAE,QAAQ,gBAAgB,EAAE;AAAA,IAChD;AAAA,EACF;AAEA,SAAO;AACT;AAcO,SAAS,kBAAkB,YAAyC;AACzE,QAAM,SAAS,oBAAI,IAAoB;AACvC,MAAI;AACJ,MAAI;AACF,cAAU,GAAG,YAAY,YAAY,EAAE,eAAe,KAAK,CAAC;AAAA,EAC9D,QAAQ;AACN,WAAO;AAAA,EACT;AACA,aAAW,SAAS,SAAS;AAC3B,QAAI,CAAC,MAAM,OAAO,KAAK,CAAC,MAAM,KAAK,SAAS,KAAK,EAAG;AACpD,UAAM,OAAOA,MAAK,KAAK,YAAY,MAAM,IAAI;AAC7C,QAAI;AACJ,QAAI;AACF,YAAM,GAAG,aAAa,MAAM,OAAO;AAAA,IACrC,QAAQ;AACN;AAAA,IACF;AACA,UAAM,OAAO,iBAAiB,GAAG;AACjC,QAAI,CAAC,KAAM;AACX,WAAO,IAAI,MAAM,iBAAiB,GAAG,CAAC;AAAA,EACxC;AACA,SAAO;AACT;;;AC3EA,OAAOC,WAAU;AAmBjB,eAAsB,gBACpB,MACiC;AACjC,QAAM,MAA8B,CAAC;AAErC,QAAM,OAAO,MAAM,KAAK,kBAAkB;AAC1C,MAAI,MAAM;AACR,UAAM,MAAM,KAAK,eAAe,IAAI;AACpC,QAAI,aAAa,IAAI;AACrB,QAAI,iBAAiB,IAAI;AACzB,QAAI,eAAe,IAAIA,MAAK,KAAK,KAAK,WAAW,eAAe,KAAK;AACrE,QAAI,cAAc,IAAIA,MAAK,KAAK,KAAK,WAAW,OAAO,KAAK;AAAA,EAC9D;AAEA,SAAO;AACT;;;AClCA,SAAS,aAAa;AACtB,OAAOC,WAAU;AAOjB,IAAM,cAAcC,MAAK;AAAA,EACvB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAOA,IAAM,aAAa;AAAA,EACjB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAGA,SAAS,IAAI,GAAW,MAAM,KAAe;AAC3C,MAAI,EAAE,UAAU,IAAK,QAAO;AAC5B,QAAM,OAAO,KAAK,MAAM,MAAM,CAAC;AAC/B,SAAO,EAAE,MAAM,GAAG,IAAI,IAAI;AAAA,mBAAiB,EAAE,SAAS,GAAG;AAAA,IAAe,EAAE,MAAM,CAAC,IAAI;AACvF;AAUO,SAAS,4BAA4B,MAAmC;AAC7E,SAAO;AAAA,IACL,aACE;AAAA,IACF,MAAM;AAAA,MACJ,MAAM;AAAA,MACN,YAAY;AAAA,QACV,KAAK,EAAE,MAAM,UAAU,aAAa,0DAAuB;AAAA,QAC3D,SAAS,EAAE,MAAM,UAAU,aAAa,oDAAsB;AAAA,QAC9D,QAAQ;AAAA,UACN,MAAM;AAAA,UACN,MAAM,CAAC,OAAO,KAAK;AAAA,UACnB,SAAS;AAAA,UACT,aAAa;AAAA,QACf;AAAA,QACA,MAAM;AAAA,UACJ,MAAM;AAAA,UACN,aAAa;AAAA,QACf;AAAA,QACA,QAAQ,EAAE,MAAM,UAAU,aAAa,wEAAgC;AAAA,QACvE,SAAS,EAAE,MAAM,UAAU,aAAa,4FAAgC;AAAA,QACxE,UAAU,EAAE,MAAM,UAAU,SAAS,IAAI,aAAa,kCAAc;AAAA,QACpE,UAAU,EAAE,MAAM,UAAU,aAAa,uCAAS;AAAA,QAClD,QAAQ,EAAE,MAAM,UAAU,aAAa,iCAAQ;AAAA,QAC/C,OAAO,EAAE,MAAM,UAAU,aAAa,oDAAsB;AAAA,QAC5D,QAAQ,EAAE,MAAM,UAAU,SAAS,GAAG,aAAa,2CAAkB;AAAA,QACrE,OAAO,EAAE,MAAM,WAAW,SAAS,OAAO,aAAa,6CAAU;AAAA,MACnE;AAAA,MACA,UAAU,CAAC,OAAO,SAAS;AAAA,IAC7B;AAAA,IACA,SAAS,OAAO,MAA+B,YAAyB;AACtE,YAAM,MAAM,OAAO,KAAK,OAAO,EAAE;AACjC,YAAM,MAAM,OAAO,KAAK,WAAW,EAAE;AACrC,UAAI,CAAC,OAAO,CAAC,KAAK;AAChB,cAAM,IAAI,MAAM,0DAA0D;AAAA,MAC5E;AACA,YAAM,OAAO,KAAK,QAAQ,OAAO,OAAO,KAAK,IAAI,IAAI;AACrD,YAAM,SAAS,KAAK,UAAU,OAAO,OAAO,KAAK,MAAM,IAAI;AAC3D,YAAM,UAAU,KAAK,WAAW,OAAO,OAAO,KAAK,OAAO,IAAI;AAC9D,UAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,SAAS;AAChC,cAAM,IAAI,MAAM,0EAA0E;AAAA,MAC5F;AAIA,YAAM,QAAQ,MAAM,KAAK,mBAAmB,QAAQ,QAAQ;AAC5D,UAAI,CAAC,SAAS,CAAC,MAAM,QAAQ;AAC3B,cAAM,IAAI,MAAM,4EAA4E;AAAA,MAC9F;AAIA,YAAM,OAAiB,CAAC,SAAS,KAAK,aAAa,KAAK,YAAY,OAAO,KAAK,UAAU,KAAK,CAAC;AAChG,UAAI,KAAM,MAAK,KAAK,UAAU,IAAI;AAClC,UAAI,OAAQ,MAAK,KAAK,YAAY,MAAM;AACxC,UAAI,QAAS,MAAK,KAAK,aAAa,OAAO;AAC3C,UAAI,KAAK,YAAY,KAAM,MAAK,KAAK,eAAe,OAAO,KAAK,QAAQ,CAAC;AACzE,UAAI,KAAK,YAAY,KAAM,MAAK,KAAK,eAAe,OAAO,KAAK,QAAQ,CAAC;AACzE,UAAI,KAAK,UAAU,KAAM,MAAK,KAAK,YAAY,OAAO,KAAK,MAAM,CAAC;AAClE,UAAI,KAAK,SAAS,KAAM,MAAK,KAAK,WAAW,OAAO,KAAK,KAAK,CAAC;AAC/D,UAAI,KAAK,UAAU,KAAM,MAAK,KAAK,YAAY,OAAO,KAAK,MAAM,CAAC;AAClE,UAAI,KAAK,UAAU,KAAM,MAAK,KAAK,SAAS;AAC5C,WAAK,KAAK,eAAe;AAIzB,YAAM,MAAyB,EAAE,GAAG,QAAQ,IAAI;AAChD,iBAAW,KAAK,WAAY,QAAO,IAAI,CAAC;AAKxC,YAAM,QAAQ,MAAM,QAAQ,CAAC,aAAa,GAAG,IAAI,GAAG;AAAA,QAClD,KAAK,QAAQ;AAAA,QACb;AAAA,QACA,OAAO,CAAC,QAAQ,QAAQ,MAAM;AAAA,MAChC,CAAC;AAID,YAAM,MAAM;AAAA,QACV,KAAK,UAAU,EAAE,QAAQ,MAAM,QAAQ,WAAW,MAAM,WAAW,SAAS,MAAM,QAAQ,CAAC;AAAA,MAC7F;AAEA,UAAI,SAAS;AACb,UAAI,SAAS;AACb,UAAI,aAAa;AACjB,YAAM,SAAS;AACf,YAAM,OAAO,GAAG,QAAQ,CAAC,UAAkB;AACzC,cAAM,OAAO,MAAM,SAAS,MAAM;AAClC,kBAAU;AAEV,cAAM,UAAU,CAAC,GAAG,KAAK,SAAS,MAAM,CAAC;AACzC,YAAI,QAAQ,QAAQ;AAClB,gBAAM,OAAO,QAAQ,QAAQ,SAAS,CAAC,EAAE,CAAC,EAAE,KAAK;AACjD,cAAI;AACF,oBAAQ,SAAS,EAAE,OAAO,cAAc,KAAK,MAAM,GAAG,EAAE,CAAC,GAAG,CAAC;AAAA,UAC/D,QAAQ;AAAA,UAER;AAAA,QACF;AAAA,MACF,CAAC;AACD,YAAM,OAAO,GAAG,QAAQ,CAAC,UAAkB;AACzC,kBAAU,MAAM,SAAS,MAAM;AAAA,MACjC,CAAC;AAGD,YAAM,UAAU,MAAM;AACpB,YAAI,CAAC,MAAM,OAAQ,OAAM,KAAK,SAAS;AAAA,MACzC;AACA,cAAQ,MAAM,iBAAiB,SAAS,OAAO;AAE/C,YAAM,OAAe,MAAM,IAAI,QAAQ,CAAC,YAAY;AAClD,cAAM,GAAG,SAAS,OAAO;AACzB,cAAM,GAAG,SAAS,CAAC,QAAe;AAChC,uBAAa,IAAI;AACjB,kBAAQ,EAAE;AAAA,QACZ,CAAC;AAAA,MACH,CAAC;AACD,cAAQ,MAAM,oBAAoB,SAAS,OAAO;AAElD,UAAI,SAAS,GAAG;AACd,YAAI,YAAY;AACd,gBAAM,IAAI,MAAM,uDAAkD,UAAU,EAAE;AAAA,QAChF;AACA,cAAM,OAAO,OAAO,MAAM,IAAK;AAC/B,cAAM,IAAI,MAAM,uCAAuC,IAAI,MAAM,QAAQ,aAAa,EAAE;AAAA,MAC1F;AACA,aAAO,EAAE,IAAI,MAAM,UAAU,MAAM,QAAQ,IAAI,MAAM,EAAE;AAAA,IACzD;AAAA,EACF;AACF;;;AJtJO,SAAS,aAAa,MAA2B;AACtD,SAAO,OAAO,UAA6C;AAEzD,UAAM,aAAaC,MAAK,KAAK,UAAU,QAAQ;AAC/C,UAAM,aAAaA,MAAK,KAAK,UAAU,QAAQ;AAK/C,UAAM,eAAe,kBAAkB,UAAU;AAEjD,WAAO;AAAA,MACL,SAAS,YAAY;AACnB,qBAAa,MAAM;AAAA,MACrB;AAAA;AAAA,MAGA,QAAQ,OAAO,QAAiC;AAE9C,cAAM,SAAU,IAAI,UAAsC,CAAC;AAC3D,YAAI,SAAS;AAKb,cAAM,QAAS,OAAO,SAAsB,CAAC;AAC7C,eAAO,QAAQ;AACf,YAAI,CAAC,MAAM,SAAS,UAAU,GAAG;AAC/B,gBAAM,KAAK,UAAU;AAAA,QACvB;AAGA,cAAM,QAAS,IAAI,SAAqD,CAAC;AACzE,YAAI,QAAQ;AAEZ,mBAAW,CAAC,MAAM,MAAM,KAAK,cAAc;AAEzC,cAAI,CAAC,MAAM,IAAI,GAAG;AAChB,kBAAM,IAAI,IAAI;AAAA,cACZ,MAAM;AAAA,cACN,QAAQ;AAAA,cACR;AAAA,YACF;AAAA,UACF;AAAA,QACF;AAAA,MAEF;AAAA;AAAA;AAAA;AAAA;AAAA,MAMA,aAAa,OACX,YACA,WACG;AACH,cAAM,SAAS,MAAM,gBAAgB,IAAI;AACzC,eAAO,OAAO,OAAO,KAAK,MAAM;AAAA,MAClC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAWA,MAAM;AAAA,QACJ,wBAAwB,4BAA4B,IAAI;AAAA,MAC1D;AAAA,IACF;AAAA,EACF;AACF;",
6
+ "names": ["path", "path", "path", "path", "path", "path"]
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "android2harmony",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "description": "A2H (Android-to-HarmonyOS) Plugin for DevEco Code — standalone plugin containing A2H skills, agents, and tools for converting Android apps to HarmonyOS",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -14,8 +14,7 @@
14
14
  "files": [
15
15
  "dist",
16
16
  "skills",
17
- "agents",
18
- "tools"
17
+ "agents"
19
18
  ],
20
19
  "scripts": {
21
20
  "build": "node scripts/build.js",
@@ -24,11 +23,7 @@
24
23
  "engines": {
25
24
  "node": ">=22.18.0"
26
25
  },
27
- "dependencies": {
28
- "@autotest/agent": "file:tools/autotest/deps/autotest-agent-0.1.1.tgz",
29
- "commander": "^14.0.0",
30
- "jsonc-parser": "^3.3.1"
31
- },
26
+ "dependencies": {},
32
27
  "devDependencies": {
33
28
  "@types/node": "^22.0.0",
34
29
  "esbuild": "^0.25.0",
@@ -1,326 +0,0 @@
1
- /**
2
- * Batch launcher — the detached background process that runs the Node.js
3
- * `@autotest/agent` engine programmatically via `new AutoTestAgent({...})`.
4
- *
5
- * Spawned detached by `self-test-runner.ts` `cmdRun` (which writes `batch.pid` +
6
- * emits `{status:'RUNNING', pid}` then exits). This process:
7
- *
8
- * 1. constructs `new AutoTestAgent({ apiKey, modelName, baseUrl, provider,
9
- * deviceSn, ip, port, agentMode })` from the `AUTOTEST_*` env vars that
10
- * `cmdRun` derived from `~/.hometrans/config.json` — so the user's API_KEY
11
- * / model / device flow in through the constructor, no config.yaml needed;
12
- * 2. reads the testcases (JSON array or JSONL — same input as the former
13
- * Python `AutoTest.batch`);
14
- * 3. runs each case via `agent.runTask(test_steps, { reportDir, taskName })`;
15
- * 4. writes `task_results.jsonl` + `summary.json` in the SAME schema the
16
- * Python `AutoTest.batch` produced, so the HomeTrans `status` /
17
- * `report-tool` contract keeps working unchanged.
18
- *
19
- * STDOUT/STDERR are captured to `batch_stdout.log` by the spawner; this process
20
- * exits 0 on completion regardless of per-case pass/fail (a non-zero exit would
21
- * be interpreted as a launch failure by `spawnBatch`).
22
- */
23
- import { createRequire } from 'node:module';
24
- import fs from 'node:fs';
25
- import path from 'node:path';
26
- import { fileURLToPath } from 'node:url';
27
- import { Command } from 'commander';
28
- import type {
29
- AutoTestAgent,
30
- AutoTestAgentConfig,
31
- RunTaskOptions,
32
- TaskResult,
33
- } from '@autotest/agent';
34
-
35
- const require = createRequire(import.meta.url);
36
- const { AutoTestAgent: AutoTestAgentCtor } = require('@autotest/agent') as {
37
- AutoTestAgent: new (config: AutoTestAgentConfig) => AutoTestAgent;
38
- };
39
-
40
- const UTF_8 = 'utf-8';
41
- const CASE_TIMEOUT_MS = 10 * 60 * 1000;
42
- const DEFAULT_PROMPT_VERSION = 'v2.0';
43
-
44
- interface CaseRow {
45
- uuid: string;
46
- spec: string;
47
- case_name: string;
48
- test_steps: string;
49
- execute_mode?: string;
50
- [k: string]: unknown;
51
- }
52
-
53
- interface ResultRow {
54
- exec_index: number;
55
- uuid: string;
56
- spec: string;
57
- case_name: string;
58
- category: string;
59
- test_steps: string;
60
- report_dir: string;
61
- start_time: string;
62
- end_time: string;
63
- duration_seconds: number;
64
- status: 'PASS' | 'FAIL' | 'UNKNOWN';
65
- return_code: number;
66
- reason: string;
67
- }
68
-
69
- interface LauncherArgs {
70
- taskDir: string;
71
- testcases: string;
72
- category: string;
73
- }
74
-
75
- function sanitizeFilename(name: string): string {
76
- return name.replace(/[\\/:*?"<>|]/g, '_');
77
- }
78
-
79
- function formatTimestamp(date: Date): string {
80
- const pad = (n: number) => String(n).padStart(2, '0');
81
- return (
82
- `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ` +
83
- `${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`
84
- );
85
- }
86
-
87
- function readTestCases(filePath: string): CaseRow[] {
88
- let content = fs.readFileSync(filePath, UTF_8);
89
- if (content.charCodeAt(0) === 0xfeff) content = content.slice(1);
90
- try {
91
- const parsed = JSON.parse(content);
92
- if (Array.isArray(parsed)) return parsed as CaseRow[];
93
- throw new Error('not an array');
94
- } catch {
95
- // Fall back to JSONL.
96
- const cases: CaseRow[] = [];
97
- for (const line of content.split('\n')) {
98
- const trimmed = line.trim();
99
- if (!trimmed) continue;
100
- try {
101
- cases.push(JSON.parse(trimmed) as CaseRow);
102
- } catch {
103
- // skip invalid lines
104
- }
105
- }
106
- return cases;
107
- }
108
- }
109
-
110
- function parseArgs(argv: string[]): LauncherArgs {
111
- const program = new Command();
112
- program.exitOverride();
113
- program
114
- .requiredOption('--task-dir <dir>', '任务目录')
115
- .requiredOption('--testcases <path>', '测试用例文件(JSON 数组或 JSONL)')
116
- .option('--category <name>', '测试分类名称', 'self_test');
117
- try {
118
- program.parse(argv, { from: 'user' });
119
- } catch (err) {
120
- const e = err as { code?: string };
121
- if (e.code !== 'commander.helpDisplayed') {
122
- console.error('用法: node batch-launcher.js --task-dir <dir> --testcases <path> [--category <name>]');
123
- process.exit(2);
124
- }
125
- }
126
- const opts = program.opts() as LauncherArgs;
127
- return { taskDir: opts.taskDir, testcases: opts.testcases, category: opts.category };
128
- }
129
-
130
- /** Construct the agent from AUTOTEST_* env vars (set by `cmdRun` from config.json). */
131
- function createAgent(): AutoTestAgent {
132
- const promptVersion = process.env.AUTOTEST_PROMPT_VERSION || DEFAULT_PROMPT_VERSION;
133
- const envPort = process.env.AUTOTEST_PORT;
134
- const envMaxSteps = process.env.AUTOTEST_MAX_STEPS;
135
- const numOrUndef = (raw: string | undefined): number | undefined => {
136
- if (!raw) return undefined;
137
- const n = Number(raw);
138
- return Number.isFinite(n) ? n : undefined;
139
- };
140
- // Resolve the bundled config.yaml inside @autotest/agent so the agent loads
141
- // model/device/agent defaults (base_url, api_key, …) even when AUTOTEST_*
142
- // env vars are not set (e.g. running batch-launcher standalone without
143
- // HomeTrans's config.json → AUTOTEST_* mapping). The ConfigManager converts
144
- // snake_case YAML keys to camelCase before zod validation, so `base_url` in
145
- // the YAML becomes `baseUrl` as the schema expects.
146
- const agentMain = require.resolve('@autotest/agent');
147
- const configPath = path.join(path.dirname(agentMain), 'common', 'config', 'config.yaml');
148
- const config: AutoTestAgentConfig = {
149
- configPath,
150
- apiKey: process.env.AUTOTEST_API_KEY,
151
- modelName: process.env.AUTOTEST_MODEL,
152
- baseUrl: process.env.AUTOTEST_BASE_URL,
153
- provider: process.env.AUTOTEST_PROVIDER,
154
- deviceSn: process.env.AUTOTEST_DEVICE_SN || undefined,
155
- ip: process.env.AUTOTEST_IP,
156
- port: numOrUndef(envPort),
157
- agentMode: (process.env.AUTOTEST_MODE || undefined) as AutoTestAgentConfig['agentMode'],
158
- maxSteps: numOrUndef(envMaxSteps),
159
- // The SingleAgent resolves its prompt version from `model.unified.promptType`
160
- // (loadPrompt(SINGLE, config.model.unified.promptType)), whose zod default is
161
- // 'default' → 'vdefault' (not a real version). Inject the real version through
162
- // configOverrides — the constructor's route for any extra config the user
163
- // wants to set through `new`.
164
- configOverrides: { model: { unified: { promptType: promptVersion } } },
165
- };
166
- return new AutoTestAgentCtor(config);
167
- }
168
-
169
- /** Run one task with a hard timeout (mirrors batch_runner.runWithTimeout). */
170
- function runWithTimeout(
171
- agent: AutoTestAgent,
172
- task: string,
173
- options: RunTaskOptions,
174
- timeoutMs: number,
175
- ): Promise<TaskResult> {
176
- let timer: NodeJS.Timeout | undefined;
177
- const timeout = new Promise<never>((_, reject) => {
178
- timer = setTimeout(
179
- () => reject(new Error(`Execution timed out (exceeded ${timeoutMs / 1000} seconds)`)),
180
- timeoutMs,
181
- );
182
- });
183
- // Promise.race attaches handlers to both promises, so a late runTask
184
- // resolution/rejection after timeout is never "unhandled"; finally clears
185
- // the timer regardless of which settles first.
186
- return Promise.race([agent.runTask(task, options), timeout]).finally(() => {
187
- if (timer) clearTimeout(timer);
188
- });
189
- }
190
-
191
- async function main(): Promise<void> {
192
- const opts = parseArgs(process.argv.slice(2));
193
-
194
- const taskDir = path.resolve(opts.taskDir);
195
- fs.mkdirSync(taskDir, { recursive: true });
196
- const jsonlFile = path.join(taskDir, 'task_results.jsonl');
197
-
198
- const testCases = readTestCases(opts.testcases);
199
- const total = testCases.length;
200
- if (total === 0) {
201
- console.log('No test cases, exiting');
202
- return;
203
- }
204
-
205
- const indexWidth = String(total).length;
206
-
207
- console.log(`Total test cases to execute: ${total}`);
208
- console.log(`Test category: ${opts.category}`);
209
- console.log(`Task directory: ${taskDir}`);
210
- console.log('='.repeat(60));
211
-
212
- // promptVersion is injected via the constructor's configOverrides (see
213
- // createAgent) because AgentAPIImpl reads it from effectiveConfig, not the
214
- // per-call option.
215
- const agent = createAgent();
216
-
217
- let pass = 0;
218
- let fail = 0;
219
- let unknown = 0;
220
- const batchStart = new Date();
221
-
222
- for (let idx = 1; idx <= total; idx++) {
223
- const tc = testCases[idx - 1];
224
- const uuid = String(tc.uuid ?? '');
225
- const spec = String(tc.spec ?? '');
226
- const caseName = String(tc.case_name ?? '');
227
- const testSteps = String(tc.test_steps ?? '');
228
- const caseMode = String(tc.execute_mode ?? '');
229
-
230
- const dirname = uuid
231
- ? sanitizeFilename(`${uuid}_${caseName}`)
232
- : sanitizeFilename(`${String(idx).padStart(indexWidth, '0')}_${caseName}`);
233
- const reportDir = path.join(taskDir, dirname).replace(/\\/g, '/');
234
- fs.mkdirSync(reportDir, { recursive: true });
235
-
236
- console.log('\n' + '-'.repeat(60));
237
- console.log(`[${opts.category}][${String(idx).padStart(indexWidth, '0')}/${total}] Execute: ${caseName}`);
238
- console.log('-'.repeat(60));
239
-
240
- const start = new Date();
241
- const runOpts: RunTaskOptions = {
242
- reportDir,
243
- taskName: caseName,
244
- ...(caseMode ? { agentMode: caseMode as RunTaskOptions['agentMode'] } : {}),
245
- };
246
-
247
- let status: 'PASS' | 'FAIL' | 'UNKNOWN' = 'UNKNOWN';
248
- let reason = '';
249
- let returnCode = 0;
250
-
251
- try {
252
- const taskResult = await runWithTimeout(agent, testSteps, runOpts, CASE_TIMEOUT_MS);
253
- returnCode = taskResult.status === 'failed' ? 1 : 0;
254
- if (taskResult.status === 'completed') {
255
- status = 'PASS';
256
- pass += 1;
257
- } else if (taskResult.status === 'failed') {
258
- status = 'FAIL';
259
- reason = taskResult.errorMessage ?? 'Execution failed';
260
- fail += 1;
261
- } else {
262
- status = 'UNKNOWN';
263
- reason = `agent status=${taskResult.status}`;
264
- unknown += 1;
265
- }
266
- } catch (e) {
267
- returnCode = -1;
268
- status = 'FAIL';
269
- reason = `Execution error: ${e instanceof Error ? e.message : String(e)}`;
270
- fail += 1;
271
- }
272
-
273
- const end = new Date();
274
- const duration = (end.getTime() - start.getTime()) / 1000;
275
-
276
- const row: ResultRow = {
277
- exec_index: idx,
278
- uuid,
279
- spec,
280
- case_name: caseName,
281
- category: opts.category,
282
- test_steps: testSteps,
283
- report_dir: reportDir,
284
- start_time: formatTimestamp(start),
285
- end_time: formatTimestamp(end),
286
- duration_seconds: Math.round(duration * 100) / 100,
287
- status,
288
- return_code: returnCode,
289
- reason,
290
- };
291
- fs.appendFileSync(jsonlFile, JSON.stringify(row) + '\n', UTF_8);
292
-
293
- const icon = status === 'PASS' ? 'OK' : status === 'FAIL' ? 'X' : '?';
294
- console.log(`Result: [${icon}] ${status} | Duration: ${duration.toFixed(2)}s`);
295
- if (reason && status === 'FAIL') console.log(`Reason: ${reason.slice(0, 100)}`);
296
- console.log(`Report: ${reportDir}`);
297
- }
298
-
299
- const summary = {
300
- total_cases: total,
301
- pass_count: pass,
302
- fail_count: fail,
303
- unknown_count: unknown,
304
- pass_rate: `${total ? ((pass / total) * 100).toFixed(2) : '0.00'}%`,
305
- category: opts.category,
306
- start_time: formatTimestamp(batchStart),
307
- end_time: formatTimestamp(new Date()),
308
- task_dir: taskDir.replace(/\\/g, '/'),
309
- };
310
- fs.writeFileSync(path.join(taskDir, 'summary.json'), JSON.stringify(summary, null, 4), UTF_8);
311
-
312
- console.log('\n' + '='.repeat(60));
313
- console.log('Execution complete!');
314
- console.log(`Total cases: ${total}`);
315
- console.log(`Pass: ${pass}`);
316
- console.log(`Fail: ${fail}`);
317
- console.log(`Unknown: ${unknown}`);
318
- console.log(`Pass rate: ${summary.pass_rate}`);
319
- console.log(`Results file: ${jsonlFile}`);
320
- console.log('='.repeat(60));
321
- }
322
-
323
- main().catch((err) => {
324
- console.error('batch-launcher fatal:', err instanceof Error ? err.stack ?? err.message : String(err));
325
- process.exit(1);
326
- });