Tolaria 与 Codex CLI 审批策略演进:从untrusted迁移到on-request/never
【免费下载链接】tolariaDesktop app to manage markdown knowledge bases项目地址: https://gitcode.com/GitHub_Trending/to/tolaria
本篇技术指南以 Tolaria(一个管理 Markdown 知识库的桌面应用,支持本地 CLI AI Agent)的架构决策记录 ADR-0179 为主体,完整剖析 Tolaria 如何将 Codex CLI 的--ask-for-approval审批策略从已退役的untrusted值平滑迁移到新版本接受的on-request/never,并结合 codex_cli.rs 源码与适配器测试,说明权限模式映射的底层实现、命令行参数契约与防回归保障。读完本文,你将理解 Codex CLI 审批策略的合法取值边界、Tolaria Vault Safe / Power User 双权限模式的真实命令形态,以及如何通过测试固定 CLI 参数契约。
背景:untrusted值为何会失效
Tolaria 早期通过 ADR-0103 将 Codex 的 Vault Safe 模式映射为"CLI 只读沙箱 +--ask-for-approval untrusted审批策略"。该 ADR 确立了核心产品契约:Tolaria 将权限模式视为产品契约,按适配器保守映射。
问题出在 Codex CLI 自身的行为变化上。当前 Codex CLI 构建(例如0.149.x系列)不再接受untrusted作为--ask-for-approval的合法值,有效的取值只剩下on-request和never。一旦传入旧值,CLI 会直接拒绝参数并退出,导致 Tolaria 应用管理的 Codex Vault Safe 会话在真正启动之前就失败,用户看到的典型报错如下:
error: invalid value 'untrusted' for '--ask-for-approval <APPROVAL_POLICY>' [possible values: on-request, never]这属于典型的上游 CLI 契约演进引发的下游适配失效:参数名没变,但枚举取值集合收窄了。ADR-0179 正是针对这一上游变化做出的对应决策,状态为active,日期为 2026-08-23,并明确 supersedes(取代)ADR-0103 中关于 Codex 审批策略的部分。
决策:新的权限模式 → CLI 参数映射
Tolaria 启动应用管理的 Codex 会话时,统一采用如下命令骨架:
codex --sandbox <sandbox> --ask-for-approval <approval> exec --json ...权限模式映射更新为:
| Tolaria 权限模式 | --sandbox | --ask-for-approval | 语义 |
|---|---|---|---|
| Safe(Vault Safe) | read-only | on-request | 模型在只读沙箱内执行命令前仍可自主决定是否请求用户批准 |
| Power User | workspace-write | never | 工作区可写,命令执行不打断会话,保持低摩擦 |
关键设计点:
on-request保留了untrusted的意图:两者都是"模型自行决定何时在执行命令前征求批准",只是 CLI 换了枚举名。因此迁移不改变安全语义,只改变参数取值。--sandbox取值完全不变:仍然是read-only与workspace-write两个值,本 ADR 只动审批策略这一维。- 从不引入危险绕过参数:Power User 虽然使用
never免审批,但绝不添加--dangerously-bypass-approvals-and-sandbox之类的完全绕过沙箱与审批的开关,这是权限契约的红线。
源码验证:Rust 适配器中的实际映射
上述映射不是文档口号,而是直接编码在 Tolaria 的 Rust 后端中。查看 codex_cli.rs:
fn codex_sandbox(permission_mode: crate::ai_agents::AiAgentPermissionMode) -> &'static str { match permission_mode { crate::ai_agents::AiAgentPermissionMode::Safe => "read-only", crate::ai_agents::AiAgentPermissionMode::PowerUser => "workspace-write", } } fn codex_approval_policy(permission_mode: crate::ai_agents::AiAgentPermissionMode) -> &'static str { match permission_mode { crate::ai_agents::AiAgentPermissionMode::Safe => "on-request", crate::ai_agents::AiAgentPermissionMode::PowerUser => "never", } }对应 codex_cli.rs 中的codex_sandbox与codex_approval_policy两个纯函数。它们以AiAgentPermissionMode枚举为输入——该枚举定义于 ai_agents.rs,包含Safe与PowerUser两个变体,且对缺失值默认归一化为Safe(unwrap_or_default())。
这两个函数随后被 build_codex_args 消费,构造出实际命令行参数前缀:
--sandbox <sandbox> --ask-for-approval <approval> exec --json -C <vault_path> ...其中exec --json表示以 JSON 行流模式执行会话,-C <vault_path>将 Codex 的工作目录固定到当前活动 vault,这与 Tolaria 以 vault 为权限作用域的产品模型一致(参见 ADR-0092 的每 vault 存储ai_agent_permission_mode设计)。ARCHITECTURE.md 的 Agent Adapters 一节也印证了这一形态:Safe 运行codex --sandbox read-only --ask-for-approval on-request exec --json -,Power User 运行codex --sandbox workspace-write --ask-for-approval never exec --json -。
完整的 Codex 会话命令:不止审批策略
实际运行时,Tolaria 为 Codex 会话构造的命令远比上面的前缀复杂。从build_codex_args的源码可以还原出完整形态:
codex --sandbox <sandbox> --ask-for-approval <approval> exec --json -C <vault_path> -c mcp_servers.tolaria.command="<resolved_node_path>" -c mcp_servers.tolaria.args=["<mcp_server_path>"] -c mcp_servers.tolaria.env={VAULT_PATH="<vault>",VAULT_PATHS="<vaults>",WS_UI_PORT="9711"} [--model <model_id>] [--output-last-message <path>] --c配置覆盖:通过-c参数向 Codex 注入临时 MCP 配置(mcp_servers.tolaria.*),使用 Tolaria 解析出的绝对 Node 路径而非裸node,并携带VAULT_PATH/VAULT_PATHS/WS_UI_PORT环境变量(UI 桥端口固定为9711)。这样既复用了 mcp-server 的 vault 工具,又不修改用户的全局 Codex 配置——符合 ADR-0103"不得静默改动全局 CLI 设置"的约束。--model可选:仅当用户在界面上显式选择模型时才追加该参数,且只传一次;默认(未选择)时完全不传,保持适配器默认模型行为(模型能力归适配器所有,见 ADR-0163)。--output-last-message:指向一个临时文件,作为流式输出的兜底读取通道(with_codex_last_message_fallback会在未收到文本增量时回读该文件)。- 尾部
-:从 stdin 接收 prompt;工作目录通过current_dir设置为 vault 路径,stdin/stdout/stderr 以管道方式交给统一的 AI Agent 事件流层处理。
会话开始后,Tolaria 通过dispatch_codex_event解析 Codex 的 JSON 事件(thread.started、item.started、item.completed),将command_execution(Bash 工具)与mcp_tool_call(Tolaria MCP 工具)归一化为ToolStart/ToolDone事件,供 AI 面板实时展示。
测试契约:防止untrusted回归
ADR-0179 明确要求"适配器测试必须拒绝重新引入已退役的untrusted审批值"。这一要求在测试代码中有三重体现:
- 合法取值硬编码断言:
codex_cli_tests/command_tests.rs中的codex_approval_policy_uses_only_supported_cli_values测试直接断言 Safe →"on-request"、Power User →"never",一旦有人把实现改回"untrusted",测试立即失败。 - 参数前缀契约:
codex_cli_tests/mod.rs中的assert_codex_permission_contract断言参数前缀必须是--sandbox <sandbox> --ask-for-approval <approval>的顺序组合,且整个参数列表中不得出现danger-full-access与--dangerously-bypass-approvals-and-sandbox两个危险开关。 - 双模式覆盖:
codex_power_user_keeps_workspace_write_without_dangerous_bypass与build_codex_args_uses_safe_default_permissions分别验证 Power User 与 Safe 两种模式下的参数契约,确保 Safe 走read-only+on-request、Power User 走workspace-write+never,且后者不引入危险绕过标志。
此外测试还覆盖了 MCP 注入细节:build_codex_args_uses_resolved_mcp_node_and_ui_bridge_env断言 MCP 命令使用绝对 Node 路径且环境变量包含WS_UI_PORT="9711",防止回退到裸node的脆弱形态。
后果与边界
- 兼容性恢复:拒绝
untrusted的 Codex CLI 版本可以从 Tolaria AI 面板以 Vault Safe 模式正常启动会话,不再出现参数被拒的启动失败。 - Vault Safe 的定位不变:它仍然是一个"尽力而为"的安全配置,而非真正关闭内置工具的模式——Codex CLI 目前只暴露 sandbox 与审批控制,没有一个专用开关能在保留 MCP 的同时移除 shell 工具。这与 ADR-0103 的结论一致,属于已知边界而非本 ADR 新引入的缺陷。
- 权限模式的保守性:从 ADR-0092 到 ADR-0103 再到本 ADR,Tolaria 始终坚持"共享 UI 契约 + 每适配器保守映射"的策略:权限模式是产品层的概念,具体到 CLI 参数由各适配器自行翻译,而测试负责锁死翻译结果。Codex 这条链路的演进正好展示了当上游 CLI 收紧参数枚举时,一个成熟适配器应如何快速收敛映射并同步加固测试。
参考链接
- ADR-0179 原文
- ADR-0103 适配器权限语义
- ADR-0092 Vault 权限模式
- Codex 适配器实现 codex_cli.rs
- Codex 参数契约测试 command_tests.rs
- Codex 测试公共断言 mod.rs
- 架构文档 Agent Adapters 一节
- MCP 服务器实现 mcp-server/
【免费下载链接】tolariaDesktop app to manage markdown knowledge bases项目地址: https://gitcode.com/GitHub_Trending/to/tolaria
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考