Fix Codex MCP duplicate user/assistant message emissions
Root cause: user_message was being emitted from TWO places: 1. agent-manager.recordUserMessage() - called by session.ts before stream() 2. codex-mcp-agent.ts stream() method - was emitting its own user_message Fix: Remove user_message emission from codex-mcp-agent.ts stream() since the agent-manager already handles this. Added explanatory comment. Updated test to expect 0 user_messages from provider (agent-manager handles this through the full stack). Verified via Playwright E2E: created new Codex agent, sent "test fix", confirmed only ONE user message appears in UI. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.5
parent
27b0a2da63
commit
b6f0cc5c9c
@@ -402,7 +402,7 @@ function getConversationIdFromMetadata(metadata: unknown): string | undefined {
|
|||||||
|
|
||||||
describe("CodexMcpAgentClient (MCP integration)", () => {
|
describe("CodexMcpAgentClient (MCP integration)", () => {
|
||||||
test(
|
test(
|
||||||
"emits exactly one user_message and one assistant_message per turn",
|
"provider does not emit user_message (agent-manager handles that), emits exactly one assistant_message",
|
||||||
async () => {
|
async () => {
|
||||||
const cwd = tmpCwd();
|
const cwd = tmpCwd();
|
||||||
const restoreSessionDir = useTempCodexSessionDir();
|
const restoreSessionDir = useTempCodexSessionDir();
|
||||||
@@ -422,7 +422,8 @@ describe("CodexMcpAgentClient (MCP integration)", () => {
|
|||||||
try {
|
try {
|
||||||
session = await client.createSession(config);
|
session = await client.createSession(config);
|
||||||
|
|
||||||
// Simple prompt that should result in exactly one user message and one assistant message
|
// Simple prompt that should result in exactly one assistant message
|
||||||
|
// NOTE: user_message is NOT emitted by the provider - that's the agent-manager's job
|
||||||
const prompt = "Say hello";
|
const prompt = "Say hello";
|
||||||
|
|
||||||
for await (const event of session.stream(prompt)) {
|
for await (const event of session.stream(prompt)) {
|
||||||
@@ -440,10 +441,9 @@ describe("CodexMcpAgentClient (MCP integration)", () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// CRITICAL: There should be exactly ONE user_message event
|
// Provider should NOT emit user_message - that's handled by agent-manager.recordUserMessage()
|
||||||
expect(userMessages.length).toBe(1);
|
// This prevents duplicate user messages when running through the full stack.
|
||||||
expect(userMessages[0].type).toBe("user_message");
|
expect(userMessages.length).toBe(0);
|
||||||
expect(userMessages[0].text).toBe(prompt);
|
|
||||||
|
|
||||||
// CRITICAL: There should be exactly ONE assistant_message event (not duplicated)
|
// CRITICAL: There should be exactly ONE assistant_message event (not duplicated)
|
||||||
expect(assistantMessages.length).toBe(1);
|
expect(assistantMessages.length).toBe(1);
|
||||||
|
|||||||
@@ -2596,11 +2596,9 @@ class CodexMcpAgentSession implements AgentSession {
|
|||||||
this.currentAbortController = abortController;
|
this.currentAbortController = abortController;
|
||||||
|
|
||||||
const promptText = toPromptText(prompt);
|
const promptText = toPromptText(prompt);
|
||||||
this.emitEvent({
|
// NOTE: user_message is NOT emitted here because the agent-manager's
|
||||||
type: "timeline",
|
// recordUserMessage() already handles emitting the user message timeline
|
||||||
provider: CODEX_PROVIDER,
|
// event before calling stream(). Emitting here would cause duplicates.
|
||||||
item: { type: "user_message", text: promptText },
|
|
||||||
});
|
|
||||||
|
|
||||||
void this.forwardPrompt(promptText, options, abortController.signal).catch((error) => {
|
void this.forwardPrompt(promptText, options, abortController.signal).catch((error) => {
|
||||||
const message = error instanceof Error ? error.message : String(error);
|
const message = error instanceof Error ? error.message : String(error);
|
||||||
|
|||||||
Reference in New Issue
Block a user