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.
- package/.claude/settings.local.json +4 -1
- package/.cli_default_systemPrompt.md +291 -0
- package/CLAUDE.md +3 -0
- package/README.md +20 -3
- package/docs/architecture.md +34 -2
- package/docs/extensions.md +199 -0
- package/docs/migration.md +100 -0
- package/docs/public-api.md +1180 -6
- package/docs/usage.md +122 -30
- package/package.json +1 -1
- package/plugins/core/audit/index.js +1 -1
- package/plugins/core/default/bootstrap.js +65 -19
- package/plugins/core/python-loader/index.js +43 -25
- package/plugins/core/skill-manager/PROMPT.md +6 -0
- package/plugins/core/skill-manager/index.js +538 -93
- package/plugins/core/sub-agent/PROMPT.md +10 -0
- package/plugins/core/sub-agent/index.js +36 -3
- package/plugins/core/think/index.js +1 -0
- package/plugins/core/workflow/index.js +106 -22
- package/plugins/executors/data-splitter/PROMPT.md +13 -0
- package/plugins/executors/data-splitter/index.js +5 -4
- package/plugins/executors/extension/extension-registry.js +145 -0
- package/plugins/executors/extension/index.js +405 -437
- package/plugins/executors/extension/prompt-builder.js +359 -0
- package/plugins/executors/extension/skill-helper.js +143 -0
- package/plugins/messaging/feishu/index.js +5 -3
- package/plugins/messaging/qq/index.js +6 -4
- package/plugins/messaging/telegram/index.js +6 -3
- package/plugins/messaging/weixin/index.js +5 -3
- package/plugins/tools/PROMPT.md +26 -0
- package/plugins/tools/index.js +6 -5
- package/skills/foliko/AGENTS.md +196 -43
- package/skills/foliko/SKILL.md +157 -28
- package/skills/mcp/SKILL.md +77 -118
- package/skills/plugins/SKILL.md +89 -3
- package/skills/python/SKILL.md +57 -39
- package/skills/skill-guide/SKILL.md +42 -34
- package/skills/workflows/SKILL.md +224 -9
- package/skills/workflows/workflow-troubleshooting/SKILL.md +221 -281
- package/src/agent/chat.js +48 -27
- package/src/agent/main.js +34 -13
- package/src/agent/prompt-registry.js +133 -87
- package/src/agent/prompts/PROMPT.md +3 -0
- package/src/agent/sub.js +1 -1
- package/src/cli/ui/chat-ui-old.js +5 -2
- package/src/cli/ui/chat-ui.js +5 -2
- package/src/common/constants.js +12 -0
- package/src/common/error-capture.js +91 -0
- package/src/common/logger.js +2 -2
- package/src/context/compressor.js +6 -2
- package/src/executors/mcp-executor.js +105 -125
- package/src/framework/framework.js +632 -6
- package/src/index.js +4 -0
- package/src/plugin/base.js +913 -10
- package/src/plugin/manager.js +29 -8
- package/src/tool/schema.js +32 -9
- package/tests/core/plugin-prompts.test.js +13 -13
- package/tests/core/prompt-registry.test.js +59 -51
- package/website/index.html +821 -0
|
@@ -91,7 +91,7 @@ class Framework extends EventEmitter {
|
|
|
91
91
|
// Subsystems
|
|
92
92
|
this.toolRegistry = new ToolRegistry();
|
|
93
93
|
this.pluginManager = new PluginManager(this);
|
|
94
|
-
this.promptRegistry = new PromptRegistry();
|
|
94
|
+
this.promptRegistry = new PromptRegistry(this);
|
|
95
95
|
|
|
96
96
|
// Agent management
|
|
97
97
|
this._agents = [];
|
|
@@ -258,7 +258,400 @@ class Framework extends EventEmitter {
|
|
|
258
258
|
|
|
259
259
|
// prompts 对象别名(系统提示词注册表)
|
|
260
260
|
get prompts() {
|
|
261
|
-
|
|
261
|
+
const reg = this.promptRegistry;
|
|
262
|
+
return {
|
|
263
|
+
available: true,
|
|
264
|
+
/**
|
|
265
|
+
* 注册 prompt part
|
|
266
|
+
* @param {string} owner 拥有者
|
|
267
|
+
* @param {string} name part 名
|
|
268
|
+
* @param {Function} provider () => string|null
|
|
269
|
+
* @param {Object} [options]
|
|
270
|
+
* @param {string} [options.scope='global'] 作用域: 'global'|'main'|agentId
|
|
271
|
+
* @param {number} [options.priority=100] 优先级(越小越靠前)
|
|
272
|
+
* @param {string} [options.description] 描述
|
|
273
|
+
*/
|
|
274
|
+
register: (owner, name, provider, options) => reg.register(owner, name, provider, options),
|
|
275
|
+
/**
|
|
276
|
+
* 从文件注册 prompt(支持变量插值)
|
|
277
|
+
* @param {string} owner 拥有者
|
|
278
|
+
* @param {string} filePath 文件路径
|
|
279
|
+
* @param {Object} [vars] 变量对象,模板中用 {{varName}} 引用
|
|
280
|
+
* @param {Object} [options] 同 register 选项
|
|
281
|
+
*/
|
|
282
|
+
registerFile: (owner, filePath, vars, options) => reg.registerFile(owner, filePath, vars, options),
|
|
283
|
+
/**
|
|
284
|
+
* 注销 prompt part
|
|
285
|
+
*/
|
|
286
|
+
unregister: (owner, name) => reg.unregister(owner, name),
|
|
287
|
+
/**
|
|
288
|
+
* 清空指定 owner 的所有 parts
|
|
289
|
+
*/
|
|
290
|
+
clearOwner: (owner) => reg.clearOwner(owner),
|
|
291
|
+
/**
|
|
292
|
+
* 检查是否存在
|
|
293
|
+
*/
|
|
294
|
+
has: (owner, name) => reg.has(owner, name),
|
|
295
|
+
/**
|
|
296
|
+
* 获取单个 part
|
|
297
|
+
*/
|
|
298
|
+
getPart: (owner, name) => reg.getPart(owner, name),
|
|
299
|
+
/**
|
|
300
|
+
* 列出所有 parts(按 priority 排序)
|
|
301
|
+
*/
|
|
302
|
+
list: () => reg.list(),
|
|
303
|
+
/**
|
|
304
|
+
* 按 scope 过滤
|
|
305
|
+
*/
|
|
306
|
+
listByScope: (scope) => reg.listByScope(scope),
|
|
307
|
+
/**
|
|
308
|
+
* 获取适用于指定 agent 的 parts
|
|
309
|
+
*/
|
|
310
|
+
listForAgent: (agentId) => reg.listForAgent(agentId),
|
|
311
|
+
/**
|
|
312
|
+
* 结构化检查(调试用)
|
|
313
|
+
*/
|
|
314
|
+
inspect: () => reg.inspect(),
|
|
315
|
+
/**
|
|
316
|
+
* 渲染完整提示词
|
|
317
|
+
*/
|
|
318
|
+
build: () => reg.build(),
|
|
319
|
+
/**
|
|
320
|
+
* 渲染适用于指定 agent 的 prompt
|
|
321
|
+
* @param {string|null} agentId agent ID,null 表示主 agent
|
|
322
|
+
*/
|
|
323
|
+
buildForAgent: (agentId) => reg.buildForAgent(agentId),
|
|
324
|
+
/**
|
|
325
|
+
* build() 的语义别名
|
|
326
|
+
*/
|
|
327
|
+
preview: () => reg.preview(),
|
|
328
|
+
/**
|
|
329
|
+
* 失效指定 part
|
|
330
|
+
*/
|
|
331
|
+
invalidate: (owner, name) => reg.invalidate(owner, name),
|
|
332
|
+
/**
|
|
333
|
+
* 失效所有 parts
|
|
334
|
+
*/
|
|
335
|
+
invalidateAll: () => reg.invalidateAll(),
|
|
336
|
+
/**
|
|
337
|
+
* 获取 part 数量
|
|
338
|
+
*/
|
|
339
|
+
count: () => reg.count(),
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// skills 对象别名(技能管理器)
|
|
344
|
+
get skills() {
|
|
345
|
+
const self = this;
|
|
346
|
+
const skillManager = this.pluginManager?.get('skill-manager');
|
|
347
|
+
if (!skillManager) {
|
|
348
|
+
return {
|
|
349
|
+
available: false,
|
|
350
|
+
list: () => [],
|
|
351
|
+
get: () => null,
|
|
352
|
+
has: () => false,
|
|
353
|
+
register: () => null,
|
|
354
|
+
remove: () => false,
|
|
355
|
+
details: () => null,
|
|
356
|
+
listCommands: () => [],
|
|
357
|
+
getCommand: () => null,
|
|
358
|
+
listReferences: () => [],
|
|
359
|
+
getReference: () => null,
|
|
360
|
+
listScripts: () => [],
|
|
361
|
+
getScript: () => null,
|
|
362
|
+
reload: () => {},
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
return {
|
|
366
|
+
available: true,
|
|
367
|
+
/**
|
|
368
|
+
* 列出所有已加载的技能
|
|
369
|
+
*/
|
|
370
|
+
list: () => skillManager.getAllSkills() || [],
|
|
371
|
+
/**
|
|
372
|
+
* 获取单个技能
|
|
373
|
+
* @param {string} name 技能名称
|
|
374
|
+
*/
|
|
375
|
+
get: (name) => skillManager.getSkill(name) || null,
|
|
376
|
+
/**
|
|
377
|
+
* 检查技能是否存在
|
|
378
|
+
*/
|
|
379
|
+
has: (name) => skillManager.hasSkill(name) || false,
|
|
380
|
+
/**
|
|
381
|
+
* 获取技能详情(包含 metadata、content、path 等)
|
|
382
|
+
*/
|
|
383
|
+
details: (name) => {
|
|
384
|
+
const skill = skillManager.getSkill(name);
|
|
385
|
+
if (!skill) return null;
|
|
386
|
+
return {
|
|
387
|
+
name: skill.name,
|
|
388
|
+
description: skill.metadata?.description || '',
|
|
389
|
+
content: skill.content || '',
|
|
390
|
+
path: skill.path || null,
|
|
391
|
+
metadata: skill.metadata || null,
|
|
392
|
+
references: skill.references ? Array.from(skill.references.keys()) : [],
|
|
393
|
+
scripts: skill.scripts ? Array.from(skill.scripts.keys()) : [],
|
|
394
|
+
fromPlugin: skill.fromPlugin || false,
|
|
395
|
+
};
|
|
396
|
+
},
|
|
397
|
+
/**
|
|
398
|
+
* 注册一个新 skill(程序化注册,不依赖文件)
|
|
399
|
+
* @param {string} name 技能名称
|
|
400
|
+
* @param {string} content 技能内容(markdown)
|
|
401
|
+
* @param {Object} [metadata] 技能元数据 { description, license, compatibility, allowedTools }
|
|
402
|
+
* @param {Object} [options] 选项
|
|
403
|
+
* @param {Array} [options.commands] 命令定义数组
|
|
404
|
+
* @param {string} [options.path] 技能路径
|
|
405
|
+
*/
|
|
406
|
+
register: (name, content, metadata = {}, options = {}) => skillManager.register(name, content, metadata, options),
|
|
407
|
+
/**
|
|
408
|
+
* 移除 skill
|
|
409
|
+
* @param {string} name 技能名称
|
|
410
|
+
*/
|
|
411
|
+
remove: (name) => skillManager.remove(name),
|
|
412
|
+
/**
|
|
413
|
+
* 列出技能的所有命令
|
|
414
|
+
* @param {string} name 技能名称
|
|
415
|
+
*/
|
|
416
|
+
listCommands: (name) => {
|
|
417
|
+
const skill = skillManager.getSkill(name);
|
|
418
|
+
if (!skill || !skill.instance?._commands) return [];
|
|
419
|
+
return skill.instance._commands.map(cmd => {
|
|
420
|
+
const extExecutor = self.pluginManager?.get('extension-executor');
|
|
421
|
+
const toolDef = extExecutor?.getExtensionTool?.('skill', `${name}:${cmd}`);
|
|
422
|
+
return {
|
|
423
|
+
name: cmd,
|
|
424
|
+
description: toolDef?.description || '',
|
|
425
|
+
options: toolDef?._options || null,
|
|
426
|
+
};
|
|
427
|
+
});
|
|
428
|
+
},
|
|
429
|
+
/**
|
|
430
|
+
* 获取技能的单个命令详情
|
|
431
|
+
* @param {string} name 技能名称
|
|
432
|
+
* @param {string} command 命令名(不含技能名前缀)
|
|
433
|
+
*/
|
|
434
|
+
getCommand: (name, command) => {
|
|
435
|
+
const fullName = `${name}:${command}`;
|
|
436
|
+
const extExecutor = self.pluginManager?.get('extension-executor');
|
|
437
|
+
const toolDef = extExecutor?.getExtensionTool?.('skill', fullName);
|
|
438
|
+
if (!toolDef) return null;
|
|
439
|
+
return {
|
|
440
|
+
name: fullName,
|
|
441
|
+
description: toolDef.description || '',
|
|
442
|
+
options: toolDef._options || null,
|
|
443
|
+
inputSchema: toolDef.inputSchema || null,
|
|
444
|
+
};
|
|
445
|
+
},
|
|
446
|
+
/**
|
|
447
|
+
* 列出技能的所有 reference 文件
|
|
448
|
+
* @param {string} name 技能名称
|
|
449
|
+
*/
|
|
450
|
+
listReferences: (name) => skillManager.listReferences(name) || [],
|
|
451
|
+
/**
|
|
452
|
+
* 获取 reference 文件内容(按需加载)
|
|
453
|
+
* @param {string} name 技能名称
|
|
454
|
+
* @param {string} refName reference 名(不含 .md)
|
|
455
|
+
*/
|
|
456
|
+
getReference: (name, refName) => skillManager.loadReference(name, refName),
|
|
457
|
+
/**
|
|
458
|
+
* 列出技能的所有脚本
|
|
459
|
+
* @param {string} name 技能名称
|
|
460
|
+
*/
|
|
461
|
+
listScripts: (name) => skillManager.listScripts(name) || [],
|
|
462
|
+
/**
|
|
463
|
+
* 获取脚本内容
|
|
464
|
+
* @param {string} name 技能名称
|
|
465
|
+
* @param {string} scriptName 脚本名(含扩展名)
|
|
466
|
+
*/
|
|
467
|
+
getScript: (name, scriptName) => skillManager.loadScript(name, scriptName),
|
|
468
|
+
/**
|
|
469
|
+
* 重载所有技能
|
|
470
|
+
*/
|
|
471
|
+
reload: () => skillManager.reload(self),
|
|
472
|
+
};
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
// workflows 对象别名(工作流引擎)
|
|
476
|
+
get workflows() {
|
|
477
|
+
const self = this;
|
|
478
|
+
const workflowPlugin = this.pluginManager?.get('workflow');
|
|
479
|
+
if (!workflowPlugin) {
|
|
480
|
+
return {
|
|
481
|
+
available: false,
|
|
482
|
+
list: () => [],
|
|
483
|
+
get: () => null,
|
|
484
|
+
has: () => false,
|
|
485
|
+
execute: async () => ({ success: false, error: 'workflow plugin not loaded' }),
|
|
486
|
+
register: () => null,
|
|
487
|
+
remove: () => false,
|
|
488
|
+
reload: () => {},
|
|
489
|
+
};
|
|
490
|
+
}
|
|
491
|
+
return {
|
|
492
|
+
available: true,
|
|
493
|
+
/**
|
|
494
|
+
* 列出所有已加载的工作流
|
|
495
|
+
*/
|
|
496
|
+
list: () => {
|
|
497
|
+
const workflows = [];
|
|
498
|
+
for (const [name, workflow] of workflowPlugin._workflows) {
|
|
499
|
+
workflows.push({
|
|
500
|
+
name,
|
|
501
|
+
description: workflow.description || '',
|
|
502
|
+
stepCount: workflow.steps?.length || 0,
|
|
503
|
+
type: workflow.type || 'sequential',
|
|
504
|
+
});
|
|
505
|
+
}
|
|
506
|
+
return workflows;
|
|
507
|
+
},
|
|
508
|
+
/**
|
|
509
|
+
* 获取单个工作流定义
|
|
510
|
+
*/
|
|
511
|
+
get: (name) => workflowPlugin._workflows.get(name) || null,
|
|
512
|
+
/**
|
|
513
|
+
* 检查工作流是否存在
|
|
514
|
+
*/
|
|
515
|
+
has: (name) => workflowPlugin._workflows.has(name),
|
|
516
|
+
/**
|
|
517
|
+
* 执行工作流
|
|
518
|
+
* @param {string|Object} workflowDef 工作流定义(名称/JSON/JS 代码)
|
|
519
|
+
* @param {Object} input 工作流输入参数
|
|
520
|
+
* @param {string} sessionId 会话 ID(可选)
|
|
521
|
+
*/
|
|
522
|
+
execute: (workflowDef, input = {}, sessionId = null) =>
|
|
523
|
+
workflowPlugin.executeWorkflow(workflowDef, input, sessionId),
|
|
524
|
+
/**
|
|
525
|
+
* 注册工作流定义(程序化注册)
|
|
526
|
+
* @param {string} name 工作流名称
|
|
527
|
+
* @param {Object} workflowDef 工作流定义
|
|
528
|
+
*/
|
|
529
|
+
register: (name, workflowDef) => {
|
|
530
|
+
if (workflowPlugin._workflows.has(name)) {
|
|
531
|
+
return { success: false, error: `Workflow "${name}" already exists` };
|
|
532
|
+
}
|
|
533
|
+
if (!workflowDef || !workflowDef.steps) {
|
|
534
|
+
return { success: false, error: 'Invalid workflow: missing steps' };
|
|
535
|
+
}
|
|
536
|
+
workflowPlugin._workflows.set(name, workflowDef);
|
|
537
|
+
// 注册为工具
|
|
538
|
+
const toolName = `workflow_${name}`;
|
|
539
|
+
const description = workflowDef.description || `执行工作流: ${name}`;
|
|
540
|
+
self.registerTool({
|
|
541
|
+
name: toolName,
|
|
542
|
+
description,
|
|
543
|
+
inputSchema: require('zod').object({
|
|
544
|
+
input: require('zod').object({}).optional().describe('工作流输入参数'),
|
|
545
|
+
}),
|
|
546
|
+
execute: async (args, framework) => {
|
|
547
|
+
let sid = null;
|
|
548
|
+
const ctx = framework?.getExecutionContext?.();
|
|
549
|
+
if (ctx?.sessionId) sid = ctx.sessionId;
|
|
550
|
+
return await workflowPlugin.executeWorkflow(workflowDef, args.input || {}, sid);
|
|
551
|
+
},
|
|
552
|
+
});
|
|
553
|
+
workflowPlugin._workflowTools.set(name, toolName);
|
|
554
|
+
return { success: true, data: { name, toolName } };
|
|
555
|
+
},
|
|
556
|
+
/**
|
|
557
|
+
* 移除工作流
|
|
558
|
+
* @param {string} name 工作流名称
|
|
559
|
+
*/
|
|
560
|
+
remove: (name) => workflowPlugin.remove(name),
|
|
561
|
+
/**
|
|
562
|
+
* 获取工作流引擎
|
|
563
|
+
*/
|
|
564
|
+
getEngine: () => workflowPlugin.getEngine(),
|
|
565
|
+
/**
|
|
566
|
+
* 重载工作流
|
|
567
|
+
*/
|
|
568
|
+
reload: () => workflowPlugin.reload(self),
|
|
569
|
+
};
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
// rules 对象别名(规则引擎)
|
|
573
|
+
get rules() {
|
|
574
|
+
const self = this;
|
|
575
|
+
const rulesPlugin = this.pluginManager?.get('rules');
|
|
576
|
+
if (!rulesPlugin) {
|
|
577
|
+
return {
|
|
578
|
+
available: false,
|
|
579
|
+
list: () => [],
|
|
580
|
+
add: () => null,
|
|
581
|
+
remove: () => false,
|
|
582
|
+
test: () => null,
|
|
583
|
+
stats: () => null,
|
|
584
|
+
};
|
|
585
|
+
}
|
|
586
|
+
return {
|
|
587
|
+
available: true,
|
|
588
|
+
/**
|
|
589
|
+
* 列出所有规则
|
|
590
|
+
*/
|
|
591
|
+
list: () => rulesPlugin._rules.map(r => ({
|
|
592
|
+
name: r.name,
|
|
593
|
+
type: r.type,
|
|
594
|
+
target: r.target,
|
|
595
|
+
description: r.description,
|
|
596
|
+
enabled: r.enabled,
|
|
597
|
+
})),
|
|
598
|
+
/**
|
|
599
|
+
* 添加规则
|
|
600
|
+
* @param {Object} rule 规则定义
|
|
601
|
+
*/
|
|
602
|
+
add: (rule) => {
|
|
603
|
+
const newRule = {
|
|
604
|
+
name: rule.name,
|
|
605
|
+
type: rule.type,
|
|
606
|
+
target: rule.target,
|
|
607
|
+
pattern: rule.pattern ? new RegExp(rule.pattern) : null,
|
|
608
|
+
description: rule.description || '',
|
|
609
|
+
action: rule.action || {},
|
|
610
|
+
enabled: rule.enabled !== false,
|
|
611
|
+
};
|
|
612
|
+
rulesPlugin._rules.push(newRule);
|
|
613
|
+
return newRule;
|
|
614
|
+
},
|
|
615
|
+
/**
|
|
616
|
+
* 移除规则
|
|
617
|
+
*/
|
|
618
|
+
remove: (name) => rulesPlugin.removeRule(name),
|
|
619
|
+
/**
|
|
620
|
+
* 测试规则匹配
|
|
621
|
+
* @param {string} target 目标(工具名或事件类型)
|
|
622
|
+
* @param {Object} input 输入参数
|
|
623
|
+
*/
|
|
624
|
+
test: (target, input = {}) => {
|
|
625
|
+
const result = rulesPlugin.evaluate(target, input);
|
|
626
|
+
return {
|
|
627
|
+
allowed: result.allowed,
|
|
628
|
+
ruleName: result.rule?.name || null,
|
|
629
|
+
ruleType: result.rule?.type || null,
|
|
630
|
+
};
|
|
631
|
+
},
|
|
632
|
+
/**
|
|
633
|
+
* 获取规则统计
|
|
634
|
+
*/
|
|
635
|
+
stats: () => ({ ...rulesPlugin._ruleStats }),
|
|
636
|
+
/**
|
|
637
|
+
* 检查工具调用
|
|
638
|
+
*/
|
|
639
|
+
checkToolCall: (toolName, args) => {
|
|
640
|
+
const result = rulesPlugin.checkToolCall(toolName, args);
|
|
641
|
+
return { allowed: result.allowed, rule: result.rule };
|
|
642
|
+
},
|
|
643
|
+
/**
|
|
644
|
+
* 检查消息
|
|
645
|
+
*/
|
|
646
|
+
checkMessage: (message, context) => {
|
|
647
|
+
const result = rulesPlugin.checkMessage(message, context);
|
|
648
|
+
return { allowed: result.allowed, rule: result.rule };
|
|
649
|
+
},
|
|
650
|
+
/**
|
|
651
|
+
* 重载规则
|
|
652
|
+
*/
|
|
653
|
+
reload: () => rulesPlugin.reload(self),
|
|
654
|
+
};
|
|
262
655
|
}
|
|
263
656
|
|
|
264
657
|
// plugins 对象别名
|
|
@@ -298,7 +691,7 @@ class Framework extends EventEmitter {
|
|
|
298
691
|
}
|
|
299
692
|
|
|
300
693
|
// Extension 对象别名
|
|
301
|
-
get
|
|
694
|
+
get extensions() {
|
|
302
695
|
const self = this;
|
|
303
696
|
return {
|
|
304
697
|
tool: (pluginName, tools) => self.registerExtensionTools(pluginName, tools),
|
|
@@ -331,8 +724,236 @@ class Framework extends EventEmitter {
|
|
|
331
724
|
return this.executeTool('ext_call', { plugin, tool, args });
|
|
332
725
|
}
|
|
333
726
|
|
|
727
|
+
// storage 对象别名(键值存储)
|
|
728
|
+
get storage() {
|
|
729
|
+
const storagePlugin = this.pluginManager?.get('storage');
|
|
730
|
+
if (!storagePlugin) {
|
|
731
|
+
return {
|
|
732
|
+
available: false,
|
|
733
|
+
get: () => null,
|
|
734
|
+
set: () => null,
|
|
735
|
+
delete: () => null,
|
|
736
|
+
list: () => [],
|
|
737
|
+
clear: () => null,
|
|
738
|
+
stats: () => null,
|
|
739
|
+
compact: () => null,
|
|
740
|
+
};
|
|
741
|
+
}
|
|
742
|
+
return {
|
|
743
|
+
available: true,
|
|
744
|
+
/**
|
|
745
|
+
* 获取存储的值
|
|
746
|
+
*/
|
|
747
|
+
get: (key, namespace) => {
|
|
748
|
+
storagePlugin._initStorage();
|
|
749
|
+
const ns = namespace ? `${namespace}:` : '';
|
|
750
|
+
return storagePlugin._storageManager.get(`${ns}${key}`);
|
|
751
|
+
},
|
|
752
|
+
/**
|
|
753
|
+
* 设置存储的值
|
|
754
|
+
*/
|
|
755
|
+
set: (key, value, namespace) => {
|
|
756
|
+
storagePlugin._initStorage();
|
|
757
|
+
const ns = namespace ? `${namespace}:` : '';
|
|
758
|
+
return storagePlugin._storageManager.set(`${ns}${key}`, value);
|
|
759
|
+
},
|
|
760
|
+
/**
|
|
761
|
+
* 删除存储的键(软删除)
|
|
762
|
+
*/
|
|
763
|
+
delete: (key, namespace) => {
|
|
764
|
+
storagePlugin._initStorage();
|
|
765
|
+
const ns = namespace ? `${namespace}:` : '';
|
|
766
|
+
return storagePlugin._storageManager.delete(`${ns}${key}`);
|
|
767
|
+
},
|
|
768
|
+
/**
|
|
769
|
+
* 列出命名空间下的所有键
|
|
770
|
+
*/
|
|
771
|
+
list: (namespace) => {
|
|
772
|
+
storagePlugin._initStorage();
|
|
773
|
+
const ns = namespace ? `${namespace}:` : '';
|
|
774
|
+
return storagePlugin._storageManager.list(ns).map(k => k.substring(ns.length));
|
|
775
|
+
},
|
|
776
|
+
/**
|
|
777
|
+
* 清空命名空间下的所有数据
|
|
778
|
+
*/
|
|
779
|
+
clear: (namespace) => {
|
|
780
|
+
storagePlugin._initStorage();
|
|
781
|
+
const ns = namespace ? `${namespace}:` : '';
|
|
782
|
+
const keys = storagePlugin._storageManager.list(ns);
|
|
783
|
+
let count = 0;
|
|
784
|
+
for (const key of keys) {
|
|
785
|
+
if (storagePlugin._storageManager.delete(key)) count++;
|
|
786
|
+
}
|
|
787
|
+
return { cleared: count };
|
|
788
|
+
},
|
|
789
|
+
/**
|
|
790
|
+
* 获取存储统计信息
|
|
791
|
+
*/
|
|
792
|
+
stats: () => {
|
|
793
|
+
storagePlugin._initStorage();
|
|
794
|
+
return storagePlugin._storageManager.getCompactionStats();
|
|
795
|
+
},
|
|
796
|
+
/**
|
|
797
|
+
* 手动触发 compaction
|
|
798
|
+
*/
|
|
799
|
+
compact: () => storagePlugin._storageManager.compact(),
|
|
800
|
+
/**
|
|
801
|
+
* 获取底层 StorageManager
|
|
802
|
+
*/
|
|
803
|
+
getStore: () => storagePlugin.getStore(),
|
|
804
|
+
};
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
// sessions 对象别名(会话管理)
|
|
808
|
+
get sessions() {
|
|
809
|
+
const self = this;
|
|
810
|
+
return {
|
|
811
|
+
/**
|
|
812
|
+
* 列出所有会话 ID
|
|
813
|
+
*/
|
|
814
|
+
list: () => Array.from(this._sessionContexts.keys()),
|
|
815
|
+
/**
|
|
816
|
+
* 获取会话上下文
|
|
817
|
+
*/
|
|
818
|
+
get: (sessionId) => this.getSessionContext(sessionId),
|
|
819
|
+
/**
|
|
820
|
+
* 创建或获取会话
|
|
821
|
+
*/
|
|
822
|
+
getOrCreate: (sessionId, options) => this.getOrCreateSessionContext(sessionId, options),
|
|
823
|
+
/**
|
|
824
|
+
* 销毁会话
|
|
825
|
+
*/
|
|
826
|
+
destroy: (sessionId) => this.destroySessionContext(sessionId),
|
|
827
|
+
/**
|
|
828
|
+
* 获取当前会话 ID
|
|
829
|
+
*/
|
|
830
|
+
currentId: () => this.getCurrentSessionId(),
|
|
831
|
+
/**
|
|
832
|
+
* 在会话中执行代码
|
|
833
|
+
*/
|
|
834
|
+
runIn: (sessionId, options, fn) => this.runInSession(sessionId, options, fn),
|
|
835
|
+
};
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
// mcps 对象别名(MCP 服务器管理)
|
|
839
|
+
get mcps() {
|
|
840
|
+
const self = this;
|
|
841
|
+
const mcpPlugin = this.pluginManager?.get('mcp');
|
|
842
|
+
if (!mcpPlugin) {
|
|
843
|
+
return {
|
|
844
|
+
available: false,
|
|
845
|
+
list: () => [],
|
|
846
|
+
get: () => null,
|
|
847
|
+
has: () => false,
|
|
848
|
+
addServer: async () => ({ success: false, error: 'mcp plugin not loaded' }),
|
|
849
|
+
removeServer: async () => false,
|
|
850
|
+
call: async () => ({ success: false, error: 'mcp plugin not loaded' }),
|
|
851
|
+
getToolSchema: async () => null,
|
|
852
|
+
setEnabled: async () => null,
|
|
853
|
+
reload: async () => null,
|
|
854
|
+
};
|
|
855
|
+
}
|
|
856
|
+
return {
|
|
857
|
+
available: true,
|
|
858
|
+
/**
|
|
859
|
+
* 列出所有已连接的 MCP 服务器
|
|
860
|
+
*/
|
|
861
|
+
list: () => {
|
|
862
|
+
const servers = [];
|
|
863
|
+
for (const [name, info] of mcpPlugin._clients) {
|
|
864
|
+
servers.push({
|
|
865
|
+
name,
|
|
866
|
+
enabled: true,
|
|
867
|
+
connected: true,
|
|
868
|
+
tools: info.tools.map((t) => ({ name: t.name, description: t.description })),
|
|
869
|
+
});
|
|
870
|
+
}
|
|
871
|
+
for (const [name, config] of mcpPlugin._serverConfigs) {
|
|
872
|
+
if (!mcpPlugin._clients.has(name)) {
|
|
873
|
+
servers.push({ name, enabled: false, connected: false, tools: [] });
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
return servers;
|
|
877
|
+
},
|
|
878
|
+
/**
|
|
879
|
+
* 获取 MCP 服务器信息
|
|
880
|
+
*/
|
|
881
|
+
get: (name) => {
|
|
882
|
+
const clientInfo = mcpPlugin._clients.get(name);
|
|
883
|
+
if (!clientInfo) {
|
|
884
|
+
const config = mcpPlugin._serverConfigs.get(name);
|
|
885
|
+
return config ? { name, enabled: false, connected: false, tools: [] } : null;
|
|
886
|
+
}
|
|
887
|
+
return {
|
|
888
|
+
name,
|
|
889
|
+
enabled: true,
|
|
890
|
+
connected: true,
|
|
891
|
+
tools: clientInfo.tools.map((t) => ({ name: t.name, description: t.description })),
|
|
892
|
+
};
|
|
893
|
+
},
|
|
894
|
+
/**
|
|
895
|
+
* 检查 MCP 服务器是否存在
|
|
896
|
+
*/
|
|
897
|
+
has: (name) => mcpPlugin._clients.has(name) || mcpPlugin._serverConfigs.has(name),
|
|
898
|
+
/**
|
|
899
|
+
* 添加 MCP 服务器
|
|
900
|
+
* @param {Object} config 服务器配置 { name, command, args, env, type, url, headers }
|
|
901
|
+
*/
|
|
902
|
+
add: (config) => mcpPlugin.addServer(config),
|
|
903
|
+
/**
|
|
904
|
+
* 添加 MCP 服务器(add 的别名)
|
|
905
|
+
*/
|
|
906
|
+
addServer: (config) => mcpPlugin.addServer(config),
|
|
907
|
+
/**
|
|
908
|
+
* 移除 MCP 服务器
|
|
909
|
+
*/
|
|
910
|
+
removeServer: async (name) => {
|
|
911
|
+
await mcpPlugin.removeServer(name);
|
|
912
|
+
return true;
|
|
913
|
+
},
|
|
914
|
+
/**
|
|
915
|
+
* 调用 MCP 工具(通过 ext_call 统一调度)
|
|
916
|
+
* @param {string} server 服务器名
|
|
917
|
+
* @param {string} tool 工具名
|
|
918
|
+
* @param {Object} args 参数
|
|
919
|
+
*/
|
|
920
|
+
call: async (server, tool, args) => {
|
|
921
|
+
return await self.executeTool('ext_call', {
|
|
922
|
+
plugin: `mcp:${server}`,
|
|
923
|
+
tool,
|
|
924
|
+
args: args || {},
|
|
925
|
+
});
|
|
926
|
+
},
|
|
927
|
+
/**
|
|
928
|
+
* 获取工具调用参数说明(通过 ext_skill)
|
|
929
|
+
*/
|
|
930
|
+
getToolSchema: async (server, _tool) => {
|
|
931
|
+
return await self.executeTool('ext_skill', { plugin: `mcp:${server}` });
|
|
932
|
+
},
|
|
933
|
+
/**
|
|
934
|
+
* 动态开启/关闭 MCP 服务器
|
|
935
|
+
*/
|
|
936
|
+
setEnabled: async (server, enabled) => {
|
|
937
|
+
return await self.executeTool('mcp_set_enabled', { server, enabled });
|
|
938
|
+
},
|
|
939
|
+
/**
|
|
940
|
+
* 重载 MCP 配置
|
|
941
|
+
*/
|
|
942
|
+
reload: async () => {
|
|
943
|
+
return await self.executeTool('mcp_reload', {});
|
|
944
|
+
},
|
|
945
|
+
};
|
|
946
|
+
}
|
|
947
|
+
|
|
334
948
|
getMainAgent() { return this._mainAgent; }
|
|
335
|
-
|
|
949
|
+
/**
|
|
950
|
+
* 获取 main agent 的完整系统提示词
|
|
951
|
+
* 统一走 framework.prompts.build()(含 _originalPrompt + 注册表所有 parts)
|
|
952
|
+
* 这是 LLM 实际看到的完整 system prompt
|
|
953
|
+
*/
|
|
954
|
+
getSystemPrompt() {
|
|
955
|
+
return this.prompts?.build?.() || '';
|
|
956
|
+
}
|
|
336
957
|
getPluginInstance(name) { return this.pluginManager.get(name) || null; }
|
|
337
958
|
getAgents() { return [...this._agents]; }
|
|
338
959
|
listSessionContexts() { return Array.from(this._sessionContexts.keys()); }
|
|
@@ -393,7 +1014,10 @@ class Framework extends EventEmitter {
|
|
|
393
1014
|
const { SessionManager } = require('../session/session');
|
|
394
1015
|
manager = SessionManager.open(sessionFile, sessionDir, cwd);
|
|
395
1016
|
}
|
|
396
|
-
} catch {
|
|
1017
|
+
} catch (err) {
|
|
1018
|
+
// Log error but continue - will create new session if load failed
|
|
1019
|
+
this.logger.warn(`[Framework] Failed to load session '${sessionId}': ${err.message}`);
|
|
1020
|
+
}
|
|
397
1021
|
if (!manager) {
|
|
398
1022
|
const { SessionManager } = require('../session/session');
|
|
399
1023
|
manager = new SessionManager(cwd, sessionDir, sessionFile, true);
|
|
@@ -418,7 +1042,9 @@ class Framework extends EventEmitter {
|
|
|
418
1042
|
this._sessionContexts.set(sessionId, manager);
|
|
419
1043
|
this.emit('session:context-created', { sessionId, manager });
|
|
420
1044
|
}
|
|
421
|
-
} catch {
|
|
1045
|
+
} catch (err) {
|
|
1046
|
+
this.logger.warn(`[Framework] Failed to load session context '${sessionId}': ${err.message}`);
|
|
1047
|
+
}
|
|
422
1048
|
return manager || null;
|
|
423
1049
|
}
|
|
424
1050
|
|
package/src/index.js
CHANGED
|
@@ -11,6 +11,10 @@
|
|
|
11
11
|
// 加载工作目录 .env 配置(优先于系统环境变量)
|
|
12
12
|
require('dotenv').config({ override: true });
|
|
13
13
|
|
|
14
|
+
// 安装全局错误捕获:把 unhandledRejection / uncaughtException 的完整堆栈
|
|
15
|
+
// 落盘到 .foliko/error.log。需要堆栈时直接 cat 这个文件即可。
|
|
16
|
+
require('./common/error-capture').install();
|
|
17
|
+
|
|
14
18
|
// ============================================================
|
|
15
19
|
// Framework
|
|
16
20
|
// ============================================================
|