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.
- package/.cli_default_systemPrompt.md +242 -0
- package/.dockerignore +45 -45
- package/.env.example +56 -56
- package/cli/src/ui/chat-ui.js +15 -13
- package/docker-compose.yml +33 -33
- package/docs/features.md +120 -120
- package/docs/quick-reference.md +160 -160
- package/package.json +1 -1
- package/plugins/extension-executor-plugin.js +19 -45
- package/skills/find-skills/SKILL.md +133 -133
- package/skills/foliko-dev/AGENTS.md +236 -236
- package/skills/mcp-usage/SKILL.md +200 -200
- package/skills/subagent-guide/SKILL.md +237 -237
- package/skills/workflow-guide/SKILL.md +646 -646
- package/src/capabilities/skill-manager.js +103 -9
- package/src/core/agent-chat.js +10 -0
- package/website_v2/styles/animations.css +7 -7
|
@@ -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
|
-
|
|
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
|
|
|
@@ -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
|
-
|
|
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
|
|
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
|
// 统一错误消息
|
|
@@ -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
|
|