Doctor 与扩展层 深度解析
Doctor 与扩展层 这页聚焦 权限阶梯、CLI 分流与 agent helper 注入。关键证据包括 src/phone_harness/helpers.py:45、src/phone_harness/mirror.py:91、src/phone_harness/ocr.py:19、src/phone_harness/admin.py:12,所有判断都回到真实源码行号。
导入链与契约
doctor 先检查运行时依赖而不是直接操作手机。src/phone_harness/admin.py:12、src/phone_harness/admin.py:16、src/phone_harness/admin.py:24、src/phone_harness/admin.py:30 把 PyObjC、Accessibility 和 Screen Recording 缺失变成用户可修复的诊断项。
12def run_doctor(): 13 print("phone-harness doctor\n") 14 ok = True 15 16 try: 17 import Quartz, Vision, AppKit # noqa: F401 18 ok &= _check("pyobjc frameworks (Quartz, Vision, AppKit)", True) 19 except ImportError as e: 20 _check("pyobjc frameworks", False, 21 f"pip install pyobjc-framework-Quartz pyobjc-framework-Vision ({e})") 22 return 1
核心实现,逐段拆
▸CLI 命令面
run.py 把 doctor、skill 和 stdin 执行分开。src/phone_harness/run.py:16、src/phone_harness/run.py:21、src/phone_harness/run.py:24、src/phone_harness/run.py:30、src/phone_harness/run.py:36 说明 agent 使用时只是喂一段 Python,helper 会被预注入。
16def main(): 17 args = sys.argv[1:] 18 if args and args[0] in {"-h", "--help"}: 19 print(USAGE) 20 return 21 if args and args[0] in {"--doctor", "doctor"}: 22 from .admin import run_doctor 23 sys.exit(run_doctor()) 24 if args and args[0] == "skill": 25 repo_root = Path(__file__).resolve().parent.parent.parent 26 print((repo_root / "SKILL.md").read_text(encoding="utf-8"), end="") 27 return 28 if args or sys.stdin.isatty(): 29 sys.exit(USAGE) 30 code = sys.stdin.read() 31 if not code.strip(): 32 sys.exit(USAGE) 33 from . import helpers 34 g = {k: v for k, v in vars(helpers).items() if not k.startswith("_")} 35 g["__name__"] = "__main__" 36 exec(code, g)
▸可编辑 helper
agent-workspace 不是核心库的一部分,却会在 import helpers 时自动加载。src/phone_harness/helpers.py:332、src/phone_harness/helpers.py:336、src/phone_harness/helpers.py:340、agent-workspace/agent_helpers.py:9、agent-workspace/agent_helpers.py:21 这个设计让任务经验可沉淀,又不污染传输层。
332def _load_agent_helpers(): 333 p = AGENT_WORKSPACE / "agent_helpers.py" 334 if not p.exists(): 335 return 336 spec = importlib.util.spec_from_file_location("phone_harness_agent_helpers", p) 337 if not spec or not spec.loader: 338 return 339 module = importlib.util.module_from_spec(spec) 340 spec.loader.exec_module(module) 341 for name, value in vars(module).items(): 342 if not name.startswith("_"): 343 globals()[name] = value 344 345 346_load_agent_helpers()
关键流程
这个流程的关键是每一步都有可观察证据:src/phone_harness/mirror.py:84、src/phone_harness/ocr.py:48、src/phone_harness/helpers.py:73、src/phone_harness/helpers.py:316、src/phone_harness/admin.py:65 分别覆盖截图、OCR 坐标、screen_info、稳定等待和 doctor 结果。它不像浏览器自动化那样有 DOM,所以截图就是最终断言。
边界 / 失败模式
src/phone_harness/helpers.py:67遇到 blocked interstitial 时要求用户处理,防止 agent 越权。src/phone_harness/mirror.py:251对无法输入字符抛错,避免用户以为文本已发送。src/phone_harness/helpers.py:280需要连续不移动才结束列表,避免 lazy-load 误判。src/phone_harness/admin.py:55用截图字节数识别空 capture,提示重启终端让录屏权限生效。