diff --git a/src/commands/tasks.test.ts b/src/commands/tasks.test.ts index bcb84a2654b9..cbc5f74430d7 100644 --- a/src/commands/tasks.test.ts +++ b/src/commands/tasks.test.ts @@ -24,6 +24,7 @@ import type { OpenClawTestState } from "../test-utils/openclaw-test-state.js"; import { tasksAuditCommand, tasksCancelCommand, + tasksListCommand, tasksMaintenanceCommand, tasksShowCommand, } from "./tasks.js"; @@ -601,6 +602,30 @@ describe("tasks commands", () => { }); }); + it("keeps task list summaries within their UTF-16 column limit", async () => { + await withTaskCommandStateDir(async () => { + createTaskRecord({ + runtime: "cli", + ownerKey: "agent:main:main", + scopeKind: "session", + runId: "task-utf16-summary", + status: "succeeded", + task: "Inspect task summary", + terminalSummary: `${"y".repeat(78)}🚀xx`, + }); + const runtime = createRuntime(); + + await tasksListCommand({}, runtime); + + const output = vi + .mocked(runtime.log) + .mock.calls.map(([line]) => String(line)) + .join("\n"); + expect(output).toContain(`${"y".repeat(78)}…`); + expect(output).not.toContain("🚀"); + }); + }); + it("explains retained lost task cleanup timing in maintenance text output", async () => { await withTaskCommandStateDir(async () => { const cleanupAfter = Date.now() + 60_000; diff --git a/src/commands/tasks.ts b/src/commands/tasks.ts index 79fbb94d3d51..76e45d65753f 100644 --- a/src/commands/tasks.ts +++ b/src/commands/tasks.ts @@ -3,6 +3,7 @@ import { timestampMsToIsoString } from "@openclaw/normalization-core/number-coercion"; import { normalizeOptionalString } from "@openclaw/normalization-core/string-coerce"; +import { truncateUtf16Safe } from "@openclaw/normalization-core/utf16-slice"; import { isRich, theme } from "../../packages/terminal-core/src/theme.js"; import { formatCliCommand } from "../cli/command-format.js"; import { formatLookupMiss } from "../cli/error-format.js"; @@ -205,10 +206,7 @@ function truncate(value: string, maxChars: number) { if (value.length <= maxChars) { return value; } - if (maxChars <= 1) { - return value.slice(0, maxChars); - } - return `${value.slice(0, maxChars - 1)}…`; + return maxChars <= 0 ? "" : `${truncateUtf16Safe(value, maxChars - 1)}…`; } function shortToken(value: string | undefined, maxChars = ID_PAD): string {