foliko 2.0.5 → 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 (65) hide show
  1. package/.claude/settings.local.json +4 -1
  2. package/.cli_default_systemPrompt.md +291 -0
  3. package/.editorconfig +56 -56
  4. package/.lintstagedrc +7 -7
  5. package/.prettierignore +29 -29
  6. package/.prettierrc +11 -11
  7. package/CLAUDE.md +3 -0
  8. package/Dockerfile +63 -63
  9. package/README.md +20 -3
  10. package/docs/architecture.md +34 -2
  11. package/docs/extensions.md +199 -0
  12. package/docs/migration.md +100 -0
  13. package/docs/public-api.md +280 -24
  14. package/docs/usage.md +122 -30
  15. package/install.ps1 +129 -129
  16. package/install.sh +121 -121
  17. package/package.json +1 -1
  18. package/plugins/core/audit/index.js +1 -1
  19. package/plugins/core/default/bootstrap.js +44 -19
  20. package/plugins/core/python-loader/index.js +43 -25
  21. package/plugins/core/skill-manager/PROMPT.md +6 -0
  22. package/plugins/core/skill-manager/index.js +402 -115
  23. package/plugins/core/sub-agent/PROMPT.md +10 -0
  24. package/plugins/core/sub-agent/index.js +36 -3
  25. package/plugins/core/think/index.js +1 -0
  26. package/plugins/core/workflow/index.js +82 -22
  27. package/plugins/executors/data-splitter/PROMPT.md +13 -0
  28. package/plugins/executors/data-splitter/index.js +5 -4
  29. package/plugins/executors/extension/extension-registry.js +145 -0
  30. package/plugins/executors/extension/index.js +405 -437
  31. package/plugins/executors/extension/prompt-builder.js +359 -0
  32. package/plugins/executors/extension/skill-helper.js +143 -0
  33. package/plugins/messaging/feishu/index.js +5 -3
  34. package/plugins/messaging/qq/index.js +6 -4
  35. package/plugins/messaging/telegram/index.js +6 -3
  36. package/plugins/messaging/weixin/index.js +5 -3
  37. package/plugins/tools/PROMPT.md +26 -0
  38. package/plugins/tools/index.js +6 -5
  39. package/skills/find-skills/SKILL.md +133 -133
  40. package/skills/foliko/AGENTS.md +196 -43
  41. package/skills/foliko/SKILL.md +157 -28
  42. package/skills/mcp/SKILL.md +77 -118
  43. package/skills/plugins/SKILL.md +89 -3
  44. package/skills/python/SKILL.md +57 -39
  45. package/skills/skill-guide/SKILL.md +42 -34
  46. package/skills/workflows/SKILL.md +224 -9
  47. package/skills/workflows/workflow-troubleshooting/SKILL.md +221 -281
  48. package/src/agent/chat.js +48 -27
  49. package/src/agent/main.js +34 -13
  50. package/src/agent/prompt-registry.js +56 -16
  51. package/src/agent/prompts/PROMPT.md +3 -0
  52. package/src/agent/sub.js +1 -1
  53. package/src/cli/ui/chat-ui-old.js +5 -2
  54. package/src/cli/ui/chat-ui.js +5 -2
  55. package/src/common/constants.js +12 -0
  56. package/src/common/error-capture.js +91 -0
  57. package/src/common/logger.js +2 -2
  58. package/src/context/compressor.js +6 -2
  59. package/src/executors/mcp-executor.js +105 -125
  60. package/src/framework/framework.js +23 -11
  61. package/src/index.js +4 -0
  62. package/src/plugin/base.js +908 -5
  63. package/src/plugin/manager.js +29 -8
  64. package/src/tool/schema.js +32 -9
  65. package/website/index.html +821 -0
