foliko 2.0.4 → 2.0.6

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 (59) hide show
  1. package/.claude/settings.local.json +4 -1
  2. package/.cli_default_systemPrompt.md +291 -0
  3. package/CLAUDE.md +3 -0
  4. package/README.md +20 -3
  5. package/docs/architecture.md +34 -2
  6. package/docs/extensions.md +199 -0
  7. package/docs/migration.md +100 -0
  8. package/docs/public-api.md +1180 -6
  9. package/docs/usage.md +122 -30
  10. package/package.json +1 -1
  11. package/plugins/core/audit/index.js +1 -1
  12. package/plugins/core/default/bootstrap.js +65 -19
  13. package/plugins/core/python-loader/index.js +43 -25
  14. package/plugins/core/skill-manager/PROMPT.md +6 -0
  15. package/plugins/core/skill-manager/index.js +538 -93
  16. package/plugins/core/sub-agent/PROMPT.md +10 -0
  17. package/plugins/core/sub-agent/index.js +36 -3
  18. package/plugins/core/think/index.js +1 -0
  19. package/plugins/core/workflow/index.js +106 -22
  20. package/plugins/executors/data-splitter/PROMPT.md +13 -0
  21. package/plugins/executors/data-splitter/index.js +5 -4
  22. package/plugins/executors/extension/extension-registry.js +145 -0
  23. package/plugins/executors/extension/index.js +405 -437
  24. package/plugins/executors/extension/prompt-builder.js +359 -0
  25. package/plugins/executors/extension/skill-helper.js +143 -0
  26. package/plugins/messaging/feishu/index.js +5 -3
  27. package/plugins/messaging/qq/index.js +6 -4
  28. package/plugins/messaging/telegram/index.js +6 -3
  29. package/plugins/messaging/weixin/index.js +5 -3
  30. package/plugins/tools/PROMPT.md +26 -0
  31. package/plugins/tools/index.js +6 -5
  32. package/skills/foliko/AGENTS.md +196 -43
  33. package/skills/foliko/SKILL.md +157 -28
  34. package/skills/mcp/SKILL.md +77 -118
  35. package/skills/plugins/SKILL.md +89 -3
  36. package/skills/python/SKILL.md +57 -39
  37. package/skills/skill-guide/SKILL.md +42 -34
  38. package/skills/workflows/SKILL.md +224 -9
  39. package/skills/workflows/workflow-troubleshooting/SKILL.md +221 -281
  40. package/src/agent/chat.js +48 -27
  41. package/src/agent/main.js +34 -13
  42. package/src/agent/prompt-registry.js +133 -87
  43. package/src/agent/prompts/PROMPT.md +3 -0
  44. package/src/agent/sub.js +1 -1
  45. package/src/cli/ui/chat-ui-old.js +5 -2
  46. package/src/cli/ui/chat-ui.js +5 -2
  47. package/src/common/constants.js +12 -0
  48. package/src/common/error-capture.js +91 -0
  49. package/src/common/logger.js +2 -2
  50. package/src/context/compressor.js +6 -2
  51. package/src/executors/mcp-executor.js +105 -125
  52. package/src/framework/framework.js +632 -6
  53. package/src/index.js +4 -0
  54. package/src/plugin/base.js +913 -10
  55. package/src/plugin/manager.js +29 -8
  56. package/src/tool/schema.js +32 -9
  57. package/tests/core/plugin-prompts.test.js +13 -13
  58. package/tests/core/prompt-registry.test.js +59 -51
  59. package/website/index.html +821 -0
