@dyyz1993/pi-coding-agent 0.69.13 → 0.69.17

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 (55) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/core/agent-session.d.ts +6 -0
  3. package/dist/core/agent-session.d.ts.map +1 -1
  4. package/dist/core/agent-session.js +31 -3
  5. package/dist/core/agent-session.js.map +1 -1
  6. package/dist/core/extensions/channel-manager.d.ts.map +1 -1
  7. package/dist/core/extensions/channel-manager.js +19 -14
  8. package/dist/core/extensions/channel-manager.js.map +1 -1
  9. package/dist/core/extensions/channel-types.d.ts +10 -0
  10. package/dist/core/extensions/channel-types.d.ts.map +1 -1
  11. package/dist/core/extensions/channel-types.js.map +1 -1
  12. package/dist/core/extensions/index.d.ts +3 -1
  13. package/dist/core/extensions/index.d.ts.map +1 -1
  14. package/dist/core/extensions/index.js +1 -0
  15. package/dist/core/extensions/index.js.map +1 -1
  16. package/dist/core/extensions/loader.d.ts.map +1 -1
  17. package/dist/core/extensions/loader.js +8 -2
  18. package/dist/core/extensions/loader.js.map +1 -1
  19. package/dist/core/extensions/types.d.ts +12 -2
  20. package/dist/core/extensions/types.d.ts.map +1 -1
  21. package/dist/core/extensions/types.js.map +1 -1
  22. package/dist/core/session-manager.d.ts +9 -1
  23. package/dist/core/session-manager.d.ts.map +1 -1
  24. package/dist/core/session-manager.js +2 -1
  25. package/dist/core/session-manager.js.map +1 -1
  26. package/dist/index.d.ts +3 -3
  27. package/dist/index.d.ts.map +1 -1
  28. package/dist/index.js.map +1 -1
  29. package/dist/modes/index.d.ts +2 -1
  30. package/dist/modes/index.d.ts.map +1 -1
  31. package/dist/modes/index.js.map +1 -1
  32. package/dist/modes/rpc/rpc-client-types.d.ts +209 -0
  33. package/dist/modes/rpc/rpc-client-types.d.ts.map +1 -0
  34. package/dist/modes/rpc/rpc-client-types.js +8 -0
  35. package/dist/modes/rpc/rpc-client-types.js.map +1 -0
  36. package/dist/modes/rpc/rpc-client.d.ts +38 -16
  37. package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
  38. package/dist/modes/rpc/rpc-client.js +94 -32
  39. package/dist/modes/rpc/rpc-client.js.map +1 -1
  40. package/dist/modes/rpc/rpc-types.d.ts +54 -0
  41. package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
  42. package/dist/modes/rpc/rpc-types.js.map +1 -1
  43. package/dist/rules-engine/index.d.ts.map +1 -1
  44. package/dist/rules-engine/index.js +0 -11
  45. package/dist/rules-engine/index.js.map +1 -1
  46. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  47. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  48. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  49. package/examples/extensions/custom-provider-qwen-cli/package.json +1 -1
  50. package/examples/extensions/file-snapshot.ts +21 -11
  51. package/examples/extensions/linked-projects-bridge/README.md +360 -0
  52. package/examples/extensions/subagent-v2/index.ts +849 -0
  53. package/examples/extensions/with-deps/package-lock.json +2 -2
  54. package/examples/extensions/with-deps/package.json +1 -1
  55. package/package.json +4 -4
@@ -285,12 +285,16 @@ export default function fileSnapshot(pi: ExtensionAPI) {
285
285
  const hasChanges = diff && (diff.added.length > 0 || diff.modified.length > 0 || diff.deleted.length > 0);
286
286
 
287
287
  if (hasChanges || isFirstSnapshot) {
288
- pi.appendEntry("step-snapshot", {
289
- baselineTreeHash: compareTo,
290
- snapshotTreeHash,
291
- diff: hasChanges ? diff : null,
292
- turnIndex,
293
- } satisfies StepSnapshot);
288
+ pi.appendEntry(
289
+ "step-snapshot",
290
+ {
291
+ baselineTreeHash: compareTo,
292
+ snapshotTreeHash,
293
+ diff: hasChanges ? diff : null,
294
+ turnIndex,
295
+ } satisfies StepSnapshot,
296
+ { display: false },
297
+ );
294
298
  lastCommittedTreeHash = snapshotTreeHash || null;
295
299
  }
296
300
 
@@ -298,6 +302,8 @@ export default function fileSnapshot(pi: ExtensionAPI) {
298
302
  });
