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.
Files changed (78) hide show
  1. package/README.md +6 -6
  2. package/docs/public-api.md +91 -0
  3. package/docs/system-prompt.md +219 -0
  4. package/docs/usage.md +6 -6
  5. package/package.json +1 -1
  6. package/plugins/ambient/ExplorerLoop.js +1 -0
  7. package/plugins/ambient/index.js +1 -0
  8. package/plugins/core/coordinator/index.js +10 -5
  9. package/plugins/core/default/bootstrap.js +21 -3
  10. package/plugins/core/default/config.js +12 -3
  11. package/plugins/core/python-loader/index.js +3 -3
  12. package/plugins/core/scheduler/index.js +26 -2
  13. package/plugins/core/skill-manager/index.js +198 -151
  14. package/plugins/core/sub-agent/index.js +34 -15
  15. package/plugins/core/think/index.js +1 -0
  16. package/plugins/core/workflow/index.js +5 -4
  17. package/plugins/executors/data-splitter/index.js +14 -1
  18. package/plugins/executors/extension/index.js +51 -37
  19. package/plugins/executors/python/index.js +3 -3
  20. package/plugins/executors/shell/index.js +6 -4
  21. package/plugins/io/web/index.js +2 -1
  22. package/plugins/memory/index.js +29 -74
  23. package/plugins/messaging/email/handlers.js +1 -19
  24. package/plugins/messaging/email/monitor.js +2 -17
  25. package/plugins/messaging/email/reply.js +2 -1
  26. package/plugins/messaging/email/utils.js +20 -1
  27. package/plugins/messaging/feishu/index.js +1 -1
  28. package/plugins/messaging/qq/index.js +1 -1
  29. package/plugins/messaging/telegram/index.js +1 -1
  30. package/plugins/messaging/weixin/index.js +1 -1
  31. package/plugins/plugin-manager/index.js +1 -33
  32. package/plugins/tools/index.js +14 -1
  33. package/skills/plugins/SKILL.md +316 -0
  34. package/skills/skill-guide/SKILL.md +5 -5
  35. package/skills/{subagent-guide → subagents}/SKILL.md +1 -1
  36. package/skills/{workflow-guide → workflows}/SKILL.md +3 -3
  37. package/skills/{workflow-troubleshooting → workflows/workflow-troubleshooting}/DEBUGGING.md +197 -197
  38. package/skills/{workflow-troubleshooting → workflows/workflow-troubleshooting}/SKILL.md +391 -391
  39. package/src/agent/base.js +5 -20
  40. package/src/agent/index.js +20 -8
  41. package/src/agent/main.js +100 -23
  42. package/src/agent/prompt-registry.js +296 -0
  43. package/src/agent/sub-config.js +1 -78
  44. package/src/agent/sub.js +19 -24
  45. package/src/cli/commands/plugin.js +1 -60
  46. package/src/cli/ui/chat-ui-old.js +1 -1
  47. package/src/cli/ui/chat-ui.js +1 -1
  48. package/src/cli/ui/components/agent-mention-provider.js +1 -1
  49. package/src/common/constants.js +42 -0
  50. package/src/common/id.js +13 -0
  51. package/src/common/queue.js +2 -2
  52. package/src/context/compressor.js +17 -9
  53. package/src/framework/framework.js +185 -0
  54. package/src/framework/index.js +1 -2
  55. package/src/framework/lifecycle.js +102 -1
  56. package/src/framework/loader.js +1 -55
  57. package/src/index.js +11 -2
  58. package/src/plugin/base.js +69 -55
  59. package/src/plugin/loader.js +1 -57
  60. package/src/session/entry.js +1 -11
  61. package/src/storage/manager.js +1 -12
  62. package/src/tool/executor.js +2 -1
  63. package/src/tool/router.js +5 -5
  64. package/src/utils/data-splitter.js +2 -1
  65. package/src/utils/index.js +150 -0
  66. package/src/utils/message-validator.js +21 -17
  67. package/src/utils/plugin-helpers.js +19 -5
  68. package/subagent.md +2 -2
  69. package/tests/core/plugin-prompts.test.js +219 -0
  70. package/tests/core/prompt-registry.test.js +209 -0
  71. package/src/cli/utils/plugin-config.js +0 -50
  72. package/src/config/plugin-config.js +0 -50
  73. /package/skills/{ambient-agent → ambient}/SKILL.md +0 -0
  74. /package/skills/{foliko-dev → foliko}/AGENTS.md +0 -0
  75. /package/skills/{foliko-dev → foliko}/SKILL.md +0 -0
  76. /package/skills/{mcp-usage → mcp}/SKILL.md +0 -0
  77. /package/skills/{plugin-guide → plugins-guide}/SKILL.md +0 -0
  78. /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
- * 验证 skill 名称
48
- * 1-64字符,字母数字、下划线和连字符,不能以连字符或下划线开头或结尾
49
+ * 格式化选项为人类可读的描述
50
+ * @param {Object} o - 选项对象 { flags, description, defaultValue, required }(required 未设置或为 false 时不标必填)
51
+ * @returns {string} 格式化后的描述
49
52
  */
