Merge PR #7740: fix(session): isolate rewind reports by checkpoint cycle (@roboomp)

This commit is contained in:
can1357
2026-08-05 21:50:23 +02:00
3 changed files with 48 additions and 2 deletions
+1
View File
@@ -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))
@@ -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);
@@ -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(
[