foliko 2.0.5 → 2.0.7
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 +280 -24
- 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 +44 -19
- package/plugins/core/python-loader/index.js +43 -25
- package/plugins/core/scheduler/index.js +1 -0
- package/plugins/core/skill-manager/PROMPT.md +6 -0
- package/plugins/core/skill-manager/index.js +402 -115
- 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/context.js +941 -0
- package/plugins/core/workflow/engine.js +66 -0
- package/plugins/core/workflow/examples/01-basic.js +42 -0
- package/plugins/core/workflow/examples/01-basic.json +30 -0
- package/plugins/core/workflow/examples/02-choice.js +75 -0
- package/plugins/core/workflow/examples/02-choice.json +59 -0
- package/plugins/core/workflow/examples/03-chain-style.js +114 -0
- package/plugins/core/workflow/examples/03-each.json +41 -0
- package/plugins/core/workflow/examples/04-parallel.js +52 -0
- package/plugins/core/workflow/examples/04-parallel.json +51 -0
- package/plugins/core/workflow/examples/05-chain-style.json +68 -0
- package/plugins/core/workflow/examples/05-each.js +65 -0
- package/plugins/core/workflow/examples/06-script.js +82 -0
- package/plugins/core/workflow/examples/07-module-export.js +43 -0
- package/plugins/core/workflow/examples/08-default-export.js +29 -0
- package/plugins/core/workflow/examples/09-next-with-args.js +50 -0
- package/plugins/core/workflow/examples/10-logger.js +34 -0
- package/plugins/core/workflow/examples/11-storage.js +68 -0
- package/plugins/core/workflow/examples/simple.js +77 -0
- package/plugins/core/workflow/examples/simple.json +75 -0
- package/plugins/core/workflow/index.js +204 -78
- package/plugins/core/workflow/js-runner.js +318 -0
- package/plugins/core/workflow/json-runner.js +323 -0
- package/plugins/core/workflow/stages/action.js +211 -0
- package/plugins/core/workflow/stages/choice.js +74 -0
- package/plugins/core/workflow/stages/delay.js +73 -0
- package/plugins/core/workflow/stages/each.js +123 -0
- package/plugins/core/workflow/stages/parallel.js +69 -0
- package/plugins/core/workflow/stages/try.js +142 -0
- package/plugins/executors/data-splitter/PROMPT.md +13 -0
- package/plugins/executors/data-splitter/index.js +8 -6
- 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/sandbox/check-context.js +5 -0
- package/sandbox/test-context.js +27 -0
- package/sandbox/test-fixes.js +40 -0
- package/sandbox/test-hello-js.js +46 -0
- 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 +753 -436
- package/src/agent/chat.js +56 -28
- package/src/agent/main.js +39 -14
- package/src/agent/prompt-registry.js +56 -16
- 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 +9 -5
- package/src/common/constants.js +12 -0
- package/src/common/error-capture.js +91 -0
- package/src/common/json-safe.js +20 -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 +78 -12
- package/src/index.js +4 -0
- package/src/plugin/base.js +908 -5
- package/src/plugin/manager.js +124 -9
- package/src/tool/schema.js +32 -9
- package/src/utils/sandbox.js +1 -1
- package/website/index.html +821 -0
- package/skills/workflows/workflow-troubleshooting/DEBUGGING.md +0 -197
- package/skills/workflows/workflow-troubleshooting/SKILL.md +0 -391
|
@@ -1,25 +1,34 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* ExtensionExecutorPlugin 扩展插件执行器
|
|
2
|
+
* ExtensionExecutorPlugin - 扩展插件执行器
|
|
3
3
|
* 统一管理扩展插件(如 gate-trading)的工具,通过 ext_call 调用
|
|
4
|
+
*
|
|
5
|
+
* 模块拆分:
|
|
6
|
+
* - extension-registry.js: 扩展注册表(数据管理)
|
|
7
|
+
* - prompt-builder.js: Prompt 构建器(提示词生成)
|
|
8
|
+
* - skill-helper.js: Skill 命令辅助
|
|
9
|
+
* - index.js (本文件): 主入口,组合各模块
|
|
4
10
|
*/
|
|
5
11
|
|
|
6
12
|
const { Plugin } = require('../../../src/plugin/base');
|
|
7
13
|
const { logger } = require('../../../src/common/logger');
|
|
8
14
|
const { z } = require('zod');
|
|
9
|
-
|
|
10
|
-
const {
|
|
15
|
+
|
|
16
|
+
const { ExtensionRegistry } = require('./extension-registry');
|
|
17
|
+
const { ExtensionPromptBuilder } = require('./prompt-builder');
|
|
18
|
+
const { SkillHelper } = require('./skill-helper');
|
|
11
19
|
|
|
12
20
|
const log = logger.child('ExtensionExecutor');
|
|
13
21
|
|
|
14
22
|
class ExtensionExecutorPlugin extends Plugin {
|
|
15
|
-
// 声明式 prompt parts
|
|
23
|
+
// 声明式 prompt parts - 由基类自动注册和管理生命周期
|
|
16
24
|
prompts = [
|
|
17
25
|
{
|
|
18
26
|
name: 'extension-tools',
|
|
19
|
-
|
|
27
|
+
scope: 'global',
|
|
28
|
+
priority: 60,
|
|
20
29
|
description: '所有扩展工具的说明和使用方式',
|
|
21
30
|
provider: function () {
|
|
22
|
-
return this.
|
|
31
|
+
return this._promptBuilder.build();
|
|
23
32
|
},
|
|
24
33
|
},
|
|
25
34
|
];
|
|
@@ -33,47 +42,93 @@ class ExtensionExecutorPlugin extends Plugin {
|
|
|
33
42
|
this.system = true;
|
|
34
43
|
|
|
35
44
|
this._framework = null;
|
|
36
|
-
this.
|
|
45
|
+
this._registry = new ExtensionRegistry();
|
|
46
|
+
this._promptBuilder = new ExtensionPromptBuilder(this._registry, this._registry);
|
|
47
|
+
this._skillHelper = null; // 延迟到 start 时创建(需要 framework)
|
|
37
48
|
}
|
|
38
49
|
|
|
50
|
+
// ============ 生命周期 ============
|
|
51
|
+
|
|
39
52
|
install(framework) {
|
|
40
53
|
this._framework = framework;
|
|
54
|
+
this._skillHelper = new SkillHelper(this._registry, framework);
|
|
41
55
|
|
|
42
|
-
framework.on('plugin:loaded', (plugin) =>
|
|
43
|
-
|
|
44
|
-
});
|
|
56
|
+
framework.on('plugin:loaded', (plugin) => this._scanPlugin(plugin));
|
|
57
|
+
framework.on('plugin:reloaded', (plugin) => this._rescanPlugin(plugin));
|
|
45
58
|
|
|
46
|
-
|
|
47
|
-
|
|
59
|
+
// 监听 MCP 工具变化(MCP 工具是异步加载的)
|
|
60
|
+
// MCP 服务器现在直接注册为 mcp:<server> 扩展,
|
|
61
|
+
// mcp:tools-changed 事件触发时让 prompt 缓存失效,让 provider 重新渲染
|
|
62
|
+
framework.on('mcp:tools-changed', () => {
|
|
63
|
+
this._invalidatePromptCaches();
|
|
64
|
+
});
|
|
65
|
+
framework.on('mcp:reloaded', () => {
|
|
66
|
+
this._invalidatePromptCaches();
|
|
48
67
|
});
|
|
49
|
-
|
|
50
|
-
// 不监听 tool:registered — 直接 registerTool 注册的工具通过 AI SDK 暴露给 LLM,无需出现在 Extensions 中
|
|
51
68
|
|
|
52
69
|
framework.on('framework:ready', () => {
|
|
53
70
|
log.debug('Framework ready, rescanning all plugin tools...');
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
if (plugin.name === 'skill-manager') {
|
|
57
|
-
continue;
|
|
58
|
-
}
|
|
59
|
-
this._scanPluginTools(plugin);
|
|
60
|
-
}
|
|
61
|
-
this._invalidatePromptCaches(framework);
|
|
71
|
+
this._scanAllPlugins();
|
|
72
|
+
this._invalidatePromptCaches();
|
|
62
73
|
});
|
|
63
74
|
|
|
64
75
|
return this;
|
|
65
76
|
}
|
|
66
77
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
78
|
+
async start(framework) {
|
|
79
|
+
super.start(framework);
|
|
80
|
+
this._framework = framework;
|
|
81
|
+
this._registry._framework = framework;
|
|
82
|
+
this._promptBuilder = new ExtensionPromptBuilder(this._registry, framework);
|
|
83
|
+
this._skillHelper = new SkillHelper(this._registry, framework);
|
|
84
|
+
|
|
85
|
+
this._scanAllPlugins();
|
|
86
|
+
this._registerBuiltinTools(framework);
|
|
87
|
+
|
|
88
|
+
this._invalidatePromptCaches();
|
|
89
|
+
return this;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async reload(framework) {
|
|
93
|
+
this._framework = framework;
|
|
94
|
+
this._registry.clear();
|
|
95
|
+
this._scanAllPlugins();
|
|
96
|
+
this._invalidatePromptCaches();
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
uninstall(framework) {
|
|
100
|
+
this._registry.clear();
|
|
101
|
+
this._framework = null;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// ============ 扫描插件 ============
|
|
105
|
+
|
|
106
|
+
_scanAllPlugins() {
|
|
107
|
+
if (!this._framework) return;
|
|
108
|
+
const plugins = this._framework.pluginManager.getAll();
|
|
109
|
+
for (const { instance: plugin } of plugins) {
|
|
110
|
+
if (plugin.name === 'skill-manager') continue;
|
|
111
|
+
this._scanPlugin(plugin);
|
|
70
112
|
}
|
|
113
|
+
}
|
|
71
114
|
|
|
72
|
-
|
|
73
|
-
|
|
115
|
+
/**
|
|
116
|
+
* 扫描单个插件的工具
|
|
117
|
+
* 兼容两种注册方式:
|
|
118
|
+
* 1. plugin.tools = { name: toolDef } - 旧的静态方式
|
|
119
|
+
* 2. plugin._registeredExtensions = [...] - 新的 this.extension.register() 方式
|
|
120
|
+
*/
|
|
121
|
+
_scanPlugin(plugin) {
|
|
122
|
+
if (!plugin || !plugin.name) return;
|
|
74
123
|
|
|
75
|
-
|
|
124
|
+
this._scanPluginTools(plugin);
|
|
125
|
+
this._scanRegisteredExtensions(plugin);
|
|
126
|
+
}
|
|
76
127
|
|
|
128
|
+
_scanPluginTools(plugin) {
|
|
129
|
+
if (!plugin.tools || typeof plugin.tools !== 'object') return;
|
|
130
|
+
|
|
131
|
+
const useExtName = plugin.name;
|
|
77
132
|
const pluginInfo = {
|
|
78
133
|
name: useExtName,
|
|
79
134
|
description: plugin.description || '',
|
|
@@ -82,7 +137,6 @@ class ExtensionExecutorPlugin extends Plugin {
|
|
|
82
137
|
|
|
83
138
|
for (const [toolName, toolDef] of Object.entries(plugin.tools)) {
|
|
84
139
|
if (!toolDef || typeof toolDef !== 'object') continue;
|
|
85
|
-
|
|
86
140
|
this.registerTool(useExtName, pluginInfo, {
|
|
87
141
|
name: toolName,
|
|
88
142
|
description: toolDef.description || '',
|
|
@@ -91,504 +145,418 @@ class ExtensionExecutorPlugin extends Plugin {
|
|
|
91
145
|
});
|
|
92
146
|
}
|
|
93
147
|
|
|
94
|
-
log.debug(` Scanned ${Object.keys(plugin.tools).length} tools from plugin '${
|
|
148
|
+
log.debug(` Scanned ${Object.keys(plugin.tools).length} tools from plugin '${plugin.name}'`);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
_scanRegisteredExtensions(plugin) {
|
|
152
|
+
if (!plugin._registeredExtensions || !Array.isArray(plugin._registeredExtensions)) return;
|
|
95
153
|
|
|
96
|
-
|
|
97
|
-
|
|
154
|
+
const pluginInfo = {
|
|
155
|
+
name: plugin.name,
|
|
156
|
+
description: plugin.description || '',
|
|
157
|
+
version: plugin.version || '1.0.0',
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
for (const { extName, toolDef } of plugin._registeredExtensions) {
|
|
161
|
+
this.registerTool(extName || plugin.name, pluginInfo, toolDef);
|
|
98
162
|
}
|
|
163
|
+
|
|
164
|
+
log.debug(` Scanned ${plugin._registeredExtensions.length} extensions from plugin '${plugin.name}'`);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
_rescanPlugin(plugin) {
|
|
168
|
+
if (!plugin || !plugin.name) return;
|
|
169
|
+
this._registry.clearPlugin(plugin.name);
|
|
170
|
+
this._scanPlugin(plugin);
|
|
99
171
|
}
|
|
100
172
|
|
|
101
|
-
|
|
102
|
-
|
|
173
|
+
// ============ 注册/注销工具 ============
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* 注册扩展工具(供 Plugin 基类 extension.register 调用)
|
|
177
|
+
*/
|
|
178
|
+
registerTool(pluginName, pluginInfo, toolDef) {
|
|
179
|
+
if (!toolDef || !toolDef.name) {
|
|
180
|
+
log.warn(`[ExtensionExecutor] Invalid tool definition for '${pluginName}'`);
|
|
103
181
|
return;
|
|
104
182
|
}
|
|
105
183
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
const extName = pluginName === 'skill-manager' ? 'skill' : pluginName;
|
|
110
|
-
|
|
111
|
-
const keysToRemove = [extName, pluginName];
|
|
112
|
-
for (const key of keysToRemove) {
|
|
113
|
-
if (this._extensions.has(key)) {
|
|
114
|
-
const ext = this._extensions.get(key);
|
|
115
|
-
for (const tool of ext.tools || []) {
|
|
116
|
-
if (this._framework?.toolRouter) {
|
|
117
|
-
this._framework.toolRouter.unregister(`ext_${tool.name}`);
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
this._extensions.delete(key);
|
|
121
|
-
}
|
|
122
|
-
}
|
|
184
|
+
this._registry.registerTool(pluginName, pluginInfo, toolDef);
|
|
185
|
+
this._invalidatePromptCaches();
|
|
186
|
+
}
|
|
123
187
|
|
|
124
|
-
|
|
188
|
+
/**
|
|
189
|
+
* 注销扩展工具(供 Plugin 基类 extension.remove 调用)
|
|
190
|
+
*/
|
|
191
|
+
_unregisterTool(pluginName, toolName) {
|
|
192
|
+
this._registry.unregisterTool(pluginName, toolName);
|
|
193
|
+
if (this._framework?.toolRouter) {
|
|
194
|
+
this._framework.toolRouter.unregister(`ext_${toolName}`);
|
|
195
|
+
}
|
|
196
|
+
this._invalidatePromptCaches();
|
|
125
197
|
}
|
|
126
198
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
}
|
|
134
|
-
this._scanPluginTools(plugin);
|
|
199
|
+
// ============ 缓存管理 ============
|
|
200
|
+
|
|
201
|
+
_invalidatePromptCaches() {
|
|
202
|
+
this._framework?.prompts?.invalidate(this.name, 'extension-tools');
|
|
203
|
+
for (const agent of this._framework?._agents || []) {
|
|
204
|
+
agent._invalidateSystemPromptCache?.();
|
|
135
205
|
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// ============ 内置工具 ============
|
|
136
209
|
|
|
137
|
-
|
|
210
|
+
_registerBuiltinTools(framework) {
|
|
211
|
+
framework.registerTool(this._createExtCallTool());
|
|
212
|
+
framework.registerTool(this._createExtListTool());
|
|
213
|
+
framework.registerTool(this._createExtSkillTool());
|
|
214
|
+
}
|
|
138
215
|
|
|
139
|
-
|
|
216
|
+
_createExtCallTool() {
|
|
217
|
+
return {
|
|
140
218
|
name: 'ext_call',
|
|
141
|
-
description:
|
|
219
|
+
description:
|
|
220
|
+
'【⚡ 执行扩展工具】调用某个扩展插件的具体工具。\n' +
|
|
221
|
+
'\n' +
|
|
222
|
+
'**必传参数**:\n' +
|
|
223
|
+
'- `plugin`:插件名(命名空间支持 4 种:普通插件名 `email` / MCP 服务器 `mcp:<server>` / Skill `skill:<name>` / Python 插件 `python:<plugin>`)\n' +
|
|
224
|
+
'- `tool`:工具名(来自 ext_list 或 ext_skill 输出)\n' +
|
|
225
|
+
'- `args`:参数对象(每个工具的必填项见 ext_skill 输出)\n' +
|
|
226
|
+
'\n' +
|
|
227
|
+
'**推荐使用顺序**:`ext_list` 查看所有可用插件 → `ext_skill` 查看参数细节 → `ext_call` 执行\n' +
|
|
228
|
+
'\n' +
|
|
229
|
+
'**调用示例**:\n' +
|
|
230
|
+
'```js\n' +
|
|
231
|
+
'ext_call({\n' +
|
|
232
|
+
' plugin: "mcp:designmd",\n' +
|
|
233
|
+
' tool: "search_design_kits",\n' +
|
|
234
|
+
' args: { query: "modern" }\n' +
|
|
235
|
+
'})\n' +
|
|
236
|
+
'```\n' +
|
|
237
|
+
'\n' +
|
|
238
|
+
'**返回值**:`{success: true, data: <工具返回的数据>}` 或 `{success: false, error: "<错误信息>"}`\n' +
|
|
239
|
+
'**注意**:调用前必须先调 ext_skill 确认参数格式和必填项,否则可能因参数缺失失败',
|
|
142
240
|
inputSchema: z.object({
|
|
143
|
-
plugin: z.string().describe('插件名称(如 email, gate-trading, mcp)'),
|
|
144
|
-
tool: z.string().describe('
|
|
145
|
-
args: z.record(z.any()).optional().describe('
|
|
241
|
+
plugin: z.string().describe('插件名称(如 email, gate-trading, mcp:designmd, skill:my-skill, python:stock)'),
|
|
242
|
+
tool: z.string().describe('工具名称(来自 ext_list 输出)'),
|
|
243
|
+
args: z.record(z.any()).optional().describe('工具参数(对象格式,详见 ext_skill 输出)'),
|
|
146
244
|
}),
|
|
147
245
|
execute: async (args) => {
|
|
148
246
|
const { plugin, tool, args: toolArgs = {} } = args;
|
|
149
|
-
|
|
150
|
-
if (plugin === 'mcp' && this._mcpExecutor) {
|
|
151
|
-
const mcpToolDef = this._mcpExecutor.tools?.[tool];
|
|
152
|
-
if (mcpToolDef && mcpToolDef.execute) {
|
|
153
|
-
return await mcpToolDef.execute(toolArgs || {});
|
|
154
|
-
}
|
|
155
|
-
return { success: false, error: `MCP tool '${tool}' not found` };
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
const ext = this._extensions.get(plugin);
|
|
159
|
-
if (!ext) {
|
|
160
|
-
framework.emit('tool:error', {
|
|
161
|
-
name: `${plugin}:${tool}`,
|
|
162
|
-
args: toolArgs,
|
|
163
|
-
error: `扩展插件 '${plugin}' 不存在`,
|
|
164
|
-
source: 'extension'
|
|
165
|
-
});
|
|
166
|
-
return { success: false, error: `扩展插件 '${plugin}' 不存在` };
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
const toolDef = ext.tools.find((t) => t.name === tool);
|
|
170
|
-
if (!toolDef) {
|
|
171
|
-
framework.emit('tool:error', {
|
|
172
|
-
name: `${plugin}:${tool}`,
|
|
173
|
-
args: toolArgs,
|
|
174
|
-
error: `扩展工具 '${tool}' 不存在`,
|
|
175
|
-
source: 'extension'
|
|
176
|
-
});
|
|
177
|
-
return { success: false, error: `扩展工具 '${tool}' 不存在` };
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
try {
|
|
181
|
-
framework.emit('tool:call', {
|
|
182
|
-
name: `${plugin}:${tool}`,
|
|
183
|
-
args: toolArgs,
|
|
184
|
-
source: 'extension'
|
|
185
|
-
});
|
|
186
|
-
framework.emit('tool-call', {
|
|
187
|
-
name: `${plugin}:${tool}`,
|
|
188
|
-
args: toolArgs,
|
|
189
|
-
source: 'extension'
|
|
190
|
-
});
|
|
191
|
-
|
|
192
|
-
const result = await toolDef.execute(toolArgs, framework);
|
|
193
|
-
|
|
194
|
-
if (result && result.success === false && result.error) {
|
|
195
|
-
framework.emit('tool:error', {
|
|
196
|
-
name: `${plugin}:${tool}`,
|
|
197
|
-
args: toolArgs,
|
|
198
|
-
error: result.error,
|
|
199
|
-
source: 'extension'
|
|
200
|
-
});
|
|
201
|
-
return result;
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
framework.emit('tool:result', {
|
|
205
|
-
name: `${plugin}:${tool}`,
|
|
206
|
-
args: toolArgs,
|
|
207
|
-
result,
|
|
208
|
-
source: 'extension'
|
|
209
|
-
});
|
|
210
|
-
|
|
211
|
-
return { success: true, data: result };
|
|
212
|
-
} catch (err) {
|
|
213
|
-
log.error(` Tool '${tool}' failed:`, err.message);
|
|
214
|
-
|
|
215
|
-
framework.emit('tool:error', {
|
|
216
|
-
name: `${plugin}:${tool}`,
|
|
217
|
-
args: toolArgs,
|
|
218
|
-
error: err.message,
|
|
219
|
-
source: 'extension'
|
|
220
|
-
});
|
|
221
|
-
|
|
222
|
-
return { success: false, error: err.message };
|
|
223
|
-
}
|
|
247
|
+
return this._executeExtensionTool(plugin, tool, toolArgs);
|
|
224
248
|
},
|
|
225
|
-
}
|
|
249
|
+
};
|
|
250
|
+
}
|
|
226
251
|
|
|
227
|
-
|
|
252
|
+
_createExtListTool() {
|
|
253
|
+
return {
|
|
228
254
|
name: 'ext_list',
|
|
229
|
-
description:
|
|
255
|
+
description:
|
|
256
|
+
'【📋 扩展工具清单】列出所有可用的扩展插件及其工具。\n' +
|
|
257
|
+
'\n' +
|
|
258
|
+
'**用途**:作为使用扩展的第一步,先调用本工具了解可用能力。\n' +
|
|
259
|
+
'\n' +
|
|
260
|
+
'**返回格式**:\n' +
|
|
261
|
+
'```js\n' +
|
|
262
|
+
'{\n' +
|
|
263
|
+
' success: true,\n' +
|
|
264
|
+
' data: [\n' +
|
|
265
|
+
' { name: "email", description: "...", tools: [{ name: "send_mail", description: "..." }] },\n' +
|
|
266
|
+
' { name: "mcp:designmd", description: "...", tools: [...] }\n' +
|
|
267
|
+
' ]\n' +
|
|
268
|
+
'}\n' +
|
|
269
|
+
'```\n' +
|
|
270
|
+
'\n' +
|
|
271
|
+
'**命名空间规则**:返回的 plugin 名支持 4 种命名空间:\n' +
|
|
272
|
+
'- 普通插件:`email`, `gate-trading`\n' +
|
|
273
|
+
'- MCP 服务器:`mcp:<servername>`(如 `mcp:designmd`)\n' +
|
|
274
|
+
'- Skill:`skill:<name>`(如 `skill:ambient-agent`)\n' +
|
|
275
|
+
'- Python 插件:`python:<plugin>`(如 `python:stock`)\n' +
|
|
276
|
+
'\n' +
|
|
277
|
+
'**推荐使用顺序**:`ext_list` → `ext_skill` → `ext_call`',
|
|
230
278
|
inputSchema: z.object({}),
|
|
231
279
|
execute: async () => {
|
|
232
|
-
const extensions =
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
}
|
|
280
|
+
const extensions = this._registry.getAll().map((ext) => ({
|
|
281
|
+
name: ext.name,
|
|
282
|
+
description: ext.description,
|
|
283
|
+
version: ext.version,
|
|
284
|
+
tools: ext.tools
|
|
285
|
+
.filter((t) => t && t.name)
|
|
286
|
+
.map((t) => ({ name: t.name, description: t.description })),
|
|
287
|
+
}));
|
|
241
288
|
return { success: true, data: extensions };
|
|
242
289
|
},
|
|
243
|
-
}
|
|
290
|
+
};
|
|
291
|
+
}
|
|
244
292
|
|
|
245
|
-
|
|
293
|
+
_createExtSkillTool() {
|
|
294
|
+
return {
|
|
246
295
|
name: 'ext_skill',
|
|
247
|
-
description:
|
|
296
|
+
description:
|
|
297
|
+
'【📖 扩展参数手册】查询某个扩展插件的详细参数结构。\n' +
|
|
298
|
+
'\n' +
|
|
299
|
+
'**必传参数**:`plugin`(支持 `mcp:<server>`, `skill:<name>`, `python:<plugin>`, 普通插件名)\n' +
|
|
300
|
+
'\n' +
|
|
301
|
+
'**返回内容**:Markdown 格式,包含:\n' +
|
|
302
|
+
'- 插件标题、描述、类型元数据\n' +
|
|
303
|
+
'- 快速调用示例(可直接复制)\n' +
|
|
304
|
+
'- 每个工具的参数表(含必填/可选、类型、描述)\n' +
|
|
305
|
+
'\n' +
|
|
306
|
+
'**典型使用**:找到陌生工具的必填参数 → 调用 ext_skill 看详情 → 再用 ext_call 执行\n' +
|
|
307
|
+
'\n' +
|
|
308
|
+
'**调用示例**:\n' +
|
|
309
|
+
'```js\n' +
|
|
310
|
+
'ext_skill({ plugin: "mcp:designmd" })\n' +
|
|
311
|
+
'```\n' +
|
|
312
|
+
'\n' +
|
|
313
|
+
'**插件不存在时**:返回友好的错误提示(包含可能原因和排查步骤)',
|
|
248
314
|
inputSchema: z.object({
|
|
249
|
-
plugin: z.string().describe('
|
|
315
|
+
plugin: z.string().describe('扩展插件名称(如 mcp:designmd, skill:my-skill, python:stock, email)'),
|
|
250
316
|
}),
|
|
251
317
|
execute: async (args) => {
|
|
252
318
|
const { plugin } = args;
|
|
253
|
-
|
|
254
|
-
return "请使用 `skill_load` 获取skill的使用详情"
|
|
255
|
-
}
|
|
256
|
-
return this.bindAllToolSkill(plugin)
|
|
319
|
+
return this._promptBuilder.buildSkill(plugin);
|
|
257
320
|
},
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
framework.on('agent:created', (agent) => {
|
|
261
|
-
// Extension tool 描述已通过 prompts 声明式注册到 framework.prompts,
|
|
262
|
-
// 并由 _syncPromptParts 同步到子 Agent,无需再注入 _originalPrompt
|
|
263
|
-
});
|
|
321
|
+
};
|
|
322
|
+
}
|
|
264
323
|
|
|
265
|
-
|
|
266
|
-
// 不再需要 _refreshAllAgentsExtPrompt 注入 _originalPrompt
|
|
324
|
+
// ============ 工具执行 ============
|
|
267
325
|
|
|
268
|
-
|
|
326
|
+
/**
|
|
327
|
+
* 执行扩展工具(供 ext_call 工具调用)
|
|
328
|
+
*/
|
|
329
|
+
async _executeExtensionTool(plugin, tool, toolArgs) {
|
|
330
|
+
return this._doExecute(plugin, tool, toolArgs, { source: 'ext_call', emitEvent: true });
|
|
331
|
+
}
|
|
269
332
|
|
|
270
|
-
|
|
333
|
+
/**
|
|
334
|
+
* 执行扩展工具(供 Plugin 基类 extension.execute 调用)
|
|
335
|
+
*/
|
|
336
|
+
async _executeTool(pluginName, toolName, args) {
|
|
337
|
+
return this._doExecute(pluginName, toolName, args, { source: 'plugin_extension', emitEvent: false });
|
|
271
338
|
}
|
|
272
339
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
340
|
+
/**
|
|
341
|
+
* 统一的工具执行内部实现
|
|
342
|
+
* @private
|
|
343
|
+
*/
|
|
344
|
+
async _doExecute(plugin, tool, toolArgs, { source, emitEvent }) {
|
|
345
|
+
// 1) 插件存在性检查 + 友好错误
|
|
346
|
+
const ext = this._registry.getPlugin(plugin);
|
|
347
|
+
if (!ext) {
|
|
348
|
+
return this._pluginNotFoundError(plugin);
|
|
277
349
|
}
|
|
278
350
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
version: pluginInfo.version || '1.0.0',
|
|
284
|
-
tools: [],
|
|
285
|
-
});
|
|
351
|
+
// 2) 工具存在性检查 + 友好错误
|
|
352
|
+
const toolDef = ext.tools.find((t) => t.name === tool);
|
|
353
|
+
if (!toolDef) {
|
|
354
|
+
return this._toolNotFoundError(plugin, ext, tool);
|
|
286
355
|
}
|
|
287
356
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
execute: toolDef.execute,
|
|
295
|
-
};
|
|
296
|
-
if (toolDef._options) {
|
|
297
|
-
toolEntry._options = toolDef._options;
|
|
298
|
-
}
|
|
299
|
-
if (existingIdx >= 0) {
|
|
300
|
-
ext.tools[existingIdx] = toolEntry;
|
|
301
|
-
} else {
|
|
302
|
-
ext.tools.push(toolEntry);
|
|
357
|
+
// 3) 参数预校验(Zod schema)
|
|
358
|
+
if (toolDef.inputSchema && typeof toolDef.inputSchema.safeParse === 'function') {
|
|
359
|
+
const parsed = toolDef.inputSchema.safeParse(toolArgs);
|
|
360
|
+
if (!parsed.success) {
|
|
361
|
+
return this._paramValidationError(plugin, tool, parsed.error);
|
|
362
|
+
}
|
|
303
363
|
}
|
|
304
364
|
|
|
305
|
-
//
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
365
|
+
// 4) 触发事件(可选)
|
|
366
|
+
if (emitEvent) {
|
|
367
|
+
this._framework?.emit?.('tool-call', {
|
|
368
|
+
name: `${plugin}:${tool}`,
|
|
369
|
+
args: toolArgs,
|
|
370
|
+
source,
|
|
371
|
+
});
|
|
309
372
|
}
|
|
310
|
-
}
|
|
311
373
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
'',
|
|
319
|
-
'> **重要**:',
|
|
320
|
-
'> 1. 调用前必须指定 `plugin`(插件名)和 `tool`(工具名)',
|
|
321
|
-
'> 2. 参数通过 `args` 对象传入,参数名和类型见各工具说明,注意引号的问题,遵守JSON的格式',
|
|
322
|
-
'> 3. 必填参数(Required)必须提供,可选参数(Optional)可省略',
|
|
323
|
-
'',
|
|
324
|
-
'---',
|
|
325
|
-
'',
|
|
326
|
-
];
|
|
327
|
-
|
|
328
|
-
const ext = this._extensions.get(pluginName);
|
|
329
|
-
if (!ext) return '找不到扩展!'
|
|
330
|
-
for (const tool of ext.tools) {
|
|
331
|
-
sections.push(`#### \`${tool.name}\``, '');
|
|
332
|
-
sections.push(tool.description || '无描述', '');
|
|
333
|
-
if (ext.name === 'mcp' && mcp) {
|
|
334
|
-
const mcp_desc = mcp._bindMcpParamsDesc(tool.inputSchema);
|
|
335
|
-
sections.push(mcp_desc, '');
|
|
336
|
-
} else if (tool.inputSchema) {
|
|
337
|
-
try {
|
|
338
|
-
const schemaMarkdown = zodSchemaToMarkdown(tool.inputSchema);
|
|
339
|
-
sections.push('**参数:**', ' ');
|
|
340
|
-
sections.push(schemaMarkdown || '无', ' ');
|
|
341
|
-
} catch (e) {
|
|
342
|
-
const schemaTable = zodSchemaToTable(tool.inputSchema);
|
|
343
|
-
sections.push('**参数:**', ' ');
|
|
344
|
-
sections.push(schemaTable || '无', ' ');
|
|
345
|
-
}
|
|
346
|
-
}
|
|
374
|
+
// 5) 执行
|
|
375
|
+
try {
|
|
376
|
+
const result = await toolDef.execute(toolArgs, this._framework);
|
|
377
|
+
return { success: true, data: result };
|
|
378
|
+
} catch (err) {
|
|
379
|
+
return this._executionError(plugin, tool, err);
|
|
347
380
|
}
|
|
381
|
+
}
|
|
348
382
|
|
|
349
|
-
|
|
350
|
-
|
|
383
|
+
// ============ 错误处理辅助方法 ============
|
|
384
|
+
|
|
385
|
+
/** 插件不存在错误 */
|
|
386
|
+
_pluginNotFoundError(plugin) {
|
|
387
|
+
const allPlugins = this._registry.listPlugins();
|
|
388
|
+
const namespaces = this._groupByNamespace(allPlugins);
|
|
389
|
+
const similar = this._findSimilar(plugin, allPlugins);
|
|
390
|
+
|
|
391
|
+
return {
|
|
392
|
+
success: false,
|
|
393
|
+
error: `扩展插件 '${plugin}' 不存在`,
|
|
394
|
+
hint: similar.length > 0
|
|
395
|
+
? `你可能想用:${similar.slice(0, 3).join(', ')}`
|
|
396
|
+
: `可用插件:\n${this._formatNamespaceList(namespaces)}`,
|
|
397
|
+
availableNamespaces: namespaces,
|
|
398
|
+
similar: similar.slice(0, 3),
|
|
399
|
+
};
|
|
351
400
|
}
|
|
352
401
|
|
|
353
|
-
/**
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
402
|
+
/** 工具不存在错误 */
|
|
403
|
+
_toolNotFoundError(plugin, ext, tool) {
|
|
404
|
+
const availableTools = ext.tools.map((t) => t.name);
|
|
405
|
+
const similar = this._findSimilar(tool, availableTools);
|
|
406
|
+
|
|
407
|
+
return {
|
|
408
|
+
success: false,
|
|
409
|
+
error: `扩展工具 '${tool}' 不存在(插件 '${plugin}')`,
|
|
410
|
+
hint: similar.length > 0
|
|
411
|
+
? `你可能想用:${similar.slice(0, 3).join(', ')}`
|
|
412
|
+
: `可用工具(${availableTools.length} 个):${availableTools.slice(0, 10).join(', ')}${availableTools.length > 10 ? '...' : ''}`,
|
|
413
|
+
availableTools,
|
|
414
|
+
similar: similar.slice(0, 3),
|
|
415
|
+
};
|
|
362
416
|
}
|
|
363
417
|
|
|
364
|
-
|
|
365
|
-
|
|
418
|
+
/** 参数校验错误(Zod) */
|
|
419
|
+
_paramValidationError(plugin, tool, zodError) {
|
|
420
|
+
const issues = (zodError && zodError.issues) || [];
|
|
421
|
+
const formatted = issues.slice(0, 5).map((iss) => {
|
|
422
|
+
const path = (iss.path && iss.path.length > 0 ? iss.path.join('.') : '<root>');
|
|
423
|
+
return ` - ${path}: ${iss.message}`;
|
|
424
|
+
}).join('\n');
|
|
425
|
+
|
|
426
|
+
return {
|
|
427
|
+
success: false,
|
|
428
|
+
error: `参数校验失败:'${plugin}:${tool}'`,
|
|
429
|
+
details: formatted,
|
|
430
|
+
issuesCount: issues.length,
|
|
431
|
+
hint: '请使用 ext_skill({ plugin: "' + plugin + '" }) 查看完整参数说明',
|
|
432
|
+
};
|
|
433
|
+
}
|
|
366
434
|
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
435
|
+
/** 执行时错误 */
|
|
436
|
+
_executionError(plugin, tool, err) {
|
|
437
|
+
const message = (err && err.message) || String(err);
|
|
438
|
+
const stack = (err && err.stack) ? err.stack.split('\n').slice(0, 5).join('\n') : '';
|
|
439
|
+
log.error(`Tool '${plugin}:${tool}' failed:`, message);
|
|
440
|
+
if (stack) log.debug(` Stack: ${stack}`);
|
|
441
|
+
|
|
442
|
+
return {
|
|
443
|
+
success: false,
|
|
444
|
+
error: message,
|
|
445
|
+
errorType: (err && err.name) || 'Error',
|
|
446
|
+
stack,
|
|
447
|
+
timeout: message.includes('timeout'),
|
|
448
|
+
};
|
|
449
|
+
}
|
|
373
450
|
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
451
|
+
// ============ 匹配/格式化辅助方法 ============
|
|
452
|
+
|
|
453
|
+
/** 模糊匹配(包含 / 编辑距离),返回相似名称列表 */
|
|
454
|
+
_findSimilar(target, candidates, max = 5) {
|
|
455
|
+
if (!target || !Array.isArray(candidates) || candidates.length === 0) return [];
|
|
456
|
+
const lower = String(target).toLowerCase();
|
|
457
|
+
const threshold = Math.max(2, Math.floor(lower.length / 3));
|
|
458
|
+
|
|
459
|
+
const scored = [];
|
|
460
|
+
for (const c of candidates) {
|
|
461
|
+
if (typeof c !== 'string') continue;
|
|
462
|
+
const cl = c.toLowerCase();
|
|
463
|
+
// 1) 包含关系
|
|
464
|
+
if (cl.includes(lower) || lower.includes(cl)) {
|
|
465
|
+
scored.push({ name: c, score: 1 });
|
|
466
|
+
continue;
|
|
380
467
|
}
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
desc += `**工具:** ${Object.keys(this._mcpExecutor.tools).map(t => `\`${t}\``).join(', ')}\n`;
|
|
468
|
+
// 2) 编辑距离
|
|
469
|
+
const dist = this._levenshtein(lower, cl);
|
|
470
|
+
if (dist <= threshold) {
|
|
471
|
+
scored.push({ name: c, score: 1 - dist / Math.max(lower.length, cl.length) });
|
|
386
472
|
}
|
|
387
|
-
|
|
388
|
-
desc += '\n## 禁止事项\n';
|
|
389
|
-
desc += '- 不先调用 `ext_skill` 获取参数就直接使用 `ext_call`\n';
|
|
390
473
|
}
|
|
391
474
|
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
return desc.trim();
|
|
475
|
+
return scored
|
|
476
|
+
.sort((a, b) => b.score - a.score)
|
|
477
|
+
.slice(0, max)
|
|
478
|
+
.map((s) => s.name);
|
|
397
479
|
}
|
|
398
480
|
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
if (
|
|
404
|
-
if (
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
return
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
481
|
+
/** Levenshtein 编辑距离(带长度预筛) */
|
|
482
|
+
_levenshtein(a, b) {
|
|
483
|
+
if (Math.abs(a.length - b.length) > 5) return 99;
|
|
484
|
+
const m = a.length, n = b.length;
|
|
485
|
+
if (m === 0) return n;
|
|
486
|
+
if (n === 0) return m;
|
|
487
|
+
const dp = Array.from({ length: m + 1 }, (_, i) => {
|
|
488
|
+
const row = new Array(n + 1);
|
|
489
|
+
row[0] = i;
|
|
490
|
+
return row;
|
|
491
|
+
});
|
|
492
|
+
for (let j = 0; j <= n; j++) dp[0][j] = j;
|
|
493
|
+
for (let i = 1; i <= m; i++) {
|
|
494
|
+
for (let j = 1; j <= n; j++) {
|
|
495
|
+
dp[i][j] = a[i - 1] === b[j - 1]
|
|
496
|
+
? dp[i - 1][j - 1]
|
|
497
|
+
: 1 + Math.min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]);
|
|
415
498
|
}
|
|
416
499
|
}
|
|
417
|
-
|
|
418
|
-
return this._schemaToMarkdown(schema);
|
|
500
|
+
return dp[m][n];
|
|
419
501
|
}
|
|
420
502
|
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
shape[key] = this._jsonSchemaPropToZod(prop, required.includes(key));
|
|
503
|
+
/** 按命名空间分组插件名 */
|
|
504
|
+
_groupByNamespace(plugins) {
|
|
505
|
+
const groups = {};
|
|
506
|
+
for (const p of plugins) {
|
|
507
|
+
let ns = 'plugin';
|
|
508
|
+
if (typeof p === 'string') {
|
|
509
|
+
if (p.startsWith('mcp:')) ns = 'mcp';
|
|
510
|
+
else if (p.startsWith('skill:')) ns = 'skill';
|
|
511
|
+
else if (p.startsWith('python:')) ns = 'python';
|
|
431
512
|
}
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
} catch (e) {
|
|
435
|
-
return null;
|
|
513
|
+
if (!groups[ns]) groups[ns] = [];
|
|
514
|
+
groups[ns].push(p);
|
|
436
515
|
}
|
|
516
|
+
return groups;
|
|
437
517
|
}
|
|
438
518
|
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
const type = prop.type || 'string';
|
|
447
|
-
switch (type) {
|
|
448
|
-
case 'string':
|
|
449
|
-
return isRequired ? z.string() : z.string().optional();
|
|
450
|
-
case 'number':
|
|
451
|
-
case 'integer':
|
|
452
|
-
return isRequired ? z.number() : z.number().optional();
|
|
453
|
-
case 'boolean':
|
|
454
|
-
return isRequired ? z.boolean() : z.boolean().optional();
|
|
455
|
-
case 'array':
|
|
456
|
-
return isRequired ? z.array(z.any()) : z.array(z.any()).optional();
|
|
457
|
-
case 'object':
|
|
458
|
-
if (prop.properties) {
|
|
459
|
-
const nested = {};
|
|
460
|
-
for (const [k, v] of Object.entries(prop.properties)) {
|
|
461
|
-
nested[k] = this._jsonSchemaPropToZod(v, prop.required?.includes(k) || false);
|
|
462
|
-
}
|
|
463
|
-
return isRequired ? z.object(nested) : z.object(nested).optional();
|
|
464
|
-
}
|
|
465
|
-
return isRequired ? z.record(z.any()) : z.record(z.any()).optional();
|
|
466
|
-
default:
|
|
467
|
-
return isRequired ? z.any() : z.any().optional();
|
|
519
|
+
/** 格式化命名空间列表为多行文本 */
|
|
520
|
+
_formatNamespaceList(namespaces) {
|
|
521
|
+
const lines = [];
|
|
522
|
+
for (const [ns, items] of Object.entries(namespaces)) {
|
|
523
|
+
const head = items.slice(0, 5).join(', ');
|
|
524
|
+
const more = items.length > 5 ? ` (+${items.length - 5} more)` : '';
|
|
525
|
+
lines.push(` [${ns}] ${head}${more}`);
|
|
468
526
|
}
|
|
527
|
+
return lines.join('\n');
|
|
469
528
|
}
|
|
470
529
|
|
|
471
|
-
|
|
472
|
-
if (!schema || !schema.properties) return null;
|
|
473
|
-
|
|
474
|
-
const props = schema.properties || {};
|
|
475
|
-
const required = schema.required || [];
|
|
476
|
-
let md = '';
|
|
477
|
-
|
|
478
|
-
for (const [key, prop] of Object.entries(props)) {
|
|
479
|
-
const isRequired = required.includes(key);
|
|
480
|
-
const type = prop.type || 'any';
|
|
481
|
-
const descText = prop.description || '';
|
|
482
|
-
md += `- \`${key}\`${isRequired ? ' (必填)' : ''}: ${type} ${descText}\n`;
|
|
483
|
-
}
|
|
484
|
-
return md || null;
|
|
485
|
-
}
|
|
530
|
+
// ============ 公开查询 API ============
|
|
486
531
|
|
|
487
532
|
getExtensions() {
|
|
488
|
-
return
|
|
489
|
-
name,
|
|
490
|
-
description: ext.description,
|
|
491
|
-
version: ext.version,
|
|
492
|
-
tools: ext.tools,
|
|
493
|
-
}));
|
|
533
|
+
return this._registry.getAll();
|
|
494
534
|
}
|
|
495
535
|
|
|
496
|
-
// 获取指定插件的所有工具
|
|
497
536
|
getExtensionTools(pluginName) {
|
|
498
|
-
|
|
499
|
-
return ext ? ext.tools : [];
|
|
537
|
+
return this._registry.listTools(pluginName);
|
|
500
538
|
}
|
|
501
539
|
|
|
502
|
-
// 获取指定插件的指定工具
|
|
503
540
|
getExtensionTool(pluginName, toolName) {
|
|
504
|
-
|
|
505
|
-
if (!ext) return null;
|
|
506
|
-
return ext.tools.find(t => t.name === toolName) || null;
|
|
541
|
+
return this._registry.getTool(pluginName, toolName);
|
|
507
542
|
}
|
|
508
543
|
|
|
509
|
-
// 获取所有插件名称
|
|
510
544
|
getExtensionNames() {
|
|
511
|
-
return
|
|
545
|
+
return this._registry.listPlugins();
|
|
512
546
|
}
|
|
513
547
|
|
|
548
|
+
// Skill 命令相关
|
|
514
549
|
getSkillCommands() {
|
|
515
|
-
|
|
516
|
-
if (!ext) return [];
|
|
517
|
-
return ext.tools
|
|
518
|
-
.filter(t => t && t.name)
|
|
519
|
-
.map(t => ({
|
|
520
|
-
name: t.name.replace(/^([^:]+):(.*)$/, '$1:$2'),
|
|
521
|
-
description: t.description || t.name,
|
|
522
|
-
options: t._options || null,
|
|
523
|
-
}));
|
|
550
|
+
return this._skillHelper?.getCommands() || [];
|
|
524
551
|
}
|
|
525
552
|
|
|
526
553
|
getSkillCommandsHelp() {
|
|
527
|
-
|
|
528
|
-
if (!ext || ext.tools.length === 0) return '';
|
|
529
|
-
|
|
530
|
-
const lines = [];
|
|
531
|
-
for (const tool of ext.tools) {
|
|
532
|
-
if (!tool || !tool.name) continue;
|
|
533
|
-
const parts = tool.name.split(':');
|
|
534
|
-
if (parts.length === 2) {
|
|
535
|
-
lines.push(`/${parts[0]}:${parts[1]} - ${tool.description || '无描述'}`);
|
|
536
|
-
}
|
|
537
|
-
}
|
|
538
|
-
return lines.join('\n');
|
|
554
|
+
return this._skillHelper?.getCommandsHelp() || '';
|
|
539
555
|
}
|
|
540
556
|
|
|
541
557
|
async executeSkillCommand(command, args) {
|
|
542
|
-
|
|
543
|
-
try {
|
|
544
|
-
const result = await this._framework.executeTool('ext_call', {
|
|
545
|
-
plugin: 'skill',
|
|
546
|
-
tool: command,
|
|
547
|
-
args: { command: args }
|
|
548
|
-
});
|
|
549
|
-
return result.success !== false ? result : null;
|
|
550
|
-
} catch (err) {
|
|
551
|
-
log.warn('Skill command failed:', err.message);
|
|
552
|
-
return null;
|
|
553
|
-
}
|
|
554
|
-
}
|
|
555
|
-
|
|
556
|
-
async reload(framework) {
|
|
557
|
-
this._framework = framework;
|
|
558
|
-
this._extensions.clear();
|
|
559
|
-
const plugins = framework.pluginManager.getAll();
|
|
560
|
-
for (const { instance: plugin } of plugins) {
|
|
561
|
-
this._rescanPluginTools(plugin);
|
|
562
|
-
}
|
|
563
|
-
this._mcpExecutor = framework.pluginManager?.get('mcp') || null;
|
|
564
|
-
this._invalidatePromptCaches(framework);
|
|
565
|
-
|
|
566
|
-
await this._reloadMCPConfig(framework);
|
|
567
|
-
}
|
|
568
|
-
|
|
569
|
-
async _reloadMCPConfig(framework) {
|
|
570
|
-
const fs = require('fs');
|
|
571
|
-
const path = require('path');
|
|
572
|
-
const mcpExecutor = framework.pluginManager?.get('mcp');
|
|
573
|
-
if (!mcpExecutor) return;
|
|
574
|
-
|
|
575
|
-
try {
|
|
576
|
-
const configPath = path.resolve('.foliko/mcp_config.json');
|
|
577
|
-
if (fs.existsSync(configPath)) {
|
|
578
|
-
const configContent = fs.readFileSync(configPath, 'utf8');
|
|
579
|
-
const config = JSON.parse(configContent);
|
|
580
|
-
await mcpExecutor.reloadConfig(config);
|
|
581
|
-
logger.info(' MCP config reloaded via extension-executor');
|
|
582
|
-
}
|
|
583
|
-
} catch (err) {
|
|
584
|
-
logger.warn(' Failed to reload MCP config:', err.message);
|
|
585
|
-
}
|
|
586
|
-
}
|
|
587
|
-
|
|
588
|
-
async uninstall(framework) {
|
|
589
|
-
this._extensions.clear();
|
|
590
|
-
this._framework = null;
|
|
558
|
+
return this._skillHelper?.executeCommand(command, args) || null;
|
|
591
559
|
}
|
|
592
560
|
}
|
|
593
561
|
|
|
594
|
-
module.exports = ExtensionExecutorPlugin
|
|
562
|
+
module.exports = ExtensionExecutorPlugin;
|