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
|
@@ -14,7 +14,7 @@ const { StdioClientTransport } = require('@modelcontextprotocol/sdk/client/stdio
|
|
|
14
14
|
const { zodSchemaToMarkdown } = require('@chnak/zod-to-markdown');
|
|
15
15
|
const { logger } = require('../common/logger');
|
|
16
16
|
const { MCPClientWrapper } = require('./mcp-client');
|
|
17
|
-
const { extractSchema
|
|
17
|
+
const { extractSchema } = require('./mcp-desc');
|
|
18
18
|
const { jsonSchemaToZod } = require('../tool/schema');
|
|
19
19
|
|
|
20
20
|
const log = logger.child('MCPExecutor');
|
|
@@ -53,120 +53,6 @@ class MCPExecutorPlugin extends Plugin {
|
|
|
53
53
|
}
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
framework.registerTool({
|
|
57
|
-
name: 'mcp_tool_schema',
|
|
58
|
-
description: '查询工具的调用示例,直接复制 example 或 fullExample 字段使用',
|
|
59
|
-
inputSchema: z.object({
|
|
60
|
-
server: z.string().describe('MCP 服务器名称'),
|
|
61
|
-
tool: z.string().describe('工具名称'),
|
|
62
|
-
}),
|
|
63
|
-
execute: async (args) => {
|
|
64
|
-
const { server, tool } = args;
|
|
65
|
-
const clientInfo = this._clients.get(server);
|
|
66
|
-
if (!clientInfo) return { success: false, error: `MCP server '${server}' not found` };
|
|
67
|
-
|
|
68
|
-
const toolInfo = clientInfo.tools.find((t) => t.name === tool);
|
|
69
|
-
if (!toolInfo) return { success: false, error: `Tool '${tool}' not found` };
|
|
70
|
-
|
|
71
|
-
const schema = extractSchema(toolInfo.inputSchema);
|
|
72
|
-
const props = schema.properties || {};
|
|
73
|
-
const required = schema.required || [];
|
|
74
|
-
|
|
75
|
-
const generateExample = (prop, depth = 0) => {
|
|
76
|
-
if (!prop) return null;
|
|
77
|
-
const type = prop.type || (prop.properties ? 'object' : 'string');
|
|
78
|
-
if (type === 'string') return prop.description?.split('.')[0] || `${prop.title || '值'}`;
|
|
79
|
-
if (type === 'number' || type === 'integer') return prop.default || 0;
|
|
80
|
-
if (type === 'boolean') return false;
|
|
81
|
-
if (type === 'array') {
|
|
82
|
-
if (prop.items) {
|
|
83
|
-
const itemExample = generateExample(prop.items, depth + 1);
|
|
84
|
-
return depth === 0 ? [itemExample] : itemExample;
|
|
85
|
-
}
|
|
86
|
-
return ['示例'];
|
|
87
|
-
}
|
|
88
|
-
if (type === 'object' || prop.properties) {
|
|
89
|
-
const obj = {};
|
|
90
|
-
for (const [key, val] of Object.entries(prop.properties || {})) {
|
|
91
|
-
obj[key] = generateExample(val, depth + 1);
|
|
92
|
-
}
|
|
93
|
-
return obj;
|
|
94
|
-
}
|
|
95
|
-
return null;
|
|
96
|
-
};
|
|
97
|
-
|
|
98
|
-
const exampleArgs = {};
|
|
99
|
-
for (const key of required) {
|
|
100
|
-
const prop = props[key];
|
|
101
|
-
if (prop) exampleArgs[key] = generateExample(prop);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
return {
|
|
105
|
-
success: true,
|
|
106
|
-
data: {
|
|
107
|
-
name: toolInfo.name, description: toolInfo.description, required,
|
|
108
|
-
parameters: Object.fromEntries(
|
|
109
|
-
required.map((key) => [key, {
|
|
110
|
-
type: props[key]?.type || 'string',
|
|
111
|
-
description: props[key]?.description || '',
|
|
112
|
-
example: generateExample(props[key]),
|
|
113
|
-
}])
|
|
114
|
-
),
|
|
115
|
-
example: JSON.stringify(exampleArgs, null, 2),
|
|
116
|
-
fullExample: `mcp_call({ server: "${server}", tool: "${tool}", args_json: ${JSON.stringify(JSON.stringify(exampleArgs))} })`,
|
|
117
|
-
},
|
|
118
|
-
};
|
|
119
|
-
},
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
framework.registerTool({
|
|
123
|
-
name: 'mcp_call',
|
|
124
|
-
description: '调用 MCP 服务器工具。args_json 是包含实际参数的 JSON 字符串',
|
|
125
|
-
inputSchema: z.object({
|
|
126
|
-
server: z.string().describe('MCP 服务器名称'),
|
|
127
|
-
tool: z.string().describe('工具名称'),
|
|
128
|
-
args_json: z.string().describe('参数 JSON 字符串'),
|
|
129
|
-
}),
|
|
130
|
-
execute: async (args) => {
|
|
131
|
-
const { server, tool, args_json } = args;
|
|
132
|
-
const clientInfo = this._clients.get(server);
|
|
133
|
-
if (!clientInfo) return { success: false, error: `MCP server '${server}' not found` };
|
|
134
|
-
|
|
135
|
-
let finalArgs = {};
|
|
136
|
-
if (args_json) {
|
|
137
|
-
try { finalArgs = JSON.parse(args_json); }
|
|
138
|
-
catch (e) { return { success: false, error: `args_json 格式错误: ${args_json}` }; }
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
if (!finalArgs || Object.keys(finalArgs).length === 0) {
|
|
142
|
-
const toolInfo = clientInfo.tools.find((t) => t.name === tool);
|
|
143
|
-
if (toolInfo) {
|
|
144
|
-
const schema = extractSchema(toolInfo.inputSchema);
|
|
145
|
-
const required = schema.required || [];
|
|
146
|
-
return { success: false, error: `参数为空!必须提供: ${required.join(', ')}`, hint: `正确格式: {"${required[0]}": "具体值"}` };
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
try {
|
|
151
|
-
const mcpTool = clientInfo.toolObjects?.[tool];
|
|
152
|
-
if (!mcpTool) return { success: false, error: `Tool '${tool}' not found on server '${server}'` };
|
|
153
|
-
|
|
154
|
-
const mcpToolName = `${server}:${tool}`;
|
|
155
|
-
framework.emit('tool:call', { name: mcpToolName, args: finalArgs, source: 'mcp' });
|
|
156
|
-
framework.emit('tool-call', { name: mcpToolName, args: finalArgs, source: 'mcp' });
|
|
157
|
-
|
|
158
|
-
const execResult = await mcpTool.execute(finalArgs);
|
|
159
|
-
|
|
160
|
-
framework.emit('tool:result', { name: mcpToolName, args: finalArgs, result: execResult, source: 'mcp' });
|
|
161
|
-
return { success: true, data: execResult };
|
|
162
|
-
} catch (err) {
|
|
163
|
-
log.error(` Tool '${tool}' failed:`, err.message);
|
|
164
|
-
framework.emit('tool:error', { name: `${server}:${tool}`, args: finalArgs, error: err.message, source: 'mcp' });
|
|
165
|
-
return { success: false, error: err.message };
|
|
166
|
-
}
|
|
167
|
-
},
|
|
168
|
-
});
|
|
169
|
-
|
|
170
56
|
framework.registerTool({
|
|
171
57
|
name: 'mcp_list_servers',
|
|
172
58
|
description: '列出已连接的 MCP 服务器及其工具',
|
|
@@ -235,9 +121,12 @@ class MCPExecutorPlugin extends Plugin {
|
|
|
235
121
|
if (!clientInfo) return { success: false, error: `MCP server '${server}' not found` };
|
|
236
122
|
try { clientInfo.client.close?.(); } catch (e) { /* ignore */ }
|
|
237
123
|
this._clients.delete(server);
|
|
238
|
-
|
|
239
|
-
|
|
124
|
+
// 注销该服务器的所有工具(this.tools 中的 key 就是 MCP 原始工具名)
|
|
125
|
+
for (const t of clientInfo.tools) {
|
|
126
|
+
if (this.tools[t.name]) delete this.tools[t.name];
|
|
240
127
|
}
|
|
128
|
+
// 同时注销对应的扩展(mcp:<server>)
|
|
129
|
+
this._unregisterExtension(server);
|
|
241
130
|
await this._saveMCPServerEnabled(server, false);
|
|
242
131
|
this._refreshAllAgentsMCPPrompt(this._framework);
|
|
243
132
|
return { success: true, data: `MCP 服务器 '${server}' 已关闭` };
|
|
@@ -343,15 +232,23 @@ class MCPExecutorPlugin extends Plugin {
|
|
|
343
232
|
toolObjects[toolName] = tool;
|
|
344
233
|
toolsInfo.push({ name: toolName, description: tool.description || '', inputSchema: tool.inputSchema || {} });
|
|
345
234
|
|
|
346
|
-
|
|
347
|
-
this.tools[
|
|
348
|
-
name:
|
|
349
|
-
description:
|
|
235
|
+
// 直接调用底层 MCP 工具(不再走 mcp_call 中转)
|
|
236
|
+
this.tools[toolName] = {
|
|
237
|
+
name: toolName,
|
|
238
|
+
description: tool.description || '',
|
|
350
239
|
inputSchema: tool.inputSchema || {},
|
|
351
240
|
execute: async (args) => {
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
241
|
+
try {
|
|
242
|
+
const mcpToolName = `${name}:${toolName}`;
|
|
243
|
+
framework?.emit?.('tool:call', { name: mcpToolName, args: args || {}, source: 'mcp' });
|
|
244
|
+
const execResult = await tool.execute(args || {});
|
|
245
|
+
framework?.emit?.('tool:result', { name: mcpToolName, args: args || {}, result: execResult, source: 'mcp' });
|
|
246
|
+
return { success: true, data: execResult };
|
|
247
|
+
} catch (err) {
|
|
248
|
+
log.error(` Tool '${toolName}' failed:`, err.message);
|
|
249
|
+
framework?.emit?.('tool:error', { name: `${name}:${toolName}`, args: args || {}, error: err.message, source: 'mcp' });
|
|
250
|
+
return { success: false, error: err.message };
|
|
251
|
+
}
|
|
355
252
|
},
|
|
356
253
|
};
|
|
357
254
|
}
|
|
@@ -359,10 +256,89 @@ class MCPExecutorPlugin extends Plugin {
|
|
|
359
256
|
|
|
360
257
|
this._clients.set(name, { client, tools: toolsInfo, toolObjects, enabled: config.enabled !== false });
|
|
361
258
|
|
|
259
|
+
// 把整个 MCP 服务器注册为扩展(mcp:<servername>),让 ext_call / ext_skill 统一调度
|
|
260
|
+
this._registerAsExtension(name, toolsInfo, toolObjects);
|
|
261
|
+
|
|
262
|
+
// 触发 mcp 工具变化事件,通知 extension-executor 刷新
|
|
263
|
+
this._framework?.emit?.('mcp:tools-changed', { name, toolsCount: toolsInfo.length });
|
|
264
|
+
|
|
362
265
|
log.debug(` Added server '${name}' with ${toolsInfo.length} tools`);
|
|
363
266
|
return { success: true, name, toolsCount: toolsInfo.length };
|
|
364
267
|
}
|
|
365
268
|
|
|
269
|
+
/**
|
|
270
|
+
* 把 MCP 服务器注册为扩展插件(mcp:<servername>)
|
|
271
|
+
* 使其能通过 ext_call({plugin:'mcp:<server>', tool:'xxx', args:{...}}) 调用
|
|
272
|
+
* 通过 ext_skill({plugin:'mcp:<server>'}) 获取参数说明
|
|
273
|
+
*/
|
|
274
|
+
_registerAsExtension(serverName, toolsInfo, toolObjects) {
|
|
275
|
+
const extExecutor = this._framework?.pluginManager?.get('extension-executor');
|
|
276
|
+
if (!extExecutor || typeof extExecutor.registerTool !== 'function') return;
|
|
277
|
+
|
|
278
|
+
const extName = `mcp:${serverName}`;
|
|
279
|
+
// 优先从 server config 取 description,否则用 server name
|
|
280
|
+
const serverConfig = this._serverConfigs.get(serverName) || {};
|
|
281
|
+
const extDescription = serverConfig.description
|
|
282
|
+
? serverConfig.description
|
|
283
|
+
: `MCP 服务器 ${serverName}`;
|
|
284
|
+
const pluginInfo = {
|
|
285
|
+
name: extName,
|
|
286
|
+
description: extDescription,
|
|
287
|
+
version: '1.0.0',
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
for (const toolInfo of toolsInfo) {
|
|
291
|
+
const mcpTool = toolObjects[toolInfo.name];
|
|
292
|
+
if (!mcpTool) continue;
|
|
293
|
+
|
|
294
|
+
// 把 JSON schema 转换为 Zod schema(用于 AI SDK 的工具参数定义)
|
|
295
|
+
// 同时保留原始 JSON schema 作为 _rawSchema,让 ext_skill 能渲染出清晰的参数说明
|
|
296
|
+
const rawSchema = extractSchema(toolInfo.inputSchema);
|
|
297
|
+
let inputSchema = rawSchema;
|
|
298
|
+
try {
|
|
299
|
+
if (rawSchema && rawSchema.properties) {
|
|
300
|
+
const zodSchema = this._jsonSchemaToZod(rawSchema);
|
|
301
|
+
if (zodSchema) inputSchema = zodSchema;
|
|
302
|
+
}
|
|
303
|
+
} catch (e) { /* fallback: 保留原始 schema */ }
|
|
304
|
+
|
|
305
|
+
extExecutor.registerTool(extName, pluginInfo, {
|
|
306
|
+
name: toolInfo.name,
|
|
307
|
+
description: toolInfo.description || '',
|
|
308
|
+
inputSchema,
|
|
309
|
+
_rawSchema: rawSchema, // 原始 JSON schema,用于 ext_skill 渲染
|
|
310
|
+
execute: async (args, framework) => {
|
|
311
|
+
try {
|
|
312
|
+
const finalArgs = args || {};
|
|
313
|
+
const mcpToolName = `${serverName}:${toolInfo.name}`;
|
|
314
|
+
framework?.emit?.('tool:call', { name: mcpToolName, args: finalArgs, source: 'mcp' });
|
|
315
|
+
const result = await mcpTool.execute(finalArgs);
|
|
316
|
+
framework?.emit?.('tool:result', { name: mcpToolName, args: finalArgs, result, source: 'mcp' });
|
|
317
|
+
return { success: true, data: result };
|
|
318
|
+
} catch (err) {
|
|
319
|
+
log.error(` Tool '${toolInfo.name}' failed:`, err.message);
|
|
320
|
+
return { success: false, error: err.message };
|
|
321
|
+
}
|
|
322
|
+
},
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* 注销 MCP 服务器对应的扩展
|
|
329
|
+
*/
|
|
330
|
+
_unregisterExtension(serverName) {
|
|
331
|
+
const extExecutor = this._framework?.pluginManager?.get('extension-executor');
|
|
332
|
+
if (!extExecutor || typeof extExecutor._unregisterTool !== 'function') return;
|
|
333
|
+
|
|
334
|
+
const extName = `mcp:${serverName}`;
|
|
335
|
+
const clientInfo = this._clients.get(serverName);
|
|
336
|
+
const toolNames = clientInfo?.tools?.map((t) => t.name) || [];
|
|
337
|
+
for (const toolName of toolNames) {
|
|
338
|
+
extExecutor._unregisterTool(extName, toolName);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
366
342
|
async removeServer(name) {
|
|
367
343
|
const clientInfo = this._clients.get(name);
|
|
368
344
|
if (clientInfo) {
|
|
@@ -373,9 +349,13 @@ class MCPExecutorPlugin extends Plugin {
|
|
|
373
349
|
}
|
|
374
350
|
} catch (e) { /* ignore */ }
|
|
375
351
|
for (const toolName of Object.keys(this.tools)) {
|
|
376
|
-
if (toolName
|
|
352
|
+
if (this.tools[toolName]?.description?.includes?.(`[${name}]`)) {
|
|
353
|
+
delete this.tools[toolName];
|
|
354
|
+
}
|
|
377
355
|
}
|
|
378
356
|
this._clients.delete(name);
|
|
357
|
+
this._unregisterExtension(name);
|
|
358
|
+
this._framework?.emit?.('mcp:tools-changed', { name, removed: true });
|
|
379
359
|
}
|
|
380
360
|
}
|
|
381
361
|
|
|
@@ -91,7 +91,7 @@ class Framework extends EventEmitter {
|
|
|
91
91
|
// Subsystems
|
|
92
92
|
this.toolRegistry = new ToolRegistry();
|
|
93
93
|
this.pluginManager = new PluginManager(this);
|
|
94
|
-
this.promptRegistry = new PromptRegistry();
|
|
94
|
+
this.promptRegistry = new PromptRegistry(this);
|
|
95
95
|
|
|
96
96
|
// Agent management
|
|
97
97
|
this._agents = [];
|
|
@@ -206,16 +206,70 @@ class Framework extends EventEmitter {
|
|
|
206
206
|
async disablePlugin(name) { await this.pluginManager.disable(name); return this; }
|
|
207
207
|
updatePluginConfig(name, config) { return this.pluginManager.updatePluginConfig(name, config); }
|
|
208
208
|
|
|
209
|
-
registerTool(tool) {
|
|
209
|
+
registerTool(tool) {
|
|
210
|
+
this.toolRegistry.register(tool);
|
|
211
|
+
// 通知所有 Agent 使 ChatHandler._aiToolsCache 失效。
|
|
212
|
+
// 🔴 修复:热重载插件时,插件调 framework.registerTool 注册新工具,
|
|
213
|
+
// 但 ChatHandler 的 _aiToolsCache 只在 agent.registerTool 被调用时清,
|
|
214
|
+
// 所以 reload 后 LLM 仍使用旧的 execute 闭包。
|
|
215
|
+
if (Array.isArray(this._agents)) {
|
|
216
|
+
for (const agent of this._agents) {
|
|
217
|
+
const ch = agent && agent._chatHandler;
|
|
218
|
+
if (ch) {
|
|
219
|
+
ch._aiToolsCache = null;
|
|
220
|
+
ch._aiToolsCacheKey = '';
|
|
221
|
+
ch._toolsTokensCacheVersion = 0;
|
|
222
|
+
if (ch._toolExecutor && ch._toolExecutor._tools) {
|
|
223
|
+
ch._toolExecutor._tools.set(tool.name, tool);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
this.emit('tool:registered-changed', tool);
|
|
229
|
+
return this;
|
|
230
|
+
}
|
|
210
231
|
tool(toolDef) {
|
|
211
232
|
const tool = { ...toolDef };
|
|
212
233
|
if (tool.input && !tool.inputSchema) tool.inputSchema = tool.input;
|
|
213
234
|
delete tool.input;
|
|
214
235
|
this.toolRegistry.register(tool);
|
|
236
|
+
// 同步使所有 Agent 的 ChatHandler 缓存失效并同步工具。
|
|
237
|
+
if (Array.isArray(this._agents)) {
|
|
238
|
+
for (const agent of this._agents) {
|
|
239
|
+
const ch = agent && agent._chatHandler;
|
|
240
|
+
if (ch) {
|
|
241
|
+
ch._aiToolsCache = null;
|
|
242
|
+
ch._aiToolsCacheKey = '';
|
|
243
|
+
ch._toolsTokensCacheVersion = 0;
|
|
244
|
+
if (ch._toolExecutor && ch._toolExecutor._tools) {
|
|
245
|
+
ch._toolExecutor._tools.set(tool.name, tool);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
215
250
|
return this;
|
|
216
251
|
}
|
|
217
252
|
getTools() { return this.toolRegistry.getAll(); }
|
|
218
253
|
|
|
254
|
+
unregisterTool(name) {
|
|
255
|
+
if (this.toolRegistry.unregister) this.toolRegistry.unregister(name);
|
|
256
|
+
if (Array.isArray(this._agents)) {
|
|
257
|
+
for (const agent of this._agents) {
|
|
258
|
+
const ch = agent && agent._chatHandler;
|
|
259
|
+
if (ch) {
|
|
260
|
+
ch._aiToolsCache = null;
|
|
261
|
+
ch._aiToolsCacheKey = '';
|
|
262
|
+
ch._toolsTokensCacheVersion = 0;
|
|
263
|
+
if (ch._toolExecutor && ch._toolExecutor._tools) {
|
|
264
|
+
ch._toolExecutor._tools.delete(name);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
this.emit('tool:unregistered-changed', { name });
|
|
270
|
+
return this;
|
|
271
|
+
}
|
|
272
|
+
|
|
219
273
|
// tools 对象别名
|
|
220
274
|
get tools() {
|
|
221
275
|
const self = this;
|
|
@@ -912,23 +966,23 @@ class Framework extends EventEmitter {
|
|
|
912
966
|
return true;
|
|
913
967
|
},
|
|
914
968
|
/**
|
|
915
|
-
* 调用 MCP
|
|
969
|
+
* 调用 MCP 工具(通过 ext_call 统一调度)
|
|
916
970
|
* @param {string} server 服务器名
|
|
917
971
|
* @param {string} tool 工具名
|
|
918
972
|
* @param {Object} args 参数
|
|
919
973
|
*/
|
|
920
974
|
call: async (server, tool, args) => {
|
|
921
|
-
return await self.executeTool('
|
|
922
|
-
server
|
|
975
|
+
return await self.executeTool('ext_call', {
|
|
976
|
+
plugin: `mcp:${server}`,
|
|
923
977
|
tool,
|
|
924
|
-
|
|
978
|
+
args: args || {},
|
|
925
979
|
});
|
|
926
980
|
},
|
|
927
981
|
/**
|
|
928
|
-
*
|
|
982
|
+
* 获取工具调用参数说明(通过 ext_skill)
|
|
929
983
|
*/
|
|
930
|
-
getToolSchema: async (server,
|
|
931
|
-
return await self.executeTool('
|
|
984
|
+
getToolSchema: async (server, _tool) => {
|
|
985
|
+
return await self.executeTool('ext_skill', { plugin: `mcp:${server}` });
|
|
932
986
|
},
|
|
933
987
|
/**
|
|
934
988
|
* 动态开启/关闭 MCP 服务器
|
|
@@ -946,7 +1000,14 @@ class Framework extends EventEmitter {
|
|
|
946
1000
|
}
|
|
947
1001
|
|
|
948
1002
|
getMainAgent() { return this._mainAgent; }
|
|
949
|
-
|
|
1003
|
+
/**
|
|
1004
|
+
* 获取 main agent 的完整系统提示词
|
|
1005
|
+
* 统一走 framework.prompts.build()(含 _originalPrompt + 注册表所有 parts)
|
|
1006
|
+
* 这是 LLM 实际看到的完整 system prompt
|
|
1007
|
+
*/
|
|
1008
|
+
getSystemPrompt() {
|
|
1009
|
+
return this.prompts?.build?.() || '';
|
|
1010
|
+
}
|
|
950
1011
|
getPluginInstance(name) { return this.pluginManager.get(name) || null; }
|
|
951
1012
|
getAgents() { return [...this._agents]; }
|
|
952
1013
|
listSessionContexts() { return Array.from(this._sessionContexts.keys()); }
|
|
@@ -1007,7 +1068,10 @@ class Framework extends EventEmitter {
|
|
|
1007
1068
|
const { SessionManager } = require('../session/session');
|
|
1008
1069
|
manager = SessionManager.open(sessionFile, sessionDir, cwd);
|
|
1009
1070
|
}
|
|
1010
|
-
} catch {
|
|
1071
|
+
} catch (err) {
|
|
1072
|
+
// Log error but continue - will create new session if load failed
|
|
1073
|
+
this.logger.warn(`[Framework] Failed to load session '${sessionId}': ${err.message}`);
|
|
1074
|
+
}
|
|
1011
1075
|
if (!manager) {
|
|
1012
1076
|
const { SessionManager } = require('../session/session');
|
|
1013
1077
|
manager = new SessionManager(cwd, sessionDir, sessionFile, true);
|
|
@@ -1032,7 +1096,9 @@ class Framework extends EventEmitter {
|
|
|
1032
1096
|
this._sessionContexts.set(sessionId, manager);
|
|
1033
1097
|
this.emit('session:context-created', { sessionId, manager });
|
|
1034
1098
|
}
|
|
1035
|
-
} catch {
|
|
1099
|
+
} catch (err) {
|
|
1100
|
+
this.logger.warn(`[Framework] Failed to load session context '${sessionId}': ${err.message}`);
|
|
1101
|
+
}
|
|
1036
1102
|
return manager || null;
|
|
1037
1103
|
}
|
|
1038
1104
|
|
package/src/index.js
CHANGED
|
@@ -11,6 +11,10 @@
|
|
|
11
11
|
// 加载工作目录 .env 配置(优先于系统环境变量)
|
|
12
12
|
require('dotenv').config({ override: true });
|
|
13
13
|
|
|
14
|
+
// 安装全局错误捕获:把 unhandledRejection / uncaughtException 的完整堆栈
|
|
15
|
+
// 落盘到 .foliko/error.log。需要堆栈时直接 cat 这个文件即可。
|
|
16
|
+
require('./common/error-capture').install();
|
|
17
|
+
|
|
14
18
|
// ============================================================
|
|
15
19
|
// Framework
|
|
16
20
|
// ============================================================
|