Files
deepseek-harness/.agents/notes/archived/bug-fix/2026-07-30-tui-adapter-registration-race.zh.md
T

3.3 KiB

Agent Note: TUI 模型上下文解析在适配器注册竞争时延后重试

Status: implemented Archived: 2026-08-04

English | 中文

Problem

Cordis 按服务可用性而非配置顺序激活插件,因此 TUI(其 inject 只要求 llm 服务)可能在 dsh-llm-pi-ai 这类已配置的适配器插件完成提供方路由注册之前就挂载。TUI 的模型控制器在挂载时立即解析所选模型的上下文窗口;当 agent 的路由指向尚未注册的提供方时,resolveModelInfoNO_ADAPTER 拒绝,于是每个新会话都会打印 Could not resolve model context: no adapter registered for provider "…" —— 对一份完全正常的配置报出的虚假错误(适配器几毫秒后就完成注册,对话也一切正常)。

Decision

TUI 模型控制器把上下文窗口解析中的 NO_ADAPTER 拒绝视为瞬态状态而非错误:静默搁置这次解析,并在下一次 llm/adapters-updated 提交时重新解析——这是 LlmService 本就在每个路由提交点发出的无载荷注册表通知。若某次提交仍缺少该路由,等待会被再次搁置,因此无关的拓扑变化保持沉默。任何目标变更都会重新进入解析并清除挂起的等待,因此延后状态绝不会相对当前选择变陈旧;其他所有解析错误仍照常打印通知。

Alternatives considered

让 TUI 等启动结算后再解析。 TUI 不依赖 Loader(测试和嵌入方在没有 Loader 的环境下运行),而且"已结算"在插件内部不可观测;为一次外观性的解析引入 Loader 耦合会颠倒依赖方向。

用定时器轮询或重试。 定时器只能猜测激活延迟,遇到慢适配器仍会误报,还会引入一个没有归属者的可调参数。注册表本就通过 llm/adapters-updated 公告每次提交;订阅它既精确又零成本。

调整配置顺序让适配器先加载。 Loader 中行顺序不承载加载语义(激活按设计由服务驱动),因此这无法用配置表达。

彻底压制 NO_ADAPTER 错误。 那样的话,永久缺失的适配器(提供方名字拼错)在上下文窗口路径上就永远不会暴露。延后重试保留了信号:错误的提供方名字仍会在选择器中表现出类似 model unset 的行为,并在分派时大声失败,而启动竞争则自行化解。

改为在每次提交消息时解析上下文窗口,而不是在挂载时。 发送路径本就按步解析(prepareCall()),且指示器是持续显示的,不只在发送时;按提交解析显示值会让指示器在首条消息之前一直空白,并为一个仅在路由变化时才变的值反复执行适配器 I/O。

Consequences

真正配置错误的提供方不再在启动时打印上下文解析错误——它改在首次分派时暴露,那才是该失败可以被处理的地方。控制器订阅每次 llm/adapters-updated 提交,但只在有等待被搁置时才动作;监听器的 disposer 经由控制器的 detach() 在频道的 detachListeners() 中释放,与同级频道监听器保持对称。由三个 TUI 测试覆盖:延后的解析在无关提交中保持沉默、在该路由的提交到来时完成;目标变更丢弃陈旧等待;频道 detach 之后注册表提交不再重新进入解析。