android2harmony 0.1.2 → 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.
- package/agents/self-tester.md +339 -0
- package/dist/index.js +625 -7
- package/dist/index.js.map +4 -4
- package/package.json +37 -29
- package/skills/hmos-incremental-ui-align/README.md +251 -0
- package/skills/hmos-incremental-ui-align/SKILL.md +365 -0
- package/skills/hmos-incremental-ui-align/diff_analysis.md +53 -0
- package/skills/hmos-incremental-ui-align/page_align.md +62 -0
- package/skills/hmos-incremental-ui-align/references/Comparison_Template.md +38 -0
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- package/skills/hmos-incremental-ui-align/references/State_Model_Template.md +74 -0
- package/skills/hmos-incremental-ui-align/references/UI_Analysis_Template.md +34 -0
- package/skills/hmos-incremental-ui-align/references/android-to-harmonyOS-ui-atomic-component-mapping-reference.md +2533 -0
- package/skills/hmos-incremental-ui-align/references/android-to-harmonyOS-ui-interaction-mapping-reference.md +555 -0
- package/skills/hmos-incremental-ui-align/references/android-to-harmonyOS-ui-layout-mapping-reference.md +117 -0
- package/skills/hmos-incremental-ui-align/scripts/app_feature_verify.ts +999 -0
- package/skills/hmos-incremental-ui-align/scripts/extract_checklist.ts +343 -0
- package/skills/hmos-incremental-ui-align/scripts/navigation-capure.md +76 -0
- package/skills/hmos-incremental-ui-align/scripts/page_capture.ts +977 -0
- package/skills/hmos-incremental-ui-align/scripts/page_capture_burst.ts +188 -0
- package/tools/autotest/deps/autotest-agent-0.1.1.tgz +0 -0
- package/tools/autotest/engine/batch-launcher.ts +326 -0
- package/tools/autotest/engine/report-tool.ts +773 -0
- package/tools/autotest/engine/self-test-runner.ts +1024 -0
- package/tools/autotest/engine/testcases-tool.ts +246 -0
- package/tools/autotest/resolve-metadata-tool.ts +143 -0
- package/tools/autotest/runner/logger.ts +45 -0
- package/tools/autotest/runner/process-utils.ts +41 -0
- package/tools/autotest/validate.ts +115 -0
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
|
-
import
|
|
2
|
+
import path10 from "node:path";
|
|
3
3
|
|
|
4
4
|
// src/paths.ts
|
|
5
5
|
import path from "node:path";
|
|
@@ -70,11 +70,609 @@ async function buildA2HEnvBase(deps) {
|
|
|
70
70
|
return env;
|
|
71
71
|
}
|
|
72
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
|
+
|
|
73
671
|
// src/index.ts
|
|
74
672
|
function createServer(deps) {
|
|
75
673
|
return async (input) => {
|
|
76
|
-
const skillsRoot =
|
|
77
|
-
const agentsRoot =
|
|
674
|
+
const skillsRoot = path10.join(PKG_ROOT, "skills");
|
|
675
|
+
const agentsRoot = path10.join(PKG_ROOT, "agents");
|
|
78
676
|
const agentPrompts = discoverA2HAgents(agentsRoot);
|
|
79
677
|
return {
|
|
80
678
|
dispose: async () => {
|
|
@@ -101,13 +699,33 @@ function createServer(deps) {
|
|
|
101
699
|
}
|
|
102
700
|
}
|
|
103
701
|
},
|
|
104
|
-
// ── shell.env hook: inject DevEco Studio paths
|
|
105
|
-
//
|
|
106
|
-
//
|
|
107
|
-
//
|
|
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.
|
|
108
706
|
"shell.env": async (shellInput, output) => {
|
|
109
707
|
const a2hEnv = await buildA2HEnvBase(deps);
|
|
110
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)
|
|
111
729
|
}
|
|
112
730
|
};
|
|
113
731
|
};
|