android2harmony 0.1.4 → 0.1.6
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/README.md +1 -407
- package/agents/self-tester.md +55 -376
- package/dist/index.js +238 -124
- package/dist/index.js.map +4 -4
- package/package.json +36 -32
- package/skills/a2h-resource-convert/SKILL.md +931 -0
- package/skills/a2h-resource-convert/references/code-vector-icon-rules.md +335 -0
- package/skills/{hmos-resources-convert → a2h-resource-convert}/references/conversion-rules.md +15 -2
- package/skills/{hmos-resources-convert → a2h-resource-convert}/references/dependency-analysis-rules.md +24 -2
- package/skills/a2h-resource-convert/references/lottie-conversion-rules.md +219 -0
- package/skills/{hmos-resources-convert → a2h-resource-convert}/references/resource-mapping-rules.md +27 -2
- package/skills/{hmos-resources-convert → a2h-resource-convert}/references/xml-drawable-to-svg-rules.md +118 -1
- package/skills/a2h-resource-convert/scripts/a2h_resource_convert.js +2186 -0
- package/skills/a2h-resource-convert/scripts/app_identity.js +741 -0
- package/skills/a2h-resource-convert/scripts/code_vector_icons.js +607 -0
- package/skills/a2h-resource-convert/scripts/package.json +3 -0
- package/skills/a2h-resource-convert/scripts/svg_fidelity_check.js +632 -0
- package/skills/a2h-ui-transfer/SKILL.md +431 -0
- package/skills/a2h-ui-transfer/references/conversion-procedure.md +547 -0
- package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm-v2/_/350/214/203/345/274/217/351/200/211/346/213/251/350/257/264/346/230/216.md +1 -1
- package/skills/a2h-ui-transfer/references/mvvm-v2/_/350/243/205/351/245/260/345/231/250/351/200/237/346/237/245.md +133 -0
- package/skills/{hmos-batch-ui-align/scripts/android_parse_fast.ts → a2h-ui-transfer/scripts/android_parse_fast.js} +532 -301
- package/skills/a2h-ui-transfer/scripts/arkts_static_check.js +1624 -0
- package/skills/a2h-ui-transfer/scripts/measure_pack.js +1005 -0
- package/skills/a2h-ui-transfer/scripts/package.json +3 -0
- package/skills/hmos-fix-build-errors/SKILL.md +1 -1
- package/skills/hmos-incremental-ui-align/README.md +15 -15
- package/skills/hmos-incremental-ui-align/SKILL.md +15 -15
- package/skills/hmos-incremental-ui-align/references/State_Model_Template.md +2 -2
- package/skills/hmos-incremental-ui-align/scripts/app_feature_verify.js +790 -0
- package/skills/hmos-incremental-ui-align/scripts/extract_checklist.js +285 -0
- package/skills/hmos-incremental-ui-align/scripts/navigation-capure.md +76 -76
- package/skills/hmos-incremental-ui-align/scripts/page_capture.js +756 -0
- package/skills/hmos-incremental-ui-align/scripts/page_capture_burst.js +155 -0
- package/skills/hmos-integration-test/README.md +341 -0
- package/skills/hmos-integration-test/SKILL.md +446 -0
- package/skills/hmos-integration-test/scripts/report-tool.mjs +646 -0
- package/skills/hmos-integration-test/scripts/resolve-metadata-tool.mjs +147 -0
- package/skills/hmos-integration-test/scripts/self-test-runner.mjs +1006 -0
- package/skills/hmos-integration-test/scripts/testcases-tool.mjs +189 -0
- package/skills/hmos-spec-generate/SKILL.md +26 -24
- package/skills/hmos-spec-generate/scripts/parse_requirements.ts +515 -0
- package/skills/hmos-spec-generate/template/REQ.txt +22 -0
- package/skills/hmos-spec-generate/template/REQ.xlsx +0 -0
- package/skills/hmos-batch-ui-align/SKILL.md +0 -141
- package/skills/hmos-batch-ui-align/references/conversion-procedure.md +0 -217
- package/skills/hmos-incremental-ui-align/scripts/app_feature_verify.ts +0 -999
- package/skills/hmos-incremental-ui-align/scripts/extract_checklist.ts +0 -343
- package/skills/hmos-incremental-ui-align/scripts/page_capture.ts +0 -977
- package/skills/hmos-incremental-ui-align/scripts/page_capture_burst.ts +0 -188
- package/skills/hmos-resources-convert/SKILL.md +0 -654
- package/skills/hmos-resources-convert/template/AppScope/app.json5 +0 -10
- package/skills/hmos-resources-convert/template/AppScope/resources/base/element/string.json +0 -8
- package/skills/hmos-resources-convert/template/AppScope/resources/base/media/background.png +0 -0
- package/skills/hmos-resources-convert/template/AppScope/resources/base/media/foreground.png +0 -0
- package/skills/hmos-resources-convert/template/AppScope/resources/base/media/layered_image.json +0 -7
- package/skills/hmos-resources-convert/template/build-profile.json5 +0 -42
- package/skills/hmos-resources-convert/template/code-linter.json5 +0 -32
- package/skills/hmos-resources-convert/template/entry/build-profile.json5 +0 -33
- package/skills/hmos-resources-convert/template/entry/hvigorfile.ts +0 -6
- package/skills/hmos-resources-convert/template/entry/obfuscation-rules.txt +0 -23
- package/skills/hmos-resources-convert/template/entry/oh-package.json5 +0 -10
- package/skills/hmos-resources-convert/template/entry/src/main/ets/entryability/EntryAbility.ets +0 -48
- package/skills/hmos-resources-convert/template/entry/src/main/ets/entrybackupability/EntryBackupAbility.ets +0 -16
- package/skills/hmos-resources-convert/template/entry/src/main/ets/pages/Index.ets +0 -23
- package/skills/hmos-resources-convert/template/entry/src/main/module.json5 +0 -55
- package/skills/hmos-resources-convert/template/entry/src/main/resources/base/element/color.json +0 -8
- package/skills/hmos-resources-convert/template/entry/src/main/resources/base/element/float.json +0 -8
- package/skills/hmos-resources-convert/template/entry/src/main/resources/base/element/string.json +0 -16
- package/skills/hmos-resources-convert/template/entry/src/main/resources/base/media/background.png +0 -0
- package/skills/hmos-resources-convert/template/entry/src/main/resources/base/media/foreground.png +0 -0
- package/skills/hmos-resources-convert/template/entry/src/main/resources/base/media/layered_image.json +0 -7
- package/skills/hmos-resources-convert/template/entry/src/main/resources/base/media/startIcon.png +0 -0
- package/skills/hmos-resources-convert/template/entry/src/main/resources/base/profile/backup_config.json +0 -3
- package/skills/hmos-resources-convert/template/entry/src/main/resources/base/profile/main_pages.json +0 -5
- package/skills/hmos-resources-convert/template/entry/src/main/resources/dark/element/color.json +0 -8
- package/skills/hmos-resources-convert/template/entry/src/mock/mock-config.json5 +0 -2
- package/skills/hmos-resources-convert/template/entry/src/ohosTest/ets/test/Ability.test.ets +0 -35
- package/skills/hmos-resources-convert/template/entry/src/ohosTest/ets/test/List.test.ets +0 -5
- package/skills/hmos-resources-convert/template/entry/src/ohosTest/module.json5 +0 -16
- package/skills/hmos-resources-convert/template/entry/src/test/List.test.ets +0 -5
- package/skills/hmos-resources-convert/template/entry/src/test/LocalUnit.test.ets +0 -33
- package/skills/hmos-resources-convert/template/hvigor/hvigor-config.json5 +0 -23
- package/skills/hmos-resources-convert/template/hvigorfile.ts +0 -6
- package/skills/hmos-resources-convert/template/oh-package-lock.json5 +0 -28
- package/skills/hmos-resources-convert/template/oh-package.json5 +0 -10
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mappings/android-to-harmonyOS-ui-atomic-component-mapping-reference.md +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mappings/android-to-harmonyOS-ui-interaction-mapping-reference.md +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mappings/android-to-harmonyOS-ui-layout-mapping-reference.md +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm/@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" +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm/@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" +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm/@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" +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm/@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" +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm/@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" +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm/@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" +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm/@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" +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm/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" +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm/Environment/357/274/232/350/256/276/345/244/207/347/216/257/345/242/203/346/237/245/350/257/242.md" +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm/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" +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm/MVVM/346/250/241/345/274/217/357/274/210V1/357/274/211.md" +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm/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" +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm//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" +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm-v2/!!/350/257/255/346/263/225/357/274/232/345/217/214/345/220/221/347/273/221/345/256/232.md" +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm-v2/@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" +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm-v2/@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" +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm-v2/@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" +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm-v2/@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" +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm-v2/@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" +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm-v2/@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" +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm-v2/@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" +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm-v2/@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" +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm-v2/@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" +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm-v2/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" +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm-v2/MVVM/346/250/241/345/274/217/357/274/210V2/357/274/211.md" +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm-v2/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" +0 -0
- /package/skills/{hmos-batch-ui-align → a2h-ui-transfer}/references/mvvm-v2//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" +0 -0
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
|
-
import
|
|
2
|
+
import path6 from "node:path";
|
|
3
3
|
|
|
4
4
|
// src/paths.ts
|
|
5
5
|
import path from "node:path";
|
|
@@ -71,22 +71,10 @@ async function buildA2HEnvBase(deps) {
|
|
|
71
71
|
}
|
|
72
72
|
|
|
73
73
|
// src/tools/a2h-app-feature-verify.ts
|
|
74
|
-
import { spawn } from "node:child_process";
|
|
75
74
|
import path4 from "node:path";
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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
|
-
];
|
|
75
|
+
|
|
76
|
+
// src/tools/spawn-helpers.ts
|
|
77
|
+
import { spawn } from "node:child_process";
|
|
90
78
|
function cap(s, max = 5e4) {
|
|
91
79
|
if (s.length <= max) return s;
|
|
92
80
|
const half = Math.floor(max / 2);
|
|
@@ -94,111 +82,228 @@ function cap(s, max = 5e4) {
|
|
|
94
82
|
\u2026[truncated ${s.length - max} chars]\u2026
|
|
95
83
|
` + s.slice(-half);
|
|
96
84
|
}
|
|
85
|
+
async function runModelScript(deps, context, scriptPath, argv, opts = {}) {
|
|
86
|
+
const label = opts.label ?? scriptPath;
|
|
87
|
+
const model = await deps.resolveModelParams(context.worktree);
|
|
88
|
+
if (!model || !model.apiKey) {
|
|
89
|
+
throw new Error(
|
|
90
|
+
`a2h (${label}): host did not provide model params (apiKey missing)`
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
const finalArgv = [...argv, "--model-stdin"];
|
|
94
|
+
const child = spawn("node", [scriptPath, ...finalArgv], {
|
|
95
|
+
cwd: context.directory,
|
|
96
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
97
|
+
// DevEco Code is a GUI host with no attached console; without windowsHide,
|
|
98
|
+
// Windows allocates a new console window for the child — visible as a
|
|
99
|
+
// flash on every a2h_self_test start and on every a2h_self_test_status
|
|
100
|
+
// poll, and as a persistent window during a2h_app_feature_verify's long
|
|
101
|
+
// sync run. Hide it.
|
|
102
|
+
windowsHide: true
|
|
103
|
+
});
|
|
104
|
+
child.stdin.end(
|
|
105
|
+
JSON.stringify({ apiKey: model.apiKey, modelName: model.modelName, baseURL: model.baseURL })
|
|
106
|
+
);
|
|
107
|
+
let stdout = "";
|
|
108
|
+
let stderr = "";
|
|
109
|
+
let spawnError = "";
|
|
110
|
+
child.stdout.on("data", (chunk) => {
|
|
111
|
+
const text = chunk.toString("utf8");
|
|
112
|
+
stdout += text;
|
|
113
|
+
const lines = text.split(/\r?\n/);
|
|
114
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
115
|
+
const line = lines[i].trim();
|
|
116
|
+
if (line) {
|
|
117
|
+
try {
|
|
118
|
+
context.metadata({ title: line.slice(0, 80) });
|
|
119
|
+
} catch {
|
|
120
|
+
}
|
|
121
|
+
break;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
child.stderr.on("data", (chunk) => {
|
|
126
|
+
stderr += chunk.toString("utf8");
|
|
127
|
+
});
|
|
128
|
+
const onAbort = () => {
|
|
129
|
+
if (!child.killed) child.kill("SIGTERM");
|
|
130
|
+
};
|
|
131
|
+
context.abort.addEventListener("abort", onAbort);
|
|
132
|
+
const code = await new Promise((resolve) => {
|
|
133
|
+
child.on("close", resolve);
|
|
134
|
+
child.on("error", (err) => {
|
|
135
|
+
spawnError = err.message;
|
|
136
|
+
resolve(-1);
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
context.abort.removeEventListener("abort", onAbort);
|
|
140
|
+
if (code !== 0 && opts.throwOnNonZero !== false) {
|
|
141
|
+
if (spawnError) {
|
|
142
|
+
throw new Error(`a2h (${label}): failed to spawn node \u2014 ${spawnError}`);
|
|
143
|
+
}
|
|
144
|
+
const tail = stderr.slice(-2e3);
|
|
145
|
+
throw new Error(
|
|
146
|
+
`a2h (${label}) failed (exit ${code}): ${tail || "(no stderr)"}`
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
return { ok: true, exitCode: code, output: cap(stdout) };
|
|
150
|
+
}
|
|
151
|
+
async function runScriptCollect(scriptPath, argv, context, label = scriptPath) {
|
|
152
|
+
const child = spawn("node", [scriptPath, ...argv], {
|
|
153
|
+
cwd: context.directory,
|
|
154
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
155
|
+
// Hide the console window — this runs on every a2h_self_test_status poll,
|
|
156
|
+
// so without it the host machine flashes a cmd window repeatedly.
|
|
157
|
+
windowsHide: true
|
|
158
|
+
});
|
|
159
|
+
let stdout = "";
|
|
160
|
+
let stderr = "";
|
|
161
|
+
let spawnError = "";
|
|
162
|
+
child.stdout.on("data", (chunk) => {
|
|
163
|
+
stdout += chunk.toString("utf8");
|
|
164
|
+
});
|
|
165
|
+
child.stderr.on("data", (chunk) => {
|
|
166
|
+
stderr += chunk.toString("utf8");
|
|
167
|
+
});
|
|
168
|
+
const onAbort = () => {
|
|
169
|
+
if (!child.killed) child.kill("SIGTERM");
|
|
170
|
+
};
|
|
171
|
+
context.abort.addEventListener("abort", onAbort);
|
|
172
|
+
const code = await new Promise((resolve) => {
|
|
173
|
+
child.on("close", resolve);
|
|
174
|
+
child.on("error", (err) => {
|
|
175
|
+
spawnError = err.message;
|
|
176
|
+
resolve(-1);
|
|
177
|
+
});
|
|
178
|
+
});
|
|
179
|
+
context.abort.removeEventListener("abort", onAbort);
|
|
180
|
+
if (spawnError) {
|
|
181
|
+
throw new Error(`a2h (${label}): failed to spawn node \u2014 ${spawnError}`);
|
|
182
|
+
}
|
|
183
|
+
return { ok: true, exitCode: code, output: cap(stdout) };
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// src/tools/a2h-app-feature-verify.ts
|
|
187
|
+
var APP_FEATURE_VERIFY_PATH = path4.join(
|
|
188
|
+
PKG_ROOT,
|
|
189
|
+
"skills",
|
|
190
|
+
"hmos-incremental-ui-align",
|
|
191
|
+
"scripts",
|
|
192
|
+
"app_feature_verify.js"
|
|
193
|
+
);
|
|
194
|
+
var kebab = (s) => "--" + s.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase();
|
|
195
|
+
function flattenParams(obj) {
|
|
196
|
+
const out = [];
|
|
197
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
198
|
+
if (v == null || v === "") continue;
|
|
199
|
+
if (typeof v === "boolean") {
|
|
200
|
+
if (v) out.push(kebab(k));
|
|
201
|
+
} else {
|
|
202
|
+
out.push(kebab(k), String(v));
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
return out;
|
|
206
|
+
}
|
|
97
207
|
function makeA2hAppFeatureVerifyTool(deps) {
|
|
98
208
|
return {
|
|
99
|
-
description: "
|
|
209
|
+
description: "UI \u5BFB\u8DEF phone-agent:\u5728 HarmonyOS(hdc)\u6216 Android(adb)\u5DF2\u8FDE\u63A5\u8BBE\u5907\u4E0A,\u6309\u8DEF\u5F84/\u63D0\u793A\u5BFC\u822A\u5230\u76EE\u6807\u9875\u9762\u5E76\u622A\u56FE + \u6A21\u578B\u5224\u5B9A\u662F\u5426\u5230\u8FBE\u3002\u6A21\u578B\u914D\u7F6E(apiKey \u7B49)\u7531\u5DE5\u5177\u5185\u90E8\u7ECF stdin \u6CE8\u5165,\u4E0D\u8981\u4F20 apiKey/modelStdin \u5B57\u6BB5\u3002\n\n| \u5B57\u6BB5 | \u7C7B\u578B | \u7EA6\u675F | \u8BF4\u660E |\n|---|---|---|---|\n| app | string | \u5FC5\u586B | \u76EE\u6807\u5E94\u7528\u663E\u793A\u540D |\n| package | string | \u5FC5\u586B | \u76EE\u6807\u5E94\u7528\u5305\u540D |\n| device | string | \u53EF\u9009(\u9ED8\u8BA4 hdc) | \u8BBE\u5907\u9A71\u52A8,\u53D6\u503C hdc \u6216 adb |\n| prompt | string | \u4E09\u9009\u4E00 | \u81EA\u7531\u5BFC\u822A\u63CF\u8FF0 |\n| task | string | \u4E09\u9009\u4E00 | L1>L2>L3 \u5C42\u7EA7\u8DEF\u5F84 |\n| feature | string | \u4E09\u9009\u4E00 | features.md \u8DEF\u5F84(\u987B .md \u7ED3\u5C3E) |\n| maxSteps | number | \u53EF\u9009(\u9ED8\u8BA4 30) | \u6700\u5927 agent \u6B65\u6570 |\n| serial | string | \u53EF\u9009 | \u8BBE\u5907\u5E8F\u5217\u53F7(\u591A\u8BBE\u5907\u65F6) |\n| appHints | string | \u53EF\u9009 | \u5E94\u7528\u5BFC\u822A\u63D0\u793A |\n| limit | number | \u53EF\u9009 | --feature \u6A21\u5F0F\u53D6\u524D N \u6761 |\n| offset | number | \u53EF\u9009(\u9ED8\u8BA4 1) | --feature \u6A21\u5F0F\u504F\u79FB |\n| quiet | boolean | \u53EF\u9009 | true \u65F6\u6291\u5236\u63D0\u793A\u6027\u8F93\u51FA |\n\n\u7EA6\u675F:app \u4E0E package \u5FC5\u586B,\u4E14 prompt/task/feature \u4E09\u9009\u4E00(\u4F20\u4E00\u4E2A)\u3002",
|
|
210
|
+
// `args` is a flat field-name→field-schema MAP (not a full `{type:"object",
|
|
211
|
+
// properties, required}` schema object). The host's `legacyJsonSchema` reads
|
|
212
|
+
// this map and builds the LLM-facing schema itself: `{type:"object",
|
|
213
|
+
// properties:{...}, required:[...every field...]}` (it marks every field
|
|
214
|
+
// required). So optional fields MUST be nullable (`["string","null"]` etc.)
|
|
215
|
+
// -- the agent passes null (or "") and `flattenParams` drops it, letting the
|
|
216
|
+
// script's own default apply. Declaring the full-schema wrapper here would
|
|
217
|
+
// make `legacyJsonSchema` mis-read `properties` as a literal field name. Keep
|
|
218
|
+
// it a field-map.
|
|
100
219
|
args: {
|
|
101
|
-
type: "
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
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"]
|
|
220
|
+
app: { type: "string", description: "\u76EE\u6807\u5E94\u7528\u663E\u793A\u540D" },
|
|
221
|
+
package: { type: "string", description: "\u76EE\u6807\u5E94\u7528\u5305\u540D" },
|
|
222
|
+
device: { type: ["string", "null"], description: "\u8BBE\u5907\u9A71\u52A8 hdc \u6216 adb,\u53EF\u9009;\u9ED8\u8BA4 hdc" },
|
|
223
|
+
prompt: { type: ["string", "null"], description: "\u81EA\u7531\u5BFC\u822A\u63CF\u8FF0(\u4E0E task/feature \u4E09\u9009\u4E00)" },
|
|
224
|
+
task: { type: ["string", "null"], description: "L1>L2>L3 \u5C42\u7EA7\u8DEF\u5F84(\u4E0E prompt/feature \u4E09\u9009\u4E00)" },
|
|
225
|
+
feature: { type: ["string", "null"], description: "features.md \u8DEF\u5F84,\u987B .md \u7ED3\u5C3E(\u4E0E prompt/task \u4E09\u9009\u4E00)" },
|
|
226
|
+
maxSteps: { type: ["number", "null"], description: "\u6700\u5927 agent \u6B65\u6570,\u53EF\u9009;\u9ED8\u8BA4 30" },
|
|
227
|
+
serial: { type: ["string", "null"], description: "\u8BBE\u5907\u5E8F\u5217\u53F7(\u591A\u8BBE\u5907\u65F6),\u53EF\u9009" },
|
|
228
|
+
appHints: { type: ["string", "null"], description: "\u5E94\u7528\u5BFC\u822A\u63D0\u793A,\u53EF\u9009" },
|
|
229
|
+
limit: { type: ["number", "null"], description: "--feature \u6A21\u5F0F\u53D6\u524D N \u6761,\u53EF\u9009" },
|
|
230
|
+
offset: { type: ["number", "null"], description: "--feature \u6A21\u5F0F\u504F\u79FB,\u53EF\u9009;\u9ED8\u8BA4 1" },
|
|
231
|
+
quiet: { type: ["boolean", "null"], description: "true \u65F6\u6291\u5236\u63D0\u793A\u6027\u8F93\u51FA,\u53EF\u9009" }
|
|
125
232
|
},
|
|
126
233
|
execute: async (args, context) => {
|
|
127
|
-
const
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
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");
|
|
234
|
+
const params = { ...args };
|
|
235
|
+
if (params.prompt == null && params.task == null && params.feature == null) {
|
|
236
|
+
throw new Error(
|
|
237
|
+
"a2h_app_feature_verify: must pass at least one of prompt / task / feature"
|
|
238
|
+
);
|
|
137
239
|
}
|
|
138
|
-
|
|
139
|
-
|
|
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
|
-
}
|
|
240
|
+
return runModelScript(deps, context, APP_FEATURE_VERIFY_PATH, flattenParams(params), {
|
|
241
|
+
label: "app_feature_verify"
|
|
178
242
|
});
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
243
|
+
}
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// src/tools/a2h-self-test.ts
|
|
248
|
+
import path5 from "node:path";
|
|
249
|
+
var SELF_TEST_RUNNER = path5.join(
|
|
250
|
+
PKG_ROOT,
|
|
251
|
+
"skills",
|
|
252
|
+
"hmos-integration-test",
|
|
253
|
+
"scripts",
|
|
254
|
+
"self-test-runner.mjs"
|
|
255
|
+
);
|
|
256
|
+
function makeA2hSelfTestTool(deps) {
|
|
257
|
+
return {
|
|
258
|
+
description: '\u542F\u52A8 HarmonyOS \u5E94\u7528\u81EA\u6D4B:\u5B89\u88C5 HAP,spawn AutoTest batch_runner(detached) \u540E\u7ACB\u5373\u8FD4\u56DE RUNNING + task_subdir,\u4E0D\u963B\u585E\u7B49\u5F85\u5B8C\u6210\u3002\u7528 a2h_self_test_status \u8F6E\u8BE2\u7EC8\u6001,\u8D85\u65F6\u5219\u7528 Bash kill \u6389\u8FD4\u56DE\u7684 pid\u3002\u6A21\u578B\u914D\u7F6E(apiKey \u7B49)\u7531\u5DE5\u5177\u5185\u90E8\u7ECF stdin \u6CE8\u5165,\u4E0D\u8981\u4F20 apiKey/modelStdin \u5B57\u6BB5\u3002\n\n| \u5B57\u6BB5 | \u7C7B\u578B | \u7EA6\u675F | \u8BF4\u660E |\n|---|---|---|---|\n| testcases | string | \u5FC5\u586B | testcases.json \u8DEF\u5F84 |\n| hap | string | \u5FC5\u586B | \u7B7E\u540D\u5305(.hap/.hsp \u6587\u4EF6/\u76EE\u5F55,\u9017\u53F7\u5206\u9694\u591A\u5305) |\n| bundleName | string | \u5FC5\u586B | \u5E94\u7528\u5305\u540D |\n| taskDir | string | \u5FC5\u586B | \u4EFB\u52A1\u8F93\u51FA\u76EE\u5F55(\u901A\u5E38 <output>/task) |\n| outputDir | string | \u53EF\u9009 | \u65E5\u5FD7\u76EE\u5F55(\u901A\u5E38 <output>);\u4E0D\u4F20\u5219\u7528 taskDir |\n| category | string | \u53EF\u9009 | \u6D4B\u8BD5\u5206\u7C7B\u540D(\u9ED8\u8BA4 self_test,\u901A\u5E38\u4F20 app_name) |\n| mode | string | \u53EF\u9009 | single(\u9ED8\u8BA4)\u6216 layered(Planner+Executor \u53CC Agent) |\n\n\u8FD4\u56DE {ok, exitCode, output},output \u4E3A self-test-runner stdout JSON\u3002\u6B63\u5E38\u4E3A {status:"RUNNING", pid, task_dir, task_subdir}(\u5B58 pid \u4E0E task_subdir \u4F9B\u8F6E\u8BE2\u4E0E\u8D85\u65F6 kill);\u73AF\u5883\u5931\u8D25\u65F6\u4E3A {status:"FAILED", error}(\u5982 @autotest/agent \u81EA\u52A8\u5B89\u88C5\u5931\u8D25\u6216 HAP \u5B89\u88C5/\u7528\u4F8B\u51C6\u5907\u629B\u9519)\u2014\u2014\u6B64\u65F6\u4E0D\u8981\u8F6E\u8BE2,\u76F4\u63A5\u628A error \u62A5\u7ED9\u7528\u6237\u5E76\u505C\u6B62\u3002\u5DE5\u5177\u5BF9 FAILED \u4E0D\u629B\u9519(\u975E\u96F6\u9000\u51FA\u7801 = FAILED),\u9700\u89E3\u6790 output \u6309 status \u5206\u652F\u3002',
|
|
259
|
+
args: {
|
|
260
|
+
testcases: { type: "string", description: "testcases.json \u8DEF\u5F84" },
|
|
261
|
+
hap: { type: "string", description: "\u7B7E\u540D\u5305\u8DEF\u5F84(.hap/.hsp \u6587\u4EF6/\u76EE\u5F55,\u9017\u53F7\u5206\u9694)" },
|
|
262
|
+
bundleName: { type: "string", description: "\u5E94\u7528\u5305\u540D" },
|
|
263
|
+
taskDir: { type: "string", description: "\u4EFB\u52A1\u8F93\u51FA\u76EE\u5F55,\u901A\u5E38 <output>/task" },
|
|
264
|
+
outputDir: { type: ["string", "null"], description: "\u65E5\u5FD7\u76EE\u5F55,\u53EF\u9009;\u4E0D\u4F20\u5219\u7528 taskDir" },
|
|
265
|
+
category: { type: ["string", "null"], description: "\u6D4B\u8BD5\u5206\u7C7B\u540D,\u53EF\u9009;\u9ED8\u8BA4 self_test,\u901A\u5E38\u4F20 app_name" },
|
|
266
|
+
mode: { type: ["string", "null"], description: "single \u6216 layered,\u53EF\u9009;\u9ED8\u8BA4 single" }
|
|
267
|
+
},
|
|
268
|
+
execute: async (args, context) => {
|
|
269
|
+
const argv = [];
|
|
270
|
+
const push = (flag, v) => {
|
|
271
|
+
if (v == null || v === "") return;
|
|
272
|
+
argv.push(flag, String(v));
|
|
184
273
|
};
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
274
|
+
push("--testcases", args.testcases);
|
|
275
|
+
push("--hap", args.hap);
|
|
276
|
+
push("--bundle-name", args.bundleName);
|
|
277
|
+
push("--task-dir", args.taskDir);
|
|
278
|
+
push("--output-dir", args.outputDir);
|
|
279
|
+
push("--category", args.category);
|
|
280
|
+
push("--mode", args.mode);
|
|
281
|
+
return runModelScript(deps, context, SELF_TEST_RUNNER, argv, {
|
|
282
|
+
label: "self_test",
|
|
283
|
+
// Non-zero exit = a FAILED start (failJson prints {status:"FAILED",error}
|
|
284
|
+
// and exits 1). Return it as output -- not throw -- so the agent can read
|
|
285
|
+
// the structured error; the description contract tells it to branch on
|
|
286
|
+
// status instead of polling. (Normal RUNNING start also exits 0.)
|
|
287
|
+
throwOnNonZero: false
|
|
192
288
|
});
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
289
|
+
}
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// src/tools/a2h-self-test-status.ts
|
|
294
|
+
function makeA2hSelfTestStatusTool() {
|
|
295
|
+
return {
|
|
296
|
+
description: '\u67E5\u8BE2 a2h_self_test \u542F\u52A8\u7684\u81EA\u6D4B\u6267\u884C\u72B6\u6001(\u8BFB summary.json / task_results.jsonl / \u65E5\u5FD7 mtime)\u3002\u4E0D\u9700\u8981 apiKey\u3002\u53CD\u590D\u8C03\u7528\u76F4\u5230\u7EC8\u6001(COMPLETED/CRASHED/NOT_STARTED),RUNNING \u5219\u95F4\u9694\u540E\u91CD\u8BD5\u3002\n\n| \u5B57\u6BB5 | \u7C7B\u578B | \u7EA6\u675F | \u8BF4\u660E |\n|---|---|---|---|\n| taskDir | string | \u5FC5\u586B | a2h_self_test \u8FD4\u56DE\u7684 task_dir |\n| outputDir | string | \u53EF\u9009 | \u65E5\u5FD7\u76EE\u5F55(\u540C a2h_self_test \u7684 outputDir) |\n\n\u8FD4\u56DE {ok, exitCode, output},output \u4E3A\u72B6\u6001 JSON:\n- COMPLETED(exit 0):{status, task_subdir, cases_done, pass_count, fail_count, unknown_count, pass_rate, log_tail}\n- RUNNING(exit 2):{status, cases_done, last_case, task_subdir, log_tail}\n- CRASHED(exit 3):\u540C RUNNING \u5F62\u72B6\n- NOT_STARTED(exit 4):{status:"NOT_STARTED"}\nCOMPLETED/CRASHED \u65F6 runner \u4F1A\u5220\u9664\u4E34\u65F6 autotest.yaml(\u542B key)\u3002',
|
|
297
|
+
args: {
|
|
298
|
+
taskDir: { type: "string", description: "a2h_self_test \u8FD4\u56DE\u7684 task_dir" },
|
|
299
|
+
outputDir: { type: ["string", "null"], description: "\u65E5\u5FD7\u76EE\u5F55,\u53EF\u9009" }
|
|
300
|
+
},
|
|
301
|
+
execute: async (args, context) => {
|
|
302
|
+
const argv = ["--status", "--task-dir", String(args.taskDir)];
|
|
303
|
+
if (args.outputDir != null && args.outputDir !== "") {
|
|
304
|
+
argv.push("--output-dir", String(args.outputDir));
|
|
200
305
|
}
|
|
201
|
-
return
|
|
306
|
+
return runScriptCollect(SELF_TEST_RUNNER, argv, context, "self_test_status");
|
|
202
307
|
}
|
|
203
308
|
};
|
|
204
309
|
}
|
|
@@ -206,8 +311,8 @@ function makeA2hAppFeatureVerifyTool(deps) {
|
|
|
206
311
|
// src/index.ts
|
|
207
312
|
function createServer(deps) {
|
|
208
313
|
return async (input) => {
|
|
209
|
-
const skillsRoot =
|
|
210
|
-
const agentsRoot =
|
|
314
|
+
const skillsRoot = path6.join(PKG_ROOT, "skills");
|
|
315
|
+
const agentsRoot = path6.join(PKG_ROOT, "agents");
|
|
211
316
|
const agentPrompts = discoverA2HAgents(agentsRoot);
|
|
212
317
|
return {
|
|
213
318
|
dispose: async () => {
|
|
@@ -236,23 +341,32 @@ function createServer(deps) {
|
|
|
236
341
|
},
|
|
237
342
|
// ── shell.env hook: inject DevEco Studio / SDK path vars only. The ──
|
|
238
343
|
// multimodal model apiKey is deliberately NOT put in env (it would leak
|
|
239
|
-
// to every adb/hdc descendant); the
|
|
240
|
-
//
|
|
344
|
+
// to every adb/hdc descendant); the a2h model-script tools pipe it to
|
|
345
|
+
// the script over stdin instead. See src/env.ts.
|
|
241
346
|
"shell.env": async (shellInput, output) => {
|
|
242
347
|
const a2hEnv = await buildA2HEnvBase(deps);
|
|
243
348
|
Object.assign(output.env, a2hEnv);
|
|
244
349
|
},
|
|
245
|
-
// ── tool hook: run
|
|
246
|
-
//
|
|
247
|
-
// the
|
|
350
|
+
// ── tool hook: run skill scripts that need the multimodal model without
|
|
351
|
+
// leaking the apiKey into env/argv. Three fixed-schema tools, each
|
|
352
|
+
// resolving the apiKey from the host and piping it over stdin
|
|
353
|
+
// (`--model-stdin`) — never env/argv, so adb/hdc descendants can't see it:
|
|
354
|
+
// • `a2h_app_feature_verify` (src/tools/a2h-app-feature-verify.ts) —
|
|
355
|
+
// phone-agent UI navigation + screenshot + model verification
|
|
356
|
+
// (app_feature_verify.js). Synchronous run; throws on non-zero exit.
|
|
357
|
+
// • `a2h_self_test` (src/tools/a2h-self-test.ts) +
|
|
358
|
+
// `a2h_self_test_status` (src/tools/a2h-self-test-status.ts) — the
|
|
359
|
+
// integration-test flow. The former spawns self-test-runner.mjs
|
|
360
|
+
// which spawns batch_runner detached and returns RUNNING immediately
|
|
361
|
+
// (no blocking); the latter polls it via `--status` (no key needed).
|
|
248
362
|
//
|
|
249
|
-
// The AutoTest engine (
|
|
250
|
-
//
|
|
251
|
-
//
|
|
252
|
-
// "Resolving the AutoTest Tools Directory" section). a2h therefore ships
|
|
253
|
-
// only this one plugin tool and carries no autotest runtime dependency.
|
|
363
|
+
// The AutoTest engine (batch_runner / testcases / report / resolve-metadata)
|
|
364
|
+
// lives in @autotest/agent + the HomeTrans package, invoked via `node`;
|
|
365
|
+
// a2h carries no autotest runtime dependency.
|
|
254
366
|
tool: {
|
|
255
|
-
a2h_app_feature_verify: makeA2hAppFeatureVerifyTool(deps)
|
|
367
|
+
a2h_app_feature_verify: makeA2hAppFeatureVerifyTool(deps),
|
|
368
|
+
a2h_self_test: makeA2hSelfTestTool(deps),
|
|
369
|
+
a2h_self_test_status: makeA2hSelfTestStatusTool()
|
|
256
370
|
}
|
|
257
371
|
};
|
|
258
372
|
};
|
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"],
|
|
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;
|
|
6
|
-
"names": ["path", "path", "path", "path", "path", "path"]
|
|
3
|
+
"sources": ["../src/index.ts", "../src/paths.ts", "../src/discovery.ts", "../src/env.ts", "../src/tools/a2h-app-feature-verify.ts", "../src/tools/spawn-helpers.ts", "../src/tools/a2h-self-test.ts", "../src/tools/a2h-self-test-status.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 { makeA2hSelfTestTool } from \"./tools/a2h-self-test\"\r\nimport { makeA2hSelfTestStatusTool } from \"./tools/a2h-self-test-status\"\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 model-script tools pipe it to\r\n // 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 skill scripts that need the multimodal model without\r\n // leaking the apiKey into env/argv. Three fixed-schema tools, each\r\n // resolving the apiKey from the host and piping it over stdin\r\n // (`--model-stdin`) \u2014 never env/argv, so adb/hdc descendants can't see it:\r\n // \u2022 `a2h_app_feature_verify` (src/tools/a2h-app-feature-verify.ts) \u2014\r\n // phone-agent UI navigation + screenshot + model verification\r\n // (app_feature_verify.js). Synchronous run; throws on non-zero exit.\r\n // \u2022 `a2h_self_test` (src/tools/a2h-self-test.ts) +\r\n // `a2h_self_test_status` (src/tools/a2h-self-test-status.ts) \u2014 the\r\n // integration-test flow. The former spawns self-test-runner.mjs\r\n // which spawns batch_runner detached and returns RUNNING immediately\r\n // (no blocking); the latter polls it via `--status` (no key needed).\r\n //\r\n // The AutoTest engine (batch_runner / testcases / report / resolve-metadata)\r\n // lives in @autotest/agent + the HomeTrans package, invoked via `node`;\r\n // a2h carries no autotest runtime dependency.\r\n tool: {\r\n a2h_app_feature_verify: makeA2hAppFeatureVerifyTool(deps),\r\n a2h_self_test: makeA2hSelfTestTool(deps),\r\n a2h_self_test_status: makeA2hSelfTestStatusTool(),\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\"\r\nimport type { A2HHostDeps } from \"./types\"\r\n\r\n/**\r\n * Build the A2H env var set from host-injected deps. Shared by the `shell.env`\r\n * hook (so every shell session sees these). Env vars are the single source of\r\n * truth in the plugin context for non-secret paths.\r\n *\r\n * Contains only DevEco/SDK path vars. The multimodal model apiKey is NOT\r\n * injected here anymore \u2014 putting it in `process.env` leaked it to every\r\n * descendant process (adb/hdc, sub-agents), to `/proc/<pid>/environ`, crash\r\n * dumps, and any `env`/`printenv` output. Instead the a2h model-script tools\r\n * (`a2h_app_feature_verify` / `a2h_self_test`) resolve the params via\r\n * `A2HHostDeps.resolveModelParams` and pipe them to the script over stdin\r\n * (`--model-stdin`), so the key stays in process memory + the stdin pipe and\r\n * never enters env/argv/disk.\r\n *\r\n * Nothing secret is present in this env set, but it is still good hygiene not\r\n * to dump it into logs verbatim.\r\n */\r\nexport async function buildA2HEnvBase(\r\n deps: A2HHostDeps,\r\n): Promise<Record<string, string>> {\r\n const env: Record<string, string> = {}\r\n\r\n const home = await deps.resolveDevEcoHome()\r\n if (home) {\r\n const sdk = deps.resolveSdkPath(home)\r\n env[\"DEVECO_HOME\"] = home\r\n env[\"DEVECO_SDK_HOME\"] = sdk\r\n env[\"OHOS_SDK_PATH\"] = path.join(sdk, \"default\", \"openharmony\", \"ets\")\r\n env[\"HMS_SDK_PATH\"] = path.join(sdk, \"default\", \"hms\", \"ets\")\r\n }\r\n\r\n return env\r\n}\r\n", "import path from \"node:path\"\r\nimport { PKG_ROOT } from \"../paths\"\r\nimport type { A2HHostDeps, ToolContext, ToolDefinition } from \"../types\"\r\nimport { runModelScript } from \"./spawn-helpers\"\r\n\r\n/**\r\n * `a2h_app_feature_verify` tool \u2014 fixed-schema tool over the app_feature_verify.js\r\n * skill script (phone-agent UI navigation + screenshot + model verification on\r\n * hdc/adb devices).\r\n *\r\n * `kebab` / `flattenParams` are local to this tool (the self-test tools hand-build\r\n * their argv instead), so they live here rather than in the shared spawn-helpers.\r\n */\r\n\r\n/** Absolute path to the app_feature_verify skill script (precompiled .js). */\r\nconst APP_FEATURE_VERIFY_PATH = path.join(\r\n PKG_ROOT,\r\n \"skills\",\r\n \"hmos-incremental-ui-align\",\r\n \"scripts\",\r\n \"app_feature_verify.js\",\r\n)\r\n\r\n/** camelCase \u2192 --kebab-case flag (maxSteps \u2192 --max-steps). */\r\nconst kebab = (s: string): string => \"--\" + s.replace(/([a-z0-9])([A-Z])/g, \"$1-$2\").toLowerCase()\r\n\r\n/**\r\n * Flatten a params object into CLI argv: each entry \u2192 `--kebab-case` flag + its\r\n * value; booleans \u2192 flag-only when true (false/null/undefined skipped).\r\n * `v == null || v === \"\"` skips null/undefined AND empty string, so an optional\r\n * passed as \"\" falls through to the script's own default instead of pushing\r\n * `--flag \"\"` and overriding it (see the device=\"\" gotcha in args comment below).\r\n */\r\nfunction flattenParams(obj: Record<string, unknown>): string[] {\r\n const out: string[] = []\r\n for (const [k, v] of Object.entries(obj)) {\r\n if (v == null || v === \"\") continue\r\n if (typeof v === \"boolean\") {\r\n if (v) out.push(kebab(k))\r\n } else {\r\n out.push(kebab(k), String(v))\r\n }\r\n }\r\n return out\r\n}\r\n\r\n/**\r\n * Build the `a2h_app_feature_verify` plugin tool. The agent passes navigation\r\n * params as top-level fields; the tool flattens them to `--kebab-case` argv and\r\n * resolves the apiKey from the host, piping it over stdin \u2014 never env/argv/disk,\r\n * so invisible to adb/hdc descendants. Use this instead of running the script via\r\n * `node` directly (a direct run can only get the key from env/argv/config,\r\n * which leaks).\r\n */\r\nexport function makeA2hAppFeatureVerifyTool(deps: A2HHostDeps): ToolDefinition {\r\n return {\r\n description:\r\n \"UI \u5BFB\u8DEF phone-agent:\u5728 HarmonyOS(hdc)\u6216 Android(adb)\u5DF2\u8FDE\u63A5\u8BBE\u5907\u4E0A,\u6309\u8DEF\u5F84/\u63D0\u793A\u5BFC\u822A\u5230\u76EE\u6807\u9875\u9762\u5E76\u622A\u56FE + \u6A21\u578B\u5224\u5B9A\u662F\u5426\u5230\u8FBE\u3002\u6A21\u578B\u914D\u7F6E(apiKey \u7B49)\u7531\u5DE5\u5177\u5185\u90E8\u7ECF stdin \u6CE8\u5165,\u4E0D\u8981\u4F20 apiKey/modelStdin \u5B57\u6BB5\u3002\\n\\n\" +\r\n \"| \u5B57\u6BB5 | \u7C7B\u578B | \u7EA6\u675F | \u8BF4\u660E |\\n\" +\r\n \"|---|---|---|---|\\n\" +\r\n \"| app | string | \u5FC5\u586B | \u76EE\u6807\u5E94\u7528\u663E\u793A\u540D |\\n\" +\r\n \"| package | string | \u5FC5\u586B | \u76EE\u6807\u5E94\u7528\u5305\u540D |\\n\" +\r\n \"| device | string | \u53EF\u9009(\u9ED8\u8BA4 hdc) | \u8BBE\u5907\u9A71\u52A8,\u53D6\u503C hdc \u6216 adb |\\n\" +\r\n \"| prompt | string | \u4E09\u9009\u4E00 | \u81EA\u7531\u5BFC\u822A\u63CF\u8FF0 |\\n\" +\r\n \"| task | string | \u4E09\u9009\u4E00 | L1>L2>L3 \u5C42\u7EA7\u8DEF\u5F84 |\\n\" +\r\n \"| feature | string | \u4E09\u9009\u4E00 | features.md \u8DEF\u5F84(\u987B .md \u7ED3\u5C3E) |\\n\" +\r\n \"| maxSteps | number | \u53EF\u9009(\u9ED8\u8BA4 30) | \u6700\u5927 agent \u6B65\u6570 |\\n\" +\r\n \"| serial | string | \u53EF\u9009 | \u8BBE\u5907\u5E8F\u5217\u53F7(\u591A\u8BBE\u5907\u65F6) |\\n\" +\r\n \"| appHints | string | \u53EF\u9009 | \u5E94\u7528\u5BFC\u822A\u63D0\u793A |\\n\" +\r\n \"| limit | number | \u53EF\u9009 | --feature \u6A21\u5F0F\u53D6\u524D N \u6761 |\\n\" +\r\n \"| offset | number | \u53EF\u9009(\u9ED8\u8BA4 1) | --feature \u6A21\u5F0F\u504F\u79FB |\\n\" +\r\n \"| quiet | boolean | \u53EF\u9009 | true \u65F6\u6291\u5236\u63D0\u793A\u6027\u8F93\u51FA |\\n\\n\" +\r\n \"\u7EA6\u675F:app \u4E0E package \u5FC5\u586B,\u4E14 prompt/task/feature \u4E09\u9009\u4E00(\u4F20\u4E00\u4E2A)\u3002\",\r\n // `args` is a flat field-name\u2192field-schema MAP (not a full `{type:\"object\",\r\n // properties, required}` schema object). The host's `legacyJsonSchema` reads\r\n // this map and builds the LLM-facing schema itself: `{type:\"object\",\r\n // properties:{...}, required:[...every field...]}` (it marks every field\r\n // required). So optional fields MUST be nullable (`[\"string\",\"null\"]` etc.)\r\n // -- the agent passes null (or \"\") and `flattenParams` drops it, letting the\r\n // script's own default apply. Declaring the full-schema wrapper here would\r\n // make `legacyJsonSchema` mis-read `properties` as a literal field name. Keep\r\n // it a field-map.\r\n args: {\r\n app: { type: \"string\", description: \"\u76EE\u6807\u5E94\u7528\u663E\u793A\u540D\" },\r\n package: { type: \"string\", description: \"\u76EE\u6807\u5E94\u7528\u5305\u540D\" },\r\n device: { type: [\"string\", \"null\"], description: \"\u8BBE\u5907\u9A71\u52A8 hdc \u6216 adb,\u53EF\u9009;\u9ED8\u8BA4 hdc\" },\r\n prompt: { type: [\"string\", \"null\"], description: \"\u81EA\u7531\u5BFC\u822A\u63CF\u8FF0(\u4E0E task/feature \u4E09\u9009\u4E00)\" },\r\n task: { type: [\"string\", \"null\"], description: \"L1>L2>L3 \u5C42\u7EA7\u8DEF\u5F84(\u4E0E prompt/feature \u4E09\u9009\u4E00)\" },\r\n feature: { type: [\"string\", \"null\"], description: \"features.md \u8DEF\u5F84,\u987B .md \u7ED3\u5C3E(\u4E0E prompt/task \u4E09\u9009\u4E00)\" },\r\n maxSteps: { type: [\"number\", \"null\"], description: \"\u6700\u5927 agent \u6B65\u6570,\u53EF\u9009;\u9ED8\u8BA4 30\" },\r\n serial: { type: [\"string\", \"null\"], description: \"\u8BBE\u5907\u5E8F\u5217\u53F7(\u591A\u8BBE\u5907\u65F6),\u53EF\u9009\" },\r\n appHints: { type: [\"string\", \"null\"], description: \"\u5E94\u7528\u5BFC\u822A\u63D0\u793A,\u53EF\u9009\" },\r\n limit: { type: [\"number\", \"null\"], description: \"--feature \u6A21\u5F0F\u53D6\u524D N \u6761,\u53EF\u9009\" },\r\n offset: { type: [\"number\", \"null\"], description: \"--feature \u6A21\u5F0F\u504F\u79FB,\u53EF\u9009;\u9ED8\u8BA4 1\" },\r\n quiet: { type: [\"boolean\", \"null\"], description: \"true \u65F6\u6291\u5236\u63D0\u793A\u6027\u8F93\u51FA,\u53EF\u9009\" },\r\n },\r\n execute: async (args: Record<string, unknown>, context: ToolContext) => {\r\n const params: Record<string, unknown> = { ...args }\r\n // The script enforces \"at least one\" of prompt/task/feature (priority\r\n // prompt > task > feature; passing two silently takes the higher-priority\r\n // one). Mirror \"at least one\" here for a clearer error before spawning.\r\n if (params.prompt == null && params.task == null && params.feature == null) {\r\n throw new Error(\r\n \"a2h_app_feature_verify: must pass at least one of prompt / task / feature\",\r\n )\r\n }\r\n return runModelScript(deps, context, APP_FEATURE_VERIFY_PATH, flattenParams(params), {\r\n label: \"app_feature_verify\",\r\n })\r\n },\r\n }\r\n}\r\n", "import { spawn } from \"node:child_process\"\r\nimport type { A2HHostDeps, ToolContext } from \"../types\"\r\n\r\n/**\r\n * Shared spawn helpers for the a2h model-script tools.\r\n *\r\n * `runModelScript` resolves the model apiKey from the host and pipes it to the\r\n * spawned script over stdin (`--model-stdin`); the key traverses only the OS\r\n * pipe + process memory, never env/argv/disk, so it is invisible to the spawned\r\n * adb/hdc descendants. `runScriptCollect` spawns without stdin/throw for poll\r\n * tools whose non-zero exit codes are valid states. Both are pure spawn\r\n * mechanics \u2014 argv construction (camelCase \u2192 --kebab-case, null-skipping) lives\r\n * in each tool file, not here.\r\n */\r\n\r\n/** Cap stdout returned to the host so a runaway loop can't overflow the chat. */\r\nfunction cap(s: string, max = 50000): string {\r\n if (s.length <= max) return s\r\n const half = Math.floor(max / 2)\r\n return s.slice(0, half) + `\\n\u2026[truncated ${s.length - max} chars]\u2026\\n` + s.slice(-half)\r\n}\r\n\r\n/**\r\n * Shared runner: resolve host model params, spawn the script with the model\r\n * config piped to stdin, collect stdout/stderr, honor abort, and return the\r\n * capped stdout (or throw on non-zero exit).\r\n *\r\n * The apiKey is resolved from the host and piped to the script over stdin\r\n * (`--model-stdin`); the agent never handles it.\r\n */\r\nexport async function runModelScript(\r\n deps: A2HHostDeps,\r\n context: ToolContext,\r\n scriptPath: string,\r\n argv: string[],\r\n opts: { label?: string; throwOnNonZero?: boolean } = {},\r\n): Promise<{ ok: true; exitCode: number; output: string }> {\r\n const label = opts.label ?? scriptPath\r\n const model = await deps.resolveModelParams(context.worktree)\r\n if (!model || !model.apiKey) {\r\n throw new Error(\r\n `a2h (${label}): host did not provide model params (apiKey missing)`,\r\n )\r\n }\r\n\r\n const finalArgv = [...argv, \"--model-stdin\"]\r\n\r\n // Spawn via PATH `node`. process.execPath might be an older/different host\r\n // node (e.g. a bundled runtime), so prefer PATH resolution to match prior\r\n // working behavior. The script is precompiled .js, so no native type-stripping\r\n // is involved and it runs fine even though the path sits under node_modules.\r\n const child = spawn(\"node\", [scriptPath, ...finalArgv], {\r\n cwd: context.directory,\r\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\r\n // DevEco Code is a GUI host with no attached console; without windowsHide,\r\n // Windows allocates a new console window for the child \u2014 visible as a\r\n // flash on every a2h_self_test start and on every a2h_self_test_status\r\n // poll, and as a persistent window during a2h_app_feature_verify's long\r\n // sync run. Hide it.\r\n windowsHide: true,\r\n })\r\n\r\n // Pipe the model config to the child's stdin and close it \u2014 the key traverses\r\n // only the OS pipe, never env/argv/disk.\r\n child.stdin.end(\r\n JSON.stringify({ apiKey: model.apiKey, modelName: model.modelName, baseURL: model.baseURL }),\r\n )\r\n\r\n let stdout = \"\"\r\n let stderr = \"\"\r\n let spawnError = \"\"\r\n child.stdout.on(\"data\", (chunk: Buffer) => {\r\n const text = chunk.toString(\"utf8\")\r\n stdout += text\r\n // Surface the last non-empty line as a running title so the host can show\r\n // progress. Generic (not script-specific) \u2014 the agent sees no secrets.\r\n const lines = text.split(/\\r?\\n/)\r\n for (let i = lines.length - 1; i >= 0; i--) {\r\n const line = lines[i].trim()\r\n if (line) {\r\n try {\r\n context.metadata({ title: line.slice(0, 80) })\r\n } catch {\r\n /* host may not implement metadata \u2014 non-essential */\r\n }\r\n break\r\n }\r\n }\r\n })\r\n child.stderr.on(\"data\", (chunk: Buffer) => {\r\n stderr += chunk.toString(\"utf8\")\r\n })\r\n\r\n // Honor abort: kill the child if the host cancels the tool call.\r\n const onAbort = () => {\r\n if (!child.killed) child.kill(\"SIGTERM\")\r\n }\r\n context.abort.addEventListener(\"abort\", onAbort)\r\n\r\n const code: number = await new Promise((resolve) => {\r\n child.on(\"close\", resolve)\r\n child.on(\"error\", (err: Error) => {\r\n spawnError = err.message\r\n resolve(-1)\r\n })\r\n })\r\n context.abort.removeEventListener(\"abort\", onAbort)\r\n\r\n if (code !== 0 && opts.throwOnNonZero !== false) {\r\n if (spawnError) {\r\n throw new Error(`a2h (${label}): failed to spawn node \u2014 ${spawnError}`)\r\n }\r\n const tail = stderr.slice(-2000)\r\n throw new Error(\r\n `a2h (${label}) failed (exit ${code}): ${tail || \"(no stderr)\"}`,\r\n )\r\n }\r\n return { ok: true, exitCode: code, output: cap(stdout) }\r\n}\r\n\r\n/**\r\n * Spawn a script, collect stdout, and return it WITHOUT throwing on non-zero\r\n * exit. Used by tools whose non-zero exit codes are valid states (e.g.\r\n * self-test-runner `--status` exits 2=RUNNING / 3=CRASHED / 4=NOT_STARTED).\r\n * No stdin injection, no model params \u2014 for status/poll tools that need no key.\r\n * Only throws on a true spawn failure (node not found, etc.).\r\n */\r\nexport async function runScriptCollect(\r\n scriptPath: string,\r\n argv: string[],\r\n context: ToolContext,\r\n label = scriptPath,\r\n): Promise<{ ok: true; exitCode: number; output: string }> {\r\n const child = spawn(\"node\", [scriptPath, ...argv], {\r\n cwd: context.directory,\r\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\r\n // Hide the console window \u2014 this runs on every a2h_self_test_status poll,\r\n // so without it the host machine flashes a cmd window repeatedly.\r\n windowsHide: true,\r\n })\r\n let stdout = \"\"\r\n let stderr = \"\"\r\n let spawnError = \"\"\r\n child.stdout.on(\"data\", (chunk: Buffer) => {\r\n stdout += chunk.toString(\"utf8\")\r\n })\r\n child.stderr.on(\"data\", (chunk: Buffer) => {\r\n stderr += chunk.toString(\"utf8\")\r\n })\r\n const onAbort = () => {\r\n if (!child.killed) child.kill(\"SIGTERM\")\r\n }\r\n context.abort.addEventListener(\"abort\", onAbort)\r\n const code: number = await new Promise((resolve) => {\r\n child.on(\"close\", resolve)\r\n child.on(\"error\", (err: Error) => {\r\n spawnError = err.message\r\n resolve(-1)\r\n })\r\n })\r\n context.abort.removeEventListener(\"abort\", onAbort)\r\n if (spawnError) {\r\n throw new Error(`a2h (${label}): failed to spawn node \u2014 ${spawnError}`)\r\n }\r\n return { ok: true, exitCode: code, output: cap(stdout) }\r\n}\r\n", "import path from \"node:path\"\r\nimport type { A2HHostDeps, ToolDefinition } from \"../types\"\r\nimport { PKG_ROOT } from \"../paths\"\r\nimport { runModelScript } from \"./spawn-helpers\"\r\n\r\n/**\r\n * self-test-runner.mjs absolute path \u2014 shared with a2h-self-test-status.ts\r\n * (the status tool polls the very runner this tool starts). Runs under node fine\r\n * inside node_modules (.mjs, no native type-stripping concern).\r\n */\r\nexport const SELF_TEST_RUNNER = path.join(\r\n PKG_ROOT,\r\n \"skills\",\r\n \"hmos-integration-test\",\r\n \"scripts\",\r\n \"self-test-runner.mjs\",\r\n)\r\n\r\n/**\r\n * a2h_self_test \u2014 start an on-device self-test.\r\n *\r\n * Spawns self-test-runner.mjs in RUN mode WITHOUT --timeout. self-test-runner\r\n * installs the HAP, spawns batch_runner detached, prints\r\n * `{status:\"RUNNING\", pid, task_dir, task_subdir}` and exits immediately \u2014 so\r\n * this tool call returns fast (does NOT block for the test duration). The agent\r\n * polls `a2h_self_test_status` until a terminal state, and kills `pid` via Bash\r\n * if it exceeds the elapsed budget.\r\n *\r\n * The model apiKey is resolved from the host and piped to the script's stdin\r\n * (`--model-stdin`, injected by runModelScript); the agent never handles it.\r\n * self-test-runner writes the key into a temp autotest.yaml (0o600) in\r\n * task_subdir \u2014 batch_runner only accepts `--config <yaml file>` (third-party\r\n * constraint). The temp yaml is deleted by `--status` once the run reaches a\r\n * terminal state. The key never enters env/argv (hdc descendants can't see it).\r\n */\r\nexport function makeA2hSelfTestTool(deps: A2HHostDeps): ToolDefinition {\r\n return {\r\n description:\r\n \"\u542F\u52A8 HarmonyOS \u5E94\u7528\u81EA\u6D4B:\u5B89\u88C5 HAP,spawn AutoTest batch_runner(detached) \u540E\u7ACB\u5373\u8FD4\u56DE RUNNING + task_subdir,\u4E0D\u963B\u585E\u7B49\u5F85\u5B8C\u6210\u3002\u7528 a2h_self_test_status \u8F6E\u8BE2\u7EC8\u6001,\u8D85\u65F6\u5219\u7528 Bash kill \u6389\u8FD4\u56DE\u7684 pid\u3002\u6A21\u578B\u914D\u7F6E(apiKey \u7B49)\u7531\u5DE5\u5177\u5185\u90E8\u7ECF stdin \u6CE8\u5165,\u4E0D\u8981\u4F20 apiKey/modelStdin \u5B57\u6BB5\u3002\\n\\n\" +\r\n \"| \u5B57\u6BB5 | \u7C7B\u578B | \u7EA6\u675F | \u8BF4\u660E |\\n\" +\r\n \"|---|---|---|---|\\n\" +\r\n \"| testcases | string | \u5FC5\u586B | testcases.json \u8DEF\u5F84 |\\n\" +\r\n \"| hap | string | \u5FC5\u586B | \u7B7E\u540D\u5305(.hap/.hsp \u6587\u4EF6/\u76EE\u5F55,\u9017\u53F7\u5206\u9694\u591A\u5305) |\\n\" +\r\n \"| bundleName | string | \u5FC5\u586B | \u5E94\u7528\u5305\u540D |\\n\" +\r\n \"| taskDir | string | \u5FC5\u586B | \u4EFB\u52A1\u8F93\u51FA\u76EE\u5F55(\u901A\u5E38 <output>/task) |\\n\" +\r\n \"| outputDir | string | \u53EF\u9009 | \u65E5\u5FD7\u76EE\u5F55(\u901A\u5E38 <output>);\u4E0D\u4F20\u5219\u7528 taskDir |\\n\" +\r\n \"| category | string | \u53EF\u9009 | \u6D4B\u8BD5\u5206\u7C7B\u540D(\u9ED8\u8BA4 self_test,\u901A\u5E38\u4F20 app_name) |\\n\" +\r\n \"| mode | string | \u53EF\u9009 | single(\u9ED8\u8BA4)\u6216 layered(Planner+Executor \u53CC Agent) |\\n\\n\" +\r\n \"\u8FD4\u56DE {ok, exitCode, output},output \u4E3A self-test-runner stdout JSON\u3002\u6B63\u5E38\u4E3A {status:\\\"RUNNING\\\", pid, task_dir, task_subdir}(\u5B58 pid \u4E0E task_subdir \u4F9B\u8F6E\u8BE2\u4E0E\u8D85\u65F6 kill);\u73AF\u5883\u5931\u8D25\u65F6\u4E3A {status:\\\"FAILED\\\", error}(\u5982 @autotest/agent \u81EA\u52A8\u5B89\u88C5\u5931\u8D25\u6216 HAP \u5B89\u88C5/\u7528\u4F8B\u51C6\u5907\u629B\u9519)\u2014\u2014\u6B64\u65F6\u4E0D\u8981\u8F6E\u8BE2,\u76F4\u63A5\u628A error \u62A5\u7ED9\u7528\u6237\u5E76\u505C\u6B62\u3002\u5DE5\u5177\u5BF9 FAILED \u4E0D\u629B\u9519(\u975E\u96F6\u9000\u51FA\u7801 = FAILED),\u9700\u89E3\u6790 output \u6309 status \u5206\u652F\u3002\",\r\n args: {\r\n testcases: { type: \"string\", description: \"testcases.json \u8DEF\u5F84\" },\r\n hap: { type: \"string\", description: \"\u7B7E\u540D\u5305\u8DEF\u5F84(.hap/.hsp \u6587\u4EF6/\u76EE\u5F55,\u9017\u53F7\u5206\u9694)\" },\r\n bundleName: { type: \"string\", description: \"\u5E94\u7528\u5305\u540D\" },\r\n taskDir: { type: \"string\", description: \"\u4EFB\u52A1\u8F93\u51FA\u76EE\u5F55,\u901A\u5E38 <output>/task\" },\r\n outputDir: { type: [\"string\", \"null\"], description: \"\u65E5\u5FD7\u76EE\u5F55,\u53EF\u9009;\u4E0D\u4F20\u5219\u7528 taskDir\" },\r\n category: { type: [\"string\", \"null\"], description: \"\u6D4B\u8BD5\u5206\u7C7B\u540D,\u53EF\u9009;\u9ED8\u8BA4 self_test,\u901A\u5E38\u4F20 app_name\" },\r\n mode: { type: [\"string\", \"null\"], description: \"single \u6216 layered,\u53EF\u9009;\u9ED8\u8BA4 single\" },\r\n },\r\n execute: async (args, context) => {\r\n const argv: string[] = []\r\n const push = (flag: string, v: unknown) => {\r\n if (v == null || v === \"\") return\r\n argv.push(flag, String(v))\r\n }\r\n push(\"--testcases\", args.testcases)\r\n push(\"--hap\", args.hap)\r\n push(\"--bundle-name\", args.bundleName)\r\n push(\"--task-dir\", args.taskDir)\r\n push(\"--output-dir\", args.outputDir)\r\n push(\"--category\", args.category)\r\n push(\"--mode\", args.mode)\r\n // No --timeout: self-test-runner spawns batch_runner detached and returns\r\n // RUNNING immediately. Poll the terminal state via a2h_self_test_status.\r\n // runModelScript appends --model-stdin and pipes the key over stdin.\r\n return runModelScript(deps, context, SELF_TEST_RUNNER, argv, {\r\n label: \"self_test\",\r\n // Non-zero exit = a FAILED start (failJson prints {status:\"FAILED\",error}\r\n // and exits 1). Return it as output -- not throw -- so the agent can read\r\n // the structured error; the description contract tells it to branch on\r\n // status instead of polling. (Normal RUNNING start also exits 0.)\r\n throwOnNonZero: false,\r\n })\r\n },\r\n }\r\n}\r\n", "import type { ToolDefinition } from \"../types\"\r\nimport { runScriptCollect } from \"./spawn-helpers\"\r\nimport { SELF_TEST_RUNNER } from \"./a2h-self-test\"\r\n\r\n/**\r\n * a2h_self_test_status \u2014 poll a self-test run started by a2h_self_test.\r\n *\r\n * Spawns self-test-runner.mjs `--status` mode, which reads summary.json /\r\n * task_results.jsonl / log mtime and returns the current state. Needs NO apiKey\r\n * (status mode never calls the model). Non-zero exit codes are valid states\r\n * (0=COMPLETED, 2=RUNNING, 3=CRASHED, 4=NOT_STARTED), so runScriptCollect does\r\n * not throw on them. On COMPLETED/CRASHED, self-test-runner deletes the temp\r\n * autotest.yaml (the one place the key sat on disk).\r\n *\r\n * Imports `SELF_TEST_RUNNER` from ./a2h-self-test \u2014 the status tool polls the\r\n * very runner the start tool spawns, so the script path is owned there.\r\n */\r\nexport function makeA2hSelfTestStatusTool(): ToolDefinition {\r\n return {\r\n description:\r\n \"\u67E5\u8BE2 a2h_self_test \u542F\u52A8\u7684\u81EA\u6D4B\u6267\u884C\u72B6\u6001(\u8BFB summary.json / task_results.jsonl / \u65E5\u5FD7 mtime)\u3002\u4E0D\u9700\u8981 apiKey\u3002\u53CD\u590D\u8C03\u7528\u76F4\u5230\u7EC8\u6001(COMPLETED/CRASHED/NOT_STARTED),RUNNING \u5219\u95F4\u9694\u540E\u91CD\u8BD5\u3002\\n\\n\" +\r\n \"| \u5B57\u6BB5 | \u7C7B\u578B | \u7EA6\u675F | \u8BF4\u660E |\\n\" +\r\n \"|---|---|---|---|\\n\" +\r\n \"| taskDir | string | \u5FC5\u586B | a2h_self_test \u8FD4\u56DE\u7684 task_dir |\\n\" +\r\n \"| outputDir | string | \u53EF\u9009 | \u65E5\u5FD7\u76EE\u5F55(\u540C a2h_self_test \u7684 outputDir) |\\n\\n\" +\r\n \"\u8FD4\u56DE {ok, exitCode, output},output \u4E3A\u72B6\u6001 JSON:\\n\" +\r\n \"- COMPLETED(exit 0):{status, task_subdir, cases_done, pass_count, fail_count, unknown_count, pass_rate, log_tail}\\n\" +\r\n \"- RUNNING(exit 2):{status, cases_done, last_case, task_subdir, log_tail}\\n\" +\r\n \"- CRASHED(exit 3):\u540C RUNNING \u5F62\u72B6\\n\" +\r\n \"- NOT_STARTED(exit 4):{status:\\\"NOT_STARTED\\\"}\\n\" +\r\n \"COMPLETED/CRASHED \u65F6 runner \u4F1A\u5220\u9664\u4E34\u65F6 autotest.yaml(\u542B key)\u3002\",\r\n args: {\r\n taskDir: { type: \"string\", description: \"a2h_self_test \u8FD4\u56DE\u7684 task_dir\" },\r\n outputDir: { type: [\"string\", \"null\"], description: \"\u65E5\u5FD7\u76EE\u5F55,\u53EF\u9009\" },\r\n },\r\n execute: async (args, context) => {\r\n const argv: string[] = [\"--status\", \"--task-dir\", String(args.taskDir)]\r\n if (args.outputDir != null && args.outputDir !== \"\") {\r\n argv.push(\"--output-dir\", String(args.outputDir))\r\n }\r\n return runScriptCollect(SELF_TEST_RUNNER, argv, context, \"self_test_status\")\r\n },\r\n }\r\n}\r\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["path", "path", "path", "path", "path", "path", "path", "path"]
|
|
7
7
|
}
|