# -*- coding: utf-8 -*- """技能健康检查。 检查项: 1. SKILL.md 成功解析,含 slug / version。 2. ``python scripts/main.py health`` 命令退出码为 0(每个 skill 都约定实现)。 """ from __future__ import annotations import logging import os import subprocess import sys from .config import SkillInfo logger = logging.getLogger("jiangchang_desktop_sdk.testing.healthcheck") class HealthCheckError(RuntimeError): """健康检查失败。""" def skill_healthcheck( skill_info: SkillInfo, *, run_cli: bool = True, timeout_s: float = 15.0, ) -> None: """对一个 skill 跑本地健康检查。失败抛 ``HealthCheckError``。""" if not skill_info.slug: raise HealthCheckError(f"SKILL.md 未定义 openclaw.slug:{skill_info.root}") if not skill_info.version or skill_info.version == "0.0.0": raise HealthCheckError(f"SKILL.md 未定义 version:{skill_info.root}") if not run_cli: return main_py = os.path.join(skill_info.root, "scripts", "main.py") if not os.path.isfile(main_py): logger.warning("skill_healthcheck: %s 不存在,跳过 CLI health 检查", main_py) return cmd = [sys.executable, main_py, "health"] logger.debug("skill_healthcheck: 运行 %s (cwd=%s)", cmd, skill_info.root) try: proc = subprocess.run( cmd, capture_output=True, text=True, timeout=timeout_s, cwd=skill_info.root, encoding="utf-8", errors="replace", ) except subprocess.TimeoutExpired as exc: raise HealthCheckError( f"skill_healthcheck: '{' '.join(cmd)}' 超时 {timeout_s}s" ) from exc if proc.returncode != 0: raise HealthCheckError( f"skill_healthcheck: '{' '.join(cmd)}' 退出码 {proc.returncode}\n" f"stdout: {proc.stdout[:600]}\n" f"stderr: {proc.stderr[:600]}" ) logger.info( "skill_healthcheck: %s v%s OK", skill_info.slug, skill_info.version )