@dyyz1993/pi-coding-agent 0.69.12 → 0.69.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/dist/core/agent-session.d.ts.map +1 -1
  2. package/dist/core/agent-session.js +13 -11
  3. package/dist/core/agent-session.js.map +1 -1
  4. package/dist/core/extensions/index.d.ts +1 -1
  5. package/dist/core/extensions/index.d.ts.map +1 -1
  6. package/dist/core/extensions/index.js.map +1 -1
  7. package/dist/core/extensions/runner.d.ts +5 -4
  8. package/dist/core/extensions/runner.d.ts.map +1 -1
  9. package/dist/core/extensions/runner.js +73 -25
  10. package/dist/core/extensions/runner.js.map +1 -1
  11. package/dist/core/extensions/server-channel.d.ts +27 -0
  12. package/dist/core/extensions/server-channel.d.ts.map +1 -0
  13. package/dist/core/extensions/server-channel.js +40 -0
  14. package/dist/core/extensions/server-channel.js.map +1 -0
  15. package/dist/core/extensions/types.d.ts +19 -29
  16. package/dist/core/extensions/types.d.ts.map +1 -1
  17. package/dist/core/extensions/types.js.map +1 -1
  18. package/dist/core/file-store/internal-git.d.ts +31 -0
  19. package/dist/core/file-store/internal-git.d.ts.map +1 -0
  20. package/dist/core/file-store/internal-git.js +176 -0
  21. package/dist/core/file-store/internal-git.js.map +1 -0
  22. package/dist/core/session-manager.d.ts +1 -0
  23. package/dist/core/session-manager.d.ts.map +1 -1
  24. package/dist/core/session-manager.js +7 -2
  25. package/dist/core/session-manager.js.map +1 -1
  26. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  27. package/dist/modes/interactive/interactive-mode.js +1 -0
  28. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  29. package/dist/modes/rpc/rpc-client.d.ts +25 -1
  30. package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
  31. package/dist/modes/rpc/rpc-client.js +26 -3
  32. package/dist/modes/rpc/rpc-client.js.map +1 -1
  33. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  34. package/dist/modes/rpc/rpc-mode.js +33 -2
  35. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  36. package/dist/modes/rpc/rpc-types.d.ts +22 -0
  37. package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
  38. package/dist/modes/rpc/rpc-types.js.map +1 -1
  39. package/dist/rules-engine/cache.d.ts +4 -0
  40. package/dist/rules-engine/cache.d.ts.map +1 -0
  41. package/dist/rules-engine/cache.js +32 -0
  42. package/dist/rules-engine/cache.js.map +1 -0
  43. package/dist/rules-engine/config.d.ts +8 -0
  44. package/dist/rules-engine/config.d.ts.map +1 -0
  45. package/dist/rules-engine/config.js +56 -0
  46. package/dist/rules-engine/config.js.map +1 -0
  47. package/dist/rules-engine/index.d.ts +10 -0
  48. package/dist/rules-engine/index.d.ts.map +1 -0
  49. package/dist/rules-engine/index.js +404 -0
  50. package/dist/rules-engine/index.js.map +1 -0
  51. package/dist/rules-engine/injector.d.ts +5 -0
  52. package/dist/rules-engine/injector.d.ts.map +1 -0
  53. package/dist/rules-engine/injector.js +57 -0
  54. package/dist/rules-engine/injector.js.map +1 -0
  55. package/dist/rules-engine/loader.d.ts +8 -0
  56. package/dist/rules-engine/loader.d.ts.map +1 -0
  57. package/dist/rules-engine/loader.js +190 -0
  58. package/dist/rules-engine/loader.js.map +1 -0
  59. package/dist/rules-engine/matcher.d.ts +3 -0
  60. package/dist/rules-engine/matcher.d.ts.map +1 -0
  61. package/dist/rules-engine/matcher.js +48 -0
  62. package/dist/rules-engine/matcher.js.map +1 -0
  63. package/dist/rules-engine/types.d.ts +150 -0
  64. package/dist/rules-engine/types.d.ts.map +1 -0
  65. package/dist/rules-engine/types.js +2 -0
  66. package/dist/rules-engine/types.js.map +1 -0
  67. package/docs/extensions.md +56 -56
  68. package/docs/file-rollback-design.md +287 -0
  69. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  70. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  71. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  72. package/examples/extensions/custom-provider-qwen-cli/package.json +1 -1
  73. package/examples/extensions/file-snapshot.ts +407 -0
  74. package/examples/extensions/with-deps/package-lock.json +2 -2
  75. package/examples/extensions/with-deps/package.json +1 -1
  76. package/package.json +4 -4