50
- function isValidSkillName(name) {
51
- if (!name || name.length < 1 || name.length > 64) return false;
52
- if (!/^[a-zA-Z0-9_-]+$/.test(name)) return false;
53
- if (name.startsWith('-') || name.startsWith('_') || name.endsWith('-') || name.endsWith('_'))
54
- return false;
55
- if (/--/.test(name) || /__/.test(name)) return false;
56
- return true;
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
- * 解析 YAML frontmatter
79
+ * 格式化选项为示例参数
80
+ * @param {Object} o - 选项对象
81
+ * @returns {string} 格式化后的示例
61
82
  */
62
- function parseFrontmatter(content) {
63
- const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
64
- if (!match) return null;
65
-
66
- const frontmatter = {};
67
- const lines = match[1].split('\n');
68
- let currentKey = null;
69
-
70
- for (const line of lines) {
71
- // 处理 metadata 嵌套
72
- if (currentKey === 'metadata') {
73
- if (line.match(/^ {2}[a-zA-Z]/)) {
74
- const colonIndex = line.indexOf(':');
75
- if (colonIndex > 0) {
76
- const key = line.substring(2, colonIndex).trim();
77
- const value = line
78
- .substring(colonIndex + 1)
79
- .trim()
80
- .replace(/^["']|["']$/g, '');
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
- * 移除 frontmatter,获取正文
107
+ * 验证 skill 名称
108
+ * 1-64字符,字母数字、下划线和连字符,不能以连字符或下划线开头或结尾
122
109
  */
123
- function stripFrontmatter(content) {
124
- const match = content.match(/^---\r?\n[\s\S]*?\r?\n---\r?\n?/);
125
- return match ? content.slice(match[0].length).trim() : content.trim();
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) return;
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)) return;
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
- delete require.cache[require.resolve(indexPath)];
196
- const mod = require(indexPath);
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
- const cmds = Array.isArray(commands) ? commands : [commands];
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 || `Skill command: ${fullName}`,
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, // 保存 options 信息用于提示词生成
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 系统,通过 ext_call 调用
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
- this._loadAllSkills();
337
+ super.start(framework);
338
+ this._loadAllSkills().catch(err => log.error('_loadAllSkills failed:', err.message));
307
339
 
308
- // 注册 loadSkill 工具
340
+ // 注册 skill_load 工具
309
341
  framework.registerTool({
310
- name: 'loadSkill',
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
- // 注册 reloadSkills 工具
389
+ // 注册 skill_reload 工具
333
390
  framework.registerTool({
334
- name: 'reloadSkills',
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
- // 注册 loadReference 工具(按需加载 skill 的附加文档)
406
+ // 注册 skill_load_reference 工具(按需加载 skill 的附加文档)
350
407
  framework.registerTool({
351
- name: 'loadReference',
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
- // 注册 listScripts 工具(列出 skill 的脚本)
444
+ // 注册 skill_list_scripts 工具(列出 skill 的脚本)
388
445
  framework.registerTool({
389
- name: 'listScripts',
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
- // 注册 loadScript 工具(读取脚本内容)
461
+ // 注册 skill_load_script 工具(读取脚本内容)
405
462
  framework.registerTool({
406
- name: 'loadScript',
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
- lines.push(`### ${name}`);
471
- lines.push(`**描述**: ${descText}`);
528
+ const parts = [];
529
+ if (descText) parts.push(descText);
472
530
 
473
- // 如果该技能有命令,添加调用方式
474
531
  if (toolsBySkill[name]) {
475
- lines.push('');
476
- lines.push('**调用方式:**');
477
- lines.push('```');
478
- lines.push(`ext_call({`);
479
- lines.push(` plugin: "skill",`);
480
- lines.push(` tool: "${name}:<命令名>",`);
481
- lines.push(` args: { command: "<命令行>" }`);
482
- lines.push(`})`);
483
- lines.push('```');
484
- lines.push('');
485
- lines.push('**可用命令:**');
486
- for (const c of toolsBySkill[name]) {
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
- lines.push('');
499
- lines.push('**示例:**');
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
- '> **重要提示**:当需要开发插件、执行专业任务时,必须先使用 `loadSkill` 工具加载对应技能,获取专业指导。'
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(); // 注册 skill 的命令
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
- this._skills.set(name, {
786
- name,
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/loadSkill 就直接使用 ext_call`
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
- this.registerPromptPart('subagents', 85, () => this._buildDescription());
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()).map(p => ({
359
- name: p.name,
360
- role: p.role,
361
- description: p.description,
362
- toolCount: p._agent ? p._agent.getTools().length : 0
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()).map(p => ({
423
- name: p.name,
424
- role: p.role,
425
- description: p.description,
426
- toolCount: p._agent ? p._agent.getTools().length : 0
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
  })