diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index e66fd755a..c9a77fa6f 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -37,6 +37,7 @@ ### Fixed +- Fixed resumed or rebuilt sessions auto-applying a new checkpoint with a stale rewind report from an earlier completed checkpoint cycle ([#7739](https://github.com/can1357/oh-my-pi/issues/7739)). - Retried concurrent-request caps with a short backoff without deleting valid Copilot credentials or rotating through sibling accounts. - Fixed the default `textVerbosity` setting being forwarded to OpenAI Codex requests unless the user explicitly configures it, preserving Codex's native response-control defaults. ([#4949](https://github.com/can1357/oh-my-pi/issues/4949)) - Reduced streaming CPU usage by coalescing the cumulative `message_update` deltas of a turn at the event-controller dispatch boundary: at most one streaming-state rebuild runs per ~33ms window instead of one per token, cutting the per-token handler work that dominated the CPU profile of streaming sessions (especially at high token rates) while preserving per-delta speech output. Subscriber dispatch is serialized so a rapid stream tail (`message_update` → `message_end` → `agent_end`) cannot overtake the coalesced flush. ([#7443](https://github.com/can1357/oh-my-pi/issues/7443)) diff --git a/packages/coding-agent/src/session/agent-session.ts b/packages/coding-agent/src/session/agent-session.ts index 9a34d5da4..edbc0ddc7 100644 --- a/packages/coding-agent/src/session/agent-session.ts +++ b/packages/coding-agent/src/session/agent-session.ts @@ -6681,9 +6681,10 @@ export class AgentSession { } #extractRewindReport(messages: AgentMessage[]): string | undefined { - if (!this.#checkpointState) return undefined; + const checkpointState = this.#checkpointState; + if (!checkpointState) return undefined; if (this.#pendingRewindReport) return this.#pendingRewindReport; - for (let i = messages.length - 1; i >= 0; i--) { + for (let i = messages.length - 1; i >= checkpointState.checkpointMessageCount; i--) { const message = messages[i]; if (message?.role !== "toolResult" || message.isError) continue; const semanticResult = semanticToolResult(message.toolName, message); diff --git a/packages/coding-agent/test/agent-session-checkpoint-rewind-branch.test.ts b/packages/coding-agent/test/agent-session-checkpoint-rewind-branch.test.ts index bfb7b6c51..5f8c74487 100644 --- a/packages/coding-agent/test/agent-session-checkpoint-rewind-branch.test.ts +++ b/packages/coding-agent/test/agent-session-checkpoint-rewind-branch.test.ts @@ -267,6 +267,50 @@ describe("AgentSession checkpoint rewind branch context", () => { expect(finalThinking?.thinkingSignature).toBe("sig_after_rewind"); }); + it("ignores a completed cycle's rewind result after rebuilding context", async () => { + const staleReport = "findings from the previous checkpoint"; + const currentReport = "findings from the current checkpoint"; + const { session, mock } = await createHarness([ + { + content: [ + { type: "toolCall", id: "call_checkpoint_a", name: "checkpoint", arguments: { goal: "inspect A" } }, + ], + stopReason: "toolUse", + }, + { + content: [{ type: "toolCall", id: "call_rewind_a", name: "rewind", arguments: { report: staleReport } }], + stopReason: "toolUse", + }, + { content: ["DONE"], stopReason: "stop" }, + ]); + await session.prompt("investigate the first checkpoint"); + + session.sessionManager.appendMessage({ + role: "toolResult", + toolCallId: "call_rewind_a_late", + toolName: "rewind", + content: [{ type: "text", text: "rewind requested" }], + details: { report: staleReport, rewound: true }, + isError: false, + timestamp: Date.now(), + }); + session.agent.replaceMessages(session.buildDisplaySessionContext().messages); + + mock.push({ + content: [{ type: "toolCall", id: "call_checkpoint_b", name: "checkpoint", arguments: { goal: "inspect B" } }], + stopReason: "toolUse", + }); + mock.push({ + content: [{ type: "toolCall", id: "call_rewind_b", name: "rewind", arguments: { report: currentReport } }], + stopReason: "toolUse", + }); + mock.push({ content: ["DONE"], stopReason: "stop" }); + + await session.prompt("investigate the second checkpoint"); + + expect(session.getLastCompletedRewind()?.report).toBe(currentReport); + }); + it("does not start checkpoint tracking for xdev help envelopes", async () => { const { session } = await createHarness( [