foliko 2.0.2 → 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 +1 -0
- package/plugins/core/coordinator/index.js +10 -5
- 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 +1 -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/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
package/src/agent/sub.js
CHANGED
|
@@ -27,10 +27,10 @@ class SubAgent extends BaseAgent {
|
|
|
27
27
|
this.baseURL = config.baseURL;
|
|
28
28
|
this.providerOptions = config.providerOptions || {};
|
|
29
29
|
|
|
30
|
-
this.defaulTools = ['ext_skill', '
|
|
30
|
+
this.defaulTools = ['ext_skill', 'skill_load', 'ext_call', 'subagent_call', 'mcp_call', 'mcp_tool_schema'];
|
|
31
31
|
this.bindTools = {
|
|
32
32
|
read: 'read_file', write: 'write_file', edit: 'modify_file',
|
|
33
|
-
glob: 'read_directory', grep: 'search_file', bash: '
|
|
33
|
+
glob: 'read_directory', grep: 'search_file', bash: 'shell_exec',
|
|
34
34
|
};
|
|
35
35
|
|
|
36
36
|
this._customSystemPrompt = config.systemPrompt || null;
|
|
@@ -39,24 +39,13 @@ class SubAgent extends BaseAgent {
|
|
|
39
39
|
this.maxRetries = config.maxRetries ?? 2;
|
|
40
40
|
this.retryDelay = config.retryDelay ?? 5000;
|
|
41
41
|
this.disableTools = config.disableTools ?? false;
|
|
42
|
-
this.
|
|
42
|
+
this.hidden = config.hidden || false; // 隐藏子Agent,不在指令系统中显示
|
|
43
43
|
this._isSubagent = true;
|
|
44
44
|
|
|
45
45
|
this.providerOptions.deepseek = this.providerOptions.deepseek || {};
|
|
46
46
|
this.providerOptions.deepseek.thinking = { type: 'disabled' };
|
|
47
47
|
|
|
48
|
-
|
|
49
|
-
this.framework.once('framework:ready', () => {
|
|
50
|
-
const extExecutor = this.framework?.pluginManager?.get('extension-executor');
|
|
51
|
-
extExecutor?._refreshAllAgentsExtPrompt?.(this.framework);
|
|
52
|
-
});
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
registerPromptPart(name, priority = 100, provider) {
|
|
57
|
-
this._promptParts.push({ name, priority, provider });
|
|
58
|
-
this._promptParts.sort((a, b) => a.priority - b.priority);
|
|
59
|
-
return this;
|
|
48
|
+
// 扩展工具 prompt 由 _syncPromptParts 同步到 promptRegistry,无需显式刷新
|
|
60
49
|
}
|
|
61
50
|
|
|
62
51
|
_validateMessagesPairing(messages) { return validateAll(messages); }
|
|
@@ -74,7 +63,15 @@ class SubAgent extends BaseAgent {
|
|
|
74
63
|
|
|
75
64
|
_getAIProvider() {
|
|
76
65
|
const { createAI } = require('../llm/provider');
|
|
77
|
-
|
|
66
|
+
// 优先从 framework 的 ai 插件拿最新配置(避免构造时拿到 stale apiKey)
|
|
67
|
+
const aiPlugin = this.framework?.pluginManager?.get('ai');
|
|
68
|
+
const aiConfig = aiPlugin ? aiPlugin.getConfig() : {};
|
|
69
|
+
return createAI({
|
|
70
|
+
provider: aiConfig.provider || this.provider,
|
|
71
|
+
model: aiConfig.model || this.model,
|
|
72
|
+
apiKey: aiConfig.apiKey || this.apiKey,
|
|
73
|
+
baseURL: aiConfig.baseURL || this.baseURL,
|
|
74
|
+
});
|
|
78
75
|
}
|
|
79
76
|
|
|
80
77
|
_buildAITools() {
|
|
@@ -111,15 +108,13 @@ class SubAgent extends BaseAgent {
|
|
|
111
108
|
const mainPrompt = mainAgent.getOriginalPrompt();
|
|
112
109
|
if (mainPrompt) { lines.push('## 主Agent的系统提示词'); lines.push(mainPrompt); lines.push(''); }
|
|
113
110
|
}
|
|
114
|
-
const subagentManager = this.framework?.pluginManager?.get('subagent-manager');
|
|
115
|
-
if (subagentManager?._buildDescription) lines.push(subagentManager._buildDescription());
|
|
116
111
|
lines.push('');
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
for (const part of this.
|
|
112
|
+
// 渲染通过 promptRegistry 注册的 prompt part(按 slot/order 排序)
|
|
113
|
+
// 扩展工具描述、子Agent 描述等都由 _syncPromptParts 同步到 promptRegistry,
|
|
114
|
+
// 无需再单独硬编码调用 _buildExtensionsDescription / _buildDescription
|
|
115
|
+
for (const part of (this.promptRegistry?.list() || [])) {
|
|
121
116
|
try {
|
|
122
|
-
const content =
|
|
117
|
+
const content = part.get();
|
|
123
118
|
if (content?.trim()) lines.push('', content.trim());
|
|
124
119
|
} catch (err) {
|
|
125
120
|
logger.warn(`[SubAgent:${this.name}] Prompt part '${part.name}' failed:`, err.message);
|
|
@@ -127,7 +122,7 @@ class SubAgent extends BaseAgent {
|
|
|
127
122
|
}
|
|
128
123
|
lines.push('', '## 核心职责', `- **专注本职**:只完成与 ${this.role} 相关的任务`, '- **不越界**:不要代替其他子Agent完成任务', '- **返回结果**:返回简洁明确的操作结果', '');
|
|
129
124
|
lines.push('当你被调用时,你应该:', '1. 仔细理解任务要求', '2. 如果任务不在职责范围内,返回"此任务不在我的职责范围内"', '3. 使用你的工具集完成任务', '4. 返回完整结果');
|
|
130
|
-
lines.push('', '## 禁止事项', '- 处理本职外的任务', '- 代替其他子Agent执行操作', '- 在回复中透露内部实现细节', '- 不先调用 ext_skill/
|
|
125
|
+
lines.push('', '## 禁止事项', '- 处理本职外的任务', '- 代替其他子Agent执行操作', '- 在回复中透露内部实现细节', '- 不先调用 ext_skill/skill_load 就直接使用 ext_call');
|
|
131
126
|
return lines.join('\n');
|
|
132
127
|
}
|
|
133
128
|
|
|
@@ -5,70 +5,11 @@
|
|
|
5
5
|
|
|
6
6
|
const fs = require('fs');
|
|
7
7
|
const path = require('path');
|
|
8
|
-
const { execSync } = require('child_process');
|
|
9
8
|
const https = require('https');
|
|
10
9
|
const http = require('http');
|
|
11
|
-
const { DEFAULT_REPO, shouldIgnore } = require('
|
|
10
|
+
const { DEFAULT_REPO, shouldIgnore, copyDirRecursive, parseGitUrl, gitCommand } = require('../../utils');
|
|
12
11
|
const { AGENT_DIR_NAME } = require('../utils/config');
|
|
13
12
|
|
|
14
|
-
/**
|
|
15
|
-
* 递归复制目录(带过滤)
|
|
16
|
-
*/
|
|
17
|
-
function copyDirRecursive(src, dest, ignorePatterns = []) {
|
|
18
|
-
if (!fs.existsSync(src)) return;
|
|
19
|
-
|
|
20
|
-
fs.mkdirSync(dest, { recursive: true });
|
|
21
|
-
|
|
22
|
-
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
23
|
-
|
|
24
|
-
for (const entry of entries) {
|
|
25
|
-
const srcPath = path.join(src, entry.name);
|
|
26
|
-
const destPath = path.join(dest, entry.name);
|
|
27
|
-
|
|
28
|
-
// 检查是否忽略
|
|
29
|
-
if (shouldIgnore(entry.name)) {
|
|
30
|
-
console.log(` Ignoring: ${entry.name}`);
|
|
31
|
-
continue;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
if (entry.isDirectory()) {
|
|
35
|
-
copyDirRecursive(srcPath, destPath, ignorePatterns);
|
|
36
|
-
} else {
|
|
37
|
-
fs.copyFileSync(srcPath, destPath);
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* 解析 Git URL 获取信息
|
|
44
|
-
*/
|
|
45
|
-
function parseGitUrl(url) {
|
|
46
|
-
// 支持多种格式
|
|
47
|
-
const patterns = [
|
|
48
|
-
/^https:\/\/github\.com\/([^/]+)\/([^/]+?)(?:\.git)?$/,
|
|
49
|
-
/^git@github\.com:([^/]+)\/([^/]+?)(?:\.git)?$/,
|
|
50
|
-
];
|
|
51
|
-
|
|
52
|
-
for (const pattern of patterns) {
|
|
53
|
-
const match = url.match(pattern);
|
|
54
|
-
if (match) {
|
|
55
|
-
return { owner: match[1], repo: match[2] };
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
return null;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* 执行 Git 命令
|
|
63
|
-
*/
|
|
64
|
-
function gitCommand(args, cwd) {
|
|
65
|
-
try {
|
|
66
|
-
return execSync(`git ${args}`, { cwd, encoding: 'utf-8', stdio: 'pipe' });
|
|
67
|
-
} catch (err) {
|
|
68
|
-
return err.stdout || err.stderr || '';
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
13
|
/**
|
|
73
14
|
* 下载文件
|
|
74
15
|
*/
|
|
@@ -168,7 +168,7 @@ class ChatUIOld {
|
|
|
168
168
|
|
|
169
169
|
async _reloadSkills() {
|
|
170
170
|
try {
|
|
171
|
-
const result = await this.agent.framework.executeTool('
|
|
171
|
+
const result = await this.agent.framework.executeTool('skill_reload', {});
|
|
172
172
|
if (result.success !== false) {
|
|
173
173
|
console.log(colored('[提示] 技能已重载', CYAN));
|
|
174
174
|
} else {
|
package/src/cli/ui/chat-ui.js
CHANGED
|
@@ -321,7 +321,7 @@ class ChatUI {
|
|
|
321
321
|
|
|
322
322
|
if (cmd === '/reload') {
|
|
323
323
|
try {
|
|
324
|
-
const result = await this.agent.framework.executeTool('
|
|
324
|
+
const result = await this.agent.framework.executeTool('skill_reload', {});
|
|
325
325
|
if (result.success !== false) {
|
|
326
326
|
this.create_message(`${colored('[提示]', CYAN)} 技能已重载\n`,chalk.dim('● '))
|
|
327
327
|
} else {
|
|
@@ -78,7 +78,7 @@ class AgentMentionProvider {
|
|
|
78
78
|
|
|
79
79
|
const mainAgent = this.framework._mainAgent;
|
|
80
80
|
return this.framework._agents
|
|
81
|
-
.filter(agent => agent !== mainAgent && agent.name)
|
|
81
|
+
.filter(agent => agent !== mainAgent && agent.name && !agent.hidden)
|
|
82
82
|
.map(agent => ({
|
|
83
83
|
name: agent.name.replace(/^subagent_/, '').replace(/^session_/, ''),
|
|
84
84
|
description: agent._role || agent.description || agent.name,
|
package/src/common/constants.js
CHANGED
|
@@ -79,6 +79,7 @@ const DEFAULT_RETRY_DELAY_MS = 2000;
|
|
|
79
79
|
// System Prompt 优先级
|
|
80
80
|
// ============================================================================
|
|
81
81
|
|
|
82
|
+
// 旧的数字优先级(保留以兼容旧代码)
|
|
82
83
|
const PROMPT_PRIORITY = {
|
|
83
84
|
DATETIME: 50,
|
|
84
85
|
ORIGINAL_PROMPT: 80,
|
|
@@ -93,6 +94,45 @@ const PROMPT_PRIORITY = {
|
|
|
93
94
|
TOOL_CORE_RULES: 150,
|
|
94
95
|
};
|
|
95
96
|
|
|
97
|
+
/**
|
|
98
|
+
* 新的语义槽位(推荐使用)
|
|
99
|
+
* order 越小越靠前;同 slot 内可用 PromptPart.order 进一步排序
|
|
100
|
+
*/
|
|
101
|
+
const PROMPT_SLOT = {
|
|
102
|
+
/** 角色身份:用户/系统设定的 systemPrompt */
|
|
103
|
+
IDENTITY: { order: 10, label: '角色身份' },
|
|
104
|
+
/** 运行环境:时间、cwd、平台等 */
|
|
105
|
+
ENVIRONMENT: { order: 20, label: '运行环境' },
|
|
106
|
+
/** 用户指令:sharedPrompt、运行时 metadata */
|
|
107
|
+
USER: { order: 30, label: '用户指令' },
|
|
108
|
+
/** 可用能力:tools、skills、subagents、extensions */
|
|
109
|
+
CAPABILITY: { order: 40, label: '可用能力' },
|
|
110
|
+
/** 行为规则:tool-core-rules、data-splitter、memory-context */
|
|
111
|
+
BEHAVIOR: { order: 50, label: '行为规则' },
|
|
112
|
+
/** 上下文摘要:自动压缩、消息历史 */
|
|
113
|
+
CONTEXT: { order: 60, label: '上下文摘要' },
|
|
114
|
+
/** 自定义兜底 */
|
|
115
|
+
CUSTOM: { order: 999, label: '自定义' },
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* 旧 PROMPT_PRIORITY 名字 → 新 PROMPT_SLOT 的映射
|
|
120
|
+
* 仅用于过渡期,旧代码迁移时按此映射
|
|
121
|
+
*/
|
|
122
|
+
const PROMPT_PRIORITY_TO_SLOT = {
|
|
123
|
+
DATETIME: 'ENVIRONMENT',
|
|
124
|
+
ORIGINAL_PROMPT: 'IDENTITY',
|
|
125
|
+
SHARED_PROMPT: 'USER',
|
|
126
|
+
METADATA: 'USER',
|
|
127
|
+
CAPABILITIES: 'CAPABILITY',
|
|
128
|
+
TOOLS: 'CAPABILITY',
|
|
129
|
+
SKILLS: 'CAPABILITY',
|
|
130
|
+
SUB_AGENTS: 'CAPABILITY',
|
|
131
|
+
MCP_TOOLS: 'CAPABILITY',
|
|
132
|
+
EXTENSION_TOOLS: 'CAPABILITY',
|
|
133
|
+
TOOL_CORE_RULES: 'BEHAVIOR',
|
|
134
|
+
};
|
|
135
|
+
|
|
96
136
|
// ============================================================================
|
|
97
137
|
// 熔断器
|
|
98
138
|
// ============================================================================
|
|
@@ -182,6 +222,8 @@ module.exports = {
|
|
|
182
222
|
|
|
183
223
|
// Priority
|
|
184
224
|
PROMPT_PRIORITY,
|
|
225
|
+
PROMPT_SLOT,
|
|
226
|
+
PROMPT_PRIORITY_TO_SLOT,
|
|
185
227
|
|
|
186
228
|
// Circuit Breaker
|
|
187
229
|
DEFAULT_CIRCUIT_FAILURE_THRESHOLD,
|
package/src/common/id.js
CHANGED
|
@@ -135,4 +135,17 @@ module.exports = {
|
|
|
135
135
|
const random = crypto.randomBytes(8).toString('base64url');
|
|
136
136
|
return `id_${Date.now()}_${random}`;
|
|
137
137
|
},
|
|
138
|
+
/**
|
|
139
|
+
* 生成唯一的 entry ID (8位 UUID 片段,避免冲突)
|
|
140
|
+
* @param {Set|string[]} existingIds - 已存在的 ID 集合
|
|
141
|
+
* @returns {string}
|
|
142
|
+
*/
|
|
143
|
+
generateEntryId: (existingIds) => {
|
|
144
|
+
const byId = existingIds instanceof Set ? existingIds : new Set(existingIds);
|
|
145
|
+
for (let i = 0; i < 100; i++) {
|
|
146
|
+
const id = uuid().slice(0, 8);
|
|
147
|
+
if (!byId.has(id)) return id;
|
|
148
|
+
}
|
|
149
|
+
return uuid();
|
|
150
|
+
},
|
|
138
151
|
};
|
package/src/common/queue.js
CHANGED
|
@@ -122,12 +122,12 @@ class ChatQueueManager extends EventEmitter {
|
|
|
122
122
|
await this.sleep(calculateDelay(attempt, { baseDelay: this.retryDelay }));
|
|
123
123
|
continue;
|
|
124
124
|
}
|
|
125
|
-
throw
|
|
125
|
+
// 不 throw — 直接跳出循环,由后面的 friendlyError 统一处理
|
|
126
|
+
break;
|
|
126
127
|
}
|
|
127
128
|
|
|
128
129
|
return result;
|
|
129
130
|
} catch (error) {
|
|
130
|
-
|
|
131
131
|
lastError = error;
|
|
132
132
|
if (attempt < this.retryAttempts && this.isRetryableError(error)) {
|
|
133
133
|
await this.sleep(calculateDelay(attempt, { baseDelay: this.retryDelay }));
|
|
@@ -326,15 +326,23 @@ class ContextCompressor {
|
|
|
326
326
|
let removedItems = 0;
|
|
327
327
|
let removedMsgs = 0;
|
|
328
328
|
for (const msg of messages) {
|
|
329
|
-
if (msg.role !== 'tool'
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
329
|
+
if (msg.role !== 'tool') continue;
|
|
330
|
+
|
|
331
|
+
if (Array.isArray(msg.content)) {
|
|
332
|
+
// AI SDK 数组格式:按 toolCallId 过滤
|
|
333
|
+
const originalLength = msg.content.length;
|
|
334
|
+
msg.content = msg.content.filter((item) => {
|
|
335
|
+
if (item && (item.type === 'tool-result' || item.type === 'tool_result') && item.toolCallId && !validToolCallIds.has(item.toolCallId)) {
|
|
336
|
+
removedItems++; return false;
|
|
337
|
+
}
|
|
338
|
+
return true;
|
|
339
|
+
});
|
|
340
|
+
if (msg.content.length === 0 && originalLength > 0) msg._orphaned = true;
|
|
341
|
+
} else if (msg.tool_call_id && !validToolCallIds.has(msg.tool_call_id)) {
|
|
342
|
+
// OpenAI 兼容格式:整条消息是 tool result,无对应 tool_calls 时标记为 orphaned
|
|
343
|
+
msg._orphaned = true;
|
|
344
|
+
removedMsgs++;
|
|
345
|
+
}
|
|
338
346
|
}
|
|
339
347
|
|
|
340
348
|
for (let i = messages.length - 1; i >= 0; i--) {
|
|
@@ -12,10 +12,13 @@ const { AsyncLocalStorage } = require('async_hooks');
|
|
|
12
12
|
const { EventEmitter } = require('../common/events');
|
|
13
13
|
const { PluginManager } = require('../plugin/manager');
|
|
14
14
|
const { ToolRegistry } = require('../tool/registry');
|
|
15
|
+
const { PromptRegistry } = require('../agent/prompt-registry');
|
|
15
16
|
const { ContextManager } = require('../context/manager');
|
|
16
17
|
const { createFrameworkLogger } = require('../common/logger');
|
|
17
18
|
const { AGENT_DIR, HOME_AGENT_DIR_NAME } = require('../common/constants');
|
|
18
19
|
const { SessionTTL } = require('../session/ttl');
|
|
20
|
+
const { Plugin } = require('../plugin/base');
|
|
21
|
+
const { z } = require('zod');
|
|
19
22
|
|
|
20
23
|
// Add framework node_modules to search path
|
|
21
24
|
const frameworkNodeModules = path.join(__dirname, '..', '..', 'node_modules');
|
|
@@ -28,6 +31,53 @@ const asyncLocalStorage = new AsyncLocalStorage();
|
|
|
28
31
|
const requestStorage = new AsyncLocalStorage();
|
|
29
32
|
const sessionStorage = new AsyncLocalStorage();
|
|
30
33
|
|
|
34
|
+
/**
|
|
35
|
+
* 将各种形式的插件定义转换为 Plugin 实例
|
|
36
|
+
* 支持:
|
|
37
|
+
* - Plugin 实例:原样返回
|
|
38
|
+
* - Plugin 子类(class extends Plugin):实例化
|
|
39
|
+
* - function(foliko) 风格:包装为 install
|
|
40
|
+
* - 普通对象 { name, install, start, ... }:拷贝字段并绑定生命周期方法
|
|
41
|
+
* @param {Plugin|Function|Object} pluginDef
|
|
42
|
+
* @returns {Plugin}
|
|
43
|
+
*/
|
|
44
|
+
function createPluginInstance(pluginDef) {
|
|
45
|
+
if (pluginDef instanceof Plugin) return pluginDef;
|
|
46
|
+
|
|
47
|
+
if (typeof pluginDef === 'function') {
|
|
48
|
+
if (pluginDef.prototype instanceof Plugin) {
|
|
49
|
+
return new pluginDef();
|
|
50
|
+
}
|
|
51
|
+
// function(foliko) 风格
|
|
52
|
+
const instance = new Plugin();
|
|
53
|
+
instance.name = pluginDef.name || 'function-plugin';
|
|
54
|
+
const fn = pluginDef;
|
|
55
|
+
instance.install = function (framework) {
|
|
56
|
+
this._framework = framework;
|
|
57
|
+
return fn(framework);
|
|
58
|
+
};
|
|
59
|
+
return instance;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (!pluginDef || typeof pluginDef !== 'object') {
|
|
63
|
+
throw new Error('Plugin definition must be a Plugin instance, subclass, function, or plain object');
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const instance = new Plugin();
|
|
67
|
+
// 显式拷贝已知字段,避免复制任意属性
|
|
68
|
+
const fields = ['name', 'version', 'description', 'priority', 'enabled', 'system', 'tools', 'config', 'agents'];
|
|
69
|
+
for (const field of fields) {
|
|
70
|
+
if (pluginDef[field] !== undefined) instance[field] = pluginDef[field];
|
|
71
|
+
}
|
|
72
|
+
// 绑定生命周期方法(覆盖原型默认值)
|
|
73
|
+
for (const method of ['install', 'start', 'reload', 'uninstall']) {
|
|
74
|
+
if (typeof pluginDef[method] === 'function') {
|
|
75
|
+
instance[method] = pluginDef[method];
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return instance;
|
|
79
|
+
}
|
|
80
|
+
|
|
31
81
|
class Framework extends EventEmitter {
|
|
32
82
|
constructor(config = {}) {
|
|
33
83
|
super();
|
|
@@ -41,6 +91,7 @@ class Framework extends EventEmitter {
|
|
|
41
91
|
// Subsystems
|
|
42
92
|
this.toolRegistry = new ToolRegistry();
|
|
43
93
|
this.pluginManager = new PluginManager(this);
|
|
94
|
+
this.promptRegistry = new PromptRegistry();
|
|
44
95
|
|
|
45
96
|
// Agent management
|
|
46
97
|
this._agents = [];
|
|
@@ -75,6 +126,7 @@ class Framework extends EventEmitter {
|
|
|
75
126
|
// Event forwarding
|
|
76
127
|
this._setupToolEventForwarding();
|
|
77
128
|
this._setupAgentEventForwarding();
|
|
129
|
+
this._setupPromptRegistryAutoRefresh();
|
|
78
130
|
this._registerBuiltinTools();
|
|
79
131
|
}
|
|
80
132
|
|
|
@@ -107,6 +159,24 @@ class Framework extends EventEmitter {
|
|
|
107
159
|
});
|
|
108
160
|
}
|
|
109
161
|
|
|
162
|
+
/**
|
|
163
|
+
* promptRegistry 变更时自动失效所有 Agent 的提示词缓存
|
|
164
|
+
* 插件重载、skill 重载、扩展工具变更等都会触发刷新
|
|
165
|
+
* 系统提示词在下次 chat 时自动重建(懒刷新,不浪费性能)
|
|
166
|
+
*/
|
|
167
|
+
_setupPromptRegistryAutoRefresh() {
|
|
168
|
+
// 仅标记脏缓存(不调 invalidateAll,避免与 promptRegistry 事件循环)
|
|
169
|
+
const invalidate = () => {
|
|
170
|
+
for (const agent of this._agents) {
|
|
171
|
+
agent._systemPromptDirty = true;
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
const REFRESH_EVENTS = ['register', 'unregister', 'clear-owner'];
|
|
175
|
+
for (const event of REFRESH_EVENTS) {
|
|
176
|
+
this.promptRegistry.on(event, invalidate);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
110
180
|
registerPlugin(plugin) { this.pluginManager.register(plugin); return this; }
|
|
111
181
|
registerProvider(name, config) { const { registerProvider } = require('../llm/provider'); registerProvider(name, config); return this; }
|
|
112
182
|
getProviderRegistry() { const { getProviderRegistry } = require('../llm/provider'); return getProviderRegistry(); }
|
|
@@ -143,6 +213,121 @@ class Framework extends EventEmitter {
|
|
|
143
213
|
}
|
|
144
214
|
getTools() { return this.toolRegistry.getAll(); }
|
|
145
215
|
|
|
216
|
+
// tools 对象别名
|
|
217
|
+
get tools() {
|
|
218
|
+
const self = this;
|
|
219
|
+
return {
|
|
220
|
+
register: (toolDef) => self.registerTool(toolDef),
|
|
221
|
+
tool: (toolDef) => self.tool(toolDef),
|
|
222
|
+
execute: (name, args) => self.executeTool(name, args),
|
|
223
|
+
list: () => self.toolRegistry.getNames(),
|
|
224
|
+
get: (name) => self.toolRegistry.get(name),
|
|
225
|
+
has: (name) => self.toolRegistry.has(name),
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Extension 工具注册
|
|
230
|
+
registerExtensionTools(pluginName, tools) {
|
|
231
|
+
const extExecutor = this.pluginManager.get('extension-executor');
|
|
232
|
+
if (!extExecutor) {
|
|
233
|
+
this.logger.warn(`[Framework] extension-executor not found, cannot register extension tools for "${pluginName}"`);
|
|
234
|
+
return this;
|
|
235
|
+
}
|
|
236
|
+
for (const [toolName, toolDef] of Object.entries(tools)) {
|
|
237
|
+
if (!toolDef || typeof toolDef !== 'object') continue;
|
|
238
|
+
extExecutor.registerTool(pluginName, {
|
|
239
|
+
name: pluginName,
|
|
240
|
+
description: toolDef.description || '',
|
|
241
|
+
version: toolDef.version || '1.0.0',
|
|
242
|
+
}, {
|
|
243
|
+
name: toolName,
|
|
244
|
+
description: toolDef.description || '',
|
|
245
|
+
inputSchema: toolDef.inputSchema,
|
|
246
|
+
execute: toolDef.execute,
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
return this;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// 常用类型和工具(方便 function(foliko) 插件使用)
|
|
253
|
+
get Plugin() { return Plugin; }
|
|
254
|
+
get z() { return z; }
|
|
255
|
+
|
|
256
|
+
// prompts 对象别名(系统提示词注册表)
|
|
257
|
+
get prompts() {
|
|
258
|
+
return this.promptRegistry;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// plugins 对象别名
|
|
262
|
+
get plugins() {
|
|
263
|
+
const self = this;
|
|
264
|
+
return {
|
|
265
|
+
get: (name) => self.pluginManager.get(name) || null,
|
|
266
|
+
list: () => self.pluginManager.getAll().map(p => p.instance?.name || p.name),
|
|
267
|
+
has: (name) => self.pluginManager.has(name),
|
|
268
|
+
/**
|
|
269
|
+
* 从插件定义创建 Plugin 实例
|
|
270
|
+
* 支持 Plugin 实例 / class / function(foliko) / 普通对象
|
|
271
|
+
*/
|
|
272
|
+
create: (pluginDef) => createPluginInstance(pluginDef),
|
|
273
|
+
/**
|
|
274
|
+
* 注册新插件(默认会自动安装 + 启动)
|
|
275
|
+
* @param {Plugin|Function|Object} pluginDef 插件定义
|
|
276
|
+
* @param {Object} [options] 传给 PluginManager
|
|
277
|
+
* @param {boolean} [options.autoLoad=true] 是否自动调用 install + start
|
|
278
|
+
* @returns {Promise<Plugin>}
|
|
279
|
+
*/
|
|
280
|
+
register: async (pluginDef, options = {}) => {
|
|
281
|
+
const autoLoad = options.autoLoad !== false;
|
|
282
|
+
const plugin = createPluginInstance(pluginDef);
|
|
283
|
+
self.pluginManager.register(plugin, options);
|
|
284
|
+
if (autoLoad) {
|
|
285
|
+
await self.pluginManager.load(plugin, options);
|
|
286
|
+
}
|
|
287
|
+
return plugin;
|
|
288
|
+
},
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// 快捷获取插件实例
|
|
293
|
+
plugin(name) {
|
|
294
|
+
return this.pluginManager.get(name) || null;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// Extension 对象别名
|
|
298
|
+
get extension() {
|
|
299
|
+
const self = this;
|
|
300
|
+
return {
|
|
301
|
+
tool: (pluginName, tools) => self.registerExtensionTools(pluginName, tools),
|
|
302
|
+
execute: (plugin, tool, args) => self.executeExtension(plugin, tool, args),
|
|
303
|
+
list: () => {
|
|
304
|
+
const extExecutor = self.pluginManager.get('extension-executor');
|
|
305
|
+
if (!extExecutor) return [];
|
|
306
|
+
return extExecutor.getExtensions?.() || [];
|
|
307
|
+
},
|
|
308
|
+
listPlugins: () => {
|
|
309
|
+
const extExecutor = self.pluginManager.get('extension-executor');
|
|
310
|
+
if (!extExecutor) return [];
|
|
311
|
+
return extExecutor.getExtensionNames?.() || [];
|
|
312
|
+
},
|
|
313
|
+
listTools: (pluginName) => {
|
|
314
|
+
const extExecutor = self.pluginManager.get('extension-executor');
|
|
315
|
+
if (!extExecutor) return [];
|
|
316
|
+
return extExecutor.getExtensionTools?.(pluginName) || [];
|
|
317
|
+
},
|
|
318
|
+
getTool: (pluginName, toolName) => {
|
|
319
|
+
const extExecutor = self.pluginManager.get('extension-executor');
|
|
320
|
+
if (!extExecutor) return null;
|
|
321
|
+
return extExecutor.getExtensionTool?.(pluginName, toolName) || null;
|
|
322
|
+
},
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// 执行 extension 工具
|
|
327
|
+
async executeExtension(plugin, tool, args = {}) {
|
|
328
|
+
return this.executeTool('ext_call', { plugin, tool, args });
|
|
329
|
+
}
|
|
330
|
+
|
|
146
331
|
getMainAgent() { return this._mainAgent; }
|
|
147
332
|
getSystemPrompt() { return this._mainAgent?._buildSystemPrompt?.() || ''; }
|
|
148
333
|
getPluginInstance(name) { return this.pluginManager.get(name) || null; }
|
package/src/framework/index.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
const { Framework } = require('./framework');
|
|
4
4
|
const { bootstrap, ready, destroy, setCwd, rescanProject } = require('./lifecycle');
|
|
5
5
|
const { FrameworkRegistry } = require('./registry');
|
|
6
|
-
const {
|
|
6
|
+
const { loadDefaultPlugins } = require('./loader');
|
|
7
7
|
|
|
8
8
|
module.exports = {
|
|
9
9
|
Framework,
|
|
@@ -13,6 +13,5 @@ module.exports = {
|
|
|
13
13
|
destroy,
|
|
14
14
|
setCwd,
|
|
15
15
|
rescanProject,
|
|
16
|
-
loadAgentConfig,
|
|
17
16
|
loadDefaultPlugins,
|
|
18
17
|
};
|
|
@@ -114,6 +114,15 @@ async function setCwd(framework, newCwd, options = {}) {
|
|
|
114
114
|
|
|
115
115
|
framework.pluginManager._setStateFile(resolved);
|
|
116
116
|
|
|
117
|
+
// 加载新目录配置,更新 skillsDirs 确保 skill-manager 能扫描到新项目技能
|
|
118
|
+
const { loadAgentConfig } = require('../../plugins/core/default');
|
|
119
|
+
const agentConfig = loadAgentConfig(framework, framework._agentDir || '.foliko');
|
|
120
|
+
const skillManager = framework.pluginManager.get('skill-manager');
|
|
121
|
+
if (skillManager && Array.isArray(agentConfig.skillsDirs)) {
|
|
122
|
+
skillManager.setSearchDirs(agentConfig.skillsDirs);
|
|
123
|
+
} else {
|
|
124
|
+
}
|
|
125
|
+
|
|
117
126
|
const count = framework._sessionContexts.size;
|
|
118
127
|
for (const sid of Array.from(framework._sessionContexts.keys())) {
|
|
119
128
|
framework.destroySessionContext(sid);
|
|
@@ -200,4 +209,96 @@ async function rescanProject(framework, options = {}) {
|
|
|
200
209
|
return result;
|
|
201
210
|
}
|
|
202
211
|
|
|
203
|
-
|
|
212
|
+
/**
|
|
213
|
+
* Reload all - 完全重新加载所有插件和 skill
|
|
214
|
+
*
|
|
215
|
+
* 与 rescanProject 的区别:rescanProject 保留 SYSTEM_PLUGINS(ext-exec、skill-manager)
|
|
216
|
+
* 的旧实例,可能残留旧 state。reloadAll 显式按顺序:
|
|
217
|
+
* 1. 重载 ext-exec(先清空 _extensions)
|
|
218
|
+
* 2. 卸载非系统插件
|
|
219
|
+
* 3. 重载 defaults plugin
|
|
220
|
+
* 4. bootstrapDefaults(加载所有 default 插件,包括新的 ext-exec 和 skill-manager)
|
|
221
|
+
* 5. 重载 skill-manager(最后一步,让新 skill 的 commands 注册到新 ext-exec)
|
|
222
|
+
*
|
|
223
|
+
* 推荐用法:切换 WORK_DIR 后调用
|
|
224
|
+
* await foliko.setCwd(fw, newCwd);
|
|
225
|
+
* await foliko.reloadAll(fw);
|
|
226
|
+
*
|
|
227
|
+
* @param {Framework} framework
|
|
228
|
+
* @param {Object} options
|
|
229
|
+
* @param {boolean} [options.reloadExtExec=true] 是否重载 ext-exec(默认 true,彻底清空旧 state)
|
|
230
|
+
* @param {boolean} [options.reloadSkillManager=true] 是否重载 skill-manager(默认 true,扫描新 skill)
|
|
231
|
+
* @param {Set<string>} [options.keepLoaded] 额外保留的插件名集合
|
|
232
|
+
*/
|
|
233
|
+
async function reloadAll(framework, options = {}) {
|
|
234
|
+
const { bootstrapDefaults, DefaultPlugins, loadAgentConfig } = require('../../plugins/core/default');
|
|
235
|
+
const { SYSTEM_PLUGINS } = require('../common/constants');
|
|
236
|
+
const keepSet = new Set(options.keepLoaded || Array.from(SYSTEM_PLUGINS));
|
|
237
|
+
|
|
238
|
+
console.log('[foliko] reloadAll: starting full reload');
|
|
239
|
+
|
|
240
|
+
// 1. 重载 ext-exec(先清空 _extensions)
|
|
241
|
+
if (options.reloadExtExec !== false && framework.pluginManager.has('extension-executor')) {
|
|
242
|
+
try {
|
|
243
|
+
await framework.pluginManager.reload('extension-executor');
|
|
244
|
+
console.log('[foliko] reloadAll: ext-executor reloaded (state cleared)');
|
|
245
|
+
} catch (err) {
|
|
246
|
+
console.log('[foliko] reloadAll: ext-executor reload failed:', err.message);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// 2. 卸载非系统插件
|
|
251
|
+
try {
|
|
252
|
+
const unloadList = await framework.pluginManager.unloadExcept(keepSet);
|
|
253
|
+
console.log(`[foliko] reloadAll: unloaded ${unloadList.length} non-system plugins`);
|
|
254
|
+
} catch (err) {
|
|
255
|
+
console.log('[foliko] reloadAll: unloadExcept failed:', err.message);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// 3. 重载 defaults plugin
|
|
259
|
+
let defaultsPlugin = framework.pluginManager.get('defaults');
|
|
260
|
+
if (!defaultsPlugin) {
|
|
261
|
+
const dp = new DefaultPlugins({ agentDir: framework._agentDir });
|
|
262
|
+
try { await framework.loadPlugin(dp); defaultsPlugin = dp; }
|
|
263
|
+
catch (err) { console.log('[foliko] reloadAll: load defaults failed:', err.message); }
|
|
264
|
+
} else {
|
|
265
|
+
try { await defaultsPlugin.reload(framework); }
|
|
266
|
+
catch (err) { console.log('[foliko] reloadAll: reload defaults failed:', err.message); }
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
const agentConfig = defaultsPlugin
|
|
270
|
+
? (defaultsPlugin.getConfig() || loadAgentConfig(framework, framework._agentDir))
|
|
271
|
+
: loadAgentConfig(framework, framework._agentDir);
|
|
272
|
+
|
|
273
|
+
// 4. bootstrapDefaults(加载所有 default 插件)
|
|
274
|
+
try {
|
|
275
|
+
await bootstrapDefaults(framework, { _config: agentConfig, _skipConfigLoad: true });
|
|
276
|
+
console.log('[foliko] reloadAll: bootstrapDefaults done');
|
|
277
|
+
} catch (err) {
|
|
278
|
+
console.log('[foliko] reloadAll: bootstrapDefaults failed:', err.message);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// 5. 重载 skill-manager(最后一步,让新 skill 的 commands 注册到新 ext-exec)
|
|
282
|
+
if (options.reloadSkillManager !== false) {
|
|
283
|
+
const skillManager = framework.pluginManager.get('skill-manager');
|
|
284
|
+
if (skillManager) {
|
|
285
|
+
if (Array.isArray(agentConfig.skillsDirs)) skillManager.setSearchDirs(agentConfig.skillsDirs);
|
|
286
|
+
try {
|
|
287
|
+
await skillManager.reload(framework);
|
|
288
|
+
const sm = skillManager;
|
|
289
|
+
const extExec = framework.pluginManager.get('extension-executor');
|
|
290
|
+
const skillTools = extExec?._extensions?.get('skill')?.tools || [];
|
|
291
|
+
console.log(
|
|
292
|
+
`[foliko] reloadAll: skill-manager reloaded, ${sm._skills.size} skills, ${Object.keys(sm.tools).length} tools in sm, ${skillTools.length} tools in ext-exec`
|
|
293
|
+
);
|
|
294
|
+
} catch (err) {
|
|
295
|
+
console.log('[foliko] reloadAll: skill-manager reload failed:', err.message);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
framework.emit('reload:complete', { framework });
|
|
301
|
+
return framework;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
module.exports = { bootstrap, ready, destroy, setCwd, rescanProject, reloadAll };
|