@dyyz1993/pi-coding-agent 0.69.14 → 0.69.18
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.
- package/CHANGELOG.md +12 -0
- package/dist/core/agent-session.d.ts +6 -0
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +31 -3
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/extensions/channel-manager.d.ts.map +1 -1
- package/dist/core/extensions/channel-manager.js +19 -14
- package/dist/core/extensions/channel-manager.js.map +1 -1
- package/dist/core/extensions/channel-types.d.ts +10 -0
- package/dist/core/extensions/channel-types.d.ts.map +1 -1
- package/dist/core/extensions/channel-types.js.map +1 -1
- package/dist/core/extensions/index.d.ts +3 -1
- package/dist/core/extensions/index.d.ts.map +1 -1
- package/dist/core/extensions/index.js +1 -0
- package/dist/core/extensions/index.js.map +1 -1
- package/dist/core/extensions/loader.d.ts.map +1 -1
- package/dist/core/extensions/loader.js +8 -2
- package/dist/core/extensions/loader.js.map +1 -1
- package/dist/core/extensions/types.d.ts +12 -2
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/session-manager.d.ts +9 -1
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +2 -1
- package/dist/core/session-manager.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/modes/index.d.ts +2 -1
- package/dist/modes/index.d.ts.map +1 -1
- package/dist/modes/index.js.map +1 -1
- package/dist/modes/rpc/rpc-client-types.d.ts +209 -0
- package/dist/modes/rpc/rpc-client-types.d.ts.map +1 -0
- package/dist/modes/rpc/rpc-client-types.js +8 -0
- package/dist/modes/rpc/rpc-client-types.js.map +1 -0
- package/dist/modes/rpc/rpc-client.d.ts +38 -16
- package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-client.js +94 -32
- package/dist/modes/rpc/rpc-client.js.map +1 -1
- package/dist/modes/rpc/rpc-types.d.ts +54 -0
- package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-types.js.map +1 -1
- package/dist/rules-engine/index.d.ts.map +1 -1
- package/dist/rules-engine/index.js +0 -11
- package/dist/rules-engine/index.js.map +1 -1
- package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/custom-provider-qwen-cli/package.json +1 -1
- package/examples/extensions/file-snapshot.ts +21 -11
- package/examples/extensions/linked-projects-bridge/README.md +360 -0
- package/examples/extensions/subagent-v2/index.ts +849 -0
- package/examples/extensions/with-deps/package-lock.json +2 -2
- package/examples/extensions/with-deps/package.json +1 -1
- 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(
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
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 =
|
|
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.
|
|
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.
|
|
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:
|
|
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 实现)
|