/** * Tests for the `/clean` slash command primitives. * * `/clean` is wired up so that: * 1. Runtime injection prefixes are stripped from user-message text blocks * (same allowlist `/compact` uses). * 2. Assistant turns, tool_use blocks, and tool_result blocks are preserved * verbatim — `/clean` never alters history shape. * 3. The user-facing result message reports tokens reclaimed and the * number of messages preserved. * * The slash-routing layer is covered by `conversation-slash-commands.test.ts`; * the graph-memory eviction hook is covered by the v2 routing tests. This * file focuses on the formatter and the strip behavior that defines the * "no history loss" contract. */ import { describe, expect, test } from "bun:test"; import { formatCleanResult } from "../daemon/conversation-process.js"; import { stripInjectionsForCompaction } from "../daemon/conversation-runtime-assembly.js"; import type { Message } from "../providers/types.js"; describe("formatCleanResult", () => { test("formats token reclamation and preserved-message count", () => { const out = formatCleanResult({ previousEstimatedInputTokens: 100_000, estimatedInputTokens: 95_000, maxInputTokens: 200_000, preservedMessages: 250, }); expect(out).toContain("Context Cleaned"); expect(out).toContain("100,000 → 95,000 (5,000 reclaimed)"); expect(out).toContain("95,000 / 200,000 tokens"); expect(out).toContain("250 preserved"); }); test("renders zero reclaimed when nothing was stripped", () => { const out = formatCleanResult({ previousEstimatedInputTokens: 12_345, estimatedInputTokens: 12_345, maxInputTokens: 200_000, preservedMessages: 10, }); expect(out).toContain("12,345 → 12,345 (0 reclaimed)"); expect(out).toContain("10 preserved"); }); }); describe("stripInjectionsForCompaction preserves history shape", () => { test("strips known injection prefixes from user text blocks", () => { const messages: Message[] = [ { role: "user", content: [ { type: "text", text: "\nfoo\n", }, { type: "text", text: "Hello, please help with X." }, ], }, ]; const out = stripInjectionsForCompaction(messages); expect(out).toHaveLength(1); expect(out[0].content).toHaveLength(1); expect(out[0].content[0]).toEqual({ type: "text", text: "Hello, please help with X.", }); }); test("preserves assistant turns and tool_use/tool_result blocks verbatim", () => { const messages: Message[] = [ { role: "user", content: [ { type: "text", text: "\nstale kb\n", }, { type: "text", text: "Run the calculator." }, ], }, { role: "assistant", content: [ { type: "text", text: "Using the calculator." }, { type: "tool_use", id: "tool-1", name: "calculator", input: { expr: "1 + 2" }, }, ], }, { role: "user", content: [ { type: "tool_result", tool_use_id: "tool-1", content: "3", }, ], }, { role: "assistant", content: [{ type: "text", text: "The answer is 3." }], }, ]; const out = stripInjectionsForCompaction(messages); expect(out).toHaveLength(4); expect(out[0].content).toEqual([ { type: "text", text: "Run the calculator." }, ]); expect(out[1]).toEqual(messages[1]); expect(out[2]).toEqual(messages[2]); expect(out[3]).toEqual(messages[3]); }); test("strips but leaves and alone", () => { const messages: Message[] = [ { role: "user", content: [ { type: "text", text: "\nnow\n" }, { type: "text", text: "\nfiles\n" }, { type: "text", text: "\nrecent\n" }, ], }, ]; const out = stripInjectionsForCompaction(messages); expect(out[0].content).toEqual([ { type: "text", text: "\nnow\n" }, { type: "text", text: "\nrecent\n" }, ]); }); });