refactor(agent): unified transcript block finalization logic

- Replaced commit-based stability checks with a unified `isTranscriptBlockFinalized` tracking mechanism.
- Removed deprecated provisional rendering configuration and flags across tool and renderer interfaces.
- Standardized native scrollback boundary logic to pin at the first unfinalized block using settled row verification.
- Updated and refactored test suites to validate block finalization and settled row boundaries instead of deprecated commit stability methods.
This commit is contained in:
can1357
2026-07-04 11:22:05 +02:00
parent 6e2bba871e
commit 42fc4e6b0a
14 changed files with 193 additions and 452 deletions
@@ -1,17 +1,4 @@
/**
* Issue #3177: `sshToolRenderer.renderResult` swaps the pending icon/frame
* state for the SSH glyph + success state when `options.isPartial` flips
* false. Without the renderer's `provisionalPartialResult: true` opt-out, a
* long-running SSH command keeps the same partial header bytes for the whole
* `STABLE_PREFIX_COMMIT_FRAMES` window, the transcript's stable-prefix
* ratchet promotes them to native scrollback, and the final render strands a
* pending `⏳ SSH: [host]` header above the final `⇄ SSH: [host]` header
* (the bug the user reported). Contract: while a partial SSH result is in
* flight, the block reports commit-unstable so `deriveLiveCommitState` keeps
* its rows in the live region; once the result settles it is commit-stable
* again.
*/
import { afterEach, beforeAll, describe, expect, it, vi } from "bun:test";
import { beforeAll, describe, expect, it } from "bun:test";
import { resetSettingsForTest, Settings } from "@oh-my-pi/pi-coding-agent/config/settings";
import { ToolExecutionComponent } from "@oh-my-pi/pi-coding-agent/modes/components/tool-execution";
import { initTheme } from "@oh-my-pi/pi-coding-agent/modes/theme/theme";
@@ -23,88 +10,52 @@ function makeSshComponent() {
return new ToolExecutionComponent("ssh", { host: "sccpu", command: "uptime" }, {}, undefined, uiStub);
}
function partialResult(text: string) {
function result(text: string) {
return { content: [{ type: "text" as const, text }] };
}
describe("ssh tool block commit stability", () => {
function expectLive(component: ToolExecutionComponent): void {
expect(component.isTranscriptBlockFinalized()).toBe(false);
expect(component.getNativeScrollbackLiveRegionStart()).toBe(0);
}
function expectFinal(component: ToolExecutionComponent): void {
expect(component.isTranscriptBlockFinalized()).toBe(true);
expect(component.getNativeScrollbackLiveRegionStart()).toBeUndefined();
}
describe("ssh tool transcript finalization", () => {
beforeAll(async () => {
resetSettingsForTest();
await Settings.init({ inMemory: true });
await initTheme();
});
afterEach(() => {
vi.restoreAllMocks();
});
it("reports commit-unstable while an SSH result is partial", () => {
it("keeps partial SSH results in the native-scrollback live region", () => {
const component = makeSshComponent();
component.updateResult(partialResult("connecting…"), true);
expect(component.isTranscriptBlockFinalized()).toBe(false);
expect(component.isTranscriptBlockCommitStable()).toBe(false);
component.updateResult(result("connecting…"), true);
expectLive(component);
});
it("keeps the collapsed pending SSH preview commit-unstable until a result arrives", () => {
// Issue #3714: with `provisionalPendingPreview: true` the pending call
// preview is commit-unstable regardless of expansion, so neither the
// `⏳ SSH: [host]` header nor the framed bottom border can leak into
// native scrollback before the result render inserts `Output`.
it("keeps pending SSH previews in the live region before a result arrives", () => {
for (const expanded of [false, true]) {
const component = makeSshComponent();
component.setExpanded(expanded);
component.setArgsComplete();
expectLive(component);
}
});
it("moves the block out of the live region as soon as the SSH result settles", () => {
const component = makeSshComponent();
component.setArgsComplete();
component.updateResult(result("connecting…"), true);
expectLive(component);
expect(component.isTranscriptBlockFinalized()).toBe(false);
expect(component.isTranscriptBlockCommitStable()).toBe(false);
});
component.updateResult(result("done\n"), false);
it("keeps the expanded pending SSH preview commit-unstable until a result arrives", () => {
// Issue #3714: the previous `"collapsed"` opt-out left expanded pending
// rows commit-stable. Once the box outgrew the viewport the stale
// `⏳ SSH: [host]` header and the pending `╰──╯` footer reached native
// scrollback, then the final result re-anchored the frame and stranded
// the pending rows above (header variant) or reused the footer row in
// place as `├── Output ──┤` (footer variant). Expanded MUST also be
// commit-unstable until the result render replaces the pending shape.
const component = makeSshComponent();
component.setExpanded(true);
component.setArgsComplete();
expect(component.isTranscriptBlockFinalized()).toBe(false);
expect(component.isTranscriptBlockCommitStable()).toBe(false);
});
it("flips commit-stable as soon as the SSH result settles", () => {
const component = makeSshComponent();
component.updateResult(partialResult("connecting…"), true);
expect(component.isTranscriptBlockCommitStable()).toBe(false);
component.updateResult(partialResult("done\n"), false);
expect(component.isTranscriptBlockFinalized()).toBe(true);
expect(component.isTranscriptBlockCommitStable()).toBe(true);
});
it("does not opt other foreground tools out of partial-result stream commits", () => {
// Sanity: bash and friends still get the existing `isPartial`
// commit-stable behaviour — the SSH opt-in must be renderer-scoped.
const component = new ToolExecutionComponent("bash", { command: "ls" }, {}, undefined, uiStub);
component.updateResult(partialResult("a\nb\n"), true);
expect(component.isTranscriptBlockFinalized()).toBe(false);
expect(component.isTranscriptBlockCommitStable()).toBe(true);
});
it("does not opt other foreground tools out of expanded pending-preview commits", () => {
// Sanity: bash/eval still use `provisionalPendingPreview: "collapsed"`,
// so once expanded their pending preview is commit-stable. The SSH
// `true` opt-in MUST remain renderer-scoped — flipping the default
// here would block long top-anchored streams (e.g. a task call's
// context/assignment markdown) from reaching native scrollback.
const component = new ToolExecutionComponent("bash", { command: "ls" }, {}, undefined, uiStub);
component.setExpanded(true);
component.setArgsComplete();
expect(component.isTranscriptBlockFinalized()).toBe(false);
expect(component.isTranscriptBlockCommitStable()).toBe(true);
expectFinal(component);
});
});