foliko 2.0.2 → 2.0.4
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 +52 -38
- 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/chat.js +67 -5
- 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 +20 -25
- 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 +21 -17
- 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 +3 -3
- package/src/context/compressor.js +17 -9
- package/src/framework/framework.js +218 -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/chat.js +1 -1
- package/src/session/entry.js +1 -11
- package/src/storage/manager.js +1 -12
- package/src/tool/executor.js +3 -2
- 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/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 }));
|
|
@@ -260,7 +260,7 @@ class ChatQueueManager extends EventEmitter {
|
|
|
260
260
|
this.activeCount = 0;
|
|
261
261
|
|
|
262
262
|
pendingItems.forEach((item) => {
|
|
263
|
-
item.reject(new Error('
|
|
263
|
+
item.reject(new Error('队列已清空'));
|
|
264
264
|
});
|
|
265
265
|
|
|
266
266
|
this.emit('queue:cleared', { count: pendingItems.length });
|
|
@@ -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 = [];
|
|
@@ -66,6 +117,9 @@ class Framework extends EventEmitter {
|
|
|
66
117
|
// Coordinator
|
|
67
118
|
this._coordinatorManager = null;
|
|
68
119
|
|
|
120
|
+
// Global abort controller for framework.stop()
|
|
121
|
+
this._abortController = null;
|
|
122
|
+
|
|
69
123
|
// Session TTL
|
|
70
124
|
this._sessionTTL = new SessionTTL({
|
|
71
125
|
getSessionContexts: () => this._sessionContexts,
|
|
@@ -75,6 +129,7 @@ class Framework extends EventEmitter {
|
|
|
75
129
|
// Event forwarding
|
|
76
130
|
this._setupToolEventForwarding();
|
|
77
131
|
this._setupAgentEventForwarding();
|
|
132
|
+
this._setupPromptRegistryAutoRefresh();
|
|
78
133
|
this._registerBuiltinTools();
|
|
79
134
|
}
|
|
80
135
|
|
|
@@ -107,6 +162,24 @@ class Framework extends EventEmitter {
|
|
|
107
162
|
});
|
|
108
163
|
}
|
|
109
164
|
|
|
165
|
+
/**
|
|
166
|
+
* promptRegistry 变更时自动失效所有 Agent 的提示词缓存
|
|
167
|
+
* 插件重载、skill 重载、扩展工具变更等都会触发刷新
|
|
168
|
+
* 系统提示词在下次 chat 时自动重建(懒刷新,不浪费性能)
|
|
169
|
+
*/
|
|
170
|
+
_setupPromptRegistryAutoRefresh() {
|
|
171
|
+
// 仅标记脏缓存(不调 invalidateAll,避免与 promptRegistry 事件循环)
|
|
172
|
+
const invalidate = () => {
|
|
173
|
+
for (const agent of this._agents) {
|
|
174
|
+
agent._systemPromptDirty = true;
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
const REFRESH_EVENTS = ['register', 'unregister', 'clear-owner'];
|
|
178
|
+
for (const event of REFRESH_EVENTS) {
|
|
179
|
+
this.promptRegistry.on(event, invalidate);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
110
183
|
registerPlugin(plugin) { this.pluginManager.register(plugin); return this; }
|
|
111
184
|
registerProvider(name, config) { const { registerProvider } = require('../llm/provider'); registerProvider(name, config); return this; }
|
|
112
185
|
getProviderRegistry() { const { getProviderRegistry } = require('../llm/provider'); return getProviderRegistry(); }
|
|
@@ -143,6 +216,121 @@ class Framework extends EventEmitter {
|
|
|
143
216
|
}
|
|
144
217
|
getTools() { return this.toolRegistry.getAll(); }
|
|
145
218
|
|
|
219
|
+
// tools 对象别名
|
|
220
|
+
get tools() {
|
|
221
|
+
const self = this;
|
|
222
|
+
return {
|
|
223
|
+
register: (toolDef) => self.registerTool(toolDef),
|
|
224
|
+
tool: (toolDef) => self.tool(toolDef),
|
|
225
|
+
execute: (name, args) => self.executeTool(name, args),
|
|
226
|
+
list: () => self.toolRegistry.getNames(),
|
|
227
|
+
get: (name) => self.toolRegistry.get(name),
|
|
228
|
+
has: (name) => self.toolRegistry.has(name),
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Extension 工具注册
|
|
233
|
+
registerExtensionTools(pluginName, tools) {
|
|
234
|
+
const extExecutor = this.pluginManager.get('extension-executor');
|
|
235
|
+
if (!extExecutor) {
|
|
236
|
+
this.logger.warn(`[Framework] extension-executor not found, cannot register extension tools for "${pluginName}"`);
|
|
237
|
+
return this;
|
|
238
|
+
}
|
|
239
|
+
for (const [toolName, toolDef] of Object.entries(tools)) {
|
|
240
|
+
if (!toolDef || typeof toolDef !== 'object') continue;
|
|
241
|
+
extExecutor.registerTool(pluginName, {
|
|
242
|
+
name: pluginName,
|
|
243
|
+
description: toolDef.description || '',
|
|
244
|
+
version: toolDef.version || '1.0.0',
|
|
245
|
+
}, {
|
|
246
|
+
name: toolName,
|
|
247
|
+
description: toolDef.description || '',
|
|
248
|
+
inputSchema: toolDef.inputSchema,
|
|
249
|
+
execute: toolDef.execute,
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
return this;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// 常用类型和工具(方便 function(foliko) 插件使用)
|
|
256
|
+
get Plugin() { return Plugin; }
|
|
257
|
+
get z() { return z; }
|
|
258
|
+
|
|
259
|
+
// prompts 对象别名(系统提示词注册表)
|
|
260
|
+
get prompts() {
|
|
261
|
+
return this.promptRegistry;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// plugins 对象别名
|
|
265
|
+
get plugins() {
|
|
266
|
+
const self = this;
|
|
267
|
+
return {
|
|
268
|
+
get: (name) => self.pluginManager.get(name) || null,
|
|
269
|
+
list: () => self.pluginManager.getAll().map(p => p.instance?.name || p.name),
|
|
270
|
+
has: (name) => self.pluginManager.has(name),
|
|
271
|
+
/**
|
|
272
|
+
* 从插件定义创建 Plugin 实例
|
|
273
|
+
* 支持 Plugin 实例 / class / function(foliko) / 普通对象
|
|
274
|
+
*/
|
|
275
|
+
create: (pluginDef) => createPluginInstance(pluginDef),
|
|
276
|
+
/**
|
|
277
|
+
* 注册新插件(默认会自动安装 + 启动)
|
|
278
|
+
* @param {Plugin|Function|Object} pluginDef 插件定义
|
|
279
|
+
* @param {Object} [options] 传给 PluginManager
|
|
280
|
+
* @param {boolean} [options.autoLoad=true] 是否自动调用 install + start
|
|
281
|
+
* @returns {Promise<Plugin>}
|
|
282
|
+
*/
|
|
283
|
+
register: async (pluginDef, options = {}) => {
|
|
284
|
+
const autoLoad = options.autoLoad !== false;
|
|
285
|
+
const plugin = createPluginInstance(pluginDef);
|
|
286
|
+
self.pluginManager.register(plugin, options);
|
|
287
|
+
if (autoLoad) {
|
|
288
|
+
await self.pluginManager.load(plugin, options);
|
|
289
|
+
}
|
|
290
|
+
return plugin;
|
|
291
|
+
},
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// 快捷获取插件实例
|
|
296
|
+
plugin(name) {
|
|
297
|
+
return this.pluginManager.get(name) || null;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// Extension 对象别名
|
|
301
|
+
get extension() {
|
|
302
|
+
const self = this;
|
|
303
|
+
return {
|
|
304
|
+
tool: (pluginName, tools) => self.registerExtensionTools(pluginName, tools),
|
|
305
|
+
execute: (plugin, tool, args) => self.executeExtension(plugin, tool, args),
|
|
306
|
+
list: () => {
|
|
307
|
+
const extExecutor = self.pluginManager.get('extension-executor');
|
|
308
|
+
if (!extExecutor) return [];
|
|
309
|
+
return extExecutor.getExtensions?.() || [];
|
|
310
|
+
},
|
|
311
|
+
listPlugins: () => {
|
|
312
|
+
const extExecutor = self.pluginManager.get('extension-executor');
|
|
313
|
+
if (!extExecutor) return [];
|
|
314
|
+
return extExecutor.getExtensionNames?.() || [];
|
|
315
|
+
},
|
|
316
|
+
listTools: (pluginName) => {
|
|
317
|
+
const extExecutor = self.pluginManager.get('extension-executor');
|
|
318
|
+
if (!extExecutor) return [];
|
|
319
|
+
return extExecutor.getExtensionTools?.(pluginName) || [];
|
|
320
|
+
},
|
|
321
|
+
getTool: (pluginName, toolName) => {
|
|
322
|
+
const extExecutor = self.pluginManager.get('extension-executor');
|
|
323
|
+
if (!extExecutor) return null;
|
|
324
|
+
return extExecutor.getExtensionTool?.(pluginName, toolName) || null;
|
|
325
|
+
},
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// 执行 extension 工具
|
|
330
|
+
async executeExtension(plugin, tool, args = {}) {
|
|
331
|
+
return this.executeTool('ext_call', { plugin, tool, args });
|
|
332
|
+
}
|
|
333
|
+
|
|
146
334
|
getMainAgent() { return this._mainAgent; }
|
|
147
335
|
getSystemPrompt() { return this._mainAgent?._buildSystemPrompt?.() || ''; }
|
|
148
336
|
getPluginInstance(name) { return this.pluginManager.get(name) || null; }
|
|
@@ -151,6 +339,36 @@ class Framework extends EventEmitter {
|
|
|
151
339
|
|
|
152
340
|
getCwd() { return this._cwd; }
|
|
153
341
|
|
|
342
|
+
/**
|
|
343
|
+
* 获取全局 abort signal,用于中断正在运行的对话
|
|
344
|
+
*/
|
|
345
|
+
getAbortSignal() {
|
|
346
|
+
if (!this._abortController || this._abortController.signal.aborted) {
|
|
347
|
+
this._abortController = new AbortController();
|
|
348
|
+
}
|
|
349
|
+
return this._abortController.signal;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* 立即中断所有正在运行的 Agent 对话
|
|
354
|
+
*/
|
|
355
|
+
stop() {
|
|
356
|
+
if (this._abortController) {
|
|
357
|
+
this._abortController.abort(new Error('用户中断'));
|
|
358
|
+
this._abortController = null;
|
|
359
|
+
}
|
|
360
|
+
// 清空消息队列
|
|
361
|
+
for (const agent of this._agents) {
|
|
362
|
+
if (agent._chatHandler?.queueManager) {
|
|
363
|
+
agent._chatHandler.queueManager.clear();
|
|
364
|
+
}
|
|
365
|
+
if (typeof agent.cancelSession === 'function') {
|
|
366
|
+
agent.cancelSession('default');
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
this.emit('framework:stopped');
|
|
370
|
+
}
|
|
371
|
+
|
|
154
372
|
registerEventDescription(eventType, description, schema = null) { this.pluginManager.registerEventDescription(eventType, description, schema); return this; }
|
|
155
373
|
getEventDescriptions() { return this.pluginManager.getEventDescriptions(); }
|
|
156
374
|
getEventDescription(eventType) { return this.pluginManager.getEventDescription(eventType); }
|
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 };
|
package/src/framework/loader.js
CHANGED
|
@@ -6,60 +6,6 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
const path = require('path');
|
|
9
|
-
const fs = require('fs');
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Load agent config from .foliko/ directory
|
|
13
|
-
* @param {Framework} framework
|
|
14
|
-
* @param {string} agentDir
|
|
15
|
-
* @returns {Object}
|
|
16
|
-
*/
|
|
17
|
-
function loadAgentConfig(framework, agentDir = '.foliko') {
|
|
18
|
-
const cwd = framework.getCwd ? framework.getCwd() : process.cwd();
|
|
19
|
-
const folikoDir = path.resolve(cwd, agentDir);
|
|
20
|
-
const config = {
|
|
21
|
-
plugins: [],
|
|
22
|
-
skills: [],
|
|
23
|
-
ai: {},
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
// Load plugins.json
|
|
27
|
-
const pluginsPath = path.join(folikoDir, 'plugins.json');
|
|
28
|
-
if (fs.existsSync(pluginsPath)) {
|
|
29
|
-
try {
|
|
30
|
-
const pluginsConfig = JSON.parse(fs.readFileSync(pluginsPath, 'utf-8'));
|
|
31
|
-
if (Array.isArray(pluginsConfig)) {
|
|
32
|
-
config.plugins = pluginsConfig;
|
|
33
|
-
} else if (pluginsConfig.plugins) {
|
|
34
|
-
config.plugins = pluginsConfig.plugins;
|
|
35
|
-
}
|
|
36
|
-
if (pluginsConfig.ai) config.ai = { ...config.ai, ...pluginsConfig.ai };
|
|
37
|
-
if (pluginsConfig.skillsDirs) config.skillsDirs = pluginsConfig.skillsDirs;
|
|
38
|
-
} catch (err) {
|
|
39
|
-
framework.logger?.warn?.('Failed to load plugins.json:', err.message);
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// Load agent configs from agents/ directory
|
|
44
|
-
const agentsDir = path.join(folikoDir, 'agents');
|
|
45
|
-
if (fs.existsSync(agentsDir)) {
|
|
46
|
-
try {
|
|
47
|
-
const files = fs.readdirSync(agentsDir).filter(f => f.endsWith('.json'));
|
|
48
|
-
config.agentFiles = files.map(f => path.join(agentsDir, f));
|
|
49
|
-
} catch { /* ignore */ }
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// Load skills config
|
|
53
|
-
const skillsDir = path.join(folikoDir, 'skills');
|
|
54
|
-
if (fs.existsSync(skillsDir)) {
|
|
55
|
-
try {
|
|
56
|
-
config.skillsDirs = config.skillsDirs || [];
|
|
57
|
-
config.skillsDirs.push(skillsDir);
|
|
58
|
-
} catch { /* ignore */ }
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
return config;
|
|
62
|
-
}
|
|
63
9
|
|
|
64
10
|
/**
|
|
65
11
|
* Load default plugins based on config
|
|
@@ -75,4 +21,4 @@ async function loadDefaultPlugins(framework, config = {}) {
|
|
|
75
21
|
return framework.pluginManager.getAll().map(p => p.name);
|
|
76
22
|
}
|
|
77
23
|
|
|
78
|
-
module.exports = {
|
|
24
|
+
module.exports = { loadDefaultPlugins };
|
package/src/index.js
CHANGED
|
@@ -15,12 +15,12 @@ require('dotenv').config({ override: true });
|
|
|
15
15
|
// Framework
|
|
16
16
|
// ============================================================
|
|
17
17
|
const { Framework } = require('./framework/framework');
|
|
18
|
-
const { bootstrap, ready, destroy } = require('./framework/lifecycle');
|
|
18
|
+
const { bootstrap, ready, destroy, setCwd, rescanProject, reloadAll } = require('./framework/lifecycle');
|
|
19
19
|
|
|
20
20
|
// ============================================================
|
|
21
21
|
// Agent
|
|
22
22
|
// ============================================================
|
|
23
|
-
const { createAgent, MainAgent, SubAgent, WorkerAgent } = require('./agent');
|
|
23
|
+
const { createAgent, MainAgent, SubAgent, WorkerAgent, PromptRegistry } = require('./agent');
|
|
24
24
|
|
|
25
25
|
// ============================================================
|
|
26
26
|
// Plugin
|
|
@@ -70,6 +70,9 @@ exports.createAgent = createAgent;
|
|
|
70
70
|
|
|
71
71
|
exports.ready = ready;
|
|
72
72
|
exports.destroy = destroy;
|
|
73
|
+
exports.setCwd = setCwd;
|
|
74
|
+
exports.rescanProject = rescanProject;
|
|
75
|
+
exports.reloadAll = reloadAll;
|
|
73
76
|
|
|
74
77
|
// --- 上下文管理 ---
|
|
75
78
|
|
|
@@ -113,6 +116,11 @@ exports.withPython = (config = {}) => {
|
|
|
113
116
|
exports.z = require('zod');
|
|
114
117
|
exports.LLM = llm;
|
|
115
118
|
|
|
119
|
+
// --- 常量 ---
|
|
120
|
+
exports.PROMPT_SLOT = require('./common/constants').PROMPT_SLOT;
|
|
121
|
+
exports.PROMPT_PRIORITY = require('./common/constants').PROMPT_PRIORITY;
|
|
122
|
+
exports.PROMPT_PRIORITY_TO_SLOT = require('./common/constants').PROMPT_PRIORITY_TO_SLOT;
|
|
123
|
+
|
|
116
124
|
// --- 框架核心类(用于扩展) ---
|
|
117
125
|
|
|
118
126
|
exports.Framework = Framework;
|
|
@@ -123,6 +131,7 @@ exports.ToolRegistry = ToolRegistry;
|
|
|
123
131
|
exports.ToolExecutor = ToolExecutor;
|
|
124
132
|
exports.Plugin = Plugin;
|
|
125
133
|
exports.PluginManager = PluginManager;
|
|
134
|
+
exports.PromptRegistry = PromptRegistry;
|
|
126
135
|
exports.EventEmitter = EventEmitter;
|
|
127
136
|
exports.Logger = Logger;
|
|
128
137
|
|