Skip to main content
输入与输出

文件快照与回滚

文件 checkpoint 用来记录一次会话里被工具修改过的本地文件状态。启用 enableFileCheckpointing(TypeScript)/ enable_file_checkpointing(Python)后,调用方可以用 rewindFiles(userMessageId, ...) / rewind_files(user_message_id, ...) 把文件回滚到某条用户消息开始处理时的状态。 这两个能力需要配合使用:没启用 checkpoint,rewind 没有可用的文件快照。

启用文件 checkpoint

import { query } from '@qodercn-ai/qodercn-agent-sdk';

const q = query({
  prompt: 'Refactor src/foo.ts into a cleaner implementation',
  options: {
    cwd: '/path/to/project',
    enableFileCheckpointing: true,
    allowedTools: ['Read', 'Edit', 'Write'],
    permissionMode: 'acceptEdits',
  },
});
Python 需要后续回滚时,使用 QoderSDKClient 保持同一个活跃会话。示例中的 extra_args={"replay-user-messages": None} 不是启用 checkpoint 的开关,它的作用是让响应流里回放 UserMessage,并带上可作为回滚锚点的 uuid——如果你的应用需要让用户点选「回到这一轮之前」,通常应该同时设置它。

获取回滚锚点:user message ID

Rewind 以用户消息 ID 为锚点,两个 SDK 获取方式不同:
  • TypeScript:需要精确回滚时,建议用结构化输入并自己生成 uuid,这样 UI 才能稳定反查「回到那条消息之前」。
  • Python:常见做法是从响应流里的 UserMessage.uuid 捕获这个 ID(配合 extra_args={"replay-user-messages": None})。
import { randomUUID } from 'node:crypto';
import { query } from '@qodercn-ai/qodercn-agent-sdk';

const userMessageId = randomUUID();

async function* input() {
  yield {
    type: 'user' as const,
    uuid: userMessageId,
    parent_tool_use_id: null,
    message: {
      role: 'user' as const,
      content: [
        {
          type: 'text' as const,
          text: 'Rewrite notes.txt as a two-line summary.',
        },
      ],
    },
  };

  // If your application needs to call rewind later within the same session,
  // keep yielding subsequent user inputs here instead of closing the stream.
}

const q = query({
  prompt: input(),
  options: {
    cwd: '/path/to/project',
    enableFileCheckpointing: true,
    allowedTools: ['Read', 'Write'],
    permissionMode: 'acceptEdits',
  },
});
回滚锚点是用户消息的 uuid,不是 session_id,也不是 result 消息的 ID。它只在产生该 checkpoint 的会话上下文中有效;其他会话不能拿这个 ID 直接回滚。

Dry run 预览

执行回滚前,建议先 dry run 预览影响范围:是否能回滚、会影响哪些文件,以及整体的插入/删除统计。Dry run 不会修改文件,适合做确认弹窗或审计日志。 返回的 RewindFilesResult 包含以下字段:
字段类型说明
canRewindboolean是否可以执行回滚。dry run 失败时不抛错,由该字段标记
errorstring?canRewind 为 false 时的诊断文案,可直接展示给用户
filesChangedstring[]?受影响文件的绝对路径列表,可用于在 UI 中列出每一个将要被回滚的文件
insertionsnumber?回滚动作总共会"撤销新增"的行数(汇总值)
deletionsnumber?回滚动作总共会"撤销删除"的行数(汇总值)
当前 SDK 只在 RewindFilesResult 中返回受影响的文件列表与汇总的行级统计,不会返回每个文件的具体 diff。如果你需要展示每文件的差异,可以在 dry run 后基于 filesChanged 自己读取磁盘内容并与 checkpoint 内容比对,或在执行 rewind 后用 git/工作区对比工具呈现。
const preview = await q.rewindFiles(userMessageId, { dryRun: true });

if (!preview.canRewind) {
  // Show the diagnostic message in the UI.
  console.error(preview.error);
  return;
}

