"""核对已安装资源、显式配置和本地宿主;不会发起模型调用。""" from __future__ import annotations import argparse import json import os import subprocess import tempfile from pathlib import Path from muse.共享.错误 import Muse错误, 环境缺失错误, 资源缺失错误, 配置错误 from muse.启动 import 构建 from muse.资源加载 import 核对资源, 读取能力目录 from muse.配置 import 读取配置 def 安全失败(动作: str, 错误: Exception) -> dict: """输出固定原因,不展示异常文本、命令、stdout/stderr 或配置上下文。""" 可重试 = False 详情 = {} if isinstance(错误, subprocess.TimeoutExpired): 类别, 说明, 可重试 = "TIMEOUT", "所选检查超过时限", True elif isinstance(错误, subprocess.CalledProcessError): 类别, 说明 = "PROCESS_FAILED", "所选检查进程非零退出" if isinstance(错误.returncode, int): 详情 = {"exit_code": 错误.returncode} elif isinstance(错误, PermissionError): 类别, 说明 = "PERMISSION_DENIED", "所选检查没有所需文件或执行权限" elif isinstance(错误, FileNotFoundError): 类别, 说明 = "NOT_FOUND", "所选检查需要的文件或可执行程序不存在" elif isinstance(错误, 资源缺失错误): 类别, 说明 = "RESOURCE_INVALID", "安装资源缺失或完整性核对失败" elif isinstance(错误, 环境缺失错误): 类别, 说明 = "ENVIRONMENT_MISSING", "所选检查需要的环境未就绪" elif isinstance(错误, 配置错误): 类别, 说明 = "CONFIG_INVALID", "所选检查的显式配置或版本要求不匹配" elif isinstance(错误, Muse错误) and 错误.错误码 == "MUSE_DB_CONNECTION": 类别, 说明, 可重试 = "CONNECTION_FAILED", "数据库连接或会话初始化失败", True elif isinstance(错误, Muse错误): 类别, 说明 = "REJECTED", "所选检查被运行合同拒绝" elif isinstance(错误, (ValueError, KeyError, TypeError)): 类别, 说明 = "INVALID_DATA", "所选检查的数据格式、字段或版本无效" elif isinstance(错误, OSError): 类别, 说明 = "IO_FAILED", "所选检查的文件、执行或连接操作失败" else: 类别, 说明 = "INTERNAL_ERROR", "所选检查遇到未预期错误" return { "code": f"PREFLIGHT_{动作}_{类别}", "action": 动作, "message": 说明, "retryable": 可重试, "details": 详情, } class 预检失败(Exception): def __init__(self, 报告: dict) -> None: self.报告 = 报告 super().__init__(报告["message"]) def 检查Pi(node: Path, cli: Path, 预期版本: str) -> dict: 动作 = "HOST_NODE" try: if not node.is_file(): raise FileNotFoundError 结果 = subprocess.run( [str(node), "--version"], capture_output=True, text=True, timeout=10, check=True ) node_version = 结果.stdout.strip().removeprefix("v") parts = tuple(int(v) for v in node_version.split(".")) if len(parts) != 3 or any(v < 0 for v in parts): raise ValueError if parts < (22, 19, 0): raise 配置错误("Node版本未达到已声明的宿主要求") 动作 = "HOST_PI" if not cli.is_file(): raise FileNotFoundError with tempfile.TemporaryDirectory(prefix="muse-pi-preflight-") as 临时: 环境 = { **os.environ, "PI_CODING_AGENT_DIR": 临时, "PI_OFFLINE": "1", "PI_TELEMETRY": "0", } 结果 = subprocess.run( [str(node), str(cli), "--version"], cwd=临时, env=环境, capture_output=True, text=True, timeout=15, check=True, ) 实际 = 结果.stdout.strip() if 实际 != 预期版本: raise 配置错误("Pi实际版本与显式要求不符") return {"node_version": node_version, "pi_version": 实际, "state": "version_verified"} except Exception as 错误: raise 预检失败(安全失败(动作, 错误)) from None def 主() -> int: 解析 = argparse.ArgumentParser(description="检查固定资源和明确选择的运行依赖,不调用模型") 解析.add_argument("--配置", type=Path) 解析.add_argument("--数据库", action="store_true", help="明确执行所给配置的数据库连接检查") 解析.add_argument("--node", type=Path) 解析.add_argument("--pi", type=Path) 解析.add_argument("--pi-version") 参数 = 解析.parse_args() if 参数.数据库 and 参数.配置 is None: 解析.error("数据库检查需要明确配置") if any((参数.node, 参数.pi, 参数.pi_version)) and not all( (参数.node, 参数.pi, 参数.pi_version) ): 解析.error("Pi检查必须同时给出node、pi和pi-version") 动作 = "RESOURCE" try: 清单 = 核对资源() 能力 = 读取能力目录() 报告 = { "resource_build_id": 清单["构建身份"], "resource_label": 清单["发布身份"], "package_version": 清单["代码版本"], "resources_verified": len(清单["资源"]), "roles": sorted(能力["role"]), "operations": sorted(能力["operation"]), "database": "not_checked", "host": {"state": "not_checked"}, "model_calls": 0, } if 参数.配置: 动作 = "CONFIG" 配置 = 读取配置(参数.配置) if 配置.资源发布身份 != 清单["构建身份"]: raise 配置错误("配置必须固定当前资源构建身份;先无配置运行预检取得身份") if 参数.数据库: 动作 = "DATABASE" # 常驻连接池只在装配生命周期内打开;不进生命周期会以「连接池未就绪」必然失败。 装配 = 构建(配置) with 装配.生命周期(): 装配.要求数据库().检查连接() 报告["database"] = "connection_verified" if 参数.node: 动作 = "HOST" 报告["host"] = 检查Pi(参数.node.resolve(), 参数.pi.resolve(), 参数.pi_version) print(json.dumps(报告, ensure_ascii=False, indent=2)) return 0 except 预检失败 as 错误: print(json.dumps(错误.报告, ensure_ascii=False)) except Exception as 错误: print(json.dumps(安全失败(动作, 错误), ensure_ascii=False)) return 1 if __name__ == "__main__": raise SystemExit(主())