fix(tui): trim shell-only transcript turns and cap stored output - #2621
fix(tui): trim shell-only transcript turns and cap stored output#2621he-yufeng wants to merge 1 commit into
Conversation
A !-heavy stretch grouped every echo and output into one tail turn that turnsToTrim can never touch, and each finished command kept its full stdout/stderr in both the component and the transcript entry. Split the undefined run at each shell echo so old commands trim with the normal window, and cap each stored stream at the last 64 KB with a truncation marker.
🦋 Changeset detectedLatest commit: 43446ed 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 |
commit: |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 43446ed556
ℹ️ About Codex in GitHub
Your team has set up Codex to 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 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if (entry.kind === 'user' && pendingUndefined.length > 0) { | ||
| turns.push({ turnId: undefined, entries: pendingUndefined }); | ||
| pendingUndefined = []; |
There was a problem hiding this comment.
Avoid splitting boundaryless entries into trimmable turns
When an undefined-turn, non-boundary entry is pending (for example the goal-completion assistant card appended without a turnId), this flush runs as soon as the next real user prompt is appended and creates a trimmable turn with no user/skill/plugin boundary. trimTranscriptWindow removes mounted components by counting those boundaries, so if such a boundaryless turn is selected for trimming (e.g. with aggressive KIMI_CODE_TUI_HYSTERESIS=0, or a run of several such entries under the default hysteresis), the entry is dropped from transcriptEntries but its component remains mounted, leaving stale UI and unreclaimed memory. Restrict the split to actual shell-command runs or teach the trim path to remove boundaryless turns too.
Useful? React with 👍 / 👎.
Related Issue
Resolve #2556
The linked issue covers two leaks. The folded-entry half is already in flight as #2603; this PR takes the other half, the
!shell path.Problem
A session that leans on
!grows memory without bound, for two reasons that compound:turnId: undefined, sogroupTurnsfolds the whole stretch into one tail turn, andturnsToTrimnever touches the last turn. Entry count climbs forever.finishShellOutputstores the complete stdout/stderr twice: once insideShellRunComponent(finalStdout/finalStderr) and once formatted into the transcript entry. The 256 KB running cap only applies mid-stream, so a single! cat bigfilepins its full size for the rest of the session.What changed
groupTurnsnow flushes the buffered undefined run when the next user entry arrives. A real prompt still lands in the turn that follows it (that case has no buffered run yet), while each!echo starts a fresh group, so old commands trim away under the normal window together with their components. The existing removal path already handles this shape: the echo is a boundary component, so entries and components stay in lockstep.finishShellOutputcaps each stored stream at the last 64 KB with a one-line… (N earlier chars truncated)marker, matching the scale of the running buffer's keep. The tail is what matters after a command spews.Relationship to #2385: that PR bounds the rendered frame and deliberately keeps full output in the entry for export; this one bounds retention, which is the part that actually leaks. They compose, and this diff does not touch the render path it changes.
Checklist
gen-changesetsskill, or this PR needs no changeset.gen-docsskill, or this PR needs no doc update.Verification
!stretch splits into one turn per command and old commands trim, prompt flow unchanged, cap keeps the tail with an accurate dropped count and never starts it on a lone low surrogate.test/tuisuite: 1786 passed.tsc --noEmitclean for the touched files, oxlint clean.