android2harmony 0.1.1 → 0.1.3

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 (58) hide show
  1. package/agents/scripts/hmos-precheck/index.mjs +0 -8
  2. package/agents/self-tester.md +339 -0
  3. package/dist/index.js +635 -77
  4. package/dist/index.js.map +4 -4
  5. package/package.json +37 -29
  6. package/skills/hmos-incremental-ui-align/README.md +251 -0
  7. package/skills/hmos-incremental-ui-align/SKILL.md +365 -0
  8. package/skills/hmos-incremental-ui-align/diff_analysis.md +53 -0
  9. package/skills/hmos-incremental-ui-align/page_align.md +62 -0
  10. package/skills/hmos-incremental-ui-align/references/Comparison_Template.md +38 -0
  11. package/skills/hmos-incremental-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243/@Link/350/243/205/351/245/260/345/231/250/357/274/232/347/210/266/345/255/220/345/217/214/345/220/221/345/220/214/346/255/245.md +648 -0
  12. package/skills/hmos-incremental-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243/@Observed/350/243/205/351/245/260/345/231/250/345/222/214@ObjectLink/350/243/205/351/245/260/345/231/250/357/274/232/345/265/214/345/245/227/347/261/273/345/257/271/350/261/241/345/261/236/346/200/247/345/217/230/345/214/226.md +2089 -0
  13. package/skills/hmos-incremental-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243/@Prop/350/243/205/351/245/260/345/231/250/357/274/232/347/210/266/345/255/220/345/215/225/345/220/221/345/220/214/346/255/245.md +1033 -0
  14. package/skills/hmos-incremental-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243/@Provide/350/243/205/351/245/260/345/231/250/345/222/214@Consume/350/243/205/351/245/260/345/231/250/357/274/232/344/270/216/345/220/216/344/273/243/347/273/204/344/273/266/345/217/214/345/220/221/345/220/214/346/255/245.md +1183 -0
  15. package/skills/hmos-incremental-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243/@State/350/243/205/351/245/260/345/231/250/357/274/232/347/273/204/344/273/266/345/206/205/347/212/266/346/200/201.md +576 -0
  16. package/skills/hmos-incremental-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243/@Track/350/243/205/351/245/260/345/231/250/357/274/232class/345/257/271/350/261/241/345/261/236/346/200/247/347/272/247/346/233/264/346/226/260.md +297 -0
  17. package/skills/hmos-incremental-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243/@Watch/350/243/205/351/245/260/345/231/250/357/274/232/347/212/266/346/200/201/345/217/230/351/207/217/346/233/264/346/224/271/351/200/232/347/237/245.md +395 -0
  18. package/skills/hmos-incremental-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243/AppStorage/357/274/232/345/272/224/347/224/250/345/205/250/345/261/200/347/232/204UI/347/212/266/346/200/201/345/255/230/345/202/250.md +903 -0
  19. package/skills/hmos-incremental-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243/Environment/357/274/232/350/256/276/345/244/207/347/216/257/345/242/203/346/237/245/350/257/242.md +106 -0
  20. package/skills/hmos-incremental-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243/LocalStorage/357/274/232/351/241/265/351/235/242/347/272/247UI/347/212/266/346/200/201/345/255/230/345/202/250.md +1178 -0
  21. package/skills/hmos-incremental-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243/MVVM/346/250/241/345/274/217V1.md +911 -0
  22. package/skills/hmos-incremental-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243/PersistentStorage/357/274/232/346/214/201/344/271/205/345/214/226/345/255/230/345/202/250UI/347/212/266/346/200/201.md +355 -0
  23. package/skills/hmos-incremental-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243//347/256/241/347/220/206/345/272/224/347/224/250/346/213/245/346/234/211/347/232/204/347/212/266/346/200/201/346/246/202/350/277/260.md +11 -0
  24. package/skills/hmos-incremental-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243V2/!!/350/257/255/346/263/225/357/274/232/345/217/214/345/220/221/347/273/221/345/256/232.md +206 -0
  25. package/skills/hmos-incremental-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243V2/@Computed/350/243/205/351/245/260/345/231/250/357/274/232/350/256/241/347/256/227/345/261/236/346/200/247.md +373 -0
  26. package/skills/hmos-incremental-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243V2/@Event/350/243/205/351/245/260/345/231/250/357/274/232/350/247/204/350/214/203/347/273/204/344/273/266/350/276/223/345/207/272.md +158 -0
  27. package/skills/hmos-incremental-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243V2/@Local/350/243/205/351/245/260/345/231/250/357/274/232/347/273/204/344/273/266/345/206/205/351/203/250/347/212/266/346/200/201.md +750 -0
  28. package/skills/hmos-incremental-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243V2/@Monitor/350/243/205/351/245/260/345/231/250/357/274/232/347/212/266/346/200/201/345/217/230/351/207/217/344/277/256/346/224/271/345/274/202/346/255/245/347/233/221/345/220/254.md +1704 -0
  29. package/skills/hmos-incremental-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243V2/@ObservedV2/350/243/205/351/245/260/345/231/250/345/222/214@Trace/350/243/205/351/245/260/345/231/250/357/274/232/347/261/273/345/261/236/346/200/247/345/217/230/345/214/226/350/247/202/346/265/213.md +1012 -0
  30. package/skills/hmos-incremental-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243V2/@Once/350/243/205/351/245/260/345/231/250/357/274/232/345/210/235/345/247/213/345/214/226/345/220/214/346/255/245/344/270/200/346/254/241.md +164 -0
  31. package/skills/hmos-incremental-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243V2/@Param/350/243/205/351/245/260/345/231/250/357/274/232/347/273/204/344/273/266/345/244/226/351/203/250/350/276/223/345/205/245.md +840 -0
  32. package/skills/hmos-incremental-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243V2/@Provider/350/243/205/351/245/260/345/231/250/345/222/214@Consumer/350/243/205/351/245/260/345/231/250/357/274/232/350/267/250/347/273/204/344/273/266/345/261/202/347/272/247/345/217/214/345/220/221/345/220/214/346/255/245.md +856 -0
  33. package/skills/hmos-incremental-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243V2/@Type/350/243/205/351/245/260/345/231/250/357/274/232/346/240/207/350/256/260/347/261/273/345/261/236/346/200/247/347/232/204/347/261/273/345/236/213.md +83 -0
  34. package/skills/hmos-incremental-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243V2/AppStorageV2/357/274/232/345/272/224/347/224/250/345/205/250/345/261/200UI/347/212/266/346/200/201/345/255/230/345/202/250.md +294 -0
  35. package/skills/hmos-incremental-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243V2/MVVM/346/250/241/345/274/217/357/274/210V2/357/274/211.md +1407 -0
  36. package/skills/hmos-incremental-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243V2/PersistenceV2/357/274/232/346/214/201/344/271/205/345/214/226/345/255/230/345/202/250UI/347/212/266/346/200/201.md +1220 -0
  37. package/skills/hmos-incremental-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243V2/_/350/214/203/345/274/217/351/200/211/346/213/251/350/257/264/346/230/216.md +47 -0
  38. package/skills/hmos-incremental-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243V2//347/212/266/346/200/201/347/256/241/347/220/206V1/345/220/221V2/350/277/201/347/247/273/344/270/216/346/267/267/347/224/250/346/214/207/345/257/274.md +840 -0
  39. package/skills/hmos-incremental-ui-align/references/State_Model_Template.md +74 -0
  40. package/skills/hmos-incremental-ui-align/references/UI_Analysis_Template.md +34 -0
  41. package/skills/hmos-incremental-ui-align/references/android-to-harmonyOS-ui-atomic-component-mapping-reference.md +2533 -0
  42. package/skills/hmos-incremental-ui-align/references/android-to-harmonyOS-ui-interaction-mapping-reference.md +555 -0
  43. package/skills/hmos-incremental-ui-align/references/android-to-harmonyOS-ui-layout-mapping-reference.md +117 -0
  44. package/skills/hmos-incremental-ui-align/scripts/app_feature_verify.ts +999 -0
  45. package/skills/hmos-incremental-ui-align/scripts/extract_checklist.ts +343 -0
  46. package/skills/hmos-incremental-ui-align/scripts/navigation-capure.md +76 -0
  47. package/skills/hmos-incremental-ui-align/scripts/page_capture.ts +977 -0
  48. package/skills/hmos-incremental-ui-align/scripts/page_capture_burst.ts +188 -0
  49. package/skills/hmos-spec-generate/SKILL.md +1 -1
  50. package/tools/autotest/deps/autotest-agent-0.1.1.tgz +0 -0
  51. package/tools/autotest/engine/batch-launcher.ts +326 -0
  52. package/tools/autotest/engine/report-tool.ts +773 -0
  53. package/tools/autotest/engine/self-test-runner.ts +1024 -0
  54. package/tools/autotest/engine/testcases-tool.ts +246 -0
  55. package/tools/autotest/resolve-metadata-tool.ts +143 -0
  56. package/tools/autotest/runner/logger.ts +45 -0
  57. package/tools/autotest/runner/process-utils.ts +41 -0
  58. package/tools/autotest/validate.ts +115 -0
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // src/index.ts
2
- import path4 from "node:path";
2
+ import path10 from "node:path";
3
3
 