299
303
 
300
304
  pi.on("session_tree", async (event: SessionTreeEvent, ctx: ExtensionContext) => {
305
+ if (event.skipFiles) return;
306
+
301
307
  const targetId = event.newLeafId;
302
308
  const s = getStore(ctx);
303
309
  const entries = ctx.sessionManager.getEntries();
@@ -325,11 +331,11 @@ export default function fileSnapshot(pi: ExtensionAPI) {
325
331
  const targetFiles = targetTreeHash ? s.readTree(targetTreeHash) : new Map<string, string>();
326
332
  const currentFiles = currentTreeHash2 ? s.readTree(currentTreeHash2) : new Map<string, string>();
327
333
 
328
- const toRestore = new Map<string, string>();
334
+ const toRestore: string[] = [];
329
335
  for (const [path, content] of targetFiles) {
330
336
  const current = currentFiles.get(path);
331
337
  if (current !== content) {
332
- toRestore.set(path, content);
338
+ toRestore.push(path);
333
339
  }
334
340
  }
335
341
 
@@ -340,7 +346,11 @@ export default function fileSnapshot(pi: ExtensionAPI) {
340
346
  }
341
347
  }
342
348
 
343
- if (toRestore.size === 0 && toDelete.length === 0) return;
349
+ if (toRestore.length === 0 && toDelete.length === 0) return;
350
+
351
+ if (event.preview) {
352
+ return { restored: toRestore.sort(), deleted: toDelete.sort() };
353
+ }
344
354
 
345
355
  const preRollbackFiles = s.scanWorkingDir(ctx.cwd);
346
356
  const preRollbackTreeHash = preRollbackFiles.size > 0 ? s.writeTree(preRollbackFiles) : "";
@@ -348,10 +358,10 @@ export default function fileSnapshot(pi: ExtensionAPI) {
348
358
  pi.appendEntry("unrevert-point", {
349
359
  preRollbackTreeHash,
350
360
  rolledBackToLeaf: targetId ?? "",
351
- restoredFiles: [...toRestore.keys()],
361
+ restoredFiles: toRestore,
352
362
  } satisfies UnrevertPoint);
353
363
 
354
- restoreFiles(ctx.cwd, toRestore);
364
+ restoreFiles(ctx.cwd, new Map(toRestore.map((p) => [p, targetFiles.get(p)!])));
355
365
  deleteFiles(ctx.cwd, toDelete);
356
366
  });
357
367
  }
