配置管理服务和可编辑持久化表面
这一阶段像系统的“设置柜台”,不是主循环干活儿,而是在幕后让前端和后台都能读、改、保存配置。客户端来的配置请求先到处理器,由它分发给配置读写服务;服务负责安全查看和修改设置,并说明哪些设置被更高规则盖住。修改 config.toml 时,编辑工具和 TOML 小助手会尽量保留原来的注释和排版。加载配置出错时,错误转换器把原因翻成前端看得懂的话。它还会导入外部工具的配置,迁移老用户的个性设置,并让后台服务记住自己的本地开关。
应用服务器配置 API
这些文件通过应用服务器 RPC 公开配置管理,转换错误,并将已验证的读取和写入委托给服务层。
app-server/src/request_processors/config_errors.rs源码 ↗
加载配置时,错误可能被一层层包起来,就像快递盒里套着小盒子。这个文件先从普通的输入输出错误里往里翻,看看里面是不是藏着“云端配置包加载失败”这种更具体的错误。如果找到了,它会把原因、错误码、详细说明、HTTP 状态码等信息放进 JSON-RPC 错误的 data 字段里。JSON-RPC 是一种用 JSON 传请求和响应的通信格式,data 字段可以带额外说明。特别重要的是:如果错误码表示认证失败,也就是登录身份不对或过期,它会额外写上 action: relogin,方便客户端直接引导用户重新登录。这样,前端不用猜错误原因,用户也不会只看到一句模糊的“加载失败”。
cloud_config_bundle_load_error3–14 ↗
fn cloud_config_bundle_load_error(err: &std::io::Error) -> Option<&CloudConfigBundleLoadError>
作用:这个函数像是在一串套娃错误里找“真正有用的那一个”。它从一个普通的文件或网络读写错误里,尝试挖出更具体的 CloudConfigBundleLoadError,也就是“云端配置包加载失败”的错误。
数据流:进去的是一个 std::io::Error,也就是 Rust 里常见的输入输出错误。函数先读取这个错误内部包着的原始原因,然后沿着 source 这条错误链一层层往下看;每一层都检查是不是 CloudConfigBundleLoadError。找到了就返回这个具体错误的引用,找不到就返回 None,原始错误本身不会被改动。
调用关系:它是 config_load_error 的辅助工具。config_load_error 在准备给客户端返回错误前,会先叫它帮忙确认:这次失败是不是云端配置包的问题。如果是,后面才能生成更详细、更适合前端处理的错误信息。
调用图:被 1 处调用(config_load_error);外部调用 1 个(get_ref)。
config_load_error16–35 ↗
fn config_load_error(err: &std::io::Error) -> JSONRPCErrorError
作用:这个函数把“配置加载失败”包装成一个标准的 JSON-RPC 错误,准备返回给调用方。它会尽量附带可操作的信息,比如错误码、状态码,认证失败时还会提示客户端让用户重新登录。
数据流:进去的是一个 std::io::Error,表示配置读取或下载时出了问题。函数先调用 cloud_config_bundle_load_error 看里面有没有更具体的云端配置包错误;如果有,就组装一份 JSON 数据,写入失败原因、错误码、详细文字,必要时再加 HTTP 状态码和 relogin 操作提示。然后它创建一个 invalid_request 错误,把“failed to load configuration: ...”作为主错误信息,并把刚才组装的 data 塞进去。出来的是一个 JSONRPCErrorError,供上层直接返回给客户端。
调用关系:它是这个文件对外给同级请求处理代码使用的主要入口。当某个请求触发配置加载、但加载失败时,上层会调用它来生成统一格式的响应。它自己负责判断能不能补充云端配置错误细节,并把最终错误交回给请求处理流程。
调用图:调用 1 个内部函数(cloud_config_bundle_load_error);外部调用 1 个(format!)。
app-server/src/request_processors/config_processor.rs源码 ↗
这个文件解决的是“配置不能乱读乱改”的问题。它一边接客户端请求,一边调用 ConfigManager(配置管家)去读写真实配置;改完后还会清掉插件和技能缓存,必要时把新配置刷新到正在运行的会话里,避免用户改了设置但程序还按旧设置干活。它还会把内部配置格式翻译成对外 API 能看懂的格式,好比把后台账本翻成前台菜单。比较重要的是:实验功能只允许一小批白名单键;插件启用或关闭会打点上报;写配置失败时会把错误包装成客户端能理解的 JSON-RPC 错误(JSON-RPC 是一种用 JSON 发请求和回响应的协议)。
ConfigRequestProcessor::new66–78 ↗
fn new(
outgoing: Arc<OutgoingMessageSender>,
config_manager: ConfigManager,
thread_manager: Arc<ThreadManager>,
analytics_events_client: AnalyticsEventsClient,
) -
作用:创建一个配置请求处理器,把它之后工作要用的几个“工具”装进去,比如发消息的人、配置管家、线程管理器和分析打点客户端。
数据流:输入是 outgoing、config_manager、thread_manager、analytics_events_client → 函数把它们保存到结构体里 → 输出一个可以处理配置请求的 ConfigRequestProcessor。
调用关系:它通常在服务器初始化相关流程里被调用,为后面 handle_initialized_client_request 分发过来的配置请求准备好处理对象。
调用图:被 1 处调用(new)。
ConfigRequestProcessor::read80–107 ↗
async fn read(
&self,
params: ConfigReadParams,
) -> Result<ConfigReadResponse, JSONRPCErrorError>
作用:读取当前配置,并额外补上几个受支持的实验功能开关状态,让客户端一次拿到完整可展示的信息。
数据流:输入是读取参数,里面可能带当前工作目录 → 它先让配置管家读取配置,再加载最新运行配置,逐个检查支持的实验功能是否启用 → 输出 ConfigReadResponse,里面的 additional.features 会被补齐。
调用关系:客户端发起读配置请求时,handle_initialized_client_request 会调用它;它把实际读文件的活交给配置管家,把判断实验功能的活交给 feature_for_key 和 load_latest_config。
调用图:调用 1 个内部函数(load_latest_config);被 1 处调用(handle_initialized_client_request);外部调用 3 个(read, feature_for_key, json!)。
ConfigRequestProcessor::config_requirements_read109–120 ↗
async fn config_requirements_read(
&self,
) -> Result<ConfigRequirementsReadResponse, JSONRPCErrorError>
作用:读取“配置要求”,也就是系统或组织规定哪些设置允许、哪些不允许。
数据流:输入没有额外参数 → 它调用配置管家读取要求文件,再把内部 TOML 配置格式转成对外 API 格式 → 输出 ConfigRequirementsReadResponse。
调用关系:客户端询问配置限制时,handle_initialized_client_request 会调用它;它依赖 read_requirements 取原始要求,并通过映射函数把结果翻译给客户端。
调用图:被 1 处调用(handle_initialized_client_request);外部调用 1 个(read_requirements)。
ConfigRequestProcessor::value_write122–129 ↗
async fn value_write(
&self,
params: ConfigValueWriteParams,
) -> Result<ClientResponsePayload, JSONRPCErrorError>
作用:处理单个配置项的写入,比如只改一个开关或一个路径。
数据流:输入是一个键路径和值 → 它调用内部 write_value 真正写入,再统一处理“配置被改动后要做的收尾” → 输出包装成 ClientResponsePayload 的写入结果。
调用关系:客户端写单项配置时,handle_initialized_client_request 会调用它;它把写入细节交给 write_value,把清缓存这类后续动作交给 handle_config_mutation_result。
调用图:调用 2 个内部函数(handle_config_mutation_result, write_value);被 1 处调用(handle_initialized_client_request)。
ConfigRequestProcessor::batch_write131–138 ↗
async fn batch_write(
&self,
params: ConfigBatchWriteParams,
) -> Result<ClientResponsePayload, JSONRPCErrorError>
作用:处理一次改多个配置项的请求,避免客户端一项一项慢慢提交。
数据流:输入是一批编辑内容和可能的刷新标记 → 它调用 batch_write_inner 执行批量写入,再统一做配置变更后的收尾 → 输出包装后的批量写入结果。
调用关系:客户端批量保存设置时,handle_initialized_client_request 会调用它;它负责把外层协议响应和内部批量写逻辑接起来。
调用图:调用 2 个内部函数(batch_write_inner, handle_config_mutation_result);被 1 处调用(handle_initialized_client_request)。
ConfigRequestProcessor::experimental_feature_enablement_set140–155 ↗
async fn experimental_feature_enablement_set(
&self,
request_id: ConnectionRequestId,
params: ExperimentalFeatureEnablementSetParams,
) -> Result<Option<ClientResponsePaylo
作用:设置实验功能开关,并直接把响应按原请求编号发回客户端。
数据流:输入是请求编号和一组实验功能开关 → 它先设置这些开关,再做配置变更后的清理,最后通过 outgoing 主动发送响应 → 返回 None,表示响应已经发出,不需要外层再包一次。
调用关系:handle_initialized_client_request 收到实验功能设置请求时会调用它;它把校验和写入交给 set_experimental_feature_enablement,把发送响应交给 outgoing。
调用图:调用 2 个内部函数(handle_config_mutation_result, set_experimental_feature_enablement);被 1 处调用(handle_initialized_client_request);外部调用 1 个(ExperimentalFeatureEnablementSet)。
ConfigRequestProcessor::model_provider_capabilities_read157–168 ↗
async fn model_provider_capabilities_read(
&self,
) -> Result<ModelProviderCapabilitiesReadResponse, JSONRPCErrorError>
作用:告诉客户端当前模型提供方支持哪些能力,比如命名空间工具、图片生成、网页搜索。
数据流:输入没有额外参数 → 它加载最新配置,根据配置创建模型提供方对象,再读取它声明的能力 → 输出 ModelProviderCapabilitiesReadResponse。
调用关系:客户端需要知道模型能做什么时,handle_initialized_client_request 会调用它;它依赖 load_latest_config 拿配置,依赖 create_model_provider 生成对应的模型提供方。
调用图:调用 1 个内部函数(load_latest_config);被 1 处调用(handle_initialized_client_request);外部调用 1 个(create_model_provider)。
ConfigRequestProcessor::handle_config_mutation170–173 ↗
async fn handle_config_mutation(&self)
作用:在配置被改动后,清掉插件和技能的缓存,防止程序继续使用旧信息。
数据流:输入是当前处理器保存的线程管理器 → 它找到插件管理器和技能管理器并清空缓存 → 没有返回值,但运行时缓存状态被更新。
调用关系:handle_config_mutation_result 会在写配置成功后调用它;import 流程也会用它来通知系统“配置变了”。
调用图:被 2 处调用(handle_config_mutation_result, import)。
ConfigRequestProcessor::handle_config_mutation_result175–182 ↗
async fn handle_config_mutation_result(
&self,
result: std::result::Result<T, JSONRPCErrorError>,
) -> Result<T, JSONRPCErrorError>
作用:统一处理配置写入结果:成功就做后续清理,失败就把错误原样传出去。
数据流:输入是某个配置修改操作的结果 → 如果是错误,直接返回错误;如果成功,调用 handle_config_mutation 清缓存 → 输出原本的成功结果。
调用关系:value_write、batch_write 和 experimental_feature_enablement_set 都通过它走同一套收尾流程,避免每个入口自己重复写清缓存逻辑。
调用图:调用 1 个内部函数(handle_config_mutation);被 3 处调用(batch_write, experimental_feature_enablement_set, value_write)。
ConfigRequestProcessor::load_latest_config184–196 ↗
async fn load_latest_config(
&self,
fallback_cwd: Option<PathBuf>,
) -> Result<codex_core::config::Config, JSONRPCErrorError>
作用:加载当前最新的完整配置,并把加载失败转换成客户端能收到的内部错误。
数据流:输入是可选的备用当前目录 → 它让配置管家重新解析最新配置 → 成功输出 Config,失败输出 JSON-RPC 错误。
调用关系:read、model_provider_capabilities_read、reload_user_config 和 set_experimental_feature_enablement 都会用它,保证拿到的是最新配置而不是旧缓存。
调用图:调用 1 个内部函数(load_latest_config);被 4 处调用(model_provider_capabilities_read, read, reload_user_config, set_experimental_feature_enablement)。
ConfigRequestProcessor::write_value198–212 ↗
async fn write_value(
&self,
params: ConfigValueWriteParams,
) -> Result<ConfigWriteResponse, JSONRPCErrorError>
作用:真正执行单项配置写入,并在涉及插件启用状态时发送分析事件。
数据流:输入是一个配置键和值 → 它先从这次修改里找出可能的插件开关变化,再让配置管家写入,最后按变化发送插件启用或禁用打点 → 输出 ConfigWriteResponse。
调用关系:value_write 会调用它;它把磁盘或配置层面的写入交给配置管家,把插件打点交给 emit_plugin_toggle_events。
调用图:调用 2 个内部函数(emit_plugin_toggle_events, collect_plugin_enabled_candidates);被 1 处调用(value_write);外部调用 1 个(write_value)。
ConfigRequestProcessor::batch_write_inner214–235 ↗
async fn batch_write_inner(
&self,
params: ConfigBatchWriteParams,
) -> Result<ConfigWriteResponse, JSONRPCErrorError>
作用:真正执行批量配置写入,并处理插件打点和可选的运行中配置刷新。
数据流:输入是一批配置编辑和 reload_user_config 标记 → 它收集插件开关变化,调用配置管家批量写入,发送插件打点;如果要求刷新,就把新配置推给已有线程 → 输出 ConfigWriteResponse。
调用关系:batch_write 会调用它;它把批量保存交给配置管家,把插件事件交给 emit_plugin_toggle_events,把运行时刷新交给 reload_user_config。
调用图:调用 3 个内部函数(emit_plugin_toggle_events, reload_user_config, collect_plugin_enabled_candidates);被 1 处调用(batch_write);外部调用 1 个(batch_write)。
ConfigRequestProcessor::set_experimental_feature_enablement237–272 ↗
async fn set_experimental_feature_enablement(
&self,
params: ExperimentalFeatureEnablementSetParams,
) -> Result<ExperimentalFeatureEnablementSetResponse, JSONRPCErrorError>
作用:设置运行时实验功能开关,同时过滤掉不支持或不认识的功能名。
数据流:输入是一张“功能名到是否启用”的表 → 它删除非法键并记录警告,若还有有效项就写入运行时功能覆盖配置,重新加载配置并刷新用户配置 → 输出实际接受的 enablement。
调用关系:experimental_feature_enablement_set 会调用它;它依赖 canonical_feature_for_key 和白名单判断合法性,成功后用 reload_user_config 让正在跑的会话感知变化。
调用图:调用 3 个内部函数(extend_runtime_feature_enablement, load_latest_config, reload_user_config);被 1 处调用(experimental_feature_enablement_set);外部调用 2 个(new, warn!)。
ConfigRequestProcessor::reload_user_config274–292 ↗
async fn reload_user_config(&self)
作用:把最新用户配置刷新到所有正在运行的线程里,避免新旧设置同时存在。
数据流:输入是处理器当前状态 → 它先加载最新配置,失败就记警告并停止;成功后列出所有线程,逐个取出线程并调用刷新方法 → 没有返回值,但运行中线程会改用新配置。
调用关系:batch_write_inner 在需要刷新时会调用它,set_experimental_feature_enablement 修改实验功能后也会调用它。
调用图:调用 1 个内部函数(load_latest_config);被 2 处调用(batch_write_inner, set_experimental_feature_enablement);外部调用 1 个(warn!)。
ConfigRequestProcessor::emit_plugin_toggle_events294–313 ↗
async fn emit_plugin_toggle_events(
&self,
pending_changes: std::collections::BTreeMap<String, bool>,
)
作用:当插件被启用或禁用时,发送相应的分析打点,方便统计插件使用情况。
数据流:输入是一张插件 ID 到启用状态的表 → 它逐个解析插件 ID,读取已安装插件的遥测元数据,再按状态发送 enabled 或 disabled 事件 → 没有业务返回值,只产生分析事件。
调用关系:write_value 和 batch_write_inner 在配置写成功后调用它;它把插件信息读取交给 installed_plugin_telemetry_metadata,把事件发送交给 analytics_events_client。
调用图:调用 5 个内部函数(track_plugin_disabled, track_plugin_enabled, codex_home, installed_plugin_telemetry_metadata, parse);被 2 处调用(batch_write_inner, write_value)。
map_requirements_toml_to_api316–380 ↗
fn map_requirements_toml_to_api(requirements: ConfigRequirementsToml) -> ConfigRequirements
作用:把内部 TOML 配置要求翻译成客户端 API 使用的配置要求格式。TOML 是一种常见配置文件格式,适合人写;API 格式适合程序通过网络传。
数据流:输入是 ConfigRequirementsToml → 它逐项转换审批策略、沙箱模式、权限档案、网页搜索、钩子、网络限制等字段,并补上一些兼容性处理 → 输出 ConfigRequirements。
调用关系:它是读取配置要求时的核心翻译层;多个测试函数也直接调用它,确认关键字段不会在翻译时丢失。
调用图:被 6 处调用(requirements_api_includes_allow_appshots, requirements_api_includes_allow_managed_hooks_only, requirements_api_includes_allow_remote_control, requirements_api_includes_allowed_windows_sandbox_implementations, requirements_api_includes_computer_use_requirements, requirements_api_includes_permission_default_and_allowlist)。
map_computer_use_requirements_to_api382–388 ↗
fn map_computer_use_requirements_to_api(
computer_use: codex_config::ComputerUseRequirementsToml,
) -> ComputerUseRequirements
作用:把“电脑使用”相关限制从配置文件格式转成 API 格式。
数据流:输入是 ComputerUseRequirementsToml → 它取出是否允许锁屏电脑使用这一项 → 输出 ComputerUseRequirements。
调用关系:它服务于配置要求翻译流程,是 map_requirements_toml_to_api 处理 computer_use 字段时的小转换器。
map_hooks_requirements_to_api390–423 ↗
fn map_hooks_requirements_to_api(hooks: ManagedHooksRequirementsToml) -> ManagedHooksRequirements
作用:把托管钩子要求转成 API 格式。钩子可以理解成“某些事情发生前后自动运行的小动作”。
数据流:输入是 ManagedHooksRequirementsToml → 它拆出托管目录和各类事件钩子,再把每类钩子的匹配组逐一转换 → 输出 ManagedHooksRequirements。
调用关系:它在配置要求翻译中处理 hooks 字段,并把每组钩子的细节交给 map_hook_matcher_groups_to_api。
调用图:调用 1 个内部函数(map_hook_matcher_groups_to_api)。
map_hook_matcher_groups_to_api425–432 ↗
fn map_hook_matcher_groups_to_api(
groups: Vec<CoreMatcherGroup>,
) -> Vec<ConfiguredHookMatcherGroup>
作用:把一批钩子匹配组从内部格式转成 API 格式。
数据流:输入是一组 CoreMatcherGroup → 它逐个调用 map_hook_matcher_group_to_api 转换 → 输出 ConfiguredHookMatcherGroup 列表。
调用关系:map_hooks_requirements_to_api 会调用它;它像流水线中间的一段,把“多组”拆成“每组”处理。
调用图:被 1 处调用(map_hooks_requirements_to_api)。
map_hook_matcher_group_to_api434–443 ↗
fn map_hook_matcher_group_to_api(group: CoreMatcherGroup) -> ConfiguredHookMatcherGroup
作用:转换单个钩子匹配组,保留匹配条件,并转换里面的钩子处理器。
数据流:输入是一个 CoreMatcherGroup → 它复制 matcher,并把 hooks 列表里的每个处理器转成 API 处理器 → 输出 ConfiguredHookMatcherGroup。
调用关系:它通常由 map_hook_matcher_groups_to_api 批量调用,负责每个组内部的细节翻译。
map_hook_handler_to_api445–463 ↗
fn map_hook_handler_to_api(handler: CoreHookHandlerConfig) -> ConfiguredHookHandler
作用:把单个钩子处理器转成客户端能理解的形式,比如命令、提示或代理。
数据流:输入是 CoreHookHandlerConfig → 它根据处理器类型分别保留命令、Windows 命令、超时、异步标记、状态消息等字段 → 输出 ConfiguredHookHandler。
调用关系:它是钩子翻译链条最底层的小零件,由匹配组转换时用于处理每个具体钩子动作。
map_sandbox_mode_requirement_to_api465–472 ↗
fn map_sandbox_mode_requirement_to_api(mode: CoreSandboxModeRequirement) -> Option<SandboxMode>
作用:把内部沙箱模式要求转成 API 沙箱模式。沙箱就是给程序活动范围加围栏,防止它乱动系统。
数据流:输入是 CoreSandboxModeRequirement → 它把只读、工作区可写、完全访问转成 API 值;遇到外部沙箱则返回 None,因为这个 API 不暴露它 → 输出可选的 SandboxMode。
调用关系:它服务于配置要求翻译流程,用来处理 allowed_sandbox_modes。
map_residency_requirement_to_api474–480 ↗
fn map_residency_requirement_to_api(
residency: CoreResidencyRequirement,
) -> codex_app_server_protocol::ResidencyRequirement
作用:把数据驻留要求转成 API 格式。数据驻留指数据必须留在哪个地区。
数据流:输入是 CoreResidencyRequirement → 目前只有美国 Us 这一种映射 → 输出协议层的 ResidencyRequirement。
调用关系:它服务于配置要求翻译流程,处理 enforce_residency 字段。
map_network_requirements_to_api482–530 ↗
fn map_network_requirements_to_api(
network: codex_config::NetworkRequirementsToml,
) -> NetworkRequirements
作用:把网络访问要求从配置文件格式转成 API 格式,包括端口、代理、域名白名单/黑名单和 Unix socket 权限。
数据流:输入是 NetworkRequirementsToml → 它提取允许域名、拒绝域名、允许的 Unix socket,并逐项转换权限枚举 → 输出 NetworkRequirements。
调用关系:它服务于配置要求翻译流程;转换域名和 Unix socket 权限时会使用对应的小映射函数。
map_network_domain_permission_to_api532–539 ↗
fn map_network_domain_permission_to_api(
permission: codex_config::NetworkDomainPermissionToml,
) -> NetworkDomainPermission
作用:把域名访问权限从内部配置值转成 API 值,只区分允许和拒绝。
数据流:输入是 NetworkDomainPermissionToml → 它把 Allow 转成 NetworkDomainPermission::Allow,把 Deny 转成 NetworkDomainPermission::Deny → 输出 API 权限值。
调用关系:它是 map_network_requirements_to_api 的底层辅助,用来翻译每条域名规则。
map_network_unix_socket_permission_to_api541–548 ↗
fn map_network_unix_socket_permission_to_api(
permission: codex_config::NetworkUnixSocketPermissionToml,
) -> NetworkUnixSocketPermission
作用:把 Unix socket 权限从内部配置值转成 API 值。Unix socket 可以理解成本机程序之间通信的一种特殊“插口”。
数据流:输入是 NetworkUnixSocketPermissionToml → 它把允许或拒绝分别转换成 API 的允许或拒绝 → 输出 NetworkUnixSocketPermission。
调用关系:它是 map_network_requirements_to_api 的底层辅助,用来翻译每条 Unix socket 规则。
map_error550–556 ↗
fn map_error(err: ConfigManagerError) -> JSONRPCErrorError
作用:把配置管家产生的错误翻译成 JSON-RPC 错误,方便客户端按统一格式处理失败。
数据流:输入是 ConfigManagerError → 它先看这个错误有没有专门的写配置错误码;有就生成带错误码的无效请求错误,没有就生成内部错误 → 输出 JSONRPCErrorError。
调用关系:读写配置的多个入口会在调用配置管家失败时使用它;如果是写入类错误,它会把活交给 config_write_error。
调用图:调用 3 个内部函数(write_error_code, internal_error, config_write_error);外部调用 1 个(to_string)。
config_write_error558–564 ↗
fn config_write_error(code: ConfigWriteErrorCode, message: impl Into<String>) -> JSONRPCErrorError
作用:生成一个带配置写入错误码的客户端错误,让前端不仅知道失败,还知道失败类型。
数据流:输入是 ConfigWriteErrorCode 和错误消息 → 它先创建 invalid_request 错误,再把 config_write_error_code 塞进 error.data → 输出 JSONRPCErrorError。
调用关系:map_error 在发现配置写入错误码时会调用它;它负责把错误细节包装成协议层能传输的 JSON 数据。
调用图:调用 1 个内部函数(invalid_request);被 1 处调用(map_error);外部调用 1 个(json!)。
tests::requirements_api_includes_allow_managed_hooks_only577–585 ↗
fn requirements_api_includes_allow_managed_hooks_only()
作用:测试 allow_managed_hooks_only 这个字段在格式转换后不会丢失。
数据流:输入是构造出的 ConfigRequirementsToml,其中 allow_managed_hooks_only 为 true → 它调用 map_requirements_toml_to_api 转换 → 用断言确认输出字段仍是 Some(true),并且 hooks 为空。
调用关系:这是 map_requirements_toml_to_api 的单元测试之一,专门守住托管钩子限制字段。
调用图:调用 1 个内部函数(map_requirements_toml_to_api);外部调用 2 个(assert_eq!, default)。
tests::requirements_api_includes_permission_default_and_allowlist588–609 ↗
fn requirements_api_includes_permission_default_and_allowlist()
作用:测试权限档案的默认值和允许列表在转换后都保留下来。
数据流:输入是带 allowed_permission_profiles 和 default_permissions 的配置要求 → 它转换成 API 格式 → 断言允许列表和默认权限名称完全一致。
调用关系:它直接验证 map_requirements_toml_to_api,防止权限配置在后台到前端的翻译过程中变形。
调用图:调用 1 个内部函数(map_requirements_toml_to_api);外部调用 3 个(from, assert_eq!, default)。
tests::requirements_api_includes_allow_appshots612–620 ↗
fn requirements_api_includes_allow_appshots()
作用:测试 allow_appshots 字段能正确出现在 API 结果里。
数据流:输入是 allow_appshots 为 false 的配置要求 → 转换后检查输出仍为 Some(false),同时 hooks 仍为空 → 测试通过表示该字段没有被漏掉。
调用关系:它是配置要求映射函数的回归测试,保证新增或修改字段时不会影响 appshots 限制。
调用图:调用 1 个内部函数(map_requirements_toml_to_api);外部调用 2 个(assert_eq!, default)。
tests::requirements_api_includes_allow_remote_control623–630 ↗
fn requirements_api_includes_allow_remote_control()
作用:测试远程控制开关限制能从配置要求传到 API 响应。
数据流:输入是 allow_remote_control 为 false 的配置要求 → 调用映射函数 → 断言输出字段也是 Some(false)。
调用关系:它直接覆盖 map_requirements_toml_to_api 中 remote control 字段的转换行为。
调用图:调用 1 个内部函数(map_requirements_toml_to_api);外部调用 2 个(assert_eq!, default)。
tests::requirements_api_includes_computer_use_requirements633–647 ↗
fn requirements_api_includes_computer_use_requirements()
作用:测试电脑使用相关限制,尤其是锁屏状态下是否允许使用电脑这一项。
数据流:输入是带 computer_use.allow_locked_computer_use=false 的配置要求 → 转换成 API 格式 → 从输出里取出嵌套字段并断言为 Some(false)。
调用关系:它验证 map_requirements_toml_to_api 以及电脑使用要求转换链路没有漏掉嵌套字段。
调用图:调用 1 个内部函数(map_requirements_toml_to_api);外部调用 2 个(assert_eq!, default)。
tests::requirements_api_includes_allowed_windows_sandbox_implementations650–668 ↗
fn requirements_api_includes_allowed_windows_sandbox_implementations()
作用:测试 Windows 沙箱实现方式的允许列表能正确转换。
数据流:输入是包含 Elevated 和 Unelevated 两种 Windows 沙箱模式的配置要求 → 转成 API 格式 → 断言输出列表对应为 WindowsSandboxSetupMode 的 Elevated 和 Unelevated。
调用关系:它验证 map_requirements_toml_to_api 对 Windows 专属沙箱配置的处理,防止平台相关限制在转换时丢失。
调用图:调用 1 个内部函数(map_requirements_toml_to_api);外部调用 3 个(assert_eq!, default, vec!)。
app-server/src/config_manager_service.rs源码 ↗
这份文件解决的是“配置从哪里来、能不能改、改完是否真的生效”的问题。系统里的配置不是只有一个文件,而是像几张透明纸叠在一起:用户配置、项目配置、系统托管配置、会话参数等都可能覆盖彼此。这里的 ConfigManager 会先把这些配置层读出来,合成最终生效的配置;写入时只允许改用户自己的配置文件,避免客户端误改系统或企业管控配置。它还会检查版本,防止两个人或两个请求同时改同一份配置造成覆盖。修改时,它把类似 foo.bar 的路径拆开,把 JSON 值转成 TOML 值,按策略合并或删除,再做配置合法性检查,最后用原子写入(一种尽量避免写坏文件的写法)保存。写完后,它还会判断刚改的值是否被更高层配置压住,并返回“你改了,但现在没生效”的说明。
ConfigManagerError::write77–82 ↗
fn write(code: ConfigWriteErrorCode, message: impl Into<String>) -> Self
作用:把“写配置失败”的原因包装成统一错误,里面带有给客户端看的错误码和说明文字。
数据流:进去的是一个写入错误码和一段说明文字 → 它把文字转成字符串,放进 Write 错误类型里 → 出来的是一个 ConfigManagerError,后面可以直接返回给调用方。
调用关系:它主要被 ConfigManager::apply_edits 使用。写配置过程中只要发现路径不允许、版本冲突、配置不合法等问题,就会用它生成一个明确的写入错误。
调用图:被 1 处调用(apply_edits);外部调用 1 个(into)。
ConfigManagerError::io84–86 ↗
fn io(context: &'static str, source: std::io::Error) -> Self
作用:把文件读写、路径解析这类输入输出错误包装成统一错误,并附上一句“当时在做什么”。
数据流:进去的是一段上下文说明和系统返回的 IO 错误 → 它把两者放进 Io 错误里 → 出来的是带背景信息的错误,方便用户或日志看懂失败地点。
调用关系:它会在创建或读取用户配置层时被 create_empty_user_layer 使用;读配置、解析路径、写空配置文件失败时也会通过这种形式往外报。
调用图:被 1 处调用(create_empty_user_layer)。
ConfigManagerError::json88–90 ↗
fn json(context: &'static str, source: serde_json::Error) -> Self
作用:把 JSON 转换失败包装成统一错误。JSON 可以理解成前后端传数据常用的一种文本格式。
数据流:进去的是一段说明和 JSON 库给出的错误 → 它们被装进 Json 错误 → 出来的是带上下文的配置服务错误。
调用关系:它在读取配置给 API 返回时很有用:ConfigManager::read 会把内部配置转成 API 需要的 JSON 形态,转换失败就靠它说明问题。
ConfigManagerError::toml92–94 ↗
fn toml(context: &'static str, source: toml::de::Error) -> Self
作用:把 TOML 配置解析或校验失败包装成统一错误。TOML 是这个项目用来保存配置文件的格式。
数据流:进去的是上下文说明和 TOML 解析错误 → 它把这些信息放进 Toml 错误 → 出来的是能说明“哪一步配置格式不对”的错误。
调用关系:它常出现在读取或校验配置时,比如 ConfigManager::read 发现配置不能变成合法结构,或者 create_empty_user_layer 读到已有配置文件但解析失败。
ConfigManagerError::anyhow96–98 ↗
fn anyhow(context: &'static str, source: anyhow::Error) -> Self
作用:把一些来源比较杂、不能简单归类的错误包装成统一错误。
数据流:进去的是上下文说明和一个通用错误 → 它们被放进 Anyhow 错误 → 出来的是配置服务可以统一返回的错误。
调用关系:它用于那些底层工具已经返回通用错误的地方,比如构造配置编辑内容、保存配置文件,或者后台写文件任务异常。
ConfigManagerError::write_error_code100–105 ↗
fn write_error_code(&self) -> Option<ConfigWriteErrorCode>
作用:从错误里取出写配置专用的错误码;如果这个错误不是写入类错误,就返回空。
数据流:进去的是一个 ConfigManagerError → 它检查是不是 Write 类型 → 如果是就拿出错误码,否则给出 None。
调用关系:它被 map_error 调用,用来把内部错误翻译成协议层能理解的响应;这样客户端可以根据错误码做不同提示。
调用图:被 1 处调用(map_error)。
ConfigManager::read109–151 ↗
async fn read(
&self,
params: ConfigReadParams,
) -> Result<ConfigReadResponse, ConfigManagerError>
作用:读取当前生效的配置,并按需要附带每一层配置的来源信息。
数据流:进去的是读取参数,可能带当前工作目录和是否包含配置层详情 → 它按目录加载相关配置层,合成最终配置,再把内部 TOML 结构转成 API 返回结构 → 出来的是配置内容、每项来源,以及可选的配置层列表。
调用关系:这是客户端读配置时会走的入口。它需要无目录时调用 ConfigManager::load_thread_agnostic_config,有目录时加载带项目上下文的配置层,然后把结果交给协议响应返回。
调用图:调用 2 个内部函数(load_thread_agnostic_config, try_from);外部调用 3 个(from, from_value, to_value)。
ConfigManager::read_requirements153–167 ↗
async fn read_requirements(
&self,
) -> Result<Option<ConfigRequirementsToml>, ConfigManagerError>
作用:读取配置要求,也就是某些功能或环境必须满足的约束;没有要求时返回空。
数据流:进去时不需要额外参数 → 它加载不依赖当前项目目录的配置层,取出 requirements 配置 → 如果内容为空就返回 None,否则返回要求内容。
调用关系:它用于外部想单独查看配置约束的时候。它把加载配置层的工作交给 ConfigManager::load_thread_agnostic_config。
调用图:调用 1 个内部函数(load_thread_agnostic_config)。
ConfigManager::write_value169–176 ↗
async fn write_value(
&self,
params: ConfigValueWriteParams,
) -> Result<ConfigWriteResponse, ConfigManagerError>
作用:写入单个配置项,比如把某个开关改成 true,或者删除某个键。
数据流:进去的是一个键路径、一个值、合并策略、目标文件和预期版本 → 它把单次修改包装成一个编辑列表 → 交给 ConfigManager::apply_edits 统一处理,出来的是写入结果。
调用关系:这是单项写入的简化入口。它不自己保存文件,而是把活儿交给 ConfigManager::apply_edits,这样单项写入和批量写入走同一套安全检查。
调用图:调用 1 个内部函数(apply_edits);外部调用 1 个(vec!)。
ConfigManager::batch_write178–190 ↗
async fn batch_write(
&self,
params: ConfigBatchWriteParams,
) -> Result<ConfigWriteResponse, ConfigManagerError>
作用:一次写入多个配置项,适合界面上点“保存”时同时提交好几处改动。
数据流:进去的是一批编辑、目标文件和预期版本 → 它把每条编辑整理成统一的三元组:键路径、值、合并策略 → 交给 ConfigManager::apply_edits,出来的是统一的写入响应。
调用关系:它是批量写入入口。和 ConfigManager::write_value 一样,它真正依赖 ConfigManager::apply_edits 来检查、合并、校验和落盘。
调用图:调用 1 个内部函数(apply_edits)。
ConfigManager::apply_edits192–353 ↗
async fn apply_edits(
&self,
file_path: Option<String>,
expected_version: Option<String>,
edits: Vec<(String, JsonValue, MergeStrategy)>,
) -> Result<ConfigWriteRes
作用:这是写配置的总流程:检查能不能写、应用改动、验证配置、保存文件,并告诉调用方改动是否被别的配置层覆盖。
数据流:进去的是可选文件路径、可选预期版本和一批编辑 → 它确认只能写用户配置,加载配置层,必要时创建空用户配置;然后解析键路径和值,执行合并或删除,校验用户配置和最终配置,最后把真实变化写入 config.toml → 出来的是写入状态、新版本、写入文件路径,以及可能的“被覆盖”说明。
调用关系:它被 ConfigManager::write_value 和 ConfigManager::batch_write 调用,是所有写入请求的中心。它会调用 parse_key_path、parse_value、apply_merge、validate_config、create_empty_user_layer、first_overridden_edit 等小零件,把一次危险的文件修改拆成可控步骤。
调用图:调用 14 个内部函数(load_thread_agnostic_config, write, apply_merge, create_empty_user_layer, first_overridden_edit, parse_key_path, parse_value, paths_match, toml_value_to_item, validate_config (+4 more));被 2 处调用(batch_write, write_value);外部调用 5 个(Borrowed, Owned, from, new, for_config_path)。
ConfigManager::load_thread_agnostic_config358–360 ↗
async fn load_thread_agnostic_config(&self) -> std::io::Result<ConfigLayerStack>
作用:加载不依赖当前工作目录的配置。也就是说,它不会把某个项目目录里的 .codex 配置算进去。
数据流:进去时没有目录参数 → 它调用通用的配置层加载函数,并明确传入没有当前目录 → 出来的是一叠全局配置层。
调用关系:它被 ConfigManager::read、ConfigManager::read_requirements 和 ConfigManager::apply_edits 使用。凡是不应该受当前项目影响的读写流程,都会走它。
调用图:被 3 处调用(apply_edits, read, read_requirements)。
create_empty_user_layer363–399 ↗
async fn create_empty_user_layer(
config_toml: &AbsolutePathBuf,
) -> Result<ConfigLayerEntry, ConfigManagerError>
作用:在用户配置层不存在时,准备一个空的用户配置层,并确保对应的配置文件可以被创建。
数据流:进去的是用户配置文件路径 → 它先处理符号链接路径,尝试读取已有文件;如果文件不存在,就写一个空文件,并用空 TOML 表作为配置内容 → 出来的是一个代表“用户配置层”的对象。
调用关系:它被 ConfigManager::apply_edits 调用。写配置时如果还没有用户配置文件,就靠它先把“可写的空白纸”准备好。
调用图:调用 4 个内部函数(io, write_empty_user_config, new, as_path);被 1 处调用(apply_edits);外部调用 6 个(Table, resolve_symlink_write_paths, read_to_string, from_str, new, clone)。
write_empty_user_config401–406 ↗
async fn write_empty_user_config(write_path: PathBuf) -> Result<(), ConfigManagerError>
作用:创建一个空的用户配置文件,并用安全写入方式落盘。
数据流:进去的是要写入的路径 → 它把实际写文件操作放到后台阻塞任务里执行,并用原子写入写入空字符串 → 成功时没有额外结果,失败时返回配置错误。
调用关系:它只被 create_empty_user_layer 使用。因为文件写入可能阻塞异步运行环境,所以它把写盘动作交给 spawn_blocking 这类专门处理阻塞任务的机制。
调用图:被 1 处调用(create_empty_user_layer);外部调用 1 个(spawn_blocking)。
parse_value408–416 ↗
fn parse_value(value: JsonValue) -> Result<Option<TomlValue>, String>
作用:把 API 传进来的 JSON 值转成配置文件使用的 TOML 值;如果传的是 null,就表示删除这个配置项。
数据流:进去的是一个 JSON 值 → 它先判断是不是 null;null 变成 None,其他值尝试转成 TOML 值 → 出来的是可选的 TOML 值,或者一段错误文字。
调用关系:它被 ConfigManager::apply_edits 调用。写入前必须先把外部传来的数据换成内部配置能操作的格式。
调用图:被 1 处调用(apply_edits);外部调用 1 个(is_null)。
parse_key_path418–463 ↗
fn parse_key_path(path: &str) -> Result<Vec<String>, String>
作用:把 a.b.c 这样的配置路径拆成一段一段的键名,同时支持带引号的特殊键名。
数据流:进去的是一条键路径字符串 → 它按点号分割,但引号里的点号不会被当成分隔符;同时检查空路径、空片段、没闭合的引号或转义 → 出来的是键名数组,或者一段说明哪里写错的错误文字。
调用关系:它被 ConfigManager::apply_edits 调用。后续的查找、合并、删除都依赖这个拆好的路径,就像先把地址拆成省市街道才能找到门牌。
调用图:被 1 处调用(apply_edits);外部调用 3 个(new, new, take)。
apply_merge470–525 ↗
fn apply_merge(
root: &mut TomlValue,
segments: &[String],
value: Option<&TomlValue>,
strategy: MergeStrategy,
) -> Result<bool, MergeError>
作用:把一个新值真正放进 TOML 配置树里,或者在值为空时删除指定路径。
数据流:进去的是可修改的配置树、路径片段、可选新值和合并策略 → 如果新值为空,它交给 clear_path 删除;否则它沿路径创建需要的表,并按策略替换或合并表 → 出来的是是否发生变化,或者校验错误。
调用关系:它被 ConfigManager::apply_edits 调用,是“内存里先改一遍配置”的关键步骤。删除时它会把工作交给 clear_path;表合并时会用 merge_toml_values。
调用图:调用 1 个内部函数(clear_path);被 1 处调用(apply_edits);外部调用 5 个(Table, Validation, merge_toml_values, matches!, new)。
clear_path527–552 ↗
fn clear_path(root: &mut TomlValue, segments: &[String]) -> Result<bool, MergeError>
作用:从 TOML 配置树里删除某个路径对应的值。
数据流:进去的是可修改配置树和路径片段 → 它沿着父路径一层层找;如果中间不存在或不是表,就说明本来没东西可删;找到父表后删除最后一个键 → 出来的是是否真的删掉了东西。
调用关系:它只被 apply_merge 调用。当写入值是 null 时,apply_merge 会把删除任务交给它完成。
调用图:被 1 处调用(apply_merge);外部调用 1 个(Validation)。
toml_value_to_item554–566 ↗
fn toml_value_to_item(value: &TomlValue) -> anyhow::Result<TomlItem>
作用:把普通 TOML 值转成可编辑 TOML 文档里的项目,方便保留和写回配置文件结构。
数据流:进去的是一个 TOML 值 → 如果是表,它递归地把每个子项转成可编辑项目;如果是普通值,就转成可编辑值 → 出来的是 toml_edit 能写入文件的项目。
调用关系:它被 ConfigManager::apply_edits 调用,用来生成实际落盘的编辑指令。遇到非表值时,它会继续调用 toml_value_to_value。
调用图:调用 1 个内部函数(toml_value_to_value);被 1 处调用(apply_edits);外部调用 3 个(Table, Value, new)。
toml_value_to_value568–590 ↗
fn toml_value_to_value(value: &TomlValue) -> anyhow::Result<toml_edit::Value>
作用:把 TOML 的具体值,比如字符串、数字、布尔值、数组,转换成编辑器库能保存的值。
数据流:进去的是一个 TOML 值 → 它按值的类型分别转换;数组会逐项转换,表会变成内联表 → 出来的是 toml_edit::Value,供写文件使用。
调用关系:它被 toml_value_to_item 调用。它负责处理所有“不是完整表项目”的细节转换。
调用图:被 1 处调用(toml_value_to_item);外部调用 5 个(new, new, Array, InlineTable, from)。
validate_config592–595 ↗
fn validate_config(value: &TomlValue) -> Result<(), toml::de::Error>
作用:检查一份 TOML 配置能不能被系统理解成合法配置。
数据流:进去的是 TOML 值 → 它尝试把这份值转换成 ConfigToml 这个正式配置结构 → 成功就返回空结果,失败就返回 TOML 解析错误。
调用关系:它被 ConfigManager::apply_edits 调用。写入前后都要用它把关,避免把格式正确但系统不认的配置保存下来。
调用图:被 1 处调用(apply_edits);外部调用 1 个(clone)。
paths_match597–599 ↗
fn paths_match(expected: impl AsRef<Path>, provided: impl AsRef<Path>) -> bool
作用:判断两个路径是不是指向同一个配置文件,即使写法略有不同也尽量识别出来。
数据流:进去的是期望路径和用户提供路径 → 它交给路径工具做规范化比较,比如处理不同写法带来的差异 → 出来的是 true 或 false。
调用关系:它被 ConfigManager::apply_edits 调用。写配置前必须确认目标就是用户配置文件,而不是系统配置或别的文件。
调用图:被 1 处调用(apply_edits);外部调用 1 个(paths_match_after_normalization)。
value_at_path601–617 ↗
fn value_at_path(root: &'a TomlValue, segments: &[String]) -> Option<&'a TomlValue>
作用:按路径从 TOML 配置树里取出一个值,支持表里的键,也支持数组下标。
数据流:进去的是配置树和路径片段 → 它一段段往下走:遇到表就按键找,遇到数组就把片段当数字下标 → 找到就返回值的引用,找不到或类型不对就返回空。
调用关系:它被 ConfigManager::apply_edits、compute_override_metadata 和 find_effective_layer 使用。写入前后比较变化、判断覆盖来源,都要靠它定位具体配置项。
调用图:被 3 处调用(apply_edits, compute_override_metadata, find_effective_layer);外部调用 1 个(try_from)。
override_message619–648 ↗
fn override_message(layer: &ConfigLayerSource) -> String
作用:把“是哪一层配置覆盖了当前值”翻译成用户能看懂的一句话。
数据流:进去的是配置层来源,比如系统配置、企业托管配置、项目配置或用户配置 → 它按来源类型拼出说明文字 → 出来的是一段可展示的覆盖提示。
调用关系:它被 compute_override_metadata 调用。当系统发现用户刚写的值没有成为最终值时,会用它生成解释原因的消息。
调用图:被 1 处调用(compute_override_metadata);外部调用 1 个(format!)。
compute_override_metadata650–679 ↗
fn compute_override_metadata(
layers: &ConfigLayerStack,
effective: &TomlValue,
segments: &[String],
) -> Option<OverriddenMetadata>
作用:判断某个刚改的配置项是否被更高优先级的配置层盖住,并整理出给客户端看的说明。
数据流:进去的是配置层栈、最终生效配置和某个键路径 → 它比较用户层的值和最终值;如果不同,就找到真正生效的那一层,并生成提示文字和最终值 → 出来的是覆盖信息,或者没有覆盖时返回空。
调用关系:它被 first_overridden_edit 调用。它内部会用 value_at_path 取值,用 find_effective_layer 找覆盖层,再用 override_message 写说明。
调用图:调用 4 个内部函数(find_effective_layer, override_message, value_at_path, get_active_user_layer);被 1 处调用(first_overridden_edit)。
first_overridden_edit681–692 ↗
fn first_overridden_edit(
layers: &ConfigLayerStack,
effective: &TomlValue,
edits: &[Vec<String>],
) -> Option<OverriddenMetadata>
作用:在一批刚写入的配置项里,找出第一个被其他配置层覆盖的项。
数据流:进去的是配置层栈、最终配置和多条编辑路径 → 它逐条调用 compute_override_metadata 检查 → 一旦发现覆盖就返回对应信息;如果都没被覆盖,就返回空。
调用关系:它被 ConfigManager::apply_edits 在写入完成后调用。这样写入响应可以告诉客户端:保存成功了,但某个值当前被更高层配置压住。
调用图:调用 1 个内部函数(compute_override_metadata);被 1 处调用(apply_edits)。
find_effective_layer694–705 ↗
fn find_effective_layer(
layers: &ConfigLayerStack,
segments: &[String],
) -> Option<ConfigLayerMetadata>
作用:找出某个配置值实际来自哪一层配置。
数据流:进去的是配置层栈和键路径 → 它按从高优先级到低优先级的顺序扫描每一层,看看哪一层有这个值 → 找到就返回那层的元数据,找不到就返回空。
调用关系:它被 compute_override_metadata 调用。发现用户值和最终值不一致后,需要靠它说明到底是谁覆盖了用户的设置。
调用图:调用 2 个内部函数(value_at_path, layers_high_to_low);被 1 处调用(compute_override_metadata)。
可编辑配置持久化
这些文件实现低级 TOML 编辑机制和高级变更管线,用于安全地持久化用户可编辑的配置更改。
core/src/config/edit.rs源码 ↗
这个文件解决的是“程序运行中要改配置,怎么可靠写回磁盘”的问题。用户点了某个开关、换了模型、改了界面主题,最后都要落到 TOML 配置文件里;TOML 可以理解成一种人能读懂的配置文本。这里先用 ConfigEdit 把每种改动描述成一张“小纸条”,再由 ConfigDocument 打开现有配置,把这些纸条逐条贴进去或擦掉。它还会尽量保留原文件里的注释、空格和表格写法,避免机器一改就把人写的配置弄得面目全非。真正写文件时,它会处理符号链接,并用原子写入(一种“先写临时文件再替换”的安全写法),避免写到一半崩溃导致配置坏掉。ConfigEditsBuilder 则像购物车,先把多个改动攒起来,最后一次性保存。
syntax_theme_edit86–91 ↗
fn syntax_theme_edit(name: &str) -> ConfigEdit
作用:生成一条“把终端界面主题改成某个名字”的配置改动。界面事件处理代码会用它来把用户选的主题保存下来。
数据流:进去的是主题名字 → 它把名字放到配置路径 tui.theme 下 → 出来的是一条 ConfigEdit,之后由保存流程真正写进 config.toml。
调用关系:它通常在 handle_event 处理用户操作时被调用;它自己不碰文件,只负责造出一张改配置的小纸条,后面交给统一的保存流程。
调用图:被 1 处调用(handle_event);外部调用 2 个(value, vec!)。
tui_pet_edit94–99 ↗
fn tui_pet_edit(name: &str) -> ConfigEdit
作用:生成一条“设置界面宠物名字”的配置改动。用户启用、选择或关闭宠物相关功能时会用到。
数据流:进去的是宠物名称 → 它把名称包装成 tui.pet 这个配置值 → 出来的是 ConfigEdit,等待后续统一保存。
调用关系:它被 handle_pet_disabled 和 handle_pet_selection_loaded 使用;这些界面流程决定要写什么宠物值,它负责把决定翻译成配置改动。
调用图:被 2 处调用(handle_pet_disabled, handle_pet_selection_loaded);外部调用 2 个(value, vec!)。
session_picker_view_edit102–107 ↗
fn session_picker_view_edit(mode: SessionPickerViewMode) -> ConfigEdit
作用:生成一条“设置会话选择器显示模式”的配置改动。会话选择器可以理解成打开历史对话时看到的列表界面。
数据流:进去的是一个显示模式 → 它把模式转成字符串,放到 tui.session_picker_view → 出来的是可保存的 ConfigEdit。
调用关系:它是给界面或设置代码调用的小工具;它不直接写文件,而是让后面的 ConfigDocument 和 apply 流程去落盘。
status_line_items_edit113–120 ↗
fn status_line_items_edit(items: &[String]) -> ConfigEdit
作用:生成一条“明确设置状态栏显示哪些项目”的配置改动。即使列表为空也会写入,用来表达“我就是要隐藏状态栏”,而不是“没设置所以用默认值”。
数据流:进去的是一组状态栏项目名称 → 它们被按顺序做成 TOML 数组 → 出来的是写到 tui.status_line 的 ConfigEdit。
调用关系:它被 handle_event 调用,通常来自用户在界面里调整状态栏;它把界面选择交给后续保存流程。
调用图:被 1 处调用(handle_event);外部调用 2 个(Value, vec!)。
status_line_use_colors_edit123–128 ↗
fn status_line_use_colors_edit(enabled: bool) -> ConfigEdit
作用:生成一条“状态栏是否使用颜色”的配置改动。用户切换彩色显示时会用到它。
数据流:进去的是 true 或 false → 它把布尔值放到 tui.status_line_use_colors → 出来的是一条 ConfigEdit。
调用关系:它被 handle_event 调用;事件处理只负责知道用户选了什么,这个函数负责把选择变成配置路径和值。
调用图:被 1 处调用(handle_event);外部调用 2 个(value, vec!)。
terminal_title_items_edit134–141 ↗
fn terminal_title_items_edit(items: &[String]) -> ConfigEdit
作用:生成一条“终端标题栏显示哪些内容”的配置改动。空列表也会写入,用来明确表示关闭标题更新。
数据流:进去的是一组标题项目名称 → 它们被做成有顺序的 TOML 数组 → 出来的是写到 tui.terminal_title 的 ConfigEdit。
调用关系:它被 handle_event 调用;它产出的改动会跟其他配置改动一起被统一应用。
调用图:被 1 处调用(handle_event);外部调用 2 个(Value, vec!)。
keymap_binding_value143–150 ↗
fn keymap_binding_value(keys: &[String]) -> TomlItem
作用:把快捷键列表转换成适合写进配置文件的值。只有一个快捷键时写成字符串,多个快捷键时写成数组,让配置更简洁。
数据流:进去的是快捷键字符串列表 → 它判断长度:一个就变成单个 TOML 字符串,多个就变成 TOML 数组 → 出来的是 TomlItem 配置值。
调用关系:它只服务于 keymap_bindings_edit;后者负责确定配置路径,它负责决定值该长什么样。
调用图:被 1 处调用(keymap_bindings_edit);外部调用 2 个(Value, value)。
keymap_bindings_edit153–163 ↗
fn keymap_bindings_edit(context: &str, action: &str, keys: &[String]) -> ConfigEdit
作用:生成一条“替换某个界面场景下某个动作的快捷键列表”的配置改动。比如把“发送消息”绑定到新的按键。
数据流:进去的是场景、动作名和快捷键列表 → 它拼出 tui.keymap.<场景>.<动作> 这条路径,并调用 keymap_binding_value 准备值 → 出来的是 SetPath 类型的 ConfigEdit。
调用关系:它被 keymap_binding_edit 和 apply_keymap_capture 使用;一个负责单键快捷写法,一个来自用户捕获快捷键的流程。
调用图:调用 1 个内部函数(keymap_binding_value);被 2 处调用(keymap_binding_edit, apply_keymap_capture);外部调用 1 个(vec!)。
keymap_binding_edit166–168 ↗
fn keymap_binding_edit(context: &str, action: &str, key: &str) -> ConfigEdit
作用:生成一条“设置单个快捷键绑定”的配置改动。它是 keymap_bindings_edit 的简化入口。
数据流:进去的是场景、动作名和一个按键 → 它把单个按键包成列表 → 出来的是 keymap_bindings_edit 生成的 ConfigEdit。
调用关系:它把工作完全交给 keymap_bindings_edit,适合调用者只想设置一个快捷键时使用。
调用图:调用 1 个内部函数(keymap_bindings_edit)。
keymap_binding_clear_edit171–180 ↗
fn keymap_binding_clear_edit(context: &str, action: &str) -> ConfigEdit
作用:生成一条“删除某个快捷键绑定”的配置改动。删除后程序可以回到默认快捷键,或者表示这个动作不再自定义。
数据流:进去的是场景和动作名 → 它拼出 tui.keymap.<场景>.<动作> 路径 → 出来的是 ClearPath 类型的 ConfigEdit。
调用关系:它被 apply_keymap_clear 调用;清除快捷键的界面流程用它来告诉保存层删掉对应配置。
调用图:被 1 处调用(apply_keymap_clear);外部调用 1 个(vec!)。
model_availability_nux_count_edits182–201 ↗
fn model_availability_nux_count_edits(shown_count: &HashMap<String, u32>) -> Vec<ConfigEdit>
作用:生成一组“记录某些模型的新手提示已经显示过几次”的配置改动。NUX 通常指新用户引导提示,用计数避免反复打扰用户。
数据流:进去的是模型名到显示次数的表 → 它先生成一条清空旧计数的改动,再按模型名排序生成每个计数的写入改动 → 出来的是一组 ConfigEdit。
调用关系:它被 ConfigEditsBuilder::set_model_availability_nux_count 调用;构建器负责收集改动,它负责把计数字典拆成具体配置路径。
调用图:被 1 处调用(set_model_availability_nux_count);外部调用 3 个(from, value, vec!)。
ConfigDocument::new214–216 ↗
fn new(doc: DocumentMut) -> Self
作用:把解析好的 TOML 文档包成 ConfigDocument,方便后面按统一方法修改。可以把它看成“把配置纸张放进编辑器”。
数据流:进去的是 DocumentMut,也就是可修改的 TOML 文档 → 它存到结构体里 → 出来的是 ConfigDocument 实例。
调用关系:它在 apply_blocking_to_resolved_file 读完并解析配置文件后被调用,随后 ConfigDocument::apply 会在这个文档上逐条执行改动。
调用图:被 1 处调用(apply_blocking_to_resolved_file)。
ConfigDocument::apply218–337 ↗
fn apply(&mut self, edit: &ConfigEdit) -> anyhow::Result<bool>
作用:执行一条具体的配置改动。它像总分拣员,看 ConfigEdit 是哪一类,再派给写值、删值、替换服务器、修改技能等具体步骤。
数据流:进去的是一条 ConfigEdit 和当前内存里的配置文档 → 它根据改动类型写入、删除或委托专门函数处理 → 出来的是是否真的改动过文档,以及可能的错误。
调用关系:它由 apply_blocking_to_resolved_file 在遍历 edits 时调用;内部会调用 write_optional_value、write_value、replace_mcp_servers、add_tool_suggest_disabled_tool、set_skill_config、insert、clear_owned,项目信任级别则交给 config.rs 里已有的 set_project_trust_level_inner。
调用图:调用 8 个内部函数(add_tool_suggest_disabled_tool, clear_owned, insert, replace_mcp_servers, set_skill_config, write_optional_value, write_value, set_project_trust_level_inner);外部调用 3 个(Name, Path, value)。
ConfigDocument::write_optional_value339–344 ↗
fn write_optional_value(&mut self, segments: &[&str], value: Option<TomlItem>) -> bool
作用:写入一个“可能有,也可能没有”的配置值。有值就保存,没值就删除对应配置。
数据流:进去的是配置路径和可选值 → 如果值存在,它调用 write_value 写入;如果值不存在,它调用 clear 删除 → 出来的是是否发生了改动。
调用关系:它被 ConfigDocument::apply 用来处理模型、推理强度、服务档位、人格等可清空的设置。
调用图:调用 2 个内部函数(clear, write_value);被 1 处调用(apply)。
ConfigDocument::write_value346–352 ↗
fn write_value(&mut self, segments: &[&str], value: TomlItem) -> bool
作用:把一个值写到用字符串切片表示的配置路径上。它是给内部代码用的简便入口。
数据流:进去的是类似 notice.hide_xxx 的路径片段和值 → 它把路径转成可拥有的字符串列表,再交给 insert → 出来的是是否写入成功。
调用关系:它被 ConfigDocument::apply、write_optional_value 和 add_tool_suggest_disabled_tool 使用;真正穿过 TOML 表格并放值的工作由 insert 完成。
调用图:调用 1 个内部函数(insert);被 3 处调用(add_tool_suggest_disabled_tool, apply, write_optional_value)。
ConfigDocument::clear354–360 ↗
fn clear(&mut self, segments: &[&str]) -> bool
作用:删除一个用字符串切片表示的配置项。它用于把某个设置恢复成“未配置”。
数据流:进去的是配置路径 → 它把路径转换成字符串列表,再交给 remove → 出来的是是否确实删掉了东西。
调用关系:它被 write_optional_value 和 replace_mcp_servers 使用;后者在服务器列表为空时会用它清掉整张 mcp_servers 表。
调用图:调用 1 个内部函数(remove);被 2 处调用(replace_mcp_servers, write_optional_value)。
ConfigDocument::add_tool_suggest_disabled_tool362–394 ↗
fn add_tool_suggest_disabled_tool(&mut self, disabled_tool: &ToolSuggestDisabledTool) -> bool
作用:把一个工具加入“不要再建议我用这个工具”的列表里,同时避免重复项。它还兼容旧的数组写法和新的表格写法。
数据流:进去的是一个要禁用建议的工具描述 → 它读取现有 disabled_tools,解析旧格式和新格式,规范化、去重,再把完整列表写回 → 出来的是是否写入成功。
调用关系:它由 ConfigDocument::apply 在遇到 AddToolSuggestDisabledTool 时调用;最后通过 write_value 把整理后的列表写回配置。
调用图:调用 2 个内部函数(write_value, tool_suggest_disabled_tools_value);被 1 处调用(apply);外部调用 4 个(get, new, clone, once)。
ConfigDocument::clear_owned396–398 ↗
ConfigDocument::replace_mcp_servers400–451 ↗
fn replace_mcp_servers(&mut self, servers: &BTreeMap<String, McpServerConfig>) -> bool
作用:整体替换配置里的 MCP 服务器列表。MCP 服务器可以理解成外部工具服务的连接配置,这里保证配置文件里的服务器与传入列表一致。
数据流:进去的是服务器名到服务器配置的有序表 → 如果为空就删除 mcp_servers;否则创建或修正 mcp_servers 表,删掉多余服务器,更新已有服务器,插入新服务器 → 出来的是是否修改了文档。
调用关系:它由 ConfigDocument::apply 处理 ReplaceMcpServers 时调用;序列化单个服务器、合并内联表等细活交给 document_helpers。
调用图:调用 6 个内部函数(clear, ensure_table_for_write, merge_inline_table, new_implicit_table, serialize_mcp_server, serialize_mcp_server_inline);被 1 处调用(apply);外部调用 2 个(as_table_mut, Table)。
ConfigDocument::set_skill_config453–562 ↗
fn set_skill_config(&mut self, selector: SkillConfigSelector, enabled: bool) -> bool
作用:启用或禁用某个技能配置项。这里的“禁用”会写一条 enabled=false;“启用”则通常删除这条覆盖配置,让技能回到默认可用状态。
数据流:进去的是技能选择器(按名字或路径)和是否启用 → 它先清理名字或规范化路径,再找到 [[skills.config]] 数组表,按情况新增、更新或删除禁用项;如果表空了还会清理外层 skills → 出来的是是否真的改动。
调用关系:它由 ConfigDocument::apply 在处理 SetSkillConfig 和 SetSkillConfigByName 时调用;会借助 normalize_skill_config_path、skill_config_selector_from_table 和 write_skill_config_selector。
调用图:调用 4 个内部函数(ensure_table_for_write, new_implicit_table, normalize_skill_config_path, write_skill_config_selector);被 1 处调用(apply);外部调用 10 个(new, as_table_mut, from, ArrayOfTables, Table, new, Name, Path, matches!, value)。
ConfigDocument::insert564–579 ↗
fn insert(&mut self, segments: &[String], value: TomlItem) -> bool
作用:把一个 TOML 值插入到指定路径。中间表不存在时会自动创建,就像在文件夹里不存在的目录先建出来。
数据流:进去的是路径片段和值 → 它拆出最后的键名,调用 descend 找到或创建父表,若原来已有值就复制原来的格式装饰,再替换成新值 → 出来的是是否成功写入。
调用关系:它被 ConfigDocument::apply 直接用于 SetPath,也被 write_value 间接调用;向下找表的工作交给 descend,保留格式交给 preserve_decor。
调用图:调用 1 个内部函数(descend);被 2 处调用(apply, write_value);外部调用 1 个(preserve_decor)。
ConfigDocument::remove581–591 ↗
fn remove(&mut self, segments: &[String]) -> bool
作用:从配置文档中删除指定路径上的值。它只在路径已经存在并且能正常走到父表时删除。
数据流:进去的是路径片段 → 它拆出最后键名,调用 descend 按“只找现有表”的方式进入父表,然后删除该键 → 出来的是是否删掉了值。
调用关系:它被 clear 和 clear_owned 使用;这些上层函数把不同形式的路径统一交给它处理。
调用图:调用 1 个内部函数(descend);被 2 处调用(clear, clear_owned)。
ConfigDocument::descend593–617 ↗
fn descend(&mut self, segments: &[String], mode: TraversalMode) -> Option<&mut TomlTable>
作用:沿着一串配置路径进入嵌套表格。它负责“走到目标所在的父表”,必要时还会创建中间表。
数据流:进去的是路径片段和遍历模式:Create 表示缺了就建,Existing 表示只找已有的 → 它从根表一步步进入子表,并确保当前位置确实能当表来写或读 → 出来的是目标父表的可修改引用,或失败时为空。
调用关系:它被 insert 和 remove 调用;insert 用创建模式,remove 用只读现有模式。表格合法性检查交给 document_helpers。
调用图:调用 3 个内部函数(ensure_table_for_read, ensure_table_for_write, new_implicit_table);被 2 处调用(insert, remove);外部调用 2 个(as_table_mut, Table)。
ConfigDocument::preserve_decor619–648 ↗
fn preserve_decor(existing: &TomlItem, replacement: &mut TomlItem)
作用:在替换配置值时尽量保留原来的格式痕迹,比如空格、换行、注释附近的装饰信息。这样程序改配置时不会显得像把文件重写了一遍。
数据流:进去的是旧的 TOML 项和新的 TOML 项 → 如果两边都是表,就复制表和键的装饰并递归处理子项;如果两边都是值,就复制值的装饰 → 出来没有单独结果,但新项会带上旧格式。
调用关系:它被 insert 在覆盖已有配置时调用;这是保持用户手写配置友好的关键步骤。
调用图:外部调用 1 个(preserve_decor)。
normalize_skill_config_path651–656 ↗
fn normalize_skill_config_path(path: &Path) -> String
作用:把技能路径整理成尽量稳定的绝对真实路径。这样同一个文件不会因为写法不同而被当成两个技能。
数据流:进去的是路径 → 它尝试 canonicalize,也就是解析成系统认可的真实路径;失败时保留原路径 → 出来的是字符串形式的路径。
调用关系:它被 ConfigDocument::set_skill_config 使用,也被读取技能选择器时用来比较已有配置。
调用图:被 1 处调用(set_skill_config);外部调用 1 个(canonicalize)。
skill_config_selector_from_table658–675 ↗
fn skill_config_selector_from_table(table: &TomlTable) -> Option<SkillConfigSelector>
作用:从一张技能配置表里读出“这个配置针对哪个技能”。它只接受按 path 或按 name 二选一的明确写法。
数据流:进去的是一张 TOML 表 → 它尝试读取 path 并规范化,也尝试读取 name 并去掉空白;如果两者刚好只有一个有效,就返回对应选择器,否则返回空 → 出来的是可比较的 SkillConfigSelector。
调用关系:它被 ConfigDocument::set_skill_config 用来查找已有的技能禁用项,判断要更新、删除还是新增。
调用图:外部调用 1 个(get)。
write_skill_config_selector677–688 ↗
fn write_skill_config_selector(table: &mut TomlTable, selector: &SkillConfigSelector)
作用:把技能选择器写回到一张 TOML 表里。按名字选择就写 name,按路径选择就写 path,并清掉另一种字段。
数据流:进去的是可修改表和选择器 → 它根据选择器类型删除冲突字段,再写入 name 或 path → 出来没有单独结果,但表内容被更新。
调用关系:它被 ConfigDocument::set_skill_config 用于更新已有条目或创建新的禁用条目。
调用图:被 1 处调用(set_skill_config);外部调用 2 个(remove, value)。
apply_blocking691–694 ↗
fn apply_blocking(codex_home: &Path, edits: &[ConfigEdit]) -> anyhow::Result<()>
作用:用同步阻塞方式把一批配置改动保存到用户主目录下的 config.toml。适合测试或本来就在阻塞线程里的代码。
数据流:进去的是 codex_home 目录和改动列表 → 它拼出 config.toml 路径 → 把实际读写交给 apply_blocking_to_resolved_file。
调用关系:它被多项测试使用,也可被同步代码调用;它只是确定默认配置文件位置,真正保存由 apply_blocking_to_resolved_file 完成。
调用图:调用 1 个内部函数(apply_blocking_to_resolved_file);被 13 处调用(replace_mcp_servers_round_trips_entries, replace_mcp_servers_serializes_cwd, replace_mcp_servers_serializes_disabled_flag, replace_mcp_servers_serializes_env_sorted, replace_mcp_servers_serializes_env_vars, replace_mcp_servers_serializes_required_flag, replace_mcp_servers_serializes_sourced_env_vars, replace_mcp_servers_serializes_tool_filters, replace_mcp_servers_streamable_http_isolates_headers_between_servers, replace_mcp_servers_streamable_http_removes_optional_sections (+3 more));外部调用 1 个(join)。
apply_blocking_to_resolved_file696–739 ↗
fn apply_blocking_to_resolved_file(
resolved_config_file: &Path,
edits: &[ConfigEdit],
) -> anyhow::Result<()>
作用:这是同步保存配置的核心流程。它负责读现有文件、应用所有改动,并在确实有变化时安全写回。
数据流:进去的是明确的配置文件路径和改动列表 → 如果没有改动就直接结束;否则解析符号链接写入路径,读取文件,不存在就从空文档开始,逐条调用 ConfigDocument::apply,最后用原子写入保存 → 出来是成功或错误。
调用关系:它被 apply_blocking、异步 apply、ConfigEditsBuilder::apply_blocking 和 ConfigEditsBuilder::apply 调用;它把文件路径处理交给 resolve_symlink_write_paths,把安全写文件交给 write_atomically。
调用图:调用 1 个内部函数(new);被 2 处调用(apply_blocking, apply_blocking);外部调用 6 个(new, new, is_empty, resolve_symlink_write_paths, write_atomically, read_to_string)。
apply743–749 ↗
async fn apply(codex_home: &Path, edits: Vec<ConfigEdit>) -> anyhow::Result<()>
作用:异步保存一批配置改动。异步可以理解成不堵住主界面或主任务,但真正的文件读写还是放到专门的阻塞线程里做。
数据流:进去的是 codex_home 和改动列表 → 它复制路径,拼出 config.toml,然后用 spawn_blocking 把同步保存工作扔到后台阻塞线程 → 出来的是保存结果。
调用关系:它适合运行时异步环境调用;内部最终仍然使用 apply_blocking_to_resolved_file,避免在异步任务里直接卡住磁盘读写。
调用图:外部调用 3 个(join, to_path_buf, spawn_blocking)。
ConfigEditsBuilder::new759–761 ↗
fn new(codex_home: &Path) -> Self
作用:创建一个面向默认 config.toml 的配置改动构建器。构建器像购物车,先收集要改的东西,最后一次性提交。
数据流:进去的是 codex_home 目录 → 它拼出 config.toml 路径 → 出来的是空的 ConfigEditsBuilder。
调用关系:它调用 for_config_path 完成实际初始化;之后调用者可以链式调用 set_model 等方法添加改动。
调用图:外部调用 2 个(join, for_config_path)。
ConfigEditsBuilder::for_config763–770 ↗
fn for_config(config: &crate::config::Config) -> Self
作用:根据已经加载好的 Config 对象创建构建器,并尽量写回用户实际使用的配置文件。这样外部配置层存在时也能找对文件。
数据流:进去的是运行中的 Config → 它先问配置层栈有没有用户配置文件路径,没有就退回 codex_home/config.toml → 出来的是对应路径的构建器。
调用关系:它调用 for_config_path;适合已经拿到完整配置对象的业务流程使用。
调用图:外部调用 1 个(for_config_path)。
ConfigEditsBuilder::for_config_path772–777 ↗
fn for_config_path(config_path: &Path) -> Self
作用:按指定文件路径创建配置改动构建器。它是最底层的构建器入口。
数据流:进去的是配置文件路径 → 它复制路径并准备一个空改动列表 → 出来的是 ConfigEditsBuilder。
调用关系:它被 new 和 for_config 调用;后续所有 set_xxx 方法都会往这个构建器的 edits 里追加改动。
调用图:外部调用 2 个(to_path_buf, new)。
ConfigEditsBuilder::set_model779–785 ↗
fn set_model(mut self, model: Option<&str>, effort: Option<ReasoningEffort>) -> Self
作用:往构建器里加入“设置或清除模型和推理强度”的改动。推理强度可以理解成模型思考得多深的偏好。
数据流:进去的是可选模型名和可选 ReasoningEffort → 它把模型名复制成自有字符串,组成 SetModel 改动并加入列表 → 出来的是更新后的构建器。
调用关系:它只是收集改动,不写文件;最后由 ConfigEditsBuilder::apply_blocking 或 ConfigEditsBuilder::apply 统一保存。
ConfigEditsBuilder::set_service_tier787–790 ↗
fn set_service_tier(mut self, service_tier: Option<String>) -> Self
作用:往构建器里加入“设置服务档位”的改动。服务档位表示之后请求偏好的服务类型,比如快一点或弹性一点。
数据流:进去的是可选服务档位字符串 → 它包装成 SetServiceTier 并加入改动列表 → 出来的是更新后的构建器。
调用关系:保存时 ConfigDocument::apply 会把它写到 service_tier,并兼容旧配置里 fast 的写法。
ConfigEditsBuilder::set_personality792–796 ↗
fn set_personality(mut self, personality: Option<Personality>) -> Self
作用:往构建器里加入“设置或清除模型人格”的改动。人格是模型回答风格的一种偏好。
数据流:进去的是可选 Personality → 它生成 SetModelPersonality 改动加入列表 → 出来的是更新后的构建器。
调用关系:它服务于想批量保存配置的调用者;真正写入 personality 键的是后面的 ConfigDocument::apply。
ConfigEditsBuilder::set_hide_full_access_warning798–802 ↗
fn set_hide_full_access_warning(mut self, acknowledged: bool) -> Self
作用:记录用户是否已经确认“完全访问权限”警告。确认后程序可以不再反复提示。
数据流:进去的是是否确认 → 它加入 SetNoticeHideFullAccessWarning 改动 → 出来的是更新后的构建器。
调用关系:保存阶段由 ConfigDocument::apply 写到 notice.hide_full_access_warning。
调用图:外部调用 1 个(SetNoticeHideFullAccessWarning)。
ConfigEditsBuilder::set_hide_world_writable_warning804–808 ↗
fn set_hide_world_writable_warning(mut self, acknowledged: bool) -> Self
作用:记录用户是否已确认 Windows 上“所有人可写目录”的安全警告。这样同一个提示不会一直出现。
数据流:进去的是是否确认 → 它加入 SetNoticeHideWorldWritableWarning 改动 → 出来的是更新后的构建器。
调用关系:后续由 ConfigDocument::apply 写入 notice.hide_world_writable_warning。
调用图:外部调用 1 个(SetNoticeHideWorldWritableWarning)。
ConfigEditsBuilder::set_hide_rate_limit_model_nudge810–814 ↗
fn set_hide_rate_limit_model_nudge(mut self, acknowledged: bool) -> Self
作用:记录用户是否已确认“速率限制时建议换模型”的提示。确认后可以减少打扰。
数据流:进去的是是否确认 → 它加入 SetNoticeHideRateLimitModelNudge 改动 → 出来的是更新后的构建器。
调用关系:保存时 ConfigDocument::apply 会写入 notice.hide_rate_limit_model_nudge。
调用图:外部调用 1 个(SetNoticeHideRateLimitModelNudge)。
ConfigEditsBuilder::set_hide_model_migration_prompt816–823 ↗
fn set_hide_model_migration_prompt(mut self, model: &str, acknowledged: bool) -> Self
作用:记录某个模型迁移提示是否要隐藏。模型迁移提示通常是在旧模型名要换到新模型名时出现。
数据流:进去的是模型标识和是否确认 → 它把模型标识复制成字符串,加入 SetNoticeHideModelMigrationPrompt 改动 → 出来的是更新后的构建器。
调用关系:后续 ConfigDocument::apply 会把它写到 notice 表下对应模型键。
调用图:外部调用 1 个(SetNoticeHideModelMigrationPrompt)。
ConfigEditsBuilder::set_hide_external_config_migration_prompt_home825–831 ↗
fn set_hide_external_config_migration_prompt_home(mut self, acknowledged: bool) -> Self
作用:记录用户是否要隐藏“主目录外部配置迁移”提示。它帮助迁移提示做到只在需要时出现。
数据流:进去的是是否确认 → 它加入 SetNoticeHideExternalConfigMigrationPromptHome 改动 → 出来的是更新后的构建器。
调用关系:保存阶段由 ConfigDocument::apply 写到 notice.external_config_migration_prompts.home。
调用图:外部调用 1 个(SetNoticeHideExternalConfigMigrationPromptHome)。
ConfigEditsBuilder::set_hide_external_config_migration_prompt_project833–845 ↗
fn set_hide_external_config_migration_prompt_project(
mut self,
project: &str,
acknowledged: bool,
) -> Self
作用:记录某个项目的外部配置迁移提示是否要隐藏。不同项目可以有不同确认状态。
数据流:进去的是项目标识和是否确认 → 它复制项目名并加入 SetNoticeHideExternalConfigMigrationPromptProject 改动 → 出来的是更新后的构建器。
调用关系:ConfigDocument::apply 会把它写到 notice.external_config_migration_prompts.projects.<项目>。
调用图:外部调用 1 个(SetNoticeHideExternalConfigMigrationPromptProject)。
ConfigEditsBuilder::record_model_migration_seen847–853 ↗
fn record_model_migration_seen(mut self, from: &str, to: &str) -> Self
作用:记录“某个旧模型到某个新模型”的迁移提示已经展示过。这样程序知道用户已经见过这次迁移信息。
数据流:进去的是旧模型名 from 和新模型名 to → 它生成 RecordModelMigrationSeen 改动加入列表 → 出来的是更新后的构建器。
调用关系:保存时 ConfigDocument::apply 会写到 notice.model_migrations,把旧模型映射到新模型。
ConfigEditsBuilder::set_model_availability_nux_count855–859 ↗
fn set_model_availability_nux_count(mut self, shown_count: &HashMap<String, u32>) -> Self
作用:把模型可用性的新手提示显示次数加入待保存改动。它用于控制提示出现频率。
数据流:进去的是模型名到次数的表 → 它调用 model_availability_nux_count_edits 生成多条改动,并追加到构建器 → 出来的是更新后的构建器。
调用关系:它把“一个计数字典”转换的工作交给 model_availability_nux_count_edits;最后仍由构建器的 apply 方法保存。
调用图:调用 1 个内部函数(model_availability_nux_count_edits)。
ConfigEditsBuilder::replace_mcp_servers861–865 ↗
fn replace_mcp_servers(mut self, servers: &BTreeMap<String, McpServerConfig>) -> Self
作用:加入“替换全部 MCP 服务器配置”的改动。它会复制传入的服务器表,避免后面外部改动影响待保存内容。
数据流:进去的是服务器配置表 → 它克隆表并包装成 ReplaceMcpServers 改动 → 出来的是更新后的构建器。
调用关系:保存时 ConfigDocument::apply 会交给 ConfigDocument::replace_mcp_servers 处理具体 TOML 写法。
调用图:外部调用 1 个(ReplaceMcpServers)。
ConfigEditsBuilder::set_project_trust_level867–877 ↗
fn set_project_trust_level(
mut self,
project_path: P,
trust_level: TrustLevel,
) -> Self
作用:加入“设置某个项目可信级别”的改动。可信级别决定程序对该项目能做多少自动操作。
数据流:进去的是项目路径和 TrustLevel → 它把路径转成 PathBuf,组成 SetProjectTrustLevel 改动 → 出来的是更新后的构建器。
调用关系:保存时 ConfigDocument::apply 会委托 config.rs 里的 set_project_trust_level_inner,保证项目表迁移和写法一致。
调用图:外部调用 1 个(into)。
ConfigEditsBuilder::set_feature_enabled884–899 ↗
fn set_feature_enabled(mut self, key: &str, enabled: bool) -> Self
作用:加入“打开或关闭某个功能开关”的改动。功能开关是用配置控制实验功能或渐进发布功能的办法。
数据流:进去的是功能 key 和是否启用 → 它检查该功能默认是否关闭;如果启用或该功能不是默认关闭,就写 features.<key>=布尔值;如果是默认关闭且要关闭,就清掉这个键 → 出来的是更新后的构建器。
调用关系:它读取 FEATURES 列表来避免把默认关闭功能永久钉死为 false;保存时由 SetPath 或 ClearPath 进入 ConfigDocument::apply。
ConfigEditsBuilder::set_windows_sandbox_mode901–907 ↗
ConfigEditsBuilder::set_realtime_microphone909–919 ↗
ConfigEditsBuilder::set_realtime_speaker921–931 ↗
ConfigEditsBuilder::set_realtime_voice933–943 ↗
ConfigEditsBuilder::clear_legacy_windows_sandbox_keys945–955 ↗
fn clear_legacy_windows_sandbox_keys(mut self) -> Self
作用:加入一组“删除旧版 Windows 沙箱功能开关键”的改动。这样旧配置不会和新配置同时存在、互相打架。
数据流:进去没有额外参数 → 它遍历几个旧 key,给每个 features.<旧key> 加一条 ClearPath → 出来的是更新后的构建器。
调用关系:它通常配合新的 Windows 沙箱设置使用;保存时 ConfigDocument::remove 会把旧键删掉。
调用图:外部调用 1 个(vec!)。
ConfigEditsBuilder::set_session_picker_view957–963 ↗
fn set_session_picker_view(mut self, mode: SessionPickerViewMode) -> Self
作用:往构建器里加入“设置会话选择器显示模式”的改动。它和独立的 session_picker_view_edit 作用类似,但适合链式构建器用法。
数据流:进去的是显示模式 → 它转成字符串并写成 tui.session_picker_view 的 SetPath → 出来的是更新后的构建器。
调用关系:它不调用独立的 session_picker_view_edit,而是在构建器里直接追加同等改动;最后由 apply 保存。
ConfigEditsBuilder::with_edits965–971 ↗
fn with_edits(mut self, edits: I) -> Self
作用:把外部已经准备好的一批 ConfigEdit 加进构建器。适合把专门函数生成的改动和链式设置混在一起保存。
数据流:进去的是任意可迭代的 ConfigEdit 集合 → 它把这些改动追加到内部 edits 列表 → 出来的是更新后的构建器。
调用关系:它是构建器的通用扩展口;之后仍通过 ConfigEditsBuilder::apply_blocking 或 ConfigEditsBuilder::apply 统一写文件。
ConfigEditsBuilder::apply_blocking974–976 ↗
fn apply_blocking(self) -> anyhow::Result<()>
作用:用阻塞方式保存构建器里攒好的所有改动。它适合命令行、测试或后台线程中使用。
数据流:进去的是构建器本身,里面有目标配置路径和改动列表 → 它把两者交给 apply_blocking_to_resolved_file → 出来的是保存成功或错误。
调用关系:它是 ConfigEditsBuilder 链式调用的同步收尾动作;真正的读、改、写由 apply_blocking_to_resolved_file 完成。
调用图:调用 1 个内部函数(apply_blocking_to_resolved_file)。
ConfigEditsBuilder::apply979–985 ↗
async fn apply(self) -> anyhow::Result<()>
作用:异步保存构建器里攒好的所有改动。它避免在异步运行环境里直接做阻塞磁盘操作。
数据流:进去的是构建器本身 → 它把同步保存逻辑放进 spawn_blocking 后台阻塞线程执行 → 出来的是保存结果,若后台任务崩溃也会变成错误。
调用关系:它是 ConfigEditsBuilder 链式调用的异步收尾动作;内部最终还是调用 apply_blocking_to_resolved_file。
调用图:外部调用 1 个(spawn_blocking)。
core/src/config/edit/document_helpers.rs源码 ↗
这个文件处理的是“配置文件怎么落到纸面上”的问题。TOML 是一种常见配置文件格式,像一张分层表格;这里用 toml_edit 这个库来改它,而不是简单把整个文件重写掉。文件里有两类帮手:一类把某个位置保证成表格,方便继续往下写;另一类把 MCP 服务器配置(可以理解为应用要连接的外部工具服务)序列化成 TOML。它还会细心地只写有意义的字段,比如空数组不写、默认值不写,并把字典按 key 排序,让生成结果稳定。还有一些函数负责在“行内表”和普通表之间转换,行内表就像 {id="x"} 这种写在一行里的小表格。整体上,它像配置编辑器里的“排版和装订工”,保证数据写得对,也尽量写得好看。
ensure_table_for_write15–33 ↗
fn ensure_table_for_write(item: &mut TomlItem) -> Option<&mut TomlTable>
作用:保证某个 TOML 项在接下来可以当作表格来写。如果原来不是表格,它会尽量把它变成表格;这能让上层代码不用每次都自己判断格式。
数据流:输入是一块可修改的 TOML 项。它先看这块内容是不是表格;如果已经是,就直接返回它;如果是行内表,就转换成普通表;如果是空的或普通值,就换成一个新的隐式表。输出是可修改的表格引用;如果遇到不能转换的类型,就返回空。
调用关系:它在向配置深处写入时被 descend、replace_mcp_servers、set_skill_config 使用。它会调用 table_from_inline 把行内表摊开,也会调用 new_implicit_table 创建缺失的表格。
调用图:调用 2 个内部函数(new_implicit_table, table_from_inline);被 3 处调用(descend, replace_mcp_servers, set_skill_config);外部调用 2 个(Table, as_table_mut)。
ensure_table_for_read35–45 ↗
fn ensure_table_for_read(item: &mut TomlItem) -> Option<&mut TomlTable>
作用:保证某个 TOML 项在读取时能被当作表格看待,但它比写入版更保守。它不会把普通值硬改成空表,避免读取时悄悄改变不该改变的配置。
数据流:输入是一块可修改的 TOML 项。它如果本来是表格,就返回这个表;如果是行内表,就转换成普通表再返回;其他情况返回空。过程中只在确实能理解为表格时才改动原内容。
调用关系:它主要被 descend 在沿着配置路径向下查找时使用。需要转换行内表时,它把工作交给 table_from_inline。
调用图:调用 1 个内部函数(table_from_inline);被 1 处调用(descend);外部调用 2 个(Table, as_table_mut)。
serialize_mcp_server_table47–163 ↗
fn serialize_mcp_server_table(config: &McpServerConfig) -> TomlTable
作用:把一个 MCP 服务器配置变成一张 TOML 表。MCP 可以理解为让应用连接外部工具或服务的一套接口,这个函数负责把这些连接信息写成配置文件能保存的样子。
数据流:输入是内存里的 McpServerConfig。它根据连接方式写入不同字段:本地命令方式会写 command、args、env、cwd 等;HTTP 方式会写 url、请求头、令牌环境变量等。之后再补上启用状态、超时、工具批准方式、允许或禁用的工具、OAuth 信息和单个工具配置。输出是一张 TOML 表。
调用关系:它是 MCP 配置序列化的核心,被 serialize_mcp_server 和 serialize_mcp_server_inline 包装使用。它会调用 array_from_iter、array_from_env_vars、table_from_pairs 等小函数来生成数组和子表,也会调用 serialize_mcp_server_tool 处理每个工具自己的配置。
调用图:调用 6 个内部函数(is_local_environment, array_from_env_vars, array_from_iter, new_implicit_table, serialize_mcp_server_tool, table_from_pairs);被 2 处调用(serialize_mcp_server, serialize_mcp_server_inline);外部调用 3 个(Table, new, value)。
serialize_mcp_server_tool165–176 ↗
fn serialize_mcp_server_tool(config: &McpServerToolConfig) -> TomlItem
作用:把某个 MCP 工具的单独配置写成 TOML 项。目前它主要记录这个工具需要怎样的批准方式。
数据流:输入是 McpServerToolConfig。它新建一张表,如果配置里有 approval_mode,就把内部枚举值翻译成配置文件里的字符串,比如 auto、prompt、approve。输出是一个 TOML 表项。
调用关系:它由 serialize_mcp_server_table 在处理服务器下面的 tools 子配置时调用。它只负责单个工具,外层服务器表负责决定有哪些工具以及工具名是什么。
调用图:被 1 处调用(serialize_mcp_server_table);外部调用 3 个(Table, new, value)。
serialize_mcp_server178–180 ↗
fn serialize_mcp_server(config: &McpServerConfig) -> TomlItem
作用:把 MCP 服务器配置包装成普通 TOML 表项,适合写进配置文件的表格位置。
数据流:输入是 McpServerConfig。它先让 serialize_mcp_server_table 生成完整表格,再把这张表包成 TomlItem。输出是可直接插入文档的 TOML 项。
调用关系:它被 replace_mcp_servers 在替换服务器配置时使用。真正的字段展开工作交给 serialize_mcp_server_table,它自己负责最后一层包装。
调用图:调用 1 个内部函数(serialize_mcp_server_table);被 1 处调用(replace_mcp_servers);外部调用 1 个(Table)。
serialize_mcp_server_inline182–184 ↗
fn serialize_mcp_server_inline(config: &McpServerConfig) -> InlineTable
作用:把 MCP 服务器配置变成 TOML 的行内表。行内表就是写在一行里的小表格,适合在原文件本来就是紧凑写法时继续保持这种风格。
数据流:输入是 McpServerConfig。它先生成普通 TOML 表,再把这张表转成 InlineTable。输出是一张行内表。
调用关系:它被 replace_mcp_servers 使用,通常用于需要更新已有行内写法的地方。核心内容仍由 serialize_mcp_server_table 生成。
调用图:调用 1 个内部函数(serialize_mcp_server_table);被 1 处调用(replace_mcp_servers)。
merge_inline_table186–198 ↗
fn merge_inline_table(existing: &mut InlineTable, replacement: InlineTable)
作用:把一张新的行内表合并进旧的行内表,同时尽量保留旧表里每个值周围的格式装饰。这里的装饰可以理解为等号后的空格、换行风格这类排版痕迹。
数据流:输入是一个要被修改的旧行内表,以及一个作为新内容的行内表。它先删掉旧表里新表没有的键,再逐个写入新表的值;如果旧键还在,就复制旧值的排版装饰后再替换。结果是旧表内容变成新内容,但格式尽量不突兀。
调用关系:它被 replace_mcp_servers 在更新已有行内服务器配置时调用。它不负责生成新配置,只负责把新旧两份行内表优雅地拼合。
调用图:被 1 处调用(replace_mcp_servers);外部调用 4 个(get_mut, insert, iter, retain)。
table_from_inline200–209 ↗
fn table_from_inline(inline: &InlineTable) -> TomlTable
作用:把 TOML 行内表转换成普通表。这样后续代码就可以像处理多行表格一样继续读写它。
数据流:输入是一张行内表。它新建一个隐式普通表,把行内表里的每个键和值复制进去,并清掉值后面的多余后缀排版。输出是一张普通 TOML 表。
调用关系:它被 ensure_table_for_write 和 ensure_table_for_read 调用。也就是说,只要读取或写入路径上遇到行内表,就由它负责把紧凑写法展开成普通表结构。
调用图:调用 1 个内部函数(new_implicit_table);被 2 处调用(ensure_table_for_read, ensure_table_for_write);外部调用 2 个(iter, Value)。
new_implicit_table211–215 ↗
fn new_implicit_table() -> TomlTable
作用:创建一张“隐式”TOML 表。隐式表可以理解为为了内部结构需要而存在的表,不一定马上在文件里写出一个显式表头。
数据流:输入为空。它新建 TomlTable,并把 implicit 标记设为 true。输出是这张隐式表。
调用关系:它是多个配置编辑流程的基础小零件,被 descend、replace_mcp_servers、set_skill_config、ensure_table_for_write、serialize_mcp_server_table、table_from_inline 使用,用来补齐配置树中缺失的表格节点。
调用图:被 6 处调用(descend, replace_mcp_servers, set_skill_config, ensure_table_for_write, serialize_mcp_server_table, table_from_inline);外部调用 1 个(new)。
parse_tool_suggest_disabled_tool217–231 ↗
fn parse_tool_suggest_disabled_tool(
value: &TomlValue,
) -> Option<ToolSuggestDisabledTool>
作用:从一个 TOML 值里读出“工具建议里被禁用的工具”。也就是配置里说某个 connector 或 plugin 不要再被推荐。
数据流:输入是一个 TOML 值。它要求这个值必须是行内表,并从里面读取 type 和 id;type 只能是 connector 或 plugin。读成功就输出 ToolSuggestDisabledTool;格式不对就输出空。
调用关系:调用图里没有显示它的直接上游,但它显然是给读取工具建议禁用列表时用的。它只解析行内表形式,不负责从数组或文件里取值。
调用图:外部调用 1 个(as_inline_table)。
parse_tool_suggest_disabled_tool_table233–246 ↗
fn parse_tool_suggest_disabled_tool_table(
table: &TomlTable,
) -> Option<ToolSuggestDisabledTool>
作用:从一张普通 TOML 表里读出“工具建议里被禁用的工具”。它和 parse_tool_suggest_disabled_tool 做的是同一件事,只是输入格式不同。
数据流:输入是一张 TOML 表。它读取 type 字段判断是 connector 还是 plugin,再读取 id 字段作为工具标识。两个字段都合法时输出 ToolSuggestDisabledTool,否则输出空。
调用关系:调用图里没有显示直接调用者;它通常会在配置读取代码遇到普通表写法时派上用场。它不处理行内表,那部分由 parse_tool_suggest_disabled_tool 负责。
调用图:外部调用 1 个(get)。
tool_suggest_disabled_tools_value248–266 ↗
fn tool_suggest_disabled_tools_value(
disabled_tools: &[ToolSuggestDisabledTool],
) -> TomlItem
作用:把一组“不要推荐的工具”写成 TOML 数组,方便保存到配置文件。数组里的每一项都是带 type 和 id 的小行内表。
数据流:输入是 ToolSuggestDisabledTool 列表。它逐个创建行内表,把工具类型写成 connector 或 plugin,把工具标识写进 id,然后放进数组。输出是一个 TOML 值项。
调用关系:它被 add_tool_suggest_disabled_tool 在新增禁用工具建议时调用。上层负责决定要禁用哪个工具,它负责把列表变成配置文件能保存的形状。
调用图:被 1 处调用(add_tool_suggest_disabled_tool);外部调用 3 个(new, new, Value)。
array_from_iter268–277 ↗
fn array_from_iter(iter: I) -> TomlItem
作用:把一串字符串变成 TOML 数组。它是一个通用小帮手,用来写 args、enabled_tools、scopes 这类字符串列表。
数据流:输入是一个会依次产出字符串的迭代器。它新建 TOML 数组,把每个字符串放进去,最后包装成 TomlItem 输出。
调用关系:它被 serialize_mcp_server_table 多次调用。外层函数决定这些字符串代表什么,它只负责把“很多字符串”变成配置文件数组。
调用图:被 1 处调用(serialize_mcp_server_table);外部调用 2 个(new, Value)。
array_from_env_vars279–295 ↗
fn array_from_env_vars(env_vars: &[McpServerEnvVar]) -> TomlItem
作用:把 MCP 服务器需要的环境变量声明写成 TOML 数组。环境变量可以只是一个名字,也可以是带来源说明的小表。
数据流:输入是 McpServerEnvVar 列表。遇到简单名字就直接放字符串;遇到带配置的环境变量,就创建行内表,写入 name,必要时再写 source。输出是一个 TOML 数组项。
调用关系:它被 serialize_mcp_server_table 在写 stdio 类型 MCP 服务器时调用,专门处理 env_vars 字段。其他服务器字段不会直接用它。
调用图:被 1 处调用(serialize_mcp_server_table);外部调用 3 个(new, new, Value)。
table_from_pairs297–309 ↗
fn table_from_pairs(pairs: I) -> TomlItem
作用:把一组键值字符串变成 TOML 表,并且先按键排序。排序的好处是每次生成的配置顺序稳定,不会今天一个顺序、明天另一个顺序。
数据流:输入是一批字符串键值对。它先收集并按键名排序,然后新建一张显式 TOML 表,把每个键和值写进去。输出是这张表对应的 TomlItem。
调用关系:它被 serialize_mcp_server_table 用来写 env、http_headers、env_http_headers 等字典型字段。外层负责选择哪些字段要写,它负责把键值对排好并变成表。
调用图:被 1 处调用(serialize_mcp_server_table);外部调用 4 个(into_iter, Table, new, value)。
配置迁移和导入
这些文件通过从外部安装导入并应用一次性用户配置迁移,将配置状态带入 Codex 管理的存储。
app-server/src/config/external_agent_config.rs源码 ↗
可以把这个文件理解成“搬家管家”。它先检查用户家目录或某个项目目录里有没有旧工具留下的 .claude 配置、技能目录、插件设置、MCP 服务器配置、hooks、commands、agents 和会话记录。检测时它不会马上动文件,只会列出“有哪些东西可以搬”。真正导入时,它会按类型处理:配置会合并进 config.toml,只补缺的项;技能和子智能体会复制到 Codex 的目录;说明文档会把 “Claude/CLAUDE.md” 这类字样改成 “Codex/AGENTS.md”;插件会区分本地市场和远程市场,本地能直接装,远程则先挂起等待后续确认。整个过程还会记录成功、失败和统计指标,方便界面告诉用户搬了什么、哪里出错。
ExternalAgentConfigImportItemResult::new116–130 ↗
fn new(
item_type: ExternalAgentConfigMigrationItemType,
description: String,
cwd: Option<PathBuf>,
) -> Self
作用:创建一条“某类迁移结果”的空记录。后面导入配置、技能或插件时,会往这条记录里追加成功和失败。
数据流:输入迁移类型、描述文字和所在目录 → 生成一个成功数和失败数都为 0 的结果对象 → 返回这个对象,供后续记录每一步导入结果。
调用关系:它在 ExternalAgentConfigService::import 开始处理每个迁移项时被使用,相当于先拿出一张空表,再慢慢填成功和错误。
ExternalAgentConfigImportItemResult::record_error132–135 ↗
fn record_error(&mut self, raw_error: ExternalAgentConfigImportRawError)
作用:给某个迁移结果记一笔失败。它会增加失败次数,并保存具体错误信息。
数据流:输入一条原始错误 → 把错误计数加 1 → 把错误详情放进当前结果的错误列表里。
调用关系:它主要由 record_import_error 调用。上层发现某个导入步骤失败时,会通过这个函数把失败写进结果里,方便最后展示给用户。
调用图:被 1 处调用(record_import_error)。
ExternalAgentConfigImportItemResult::record_success137–145 ↗
fn record_success(&mut self, source: Option<String>, target: Option<String>)
作用:给某个迁移结果记一笔成功。比如某个技能、插件或配置文件成功搬过去了,就会调用它。
数据流:输入来源名称和目标名称 → 成功计数加 1 → 保存一条成功记录,说明从哪里迁到了哪里。
调用关系:它在导入流程成功完成某个小项目时使用,也会被会话导入流程使用,用来统一汇总成功信息。
调用图:被 1 处调用(import_sessions)。
ExternalAgentConfigService::new181–187 ↗
fn new(codex_home: PathBuf) -> Self
作用:创建外部智能体配置迁移服务。调用者只需要给 Codex 的家目录,它会自己推断旧工具的默认目录。
数据流:输入 Codex 主目录 → 调用 default_external_agent_home 找到旧工具目录 → 返回一个带有新旧目录位置的服务对象。
调用关系:这是正常运行时创建服务的入口。后续检测和导入都靠这个服务对象执行。
调用图:调用 1 个内部函数(default_external_agent_home);被 1 处调用(new)。
ExternalAgentConfigService::new_for_test190–195 ↗
fn new_for_test(codex_home: PathBuf, external_agent_home: PathBuf) -> Self
作用:专门给测试用的构造函数,可以手动指定 Codex 目录和旧工具目录。这样测试不用依赖真实用户电脑上的目录。
数据流:输入两个目录路径 → 直接放进服务对象 → 返回可控的测试服务。
调用关系:它被测试辅助函数 service_for_paths 使用,让测试能在临时目录里模拟迁移过程。
调用图:被 1 处调用(service_for_paths)。
ExternalAgentConfigService::detect197–215 ↗
async fn detect(
&self,
params: ExternalAgentConfigDetectOptions,
) -> io::Result<Vec<ExternalAgentConfigMigrationItem>>
作用:扫描哪些旧工具内容可以迁到 Codex。它只做“发现”,不真正复制或改文件。
数据流:输入是否扫描家目录、以及要扫描的工作目录列表 → 对家目录和每个项目根目录调用内部检测 → 返回一组可迁移项目。
调用关系:这是外部调用检测功能的主入口。它把实际细查工作交给 detect_migrations,并用 find_repo_root 把普通路径归到项目根目录。
调用图:调用 2 个内部函数(detect_migrations, find_repo_root);被 1 处调用(detect);外部调用 1 个(new)。
ExternalAgentConfigService::external_agent_session_source_path217–235 ↗
fn external_agent_session_source_path(
&self,
path: &Path,
) -> io::Result<Option<PathBuf>>
作用:判断一个会话文件是不是来自旧工具的会话目录。它主要用来防止导入了不该导入的文件。
数据流:输入一个路径 → 检查是不是 .jsonl 文件,并把路径转成真实绝对路径 → 如果它位于旧工具的 projects 目录下,就返回该路径,否则返回空。
调用关系:它被 validate_pending_session_imports 使用。会话迁移前会先经过这道门,确认来源安全、格式也像旧工具会话文件。
调用图:被 1 处调用(validate_pending_session_imports);外部调用 4 个(extension, starts_with, join, canonicalize)。
ExternalAgentConfigService::import237–427 ↗
async fn import(
&self,
migration_items: Vec<ExternalAgentConfigMigrationItem>,
) -> io::Result<ExternalAgentConfigImportOutcome>
作用:真正执行迁移,把检测出来的项目逐个搬到 Codex。它还会记录每一项成功、失败,以及哪些插件需要稍后处理。
数据流:输入一组迁移项目 → 按项目类型分别导入配置、技能、文档、插件、MCP、hooks、commands 等 → 输出总结果,里面有每项结果和挂起的插件导入请求。
调用关系:这是 import_external_agent_config 调用的主流程。它像总调度员,把具体活儿分给 import_config、import_skills、import_plugins 等函数。
调用图:调用 6 个内部函数(new, import_plugins, partition_plugin_migration_details, emit_migration_metric, invalid_data_error, record_import_error);被 1 处调用(import_external_agent_config);外部调用 1 个(default)。
ExternalAgentConfigService::detect_migrations429–739 ↗
async fn detect_migrations(
&self,
repo_root: Option<&Path>,
items: &mut Vec<ExternalAgentConfigMigrationItem>,
) -> io::Result<()>
作用:检查某一个范围,也就是家目录或某个项目目录里,到底有哪些东西值得迁移。它会尽量避免重复迁移已有内容。
数据流:输入一个可选项目根目录和待追加的列表 → 读取旧工具设置、检查 Codex 目标文件、计算缺失内容 → 往列表里加入配置、技能、插件、会话等迁移项。
调用关系:它由 detect 反复调用。它内部会请很多小工具帮忙,比如读取设置、构建 TOML 配置、统计缺少的目录、检测插件市场和会话。
调用图:调用 17 个内部函数(detect_plugin_migration, mcp_settings, source_root, build_config_from_external, configured_marketplace_plugins, count_missing_subdirectories, effective_external_settings, emit_migration_metric, find_repo_agents_md_source, is_empty_toml_table (+7 more));被 1 处调用(detect);外部调用 16 个(default, as_path, clone, join, Table, build_mcp_config_from_external, count_missing_commands, count_missing_subagents, hook_migration_event_names, missing_command_names (+6 more))。
ExternalAgentConfigService::home_target_skills_dir741–746 ↗
fn home_target_skills_dir(&self) -> PathBuf
作用:算出家目录级别的技能应该放到哪里。因为技能目录不一定直接在 Codex 主目录里,所以需要统一计算。
数据流:读取服务里的 Codex 主目录 → 找到它的上级目录并拼出 .agents/skills → 返回目标技能目录路径。
调用关系:它被 import_commands 和 import_skills 使用,保证家目录迁移时命令和技能都放到同一个目标位置。
调用图:被 2 处调用(import_commands, import_skills);外部调用 1 个(parent)。
ExternalAgentConfigService::mcp_settings748–769 ↗
fn mcp_settings(
&self,
repo_root: Option<&Path>,
source_settings: Option<JsonValue>,
) -> io::Result<Option<JsonValue>>
作用:决定迁移 MCP 服务器配置时该用哪份旧设置。MCP 可以理解成“让智能体连接外部工具的服务器配置”。
数据流:输入项目目录和项目设置 → 如果项目没有设置,就尝试读取旧工具家目录设置作为补充 → 返回可用设置,读取失败时会记录警告但尽量不中断。
调用关系:它被检测和实际导入 MCP 配置时使用。这样项目级迁移也能继承旧工具全局 MCP 设置。
调用图:调用 1 个内部函数(effective_external_settings);被 2 处调用(detect_migrations, import_mcp_server_config);外部调用 2 个(join, warn!)。
ExternalAgentConfigService::source_root771–781 ↗
fn source_root(&self, repo_root: Option<&Path>) -> PathBuf
作用:确定旧工具配置里的相对路径应该以哪个目录为起点。没有这个,像 ./plugins 这样的路径就不知道指向哪里。
数据流:输入可选项目根目录 → 有项目就返回项目根目录;没有项目就返回旧工具目录的父目录或当前目录 → 输出用于解析相对路径的根路径。
调用关系:它被检测和导入 MCP 配置使用,也帮助插件市场路径解析时保持位置正确。
调用图:被 2 处调用(detect_migrations, import_mcp_server_config)。
ExternalAgentConfigService::detect_plugin_migration783–810 ↗
fn detect_plugin_migration(
&self,
source_settings: &Path,
source_root: &Path,
cwd: Option<PathBuf>,
settings: &JsonValue,
configured_plugin_ids: &HashS
作用:检查旧工具设置里有没有启用但 Codex 还没有的插件。它会把可迁移插件整理成一个迁移项。
数据流:输入旧设置、来源根目录、当前目录、已配置插件和可用市场插件 → 提取还需要迁的插件 → 如果有,就返回插件迁移项。
调用关系:它由 detect_migrations 调用。真正判断插件细节的工作交给 extract_plugin_migration_details,它自己负责包装成用户能看到的迁移项。
调用图:调用 2 个内部函数(emit_migration_metric, extract_plugin_migration_details);被 1 处调用(detect_migrations);外部调用 1 个(format!)。
ExternalAgentConfigService::partition_plugin_migration_details812–857 ↗
fn partition_plugin_migration_details(
&self,
cwd: Option<&Path>,
details: MigrationDetails,
) -> io::Result<(Option<MigrationDetails>, Option<MigrationDetails>)>
作用:把插件迁移拆成本地插件市场和远程插件市场两类。这样本地的可以直接装,远程的可以先挂起等待确认。
数据流:输入当前目录和插件详情 → 重新读取旧设置里的插件市场来源 → 判断每个市场是不是本地路径 → 输出本地详情和远程详情两份可选结果。
调用关系:它在 import 处理插件项时使用。拆分后,本地部分交给 import_plugins,远程部分放进 pending 列表等后续流程。
调用图:调用 1 个内部函数(effective_external_settings);被 1 处调用(import);外部调用 3 个(default, as_path, new)。
ExternalAgentConfigService::import_plugins859–972 ↗
async fn import_plugins(
&self,
cwd: Option<&Path>,
details: Option<MigrationDetails>,
) -> io::Result<PluginImportOutcome>
作用:安装插件迁移项里的插件。它先添加对应插件市场,再从市场里安装指定插件。
数据流:输入目录和插件详情 → 找到每组插件对应的市场来源,安装市场,定位市场清单,再逐个安装插件 → 返回哪些市场和插件成功、哪些失败以及错误原因。
调用关系:它由 import 处理本地插件时调用,也会被 complete_pending_plugin_import 用来完成之前挂起的插件导入。
调用图:调用 7 个内部函数(effective_external_settings, invalid_data_error, plugin_import_raw_error, record_plugin_import_errors, new, find_marketplace_manifest_path, add_marketplace);被 2 处调用(import, complete_pending_plugin_import);外部调用 5 个(as_path, clone, new, default, format!)。
ExternalAgentConfigService::import_config974–1027 ↗
fn import_config(&self, cwd: Option<&Path>) -> io::Result<Option<(String, String)>>
作用:把旧工具的基础设置迁到 Codex 的 config.toml。它只补 Codex 缺少的配置,不会随便覆盖已有配置。
数据流:输入可选目录 → 找到旧设置和目标 config.toml → 把 JSON 设置转换成 TOML,和已有配置合并 → 如果有变化就写入文件,并返回来源和目标路径。
调用关系:它由主导入流程 import 在处理 Config 项时调用。它依赖 build_config_from_external 做格式转换,依赖 merge_missing_toml_values 做安全合并。
调用图:调用 7 个内部函数(build_config_from_external, effective_external_settings, find_repo_root, invalid_data_error, is_empty_toml_table, merge_missing_toml_values, write_toml_file);外部调用 5 个(default, join, Table, create_dir_all, read_to_string)。
ExternalAgentConfigService::import_mcp_server_config1029–1079 ↗
fn import_mcp_server_config(&self, cwd: Option<&Path>) -> io::Result<Vec<String>>
作用:迁移 MCP 服务器配置到 Codex 配置文件。它只添加 Codex 还没有的服务器。
数据流:输入可选目录 → 找到旧设置和目标配置 → 生成 MCP 配置片段 → 新文件就直接写,已有文件就只合并缺失服务器 → 返回成功迁入的服务器名称。
调用关系:它由 import 处理 McpServerConfig 项时调用。它会用 mcp_settings 选择设置,用 merge_missing_mcp_servers 避免覆盖已有服务器。
调用图:调用 9 个内部函数(mcp_settings, source_root, effective_external_settings, find_repo_root, invalid_data_error, is_empty_toml_table, merge_missing_mcp_servers, migrated_mcp_server_names, write_toml_file);外部调用 8 个(default, as_path, join, Table, new, build_mcp_config_from_external, create_dir_all, read_to_string)。
ExternalAgentConfigService::import_subagents1081–1097 ↗
fn import_subagents(&self, cwd: Option<&Path>) -> io::Result<Vec<String>>
作用:迁移旧工具的子智能体定义。子智能体可以理解成专门做某类任务的小助手配置。
数据流:输入可选目录 → 判断是项目级还是家目录级迁移 → 找到旧的 agents 目录和 Codex 目标目录 → 调用外部迁移函数复制缺失的子智能体,并返回名称。
调用关系:它由 import 处理 Subagents 项时调用。具体比较和复制逻辑交给外部的 import_subagents。
调用图:调用 1 个内部函数(find_repo_root);外部调用 3 个(join, new, import_subagents)。
ExternalAgentConfigService::import_hooks1099–1121 ↗
fn import_hooks(&self, cwd: Option<&Path>) -> io::Result<Vec<String>>
作用:迁移 hooks 配置。hooks 是在某些事件发生时自动执行的小动作。
数据流:输入可选目录 → 找到旧工具目录和 Codex 的 hooks.json → 先收集可迁移事件名,再执行导入 → 如果确实导入成功就返回事件名。
调用关系:它由 import 处理 Hooks 项时调用。它使用外部迁移库识别事件名并写入 Codex hooks 文件。
调用图:调用 1 个内部函数(find_repo_root);外部调用 5 个(clone, join, new, hook_migration_event_names, import_hooks)。
ExternalAgentConfigService::import_commands1123–1139 ↗
fn import_commands(&self, cwd: Option<&Path>) -> io::Result<Vec<String>>
作用:把旧工具的自定义命令迁成 Codex 技能。这样用户以前写的命令还能继续用。
数据流:输入可选目录 → 找到旧的 commands 目录和目标 skills 目录 → 调用外部命令迁移函数 → 返回成功导入的命令名称。
调用关系:它由 import 处理 Commands 项时调用。家目录场景下会用 home_target_skills_dir 找到统一的技能目标目录。
调用图:调用 2 个内部函数(home_target_skills_dir, find_repo_root);外部调用 3 个(join, new, import_commands)。
ExternalAgentConfigService::import_skills1141–1179 ↗
fn import_skills(&self, cwd: Option<&Path>) -> io::Result<Vec<String>>
作用:复制旧工具的技能目录到 Codex 能识别的位置。已有同名技能不会被覆盖。
数据流:输入可选目录 → 找到来源 skills 和目标 skills → 创建目标目录,逐个复制源目录中目标还没有的子目录 → 返回复制成功的技能名。
调用关系:它由 import 处理 Skills 项时调用。真正递归复制文件的工作交给 copy_dir_recursive。
调用图:调用 3 个内部函数(home_target_skills_dir, copy_dir_recursive, find_repo_root);外部调用 4 个(join, new, create_dir_all, read_dir)。
ExternalAgentConfigService::import_agents_md1181–1211 ↗
fn import_agents_md(&self, cwd: Option<&Path>) -> io::Result<Option<(String, String)>>
作用:迁移说明文件,把旧工具的 CLAUDE.md 复制成 Codex 使用的 AGENTS.md。复制时会顺手改掉文中的旧工具名称。
数据流:输入可选目录 → 找到可用的旧说明文件和目标 AGENTS.md → 确认来源非空且目标不存在或为空 → 改写文本后写入目标,并返回路径信息。
调用关系:它由 import 处理 AgentsMd 项时调用。它会用 find_repo_agents_md_source 找项目里的来源文件,用 rewrite_and_copy_text_file 完成改名改词。
调用图:调用 6 个内部函数(find_repo_agents_md_source, find_repo_root, invalid_data_error, is_missing_or_empty_text_file, is_non_empty_text_file, rewrite_and_copy_text_file);外部调用 2 个(join, create_dir_all)。
default_external_agent_home1214–1220 ↗
read_external_settings1222–1231 ↗
fn read_external_settings(path: &Path) -> io::Result<Option<JsonValue>>
作用:读取旧工具的一个 JSON 设置文件。文件不存在时不算错误,只返回没有设置。
数据流:输入设置文件路径 → 如果不是文件就返回空 → 如果存在就读文本并解析成 JSON → 返回解析后的设置或格式错误。
调用关系:它只被 effective_external_settings 调用,是读取基础设置和本地覆盖设置的底层小工具。
调用图:被 1 处调用(effective_external_settings);外部调用 3 个(is_file, read_to_string, from_str)。
effective_external_settings1233–1251 ↗
fn effective_external_settings(project_settings: &Path) -> io::Result<Option<JsonValue>>
作用:得到“最终生效”的旧工具设置。它会把普通设置和 settings.local.json 合在一起。
数据流:输入项目设置路径 → 先读主设置,再尝试读同目录的本地设置 → 本地设置存在且有效时覆盖或补充主设置 → 返回合并后的 JSON。
调用关系:检测和导入配置、MCP、插件时都会用它。它内部靠 read_external_settings 读文件,靠 merge_json_settings 合并内容。
调用图:调用 2 个内部函数(merge_json_settings, read_external_settings);被 6 处调用(detect_migrations, import_config, import_mcp_server_config, import_plugins, mcp_settings, partition_plugin_migration_details);外部调用 1 个(parent)。
merge_json_settings1253–1269 ↗
fn merge_json_settings(existing: &mut JsonValue, incoming: &JsonValue)
作用:把两份 JSON 设置合成一份。本地设置会覆盖主设置里同名的普通值。
数据流:输入已有 JSON 和新来的 JSON → 如果两边都是对象,就逐项递归合并;否则直接用新值替换旧值 → 修改已有 JSON 本身。
调用关系:它由 effective_external_settings 调用,用来模拟旧工具“主设置 + 本地设置”的最终效果。
调用图:被 1 处调用(effective_external_settings);外部调用 3 个(clone, get_mut, insert)。
extract_plugin_migration_details1270–1328 ↗
fn extract_plugin_migration_details(
settings: &JsonValue,
source_root: &Path,
configured_plugin_ids: &HashSet<String>,
configured_marketplace_plugins: &BTreeMap<String, HashSet<String
作用:从旧设置里挑出真正值得迁移的插件。它会排除 Codex 已经有的、格式不对的、或当前市场不可安装的插件。
数据流:输入旧设置、来源根目录、已配置插件集合和可用市场插件表 → 收集启用插件和市场来源 → 按市场分组筛选 → 返回迁移详情,没东西可迁就返回空。
调用关系:它被 detect_plugin_migration 调用,是插件检测的核心判断函数。它会用 collect_enabled_plugins 和 collect_marketplace_import_sources 收集原始信息。
调用图:调用 3 个内部函数(collect_enabled_plugins, collect_marketplace_import_sources, parse);被 1 处调用(detect_plugin_migration);外部调用 2 个(new, default)。
collect_enabled_plugins1330–1350 ↗
fn collect_enabled_plugins(settings: &JsonValue) -> Vec<String>
作用:从旧工具设置里找出被用户打开的插件。关闭的插件不会迁。
数据流:输入 JSON 设置 → 找到 enabledPlugins 对象 → 只保留值为 true 且插件编号格式正确的项 → 返回标准化后的插件编号列表。
调用关系:它被 extract_plugin_migration_details 和 has_enabled_plugin_for_marketplace 使用,是判断插件是否需要迁移的基础。
调用图:被 2 处调用(extract_plugin_migration_details, has_enabled_plugin_for_marketplace);外部调用 2 个(as_object, new)。
has_enabled_plugin_for_marketplace1352–1360 ↗
fn has_enabled_plugin_for_marketplace(settings: &JsonValue, marketplace_name: &str) -> bool
作用:判断某个插件市场下面是否有启用的插件。这里的市场可以理解成插件仓库。
数据流:输入设置和市场名 → 收集所有启用插件 → 解析每个插件编号里的市场名 → 只要有一个匹配就返回 true。
调用关系:它被 collect_marketplace_import_sources 使用,尤其用于判断是否需要自动加入官方插件市场来源。
调用图:调用 1 个内部函数(collect_enabled_plugins);被 1 处调用(collect_marketplace_import_sources)。
configured_marketplace_plugins1362–1392 ↗
fn configured_marketplace_plugins(
config: &Config,
plugins_manager: &PluginsManager,
) -> io::Result<BTreeMap<String, HashSet<String>>>
作用:列出 Codex 当前配置下,各个插件市场里哪些插件可以安装。不可用或不适合 Codex 的插件会被过滤掉。
数据流:输入 Codex 配置和插件管理器 → 读取插件配置并列出市场 → 过滤不可安装、产品不匹配的插件 → 返回“市场名到插件名集合”的表。
调用关系:它被 detect_migrations 调用。检测旧插件时,需要用它确认 Codex 这边是否已经有可用市场和插件。
调用图:调用 1 个内部函数(list_marketplaces_for_config);被 1 处调用(detect_migrations);外部调用 2 个(new, plugins_config_input)。
collect_marketplace_import_sources1394–1453 ↗
fn collect_marketplace_import_sources(
settings: &JsonValue,
source_root: &Path,
) -> BTreeMap<String, MarketplaceImportSource>
作用:从旧设置里收集插件市场的来源地址。来源可能是 Git 仓库、URL,也可能是本地路径。
数据流:输入旧设置和来源根目录 → 读取 extraKnownMarketplaces → 提取每个市场的 repo/url/path 和 ref → 把相对本地路径转成完整路径,并必要时补上官方市场 → 返回市场来源表。
调用关系:它被插件检测和插件导入相关流程使用。它还会调用 has_enabled_plugin_for_marketplace 来决定是否自动补官方市场。
调用图:调用 1 个内部函数(has_enabled_plugin_for_marketplace);被 1 处调用(extract_plugin_migration_details);外部调用 1 个(as_object)。
resolve_external_marketplace_source1461–1467 ↗
fn resolve_external_marketplace_source(source: &str, source_root: &Path) -> String
作用:把旧设置里的插件市场路径整理成可用来源。相对路径会变成基于来源根目录的完整路径。
数据流:输入来源字符串和根目录 → 判断它像不像相对本地路径 → 是就拼到根目录下,不是就原样返回。
调用关系:它服务于 collect_marketplace_import_sources 的路径解析,让 ./marketplace 这种写法在迁移后仍能找到正确位置。
调用图:调用 1 个内部函数(looks_like_relative_local_path);外部调用 1 个(join)。
looks_like_relative_local_path1469–1471 ↗
fn looks_like_relative_local_path(source: &str) -> bool
作用:判断一个来源字符串是不是相对本地路径。比如 ./x、../x、.、..。
数据流:输入字符串 → 检查开头或完整内容是否符合相对路径形式 → 返回 true 或 false。
调用关系:它被 resolve_external_marketplace_source 调用,是一个简单但关键的路径分类小工具。
调用图:被 1 处调用(resolve_external_marketplace_source)。
find_repo_root1473–1507 ↗
fn find_repo_root(cwd: Option<&Path>) -> io::Result<Option<PathBuf>>
作用:从一个目录或文件位置往上找项目根目录。项目根通常以 .git 为标志。
数据流:输入可选当前路径 → 空路径返回无项目;相对路径先转成当前目录下的绝对含义;文件就用父目录 → 向上找 .git,找不到就返回原始可用目录。
调用关系:它被检测和各类导入函数大量使用,保证项目级迁移都落在项目根目录,而不是随便落在子目录。
调用图:被 8 处调用(detect, import_agents_md, import_commands, import_config, import_hooks, import_mcp_server_config, import_skills, import_subagents);外部调用 1 个(current_dir)。
collect_subdirectory_names1509–1523 ↗
fn collect_subdirectory_names(path: &Path) -> io::Result<HashSet<OsString>>
作用:收集一个目录下所有子目录的名字。它不关心普通文件。
数据流:输入目录路径 → 如果目录不存在就返回空集合 → 读取目录项,只保留子目录名 → 返回名字集合。
调用关系:它被 count_missing_subdirectories 调用,用来比较技能目录哪些已经有、哪些还缺。
调用图:被 1 处调用(count_missing_subdirectories);外部调用 3 个(new, is_dir, read_dir)。
count_missing_subdirectories1525–1532 ↗
fn count_missing_subdirectories(source: &Path, target: &Path) -> io::Result<usize>
作用:统计来源目录里有、目标目录里没有的子目录数量。常用于判断有多少技能需要迁。
数据流:输入来源目录和目标目录 → 分别收集子目录名 → 计算来源中不在目标里的数量 → 返回缺失数量。
调用关系:它由 detect_migrations 调用,检测 Skills 是否需要显示为可迁移项。
调用图:调用 1 个内部函数(collect_subdirectory_names);被 1 处调用(detect_migrations)。
is_missing_or_empty_text_file1534–1543 ↗
fn is_missing_or_empty_text_file(path: &Path) -> io::Result<bool>
作用:判断一个文本文件是不是不存在或内容为空。目标文件为空时,可以安全写入。
数据流:输入路径 → 不存在返回 true;存在但不是文件返回 false;是文件就读取内容并检查去掉空白后是否为空 → 返回判断结果。
调用关系:它被检测和 import_agents_md 使用,用来避免覆盖用户已有的非空说明文件或 hooks 文件。
调用图:被 2 处调用(detect_migrations, import_agents_md);外部调用 3 个(exists, is_file, read_to_string)。
is_non_empty_text_file1545–1551 ↗
fn is_non_empty_text_file(path: &Path) -> io::Result<bool>
作用:判断一个路径是不是有内容的文本文件。空文件不算有效来源。
数据流:输入路径 → 不是文件就返回 false → 读取文本并去掉空白 → 有内容返回 true。
调用关系:它被检测、import_agents_md 和 find_repo_agents_md_source 使用,确保只迁移真正有内容的说明文件。
调用图:被 3 处调用(detect_migrations, import_agents_md, find_repo_agents_md_source);外部调用 2 个(is_file, read_to_string)。
find_repo_agents_md_source1553–1566 ↗
fn find_repo_agents_md_source(repo_root: &Path) -> io::Result<Option<PathBuf>>
作用:在项目里寻找旧工具的说明文件来源。它会兼容两种常见位置。
数据流:输入项目根目录 → 依次检查项目根下的 CLAUDE.md 和 .claude/CLAUDE.md → 找到第一个非空文件就返回路径,否则返回空。
调用关系:它被检测和 import_agents_md 使用,是项目级说明文件迁移的来源定位器。
调用图:调用 1 个内部函数(is_non_empty_text_file);被 2 处调用(detect_migrations, import_agents_md);外部调用 1 个(join)。
copy_dir_recursive1568–1592 ↗
fn copy_dir_recursive(source: &Path, target: &Path) -> io::Result<()>
作用:递归复制整个目录。遇到技能说明文件 SKILL.md 时,会先改写旧工具名字再复制。
数据流:输入来源目录和目标目录 → 创建目标目录 → 遍历每个子项;目录继续递归,普通文件复制,SKILL.md 做文本改写后写入 → 完成目录复制。
调用关系:它由 import_skills 调用,负责实际把技能目录搬过去,同时通过 rewrite_and_copy_text_file 做文字替换。
调用图:调用 2 个内部函数(is_skill_md, rewrite_and_copy_text_file);被 1 处调用(import_skills);外部调用 4 个(join, copy, create_dir_all, read_dir)。
is_skill_md1594–1598 ↗
fn is_skill_md(path: &Path) -> bool
作用:判断一个文件是不是技能说明文件 SKILL.md。大小写不同也算。
数据流:输入路径 → 取文件名并忽略大小写比较是否等于 SKILL.md → 返回判断结果。
调用关系:它被 copy_dir_recursive 调用,决定某个文件是普通复制,还是需要先替换旧工具名称。
调用图:被 1 处调用(copy_dir_recursive);外部调用 1 个(file_name)。
rewrite_and_copy_text_file1600–1604 ↗
fn rewrite_and_copy_text_file(source: &Path, target: &Path) -> io::Result<()>
作用:复制文本文件前先把旧工具相关称呼改成 Codex 的称呼。
数据流:输入来源文件和目标文件 → 读取来源文本 → 调用 rewrite_external_agent_terms 改写内容 → 写入目标文件。
调用关系:它被 import_agents_md 和 copy_dir_recursive 使用,统一处理 AGENTS.md 和 SKILL.md 里的名称替换。
调用图:调用 1 个内部函数(rewrite_external_agent_terms);被 2 处调用(import_agents_md, copy_dir_recursive);外部调用 2 个(read_to_string, write)。
rewrite_external_agent_terms1606–1622 ↗
fn rewrite_external_agent_terms(content: &str) -> String
作用:把文本里的旧工具名称替换成 Codex 名称。比如把 CLAUDE.md 改成 AGENTS.md,把 Claude 相关词改成 Codex。
数据流:输入文本内容 → 按多个关键词逐轮做不区分大小写的整词替换 → 返回改写后的文本。
调用关系:它由 rewrite_and_copy_text_file 调用,具体替换动作交给 replace_case_insensitive_with_boundaries。
调用图:调用 1 个内部函数(replace_case_insensitive_with_boundaries);被 1 处调用(rewrite_and_copy_text_file)。
replace_case_insensitive_with_boundaries1624–1661 ↗
fn replace_case_insensitive_with_boundaries(
input: &str,
needle: &str,
replacement: &str,
) -> String
作用:做“不区分大小写、但只替换完整词”的字符串替换。这样不会把单词中间的一段误替换掉。
数据流:输入原文、要找的词和替换词 → 用小写副本搜索位置 → 检查前后是不是单词边界 → 符合就替换,不符合就跳过 → 返回新文本或原文本。
调用关系:它被 rewrite_external_agent_terms 多次调用。is_word_byte 帮它判断某个字符是不是字母、数字或下划线。
调用图:调用 1 个内部函数(is_word_byte);被 1 处调用(rewrite_external_agent_terms);外部调用 1 个(with_capacity)。
is_word_byte1663–1665 ↗
fn is_word_byte(byte: u8) -> bool
作用:判断一个字节是不是“单词字符”。这里指英文字母、数字或下划线。
数据流:输入一个字节 → 检查它是否为 ASCII 字母数字或 _ → 返回 true 或 false。
调用关系:它被 replace_case_insensitive_with_boundaries 使用,用来决定替换位置前后是不是完整词边界。
调用图:被 1 处调用(replace_case_insensitive_with_boundaries)。
build_config_from_external1667–1705 ↗
fn build_config_from_external(settings: &JsonValue) -> io::Result<TomlValue>
作用:把旧工具 JSON 设置转换成 Codex 的 TOML 配置片段。TOML 可以理解成 Codex 配置文件用的另一种文本格式。
数据流:输入旧设置 JSON → 要求根节点是对象 → 把环境变量设置转成 shell_environment_policy,把启用的 sandbox 转成 sandbox_mode → 返回 TOML 表。
调用关系:它被检测和 import_config 使用。检测时看有没有可迁配置,导入时把结果写进 config.toml。
调用图:调用 2 个内部函数(invalid_data_error, json_object_to_env_toml_table);被 2 处调用(detect_migrations, import_config);外部调用 4 个(as_object, String, Table, new)。
json_object_to_env_toml_table1707–1717 ↗
fn json_object_to_env_toml_table(
object: &serde_json::Map<String, JsonValue>,
) -> toml::map::Map<String, TomlValue>
作用:把旧设置里的环境变量对象转成 TOML 表。只有能表示成字符串的值会保留。
数据流:输入 JSON 对象 → 遍历每个键值 → 用 json_env_value_to_string 把值转成字符串 → 返回 TOML 表。
调用关系:它由 build_config_from_external 调用,专门负责环境变量这一块的格式转换。
调用图:调用 1 个内部函数(json_env_value_to_string);被 1 处调用(build_config_from_external);外部调用 2 个(String, new)。
json_env_value_to_string1719–1727 ↗
fn json_env_value_to_string(value: &JsonValue) -> Option<String>
作用:把环境变量值转成字符串。环境变量最终都必须是文本。
数据流:输入 JSON 值 → 字符串原样返回,布尔和数字转成文本,空值、数组和对象丢弃 → 返回可选字符串。
调用关系:它被 json_object_to_env_toml_table 调用,决定哪些环境变量能安全迁移。
调用图:被 1 处调用(json_object_to_env_toml_table);外部调用 2 个(clone, to_string)。
merge_missing_toml_values1729–1756 ↗
fn merge_missing_toml_values(existing: &mut TomlValue, incoming: &TomlValue) -> io::Result<bool>
作用:把迁移来的 TOML 配置补进已有配置,但只补缺失项。已有值不会被覆盖。
数据流:输入已有 TOML 和新 TOML → 两边都是表时逐项递归合并 → 缺少的键插入,已有的普通值保持不动 → 返回是否发生变化。
调用关系:它被检测和 import_config 使用。检测时判断是否值得显示迁移项,导入时真正合并写文件。
调用图:调用 1 个内部函数(invalid_data_error);被 2 处调用(detect_migrations, import_config);外部调用 1 个(matches!)。
merge_missing_mcp_servers1758–1793 ↗
fn merge_missing_mcp_servers(
existing: &mut TomlValue,
incoming: &TomlValue,
) -> io::Result<Vec<String>>
作用:只合并缺失的 MCP 服务器配置。已有同名服务器不会被改动。
数据流:输入已有配置和迁移来的 MCP 配置 → 找到 mcp_servers 表 → 如果目标没有就整表加入;如果有就逐个补缺 → 返回新加入的服务器名。
调用关系:它被检测和 import_mcp_server_config 使用,保证迁移 MCP 时不破坏用户已经配置好的服务器。
调用图:被 2 处调用(detect_migrations, import_mcp_server_config);外部调用 4 个(Table, as_table, as_table_mut, new)。
write_toml_file1795–1799 ↗
fn write_toml_file(path: &Path, value: &TomlValue) -> io::Result<()>
作用:把 TOML 配置对象写成漂亮的配置文件文本。
数据流:输入文件路径和 TOML 值 → 序列化成格式化文本 → 去掉末尾多余空白并补一个换行 → 写入目标文件。
调用关系:它被 import_config 和 import_mcp_server_config 调用,是最终落盘写配置文件的地方。
调用图:被 2 处调用(import_config, import_mcp_server_config);外部调用 3 个(format!, write, to_string_pretty)。
migrated_mcp_server_names1801–1807 ↗
fn migrated_mcp_server_names(value: &TomlValue) -> Vec<String>
作用:从生成的 MCP 配置里提取服务器名称。用于告诉用户具体迁了哪些服务器。
数据流:输入 TOML 值 → 查找 mcp_servers 表 → 收集所有键名 → 返回服务器名列表,没有就返回空。
调用关系:它被检测和 import_mcp_server_config 使用,给迁移详情和成功结果提供可读名称。
调用图:被 2 处调用(detect_migrations, import_mcp_server_config);外部调用 1 个(get)。
named_migrations1809–1814 ↗
fn named_migrations(names: Vec<String>) -> Vec<NamedMigration>
作用:把一组名字包装成迁移详情里统一使用的小对象。
数据流:输入字符串列表 → 每个字符串变成一个 NamedMigration → 返回对象列表。
调用关系:它由 detect_migrations 使用,把 hooks、commands、subagents、MCP 服务器名整理进迁移详情。
调用图:被 1 处调用(detect_migrations)。
is_empty_toml_table1816–1826 ↗
fn is_empty_toml_table(value: &TomlValue) -> bool
作用:判断一个 TOML 值是不是空表。空表表示没有实际配置可迁。
数据流:输入 TOML 值 → 如果是表就检查是否没有键;如果是其他类型就认为不为空 → 返回布尔值。
调用关系:它被检测、import_config 和 import_mcp_server_config 使用,用来跳过没有内容的迁移。
调用图:被 3 处调用(detect_migrations, import_config, import_mcp_server_config)。
invalid_data_error1828–1830 ↗
fn invalid_data_error(message: impl Into<String>) -> io::Error
作用:创建一种“数据格式不对”的输入输出错误。这样上层能统一按文件或配置错误处理。
数据流:输入错误消息 → 包装成 io::ErrorKind::InvalidData 类型的错误 → 返回错误对象。
调用关系:它被很多解析和导入函数调用,比如配置格式错误、目标路径不合理、插件详情缺失时,都会用它生成清晰错误。
调用图:被 7 处调用(import, import_agents_md, import_config, import_mcp_server_config, import_plugins, build_config_from_external, merge_missing_toml_values);外部调用 2 个(into, new)。
migration_item_type_label1832–1844 ↗
fn migration_item_type_label(item_type: ExternalAgentConfigMigrationItemType) -> &'static str
作用:把迁移类型变成适合打点统计的短标签。比如 Skills 变成 skills。
数据流:输入迁移类型枚举 → 按类型匹配固定字符串 → 返回标签文本。
调用关系:它为迁移指标服务,通常通过 migration_metric_tags 间接使用,让监控系统能按迁移类型分类统计。
record_import_error1846–1860 ↗
fn record_import_error(
result: &mut ExternalAgentConfigImportItemResult,
failure_stage: &'static str,
message: impl Into<String>,
source: Option<String>,
)
作用:把一次导入错误写进某个迁移结果里。它会补上类型、阶段、目录和来源等上下文。
数据流:输入结果对象、失败阶段、错误消息和可选来源 → 组装成原始错误记录 → 调用 record_error 保存到结果中。
调用关系:它被 import、会话校验和会话导入流程使用,是统一记录导入失败的入口。
调用图:调用 1 个内部函数(record_error);被 4 处调用(import, import, validate_pending_session_imports, import_sessions);外部调用 1 个(into)。
record_plugin_import_errors1862–1875 ↗
fn record_plugin_import_errors(
outcome: &mut PluginImportOutcome,
cwd: Option<&Path>,
plugin_ids: &[String],
failure_stage: &'static str,
message: impl Into<String>,
)
作用:一次性给多个插件记录同一个导入失败。比如插件市场安装失败时,市场里的多个插件都会失败。
数据流:输入插件导入结果、目录、插件编号列表、失败阶段和消息 → 为每个插件生成一条错误 → 追加到插件结果的错误列表。
调用关系:它只被 import_plugins 调用,专门处理“某个市场级错误导致一组插件都失败”的情况。
调用图:被 1 处调用(import_plugins);外部调用 1 个(into)。
plugin_import_raw_error1877–1891 ↗
fn plugin_import_raw_error(
cwd: Option<&Path>,
failure_stage: &'static str,
message: String,
source: Option<String>,
) -> ExternalAgentConfigImportRawError
作用:创建一条插件导入错误记录。它把错误统一标记为 Plugins 类型。
数据流:输入目录、失败阶段、消息和插件来源编号 → 组装成 ExternalAgentConfigImportRawError → 返回给调用者保存。
调用关系:它被 import_plugins 直接使用,也被 record_plugin_import_errors 间接用于批量生成插件错误。
调用图:被 1 处调用(import_plugins)。
migration_metric_tags1893–1910 ↗
fn migration_metric_tags(
item_type: ExternalAgentConfigMigrationItemType,
skills_count: Option<usize>,
) -> Vec<(&'static str, String)>
作用:为迁移统计指标准备标签。标签就是监控里用来分类的小字段。
数据流:输入迁移类型和可选数量 → 生成 migration_type 标签;如果是技能、子智能体或命令,再加上数量标签 → 返回标签列表。
调用关系:它被 emit_migration_metric 调用。这样检测和导入阶段发出的指标都有统一格式。
调用图:被 1 处调用(emit_migration_metric);外部调用 2 个(matches!, vec!)。
emit_migration_metric1912–1926 ↗
fn emit_migration_metric(
metric_name: &str,
item_type: ExternalAgentConfigMigrationItemType,
skills_count: Option<usize>,
)
作用:发送一条迁移统计指标。它用于记录用户检测或导入了哪类内容。
数据流:输入指标名、迁移类型和数量 → 获取全局监控对象;没有监控就直接跳过 → 生成标签并给计数器加 1。
调用关系:它被检测、插件检测和主导入流程调用。它不影响迁移成功与否,只负责给系统留下统计记录。
调用图:调用 1 个内部函数(migration_metric_tags);被 3 处调用(detect_migrations, detect_plugin_migration, import);外部调用 1 个(global)。
core/src/personality_migration.rs源码 ↗
这个文件解决的是升级兼容问题:新版程序需要一个 personality(可以理解成助手说话和做事的风格)配置,但老用户以前可能没有这个字段。它不会粗暴地给所有人都改配置,而是先看 codex_home 目录里有没有迁移标记文件;有就说明以前处理过,直接跳过。没有的话,再看用户是否已经手动写了 personality;如果写了,就尊重用户选择,只补一个标记。再看本地有没有历史会话;如果没有,说明不像老用户,也不强行写配置。只有在“没有标记、没有显式个性、但有历史会话”时,它才把 personality 写成 Pragmatic。最后都会尽量创建 .personality_migration 这个标记,像在门口贴一张“已检查”的纸条,避免下次启动重复迁移。
maybe_migrate_personality25–60 ↗
async fn maybe_migrate_personality(
codex_home: &Path,
config_toml: &ConfigToml,
state_db: Option<StateDbHandle>,
) -> io::Result<PersonalityMigrationStatus>
作用:这是整个迁移流程的总入口。它判断这次启动要不要给老用户补上默认 personality,并返回一个状态,说明是跳过了、还是实际改了配置。
数据流:进去的是用户目录 codex_home、当前读到的 config_toml 配置,以及可选的 state_db 数据库句柄。它先拼出 .personality_migration 标记文件路径并检查是否存在;再检查配置里是否已经有 personality;再通过 has_recorded_sessions 查看有没有历史会话。符合迁移条件时,它用 ConfigEditsBuilder 把 personality 写成 Pragmatic,然后调用 create_marker 写标记。出来的是 PersonalityMigrationStatus,告诉调用者发生了哪种结果;同时可能会改配置文件,并可能新建标记文件。
调用关系:它会在启动或配置加载相关流程里被调用,比如 migrate_personality_if_needed 和 run_main_with_transport_options,也被多组测试直接验证。它自己不亲自翻会话细节,而是把“有没有历史会话”的判断交给 has_recorded_sessions;需要留下“已处理”痕迹时交给 create_marker。
调用图:调用 3 个内部函数(new, create_marker, has_recorded_sessions);被 13 处调用(migrate_personality_if_needed, run_main_with_transport_options, applied_migration_is_idempotent_on_second_run, marker_short_circuits_migration_with_legacy_profile, migration_marker_exists_no_sessions_no_change, missing_legacy_profile_does_not_block_migration, no_marker_archived_sessions_sets_personality, no_marker_explicit_global_personality_skips_migration, no_marker_meta_only_rollout_is_treated_as_no_sessions, no_marker_no_sessions_no_change (+3 more));外部调用 2 个(join, try_exists)。
has_recorded_sessions62–79 ↗
async fn has_recorded_sessions(
codex_home: &Path,
default_provider: &str,
state_db: Option<StateDbHandle>,
) -> io::Result<bool>
作用:这个函数回答一个简单问题:这个用户目录里有没有记录过会话。迁移逻辑靠它判断用户是不是可能从旧版本一路用过来的老用户。
数据流:进去的是用户目录 codex_home、默认模型提供方 default_provider,以及可选的 state_db。它先创建一个 LocalThreadStore,也就是本地会话仓库的读取入口;然后先查未归档会话,再查已归档会话。只要任意一边找到至少一条会话,就返回 true;都没有才返回 false;读取出错会变成 io::Result 里的错误返回。
调用关系:它只被 maybe_migrate_personality 调用,是迁移流程里的“查历史记录”步骤。它不直接列出很多会话,而是把具体查询交给 has_threads,并且分别问一次普通会话和归档会话。
调用图:调用 2 个内部函数(has_threads, new);被 1 处调用(maybe_migrate_personality);外部调用 1 个(to_path_buf)。
has_threads81–99 ↗
async fn has_threads(store: &LocalThreadStore, archived: bool) -> io::Result<bool>
作用:这个函数用最省事的方式检查会话仓库里有没有会话:只请求一条。它不关心具体内容,只关心“有没有”。
数据流:进去的是 LocalThreadStore 会话仓库,以及 archived 这个开关,表示查归档还是未归档。它调用 list_threads,请求第一页、每页只要 1 条,并按创建时间倒序查。仓库返回一页结果后,它看 items 是否为空;不为空就输出 true,空就输出 false;如果底层查询失败,就把错误包装成 io 错误返回。
调用关系:它被 has_recorded_sessions 连续调用两次:一次查未归档,一次查已归档。它是最底层的“探测器”,只负责向本地会话仓库 list_threads 发一个很小的查询,帮助上层快速决定是否需要迁移。
调用图:调用 1 个内部函数(list_threads);被 1 处调用(has_recorded_sessions);外部调用 1 个(new)。
create_marker101–112 ↗
async fn create_marker(marker_path: &Path) -> io::Result<()>
作用:这个函数创建迁移标记文件,表示 personality 迁移检查已经做过。它的作用是让程序下次启动时不要重复判断和重复修改配置。
数据流:进去的是 marker_path,也就是要创建的 .personality_migration 文件路径。它用“只在文件不存在时创建”的方式打开文件;成功后写入文本 v1 和换行。出来是成功或失败的 io::Result;如果文件刚好已经存在,它把这件事当成成功,不报错;如果是别的磁盘错误,就把错误交回调用者。
调用关系:它由 maybe_migrate_personality 在多个分支里调用:用户已有 personality 时、没有历史会话时、真正完成迁移后都会调用它。它像流程最后盖章的人,不决定要不要迁移,只负责留下“这一步已经处理过”的凭证。
调用图:被 1 处调用(maybe_migrate_personality);外部调用 1 个(new)。
守护进程本地设置存储
此文件在主应用服务器配置流程之外,定义并持久化守护进程自己的本地设置。
app-server-daemon/src/settings.rs源码 ↗
这个文件的核心是 DaemonSettings,也就是“后台服务设置”。目前它只保存一个开关:remote_control_enabled,意思是远程控制功能是否打开。程序启动或需要配置时,会从磁盘上的 JSON 文件读取这些设置;如果文件还不存在,就当作默认设置使用,不把“第一次运行没配置文件”当成错误。保存时,它会先确保目标文件夹存在,再把设置写成好看的 JSON。这里还特别规定 JSON 字段用 camelCase(小驼峰,比如 remoteControlEnabled),这是为了让配置文件格式更像常见的 Web/JSON 风格,也方便和其他工具对接。整体上,它像一个小记事本:负责把服务的少量偏好安全地存到硬盘上,再在需要时拿回来。
DaemonSettings::load16–28 ↗
async fn load(path: &Path) -> Result<Self>
作用:从指定路径读取后台服务设置。如果设置文件不存在,它不会报错,而是给出默认设置,这样程序第一次启动也能正常跑。
数据流:进去的是一个文件路径。它先尝试把这个文件读成文字;如果发现文件不存在,就直接产出默认的 DaemonSettings;如果读取失败,就带上“哪个文件读失败了”的说明返回错误。读到内容后,它把 JSON 文字解析成 DaemonSettings,解析失败也会告诉调用者是哪个设置文件有问题。
调用关系:它是配置读取流程里的入口小零件,会被 load_settings 调用。load_settings 需要拿到服务配置时,把路径交给它;它再把实际读文件和解析 JSON 的活交给系统文件读取函数和 serde_json 解析函数。
调用图:被 1 处调用(load_settings);外部调用 3 个(default, read_to_string, from_str)。
DaemonSettings::save30–44 ↗
async fn save(&self, path: &Path) -> Result<()>
作用:把当前的后台服务设置保存到指定文件。有人改了设置之后,可以用它把新状态写回硬盘,避免下次启动时丢失。
数据流:进去的是当前这份 DaemonSettings 和一个目标文件路径。它先看路径有没有父目录,有的话就创建这些目录,避免因为文件夹不存在而写入失败。然后它把设置转成格式化过的 JSON 字节,最后写入目标文件。成功时没有额外结果;失败时返回带说明的错误,并且可能已经创建过目录。
调用关系:它位于“改完配置后落盘”的环节。调用者把要保存的设置交给它,它负责串起三件事:准备目录、把设置变成 JSON、写入磁盘。文件中没有显示谁调用它,但它明显是给保存设置的流程使用的。
调用图:外部调用 4 个(parent, create_dir_all, write, to_vec_pretty)。
tests::daemon_settings_use_camel_case_json54–62 ↗
fn daemon_settings_use_camel_case_json()
作用:这是一个测试,用来确认设置写成 JSON 时字段名确实是 remoteControlEnabled,而不是 Rust 代码里的 remote_control_enabled。
数据流:进去的是测试里临时创建的一份 DaemonSettings,其中远程控制开关为 true。测试把它序列化成 JSON 字符串,然后和预期的字符串比较。如果两者一样,说明输出格式没变;如果不一样,测试失败,提醒开发者配置文件格式可能被改坏了。
调用关系:它只在测试时运行,不参与真实服务启动。它保护 DaemonSettings 的 JSON 外观,避免以后有人改字段命名规则时,悄悄破坏已有配置文件或外部工具的兼容性。
调用图:外部调用 1 个(assert_eq!)。