@oyasmi/pipiclaw 0.3.4 → 0.4.0

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 (78) hide show
  1. package/CHANGELOG.md +3 -0
  2. package/LICENSE +184 -0
  3. package/README.md +230 -231
  4. package/dist/agent.d.ts.map +1 -1
  5. package/dist/agent.js +2 -19
  6. package/dist/agent.js.map +1 -1
  7. package/dist/command-extension.d.ts.map +1 -1
  8. package/dist/command-extension.js.map +1 -1
  9. package/dist/commands.d.ts.map +1 -1
  10. package/dist/commands.js.map +1 -1
  11. package/dist/config-loader.d.ts.map +1 -1
  12. package/dist/config-loader.js.map +1 -1
  13. package/dist/context.d.ts.map +1 -1
  14. package/dist/context.js +0 -2
  15. package/dist/context.js.map +1 -1
  16. package/dist/delivery.d.ts.map +1 -1
  17. package/dist/delivery.js +11 -14
  18. package/dist/delivery.js.map +1 -1
  19. package/dist/dingtalk.d.ts.map +1 -1
  20. package/dist/dingtalk.js +26 -26
  21. package/dist/dingtalk.js.map +1 -1
  22. package/dist/events.d.ts.map +1 -1
  23. package/dist/events.js +5 -8
  24. package/dist/events.js.map +1 -1
  25. package/dist/index.d.ts +20 -0
  26. package/dist/index.d.ts.map +1 -0
  27. package/dist/index.js +20 -0
  28. package/dist/index.js.map +1 -0
  29. package/dist/log.d.ts.map +1 -1
  30. package/dist/log.js.map +1 -1
  31. package/dist/main.d.ts.map +1 -1
  32. package/dist/main.js.map +1 -1
  33. package/dist/memory-consolidation.d.ts.map +1 -1
  34. package/dist/memory-consolidation.js.map +1 -1
  35. package/dist/memory-files.d.ts.map +1 -1
  36. package/dist/memory-files.js.map +1 -1
  37. package/dist/memory-lifecycle.d.ts.map +1 -1
  38. package/dist/memory-lifecycle.js +1 -2
  39. package/dist/memory-lifecycle.js.map +1 -1
  40. package/dist/model-utils.d.ts.map +1 -1
  41. package/dist/model-utils.js.map +1 -1
  42. package/dist/paths.d.ts.map +1 -1
  43. package/dist/prompt-builder.d.ts.map +1 -1
  44. package/dist/prompt-builder.js.map +1 -1
  45. package/dist/sandbox.d.ts.map +1 -1
  46. package/dist/sandbox.js +0 -1
  47. package/dist/sandbox.js.map +1 -1
  48. package/dist/shell-escape.d.ts.map +1 -1
  49. package/dist/shell-escape.js.map +1 -1
  50. package/dist/store.d.ts.map +1 -1
  51. package/dist/store.js +2 -3
  52. package/dist/store.js.map +1 -1
  53. package/dist/sub-agents.d.ts.map +1 -1
  54. package/dist/sub-agents.js +42 -10
  55. package/dist/sub-agents.js.map +1 -1
  56. package/dist/tools/attach.d.ts.map +1 -1
  57. package/dist/tools/attach.js.map +1 -1
  58. package/dist/tools/bash.d.ts.map +1 -1
  59. package/dist/tools/bash.js.map +1 -1
  60. package/dist/tools/edit.d.ts.map +1 -1
  61. package/dist/tools/edit.js.map +1 -1
  62. package/dist/tools/index.d.ts.map +1 -1
  63. package/dist/tools/index.js.map +1 -1
  64. package/dist/tools/read.d.ts.map +1 -1
  65. package/dist/tools/read.js.map +1 -1
  66. package/dist/tools/subagent.d.ts.map +1 -1
  67. package/dist/tools/subagent.js.map +1 -1
  68. package/dist/tools/truncate.d.ts.map +1 -1
  69. package/dist/tools/truncate.js.map +1 -1
  70. package/dist/tools/write-content.d.ts.map +1 -1
  71. package/dist/tools/write-content.js.map +1 -1
  72. package/dist/tools/write.d.ts.map +1 -1
  73. package/dist/tools/write.js.map +1 -1
  74. package/docs/memory-rfc.md +291 -0
  75. package/docs/subagent/pi-subagent-analyse.txt +190 -0
  76. package/docs/subagent/pi-subagent-design.txt +266 -0
  77. package/docs/subagent/pi-subagent-phase1-plan.txt +529 -0
  78. package/package.json +69 -53
