zai-org / zai-org/feedback

[Bug] 3.10.1/3.10.2 (Windows x64) 上下文重构(compaction/rewind)后 tool call 渲染丢失或错位:顺序型 tool call ID 复用 × 投影层全局去重

Open
#456 6 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

priority: P2
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_0Edit_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 混用)。

最短复现

  1. 使用返回顺序型 tool call ID 的 openai-compatible 渠道(部分中转服务转发 kimi 时出现;也可用 mock 渠道固定返回 toolName_N 型 ID);
  2. 长会话触发一次 compaction(自动/手动)或 rewind;
  3. 之后回合中同名工具调用 ID 与历史复用 → 工具块不再渲染。
    对照:换用返回 UUID 型 ID 的渠道后立即恢复。

现象

  • 「已工作 X 分 X 秒」折叠区块存在(时长正确),展开为空;
  • 同一消息内 text / reasoning part 渲染正常,仅 tool part 丢失;
  • compaction 消息被渲染成带操作图标的空白卡片(次要缺陷);
  • 重启无效(冷恢复重放走同一去重路径);fork 会话恢复正常。

根因(ProductProjection 类)

  1. openToolRow(ModelStreaming tool_input_start):if (callId==="" || shouldHideInvalidToolCall(callId) || this.toolRowIdByCallId.has(callId)) return []; —— callID 已见即跳过建行;
  2. finalizeStreamingToolInput / onToolCallScheduledfindToolRow(callID) 命中即 upsert 到旧行(新调用 input/output 覆盖旧行内容);
  3. updateToolIndexesAfterDeltasrow.removed 时仅清理指向被截断行的条目,指向幸存行的条目保留 → 复用 ID 命中幸存行。
    净效果:每个 callID 在投影生命周期内只渲染一个块(首次出现位置);上下文重构使计数重置 → 后续调用复用 ID → 被跳过或错位覆盖。

验证实验(用户环境实测)

  1. fork 对照:fork 将全部 callID 重写为 UUID → 渲染立即恢复;同会话不 fork 持续丢失;
  2. 编号重置实测:compaction 前后 Bash_157 → Bash_9,重置后新调用 96%(25/26)与历史 ID 撞车;
  3. 模型切换 A/B:同一会话仅切换渠道(顺序型 → UUID 型 ID)→ 渲染立即恢复(两例);
  4. 数据验证:单会话 Bash_0 出现 26 次;空「已工作」区块内 5 个 callID 均在更早处出现过 3–23 次。

修复建议(任一)

  1. 上下文重构边界(rewind / compaction 成功)时清空 toolRowIdByCallId
  2. 去重范围收窄到单 turn;
  3. toolCallId + assistantMessageId 复合键去重。

Workaround:切换到 UUID 型 ID 的渠道/模型;或 fork 会话(fork 重写全部 callID,这也是 fork 恢复的原因)。

已排除:数据库 tool part 完整;sequence/parentID/可见性正常;渲染进程无崩溃;模型本身(换 UUID 渠道即恢复)。

:截图对位、消息 ID、日志事件与逐轮修正记录见评论区。

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.