Skip to content

Commit c520a62

Browse files
author
PAAI
committed
Filter internal compaction artifacts from chat history
1 parent 7b7d69a commit c520a62

3 files changed

Lines changed: 151 additions & 0 deletions

File tree

src/gateway/server-methods/chat.ts

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -943,6 +943,59 @@ function hasAssistantNonTextContent(message: unknown): boolean {
943943
);
944944
}
945945

946+
function extractHistoryMessageText(message: unknown): string | undefined {
947+
if (!message || typeof message !== "object") {
948+
return undefined;
949+
}
950+
const entry = message as { text?: unknown; content?: unknown };
951+
if (typeof entry.text === "string") {
952+
return entry.text;
953+
}
954+
if (typeof entry.content === "string") {
955+
return entry.content;
956+
}
957+
if (!Array.isArray(entry.content) || entry.content.length === 0) {
958+
return undefined;
959+
}
960+
961+
const texts: string[] = [];
962+
for (const block of entry.content) {
963+
if (!block || typeof block !== "object") {
964+
continue;
965+
}
966+
const typed = block as { type?: unknown; text?: unknown };
967+
if (typed.type === "text" && typeof typed.text === "string") {
968+
texts.push(typed.text);
969+
}
970+
}
971+
return texts.length > 0 ? texts.join("\n") : undefined;
972+
}
973+
974+
function isCompactionHistoryMarker(message: unknown): boolean {
975+
if (!message || typeof message !== "object") {
976+
return false;
977+
}
978+
const openclaw = (message as { __openclaw?: unknown }).__openclaw;
979+
if (!openclaw || typeof openclaw !== "object" || Array.isArray(openclaw)) {
980+
return false;
981+
}
982+
return (openclaw as { kind?: unknown }).kind === "compaction";
983+
}
984+
985+
function shouldDropInternalHistoryMessage(message: unknown): boolean {
986+
if (isCompactionHistoryMarker(message)) {
987+
return true;
988+
}
989+
const text = extractHistoryMessageText(message)?.trim();
990+
if (!text) {
991+
return false;
992+
}
993+
return (
994+
text.startsWith("Pre-compaction memory flush.") ||
995+
text.startsWith("[Post-compaction context refresh]")
996+
);
997+
}
998+
946999
function shouldDropAssistantHistoryMessage(message: unknown): boolean {
9471000
if (!message || typeof message !== "object") {
9481001
return false;
@@ -971,12 +1024,20 @@ export function sanitizeChatHistoryMessages(
9711024
let changed = false;
9721025
const next: unknown[] = [];
9731026
for (const message of messages) {
1027+
if (shouldDropInternalHistoryMessage(message)) {
1028+
changed = true;
1029+
continue;
1030+
}
9741031
if (shouldDropAssistantHistoryMessage(message)) {
9751032
changed = true;
9761033
continue;
9771034
}
9781035
const res = sanitizeChatHistoryMessage(message, maxChars);
9791036
changed ||= res.changed;
1037+
if (shouldDropInternalHistoryMessage(res.message)) {
1038+
changed = true;
1039+
continue;
1040+
}
9801041
if (shouldDropAssistantHistoryMessage(res.message)) {
9811042
changed = true;
9821043
continue;

src/gateway/server-methods/server-methods.test.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,50 @@ describe("sanitizeChatHistoryMessages", () => {
301301
},
302302
]);
303303
});
304+
305+
it("drops compaction and internal memory recovery artifacts from chat history", () => {
306+
const result = sanitizeChatHistoryMessages([
307+
{
308+
role: "user",
309+
content: [{ type: "text", text: "hello" }],
310+
timestamp: 1,
311+
},
312+
{
313+
role: "system",
314+
content: [{ type: "text", text: "Compaction" }],
315+
__openclaw: { kind: "compaction" },
316+
timestamp: 2,
317+
},
318+
{
319+
role: "user",
320+
content: [{ type: "text", text: "Pre-compaction memory flush.\nNO_REPLY" }],
321+
timestamp: 3,
322+
},
323+
{
324+
role: "system",
325+
content: [{ type: "text", text: "[Post-compaction context refresh]\n\nreload state" }],
326+
timestamp: 4,
327+
},
328+
{
329+
role: "assistant",
330+
content: [{ type: "text", text: "real reply" }],
331+
timestamp: 5,
332+
},
333+
]);
334+
335+
expect(result).toEqual([
336+
{
337+
role: "user",
338+
content: [{ type: "text", text: "hello" }],
339+
timestamp: 1,
340+
},
341+
{
342+
role: "assistant",
343+
content: [{ type: "text", text: "real reply" }],
344+
timestamp: 5,
345+
},
346+
]);
347+
});
304348
});
305349

306350
describe("resolveEffectiveChatHistoryMaxChars", () => {

src/gateway/server.chat.gateway-server-chat.test.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -565,6 +565,52 @@ describe("gateway server chat", () => {
565565
expect(collectHistoryTextValues(historyMessages)).toEqual(["hello", "real reply"]);
566566
});
567567

568+
test("chat.history hides compaction and memory recovery artifacts", async () => {
569+
const historyMessages = await withMainSessionStore(async (dir) => {
570+
await fs.writeFile(
571+
path.join(dir, "sess-main.jsonl"),
572+
[
573+
JSON.stringify({ message: { role: "user", content: [{ type: "text", text: "hello" }] } }),
574+
JSON.stringify({
575+
type: "compaction",
576+
id: "comp-1",
577+
timestamp: "2026-02-07T00:00:00.000Z",
578+
summary: "Compacted history",
579+
}),
580+
JSON.stringify({
581+
message: {
582+
role: "user",
583+
content: [{ type: "text", text: "Pre-compaction memory flush.\nNO_REPLY" }],
584+
},
585+
}),
586+
JSON.stringify({
587+
message: {
588+
role: "system",
589+
content: [
590+
{
591+
type: "text",
592+
text: "[Post-compaction context refresh]\n\nReload workspace state.",
593+
},
594+
],
595+
},
596+
}),
597+
JSON.stringify({
598+
message: { role: "assistant", content: [{ type: "text", text: "real reply" }] },
599+
}),
600+
].join("\n"),
601+
"utf-8",
602+
);
603+
604+
const res = await rpcReq<{ messages?: unknown[] }>(ws, "chat.history", {
605+
sessionKey: "main",
606+
});
607+
expect(res.ok).toBe(true);
608+
return res.payload?.messages ?? [];
609+
});
610+
611+
expect(collectHistoryTextValues(historyMessages)).toEqual(["hello", "real reply"]);
612+
});
613+
568614
test("chat.history hides assistant announce/reply skip-only entries", async () => {
569615
const historyMessages = await loadChatHistoryWithMessages([
570616
{

0 commit comments

Comments
 (0)