fix(tui): keep long sessions responsive and bound resumed history#1976
Conversation
🦋 Changeset detectedLatest commit: cf511ff The changes in this PR will be included in the next version bump. This PR includes changesets to release 4 packages
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 |
commit: |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: cc1b646ffd
ℹ️ 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".
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 191958dd96
ℹ️ 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".
Each frame previously re-truncated, re-normalized, and re-compared every transcript line, so steady-state frames (spinner ticks, streaming flushes) cost O(total lines x chars) and pegged one core in long sessions. Keep the previous frame's raw lines, processed output, and per-line kitty image ids; a line whose raw string reference is unchanged reuses its processed output verbatim, and image-id consumers read the cache instead of re-scanning text.
Grouping a second Read/Agent call, removing swarm progress, finalizing an MCP status row, replay tool-call removal, and /undo each invalidated the entire transcript tree, forcing every mounted message to re-render (markdown lexing + code highlighting) on the next frame. The container's render cache already validates per child by reference, so structural child-list changes are picked up without a tree-wide invalidate; reserve it for global style changes such as theme switches.
Step merging only collapsed thinking/tool steps, so assistant text blocks accumulated without bound inside a turn (hundreds of markdown components in a single long turn, all re-processed every frame). A running turn now keeps its last 20 assistant messages (KIMI_CODE_TUI_KEEP_RECENT_ASSISTANT), and a finished turn folds down to its conclusion tail of 2 (KIMI_CODE_TUI_KEEP_RECENT_ASSISTANT_COMPLETED); older ones collapse into the step summary line with a message count. Entries are kept, so expand behavior is unchanged.
Resume used to return every agent's full replay over the RPC boundary (a long session can reach ~100MB, serialized and parsed several times on an in-process call), while the TUI only renders the last 10 user turns. The resume payload now accepts an optional replayTurnLimit, the turn-boundary predicate moves into agent-core as the single source of truth (limitAgentReplayByTurns, re-exported through the SDK), and the CLI passes its existing 10-turn limit so resume transfers just the tail.
Replay turn boundaries only matched real user input, so a 100-round goal (a handful of user prompts plus 100 system-trigger continuations) fell entirely inside the 10-turn replay window and resumed by rendering the whole run from the start. The goal driver already fires one synthetic prompt per goal turn and counts those as turns itself, so replay trimming now treats goal_continuation prompts as turn boundaries and keeps the most recent 10 rounds. The continuation prompt is model-facing and hidden live; replay no longer renders it as a user bubble either, while still advancing the replay turn so each round groups separately.
Suppressing goal continuation bubbles removed every turn-boundary component from goal-session replays, so mergeAllTurnSteps found no turn edges and skipped step/assistant folding entirely — a single oversized goal round still mounted all of its tool cards and assistant messages. Mount an invisible ReplayTurnBoundaryComponent for each hidden continuation: it renders zero lines but keeps the turn edges discoverable to folding and window trimming, matching replay trimming's per-round turn semantics.
95076ae to
cf511ff
Compare
Related Issue
No linked issue — the problem is described below, based on a real session debug export.
Problem
A multi-day session (53 user turns, ~4.9k tool calls, single turns with 1,100+ steps and 400+ assistant text blocks, 100MB of wire history) made the CLI progressively laggy with the node process pinned at 100% CPU, and resume took seconds while flooding the screen:
What changed
Five focused commits (details in each message):
fix(pi-tui)renderer fast path — keep the previous frame's raw lines, processed output, and per-line kitty image ids; a line whose raw string reference is unchanged reuses its processed output verbatim. Steady-state frame over a 30k-line transcript: 8.6ms → 0.56ms (~15x), with a vitest bench guarding the fast path. Behavior is byte-identical: reuse only hits on reference equality at unchanged width; a miss degrades to the old path.fix(tui)invalidate storms — remove tree-widetranscriptContainer.invalidate()from six hot paths (Read/Agent grouping, swarm progress removal, MCP status finalize, replay tool-call removal, /undo). The container's per-child reference-checked cache already handles structural changes; tree invalidation stays for theme switches.fix(tui)assistant folding — a running turn keeps its last 20 assistant messages; a finished turn folds to its conclusion tail of 2; older ones collapse into the step summary line (… thinking N times, call M tools, K messages). Both knobs are env-tunable and default-off-able; entries are preserved.fix(session)bounded resume —resumeSessionaccepts an optionalreplayTurnLimit; the turn-boundary predicate moves into agent-core as the single source of truth (limitAgentReplayByTurns, re-exported through the SDK); the CLI passes its existing 10-turn limit so resume transfers only the tail instead of the full history.fix(session)goal rounds as turns —goal_continuationprompts count as replay turn boundaries (matching the goal system's own turn counting), so a 100-round goal resumes with its most recent 10 rounds; the synthetic continuation prompt no longer renders as a user bubble on replay (it is never shown live either).Verification:
Checklist
gen-changesetsskill, or this PR needs no changeset.gen-docsskill, or this PR needs no doc update.