Agent Zero _orchestrator 终端 Agent 适配器契约:TerminalAgentAdapter 的设计、认证检测与验证实践

发布时间:2026/9/14 17:07:44
Agent Zero _orchestrator 终端 Agent 适配器契约:TerminalAgentAdapter 的设计、认证检测与验证实践 Agent Zero _orchestrator 终端 Agent 适配器契约TerminalAgentAdapter 的设计、认证检测与验证实践【免费下载链接】agent-zeroAgent Zero AI framework项目地址: https://gitcode.com/GitHub_Trending/ag/agent-zero本文以 Agent Zero 仓库中 plugins/_orchestrator/helpers/adapters/AGENTS.md 这一适配层开发约定文档为核心结合 base.py、registry.py、api/status.py 与 test_status_adapters.py 等仓库文件完整讲解终端编码 Agent 状态适配器的职责边界、TerminalAgentAdapter契约、各适配器的认证检测策略以及验证方式。读完本文你将能够准确理解 _orchestrator 插件只报状态、不执行命令的架构约束并具备按同一契约为仓库新增状态适配器的完整方法。适配器层的定位与所有权plugins/_orchestrator/helpers/adapters/AGENTS.md 开宗明义地说明了该目录的两个目的把每一个受支持的外部终端 Agent 表示为插件 UI 与 API 可用的状态/认证元数据status/auth metadata保证每个适配器都兼容共享的TerminalAgentAdapter契约。从所有权Ownership角度看该目录负责base.py基类以及 A0 Headless、Codex CLI、Claude Code、Cursor CLI、Gemini CLI、Grok Build、Hermes Agent、OpenCode 和未来的状态适配器的模块实现仅在适配器能精确定位凭据存储时才提供凭据路径检测credential-path detection与安全的断开行为safe disconnect。这一只读状态定位与整个 _orchestrator 插件的架构决策一致插件 README 明确指出插件刻意不提供terminal_agent工具也没有设置界面的安装按钮真正的任务委派由orchestratorskill 通过用户主机上的 A0 CLI 桥接或容器 shell 完成而适配器层只回答这个 CLI 装没装、认证没认证、凭据在哪里这三个问题。TerminalAgentAdapter 基类契约契约的实体在 plugins/_orchestrator/helpers/adapters/base.py 中定义。TerminalAgentAdapter是一个 ABC抽象基类规定每个子类必须实现auth_status()并提供一组带默认行为的辅助方法class TerminalAgentAdapter(ABC): Contract every terminal coding agent status adapter must implement. id: str title: str binary: str install_hint: str description: str def data_dir(self) - Path: Plugin-owned private directory for this adapter (auth, state). path Path( files.get_abs_path(usr, plugins, _orchestrator, data, self.id) ) path.mkdir(parentsTrue, exist_okTrue) return path def resolve_binary(self, config: dict[str, Any] | None None) - str: cfg config if isinstance(config, dict) else {} configured str(cfg.get(binary) or ).strip() return configured or self.binary def is_installed(self, config: dict[str, Any] | None None) - bool: binary self.resolve_binary(config) if not binary: return False if os.path.isabs(binary): return Path(binary).is_file() and os.access(binary, os.X_OK) return shutil.which(binary) is not None abstractmethod def auth_status(self, config: dict[str, Any] | None None) - dict[str, Any]: Return {connected: bool, mode: plugin|external|, auth_path: str}. def supports_device_login(self) - bool: return False def can_disconnect(self, config: dict[str, Any] | None None) - bool: return False def disconnect(self, config: dict[str, Any] | None None) - dict[str, Any]: raise NotImplementedError(f{self.id} does not support disconnect.)AGENTS.md 的 Local Contracts 一节对上述契约逐条给出了硬性规则这里逐条对照源码解读子类必须定义的六个成员子类必须定义id、title、binary、install_hint、description与auth_status()。以 codex.py 为例class CodexAdapter(TerminalAgentAdapter): id codex title OpenAI Codex CLI binary codex install_hint npm install -g openai/codex description OpenAI Codex CLI for autonomous coding tasks in a workdir.其中id是注册表的主键小写、唯一title和description直接渲染到设置界面binary是默认的可执行文件名。auth_status() 的返回字典auth_status()必须返回至少包含connected、mode、auth_path三个键的 dict可选的error字段必须是显示安全的display-safe即不包含凭据内容、可直接展示给用户的错误描述。基类 docstring 将mode的取值标注为plugin|external|plugin凭据位于插件私有目录由插件自己管理典型是 Codex 的 device-login 流程写入的auth.jsonexternal凭据来自外部 CLI 自身的认证体系~/.codex/auth.json、~/.claude/.credentials.json、~/.gemini/gemini-credentials.json等空串未检测到任何认证。此外 claude.py 与 cursor.py 中还使用了env模式当检测到环境变量认证如ANTHROPIC_API_KEY、CURSOR_API_KEY时返回mode: env并把auth_path设为环境变量名而非文件路径。install_hint 只是提示不是行为文档明确要求install_hint是信息性的informational绝不能变成可执行的 API 行为。这一点由测试固化——test_status_adapters.py 中的test_adapters_are_status_only遍历所有已注册适配器断言没有任何适配器持有install_command、build_command、parse_session_id或format_output属性def test_adapters_are_status_only(): for adapter in list_adapters(): assert not hasattr(adapter, install_command) assert not hasattr(adapter, build_command) assert not hasattr(adapter, parse_session_id) assert not hasattr(adapter, format_output)AGENTS.md 因此给出同样的禁止条款不要添加build_command、install_command、parse_session_id或format_output这类命令执行钩子。同一测试文件中还有test_tool_runner_files_are_removed断言插件根目录下不存在tools/terminal_agent.py、helpers/runner.py等旧执行入口——适配器层不执行命令的边界是有回归测试守护的。resolve_binary 与 is_installed 的语义resolve_binary()应尊重配置中的绝对路径回退到适配器的默认binary。基类实现即配置binary优先否则用类属性test_status_adapters.py 的test_configured_absolute_binary_is_installed用临时目录中的可执行文件验证了这条链路。A0 适配器还做了特化覆盖当a0不在 PATH 中时回退到容器内捆绑的/opt/venv/bin/a0见下文 A0 小节。is_installed()只做可执行性检查绝对路径走Path.is_file() os.access(X_OK)相对名走shutil.which不得安装、变更或提示用户。data_dir() 的用途边界data_dir()仅用于插件自有的私有状态如 Codex device-login 的认证文件落在usr/plugins/_orchestrator/data/id/下。文档特别强调不要把用户输入的 provider key 写进源码或宽泛的配置文件。这也解释了为什么 Codex 的_write_auth()采用临时文件 os.chmod(tmp, 0o600) 原子replace的写法把凭据严格限制在插件私有目录内。注册表与状态 API 的消费链路适配器不直接服务 UI而是经 plugins/_orchestrator/helpers/registry.py 汇总消费。注册表以字典推导式实例化全部 8 个适配器id作为键_ADAPTERS: dict[str, TerminalAgentAdapter] { adapter.id: adapter for adapter in ( AgentZeroAdapter(), CodexAdapter(), ClaudeCodeAdapter(), CursorCliAdapter(), GeminiCliAdapter(), GrokBuildAdapter(), HermesAgentAdapter(), OpenCodeAdapter(), ) }测试test_registry_order_puts_a0_first断言了注册顺序a0, codex, claude, cursor, gemini, grok, hermes, opencode——A0 排首位与 README 中a0 是设置例外的定位呼应。get_adapter()对未知 id 抛出带可用列表的ValueErroradapter_config()则从插件配置中取出每个 Agent 的子配置对应 default_config.yaml 的顶层键。设置界面背后的状态接口在 plugins/_orchestrator/api/status.py。它对每个适配器聚合出前端所需的完整字段并对auth_status()的异常做兜底异常时降级为connected: False 显示安全的error文本agents.append({ id: adapter.id, title: adapter.title, description: adapter.description, binary: adapter.resolve_binary(cfg), installed: adapter.is_installed(cfg), install_hint: adapter.install_hint, supports_device_login: adapter.supports_device_login(), can_disconnect: adapter.can_disconnect(cfg), auth: auth, })注意这里的调用顺序正是契约的体现resolve_binary/is_installed只读auth_status只检测。每个适配器的子配置来自adapter_config(adapter.id, plugin_config)即插件配置字典中以 Agent id 为键的一段默认值定义在 default_config.yaml例如a0: binary: a0 # falls back to /opt/venv/bin/a0 in Agent Zero Docker host: # empty AGENT_ZERO_HOST env, else local instance (http://localhost:80) claude: binary: claude permission_mode: bypassPermissions allowed_tools: Bash,Read,Edit cursor: binary: agent output_format: text force: true各适配器的检测策略逐条对照 Work GuidanceAGENTS.md 的 Work Guidance 一节按 Agent 给出了具体规则。下面逐条结合源码说明每条规则是如何落地的。A0 Headless主机解析与容器内回退规则原文主机解析顺序为配置a0.host然后AGENT_ZERO_HOST环境变量然后 Agent Zero 容器内的http://localhost:80当普通a0不可用时回退到 Docker 捆绑二进制/opt/venv/bin/a0连接成功的含义是主机 socket 可达登录/目标选择由 skill 处理。a0.py 的实现对应该规则DEFAULT_HOST http://localhost:80 DEFAULT_DOCKER_A0_BINARY /opt/venv/bin/a0 def resolve_host(self, configNone) - str: host str((config or {}).get(host) or ).strip() if host: return host return os.environ.get(AGENT_ZERO_HOST, ).strip() or DEFAULT_HOST def auth_status(self, configNone) - dict[str, Any]: host self.resolve_host(config) if _probe(host): return {connected: True, mode: external, auth_path: host} return {connected: False, mode: , auth_path: host}_probe()用 2 秒超时的socket.create_connection判断 TCP 可达性——这正对应文档所说状态意味着主机 socket 可达它不触碰任何凭据文件因此把auth_path填为主机地址本身。而resolve_binary的覆写实现了 Docker 回退仅当解析结果仍是默认名a0且shutil.which查不到时才检查/opt/venv/bin/a0是否存在。Codex CLI插件凭据优先断开行为按来源区分规则优先检测插件自有的data/codex/auth.json再看外部CODEX_HOME或~/.codex/auth.jsondevice-code OAuth 与_oauth参考实现保持兼容外部断开可调用codex logout插件自有断开只删除插件认证文件。codex.py 的检测链def auth_status(self, configNone) - dict[str, Any]: plugin_path self._plugin_auth_path() # data/codex/auth.json if _has_chatgpt_tokens(plugin_path): return {connected: True, mode: plugin, auth_path: str(plugin_path)} external_path self._external_auth_path() # $CODEX_HOME/auth.json 或 ~/.codex/auth.json if _has_chatgpt_tokens(external_path): return {connected: True, mode: external, auth_path: str(external_path)} return {connected: False, mode: , auth_path: }_has_chatgpt_tokens()只在文件内同时存在非空access_token与refresh_token时返回 True——这是检测凭据存在而非读取凭据内容的典型写法。断开行为严格区分来源mode plugin时path.unlink()删除插件文件mode external时才以子进程调用codex logout并透传其 stderr 作为错误。device-login 流程start_device_login/poll_device_login使用与官方 CLI 相同的 OAuth 客户端常量文档中与plugins/_oauth参考实现保持兼容的要求在此体现为注释指向plugins/_oauth成功换取的 token 写入插件私有目录并设为0o600权限。Claude Code环境变量优先凭据文件只查不读规则ANTHROPIC_API_KEY视为环境认证检测 CLI 凭据文件但绝不读取或返回凭据内容不要把首跑 TUI 建模为状态 API 流程。claude.py 的顺序是先查ANTHROPIC_API_KEY命中即mode: env再看$CLAUDE_CONFIG_DIR默认~/.claude下的.credentials.json仅用is_file() and stat().st_size 0判断存在性不解析内容读取过程中如遇OSError则降级为带error字段的未连接结果。断开方面can_disconnect()仅在没有环境变量 凭据文件存在时为 Truedisconnect()对环境变量已设置的情况返回removed: False并提示避免误删仍有效的环境认证。不建模首跑 TUI的约定在 README 中有对应的人机流程说明未认证时应引导使用claude auth login的显式子命令--claudeai/--console/--sso而不是裸启动claude陷入主题/供应商菜单。Cursor CLI已知文件探测不返回密钥内容规则CURSOR_API_KEY视为环境认证探测~/.cursor/下的已知文件但不返回密钥内容不把交互式终端 UI 建模为状态 API 流程。cursor.py 的auth_status()先按(CURSOR_API_KEY, API_KEY_CURSOR)顺序查环境变量后者是 Agent Zero 密钥管理的映射名有专门测试覆盖再遍历$CURSOR_HOME默认~/.cursor下的候选文件元组_AUTH_FILES ( auth.json, credentials.json, token.json, agent/auth.json, agent/credentials.json, agent/token.json, )判定函数_file_has_secret()读取文件后做递归 JSON 遍历只要发现_SECRET_KEYSaccess_token、api_key、id_token、refresh_token、token对应的非空字符串值即返回 TrueJSON 解析失败时退化为对文本做键名模式匹配。返回值中永远只有connected布尔语义密钥本体不出现在任何返回路径上。Grok BuildTOML 中的 env_key 需要变量真实存在才算数规则XAI_API_KEY视为环境认证检测~/.grok/config.toml、~/.grok/auth.json与~/.grok/auth/目录但不返回密钥内容不建模全屏 TUI。grok.py 在通用 JSON/文本密钥检测之外对 TOML 做了专门处理其中最有意思的语义是env_keydef _toml_has_secret(text: str) - bool: for line in text.splitlines(): raw line.strip() if not raw or raw.startswith(#) or not in raw: continue key, value raw.split(, 1) key key.strip() value value.strip().strip(\) if key api_key and value: return True if key env_key and value and os.environ.get(value): return True return False即config.toml里写env_key GROK_TEST_KEY并不直接算已认证还必须该环境变量在运行时确有取值。test_status_adapters.py 的test_grok_env_key_requires_present_environment_value正是针对这一边界编写先断言未设置时返回 False再设置变量后断言 True。测试test_grok_detects_agent_zero_xai_env_key则验证了API_KEY_XAI这个 Agent Zero 侧映射名同样被识别。Gemini CLI多来源凭据的完整检测链规则检测GEMINI_API_KEY、GOOGLE_API_KEY、服务账号/ADC 文件以及当前或旧版 Gemini 凭据文件均不返回密钥内容不建模交互式登录 TUI。gemini.py 的检测顺序完整地体现了文档要求环境变量GEMINI_API_KEY/GOOGLE_API_KEY→mode: envGOOGLE_APPLICATION_CREDENTIALS指向的服务账号文件非空即算~/.gemini/可被GEMINI_CLI_HOME覆盖下的当前文件gemini-credentials.json与旧版oauth_creds.json~/.gemini/.env文件中是否含上述 key_env_file_has_key逐行解析跳过注释与空行gcloud 的 ADC 文件~/.config/gcloud/application_default_credentials.json可被CLOUDSDK_CONFIG重定位。测试test_gemini_detects_supported_auth_sources覆盖了环境变量路径与.env文件两条路径。Hermes Agent 与 OpenCode环境变量 认证文件的双通道规则检测已知的 provider 环境变量与已知认证文件密钥检测只回答是/否不返回密钥值。hermes.py 维护了一个 30 余项的_AUTH_ENV_VARS集合涵盖OPENAI_API_KEY、ANTHROPIC_API_KEY、GEMINI_API_KEY、OPENROUTER_API_KEY、KIMI_API_KEY等多供应商变量按字母序遍历后依次检测$HERMES_HOME/.env与~/.hermes/auth.json。其_contains_secret()对字符串值还加了len(item.strip()) 3的最小长度门槛降低对占位值的误报。opencode.py 则较小_ENV_KEYS仅 10 个常用变量认证文件位于 XDG 数据目录$XDG_DATA_HOME/opencode/auth.json默认~/.local/share/opencode/auth.json与 README 支持列表 中 OpenCode 一行的说明一致。两个适配器的共同点是auth_status()的每一次命中都只产生connected/mode/auth_path三元组任何密钥值都不进入返回结构。安全与可测试性设计要点小结把 AGENTS.md 的规则与源码放在一起看可以提炼出适配器层的三条不变式它们也是新增适配器时必须满足的验收标准只检测不执行状态接口中不存在任何命令构建/安装钩子is_installed与auth_status全部是只读探测文件存在性、socket 可达性、环境变量取值。test_status_adapters.py 用hasattr断言和旧 runner 文件已删除断言把这条不变式固化为回归测试。密钥只判存在性所有_file_has_secret/_contains_secret/_has_chatgpt_tokens类函数返回布尔error字段只承载OSError描述或 OAuth 错误文本不携带凭据。断开行为与凭据来源一一对应插件自有的凭据mode: plugin由插件直接删文件外部凭据mode: external只有在该适配器能精确定位凭据存储或官方 logout 命令时才提供断开这正是文档中safe disconnect only when the adapter can identify the exact credential store的落地。验证方式AGENTS.md 的 Verification 一节给出两条验证命令原文使用了具体容器 ID 的docker exec形式容器哈希因环境而异可按需替换为你的 Agent Zero 容器运行适配器测试套件docker exec 容器ID bash -lc cd /a0 /opt/venv-a0/bin/python plugins/_orchestrator/tests/test_status_adapters.py仅改语法层面的适配器编辑时做编译检查docker exec 容器ID bash -lc cd /a0 /opt/venv-a0/bin/python -m py_compile plugins/_orchestrator/helpers/adapters/*.pytests/test_status_adapters.py 文件末尾自带__main__入口会在宿主机 Python 环境下依次调用全部测试函数测试通过向上查找包含 agent.py 的目录来定位仓库根并注入sys.path因此也可以在仓库根目录直接执行python plugins/_orchestrator/tests/test_status_adapters.py。当前测试覆盖的点包括配置绝对路径二进制的安装判定、注册表顺序a0 居首、状态专用断言、默认配置中的 headless 参数如permission_mode: bypassPermissions、grok 的output_format: json/always_approve: true、各适配器的环境变量识别路径以及 orchestrator skill 参考文档的内容完整性。结语plugins/_orchestrator/helpers/adapters/AGENTS.md 的价值在于把外部终端 Agent 接入这一容易膨胀为命令执行框架的需求收敛为一层薄的、只读的状态契约六个必定义成员、一个必实现的auth_status()、一组明确禁止的执行钩子以及八个已落地适配器各自的检测策略。理解这套契约后为仓库新增一个状态适配器的路径是清晰的——在 adapters 目录 新建模块实现TerminalAgentAdapter子类将其加入 registry.py 的_ADAPTERS元组在 default_config.yaml 增加同名配置段并确保密钥检测只返回布尔结论、断开行为与凭据来源严格对应最后跑通适配器测试套件即可。【免费下载链接】agent-zeroAgent Zero AI framework项目地址: https://gitcode.com/GitHub_Trending/ag/agent-zero创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考