文件行号
internal/agentruntime/desktop_stream.go L135-142(Done 分支 L142 round.Reset())vs L151-160(Error 分支无任何 Reset——只构造 message/mirror.Flush/PushError)+ desktop/wailskit/chat.go:141(imRound 为 ChatBridge 字段——桥生命周期累积器;全文件仅 141/2302 两处引用,无外部 Reset)+ 对照 internal/im/round_state.go:23-27(前端显式 Reset——路径不对称);config_hotreload.go L164-166(applyFreshConfig 仅赋值 Vendors/Fallback/KnightConfig/MaxIterations/SessionTokenBudget/ToolCallBudget 六字段——无 Fallbacks)+ internal/config/config.go:556-567(FallbackChain 同时消费 c.Fallback 与 c.Fallbacks)+ L176-177("config refreshed ... fallback=%v" 日志)+ config_access.go:1508/1546(运行期可写 Fallbacks——单条路径热刷语义已确立);config_hotreload.go L174-177(解锁后读 old.Fallback.IsConfigured()——写路径 setFallback* 族 L1482-1547 均不持 cfgMu);interactions.go L40-47(AwaitApproval select)/L55-61(AwaitAskUser 同构)+ L64-74(ResolveApproval 先 remove 再非阻塞 send——UI 侧认为已批准)
问题
案 A(Med·复核升格 Med-High,静默失效+日志误导):热重载只刷新遗留单条 Fallback——现代 Fallbacks 链永远停留启动快照
- 用户编辑 ggcode.yaml 的
fallbacks: 数组 → 2s 后日志打出 "config refreshed ... fallback=..." → provider 重建 FallbackChain 仍用旧数组——多级 failover 链静默失效,需重启;单条 fallback: 字段则正常热刷——行为不一致加重迷惑
- 三重实证:applyFreshConfig 全文无 Fallbacks 赋值;a.cfg 指针从不替换(grep
a.cfg = 零匹配——只改旧对象字段);日志只反映 legacy 单条却以整体 "config refreshed" 名义打出
- 排障成本高:用户看到"已刷新"日志却毫无效果
修:applyFreshConfig 补 old.Fallbacks = fresh.Fallbacks(一行)。
案 B(Med,跨轮泄漏):Error 轮不 Reset round——错误轮残留文本/工具计数泄漏进下一轮 EmitRoundSummary
- 轮 1 流式中途 provider 报错(网络断/abort)→ round 已累积部分文本+N 工具计数 → Error 分支不 Reset → 轮 2 完成 → Done 的 round.Text() = 轮 1 残留+轮 2 拼接、计数两轮之和 → EmitRoundSummary(IM/移动端推送)发出污染摘要
- wailskit 桥无外部 Reset(imRound 全仓仅两处引用);emitIfCurrent 代守卫只决定"是否发"不清理 round;对照 IM 侧前端显式 Reset——路径不对称
修:Error 分支补 round.Reset()(在 mirror.PushError 后)。
案 C(Low-Med,已送达仍 Deny):AwaitApproval 的 ctx.Done 分支与已缓冲 decision 的 select 随机竞争——用户批准被确认接收后仍被丢弃
案 D(Low,真实 race·日志-only 后果):热重载解锁后读 Fallback + 写路径全族不持锁
- L177 解锁后读 old.Fallback.IsConfigured();更关键:setFallback* 族(L1482-1547)写 a.cfg.Fallback* 均不持 cfgMu——热刷新赋值与 /config set fallback.* 写路径无同步并发(-race 可报)
- 后果限于 debug 日志瞬时失真+可能丢一次更新(saveAndPatch 落盘可恢复)
修:setFallback* 族补锁;日志读移锁内。
干净项
im_round.go 无问题(单 goroutine 累积器,三侧独立实例)。desktop_adapters.go:nil 接收者/回调判空完备;mirror 半部分主树零调用方(chat.go:2302 传 nil)为预留死代码 Info 级。config_hotreload:重复启动/watcher 生命周期/防抖自愈/坏 YAML last-good/a.cfg 与 agentInst 先行发生安全/vendors 保守门全对。desktop_stream:channel-free 同步转换/顺序固定/usage 无溢出/truncateRunes 边界全对。interactions:Resolve 原子出表双 resolve 不可能/超时职责划分/CancelAll 锁外投递无死锁/FirstPending 随机序不构成错路由。
严重程度
Med-High + Med + Low-Med + Low
独立复核确认:两分支 Reset 对照原文、imRound 全仓 grep 两处、FallbackChain 双字段消费、applyFreshConfig 全字段枚举、a.cfg= 零匹配、日志原文、setFallback 无锁族、select 随机性与 drain 修法、#1023 设计意图边界论证。*
文件行号
internal/agentruntime/desktop_stream.goL135-142(Done 分支 L142round.Reset())vs L151-160(Error 分支无任何 Reset——只构造 message/mirror.Flush/PushError)+ desktop/wailskit/chat.go:141(imRound 为 ChatBridge 字段——桥生命周期累积器;全文件仅 141/2302 两处引用,无外部 Reset)+ 对照 internal/im/round_state.go:23-27(前端显式 Reset——路径不对称);config_hotreload.go L164-166(applyFreshConfig 仅赋值 Vendors/Fallback/KnightConfig/MaxIterations/SessionTokenBudget/ToolCallBudget 六字段——无 Fallbacks)+ internal/config/config.go:556-567(FallbackChain 同时消费 c.Fallback 与 c.Fallbacks)+ L176-177("config refreshed ... fallback=%v" 日志)+ config_access.go:1508/1546(运行期可写 Fallbacks——单条路径热刷语义已确立);config_hotreload.go L174-177(解锁后读 old.Fallback.IsConfigured()——写路径 setFallback* 族 L1482-1547 均不持 cfgMu);interactions.go L40-47(AwaitApproval select)/L55-61(AwaitAskUser 同构)+ L64-74(ResolveApproval 先 remove 再非阻塞 send——UI 侧认为已批准)问题
案 A(Med·复核升格 Med-High,静默失效+日志误导):热重载只刷新遗留单条 Fallback——现代 Fallbacks 链永远停留启动快照
fallbacks:数组 → 2s 后日志打出 "config refreshed ... fallback=..." → provider 重建 FallbackChain 仍用旧数组——多级 failover 链静默失效,需重启;单条fallback:字段则正常热刷——行为不一致加重迷惑a.cfg =零匹配——只改旧对象字段);日志只反映 legacy 单条却以整体 "config refreshed" 名义打出修:applyFreshConfig 补
old.Fallbacks = fresh.Fallbacks(一行)。案 B(Med,跨轮泄漏):Error 轮不 Reset round——错误轮残留文本/工具计数泄漏进下一轮 EmitRoundSummary
修:Error 分支补 round.Reset()(在 mirror.PushError 后)。
案 C(Low-Med,已送达仍 Deny):AwaitApproval 的 ctx.Done 分支与已缓冲 decision 的 select 随机竞争——用户批准被确认接收后仍被丢弃
修:Done 分支先非阻塞 drain ch 再降级 Deny。
案 D(Low,真实 race·日志-only 后果):热重载解锁后读 Fallback + 写路径全族不持锁
修:setFallback* 族补锁;日志读移锁内。
干净项
im_round.go 无问题(单 goroutine 累积器,三侧独立实例)。desktop_adapters.go:nil 接收者/回调判空完备;mirror 半部分主树零调用方(chat.go:2302 传 nil)为预留死代码 Info 级。config_hotreload:重复启动/watcher 生命周期/防抖自愈/坏 YAML last-good/a.cfg 与 agentInst 先行发生安全/vendors 保守门全对。desktop_stream:channel-free 同步转换/顺序固定/usage 无溢出/truncateRunes 边界全对。interactions:Resolve 原子出表双 resolve 不可能/超时职责划分/CancelAll 锁外投递无死锁/FirstPending 随机序不构成错路由。
严重程度
Med-High + Med + Low-Med + Low
独立复核确认:两分支 Reset 对照原文、imRound 全仓 grep 两处、FallbackChain 双字段消费、applyFreshConfig 全字段枚举、a.cfg= 零匹配、日志原文、setFallback 无锁族、select 随机性与 drain 修法、#1023 设计意图边界论证。*