foliko 2.0.4 → 2.0.6
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/.claude/settings.local.json +4 -1
- package/.cli_default_systemPrompt.md +291 -0
- package/CLAUDE.md +3 -0
- package/README.md +20 -3
- package/docs/architecture.md +34 -2
- package/docs/extensions.md +199 -0
- package/docs/migration.md +100 -0
- package/docs/public-api.md +1180 -6
- package/docs/usage.md +122 -30
- package/package.json +1 -1
- package/plugins/core/audit/index.js +1 -1
- package/plugins/core/default/bootstrap.js +65 -19
- package/plugins/core/python-loader/index.js +43 -25
- package/plugins/core/skill-manager/PROMPT.md +6 -0
- package/plugins/core/skill-manager/index.js +538 -93
- package/plugins/core/sub-agent/PROMPT.md +10 -0
- package/plugins/core/sub-agent/index.js +36 -3
- package/plugins/core/think/index.js +1 -0
- package/plugins/core/workflow/index.js +106 -22
- package/plugins/executors/data-splitter/PROMPT.md +13 -0
- package/plugins/executors/data-splitter/index.js +5 -4
- package/plugins/executors/extension/extension-registry.js +145 -0
- package/plugins/executors/extension/index.js +405 -437
- package/plugins/executors/extension/prompt-builder.js +359 -0
- package/plugins/executors/extension/skill-helper.js +143 -0
- package/plugins/messaging/feishu/index.js +5 -3
- package/plugins/messaging/qq/index.js +6 -4
- package/plugins/messaging/telegram/index.js +6 -3
- package/plugins/messaging/weixin/index.js +5 -3
- package/plugins/tools/PROMPT.md +26 -0
- package/plugins/tools/index.js +6 -5
- package/skills/foliko/AGENTS.md +196 -43
- package/skills/foliko/SKILL.md +157 -28
- package/skills/mcp/SKILL.md +77 -118
- package/skills/plugins/SKILL.md +89 -3
- package/skills/python/SKILL.md +57 -39
- package/skills/skill-guide/SKILL.md +42 -34
- package/skills/workflows/SKILL.md +224 -9
- package/skills/workflows/workflow-troubleshooting/SKILL.md +221 -281
- package/src/agent/chat.js +48 -27
- package/src/agent/main.js +34 -13
- package/src/agent/prompt-registry.js +133 -87
- package/src/agent/prompts/PROMPT.md +3 -0
- package/src/agent/sub.js +1 -1
- package/src/cli/ui/chat-ui-old.js +5 -2
- package/src/cli/ui/chat-ui.js +5 -2
- package/src/common/constants.js +12 -0
- package/src/common/error-capture.js +91 -0
- package/src/common/logger.js +2 -2
- package/src/context/compressor.js +6 -2
- package/src/executors/mcp-executor.js +105 -125
- package/src/framework/framework.js +632 -6
- package/src/index.js +4 -0
- package/src/plugin/base.js +913 -10
- package/src/plugin/manager.js +29 -8
- package/src/tool/schema.js +32 -9
- package/tests/core/plugin-prompts.test.js +13 -13
- package/tests/core/prompt-registry.test.js +59 -51
- package/website/index.html +821 -0
package/docs/usage.md
CHANGED
|
@@ -166,32 +166,67 @@ skills/ → 子目录内有 SKILL.md → 加载为 skill
|
|
|
166
166
|
|
|
167
167
|
### 命令注册
|
|
168
168
|
|
|
169
|
-
如果 skill 目录下有 `index.js
|
|
169
|
+
如果 skill 目录下有 `index.js`,导出命令数组即可注册为可调用的扩展。**每个 skill 自动注册为 `skill:<skillname>` 扩展**,命令是扩展下的 tool。
|
|
170
170
|
|
|
171
171
|
```js
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
172
|
+
// index.js
|
|
173
|
+
module.exports = {
|
|
174
|
+
commands: [
|
|
175
|
+
{
|
|
176
|
+
name: 'greet',
|
|
177
|
+
description: '打招呼',
|
|
178
|
+
options: [
|
|
179
|
+
{ flags: '-n, --name <name>', description: '姓名', required: true },
|
|
180
|
+
{ flags: '-l, --lang <lang>', description: '语言', defaultValue: 'zh' },
|
|
181
|
+
{ flags: '-s, --shout', description: '是否大声喊' },
|
|
182
|
+
],
|
|
183
|
+
execute: async (args) => {
|
|
184
|
+
// args 是 commander.js 解析后的对象,无需自己解析命令字符串
|
|
185
|
+
const name = args.name || 'World';
|
|
186
|
+
const lang = args.lang || 'zh';
|
|
187
|
+
const shout = !!args.shout;
|
|
188
|
+
return `Hello, ${name}!` + (shout ? '!!!' : '');
|
|
189
|
+
},
|
|
190
|
+
},
|
|
191
|
+
],
|
|
192
|
+
};
|
|
184
193
|
```
|
|
185
194
|
|
|
186
|
-
|
|
195
|
+
#### 调用方式
|
|
196
|
+
|
|
197
|
+
```js
|
|
198
|
+
// 1. 查询参数
|
|
199
|
+
ext_skill({ plugin: "skill:<skillname>" })
|
|
200
|
+
|
|
201
|
+
// 2. 调用(传命令行字符串)
|
|
202
|
+
ext_call({
|
|
203
|
+
plugin: "skill:<skillname>",
|
|
204
|
+
tool: "greet",
|
|
205
|
+
args: { command: "-n Claude -l en --shout" },
|
|
206
|
+
})
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
#### 参数传递
|
|
210
|
+
|
|
211
|
+
Skill 命令参数通过 `args: { command: "..." }` 传递命令行字符串,由 Commander.js 解析后传给 handler:
|
|
212
|
+
|
|
213
|
+
| flags | 解析后 args 字段 |
|
|
214
|
+
|---|---|
|
|
215
|
+
| `-n, --name <name>` | `args.name` |
|
|
216
|
+
| `-l, --lang <lang>` | `args.lang` |
|
|
217
|
+
| `-s, --shout` | `args.shout` |
|
|
187
218
|
|
|
188
219
|
### 相关工具
|
|
189
220
|
|
|
190
|
-
- `skill_load({ skill: "name" })` —
|
|
221
|
+
- `skill_load({ skill: "name" })` — 获取技能内容(SKILL.md + 命令参考)
|
|
191
222
|
- `skill_reload` — 重载所有技能
|
|
192
223
|
- `skill_load_reference({ skill, reference })` — 加载技能的参考文档
|
|
193
224
|
- `skill_list_scripts` / `skill_load_script` — 技能脚本管理
|
|
194
225
|
|
|
226
|
+
### 程序化注册(不依赖文件)
|
|
227
|
+
|
|
228
|
+
可在 Plugin 中用 `this.skill.register(...)` 程序化创建技能,详见 [Plugin 基类的 skill accessor](#技能注册skillregister)。
|
|
229
|
+
|
|
195
230
|
## Workflows
|
|
196
231
|
|
|
197
232
|
### 结构化工作流
|
|
@@ -330,25 +365,67 @@ class MyPlugin extends Plugin {
|
|
|
330
365
|
this.system = false
|
|
331
366
|
}
|
|
332
367
|
|
|
333
|
-
|
|
334
|
-
//
|
|
335
|
-
|
|
368
|
+
onStart(framework) {
|
|
369
|
+
// 1. 注册 AI SDK 直调工具
|
|
370
|
+
this.tool.register({
|
|
336
371
|
name: 'my_tool',
|
|
337
372
|
description: 'My tool',
|
|
338
|
-
inputSchema: z.object({
|
|
339
|
-
|
|
373
|
+
inputSchema: framework.z.object({
|
|
374
|
+
param: framework.z.string().describe('参数')
|
|
375
|
+
}),
|
|
376
|
+
execute: async (args) => { /* ... */ }
|
|
377
|
+
})
|
|
378
|
+
|
|
379
|
+
// 2. 注册扩展工具(通过 ext_call 调用)
|
|
380
|
+
this.extension.register({
|
|
381
|
+
name: 'my_ext',
|
|
382
|
+
description: 'Extension tool',
|
|
383
|
+
inputSchema: framework.z.object({ /* ... */ }),
|
|
384
|
+
execute: async (args) => { /* ... */ }
|
|
340
385
|
})
|
|
386
|
+
|
|
387
|
+
// 3. 注册工作流
|
|
388
|
+
this.workflow.register('my-workflow', {
|
|
389
|
+
name: 'my-workflow',
|
|
390
|
+
steps: [/* ... */]
|
|
391
|
+
})
|
|
392
|
+
|
|
393
|
+
// 4. 注册子代理
|
|
394
|
+
this.agent.register({
|
|
395
|
+
name: 'helper',
|
|
396
|
+
role: '助手',
|
|
397
|
+
description: '辅助处理',
|
|
398
|
+
tools: {}
|
|
399
|
+
})
|
|
400
|
+
|
|
401
|
+
// 5. 注册 prompt part
|
|
402
|
+
this.prompt.register('my-rules', () => 'Rules content', {
|
|
403
|
+
scope: 'global',
|
|
404
|
+
priority: 50
|
|
405
|
+
})
|
|
406
|
+
|
|
407
|
+
// 6. 注册程序化 Skill(自动成为 skill:<name> 扩展)
|
|
408
|
+
this.skill.register('my-skill', '# My Skill\n\n...', {
|
|
409
|
+
description: 'My skill description'
|
|
410
|
+
}, {
|
|
411
|
+
commands: [{
|
|
412
|
+
name: 'do',
|
|
413
|
+
description: '...',
|
|
414
|
+
options: [{ flags: '-n, --name <name>', required: true }],
|
|
415
|
+
execute: async (args) => ({ success: true })
|
|
416
|
+
}]
|
|
417
|
+
})
|
|
418
|
+
|
|
341
419
|
framework.on('some:event', handler)
|
|
342
|
-
return this
|
|
343
420
|
}
|
|
344
421
|
|
|
345
|
-
|
|
346
|
-
//
|
|
347
|
-
return this
|
|
422
|
+
onStop() {
|
|
423
|
+
// 清理资源(工具/扩展/工作流/子代理/prompt/skill 基类自动处理)
|
|
348
424
|
}
|
|
349
425
|
|
|
350
426
|
reload(framework) {
|
|
351
|
-
//
|
|
427
|
+
// 声明式 prompts 自动清理/注册
|
|
428
|
+
// tool/extension/workflow/agent/prompt/skill 基类自动重新注册
|
|
352
429
|
}
|
|
353
430
|
|
|
354
431
|
async onCwdChanged(oldCwd, newCwd, framework) {
|
|
@@ -364,22 +441,37 @@ class MyPlugin extends Plugin {
|
|
|
364
441
|
module.exports = MyPlugin
|
|
365
442
|
```
|
|
366
443
|
|
|
444
|
+
#### 插件基类的 6 个 accessor 一览
|
|
445
|
+
|
|
446
|
+
| Accessor | 方法 | 用途 |
|
|
447
|
+
|---|---|---|
|
|
448
|
+
| `this.tool` | `register`/`remove`/`execute` | AI SDK 直接调用的工具 |
|
|
449
|
+
| `this.extension` | `register`/`remove`/`execute` | 扩展工具(通过 ext_call) |
|
|
450
|
+
| `this.workflow` | `register`/`remove`/`execute` | 工作流 |
|
|
451
|
+
| `this.agent` | `register`/`remove`/`get` | 子代理 |
|
|
452
|
+
| `this.prompt` | `register`/`remove` | 提示词 part |
|
|
453
|
+
| **`this.skill`** | `register`/`remove`/`get`/`has`/`list`/`details`/`listOwned`/`execute` | 技能(自动注册为 `skill:<name>` 扩展) |
|
|
454
|
+
|
|
367
455
|
### 生命周期
|
|
368
456
|
|
|
369
457
|
```
|
|
370
|
-
install() → load() → [start() + 注册工具]
|
|
458
|
+
install() → load() → [start() → onStart() + 注册工具]
|
|
371
459
|
↓
|
|
372
460
|
startAll() → [start() 再次调用]
|
|
373
461
|
↓
|
|
374
|
-
reload() →
|
|
462
|
+
reload() → 清理 + onStart()
|
|
463
|
+
↓
|
|
464
|
+
disable() → stop() → onStop()
|
|
465
|
+
↓
|
|
466
|
+
enable() → start() → onStart()
|
|
375
467
|
↓
|
|
376
|
-
uninstall() →
|
|
468
|
+
uninstall() → 清理 + 移除工具
|
|
377
469
|
```
|
|
378
470
|
|
|
379
471
|
**注意**:`start()` 会被调用两次(`load()` 和 `startAll()`),插件需通过 `_loaded` 标志或幂等设计来防止重复初始化。
|
|
380
472
|
|
|
381
473
|
### 放置位置
|
|
382
474
|
|
|
383
|
-
-
|
|
475
|
+
- 项目级:`.foliko/plugins/<name>/index.js`
|
|
384
476
|
- 全局:`~/.foliko/plugins/<name>.js` 或 `~/.foliko/plugins/<name>/index.js`
|
|
385
|
-
-
|
|
477
|
+
- 内置:`plugins/<name>/index.js`
|
package/package.json
CHANGED
|
@@ -23,7 +23,7 @@ class AuditPlugin extends Plugin {
|
|
|
23
23
|
this.config = {
|
|
24
24
|
maxLogs: config.maxLogs || 1000,
|
|
25
25
|
retentionDays: config.retentionDays || 30,
|
|
26
|
-
logDir: config.logDir || path.join(os.homedir(), '.
|
|
26
|
+
logDir: config.logDir || path.join(os.homedir(), '.foliko', 'logs', 'audit')
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
this._framework = null
|
|
@@ -40,15 +40,36 @@ async function loadCustomPlugins(framework, agentConfig) {
|
|
|
40
40
|
delete require.cache[require.resolve(resolved.path)];
|
|
41
41
|
const mod = require(resolved.path);
|
|
42
42
|
let pluginClass = mod.default || mod;
|
|
43
|
+
|
|
44
|
+
// 如果默认导出不是 Plugin 子类,查找模块中的 Plugin 子类
|
|
43
45
|
if (typeof pluginClass === 'function' && !(pluginClass.prototype instanceof Plugin)) {
|
|
44
46
|
const keys = Object.keys(mod).filter(k => mod[k] && mod[k].prototype instanceof Plugin);
|
|
45
47
|
if (keys.length > 0) pluginClass = mod[keys[0]];
|
|
46
48
|
}
|
|
49
|
+
|
|
50
|
+
// 如果 pluginClass 是对象(模块直接导出对象而非类),查找其中的 Plugin 子类
|
|
51
|
+
if (typeof pluginClass !== 'function' && pluginClass.prototype === undefined) {
|
|
52
|
+
const keys = Object.keys(mod).filter(k => mod[k] && mod[k].prototype instanceof Plugin);
|
|
53
|
+
if (keys.length > 0) pluginClass = mod[keys[0]];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// 检查 pluginClass 是否是 Plugin 实例
|
|
57
|
+
if (pluginClass instanceof Plugin) {
|
|
58
|
+
pluginClass = pluginClass.constructor;
|
|
59
|
+
}
|
|
60
|
+
|
|
47
61
|
let resolvedName = pluginName;
|
|
48
62
|
if (typeof pluginClass === 'function' && pluginClass.prototype instanceof Plugin) {
|
|
49
63
|
resolvedName = new pluginClass().name || pluginName;
|
|
50
64
|
}
|
|
51
65
|
if (framework.pluginManager.has(resolvedName) && framework.pluginManager.get(resolvedName)?._started) continue;
|
|
66
|
+
|
|
67
|
+
// 跳过无法识别的模块(不是 Plugin 类也不是 Plugin 实例)
|
|
68
|
+
if (typeof pluginClass !== 'function' && !(pluginClass instanceof Plugin)) {
|
|
69
|
+
log.debug(`Skipping ${pluginName}: not a Plugin class or instance`);
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
|
|
52
73
|
const pluginConfig = agentConfig[resolvedName] || {};
|
|
53
74
|
let instance;
|
|
54
75
|
if (typeof pluginClass === 'function') {
|
|
@@ -56,10 +77,21 @@ async function loadCustomPlugins(framework, agentConfig) {
|
|
|
56
77
|
// 类式:new PluginClass()
|
|
57
78
|
instance = new pluginClass(pluginConfig);
|
|
58
79
|
} else {
|
|
59
|
-
// function(foliko)
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
80
|
+
// function(Plugin) 或 function(foliko) 格式:尝试调用
|
|
81
|
+
try {
|
|
82
|
+
// 优先尝试 function(Plugin) 格式
|
|
83
|
+
const Result = pluginClass(Plugin);
|
|
84
|
+
if (Result && Result.prototype instanceof Plugin) {
|
|
85
|
+
instance = new Result(pluginConfig);
|
|
86
|
+
} else {
|
|
87
|
+
// function(foliko) 格式:传入 framework
|
|
88
|
+
pluginClass(framework);
|
|
89
|
+
instance = null;
|
|
90
|
+
}
|
|
91
|
+
} catch (err) {
|
|
92
|
+
log.error(`Failed to call plugin factory ${pluginName}:`, err.message);
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
63
95
|
}
|
|
64
96
|
} else {
|
|
65
97
|
instance = pluginClass;
|
|
@@ -127,21 +159,6 @@ async function bootstrapDefaults(framework, config = {}) {
|
|
|
127
159
|
}));
|
|
128
160
|
}
|
|
129
161
|
|
|
130
|
-
// 1.5 Main Agent
|
|
131
|
-
if (!framework._mainAgent) {
|
|
132
|
-
const { Agent } = require('../../../src/agent/main');
|
|
133
|
-
const aiPlugin = framework.pluginManager.get('ai');
|
|
134
|
-
framework._mainAgent = framework.createAgent({
|
|
135
|
-
name: 'MainAgent',
|
|
136
|
-
systemPrompt: '你是一个智能助手。当用户提出问题或任务时,你会主动分析需求,选择合适的工具来获取信息或执行操作。你善于将复杂任务拆解为多个步骤,通过工具协作完成。',
|
|
137
|
-
model: aiConfig.model || 'deepseek-chat',
|
|
138
|
-
provider: aiConfig.provider || 'deepseek',
|
|
139
|
-
apiKey: aiPlugin ? aiPlugin.config.apiKey : (aiConfig.apiKey || envApiKey),
|
|
140
|
-
baseURL: aiConfig.baseURL,
|
|
141
|
-
});
|
|
142
|
-
framework._agents.push(framework._mainAgent);
|
|
143
|
-
}
|
|
144
|
-
|
|
145
162
|
// 1.875 Extension executor (needed by skill-manager for slash commands)
|
|
146
163
|
if (shouldLoad('extension-executor')) {
|
|
147
164
|
const ExtensionExecutorPlugin = require('../../executors/extension');
|
|
@@ -230,6 +247,35 @@ async function bootstrapDefaults(framework, config = {}) {
|
|
|
230
247
|
// 统一启动所有插件
|
|
231
248
|
await framework.pluginManager.startAll();
|
|
232
249
|
|
|
250
|
+
// 等待 skill-manager 完成首次异步加载 (`_loadAllSkills` 包含动态 import ESM 的 index.js)
|
|
251
|
+
// 否则 MainAgent 首次 _buildSystemPrompt() 会错过 skill 命令
|
|
252
|
+
const skillManagerEntry = framework.pluginManager.get('skill-manager');
|
|
253
|
+
if (skillManagerEntry && typeof skillManagerEntry._loadReady?.then === 'function') {
|
|
254
|
+
try {
|
|
255
|
+
await skillManagerEntry._loadReady;
|
|
256
|
+
} catch (_) { /* 加载失败已在 skill-manager 内 log */ }
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// ============= Main Agent (在所有 plugins 加载并 start 之后) =============
|
|
260
|
+
// 修复: 必须放到所有 plugins 加载并 start 之后再创建,并且 await skill-manager 的
|
|
261
|
+
// _loadReady。
|
|
262
|
+
// 原顺序下 MainAgent.constructor 会立即 _buildSystemPrompt(),此时 extension-executor
|
|
263
|
+
// 和 skill-manager 还没注册工具到 prompt registry,导致系统提示词里看不到 skill 命令
|
|
264
|
+
// —— 之前用户必须 reload 一次才看到就是这个 race。
|
|
265
|
+
if (!framework._mainAgent) {
|
|
266
|
+
const { Agent } = require('../../../src/agent/main');
|
|
267
|
+
const aiPlugin = framework.pluginManager.get('ai');
|
|
268
|
+
framework._mainAgent = framework.createAgent({
|
|
269
|
+
name: 'MainAgent',
|
|
270
|
+
systemPrompt: '你是一个智能助手。当用户提出问题或任务时,你会主动分析需求,选择合适的工具来获取信息或执行操作。你善于将复杂任务拆解为多个步骤,通过工具协作完成。',
|
|
271
|
+
model: aiConfig.model || 'deepseek-chat',
|
|
272
|
+
provider: aiConfig.provider || 'deepseek',
|
|
273
|
+
apiKey: aiPlugin ? aiPlugin.config.apiKey : (aiConfig.apiKey || envApiKey),
|
|
274
|
+
baseURL: aiConfig.baseURL,
|
|
275
|
+
});
|
|
276
|
+
framework._agents.push(framework._mainAgent);
|
|
277
|
+
}
|
|
278
|
+
|
|
233
279
|
// 退出 bootstrap 模式
|
|
234
280
|
framework.pluginManager.setBootstrapping(false);
|
|
235
281
|
}
|
|
@@ -53,15 +53,14 @@ class PythonPluginLoader extends Plugin {
|
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
let desc = '## 【Python 插件】\n\n'
|
|
56
|
-
desc += 'Python
|
|
56
|
+
desc += '每个 Python 插件自动注册为 `python:<插件名>` 扩展,通过 `ext_skill` + `ext_call` 统一调用:\n\n'
|
|
57
57
|
|
|
58
58
|
for (const [name, plugin] of this._pythonPlugins) {
|
|
59
|
-
desc += `###
|
|
59
|
+
desc += `### python:${plugin.info.name}\n`
|
|
60
60
|
desc += `${plugin.info.description || ''}\n\n`
|
|
61
61
|
if (plugin.info.tools && Array.isArray(plugin.info.tools)) {
|
|
62
62
|
for (const tool of plugin.info.tools) {
|
|
63
|
-
|
|
64
|
-
desc += `- **${fullName}**: ${tool.description || '无描述'}\n`
|
|
63
|
+
desc += `- **${tool.name}**: ${tool.description || '无描述'}\n`
|
|
65
64
|
// 使用 zodSchemaToMarkdown 生成参数描述
|
|
66
65
|
const params = tool.params || {}
|
|
67
66
|
if (Object.keys(params).length > 0) {
|
|
@@ -82,7 +81,9 @@ class PythonPluginLoader extends Plugin {
|
|
|
82
81
|
}
|
|
83
82
|
|
|
84
83
|
desc += '**调用格式:**\n'
|
|
85
|
-
desc += '```\
|
|
84
|
+
desc += '```\n'
|
|
85
|
+
desc += '// 1. 查询参数\next_skill({ plugin: "python:<插件名>" })\n'
|
|
86
|
+
desc += '// 2. 调用\next_call({ plugin: "python:<插件名>", tool: "<工具名>", args: {...} })\n'
|
|
86
87
|
desc += '```\n'
|
|
87
88
|
return desc.trim()
|
|
88
89
|
}
|
|
@@ -151,7 +152,7 @@ class PythonPluginLoader extends Plugin {
|
|
|
151
152
|
// 注册插件的每个工具
|
|
152
153
|
if (pluginInfo.tools && Array.isArray(pluginInfo.tools)) {
|
|
153
154
|
for (const tool of pluginInfo.tools) {
|
|
154
|
-
this._registerPythonTool(pluginName, tool)
|
|
155
|
+
this._registerPythonTool(pluginName, tool, pluginInfo);
|
|
155
156
|
}
|
|
156
157
|
}
|
|
157
158
|
}
|
|
@@ -160,22 +161,32 @@ class PythonPluginLoader extends Plugin {
|
|
|
160
161
|
}
|
|
161
162
|
}
|
|
162
163
|
|
|
164
|
+
// 所有 Python 插件注册完毕后,强制失效 prompt 缓存,让 LLM 看到新的扩展
|
|
165
|
+
this._extensionExecutor?._invalidatePromptCaches?.(this._framework);
|
|
166
|
+
|
|
163
167
|
//log.info(` Total Python plugins: ${this._pythonPlugins.size}`)
|
|
164
168
|
}
|
|
165
169
|
|
|
166
170
|
/**
|
|
167
|
-
* 注册 Python
|
|
168
|
-
*
|
|
169
|
-
*
|
|
171
|
+
* 注册 Python 插件的单个工具到 ExtensionExecutor
|
|
172
|
+
*
|
|
173
|
+
* 每个 Python 插件注册为独立扩展 `python:<插件名>`,工具名是原始名(不带前缀)。
|
|
174
|
+
* LLM 通过以下方式调用:
|
|
175
|
+
* 1. 查询参数:ext_skill({ plugin: "python:<插件名>" })
|
|
176
|
+
* 2. 调用:ext_call({ plugin: "python:<插件名>", tool: "<工具名>", args: {...} })
|
|
177
|
+
*
|
|
178
|
+
* @param {string} pluginName Python 插件名(PLUGIN.name,例如 'stock')
|
|
179
|
+
* @param {Object} tool 工具定义对象
|
|
180
|
+
* @param {string} tool.name 工具名(PLUGIN.TOOLS[].name,例如 'get_price')
|
|
181
|
+
* @param {string} [tool.description] 工具描述(用于提示词)
|
|
182
|
+
* @param {Object} [tool.params] 工具参数定义(key → 类型占位符或 JSON Schema)
|
|
183
|
+
* @param {Object} [pluginMeta] 插件元信息(含 description),用于丰富扩展描述
|
|
170
184
|
*/
|
|
171
|
-
_registerPythonTool(pluginName, tool) {
|
|
185
|
+
_registerPythonTool(pluginName, tool, pluginMeta = null) {
|
|
172
186
|
if (!tool.name) return
|
|
173
187
|
|
|
174
|
-
// 格式:pluginname_toolname
|
|
175
|
-
const fullToolName = `${pluginName}_${tool.name}`
|
|
176
|
-
|
|
177
188
|
const toolDef = {
|
|
178
|
-
name:
|
|
189
|
+
name: tool.name, // 工具原始名(如 get_price),不带 pluginName_ 前缀
|
|
179
190
|
description: tool.description || `${pluginName} 的 ${tool.name} 工具`,
|
|
180
191
|
inputSchema: this._parseToolParams(tool.params || {}),
|
|
181
192
|
execute: async (args) => {
|
|
@@ -183,26 +194,33 @@ class PythonPluginLoader extends Plugin {
|
|
|
183
194
|
}
|
|
184
195
|
}
|
|
185
196
|
|
|
197
|
+
// 每个 Python 插件注册为独立扩展 `python:<pluginName>`
|
|
198
|
+
// 优先用 PLUGIN.description,fallback 到默认描述
|
|
199
|
+
const extName = `python:${pluginName}`;
|
|
200
|
+
const extDescription = pluginMeta?.description
|
|
201
|
+
? pluginMeta.description
|
|
202
|
+
: `Python 插件 ${pluginName}`;
|
|
203
|
+
const pluginInfo = {
|
|
204
|
+
name: extName,
|
|
205
|
+
description: extDescription,
|
|
206
|
+
version: pluginMeta?.version || '1.0.0',
|
|
207
|
+
};
|
|
208
|
+
|
|
186
209
|
try {
|
|
187
|
-
// 直接注册到 ExtensionExecutor(如果可用)
|
|
188
210
|
if (this._extensionExecutor) {
|
|
189
|
-
this._extensionExecutor.registerTool(
|
|
190
|
-
'python', // 插件名,用于 ext_call 的 plugin 参数
|
|
191
|
-
{ name: 'python', description: 'Python 插件工具', version: '1.0.0' },
|
|
192
|
-
toolDef
|
|
193
|
-
)
|
|
211
|
+
this._extensionExecutor.registerTool(extName, pluginInfo, toolDef);
|
|
194
212
|
} else {
|
|
195
|
-
// 回退到基类的 registerTool
|
|
213
|
+
// 回退到基类的 registerTool(带 pluginName 前缀以避免冲突)
|
|
196
214
|
this.registerTool({
|
|
197
|
-
name:
|
|
198
|
-
description:
|
|
215
|
+
name: `${pluginName}_${tool.name}`,
|
|
216
|
+
description: tool.description || `${pluginName} 的 ${tool.name} 工具`,
|
|
199
217
|
pluginName: pluginName,
|
|
200
218
|
inputSchema: this._parseToolParams(tool.params || {}),
|
|
201
219
|
execute: toolDef.execute
|
|
202
|
-
})
|
|
220
|
+
});
|
|
203
221
|
}
|
|
204
222
|
} catch (err) {
|
|
205
|
-
log.error(` Failed to register tool ${
|
|
223
|
+
log.error(` Failed to register tool ${tool.name} for ${extName}:`, err.message)
|
|
206
224
|
}
|
|
207
225
|
}
|
|
208
226
|
|