makecindy / makecindy/cindy

feat(rewind): 统一 Claude Code / Codex 撤回与编辑语义,支持运行中确认后自动停止

Open
#69 1 comment 0 reactions 1 assignee Claimed by @dashhuang View on GitHub
Dominant language
TypeScript
Stars
2.7k
Forks
395
Avg merge
21h 48m
Merged PRs (30d)
776

Description

## 背景

当前撤回 / 编辑存在两类不一致:

1. 任务运行中按钮不可用。实际上操作已有确认弹窗,用户确认后可以安全地先停止当前 turn,等待进入终态,再执行撤回。
2. Codex 的撤回 / 编辑能力与文件回滚配置耦合,导致未启用或不满足 Git Safety 前置条件时,连“只撤回对话”也不可用。

产品语义应统一为:**对话撤回是基础能力,文件恢复是可选能力**。是否能恢复文件不应决定撤回 / 编辑按钮是否出现。

## 官方行为对照

### Claude Code

官方 Checkpointing 文档定义了以下动作:

- Restore code and conversation
- Restore conversation
- Restore code

如果目标 checkpoint 没有可恢复的文件修改,仍提供 Restore conversation,不会禁用整个 rewind。

官方 Agent SDK 提供的底层能力也是分离的:

- `rewindFiles(userMessageId)`:只恢复文件,不修改对话。
- `resumeSessionAt`:从指定 message UUID 恢复会话上下文。
- `forkSession`:恢复时创建新的 SDK session ID,保留原会话。

Claude Code CLI 在运行中按 `Esc` 只负责 interrupt;停止后再通过 `/rewind` 或 `Esc Esc` 进入撤回菜单。Cindy 可以在确认弹窗后自动完成这两个步骤,减少一次手动操作。

文档:

- https://code.claude.com/docs/en/checkpointing
- https://code.claude.com/docs/en/interactive-mode
- https://code.claude.com/docs/en/agent-sdk/file-checkpointing
- https://code.claude.com/docs/en/agent-sdk/typescript

### Codex

官方 `openai/codex` 最新源码的 TUI 已采用 source-preserving branch:

1. `thread/read(includeTurns: true)`
2. 将所选 prompt 映射到持久化 turn
3. `thread/fork(beforeTurnId)`
4. 切换到 fork 后的新 thread
5. 把原 prompt 恢复到输入框

参考:

- https://github.com/openai/codex/blob/3e2f79727a4e8ddfc8e3acb838d496b121094b9e/codex-rs/tui/src/app_backtrack.rs
- https://github.com/openai/codex/blob/3e2f79727a4e8ddfc8e3acb838d496b121094b9e/codex-rs/tui/src/app/event_dispatch.rs
- https://github.com/openai/codex/blob/3e2f79727a4e8ddfc8e3acb838d496b121094b9e/codex-rs/app-server-protocol/src/protocol/v2/thread.rs

`thread/rollback` 已被官方标记为 deprecated,并且只修改 thread history,不恢复本地文件。官方的 `turn/diff/updated` 适合展示变更,但不是完整、持久的文件 checkpoint。

## 当前实现问题

- 当前内置 Codex 版本为 `0.144.1`。
- `packages/maker-core/src/agents/codex/index.ts` 的 rewind 和精确 fork 仍直接依赖 `thread/rollback`。
- Codex 文件恢复由 Cindy 自己的 Git savepoint / revert 实现。
- Claude 文件恢复使用 Agent SDK 原生 checkpoint;对话恢复通过 `resumeSessionAt + forkSession` 重建 Query。
- `commitRewindFiles` 同时承载“恢复文件”和“裁剪 / 分叉对话”两种概念,接口命名和职责已经不准确。
- 运行中 gate 分散在 renderer、orchestration 和 agent handle,容易出现按钮、确认弹窗和底层能力判断不一致。

## 目标

1. Claude Code 与 Codex 使用一致的产品交互和状态机。
2. 有可撤回的完整 user turn 时,撤回 / 编辑始终可用,不依赖文件 checkpoint 或 Git Safety。
3. 任务运行中允许点击;确认后自动 interrupt,等待 turn 真正进入终态,再撤回。
4. 对话采用 source-preserving branch,保留原 vendor session / thread。
5. 文件恢复作为可选层单独展示、预览和执行。
6. 移除对 deprecated `thread/rollback` 的长期依赖。

