fix(dry-balance): fixed bench progress staircasing in raw-mode tty

- Anchored each redraw at column 0 and terminated rows with CRLF instead of bare LF.
- Capped each line to terminal width so wrapping cannot desync the cursor-up.
- Threaded stdout/stderr columns into the progress sink.
This commit is contained in:
can1357
2026-06-07 02:36:10 +02:00
parent e401f7d407
commit 38ffd47e35
2 changed files with 19 additions and 4 deletions
+1
View File
@@ -9,6 +9,7 @@
- Fixed duplicate file entries in grouped outputs for `find`, `search`, `ast_grep`, `ast_edit`, and `lsp` diagnostics when the same path appeared multiple times
- Fixed search, grep, and edit output rendering so repeated directory group blank-line boundaries no longer break nested path/link reconstruction
- Fixed `omp dry-balance --bench` flooding the terminal with staircased, duplicated spinner/status lines (and an indented summary) when the tty has ONLCR/OPOST disabled (raw mode). The interactive progress region separated rows with a bare LF and repositioned with a column-preserving `\x1b[<n>A` cursor-up, both of which only land at column 0 when the terminal translates LF→CRLF; with that translation off, every 80 ms redraw cascaded down and to the right into scrollback. The live region now carriage-returns before every cleared row, terminates each row with CRLF, and caps each row to the terminal width so a wrapped line cannot desync the cursor-up from the logical line count.
## [15.10.0] - 2026-06-06
@@ -152,6 +152,8 @@ export interface DryBalanceDependencies {
now?: () => number;
stdoutIsTTY?: boolean;
stderrIsTTY?: boolean;
stdoutColumns?: number;
stderrColumns?: number;
}
type DryBalanceAttemptResult =
@@ -310,10 +312,11 @@ function renderBenchStatusLine(
}
}
function createBenchProgressSink(
export function createBenchProgressSink(
total: number,
write: (text: string) => void,
interactive: boolean,
columns: number,
): DryBalanceBenchProgressSink {
const statuses: DryBalanceBenchProgressStatus[] = Array.from({ length: total }, () => ({ state: "waiting" }));
if (!interactive) {
@@ -333,13 +336,21 @@ function createBenchProgressSink(
let frame = 0;
let lineCount = 0;
let timer: NodeJS.Timeout | undefined;
const width = Number.isFinite(columns) && columns > 0 ? Math.trunc(columns) : 80;
const render = (): void => {
const lines = [
chalk.bold("bench requests"),
...statuses.map((status, index) => renderBenchStatusLine(status, index, total, frame)),
];
if (lineCount > 0) write(`\x1b[${lineCount}A`);
write(`${lines.map(line => `\x1b[2K${line}`).join("\n")}\n`);
// Anchor every redraw at column 0 and terminate each row with CRLF: a
// bare `\n` only returns to column 0 when the tty performs ONLCR
// translation, which is off whenever the terminal is in raw mode — there
// the old column-preserving cursor-up staircased each frame into
// scrollback. Cap each line to the terminal width so a wrapped row never
// desyncs the `\x1b[<n>A` cursor-up from the logical line count.
const move = lineCount > 0 ? `\x1b[${lineCount}A` : "";
const body = lines.map(line => `\x1b[2K${truncateToWidth(line, width)}`).join("\r\n");
write(`${move}\r${body}\r\n`);
lineCount = lines.length;
};
render();
@@ -792,7 +803,10 @@ export async function runDryBalanceCommand(
const progressInteractive = command.flags.json
? (deps.stderrIsTTY ?? process.stderr.isTTY === true)
: (deps.stdoutIsTTY ?? process.stdout.isTTY === true);
progress = createBenchProgressSink(targets.length, progressWrite, progressInteractive);
const progressColumns = command.flags.json
? (deps.stderrColumns ?? process.stderr.columns ?? 80)
: (deps.stdoutColumns ?? process.stdout.columns ?? 80);
progress = createBenchProgressSink(targets.length, progressWrite, progressInteractive, progressColumns);
benchResults = await runBenchTargets(model, targets, randomSessionId, progress, streamFn, now);
results = targets.map(target =>
target.ok ? { ok: true, account: target.account } : { ok: false, reason: target.error },