做 CreatorWeave 的时候,有个问题绕不过去:Agent 改了文件,用户怎么知道改了什么?改坏了怎么回滚?
终端里的 Agent 好办------直接改磁盘文件,用户不满意就 git checkout -- .。但 CreatorWeave 跑在浏览器里,没有 git,没有命令行。用户授权了一个本地磁盘目录给 Agent,Agent 要是直接往里写文件,出了事没有后悔药。
我试过 isomorphic-git------浏览器端的 git 实现。测了一下,一个普通项目做 git status,Node.js 环境下 200ms,浏览器 OPFS 环境2 分钟。600 倍的差距。而且 isomorphic-git 对接 OPFS 的性能表现完全不能接受,慢到你怀疑它是不是卡死了。
更要命的是------普通用户的文件根本不是 git 仓库。你给他装一个完整的 git 实现,纯属杀鸡用牛刀。
所以我决定不装了。不用 git,自己造一个轻量的文件版本管理系统,专门给浏览器端的 Agent 用。
核心思路:OPFS 是沙盒,磁盘是真实世界

先说一个关键决策:Agent 不直接改磁盘文件。
Agent 的所有文件操作都在 OPFS(Origin Private File System)里完成------浏览器提供的私有文件系统,和用户磁盘完全隔离。用户审批了才同步到磁盘。
arduino
本地磁盘目录(用户授权的) ← 真实世界,用户说了算
↕ 审批同步
OPFS files/(Agent 草稿区) ← Agent 随便搞,改坏了不疼
OPFS .baseline/(原始快照) ← "收据",记录改了什么
这三层存储各有各的职责:
| 层 | 存储位置 | 干什么 | 谁说了算 |
|---|---|---|---|
| 本地磁盘 | File System Access API | 用户的真实文件 | 用户 |
| OPFS files/ | 浏览器私有文件系统 | Agent 的工作草稿 | Agent |
| OPFS .baseline/ | 浏览器私有文件系统 | 修改前的原始快照 | 系统 |
Agent 在 files/ 里随便搞------读、写、改、删,都没问题。改坏了?files/ 里有冲突标记,或者直接从 .baseline/ 回滚。用户看着没问题了,审批同步到磁盘。
这个设计从采用 OPFS 的第一天就确定了。因为我一开始就知道:浏览器端的 Agent 不能 YOLO,必须有"后悔药"。
.baseline------变更审批的"原始收据"
.baseline/ 这个设计解决一个核心问题:怎么知道 Agent 到底改了什么?
Agent 修改文件的流程是这样的:
markdown
1. Agent 读取 src/main.ts(从磁盘或 OPFS 缓存)
2. 第一次修改时:
- 把原始内容存到 .baseline/src/main.ts(快照)
- 把新内容写到 files/src/main.ts(草稿)
3. 后续修改:
- .baseline 保持不变(保留最原始的版本)
- 只更新 files/
4. 用户审批时:
- diff(.baseline, files) = Agent 的真实改动
- 用户看到的 diff 就是"Agent 到底改了什么"
5. 同步到磁盘后:
- 清理 .baseline 和 pending 记录
这个流程有几个实际踩坑后才加的细节。
Ghost Dedup:改了又改回去了

