foliko 1.1.71 → 1.1.73

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.
@@ -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,14 +218,20 @@ 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
- const program = new Command();
190
- for (const opt of cmd.options) {
191
- program.option(opt.flags, opt.description, opt.defaultValue);
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
 
@@ -202,6 +241,7 @@ class Skill {
202
241
  inputSchema: z.object({
203
242
  args: z.string().optional().describe('命令参数'),
204
243
  }),
244
+ _options: cmd.options || null, // 保存 options 信息用于提示词生成
205
245
  execute: async (toolArgs, framework) => {
206
246
  const ctx = {
207
247
  sessionId: framework?._currentSessionId || 'unknown',
@@ -403,13 +443,67 @@ class SkillManagerPlugin extends Plugin {
403
443
  }
404
444
 
405
445
  const lines = ['## 可用技能', ''];
446
+
447
+ // 获取 skill 的命令(通过 extension-executor 插件获取已注册的工具)
448
+ const extExecutor = this._framework?.pluginManager?.get('extension-executor');
449
+ const skillCommands = extExecutor?.getSkillCommands?.() || [];
450
+
451
+ // 按技能名分组
452
+ const toolsBySkill = {};
453
+ for (const tool of skillCommands) {
454
+ if (!tool || !tool.name) continue;
455
+ const parts = tool.name.split(':');
456
+ if (parts.length < 2) continue;
457
+ const skillName = parts[0];
458
+ const cmdName = parts.slice(1).join(':');
459
+ if (!toolsBySkill[skillName]) {
460
+ toolsBySkill[skillName] = [];
461
+ }
462
+ toolsBySkill[skillName].push({ name: cmdName, description: tool.description || '', options: tool.options || null });
463
+ }
464
+
406
465
  for (const skill of skills) {
407
466
  const name = skill.metadata?.name || skill.name || 'unknown';
408
467
  const descText = skill.metadata?.description || '';
409
- lines.push(`- ${name}: ${descText}`);
468
+
469
+ lines.push(`### ${name}`);
470
+ lines.push(`**描述**: ${descText}`);
471
+
472
+ // 如果该技能有命令,添加调用方式
473
+ if (toolsBySkill[name]) {
474
+ lines.push('');
475
+ lines.push('**调用方式:**');
476
+ lines.push('```');
477
+ lines.push(`ext_call({`);
478
+ lines.push(` plugin: "skill",`);
479
+ lines.push(` tool: "${name}:<命令名>",`);
480
+ lines.push(` args: { args: "参数" } // 注意:args 必须是对象,不能是字符串`);
481
+ lines.push(`})`);
482
+ lines.push('```');
483
+ lines.push('');
484
+ lines.push('**可用命令:**');
485
+ for (const c of toolsBySkill[name]) {
486
+ lines.push(`- \`${c.name}\`: ${c.description}`);
487
+ // 如果有 options,显示参数格式
488
+ if (c.options && c.options.length > 0) {
489
+ const optStr = c.options.map(o => {
490
+ const flag = o.flags?.split(' ')[0] || o.flags || '';
491
+ const def = o.defaultValue !== undefined ? ` (默认: ${JSON.stringify(o.defaultValue)})` : '';
492
+ return `${flag}${def}`;
493
+ }).join(' ');
494
+ lines.push(` - 选项: ${optStr}`);
495
+ }
496
+ }
497
+ lines.push('');
498
+ lines.push('**示例:**');
499
+ for (const c of toolsBySkill[name]) {
500
+ lines.push(`- 调用 \`ext_call({ plugin: "skill", tool: "${name}:${c.name}", args: { args: "参数" } })\``);
501
+ }
502
+ }
503
+ lines.push('');
410
504
  }
505
+
411
506
  lines.push(
412
- '',
413
507
  '> **重要提示**:当需要开发插件、执行专业任务时,必须先使用 `loadSkill` 工具加载对应技能,获取专业指导。'
414
508
  );
415
509
 
@@ -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
  // 统一错误消息
@@ -1,8 +1,8 @@
1
- /* Foliko v2 - Animations */
2
- @keyframes fadeInUp { from { opacity: 0; transform: translateY(30px); } to { opacity: 1; transform: translateY(0); } }
3
- @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
4
- @keyframes glow { 0%, 100% { opacity: 0.4; transform: translateX(-50%) scale(1); } 50% { opacity: 0.7; transform: translateX(-50%) scale(1.1); } }
5
- @keyframes pulse { 0%, 100% { opacity: 1; transform: scale(1); } 50% { opacity: 0.5; transform: scale(1.2); } }
6
- .reveal { opacity: 0; transform: translateY(30px); transition: all 0.6s cubic-bezier(0.4, 0, 0.2, 1); }
7
- .reveal.visible { opacity: 1; transform: translateY(0); }
1
+ /* Foliko v2 - Animations */
2
+ @keyframes fadeInUp { from { opacity: 0; transform: translateY(30px); } to { opacity: 1; transform: translateY(0); } }
3
+ @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
4
+ @keyframes glow { 0%, 100% { opacity: 0.4; transform: translateX(-50%) scale(1); } 50% { opacity: 0.7; transform: translateX(-50%) scale(1.1); } }
5
+ @keyframes pulse { 0%, 100% { opacity: 1; transform: scale(1); } 50% { opacity: 0.5; transform: scale(1.2); } }
6
+ .reveal { opacity: 0; transform: translateY(30px); transition: all 0.6s cubic-bezier(0.4, 0, 0.2, 1); }
7
+ .reveal.visible { opacity: 1; transform: translateY(0); }
8
8