mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-03 13:21:37 +00:00
* fix(transcripts): imported text can inject terminal escapes through transcripts show Imported transcript text, speaker labels, and session titles were rendered into summary.md unsanitized, and openclaw transcripts show writes that file directly to stdout, so a transcript could clear the terminal or spoof colored status text. Sanitize external strings with the terminal-core sanitizer when the summary is built, so summary.json, summary.md, and the CLI output stay free of control bytes while transcript.jsonl keeps the raw capture. * fix(transcripts): protect terminal output boundaries * fix(transcripts): keep canonical session identity separate from terminal presentation * fix(transcripts): escape C1 control characters in transcripts json output
74 lines
2.9 KiB
TypeScript
74 lines
2.9 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import { renderTranscriptsMarkdown, summarizeTranscripts } from "./summary.js";
|
|
|
|
const ESC = "\u001b";
|
|
const C1_CSI = "\u009b";
|
|
const CSI_CLEAR = `${ESC}[2J`;
|
|
const CSI_RED = `${ESC}[31m`;
|
|
const CSI_RESET = `${ESC}[0m`;
|
|
const OSC_LINK = `${ESC}]8;;https://example.com${ESC}\\click${ESC}]8;;${ESC}\\`;
|
|
|
|
describe("summarizeTranscripts", () => {
|
|
it("strips terminal control sequences from imported text, speaker labels, and titles", () => {
|
|
const summary = summarizeTranscripts({
|
|
session: {
|
|
sessionId: `${CSI_RED}transcript-2026-07-17Tansi${CSI_RESET}`,
|
|
title: `${CSI_RED}Weekly sync${CSI_RESET}`,
|
|
source: { providerId: "manual-transcript" },
|
|
startedAt: "2026-07-17T10:00:00.000Z",
|
|
},
|
|
utterances: [
|
|
{
|
|
text: `${CSI_CLEAR}${CSI_RED}ADMIN APPROVED${CSI_RESET} decision: ship it`,
|
|
speaker: { label: `${CSI_RED}Attacker${CSI_RESET}` },
|
|
},
|
|
{ text: `follow up ${OSC_LINK}` },
|
|
{ text: `${C1_CSI}31mrisk of red text${C1_CSI}0m` },
|
|
],
|
|
});
|
|
const markdown = renderTranscriptsMarkdown(summary);
|
|
|
|
expect(summary.title).toBe("Weekly sync");
|
|
expect(summary.sessionId).toBe(`${CSI_RED}transcript-2026-07-17Tansi${CSI_RESET}`);
|
|
expect(markdown).toContain("Session: transcript-2026-07-17Tansi");
|
|
expect(summary.transcript[0]).toBe("Attacker: ADMIN APPROVED decision: ship it");
|
|
expect(summary.transcript[1]).toBe("follow up click");
|
|
expect(summary.transcript[2]).toBe("risk of red text");
|
|
expect(summary.utteranceCount).toBe(3);
|
|
expect(markdown).toContain("ADMIN APPROVED decision: ship it");
|
|
expect(markdown).not.toContain(ESC);
|
|
expect(markdown).not.toContain(C1_CSI);
|
|
});
|
|
|
|
it("keeps plain transcript content unchanged", () => {
|
|
const summary = summarizeTranscripts({
|
|
session: {
|
|
sessionId: "transcript-2026-07-17Tplain",
|
|
title: "Design review",
|
|
source: { providerId: "manual-transcript" },
|
|
startedAt: "2026-07-17T10:00:00.000Z",
|
|
},
|
|
utterances: [{ text: "We decided to ship the CLI.", speaker: { label: "Sam" } }],
|
|
});
|
|
|
|
expect(summary.title).toBe("Design review");
|
|
expect(summary.transcript).toEqual(["Sam: We decided to ship the CLI."]);
|
|
expect(summary.decisions).toEqual(["Sam: We decided to ship the CLI."]);
|
|
});
|
|
|
|
it("renders live-provider line breaks and tabs visibly in single-line summary fields", () => {
|
|
const text = "first line\nsecond\tcolumn";
|
|
const summary = summarizeTranscripts({
|
|
session: {
|
|
sessionId: "live-captions",
|
|
source: { providerId: "live-caption", kind: "live-caption" },
|
|
startedAt: "2026-07-17T10:00:00.000Z",
|
|
},
|
|
utterances: [{ text, speaker: { label: "Sam\tHost" } }],
|
|
});
|
|
|
|
expect(summary.transcript).toEqual(["Sam\\tHost: first line\\nsecond\\tcolumn"]);
|
|
expect(text).toBe("first line\nsecond\tcolumn");
|
|
});
|
|
});
|