4
4
  // src/paths.ts
5
5
  import path from "node:path";
@@ -20,67 +20,16 @@ function parseFrontmatter(content) {
20
20
  const match = content.match(/^---\s*\r?\n([\s\S]*?)\r?\n---/);
21
21
  if (!match) return null;
22
22
  const lines = match[1].split(/\r?\n/);
23
- let name = null;
24
- let hidden = false;
25
- let inMetadata = false;
26
- let metadataIndent = -1;
27
23
  for (const line of lines) {
28
24
  if (!line.trim() || line.trim().startsWith("#")) continue;
29
- const indent = line.length - line.trimStart().length;
30
- const trimmed = line.trim();
31
- const kv = trimmed.match(/^([a-zA-Z_][a-zA-Z0-9_-]*):\s*(.*)$/);
32
- if (indent === 0) {
33
- inMetadata = false;
34
- if (!kv) continue;
35
- const value = kv[2].trim();
36
- if (kv[1] === "name") {
37
- name = value.replace(/^["']|["']$/g, "");
38
- } else if (kv[1] === "metadata") {
39
- if (value === "") {
40
- inMetadata = true;
41
- metadataIndent = -1;
42
- } else if (/visible\s*:\s*false/.test(value)) {
43
- hidden = true;
44
- }
45
- }
46
- } else if (inMetadata) {
47
- if (metadataIndent === -1) metadataIndent = indent;
48
- if (indent < metadataIndent) {
49
- inMetadata = false;
50
- continue;
51
- }
52
- if (kv && kv[1] === "visible" && kv[2].trim() === "false") {
53
- hidden = true;
54
- }
25
+ if (line.length - line.trimStart().length !== 0) continue;
26
+ const kv = line.trim().match(/^([a-zA-Z_][a-zA-Z0-9_-]*):\s*(.*)$/);
27
+ if (!kv) continue;
28
+ if (kv[1] === "name") {
29
+ return kv[2].trim().replace(/^["']|["']$/g, "");
55
30
  }
56
31
  }
57
- return name ? { name, hidden } : null;
58
- }
59
- function discoverA2HSkills(skillsRoot) {
60
- const hidden = [];
61
- const all = [];
62
- let entries;
63
- try {
64
- entries = fs.readdirSync(skillsRoot, { withFileTypes: true });
65
- } catch {
66
- return { hidden, all };
67
- }
68
- for (const entry of entries) {
69
- if (!entry.isDirectory()) continue;
70
- const skillFile = path2.join(skillsRoot, entry.name, "SKILL.md");
71
- let raw;
72
- try {
73
- raw = fs.readFileSync(skillFile, "utf-8");
74
- } catch {
75
- continue;
76
- }
77
- const info = parseFrontmatter(raw);
78
- if (info) {
79
- all.push(info.name);
80
- if (info.hidden) hidden.push(info.name);
81
- }
82
- }
83
- return { hidden, all };
32
+ return null;
84
33
  }
85
34
  function discoverA2HAgents(agentsRoot) {
86
35
  const result = /* @__PURE__ */ new Map();
@@ -99,9 +48,9 @@ function discoverA2HAgents(agentsRoot) {
99
48
  } catch {
100
49
  continue;
101
50
  }
102
- const info = parseFrontmatter(raw);
103
- if (!info) continue;
104
- result.set(info.name, stripFrontmatter(raw));
51
+ const name = parseFrontmatter(raw);
52
+ if (!name) continue;
53
+ result.set(name, stripFrontmatter(raw));
105
54
  }
106
55
  return result;
107
56
  }
@@ -121,17 +70,615 @@ async function buildA2HEnvBase(deps) {
121
70
  return env;
122
71
  }
123
72
 
73
+ // src/tools/a2h-app-feature-verify.ts
74
+ import { spawn } from "node:child_process";
75
+ import path4 from "node:path";
76
+ var SCRIPT_PATH = path4.join(
77
+ PKG_ROOT,
78
+ "skills",
79
+ "hmos-incremental-ui-align",
80
+ "scripts",
81
+ "app_feature_verify.ts"
82
+ );
83
+ var SECRET_ENV = [
84
+ "HOMETRANS_MODEL_API_KEY",
85
+ "HOMETRANS_MODEL_NAME",
86
+ "HOMETRANS_MODEL_BASE_URL",
87
+ "GLM_API_KEY",
88
+ "TEST_API_KEY"
89
+ ];
90
+ function cap(s, max = 5e4) {
91
+ if (s.length <= max) return s;
92
+ const half = Math.floor(max / 2);
93
+ return s.slice(0, half) + `
94
+ \u2026[truncated ${s.length - max} chars]\u2026
95
+ ` + s.slice(-half);
96
+ }
97
+ function makeA2hAppFeatureVerifyTool(deps) {
98
+ return {
99
+ description: "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.",
100
+ args: {
101
+ type: "object",
102
+ properties: {
103
+ app: { type: "string", description: "\u76EE\u6807 app \u663E\u793A\u540D\uFF08required\uFF09" },
104
+ package: { type: "string", description: "\u76EE\u6807 app \u5305\u540D\uFF08required\uFF09" },
105
+ device: {
106
+ type: "string",
107
+ enum: ["hdc", "adb"],
108
+ default: "hdc",
109
+ description: "\u8BBE\u5907\u9A71\u52A8\uFF1Ahdc(HarmonyOS) / adb(Android)"
110
+ },
111
+ task: {
112
+ type: "string",
113
+ 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"
114
+ },
115
+ prompt: { type: "string", description: "\u81EA\u7531 prompt\uFF08\u4E0E task/feature \u4E09\u9009\u4E00\uFF09" },
116
+ feature: { type: "string", description: ".md \u529F\u80FD\u6E05\u5355\u8DEF\u5F84\uFF08\u4E0E task/prompt \u4E09\u9009\u4E00\uFF09" },
117
+ maxSteps: { type: "number", default: 30, description: "\u6700\u5927 agent \u6B65\u6570" },
118
+ appHints: { type: "string", description: "\u5E94\u7528\u5BFC\u822A\u63D0\u793A" },
119
+ serial: { type: "string", description: "\u8BBE\u5907\u5E8F\u5217\u53F7" },
120
+ limit: { type: "number", description: "--feature \u6A21\u5F0F\u4E0B\u53D6\u524D N \u6761" },
121
+ offset: { type: "number", default: 1, description: "--feature \u6A21\u5F0F\u4E0B\u504F\u79FB" },
122
+ quiet: { type: "boolean", default: false, description: "\u6291\u5236\u63D0\u793A\u6027\u8F93\u51FA" }
123
+ },
124
+ required: ["app", "package"]
125
+ },
126
+ execute: async (args, context) => {
127
+ const app = String(args.app ?? "");
128
+ const pkg = String(args.package ?? "");
129
+ if (!app || !pkg) {
130
+ throw new Error("a2h_app_feature_verify: `app` and `package` are required");
131
+ }
132
+ const task = args.task != null ? String(args.task) : null;
133
+ const prompt = args.prompt != null ? String(args.prompt) : null;
134
+ const feature = args.feature != null ? String(args.feature) : null;
135
+ if (!task && !prompt && !feature) {
136
+ throw new Error("a2h_app_feature_verify: one of `task` / `prompt` / `feature` is required");
137
+ }
138
+ const model = await deps.resolveModelParams(context.worktree);
139
+ if (!model || !model.apiKey) {
140
+ throw new Error("a2h_app_feature_verify: host did not provide model params (apiKey missing)");
141
+ }
142
+ const argv = ["--app", app, "--package", pkg, "--device", String(args.device ?? "hdc")];
143
+ if (task) argv.push("--task", task);
144
+ if (prompt) argv.push("--prompt", prompt);
145
+ if (feature) argv.push("--feature", feature);
146
+ if (args.maxSteps != null) argv.push("--max-steps", String(args.maxSteps));
147
+ if (args.appHints != null) argv.push("--app-hints", String(args.appHints));
148
+ if (args.serial != null) argv.push("--serial", String(args.serial));
149
+ if (args.limit != null) argv.push("--limit", String(args.limit));
150
+ if (args.offset != null) argv.push("--offset", String(args.offset));
151
+ if (args.quiet === true) argv.push("--quiet");
152
+ argv.push("--model-stdin");
153
+ const env = { ...process.env };
154
+ for (const k of SECRET_ENV) delete env[k];
155
+ const child = spawn("node", [SCRIPT_PATH, ...argv], {
156
+ cwd: context.directory,
157
+ env,
158
+ stdio: ["pipe", "pipe", "pipe"]
159
+ });
160
+ child.stdin.end(
161
+ JSON.stringify({ apiKey: model.apiKey, modelName: model.modelName, baseURL: model.baseURL })
162
+ );
163
+ let stdout = "";
164
+ let stderr = "";
165
+ let spawnError = "";
166
+ const stepRe = /执行动作:\s*(.*)/g;
167
+ child.stdout.on("data", (chunk) => {
168
+ const text = chunk.toString("utf8");
169
+ stdout += text;
170
+ const matches = [...text.matchAll(stepRe)];
171
+ if (matches.length) {
172
+ const last = matches[matches.length - 1][1].trim();
173
+ try {
174
+ context.metadata({ title: `UI verify: ${last.slice(0, 80)}` });
175
+ } catch {
176
+ }
177
+ }
178
+ });
179
+ child.stderr.on("data", (chunk) => {
180
+ stderr += chunk.toString("utf8");
181
+ });
182
+ const onAbort = () => {
183
+ if (!child.killed) child.kill("SIGTERM");
184
+ };
185
+ context.abort.addEventListener("abort", onAbort);
186
+ const code = await new Promise((resolve) => {
187
+ child.on("close", resolve);
188
+ child.on("error", (err) => {
189
+ spawnError = err.message;
190
+ resolve(-1);
191
+ });
192
+ });
193
+ context.abort.removeEventListener("abort", onAbort);
194
+ if (code !== 0) {
195
+ if (spawnError) {
196
+ throw new Error(`a2h_app_feature_verify: failed to spawn node \u2014 ${spawnError}`);
197
+ }
198
+ const tail = stderr.slice(-2e3);
199
+ throw new Error(`a2h_app_feature_verify failed (exit ${code}): ${tail || "(no stderr)"}`);
200
+ }
201
+ return { ok: true, exitCode: code, output: cap(stdout) };
202
+ }
203
+ };
204
+ }
205
+
206
+ // src/tools/a2h-autotest-resolve-metadata.ts
207
+ import path6 from "node:path";
208
+
209
+ // src/tools/autotest-shared.ts
210
+ import { spawn as spawn2 } from "node:child_process";
211
+ import path5 from "node:path";
212
+ var AUTOTEST_ROOT = path5.join(PKG_ROOT, "tools", "autotest");
213
+ var SECRET_ENV2 = [
214
+ "HOMETRANS_MODEL_API_KEY",
215
+ "HOMETRANS_MODEL_NAME",
216
+ "HOMETRANS_MODEL_BASE_URL",
217
+ "GLM_API_KEY",
218
+ "TEST_API_KEY"
219
+ ];
220
+ function cap2(s, max = 5e4) {
221
+ if (s.length <= max) return s;
222
+ const half = Math.floor(max / 2);
223
+ return s.slice(0, half) + `
224
+ \u2026[truncated ${s.length - max} chars]\u2026
225
+ ` + s.slice(-half);
226
+ }
227
+ function runAutotestScript(opts) {
228
+ return new Promise((resolve) => {
229
+ const scriptPath = path5.join(AUTOTEST_ROOT, opts.scriptRel);
230
+ const env = { ...process.env };
231
+ for (const k of SECRET_ENV2) delete env[k];
232
+ const child = spawn2("node", [scriptPath, ...opts.argv], {
233
+ cwd: opts.cwd,
234
+ env,
235
+ stdio: ["pipe", "pipe", "pipe"]
236
+ });
237
+ let stdout = "";
238
+ let stderr = "";
239
+ let spawnError = "";
240
+ child.stdout.on("data", (chunk) => {
241
+ const text = chunk.toString("utf8");
242
+ stdout += text;
243
+ opts.onStdoutChunk?.(text);
244
+ });
245
+ child.stderr.on("data", (chunk) => {
246
+ stderr += chunk.toString("utf8");
247
+ });
248
+ child.stdin.end(opts.stdin ?? "");
249
+ const onAbort = () => {
250
+ if (!child.killed) child.kill("SIGTERM");
251
+ };
252
+ opts.abort.addEventListener("abort", onAbort);
253
+ child.on("close", (code) => {
254
+ opts.abort.removeEventListener("abort", onAbort);
255
+ resolve({ ok: code === 0, exitCode: code ?? -1, stdout: cap2(stdout), stderr, spawnError });
256
+ });
257
+ child.on("error", (err) => {
258
+ opts.abort.removeEventListener("abort", onAbort);
259
+ spawnError = err.message;
260
+ resolve({ ok: false, exitCode: -1, stdout: cap2(stdout), stderr, spawnError });
261
+ });
262
+ });
263
+ }
264
+ function autotestToolError(toolId, r) {
265
+ if (r.spawnError) return new Error(`${toolId}: failed to spawn node \u2014 ${r.spawnError}`);
266
+ const tail = r.stderr.slice(-2e3);
267
+ return new Error(`${toolId} failed (exit ${r.exitCode}): ${tail || "(no stderr)"}`);
268
+ }
269
+
270
+ // src/tools/a2h-autotest-resolve-metadata.ts
271
+ var SCRIPT_REL = path6.join("resolve-metadata-tool.ts");
272
+ var TOOL_ID = "a2h_autotest_resolve_metadata";
273
+ function makeA2hAutotestResolveMetadataTool(_deps) {
274
+ return {
275
+ description: "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.",
276
+ args: {
277
+ type: "object",
278
+ properties: {
279
+ projectDir: {
280
+ type: "string",
281
+ description: "HarmonyOS \u5DE5\u7A0B\u6839\u76EE\u5F55\uFF08\u542B AppScope/app.json5\uFF09"
282
+ },
283
+ output: {
284
+ type: "string",
285
+ description: "app-metadata.json \u8F93\u51FA\u8DEF\u5F84"
286
+ }
287
+ },
288
+ required: ["projectDir", "output"]
289
+ },
290
+ execute: async (args, context) => {
291
+ const projectDir = String(args.projectDir ?? "");
292
+ const output = String(args.output ?? "");
293
+ if (!projectDir || !output) {
294
+ throw new Error(`${TOOL_ID}: \`projectDir\` and \`output\` are required`);
295
+ }
296
+ const argv = ["--project-dir", projectDir, "--output", output];
297
+ const r = await runAutotestScript({
298
+ scriptRel: SCRIPT_REL,
299
+ argv,
300
+ cwd: context.directory,
301
+ abort: context.abort
302
+ });
303
+ if (!r.ok) throw autotestToolError(TOOL_ID, r);
304
+ return { ok: true, exitCode: r.exitCode, output: r.stdout };
305
+ }
306
+ };
307
+ }
308
+
309
+ // src/tools/a2h-autotest-testcases.ts
310
+ import path7 from "node:path";
311
+ var SCRIPT_REL2 = path7.join("engine", "testcases-tool.ts");
312
+ var TOOL_ID2 = "a2h_autotest_testcases";
313
+ function makeA2hAutotestTestcasesTool(_deps) {
314
+ return {
315
+ description: "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.",
316
+ args: {
317
+ type: "object",
318
+ properties: {
319
+ action: {
320
+ type: "string",
321
+ enum: ["generate", "validate"],
322
+ description: "generate=\u4ECE\u62BD\u53D6\u8F93\u51FA\u5EFA testcases.json\uFF1Bvalidate=\u6821\u9A8C\u5DF2\u6709 testcases.json"
323
+ },
324
+ input: { type: "string", description: "[generate] \u62BD\u53D6\u8F93\u51FA JSON \u8DEF\u5F84" },
325
+ output: { type: "string", description: "[generate] testcases.json \u8F93\u51FA\u8DEF\u5F84" },
326
+ validate: { type: "boolean", default: false, description: "[generate] \u5199\u5B8C\u540E\u5C31\u5730\u6821\u9A8C" },
327
+ path: { type: "string", description: "[validate] \u8981\u6821\u9A8C\u7684 testcases.json \u8DEF\u5F84" }
328
+ },
329
+ required: ["action"]
330
+ },
331
+ execute: async (args, context) => {
332
+ const action = String(args.action ?? "");
333
+ if (action === "generate") {
334
+ const input = String(args.input ?? "");
335
+ const output = String(args.output ?? "");
336
+ if (!input || !output) {
337
+ throw new Error(`${TOOL_ID2}: \`action:"generate"\` requires \`input\` and \`output\``);
338
+ }
339
+ const argv = ["generate", input, output];
340
+ if (args.validate === true) argv.push("--validate");
341
+ const r = await runAutotestScript({
342
+ scriptRel: SCRIPT_REL2,
343
+ argv,
344
+ cwd: context.directory,
345
+ abort: context.abort
346
+ });
347
+ if (!r.ok) throw autotestToolError(TOOL_ID2, r);
348
+ return { ok: true, exitCode: r.exitCode, output: r.stdout };
349
+ }
350
+ if (action === "validate") {
351
+ const p = String(args.path ?? "");
352
+ if (!p) {
353
+ throw new Error(`${TOOL_ID2}: \`action:"validate"\` requires \`path\``);
354
+ }
355
+ const argv = ["validate", p];
356
+ const r = await runAutotestScript({
357
+ scriptRel: SCRIPT_REL2,
358
+ argv,
359
+ cwd: context.directory,
360
+ abort: context.abort
361
+ });
362
+ if (!r.ok) throw autotestToolError(TOOL_ID2, r);
363
+ return { ok: true, exitCode: r.exitCode, output: r.stdout };
364
+ }
365
+ throw new Error(`${TOOL_ID2}: unknown \`action\` "${action}" (expected generate|validate)`);
366
+ }
367
+ };
368
+ }
369
+
370
+ // src/tools/a2h-autotest-report.ts
371
+ import path8 from "node:path";
372
+ var SCRIPT_REL3 = path8.join("engine", "report-tool.ts");
373
+ var TOOL_ID3 = "a2h_autotest_report";
374
+ function makeA2hAutotestReportTool(_deps) {
375
+ return {
376
+ description: "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.",
377
+ args: {
378
+ type: "object",
379
+ properties: {
380
+ action: {
381
+ type: "string",
382
+ enum: ["generate", "validate"],
383
+ description: "generate=\u4ECE task \u76EE\u5F55\u6E32\u67D3 self-test-report.md\uFF1Bvalidate=\u6821\u9A8C\u5DF2\u6709 report.md \u5E03\u5C40"
384
+ },
385
+ taskSubdir: { type: "string", description: "[generate] task_<ts>/ \u76EE\u5F55\u8DEF\u5F84" },
386
+ appMetadata: { type: "string", description: "[generate] app-metadata.json \u8DEF\u5F84" },
387
+ hap: { type: "string", description: "[generate] HAP \u6587\u4EF6\u8DEF\u5F84" },
388
+ device: { type: "string", description: "[generate] \u8BBE\u5907\u6807\u8BC6\uFF08\u5982 127.0.0.1:5555\uFF09" },
389
+ suite: { type: "string", description: "[generate] \u6D4B\u8BD5\u5957\u4EF6\u6807\u9898" },
390
+ out: { type: "string", description: "[generate] self-test-report.md \u8F93\u51FA\u8DEF\u5F84" },
391
+ validate: { type: "boolean", default: false, description: "[generate] \u5199\u5B8C\u540E\u5C31\u5730\u6821\u9A8C" },
392
+ path: { type: "string", description: "[validate] \u8981\u6821\u9A8C\u7684 report.md \u8DEF\u5F84" }
393
+ },
394
+ required: ["action"]
395
+ },
396
+ execute: async (args, context) => {
397
+ const action = String(args.action ?? "");
398
+ if (action === "generate") {
399
+ const taskSubdir = String(args.taskSubdir ?? "");
400
+ const appMetadata = String(args.appMetadata ?? "");
401
+ const hap = String(args.hap ?? "");
402
+ const device = String(args.device ?? "");
403
+ const suite = String(args.suite ?? "");
404
+ const out = String(args.out ?? "");
405
+ if (!taskSubdir || !appMetadata || !hap || !device || !suite || !out) {
406
+ throw new Error(
407
+ `${TOOL_ID3}: \`action:"generate"\` requires \`taskSubdir\`, \`appMetadata\`, \`hap\`, \`device\`, \`suite\`, \`out\``
408
+ );
409
+ }
410
+ const argv = [
411
+ "generate",
412
+ "--task-subdir",
413
+ taskSubdir,
414
+ "--app-metadata",
415
+ appMetadata,
416
+ "--hap",
417
+ hap,
418
+ "--device",
419
+ device,
420
+ "--suite",
421
+ suite,
422
+ "--out",
423
+ out
424
+ ];
425
+ if (args.validate === true) argv.push("--validate");
426
+ const r = await runAutotestScript({
427
+ scriptRel: SCRIPT_REL3,
428
+ argv,
429
+ cwd: context.directory,
430
+ abort: context.abort
431
+ });
432
+ if (!r.ok) throw autotestToolError(TOOL_ID3, r);
433
+ return { ok: true, exitCode: r.exitCode, output: r.stdout };
434
+ }
435
+ if (action === "validate") {
436
+ const p = String(args.path ?? "");
437
+ if (!p) {
438
+ throw new Error(`${TOOL_ID3}: \`action:"validate"\` requires \`path\``);
439
+ }
440
+ const argv = ["validate", p];
441
+ const r = await runAutotestScript({
442
+ scriptRel: SCRIPT_REL3,
443
+ argv,
444
+ cwd: context.directory,
445
+ abort: context.abort
446
+ });
447
+ if (!r.ok) throw autotestToolError(TOOL_ID3, r);
448
+ return { ok: true, exitCode: r.exitCode, output: r.stdout };
449
+ }
450
+ throw new Error(`${TOOL_ID3}: unknown \`action\` "${action}" (expected generate|validate)`);
451
+ }
452
+ };
453
+ }
454
+
455
+ // src/tools/a2h-autotest-selftest.ts
456
+ import { spawn as spawn3 } from "node:child_process";
457
+ import path9 from "node:path";
458
+ var SCRIPT_REL4 = path9.join("engine", "self-test-runner.ts");
459
+ var TOOL_ID4 = "a2h_autotest_selftest";
460
+ function makeA2hAutotestSelftestTool(deps) {
461
+ return {
462
+ description: "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.",
463
+ args: {
464
+ type: "object",
465
+ properties: {
466
+ action: {
467
+ type: "string",
468
+ enum: ["run", "status", "kill", "check_hap", "check_inputs", "check_pre"],
469
+ description: "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"
470
+ },
471
+ // ── run ──
472
+ testcases: { type: "string", description: "[run/check_inputs/check_pre] \u6D4B\u8BD5\u7528\u4F8B\u6587\u4EF6\uFF08JSON \u6570\u7EC4\u6216 JSONL\uFF09" },
473
+ 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" },
474
+ bundleName: { type: "string", description: "[run] \u5305\u540D\uFF08bundle_name\uFF09" },
475
+ taskDir: {
476
+ type: "string",
477
+ 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"
478
+ },
479
+ outputDir: { type: "string", description: "[run/status] \u65E5\u5FD7\u76EE\u5F55\uFF08\u4E0D\u4F20\u5219\u540C taskDir\uFF09" },
480
+ category: { type: "string", description: "[run] \u6D4B\u8BD5\u5206\u7C7B\u540D\uFF0C\u4F20\u7ED9 batch-launcher" },
481
+ timeout: {
482
+ type: ["number", "string"],
483
+ 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\xD7720s \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'
484
+ },
485
+ deviceSn: { type: "string", description: "[run] \u8BBE\u5907\u5E8F\u5217\u53F7\uFF08\u2192 AutoTestAgent.deviceSn\uFF09" },
486
+ ip: { type: "string", description: "[run] \u8BBE\u5907 IP\uFF08\u2192 AutoTestAgent.ip\uFF09" },
487
+ port: { type: "string", description: "[run] \u8BBE\u5907\u7AEF\u53E3\uFF08\u2192 AutoTestAgent.port\uFF09" },
488
+ agentMode: { type: "string", description: "[run] agent \u6A21\u5F0F\uFF08\u9ED8\u8BA4 single\uFF09" },
489
+ maxSteps: { type: "number", description: "[run] \u5355\u7528\u4F8B\u6700\u5927\u6B65\u6570" },
490
+ promptVersion: { type: "string", description: "[run] prompt \u7248\u672C\uFF08\u9ED8\u8BA4 v2.0\uFF09" },
491
+ // ── check_inputs ──
492
+ metadata: { type: "string", description: "[check_inputs] app-metadata.json \u8DEF\u5F84" }
493
+ },
494
+ required: ["action"]
495
+ },
496
+ execute: async (args, context) => {
497
+ const action = String(args.action ?? "");
498
+ if (action === "run") {
499
+ const testcases = String(args.testcases ?? "");
500
+ const hap = String(args.hap ?? "");
501
+ const bundleName = String(args.bundleName ?? "");
502
+ if (!testcases || !hap || !bundleName) {
503
+ throw new Error(`${TOOL_ID4}: \`action:"run"\` requires \`testcases\`, \`hap\`, \`bundleName\``);
504
+ }
505
+ const taskDir = String(args.taskDir ?? "task");
506
+ const argv = [
507
+ "run",
508
+ "--testcases",
509
+ testcases,
510
+ "--hap",
511
+ hap,
512
+ "--bundle-name",
513
+ bundleName,
514
+ "--task-dir",
515
+ taskDir,
516
+ "--model-stdin"
517
+ ];
518
+ if (args.outputDir) argv.push("--output-dir", String(args.outputDir));
519
+ if (args.category) argv.push("--category", String(args.category));
520
+ if (args.deviceSn) argv.push("--device-sn", String(args.deviceSn));
521
+ if (args.ip) argv.push("--ip", String(args.ip));
522
+ if (args.port) argv.push("--port", String(args.port));
523
+ if (args.agentMode) argv.push("--agent-mode", String(args.agentMode));
524
+ if (args.maxSteps !== void 0 && args.maxSteps !== null) {
525
+ argv.push("--max-steps", String(args.maxSteps));
526
+ }
527
+ if (args.promptVersion) argv.push("--prompt-version", String(args.promptVersion));
528
+ if (args.timeout !== void 0 && args.timeout !== null) {
529
+ argv.push("--timeout", String(args.timeout));
530
+ }
531
+ const model = await deps.resolveModelParams(context.worktree);
532
+ if (!model) {
533
+ throw new Error(
534
+ `${TOOL_ID4}: \`action:"run"\` needs a model but deps.resolveModelParams returned null for worktree ${context.worktree ?? "(none)"} \u2014 configure the host's model provider`
535
+ );
536
+ }
537
+ const stdin = JSON.stringify({
538
+ apiKey: model.apiKey,
539
+ modelName: model.modelName,
540
+ baseURL: model.baseURL
541
+ });
542
+ const onAbortKill = () => {
543
+ try {
544
+ const env = { ...process.env };
545
+ delete env.HOMETRANS_MODEL_API_KEY;
546
+ delete env.HOMETRANS_MODEL_NAME;
547
+ delete env.HOMETRANS_MODEL_BASE_URL;
548
+ delete env.GLM_API_KEY;
549
+ delete env.TEST_API_KEY;
550
+ const k = spawn3(
551
+ "node",
552
+ [path9.join(AUTOTEST_ROOT, SCRIPT_REL4), "kill", "--task-dir", taskDir],
553
+ { cwd: context.directory, env, stdio: "ignore", detached: true, windowsHide: true }
554
+ );
555
+ k.on("error", () => {
556
+ });
557
+ k.unref();
558
+ } catch {
559
+ }
560
+ };
561
+ if (!context.abort.aborted) {
562
+ context.abort.addEventListener("abort", onAbortKill);
563
+ }
564
+ const r = await runAutotestScript({
565
+ scriptRel: SCRIPT_REL4,
566
+ argv,
567
+ cwd: context.directory,
568
+ abort: context.abort,
569
+ stdin
570
+ });
571
+ context.abort.removeEventListener("abort", onAbortKill);
572
+ if (r.spawnError) {
573
+ throw new Error(`${TOOL_ID4}: failed to spawn node \u2014 ${r.spawnError}`);
574
+ }
575
+ return { ok: r.exitCode === 0, exitCode: r.exitCode, output: r.stdout };
576
+ }
577
+ if (action === "status") {
578
+ const taskDir = String(args.taskDir ?? "");
579
+ if (!taskDir) {
580
+ throw new Error(`${TOOL_ID4}: \`action:"status"\` requires \`taskDir\``);
581
+ }
582
+ const argv = ["status", "--task-dir", taskDir];
583
+ if (args.outputDir) argv.push("--output-dir", String(args.outputDir));
584
+ const r = await runAutotestScript({
585
+ scriptRel: SCRIPT_REL4,
586
+ argv,
587
+ cwd: context.directory,
588
+ abort: context.abort
589
+ });
590
+ if (r.spawnError) {
591
+ throw new Error(`${TOOL_ID4}: failed to spawn node \u2014 ${r.spawnError}`);
592
+ }
593
+ return { ok: r.exitCode === 0, exitCode: r.exitCode, output: r.stdout };
594
+ }
595
+ if (action === "kill") {
596
+ const taskDir = String(args.taskDir ?? "");
597
+ if (!taskDir) {
598
+ throw new Error(`${TOOL_ID4}: \`action:"kill"\` requires \`taskDir\``);
599
+ }
600
+ const argv = ["kill", "--task-dir", taskDir];
601
+ const r = await runAutotestScript({
602
+ scriptRel: SCRIPT_REL4,
603
+ argv,
604
+ cwd: context.directory,
605
+ abort: context.abort
606
+ });
607
+ if (r.spawnError) {
608
+ throw new Error(`${TOOL_ID4}: failed to spawn node \u2014 ${r.spawnError}`);
609
+ }
610
+ return { ok: r.exitCode === 0, exitCode: r.exitCode, output: r.stdout };
611
+ }
612
+ if (action === "check_hap") {
613
+ const hap = String(args.hap ?? "");
614
+ if (!hap) {
615
+ throw new Error(`${TOOL_ID4}: \`action:"check_hap"\` requires \`hap\``);
616
+ }
617
+ const argv = ["check-hap", "--hap", hap];
618
+ const r = await runAutotestScript({
619
+ scriptRel: SCRIPT_REL4,
620
+ argv,
621
+ cwd: context.directory,
622
+ abort: context.abort
623
+ });
624
+ if (r.spawnError) {
625
+ throw new Error(`${TOOL_ID4}: failed to spawn node \u2014 ${r.spawnError}`);
626
+ }
627
+ return { ok: r.exitCode === 0, exitCode: r.exitCode, output: r.stdout };
628
+ }
629
+ if (action === "check_inputs") {
630
+ const testcases = String(args.testcases ?? "");
631
+ const metadata = String(args.metadata ?? "");
632
+ if (!testcases || !metadata) {
633
+ throw new Error(`${TOOL_ID4}: \`action:"check_inputs"\` requires \`testcases\` and \`metadata\``);
634
+ }
635
+ const argv = ["check-inputs", "--testcases", testcases, "--metadata", metadata];
636
+ const r = await runAutotestScript({
637
+ scriptRel: SCRIPT_REL4,
638
+ argv,
639
+ cwd: context.directory,
640
+ abort: context.abort
641
+ });
642
+ if (r.spawnError) {
643
+ throw new Error(`${TOOL_ID4}: failed to spawn node \u2014 ${r.spawnError}`);
644
+ }
645
+ return { ok: r.exitCode === 0, exitCode: r.exitCode, output: r.stdout };
646
+ }
647
+ if (action === "check_pre") {
648
+ const testcases = String(args.testcases ?? "");
649
+ if (!testcases) {
650
+ throw new Error(`${TOOL_ID4}: \`action:"check_pre"\` requires \`testcases\``);
651
+ }
652
+ const argv = ["check-pre", "--testcases", testcases];
653
+ const r = await runAutotestScript({
654
+ scriptRel: SCRIPT_REL4,
655
+ argv,
656
+ cwd: context.directory,
657
+ abort: context.abort
658
+ });
659
+ if (r.spawnError) {
660
+ throw new Error(`${TOOL_ID4}: failed to spawn node \u2014 ${r.spawnError}`);
661
+ }
662
+ return { ok: r.exitCode === 0, exitCode: r.exitCode, output: r.stdout };
663
+ }
664
+ throw new Error(
665
+ `${TOOL_ID4}: unknown \`action\` "${action}" (expected run|status|kill|check_hap|check_inputs|check_pre)`
666
+ );
667
+ }
668
+ };
669
+ }
670
+
124
671
  // src/index.ts
125
672
  function createServer(deps) {
126
673
  return async (input) => {
127
- const skillsRoot = path4.join(PKG_ROOT, "skills");
128
- const agentsRoot = path4.join(PKG_ROOT, "agents");
674
+ const skillsRoot = path10.join(PKG_ROOT, "skills");
675
+ const agentsRoot = path10.join(PKG_ROOT, "agents");
129
676
  const agentPrompts = discoverA2HAgents(agentsRoot);
130
677
  return {
131
678
  dispose: async () => {
132
679
  agentPrompts.clear();
133
680
  },
134
- // ── config hook: register skills search paths, hidden skills, and agents ──
681
+ // ── config hook: register skills search paths and agents ──
135
682
  config: async (cfg) => {
136
683
  const skills = cfg.skills || {};
137
684
  cfg.skills = skills;
@@ -140,15 +687,6 @@ function createServer(deps) {
140
687
  if (!paths.includes(skillsRoot)) {
141
688
  paths.push(skillsRoot);
142
689
  }
143
- const { hidden: a2hHidden, all: a2hAll } = discoverA2HSkills(skillsRoot);
144
- const currentHidden = skills.hidden || [];
145
- const cleaned = currentHidden.filter((name) => !a2hAll.includes(name));
146
- for (const name of a2hHidden) {
147
- if (!cleaned.includes(name)) {
148
- cleaned.push(name);
149
- }
150
- }
151
- skills.hidden = cleaned;
152
690
  const agent = cfg.agent || {};
153
691
  cfg.agent = agent;
154
692
  for (const [name, prompt] of agentPrompts) {
@@ -161,13 +699,33 @@ function createServer(deps) {
161
699
  }
162
700
  }
163
701
  },
164
- // ── shell.env hook: inject DevEco Studio paths as env vars ──
165
- // NOTE: deliberately uses the *base* env the multimodal model apiKey
166
- // is NOT injected into LLM shell sessions (would leak into every command
167
- // and the transcript).
702
+ // ── shell.env hook: inject DevEco Studio paths + multimodal model ──
703
+ // params (from A2HHostDeps.resolveModelParams) as env vars, so the
704
+ // UI-alignment scripts (app_feature_verify.ts) can call the model
705
+ // without falling back to ~/.hometrans/config.json.
168
706
  "shell.env": async (shellInput, output) => {
169
707
  const a2hEnv = await buildA2HEnvBase(deps);
170
708
  Object.assign(output.env, a2hEnv);
709
+ },
710
+ // ── tool hook: run the UI-verification phone agent without leaking the
711
+ // model apiKey into env/argv — it is resolved from the host and piped to
712
+ // the script over stdin. See src/tools/a2h-app-feature-verify.ts.
713
+ //
714
+ // The autotest_* tools wrap the engine scripts under tools/autotest/
715
+ // (vendored from HomeTrans). They are grouped by noun — each tool
716
+ // dispatches on an `action` enum (e.g. `a2h_autotest_selftest` covers
717
+ // run|status|kill|check_hap|check_inputs|check_pre), so 11 engine
718
+ // subcommands surface as 4 plugin tools. Only `a2h_autotest_selftest`
719
+ // with `action:"run"` needs a model, resolved via deps.resolveModelParams
720
+ // and piped over stdin with the same secret-handling discipline; the
721
+ // rest are pure JSON/Markdown transforms or state probes with no model
722
+ // and no device.
723
+ tool: {
724
+ a2h_app_feature_verify: makeA2hAppFeatureVerifyTool(deps),
725
+ a2h_autotest_resolve_metadata: makeA2hAutotestResolveMetadataTool(deps),
726
+ a2h_autotest_testcases: makeA2hAutotestTestcasesTool(deps),
727
+ a2h_autotest_report: makeA2hAutotestReportTool(deps),
728
+ a2h_autotest_selftest: makeA2hAutotestSelftestTool(deps)
171
729
  }
172
730
  };
173
731
  };