@@ -0,0 +1,287 @@
1
+ # File Version Rollback — 设计文档
2
+
3
+ ## 1. 背景与目标
4
+
5
+ pi-momo-fork 的 session 是 append-only JSONL 树结构。用户通过 `/tree` 回滚消息(移动 leaf 指针)或 `/fork` 创建新 session。但 **文件系统不跟着回滚**,导致消息说"创建了 foo.ts",磁盘上却可能是更新后的版本。
6
+
7
+ 本功能目标:**让文件变更可追踪、可恢复**,在回滚消息时提供选项让用户决定是否同时恢复文件。
8
+
9
+ ## 2. 核心概念
10
+
11
+ ### 2.1 两种操作的区别
12
+
13
+ | | Tree(回滚) | Fork(分叉) |
14
+ |---|---|---|
15
+ | 命令 | `/tree` | `/fork` |
16
+ | 触发事件 | `session_before_tree` → `session_tree` | `session_before_fork` |
17
+ | session | 同一个,不变 | 创建新的 |
18
+ | InternalGit store | 同一个 | 新建,复制父历史 |
19
+ | 磁盘操作 | **可能需要恢复文件** | 不修改文件 |
20
+ | commit 历史 | append 新的回滚 commit | 复制父的(到 fork 点) |
21
+ | 冲突风险 | 高(要覆盖磁盘文件) | 无 |
22
+
23
+ ### 2.2 InternalGit 存储结构
24
+
25
+ ```
26
+ ~/.pi/agent/file-store/<sessionId>/
27
+ ├── objects/ # 内容寻址存储(按 hash)
28
+ │ ├── a1/
29
+ │ │ └── b2c3d4e5 # 文件内容
30
+ │ └── f0/
31
+ │ └── 9e8d7c6b # tree 数据
32
+ └── commits.jsonl # append-only commit 日志
33
+ ```
34
+
35
+ ### 2.3 Commit 结构
36
+
37
+ ```typescript
38
+ interface FileCommit {
39
+ id: string;
40
+ parentCommitId: string | null;
41
+ sessionId: string;
42
+ sessionEntryId: string; // 关联到 session tree 的 entry
43
+ turnIndex: number;
44
+ treeHash: string; // 该时刻的完整文件树 hash
45
+ diff: {
46
+ added: string[];
47
+ modified: string[];
48
+ deleted: string[];
49
+ };
50
+ timestamp: string;
51
+ }
52
+ ```
53
+
54
+ ### 2.4 快照采集时机
55
+
56
+ ```
57
+ turn_start → 清空 turnFiles
58
+ tool_call → 记录文件"修改前"内容(edit/write)
59
+ tool_result → 记录文件"修改后"内容(write/edit/bash)
60
+ turn_end → 批量提交:计算 diff → appendCommit → pi.appendEntry()
61
+ ```
62
+
63
+ ## 3. Tree 回滚 — 文件恢复流程
64
+
65
+ ### 3.1 用户交互(三选一)
66
+
67
+ 当用户通过 `/tree` 回滚到某个 entry 时,弹窗提供三个选项:
68
+
69
+ ```
70
+ 检测到 N 个文件在回滚点之后发生了变更:
71
+ - foo.ts (modified)
72
+ - bar.ts (added)
73
+ - baz.ts (deleted)
74
+
75
+ 选择操作:
76
+ [1. 消息 + 文件一起回滚] ← 消息回退 + 磁盘文件恢复
77
+ [2. 只回滚消息] ← 仅移动 leaf,文件不动
78
+ [3. 压缩到该节点] ← compact 功能,独立处理
79
+ ```
80
+
81
+ ### 3.2 文件恢复的内部流程
82
+
83
+ ```
84
+ session_before_tree 触发
85
+ → preparation.targetId = 目标 entryId
86
+ → preparation.oldLeafId = 当前 leaf entryId
87
+
88
+ 1. 查找 commit
89
+ targetCommit = findCommitByEntryId(sessionId, targetId)
90
+ currentCommit = findCommitByEntryId(sessionId, oldLeafId)
91
+
92
+ 如果 targetCommit 或 currentCommit 不存在 → 无快照数据,跳过文件恢复
93
+
94
+ 2. 恢复两棵树
95
+ targetTree = restoreTree(targetCommit.treeHash) → 目标状态
96
+ currentTree = restoreTree(currentCommit.treeHash) → 当前状态
97
+
98
+ 3. 计算恢复 diff
99
+ diff = computeDiff(currentTree, targetTree)
100
+ → 需要告诉用户哪些文件会变
101
+
102
+ 4. 冲突检测
103
+ 对于 diff 中涉及的每个文件:
104
+ - 读磁盘内容 → hash
105
+ - 与 currentTree 中记录的 hash 对比
106
+ - 不一致 → 标记为 "dirty"(被外部修改过)
107
+
108
+ 5. 用户选择
109
+ 选项 1(消息+文件回滚):
110
+ - clean 文件:直接恢复
111
+ - dirty 文件:二次确认(强制恢复 / 跳过 / 取消)
112
+ - 执行磁盘写回/删除
113
+ - 追加回滚 commit(append-only)
114
+ 选项 2(只回滚消息):
115
+ - 不动文件,直接 return
116
+ 选项 3(压缩):
117
+ - 不动文件,触发 compact 逻辑(独立功能)
118
+
119
+ 6. session_tree 触发(导航完成后)
120
+ - leaf 已经移动到新位置
121
+ - 后续 turn 从新 leaf 继续追踪
122
+ ```
123
+
124
+ ### 3.3 回滚 commit 是追加的
125
+
126
+ 回滚操作本身产生一个新 commit,不删除任何旧 commit:
127
+
128
+ ```jsonl
129
+ {"id":"c001","treeHash":"h1","sessionEntryId":"e1",...}
130
+ {"id":"c002","treeHash":"h2","sessionEntryId":"e3",...}
131
+ {"id":"c003","treeHash":"h3","sessionEntryId":"e5",...}
132
+ {"id":"c-rollback-1","parentCommitId":"c003","treeHash":"h1","turnIndex":-1,...}
133
+ ```
134
+
135
+ ## 4. Fork — session 继承
136
+
137
+ ### 4.1 流程
138
+
139
+ ```
140
+ session_before_fork 触发
141
+ → event.entryId = fork 点的 entryId
142
+
143
+ 1. 创建 session-B 的 InternalGit store
144
+ childGit = InternalGit.create(storeRoot, sessionBId)
145
+
146
+ 2. 复制 commit 历史(到 fork 点为止)
147
+ parentCommits = readCommits().filter(c => sessionId === parentId && c.sessionEntryId <= forkEntryId)
148
+ for each commit → childGit.appendCommit(commit)
149
+
150
+ 3. objects 目录处理
151
+ 方案:在 fork 时把需要的 object 文件 hard link 过去
152
+ (hash 相同 → 内容相同 → 硬链接省空间,修改时 copy-on-write 自然隔离)
153
+
154
+ 4. 文件不动
155
+ fork 时磁盘文件就是当前状态,不需要修改
156
+ ```
157
+
158
+ ## 5. Bash 工具盲区
159
+
160
+ ### 5.1 问题
161
+
162
+ LLM 通过 bash 工具执行 `sed -i`、`npm install` 等操作时:
163
+ - `toolName` 是 `"bash"`,没有 `input.path`
164
+ - 无法从事件中提取具体改了哪些文件
165
+
166
+ ### 5.2 方案:turn_start 全量快照 + turn_end 对比
167
+
168
+ ```
169
+ turn_start:
170
+ → 扫描所有已追踪文件 → 记录 { path: hash } 作为 baseline
171
+
172
+ turn_end:
173
+ → 再次扫描已追踪文件 → 计算与 baseline 的 diff
174
+ → 如果有变更,追加 commit
175
+ ```
176
+
177
+ 不追踪新文件(因为不知道 bash 创建了什么文件),但能检测到已追踪文件被 bash 修改。
178
+
179
+ ### 5.3 未来优化(可选)
180
+
181
+ - 集成 `git stash create` 作为完整快照兜底
182
+ - 通过 `fs.watch` 实时监控文件变更
183
+ - 在 bash 工具返回后解析 diff
184
+
185
+ ## 6. 测试场景
186
+
187
+ ### 6.1 Tree 回滚文件恢复(P0 — 最高优先级)
188
+
189
+ | # | 场景 | 预期 |
190
+ |---|------|------|
191
+ | T1 | 回滚到早期 entry,被追踪的文件恢复到旧版本 | `foo.ts` 从 v2 → v1 |
192
+ | T2 | 回滚后,fork 点之后新增的文件被删除 | `bar.ts`(在 e3 新增)回滚到 e1 时被删除 |
193
+ | T3 | 回滚时,不在追踪范围内的文件不受影响 | `untracked.ts` 不被触碰 |
194
+ | T4 | 回滚产生 append-only 的 rollback commit | 旧 commit 不变,新增 rollback commit |
195
+ | T5 | 无快照数据的 entry 回滚时不崩溃 | 找不到 commit → 跳过文件恢复 |
196
+
197
+ ### 6.2 冲突检测(P0)
198
+
199
+ | # | 场景 | 预期 |
200
+ |---|------|------|
201
+ | C1 | 回滚时文件被外部编辑器修改 → 检测到 dirty | 标记 dirty,提示用户 |
202
+ | C2 | dirty 文件用户选择"跳过" → 该文件不恢复 | 其他文件恢复,dirty 的不动 |
203
+ | C3 | dirty 文件用户选择"强制恢复" → 覆盖写入 | 写回旧版本 |
204
+ | C4 | 所有文件都是 dirty → 用户取消 → 全部不恢复 | 磁盘不变 |
205
+
206
+ ### 6.3 用户交互三选一(P0)
207
+
208
+ | # | 场景 | 预期 |
209
+ |---|------|------|
210
+ | U1 | 选择"只回滚消息" → 文件不变 | leaf 移动,磁盘不动 |
211
+ | U2 | 选择"消息+文件回滚" → 文件恢复 | leaf 移动 + 磁盘恢复 |
212
+ | U3 | 无文件变更的回滚 → 不弹窗 | diff 为空,跳过提示 |
213
+
214
+ ### 6.4 Fork 继承(P1)
215
+
216
+ | # | 场景 | 预期 |
217
+ |---|------|------|
218
+ | F1 | Fork 后子 session 有父的 commit 历史(到 fork 点) | childStore.readCommits() 包含父的 commit |
219
+ | F2 | 子 session 新增 commit 不影响父 session | 父 store 不变 |
220
+ | F3 | 子 session 能 restoreTree 到父的任意历史 commit | 恢复正确 |
221
+ | F4 | Fork 时不修改磁盘文件 | 磁盘不变 |
222
+
223
+ ### 6.5 Bash 盲区(P1)
224
+
225
+ | # | 场景 | 预期 |
226
+ |---|------|------|
227
+ | B1 | 已追踪文件被 bash `sed -i` 修改 → turn_end 检测到变更 | diff 包含该文件 |
228
+ | B2 | 已追踪文件被 bash 删除 → turn_end 检测到删除 | diff.deleted 包含该文件 |
229
+ | B3 | bash 创建新文件 → 不在追踪范围内 | 不追踪(符合预期) |
230
+
231
+ ### 6.6 快照采集(已实现,补充边界场景)
232
+
233
+ | # | 场景 | 预期 |
234
+ |---|------|------|
235
+ | S1 | 空 turn(无文件变更)→ 不产生 commit | readCommits() 不增加 |
236
+ | S2 | 同一文件同一 turn 内多次 edit → 只取最终状态 | commit 里是最终内容 |
237
+ | S3 | write 创建新文件 → tool_call 时文件不存在 | before=null,after=内容 |
238
+ | S4 | edit 修改已存在文件 → tool_call 时读到旧内容 | before=旧内容,after=新内容 |
239
+
240
+ ### 6.7 持久化与恢复(已实现,补充场景)
241
+
242
+ | # | 场景 | 预期 |
243
+ |---|------|------|
244
+ | P1 | session resume → 从 custom entries 重建快照索引 | snapshotsByEntry 恢复 |
245
+ | P2 | session reload → InternalGit store 从磁盘恢复 | commits/objects 完整 |
246
+
247
+ ## 7. 任务拆分
248
+
249
+ ### Task 1: 修正 file-snapshot 扩展事件绑定
250
+ - 把文件恢复逻辑从 `session_before_fork` 移到 `session_before_tree`
251
+ - 重写 `session_before_fork` 只做 store 继承
252
+ - 修改 `examples/extensions/file-snapshot.ts`
253
+
254
+ ### Task 2: Tree 回滚文件恢复 — 测试 + 实现(TDD)
255
+ - 写测试:T1-T5, C1-C4, U1-U3
256
+ - 实现 `session_before_tree` handler
257
+ - 实现冲突检测 + 用户交互
258
+ - 运行测试直到全绿
259
+
260
+ ### Task 3: Fork 继承 — 测试 + 实现(TDD)
261
+ - 写测试:F1-F4
262
+ - 实现 `session_before_fork` handler(store 复制 + object 继承)
263
+ - 运行测试直到全绿
264
+
265
+ ### Task 4: Bash 盲区兜底 — 测试 + 实现(TDD)
266
+ - 写测试:B1-B3
267
+ - 实现 turn_start baseline + turn_end diff
268
+ - 运行测试直到全绿
269
+
270
+ ### Task 5: 边界场景补充
271
+ - 写测试:S1-S4, P1-P2
272
+ - 补充实现
273
+ - 全量测试通过
274
+
275
+ ### Task 6: 集成验证 + npm run check
276
+ - 所有测试文件一起跑
277
+ - `npm run check` 通过
278
+ - biome lint 无新增 warning
279
+
280
+ ## 8. 文件清单
281
+
282
+ | 文件 | 作用 | 状态 |
283
+ |------|------|------|
284
+ | `src/core/file-store/internal-git.ts` | InternalGit 类(对象存储/commit/tree/diff) | ✅ 已实现,37 测试通过 |
285
+ | `examples/extensions/file-snapshot.ts` | 文件快照扩展 | ⚠️ 需修正事件绑定 |
286
+ | `test/file-store/internal-git.test.ts` | InternalGit 单元测试 | ✅ 37 测试通过 |
287
+ | `test/suite/file-snapshot-extension.test.ts` | 扩展集成测试 | ✅ 8 测试通过,需补充新场景 |
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "pi-extension-custom-provider",
3
- "version": "0.69.9",
3
+ "version": "0.69.11",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "pi-extension-custom-provider",
9
- "version": "0.69.9",
9
+ "version": "0.69.11",
10
10
  "dependencies": {
11
11
  "@anthropic-ai/sdk": "^0.52.0"
12
12
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pi-extension-custom-provider-anthropic",
3
3
  "private": true,
4
- "version": "0.69.9",
4
+ "version": "0.69.11",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "clean": "echo 'nothing to clean'",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pi-extension-custom-provider-gitlab-duo",
3
3
  "private": true,
4
- "version": "0.69.9",
4
+ "version": "0.69.11",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "clean": "echo 'nothing to clean'",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pi-extension-custom-provider-qwen-cli",
3
3
  "private": true,
4
- "version": "0.69.9",
4
+ "version": "0.69.11",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "clean": "echo 'nothing to clean'",