@@ -0,0 +1,360 @@
1
+ # linked-projects-bridge 插件
2
+
3
+ ## 功能说明
4
+
5
+ 实现跨项目知识桥接,解决项目 A 依赖项目 B 但文档不足导致的认知断层问题。
6
+
7
+ ### 核心能力
8
+
9
+ 1. **配置关联的外部项目**:在项目根目录的 `.pi/linked-projects.json` 配置多个关联项目
10
+ 2. **工具拦截**:拦截访问关联项目路径的工具调用,引导使用子任务查找
11
+ 3. **知识沉淀**:子任务查找结果自动写入项目级知识文件和个人级 agent memories
12
+ 4. **系统提示词注入**:自动将关联项目信息和已有知识注入到 LLM 提示词中
13
+
14
+ ---
15
+
16
+ ## Channel 协议设计(ServerChannel)
17
+
18
+ 本插件使用 `ServerChannel<T>` 类型安全的双向通信机制,区分调用(call)和推送(emit)职责。
19
+
20
+ ### Contract 定义
21
+
22
+ ```typescript
23
+ interface LinkedProjectsChannelContract {
24
+ methods: {
25
+ // 获取所有项目列表
26
+ config_list: {
27
+ params: { readonly?: boolean };
28
+ return: { projects: LinkedProject[] };
29
+ };
30
+
31
+ // 获取单个项目详情
32
+ config_get: {
33
+ params: { projectId: string };
34
+ return: { project: LinkedProject | null };
35
+ };
36
+
37
+ // 添加新项目
38
+ config_add: {
39
+ params: { project: LinkedProject };
40
+ return: { success: boolean };
41
+ };
42
+
43
+ // 更新项目配置
44
+ config_update: {
45
+ params: { projectId: string; project: Partial<LinkedProject> };
46
+ return: { success: boolean };
47
+ };
48
+
49
+ // 删除项目
50
+ config_delete: {
51
+ params: { projectId: string };
52
+ return: { success: boolean };
53
+ };
54
+ };
55
+
56
+ events: {
57
+ // 配置变更时推送(服务器主动推送)
58
+ config_changed: { projects: LinkedProject[] };
59
+
60
+ // 知识更新时推送(服务器主动推送)
61
+ knowledge_changed: { projectId: string };
62
+ };
63
+ }
64
+ ```
65
+
66
+ ### 消息格式
67
+
68
+ #### 调用消息(右侧面板 → 插件)
69
+
70
+ 右侧面板作为**调用方(Client)**,通过 `channel.call()` 调用插件提供的方法:
71
+
72
+ ```typescript
73
+ // 获取项目列表(初始化时调用)
74
+ const result = await channel.call("config_list", {});
75
+ // → { projects: [...] }
76
+
77
+ // 添加项目
78
+ const result = await channel.call("config_add", { project: { id: "my-lib", path: "/path", ... } });
79
+ // → { success: true }
80
+
81
+ // 获取单个项目
82
+ const result = await channel.call("config_get", { projectId: "my-lib" });
83
+ // → { project: { ... } | null }
84
+
85
+ // 更新项目
86
+ const result = await channel.call("config_update", { projectId: "my-lib", project: { readonly: true } });
87
+ // → { success: true }
88
+
89
+ // 删除项目
90
+ const result = await channel.call("config_delete", { projectId: "my-lib" });
91
+ // → { success: true }
92
+ ```
93
+
94
+ #### 推送消息(插件 → 右侧面板)
95
+
96
+ 插件作为**服务方(Server)**,主动推送事件到右侧面板:
97
+
98
+ ```typescript
99
+ // 推送配置变更(添加/更新/删除项目后)
100
+ channel.emit("config_changed", { projects: config.projects });
101
+
102
+ // 推送知识更新(知识沉淀后)
103
+ channel.emit("knowledge_changed", { projectId: "my-lib" });
104
+ ```
105
+
106
+ #### 事件消息(右侧面板 → 插件)
107
+
108
+ 右侧面板可以监听这些事件:
109
+
110
+ ```typescript
111
+ channel.onReceive((data) => {
112
+ if (data.type === "config_changed") {
113
+ console.log("Projects updated:", data.projects);
114
+ } else if (data.type === "knowledge_changed") {
115
+ console.log("Knowledge updated for project:", data.projectId);
116
+ }
117
+ });
118
+ ```
119
+
120
+ ---
121
+
122
+ ## 生命周期说明
123
+
124
+ ### 初始化流程
125
+
126
+ ```
127
+ 1. 插件加载
128
+ ├─ 读取 .pi/linked-projects.json
129
+ ├─ 读取 .pi/linked-knowledge/*.md
130
+ ├─ 在 before_agent_start 中注入系统提示词
131
+ └─ 注册 Channel "linked-projects"
132
+
133
+ 2. 右侧面板初始化
134
+ ├─ 调用 channel.call("config_list", {})
135
+ ├─ 获取完整项目列表
136
+ ├─ 渲染配置 UI
137
+ └─ 订阅 channel.onReceive 监听后续事件
138
+
139
+ 3. 配置变更推送(双向)
140
+ 用户在右侧面板操作:
141
+ ├─ 调用 channel.call("config_add/update/delete", { ... })
142
+ ├─ 插件保存配置到文件
143
+ ├─ 插件推送 channel.emit("config_changed")
144
+ └─ 右侧面板收到事件后更新 UI
145
+
146
+ 4. 知识更新推送
147
+ ├─ 子任务查找完成
148
+ ├─ 插件沉淀知识到文件 + agent memories
149
+ ├─ 插件推送 channel.emit("knowledge_changed")
150
+ └─ 右侧面板收到后刷新知识展示
151
+
152
+ 5. 持续变化通知
153
+ ├─ 右侧面板可以随时拉取最新状态
154
+ ├─ 配置变化 → 插件推送 config_changed
155
+ └─ 知识变化 → 插件推送 knowledge_changed
156
+ ```
157
+
158
+ ---
159
+
160
+ ## 使用示例
161
+
162
+ ### 右侧面板(Client 端)
163
+
164
+ ```typescript
165
+ import { RpcClient } from "@dyyz1993/pi-coding-agent";
166
+
167
+ async function initLinkedProjectsPanel(client: RpcClient) {
168
+ const channel = client.channel("linked-projects");
169
+
170
+ await client.start();
171
+
172
+ channel.onReceive((data) => {
173
+ if (data.type === "config_changed") {
174
+ updateProjectsUI(data.projects);
175
+ } else if (data.type === "knowledge_changed") {
176
+ refreshKnowledgeDisplay(data.projectId);
177
+ }
178
+ });
179
+
180
+ const result = await channel.call("config_list", {});
181
+ if (result.projects) {
182
+ updateProjectsUI(result.projects);
183
+ }
184
+ }
185
+
186
+ function updateProjectsUI(projects: LinkedProject[]) {
187
+ console.log("Projects:", projects);
188
+ }
189
+
190
+ function refreshKnowledgeDisplay(projectId: string) {
191
+ console.log("Refreshing knowledge for:", projectId);
192
+ }
193
+ ```
194
+
195
+ ---
196
+
197
+ ## 数据持久化
198
+
199
+ ### 配置文件
200
+
201
+ 路径:项目根目录 `.pi/linked-projects.json`
202
+
203
+ ```json
204
+ {
205
+ "projects": [
206
+ {
207
+ "id": "pi-mono",
208
+ "path": "/Users/dev/pi-mono",
209
+ "description": "pi coding agent 主仓库",
210
+ "relationship": "upstream",
211
+ "keyPaths": [
212
+ { "path": "packages/coding-agent/src/core/extensions/", "description": "扩展 API" },
213
+ { "path": "packages/coding-agent/src/core/tools/", "description": "工具定义" }
214
+ ],
215
+ "readonly": true
216
+ }
217
+ ]
218
+ }
219
+ ```
220
+
221
+ ### 知识沉淀文件
222
+
223
+ 路径:`.pi/linked-knowledge/<project-id>.md`
224
+
225
+ ```markdown
226
+ # pi-mono 知识沉淀
227
+
228
+ ## 2026-04-30: Extension API 概览
229
+ - Extension 通过 `pi.on("tool_call", handler)` 注册工具调用拦截
230
+ - `before_agent_start` 事件可注入/替换系统提示词
231
+ - Channel 机制支持插件与 UI 双向通信
232
+ - 子任务通过 spawn 独立 pi 进程实现
233
+
234
+ ## 2026-04-30: 工具拦截机制
235
+ - 三层拦截: Agent Core hooks → Extension events → Hooks system
236
+ - `beforeToolCall` 返回 `{ block: true }` 可阻止执行
237
+ - bash 工具具有 `spawnHook` 可感知/修改 cwd
238
+
239
+ ## 2026-05-01: 文件快照机制发现
240
+ - 项目支持增量快照,每次 tool 执行后自动记录文件状态变化
241
+ - 通过 `navigateTree` 支持快速回滚到任意历史节点
242
+ - 快照数据存储在项目级文件系统,支持跨会话持久化
243
+ ```
244
+
245
+ ---
246
+
247
+ ## 配置说明
248
+
249
+ ### 字段说明
250
+
251
+ - **id**: 项目唯一标识符,用于知识文件命名和引用
252
+ - **path**: 关联项目的绝对路径
253
+ - **description**: 项目关系说明,注入到系统提示词
254
+ - **relationship**: `upstream`(上游依赖)/ `downstream`(下游消费者)/ `sibling`(同级关联)
255
+ - **keyPaths**: 关键目录/文件及说明,帮助子任务缩小搜索范围。目录优先,偶尔可以指定关键文件
256
+ - **readonly**: 默认 true,子任务只有读权限
257
+
258
+ ### 操作权限
259
+
260
+ - **readonly = true**: 子任务只能查询,不能修改(通过 UI 也不能修改)
261
+ - **readonly = false** 或未设置: 允许修改(需要权限检查)
262
+
263
+ ---
264
+
265
+ ## 与子任务集成
266
+
267
+ 当 LLM 查看关联项目代码时,插件会拦截工具调用并引导使用子任务。
268
+
269
+ ### 拦截提示
270
+
271
+ ```
272
+ 该路径属于关联项目「pi-mono (upstream)」。
273
+ 请启动子任务(Task)查找,建议 prompt:
274
+
275
+ ---
276
+ 在项目 /Users/dev/pi-mono 中查找以下信息:
277
+ [用户的原始意图]
278
+ 重点关注目录:
279
+ - packages/coding-agent/src/core/extensions/ — 扩展 API
280
+ - packages/coding-agent/src/core/tools/ — 工具定义
281
+
282
+ 已有知识参考:.pi/linked-knowledge/pi-mono.md
283
+ 请总结关键发现。
284
+ ---
285
+ ```
286
+
287
+ ### 子任务环境
288
+
289
+ 子任务会以关联项目的路径为 `cwd` 启动,因此:
290
+ - 子任务可以自由访问关联项目的所有文件
291
+ - 子任务不受主任务的拦截规则影响
292
+ - 子任务返回的结构化结果会被插件沉淀为知识
293
+
294
+ ---
295
+
296
+ ## 文件结构
297
+
298
+ ```
299
+ examples/extensions/linked-projects-bridge/
300
+ ├── index.ts # 插件入口,注册所有事件和 Channel
301
+ ├── config.ts # 配置文件读写(.pi/linked-projects.json)
302
+ ├── interceptor.ts # 工具调用拦截逻辑
303
+ ├── knowledge.ts # 知识文件管理(.pi/linked-knowledge/*.md)
304
+ └── README.md # 本文档
305
+ ```
306
+
307
+ ---
308
+
309
+ ## 技术细节
310
+
311
+ ### ServerChannel 使用优势
312
+
313
+ - **类型安全**:通过 Contract 接口完全定义方法签名和返回类型
314
+ - **自动 invokeId**:插件调用 `channel.handle()` 注册的方法时自动生成 invokeId,调用方通过 `channel.invoke()` 自动附加
315
+ - **错误处理**:方法执行出错时自动 reject Promise,调用方可以捕获错误
316
+ - **消息隔离**:调用消息(`__call`)和推送消息(`emit`)完全分离,不会混淆
317
+
318
+ ### 拦截机制
319
+
320
+ 插件监听 `tool_call` 事件,当检测到访问关联项目路径时:
321
+ - 返回 `{ block: true }` 阻止工具执行
322
+ - 返回引导消息,建议 LLM 使用子任务
323
+
324
+ ### 知识管理
325
+
326
+ - **项目级**:存储在 `.pi/linked-knowledge/<project-id>.md`,团队共享
327
+ - **个人级**:通过 agent memories 持久化,跨项目个人知识积累
328
+ - **自动追加**:每次子任务完成后追加最新发现到知识文件
329
+
330
+ ---
331
+
332
+ ## 开发和测试
333
+
334
+ ### 开发插件
335
+
336
+ 插件位于 `packages/coding-agent/examples/extensions/linked-projects-bridge/`
337
+
338
+ ### 运行测试
339
+
340
+ ```bash
341
+ cd packages/coding-agent
342
+ npx tsx ../../node_modules/vitest/dist/cli.js --run test/suite/linked-projects-bridge.test.ts
343
+ ```
344
+
345
+ ### 测试覆盖
346
+
347
+ - ✅ 配置文件读写(loadConfig, saveConfig)
348
+ - ✅ 知识文件管理(appendKnowledge, KnowledgeStore)
349
+ - ✅ 路径匹配(matchLinkedPath)
350
+ - ✅ 系统提示词构建(buildSystemPromptSection)
351
+ - ✅ 拦截消息构建(buildInterceptMessage)
352
+
353
+ ---
354
+
355
+ ## 注意事项
356
+
357
+ 1. **首次使用**:首次使用时需要创建 `.pi/linked-projects.json` 配置文件
358
+ 2. **路径权限**:确保关联项目路径可读(子任务会以该路径为 cwd 启动)
359
+ 3. **知识复用**:系统提示词中会包含已有知识摘要,LLM 可能直接复用,减少子任务调用
360
+ 4. **配置热重载**:插件监听配置文件变化,支持外部工具修改配置后自动重载(通过 watchFile 实现)