From 761177c8c4a8e96ca112ef03e1805ba18bfa3ccf Mon Sep 17 00:00:00 2001 From: mastertyko <11311479+mastertyko@users.noreply.github.com> Date: Sun, 12 Apr 2026 18:14:03 +0200 Subject: [PATCH] fix(pi-coding-agent): use safe compaction role markers --- .../src/core/compaction-utils.test.ts | 50 +++++++++++++++++++ .../src/core/compaction/utils.ts | 10 ++-- 2 files changed, 55 insertions(+), 5 deletions(-) create mode 100644 packages/pi-coding-agent/src/core/compaction-utils.test.ts diff --git a/packages/pi-coding-agent/src/core/compaction-utils.test.ts b/packages/pi-coding-agent/src/core/compaction-utils.test.ts new file mode 100644 index 000000000..87fd3d3fb --- /dev/null +++ b/packages/pi-coding-agent/src/core/compaction-utils.test.ts @@ -0,0 +1,50 @@ +import assert from "node:assert/strict"; +import test from "node:test"; + +import type { Message } from "@gsd/pi-ai"; + +import { serializeConversation } from "./compaction/index.js"; + +test("serializeConversation uses narrative role markers instead of chat-style delimiters (#4054)", () => { + const messages: Message[] = [ + { role: "user", content: "Please refactor the parser." } as Message, + { + role: "assistant", + content: [ + { type: "thinking", thinking: "I should inspect the parser entry points first." }, + { type: "text", text: "I'll start with the parser entry points." }, + { type: "toolCall", id: "tool-1", name: "Read", arguments: { path: "src/parser.ts" } }, + ], + api: "anthropic-messages", + provider: "anthropic", + model: "claude-sonnet-4-6", + usage: { + input: 0, + output: 0, + cacheRead: 0, + cacheWrite: 0, + totalTokens: 0, + cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 }, + }, + stopReason: "stop", + timestamp: Date.now(), + } as Message, + { + role: "toolResult", + content: [{ type: "text", text: "parser contents" }], + toolName: "Read", + toolCallId: "tool-1", + } as Message, + ]; + + const serialized = serializeConversation(messages); + + assert.match(serialized, /\*\*User said:\*\* Please refactor the parser\./); + assert.match(serialized, /\*\*Assistant thinking:\*\* I should inspect the parser entry points first\./); + assert.match(serialized, /\*\*Assistant responded:\*\* I'll start with the parser entry points\./); + assert.match(serialized, /\*\*Assistant tool calls:\*\* Read\(path="src\/parser\.ts"\)/); + assert.match(serialized, /\*\*Tool result:\*\* parser contents/); + assert.ok(!serialized.includes("[User]:"), "chat-style [User]: markers should not remain"); + assert.ok(!serialized.includes("[Assistant]:"), "chat-style [Assistant]: markers should not remain"); + assert.ok(!serialized.includes("[Tool result]:"), "chat-style [Tool result]: markers should not remain"); +}); diff --git a/packages/pi-coding-agent/src/core/compaction/utils.ts b/packages/pi-coding-agent/src/core/compaction/utils.ts index 86fc21a2d..95cf42555 100644 --- a/packages/pi-coding-agent/src/core/compaction/utils.ts +++ b/packages/pi-coding-agent/src/core/compaction/utils.ts @@ -218,7 +218,7 @@ export function serializeConversation(messages: Message[]): string { .filter((c): c is { type: "text"; text: string } => c.type === "text") .map((c) => c.text) .join(""); - if (content) parts.push(`[User]: ${content}`); + if (content) parts.push(`**User said:** ${content}`); } else if (msg.role === "assistant") { const textParts: string[] = []; const thinkingParts: string[] = []; @@ -239,13 +239,13 @@ export function serializeConversation(messages: Message[]): string { } if (thinkingParts.length > 0) { - parts.push(`[Assistant thinking]: ${thinkingParts.join("\n")}`); + parts.push(`**Assistant thinking:** ${thinkingParts.join("\n")}`); } if (textParts.length > 0) { - parts.push(`[Assistant]: ${textParts.join("\n")}`); + parts.push(`**Assistant responded:** ${textParts.join("\n")}`); } if (toolCalls.length > 0) { - parts.push(`[Assistant tool calls]: ${toolCalls.join("; ")}`); + parts.push(`**Assistant tool calls:** ${toolCalls.join("; ")}`); } } else if (msg.role === "toolResult") { const content = msg.content @@ -253,7 +253,7 @@ export function serializeConversation(messages: Message[]): string { .map((c) => c.text) .join(""); if (content) { - parts.push(`[Tool result]: ${truncateForSummary(content, TOOL_RESULT_MAX_CHARS)}`); + parts.push(`**Tool result:** ${truncateForSummary(content, TOOL_RESULT_MAX_CHARS)}`); } } }