Skip to content

feat(rewind): add file restoration support to /rewind command#4064

Merged
wenshao merged 32 commits into
mainfrom
feat/file-history-rewind
May 16, 2026
Merged

feat(rewind): add file restoration support to /rewind command#4064
wenshao merged 32 commits into
mainfrom
feat/file-history-rewind

Conversation

@doudouOUC

@doudouOUC doudouOUC commented May 11, 2026

Copy link
Copy Markdown
Collaborator

Summary

Closes #3697

Previously /rewind only truncated conversation history — files modified by the assistant remained on disk, requiring manual git checkout to undo. This PR adds a file-copy-based backup system (ported from claude-code's fileHistory) so users can optionally roll back file changes when rewinding.

Core Service (fileHistoryService.ts)

  • Snapshot/backup lifecycle: trackEdit() saves a copy of each file before the first tool-initiated write; makeSnapshot() captures per-turn file state at each user turn boundary
  • Restore: rewind(promptId) returns RewindResult { filesChanged, filesFailed } — partial failures are surfaced to the caller instead of being silently swallowed
  • DiffStats: computes +insertions -deletions for the RewindSelector UI via diff.diffLines
  • Version safety: both trackEdit and makeSnapshot use global max-version scan to prevent backup filename collisions after rewind or snapshot eviction
  • Backup storage: ~/.qwen/file-history/{sessionId}/{sha256hash}@v{version}

Tool Integration

  • edit.ts and write-file.ts call trackEdit() before each file write, wrapped in try/catch so file history never breaks core tool operations
  • client.ts calls makeSnapshot() at each UserQuery turn boundary (wrapped in try/catch so file history never breaks the core chat flow)

Config

  • New fileCheckpointingEnabled parameter (default: true for interactive, false for SDK mode and non-interactive -p mode)
  • Gates on !params.sdkMode && params.interactive !== false
  • Lazy-initialized FileHistoryService singleton on Config

RewindSelector UI (3-phase flow)

  1. Pick list — select which user turn to rewind to (unchanged)
  2. Restore options — NEW: choose what to restore with async diff stats loading
    • "Restore code and conversation" (shown only when file changes exist, with +N -N in M files detail)
    • "Restore conversation only"
    • "Restore code only" (shown only when file changes exist)
    • "Never mind"
  3. Loading state — selector stays open during async restore, showing "Restoring..." with all input disabled
  4. Legacy confirm — Y/N fallback when fileCheckpointingEnabled is false

Error handling

  • rewind() returns RewindResult { filesChanged, filesFailed } — partial failures are reported with file names
  • In "both" mode, conversation truncation is skipped when any file fails to restore (prevents inconsistent state)
  • handleRewindConfirm uses try/finally to ensure the selector closes on all exit paths (including early returns for compressed turns)

Other changes

  • HistoryItemUser.promptId for snapshot lookup during rewind
  • handleRewindConfirm accepts RestoreOption, handles file restore before conversation truncation
  • i18n strings added for all 9 locales (en, zh, ca, de, fr, ja, pt, ru, zh-TW)

Test plan

Unit tests

npx vitest run packages/core/src/services/fileHistoryService.test.ts

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

npm run build && npm run start
# or use dev mode:
npm run dev

2. File restoration (golden path)

  1. Ask the AI to edit a file: e.g. Add a comment to the top of README.md
  2. Ask the AI to edit a second file: e.g. Add a space to the description in package.json
  3. Trigger rewind: press Esc Esc or type /rewind
  4. Verify the 3-phase UI:
    • Phase 1: pick list to select which turn to rewind to
    • Phase 2: restore options with diff stats (+N -M in K files)
  5. Select Restore code and conversation → verify files revert AND conversation truncates
  6. Verify files are restored: git diff should show no changes

3. Test each restore option

Option Expected behavior
Restore code and conversation Files revert + conversation truncates to selected turn
Restore conversation only Files unchanged, conversation truncates
Restore code only Files revert, conversation preserved
Never mind No-op

4. Edge cases

  • Esc to go back: Press Esc during restore options phase → should return to pick list
  • Loading state: Selector shows "Restoring..." during async restore, input disabled
  • New file created then rewind: Ask AI to create a file test-temp.txt, rewind → file should be deleted
  • file checkpointing disabled: In SDK mode (fileCheckpointingEnabled: false), rewind should only show Y/N confirm with no file restore options
  • /clear then edit: Run /clear, ask AI to edit a file, rewind → should work correctly (session reset)
  • Diff loading state: After selecting a turn, should briefly show "Computing file changes..." before options appear
  • Compressed turn: Select a compressed turn → should show error, does not crash
  • Multiple edits to same file in one turn: Only first pre-edit state is captured, restore is correct

Automated verification results (tmux)

The following scenarios were verified programmatically via tmux send-keys against npm run dev with glm-5 model:

Scenario 1: Restore code and conversation

Step Input Expected Actual
Ask AI to edit Add a comment '// rewind test' to the top of README.md Edit tool called, file modified ✅ Edit applied
Trigger rewind Esc Esc Phase 1: pick list appears ✅ Pick list shown
Select turn Enter Phase 2: restore options with diff stats ✅ "恢复代码和对话 (+1 -0 in 1 file)" shown
Confirm restore Enter (on first option) File reverts + conversation truncates ✅ "已恢复 1 个文件" + "Conversation rewound" shown
Verify file head README.md No comment at top ✅ File restored to original state

Scenario 2: Restore conversation only

Step Input Expected Actual
Re-submit edit prompt Enter (pre-filled prompt) Model edits file again ✅ Comment added back
Trigger rewind + select Esc EscEnterDownEnter Conversation truncates, file unchanged ✅ "Conversation rewound" shown, file NOT restored

Scenario 3: Restore code only

Step Input Expected Actual
Ask AI to remove comment Remove the first line from README.md Edit tool removes line ✅ Line removed
Trigger rewind + select Esc EscEnterDown DownEnter File reverts, conversation preserved ✅ "已恢复 1 个文件", conversation intact

Scenario 4: Esc navigation

Step Input Expected Actual
Open rewind → phase 2 → Esc Esc EscEnterEsc Returns to pick list
Esc again Esc Rewind cancelled, back to input

人工验证视频:

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

Copilot AI review requested due to automatic review settings May 11, 2026 13:47
@doudouOUC doudouOUC self-assigned this May 11, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 into Config and core client turn boundaries.
  • Track edits before edit/write_file tool writes so rewinds can restore/delete files to a chosen snapshot.
  • Extend CLI /rewind UI 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.

Comment thread packages/core/src/tools/write-file.ts Outdated
Comment thread packages/core/src/tools/edit.ts Outdated
Comment thread packages/core/src/core/client.ts Outdated
Comment thread packages/cli/src/ui/components/RewindSelector.tsx
Comment thread packages/cli/src/ui/components/RewindSelector.tsx
Comment thread packages/cli/src/ui/components/RewindSelector.tsx
Comment thread packages/core/src/services/fileHistoryService.ts Outdated
Comment thread packages/core/src/services/fileHistoryService.ts
Comment thread packages/core/src/services/fileHistoryService.ts
@github-actions

github-actions Bot commented May 11, 2026

Copy link