Skip to content

功能规格说明:Rewind 命令 ​

创建日期:2026-02-02

用户场景与测试 (必填) ​

用户故事:基本消息回退(优先级:P1) ​

作为用户,我希望能够通过选择特定的用户消息将对话恢复到之前的状态,以便纠正错误或探索不同的路径,而不会使历史记录变得混乱。

为什么是这个优先级:这是该功能的核心功能。它允许用户管理对话历史并撤消不需要的 AI 响应。

独立测试:可以通过发送多条消息、触发 /rewind、选择一条之前的用户消息,并验证所选消息及其后的所有消息已从历史记录中删除来进行完整测试。

验收场景:

  1. 假设对话中有 3 条用户消息和 3 条 AI 响应,当用户输入 /rewind 并选择第 2 条用户消息时,则第 2 条用户消息、其对应的 AI 响应、第 3 条用户消息及其 AI 响应全部被删除。
  2. 假设存在一个对话,当用户输入 /rewind 但取消选择时,则不删除任何消息,对话保持不变。

用户故事:带文件回退的回退(优先级:P2) ​

作为用户,我希望在删除消息中由 agent 所做的文件更改能够自动回退,以便我的本地环境与对话状态保持同步。

为什么是这个优先级:这确保了对话历史与磁盘上文件的实际状态之间的一致性,这对于编码助手至关重要。

独立测试:可以通过让 agent 创建或修改文件,然后回退到该文件操作之前的某个点,并验证文件已恢复到之前的状态,或者如果是新创建的文件则被删除来进行测试。

验收场景:

  1. 假设 agent 在最近一轮中修改了 src/main.ts,当用户回退到该轮之前的用户消息时,则 src/main.ts 恢复到该修改之前的内容。
  2. 假设 agent 创建了新文件 tests/new_test.ts,当用户回退到文件创建之前的某个点时,则 tests/new_test.ts 从文件系统中删除。

用户故事:支持子 agent 的回退(优先级:P2) ​

作为用户,我希望回退主对话时子 agent 所做的文件更改能够自动回退,并且任何正在运行的后台子 agent 任务在其上下文被移除时能够被终止。

为什么是这个优先级:这确保了回退功能在整个 agent 生态系统中保持一致,包括模块化子 agent 和后台任务。

验收场景:

  1. 假设子 agent 修改了一个文件,当用户将主对话回退到子 agent 被调用之前的某个点时,则子 agent 所做的文件更改被回退。
  2. 假设子 agent 在后台运行一个长时间任务,当用户将主对话回退到子 agent 任务启动之前的某个点时,则后台子 agent 任务自动终止。

用户故事:Webview 端 /rewind 检查点列表(优先级:P1) ​

作为 IDE/桌面端用户,我希望输入 /rewind 后看到由所有用户消息组成的检查点列表(完整展示、可键盘导航),而不是只能在消息气泡上逐条寻找回滚按钮,以便快速回退到任意历史节点(包括压缩前的消息)。

为什么是这个优先级:目前 webview 端只能通过 hover 每条用户消息上的回滚按钮来回退,无法一览所有检查点,且压缩前的消息完全不可达。这与 CLI 的 /rewind 体验差距很大。

独立测试:可以通过发送多条消息、输入 /rewind、键盘导航选择一条检查点并确认,验证所选消息及其后的所有消息被删除来进行完整测试。

验收场景:

  1. 假设对话中有 5 条用户消息,当用户在输入框选择 /rewind 命令时,则界面以列表形式完整展示全部 5 条用户消息的文本预览(非输入补全下拉),列表可滚动,最新一条默认选中。
  2. 假设检查点列表已打开,当用户按 ↑/↓ 键时,则选中项在列表中移动并自动滚动到可见区域;当用户按 Enter 时,则弹出与现有"回滚到此消息"一致的二次确认框;当用户按 Esc 时,则关闭列表且不产生任何改动。
  3. 假设检查点列表已打开,当用户用鼠标点击某条检查点时,则与按 Enter 效果一致(弹出二次确认框)。
  4. 假设对话经历过压缩,当用户打开 /rewind 列表时,则压缩前的用户消息也出现在列表中,选中确认后可正常回退。
  5. 假设 agent 正在流式输出,当用户触发 /rewind 时,则命令被忽略(与 /clear 行为一致)。
  6. 假设对话中没有任何用户消息,当用户打开 /rewind 列表时,则列表展示空状态提示,按 Esc 关闭。
  7. 假设对话中存在后台任务通知(task_notification)和 hook 注入的 user 消息(source: "hook"),当用户打开 /rewind 列表(CLI 或 Webview 端)时,则这些系统生成、用户不可见的消息不显示在检查点列表中,列表仅包含用户实际输入的可见消息。
  8. 假设对话经历过多次压缩(如 u1 a1 u2 a2 u3 a3 c1 u2 a2 u3 a3 u4 a4 c2 u3 a3 u4 a4 u5 a5),当用户回退到压缩边界之后的用户消息(如回退到 u5)时,则 webview 显示与回退时刻发给 LLM 的消息一致——从最后一条压缩摘要开始展示(如 c2 u3 a3 u4 a4),压缩前的重复原始历史不再显示。

