03 三方配置合并去重 本节摘要:上一节讲 OpenWork 用「服务端管理的配置文件」注入,但现实里用户可能也有自己的引擎配置,甚至可能有 legacy(老格式)配置。三方配置凑在一起,怎么合并?OpenCode 的解法是三方配置合并去重:legacy 配置、用户配置、服务端管理配置按规则叠加,冲突时按优先级解决,重复项去重。本节讲清这套合并:三方分别是什么、合并优先级、为什么必须去重。
本节摘要:上一节讲 OpenWork 用「服务端管理的配置文件」注入,但现实里用户可能也有自己的引擎配置,甚至可能有 legacy(老格式)配置。三方配置凑在一起,怎么合并?OpenCode 的解法是三方配置合并去重:legacy 配置、用户配置、服务端管理配置按规则叠加,冲突时按优先级解决,重复项去重。本节讲清这套合并:三方分别是什么、合并优先级、为什么必须去重。
先理清这三方配置分别是什么、从哪来:
| 配置方 | 来源 | 例子 |
|---|---|---|
| legacy 配置 | 老格式的历史配置 | 早期 OpenWork 用的格式,现在兼容 |
| 用户配置 | 用户自己的引擎配置 | 用户直接用引擎时写的配置 |
| 服务端管理配置 | OpenWork 注入的(第 02 节) | OpenWork 想让引擎用的 Agent/插件/MCP/Provider |
这三方都可能存在,引擎要把它们合并成一份最终生效的配置。这就是「三方合并」的来源。
💡 为什么有 legacy:软件演进过程中,配置格式会变。老用户可能有老格式的配置,OpenWork 不能强迫他们立刻迁移,所以要兼容(读 legacy 格式)。这是软件演进的常态代价。
三方合并的核心问题是优先级——冲突时谁胜出。OpenWork 的合并大致遵循这样的优先级(具体以本地实现为准):
服务端管理配置(最高优先级) │ 冲突时覆盖 用户配置 │ 冲突时覆盖 legacy 配置(最低优先级)
也就是说:
这个优先级设计反映了一个权衡:OpenWork 要能接管引擎(服务端配置最高),但又尊重用户的合理自定义(用户配置覆盖 legacy)。
⚠️ 注意:这个优先级不是「OpenWork 蛮横地覆盖一切」。服务端管理配置主要管「OpenWork 注入的能力」(Agent/插件/MCP/Provider),不会无端覆盖用户的其他配置。冲突主要发生在「同一项被多方定义」时。
合并不是「整体替换」,而是按项合并。配置里有很多项(Agent 列表、插件列表、MCP 列表、Provider 设置......),每一项单独合并:
Agent 列表: legacy: [A] 用户: [B] 服务端: [C, D] ──► 合并后:[A, B, C, D](各自加进来) 某 Provider 设置: legacy: { url: X } 用户: { url: Y } 服务端: { url: Z } ──► 合并后:{ url: Z }(按优先级取最高的)
列表类(如 Agent 列表)通常是「合集」(都加进来),标量类(如某个 Provider 的 url)是「按优先级取一」。这种「按项、按类型」的合并,让三方能共存而不全盘互斥。
合并里有一个关键步骤——去重。为什么必须去重?考虑这个场景:
用户配置:[插件甲] 服务端管理配置:[插件甲(同一个,但 OpenWork 也注入了)] │ ▼ 不去重直接合并 [插件甲, 插件甲] ← 重复了! │ ▼ 引擎看到两个「插件甲」 报错或行为异常(引擎不允许同名重复)
所以合并必须去重——识别出「同一个东西被多方定义」,只保留一份(按优先级留最高那方)。去重的依据通常是名字或唯一标识:
合并后:[插件甲, 插件甲] │ ▼ 按名字去重(同名只留优先级最高的) [插件甲(服务端版,因为服务端优先级最高)]
💡 去重避免引擎报错:很多引擎不允许同名配置重复,不去重会直接启动失败。去重看似细节,实则是合并能成功的关键。
诚实地说,三方合并是第 5 章里最容易出 bug 的部分。复杂性来自:
这就是为什么 OpenWork 有专门的「运行时配置存储」模块来管这件事(下一节讲它的同步)。合并不是「拼一拼」那么简单,它是一个需要仔细设计的子系统。
合并的最终产物,是一份引擎实际生效的配置——它包含了三方的合力:legacy 的兼容兜底 + 用户的自定义 + OpenWork 的注入。引擎读到这份合并配置,就同时满足了「兼容老用户、尊重用户自定义、被 OpenWork 接管」三个目标。
legacy 配置 ─┐ 用户配置 ─┼─► 三方合并去重 ──► 引擎生效配置 服务端配置 ─┘
合并产出了配置,但配置改了怎么让引擎知道?这就是下一节的「配置新鲜度同步」。