// Overall stats across all affected files.
console.log({
  files: preview.filesChanged?.length ?? 0,
  insertions: preview.insertions ?? 0,
  deletions: preview.deletions ?? 0,
});

// Per-file listing — useful for a confirmation dialog.
for (const file of preview.filesChanged ?? []) {
  console.log(`will be reverted: ${file}`);
}

执行回滚

确认影响范围后,不传 dry run 参数即可执行回滚:
const result = await q.rewindFiles(userMessageId);
console.log(result.filesChanged);
回滚只恢复被 checkpoint 追踪到的本地文件状态,不会回滚对话历史。也就是说,模型仍然保留之前会话里的上下文;UI 需要根据 filesChanged 自行刷新编辑器、文件树或 diff 视图。

失败语义

调用形式不可回滚时的表现
dry run 模式(rewindFiles(id, { dryRun: true }) / rewind_files(id, dry_run=True)返回 { canRewind: false, error },便于在 UI 上展示诊断
执行模式(rewindFiles(id) / rewind_files(id)TypeScript 中 Promise reject;Python 中抛出异常。调用方应捕获并展示失败原因
try {
  await q.rewindFiles(userMessageId);
} catch (error) {
  console.error(error instanceof Error ? error.message : String(error));
}
常见失败原因包括:没有启用文件 checkpoint、传入的 ID 不是有效用户消息 UUID、该 ID 不属于当前会话、目标消息没有可回滚的文件快照。

Settings 关系

options 里的 settings 字段可以和文件 checkpoint 开关同时使用。它可以传一个 Settings 对象,也可以传一个 settings 文件的绝对路径字符串:
  • settings 对象时,SDK 会把 general.fileCheckpointing.enabled = true 自动合并进去,无需手动写。如果已有其他 settings 字段,它们会被保留;如果已有 fileCheckpointing 配置,enabled 会以 SDK 选项为准。
  • 传 settings 文件路径字符串时,SDK 不会改写文件内容,需要你自己在该文件中配置:
{
  "general": {
    "fileCheckpointing": {
      "enabled": true
    }
  }
}
只设置 checkpoint 开关、不传 settings,对于纯粹只用 rewind 的场景已经够用。
options: {
  cwd: '/path/to/project',
  settings: { theme: 'dark' },
  enableFileCheckpointing: true,
}

边界

  • 只回滚本地文件 checkpoint;MCP 工具、远程服务或数据库等外部副作用不会被撤销。
  • 通过 Bash 直接写文件的变更不作为可回滚文件快照处理。
  • 文件内容可以恢复;目录创建这类目录级副作用不一定会被撤销。
  • checkpoint ID 与会话绑定。恢复同一会话后可以继续使用对应 ID;不同会话之间不可混用。

字段速查

入口(TypeScript / Python)说明
enableFileCheckpointing / enable_file_checkpointing启用文件 checkpoint,供 rewind 使用
settings / settings传给 CLI 的 settings;传对象时 SDK 会合并 general.fileCheckpointing.enabled
extra_args(仅 Python){"replay-user-messages": None} 可在流里拿到 UserMessage.uuid
q.rewindFiles(userMessageId, { dryRun }) / client.rewind_files(user_message_id, dry_run=False)预览或执行文件回滚

返回值参考

type RewindFilesResult = {
  canRewind: boolean;
  error?: string;
  filesChanged?: string[];
  insertions?: number;
  deletions?: number;
};

最佳实践

  • 保存 user message ID:需要回滚能力的应用应在发送消息时保存 uuid(Python 中把 UserMessage.uuid 和你的 UI 消息记录绑定起来),不要依赖 UI 文本反查。
  • Rewind 前先 dry run:先展示影响范围,再让用户确认执行回滚。
  • 回滚后刷新 UI:rewind 只改文件,不改会话历史,UI 需要根据 filesChanged 自行重新加载相关视图。
  • 失败时给用户看到 errorcanRewind 为 false 时的 error 文案通常能直接展示给最终用户做诊断。