用户故事:回退不破坏会话文件级元信息(优先级:P1) ​

作为用户,我希望回退对话后会话的标题、创建时间与 git 分支保持不变,且回退中途被打断也不会损坏会话文件,以便回退之后仍能在会话列表里认出这条对话并正常恢复它。

为什么是这个优先级:/rewind 是转录文件唯一的整文件重写路径。头部 metadata 与 custom-title 都是文件级保留条目、不在消息数组里,重写时若一并丢弃,会话会静默退化成「legacy 无头文件」——createdAt 变成当前时间、gitBranch 消失、自定义标题再也读不回来,且不会有任何报错。这条与回退本身同等重要,因为它决定回退是否可逆。

独立测试:对一条既有自定义标题、又有多轮对话的会话执行 /rewind,验证文件首行仍为创建时的 metadata header、custom-title 条目仍在、会话列表显示的自定义标题与创建时间未变;再在一次 /rewind 写入过程中杀死进程,验证文件要么是回退前内容、要么是回退后内容,不存在半截状态。

验收场景:

  1. 假设会话已设置自定义标题且包含多轮对话,当用户回退到其中某条历史消息时,则重写后的文件仍以 metadata header 开头(workdir / createdAt / gitBranch 与创建时逐字一致),且 custom-title 条目仍在,会话列表与恢复流程仍能读出该自定义标题。
  2. 假设会话没有自定义标题,当用户回退时,则文件仍保留首行 metadata header,且不得凭空补写 custom-title 条目。
  3. 假设会话文件本身是 legacy 形态(无 metadata header),当用户回退时,则重写后同样保持无 header 的既有形态,不补造、不报错。
  4. 假设回退的整文件写入被中断(进程被杀、磁盘写满、断电),当再次读取该会话时,则必须读到回退前或回退后的完整内容之一——不得出现首行丢失、文件被截断或半写状态。
  5. 假设用户对同一会话连续多次回退,当每次回退完成时,则 metadata header 始终只有一行且内容不变;custom-title 不丢失、也不会回退成首条消息——保留条目 append-only,回退原样保留既有条目,并按会话管理的稀疏重追加口径(ui/session-management.md 验收场景 5 第 5 项)把标题再写一份到 EOF,列表读最后一个即用户设置的最新标题。

边界情况 ​

  • 用户回退到第一条消息时会发生什么? 整个对话历史应被清除,会话期间所做的任何文件更改(包括子 agent 所做的更改)都应被回退。
  • 对话经过压缩(compact)后还能回退到压缩前的消息吗? 可以。/rewind 的检查点列表必须包含压缩前的用户消息;回退到压缩边界之前的消息时,压缩摘要及其后的所有消息一并删除,对话恢复到该检查点的原始(未压缩)状态。
  • 回退到压缩边界之后的消息时,webview 会显示什么? 显示与回退时刻发给 LLM 的消息完全一致:从最近一条压缩摘要开始,仅展示该压缩摘要及其后的消息(如回退到 u5 时显示 c2 u3 a3 u4 a4)。回退后 agent 的内存消息直接折叠到最近一条压缩摘要(与 compact、resume session 的行为一致),压缩前的重复原始历史不再显示;但会话文件仍保留 rewind 截断后的完整历史(含压缩前消息),因此检查点列表仍包含压缩前的用户消息、仍可继续回退到更早的检查点。
  • 如果触发回退时子 agent 任务正在运行会怎样? 如果启动子 agent 的消息被移除,该任务必须立即停止,以防止不一致的状态。
  • 如果要回退的文件在 agent 修改后被外部修改了会怎样? 系统必须覆盖任何外部更改,并将文件恢复到 agent 在该检查点操作之前的确切状态。
  • 如果要回退的文件已被用户手动删除会怎样? 系统应在之前的内容可用时尝试恢复它,或者优雅地处理缺失的文件而不崩溃。
  • 回退到第一条消息时文件级元信息怎么办? 历史被清空,但 metadata header 与已有的自定义标题必须保留——标题是用户显式设置、与会话内容无关的会话身份信息。
  • 回退是转录 append-only 契约的唯一例外:被回退的消息按用户意图从文件中物理删除,这与 Claude Code「只追加、把被回退的历史留成孤儿死分支」的做法不同,是刻意取舍而非缺陷。转录其余时刻仍是纯追加;多写入方同时改写同一会话文件的覆盖风险不在本轮范围内(与会话管理既有的「无锁」取舍一致,见 ui/session-management.md)。