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
+29 -1
View File
@@ -102,15 +102,23 @@ import { useAttachmentPreviewUrl } from "@/attachments/use-attachment-preview-ur
import { persistAttachmentFromBytes, persistAttachmentFromDataUrl } from "@/attachments/service";
import type { DaemonClient } from "@server/client/daemon-client";
import { isWeb, isNative } from "@/constants/platform";
import type { AgentCapabilityFlags } from "@server/server/agent/agent-sdk-types";
import { RewindMenu, type RewindMode } from "@/components/rewind/rewind-menu";
import { useRewindAgentMutation } from "@/components/rewind/use-rewind-agent-mutation";
export type { InlinePathTarget } from "@/assistant-file-links";
type MarkdownStyles = Record<string, TextStyle & ViewStyle & { [key: string]: unknown }>;
interface UserMessageProps {
serverId?: string;
agentId?: string;
messageId?: string;
message: string;
images?: UserMessageImageAttachment[];
attachments?: AgentAttachment[];
timestamp: number;
capabilities?: AgentCapabilityFlags;
client?: DaemonClient | null;
isFirstInGroup?: boolean;
isLastInGroup?: boolean;
disableOuterSpacing?: boolean;
@@ -450,10 +458,15 @@ function getUserMessageAttachmentLabel(attachment: AgentAttachment): string {
}
export const UserMessage = memo(function UserMessage({
serverId,
agentId,
messageId,
message,
images = [],
attachments = [],
timestamp,
capabilities,
client,
isFirstInGroup = true,
isLastInGroup = true,
disableOuterSpacing,
@@ -469,10 +482,17 @@ export const UserMessage = memo(function UserMessage({
() => formatMessageTimestamp(new Date(timestamp)),
[timestamp],
);
const rewindMutation = useRewindAgentMutation({ serverId, agentId, client, messageId });
const handlePointerEnter = useCallback(() => setIsHovered(true), []);
const handlePointerLeave = useCallback(() => setIsHovered(false), []);
const getMessageContent = useCallback(() => message, [message]);
const handleRewind = useCallback(
(input: { mode: RewindMode; rewoundText: string }) => {
return rewindMutation.rewindAgent(input);
},
[rewindMutation],
);
const containerStyle = useMemo(
() => [
@@ -510,7 +530,7 @@ export const UserMessage = memo(function UserMessage({
);
return (
<View style={containerStyle}>
<View style={containerStyle} testID="user-message">
<View
style={userMessageStylesheet.content}
onPointerEnter={handlePointerEnter}
@@ -549,6 +569,14 @@ export const UserMessage = memo(function UserMessage({
{hasText ? (
<View style={trailingRowStyle} pointerEvents={showTrailingRow ? "auto" : "none"}>
<Text style={userMessageStylesheet.timestampText}>{formattedTimestamp}</Text>
{capabilities ? (
<RewindMenu
capabilities={capabilities}
isPending={rewindMutation.isPending}
rewoundText={message}
onRewind={handleRewind}
/>
) : null}
<TurnCopyButton
getContent={getMessageContent}
containerStyle={userMessageStylesheet.copyButton}