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.
Files changed (59) hide show
  1. package/.claude/settings.local.json +4 -1
  2. package/.cli_default_systemPrompt.md +291 -0
  3. package/CLAUDE.md +3 -0
  4. package/README.md +20 -3
  5. package/docs/architecture.md +34 -2
  6. package/docs/extensions.md +199 -0
  7. package/docs/migration.md +100 -0
  8. package/docs/public-api.md +1180 -6
  9. package/docs/usage.md +122 -30
  10. package/package.json +1 -1
  11. package/plugins/core/audit/index.js +1 -1
  12. package/plugins/core/default/bootstrap.js +65 -19
  13. package/plugins/core/python-loader/index.js +43 -25
  14. package/plugins/core/skill-manager/PROMPT.md +6 -0
  15. package/plugins/core/skill-manager/index.js +538 -93
  16. package/plugins/core/sub-agent/PROMPT.md +10 -0
  17. package/plugins/core/sub-agent/index.js +36 -3
  18. package/plugins/core/think/index.js +1 -0
  19. package/plugins/core/workflow/index.js +106 -22
  20. package/plugins/executors/data-splitter/PROMPT.md +13 -0
  21. package/plugins/executors/data-splitter/index.js +5 -4
  22. package/plugins/executors/extension/extension-registry.js +145 -0
  23. package/plugins/executors/extension/index.js +405 -437
  24. package/plugins/executors/extension/prompt-builder.js +359 -0
  25. package/plugins/executors/extension/skill-helper.js +143 -0
  26. package/plugins/messaging/feishu/index.js +5 -3
  27. package/plugins/messaging/qq/index.js +6 -4
  28. package/plugins/messaging/telegram/index.js +6 -3
  29. package/plugins/messaging/weixin/index.js +5 -3
  30. package/plugins/tools/PROMPT.md +26 -0
  31. package/plugins/tools/index.js +6 -5
  32. package/skills/foliko/AGENTS.md +196 -43
  33. package/skills/foliko/SKILL.md +157 -28
  34. package/skills/mcp/SKILL.md +77 -118
  35. package/skills/plugins/SKILL.md +89 -3
  36. package/skills/python/SKILL.md +57 -39
  37. package/skills/skill-guide/SKILL.md +42 -34
  38. package/skills/workflows/SKILL.md +224 -9
  39. package/skills/workflows/workflow-troubleshooting/SKILL.md +221 -281
  40. package/src/agent/chat.js +48 -27
  41. package/src/agent/main.js +34 -13
  42. package/src/agent/prompt-registry.js +133 -87
  43. package/src/agent/prompts/PROMPT.md +3 -0
  44. package/src/agent/sub.js +1 -1
  45. package/src/cli/ui/chat-ui-old.js +5 -2
  46. package/src/cli/ui/chat-ui.js +5 -2
  47. package/src/common/constants.js +12 -0
  48. package/src/common/error-capture.js +91 -0
  49. package/src/common/logger.js +2 -2
  50. package/src/context/compressor.js +6 -2
  51. package/src/executors/mcp-executor.js +105 -125
  52. package/src/framework/framework.js +632 -6
  53. package/src/index.js +4 -0
  54. package/src/plugin/base.js +913 -10
  55. package/src/plugin/manager.js +29 -8
  56. package/src/tool/schema.js +32 -9
  57. package/tests/core/plugin-prompts.test.js +13 -13
  58. package/tests/core/prompt-registry.test.js +59 -51
  59. 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`,导出命令数组即可注册为可调用的 slash 命令:
169
+ 如果 skill 目录下有 `index.js`,导出命令数组即可注册为可调用的扩展。**每个 skill 自动注册为 `skill:<skillname>` 扩展**,命令是扩展下的 tool。
170
170
 
171
171
  ```js
172
- module.exports = [
173
- {
174
- name: 'greet',
175
- description: '打招呼',
176
- options: [
177
- { flags: '-n, --name <value>', description: '姓名' }
178
- ],
179
- execute: async (parsedArgs, ctx) => {
180
- return `Hello, ${parsedArgs.name || 'World'}!`
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
- 命令通过 `ext_call({ plugin: "skill", tool: "skill-name:command-name", args: { command: "..." } })` 调用。
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
- install(framework) {
334
- // 注册工具、监听事件
335
- framework.registerTool({
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
- execute: async (args) => { ... }
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
- start(framework) {
346
- // 初始化逻辑,start() 可能会被调用多次
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
- - 项目级:`plugins/<name>/index.js`
475
+ - 项目级:`.foliko/plugins/<name>/index.js`
384
476
  - 全局:`~/.foliko/plugins/<name>.js` 或 `~/.foliko/plugins/<name>/index.js`
385
- - 通过 `pluginLinks` 指定任意路径
477
+ - 内置:`plugins/<name>/index.js`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "foliko",
3
- "version": "2.0.4",
3
+ "version": "2.0.6",
4
4
  "description": "简约的插件化 Agent 框架",
5
5
  "main": "src/index.js",
6
6
  "type": "commonjs",
@@ -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(), '.vb-agent', 'logs', 'audit')
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) 格式:直接调用,传入 framework
60
- pluginClass(framework);
61
- // function(foliko) 不需要 return,插件内部自己注册
62
- instance = null; // 不再作为插件实例加载
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 插件工具通过 `ext_call` 调用:\n\n'
56
+ desc += '每个 Python 插件自动注册为 `python:<插件名>` 扩展,通过 `ext_skill` + `ext_call` 统一调用:\n\n'
57
57
 
58
58
  for (const [name, plugin] of this._pythonPlugins) {
59
- desc += `### ${plugin.info.name}\n`
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
- const fullName = `${name}_${tool.name}`
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 += '```\next_call({ plugin: "python", tool: "插件名_工具名", args: {...} })\n'
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
- * 工具名格式:插件名_工具名(如 stock_get_price)
169
- * 直接注册到 ExtensionExecutor,使用 ext_call({ plugin: "python", tool: "插件名_工具名", args: {...} }) 调用
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: fullToolName,
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: fullToolName,
198
- description: `[${pluginName}] ${tool.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 ${fullToolName}:`, err.message)
223
+ log.error(` Failed to register tool ${tool.name} for ${extName}:`, err.message)
206
224
  }
207
225
  }
208
226
 
@@ -0,0 +1,6 @@
1
+ ## 可用技能
2
+
3
+ > 命令详情(参数、调用方式)请查看上方【Extensions】扩展插件段落。
4
+ > 详细使用指南用 `skill_load("技能名")` 加载。
5
+
6
+ {{skills}}