@@ -0,0 +1,359 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * ExtensionPromptBuilder - Prompt 构建器
5
+ * 统一生成扩展工具的系统提示词
6
+ *
7
+ * MCP 服务器现在以 `mcp:<servername>` 形式注册为普通扩展,
8
+ * 由 extension-registry 统一管理,不再需要特殊处理。
9
+ */
10
+
11
+ const { zodSchemaToMarkdown, zodSchemaToTable } = require('@chnak/zod-to-markdown');
12
+
13
+ // 内部扩展名(不显示在提示词中)
14
+ const HIDDEN_PLUGINS = new Set(['skill']);
15
+
16
+ // 命名空间常量
17
+ const NS = {
18
+ NORMAL: 'normal',
19
+ MCP: 'mcp',
20
+ SKILL: 'skill',
21
+ PYTHON: 'python',
22
+ };
23
+
24
+ const NS_LABEL = {
25
+ [NS.NORMAL]: '扩展',
26
+ [NS.MCP]: 'MCP 服务器',
27
+ [NS.SKILL]: 'Skill',
28
+ [NS.PYTHON]: 'Python 插件',
29
+ };
30
+
31
+ class ExtensionPromptBuilder {
32
+ constructor(registry, framework) {
33
+ this._registry = registry;
34
+ this._framework = framework;
35
+ }
36
+
37
+ setMcpExecutor(_mcpExecutor) {
38
+ // 保留方法签名以兼容旧代码,但不再使用
39
+ }
40
+
41
+ /**
42
+ * 完整的 Extensions 提示词
43
+ */
44
+ build() {
45
+ const body = this._buildBody();
46
+ if (!body) return '';
47
+
48
+ return [
49
+ '## 【Extensions】扩展插件',
50
+ '',
51
+ this._buildHeader(),
52
+ body,
53
+ this._buildFooter(),
54
+ ].join('\n');
55
+ }
56
+
57
+ /**
58
+ * 仅构建扩展列表部分(不含 header/footer)
59
+ */
60
+ buildBody() {
61
+ return this._buildBody();
62
+ }
63
+
64
+ // ============ 内部方法 ============
65
+
66
+ _buildHeader() {
67
+ return [
68
+ '**调用模式:所有扩展都通过 `ext_call` 统一调用。**',
69
+ '',
70
+ '**plugin 命名空间(4 种类型,按前缀区分):**',
71
+ '- **普通扩展**(无前缀):如 `email`、`gate-trading`',
72
+ '- **MCP 服务器**(`mcp:`):如 `mcp:designmd`',
73
+ '- **Skill**(`skill:`):如 `skill:ambient-agent`',
74
+ '- **Python 插件**(`python:`):如 `python:stock`',
75
+ '',
76
+ '**使用流程(推荐 1 → 2):**',
77
+ '1. 首次使用某插件前,先 `ext_skill({plugin: "<name>"})` 查询参数 schema',
78
+ '2. 然后 `ext_call({plugin, tool, args: {...}})` 调用(`args` 必须是 **JSON 对象**,不是字符串)',
79
+ '',
80
+ '**调用示例:**',
81
+ '```js',
82
+ '// Gate.io 查询余额',
83
+ 'ext_call({plugin: "gate-trading", tool: "gate_get_account_balance", args: {currency: "USDT"}})',
84
+ '',
85
+ '// MCP 服务器搜索设计',
86
+ 'ext_call({plugin: "mcp:designmd", tool: "search_design_kits", args: {query: "dark mode"}})',
87
+ '',
88
+ '// Skill 命令',
89
+ 'ext_call({plugin: "skill:ambient-agent", tool: "ambient_goals", args: {action: "list"}})',
90
+ '```',
91
+ '',
92
+ '> **关于 `args`**:必须是结构化对象(不是 JSON 字符串、不是命令行)。',
93
+ '> ✅ `args: {name: "alice", age: 30}`',
94
+ '> ❌ `args: \'{"name":"alice","age":30}\'`(JSON 字符串)',
95
+ '> ❌ `args: "--name alice --age 30"`(命令行)',
96
+ '',
97
+ ].join('\n');
98
+ }
99
+
100
+ _buildFooter() {
101
+ return [
102
+ '',
103
+ '**返回值格式:**',
104
+ '- 大多数工具:`{success: true, data: {...}}` 或 `{success: false, error: "..."}`',
105
+ '- `ext_skill` 本身:返回参数说明的 Markdown 文本',
106
+ '- 部分工具可能直接返回字符串(如命令执行结果)',
107
+ '',
108
+ '**错误处理:**',
109
+ '- 插件不存在 → 返回 `{success:false, error: "扩展插件 \'xxx\' 不存在"}`',
110
+ '- 参数错误 → 返回 `{success:false, error: "..."}`',
111
+ '- 先读 `success` 字段;为 false 时读 `error` 并考虑修正后重试',
112
+ '',
113
+ '**禁止事项:**',
114
+ '- ❌ 不先调用 `ext_skill` 就直接 `ext_call`(除非你已熟悉参数结构)',
115
+ '- ❌ 凭记忆编造 `plugin` 名(必须从下方列表或 `ext_list` 复制)',
116
+ '- ❌ 把 `args` 写成 JSON 字符串或命令行',
117
+ '- ❌ 看到错误就放弃(应分析 `error` 信息并尝试修正)',
118
+ '',
119
+ ].join('\n');
120
+ }
121
+
122
+ _buildBody() {
123
+ const buckets = {
124
+ mcp: [],
125
+ skill: [],
126
+ python: [],
127
+ normal: [],
128
+ };
129
+
130
+ for (const [name, ext] of this._getExtensionsIterable()) {
131
+ if (HIDDEN_PLUGINS.has(name)) continue;
132
+ const item = { name, ext };
133
+
134
+ if (name.startsWith('mcp:')) buckets.mcp.push(item);
135
+ else if (name.startsWith('skill:')) buckets.skill.push(item);
136
+ else if (name.startsWith('python:')) buckets.python.push(item);
137
+ else buckets.normal.push(item);
138
+ }
139
+
140
+ const sections = [];
141
+ const groupTitles = [
142
+ { key: 'normal', label: '普通扩展' },
143
+ { key: 'mcp', label: 'MCP 服务器' },
144
+ { key: 'skill', label: 'Skill 命令' },
145
+ { key: 'python', label: 'Python 插件' },
146
+ ];
147
+
148
+ for (const { key, label } of groupTitles) {
149
+ const items = buckets[key];
150
+ if (items.length === 0) continue;
151
+ sections.push(`#### ${label}`);
152
+ for (const { ext } of items) {
153
+ sections.push(this._formatPlugin(ext));
154
+ }
155
+ sections.push('');
156
+ }
157
+
158
+ return sections.join('\n').trimEnd();
159
+ }
160
+
161
+ _getExtensionsIterable() {
162
+ return this._registry._extensions.entries();
163
+ }
164
+
165
+ _formatPlugin(ext) {
166
+ const lines = [
167
+ `- **\`${ext.name}\`**:${ext.description || '无描述'}`,
168
+ ];
169
+ for (const tool of ext.tools) {
170
+ if (!tool || !tool.name) continue;
171
+ lines.push(` - \`${tool.name}\`: ${tool.description || '无描述'}`);
172
+ }
173
+ return lines.join('\n');
174
+ }
175
+
176
+ // ============ ext_skill 工具调用返回 ============
177
+
178
+ /**
179
+ * 生成 ext_skill 工具的返回内容(统一入口)
180
+ * 内部根据 namespace 类型差异化渲染
181
+ */
182
+ buildSkill(pluginName) {
183
+ return this._renderPluginDetail(pluginName);
184
+ }
185
+
186
+ /**
187
+ * 统一渲染插件详情(普通 / MCP / Skill / Python 共用)
188
+ */
189
+ _renderPluginDetail(pluginName) {
190
+ const ns = this._detectNamespace(pluginName);
191
+ const ext = this._registry.getPlugin(pluginName);
192
+
193
+ if (!ext) {
194
+ return this._renderNotFound(pluginName, ns);
195
+ }
196
+
197
+ const parts = [
198
+ this._renderHead(pluginName, ext, ns),
199
+ ];
200
+
201
+ // 仅 skill: 类型附加 SKILL.md 内容
202
+ if (ns === NS.SKILL) {
203
+ const skillBody = this._renderSkillBody(pluginName);
204
+ if (skillBody) parts.push(skillBody);
205
+ }
206
+
207
+ parts.push(this._renderToolList(ext));
208
+
209
+ return parts.join('\n\n').trimEnd() + '\n';
210
+ }
211
+
212
+ /**
213
+ * 检测 plugin 名称的命名空间
214
+ */
215
+ _detectNamespace(pluginName) {
216
+ if (typeof pluginName !== 'string') return NS.NORMAL;
217
+ if (pluginName.startsWith('skill:')) return NS.SKILL;
218
+ if (pluginName.startsWith('mcp:')) return NS.MCP;
219
+ if (pluginName.startsWith('python:')) return NS.PYTHON;
220
+ return NS.NORMAL;
221
+ }
222
+
223
+ /**
224
+ * 渲染友好的"未找到"错误提示
225
+ */
226
+ _renderNotFound(pluginName, ns) {
227
+ const label = NS_LABEL[ns] || '扩展';
228
+ return [
229
+ `## ❌ 找不到${label}:\`${pluginName}\``,
230
+ '',
231
+ '**可能的原因:**',
232
+ '- 插件名拼写错误(plugin 名称区分大小写)',
233
+ '- 插件未加载(先调用 `ext_list` 工具查看已注册的插件)',
234
+ '- 命名空间前缀错误(注意 `mcp:`、`skill:`、`python:` 前缀)',
235
+ '',
236
+ '**建议步骤:**',
237
+ '1. 调用 `ext_list` 列出所有可用插件',
238
+ '2. 复制准确的 plugin 名(区分大小写)',
239
+ '3. 再次调用 `ext_skill({plugin: "<正确的名字>"})`',
240
+ '',
241
+ ].join('\n');
242
+ }
243
+
244
+ /**
245
+ * 渲染插件头部:标题、描述、类型元数据、快速调用示例
246
+ */
247
+ _renderHead(pluginName, ext, ns) {
248
+ const label = NS_LABEL[ns] || '扩展';
249
+ const toolCount = ext.tools.length;
250
+ const exampleTool = ext.tools.find((t) => t && t.name);
251
+ const description = ext.description || '无描述';
252
+
253
+ const lines = [
254
+ `## 📦 ${label}:\`${pluginName}\``,
255
+ '',
256
+ `> **${description}**`,
257
+ '',
258
+ `**类型**:${label} · **工具数量**:${toolCount}`,
259
+ '',
260
+ ];
261
+
262
+ if (exampleTool) {
263
+ lines.push(
264
+ '**🚀 快速调用示例**:',
265
+ '```js',
266
+ `ext_call({`,
267
+ ` plugin: "${pluginName}",`,
268
+ ` tool: "${exampleTool.name}",`,
269
+ ` args: { /* 必填参数见下方表格 */ }`,
270
+ `})`,
271
+ '```'
272
+ );
273
+ }
274
+
275
+ return lines.join('\n');
276
+ }
277
+
278
+ /**
279
+ * 渲染 SKILL.md 额外内容(仅 skill: 类型)
280
+ */
281
+ _renderSkillBody(pluginName) {
282
+ const skillName = pluginName.slice('skill:'.length);
283
+
284
+ let skillInfo = null;
285
+ try {
286
+ const fw = this._getFramework();
287
+ const sm = fw?.pluginManager?.get('skill-manager');
288
+ skillInfo = sm?.getSkill?.(skillName);
289
+ } catch { /* 忽略 */ }
290
+
291
+ if (!skillInfo?.content?.trim()) return '';
292
+
293
+ return [
294
+ '### 📖 技能说明(来自 SKILL.md)',
295
+ '',
296
+ skillInfo.content.trim(),
297
+ '',
298
+ '---',
299
+ ].join('\n');
300
+ }
301
+
302
+ /**
303
+ * 渲染工具列表(含各工具的参数详情)
304
+ */
305
+ _renderToolList(ext) {
306
+ if (!ext.tools || ext.tools.length === 0) {
307
+ return '### ⚠️ 该插件暂无可用工具';
308
+ }
309
+
310
+ const sections = ['### 📚 工具参数详情', ''];
311
+
312
+ for (const tool of ext.tools) {
313
+ if (!tool || !tool.name) continue;
314
+
315
+ sections.push(`#### \`${tool.name}\``);
316
+ sections.push(tool.description || '*无描述*');
317
+
318
+ if (tool.inputSchema) {
319
+ const schemaText = this._formatSchema(tool.inputSchema, tool._rawSchema);
320
+ if (schemaText && schemaText !== '无') {
321
+ sections.push('');
322
+ sections.push('**参数:**');
323
+ sections.push('');
324
+ sections.push(schemaText);
325
+ }
326
+ }
327
+
328
+ sections.push('');
329
+ sections.push('---');
330
+ sections.push('');
331
+ }
332
+
333
+ // 移除末尾多余的 `---` 和空行,保持整洁
334
+ while (sections.length > 0 && (sections[sections.length - 1] === '' || sections[sections.length - 1] === '---')) {
335
+ sections.pop();
336
+ }
337
+
338
+ return sections.join('\n').trimEnd();
339
+ }
340
+
341
+ _getFramework() {
342
+ return this._framework;
343
+ }
344
+
345
+ _formatSchema(schema, rawSchema) {
346
+ // 优先用 zodSchemaToMarkdown(MCP/普通扩展都走这条路径)
347
+ try {
348
+ const md = zodSchemaToMarkdown(schema);
349
+ if (md && !/Zod\w+/.test(md)) return md;
350
+ } catch { /* fall through */ }
351
+ try {
352
+ return zodSchemaToTable(schema) || '无';
353
+ } catch {
354
+ return '无';
355
+ }
356
+ }
357
+ }
358
+
359
+ module.exports = { ExtensionPromptBuilder, HIDDEN_PLUGINS };
@@ -0,0 +1,143 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * SkillHelper - Skill 命令辅助
5
+ * 提供 Skill 命令的查询和执行能力
6
+ *
7
+ * 注意:Skill 命令现在以 `skill:<skillname>` 扩展形式注册,
8
+ * 每个 skill 的命令是扩展下的 tool(tool.name 是命令名,不含前缀)
9
+ *
10
+ * 命令模型:
11
+ * {
12
+ * plugin: 'skill:my-skill', // 完整 namespace,可直接用于 ext_call
13
+ * command: 'list', // 不带 skill: 前缀的纯命令名
14
+ * name: 'my-skill:list', // 旧式命名(保留向后兼容)
15
+ * description: '...',
16
+ * options: { ... } | null, // 来自 SKILL.md 的命令参数元数据
17
+ * }
18
+ */
19
+
20
+ class SkillHelper {
21
+ constructor(registry, framework) {
22
+ this._registry = registry;
23
+ this._framework = framework;
24
+ }
25
+
26
+ /**
27
+ * 获取所有 Skill 命令(统一从 skill:<name> 扩展下收集)
28
+ * @returns {Array<{plugin: string, command: string, name: string, description: string, options: object|null}>}
29
+ */
30
+ getCommands() {
31
+ const commands = [];
32
+
33
+ for (const [extName, ext] of this._registry._extensions.entries()) {
34
+ if (!extName || !extName.startsWith('skill:')) continue;
35
+
36
+ const skillName = extName.slice('skill:'.length);
37
+ if (!skillName) continue;
38
+
39
+ // 完整的 namespace 标识,可直接传给 ext_call({ plugin })
40
+ const plugin = extName;
41
+
42
+ for (const tool of ext.tools || []) {
43
+ if (!tool || !tool.name) continue;
44
+
45
+ commands.push({
46
+ plugin, // ✅ 新增:完整 namespace
47
+ command: tool.name, // ✅ 新增:纯命令名(不含 skill: 前缀)
48
+ name: `${skillName}:${tool.name}`, // 旧式命名,保留兼容
49
+ description: tool.description || tool.name,
50
+ options: tool._options || null,
51
+ });
52
+ }
53
+ }
54
+
55
+ return commands;
56
+ }
57
+
58
+ /**
59
+ * 获取 Skill 命令的帮助文本(按 skill 分组展示)
60
+ * @returns {string}
61
+ */
62
+ getCommandsHelp() {
63
+ const commands = this.getCommands();
64
+
65
+ if (commands.length === 0) {
66
+ return '(暂无可用 Skill 命令)';
67
+ }
68
+
69
+ // 按 plugin (skill 完整 namespace) 分组
70
+ const grouped = new Map();
71
+ for (const cmd of commands) {
72
+ const list = grouped.get(cmd.plugin) || [];
73
+ list.push(cmd);
74
+ grouped.set(cmd.plugin, list);
75
+ }
76
+
77
+ const lines = [];
78
+ for (const [plugin, cmds] of grouped) {
79
+ lines.push(`## ${plugin}`);
80
+ for (const cmd of cmds) {
81
+ const desc = cmd.description || '无描述';
82
+ lines.push(` · \`${cmd.command}\` - ${desc}`);
83
+ }
84
+ lines.push('');
85
+ }
86
+
87
+ return lines.join('\n').trimEnd();
88
+ }
89
+
90
+ /**
91
+ * 执行 Skill 命令
92
+ *
93
+ * @param {string} command 格式:`<skill>:<command>` (如 'my-skill:list')
94
+ * @param {Object|string} [args] 参数对象;旧模式下也支持字符串(自动包装为 `{ command: <str> }`)
95
+ * @returns {Promise<Object|null>} 工具成功返回的结果;以下情况返回 null:
96
+ * - command 格式不合法
97
+ * - command 为空
98
+ * - skillName 或 cmdName 为空
99
+ * - framework.executeTool 抛出异常
100
+ * - ext_call 返回 success: false
101
+ */
102
+ async executeCommand(command, args) {
103
+ // 参数校验:先剥离非法输入
104
+ if (typeof command !== 'string' || !command.includes(':')) {
105
+ return null;
106
+ }
107
+
108
+ const [skillName, ...rest] = command.split(':');
109
+ const cmdName = rest.join(':');
110
+
111
+ if (!skillName || !cmdName) {
112
+ return null;
113
+ }
114
+
115
+ // 兼容旧的字符串模式(旧模式下 args 本身就是命令内容)
116
+ let toolArgs = args;
117
+ if (typeof args === 'string') {
118
+ toolArgs = { command: args };
119
+ } else if (args === undefined || args === null) {
120
+ toolArgs = {};
121
+ }
122
+
123
+ try {
124
+ const result = await this._framework.executeTool('ext_call', {
125
+ plugin: `skill:${skillName}`,
126
+ tool: cmdName,
127
+ args: toolArgs,
128
+ });
129
+
130
+ // ext_call 返回 {success:false, error} 时视为失败
131
+ return result && result.success !== false ? result : null;
132
+ } catch (err) {
133
+ // 异常吞掉以保持向后兼容。调用方基于返回 null 判断失败
134
+ try {
135
+ // 尝试用 framework logger 记录错误(失败也无所谓)
136
+ this._framework?.log?.error?.('SkillHelper.executeCommand failed:', err.message);
137
+ } catch { /* ignore */ }
138
+ return null;
139
+ }
140
+ }
141
+ }
142
+
143
+ module.exports = { SkillHelper };
@@ -234,11 +234,13 @@ class FeishuPlugin extends Plugin {
234
234
  if (text.startsWith('/')) {
235
235
  const cmdParts = command.split(':')
236
236
  if (cmdParts.length === 2) {
237
- const skillCmd = `${cmdParts[0]}:${cmdParts[1]}`
237
+ const skillName = cmdParts[0]
238
+ const cmdName = cmdParts[1]
238
239
  try {
240
+ // 新格式:plugin = `skill:<name>`, tool = command name
239
241
  const result = await this._framework.executeTool('ext_call', {
240
- plugin: 'skill',
241
- tool: skillCmd,
242
+ plugin: `skill:${skillName}`,
243
+ tool: cmdName,
242
244
  args: { command: args }
243
245
  })
244
246
  if (result.success !== false) {
@@ -46,7 +46,7 @@ class QQPlugin extends Plugin {
46
46
  this._initialized = false
47
47
  this._myUserId = null
48
48
  this._typingUsers = new Set() // 正在发送 typing 的用户(防抖)
49
- this.agent = null
49
+ // 注: 不能 `this.agent = ...`,Plugin 基类的 get agent() 是只读访问器
50
50
  this.sessionId = null
51
51
 
52
52
  this.downloader = null
@@ -716,11 +716,13 @@ class QQPlugin extends Plugin {
716
716
  if (text.startsWith('/')) {
717
717
  const cmdParts = command.split(':')
718
718
  if (cmdParts.length === 2) {
719
- const skillCmd = `${cmdParts[0]}:${cmdParts[1]}`
719
+ const skillName = cmdParts[0]
720
+ const cmdName = cmdParts[1]
720
721
  try {
722
+ // 新格式:plugin = `skill:<name>`, tool = command name
721
723
  const result = await this._framework.executeTool('ext_call', {
722
- plugin: 'skill',
723
- tool: skillCmd,
724
+ plugin: `skill:${skillName}`,
725
+ tool: cmdName,
724
726
  args: { command: args }
725
727
  })
726
728
  if (result.success !== false) {
@@ -352,11 +352,14 @@ class TelegramPlugin extends Plugin {
352
352
  if (text.startsWith('/')) {
353
353
  const cmdParts = command.split(':')
354
354
  if (cmdParts.length === 2) {
355
- const skillCmd = `${cmdParts[0]}:${cmdParts[1]}`
355
+ const skillName = cmdParts[0]
356
+ const cmdName = cmdParts[1]
356
357
  try {
358
+ // 新格式:plugin = `skill:<name>`, tool = command name
359
+ // 向后兼容:仍支持 { command: "<string>" } 旧风格(handler 内部识别)
357
360
  const result = await this._framework.executeTool('ext_call', {
358
- plugin: 'skill',
359
- tool: skillCmd,
361
+ plugin: `skill:${skillName}`,
362
+ tool: cmdName,
360
363
  args: { command: args }
361
364
  })
362
365
  if (result.success !== false) {
@@ -64,7 +64,9 @@ class WeixinPlugin extends Plugin {
64
64
  // 预创建的 sessionScope 监听器(避免每次消息都创建/销毁)
65
65
  this._sessionScopes = new Map()
66
66
  this.downloader = null
67
- this.agent=null
67
+ // 注: 不能 `this.agent = ...`,Plugin 基类的 get agent() 是只读访问器
68
+ // 当前活跃 agent 用 _currentAgent 跟踪
69
+ this._currentAgent = null
68
70
  this.sessionId=null
69
71
  }
70
72
 
@@ -382,8 +384,8 @@ class WeixinPlugin extends Plugin {
382
384
 
383
385
  _on_event_message(){
384
386
  const {sessionId}=this
385
- const sessionScope = this.agent.createSessionScope(sessionId);
386
- sessionScope.on('stream:chunk', ({ chunk }) => {})
387
+ const sessionScope = this._currentAgent?.createSessionScope(sessionId);
388
+ sessionScope?.on('stream:chunk', ({ chunk }) => {})
387
389
  }
388
390
 
389
391
  /**
@@ -0,0 +1,26 @@
1
+ ## 工具调用核心规则
2
+
3
+ 1. **子 Agent 优先**:任务匹配子 Agent 专业领域时,**必须优先使用** `subagent_call` 工具并指定 `agentName` 委托给对应子 Agent 处理,而不是直接调用工具。只有当没有匹配的子 Agent 时,才直接调用工具。
4
+ 2. **子 Agent 任务独立**:每个子 Agent 负责独立任务。**不要重复完成子 Agent 已经完成的任务**,但可以继续调用其他子 Agent 或工具完成其他任务。
5
+ 3. **必须先调用工具再回复**:当问题需要信息、操作或计算时,必须调用工具获取真实结果后才能回答。禁止在未调用工具的情况下直接给出答案。
6
+ 4. **禁止编造数据**:不许捏造用户、订单、任务、文件、内容等任何数据。所有数据必须通过工具获取。
7
+ 5. **工具优先**:可用工具列表会提供,格式为 `toolName(toolArgs)`。不确定用哪个工具时,优先调用可能相关的工具。
8
+ 6. **结果导向**:调用工具后,基于返回结果回答,不要重复工具的内部实现细节。
9
+ 7. **多步骤任务**:复杂任务拆解为多个工具调用,逐步完成,每步基于结果决定下一步。
10
+ 8. **步骤未完成必须提示继续**:如果任务需要多步骤操作,在返回前必须明确告知用户当前进度和下一步操作。如果回复内容为空或不完整,必须说明"步骤进行中,请继续"。
11
+
12
+ ## 响应规范
13
+
14
+ > 直接给出结论或结果,不说"我需要..."、"我建议..."等铺垫话术
15
+
16
+ - 如果工具返回错误,说明错误原因并给出解决建议
17
+ - 如果信息不足,先调用工具获取必要信息,不要假设或猜测
18
+ - **重要**:如果任务需要多步骤操作,在回复末尾必须提示用户"请继续"或"是否继续下一步"
19
+
20
+ ## 禁止事项
21
+
22
+ - 不调用工具就直接回答
23
+ - 编造不存在的数据、文件、订单、用户等信息
24
+ - 回复含糊不清,让用户无法确定答案是否正确
25
+ - **多步骤任务未完成就结束,不提示用户继续**
26
+ - **重复完成子 Agent 已经完成的任务**
@@ -3,10 +3,12 @@
3
3
  * 提供热重载、插件管理、工具列表等内置工具
4
4
  */
5
5
 
6
+ const path = require('path');
6
7
  const { Plugin } = require('../../src/plugin/base');
7
8
  const { logger } = require('../../src/common/logger');
8
9
  const { z } = require('zod');
9
10
  const log = logger.child('Tools');
11
+ const PROMPT_DIR = __dirname;
10
12
 
11
13
  class ToolsPlugin extends Plugin {
12
14
  constructor(config = {}) {
@@ -19,15 +21,14 @@ class ToolsPlugin extends Plugin {
19
21
  this._framework = null;
20
22
  }
21
23
 
22
- // 声明式 prompt parts(替代原 start() 中的 registerPromptPart 调用)
24
+ // 声明式 prompt parts
23
25
  prompts = [
24
26
  {
25
27
  name: 'tool-core-rules',
26
- slot: 'BEHAVIOR',
28
+ file: path.join(PROMPT_DIR, 'PROMPT.md'),
29
+ scope: 'global',
30
+ priority: 50,
27
31
  description: '工具调用核心规则 + 响应规范 + 禁止事项',
28
- provider: function () {
29
- return this._getToolCoreRules();
30
- },
31
32
  },
32
33
  ];
33
34