fix(web): make mid-turn delta offsets step-relative#1609
Conversation
Reset in-flight text and client stream alignment at step boundaries so resync seeds only the current step instead of duplicating prior steps.
🦋 Changeset detectedLatest commit: 51db588 The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 86be5e384e
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| s.turnTextLen = 0; | ||
| s.turnThinkLen = 0; |
There was a problem hiding this comment.
Keep offset semantics compatible with the v1 backend
When the web app is connected to the v1 backend, this unconditional reset makes every later-step delta look like a gap: packages/server/src/services/gateway/inFlightTurnTracker.ts still has no turn.step.started reset and stamps offset from the whole-turn assistantText.length / thinkingText.length, while the client now resets its local length to 0 at each step. In a v1 multi-step turn, the first step-2 delta therefore arrives with a cumulative offset greater than the local length, causing historyCompacted resyncs or stalled streaming instead of appending the delta; either v1 needs the same server-side offset change or the client reset needs to be gated to step-relative backends.
Useful? React with 👍 / 👎.
commit: |
The exact-JSON content signature missed duplicates when the two copies differed by thinking signature, tool progress, part boundaries, or the tool set (finished parallel tools leave running_tools). Reduce content to concatenated stream text plus sorted tool-call ids and treat a covered subset as the duplicate, merging the seed's tool progress into the existing cards before dropping it.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 51db588b98
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| s.turnTextLen = 0; | ||
| s.turnThinkLen = 0; |
There was a problem hiding this comment.
Keep step offsets compatible with the v1 backend
When kimi-web is connected to the v1 server, this reset makes the first delta after every turn.step.started look like a gap because packages/server/src/services/gateway/inFlightTurnTracker.ts still stamps offset from the whole-turn assistantText/thinkingText and was not updated to reset at step boundaries. I checked that tracker: it has no turn.step.started case, so on step 2 it sends an offset greater than the freshly reset local length here, causing alignDelta() to emit delta_gap and force a resync for normal multi-step turns on the supported v1 backend.
Useful? React with 👍 / 👎.
Related Issue
No linked issue — the problem is explained below.
Problem
In kimi-web, when a snapshot resync happens while a multi-step turn is still running (page refresh, WebSocket reconnect on tab focus, or a server-requested resync), the turn re-renders incorrectly: earlier steps' body text is re-appended as a trailing blob of duplicated Markdown after the structured history, and thinking blocks / tool cards stop separating properly. Long turns (30+ steps, thinking enabled) make it most visible.
Root cause on the v2 (kap-server) sync path: the server's in-flight turn tracker accumulated
assistant_text/thinking_textfor the WHOLE turn instead of the currently-streaming step. Completed steps are already present in the snapshot transcript as structured messages, so when a reconnecting client seeded a partially-streamed assistant message fromin_flight_turn, it carried a second copy of every earlier step's text.What changed
turn.step.started(running tool calls are intentionally kept — a call without a result yet is not in the transcript and must still be seeded). The pre-appendoffsetstamped on volatile delta frames becomes step-relative as a consequence.turn.step.startedto match the step-relative offsets. Without this, every delta from step 2 onward misaligns (silently skipped as a duplicate, or misread as a gap). A lost head-of-step delta now surfaces as a recoverable gap → resync instead of a permanent silent stall.signature, tool progressoutputLines, part boundaries, or the tool set (a finished parallel tool leavesrunning_tools, so the seed's id set is a strict subset). Before dropping the seed, its tool progress is merged into the existing card so a mid-tool refresh keeps the latest output (and never overwrites a settled tool result).in_flight_turn.assistant_text/thinking_textas current-step accumulation (reset atturn.step.started; earlier steps are inmessages).Checklist
gen-changesetsskill, or this PR needs no changeset.gen-docsskill, or this PR needs no doc update.