foliko 2.0.1 → 2.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -6
- package/docs/public-api.md +91 -0
- package/docs/system-prompt.md +219 -0
- package/docs/usage.md +6 -6
- package/package.json +1 -1
- package/plugins/ambient/ExplorerLoop.js +1 -0
- package/plugins/ambient/index.js +5 -0
- package/plugins/core/coordinator/index.js +11 -6
- package/plugins/core/default/bootstrap.js +21 -3
- package/plugins/core/default/config.js +12 -3
- package/plugins/core/python-loader/index.js +3 -3
- package/plugins/core/scheduler/index.js +26 -2
- package/plugins/core/skill-manager/index.js +198 -151
- package/plugins/core/sub-agent/index.js +34 -15
- package/plugins/core/think/index.js +1 -0
- package/plugins/core/workflow/index.js +5 -4
- package/plugins/executors/data-splitter/index.js +14 -1
- package/plugins/executors/extension/index.js +51 -37
- package/plugins/executors/python/index.js +3 -3
- package/plugins/executors/shell/index.js +6 -4
- package/plugins/io/web/index.js +2 -1
- package/plugins/memory/index.js +29 -74
- package/plugins/messaging/email/handlers.js +1 -19
- package/plugins/messaging/email/monitor.js +2 -17
- package/plugins/messaging/email/reply.js +2 -1
- package/plugins/messaging/email/utils.js +20 -1
- package/plugins/messaging/feishu/index.js +1 -1
- package/plugins/messaging/qq/index.js +1 -1
- package/plugins/messaging/telegram/index.js +5 -1
- package/plugins/messaging/weixin/index.js +1 -1
- package/plugins/plugin-manager/index.js +1 -33
- package/plugins/tools/index.js +14 -1
- package/skills/plugins/SKILL.md +316 -0
- package/skills/skill-guide/SKILL.md +5 -5
- package/skills/{subagent-guide → subagents}/SKILL.md +1 -1
- package/skills/{workflow-guide → workflows}/SKILL.md +3 -3
- package/skills/{workflow-troubleshooting → workflows/workflow-troubleshooting}/DEBUGGING.md +197 -197
- package/skills/{workflow-troubleshooting → workflows/workflow-troubleshooting}/SKILL.md +391 -391
- package/src/agent/base.js +5 -20
- package/src/agent/index.js +20 -8
- package/src/agent/main.js +100 -23
- package/src/agent/prompt-registry.js +296 -0
- package/src/agent/sub-config.js +1 -78
- package/src/agent/sub.js +19 -24
- package/src/cli/commands/plugin.js +1 -60
- package/src/cli/ui/chat-ui-old.js +1 -1
- package/src/cli/ui/chat-ui.js +1 -1
- package/src/cli/ui/components/agent-mention-provider.js +1 -1
- package/src/common/constants.js +42 -0
- package/src/common/id.js +13 -0
- package/src/common/queue.js +2 -2
- package/src/context/compressor.js +17 -9
- package/src/framework/framework.js +185 -0
- package/src/framework/index.js +1 -2
- package/src/framework/lifecycle.js +102 -1
- package/src/framework/loader.js +1 -55
- package/src/index.js +11 -2
- package/src/plugin/base.js +69 -55
- package/src/plugin/loader.js +1 -57
- package/src/plugin/manager.js +13 -4
- package/src/session/entry.js +1 -11
- package/src/storage/manager.js +1 -12
- package/src/tool/executor.js +2 -1
- package/src/tool/router.js +5 -5
- package/src/utils/data-splitter.js +2 -1
- package/src/utils/index.js +150 -0
- package/src/utils/message-validator.js +21 -17
- package/src/utils/plugin-helpers.js +19 -5
- package/subagent.md +2 -2
- package/tests/core/plugin-prompts.test.js +219 -0
- package/tests/core/prompt-registry.test.js +209 -0
- package/src/cli/utils/plugin-config.js +0 -50
- package/src/config/plugin-config.js +0 -50
- /package/skills/{ambient-agent → ambient}/SKILL.md +0 -0
- /package/skills/{foliko-dev → foliko}/AGENTS.md +0 -0
- /package/skills/{foliko-dev → foliko}/SKILL.md +0 -0
- /package/skills/{mcp-usage → mcp}/SKILL.md +0 -0
- /package/skills/{plugin-guide → plugins-guide}/SKILL.md +0 -0
- /package/skills/{python-plugin-dev → python}/SKILL.md +0 -0
|
@@ -173,7 +173,7 @@ class StepExecutor {
|
|
|
173
173
|
// 解析工具参数,支持变量引用
|
|
174
174
|
let resolvedArgs = this._resolveArgs(step.args || {}, context);
|
|
175
175
|
// 兼容旧格式:处理 step.input 字段(${stepId.output} 引用)
|
|
176
|
-
// 将 input 注入到 args.input_data,供
|
|
176
|
+
// 将 input 注入到 args.input_data,供 py_execute 使用
|
|
177
177
|
if (step.input) {
|
|
178
178
|
const resolvedInput = this._resolveArgs(step.input, context);
|
|
179
179
|
resolvedArgs.input_data = resolvedInput;
|
|
@@ -344,6 +344,7 @@ class StepExecutor {
|
|
|
344
344
|
const messageAgent = this.framework.createSubAgent({
|
|
345
345
|
name: 'workflow_message',
|
|
346
346
|
role: '工作流任务执行助手,专注于处理工作流中的消息任务',
|
|
347
|
+
hidden: true
|
|
347
348
|
});
|
|
348
349
|
let content = step.content || '';
|
|
349
350
|
// 支持变量引用
|
|
@@ -863,9 +864,9 @@ class WorkflowPlugin extends Plugin {
|
|
|
863
864
|
start(framework) {
|
|
864
865
|
this._loadWorkflows();
|
|
865
866
|
this._registerWorkflowTools();
|
|
866
|
-
// 注册
|
|
867
|
+
// 注册 workflow_reload 工具
|
|
867
868
|
framework.registerTool({
|
|
868
|
-
name: '
|
|
869
|
+
name: 'workflow_reload',
|
|
869
870
|
description: '重载所有工作流,当用户添加或修改工作流后调用此工具',
|
|
870
871
|
inputSchema: z.object({}),
|
|
871
872
|
execute: async () => {
|
|
@@ -880,7 +881,7 @@ class WorkflowPlugin extends Plugin {
|
|
|
880
881
|
|
|
881
882
|
// 注册列出工作流的工具
|
|
882
883
|
framework.registerTool({
|
|
883
|
-
name: '
|
|
884
|
+
name: 'workflow_list',
|
|
884
885
|
description: '列出所有已加载的工作流及其描述信息',
|
|
885
886
|
inputSchema: z.object({}),
|
|
886
887
|
execute: async () => {
|
|
@@ -39,9 +39,22 @@ class DataSplitterPlugin extends Plugin {
|
|
|
39
39
|
return this;
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
+
// 声明式 prompt part
|
|
43
|
+
prompts = [
|
|
44
|
+
{
|
|
45
|
+
name: 'data-splitter-rules',
|
|
46
|
+
slot: 'BEHAVIOR',
|
|
47
|
+
description: '大工具结果自动分拆规则',
|
|
48
|
+
provider: function () {
|
|
49
|
+
return this._getPromptRules();
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
];
|
|
53
|
+
|
|
42
54
|
start(framework) {
|
|
55
|
+
super.start(framework);
|
|
43
56
|
this._registerSplitTools();
|
|
44
|
-
|
|
57
|
+
// prompt part 由基类 _autoRegisterPrompts 自动处理
|
|
45
58
|
this._registerAutoSplitHook();
|
|
46
59
|
}
|
|
47
60
|
|
|
@@ -12,6 +12,18 @@ const { zodSchemaToMarkdown, zodSchemaToTable } = require('@chnak/zod-to-markdow
|
|
|
12
12
|
const log = logger.child('ExtensionExecutor');
|
|
13
13
|
|
|
14
14
|
class ExtensionExecutorPlugin extends Plugin {
|
|
15
|
+
// 声明式 prompt parts
|
|
16
|
+
prompts = [
|
|
17
|
+
{
|
|
18
|
+
name: 'extension-tools',
|
|
19
|
+
slot: 'CAPABILITY',
|
|
20
|
+
description: '所有扩展工具的说明和使用方式',
|
|
21
|
+
provider: function () {
|
|
22
|
+
return this._buildExtensionsDescription();
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
];
|
|
26
|
+
|
|
15
27
|
constructor(config = {}) {
|
|
16
28
|
super();
|
|
17
29
|
this.name = 'extension-executor';
|
|
@@ -35,6 +47,8 @@ class ExtensionExecutorPlugin extends Plugin {
|
|
|
35
47
|
this._rescanPluginTools(plugin);
|
|
36
48
|
});
|
|
37
49
|
|
|
50
|
+
// 不监听 tool:registered — 直接 registerTool 注册的工具通过 AI SDK 暴露给 LLM,无需出现在 Extensions 中
|
|
51
|
+
|
|
38
52
|
framework.on('framework:ready', () => {
|
|
39
53
|
log.debug('Framework ready, rescanning all plugin tools...');
|
|
40
54
|
const plugins = framework.pluginManager.getAll();
|
|
@@ -44,7 +58,7 @@ class ExtensionExecutorPlugin extends Plugin {
|
|
|
44
58
|
}
|
|
45
59
|
this._scanPluginTools(plugin);
|
|
46
60
|
}
|
|
47
|
-
this.
|
|
61
|
+
this._invalidatePromptCaches(framework);
|
|
48
62
|
});
|
|
49
63
|
|
|
50
64
|
return this;
|
|
@@ -80,7 +94,7 @@ class ExtensionExecutorPlugin extends Plugin {
|
|
|
80
94
|
log.debug(` Scanned ${Object.keys(plugin.tools).length} tools from plugin '${pluginName}' (as '${useExtName}')`);
|
|
81
95
|
|
|
82
96
|
if (this._framework && this._framework._ready) {
|
|
83
|
-
this.
|
|
97
|
+
this._invalidatePromptCaches(this._framework);
|
|
84
98
|
}
|
|
85
99
|
}
|
|
86
100
|
|
|
@@ -111,6 +125,7 @@ class ExtensionExecutorPlugin extends Plugin {
|
|
|
111
125
|
}
|
|
112
126
|
|
|
113
127
|
async start(framework) {
|
|
128
|
+
super.start(framework);
|
|
114
129
|
const plugins = framework.pluginManager.getAll();
|
|
115
130
|
for (const { instance: plugin } of plugins) {
|
|
116
131
|
if (plugin.name === 'skill-manager') {
|
|
@@ -236,25 +251,21 @@ class ExtensionExecutorPlugin extends Plugin {
|
|
|
236
251
|
execute: async (args) => {
|
|
237
252
|
const { plugin } = args;
|
|
238
253
|
if (plugin === 'skill') {
|
|
239
|
-
return "请使用 `
|
|
254
|
+
return "请使用 `skill_load` 获取skill的使用详情"
|
|
240
255
|
}
|
|
241
256
|
return this.bindAllToolSkill(plugin)
|
|
242
257
|
},
|
|
243
258
|
});
|
|
244
259
|
|
|
245
260
|
framework.on('agent:created', (agent) => {
|
|
246
|
-
|
|
261
|
+
// Extension tool 描述已通过 prompts 声明式注册到 framework.prompts,
|
|
262
|
+
// 并由 _syncPromptParts 同步到子 Agent,无需再注入 _originalPrompt
|
|
247
263
|
});
|
|
248
264
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
} else {
|
|
252
|
-
framework.once('framework:ready', () => {
|
|
253
|
-
this._refreshAllAgentsExtPrompt(framework);
|
|
254
|
-
});
|
|
255
|
-
}
|
|
265
|
+
// Extension tool 描述已通过 prompts 声明式注册到 framework.prompts,
|
|
266
|
+
// 不再需要 _refreshAllAgentsExtPrompt 注入 _originalPrompt
|
|
256
267
|
|
|
257
|
-
|
|
268
|
+
// prompt part 由基类 _autoRegisterPrompts 自动处理
|
|
258
269
|
|
|
259
270
|
return this;
|
|
260
271
|
}
|
|
@@ -294,7 +305,7 @@ class ExtensionExecutorPlugin extends Plugin {
|
|
|
294
305
|
log.debug(` Registered tool '${toolDef.name}' for extension '${pluginName}'`);
|
|
295
306
|
|
|
296
307
|
if (this._framework && this._framework._ready) {
|
|
297
|
-
this.
|
|
308
|
+
this._invalidatePromptCaches(this._framework);
|
|
298
309
|
}
|
|
299
310
|
}
|
|
300
311
|
|
|
@@ -339,32 +350,17 @@ class ExtensionExecutorPlugin extends Plugin {
|
|
|
339
350
|
return sections.join('\n')
|
|
340
351
|
}
|
|
341
352
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
traverse(subAgentInfo.agent);
|
|
351
|
-
}
|
|
352
|
-
};
|
|
353
|
-
for (const agent of framework._agents || []) {
|
|
354
|
-
traverse(agent);
|
|
353
|
+
/**
|
|
354
|
+
* 失效所有 Agent 的延展工具 prompt 缓存
|
|
355
|
+
* 不再注入 _originalPrompt,仅失效缓存让下次 build 时自动读取最新 _buildExtensionsDescription()
|
|
356
|
+
*/
|
|
357
|
+
_invalidatePromptCaches(framework) {
|
|
358
|
+
framework?.prompts?.invalidate(this.name, 'extension-tools');
|
|
359
|
+
for (const agent of framework?._agents || []) {
|
|
360
|
+
agent._invalidateSystemPromptCache?.();
|
|
355
361
|
}
|
|
356
362
|
}
|
|
357
363
|
|
|
358
|
-
_refreshAgentExtPrompt(agent) {
|
|
359
|
-
const extDesc = this._buildExtensionsDescription();
|
|
360
|
-
if (!extDesc) return;
|
|
361
|
-
|
|
362
|
-
let existingPrompt = agent._originalPrompt || '';
|
|
363
|
-
|
|
364
|
-
const extRegex = /## 【Extensions】[\s\S]*?(?=\n## 【|$)/;
|
|
365
|
-
existingPrompt = existingPrompt.replace(extRegex, '').trim();
|
|
366
|
-
}
|
|
367
|
-
|
|
368
364
|
_buildExtensionsDescription() {
|
|
369
365
|
let desc = '';
|
|
370
366
|
|
|
@@ -497,6 +493,24 @@ class ExtensionExecutorPlugin extends Plugin {
|
|
|
497
493
|
}));
|
|
498
494
|
}
|
|
499
495
|
|
|
496
|
+
// 获取指定插件的所有工具
|
|
497
|
+
getExtensionTools(pluginName) {
|
|
498
|
+
const ext = this._extensions.get(pluginName);
|
|
499
|
+
return ext ? ext.tools : [];
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
// 获取指定插件的指定工具
|
|
503
|
+
getExtensionTool(pluginName, toolName) {
|
|
504
|
+
const ext = this._extensions.get(pluginName);
|
|
505
|
+
if (!ext) return null;
|
|
506
|
+
return ext.tools.find(t => t.name === toolName) || null;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
// 获取所有插件名称
|
|
510
|
+
getExtensionNames() {
|
|
511
|
+
return Array.from(this._extensions.keys());
|
|
512
|
+
}
|
|
513
|
+
|
|
500
514
|
getSkillCommands() {
|
|
501
515
|
const ext = this._extensions.get('skill');
|
|
502
516
|
if (!ext) return [];
|
|
@@ -547,7 +561,7 @@ class ExtensionExecutorPlugin extends Plugin {
|
|
|
547
561
|
this._rescanPluginTools(plugin);
|
|
548
562
|
}
|
|
549
563
|
this._mcpExecutor = framework.pluginManager?.get('mcp') || null;
|
|
550
|
-
this.
|
|
564
|
+
this._invalidatePromptCaches(framework);
|
|
551
565
|
|
|
552
566
|
await this._reloadMCPConfig(framework);
|
|
553
567
|
}
|
|
@@ -46,7 +46,7 @@ class PythonExecutorPlugin extends Plugin {
|
|
|
46
46
|
|
|
47
47
|
start(framework) {
|
|
48
48
|
framework.registerTool({
|
|
49
|
-
name: '
|
|
49
|
+
name: 'py_execute',
|
|
50
50
|
description: '执行 Python 代码,禁止传入无用的emoji',
|
|
51
51
|
inputSchema: z.object({
|
|
52
52
|
code: z.string().describe('Python 代码'),
|
|
@@ -59,7 +59,7 @@ class PythonExecutorPlugin extends Plugin {
|
|
|
59
59
|
})
|
|
60
60
|
|
|
61
61
|
framework.registerTool({
|
|
62
|
-
name: '
|
|
62
|
+
name: 'py_script',
|
|
63
63
|
description: '执行 Python 脚本文件',
|
|
64
64
|
inputSchema: z.object({
|
|
65
65
|
scriptPath: z.string().describe('Python 脚本文件路径'),
|
|
@@ -72,7 +72,7 @@ class PythonExecutorPlugin extends Plugin {
|
|
|
72
72
|
})
|
|
73
73
|
|
|
74
74
|
framework.registerTool({
|
|
75
|
-
name: '
|
|
75
|
+
name: 'py_pip_install',
|
|
76
76
|
description: '安装 Python 包',
|
|
77
77
|
inputSchema: z.object({
|
|
78
78
|
package: z.string().describe('包名 (例如: numpy, pandas>=1.0)'),
|
|
@@ -34,9 +34,8 @@ class ShellExecutorPlugin extends Plugin {
|
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
start(framework) {
|
|
37
|
-
// 注册 shell
|
|
38
|
-
|
|
39
|
-
name: 'shell',
|
|
37
|
+
// 注册 shell 工具(同时注册新旧两个名字以兼容旧代码)
|
|
38
|
+
const shellExecHandler = {
|
|
40
39
|
description: '执行 Shell 命令',
|
|
41
40
|
inputSchema: z.object({
|
|
42
41
|
command: z.string().describe('要执行的命令'),
|
|
@@ -108,7 +107,10 @@ class ShellExecutorPlugin extends Plugin {
|
|
|
108
107
|
}, timeout)
|
|
109
108
|
})
|
|
110
109
|
}
|
|
111
|
-
}
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
framework.registerTool({ name: 'shell_exec', ...shellExecHandler });
|
|
113
|
+
framework.registerTool({ name: 'shell', ...shellExecHandler });
|
|
112
114
|
|
|
113
115
|
if (process.platform === 'win32') {
|
|
114
116
|
framework.registerTool({
|
package/plugins/io/web/index.js
CHANGED
|
@@ -455,7 +455,8 @@ class WebPlugin extends Plugin {
|
|
|
455
455
|
|
|
456
456
|
const webhookAgent = this._framework.createSubAgent({
|
|
457
457
|
name: 'webhook_handler',
|
|
458
|
-
role: 'Webhook处理助手,专注于处理webhook数据并生成适当响应'
|
|
458
|
+
role: 'Webhook处理助手,专注于处理webhook数据并生成适当响应',
|
|
459
|
+
hidden: true
|
|
459
460
|
})
|
|
460
461
|
|
|
461
462
|
if (!webhook.awaitResponse) {
|
package/plugins/memory/index.js
CHANGED
|
@@ -10,7 +10,7 @@ const { z } = require('zod')
|
|
|
10
10
|
const fs = require('fs')
|
|
11
11
|
const path = require('path')
|
|
12
12
|
const { EventEmitter } = require('events')
|
|
13
|
-
const {cleanResponse} =require('../../src/utils')
|
|
13
|
+
const {cleanResponse, parseFrontmatter} =require('../../src/utils')
|
|
14
14
|
/**
|
|
15
15
|
* 记忆类型常量
|
|
16
16
|
*/
|
|
@@ -26,64 +26,6 @@ const MEMORY_TYPES = {
|
|
|
26
26
|
*/
|
|
27
27
|
const MEMORY_BASE_DIR = '.foliko/memory'
|
|
28
28
|
|
|
29
|
-
/**
|
|
30
|
-
* 解析 YAML frontmatter(从 skill-manager 复用)
|
|
31
|
-
*/
|
|
32
|
-
function parseFrontmatter(content) {
|
|
33
|
-
const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/)
|
|
34
|
-
if (!match) return null
|
|
35
|
-
|
|
36
|
-
const frontmatter = {}
|
|
37
|
-
const lines = match[1].split('\n')
|
|
38
|
-
let currentKey = null
|
|
39
|
-
|
|
40
|
-
for (const line of lines) {
|
|
41
|
-
if (currentKey === 'metadata') {
|
|
42
|
-
if (line.match(/^ {2}[a-zA-Z]/)) {
|
|
43
|
-
const colonIndex = line.indexOf(':')
|
|
44
|
-
if (colonIndex > 0) {
|
|
45
|
-
const key = line.substring(2, colonIndex).trim()
|
|
46
|
-
const value = line.substring(colonIndex + 1).trim().replace(/^["']|["']$/g, '')
|
|
47
|
-
frontmatter[currentKey][key] = value
|
|
48
|
-
continue
|
|
49
|
-
}
|
|
50
|
-
} else if (line.match(/^[a-zA-Z]/)) {
|
|
51
|
-
currentKey = null
|
|
52
|
-
continue
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
const colonIndex = line.indexOf(':')
|
|
57
|
-
if (colonIndex === -1) continue
|
|
58
|
-
|
|
59
|
-
const key = line.substring(0, colonIndex).trim()
|
|
60
|
-
let value = line.substring(colonIndex + 1).trim()
|
|
61
|
-
|
|
62
|
-
if (
|
|
63
|
-
(value.startsWith('"') && value.endsWith('"')) ||
|
|
64
|
-
(value.startsWith("'") && value.endsWith("'"))
|
|
65
|
-
) {
|
|
66
|
-
value = value.slice(1, -1)
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
if (key === 'metadata') {
|
|
70
|
-
currentKey = 'metadata'
|
|
71
|
-
frontmatter.metadata = {}
|
|
72
|
-
} else if (key === 'tags') {
|
|
73
|
-
// 去除首尾空白和方括号
|
|
74
|
-
value = value.trim().replace(/^\[|\]$/g, '')
|
|
75
|
-
frontmatter[key] = value
|
|
76
|
-
.split(',')
|
|
77
|
-
.map(t => t.trim().replace(/^["']|["']$/g, ''))
|
|
78
|
-
.filter(t => t)
|
|
79
|
-
} else {
|
|
80
|
-
frontmatter[key] = value
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
return frontmatter
|
|
85
|
-
}
|
|
86
|
-
|
|
87
29
|
/**
|
|
88
30
|
* 序列化 frontmatter
|
|
89
31
|
*/
|
|
@@ -575,13 +517,14 @@ class MemoryPlugin extends Plugin {
|
|
|
575
517
|
// 初始化存储
|
|
576
518
|
this._store = new MemoryStore(this.config.memoryDir, this._framework?.getCwd?.())
|
|
577
519
|
|
|
578
|
-
// 创建复用的记忆提取子 Agent
|
|
520
|
+
// 创建复用的记忆提取子 Agent(隐藏,不在指令系统中显示)
|
|
579
521
|
this._memoryAgent = framework.createSubAgent({
|
|
580
522
|
name: 'memory-extractor',
|
|
581
523
|
role: '记忆提取专家',
|
|
582
524
|
description: '专门负责从对话和错误中提取记忆的子 Agent',
|
|
583
525
|
tools: {}, // 不需要额外工具,隔离主 Agent 工具
|
|
584
|
-
parentTools: [] // 不继承任何工具,纯粹做提取
|
|
526
|
+
parentTools: [], // 不继承任何工具,纯粹做提取
|
|
527
|
+
hidden: true, // 隐藏子Agent,不在指令系统中显示
|
|
585
528
|
})
|
|
586
529
|
|
|
587
530
|
// 注册工具
|
|
@@ -1166,25 +1109,28 @@ ${prompt}`
|
|
|
1166
1109
|
|
|
1167
1110
|
/**
|
|
1168
1111
|
* 注册 memory-context 到系统提示
|
|
1112
|
+
* 使用 framework.prompts 新 API,slot=CONTEXT
|
|
1169
1113
|
*/
|
|
1170
1114
|
_registerMemoryContext(framework) {
|
|
1171
|
-
const
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1115
|
+
const reg = framework?.prompts
|
|
1116
|
+
const doRegister = () => {
|
|
1117
|
+
if (!reg) return
|
|
1118
|
+
reg.register(this.name, 'memory-context', () => this._buildMemoryContext(), {
|
|
1119
|
+
slot: 'CONTEXT',
|
|
1120
|
+
description: '长期记忆上下文摘要',
|
|
1175
1121
|
})
|
|
1176
|
-
mainAgent._refreshContext()
|
|
1177
|
-
}
|
|
1178
|
-
|
|
1179
|
-
framework.on('framework:ready', () => {
|
|
1180
1122
|
const agent = framework._mainAgent
|
|
1181
|
-
if (agent) {
|
|
1182
|
-
agent.
|
|
1183
|
-
return this._buildMemoryContext()
|
|
1184
|
-
})
|
|
1123
|
+
if (agent && typeof agent._invalidateSystemPromptCache === 'function') {
|
|
1124
|
+
agent._invalidateSystemPromptCache()
|
|
1185
1125
|
agent._refreshContext()
|
|
1186
1126
|
}
|
|
1187
|
-
}
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1129
|
+
if (framework._ready) {
|
|
1130
|
+
doRegister()
|
|
1131
|
+
} else {
|
|
1132
|
+
framework.on('framework:ready', doRegister)
|
|
1133
|
+
}
|
|
1188
1134
|
}
|
|
1189
1135
|
|
|
1190
1136
|
/**
|
|
@@ -1225,14 +1171,23 @@ ${prompt}`
|
|
|
1225
1171
|
|
|
1226
1172
|
if (userMemories.length > 0) {
|
|
1227
1173
|
parts.push('### 【用户偏好】')
|
|
1174
|
+
const seen = new Set()
|
|
1228
1175
|
for (const m of userMemories) {
|
|
1176
|
+
// 去重:跳过前 30 字相似的内容
|
|
1177
|
+
const key = (m.body || '').trim().slice(0, 30)
|
|
1178
|
+
if (seen.has(key)) continue
|
|
1179
|
+
seen.add(key)
|
|
1229
1180
|
parts.push(`- ${m.name}: ${m.body.substring(0, 150)}${m.body.length > 150 ? '...' : ''}`)
|
|
1230
1181
|
}
|
|
1231
1182
|
}
|
|
1232
1183
|
|
|
1233
1184
|
if (projectMemories.length > 0) {
|
|
1234
1185
|
parts.push('### 【项目上下文】')
|
|
1186
|
+
const seen = new Set()
|
|
1235
1187
|
for (const m of projectMemories) {
|
|
1188
|
+
const key = (m.body || '').trim().slice(0, 30)
|
|
1189
|
+
if (seen.has(key)) continue
|
|
1190
|
+
seen.add(key)
|
|
1236
1191
|
const projectTag = m.project ? `[${m.project}] ` : ''
|
|
1237
1192
|
parts.push(`- ${projectTag}${m.name}: ${m.body.substring(0, 100)}${m.body.length > 100 ? '...' : ''}`)
|
|
1238
1193
|
}
|
|
@@ -4,25 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
const Imap = require('imap-mkl')
|
|
6
6
|
const { simpleParser } = require('mailparser')
|
|
7
|
-
const { getConfig, processAttachment } = require('./utils')
|
|
8
|
-
const { DEFAULT_IMAP, IMAP_CLIENT_INFO } = require('./constants')
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* 创建 IMAP 配置
|
|
12
|
-
* @param {Object} config - 用户配置
|
|
13
|
-
* @returns {Object} IMAP 配置
|
|
14
|
-
*/
|
|
15
|
-
function createImapConfig(config = {}) {
|
|
16
|
-
const cfg = getConfig(config)
|
|
17
|
-
return {
|
|
18
|
-
...DEFAULT_IMAP,
|
|
19
|
-
user: cfg.user,
|
|
20
|
-
password: cfg.password,
|
|
21
|
-
host: cfg.host,
|
|
22
|
-
port: cfg.port,
|
|
23
|
-
id: IMAP_CLIENT_INFO
|
|
24
|
-
}
|
|
25
|
-
}
|
|
7
|
+
const { getConfig, processAttachment, createImapConfig } = require('./utils')
|
|
26
8
|
|
|
27
9
|
/**
|
|
28
10
|
* 发送邮件
|
|
@@ -4,23 +4,8 @@
|
|
|
4
4
|
|
|
5
5
|
const Imap = require('imap-mkl')
|
|
6
6
|
const { simpleParser } = require('mailparser')
|
|
7
|
-
const {
|
|
8
|
-
const { getConfig, generateCallId } = require('./utils')
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* 创建 IMAP 配置
|
|
12
|
-
*/
|
|
13
|
-
function createImapConfig(config = {}) {
|
|
14
|
-
const cfg = getConfig(config)
|
|
15
|
-
return {
|
|
16
|
-
...DEFAULT_IMAP,
|
|
17
|
-
user: cfg.user,
|
|
18
|
-
password: cfg.password,
|
|
19
|
-
host: cfg.host,
|
|
20
|
-
port: cfg.port,
|
|
21
|
-
id: IMAP_CLIENT_INFO
|
|
22
|
-
}
|
|
23
|
-
}
|
|
7
|
+
const { EMAIL_DEFAULTS } = require('./constants')
|
|
8
|
+
const { getConfig, generateCallId, createImapConfig } = require('./utils')
|
|
24
9
|
|
|
25
10
|
/**
|
|
26
11
|
* 标记所有现有未读邮件为已读(启动监控前调用)
|
|
@@ -39,7 +39,8 @@ async function handleAutoReply(emailPlugin, args) {
|
|
|
39
39
|
const replyAgent = emailPlugin._framework.createSubAgent({
|
|
40
40
|
name: 'email_replier',
|
|
41
41
|
role: '邮件自动回复助手,专注于生成专业、礼貌、简洁的邮件回复',
|
|
42
|
-
parentTools: []
|
|
42
|
+
parentTools: [],
|
|
43
|
+
hidden: true
|
|
43
44
|
})
|
|
44
45
|
|
|
45
46
|
// 构建提示让 LLM 生成回复
|
|
@@ -114,11 +114,30 @@ function generateCallId() {
|
|
|
114
114
|
return `${Date.now()}_${Math.random().toString(36).substr(2, 5)}`
|
|
115
115
|
}
|
|
116
116
|
|
|
117
|
+
/**
|
|
118
|
+
* 创建 IMAP 配置
|
|
119
|
+
* @param {Object} config - 配置对象
|
|
120
|
+
* @returns {Object} IMAP 配置
|
|
121
|
+
*/
|
|
122
|
+
function createImapConfig(config = {}) {
|
|
123
|
+
const cfg = getConfig(config)
|
|
124
|
+
const { DEFAULT_IMAP, IMAP_CLIENT_INFO } = require('./constants')
|
|
125
|
+
return {
|
|
126
|
+
...DEFAULT_IMAP,
|
|
127
|
+
user: cfg.user,
|
|
128
|
+
password: cfg.password,
|
|
129
|
+
host: cfg.host,
|
|
130
|
+
port: cfg.port,
|
|
131
|
+
id: IMAP_CLIENT_INFO
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
117
135
|
module.exports = {
|
|
118
136
|
isValidEmail,
|
|
119
137
|
parseEmailAddress,
|
|
120
138
|
getConfig,
|
|
121
139
|
fetchUrl,
|
|
122
140
|
processAttachment,
|
|
123
|
-
generateCallId
|
|
141
|
+
generateCallId,
|
|
142
|
+
createImapConfig
|
|
124
143
|
}
|
|
@@ -212,7 +212,7 @@ class FeishuPlugin extends Plugin {
|
|
|
212
212
|
break
|
|
213
213
|
case 'reload':
|
|
214
214
|
try {
|
|
215
|
-
await this._framework.executeTool('
|
|
215
|
+
await this._framework.executeTool('skill_reload', {})
|
|
216
216
|
await this._sendMessage(openId, '✅ 技能已重载', originalMsg)
|
|
217
217
|
} catch (err) {
|
|
218
218
|
await this._sendMessage(openId, '❌ 重载失败: ' + err.message, originalMsg)
|
|
@@ -695,7 +695,7 @@ class QQPlugin extends Plugin {
|
|
|
695
695
|
break
|
|
696
696
|
case 'reload':
|
|
697
697
|
try {
|
|
698
|
-
await this._framework.executeTool('
|
|
698
|
+
await this._framework.executeTool('skill_reload', {})
|
|
699
699
|
await this._sendMessage(openid, '✅ 技能已重载')
|
|
700
700
|
} catch (err) {
|
|
701
701
|
await this._sendMessage(openid, '❌ 重载失败: ' + err.message)
|
|
@@ -56,6 +56,7 @@ class TelegramPlugin extends Plugin {
|
|
|
56
56
|
this._sessionScopes = new Map()
|
|
57
57
|
this._pendingResponses = new Map()
|
|
58
58
|
this._thinkingMessages = new Map()
|
|
59
|
+
this._initialized = false
|
|
59
60
|
}
|
|
60
61
|
|
|
61
62
|
install(framework) {
|
|
@@ -64,6 +65,9 @@ class TelegramPlugin extends Plugin {
|
|
|
64
65
|
}
|
|
65
66
|
|
|
66
67
|
start(framework) {
|
|
68
|
+
if (this._initialized) return this;
|
|
69
|
+
this._initialized = true;
|
|
70
|
+
|
|
67
71
|
if (!this.config.botToken) {
|
|
68
72
|
log.warn(' No bot token. Set TELEGRAM_BOT_TOKEN env var.')
|
|
69
73
|
return this
|
|
@@ -324,7 +328,7 @@ class TelegramPlugin extends Plugin {
|
|
|
324
328
|
break
|
|
325
329
|
case 'reload':
|
|
326
330
|
try {
|
|
327
|
-
await this._framework.executeTool('
|
|
331
|
+
await this._framework.executeTool('skill_reload', {})
|
|
328
332
|
await this._sendMessage(chatId, '✅ 技能已重载', msg.message_id)
|
|
329
333
|
} catch (err) {
|
|
330
334
|
await this._sendMessage(chatId, '❌ 重载失败: ' + err.message, msg.message_id)
|
|
@@ -641,7 +641,7 @@ class WeixinPlugin extends Plugin {
|
|
|
641
641
|
break
|
|
642
642
|
case 'reload':
|
|
643
643
|
try {
|
|
644
|
-
await this._framework.executeTool('
|
|
644
|
+
await this._framework.executeTool('skill_reload', {})
|
|
645
645
|
await this._sendMessageBatch(originalMsg, userId, '✅ 技能已重载', true)
|
|
646
646
|
} catch (err) {
|
|
647
647
|
await this._sendMessageBatch(originalMsg, userId, '❌ 重载失败: ' + err.message, true)
|
|
@@ -5,45 +5,13 @@
|
|
|
5
5
|
|
|
6
6
|
const fs = require('fs');
|
|
7
7
|
const path = require('path');
|
|
8
|
-
const { execSync } = require('child_process');
|
|
9
8
|
const { Plugin } = require('../../src/plugin/base');
|
|
10
9
|
const { logger } = require('../../src/common/logger');
|
|
11
10
|
const { z } = require('zod');
|
|
12
|
-
const { DEFAULT_REPO, shouldIgnore } = require('../../src/
|
|
11
|
+
const { DEFAULT_REPO, shouldIgnore, copyDirRecursive, parseGitUrl, gitCommand } = require('../../src/utils');
|
|
13
12
|
|
|
14
13
|
const log = logger.child('PluginManagerPlugin');
|
|
15
14
|
|
|
16
|
-
function copyDirRecursive(src, dest) {
|
|
17
|
-
if (!fs.existsSync(src)) return;
|
|
18
|
-
fs.mkdirSync(dest, { recursive: true });
|
|
19
|
-
|
|
20
|
-
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
21
|
-
for (const entry of entries) {
|
|
22
|
-
const srcPath = path.join(src, entry.name);
|
|
23
|
-
const destPath = path.join(dest, entry.name);
|
|
24
|
-
if (shouldIgnore(entry.name)) { log.debug(`Ignoring: ${entry.name}`); continue; }
|
|
25
|
-
if (entry.isDirectory()) { copyDirRecursive(srcPath, destPath); }
|
|
26
|
-
else { fs.copyFileSync(srcPath, destPath); }
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function gitCommand(args, cwd) {
|
|
31
|
-
try { return execSync(`git ${args}`, { cwd, encoding: 'utf-8', stdio: 'pipe' }); }
|
|
32
|
-
catch (err) { return err.stdout || err.stderr || ''; }
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function parseGitUrl(url) {
|
|
36
|
-
const patterns = [
|
|
37
|
-
/^https:\/\/github\.com\/([^/]+)\/([^/]+?)(?:\.git)?$/,
|
|
38
|
-
/^git@github\.com:([^/]+)\/([^/]+?)(?:\.git)?$/,
|
|
39
|
-
];
|
|
40
|
-
for (const pattern of patterns) {
|
|
41
|
-
const match = url.match(pattern);
|
|
42
|
-
if (match) return { owner: match[1], repo: match[2] };
|
|
43
|
-
}
|
|
44
|
-
return null;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
15
|
class PluginManagerPlugin extends Plugin {
|
|
48
16
|
constructor(config = {}) {
|
|
49
17
|
super();
|