qwen-code/packages/cli
jinye c406c73509
feat(cli): add conversation rewind feature with double-ESC and /rewind command (#3441)
* feat(cli): add conversation rewind feature with double-ESC and /rewind command (#3186)

Add the ability to rewind conversation to a previous user turn, similar
to Claude Code's message selector. Users can trigger rewind via:
- Double-ESC on empty prompt while idle
- /rewind (or /rollback) slash command

The RewindSelector component provides a two-phase UI: a scrollable
pick-list of user turns followed by a confirmation dialog. On confirm,
both UI history and API history are truncated consistently, the terminal
is re-rendered, and the original prompt text is pre-populated in the
input for editing.

Key implementation details:
- historyMapping.ts correctly handles tool-call loops (functionResponse
  entries) and the startup context pair when mapping UI turns to API
  Content[] indices
- useDoublePress hook provides generic double-press detection with
  800ms timeout and proper cleanup on unmount
- ESC handler guards against WaitingForConfirmation state to prevent
  accidental rewind during tool approval
- Chat recording service records rewind events with tree-branching
  via parentUuid for session replay support

Closes #3186

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix: call recordRewind() in handleRewindConfirm and simplify payload

- Actually invoke chatRecordingService.recordRewind() after rewind
- Remove tree-branching from recordRewind (no UI-to-recording UUID
  mapping exists yet) to avoid corrupting the parentUuid chain
- Simplify RewindRecordPayload to just truncatedCount

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* test: add tmux-based E2E script for rewind feature

Automated verification of all 5 manual test items from PR description:
  1. /rewind command flow (pick turn, confirm, verify truncation)
  2. Double-ESC opens selector (with btw dismiss handling)
  3. ESC during streaming cancels (no rewind)
  4. /rewind with no history (guard blocks)
  5. After rewind, model ignores removed turns

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(rewind): resolve resume persistence and IDE mode issues

- chatRecordingService: add turnParentUuids tracking and rewindRecording()
  which re-roots the parentUuid chain so rewound messages land on a dead
  branch; reconstructHistory() then skips them automatically on resume.
  Add rebuildTurnBoundaries() for re-populating the index after /resume.
- AppContainer: fix truncatedCount bug (was always 0 after loadHistory),
  wire handleRewindConfirm to rewindRecording() with correct targetTurnIndex,
  add config.getIdeMode() guard to openRewindSelector so rewind is disabled
  in IDE sessions where extra user Content entries break the API boundary
  mapping.
- useResumeCommand: call rebuildTurnBoundaries() after startNewSession so
  rewind works correctly within resumed sessions.
- resumeHistoryUtils: surface "Conversation rewound." info item when a
  rewind record is encountered during history reconstruction.
- historyMapping.test.ts: add 9 unit tests for computeApiTruncationIndex
  covering normal flow, startup context pair, tool responses, and
  compression fallback.
- Copyright headers: standardize new files to "Copyright 2025 Qwen Code".

🤖 Generated with [Qwen Code](https://github.com/QwenLM/qwen-code)

* fix(rewind): close slash-command, compression, and IDE bypass holes

Three bugs found by Codex review:

1. P1: `/rewind` slash command bypassed the IDE-mode guard because
   `slashCommandActions.openRewindSelector` called `setIsRewindSelectorOpen`
   directly. Fixed by introducing a ref bridge (`openRewindSelectorRef`)
   that delegates to the guarded callback.

2. P1: Slash-command invocations (`/help`, `/stats`, etc.) are stored as
   `type: 'user'` in UI history but never reach the API or recording
   service. The turn-index counter in `handleRewindConfirm` and
   `computeApiTruncationIndex` counted them, producing off-by-N errors.
   Added `isRealUserTurn()` helper that excludes items starting with
   `/` or `?`, applied in all three counting sites (AppContainer,
   historyMapping, RewindSelector).

3. P2: After chat compression, `computeApiTruncationIndex` returned
   `apiHistory.length` when the target turn was unreachable, silently
   keeping the full API history while the UI was truncated. Changed to
   return `-1`; `handleRewindConfirm` now aborts with an error message
   when the target turn was absorbed by compression.

Tests: 14 unit tests for historyMapping (including slash-command and
compression cases), full suite 616/616 passed.

🤖 Generated with [Qwen Code](https://github.com/QwenLM/qwen-code)

---------

Co-authored-by: jinye.djy <jinye.djy@alibaba-inc.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-04-25 22:12:29 +08:00
..
src feat(cli): add conversation rewind feature with double-ESC and /rewind command (#3441) 2026-04-25 22:12:29 +08:00
index.ts feat(core): detect tool validation retry loops and inject stop directive (#3178) 2026-04-18 10:24:46 +08:00
package.json chore(release): bump version to 0.15.2 (#3596) 2026-04-24 19:55:12 +08:00
test-setup.ts fix: prevent bogus shell permission rules in tests 2026-03-20 17:55:33 +08:00
tsconfig.json Merge pull request #2428 from qqqys/feat/mcp_reconnect_error 2026-04-01 16:53:36 +08:00
vitest.config.ts refactor(core): Unify package exports and improve dev experience 2026-02-01 11:59:05 +08:00