MoonshotAI / MoonshotAI/kimi-code

Web transcript renders duplicated lines: full message plus leftover stream chunks after a turn ends

Open
#3,387 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
TypeScript
Stars
7.5k
Forks
1.2k
Avg merge
11h 53m
Merged PRs (30d)
350

Description

Symptom

In the web composer, once a turn completes, an assistant message can render as the full reply followed by fragments of the same reply repeated as extra lines (e.g. the full text, then , here's one, :, \n\nWhy do programmers, … one per stream chunk).

Repro

Any session whose model stream interleaves non-text parts between text deltas — e.g. an OpenAI-compatible provider that sends a reasoning_content key (even empty) on every chunk, which makes _convertStreamResponse yield a think part per chunk so text parts are never merged. Then:

  1. The wire gets one content.part record per text delta.
  2. The live projection keeps a single text frame per step, updated with the accumulated text.
  3. After turn.ended, the heal pass (healTurnOps in packages/kap-server/src/services/transcript/transcriptService.ts) re-reads the cold snapshot — which has one text frame per delta — and merges it into the live store. The dedup is per-frameId: cold f2 collides with the live consolidated frame and is skipped, but cold f3…fN (the remaining deltas) have no live counterpart and are appended verbatim.
  4. The web UI renders every text frame as its own paragraph → full text + duplicated fragments. The same merge runs at attach-time backfill, so reopened sessions show it too.

Observed on a real session's GET /api/v1/sessions/<id>/transcript: final step frames were f1 thinking, f2 = full accumulated text, then f3…f8 = deltas[1..6] verbatim. Intermediate steps inspected before the heal debounce only show the single live frame, which is why this appears specifically on ended turns.

Expected

One consolidated text frame per step after heal, same as intermediate steps.

Fix

PR follows: teach healTurnOps to skip a snapshot text/thinking frame when any same-kind, same-role live frame in the step already contains its text.

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 in packages/kap-server/src/services/transcript/transcriptService.ts at healTurnOps and trace how snapshot text and thinking frames are merged with live frames. Reproduce a completed turn with interleaved non-text stream parts, then verify that ended turns and attach-time backfills retain one consolidated text frame per step without duplicated web paragraphs.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
backend, web-dev
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
72/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.