@@ -0,0 +1,266 @@
1
+ Sub-Agent 集成方案
2
+
3
+ 整体思路
4
+
5
+ 现有 examples/extensions/subagent/ 是通过 spawn pi CLI 子进程 实现隔离的,但 pipiclaw 是长驻
6
+ DingTalk bot 进程,没有 CLI 入口。所以核心改造是:用进程内 new Agent() 替代 subprocess
7
+ spawn,复用同一个 Executor(sandbox)实例。
8
+
9
+ 现有 subagent (pi-coding-agent CLI):
10
+ 主 Agent → spawn("pi", ["--mode json"]) → 子进程 Agent → stdout NDJSON → 解析
11
+
12
+ pipiclaw 方案:
13
+ 主 Agent → tool call "subagent" → new Agent({tools, systemPrompt}) → 事件订阅 → 结果收集
14
+
15
+ 需要做的事情(共 5 件)
16
+
17
+ ---
18
+ 一、新建 src/tools/subagent.ts — Sub-Agent 工具实现
19
+
20
+ 这是核心,作为一个 AgentTool 注册给主 Agent,LLM 通过 tool call 触发。
21
+
22
+ 关键设计:
23
+
24
+ // src/tools/subagent.ts 骨架
25
+
26
+ interface SubAgentToolOptions {
27
+ executor: Executor; // 复用父 Agent 的 sandbox
28
+ getModel: () => Model<Api>; // 当前活跃模型
29
+ resolveApiKey: (model: Model<Api>) => Promise<string>;
30
+ workspaceDir: string; // Agent 定义文件搜索根
31
+ channelDir: string; // 当前频道目录
32
+ }
33
+
34
+ // 工具 schema — 简化版,只支持 single 模式(Phase 1)
35
+ const subagentSchema = Type.Object({
36
+ label: Type.String({ description: "Brief description of what this subagent task does" }),
37
+ agent: Type.String({ description: "Name of the agent to invoke (from .pi/agents/)" }),
38
+ task: Type.String({ description: "Task description for the subagent" }),
39
+ });
40
+
41
+ 执行流程:
42
+
43
+ 1. 发现 Agent — 从 {workspaceDir}/.pi/agents/ 和 {channelDir}/agents/ 加载 .md 文件,解析
44
+ frontmatter 得到 AgentConfig
45
+ 2. 构造工具集 — 根据 AgentConfig.tools 字段(如 "bash,read"),从已有的
46
+ createReadTool/createBashTool/... 中按名过滤
47
+ 3. 创建 Agent 实例 — new Agent({initialState: {systemPrompt, model, tools}, ...})
48
+ 4. 订阅事件收集结果 — 通过 agent.subscribe() 监听 message_end 事件,累积输出
49
+ 5. 等待完成 — await agent.waitForIdle(),提取最终文本输出返回给主 Agent
50
+ 6. 传递 abort 信号 — 父 Agent 被取消时,子 Agent 也跟着取消
51
+
52
+ 与现有 memory-consolidation 中 runWorkerPrompt 的区别:
53
+
54
+ ┌──────────┬────────────────────────────┬─────────────────────────────────┐
55
+ │ 维度 │ runWorkerPrompt │ subagent tool │
56
+ ├──────────┼────────────────────────────┼─────────────────────────────────┤
57
+ │ tools │ [](纯文本生成) │ 按配置注入 bash/read/edit/write │
58
+ ├──────────┼────────────────────────────┼─────────────────────────────────┤
59
+ │ 触发方 │ 系统自动(compaction 时) │ LLM 自主决策(tool call) │
60
+ ├──────────┼────────────────────────────┼─────────────────────────────────┤
61
+ │ 输出 │ 内部消费(更新 MEMORY.md) │ 返回给主 Agent 作为 tool result │
62
+ ├──────────┼────────────────────────────┼─────────────────────────────────┤
63
+ │ 生命周期 │ fire-and-forget │ 可被 abort │
64
+ └──────────┴────────────────────────────┴─────────────────────────────────┘
65
+
66
+ ---
67
+ 二、新建 src/agents.ts — Agent 发现与加载
68
+
69
+ 从 examples/extensions/subagent/agents.ts 提取核心逻辑,适配 pipiclaw 的目录结构:
70
+
71
+ 搜索路径(优先级从低到高):
72
+ ~/.pi/agents/*.md → 用户全局 Agent 定义
73
+ {workspaceDir}/.pi/agents/*.md → 项目级 Agent 定义(覆盖同名)
74
+
75
+ 这个文件基本可以直接复用 examples/extensions/subagent/agents.ts 的 discoverAgents() +
76
+ loadAgentsFromDir() + frontmatter 解析逻辑,只需要:
77
+ - 去掉对 getAgentDir() 的依赖(pipiclaw 有自己的目录约定)
78
+ - 把 findNearestProjectAgentsDir 简化为直接用已知的 workspaceDir
79
+
80
+ Agent 定义文件格式(与现有一致):
81
+
82
+ ---
83
+ name: reviewer
84
+ description: Reviews code changes for quality and correctness
85
+ model: claude-sonnet-4-20250514
86
+ tools: bash,read
87
+ ---
88
+
89
+ You are a code reviewer. Given a task, review the relevant code...
90
+
91
+ ---
92
+ 三、修改 src/tools/index.ts — 工具注册集成
93
+
94
+ 当前 createPipiclawTools() 返回固定的 4 个工具。需要改为:
95
+
96
+ // 改造前
97
+ export function createPipiclawTools(executor: Executor): AgentTool<any>[] {
98
+ return [createReadTool(executor), createBashTool(executor), createEditTool(executor),
99
+ createWriteTool(executor)];
100
+ }
101
+
102
+ // 改造后
103
+ export function createPipiclawTools(executor: Executor): AgentTool<any>[] {
104
+ return [createReadTool(executor), createBashTool(executor), createEditTool(executor),
105
+ createWriteTool(executor)];
106
+ }
107
+
108
+ // 新增:按名称过滤工具子集(供 sub-agent 使用)
109
+ export function filterToolsByName(allTools: AgentTool<any>[], names: string[]): AgentTool<any>[] {
110
+ const nameSet = new Set(names);
111
+ return allTools.filter(t => nameSet.has(t.name));
112
+ }
113
+
114
+ // 新增:创建包含 subagent 的完整工具集(供主 Agent 使用)
115
+ export function createPipiclawToolsWithSubAgent(
116
+ executor: Executor,
117
+ subagentOptions: SubAgentToolOptions,
118
+ ): AgentTool<any>[] {
119
+ const baseTools = createPipiclawTools(executor);
120
+ const subagentTool = createSubAgentTool(subagentOptions, baseTools);
121
+ return [...baseTools, subagentTool];
122
+ }
123
+
124
+ 关键设计决策:sub-agent 不能递归调用 subagent 工具。 createSubAgentTool 内部只传入 baseTools(4
125
+ 个基础工具),不包含 subagent 自身,天然防止递归。
126
+
127
+ ---
128
+ 四、修改 src/agent.ts (ChannelRunner) — 接入主流程
129
+
130
+ 在 ChannelRunner 构造器中,将 subagent 工具加入主 Agent 的工具集:
131
+
132
+ // agent.ts 中现有的工具创建:
133
+ // const tools = createPipiclawTools(executor);
134
+
135
+ // 改为:
136
+ const baseTools = createPipiclawTools(executor);
137
+ const subagentTool = createSubAgentTool({
138
+ executor,
139
+ getModel: () => this.activeModel,
140
+ resolveApiKey: (model) => getApiKeyForModel(this.modelRegistry, model),
141
+ workspaceDir: this.workspaceDir,
142
+ channelDir,
143
+ }, baseTools);
144
+ const tools = [...baseTools, subagentTool];
145
+
146
+ 变更范围极小 — 只改工具数组的组装方式,Agent 构造、AgentSession 构造、Extension 注册等全部不变。
147
+
148
+ ---
149
+ 五、修改 src/prompt-builder.ts — 告诉 LLM 如何使用 sub-agent
150
+
151
+ 在 buildAppendSystemPrompt() 中新增一个 section,告知主 Agent sub-agent 的存在和使用策略:
152
+
153
+ ## Sub-Agents
154
+
155
+ You have a `subagent` tool that delegates tasks to specialized agents.
156
+ Available agents are discovered from `.pi/agents/*.md` files.
157
+
158
+ Use sub-agents when:
159
+ - The task can be decomposed into independent sub-problems
160
+ - You need a fresh context window for a heavy computation
161
+ - A specialized agent (reviewer, researcher) would produce better results
162
+ - The current context is getting long and you want to offload work
163
+
164
+ Do NOT use sub-agents for:
165
+ - Simple questions or short tasks
166
+ - Tasks that heavily depend on current conversation context
167
+ - Interactive tasks requiring user confirmation
168
+
169
+ Each sub-agent runs with an isolated context — it cannot see your conversation history.
170
+ You must provide sufficient context in the `task` parameter.
171
+
172
+ 同时,如果 workspace 下存在 .pi/agents/ 目录,可以在 prompt 中列出可用 Agent 名称和描述,帮助 LLM
173
+ 决策。
174
+
175
+ ---
176
+ 数据流总览
177
+
178
+ 用户 (DingTalk)
179
+
180
+
181
+ 主 Agent (ChannelRunner)
182
+ │ tools: [read, bash, edit, write, subagent]
183
+
184
+ │ LLM 决定调用 subagent({agent: "reviewer", task: "Review PR #42"})
185
+
186
+
187
+ subagent tool execute()
188
+
189
+ ├─ discoverAgents(workspaceDir) → 找到 "reviewer" 配置
190
+
191
+ ├─ filterToolsByName(baseTools, ["bash", "read"]) → 子工具集
192
+
193
+ ├─ new Agent({
194
+ │ systemPrompt: reviewer.systemPrompt,
195
+ │ model: reviewer.model ?? parentModel,
196
+ │ tools: filteredTools,
197
+ │ thinkingLevel: "off",
198
+ │ })
199
+
200
+ ├─ agent.subscribe(event => { 收集 message_end 事件 })
201
+
202
+ ├─ await agent.prompt(task)
203
+ ├─ await agent.waitForIdle()
204
+
205
+ ├─ 提取最终文本输出
206
+
207
+ └─ return { content: [{type: "text", text: output}] }
208
+
209
+
210
+ 主 Agent 收到 tool result,继续推理
211
+
212
+ ---
213
+ 需要注意的设计要点
214
+
215
+ 1. 工具实例共享安全性
216
+
217
+ sub-agent 和主 Agent 共享同一个 Executor 实例。这是安全的,因为:
218
+ - Executor.exec() 是无状态的(每次 spawn 新进程)
219
+ - Docker sandbox 本身提供了文件系统隔离
220
+ - Host 模式下两者操作相同的工作目录,这是预期行为
221
+
222
+ 2. API Key 与 Model 解析
223
+
224
+ sub-agent 可以指定不同的 model(如主 Agent 用 opus,子 Agent 用 sonnet 降低成本)。API key
225
+ 通过同一个 ModelRegistry 解析,不需要额外配置。
226
+
227
+ 3. Token 消耗可见性
228
+
229
+ sub-agent 的 token 消耗不会自动体现在主 Agent 的 usage 统计中。可以在 tool result 的 details
230
+ 中附带 usage 信息,并在 /session 命令中展示。这个是增强项,不阻塞 Phase 1。
231
+
232
+ 4. 超时与取消
233
+
234
+ - 主 Agent 的 AbortSignal 通过 tool execute 的 signal 参数传入
235
+ - sub-agent 创建时用同一个 signal,或创建子 AbortController 链接到父 signal
236
+ - DingTalk 用户发新消息中断时,整条链路都能正确取消
237
+
238
+ 5. Phase 2 扩展路径
239
+
240
+ Phase 1 只做 single 模式。后续扩展:
241
+ - Parallel 模式:schema 加 tasks 数组,复用 mapWithConcurrencyLimit 模式,并发创建多个 Agent
242
+ - Chain 模式:schema 加 chain 数组,顺序执行,{previous} 占位符替换
243
+ - Streaming 更新:通过 onUpdate 回调实时推送子 Agent 进度到 DingTalk 卡片
244
+
245
+ ---
246
+ 文件变更清单
247
+
248
+
249
+ ┌───────────────────────┬──────┬────────────────────────────────────────────────┐
250
+ │ 文件 │ 操作 │ 说明 │
251
+ ├───────────────────────┼──────┼────────────────────────────────────────────────┤
252
+ │ src/tools/subagent.ts │ 新建 │ Sub-agent 工具核心实现 │
253
+ ├───────────────────────┼──────┼────────────────────────────────────────────────┤
254
+ │ src/agents.ts │ 新建 │ Agent 发现与加载(从 examples 提取) │
255
+ ├───────────────────────┼──────┼────────────────────────────────────────────────┤
256
+ │ src/tools/index.ts │ 修改 │ 新增 filterToolsByName,导出 subagent 相关函数 │
257
+ ├───────────────────────┼──────┼────────────────────────────────────────────────┤
258
+ │ src/agent.ts │ 修改 │ ChannelRunner 中组装工具时加入 subagent │
259
+ ├───────────────────────┼──────┼────────────────────────────────────────────────┤
260
+ │ src/prompt-builder.ts │ 修改 │ 新增 sub-agent 使用指南 section │
261
+ └───────────────────────┴──────┴────────────────────────────────────────────────┘
262
+
263
+ 不需要改动的部分: AgentSession 构造、Extension
264
+ 系统、MemoryLifecycle、DingTalkBot、DeliveryController、Store、Sandbox — 全部保持不变。
265
+
266
+ 这个方案的核心优势是侵入性极低:本质上就是多注册了一个 tool,其余架构完全不动。