foliko 2.0.30 → 2.0.31
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/docs/plugin-dev-guide.md +850 -0
- package/package.json +1 -1
- package/src/agent/tool-loop.js +19 -18
|
@@ -0,0 +1,850 @@
|
|
|
1
|
+
# Plugin 开发指南
|
|
2
|
+
|
|
3
|
+
> 本指南详细说明如何在 Foliko 框架中开发插件。
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 目录
|
|
8
|
+
|
|
9
|
+
- [1. 概述](#1-概述)
|
|
10
|
+
- [2. 插件存放位置](#2-插件存放位置)
|
|
11
|
+
- [3. 用户插件开发](#3-用户插件开发)
|
|
12
|
+
- [4. 内置插件开发](#4-内置插件开发)
|
|
13
|
+
- [5. 插件属性](#5-插件属性)
|
|
14
|
+
- [6. 生命周期](#6-生命周期)
|
|
15
|
+
- [7. 工具注册](#7-工具注册)
|
|
16
|
+
- [8. 扩展工具](#8-扩展工具)
|
|
17
|
+
- [9. 子 Agent](#9-子-agent)
|
|
18
|
+
- [10. 工作流](#10-工作流)
|
|
19
|
+
- [11. Prompt Parts](#11-prompt-parts)
|
|
20
|
+
- [12. Skill 管理](#12-skill-管理)
|
|
21
|
+
- [13. 依赖管理](#13-依赖管理)
|
|
22
|
+
- [14. 系统插件访问](#14-系统插件访问)
|
|
23
|
+
- [15. 最佳实践](#15-最佳实践)
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## 1. 概述
|
|
28
|
+
|
|
29
|
+
Foliko 是一个基于插件的 Agent 框架,核心保持简单,通过插件扩展功能。
|
|
30
|
+
|
|
31
|
+
### 插件 vs 技能
|
|
32
|
+
|
|
33
|
+
| 类型 | 存储位置 | 说明 |
|
|
34
|
+
|------|----------|------|
|
|
35
|
+
| **Plugin(插件)** | `.foliko/plugins/` | 扩展功能模块 |
|
|
36
|
+
| **Skill(技能)** | `.foliko/skills/` | 为 Agent 提供指导的知识包 |
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## 2. 插件存放位置
|
|
41
|
+
|
|
42
|
+
### 用户插件 `.foliko/plugins/`
|
|
43
|
+
|
|
44
|
+
用户自定义插件放在项目根目录的 `.foliko/plugins/` 下,**自动加载**,无需手动注册。
|
|
45
|
+
|
|
46
|
+
**推荐使用文件夹结构**(适合复杂插件):
|
|
47
|
+
|
|
48
|
+
```
|
|
49
|
+
项目目录/
|
|
50
|
+
└── .foliko/
|
|
51
|
+
└── plugins/
|
|
52
|
+
├── my-plugin/ ✅ 文件夹结构(推荐)
|
|
53
|
+
│ ├── package.json # 可选,main 字段指定入口
|
|
54
|
+
│ ├── index.js # 默认入口
|
|
55
|
+
│ └── node_modules/ # 可选,插件私有依赖
|
|
56
|
+
├── another-plugin/ ✅ 另一个文件夹插件
|
|
57
|
+
│ └── index.js
|
|
58
|
+
└── legacy.js ✅ 单文件仍支持
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### 内置插件 `plugins/`
|
|
62
|
+
|
|
63
|
+
框架内置插件位于仓库根目录的 `plugins/` 目录,需要 `require` Plugin 基类。
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## 3. 用户插件开发
|
|
68
|
+
|
|
69
|
+
### 文件夹结构(推荐)
|
|
70
|
+
|
|
71
|
+
```
|
|
72
|
+
.foliko/plugins/my-plugin/
|
|
73
|
+
├── package.json # 可选
|
|
74
|
+
└── index.js # 入口
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
```javascript
|
|
78
|
+
// .foliko/plugins/my-plugin/index.js
|
|
79
|
+
const { z } = require('zod');
|
|
80
|
+
|
|
81
|
+
module.exports = function (Plugin) {
|
|
82
|
+
return class MyPlugin extends Plugin {
|
|
83
|
+
constructor(config = {}) {
|
|
84
|
+
super();
|
|
85
|
+
this.name = 'my-plugin';
|
|
86
|
+
this.version = '1.0.0';
|
|
87
|
+
this.description = '我的工具插件';
|
|
88
|
+
this.priority = 10;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// 方式1:声明式工具定义
|
|
92
|
+
tools = {
|
|
93
|
+
my_tool: {
|
|
94
|
+
description: '我的工具',
|
|
95
|
+
inputSchema: z.object({
|
|
96
|
+
param: z.string().describe('参数描述'),
|
|
97
|
+
}),
|
|
98
|
+
execute: async (args) => {
|
|
99
|
+
return { success: true, result: args.param };
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
// 方式2:生命周期方法
|
|
105
|
+
onStart(framework) {
|
|
106
|
+
// 注册额外工具
|
|
107
|
+
this.tool.register({
|
|
108
|
+
name: 'another_tool',
|
|
109
|
+
description: '另一个工具',
|
|
110
|
+
inputSchema: z.object({}),
|
|
111
|
+
execute: async (args) => ({ success: true }),
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
uninstall(framework) {
|
|
116
|
+
// 清理资源
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
};
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### 单文件结构(仍支持)
|
|
123
|
+
|
|
124
|
+
```javascript
|
|
125
|
+
// .foliko/plugins/my-plugin.js
|
|
126
|
+
const { z } = require('zod');
|
|
127
|
+
|
|
128
|
+
module.exports = function (Plugin) {
|
|
129
|
+
return class MyPlugin extends Plugin {
|
|
130
|
+
constructor(config = {}) {
|
|
131
|
+
super();
|
|
132
|
+
this.name = 'my-plugin';
|
|
133
|
+
this.version = '1.0.0';
|
|
134
|
+
this.description = '我的工具插件';
|
|
135
|
+
this.priority = 10;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
tools = {
|
|
139
|
+
my_tool: {
|
|
140
|
+
description: '我的工具',
|
|
141
|
+
inputSchema: z.object({
|
|
142
|
+
param: z.string().describe('参数描述'),
|
|
143
|
+
}),
|
|
144
|
+
execute: async (args) => {
|
|
145
|
+
return { success: true, result: args.param };
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
onStart(framework) {}
|
|
151
|
+
uninstall(framework) {}
|
|
152
|
+
};
|
|
153
|
+
};
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
**注意**:如果文件夹和同名 `.js` 文件同时存在,**文件夹优先**。
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
## 4. 内置插件开发
|
|
161
|
+
|
|
162
|
+
内置插件需要显式引入 Plugin 基类:
|
|
163
|
+
|
|
164
|
+
```javascript
|
|
165
|
+
// plugins/my-plugin.js
|
|
166
|
+
const { Plugin } = require('../src/plugin/base');
|
|
167
|
+
const { z } = require('zod');
|
|
168
|
+
|
|
169
|
+
class MyPlugin extends Plugin {
|
|
170
|
+
constructor(config = {}) {
|
|
171
|
+
super();
|
|
172
|
+
this.name = 'my-plugin';
|
|
173
|
+
this.version = '1.0.0';
|
|
174
|
+
this.description = '我的工具插件';
|
|
175
|
+
this.priority = 10;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
tools = {
|
|
179
|
+
my_tool: {
|
|
180
|
+
description: '我的工具',
|
|
181
|
+
inputSchema: z.object({
|
|
182
|
+
param: z.string().describe('参数描述'),
|
|
183
|
+
}),
|
|
184
|
+
execute: async (args) => {
|
|
185
|
+
return { success: true, result: args.param };
|
|
186
|
+
},
|
|
187
|
+
},
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
onStart(framework) {
|
|
191
|
+
// 注册额外工具
|
|
192
|
+
this.tool.register({
|
|
193
|
+
name: 'another_tool',
|
|
194
|
+
description: '另一个工具',
|
|
195
|
+
inputSchema: z.object({}),
|
|
196
|
+
execute: async (args) => ({ success: true }),
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
uninstall(framework) {}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
module.exports = MyPlugin;
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
---
|
|
207
|
+
|
|
208
|
+
## 5. 插件属性
|
|
209
|
+
|
|
210
|
+
| 属性 | 类型 | 必须 | 说明 |
|
|
211
|
+
|------|------|------|------|
|
|
212
|
+
| `name` | string | ✅ | 唯一名称 |
|
|
213
|
+
| `version` | string | ❌ | 版本号,默认 '1.0.0' |
|
|
214
|
+
| `description` | string | ❌ | 插件描述 |
|
|
215
|
+
| `priority` | number | ❌ | 加载优先级,默认 100 |
|
|
216
|
+
| `tools` | object | ❌ | 声明式工具定义 |
|
|
217
|
+
| `agents` | array | ❌ | 声明式子 Agent 定义 |
|
|
218
|
+
| `prompts` | array | ❌ | 声明式 prompt parts |
|
|
219
|
+
| `enabled` | boolean | ❌ | 是否启用,默认 true |
|
|
220
|
+
| `system` | boolean | ❌ | 是否系统插件,默认 false |
|
|
221
|
+
|
|
222
|
+
---
|
|
223
|
+
|
|
224
|
+
## 6. 生命周期
|
|
225
|
+
|
|
226
|
+
| 方法 | 调用时机 | 必须实现 | 说明 |
|
|
227
|
+
|------|----------|----------|------|
|
|
228
|
+
| `install(framework)` | 插件安装时 | ✅ | 初始化,获取 framework |
|
|
229
|
+
| `onStart(framework)` | start/enable/reload 时 | 推荐 | 注册工具、扩展、子代理 |
|
|
230
|
+
| `onStop()` | disable 时 | 可选 | 清理资源 |
|
|
231
|
+
| `reload(framework)` | 热重载时 | 可选 | prompts 自动清理/注册 |
|
|
232
|
+
| `uninstall(framework)` | 卸载时 | 推荐 | 清理资源 |
|
|
233
|
+
|
|
234
|
+
### 生命周期流程
|
|
235
|
+
|
|
236
|
+
```
|
|
237
|
+
install() → onStart() → (reload()) → onStop() → uninstall()
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
### onStart / onStop(推荐方式)
|
|
241
|
+
|
|
242
|
+
**推荐使用 `onStart` 注册工具和扩展**,基类自动处理生命周期:
|
|
243
|
+
|
|
244
|
+
```javascript
|
|
245
|
+
class MyPlugin extends Plugin {
|
|
246
|
+
name = 'my-plugin';
|
|
247
|
+
|
|
248
|
+
onStart(framework) {
|
|
249
|
+
// 注册 AI SDK 工具(直接调用)
|
|
250
|
+
this.tool.register({
|
|
251
|
+
name: 'hello',
|
|
252
|
+
description: '打招呼',
|
|
253
|
+
inputSchema: framework.z.object({
|
|
254
|
+
name: framework.z.string().describe('姓名')
|
|
255
|
+
}),
|
|
256
|
+
execute: async (args) => ({ message: `Hello, ${args.name}!` }),
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
// 注册扩展工具(通过 ext_call 调用)
|
|
260
|
+
this.extension.register({
|
|
261
|
+
name: 'greet_ext',
|
|
262
|
+
description: '打招呼扩展',
|
|
263
|
+
inputSchema: framework.z.object({
|
|
264
|
+
name: framework.z.string()
|
|
265
|
+
}),
|
|
266
|
+
execute: async (args) => ({ message: `Hi, ${args.name}!` }),
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
onStop() {
|
|
271
|
+
// 清理资源(如果需要)
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
### 生命周期自动管理
|
|
277
|
+
|
|
278
|
+
| 操作 | 效果 |
|
|
279
|
+
|------|------|
|
|
280
|
+
| `disable` | 自动移除工具/扩展(保留注册记录) |
|
|
281
|
+
| `enable` | 自动重新注册工具/扩展 |
|
|
282
|
+
| `reload` | 移除后重新注册 |
|
|
283
|
+
| `uninstall` | 移除并清空注册记录 |
|
|
284
|
+
|
|
285
|
+
---
|
|
286
|
+
|
|
287
|
+
## 7. 工具注册
|
|
288
|
+
|
|
289
|
+
### 方式1:`this.tools = {}`(声明式)
|
|
290
|
+
|
|
291
|
+
插件将工具定义在 `this.tools = {}` 中,ExtensionExecutor 会自动扫描并注册:
|
|
292
|
+
|
|
293
|
+
```javascript
|
|
294
|
+
class MyPlugin extends Plugin {
|
|
295
|
+
name = 'my-plugin';
|
|
296
|
+
|
|
297
|
+
tools = {
|
|
298
|
+
my_tool: {
|
|
299
|
+
description: '我的工具',
|
|
300
|
+
inputSchema: z.object({
|
|
301
|
+
param: z.string().describe('参数描述'),
|
|
302
|
+
}),
|
|
303
|
+
execute: async (args) => {
|
|
304
|
+
return { success: true, result: args.param };
|
|
305
|
+
},
|
|
306
|
+
},
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
### 方式2:`this.tool.register()`(推荐)
|
|
312
|
+
|
|
313
|
+
在 `onStart` 中使用,支持完整的生命周期管理:
|
|
314
|
+
|
|
315
|
+
```javascript
|
|
316
|
+
onStart(framework) {
|
|
317
|
+
this.tool.register({
|
|
318
|
+
name: 'my_tool',
|
|
319
|
+
description: '我的工具',
|
|
320
|
+
inputSchema: framework.z.object({
|
|
321
|
+
param: framework.z.string().describe('参数描述')
|
|
322
|
+
}),
|
|
323
|
+
execute: async (args) => {
|
|
324
|
+
return { success: true, result: args.param };
|
|
325
|
+
}
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
### 方式3:`this.registerTool()`(旧方式)
|
|
331
|
+
|
|
332
|
+
```javascript
|
|
333
|
+
install(framework) {
|
|
334
|
+
this.registerTool('my_tool', {
|
|
335
|
+
description: '我的工具',
|
|
336
|
+
inputSchema: framework.z.object({
|
|
337
|
+
param: framework.z.string().describe('参数描述')
|
|
338
|
+
}),
|
|
339
|
+
execute: async (args) => ({ success: true })
|
|
340
|
+
});
|
|
341
|
+
return this;
|
|
342
|
+
}
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
### 工具操作
|
|
346
|
+
|
|
347
|
+
```javascript
|
|
348
|
+
// 注册
|
|
349
|
+
this.tool.register({ name: 'xxx', ... });
|
|
350
|
+
|
|
351
|
+
// 移除
|
|
352
|
+
this.tool.remove('xxx');
|
|
353
|
+
|
|
354
|
+
// 执行
|
|
355
|
+
const result = await this.tool.execute('any-tool', { arg: 'value' });
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
---
|
|
359
|
+
|
|
360
|
+
## 8. 扩展工具
|
|
361
|
+
|
|
362
|
+
扩展工具通过 `ext_call` 间接调用,适合需要额外封装的工具。
|
|
363
|
+
|
|
364
|
+
### 注册扩展工具
|
|
365
|
+
|
|
366
|
+
```javascript
|
|
367
|
+
onStart(framework) {
|
|
368
|
+
this.extension.register({
|
|
369
|
+
name: 'my_ext_tool',
|
|
370
|
+
description: '扩展工具',
|
|
371
|
+
inputSchema: framework.z.object({
|
|
372
|
+
param: framework.z.string()
|
|
373
|
+
}),
|
|
374
|
+
execute: async (args) => ({ success: true, data: args.param })
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
### 调用扩展工具
|
|
380
|
+
|
|
381
|
+
```javascript
|
|
382
|
+
// 通过 ext_call 调用
|
|
383
|
+
const result = await ext_call({
|
|
384
|
+
plugin: 'my-plugin',
|
|
385
|
+
tool: 'my_ext_tool',
|
|
386
|
+
args: { param: 'value' }
|
|
387
|
+
});
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
### 扩展工具操作
|
|
391
|
+
|
|
392
|
+
```javascript
|
|
393
|
+
// 注册
|
|
394
|
+
this.extension.register({ name: 'xxx', ... });
|
|
395
|
+
|
|
396
|
+
// 移除
|
|
397
|
+
this.extension.remove('xxx');
|
|
398
|
+
|
|
399
|
+
// 执行(当前插件)
|
|
400
|
+
const r1 = await this.extension.execute('xxx', { arg: 'value' });
|
|
401
|
+
|
|
402
|
+
// 执行(指定插件)
|
|
403
|
+
const r2 = await this.extension.execute('plugin-name', 'xxx', { arg: 'value' });
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
---
|
|
407
|
+
|
|
408
|
+
## 9. 子 Agent
|
|
409
|
+
|
|
410
|
+
### 配置式注册
|
|
411
|
+
|
|
412
|
+
```javascript
|
|
413
|
+
class MyPlugin extends Plugin {
|
|
414
|
+
name = 'my-plugin';
|
|
415
|
+
|
|
416
|
+
agents = [
|
|
417
|
+
{
|
|
418
|
+
name: 'code-agent',
|
|
419
|
+
role: '代码专家',
|
|
420
|
+
description: '处理代码开发任务',
|
|
421
|
+
tools: {
|
|
422
|
+
compile: {
|
|
423
|
+
description: '编译代码',
|
|
424
|
+
inputSchema: z.object({
|
|
425
|
+
language: z.string(),
|
|
426
|
+
code: z.string(),
|
|
427
|
+
}),
|
|
428
|
+
execute: async (args) => ({ success: true }),
|
|
429
|
+
},
|
|
430
|
+
},
|
|
431
|
+
parentTools: ['read_file', 'write_file'],
|
|
432
|
+
},
|
|
433
|
+
];
|
|
434
|
+
}
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
### 手动注册
|
|
438
|
+
|
|
439
|
+
```javascript
|
|
440
|
+
onStart(framework) {
|
|
441
|
+
this.agent.register({
|
|
442
|
+
name: 'code-agent',
|
|
443
|
+
role: '代码专家',
|
|
444
|
+
description: '处理代码开发任务',
|
|
445
|
+
tools: {
|
|
446
|
+
compile: {
|
|
447
|
+
description: '编译代码',
|
|
448
|
+
inputSchema: framework.z.object({
|
|
449
|
+
language: framework.z.string(),
|
|
450
|
+
code: framework.z.string(),
|
|
451
|
+
}),
|
|
452
|
+
execute: async (args) => ({ success: true }),
|
|
453
|
+
},
|
|
454
|
+
},
|
|
455
|
+
parentTools: ['read_file', 'write_file'],
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
### 子 Agent 配置说明
|
|
461
|
+
|
|
462
|
+
| 字段 | 类型 | 说明 |
|
|
463
|
+
|------|------|------|
|
|
464
|
+
| `name` | string | 子 Agent 名称(唯一标识) |
|
|
465
|
+
| `role` | string | 角色描述 |
|
|
466
|
+
| `description` | string | 详细描述 |
|
|
467
|
+
| `tools` | object | 自定义工具,只属于此子 Agent |
|
|
468
|
+
| `parentTools` | array | 从父 Agent 继承的工具名称列表 |
|
|
469
|
+
|
|
470
|
+
### 子 Agent 操作
|
|
471
|
+
|
|
472
|
+
```javascript
|
|
473
|
+
// 获取
|
|
474
|
+
const agent = this.agent.get('my-agent');
|
|
475
|
+
|
|
476
|
+
// 移除
|
|
477
|
+
this.agent.remove('my-agent');
|
|
478
|
+
```
|
|
479
|
+
|
|
480
|
+
---
|
|
481
|
+
|
|
482
|
+
## 10. 工作流
|
|
483
|
+
|
|
484
|
+
### 注册工作流
|
|
485
|
+
|
|
486
|
+
```javascript
|
|
487
|
+
onStart(framework) {
|
|
488
|
+
this.workflow.register('my-workflow', {
|
|
489
|
+
name: 'my-workflow',
|
|
490
|
+
description: '我的工作流',
|
|
491
|
+
inputSchema: framework.z.object({
|
|
492
|
+
msg: framework.z.string()
|
|
493
|
+
}),
|
|
494
|
+
steps: [
|
|
495
|
+
{ type: 'tool', tool: 'hello', args: { name: '{{inputs.msg}}' } },
|
|
496
|
+
{ type: 'action', code: 'return { result: inputs.msg + "!" }' }
|
|
497
|
+
]
|
|
498
|
+
});
|
|
499
|
+
}
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
### 工作流操作
|
|
503
|
+
|
|
504
|
+
```javascript
|
|
505
|
+
// 执行
|
|
506
|
+
const result = await this.workflow.execute('my-workflow', { msg: 'hello' });
|
|
507
|
+
|
|
508
|
+
// 移除
|
|
509
|
+
this.workflow.remove('my-workflow');
|
|
510
|
+
```
|
|
511
|
+
|
|
512
|
+
---
|
|
513
|
+
|
|
514
|
+
## 11. Prompt Parts
|
|
515
|
+
|
|
516
|
+
### 配置式注册
|
|
517
|
+
|
|
518
|
+
```javascript
|
|
519
|
+
class MyPlugin extends Plugin {
|
|
520
|
+
name = 'my-plugin';
|
|
521
|
+
|
|
522
|
+
prompts = [
|
|
523
|
+
{
|
|
524
|
+
name: 'my-prompt',
|
|
525
|
+
scope: 'global', // 'global' | 'main' | agentId
|
|
526
|
+
priority: 50, // 数字越小越靠前
|
|
527
|
+
description: '我的提示词',
|
|
528
|
+
provider() {
|
|
529
|
+
return '这是动态生成的提示词内容';
|
|
530
|
+
},
|
|
531
|
+
},
|
|
532
|
+
];
|
|
533
|
+
}
|
|
534
|
+
```
|
|
535
|
+
|
|
536
|
+
### 手动注册
|
|
537
|
+
|
|
538
|
+
```javascript
|
|
539
|
+
onStart(framework) {
|
|
540
|
+
this.prompt.register('my-prompt', () => 'Prompt content', {
|
|
541
|
+
scope: 'global',
|
|
542
|
+
priority: 50
|
|
543
|
+
});
|
|
544
|
+
}
|
|
545
|
+
```
|
|
546
|
+
|
|
547
|
+
### Prompt 操作
|
|
548
|
+
|
|
549
|
+
```javascript
|
|
550
|
+
// 移除
|
|
551
|
+
this.prompt.remove('my-prompt');
|
|
552
|
+
```
|
|
553
|
+
|
|
554
|
+
---
|
|
555
|
+
|
|
556
|
+
## 12. Skill 管理
|
|
557
|
+
|
|
558
|
+
### 注册 Skill
|
|
559
|
+
|
|
560
|
+
```javascript
|
|
561
|
+
onStart(framework) {
|
|
562
|
+
this.skill.register(
|
|
563
|
+
'my-skill',
|
|
564
|
+
'# 我的技能\n\n这是一个技能说明',
|
|
565
|
+
{
|
|
566
|
+
description: '技能描述',
|
|
567
|
+
tags: ['test'],
|
|
568
|
+
},
|
|
569
|
+
{
|
|
570
|
+
commands: [
|
|
571
|
+
{
|
|
572
|
+
name: 'do',
|
|
573
|
+
description: '执行操作',
|
|
574
|
+
options: [
|
|
575
|
+
{ flags: '-x <x>', description: '参数', required: true },
|
|
576
|
+
],
|
|
577
|
+
execute: async (args) => ({ x: args.x }),
|
|
578
|
+
},
|
|
579
|
+
],
|
|
580
|
+
}
|
|
581
|
+
);
|
|
582
|
+
}
|
|
583
|
+
```
|
|
584
|
+
|
|
585
|
+
### Skill 操作
|
|
586
|
+
|
|
587
|
+
```javascript
|
|
588
|
+
// 检查是否存在
|
|
589
|
+
this.skill.has('my-skill');
|
|
590
|
+
|
|
591
|
+
// 获取详情
|
|
592
|
+
this.skill.get('my-skill');
|
|
593
|
+
this.skill.details('my-skill');
|
|
594
|
+
|
|
595
|
+
// 列出
|
|
596
|
+
this.skill.list();
|
|
597
|
+
this.skill.listOwned(); // 本插件注册的
|
|
598
|
+
|
|
599
|
+
// 执行
|
|
600
|
+
await this.skill.execute('my-skill:do', { x: 1 });
|
|
601
|
+
|
|
602
|
+
// 移除
|
|
603
|
+
this.skill.remove('my-skill');
|
|
604
|
+
```
|
|
605
|
+
|
|
606
|
+
---
|
|
607
|
+
|
|
608
|
+
## 13. 依赖管理
|
|
609
|
+
|
|
610
|
+
### 依赖安装位置
|
|
611
|
+
|
|
612
|
+
| 插件类型 | 安装位置 | 说明 |
|
|
613
|
+
|----------|----------|------|
|
|
614
|
+
| **文件夹插件** | `.foliko/plugins/插件名/node_modules/` | 插件自包含,可独立迁移 |
|
|
615
|
+
| **单文件插件** | `.foliko/node_modules/` | 共享依赖 |
|
|
616
|
+
|
|
617
|
+
### 安装工具
|
|
618
|
+
|
|
619
|
+
```javascript
|
|
620
|
+
// 安装到默认位置 .foliko/node_modules
|
|
621
|
+
install({ package: 'zod' });
|
|
622
|
+
|
|
623
|
+
// 安装到指定目录(文件夹插件)
|
|
624
|
+
install({ package: 'axios', path: '.foliko/plugins/my-plugin' });
|
|
625
|
+
|
|
626
|
+
// 从 package.json 安装
|
|
627
|
+
install({ file: './my-plugin/package.json', path: '.foliko/plugins/my-plugin' });
|
|
628
|
+
```
|
|
629
|
+
|
|
630
|
+
### 重要依赖版本要求
|
|
631
|
+
|
|
632
|
+
**zod 必须使用 `"zod": "^3.25.76"` 版本**
|
|
633
|
+
|
|
634
|
+
---
|
|
635
|
+
|
|
636
|
+
## 14. 系统插件访问
|
|
637
|
+
|
|
638
|
+
### 可访问的系统插件
|
|
639
|
+
|
|
640
|
+
| 插件名 | 说明 | 可用方法 |
|
|
641
|
+
|--------|------|----------|
|
|
642
|
+
| `storage` | 键值对存储 | `get(key)`, `set(key, value)`, `delete(key)` |
|
|
643
|
+
| `session` | 会话管理 | `getSession(id)`, `getHistory(id)` |
|
|
644
|
+
| `audit` | 审计日志 | `log(type, data)` |
|
|
645
|
+
| `scheduler` | 定时任务 | `addTask()`, `removeTask()` |
|
|
646
|
+
| `file-system` | 文件操作 | 工具已注册: `read_file`, `write_file` |
|
|
647
|
+
| `tools` | 工具管理 | `list()`, `reload()` |
|
|
648
|
+
| `ai` | AI 配置 | `getAIClient()` |
|
|
649
|
+
|
|
650
|
+
### 访问示例
|
|
651
|
+
|
|
652
|
+
```javascript
|
|
653
|
+
install(framework) {
|
|
654
|
+
// 访问 storage 插件
|
|
655
|
+
const storage = this._framework.pluginManager.get('storage');
|
|
656
|
+
|
|
657
|
+
// 访问 session 插件
|
|
658
|
+
const session = this._framework.pluginManager.get('session');
|
|
659
|
+
|
|
660
|
+
// 访问 audit 插件
|
|
661
|
+
const audit = this._framework.pluginManager.get('audit');
|
|
662
|
+
}
|
|
663
|
+
```
|
|
664
|
+
|
|
665
|
+
### Storage 插件 API
|
|
666
|
+
|
|
667
|
+
```javascript
|
|
668
|
+
const storage = this._framework.pluginManager.get('storage');
|
|
669
|
+
|
|
670
|
+
// 方式1:通过工具调用(推荐)
|
|
671
|
+
// storage_get({ key: 'xxx', namespace: 'yyy' })
|
|
672
|
+
// storage_set({ key: 'xxx', value: {...}, namespace: 'yyy' })
|
|
673
|
+
|
|
674
|
+
// 方式2:直接调用内部方法
|
|
675
|
+
const value = storage.getStore().get('key');
|
|
676
|
+
storage.setDirect('key', value);
|
|
677
|
+
storage.deleteDirect('key');
|
|
678
|
+
```
|
|
679
|
+
|
|
680
|
+
### Session 插件 API
|
|
681
|
+
|
|
682
|
+
```javascript
|
|
683
|
+
const session = this._framework.pluginManager.get('session');
|
|
684
|
+
|
|
685
|
+
// 获取会话
|
|
686
|
+
const sess = session.getSession(sessionId);
|
|
687
|
+
|
|
688
|
+
// 获取历史消息
|
|
689
|
+
const messages = session.getHistory(sessionId);
|
|
690
|
+
|
|
691
|
+
// 添加消息
|
|
692
|
+
session.addMessage(sessionId, { role: 'user', content: 'hello' });
|
|
693
|
+
```
|
|
694
|
+
|
|
695
|
+
---
|
|
696
|
+
|
|
697
|
+
## 15. 最佳实践
|
|
698
|
+
|
|
699
|
+
### 核心规则
|
|
700
|
+
|
|
701
|
+
| 规则 | 说明 |
|
|
702
|
+
|------|------|
|
|
703
|
+
| **优先使用 this.tools = {}** | ExtensionExecutor 自动扫描并注册到系统提示词 |
|
|
704
|
+
| **优先使用文件夹结构** | 复杂插件推荐用文件夹,便于管理依赖和代码 |
|
|
705
|
+
| **必须用 inputSchema** | `inputSchema: z.object({})`(不是 parameters!) |
|
|
706
|
+
| **必须用 zod 定义参数** | `param: z.string().describe('描述')` |
|
|
707
|
+
| **install 必须返回 this** | 确保链式调用 |
|
|
708
|
+
| **文件夹与文件重名时** | 文件夹优先,同名 `.js` 文件会被忽略 |
|
|
709
|
+
| **工具自动出现在系统提示词** | 定义在 `this.tools` 中会自动注册 |
|
|
710
|
+
|
|
711
|
+
### 开发流程
|
|
712
|
+
|
|
713
|
+
1. **创建插件**(优先使用文件夹结构)
|
|
714
|
+
2. **安装依赖**:`install { package: "包名", path: ".foliko/plugins/插件名" }`
|
|
715
|
+
3. **热重载**:`reload_plugins`
|
|
716
|
+
4. **检查加载状态**
|
|
717
|
+
|
|
718
|
+
### 引入第三方库
|
|
719
|
+
|
|
720
|
+
**必须先安装依赖,再热重载!**
|
|
721
|
+
|
|
722
|
+
```javascript
|
|
723
|
+
// .foliko/plugins/my-plugin/index.js
|
|
724
|
+
const { z } = require('zod');
|
|
725
|
+
|
|
726
|
+
module.exports = function (Plugin) {
|
|
727
|
+
return class MyPlugin extends Plugin {
|
|
728
|
+
name = 'my-plugin';
|
|
729
|
+
|
|
730
|
+
tools = {
|
|
731
|
+
fetch_data: {
|
|
732
|
+
description: '获取远程数据',
|
|
733
|
+
inputSchema: z.object({
|
|
734
|
+
url: z.string().describe('API URL'),
|
|
735
|
+
}),
|
|
736
|
+
execute: async (args) => {
|
|
737
|
+
// 安装到插件自己的目录
|
|
738
|
+
await this._framework.callTool('install', {
|
|
739
|
+
package: 'axios',
|
|
740
|
+
path: '.foliko/plugins/my-plugin',
|
|
741
|
+
});
|
|
742
|
+
|
|
743
|
+
// 引入插件目录下的 node_modules
|
|
744
|
+
const axios = require('.foliko/plugins/my-plugin/node_modules/axios');
|
|
745
|
+
const response = await axios.get(args.url);
|
|
746
|
+
return { success: true, data: response.data };
|
|
747
|
+
},
|
|
748
|
+
},
|
|
749
|
+
};
|
|
750
|
+
};
|
|
751
|
+
};
|
|
752
|
+
```
|
|
753
|
+
|
|
754
|
+
### 错误处理
|
|
755
|
+
|
|
756
|
+
```javascript
|
|
757
|
+
onStart(framework) {
|
|
758
|
+
try {
|
|
759
|
+
this.tool.register({ ... });
|
|
760
|
+
} catch (error) {
|
|
761
|
+
framework.logger.error(`[${this.name}] Failed to register tool: ${error.message}`);
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
uninstall(framework) {
|
|
766
|
+
// 确保清理所有资源
|
|
767
|
+
this.tool.remove('my_tool');
|
|
768
|
+
this.extension.remove('my_ext');
|
|
769
|
+
this.agent.remove('my-agent');
|
|
770
|
+
}
|
|
771
|
+
```
|
|
772
|
+
|
|
773
|
+
---
|
|
774
|
+
|
|
775
|
+
## 示例插件
|
|
776
|
+
|
|
777
|
+
### 完整示例:天气插件
|
|
778
|
+
|
|
779
|
+
```javascript
|
|
780
|
+
// .foliko/plugins/weather/index.js
|
|
781
|
+
const { z } = require('zod');
|
|
782
|
+
|
|
783
|
+
module.exports = function (Plugin) {
|
|
784
|
+
return class WeatherPlugin extends Plugin {
|
|
785
|
+
constructor() {
|
|
786
|
+
super();
|
|
787
|
+
this.name = 'weather';
|
|
788
|
+
this.version = '1.0.0';
|
|
789
|
+
this.description = '天气查询插件';
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
tools = {
|
|
793
|
+
get_weather: {
|
|
794
|
+
description: '查询指定城市的天气',
|
|
795
|
+
inputSchema: z.object({
|
|
796
|
+
city: z.string().describe('城市名称'),
|
|
797
|
+
units: z.enum(['celsius', 'fahrenheit']).optional().describe('温度单位,默认 celsius'),
|
|
798
|
+
}),
|
|
799
|
+
execute: async (args) => {
|
|
800
|
+
// 这里应该是真实的 API 调用
|
|
801
|
+
return {
|
|
802
|
+
city: args.city,
|
|
803
|
+
temperature: 25,
|
|
804
|
+
units: args.units || 'celsius',
|
|
805
|
+
condition: 'Sunny',
|
|
806
|
+
};
|
|
807
|
+
},
|
|
808
|
+
},
|
|
809
|
+
};
|
|
810
|
+
|
|
811
|
+
onStart(framework) {
|
|
812
|
+
// 注册额外的工具
|
|
813
|
+
this.tool.register({
|
|
814
|
+
name: 'get_forecast',
|
|
815
|
+
description: '获取天气预报',
|
|
816
|
+
inputSchema: framework.z.object({
|
|
817
|
+
city: framework.z.string().describe('城市名称'),
|
|
818
|
+
days: framework.z.number().min(1).max(7).default(3).describe('预报天数'),
|
|
819
|
+
}),
|
|
820
|
+
execute: async (args) => {
|
|
821
|
+
return {
|
|
822
|
+
city: args.city,
|
|
823
|
+
forecast: Array.from({ length: args.days }, (_, i) => ({
|
|
824
|
+
day: i + 1,
|
|
825
|
+
temp: 20 + Math.floor(Math.random() * 10),
|
|
826
|
+
condition: ['Sunny', 'Cloudy', 'Rainy'][Math.floor(Math.random() * 3)],
|
|
827
|
+
})),
|
|
828
|
+
};
|
|
829
|
+
},
|
|
830
|
+
});
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
onStop() {
|
|
834
|
+
// 清理资源
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
uninstall(framework) {
|
|
838
|
+
// 完全清理
|
|
839
|
+
}
|
|
840
|
+
};
|
|
841
|
+
};
|
|
842
|
+
```
|
|
843
|
+
|
|
844
|
+
---
|
|
845
|
+
|
|
846
|
+
## 相关文档
|
|
847
|
+
|
|
848
|
+
- [扩展系统](./extensions.md) - ext_call、ext_skill 统一调用
|
|
849
|
+
- [Public API](./public-api.md) - 框架公共 API 参考
|
|
850
|
+
- [架构文档](./architecture.md) - 系统架构设计
|
package/package.json
CHANGED
package/src/agent/tool-loop.js
CHANGED
|
@@ -194,21 +194,13 @@ class ToolLoop extends EventEmitter {
|
|
|
194
194
|
api.push({ role: 'user', content: text || '' });
|
|
195
195
|
}
|
|
196
196
|
} else if (msg.role === 'assistant') {
|
|
197
|
-
// ★
|
|
198
|
-
|
|
199
|
-
const
|
|
197
|
+
// ★ 提取 text + tool_calls(兼容 content=string、content=array、顶层 tool_calls 三种格式)
|
|
198
|
+
let text = '';
|
|
199
|
+
const toolCalls = [];
|
|
200
200
|
|
|
201
201
|
if (typeof msg.content === 'string') {
|
|
202
|
-
|
|
203
|
-
// thinking mode 下,无 reasoning 则自动补 placeholder
|
|
204
|
-
const reasonVal = hasReasoningTopLevel || (this._thinkingMode ? '...' : '');
|
|
205
|
-
api.push({ role: 'assistant', content: msg.content || null, tool_calls: msg.tool_calls, ...(reasonVal ? { reasoning_content: reasonVal } : {}) });
|
|
206
|
-
} else {
|
|
207
|
-
api.push({ role: 'assistant', content: msg.content });
|
|
208
|
-
}
|
|
202
|
+
text = msg.content;
|
|
209
203
|
} else if (Array.isArray(msg.content)) {
|
|
210
|
-
let text = '';
|
|
211
|
-
const toolCalls = [];
|
|
212
204
|
for (const part of msg.content) {
|
|
213
205
|
if (!part) continue;
|
|
214
206
|
if (part.type === 'text') text += part.text;
|
|
@@ -223,15 +215,24 @@ class ToolLoop extends EventEmitter {
|
|
|
223
215
|
});
|
|
224
216
|
}
|
|
225
217
|
}
|
|
218
|
+
}
|
|
226
219
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
220
|
+
// ★ 合并顶层 tool_calls(OpenAI 格式,来自 session 文件)
|
|
221
|
+
const existingIds = new Set(toolCalls.map(tc => tc.id));
|
|
222
|
+
if (msg.tool_calls && Array.isArray(msg.tool_calls)) {
|
|
223
|
+
for (const tc of msg.tool_calls) {
|
|
224
|
+
if (!existingIds.has(tc.id)) toolCalls.push(tc);
|
|
225
|
+
existingIds.add(tc.id);
|
|
232
226
|
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (toolCalls.length > 0) {
|
|
230
|
+
// ★ 核心:thinking mode 下,所有带 tool_calls 的消息都必须有 reasoning_content
|
|
231
|
+
// 无条件注入,不依赖 hasReasoningTopLevel 检测
|
|
232
|
+
const reasonVal = msg.reasoning_content || msg.reasoning || (this._thinkingMode ? '...' : '');
|
|
233
|
+
api.push({ role: 'assistant', content: text || null, tool_calls: toolCalls, ...(reasonVal ? { reasoning_content: reasonVal } : {}) });
|
|
233
234
|
} else {
|
|
234
|
-
api.push({ role: 'assistant', content:
|
|
235
|
+
api.push({ role: 'assistant', content: text || '' });
|
|
235
236
|
}
|
|
236
237
|
} else if (msg.role === 'tool') {
|
|
237
238
|
if (Array.isArray(msg.content)) {
|