foliko 1.1.70 → 1.1.72
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/cli/src/ui/chat-ui.js +90 -36
- package/package.json +1 -1
- package/plugins/extension-executor-plugin.js +49 -22
- package/plugins/feishu-plugin.js +9 -1
- package/plugins/qq-plugin.js +9 -1
- package/plugins/telegram-plugin.js +9 -1
- package/plugins/weixin-plugin.js +9 -1
- package/src/capabilities/skill-manager.js +53 -15
- package/src/core/agent-chat.js +10 -0
package/cli/src/ui/chat-ui.js
CHANGED
|
@@ -59,7 +59,8 @@ const markdownTheme = {
|
|
|
59
59
|
strikethrough: (text) => chalk.strikethrough(text),
|
|
60
60
|
underline: (text) => chalk.underline(text),
|
|
61
61
|
highlightCode: (code, lang) => {
|
|
62
|
-
const
|
|
62
|
+
const language = lang && hl.supportsLanguage(lang) ? lang : 'text';
|
|
63
|
+
const highlighted = hl.highlight(code, { language, theme: 'nord' });
|
|
63
64
|
return highlighted.split('\n');
|
|
64
65
|
},
|
|
65
66
|
codeBlockIndent: "",
|
|
@@ -90,28 +91,25 @@ class ChatUI {
|
|
|
90
91
|
this.sessionId = options.sessionId || 'cli_default';
|
|
91
92
|
this.sessionPlugin = null;
|
|
92
93
|
|
|
93
|
-
// 获取 skill
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
const extExecutor = agent.framework.pluginManager?.get('extension-executor');
|
|
97
|
-
if (extExecutor && typeof extExecutor.getSkillCommands === 'function') {
|
|
98
|
-
skillCommands.push(...extExecutor.getSkillCommands());
|
|
99
|
-
}
|
|
100
|
-
}
|
|
94
|
+
// 获取 skill 命令列表(保存到 this,供后续刷新)
|
|
95
|
+
this.skillCommands = [];
|
|
96
|
+
this._refreshSkillCommands(agent.framework);
|
|
101
97
|
|
|
102
|
-
const baseCommands = [
|
|
98
|
+
const baseCommands = this.baseCommands = [
|
|
103
99
|
{ name: "compress", description: "压缩记录" },
|
|
104
100
|
{ name: "clear", description: "清除记录" },
|
|
105
101
|
{ name: "exit", description: "退出" },
|
|
102
|
+
{ name: "help", description: "显示帮助信息" },
|
|
103
|
+
{ name: "reload", description: "重载技能" },
|
|
106
104
|
];
|
|
107
105
|
|
|
108
|
-
|
|
109
|
-
[...baseCommands, ...skillCommands],
|
|
106
|
+
this.autocompleteProvider = new CombinedAutocompleteProvider(
|
|
107
|
+
[...baseCommands, ...this.skillCommands],
|
|
110
108
|
process.cwd(),
|
|
111
109
|
);
|
|
112
|
-
|
|
110
|
+
|
|
113
111
|
const editor=this.editor = new Editor(this.tui, editorTheme,{paddingX:1});
|
|
114
|
-
editor.setAutocompleteProvider(autocompleteProvider);
|
|
112
|
+
this.editor.setAutocompleteProvider(this.autocompleteProvider);
|
|
115
113
|
this.editor.onSubmit=this.handleOnSubmit.bind(this)
|
|
116
114
|
this.tui.addChild(this.editor);
|
|
117
115
|
this.tui.setFocus(this.editor);
|
|
@@ -194,13 +192,13 @@ class ChatUI {
|
|
|
194
192
|
ERROR: 'red',
|
|
195
193
|
}
|
|
196
194
|
// 监听错误日志,显示到 tooler
|
|
197
|
-
this._logHandler = (data) => {
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
};
|
|
203
|
-
logger.on('log', this._logHandler);
|
|
195
|
+
// this._logHandler = (data) => {
|
|
196
|
+
// if(!Object.keys(level_dict).includes(data.level))return;
|
|
197
|
+
// const level=folikoTerracotta(`[${data.level}]`)
|
|
198
|
+
// const level_key=level_dict[data.level]||'dim'
|
|
199
|
+
// this.statusBar.notifier.show(`${level} ${chalk[level_key](data.message)}`)
|
|
200
|
+
// };
|
|
201
|
+
// logger.on('log', this._logHandler);
|
|
204
202
|
|
|
205
203
|
// 监听通知事件,显示到通知区域
|
|
206
204
|
this._notificationHandler = (data) => {
|
|
@@ -230,6 +228,13 @@ class ChatUI {
|
|
|
230
228
|
}
|
|
231
229
|
};
|
|
232
230
|
agent.framework.on('agent:usage', this._usageHandler);
|
|
231
|
+
|
|
232
|
+
// 监听 skill 热重载事件,刷新 autocomplete
|
|
233
|
+
this._skillReloadedHandler = () => {
|
|
234
|
+
this._refreshSkillCommands(agent.framework);
|
|
235
|
+
this._refreshAutocomplete();
|
|
236
|
+
};
|
|
237
|
+
agent.framework.on('skill:reloaded', this._skillReloadedHandler);
|
|
233
238
|
}
|
|
234
239
|
}
|
|
235
240
|
|
|
@@ -242,10 +247,10 @@ class ChatUI {
|
|
|
242
247
|
clearTimeout(this._streamBufferTimer);
|
|
243
248
|
this._streamBufferTimer = null;
|
|
244
249
|
}
|
|
245
|
-
if (this._logHandler && this.agent.framework) {
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
}
|
|
250
|
+
// if (this._logHandler && this.agent.framework) {
|
|
251
|
+
// logger.off('log', this._logHandler);
|
|
252
|
+
// this._logHandler = null;
|
|
253
|
+
// }
|
|
249
254
|
if (this._notificationHandler && this.agent.framework) {
|
|
250
255
|
this.agent.framework.off('notification', this._notificationHandler);
|
|
251
256
|
this._notificationHandler = null;
|
|
@@ -254,6 +259,10 @@ class ChatUI {
|
|
|
254
259
|
this.agent.framework.off('agent:usage', this._usageHandler);
|
|
255
260
|
this._usageHandler = null;
|
|
256
261
|
}
|
|
262
|
+
if (this._skillReloadedHandler && this.agent.framework) {
|
|
263
|
+
this.agent.framework.off('skill:reloaded', this._skillReloadedHandler);
|
|
264
|
+
this._skillReloadedHandler = null;
|
|
265
|
+
}
|
|
257
266
|
}
|
|
258
267
|
|
|
259
268
|
clear_message_done(){
|
|
@@ -275,6 +284,7 @@ class ChatUI {
|
|
|
275
284
|
queue.add(function(){
|
|
276
285
|
var next = this.next;
|
|
277
286
|
self.agent.sendMessage(message, { sessionId:self.sessionId }).then(next).catch(function(e){
|
|
287
|
+
self.create_message(chalk.red(`[错误] ${e?.message || e || '发送消息失败'}\n`), colored('● ', RED))
|
|
278
288
|
self.clear_message_done();
|
|
279
289
|
});
|
|
280
290
|
})
|
|
@@ -305,6 +315,20 @@ class ChatUI {
|
|
|
305
315
|
return true;
|
|
306
316
|
}
|
|
307
317
|
|
|
318
|
+
if (cmd === '/reload') {
|
|
319
|
+
try {
|
|
320
|
+
const result = await this.agent.framework.executeTool('reloadSkills', {});
|
|
321
|
+
if (result.success !== false) {
|
|
322
|
+
this.create_message(`${colored('[提示]', CYAN)} 技能已重载\n`,chalk.dim('● '))
|
|
323
|
+
} else {
|
|
324
|
+
this.create_message(`${colored('[错误]', RED)} 重载失败: ${result.error}\n`,chalk.dim('● '))
|
|
325
|
+
}
|
|
326
|
+
} catch (err) {
|
|
327
|
+
this.create_message(`${colored('[错误]', RED)} 重载失败: ${err.message}\n`,chalk.dim('● '))
|
|
328
|
+
}
|
|
329
|
+
return true;
|
|
330
|
+
}
|
|
331
|
+
|
|
308
332
|
// /help 命令显示所有注册的命令
|
|
309
333
|
if (cmd === '/help') {
|
|
310
334
|
const { getCommandRegistry } = require('../../../src/core/command-registry');
|
|
@@ -312,17 +336,18 @@ class ChatUI {
|
|
|
312
336
|
const commands = registry.getAllCommands();
|
|
313
337
|
|
|
314
338
|
let helpText = `${colored('[帮助]', CYAN)} 可用命令:\n`;
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
339
|
+
this.baseCommands.forEach(c => {
|
|
340
|
+
helpText += ` ${colored('/' + c.name, CYAN)} - ${c.description}\n`;
|
|
341
|
+
})
|
|
342
|
+
|
|
318
343
|
|
|
319
344
|
// 添加 skill 命令
|
|
320
345
|
const extExecutor = this.agent.framework?.pluginManager?.get('extension-executor');
|
|
321
|
-
if (extExecutor && typeof extExecutor.
|
|
322
|
-
const
|
|
323
|
-
if (
|
|
346
|
+
if (extExecutor && typeof extExecutor.getSkillCommands === 'function') {
|
|
347
|
+
const skillCommands = extExecutor.getSkillCommands();
|
|
348
|
+
if (skillCommands.length > 0) {
|
|
324
349
|
helpText += `\n${colored('[技能命令]', CYAN)}\n`;
|
|
325
|
-
helpText +=
|
|
350
|
+
helpText += skillCommands.map(cmd => ` ${colored('/' + cmd.name, CYAN)} - ${cmd.description || '无描述'}`).join('\n') + '\n';
|
|
326
351
|
}
|
|
327
352
|
}
|
|
328
353
|
|
|
@@ -350,7 +375,10 @@ class ChatUI {
|
|
|
350
375
|
args: { args: cmdArgs }
|
|
351
376
|
});
|
|
352
377
|
if (result.success !== false) {
|
|
353
|
-
this.create_message(`${colored('[
|
|
378
|
+
this.create_message(`${colored('[指令] '+cmdName, CYAN)}\n\n ${result.data || result}\n`, chalk.green('● '));
|
|
379
|
+
return true;
|
|
380
|
+
}else{
|
|
381
|
+
this.create_message(`${colored('[指令] '+cmdName, RED)}\n\n ${result.error || '未知错误'}\n`, chalk.red('● '));
|
|
354
382
|
return true;
|
|
355
383
|
}
|
|
356
384
|
} catch (err) {
|
|
@@ -371,10 +399,10 @@ class ChatUI {
|
|
|
371
399
|
framework: this.agent.framework,
|
|
372
400
|
};
|
|
373
401
|
registry.execute(cmdName, cmdArgs, context).catch(err => {
|
|
374
|
-
this.create_message(`${colored('[命令错误]', 'red')} ${err.message}\n`, chalk.
|
|
402
|
+
this.create_message(`${colored('[命令错误]', 'red')} ${err.message}\n`, chalk.red('● '));
|
|
375
403
|
});
|
|
376
404
|
} catch (err) {
|
|
377
|
-
this.create_message(`${colored('[命令错误]', 'red')} ${err.message}\n`, chalk.
|
|
405
|
+
this.create_message(`${colored('[命令错误]', 'red')} ${err.message}\n`, chalk.red('● '));
|
|
378
406
|
}
|
|
379
407
|
return true;
|
|
380
408
|
}
|
|
@@ -557,6 +585,32 @@ class ChatUI {
|
|
|
557
585
|
this.statusBar.tooler.setText(`${colored('[错误]', RED)} 无 AgentChatHandler`);
|
|
558
586
|
}
|
|
559
587
|
}
|
|
588
|
+
/**
|
|
589
|
+
* 刷新 skill 命令列表(用于热重载)
|
|
590
|
+
*/
|
|
591
|
+
_refreshSkillCommands(framework) {
|
|
592
|
+
this.skillCommands = [];
|
|
593
|
+
if (!framework) return;
|
|
594
|
+
const extExecutor = framework.pluginManager?.get('extension-executor');
|
|
595
|
+
if (extExecutor && typeof extExecutor.getSkillCommands === 'function') {
|
|
596
|
+
this.skillCommands.push(...extExecutor.getSkillCommands());
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
/**
|
|
601
|
+
* 刷新自动补全提供者(skill 命令热重载时调用)
|
|
602
|
+
*/
|
|
603
|
+
_refreshAutocomplete() {
|
|
604
|
+
if (!this.editor) return;
|
|
605
|
+
;
|
|
606
|
+
// 重新构建 CombinedAutocompleteProvider 并更新引用
|
|
607
|
+
const { CombinedAutocompleteProvider } = require('@earendil-works/pi-tui');
|
|
608
|
+
this.autocompleteProvider = new CombinedAutocompleteProvider(
|
|
609
|
+
[...this.baseCommands, ...this.skillCommands],
|
|
610
|
+
process.cwd()
|
|
611
|
+
);
|
|
612
|
+
this.editor.setAutocompleteProvider(this.autocompleteProvider);
|
|
613
|
+
}
|
|
560
614
|
/**
|
|
561
615
|
* 刷新流式输出缓冲区:合并高频 chunk 后统一渲染,减少 Markdown 重复解析
|
|
562
616
|
*/
|
|
@@ -607,7 +661,7 @@ class ChatUI {
|
|
|
607
661
|
this._streamBufferTimer = setTimeout(() => this._flushStreamBuffer(), 0);
|
|
608
662
|
}
|
|
609
663
|
} else if (chunk.type === 'tool-call') {
|
|
610
|
-
const args = chunk.input ? JSON.stringify(chunk.input).slice(0,
|
|
664
|
+
const args = chunk.input ? JSON.stringify(chunk.input).slice(0, 30) : '';
|
|
611
665
|
this.statusBar.tooler.show(`${chalk.yellow('[Tool]')} ${folikoGold(chunk.toolName)} ${chalk.gray(args+'...')}`)
|
|
612
666
|
} else if(chunk.type==='tool-result'){
|
|
613
667
|
const result = chunk.result;
|
package/package.json
CHANGED
|
@@ -42,6 +42,11 @@ class ExtensionExecutorPlugin extends Plugin {
|
|
|
42
42
|
log.debug('Framework ready, rescanning all plugin tools...');
|
|
43
43
|
const plugins = framework.pluginManager.getAll();
|
|
44
44
|
for (const { instance: plugin } of plugins) {
|
|
45
|
+
// skill-manager 插件的工具已通过 Skill.registerCommands 直接注册,
|
|
46
|
+
// 不需要再通过 _scanPluginTools 扫描,避免重复注册
|
|
47
|
+
if (plugin.name === 'skill-manager') {
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
45
50
|
this._scanPluginTools(plugin);
|
|
46
51
|
}
|
|
47
52
|
// 刷新所有 Agent 的扩展提示词
|
|
@@ -53,8 +58,10 @@ class ExtensionExecutorPlugin extends Plugin {
|
|
|
53
58
|
|
|
54
59
|
/**
|
|
55
60
|
* 扫描插件的 this.tools 并注册到扩展系统
|
|
61
|
+
* @param {Object} plugin - 插件实例
|
|
62
|
+
* @param {string} [extName] - 可选的扩展名(用于统一命名)
|
|
56
63
|
*/
|
|
57
|
-
_scanPluginTools(plugin) {
|
|
64
|
+
_scanPluginTools(plugin, extName) {
|
|
58
65
|
if (!plugin || !plugin.tools || typeof plugin.tools !== 'object') {
|
|
59
66
|
return;
|
|
60
67
|
}
|
|
@@ -62,9 +69,12 @@ class ExtensionExecutorPlugin extends Plugin {
|
|
|
62
69
|
const pluginName = plugin.name;
|
|
63
70
|
if (!pluginName) return;
|
|
64
71
|
|
|
72
|
+
// 确定扩展名:优先使用 extName,否则使用 pluginName
|
|
73
|
+
const useExtName = extName || pluginName;
|
|
74
|
+
|
|
65
75
|
// 获取插件信息
|
|
66
76
|
const pluginInfo = {
|
|
67
|
-
name:
|
|
77
|
+
name: useExtName,
|
|
68
78
|
description: plugin.description || '',
|
|
69
79
|
version: plugin.version || '1.0.0',
|
|
70
80
|
};
|
|
@@ -73,7 +83,7 @@ class ExtensionExecutorPlugin extends Plugin {
|
|
|
73
83
|
for (const [toolName, toolDef] of Object.entries(plugin.tools)) {
|
|
74
84
|
if (!toolDef || typeof toolDef !== 'object') continue;
|
|
75
85
|
|
|
76
|
-
this.registerTool(
|
|
86
|
+
this.registerTool(useExtName, pluginInfo, {
|
|
77
87
|
name: toolName,
|
|
78
88
|
description: toolDef.description || '',
|
|
79
89
|
inputSchema: toolDef.inputSchema,
|
|
@@ -81,7 +91,7 @@ class ExtensionExecutorPlugin extends Plugin {
|
|
|
81
91
|
});
|
|
82
92
|
}
|
|
83
93
|
|
|
84
|
-
log.debug(` Scanned ${Object.keys(plugin.tools).length} tools from plugin '${pluginName}'`);
|
|
94
|
+
log.debug(` Scanned ${Object.keys(plugin.tools).length} tools from plugin '${pluginName}' (as '${useExtName}')`);
|
|
85
95
|
|
|
86
96
|
// 刷新所有 Agent 的扩展提示词
|
|
87
97
|
if (this._framework && this._framework._ready) {
|
|
@@ -100,21 +110,27 @@ class ExtensionExecutorPlugin extends Plugin {
|
|
|
100
110
|
const pluginName = plugin.name;
|
|
101
111
|
if (!pluginName) return;
|
|
102
112
|
|
|
103
|
-
//
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
113
|
+
// skill-manager 插件使用 'skill' 作为扩展名(保持与 Skill.registerCommands 一致)
|
|
114
|
+
const extName = pluginName === 'skill-manager' ? 'skill' : pluginName;
|
|
115
|
+
|
|
116
|
+
// 先移除旧工具(同时检查 'skill' 和 'skill-manager' 两个扩展名)
|
|
117
|
+
const keysToRemove = [extName, pluginName];
|
|
118
|
+
for (const key of keysToRemove) {
|
|
119
|
+
if (this._extensions.has(key)) {
|
|
120
|
+
const ext = this._extensions.get(key);
|
|
121
|
+
for (const tool of ext.tools || []) {
|
|
122
|
+
// 从 toolRouter 移除
|
|
123
|
+
if (this._framework?.toolRouter) {
|
|
124
|
+
this._framework.toolRouter.unregister(`ext_${tool.name}`);
|
|
125
|
+
}
|
|
110
126
|
}
|
|
127
|
+
this._extensions.delete(key);
|
|
111
128
|
}
|
|
112
|
-
this._extensions.delete(pluginName);
|
|
113
129
|
}
|
|
114
130
|
|
|
115
|
-
//
|
|
116
|
-
this._scanPluginTools(plugin);
|
|
117
|
-
log.info(` Rescanned tools from plugin '${pluginName}'`);
|
|
131
|
+
// 重新扫描工具,使用正确的扩展名
|
|
132
|
+
this._scanPluginTools(plugin, extName);
|
|
133
|
+
log.info(` Rescanned tools from plugin '${pluginName}' (as '${extName}')`);
|
|
118
134
|
}
|
|
119
135
|
|
|
120
136
|
async start(framework) {
|
|
@@ -143,7 +159,7 @@ class ExtensionExecutorPlugin extends Plugin {
|
|
|
143
159
|
if (plugin === 'mcp' && this._mcpExecutor) {
|
|
144
160
|
const mcpToolDef = this._mcpExecutor.tools?.[tool];
|
|
145
161
|
if (mcpToolDef && mcpToolDef.execute) {
|
|
146
|
-
log.info(` ext_call [MCP]: tool=${tool}`);
|
|
162
|
+
//log.info(` ext_call [MCP]: tool=${tool}`);
|
|
147
163
|
return await mcpToolDef.execute(toolArgs || {});
|
|
148
164
|
}
|
|
149
165
|
return { success: false, error: `MCP tool '${tool}' not found` };
|
|
@@ -237,7 +253,7 @@ class ExtensionExecutorPlugin extends Plugin {
|
|
|
237
253
|
name,
|
|
238
254
|
description: ext.description,
|
|
239
255
|
version: ext.version,
|
|
240
|
-
tools: ext.tools.map((t) => ({ name: t.name, description: t.description })),
|
|
256
|
+
tools: ext.tools.filter(t => t && t.name).map((t) => ({ name: t.name, description: t.description })),
|
|
241
257
|
});
|
|
242
258
|
}
|
|
243
259
|
return { success: true, data: extensions };
|
|
@@ -275,6 +291,11 @@ class ExtensionExecutorPlugin extends Plugin {
|
|
|
275
291
|
}
|
|
276
292
|
|
|
277
293
|
registerTool(pluginName, pluginInfo, toolDef) {
|
|
294
|
+
if (!toolDef || !toolDef.name) {
|
|
295
|
+
log.warn(`[ExtensionExecutor] Invalid tool definition for '${pluginName}'`);
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
|
|
278
299
|
if (!this._extensions.has(pluginName)) {
|
|
279
300
|
this._extensions.set(pluginName, {
|
|
280
301
|
name: pluginInfo.name || pluginName,
|
|
@@ -401,6 +422,8 @@ class ExtensionExecutorPlugin extends Plugin {
|
|
|
401
422
|
// Skill 命令按技能名分组显示
|
|
402
423
|
const toolsBySkill = {};
|
|
403
424
|
for (const tool of ext.tools) {
|
|
425
|
+
// 跳过无效工具
|
|
426
|
+
if (!tool || !tool.name) continue;
|
|
404
427
|
// 命令格式: skillname:cmdname
|
|
405
428
|
const parts = tool.name.split(':');
|
|
406
429
|
const skillName = parts[0];
|
|
@@ -425,7 +448,8 @@ class ExtensionExecutorPlugin extends Plugin {
|
|
|
425
448
|
} else {
|
|
426
449
|
desc += `### ${ext.name || name}\n`;
|
|
427
450
|
desc += `${ext.description || '无描述'}\n`;
|
|
428
|
-
|
|
451
|
+
const validTools = ext.tools.filter(t => t && t.name);
|
|
452
|
+
desc += `**工具:** ${validTools.map(t => `\`${t.name}\``).join(', ')}\n\n`;
|
|
429
453
|
}
|
|
430
454
|
}
|
|
431
455
|
|
|
@@ -568,10 +592,12 @@ class ExtensionExecutorPlugin extends Plugin {
|
|
|
568
592
|
getSkillCommands() {
|
|
569
593
|
const ext = this._extensions.get('skill');
|
|
570
594
|
if (!ext) return [];
|
|
571
|
-
return ext.tools
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
595
|
+
return ext.tools
|
|
596
|
+
.filter(t => t && t.name)
|
|
597
|
+
.map(t => ({
|
|
598
|
+
name: t.name.replace(/^([^:]+):(.*)$/, '$1:$2'),
|
|
599
|
+
description: t.description || t.name,
|
|
600
|
+
}));
|
|
575
601
|
}
|
|
576
602
|
|
|
577
603
|
/**
|
|
@@ -583,6 +609,7 @@ class ExtensionExecutorPlugin extends Plugin {
|
|
|
583
609
|
|
|
584
610
|
const lines = [];
|
|
585
611
|
for (const tool of ext.tools) {
|
|
612
|
+
if (!tool || !tool.name) continue;
|
|
586
613
|
const parts = tool.name.split(':');
|
|
587
614
|
if (parts.length === 2) {
|
|
588
615
|
lines.push(`/${parts[0]}:${parts[1]} - ${tool.description || '无描述'}`);
|
package/plugins/feishu-plugin.js
CHANGED
|
@@ -210,6 +210,14 @@ class FeishuPlugin extends Plugin {
|
|
|
210
210
|
case 'compress':
|
|
211
211
|
await this._compressSessionContext(openId)
|
|
212
212
|
break
|
|
213
|
+
case 'reload':
|
|
214
|
+
try {
|
|
215
|
+
await this._framework.executeTool('reloadSkills', {})
|
|
216
|
+
await this._sendMessage(openId, '✅ 技能已重载', originalMsg)
|
|
217
|
+
} catch (err) {
|
|
218
|
+
await this._sendMessage(openId, '❌ 重载失败: ' + err.message, originalMsg)
|
|
219
|
+
}
|
|
220
|
+
break
|
|
213
221
|
case 'history':
|
|
214
222
|
if (this._sessionPlugin) {
|
|
215
223
|
const session = this._sessionPlugin.getSession(`feishu_${openId}`)
|
|
@@ -512,7 +520,7 @@ class FeishuPlugin extends Plugin {
|
|
|
512
520
|
const extExecutor = this._framework?.pluginManager?.get('extension-executor')
|
|
513
521
|
const skillHelp = extExecutor?.getSkillCommandsHelp?.() || ''
|
|
514
522
|
|
|
515
|
-
let helpText = '👋 欢迎使用 AI 助手!\n\n直接发送消息即可与我对话。\n\n可用命令:\n/start - 显示帮助\n/clean - 清除对话上下文\n/compress - 压缩上下文\n/history - 查看历史消息数'
|
|
523
|
+
let helpText = '👋 欢迎使用 AI 助手!\n\n直接发送消息即可与我对话。\n\n可用命令:\n/start - 显示帮助\n/clean - 清除对话上下文\n/compress - 压缩上下文\n/reload - 重载技能\n/history - 查看历史消息数'
|
|
516
524
|
|
|
517
525
|
if (skillHelp) {
|
|
518
526
|
helpText += '\n\n【技能命令】\n' + skillHelp.split('\n').map(line => line.replace(/^\//, '/')).join('\n')
|
package/plugins/qq-plugin.js
CHANGED
|
@@ -701,6 +701,14 @@ class QQPlugin extends Plugin {
|
|
|
701
701
|
case 'compress':
|
|
702
702
|
await this._compressSessionContext(openid)
|
|
703
703
|
break
|
|
704
|
+
case 'reload':
|
|
705
|
+
try {
|
|
706
|
+
await this._framework.executeTool('reloadSkills', {})
|
|
707
|
+
await this._sendMessage(openid, '✅ 技能已重载')
|
|
708
|
+
} catch (err) {
|
|
709
|
+
await this._sendMessage(openid, '❌ 重载失败: ' + err.message)
|
|
710
|
+
}
|
|
711
|
+
break
|
|
704
712
|
case 'history':
|
|
705
713
|
const sessionInfo = this._getSessionAgent(openid)
|
|
706
714
|
if (sessionInfo && this._framework) {
|
|
@@ -752,7 +760,7 @@ class QQPlugin extends Plugin {
|
|
|
752
760
|
const extExecutor = this._framework?.pluginManager?.get('extension-executor')
|
|
753
761
|
const skillHelp = extExecutor?.getSkillCommandsHelp?.() || ''
|
|
754
762
|
|
|
755
|
-
let helpText = '👋 欢迎使用 AI 助手!\n\n直接发送消息即可与我对话。\n\n可用命令:\n/clean - 清除对话上下文\n/compress - 压缩上下文\n/history - 查看历史消息数'
|
|
763
|
+
let helpText = '👋 欢迎使用 AI 助手!\n\n直接发送消息即可与我对话。\n\n\n可用命令:\n/clean - 清除对话上下文\n/compress - 压缩上下文\n/reload - 重载技能\n/history - 查看历史消息数'
|
|
756
764
|
|
|
757
765
|
if (skillHelp) {
|
|
758
766
|
helpText += '\n\n【技能命令】\n' + skillHelp.split('\n').map(line => line.replace(/^\//, '/')).join('\n')
|
|
@@ -322,6 +322,14 @@ class TelegramPlugin extends Plugin {
|
|
|
322
322
|
case 'compress':
|
|
323
323
|
await this._compressSessionContext(chatId, msg.message_id)
|
|
324
324
|
break
|
|
325
|
+
case 'reload':
|
|
326
|
+
try {
|
|
327
|
+
await this._framework.executeTool('reloadSkills', {})
|
|
328
|
+
await this._sendMessage(chatId, '✅ 技能已重载', msg.message_id)
|
|
329
|
+
} catch (err) {
|
|
330
|
+
await this._sendMessage(chatId, '❌ 重载失败: ' + err.message, msg.message_id)
|
|
331
|
+
}
|
|
332
|
+
break
|
|
325
333
|
case 'history':
|
|
326
334
|
const sessionInfo = this._getSessionAgent(chatId)
|
|
327
335
|
if (sessionInfo && this._framework) {
|
|
@@ -525,7 +533,7 @@ class TelegramPlugin extends Plugin {
|
|
|
525
533
|
const extExecutor = this._framework?.pluginManager?.get('extension-executor')
|
|
526
534
|
const skillHelp = extExecutor?.getSkillCommandsHelp?.() || ''
|
|
527
535
|
|
|
528
|
-
let helpText = '👋 欢迎使用 AI 助手!\n\n直接发送消息即可与我对话。\n\n可用命令:\n/clean - 清除对话上下文\n/compress - 压缩上下文\n/history - 查看历史消息数'
|
|
536
|
+
let helpText = '👋 欢迎使用 AI 助手!\n\n直接发送消息即可与我对话。\n\n可用命令:\n/clean - 清除对话上下文\n/compress - 压缩上下文\n/reload - 重载技能\n/history - 查看历史消息数'
|
|
529
537
|
|
|
530
538
|
if (skillHelp) {
|
|
531
539
|
helpText += '\n\n【技能命令】\n' + skillHelp.split('\n').map(line => line.replace(/^\//, '/')).join('\n')
|
package/plugins/weixin-plugin.js
CHANGED
|
@@ -601,6 +601,14 @@ class WeixinPlugin extends Plugin {
|
|
|
601
601
|
case 'compress':
|
|
602
602
|
await this._compressSessionContext(userId)
|
|
603
603
|
break
|
|
604
|
+
case 'reload':
|
|
605
|
+
try {
|
|
606
|
+
await this._framework.executeTool('reloadSkills', {})
|
|
607
|
+
await this._sendMessageBatch(originalMsg, userId, '✅ 技能已重载', true)
|
|
608
|
+
} catch (err) {
|
|
609
|
+
await this._sendMessageBatch(originalMsg, userId, '❌ 重载失败: ' + err.message, true)
|
|
610
|
+
}
|
|
611
|
+
break
|
|
604
612
|
case 'history':
|
|
605
613
|
const sessionInfo = this._getSessionAgent(userId)
|
|
606
614
|
if (sessionInfo && this._framework) {
|
|
@@ -634,7 +642,7 @@ class WeixinPlugin extends Plugin {
|
|
|
634
642
|
const extExecutor = this._framework?.pluginManager?.get('extension-executor')
|
|
635
643
|
const skillHelp = extExecutor?.getSkillCommandsHelp?.() || ''
|
|
636
644
|
|
|
637
|
-
let helpText = '👋 欢迎使用 AI 助手!\n\n直接发送消息即可与我对话。\n\n可用命令:\n/clean - 清除对话上下文\n/compress - 压缩上下文\n/history - 查看历史消息数'
|
|
645
|
+
let helpText = '👋 欢迎使用 AI 助手!\n\n直接发送消息即可与我对话。\n\n可用命令:\n/clean - 清除对话上下文\n/compress - 压缩上下文\n/reload - 重载技能\n/history - 查看历史消息数'
|
|
638
646
|
|
|
639
647
|
if (skillHelp) {
|
|
640
648
|
helpText += '\n\n【技能命令】\n' + skillHelp.split('\n').map(line => line.replace(/^\//, '/')).join('\n')
|
|
@@ -10,6 +10,39 @@ const { logger } = require('../utils/logger');
|
|
|
10
10
|
const log = logger.child('SkillManager');
|
|
11
11
|
const { z } = require('zod');
|
|
12
12
|
const { Command } = require('commander');
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* 简单的 shell 参数解析器(处理带引号的参数)
|
|
16
|
+
* @param {string} str - 参数字符串
|
|
17
|
+
* @returns {string[]} 参数数组
|
|
18
|
+
*/
|
|
19
|
+
function parseShellArgs(str) {
|
|
20
|
+
if (!str || !str.trim()) return [];
|
|
21
|
+
const args = [];
|
|
22
|
+
let current = '';
|
|
23
|
+
let inQuote = false;
|
|
24
|
+
let quoteChar = '';
|
|
25
|
+
|
|
26
|
+
for (let i = 0; i < str.length; i++) {
|
|
27
|
+
const char = str[i];
|
|
28
|
+
if (!inQuote && (char === '"' || char === "'")) {
|
|
29
|
+
inQuote = true;
|
|
30
|
+
quoteChar = char;
|
|
31
|
+
} else if (inQuote && char === quoteChar) {
|
|
32
|
+
inQuote = false;
|
|
33
|
+
} else if (!inQuote && char === ' ') {
|
|
34
|
+
if (current.trim()) {
|
|
35
|
+
args.push(current.trim());
|
|
36
|
+
current = '';
|
|
37
|
+
}
|
|
38
|
+
} else {
|
|
39
|
+
current += char;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
if (current.trim()) args.push(current.trim());
|
|
43
|
+
return args;
|
|
44
|
+
}
|
|
45
|
+
|
|
13
46
|
/**
|
|
14
47
|
* 验证 skill 名称
|
|
15
48
|
* 1-64字符,字母数字、下划线和连字符,不能以连字符或下划线开头或结尾
|
|
@@ -185,18 +218,25 @@ class Skill {
|
|
|
185
218
|
let argumentParser = cmd.argumentParser;
|
|
186
219
|
if (!argumentParser && Array.isArray(cmd.options) && cmd.options.length > 0) {
|
|
187
220
|
argumentParser = (rawArgs) => {
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
program.
|
|
221
|
+
try {
|
|
222
|
+
const program = new Command();
|
|
223
|
+
program.exitOverride(); // 不自动退出
|
|
224
|
+
program.configureOutput({ writeErr: () => {} }); // 隐藏错误输出
|
|
225
|
+
for (const opt of cmd.options) {
|
|
226
|
+
program.option(opt.flags, opt.description, opt.defaultValue);
|
|
227
|
+
}
|
|
228
|
+
program.arguments('[args...]');
|
|
229
|
+
program.parse(['node', 'cmd', ...parseShellArgs(rawArgs)]);
|
|
230
|
+
return { ...program.opts(), args: program.args };
|
|
231
|
+
} catch (err) {
|
|
232
|
+
log.warn(`[SkillManager] Command parse error: ${err.message}`);
|
|
233
|
+
return { args: rawArgs };
|
|
192
234
|
}
|
|
193
|
-
program.arguments('[args...]');
|
|
194
|
-
program.parse(['node', 'cmd', ...(rawArgs || '').split(' ').filter(Boolean)]);
|
|
195
|
-
return { ...program.opts(), args: program.args };
|
|
196
235
|
};
|
|
197
236
|
}
|
|
198
237
|
|
|
199
238
|
const toolDef = {
|
|
239
|
+
name: fullName,
|
|
200
240
|
description: cmd.description || `Skill command: ${fullName}`,
|
|
201
241
|
inputSchema: z.object({
|
|
202
242
|
args: z.string().optional().describe('命令参数'),
|
|
@@ -832,14 +872,12 @@ class SkillManagerPlugin extends Plugin {
|
|
|
832
872
|
this._framework = framework;
|
|
833
873
|
this._loadAllSkills();
|
|
834
874
|
|
|
835
|
-
// 通知 extension-executor 重载 skill
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
}
|
|
842
|
-
}
|
|
875
|
+
// 通知 extension-executor 重载 skill 命令(通过监听器自动处理)
|
|
876
|
+
// 注意:skill 命令已经在 registerCommands() 中直接注册到 extension-executor,
|
|
877
|
+
// 不需要再调用 _rescanPluginTools
|
|
878
|
+
|
|
879
|
+
// 通知 UI 刷新 skill 命令(用于 autocomplete 热重载)
|
|
880
|
+
framework.emit('skill:reloaded', { skills: Array.from(this._skills.keys()) });
|
|
843
881
|
}
|
|
844
882
|
|
|
845
883
|
uninstall(framework) {
|
package/src/core/agent-chat.js
CHANGED
|
@@ -500,6 +500,8 @@ class AgentChatHandler extends EventEmitter {
|
|
|
500
500
|
const stream = result.fullStream;
|
|
501
501
|
let fullText = '';
|
|
502
502
|
const iterator = stream[Symbol.asyncIterator] ? stream : stream.fullStream;
|
|
503
|
+
// 追踪发送的 tool-call ID,过滤 API 返回的不匹配 tool-result
|
|
504
|
+
const sentToolCallIds = new Set();
|
|
503
505
|
|
|
504
506
|
for await (const part of iterator || stream) {
|
|
505
507
|
if (part.type === 'text-delta') {
|
|
@@ -510,8 +512,16 @@ class AgentChatHandler extends EventEmitter {
|
|
|
510
512
|
reasoningContent += part.text || '';
|
|
511
513
|
yield { type: 'thinking', text: part.text };
|
|
512
514
|
} else if (part.type === 'tool-call') {
|
|
515
|
+
const toolCallId = part.toolCallId;
|
|
516
|
+
if (toolCallId) sentToolCallIds.add(toolCallId);
|
|
513
517
|
yield { type: 'tool-call', toolName: part.toolName, input: part.input };
|
|
514
518
|
} else if (part.type === 'tool-result') {
|
|
519
|
+
// 过滤 toolCallId 不匹配的 tool-result(API bug)
|
|
520
|
+
const resultToolCallId = part.toolCallId;
|
|
521
|
+
if (resultToolCallId && !sentToolCallIds.has(resultToolCallId)) {
|
|
522
|
+
logger.debug(`[stream]过滤不匹配的 tool-result: ${resultToolCallId}`);
|
|
523
|
+
continue;
|
|
524
|
+
}
|
|
515
525
|
yield { type: 'tool-result', toolName: part.toolName, result: part.output };
|
|
516
526
|
} else if (part.type === 'error') {
|
|
517
527
|
// 统一错误消息
|