fix(tui): preserve shared prompts when scrollback is full (#115165)

This commit is contained in:
Peter Steinberger
2026-07-28 08:05:16 -04:00
committed by GitHub
parent 89b6d85cdd
commit 0f188c8cc6
2 changed files with 40 additions and 5 deletions
+32
View File
@@ -387,6 +387,38 @@ describe("ChatLog", () => {
expect(normalizeTestText(chatLog.render(120).join("\n"))).toContain("Still streaming.");
});
it("preserves a delayed shared prompt and its active reply when scrollback is full", () => {
const chatLog = new ChatLog(20);
chatLog.updateAssistant("Already streaming.", "shared-run");
for (let index = 0; index < 19; index += 1) {
chatLog.addSystem(`Older notice ${index}.`);
}
chatLog.addLiveUser("Sent from the other client.", {
messageId: "shared-overflow-user",
runId: "shared-run",
});
chatLog.addLiveUser("Sent from the other client.", {
messageId: "shared-overflow-user",
runId: "shared-run",
});
const rendered = normalizeTestText(chatLog.render(120).join("\n"));
expect(chatLog.children).toHaveLength(20);
expect(rendered).toContain("Sent from the other client.");
expect(rendered).toContain("Already streaming.");
expect(rendered).not.toContain("Older notice 0.");
expect(rendered.match(/Sent from the other client\./g)).toHaveLength(1);
expect(rendered.indexOf("Sent from the other client.")).toBeLessThan(
rendered.indexOf("Already streaming."),
);
chatLog.updateAssistant("Still streaming after overflow.", "shared-run");
expect(normalizeTestText(chatLog.render(120).join("\n"))).toContain(
"Still streaming after overflow.",
);
});
it("deduplicates authoritative user events and adopts the matching pending prompt", () => {
const chatLog = new ChatLog(40);
chatLog.addPendingUser("shared-run", "Persisted prompt.");
+8 -5
View File
@@ -87,9 +87,11 @@ export class ChatLog extends Container {
}
}
private pruneOverflow() {
private pruneOverflow(protectedComponents?: ReadonlySet<Component>) {
while (this.children.length > this.maxComponents) {
const oldest = this.children[0];
const oldest = protectedComponents
? this.children.find((component) => !protectedComponents.has(component))
: this.children[0];
if (!oldest) {
return;
}
@@ -231,12 +233,13 @@ export class ChatLog extends Container {
frozen?.values().next().value ??
(options.runId ? this.streamingRuns.get(options.runId) : undefined);
const assistantIndex = assistant ? this.children.indexOf(assistant) : -1;
if (assistantIndex >= 0) {
if (assistant && assistantIndex >= 0) {
// Transcript broadcasts can trail the first delta; insert their prompt
// before the existing reply without resetting live stream or tool state.
// before the existing reply. Preserve both when full scrollback evicts
// older components so the newly recovered prompt cannot disappear.
this.repeatableSystemMessage = null;
this.children.splice(assistantIndex, 0, component);
this.pruneOverflow();
this.pruneOverflow(new Set([component, assistant]));
return component;
}
this.appendNonSystem(component);