Agent 经常干一件事:读取文件 → 修改 → 写回。但有时候 LLM 犹豫了,改了又改回去了------新内容和原始文件一模一样。
这时候如果还标记为"有变更",用户审批时看到的 diff 就是空的,一脸懵。
所以在写入之前,我加了一层检测:
typescript
// 写入前检测:新内容 == baseline 内容?
if (!isNewFile && baselineContent !== null) {
const contentsMatch = await this.areFileContentsEqual(baselineContent, content)
if (contentsMatch) {
// 清理 pending 记录和 baseline 快照
await this.pendingManager.removeByPath(normalizedPath)
await this.deleteFromBaselineDirIfExists(normalizedPath)
return // 跳过写入,不产生虚假变更
}
}
这个检测用的是 Uint32Array 批量比较------把逐字节比较提升为 4 字节批量比较,大文件约 4 倍加速。
readFile 的多层决策树
读取文件时,三套存储的优先级不是固定的,而是根据上下文动态决策:
scss
readFile(path)
│
├── preferNative? → 直接读磁盘文件
│
├── 有 pending 变更?
│ ├── OPFS 内容有冲突标记 → 返回 OPFS 内容(让 Agent 看到冲突)
│ ├── 磁盘 mtime > baseline mtime?
│ │ ├── 磁盘内容 == baseline → 迁移场景,保留 OPFS 草稿
│ │ └── 磁盘内容 != baseline → 真冲突,返回磁盘内容
│ └── 无冲突 → 返回 OPFS 草稿内容
│
├── 无 pending,非 preferOpfs → 读磁盘文件
│
└── 兜底 → 读 OPFS files/
最复杂的场景是:Agent 改了文件 A,但与此同时用户在编辑器里也改了文件 A。这时候怎么处理?
答案是 Git 风格的冲突标记------用行级 diff 生成冲突块:
markdown
<<<<<<< OPFS
Agent 改的内容
=======
用户改的内容
>>>>>>> DISK
但不是整个文件标记为冲突------只在实际差异的行标记,公共行保持不变。这样 Agent 能看懂冲突,用户也能看懂。
冲突让 Agent 来解
两个工作区改了同一个文件怎么办?我不会让用户自己去解 <<<<<<< OPFS 这种东西。
标记冲突后,直接扔给 Agent。Agent 看得懂冲突标记,它会分析两边的内容,生成一个合并后的版本。用户只需要审批最终结果。
这比 git 的冲突解决体验好多了------git 要你自己开编辑器手动合并,我的方案是 Agent 帮你合并,你只管点头或摇头。
多工作区并行------灵感来自 vibe-kanban
有了沙盒和审批,下一个问题来了:能不能让多个 Agent 同时工作?
我之前用过 vibe-kanban------一个用 Kanban 看板编排 AI Agent 的工具。它的核心设计是 git worktree:每个 workspace 给 Agent 一个独立的 git 分支 + 工作目录,多个 Agent 并行互不冲突。
这和我想要的效果一样。但浏览器端没有 git worktree。怎么办?
用 OPFS workspace 替代 git worktree。
markdown
一个本地磁盘目录(用户授权的)
├── workspace-1/ ← Agent A 在重构代码
├── workspace-2/ ← Agent B 在写测试
└── workspace-3/ ← Agent C 在做 code review
每个 workspace 有独立的:
- files/ ← 自己的草稿区
- .baseline/ ← 自己的快照
- assets/ ← 自己的临时文件
- .subagents/ ← 自己的子 Agent
但共享同一个本地磁盘目录。
日常开发中这种场景太多了:一边让 Agent 重构组件,一边让它写单元测试,一边还让它 review 自己的代码。如果只有一个工作区,Agent 得排队,效率太低。
多工作区并行,各自审批各自的。如果改了同一个文件?冲突标记 + Agent 自动合并。
还能挂载多个本地磁盘目录
后来又加了一个能力:一个项目可以挂载多个本地磁盘目录。
典型的跨项目依赖场景------比如前端项目和后端项目在不同的磁盘目录,Agent 需要同时读写两边的代码才能正确理解依赖关系。一个 CreatorWeave 项目挂载多个根目录,Agent 在不同根目录下工作,路径解析自动路由。
在 SQLite 上构建 Git 语义
OPFS 只管文件存储。Git 语义------status、diff、log、restore------全部构建在 SQLite 之上。
bash
fs_changesets 表 ← 快照(相当于 git commit)
fs_ops 表 ← 文件操作(相当于 git 的 index)
fs_snapshot_files 表 ← 快照内容(before/after,用于回滚)
每次审批生成一个 changeset(快照),包含所有被修改文件的 before/after 内容。
在此基础上实现了完整的 Git 命令映射:
| Git 命令 | CreatorWeave 对应 | 实现 |
|---|---|---|
git status |
git_status() |
查 fs_ops 的 review_status + status |
git diff |
git_diff() |
diff .baseline vs files/,支持 stat/numstat/patch |
git log |
git_log() |
查 fs_changesets 按时间倒序 |
git restore --staged |
git_restore(staged=true) |
review_status 从 approved 改回 pending |
git restore |
git_restore() |
discard pending 或从 snapshot 恢复 |
git commit |
用户点击"审批" | pending → approved → synced |
速度快是因为不走文件系统扫描------所有变更记录都在 SQLite 索引里,毫秒级返回。isomorphic-git 要遍历整个文件树做 diff,而我的方案只需要查几张表。
快照切换的事务性
快照切换(从一个 commit 跳到另一个)实现了类似 Saga 模式的补偿机制:
markdown
切换到目标快照:
1. 前进:apply 目标快照
2. 后退:rollback 目标快照
3. 如果中间某个快照回滚失败 → 自动反向补偿之前已成功的快照
这保证了切换的一致性------要么全部成功,要么全部回退到切换前的状态。
和 git 的对比
| Git | CreatorWeave 的方案 | |
|---|---|---|
| 依赖 | 需要安装 git | 纯浏览器,零依赖 |
| 仓库初始化 | git init |
用户授权文件夹就有了 |
| 暂存 | git add |
所有修改自动进入 pending |
| 提交 | git commit |
用户审批 = commit |
| 回滚 | git checkout -- . |
从 .baseline/ 恢复 |
| 分支 | git branch |
多 workspace 并行 |
| 冲突解决 | 手动或 merge tool | Agent 自动解决 |
| status 速度 | isomorphic-git in OPFS 2 分钟 | 毫秒级(SQLite 索引) |
| 适用场景 | 开发者、代码仓库 | 普通用户、任意文件 |
学到了什么
- 不要在浏览器里硬塞终端的设计 --- git 是给开发者用的,普通用户不懂什么叫 commit、branch、merge。浏览器端 Agent 面向的是普通用户,需要一套更直观的方案:改了什么、看不看、同不同意
- OPFS 天然是沙盒 --- Agent 改 OPFS 不改磁盘,这个"隔离"是浏览器免费给你的,不用白不用
- 冲突不应该让人来解 --- Agent 看得懂冲突标记,让它来合并,用户只管审批结果
- 多工作区并行的价值被低估了 --- vibe-kanban 用 git worktree 实现并行,浏览器端用 OPFS workspace 同样能做到,而且更轻
- 这套系统的本质是让 Agent 有"外部感知" --- Agent 改了什么、文件从哪里来、到哪里去,全程可追溯。不是版本管理工具,是 Agent 和真实世界之间的缓冲层
🔗 链接:
- CreatorWeave:github.com/nutstore/cr...
- 在线体验:creatorweave.eo2suite.cn
如果觉得有意思,点个赞让更多人看到。你觉得浏览器端 Agent 还需要哪些 git 特性?rebase?cherry-pick?欢迎评论区聊聊。