Rewind chat or files from any user message (#1154)

* Add Pi extension-launch plumbing for in-process tree primitives

* Add Rewind controls for agent sessions

* Preserve SDK checkpoint env var so Claude file rewind works

The SDK injects CLAUDE_CODE_ENABLE_SDK_FILE_CHECKPOINTING=true when file checkpointing is enabled. The earlier parent-env stripping from c4782ec71c was too broad and removed that child flag along with real parent-session markers.

Keep stripping parent markers, but preserve the checkpoint flag so the SDK rewind path can run directly. With that boundary fixed, the JSONL file-rewind fallback is no longer needed.

* Update OpenCode history replay tests for rewind state

* Show Rewind actions in an adaptive sheet

* Fix rewind semantics across providers

* Refactor Pi IDs through extension capture

* Restore user message to composer after rewind

* Fix Claude rewind tool result mapping

* Fix Claude rewind session switch roundtrip

* Fix rewind and optimistic message regressions

* Tighten rewind test discipline

* Fix OpenCode optimistic message reconciliation

* Reconcile optimistic user messages by marker

* Reorder user message actions

* Unify provider user message contract

* Add Claude rewind flow Playwright contract

* Add Codex rewind flow Playwright contract

* Add OpenCode rewind flow Playwright contract

* Add Pi rewind flow Playwright contract

* Remove real-provider rewind Playwright contracts

* Gate real-provider rewind specs from CI Playwright
This commit is contained in:
Mohamed Boudra
2026-05-25 17:11:34 +08:00
committed by GitHub
parent e966f70322
commit 132e572d6e
99 changed files with 7106 additions and 3425 deletions
@@ -64,6 +64,7 @@ import type {
ProviderDiagnosticResponseMessage,
DaemonGetStatusResponse,
DaemonGetPairingOfferResponse,
AgentRewindResponseMessage,
ListTerminalsResponse,
CreateTerminalResponse,
SubscribeTerminalResponse,
@@ -2232,6 +2233,40 @@ export class DaemonClient {
await this.sendAgentMessage(agentId, text, options);
}
async rewindAgent(
agentId: string,
messageId: string,
mode: "conversation" | "files" | "both",
): Promise<AgentRewindResponseMessage["payload"]> {
const requestId = this.createRequestId();
const message = SessionInboundMessageSchema.parse({
type: "agent.rewind.request",
requestId,
agentId,
messageId,
mode,
});
const payload = await this.sendRequest({
requestId,
message,
timeout: 15000,
options: { skipQueue: true },
select: (msg) => {
if (msg.type !== "agent.rewind.response") {
return null;
}
if (msg.payload.requestId !== requestId) {
return null;
}
return msg.payload;
},
});
if (!payload.ok) {
throw new Error(payload.error ?? "Agent rewind failed");
}
return payload;
}
async cancelAgent(agentId: string): Promise<void> {
const requestId = this.createRequestId();
const message = SessionInboundMessageSchema.parse({