@buaa_smat/hometrans 0.1.14 → 0.1.16
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 +110 -139
- package/agents/build-fixer.md +385 -384
- package/agents/code-reviewer.md +240 -240
- package/agents/logic-coder.md +199 -199
- package/agents/logic-context-builder.md +194 -194
- package/agents/review-fixer.md +405 -405
- package/agents/self-test-fixer.md +296 -296
- package/agents/self-tester.md +396 -393
- package/agents/spec-generator.md +540 -540
- package/dist/cli/config-store.js +150 -17
- package/dist/cli/config.js +10 -3
- package/dist/cli/init.js +391 -233
- package/dist/cli/uninstall.js +12 -7
- package/dist/context/index.js +19 -2
- package/env-requirements.json +177 -181
- package/package.json +1 -1
- package/resource/choose_editor.png +0 -0
- package/resource/migration_process.svg +94 -0
- package/resource/migration_process_transparent.svg +93 -0
- package/resource/set_environment.png +0 -0
- package/resource/set_multimodel.png +0 -0
- package/resource/set_sdk.png +0 -0
- package/skills/hmos-batch-ui-align/SKILL.md +108 -108
- package/skills/hmos-convert-pipeline/SKILL.md +429 -429
- package/skills/hmos-fix-build-errors/SKILL.md +273 -272
- package/skills/hmos-incremental-ui-align/{readme.md → README.md} +234 -237
- package/skills/hmos-incremental-ui-align/SKILL.md +218 -218
- package/skills/hmos-incremental-ui-align/scripts/__pycache__/app_feature_verify.cpython-314.pyc +0 -0
- package/skills/hmos-incremental-ui-align/scripts/app_feature_verify.py +128 -29
- package/skills/hmos-incremental-ui-align/scripts/navigation-capure.md +37 -37
- package/skills/hmos-incremental-ui-align/scripts/page_capture.py +7 -2
- package/skills/hmos-integration-test/{readme.md → README.md} +309 -309
- package/skills/hmos-integration-test/SKILL.md +380 -380
- package/skills/hmos-resources-convert/SKILL.md +623 -623
- package/skills/hmos-spec-generate/SKILL.md +324 -331
- package/tools/test-tools/autotest/README.md +10 -11
- package/tools/test-tools/autotest/self_test_runner.py +40 -12
- package/resource/common_config.png +0 -0
- package/resource/integration_test_config.png +0 -0
- package/resource/set_env.png +0 -0
- package/resource/ui_align_config.png +0 -0
|
@@ -1,22 +1,32 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
# -*- coding: utf-8 -*-
|
|
3
|
+
# /// script
|
|
4
|
+
# requires-python = ">=3.12"
|
|
5
|
+
# dependencies = [
|
|
6
|
+
# "openpyxl",
|
|
7
|
+
# "phone-agent",
|
|
8
|
+
# ]
|
|
9
|
+
# [[tool.uv.index]]
|
|
10
|
+
# url = "https://pypi.tuna.tsinghua.edu.cn/simple"
|
|
11
|
+
# ///
|
|
3
12
|
"""
|
|
4
13
|
应用页面导航工具 —— 使用 PhoneAgent 按指定路径操作设备,导航到目标页面。
|
|
5
14
|
|
|
6
15
|
支持 Android (ADB) 和 HarmonyOS (HDC) 两种设备类型。
|
|
7
16
|
|
|
8
|
-
依赖:
|
|
17
|
+
依赖: openpyxl、phone-agent —— 已在文件顶部 PEP 723 元数据声明,`uv run` 自动安装(无需手动 pip install)
|
|
9
18
|
运行前需确保: 设备已连接 (HDC/ADB)、模型 API 可用
|
|
10
19
|
|
|
11
20
|
用法:
|
|
12
21
|
# 自由 prompt 导航
|
|
13
|
-
|
|
22
|
+
uv run app_feature_verify.py --device adb --app Gallery --package com.example.gallery --prompt "打开相册,进入设置页面"
|
|
14
23
|
|
|
15
24
|
# 按功能路径导航
|
|
16
|
-
|
|
25
|
+
uv run app_feature_verify.py --app 简单图库 --package com.simplemobiletools.gallery.pro --task "L1相册详情L2更多L3筛选媒体文件"
|
|
17
26
|
"""
|
|
18
27
|
|
|
19
28
|
import argparse
|
|
29
|
+
import json
|
|
20
30
|
import re
|
|
21
31
|
import sys
|
|
22
32
|
import os
|
|
@@ -267,25 +277,62 @@ def _apply_phone_agent_patches():
|
|
|
267
277
|
_swipe._patched = True
|
|
268
278
|
_hdc.swipe = _swipe
|
|
269
279
|
|
|
270
|
-
# Patch 2:
|
|
280
|
+
# Patch 2: _parse_response — strip <think>/<answer> tags before splitting by do()/finish()
|
|
281
|
+
import phone_agent.model.client as _client
|
|
282
|
+
if not getattr(_client.ModelClient._parse_response, '_patched', False):
|
|
283
|
+
_orig_parse_response = _client.ModelClient._parse_response
|
|
284
|
+
|
|
285
|
+
def _patched_parse_response(self, content: str):
|
|
286
|
+
# Strip <think>...</think> and <answer>...</answer> wrappers first
|
|
287
|
+
cleaned = re.sub(r'<think>.*?</think>\s*', '', content, flags=re.DOTALL)
|
|
288
|
+
m = re.search(r'<answer>\s*(.*?)\s*</answer>', cleaned, re.DOTALL)
|
|
289
|
+
if m:
|
|
290
|
+
cleaned = m.group(1)
|
|
291
|
+
# Also strip trailing </answer> if only opening was consumed by rule 2
|
|
292
|
+
cleaned = cleaned.replace('</answer>', '').strip()
|
|
293
|
+
if cleaned:
|
|
294
|
+
return _orig_parse_response(self, cleaned)
|
|
295
|
+
return _orig_parse_response(self, content)
|
|
296
|
+
|
|
297
|
+
_patched_parse_response._patched = True
|
|
298
|
+
_client.ModelClient._parse_response = _patched_parse_response
|
|
299
|
+
|
|
300
|
+
# Patch 3: parse_action — strip thinking tags + regex fallback for malformed do()
|
|
271
301
|
import phone_agent.actions.handler as _handler
|
|
272
302
|
if not getattr(_handler.parse_action, '_patched', False):
|
|
273
303
|
_orig_parse = _handler.parse_action
|
|
304
|
+
_THINK_RE = re.compile(r'<think>.*?</think>\s*', re.DOTALL)
|
|
305
|
+
_ANSWER_RE = re.compile(r'<answer>\s*(.*?)\s*</answer>', re.DOTALL)
|
|
306
|
+
|
|
307
|
+
def _strip_llm_tags(text: str) -> str:
|
|
308
|
+
text = _THINK_RE.sub('', text)
|
|
309
|
+
m = _ANSWER_RE.search(text)
|
|
310
|
+
if m:
|
|
311
|
+
text = m.group(1)
|
|
312
|
+
return text.strip()
|
|
313
|
+
|
|
274
314
|
def _parse_action(response):
|
|
315
|
+
cleaned = _strip_llm_tags(response)
|
|
275
316
|
try:
|
|
276
|
-
return _orig_parse(
|
|
277
|
-
except ValueError
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
)
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
317
|
+
return _orig_parse(cleaned)
|
|
318
|
+
except ValueError:
|
|
319
|
+
pass
|
|
320
|
+
# regex fallback for do() with unescaped quotes
|
|
321
|
+
for text in (cleaned, response.strip()):
|
|
322
|
+
m = re.search(
|
|
323
|
+
r'do\s*\(\s*action\s*=\s*"([^"]*)"\s*,\s*message\s*="(.*)"\s*\)\s*$',
|
|
324
|
+
text, re.DOTALL,
|
|
325
|
+
)
|
|
326
|
+
if m:
|
|
327
|
+
msg = m.group(2)
|
|
328
|
+
msg = msg.replace("\\n", "\x0a").replace("\\t", "\x09").replace("\\r", "\x0d")
|
|
329
|
+
msg = msg.replace('\\"', '"')
|
|
330
|
+
return {"_metadata": "do", "action": m.group(1), "message": msg}
|
|
331
|
+
# fallback: extract bare do() from anywhere in the text
|
|
332
|
+
m = re.search(r'do\s*\(\s*action\s*=\s*"([^"]*)".*?\)', cleaned)
|
|
333
|
+
if m:
|
|
334
|
+
return _orig_parse(m.group(0))
|
|
335
|
+
return _orig_parse(response)
|
|
289
336
|
_parse_action._patched = True
|
|
290
337
|
_handler.parse_action = _parse_action
|
|
291
338
|
|
|
@@ -374,6 +421,63 @@ def navigate(
|
|
|
374
421
|
agent.reset()
|
|
375
422
|
|
|
376
423
|
|
|
424
|
+
# ---------------------------------------------------------------------------
|
|
425
|
+
# 模型配置加载:UI 对齐与自测共用同一个多模态模型。
|
|
426
|
+
# 优先级:CLI --api-key > HOMETRANS_MODEL_* 环境变量 > ~/.hometrans/config.json
|
|
427
|
+
# 的 autotest.unified_model(由 `ht init` 写入,两端共用)> 老版本 GLM_API_KEY(兼容)。
|
|
428
|
+
# ---------------------------------------------------------------------------
|
|
429
|
+
|
|
430
|
+
def _load_model_config(cli_api_key: str = ""):
|
|
431
|
+
"""加载统一多模态模型配置(UI 对齐 + 自测共用)。
|
|
432
|
+
|
|
433
|
+
`ht init` 把模型四要素(api_key / name / base_url / provider)写入
|
|
434
|
+
`autotest.unified_model`,并导出为 HOMETRANS_MODEL_* 环境变量。这里优先读
|
|
435
|
+
环境变量,缺字段时回落到 config.json,单个字段可被 CLI/环境变量覆盖;
|
|
436
|
+
都没有时再兼容老版本的 GLM_API_KEY(走智谱 GLM 端点)。
|
|
437
|
+
"""
|
|
438
|
+
from phone_agent.model import ModelConfig
|
|
439
|
+
|
|
440
|
+
# 1) 环境变量(ht init 导出)+ CLI 覆盖
|
|
441
|
+
api_key = (cli_api_key or os.environ.get("HOMETRANS_MODEL_API_KEY", "")).strip()
|
|
442
|
+
base_url = os.environ.get("HOMETRANS_MODEL_BASE_URL", "").strip()
|
|
443
|
+
model_name = os.environ.get("HOMETRANS_MODEL_NAME", "").strip()
|
|
444
|
+
temperature = 0.1
|
|
445
|
+
|
|
446
|
+
# 2) 回落 ~/.hometrans/config.json 的 autotest.unified_model(补齐缺失字段)
|
|
447
|
+
config_path = Path.home() / ".hometrans" / "config.json"
|
|
448
|
+
if config_path.exists():
|
|
449
|
+
try:
|
|
450
|
+
cfg = json.loads(config_path.read_text(encoding="utf-8"))
|
|
451
|
+
um = cfg.get("autotest", {}).get("unified_model", {})
|
|
452
|
+
api_key = api_key or str(um.get("api_key", "")).strip()
|
|
453
|
+
base_url = base_url or str(um.get("base_url", "")).strip()
|
|
454
|
+
model_name = model_name or str(um.get("name", "")).strip()
|
|
455
|
+
if um.get("temperature") is not None:
|
|
456
|
+
temperature = um.get("temperature")
|
|
457
|
+
except (json.JSONDecodeError, KeyError):
|
|
458
|
+
pass
|
|
459
|
+
|
|
460
|
+
if api_key:
|
|
461
|
+
return ModelConfig(
|
|
462
|
+
base_url=base_url or "http://localhost:8000/v1",
|
|
463
|
+
api_key=api_key,
|
|
464
|
+
model_name=model_name,
|
|
465
|
+
temperature=temperature,
|
|
466
|
+
)
|
|
467
|
+
|
|
468
|
+
# 3) 向后兼容老版本:仅设置了 GLM_API_KEY 时,回落到智谱 GLM 端点。
|
|
469
|
+
glm_key = os.environ.get("GLM_API_KEY", "").strip()
|
|
470
|
+
if glm_key:
|
|
471
|
+
return ModelConfig(
|
|
472
|
+
base_url="https://open.bigmodel.cn/api/paas/v4",
|
|
473
|
+
api_key=glm_key,
|
|
474
|
+
model_name="autoglm-phone",
|
|
475
|
+
temperature=0.1,
|
|
476
|
+
)
|
|
477
|
+
|
|
478
|
+
return ModelConfig(temperature=temperature)
|
|
479
|
+
|
|
480
|
+
|
|
377
481
|
# ---------------------------------------------------------------------------
|
|
378
482
|
# CLI 入口
|
|
379
483
|
# ---------------------------------------------------------------------------
|
|
@@ -395,7 +499,7 @@ def main():
|
|
|
395
499
|
parser.add_argument("--limit", type=int, default=None, help="仅导航前 N 条")
|
|
396
500
|
parser.add_argument("--offset", type=int, default=1, help="从第几条开始(1-based)")
|
|
397
501
|
parser.add_argument("--sheet", type=int, default=0, help="Excel 工作表索引")
|
|
398
|
-
parser.add_argument("--api-key", type=str, default="", help="
|
|
502
|
+
parser.add_argument("--api-key", type=str, default="", help="API Key(可选,不传则读取 HOMETRANS_MODEL_* 环境变量 / ~/.hometrans/config.json)")
|
|
399
503
|
parser.add_argument("--max-steps", type=int, default=30, help="Agent 最大步数")
|
|
400
504
|
parser.add_argument("--app-hints", type=str, default=None, help="应用上下文提示")
|
|
401
505
|
parser.add_argument("-q", "--quiet", action="store_true", help="减少输出")
|
|
@@ -404,19 +508,14 @@ def main():
|
|
|
404
508
|
if not args.prompt and not args.task and not args.feature:
|
|
405
509
|
parser.error("必须指定 --feature、--task 或 --prompt 之一")
|
|
406
510
|
|
|
407
|
-
api_key = args.api_key or os.environ.get("GLM_API_KEY", "")
|
|
408
|
-
if not api_key and not args.quiet:
|
|
409
|
-
print("提示: 未设置 API Key,请通过 --api-key 或环境变量 GLM_API_KEY 配置")
|
|
410
|
-
print("继续运行可能因鉴权失败而报错\n")
|
|
411
|
-
|
|
412
511
|
from phone_agent.model import ModelConfig
|
|
413
512
|
|
|
414
|
-
model_config =
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
513
|
+
model_config = _load_model_config(args.api_key)
|
|
514
|
+
if not model_config.api_key or model_config.api_key == "EMPTY":
|
|
515
|
+
if not args.quiet:
|
|
516
|
+
print("提示: 未找到可用的模型配置。请运行 `ht init` 配置统一模型(UI 对齐 + 自测共用),")
|
|
517
|
+
print("它会写入 ~/.hometrans/config.json 的 autotest.unified_model 并导出 HOMETRANS_MODEL_* 环境变量;")
|
|
518
|
+
print("或通过 --api-key 传入 API Key。\n")
|
|
420
519
|
|
|
421
520
|
device_label = "ADB (Android)" if args.device == "adb" else "HDC (HarmonyOS)"
|
|
422
521
|
print(f"设备: {device_label}, 应用: {args.app} ({args.package})")
|
|
@@ -1,37 +1,37 @@
|
|
|
1
|
-
Create a timestamped output directory under `capture_output_dir`:
|
|
2
|
-
```
|
|
3
|
-
task_dir = capture_output_dir / "task_{timestamp}"
|
|
4
|
-
android_capture_dir = task_dir / "android_page_{i}_{name}"
|
|
5
|
-
hmos_capture_dir = task_dir / "hmos_page_{i}_{name}"
|
|
6
|
-
```
|
|
7
|
-
Each paired hmos-android page-pair should have the same `i`
|
|
8
|
-
|
|
9
|
-
## Navigation prompt rules
|
|
10
|
-
|
|
11
|
-
Each page's `android_nav_path` / `hmos_nav_path` is used as the `--prompt` for `app_feature_verify.py`.
|
|
12
|
-
The `--prompt` mode auto-prepends "打开{app_name}," so do not include "打开{app_name}" in the prompt.
|
|
13
|
-
|
|
14
|
-
## Navigate to Android page and Capture (If navigated Success)
|
|
15
|
-
```bash
|
|
16
|
-
PYTHONIOENCODING=utf-8
|
|
17
|
-
--device adb \
|
|
18
|
-
--app "{android.app_name}" \
|
|
19
|
-
--package "{android.package}" \
|
|
20
|
-
--prompt "{pages[i].android_nav_path}" \
|
|
21
|
-
|
|
22
|
-
--max-steps 15
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
**Capture Android page** (if navigation succeeded)
|
|
26
|
-
```bash
|
|
27
|
-
PYTHONIOENCODING=utf-8
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
## Navigate & Capture HarmonyOS Pages
|
|
31
|
-
|
|
32
|
-
Same as Andorid but with `--device hdc` and HarmonyOS config values. Directory pattern:
|
|
33
|
-
```
|
|
34
|
-
hmos_capture_dir = task_dir / "hmos_page_{i}_{name}"
|
|
35
|
-
```
|
|
36
|
-
For pages missed in harmony OS app, navigation will certainly fail — `hmos_capture_dir` will be empty, which is expected. Skip navigation for these pages and leave the directory empty.
|
|
37
|
-
|
|
1
|
+
Create a timestamped output directory under `capture_output_dir`:
|
|
2
|
+
```
|
|
3
|
+
task_dir = capture_output_dir / "task_{timestamp}"
|
|
4
|
+
android_capture_dir = task_dir / "android_page_{i}_{name}"
|
|
5
|
+
hmos_capture_dir = task_dir / "hmos_page_{i}_{name}"
|
|
6
|
+
```
|
|
7
|
+
Each paired hmos-android page-pair should have the same `i`
|
|
8
|
+
|
|
9
|
+
## Navigation prompt rules
|
|
10
|
+
|
|
11
|
+
Each page's `android_nav_path` / `hmos_nav_path` is used as the `--prompt` for `app_feature_verify.py`.
|
|
12
|
+
The `--prompt` mode auto-prepends "打开{app_name}," so do not include "打开{app_name}" in the prompt.
|
|
13
|
+
|
|
14
|
+
## Navigate to Android page and Capture (If navigated Success)
|
|
15
|
+
```bash
|
|
16
|
+
PYTHONIOENCODING=utf-8 uv run ./app_feature_verify.py \
|
|
17
|
+
--device adb \
|
|
18
|
+
--app "{android.app_name}" \
|
|
19
|
+
--package "{android.package}" \
|
|
20
|
+
--prompt "{pages[i].android_nav_path}" \
|
|
21
|
+
\
|
|
22
|
+
--max-steps 15
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
**Capture Android page** (if navigation succeeded)
|
|
26
|
+
```bash
|
|
27
|
+
PYTHONIOENCODING=utf-8 uv run ./page_capture.py --device adb -o "{android_capture_dir}"
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Navigate & Capture HarmonyOS Pages
|
|
31
|
+
|
|
32
|
+
Same as Andorid but with `--device hdc` and HarmonyOS config values. Directory pattern:
|
|
33
|
+
```
|
|
34
|
+
hmos_capture_dir = task_dir / "hmos_page_{i}_{name}"
|
|
35
|
+
```
|
|
36
|
+
For pages missed in harmony OS app, navigation will certainly fail — `hmos_capture_dir` will be empty, which is expected. Skip navigation for these pages and leave the directory empty.
|
|
37
|
+
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
# -*- coding: utf-8 -*-
|
|
3
|
+
# /// script
|
|
4
|
+
# requires-python = ">=3.10"
|
|
5
|
+
# [[tool.uv.index]]
|
|
6
|
+
# url = "https://pypi.tuna.tsinghua.edu.cn/simple"
|
|
7
|
+
# ///
|
|
3
8
|
"""
|
|
4
9
|
统一页面采集工具 —— 单次采集当前设备页面的 View Tree (XML)、截图,支持滚动采集。
|
|
5
10
|
|
|
@@ -7,10 +12,10 @@
|
|
|
7
12
|
|
|
8
13
|
用法:
|
|
9
14
|
# HarmonyOS
|
|
10
|
-
|
|
15
|
+
uv run page_capture.py --device hdc -o ./output
|
|
11
16
|
|
|
12
17
|
# Android
|
|
13
|
-
|
|
18
|
+
uv run page_capture.py --device adb -o ./output --serial emulator-5554
|
|
14
19
|
"""
|
|
15
20
|
|
|
16
21
|
import argparse
|