## 建议设计

### 共享 orchestration

统一为以下流程:

1. 重新校验目标消息和当前可见消息边界。
2. 若 turn 正在运行,发起一次 interrupt。
3. 等待对应 turn 的 terminal event,并阻止期间的新 send / steer。
4. 解析 vendor 对话锚点。
5. 可选执行文件恢复。
6. 创建目标位置的对话分支。
7. 在 SQLite 事务中软删除目标及之后消息、写回 replacement `sdkSessionId`、重置 usage。
8. 编辑场景把原 prompt 恢复到输入框;普通撤回保持输入框为空或按现有产品约定处理。
9. 任一步失败时保持可恢复状态,并避免出现“DB 已撤回、vendor history 未撤回”的静默分裂。

### Vendor adapter

建议拆分当前能力:

- `restoreFilesAtTarget(...)`
- Claude:SDK `rewindFiles`
- Codex:Git savepoint / protected revert
- `forkConversationAtTarget(...)`
- Claude:`resumeSessionAt + forkSession`
- Codex:`thread/read + thread/fork`

Codex 边界方案:

- 优先升级到支持 `beforeTurnId` 的版本并对齐官方最新流程。
- 若需要兼容 `0.144.1`,可用 `lastTurnId=目标前一个 completed turn` 过渡;编辑第一条 prompt 时创建空的新 thread。
- 不再采用“fork latest 后 rollback N turns”作为长期实现。

### 文件恢复降级

- 文件恢复不可用或没有文件变更时,仍允许只撤回对话。
- Claude checkpoint 只覆盖 Write / Edit / NotebookEdit,不覆盖 Bash、外部编辑和并发会话修改;UI 不得承诺完整文件恢复。
- Codex Git 恢复失败时必须有明确提示和补偿策略,不得影响纯对话撤回能力。
- 确认弹窗应明确显示本次是:
- 对话 + N 个文件
- 仅对话
- 文件恢复不可用,但可继续撤回对话

## 验收标准

- [ ] Claude Code / Codex 的 user message 都有一致的撤回和编辑入口。
- [ ] Codex 未启用 Git Safety、非 Git 目录或没有可回滚文件时,仍能只撤回对话。
- [ ] 任务运行中按钮可点击;用户取消确认时不 interrupt。
- [ ] 用户确认后只 interrupt 一次,并等待目标 turn 进入 completed / interrupted / failed 后再执行撤回。
- [ ] interrupt 等待期间阻止新的 send / steer 与第二次撤回。
- [ ] Claude 对话恢复使用 message UUID 边界,Codex 使用持久化 turn 边界,不在 tool_use / tool_result 中间截断。
- [ ] 撤回中间 turn、最后一轮、第一轮均有明确行为。
- [ ] 编辑完成后目标 prompt 正确恢复到输入框,附件和 mention bindings 不丢失。
- [ ] 原 vendor session / thread 保留;当前业务 session 写回新的 `sdkSessionId`。
- [ ] SQLite 可见消息与 vendor 分支历史一致。
- [ ] 文件恢复失败、vendor fork 失败、DB 事务失败均有测试和可恢复策略。
- [ ] 不再新增 `thread/rollback` 调用;迁移完成后移除现有依赖。
- [ ] 覆盖 Claude / Codex、运行中 / idle、仅对话 / 对话+文件、成功 / 失败 / 并发竞态测试。
- [ ] macOS 与 Windows 至少各完成一组黑盒验证。

## 远程与手机版影响

- 本地 desktop:本 issue 的主要实现范围。
- device-link 远程控制:按钮能力与执行必须以被控端 session 为准,interrupt / rewind 走现有远程 IPC 白名单。
- SSH 远程 Claude:当前 cc-manager 未暴露完整 checkpoint / rewind 能力时应 fail closed,并显示明确原因;不要误用本地 checkpoint。
- mobile:如果移动端展示 message action,应消费被控 desktop 返回的统一 rewind capability,不在移动端自行推断 Git / vendor 前置条件。

## 非目标

- 用 checkpoint 替代 Git 长期版本管理。
- 恢复 Bash、外部编辑器或其他并发会话造成的全部文件变化。
- 改变已经存在的会话归档 / 删除语义。

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.