Codex 系统手册

配置、功能解析和启动策略组装

stage-4105 个文件

这一阶段是在真正干活前的“开机配餐”。它先把本地文件、云端规则、项目要求、命令行参数和临时请求都收齐,按优先级合成一份最终配置。再把功能开关、模型列表、插件、MCP 服务、内置模板等备好。权限部分会把用户写的规则翻译成沙箱能执行的读写、联网限制,Windows 还要单独算路径和身份牌。界面和服务器也用同一套入口改配置、存配置、查调试信息,避免前后说法不一致。

子阶段

本阶段涉及的状态20
  • reg-effective-config系统最终采用的那份配置,把文件、云端规则、项目设置、命令行参数和临时覆盖合在一起,后面各模块都按它办事。
  • reg-feature-flags当前打开或关闭的实验功能和能力开关,决定界面、模型、工具、插件等功能能不能用。
  • reg-installation-environment程序知道自己装在哪里、家目录在哪里、运行在哪台机器和什么终端里的那组环境信息。
  • reg-model-catalog系统当前知道有哪些模型、各模型有什么能力、来自哪个提供方以及能不能选用的模型清单。
  • reg-backend-remote-config-cache从云端拉下来的配置、目录和规则的本地缓存,启动和后台刷新都会读写它。
  • reg-plugin-registry已安装、可用、来自市场或本地的插件清单,以及每个插件的能力和加载结果。
  • reg-mcp-registryMCP 服务器、连接、工具和资源的登记表,模型和前端都靠它知道能调用哪些外部能力。
  • reg-skills-catalog当前能用的技能目录,包括技能来自哪里、说明文件在哪里、是否允许自动调用。
  • reg-permissions-approvals用户已经允许、拒绝或需要确认的命令、文件、联网、MCP 调用等权限和审批状态。
  • reg-sandbox-execution-policy命令和工具运行时要遵守的沙箱、读写、联网、身份和执行策略。
  • reg-tool-registry模型这一轮能看到和调用的工具集合,包括内置工具、动态工具、插件工具、MCP 工具和搜索工具。
  • reg-hook-lifecycle-state钩子在配置、回合、工具调用和审批过程里会触发什么、传什么上下文、返回什么结果的共享状态。
  • reg-network-client-proxyHTTP 客户端、证书、Cookie、重试、流式连接和网络代理放行/拒绝规则的共享联网状态。
  • reg-instructions-context-store用户规则、项目 AGENTS.md、扩展贡献指令和其他需持续注入模型的说明片段集合。
  • reg-prompt-fragment-registry内置提示词模板、上下文片段来源、扩展提示词贡献及其组装顺序的登记状态。
  • reg-invocation-dispatch-state本次进程启动选择的入口模式、子命令和早期解析参数,决定后续走 TUI、exec、登录、服务器或维护流程。
  • reg-active-model-selection当前线程或会话实际选中的模型、提供方和推理参数状态,区别于仅列出可用模型的模型目录。
  • reg-active-workspace-context当前会话选中的工作目录、项目根和能力根等工作区上下文,用于提示词、工具执行、权限判断和结果展示。
  • reg-onboarding-preferences-state新手引导、提示是否已看过、界面偏好和类似本地用户体验标记的持久状态。
  • reg-extension-lifecycle-state已加载扩展在账户、线程、回合和工具生命周期中注册的贡献、回调上下文和运行中结果状态。

本阶段的文件30

配置来源和有效组装

这些文件定义共享 CLI 和配置输入,公开配置加载门面,并组装系统其他部分使用的完整解析后的运行时配置。

utils/cli/src/shared_options.rs源码 ↗
configstartup / config load

这个文件像一张“通用点菜单”:用户启动 Codex 时可以通过命令行点选模型、配置档、工作目录、图片、可写目录,以及安全相关选项。这里的 SharedCliOptions 是这些选项的统一清单,并借助 clap(一个把命令行参数解析成结构体的工具)把 --model、--sandbox、--cd 这类文字参数变成程序能读的字段。文件里最重要的两段逻辑是“继承”和“覆盖”:有些命令有根命令和子命令两层参数,根命令给默认值,子命令可以改掉一部分。它还特别小心处理危险开关,比如跳过审批和沙箱;沙箱可以理解成给命令执行加一个安全围栏,防止乱改系统。没有这层统一处理,用户在不同入口传同样参数时,可能会得到不同结果,甚至误开危险模式。

函数细节2
SharedCliOptions::inherit_exec_root_options66–129 ↗
fn inherit_exec_root_options(&mut self, root: &Self)

作用:这个函数把“根命令”上的通用选项补到当前选项里。它适合在子命令没有自己写某个参数时,自动沿用外层命令给的默认选择。

数据流:进去的是当前这份可修改的选项 self,以及一份根命令选项 root。函数会逐项检查:如果当前没有选模型、配置档、沙箱模式、工作目录等,就从 root 复制过来;如果 root 里有图片或额外可写目录,就放到当前列表前面一起保留。出来时没有返回新对象,而是直接把 self 改好,让它带上继承后的完整设置。

调用关系:它通常出现在命令行参数刚解析完、真正启动任务之前。外层入口先拿到根命令的选择,再让当前命令调用这个函数补齐缺省值。它不把工作交给别的函数,而是在本地把每个字段按规则合并好,给后面的运行流程使用。

SharedCliOptions::apply_subcommand_overrides131–176 ↗
fn apply_subcommand_overrides(&mut self, subcommand: Self)

作用:这个函数把“子命令”明确写出来的选项覆盖到当前选项上。它解决的是:根命令有默认设置,但用户在具体子命令里又说了另一套时,应该尊重更具体的选择。

数据流:进去的是当前可修改的选项 self,以及子命令解析出的 subcommand。函数会看子命令哪些字段真的被用户设置了:有新模型就替换模型,有新工作目录就替换目录,有图片就用子命令图片列表,有额外目录就追加进去。对沙箱和危险绕过开关,它会先判断子命令是否真的选择了安全模式,再整体覆盖,避免无意中改掉原来的安全决定。结果同样不返回值,而是直接更新 self。

调用关系:它一般在已有一份基础选项之后调用,用来叠加更靠近用户意图的子命令设置。可以把它看成菜单上的“特殊要求”:先有默认套餐,再把子命令里明确点的内容盖上去。后续真正启动模型、执行命令、设置目录和安全策略的代码,会读取它整理好的最终选项。

app-server/src/config/mod.rs源码 ↗
configconfig load

这个文件本身不做复杂计算,也不保存具体配置。它的作用更像一本书的章节目录:把 external_agent_config 这个配置文件模块挂到 config 下面,让同一个包里的其他代码可以通过 config 这条路径找到它。这里的 pub(crate) 表示“只在当前这个 Rust 包内部公开”,也就是项目内部能用,但不会暴露给外部库或外部程序。没有这个文件里的声明,Rust 编译器就不知道 external_agent_config 是配置模块的一部分,其他地方也就没法按正常路径引用相关配置代码。

app-server/src/config_manager.rs源码 ↗
configconfig load / cross-cutting

配置来源很多,就像一张菜单可能被店规、会员偏好、临时备注一起改过。如果每个地方都自己拼配置,很容易漏掉某一层,或者同一个开关在不同请求里表现不一致。这个文件里的 ConfigManager 就是专门干这件事的:记住 Codex 的家目录、启动时传入的覆盖项、云端配置加载器、线程配置加载器,以及运行中临时打开或关闭的功能。别人要配置时,它会重新读取最新状态,把各种覆盖项按规则合并,再补上当前可执行文件路径这类运行环境信息。它还特别保护某些“被配置文件或要求锁住”的功能开关,防止运行时随便改掉。这样登录刷新、线程恢复、插件刷新、一次性命令执行等场景,都能拿到同一套可信的最终配置。

函数细节25
ConfigManager::new41–60 ↗
fn new(
        codex_home: PathBuf,
        cli_overrides: Vec<(String, TomlValue)>,
        loader_overrides: LoaderOverrides,
        strict_config: bool,
        cloud_config_bundle: CloudConfigBu

作用:创建一个新的配置管理器,把后面加载配置要用的各种材料先收好。别人通常在服务启动或测试搭环境时调用它。

数据流:进去的是 Codex 家目录、命令行覆盖项、加载器设置、是否严格检查配置、云端配置加载器、可执行文件路径、线程配置加载器。它把其中会被运行中替换的东西放进 Arc 和 RwLock 里;Arc 可以让多处共享同一份东西,RwLock 是读写锁,防止一边读一边改。出来的是一个可以被复制引用的 ConfigManager。

调用关系:它是整个配置流程的起点。服务启动、主运行流程和测试构造环境时会先建它;后面的 load_latest_config、load_for_cwd 等方法都依赖它保存的这些基础材料。

调用图:被 5 处调用(start_uninitialized, refresh_test_state, build_test_processor, derive_config_from_params_uses_session_thread_config_model_provider, run_main_with_transport_options);外部调用 3 个(new, new, new)。

ConfigManager::codex_home62–64 ↗
fn codex_home(&self) -> &Path

作用:返回 Codex 的家目录,也就是配置和相关文件通常放在哪里。调用者用它来定位用户配置或发出配置相关事件。

数据流:进去的是已经存在的 ConfigManager。它只读取里面保存的 codex_home,并把它作为路径引用交出去,不复制也不修改任何东西。

调用关系:它是一个小型取值口。user_config_path 会用它算用户配置文件位置,load_default_config 也会用它处理默认配置,其他地方也会拿它判断当前配置根目录。

调用图:被 3 处调用(load_default_config, user_config_path, emit_plugin_toggle_events);外部调用 1 个(as_path)。

ConfigManager::user_config_path66–68 ↗
fn user_config_path(&self) -> std::io::Result<AbsolutePathBuf>

作用:算出当前应该使用的用户配置文件路径。这样调用者不用自己猜配置文件到底放在哪。

数据流:进去的是 ConfigManager。它先通过 ConfigManager::codex_home 拿到家目录,再交给 loader_overrides 的 user_config_path 规则计算路径;出来的是绝对路径,或者一个输入输出错误。

调用关系:它站在“配置路径解析”这一步。load_default_config 需要它把默认配置层和指定用户配置文件接上,外部也可以调用它拿到当前选中的用户配置文件。

调用图:调用 2 个内部函数(codex_home, user_config_path)。

ConfigManager::current_cli_overrides70–75 ↗
fn current_cli_overrides(&self) -> Vec<(String, TomlValue)>

作用:取出当前保存的命令行覆盖配置。命令行覆盖配置就是启动程序时临时指定的配置值,通常优先级较高。

数据流:进去的是 ConfigManager。它尝试读取 cli_overrides 这把读写锁,读到就复制一份列表返回;如果锁出问题,就返回空列表,避免整个服务崩掉。它不改动原始数据。

调用关系:很多加载配置的入口都会先调用它,比如 load_latest_config、load_for_cwd、load_with_overrides 和 load_config_layers。它提供的是“启动时用户明确说过要覆盖什么”。

调用图:被 6 处调用(load_config_layers, load_default_config, load_for_cwd, load_latest_config, load_with_overrides, thread_start_task)。

ConfigManager::current_cloud_config_bundle77–82 ↗
fn current_cloud_config_bundle(&self) -> CloudConfigBundleLoader

作用:取出当前的云端配置加载器。云端配置加载器负责从云端或登录态相关位置拿受管理的配置包。

数据流:进去的是 ConfigManager。它读取 cloud_config_bundle 这把锁,成功就复制当前加载器,失败就返回默认加载器。它不会立即加载云端配置,只是把加载工具交出去。

调用关系:load_with_cli_overrides 和 load_config_layers 会在真正构造配置时用它。登录完成后如果换了认证信息,replace_cloud_config_bundle_loader 会先更新这里保存的加载器。

调用图:被 2 处调用(load_config_layers, load_with_cli_overrides)。

ConfigManager::extend_runtime_feature_enablement84–92 ↗
fn extend_runtime_feature_enablement(&self, enablement: I) -> Result<(), ()>

作用:在程序运行中追加一批功能开关设置,比如临时打开某个实验功能。这里的功能开关是“不重启也能调整”的开关。

数据流:进去的是一组“功能名 → 是否启用”的值。它拿到 runtime_feature_enablement 的写锁,把这些值并入已有表里;成功返回空成功结果,锁失败就返回错误。它会改变 ConfigManager 内部保存的运行时开关状态。

调用关系:set_experimental_feature_enablement 会调用它。之后再加载配置时,ConfigManager::apply_runtime_feature_enablement 会把这些运行时开关套到最终 Config 上。

调用图:被 1 处调用(set_experimental_feature_enablement)。

ConfigManager::replace_cloud_config_bundle_loader94–106 ↗
fn replace_cloud_config_bundle_loader(
        &self,
        auth_manager: Arc<AuthManager>,
        chatgpt_base_url: String,
    )

作用:登录状态变化后,换掉云端配置加载器。这样后续读取配置时,会使用新的账号认证和新的云端地址。

数据流:进去的是认证管理器、ChatGPT 基础地址,以及 ConfigManager 里已有的 Codex 家目录。它用这些创建新的 cloud_config_bundle_loader,然后拿写锁替换旧加载器;如果写锁失败,就只写一条警告日志。

调用关系:登录拿到新 token 或登录完成通知时会调用它。它不直接重新加载配置,只是更新“下次加载配置时该用谁去拿云端配置”。

调用图:被 2 处调用(login_chatgpt_auth_tokens_response, send_chatgpt_login_completion_notifications);外部调用 3 个(clone, cloud_config_bundle_loader, warn!)。

ConfigManager::replace_thread_config_loader108–117 ↗
fn replace_thread_config_loader(
        &self,
        thread_config_loader: Arc<dyn ThreadConfigLoader>,
    )

作用:替换线程配置加载器。线程配置加载器可以理解为“按某个会话或线程读取它自己的配置补丁”的工具。

数据流:进去的是新的 ThreadConfigLoader。它尝试拿写锁,把内部保存的加载器换成新的;失败时记录警告。成功后,后续配置加载会用新加载器。

调用关系:它是给外部热替换线程配置来源用的。之后 current_thread_config_loader 会把这个新加载器交给 load_with_cli_overrides 或 load_config_layers。

调用图:外部调用 1 个(warn!)。

ConfigManager::current_thread_config_loader119–124 ↗
fn current_thread_config_loader(&self) -> Arc<dyn ThreadConfigLoader>

作用:取出当前线程配置加载器,供配置构建过程使用。这样线程级别的配置不会被主配置流程漏掉。

数据流:进去的是 ConfigManager。它读取 thread_config_loader 锁,成功就复制 Arc 指针返回;如果锁坏了,就返回一个 NoopThreadConfigLoader,也就是“什么都不做”的备用加载器。

调用关系:load_with_cli_overrides 和 load_config_layers 会调用它。它把“线程自己的配置来源”接到真正的配置加载管线里。

调用图:被 2 处调用(load_config_layers, load_with_cli_overrides)。

ConfigManager::sync_default_client_residency_requirement126–136 ↗
async fn sync_default_client_residency_requirement(&self)

作用:把最新配置里的数据驻留要求同步给默认客户端。数据驻留要求就是“请求或数据必须留在哪个地区处理”的限制。

数据流:进去的是 ConfigManager。它先调用 load_latest_config 读取最新配置;成功后取出 enforce_residency 的值并设置到默认客户端;失败就记录警告,不中断服务。

调用关系:登录刷新或登录完成通知后会调用它。它把配置系统的结果传给登录/客户端系统,让后续网络请求遵守最新的地区要求。

调用图:调用 2 个内部函数(load_latest_config, set_default_client_residency_requirement);被 2 处调用(login_chatgpt_auth_tokens_response, send_chatgpt_login_completion_notifications);外部调用 1 个(warn!)。

ConfigManager::load_latest_config138–149 ↗
async fn load_latest_config(
        &self,
        fallback_cwd: Option<PathBuf>,
    ) -> std::io::Result<Config>

作用:读取当前最新的完整配置,不额外添加请求级覆盖项。它是很多地方获取“现在应该怎么运行”的常用入口。

数据流:进去的是一个可选 fallback_cwd,意思是没有明确工作目录时可用的备用目录。它先拿当前命令行覆盖项,再调用 load_with_cli_overrides,并传入空的请求覆盖和默认类型安全覆盖;出来的是最终 Config 或错误。

调用关系:它把简单调用转到更通用的 load_with_cli_overrides。同步驻留要求、刷新插件缓存、严格刷新、线程配置刷新等场景都会用它拿最新配置。

调用图:调用 2 个内部函数(current_cli_overrides, load_with_cli_overrides);被 10 处调用(load_latest_config_for_thread, sync_default_client_residency_requirement, queue_strict_refresh, maybe_refresh_plugin_caches_for_current_config, load_latest_config, load_latest_config, load_latest_config, load_latest_config, load_latest_config, load_latest_config);外部调用 1 个(default)。

ConfigManager::load_latest_config_for_thread151–164 ↗
async fn load_latest_config_for_thread(
        &self,
        thread_config: &Config,
    ) -> std::io::Result<Config>

作用:为已有线程刷新配置,同时保留这个线程会话里应该保留的配置层。它避免线程运行一半时因为全局配置刷新而丢掉会话自己的设置。

数据流:进去的是这个线程当前使用的 Config。它先用线程的 cwd 作为备用目录读取最新配置,再调用 rebuild_preserving_session_layers 把新配置和原线程会话层重新拼好,最后套用运行时功能开关和可执行文件路径;出来的是刷新后的 Config。

调用关系:build_refresh_config、实验功能列表、MCP 服务器状态列表等需要“站在线程视角看配置”的地方会调用它。它内部依赖 load_latest_config,再补上 ConfigManager 自己的运行时修正。

调用图:调用 3 个内部函数(apply_arg0_paths, apply_runtime_feature_enablement, load_latest_config);被 3 处调用(build_refresh_config, experimental_feature_list_response, list_mcp_server_status);外部调用 1 个(rebuild_preserving_session_layers)。

ConfigManager::load_default_config166–185 ↗
async fn load_default_config(&self) -> std::io::Result<Config>

作用:读取默认配置,主要用于需要一个基础配置而不是按具体请求定制配置的场景。它也会尊重启动时的命令行覆盖项。

数据流:进去的是 ConfigManager。它调用底层的默认配置加载函数,传入 Codex 家目录和当前命令行覆盖项;如果 loader_overrides 指定了用户配置路径或 profile,它还会把配置层记录调整到对应用户配置文件;最后套用运行时功能开关和可执行文件路径,返回 Config。

调用关系:它是“默认配置”入口,内部会调用 codex_home、current_cli_overrides、user_config_path、ConfigManager::apply_runtime_feature_enablement 和 ConfigManager::apply_arg0_paths。

调用图:调用 5 个内部函数(apply_arg0_paths, apply_runtime_feature_enablement, codex_home, current_cli_overrides, user_config_path);外部调用 4 个(clone, Table, load_default_with_cli_overrides_for_codex_home, new)。

ConfigManager::load_with_overrides187–199 ↗
async fn load_with_overrides(
        &self,
        request_overrides: Option<HashMap<String, serde_json::Value>>,
        typesafe_overrides: ConfigOverrides,
    ) -> std::io::Result<Config>

作用:按请求临时覆盖一些配置后,读取完整配置。比如某个接口请求想临时指定模型、沙箱设置或其他选项。

数据流:进去的是可选的 JSON 形式请求覆盖项,以及类型安全覆盖项。它取当前命令行覆盖项,把这些一起交给 load_with_cli_overrides;出来的是合并后的 Config 或错误。

调用关系:persist_session 和 thread_start_task 会调用它。它是“我有额外请求参数,但不关心工作目录”的便捷入口。

调用图:调用 2 个内部函数(current_cli_overrides, load_with_cli_overrides);被 2 处调用(persist_session, thread_start_task)。

ConfigManager::load_for_cwd201–214 ↗
async fn load_for_cwd(
        &self,
        request_overrides: Option<HashMap<String, serde_json::Value>>,
        typesafe_overrides: ConfigOverrides,
        cwd: Option<PathBuf>,
    ) -> std::io

作用:按指定工作目录读取配置。不同项目目录可能有不同配置,所以执行命令、恢复线程、列 hooks 时需要这个入口。

数据流:进去的是请求覆盖项、类型安全覆盖项,以及可选 cwd。它拿当前命令行覆盖项,然后调用 load_with_cli_overrides,把 cwd 作为 fallback_cwd 传下去;出来的是针对这个目录生效的 Config。

调用关系:hooks 列表、一次性命令执行、线程 fork/resume、Windows 沙箱设置等流程会调用它。它把“目录上下文”带进统一配置加载流程。

调用图:调用 2 个内部函数(current_cli_overrides, load_with_cli_overrides);被 6 处调用(hooks_list_response, exec_one_off_command_inner, thread_fork_inner, thread_resume_inner, build_thread_settings_overrides, windows_sandbox_setup_start_inner)。

ConfigManager::load_with_cli_overrides217–257 ↗
async fn load_with_cli_overrides(
        &self,
        cli_overrides: &[(String, TomlValue)],
        request_overrides: Option<HashMap<String, serde_json::Value>>,
        mut typesafe_overrides: C

作用:这是本文件最核心的配置加载函数。它把命令行覆盖、请求覆盖、类型安全覆盖、云端配置、线程配置等全部合并,生成最终 Config。

数据流:进去的是命令行覆盖列表、可选请求覆盖、类型安全覆盖和备用工作目录。它先特别处理 bypass_hook_trust,要求它必须是布尔值;再把请求里的 JSON 值转成 TOML 值,和命令行覆盖合并。随后用 ConfigBuilder 填入家目录、覆盖项、加载器设置、严格模式、云端加载器、线程加载器等,异步构建 Config。最后再应用运行时功能开关和可执行文件路径,返回最终配置。

调用关系:load_latest_config、load_with_overrides、load_for_cwd 和 thread_start_task 都会把工作交给它。它又会取 current_cloud_config_bundle、current_thread_config_loader,并调用 ConfigManager::apply_runtime_feature_enablement 和 ConfigManager::apply_arg0_paths 做最后修正。

调用图:调用 4 个内部函数(apply_arg0_paths, apply_runtime_feature_enablement, current_cloud_config_bundle, current_thread_config_loader);被 4 处调用(load_for_cwd, load_latest_config, load_with_overrides, thread_start_task);外部调用 3 个(clone, clone, default)。

ConfigManager::load_config_layers_for_cwd259–264 ↗
async fn load_config_layers_for_cwd(
        &self,
        cwd: AbsolutePathBuf,
    ) -> std::io::Result<ConfigLayerStack>

作用:读取某个目录下配置的“分层信息”。分层信息说明最终配置分别来自默认值、用户文件、云端管理、会话等哪些层。

数据流:进去的是一个绝对工作目录。它只是把这个目录包成 Some,转给 load_config_layers;出来的是 ConfigLayerStack 或错误。

调用关系:resolve_cwd_config 会调用它。它是面向“指定目录查配置层”的便捷包装,真正工作由 load_config_layers 完成。

调用图:调用 1 个内部函数(load_config_layers);被 1 处调用(resolve_cwd_config)。

ConfigManager::load_config_layers266–284 ↗
async fn load_config_layers(
        &self,
        cwd: Option<AbsolutePathBuf>,
    ) -> std::io::Result<ConfigLayerStack>

作用:读取配置层本身,而不只是最终合成后的配置值。这样界面或接口可以解释“这个值为什么是这样、是谁覆盖了谁”。

数据流:进去的是可选 cwd。它取当前线程配置加载器、命令行覆盖项、云端配置加载器和加载选项,然后调用 load_config_layers_state,从本地文件系统和各配置来源读取配置层;出来的是 ConfigLayerStack。

调用关系:load_config_layers_for_cwd 和 permission_profile_list_response 会调用它。它把 ConfigManager 保存的各种加载条件交给底层 load_config_layers_state。

调用图:调用 4 个内部函数(current_cli_overrides, current_cloud_config_bundle, current_thread_config_loader, load_config_layers_state);被 2 处调用(load_config_layers_for_cwd, permission_profile_list_response);外部调用 1 个(clone)。

ConfigManager::apply_runtime_feature_enablement286–288 ↗
fn apply_runtime_feature_enablement(&self, config: &mut Config)

作用:把 ConfigManager 里记录的运行时功能开关应用到某份 Config 上。它是方法版包装,方便内部在每次加载配置后调用。

数据流:进去的是一份可修改的 Config。它先复制当前 runtime_feature_enablement 表,再调用同名的文件级函数 apply_runtime_feature_enablement;结果是 Config 里的 features 可能被打开或关闭。

调用关系:load_default_config、load_latest_config_for_thread 和 load_with_cli_overrides 在返回配置前都会调用它。真正判断哪些功能能改、怎么改,由文件级 apply_runtime_feature_enablement 完成。

调用图:调用 2 个内部函数(current_runtime_feature_enablement, apply_runtime_feature_enablement);被 3 处调用(load_default_config, load_latest_config_for_thread, load_with_cli_overrides)。

ConfigManager::current_runtime_feature_enablement290–295 ↗
fn current_runtime_feature_enablement(&self) -> BTreeMap<String, bool>

作用:取出当前运行时功能开关表。调用者通常不会直接改这份表,而是拿它来应用到配置上。

数据流:进去的是 ConfigManager。它读取 runtime_feature_enablement 锁,成功就复制一份 BTreeMap 返回;失败就返回空表。它不改变内部状态。

调用关系:ConfigManager::apply_runtime_feature_enablement 会调用它。它提供“运行中临时打开或关闭了哪些功能”的快照。

调用图:被 1 处调用(apply_runtime_feature_enablement)。

ConfigManager::apply_arg0_paths297–301 ↗
fn apply_arg0_paths(&self, config: &mut Config)

作用:把当前程序实际使用的几个可执行文件路径写进 Config。这样后续需要重新调用 Codex 自己或沙箱程序时,能找到正确文件。

数据流:进去的是一份可修改的 Config。它从 arg0_paths 里复制 codex_self_exe、codex_linux_sandbox_exe、main_execve_wrapper_exe 三个路径,写到 Config 对应字段;没有返回值,但 Config 被更新。

调用关系:load_default_config、load_latest_config_for_thread 和 load_with_cli_overrides 都会在配置加载完成后调用它。它把启动时识别到的程序路径补进最终配置。

调用图:被 3 处调用(load_default_config, load_latest_config_for_thread, load_with_cli_overrides)。

ConfigManager::new_for_tests304–319 ↗
fn new_for_tests(
        codex_home: PathBuf,
        cli_overrides: Vec<(String, TomlValue)>,
        loader_overrides: LoaderOverrides,
        cloud_config_bundle: CloudConfigBundleLoader,
    ) -

作用:给测试创建一个简化版 ConfigManager。测试不需要完整的严格配置、真实 arg0 路径或真实线程加载器。

数据流:进去的是测试用 Codex 家目录、命令行覆盖、加载器覆盖和云端配置加载器。它调用 ConfigManager::new,固定 strict_config 为 false,使用默认 arg0 路径和什么都不做的线程配置加载器;出来的是测试用 ConfigManager。

调用关系:多种配置读写测试会调用它。它让测试能专注验证配置规则,而不用搭完整服务运行环境。

调用图:被 9 处调用(invalid_user_value_rejected_even_if_overridden_by_managed, load_default_config_preserves_selected_user_config_path_after_load_error, read_includes_origins_and_layers, read_reports_managed_overrides_user_and_session_flags, write_value_defaults_to_selected_user_config_path, write_value_rejects_feature_requirement_conflict, write_value_reports_managed_override, write_value_reports_override, write_value_succeeds_when_managed_preferences_expand_home_directory_paths);外部调用 3 个(new, new, default)。

ConfigManager::without_managed_config_for_tests322–329 ↗
fn without_managed_config_for_tests(codex_home: PathBuf) -> Self

作用:给测试创建一个“不带受管理配置”的 ConfigManager。受管理配置通常来自组织或云端,测试某些本地规则时需要排除它。

数据流:进去的是测试用 Codex 家目录。它准备空命令行覆盖、测试专用的“没有 managed config”加载器覆盖、默认云端配置加载器,然后调用 new_for_tests;出来的是简化测试管理器。

调用关系:很多写配置、清配置、冲突检查测试会调用它。它建立一个干净环境,避免云端或组织配置影响测试结果。

调用图:调用 2 个内部函数(default, without_managed_config_for_tests);被 12 处调用(batch_write_rejects_legacy_profile_selector, clear_missing_nested_config_is_noop, reserved_builtin_provider_override_rejected, upsert_merges_tables_replace_overwrites, version_conflict_rejected, write_value_defaults_to_user_config_path, write_value_preserves_comments_and_order, write_value_rejects_legacy_profile_selector, write_value_rejects_legacy_profile_table, write_value_supports_custom_mcp_server_default_tool_approval_mode (+2 more));外部调用 2 个(new_for_tests, new)。

protected_feature_keys332–349 ↗
fn protected_feature_keys(config_layer_stack: &ConfigLayerStack) -> BTreeSet<String>

作用:找出哪些功能开关不能被运行时随便改。这些功能要么已经在有效配置里写明,要么被 requirements 文件明确要求。

数据流:进去的是 ConfigLayerStack。它先查看最终生效配置里的 features 表,把里面出现过的功能名收集起来;再查看 requirements_toml 里的功能要求,把那些功能名也加入集合;出来的是一个功能名集合。

调用关系:文件级 apply_runtime_feature_enablement 会先调用它。它相当于列出“受保护名单”,防止运行时开关覆盖更高优先级或强制要求的配置。

调用图:调用 2 个内部函数(effective_config, requirements_toml);被 1 处调用(apply_runtime_feature_enablement)。

apply_runtime_feature_enablement351–371 ↗
fn apply_runtime_feature_enablement(
    config: &mut Config,
    runtime_feature_enablement: &BTreeMap<String, bool>,
)

作用:把一张运行时功能开关表应用到 Config,但会跳过受保护或未知的功能。这样既能临时开关实验功能,又不会破坏配置文件和组织要求。

数据流:进去的是可修改的 Config,以及“功能名 → 是否启用”的表。它先用 protected_feature_keys 找出不能动的功能;然后逐个检查运行时开关:受保护的跳过,系统不认识的跳过,认识且允许改的就调用 config.features.set_enabled 设置;如果设置失败就写警告。结果是 Config 的功能状态可能被改变。

调用关系:ConfigManager::apply_runtime_feature_enablement 会调用它。它是运行时功能开关真正落地的地方,并依赖 feature_for_key 把字符串名字转换成系统认识的功能对象。

调用图:调用 1 个内部函数(protected_feature_keys);被 1 处调用(apply_runtime_feature_enablement);外部调用 2 个(feature_for_key, warn!)。

config/src/project_root_markers.rs源码 ↗
configconfig load

很多工具启动时都要先弄清楚“我现在属于哪个项目”。最常见的办法,是从当前目录一路往上找某些标记文件或文件夹,比如“.git”。这个文件就负责两件事:第一,从配置文件里的 project_root_markers 读出用户自定义的标记;第二,在用户没配置时提供默认标记“.git”。它会认真检查配置格式:这个字段必须是字符串数组,也就是一串文字列表。如果用户写成别的类型,比如一个数字或普通文本,它会报错,避免后面用错配置。还有一个重要细节:空数组是合法的,意思不是“用默认值”,而是“关闭自动寻找项目根目录”。这让用户可以明确控制工具的行为。

函数细节2
project_root_markers_from_config16–43 ↗
fn project_root_markers_from_config(config: &TomlValue) -> io::Result<Option<Vec<String>>>

作用:从已经合并好的 config.toml 配置内容里,读出 project_root_markers 这一项。它会区分三种情况:没写、写了正确的字符串列表、写错格式。

数据流:输入是一份 TOML 配置值,也就是配置文件被解析后的结构。它先确认最外层是不是表格,再找 project_root_markers 字段;如果找不到,就返回“没有配置”。如果找到了,它要求这个字段必须是数组,并且数组里每一项都必须是字符串;检查通过后输出字符串列表。它不会修改配置,只负责读取和校验;如果格式不对,就返回一个输入数据无效的错误。

调用关系:它会在 load_config_layers_state 读取配置层时被调用,用来把配置文件里的项目根目录规则变成程序能直接使用的列表。它内部只借助 TOML 值的表格读取能力和标准错误创建方法,不把工作再交给项目里的其他函数。

调用图:被 1 处调用(load_config_layers_state);外部调用 3 个(as_table, new, new)。

default_project_root_markers45–50 ↗
fn default_project_root_markers() -> Vec<String>

作用:提供默认的项目根目录标记列表。当前默认只有“.git”,意思是看到 Git 仓库目录就把那里当作项目根目录。

数据流:它没有外部输入,只读取文件里写死的默认标记常量。然后把这些默认字符串转换成一份新的字符串列表返回给调用者。它不修改任何全局状态,所以每次调用都会得到一份可独立使用的列表。

调用关系:当配置里没有指定 project_root_markers 时,其他配置加载流程可以用它拿到默认规则。它是一个简单的兜底工具函数,不调用项目里的其他函数,也没有在提供的调用图里显示被谁直接调用。

core/src/config/mod.rs源码 ↗
configstartup/config load/cross-cutting

可以把这个文件想成开机时的总配电箱。用户写在 config.toml 里的设置只是原材料,命令行参数、项目级配置、托管要求、功能开关也会插进来;这个文件负责把它们按优先级合并,检查有没有冲突,再变成程序后面统一使用的 Config。它特别重视“权限”和“沙箱”:也就是哪些文件能读写、网络能不能通、执行命令要不要先问用户。如果配置被管理员要求限制,它会把不合规的值压回安全值,并留下启动警告。它还会准备模型提供商、MCP 工具服务器、插件输入、Web 搜索、多代理、TUI 界面、日志和数据库目录等。很多小函数看起来只是取值,其实是在保证旧配置、新配置和运行期状态能互相兼容。

函数细节124
GhostSnapshotConfig::default182–188 ↗
fn default() -> Self

作用:给旧版 ghost_snapshot 配置准备默认值。这个功能现在只是为了兼容老配置,不再真的生成快照。

数据流:进去没有参数 → 填上默认的大文件、大目录忽略阈值和不关闭警告 → 出来一份 GhostSnapshotConfig 默认配置。

调用关系:Config::load_config_with_layer_stack 在组装总配置时会用它兜底,测试里的 new_config 也会依赖这份默认值。

调用图:被 2 处调用(load_config_with_layer_stack, new_config)。

default_multi_agent_v2_usage_hint_text250–254 ↗
fn default_multi_agent_v2_usage_hint_text(usage_hint_text: &str, max_concurrency: usize) -> String

作用:拼出多代理 V2 的默认使用说明。它会把角色说明、共享规则和并发数量写成一段模型能看的提示文字。

数据流:进去一段基础说明和最大并发数 → 用固定共享说明把它们拼起来 → 出来完整的多代理提示文本。

调用关系:MultiAgentV2Config::defaults_for_max_concurrency 调它来生成 root agent 和子 agent 的默认提示。

调用图:被 1 处调用(defaults_for_max_concurrency);外部调用 1 个(format!)。

resolve_sqlite_home_env266–278 ↗
fn resolve_sqlite_home_env(resolved_cwd: &Path) -> Option<PathBuf>

作用:读取环境变量里的 SQLite 数据目录。SQLite 是一个本地小数据库,用来存 Codex 状态。

数据流:进去当前工作目录 → 读取 CODEX 的 SQLite home 环境变量,空值忽略,相对路径补到当前目录下 → 出来一个可用路径或 None。

调用关系:总配置加载时用它决定 sqlite_home;如果配置文件没写,就允许环境变量接管。

调用图:外部调用 3 个(join, from, var)。

resolve_cli_auth_credentials_store_mode280–291 ↗
fn resolve_cli_auth_credentials_store_mode(
    configured: AuthCredentialsStoreMode,
    package_version: &str,
) -> AuthCredentialsStoreMode

作用:决定 CLI 登录凭据放文件还是系统钥匙串。开发版会强制用文件,避免本地调试时误碰系统钥匙串。

数据流:进去配置的存储模式和包版本 → 如果是 0.0.0 开发版且配置是 keyring/auto,就改成 file → 出来最终存储模式。

调用关系:Config::load_config_with_layer_stack 在填 cli_auth_credentials_store_mode 时调用它。

调用图:被 1 处调用(load_config_with_layer_stack)。

resolve_mcp_oauth_credentials_store_mode293–304 ↗
fn resolve_mcp_oauth_credentials_store_mode(
    configured: OAuthCredentialsStoreMode,
    package_version: &str,
) -> OAuthCredentialsStoreMode

作用:决定 MCP OAuth 凭据放文件还是钥匙串。它和 CLI 凭据规则类似,也对开发版做安全简化。

数据流:进去配置的 OAuth 存储模式和包版本 → 开发版把 keyring/auto 改成 file,其他保持原样 → 出来最终模式。

调用关系:Config::load_config_with_layer_stack 在生成 MCP 配置相关字段时使用它。

调用图:被 1 处调用(load_config_with_layer_stack)。

test_config307–316 ↗
async fn test_config() -> Config

作用:给测试快速造一份默认 Config。它不读真实用户配置,避免测试受本机环境影响。

数据流:进去没有参数 → 创建临时 Codex home,用默认 ConfigToml 和默认覆盖项加载 → 出来一份可用于测试的 Config。

调用关系:大量测试会调用它,底层交给 Config::load_from_base_config_with_overrides 完成实际构造。

调用图:调用 1 个内部函数(from_absolute_path);被 32 处调用(interrupted_v2_agent_is_lost_after_residency_eviction, residency_slot_reservation_unloads_oldest_idle_v2_agent, guardian_review_session_compact_scope_change_invalidates_cached_session, guardian_review_session_config_change_invalidates_cached_session, guardian_review_session_config_disables_hooks, guardian_review_session_config_disables_skill_instructions, guardian_review_session_config_allows_pinned_disabled_feature, guardian_review_session_config_clears_legacy_notify, guardian_review_session_config_clears_parent_developer_instructions, guardian_review_session_config_disables_mcp_apps_plugins_and_memories (+15 more));外部调用 4 个(load_from_base_config_with_overrides, default, default, tempdir)。

Permissions::from_approval_and_profile352–368 ↗
fn from_approval_and_profile(
        approval_policy: Constrained<AskForApproval>,
        permission_profile: Constrained<PermissionProfile>,
    ) -> ConstraintResult<Self>

作用:用最少信息造一份权限配置。调用者只要给“何时审批”和“允许什么权限档案”,它补齐其他默认项。

数据流:进去审批策略和权限档案 → 转成内部 PermissionProfileState,并填空工作区、网络、shell、Windows 沙箱默认值 → 出来 Permissions。

调用关系:测试和简化配置构造会用它,内部依赖 PermissionProfileState::from_constrained_legacy。

调用图:调用 2 个内部函数(from_constrained_legacy, default);被 3 处调用(permission_snapshot_setter_preserves_permission_constraints, new_config, debug_config_output_filters_sandbox_modes_blocked_by_deny_read_requirements);外部调用 1 个(new)。

Permissions::permission_profile_state370–372 ↗
fn permission_profile_state(&self) -> &PermissionProfileState

作用:借出内部保存的权限档案状态。这个状态不只是权限本身,还记着它来自哪个命名档案。

数据流:进去一个 Permissions 引用 → 不修改任何东西,只返回内部状态引用 → 出来 PermissionProfileState 的只读视图。

调用关系:供同模块内部需要看完整权限状态的代码使用。

Permissions::set_permission_profile_state374–379 ↗
fn set_permission_profile_state(
        &mut self,
        permission_profile_state: PermissionProfileState,
    )

作用:直接替换内部权限档案状态。它用于已经解析好完整状态、只需要塞回 Permissions 的场景。

数据流:进去新的 PermissionProfileState → 覆盖旧状态 → Permissions 的有效权限来源被更新。

调用关系:apply_permission_profile_to_permissions 会用它把外部选好的权限状态应用进来。

调用图:被 1 处调用(apply_permission_profile_to_permissions)。

Permissions::set_permission_profile_from_session_snapshot386–392 ↗
fn set_permission_profile_from_session_snapshot(
        &mut self,
        snapshot: PermissionProfileSnapshot,
    ) -> ConstraintResult<()>

作用:把核心会话发出的权限快照应用到本地权限中。快照就是“当前会话实际在用的权限照片”。

数据流:进去 PermissionProfileSnapshot → 交给内部状态验证并设置 → 成功则当前权限变成快照描述的权限。

调用关系:用于客户端同步核心会话状态,内部调用 set_permission_profile_snapshot。

调用图:调用 1 个内部函数(set_permission_profile_snapshot)。

Permissions::replace_permission_profile_from_session_snapshot397–407 ↗
fn replace_permission_profile_from_session_snapshot(
        &mut self,
        snapshot: PermissionProfileSnapshot,
    ) -> ConstraintResult<()>

作用:在本地约束不接受快照时,强行用可信会话快照替换权限。它是一个“以核心会话为准”的桥。

数据流:进去快照 → 把快照权限包装成只允许这一种的约束,再转成已解析状态 → 覆盖当前权限状态。

调用关系:给必须镜像核心会话的客户端使用,内部会调用 allow_only、permission_profile 和 from_constrained_resolved。

调用图:调用 4 个内部函数(allow_only, into_resolved_permission_profile, permission_profile, from_constrained_resolved)。

Permissions::permission_profile411–413 ↗
fn permission_profile(&self) -> &PermissionProfile

作用:取出还没套入运行期工作区根目录的原始权限档案。它回答“配置本身允许什么”。

数据流:进去 Permissions → 读取内部状态里的权限档案 → 出来 PermissionProfile 引用。

调用关系:materialized_permission_profile 和 network_sandbox_policy 会从这里继续计算。

调用图:调用 1 个内部函数(permission_profile);被 2 处调用(materialized_permission_profile, network_sandbox_policy)。

Permissions::can_set_permission_profile415–421 ↗
fn can_set_permission_profile(
        &self,
        permission_profile: &PermissionProfile,
    ) -> ConstraintResult<()>

作用:检查某个权限档案能不能被当前约束接受。它只检查,不真的改。

数据流:进去候选 PermissionProfile → 用内部约束判断是否合法 → 出来成功或约束错误。

调用关系:sandbox_mode_is_allowed_by_permissions 等调用方用它提前判断用户选择是否可用。

调用图:调用 1 个内部函数(can_set_legacy_permission_profile);被 1 处调用(sandbox_mode_is_allowed_by_permissions)。

Permissions::set_workspace_roots423–425 ↗
fn set_workspace_roots(&mut self, workspace_roots: Vec<AbsolutePathBuf>)

作用:替换运行期工作区根目录列表。工作区根目录就是工具被允许围绕操作的项目目录。

数据流:进去一组绝对路径 → 覆盖旧 workspace_roots → 后续权限物化会按新目录计算。

调用关系:当会话或调用方重新指定工作区时使用。

Permissions::workspace_roots427–429 ↗
fn workspace_roots(&self) -> &[AbsolutePathBuf]

作用:查看当前运行期工作区根目录。它不会包含额外推导,只返回保存的列表。

数据流:进去 Permissions → 读取 workspace_roots → 出来路径切片。

调用关系:Config::set_legacy_sandbox_policy 在同步 Config 字段时会读取它。

调用图:被 1 处调用(set_legacy_sandbox_policy)。

Permissions::user_visible_workspace_roots433–435 ↗
fn user_visible_workspace_roots(&self) -> &[AbsolutePathBuf]

作用:返回用户可见的工作区根目录。内部专用的可写目录不会混进来。

数据流:进去 Permissions → 直接返回 workspace_roots → 出来用户应该看到的路径列表。

调用关系:给界面或外部展示使用,避免把 Codex 内部辅助目录暴露成用户选择。

Permissions::profile_workspace_roots437–439 ↗
fn profile_workspace_roots(&self) -> &[AbsolutePathBuf]

作用:返回权限档案自己声明的工作区根目录。它和运行期额外传入的根目录分开保存。

数据流:进去 Permissions → 从 PermissionProfileState 取 profile_workspace_roots → 出来路径列表。

调用关系:Config::effective_workspace_roots 会把它和运行期根目录合并。

调用图:调用 1 个内部函数(profile_workspace_roots);被 1 处调用(effective_workspace_roots)。

Permissions::materialized_permission_profile441–445 ↗
fn materialized_permission_profile(&self) -> PermissionProfile

作用:把权限档案里的“工作区占位符”替换成真实目录。这样后面的沙箱策略才能落到具体路径。

数据流:进去 Permissions → 克隆原权限档案,并用 workspace_roots 填充项目根目录 → 出来可直接执行的 PermissionProfile。

调用关系:effective_permission_profile、file_system_sandbox_policy 和 legacy_sandbox_policy 都依赖它。

调用图:调用 1 个内部函数(permission_profile);被 3 处调用(effective_permission_profile, file_system_sandbox_policy, legacy_sandbox_policy)。

Permissions::effective_permission_profile449–451 ↗
fn effective_permission_profile(&self) -> PermissionProfile

作用:得到运行时真正生效的权限档案。它已经把工作区目录这些运行期信息算进去了。

数据流:进去 Permissions → 调 materialized_permission_profile → 出来最终 PermissionProfile。

调用关系:给执行命令、展示权限、会话同步等流程使用。

调用图:调用 1 个内部函数(materialized_permission_profile)。

Permissions::active_permission_profile454–456 ↗
fn active_permission_profile(&self) -> Option<ActivePermissionProfile>

作用:返回当前是否选中了某个命名权限档案。命名档案像“只读”“工作区可写”这类可复选的预设。

数据流:进去 Permissions → 从状态里取 active profile → 出来 Some 档案信息或 None。

调用关系:界面和会话配置会用它告诉用户当前权限来自哪个档案。

调用图:调用 1 个内部函数(active_permission_profile)。

Permissions::file_system_sandbox_policy459–462 ↗
fn file_system_sandbox_policy(&self) -> FileSystemSandboxPolicy

作用:把权限档案转换成文件系统沙箱策略。沙箱策略就是限制程序能读写哪些文件的规则。

数据流:进去 Permissions → 先物化权限档案,再提取文件系统规则 → 出来 FileSystemSandboxPolicy。

调用关系:执行 shell 或统一执行工具时会用这份规则。

调用图:调用 1 个内部函数(materialized_permission_profile)。

Permissions::network_sandbox_policy465–467 ↗
fn network_sandbox_policy(&self) -> NetworkSandboxPolicy

作用:把权限档案转换成网络沙箱策略。它说明工具进程能不能访问网络。

数据流:进去 Permissions → 从原始权限档案读取网络规则 → 出来 NetworkSandboxPolicy。

调用关系:网络代理和执行环境会依据它决定是否放通网络。

调用图:调用 1 个内部函数(permission_profile)。

Permissions::legacy_sandbox_policy470–473 ↗
fn legacy_sandbox_policy(&self, cwd: &Path) -> SandboxPolicy

作用:把新权限档案投影成旧版 SandboxPolicy。这样老代码还能看懂当前权限。

数据流:进去当前工作目录 → 物化权限档案,再转换成兼容旧接口的沙箱策略 → 出来 SandboxPolicy。

调用关系:Config::legacy_sandbox_policy 会调用它,给仍使用旧沙箱表达方式的流程。

调用图:调用 1 个内部函数(materialized_permission_profile);被 1 处调用(legacy_sandbox_policy);外部调用 1 个(compatibility_sandbox_policy_for_permission_profile)。

Permissions::can_set_legacy_sandbox_policy477–492 ↗
fn can_set_legacy_sandbox_policy(
        &self,
        sandbox_policy: &SandboxPolicy,
        cwd: &Path,
    ) -> ConstraintResult<()>

作用:检查旧版沙箱策略能不能应用到当前权限约束。它先把旧策略翻译成新权限档案再验证。

数据流:进去 SandboxPolicy 和 cwd → 转成文件、网络和执行强度组合,再组成 PermissionProfile → 出来是否允许设置。

调用关系:Permissions::set_legacy_sandbox_policy 会先调用它防止非法修改。

调用图:调用 5 个内部函数(can_set_legacy_permission_profile, from_runtime_permissions_with_enforcement, from_legacy_sandbox_policy, from_legacy_sandbox_policy_for_cwd, from);被 1 处调用(set_legacy_sandbox_policy)。

Permissions::set_legacy_sandbox_policy496–534 ↗
fn set_legacy_sandbox_policy(
        &mut self,
        sandbox_policy: SandboxPolicy,
        cwd: &Path,
    ) -> ConstraintResult<()>

作用:用旧版 SandboxPolicy 更新当前权限,并保持工作区根目录同步。它用于兼容老入口。

数据流:进去 SandboxPolicy 和 cwd → 先检查合法,再转成新权限档案,按策略重建 workspace_roots → 更新内部权限状态并返回结果。

调用关系:Config::set_legacy_sandbox_policy 会把用户或旧接口传来的策略交给它。

调用图:调用 6 个内部函数(can_set_legacy_sandbox_policy, set_legacy_permission_profile, from_runtime_permissions_with_enforcement, from_legacy_sandbox_policy, from_legacy_sandbox_policy_for_cwd, from);被 1 处调用(set_legacy_sandbox_policy);外部调用 1 个(vec!)。

Permissions::set_permission_profile537–543 ↗
fn set_permission_profile(
        &mut self,
        permission_profile: PermissionProfile,
    ) -> ConstraintResult<()>

作用:直接设置新的规范权限档案。它是新权限系统的普通写入口。

数据流:进去 PermissionProfile → 交给内部状态按约束保存 → 成功后当前权限档案被替换。

调用关系:适合已经拿到规范 PermissionProfile 的调用方,不需要走旧 SandboxPolicy。

调用图:调用 1 个内部函数(set_legacy_permission_profile)。

profile_allows_configured_network_proxy549–556 ↗
fn profile_allows_configured_network_proxy(permission_profile: &PermissionProfile) -> bool

作用:判断某个权限档案能不能继承配置里的网络代理。没有外层沙箱的“完全禁用权限档案”不会开启托管代理。

数据流:进去 PermissionProfile → 看它是托管/外部且网络启用,还是完全禁用 → 出来 true 或 false。

调用关系:Config::load_config_with_layer_stack 和 Config::network_proxy_spec_for_active_permission_profile 用它决定是否构造代理。

调用图:被 2 处调用(load_config_with_layer_stack, network_proxy_spec_for_active_permission_profile)。

build_network_proxy_spec558–589 ↗
fn build_network_proxy_spec(
    configured_network_proxy_config: NetworkProxyConfig,
    network_requirements: Option<Sourced<codex_config::NetworkConstraints>>,
    permission_profile: &PermissionPr

作用:把网络代理配置、管理员网络要求和权限档案合成最终代理规格。代理规格说明是否要启动受控网络通道。

数据流:进去配置的 NetworkProxyConfig、可选网络要求和权限档案 → 调 NetworkProxySpec::from_config_and_constraints,并包装错误来源 → 出来 Some 代理规格或 None。

调用关系:总配置加载和切换权限档案时都会调用它。

调用图:调用 1 个内部函数(from_config_and_constraints);被 2 处调用(load_config_with_layer_stack, network_proxy_spec_for_active_permission_profile)。

MultiAgentV2Config::defaults_for_max_concurrency1079–1099 ↗
fn defaults_for_max_concurrency(max_concurrent_threads_per_session: usize) -> Self

作用:按指定并发数生成多代理 V2 默认配置。并发数会写进模型提示,告诉它最多能同时有几个代理活跃。

数据流:进去最大并发数 → 填默认等待时间、提示开关、提示文本、命名空间和展示选项 → 出来 MultiAgentV2Config。

调用关系:resolve_multi_agent_v2_config 用它作为用户配置的底板。

调用图:调用 1 个内部函数(default_multi_agent_v2_usage_hint_text);被 1 处调用(resolve_multi_agent_v2_config)。

MultiAgentV2Config::default1103–1107 ↗
fn default() -> Self

作用:生成多代理 V2 的全局默认配置。默认允许 4 个并发线程。

数据流:进去没有参数 → 调 defaults_for_max_concurrency 使用默认并发数 → 出来默认 MultiAgentV2Config。

调用关系:默认 Config 或测试构造会间接依赖它。

调用图:被 1 处调用(new_config);外部调用 1 个(defaults_for_max_concurrency)。

Config::codex_home1127–1129 ↗
fn codex_home(&self) -> PathBuf

作用:把 Config 里的 Codex home 目录提供给认证模块。Codex home 是保存配置、凭据和状态的主目录。

数据流:进去 Config → 克隆 codex_home 路径 → 出来 PathBuf。

调用关系:这是 AuthManagerConfig trait 的实现,登录管理器通过它找本地目录。

调用图:调用 1 个内部函数(to_path_buf)。

Config::cli_auth_credentials_store_mode1131–1133 ↗
fn cli_auth_credentials_store_mode(&self) -> AuthCredentialsStoreMode

作用:告诉认证模块 CLI 凭据应该怎么存。比如存在文件里,还是系统钥匙串里。

数据流:进去 Config → 读取 cli_auth_credentials_store_mode → 出来存储模式。

调用关系:认证管理器通过 AuthManagerConfig trait 调用它。

Config::auth_keyring_backend_kind1135–1137 ↗
fn auth_keyring_backend_kind(&self) -> AuthKeyringBackendKind

作用:告诉认证模块该用哪种系统钥匙串后端。钥匙串就是操作系统提供的安全凭据保险箱。

数据流:进去 Config → 调配置的 keyring 解析逻辑 → 出来 AuthKeyringBackendKind。

调用关系:to_mcp_config_with_plugin_registrations 也会用它给 MCP OAuth 配置凭据存储。

调用图:被 1 处调用(to_mcp_config_with_plugin_registrations);外部调用 1 个(auth_keyring_backend_kind)。

Config::forced_chatgpt_workspace_id1139–1141 ↗
fn forced_chatgpt_workspace_id(&self) -> Option<Vec<String>>

作用:返回是否限制只能登录某些 ChatGPT 工作区。它用于受管环境控制账号范围。

数据流:进去 Config → 克隆 forced_chatgpt_workspace_id → 出来工作区 ID 列表或 None。

调用关系:认证管理器通过 AuthManagerConfig trait 查询这个限制。

Config::chatgpt_base_url1143–1145 ↗
fn chatgpt_base_url(&self) -> String

作用:返回 ChatGPT 后端地址。登录和某些服务请求会用这个地址。

数据流:进去 Config → 克隆 chatgpt_base_url → 出来字符串 URL。

调用关系:认证管理器和 MCP 配置生成流程都会需要这个基础地址。

ConfigBuilder::codex_home1161–1164 ↗
fn codex_home(mut self, codex_home: PathBuf) -> Self

作用:给配置构建器指定 Codex home。它让调用方不必依赖默认的 ~/.codex。

数据流:进去构建器和路径 → 把路径存进 builder → 出来更新后的 builder。

调用关系:这是链式 builder 方法,最后由 ConfigBuilder::build_inner 使用。

ConfigBuilder::cli_overrides1166–1169 ↗
fn cli_overrides(mut self, cli_overrides: Vec<(String, TomlValue)>) -> Self

作用:给配置构建器设置命令行覆盖项。命令行覆盖项优先级通常比配置文件高。

数据流:进去 builder 和一组 TOML 路径/值 → 存进 builder → 出来更新后的 builder。

调用关系:Config::load_with_cli_overrides 等入口会用它再调用 build。

ConfigBuilder::harness_overrides1171–1174 ↗
fn harness_overrides(mut self, harness_overrides: ConfigOverrides) -> Self

作用:设置运行框架专用覆盖项。这里包含 cwd、可执行文件路径等不能写进 config.toml 的值。

数据流:进去 builder 和 ConfigOverrides → 存起来 → 出来 builder。

调用关系:Config::load_with_cli_overrides_and_harness_overrides 用它构建最终配置。

ConfigBuilder::loader_overrides1176–1179 ↗
fn loader_overrides(mut self, loader_overrides: LoaderOverrides) -> Self

作用:设置配置加载器本身的覆盖项。它控制加载哪些层、是否跳过托管配置等。

数据流:进去 builder 和 LoaderOverrides → 存进 builder → 出来 builder。

调用关系:测试和 profile 加载路径会用它改变加载行为。

ConfigBuilder::strict_config1181–1184 ↗
fn strict_config(mut self, strict_config: bool) -> Self

作用:打开或关闭严格配置模式。严格模式会更认真地把配置问题当成错误。

数据流:进去 builder 和布尔值 → 更新 strict_config 字段 → 出来 builder。

调用关系:ConfigBuilder::build_inner 会把这个选项传给 load_config_layers_state。

ConfigBuilder::cloud_config_bundle1186–1189 ↗
fn cloud_config_bundle(mut self, cloud_config_bundle: CloudConfigBundleLoader) -> Self

作用:设置云端配置包加载器。它用于把远端或托管下发的配置也纳入合并。

数据流:进去 builder 和 CloudConfigBundleLoader → 保存它 → 出来 builder。

调用关系:ConfigBuilder::build_inner 会把它放进 ConfigLoadOptions。

ConfigBuilder::thread_config_loader1191–1197 ↗
fn thread_config_loader(
        mut self,
        thread_config_loader: Arc<dyn ThreadConfigLoader>,
    ) -> Self

作用:设置线程级配置加载器。线程级配置就是某个会话或线程单独带来的配置层。

数据流:进去 builder 和加载器对象 → 用 Arc 保存共享引用 → 出来 builder。

调用关系:build_inner 加载配置层时会把它传给 load_config_layers_state。

ConfigBuilder::fallback_cwd1199–1202 ↗
fn fallback_cwd(mut self, fallback_cwd: Option<PathBuf>) -> Self

作用:设置备用当前目录。如果 harness 覆盖里没指定 cwd,就用这个目录。

数据流:进去 builder 和可选路径 → 保存为 fallback_cwd → 出来 builder。

调用关系:build_inner 解析 cwd 时会在 harness cwd 和当前进程目录之间使用它。

ConfigBuilder::build1204–1207 ↗
async fn build(self) -> std::io::Result<Config>

作用:启动配置构建流程。它把真正的大型异步加载任务包起来,避免小线程栈被撑爆。

数据流:进去 builder → 把 build_inner pin 住并等待 → 出来 Config 或 IO 错误。

调用关系:build_config_on_runtime_worker 等上层入口调用它,实际工作交给 build_inner。

调用图:调用 1 个内部函数(build_inner);被 1 处调用(build_config_on_runtime_worker);外部调用 1 个(pin)。

ConfigBuilder::build_inner1209–1316 ↗
async fn build_inner(self) -> std::io::Result<Config>

作用:配置构建器的核心流程。它找目录、算 cwd、加载配置层、处理 lockfile,最后生成 Config。

数据流:进去 builder 内所有选项 → 解析 codex_home/cwd,调用 load_config_layers_state 合并 TOML,必要时读取配置锁文件,再调用 Config::load_config_with_layer_stack → 出来最终 Config。

调用关系:由 ConfigBuilder::build 调用,是普通启动配置加载的主通道。

调用图:调用 9 个内部函数(load_config_layers_state, new, find_codex_home, config_without_lock_controls, lock_layer_from_config, read_config_lock_from_path, current_dir, from_absolute_path, relative_to_current_dir);被 1 处调用(build);外部调用 5 个(new, load_config_with_layer_stack, new, io_error_from_config_error, vec!)。

ConfigBuilder::without_managed_config_for_tests1319–1321 ↗
fn without_managed_config_for_tests() -> Self

作用:给测试创建一个不加载托管配置的 builder。这样测试不会被机器上的公司策略影响。

数据流:进去没有参数 → 从默认 builder 开始,加上测试用 LoaderOverrides → 出来 builder。

调用关系:很多配置测试会先调用它,再设置 CLI 或文件内容。

调用图:调用 1 个内部函数(without_managed_config_for_tests);被 58 处调用(test_config_with_cli_overrides, inline_instructions_set_base_instructions, active_profile_is_cleared_when_requirements_force_fallback, agent_role_file_metadata_overrides_config_toml_metadata, agent_role_file_name_takes_precedence_over_config_key, agent_role_file_without_developer_instructions_is_dropped_with_warning, agent_role_relative_config_file_resolves_against_config_toml, agent_role_without_description_after_merge_is_dropped_with_warning, approvals_reviewer_can_be_set_in_config_without_guardian_approval, approvals_reviewer_defaults_to_manual_only_without_guardian_feature (+15 more));外部调用 1 个(default)。

Config::multi_agent_version_from_features1325–1333 ↗
fn multi_agent_version_from_features(&self) -> MultiAgentVersion

作用:根据功能开关判断当前使用哪一代多代理。功能开关就是集中控制某功能是否启用的开关。

数据流:进去 Config → 查看 MultiAgentV2 和 Collab 是否启用 → 出来 V2、V1 或 Disabled。

调用关系:运行时决定代理工具表面时会查询它。

调用图:外部调用 1 个(enabled)。

Config::validate_multi_agent_v2_config1335–1344 ↗
fn validate_multi_agent_v2_config(&self) -> std::io::Result<()>

作用:检查多代理 V2 和旧 agents.max_threads 配置是否冲突。V2 有自己的并发设置,不能再混用旧字段。

数据流:进去 Config → 如果 MultiAgentV2 启用且 agent_max_threads 被设置,就返回错误 → 否则成功。

调用关系:启动或会话配置验证阶段会用它阻止含糊配置。

调用图:外部调用 2 个(new, enabled)。

Config::effective_agent_max_threads1346–1360 ↗
fn effective_agent_max_threads(
        &self,
        multi_agent_version: MultiAgentVersion,
    ) -> Option<usize>

作用:算出实际允许的子代理线程数。V2 要给主代理占一个并发槽,所以会减一。

数据流:进去多代理版本 → V2 用 max_concurrent_threads_per_session 减一,V1/禁用用旧配置或默认值 → 出来可选线程数。

调用关系:代理调度器会用它限制同时打开的代理数量。

Config::legacy_sandbox_policy1362–1364 ↗
fn legacy_sandbox_policy(&self) -> SandboxPolicy

作用:从整份 Config 里取旧版沙箱策略。它把工作目录一起传给 Permissions。

数据流:进去 Config → 用 cwd 调 Permissions::legacy_sandbox_policy → 出来 SandboxPolicy。

调用关系:给仍依赖旧 SandboxPolicy 的调用方使用。

调用图:调用 2 个内部函数(legacy_sandbox_policy, as_path)。

Config::set_legacy_sandbox_policy1366–1378 ↗
fn set_legacy_sandbox_policy(
        &mut self,
        sandbox_policy: SandboxPolicy,
    ) -> ConstraintResult<()>

作用:用旧版沙箱策略修改整份 Config 的权限,并同步 workspace_roots 字段。

数据流:进去 SandboxPolicy → 标记工作区根是否显式设置,交给 Permissions 更新,再把 Permissions 的 roots 拷回 Config → 出来成功或约束错误。

调用关系:外部旧接口设置沙箱时调用它,核心修改交给 Permissions::set_legacy_sandbox_policy。

调用图:调用 3 个内部函数(set_legacy_sandbox_policy, workspace_roots, as_path);外部调用 1 个(matches!)。

Config::effective_workspace_roots1380–1385 ↗
fn effective_workspace_roots(&self) -> Vec<AbsolutePathBuf>

作用:算出最终工作区根目录列表。它把运行期 roots 和权限档案自带 roots 合并并去重。

数据流:进去 Config → 克隆 workspace_roots,加上 permissions.profile_workspace_roots,再去重 → 出来路径列表。

调用关系:工具执行、界面展示和权限判断都可能用这份最终列表。

调用图:调用 2 个内部函数(profile_workspace_roots, dedupe_absolute_paths)。

Config::to_models_manager_config1387–1397 ↗
fn to_models_manager_config(&self) -> ModelsManagerConfig

作用:把大 Config 中和模型管理有关的部分摘出来。模型管理器不需要知道权限、TUI 等杂项。

数据流:进去 Config → 拷贝模型窗口、压缩阈值、提示、个性功能和模型目录字段 → 出来 ModelsManagerConfig。

调用关系:模型管理子系统初始化时用它获得自己需要的设置。

调用图:外部调用 1 个(enabled)。

Config::plugins_config_input1400–1407 ↗
fn plugins_config_input(&self) -> PluginsConfigInput

作用:为插件管理器准备输入。它告诉插件系统配置层、插件功能开关和后端地址。

数据流:进去 Config → 克隆 config_layer_stack,读取 Plugins/RemotePlugin 开关和 ChatGPT URL → 出来 PluginsConfigInput。

调用关系:to_mcp_config_with_plugin_registrations 会先调用它来加载可用插件。

调用图:调用 1 个内部函数(new);被 1 处调用(to_mcp_config_with_plugin_registrations);外部调用 2 个(clone, enabled)。

Config::apply_plugin_mcp_server_requirements1410–1427 ↗
fn apply_plugin_mcp_server_requirements(
        &self,
        plugin_id: &str,
        mcp_servers: &mut HashMap<String, McpServerConfig>,
    )

作用:把托管要求应用到某个插件声明的 MCP 服务器上。MCP 是模型上下文协议,用来接工具服务器。

数据流:进去插件 ID 和可变服务器表 → 先按插件要求过滤,再按全局空白名单要求过滤 → 服务器会被禁用或清除禁用原因。

调用关系:to_mcp_config_with_plugin_registrations 在注册插件 MCP 服务器前调用它。

调用图:调用 3 个内部函数(requirements, filter_mcp_servers_by_requirements, filter_plugin_mcp_servers_by_requirements);被 1 处调用(to_mcp_config_with_plugin_registrations)。

Config::to_mcp_config1429–1438 ↗
async fn to_mcp_config(
        &self,
        plugins_manager: &codex_core_plugins::PluginsManager,
    ) -> McpConfig

作用:把 Config 转成 MCP 子系统需要的配置。普通调用方不用额外塞插件注册项。

数据流:进去 Config 和插件管理器 → 调带额外注册项的版本且传空迭代器 → 出来 McpConfig。

调用关系:它是公开简化入口,实际组装交给 to_mcp_config_with_plugin_registrations。

调用图:调用 1 个内部函数(to_mcp_config_with_plugin_registrations)。

Config::to_mcp_config_with_plugin_registrations1440–1508 ↗
async fn to_mcp_config_with_plugin_registrations(
        &self,
        plugins_manager: &codex_core_plugins::PluginsManager,
        additional_plugin_registrations: impl IntoIterator<Item = McpServ

作用:完整组装 MCP 配置。它合并插件提供的服务器、额外注册项和用户配置的服务器。

数据流:进去 Config、插件管理器和额外注册项 → 加载活跃插件,过滤服务器,登记来源和顺序,再填 OAuth、沙箱、能力开关等字段 → 出来 McpConfig。

调用关系:Config::to_mcp_config 调用它,内部还会用 plugins_config_input、apply_plugin_mcp_server_requirements、prefix_mcp_tool_names。

调用图:调用 11 个内部函数(new, from_config, from_plugin, builder, get, plugins_for_config, apply_plugin_mcp_server_requirements, auth_keyring_backend_kind, plugins_config_input, prefix_mcp_tool_names (+1 more));被 1 处调用(to_mcp_config);外部调用 5 个(default, default, default, enabled, use_legacy_landlock)。

Config::prefix_mcp_tool_names1510–1512 ↗
fn prefix_mcp_tool_names(&self) -> bool

作用:判断 MCP 工具名是否要加前缀。加前缀能避免不同服务器的工具同名冲突。

数据流:进去 Config → 查看 NonPrefixedMcpToolNames 功能是否关闭 → 出来 true 表示要加前缀。

调用关系:to_mcp_config_with_plugin_registrations 会把这个决定写进 McpConfig。

调用图:被 1 处调用(to_mcp_config_with_plugin_registrations);外部调用 1 个(enabled)。

Config::rebuild_preserving_session_layers1514–1575 ↗
async fn rebuild_preserving_session_layers(
        &self,
        refreshed_config: &Config,
    ) -> std::io::Result<Self>

作用:刷新配置时保留当前会话层。这样用户改了全局配置,也不会丢掉本会话启动时带的临时参数。

数据流:进去旧 Config 和刷新后的 Config → 取刷新后的非会话层,加回旧配置中的会话层,重新排序并重新加载 → 出来新的 Config。

调用关系:运行中热刷新配置时使用,最后仍交给 load_config_with_layer_stack 重新构造。

调用图:调用 3 个内部函数(get_layers, new, to_path_buf);外部调用 2 个(default, load_config_with_layer_stack)。

Config::load_with_cli_overrides1578–1585 ↗
async fn load_with_cli_overrides(
        cli_overrides: Vec<(String, TomlValue)>,
    ) -> std::io::Result<Self>

作用:用命令行覆盖项加载完整 Config。这是推荐的普通加载入口。

数据流:进去 CLI 覆盖列表 → 创建默认 ConfigBuilder,设置 cli_overrides 并 build → 出来 Config 或错误。

调用关系:CLI 启动路径通常从这里进入配置系统。

调用图:外部调用 1 个(default)。

Config::load_default_with_cli_overrides1588–1597 ↗
async fn load_default_with_cli_overrides(
        cli_overrides: Vec<(String, TomlValue)>,
    ) -> std::io::Result<Self>

作用:当用户配置坏掉时,用默认配置加 CLI 覆盖项启动。它给程序一个可恢复的保底方案。

数据流:进去 CLI 覆盖列表 → 找 codex_home,再调用指定 home 的默认加载函数 → 出来 Config。

调用关系:错误恢复路径会用它跳过用户配置文件。

调用图:调用 1 个内部函数(find_codex_home);外部调用 1 个(load_default_with_cli_overrides_for_codex_home)。

Config::load_default_with_cli_overrides_for_codex_home1601–1623 ↗
async fn load_default_with_cli_overrides_for_codex_home(
        codex_home: PathBuf,
        cli_overrides: Vec<(String, TomlValue)>,
    ) -> std::io::Result<Self>

作用:在指定 Codex home 下加载默认配置,不读用户、项目或系统配置层。

数据流:进去 codex_home 和 CLI 覆盖 → 把默认 ConfigToml 转成 TOML,合并 CLI 层,反序列化后加载 → 出来 Config。

调用关系:Config::load_default_with_cli_overrides 调它;内部用 deserialize_config_toml_with_base 和 load_config_with_layer_stack。

调用图:调用 2 个内部函数(deserialize_config_toml_with_base, from_absolute_path_checked);外部调用 7 个(load_config_with_layer_stack, build_cli_overrides_layer, merge_toml_values, default, default, default, try_from)。

Config::load_with_cli_overrides_and_harness_overrides1632–1641 ↗
async fn load_with_cli_overrides_and_harness_overrides(
        cli_overrides: Vec<(String, TomlValue)>,
        harness_overrides: ConfigOverrides,
    ) -> std::io::Result<Self>

作用:同时使用命令行覆盖和运行框架覆盖来加载 Config。适合 exec 这类需要强制某些行为的入口。

数据流:进去 CLI 覆盖和 ConfigOverrides → 设置到默认 ConfigBuilder 后 build → 出来 Config。

调用关系:特殊子命令或测试框架通过它进入完整配置加载流程。

调用图:外部调用 1 个(default)。

resolve_profile_v2_config_path1644–1652 ↗
fn resolve_profile_v2_config_path(
    codex_home: &Path,
    profile_name: &ProfileV2Name,
) -> AbsolutePathBuf

作用:把 profile 名字转换成对应的配置文件路径。profile 是一套可选配置预设。

数据流:进去 codex_home 和 ProfileV2Name → 拼成 <name>.config.toml 并按 codex_home 解析成绝对路径 → 出来 AbsolutePathBuf。

调用关系:profile 加载、主程序启动和归档 app-server 启动会用它找配置文件。

调用图:调用 1 个内部函数(resolve_path_against_base);被 3 处调用(loader_overrides_for_profile, run_main, start_app_server_for_archive_command);外部调用 1 个(format!)。

load_config_as_toml_with_cli_overrides1657–1670 ↗
async fn load_config_as_toml_with_cli_overrides(
    codex_home: &Path,
    cwd: Option<&AbsolutePathBuf>,
    cli_overrides: Vec<(String, TomlValue)>,
    loader_overrides: LoaderOverrides,
) -> std:

作用:旧接口:只加载合并后的 ConfigToml,而不是完整 Config。注释明确提醒多数调用方不该再用它。

数据流:进去 codex_home、cwd、CLI 覆盖和 loader 覆盖 → 转交给下一层旧接口 → 出来 ConfigToml。

调用关系:它只是兼容包装,实际调用 load_config_as_toml_with_cli_and_loader_overrides。

调用图:调用 1 个内部函数(load_config_as_toml_with_cli_and_loader_overrides)。

load_config_as_toml_with_cli_and_loader_overrides1676–1684 ↗
async fn load_config_as_toml_with_cli_and_loader_overrides(
    codex_home: &Path,
    cwd: Option<&AbsolutePathBuf>,
    cli_overrides: Vec<(String, TomlValue)>,
    loader_overrides: LoaderOverrides

作用:旧接口:带 loader 覆盖加载 ConfigToml。风险是还没应用托管要求。

数据流:进去目录、cwd、CLI 覆盖和 LoaderOverrides → 转给更通用的 load options 函数 → 出来 ConfigToml。

调用关系:由 load_config_as_toml_with_cli_overrides 调用,继续委托给 load_config_as_toml_with_cli_and_load_options。

调用图:调用 1 个内部函数(load_config_as_toml_with_cli_and_load_options);被 1 处调用(load_config_as_toml_with_cli_overrides)。

load_config_as_toml_with_cli_and_load_options1690–1699 ↗
async fn load_config_as_toml_with_cli_and_load_options(
    codex_home: &Path,
    cwd: Option<&AbsolutePathBuf>,
    cli_overrides: Vec<(String, TomlValue)>,
    options: impl Into<ConfigLoadOptions>

作用:旧接口:用加载选项得到半成品 ConfigToml。它只取结果中的 config_toml。

数据流:进去 codex_home、cwd、CLI 覆盖和加载选项 → 调 load_config_toml_with_layer_stack → 出来其中的 ConfigToml。

调用关系:这是几个旧 TOML 加载包装的最后一层。

调用图:调用 1 个内部函数(load_config_toml_with_layer_stack);被 1 处调用(load_config_as_toml_with_cli_and_loader_overrides)。

load_config_toml_with_layer_stack1713–1739 ↗
async fn load_config_toml_with_layer_stack(
    codex_home: &Path,
    cwd: Option<&AbsolutePathBuf>,
    cli_overrides: Vec<(String, TomlValue)>,
    options: impl Into<ConfigLoadOptions>,
) -> std::

作用:加载合并后的原始配置和它的配置层堆栈。配置层堆栈记录每个值来自哪里。

数据流:进去 codex_home、cwd、CLI 覆盖和加载选项 → 调 load_config_layers_state 合并层,再反序列化成 ConfigToml → 出来 ConfigTomlLoadResult。

调用关系:启动早期需要先看原始配置的路径会调用它,比如 bootstrap 配置加载。

调用图:调用 2 个内部函数(load_config_layers_state, deserialize_config_toml_with_base);被 4 处调用(load_config_as_toml_with_cli_and_load_options, load_bootstrap_config_or_exit, load_bootstrap_config_or_exit, start_app_server_for_archive_command)。

deserialize_config_toml_with_base1741–1751 ↗
fn deserialize_config_toml_with_base(
    root_value: TomlValue,
    config_base_dir: &Path,
) -> std::io::Result<ConfigToml>

作用:把 TOML 值变成 ConfigToml,并让相对路径按指定目录解析。这样配置里的相对路径不会乱飘。

数据流:进去 TOML 根值和基础目录 → 临时设置 AbsolutePathBufGuard,再 try_into → 出来 ConfigToml 或数据错误。

调用关系:配置编辑、角色层加载、默认加载和半成品加载都会用它。

调用图:调用 1 个内部函数(new);被 5 处调用(apply_edits, load_role_layer_toml, deserialize_effective_config, load_default_with_cli_overrides_for_codex_home, load_config_toml_with_layer_stack);外部调用 1 个(try_into)。

validate_feature_requirements_for_config_toml1754–1760 ↗
fn validate_feature_requirements_for_config_toml(
    cfg: &ConfigToml,
    feature_requirements: Option<&Sourced<FeatureRequirementsToml>>,
) -> std::io::Result<()>

作用:检查用户配置的功能开关是否违反托管要求。托管要求就是管理员或系统强制的规则。

数据流:进去 ConfigToml 和可选功能要求 → 先检查显式设置,再检查要求本身 → 出来成功或错误。

调用关系:apply_edits 在写配置前会调用它,避免保存不合规配置。

调用图:调用 2 个内部函数(validate_explicit_feature_settings_in_config_toml, validate_feature_requirements_in_config_toml);被 1 处调用(apply_edits)。

load_catalog_json1762–1783 ↗
fn load_catalog_json(path: &AbsolutePathBuf) -> std::io::Result<ModelsResponse>

作用:从 JSON 文件读取模型目录。模型目录告诉程序有哪些模型可用。

数据流:进去 JSON 文件路径 → 读文件、解析成 ModelsResponse,并确保至少有一个模型 → 出来模型目录或错误。

调用关系:load_model_catalog 在用户配置了 model_catalog_json 时调用它。

调用图:外部调用 3 个(new, format!, read_to_string)。

load_model_catalog1785–1791 ↗
fn load_model_catalog(
    model_catalog_json: Option<AbsolutePathBuf>,
) -> std::io::Result<Option<ModelsResponse>>

作用:按需加载模型目录文件。没配置路径时就什么也不加载。

数据流:进去可选路径 → 有路径就调用 load_catalog_json,没有就返回 None → 出来可选 ModelsResponse。

调用关系:Config::load_config_with_layer_stack 用它填 Config.model_catalog。

调用图:被 1 处调用(load_config_with_layer_stack)。

filter_mcp_servers_by_requirements1793–1816 ↗
fn filter_mcp_servers_by_requirements(
    mcp_servers: &mut HashMap<String, McpServerConfig>,
    mcp_requirements: Option<&Sourced<BTreeMap<String, McpServerRequirement>>>,
)

作用:按全局 MCP 服务器白名单过滤服务器。没在要求里或身份不匹配的服务器会被禁用。

数据流:进去可变服务器表和可选要求 → 对每个服务器检查名称和命令/URL 是否匹配 → 修改 enabled 和 disabled_reason。

调用关系:Config::apply_plugin_mcp_server_requirements 会用它处理全局 MCP 要求。

调用图:被 1 处调用(apply_plugin_mcp_server_requirements)。

filter_plugin_mcp_servers_by_requirements1818–1845 ↗
fn filter_plugin_mcp_servers_by_requirements(
    plugin_config_name: &str,
    mcp_servers: &mut HashMap<String, McpServerConfig>,
    plugin_requirements: Option<&Sourced<BTreeMap<String, PluginRequ

作用:按插件专属要求过滤插件提供的 MCP 服务器。不同插件可以有各自允许的服务器清单。

数据流:进去插件配置名、服务器表和插件要求 → 找到该插件要求后逐个匹配服务器 → 不允许的服务器被禁用并记录原因。

调用关系:Config::apply_plugin_mcp_server_requirements 会先用它过滤插件服务器。

调用图:被 1 处调用(apply_plugin_mcp_server_requirements)。

constrain_mcp_servers1847–1860 ↗
fn constrain_mcp_servers(
    mcp_servers: HashMap<String, McpServerConfig>,
    mcp_requirements: Option<&Sourced<BTreeMap<String, McpServerRequirement>>>,
) -> ConstraintResult<Constrained<HashMap<S

作用:把 MCP 服务器表包成带约束的值。带约束的值会在以后设置时自动套用规则。

数据流:进去服务器表和可选要求 → 没要求就 allow_any,有要求就创建 normalized 包装并在内部过滤 → 出来 Constrained 服务器表。

调用关系:Config::load_config_with_layer_stack 在最终保存 mcp_servers 前调用它。

调用图:调用 2 个内部函数(allow_any, normalized);被 1 处调用(load_config_with_layer_stack)。

apply_requirement_constrained_value1862–1896 ↗
fn apply_requirement_constrained_value(
    field_name: &'static str,
    configured_value: T,
    constrained_value: &mut ConstrainedWithSource<T>,
    startup_warnings: &mut Vec<String>,
) -> std::i

作用:把一个配置值写进受要求限制的字段。如果不被允许,它会退回要求允许的值并记录启动警告。

数据流:进去字段名、配置值、受约束值和警告列表 → 尝试 set,失败则取 fallback、写日志和警告,再设置 fallback → 出来是否发生过回退。

调用关系:Config::load_config_with_layer_stack 多次调用它处理审批、权限、Web 搜索、Windows 沙箱等字段。

调用图:被 1 处调用(load_config_with_layer_stack);外部调用 4 个(get, set, format!, warn!)。

mcp_server_matches_requirement1898–1916 ↗
fn mcp_server_matches_requirement(
    requirement: &McpServerRequirement,
    server: &McpServerConfig,
) -> bool

作用:判断一个 MCP 服务器是否符合要求里的身份。身份可以是启动命令,也可以是 HTTP URL。

数据流:进去 McpServerRequirement 和 McpServerConfig → 比较 stdio command 或 streamable HTTP url → 出来 true 或 false。

调用关系:两个 MCP 过滤函数会用它做具体匹配判断。

调用图:外部调用 1 个(matches!)。

load_global_mcp_servers1918–1952 ↗
async fn load_global_mcp_servers(
    codex_home: &Path,
) -> std::io::Result<BTreeMap<String, McpServerConfig>>

作用:只读取全局 MCP 服务器配置,用来支持 mcp add/remove 等命令。它故意不带项目上下文。

数据流:进去 codex_home → 加载无 cwd 的配置层,取 mcp_servers,检查不支持的 bearer_token,再反序列化 → 出来服务器表。

调用关系:MCP 增删命令和相关测试会调用它,内部用 ensure_no_inline_bearer_tokens。

调用图:调用 2 个内部函数(load_config_layers_state, ensure_no_inline_bearer_tokens);被 11 处调用(run_add, run_remove, add_and_remove_server_updates_global_config, add_cant_add_command_and_url, add_streamable_http_rejects_removed_flag, add_streamable_http_with_custom_env_var, add_streamable_http_with_oauth_options, add_streamable_http_without_manual_token, add_with_env_preserves_key_order_and_values, get_disabled_server_shows_single_line (+1 more));外部调用 3 个(new, new, default)。

ensure_no_inline_bearer_tokens1956–1973 ↗
fn ensure_no_inline_bearer_tokens(value: &TomlValue) -> std::io::Result<()>

作用:拒绝 MCP 配置里直接写明文 bearer_token。它要求用户改用环境变量,避免秘密泄漏。

数据流:进去 TOML 值 → 如果是服务器表,就检查每个服务器是否有 bearer_token 字段 → 有则返回错误,否则成功。

调用关系:load_global_mcp_servers 在解析全局服务器前调用它。

调用图:被 1 处调用(load_global_mcp_servers);外部调用 3 个(as_table, new, format!)。

set_project_trust_level_inner1975–2042 ↗
fn set_project_trust_level_inner(
    doc: &mut DocumentMut,
    project_path: &Path,
    trust_level: TrustLevel,
) -> anyhow::Result<()>

作用:在 TOML 文档里设置某个项目的信任级别。信任级别会影响默认审批和权限选择。

数据流:进去可编辑 TOML 文档、项目路径和信任级别 → 确保 projects 表结构是人类友好的普通表,再写入 trust_level → 文档被原地修改。

调用关系:配置编辑 apply 流程会调用它做实际文档修改。

调用图:调用 1 个内部函数(project_trust_key);被 1 处调用(apply);外部调用 7 个(as_table_mut, anyhow!, to_string, Table, new, table, value)。

set_project_trust_level2046–2056 ↗
fn set_project_trust_level(
    codex_home: &Path,
    project_path: &Path,
    trust_level: TrustLevel,
) -> anyhow::Result<()>

作用:把某项目的信任级别写入 CODEX_HOME/config.toml。它是对编辑器流程的便捷包装。

数据流:进去 codex_home、项目路径和信任级别 → 创建 ConfigEditsBuilder,添加设置信任级别操作并阻塞应用 → 出来成功或 anyhow 错误。

调用关系:线程启动、配置读取、插件/MCP/权限列表等流程会用它记录项目是否可信。

调用图:调用 1 个内部函数(new);被 10 处调用(thread_start_task, config_read_includes_project_layers_for_cwd, hooks_list_uses_each_cwds_effective_feature_enablement, hooks_list_uses_root_repo_hooks_for_linked_worktrees, mcp_server_status_list_uses_thread_project_local_config, permission_profile_list_discovers_project_profiles_without_default_selection, permission_profile_list_resolves_project_profiles_and_paginates, plugin_list_uses_home_config_for_enabled_state, thread_start_respects_project_config_from_cwd, thread_start_skips_trust_write_when_project_is_already_trusted)。

set_default_oss_provider2059–2072 ↗
fn set_default_oss_provider(codex_home: &Path, provider: &str) -> std::io::Result<()>

作用:保存默认开源模型提供商偏好。写入前会先验证提供商名称合法。

数据流:进去 codex_home 和 provider 字符串 → 验证 provider,构造设置 oss_provider 的编辑并写 config.toml → 出来 IO 结果。

调用关系:本地/开源模型选择流程会调用它持久化用户选择。

调用图:调用 2 个内部函数(validate_oss_provider, new);外部调用 1 个(vec!)。

resolve_tool_suggest_config2091–2096 ↗
fn resolve_tool_suggest_config(
    config_toml: &ConfigToml,
    config_layer_stack: &ConfigLayerStack,
) -> ToolSuggestConfig

作用:解析工具建议配置。工具建议就是界面或模型可以发现、隐藏某些工具的设置。

数据流:进去 ConfigToml 和配置层堆栈 → 取 cfg.tool_suggest,再交给通用解析函数 → 出来 ToolSuggestConfig。

调用关系:Config::load_config_with_layer_stack 在生成总 Config 时调用它。

调用图:调用 1 个内部函数(resolve_tool_suggest_config_from_config);被 1 处调用(load_config_with_layer_stack)。

resolve_tool_suggest_config_from_layer_stack2098–2107 ↗
fn resolve_tool_suggest_config_from_layer_stack(
    config_layer_stack: &ConfigLayerStack,
) -> ToolSuggestConfig

作用:只从配置层堆栈解析工具建议配置。适合运行中刷新某一层配置后重新计算。

数据流:进去 ConfigLayerStack → 从 effective_config 里取 tool_suggest 并反序列化,再走通用解析 → 出来 ToolSuggestConfig。

调用关系:refresh_runtime_config 和 reload_user_config_layer 会用它热更新工具建议。

调用图:调用 2 个内部函数(effective_config, resolve_tool_suggest_config_from_config);被 2 处调用(refresh_runtime_config, reload_user_config_layer)。

resolve_tool_suggest_config_from_config2109–2169 ↗
fn resolve_tool_suggest_config_from_config(
    tool_suggest: Option<&ToolSuggestConfig>,
    config_layer_stack: &ConfigLayerStack,
) -> ToolSuggestConfig

作用:工具建议解析的核心逻辑。它会清理空 ID,并按配置层顺序合并禁用工具且去重。

数据流:进去可选 ToolSuggestConfig 和层堆栈 → 过滤 discoverables,逐层收集 disabled_tools,规范化并去重 → 出来干净的 ToolSuggestConfig。

调用关系:resolve_tool_suggest_config 和 resolve_tool_suggest_config_from_layer_stack 都委托它。

调用图:调用 1 个内部函数(get_layers);被 2 处调用(resolve_tool_suggest_config, resolve_tool_suggest_config_from_layer_stack);外部调用 2 个(new, new)。

thread_store_config2171–2177 ↗
fn thread_store_config(thread_store: Option<ThreadStoreToml>) -> ThreadStoreConfig

作用:把 TOML 里的线程存储设置转成运行时枚举。线程存储决定会话记录存在本地还是内存里。

数据流:进去可选 ThreadStoreToml → Local 变 Local,InMemory 带 id,None 默认 Local → 出来 ThreadStoreConfig。

调用关系:Config::load_config_with_layer_stack 用它填 experimental_thread_store。

调用图:被 1 处调用(load_config_with_layer_stack)。

is_session_layer2179–2181 ↗
fn is_session_layer(source: &ConfigLayerSource) -> bool

作用:判断一个配置层是不是会话临时参数层。会话层需要在刷新配置时特殊保留。

数据流:进去 ConfigLayerSource → 判断是否等于 SessionFlags → 出来布尔值。

调用关系:Config::rebuild_preserving_session_layers 用它筛选旧配置和新配置的层。

调用图:外部调用 1 个(matches!)。

EffectivePermissionSelection::has_profiles2206–2210 ↗
fn has_profiles(&self) -> bool

作用:判断当前是否有可用的命名权限档案。空表不算真正有 profiles。

数据流:进去 EffectivePermissionSelection → 查看 profiles 是否存在且不为空 → 出来 true 或 false。

调用关系:load_config_with_layer_stack 用它决定缺少 default_permissions 时是否报错。

EffectivePermissionSelection::profiles_are_active2212–2224 ↗
fn profiles_are_active(
        &self,
        default_permissions_override: Option<&str>,
        permission_config_syntax: Option<PermissionConfigSyntax>,
    ) -> bool

作用:判断命名权限档案模式是否应该生效。它会考虑管理员要求、命令行覆盖和配置语法。

数据流:进去 default_permissions 覆盖和解析出的权限语法 → 任一条件说明 profiles 应生效就返回 true → 出来布尔值。

调用关系:Config::load_config_with_layer_stack 用它决定走 profiles 逻辑还是旧 sandbox_mode 逻辑。

调用图:外部调用 1 个(matches!)。

resolve_permission_config_syntax2227–2281 ↗
fn resolve_permission_config_syntax(
    config_layer_stack: &ConfigLayerStack,
    cfg: &ConfigToml,
    sandbox_mode_override: Option<SandboxMode>,
) -> Option<PermissionConfigSyntax>

作用:判断用户正在用旧 sandbox_mode 语法还是新 permissions profiles 语法。两种语法不能混着糊涂用。

数据流:进去配置层堆栈、ConfigToml 和 sandbox override → 按最高优先级会话层、各层顺序、最终 cfg 判断 → 出来 Legacy、Profiles 或 None。

调用关系:Config::load_config_with_layer_stack 在解析权限前调用它决定分支。

调用图:调用 1 个内部函数(get_layers);被 1 处调用(load_config_with_layer_stack)。

apply_managed_filesystem_constraints2283–2312 ↗
fn apply_managed_filesystem_constraints(
    file_system_sandbox_policy: &mut FileSystemSandboxPolicy,
    filesystem_constraints: &codex_config::FilesystemConstraints,
)

作用:把托管文件系统限制加进沙箱策略。主要是追加禁止读取的路径或通配规则。

数据流:进去可变 FileSystemSandboxPolicy 和 filesystem constraints → 把 deny_read 转成沙箱条目,避免重复后追加 → 策略被原地收紧。

调用关系:Config::load_config_with_layer_stack 在最终权限档案成形前调用它。

调用图:调用 1 个内部函数(try_from);被 1 处调用(load_config_with_layer_stack)。

dedupe_absolute_paths2346–2349 ↗
fn dedupe_absolute_paths(paths: &mut Vec<AbsolutePathBuf>)

作用:给绝对路径列表去重,同时保留原顺序。这样同一个工作区不会出现多次。

数据流:进去可变路径 Vec → 用 HashSet 记录见过的路径并 retain → 原列表变成去重版本。

调用关系:effective_workspace_roots 和 load_config_with_layer_stack 都用它清理工作区根目录。

调用图:被 2 处调用(effective_workspace_roots, load_config_with_layer_stack);外部调用 1 个(new)。

resolve_oss_provider2353–2363 ↗
fn resolve_oss_provider(
    explicit_provider: Option<&str>,
    config_toml: &ConfigToml,
) -> Option<String>

作用:决定使用哪个开源模型提供商。命令行显式指定会覆盖全局配置。

数据流:进去可选显式 provider 和 ConfigToml → 有显式值就返回它,否则返回 config_toml.oss_provider → 出来可选字符串。

调用关系:run_main 和归档 app-server 启动会用它选择本地提供商。

调用图:被 3 处调用(run_main, run_main, start_app_server_for_archive_command)。

resolve_web_search_mode2366–2377 ↗
fn resolve_web_search_mode(config_toml: &ConfigToml, features: &Features) -> Option<WebSearchMode>

作用:从配置和功能开关决定 Web 搜索模式。Web 搜索可以是实时、缓存或关闭。

数据流:进去 ConfigToml 和 Features → 配置里写了就优先用,否则看 WebSearchCached/WebSearchRequest 开关 → 出来可选 WebSearchMode。

调用关系:Config::load_config_with_layer_stack 用它生成受约束的 web_search_mode。

调用图:调用 1 个内部函数(enabled);被 1 处调用(load_config_with_layer_stack)。

resolve_web_search_config2379–2386 ↗
fn resolve_web_search_config(config_toml: &ConfigToml) -> Option<WebSearchConfig>

作用:读取 Web 搜索工具的附加配置。比如工具参数之类的细节。

数据流:进去 ConfigToml → 沿 tools.web_search 找配置并转换 → 出来 WebSearchConfig 或 None。

调用关系:总配置加载时填 Config.web_search_config。

调用图:被 1 处调用(load_config_with_layer_stack)。

resolve_experimental_request_user_input_enabled2388–2394 ↗
fn resolve_experimental_request_user_input_enabled(config_toml: &ConfigToml) -> bool

作用:判断实验性的 request_user_input 工具是否启用。没写配置时默认启用。

数据流:进去 ConfigToml → 查 tools.experimental_request_user_input.enabled,缺省视为 true → 出来布尔值。

调用关系:Config::load_config_with_layer_stack 用它决定是否注册该实验工具。

调用图:被 1 处调用(load_config_with_layer_stack)。

resolve_code_mode_config2396–2405 ↗
fn resolve_code_mode_config(config_toml: &ConfigToml) -> CodeModeConfig

作用:解析实验性 code mode 的配置。它目前主要关心要排除哪些工具命名空间。

数据流:进去 ConfigToml → 从 features.code_mode 中取配置型开关,提取 excluded_tool_namespaces 或默认空列表 → 出来 CodeModeConfig。

调用关系:总配置加载时调用,内部依赖 code_mode_toml_config。

调用图:调用 1 个内部函数(code_mode_toml_config);被 1 处调用(load_config_with_layer_stack)。

resolve_multi_agent_v2_config2407–2462 ↗
fn resolve_multi_agent_v2_config(config_toml: &ConfigToml) -> MultiAgentV2Config

作用:解析多代理 V2 配置,并用默认值补齐缺项。它还处理空字符串表示关闭某些提示文本。

数据流:进去 ConfigToml → 取 features.multi_agent_v2 配置,先建默认值,再逐项覆盖等待时间、提示、命名空间和展示选项 → 出来 MultiAgentV2Config。

调用关系:Config::load_config_with_layer_stack 调用它,后续还会做合法性校验。

调用图:调用 3 个内部函数(defaults_for_max_concurrency, multi_agent_v2_toml_config, resolve_optional_prompt_text);被 1 处调用(load_config_with_layer_stack)。

resolve_terminal_resize_reflow_config2464–2476 ↗
fn resolve_terminal_resize_reflow_config(config_toml: &ConfigToml) -> TerminalResizeReflowConfig

作用:解析终端窗口大小变化时的重排限制。0 表示不限制保留行,缺省表示自动。

数据流:进去 ConfigToml → 如果没有 tui 配置就默认;否则把 terminal_resize_reflow_max_rows 映射成 Auto、Disabled 或 Limit → 出来 TerminalResizeReflowConfig。

调用关系:总配置加载时用它填 TUI 重排配置。

调用图:被 1 处调用(load_config_with_layer_stack);外部调用 2 个(default, Limit)。

resolve_optional_prompt_text2478–2487 ↗
fn resolve_optional_prompt_text(
    configured: Option<&Option<String>>,
    default: Option<String>,
) -> Option<String>

作用:解析可选提示文本。它区分“没配置”“配置为空字符串”和“配置了内容”。

数据流:进去配置值和默认值 → 空字符串变 None,有内容用内容,没配置用默认 → 出来可选字符串。

调用关系:resolve_multi_agent_v2_config 用它处理 root/subagent 的提示文本。

调用图:被 1 处调用(resolve_multi_agent_v2_config)。

code_mode_toml_config2489–2494 ↗
fn code_mode_toml_config(features: Option<&FeaturesToml>) -> Option<&CodeModeConfigToml>

作用:从 features 表里取 code_mode 的详细配置。单纯 enabled 不带详细配置时返回 None。

数据流:进去可选 FeaturesToml → 找 code_mode,如果是 Config 就返回引用,如果是 Enabled 或缺失就 None → 出来可选配置引用。

调用关系:resolve_code_mode_config 调用它。

调用图:被 1 处调用(resolve_code_mode_config)。

multi_agent_v2_toml_config2496–2501 ↗
fn multi_agent_v2_toml_config(features: Option<&FeaturesToml>) -> Option<&MultiAgentV2ConfigToml>

作用:从 features 表里取 multi_agent_v2 的详细配置。它只关心配置型开关。

数据流:进去可选 FeaturesToml → 找 multi_agent_v2,Config 返回配置引用,Enabled/缺失返回 None → 出来可选配置引用。

调用关系:resolve_multi_agent_v2_config 调用它。

调用图:被 1 处调用(resolve_multi_agent_v2_config)。

network_proxy_toml_config2503–2508 ↗
fn network_proxy_toml_config(features: Option<&FeaturesToml>) -> Option<&NetworkProxyConfigToml>

作用:从 features 表里取 network_proxy 的详细配置。它用于进一步调整托管网络代理。

数据流:进去可选 FeaturesToml → 如果 network_proxy 是 Config 就返回配置,否则 None → 出来可选配置引用。

调用关系:Config::load_config_with_layer_stack 和 network_proxy_spec_for_active_permission_profile 用它。

调用图:被 2 处调用(load_config_with_layer_stack, network_proxy_spec_for_active_permission_profile)。

resolve_web_search_mode_for_turn2510–2544 ↗
fn resolve_web_search_mode_for_turn(
    web_search_mode: &Constrained<WebSearchMode>,
    permission_profile: &PermissionProfile,
) -> WebSearchMode

作用:为某一轮对话决定真正使用的 Web 搜索模式。它会避开当前权限不允许的模式。

数据流:进去受约束的 WebSearchMode 和权限档案 → 先看偏好值,权限禁用时优先降级,再按允许列表寻找可用模式 → 出来最终模式,最坏关闭。

调用关系:每轮请求准备工具时会用它把配置偏好和权限约束对齐。

调用图:调用 2 个内部函数(can_set, value);外部调用 1 个(matches!)。

validate_multi_agent_v2_wait_timeout2546–2560 ↗
fn validate_multi_agent_v2_wait_timeout(label: &str, value: i64) -> std::io::Result<()>

作用:检查多代理 V2 等待超时时间是否在硬性范围内。太小或太大都直接报错。

数据流:进去字段名和毫秒值 → 比较硬最小值和硬最大值 → 出来成功或 InvalidInput 错误。

调用关系:Config::load_config_with_layer_stack 校验 multi_agent_v2 三个 timeout 时调用它。

调用图:被 1 处调用(load_config_with_layer_stack);外部调用 2 个(new, format!)。

validate_multi_agent_v2_tool_namespace2562–2623 ↗
fn validate_multi_agent_v2_tool_namespace(namespace: Option<&str>) -> std::io::Result<()>

作用:检查多代理工具命名空间是否合法。命名空间就是工具名前面的分组名。

数据流:进去可选 namespace → None 通过;有值则检查非空、无首尾空白、字符集、长度和保留名 → 出来成功或错误。

调用关系:Config::load_config_with_layer_stack 在接受 multi_agent_v2 配置前调用它。

调用图:被 1 处调用(load_config_with_layer_stack);外部调用 2 个(new, format!)。

Config::load_from_base_config_with_overrides2627–2642 ↗
async fn load_from_base_config_with_overrides(
        cfg: ConfigToml,
        overrides: ConfigOverrides,
        codex_home: AbsolutePathBuf,
    ) -> std::io::Result<Self>

作用:测试专用:从给定 ConfigToml 和覆盖项构造 Config。它故意忽略 requirements.toml 强制规则。

数据流:进去基础 cfg、overrides 和 codex_home → 创建默认 ConfigLayerStack,再调用 load_config_with_layer_stack → 出来 Config。

调用关系:test_config 和多种配置单元测试会用它。

调用图:外部调用 2 个(load_config_with_layer_stack, default)。

Config::load_config_with_layer_stack2644–3730 ↗
async fn load_config_with_layer_stack(
        fs: &dyn ExecutorFileSystem,
        cfg: ConfigToml,
        overrides: ConfigOverrides,
        codex_home: AbsolutePathBuf,
        config_layer_stack

作用:这是本文件最核心的装配函数。它把已经合并的 ConfigToml、运行时覆盖项、Codex home 和配置层堆栈,变成最终 Config。

数据流:进去文件系统接口、ConfigToml、覆盖项、codex_home 和 ConfigLayerStack → 校验废弃字段和模型提供商,解析功能开关、权限、沙箱、网络代理、MCP、模型、提示文件、路径、TUI、代理配置,并应用托管要求 → 出来完整 Config 或明确错误。

调用关系:ConfigBuilder::build_inner、默认加载、测试加载、刷新配置都会交给它;它又调用本文件大量 resolve/validate/helper 函数,是配置系统的中枢。

调用图:调用 47 个内部函数(derive_permission_profile, get_active_project, validate_model_providers, requirements, requirements_toml, startup_warnings, default, load_agent_roles, apply_managed_filesystem_constraints, apply_requirement_constrained_value (+15 more));外部调用 19 个(pin, default, try_read_non_empty_file, new, new, sandbox_mode_requirement_for_permission_profile, resolve_root_git_project_for_trust, memory_root, built_in_model_providers, merge_configured_model_providers (+9 more))。

Config::try_read_non_empty_file3735–3764 ↗
async fn try_read_non_empty_file(
        fs: &dyn ExecutorFileSystem,
        path: Option<&AbsolutePathBuf>,
        context: &str,
    ) -> std::io::Result<Option<String>>

作用:读取一个可选文本文件,并要求内容非空。它常用于从文件加载提示词。

数据流:进去文件系统、可选绝对路径和上下文说明 → 没路径返回 None,有路径就读文本、trim,空内容报错 → 出来 Some 文本或错误。

调用关系:Config::load_config_with_layer_stack 用它读取 model instructions 和 compact prompt 文件。

调用图:调用 2 个内部函数(read_file_text, from_abs_path);外部调用 2 个(new, format!)。

Config::set_windows_sandbox_enabled3766–3777 ↗
fn set_windows_sandbox_enabled(&mut self, value: bool)

作用:打开或关闭 Windows 非提升沙箱。非提升沙箱就是用较低权限运行子进程。

数据流:进去布尔值 → true 设置为 Unelevated;false 只在当前正是 Unelevated 时清掉 → 更新 permissions.windows_sandbox_mode。

调用关系:运行时 UI 或控制接口切换 Windows 沙箱时可调用它。

调用图:外部调用 1 个(matches!)。

Config::set_windows_elevated_sandbox_enabled3779–3790 ↗
fn set_windows_elevated_sandbox_enabled(&mut self, value: bool)

作用:打开或关闭 Windows 提升沙箱模式。它只会清掉自己设置的 Elevated 状态,不误删其他模式。

数据流:进去布尔值 → true 设置 Elevated;false 且当前是 Elevated 时设 None,否则保持 → 更新权限里的 Windows 沙箱模式。

调用关系:Windows 沙箱相关控制路径会用它。

调用图:外部调用 1 个(matches!)。

Config::managed_network_requirements_enabled3792–3801 ↗
fn managed_network_requirements_enabled(&self) -> bool

作用:判断当前是否有托管网络要求正在生效。权限完全禁用时不算启用。

数据流:进去 Config → 检查权限档案不是 Disabled,且 requirements_toml 里有 network 要求 → 出来布尔值。

调用关系:界面或执行层可用它决定是否展示/使用托管网络限制。

调用图:调用 1 个内部函数(requirements_toml);外部调用 1 个(matches!)。

Config::network_proxy_spec_for_active_permission_profile3803–3848 ↗
fn network_proxy_spec_for_active_permission_profile(
        &self,
        active_permission_profile: &ActivePermissionProfile,
        permission_profile: &PermissionProfile,
    ) -> std::io::Resul

作用:为某个被选中的权限档案重新计算网络代理规格。切换权限时网络代理也要跟着变。

数据流:进去 active profile 和 PermissionProfile → 如果档案允许代理,就从有效配置重读该 profile 的代理设置并套功能配置,再合并托管网络要求 → 出来可选 NetworkProxySpec。

调用关系:权限档案切换流程会用它,内部复用 profile_allows_configured_network_proxy、network_proxy_toml_config 和 build_network_proxy_spec。

调用图:调用 8 个内部函数(effective_config, requirements, build_network_proxy_spec, network_proxy_toml_config, apply_network_proxy_feature_config, network_proxy_config_for_profile_selection, profile_allows_configured_network_proxy, network_sandbox_policy);外部调用 2 个(enabled, default)。

Config::bundled_skills_enabled3850–3852 ↗
fn bundled_skills_enabled(&self) -> bool

作用:判断内置 skills 是否启用。skills 可以理解为随程序打包的能力说明或工具包。

数据流:进去 Config → 把 config_layer_stack 交给 manager 的判断函数 → 出来布尔值。

调用关系:技能管理相关流程会通过它读取最终开关。

调用图:调用 1 个内部函数(bundled_skills_enabled_from_stack)。

guardian_policy_config_from_requirements3855–3859 ↗
fn guardian_policy_config_from_requirements(
    requirements_toml: &ConfigRequirementsToml,
) -> Option<String>

作用:从托管要求里取 Guardian 策略配置。Guardian 是审查/安全判断相关的策略层。

数据流:进去 ConfigRequirementsToml → 取 guardian_policy_config 字符串并规范化空白 → 出来可选策略文本。

调用关系:Config::load_config_with_layer_stack 在准备 guardian_policy_config 时调用它。

调用图:调用 1 个内部函数(normalize_guardian_policy_config);被 1 处调用(load_config_with_layer_stack)。

merge_managed_permission_profiles3861–3890 ↗
fn merge_managed_permission_profiles(
    configured_permissions: Option<&PermissionsToml>,
    requirements_toml: &ConfigRequirementsToml,
) -> std::io::Result<Option<PermissionsToml>>

作用:把管理员下发的权限档案合并进用户配置。若同名冲突就报错,避免谁覆盖谁不清楚。

数据流:进去用户 permissions 和 requirements_toml → 没托管档案就返回用户档案,有则复制用户档案再插入托管档案 → 出来合并结果或冲突错误。

调用关系:resolve_effective_permission_selection 调用它作为权限选择解析的第一步。

调用图:被 1 处调用(resolve_effective_permission_selection);外部调用 2 个(new, format!)。

resolve_effective_permission_selection3892–3916 ↗
fn resolve_effective_permission_selection(
    configured_permissions: Option<&PermissionsToml>,
    default_permissions_override: Option<&'a str>,
    configured_default_permissions: Option<&'a str>,

作用:解析最终可用的权限档案目录和当前选中的档案名。它同时检查用户命名和管理员要求。

数据流:进去配置档案、命令行默认权限、配置默认权限、requirements 和警告列表 → 合并托管档案、验证名称和目录、解析默认选择 → 出来 EffectivePermissionSelection。

调用关系:Config::load_config_with_layer_stack 在权限大分支前调用它。

调用图:调用 4 个内部函数(merge_managed_permission_profiles, validate_user_permission_profile_names, resolve_default_permissions, validate_required_permission_profile_catalog);被 1 处调用(load_config_with_layer_stack)。

resolve_default_permissions3918–3954 ↗
fn resolve_default_permissions(
    default_permissions_override: Option<&'a str>,
    configured_default_permissions: Option<&'a str>,
    requirements_toml: &'a ConfigRequirementsToml,
    startup_w

作用:决定 default_permissions 最终是哪一个。管理员白名单存在时,不允许的选择会回退并写启动警告。

数据流:进去命令行选择、配置选择、requirements 和警告列表 → 若无白名单直接用选择;有白名单则找 fallback 并检查 selected 是否允许 → 出来可选档案名。

调用关系:resolve_effective_permission_selection 调用它。

调用图:调用 1 个内部函数(is_permission_allowed);被 1 处调用(resolve_effective_permission_selection);外部调用 2 个(new, format!)。

validate_required_permission_profile_catalog3956–4008 ↗
fn validate_required_permission_profile_catalog(
    requirements_toml: &ConfigRequirementsToml,
    available_permissions: Option<&PermissionsToml>,
) -> std::io::Result<()>

作用:校验 requirements.toml 里提到的权限档案都存在且默认值被允许。否则托管要求本身就是坏的。

数据流:进去 requirements_toml 和可用权限档案 → 检查 allowed_permission_profiles 引用是否已定义、default_permissions 是否合理 → 出来成功或错误。

调用关系:resolve_effective_permission_selection 在选档案前调用它。

调用图:调用 1 个内部函数(is_permission_allowed);被 1 处调用(resolve_effective_permission_selection);外部调用 2 个(new, format!)。

implicit_default_permissions4010–4016 ↗
fn implicit_default_permissions(
    allowed_permission_profiles: &BTreeMap<String, bool>,
) -> Option<&'static str>

作用:在允许列表同时允许工作区和只读时,给出隐式默认工作区权限。否则无法安全猜默认值。

数据流:进去 allowed_permission_profiles → 检查 :workspace 和 :read-only 是否都允许 → 出来 Some(':workspace') 或 None。

调用关系:resolve_default_permissions 和 validate_required_permission_profile_catalog 会用它补默认值。

调用图:调用 1 个内部函数(is_permission_allowed)。

is_permission_allowed4018–4026 ↗
fn is_permission_allowed(
    allowed_permission_profiles: &BTreeMap<String, bool>,
    profile_id: &str,
) -> bool

作用:检查某个权限档案 ID 是否在允许列表里且值为 true。

数据流:进去允许列表和 profile_id → 查表并把缺失当 false → 出来布尔值。

调用关系:implicit_default_permissions、resolve_default_permissions 和 validate_required_permission_profile_catalog 都用它做白名单判断。

调用图:被 3 处调用(implicit_default_permissions, resolve_default_permissions, validate_required_permission_profile_catalog)。

normalize_guardian_policy_config4028–4033 ↗
fn normalize_guardian_policy_config(value: Option<&str>) -> Option<String>

作用:清理 Guardian 策略文本。全是空白的配置会被当成没配置。

数据流:进去可选字符串引用 → trim 后如果非空就复制成 String,否则 None → 出来可选文本。

调用关系:guardian_policy_config_from_requirements 和 Config::load_config_with_layer_stack 的配置回退逻辑会用它。

调用图:被 1 处调用(guardian_policy_config_from_requirements)。

find_codex_home4043–4045 ↗
fn find_codex_home() -> std::io::Result<AbsolutePathBuf>

作用:找到 Codex 配置目录。可以由 CODEX_HOME 环境变量指定,否则默认 ~/.codex。

数据流:进去没有参数 → 调底层 home-dir 工具解析并校验环境变量目录 → 出来 AbsolutePathBuf 或 IO 错误。

调用关系:主程序、CLI、登录、配置编辑、profile 加载等许多启动路径都会先调用它。

调用图:被 21 处调用(from_listen_url, default_control_socket_path, run_main_with_transport_options, cli_main, disable_feature_in_config, fallback_state_check, enable_feature_in_config, loader_overrides_for_profile, run_add, run_remove (+11 more));外部调用 1 个(find_codex_home)。

log_dir4049–4051 ↗
fn log_dir(cfg: &Config) -> std::io::Result<PathBuf>

作用:返回当前配置指定的日志目录。它只读配置,不检查目录是否存在。

数据流:进去 Config → 克隆 cfg.log_dir → 出来 PathBuf。

调用关系:init_login_file_logging 用它决定登录相关日志写到哪里。

调用图:被 1 处调用(init_login_file_logging)。

exec-server/src/runtime_paths.rs源码 ↗
data_modelstartup / child process setup

exec-server 有时需要再启动 Codex 自己,或者在 Linux 沙箱里通过一个特殊别名重新进入 Codex。要做到这一点,它必须拿到可靠的程序路径。这个文件就像一张“启动用地址卡”:ExecServerRuntimePaths 里面放着 Codex 主程序的绝对路径,以及可选的 Linux 沙箱助手别名路径。这里特别强调“绝对路径”,也就是从磁盘根目录开始写清楚的位置,避免当前目录一变,程序就找错地方。创建这张地址卡时,如果 Codex 主程序路径没传进来,会直接报错;如果传进来了但不是合法绝对路径,也会报错。这样问题会在启动或配置阶段尽早暴露,而不是等到真正拉起子进程时才失败。

函数细节3
ExecServerRuntimePaths::from_optional_paths16–27 ↗
fn from_optional_paths(
        codex_self_exe: Option<PathBuf>,
        codex_linux_sandbox_exe: Option<PathBuf>,
    ) -> std::io::Result<Self>

作用:这个函数用来从“可能有、也可能没有”的路径配置里生成运行时路径。它会强制要求 Codex 主程序路径必须存在,因为没有它就没法可靠地启动后续助手进程。

数据流:进去的是两个可选路径:Codex 自己的可执行文件路径,以及可选的 Linux 沙箱别名路径。它先检查第一个路径有没有;如果没有,就产出一个“输入无效”的错误。若有,就把这两个值交给 ExecServerRuntimePaths::new 继续检查并整理,最后出来的是一份可用的 ExecServerRuntimePaths,或者一个说明哪里不对的错误。

调用关系:它通常被更上层的启动流程调用,比如 run_main、run_main_with_transport_options、build_prompt_input 以及启动归档命令的流程。这些地方拿到外部传入或配置出来的路径后,会先通过它做一道入口检查;它自己不做全部细节,而是把真正的路径规范化工作交给 ExecServerRuntimePaths::new。

调用图:被 8 处调用(run_main_with_transport_options, list_accessible_connectors_from_mcp_tools_with_options_and_status, build_prompt_input, run_main, run_main, run_main, run_main, start_app_server_for_archive_command);外部调用 1 个(new)。

ExecServerRuntimePaths::new29–37 ↗
fn new(
        codex_self_exe: PathBuf,
        codex_linux_sandbox_exe: Option<PathBuf>,
    ) -> std::io::Result<Self>

作用:这个函数是正式创建 ExecServerRuntimePaths 的地方。它把传入的路径转换成经过验证的绝对路径,确保后面用这些路径启动程序时不会受当前目录影响。

数据流:进去的是一个必填的 Codex 可执行文件路径,以及一个可选的 Linux 沙箱别名路径。它会分别调用 absolute_path 检查这些路径是不是合格的绝对路径;可选路径如果没有就保持为空,如果有也必须通过检查。出来的是一个包含干净路径的 ExecServerRuntimePaths;如果任一路径不合格,就返回输入错误。

调用关系:它既会被 ExecServerRuntimePaths::from_optional_paths 调用,也会被很多已经拿到确定路径的代码直接调用,比如运行 exec-server 命令、构建测试环境、权限相关测试等。它是这份“运行时地址卡”的核心装配点,具体的单个路径检查则交给 absolute_path。

调用图:调用 1 个内部函数(absolute_path);被 27 处调用(runtime_start_args_forward_environment_manager, run_exec_server_command, test_runtime_paths, build_with_home_and_base_url, test_runtime_paths, helper_permissions_include_helper_read_root_without_additional_permissions, helper_permissions_include_linux_sandbox_alias_parent, helper_permissions_preserve_existing_writes, sandbox_exec_request_carries_helper_env, processor_exit_reports_closed_virtual_stream (+15 more))。

absolute_path40–43 ↗
fn absolute_path(path: PathBuf) -> std::io::Result<AbsolutePathBuf>

作用:这个小函数专门检查并转换一个路径,确认它是绝对路径。它把底层路径库给出的错误包装成普通的输入错误,方便上层统一处理。

数据流:进去的是一个 PathBuf,也就是一段文件路径。它先把路径借出来给 AbsolutePathBuf::from_absolute_path 检查;如果路径确实是绝对路径,就出来一个 AbsolutePathBuf;如果不是或不合法,就出来一个 std::io::Error,错误类型表示“传入参数不对”。它不修改外部状态。

调用关系:它只被 ExecServerRuntimePaths::new 调用,是创建运行时路径时的基础检查工具。new 负责决定要检查哪些路径,absolute_path 负责把每个单独路径变成安全可靠的绝对路径。

调用图:调用 1 个内部函数(from_absolute_path);被 1 处调用(new);外部调用 1 个(as_path)。

tools/src/tool_config.rs源码 ↗
configconfig load / session setup

这个文件像一个“工具模式分流器”。项目里有很多功能开关,比如是否允许 shell 工具、是否使用统一执行方式、是否用 zsh 的特殊拦截方案。模型自己也可能声明想用哪种 shell。这个文件把这些信息合在一起,最后给出安全、可用的选择:比如用传统 shell 命令、用统一执行,还是彻底禁用。它还会看运行时条件,例如当前平台是否支持伪终端能力,以及用户是不是 zsh。几个枚举类型就是清晰的选项牌:ShellCommandBackendConfig 表示传统后端还是 zsh-fork 后端;UnifiedExecFeatureMode 表示统一执行在功能开关层面是否可用;UnifiedExecShellMode 表示一次会话里实际怎么跑;ToolEnvironmentMode 则把工具环境数量归成没有、一个、多个。重点是它不会因为一个组合开关打开,就偷偷打开另一个本该关闭的能力,这对企业配置和安全边界很重要。

函数细节7
request_user_input_available_modes38–47 ↗
fn request_user_input_available_modes(features: &Features) -> Vec<ModeKind>

作用:这个函数决定哪些协作模式可以向用户要输入。它把固定可见的模式列表过滤一遍,只留下本来允许请求用户输入的模式,或者在功能开关允许时把默认模式也放进来。

数据流:进去的是一组功能开关 Features,以及文件里引用的可见协作模式列表。函数逐个检查模式:如果模式本身允许请求用户输入,就保留;如果打开了 DefaultModeRequestUserInput,并且这个模式是 Default,也保留。出来的是一个模式列表,供后面展示或选择。

调用关系:它处在界面和配置之间,像给菜单做筛选的人。调用图里没有显示它再调用本文件的其他函数,它主要依赖模式自身的判断和功能开关来给外部提供可用选项。

shell_command_backend_for_features49–55 ↗
fn shell_command_backend_for_features(features: &Features) -> ShellCommandBackendConfig

作用:这个函数判断普通 shell 命令应该走哪种后端。简单说,如果 shell 工具和 zsh-fork 这两个开关都开了,就用 zsh-fork;否则用经典方式。

数据流:进去的是 Features,也就是当前启用的功能开关。它读取 ShellTool 和 ShellZshFork 两个开关:两者都开,输出 ShellCommandBackendConfig::ZshFork;只要有一个没开,输出 ShellCommandBackendConfig::Classic。它不改动任何外部状态。

调用关系:它被 shell_type_for_model_and_features 调用,是后者做最终 shell 类型选择时的一块小拼图。它自己只调用 enabled 来问“某个功能有没有打开”。

调用图:调用 1 个内部函数(enabled);被 1 处调用(shell_type_for_model_and_features)。

unified_exec_feature_mode_for_features67–79 ↗
fn unified_exec_feature_mode_for_features(features: &Features) -> UnifiedExecFeatureMode

作用:这个函数先只看功能开关,判断“统一执行”这条路在政策上能不能走,以及要不要走 zsh-fork 变体。它还特意防止一个组合开关偷偷启用底层功能。

数据流:进去的是 Features。函数先检查 ShellTool 和 UnifiedExec:只要 shell 工具没开,或统一执行没开,就输出 Disabled。然后看 ShellZshFork:如果 zsh-fork 打开,还必须 UnifiedExecZshFork 也打开,才输出 ZshFork;否则仍然 Disabled。如果没有 zsh-fork 这层需求,就输出 Direct。

调用关系:它被 shell_type_for_model_and_features 调用,负责先给出“功能政策层面”的统一执行选择。它自己只通过 enabled 查询功能开关,不处理平台、用户 shell、路径这些运行时信息。

调用图:调用 1 个内部函数(enabled);被 1 处调用(shell_type_for_model_and_features)。

shell_type_for_model_and_features81–116 ↗
fn shell_type_for_model_and_features(
    model_info: &ModelInfo,
    features: &Features,
) -> ConfigShellToolType

作用:这个函数决定最终给模型使用哪种 shell 工具类型。它把模型偏好、功能开关、后端选择和系统能力合在一起,避免选出一个当前机器不能用或配置不允许的方式。

数据流:进去的是 ModelInfo 里的模型 shell 偏好,以及 Features 功能开关。它先问 unified_exec_feature_mode_for_features:统一执行是否允许;再把模型声明里的 Default、Local 等情况折成普通 ShellCommand。如果 shell 命令后端是 zsh-fork,它也会把类型压回 ShellCommand。接着如果 ShellTool 没开,直接输出 Disabled。若统一执行被允许,还会检查 conpty_supported,也就是当前系统是否支持需要的伪终端能力;支持就输出 UnifiedExec,不支持就退回 ShellCommand。

调用关系:它是本文件里最核心的总判断器。它调用 shell_command_backend_for_features 和 unified_exec_feature_mode_for_features,把两个较小判断合成最终结果;也调用外部的 conpty_supported 来确认系统能力。其他配置流程需要知道“到底启用哪种 shell 工具”时,就应该依赖它。

调用图:调用 3 个内部函数(enabled, shell_command_backend_for_features, unified_exec_feature_mode_for_features);外部调用 2 个(conpty_supported, matches!)。

UnifiedExecShellMode::for_session131–164 ↗
fn for_session(
        feature_mode: UnifiedExecFeatureMode,
        user_shell_type: ToolUserShellType,
        shell_zsh_path: Option<&PathBuf>,
        main_execve_wrapper_exe: Option<&PathBuf>,

作用:这个函数为一次具体会话决定统一执行该直接运行,还是走 zsh-fork 特殊模式。它不只看功能开关,还要看系统是不是 Unix、用户是不是 zsh、相关程序路径是不是有效。

数据流:进去的是功能层面的 UnifiedExecFeatureMode、用户 shell 类型、zsh 路径、execve 包装程序路径。函数按条件逐个过关:必须是 Unix,功能模式必须是 ZshFork,用户 shell 必须是 Zsh,两个路径都必须存在,并且都能转成绝对路径。全部满足时,输出 UnifiedExecShellMode::ZshFork,并带上两个绝对路径;任一条件不满足,就输出 Direct。路径转换失败时会写警告日志,但仍然安全退回 Direct。

调用关系:它在会话创建阶段使用,调用图显示会被 spawn_review_thread 和 make_turn_context 调用,测试 unified_exec_shell_mode_uses_zsh_fork_only_when_all_inputs_match 也会验证它的边界行为。它会调用路径转换 try_from,并在条件满足时构造 ZshFork 配置。

调用图:调用 1 个内部函数(try_from);被 3 处调用(spawn_review_thread, make_turn_context, unified_exec_shell_mode_uses_zsh_fork_only_when_all_inputs_match);外部调用 3 个(ZshFork, cfg!, matches!)。

ToolEnvironmentMode::from_count175–181 ↗
fn from_count(count: usize) -> Self

作用:这个函数把工具环境的数量翻译成更好理解的三种状态:没有、一个、多个。这样其他代码不用到处写数字判断。

数据流:进去的是一个数量 count。count 是 0 时输出 None;是 1 时输出 Single;大于 1 时输出 Multiple。它只做分类,不读取也不修改外部数据。

调用关系:它被 tool_environment_mode 调用,处在“把原始数量变成配置语义”的位置。可以把它理解成把人数 0、1、2+ 翻译成“没人、单人、多人”的小标签机。

调用图:被 1 处调用(tool_environment_mode)。

ToolEnvironmentMode::has_environment183–185 ↗
fn has_environment(self) -> bool

作用:这个函数回答一个简单问题:当前有没有可用的工具环境。它把 None 当成没有,Single 和 Multiple 都当成有。

数据流:进去的是一个 ToolEnvironmentMode 值。函数检查它是不是 None:如果不是 None,输出 true;如果是 None,输出 false。它不改变这个值,也不影响外部状态。

调用关系:它是给其他代码做判断用的小帮手,避免每个调用者都重复写“不是 None 就算有环境”。它内部只用 matches! 这样的模式匹配工具来完成判断。

调用图:外部调用 1 个(matches!)。

core/src/session/config_lock.rs源码 ↗
domain_logicsession setup / config lock validation / export

可以把配置锁文件想成一张“当时机器怎么调好的拍照记录”。用户写的配置可能经过默认值、模型目录、功能开关、会话初始化等步骤后才变成真正运行时的样子;这个文件做的事,就是把这份“真正用上的配置”整理出来,去掉不能稳定重放的输入,比如 profile、调试开关、文件路径、权限沙箱等,再保存或拿来比较。启动会话时,如果配置里带了旧的锁文件,它会生成一份当前实际配置并对比,发现漂移就报错;如果配置要求导出,它会把锁文件写到指定目录。里面还特别处理了功能开关、multi-agent v2、多线程限制、记忆配置等,确保锁住的是运行行为,而不只是用户原始 TOML 文件里的文字。

函数细节14
validate_config_lock_if_configured20–41 ↗
async fn validate_config_lock_if_configured(
    session_configuration: &SessionConfiguration,
) -> anyhow::Result<()>

作用:如果这次会话配置了“要按锁文件重放”,它就检查当前实际配置和锁文件是否一致。这样可以及时发现配置变了,避免用户以为自己在复现同一个环境,其实已经不是了。

数据流:进去的是一个 SessionConfiguration,也就是会话已经整理好的配置。它先跳过非根 agent,因为子 agent 不负责这件事;再看原始配置里有没有 config lock;有的话就把当前会话转成锁文件格式,再调用对比函数。出来是成功的空结果,或者带上下文信息的错误;它不改动会话本身。

调用关系:它通常在会话建立阶段被调用。它把“生成当前锁文件”的活交给 SessionConfiguration::to_config_lockfile_toml,再把“逐项比较”的活交给 validate_config_lock_replay。

调用图:调用 1 个内部函数(validate_config_lock_replay);外部调用 1 个(to_config_lockfile_toml)。

export_config_lock_if_configured43–69 ↗
async fn export_config_lock_if_configured(
    session_configuration: &SessionConfiguration,
    conversation_id: ThreadId,
) -> anyhow::Result<()>

作用:如果配置要求导出锁文件,它就把当前会话的配置锁写成一个 TOML 文件。TOML 是一种人能读的配置文件格式,类似带层级的键值表。

数据流:进去的是会话配置和会话编号 conversation_id。它读取导出目录;没有目录就什么也不做。有目录时,它把会话转成锁文件对象,再转成漂亮格式的 TOML 字符串,创建目录,最后写入名为“会话编号.config.lock.toml”的文件。出来是成功或文件创建、写入、序列化失败的错误。

调用关系:它在需要留档或调试复现时被调用。它依赖 SessionConfiguration::to_config_lockfile_toml 生成内容,再使用异步文件操作 create_dir_all 和 write 落盘。

调用图:外部调用 5 个(to_config_lockfile_toml, format!, create_dir_all, write, to_string_pretty)。

SessionConfiguration::to_config_lockfile_toml72–76 ↗
fn to_config_lockfile_toml(&self) -> anyhow::Result<ConfigLockfileToml>

作用:这是把一个会话配置变成完整锁文件对象的入口。别人不需要知道内部怎么清理、补全配置,只要调用它就能拿到可保存、可比较的锁文件结构。

数据流:进去的是当前 SessionConfiguration。它先调用 session_configuration_to_lock_config_toml 得到锁文件里的 config 部分,再用 config_lockfile 包上一层锁文件元信息,比如锁文件版本等。出来是 ConfigLockfileToml,失败时返回错误。

调用关系:validate_config_lock_if_configured 和 export_config_lock_if_configured 都会走到这里。它自己不做细活,而是把配置整理交给 session_configuration_to_lock_config_toml,把最后包装交给 config_lockfile。

调用图:调用 2 个内部函数(config_lockfile, session_configuration_to_lock_config_toml)。

session_configuration_to_lock_config_toml79–100 ↗
fn session_configuration_to_lock_config_toml(
    sc: &SessionConfiguration,
) -> anyhow::Result<ConfigToml>

作用:这个函数负责把“会话实际运行用的配置”整理成锁文件里的 config 内容。它是锁文件内容是否靠谱的核心。

数据流:进去的是 SessionConfiguration。它先从已经合并好的配置层栈中取出有效配置,转成 ConfigToml;如果配置允许,还会补上会话初始化后才知道的模型、提示词、审批策略等;接着补上 Config 上已经解析、默认化或物化后的字段;最后删掉不适合写进锁文件的输入项。出来是一份干净、可重放的 ConfigToml。

调用关系:它只被 SessionConfiguration::to_config_lockfile_toml 调用。它像流水线总工,依次安排 save_session_resolved_fields、save_config_resolved_fields 和 drop_lockfile_inputs 完成补全与清理。

调用图:调用 3 个内部函数(drop_lockfile_inputs, save_config_resolved_fields, save_session_resolved_fields);被 1 处调用(to_config_lockfile_toml)。

save_session_resolved_fields107–118 ↗
fn save_session_resolved_fields(sc: &SessionConfiguration, lock_config: &mut ConfigToml)

作用:它把会话创建过程中才确定下来的值写进锁配置,比如最终模型、提示词、服务等级和审批规则。没有它,锁文件可能只记录用户原始输入,却漏掉真正运行时的关键决定。

数据流:进去的是 SessionConfiguration 和一份可修改的 ConfigToml。它从会话里读取模型、推理强度、摘要设置、基础提示词、开发者提示词、压缩提示词、人格、审批策略等,然后填到 lock_config 对应字段里。出来没有新对象,但 lock_config 被补全了。

调用关系:它由 session_configuration_to_lock_config_toml 在允许保存模型目录解析字段时调用。它专门处理“会话层面才知道”的配置,和 save_config_resolved_fields 处理的 Config 层字段互补。

调用图:被 1 处调用(session_configuration_to_lock_config_toml)。

save_config_resolved_fields125–174 ↗
fn save_config_resolved_fields(
    config: &Config,
    lock_config: &mut ConfigToml,
) -> anyhow::Result<()>

作用:它把 Config 里已经解析好的最终值写进锁配置。重点是保存“程序实际按什么运行”,而不是只保存用户文件里最初写了什么。

数据流:进去的是 Config 和可修改的 ConfigToml。它写入网页搜索模式、模型供应商、计划模式推理强度、详细程度、是否加入权限说明、环境上下文、后台终端超时等;还把功能开关展开成明确状态,把 multi-agent v2 和 memories 通过 TOML 往返转换成锁文件能保存的形式;最后补上 agent 和 skills 的相关配置。出来是成功或转换失败;lock_config 会被大量补全。

调用关系:它由 session_configuration_to_lock_config_toml 调用,是锁文件“贴近真实运行行为”的关键步骤。遇到复杂配置对象时,它把转换工作交给 resolved_config_to_toml。

调用图:调用 1 个内部函数(resolved_config_to_toml);被 1 处调用(session_configuration_to_lock_config_toml);外部调用 1 个(Config)。

drop_lockfile_inputs176–191 ↗
fn drop_lockfile_inputs(lock_config: &mut ConfigToml)

作用:它负责从锁配置里删掉不该进入锁文件的“输入来源”。这些东西会影响当时怎么得出配置,但不适合用来重放最终配置。

数据流:进去的是一份可修改的 ConfigToml。它清空 profile 和 profiles,清掉配置锁相关调试控制,移除模型提示文件、压缩提示文件、模型目录 JSON、沙箱模式、权限输入等字段。出来没有新对象,但 lock_config 变得更稳定、更适合重放。

调用关系:它由 session_configuration_to_lock_config_toml 在最后调用,像装箱前的清洁工。它还会调用 clear_config_lock_debug_controls,专门清理调试控制字段。

调用图:调用 1 个内部函数(clear_config_lock_debug_controls);被 1 处调用(session_configuration_to_lock_config_toml)。

resolved_config_to_toml193–201 ↗
fn resolved_config_to_toml(
    value: &impl serde::Serialize,
    label: &'static str,
) -> anyhow::Result<Toml>

作用:这是一个小转换工具,用来把已经解析好的配置对象变成对应的 TOML 配置结构。它让复杂内部对象也能被锁文件保存。

数据流:进去的是一个能序列化的值和一个 label,label 用来说明出错时是哪块配置。它通过 toml_round_trip 先序列化再反序列化,相当于让数据走一遍 TOML 格式检查和转换。出来是目标 TOML 类型,或者转换错误。

调用关系:它被 save_config_resolved_fields 用来处理 multi_agent_v2 和 memories 这类嵌套配置。真正的转换细节交给 toml_round_trip。

调用图:调用 1 个内部函数(toml_round_trip);被 1 处调用(save_config_resolved_fields)。

tests::lock_contains_prompts_and_materializes_features210–277 ↗
async fn lock_contains_prompts_and_materializes_features()

作用:这个测试确认生成的锁文件确实包含会话里解析后的提示词和功能开关状态。它防止以后有人改代码时不小心漏掉这些关键运行信息。

数据流:进去的是测试创建出的会话配置。测试手动设置基础提示词、开发者提示词和压缩提示词,再生成锁文件,检查提示词、模型、profile 清理、memories、所有 feature 状态、multi_agent_v2 配置和锁文件版本是否符合预期。出来是测试通过,或断言失败指出哪里不一致。

调用关系:它通过 make_session_configuration_for_tests 准备假会话,再间接覆盖 SessionConfiguration::to_config_lockfile_toml 以及后面的补全、清理流程。

调用图:调用 1 个内部函数(make_session_configuration_for_tests);外部调用 2 个(assert!, assert_eq!)。

tests::lock_skips_session_values_when_model_catalog_fields_are_not_saved280–303 ↗
async fn lock_skips_session_values_when_model_catalog_fields_are_not_saved()

作用:这个测试确认当配置明确说“不保存模型目录解析出来的会话字段”时,锁文件不会偷偷写入这些字段。这样可以保证开关的含义是真的。

数据流:进去的是测试会话配置。测试把 config_lock_save_fields_resolved_from_model_catalog 设为 false,再设置一批会话字段;生成锁文件后,检查模型、推理强度、提示词、服务等级、人格和审批字段都还是空。出来是测试通过或断言失败。

调用关系:它验证 session_configuration_to_lock_config_toml 对 save_session_resolved_fields 的条件调用是否正确。准备数据同样依赖 make_session_configuration_for_tests。

调用图:调用 1 个内部函数(make_session_configuration_for_tests);外部调用 2 个(new, assert_eq!)。

tests::lock_validation_reports_config_diff306–321 ↗
async fn lock_validation_reports_config_diff()

作用:这个测试确认当实际配置和锁文件不一样时,校验会报出能看懂的差异信息。它关心的不只是失败,还关心失败原因是否有用。

数据流:进去的是一个测试会话生成的锁文件。测试复制一份锁文件并把 model 改成不同值,然后调用 validate_config_lock_replay;预期得到错误,并检查错误消息里包含“有效配置不匹配”和 model 字段。出来是测试通过或错误消息不符合预期。

调用关系:它直接测试 validate_config_lock_replay 的报错行为,同时用 make_session_configuration_for_tests 和 SessionConfiguration::to_config_lockfile_toml 准备对比材料。

调用图:调用 2 个内部函数(validate_config_lock_replay, make_session_configuration_for_tests);外部调用 2 个(assert!, default)。

tests::lock_validation_ignores_removed_apps_mcp_path_override324–347 ↗
async fn lock_validation_ignores_removed_apps_mcp_path_override()

作用:这个测试确认一个已经废弃的旧配置项不会让锁文件校验误报失败。它保证旧锁文件在兼容范围内还能用。

数据流:进去的是测试会话生成的当前锁文件。测试把 expected 转成 TOML 值,手动塞入旧的 features.apps_mcp_path_override 字段,再转回 ConfigLockfileToml;然后拿它和当前锁文件校验,预期成功。出来是测试通过或校验错误。

调用关系:它直接调用 validate_config_lock_replay,验证配置锁回放校验里的兼容性规则。测试数据仍由 make_session_configuration_for_tests 生成。

调用图:调用 2 个内部函数(validate_config_lock_replay, make_session_configuration_for_tests);外部调用 6 个(default, from_iter, Boolean, String, Table, try_from)。

tests::lock_validation_rejects_codex_version_mismatch_by_default350–368 ↗
async fn lock_validation_rejects_codex_version_mismatch_by_default()

作用:这个测试确认默认情况下,锁文件里的 Codex 版本和当前版本不一致会被拒绝。因为不同版本的程序可能用同一份配置跑出不同结果。

数据流:进去的是测试会话生成的 expected 和 actual 两份锁文件。测试把 expected 的 codex_version 改成旧版本,再调用 validate_config_lock_replay;预期失败,并检查错误信息提示版本不一致以及如何打开忽略开关。出来是测试通过或断言失败。

调用关系:它直接测试 validate_config_lock_replay 的版本保护规则。make_session_configuration_for_tests 负责提供基础会话配置。

调用图:调用 2 个内部函数(validate_config_lock_replay, make_session_configuration_for_tests);外部调用 2 个(assert!, default)。

tests::lock_validation_can_ignore_codex_version_mismatch371–385 ↗
async fn lock_validation_can_ignore_codex_version_mismatch()

作用:这个测试确认如果用户明确允许忽略 Codex 版本差异,校验就不会因为版本号不同而失败。它验证的是那个“我知道风险,继续”的开关。

数据流:进去的是测试会话生成的两份锁文件,其中 expected 被改成旧版本。测试调用 validate_config_lock_replay,并传入 allow_codex_version_mismatch 为 true 的选项。出来预期是成功;如果仍然报错,测试失败。

调用关系:它补充测试 validate_config_lock_replay 的另一条路径:和上一个测试相反,这里版本不一致但选项允许通过。

调用图:调用 2 个内部函数(validate_config_lock_replay, make_session_configuration_for_tests)。

tui/src/debug_config.rs源码 ↗
domain_logic用户在 TUI 中触发 /debug-config 时运行;测试在开发和持续集成时运行

程序的配置可能来自很多地方:系统文件、用户文件、项目文件、企业托管配置、启动时传入的参数等。后来的配置还可能覆盖前面的配置。这个文件就像一张“配置来源清单”,把这些层按优先级排好,标出启用还是禁用,并把重要限制也列出来,比如允许哪些沙箱模式、能不能联网、哪些文件不许读。它还会显示会话里的网络代理地址。这样用户或支持人员不用猜配置是从哪来的,也不用翻多个文件。代码的核心是先收集配置层,再把每一项限制格式化成 TUI(终端界面)里的一行文字。测试部分则用各种假配置确认输出没有漏项、格式稳定、特殊情况也能说明白。

函数细节37
new_debug_config_output25–55 ↗
fn new_debug_config_output(
    config: &Config,
    session_network_proxy: Option<&SessionNetworkProxyRuntime>,
) -> PlainHistoryCell

作用:生成一块可以放进聊天历史里的 /debug-config 输出。有人想看当前配置和会话网络代理时,就会用它。

数据流:它接收当前配置和可选的会话网络代理信息 → 先让 render_debug_config_lines 把配置层和限制变成多行文字 → 如果有代理,再追加 HTTP_PROXY 和 ALL_PROXY 地址 → 最后包装成 PlainHistoryCell,交给界面显示。

调用关系:它由 add_debug_config_output 调用,是用户触发调试配置命令后的主要入口。它把大部分配置渲染工作交给 render_debug_config_lines,把代理地址选择交给 session_all_proxy_url,最后调用 PlainHistoryCell::new 生成界面需要的历史单元。

调用图:调用 3 个内部函数(render_debug_config_lines, session_all_proxy_url, new);被 1 处调用(add_debug_config_output);外部调用 1 个(format!)。

sandbox_mode_is_allowed_by_permissions57–73 ↗
fn sandbox_mode_is_allowed_by_permissions(
    permissions: &Permissions,
    mode: SandboxModeRequirement,
) -> bool

作用:判断某个沙箱模式现在到底能不能用。沙箱模式可以理解成“程序被关在多严的安全笼子里”。

数据流:它接收权限设置和一个沙箱要求 → 把这个沙箱要求换成对应的权限档案,比如只读、工作区可写、完全不限制、外部沙箱 → 再问权限系统是否允许切到这个档案 → 返回 true 或 false。

调用关系:它是 render_debug_config_lines 的过滤依据之一,通过 new_debug_config_output 传进去。这样调试输出不会只照搬配置文件,而是显示当前权限真正允许的沙箱选项。

调用图:调用 3 个内部函数(can_set_permission_profile, read_only, workspace_write)。

session_all_proxy_url75–81 ↗
fn session_all_proxy_url(http_addr: &str, socks_addr: &str, socks_enabled: bool) -> String

作用:决定 ALL_PROXY 这个代理环境变量该写 HTTP 地址还是 SOCKS 地址。SOCKS 是另一种代理协议,常用于更通用的网络转发。

数据流:它接收 HTTP 代理地址、SOCKS 代理地址,以及 SOCKS 是否启用 → 如果启用 SOCKS,就生成 socks5h:// 开头的地址 → 否则生成 http:// 开头的地址 → 返回这段字符串。

调用关系:它被 new_debug_config_output 调用,用来补齐会话运行时代理信息。两个单元测试分别确认启用和不启用 SOCKS 时输出正确。

调用图:被 1 处调用(new_debug_config_output);外部调用 1 个(format!)。

render_debug_config_lines83–305 ↗
fn render_debug_config_lines(
    stack: &ConfigLayerStack,
    sandbox_mode_is_effectively_allowed: impl Fn(SandboxModeRequirement) -> bool,
) -> Vec<Line<'static>>

作用:把配置栈和各种要求整理成终端界面可显示的一行行文字。这是本文件最核心的“排版员”。

数据流:它接收配置层栈,以及一个判断沙箱模式是否实际允许的小函数 → 先列出所有配置层,包含被禁用的层和禁用原因 → 再读取配置要求和原始要求文件内容 → 把审批策略、沙箱、联网、文件系统、托管钩子等要求逐项变成文字行 → 返回 Line 列表。

调用关系:它被 new_debug_config_output 用于真实界面输出,也被测试辅助函数 render_stack_to_text_with_sandbox_mode_filter 调用。它会把具体小活分给 render_non_file_layer_details、requirement_line、join_or_empty、normalize_allowed_web_search_modes、format_managed_hooks_requirements、format_network_constraints 等函数。

调用图:调用 10 个内部函数(get_layers, requirements, requirements_toml, format_managed_hooks_requirements, format_network_constraints, format_residency_requirement, join_or_empty, normalize_allowed_web_search_modes, render_non_file_layer_details, requirement_line);被 2 处调用(new_debug_config_output, render_stack_to_text_with_sandbox_mode_filter);外部调用 4 个(new, format_config_layer_source, format!, vec!)。

render_non_file_layer_details307–318 ↗
fn render_non_file_layer_details(layer: &ConfigLayerEntry) -> Vec<Line<'static>>

作用:为不是普通配置文件的配置层补充细节,比如启动参数层、企业托管层、MDM 管理层。

数据流:它接收一个配置层 → 看这个层的来源类型 → 如果是会话启动参数,就展开成键值对;如果是托管配置,就显示原始配置值;如果是普通系统、用户、项目文件,就不额外显示 → 返回若干显示行。

调用关系:它只在 render_debug_config_lines 列出每一层配置时被调用。它根据情况把工作交给 render_session_flag_details 或 render_non_file_layer_value。

调用图:调用 2 个内部函数(render_non_file_layer_value, render_session_flag_details);被 1 处调用(render_debug_config_lines);外部调用 1 个(new)。

render_session_flag_details320–332 ↗
fn render_session_flag_details(config: &TomlValue) -> Vec<Line<'static>>

作用:把启动时传入的配置参数展开成容易读的“键 = 值”列表。

数据流:它接收一段 TOML 配置值,TOML 是常见的配置文件格式 → 调用 flatten_toml_key_values 把嵌套结构压平成点号路径,比如 a.b.c → 如果没有内容就返回“none” → 否则返回每个键值对对应的一行文字。

调用关系:它由 render_non_file_layer_details 在遇到 SessionFlags 配置层时调用。它依赖 flatten_toml_key_values 做真正的展开。

调用图:调用 1 个内部函数(flatten_toml_key_values);被 1 处调用(render_non_file_layer_details);外部调用 2 个(new, vec!)。

format_managed_hooks_requirements334–349 ↗
fn format_managed_hooks_requirements(hooks: &ManagedHooksRequirementsToml) -> String

作用:把托管钩子要求压缩成一句摘要。钩子就是在某些操作前后自动运行的小脚本或处理器。

数据流:它接收托管钩子的配置 → 如果有托管目录或 Windows 托管目录,就写进摘要 → 再加上处理器数量 → 用 join_or_empty 拼成逗号分隔的字符串 → 返回给调试输出使用。

调用关系:它被 render_debug_config_lines 调用,用在 Requirements 里的 hooks 一项。它把复杂的钩子配置变成短句,避免调试页太长。

调用图:调用 1 个内部函数(join_or_empty);被 1 处调用(render_debug_config_lines);外部调用 2 个(new, format!)。

render_non_file_layer_value351–368 ↗
fn render_non_file_layer_value(layer: &ConfigLayerEntry) -> Vec<Line<'static>>

作用:显示托管配置层的实际内容,让人看到企业或 MDM 下发了什么。

数据流:它接收一个配置层 → 先决定显示标签,比如“MDM value”或“Enterprise-managed config value” → 优先取原始 TOML 文本,没有的话就把已解析的配置值转成字符串 → 如果为空显示 empty,如果有多行就缩进成多行 → 返回显示行。

调用关系:它由 render_non_file_layer_details 调用,专门处理 MDM、企业托管这类不是普通文件路径的配置层。标签由 non_file_layer_value_label 提供。

调用图:调用 2 个内部函数(raw_toml, non_file_layer_value_label);被 1 处调用(render_non_file_layer_details);外部调用 1 个(vec!)。

non_file_layer_value_label370–382 ↗
fn non_file_layer_value_label(source: &ConfigLayerSource) -> &'static str

作用:给非文件配置层选择一个人能看懂的标题。

数据流:它接收配置来源类型 → 如果是 MDM,就返回“MDM value” → 如果是企业托管,就返回“Enterprise-managed config value” → 其他情况返回通用的“Layer value”。

调用关系:它被 render_non_file_layer_value 调用,只负责取标签,不处理具体配置内容。

调用图:被 1 处调用(render_non_file_layer_value)。

flatten_toml_key_values384–407 ↗
fn flatten_toml_key_values(
    value: &TomlValue,
    prefix: Option<&str>,
    out: &mut Vec<(String, String)>,
)

作用:把嵌套的 TOML 配置摊平成一串键值对,方便在终端里逐行展示。

数据流:它接收一个 TOML 值、当前路径前缀、输出列表 → 如果当前值是表,就按键名排序后递归进入每个子项 → 如果是普通值,就把完整路径和格式化后的值加入输出列表 → 最终改动传入的列表。

调用关系:它由 render_session_flag_details 调用,是展开 session flags 的底层工具。它用 format_toml_value 把具体值转成文字。

调用图:调用 1 个内部函数(format_toml_value);被 1 处调用(render_session_flag_details);外部调用 1 个(format!)。

format_toml_value409–411 ↗
fn format_toml_value(value: &TomlValue) -> String

作用:把一个 TOML 值转成字符串,供调试输出显示。

数据流:它接收 TOML 值 → 调用该值自己的 to_string 能力 → 返回字符串。

调用关系:它被 flatten_toml_key_values 使用。这个函数很小,但让值格式化的入口集中在一个地方。

调用图:被 1 处调用(flatten_toml_key_values);外部调用 1 个(to_string)。

requirement_line413–422 ↗
fn requirement_line(
    name: &str,
    value: String,
    source: Option<&RequirementSource>,
) -> Line<'static>

作用:把一条配置要求做成统一格式的显示行,并附上来源。

数据流:它接收要求名称、要求值、可选来源 → 如果来源不存在,就写成“unspecified” → 拼成“名称: 值 (source: 来源)” → 返回一行终端文字。

调用关系:它被 render_debug_config_lines 反复调用,用来保证 Requirements 区域的每一项格式一致。

调用图:被 1 处调用(render_debug_config_lines);外部调用 1 个(format!)。

join_or_empty424–430 ↗
fn join_or_empty(values: Vec<String>) -> String

作用:把多个字符串用逗号连起来;如果一个都没有,就明确写成“empty”。

数据流:它接收字符串列表 → 如果列表为空,返回“<empty>” → 否则把所有字符串用“, ”连接 → 返回结果。

调用关系:它被 render_debug_config_lines、format_managed_hooks_requirements、format_network_constraints 使用。它避免空列表在调试输出里看起来像漏显示。

调用图:被 3 处调用(format_managed_hooks_requirements, format_network_constraints, render_debug_config_lines)。

normalize_allowed_web_search_modes432–444 ↗
fn normalize_allowed_web_search_modes(
    modes: &[WebSearchModeRequirement],
) -> Vec<WebSearchModeRequirement>

作用:整理允许的网页搜索模式列表,保证“disabled(关闭搜索)”这个选项被合理表达。

数据流:它接收网页搜索模式数组 → 如果数组本来为空,就返回只包含 Disabled 的列表 → 如果不为空但没有 Disabled,就补上 Disabled → 返回整理后的列表。

调用关系:它被 render_debug_config_lines 调用,用于 Requirements 里的 allowed_web_search_modes。这样输出会体现:即使允许某些搜索模式,关闭搜索通常也是一个可选状态。

调用图:被 1 处调用(render_debug_config_lines);外部调用 3 个(is_empty, to_vec, vec!)。

format_sandbox_mode_requirement446–453 ↗
fn format_sandbox_mode_requirement(mode: SandboxModeRequirement) -> String

作用:把沙箱模式枚举值转成配置里常见的短横线文本,比如 read-only。

数据流:它接收一个沙箱模式 → 按类型匹配成对应字符串 → 返回这个字符串。

调用关系:它作为 render_debug_config_lines 格式化沙箱要求时的小工具使用,让内部类型名变成人能读的配置名。

format_residency_requirement455–459 ↗
fn format_residency_requirement(requirement: ResidencyRequirement) -> String

作用:把数据驻留要求转成显示文字。数据驻留指数据必须留在哪个地区。

数据流:它接收一个驻留要求 → 目前只有美国 Us 这一种 → 返回“us”。

调用关系:它被 render_debug_config_lines 调用,用来显示 enforce_residency 这一项。

调用图:被 1 处调用(render_debug_config_lines)。

format_network_constraints461–524 ↗
fn format_network_constraints(network: &NetworkConstraints) -> String

作用:把网络限制整理成一句摘要,说明代理端口、域名权限、本地绑定等网络规则。

数据流:它接收 NetworkConstraints → 逐个检查里面哪些字段有值 → 有值的字段就写成 key=value,例如 enabled=true、http_port=3128、domains={example.com=allow} → 最后用 join_or_empty 拼成一串 → 返回摘要字符串。

调用关系:它被 render_debug_config_lines 调用,用于 Requirements 里的 experimental_network。它会借助网络权限条目格式化函数把域名和 Unix socket 规则写清楚。

调用图:调用 1 个内部函数(join_or_empty);被 1 处调用(render_debug_config_lines);外部调用 2 个(new, format!)。

format_network_permission_entries526–535 ↗
fn format_network_permission_entries(
    entries: &std::collections::BTreeMap<String, T>,
    format_value: impl Fn(T) -> &'static str,
) -> String

作用:把一组网络许可规则变成花括号里的列表,比如 {example.com=allow}。

数据流:它接收一个按名称排序的映射表,以及一个把权限值转成文字的小函数 → 逐项生成“名称=权限” → 用逗号连接并包上花括号 → 返回字符串。

调用关系:它是 format_network_constraints 这类网络摘要逻辑的通用小帮手,可配合 format_network_domain_permission 或 format_network_unix_socket_permission 使用。

调用图:外部调用 1 个(format!)。

format_network_domain_permission537–542 ↗
fn format_network_domain_permission(permission: NetworkDomainPermissionToml) -> &'static str

作用:把域名访问权限转成文字:允许就是 allow,拒绝就是 deny。

数据流:它接收一个域名权限枚举 → 如果是 Allow 返回“allow” → 如果是 Deny 返回“deny”。

调用关系:它用于网络权限条目的显示,通常配合 format_network_permission_entries 把域名规则写进 format_network_constraints 的输出。

format_network_unix_socket_permission544–551 ↗
fn format_network_unix_socket_permission(
    permission: NetworkUnixSocketPermissionToml,
) -> &'static str

作用:把 Unix socket 的访问权限转成文字。Unix socket 可以理解成同一台机器上程序之间通信的本地接口。

数据流:它接收一个 Unix socket 权限枚举 → 如果是 Allow 返回“allow” → 如果是 Deny 返回“deny”。

调用关系:它用于网络权限条目的显示,通常配合 format_network_permission_entries 把 socket 规则写进 format_network_constraints 的输出。

tests::empty_toml_table595–597 ↗
fn empty_toml_table() -> TomlValue

作用:给测试快速造一个空的 TOML 表。

数据流:它不接收输入 → 创建一个空的 TOML map → 包成 TomlValue::Table 返回。

调用关系:多个测试用它来构造“有配置层但内容为空”的场景,避免每个测试重复写创建空表的代码。

调用图:外部调用 2 个(Table, new)。

tests::absolute_path599–601 ↗
fn absolute_path(path: &str) -> AbsolutePathBuf

作用:给测试把字符串变成项目要求的绝对路径类型。

数据流:它接收路径字符串 → 调用 from_absolute_path 检查并转换 → 如果不是绝对路径测试就失败 → 返回 AbsolutePathBuf。

调用关系:它被涉及系统文件、用户文件、项目目录、托管目录的测试调用,用来生成合法路径。

调用图:调用 1 个内部函数(from_absolute_path)。

tests::render_to_text603–614 ↗
fn render_to_text(lines: &[Line<'static>]) -> String

作用:把终端界面用的 Line 列表转成普通字符串,方便测试用 contains 检查。

数据流:它接收多行 Line → 逐行取出里面所有 span 的文字内容 → 拼成每一行文本 → 再用换行符连接 → 返回普通字符串。

调用关系:它是测试辅助工具,被 render_stack_to_text_with_sandbox_mode_filter 间接使用,让测试不用关心颜色、加粗等终端样式。

调用图:外部调用 1 个(iter)。

tests::render_stack_to_text616–618 ↗
fn render_stack_to_text(stack: &ConfigLayerStack) -> String

作用:用最简单的方式把配置栈渲染成纯文本,默认不额外过滤沙箱模式。

数据流:它接收配置栈 → 调用 render_stack_to_text_with_sandbox_mode_filter,并传入永远返回 true 的过滤函数 → 返回渲染后的文本。

调用关系:大多数测试通过它检查 render_debug_config_lines 的输出。需要特殊沙箱过滤时,测试会直接用 render_stack_to_text_with_sandbox_mode_filter。

调用图:外部调用 1 个(render_stack_to_text_with_sandbox_mode_filter)。

tests::render_stack_to_text_with_sandbox_mode_filter620–628 ↗
fn render_stack_to_text_with_sandbox_mode_filter(
        stack: &ConfigLayerStack,
        sandbox_mode_is_effectively_allowed: impl Fn(SandboxModeRequirement) -> bool,
    ) -> String

作用:测试专用:带自定义沙箱过滤规则地渲染配置栈。

数据流:它接收配置栈和一个判断沙箱模式是否允许的函数 → 调用 render_debug_config_lines 得到界面行 → 再用 render_to_text 转成普通文本 → 返回字符串。

调用关系:它连接被测的 render_debug_config_lines 和测试断言。debug_config_output_filters_sandbox_modes_blocked_by_deny_read_requirements 会用它模拟部分沙箱被禁用的情况。

调用图:调用 1 个内部函数(render_debug_config_lines);外部调用 1 个(render_to_text)。

tests::debug_config_output_lists_all_layers_including_disabled631–669 ↗
fn debug_config_output_lists_all_layers_including_disabled()

作用:确认调试输出会列出所有配置层,包括被禁用的层。

数据流:它构造一个启用的系统层和一个禁用的项目层 → 渲染成文本 → 检查文本包含 enabled、disabled、禁用原因和空 Requirements 提示。

调用关系:这是 render_debug_config_lines 的基础行为测试,确保排查配置时不会把禁用层隐藏掉。

调用图:调用 1 个内部函数(new);外部调用 7 个(default, assert!, cfg!, default, absolute_path, render_stack_to_text, vec!)。

tests::debug_config_output_lists_requirement_sources672–868 ↗
fn debug_config_output_lists_requirement_sources()

作用:确认各种配置要求都会显示它们来自哪里。

数据流:它构造一大组要求,比如审批策略、沙箱、网页搜索、MCP 服务器、网络限制、文件禁止读取等 → 渲染配置栈 → 检查每一项值和 source 来源文字都出现在输出里。

调用关系:它覆盖 render_debug_config_lines 中 Requirements 区域的大部分分支,也通过快照测试固定非 Windows 平台上的整体输出格式。

调用图:调用 5 个内部函数(new, new, allow_any, new, read_only);外部调用 9 个(from, default, default, assert!, cfg!, assert_snapshot!, absolute_path, render_stack_to_text, vec!)。

tests::debug_config_output_filters_sandbox_modes_blocked_by_deny_read_requirements871–955 ↗
fn debug_config_output_filters_sandbox_modes_blocked_by_deny_read_requirements()

作用:确认调试输出里的沙箱模式会按真实权限过滤,而不是盲目显示配置文件写了什么。

数据流:它构造允许多种沙箱模式的要求,又构造一个只允许只读和工作区可写的权限对象 → 渲染时传入 sandbox_mode_is_allowed_by_permissions 作为过滤器 → 检查危险全权限和外部沙箱没有显示。

调用关系:它验证 sandbox_mode_is_allowed_by_permissions 与 render_debug_config_lines 的配合,防止调试页误导用户。

调用图:调用 7 个内部函数(new, new, allow_any, new, new, from_approval_and_profile, read_only);外部调用 9 个(new, default, assert!, cfg!, default, assert_snapshot!, absolute_path, render_stack_to_text_with_sandbox_mode_filter, vec!)。

tests::debug_config_output_lists_approvals_reviewer_as_requirement958–978 ↗
fn debug_config_output_lists_approvals_reviewer_as_requirement()

作用:确认 approvals reviewer(审批复核者)也会作为要求显示出来。

数据流:它构造一个允许 auto_review 的复核者要求 → 渲染配置栈 → 检查输出包含对应文字,并且 Requirements 不再显示 none。

调用关系:它专门覆盖 render_debug_config_lines 里 allowed_approvals_reviewers 这一项,防止以后改代码时漏掉。

调用图:调用 3 个内部函数(new, allow_any, new);外部调用 6 个(new, default, assert!, default, render_stack_to_text, vec!)。

tests::debug_config_output_formats_unix_socket_permissions981–1013 ↗
fn debug_config_output_formats_unix_socket_permissions()

作用:确认 Unix socket 的允许和拒绝规则能正确显示。

数据流:它构造两个 socket 路径,一个 allow、一个 deny → 渲染配置栈 → 检查 experimental_network 行里包含按路径排序后的 socket 权限摘要。

调用关系:它验证 format_network_constraints 以及 Unix socket 权限格式化相关逻辑。

调用图:调用 2 个内部函数(new, new);外部调用 7 个(from, default, new, default, assert!, default, render_stack_to_text)。

tests::debug_config_output_lists_session_flag_key_value_pairs1016–1043 ↗
fn debug_config_output_lists_session_flag_key_value_pairs()

作用:确认会话启动参数会被展开成清楚的键值对。

数据流:它从一段 TOML 文本解析出 session flags → 放进 SessionFlags 配置层 → 渲染后检查 model、嵌套 network_access、writable_roots 等内容都出现。

调用关系:它覆盖 render_session_flag_details 和 flatten_toml_key_values,确保嵌套配置不会只显示成一坨难读的结构。

调用图:调用 1 个内部函数(new);外部调用 5 个(default, assert!, default, render_stack_to_text, vec!)。

tests::debug_config_output_shows_legacy_mdm_layer_value1046–1077 ↗
fn debug_config_output_shows_legacy_mdm_layer_value()

作用:确认旧版 MDM 管理配置的原始内容会显示出来。

数据流:它构造一段带注释的 managed_config.toml 原文 → 放入 LegacyManagedConfigTomlFromMdm 配置层 → 渲染后检查标题是 MDM value,并且注释和配置项都保留。

调用关系:它验证 render_non_file_layer_value 对 MDM 层优先显示 raw_toml 的行为。

调用图:调用 1 个内部函数(new);外部调用 7 个(default, assert!, cfg!, default, absolute_path, render_stack_to_text, vec!)。

tests::debug_config_output_shows_enterprise_managed_layer_value1080–1115 ↗
fn debug_config_output_shows_enterprise_managed_layer_value()

作用:确认企业托管配置会用企业托管的标题显示,而不是误标成 MDM。

数据流:它构造一段企业云端下发的 TOML 原文 → 放入 EnterpriseManaged 配置层 → 渲染后检查显示 enterprise-managed 信息和 Enterprise-managed config value 标题,并确认没有 MDM value。

调用关系:它覆盖 non_file_layer_value_label 和 render_non_file_layer_value 中企业托管配置的分支。

调用图:调用 1 个内部函数(new);外部调用 7 个(default, assert!, cfg!, default, absolute_path, render_stack_to_text, vec!)。

tests::debug_config_output_normalizes_empty_web_search_mode_list1118–1160 ↗
fn debug_config_output_normalizes_empty_web_search_mode_list()

作用:确认网页搜索模式列表为空时,会显示为 disabled,而不是空白或漏掉。

数据流:它构造一个 allowed_web_search_modes 为空数组的要求 → 渲染配置栈 → 检查输出写着 disabled,并带有正确来源。

调用关系:它验证 normalize_allowed_web_search_modes 被 render_debug_config_lines 正确使用。

调用图:调用 3 个内部函数(new, allow_any, new);外部调用 4 个(new, default, assert!, render_stack_to_text)。

tests::debug_config_output_lists_managed_hooks_requirement1163–1206 ↗
fn debug_config_output_lists_managed_hooks_requirement()

作用:确认托管钩子要求能显示摘要,尤其是处理器数量。

数据流:它构造一个托管钩子配置,里面有目录和一个命令处理器 → 渲染配置栈 → 检查输出包含 hooks、handlers=1 和来源。

调用关系:它覆盖 format_managed_hooks_requirements 和 render_debug_config_lines 的 hooks 显示逻辑。

调用图:调用 3 个内部函数(new, allow_any, new);外部调用 9 个(default, new, default, assert!, cfg!, default, from, render_stack_to_text, vec!)。

tests::session_all_proxy_url_uses_socks_when_enabled1209–1218 ↗
fn session_all_proxy_url_uses_socks_when_enabled()

作用:确认 SOCKS 代理启用时,ALL_PROXY 使用 socks5h 地址。

数据流:它传入 HTTP 地址、SOCKS 地址和 socks_enabled=true → 调用 session_all_proxy_url → 检查返回值是 socks5h:// 加 SOCKS 地址。

调用关系:它直接测试 session_all_proxy_url 的一个分支,保证 new_debug_config_output 显示代理时不会选错协议。

调用图:外部调用 1 个(assert_eq!)。

tests::session_all_proxy_url_uses_http_when_socks_disabled1221–1230 ↗
fn session_all_proxy_url_uses_http_when_socks_disabled()

作用:确认 SOCKS 代理没启用时,ALL_PROXY 使用 HTTP 地址。

数据流:它传入 HTTP 地址、SOCKS 地址和 socks_enabled=false → 调用 session_all_proxy_url → 检查返回值是 http:// 加 HTTP 地址。

调用关系:它直接测试 session_all_proxy_url 的另一个分支,和 SOCKS 启用测试一起覆盖代理选择逻辑。

调用图:外部调用 1 个(assert_eq!)。

权限配置文件解析

这些文件定义面向 TOML 的权限语法,将其编译为规范运行时配置文件,保留解析后的身份,并为兼容性和 UI 警告进行适配。

config/src/permissions_toml.rs源码 ↗
configconfig load / startup

这个文件像一张“权限说明书的翻译表”。用户在 TOML 里写权限,比如哪些工作目录可用、哪些文件能读写、哪些网站能访问、哪些 Unix socket 能连、网络代理要不要改请求头。这里先用一组结构体把这些配置读进来,再检查配置是不是自相矛盾,比如某个网络拦截动作为空,或者 hook 引用了不存在的动作。它还支持权限 profile 继承:子配置可以继承父配置,再覆盖其中一部分,像“公司默认规则”上叠加“某个项目的特殊规则”。合并时会特别处理域名大小写和格式,避免同一个域名因为写法不同而重复。最后,它会把 TOML 里的简单文字规则转换成网络代理模块能直接执行的运行时配置。

函数细节24
PermissionsToml::is_empty29–31 ↗
fn is_empty(&self) -> bool

作用:检查整份权限配置里有没有任何 profile。外部代码可以用它快速判断“用户是不是根本没写权限配置”。

数据流:进去的是一个 PermissionsToml 对象 → 它只看里面的 entries 这张表是不是空 → 出来一个 true 或 false,不改动任何数据。

调用关系:它是一个很轻的小检查工具,通常会在加载配置后被调用,用来决定后面还要不要继续解析权限 profile。

PermissionsToml::resolve_profile40–108 ↗
fn resolve_profile(
        &self,
        profile_name: &str,
        mut parent_profile: F,
    ) -> Result<PermissionProfileToml, PermissionProfileResolutionError>

作用:把一个指定名字的权限 profile,以及它一路继承的父 profile,合成一份最终配置。这样使用者只需要拿到“最后结果”,不用自己处理继承关系。

数据流:进去的是 profile 名字,以及一个可选的父级查找函数 → 它从当前配置和外部父配置里逐层找 profile,检查有没有找不到、继承内置但不支持、或者循环继承 → 然后从最老的父配置开始一层层合并,出来一份 PermissionProfileToml;如果有问题就出来明确的错误。

调用关系:它会被 resolve_permission_profile 调用,是权限 profile 继承链的核心入口。内部在最终合并阶段把具体合并工作交给 merge_permission_profiles;它还用到标准库的 once 来拼出循环继承的错误路径。

调用图:被 1 处调用(resolve_permission_profile);外部调用 2 个(new, once)。

merge_permission_profiles158–191 ↗
fn merge_permission_profiles(
    mut parent: PermissionProfileToml,
    mut child: PermissionProfileToml,
) -> Result<PermissionProfileToml, PermissionProfileResolutionError>

作用:把父 profile 和子 profile 合成一个 profile,并保证子 profile 的设置覆盖父 profile。它解决的是“继承后到底谁说了算”的问题。

数据流:进去的是父配置和子配置 → 它先去掉父配置的 description 和 extends,避免父级说明文字污染当前 profile;如果双方都有网络域名规则,就先规范化域名写法 → 再把两份结构转成 TOML 值,用通用合并函数叠在一起 → 最后转回 PermissionProfileToml,成功返回合并结果,失败返回序列化或反序列化错误。

调用关系:它由 PermissionsToml::resolve_profile 在整理继承链时调用。真正的“两个 TOML 值怎么覆盖”交给 merge_toml_values;域名统一写法交给 normalize_profile_network_domains。

调用图:调用 2 个内部函数(merge_toml_values, normalize_profile_network_domains);外部调用 1 个(try_from)。

normalize_profile_network_domains193–207 ↗
fn normalize_profile_network_domains(profile: &mut PermissionProfileToml)

作用:把 profile 里的网络域名规则统一成标准写法。这样 example.com、大小写不同的域名之类不会被当成完全不同的规则。

数据流:进去的是一份可修改的 PermissionProfileToml → 它找到 network.domains 里的每个域名模式,用 normalize_host 规范化键名 → 再把整理后的表放回去;如果没有域名规则,就什么也不做。

调用关系:它只在 merge_permission_profiles 发现父子双方都写了域名规则时使用。它像合并前的“整理名单”,避免后续覆盖规则因为写法不同而失效。

调用图:被 1 处调用(merge_permission_profiles);外部调用 1 个(take)。

WorkspaceRootsToml::enabled_roots216–220 ↗
fn enabled_roots(&self) -> impl Iterator<Item = &String>

作用:列出被启用的工作目录根路径。配置里可以写某个目录 true 或 false,这个函数只挑出 true 的那些。

数据流:进去的是 workspace_roots 这张路径到开关的表 → 它逐个查看布尔值 → 出来一个迭代器,里面只有启用状态为 true 的路径引用;不改原始配置。

调用关系:它通常在把配置转换成实际工作区权限时使用,给后续代码一份“真正允许作为工作区根目录”的清单。

FilesystemPermissionsToml::is_empty234–236 ↗
fn is_empty(&self) -> bool

作用:检查文件系统权限表是不是没有任何规则。它让调用者能快速判断有没有必要继续处理文件权限。

数据流:进去的是 FilesystemPermissionsToml → 它查看 entries 是否为空 → 返回 true 或 false,不读取具体每条权限,也不改数据。

调用关系:它是文件权限配置的小辅助函数,常用于配置加载或合并后做快速判断。

NetworkDomainPermissionsToml::is_empty253–255 ↗
fn is_empty(&self) -> bool

作用:检查网络域名权限表是不是空的。也就是用户有没有写“允许或禁止访问哪些域名”。

数据流:进去的是 NetworkDomainPermissionsToml → 它查看 entries 表里有没有项目 → 返回 true 或 false,不修改配置。

调用关系:它服务于网络权限配置处理流程,帮助上层代码决定是否需要应用域名限制。

NetworkDomainPermissionsToml::allowed_domains257–265 ↗
fn allowed_domains(&self) -> Option<Vec<String>>

作用:从域名权限表里挑出所有明确允许访问的域名模式。它给需要白名单的地方提供一份简单列表。

数据流:进去的是域名到 allow/deny 的表 → 它过滤出权限为 Allow 的项,并复制这些域名模式 → 如果有结果就返回 Some(Vec<String>),没有允许项就返回 None。

调用关系:它是把配置拆成更直接清单的辅助函数,常给网络限制或展示逻辑使用。

NetworkDomainPermissionsToml::denied_domains267–275 ↗
fn denied_domains(&self) -> Option<Vec<String>>

作用:从域名权限表里挑出所有明确禁止访问的域名模式。它给需要黑名单的地方提供一份简单列表。

数据流:进去的是域名到 allow/deny 的表 → 它过滤出权限为 Deny 的项,并复制这些域名模式 → 如果有结果就返回 Some(Vec<String>),没有禁止项就返回 None。

调用关系:它和 allowed_domains 是一对,一个整理允许名单,一个整理禁止名单,方便后续网络策略使用。

NetworkDomainPermissionToml::fmt288–294 ↗
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result

作用:把域名权限值显示成普通文字,比如 allow 或 deny。这样错误信息、日志、界面展示时能看懂。

数据流:进去的是 Allow 或 Deny 这个枚举值,以及一个格式化输出器 → 它选择对应的小写字符串 → 写入输出器,返回格式化是否成功。

调用关系:它实现的是 Rust 的 Display 显示接口。别人用字符串格式化这个权限值时,会自动走到这里,并把写字符串的动作交给 write_str。

调用图:外部调用 1 个(write_str)。

NetworkUnixSocketPermissionsToml::is_empty304–306 ↗
fn is_empty(&self) -> bool

作用:检查 Unix socket 权限表是不是空的。Unix socket 可以理解成本机程序之间通信的一种特殊文件通道。

数据流:进去的是 NetworkUnixSocketPermissionsToml → 它查看 entries 里有没有路径规则 → 返回 true 或 false,不改变原数据。

调用关系:它是网络本地通道权限处理里的快速判断工具,上层代码可以据此跳过不必要的转换。

NetworkUnixSocketPermissionsToml::allow_unix_sockets308–314 ↗
fn allow_unix_sockets(&self) -> Vec<String>

作用:列出所有明确允许连接的 Unix socket 路径。这样运行时代理可以知道哪些本机通信通道能放行。

数据流:进去的是路径到 allow/deny 的表 → 它只保留权限为 Allow 的路径,并复制成字符串列表 → 出来一个 Vec<String>,不修改原表。

调用关系:它服务于网络权限转换流程,把 TOML 里带权限标记的表变成更容易消费的允许列表。

NetworkUnixSocketPermissionToml::fmt327–333 ↗
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result

作用:把 Unix socket 权限值显示成 allow 或 deny。这样日志和错误信息不会打印成难懂的内部名字。

数据流:进去的是 Allow 或 Deny,以及格式化输出器 → 它选出对应的小写文字 → 写入输出器并返回结果。

调用关系:它实现 Rust 的 Display 显示接口。任何地方格式化这个权限枚举时,都会自动使用它;实际写入文字交给 write_str。

调用图:外部调用 1 个(write_str)。

NetworkMitmToml::deserialize414–426 ↗
fn deserialize(deserializer: D) -> Result<Self, D::Error>

作用:读取 network.mitm 这段 TOML 配置,并在读完后立刻做基础检查。MITM 在这里指代理“夹在中间”查看或改写请求的机制。

数据流:进去的是 serde 反序列化器,也就是正在读配置的工具 → 它先读成一个未检查的临时结构 → 再组成 NetworkMitmToml,并调用 validate_action_definitions 检查动作和 hook 是否基本有效 → 成功就返回配置对象,失败就把错误交给 serde 报出去。

调用关系:它是 NetworkMitmToml 的自定义读取入口。配置文件被解析到 network.mitm 时会自动调用它,确保坏配置不会悄悄进入后续运行流程。

调用图:外部调用 1 个(deserialize)。

NetworkMitmToml::validate_action_definitions430–454 ↗
fn validate_action_definitions(&self) -> Result<(), String>

作用:检查网络拦截配置里的动作定义是不是空的,以及 hook 有没有指定动作。没有这些检查,配置可能看似存在但实际什么也不会做。

数据流:进去的是 NetworkMitmToml → 它先看每个 action 是否至少包含一个操作,比如删请求头或注入请求头;再看每个 hook 的 action 列表是否为空 → 如果发现问题,返回带具体路径的错误文字;都正常就返回 Ok。

调用关系:它会在 NetworkMitmToml::deserialize 读配置时使用,也会被 validate_action_references 先调用,作为更深入引用检查前的第一道门。

调用图:被 1 处调用(validate_action_references);外部调用 1 个(format!)。

NetworkMitmToml::validate_action_references456–477 ↗
fn validate_action_references(
        &self,
        actions_by_name: &IndexMap<String, NetworkMitmActionToml>,
    ) -> Result<(), String>

作用:检查每个 hook 引用的动作名字是不是真的存在。它防止配置里写了一个动作名,但实际没有定义,导致拦截规则运行时缺胳膊少腿。

数据流:进去的是当前 MITM 配置,以及一张按名字保存的动作表 → 它先调用 validate_action_definitions 做基础检查 → 再逐个 hook、逐个 action 名检查动作表里是否有这个名字 → 找不到就返回清楚的错误,全部存在就返回 Ok。

调用关系:它建立在 validate_action_definitions 之上,属于配置最终校验的一步。它用 contains_key 查动作表,确保 hook 和 action 两个零件能接上。

调用图:调用 1 个内部函数(validate_action_definitions);外部调用 2 个(contains_key, format!)。

NetworkMitmToml::to_runtime_hooks479–492 ↗
fn to_runtime_hooks(
        &self,
        actions_by_name: Option<&IndexMap<String, NetworkMitmActionToml>>,
    ) -> Vec<MitmHookConfig>

作用:把 TOML 里的网络拦截 hook 转成代理运行时能直接使用的 hook 配置。也就是把“配置文件文字”变成“程序执行规则”。

数据流:进去的是 NetworkMitmToml,以及可选的动作定义表 → 它遍历 hooks,把每个 hook 交给 NetworkMitmHookToml::to_runtime 转换 → 返回 MitmHookConfig 列表;如果没有 hooks,就返回空列表。

调用关系:它在 NetworkToml::apply_to_network_proxy_config 应用网络配置时被调用。它是 MITM 配置进入网络代理运行时之前的转换桥梁。

NetworkMitmActionToml::is_empty496–498 ↗
fn is_empty(&self) -> bool

作用:判断一个 MITM 动作是不是完全没操作。空动作没有意义,所以校验时需要抓出来。

数据流:进去的是一个 NetworkMitmActionToml → 它检查 strip_request_headers 和 inject_request_headers 两个列表是否都为空 → 返回 true 或 false,不改配置。

调用关系:它被 validate_action_definitions 用来判断 action 是否有效。它像检查菜单上某道菜是不是没有任何配料。

NetworkToml::apply_to_network_proxy_config502–558 ↗
fn apply_to_network_proxy_config(&self, config: &mut NetworkProxyConfig)

作用:把用户写在 TOML 里的网络配置覆盖到真正的网络代理配置上。它让“配置文件里的选择”真正影响程序联网行为。

数据流:进去的是 NetworkToml,以及一个可修改的 NetworkProxyConfig → 它逐项检查配置是否填写:开关、代理地址、SOCKS5、网络模式、域名规则、Unix socket、本地绑定、MITM hook 等;填了就覆盖运行时配置,没填就保留原值 → 最后还会根据网络模式或 hook 是否存在,决定是否启用 MITM。

调用关系:它被 to_network_proxy_config 用来从零生成配置,也会被 apply_network 在更大的配置应用流程里调用。域名规则的叠加交给 overlay_network_domain_permissions,MITM hook 的转换交给 NetworkMitmToml::to_runtime_hooks。

调用图:调用 1 个内部函数(overlay_network_domain_permissions);被 2 处调用(to_network_proxy_config, apply_network)。

NetworkToml::to_network_proxy_config560–564 ↗
fn to_network_proxy_config(&self) -> NetworkProxyConfig

作用:从一份 NetworkToml 直接生成完整的 NetworkProxyConfig。调用者不需要先手动创建默认配置再应用。

数据流:进去的是 NetworkToml → 它先创建一个默认的 NetworkProxyConfig → 再调用 apply_to_network_proxy_config 把 TOML 里的设置盖上去 → 返回最终的运行时网络代理配置。

调用关系:它是一个方便入口,内部真正干活的是 apply_to_network_proxy_config。适合只需要把单份网络配置变成运行时配置的场景。

调用图:调用 1 个内部函数(apply_to_network_proxy_config);外部调用 1 个(default)。

NetworkMitmHookToml::to_runtime568–583 ↗
fn to_runtime(
        &self,
        actions_by_name: Option<&IndexMap<String, NetworkMitmActionToml>>,
    ) -> MitmHookConfig

作用:把单个 MITM hook 从 TOML 形式转换成运行时 hook。hook 可以理解成“当请求符合这些条件时,就执行某些动作”的规则。

数据流:进去的是一个 hook 配置,以及可选的动作表 → 它复制 host、请求方法、路径前缀、查询参数、请求头、请求体匹配规则 → 再调用 selected_actions 找出要执行的动作 → 出来一个 MitmHookConfig。

调用关系:它由 NetworkMitmToml::to_runtime_hooks 对每个 hook 调用。它自己不展开动作细节,而是把动作挑选和合并交给 selected_actions。

调用图:调用 1 个内部函数(selected_actions)。

NetworkMitmHookToml::selected_actions585–608 ↗
fn selected_actions(
        &self,
        actions_by_name: Option<&IndexMap<String, NetworkMitmActionToml>>,
    ) -> MitmHookActionsConfig

作用:根据 hook 里写的动作名字,收集这些动作真正要做的事,比如删除请求头或注入请求头。它把多个命名动作合并成一份执行清单。

数据流:进去的是 hook 自己的 action 名字列表,以及可选的动作表 → 如果没有动作表,就返回空动作配置 → 如果有,就按名字查找每个动作,把要删除的请求头追加进去,把要注入的请求头转换成运行时格式后追加进去 → 返回合并后的 MitmHookActionsConfig。

调用关系:它只由 NetworkMitmHookToml::to_runtime 调用,是 hook 转运行时配置时的动作装配步骤。注入请求头的单项转换由 NetworkMitmInjectedHeaderToml::to_runtime 完成。

调用图:被 1 处调用(to_runtime);外部调用 1 个(default)。

NetworkMitmInjectedHeaderToml::to_runtime612–619 ↗
fn to_runtime(&self) -> InjectedHeaderConfig

作用:把 TOML 里“要注入的请求头”转换成代理运行时使用的格式。注入请求头通常用于给特定请求自动加认证信息或其他隐藏值。

数据流:进去的是请求头配置,包括名字、从哪个环境变量或文件取秘密值、可选前缀 → 它逐项复制这些字段 → 出来一个 InjectedHeaderConfig,不读取秘密本身,也不改原配置。

调用关系:它在 NetworkMitmHookToml::selected_actions 合并动作时被调用。它只负责单个请求头配置的形状转换,不负责判断什么时候注入。

overlay_network_domain_permissions622–635 ↗
fn overlay_network_domain_permissions(
    config: &mut NetworkProxyConfig,
    domains: &NetworkDomainPermissionsToml,
)

作用:把 TOML 里的域名允许/禁止规则叠加到网络代理配置上。它解决的是“新配置要覆盖或补充已有域名规则”的问题。

数据流:进去的是可修改的 NetworkProxyConfig,以及 NetworkDomainPermissionsToml → 它遍历每个域名模式,把 Allow/Deny 转成代理模块认识的权限类型 → 再调用网络配置里的 upsert_domain_permission 写入或更新规则,同时用 normalize_host 统一域名写法 → 最终改变传入的代理配置。

调用关系:它被 NetworkToml::apply_to_network_proxy_config 调用,也会被 apply_network_constraints 使用。它是域名权限从配置层进入网络代理层的关键连接点。

调用图:被 2 处调用(apply_to_network_proxy_config, apply_network_constraints)。

core/src/config/permissions.rs源码 ↗
configconfig load / startup

可以把这个文件理解成 Codex 的“门禁规则翻译员”。用户在配置文件里写的是比较好读的权限名字、路径、通配符和网络选项;但运行时沙箱需要的是严格的文件系统规则和网络规则。这个文件先选择内置方案,比如只读、允许工作区写入、完全放开;也能解析用户自定义方案。然后它检查名字是否合法,把特殊路径(比如“工作区根目录”“临时目录”)变成内部认识的规则,把相对的子路径拼到正确位置,并对不安全或当前平台支持不好的写法给出启动警告。它还处理网络代理配置,但会特别避免“权限配置本身就偷偷启动代理”。没有它,配置里的权限可能会被误解,轻则工具无法正常读写,重则把不该开放的文件或网络权限放开。

函数细节44
default_builtin_permission_profile_name48–59 ↗
fn default_builtin_permission_profile_name(
    active_project: &ProjectConfig,
    windows_sandbox_level: WindowsSandboxLevel,
) -> &'static str

作用:根据当前项目是否可信,以及 Windows 沙箱是否可用,选一个默认的内置权限方案。它的作用是让新项目在没有明确配置时也有一个安全、合理的起点。

数据流:输入当前项目状态和 Windows 沙箱级别 → 查看项目是不是可信或不可信,并避开 Windows 沙箱被关闭的特殊情况 → 输出内置方案名,通常是“工作区可写”,否则是“只读”。

调用关系:它是权限选择的前置判断之一,会调用项目配置里的 is_trusted 和 is_untrusted 来看项目状态;后续真正生成权限规则时,会用这里选出的名字进入内置或自定义方案流程。

调用图:调用 2 个内部函数(is_trusted, is_untrusted);外部调用 1 个(cfg!)。

is_builtin_permission_profile_name61–68 ↗
fn is_builtin_permission_profile_name(profile_name: &str) -> bool

作用:判断一个权限方案名字是不是 Codex 自带的那几个固定名字。这样程序能区分“系统内置方案”和“用户自己写的方案”。

数据流:输入一个方案名字符串 → 和只读、工作区、完全访问这三个内置名字比较 → 输出 true 或 false。

调用关系:它会被 network_proxy_config_for_profile_selection 和 compile_permission_profile_workspace_roots 使用;这两个流程先靠它判断是否可以直接走内置规则,还是必须去读取用户配置。

调用图:被 2 处调用(compile_permission_profile_workspace_roots, network_proxy_config_for_profile_selection);外部调用 1 个(matches!)。

builtin_permission_profile70–97 ↗
fn builtin_permission_profile(
    profile_name: &str,
    workspace_write: Option<&SandboxWorkspaceWrite>,
) -> Option<PermissionProfile>

作用:把内置权限方案名直接变成运行时能用的权限对象。有人选择内置方案时,就不用再去配置文件里查详细规则。

数据流:输入方案名和可选的工作区写入设置 → 如果是只读就生成只读权限;如果是工作区方案,就按是否允许网络、是否排除临时目录等设置生成规则;如果是完全访问就关闭沙箱限制 → 输出一个权限对象,或在名字不认识时输出空。

调用关系:它会被 compile_permission_profile_selection 调用来优先处理内置方案,也会被配置加载流程 load_config_with_layer_stack 直接使用;内部把具体创建工作交给 PermissionProfile 的 read_only、workspace_write 和 workspace_write_with。

调用图:调用 3 个内部函数(read_only, workspace_write, workspace_write_with);被 2 处调用(load_config_with_layer_stack, compile_permission_profile_selection)。

validate_user_permission_profile_names99–118 ↗
fn validate_user_permission_profile_names(
    permissions: Option<&PermissionsToml>,
) -> io::Result<()>

作用:检查用户自定义权限方案名有没有占用系统保留前缀。冒号开头的名字留给内置特殊含义,用户不能拿来当普通方案名。

数据流:输入可选的权限配置表 → 如果没有配置就直接通过;如果有,就逐个看方案名是否以冒号开头 → 没问题返回成功,有问题返回配置错误。

调用关系:它会在 resolve_effective_permission_selection 中被调用,属于正式解析权限前的把关步骤,防止用户名字和内置名字体系撞车。

调用图:被 1 处调用(resolve_effective_permission_selection);外部调用 2 个(new, format!)。

network_proxy_config_from_profile_network120–132 ↗
fn network_proxy_config_from_profile_network(
    network: Option<&NetworkToml>,
) -> NetworkProxyConfig

作用:从某个权限方案里的网络配置提取网络代理设置,但故意不让它直接启用代理。这样权限方案可以提供代理参数,却不能单独决定启动代理功能。

数据流:输入可选的 network 配置 → 有配置就转换成代理配置,没有就用默认值 → 最后强制把代理启用开关设为 false → 输出代理配置。

调用关系:它会被 network_proxy_config_for_profile_selection 调用,用在用户选择某个自定义权限方案后提取相关代理信息。

调用图:被 1 处调用(network_proxy_config_for_profile_selection)。

apply_network_proxy_feature_config134–192 ↗
fn apply_network_proxy_feature_config(
    config: &mut NetworkProxyConfig,
    feature_config: &NetworkProxyConfigToml,
)

作用:把功能开关里的网络代理配置套用到已有代理配置上。它像把“全局功能设置”覆盖到“权限方案带来的基础设置”上。

数据流:输入一个可修改的代理配置和功能配置 → 把启用状态、代理地址、SOCKS5、域名允许/拒绝、Unix socket 规则等字段转换成统一格式 → 直接改写传入的代理配置。

调用关系:它会被 load_config_with_layer_stack 和 network_proxy_spec_for_active_permission_profile 调用;它不自己决定权限,只负责把功能配置转换后交给 NetworkToml 的 apply_to_network_proxy_config 合并。

调用图:被 2 处调用(load_config_with_layer_stack, network_proxy_spec_for_active_permission_profile)。

resolve_permission_profile194–201 ↗
fn resolve_permission_profile(
    permissions: &PermissionsToml,
    profile_name: &str,
) -> io::Result<PermissionProfileToml>

作用:按名字找到并展开一个用户权限方案。它还允许用户方案继承部分内置方案,就像在现成模板上加自己的规则。

数据流:输入完整权限配置和方案名 → 调用配置对象的 resolve_profile,并提供可继承的内置父方案查找方法 → 成功输出展开后的方案配置,失败转成输入错误。

调用关系:compile_permission_profile、compile_permission_profile_workspace_roots 和 network_proxy_config_for_profile_selection 都会先用它拿到完整方案,再继续编译文件、工作区或网络规则。

调用图:调用 1 个内部函数(resolve_profile);被 3 处调用(compile_permission_profile, compile_permission_profile_workspace_roots, network_proxy_config_for_profile_selection)。

extensible_builtin_parent_profile203–214 ↗
fn extensible_builtin_parent_profile(profile_name: &str) -> Option<PermissionProfileToml>

作用:提供可以被用户方案继承的内置父方案。目前只允许继承只读和工作区写入这类安全模板。

数据流:输入一个父方案名 → 如果是支持继承的内置名,就先生成对应文件系统沙箱策略,再转成 TOML 风格的配置对象 → 输出可继承配置;不支持则输出空。

调用关系:它被 resolve_permission_profile 作为回调使用;内部会调用 permission_profile_toml_from_file_system_policy,把运行时策略转回可合并的配置形态。

调用图:调用 3 个内部函数(permission_profile_toml_from_file_system_policy, read_only, workspace_write)。

permission_profile_toml_from_file_system_policy216–233 ↗
fn permission_profile_toml_from_file_system_policy(
    file_system: FileSystemSandboxPolicy,
) -> PermissionProfileToml

作用:把内部文件系统沙箱策略倒回成配置文件那种结构。这样内置方案也能像普通配置一样被用户方案继承和扩展。

数据流:输入文件系统沙箱策略 → 新建一个文件系统配置表 → 把每条沙箱规则插入成 TOML 权限项 → 输出一个权限方案配置对象。

调用关系:它由 extensible_builtin_parent_profile 调用;具体每条规则怎么写进表里,会交给 insert_filesystem_permission_toml。

调用图:调用 1 个内部函数(insert_filesystem_permission_toml);被 1 处调用(extensible_builtin_parent_profile);外部调用 1 个(new)。

insert_filesystem_permission_toml235–253 ↗
fn insert_filesystem_permission_toml(
    entries: &mut BTreeMap<String, FilesystemPermissionToml>,
    entry: FileSystemSandboxEntry,
)

作用:把一条内部文件权限规则写进配置表。它负责区分普通路径、通配符模式和特殊路径。

数据流:输入配置表和一条文件系统规则 → 如果是普通路径就用路径字符串做键;如果是通配符就用模式字符串做键;如果是特殊路径就交给专门函数处理 → 配置表被新增或更新一项。

调用关系:它由 permission_profile_toml_from_file_system_policy 逐条调用;遇到特殊路径时会继续交给 insert_special_filesystem_permission_toml。

调用图:调用 1 个内部函数(insert_special_filesystem_permission_toml);被 1 处调用(permission_profile_toml_from_file_system_policy);外部调用 1 个(Access)。

insert_special_filesystem_permission_toml255–301 ↗
fn insert_special_filesystem_permission_toml(
    entries: &mut BTreeMap<String, FilesystemPermissionToml>,
    value: FileSystemSpecialPath,
    access: FileSystemAccessMode,
)

作用:把“特殊路径”规则写回配置表,比如根目录、最小必需目录、工作区根目录、临时目录。特殊路径不是普通磁盘路径,所以需要单独翻译。

数据流:输入配置表、特殊路径类型和访问权限 → 把内部特殊路径变成配置里使用的冒号名字;如果带子路径,就写成分层权限 → 配置表被更新。

调用关系:它被 insert_filesystem_permission_toml 调用;当特殊路径还有下级范围时,会把细节交给 insert_scoped_filesystem_permission_toml。

调用图:调用 1 个内部函数(insert_scoped_filesystem_permission_toml);被 1 处调用(insert_filesystem_permission_toml);外部调用 1 个(Access)。

insert_scoped_filesystem_permission_toml303–323 ↗
fn insert_scoped_filesystem_permission_toml(
    entries: &mut BTreeMap<String, FilesystemPermissionToml>,
    path: String,
    subpath: PathBuf,
    access: FileSystemAccessMode,
)

作用:把某个路径下面的子路径权限写成“分层配置”。这适合表达“工作区根目录下的某个目录可写”这种规则。

数据流:输入配置表、父路径名、子路径和访问权限 → 如果父路径还没有分层项就创建;如果原来是简单权限,就替换成分层权限 → 子路径权限被写入表中。

调用关系:它由 insert_special_filesystem_permission_toml 调用,是把特殊路径的子范围保存回 TOML 结构的最后一步。

调用图:被 1 处调用(insert_special_filesystem_permission_toml);外部调用 3 个(from, to_string_lossy, Scoped)。

network_proxy_config_for_profile_selection325–344 ↗
fn network_proxy_config_for_profile_selection(
    permissions: Option<&PermissionsToml>,
    profile_name: &str,
) -> io::Result<NetworkProxyConfig>

作用:根据当前选择的权限方案,拿到它对应的网络代理配置。内置方案没有自定义代理设置,所以直接给默认值。

数据流:输入可选权限配置和方案名 → 如果是内置方案就返回默认代理配置;如果是冒号开头但未知的内置名就报错;否则解析用户方案并提取 network 部分 → 输出代理配置或错误。

调用关系:它被 load_config_with_layer_stack 和 network_proxy_spec_for_active_permission_profile 调用;内部会用 is_builtin_permission_profile_name、reject_unknown_builtin_permission_profile、resolve_permission_profile 和 network_proxy_config_from_profile_network 串起整个判断链。

调用图:调用 4 个内部函数(is_builtin_permission_profile_name, network_proxy_config_from_profile_network, reject_unknown_builtin_permission_profile, resolve_permission_profile);被 2 处调用(load_config_with_layer_stack, network_proxy_spec_for_active_permission_profile);外部调用 1 个(default)。

compile_permission_profile346–409 ↗
fn compile_permission_profile(
    permissions: &PermissionsToml,
    profile_name: &str,
    policy_cwd: &Path,
    startup_warnings: &mut Vec<String>,
) -> io::Result<(FileSystemSandboxPolicy, Netwo

作用:把一个已经命名的用户权限方案编译成真正运行时使用的文件系统沙箱策略和网络沙箱策略。这里是自定义权限配置落地的核心步骤。

数据流:输入权限配置、方案名、配置相对路径基准目录和启动警告列表 → 解析方案,检查文件系统规则是否为空,提示平台不支持的通配符写法,逐条编译路径权限,验证 glob_scan_max_depth,再编译网络开关 → 输出文件系统策略和网络策略,并可能追加警告。

调用关系:它由 compile_permission_profile_selection 调用;内部把配置解析交给 resolve_permission_profile,把每条文件权限交给 compile_filesystem_permission,把网络部分交给 compile_network_sandbox_policy,还用多个检查函数生成友好警告。

调用图:调用 9 个内部函数(compile_filesystem_permission, compile_network_sandbox_policy, missing_filesystem_entries_warning, push_warning, resolve_permission_profile, unbounded_unreadable_globstar_paths, unsupported_read_write_glob_paths, validate_glob_scan_max_depth, restricted);被 1 处调用(compile_permission_profile_selection);外部调用 3 个(new, cfg!, format!)。

compile_permission_profile_selection411–430 ↗
fn compile_permission_profile_selection(
    permissions: Option<&PermissionsToml>,
    profile_name: &str,
    workspace_write: Option<&SandboxWorkspaceWrite>,
    policy_cwd: &Path,
    startup_warn

作用:根据用户最终选中的权限方案,生成运行时权限。它先处理内置方案,处理不了才进入自定义方案编译。

数据流:输入可选权限配置、方案名、工作区写入设置、路径基准目录和警告列表 → 尝试把名字当内置方案生成权限;如果不是内置,就拒绝未知冒号方案,再要求存在 permissions 配置表并编译自定义方案 → 输出文件系统和网络沙箱策略。

调用关系:它被 load_config_with_layer_stack 调用,是配置加载时生成最终沙箱权限的总入口之一;它会调用 builtin_permission_profile、reject_unknown_builtin_permission_profile 和 compile_permission_profile。

调用图:调用 3 个内部函数(builtin_permission_profile, compile_permission_profile, reject_unknown_builtin_permission_profile);被 1 处调用(load_config_with_layer_stack)。

compile_permission_profile_workspace_roots432–453 ↗
fn compile_permission_profile_workspace_roots(
    permissions: Option<&PermissionsToml>,
    profile_name: &str,
    policy_cwd: &Path,
) -> io::Result<Vec<AbsolutePathBuf>>

作用:从自定义权限方案里提取额外的工作区根目录。工作区根目录会影响哪些项目目录被当成“允许范围”的一部分。

数据流:输入可选权限配置、方案名和路径基准目录 → 内置方案直接返回空列表;自定义方案先解析,再读取 workspace_roots 配置并转成绝对路径 → 输出绝对路径列表。

调用关系:它被 load_config_with_layer_stack 调用;它先用 is_builtin_permission_profile_name 和 reject_unknown_builtin_permission_profile 做名字判断,再用 resolve_permission_profile 和 compile_workspace_roots 完成实际转换。

调用图:调用 4 个内部函数(compile_workspace_roots, is_builtin_permission_profile_name, reject_unknown_builtin_permission_profile, resolve_permission_profile);被 1 处调用(load_config_with_layer_stack);外部调用 1 个(new)。

compile_workspace_roots455–465 ↗
fn compile_workspace_roots(
    workspace_roots: Option<&WorkspaceRootsToml>,
    policy_cwd: &Path,
) -> Vec<AbsolutePathBuf>

作用:把配置里的工作区根目录列表变成绝对路径列表。这样后续沙箱不用猜相对路径到底相对哪里。

数据流:输入可选的 workspace_roots 配置和基准目录 → 没配置就返回空列表;有配置就只取启用的根目录,并逐个按基准目录解析成绝对路径 → 输出绝对路径数组。

调用关系:它只被 compile_permission_profile_workspace_roots 调用,是工作区根目录编译流程里的小转换器。

调用图:被 1 处调用(compile_permission_profile_workspace_roots)。

reject_unknown_builtin_permission_profile467–476 ↗
fn reject_unknown_builtin_permission_profile(profile_name: &str) -> io::Result<()>

作用:拒绝看起来像内置方案、但当前版本不认识的方案名。这样可以避免用户拼错内置名字后被当成普通自定义方案。

数据流:输入方案名 → 如果以冒号开头就返回“未知内置方案”的错误;否则返回成功 → 不产生其他改动。

调用关系:compile_permission_profile_selection、compile_permission_profile_workspace_roots 和 network_proxy_config_for_profile_selection 都会调用它,用来统一守住内置名字空间。

调用图:被 3 处调用(compile_permission_profile_selection, compile_permission_profile_workspace_roots, network_proxy_config_for_profile_selection);外部调用 2 个(new, format!)。

get_readable_roots_required_for_codex_runtime481–507 ↗
fn get_readable_roots_required_for_codex_runtime(
    codex_home: &Path,
    zsh_path: Option<&PathBuf>,
    main_execve_wrapper_exe: Option<&PathBuf>,
) -> Vec<AbsolutePathBuf>

作用:列出 Codex 自己运行时必须能读取的几个路径,比如 shell 或执行包装器的位置。即使用户权限很严,也不能把这些基础工具完全挡住,否则 Codex 自己会跑不起来。

数据流:输入 Codex 主目录、可选 zsh 路径、可选 execve 包装器路径 → 尝试把这些路径转成绝对路径,并对位于 arg0 临时目录下的包装器取父目录 → 输出必须额外可读的路径列表。

调用关系:它被 load_config_with_layer_stack 调用,用在加载配置时补齐运行所需的最低可读路径。

调用图:调用 1 个内部函数(from_absolute_path);被 1 处调用(load_config_with_layer_stack);外部调用 2 个(join, new)。

compile_network_sandbox_policy509–522 ↗
fn compile_network_sandbox_policy(
    network: Option<&NetworkToml>,
    base_network_sandbox_policy: NetworkSandboxPolicy,
) -> NetworkSandboxPolicy

作用:把权限方案里的网络 enabled 开关变成运行时网络沙箱策略。简单说,就是决定网络是放开还是受限。

数据流:输入可选网络配置和基础网络策略 → 没有配置就沿用基础策略;enabled 为 true 就允许网络;enabled 为 false 就限制网络;没写 enabled 也沿用基础策略 → 输出网络策略。

调用关系:它由 compile_permission_profile 调用,是自定义权限方案编译到最后生成网络策略的步骤。

调用图:被 1 处调用(compile_permission_profile)。

compile_filesystem_permission524–569 ↗
fn compile_filesystem_permission(
    path: &str,
    permission: &FilesystemPermissionToml,
    policy_cwd: &Path,
    startup_warnings: &mut Vec<String>,
) -> io::Result<Vec<FileSystemSandboxEntry>>

作用:把配置表里一条文件系统权限编译成一条或多条沙箱规则。它处理简单路径,也处理“某个父路径下多个子路径”的分层写法。

数据流:输入路径键、权限值、基准目录和警告列表 → 如果是简单权限,就编译路径本身;如果是分层权限,就逐个处理子路径,必要时把 deny 通配符变成模式规则 → 输出沙箱规则列表。

调用关系:它由 compile_permission_profile 对每条配置调用;内部会根据情况把工作交给 compile_filesystem_access_path、compile_scoped_filesystem_path、compile_scoped_filesystem_pattern 或 compile_read_write_glob_path。

调用图:调用 6 个内部函数(compile_filesystem_access_path, compile_read_write_glob_path, compile_scoped_filesystem_path, compile_scoped_filesystem_pattern, contains_glob_chars, parse_special_path);被 1 处调用(compile_permission_profile);外部调用 1 个(new)。

compile_filesystem_access_path571–592 ↗
fn compile_filesystem_access_path(
    path: &str,
    access: FileSystemAccessMode,
    startup_warnings: &mut Vec<String>,
) -> io::Result<FileSystemPath>

作用:编译一条没有分层子项的文件路径权限。它特别处理通配符:拒绝访问可以用模式,读写访问则只允许安全的尾部 /** 写法。

数据流:输入路径字符串、访问权限和警告列表 → 如果没有通配符就按普通路径编译;如果是 deny 通配符,就先要求它是绝对路径再变成模式;如果是读写通配符,就检查并去掉尾部 /** 后再按普通路径处理 → 输出内部文件路径表达。

调用关系:它由 compile_filesystem_permission 调用;普通路径会继续交给 compile_filesystem_path,复杂读写通配符会交给 compile_read_write_glob_path 检查。

调用图:调用 4 个内部函数(compile_filesystem_path, compile_read_write_glob_path, contains_glob_chars, parse_absolute_path);被 1 处调用(compile_filesystem_permission)。

compile_filesystem_path594–605 ↗
fn compile_filesystem_path(
    path: &str,
    startup_warnings: &mut Vec<String>,
) -> io::Result<FileSystemPath>

作用:把一个顶层路径字符串变成内部文件路径对象。它能识别冒号开头的特殊路径,也能要求普通路径必须是绝对路径。

数据流:输入路径字符串和警告列表 → 先看是不是特殊路径;未知特殊路径会记录警告但仍保留为可忽略对象;普通路径则解析为绝对路径 → 输出内部路径对象或错误。

调用关系:它被 compile_filesystem_access_path 和 compile_scoped_filesystem_path 使用;它会调用 parse_special_path、maybe_push_unknown_special_path_warning 和 parse_absolute_path。

调用图:调用 3 个内部函数(maybe_push_unknown_special_path_warning, parse_absolute_path, parse_special_path);被 2 处调用(compile_filesystem_access_path, compile_scoped_filesystem_path)。

compile_scoped_filesystem_path607–640 ↗
fn compile_scoped_filesystem_path(
    path: &str,
    subpath: &str,
    startup_warnings: &mut Vec<String>,
) -> io::Result<FileSystemPath>

作用:编译“父路径 + 子路径”这种分层文件权限。比如把“/repo”下面的“src”拼成“/repo/src”,或者把“工作区根目录”下面的子目录保留成特殊规则。

数据流:输入父路径、子路径和警告列表 → 子路径是点号时直接当父路径处理;如果父路径是特殊路径,就检查子路径必须是干净的相对路径并生成特殊子路径规则;如果父路径是普通绝对路径,就把两者拼成绝对路径 → 输出内部路径对象。

调用关系:它由 compile_filesystem_permission 调用;它会借助 parse_special_path、parse_relative_subpath、parse_absolute_path 和 resolve_path_against_base,并在未知特殊路径时调用 maybe_push_unknown_special_path_warning。

调用图:调用 6 个内部函数(compile_filesystem_path, maybe_push_unknown_special_path_warning, parse_absolute_path, parse_relative_subpath, parse_special_path, resolve_path_against_base);被 1 处调用(compile_filesystem_permission);外部调用 4 个(project_roots, unknown, new, format!)。

compile_scoped_filesystem_pattern642–675 ↗
fn compile_scoped_filesystem_pattern(
    path: &str,
    subpath: &str,
    access: FileSystemAccessMode,
    _policy_cwd: &Path,
) -> io::Result<String>

作用:把分层配置里的通配符 deny 子路径编译成沙箱模式。这里目前只支持拒绝访问,不支持用通配符来放开读写。

数据流:输入父路径、通配符子路径、访问权限和基准目录 → 先确认访问权限必须是 deny,再检查子路径是安全相对路径;如果父路径是工作区根目录,就生成符号化的工作区 glob 模式;如果是普通绝对路径,就拼成完整模式 → 输出模式字符串或错误。

调用关系:它由 compile_filesystem_permission 在遇到可编译的 deny 通配符子项时调用;内部使用 parse_relative_subpath、parse_special_path、parse_absolute_path 和 project_roots_glob_pattern。

调用图:调用 4 个内部函数(parse_absolute_path, parse_relative_subpath, parse_special_path, project_roots_glob_pattern);被 1 处调用(compile_filesystem_permission);外部调用 2 个(new, format!)。

compile_read_write_glob_path677–693 ↗
fn compile_read_write_glob_path(path: &str, access: FileSystemAccessMode) -> io::Result<&str>

作用:检查读写权限里的通配符路径是否安全。当前只允许“某个目录/**”这种表示整棵子树的写法,不允许更复杂的读写 glob。

数据流:输入路径和访问权限 → 如果没有通配符就原样返回;如果只有尾部 /**,就去掉这个后缀返回目录路径;如果还有其他通配符,就返回错误并说明只能用于 deny 或改成安全写法 → 输出可继续解析的路径字符串。

调用关系:它被 compile_filesystem_access_path 和 compile_filesystem_permission 调用,是读写权限遇到通配符时的安全阀。

调用图:调用 2 个内部函数(contains_glob_chars, remove_trailing_glob_suffix);被 2 处调用(compile_filesystem_access_path, compile_filesystem_permission);外部调用 2 个(new, format!)。

unsupported_read_write_glob_paths695–718 ↗
fn unsupported_read_write_glob_paths(filesystem: &FilesystemPermissionsToml) -> Vec<String>

作用:找出文件权限里那些在非 macOS 平台上不太可靠的读写通配符。它不直接报错,而是帮助生成启动警告。

数据流:输入文件系统权限配置 → 遍历顶层和分层条目,找出非 deny 权限里除尾部 /** 之外还带通配符的路径 → 输出这些问题路径列表。

调用关系:它由 compile_permission_profile 在非 macOS 平台调用;找到的路径会被包装成警告,通过 push_warning 放进启动警告列表。

调用图:调用 2 个内部函数(contains_glob_chars, remove_trailing_glob_suffix);被 1 处调用(compile_permission_profile);外部调用 2 个(new, format!)。

unbounded_unreadable_globstar_paths720–744 ↗
fn unbounded_unreadable_globstar_paths(filesystem: &FilesystemPermissionsToml) -> Vec<String>

作用:找出 deny 规则里没有深度限制的 通配符。因为某些平台不能原生处理无限层级的 ,需要提醒用户设置扫描深度。

数据流:输入文件系统权限配置 → 如果已经设置 glob_scan_max_depth 就不用警告;否则遍历 deny 规则,找出包含 ** 的路径或子路径 → 输出这些路径列表。

调用关系:它由 compile_permission_profile 调用;结果会变成启动警告,提示用户设置 glob_scan_max_depth 或枚举明确深度。

调用图:被 1 处调用(compile_permission_profile);外部调用 2 个(new, format!)。

validate_glob_scan_max_depth746–754 ↗
fn validate_glob_scan_max_depth(max_depth: Option<usize>) -> io::Result<Option<usize>>

作用:检查通配符扫描最大深度是否合法。深度不能是 0,因为 0 没有实际扫描意义。

数据流:输入可选最大深度 → 如果是 Some(0) 就返回配置错误;其他值原样通过 → 输出合法的可选深度。

调用关系:它由 compile_permission_profile 调用,用来在把 glob_scan_max_depth 写入运行时策略前做最后校验。

调用图:被 1 处调用(compile_permission_profile);外部调用 1 个(new)。

contains_glob_chars756–758 ↗
fn contains_glob_chars(path: &str) -> bool

作用:判断路径字符串里有没有通配符字符,比如 *、?、[、]。这决定后面要按普通路径还是模式路径处理。

数据流:输入路径字符串 → 按当前平台选择是否做 Windows 路径规范化 → 检查是否包含通配符字符 → 输出 true 或 false。

调用关系:它被 compile_filesystem_access_path、compile_filesystem_permission、compile_read_write_glob_path 和 unsupported_read_write_glob_paths 调用;实际平台差异交给 contains_glob_chars_for_platform。

调用图:调用 1 个内部函数(contains_glob_chars_for_platform);被 4 处调用(compile_filesystem_access_path, compile_filesystem_permission, compile_read_write_glob_path, unsupported_read_write_glob_paths);外部调用 1 个(cfg!)。

contains_glob_chars_for_platform760–768 ↗
fn contains_glob_chars_for_platform(path: &str, is_windows: bool) -> bool

作用:按指定平台规则检查路径中是否有通配符。Windows 下会先把某些特殊设备路径写法变成普通写法,避免误判。

数据流:输入路径字符串和是否 Windows 的标志 → 如果是 Windows,先尝试规范化设备路径;然后扫描字符 → 输出是否含有 glob 通配符。

调用关系:它由 contains_glob_chars 调用;Windows 特殊路径处理会交给 normalize_windows_device_path。

调用图:调用 1 个内部函数(normalize_windows_device_path);被 1 处调用(contains_glob_chars)。

remove_trailing_glob_suffix770–772 ↗
fn remove_trailing_glob_suffix(path: &str) -> &str

作用:去掉路径末尾的 /** 后缀。这个后缀在配置里表示“整个目录树”,但真正用于读写路径时需要还原成目录本身。

数据流:输入路径字符串 → 如果末尾是 /** 就返回去掉后的部分,否则返回原字符串 → 不分配新路径,只返回原字符串的一段。

调用关系:它被 compile_read_write_glob_path 和 unsupported_read_write_glob_paths 使用,用来判断通配符是否只是安全的尾部子树写法。

调用图:被 2 处调用(compile_read_write_glob_path, unsupported_read_write_glob_paths)。

parse_special_path779–791 ↗
fn parse_special_path(path: &str) -> Option<FileSystemSpecialPath>

作用:识别冒号开头的特殊文件路径名,比如 :root、:workspace_roots、:tmpdir。它还会保留未知特殊路径,方便旧版本遇到新配置时给警告而不是直接崩掉。

数据流:输入路径字符串 → 和已知特殊名比较;已知就生成对应特殊路径对象;冒号开头但未知就生成 Unknown;普通路径返回空 → 输出可选特殊路径对象。

调用关系:它被 compile_filesystem_path、compile_filesystem_permission、compile_scoped_filesystem_path 和 compile_scoped_filesystem_pattern 调用,是特殊路径解析的统一入口。

调用图:被 4 处调用(compile_filesystem_path, compile_filesystem_permission, compile_scoped_filesystem_path, compile_scoped_filesystem_pattern);外部调用 2 个(project_roots, unknown)。

parse_absolute_path793–795 ↗
fn parse_absolute_path(path: &str) -> io::Result<AbsolutePathBuf>

作用:把字符串解析成绝对路径对象,并使用当前系统的平台规则。普通文件权限必须是绝对路径、家目录写法,或者特殊冒号路径。

数据流:输入路径字符串 → 按当前是否 Windows 调用平台版解析 → 输出绝对路径对象或配置错误。

调用关系:它被 compile_filesystem_access_path、compile_filesystem_path、compile_scoped_filesystem_path 和 compile_scoped_filesystem_pattern 调用;真正检查逻辑在 parse_absolute_path_for_platform。

调用图:调用 1 个内部函数(parse_absolute_path_for_platform);被 4 处调用(compile_filesystem_access_path, compile_filesystem_path, compile_scoped_filesystem_path, compile_scoped_filesystem_pattern);外部调用 1 个(cfg!)。

parse_absolute_path_for_platform797–809 ↗
fn parse_absolute_path_for_platform(path: &str, is_windows: bool) -> io::Result<AbsolutePathBuf>

作用:按指定平台规则验证并创建绝对路径。它处理 Unix 和 Windows 对“绝对路径”的不同判断方式。

数据流:输入路径字符串和是否 Windows → 先规范化可能的 Windows 特殊路径,再判断是不是绝对路径或家目录写法;不符合就报错;符合就转成 AbsolutePathBuf → 输出绝对路径对象。

调用关系:它由 parse_absolute_path 调用;内部会用 normalize_absolute_path_for_platform 和 is_absolute_path_for_platform 做平台差异处理。

调用图:调用 3 个内部函数(is_absolute_path_for_platform, normalize_absolute_path_for_platform, from_absolute_path);被 1 处调用(parse_absolute_path);外部调用 2 个(new, format!)。

is_absolute_path_for_platform811–818 ↗
fn is_absolute_path_for_platform(path: &str, normalized_path: &Path, is_windows: bool) -> bool

作用:判断一个路径在指定平台上算不算绝对路径。Windows 的盘符和网络路径规则跟 Unix 不一样,所以要分开看。

数据流:输入原始路径、规范化后的 Path、是否 Windows → Windows 下检查盘符绝对路径或反斜杠网络路径;非 Windows 下调用系统路径的 is_absolute → 输出 true 或 false。

调用关系:它被 parse_absolute_path_for_platform 调用;Windows 情况下会继续使用 is_windows_absolute_path。

调用图:调用 1 个内部函数(is_windows_absolute_path);被 1 处调用(parse_absolute_path_for_platform);外部调用 2 个(is_absolute, to_string_lossy)。

normalize_absolute_path_for_platform820–829 ↗
fn normalize_absolute_path_for_platform(path: &str, is_windows: bool) -> Cow<'_, Path>

作用:在需要时把 Windows 的设备路径写法转换成普通路径写法。非 Windows 平台则不动原路径。

数据流:输入路径字符串和是否 Windows → 非 Windows 直接借用原路径;Windows 下尝试规范化 \\?\ 或 \\.\ 这类前缀 → 输出借用路径或新建路径。

调用关系:它由 parse_absolute_path_for_platform 调用;具体 Windows 前缀识别交给 normalize_windows_device_path。

调用图:调用 1 个内部函数(normalize_windows_device_path);被 1 处调用(parse_absolute_path_for_platform);外部调用 4 个(Borrowed, Owned, new, from)。

normalize_windows_device_path831–849 ↗
fn normalize_windows_device_path(path: &str) -> Option<String>

作用:把 Windows 的特殊设备路径前缀改成更普通的路径形式。比如把某些 UNC 设备路径还原成网络共享路径。

数据流:输入路径字符串 → 识别 \\?\UNC\、\\.\UNC\、\\?\C:\、\\.\C:\ 等形式 → 如果能规范化就输出新字符串,否则输出空。

调用关系:它被 contains_glob_chars_for_platform 和 normalize_absolute_path_for_platform 调用;识别盘符绝对路径时会使用 is_windows_drive_absolute_path。

调用图:调用 1 个内部函数(is_windows_drive_absolute_path);被 2 处调用(contains_glob_chars_for_platform, normalize_absolute_path_for_platform);外部调用 1 个(format!)。

is_windows_absolute_path851–853 ↗
fn is_windows_absolute_path(path: &str) -> bool

作用:判断字符串是不是 Windows 绝对路径。它覆盖盘符路径和网络共享路径两种常见形式。

数据流:输入路径字符串 → 检查是不是 C:\ 这类盘符绝对路径,或者是否以双反斜杠开头 → 输出 true 或 false。

调用关系:它被 is_absolute_path_for_platform 调用;盘符部分的细节交给 is_windows_drive_absolute_path。

调用图:调用 1 个内部函数(is_windows_drive_absolute_path);被 1 处调用(is_absolute_path_for_platform)。

is_windows_drive_absolute_path855–861 ↗
fn is_windows_drive_absolute_path(path: &str) -> bool

作用:判断路径是不是 Windows 盘符开头的绝对路径,比如 C:\foo 或 D:/bar。

数据流:输入路径字符串 → 查看前三个字节是否符合“字母 + 冒号 + 斜杠或反斜杠” → 输出 true 或 false。

调用关系:它被 is_windows_absolute_path 和 normalize_windows_device_path 调用,是 Windows 路径判断里最底层的小检查。

调用图:被 2 处调用(is_windows_absolute_path, normalize_windows_device_path);外部调用 1 个(matches!)。

parse_relative_subpath863–880 ↗
fn parse_relative_subpath(subpath: &str) -> io::Result<PathBuf>

作用:检查子路径是不是安全的相对后代路径。它禁止空路径、点号、双点号等可能跳出父目录的写法。

数据流:输入子路径字符串 → 转成 Path 并检查每个组成部分都只是普通名字 → 合法则输出 PathBuf;否则返回配置错误。

调用关系:它被 compile_scoped_filesystem_path 和 compile_scoped_filesystem_pattern 调用,用来保证分层权限不会通过 ../ 之类写法绕出父路径。

调用图:被 2 处调用(compile_scoped_filesystem_path, compile_scoped_filesystem_pattern);外部调用 3 个(new, new, format!)。

push_warning882–885 ↗
fn push_warning(startup_warnings: &mut Vec<String>, message: String)

作用:统一记录启动警告。它既写日志,也把警告文字放进列表,方便之后展示给用户。

数据流:输入可修改的警告列表和一条消息 → 通过 tracing 写一条 warn 日志 → 把消息追加到列表里。

调用关系:它被 compile_permission_profile 和 maybe_push_unknown_special_path_warning 调用,是权限配置警告的集中出口。

调用图:被 2 处调用(compile_permission_profile, maybe_push_unknown_special_path_warning);外部调用 1 个(warn!)。

missing_filesystem_entries_warning887–891 ↗
fn missing_filesystem_entries_warning(profile_name: &str) -> String

作用:生成“这个权限方案没有可识别文件系统规则”的提示文字。这样用户知道当前文件权限会保持受限,不是程序默默忽略了。

数据流:输入权限方案名 → 拼出一段说明该方案没有被当前版本识别到文件系统条目的警告文本 → 输出字符串。

调用关系:它被 compile_permission_profile 调用;生成的文字会再交给 push_warning 记录。

调用图:被 1 处调用(compile_permission_profile);外部调用 1 个(format!)。

maybe_push_unknown_special_path_warning893–912 ↗
fn maybe_push_unknown_special_path_warning(
    special: &FileSystemSpecialPath,
    startup_warnings: &mut Vec<String>,
)

作用:如果配置里用了当前版本不认识的特殊路径,就给用户一条警告。这样新版本配置被旧版本读取时,不会直接失败,但用户能知道有规则被忽略了。

数据流:输入特殊路径对象和警告列表 → 如果不是 Unknown 就什么都不做;如果是未知特殊路径,就根据是否带子路径生成警告 → 调用 push_warning 记录。

调用关系:它被 compile_filesystem_path 和 compile_scoped_filesystem_path 调用;它把未知特殊路径问题转交给 push_warning 做统一日志和收集。

调用图:调用 1 个内部函数(push_warning);被 2 处调用(compile_filesystem_path, compile_scoped_filesystem_path);外部调用 1 个(format!)。

core/src/config/resolved_permission_profile.rs源码 ↗
configconfig load, session state sync, runtime permission updates

权限档案可以理解成一套“这次运行允许做什么、不允许做什么”的规则。这个文件解决的是:同一套规则可能来自老配置、内置档案,或用户自定义档案,系统需要把它们包成统一格式。这里的 ResolvedPermissionProfile 就像贴好标签的文件夹:里面有具体权限规则,也可能带着“我是哪个档案”的名字和档案声明的工作区目录。PermissionProfileSnapshot 是一个可信快照,用来在会话之间传递“当时已经算好的权限状态”,但它自己不负责验证名字和内容是否匹配。PermissionProfileState 再加上一层 Constrained(带限制的盒子,防止之后改成不被允许的权限),让运行中切换权限时仍然不能越过配置约束。没有这个文件,权限规则和档案身份容易脱节,可能导致界面显示一个档案,实际执行另一套权限。

函数细节24
BuiltInPermissionProfileId::from_str18–25 ↗
fn from_str(id: &str) -> Option<Self>

作用:把外部传进来的档案名字,认成系统内置的三种权限档案之一。认不出来就说明它不是内置档案,可能是用户自定义档案。

数据流:输入一个字符串 id → 和三个固定的内置名字逐个比较 → 如果匹配就输出对应的内置枚举值,不匹配就输出空值。

调用关系:ResolvedPermissionProfile::from_active_profile 在整理当前激活档案时会先问它一句:这个名字是不是内置的?如果是,就走内置档案分支;如果不是,就当作命名档案处理。

调用图:被 1 处调用(from_active_profile)。

BuiltInPermissionProfileId::as_str27–33 ↗
fn as_str(self) -> &'static str

作用:把系统内部保存的内置档案编号,转换回对外使用的固定字符串名字。这样会话状态或前端界面能看到熟悉的档案 id。

数据流:输入一个内置档案枚举值 → 按 ReadOnly、Workspace、DangerFullAccess 三种情况选择固定常量 → 输出对应的字符串。

调用关系:它是 from_str 的反向操作。系统需要把已解析的内置档案重新包装成 ActivePermissionProfile 时,会用它恢复原来的公开名字。

ResolvedPermissionProfile::from_active_profile78–103 ↗
fn from_active_profile(
        permission_profile: PermissionProfile,
        active_permission_profile: Option<ActivePermissionProfile>,
        profile_workspace_roots: Vec<AbsolutePathBuf>,
    )

作用:把“具体权限规则”和“当前激活的档案信息”合成一个已解析的权限档案。它会分清这是老式无名权限、内置档案,还是用户自定义档案。

数据流:输入具体 PermissionProfile、可选的 ActivePermissionProfile、以及档案声明的工作区根路径 → 如果没有激活档案信息,就做成 Legacy;如果有,就先判断 id 是否内置,再分别做成 BuiltIn 或 Named → 输出统一的 ResolvedPermissionProfile。

调用关系:PermissionProfileSnapshot::active_with_profile_workspace_roots 和 PermissionProfileState::from_constrained_active_profile 都会调用它。它在权限配置刚被选中或加载时,把零散信息装进统一容器,后面其他代码就不用反复判断来源。

调用图:调用 1 个内部函数(from_str);被 2 处调用(active_with_profile_workspace_roots, from_constrained_active_profile);外部调用 3 个(BuiltIn, Named, legacy)。

ResolvedPermissionProfile::legacy105–107 ↗
fn legacy(permission_profile: PermissionProfile) -> Self

作用:创建一种老式权限档案:只有具体权限规则,没有“当前选中了哪个档案”的名字。用于兼容旧配置或临时覆盖。

数据流:输入一个 PermissionProfile → 包进 LegacyPermissionProfile → 输出 ResolvedPermissionProfile::Legacy。

调用关系:很多入口会用它处理无名权限,例如 PermissionProfileSnapshot::legacy、PermissionProfileState::from_constrained_legacy、以及运行中检查或设置老式权限时。

调用图:被 4 处调用(legacy, can_set_legacy_permission_profile, from_constrained_legacy, set_legacy_permission_profile);外部调用 1 个(Legacy)。

ResolvedPermissionProfile::permission_profile109–115 ↗
fn permission_profile(&self) -> &PermissionProfile

作用:不管权限档案来自哪里,都取出里面真正的权限规则。调用者不用关心它是老式、内置,还是自定义。

数据流:输入一个已解析权限档案的引用 → 按当前变体找到内部的 permission_profile 字段 → 输出这份具体权限规则的只读引用。

调用关系:PermissionProfileSnapshot 和 PermissionProfileState 的同名读取方法都会靠它取实际规则。它是其他模块查看“真正允许什么”的统一出口。

调用图:被 1 处调用(permission_profile)。

ResolvedPermissionProfile::active_permission_profile117–129 ↗
fn active_permission_profile(&self) -> Option<ActivePermissionProfile>

作用:取出当前激活档案的身份信息。老式权限没有身份,所以会返回空。

数据流:输入一个已解析权限档案 → 如果是 Legacy,输出空值;如果是 BuiltIn,就把内置编号转成字符串并带上 extends;如果是 Named,就复制自定义 id 和 extends → 输出可选的 ActivePermissionProfile。

调用关系:PermissionProfileSnapshot 和 PermissionProfileState 需要向会话、界面或其他配置层报告当前选中档案时,会通过它恢复“档案名字”。

调用图:被 1 处调用(active_permission_profile)。

ResolvedPermissionProfile::profile_workspace_roots131–137 ↗
fn profile_workspace_roots(&self) -> &[AbsolutePathBuf]

作用:取出这个权限档案自己声明的工作区根目录。老式权限没有这类附加目录,所以返回空列表。

数据流:输入一个已解析权限档案 → 如果是 BuiltIn 或 Named,就返回保存的 profile_workspace_roots;如果是 Legacy,就返回空切片 → 不复制数据,只给只读视图。

调用关系:PermissionProfileSnapshot 和 PermissionProfileState 的读取方法会调用它,让权限系统能把档案自带的工作目录和运行时临时工作目录区分开。

调用图:被 1 处调用(profile_workspace_roots)。

PermissionProfileSnapshot::legacy146–150 ↗
fn legacy(permission_profile: PermissionProfile) -> Self

作用:创建一个没有激活档案名字的权限快照。适合旧数据,或确实不属于任何命名档案的本地覆盖。

数据流:输入具体 PermissionProfile → 调用 ResolvedPermissionProfile::legacy 包成老式已解析档案 → 输出 PermissionProfileSnapshot。

调用关系:设置权限投影、继承父线程运行时设置、更新功能开关、同步会话配置等流程会用它保存无名权限状态。它明确表示“这里只有规则,没有档案身份”。

调用图:调用 1 个内部函数(legacy);被 4 处调用(set_permission_profile_projection, side_fork_config_inherits_parent_thread_runtime_settings, update_feature_flags_disabling_guardian_clears_review_policy_and_restores_default, session_configured_syncs_widget_config_permissions_and_cwd)。

PermissionProfileSnapshot::active158–167 ↗
fn active(
        permission_profile: PermissionProfile,
        active_permission_profile: ActivePermissionProfile,
    ) -> Self

作用:创建一个带当前激活档案名字的权限快照。用于调用者已经可信地知道“这个名字”和“这套权限规则”是一起解析出来的场景。

数据流:输入具体 PermissionProfile 和 ActivePermissionProfile → 用空的档案工作区根路径调用 active_with_profile_workspace_roots → 输出 PermissionProfileSnapshot。

调用关系:运行时应用配置、处理事件、同步界面权限选择、测试权限约束等很多地方会用它。它是最常用的“保存当前选中权限档案”的快捷入口。

调用图:被 21 处调用(permission_snapshot_setter_preserves_permission_constraints, apply, sync_auto_review_runtime_state_from_effective_config, try_set_builtin_active_permission_profile_on_config, handle_event, permission_settings_sync_updates_active_snapshot_without_rewriting_side_thread, profile_permissions_selection_emits_active_custom_profile, profile_permissions_selection_emits_auto_review_mode_event, profile_permissions_selection_emits_named_profile_event_only, profile_permissions_selection_popup_snapshot (+11 more));外部调用 2 个(active_with_profile_workspace_roots, new)。

PermissionProfileSnapshot::active_with_profile_workspace_roots175–187 ↗
fn active_with_profile_workspace_roots(
        permission_profile: PermissionProfile,
        active_permission_profile: ActivePermissionProfile,
        profile_workspace_roots: Vec<AbsolutePathBuf>

作用:创建一个带档案身份、并且保留档案自带工作区根目录的权限快照。适合选中的档案自己声明了额外可用目录的情况。

数据流:输入具体 PermissionProfile、ActivePermissionProfile、以及一组绝对路径根目录 → 调用 ResolvedPermissionProfile::from_active_profile 统一分类并打包 → 输出 PermissionProfileSnapshot。

调用关系:设置权限投影和检查状态栏工作区根目录时会用它。它比 active 多保留一份“这些目录是档案声明的”信息,避免和临时运行目录混在一起。

调用图:调用 1 个内部函数(from_active_profile);被 2 处调用(set_permission_profile_projection, status_permissions_workspace_roots_include_profile_defined_directories)。

PermissionProfileSnapshot::from_session_snapshot195–205 ↗
fn from_session_snapshot(
        permission_profile: PermissionProfile,
        active_permission_profile: Option<ActivePermissionProfile>,
    ) -> Self

作用:从会话里保存下来的信息重新拼出权限快照。它用于恢复 core 已经发出过的可信会话状态。

数据流:输入具体 PermissionProfile 和可选 ActivePermissionProfile → 如果有激活档案,就走 active;如果没有,就走 legacy → 输出 PermissionProfileSnapshot。

调用关系:应用权限选择、运行时策略覆盖、功能开关更新、线程设置应用等流程会调用它。它的定位是“恢复已保存状态”,不是拿来随便解析用户新输入。

调用图:被 6 处调用(apply_permission_profile_selection, apply_runtime_policy_overrides, update_feature_flags, on_session_configured_with_display_and_fork_parent_title, apply_thread_settings, set_permission_profile_with_active_profile);外部调用 2 个(active, legacy)。

PermissionProfileSnapshot::permission_profile208–210 ↗
fn permission_profile(&self) -> &PermissionProfile

作用:从快照里取出真正的权限规则。调用者可以忽略快照里是否带档案名字。

数据流:输入一个 PermissionProfileSnapshot → 转给内部的 ResolvedPermissionProfile::permission_profile → 输出只读的 PermissionProfile 引用。

调用关系:替换会话快照中的权限档案时会用它查看快照里的实际规则。它是快照对外暴露具体权限内容的小窗口。

调用图:调用 1 个内部函数(permission_profile);被 1 处调用(replace_permission_profile_from_session_snapshot)。

PermissionProfileSnapshot::active_permission_profile213–215 ↗
fn active_permission_profile(&self) -> Option<ActivePermissionProfile>

作用:从快照里取出“当前激活的是哪个权限档案”。如果这个快照是老式无名权限,就返回空。

数据流:输入一个 PermissionProfileSnapshot → 询问内部 ResolvedPermissionProfile 的 active_permission_profile → 输出可选的 ActivePermissionProfile。

调用关系:它把内部分类细节藏起来,让外部只需要问快照本身,就能知道是否有当前档案身份。

调用图:调用 1 个内部函数(active_permission_profile)。

PermissionProfileSnapshot::profile_workspace_roots218–220 ↗
fn profile_workspace_roots(&self) -> &[AbsolutePathBuf]

作用:从快照里取出档案声明的工作区根目录列表。这个列表说明该档案额外认可哪些目录范围。

数据流:输入一个 PermissionProfileSnapshot → 转给内部 ResolvedPermissionProfile::profile_workspace_roots → 输出只读路径列表。

调用关系:它服务于需要安装或展示权限工作区范围的代码。外部不用知道这些根目录只存在于内置或命名档案里。

调用图:调用 1 个内部函数(profile_workspace_roots)。

PermissionProfileSnapshot::into_resolved_permission_profile222–224 ↗
fn into_resolved_permission_profile(self) -> ResolvedPermissionProfile

作用:把快照拆开,拿出里面的已解析权限档案本体。调用后快照会被消耗掉。

数据流:输入一个 PermissionProfileSnapshot 本身 → 取出 resolved_permission_profile 字段 → 输出 ResolvedPermissionProfile,并不再保留原快照。

调用关系:替换会话快照权限、或 PermissionProfileState::set_permission_profile_snapshot 安装快照时会用它。它是从“传递用快照”进入“运行中状态”的交接口。

调用图:被 2 处调用(replace_permission_profile_from_session_snapshot, set_permission_profile_snapshot)。

PermissionProfileState::from_constrained_legacy233–239 ↗
fn from_constrained_legacy(
        constrained_permission_profile: Constrained<PermissionProfile>,
    ) -> ConstraintResult<Self>

作用:用一份已经带限制的老式权限规则,创建运行中的权限状态。限制会继续保留,防止以后改成越界权限。

数据流:输入 Constrained<PermissionProfile>,也就是带规则限制的权限盒子 → 取出当前权限规则并包装成 Legacy → 交给 from_constrained_resolved 重新套上针对已解析档案的限制 → 输出 PermissionProfileState 或限制错误。

调用关系:从审批模式和权限档案创建配置时会调用它。它负责把旧格式权限接入新的统一状态模型。

调用图:调用 2 个内部函数(get, legacy);被 1 处调用(from_approval_and_profile);外部调用 1 个(from_constrained_resolved)。

PermissionProfileState::from_constrained_active_profile241–252 ↗
fn from_constrained_active_profile(
        constrained_permission_profile: Constrained<PermissionProfile>,
        active_permission_profile: Option<ActivePermissionProfile>,
        profile_workspac

作用:用一份已经带限制的权限规则,加上当前激活档案信息,创建运行中的权限状态。它既保留档案身份,也保留权限约束。

数据流:输入 Constrained<PermissionProfile>、可选 ActivePermissionProfile、以及档案工作区根路径 → 取出当前权限规则,调用 from_active_profile 组合成已解析档案 → 交给 from_constrained_resolved 安装限制 → 输出 PermissionProfileState 或错误。

调用关系:加载带层级的配置时会调用它。它是配置文件里的权限选择变成运行时状态的关键一步。

调用图:调用 2 个内部函数(get, from_active_profile);被 1 处调用(load_config_with_layer_stack);外部调用 1 个(from_constrained_resolved)。

PermissionProfileState::from_constrained_resolved254–268 ↗
fn from_constrained_resolved(
        constrained_permission_profile: Constrained<PermissionProfile>,
        resolved_permission_profile: ResolvedPermissionProfile,
    ) -> ConstraintResult<Self>

作用:把已解析权限档案放进一个带限制的状态盒子里。以后无论设置哪个档案,都必须先通过原来的权限约束检查。

数据流:输入带限制的 PermissionProfile 和一个 ResolvedPermissionProfile → 建立新的 Constrained<ResolvedPermissionProfile>;检查候选档案时,只看候选档案里的具体 PermissionProfile 是否被原限制允许 → 成功则输出 PermissionProfileState,失败则返回限制错误。

调用关系:老式构造、激活档案构造、以及从会话快照替换权限时都会走到这里。它是把“限制规则”从普通权限迁移到已解析权限档案上的核心关卡。

调用图:调用 1 个内部函数(new);被 1 处调用(replace_permission_profile_from_session_snapshot)。

PermissionProfileState::permission_profile270–272 ↗
fn permission_profile(&self) -> &PermissionProfile

作用:读取当前运行状态里的实际权限规则。外部模块用它判断当前允许读写、执行或联网到什么程度。

数据流:输入 PermissionProfileState → 从内部 Constrained 盒子取出当前 ResolvedPermissionProfile → 再取出具体 PermissionProfile → 输出只读引用。

调用关系:权限查询、网络沙箱策略等流程会调用它。它是运行时读取当前权限规则的常规入口。

调用图:调用 1 个内部函数(get);被 3 处调用(permission_profile, network_sandbox_policy, permission_profile)。

PermissionProfileState::active_permission_profile274–278 ↗
fn active_permission_profile(&self) -> Option<ActivePermissionProfile>

作用:读取当前运行状态里记录的激活权限档案身份。没有身份时返回空,表示这是老式或无名权限。

数据流:输入 PermissionProfileState → 从内部受限状态取出当前已解析档案 → 调用 active_permission_profile → 输出可选的 ActivePermissionProfile。

调用关系:外层配置对象或会话状态需要显示、同步当前选中权限档案时会调用它。它把状态里的档案身份安全地传出去。

调用图:调用 1 个内部函数(get);被 2 处调用(active_permission_profile, active_permission_profile)。

PermissionProfileState::profile_workspace_roots280–284 ↗
fn profile_workspace_roots(&self) -> &[AbsolutePathBuf]

作用:读取当前权限档案声明的工作区根目录。权限系统可用它决定哪些目录属于档案允许范围。

数据流:输入 PermissionProfileState → 从内部受限状态取出当前已解析档案 → 调用 profile_workspace_roots → 输出只读路径列表。

调用关系:外层权限对象或配置对象需要汇总工作区根目录时会调用它。它保证读取的是当前已安装档案的目录信息。

调用图:调用 1 个内部函数(get);被 2 处调用(profile_workspace_roots, profile_workspace_roots)。

PermissionProfileState::can_set_legacy_permission_profile286–292 ↗
fn can_set_legacy_permission_profile(
        &self,
        permission_profile: &PermissionProfile,
    ) -> ConstraintResult<()>

作用:预先检查:如果把当前状态改成某个老式无名权限,会不会违反限制。它只检查,不真正修改。

数据流:输入一个想要设置的 PermissionProfile 引用 → 复制这份权限并包装成 Legacy 候选值 → 交给内部 Constrained 的 can_set 检查 → 输出成功或限制错误,原状态不变。

调用关系:检查是否能设置老式沙箱策略或权限档案时会调用它。它像试穿衣服,不合身就提前告诉你,不会真的换上。

调用图:调用 2 个内部函数(can_set, legacy);被 2 处调用(can_set_legacy_sandbox_policy, can_set_permission_profile);外部调用 1 个(clone)。

PermissionProfileState::set_legacy_permission_profile294–300 ↗
fn set_legacy_permission_profile(
        &mut self,
        permission_profile: PermissionProfile,
    ) -> ConstraintResult<()>

作用:把当前权限状态真正改成一份老式无名权限。修改前仍然必须通过内部限制。

数据流:输入新的 PermissionProfile → 包装成 Legacy 候选值 → 调用内部 Constrained 的 set;如果限制允许就替换当前值,不允许就返回错误并保持原状。

调用关系:设置老式沙箱策略、设置权限档案、测试中设置权限时会调用它。它是老接口修改当前权限的实际执行点。

调用图:调用 2 个内部函数(set, legacy);被 3 处调用(set_legacy_sandbox_policy, set_permission_profile, set_permission_profile_for_tests)。

PermissionProfileState::set_permission_profile_snapshot302–308 ↗
fn set_permission_profile_snapshot(
        &mut self,
        snapshot: PermissionProfileSnapshot,
    ) -> ConstraintResult<()>

作用:用一个可信权限快照替换当前运行状态。它会保留快照里的档案身份和工作区根目录,但仍要过权限限制。

数据流:输入 PermissionProfileSnapshot → 先把快照拆成 ResolvedPermissionProfile → 调用内部 Constrained 的 set 尝试安装 → 成功则当前状态变成快照内容,失败则返回限制错误。

调用关系:从会话快照设置权限、或设置权限投影时会调用它。它是“外部已经整理好的权限快照”进入 PermissionProfileState 的主要入口。

调用图:调用 2 个内部函数(set, into_resolved_permission_profile);被 2 处调用(set_permission_profile_from_session_snapshot, set_permission_profile_projection)。

tui/src/permission_compat.rs源码 ↗
domain_logic权限配置转换时,通常在把权限交给旧接口或远端服务之前

权限配置就像一张门禁卡,说明程序能读哪些文件、能写哪些目录、能不能联网。新版本的门禁卡更细,但有些旧服务只认旧版门禁卡。这个文件的核心做法是:先试试看当前 PermissionProfile 能不能直接变成旧格式;如果能,就原样返回。不能的话,就重新拼一个旧格式能表达的权限:保留网络限制,保留额外可写目录,避免把当前工作目录重复算进去,还特别检查临时目录 TMPDIR 和 /tmp 是否真的可写。最后用 workspace_write_with 造出一个旧接口能接受的权限配置。这里重要的一点是,它不是随便放宽权限,而是在“旧格式能理解”的范围内尽量保留原来的限制和可写范围,防止远端服务因为看不懂新权限而出错。

函数细节2
legacy_compatible_permission_profile8–42 ↗
fn legacy_compatible_permission_profile(
    permission_profile: &PermissionProfile,
    cwd: &Path,
) -> PermissionProfile

作用:把一个新的权限配置转换成旧接口也能使用的权限配置。如果原配置本来就能直接转换成旧格式,它就不改;如果不能,它会做一个尽量等价、但旧系统能理解的替代版本。

数据流:进去的是一个 PermissionProfile 和当前工作目录 cwd。它先调用 to_legacy_sandbox_policy 检查能不能直接投影成旧权限;能的话就 clone 一份返回。不能的话,它读取文件系统权限和网络权限,找出当前工作目录以外仍然允许写入的目录,再检查 TMPDIR 环境变量指向的临时目录和固定的 /tmp 是否可写。最后把这些信息交给 workspace_write_with,产出一个旧格式可接受的新 PermissionProfile。

调用关系:它在权限下发前充当“翻译员”。turn_permissions_overrides 会调用它,把用户或系统给出的权限改造成老接口可接收的形状。它自己会借助 PermissionProfile 上的 file_system_sandbox_policy、network_sandbox_policy、to_legacy_sandbox_policy 和 workspace_write_with 来完成判断和重建。测试函数 tests::compatibility_profile_preserves_unbridgeable_write_roots 也会调用它,确认它没有丢掉额外的可写目录。

调用图:调用 5 个内部函数(file_system_sandbox_policy, network_sandbox_policy, to_legacy_sandbox_policy, workspace_write_with, from_absolute_path);被 2 处调用(turn_permissions_overrides, compatibility_profile_preserves_unbridgeable_write_roots);外部调用 3 个(new, clone, var_os)。

tests::compatibility_profile_preserves_unbridgeable_write_roots56–92 ↗
fn compatibility_profile_preserves_unbridgeable_write_roots()

作用:这是一个测试,用来确认兼容转换不会把旧格式不好表达的额外可写目录弄丢。换句话说,它检查这个“翻译员”有没有漏翻重要权限。

数据流:它先造出一个场景:当前目录是 /workspace/project,另一个允许写入的目录是 /workspace/extra,根目录只允许读取。然后把这个权限配置交给 legacy_compatible_permission_profile。得到兼容配置后,它再转成旧权限格式,取出可写目录列表,最后用 assert_eq! 检查结果必须包含额外目录和当前工作目录。

调用关系:它只在测试运行时生效,不参与正常程序流程。它直接调用 legacy_compatible_permission_profile,模拟真实的权限转换过程,并用断言保护这个文件最关键的行为:为了兼容旧接口而重建权限时,不能丢失原本允许写入的额外路径。

调用图:调用 2 个内部函数(legacy_compatible_permission_profile, try_from);外部调用 2 个(assert_eq!, vec!)。

tui/src/additional_dirs.rs源码 ↗
domain_logicstartup

用户启动工具时,可能会用 --add-dir 告诉程序“这个目录也允许写”。但这要看当前权限模式是否支持。这个文件就像门口的提示牌:先看用户有没有填额外目录;再看权限是不是完全开放、外部沙盒,或者已经允许写当前工作目录;这些情况下就不用提醒。只有在权限太严格,比如只读模式,导致额外目录会被忽略时,它才返回一段警告文字。这里的“沙盒”可以理解成一个安全围栏,规定程序能碰哪些文件、不能碰哪些文件。主程序 run_main 会在启动时用这个判断,把容易被误解的情况提前告诉用户。文件后半部分是测试,用不同权限场景确认它不会乱报警,也不会漏报警。

函数细节8
add_dir_warning_message7–33 ↗
fn add_dir_warning_message(
    additional_dirs: &[PathBuf],
    permission_profile: &PermissionProfile,
    cwd: &std::path::Path,
) -> Option<String>

作用:判断用户传入的额外可写目录会不会因为当前权限设置而被忽略;如果会,就返回一条给用户看的警告。这样用户不会误以为 --add-dir 已经生效。

数据流:输入是一组额外目录、当前权限档案,以及当前工作目录。它先检查目录列表是不是空的,再检查权限是不是完全开放或由外部系统接管,然后读取文件系统沙盒策略,看是否已有足够写权限;如果这些检查都说明没问题,就返回 None,表示不用提醒;如果额外目录会被忽略,就调用 format_warning 拼出警告文字并返回。

调用关系:它是这份文件的核心判断函数。启动流程里的 run_main 会用它决定是否向用户显示警告;测试里的 warns_for_read_only 会验证只读权限下确实会报警。它自己不负责打印,只负责把该不该提醒、提醒什么算出来;真正拼文字的活交给 format_warning

调用图:调用 2 个内部函数(file_system_sandbox_policy, format_warning);被 2 处调用(warns_for_read_only, run_main);外部调用 2 个(is_empty, matches!)。

format_warning35–44 ↗
fn format_warning(additional_dirs: &[PathBuf]) -> String

作用:把一组额外目录变成一段完整、可读的警告信息。它让用户明确知道哪些 --add-dir 被忽略,以及应该切换到什么权限模式。

数据流:输入是一组路径。它把每个路径转成适合显示的文字,用逗号连起来,然后塞进固定的英文提示模板里;输出是一整条警告字符串,不改动任何外部状态。

调用关系:它是 add_dir_warning_message 的小帮手。前面的权限判断确认需要警告后,才会把目录列表交给它来整理成最终要展示给用户的话。

调用图:被 1 处调用(add_dir_warning_message);外部调用 2 个(iter, format!)。

tests::returns_none_for_workspace_write61–68 ↗
fn returns_none_for_workspace_write()

作用:测试“工作区可写”权限下不会出现警告。因为这种模式本来就允许在工作区内写文件,额外目录不应该被误判为无效。

数据流:测试里准备一个 workspace-write 权限档案和一个额外目录,再把结果拿去和 None 比较。期望结果是没有警告,也就是函数不要多嘴提醒。

调用关系:它由 Rust 的测试运行器执行。它通过构造 workspace_write 权限、创建目录列表,并用 assert_eq! 做比较,守住“正常可写时不报警”这条规则。

调用图:调用 1 个内部函数(workspace_write);外部调用 2 个(assert_eq!, vec!)。

tests::returns_none_for_danger_full_access71–78 ↗
fn returns_none_for_danger_full_access()

作用:测试完全开放权限下不会出现警告。因为这种模式已经允许程序访问磁盘,额外目录限制就不是问题。

数据流:测试准备一个 Disabled 权限档案,这里表示沙盒限制关闭,再准备一个额外目录。检查结果应为 None,也就是没有警告文字产生。

调用关系:它由测试运行器调用,主要用断言确认完全开放场景不会被误报。它保护的是“权限足够大时不要吓用户”这个行为。

调用图:外部调用 2 个(assert_eq!, vec!)。

tests::returns_none_for_external_sandbox81–90 ↗
fn returns_none_for_external_sandbox()

作用:测试外部沙盒接管权限时不会出现这个文件生成的警告。因为这种情况下权限规则由外部系统决定,本文件不该擅自判断 --add-dir 无效。

数据流:测试构造一个 External 权限档案,并设置网络沙盒状态,再准备一个额外目录。最后检查返回值是 None,表示本地逻辑不发警告。

调用关系:它由测试运行器执行,通过断言确保外部沙盒模式被跳过。这样 add_dir_warning_message 不会在自己无法完整判断的场景里给出误导信息。

调用图:外部调用 2 个(assert_eq!, vec!)。

tests::warns_for_read_only93–102 ↗
fn warns_for_read_only()

作用:测试只读权限下会正确报警。只读模式不允许添加额外可写根目录,所以用户传 --add-dir 时必须被提醒。

数据流:测试创建只读权限档案,准备一个相对路径和一个绝对路径,然后调用 add_dir_warning_message。它期望拿到一条完整警告,并用断言确认文字里列出了这些目录,还说明应切换到 workspace-writedanger-full-access

调用关系:它由测试运行器执行,是核心报警路径的直接验证者。它把只读场景交给 add_dir_warning_message,再检查返回的警告是否正好符合预期。

调用图:调用 2 个内部函数(read_only, add_dir_warning_message);外部调用 3 个(new, assert_eq!, vec!)。

tests::warns_when_profile_can_write_elsewhere_but_not_cwd105–132 ↗
fn warns_when_profile_can_write_elsewhere_but_not_cwd()

作用:测试一种容易搞混的情况:权限允许写别的地方,但不允许写当前项目目录时,仍然要报警。这样可以避免用户以为“有某处可写”就等于额外目录会生效。

数据流:测试手工搭出一个受管理的权限档案:根目录只读,只有 /tmp/writable 可写;当前工作目录却是 /tmp/project。再传入 /tmp/extra 作为额外目录,期望得到警告字符串,说明这些额外可写根目录不会被接受。

调用关系:它由测试运行器调用,用较复杂的权限组合覆盖边界情况。它确保核心判断不是简单看“有没有任何可写目录”,而是要看当前有效权限是否真的支持额外可写根。

调用图:外部调用 2 个(assert_eq!, vec!)。

tests::returns_none_when_no_additional_dirs135–142 ↗
fn returns_none_when_no_additional_dirs()

作用:测试用户没有传任何额外目录时不会报警。没有 --add-dir,就没有什么会被忽略,也就不该打扰用户。

数据流:测试创建只读权限档案,但额外目录列表是空的。它检查结果是 None,表示即使权限严格,只要用户没请求额外目录,就不生成警告。

调用关系:它由测试运行器执行,守住函数最前面的快速退出逻辑。这个测试防止程序在没有问题时也显示无意义警告。

调用图:调用 1 个内部函数(read_only);外部调用 2 个(new, assert_eq!)。

Windows 沙盒策略

这些文件将解析后的权限转换为 Windows 特定的执行细节,并编排沙盒设置和持久化。

windows-sandbox-rs/src/cap.rs源码 ↗
domain_logicsandbox setup / permission preparation

Windows 沙箱不能只靠“路径看起来一样”来判断权限,因为同一个目录可能有大小写不同、斜杠不同等写法。这个文件做的事,就是给每个工作区、只读身份、额外可写目录生成稳定的能力 SID(Security Identifier,Windows 用来识别权限身份的一串编号),并把它们存到 codex_home 下面的 cap_sid 文件里。这样下次启动时还能用同一批身份牌,不会每次都变。它还会把路径先规范化,再作为查表的钥匙,避免“同一个目录两种写法”被误认为两个目录。对于工作区本身和额外可写根目录,它会分开给 SID,防止以前允许过的目录在以后意外继续扩大权限。文件末尾的几个小函数用来判断一个可写根目录是否包含某个路径、两个路径是否重叠,以及哪个根目录更具体。

函数细节12
cap_sid_file35–37 ↗
fn cap_sid_file(codex_home: &Path) -> PathBuf

作用:算出保存能力 SID 的文件位置。调用者只要给它 Codex 的家目录,它就告诉你那份权限身份记录应该放在哪里。

数据流:进去的是 codex_home 这个目录路径;它在后面拼上固定文件名 cap_sid;出来的是完整文件路径,比如“某个家目录/cap_sid”。它不读写磁盘,只负责算路径。

调用关系:这是很多流程的第一步。load_or_create_cap_sids 用它找到要读写的记录文件,workspace_cap_sid_for_cwd 和 writable_root_cap_sid_for_path 用它知道更新后该存回哪里,外部的权限拒绝逻辑也会通过它定位同一份 SID 记录。

调用图:被 4 处调用(apply_capability_denies_for_world_writable_for_permissions, load_or_create_cap_sids, workspace_cap_sid_for_cwd, writable_root_cap_sid_for_path);外部调用 1 个(join)。

make_random_cap_sid_string39–46 ↗
fn make_random_cap_sid_string() -> String

作用:生成一个新的随机能力 SID 字符串。它像是给沙箱发一张新的随机身份牌,避免和别的沙箱身份混在一起。

数据流:进去没有业务输入;它从随机数生成器拿 4 个随机数字,再拼成 Windows SID 形式的字符串;出来是一段类似 S-1-5-21-... 的身份编号。它不保存结果,只负责制造新编号。

调用关系:当 load_or_create_cap_sids 发现还没有身份记录,或者 workspace_cap_sid_for_cwd、writable_root_cap_sid_for_path 发现某个路径还没有专属 SID 时,就会叫它生成一个新的。生成后通常会交给 persist_caps 保存。

调用图:被 3 处调用(load_or_create_cap_sids, workspace_cap_sid_for_cwd, writable_root_cap_sid_for_path);外部调用 2 个(from_entropy, format!)。

persist_caps48–55 ↗
fn persist_caps(path: &Path, caps: &CapSids) -> Result<()>

作用:把当前的能力 SID 列表写进磁盘文件。这样程序重启后还能记得以前给哪些目录发过哪些身份牌。

数据流:进去的是目标文件路径和 CapSids 这包数据;它先确保父目录存在,再把数据转成 JSON 文本,最后写入文件;出来是成功或错误结果,同时磁盘上的 cap_sid 文件会被创建或覆盖。

调用关系:它是保存环节。load_or_create_cap_sids 在新建或升级旧格式时会用它,workspace_cap_sid_for_cwd 和 writable_root_cap_sid_for_path 在新增某个路径 SID 后也会用它把变化落盘。

调用图:被 3 处调用(load_or_create_cap_sids, workspace_cap_sid_for_cwd, writable_root_cap_sid_for_path);外部调用 4 个(parent, create_dir_all, write, to_string)。

load_or_create_cap_sids57–86 ↗
fn load_or_create_cap_sids(codex_home: &Path) -> Result<CapSids>

作用:读取已有的能力 SID;如果没有,就创建一套新的并保存。它保证沙箱权限身份有一个稳定来源。

数据流:进去的是 codex_home;它先通过 cap_sid_file 找到记录文件。如果文件存在,就尝试读取 JSON 格式的 CapSids;如果发现是旧版的单个 SID 文本,就把它升级成新版结构并补上只读 SID;如果文件不存在或内容不可用,就生成全新的 workspace 和 readonly SID。出来是一份 CapSids,同时必要时会把新数据写回磁盘。

调用关系:这是权限准备流程的核心入口之一。很多外部流程会先调用它拿到 SID,比如 Windows 沙箱启动、提升权限启动准备、旧版会话安全准备,以及针对全局可写目录的拒绝规则。它内部会调用 cap_sid_file、make_random_cap_sid_string 和 persist_caps。

调用图:调用 3 个内部函数(cap_sid_file, make_random_cap_sid_string, persist_caps);被 9 处调用(apply_capability_denies_for_world_writable_for_permissions, equivalent_cwd_spellings_share_workspace_sid_key, write_roots_get_path_scoped_sids, workspace_cap_sid_for_cwd, writable_root_cap_sid_for_path, run_windows_sandbox_capture_for_permission_profile, prepare_elevated_spawn_context_for_permissions, prepare_legacy_session_security, root_capability_sids_only_include_active_roots);外部调用 2 个(new, read_to_string)。

workspace_cap_sid_for_cwd89–100 ↗
fn workspace_cap_sid_for_cwd(codex_home: &Path, cwd: &Path) -> Result<String>

作用:拿到某个工作区目录专属的写入能力 SID。它确保同一个工作区无论路径写法怎么变,都拿到同一张身份牌。

数据流:进去的是 codex_home 和当前工作目录 cwd;它先读取或创建总的 CapSids,再把 cwd 规范化成统一钥匙。如果表里已有 SID,就直接返回;如果没有,就生成新的 SID,放进 workspace_by_cwd,并保存到磁盘。出来是这个工作区对应的 SID。

调用关系:workspace_write_cap_sid_for_root 在判断写入根目录就是工作区本身时会用它。测试 equivalent_cwd_spellings_share_workspace_sid_key 也专门验证它不会被大小写或斜杠写法骗到。它把路径规范化交给 canonical_path_key,把保存交给 persist_caps。

调用图:调用 5 个内部函数(cap_sid_file, load_or_create_cap_sids, make_random_cap_sid_string, persist_caps, canonical_path_key);被 2 处调用(equivalent_cwd_spellings_share_workspace_sid_key, workspace_write_cap_sid_for_root)。

writable_root_cap_sid_for_path103–114 ↗
fn writable_root_cap_sid_for_path(codex_home: &Path, root: &Path) -> Result<String>

作用:拿到某个额外可写根目录专属的能力 SID。它把“工作区本身”和“额外允许写的目录”分开,避免权限串门。

数据流:进去的是 codex_home 和额外可写目录 root;它读取或创建 CapSids,把 root 规范化成统一钥匙。已有记录就返回已有 SID;没有记录就生成新 SID,写入 writable_root_by_path,并保存。出来是这个可写根目录对应的 SID。

调用关系:workspace_write_cap_sid_for_root 在发现目标根目录不是当前工作区时会调用它。测试 write_roots_get_path_scoped_sids 会检查它确实给额外根目录分配了不同于工作区的 SID。

调用图:调用 5 个内部函数(cap_sid_file, load_or_create_cap_sids, make_random_cap_sid_string, persist_caps, canonical_path_key);被 1 处调用(workspace_write_cap_sid_for_root)。

workspace_write_cap_sid_for_root116–126 ↗
fn workspace_write_cap_sid_for_root(
    codex_home: &Path,
    cwd: &Path,
    root: &Path,
) -> Result<String>

作用:根据一个可写根目录到底是不是当前工作区,选出该用哪种 SID。它是“给这个写入范围挑身份牌”的分流口。

数据流:进去的是 codex_home、当前工作区 cwd、待授权的写入根目录 root;它把 cwd 和 root 都规范化后比较。如果两个其实是同一个目录,就返回工作区 SID;否则返回额外可写根目录 SID。出来是应该放进写入权限里的那一个 SID。

调用关系:权限构建逻辑 root_capability_sids 会用它收集当前可写根目录需要的 SID;一些测试也用它确认只包含当前活跃根目录、嵌套路径拒绝规则不会误放权。它自己把具体取 SID 的工作交给 workspace_cap_sid_for_cwd 或 writable_root_cap_sid_for_path。

调用图:调用 3 个内部函数(workspace_cap_sid_for_cwd, writable_root_cap_sid_for_path, canonical_path_key);被 4 处调用(write_roots_get_path_scoped_sids, root_capability_sids, legacy_deny_path_includes_nested_active_root_sid, root_capability_sids_only_include_active_roots)。

workspace_write_root_contains_path128–130 ↗
fn workspace_write_root_contains_path(root: &Path, path: &Path) -> bool

作用:判断一个可写根目录是否包含某个路径。简单说,就是看“这个文件或目录是不是在允许写的范围里面”。

数据流:进去的是 root 和 path 两个路径;它先把两者都规范化,消除大小写、斜杠等写法差异,再检查 path 是否以 root 开头;出来是 true 或 false。

调用关系:它被 workspace_write_root_overlaps_path 使用,作为判断两个路径有没有包含关系的基础小零件。它依赖 canonicalize_path 来避免路径写法不同导致误判。

调用图:调用 1 个内部函数(canonicalize_path);被 1 处调用(workspace_write_root_overlaps_path)。

workspace_write_root_overlaps_path132–134 ↗
fn workspace_write_root_overlaps_path(root: &Path, path: &Path) -> bool

作用:判断一个可写根目录和另一个路径是否有重叠。重叠的意思是:要么根目录包含它,要么它反过来包含根目录。

数据流:进去的是 root 和 path;它分别检查 root 是否包含 path,以及 path 是否包含 root;只要有一个方向成立,出来就是 true,否则是 false。它不改动任何数据。

调用关系:它把判断工作交给 workspace_write_root_contains_path 做两次。这个函数适合用在权限规则里,判断两个目录范围会不会互相影响。

调用图:调用 1 个内部函数(workspace_write_root_contains_path)。

workspace_write_root_specificity136–138 ↗
fn workspace_write_root_specificity(root: &Path) -> usize

作用:计算一个可写根目录有多“具体”。路径层级越深,通常越具体,比如 C:\a\b 比 C:\a 更具体。

数据流:进去的是 root 路径;它先规范化路径,再数路径里有多少个组成部分;出来是一个数字,数字越大表示路径越深、范围通常越窄。

调用关系:它是给权限选择或排序用的小工具。虽然这里没有显示调用者,但它通常用于在多个根目录里判断谁更精确,避免宽范围规则盖过窄范围规则。

调用图:调用 1 个内部函数(canonicalize_path)。

tests::equivalent_cwd_spellings_share_workspace_sid_key150–175 ↗
fn equivalent_cwd_spellings_share_workspace_sid_key()

作用:测试同一个工作区用不同写法表示时,会不会拿到同一个工作区 SID。它防止因为大小写或斜杠不同而重复创建权限身份。

数据流:进去是测试临时创建的一组目录;它建立一个临时 codex_home 和工作区,把工作区路径变成一种标准写法,再造一个大小写和斜杠不同的写法。然后分别调用 workspace_cap_sid_for_cwd,比较两个结果是否相同,并读取 CapSids 确认只存了一条工作区记录。出来是测试通过或失败。

调用关系:这是 workspace_cap_sid_for_cwd 和 load_or_create_cap_sids 的安全网。它模拟真实用户可能用不同路径写法进入同一目录的情况,确认 canonical_path_key 的规范化确实起作用。

调用图:调用 2 个内部函数(load_or_create_cap_sids, workspace_cap_sid_for_cwd);外部调用 5 个(from, assert_eq!, canonicalize, create_dir_all, tempdir)。

tests::write_roots_get_path_scoped_sids178–202 ↗
fn write_roots_get_path_scoped_sids()

作用:测试工作区和额外可写目录会拿到不同 SID。它防止一个目录的写权限不小心扩大到另一个目录。

数据流:进去是测试创建的临时 codex_home、workspace 和 extra_root;它分别向 workspace_write_cap_sid_for_root 要工作区 SID 和额外根目录 SID,确认两者不同,再确认直接查询额外根目录时拿到同一个 SID。最后读取 CapSids,检查工作区表和额外根目录表各有一条记录。出来是测试通过或失败。

调用关系:这是 workspace_write_cap_sid_for_root、writable_root_cap_sid_for_path 和 load_or_create_cap_sids 的配合测试。它验证分流逻辑正确:工作区走工作区 SID,额外可写根目录走路径专属 SID。

调用图:调用 2 个内部函数(load_or_create_cap_sids, workspace_write_cap_sid_for_root);外部调用 4 个(assert_eq!, assert_ne!, create_dir_all, tempdir)。

windows-sandbox-rs/src/deny_read_resolver.rs源码 ↗
domain_logicsandbox setup

这个文件解决的是沙箱权限落地的问题。Codex 的权限规则里可以写“这个目录不能读”,也可以写“所有 .env 文件不能读”这种通配规则。但 Windows ACL(访问控制列表,可以理解成系统权限名单)只能对具体路径下禁令,不能直接理解通配符。所以这里先收集明确写死的不可读路径;这些路径哪怕还不存在,也会保留下来,因为之后可能要先创建占位再加权限。然后它会处理通配规则:先找出最适合开始扫描的目录,比如 C:\repo\**\*.env 就从 C:\repo 扫,而不是从整个磁盘扫;再递归查看已经存在的文件和目录,把匹配规则的路径加入结果。它还会限制扫描深度,避免无边无际地找;遇到符号链接或目录别名时,会用真实目录做“已扫描”标记,防止绕圈,但加入权限列表时仍保留用户看到的原路径。

函数细节15
resolve_windows_deny_read_paths23–69 ↗
fn resolve_windows_deny_read_paths(
    file_system_sandbox_policy: &FileSystemSandboxPolicy,
    cwd: &AbsolutePathBuf,
) -> Result<Vec<AbsolutePathBuf>, String>

作用:这是本文件的主入口:把沙箱策略里的“禁止读取”规则变成 Windows 可以使用的绝对路径清单。调用它的人拿到这份清单后,就能去设置真正的 Windows 拒读权限。

数据流:输入是一份文件系统沙箱策略和当前工作目录。它先读取明确禁止读取的路径,转成绝对路径并去重;再读取禁止读取的通配规则,构造一个专门判断“这个路径是否该禁止读”的匹配器;接着按每条通配规则制定扫描计划,到磁盘上找已经存在且匹配的文件或目录。输出是去重后的绝对路径列表;如果通配规则本身写错,输出错误信息。

调用关系:它站在整个转换流程的最前面。它会调用策略对象的读取函数拿规则,用 push_absolute_path 放入明确路径,用 glob_scan_plan 决定从哪里扫,再把实际扫描工作交给 collect_existing_glob_matches。测试里的多个用例会直接调用它,检查通配展开、错误通配、符号链接别名等情况。

调用图:调用 8 个内部函数(get_unreadable_globs_with_cwd, get_unreadable_roots_with_cwd, restricted, try_new, as_path, collect_existing_glob_matches, glob_scan_plan, push_absolute_path);被 3 处调用(aliased_glob_roots_each_preserve_their_lexical_matches, glob_patterns_expand_to_existing_matches, invalid_glob_patterns_fail_before_expansion);外部调用 2 个(new, new)。

collect_existing_glob_matches71–123 ↗
fn collect_existing_glob_matches(
    path: &Path,
    matcher: &ReadDenyMatcher,
    paths: &mut Vec<AbsolutePathBuf>,
    seen_paths: &mut HashSet<PathBuf>,
    seen_scan_dirs: &mut HashSet<PathBuf>

作用:这个函数负责在磁盘上实际走一遍目录树,找出现在已经存在、并且命中拒读通配规则的路径。它像一个带规则的文件夹搜索器。

数据流:输入是要扫描的起点路径、匹配器、结果列表、去重集合、已扫描目录集合、最大深度和当前深度。它先确认路径存在;如果匹配拒读规则,就把它加入结果;如果这是目录,就检查是否已经扫描过同一个真实目录,避免符号链接绕圈;如果还没超过深度限制,就读取目录项并递归扫描子项。输出本身是成功或错误;主要改动是往结果列表和去重集合里加入匹配路径。

调用关系resolve_windows_deny_read_paths 在处理每条通配规则时会调用它。它自己不决定规则怎么来,只负责拿着 ReadDenyMatcher 判断路径是否该禁止读,并在命中时交给 push_absolute_path 做绝对路径整理和去重。

调用图:调用 2 个内部函数(is_read_denied, push_absolute_path);被 1 处调用(resolve_windows_deny_read_paths);外部调用 5 个(exists, metadata, to_path_buf, canonicalize, read_dir)。

push_absolute_path125–136 ↗
fn push_absolute_path(
    paths: &mut Vec<AbsolutePathBuf>,
    seen: &mut HashSet<PathBuf>,
    path: PathBuf,
) -> Result<(), String>

作用:这个小工具把一个路径整理成项目认可的绝对路径,并保证同一个路径只加入一次。它避免结果里出现重复项或不规范路径。

数据流:输入是结果列表、已见过路径的集合,以及一个待加入的路径。它先用 dunce::simplified 简化路径写法,再用 AbsolutePathBuf::from_absolute_path 确认它确实是绝对路径;如果这个路径以前没加入过,就放进结果列表。输出是成功或错误;成功时可能会新增一个路径。

调用关系:它被 resolve_windows_deny_read_paths 用来加入明确拒读路径,也被 collect_existing_glob_matches 用来加入通配扫描命中的路径。也就是说,所有最终进入结果清单的路径都会经过它这道“整理和去重”的门。

调用图:调用 1 个内部函数(from_absolute_path);被 2 处调用(collect_existing_glob_matches, resolve_windows_deny_read_paths);外部调用 1 个(simplified)。

glob_scan_plan138–170 ↗
fn glob_scan_plan(pattern: &str, configured_max_depth: Option<usize>) -> GlobScanPlan

作用:这个函数为一条通配规则制定“从哪里开始扫、最多扫多深”的计划。这样可以少扫很多无关目录,避免为了找几个文件把整个磁盘翻一遍。

数据流:输入是一条通配字符串和可选的最大扫描深度配置。它先找到第一个通配符,比如 *?[;再取它前面最深的普通目录作为扫描根目录;最后把剩下的规则片段交给 effective_glob_scan_max_depth 算深度。输出是一个 GlobScanPlan,里面有扫描起点和最大深度。

调用关系resolve_windows_deny_read_paths 每处理一条通配规则,都会先调用它做路线规划。它内部再调用 effective_glob_scan_max_depth,把“该扫多深”这件事单独算清楚。测试会检查它是否正确选择普通前缀目录,比如不会从盘符根目录乱扫。

调用图:调用 1 个内部函数(effective_glob_scan_max_depth);被 1 处调用(resolve_windows_deny_read_paths);外部调用 1 个(from)。

effective_glob_scan_max_depth172–186 ↗
fn effective_glob_scan_max_depth(
    pattern_suffix: &str,
    configured_max_depth: Option<usize>,
) -> Option<usize>

作用:这个函数计算通配扫描的实际深度上限。它的目的很简单:能少扫就少扫,但遇到 ** 这种“任意层级”的规则时,也尊重外部配置的安全上限。

数据流:输入是通配规则中扫描根目录后面的那段,以及用户配置的最大深度。它把后缀按路径分隔符拆成几段;如果里面有 **,说明可能要递归很多层,就直接使用配置的最大深度;如果没有 **,它会用规则本身的层数作为天然上限,并和配置上限取更小值。输出是一个可选数字:有数字表示最多扫这么多层,没有数字表示不限制。

调用关系:它只被 glob_scan_plan 调用,是扫描计划里的深度计算零件。上层不直接关心这些细节,只拿最终的 GlobScanPlan 去扫描。

调用图:被 1 处调用(glob_scan_plan)。

tests::unreadable_glob_entry205–210 ↗
fn unreadable_glob_entry(pattern: String) -> FileSystemSandboxEntry

作用:这是测试用的小帮手,用来快速造一条“禁止读取某个通配模式”的沙箱规则。它让测试代码不用每次都手写同样的结构。

数据流:输入是一条通配字符串。它把这条字符串包装成 FileSystemSandboxEntry,路径类型是通配模式,访问权限是拒绝。输出是一条可放进测试策略里的拒读通配规则。

调用关系:它服务于本文件的测试用例。测试在构造沙箱策略时会用它生成规则,然后把规则交给 FileSystemSandboxPolicy::restricted,最后再检查解析结果是否符合预期。

tests::unreadable_path_entry212–219 ↗
fn unreadable_path_entry(path: PathBuf) -> FileSystemSandboxEntry

作用:这是测试用的小帮手,用来快速造一条“禁止读取某个明确路径”的沙箱规则。它主要用于测试没有通配符的情况。

数据流:输入是一个路径。它先把路径确认并包装成绝对路径,然后生成一条访问权限为拒绝的沙箱规则。输出是一条明确路径的拒读规则;如果测试传入的不是绝对路径,会在测试里直接失败。

调用关系:它被测试用例用来准备策略数据,尤其是检查“明确路径即使不存在也要保留”的场景。它调用 from_absolute_path,确保测试数据符合生产代码对绝对路径的要求。

调用图:调用 1 个内部函数(from_absolute_path)。

tests::scan_root_uses_literal_prefix_before_glob222–239 ↗
fn scan_root_uses_literal_prefix_before_glob()

作用:这个测试确认通配扫描不会从太大的范围开始,而是从第一个通配符前面最深的普通目录开始。这样能避免无谓地扫描整台机器。

数据流:输入是几条典型通配规则,包括 Unix 风格路径和 Windows 盘符路径。测试调用 glob_scan_plan 得到扫描根目录,然后用断言比较它和预期目录是否一致。输出是测试通过或失败。

调用关系:它专门检查 glob_scan_plan 的根目录选择逻辑。这个逻辑会影响 resolve_windows_deny_read_paths 后续扫描的范围,所以这个测试是在保护性能和安全边界。

调用图:外部调用 1 个(assert_eq!)。

tests::scan_depth_is_bounded_for_non_recursive_globs242–255 ↗
fn scan_depth_is_bounded_for_non_recursive_globs()

作用:这个测试确认普通通配规则不会无限递归扫描。比如 *.env 只该看当前层,*/*.env 只该看下一层。

数据流:输入是几条不含或含 ** 的通配规则。测试调用 glob_scan_plan,读取它算出的最大深度,然后和预期值比较。输出是测试通过或失败。

调用关系:它验证 glob_scan_planeffective_glob_scan_max_depth 配合得是否正确。这个结果会直接影响 collect_existing_glob_matches 是否继续往子目录里走。

调用图:外部调用 1 个(assert_eq!)。

tests::configured_depth_caps_recursive_glob_scans258–267 ↗
fn configured_depth_caps_recursive_glob_scans()

作用:这个测试确认外部配置的最大扫描深度真的会生效。它防止 ** 这种递归通配把目录扫得太深。

数据流:输入是带配置深度的通配规则。测试调用 glob_scan_plan,检查最终最大深度是否被配置值限制住。输出是测试通过或失败。

调用关系:它保护的是 effective_glob_scan_max_depth 的上限逻辑。实际运行时,resolve_windows_deny_read_paths 会把这个深度交给扫描函数,用来控制磁盘遍历范围。

调用图:外部调用 1 个(assert_eq!)。

tests::exact_missing_paths_are_preserved270–287 ↗
fn exact_missing_paths_are_preserved()

作用:这个测试确认明确写出的拒读路径,即使文件现在还不存在,也会出现在最终结果里。这样后续权限设置阶段还能处理未来才会出现的敏感路径。

数据流:测试先创建临时目录,把其中一个还不存在的文件路径写进拒读策略。然后调用解析函数,检查结果是否包含这个缺失文件的绝对路径。输出是测试通过或失败。

调用关系:它覆盖 resolve_windows_deny_read_paths 处理明确路径的分支,也间接覆盖 push_absolute_path 的路径整理行为。它说明明确路径和通配路径不一样:明确路径不需要磁盘上已经存在。

调用图:调用 2 个内部函数(restricted, from_absolute_path);外部调用 3 个(new, assert_eq!, vec!)。

tests::glob_patterns_expand_to_existing_matches290–313 ↗
fn glob_patterns_expand_to_existing_matches()

作用:这个测试确认通配规则会展开成磁盘上真实存在的匹配文件。比如规则写所有 .env,结果就应该包含当前已有的 .env 文件,而不是普通文本文件。

数据流:测试创建临时目录、根目录下的 .env、子目录里的 .env 和一个不该匹配的 notes.txt。然后构造 **/*.env 拒读规则,调用 resolve_windows_deny_read_paths,把结果转成集合后和预期集合比较。输出是测试通过或失败。

调用关系:它直接验证主流程:策略读取、匹配器创建、glob_scan_plan 规划、collect_existing_glob_matches 扫描,以及 push_absolute_path 加入结果。它是通配展开功能的核心测试之一。

调用图:调用 3 个内部函数(restricted, from_absolute_path, resolve_windows_deny_read_paths);外部调用 5 个(new, assert_eq!, create_dir_all, write, vec!)。

tests::invalid_glob_patterns_fail_before_expansion316–330 ↗
fn invalid_glob_patterns_fail_before_expansion()

作用:这个测试确认通配规则如果写错,会在扫描磁盘前就报错。这样可以尽早告诉调用者规则不合法,而不是悄悄忽略或得到奇怪结果。

数据流:测试构造一个非法通配规则,比如字符范围 [z-a]。然后调用 resolve_windows_deny_read_paths,期待它返回错误,并检查错误信息里包含“非法拒读通配”和具体的范围错误。输出是测试通过或失败。

调用关系:它验证 resolve_windows_deny_read_paths 在创建 ReadDenyMatcher::try_new 时会正确拦截坏规则。这个测试保证后面的 collect_existing_glob_matches 不会拿着错误规则去扫描。

调用图:调用 3 个内部函数(restricted, from_absolute_path, resolve_windows_deny_read_paths);外部调用 3 个(new, assert!, vec!)。

tests::non_recursive_globs_do_not_expand_nested_matches333–350 ↗
fn non_recursive_globs_do_not_expand_nested_matches()

作用:这个测试确认不带 ** 的通配规则不会误匹配更深层目录里的文件。比如 /*.env 只能匹配根目录下的 .env,不能匹配 app/.env

数据流:测试创建一个根目录 .env 和一个子目录 .env,再构造只匹配当前层的通配规则。然后解析结果,检查只返回根目录那个文件。输出是测试通过或失败。

调用关系:它保护的是扫描深度控制。glob_scan_plan 算出的深度会限制 collect_existing_glob_matches 递归,所以这个测试能发现扫描过深导致的错误匹配。

调用图:调用 2 个内部函数(restricted, from_absolute_path);外部调用 5 个(new, assert_eq!, create_dir_all, write, vec!)。

tests::aliased_glob_roots_each_preserve_their_lexical_matches354–380 ↗
fn aliased_glob_roots_each_preserve_their_lexical_matches()

作用:这个测试确认当两个不同路径其实指向同一个目录时,结果仍保留用户写的那两个路径形式。简单说,就是两个门通向同一个房间,权限名单里仍要按两个门的名字记录。

数据流:测试创建一个真实目录和两个指向它的符号链接,再在真实目录里放一个 .env 文件。它分别对两个链接路径写通配拒读规则,调用解析函数,最后检查结果包含 alias-a/secret.envalias-b/secret.env 这两个表面路径。输出是测试通过或失败。

调用关系:它验证 collect_existing_glob_matches 的一个细节:扫描时用规范化后的真实目录防止循环,但加入结果时保留原始路径写法。它通过 resolve_windows_deny_read_paths 走完整流程,并依赖符号链接创建来模拟目录别名。

调用图:调用 3 个内部函数(restricted, from_absolute_path, resolve_windows_deny_read_paths);外部调用 6 个(new, assert_eq!, create_dir_all, write, symlink, vec!)。

windows-sandbox-rs/src/resolved_permissions.rs源码 ↗
domain_logicsandbox setup / permission resolution

这个文件像一个“权限翻译员”。用户或配置里说的是比较通用的 PermissionProfile(权限档案,也就是允许读哪里、写哪里、能不能联网),但 Windows 沙箱需要更具体的答案:哪些目录可读,哪些目录可写,要不要禁网,用哪种受限令牌(一种带权限限制的 Windows 身份)启动进程。文件里的 ResolvedWindowsSandboxPermissions 保存已经算好的文件系统和网络规则,并提供一组查询方法。它还会拒绝 Windows 沙箱无法安全执行的情况,比如全盘写入。一个特别点是临时目录:配置里写“可写临时目录”时,它会从 TEMP、TMP 环境变量里找出 Windows 实际使用的临时目录,加入可写列表。底部测试确保这些规则不会在常见场景下算错。

函数细节22
token_mode_for_permission_profile38–59 ↗
fn token_mode_for_permission_profile(
    permission_profile: &PermissionProfile,
    workspace_roots: &[AbsolutePathBuf],
    cwd: &Path,
    env_map: &HashMap<String, String>,
) -> Result<WindowsSan

作用:根据一份权限档案判断 Windows 沙箱该用哪类受限令牌启动程序:纯只读,还是带可写目录能力。这样后面的启动代码就知道要给子进程发哪种“通行证”。

数据流:输入是一份权限档案、工作区根目录列表、当前目录和环境变量表。它先把权限档案解析成 Windows 可用的权限;如果发现要求全盘写入,就直接报错,因为 Windows 沙箱无法安全执行;然后查看当前目录下有没有可写根目录,有就输出 WritableRootsCapability,没有就输出 ReadOnlyCapability。

调用关系:它先把实际解析工作交给 ResolvedWindowsSandboxPermissions::try_from_permission_profile_for_workspace_roots。测试 token_mode_for_profile_with_writable_roots_uses_write_capabilities、token_mode_for_profile_without_writable_roots_uses_readonly_capability 和 token_mode_rejects_full_disk_write_entries 用它来确认令牌选择和错误拒绝是否正确。

调用图:调用 1 个内部函数(try_from_permission_profile_for_workspace_roots);被 3 处调用(token_mode_for_profile_with_writable_roots_uses_write_capabilities, token_mode_for_profile_without_writable_roots_uses_readonly_capability, token_mode_rejects_full_disk_write_entries);外部调用 1 个(bail!)。

ResolvedWindowsSandboxPermissions::try_from_permission_profile62–78 ↗
fn try_from_permission_profile(permission_profile: &PermissionProfile) -> Result<Self>

作用:把通用权限档案转换成 Windows 本地沙箱权限对象。它只接受“受管理且受限制”的权限,因为只有这种权限 Windows 沙箱能可靠执行。

数据流:输入是一份 PermissionProfile。它先检查这份档案是不是 Managed(受管理的权限方案),再调用 to_runtime_permissions 得到运行时的文件系统规则和网络规则;如果文件系统不是 Restricted(受限制模式),就报错;成功时输出 ResolvedWindowsSandboxPermissions。

调用关系:这是最基础的转换入口。ResolvedWindowsSandboxPermissions::try_from_permission_profile_for_workspace_roots 会在它的基础上继续展开工作区根目录;多个测试用它验证正常转换、拒绝禁用权限档案、拒绝不受限文件系统权限。

调用图:调用 1 个内部函数(to_runtime_permissions);被 3 处调用(permission_profile_rejects_disabled_profiles, permission_profile_rejects_unrestricted_managed_filesystem, permission_profile_workspace_write_uses_windows_temp_env_vars);外部调用 2 个(bail!, matches!)。

ResolvedWindowsSandboxPermissions::try_from_permission_profile_for_workspace_roots82–91 ↗
fn try_from_permission_profile_for_workspace_roots(
        permission_profile: &PermissionProfile,
        workspace_roots: &[AbsolutePathBuf],
    ) -> Result<Self>

作用:在普通解析的基础上,把“工作区根目录”这种占位说法换成运行时真正的目录路径。这样沙箱拿到的是明确的 Windows 路径,而不是抽象标签。

数据流:输入是一份权限档案和一组实际工作区根目录。它先调用 ResolvedWindowsSandboxPermissions::try_from_permission_profile 得到基础权限,再把文件系统规则里的 :workspace_roots 之类符号项展开成传入的真实路径,最后输出更新后的权限对象。

调用关系:这是很多 Windows 沙箱准备流程会用的入口,例如 run_elevated_setup、spawn_world_writable_scan、world_writable_warning_details、compute_allow_paths、run_windows_sandbox_capture_for_permission_profile 等都会在真正设置沙箱前调用它。相关测试也用它确认工作区根目录会正确展开。

调用图:被 19 处调用(run_elevated_setup, spawn_world_writable_scan, world_writable_warning_details, run_elevated_setup, compute_allow_paths, run_windows_sandbox_capture_for_permission_profile, permission_profile_workspace_root_uses_runtime_workspace_roots, permission_profile_workspace_roots_expand_all_runtime_workspace_roots, token_mode_for_permission_profile, run_setup_refresh (+9 more));外部调用 1 个(try_from_permission_profile)。

ResolvedWindowsSandboxPermissions::should_apply_network_block93–95 ↗
fn should_apply_network_block(&self) -> bool

作用:判断这份权限是否要求禁用网络。外部代码可以用它决定要不要给沙箱加上“不能联网”的限制。

数据流:输入是已经解析好的权限对象。它读取里面的网络策略,调用 is_enabled 判断网络是否开启;如果网络没有开启,就输出 true,表示应该拦网络,否则输出 false。

调用关系:它是网络限制设置时的一个小查询口。它不自己修改系统网络,只把网络策略翻译成一个简单的是/否答案。

调用图:调用 1 个内部函数(is_enabled)。

ResolvedWindowsSandboxPermissions::network_policy97–99 ↗
fn network_policy(&self) -> NetworkSandboxPolicy

作用:直接取出当前权限里的网络策略。需要完整网络规则的代码可以通过它拿到原始判断依据。

数据流:输入是权限对象本身。它读取内部保存的 network 字段,并把这个 NetworkSandboxPolicy 返回出去,不做额外加工。

调用关系:from_permissions 会调用它,把这里解析出的网络策略继续交给后续配置或执行环节。

调用图:被 1 处调用(from_permissions)。

ResolvedWindowsSandboxPermissions::is_enforceable_by_windows_sandbox101–103 ↗
fn is_enforceable_by_windows_sandbox(&self) -> bool

作用:判断这份文件系统权限是不是 Windows 沙箱能够执行的类型。简单说,就是确认它是否处在“受限制”模式。

数据流:输入是权限对象。它查看 file_system.kind,如果是 Restricted 就输出 true,否则输出 false。

调用关系:apply_capability_denies_for_world_writable_for_permissions 会用它先做把关:只有 Windows 沙箱能执行的权限,才继续应用后面的能力限制。

调用图:被 1 处调用(apply_capability_denies_for_world_writable_for_permissions);外部调用 1 个(matches!)。

ResolvedWindowsSandboxPermissions::has_full_disk_read_access105–107 ↗
fn has_full_disk_read_access(&self) -> bool

作用:判断这份权限是否允许读取整个磁盘。后续收集可读目录时,需要知道是不是已经拥有全盘读取能力。

数据流:输入是权限对象。它把问题转交给内部的文件系统策略,得到一个布尔值:允许全盘读就是 true,否则是 false。

调用关系:gather_read_roots 会调用它。如果已经全盘可读,后续可能就不需要再逐个列很多可读目录。

调用图:调用 1 个内部函数(has_full_disk_read_access);被 1 处调用(gather_read_roots)。

ResolvedWindowsSandboxPermissions::include_platform_defaults109–111 ↗
fn include_platform_defaults(&self) -> bool

作用:判断是否要自动加入平台默认可读路径。比如系统运行常常需要读一些 Windows 基础目录,这个函数告诉后续流程要不要加上。

数据流:输入是权限对象。它读取内部文件系统策略里的默认路径开关,并返回 true 或 false。

调用关系:gather_read_roots 会调用它,在收集可读路径时决定是否额外加入 Windows 平台默认目录。

调用图:调用 1 个内部函数(include_platform_defaults);被 1 处调用(gather_read_roots)。

ResolvedWindowsSandboxPermissions::readable_roots_for_cwd113–119 ↗
fn readable_roots_for_cwd(&self, cwd: &Path) -> Vec<PathBuf>

作用:算出在某个当前目录下,沙箱允许读取哪些根目录。它把抽象权限变成普通 PathBuf 路径,方便 Windows 侧代码使用。

数据流:输入是权限对象和当前工作目录 cwd。它调用内部文件系统策略的 get_readable_roots_with_cwd,根据 cwd 解析出可读根目录;然后把 AbsolutePathBuf 转成普通 PathBuf 列表输出。

调用关系:gather_read_roots 会调用它,把这里得到的可读目录加入最终沙箱配置。

调用图:调用 1 个内部函数(get_readable_roots_with_cwd);被 1 处调用(gather_read_roots)。

ResolvedWindowsSandboxPermissions::uses_write_capabilities_for_cwd121–127 ↗
fn uses_write_capabilities_for_cwd(
        &self,
        cwd: &Path,
        env_map: &HashMap<String, String>,
    ) -> bool

作用:快速判断在某个当前目录下是否需要写权限能力。它不关心具体能写哪里,只问“有没有任何可写目录”。

数据流:输入是权限对象、当前目录和环境变量表。它调用 ResolvedWindowsSandboxPermissions::writable_roots_for_cwd 算出可写根目录;如果列表非空就输出 true,否则输出 false。

调用关系:apply_capability_denies_for_world_writable_for_permissions、legacy_session_capability_roots 和 prepare_elevated_spawn_context_for_permissions 会用它决定是否走带写能力的沙箱路径。

调用图:调用 1 个内部函数(writable_roots_for_cwd);被 3 处调用(apply_capability_denies_for_world_writable_for_permissions, legacy_session_capability_roots, prepare_elevated_spawn_context_for_permissions)。

ResolvedWindowsSandboxPermissions::writable_roots_for_cwd129–170 ↗
fn writable_roots_for_cwd(
        &self,
        cwd: &Path,
        env_map: &HashMap<String, String>,
    ) -> Vec<WindowsWritableRoot>

作用:算出当前目录下真正允许写入的 Windows 目录列表,并附带这些目录里仍然只读的子路径。它是设置写权限时最关键的清单生成器。

数据流:输入是权限对象、当前目录和环境变量表。它先复制一份文件系统规则,并暂时移除特殊临时目录项,避免普通路径解析把 tmp 处理错;再调用 get_writable_roots_with_cwd 算出可写根目录;接着把路径转成 Windows 本地 PathBuf。如果原权限里有可写 Tmpdir,它还会调用 windows_temp_env_roots,从 TEMP、TMP 找到实际临时目录并追加到结果里。最后输出 WindowsWritableRoot 列表。

调用关系:compute_allow_paths_for_permissions、gather_full_read_roots_for_permissions、gather_write_roots_for_permissions 会用它生成实际允许路径;ResolvedWindowsSandboxPermissions::uses_write_capabilities_for_cwd 也靠它判断是否需要写能力。它内部会调用 ResolvedWindowsSandboxPermissions::has_writable_tmpdir_entry 和 windows_temp_env_roots。

调用图:调用 2 个内部函数(has_writable_tmpdir_entry, windows_temp_env_roots);被 4 处调用(compute_allow_paths_for_permissions, uses_write_capabilities_for_cwd, gather_full_read_roots_for_permissions, gather_write_roots_for_permissions);外部调用 1 个(clone)。

ResolvedWindowsSandboxPermissions::has_writable_tmpdir_entry172–184 ↗
fn has_writable_tmpdir_entry(&self) -> bool

作用:检查权限规则里是否明确允许写入临时目录。这个判断用于决定要不要把 Windows 的 TEMP、TMP 目录加入可写列表。

数据流:输入是权限对象。它遍历文件系统规则里的每一项,寻找路径类型为 Tmpdir 且访问模式允许写入的条目;找到就输出 true,找不到就输出 false。

调用关系:它只服务于 ResolvedWindowsSandboxPermissions::writable_roots_for_cwd。后者用这个结果决定是否继续调用 windows_temp_env_roots。

调用图:被 1 处调用(writable_roots_for_cwd)。

windows_temp_env_roots187–198 ↗
fn windows_temp_env_roots(env_map: &HashMap<String, String>) -> Vec<PathBuf>

作用:从环境变量里找出 Windows 当前使用的临时目录。这样“允许写临时目录”不会停留在抽象说法,而是变成具体路径。

数据流:输入是一张环境变量表。它依次查看 TEMP 和 TMP:优先用传入表里的值,找不到再读系统环境变量;然后只保留绝对路径,最后输出路径列表。

调用关系:ResolvedWindowsSandboxPermissions::writable_roots_for_cwd 在发现权限允许写 Tmpdir 时调用它,把真实临时目录追加进可写根目录。

调用图:被 1 处调用(writable_roots_for_cwd)。

tests::workspace_roots_for211–213 ↗
fn workspace_roots_for(root: &Path) -> Vec<AbsolutePathBuf>

作用:测试用的小帮手,把一个普通路径包装成测试需要的工作区根目录列表。这样每个测试不用重复写同样的转换代码。

数据流:输入是一个路径。它把这个路径转成 AbsolutePathBuf,也就是确认过的绝对路径,再放进一个只有一个元素的 Vec 列表输出。

调用关系:多个测试会调用它来准备 workspace_roots 参数,比如工作区写入、只读令牌、写令牌等测试。

调用图:外部调用 1 个(vec!)。

tests::permission_profile_workspace_write_uses_windows_temp_env_vars216–245 ↗
fn permission_profile_workspace_write_uses_windows_temp_env_vars()

作用:验证“工作区可写”权限会把 Windows 临时目录也算进可写范围。否则程序可能在沙箱里无法写临时文件。

数据流:测试先创建临时工作区和临时目录,再把 TEMP、TMP 环境变量指向这个临时目录。它解析 workspace_write 权限,调用 writable_roots_for_cwd 拿到可写根目录,最后断言结果里同时包含当前工作区和临时目录。

调用关系:它调用 ResolvedWindowsSandboxPermissions::try_from_permission_profile 和权限对象的 writable_roots_for_cwd,专门覆盖 windows_temp_env_roots 参与的路径。

调用图:调用 2 个内部函数(workspace_write, try_from_permission_profile);外部调用 5 个(new, new, assert_eq!, canonicalize, create_dir_all)。

tests::permission_profile_workspace_root_uses_runtime_workspace_roots248–284 ↗
fn permission_profile_workspace_root_uses_runtime_workspace_roots()

作用:验证权限里的“项目根目录”会使用运行时传入的工作区根目录,而不是简单使用命令当前目录。这样子目录里执行命令时,仍然能正确授权整个工作区。

数据流:测试创建一个工作区和它下面的子目录,把权限设置成允许写 project_roots。然后传入真实工作区根目录进行解析,再以子目录作为 cwd 查询可写根目录,最后断言结果是工作区根目录本身。

调用关系:它调用 tests::workspace_roots_for 准备输入,再调用 ResolvedWindowsSandboxPermissions::try_from_permission_profile_for_workspace_roots 验证工作区占位符展开逻辑。

调用图:调用 1 个内部函数(try_from_permission_profile_for_workspace_roots);外部调用 6 个(new, new, assert_eq!, create_dir_all, vec!, workspace_roots_for)。

tests::permission_profile_workspace_roots_expand_all_runtime_workspace_roots287–378 ↗
fn permission_profile_workspace_roots_expand_all_runtime_workspace_roots()

作用:验证当有多个工作区根目录时,权限里的项目根目录、子路径和通配规则都会为每个根目录展开一份。这样多工作区场景不会漏授权或漏拒绝。

数据流:测试准备 first 和 second 两个绝对工作区根目录,并设置三类规则:允许写项目根、拒绝 .git、拒绝匹配 **/*.env 的文件。解析后,它断言内部文件系统策略被展开成对应两个根目录的六条具体规则。

调用关系:它调用 ResolvedWindowsSandboxPermissions::try_from_permission_profile_for_workspace_roots,重点检查 materialize_project_roots_with_workspace_roots 的效果是否反映到最终权限对象里。

调用图:调用 2 个内部函数(from_absolute_path, try_from_permission_profile_for_workspace_roots);外部调用 3 个(new, assert_eq!, vec!)。

tests::token_mode_for_profile_without_writable_roots_uses_readonly_capability381–396 ↗
fn token_mode_for_profile_without_writable_roots_uses_readonly_capability()

作用:验证没有可写目录的权限档案会选择只读令牌。这样只读场景不会被错误地授予写能力。

数据流:测试创建一个工作区目录,构造 read_only 权限档案和工作区根目录,然后调用 token_mode_for_permission_profile。最后断言输出是 WindowsSandboxTokenMode::ReadOnlyCapability。

调用关系:它通过 tests::workspace_roots_for 准备工作区输入,并直接测试 token_mode_for_permission_profile 的只读分支。

调用图:调用 2 个内部函数(read_only, token_mode_for_permission_profile);外部调用 5 个(new, new, assert_eq!, create_dir_all, workspace_roots_for)。

tests::token_mode_for_profile_with_writable_roots_uses_write_capabilities399–414 ↗
fn token_mode_for_profile_with_writable_roots_uses_write_capabilities()

作用:验证有可写目录的权限档案会选择带写能力的令牌。否则需要写工作区的命令会在沙箱里失败。

数据流:测试创建工作区目录,构造 workspace_write 权限档案和工作区根目录,然后调用 token_mode_for_permission_profile。最后断言输出是 WindowsSandboxTokenMode::WritableRootsCapability。

调用关系:它通过 tests::workspace_roots_for 准备输入,并覆盖 token_mode_for_permission_profile 的可写分支。

调用图:调用 2 个内部函数(workspace_write, token_mode_for_permission_profile);外部调用 5 个(new, new, assert_eq!, create_dir_all, workspace_roots_for)。

tests::permission_profile_rejects_disabled_profiles417–427 ↗
fn permission_profile_rejects_disabled_profiles()

作用:验证禁用沙箱的权限档案不能被解析成 Windows 沙箱权限。因为“禁用”代表不受沙箱管理,不能假装可以执行限制。

数据流:测试把 PermissionProfile::Disabled 传给 ResolvedWindowsSandboxPermissions::try_from_permission_profile,期望得到错误。然后检查错误信息里包含“only managed permission profiles can be enforced”。

调用关系:它直接测试 ResolvedWindowsSandboxPermissions::try_from_permission_profile 的第一道拒绝逻辑。

调用图:调用 1 个内部函数(try_from_permission_profile);外部调用 1 个(assert!)。

tests::permission_profile_rejects_unrestricted_managed_filesystem430–444 ↗
fn permission_profile_rejects_unrestricted_managed_filesystem()

作用:验证即使权限档案是 Managed,只要文件系统是不受限制的,也不能交给 Windows 沙箱执行。因为不受限文件系统和沙箱限制目标相冲突。

数据流:测试构造一个 Managed 权限档案,其中 file_system 是 Unrestricted,network 是 Restricted。调用 ResolvedWindowsSandboxPermissions::try_from_permission_profile 后期望报错,并检查错误信息说明只支持受限制文件系统权限。

调用关系:它直接覆盖 ResolvedWindowsSandboxPermissions::try_from_permission_profile 的第二道拒绝逻辑。

调用图:调用 1 个内部函数(try_from_permission_profile);外部调用 1 个(assert!)。

tests::token_mode_rejects_full_disk_write_entries447–477 ↗
fn token_mode_rejects_full_disk_write_entries()

作用:验证要求全盘写入的权限不会被转换成任何 Windows 沙箱令牌模式。这样可以避免给出一个看似受限、实际无法安全实现的配置。

数据流:测试创建工作区目录,构造一份允许写 Root(磁盘根)的受管理权限档案,然后调用 token_mode_for_permission_profile。它期望得到错误,并检查错误信息提到 Windows 沙箱无法执行全盘写入。

调用关系:它通过 tests::workspace_roots_for 准备工作区输入,并专门测试 token_mode_for_permission_profile 在发现全盘写入时的拒绝分支。

调用图:调用 1 个内部函数(token_mode_for_permission_profile);外部调用 6 个(new, new, assert!, create_dir_all, vec!, workspace_roots_for)。

windows-sandbox-rs/src/allow.rs源码 ↗
domain_logic执行命令前准备沙箱权限时

运行命令前,系统需要把“权限配置”变成 Windows 沙箱能用的路径名单。这个文件做的就是这件事:先根据当前工作目录和环境变量,找出应该允许写入的根目录;再把其中需要保护的子路径放进禁止写入名单。可以把它想成门禁表:大门允许进入,但里面某些保险柜还要单独锁起来。核心结果是 AllowDenyPaths,里面有 allow 和 deny 两组路径,使用集合(不重复的一袋路径)保存。计算时它只加入真实存在的路径,并尽量把路径转成规范形式,避免同一个位置因为写法不同而被当成两个地方。文件后半部分是测试,覆盖额外可写目录、临时目录环境变量、Unix 风格 /tmp 在 Windows 上的处理,以及 .git、.codex、.agents 等保护目录。

函数细节13
compute_allow_paths_for_permissions14–42 ↗
fn compute_allow_paths_for_permissions(
    permissions: &ResolvedWindowsSandboxPermissions,
    command_cwd: &Path,
    env_map: &HashMap<String, String>,
) -> AllowDenyPaths

作用:把已经解析好的权限配置,换算成一份“允许写”和“禁止写”的路径清单。沙箱启动前会用它决定哪些地方能被命令改动,哪些地方必须保护起来。

数据流:输入是权限对象、命令的当前目录、环境变量表。它先向权限对象询问当前目录下有哪些可写根目录,再把存在的根目录加入 allow;同时把这些根目录里被标记为只读的子路径,如果确实存在,就加入 deny。输出是 AllowDenyPaths,不直接改文件系统,只产出路径名单。

调用关系:它是这个文件的核心函数。上游会在构建沙箱写入限制、套用旧版会话访问规则、计算旧版会话能力根目录、准备提权启动上下文时调用它;测试里的 tests::compute_allow_paths 也会调用它。它把“具体哪些根目录可写”的判断交给 writable_roots_for_cwd,并用 canonicalize 尽量统一路径写法。

调用图:调用 1 个内部函数(writable_roots_for_cwd);被 5 处调用(compute_allow_paths, build_payload_deny_write_paths, apply_legacy_session_acl_rules, legacy_session_capability_roots, prepare_elevated_spawn_context_for_permissions);外部调用 2 个(new, canonicalize)。

tests::workspace_write_profile53–64 ↗
fn workspace_write_profile(
        writable_roots: &[AbsolutePathBuf],
        exclude_tmpdir_env_var: bool,
        exclude_slash_tmp: bool,
    ) -> PermissionProfile

作用:测试用的小帮手,用来快速造出一种“工作区可写”的权限配置。这样每个测试不用重复写一大段权限创建代码。

数据流:输入是额外可写根目录,以及是否排除临时目录环境变量、是否排除 /tmp 的两个开关。它把这些参数交给 PermissionProfile::workspace_write_with,得到一个测试用的权限配置并返回。

调用关系:它只在测试里被各个测试函数调用。它负责准备测试场景的“规则纸”,后续 tests::compute_allow_paths 会把这张规则纸转换成真正的允许和禁止路径。

调用图:调用 1 个内部函数(workspace_write_with)。

tests::workspace_roots_for66–68 ↗
fn workspace_roots_for(root: &Path) -> Vec<AbsolutePathBuf>

作用:测试用的小帮手,把一个普通路径包装成“工作区根目录列表”。测试通常只需要一个工作区,所以它返回只含一个元素的列表。

数据流:输入是一个路径。它把这个路径转换成 AbsolutePathBuf,也就是明确表示“这是绝对路径”的类型,然后放进一个列表返回。

调用关系:它被多个测试函数调用,用来准备 ResolvedWindowsSandboxPermissions 需要的工作区根目录参数。它不参与生产代码,只让测试写起来更清楚。

调用图:外部调用 1 个(vec!)。

tests::compute_allow_paths70–83 ↗
fn compute_allow_paths(
        permission_profile: &PermissionProfile,
        workspace_roots: &[AbsolutePathBuf],
        command_cwd: &Path,
        env_map: &HashMap<String, String>,
    ) -> All

作用:测试用的包装函数,把测试里的权限配置先解析,再调用真正的 compute_allow_paths_for_permissions。它让测试能从高层权限配置开始验证最终结果。

数据流:输入是权限配置、工作区根目录、命令当前目录和环境变量。它先调用 try_from_permission_profile_for_workspace_roots 把权限配置解析成 Windows 沙箱需要的形式,再调用 compute_allow_paths_for_permissions 得到 allow 和 deny 清单并返回。

调用关系:它夹在测试用例和生产核心函数之间。各个测试函数调用它,它再把真正计算工作交给 compute_allow_paths_for_permissions。这样测试既能覆盖权限解析,也能覆盖最终路径计算。

调用图:调用 2 个内部函数(compute_allow_paths_for_permissions, try_from_permission_profile_for_workspace_roots)。

tests::includes_additional_writable_roots86–119 ↗
fn includes_additional_writable_roots()

作用:验证如果权限配置里额外声明了可写目录,最终 allow 清单里应该同时包含工作区和这个额外目录。

数据流:它先创建临时目录、工作区目录和额外目录,再构造带额外可写根目录的权限配置。然后调用 tests::compute_allow_paths,最后检查 allow 里有工作区和额外目录,deny 为空。

调用关系:这是一个测试用例,由 Rust 测试运行器执行。它通过 workspace_write_profile、workspace_roots_for 和 tests::compute_allow_paths 串起测试准备、权限解析和核心计算。

调用图:外部调用 8 个(new, new, assert!, create_dir_all, vec!, compute_allow_paths, workspace_roots_for, workspace_write_profile)。

tests::uses_runtime_workspace_roots_for_workspace_root122–153 ↗
fn uses_runtime_workspace_roots_for_workspace_root()

作用:验证工作区根目录来自运行时传入的 workspace_roots,而不是简单拿命令所在的子目录当根目录。

数据流:它创建一个工作区和其中的子目录,把命令当前目录设为子目录。计算后检查 allow 包含真正的工作区根目录,而不包含子目录本身;deny 为空。

调用关系:这个测试保护一个容易出错的点:命令可能在工作区的子文件夹里运行,但可写范围应该是整个工作区。它调用测试帮手生成配置和路径,再间接验证 compute_allow_paths_for_permissions。

调用图:外部调用 7 个(new, new, assert!, create_dir_all, compute_allow_paths, workspace_roots_for, workspace_write_profile)。

tests::excludes_tmp_env_vars_when_requested156–191 ↗
fn excludes_tmp_env_vars_when_requested()

作用:验证当配置要求排除 TEMP、TMP 这类临时目录环境变量时,这些目录不会被加入可写清单。

数据流:它创建工作区和临时目录,并在环境变量表里放入 TEMP 和 TMP。权限配置打开“排除临时目录环境变量”开关后,计算结果应只允许工作区,不允许临时目录,deny 为空。

调用关系:这个测试关注环境变量对沙箱可写路径的影响。它把环境变量传给 tests::compute_allow_paths,最终由权限解析和 compute_allow_paths_for_permissions 共同决定哪些路径进入 allow。

调用图:外部调用 7 个(new, new, assert!, create_dir_all, compute_allow_paths, workspace_roots_for, workspace_write_profile)。

tests::includes_tmp_env_vars_when_requested194–227 ↗
fn includes_tmp_env_vars_when_requested()

作用:验证当配置允许使用 TEMP、TMP 环境变量时,临时目录会被加入可写清单。

数据流:它创建工作区和临时目录,把 TEMP、TMP 都指向该临时目录。权限配置不排除这些环境变量,计算后期望 allow 正好包含工作区和临时目录,deny 为空。

调用关系:这个测试和 tests::excludes_tmp_env_vars_when_requested 是一对反向检查。它调用同样的测试帮手,但用不同开关确认临时目录是否按配置进入 allow。

调用图:外部调用 9 个(new, new, assert!, assert_eq!, canonicalize, create_dir_all, compute_allow_paths, workspace_roots_for, workspace_write_profile)。

tests::ignores_unix_slash_tmp_for_windows_allow_roots230–254 ↗
fn ignores_unix_slash_tmp_for_windows_allow_roots()

作用:验证 Windows 沙箱计算可写路径时,不会把 Unix 系统常见的 /tmp 当成 Windows 上要允许的目录。

数据流:它只创建一个工作区,并把配置设成不排除 /tmp。计算后仍然只期望 allow 包含工作区,deny 为空,说明 /tmp 没有被误加进 Windows 路径清单。

调用关系:这个测试防止跨平台规则误伤 Windows。它通过 tests::compute_allow_paths 间接检查 compute_allow_paths_for_permissions 和权限解析在 Windows 语境下的行为。

调用图:外部调用 9 个(new, new, assert!, assert_eq!, canonicalize, create_dir_all, compute_allow_paths, workspace_roots_for, workspace_write_profile)。

tests::denies_git_dir_inside_writable_root257–285 ↗
fn denies_git_dir_inside_writable_root()

作用:验证工作区虽然整体可写,但里面真实存在的 .git 目录会被放进禁止写清单,避免破坏版本库元数据。

数据流:它创建工作区和其中的 .git 目录。计算后期望 allow 包含工作区,同时 deny 包含 .git 目录。

调用关系:这个测试检查“可写根目录里的受保护子目录”机制。它先用测试帮手得到路径结果,再和预期的 allow、deny 集合比较,间接保护 compute_allow_paths_for_permissions 的过滤行为。

调用图:外部调用 8 个(new, new, assert_eq!, canonicalize, create_dir_all, compute_allow_paths, workspace_roots_for, workspace_write_profile)。

tests::denies_git_file_inside_writable_root288–317 ↗
fn denies_git_file_inside_writable_root()

作用:验证 .git 即使是文件而不是目录,也会被禁止写入。某些 Git worktree 场景里 .git 就是一个指向真实目录的文件。

数据流:它创建工作区,并写入一个名为 .git 的文件。计算后期望 allow 包含工作区,deny 包含这个 .git 文件。

调用关系:这个测试补上 .git 文件形态的情况,避免只保护 .git 目录而漏掉 worktree。它调用 tests::compute_allow_paths,最终验证核心计算产出的 deny 清单。

调用图:外部调用 9 个(new, new, assert_eq!, canonicalize, create_dir_all, write, compute_allow_paths, workspace_roots_for, workspace_write_profile)。

tests::denies_codex_and_agents_inside_writable_root320–353 ↗
fn denies_codex_and_agents_inside_writable_root()

作用:验证工作区里的 .codex 和 .agents 目录会被禁止写入,避免命令改动工具自身或代理相关的敏感配置。

数据流:它创建工作区,以及 .codex、.agents 两个目录。计算后期望 allow 只有工作区,deny 包含这两个受保护目录。

调用关系:这个测试和 .git 相关测试类似,关注受保护子路径。它通过测试帮手走完整权限解析和路径计算流程,确认敏感目录会进入 deny。

调用图:外部调用 8 个(new, new, assert_eq!, canonicalize, create_dir_all, compute_allow_paths, workspace_roots_for, workspace_write_profile)。

tests::skips_protected_subdirs_when_missing356–379 ↗
fn skips_protected_subdirs_when_missing()

作用:验证如果 .git、.codex、.agents 这类受保护子路径根本不存在,就不会凭空加入 deny 清单。

数据流:它只创建工作区,不创建任何受保护子目录。计算后检查 allow 只有一个路径,deny 为空。

调用关系:这个测试对应 compute_allow_paths_for_permissions 里“只加入存在路径”的规则。它防止系统把不存在的路径也写进禁止名单,造成不必要或混乱的沙箱规则。

调用图:外部调用 8 个(new, new, assert!, assert_eq!, create_dir_all, compute_allow_paths, workspace_roots_for, workspace_write_profile)。

core/src/windows_sandbox.rs源码 ↗
orchestrationconfig load / sandbox setup / cross-cutting metrics

Windows 沙盒可以理解成给程序套一个“安全围栏”,限制它能碰哪些文件和系统资源。这个文件先从配置文件和功能开关里判断沙盒等级:不开、普通受限模式,还是需要更高权限准备的 elevated 模式。它还兼容老配置名,避免老用户升级后设置失效。真正开始设置时,它会把用户的权限方案、工作目录、环境变量和 Codex 主目录打包成请求,然后在后台线程里跑 Windows 专用的准备工作。成功后,它会把选择写回配置文件,像把“已经选好这个模式”贴到用户设置里。失败或成功都会记录指标,也就是给开发者看的统计数据,用来知道设置用了多久、哪里失败。非 Windows 系统上,这些函数大多直接返回“不支持”,避免误调用。

函数细节20
WindowsSandboxLevel::from_config31–37 ↗
fn from_config(config: &Config) -> WindowsSandboxLevel

作用:从完整配置里判断当前 Windows 沙盒应该处于什么等级。有人会用它来把用户写的设置变成程序内部能直接判断的状态。

数据流:进去的是一份 Config 配置;它先看用户是否明确写了 Windows 沙盒模式,如果写了 elevated 就返回高权限沙盒,如果写了 unelevated 就返回受限令牌沙盒;如果没写,就继续看功能开关;出来的是一个 WindowsSandboxLevel。

调用关系:它是配置判断的第一站。windows_sandbox_level_from_config 会调用它;当配置里没有明确模式时,它会把判断交给 WindowsSandboxLevel::from_features。

调用图:外部调用 1 个(from_features)。

WindowsSandboxLevel::from_features39–48 ↗
fn from_features(features: &Features) -> WindowsSandboxLevel

作用:只根据功能开关判断 Windows 沙盒等级。它用于没有明确配置时,按实验功能或默认开关来决定走哪条路。

数据流:进去的是 Features 功能开关集合;它先问 elevated 沙盒开关有没有打开,再问普通 Windows 沙盒开关有没有打开;最后输出 elevated、restricted token 或 disabled 三种结果之一。

调用关系:它被 WindowsSandboxLevel::from_config 作为后备判断使用,也被 windows_sandbox_level_from_features 包一层给外部调用。它依赖 Features 的 enabled 方法查询具体开关。

调用图:调用 1 个内部函数(enabled)。

windows_sandbox_level_from_config51–53 ↗
fn windows_sandbox_level_from_config(config: &Config) -> WindowsSandboxLevel

作用:这是一个小包装函数,让别的代码不用直接调用 trait 方法,也能从配置得到沙盒等级。

数据流:进去的是 Config;它原样交给 WindowsSandboxLevel::from_config;出来的是判断好的 WindowsSandboxLevel,不额外改动配置。

调用关系:它像一个门面入口,把外部调用引到 WindowsSandboxLevel::from_config,方便调用方少知道一点内部写法。

调用图:外部调用 1 个(from_config)。

windows_sandbox_level_from_features55–57 ↗
fn windows_sandbox_level_from_features(features: &Features) -> WindowsSandboxLevel

作用:这是另一个小包装函数,用来从功能开关直接得到 Windows 沙盒等级。

数据流:进去的是 Features;它交给 WindowsSandboxLevel::from_features 查询开关;出来的是对应的沙盒等级。

调用关系:它把外部调用转给 WindowsSandboxLevel::from_features,适合只有功能开关、还没有完整配置的场景。

调用图:外部调用 1 个(from_features)。

resolve_windows_sandbox_mode59–64 ↗
fn resolve_windows_sandbox_mode(cfg: &ConfigToml) -> Option<WindowsSandboxModeToml>

作用:读取配置文件里的 Windows 沙盒模式,并兼容老版本配置。没有它,老用户以前写的沙盒开关可能升级后就不生效了。

数据流:进去的是 ConfigToml,也就是从配置文件读出的原始设置;它先找新的 windows.sandbox 字段,找不到再去旧的 features 开关里推断;出来的是可选的 WindowsSandboxModeToml,有就表示用户或旧配置指定了模式。

调用关系:它在 load_config_with_layer_stack 加载配置时被调用,是配置从“文件文字”变成“程序决定”的一环。找不到新字段时,它会交给 legacy_windows_sandbox_mode 处理旧写法。

调用图:被 1 处调用(load_config_with_layer_stack)。

resolve_windows_sandbox_private_desktop66–71 ↗
fn resolve_windows_sandbox_private_desktop(cfg: &ConfigToml) -> bool

作用:决定 Windows 沙盒是否使用 private desktop,也就是一个更隔离的桌面环境。它给这个选项提供默认值,避免配置缺失时行为不确定。

数据流:进去的是 ConfigToml;它读取 windows.sandbox_private_desktop,如果用户写了就用用户的值,如果没写就默认 true;出来的是一个布尔值。

调用关系:它同样在 load_config_with_layer_stack 加载配置时被调用,负责把这个 Windows 专属选项补成明确的 true 或 false。

调用图:被 1 处调用(load_config_with_layer_stack)。

legacy_windows_sandbox_mode73–78 ↗
fn legacy_windows_sandbox_mode(
    features: Option<&FeaturesToml>,
) -> Option<WindowsSandboxModeToml>

作用:从老版本的 features 配置里推断 Windows 沙盒模式。它是升级兼容用的“翻译器”。

数据流:进去的是可能存在的 FeaturesToml;如果没有 features,就直接没有结果;如果有,就取出里面的开关表,再交给下一层函数判断;出来的是可选的沙盒模式。

调用关系:它被 resolve_windows_sandbox_mode 在新配置缺失时调用。它自己不细看每个开关,而是把开关表交给 legacy_windows_sandbox_mode_from_entries。

调用图:调用 1 个内部函数(legacy_windows_sandbox_mode_from_entries)。

legacy_windows_sandbox_mode_from_entries80–103 ↗
fn legacy_windows_sandbox_mode_from_entries(
    entries: &BTreeMap<String, bool>,
) -> Option<WindowsSandboxModeToml>

作用:根据老功能开关的名字,具体判断应该映射成哪种 Windows 沙盒模式。

数据流:进去的是一张“开关名到 true/false”的表;它先看 elevated 沙盒开关,如果开了就返回 Elevated;再看普通 Windows 沙盒开关和更早的实验开关名,如果开了就返回 Unelevated;都没有就返回 None。

调用关系:它是 legacy_windows_sandbox_mode 的实际判断核心。这样老配置名、旧实验配置名都能被统一翻译成新的配置模式。

调用图:被 1 处调用(legacy_windows_sandbox_mode)。

sandbox_setup_is_complete111–113 ↗
fn sandbox_setup_is_complete(_codex_home: &Path) -> bool

作用:检查 elevated Windows 沙盒的准备工作是否已经做完。这样程序不用每次都重复跑昂贵或打扰用户的设置流程。

数据流:进去的是 codex_home 路径,也就是 Codex 放自己数据的目录;在 Windows 上,它把这个路径交给 Windows 沙盒库检查;在非 Windows 上,它直接返回 false;出来的是“是否已完成设置”。

调用关系:run_windows_sandbox_setup_and_persist 在 elevated 模式下会先用它检查。如果已经完成,就跳过 run_elevated_setup;如果没完成,才继续执行设置。

调用图:外部调用 1 个(sandbox_setup_is_complete)。

elevated_setup_failure_details124–126 ↗
fn elevated_setup_failure_details(_err: &anyhow::Error) -> Option<(String, String)>

作用:从 elevated 沙盒设置失败的错误里提取适合记录指标的失败码和失败信息。它会清洗信息,避免把不适合当标签的内容直接上报。

数据流:进去的是 anyhow::Error 错误对象;在 Windows 上,它尝试抽出沙盒设置失败详情,再把错误码转成字符串,并清洗错误消息;在非 Windows 上返回 None;出来的是可选的“错误码、错误消息”。

调用关系:emit_windows_sandbox_setup_failure_metrics 在 elevated 模式失败时会调用它,用这些详情给失败指标加标签。它依赖底层 Windows 沙盒库的 extract_setup_failure 和 sanitize_setup_metric_tag_value。

调用图:被 1 处调用(emit_windows_sandbox_setup_failure_metrics);外部调用 2 个(extract_setup_failure, sanitize_setup_metric_tag_value)。

elevated_setup_failure_metric_name143–145 ↗
fn elevated_setup_failure_metric_name(_err: &anyhow::Error) -> &'static str

作用:给 elevated 沙盒设置失败选择正确的指标名字。它把“用户取消”和“真正失败”分开统计。

数据流:进去的是错误对象;在 Windows 上,它检查错误里是否是 orchestrator helper 启动被取消,如果是就返回 canceled 指标名,否则返回 failure 指标名;在非 Windows 上调用会直接 panic,也就是程序明确报错。

调用关系:emit_windows_sandbox_setup_failure_metrics 在记录 elevated 失败计数时会调用它。它让监控数据能区分用户主动取消和系统出错。

调用图:被 1 处调用(emit_windows_sandbox_setup_failure_metrics);外部调用 2 个(extract_setup_failure, panic!)。

run_elevated_setup178–186 ↗
fn run_elevated_setup(
    _permission_profile: &PermissionProfile,
    _workspace_roots: &[AbsolutePathBuf],
    _command_cwd: &Path,
    _env_map: &HashMap<String, String>,
    _codex_home: &Path,
)

作用:执行 elevated Windows 沙盒的正式准备工作。它把用户权限设置翻译成 Windows 沙盒库能理解的权限清单,然后启动底层设置流程。

数据流:进去的是权限方案、工作区根目录、命令工作目录、环境变量和 codex_home;在 Windows 上,它先根据权限方案和工作区生成沙盒权限,再构造设置请求交给底层库;在非 Windows 上直接返回“不支持”的错误;成功时没有额外返回值,只表示设置完成。

调用关系:run_windows_sandbox_setup_and_persist 在 elevated 模式且检测到还没设置完成时会走到它。它把上层的通用请求交给 codex_windows_sandbox::run_elevated_setup 真正执行。

调用图:调用 1 个内部函数(try_from_permission_profile_for_workspace_roots);外部调用 3 个(bail!, run_elevated_setup, default)。

run_elevated_provisioning_setup189–191 ↗
fn run_elevated_provisioning_setup(_codex_home: &Path, _real_user: &str) -> anyhow::Result<()>

作用:执行 elevated 沙盒的预配置准备,通常用于为真实用户提前准备所需环境。

数据流:进去的是 codex_home 和真实用户名;在 Windows 上,它直接交给底层 Windows 沙盒库做 provisioning setup;在非 Windows 上返回“不支持”的错误;出来是成功或失败。

调用关系:调用图显示它会被 run_elevated 使用。它是更专门的 elevated 准备入口,不负责普通沙盒判断。

调用图:被 1 处调用(run_elevated);外部调用 2 个(bail!, run_elevated_provisioning_setup)。

run_legacy_setup_preflight231–239 ↗
fn run_legacy_setup_preflight(
    _permission_profile: &PermissionProfile,
    _workspace_roots: &[AbsolutePathBuf],
    _command_cwd: &Path,
    _env_map: &HashMap<String, String>,
    _codex_home:

作用:执行旧版、非 elevated Windows 沙盒的预检查。它在真正使用旧沙盒前确认当前权限和路径设置能不能工作。

数据流:进去的是权限方案、工作区根目录、命令目录、环境变量和 codex_home;在 Windows 上,它把这些交给旧版沙盒 preflight 检查;在非 Windows 上返回“不支持”的错误;出来是检查成功或错误。

调用关系:run_windows_sandbox_setup_and_persist 在 Unelevated 模式下会调用它。它对应旧沙盒路线,和 run_elevated_setup 是两条不同准备路径。

调用图:外部调用 2 个(bail!, run_windows_sandbox_legacy_preflight)。

run_setup_refresh_with_extra_read_roots242–251 ↗
fn run_setup_refresh_with_extra_read_roots(
    _permission_profile: &PermissionProfile,
    _workspace_roots: &[AbsolutePathBuf],
    _command_cwd: &Path,
    _env_map: &HashMap<String, String>,

作用:刷新 Windows 沙盒设置,并额外允许一些只读目录。它解决的是运行中临时需要读更多文件,但又不想放开写权限的问题。

数据流:进去的是权限方案、工作区根、命令目录、环境变量、codex_home,以及额外只读目录列表;在 Windows 上,它把这些传给底层刷新函数,并标记 proxy_enforced 为 false;在非 Windows 上返回“不支持”的错误;结果是刷新成功或失败。

调用关系:调用图显示 grant_read_root_non_elevated 会用它。也就是说,当非 elevated 沙盒需要新增只读访问范围时,会通过它更新底层设置。

调用图:被 1 处调用(grant_read_root_non_elevated);外部调用 2 个(bail!, run_setup_refresh_with_extra_read_roots)。

run_windows_sandbox_setup269–294 ↗
async fn run_windows_sandbox_setup(request: WindowsSandboxSetupRequest) -> anyhow::Result<()>

作用:这是对外的 Windows 沙盒设置总入口。它负责计时、执行设置、成功或失败后记录指标。

数据流:进去的是 WindowsSandboxSetupRequest,里面装着模式、权限、目录和环境变量;它记录开始时间,取得 originator,也就是这次操作来自哪里,再清洗成适合指标标签的字符串;然后调用真正执行和保存配置的函数;成功就记录成功指标并返回 Ok,失败就记录失败指标并把错误原样返回。

调用关系:它被 windows_sandbox_setup_start_inner 调用,是用户触发沙盒设置后的主流程入口。它把实际工作交给 run_windows_sandbox_setup_and_persist,并根据结果分别调用成功或失败的埋点函数。

调用图:调用 4 个内部函数(emit_windows_sandbox_setup_failure_metrics, emit_windows_sandbox_setup_success_metrics, run_windows_sandbox_setup_and_persist, originator);被 1 处调用(windows_sandbox_setup_start_inner);外部调用 2 个(now, sanitize_metric_tag_value)。

run_windows_sandbox_setup_and_persist296–343 ↗
async fn run_windows_sandbox_setup_and_persist(
    request: WindowsSandboxSetupRequest,
) -> anyhow::Result<()>

作用:真正执行 Windows 沙盒设置,并在成功后把选择的模式写回配置文件。它保证“做完设置”和“记住设置”连在一起。

数据流:进去的是设置请求;它拆出模式、权限、目录、环境变量等信息,然后用 spawn_blocking 把可能阻塞的 Windows 设置工作放到后台线程;elevated 模式会先检查是否已完成,没完成才跑 elevated 设置,unelevated 模式会跑旧版 preflight;全部成功后,它用 ConfigEditsBuilder 写入 windows sandbox mode,并清掉旧配置键;出来是成功或带原因的错误。

调用关系:它只被 run_windows_sandbox_setup 调用,是主入口下面的实干层。它会用 windows_sandbox_setup_mode_tag 把模式变成配置里要保存的文字,并通过 ConfigEditsBuilder 完成持久化。

调用图:调用 2 个内部函数(new, windows_sandbox_setup_mode_tag);被 1 处调用(run_windows_sandbox_setup);外部调用 1 个(spawn_blocking)。

emit_windows_sandbox_setup_success_metrics345–368 ↗
fn emit_windows_sandbox_setup_success_metrics(
    mode: WindowsSandboxSetupMode,
    originator_tag: &str,
    duration: std::time::Duration,
)

作用:记录 Windows 沙盒设置成功的统计数据。这样开发者能看到成功次数和耗时。

数据流:进去的是设置模式、来源标签和耗时;它先拿全局 metrics 记录器,如果没有就什么也不做;有的话,它把模式转成标签,记录一次耗时,并把成功计数加一;它不改变沙盒本身,只发送统计信息。

调用关系:run_windows_sandbox_setup 在设置成功后调用它。它会调用 windows_sandbox_setup_mode_tag 生成统一的 mode 标签,并依赖 codex_otel::global 取得指标系统。

调用图:调用 1 个内部函数(windows_sandbox_setup_mode_tag);被 1 处调用(run_windows_sandbox_setup);外部调用 1 个(global)。

emit_windows_sandbox_setup_failure_metrics370–424 ↗
fn emit_windows_sandbox_setup_failure_metrics(
    mode: WindowsSandboxSetupMode,
    originator_tag: &str,
    duration: std::time::Duration,
    _err: &anyhow::Error,
)

作用:记录 Windows 沙盒设置失败的统计数据。它不仅记失败次数,还会尽量区分 elevated 失败、用户取消和旧版 preflight 失败。

数据流:进去的是模式、来源标签、耗时和错误;它先拿全局 metrics 记录器,没有就退出;有的话记录失败耗时和失败计数;如果是 elevated 模式,在 Windows 上还会提取失败详情并选择更具体的指标名;如果是 unelevated 模式,则记录旧版预检查失败指标。

调用关系:run_windows_sandbox_setup 在设置失败后调用它。它会用 windows_sandbox_setup_mode_tag 生成标签;在 elevated 失败时,还会调用 elevated_setup_failure_details 和 elevated_setup_failure_metric_name。

调用图:调用 3 个内部函数(elevated_setup_failure_details, elevated_setup_failure_metric_name, windows_sandbox_setup_mode_tag);被 1 处调用(run_windows_sandbox_setup);外部调用 3 个(global, matches!, vec!)。

windows_sandbox_setup_mode_tag426–431 ↗
fn windows_sandbox_setup_mode_tag(mode: WindowsSandboxSetupMode) -> &'static str

作用:把内部的沙盒设置模式变成固定的小写文字标签。这个标签既用于写配置,也用于指标统计。

数据流:进去的是 WindowsSandboxSetupMode;如果是 Elevated,就输出字符串 "elevated";如果是 Unelevated,就输出 "unelevated";没有副作用。

调用关系:run_windows_sandbox_setup_and_persist 用它决定写回配置的值;成功和失败的指标函数也用它保证统计标签写法一致。

调用图:被 3 处调用(emit_windows_sandbox_setup_failure_metrics, emit_windows_sandbox_setup_success_metrics, run_windows_sandbox_setup_and_persist)。

配置编辑与迁移工具

这些文件提供定向编辑器和迁移助手,用于导入或保存用户管理的配置部分及相关资源。

config/src/marketplace_edit.rs源码 ↗
configconfig load/update

这个文件处理的是用户目录里的 config.toml。它把 marketplace 的来源、更新时间、版本、分支名、只下载哪些路径等信息写进配置里的 marketplaces 区块。写入时,如果配置文件不存在,就新建一份;如果 marketplaces 不存在或格式不对,就整理成可以写的表。删除时,它会读配置、找到指定名字并删掉;如果只是不小心把 debug 写成 Debug,它不会假装没找到,而是返回“大小写不匹配”,告诉上层真正配置的名字是什么。这里用 toml_edit 来改 TOML 文件,TOML 是一种常见配置文件格式,toml_edit 的好处是能像改表格一样改配置内容。文件末尾的测试用临时目录模拟真实配置文件,检查新增、删除、缺失、大小写错误和内联表写法都能正常工作。

函数细节12
record_user_marketplace29–39 ↗
fn record_user_marketplace(
    codex_home: &Path,
    marketplace_name: &str,
    update: &MarketplaceConfigUpdate<'_>,
) -> std::io::Result<()>

作用:把一个 marketplace 的信息写入用户配置文件。有人添加或更新 marketplace 来源时,会用它把这件事落到磁盘上,保证下次启动还能记得。

数据流:输入是用户配置目录、marketplace 名字,以及一包更新信息。它先拼出 config.toml 的路径,读取已有配置;如果没有文件,就从空配置开始。然后把这条 marketplace 写入配置内容,最后确保目录存在,并把新配置写回文件。

调用关系:它是写入流程的对外入口。内部先把读文件的活交给 read_or_create_document,再把改 TOML 内容的活交给 upsert_marketplace,最后自己负责创建目录和写文件。测试里会用它先准备配置,再验证删除功能。

调用图:调用 2 个内部函数(read_or_create_document, upsert_marketplace);被 2 处调用(remove_user_marketplace_config_reports_case_mismatch, remove_user_marketplace_removes_requested_entry);外部调用 3 个(join, create_dir_all, write)。

remove_user_marketplace41–44 ↗
fn remove_user_marketplace(codex_home: &Path, marketplace_name: &str) -> std::io::Result<bool>

作用:用最简单的方式删除一个 marketplace,并只告诉调用者“删没删成功”。它适合那些不关心细节、只需要布尔结果的地方。

数据流:输入是用户配置目录和要删除的名字。它调用更详细的删除函数拿到结果,然后把“Removed”翻译成 true,把其他情况翻译成 false。它本身不直接改文件。

调用关系:它是 remove_user_marketplace_config 的简化包装。普通调用方如果只想知道是否删除成功,可以走这里;需要知道是不是大小写写错的调用方,则应直接走 remove_user_marketplace_config。

调用图:调用 1 个内部函数(remove_user_marketplace_config);被 2 处调用(remove_user_marketplace_removes_requested_entry, remove_user_marketplace_returns_false_when_missing)。

remove_user_marketplace_config46–69 ↗
fn remove_user_marketplace_config(
    codex_home: &Path,
    marketplace_name: &str,
) -> std::io::Result<RemoveMarketplaceConfigOutcome>

作用:从配置文件里删除指定 marketplace,并返回更细的结果:删掉了、没找到,或者名字大小写不一致。它用于需要给用户准确提示的删除场景。

数据流:输入是用户配置目录和要删的 marketplace 名。它读取 config.toml;如果文件不存在,直接返回“没找到”。如果文件内容不是合法 TOML,就报错。读成功后,它让 remove_marketplace 修改内存里的配置;只有真的删掉了,才把新配置写回磁盘。

调用关系:它是删除流程的主要入口。它负责文件读写和错误处理,把真正“在配置结构里找并删”的工作交给 remove_marketplace。remove_user_marketplace 会调用它并把详细结果压缩成 true/false,测试也直接调用它来检查大小写和内联表情况。

调用图:调用 1 个内部函数(remove_marketplace);被 3 处调用(remove_user_marketplace, remove_user_marketplace_config_removes_inline_table_entry, remove_user_marketplace_config_reports_case_mismatch);外部调用 4 个(join, create_dir_all, read_to_string, write)。

read_or_create_document71–79 ↗
fn read_or_create_document(config_path: &Path) -> std::io::Result<DocumentMut>

作用:读取 config.toml,并把它变成可编辑的 TOML 文档;如果文件还不存在,就给一份空文档。这样写入流程不用自己到处判断文件有没有。

数据流:输入是配置文件路径。它尝试读文件;读到了就解析成 DocumentMut,也就是可修改的 TOML 文档。文件不存在时,它返回一个新的空文档;其他读文件错误或解析错误会变成错误返回。

调用关系:它只服务于 record_user_marketplace。record_user_marketplace 需要先拿到一份可改的配置,再交给 upsert_marketplace 写入具体内容。

调用图:被 1 处调用(record_user_marketplace);外部调用 2 个(new, read_to_string)。

upsert_marketplace81–118 ↗
fn upsert_marketplace(
    doc: &mut DocumentMut,
    marketplace_name: &str,
    update: &MarketplaceConfigUpdate<'_>,
)

作用:在内存里的 TOML 配置中新增或替换一个 marketplace 条目。“upsert”可以理解成“有就更新,没有就插入”。

数据流:输入是一份可编辑配置、marketplace 名字和更新信息。它先确保根部有 marketplaces 这个表;如果原来不是表,就改成表。然后建立该 marketplace 的配置项,写入更新时间、版本、来源类型、来源地址、分支名和稀疏路径,最后放到 marketplaces 下面。

调用关系:它不碰磁盘,只负责改内存里的配置结构。record_user_marketplace 先读出配置,再调用它填内容,之后再把结果写回文件。它会用 new_implicit_table 创建 TOML 里需要的隐式表。

调用图:调用 1 个内部函数(new_implicit_table);被 1 处调用(record_user_marketplace);外部调用 6 个(as_table_mut, Table, Value, new, Array, value)。

remove_marketplace120–167 ↗
fn remove_marketplace(
    doc: &mut DocumentMut,
    marketplace_name: &str,
) -> RemoveMarketplaceConfigOutcome

作用:在内存里的 TOML 配置中找到并删除一个 marketplace。它还会识别“名字大小写不同但看起来是同一个”的情况,方便给用户更友好的提示。

数据流:输入是一份可编辑配置和要删除的名字。它寻找 marketplaces 区块,支持普通表和内联表两种写法;找到精确名字就删除。没找到时,它会检查是否存在大小写不同的同名项。删除后如果 marketplaces 已经空了,就把整个 marketplaces 区块也移掉。

调用关系:它是删除配置内容的核心零件,由 remove_user_marketplace_config 调用。它把大小写检查交给 case_mismatched_key,自己负责根据结果决定返回 Removed、NotFound 还是 NameCaseMismatch。

调用图:调用 1 个内部函数(case_mismatched_key);被 1 处调用(remove_user_marketplace_config);外部调用 1 个(as_table_mut)。

case_mismatched_key169–175 ↗
fn case_mismatched_key(
    mut keys: impl Iterator<Item = &'a str>,
    requested_name: &str,
) -> Option<String>

作用:检查一堆已有名字里,是否有一个和请求名字只差大小写。比如配置里是 debug,用户输入 Debug,它就能找出来。

数据流:输入是一串已有 key 和用户请求的名字。它逐个比较:必须不是完全一样,但忽略大小写后相同。找到就返回配置里真实的名字;找不到就返回空。

调用关系:它是 remove_marketplace 的小助手。remove_marketplace 删除失败时会调用它,判断这是“真的没有”,还是“用户大小写写错了”。

调用图:被 1 处调用(remove_marketplace);外部调用 1 个(find)。

new_implicit_table177–181 ↗
fn new_implicit_table() -> TomlTable

作用:创建一个 TOML 隐式表。隐式表可以理解成为了容纳子项自动出现的表,不一定要用户显式写出完整表头。

数据流:它不需要输入。它新建一个 TOML 表,并把它标记为隐式,然后返回给调用者使用。

调用关系:它服务于 upsert_marketplace。upsert_marketplace 在发现 marketplaces 不存在或需要重建时,会用它生成合适的表结构。

调用图:被 1 处调用(upsert_marketplace);外部调用 1 个(new)。

tests::remove_user_marketplace_removes_requested_entry190–215 ↗
fn remove_user_marketplace_removes_requested_entry()

作用:测试删除一个已有 marketplace 时,只会删掉指定那一条,不会误删别的条目。

数据流:它先创建临时配置目录,再写入 debug 和 other 两个 marketplace。然后删除 debug,读取配置文件检查结果:删除返回 true,配置里只剩 other。

调用关系:这是删除主流程的安全网。它先借 record_user_marketplace 准备数据,再调用 remove_user_marketplace,最后用读取和解析配置文件的方式确认磁盘上的结果真的正确。

调用图:调用 2 个内部函数(record_user_marketplace, remove_user_marketplace);外部调用 5 个(new, assert!, assert_eq!, read_to_string, from_str)。

tests::remove_user_marketplace_returns_false_when_missing218–224 ↗
fn remove_user_marketplace_returns_false_when_missing()

作用:测试要删除的 marketplace 不存在时,简化删除函数会返回 false,而不是报错。

数据流:它创建一个空的临时配置目录,然后尝试删除 debug。因为没有配置项可删,结果应该是 false。

调用关系:它验证 remove_user_marketplace 对“没找到”这种正常情况的处理。这个测试保证上层可以把缺失当作普通结果处理,而不是异常崩掉。

调用图:调用 1 个内部函数(remove_user_marketplace);外部调用 2 个(new, assert!)。

tests::remove_user_marketplace_config_reports_case_mismatch227–247 ↗
fn remove_user_marketplace_config_reports_case_mismatch()

作用:测试用户输入名字大小写不一致时,详细删除函数会指出真正配置的名字。

数据流:它先写入名为 debug 的 marketplace,然后尝试删除 Debug。函数不应删除,也不应简单说没找到,而应返回 NameCaseMismatch,并带上配置中的 debug。

调用关系:它覆盖 remove_user_marketplace_config 到 remove_marketplace 再到 case_mismatched_key 这条路径。这个测试保证命令行或界面可以给用户提示“你是不是想删 debug”。

调用图:调用 2 个内部函数(record_user_marketplace, remove_user_marketplace_config);外部调用 2 个(new, assert_eq!)。

tests::remove_user_marketplace_config_removes_inline_table_entry250–275 ↗
fn remove_user_marketplace_config_removes_inline_table_entry()

作用:测试 marketplaces 用内联表写法时,也能正确删除其中一项。内联表就是把表内容写在一对大括号里的一种 TOML 写法。

数据流:它手动写入一份包含 debug 和 other 的配置文件,其中 marketplaces 是内联表。然后删除 debug,重新读取配置,确认返回 Removed,并且只剩 other。

调用关系:它验证 remove_marketplace 对两种 TOML 表达方式都兼容。这样用户或旧版本程序不管用普通表还是内联表保存配置,删除功能都能正常工作。

调用图:调用 1 个内部函数(remove_user_marketplace_config);外部调用 6 个(new, assert!, assert_eq!, read_to_string, write, from_str)。

config/src/mcp_edit.rs源码 ↗
configconfig load / config update

MCP 服务器可以理解成外部工具服务,程序要知道它们叫什么、怎么启动、用什么网址、需要哪些环境变量。这个文件就是它们在配置文件里的“出入口”。读的时候,它打开用户目录下的 config.toml,找到 mcp_servers 这一段,转换成程序能用的结构;如果文件不存在,就当作没有配置。它还会特意拦住 bearer_token 这种把密钥直接写进文件的旧写法,提醒改用环境变量,避免秘密泄露。写的时候,它用 ConfigEditsBuilder 先收集要改的内容,再把磁盘读写放到后台线程里做,避免卡住异步程序。真正写入前,它会把服务器配置整理成 TOML 格式,只写有意义的字段,空列表和默认值尽量不写,让配置文件保持干净。

函数细节12
load_global_mcp_servers20–41 ↗
async fn load_global_mcp_servers(
    codex_home: &Path,
) -> std::io::Result<BTreeMap<String, McpServerConfig>>

作用:读取全局 config.toml 里的 MCP 服务器配置,返回一张按名字排列的服务器表。启动程序或需要查看现有服务器时会用它。

数据流:输入是 codex_home,也就是配置文件所在的用户目录。它拼出 config.toml 路径,异步读取文件;如果文件不存在,就返回空表。读到内容后,它把 TOML 文本解析成通用值,取出 mcp_servers,先检查有没有不安全的内联 bearer_token,再转换成 McpServerConfig 表返回;如果文本格式坏了,就返回读取错误。

调用关系:这是读取配置的入口。它自己负责找文件和解析大框架,中途把安全检查交给 ensure_no_inline_bearer_tokens;外部调用者拿到结果后,就可以列出、连接或修改这些 MCP 服务器。

调用图:调用 1 个内部函数(ensure_no_inline_bearer_tokens);外部调用 3 个(new, join, read_to_string)。

ensure_no_inline_bearer_tokens43–60 ↗
fn ensure_no_inline_bearer_tokens(value: &TomlValue) -> std::io::Result<()>

作用:检查 MCP 服务器配置里有没有直接写在文件里的 bearer_token。这样做是为了避免把访问令牌这种秘密明文存在配置文件中。

数据流:输入是一段已经解析好的 TOML 值。它先确认这段值是不是服务器表;如果不是,就直接通过。然后逐个服务器看有没有 bearer_token 字段;一旦发现,就生成一条明确的错误信息,要求改用 bearer_token_env_var;全部检查通过则不返回数据,只表示没问题。

调用关系:它只在 load_global_mcp_servers 读配置时被调用,像门口的安检员。load_global_mcp_servers 负责读文件,它负责挡住不再支持、也不够安全的写法。

调用图:被 1 处调用(load_global_mcp_servers);外部调用 3 个(as_table, new, format!)。

ConfigEditsBuilder::new68–73 ↗
fn new(codex_home: &Path) -> Self

作用:创建一个配置修改器,记住配置目录在哪里。后续要改 MCP 服务器列表时,先从这里开始。

数据流:输入是 codex_home 路径。它把这个路径复制成自己拥有的一份 PathBuf,并把待替换的 MCP 服务器列表设为空,表示目前还没安排任何改动。输出是一个 ConfigEditsBuilder 对象。

调用关系:这是写配置流程的起点。添加、删除、启用、禁用服务器等上层命令会先调用它,然后通常接着调用 replace_mcp_servers 填入新列表,最后调用 apply 保存。

调用图:被 43 处调用(skills_config_write_response_inner, disable_feature_in_config, enable_feature_in_config, run_add, run_remove, run_elevated, get_disabled_server_shows_single_line, list_and_get_render_expected_output, replace_mcp_servers_serializes_oauth_client_id, replace_mcp_servers_serializes_per_tool_approval_overrides (+15 more));外部调用 1 个(to_path_buf)。

ConfigEditsBuilder::replace_mcp_servers75–78 ↗
fn replace_mcp_servers(mut self, servers: &BTreeMap<String, McpServerConfig>) -> Self

作用:告诉配置修改器:保存时要用这份新的 MCP 服务器列表替换旧列表。它只是登记改动,还不碰磁盘。

数据流:输入是现有或新生成的服务器表。它把这张表克隆一份存在 builder 里面,作为稍后写入 config.toml 的内容。输出还是同一个 builder,方便调用者继续链式调用。

调用关系:它夹在 ConfigEditsBuilder::new 和 ConfigEditsBuilder::apply 之间。上层命令先准备好新服务器表,再用这个函数把“我要替换 mcp_servers”这件事交给 builder。

ConfigEditsBuilder::apply80–86 ↗
async fn apply(self) -> std::io::Result<()>

作用:真正执行配置保存,但把耗时的磁盘读写放到后台阻塞任务里。这样异步程序的主流程不会因为写文件而卡住。

数据流:输入是已经登记好改动的 builder。它把 builder 移进 spawn_blocking,也就是专门跑阻塞工作的后台线程;后台调用 apply_blocking 完成实际读写。输出是成功或失败的结果;如果后台任务崩了,会包装成普通的输入输出错误。

调用关系:这是外部保存配置时调用的公共入口。它不直接写文件,而是把具体工作交给 ConfigEditsBuilder::apply_blocking,自己负责把同步磁盘操作安全地接入异步环境。

调用图:外部调用 1 个(spawn_blocking)。

ConfigEditsBuilder::apply_blocking88–96 ↗
fn apply_blocking(self) -> std::io::Result<()>

作用:在普通阻塞线程里完成真正的配置文件修改和写回。它是 apply 背后的实际干活者。

数据流:输入是包含配置目录和待修改内容的 builder。它拼出 config.toml 路径,读取已有 TOML 文档;如果文件不存在就新建空文档。若 builder 里有新的 MCP 服务器表,就替换文档里的 mcp_servers。最后确保配置目录存在,把修改后的 TOML 文本写回文件。

调用关系:它由 ConfigEditsBuilder::apply 在后台线程中调用。它负责调度具体步骤:读文档交给 read_or_create_document,替换服务器段交给 replace_mcp_servers,最后自己完成建目录和写文件。

调用图:调用 2 个内部函数(read_or_create_document, replace_mcp_servers);外部调用 3 个(join, create_dir_all, write)。

read_or_create_document99–107 ↗
fn read_or_create_document(config_path: &Path) -> std::io::Result<DocumentMut>

作用:读取 config.toml,并把它变成可以编辑的 TOML 文档;如果文件还没有,就给一个空文档。

数据流:输入是 config.toml 的路径。它尝试从磁盘读文本;读到后解析成 DocumentMut,也就是可修改的 TOML 文档。文件不存在时,它返回一个新的空文档;其他读取错误或 TOML 格式错误则原样变成错误返回。

调用关系:它被 ConfigEditsBuilder::apply_blocking 调用,是写配置前的第一步。后续 replace_mcp_servers 会在它返回的文档上动手修改。

调用图:被 1 处调用(apply_blocking);外部调用 2 个(new, read_to_string)。

replace_mcp_servers109–122 ↗
fn replace_mcp_servers(doc: &mut DocumentMut, servers: &BTreeMap<String, McpServerConfig>)

作用:把 TOML 文档里的 mcp_servers 整段替换成新的服务器列表。列表为空时,它会直接删除这一段。

数据流:输入是一个可修改的 TOML 文档和一张服务器配置表。它先拿到文档根表;如果服务器表为空,就移除 mcp_servers。否则新建一张 TOML 表,逐个服务器调用 serialize_mcp_server 转成 TOML 项,再把整张表放回文档根部。输出没有单独返回值,但文档内容已经被改好。

调用关系:它由 ConfigEditsBuilder::apply_blocking 调用,处在“读出文档”和“写回文件”之间。它把每个服务器的细节序列化工作交给 serialize_mcp_server。

调用图:调用 1 个内部函数(serialize_mcp_server);被 1 处调用(apply_blocking);外部调用 3 个(as_table_mut, Table, new)。

serialize_mcp_server124–250 ↗
fn serialize_mcp_server(config: &McpServerConfig) -> TomlItem

作用:把一个程序内部的 MCP 服务器配置,翻译成 config.toml 里能写出来的一段 TOML。它决定哪些字段该写、该怎么写。

数据流:输入是一个 McpServerConfig。它先根据连接方式分两类写字段:Stdio 是本地命令、参数、环境变量、工作目录;StreamableHttp 是网址、令牌环境变量、HTTP 头等。然后补充启用状态、环境编号、是否必需、超时、工具审批方式、启用或禁用的工具、OAuth 信息、单个工具配置等。空值和默认值多数会被跳过。输出是一个 TOML 表项。

调用关系:它被 replace_mcp_servers 逐个服务器调用,是配置写出格式的核心。遇到字符串列表会交给 array_from_strings,遇到环境变量列表会交给 array_from_env_vars,遇到键值表会交给 table_from_pairs。

调用图:调用 4 个内部函数(array_from_env_vars, array_from_strings, table_from_pairs, is_local_environment);被 1 处调用(replace_mcp_servers);外部调用 3 个(Table, new, value)。

array_from_strings252–258 ↗
fn array_from_strings(values: &[String]) -> TomlItem

作用:把一串普通字符串变成 TOML 数组,比如参数列表、工具名列表、权限范围列表。这样它们才能正确写进配置文件。

数据流:输入是字符串切片。它新建一个 TOML 数组,把每个字符串复制进去,最后包装成 TOML 值返回。原始列表不会被修改。

调用关系:它是 serialize_mcp_server 的小帮手。凡是服务器配置里有“多个字符串”的字段,serialize_mcp_server 就会让它来做统一转换。

调用图:被 1 处调用(serialize_mcp_server);外部调用 2 个(Value, new)。

array_from_env_vars260–276 ↗
fn array_from_env_vars(env_vars: &[McpServerEnvVar]) -> TomlItem

作用:把 MCP 服务器需要的环境变量声明变成 TOML 数组。它同时支持简单名字和带来源说明的复杂写法。

数据流:输入是一组 McpServerEnvVar。每一项如果只是 Name,就写成一个字符串;如果是 Config,就写成一个内联小表,里面有 name,必要时还有 source。最后输出一个 TOML 数组值。

调用关系:它只由 serialize_mcp_server 调用,用在 Stdio 类型服务器的 env_vars 字段上。这样配置文件既能写简单环境变量,也能写带额外来源信息的环境变量。

调用图:被 1 处调用(serialize_mcp_server);外部调用 3 个(Value, new, new)。

table_from_pairs278–290 ↗
fn table_from_pairs(pairs: I) -> TomlItem

作用:把一组字符串键值对变成 TOML 表,比如环境变量表或 HTTP 请求头表。它会先按键排序,让写出的配置更稳定、更好比较。

数据流:输入是一批键和值都是字符串的配对。它先收集并按键名排序,再新建 TOML 表,把每个键值写进去,最后返回这个表项。输入数据本身不变。

调用关系:它是 serialize_mcp_server 的通用小工具。无论是 env、http_headers,还是 env_http_headers,只要是“名字到值”的表,都会交给它统一生成 TOML。

调用图:被 1 处调用(serialize_mcp_server);外部调用 4 个(into_iter, Table, new, value)。

config/src/plugin_edit.rs源码 ↗
domain_logicconfig update

插件开关最终要落到用户目录里的 TOML 配置文件中。TOML 可以理解成一种人也能读的配置文本。这个文件做的事很具体:打开配置文件,没有就新建;找到 plugins 区域;把某个插件的 enabled 改成 true 或 false,或者把这个插件条目删掉。它还会处理几个容易出错的地方:如果配置文件是软链接,会写到真正目标文件;写文件时用原子写入,也就是先写好临时内容再替换,减少写到一半坏掉的风险;如果原来字段有排版装饰,会尽量保留。异步入口会把真正读写磁盘的活儿丢到后台阻塞线程,避免卡住主程序。

函数细节19
set_user_plugin_enabled21–34 ↗
async fn set_user_plugin_enabled(
    codex_home: &Path,
    plugin_key: String,
    enabled: bool,
) -> std::io::Result<()>

作用:把某个插件在用户配置里的启用状态设成开或关。外部代码通常不需要关心配置文件怎么改,只要告诉它用户目录、插件名字和 true/false。

数据流:进去的是 Codex 用户目录、插件 key、是否启用。它把这些包装成一个“设置 enabled 字段”的编辑动作,然后交给统一的配置编辑函数。出来的是写入成功或失败的结果,配置文件可能被更新。

调用关系:它是给外部使用的简单入口。测试里的写入、保留字段、软链接场景都会调用它;它自己不直接碰文件,而是把活儿交给 apply_user_plugin_config_edits。

调用图:调用 1 个内部函数(apply_user_plugin_config_edits);被 3 处调用(set_user_plugin_enabled_follows_config_symlink, set_user_plugin_enabled_preserves_existing_plugin_fields, set_user_plugin_enabled_writes_plugin_entry);外部调用 1 个(vec!)。

clear_user_plugin36–38 ↗
async fn clear_user_plugin(codex_home: &Path, plugin_key: String) -> std::io::Result<()>

作用:从用户配置中删除某个插件的配置条目。常见用途是用户不再想保留这个插件的本地设置。

数据流:进去的是 Codex 用户目录和插件 key。它生成一个“删除这个插件”的编辑动作,再交给统一编辑函数。出来的是操作结果;如果插件存在,配置文件会少掉这个插件条目;如果不存在,通常不会新建配置文件。

调用关系:它是删除插件配置的对外入口。相关测试会用它检查删除已有条目和删除不存在条目的行为;实际文件读写交给 apply_user_plugin_config_edits。

调用图:调用 1 个内部函数(apply_user_plugin_config_edits);被 2 处调用(clear_user_plugin_missing_entry_does_not_create_config, clear_user_plugin_removes_empty_plugins_table);外部调用 1 个(vec!)。

apply_user_plugin_config_edits40–48 ↗
async fn apply_user_plugin_config_edits(
    codex_home: &Path,
    edits: Vec<PluginConfigEdit>,
) -> std::io::Result<()>

作用:统一接收一批插件配置修改,并安排它们安全执行。它特别重要,因为读写文件是慢活,不能直接堵住异步程序的主流程。

数据流:进去的是用户目录和一组编辑动作。它先把路径复制成可在线程里使用的对象,然后用 spawn_blocking 把真正的磁盘读写放到后台阻塞线程。出来的是成功或错误;如果后台任务崩了,会转换成普通输入输出错误。

调用关系:set_user_plugin_enabled 和 clear_user_plugin 都会调用它。它像调度员,只负责把编辑任务送到 apply_user_plugin_config_edits_blocking 这类真正干活的同步逻辑里。

调用图:被 2 处调用(clear_user_plugin, set_user_plugin_enabled);外部调用 2 个(to_path_buf, spawn_blocking)。

apply_user_plugin_config_edits_blocking50–75 ↗
fn apply_user_plugin_config_edits_blocking(
    codex_home: &Path,
    edits: Vec<PluginConfigEdit>,
) -> std::io::Result<()>

作用:真正执行插件配置修改:读配置、改内容、必要时写回文件。它是这个文件的核心工作台。

数据流:进去的是用户目录和编辑列表。它先忽略空编辑;再定位 config.toml,并解析软链接的读写路径;接着读取或新建 TOML 文档;逐条执行设置或删除;如果确实改了内容,就用原子写入保存。出来的是成功或文件读写、解析错误;磁盘上的配置可能被更新。

调用关系:它由异步包装函数间接调用。它会调用 read_or_create_document 读配置,按编辑类型调用 set_plugin_enabled 或 clear_plugin,最后调用 write_atomically 保存。

调用图:调用 3 个内部函数(clear_plugin, read_or_create_document, set_plugin_enabled);外部调用 3 个(join, resolve_symlink_write_paths, write_atomically)。

read_or_create_document77–88 ↗
fn read_or_create_document(config_path: Option<&Path>) -> std::io::Result<DocumentMut>

作用:读取现有 TOML 配置;如果文件不存在,就给后续代码一个空配置。这样新用户第一次改插件设置时也能正常工作。

数据流:进去的是一个可选的配置文件路径。没有路径就直接创建空 TOML 文档;有路径就读取文本并解析成可编辑文档;文件不存在也返回空文档。出来的是可修改的配置文档,或者读取失败、格式不合法的错误。

调用关系:apply_user_plugin_config_edits_blocking 在改配置前会先调用它。它只负责把磁盘文本变成内存里的 TOML 文档,不负责具体改哪个插件。

调用图:被 1 处调用(apply_user_plugin_config_edits_blocking);外部调用 2 个(new, read_to_string)。

set_plugin_enabled90–103 ↗
fn set_plugin_enabled(doc: &mut DocumentMut, plugin_key: &str, enabled: bool) -> bool

作用:在 TOML 文档里写入某个插件的 enabled 字段。它会尽量不动这个插件的其他字段,比如 source 路径。

数据流:进去的是可修改的 TOML 文档、插件 key、true/false。它先确保有 plugins 表,再确保这个插件自己也是一张可写的表,然后把 enabled 值写进去;如果旧的 enabled 已存在,会复制它的排版装饰。出来的是是否真的完成了修改,文档内容会被改动。

调用关系:apply_user_plugin_config_edits_blocking 遇到 SetEnabled 编辑时调用它。它会借助 ensure_plugins_table、ensure_table_for_write 和 preserve_decor 完成结构准备和排版保留。

调用图:调用 3 个内部函数(ensure_plugins_table, ensure_table_for_write, preserve_decor);被 1 处调用(apply_user_plugin_config_edits_blocking);外部调用 1 个(value)。

clear_plugin105–114 ↗
fn clear_plugin(doc: &mut DocumentMut, plugin_key: &str) -> bool

作用:从 TOML 文档中移除某个插件的整段配置。它只删除目标插件,不会主动制造新的 plugins 区域。

数据流:进去的是可修改的 TOML 文档和插件 key。它先找根部的 plugins 项;找不到就说明无需删除;如果找到了,会确认它能当表来读,然后删除对应插件。出来的是是否真的删掉了东西,文档可能变少。

调用关系:apply_user_plugin_config_edits_blocking 遇到 Clear 编辑时调用它。它依赖 ensure_table_for_read 来温和地处理 inline table,也就是写在一行里的小表。

调用图:调用 1 个内部函数(ensure_table_for_read);被 1 处调用(apply_user_plugin_config_edits_blocking);外部调用 1 个(as_table_mut)。

ensure_plugins_table116–122 ↗
fn ensure_plugins_table(doc: &mut DocumentMut) -> Option<&mut TomlTable>

作用:确保配置文档里有一个 plugins 表,供插件配置存放。没有这个表时,它会创建一个隐式表,意思是内部存在但不一定单独打印成标题。

数据流:进去的是可修改的 TOML 文档。它查看根表里有没有 plugins;没有就插入一个新的隐式表;然后确认这个位置确实能作为表来写。出来的是 plugins 表的可修改引用,或者在结构不合适时返回空。

调用关系:set_plugin_enabled 在写插件状态前调用它。它再调用 ensure_table_for_write,把已有内容转换成可写表。

调用图:调用 2 个内部函数(ensure_table_for_write, new_implicit_table);被 1 处调用(set_plugin_enabled);外部调用 2 个(as_table_mut, Table)。

ensure_table_for_write124–140 ↗
fn ensure_table_for_write(item: &mut TomlItem) -> Option<&mut TomlTable>

作用:把一个 TOML 项准备成“可以写字段的表”。它让代码能兼容空项、普通表、以及一行写法的 inline table。

数据流:进去的是一个 TOML 项。它如果已经是表就直接返回;如果是 inline table,就转换成普通表;如果是空项,就新建表;如果是其他不能变成表的东西,就放弃。出来的是可写表引用,或没有结果;传入的 TOML 项可能被替换成表。

调用关系:ensure_plugins_table 和 set_plugin_enabled 都会用它。它在写入前充当“把容器准备好”的适配器,必要时会用 new_implicit_table 创建新表。

调用图:调用 1 个内部函数(new_implicit_table);被 2 处调用(ensure_plugins_table, set_plugin_enabled);外部调用 2 个(Table, as_table_mut)。

ensure_table_for_read142–152 ↗
fn ensure_table_for_read(item: &mut TomlItem) -> Option<&mut TomlTable>

作用:把一个 TOML 项准备成“可以按表读取”的形态。和写入版相比,它更谨慎,不会凭空创建新表。

数据流:进去的是一个 TOML 项。它如果已经是表,就保持不变;如果是一行写法的 inline table,就复制并转换成普通表;如果是别的类型,就返回空。出来的是可读写的表引用,或者没有结果;传入项可能从 inline table 变成普通表。

调用关系:clear_plugin 删除插件前调用它。遇到 inline table 时,它把转换工作交给 table_from_inline。

调用图:调用 1 个内部函数(table_from_inline);被 1 处调用(clear_plugin);外部调用 2 个(Table, as_table_mut)。

table_from_inline154–162 ↗
fn table_from_inline(inline: &toml_edit::InlineTable) -> TomlTable

作用:把 TOML 的 inline table,也就是类似 { enabled = true } 的一行小表,转换成普通表。这样后面的代码就能用统一方式增删字段。

数据流:进去的是 inline table。它新建一个隐式普通表,逐个复制里面的键和值,并清掉值后面多余的后缀装饰。出来的是一个普通 TOML 表。

调用关系:ensure_table_for_read 会在需要把 inline table 当普通表处理时调用它。它自己使用 new_implicit_table 创建目标表。

调用图:调用 1 个内部函数(new_implicit_table);被 1 处调用(ensure_table_for_read);外部调用 2 个(iter, Value)。

new_implicit_table164–168 ↗
fn new_implicit_table() -> TomlTable

作用:创建一个隐式 TOML 表。隐式表像一个内部文件夹:可以装配置,但不一定在文件里单独显示一个标题。

数据流:进去没有业务输入。它创建新的 TOML 表,并把 implicit 标记设为 true。出来的是这个新表。

调用关系:ensure_plugins_table、ensure_table_for_write 和 table_from_inline 都会调用它。它是创建配置表时共用的小工具。

调用图:被 3 处调用(ensure_plugins_table, ensure_table_for_write, table_from_inline);外部调用 1 个(new)。

preserve_decor170–178 ↗
fn preserve_decor(existing: &TomlItem, replacement: &mut TomlItem)

作用:尽量保留旧 TOML 值的排版装饰,比如空格、注释相关的格式信息。这样程序改值时,不会把用户手写配置弄得面目全非。

数据流:进去的是旧的 TOML 项和新的替换项。只有两边都是普通值时,它才把旧值的 decor 装饰复制到新值上。出来没有单独返回值,但新的替换项会带上旧排版信息。

调用关系:set_plugin_enabled 在覆盖 enabled 值前调用它。它不决定改什么,只负责让改完后的文本尽量像原来。

调用图:被 1 处调用(set_plugin_enabled)。

tests::set_user_plugin_enabled_writes_plugin_entry187–207 ↗
async fn set_user_plugin_enabled_writes_plugin_entry()

作用:测试第一次启用插件时,配置文件会写出正确的插件段落和 enabled = true。

数据流:进去的是一个临时用户目录。测试调用 set_user_plugin_enabled,然后读取生成的配置并和预期 TOML 比较。出来是测试通过或失败;临时目录里会出现配置文件。

调用关系:它验证 set_user_plugin_enabled 这条对外入口能从无到有写配置。它用 read_config 帮忙读取结果。

调用图:调用 1 个内部函数(set_user_plugin_enabled);外部调用 4 个(new, assert_eq!, read_config, from_str)。

tests::set_user_plugin_enabled_preserves_existing_plugin_fields210–240 ↗
async fn set_user_plugin_enabled_preserves_existing_plugin_fields()

作用:测试修改 enabled 时,不会删掉同一个插件下已有的其他字段。这里用 source 字段做例子。

数据流:进去的是带有现成配置文件的临时目录。测试先写入 enabled = false 和 source,然后调用 set_user_plugin_enabled 改成 true,最后读取配置和预期比较。出来是测试通过或失败;配置里的 source 应保留下来。

调用关系:它保护 set_plugin_enabled 的关键行为:只改 enabled,不误伤别的插件信息。它通过 set_user_plugin_enabled 走完整外部流程。

调用图:调用 1 个内部函数(set_user_plugin_enabled);外部调用 5 个(new, assert_eq!, read_config, write, from_str)。

tests::clear_user_plugin_removes_empty_plugins_table243–262 ↗
async fn clear_user_plugin_removes_empty_plugins_table()

作用:测试删除唯一插件后,配置文件不会留下没内容的插件配置。也就是删干净后文件内容为空。

数据流:进去的是一个已有插件配置的临时目录。测试调用 clear_user_plugin 删除该插件,再直接读取 config.toml 的文本。出来是测试通过或失败;文件内容应变成空字符串。

调用关系:它验证 clear_user_plugin 到 clear_plugin 的删除路径。这个测试确保清理结果对用户来说是干净的。

调用图:调用 1 个内部函数(clear_user_plugin);外部调用 3 个(new, assert_eq!, write)。

tests::clear_user_plugin_missing_entry_does_not_create_config265–273 ↗
async fn clear_user_plugin_missing_entry_does_not_create_config()

作用:测试删除一个本来不存在的插件时,不会莫名其妙创建配置文件。这避免用户目录被无意义地写入。

数据流:进去的是空的临时用户目录。测试调用 clear_user_plugin,然后检查 config.toml 是否不存在。出来是测试通过或失败;理想情况下磁盘不发生写入。

调用关系:它验证 apply_user_plugin_config_edits_blocking 的“没变化就不写文件”行为。入口仍然走 clear_user_plugin。

调用图:调用 1 个内部函数(clear_user_plugin);外部调用 2 个(new, assert!)。

tests::read_config304–306 ↗
fn read_config(codex_home: &Path) -> toml::Value

作用:测试用的小帮手:从临时用户目录读取 config.toml,并解析成 TOML 值,方便断言比较。

数据流:进去的是用户目录路径。它拼出 config.toml 路径,读取文本,再解析成 toml::Value。出来的是可比较的 TOML 数据;如果读取或解析失败,测试会直接报错。

调用关系:写入相关测试会调用它来检查最终配置。它只服务测试,不参与正式运行流程。

调用图:外部调用 3 个(join, read_to_string, from_str)。

external-agent-migration/src/lib.rs源码 ↗
domain_logicmigration/import time

这个文件像一个“搬家师傅”:它会去旧目录里找 Claude 的配置文件、Markdown 文档和 hook 脚本,然后筛选出 Codex 能理解、也比较安全的部分,再写成 Codex 的 TOML、JSON 或技能文件。MCP 可以理解成外部工具服务器配置;hooks 是在某些事件发生时自动跑的小脚本;subagents 是专门干某类活的小助手;commands 是用户自定义命令模板。文件里很重要的一点是“宁可不搬,也不乱搬”:如果发现环境变量占位符、复杂模板、异步 hook、Windows 风格路径或可能冲突的名字,它会跳过,避免生成坏配置。它还会把文档里的 Claude 叫法改成 Codex,帮迁移后的内容更自然。

函数细节105
build_mcp_config_from_external44–84 ↗
fn build_mcp_config_from_external(
    source_root: &Path,
    external_agent_home: Option<&Path>,
    settings: Option<&JsonValue>,
) -> io::Result<TomlValue>

作用:从外部智能体的 MCP 配置里挑出 Codex 能用的服务器配置,并组装成 Codex 的 TOML 配置。MCP 可以简单理解成“让智能体连接外部工具的接口”。

数据流:输入项目目录、可选的外部智能体主目录、可选设置 → 读取旧 MCP 服务器,按启用/禁用名单过滤,再逐个转成 TOML 表 → 输出一个 TOML 值;如果没有可用服务器,就输出空表。

调用关系:这是 MCP 迁移的对外入口。它先把读取工作交给 read_external_mcp_servers,再把每个服务器交给 mcp_server_toml_table 做格式转换。

调用图:调用 2 个内部函数(mcp_server_toml_table, read_external_mcp_servers);外部调用 3 个(default, Table, new)。

hooks_migration_description86–99 ↗
fn hooks_migration_description(
    source_external_agent_dir: &Path,
    target_hooks: &Path,
) -> io::Result<Option<String>>

作用:生成一句给用户看的 hook 迁移说明。只有确实有 hook 可以迁移时才返回说明。

数据流:输入旧 hook 配置目录和目标 hooks 文件路径 → 先查询有哪些事件会被迁移 → 如果没有事件返回空,否则返回“从哪里迁到哪里”的文字。

调用关系:它是展示层会用的小帮手,内部把判断工作交给 hook_migration_event_names。

调用图:调用 1 个内部函数(hook_migration_event_names);外部调用 1 个(format!)。

hook_migration_event_names101–107 ↗
fn hook_migration_event_names(
    source_external_agent_dir: &Path,
    target_hooks: &Path,
) -> io::Result<Vec<String>>

作用:列出这次 hook 迁移会涉及哪些事件名,方便提前展示或统计。

数据流:输入旧配置目录和目标 hooks 路径 → 根据目标路径的父目录模拟一次 hook 迁移 → 输出迁移结果里的事件名列表。

调用关系:hooks_migration_description 会调用它;它本身依赖 hook_migration 做真正的解析和筛选。

调用图:调用 1 个内部函数(hook_migration);被 1 处调用(hooks_migration_description);外部调用 1 个(parent)。

import_hooks109–132 ↗
fn import_hooks(source_external_agent_dir: &Path, target_hooks: &Path) -> io::Result<bool>

作用:真正把可迁移的 hooks 写到 Codex 的 hooks.json,并复制相关脚本。它会避免覆盖已有非空 hooks 文件。

数据流:输入旧外部智能体目录和目标 hooks 文件 → 生成迁移内容,确认目标父目录存在,如果目标文件不存在或为空才写入 → 输出是否真的写入了新的活动 hooks。

调用关系:这是 hooks 迁移的主入口。它调用 hook_migration 生成 JSON,调用 copy_hook_scripts 复制脚本,并用 is_missing_or_empty_text_file 判断能不能安全写入。

调用图:调用 4 个内部函数(copy_hook_scripts, hook_migration, invalid_data_error, is_missing_or_empty_text_file);外部调用 7 个(Object, parent, format!, create_dir_all, write, new, to_string_pretty)。

count_missing_subagents134–136 ↗
fn count_missing_subagents(source_agents: &Path, target_agents: &Path) -> io::Result<usize>

作用:统计有多少旧子智能体还没有迁到目标目录。

数据流:输入旧 agents 目录和新 agents 目录 → 调用 missing_subagent_names 得到缺失名称 → 输出数量。

调用关系:这是给 UI 或迁移摘要用的计数包装函数,真正查找工作由 missing_subagent_names 完成。

调用图:调用 1 个内部函数(missing_subagent_names)。

missing_subagent_names138–156 ↗
fn missing_subagent_names(
    source_agents: &Path,
    target_agents: &Path,
) -> io::Result<Vec<String>>

作用:找出旧目录里存在、但 Codex 目标目录里还没有的子智能体名字。

数据流:输入源 agents 目录和目标 agents 目录 → 扫描 Markdown 文件、解析文档头信息、算出目标 TOML 文件名 → 输出尚未存在的子智能体名称列表。

调用关系:count_missing_subagents 会用它做统计;它串起 agent_source_files、parse_document、agent_metadata 和 subagent_target_file。

调用图:调用 4 个内部函数(agent_metadata, agent_source_files, parse_document, subagent_target_file);被 1 处调用(count_missing_subagents);外部调用 1 个(new)。

import_subagents158–181 ↗
fn import_subagents(source_agents: &Path, target_agents: &Path) -> io::Result<Vec<String>>

作用:把旧的子智能体 Markdown 文件转换成 Codex 的 TOML 子智能体文件。

数据流:输入源 agents 目录和目标 agents 目录 → 创建目标目录,逐个读取可用 Markdown,跳过已存在目标文件和缺少必要信息的文件 → 写出 TOML,并返回导入成功的名字。

调用关系:这是子智能体迁移入口。它负责流程安排,具体解析交给 parse_document,信息提取交给 agent_metadata,渲染交给 render_agent_toml。

调用图:调用 5 个内部函数(agent_metadata, agent_source_files, parse_document, render_agent_toml, subagent_target_file);外部调用 4 个(is_dir, new, create_dir_all, write)。

count_missing_commands183–185 ↗
fn count_missing_commands(source_commands: &Path, target_skills: &Path) -> io::Result<usize>

作用:统计有多少旧命令模板还没有变成 Codex skill。skill 可以理解成 Codex 可调用的一份技能说明和模板。

数据流:输入旧 commands 目录和目标 skills 目录 → 找出缺失命令名 → 输出数量。

调用关系:这是 missing_command_names 的计数包装,通常用于迁移前提示用户。

调用图:调用 1 个内部函数(missing_command_names)。

missing_command_names187–196 ↗
fn missing_command_names(
    source_commands: &Path,
    target_skills: &Path,
) -> io::Result<Vec<String>>

作用:列出旧命令里可迁移、但目标 skills 目录还没有的命令名字。

数据流:输入源 commands 目录和目标 skills 目录 → 找到唯一且支持的命令源 → 过滤掉目标目录已经存在的 → 输出缺失名称。

调用关系:count_missing_commands 调用它;它依赖 unique_supported_command_sources 先完成“能不能迁”的筛选。

调用图:调用 1 个内部函数(unique_supported_command_sources);被 1 处调用(count_missing_commands)。

import_commands198–224 ↗
fn import_commands(source_commands: &Path, target_skills: &Path) -> io::Result<Vec<String>>

作用:把旧命令 Markdown 转成 Codex 的 skill 目录和 SKILL.md 文件。

数据流:输入源 commands 目录和目标 skills 目录 → 扫描支持的命令,创建对应 skill 目录,写入 SKILL.md → 输出成功导入的 skill 名称。

调用关系:这是命令迁移入口。它从 unique_supported_command_sources 拿候选项,再用 command_skill_description、command_source_name 和 render_command_skill 生成最终文件。

调用图:调用 5 个内部函数(command_skill_description, command_source_name, parse_document, render_command_skill, unique_supported_command_sources);外部调用 5 个(is_dir, join, new, create_dir_all, write)。

read_external_mcp_servers226–269 ↗
fn read_external_mcp_servers(
    source_root: &Path,
    external_agent_home: Option<&Path>,
) -> io::Result<BTreeMap<String, JsonValue>>

作用:从多个可能的位置读取旧 MCP 服务器配置,并合并成一个按名字排列的表。

数据流:输入项目根目录和可选外部智能体 home → 读取 .mcp.json、项目级配置、home 旁边的项目配置 → 输出服务器名到原始 JSON 配置的映射。

调用关系:build_mcp_config_from_external 先调用它收集原料。它再调用 append_mcp_servers_from_value 合并配置,并用 project_path_matches_source_root 只取当前项目相关条目。

调用图:调用 4 个内部函数(append_external_agent_project_mcp_servers, append_mcp_servers_from_value, external_agent_project_config_file, project_path_matches_source_root);被 1 处调用(build_mcp_config_from_external);外部调用 4 个(new, join, read_to_string, from_str)。

append_external_agent_project_mcp_servers271–295 ↗
fn append_external_agent_project_mcp_servers(
    source_file: &Path,
    source_root: &Path,
    servers: &mut BTreeMap<String, JsonValue>,
) -> io::Result<()>

作用:从外部智能体的全局项目配置里,补充当前项目对应的 MCP 服务器。

数据流:输入配置文件路径、项目根目录、已有服务器表 → 如果文件存在就读 JSON,找到匹配当前项目的 projects 条目 → 把里面的服务器加入表,但不覆盖已有服务器。

调用关系:read_external_mcp_servers 在发现 home 外还有项目配置时调用它;它继续使用 append_mcp_servers_from_value 做合并。

调用图:调用 2 个内部函数(append_mcp_servers_from_value, project_path_matches_source_root);被 1 处调用(read_external_mcp_servers);外部调用 3 个(is_file, read_to_string, from_str)。

append_mcp_servers_from_value303–323 ↗
fn append_mcp_servers_from_value(
    value: &JsonValue,
    servers: &mut BTreeMap<String, JsonValue>,
    merge: McpServerMerge,
)

作用:从一段 JSON 里拿出 mcpServers 字段,并按指定规则放进服务器表。

数据流:输入 JSON、可修改的服务器表、合并策略 → 找到 mcpServers 对象 → 覆盖已有值,或只填空缺值。

调用关系:read_external_mcp_servers 和 append_external_agent_project_mcp_servers 都靠它统一执行合并规则。

调用图:被 2 处调用(append_external_agent_project_mcp_servers, read_external_mcp_servers);外部调用 1 个(get)。

project_path_matches_source_root325–336 ↗
fn project_path_matches_source_root(project_path: &str, source_root: &Path) -> bool

作用:判断配置里写的项目路径是不是当前正在迁移的项目目录。

数据流:输入一个路径字符串和当前项目路径 → 先直接比,再尝试转成真实绝对路径比较 → 输出是否匹配。

调用关系:读取项目级 MCP 配置时用它过滤 projects,避免把别的项目的服务器搬进来。

调用图:被 2 处调用(append_external_agent_project_mcp_servers, read_external_mcp_servers);外部调用 2 个(canonicalize, new)。

mcp_server_toml_table338–396 ↗
fn mcp_server_toml_table(
    server_name: &str,
    server_config: Option<&serde_json::Map<String, JsonValue>>,
    enabled_servers: &[String],
    disabled_servers: &BTreeSet<String>,
) -> Option<to

作用:把单个旧 MCP 服务器配置转换成 Codex 能保存的 TOML 表。不能安全转换的服务器会被跳过。

数据流:输入服务器名、旧 JSON 配置、启用名单、禁用名单 → 判断是否禁用,检查命令型或 HTTP 型配置,处理参数、环境变量和请求头 → 输出 TOML 表,或返回空表示跳过。

调用关系:build_mcp_config_from_external 对每个服务器调用它。它会把环境变量交给 append_env_config,把 HTTP 头交给 append_header_config。

调用图:调用 5 个内部函数(append_env_config, append_header_config, contains_env_placeholder, json_string_vec, mcp_server_is_disabled);被 1 处调用(build_mcp_config_from_external);外部调用 4 个(Array, String, matches!, new)。

mcp_server_is_disabled398–414 ↗
fn mcp_server_is_disabled(
    server_name: &str,
    server_config: &serde_json::Map<String, JsonValue>,
    enabled_servers: &[String],
    disabled_servers: &BTreeSet<String>,
) -> bool

作用:判断一个 MCP 服务器是不是被旧配置或用户设置禁用了。

数据流:输入服务器名、服务器配置、启用名单、禁用集合 → 检查 enabled、disabled 字段和名单 → 输出 true 或 false。

调用关系:mcp_server_toml_table 在转换前先调用它,决定这个服务器是否直接丢弃。

调用图:被 1 处调用(mcp_server_toml_table);外部调用 2 个(contains, get)。

append_header_config416–456 ↗
fn append_header_config(
    table: &mut toml::map::Map<String, TomlValue>,
    headers: &serde_json::Map<String, JsonValue>,
) -> Option<()>

作用:把 HTTP 请求头从旧 JSON 转成 Codex 的 TOML 写法,并识别常见的 token 环境变量。

数据流:输入正在构造的 TOML 表和 headers JSON → 把普通请求头放进 http_headers,把纯环境变量占位符放进 env_http_headers,把 Authorization Bearer 特例变成 bearer_token_env_var → 成功返回 Some,遇到复杂占位符返回 None。

调用关系:mcp_server_toml_table 转换 HTTP 型 MCP 服务器时调用它。它依赖 parse_env_placeholder 和 contains_env_placeholder 判断占位符是否安全。

调用图:调用 3 个内部函数(contains_env_placeholder, json_string, parse_env_placeholder);被 1 处调用(mcp_server_toml_table);外部调用 4 个(insert, String, Table, new)。

append_env_config458–483 ↗
fn append_env_config(
    table: &mut toml::map::Map<String, TomlValue>,
    env: &serde_json::Map<String, JsonValue>,
) -> Option<()>

作用:把旧 MCP 命令里的环境变量配置转成 Codex 能理解的格式。

数据流:输入 TOML 表和 env JSON → 如果值是同名环境变量占位符,就记录到 env_vars;如果是固定值,就放进 env;如果是复杂占位符,就失败 → 修改 TOML 表并返回是否成功。

调用关系:mcp_server_toml_table 转换命令型 MCP 服务器时调用它;它和 append_header_config 使用同一套占位符判断方法。

调用图:调用 3 个内部函数(contains_env_placeholder, json_string, parse_env_placeholder);被 1 处调用(mcp_server_toml_table);外部调用 6 个(insert, Array, String, Table, new, new)。

parse_env_placeholder485–499 ↗
fn parse_env_placeholder(value: &str) -> Option<String>

作用:识别像 ${TOKEN} 或 ${TOKEN:-fallback} 这样的环境变量占位符,并取出变量名。

数据流:输入字符串 → 检查是否完整包在 ${} 中,去掉默认值部分,再验证变量名是否合法 → 输出变量名或空。

调用关系:append_env_config 和 append_header_config 用它区分“可以安全迁移的环境变量引用”和复杂表达式。

调用图:被 2 处调用(append_env_config, append_header_config)。

contains_env_placeholder501–503 ↗
fn contains_env_placeholder(value: &str) -> bool

作用:快速判断一段文字里是否出现了环境变量占位符开头 ${。

数据流:输入字符串 → 搜索 ${ → 输出是否包含。

调用关系:MCP 转换和头部/环境变量转换都会用它;如果含有复杂占位符,很多配置会被保守跳过。

调用图:被 3 处调用(append_env_config, append_header_config, mcp_server_toml_table)。

hook_migration505–535 ↗
fn hook_migration(
    source_external_agent_dir: &Path,
    target_config_dir: Option<&Path>,
) -> io::Result<serde_json::Map<String, JsonValue>>

作用:读取旧 settings.json 和 settings.local.json,生成可迁移的 hooks JSON 内容。

数据流:输入旧外部智能体目录和可选目标配置目录 → 读取设置文件,尊重 disableAllHooks,筛选可转换 hook 分组 → 输出按事件名组织的 JSON 对象。

调用关系:import_hooks 用它真正写文件;hook_migration_event_names 用它只看有哪些事件。筛选细节交给 append_convertible_hook_groups。

调用图:调用 1 个内部函数(append_convertible_hook_groups);被 2 处调用(hook_migration_event_names, import_hooks);外部调用 5 个(join, new, read_to_string, new, from_str)。

append_convertible_hook_groups537–660 ↗
fn append_convertible_hook_groups(
    settings: &JsonValue,
    hooks_payload: &mut serde_json::Map<String, JsonValue>,
    target_config_dir: Option<&Path>,
)

作用:从旧 hooks 配置里挑出 Codex 支持的、简单安全的 command hook。

数据流:输入一份 settings JSON、待追加的 hooks JSON、可选目标目录 → 遍历支持的事件,跳过带条件、异步、HTTP、prompt 或陌生字段的 hook,把命令和超时等字段转成新格式 → 追加到输出 JSON。

调用关系:hook_migration 调用它处理每个设置文件;测试也直接调用它验证不兼容 hook 会被忽略。它会调用 rewrite_hook_command 改脚本路径,调用 rewrite_external_agent_terms 改文案。

调用图:调用 2 个内部函数(rewrite_external_agent_terms, rewrite_hook_command);被 3 处调用(hook_migration, hook_migration_drops_negative_timeouts, hook_migration_ignores_unsupported_handlers);外部调用 9 个(Array, Number, Object, String, get, entry, new, new, from)。

rewrite_hook_command662–677 ↗
fn rewrite_hook_command(command: &str, target_config_dir: Option<&Path>) -> String

作用:把 hook 命令里指向旧 .claude/hooks 的脚本路径,改成迁移后的 Codex hooks 目录。

数据流:输入命令字符串和目标配置目录 → 如果没有目标目录或看起来是 Windows 命令就原样返回;否则分别处理单引号、双引号和未加引号的路径 → 输出改写后的命令。

调用关系:append_convertible_hook_groups 在迁移每个 command hook 时调用它。它把具体查找替换交给 replace_quoted_hook_paths 和 replace_unquoted_hook_paths。

调用图:调用 3 个内部函数(looks_like_windows_hook_command, replace_quoted_hook_paths, replace_unquoted_hook_paths);被 1 处调用(append_convertible_hook_groups);外部调用 1 个(format!)。

replace_quoted_hook_paths679–711 ↗
fn replace_quoted_hook_paths(
    command: &str,
    quote: char,
    source_hooks_path: &str,
    target_hooks_dir: &Path,
) -> String

作用:改写命令中被单引号或双引号包住的旧 hook 路径。

数据流:输入命令、引号类型、旧 hooks 路径前缀、目标 hooks 目录 → 找出成对引号内的内容,如果里面是可安全替换的旧路径就替换成目标路径 → 输出新命令。

调用关系:rewrite_hook_command 会分别用单引号和双引号调用它;真正判断路径能不能替换交给 target_hook_path_replacement。

调用图:调用 1 个内部函数(target_hook_path_replacement);被 1 处调用(rewrite_hook_command)。

replace_unquoted_hook_paths713–744 ↗
fn replace_unquoted_hook_paths(
    command: &str,
    source_hooks_path: &str,
    target_hooks_dir: &Path,
) -> String

作用:改写命令中没有引号包住的旧 hook 路径。

数据流:输入命令、旧 hooks 路径前缀、目标 hooks 目录 → 在不在引号内的位置查找旧路径,扩展出完整 shell 路径,避开变量赋值场景 → 输出替换后的命令。

调用关系:rewrite_hook_command 在处理完带引号路径后调用它。它依赖 find_unquoted_source_hook_path、shell_path_start、shell_path_end 和 target_hook_path_replacement。

调用图:调用 5 个内部函数(find_unquoted_source_hook_path, is_assignment_value_start, shell_path_end, shell_path_start, target_hook_path_replacement);被 1 处调用(rewrite_hook_command)。

find_unquoted_source_hook_path746–781 ↗
fn find_unquoted_source_hook_path(
    command: &str,
    source_hooks_path: &str,
    start: usize,
) -> Option<usize>

作用:在 shell 命令里找到不在引号中的旧 hook 路径位置。

数据流:输入命令、要找的旧路径、搜索起点 → 一边扫描一边记录当前是否在单引号、双引号或转义状态中 → 输出第一个未被引号包住的匹配位置。

调用关系:replace_unquoted_hook_paths 用它确保不会误改嵌在引号或转义里的内容。

调用图:被 1 处调用(replace_unquoted_hook_paths)。

is_pure_shell_path_content783–787 ↗
fn is_pure_shell_path_content(content: &str, source_hooks_start: usize) -> bool

作用:判断某段内容是不是一个干净的 shell 路径,而不是夹在别的复杂表达式里。

数据流:输入路径内容和旧 hooks 前缀出现的位置 → 检查前缀部分是否为空、./ 或目录结尾,并确认没有 shell 分隔符 → 输出是否可安全替换。

调用关系:target_hook_path_replacement 用它作为安全检查之一,防止替换错命令片段。

调用图:被 1 处调用(target_hook_path_replacement)。

shell_path_start789–795 ↗
fn shell_path_start(command: &str, end: usize) -> usize

作用:从某个位置往前找,确定 shell 命令里一个路径从哪里开始。

数据流:输入命令和路径内部结束位置 → 往前找最近的空白或 shell 分隔符 → 输出路径起点。

调用关系:replace_unquoted_hook_paths 用它截出完整未加引号路径。

调用图:被 1 处调用(replace_unquoted_hook_paths)。

shell_path_end797–813 ↗
fn shell_path_end(command: &str, start: usize) -> usize

作用:从某个位置往后找,确定 shell 命令里一个路径到哪里结束。

数据流:输入命令和起点 → 扫描字符,跳过被反斜杠转义的字符,遇到空白或 shell 分隔符就停 → 输出路径终点。

调用关系:replace_unquoted_hook_paths 用它截出完整路径;它用 is_shell_path_boundary 判断边界。

调用图:调用 1 个内部函数(is_shell_path_boundary);被 1 处调用(replace_unquoted_hook_paths)。

is_shell_path_boundary815–817 ↗
fn is_shell_path_boundary(ch: char) -> bool

作用:判断一个字符是不是 shell 命令中分隔路径的边界。

数据流:输入字符 → 检查是否空白,或是否是 =、;、|、&、括号、重定向符等 → 输出布尔值。

调用关系:shell_path_end 调用它决定路径何时结束。

调用图:被 1 处调用(shell_path_end);外部调用 1 个(matches!)。

is_assignment_value_start819–824 ↗
fn is_assignment_value_start(command: &str, path_start: usize) -> bool

作用:判断路径是不是紧跟在等号后面,也就是变量赋值的值。

数据流:输入命令和路径起点 → 看路径前一个字符是不是 = → 输出是否属于赋值值开头。

调用关系:replace_unquoted_hook_paths 用它避开 HOOK=... 这类赋值,防止改坏后续命令逻辑。

调用图:被 1 处调用(replace_unquoted_hook_paths)。

target_hook_path_replacement826–839 ↗
fn target_hook_path_replacement(
    target_hooks_dir: &Path,
    path: &str,
    source_hooks_start: usize,
    suffix: &str,
) -> Option<String>

作用:为一个旧 hook 路径生成安全的新目标路径文本。

数据流:输入目标 hooks 目录、原路径、旧前缀位置和后缀 → 检查路径内容是否纯净、后缀是否静态 → 拼成目标路径并用 shell 单引号包好,或返回空表示不能替换。

调用关系:带引号和不带引号的替换函数都会调用它。它依赖 is_pure_shell_path_content、is_static_hook_path_suffix 和 shell_single_quote。

调用图:调用 3 个内部函数(is_pure_shell_path_content, is_static_hook_path_suffix, shell_single_quote);被 2 处调用(replace_quoted_hook_paths, replace_unquoted_hook_paths);外部调用 1 个(join)。

is_static_hook_path_suffix841–846 ↗
fn is_static_hook_path_suffix(suffix: &str) -> bool

作用:判断 hook 脚本路径后缀是不是固定文件名,而不是带变量或通配符的动态表达式。

数据流:输入路径后缀 → 确认非空,且不含反斜杠、美元符、反引号、星号、问号、括号花括号等特殊字符 → 输出是否安全。

调用关系:target_hook_path_replacement 用它决定能不能迁移路径。

调用图:被 1 处调用(target_hook_path_replacement)。

looks_like_windows_hook_command848–857 ↗
fn looks_like_windows_hook_command(command: &str) -> bool

作用:判断 hook 命令是否像 Windows 写法;这类命令暂时不自动改路径。

数据流:输入命令 → 检查是否有反斜杠版 hooks 路径、%VAR% 或 PowerShell 的 $env:VAR → 输出是否像 Windows 命令。

调用关系:rewrite_hook_command 先调用它;如果像 Windows 命令,就直接保持原样,避免误改。

调用图:调用 1 个内部函数(external_agent_project_dir_env_var);被 1 处调用(rewrite_hook_command);外部调用 1 个(format!)。

shell_single_quote859–861 ↗
fn shell_single_quote(value: &str) -> String

作用:把一个字符串安全地包装成 shell 单引号形式。

数据流:输入路径或文本 → 把内部单引号转义,再在外面加单引号 → 输出可放进 shell 命令的字符串。

调用关系:target_hook_path_replacement 用它包装迁移后的 hook 脚本路径。

调用图:被 1 处调用(target_hook_path_replacement);外部调用 1 个(format!)。

copy_hook_scripts863–870 ↗
fn copy_hook_scripts(source_external_agent_dir: &Path, target_config_dir: &Path) -> io::Result<()>

作用:把旧 hooks 目录里的脚本复制到 Codex 配置目录下的 hooks 目录。

数据流:输入旧外部智能体目录和目标配置目录 → 如果旧 hooks 目录不存在就不做事;否则递归复制到目标 hooks 目录 → 已有目标文件不会被覆盖。

调用关系:import_hooks 在写 hooks.json 前后会调用它;测试也用它确认不会覆盖现有脚本。

调用图:调用 1 个内部函数(copy_dir_recursive_skip_existing);被 2 处调用(import_hooks, hook_script_copy_keeps_existing_target_scripts);外部调用 1 个(join)。

copy_dir_recursive_skip_existing872–886 ↗
fn copy_dir_recursive_skip_existing(source: &Path, target: &Path) -> io::Result<()>

作用:递归复制目录,但保留目标里已经存在的文件。

数据流:输入源目录和目标目录 → 创建目标目录,遍历子项;目录就递归,普通文件只有目标不存在时才复制 → 修改磁盘文件。

调用关系:copy_hook_scripts 把实际复制工作交给它。

调用图:被 1 处调用(copy_hook_scripts);外部调用 4 个(join, copy, create_dir_all, read_dir)。

agent_source_files888–909 ↗
fn agent_source_files(source_agents: &Path) -> io::Result<Vec<PathBuf>>

作用:找出旧子智能体目录里可作为子智能体来源的 Markdown 文件。

数据流:输入 agents 目录 → 如果不是目录返回空;否则读取直接子文件,只保留 .md,跳过 README → 输出排序后的文件路径列表。

调用关系:missing_subagent_names 和 import_subagents 都先用它拿候选文件。

调用图:被 2 处调用(import_subagents, missing_subagent_names);外部调用 3 个(is_dir, new, read_dir)。

subagent_target_file911–913 ↗
fn subagent_target_file(source_file: &Path, target_agents: &Path) -> Option<PathBuf>

作用:根据旧子智能体 Markdown 文件名,算出 Codex 目标 TOML 文件路径。

数据流:输入源文件路径和目标 agents 目录 → 取源文件的文件名主干,加上 .toml → 输出目标路径;如果文件名不能转成文字则返回空。

调用关系:missing_subagent_names 用它判断是否已存在;import_subagents 用它决定写到哪里。

调用图:被 2 处调用(import_subagents, missing_subagent_names);外部调用 2 个(join, format!)。

command_source_files915–920 ↗
fn command_source_files(source_commands: &Path) -> io::Result<Vec<PathBuf>>

作用:递归找出旧命令目录里的所有 Markdown 文件。

数据流:输入 commands 目录 → 调用 collect_markdown_files 递归收集 .md → 排序后输出路径列表。

调用关系:unique_supported_command_sources 用它获得命令候选文件。

调用图:调用 1 个内部函数(collect_markdown_files);被 1 处调用(unique_supported_command_sources);外部调用 1 个(new)。

unique_supported_command_sources922–942 ↗
fn unique_supported_command_sources(source_commands: &Path) -> io::Result<Vec<(PathBuf, String)>>

作用:筛出能迁移、且迁移后名字不冲突的命令文件。

数据流:输入 commands 根目录 → 收集 Markdown,解析每个文档,计算支持的 skill 名;按名字分组,只保留唯一来源的名字 → 输出源文件和 skill 名的配对。

调用关系:missing_command_names 和 import_commands 都依赖它。它把单个文件是否支持的判断交给 command_skill_name_if_supported。

调用图:调用 3 个内部函数(command_skill_name_if_supported, command_source_files, parse_document);被 2 处调用(import_commands, missing_command_names);外部调用 1 个(new)。

collect_markdown_files944–961 ↗
fn collect_markdown_files(dir: &Path, files: &mut Vec<PathBuf>) -> io::Result<()>

作用:递归遍历目录,收集所有 .md 文件。

数据流:输入目录和可追加的文件列表 → 如果目录存在就读取子项,子目录继续递归,Markdown 文件加入列表 → 修改传入的列表。

调用关系:command_source_files 调用它完成实际目录扫描。

调用图:被 1 处调用(command_source_files);外部调用 2 个(is_dir, read_dir)。

parse_document963–966 ↗
fn parse_document(source_file: &Path) -> io::Result<ParsedDocument>

作用:读取一个 Markdown 文件,并解析它的正文和开头的 YAML frontmatter。frontmatter 是 Markdown 顶部用 --- 包起来的元信息。

数据流:输入文件路径 → 读成字符串 → 交给 parse_document_content 解析 → 输出 ParsedDocument。

调用关系:子智能体和命令迁移都会用它读取源文件。

调用图:调用 1 个内部函数(parse_document_content);被 4 处调用(import_commands, import_subagents, missing_subagent_names, unique_supported_command_sources);外部调用 1 个(read_to_string)。

parse_document_content968–995 ↗
fn parse_document_content(content: &str) -> ParsedDocument

作用:把 Markdown 内容拆成“头部元信息”和“正文”。

数据流:输入完整文本 → 如果开头不是 --- 就当作没有头信息;如果有,就找结束分隔线并解析 YAML → 输出包含 frontmatter、body 和解析错误的 ParsedDocument。

调用关系:parse_document 调用它;很多测试直接调用它验证不同文档格式。

调用图:调用 2 个内部函数(frontmatter_end, parse_frontmatter);被 8 处调用(parse_document, command_skill_names_must_fit_codex_skill_loader_limit, commands_with_provider_runtime_expansion_are_skipped, commands_without_description_are_skipped, frontmatter_accepts_crlf_delimiters, subagent_accepts_yaml_block_lists_by_ignoring_unsupported_fields, subagent_preserves_default_model_when_source_model_is_present, subagent_requires_minimum_codex_agent_fields);外部调用 1 个(new)。

frontmatter_end997–1009 ↗
fn frontmatter_end(rest: &str) -> Option<(usize, usize)>

作用:找到 YAML frontmatter 的结束位置,兼容不同换行符。

数据流:输入去掉开头 --- 后的文本 → 搜索多种结束分隔符,比如 \n---\n 和 \r\n---\r\n → 输出头信息结束和正文开始的位置。

调用关系:parse_document_content 用它判断 frontmatter 是否完整。

调用图:被 1 处调用(parse_document_content)。

parse_frontmatter1011–1034 ↗
fn parse_frontmatter(
    raw_frontmatter: &str,
) -> (BTreeMap<String, FrontmatterValue>, Option<String>)

作用:把 frontmatter 的 YAML 文本解析成一个简单键值表。

数据流:输入 YAML 文本 → 用 YAML 解析器读取,要求顶层是映射表 → 把键转成字符串,把值转成简单标量或 Other → 输出键值表和可选错误信息。

调用关系:parse_document_content 找到 frontmatter 后调用它;单个值转换交给 frontmatter_value_from_yaml。

调用图:调用 1 个内部函数(frontmatter_value_from_yaml);被 1 处调用(parse_document_content);外部调用 2 个(new, from_str)。

frontmatter_value_from_yaml1036–1045 ↗
fn frontmatter_value_from_yaml(value: &YamlValue) -> FrontmatterValue

作用:把 YAML 值简化成迁移代码关心的形式。

数据流:输入 YAML 值 → 字符串、布尔值、数字转成文字标量;空值、列表、对象等复杂值标为 Other → 输出 FrontmatterValue。

调用关系:parse_frontmatter 用它处理每个 frontmatter 字段。

调用图:被 1 处调用(parse_frontmatter);外部调用 3 个(to_string, trim, Scalar)。

agent_metadata1047–1071 ↗
fn agent_metadata(document: &ParsedDocument) -> Option<AgentMetadata>

作用:从解析好的子智能体文档里提取 Codex 需要的基本信息。

数据流:输入 ParsedDocument → 如果 frontmatter 有错误或正文为空就跳过;要求 name 和 description 都存在且非空;可选读取 permissionMode 和 effort → 输出 AgentMetadata 或空。

调用关系:missing_subagent_names 用它拿名字,import_subagents 用它生成 TOML,测试也用它验证最低字段要求。

调用图:调用 1 个内部函数(frontmatter_string);被 3 处调用(import_subagents, missing_subagent_names, subagent_preserves_default_model_when_source_model_is_present)。

render_agent_toml1073–1106 ↗
fn render_agent_toml(body: &str, metadata: &AgentMetadata) -> io::Result<String>

作用:把子智能体正文和元信息渲染成 Codex 的 TOML 文件内容。

数据流:输入正文和 AgentMetadata → 写入 name、description、developer_instructions,映射 effort 和权限模式,替换 Claude 相关叫法 → 输出格式化后的 TOML 字符串。

调用关系:import_subagents 在真正写文件前调用它;它内部用 render_agent_body、map_agent_reasoning_effort 和 rewrite_external_agent_terms。

调用图:调用 3 个内部函数(map_agent_reasoning_effort, render_agent_body, rewrite_external_agent_terms);被 2 处调用(import_subagents, subagent_preserves_default_model_when_source_model_is_present);外部调用 5 个(String, Table, format!, new, to_string_pretty)。

render_agent_body1108–1115 ↗
fn render_agent_body(body: &str) -> String

作用:整理子智能体正文,作为 Codex 的开发者指令保存。

数据流:输入正文 → 去掉首尾空白,替换外部智能体名称;如果空则放入默认提示语 → 输出指令文本。

调用关系:render_agent_toml 调用它生成 developer_instructions 字段。

调用图:调用 1 个内部函数(rewrite_external_agent_terms);被 1 处调用(render_agent_toml)。

command_skill_name1117–1122 ↗
fn command_skill_name(source_commands: &Path, source_file: &Path) -> String

作用:根据旧命令文件路径生成 Codex skill 名称。

数据流:输入 commands 根目录和源文件路径 → 算出相对命令名,加上 source-command 前缀,再转成安全短横线格式 → 输出 skill 名。

调用关系:command_skill_name_if_supported 用它给候选命令命名。

调用图:调用 1 个内部函数(slugify_name);被 1 处调用(command_skill_name_if_supported);外部调用 1 个(format!)。

command_skill_name_if_supported1124–1145 ↗
fn command_skill_name_if_supported(
    source_commands: &Path,
    source_file: &Path,
    document: &ParsedDocument,
) -> Option<String>

作用:判断一个旧命令能不能迁移成 skill,并在可以时给出 skill 名。

数据流:输入 commands 根目录、源文件和解析文档 → 跳过 README,要求有描述,检查名字和描述长度,排除不支持的模板功能 → 输出 skill 名或空。

调用关系:unique_supported_command_sources 对每个命令文件调用它。它依赖 command_skill_description、command_skill_name 和 has_unsupported_command_template_features。

调用图:调用 4 个内部函数(command_skill_description, command_skill_name, command_source_name, has_unsupported_command_template_features);被 1 处调用(unique_supported_command_sources);外部调用 1 个(file_stem)。

command_skill_description1147–1154 ↗
fn command_skill_description(document: &ParsedDocument, _source_name: &str) -> Option<String>

作用:从命令文档 frontmatter 里取出 description,作为 skill 描述。

数据流:输入 ParsedDocument 和源命令名 → 找 description 字段,要求是非空普通文本 → 输出描述或空。

调用关系:command_skill_name_if_supported 用它判断是否能迁移;import_commands 用它写 SKILL.md。

调用图:被 2 处调用(command_skill_name_if_supported, import_commands)。

command_source_name1156–1165 ↗
fn command_source_name(source_commands: &Path, source_file: &Path) -> String

作用:把命令文件路径转成旧命令的可读名字。

数据流:输入 commands 根目录和源文件路径 → 去掉根目录前缀和扩展名,把多级目录用短横线连起来 → 输出源命令名。

调用关系:command_skill_name_if_supported 和 import_commands 都用它,让嵌套命令也有稳定名字。

调用图:被 2 处调用(command_skill_name_if_supported, import_commands);外部调用 1 个(strip_prefix)。

render_command_skill1167–1179 ↗
fn render_command_skill(body: &str, name: &str, description: &str, source_name: &str) -> String

作用:把旧命令模板渲染成 Codex 的 SKILL.md 文件内容。

数据流:输入正文、skill 名、描述和源命令名 → 替换外部智能体叫法,补默认正文,写入 YAML 头和说明段落 → 输出 Markdown 文本。

调用关系:import_commands 在创建 skill 目录后调用它写 SKILL.md。

调用图:调用 1 个内部函数(rewrite_external_agent_terms);被 1 处调用(import_commands);外部调用 1 个(format!)。

has_unsupported_command_template_features1181–1190 ↗
fn has_unsupported_command_template_features(template: &str) -> bool

作用:检查命令模板里是否用了 Codex 不能安全迁移的动态功能。

数据流:输入模板正文 → 查找 $ARGUMENTS、$1 这类参数、双花括号模板、命令执行标记、@文件引用等 → 输出是否包含不支持功能。

调用关系:command_skill_name_if_supported 用它决定是否跳过某个命令。

调用图:调用 1 个内部函数(contains_numbered_argument_placeholder);被 1 处调用(command_skill_name_if_supported)。

contains_numbered_argument_placeholder1192–1197 ↗
fn contains_numbered_argument_placeholder(template: &str) -> bool

作用:检测模板里是否有 $1、$2 这类编号参数占位符。

数据流:输入模板字符串 → 按字节查看相邻两个字符是否是美元符加数字 → 输出是否存在。

调用关系:has_unsupported_command_template_features 调用它做其中一项检查。

调用图:被 1 处调用(has_unsupported_command_template_features)。

frontmatter_string1199–1207 ↗
fn frontmatter_string(
    frontmatter: &BTreeMap<String, FrontmatterValue>,
    key: &str,
) -> Option<String>

作用:从 frontmatter 表里取一个普通文本字段。

数据流:输入 frontmatter 表和字段名 → 找到字段并确认它是 Scalar → 输出字符串副本或空。

调用关系:agent_metadata 用它读取 permissionMode 和 effort 这类可选字段。

调用图:被 1 处调用(agent_metadata)。

map_agent_reasoning_effort1209–1219 ↗
fn map_agent_reasoning_effort(effort: &str) -> Option<String>

作用:把旧子智能体的推理强度字段映射成 Codex 支持的值。

数据流:输入 effort 文本 → 把 max 改成 xhigh,其它值原样检查是否在允许列表中 → 输出映射后的值或空。

调用关系:render_agent_toml 用它决定是否写 model_reasoning_effort。

调用图:被 1 处调用(render_agent_toml);外部调用 1 个(matches!)。

map_agent_permission_mode1221–1227 ↗
fn map_agent_permission_mode(permission_mode: &str) -> Option<&'static str>

作用:把旧权限模式翻译成 Codex 的沙箱模式。沙箱模式可以理解成“这个智能体能不能读写项目文件”的限制。

数据流:输入旧 permissionMode → acceptEdits 变成 workspace-write,readOnly 变成 read-only,其它值不迁移 → 输出新模式或空。

调用关系:render_agent_toml 在生成子智能体 TOML 时用它写 sandbox_mode。

json_string_vec1229–1234 ↗
fn json_string_vec(value: &JsonValue) -> Vec<String>

作用:把 JSON 值尽量转成字符串列表。

数据流:输入 JSON → 如果是数组,就把数组里的简单值转成字符串;如果是单个简单值,就变成一个元素的列表 → 输出字符串列表。

调用关系:build_mcp_config_from_external 用它读取启用/禁用名单,mcp_server_toml_table 用它读取 args。

调用图:调用 1 个内部函数(json_string);被 1 处调用(mcp_server_toml_table)。

json_string1236–1244 ↗
fn json_string(value: &JsonValue) -> Option<String>

作用:把简单 JSON 值转成字符串,复杂对象和数组不转。

数据流:输入 JSON → null 返回空,字符串原样,布尔值和数字转文字,数组和对象返回空 → 输出可选字符串。

调用关系:json_string_vec、append_env_config 和 append_header_config 都用它处理宽松格式。

调用图:被 3 处调用(append_env_config, append_header_config, json_string_vec);外部调用 2 个(clone, to_string)。

json_u641246–1251 ↗
fn json_u64(value: &JsonValue) -> Option<u64>

作用:把 JSON 值解析成非负整数,用于 timeout 这类字段。

数据流:输入 JSON → 布尔和 null 直接拒绝;数字按无符号整数取,字符串尝试解析 → 输出 u64 或空。

调用关系:append_convertible_hook_groups 在迁移 hook 超时时用它,负数或不合法值会被丢弃。

调用图:外部调用 3 个(as_u64, is_boolean, is_null)。

yaml_string1253–1255 ↗
fn yaml_string(value: &str) -> String

作用:把字符串包装成适合写进 YAML 的双引号字符串。

数据流:输入文本 → 转义反斜杠和双引号,再加双引号 → 输出 YAML 字符串字面量。

调用关系:render_command_skill 用它写 SKILL.md 顶部的 name 和 description。

调用图:外部调用 1 个(format!)。

slugify_name1257–1276 ↗
fn slugify_name(value: &str) -> String

作用:把任意名字变成安全的短横线小写标识。

数据流:输入字符串 → 字母数字保留并转小写,其它字符合并成短横线,去掉首尾短横线;如果最后为空则用 migrated → 输出 slug。

调用关系:command_skill_name 用它生成 Codex skill 名。

调用图:被 1 处调用(command_skill_name);外部调用 1 个(new)。

FrontmatterValue::as_scalar1279–1284 ↗
fn as_scalar(&self) -> Option<&str>

作用:如果 frontmatter 值是普通文本,就把里面的字符串拿出来。

数据流:输入 FrontmatterValue → Scalar 返回内部字符串引用,Other 返回空 → 不修改数据。

调用关系:agent_metadata、command_skill_description 和 frontmatter_string 等读取元信息的地方会用它。

is_missing_or_empty_text_file1287–1296 ↗
fn is_missing_or_empty_text_file(path: &Path) -> io::Result<bool>

作用:判断目标文本文件是否不存在,或者存在但内容为空。

数据流:输入路径 → 不存在返回 true;不是普通文件返回 false;普通文件则读取并检查去掉空白后是否为空 → 输出布尔值。

调用关系:import_hooks 用它决定是否可以写 hooks.json,避免覆盖用户已有配置。

调用图:被 1 处调用(import_hooks);外部调用 3 个(exists, is_file, read_to_string)。

rewrite_external_agent_terms1298–1308 ↗
fn rewrite_external_agent_terms(content: &str) -> String

作用:把迁移内容里的旧品牌/文件名说法改成 Codex 的说法。

数据流:输入文本 → 先把 claude.md 这类文档名改成 AGENTS.md,再把 Claude 的各种写法按单词边界改成 Codex → 输出改写后的文本。

调用关系:迁移 hooks 状态消息、子智能体说明和 command skill 时都会调用它;底层替换交给 replace_case_insensitive_with_boundaries。

调用图:调用 3 个内部函数(external_agent_doc_file_name, external_agent_term_variants, replace_case_insensitive_with_boundaries);被 4 处调用(append_convertible_hook_groups, render_agent_body, render_agent_toml, render_command_skill)。

replace_case_insensitive_with_boundaries1310–1347 ↗
fn replace_case_insensitive_with_boundaries(
    input: &str,
    needle: &str,
    replacement: &str,
) -> String

作用:做不区分大小写、但尊重单词边界的文本替换。

数据流:输入原文、要找的词、替换词 → 用小写副本查找位置,确认前后不是字母数字或下划线 → 输出替换后的文本;没有命中就原样返回。

调用关系:rewrite_external_agent_terms 用它避免把单词中间的 claude 误替换掉。

调用图:调用 1 个内部函数(is_word_byte);被 1 处调用(rewrite_external_agent_terms);外部调用 1 个(with_capacity)。

is_word_byte1349–1351 ↗
fn is_word_byte(byte: u8) -> bool

作用:判断一个字节是不是英文单词字符。

数据流:输入字节 → 检查是否 ASCII 字母、数字或下划线 → 输出布尔值。

调用关系:replace_case_insensitive_with_boundaries 用它判断替换边界。

调用图:被 1 处调用(replace_case_insensitive_with_boundaries)。

invalid_data_error1353–1355 ↗
fn invalid_data_error(message: impl Into<String>) -> io::Error

作用:创建一个“数据格式不合法”的 IO 错误。

数据流:输入错误信息 → 包装成 io::ErrorKind::InvalidData → 输出 IO 错误对象。

调用关系:import_hooks 和多处解析/序列化失败场景用它把错误类型统一成数据错误。

调用图:被 1 处调用(import_hooks);外部调用 2 个(into, new)。

external_agent_config_dir1357–1359 ↗
fn external_agent_config_dir() -> String

作用:生成旧外部智能体配置目录名。

数据流:读取固定来源名 claude → 拼成 .claude → 输出字符串。

调用关系:路径改写和测试辅助会用它,确保旧目录名来自同一个常量。

调用图:被 4 处调用(hook_script_copy_keeps_existing_target_scripts, mcp_migration_preserves_repo_servers_over_home_project_entries, mcp_migration_reads_matching_project_entries_from_home_external_project_config, source_path);外部调用 1 个(format!)。

external_agent_project_config_file1361–1363 ↗
fn external_agent_project_config_file() -> String

作用:生成旧外部智能体的项目级配置文件名。

数据流:读取固定来源名 claude → 拼成 .claude.json → 输出字符串。

调用关系:read_external_mcp_servers 用它找项目配置文件,相关测试也用它造测试文件。

调用图:被 4 处调用(read_external_mcp_servers, mcp_migration_preserves_repo_servers_over_home_project_entries, mcp_migration_reads_matching_project_entries_from_home_external_project_config, mcp_migration_reads_matching_project_entries_from_repo_external_project_config);外部调用 1 个(format!)。

external_agent_project_dir_env_var1365–1370 ↗
fn external_agent_project_dir_env_var() -> String

作用:生成旧外部智能体用来表示项目目录的环境变量名。

数据流:读取来源名 claude,转大写 → 拼成 CLAUDE_PROJECT_DIR → 输出字符串。

调用关系:looks_like_windows_hook_command 和 hook 路径改写测试用它识别旧路径写法。

调用图:被 2 处调用(looks_like_windows_hook_command, hook_command_paths_rewrite_to_target_hook_dir);外部调用 1 个(format!)。

external_agent_doc_file_name1372–1374 ↗
fn external_agent_doc_file_name() -> String

作用:生成旧外部智能体的说明文件名。

数据流:读取来源名 claude → 拼成 claude.md → 输出字符串。

调用关系:rewrite_external_agent_terms 用它把旧说明文件名替换成 AGENTS.md。

调用图:被 1 处调用(rewrite_external_agent_terms);外部调用 1 个(format!)。

external_agent_term_variants1376–1384 ↗
fn external_agent_term_variants() -> [String; 5]

作用:列出旧外部智能体名称的几种常见写法,方便统一替换。

数据流:读取来源名 claude → 生成 claude code、claude-code、claude_code、claudecode、claude 五种字符串 → 输出数组。

调用关系:rewrite_external_agent_terms 会逐个使用这些变体,把文案改成 Codex。

调用图:被 1 处调用(rewrite_external_agent_terms);外部调用 1 个(format!)。

tests::source_path1391–1395 ↗
fn source_path(relative_path: &str) -> PathBuf

作用:测试用:构造一个位于 /repo/.claude 下的源文件路径。

数据流:输入相对路径 → 拼到 /repo、旧配置目录名和相对路径后面 → 输出 PathBuf。

调用关系:多个命令和子智能体测试用它快速生成一致的旧路径。

调用图:调用 1 个内部函数(external_agent_config_dir);外部调用 1 个(new)。

tests::source_hook_command1397–1402 ↗
fn source_hook_command(script_name: &str) -> String

作用:测试用:生成一条调用旧 hooks 脚本的命令。

数据流:输入脚本名 → 拼成 python3 .claude/hooks/脚本名 → 输出命令字符串。

调用关系:hook 迁移测试用它准备旧命令样本。

调用图:外部调用 1 个(format!)。

tests::source_hook_command_with_project_dir1404–1410 ↗
fn source_hook_command_with_project_dir(script_name: &str) -> String

作用:测试用:生成带项目目录环境变量的旧 hook 命令。

数据流:输入脚本名 → 拼成 python3 "$CLAUDE_PROJECT_DIR"/.claude/hooks/脚本名 → 输出命令字符串。

调用关系:hook 路径改写测试用它覆盖带环境变量的路径写法。

调用图:外部调用 1 个(format!)。

tests::migrated_hook_command1412–1414 ↗
fn migrated_hook_command(script_name: &str) -> String

作用:测试用:生成预期的迁移后 hook 命令。

数据流:输入脚本名 → 调用 migrated_quoted_hook_command → 输出预期命令字符串。

调用关系:hook 迁移断言中用它和实际改写结果比较。

调用图:外部调用 1 个(migrated_quoted_hook_command)。

tests::migrated_quoted_hook_command1416–1424 ↗
fn migrated_quoted_hook_command(script_name: &str) -> String

作用:测试用:生成带单引号保护的目标 hook 脚本命令。

数据流:输入脚本名 → 拼成 /repo/.codex/hooks/脚本名,并包成 shell 单引号 → 输出 python3 命令。

调用关系:migrated_hook_command 和多个 hook 路径测试用它生成标准答案。

调用图:外部调用 2 个(new, format!)。

tests::env_placeholder_accepts_defaults1427–1432 ↗
fn env_placeholder_accepts_defaults()

作用:测试 parse_env_placeholder 能接受带默认值的环境变量占位符。

数据流:输入固定样例 ${TOKEN:-fallback} → 调用被测逻辑并比较结果 → 测试通过表示变量名 TOKEN 能被正确取出。

调用关系:它直接验证环境变量解析规则,保护 MCP 迁移里的 token 处理。

调用图:外部调用 1 个(assert_eq!)。

tests::mcp_migration_skips_placeholder_args1435–1452 ↗
fn mcp_migration_skips_placeholder_args()

作用:测试 MCP 命令参数里有环境变量占位符时会被跳过。

数据流:创建临时 .mcp.json,写入 args: ${DATABASE_URL} → 调用 build_mcp_config_from_external → 断言结果为空 TOML。

调用关系:它覆盖 mcp_server_toml_table 对复杂参数的保守行为。

调用图:外部调用 3 个(assert_eq!, write, new)。

tests::mcp_migration_prefers_command_transport_for_mixed_server_config1455–1495 ↗
fn mcp_migration_prefers_command_transport_for_mixed_server_config()

作用:测试一个 MCP 服务器同时有 command 和 url 时,迁移会优先按 command 型处理。

数据流:写入同时含 command、args、url 的配置 → 调用 MCP 迁移 → 断言输出包含 command 和 args。

调用关系:它验证 build_mcp_config_from_external 和 mcp_server_toml_table 的选择规则。

调用图:外部调用 3 个(assert_eq!, write, new)。

tests::mcp_migration_skips_unsupported_transports1498–1530 ↗
fn mcp_migration_skips_unsupported_transports()

作用:测试不支持的 MCP transport 会跳过,同时支持的 HTTP token 配置能迁移。

数据流:写入 sse 服务器和带 Bearer 环境变量的 HTTP 服务器 → 执行迁移 → 断言只保留 HTTP 服务器并提取 token 环境变量。

调用关系:它覆盖 mcp_server_toml_table 和 append_header_config 的关键分支。

调用图:外部调用 3 个(assert_eq!, write, new)。

tests::mcp_migration_reads_matching_project_entries_from_repo_external_project_config1533–1578 ↗
fn mcp_migration_reads_matching_project_entries_from_repo_external_project_config()

作用:测试项目目录里的 .claude.json 只迁移当前项目匹配的 MCP 配置。

数据流:创建 repo 和 other 两个目录,在配置里写两个 projects 条目 → 执行迁移 → 断言只包含 repo 对应服务器和顶层服务器。

调用关系:它验证 read_external_mcp_servers 和 project_path_matches_source_root 的配合。

调用图:调用 1 个内部函数(external_agent_project_config_file);外部调用 5 个(assert_eq!, create_dir_all, write, json!, new)。

tests::mcp_migration_reads_matching_project_entries_from_home_external_project_config1581–1617 ↗
fn mcp_migration_reads_matching_project_entries_from_home_external_project_config()

作用:测试能从外部智能体 home 旁边的全局项目配置读取当前项目的 MCP 服务器。

数据流:创建项目目录和 .claude home,在上层 .claude.json 写 projects → 执行迁移 → 断言读到对应服务器。

调用关系:它覆盖 read_external_mcp_servers 调用 append_external_agent_project_mcp_servers 的路径。

调用图:调用 2 个内部函数(external_agent_config_dir, external_agent_project_config_file);外部调用 5 个(assert_eq!, create_dir_all, write, json!, new)。

tests::mcp_migration_preserves_repo_servers_over_home_project_entries1620–1670 ↗
fn mcp_migration_preserves_repo_servers_over_home_project_entries()

作用:测试仓库本地 MCP 配置比 home 项目配置优先,不会被覆盖。

数据流:本地 .mcp.json 和 home 项目配置都写 shared 服务器 → 执行迁移 → 断言 shared 保留本地值,同时加入 home-only。

调用关系:它验证 append_mcp_servers_from_value 的 PreserveExisting 合并策略。

调用图:调用 2 个内部函数(external_agent_config_dir, external_agent_project_config_file);外部调用 5 个(assert_eq!, create_dir_all, write, json!, new)。

tests::mcp_migration_skips_disabled_servers1673–1706 ↗
fn mcp_migration_skips_disabled_servers()

作用:测试被显式禁用或不在启用名单中的 MCP 服务器会被跳过。

数据流:写入三个服务器和启用/禁用设置 → 执行迁移 → 断言只剩 enabled 服务器。

调用关系:它主要保护 mcp_server_is_disabled 的判断规则。

调用图:外部调用 4 个(assert_eq!, write, json!, new)。

tests::command_skill_names_include_nested_paths1709–1714 ↗
fn command_skill_names_include_nested_paths()

作用:测试嵌套目录中的命令会把路径也放进 skill 名。

数据流:构造 commands/pr/review.md → 调用 command_skill_name → 断言结果是 source-command-pr-review。

调用关系:它验证 command_source_name 和 slugify_name 组合后的命名效果。

调用图:外部调用 2 个(assert_eq!, source_path)。

tests::command_skill_names_must_fit_codex_skill_loader_limit1717–1723 ↗
fn command_skill_names_must_fit_codex_skill_loader_limit()

作用:测试 skill 名太长的命令不会迁移。

数据流:构造很深很长的命令路径和带描述文档 → 调用 command_skill_name_if_supported → 断言返回空。

调用关系:它保护 MAX_SKILL_NAME_LEN 限制,避免生成 Codex 加载不了的 skill。

调用图:调用 1 个内部函数(parse_document_content);外部调用 2 个(assert!, source_path)。

tests::commands_with_provider_runtime_expansion_are_skipped1726–1734 ↗
fn commands_with_provider_runtime_expansion_are_skipped()

作用:测试含有旧运行时动态展开语法的命令会被跳过。

数据流:构造正文含 $ARGUMENTS 和 @release.yaml 的命令文档 → 调用 command_skill_name_if_supported → 断言不可迁移。

调用关系:它验证 has_unsupported_command_template_features 的过滤作用。

调用图:调用 1 个内部函数(parse_document_content);外部调用 2 个(assert!, source_path)。

tests::commands_without_description_are_skipped1737–1743 ↗
fn commands_without_description_are_skipped()

作用:测试没有 description 的命令不会迁移。

数据流:构造 README 风格文档 → 调用 command_skill_name_if_supported → 断言返回空。

调用关系:它保护 command_skill_description 和 README 跳过规则。

调用图:调用 1 个内部函数(parse_document_content);外部调用 2 个(assert!, source_path)。

tests::command_slug_collisions_are_skipped1746–1765 ↗
fn command_slug_collisions_are_skipped()

作用:测试两个命令如果迁移后名字撞车,就两个都不迁。

数据流:创建 foo-bar.md 和 foo_bar.md,它们会生成同一个 slug → 调用 unique_supported_command_sources → 断言结果为空。

调用关系:它验证 unique_supported_command_sources 的冲突处理,避免导入时互相覆盖。

调用图:外部调用 4 个(assert_eq!, create_dir_all, write, new)。

tests::subagent_accepts_yaml_block_lists_by_ignoring_unsupported_fields1768–1774 ↗
fn subagent_accepts_yaml_block_lists_by_ignoring_unsupported_fields()

作用:测试子智能体 frontmatter 里有列表字段时,只要必要字段存在就仍可迁移。

数据流:构造含 skills、tools 等列表字段的文档 → 解析并调用 agent_metadata → 断言能提取元信息。

调用关系:它验证 parse_frontmatter 会把复杂字段标成 Other,而 agent_metadata 会忽略不需要的字段。

调用图:调用 1 个内部函数(parse_document_content);外部调用 1 个(assert!)。

tests::subagent_requires_minimum_codex_agent_fields1777–1785 ↗
fn subagent_requires_minimum_codex_agent_fields()

作用:测试子智能体必须至少有 name、description 和非空正文。

数据流:构造缺 description 和缺正文的文档 → 调用 agent_metadata → 断言都不能迁移。

调用关系:它保护 agent_metadata 的最低质量门槛。

调用图:调用 1 个内部函数(parse_document_content);外部调用 1 个(assert!)。

tests::subagent_preserves_default_model_when_source_model_is_present1788–1808 ↗
fn subagent_preserves_default_model_when_source_model_is_present()

作用:测试旧子智能体里即使写了 model,也不会强行迁移成 Codex 模型设置。

数据流:构造含 model 和 effort: max 的文档 → 提取元信息并渲染 TOML → 断言输出只映射 effort,不包含旧 model。

调用关系:它验证 agent_metadata 和 render_agent_toml 不会把旧模型字段误带过来。

调用图:调用 3 个内部函数(agent_metadata, parse_document_content, render_agent_toml);外部调用 2 个(assert_eq!, from_str)。

tests::subagent_target_preserves_dotted_file_stem1811–1819 ↗
fn subagent_target_preserves_dotted_file_stem()

作用:测试子智能体文件名里有点号时,目标文件名会保留点号。

数据流:输入 security.audit.md 和目标 agents 目录 → 调用 subagent_target_file → 断言输出 security.audit.toml。

调用关系:它保护 subagent_target_file 的文件名转换行为。

调用图:外部调用 3 个(new, assert_eq!, source_path)。

tests::frontmatter_accepts_crlf_delimiters1822–1845 ↗
fn frontmatter_accepts_crlf_delimiters()

作用:测试 Windows 风格换行的 frontmatter 也能解析。

数据流:构造使用 \r\n 的文档 → 调用 parse_document_content → 断言 name、description 和正文都正确。

调用关系:它验证 frontmatter_end 对多种换行分隔符的兼容。

调用图:调用 1 个内部函数(parse_document_content);外部调用 1 个(assert_eq!)。

tests::hook_migration_ignores_unsupported_handlers1848–1903 ↗
fn hook_migration_ignores_unsupported_handlers()

作用:测试 hook 迁移会忽略不支持的条件、类型和字段,只保留安全 command hook。

数据流:构造包含 if、http、prompt 等多种 hook 的 settings → 调用 append_convertible_hook_groups → 断言只迁移 PermissionRequest 的简单命令。

调用关系:它直接覆盖 append_convertible_hook_groups 的筛选规则和 rewrite_hook_command 的路径改写。

调用图:调用 1 个内部函数(append_convertible_hook_groups);外部调用 4 个(new, assert_eq!, new, json!)。

tests::hook_migration_honors_disable_all_hooks1906–1926 ↗
fn hook_migration_honors_disable_all_hooks()

作用:测试 disableAllHooks 为 true 时不会迁移任何 hook。

数据流:写入带 disableAllHooks: true 的 settings.json → 调用 hook_migration → 断言结果为空。

调用关系:它保护 hook_migration 对全局禁用开关的处理。

调用图:外部调用 3 个(assert_eq!, write, new)。

tests::hook_migration_honors_settings_local_disable_override1929–1979 ↗
fn hook_migration_honors_settings_local_disable_override()

作用:测试 settings.local.json 可以覆盖 settings.json 里的 disableAllHooks。

数据流:settings.json 写禁用,settings.local.json 写启用并各自带 hook → 调用 hook_migration → 断言两个文件里的 hook 都被迁移。

调用关系:它验证 hook_migration 读取多个设置文件和最后禁用值生效的行为。

调用图:外部调用 3 个(assert_eq!, write, new)。

tests::hook_command_paths_rewrite_to_target_hook_dir1982–2129 ↗
fn hook_command_paths_rewrite_to_target_hook_dir()

作用:测试 hook 命令路径改写的各种边界情况。

数据流:构造带环境变量、引号、相对路径、绝对路径、变量赋值、动态后缀、Windows 路径等命令 → 调用 rewrite_hook_command → 逐个断言该改的改、不该改的不改。

调用关系:它覆盖 rewrite_hook_command、replace_quoted_hook_paths、replace_unquoted_hook_paths 和路径安全检查函数。

调用图:调用 1 个内部函数(external_agent_project_dir_env_var);外部调用 2 个(assert_eq!, format!)。

tests::hook_script_copy_keeps_existing_target_scripts2132–2149 ↗
fn hook_script_copy_keeps_existing_target_scripts()

作用:测试复制 hook 脚本时不会覆盖目标目录已有脚本。

数据流:创建源脚本和同名目标脚本,内容不同 → 调用 copy_hook_scripts → 读取目标脚本并断言仍是旧内容。

调用关系:它验证 copy_hook_scripts 和 copy_dir_recursive_skip_existing 的“不覆盖”承诺。

调用图:调用 2 个内部函数(copy_hook_scripts, external_agent_config_dir);外部调用 4 个(assert_eq!, create_dir_all, write, new)。

tests::hook_migration_drops_negative_timeouts2152–2183 ↗
fn hook_migration_drops_negative_timeouts()

作用:测试 hook timeout 为负数时会丢掉 timeout 字段,而不是生成错误值。

数据流:构造 timeout: -1 的 hook → 调用 append_convertible_hook_groups → 断言迁移结果保留命令但没有 timeout。

调用关系:它保护 json_u64 在 hook 迁移中的过滤效果。

调用图:调用 1 个内部函数(append_convertible_hook_groups);外部调用 3 个(assert_eq!, new, json!)。

execpolicy-legacy/build.rs源码 ↗
orchestrationstartup/build time

Rust 里的 Cargo 是构建工具,可以理解成“项目管家”,负责编译代码。这个文件在真正编译项目之前运行。它只做一件事:打印一条给 Cargo 看的指令,说明 src/default.policy 这个文件一旦发生变化,就应该重新运行构建流程。这样做很重要,因为策略文件不是普通 Rust 代码,Cargo 默认不一定知道它会影响最终结果。没有这句提醒,开发者可能修改了默认策略,但程序仍然沿用旧的构建产物,就像改了菜单却厨房还按旧菜单做菜。这里没有复杂逻辑,也不读写文件,只是通过标准输出把“请盯着这个文件”这句话交给 Cargo。

函数细节1
main1–3 ↗
fn main()

作用:这是构建脚本的入口函数。它告诉 Cargo 构建工具:只要 src/default.policy 文件改了,就需要重新执行这个构建脚本。

数据流:进去时没有参数,也不读取项目数据;它生成一行特殊格式的文字 cargo:rerun-if-changed=src/default.policy,并把这行文字打印到标准输出;Cargo 看到后会记录这个依赖关系,之后用来判断什么时候需要重新构建。

调用关系:Cargo 在构建这个包时自动调用 mainmain 不把工作交给项目里的其他函数,只调用外部的 println! 宏把指令打印出来,让 Cargo 接手后续的重新构建判断。

调用图:外部调用 1 个(println!)。

TUI 配置同步与运行时设置

这些文件使应用和聊天组件设置与已保存配置保持同步,解析服务层级行为,并在 TUI 中应用面向用户的运行时策略。

tui/src/config_update.rs源码 ↗
config用户在 TUI 中修改设置、读取设置或保存选择时活跃

TUI 里有很多地方会改设置,比如换模型、开关功能、信任某个项目、启用技能等。这个文件把这些改动统一包装成 ConfigEdit,也就是“一条配置修改命令”:改哪个键、写什么值、用什么方式写。然后它再通过 AppServerRequestHandle,把这些命令发给 app server。可以把它理解成:界面只填表,不亲自进仓库改货;真正入库由仓库管理员 app server 来做。这样做的好处是配置版本、重新加载、路径规则、特殊转义都能集中处理。文件里既有很小的构造函数,比如“把某个配置值清空”,也有面向场景的函数,比如“构造模型选择需要的两条修改”,还有真正发请求的异步函数,比如批量写配置、读取当前生效配置、写技能开关。

函数细节15
replace_config_value30–36 ↗
fn replace_config_value(key_path: impl Into<String>, value: JsonValue) -> ConfigEdit

作用:生成一条“把某个配置项替换成某个值”的修改命令。别人不需要知道底层 ConfigEdit 怎么填,只要给键名和值就行。

数据流:输入一个配置路径,比如 model,以及一个 JSON 值,比如 "gpt-5" → 它把路径转成字符串,配上“替换”这种写入方式 → 输出一个 ConfigEdit,表示之后要把这个配置项改成这个值,不会立刻写入文件。

调用关系:这是很多配置修改的基础积木。功能开关、OSS provider、信任项目、清空配置等流程都会借它生成标准修改项,之后通常交给 write_config_batch 发给 app server。

调用图:被 6 处调用(update_feature_flags, handle_event, build_feature_enabled_edit, build_oss_provider_edit, clear_config_value, trusted_project_edit);外部调用 1 个(into)。

clear_config_value38–40 ↗
fn clear_config_value(key_path: impl Into<String>) -> ConfigEdit

作用:生成一条“清掉某个配置项”的修改命令。这里的清掉不是马上删除文件内容,而是把值设成 JSON 的 null,让后台按协议处理。

数据流:输入一个配置路径 → 它调用 replace_config_value,把这个路径对应的值设为 null → 输出一条 ConfigEdit,表示这个配置项应该被清空或恢复默认。

调用关系:它是 replace_config_value 的简化用法。功能开关和界面事件在需要撤销用户设置、回到默认值时会用它。

调用图:调用 1 个内部函数(replace_config_value);被 3 处调用(update_feature_flags, handle_event, build_feature_enabled_edit)。

app_scoped_key_path42–45 ↗
fn app_scoped_key_path(app_id: &str, key_path: &str) -> String

作用:拼出“某个应用自己的配置项”的完整路径。这样不同 app 的配置能放在 apps 下面各自的格子里,不容易串台。

数据流:输入 app_id 和普通配置路径 → 它先把 app_id 按 JSON 字符串规则转义,避免特殊字符破坏路径 → 输出类似 apps."某应用".某配置 的路径字符串。

调用关系:它是一个纯粹的路径拼接工具。调用方可以用它先算出准确键名,再把键名交给 replace_config_value 或 clear_config_value 之类的函数。

调用图:外部调用 2 个(format!, String)。

format_config_error47–49 ↗
fn format_config_error(err: &impl Display) -> String

作用:把配置相关错误整理成适合显示给人的文字。这样界面展示错误时不会只给用户一个生硬的内部对象。

数据流:输入一个可以显示的错误对象 → 它用带上下文的格式化方式把错误转成字符串 → 输出一段错误说明文字,不改动任何配置。

调用关系:当功能开关更新、界面事件处理、保存信任选择失败时,上层会调用它来把失败原因变成可读消息。

调用图:被 3 处调用(update_feature_flags, handle_event, persist_selected_trust);外部调用 1 个(format!)。

trusted_project_edit51–59 ↗
fn trusted_project_edit(project_path: &Path) -> ConfigEdit

作用:生成一条“把某个项目标记为受信任”的配置修改。受信任通常意味着这个项目以后可以用更少确认步骤运行相关能力。

数据流:输入一个项目路径 → 它先把路径转成配置里使用的项目键,并处理反斜杠和引号这类容易弄坏配置路径的字符 → 输出一条 ConfigEdit,把该项目的 trust_level 写成 Trusted。

调用关系:这是 write_trusted_project 的内部帮手。用户在 TUI 里确认信任项目后,上层通过 write_trusted_project 间接用到它,然后把修改发给 app server。

调用图:调用 2 个内部函数(project_trust_key, replace_config_value);外部调用 2 个(format!, json!)。

build_model_selection_edits61–78 ↗
fn build_model_selection_edits(
    model: &str,
    effort: Option<impl ToString>,
) -> Vec<ConfigEdit>

作用:生成切换模型时需要写入的配置修改。它同时处理模型名和推理努力程度,避免只改一半造成界面显示和实际行为不一致。

数据流:输入模型名,以及可选的 effort(可以理解成模型思考强度)→ 它总是生成一条 model 修改;如果 effort 有值就写入,没有值就清空 model_reasoning_effort → 输出一组 ConfigEdit。

调用关系:TUI 处理用户选择模型的事件时会调用它。它只负责准备修改清单,真正保存通常交给 write_config_batch。

调用图:被 1 处调用(handle_event);外部调用 1 个(vec!)。

build_service_tier_selection_edits80–97 ↗
fn build_service_tier_selection_edits(service_tier: Option<&str>) -> Vec<ConfigEdit>

作用:生成切换服务档位时的配置修改。服务档位可以理解成请求走“更快”或“更灵活”等不同后端策略。

数据流:输入一个可选的 service_tier 字符串 → 没有值就生成清空 service_tier 的修改;有值时会把请求值转换成配置里稳定使用的写法,比如 fast 或 flex → 输出一组 ConfigEdit。

调用关系:TUI 处理服务档位选择事件时会用它准备修改项,然后把结果交给 write_config_batch 统一写入。

调用图:被 1 处调用(handle_event);外部调用 1 个(vec!)。

build_windows_sandbox_mode_edits100–115 ↗
fn build_windows_sandbox_mode_edits(elevated_enabled: bool) -> Vec<ConfigEdit>

作用:在 Windows 上生成切换沙盒模式需要的配置修改。沙盒可以理解成给程序活动划一块隔离区域,降低误操作风险。

数据流:输入 elevated_enabled,表示是否使用更高权限的沙盒 → 它写入 windows.sandbox 为 elevated 或 unelevated,同时清掉几个旧的或实验性的功能开关键 → 输出一组 ConfigEdit。

调用关系:这个函数只在 Windows 编译时存在。TUI 处理 Windows 沙盒设置事件时会调用它,之后仍然由 write_config_batch 把这些修改交给 app server。

调用图:被 1 处调用(handle_event);外部调用 1 个(vec!)。

build_feature_enabled_edit117–128 ↗
fn build_feature_enabled_edit(feature_key: &str, enabled: bool) -> ConfigEdit

作用:生成一条打开或关闭某个功能开关的配置修改。它还会照顾默认关闭的功能:如果用户把这种功能关掉,就直接清掉配置,让默认值接管。

数据流:输入功能名和 enabled 布尔值 → 它先拼出 features.某功能 这样的配置路径,再查这个功能默认是不是关闭 → 如果需要明确记录就写 true 或 false;如果关掉的是默认关闭功能,就生成清空配置的修改 → 输出一个 ConfigEdit。

调用关系:update_feature_flags 会用它为每个功能开关准备修改。它内部会根据情况调用 replace_config_value 或 clear_config_value。

调用图:调用 2 个内部函数(clear_config_value, replace_config_value);被 1 处调用(update_feature_flags);外部调用 2 个(format!, json!)。

build_memory_settings_edits130–141 ↗
fn build_memory_settings_edits(
    use_memories: bool,
    generate_memories: bool,
) -> Vec<ConfigEdit>

作用:生成记忆功能相关的两条配置修改:是否使用记忆,以及是否自动生成记忆。这样两个开关可以一起保存,减少状态不同步。

数据流:输入 use_memories 和 generate_memories 两个布尔值 → 它分别写到 memories.use_memories 和 memories.generate_memories → 输出包含两条 ConfigEdit 的列表。

调用关系:update_memory_settings 在用户改记忆设置时会调用它。生成的修改列表随后交给 write_config_batch 写入。

调用图:被 1 处调用(update_memory_settings);外部调用 1 个(vec!)。

build_oss_provider_edit143–145 ↗
fn build_oss_provider_edit(provider: &str) -> ConfigEdit

作用:生成一条修改 OSS provider 的配置命令。这里的 provider 可以理解成选择使用哪个开源服务或供应方。

数据流:输入 provider 名称 → 它把名称包成 JSON 字符串,并指定写到 oss_provider 这个配置项 → 输出一个 ConfigEdit。

调用关系:它复用 replace_config_value 这个基础积木。调用方拿到这条修改后,可以和其他修改一起交给 write_config_batch。

调用图:调用 1 个内部函数(replace_config_value);外部调用 1 个(json!)。

write_config_batch147–164 ↗
async fn write_config_batch(
    request_handle: AppServerRequestHandle,
    edits: Vec<ConfigEdit>,
) -> Result<ConfigWriteResponse>

作用:把一批配置修改真正发给 app server 保存。它是这个文件里从“准备修改”走向“实际写入”的关键出口。

数据流:输入 app server 请求句柄和一组 ConfigEdit → 它生成一个带 tui-config-write 前缀的唯一请求编号,把修改包进 ConfigBatchWrite 请求,并要求写完后重新加载用户配置 → 成功时返回 ConfigWriteResponse,失败时给错误加上“TUI 批量写配置失败”的上下文。

调用关系:很多上层流程都会走到这里,比如更新功能开关、更新记忆设置、界面事件处理、启动 TUI 时的配置写入,以及 write_trusted_project。它把活儿交给 request_typed,也就是 app server 客户端的 typed request 发送机制。

调用图:调用 1 个内部函数(request_typed);被 5 处调用(update_feature_flags, update_memory_settings, handle_event, write_trusted_project, run_ratatui_app);外部调用 2 个(String, format!)。

write_trusted_project166–171 ↗
async fn write_trusted_project(
    request_handle: AppServerRequestHandle,
    project_path: &Path,
) -> Result<ConfigWriteResponse>

作用:把“信任这个项目”的决定保存到配置里。它把专门的信任项目修改包装好,再走统一的批量写入通道。

数据流:输入 app server 请求句柄和项目路径 → 它先调用 trusted_project_edit 生成一条信任项目的 ConfigEdit → 再调用 write_config_batch 发给 app server → 输出写配置的响应或错误。

调用关系:persist_selected_trust 在用户确认信任选择时会调用它。它本身不直接发底层请求,而是把请求交给 write_config_batch。

调用图:调用 1 个内部函数(write_config_batch);被 1 处调用(persist_selected_trust);外部调用 1 个(vec!)。

read_effective_config173–188 ↗
async fn read_effective_config(
    request_handle: AppServerRequestHandle,
    cwd: String,
) -> Result<ConfigReadResponse>

作用:向 app server 读取当前真正生效的配置。所谓生效配置,是把默认值、用户配置、项目配置等规则算完以后,程序实际会用的结果。

数据流:输入 app server 请求句柄和当前工作目录 cwd → 它生成一个唯一读取请求编号,发送 ConfigRead 请求,并说明不需要返回每一层配置细节,只要最终结果 → 成功时返回 ConfigReadResponse,失败时给错误加上“TUI 读取配置失败”的上下文。

调用关系:read_effective_config_after_overridden_write 会调用它,常见场景是写入覆盖配置后再确认当前生效值。它把实际通信交给 request_typed。

调用图:调用 1 个内部函数(request_typed);被 1 处调用(read_effective_config_after_overridden_write);外部调用 2 个(String, format!)。

write_skill_enabled190–208 ↗
async fn write_skill_enabled(
    request_handle: AppServerRequestHandle,
    path: AbsolutePathBuf,
    enabled: bool,
) -> Result<()>

作用:保存某个技能是否启用。技能可以理解成工具箱里的一个可选能力,用户可以在 TUI 里打开或关闭。

数据流:输入 app server 请求句柄、技能所在的绝对路径,以及 enabled 开关 → 它生成唯一请求编号,发送 SkillsConfigWrite 请求,按路径定位技能并写入启用状态 → 成功时返回空结果,失败时带上“技能配置写入失败”的上下文。

调用关系:TUI 的事件处理在用户切换技能开关时会调用它。它不像普通配置那样走 ConfigBatchWrite,而是走专门的 SkillsConfigWrite 请求。

调用图:调用 1 个内部函数(request_typed);被 1 处调用(handle_event);外部调用 2 个(String, format!)。

tui/src/app/config_persistence.rs源码 ↗
orchestrationcross-cutting,主要在用户修改设置、切换线程、恢复会话、刷新配置时活跃

用户在界面里改权限、实验功能、记忆开关、主题等设置时,不能只改屏幕上的按钮,也不能只写进文件。否则会出现“看起来改了但下一轮对话没生效”,或者“文件保存了但当前会话还用旧设置”的问题。这个文件就是中间的协调员。它会在需要时重新读取配置文件,把运行时临时覆盖的权限再补回去;写配置后如果发现有更高优先级的配置挡住了这次修改,也会读回真正生效的配置,并同步到界面。它还会把权限和沙盒变化通知正在运行的会话,让当前线程马上按新规则工作。可以把它想成酒店前台:既要更新登记簿,也要把新房卡发给客人,还要通知楼层服务别用旧信息。

函数细节53
build_config_on_runtime_worker17–26 ↗
async fn build_config_on_runtime_worker(
    builder: ConfigBuilder,
    error_context: String,
) -> Result<Config>

作用:在后台任务里重新构建配置,避免把当前界面的运行流程卡住。它还会把失败原因包装得更清楚,方便显示和排查。

数据流:输入是一个 ConfigBuilder(配置建造器)和一段错误说明 → 它把真正的 build 放进 tokio 后台任务执行,并区分普通失败、任务失败、任务恐慌崩溃 → 输出新的 Config,或者带上下文的错误。

调用关系:App::rebuild_config_for_cwd 和 App::rebuild_config_for_permission_profile 都把具体准备好的建造器交给它;它是所有运行时重建配置的共同入口。

调用图:调用 1 个内部函数(build);被 2 处调用(rebuild_config_for_cwd, rebuild_config_for_permission_profile);外部调用 2 个(resume_unwind, spawn)。

App::rebuild_config_for_cwd29–44 ↗
async fn rebuild_config_for_cwd(&self, cwd: PathBuf) -> Result<Config>

作用:按某个工作目录重新算出一份配置。用户切换项目目录或恢复不同目录的会话时,需要用这个目录对应的配置规则。

数据流:输入是 cwd 路径,同时读取应用当前的配置来源、命令行覆盖项、测试覆盖项、云端配置包等 → 它把 cwd 塞进覆盖配置,组装 ConfigBuilder → 输出针对这个 cwd 重新构建出的 Config。

调用关系:它把耗时构建交给 build_config_on_runtime_worker;App::refresh_in_memory_config_from_disk 和 App::rebuild_config_for_resume_or_fallback 会调用它。

调用图:调用 1 个内部函数(build_config_on_runtime_worker);被 2 处调用(rebuild_config_for_resume_or_fallback, refresh_in_memory_config_from_disk);外部调用 4 个(clone, display, default, format!)。

App::rebuild_config_for_permission_profile46–66 ↗
async fn rebuild_config_for_permission_profile(
        &self,
        profile_id: &str,
    ) -> Result<Config>

作用:按用户选中的权限档案重新算一份配置。这样可以知道这个档案真正会带来哪些权限、沙盒和网络设置。

数据流:输入是 profile_id,同时读取当前聊天窗口的 cwd 和应用现有配置来源 → 它清掉旧的沙盒、旧的权限档案覆盖,改用指定默认权限档案 → 输出解析后的 Config。

调用关系:它把实际构建交给 build_config_on_runtime_worker;权限选择流程 App::apply_permission_profile_selection 和 Windows 权限准备 App::windows_setup_permissions 会用它。

调用图:调用 1 个内部函数(build_config_on_runtime_worker);被 2 处调用(apply_permission_profile_selection, windows_setup_permissions);外部调用 2 个(default, format!)。

App::windows_setup_permissions69–89 ↗
async fn windows_setup_permissions(
        &self,
        preset: &ApprovalPreset,
        profile_selection: Option<&PermissionProfileSelection>,
    ) -> Result<WindowsSetupPermissions>

作用:在 Windows 上准备权限设置需要的信息。它决定用用户选中的权限档案,还是用预设里的权限档案。

数据流:输入是一个 Windows 审批预设,以及可选的权限档案选择 → 如果有选择,就重新构建该档案的配置并取出权限档案和工作区根目录;如果没有,就用预设和当前配置 → 输出 WindowsSetupPermissions。

调用关系:当 Windows 平台需要设置权限时调用;如果要解析用户选择,它会先交给 App::rebuild_config_for_permission_profile。

调用图:调用 1 个内部函数(rebuild_config_for_permission_profile)。

App::apply_permission_profile_selection91–206 ↗
async fn apply_permission_profile_selection(
        &mut self,
        selection: PermissionProfileSelection,
    ) -> bool

作用:把用户在权限菜单里选的档案真正应用到当前应用和当前聊天会话里。没有它,界面可能显示改了权限,但正在进行的会话仍按旧规则运行。

数据流:输入是 PermissionProfileSelection,里面有档案 ID、审批策略、审批人和显示名字 → 它先重建该档案配置,再更新 App 的 Config、ChatWidget 的配置副本、运行时覆盖记录和当前线程上下文 → 输出 true 表示成功,false 表示失败并在聊天界面显示错误。

调用关系:它调用 App::rebuild_config_for_permission_profile 取真实配置,用 App::try_set_approval_policy_on_config 写审批策略,最后通过 AppEvent 和 AppCommand::override_turn_context 通知当前会话立即换权限。

调用图:调用 4 个内部函数(from_session_snapshot, from_config, rebuild_config_for_permission_profile, try_set_approval_policy_on_config);外部调用 7 个(new, override_turn_context, CodexOp, InsertHistoryCell, format!, new_info_event, warn!)。

App::refresh_in_memory_config_from_disk208–216 ↗
async fn refresh_in_memory_config_from_disk(&mut self) -> Result<()>

作用:从磁盘重新读取配置,并更新应用内存里的配置。它用于确保用户改过 config.toml 后,界面和当前应用能看到新内容。

数据流:输入是当前 App 状态,尤其是聊天窗口里的 cwd → 它按该 cwd 重建 Config,再把运行时临时权限覆盖补回去,然后替换 self.config 并同步插件提及配置 → 输出成功或错误。

调用关系:它调用 App::rebuild_config_for_cwd 和 App::apply_runtime_policy_overrides;App::refresh_in_memory_config_from_disk_best_effort 包了一层安全调用。

调用图:调用 2 个内部函数(apply_runtime_policy_overrides, rebuild_config_for_cwd);被 1 处调用(refresh_in_memory_config_from_disk_best_effort)。

App::refresh_in_memory_config_from_disk_best_effort218–226 ↗
async fn refresh_in_memory_config_from_disk_best_effort(&mut self, action: &str)

作用:尽力刷新磁盘配置,但失败时不打断用户操作。适合切换线程这类“能刷新最好,不能也别崩”的场景。

数据流:输入是一段动作说明 action → 它调用 App::refresh_in_memory_config_from_disk;如果失败,只记日志,继续保留当前内存配置 → 没有返回配置,也不会抛出错误给上层。

调用关系:它是 refresh_in_memory_config_from_disk 的容错包装,常用于线程转换前的准备步骤。

调用图:调用 1 个内部函数(refresh_in_memory_config_from_disk);外部调用 1 个(warn!)。

App::read_effective_config_after_overridden_write228–248 ↗
async fn read_effective_config_after_overridden_write(
        &mut self,
        app_server: &mut AppServerSession,
        setting: &str,
    ) -> Option<ConfigReadResponse>

作用:配置写入后如果被更高优先级配置覆盖,就读取“真正生效”的配置。这样界面不会误以为刚写的值已经生效。

数据流:输入是 AppServerSession 和设置名称 → 它拿当前 cwd 向 app server 请求 effective config(最终生效配置)→ 成功返回 ConfigReadResponse,失败则在聊天界面提示“保存了但无法刷新”。

调用关系:App::update_feature_flags、App::update_memory_settings 和 App::sync_windows_sandbox_after_overridden_write 会在写入被覆盖时调用它。

调用图:调用 2 个内部函数(request_handle, read_effective_config);被 3 处调用(sync_windows_sandbox_after_overridden_write, update_feature_flags, update_memory_settings);外部调用 2 个(format!, warn!)。

App::rebuild_config_for_resume_or_fallback250–271 ↗
async fn rebuild_config_for_resume_or_fallback(
        &mut self,
        current_cwd: &Path,
        resume_cwd: PathBuf,
    ) -> Result<Config>

作用:恢复会话时为会话目录重新构建配置;如果同目录构建失败,就安全地退回当前配置。这样坏配置不会轻易破坏同目录恢复。

数据流:输入是当前 cwd 和要恢复的 resume_cwd → 它先尝试按 resume_cwd 重建配置;失败时比较两个目录,目录不同就返回错误,目录相同就记录警告并返回当前 self.config 的克隆 → 输出 Config 或错误。

调用关系:它调用 App::rebuild_config_for_cwd 和 session_resume::cwds_differ,是会话恢复流程里的保护垫。

调用图:调用 2 个内部函数(rebuild_config_for_cwd, cwds_differ);外部调用 3 个(clone, display, warn!)。

App::apply_runtime_policy_overrides273–302 ↗
fn apply_runtime_policy_overrides(&mut self, config: &mut Config)

作用:把运行中临时改过的权限选择重新盖到新读取的配置上。否则刷新 config.toml 后,当前会话临时选择的权限可能被冲掉。

数据流:输入是可修改的 Config,同时读取 App 里保存的运行时审批策略覆盖和权限档案覆盖 → 它把这些覆盖写入 Config,失败就记日志并提示用户 → 输出体现在传入的 Config 被修改。

调用关系:App::refresh_in_memory_config_from_disk 在替换内存配置前调用它,保证磁盘刷新和运行时选择不互相打架。

调用图:调用 1 个内部函数(from_session_snapshot);被 1 处调用(refresh_in_memory_config_from_disk);外部调用 2 个(format!, warn!)。

App::set_approvals_reviewer_in_app_and_widget304–307 ↗
fn set_approvals_reviewer_in_app_and_widget(&mut self, reviewer: ApprovalsReviewer)

作用:同时更新 App 配置和聊天窗口配置里的“谁来审批”。它避免两边保存的同一项设置不一致。

数据流:输入是 ApprovalsReviewer(审批人设置)→ 它写入 self.config.approvals_reviewer,并调用聊天窗口同步同样的值 → 没有返回值,但两个地方都会被改。

调用关系:App::update_feature_flags 和 App::sync_feature_state_from_effective_config 在审批人需要同步时调用它。

调用图:被 2 处调用(sync_feature_state_from_effective_config, update_feature_flags)。

App::try_set_approval_policy_on_config309–324 ↗
fn try_set_approval_policy_on_config(
        &mut self,
        config: &mut Config,
        policy: AskForApproval,
        user_message_prefix: &str,
        log_message: &str,
    ) -> bool

作用:尝试把审批策略写进某份 Config,并统一处理失败提示。审批策略就是遇到敏感操作时要不要问用户。

数据流:输入是要修改的 Config、目标策略、给用户看的错误前缀和日志文字 → 它把界面层策略转成核心层格式并写入配置 → 成功返回 true,失败返回 false 并显示错误。

调用关系:App::apply_permission_profile_selection 和 App::update_feature_flags 用它安全地改审批策略,避免每处都重复写错误处理。

调用图:调用 1 个内部函数(to_core);被 2 处调用(apply_permission_profile_selection, update_feature_flags);外部调用 2 个(format!, warn!)。

App::try_set_builtin_active_permission_profile_on_config326–361 ↗
fn try_set_builtin_active_permission_profile_on_config(
        &mut self,
        config: &mut Config,
        active_permission_profile: ActivePermissionProfile,
        user_message_prefix: &str,

作用:把一个内置的活动权限档案写进 Config。内置档案可以理解为系统预先准备好的几套权限方案。

数据流:输入是要修改的 Config、活动权限档案、用户错误前缀和日志文字 → 它先查这个活动档案是否有对应内置权限档案,再写入配置 → 成功返回 PermissionProfile,失败返回 None 并提示用户。

调用关系:App::update_feature_flags 和 App::sync_auto_review_runtime_state_from_effective_config 在启用或同步自动审核相关权限时调用它。

调用图:调用 1 个内部函数(active);被 2 处调用(sync_auto_review_runtime_state_from_effective_config, update_feature_flags);外部调用 2 个(format!, warn!)。

App::update_feature_flags363–616 ↗
async fn update_feature_flags(
        &mut self,
        app_server: &mut AppServerSession,
        updates: Vec<(Feature, bool)>,
    )

作用:保存并应用实验功能开关。它不只是改一个布尔值,有些功能还会连带改变审批人、审批策略、沙盒和当前会话上下文。

数据流:输入是 app server 和一组 Feature 开关更新 → 它先在临时配置里计算每个开关的最终结果,准备要写入 config.toml 的批量修改,写成功后再更新内存配置和聊天窗口;如果写入被覆盖,就读回真正生效配置再同步 → 没有返回值,但会修改配置、界面和可能的当前会话权限。

调用关系:这是实验功能设置的主流程;它调用读取覆盖配置、同步 feature、同步自动审核运行态、传播 Windows 沙盒等多个辅助函数,并在必要时向聊天窗口提交 override_turn_context。

调用图:调用 18 个内部函数(from, from_session_snapshot, from_config, propagate_windows_sandbox_turn_context, read_effective_config_after_overridden_write, set_approvals_reviewer_in_app_and_widget, sync_auto_review_runtime_state_from_effective_config, sync_feature_state_from_effective_config, try_set_approval_policy_on_config, try_set_builtin_active_permission_profile_on_config (+8 more));外部调用 7 个(new, with_capacity, override_turn_context, format!, json!, error!, warn!)。

App::update_memory_settings618–664 ↗
async fn update_memory_settings(
        &mut self,
        app_server: &mut AppServerSession,
        use_memories: bool,
        generate_memories: bool,
    ) -> bool

作用:保存并应用记忆功能的两个开关:是否使用记忆、是否生成记忆。它保证文件、内存和界面尽量一致。

数据流:输入是 app server、use_memories 和 generate_memories → 它构造配置编辑并写入;如果被覆盖,就读回有效配置并同步;如果成功,就直接更新 self.config.memories 和聊天窗口 → 返回 true 表示应用成功,false 表示失败。

调用关系:App::update_memory_settings_with_app_server 会先调用它;它在覆盖写入场景下会调用 App::read_effective_config_after_overridden_write 和 App::sync_memory_state_from_effective_config。

调用图:调用 6 个内部函数(read_effective_config_after_overridden_write, sync_memory_state_from_effective_config, overridden_write_message, request_handle, build_memory_settings_edits, write_config_batch);被 1 处调用(update_memory_settings_with_app_server);外部调用 3 个(format!, error!, warn!)。

App::update_memory_settings_with_app_server666–701 ↗
async fn update_memory_settings_with_app_server(
        &mut self,
        app_server: &mut AppServerSession,
        use_memories: bool,
        generate_memories: bool,
    )

作用:在保存记忆设置后,把当前线程的记忆模式也同步给服务器。这样不必等下一次新会话才生效。

数据流:输入是 app server 和两个记忆开关 → 它先调用 App::update_memory_settings 保存设置;如果 generate_memories 发生变化并且有当前线程,就调用服务器设置该线程的记忆模式 → 没有返回值,失败会在界面提示。

调用关系:它是记忆设置菜单更完整的入口,把本地配置更新和服务器线程状态更新串起来。

调用图:调用 2 个内部函数(update_memory_settings, thread_memory_mode_set);外部调用 2 个(format!, error!)。

App::reset_memories_with_app_server703–716 ↗
async fn reset_memories_with_app_server(
        &mut self,
        app_server: &mut AppServerSession,
    )

作用:请求服务器清空本地记忆,并把结果告诉用户。适合用户想从头开始、不再保留旧记忆时使用。

数据流:输入是 app server → 它调用 memory_reset;失败时显示错误,成功时在聊天窗口显示“已重置本地记忆” → 没有返回值。

调用关系:它直接和 AppServerSession 交互,是记忆重置按钮或命令背后的执行函数。

调用图:调用 1 个内部函数(memory_reset);外部调用 2 个(format!, error!)。

App::reasoning_label718–723 ↗
fn reasoning_label(reasoning_effort: Option<&ReasoningEffortConfig>) -> String

作用:把推理强度设置转成给人看的文字。没有设置或明确为 None 时,都显示 default。

数据流:输入是可选的 ReasoningEffortConfig → 它匹配 None、None 档或具体档位 → 输出一个 String 标签。

调用关系:它是显示用的小工具;App::reasoning_label_for 会进一步按模型决定是否使用这个标签。

App::reasoning_label_for725–730 ↗
fn reasoning_label_for(
        model: &str,
        reasoning_effort: Option<&ReasoningEffortConfig>,
    ) -> Option<String>

作用:按模型名决定要不要显示推理强度标签。某些 codex-auto- 开头的自动模型不显示这个标签。

数据流:输入是模型名和可选推理强度 → 如果模型名不是 codex-auto- 开头,就调用 App::reasoning_label 生成标签;否则返回 None → 输出可选字符串。

调用关系:它包装 App::reasoning_label,用在需要展示模型设置但又要隐藏自动模型细节的地方。

App::token_usage732–734 ↗
fn token_usage(&self) -> crate::token_usage::TokenUsage

作用:取当前聊天窗口统计到的 token 用量。token 可以粗略理解为模型读写文字时按小片段计费或计量的单位。

数据流:输入是 App 自身 → 它从 chat_widget 读取 token_usage → 输出 TokenUsage。

调用关系:它是 App 对外暴露聊天窗口统计信息的薄封装,把调用方和 ChatWidget 的内部结构隔开。

App::on_update_reasoning_effort736–741 ↗
fn on_update_reasoning_effort(&mut self, effort: Option<ReasoningEffortConfig>)

作用:当用户更新推理强度时,同步到 App 配置和聊天窗口。这样新会话状态和当前界面显示保持一致。

数据流:输入是可选 ReasoningEffortConfig → 它把值写进 self.config.model_reasoning_effort,并通知 chat_widget 设置同样的推理强度 → 没有返回值。

调用关系:它是推理强度设置变化的响应函数,直接更新两份运行时状态。

App::on_update_personality743–746 ↗
fn on_update_personality(&mut self, personality: Personality)

作用:当用户更新助手性格时,把选择同步到应用配置和聊天窗口。性格比如 Friendly、Pragmatic 这类展示和行为偏好。

数据流:输入是 Personality → 它写入 self.config.personality,并调用 chat_widget.set_personality → 没有返回值。

调用关系:它是性格设置变化的响应函数,和 App::personality_label 这样的显示辅助配合使用。

App::sync_tui_theme_selection748–751 ↗
fn sync_tui_theme_selection(&mut self, name: String)

作用:同步用户选择的 TUI 主题。TUI 是终端用户界面,也就是命令行里的图形化界面。

数据流:输入是主题名称字符串 → 它把名称写进 self.config.tui_theme,并把同样的值交给聊天窗口 → 没有返回值。

调用关系:主题选择界面会调用它;真正恢复高亮主题由 App::restore_runtime_theme_from_config 处理。

App::sync_tui_pet_selection754–757 ↗
fn sync_tui_pet_selection(&mut self, pet: String)

作用:在测试构建中同步用户选择的 TUI 小宠物。它保证 App 和聊天窗口看到的是同一个宠物 ID。

数据流:输入是宠物 ID 字符串 → 它写入 self.config.tui_pet,并通知 chat_widget 设置同样值 → 没有返回值。

调用关系:它只在测试配置下编译,相关测试会用它验证宠物选择同步是否正确。

App::sync_tui_pet_disabled759–763 ↗
fn sync_tui_pet_disabled(&mut self)

作用:把 TUI 小宠物设置成禁用状态。它用统一的禁用 ID,而不是让各处自己猜该写什么。

数据流:没有外部输入 → 它读取 DISABLED_PET_ID,写进 self.config.tui_pet,并同步给聊天窗口 → 没有返回值。

调用关系:宠物关闭操作会用它;测试也验证它会同时更新 App 和 ChatWidget。

App::restore_runtime_theme_from_config765–781 ↗
fn restore_runtime_theme_from_config(&self)

作用:根据配置恢复代码高亮主题。配置里有主题就用配置,没有或找不到就退回自适应默认主题。

数据流:输入是 App 当前配置 → 它先尝试按 tui_theme 名称解析主题;成功就设置全局语法高亮主题;失败则解析自适应默认主题并设置 → 没有返回值,但会改变运行时渲染主题。

调用关系:它调用 render::highlight 里的主题解析和设置函数,通常在启动或配置恢复后让界面颜色真正生效。

调用图:调用 3 个内部函数(adaptive_default_theme_name, resolve_theme_by_name, set_syntax_theme)。

App::personality_label783–789 ↗
fn personality_label(personality: Personality) -> &'static str

作用:把 Personality 枚举转成固定英文标签,方便界面显示。

数据流:输入是 Personality → 它匹配 None、Friendly、Pragmatic → 输出对应的静态字符串。

调用关系:它是显示层的小工具,配合 App::on_update_personality 所维护的性格设置使用。

App::sync_feature_state_from_effective_config791–839 ↗
fn sync_feature_state_from_effective_config(
        &mut self,
        effective_config: &ConfigReadResponse,
        feature_updates: &[(Feature, bool)],
    )

作用:当功能开关写入被更高优先级配置覆盖时,把界面和内存改成真正生效的状态。它防止用户看到一个并没有生效的开关状态。

数据流:输入是 ConfigReadResponse 和刚尝试更新的功能列表 → 它逐个从 effective config 读实际开关值,更新 self.config.features 和聊天窗口;还会同步审批人和审批策略 → 没有返回值,但会改内存和界面。

调用关系:App::update_feature_flags 在检测到 OkOverridden 后调用它;它依赖 feature_enabled_from_effective_config、approvals_reviewer_from_effective_config 和 approval_policy_from_effective_config 取值。

调用图:调用 4 个内部函数(set_approvals_reviewer_in_app_and_widget, approval_policy_from_effective_config, approvals_reviewer_from_effective_config, feature_enabled_from_effective_config);被 1 处调用(update_feature_flags);外部调用 3 个(iter, format!, warn!)。

App::sync_auto_review_runtime_state_from_effective_config841–911 ↗
async fn sync_auto_review_runtime_state_from_effective_config(
        &mut self,
        effective_config: &ConfigReadResponse,
        feature_updates: &[(Feature, bool)],
    )

作用:在“自动审核/Approve for me”功能写入被覆盖后,尽量把当前运行会话同步到真正生效的自动审核状态。

数据流:输入是 effective config 和功能更新列表 → 它确认 GuardianApproval 真的启用且沙盒是 workspace-write,然后设置内置权限档案、更新聊天窗口、保存运行时覆盖,并向当前线程提交上下文覆盖命令 → 没有返回值。

调用关系:App::update_feature_flags 在覆盖写入场景调用它;它会用 App::try_set_builtin_active_permission_profile_on_config,并可能触发 note_active_thread_outbound_op 和 refresh_pending_thread_approvals。

调用图:调用 6 个内部函数(from, active, from_config, try_set_builtin_active_permission_profile_on_config, sandbox_mode_from_effective_config, op_can_change_pending_replay_state);被 1 处调用(update_feature_flags);外部调用 4 个(override_turn_context, iter, format!, warn!)。

App::sync_memory_state_from_effective_config913–934 ↗
fn sync_memory_state_from_effective_config(
        &mut self,
        effective_config: &ConfigReadResponse,
    ) -> bool

作用:当记忆设置写入被覆盖时,用真正生效的配置修正内存和界面。

数据流:输入是 ConfigReadResponse → 它从 additional.memories 里解析记忆设置;缺字段就沿用当前值;然后更新 self.config.memories 和聊天窗口 → 返回 true 表示同步成功,false 表示没拿到可用记忆配置。

调用关系:App::update_memory_settings 在写入状态为 OkOverridden 时调用它;它依赖 memories_from_effective_config 解析数据。

调用图:调用 1 个内部函数(memories_from_effective_config);被 1 处调用(update_memory_settings);外部调用 1 个(warn!)。

App::sync_windows_sandbox_after_overridden_write937–962 ↗
async fn sync_windows_sandbox_after_overridden_write(
        &mut self,
        app_server: &mut AppServerSession,
        write_response: &ConfigWriteResponse,
    )

作用:在 Windows 沙盒设置写入被覆盖时,把界面修正成真正生效的 Windows 沙盒模式,并通知当前会话。

数据流:输入是 app server 和写配置响应 → 它先显示“保存但未应用”的原因,再读取 effective config,取出 Windows 沙盒模式,更新 App 配置和聊天窗口,最后传播到当前线程上下文 → 没有返回值。

调用关系:Windows 沙盒设置流程会调用它;它使用 App::read_effective_config_after_overridden_write、windows_sandbox_mode_from_effective_config 和 App::propagate_windows_sandbox_turn_context。

调用图:调用 4 个内部函数(propagate_windows_sandbox_turn_context, read_effective_config_after_overridden_write, overridden_write_message, windows_sandbox_mode_from_effective_config);外部调用 2 个(format!, warn!)。

App::propagate_windows_sandbox_turn_context964–984 ↗
fn propagate_windows_sandbox_turn_context(&self)

作用:把当前 Windows 沙盒级别发送给正在运行的会话。这样沙盒改动能影响当前线程,而不只是以后新开的线程。

数据流:输入是 App 当前配置 → 在 Windows 平台上,它从配置算出沙盒级别,构造 override_turn_context 命令并通过 app_event_tx 发出 → 没有返回值。

调用关系:App::update_feature_flags 和 App::sync_windows_sandbox_after_overridden_write 在沙盒相关设置变化后调用它。

调用图:调用 1 个内部函数(level_from_config);被 2 处调用(sync_windows_sandbox_after_overridden_write, update_feature_flags);外部调用 2 个(override_turn_context, CodexOp)。

overridden_write_message987–993 ↗
fn overridden_write_message(write_response: &ConfigWriteResponse) -> &str

作用:从配置写入响应里取出“为什么被覆盖”的说明。没有具体说明时,返回一段通用解释。

数据流:输入是 ConfigWriteResponse → 它查看 overridden_metadata.message;有就返回那段文本,没有就返回默认消息 → 输出字符串切片。

调用关系:App::update_feature_flags、App::update_memory_settings 和 App::sync_windows_sandbox_after_overridden_write 用它生成给用户看的提示。

调用图:被 3 处调用(sync_windows_sandbox_after_overridden_write, update_feature_flags, update_memory_settings)。

feature_enabled_from_effective_config995–1008 ↗
fn feature_enabled_from_effective_config(
    effective_config: &ConfigReadResponse,
    feature: Feature,
) -> bool

作用:从最终生效配置里读某个实验功能是否开启。如果配置里没写,就使用该功能自己的默认值。

数据流:输入是 ConfigReadResponse 和 Feature → 它从 additional.features 解析功能表,查 feature.key 对应值 → 输出 bool。

调用关系:App::sync_feature_state_from_effective_config 用它逐个修正功能开关状态。

调用图:被 1 处调用(sync_feature_state_from_effective_config)。

approvals_reviewer_from_effective_config1010–1017 ↗
fn approvals_reviewer_from_effective_config(
    effective_config: &ConfigReadResponse,
) -> Option<ApprovalsReviewer>

作用:从最终生效配置里读取审批人设置,并转换成核心程序使用的类型。

数据流:输入是 ConfigReadResponse → 它查看 config.approvals_reviewer;有值就转成 core 类型,没有就返回 None → 输出可选 ApprovalsReviewer。

调用关系:App::sync_feature_state_from_effective_config 用它在覆盖写入后同步审批人。

调用图:被 1 处调用(sync_feature_state_from_effective_config)。

approval_policy_from_effective_config1019–1023 ↗
fn approval_policy_from_effective_config(
    effective_config: &ConfigReadResponse,
) -> Option<AskForApproval>

作用:从最终生效配置里读取审批策略。

数据流:输入是 ConfigReadResponse → 它直接取 config.approval_policy → 输出可选 AskForApproval。

调用关系:App::sync_feature_state_from_effective_config 用它在覆盖写入后同步审批策略。

调用图:被 1 处调用(sync_feature_state_from_effective_config)。

sandbox_mode_from_effective_config1025–1029 ↗
fn sandbox_mode_from_effective_config(
    effective_config: &ConfigReadResponse,
) -> Option<AppServerSandboxMode>

作用:从最终生效配置里读取沙盒模式。沙盒模式决定程序能不能写工作区、能不能访问更广范围。

数据流:输入是 ConfigReadResponse → 它直接取 config.sandbox_mode → 输出可选 AppServerSandboxMode。

调用关系:App::sync_auto_review_runtime_state_from_effective_config 用它确认自动审核所需的 workspace-write 沙盒是否真的生效。

调用图:被 1 处调用(sync_auto_review_runtime_state_from_effective_config)。

memories_from_effective_config1031–1037 ↗
fn memories_from_effective_config(effective_config: &ConfigReadResponse) -> Option<MemoriesToml>

作用:从最终生效配置的附加字段里解析记忆设置。

数据流:输入是 ConfigReadResponse → 它查 additional.memories,并用 JSON 反序列化成 MemoriesToml → 输出可选 MemoriesToml。

调用关系:App::sync_memory_state_from_effective_config 用它取得被覆盖后的真实记忆开关。

调用图:被 1 处调用(sync_memory_state_from_effective_config)。

features_toml_from_json1039–1041 ↗
fn features_toml_from_json(value: &serde_json::Value) -> Option<FeaturesToml>

作用:把 JSON 形式的 features 配置转成程序更好用的 FeaturesToml 结构。

数据流:输入是 serde_json::Value → 它克隆这段 JSON 并尝试反序列化 → 成功输出 Some(FeaturesToml),失败输出 None。

调用关系:feature_enabled_from_effective_config 通过它解析 effective config 里的功能开关表。

调用图:外部调用 2 个(clone, from_value)。

windows_sandbox_mode_from_effective_config1044–1053 ↗
fn windows_sandbox_mode_from_effective_config(
    effective_config: &ConfigReadResponse,
) -> Option<codex_config::types::WindowsSandboxModeToml>

作用:在 Windows 上,从最终生效配置里读取 Windows 沙盒模式。

数据流:输入是 ConfigReadResponse → 它从 additional.windows 解析 WindowsToml,再取其中 sandbox 字段 → 输出可选 WindowsSandboxModeToml。

调用关系:App::sync_windows_sandbox_after_overridden_write 用它修正 Windows 沙盒界面和运行时状态。

调用图:被 1 处调用(sync_windows_sandbox_after_overridden_write)。

windows_toml_from_json1056–1058 ↗
fn windows_toml_from_json(value: &serde_json::Value) -> Option<WindowsToml>

作用:把 JSON 形式的 windows 配置转成 WindowsToml 结构。

数据流:输入是 serde_json::Value → 它克隆 JSON 并尝试反序列化 → 成功返回 Some(WindowsToml),失败返回 None。

调用关系:windows_sandbox_mode_from_effective_config 用它解析 effective config 里的 Windows 配置块。

调用图:外部调用 2 个(clone, from_value)。

tests::update_reasoning_effort_updates_collaboration_mode1072–1087 ↗
async fn update_reasoning_effort_updates_collaboration_mode()

作用:测试更新推理强度时,聊天窗口和 App 配置都会变成新值。

数据流:输入是测试创建的 App → 它先设置旧推理强度,再调用 App::on_update_reasoning_effort 改成 High → 断言聊天窗口和配置都保存了 High。

调用关系:它验证 App::on_update_reasoning_effort 的同步行为,防止以后只改一边。

调用图:调用 1 个内部函数(make_test_app);外部调用 1 个(assert_eq!)。

tests::refresh_in_memory_config_from_disk_loads_latest_apps_state1090–1126 ↗
async fn refresh_in_memory_config_from_disk_loads_latest_apps_state() -> Result<()>

作用:测试刷新磁盘配置能读到最新的 apps 开关状态。

数据流:输入是临时 codex_home 和测试 App → 它先确认内存里没有某 app 设置,再把禁用信息写进 config.toml,调用刷新 → 断言内存配置读到了禁用状态。

调用关系:它覆盖 App::refresh_in_memory_config_from_disk,确保磁盘修改能进入运行时 Config。

调用图:调用 1 个内部函数(make_test_app);外部调用 4 个(assert_eq!, for_config, tempdir, vec!)。

tests::refresh_in_memory_config_from_disk_keeps_cloud_requirements_for_thread_transitions1131–1186 ↗
async fn refresh_in_memory_config_from_disk_keeps_cloud_requirements_for_thread_transitions() -> Result<()>

作用:测试刷新配置时不会丢掉云端或企业要求。比如企业要求只能用某种审批策略,刷新后仍要保留。

数据流:输入是带云端要求的测试配置和临时 config.toml → 它刷新内存配置,并检查 apps 设置被读到,同时云端要求和审批策略仍然存在 → 测试返回成功或错误。

调用关系:它保护 App::refresh_in_memory_config_from_disk 在 /new、/clear、/fork 等线程切换前的行为。

调用图:调用 3 个内部函数(without_managed_config_for_tests, loader_with_enterprise_requirement, make_test_app);外部调用 5 个(assert_eq!, default, format!, write, tempdir)。

tests::refresh_in_memory_config_from_disk_best_effort_keeps_current_config_on_error1189–1202 ↗
async fn refresh_in_memory_config_from_disk_best_effort_keeps_current_config_on_error() -> Result<()>

作用:测试尽力刷新遇到坏配置文件时,不会破坏当前内存配置。

数据流:输入是测试 App 和写坏的 config.toml → 它保存原配置,调用 best-effort 刷新 → 断言 self.config 仍等于原配置。

调用关系:它验证 App::refresh_in_memory_config_from_disk_best_effort 的容错承诺。

调用图:调用 1 个内部函数(make_test_app);外部调用 3 个(assert_eq!, write, tempdir)。

tests::refresh_in_memory_config_from_disk_uses_active_chat_widget_cwd1205–1242 ↗
async fn refresh_in_memory_config_from_disk_uses_active_chat_widget_cwd() -> Result<()>

作用:测试刷新配置时使用当前聊天线程的 cwd,而不是 App 里可能过时的 cwd。

数据流:输入是测试 App 和一个新临时目录 → 它把聊天窗口切到新 cwd,调用刷新 → 断言 App 配置的 cwd 跟聊天窗口一致。

调用关系:它验证 App::refresh_in_memory_config_from_disk 内部调用 App::rebuild_config_for_cwd 时选目录的规则。

调用图:调用 3 个内部函数(read_only, new, make_test_app);外部调用 4 个(new, new, assert_eq!, tempdir)。

tests::refresh_in_memory_config_from_disk_updates_resize_reflow_config1245–1264 ↗
async fn refresh_in_memory_config_from_disk_updates_resize_reflow_config() -> Result<()>

作用:测试刷新配置能更新终端窗口大小变化时的重排设置。

数据流:输入是写了 terminal_resize_reflow_max_rows 的临时配置文件 → 它调用刷新 → 断言内存配置里的最大行数变成 9000。

调用关系:它给 App::refresh_in_memory_config_from_disk 补充覆盖,确保 TUI 显示相关配置也会被刷新。

调用图:调用 1 个内部函数(make_test_app);外部调用 3 个(assert_eq!, write, tempdir)。

tests::overridden_disabled_guardian_does_not_apply_auto_review_companions1267–1304 ↗
async fn overridden_disabled_guardian_does_not_apply_auto_review_companions() -> Result<()>

作用:测试当最终配置把 GuardianApproval 关掉时,不会顺手应用自动审核的配套审批策略。

数据流:输入是一个伪造的 effective config,其中审批策略像自动审核,但 guardian_approval 为 false → 它调用同步功能状态 → 断言功能关闭、审批人回到 User、原审批策略没被改。

调用关系:它验证 App::sync_feature_state_from_effective_config 对覆盖写入的边界处理,防止错误启用自动审核伴随设置。

调用图:调用 1 个内部函数(make_test_app);外部调用 4 个(assert!, assert_eq!, from_value, json!)。

tests::rebuild_config_for_resume_or_fallback_uses_current_config_on_same_cwd_error1307–1322 ↗
async fn rebuild_config_for_resume_or_fallback_uses_current_config_on_same_cwd_error() -> Result<()>

作用:测试恢复同一目录会话时,如果配置重建失败,会退回当前配置。

数据流:输入是测试 App、坏 config.toml 和当前 cwd → 它调用 App::rebuild_config_for_resume_or_fallback → 断言返回的配置就是原当前配置。

调用关系:它验证同目录恢复的安全兜底逻辑。

调用图:调用 1 个内部函数(make_test_app);外部调用 3 个(assert_eq!, write, tempdir)。

tests::rebuild_config_for_resume_or_fallback_errors_when_cwd_changes1325–1340 ↗
async fn rebuild_config_for_resume_or_fallback_errors_when_cwd_changes() -> Result<()>

作用:测试恢复不同目录会话时,如果配置重建失败,应该返回错误而不是乱用旧目录配置。

数据流:输入是测试 App、坏 config.toml、当前 cwd 和另一个 cwd → 它调用 App::rebuild_config_for_resume_or_fallback → 断言结果是错误。

调用关系:它和同目录兜底测试成对出现,验证 App::rebuild_config_for_resume_or_fallback 的目录判断。

调用图:调用 1 个内部函数(make_test_app);外部调用 3 个(assert!, write, tempdir)。

tests::sync_tui_theme_selection_updates_chat_widget_config_copy1343–1353 ↗
async fn sync_tui_theme_selection_updates_chat_widget_config_copy()

作用:测试选择主题时,App 配置和聊天窗口配置副本都会更新。

数据流:输入是测试 App → 它调用 App::sync_tui_theme_selection 写入 dracula → 断言两处 tui_theme 都是 dracula。

调用关系:它验证主题同步函数不会只改一边。

调用图:调用 1 个内部函数(make_test_app);外部调用 1 个(assert_eq!)。

tests::sync_tui_pet_selection_updates_chat_widget_config_copy1356–1366 ↗
async fn sync_tui_pet_selection_updates_chat_widget_config_copy()

作用:测试选择 TUI 小宠物时,App 和聊天窗口的配置副本保持一致。

数据流:输入是测试 App → 它调用 App::sync_tui_pet_selection 写入 chefito → 断言两处 tui_pet 都是 chefito。

调用关系:它验证测试专用的宠物选择同步函数。

调用图:调用 1 个内部函数(make_test_app);外部调用 1 个(assert_eq!)。

tests::sync_tui_pet_disabled_updates_chat_widget_config_copy1369–1382 ↗
async fn sync_tui_pet_disabled_updates_chat_widget_config_copy()

作用:测试禁用 TUI 小宠物时,App 和聊天窗口都会写入统一的禁用 ID。

数据流:输入是测试 App → 它调用 App::sync_tui_pet_disabled → 断言两处 tui_pet 都等于 DISABLED_PET_ID。

调用关系:它验证关闭宠物的同步逻辑,防止界面和 App 状态不一致。

调用图:调用 1 个内部函数(make_test_app);外部调用 1 个(assert_eq!)。

tui/src/service_tier_resolution.rs源码 ↗
domain_logicconfig load / session setup / model change

这个文件像一个“订票规则检查员”:用户可能指定了想坐的舱位,模型本身也可能有默认舱位,但不是每个模型都支持每种舱位。这里的“服务档位”可以理解成请求模型时使用的速度或服务等级。代码先看功能开关 FastMode 是否打开;没打开就完全不处理档位。打开后,它会先读用户配置,如果用户没显式配置,但以前选择过退出快速默认值,也会补一个特殊的默认请求值。接着,它会找到当前模型的预设信息,检查这个档位是不是模型支持的。支持就用,不支持就丢掉;如果用户没配置,就尽量用模型自己的默认档位。最后还有一个给核心层用的函数,会把结果包装成“要不要更新、更新成什么”的形式,确保核心层收到的是安全、合理的值。

函数细节4
configured_service_tier6–11 ↗
fn configured_service_tier(config: &Config) -> Option<String>

作用:这个函数只看用户配置,算出“用户这边想要的服务档位”。如果用户明确写了档位,就用它;如果用户没有写,但有一个旧的退出默认快速模式标记,就返回一个特殊的默认请求值。

数据流:进去的是一份 Config 配置。它先读取 config.service_tier;如果这里有值,就复制出来返回。否则它再看 config.notices.fast_default_opt_out 是否等于 true;如果是,就生成 SERVICE_TIER_DEFAULT_REQUEST_VALUE 这个默认请求字符串。最后出来的是 Option<String>:有可能是某个档位,也可能是什么都没有。它不改配置,只做读取和判断。

调用关系:它是后续判断的第一步,被 effective_service_tier 调用。effective_service_tier 需要先知道用户有没有表达偏好,然后才会结合模型支持情况决定最终能不能用。

调用图:被 1 处调用(effective_service_tier)。

effective_service_tier13–36 ↗
fn effective_service_tier(
    config: &Config,
    model: &str,
    models: &[ModelPreset],
) -> Option<String>

作用:这个函数算出“真正应该生效的服务档位”。它会同时考虑功能开关、用户配置、当前模型、模型支持哪些档位,以及模型自己的默认档位。

数据流:进去的是 Config、当前模型名字 model,以及一组 ModelPreset 模型预设。它先检查 FastMode 功能是否开启;没开就直接返回 None,表示不要使用服务档位。然后它调用 configured_service_tier 拿到用户配置的档位,再在 models 里找当前模型的预设。如果找不到模型,就只能返回用户配置的结果。如果找到了模型,它会判断:特殊默认请求值可以直接保留;用户配置的普通档位必须经过 model_supports_service_tier 检查,模型支持才返回;不支持就返回 None;如果用户没配置,则尝试使用模型预设里的 default_service_tier,但也要确认模型支持。最终出来的是 Option<String>,表示最终可用的档位,或没有可用档位。

调用关系:这是本文件的核心判断函数。它会调用 configured_service_tier 读取用户意图,也会调用 model_supports_service_tier 检查模型能力。外部在创建带操作目标的新配置、刷新当前有效服务档位、以及准备给核心层的更新值时,会调用它来得到最终答案。

调用图:调用 2 个内部函数(configured_service_tier, model_supports_service_tier);被 3 处调用(new_with_op_target, refresh_effective_service_tier, service_tier_update_for_core);外部调用 1 个(iter)。

service_tier_update_for_core38–57 ↗
fn service_tier_update_for_core(
    config: &Config,
    model: &str,
    models: &[ModelPreset],
) -> Option<Option<String>>

作用:这个函数把“有效服务档位”的判断结果转换成核心层需要的更新格式。它不只是回答用哪个档位,还回答“要不要告诉核心层更新”。

数据流:进去的是 Config、当前模型名,以及模型预设列表。它先看 FastMode 是否开启;没开就返回 None,表示不用给核心层发服务档位更新。然后它调用 effective_service_tier。若算出了有效档位,就返回 Some(Some(档位)),意思是“需要更新,并把档位设成这个”。如果没算出有效档位,它会检查当前模型是否存在于预设列表里;如果模型都找不到,就返回 None,不乱发更新。如果模型存在但没有可用档位,它会返回 Some(Some(SERVICE_TIER_DEFAULT_REQUEST_VALUE)),让核心层回到特殊的默认请求值。

调用关系:它站在界面层和核心层之间,负责把本文件的判断结果翻译成核心层能理解的形式。它会调用 effective_service_tier,外部在生成带有效服务档位的会话配置时会用到它;调用图里也显示有同名调用关系,说明这段更新逻辑可能通过同名包装或转发路径被复用。

调用图:调用 1 个内部函数(effective_service_tier);被 2 处调用(session_config_with_effective_service_tier, service_tier_update_for_core);外部调用 1 个(iter)。

model_supports_service_tier59–64 ↗
fn model_supports_service_tier(model: &ModelPreset, service_tier: &str) -> bool

作用:这个函数检查某个模型是否支持指定的服务档位。它像是在模型说明书里查一眼:这个档位是不是列在可用清单里。

数据流:进去的是一个 ModelPreset 模型预设和一个 service_tier 字符串。它遍历 model.service_tiers 这个档位列表,检查是否有某一项的 id 等于传入的 service_tier。出来的是 bool:true 表示支持,false 表示不支持。它不修改任何东西。

调用关系:它是 effective_service_tier 的小检查工具。effective_service_tier 在决定是否接受用户配置档位、或者是否使用模型默认档位时,会把具体的支持性检查交给它。

调用图:被 1 处调用(effective_service_tier)。

tui/src/chatwidget/service_tiers.rs源码 ↗
domain_logic用户切换服务档位、刷新模型信息、发送对话前后

可以把这里想成聊天界面的“档位拨杆”。用户可能在配置里写了一个服务档位,也可能在界面里临时切换快速模式;同时,不同模型支持的档位还不一样。这个文件做的事,就是先查当前模型到底有哪些可用档位,再算出现在真正生效的档位,然后把这些信息同步到界面和后端核心。它还会判断什么时候能按快捷键切换快速模式:功能开关要打开,当前模型要支持快速档位,不能正有一轮对话在跑,界面也不能弹着窗口。用户切换后,它会更新本地配置、刷新界面上依赖模型和档位的内容,并发事件告诉核心“下一轮对话用这个档位”,还会发事件把选择保存下来。这样避免了一个常见问题:界面看起来切了档,但后端实际没切。

函数细节15
ChatWidget::set_service_tier14–18 ↗
fn set_service_tier(&mut self, service_tier: Option<String>)

作用:把聊天界面当前选择的服务档位写进配置,并重新计算真正生效的档位。别人通常在用户改了档位后调用它。

数据流:进去的是一个可能为空的档位字符串;它先把这个值放到界面的配置里,再调用 refresh_effective_service_tier 重新算实际要用的档位,最后刷新那些会受模型或档位影响的界面区域。出来没有返回值,但聊天界面的内部状态已经变了。

调用关系:它是内部更新档位的基础动作。set_service_tier_selection 在用户从界面选择档位后会先调用它,然后再继续通知核心和保存设置。

调用图:调用 1 个内部函数(refresh_effective_service_tier);被 1 处调用(set_service_tier_selection)。

ChatWidget::current_service_tier20–22 ↗
fn current_service_tier(&self) -> Option<&str>

作用:读取当前真正生效的服务档位。它回答的是“现在实际会用哪个档位”,不是只看用户配置里写了什么。

数据流:进去不需要额外输入;它读取 effective_service_tier 这个已经算好的内部值,并把里面的字符串借出来返回。如果当前没有明确档位,就返回空。

调用关系:切换按钮需要知道“现在是不是已经在这个档位上”,所以 toggle_fast_mode_from_uitoggle_service_tier_from_ui 会用它来决定下一步是切到目标档位,还是退回默认档位。

调用图:被 2 处调用(toggle_fast_mode_from_ui, toggle_service_tier_from_ui)。

ChatWidget::configured_service_tier24–26 ↗
fn configured_service_tier(&self) -> Option<String>

作用:读取用户配置里直接保存的服务档位。它关心的是“用户选过什么”,不负责判断这个选择对当前模型是否真的有效。

数据流:进去不需要额外输入;它从配置里复制一份 service_tier 返回。返回值可能是某个档位字符串,也可能为空。

调用关系:它提供一个安全的读取口,给需要查看原始配置选择的地方用。和 current_service_tier 不同,它不参与计算实际生效结果。

ChatWidget::service_tier_update_for_core28–34 ↗
fn service_tier_update_for_core(&self) -> Option<Option<String>>

作用:算出是否需要把服务档位变化告诉后端核心。后端核心是真正执行对话请求的部分,所以它必须拿到正确档位。

数据流:进去不需要额外输入;它读取当前配置、当前模型,以及模型目录里的模型列表,然后交给外部的 service_tier_update_for_core 规则函数判断。出来的是一个可能存在的更新指令:可能表示要设置某个档位,也可能表示要清掉档位,或者根本不用更新。

调用关系:它位于界面状态和后端核心之间,像翻译员一样把界面的配置和模型能力整理成核心能理解的更新。具体判断逻辑交给 service_tier_resolution::service_tier_update_for_core

调用图:调用 1 个内部函数(service_tier_update_for_core)。

ChatWidget::should_show_fast_status36–41 ↗
fn should_show_fast_status(&self, model: &str, service_tier: Option<&str>) -> bool

作用:判断界面上要不要显示“快速模式/快速档位”的状态提示。它避免在模型不支持、用户没账号或当前不是快速档位时误导用户。

数据流:进去的是模型名和当前服务档位;它检查档位是不是 Fast,当前模型是否支持这个档位,并确认用户有 ChatGPT 账号。只有这些条件都满足时,才返回 true。

调用关系:它给界面显示逻辑提供判断依据。判断模型是否支持档位时,它会用本文件里的模型档位查询能力。

ChatWidget::fast_mode_enabled43–45 ↗
fn fast_mode_enabled(&self) -> bool

作用:检查“快速模式”这个功能开关有没有打开。这个开关像总电源,没开的话界面就不应该让用户使用快速模式相关操作。

数据流:进去不需要额外输入;它读取配置里的功能开关列表,检查 FastMode 是否启用。出来是 true 或 false。

调用关系can_toggle_fast_mode_from_keybinding 用它判断快捷键是否可用,sync_service_tier_commands 用它决定斜杠菜单里的服务档位命令是否启用。

调用图:被 2 处调用(can_toggle_fast_mode_from_keybinding, sync_service_tier_commands)。

ChatWidget::can_toggle_fast_mode_from_keybinding47–52 ↗
fn can_toggle_fast_mode_from_keybinding(&self) -> bool

作用:判断用户现在能不能通过快捷键切换快速模式。它把“功能开了没、模型支持没、当前能不能操作”这些条件一次性查清楚。

数据流:进去不需要额外输入;它先看快速模式功能开关是否打开,再看当前模型有没有快速档位,然后确认没有用户请求正在等待或运行,最后确认底部面板没有弹窗或对话框挡着。全部满足才返回 true。

调用关系:它会调用 fast_mode_enabledcurrent_model_fast_service_tier。快捷键处理逻辑在真正切换前会先问它,避免在不合适的时候硬切档位。

调用图:调用 2 个内部函数(current_model_fast_service_tier, fast_mode_enabled)。

ChatWidget::toggle_fast_mode_from_ui54–64 ↗
fn toggle_fast_mode_from_ui(&mut self)

作用:响应用户在界面里切换快速模式的动作。已经在快速档位时,它会退回默认;不在快速档位时,它会切到快速。

数据流:进去不需要额外输入;它先找当前模型支持的快速档位,找不到就什么也不做。找到了以后,它读取当前生效档位:如果已经是快速档位,就准备切回默认档位;否则准备切到快速档位。最后把新选择交给 set_service_tier_selection

调用关系:它是 UI 操作入口之一。它调用 current_model_fast_service_tier 找可用的快速档位,调用 current_service_tier 判断当前状态,再把真正的更新、通知和保存交给 set_service_tier_selection

调用图:调用 3 个内部函数(current_model_fast_service_tier, current_service_tier, set_service_tier_selection)。

ChatWidget::toggle_service_tier_from_ui66–73 ↗
fn toggle_service_tier_from_ui(&mut self, command: ServiceTierCommand)

作用:响应用户从界面选择某个具体服务档位的动作。用户点同一个档位时,它会理解为“取消这个选择,回到默认”。

数据流:进去的是一个服务档位命令,里面有档位 id、名字和说明;它比较当前生效档位和这个命令的 id。如果已经选中同一个档位,就改成默认档位;否则切到命令指定的档位。最后交给 set_service_tier_selection 执行。

调用关系:它处理普通服务档位菜单的点击。它自己只决定下一个档位是什么,实际更新界面状态、通知核心、保存设置都交给 set_service_tier_selection

调用图:调用 2 个内部函数(current_service_tier, set_service_tier_selection)。

ChatWidget::sync_service_tier_commands75–80 ↗
fn sync_service_tier_commands(&mut self)

作用:把当前模型可用的服务档位同步到底部面板的命令列表里。这样用户打开斜杠菜单时,看到的是当前模型真正能用的选项。

数据流:进去不需要额外输入;它先根据快速模式功能开关决定服务档位命令是否启用,再生成当前模型支持的档位命令列表,并塞到底部面板。没有返回值,但底部面板的可用命令会被更新。

调用关系:它是界面同步步骤。它调用 fast_mode_enabled 决定命令开关,调用 current_model_service_tier_commands 生成命令内容,然后把结果交给 bottom pane,也就是聊天界面底部输入区。

调用图:调用 2 个内部函数(current_model_service_tier_commands, fast_mode_enabled)。

ChatWidget::current_model_service_tier_commands82–104 ↗
fn current_model_service_tier_commands(&self) -> Vec<ServiceTierCommand>

作用:把当前模型支持的服务档位转换成界面能显示、能点击的命令。没有它,菜单就不知道该给用户列哪些档位。

数据流:进去不需要额外输入;它读取当前模型名,再从模型目录里找对应模型。找到后,把模型的每个服务档位转换成 ServiceTierCommand,包括 id、展示名和说明;如果模型目录读不到或找不到模型,就返回空列表。

调用关系sync_service_tier_commands 用它刷新菜单,current_model_fast_service_tier 用它从所有档位里挑出快速档位。它是模型目录和 UI 命令之间的转换层。

调用图:被 2 处调用(current_model_fast_service_tier, sync_service_tier_commands)。

ChatWidget::set_service_tier_selection106–125 ↗
fn set_service_tier_selection(&mut self, service_tier: Option<String>)

作用:完成一次服务档位选择的全套后续动作:改本地状态、通知后端核心、保存用户选择。它是切换档位时真正“落地”的地方。

数据流:进去的是一个可能为空的服务档位;它先调用 set_service_tier 更新界面配置和生效档位。接着发出一个 CodexOp 事件,里面通过 override_turn_context 告诉核心下一轮对话要用的新服务档位。最后再发一个保存事件,把这个选择持久化。出来没有返回值,但状态、核心上下文和保存请求都会被更新或发出。

调用关系toggle_fast_mode_from_uitoggle_service_tier_from_ui 都会把决定好的新档位交给它。它再向外部事件系统发送命令,其中 override_turn_context 负责构造“覆盖本轮上下文”的请求,CodexOp 把这个请求包装成核心操作事件。

调用图:调用 1 个内部函数(set_service_tier);被 2 处调用(toggle_fast_mode_from_ui, toggle_service_tier_from_ui);外部调用 2 个(override_turn_context, CodexOp)。

ChatWidget::model_supports_service_tier127–143 ↗
fn model_supports_service_tier(&self, model: &str, service_tier: &str) -> bool

作用:检查某个模型是否支持某个服务档位。它防止界面把一个模型根本不能用的档位显示成可用状态。

数据流:进去的是模型名和服务档位 id;它读取模型目录,找到同名模型后,检查这个模型的服务档位列表里有没有相同 id。找到就返回 true,任何读取失败、找不到模型或找不到档位的情况都返回 false。

调用关系:它是档位能力检查的小工具。should_show_fast_status 需要用它确认当前模型真的支持快速档位,然后才允许显示快速状态。

ChatWidget::current_model_fast_service_tier145–149 ↗
fn current_model_fast_service_tier(&self) -> Option<ServiceTierCommand>

作用:找出当前模型的“快速”服务档位。不同模型的档位来自模型目录,所以不能凭空假设一定存在。

数据流:进去不需要额外输入;它先拿到当前模型的所有服务档位命令,再从里面找名字等于 fast 的那一项,大小写不同也算匹配。找到就返回这个档位命令,找不到就返回空。

调用关系can_toggle_fast_mode_from_keybinding 用它判断快捷键是否有意义,toggle_fast_mode_from_ui 用它拿到真正要切换到的快速档位。它本身依赖 current_model_service_tier_commands 提供候选列表。

调用图:调用 1 个内部函数(current_model_service_tier_commands);被 2 处调用(can_toggle_fast_mode_from_keybinding, toggle_fast_mode_from_ui)。

ChatWidget::refresh_effective_service_tier151–157 ↗
fn refresh_effective_service_tier(&mut self)

作用:重新计算当前真正生效的服务档位。它把用户配置、当前模型和模型支持情况放在一起判断,得出最终答案。

数据流:进去不需要额外输入;它读取配置、当前模型和模型目录列表,然后交给外部的 effective_service_tier 规则函数计算。算出的结果会写回 effective_service_tier 字段。没有返回值,但聊天界面的实际档位状态会更新。

调用关系set_service_tier 在配置里的档位变更后会调用它。具体规则不写在这里,而是交给 service_tier_resolution::effective_service_tier,这样界面只负责刷新状态,不重复实现复杂判断。

调用图:调用 1 个内部函数(effective_service_tier);被 1 处调用(set_service_tier)。

tui/src/chatwidget/settings.rs源码 ↗
orchestrationcross-cutting

可以把这个文件理解成 ChatWidget 的“控制面板接线盒”。用户在界面里切模型、开关功能、改审批权限,或者后端通知某个会话设置变了,都会落到这里。它先改 ChatWidget 里保存的配置副本,再顺手刷新页眉、底部状态栏、输入框能力、协作模式提示等可见部分。这里还处理“协作模式”,比如普通模式和 Plan 模式:同一个聊天可以临时套上一层模式遮罩,改变模型、推理强度或说明文字。文件里还有一些保护性行为,比如模型信息读不到时默认允许图片输入,避免临时故障把用户卡死;切到 Plan 模式后会隐藏提示,避免一直打扰用户。总体上,它不是在真正联网聊天,而是在保证“当前会话该怎么聊”这套状态准确、同步、可显示。

函数细节61
ChatWidget::set_approval_policy8–19 ↗
fn set_approval_policy(&mut self, policy: AskForApproval)

作用:设置当前聊天的审批策略,也就是哪些操作需要先问用户。这样可以控制助手执行敏感动作前是不是必须停下来确认。

数据流:传入一个审批策略 → 转成核心配置能懂的格式并写进聊天配置 → 成功后刷新状态显示,失败时只记一条警告,不让界面崩掉。

调用关系:它会在同步线程设置时被 ChatWidget::apply_thread_settings 调用,用来把服务器发来的审批要求落到本地界面状态里。

调用图:调用 1 个内部函数(to_core);被 1 处调用(apply_thread_settings);外部调用 1 个(warn!)。

ChatWidget::set_permission_profile_from_session_snapshot22–31 ↗
fn set_permission_profile_from_session_snapshot(
        &mut self,
        snapshot: PermissionProfileSnapshot,
    ) -> ConstraintResult<()>

作用:用会话快照里的权限配置更新当前聊天。它适合把某个已经存在的会话权限原样恢复到界面里。

数据流:传入权限快照 → 交给配置里的 permissions 校验并保存 → 刷新状态显示,最后返回成功或约束错误。

调用关系:这是一个直接的设置入口,主要服务于外部已经拿到完整权限快照时的恢复或同步流程。

ChatWidget::set_permission_profile_with_active_profile33–48 ↗
fn set_permission_profile_with_active_profile(
        &mut self,
        profile: PermissionProfile,
        active_permission_profile: Option<ActivePermissionProfile>,
    ) -> ConstraintResult<()>

作用:用一个权限档案和可选的当前激活档案,组合出会话权限并应用。它让调用者不用自己拼完整快照。

数据流:传入权限档案和当前激活档案 → 先构造成会话快照 → 写入配置权限区 → 刷新状态显示并返回是否成功。

调用关系:它把快照构造这一步包起来,内部依赖 from_session_snapshot,适合用户切换权限档案时调用。

调用图:调用 1 个内部函数(from_session_snapshot)。

ChatWidget::set_permission_network50–55 ↗
fn set_permission_network(
        &mut self,
        network: Option<crate::legacy_core::config::NetworkProxySpec>,
    )

作用:设置当前权限配置里的网络代理或网络限制说明。它决定助手访问网络时该走什么网络配置。

数据流:传入一个可选的网络代理配置 → 直接写到 config.permissions.network → 不额外刷新界面。

调用关系:这是一个很薄的配置写入口,供其他设置流程在需要改网络权限时使用。

ChatWidget::set_windows_sandbox_mode58–66 ↗
fn set_windows_sandbox_mode(&mut self, mode: Option<WindowsSandboxModeToml>)

作用:设置 Windows 沙箱模式。沙箱可以理解成给程序划一个受限制的小房间,避免它随便碰系统资源。

数据流:传入可选的 Windows 沙箱模式 → 保存到权限配置 → 在 Windows 上再判断当前是否是降级受限沙箱,并更新底部提示。

调用关系:它只在 Windows 相关能力中真正影响界面;非 Windows 平台保留函数但可能不会用到。

调用图:外部调用 1 个(matches!)。

ChatWidget::set_feature_enabled69–117 ↗
fn set_feature_enabled(&mut self, feature: Feature, enabled: bool) -> bool

作用:打开或关闭某个运行时功能开关,比如插件、目标、图片提及、新版 mentions 等。它不只是改开关,还会把相关按钮和状态一起同步。

数据流:传入功能名和是否启用 → 尝试写入受约束的功能配置 → 再读取最终实际状态 → 按功能种类刷新对应命令、提示、模式指示或生命周期状态 → 返回最终是否启用。

调用关系:这是功能开关的总入口,会调用 sync_personality_command_enabled、sync_plugins_command_enabled、sync_goal_command_enabled、sync_mentions_v2_enabled 和 update_collaboration_mode_indicator 等函数,把配置变化传播到界面。

调用图:调用 5 个内部函数(sync_goal_command_enabled, sync_mentions_v2_enabled, sync_personality_command_enabled, sync_plugins_command_enabled, update_collaboration_mode_indicator);外部调用 2 个(matches!, warn!)。

ChatWidget::set_approvals_reviewer119–122 ↗
fn set_approvals_reviewer(&mut self, policy: ApprovalsReviewer)

作用:设置谁来审核需要批准的操作。它影响助手请求执行敏感动作时由哪类审核者来判断。

数据流:传入审核者策略 → 写入 config.approvals_reviewer → 刷新状态显示,让界面反映新的审核规则。

调用关系:它会被 ChatWidget::apply_thread_settings 调用,用于接收后端线程设置更新。

调用图:被 1 处调用(apply_thread_settings)。

ChatWidget::set_full_access_warning_acknowledged124–126 ↗
fn set_full_access_warning_acknowledged(&mut self, acknowledged: bool)

作用:记录用户是否已经看过并确认“完全访问权限”的警告。这样同一个警告就不用一直弹出来打扰用户。

数据流:传入是否确认 → 写入 notices.hide_full_access_warning → 没有返回额外结果。

调用关系:这是通知偏好的保存入口,通常由用户点掉警告后触发。

ChatWidget::set_world_writable_warning_acknowledged128–130 ↗
fn set_world_writable_warning_acknowledged(&mut self, acknowledged: bool)

作用:记录用户是否已经确认“目录任何人都可写”的安全警告。这个警告通常意味着项目目录可能不安全。

数据流:传入是否确认 → 写入 notices.hide_world_writable_warning → 后续显示逻辑可以据此决定是否隐藏。

调用关系:它和 ChatWidget::world_writable_warning_hidden 配套,一个负责写,一个负责读。

ChatWidget::world_writable_warning_hidden133–138 ↗
fn world_writable_warning_hidden(&self) -> bool

作用:查询那个“任何人都可写目录”的警告现在是否应该隐藏。没有保存过选择时,默认不隐藏。

数据流:读取 notices.hide_world_writable_warning → 如果里面有值就用它,没有就当作 false → 返回布尔值。

调用关系:它供界面判断安全提示是否还要出现,读取的是 set_world_writable_warning_acknowledged 保存的状态。

ChatWidget::set_plan_mode_reasoning_effort145–160 ↗
fn set_plan_mode_reasoning_effort(&mut self, effort: Option<ReasoningEffortConfig>)

作用:专门设置 Plan 模式下的推理强度。推理强度可以理解成让模型“想得更浅或更深”的档位。

数据流:传入可选推理强度 → 保存到配置 → 如果当前正处于 Plan 模式,就立刻更新当前模式遮罩;传 None 时恢复 Plan 预设默认值 → 最后刷新跟模型相关的界面。

调用关系:它会检查 collaboration_modes_enabled,并可能读取 plan_mask;最后调用 refresh_model_dependent_surfaces,确保页眉和状态栏马上变化。

调用图:调用 3 个内部函数(collaboration_modes_enabled, refresh_model_dependent_surfaces, plan_mask)。

ChatWidget::set_reasoning_effort166–181 ↗
fn set_reasoning_effort(&mut self, effort: Option<ReasoningEffortConfig>)

作用:设置非 Plan 模式下的普通推理强度。它故意不改正在使用的 Plan 模式,因为 Plan 有自己的专用规则。

数据流:传入可选推理强度 → 更新当前协作模式的基础设置 → 如果当前激活的不是 Plan 遮罩,也同步更新遮罩 → 刷新模型相关显示。

调用关系:它通过 collaboration_modes_enabled 判断是否要处理遮罩,并调用 refresh_model_dependent_surfaces 让界面同步。

调用图:调用 2 个内部函数(collaboration_modes_enabled, refresh_model_dependent_surfaces)。

ChatWidget::set_personality184–186 ↗
fn set_personality(&mut self, personality: Personality)

作用:设置当前聊天使用的人格风格。人格就是助手回答时的语气或行为倾向。

数据流:传入人格配置 → 保存到 config.personality → 不在这里做额外刷新。

调用关系:这是一个简单配置入口,通常和人格功能开关、底部命令是否可用一起配合。

ChatWidget::status_account_display188–190 ↗
fn status_account_display(&self) -> Option<&StatusAccountDisplay>

作用:取出当前账号在状态区该怎么显示的信息。没有账号显示信息时返回空。

数据流:读取 self.status_account_display → 转成可借用的可选引用 → 返回给调用者。

调用关系:它是状态展示用的读取口,和 update_account_state 写入的账号状态相对应。

ChatWidget::runtime_model_provider_base_url192–194 ↗
fn runtime_model_provider_base_url(&self) -> Option<&str>

作用:读取运行时模型服务商的基础网址。基础网址就是请求模型服务时使用的服务器根地址。

数据流:读取 runtime_model_provider_base_url → 如果有字符串就返回字符串引用,没有就返回空。

调用关系:这是给需要显示或使用模型服务地址的代码准备的只读入口。

ChatWidget::model_catalog197–199 ↗
fn model_catalog(&self) -> Arc<ModelCatalog>

作用:返回当前模型目录。模型目录就是一份“有哪些模型、各自支持什么能力”的清单。

数据流:读取 self.model_catalog → 克隆 Arc 引用计数指针,也就是多给一张共享使用权 → 返回给调用者。

调用关系:它常用于测试或其他组件查看模型能力,不会复制整份目录数据。

ChatWidget::current_plan_type201–203 ↗
fn current_plan_type(&self) -> Option<PlanType>

作用:读取当前账号或会话对应的套餐类型。套餐类型会影响可用能力或显示内容。

数据流:读取 self.plan_type → 原样返回可选值。

调用关系:它是账号状态的只读入口,数据由 update_account_state 更新。

ChatWidget::has_chatgpt_account205–207 ↗
fn has_chatgpt_account(&self) -> bool

作用:告诉调用者当前是否有 ChatGPT 账号。界面可以据此决定显示哪些账号相关入口。

数据流:读取 self.has_chatgpt_account → 返回 true 或 false。

调用关系:它读取的是 update_account_state 写入的账号标志。

ChatWidget::has_codex_backend_auth209–211 ↗
fn has_codex_backend_auth(&self) -> bool

作用:告诉调用者当前是否已经有 Codex 后端认证。没有认证时,一些后端功能不应该开放。

数据流:读取 self.has_codex_backend_auth → 返回 true 或 false。

调用关系:它和 update_account_state 配合,后者会在认证状态变化时更新底部命令可用性。

ChatWidget::update_account_state213–235 ↗
fn update_account_state(
        &mut self,
        status_account_display: Option<StatusAccountDisplay>,
        plan_type: Option<PlanType>,
        has_chatgpt_account: bool,
        has_codex_back

作用:更新账号显示、套餐和认证状态。账号变化时,它还会清掉一些旧的刷新请求,避免拿旧账号的数据继续刷新。

数据流:传入账号显示、套餐、两个认证标志 → 比较是否和旧状态不同 → 如果变了就清理待处理的 token 活动刷新和限流重置请求 → 保存新状态 → 更新底部连接器和 token 活动命令是否可用。

调用关系:这是账号状态同步入口,写入的值会被 status_account_display、current_plan_type、has_chatgpt_account 和 has_codex_backend_auth 读取。

ChatWidget::set_tui_theme238–240 ↗
fn set_tui_theme(&mut self, theme: Option<String>)

作用:设置终端界面的语法高亮主题。主题为空时表示不使用覆盖设置。

数据流:传入可选主题名 → 写入 config.tui_theme → 不额外返回结果。

调用关系:这是界面外观设置的写入口,供用户更改主题时使用。

ChatWidget::set_model243–256 ↗
fn set_model(&mut self, model: &str)

作用:切换当前聊天使用的模型。模型就是实际回答问题的 AI 引擎,不同模型能力和速度可能不同。

数据流:传入模型名 → 写入当前协作模式的基础设置 → 如果协作模式启用,也更新当前遮罩里的模型 → 刷新服务档位和所有依赖模型的界面。

调用关系:它会检查 collaboration_modes_enabled,并调用 refresh_model_dependent_surfaces;后续 current_model 会读到这次更新。

调用图:调用 2 个内部函数(collaboration_modes_enabled, refresh_model_dependent_surfaces)。

ChatWidget::current_model258–266 ↗
fn current_model(&self) -> &str

作用:算出当前真正生效的模型名。它会考虑协作模式遮罩,因为界面上选的模式可能临时覆盖基础模型。

数据流:先看协作模式是否启用 → 没启用就返回基础协作模式里的模型 → 启用时优先返回激活遮罩里的模型,没有遮罩模型再退回基础模型。

调用关系:它被 current_model_supports_images、current_model_supports_personality、model_display_name 和 set_collaboration_mask 使用,是很多显示和能力判断的共同来源。

调用图:调用 1 个内部函数(collaboration_modes_enabled);被 4 处调用(current_model_supports_images, current_model_supports_personality, model_display_name, set_collaboration_mask)。

ChatWidget::sync_personality_command_enabled268–271 ↗
fn sync_personality_command_enabled(&mut self)

作用:让底部面板的人格命令按钮跟功能开关保持一致。功能关了,按钮就不该还能点。

数据流:读取 Feature::Personality 是否启用 → 调用底部面板设置人格命令可用或不可用 → 不返回结果。

调用关系:它会被 set_feature_enabled 和 apply_thread_settings 调用,用来在功能开关或线程设置变化后同步界面。

调用图:被 2 处调用(apply_thread_settings, set_feature_enabled)。

ChatWidget::sync_plugins_command_enabled273–276 ↗
fn sync_plugins_command_enabled(&mut self)

作用:同步插件命令按钮是否可用。插件功能关闭时,用户不应看到可用的插件入口。

数据流:读取 Feature::Plugins 开关 → 设置底部面板插件命令状态 → 完成界面同步。

调用关系:它由 set_feature_enabled 调用,是插件功能开关变化后的界面收尾动作之一。

调用图:被 1 处调用(set_feature_enabled)。

ChatWidget::sync_goal_command_enabled278–281 ↗
fn sync_goal_command_enabled(&mut self)

作用:同步目标命令按钮是否可用。目标功能关闭时,也会让相关入口消失或变灰。

数据流:读取 Feature::Goals 开关 → 设置底部面板目标命令状态 → 不返回值。

调用关系:它由 set_feature_enabled 调用,并和目标状态指示器的清理逻辑一起工作。

调用图:被 1 处调用(set_feature_enabled)。

ChatWidget::sync_mentions_v2_enabled283–286 ↗
fn sync_mentions_v2_enabled(&mut self)

作用:同步新版 mentions 功能是否启用。mentions 可以理解成在输入中引用某些对象或上下文的能力。

数据流:读取 Feature::MentionsV2 开关 → 通知底部面板启用或停用新版 mentions → 完成状态同步。

调用关系:它由 set_feature_enabled 调用,专门处理 MentionsV2 这个功能开关。

调用图:被 1 处调用(set_feature_enabled)。

ChatWidget::current_model_supports_personality288–300 ↗
fn current_model_supports_personality(&self) -> bool

作用:判断当前模型是否支持人格设置。不是所有模型都能按人格风格工作。

数据流:先通过 current_model 得到当前模型名 → 从模型目录里找同名模型 → 读它的 supports_personality 标志 → 找不到或读失败就返回 false。

调用关系:它依赖 current_model 的有效模型判断,供界面决定人格相关功能是否该展示或可用。

调用图:调用 1 个内部函数(current_model)。

ChatWidget::current_model_supports_images306–318 ↗
fn current_model_supports_images(&self) -> bool

作用:判断当前模型是否支持图片输入。这里有个重要选择:如果模型目录暂时读不到,就默认支持,避免误伤用户输入。

数据流:通过 current_model 得到模型名 → 查模型目录 → 看输入能力里有没有 Image → 查不到或目录失败时返回 true。

调用关系:它被 sync_image_paste_enabled 调用,用来控制粘贴图片的入口是否打开。

调用图:调用 1 个内部函数(current_model);被 1 处调用(sync_image_paste_enabled)。

ChatWidget::sync_image_paste_enabled320–323 ↗
fn sync_image_paste_enabled(&mut self)

作用:根据当前模型能力,打开或关闭图片粘贴。这样用户不会把图片发给明确不支持图片的模型。

数据流:调用 current_model_supports_images 得到是否支持图片 → 把结果写到底部面板的图片粘贴开关 → 不返回值。

调用关系:它由 refresh_model_display 调用,所以每次模型显示刷新时,图片输入能力也会跟着同步。

调用图:调用 1 个内部函数(current_model_supports_images);被 1 处调用(refresh_model_display)。

ChatWidget::image_inputs_not_supported_message325–330 ↗
fn image_inputs_not_supported_message(&self) -> String

作用:生成一条给用户看的错误提示,说明当前模型不支持图片输入。提示里会带上模型名,方便用户知道该换哪个设置。

数据流:读取当前模型名 → 拼成“该模型不支持图片,请移除图片或切换模型”的字符串 → 返回这段文字。

调用关系:它是图片输入被拒绝时的提示文案来源,和 current_model_supports_images 的判断配套。

调用图:外部调用 1 个(format!)。

ChatWidget::current_collaboration_mode333–335 ↗
fn current_collaboration_mode(&self) -> &CollaborationMode

作用:返回当前保存的基础协作模式。协作模式可以理解成聊天的工作姿态,比如默认、Plan 等。

数据流:读取 self.current_collaboration_mode → 返回只读引用 → 不修改任何状态。

调用关系:这个函数主要给测试或其他内部代码查看当前模式设置。

ChatWidget::current_reasoning_effort337–339 ↗
fn current_reasoning_effort(&self) -> Option<ReasoningEffortConfig>

作用:读取当前真正生效的推理强度。它会把普通设置和协作模式遮罩都考虑进去。

数据流:调用 effective_reasoning_effort → 得到可选推理强度 → 返回给调用者。

调用关系:它是 effective_reasoning_effort 的公开一点的包装入口,方便其他地方不用理解遮罩细节。

调用图:调用 1 个内部函数(effective_reasoning_effort)。

ChatWidget::on_thread_settings_updated341–357 ↗
fn on_thread_settings_updated(
        &mut self,
        notification: ThreadSettingsUpdatedNotification,
    )

作用:处理后端发来的“线程设置已更新”通知。它会先确认通知确实属于当前聊天,避免把别的会话设置套到这里。

数据流:传入通知 → 尝试把通知里的 thread_id 转成内部线程编号 → 编号无效就警告并退出,不是当前线程也退出 → 匹配当前线程时应用其中的设置。

调用关系:它在收到服务器通知时触发,校验后把真正更新工作交给 apply_thread_settings。

调用图:调用 2 个内部函数(from_string, apply_thread_settings);外部调用 1 个(warn!)。

ChatWidget::active_collaboration_mode_kind360–362 ↗
fn active_collaboration_mode_kind(&self) -> ModeKind

作用:返回当前激活的协作模式种类。这个函数主要用于测试,看看现在到底是默认模式还是 Plan 等模式。

数据流:调用 active_mode_kind → 返回模式枚举值 → 不改状态。

调用关系:它是 active_mode_kind 的测试友好包装。

调用图:调用 1 个内部函数(active_mode_kind)。

ChatWidget::is_session_configured364–366 ↗
fn is_session_configured(&self) -> bool

作用:判断当前聊天是否已经绑定了一个线程会话。没有线程编号时,说明会话还没真正配置好。

数据流:检查 self.thread_id 是否存在 → 存在返回 true,不存在返回 false。

调用关系:它给其他流程判断“现在能不能提交线程相关操作”提供简单答案。

ChatWidget::collaboration_modes_enabled368–370 ↗
fn collaboration_modes_enabled(&self) -> bool

作用:告诉系统协作模式功能是否启用。当前实现总是返回 true,相当于这个功能默认打开。

数据流:不读取复杂配置 → 直接返回 true。

调用关系:它被 current_model、set_model、effective_collaboration_mode、cycle_collaboration_mode、set_collaboration_mask 等多处调用,是协作模式相关流程的统一开关。

调用图:被 11 处调用(collaboration_mode_indicator, collaboration_mode_label, current_model, cycle_collaboration_mode, effective_collaboration_mode, effective_reasoning_effort, set_collaboration_mask, set_model, set_plan_mode_reasoning_effort, set_reasoning_effort (+1 more))。

ChatWidget::plan_mode_nudge_scope373–376 ↗
fn plan_mode_nudge_scope(&self) -> PlanModeNudgeScope

作用:判断 Plan 模式提示的“记住不再提示”范围。新聊天和已有线程用不同范围,避免一个地方点掉影响另一个地方。

数据流:检查当前有没有 thread_id → 没有就是 NewThread 范围,有就是 Thread 范围 → 返回这个范围值。

调用关系:它被 should_show_plan_mode_nudge、dismiss_plan_mode_nudge 和 set_collaboration_mask 使用,用来记录 Plan 提示该隐藏多久。

调用图:被 3 处调用(dismiss_plan_mode_nudge, set_collaboration_mask, should_show_plan_mode_nudge)。

ChatWidget::should_show_plan_mode_nudge384–399 ↗
fn should_show_plan_mode_nudge(&self) -> bool

作用:判断现在是否应该显示“要不要切到 Plan 模式”的提示。它会看用户输入、当前模式、是否有弹窗、任务是否运行等条件。

数据流:读取输入框文字并去掉开头空白 → 检查协作模式、Plan 是否可用、当前不是 Plan、输入框可用、无任务和弹窗、不是斜杠命令或 shell 命令、文字里有计划类关键词、当前范围没被关闭 → 全部满足才返回 true。

调用关系:它调用 active_mode_kind、collaboration_modes_enabled、plan_mode_nudge_scope 和 plan_mask;refresh_plan_mode_nudge 会用它的结果更新底部显示。

调用图:调用 4 个内部函数(active_mode_kind, collaboration_modes_enabled, plan_mode_nudge_scope, plan_mask);被 1 处调用(refresh_plan_mode_nudge)。

ChatWidget::refresh_plan_mode_nudge402–405 ↗
fn refresh_plan_mode_nudge(&mut self)

作用:刷新底部面板上的 Plan 模式提示是否可见。它把复杂判断结果变成界面上的一个显示开关。

数据流:调用 should_show_plan_mode_nudge 得到是否显示 → 写入 bottom_pane 的 Plan 提示可见状态 → 不返回值。

调用关系:它会在 dismiss_plan_mode_nudge、set_collaboration_mask 和 set_effective_collaboration_mode 后调用,确保模式或用户选择变化后提示马上更新。

调用图:调用 1 个内部函数(should_show_plan_mode_nudge);被 3 处调用(dismiss_plan_mode_nudge, set_collaboration_mask, set_effective_collaboration_mode)。

ChatWidget::dismiss_plan_mode_nudge408–412 ↗
fn dismiss_plan_mode_nudge(&mut self)

作用:用户关闭 Plan 模式提示时调用。它会记住当前范围内不要再显示这个提示。

数据流:算出当前提示范围 → 把这个范围加入已关闭集合 → 再刷新提示可见性,让它立刻消失。

调用关系:它调用 plan_mode_nudge_scope 和 refresh_plan_mode_nudge,是用户主动关闭提示的处理入口。

调用图:调用 2 个内部函数(plan_mode_nudge_scope, refresh_plan_mode_nudge)。

ChatWidget::initial_collaboration_mask414–424 ↗
fn initial_collaboration_mask(
        _config: &Config,
        model_catalog: &ModelCatalog,
        model_override: Option<&str>,
    ) -> Option<CollaborationModeMask>

作用:创建聊天一开始使用的协作模式遮罩。遮罩像一张临时贴纸,可以覆盖模型或模式的部分设置。

数据流:传入配置、模型目录和可选模型覆盖值 → 从模型目录取默认遮罩 → 如果传了模型覆盖,就把遮罩里的模型改成该模型 → 返回遮罩或空。

调用关系:它调用 default_mask,用于 ChatWidget 初始化协作模式状态。

调用图:调用 1 个内部函数(default_mask)。

ChatWidget::active_mode_kind426–431 ↗
fn active_mode_kind(&self) -> ModeKind

作用:读取当前激活的协作模式种类。没有激活遮罩时,就当作默认模式。

数据流:查看 active_collaboration_mask → 如果里面有 mode 就返回它 → 否则返回 ModeKind::Default。

调用关系:它被 active_collaboration_mode_kind、collaboration_mode_indicator、collaboration_mode_label、set_collaboration_mask 和 should_show_plan_mode_nudge 使用。

调用图:被 5 处调用(active_collaboration_mode_kind, collaboration_mode_indicator, collaboration_mode_label, set_collaboration_mask, should_show_plan_mode_nudge)。

ChatWidget::effective_reasoning_effort433–442 ↗
fn effective_reasoning_effort(&self) -> Option<ReasoningEffortConfig>

作用:算出当前真正生效的推理强度。它会处理“基础设置”和“当前模式临时覆盖”之间的优先级。

数据流:如果协作模式没启用,就返回基础协作模式的推理强度 → 启用时先取基础强度,再看遮罩里有没有覆盖 → 有覆盖用覆盖,没有就用基础值。

调用关系:它被 current_reasoning_effort 和 set_collaboration_mask 调用,用来比较或展示模式切换前后的推理强度。

调用图:调用 1 个内部函数(collaboration_modes_enabled);被 2 处调用(current_reasoning_effort, set_collaboration_mask)。

ChatWidget::effective_collaboration_mode444–452 ↗
fn effective_collaboration_mode(&self) -> CollaborationMode

作用:合成当前真正生效的协作模式。它把基础模式和激活遮罩叠在一起,得出最终要提交给后端的设置。

数据流:如果协作模式未启用,返回基础模式副本 → 如果没有遮罩,也返回基础模式副本 → 有遮罩时把遮罩应用到基础模式上 → 返回合成后的模式。

调用关系:它被 refresh_model_display 用于显示当前模型,也被 submit_collaboration_mode_settings_update 用于把模式提交给后端。

调用图:调用 1 个内部函数(collaboration_modes_enabled);被 2 处调用(refresh_model_display, submit_collaboration_mode_settings_update)。

ChatWidget::refresh_model_display454–461 ↗
fn refresh_model_display(&mut self)

作用:刷新所有直接显示当前模型的地方。比如会话头部、图片粘贴能力、服务档位命令和终端标题。

数据流:调用 effective_collaboration_mode 得到最终模式 → 把模型名写到 session_header → 同步图片粘贴能力、服务档位命令和终端标题 → 不返回值。

调用关系:它被 refresh_model_dependent_surfaces 调用,是“模型相关界面刷新”的第一部分。

调用图:调用 2 个内部函数(effective_collaboration_mode, sync_image_paste_enabled);被 1 处调用(refresh_model_dependent_surfaces)。

ChatWidget::refresh_model_dependent_surfaces471–474 ↗
fn refresh_model_dependent_surfaces(&mut self)

作用:统一刷新所有依赖模型、推理强度或协作模式的界面区域。这样调用者不会忘了只刷新页眉却漏掉状态栏。

数据流:先调用 refresh_model_display 更新模型相关显示 → 再刷新状态行 → 不返回值。

调用关系:它被 set_model、set_reasoning_effort、set_plan_mode_reasoning_effort、set_collaboration_mask 和 set_effective_collaboration_mode 调用,是模型状态变化后的统一收尾函数。

调用图:调用 1 个内部函数(refresh_model_display);被 5 处调用(set_collaboration_mask, set_effective_collaboration_mode, set_model, set_plan_mode_reasoning_effort, set_reasoning_effort)。

ChatWidget::apply_thread_settings476–523 ↗
fn apply_thread_settings(&mut self, mut settings: ThreadSettings)

作用:把后端发来的线程设置完整应用到当前 ChatWidget。它是一次“大同步”:目录、模型、权限、审批、人格、协作模式都会跟着更新。

数据流:传入线程设置 → 检查工作目录是否变化并应用 → 更新模型服务商、服务档位、审批策略、审核者、人格和权限档案 → 把线程里的模型与推理强度塞进协作模式 → 设置最终协作模式 → 刷新服务档位、状态显示、命令可用性、技能、插件提及并请求重绘。

调用关系:它由 on_thread_settings_updated 在确认线程编号匹配后调用;内部会调用 apply_thread_settings_cwd、set_approval_policy、set_approvals_reviewer、set_effective_collaboration_mode 和 sync_personality_command_enabled 等函数。

调用图:调用 7 个内部函数(from_session_snapshot, from_legacy_sandbox_policy_for_cwd, apply_thread_settings_cwd, set_approval_policy, set_approvals_reviewer, set_effective_collaboration_mode, sync_personality_command_enabled);被 1 处调用(on_thread_settings_updated);外部调用 2 个(error!, warn!)。

ChatWidget::apply_thread_settings_cwd525–544 ↗
fn apply_thread_settings_cwd(&mut self, cwd: AbsolutePathBuf)

作用:应用线程设置里的当前工作目录。当前工作目录就是助手理解“项目在哪里”的起点。

数据流:传入新的绝对路径 → 替换 config.cwd,保存 current_cwd,并清掉项目根名称缓存 → 如果旧目录在工作区根列表里,就用新目录替换旧目录,同时保留其他根目录 → 更新权限里的工作区根。

调用关系:它只被 apply_thread_settings 调用,负责目录变化这块比较细的同步。

调用图:调用 1 个内部函数(to_path_buf);被 1 处调用(apply_thread_settings);外部调用 3 个(replace, take, clone)。

ChatWidget::set_effective_collaboration_mode546–565 ↗
fn set_effective_collaboration_mode(&mut self, mode: CollaborationMode)

作用:直接设置当前生效的协作模式。它通常用于后端同步来的模式,而不是用户手动点切换。

数据流:传入完整协作模式 → 拆出模式种类和设置 → 如果是默认模式,也更新基础协作模式 → 构造新的激活遮罩 → 更新模式指示器、Plan 提示和模型相关界面。

调用关系:它由 apply_thread_settings 调用;内部会调用 update_collaboration_mode_indicator、refresh_plan_mode_nudge 和 refresh_model_dependent_surfaces。

调用图:调用 3 个内部函数(refresh_model_dependent_surfaces, refresh_plan_mode_nudge, update_collaboration_mode_indicator);被 1 处调用(apply_thread_settings)。

ChatWidget::model_display_name567–574 ↗
fn model_display_name(&self) -> &str

作用:返回界面上该显示的模型名字。如果模型名为空,就用默认显示名兜底。

数据流:调用 current_model 得到当前模型名 → 如果为空返回 DEFAULT_MODEL_DISPLAY_NAME → 否则返回模型名本身。

调用关系:它依赖 current_model,所以会自动考虑协作模式遮罩带来的模型覆盖。

调用图:调用 1 个内部函数(current_model)。

ChatWidget::collaboration_mode_label577–585 ↗
fn collaboration_mode_label(&self) -> Option<&'static str>

作用:返回当前协作模式在界面上要显示的文字标签。不可见的模式就不显示标签。

数据流:先检查协作模式是否启用 → 读取 active_mode_kind → 如果该模式允许在 TUI 显示,就返回它的显示名,否则返回空。

调用关系:它调用 collaboration_modes_enabled 和 active_mode_kind,供状态栏或页脚显示当前模式名。

调用图:调用 2 个内部函数(active_mode_kind, collaboration_modes_enabled)。

ChatWidget::collaboration_mode_indicator587–595 ↗
fn collaboration_mode_indicator(&self) -> Option<CollaborationModeIndicator>

作用:决定底部面板要不要显示协作模式图标或提示。目前 Plan 模式会显示,默认、结对编程、执行模式不显示。

数据流:检查协作模式是否启用 → 读取当前模式 → 如果是 Plan 返回 Plan 指示器,否则返回空。

调用关系:它被 update_collaboration_mode_indicator 和 refresh_goal_status_indicator_for_time_tick 调用,用来决定模式指示和目标指示谁优先。

调用图:调用 2 个内部函数(active_mode_kind, collaboration_modes_enabled);被 2 处调用(refresh_goal_status_indicator_for_time_tick, update_collaboration_mode_indicator)。

ChatWidget::update_collaboration_mode_indicator597–607 ↗
fn update_collaboration_mode_indicator(&mut self)

作用:更新底部面板里的协作模式指示器和目标状态指示器。两者不能随便抢位置:有协作模式指示时,就不显示目标指示。

数据流:先算协作模式指示器 → 如果没有协作模式指示,再按当前时间计算目标状态指示 → 保存当前目标指示缓存 → 分别写到底部面板。

调用关系:它被 on_thread_goal_updated、set_collaboration_mask、set_effective_collaboration_mode 和 set_feature_enabled 调用,是模式或目标状态变化后的界面同步点。

调用图:调用 2 个内部函数(collaboration_mode_indicator, goal_status_indicator);被 4 处调用(on_thread_goal_updated, set_collaboration_mask, set_effective_collaboration_mode, set_feature_enabled);外部调用 1 个(now)。

ChatWidget::refresh_goal_status_indicator_for_time_tick609–618 ↗
fn refresh_goal_status_indicator_for_time_tick(&mut self)

作用:随着时间流逝刷新目标状态指示器。有些目标状态可能会按时间变化,所以需要定时看一眼。

数据流:如果当前有协作模式指示器,就直接退出 → 否则按当前时间重新计算目标指示 → 如果和缓存不同,就更新缓存并写到底部面板。

调用关系:它调用 collaboration_mode_indicator 和 goal_status_indicator,通常在时间 tick 触发时运行。

调用图:调用 2 个内部函数(collaboration_mode_indicator, goal_status_indicator);外部调用 1 个(now)。

ChatWidget::goal_status_indicator620–627 ↗
fn goal_status_indicator(&self, now: Instant) -> Option<GoalStatusIndicator>

作用:根据当前目标状态算出该显示什么目标提示。目标功能关闭时,一律不显示。

数据流:先检查 Feature::Goals 是否启用 → 如果关闭返回空 → 如果有当前目标状态,就让状态对象根据当前时间和活动轮次开始时间生成指示器。

调用关系:它被 update_collaboration_mode_indicator 和 refresh_goal_status_indicator_for_time_tick 调用,是目标提示的具体计算处。

调用图:被 2 处调用(refresh_goal_status_indicator_for_time_tick, update_collaboration_mode_indicator)。

ChatWidget::on_thread_goal_updated629–648 ↗
fn on_thread_goal_updated(&mut self, goal: AppThreadGoal, turn_id: Option<String>)

作用:处理线程目标更新。目标可以理解成这轮对话正在追的任务状态,比如是否受预算限制。

数据流:传入目标和可选 turn_id → 如果目标不属于当前线程就忽略 → 如果目标功能关闭,就清空目标状态并刷新指示器 → 如果是预算受限且有 turn_id,就记录该轮受限 → 保存新的目标状态并更新指示器。

调用关系:它在收到目标更新事件时调用,并把界面刷新交给 update_collaboration_mode_indicator。

调用图:调用 2 个内部函数(new, update_collaboration_mode_indicator);外部调用 1 个(now)。

ChatWidget::cycle_collaboration_mode651–662 ↗
fn cycle_collaboration_mode(&mut self)

作用:让用户在协作模式之间循环切换,比如从 Plan 到默认再回 Plan。它是快捷切换模式的入口。

数据流:先检查协作模式是否启用 → 询问 collaboration_modes::next_mask 下一个遮罩是什么 → 如果有,就按用户操作设置这个遮罩。

调用关系:它调用 set_collaboration_mask_from_user_action;真正改状态和提交后端更新由后者继续完成。

调用图:调用 3 个内部函数(collaboration_modes_enabled, set_collaboration_mask_from_user_action, next_mask)。

ChatWidget::set_collaboration_mask_from_user_action664–667 ↗
fn set_collaboration_mask_from_user_action(&mut self, mask: CollaborationModeMask)

作用:处理用户主动选择协作模式遮罩。除了本地切换,还会把选择提交给后端,让会话设置跟着变。

数据流:传入新遮罩 → 调用 set_collaboration_mask 更新本地状态和界面 → 再调用 submit_collaboration_mode_settings_update 提交线程设置覆盖。

调用关系:它由 cycle_collaboration_mode 调用,是“用户动作”和“后端同步”之间的桥。

调用图:调用 2 个内部函数(set_collaboration_mask, submit_collaboration_mode_settings_update);被 1 处调用(cycle_collaboration_mode)。

ChatWidget::set_collaboration_mask673–714 ↗
fn set_collaboration_mask(&mut self, mut mask: CollaborationModeMask)

作用:更新当前激活的协作模式遮罩,并刷新所有受影响的界面。遮罩会临时覆盖模型、推理强度或模式。

数据流:传入遮罩 → 如果协作模式未启用就退出 → 记录切换前的模式、模型、推理强度 → Plan 模式时套用 Plan 专用推理强度并关闭当前范围的 Plan 提示 → 保存新遮罩 → 更新指示器、提示、模型相关界面 → 如果模式变化同时模型或推理强度也变了,就加一条信息消息 → 请求重绘。

调用关系:它由 set_collaboration_mask_from_user_action 调用;内部依赖 active_mode_kind、current_model、effective_reasoning_effort、plan_mode_nudge_scope、update_collaboration_mode_indicator、refresh_plan_mode_nudge 和 refresh_model_dependent_surfaces。

调用图:调用 8 个内部函数(active_mode_kind, collaboration_modes_enabled, current_model, effective_reasoning_effort, plan_mode_nudge_scope, refresh_model_dependent_surfaces, refresh_plan_mode_nudge, update_collaboration_mode_indicator);被 1 处调用(set_collaboration_mask_from_user_action);外部调用 1 个(format!)。

ChatWidget::submit_collaboration_mode_settings_update716–737 ↗
fn submit_collaboration_mode_settings_update(&self)

作用:把当前生效的协作模式提交给后端线程。这样用户在界面切换模式后,服务器也知道下一轮该按什么模式处理。

数据流:先检查是否有 thread_id,没有就直接退出 → 生成一个 override_turn_context 命令,只覆盖协作模式这一项 → 通过 app_event_tx 发出 SubmitThreadOp 事件。

调用关系:它由 set_collaboration_mask_from_user_action 调用;提交内容来自 effective_collaboration_mode。

调用图:调用 1 个内部函数(effective_collaboration_mode);被 1 处调用(set_collaboration_mask_from_user_action);外部调用 1 个(override_turn_context)。

tui/src/keymap.rs源码 ↗
configconfig load / startup / keymap refresh

终端界面里有很多地方要用快捷键:主界面、聊天、输入框、Vim 模式、弹窗列表、审批窗口、翻页窗口等。这个文件的作用,就是把配置文件里的文字,比如“ctrl-t”“page-down”,翻译成程序能识别的按键对象。它会按固定顺序决定用哪个设置:先看当前区域自己的设置,再看全局设置,最后才用内置默认值。用户也可以把某个动作设成空列表,意思是故意取消这个快捷键。更重要的是,它会像检查道路交通一样,找出会撞车的按键:比如一个键既被主界面抢走,又想给输入框用,那输入框永远收不到这个键。发现问题时,它会返回带配置路径的错误,告诉用户该改哪里。

函数细节83
primary_binding270–272 ↗
fn primary_binding(bindings: &[KeyBinding]) -> Option<KeyBinding>

作用:从一组快捷键里取第一个,作为界面上显示给用户看的主要提示。比如一个动作有多个按键都能触发,提示栏通常只显示最常用的那个。

数据流:输入是一串按键绑定 → 它只看第一个元素 → 输出这个按键;如果列表为空,就输出空值,不改动任何数据。

调用关系:很多界面展示提示时会调用它,比如状态栏、审批窗口底部提示、弹窗提示等。真正按键匹配仍然用完整列表,这里只负责挑一个好看的代表。

调用图:被 9 处调用(ensure_status_indicator, set_keymap_bindings, set_task_running, approval_footer_hint, new_with_config, set_keymap_bindings, build, standard_popup_hint_line_for_keymap, skills_toggle_hint_line);外部调用 1 个(first)。

RuntimeKeymap::defaults372–374 ↗
fn defaults() -> Self

作用:拿到一整套内置默认快捷键。常用于还没读用户配置时,或者测试时需要一份稳定的快捷键表。

数据流:没有外部输入 → 调用内置默认表构造函数 → 返回完整的 RuntimeKeymap,不读取用户配置。

调用关系:启动、测试、创建默认应用状态时会用它。它只是方便入口,真正列出默认按键的是 RuntimeKeymap::built_in_defaults。

调用图:被 107 处调用(make_test_app, clear_only_ui_reset_preserves_chat_session_state, make_test_app, make_test_app_with_channels, new, new, remapped_horizontal_list_keys_control_action_selection, additional_permissions_exec_options_hide_execpolicy_amendment, apply_patch_prompt_with_thread_label_omits_command_line, configured_list_cancel_aborts_exec_approval (+15 more));外部调用 1 个(built_in_defaults)。

RuntimeKeymap::from_config386–902 ↗
fn from_config(keymap: &TuiKeymap) -> Result<Self, String>

作用:这是本文件最核心的函数:把用户配置转换成运行时快捷键表,并检查所有容易出错的地方。配置写错、快捷键冲突、显式取消绑定,都会在这里处理。

数据流:输入是 TuiKeymap,也就是已经从配置文件读出来的快捷键配置 → 它先拿默认值,再逐个区域解析用户设置、套用全局回退、保留用户旧配置优先级、删除会抢键的新默认项,最后做冲突检查 → 成功时输出 RuntimeKeymap,失败时输出能给用户看的错误文字。

调用关系:程序加载配置、打开快捷键选择器、测试重映射时都会调用它。它把具体工作分给 resolve_bindings、resolve_new_default_bindings、configured_bindings_to_preserve、configured_main_surface_alias_is_used,最后交给 validate_conflicts 做总检查。

调用图:调用 4 个内部函数(configured_bindings_to_preserve, configured_main_surface_alias_is_used, resolve_bindings, resolve_new_default_bindings);被 48 处调用(run, apply_keymap_capture, apply_keymap_clear, new_with_op_target, open_keymap_picker, dispatch_prepared_command_with_args, copy_shortcut_can_be_remapped, configured_app_bindings_prune_new_list_default_overlaps, configured_approval_bindings_prune_new_list_default_overlaps, configured_legacy_list_bindings_can_prune_all_new_default_keys (+15 more));外部调用 3 个(built_in_defaults, resolve_local!, resolve_with_global!)。

RuntimeKeymap::built_in_defaults909–1153 ↗
fn built_in_defaults() -> Self

作用:写死程序自带的全部默认快捷键。没有用户配置时,界面就是靠这张表知道 Enter 提交、Esc 取消、方向键移动等。

数据流:没有输入 → 按主界面、聊天、输入框、Vim、列表、审批、翻页等分类创建很多按键列表 → 输出完整默认 RuntimeKeymap。

调用关系:RuntimeKeymap::defaults 和 RuntimeKeymap::from_config 都依赖它。它不检查冲突,只负责提供“出厂设置”。

调用图:外部调用 1 个(default_bindings!)。

RuntimeKeymap::validate_conflicts1164–1688 ↗
fn validate_conflicts(&self) -> Result<(), String>

作用:检查运行时快捷键表里有没有会互相打架的按键。没有它,用户按一个键时程序可能不知道该执行哪个动作,或者某个动作永远被前面的处理器抢走。

数据流:输入是已经解析好的 RuntimeKeymap → 它按实际界面处理顺序分批检查重复、遮挡和保留键冲突 → 没问题返回成功,有问题返回说明具体动作名的错误消息。

调用关系:RuntimeKeymap::from_config 在最后调用它。它再调用 validate_unique、validate_no_reserved、validate_no_shadow_with_allowed_overlaps 来分别检查不同类型的问题。

调用图:调用 5 个内部函数(ctrl, plain, validate_no_reserved, validate_no_shadow_with_allowed_overlaps, validate_unique);外部调用 3 个(new, Char, format!)。

validate_unique1695–1713 ↗
fn validate_unique(
    context: &str,
    pairs: [(&'static str, &[KeyBinding]); N],
) -> Result<(), String>

作用:检查同一个上下文里,两个动作是不是用了同一个按键。这里的上下文可以理解成“同一张菜单或同一个输入场景”。

数据流:输入是上下文名字和若干动作的按键列表 → 它把每个按键记到表里,发现同一个按键第二次出现就报错 → 输出成功或冲突错误。

调用关系:RuntimeKeymap::validate_conflicts 多次调用它,分别检查 editor、pager、list、approval、Vim 模式等各自内部是否干净。

调用图:被 1 处调用(validate_conflicts);外部调用 2 个(new, format!)。

validate_no_shadow_with_allowed_overlaps1715–1749 ↗
fn validate_no_shadow_with_allowed_overlaps(
    context: &str,
    primary: [(&'static str, &[KeyBinding]); N],
    shadowed: [(&'static str, &[KeyBinding]); M],
    allowed_overlaps: [(&'static str,

作用:检查前一层处理器会不会把后一层需要的按键抢走。比如主界面先处理 Ctrl-Y,输入框里的“粘贴”就可能永远收不到 Ctrl-Y。

数据流:输入是一个场景名、一组优先处理的动作、一组可能被遮住的动作,以及少量允许重叠的例外 → 它先记录前一组按键,再检查后一组是否撞上 → 输出成功或“某动作遮住某动作”的错误。

调用关系:RuntimeKeymap::validate_conflicts 用它模拟真实事件流。它专门处理“不是同一张表重复,但实际运行会被抢键”的问题。

调用图:被 1 处调用(validate_conflicts);外部调用 3 个(new, format!, iter)。

validate_no_reserved1751–1782 ↗
fn validate_no_reserved(
    context: &str,
    pairs: [(&'static str, &[KeyBinding]); N],
    reserved: &[(&'static str, KeyBinding)],
    allowed_overlaps: [(&'static str, &'static str, KeyBinding);

作用:检查用户可配置快捷键有没有占用程序固定保留的按键。保留键就像紧急出口,不能随便改给别的动作。

数据流:输入是当前动作按键、保留按键表和允许例外 → 它逐个比对,发现用了保留键就报错,除非在例外名单里 → 输出成功或说明被哪个固定动作占用。

调用关系:RuntimeKeymap::validate_conflicts 用它保护主界面的 Ctrl-C、Ctrl-D、粘贴图片键,以及 transcript 编辑里的固定按键。

调用图:被 1 处调用(validate_conflicts);外部调用 2 个(format!, iter)。

resolve_bindings_with_global_fallback1835–1848 ↗
fn resolve_bindings_with_global_fallback(
    configured: Option<&KeybindingsSpec>,
    global: Option<&KeybindingsSpec>,
    fallback: &[KeyBinding],
    path: &str,
) -> Result<Vec<KeyBinding>, Stri

作用:解析一个支持“局部设置优先、全局设置其次、默认值兜底”的动作快捷键。比如 composer 的提交键可以使用全局 submit 设置。

数据流:输入是局部配置、全局配置、默认按键和配置路径 → 如果局部写了就解析局部;否则如果全局写了就解析全局;都没写就复制默认值 → 输出按键列表或解析错误。

调用关系:RuntimeKeymap::from_config 通过宏使用它。它内部把字符串解析工作交给 parse_bindings。

调用图:调用 1 个内部函数(parse_bindings);外部调用 1 个(to_vec)。

resolve_bindings1854–1863 ↗
fn resolve_bindings(
    configured: Option<&KeybindingsSpec>,
    fallback: &[KeyBinding],
    path: &str,
) -> Result<Vec<KeyBinding>, String>

作用:解析一个不走全局回退的动作快捷键。用户没写就用默认值,用户写了就完全按用户写的来。

数据流:输入是可选配置、默认按键和配置路径 → 没配置就复制默认按键;有配置就解析配置,空列表也会保留为空 → 输出按键列表或错误。

调用关系:RuntimeKeymap::from_config 大量调用它,用来解析 editor、pager、Vim、approval 等只看本区域配置的动作。

调用图:调用 1 个内部函数(parse_bindings);被 1 处调用(from_config);外部调用 1 个(to_vec)。

configured_bindings_to_preserve1865–1880 ↗
fn configured_bindings_to_preserve(
    pairs: [(Option<&KeybindingsSpec>, &[KeyBinding]); N],
) -> Vec<KeyBinding>

作用:找出用户已经明确配置过的按键,后面新增默认快捷键时要避开它们。这样升级程序后,新默认键不会突然抢走用户原来的设置。

数据流:输入是若干“原始配置是否存在”和“解析后的按键列表”配对 → 它只收集那些用户确实配置过的按键,并去重 → 输出需要保护的按键列表。

调用关系:RuntimeKeymap::from_config 用它处理列表快捷键、审批快捷键和 Vim 新增默认键的兼容问题。

调用图:被 1 处调用(from_config);外部调用 1 个(new)。

configured_main_surface_alias_is_used1882–1904 ↗
fn configured_main_surface_alias_is_used(keymap: &TuiKeymap, alias: &str) -> bool

作用:检查某个按键别名,比如“shift-up”,是不是已经被主输入路径上的用户配置用了。这样默认的推理强度快捷键会让位给用户显式设置。

数据流:输入是整个 TuiKeymap 和一个按键别名字符串 → 它先处理全局设置和 composer 局部覆盖的关系,再搜索多个主界面相关区域 → 输出 true 或 false。

调用关系:RuntimeKeymap::from_config 用它决定是否删掉 reasoning effort 的 shift-up、shift-down 默认别名。它把每个区域的搜索交给 configured_context_alias_is_used。

调用图:调用 1 个内部函数(configured_context_alias_is_used);被 1 处调用(from_config)。

configured_context_alias_is_used1906–1911 ↗
fn configured_context_alias_is_used(context: &impl Serialize, alias: &str) -> bool

作用:检查某一个配置区域里有没有出现指定的按键别名。它把结构体临时变成通用 JSON,方便递归查找。

数据流:输入是一个可序列化的配置区域和别名 → 先转成 JSON 值,失败就当没找到;成功后递归搜索 → 输出是否包含这个别名。

调用关系:configured_main_surface_alias_is_used 调用它来扫描 global、chat、composer、editor、Vim 等区域。递归细节交给 keymap_value_contains_alias。

调用图:调用 1 个内部函数(keymap_value_contains_alias);被 1 处调用(configured_main_surface_alias_is_used);外部调用 1 个(to_value)。

keymap_value_contains_alias1913–1926 ↗
fn keymap_value_contains_alias(value: &serde_json::Value, alias: &str) -> bool

作用:在 JSON 值里递归查找某个字符串。它能穿过数组和对象,找到深处的快捷键别名。

数据流:输入是 JSON 值和目标别名 → 如果是字符串就直接比较;如果是数组或对象就继续往里找;其他类型直接忽略 → 输出是否找到。

调用关系:configured_context_alias_is_used 用它做真正的搜索。它不懂快捷键语义,只负责“树里有没有这个字符串”。

调用图:被 1 处调用(configured_context_alias_is_used)。

resolve_new_default_bindings1928–1942 ↗
fn resolve_new_default_bindings(
    configured: Option<&KeybindingsSpec>,
    fallback: &[KeyBinding],
    configured_bindings_to_preserve: &[KeyBinding],
    path: &str,
) -> Result<Vec<KeyBinding>,

作用:解析新增默认快捷键时,自动避开用户已经配置过的旧按键。它主要用于保持老用户升级后的体验稳定。

数据流:输入是可选用户配置、默认按键、需要保护的按键、配置路径 → 如果用户显式配置了这个动作,就正常解析;如果没配置,就从默认值里过滤掉受保护按键 → 输出最终按键列表或错误。

调用关系:RuntimeKeymap::from_config 用它处理 list 的 page、jump、左右移动等新默认动作,避免和旧配置或审批配置撞车。

调用图:调用 1 个内部函数(parse_bindings);被 1 处调用(from_config);外部调用 1 个(iter)。

parse_bindings1948–1964 ↗
fn parse_bindings(spec: &KeybindingsSpec, path: &str) -> Result<Vec<KeyBinding>, String>

作用:把配置里一个动作的快捷键写法解析成按键对象列表。它支持单个字符串,也支持字符串数组。

数据流:输入是 KeybindingsSpec 和配置路径 → 它逐个取出原始字符串,调用 parse_keybinding 解析,并去掉重复项但保留第一次出现的顺序 → 输出按键列表;解析失败时输出带路径的错误。

调用关系:resolve_bindings、resolve_bindings_with_global_fallback、resolve_new_default_bindings 都靠它把用户文字配置变成程序按键。

调用图:调用 2 个内部函数(specs, parse_keybinding);被 3 处调用(resolve_bindings, resolve_bindings_with_global_fallback, resolve_new_default_bindings);外部调用 1 个(new)。

parse_keybinding1970–2022 ↗
fn parse_keybinding(spec: &str) -> Option<KeyBinding>

作用:解析一个具体快捷键字符串,比如“ctrl-alt-a”“page-down”“f12”。这是从人写的文字到程序按键对象的最小转换器。

数据流:输入是一个规范化字符串 → 它先拆出 ctrl、alt、shift 这些修饰键,再识别 enter、tab、方向键、功能键或单字符 → 成功输出 KeyBinding,格式不认识就输出空值。

调用关系:parse_bindings 调用它处理每个字符串;测试也直接调用它确认各种合法和非法写法。

调用图:调用 1 个内部函数(new);被 2 处调用(parse_bindings, parses_canonical_binding);外部调用 3 个(Char, F, from)。

tests::one2029–2031 ↗
fn one(spec: &str) -> KeybindingsSpec

作用:测试用的小工具,把一个字符串快速包装成单个快捷键配置。这样测试代码不用反复写复杂构造。

数据流:输入是字符串 → 包成 KeybindingSpec,再包成 KeybindingsSpec::One → 输出测试可用的配置值。

调用关系:大量测试用它设置某个动作的快捷键,然后交给 RuntimeKeymap::from_config 检查结果。

调用图:外部调用 2 个(new, One)。

tests::expect_conflict2033–2037 ↗
fn expect_conflict(keymap: &TuiKeymap, first: &str, second: &str)

作用:测试用的小工具,断言某份配置一定会产生快捷键冲突,并且错误消息里包含两个相关动作名。

数据流:输入是 keymap 和两个应该出现在错误里的名字 → 调用 RuntimeKeymap::from_config,期待失败 → 检查错误文字包含指定内容。

调用关系:很多冲突类测试调用它,避免每个测试重复写同样的断言流程。

调用图:调用 1 个内部函数(from_config);外部调用 1 个(assert!)。

tests::parses_canonical_binding2040–2047 ↗
fn parses_canonical_binding()

作用:确认标准写法“ctrl-alt-shift-a”能被正确解析成一个带三个修饰键的按键。

数据流:输入是固定字符串 → 调用 parse_keybinding → 检查输出的键是 a,修饰键是 Ctrl、Alt、Shift。

调用关系:它直接验证 parse_keybinding 的基础能力,是解析链路的最小单元测试。

调用图:调用 1 个内部函数(parse_keybinding);外部调用 1 个(assert_eq!)。

tests::rejects_shadowing_composer_binding_in_app_scope2050–2058 ↗
fn rejects_shadowing_composer_binding_in_app_scope()

作用:确认主界面快捷键不能和 composer 提交键用同一个键,因为主界面会先抢到这个键。

数据流:构造一份 open_transcript 和 composer.submit 都是 ctrl-t 的配置 → 调用 from_config → 期待错误里提到两个冲突动作。

调用关系:验证 RuntimeKeymap::validate_conflicts 中 app 与 composer 同路径冲突检查。

调用图:调用 1 个内部函数(from_config);外部调用 3 个(assert!, default, one)。

tests::rejects_shadowing_composer_queue_in_app_scope2061–2069 ↗
fn rejects_shadowing_composer_queue_in_app_scope()

作用:确认主界面的外部编辑器快捷键不能遮住 composer 的排队快捷键。

数据流:把 open_external_editor 和 composer.queue 都设成 ctrl-g → 解析配置 → 期待冲突错误。

调用关系:覆盖 app 层先处理、composer 后处理的冲突规则。

调用图:调用 1 个内部函数(from_config);外部调用 3 个(assert!, default, one)。

tests::rejects_shadowing_composer_toggle_shortcuts_in_app_scope2072–2080 ↗
fn rejects_shadowing_composer_toggle_shortcuts_in_app_scope()

作用:确认主界面动作不能和 composer 的快捷键帮助开关共用同一个键。

数据流:把 open_transcript 和 composer.toggle_shortcuts 都设为 ctrl-k → 调 from_config → 检查错误包含双方动作名。

调用关系:补充验证 composer 相关动作都会参与 app 范围冲突检查。

调用图:调用 1 个内部函数(from_config);外部调用 3 个(assert!, default, one)。

tests::rejects_shadowing_editor_binding_in_main_scope2083–2091 ↗
fn rejects_shadowing_editor_binding_in_main_scope()

作用:确认主输入路径上的提交键不能和编辑器插入换行键完全重合,除非是被允许的默认例外。

数据流:把 composer.submit 和 editor.insert_newline 都设成 ctrl-j → 解析 → 期待遮挡冲突。

调用关系:验证 main 层处理器会在 textarea 编辑器之前消费按键的规则。

调用图:调用 1 个内部函数(from_config);外部调用 3 个(assert!, default, one)。

tests::rejects_shadowing_editor_binding_from_outer_main_handler2094–2102 ↗
fn rejects_shadowing_editor_binding_from_outer_main_handler()

作用:确认外层主界面动作不能抢走编辑器内部动作的快捷键。

数据流:把全局 copy 和 editor.yank 都设成 ctrl-y → 调 from_config → 期待错误提到 copy 和 editor.yank。

调用关系:测试 validate_no_shadow_with_allowed_overlaps 对外层 app 动作和 editor 动作的保护。

调用图:调用 1 个内部函数(from_config);外部调用 3 个(assert!, default, one)。

tests::rejects_shadowing_approval_binding_in_app_scope2105–2112 ↗
fn rejects_shadowing_approval_binding_in_app_scope()

作用:确认主界面全局快捷键不能和审批弹窗里的确认键冲突。

数据流:把 open_transcript 设置为 y,而审批默认 approve 也是 y → 解析 → 期待遮挡冲突。

调用关系:验证 app 动作和 approval overlay 动作之间也要检查,因为弹窗显示时 app 层可能先拿到键。

调用图:调用 1 个内部函数(from_config);外部调用 3 个(assert!, default, one)。

tests::rejects_shadowing_list_binding_in_app_scope2115–2122 ↗
fn rejects_shadowing_list_binding_in_app_scope()

作用:确认全局动作不能和列表移动键冲突。

数据流:把 copy 设置成 down,而列表默认 move_down 也是 down → 解析 → 期待冲突错误。

调用关系:覆盖 app 和 list 弹窗之间的冲突验证。

调用图:调用 1 个内部函数(from_config);外部调用 3 个(assert!, default, one)。

tests::supports_string_or_array_bindings2125–2142 ↗
fn supports_string_or_array_bindings()

作用:确认配置既能写单个快捷键,也能写多个快捷键;同时非法修饰词会报错。

数据流:先设置 composer.submit 为包含 meta-enter 的数组 → 解析应失败;再换成 ctrl-enter 和 ctrl-shift-enter → 解析应成功并得到两个绑定。

调用关系:验证 parse_bindings 对数组、多绑定和错误路径的支持。

调用图:调用 1 个内部函数(from_config);外部调用 5 个(assert!, assert_eq!, Many, default, vec!)。

tests::deduplicates_repeated_bindings_while_preserving_first_seen_order2145–2161 ↗
fn deduplicates_repeated_bindings_while_preserving_first_seen_order()

作用:确认同一个动作里重复写同一个快捷键时,会自动去重,同时保留第一次出现的顺序。

数据流:输入包含重复 ctrl-enter 的 submit 配置 → from_config 解析 → 输出只保留一个 ctrl-enter,后面还有 ctrl-shift-enter。

调用关系:验证 parse_bindings 的去重行为,也保证 primary_binding 看到的第一个提示稳定。

调用图:调用 1 个内部函数(from_config);外部调用 4 个(assert_eq!, Many, default, vec!)。

tests::falls_back_to_global_binding_when_context_override_is_not_set2164–2173 ↗
fn falls_back_to_global_binding_when_context_override_is_not_set()

作用:确认支持全局回退的 composer 动作,在局部没配置时会使用 global 里的设置。

数据流:只设置 global.queue 为 ctrl-q → 解析 → composer.queue 得到 ctrl-q。

调用关系:测试 resolve_bindings_with_global_fallback 的核心优先级。

调用图:调用 1 个内部函数(from_config);外部调用 3 个(assert_eq!, default, one)。

tests::invalid_global_open_transcript_binding_reports_global_path2176–2182 ↗
fn invalid_global_open_transcript_binding_reports_global_path()

作用:确认全局 open_transcript 写了非法快捷键时,错误消息会指出正确配置路径。

数据流:把 global.open_transcript 设成 meta-t → from_config 失败 → 检查错误包含 tui.keymap.global.open_transcript。

调用关系:验证 parse_bindings 生成的用户可读错误路径。

调用图:调用 1 个内部函数(from_config);外部调用 3 个(assert!, default, one)。

tests::invalid_global_open_external_editor_binding_reports_global_path2185–2191 ↗
fn invalid_global_open_external_editor_binding_reports_global_path()

作用:确认全局外部编辑器快捷键解析失败时,错误会指向对应路径。

数据流:设置 global.open_external_editor 为 meta-g → 解析失败 → 检查错误路径。

调用关系:补充测试全局 app 动作的错误提示准确性。

调用图:调用 1 个内部函数(from_config);外部调用 3 个(assert!, default, one)。

tests::default_copy_binding_is_ctrl_o2194–2197 ↗
fn default_copy_binding_is_ctrl_o()

作用:确认复制最后回复的默认快捷键是 Ctrl-O。

数据流:取默认 RuntimeKeymap → 读取 app.copy → 与 ctrl-o 比较。

调用关系:保护 built_in_defaults 中 copy 默认值不被意外改掉。

调用图:调用 1 个内部函数(defaults);外部调用 1 个(assert_eq!)。

tests::defaults_include_reassignable_main_surface_actions2200–2239 ↗
fn defaults_include_reassignable_main_surface_actions()

作用:确认主界面里一批可重设动作的默认值符合预期,包括中断、推理强度、历史搜索等。

数据流:取默认快捷键表 → 检查多个 app、chat、composer、editor 字段 → 断言每个默认按键列表正确。

调用关系:作为默认表的回归测试,防止升级时不小心改变用户习惯。

调用图:调用 1 个内部函数(defaults);外部调用 1 个(assert_eq!)。

tests::defaults_include_list_page_and_jump_actions2242–2296 ↗
fn defaults_include_list_page_and_jump_actions()

作用:确认列表弹窗的上下移动、左右移动、翻页、跳到顶部底部等默认键都存在。

数据流:取默认快捷键表 → 检查 list 的多个字段 → 与预期按键列表比较。

调用关系:保护列表导航默认体验,尤其是新增 page 和 jump 动作。

调用图:调用 1 个内部函数(defaults);外部调用 1 个(assert_eq!)。

tests::configured_main_surface_bindings_prune_reasoning_fallback_aliases2299–2319 ↗
fn configured_main_surface_bindings_prune_reasoning_fallback_aliases()

作用:确认用户把 shift-up 或 shift-down 用在别处时,推理强度的默认备用别名会自动让位。

数据流:设置 editor.move_up 为 shift-up、vim_text_object.word 为 shift-down → 解析 → reasoning 只保留 alt 版本,不再占用 shift 方向键。

调用关系:验证 configured_main_surface_alias_is_used 和 from_config 中的兼容修剪逻辑。

调用图:调用 1 个内部函数(from_config);外部调用 3 个(assert_eq!, default, one)。

tests::explicit_reasoning_binding_still_conflicts_with_editor_binding2322–2328 ↗
fn explicit_reasoning_binding_still_conflicts_with_editor_binding()

作用:确认如果用户明确把推理强度也设成 shift-up,它不会自动让位,而是正常报冲突。

数据流:同时设置 editor.move_up 和 chat.increase_reasoning_effort 为 shift-up → 调 expect_conflict → 期待两者冲突。

调用关系:区分“默认备用键可让位”和“用户显式设置必须检查冲突”这两种情况。

调用图:外部调用 3 个(default, expect_conflict, one)。

tests::configured_legacy_list_bindings_prune_new_default_overlaps2331–2351 ↗
fn configured_legacy_list_bindings_prune_new_default_overlaps()

作用:确认老用户把 page-up/page-down 用作列表上下移动时,新增的列表翻页默认键会避开这些键。

数据流:设置 list.move_up 为 page-up、move_down 为 page-down → 解析 → page_up/page_down 只剩不冲突的 ctrl-b/ctrl-f。

调用关系:验证 resolve_new_default_bindings 对新默认列表键的兼容处理。

调用图:调用 1 个内部函数(from_config);外部调用 3 个(assert_eq!, default, one)。

tests::configured_legacy_list_bindings_can_prune_all_new_default_keys2354–2371 ↗
fn configured_legacy_list_bindings_can_prune_all_new_default_keys()

作用:确认如果旧配置占用了新动作的全部默认键,新动作可以变成没有默认快捷键,而不是报错。

数据流:把 list.move_up 设成 page-up 和 ctrl-b → 解析 → list.page_up 被过滤成空列表。

调用关系:测试“新增默认键让位旧配置”的极端情况。

调用图:调用 1 个内部函数(from_config);外部调用 4 个(assert_eq!, Many, default, vec!)。

tests::explicit_new_list_bindings_still_conflict_with_legacy_bindings2374–2380 ↗
fn explicit_new_list_bindings_still_conflict_with_legacy_bindings()

作用:确认用户显式给新列表动作设置了冲突键时,系统不会悄悄过滤,而是报错。

数据流:同时设置 list.move_up 和 list.page_up 为 page-up → 调 expect_conflict → 期待冲突。

调用关系:保证显式配置始终受 validate_unique 检查。

调用图:外部调用 3 个(default, expect_conflict, one)。

tests::configured_app_bindings_prune_new_list_default_overlaps2383–2394 ↗
fn configured_app_bindings_prune_new_list_default_overlaps()

作用:确认用户全局 app 快捷键占用了列表新默认键时,列表新默认键会让位。

数据流:设置 global.copy 为 page-down → 解析 → list.page_down 去掉 page-down,只保留 ctrl-f。

调用关系:验证 app 配置也会进入 configured_bindings_to_preserve 的保护集合。

调用图:调用 1 个内部函数(from_config);外部调用 3 个(assert_eq!, default, one)。

tests::configured_approval_bindings_prune_new_list_default_overlaps2397–2408 ↗
fn configured_approval_bindings_prune_new_list_default_overlaps()

作用:确认审批弹窗的用户配置能让列表新增默认键让位。

数据流:设置 approval.approve 为 home → 解析 → list.jump_top 默认 home 被移除。

调用关系:保护 approval 和 list 在同一 overlay 路径上的兼容关系。

调用图:调用 1 个内部函数(from_config);外部调用 3 个(assert_eq!, default, one)。

tests::explicit_new_list_bindings_still_conflict_with_configured_approval_bindings2411–2417 ↗
fn explicit_new_list_bindings_still_conflict_with_configured_approval_bindings()

作用:确认如果用户显式把新列表动作也设成审批已用的键,会报冲突。

数据流:设置 approval.approve 和 list.jump_top 都为 home → 调 expect_conflict → 期待错误提到双方。

调用关系:验证自动让位只适用于默认值,不适用于用户显式冲突。

调用图:外部调用 3 个(default, expect_conflict, one)。

tests::configured_legacy_vim_normal_bindings_prune_new_change_operator_default2420–2431 ↗
fn configured_legacy_vim_normal_bindings_prune_new_change_operator_default()

作用:确认老 Vim normal 配置占用 c 时,新增的 change operator 默认 c 会让位。

数据流:设置 vim_normal.move_left 为 c → 解析 → move_left 使用 c,start_change_operator 变为空。

调用关系:验证 from_config 对 Vim normal 新增默认键的兼容修剪。

调用图:调用 1 个内部函数(from_config);外部调用 3 个(assert_eq!, default, one)。

tests::explicit_new_vim_normal_binding_still_conflicts_with_legacy_binding2434–2440 ↗
fn explicit_new_vim_normal_binding_still_conflicts_with_legacy_binding()

作用:确认用户显式把 Vim 新旧两个动作都设为 c 时,会报冲突。

数据流:设置 move_left 和 start_change_operator 都为 c → expect_conflict → 检查冲突。

调用关系:保护 Vim normal 的 validate_unique 行为。

调用图:外部调用 3 个(default, expect_conflict, one)。

tests::configured_legacy_vim_normal_bindings_prune_new_substitute_default2443–2454 ↗
fn configured_legacy_vim_normal_bindings_prune_new_substitute_default()

作用:确认老 Vim normal 配置占用 s 时,新增 substitute 默认 s 会让位。

数据流:设置 vim_normal.move_left 为 s → 解析 → substitute_char 被过滤为空。

调用关系:测试 Vim normal 另一个新增默认键的兼容规则。

调用图:调用 1 个内部函数(from_config);外部调用 3 个(assert_eq!, default, one)。

tests::explicit_new_vim_normal_substitute_binding_still_conflicts_with_legacy_binding2457–2463 ↗
fn explicit_new_vim_normal_substitute_binding_still_conflicts_with_legacy_binding()

作用:确认显式配置 substitute_char 和旧动作同键时会报冲突。

数据流:设置 move_left 和 substitute_char 都为 s → expect_conflict → 期待冲突。

调用关系:验证用户显式配置不会被自动修剪掩盖。

调用图:外部调用 3 个(default, expect_conflict, one)。

tests::configured_legacy_vim_operator_bindings_prune_new_text_object_defaults2466–2483 ↗
fn configured_legacy_vim_operator_bindings_prune_new_text_object_defaults()

作用:确认老 Vim operator 配置占用 i 或 a 时,新增文本对象选择默认键会让位。

数据流:设置 motion_left 为 i、motion_right 为 a → 解析 → select_inner_text_object 和 select_around_text_object 变为空。

调用关系:验证 Vim operator 新默认键兼容旧配置。

调用图:调用 1 个内部函数(from_config);外部调用 3 个(assert_eq!, default, one)。

tests::explicit_new_vim_operator_binding_still_conflicts_with_legacy_binding2486–2492 ↗
fn explicit_new_vim_operator_binding_still_conflicts_with_legacy_binding()

作用:确认用户显式把 Vim operator 的旧动作和新动作设成同一个键时会报冲突。

数据流:设置 motion_left 和 select_inner_text_object 都为 i → expect_conflict → 检查错误。

调用关系:覆盖 Vim operator 内部唯一性检查。

调用图:外部调用 3 个(default, expect_conflict, one)。

tests::vim_normal_defaults_include_insert_and_arrow_aliases2495–2533 ↗
fn vim_normal_defaults_include_insert_and_arrow_aliases()

作用:确认 Vim normal 模式默认既支持字母移动,也支持方向键和 Insert 进入插入模式。

数据流:取默认快捷键表 → 检查 enter_insert、move_left/right/up/down → 与预期列表比较。

调用关系:保护 Vim 默认体验,避免常用移动键被误删。

调用图:调用 1 个内部函数(defaults);外部调用 1 个(assert_eq!)。

tests::invalid_global_copy_binding_reports_global_path2536–2542 ↗
fn invalid_global_copy_binding_reports_global_path()

作用:确认 copy 的全局快捷键写错时,错误消息会指向 copy 的配置路径。

数据流:把 global.copy 设置为 meta-o → from_config 失败 → 检查错误包含 tui.keymap.global.copy。

调用关系:继续验证全局配置解析错误的可诊断性。

调用图:调用 1 个内部函数(from_config);外部调用 3 个(assert!, default, one)。

tests::rejects_conflicting_editor_bindings2545–2551 ↗
fn rejects_conflicting_editor_bindings()

作用:确认编辑器内部两个动作不能共用同一个键。

数据流:设置 editor.move_left 和 move_right 都为 ctrl-h → expect_conflict → 期待两者冲突。

调用关系:测试 editor 上下文的 validate_unique。

调用图:外部调用 3 个(default, expect_conflict, one)。

tests::rejects_conflicting_pager_bindings2554–2560 ↗
fn rejects_conflicting_pager_bindings()

作用:确认翻页窗口内部动作不能共用同一个键。

数据流:设置 pager.scroll_up 和 scroll_down 都为 ctrl-u → expect_conflict → 期待冲突。

调用关系:测试 pager 上下文唯一性检查。

调用图:外部调用 3 个(default, expect_conflict, one)。

tests::rejects_conflicting_list_bindings2563–2575 ↗
fn rejects_conflicting_list_bindings()

作用:确认列表上下、左右动作之间不能使用同一个键。

数据流:先让 move_up 和 move_down 都用 up,再让 move_left 和 move_right 都用 left → 每次都 expect_conflict。

调用关系:覆盖 list 基础方向动作的冲突检查。

调用图:外部调用 3 个(default, expect_conflict, one)。

tests::rejects_conflicting_list_page_and_jump_bindings2578–2584 ↗
fn rejects_conflicting_list_page_and_jump_bindings()

作用:确认列表翻页和跳转动作之间也不能共用按键。

数据流:设置 list.page_up 和 list.jump_top 都为 home → expect_conflict → 期待冲突。

调用关系:补充 list 新动作的唯一性测试。

调用图:外部调用 3 个(default, expect_conflict, one)。

tests::rejects_conflicting_approval_bindings2587–2593 ↗
fn rejects_conflicting_approval_bindings()

作用:确认审批弹窗里 approve 和 decline 不能用同一个键。

数据流:设置 approval.approve 和 approval.decline 都为 y → expect_conflict → 检查冲突。

调用关系:测试 approval 内部唯一性。

调用图:外部调用 3 个(default, expect_conflict, one)。

tests::rejects_conflicting_approval_deny_binding2596–2602 ↗
fn rejects_conflicting_approval_deny_binding()

作用:确认审批弹窗里 approve 和 deny 不能共用按键。

数据流:设置 approval.approve 和 approval.deny 都为 y → expect_conflict → 期待冲突。

调用关系:覆盖审批拒绝动作的冲突检查。

调用图:外部调用 3 个(default, expect_conflict, one)。

tests::rejects_conflicting_approval_overlay_accept_binding2605–2610 ↗
fn rejects_conflicting_approval_overlay_accept_binding()

作用:确认列表通用 accept 键不能和审批 approve 键冲突。

数据流:设置 list.accept 为 y,而 approval.approve 默认也是 y → expect_conflict → 期待 overlay 冲突。

调用关系:验证 list 与 approval 组合在审批弹窗里会一起检查。

调用图:外部调用 3 个(default, expect_conflict, one)。

tests::rejects_conflicting_approval_overlay_cancel_binding2613–2618 ↗
fn rejects_conflicting_approval_overlay_cancel_binding()

作用:确认列表 cancel 键不能和审批 cancel 键冲突。

数据流:设置 list.cancel 为 c,而 approval.cancel 默认也是 c → expect_conflict → 期待冲突。

调用关系:测试审批 overlay 中列表动作和审批动作的交叉冲突。

调用图:外部调用 3 个(default, expect_conflict, one)。

tests::reassignable_fixed_shortcuts_conflict_until_original_action_is_unbound2621–2630 ↗
fn reassignable_fixed_shortcuts_conflict_until_original_action_is_unbound()

作用:确认想把一个键改给别的动作时,原来占用这个键的动作必须先解绑。

数据流:先把 copy 设为 alt-.,它和 increase_reasoning_effort 默认冲突 → 报错;再把 increase_reasoning_effort 设为空 → 解析成功,copy 得到 alt-.。

调用关系:验证显式空数组解绑的实际用途。

调用图:调用 1 个内部函数(from_config);外部调用 6 个(assert_eq!, Many, default, expect_conflict, one, vec!)。

tests::kill_whole_line_can_be_assigned_without_default_binding2633–2646 ↗
fn kill_whole_line_can_be_assigned_without_default_binding()

作用:确认没有默认键的 kill_whole_line 动作也可以被用户配置出来。

数据流:设置 editor.kill_whole_line 为 ctrl-shift-u → from_config → 检查解析结果是对应组合键。

调用关系:测试空默认动作仍然支持用户自定义。

调用图:调用 1 个内部函数(from_config);外部调用 3 个(assert_eq!, default, one)。

tests::kill_whole_line_conflicts_until_kill_line_start_is_unbound2649–2661 ↗
fn kill_whole_line_conflicts_until_kill_line_start_is_unbound()

作用:确认 kill_whole_line 想使用 ctrl-u 时,必须先解绑默认占用 ctrl-u 的 kill_line_start。

数据流:先设置 kill_whole_line 为 ctrl-u → expect_conflict;再把 kill_line_start 设为空 → 解析成功。

调用关系:验证 editor 内部冲突和解绑流程。

调用图:调用 1 个内部函数(from_config);外部调用 6 个(assert_eq!, Many, default, expect_conflict, one, vec!)。

tests::toggle_fast_mode_can_be_assigned_without_default_binding2664–2677 ↗
fn toggle_fast_mode_can_be_assigned_without_default_binding()

作用:确认默认没有快捷键的 toggle_fast_mode 可以被用户手动绑定。

数据流:设置 global.toggle_fast_mode 为 ctrl-shift-f → 解析 → 检查 app.toggle_fast_mode 得到该组合键。

调用关系:测试 app 中空默认动作的可配置性。

调用图:调用 1 个内部函数(from_config);外部调用 3 个(assert_eq!, default, one)。

tests::toggle_fast_mode_conflicts_with_existing_main_surface_bindings2680–2685 ↗
fn toggle_fast_mode_conflicts_with_existing_main_surface_bindings()

作用:确认 toggle_fast_mode 如果绑定到已有主界面键,会被检测为冲突。

数据流:设置 toggle_fast_mode 为 ctrl-l,而 clear_terminal 默认也是 ctrl-l → expect_conflict。

调用关系:验证新增可配置动作同样参与 app 主路径唯一性检查。

调用图:外部调用 3 个(default, expect_conflict, one)。

tests::rejects_main_bindings_that_collide_with_remaining_fixed_shortcuts2688–2693 ↗
fn rejects_main_bindings_that_collide_with_remaining_fixed_shortcuts()

作用:确认主界面可配置动作不能占用仍然固定保留的快捷键,比如粘贴图片键。

数据流:设置 composer.submit 为 ctrl-v → expect_conflict → 错误应提到 fixed.paste_image。

调用关系:测试 MAIN_RESERVED_BINDINGS 保护逻辑。

调用图:外部调用 3 个(default, expect_conflict, one)。

tests::interrupt_turn_allows_backtrack_escape_and_can_be_remapped_or_unbound2696–2714 ↗
fn interrupt_turn_allows_backtrack_escape_and_can_be_remapped_or_unbound()

作用:确认默认 Esc 可以中断回合,同时用户可以把它改到别的键或取消绑定。

数据流:先解析默认配置,检查 interrupt_turn 是 Esc;再改成 f12,检查变成 f12;最后设为空数组,检查列表为空。

调用关系:验证 interrupt_turn 对固定 backtrack Esc 的特殊允许例外,以及重映射和解绑。

调用图:调用 1 个内部函数(from_config);外部调用 6 个(assert!, assert_eq!, Many, default, one, vec!)。

tests::interrupt_turn_rejects_other_fixed_shortcuts2717–2722 ↗
fn interrupt_turn_rejects_other_fixed_shortcuts()

作用:确认 interrupt_turn 不能占用其他固定保留键。

数据流:设置 chat.interrupt_turn 为 ctrl-v → expect_conflict → 错误应指向 fixed.paste_image。

调用关系:补充测试 validate_no_reserved 的例外只允许特定 Esc,不放开其他固定键。

调用图:外部调用 3 个(default, expect_conflict, one)。

tests::interrupt_turn_rejects_request_user_input_question_navigation_bindings2725–2731 ↗
fn interrupt_turn_rejects_request_user_input_question_navigation_bindings()

作用:确认请求用户输入的弹窗里,中断键不能和问题导航左右键冲突。

数据流:设置 interrupt_turn 和 list.move_right 都为 f12 → expect_conflict → 期待两者冲突。

调用关系:验证 request_user_input 特殊场景的遮挡检查。

调用图:外部调用 3 个(default, expect_conflict, one)。

tests::rejects_pager_bindings_that_collide_with_transcript_backtrack_keys2734–2739 ↗
fn rejects_pager_bindings_that_collide_with_transcript_backtrack_keys()

作用:确认 transcript 翻页窗口不能占用用于编辑上一条、下一条、确认编辑的固定键。

数据流:设置 pager.close 为 left → expect_conflict → 错误应提到 fixed.transcript_edit_previous。

调用关系:测试 TRANSCRIPT_BACKTRACK_RESERVED_BINDINGS 的保护规则。

调用图:外部调用 3 个(default, expect_conflict, one)。

tests::parses_function_keys_and_rejects_out_of_range_function_keys2742–2752 ↗
fn parses_function_keys_and_rejects_out_of_range_function_keys()

作用:确认功能键 F1 到最大允许值能解析,超出范围会失败。

数据流:分别解析 f1、f24、f25 → 前两个得到对应功能键,f25 得到空值。

调用关系:直接覆盖 parse_keybinding 对功能键范围的处理。

调用图:外部调用 1 个(assert_eq!)。

tests::parses_all_named_non_character_keys2755–2780 ↗
fn parses_all_named_non_character_keys()

作用:确认 tab、esc、方向键、home、page-down、space、minus 等命名键都能解析。

数据流:遍历一组固定字符串和预期 KeyCode → 调 parse_keybinding → 每个都比较输出。

调用关系:保护 parse_keybinding 对常见非字母键名称的支持。

调用图:外部调用 2 个(Char, assert_eq!)。

tests::rejects_modifier_only_and_nonnumeric_function_key_specs2783–2786 ↗
fn rejects_modifier_only_and_nonnumeric_function_key_specs()

作用:确认只有修饰键的写法和错误功能键写法不会被接受。

数据流:解析 ctrl 和 ff → 两者都应返回空值。

调用关系:测试 parse_keybinding 的严格性,避免模糊配置被误认为合法。

调用图:外部调用 1 个(assert_eq!)。

tests::parses_minus_alias_and_legacy_literal_minus2789–2802 ↗
fn parses_minus_alias_and_legacy_literal_minus()

作用:确认减号既可以写成 minus,也兼容旧的直接写 '-' 的方式。

数据流:解析 alt-minus、alt--、- → 都应得到减号键,只是修饰键不同。

调用关系:保护 parse_keybinding 的兼容写法,避免老配置失效。

调用图:外部调用 1 个(assert_eq!)。

tests::explicit_empty_array_unbinds_action2805–2810 ↗
fn explicit_empty_array_unbinds_action()

作用:确认用户把动作配置成空数组时,表示真的取消这个快捷键,而不是回退到默认值。

数据流:把 composer.toggle_shortcuts 设为空数组 → from_config → 检查对应绑定为空。

调用关系:验证 resolve_bindings 对显式空列表的语义。

调用图:调用 1 个内部函数(from_config);外部调用 4 个(assert!, Many, default, vec!)。

tests::raw_output_toggle_defaults_to_alt_r2813–2819 ↗
fn raw_output_toggle_defaults_to_alt_r()

作用:确认原始输出模式开关的默认快捷键是 Alt-R。

数据流:取默认快捷键表 → 读取 app.toggle_raw_output → 与 alt-r 比较。

调用关系:保护 built_in_defaults 中 raw output 的默认键。

调用图:调用 1 个内部函数(defaults);外部调用 1 个(assert_eq!)。

tests::raw_output_toggle_can_be_remapped2822–2832 ↗
fn raw_output_toggle_can_be_remapped()

作用:确认 raw output 开关可以被用户改到其他键。

数据流:设置 global.toggle_raw_output 为 f12 → from_config → 检查 app.toggle_raw_output 是 F12。

调用关系:验证该全局 app 动作的重映射流程。

调用图:调用 1 个内部函数(from_config);外部调用 3 个(assert_eq!, default, one)。

tests::default_editor_insert_newline_includes_current_aliases2835–2847 ↗
fn default_editor_insert_newline_includes_current_aliases()

作用:确认输入框插入换行的默认键包含多种终端可能发出的 Enter 变体。

数据流:取默认快捷键表 → 检查 editor.insert_newline 列表 → 确认含 ctrl-j、ctrl-m、enter、shift-enter、alt-enter。

调用关系:保护跨终端兼容性,因为不同终端对 Enter 组合键上报不同。

调用图:调用 1 个内部函数(defaults);外部调用 1 个(assert_eq!)。

tests::default_editor_delete_forward_word_includes_alt_d2850–2858 ↗
fn default_editor_delete_forward_word_includes_alt_d()

作用:确认向前删除一个词的默认键包含 Alt-D。

数据流:取默认快捷键表 → 查看 editor.delete_forward_word → 断言其中有 alt-d。

调用关系:保护常见编辑习惯快捷键。

调用图:调用 1 个内部函数(defaults);外部调用 1 个(assert!)。

tests::default_editor_deletion_includes_modified_backspace_delete_aliases2861–2906 ↗
fn default_editor_deletion_includes_modified_backspace_delete_aliases()

作用:确认删除相关默认键包含 Shift、Ctrl 等组合的 Backspace/Delete 变体。

数据流:取默认快捷键表 → 检查 delete_backward、delete_forward、delete_backward_word、delete_forward_word → 确认多个组合键存在。

调用关系:保护不同终端和键盘发送删除键变体时的兼容性。

调用图:调用 1 个内部函数(defaults);外部调用 1 个(assert!)。

tests::default_composer_toggle_shortcuts_includes_shift_question_mark2909–2917 ↗
fn default_composer_toggle_shortcuts_includes_shift_question_mark()

作用:确认打开 composer 快捷键提示的默认键包含 Shift-?。

数据流:取默认快捷键表 → 检查 composer.toggle_shortcuts → 确认有 shift-question-mark。

调用关系:保护问号键在不同终端 shift 上报差异下仍可用。

调用图:调用 1 个内部函数(defaults);外部调用 1 个(assert!)。

tests::default_approval_open_fullscreen_includes_ctrl_shift_a2920–2926 ↗
fn default_approval_open_fullscreen_includes_ctrl_shift_a()

作用:确认审批详情全屏打开默认支持 Ctrl-Shift-A。

数据流:取默认快捷键表 → 检查 approval.open_fullscreen → 确认包含 Ctrl+Shift+A。

调用关系:保护审批弹窗大内容查看的默认入口。

调用图:调用 1 个内部函数(defaults);外部调用 1 个(assert!)。

tests::primary_binding_returns_first_or_none2929–2939 ↗
fn primary_binding_returns_first_or_none()

作用:确认 primary_binding 有按键时返回第一个,没有按键时返回空。

数据流:传入两个按键的列表 → 返回第一个;再传入空列表 → 返回 None。

调用关系:直接测试界面提示取主快捷键的小工具。

调用图:外部调用 2 个(assert_eq!, vec!)。

tests::defaults_pass_conflict_validation2942–2946 ↗
fn defaults_pass_conflict_validation()

作用:确认程序自带的默认快捷键表本身没有冲突。

数据流:取默认 RuntimeKeymap → 调 validate_conflicts → 期待成功。

调用关系:这是兜底回归测试,保证 built_in_defaults 改动后仍是一张可用的快捷键表。

调用图:调用 1 个内部函数(defaults)。