> ## Documentation Index
> Fetch the complete documentation index at: https://docs.qoder.com/llms.txt
> Use this file to discover all available pages before exploring further.

# 文件快照与回滚

`enableFileCheckpointing` 是 `query()` 的 options 参数之一，用来让 CLI 在工具修改文件前后做快照；启用后，调用方可以用 `q.rewindFiles(userMessageId, ...)` 把文件回滚到某条用户消息开始处理时的状态。这两件事必须配合使用：没启用 checkpoint，rewind 不可用。

<div id="启用文件-checkpoint" />

## 启用文件 checkpoint

```typescript theme={null}
import { query } from '@qoder-ai/qoder-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',
  },
});
```

> 进阶：通过 `settings` 同时调整其他 CLI 配置时
>
> `query()` 的 options 还支持一个 `settings` 字段，可以传一个 `Settings` 对象，也可以传一个 settings 文件的绝对路径字符串。它和 `enableFileCheckpointing` 的关系如下：
>
> * 传 `settings` 对象时，SDK 会把 `general.fileCheckpointing.enabled = true` 自动合并进去，无需手动写。
> * 传 `settings` 文件路径字符串时，SDK 不会改写文件内容，需要你自己在该文件中配置：
>
> ```json theme={null}
> {
>   "general": {
>     "fileCheckpointing": {
>       "enabled": true
>     }
>   }
> }
> ```
>
> 只设置 `enableFileCheckpointing: true`、不传 `settings`，对于纯粹只用 rewind 的场景已经够用。

***

<div id="使用显式-user-message-id" />

## 使用显式 user message id

Rewind 以用户消息 ID 为锚点。需要精确回滚时，建议用结构化输入并自己生成 `uuid`，这样 UI 才能稳定反查「回到那条消息之前」。

```typescript theme={null}
import { randomUUID } from 'node:crypto';
import { query } from '@qoder-ai/qoder-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',
  },
});
```

***

<div id="dry-run-预览" />

## Dry run 预览

先 dry run 可以看到是否能回滚、会影响哪些文件，以及整体的插入/删除统计。Dry run 不会修改文件。

返回的 `RewindFilesResult` 包含以下字段：

| 字段             | 类型          | 说明                                  |
| -------------- | ----------- | ----------------------------------- |
| `canRewind`    | `boolean`   | 是否可以执行回滚。dry run 失败时不抛错，由该字段标记      |
| `error`        | `string?`   | `canRewind=false` 时的诊断文案，可直接展示给用户   |
| `filesChanged` | `string[]?` | 受影响文件的绝对路径列表，可用于在 UI 中列出每一个将要被回滚的文件 |
| `insertions`   | `number?`   | 回滚动作总共会“撤销新增”的行数（汇总值）               |
| `deletions`    | `number?`   | 回滚动作总共会“撤销删除”的行数（汇总值）               |

> 当前 SDK 只在 `RewindFilesResult` 中返回受影响的文件列表与汇总的行级统计，不会返回每个文件的具体 diff。如果你需要展示每文件的差异，可以在 dry run 后基于 `filesChanged` 自己读取磁盘内容并与 checkpoint 内容比对，或在执行 rewind 后用 git/工作区对比工具呈现。

```typescript theme={null}
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}`);
}
```

***

<div id="执行回滚" />

## 执行回滚

```typescript theme={null}
const result = await q.rewindFiles(userMessageId);
console.log(result.filesChanged);
```

***

<div id="失败语义" />

## 失败语义

| 调用形式                                | 不可回滚时的表现                                              |
| ----------------------------------- | ----------------------------------------------------- |
| `rewindFiles(id, { dryRun: true })` | resolve 一个 `{ canRewind: false, error }`，便于在 UI 上展示诊断 |
| `rewindFiles(id)`（执行模式）             | Promise reject，调用方建议用 `try/catch` 展示失败原因              |

```typescript theme={null}
try {
  await q.rewindFiles(userMessageId);
} catch (error) {
  console.error(error instanceof Error ? error.message : String(error));
}
```

***

<div id="options-速查" />

## Options 速查

| 字段                        | 类型                   | 说明                                                                 |
| ------------------------- | -------------------- | ------------------------------------------------------------------ |
| `enableFileCheckpointing` | `boolean`            | 启用文件 checkpoint，供 `rewindFiles()` 使用                               |
| `settings`                | `string \| Settings` | 传给 CLI 的 settings；传对象时 SDK 会合并 `general.fileCheckpointing.enabled` |

***

<div id="返回值参考" />

## 返回值参考

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

***

<div id="最佳实践" />

## 最佳实践

* **保存 user message id**：需要回滚能力的应用应在发送消息时保存 `uuid`，不要依赖 UI 文本反查。
* **Rewind 前先 dry run**：先展示影响范围，再让用户确认执行回滚。
* **回滚后刷新 UI**：rewind 只改文件，不改会话历史，UI 需要根据 `filesChanged` 自行重新加载相关视图。
* **失败时给用户看到 `error`**：`canRewind=false` 时的 `error` 文案通常能直接展示给最终用户做诊断。
