diff --git a/packages/server/src/server/agent/providers/claude-agent.test.ts b/packages/server/src/server/agent/providers/claude-agent.test.ts index 8da63128c..35c608c5f 100644 --- a/packages/server/src/server/agent/providers/claude-agent.test.ts +++ b/packages/server/src/server/agent/providers/claude-agent.test.ts @@ -159,6 +159,79 @@ describe("convertClaudeHistoryEntry", () => { expect(convertClaudeHistoryEntry(assistantNoiseEntry, mapBlocks)).toEqual([]); }); + test("skips messages (model switch, /context, etc.)", () => { + // Real entries from Claude Code JSONL history files + const modelSwitch = { + type: "user", + message: { + role: "user", + content: + "Set model to claude-opus-4-6", + }, + userType: "external", + }; + + const modelSwitchWithAnsi = { + type: "user", + message: { + role: "user", + content: + 'Set model to \u001b[1mopus (claude-opus-4-6)\u001b[22m', + }, + }; + + const contextDump = { + type: "user", + message: { + role: "user", + content: + "## Context Usage\n\n**Model:** claude-opus-4-6\n**Tokens:** 19k", + }, + }; + + const planMode = { + type: "user", + message: { + role: "user", + content: "Enabled plan mode", + }, + }; + + const goodbye = { + type: "user", + message: { + role: "user", + content: "Bye!", + }, + }; + + const empty = { + type: "user", + message: { + role: "user", + content: "", + }, + }; + + const mapBlocks = vi.fn().mockReturnValue([]); + + expect(convertClaudeHistoryEntry(modelSwitch, mapBlocks)).toEqual([]); + expect(convertClaudeHistoryEntry(modelSwitchWithAnsi, mapBlocks)).toEqual([]); + expect(convertClaudeHistoryEntry(contextDump, mapBlocks)).toEqual([]); + expect(convertClaudeHistoryEntry(planMode, mapBlocks)).toEqual([]); + expect(convertClaudeHistoryEntry(goodbye, mapBlocks)).toEqual([]); + expect(convertClaudeHistoryEntry(empty, mapBlocks)).toEqual([]); + + // Real user messages must NOT be filtered + const realMessage = { + type: "user", + message: { role: "user", content: "fix the bug in auth.ts" }, + }; + expect(convertClaudeHistoryEntry(realMessage, mapBlocks)).toEqual([ + { type: "user_message", text: "fix the bug in auth.ts" }, + ]); + }); + test("maps task notifications to synthetic tool calls", () => { const entry = { type: "system", diff --git a/packages/server/src/server/agent/providers/claude-agent.ts b/packages/server/src/server/agent/providers/claude-agent.ts index b8c742799..daabaa3cf 100644 --- a/packages/server/src/server/agent/providers/claude-agent.ts +++ b/packages/server/src/server/agent/providers/claude-agent.ts @@ -418,8 +418,19 @@ function isClaudeNoResponsePlaceholderText(value: unknown): boolean { return normalizeClaudeTranscriptText(value) === NO_RESPONSE_REQUESTED_PLACEHOLDER; } +const LOCAL_COMMAND_STDOUT_PATTERN = /^\s*[\s\S]*<\/local-command-stdout>\s*$/; + +function isClaudeLocalCommandStdout(value: unknown): boolean { + const normalized = normalizeClaudeTranscriptText(value); + return normalized !== null && LOCAL_COMMAND_STDOUT_PATTERN.test(normalized); +} + function isClaudeTranscriptNoiseText(value: unknown): boolean { - return isClaudeInterruptPlaceholderText(value) || isClaudeNoResponsePlaceholderText(value); + return ( + isClaudeInterruptPlaceholderText(value) || + isClaudeNoResponsePlaceholderText(value) || + isClaudeLocalCommandStdout(value) + ); } function collectClaudeTextContentParts(content: unknown): string[] {