diff --git a/packages/server/src/server/agent/agent-manager.ts b/packages/server/src/server/agent/agent-manager.ts index 28c97d2e7..480f43013 100644 --- a/packages/server/src/server/agent/agent-manager.ts +++ b/packages/server/src/server/agent/agent-manager.ts @@ -341,6 +341,20 @@ function normalizeMessageId(messageId: string | undefined): string | undefined { return trimmed.length > 0 ? trimmed : undefined; } +function isDuplicateLegacyUserMessage( + item: AgentTimelineItem, + canonicalUserMessagesById: Map, +): boolean { + if (item.type !== "user_message") { + return false; + } + const eventMessageId = normalizeMessageId(item.messageId); + if (!eventMessageId) { + return false; + } + return canonicalUserMessagesById.get(eventMessageId) === item.text; +} + function buildExplicitTimelineSeedForRegister( now: Date, options: @@ -2393,17 +2407,9 @@ export class AgentManager { if (event.type !== "timeline") { continue; } - - if (event.item.type === "user_message") { - const eventMessageId = normalizeMessageId(event.item.messageId); - if (eventMessageId) { - const canonicalText = canonicalUserMessagesById.get(eventMessageId); - if (canonicalText === event.item.text) { - continue; - } - } + if (isDuplicateLegacyUserMessage(event.item, canonicalUserMessagesById)) { + continue; } - this.recordTimeline(agent.id, event.item); } } catch { diff --git a/packages/server/src/server/agent/providers/claude-agent.ts b/packages/server/src/server/agent/providers/claude-agent.ts index 1e293fb71..9c8fb7509 100644 --- a/packages/server/src/server/agent/providers/claude-agent.ts +++ b/packages/server/src/server/agent/providers/claude-agent.ts @@ -2602,16 +2602,28 @@ class ClaudeAgentSession implements AgentSession { "Claude query pump: raw SDK message", ); }; + const handlePumpedMessage = async (message: SDKMessage): Promise => { + logRawMessage(message); + consecutiveInterruptAbortRecoveries = 0; + if (await this.handleMissingResumedConversation(message, activeQuery)) { + return true; + } + this.routeSdkMessageFromPump(message); + return false; + }; + const drainActiveQuery = async (): Promise => { + for await (const message of activeQuery) { + if (await handlePumpedMessage(message)) { + return true; + } + } + return false; + }; try { while (!this.closed && this.query === activeQuery) { try { - for await (const message of activeQuery) { - logRawMessage(message); - consecutiveInterruptAbortRecoveries = 0; - if (await this.handleMissingResumedConversation(message, activeQuery)) { - return; - } - this.routeSdkMessageFromPump(message); + if (await drainActiveQuery()) { + return; } if (!this.closed && this.query === activeQuery) { this.failActiveTurns("Claude stream ended before terminal result"); diff --git a/packages/server/src/server/agent/providers/codex-app-server-agent.ts b/packages/server/src/server/agent/providers/codex-app-server-agent.ts index 069fc466a..fa9c85c69 100644 --- a/packages/server/src/server/agent/providers/codex-app-server-agent.ts +++ b/packages/server/src/server/agent/providers/codex-app-server-agent.ts @@ -2810,17 +2810,7 @@ class CodexAppServerAgentSession implements AgentSession { if (thread && Array.isArray(thread.turns)) { for (const turn of thread.turns) { const items = Array.isArray(turn.items) ? turn.items : []; - for (const item of items) { - const timelineItem = threadItemToTimeline(item, { - cwd: this.config.cwd ?? null, - }); - if (timelineItem) { - if (timelineItem.type === "tool_call") { - this.warnOnIncompleteEditToolCall(timelineItem, "thread_read", item); - } - threadTimeline.push(timelineItem); - } - } + this.collectThreadTurnTimelineItems(items, threadTimeline); } } @@ -4015,6 +4005,21 @@ class CodexAppServerAgentSession implements AgentSession { return true; } + private collectThreadTurnTimelineItems(items: unknown[], target: AgentTimelineItem[]): void { + for (const item of items) { + const timelineItem = threadItemToTimeline(item, { + cwd: this.config.cwd ?? null, + }); + if (!timelineItem) { + continue; + } + if (timelineItem.type === "tool_call") { + this.warnOnIncompleteEditToolCall(timelineItem, "thread_read", item); + } + target.push(timelineItem); + } + } + private warnOnIncompleteEditToolCall( item: ToolCallTimelineItem, source: string, diff --git a/packages/server/src/server/agent/providers/opencode-agent.ts b/packages/server/src/server/agent/providers/opencode-agent.ts index e346ead9b..275b8008b 100644 --- a/packages/server/src/server/agent/providers/opencode-agent.ts +++ b/packages/server/src/server/agent/providers/opencode-agent.ts @@ -359,11 +359,12 @@ function isOpenCodeHeadersTimeoutFailure(error: unknown): boolean { }; for (const value of [record.message, record.code, record.name]) { - if (typeof value === "string") { - const diagnostic = value.trim().toLowerCase(); - if (diagnostic) { - diagnostics.add(diagnostic); - } + if (typeof value !== "string") { + continue; + } + const diagnostic = value.trim().toLowerCase(); + if (diagnostic) { + diagnostics.add(diagnostic); } } @@ -2305,15 +2306,16 @@ class OpenCodeAgentSession implements AgentSession { }; continue; } - if (part.type === "tool") { - const parsedToolPart = OpencodeToolPartToTimelineItemSchema.safeParse(part); - if (parsedToolPart.success && parsedToolPart.data) { - yield { - type: "timeline", - provider: "opencode", - item: parsedToolPart.data, - }; - } + if (part.type !== "tool") { + continue; + } + const parsedToolPart = OpencodeToolPartToTimelineItemSchema.safeParse(part); + if (parsedToolPart.success && parsedToolPart.data) { + yield { + type: "timeline", + provider: "opencode", + item: parsedToolPart.data, + }; } }