[Bug] 3.10.1/3.10.2 (Windows x64) 上下文重构(compaction/rewind)后 tool call 渲染丢失或错位:顺序型 tool call ID 复用 × 投影层全局去重
Nobody has claimed this yet.
- Dominant language
- No language data
- Stars
- 22
- Forks
- 1
- PR merge metrics
- No merged PRs in 30d
Description
摘要
openai-compatible 渠道(如经中转的 kimi-k3)返回的 tool call ID 呈「工具名 + 当前上下文内出现序号」的顺序型特征(Bash_0、Edit_14…;compaction/rewind 后从头重数。生成方在模型/中转/SDK 的哪一层未逐层定位,但不影响本 bug 成立:只要 ID 复用即触发)。上下文重构(自动/手动 compaction、rewind、编辑重发)会让新工具调用与历史 ID 大规模复用,而 ProductProjection 以 callID 为全局键管理工具行:openToolRow 对已见 ID 直接跳过,finalizeStreamingToolInput/onToolCallScheduled 将新调用 upsert 覆盖到首次出现的旧行。净效果:重构后的回合「已工作 X 分 X 秒」区块存在但展开为空,新工具内容静默写入历史旧行。数据库中 tool part 完整无损,纯显示层缺陷。
环境:ZCode Desktop 3.10.1.6272 / 3.10.2.6414,Windows 10 26200 x64。代码引自 3.10.2 包内 resources/glm/zcode.cjs。模型经 openai-compatible 中转(kimi-k3 / glm-5.3 / qwen3.8-max 混用)。
最短复现
- 使用返回顺序型 tool call ID 的 openai-compatible 渠道(部分中转服务转发 kimi 时出现;也可用 mock 渠道固定返回
toolName_N型 ID); - 长会话触发一次 compaction(自动/手动)或 rewind;
- 之后回合中同名工具调用 ID 与历史复用 → 工具块不再渲染。
对照:换用返回 UUID 型 ID 的渠道后立即恢复。
现象
- 「已工作 X 分 X 秒」折叠区块存在(时长正确),展开为空;
- 同一消息内 text / reasoning part 渲染正常,仅 tool part 丢失;
- compaction 消息被渲染成带操作图标的空白卡片(次要缺陷);
- 重启无效(冷恢复重放走同一去重路径);fork 会话恢复正常。
根因(ProductProjection 类)
openToolRow(ModelStreamingtool_input_start):if (callId==="" || shouldHideInvalidToolCall(callId) || this.toolRowIdByCallId.has(callId)) return [];—— callID 已见即跳过建行;finalizeStreamingToolInput/onToolCallScheduled:findToolRow(callID)命中即 upsert 到旧行(新调用 input/output 覆盖旧行内容);updateToolIndexesAfterDeltas:row.removed时仅清理指向被截断行的条目,指向幸存行的条目保留 → 复用 ID 命中幸存行。
净效果:每个 callID 在投影生命周期内只渲染一个块(首次出现位置);上下文重构使计数重置 → 后续调用复用 ID → 被跳过或错位覆盖。
验证实验(用户环境实测)
- fork 对照:fork 将全部 callID 重写为 UUID → 渲染立即恢复;同会话不 fork 持续丢失;
- 编号重置实测:compaction 前后
Bash_157 → Bash_9,重置后新调用 96%(25/26)与历史 ID 撞车; - 模型切换 A/B:同一会话仅切换渠道(顺序型 → UUID 型 ID)→ 渲染立即恢复(两例);
- 数据验证:单会话
Bash_0出现 26 次;空「已工作」区块内 5 个 callID 均在更早处出现过 3–23 次。
修复建议(任一)
- 上下文重构边界(rewind / compaction 成功)时清空
toolRowIdByCallId; - 去重范围收窄到单 turn;
- 以
toolCallId + assistantMessageId复合键去重。
Workaround:切换到 UUID 型 ID 的渠道/模型;或 fork 会话(fork 重写全部 callID,这也是 fork 恢复的原因)。
已排除:数据库 tool part 完整;sequence/parentID/可见性正常;渲染进程无崩溃;模型本身(换 UUID 渠道即恢复)。
附:截图对位、消息 ID、日志事件与逐轮修正记录见评论区。
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with resources/glm/zcode.cjs and inspect ProductProjection.openToolRow, finalizeStreamingToolInput, onToolCallScheduled, and updateToolIndexesAfterDeltas. Reproduce with sequential tool IDs across compaction or rewind, compare UUID IDs, and verify that post-reconstruction tool calls render in their new blocks without altering earlier rows.
Written by the indexing model from the issue text.
Assessment
- Domain
- desktop
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 48/100