feat(rewind): add file restoration support to /rewind command#4064
Merged
Conversation
Contributor
There was a problem hiding this comment.
Pull request overview
Adds file checkpointing and restoration so /rewind can optionally roll back workspace file changes in addition to truncating conversation history. This is implemented via a new core FileHistoryService that snapshots tool-initiated edits per user turn and is surfaced in the CLI RewindSelector as a multi-step “what to restore” flow.
Changes:
- Introduce
FileHistoryService(backup/snapshot/restore + diff stats) and wire it intoConfigand core client turn boundaries. - Track edits before
edit/write_filetool writes so rewinds can restore/delete files to a chosen snapshot. - Extend CLI
/rewindUI to offer restore options (conversation/code/both) with async diff stats + new i18n strings.
Reviewed changes
Copilot reviewed 14 out of 14 changed files in this pull request and generated 9 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/core/src/services/fileHistoryService.ts | New snapshot/backup/restore service used to roll back files on rewind and compute diff stats. |
| packages/core/src/config/config.ts | Adds fileCheckpointingEnabled and lazy getFileHistoryService() singleton. |
| packages/core/src/core/client.ts | Creates snapshots at UserQuery boundaries (best-effort). |
| packages/core/src/tools/edit.ts | Calls trackEdit() before applying edits. |
| packages/core/src/tools/write-file.ts | Calls trackEdit() before writing file content. |
| packages/core/src/index.ts | Exports the new FileHistoryService from core entrypoint. |
| packages/cli/src/ui/types.ts | Adds promptId to user history items for snapshot lookup. |
| packages/cli/src/ui/hooks/useGeminiStream.ts | Stores prompt_id on user history items. |
| packages/cli/src/ui/contexts/UIActionsContext.tsx | Updates rewind confirm signature to include restore option. |
| packages/cli/src/ui/components/RewindSelector.tsx | Adds restore-option phase with async diff stats loading. |
| packages/cli/src/ui/components/DialogManager.tsx | Passes file checkpointing flags/service into RewindSelector. |
| packages/cli/src/ui/AppContainer.tsx | Implements restore option handling (restore files and/or truncate conversation). |
| packages/cli/src/i18n/locales/en.js | Adds new strings for restore options and restore result/errors. |
| packages/cli/src/i18n/locales/zh.js | Adds new strings for restore options and restore result/errors. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Summary
Closes #3697
Previously
/rewindonly truncated conversation history — files modified by the assistant remained on disk, requiring manualgit checkoutto undo. This PR adds a file-copy-based backup system (ported from claude-code'sfileHistory) so users can optionally roll back file changes when rewinding.Core Service (
fileHistoryService.ts)trackEdit()saves a copy of each file before the first tool-initiated write;makeSnapshot()captures per-turn file state at each user turn boundaryrewind(promptId)returnsRewindResult { filesChanged, filesFailed }— partial failures are surfaced to the caller instead of being silently swallowed+insertions -deletionsfor the RewindSelector UI viadiff.diffLinestrackEditandmakeSnapshotuse global max-version scan to prevent backup filename collisions after rewind or snapshot eviction~/.qwen/file-history/{sessionId}/{sha256hash}@v{version}Tool Integration
edit.tsandwrite-file.tscalltrackEdit()before each file write, wrapped in try/catch so file history never breaks core tool operationsclient.tscallsmakeSnapshot()at eachUserQueryturn boundary (wrapped in try/catch so file history never breaks the core chat flow)Config
fileCheckpointingEnabledparameter (default:truefor interactive,falsefor SDK mode and non-interactive-pmode)!params.sdkMode && params.interactive !== falseFileHistoryServicesingleton onConfigRewindSelector UI (3-phase flow)
+N -N in M filesdetail)fileCheckpointingEnabledis falseError handling
rewind()returnsRewindResult { filesChanged, filesFailed }— partial failures are reported with file nameshandleRewindConfirmusestry/finallyto ensure the selector closes on all exit paths (including early returns for compressed turns)Other changes
HistoryItemUser.promptIdfor snapshot lookup during rewindhandleRewindConfirmacceptsRestoreOption, handles file restore before conversation truncationTest plan
Unit tests
17 tests covering: trackEdit, makeSnapshot, version inheritance, rewind (success/deletion/partial failure/truncateHistory), snapshot eviction, getDiffStats, and disabled service.
Manual verification
1. Build and start
2. File restoration (golden path)
Add a comment to the top of README.mdAdd a space to the description in package.jsonEsc Escor type/rewind(+N -M in K files)git diffshould show no changes3. Test each restore option
4. Edge cases
create a file test-temp.txt, rewind → file should be deletedfileCheckpointingEnabled: false), rewind should only show Y/N confirm with no file restore options/clear, ask AI to edit a file, rewind → should work correctly (session reset)Automated verification results (tmux)
The following scenarios were verified programmatically via
tmux send-keysagainstnpm run devwith glm-5 model:Scenario 1: Restore code and conversation
Add a comment '// rewind test' to the top of README.mdEsc EscEnterEnter(on first option)head README.mdScenario 2: Restore conversation only
Enter(pre-filled prompt)Esc Esc→Enter→Down→EnterScenario 3: Restore code only
Remove the first line from README.mdEsc Esc→Enter→Down Down→EnterScenario 4: Esc navigation
Esc Esc→Enter→EscEsc人工验证视频:
Restore code and conversation:
https://github.com/user-attachments/assets/9425fa1f-5b86-445f-aebd-b5d9dd1cae10
Restore conversation only:
https://github.com/user-attachments/assets/07454200-60e8-4f75-857c-cda34bfe2d7f
Restore code only:
https://github.com/user-attachments/assets/ee6d2a96-dc75-4a8f-aa3d-577d340cb151
🤖 Generated with Qwen Code