fix(note): prevent clack from re-breaking copy-sensitive tokens (#94746)

Summary:
- The PR widens the virtual Clack output columns for wrapped terminal notes and adds a rendered-output regression test for copy-sensitive session-lock paths.
- PR surface: Source +8, Tests +28. Total +36 across 2 files.
- Reproducibility: yes. Current source routes session lock paths through `note()`, and the pinned Clack note renderer hard-wraps final content from `getColumns(output) - 6` after OpenClaw's first wrapping pass.

Automerge notes:
- PR branch already contained follow-up commit before automerge: test(note): add rendered-output regression test for copy-sensitive to…

Validation:
- ClawSweeper review passed for head b17a4ff571.
- Required merge gates passed before the squash merge.

Prepared head SHA: b17a4ff571
Review: https://github.com/openclaw/openclaw/pull/94746#issuecomment-4747714518

Co-authored-by: Dirk <0668000837@xydigit.com>
This commit is contained in:
Dirk
2026-06-19 11:59:09 +08:00
committed by GitHub
parent fb69db6365
commit 6504237900
2 changed files with 39 additions and 3 deletions

View File

@@ -186,6 +186,13 @@ export function resolveNoteColumns(columns: number | undefined): number {
return columns;
}
export function resolveNoteOutputColumns(message: string, columns: number): number {
const widestLine = message
.split("\n")
.reduce((max, line) => Math.max(max, visibleWidth(line)), 0);
return Math.max(columns, widestLine + 6);
}
function createNoteOutput(columns: number): NodeJS.WriteStream {
if (process.stdout.columns === columns) {
return process.stdout;
@@ -207,8 +214,9 @@ export function note(message: unknown, title?: string) {
return;
}
const columns = resolveNoteColumns(process.stdout.columns);
clackNote(wrapNoteMessage(message, { columns }), stylePromptTitle(title), {
output: createNoteOutput(columns),
const wrappedMessage = wrapNoteMessage(message, { columns });
clackNote(wrappedMessage, stylePromptTitle(title), {
output: createNoteOutput(resolveNoteOutputColumns(wrappedMessage, columns)),
format: (line) => line,
});
}

View File

@@ -1,7 +1,8 @@
import { note as clackNote } from "@clack/prompts";
// Terminal Core tests cover table behavior.
import { afterEach, describe, expect, it, vi } from "vitest";
import { visibleWidth } from "./ansi.js";
import { resolveNoteColumns, wrapNoteMessage } from "./note.js";
import { resolveNoteColumns, resolveNoteOutputColumns, wrapNoteMessage } from "./note.js";
import { renderTable } from "./table.js";
function mockProcessPlatform(platform: NodeJS.Platform): void {
@@ -348,6 +349,33 @@ describe("wrapNoteMessage", () => {
expect(resolveNoteColumns(120)).toBe(120);
});
it("widens note output columns so clack does not re-wrap copy-sensitive lines", () => {
const wrapped = wrapNoteMessage(
[
"- Found 1 session lock file.",
"- ~/.openclaw/agents/main/sessions/9c2acae5-841f-4aea-936b-fdb513b60202.jsonl.lock pid=86519 (alive) age=2m47s stale=no",
].join("\n"),
{ columns: 80 },
);
const writes: string[] = [];
const output = {
columns: resolveNoteOutputColumns(wrapped, 80),
write(chunk: string) {
writes.push(chunk);
return true;
},
} as unknown as NodeJS.WriteStream;
clackNote(wrapped, "Session locks", { output, format: (line) => line });
const rendered = writes.join("");
expect(rendered).toContain(".jsonl.lock");
expect(rendered).not.toContain(".js\n");
expect(rendered).toContain(
"- ~/.openclaw/agents/main/sessions/9c2acae5-841f-4aea-936b-fdb513b60202.jsonl.lock",
);
});
it("coerces nullish and non-string note messages before wrapping", () => {
expect(wrapNoteMessage(undefined, { maxWidth: 20, columns: 80 })).toBe("");
expect(wrapNoteMessage(null, { maxWidth: 20, columns: 80 })).toBe("");