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
@@ -113,12 +113,12 @@ framework.tools.execute(name, args);
113
113
 
114
114
  // 扩展工具
115
115
  framework.registerExtensionTools(pluginName, tools);
116
- framework.extension.tool(pluginName, tools);
117
- framework.extension.execute(plugin, tool, args);
118
- framework.extension.list();
119
- framework.extension.listPlugins();
120
- framework.extension.listTools(pluginName);
121
- framework.extension.getTool(pluginName, toolName);
116
+ framework.extensions.tool(pluginName, tools);
117
+ framework.extensions.execute(plugin, tool, args);
118
+ framework.extensions.list();
119
+ framework.extensions.listPlugins();
120
+ framework.extensions.listTools(pluginName);
121
+ framework.extensions.getTool(pluginName, toolName);
122
122
 
123
123
  // 事件
124
124
  framework.on('email:received', handler);
@@ -195,6 +195,1180 @@ module.exports = function (foliko) {
195
195
  | `foliko.z` | Zod schema 创建 |
196
196
  | `foliko.Plugin` | Plugin 基类 |
197
197
 
198
+ ## 插件开发 API
199
+
200
+ 在 `install(framework)` / `start(framework)` / `reload(framework)` 生命周期方法中,可通过 `framework` 参数使用以下 API:
201
+
202
+ ### framework.plugins
203
+
204
+ ```js
205
+ // 检查插件是否存在
206
+ framework.plugins.has('my-plugin'); // true/false
207
+
208
+ // 获取插件实例
209
+ framework.plugins.get('my-plugin'); // Plugin 实例
210
+
211
+ // 列出所有已加载的插件名
212
+ framework.plugins.list(); // ['ai', 'shell', 'telegram', ...]
213
+
214
+ // 创建 Plugin 实例(不注册/加载)
215
+ // 支持四种插件定义形式
216
+ const plugin = framework.plugins.create(pluginDef);
217
+ ```
218
+
219
+ #### createPluginInstance 支持的形式
220
+
221
+ ```js
222
+ // 1. Plugin 实例(直接返回)
223
+ const plugin = framework.plugins.create(existingPluginInstance);
224
+
225
+ // 2. Plugin 子类(自动实例化)
226
+ const plugin = framework.plugins.create(class MyPlugin extends Plugin {
227
+ name = 'my-plugin';
228
+ // ...
229
+ });
230
+
231
+ // 3. function(foliko) 函数(包装为 Plugin)
232
+ const plugin = framework.plugins.create(function(foliko) {
233
+ foliko.tools.tool({ name: 'hello', execute: () => 'hi' });
234
+ });
235
+
236
+ // 4. 普通对象(拷贝字段)
237
+ const plugin = framework.plugins.create({
238
+ name: 'my-plugin',
239
+ version: '1.0.0',
240
+ install(framework) { /* ... */ },
241
+ start(framework) { /* ... */ },
242
+ });
243
+ ```
244
+
245
+ #### plugins.register — 注册并加载插件
246
+
247
+ ```js
248
+ // 注册并自动加载(install + start)
249
+ await framework.plugins.register({
250
+ name: 'my-plugin',
251
+ version: '1.0.0',
252
+ install(fw) { /* 初始化 */ },
253
+ start(fw) { /* 启动 */ },
254
+ });
255
+
256
+ // 仅创建实例,不自动加载
257
+ await framework.plugins.register(myPluginDef, { autoLoad: false });
258
+
259
+ // 注册后手动加载
260
+ await framework.plugins.register(myPluginDef, { autoLoad: false });
261
+ await framework.loadPlugin(myPluginDef);
262
+ ```
263
+
264
+ | 选项 | 类型 | 默认值 | 说明 |
265
+ |------|------|--------|------|
266
+ | `autoLoad` | boolean | `true` | 是否自动调用 `install` + `start` |
267
+
268
+ ---
269
+
270
+ ### framework.prompts
271
+
272
+ `framework.prompts` 是全局系统提示词注册表,简化设计:只用 **priority 优先级** + **scope 作用域**。
273
+
274
+ ```js
275
+ // 注册 prompt
276
+ framework.prompts.register(owner, name, provider, options);
277
+
278
+ // 从文件注册(支持变量插值)
279
+ framework.prompts.registerFile('my-plugin', './system.md', { name: 'Alice', version: '1.0' });
280
+
281
+ // 注销
282
+ framework.prompts.unregister('my-plugin', 'my-rules');
283
+
284
+ // 清空 owner 所有 parts
285
+ framework.prompts.clearOwner('my-plugin');
286
+
287
+ // 检查存在
288
+ framework.prompts.has('my-plugin', 'my-rules'); // true/false
289
+
290
+ // 获取 part
291
+ framework.prompts.getPart('my-plugin', 'my-rules');
292
+
293
+ // 列出所有(按 priority 排序)
294
+ framework.prompts.list();
295
+
296
+ // 按 scope 过滤
297
+ framework.prompts.listByScope('global');
298
+
299
+ // 获取适用于指定 agent 的 parts
300
+ framework.prompts.listForAgent('sub-agent-1');
301
+
302
+ // 渲染(适用于当前调用者)
303
+ framework.prompts.build();
304
+ framework.prompts.preview(); // build() 别名
305
+
306
+ // 渲染适用于特定 agent
307
+ framework.prompts.buildForAgent('sub-agent-1');
308
+
309
+ // 结构化检查(调试用)
310
+ framework.prompts.inspect();
311
+ // [{ owner, name, scope, priority, hasContent, length, contentPreview, ... }]
312
+
313
+ // 失效(下次 build 重新计算)
314
+ framework.prompts.invalidate('my-plugin', 'my-rules');
315
+ framework.prompts.invalidateAll();
316
+
317
+ // 数量
318
+ framework.prompts.count();
319
+ ```
320
+
321
+ #### 选项
322
+
323
+ | 选项 | 默认值 | 说明 |
324
+ |------|--------|------|
325
+ | `scope` | `'global'` | 作用域:`'global'`(公用)\| `'main'`(主 agent)\| agentId |
326
+ | `priority` | `100` | 优先级,数字越小越靠前 |
327
+ | `description` | `''` | 描述(用于调试)|
328
+
329
+ #### 注册示例
330
+
331
+ ```js
332
+ // 在插件 install() 中注册
333
+ install(framework) {
334
+ // 公用 prompt(所有 agent 可用)
335
+ framework.prompts.register('my-plugin', 'rules', () => {
336
+ return '## 我的规则\n1. 规则一\n2. 规则二';
337
+ }, {
338
+ scope: 'global',
339
+ priority: 50,
340
+ description: '我的规则',
341
+ });
342
+
343
+ // 主 agent 专用
344
+ framework.prompts.register('my-plugin', 'main-only', () => {
345
+ return '仅主 agent 可见的内容';
346
+ }, {
347
+ scope: 'main',
348
+ priority: 30,
349
+ });
350
+
351
+ // 特定子 agent 专用
352
+ framework.prompts.register('my-plugin', 'sub-agent-rules', () => {
353
+ return '子 agent 专用规则';
354
+ }, {
355
+ scope: 'sub-agent-1',
356
+ priority: 40,
357
+ });
358
+
359
+ // 动态内容(带缓存)
360
+ framework.prompts.register('my-plugin', 'dynamic', () => {
361
+ return `当前连接数: ${this._getConnectionCount()}`;
362
+ }, {
363
+ priority: 60,
364
+ });
365
+
366
+ // 从文件注册(支持变量插值)
367
+ // 文件内容如:# System {{name}} v{{version}}
368
+ framework.prompts.registerFile('my-plugin', './system.md', {
369
+ name: 'MyApp',
370
+ version: '2.0',
371
+ }, {
372
+ scope: 'global',
373
+ priority: 10,
374
+ });
375
+ }
376
+ ```
377
+
378
+ #### provider 规范
379
+
380
+ - 返回 `string` — 正常内容
381
+ - 返回 `null` / 空字符串 — build 时被跳过
382
+ - 抛出异常 — 警告但不中断渲染
383
+
384
+ ---
385
+
386
+ ### framework.skills
387
+
388
+ `framework.skills` 是技能管理器接口(需要加载 `skill-manager` 插件)。提供技能的查询、命令注册、reference 和 scripts 管理。
389
+
390
+ **注意**:需要先加载 `skill-manager` 插件才能使用:
391
+
392
+ ```js
393
+ const { SkillManagerPlugin } = require('foliko');
394
+ await framework.loadPlugin(new SkillManagerPlugin({ skillsDir: './skills' }));
395
+ ```
396
+
397
+ #### 基本查询
398
+
399
+ ```js
400
+ // 检查 skills 模块是否可用
401
+ framework.skills.available; // true/false
402
+
403
+ // 列出所有已加载的技能
404
+ framework.skills.list();
405
+ // [{ name, metadata, content, path, references, scripts, fromPlugin }, ...]
406
+
407
+ // 获取单个技能
408
+ framework.skills.get('my-skill');
409
+ // { name, metadata, content, path, references, scripts, fromPlugin }
410
+
411
+ // 检查技能是否存在
412
+ framework.skills.has('my-skill'); // true/false
413
+
414
+ // 获取技能详细信息(包含完整 metadata)
415
+ framework.skills.details('my-skill');
416
+ // {
417
+ // name: 'my-skill',
418
+ // description: '技能描述',
419
+ // content: '技能内容(markdown)',
420
+ // path: '/path/to/skill',
421
+ // metadata: SkillMetadata 实例,
422
+ // references: ['ref1', 'ref2'],
423
+ // scripts: ['script1.sh', 'script2.js'],
424
+ // fromPlugin: false
425
+ // }
426
+ ```
427
+
428
+ #### 注册 Skill(程序化)
429
+
430
+ ```js
431
+ // 注册一个新 skill(不依赖文件)
432
+ const skill = framework.skills.register('my-skill', '# 我的技能\n\n这是技能内容', {
433
+ description: '技能描述',
434
+ allowedTools: ['Read', 'Write'],
435
+ }, {
436
+ commands: [
437
+ {
438
+ name: 'hello',
439
+ description: '打招呼',
440
+ options: [
441
+ { flags: '-n, --name <value>', description: '名字', defaultValue: 'World' }
442
+ ],
443
+ execute: async (args, ctx) => {
444
+ return `Hello ${args.name}!`;
445
+ }
446
+ }
447
+ ]
448
+ });
449
+
450
+ // 返回注册的 skill 对象
451
+ // { name, metadata, content, instance, path, references, scripts, fromPlugin, registered }
452
+ ```
453
+
454
+ | 参数 | 类型 | 说明 |
455
+ |------|------|------|
456
+ | `name` | string | 技能名称(唯一标识) |
457
+ | `content` | string | 技能内容(markdown 格式) |
458
+ | `metadata` | object | 可选,元数据 `{ description, allowedTools, license, compatibility }` |
459
+ | `options.commands` | array | 可选,命令定义数组 |
460
+ | `options.path` | string | 可选,技能路径(用于扫描 references/scripts) |
461
+
462
+ #### 移除 Skill
463
+
464
+ ```js
465
+ // 移除已注册的程序化 skill
466
+ framework.skills.remove('my-skill'); // true/false
467
+ ```
468
+
469
+ **注意**:只能移除程序化注册的 skill(通过 `framework.skills.register()` 创建),从文件加载的 skill 无法通过此方法移除。
470
+
471
+ #### 重载技能
472
+
473
+ ```js
474
+ // 重载所有技能(重新扫描 skills 目录)
475
+ framework.skills.reload();
476
+ ```
477
+
478
+ #### 命令管理(Commands)
479
+
480
+ 技能可以注册命令供 LLM 调用,命令格式为 `{skill}:{cmd}`:
481
+
482
+ ```js
483
+ // 列出技能的所有命令
484
+ framework.skills.listCommands('my-skill');
485
+ // [{ name: 'cmd1', description: '...', options: [...] }, ...]
486
+
487
+ // 获取单个命令详情
488
+ framework.skills.getCommand('my-skill', 'run');
489
+ // { name: 'run', description: '...', options: [...], inputSchema: {...} }
490
+
491
+ // 调用命令(通过 extension 执行)
492
+ await framework.extensions.execute('skill:my-skill', 'run', { command: '--arg value' });
493
+ ```
494
+
495
+ #### Reference 管理(按需加载的附加文档)
496
+
497
+ Reference 文件放在技能的 `references/` 子目录下:
498
+
499
+ ```js
500
+ // 列出可用的 reference 文件
501
+ framework.skills.listReferences('my-skill'); // ['api-docs', 'guide']
502
+
503
+ // 获取 reference 文件内容(按需加载)
504
+ framework.skills.getReference('my-skill', 'api-docs');
505
+ // 返回文件内容(不含 .md 后缀)
506
+ ```
507
+
508
+ #### Scripts 管理
509
+
510
+ Scripts 文件放在技能的 `scripts/` 子目录下:
511
+
512
+ ```js
513
+ // 列出所有脚本
514
+ framework.skills.listScripts('my-skill');
515
+ // [{ name: 'deploy.sh', path: '/path/to/deploy.sh', isExecutable: true }, ...]
516
+
517
+ // 获取脚本内容
518
+ framework.skills.getScript('my-skill', 'deploy.sh');
519
+ // 返回脚本文件内容
520
+ ```
521
+
522
+ #### Skills 工具(内置工具)
523
+
524
+ `skill-manager` 插件注册了以下内置工具:
525
+
526
+ | 工具 | 说明 |
527
+ |------|------|
528
+ | `skill_load` | 加载指定技能,获取技能内容和命令文档 |
529
+ | `skill_reload` | 重载所有技能 |
530
+ | `skill_load_reference` | 加载技能的附加参考文档 |
531
+ | `skill_list_scripts` | 列出技能的所有脚本 |
532
+ | `skill_load_script` | 读取脚本文件内容 |
533
+
534
+ ---
535
+
536
+ ### 创建新 Skill
537
+
538
+ Skill 存放位置有两种方式:
539
+
540
+ #### 方式一:`.foliko/skills/` 目录
541
+
542
+ ```
543
+ 项目目录/
544
+ ├── .foliko/
545
+ │ └── skills/
546
+ │ └── my-skill/ # 技能文件夹
547
+ │ ├── SKILL.md # 必需,技能定义文件
548
+ │ ├── index.js # 可选,命令定义
549
+ │ ├── references/ # 可选,附加文档
550
+ │ └── scripts/ # 可选,脚本文件
551
+ ```
552
+
553
+ #### 方式二:插件目录下的 SKILL.md
554
+
555
+ ```
556
+ 项目目录/
557
+ └── plugins/
558
+ └── my-plugin/
559
+ ├── index.js
560
+ └── SKILL.md # 自动加载为技能
561
+ ```
562
+
563
+ #### 1. 创建 SKILL.md
564
+
565
+ ```markdown
566
+ ---
567
+ name: my-skill
568
+ description: 这是一个自定义技能的描述。当用户说"..."时调用此技能。
569
+ allowed-tools: Read, Write, Glob, Grep
570
+ license: MIT
571
+ compatibility: v2.0.0
572
+ ---
573
+
574
+ # 我的技能
575
+
576
+ 这里是技能的详细说明内容。
577
+ ```
578
+
579
+ **frontmatter 字段:**
580
+
581
+ | 字段 | 必需 | 说明 |
582
+ |------|------|------|
583
+ | `name` | 是 | 技能唯一标识,1-64字符,字母数字、下划线、横杠 |
584
+ | `description` | 是 | 技能描述,用于意图匹配 |
585
+ | `allowed-tools` | 否 | 允许使用的工具列表 |
586
+ | `license` | 否 | 许可证 |
587
+ | `compatibility` | 否 | 兼容版本 |
588
+
589
+ #### 2. 添加命令(可选)
590
+
591
+ 在技能目录下创建 `index.js`:
592
+
593
+ ```javascript
594
+ module.exports = [
595
+ {
596
+ name: 'hello',
597
+ description: '打招呼命令',
598
+ options: [
599
+ { flags: '-n, --name <value>', description: '名字', defaultValue: 'World' }
600
+ ],
601
+ execute: async (args, ctx) => {
602
+ return `Hello ${args.name}!`;
603
+ }
604
+ },
605
+ {
606
+ name: 'status',
607
+ description: '查看状态',
608
+ execute: async (args, ctx) => {
609
+ return JSON.stringify({ session: ctx.sessionId });
610
+ }
611
+ }
612
+ ];
613
+ ```
614
+
615
+ **命令调用方式:**
616
+
617
+ ```js
618
+ // 通过 extension 执行
619
+ await framework.extensions.execute('skill:my-skill', 'hello', { command: '-n Alice' });
620
+ ```
621
+
622
+ #### 3. 添附加文档(可选)
623
+
624
+ 在 `references/` 目录下放 `.md` 文件:
625
+
626
+ ```
627
+ my-skill/
628
+ ├── SKILL.md
629
+ └── references/
630
+ ├── api.md
631
+ └── guide.md
632
+ ```
633
+
634
+ 使用 `skill_load_reference` 工具加载:
635
+ ```js
636
+ framework.skills.getReference('my-skill', 'api'); // 返回 api.md 内容
637
+ ```
638
+
639
+ #### 4. 重载技能
640
+
641
+ 创建或修改技能后,调用 `skill_reload` 工具重载:
642
+
643
+ ```js
644
+ await framework.executeTool('skill_reload', {});
645
+
646
+ // 或通过 API
647
+ const skillManager = framework.pluginManager.get('skill-manager');
648
+ await skillManager.reload(framework);
649
+ ```
650
+
651
+ #### 完整示例
652
+
653
+ 创建 `skills/my-skill/` 目录:
654
+
655
+ ```
656
+ skills/my-skill/
657
+ ├── SKILL.md
658
+ ├── index.js
659
+ └── references/
660
+ └── usage.md
661
+ ```
662
+
663
+ **SKILL.md:**
664
+ ```markdown
665
+ ---
666
+ name: my-skill
667
+ description: 我的自定义技能
668
+ ---
669
+
670
+ # 我的自定义技能
671
+
672
+ 这是一个示例技能,用于...
673
+
674
+ ## 使用方法
675
+
676
+ 1. 第一步
677
+ 2. 第二步
678
+ ```
679
+
680
+ **index.js:**
681
+ ```javascript
682
+ module.exports = [
683
+ {
684
+ name: 'run',
685
+ description: '执行任务',
686
+ options: [
687
+ { flags: '-t, --task <value>', description: '任务名', required: true }
688
+ ],
689
+ execute: async (args, ctx) => {
690
+ return `Running: ${args.task}`;
691
+ }
692
+ }
693
+ ];
694
+ ```
695
+
696
+ ---
697
+
698
+ ### framework.workflows
699
+
700
+ `framework.workflows` 是工作流引擎接口(需要加载 `workflow` 插件)。用于定义和执行结构化工作流。
701
+
702
+ **注意**:需要先加载 `workflow` 插件才能使用:
703
+
704
+ ```js
705
+ const { WorkflowPlugin } = require('foliko');
706
+ await framework.loadPlugin(new WorkflowPlugin({ workflowsDir: '.foliko/workflows' }));
707
+ ```
708
+
709
+ #### 基本查询
710
+
711
+ ```js
712
+ // 检查 workflows 模块是否可用
713
+ framework.workflows.available; // true/false
714
+
715
+ // 列出所有已加载的工作流
716
+ framework.workflows.list();
717
+ // [{ name, description, stepCount, type }, ...]
718
+
719
+ // 获取单个工作流定义
720
+ framework.workflows.get('my-workflow');
721
+ // { name, description, steps: [...], type }
722
+
723
+ // 检查工作流是否存在
724
+ framework.workflows.has('my-workflow'); // true/false
725
+
726
+ // 获取工作流引擎(用于创建步骤)
727
+ framework.workflows.getEngine(); // WorkflowEngine 实例
728
+ ```
729
+
730
+ #### 执行工作流
731
+
732
+ ```js
733
+ // 执行工作流(通过名称/JSON/JS 代码)
734
+ const result = await framework.workflows.execute('my-workflow', { input: 'value' });
735
+
736
+ // 直接传入工作流定义
737
+ const result = await framework.workflows.execute({
738
+ name: 'my-workflow',
739
+ description: '我的工作流',
740
+ steps: [
741
+ { id: 'step1', tool: 'some_tool', args: { msg: 'hello' } },
742
+ { id: 'step2', tool: 'another_tool', args: { msg: '{{step1.result}}' } },
743
+ ]
744
+ }, { input: 'value' });
745
+ ```
746
+
747
+ #### 注册工作流(程序化)
748
+
749
+ ```js
750
+ // 注册工作流定义
751
+ const result = framework.workflows.register('greet', {
752
+ description: '打招呼工作流',
753
+ steps: [
754
+ { tool: 'echo', args: { message: 'Hello {{input.name}}' } }
755
+ ]
756
+ });
757
+ // { success: true, data: { name: 'greet', toolName: 'workflow_greet' } }
758
+
759
+ // 移除工作流
760
+ framework.workflows.remove('greet'); // true/false
761
+
762
+ // 重载工作流
763
+ framework.workflows.reload();
764
+ ```
765
+
766
+ #### 工作流步骤类型
767
+
768
+ | 类型 | 说明 | 示例 |
769
+ |------|------|------|
770
+ | `tool` | 执行工具 | `{ tool: 'my_tool', args: { ... } }` |
771
+ | `script` | 执行脚本 | `{ script: 'return input * 2' }` |
772
+ | `condition` | 条件分支 | `{ branches: [{ condition: 'x > 0', steps: [...] }] }` |
773
+ | `switch` | 多值分支 | `{ value: '{{status}}', branches: [...] }` |
774
+ | `try` | 异常捕获 | `{ try: { steps: [...] }, catch: { steps: [...] } }` |
775
+ | `parallel` | 并行执行 | `{ parallel: true, steps: [...] }` |
776
+ | `loop` | 循环执行 | `{ loop: { maxIterations: 10, steps: [...] } }` |
777
+ | `delay` | 延迟等待 | `{ delayMs: 1000 }` |
778
+ | `workflow` | 嵌套工作流 | `{ workflow: subWorkflowDef }` |
779
+
780
+ #### 工作流工具(内置工具)
781
+
782
+ | 工具 | 说明 |
783
+ |------|------|
784
+ | `execute_workflow` | 执行指定的工作流定义 |
785
+ | `workflow_list` | 列出所有已加载的工作流 |
786
+ | `workflow_reload` | 重载所有工作流 |
787
+
788
+ ---
789
+
790
+ ### framework.rules
791
+
792
+ `framework.rules` 是规则引擎接口(需要加载 `rules` 插件)。用于控制工具调用权限、内容过滤、触发动作。
793
+
794
+ **注意**:需要先加载 `rules` 插件才能使用:
795
+
796
+ ```js
797
+ const RulesPlugin = require('foliko').RulesPlugin || require('foliko/plugins/core/rules');
798
+ await framework.loadPlugin(new RulesPlugin({ rulesDir: '.foliko/rules' }));
799
+ ```
800
+
801
+ #### 基本操作
802
+
803
+ ```js
804
+ // 检查 rules 模块是否可用
805
+ framework.rules.available; // true/false
806
+
807
+ // 列出所有规则
808
+ framework.rules.list();
809
+ // [{ name, type, target, description, enabled }, ...]
810
+
811
+ // 添加规则
812
+ framework.rules.add({
813
+ name: 'deny-shell',
814
+ type: 'deny', // 'allow' | 'deny' | 'transform'
815
+ target: 'shell_execute', // 工具名或 '*'
816
+ pattern: 'rm -rf', // 可选,正则表达式
817
+ description: '禁止危险 shell 命令',
818
+ });
819
+
820
+ // 移除规则
821
+ framework.rules.remove('deny-shell'); // true/false
822
+
823
+ // 获取规则统计
824
+ framework.rules.stats();
825
+ // { total, allowed, denied, transformed }
826
+
827
+ // 重载规则
828
+ framework.rules.reload();
829
+ ```
830
+
831
+ #### 规则检查
832
+
833
+ ```js
834
+ // 测试规则匹配
835
+ const result = framework.rules.test('shell_execute', { command: 'ls' });
836
+ // { allowed: true, ruleName: null, ruleType: null }
837
+
838
+ // 检查工具调用
839
+ const result = framework.rules.checkToolCall('my_tool', { arg: 'value' });
840
+ // { allowed: true, rule: Rule | null }
841
+
842
+ // 检查消息
843
+ const result = framework.rules.checkMessage('some message', { context: 'info' });
844
+ // { allowed: true, rule: Rule | null }
845
+ ```
846
+
847
+ #### 规则类型
848
+
849
+ | 类型 | 说明 | 用途 |
850
+ |------|------|------|
851
+ | `allow` | 允许 | 白名单,放行匹配的调用 |
852
+ | `deny` | 拒绝 | 黑名单,阻止匹配的调用 |
853
+ | `transform` | 转换 | 修改输入参数后放行 |
854
+
855
+ #### 规则工具(内置工具)
856
+
857
+ | 工具 | 说明 |
858
+ |------|------|
859
+ | `rules_list` | 列出所有规则 |
860
+ | `rules_add` | 添加规则 |
861
+ | `rules_remove` | 移除规则 |
862
+ | `rules_stats` | 获取规则统计 |
863
+ | `rules_test` | 测试规则匹配 |
864
+
865
+ ---
866
+
867
+ ### framework.storage
868
+
869
+ `framework.storage` 是键值存储接口(需要加载 `storage` 插件)。提供 JSONL 格式的持久化存储,支持自动 compaction。
870
+
871
+ **注意**:需要先加载 `storage` 插件才能使用:
872
+
873
+ ```js
874
+ const { StoragePlugin } = require('foliko');
875
+ await framework.loadPlugin(new StoragePlugin({ path: '.foliko/data', namespace: 'default' }));
876
+ ```
877
+
878
+ #### 基本操作
879
+
880
+ ```js
881
+ // 检查 storage 模块是否可用
882
+ framework.storage.available; // true/false
883
+
884
+ // 设置值
885
+ framework.storage.set('my-key', { data: 'value' }, 'namespace'); // namespace 可选
886
+
887
+ // 获取值
888
+ framework.storage.get('my-key', 'namespace'); // null 或值
889
+
890
+ // 删除键(软删除)
891
+ framework.storage.delete('my-key', 'namespace');
892
+
893
+ // 列出命名空间下的所有键
894
+ framework.storage.list('namespace'); // ['key1', 'key2', ...]
895
+
896
+ // 清空命名空间
897
+ framework.storage.clear('namespace'); // { cleared: 10 }
898
+
899
+ // 获取存储统计
900
+ framework.storage.stats();
901
+ // { entryCount, tombstoneCount, fileSize, ... }
902
+
903
+ // 手动触发 compaction
904
+ framework.storage.compact();
905
+
906
+ // 获取底层 StorageManager
907
+ framework.storage.getStore();
908
+ ```
909
+
910
+ #### Storage 工具(内置工具)
911
+
912
+ | 工具 | 说明 |
913
+ |------|------|
914
+ | `storage_set` | 存储数据 |
915
+ | `storage_get` | 获取数据 |
916
+ | `storage_delete` | 删除数据 |
917
+ | `storage_list` | 列出键 |
918
+ | `storage_clear` | 清空数据 |
919
+ | `storage_watch` | 监听键值变化 |
920
+ | `storage_compact` | 手动触发 compaction |
921
+ | `storage_stats` | 获取统计信息 |
922
+
923
+ ---
924
+
925
+ ### framework.sessions
926
+
927
+ `framework.sessions` 是会话管理接口,提供会话的查询和操作。
928
+
929
+ ```js
930
+ // 列出所有会话 ID
931
+ framework.sessions.list(); // ['session1', 'session2', ...]
932
+
933
+ // 获取会话上下文
934
+ framework.sessions.get('session-id'); // SessionManager 或 null
935
+
936
+ // 创建或获取会话
937
+ framework.sessions.getOrCreate('session-id', options);
938
+
939
+ // 销毁会话
940
+ framework.sessions.destroy('session-id');
941
+
942
+ // 获取当前会话 ID
943
+ framework.sessions.currentId(); // 'session-id' 或 null
944
+
945
+ // 在会话中执行代码
946
+ await framework.sessions.runIn('session-id', options, async () => {
947
+ // 在此作用域内可通过 framework.getCurrentSessionContext() 获取会话
948
+ });
949
+ ```
950
+
951
+ ---
952
+
953
+ ### framework.mcps
954
+
955
+ `framework.mcps` 是 MCP (Model Context Protocol) 服务器管理接口(需要加载 `mcp` 插件)。
956
+
957
+ **注意**:需要先加载 `mcp` 插件才能使用:
958
+
959
+ ```js
960
+ const { MCPExecutorPlugin } = require('foliko');
961
+ await framework.loadPlugin(new MCPExecutorPlugin({
962
+ servers: [
963
+ { name: 'my-server', command: 'npx', args: ['-y', 'some-mcp-server'] }
964
+ ]
965
+ }));
966
+ ```
967
+
968
+ #### 基本操作
969
+
970
+ ```js
971
+ // 检查 mcps 模块是否可用
972
+ framework.mcps.available; // true/false
973
+
974
+ // 列出所有 MCP 服务器
975
+ framework.mcps.list();
976
+ // [{ name, enabled, connected, tools: [{ name, description }, ...] }, ...]
977
+
978
+ // 获取单个服务器信息
979
+ framework.mcps.get('my-server');
980
+ // { name, enabled, connected, tools: [...] } 或 null
981
+
982
+ // 检查服务器是否存在
983
+ framework.mcps.has('my-server'); // true/false
984
+
985
+ // 添加 MCP 服务器
986
+ await framework.mcps.add({
987
+ name: 'my-server',
988
+ command: 'npx',
989
+ args: ['-y', 'some-mcp-server'],
990
+ env: { API_KEY: 'xxx' }
991
+ });
992
+ // addServer 是 add 的别名
993
+ await framework.mcps.addServer({ name: 'another-server', ... });
994
+
995
+ // 移除 MCP 服务器
996
+ await framework.mcps.removeServer('my-server');
997
+
998
+ // 动态开启/关闭服务器
999
+ await framework.mcps.setEnabled('my-server', false);
1000
+
1001
+ // 重载 MCP 配置
1002
+ await framework.mcps.reload();
1003
+ ```
1004
+
1005
+ #### 调用 MCP 工具
1006
+
1007
+ ```js
1008
+ // 调用 MCP 服务器上的工具
1009
+ await framework.mcps.call('my-server', 'tool-name', { arg1: 'value' });
1010
+
1011
+ // 获取工具的调用示例和参数说明
1012
+ await framework.mcps.getToolSchema('my-server', 'tool-name');
1013
+ // { success, data: { name, description, parameters, example, fullExample } }
1014
+ ```
1015
+
1016
+ #### MCP 工具(内置工具)
1017
+
1018
+ | 工具 | 说明 |
1019
+ |------|------|
1020
+ | `mcp_list_servers` | 列出所有 MCP 服务器及其状态 |
1021
+ | `mcp_set_enabled` | 动态开启/关闭某个 MCP 服务器 |
1022
+ | `mcp_reload` | 重载 MCP 配置(`.foliko/mcp_config.json`) |
1023
+
1024
+ > **MCP 工具调用已统一**:MCP 服务器启动后自动注册为 `mcp:<servername>` 扩展,
1025
+ > 工具通过 `ext_skill({ plugin: "mcp:<servername>" })` + `ext_call(...)` 统一调度,
1026
+ > 不再需要独立的 `mcp_call` / `mcp_tool_schema`。
1027
+
1028
+ ---
1029
+
1030
+ ### 声明式 prompts(推荐)
1031
+
1032
+ 除手动调用 `framework.prompts.register()` 外,还可在 Plugin 子类中用 `prompts` 字段声明:
1033
+
1034
+ ```js
1035
+ class MyPlugin extends Plugin {
1036
+ name = 'my-plugin';
1037
+
1038
+ prompts = [
1039
+ {
1040
+ name: 'my-rules',
1041
+ scope: 'global',
1042
+ priority: 50,
1043
+ description: '我的自定义规则',
1044
+ provider: function () {
1045
+ // this 绑定到 Plugin 实例
1046
+ return `## 规则\n当前版本: ${this.version}`;
1047
+ },
1048
+ },
1049
+ ];
1050
+
1051
+ start(framework) {
1052
+ // 基类 start() 会自动注册 prompts
1053
+ // 无需手动调用 framework.prompts.register()
1054
+ }
1055
+ }
1056
+ ```
1057
+
1058
+ - `provider` 必须用 `function`,不能用箭头函数(需要绑定 `this` 到 Plugin 实例)
1059
+ - 生命周期自动管理:卸载/重载时自动清理
1060
+ - 失败一个 entry 不影响其他
1061
+
1062
+ ---
1063
+
1064
+ ### 完整的插件示例
1065
+
1066
+ ```js
1067
+ const { Plugin } = require('foliko');
1068
+
1069
+ class MyPlugin extends Plugin {
1070
+ name = 'my-plugin';
1071
+ version = '1.0.0';
1072
+ description = '我的插件';
1073
+
1074
+ prompts = [
1075
+ {
1076
+ name: 'plugin-info',
1077
+ scope: 'global',
1078
+ priority: 50,
1079
+ provider: function () {
1080
+ return `## 我的插件\n版本: ${this.version}`;
1081
+ },
1082
+ },
1083
+ ];
1084
+
1085
+ onStart(framework) {
1086
+ // 注册 AI SDK 工具(直接调用)
1087
+ this.tool.register({
1088
+ name: 'my_tool',
1089
+ description: '做某事',
1090
+ inputSchema: framework.z.object({ msg: framework.z.string() }),
1091
+ execute: async (args) => ({ result: args.msg }),
1092
+ });
1093
+
1094
+ // 注册扩展工具(通过 ext_call 调用)
1095
+ this.extension.register({
1096
+ name: 'my_ext_tool',
1097
+ description: '扩展工具',
1098
+ inputSchema: framework.z.object({ data: framework.z.string() }),
1099
+ execute: async (args) => ({ data: args.data }),
1100
+ });
1101
+ }
1102
+
1103
+ onStop() {
1104
+ // 自定义清理逻辑(工具/扩展基类自动处理)
1105
+ }
1106
+ }
1107
+
1108
+ module.exports = MyPlugin;
1109
+ ```
1110
+
1111
+ ---
1112
+
1113
+ ### 工具注册(tool.register / extension.register)
1114
+
1115
+ Plugin 基类提供两个属性用于注册工具:
1116
+
1117
+ #### tool.register - AI SDK 直接调用
1118
+
1119
+ 通过 `this.tool.register()` 注册的工具会直接暴露给 AI 模型调用:
1120
+
1121
+ ```js
1122
+ this.tool.register({
1123
+ name: 'get-weather',
1124
+ description: '获取天气',
1125
+ inputSchema: framework.z.object({ city: framework.z.string() }),
1126
+ execute: async (args, framework) => {
1127
+ return { weather: '晴天', temp: 25 };
1128
+ },
1129
+ });
1130
+ ```
1131
+
1132
+ #### extension.register - ext_call 间接调用
1133
+
1134
+ 通过 `this.extension.register()` 注册的工具通过 `ext_call` 间接调用:
1135
+
1136
+ ```js
1137
+ this.extension.register({
1138
+ name: 'send-notification',
1139
+ description: '发送通知',
1140
+ inputSchema: framework.z.object({
1141
+ title: framework.z.string(),
1142
+ message: framework.z.string(),
1143
+ }),
1144
+ execute: async (args) => {
1145
+ // ...
1146
+ },
1147
+ });
1148
+ ```
1149
+
1150
+ 扩展名自动使用 `this.name`。
1151
+
1152
+ #### 生命周期管理
1153
+
1154
+ | 操作 | 效果 |
1155
+ |------|------|
1156
+ | `disable` | 自动移除工具/扩展(保留注册记录) |
1157
+ | `enable` | 自动重新注册工具/扩展 |
1158
+ | `reload` | 移除后重新注册 |
1159
+ | `uninstall` | 移除并清空注册记录 |
1160
+
1161
+ #### 手动移除
1162
+
1163
+ ```js
1164
+ this.tool.remove('tool-name');
1165
+ this.extension.remove('tool-name');
1166
+ ```
1167
+
1168
+ #### 执行工具
1169
+
1170
+ **tool.execute** - 执行所有已注册的 AI SDK 工具(不仅限当前插件):
1171
+
1172
+ ```js
1173
+ // 执行任何已注册的工具
1174
+ const result = await this.tool.execute('any-tool-name', { arg1: 'value' });
1175
+ ```
1176
+
1177
+ **extension.execute** - 执行扩展工具,支持两种调用方式:
1178
+
1179
+ ```js
1180
+ // 执行当前插件注册的扩展工具
1181
+ const result = await this.extension.execute('tool-name', { arg1: 'value' });
1182
+
1183
+ // 执行指定插件注册的扩展工具
1184
+ const result = await this.extension.execute('plugin-name', 'tool-name', { arg1: 'value' });
1185
+ ```
1186
+
1187
+ ---
1188
+
1189
+ ### 工作流注册(workflow.register)
1190
+
1191
+ Plugin 基类提供 `workflow` 属性用于注册和执行工作流:
1192
+
1193
+ ```js
1194
+ // 注册工作流
1195
+ this.workflow.register('my-workflow', {
1196
+ name: 'my-workflow',
1197
+ description: '我的工作流',
1198
+ steps: [
1199
+ { type: 'tool', tool: 'hello', args: {} },
1200
+ { type: 'script', script: 'return context.variables;' }
1201
+ ]
1202
+ });
1203
+
1204
+ // 执行工作流
1205
+ const result = await this.workflow.execute('my-workflow', { input: 'value' });
1206
+
1207
+ // 移除工作流
1208
+ this.workflow.remove('my-workflow');
1209
+ ```
1210
+
1211
+ #### 执行结果
1212
+
1213
+ `workflow.execute` 返回:
1214
+
1215
+ ```js
1216
+ {
1217
+ success: true,
1218
+ stepCount: 2,
1219
+ result: { ... }, // 最后一步的结果
1220
+ output: { ... } // 所有非下划线开头的变量
1221
+ }
1222
+ ```
1223
+
1224
+ #### 生命周期管理
1225
+
1226
+ | 操作 | 效果 |
1227
+ |------|------|
1228
+ | `disable` | 自动移除工作流(保留注册记录) |
1229
+ | `enable` | 自动重新注册工作流 |
1230
+ | `reload` | 移除后重新注册 |
1231
+ | `uninstall` | 移除并清空注册记录 |
1232
+
1233
+ ---
1234
+
1235
+ ### 子代理注册(agent.register)
1236
+
1237
+ Plugin 基类提供 `agent` 属性用于注册子代理:
1238
+
1239
+ ```js
1240
+ // 注册子代理
1241
+ this.agent.register({
1242
+ name: 'my-agent',
1243
+ role: '助手',
1244
+ goal: '描述',
1245
+ tools: {},
1246
+ parentTools: ['read_file', 'write_file']
1247
+ });
1248
+
1249
+ // 获取已注册的子代理
1250
+ const agent = this.agent.get('my-agent');
1251
+
1252
+ // 移除子代理
1253
+ this.agent.remove('my-agent');
1254
+ ```
1255
+
1256
+ #### 生命周期管理
1257
+
1258
+ | 操作 | 效果 |
1259
+ |------|------|
1260
+ | `disable` | 自动移除子代理(保留注册记录) |
1261
+ | `enable` | 自动重新注册子代理 |
1262
+ | `reload` | 移除后重新注册 |
1263
+ | `uninstall` | 移除并清空注册记录 |
1264
+
1265
+ ---
1266
+
1267
+ ### Prompt 注册(prompt.register)
1268
+
1269
+ Plugin 基类提供 `prompt` 属性用于注册 prompt parts:
1270
+
1271
+ ```js
1272
+ // 注册 prompt part
1273
+ this.prompt.register('my-prompt', () => 'Prompt content here', {
1274
+ scope: 'global',
1275
+ priority: 50,
1276
+ description: '描述'
1277
+ });
1278
+
1279
+ // 移除 prompt part
1280
+ this.prompt.remove('my-prompt');
1281
+ ```
1282
+
1283
+ #### 生命周期管理
1284
+
1285
+ | 操作 | 效果 |
1286
+ |------|------|
1287
+ | `disable` | 自动移除 prompt(保留注册记录) |
1288
+ | `enable` | 自动重新注册 prompt |
1289
+ | `reload` | 移除后重新注册 |
1290
+ | `uninstall` | 移除并清空注册记录 |
1291
+
1292
+ ---
1293
+
1294
+ ### 技能注册(skill.register)
1295
+
1296
+ Plugin 基类提供 `skill` 属性用于程序化管理技能。注册后自动以 `skill:<name>` 形式暴露为扩展,可通过 `ext_skill` + `ext_call` 统一调用。
1297
+
1298
+ ```js
1299
+ // 注册技能
1300
+ this.skill.register('my-skill', '# 技能说明\n\n...内容', {
1301
+ description: '技能描述',
1302
+ 'allowed-tools': 'tool1,tool2',
1303
+ }, {
1304
+ commands: [{
1305
+ name: 'do-something',
1306
+ description: '做某事',
1307
+ options: [
1308
+ { flags: '-n, --name <name>', description: '姓名', required: true },
1309
+ { flags: '-v, --verbose', description: '详细模式' },
1310
+ ],
1311
+ execute: async (args) => {
1312
+ // args 是 commander.js 解析后的对象: { name: 'xxx', verbose: true }
1313
+ return { success: true, data: '处理结果' };
1314
+ },
1315
+ }],
1316
+ path: '/optional/path', // 可选:技能路径
1317
+ });
1318
+
1319
+ // 查询
1320
+ this.skill.has('my-skill'); // boolean
1321
+ this.skill.get('my-skill'); // 完整 skill 对象
1322
+ this.skill.details('my-skill'); // { name, description, content, commands, references, ... }
1323
+ this.skill.list(); // 所有技能数组
1324
+ this.skill.listOwned(); // 本插件注册的技能名
1325
+
1326
+ // 删除
1327
+ this.skill.remove('my-skill'); // boolean
1328
+
1329
+ // 直接执行(内部走 ext_call)
1330
+ await this.skill.execute('skill:my-skill', 'do-something', { command: '-n Claude --verbose' });
1331
+ ```
1332
+
1333
+ #### 生命周期管理
1334
+
1335
+ | 操作 | 效果 |
1336
+ |------|------|
1337
+ | `disable` | 自动移除技能(保留注册记录) |
1338
+ | `enable` | 自动重新注册技能 |
1339
+ | `reload` | 移除后重新注册(`remove()` 后 reload 不会复活) |
1340
+ | `uninstall` | 移除并清空注册记录 |
1341
+
1342
+ ---
1343
+
1344
+ ## 扩展工具统一调用(ext_call / ext_skill)
1345
+
1346
+ 所有"非 AI SDK 直调"的工具(普通扩展、MCP 服务器、Skill 命令)都通过 `ext_call` + `ext_skill` 统一调度:
1347
+
1348
+ ```js
1349
+ // 1. 查询参数
1350
+ ext_skill({ plugin: "<extension_name>" })
1351
+
1352
+ // 2. 调用
1353
+ ext_call({
1354
+ plugin: "<extension_name>",
1355
+ tool: "<tool_name>",
1356
+ args: { /* 结构化参数 */ }
1357
+ })
1358
+ ```
1359
+
1360
+ 扩展命名空间:
1361
+
1362
+ | 扩展类型 | plugin 名格式 | 工具名格式 |
1363
+ |---|---|---|
1364
+ | 普通扩展(如 `gate-trading`) | `gate-trading` | `gate_get_balance` |
1365
+ | MCP 服务器 | `mcp:<servername>` | MCP 原始工具名(无前缀) |
1366
+ | Skill 命令 | `skill:<skillname>` | skill 命令名(无前缀) |
1367
+
1368
+ 完整示例见 [`docs/usage.md` 的 Extensions 章节](#extensions-统一调用)。
1369
+
1370
+ ---
1371
+
198
1372
  ## 导出类型
199
1373
 
200
1374
  ```js