G4|Feature Flags:有效开关来自注册表,不是一份 [features]
先别把 config.toml 当成产品目录
读完 B5,下一件错事是打开 [features],按表里的 true/false 猜「仓库里有什么」。然后 multi_agent 被理解成协作模式,network_proxy = true 被理解成已经在代理流量,collaboration_modes = false 被理解成能关掉 /plan。
判断先说清楚:codex-features 是单一注册表。 每个能力有稳定 key、Stage、出厂 default_enabled。你在 toml 里写的只是叠加层。effective set = 出厂 default → 分层 TOML / legacy 别名 → CLI overlay → 依赖归一 → 企业 ManagedFeatures 钉死。B5 的 /status 和 codex features 是这面镜子;这一章讲镜子后面的表。
1. 注册表即契约:Stage 决定谁看得见
Stage 五种(features/src/lib.rs:44-60):UnderDevelopment / Experimental / Stable / Deprecated / Removed。FEATURES 表把每个 Feature 钉死:id、key、阶段、默认开不开。
阶段不是注释。UnderDevelopment 不该出现在用户 toml 的「随便开」清单里;Experimental 走 /experimental 和 codex features;Removed 的键还在,是为了旧配置别炸——CollaborationModes 就是 Removed 且恒开(:1645-1648),TUI collaboration_modes_enabled() 直接返回 true。你在 config 里写 false,开关已经不存在了。
几个会读歪的出厂值:
| Feature | key | Stage | 默认 |
|---|---|---|---|
Collab | multi_agent | Stable | 开(子代理工具门闩,D5) |
MultiAgentV2 | multi_agent_v2 | Stable | 关 |
NetworkProxy | network_proxy | Experimental | 关(G6) |
Goals | goals | Stable | 开 |
CodeMode | code_mode | Experimental | 关 |
CollaborationModes | collaboration_modes | Removed | 恒开 |
没有注册表会怎样?每个 crate 自己 if cfg,legacy 键和现名各长各的,企业无法钉死「不许开代理」。抄开关先抄这张表,不要抄一份营销功能清单。
2. 合并顺序:后面的 overlay 赢,企业最后可以锁死
Features::with_defaults() 先铺出厂值。from_sources()(约 :651-683)再叠:用户/项目/profile 的 [features]、legacy 键、CLI --enable / --disable。然后 normalize_dependencies 把「开了 A 必须带上 B」收干净。最后 ManagedFeatures(core/src/config/managed_features.rs:151-166)按企业 feature_requirements 钉死——管理员不许的,你在 toml 里写 true 也进不了 effective set。
配置层本身仍是 B5 那条 overlay 链。features 只是其中一组 key。项目层就算被信任,也进不了 denylist 里的路由/凭证键;feature 能否被项目打开,还要看 requirements。
没有这层顺序会怎样?你以为关掉了 unified_exec,企业或依赖归一又给开回来;或者项目 .codex/config.toml 偷偷打开 Experimental,clone 即生效。有效开关必须能指出 是哪一层赢的。
3. codex features 只是写用户 config 的糖
CLI codex features list|enable|disable(cli/src/main.rs 里 Features 子命令)改的是用户 CODEX_HOME/config.toml,不是内存里另建一张表。--enable / --disable 是 SessionFlags,优先级更高,仍然要过同一条 from_sources。管理员 requirements 可以不许你打开某项——命令成功写了文件,effective 仍是关。
B5 已经说过:这三面镜子(features CLI、codex doctor、/status)都不拥有真相。G4 补一句:写开关和读有效值不是同一条路。 写进用户层,读的时候还要折企业层。
没有「只是糖」会怎样?你会以为 codex features disable multi_agent 立刻拆掉 D5 工具,却忘了下一层 overlay 或 Removed 键根本不听。
4. /experimental 走发现 RPC,不是第二份注册表
TUI /experimental 弹窗走 app-server 的实验功能 RPC。它列出的是 当前身份下允许切换的实验项,不是 FEATURES 表全文。服务端可以按套餐、requirements、Stage 过滤。勾选之后仍写回配置层,再走第 2 节的合并。
没有「发现面」会怎样?要么把 UnderDevelopment 全摊给用户,要么每个前端自己维护一份「实验清单」,和注册表漂移。UI 是过滤后的投影。
5. 扁平 bool 和嵌套表是双入口
[features] network_proxy = true 是扁平键。[features.network_proxy] 里还有 mode、credential_broker、domain 表(feature_configs.rs 的 FeatureToml)。两种写法收成同一套迭代。G6 的代理、D5 的 multi_agent_v2 嵌套槽位,都是这个双入口的消费者,不是又一套 ini。
legacy 键在 legacy.rs:11-52:collab → Collab,web_search → WebSearchRequest,telepathy → Chronicle,experimental_use_unified_exec_tool → UnifiedExec。旧文档和旧 toml 能打开新 Feature,新代码只认新 id。
没有双入口会怎样?嵌套配置找不到扁平开关,代理「开了」但 mode 丢失;或者旧 collab = true 静默失效,子代理工具突然消失。
6. 结语:带走一句话
抄开关要抄注册表和合并链——Stage 决定谁看得见,企业可以锁死,CLI 和 /experimental 只是写配置的糖——不要把一份 [features] 当成产品目录。