@@ -21,6 +21,12 @@ class Plugin {
21
21
  _framework = null;
22
22
  _subAgents = [];
23
23
  tools = {};
24
+ _registeredTools = []; // 记录通过 tool.register 注册的工具 { name, def }
25
+ _registeredExtensions = []; // 记录通过 extension.register 注册的扩展工具 { extName, toolName, toolDef }
26
+ _registeredWorkflows = []; // 记录通过 workflow.register 注册的工作流 { name, def }
27
+ _registeredAgents = []; // 记录通过 agent.register 注册的子代理 { name, agent }
28
+ _registeredPrompts = []; // 记录通过 prompt.register 注册的 prompt parts { name, provider, options }
29
+ _registeredSkills = []; // 记录通过 skill.register 注册的技能 { name, content, metadata, options }
24
30
 
25
31
  /**
26
32
  * 声明式 prompt parts(推荐使用,替代 start() 中的 registerPromptPart 调用)
@@ -74,16 +80,730 @@ class Plugin {
74
80
  }
75
81
  }
76
82
 
83
+ /**
84
+ * 注册工具到 ToolRegistry(AI SDK 直接调用方式)
85
+ * 会在 uninstall/reload 时自动移除
86
+ *
87
+ * 用法:
88
+ * this.tool.register({
89
+ * name: 'my-tool',
90
+ * description: '描述',
91
+ * inputSchema: z.object({ ... }),
92
+ * execute: async (args) => { ... }
93
+ * });
94
+ */
95
+ get tool() {
96
+ if (!this._toolAccessor) {
97
+ this._toolAccessor = {
98
+ register: (toolDef) => {
99
+ if (!this._framework) {
100
+ log.warn(`[${this.name}] tool.register() called before plugin installed`);
101
+ return;
102
+ }
103
+ const registry = this._framework.toolRegistry;
104
+ if (!registry) {
105
+ log.warn(`[${this.name}] toolRegistry not found`);
106
+ return;
107
+ }
108
+ if (!toolDef || !toolDef.name) {
109
+ log.warn(`[${this.name}] tool.register() missing tool name`);
110
+ return;
111
+ }
112
+ // 记录,用于卸载时清理
113
+ this._registeredTools.push({ name: toolDef.name, def: toolDef });
114
+ // 阻止重复注册同名工具
115
+ if (registry.has(toolDef.name)) {
116
+ log.debug(`[${this.name}] Tool '${toolDef.name}' already registered, skipping`);
117
+ return;
118
+ }
119
+ registry.register(toolDef);
120
+ log.debug(`[${this.name}] Registered tool: ${toolDef.name}`);
121
+ },
122
+ remove: (toolName) => {
123
+ if (!this._framework) {
124
+ log.warn(`[${this.name}] tool.remove() called before plugin installed`);
125
+ return;
126
+ }
127
+ const registry = this._framework.toolRegistry;
128
+ if (!registry) return;
129
+ registry.unregister(toolName);
130
+ this._registeredTools = this._registeredTools.filter(t => t.name !== toolName);
131
+ log.debug(`[${this.name}] Removed tool: ${toolName}`);
132
+ },
133
+
134
+ /**
135
+ * 执行已注册的工具
136
+ * @param {string} toolName 工具名称
137
+ * @param {object} args 工具参数
138
+ * @param {object} context 执行上下文(可选)
139
+ */
140
+ execute: async (toolName, args, context = {}) => {
141
+ if (!this._framework) {
142
+ throw new Error(`[${this.name}] tool.execute() called before plugin installed`);
143
+ }
144
+ const registry = this._framework.toolRegistry;
145
+ if (!registry) {
146
+ throw new Error(`[${this.name}] toolRegistry not found`);
147
+ }
148
+ if (!registry.has(toolName)) {
149
+ throw new Error(`[${this.name}] Tool '${toolName}' not found`);
150
+ }
151
+ return await registry.execute(toolName, args, context);
152
+ },
153
+ };
154
+ }
155
+ return this._toolAccessor;
156
+ }
157
+
158
+ /**
159
+ * 注册扩展工具到 ExtensionExecutor(通过 ext_call 间接调用)
160
+ * 会在 uninstall/reload 时自动移除
161
+ * 扩展名自动使用 this.name
162
+ *
163
+ * 用法:
164
+ * this.extension.register({
165
+ * name: 'do-something',
166
+ * description: '描述',
167
+ * inputSchema: z.object({ ... }),
168
+ * execute: async (args) => { ... }
169
+ * });
170
+ */
171
+ get extension() {
172
+ if (!this._extensionAccessor) {
173
+ this._extensionAccessor = {
174
+ register: (toolDef) => {
175
+ if (!this._framework) {
176
+ log.warn(`[${this.name}] extension.register() called before plugin installed`);
177
+ return;
178
+ }
179
+ if (!toolDef || !toolDef.name) {
180
+ log.warn(`[${this.name}] extension.register() missing tool name`);
181
+ return;
182
+ }
183
+ // 记录,用于卸载时清理(扩展名自动用 this.name)
184
+ // 即使 extExec 还未加载,也先记录
185
+ this._registeredExtensions.push({ extName: this.name, toolName: toolDef.name, toolDef });
186
+
187
+ const extExec = this._framework.pluginManager.get('extension-executor');
188
+ if (!extExec) {
189
+ log.debug(`[${this.name}] extension-executor not found yet, deferring tool registration: ${toolDef.name}`);
190
+ return;
191
+ }
192
+ // extExec 可用,立即注册
193
+ extExec.registerTool(
194
+ this.name,
195
+ {
196
+ name: this.name,
197
+ description: this.description,
198
+ version: this.version,
199
+ },
200
+ toolDef
201
+ );
202
+ // 强制失效缓存,确保 prompt 能更新
203
+ extExec._invalidatePromptCaches(this._framework);
204
+ log.debug(`[${this.name}] Registered extension tool: ${this.name}/${toolDef.name}`);
205
+ },
206
+ remove: (toolName) => {
207
+ if (!this._framework) {
208
+ log.warn(`[${this.name}] extension.remove() called before plugin installed`);
209
+ return;
210
+ }
211
+ const extExec = this._framework.pluginManager.get('extension-executor');
212
+ if (extExec) {
213
+ extExec._unregisterTool(this.name, toolName);
214
+ }
215
+ this._registeredExtensions = this._registeredExtensions.filter(
216
+ e => !(e.extName === this.name && e.toolName === toolName)
217
+ );
218
+ log.debug(`[${this.name}] Removed extension tool: ${this.name}/${toolName}`);
219
+ },
220
+
221
+ /**
222
+ * 执行扩展工具(通过 ext_call 调用)
223
+ * 支持两种调用方式:
224
+ * this.extension.execute('tool-name', args) // 执行当前插件的
225
+ * this.extension.execute('plugin-name', 'tool-name', args) // 执行指定插件的
226
+ */
227
+ execute: async (pluginNameOrToolName, toolNameOrArgs, args = {}) => {
228
+ if (!this._framework) {
229
+ throw new Error(`[${this.name}] extension.execute() called before plugin installed`);
230
+ }
231
+ const extExec = this._framework.pluginManager.get('extension-executor');
232
+ if (!extExec) {
233
+ return { success: false, error: 'extension-executor not found' };
234
+ }
235
+
236
+ let pluginName, toolName;
237
+ if (typeof toolNameOrArgs === 'object') {
238
+ // 第一种:只传工具名和参数
239
+ pluginName = this.name;
240
+ toolName = pluginNameOrToolName;
241
+ args = toolNameOrArgs;
242
+ } else {
243
+ // 第二种:传插件名、工具名和参数
244
+ pluginName = pluginNameOrToolName;
245
+ toolName = toolNameOrArgs;
246
+ }
247
+
248
+ const result = await extExec._executeTool(pluginName, toolName, args, this._framework);
249
+ return result;
250
+ },
251
+ };
252
+ }
253
+ return this._extensionAccessor;
254
+ }
255
+
256
+ /**
257
+ * 注册工作流到 WorkflowEngine
258
+ * 支持 register/remove/execute 方法
259
+ *
260
+ * 用法:
261
+ * this.workflow.register('my-workflow', {
262
+ * name: 'my-workflow',
263
+ * steps: [...]
264
+ * });
265
+ */
266
+ get workflow() {
267
+ if (!this._workflowAccessor) {
268
+ this._workflowAccessor = {
269
+ register: (name, workflowDef) => {
270
+ if (!this._framework) {
271
+ log.warn(`[${this.name}] workflow.register() called before plugin installed`);
272
+ return;
273
+ }
274
+ const workflowPlugin = this._framework.pluginManager?.get('workflow');
275
+ if (!workflowPlugin) {
276
+ log.warn(`[${this.name}] workflow plugin not found`);
277
+ return;
278
+ }
279
+ if (!name || !workflowDef) {
280
+ log.warn(`[${this.name}] workflow.register() missing name or definition`);
281
+ return;
282
+ }
283
+ // 记录,用于卸载时清理
284
+ this._registeredWorkflows.push({ name, def: workflowDef });
285
+ // 注册到 workflow plugin
286
+ workflowPlugin._workflows.set(name, workflowDef);
287
+ // 注册为工具
288
+ workflowPlugin._registerWorkflowTools();
289
+ log.debug(`[${this.name}] Registered workflow: ${name}`);
290
+ },
291
+
292
+ remove: (name) => {
293
+ if (!this._framework) {
294
+ log.warn(`[${this.name}] workflow.remove() called before plugin installed`);
295
+ return;
296
+ }
297
+ const workflowPlugin = this._framework.pluginManager?.get('workflow');
298
+ if (!workflowPlugin) return;
299
+ workflowPlugin.remove(name);
300
+ this._registeredWorkflows = this._registeredWorkflows.filter(w => w.name !== name);
301
+ log.debug(`[${this.name}] Removed workflow: ${name}`);
302
+ },
303
+
304
+ execute: async (name, input = {}) => {
305
+ if (!this._framework) {
306
+ throw new Error(`[${this.name}] workflow.execute() called before plugin installed`);
307
+ }
308
+ const workflowPlugin = this._framework.pluginManager?.get('workflow');
309
+ if (!workflowPlugin) {
310
+ throw new Error(`[${this.name}] workflow plugin not found`);
311
+ }
312
+ return await workflowPlugin.executeWorkflow(name, input);
313
+ },
314
+ };
315
+ }
316
+ return this._workflowAccessor;
317
+ }
318
+
319
+ /**
320
+ * 注册子代理到框架
321
+ * 支持 register/remove/execute 方法
322
+ *
323
+ * 用法:
324
+ * this.agent.register({
325
+ * name: 'my-agent',
326
+ * role: '助手',
327
+ * goal: '描述',
328
+ * tools: {},
329
+ * parentTools: []
330
+ * });
331
+ */
332
+ get agent() {
333
+ if (!this._agentAccessor) {
334
+ this._agentAccessor = {
335
+ register: (config) => {
336
+ if (!this._framework) {
337
+ log.warn(`[${this.name}] agent.register() called before plugin installed`);
338
+ return;
339
+ }
340
+ if (!config || !config.name) {
341
+ log.warn(`[${this.name}] agent.register() missing config or name`);
342
+ return;
343
+ }
344
+ const subAgentManager = this._framework.pluginManager.get('subagent-manager');
345
+ if (!subAgentManager) {
346
+ log.warn(`[${this.name}] subagent-manager plugin not found`);
347
+ return;
348
+ }
349
+
350
+ const agent = this._framework.createSubAgent({
351
+ name: config.name,
352
+ role: config.role,
353
+ goal: config.description || config.role,
354
+ tools: config.tools || {},
355
+ parentTools: config.parentTools || [],
356
+ });
357
+
358
+ subAgentManager.registerPlugin({
359
+ name: config.name,
360
+ role: config.role,
361
+ goal: config.description || config.role,
362
+ agent,
363
+ tools: config.tools || {},
364
+ });
365
+
366
+ // 记录,用于卸载时清理
367
+ this._registeredAgents.push({ name: config.name, agent });
368
+
369
+ // 也添加到 _subAgents 供基类管理
370
+ this._subAgents.push({
371
+ name: config.name,
372
+ agent,
373
+ plugin: null,
374
+ });
375
+
376
+ log.debug(`[${this.name}] Registered agent: ${config.name}`);
377
+ },
378
+
379
+ remove: (name) => {
380
+ if (!this._framework) {
381
+ log.warn(`[${this.name}] agent.remove() called before plugin installed`);
382
+ return;
383
+ }
384
+ const subAgentManager = this._framework.pluginManager.get('subagent-manager');
385
+ if (!subAgentManager) return;
386
+
387
+ subAgentManager.unregisterPlugin(name);
388
+
389
+ // 从 _subAgents 移除
390
+ this._subAgents = this._subAgents.filter(s => s.name !== name);
391
+ // 从 _registeredAgents 移除
392
+ this._registeredAgents = this._registeredAgents.filter(a => a.name !== name);
393
+
394
+ log.debug(`[${this.name}] Removed agent: ${name}`);
395
+ },
396
+
397
+ get: (name) => {
398
+ return this._registeredAgents.find(a => a.name === name)?.agent;
399
+ },
400
+ };
401
+ }
402
+ return this._agentAccessor;
403
+ }
404
+
405
+ /**
406
+ * 注册 Prompt Part 到 framework.prompts
407
+ * 支持 register/remove 方法
408
+ *
409
+ * 用法:
410
+ * this.prompt.register('my-prompt', () => 'prompt content', { scope: 'global', priority: 50 });
411
+ */
412
+ get prompt() {
413
+ if (!this._promptAccessor) {
414
+ this._promptAccessor = {
415
+ register: (name, provider, options = {}) => {
416
+ if (!this._framework) {
417
+ log.warn(`[${this.name}] prompt.register() called before plugin installed`);
418
+ return;
419
+ }
420
+ const reg = this._framework.prompts;
421
+ if (!reg) {
422
+ log.warn(`[${this.name}] prompts registry not found`);
423
+ return;
424
+ }
425
+ if (!name || !provider) {
426
+ log.warn(`[${this.name}] prompt.register() missing name or provider`);
427
+ return;
428
+ }
429
+ // 记录,用于卸载时清理
430
+ this._registeredPrompts.push({ name, provider, options });
431
+ // 注册到 framework.prompts
432
+ reg.register(this.name, name, provider, options);
433
+ log.debug(`[${this.name}] Registered prompt: ${name}`);
434
+ },
435
+
436
+ remove: (name) => {
437
+ if (!this._framework) {
438
+ log.warn(`[${this.name}] prompt.remove() called before plugin installed`);
439
+ return;
440
+ }
441
+ const reg = this._framework.prompts;
442
+ if (!reg) return;
443
+ reg.clearOwner(this.name, name);
444
+ this._registeredPrompts = this._registeredPrompts.filter(p => p.name !== name);
445
+ log.debug(`[${this.name}] Removed prompt: ${name}`);
446
+ },
447
+ };
448
+ }
449
+ return this._promptAccessor;
450
+ }
451
+
452
+ /**
453
+ * 注册/查询/删除 Skill(程序化管理技能)
454
+ * 会在 uninstall/reload 时自动清理
455
+ *
456
+ * 用法:
457
+ * // 注册
458
+ * this.skill.register('my-skill', '技能内容(markdown)', {
459
+ * description: '技能描述',
460
+ * 'allowed-tools': 'tool1,tool2'
461
+ * }, {
462
+ * commands: [{ name: 'cmd1', description: '...', execute: async (args) => {...} }],
463
+ * references: { 'extra': 'extra.md' },
464
+ * path: '/path/to/skill' // 可选
465
+ * });
466
+ *
467
+ * // 查询
468
+ * const skill = this.skill.get('my-skill');
469
+ * const details = this.skill.details('my-skill');
470
+ *
471
+ * // 删除
472
+ * this.skill.remove('my-skill');
473
+ */
474
+ get skill() {
475
+ if (!this._skillAccessor) {
476
+ this._skillAccessor = {
477
+ /**
478
+ * 注册一个技能
479
+ * @param {string} name 技能名称
480
+ * @param {string} content 技能内容(markdown)
481
+ * @param {Object} [metadata] 元数据 { description, license, compatibility, allowedTools, 'allowed-tools' }
482
+ * @param {Object} [options] 选项 { commands, references, scripts, path }
483
+ * @returns {Object|null} 注册结果
484
+ */
485
+ register: (name, content, metadata = {}, options = {}) => {
486
+ if (!this._framework) {
487
+ log.warn(`[${this.name}] skill.register() called before plugin installed`);
488
+ return null;
489
+ }
490
+ if (!name) {
491
+ log.warn(`[${this.name}] skill.register() missing name`);
492
+ return null;
493
+ }
494
+ const sm = this._framework.pluginManager?.get('skill-manager');
495
+ if (!sm) {
496
+ log.warn(`[${this.name}] skill-manager plugin not found`);
497
+ return null;
498
+ }
499
+ // 记录,用于卸载时清理
500
+ this._registeredSkills.push({ name, content, metadata, options });
501
+ // 调用 skill-manager 的 register
502
+ const result = sm.register(name, content || '', metadata, options);
503
+ log.debug(`[${this.name}] Registered skill: ${name}`);
504
+ return result;
505
+ },
506
+
507
+ /**
508
+ * 获取技能信息
509
+ * @param {string} name 技能名
510
+ * @returns {Object|null}
511
+ */
512
+ get: (name) => {
513
+ const sm = this._framework?.pluginManager?.get('skill-manager');
514
+ return sm?.getSkill?.(name) || null;
515
+ },
516
+
517
+ /**
518
+ * 检查技能是否存在
519
+ */
520
+ has: (name) => {
521
+ const sm = this._framework?.pluginManager?.get('skill-manager');
522
+ return sm?.hasSkill?.(name) || false;
523
+ },
524
+
525
+ /**
526
+ * 获取技能详情(description、metadata、content、path、commands、references、scripts)
527
+ */
528
+ details: (name) => {
529
+ const sm = this._framework?.pluginManager?.get('skill-manager');
530
+ return sm?.getSkillDetails?.(name) || null;
531
+ },
532
+
533
+ /**
534
+ * 列出所有已注册的技能名
535
+ */
536
+ list: () => {
537
+ const sm = this._framework?.pluginManager?.get('skill-manager');
538
+ return sm?.getAllSkills?.() || [];
539
+ },
540
+
541
+ /**
542
+ * 列出本插件注册的技能
543
+ */
544
+ listOwned: () => {
545
+ return this._registeredSkills.map((s) => s.name);
546
+ },
547
+
548
+ /**
549
+ * 删除技能
550
+ * @param {string} name 技能名
551
+ * @returns {boolean} 是否成功删除
552
+ */
553
+ remove: (name) => {
554
+ if (!this._framework) {
555
+ log.warn(`[${this.name}] skill.remove() called before plugin installed`);
556
+ return false;
557
+ }
558
+ const sm = this._framework.pluginManager?.get('skill-manager');
559
+ if (!sm) return false;
560
+ const removed = sm.remove(name);
561
+ if (removed) {
562
+ this._registeredSkills = this._registeredSkills.filter((s) => s.name !== name);
563
+ log.debug(`[${this.name}] Removed skill: ${name}`);
564
+ }
565
+ return removed;
566
+ },
567
+
568
+ /**
569
+ * 直接执行技能命令(通过 ext_call)
570
+ * @param {string} command 格式 `<skill>:<command>`,如 'my-skill:cmd1'
571
+ * @param {Object} [args] 参数对象
572
+ */
573
+ execute: async (command, args = {}) => {
574
+ if (!this._framework) {
575
+ throw new Error(`[${this.name}] skill.execute() called before plugin installed`);
576
+ }
577
+ if (!command || !command.includes(':')) {
578
+ throw new Error(`[${this.name}] skill.execute() requires '<skill>:<command>' format`);
579
+ }
580
+ const [skillName, ...rest] = command.split(':');
581
+ const cmdName = rest.join(':');
582
+ return await this._framework.executeTool('ext_call', {
583
+ plugin: `skill:${skillName}`,
584
+ tool: cmdName,
585
+ args: args || {},
586
+ });
587
+ },
588
+ };
589
+ }
590
+ return this._skillAccessor;
591
+ }
592
+
593
+ /**
594
+ * 插件启动钩子 - 子类可重写
595
+ * 注意:不需要调用 super.start(framework),基类自动处理
596
+ */
597
+ onStart(framework) {
598
+ // 子类可重写此方法注册工具/扩展
599
+ }
600
+
77
601
  start(framework) {
602
+ this._framework = framework;
78
603
  this._autoRegisterSubAgents();
79
604
  this._autoRegisterPrompts();
605
+ this._reRegisterTools();
606
+ this._reRegisterExtensions();
607
+ this._reRegisterWorkflows();
608
+ this._reRegisterAgents();
609
+ this._reRegisterPrompts();
610
+ this._reRegisterSkills();
611
+ this.onStart(framework);
80
612
  return this;
81
613
  }
82
614
 
615
+ /**
616
+ * 重新注册所有通过 tool.register 注册的工具
617
+ */
618
+ _reRegisterTools() {
619
+ const registry = this._framework?.toolRegistry;
620
+ if (!registry) return;
621
+ for (const { name, def } of this._registeredTools) {
622
+ if (!registry.has(name)) {
623
+ registry.register(def);
624
+ log.debug(`[${this.name}] Re-registered tool: ${name}`);
625
+ }
626
+ }
627
+ }
628
+
629
+ /**
630
+ * 重新注册所有通过 extension.register 注册的扩展工具
631
+ */
632
+ _reRegisterExtensions() {
633
+ const extExec = this._framework?.pluginManager?.get('extension-executor');
634
+ if (!extExec) return;
635
+ for (const { extName, toolDef } of this._registeredExtensions) {
636
+ extExec.registerTool(
637
+ extName,
638
+ {
639
+ name: extName,
640
+ description: this.description,
641
+ version: this.version,
642
+ },
643
+ toolDef
644
+ );
645
+ log.debug(`[${this.name}] Re-registered extension tool: ${extName}/${toolDef.name}`);
646
+ }
647
+ }
648
+
649
+ /**
650
+ * 重新注册所有通过 workflow.register 注册的工作流
651
+ */
652
+ _reRegisterWorkflows() {
653
+ const workflowPlugin = this._framework?.pluginManager?.get('workflow');
654
+ if (!workflowPlugin) return;
655
+ for (const { name, def } of this._registeredWorkflows) {
656
+ workflowPlugin._workflows.set(name, def);
657
+ workflowPlugin._registerWorkflowTools();
658
+ log.debug(`[${this.name}] Re-registered workflow: ${name}`);
659
+ }
660
+ }
661
+
662
+ /**
663
+ * 重新注册所有通过 agent.register 注册的子代理
664
+ */
665
+ _reRegisterAgents() {
666
+ const subAgentManager = this._framework?.pluginManager?.get('subagent-manager');
667
+ if (!subAgentManager) return;
668
+ for (const { name, agent } of this._registeredAgents) {
669
+ subAgentManager.registerPlugin({
670
+ name,
671
+ agent,
672
+ role: agent.role || '',
673
+ goal: agent.goal || '',
674
+ tools: {},
675
+ });
676
+ log.debug(`[${this.name}] Re-registered agent: ${name}`);
677
+ }
678
+ }
679
+
680
+ /**
681
+ * 重新注册所有通过 prompt.register 注册的 prompt parts
682
+ */
683
+ _reRegisterPrompts() {
684
+ const reg = this._framework?.prompts;
685
+ if (!reg) return;
686
+ for (const { name, provider, options } of this._registeredPrompts) {
687
+ reg.register(this.name, name, provider, options);
688
+ log.debug(`[${this.name}] Re-registered prompt: ${name}`);
689
+ }
690
+ }
691
+
692
+ /**
693
+ * 重新注册所有通过 skill.register 注册的技能
694
+ */
695
+ _reRegisterSkills() {
696
+ const sm = this._framework?.pluginManager?.get('skill-manager');
697
+ if (!sm) return;
698
+ for (const { name, content, metadata, options } of this._registeredSkills) {
699
+ // 如果技能已经存在(可能未在 reload 中清理),先移除
700
+ if (sm.hasSkill(name)) {
701
+ sm.remove(name);
702
+ }
703
+ sm.register(name, content || '', metadata, options);
704
+ log.debug(`[${this.name}] Re-registered skill: ${name}`);
705
+ }
706
+ }
707
+
708
+ /**
709
+ * 插件停止钩子 - 子类可重写
710
+ * 注意:不需要调用 super.stop(),基类自动处理
711
+ */
712
+ onStop() {
713
+ // 子类可重写此方法进行清理
714
+ }
715
+
716
+ /**
717
+ * 停止插件(disable 时调用)
718
+ * 移除工具和扩展,但保留注册记录以便 re-enable 时重新注册
719
+ */
720
+ stop() {
721
+ // 移除 tool.register 注册的工具
722
+ for (const { name } of this._registeredTools) {
723
+ try {
724
+ this._framework?.toolRegistry?.unregister(name);
725
+ log.debug(`[${this.name}] Stopped tool: ${name}`);
726
+ } catch (err) {
727
+ // ignore
728
+ }
729
+ }
730
+ // 移除 extension.register 注册的扩展工具
731
+ for (const { extName, toolName } of this._registeredExtensions) {
732
+ try {
733
+ const extExec = this._framework?.pluginManager?.get('extension-executor');
734
+ if (extExec) {
735
+ extExec._unregisterTool(extName, toolName);
736
+ log.debug(`[${this.name}] Stopped extension tool: ${extName}/${toolName}`);
737
+ }
738
+ } catch (err) {
739
+ // ignore
740
+ }
741
+ }
742
+ // 移除 workflow.register 注册的工作流
743
+ for (const { name } of this._registeredWorkflows) {
744
+ try {
745
+ const workflowPlugin = this._framework?.pluginManager?.get('workflow');
746
+ if (workflowPlugin) {
747
+ workflowPlugin.remove(name);
748
+ log.debug(`[${this.name}] Stopped workflow: ${name}`);
749
+ }
750
+ } catch (err) {
751
+ // ignore
752
+ }
753
+ }
754
+ // 移除 agent.register 注册的子代理
755
+ for (const { name } of this._registeredAgents) {
756
+ try {
757
+ const subAgentManager = this._framework?.pluginManager?.get('subagent-manager');
758
+ if (subAgentManager) {
759
+ subAgentManager.unregisterPlugin(name);
760
+ log.debug(`[${this.name}] Stopped agent: ${name}`);
761
+ }
762
+ } catch (err) {
763
+ // ignore
764
+ }
765
+ }
766
+ // 移除 prompt.register 注册的 prompt parts
767
+ for (const { name } of this._registeredPrompts) {
768
+ try {
769
+ const reg = this._framework?.prompts;
770
+ if (reg) {
771
+ reg.clearOwner(this.name, name);
772
+ log.debug(`[${this.name}] Stopped prompt: ${name}`);
773
+ }
774
+ } catch (err) {
775
+ // ignore
776
+ }
777
+ }
778
+ // 移除 skill.register 注册的技能
779
+ for (const { name } of this._registeredSkills) {
780
+ try {
781
+ const sm = this._framework?.pluginManager?.get('skill-manager');
782
+ if (sm) {
783
+ sm.remove(name);
784
+ log.debug(`[${this.name}] Stopped skill: ${name}`);
785
+ }
786
+ } catch (err) {
787
+ // ignore
788
+ }
789
+ }
790
+ this.onStop();
791
+ }
792
+
83
793
  /**
84
794
  * 自动注册 this.prompts 声明的所有 part 到 framework.prompts
85
795
  * - 失败一个不影响其他
86
796
  * - 'this' 在 provider 中绑定到 plugin 实例(必须用普通 function,箭头函数的 this 是词法的)
797
+ *
798
+ * 支持两种声明方式:
799
+ * 1. provider 函数(原有方式):
800
+ * { name: 'xxx', provider() { return this._buildSomething(); } }
801
+ *
802
+ * 2. 文件方式(新增):
803
+ * { name: 'xxx', file: './PROMPT.md', vars: { key: 'value' } }
804
+ * - file 相对于插件文件路径解析
805
+ * - vars 支持静态对象或 () => object 动态函数
806
+ * - {{varName}} 在渲染时被替换
87
807
  */
88
808
  _autoRegisterPrompts() {
89
809
  const reg = this._framework?.prompts;
@@ -91,14 +811,23 @@ class Plugin {
91
811
  if (!Array.isArray(this.prompts) || this.prompts.length === 0) return;
92
812
 
93
813
  for (const entry of this.prompts) {
94
- if (!entry || !entry.name || typeof entry.provider !== 'function') {
95
- log.warn(`[${this.name}] Invalid prompt entry: missing name or provider`);
814
+ if (!entry || !entry.name) {
815
+ log.warn(`[${this.name}] Invalid prompt entry: missing name`);
96
816
  continue;
97
817
  }
98
818
  try {
99
- // bind 让 provider 中的 this 指向 plugin 实例
100
- const boundProvider = entry.provider.bind(this);
101
- reg.register(this.name, entry.name, boundProvider, {
819
+ let provider;
820
+ if (entry.file) {
821
+ // 文件方式:读取 PROMPT.md 并支持变量插值
822
+ provider = this._createFilePromptProvider(entry.file, entry.vars, entry.interpolate);
823
+ } else if (typeof entry.provider === 'function') {
824
+ // 函数方式
825
+ provider = entry.provider.bind(this);
826
+ } else {
827
+ log.warn(`[${this.name}] Prompt entry "${entry.name}" missing provider or file`);
828
+ continue;
829
+ }
830
+ reg.register(this.name, entry.name, provider, {
102
831
  scope: entry.scope,
103
832
  priority: entry.priority,
104
833
  description: entry.description,
@@ -109,6 +838,70 @@ class Plugin {
109
838
  }
110
839
  }
111
840
 
841
+ /**
842
+ * 创建文件 prompt provider
843
+ * @param {string} filePath 文件路径(绝对或相对)
844
+ * @param {Object|Function} vars 变量(静态或动态函数)
845
+ * @param {Function} interpolate 后处理函数,用于注入动态内容
846
+ */
847
+ _createFilePromptProvider(filePath, vars, interpolate) {
848
+ const fs = require('fs');
849
+ const path = require('path');
850
+
851
+ // 解析文件路径:相对路径基于插件文件目录,绝对路径直接用
852
+ let fullPath = filePath;
853
+ if (!path.isAbsolute(filePath)) {
854
+ // 使用栈追踪获取插件文件路径
855
+ // 栈结构: Error, _createFilePromptProvider, _autoRegisterPrompts, 子类start, plugin-manager调用start, ...
856
+ const stack = new Error().stack.split('\n');
857
+ let pluginFile;
858
+ for (let i = 3; i < stack.length; i++) { // 从索引3开始跳过 base.js 的方法
859
+ const line = stack[i];
860
+ // 排除 base.js 自身和 node: 内置模块
861
+ if (line.includes('plugin/base.js') || line.includes('node:')) continue;
862
+ // 提取文件路径 (filename:line:col) 或 (filename)
863
+ const match = line.match(/\((.*?):\d+:\d+\)/) || line.match(/at\s+(.*?):\d+:\d+/);
864
+ if (match && match[1] !== '[eval]') {
865
+ pluginFile = match[1];
866
+ break;
867
+ }
868
+ }
869
+ const pluginDir = pluginFile ? path.dirname(path.resolve(pluginFile)) : process.cwd();
870
+ fullPath = path.resolve(pluginDir, filePath);
871
+ }
872
+
873
+ // 缓存原始文件内容(只读一次)
874
+ if (!fs.existsSync(fullPath)) {
875
+ return () => { throw new Error(`Prompt file not found: ${fullPath}`); };
876
+ }
877
+ const rawContent = fs.readFileSync(fullPath, 'utf-8');
878
+
879
+ return () => {
880
+ let content = rawContent;
881
+
882
+ // 变量插值
883
+ if (vars) {
884
+ const varsObj = typeof vars === 'function' ? vars() : vars;
885
+ if (varsObj && typeof varsObj === 'object') {
886
+ content = content.replace(/\{\{(\w+)\}\}/g, (match, varName) => {
887
+ if (Object.prototype.hasOwnProperty.call(varsObj, varName)) {
888
+ const val = varsObj[varName];
889
+ return val == null ? match : String(val);
890
+ }
891
+ return match;
892
+ });
893
+ }
894
+ }
895
+
896
+ // 后处理:用于注入动态生成的列表(如 {{subagents}}, {{skills}} 等)
897
+ if (typeof interpolate === 'function') {
898
+ content = interpolate(content, this);
899
+ }
900
+
901
+ return content;
902
+ };
903
+ }
904
+
112
905
  /**
113
906
  * 清理当前 plugin 在 framework.prompts 中注册的所有 part
114
907
  */
@@ -206,7 +999,36 @@ class Plugin {
206
999
  this._cleanupPrompts();
207
1000
  this._autoRegisterPrompts();
208
1001
 
1002
+ // 清理 tool.register 注册的工具(reload 后需重新注册)
1003
+ for (const { name: toolName } of this._registeredTools) {
1004
+ try {
1005
+ framework.toolRegistry?.unregister(toolName);
1006
+ } catch (err) {
1007
+ // ignore
1008
+ }
1009
+ }
1010
+ // 注:_registeredTools 保留,reload 后 framework 重新设置后仍可重新注册
1011
+
1012
+ // 清理 extension.register 注册的扩展工具
1013
+ for (const { extName, toolName } of this._registeredExtensions) {
1014
+ try {
1015
+ const extExec = framework.pluginManager?.get('extension-executor');
1016
+ if (extExec) {
1017
+ extExec._unregisterTool(extName, toolName);
1018
+ }
1019
+ } catch (err) {
1020
+ // ignore
1021
+ }
1022
+ }
1023
+
209
1024
  this._autoRegisterSubAgents();
1025
+ this._reRegisterTools();
1026
+ this._reRegisterExtensions();
1027
+ this._reRegisterWorkflows();
1028
+ this._reRegisterAgents();
1029
+ this._reRegisterPrompts();
1030
+ this._reRegisterSkills();
1031
+ this.onStart(framework);
210
1032
  }
211
1033
 
212
1034
  onCwdChanged(oldCwd, newCwd, framework) {
@@ -217,6 +1039,87 @@ class Plugin {
217
1039
  // 清理 framework.prompts 中该插件注册的所有 part
218
1040
  this._cleanupPrompts();
219
1041
 
1042
+ // 清理通过 tool.register 注册的工具
1043
+ for (const { name: toolName } of this._registeredTools) {
1044
+ try {
1045
+ framework.toolRegistry?.unregister(toolName);
1046
+ log.debug(`[${this.name}] Unregistered tool: ${toolName}`);
1047
+ } catch (err) {
1048
+ log.warn(`[${this.name}] Failed to unregister tool '${toolName}':`, err.message);
1049
+ }
1050
+ }
1051
+ this._registeredTools = [];
1052
+
1053
+ // 清理通过 extension.register 注册的扩展工具
1054
+ for (const { extName, toolName } of this._registeredExtensions) {
1055
+ try {
1056
+ const extExec = framework.pluginManager?.get('extension-executor');
1057
+ if (extExec) {
1058
+ extExec._unregisterTool(extName, toolName);
1059
+ log.debug(`[${this.name}] Unregistered extension tool: ${extName}/${toolName}`);
1060
+ }
1061
+ } catch (err) {
1062
+ log.warn(`[${this.name}] Failed to unregister extension tool '${extName}/${toolName}':`, err.message);
1063
+ }
1064
+ }
1065
+ this._registeredExtensions = [];
1066
+
1067
+ // 清理通过 workflow.register 注册的工作流
1068
+ for (const { name } of this._registeredWorkflows) {
1069
+ try {
1070
+ const workflowPlugin = framework.pluginManager?.get('workflow');
1071
+ if (workflowPlugin) {
1072
+ workflowPlugin.remove(name);
1073
+ log.debug(`[${this.name}] Unregistered workflow: ${name}`);
1074
+ }
1075
+ } catch (err) {
1076
+ log.warn(`[${this.name}] Failed to unregister workflow '${name}':`, err.message);
1077
+ }
1078
+ }
1079
+ this._registeredWorkflows = [];
1080
+
1081
+ // 清理通过 agent.register 注册的子代理
1082
+ for (const { name } of this._registeredAgents) {
1083
+ try {
1084
+ const subAgentManager = framework.pluginManager?.get('subagent-manager');
1085
+ if (subAgentManager) {
1086
+ subAgentManager.unregisterPlugin(name);
1087
+ log.debug(`[${this.name}] Unregistered agent: ${name}`);
1088
+ }
1089
+ } catch (err) {
1090
+ log.warn(`[${this.name}] Failed to unregister agent '${name}':`, err.message);
1091
+ }
1092
+ }
1093
+ this._registeredAgents = [];
1094
+
1095
+ // 清理通过 prompt.register 注册的 prompt parts
1096
+ for (const { name } of this._registeredPrompts) {
1097
+ try {
1098
+ const reg = framework.prompts;
1099
+ if (reg) {
1100
+ reg.clearOwner(this.name, name);
1101
+ log.debug(`[${this.name}] Unregistered prompt: ${name}`);
1102
+ }
1103
+ } catch (err) {
1104
+ log.warn(`[${this.name}] Failed to unregister prompt '${name}':`, err.message);
1105
+ }
1106
+ }
1107
+ this._registeredPrompts = [];
1108
+
1109
+ // 清理通过 skill.register 注册的技能
1110
+ for (const { name } of this._registeredSkills) {
1111
+ try {
1112
+ const sm = framework.pluginManager?.get('skill-manager');
1113
+ if (sm) {
1114
+ sm.remove(name);
1115
+ log.debug(`[${this.name}] Unregistered skill: ${name}`);
1116
+ }
1117
+ } catch (err) {
1118
+ log.warn(`[${this.name}] Failed to unregister skill '${name}':`, err.message);
1119
+ }
1120
+ }
1121
+ this._registeredSkills = [];
1122
+
220
1123
  for (const plugin of this._subAgents) {
221
1124
  try {
222
1125
  plugin.uninstall(framework);