foliko 2.0.2 → 2.0.3
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/README.md +6 -6
- package/docs/public-api.md +91 -0
- package/docs/system-prompt.md +219 -0
- package/docs/usage.md +6 -6
- package/package.json +1 -1
- package/plugins/ambient/ExplorerLoop.js +1 -0
- package/plugins/ambient/index.js +1 -0
- package/plugins/core/coordinator/index.js +10 -5
- package/plugins/core/default/bootstrap.js +21 -3
- package/plugins/core/default/config.js +12 -3
- package/plugins/core/python-loader/index.js +3 -3
- package/plugins/core/scheduler/index.js +26 -2
- package/plugins/core/skill-manager/index.js +198 -151
- package/plugins/core/sub-agent/index.js +34 -15
- package/plugins/core/think/index.js +1 -0
- package/plugins/core/workflow/index.js +5 -4
- package/plugins/executors/data-splitter/index.js +14 -1
- package/plugins/executors/extension/index.js +51 -37
- package/plugins/executors/python/index.js +3 -3
- package/plugins/executors/shell/index.js +6 -4
- package/plugins/io/web/index.js +2 -1
- package/plugins/memory/index.js +29 -74
- package/plugins/messaging/email/handlers.js +1 -19
- package/plugins/messaging/email/monitor.js +2 -17
- package/plugins/messaging/email/reply.js +2 -1
- package/plugins/messaging/email/utils.js +20 -1
- package/plugins/messaging/feishu/index.js +1 -1
- package/plugins/messaging/qq/index.js +1 -1
- package/plugins/messaging/telegram/index.js +1 -1
- package/plugins/messaging/weixin/index.js +1 -1
- package/plugins/plugin-manager/index.js +1 -33
- package/plugins/tools/index.js +14 -1
- package/skills/plugins/SKILL.md +316 -0
- package/skills/skill-guide/SKILL.md +5 -5
- package/skills/{subagent-guide → subagents}/SKILL.md +1 -1
- package/skills/{workflow-guide → workflows}/SKILL.md +3 -3
- package/skills/{workflow-troubleshooting → workflows/workflow-troubleshooting}/DEBUGGING.md +197 -197
- package/skills/{workflow-troubleshooting → workflows/workflow-troubleshooting}/SKILL.md +391 -391
- package/src/agent/base.js +5 -20
- package/src/agent/index.js +20 -8
- package/src/agent/main.js +100 -23
- package/src/agent/prompt-registry.js +296 -0
- package/src/agent/sub-config.js +1 -78
- package/src/agent/sub.js +19 -24
- package/src/cli/commands/plugin.js +1 -60
- package/src/cli/ui/chat-ui-old.js +1 -1
- package/src/cli/ui/chat-ui.js +1 -1
- package/src/cli/ui/components/agent-mention-provider.js +1 -1
- package/src/common/constants.js +42 -0
- package/src/common/id.js +13 -0
- package/src/common/queue.js +2 -2
- package/src/context/compressor.js +17 -9
- package/src/framework/framework.js +185 -0
- package/src/framework/index.js +1 -2
- package/src/framework/lifecycle.js +102 -1
- package/src/framework/loader.js +1 -55
- package/src/index.js +11 -2
- package/src/plugin/base.js +69 -55
- package/src/plugin/loader.js +1 -57
- package/src/session/entry.js +1 -11
- package/src/storage/manager.js +1 -12
- package/src/tool/executor.js +2 -1
- package/src/tool/router.js +5 -5
- package/src/utils/data-splitter.js +2 -1
- package/src/utils/index.js +150 -0
- package/src/utils/message-validator.js +21 -17
- package/src/utils/plugin-helpers.js +19 -5
- package/subagent.md +2 -2
- package/tests/core/plugin-prompts.test.js +219 -0
- package/tests/core/prompt-registry.test.js +209 -0
- package/src/cli/utils/plugin-config.js +0 -50
- package/src/config/plugin-config.js +0 -50
- /package/skills/{ambient-agent → ambient}/SKILL.md +0 -0
- /package/skills/{foliko-dev → foliko}/AGENTS.md +0 -0
- /package/skills/{foliko-dev → foliko}/SKILL.md +0 -0
- /package/skills/{mcp-usage → mcp}/SKILL.md +0 -0
- /package/skills/{plugin-guide → plugins-guide}/SKILL.md +0 -0
- /package/skills/{python-plugin-dev → python}/SKILL.md +0 -0
|
@@ -5,11 +5,13 @@
|
|
|
5
5
|
|
|
6
6
|
const fs = require('fs');
|
|
7
7
|
const path = require('path');
|
|
8
|
+
const { pathToFileURL } = require('url');
|
|
8
9
|
const { Plugin } = require('../../../src/plugin/base');
|
|
9
10
|
const { logger } = require('../../../src/common/logger');
|
|
10
11
|
const log = logger.child('SkillManager');
|
|
11
12
|
const { z } = require('zod');
|
|
12
13
|
const { Command } = require('commander');
|
|
14
|
+
const { parseFrontmatter, stripFrontmatter } = require('../../../src/utils');
|
|
13
15
|
|
|
14
16
|
/**
|
|
15
17
|
* 简单的 shell 参数解析器(处理带引号的参数)
|
|
@@ -44,85 +46,74 @@ function parseShellArgs(str) {
|
|
|
44
46
|
}
|
|
45
47
|
|
|
46
48
|
/**
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
+
* 格式化选项为人类可读的描述
|
|
50
|
+
* @param {Object} o - 选项对象 { flags, description, defaultValue, required }(required 未设置或为 false 时不标必填)
|
|
51
|
+
* @returns {string} 格式化后的描述
|
|
49
52
|
*/
|
|
50
|
-
function
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
53
|
+
function formatOptionForHelp(o) {
|
|
54
|
+
const flags = o.flags || '';
|
|
55
|
+
const hasValue = flags.includes('<');
|
|
56
|
+
const defaultVal = o.defaultValue !== undefined ? ` (默认: ${o.defaultValue})` : '';
|
|
57
|
+
const required = o.required === true && hasValue;
|
|
58
|
+
|
|
59
|
+
const parts = flags.split(/\s+/).filter(p => p && !p.includes('<')).map(p => p.replace(/,$/, ''));
|
|
60
|
+
if (parts.length === 0) return `${flags}${defaultVal}`;
|
|
61
|
+
|
|
62
|
+
const firstPart = parts[0];
|
|
63
|
+
const isShort = /^-[a-zA-Z]$/.test(firstPart);
|
|
64
|
+
const isLong = firstPart.startsWith('--');
|
|
65
|
+
|
|
66
|
+
if (isShort && parts.length > 1) {
|
|
67
|
+
// 短+长选项: -i, --video-id -> -i/--video-id
|
|
68
|
+
const longOpt = parts[1].replace(/^--+/, '');
|
|
69
|
+
return `-${parts[0].slice(1)}/--${longOpt}${hasValue ? ' <值>' : ''}: ${o.description || ''}${required ? ' [必填]' : ''}${defaultVal}`;
|
|
70
|
+
} else if (isLong) {
|
|
71
|
+
return `--${parts[0].slice(2)}${hasValue ? ' <值>' : ''}: ${o.description || ''}${required ? ' [必填]' : ''}${defaultVal}`;
|
|
72
|
+
} else if (isShort) {
|
|
73
|
+
return `-${firstPart.slice(1)}${hasValue ? ' <值>' : ''}: ${o.description || ''}${required ? ' [必填]' : ''}${defaultVal}`;
|
|
74
|
+
}
|
|
75
|
+
return `${flags}${defaultVal}`;
|
|
57
76
|
}
|
|
58
77
|
|
|
59
78
|
/**
|
|
60
|
-
*
|
|
79
|
+
* 格式化选项为示例参数
|
|
80
|
+
* @param {Object} o - 选项对象
|
|
81
|
+
* @returns {string} 格式化后的示例
|
|
61
82
|
*/
|
|
62
|
-
function
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
frontmatter[currentKey][key] = value;
|
|
82
|
-
continue;
|
|
83
|
-
}
|
|
84
|
-
} else if (line.match(/^[a-zA-Z]/)) {
|
|
85
|
-
currentKey = null;
|
|
86
|
-
continue;
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
const colonIndex = line.indexOf(':');
|
|
91
|
-
if (colonIndex === -1) continue;
|
|
92
|
-
|
|
93
|
-
const key = line.substring(0, colonIndex).trim();
|
|
94
|
-
let value = line.substring(colonIndex + 1).trim();
|
|
95
|
-
|
|
96
|
-
// 移除引号
|
|
97
|
-
if (
|
|
98
|
-
(value.startsWith('"') && value.endsWith('"')) ||
|
|
99
|
-
(value.startsWith("'") && value.endsWith("'"))
|
|
100
|
-
) {
|
|
101
|
-
value = value.slice(1, -1);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
if (key === 'metadata') {
|
|
105
|
-
currentKey = 'metadata';
|
|
106
|
-
frontmatter.metadata = {};
|
|
107
|
-
} else if (key === 'allowed-tools') {
|
|
108
|
-
frontmatter[key] = value
|
|
109
|
-
.split(',')
|
|
110
|
-
.map((t) => t.trim().replace(/^["']|["']$/g, ''))
|
|
111
|
-
.filter((t) => t);
|
|
112
|
-
} else {
|
|
113
|
-
frontmatter[key] = value;
|
|
114
|
-
}
|
|
83
|
+
function formatOptionForExample(o) {
|
|
84
|
+
const flags = o.flags || '';
|
|
85
|
+
const hasValue = flags.includes('<');
|
|
86
|
+
const def = o.defaultValue !== undefined ? String(o.defaultValue) : '';
|
|
87
|
+
|
|
88
|
+
const parts = flags.split(/\s+/).filter(p => p && !p.includes('<')).map(p => p.replace(/,$/, ''));
|
|
89
|
+
if (parts.length === 0) return '';
|
|
90
|
+
|
|
91
|
+
const firstPart = parts[0];
|
|
92
|
+
const isShort = /^-[a-zA-Z]$/.test(firstPart);
|
|
93
|
+
const isLong = firstPart.startsWith('--');
|
|
94
|
+
|
|
95
|
+
if (isShort && parts.length > 1) {
|
|
96
|
+
const longOpt = parts[1].replace(/^--+/, '');
|
|
97
|
+
return `-${parts[0].slice(1)} ${def || '<值>'}`;
|
|
98
|
+
} else if (isLong) {
|
|
99
|
+
return `--${parts[0].slice(2)} ${def || '<值>'}`;
|
|
100
|
+
} else if (isShort) {
|
|
101
|
+
return `-${firstPart.slice(1)} ${def || '<值>'}`;
|
|
115
102
|
}
|
|
116
|
-
|
|
117
|
-
return frontmatter;
|
|
103
|
+
return firstPart;
|
|
118
104
|
}
|
|
119
105
|
|
|
120
106
|
/**
|
|
121
|
-
*
|
|
107
|
+
* 验证 skill 名称
|
|
108
|
+
* 1-64字符,字母数字、下划线和连字符,不能以连字符或下划线开头或结尾
|
|
122
109
|
*/
|
|
123
|
-
function
|
|
124
|
-
|
|
125
|
-
|
|
110
|
+
function isValidSkillName(name) {
|
|
111
|
+
if (!name || name.length < 1 || name.length > 64) return false;
|
|
112
|
+
if (!/^[a-zA-Z0-9_-]+$/.test(name)) return false;
|
|
113
|
+
if (name.startsWith('-') || name.startsWith('_') || name.endsWith('-') || name.endsWith('_'))
|
|
114
|
+
return false;
|
|
115
|
+
if (/--/.test(name) || /__/.test(name)) return false;
|
|
116
|
+
return true;
|
|
126
117
|
}
|
|
127
118
|
|
|
128
119
|
/**
|
|
@@ -168,12 +159,18 @@ class Skill {
|
|
|
168
159
|
|
|
169
160
|
/**
|
|
170
161
|
* 注册 skill 的命令到 extension 系统
|
|
162
|
+
* 异步版本:用动态 import() 替代 require(),支持 ESM 包
|
|
163
|
+
* (Node 22+ 不允许 require ESM-only 包,比如 puppeteer、nanoid)
|
|
171
164
|
*/
|
|
172
|
-
registerCommands() {
|
|
173
|
-
if (!this._framework || !this._skillPath)
|
|
165
|
+
async registerCommands() {
|
|
166
|
+
if (!this._framework || !this._skillPath) {
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
174
169
|
|
|
175
170
|
const indexPath = path.join(this._skillPath, 'index.js');
|
|
176
|
-
if (!fs.existsSync(indexPath))
|
|
171
|
+
if (!fs.existsSync(indexPath)) {
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
177
174
|
|
|
178
175
|
const skillName = this.metadata?.name || this.name || path.basename(this._skillPath);
|
|
179
176
|
|
|
@@ -191,16 +188,21 @@ class Skill {
|
|
|
191
188
|
};
|
|
192
189
|
|
|
193
190
|
try {
|
|
194
|
-
//
|
|
195
|
-
|
|
196
|
-
const mod =
|
|
197
|
-
|
|
198
|
-
// 检查导出格式
|
|
199
|
-
const commands = mod.exports || mod;
|
|
191
|
+
// 用动态 import 同时支持 CJS 和 ESM
|
|
192
|
+
// (Node 22+ 默认开启 require(ESM),Node 20 需 --experimental-require-module 标志)
|
|
193
|
+
const mod = await import(pathToFileURL(indexPath).href);
|
|
194
|
+
const commands = mod.default || mod.exports || mod;
|
|
200
195
|
if (!commands) return;
|
|
201
196
|
|
|
202
|
-
//
|
|
203
|
-
|
|
197
|
+
// 支持多种导出格式:数组 / 单个对象 / { commands: [...] }
|
|
198
|
+
let cmds;
|
|
199
|
+
if (Array.isArray(commands)) {
|
|
200
|
+
cmds = commands;
|
|
201
|
+
} else if (Array.isArray(commands.commands)) {
|
|
202
|
+
cmds = commands.commands;
|
|
203
|
+
} else {
|
|
204
|
+
cmds = [commands];
|
|
205
|
+
}
|
|
204
206
|
|
|
205
207
|
for (const cmd of cmds) {
|
|
206
208
|
if (!cmd || !cmd.name) continue;
|
|
@@ -235,21 +237,42 @@ class Skill {
|
|
|
235
237
|
};
|
|
236
238
|
}
|
|
237
239
|
|
|
240
|
+
// 构建详细的命令描述,帮助 LLM 正确调用
|
|
241
|
+
let commandHint = '';
|
|
242
|
+
if (cmd.options && cmd.options.length > 0) {
|
|
243
|
+
const optDescs = cmd.options.map(formatOptionForHelp);
|
|
244
|
+
commandHint = `\n 可用选项:\n ${optDescs.join('\n ')}`;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// 生成示例命令
|
|
248
|
+
const exampleCmd = cmd.options && cmd.options.length > 0
|
|
249
|
+
? cmd.options.map(o => {
|
|
250
|
+
const flag = (o.flags || '').split(/[,\s]+/).filter(p => p && !p.includes('<') && !p.includes(','))[0]?.replace(/^-*/, '') || '';
|
|
251
|
+
const hasValue = (o.flags || '').includes('<');
|
|
252
|
+
const def = o.defaultValue !== undefined && o.defaultValue !== '' ? String(o.defaultValue) : '';
|
|
253
|
+
const val = def || '<值>';
|
|
254
|
+
return hasValue ? `-${flag} ${val}` : `-${flag}`;
|
|
255
|
+
}).join(' ')
|
|
256
|
+
: '';
|
|
257
|
+
|
|
238
258
|
const toolDef = {
|
|
239
259
|
name: fullName,
|
|
240
|
-
description: cmd.description ||
|
|
260
|
+
description: `${cmd.description || 'Skill command'}\n\n**重要**: args.command 是**字符串**,不是对象!\n例: args: { command: "${exampleCmd}" }\n\n${commandHint}`,
|
|
241
261
|
inputSchema: z.object({
|
|
242
|
-
command: z.string().optional().describe(
|
|
262
|
+
command: z.string().optional().describe(
|
|
263
|
+
cmd.options && cmd.options.length > 0
|
|
264
|
+
? `**字符串**,空格分隔,如: ${cmd.options.map(formatOptionForExample).join(' ')}`
|
|
265
|
+
: `**字符串**,命令行参数`
|
|
266
|
+
),
|
|
243
267
|
}),
|
|
244
|
-
_options: cmd.options || null,
|
|
268
|
+
_options: cmd.options || null,
|
|
245
269
|
execute: async (toolArgs, framework) => {
|
|
246
270
|
const ctx = {
|
|
247
271
|
sessionId: framework?._currentSessionId || 'unknown',
|
|
248
272
|
agent: null,
|
|
249
273
|
framework: framework,
|
|
250
|
-
cwd: framework?.getCwd?.() ?? process.cwd(),
|
|
274
|
+
cwd: framework?.getCwd?.() ?? process.cwd(),
|
|
251
275
|
};
|
|
252
|
-
// 支持 argumentParser 先解析参数
|
|
253
276
|
let parsedArgs = toolArgs.command || '';
|
|
254
277
|
if (typeof argumentParser === 'function') {
|
|
255
278
|
parsedArgs = argumentParser(toolArgs.command || '');
|
|
@@ -258,7 +281,7 @@ class Skill {
|
|
|
258
281
|
},
|
|
259
282
|
};
|
|
260
283
|
|
|
261
|
-
// 注册到 extension
|
|
284
|
+
// 注册到 extension 系统
|
|
262
285
|
extExecutor.registerTool('skill', skillInfo, toolDef);
|
|
263
286
|
|
|
264
287
|
// 同时添加到 this.tools,供 extension-executor 的 _scanPluginTools 扫描
|
|
@@ -266,10 +289,6 @@ class Skill {
|
|
|
266
289
|
|
|
267
290
|
this._commands.push(fullName);
|
|
268
291
|
}
|
|
269
|
-
|
|
270
|
-
if (this._commands.length > 0) {
|
|
271
|
-
// log.info(`[${skillName}] Registered ${this._commands.length} commands: ${this._commands.join(', ')}`);
|
|
272
|
-
}
|
|
273
292
|
} catch (err) {
|
|
274
293
|
log.warn(`Failed to load commands from ${indexPath}:`, err.message);
|
|
275
294
|
}
|
|
@@ -280,6 +299,18 @@ class Skill {
|
|
|
280
299
|
* SkillManager 插件
|
|
281
300
|
*/
|
|
282
301
|
class SkillManagerPlugin extends Plugin {
|
|
302
|
+
// 声明式 prompt parts
|
|
303
|
+
prompts = [
|
|
304
|
+
{
|
|
305
|
+
name: 'skills',
|
|
306
|
+
slot: 'CAPABILITY',
|
|
307
|
+
description: '可用技能列表',
|
|
308
|
+
provider: function () {
|
|
309
|
+
return this._buildSkillsDescription();
|
|
310
|
+
},
|
|
311
|
+
},
|
|
312
|
+
];
|
|
313
|
+
|
|
283
314
|
constructor(config = {}) {
|
|
284
315
|
super();
|
|
285
316
|
this.name = 'skill-manager';
|
|
@@ -303,11 +334,12 @@ class SkillManagerPlugin extends Plugin {
|
|
|
303
334
|
}
|
|
304
335
|
|
|
305
336
|
start(framework) {
|
|
306
|
-
|
|
337
|
+
super.start(framework);
|
|
338
|
+
this._loadAllSkills().catch(err => log.error('_loadAllSkills failed:', err.message));
|
|
307
339
|
|
|
308
|
-
// 注册
|
|
340
|
+
// 注册 skill_load 工具
|
|
309
341
|
framework.registerTool({
|
|
310
|
-
name: '
|
|
342
|
+
name: 'skill_load',
|
|
311
343
|
description: '加载指定技能,获取技能的使用指南和内容',
|
|
312
344
|
inputSchema: z.object({
|
|
313
345
|
skill: z.string().describe('技能名称'),
|
|
@@ -318,9 +350,34 @@ class SkillManagerPlugin extends Plugin {
|
|
|
318
350
|
if (!skill) {
|
|
319
351
|
return { success: false, error: `Skill '${skillName}' not found` };
|
|
320
352
|
}
|
|
353
|
+
|
|
354
|
+
// 附加该技能注册的命令文档(含参数说明)
|
|
355
|
+
let commandDocs = '';
|
|
356
|
+
const extExecutor = this._framework?.pluginManager?.get('extension-executor');
|
|
357
|
+
const skillCommands = extExecutor?.getSkillCommands?.() || [];
|
|
358
|
+
const cmds = skillCommands.filter(t => t.name && t.name.startsWith(skillName + ':'));
|
|
359
|
+
if (cmds.length > 0) {
|
|
360
|
+
const cmdLines = ['', '---', '### 命令参考', ''];
|
|
361
|
+
for (const cmd of cmds) {
|
|
362
|
+
const cmdName = cmd.name.split(':').slice(1).join(':');
|
|
363
|
+
cmdLines.push(`**${cmdName}**:${cmd.description || '无描述'}`);
|
|
364
|
+
if (cmd.options && cmd.options.length > 0) {
|
|
365
|
+
cmdLines.push('', '参数:');
|
|
366
|
+
for (const opt of cmd.options) {
|
|
367
|
+
const defaultStr = opt.defaultValue !== undefined ? `(默认: ${opt.defaultValue})` : '';
|
|
368
|
+
const requiredStr = opt.required === true ? ' [必填]' : '';
|
|
369
|
+
cmdLines.push(` \`${opt.flags}\`${requiredStr}${defaultStr}`);
|
|
370
|
+
if (opt.description) cmdLines.push(` ${opt.description}`);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
cmdLines.push('');
|
|
374
|
+
}
|
|
375
|
+
cmdLines.push('调用:`ext_call({ plugin: "skill", tool: "' + skillName + ':<命令>", args: { command: "参数" } })`');
|
|
376
|
+
commandDocs = cmdLines.join('\n');
|
|
377
|
+
}
|
|
321
378
|
return {
|
|
322
379
|
success: true,
|
|
323
|
-
data: skill.content,
|
|
380
|
+
data: [skill.content,commandDocs].join('\n'),
|
|
324
381
|
metadata: {
|
|
325
382
|
name: skill.name,
|
|
326
383
|
description: skill.metadata?.description || '',
|
|
@@ -329,9 +386,9 @@ class SkillManagerPlugin extends Plugin {
|
|
|
329
386
|
},
|
|
330
387
|
});
|
|
331
388
|
|
|
332
|
-
// 注册
|
|
389
|
+
// 注册 skill_reload 工具
|
|
333
390
|
framework.registerTool({
|
|
334
|
-
name: '
|
|
391
|
+
name: 'skill_reload',
|
|
335
392
|
description: '重载所有技能,当用户添加新技能或修改技能后调用此工具',
|
|
336
393
|
inputSchema: z.object({}),
|
|
337
394
|
execute: async () => {
|
|
@@ -346,9 +403,9 @@ class SkillManagerPlugin extends Plugin {
|
|
|
346
403
|
},
|
|
347
404
|
});
|
|
348
405
|
|
|
349
|
-
// 注册
|
|
406
|
+
// 注册 skill_load_reference 工具(按需加载 skill 的附加文档)
|
|
350
407
|
framework.registerTool({
|
|
351
|
-
name: '
|
|
408
|
+
name: 'skill_load_reference',
|
|
352
409
|
description: '加载指定技能的附加参考文档(references 目录下的文件)',
|
|
353
410
|
inputSchema: z.object({
|
|
354
411
|
skill: z.string().describe('技能名称'),
|
|
@@ -384,9 +441,9 @@ class SkillManagerPlugin extends Plugin {
|
|
|
384
441
|
},
|
|
385
442
|
});
|
|
386
443
|
|
|
387
|
-
// 注册
|
|
444
|
+
// 注册 skill_list_scripts 工具(列出 skill 的脚本)
|
|
388
445
|
framework.registerTool({
|
|
389
|
-
name: '
|
|
446
|
+
name: 'skill_list_scripts',
|
|
390
447
|
description: '列出指定技能下的所有可执行脚本',
|
|
391
448
|
inputSchema: z.object({
|
|
392
449
|
skill: z.string().describe('技能名称'),
|
|
@@ -401,9 +458,9 @@ class SkillManagerPlugin extends Plugin {
|
|
|
401
458
|
},
|
|
402
459
|
});
|
|
403
460
|
|
|
404
|
-
// 注册
|
|
461
|
+
// 注册 skill_load_script 工具(读取脚本内容)
|
|
405
462
|
framework.registerTool({
|
|
406
|
-
name: '
|
|
463
|
+
name: 'skill_load_script',
|
|
407
464
|
description: '读取指定技能下脚本文件的内容',
|
|
408
465
|
inputSchema: z.object({
|
|
409
466
|
skill: z.string().describe('技能名称'),
|
|
@@ -428,9 +485,7 @@ class SkillManagerPlugin extends Plugin {
|
|
|
428
485
|
},
|
|
429
486
|
});
|
|
430
487
|
|
|
431
|
-
//
|
|
432
|
-
this.registerPromptPart('skills', 90, () => this._buildSkillsDescription());
|
|
433
|
-
|
|
488
|
+
// prompt part 由基类 _autoRegisterPrompts 自动处理
|
|
434
489
|
return this;
|
|
435
490
|
}
|
|
436
491
|
|
|
@@ -443,13 +498,16 @@ class SkillManagerPlugin extends Plugin {
|
|
|
443
498
|
return '';
|
|
444
499
|
}
|
|
445
500
|
|
|
446
|
-
const lines = [
|
|
501
|
+
const lines = [
|
|
502
|
+
'## 可用技能',
|
|
503
|
+
'命令调用(仅对上方标注了「命令:」的技能有效):`ext_call({ plugin: "skill", tool: "<技能名>:<命令>", args: { command: "参数" } })`',
|
|
504
|
+
'',
|
|
505
|
+
];
|
|
447
506
|
|
|
448
|
-
// 获取 skill 的命令(通过 extension-executor 插件获取已注册的工具)
|
|
449
507
|
const extExecutor = this._framework?.pluginManager?.get('extension-executor');
|
|
450
508
|
const skillCommands = extExecutor?.getSkillCommands?.() || [];
|
|
451
509
|
|
|
452
|
-
//
|
|
510
|
+
// 按技能名分组命令(保留完整信息以渲染选项)
|
|
453
511
|
const toolsBySkill = {};
|
|
454
512
|
for (const tool of skillCommands) {
|
|
455
513
|
if (!tool || !tool.name) continue;
|
|
@@ -457,55 +515,43 @@ class SkillManagerPlugin extends Plugin {
|
|
|
457
515
|
if (parts.length < 2) continue;
|
|
458
516
|
const skillName = parts[0];
|
|
459
517
|
const cmdName = parts.slice(1).join(':');
|
|
460
|
-
if (!toolsBySkill[skillName])
|
|
461
|
-
toolsBySkill[skillName] = [];
|
|
462
|
-
}
|
|
518
|
+
if (!toolsBySkill[skillName]) toolsBySkill[skillName] = [];
|
|
463
519
|
toolsBySkill[skillName].push({ name: cmdName, description: tool.description || '', options: tool.options || null });
|
|
464
520
|
}
|
|
465
521
|
|
|
522
|
+
// 一列一技能,命令含选项时显示参数
|
|
466
523
|
for (const skill of skills) {
|
|
467
524
|
const name = skill.metadata?.name || skill.name || 'unknown';
|
|
468
525
|
const descText = skill.metadata?.description || '';
|
|
526
|
+
if (!descText && !toolsBySkill[name]) continue;
|
|
469
527
|
|
|
470
|
-
|
|
471
|
-
|
|
528
|
+
const parts = [];
|
|
529
|
+
if (descText) parts.push(descText);
|
|
472
530
|
|
|
473
|
-
// 如果该技能有命令,添加调用方式
|
|
474
531
|
if (toolsBySkill[name]) {
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
lines.push(`- \`${c.name}\`: ${c.description}`);
|
|
488
|
-
// 如果有 options,显示参数格式
|
|
489
|
-
if (c.options && c.options.length > 0) {
|
|
490
|
-
const optStr = c.options.map(o => {
|
|
491
|
-
const flag = o.flags?.split(' ')[0] || o.flags || '';
|
|
492
|
-
const def = o.defaultValue !== undefined ? ` (默认: ${JSON.stringify(o.defaultValue)})` : '';
|
|
493
|
-
return `${flag}${def}`;
|
|
494
|
-
}).join(' ');
|
|
495
|
-
lines.push(` - 选项: ${optStr}`);
|
|
532
|
+
const cmdStrs = toolsBySkill[name].map(cmd => {
|
|
533
|
+
if (cmd.options && cmd.options.length > 0) {
|
|
534
|
+
const optStrs = cmd.options.map(o => {
|
|
535
|
+
// 提取长选项名(--xxx)作为参数名,形如 "--tokens <值>"
|
|
536
|
+
const flag = o.flags || '';
|
|
537
|
+
const longOpt = flag.split(/\s+/).filter(f => f.startsWith('--'))[0];
|
|
538
|
+
const valPart = flag.includes('<') ? flag.match(/<[^>]+>/)?.[0] || '' : '';
|
|
539
|
+
if (longOpt && valPart) return `${longOpt} ${valPart}`;
|
|
540
|
+
if (longOpt) return longOpt;
|
|
541
|
+
return flag.split(/\s+/).filter(f => f && !f.includes('<')).map(f => f.replace(/,$/, ''))[0] || flag;
|
|
542
|
+
});
|
|
543
|
+
return `${cmd.name}(${optStrs.join(', ')})`;
|
|
496
544
|
}
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
for (const c of toolsBySkill[name]) {
|
|
501
|
-
lines.push(`- 调用 \`ext_call({ plugin: "skill", tool: "${name}:${c.name}", args: { command: "<命令行>" } })\``);
|
|
502
|
-
}
|
|
545
|
+
return cmd.name;
|
|
546
|
+
});
|
|
547
|
+
parts.push(`命令: ${cmdStrs.join(', ')}`);
|
|
503
548
|
}
|
|
504
|
-
lines.push('');
|
|
549
|
+
lines.push(`- **${name}**: ${parts.join(' | ')}`);
|
|
505
550
|
}
|
|
506
551
|
|
|
507
552
|
lines.push(
|
|
508
|
-
'
|
|
553
|
+
'',
|
|
554
|
+
'> **提示**:使用 `skill_load("${skill名}")` 加载技能获取详细使用指南。'
|
|
509
555
|
);
|
|
510
556
|
|
|
511
557
|
return lines.join('\n');
|
|
@@ -514,7 +560,7 @@ class SkillManagerPlugin extends Plugin {
|
|
|
514
560
|
/**
|
|
515
561
|
* 加载所有 skills
|
|
516
562
|
*/
|
|
517
|
-
_loadAllSkills() {
|
|
563
|
+
async _loadAllSkills() {
|
|
518
564
|
if (this._loaded) return;
|
|
519
565
|
|
|
520
566
|
let totalLoaded = 0;
|
|
@@ -554,7 +600,7 @@ class SkillManagerPlugin extends Plugin {
|
|
|
554
600
|
if (!isValidSkillName(entry.name)) continue;
|
|
555
601
|
if (this._skills.has(entry.name)) continue;
|
|
556
602
|
try {
|
|
557
|
-
this._loadSkill(entry.name, skillPath);
|
|
603
|
+
await this._loadSkill(entry.name, skillPath);
|
|
558
604
|
totalLoaded++;
|
|
559
605
|
} catch (err) {
|
|
560
606
|
log.error(` Failed to load skill '${entry.name}':`, err.message);
|
|
@@ -744,7 +790,7 @@ class SkillManagerPlugin extends Plugin {
|
|
|
744
790
|
/**
|
|
745
791
|
* 加载单个 skill
|
|
746
792
|
*/
|
|
747
|
-
_loadSkill(name, skillPath) {
|
|
793
|
+
async _loadSkill(name, skillPath) {
|
|
748
794
|
// 查找 markdown 文件,优先加载 SKILL.md 或 AGENTS.md(支持多层嵌套)
|
|
749
795
|
const skillMdPath = this._findSkillMarkdown(skillPath);
|
|
750
796
|
if (!skillMdPath) {
|
|
@@ -771,7 +817,7 @@ class SkillManagerPlugin extends Plugin {
|
|
|
771
817
|
const skill = new Skill(metadata, stripFrontmatter(content));
|
|
772
818
|
skill._skillPath = skillPath; // 保存路径用于加载 index.js
|
|
773
819
|
skill.install(this._framework);
|
|
774
|
-
skill.registerCommands(); //
|
|
820
|
+
await skill.registerCommands(); // 异步注册(支持 ESM 包)
|
|
775
821
|
|
|
776
822
|
// 将 skill 的命令添加到 SkillManagerPlugin.tools,供 extension-executor 扫描
|
|
777
823
|
Object.assign(this.tools, skill.tools);
|
|
@@ -782,8 +828,9 @@ class SkillManagerPlugin extends Plugin {
|
|
|
782
828
|
// 扫描 scripts 子目录(可执行脚本)
|
|
783
829
|
const scripts = this._scanScripts(skillPath);
|
|
784
830
|
|
|
785
|
-
|
|
786
|
-
|
|
831
|
+
const skillName = metadata.name || name;
|
|
832
|
+
this._skills.set(skillName, {
|
|
833
|
+
name: skillName,
|
|
787
834
|
metadata,
|
|
788
835
|
content: skill.content,
|
|
789
836
|
instance: skill,
|
|
@@ -966,7 +1013,7 @@ class SkillManagerPlugin extends Plugin {
|
|
|
966
1013
|
return this._skills.size;
|
|
967
1014
|
}
|
|
968
1015
|
|
|
969
|
-
reload(framework) {
|
|
1016
|
+
async reload(framework) {
|
|
970
1017
|
this._skills.clear();
|
|
971
1018
|
this.tools = {};
|
|
972
1019
|
this._loaded = false;
|
|
@@ -978,7 +1025,7 @@ class SkillManagerPlugin extends Plugin {
|
|
|
978
1025
|
extExecutor._extensions.delete('skill');
|
|
979
1026
|
}
|
|
980
1027
|
|
|
981
|
-
this._loadAllSkills();
|
|
1028
|
+
await this._loadAllSkills();
|
|
982
1029
|
|
|
983
1030
|
// 通知 UI 刷新 skill 命令(用于 autocomplete 热重载)
|
|
984
1031
|
framework.emit('skill:reloaded', { skills: Array.from(this._skills.keys()) });
|
|
@@ -24,6 +24,7 @@ class SubAgentPlugin extends Plugin {
|
|
|
24
24
|
this.tools = config.tools || {}
|
|
25
25
|
this.parentTools = config.parentTools || []
|
|
26
26
|
this.llmConfig = config.llmConfig || null
|
|
27
|
+
this.hidden = config.hidden || false // 隐藏子Agent,不在指令系统中显示
|
|
27
28
|
|
|
28
29
|
this.config = {}
|
|
29
30
|
|
|
@@ -83,7 +84,8 @@ class SubAgentPlugin extends Plugin {
|
|
|
83
84
|
apiKey: llmConfig.apiKey,
|
|
84
85
|
baseURL: llmConfig.baseURL,
|
|
85
86
|
tools:{},
|
|
86
|
-
parentTools: all_tools
|
|
87
|
+
parentTools: all_tools,
|
|
88
|
+
hidden: this.hidden
|
|
87
89
|
})
|
|
88
90
|
|
|
89
91
|
this._parentAgent = parentAgent
|
|
@@ -155,7 +157,7 @@ class SubAgentPlugin extends Plugin {
|
|
|
155
157
|
- 处理本职外的任务
|
|
156
158
|
- 代替其他子Agent执行操作
|
|
157
159
|
- 在回复中透露你的内部实现细节
|
|
158
|
-
- 不先调用 ext_skill/
|
|
160
|
+
- 不先调用 ext_skill/skill_load 就直接使用 ext_call`
|
|
159
161
|
}
|
|
160
162
|
|
|
161
163
|
_registerDelegateTool() {
|
|
@@ -300,6 +302,18 @@ class SubAgentPlugin extends Plugin {
|
|
|
300
302
|
}
|
|
301
303
|
|
|
302
304
|
class SubAgentManagerPlugin extends Plugin {
|
|
305
|
+
// 声明式 prompt parts
|
|
306
|
+
prompts = [
|
|
307
|
+
{
|
|
308
|
+
name: 'subagents',
|
|
309
|
+
slot: 'CAPABILITY',
|
|
310
|
+
description: '子Agent描述与分配规则',
|
|
311
|
+
provider: function () {
|
|
312
|
+
return this._buildDescription();
|
|
313
|
+
},
|
|
314
|
+
},
|
|
315
|
+
];
|
|
316
|
+
|
|
303
317
|
constructor(config = {}) {
|
|
304
318
|
super()
|
|
305
319
|
this.name = 'subagent-manager'
|
|
@@ -324,6 +338,7 @@ class SubAgentManagerPlugin extends Plugin {
|
|
|
324
338
|
}
|
|
325
339
|
|
|
326
340
|
start(framework) {
|
|
341
|
+
super.start(framework);
|
|
327
342
|
this._loadAgentsFromDir()
|
|
328
343
|
|
|
329
344
|
for (const agentConfig of this.agents) {
|
|
@@ -344,7 +359,7 @@ class SubAgentManagerPlugin extends Plugin {
|
|
|
344
359
|
}
|
|
345
360
|
})
|
|
346
361
|
|
|
347
|
-
|
|
362
|
+
// prompt part 由基类 _autoRegisterPrompts 自动处理
|
|
348
363
|
|
|
349
364
|
return this
|
|
350
365
|
}
|
|
@@ -355,12 +370,14 @@ class SubAgentManagerPlugin extends Plugin {
|
|
|
355
370
|
description: '列出所有子Agent',
|
|
356
371
|
inputSchema: z.object({}),
|
|
357
372
|
execute: async () => {
|
|
358
|
-
const agents = Array.from(this._subAgents.values())
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
373
|
+
const agents = Array.from(this._subAgents.values())
|
|
374
|
+
.filter(p => !p.hidden) // 过滤隐藏的子Agent
|
|
375
|
+
.map(p => ({
|
|
376
|
+
name: p.name,
|
|
377
|
+
role: p.role,
|
|
378
|
+
description: p.description,
|
|
379
|
+
toolCount: p._agent ? p._agent.getTools().length : 0
|
|
380
|
+
}))
|
|
364
381
|
return { success: true, data: agents }
|
|
365
382
|
}
|
|
366
383
|
})
|
|
@@ -419,12 +436,14 @@ class SubAgentManagerPlugin extends Plugin {
|
|
|
419
436
|
description: '列出所有子Agent',
|
|
420
437
|
inputSchema: z.object({}),
|
|
421
438
|
execute: async () => {
|
|
422
|
-
const agents = Array.from(this._subAgents.values())
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
439
|
+
const agents = Array.from(this._subAgents.values())
|
|
440
|
+
.filter(p => !p.hidden) // 过滤隐藏的子Agent
|
|
441
|
+
.map(p => ({
|
|
442
|
+
name: p.name,
|
|
443
|
+
role: p.role,
|
|
444
|
+
description: p.description,
|
|
445
|
+
toolCount: p._agent ? p._agent.getTools().length : 0
|
|
446
|
+
}))
|
|
428
447
|
return { success: true, data: agents }
|
|
429
448
|
}
|
|
430
449
|
})
|