From 650423790034fd652bcbb5b4a89287acb41bb57d Mon Sep 17 00:00:00 2001 From: Dirk <0668000837@xydigit.com> Date: Fri, 19 Jun 2026 11:59:09 +0800 Subject: [PATCH] fix(note): prevent clack from re-breaking copy-sensitive tokens (#94746) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 b17a4ff5711c1d7607ffbc06b7f4f13ff3373afe. - Required merge gates passed before the squash merge. Prepared head SHA: b17a4ff5711c1d7607ffbc06b7f4f13ff3373afe Review: https://github.com/openclaw/openclaw/pull/94746#issuecomment-4747714518 Co-authored-by: Dirk <0668000837@xydigit.com> --- packages/terminal-core/src/note.ts | 12 ++++++++-- packages/terminal-core/src/table.test.ts | 30 +++++++++++++++++++++++- 2 files changed, 39 insertions(+), 3 deletions(-) diff --git a/packages/terminal-core/src/note.ts b/packages/terminal-core/src/note.ts index 9e679456364..c5e7b2f635b 100644 --- a/packages/terminal-core/src/note.ts +++ b/packages/terminal-core/src/note.ts @@ -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, }); } diff --git a/packages/terminal-core/src/table.test.ts b/packages/terminal-core/src/table.test.ts index cab80bfff02..7dcd4e8a733 100644 --- a/packages/terminal-core/src/table.test.ts +++ b/packages/terminal-core/src/table.test.ts @@ -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("");