foliko 1.1.92 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (212) hide show
  1. package/.claude/settings.local.json +2 -1
  2. package/CLAUDE.md +56 -30
  3. package/REFACTORING_PLAN.md +645 -0
  4. package/docs/architecture.md +131 -0
  5. package/docs/migration.md +57 -0
  6. package/docs/public-api.md +138 -0
  7. package/docs/usage.md +385 -0
  8. package/examples/ambient-example.js +20 -137
  9. package/examples/basic.js +21 -48
  10. package/examples/bootstrap.js +16 -74
  11. package/examples/mcp-example.js +6 -29
  12. package/examples/skill-example.js +6 -19
  13. package/examples/workflow.js +8 -56
  14. package/package.json +8 -4
  15. package/plugins/README.md +49 -0
  16. package/plugins/{ambient-agent → ambient}/EventWatcher.js +1 -1
  17. package/plugins/{ambient-agent → ambient}/ExplorerLoop.js +3 -3
  18. package/plugins/{ambient-agent → ambient}/GoalManager.js +2 -2
  19. package/plugins/ambient/README.md +14 -0
  20. package/plugins/{ambient-agent → ambient}/Reflector.js +1 -1
  21. package/plugins/{ambient-agent → ambient}/StateStore.js +1 -1
  22. package/plugins/{ambient-agent → ambient}/index.js +2 -2
  23. package/plugins/{ai-plugin.js → core/ai/index.js} +14 -30
  24. package/plugins/{audit-plugin.js → core/audit/index.js} +3 -30
  25. package/plugins/{coordinator-plugin.js → core/coordinator/index.js} +3 -35
  26. package/plugins/core/default/bootstrap.js +202 -0
  27. package/plugins/core/default/config.js +220 -0
  28. package/plugins/core/default/index.js +58 -0
  29. package/plugins/core/mcp/index.js +1 -0
  30. package/plugins/{python-plugin-loader.js → core/python-loader/index.js} +7 -187
  31. package/plugins/{rules-plugin.js → core/rules/index.js} +121 -64
  32. package/plugins/{scheduler-plugin.js → core/scheduler/index.js} +12 -114
  33. package/plugins/{session-plugin.js → core/session/index.js} +9 -73
  34. package/{src/capabilities/skill-manager.js → plugins/core/skill-manager/index.js} +64 -18
  35. package/plugins/{storage-plugin.js → core/storage/index.js} +5 -29
  36. package/plugins/{subagent-plugin.js → core/sub-agent/index.js} +10 -171
  37. package/plugins/{think-plugin.js → core/think/index.js} +24 -91
  38. package/{src/capabilities/workflow-engine.js → plugins/core/workflow/index.js} +87 -85
  39. package/plugins/default-plugins.js +6 -720
  40. package/plugins/{data-splitter-plugin.js → executors/data-splitter/index.js} +9 -83
  41. package/plugins/{extension-executor-plugin.js → executors/extension/index.js} +13 -97
  42. package/plugins/{python-executor-plugin.js → executors/python/index.js} +6 -31
  43. package/plugins/{shell-executor-plugin.js → executors/shell/index.js} +2 -5
  44. package/plugins/install/README.md +9 -0
  45. package/plugins/{install-plugin.js → install/index.js} +3 -3
  46. package/plugins/{file-system-plugin.js → io/file-system/index.js} +34 -236
  47. package/plugins/{web-plugin.js → io/web/index.js} +11 -113
  48. package/plugins/memory/README.md +13 -0
  49. package/plugins/{memory-plugin.js → memory/index.js} +4 -18
  50. package/plugins/messaging/email/README.md +19 -0
  51. package/plugins/{email → messaging/email}/index.js +2 -2
  52. package/plugins/{feishu-plugin.js → messaging/feishu/index.js} +3 -3
  53. package/plugins/{qq-plugin.js → messaging/qq/index.js} +5 -16
  54. package/plugins/{telegram-plugin.js → messaging/telegram/index.js} +3 -3
  55. package/plugins/{weixin-plugin.js → messaging/weixin/index.js} +15 -15
  56. package/plugins/{plugin-manager-plugin.js → plugin-manager/index.js} +36 -180
  57. package/plugins/{tools-plugin.js → tools/index.js} +68 -116
  58. package/plugins/trading/README.md +15 -0
  59. package/plugins/{gate-trading.js → trading/index.js} +8 -8
  60. package/{examples → sandbox}/test-concurrent-chat.js +2 -2
  61. package/{examples → sandbox}/test-long-chat.js +2 -2
  62. package/{examples → sandbox}/test-session-chat.js +2 -2
  63. package/{examples → sandbox}/test-web-plugin.js +1 -1
  64. package/{examples → sandbox}/test-weixin-feishu.js +2 -2
  65. package/src/agent/base.js +56 -0
  66. package/src/{core/agent-chat.js → agent/chat.js} +11 -11
  67. package/src/{core/coordinator-manager.js → agent/coordinator.js} +3 -3
  68. package/src/agent/index.js +111 -0
  69. package/src/agent/main.js +337 -0
  70. package/src/agent/prompt.js +78 -0
  71. package/src/agent/sub.js +198 -0
  72. package/src/agent/worker.js +104 -0
  73. package/{cli/bin/foliko.js → src/cli/bin.js} +1 -1
  74. package/{cli/src → src/cli}/commands/chat.js +25 -21
  75. package/{cli/src → src/cli}/index.js +1 -0
  76. package/{cli/src → src/cli}/ui/chat-ui-old.js +40 -178
  77. package/{cli/src → src/cli}/ui/chat-ui.js +3 -3
  78. package/{cli/src → src/cli}/ui/components/footer-bar.js +1 -1
  79. package/src/{core → common}/constants.js +3 -0
  80. package/src/common/errors.js +402 -0
  81. package/src/{utils → common}/logger.js +33 -0
  82. package/src/{utils/chat-queue.js → common/queue.js} +2 -2
  83. package/src/config/plugin-config.js +50 -0
  84. package/src/context/agent.js +32 -0
  85. package/src/context/compaction-prompts.js +170 -0
  86. package/src/context/compaction-utils.js +191 -0
  87. package/src/context/compressor.js +413 -0
  88. package/src/context/index.js +9 -0
  89. package/src/{core/context-manager.js → context/manager.js} +1 -1
  90. package/src/context/request.js +50 -0
  91. package/src/context/session.js +33 -0
  92. package/src/context/storage.js +30 -0
  93. package/src/executors/mcp-client.js +153 -0
  94. package/src/executors/mcp-desc.js +236 -0
  95. package/src/executors/mcp-executor.js +91 -956
  96. package/src/{core → framework}/command-registry.js +1 -1
  97. package/src/framework/framework.js +300 -0
  98. package/src/framework/index.js +18 -0
  99. package/src/framework/lifecycle.js +203 -0
  100. package/src/framework/loader.js +78 -0
  101. package/src/framework/registry.js +86 -0
  102. package/src/{core/ui-extension-context.js → framework/ui-extension.js} +1 -1
  103. package/src/index.js +130 -15
  104. package/src/llm/index.js +26 -0
  105. package/src/llm/provider.js +212 -0
  106. package/src/llm/registry.js +11 -0
  107. package/src/{core/token-counter.js → llm/tokens.js} +4 -37
  108. package/src/{core/plugin-base.js → plugin/base.js} +10 -136
  109. package/src/plugin/index.js +14 -0
  110. package/src/plugin/loader.js +101 -0
  111. package/src/plugin/manager.js +261 -0
  112. package/src/{core → session}/branch-summary-auto.js +2 -2
  113. package/src/{core/chat-session.js → session/chat.js} +2 -2
  114. package/src/session/index.js +7 -0
  115. package/src/{core/session-manager.js → session/session.js} +2 -2
  116. package/src/session/ttl.js +92 -0
  117. package/src/{core/jsonl-storage.js → storage/jsonl.js} +1 -1
  118. package/src/tool/executor.js +85 -0
  119. package/src/tool/index.js +15 -0
  120. package/src/tool/registry.js +143 -0
  121. package/src/{core/tool-router.js → tool/router.js} +17 -124
  122. package/src/tool/schema.js +108 -0
  123. package/src/utils/data-splitter.js +1 -1
  124. package/src/utils/download.js +1 -1
  125. package/src/utils/index.js +6 -6
  126. package/src/utils/message-validator.js +1 -1
  127. package/tests/core/context-storage.test.js +46 -0
  128. package/tests/core/llm.test.js +54 -0
  129. package/tests/core/plugin.test.js +42 -0
  130. package/tests/core/tool.test.js +60 -0
  131. package/tests/setup.js +10 -0
  132. package/tests/smoke.test.js +58 -0
  133. package/vitest.config.js +9 -0
  134. package/cli/src/daemon.js +0 -149
  135. package/docs/CONTEXT_DESIGN.md +0 -1596
  136. package/docs/ai-sdk-optimization.md +0 -655
  137. package/docs/features.md +0 -120
  138. package/docs/qq-bot.md +0 -976
  139. package/docs/quick-reference.md +0 -160
  140. package/docs/user-manual.md +0 -1391
  141. package/images/geometric_shapes.jpg +0 -0
  142. package/images/sunset_mountain_lake.jpg +0 -0
  143. package/skills/poster-guide/SKILL.md +0 -792
  144. package/src/capabilities/index.js +0 -11
  145. package/src/core/agent.js +0 -808
  146. package/src/core/context-compressor.js +0 -959
  147. package/src/core/enhanced-context-compressor.js +0 -210
  148. package/src/core/framework.js +0 -1422
  149. package/src/core/index.js +0 -30
  150. package/src/core/plugin-manager.js +0 -961
  151. package/src/core/provider-registry.js +0 -159
  152. package/src/core/provider.js +0 -156
  153. package/src/core/request-context.js +0 -98
  154. package/src/core/subagent.js +0 -442
  155. package/src/core/system-prompt-builder.js +0 -120
  156. package/src/core/tool-executor.js +0 -202
  157. package/src/core/tool-registry.js +0 -517
  158. package/src/core/worker-agent.js +0 -192
  159. package/src/executors/executor-base.js +0 -58
  160. package/src/utils/error-boundary.js +0 -363
  161. package/src/utils/error.js +0 -374
  162. package/system.md +0 -1645
  163. package/website_v2/README.md +0 -57
  164. package/website_v2/SPEC.md +0 -1
  165. package/website_v2/docs/api.html +0 -128
  166. package/website_v2/docs/configuration.html +0 -147
  167. package/website_v2/docs/plugin-development.html +0 -129
  168. package/website_v2/docs/project-structure.html +0 -89
  169. package/website_v2/docs/skill-development.html +0 -85
  170. package/website_v2/index.html +0 -489
  171. package/website_v2/scripts/main.js +0 -93
  172. package/website_v2/styles/animations.css +0 -8
  173. package/website_v2/styles/docs.css +0 -83
  174. package/website_v2/styles/main.css +0 -417
  175. package/xhs_auth.json +0 -268
  176. package//346/265/267/346/212/245/346/217/222/344/273/266.md +0 -621
  177. /package/plugins/{ambient-agent → ambient}/constants.js +0 -0
  178. /package/plugins/{email → messaging/email}/constants.js +0 -0
  179. /package/plugins/{email → messaging/email}/handlers.js +0 -0
  180. /package/plugins/{email → messaging/email}/monitor.js +0 -0
  181. /package/plugins/{email → messaging/email}/parser.js +0 -0
  182. /package/plugins/{email → messaging/email}/reply.js +0 -0
  183. /package/plugins/{email → messaging/email}/utils.js +0 -0
  184. /package/{examples → sandbox}/test-chat.js +0 -0
  185. /package/{examples → sandbox}/test-mcp.js +0 -0
  186. /package/{examples → sandbox}/test-reload.js +0 -0
  187. /package/{examples → sandbox}/test-telegram.js +0 -0
  188. /package/{examples → sandbox}/test-tg-bot.js +0 -0
  189. /package/{examples → sandbox}/test-tg-simple.js +0 -0
  190. /package/{examples → sandbox}/test-tg.js +0 -0
  191. /package/{examples → sandbox}/test-think.js +0 -0
  192. /package/src/{core/sub-agent-config.js → agent/sub-config.js} +0 -0
  193. /package/{cli/src → src/cli}/commands/daemon.js +0 -0
  194. /package/{cli/src → src/cli}/commands/list.js +0 -0
  195. /package/{cli/src → src/cli}/commands/plugin.js +0 -0
  196. /package/{cli/src → src/cli}/ui/components/agent-mention-provider.js +0 -0
  197. /package/{cli/src → src/cli}/ui/components/chained-autocomplete-provider.js +0 -0
  198. /package/{cli/src → src/cli}/ui/components/message-bubble.js +0 -0
  199. /package/{cli/src → src/cli}/ui/components/status-bar.js +0 -0
  200. /package/{cli/src → src/cli}/utils/ansi.js +0 -0
  201. /package/{cli/src → src/cli}/utils/config.js +0 -0
  202. /package/{cli/src → src/cli}/utils/markdown.js +0 -0
  203. /package/{cli/src → src/cli}/utils/plugin-config.js +0 -0
  204. /package/{cli/src → src/cli}/utils/render-diff.js +0 -0
  205. /package/src/{utils/circuit-breaker.js → common/circuit.js} +0 -0
  206. /package/src/{utils/edit-diff.js → common/diff.js} +0 -0
  207. /package/src/{utils/event-emitter.js → common/events.js} +0 -0
  208. /package/src/{utils → common}/id.js +0 -0
  209. /package/src/{utils → common}/retry.js +0 -0
  210. /package/src/{core/notification-manager.js → notification/manager.js} +0 -0
  211. /package/src/{core/session-entry.js → session/entry.js} +0 -0
  212. /package/src/{core/storage-manager.js → storage/manager.js} +0 -0
@@ -1,1422 +0,0 @@
1
- /**
2
- * Framework 核心容器
3
- * 管理所有子系统,提供统一入口
4
- */
5
-
6
- const path = require('path');
7
- const os = require('os');
8
- const { AsyncLocalStorage } = require('async_hooks');
9
- const { EventEmitter } = require('../utils/event-emitter');
10
- const { PluginManager } = require('./plugin-manager');
11
- const { ToolRegistry } = require('./tool-registry');
12
- const { Agent } = require('./agent');
13
- const { Subagent } = require('./subagent');
14
- const { SubAgentConfigManager } = require('./sub-agent-config');
15
- const { ContextManager } = require('./context-manager');
16
- const { SessionManager } = require('./session-manager');
17
- const { CoordinatorManager } = require('./coordinator-manager');
18
- const { Logger, LOG_LEVELS } = require('../utils/logger');
19
- const { AGENT_DIR, HOME_AGENT_DIR_NAME, SYSTEM_PLUGINS } = require('./constants');
20
- // 创建一个连接到 Framework 的 logger
21
- function createFrameworkLogger(framework) {
22
- const logger = new Logger({ namespace: 'foliko' });
23
- const state = { silent: false };
24
-
25
- // 获取当前有效的级别(优先用全局级别)
26
- const getLevel = () => Logger.globalLevel ?? logger._level ?? LOG_LEVELS.INFO;
27
-
28
- // 日志级别配置表,消除 4×2 重复代码
29
- const LEVEL_CONFIG = [
30
- ['info', LOG_LEVELS.INFO, 'INFO'],
31
- ['warn', LOG_LEVELS.WARN, 'WARN'],
32
- ['error', LOG_LEVELS.ERROR, 'ERROR'],
33
- ['debug', LOG_LEVELS.DEBUG, 'DEBUG'],
34
- ];
35
-
36
- // 通用日志拦截包装器
37
- function wrapLogMethod(targetLogger, originalMethod, levelThreshold, levelName) {
38
- return (...args) => {
39
- const lvl = getLevel();
40
- if (lvl <= levelThreshold) {
41
- if (!state.silent) originalMethod(...args);
42
- framework.emit('log', { level: levelName, message: args.join(' '), namespace: targetLogger.namespace });
43
- }
44
- };
45
- }
46
-
47
- // 包装所有级别
48
- const originals = {};
49
- for (const [name] of LEVEL_CONFIG) {
50
- originals[name] = logger[name].bind(logger);
51
- }
52
- for (const [name, threshold, label] of LEVEL_CONFIG) {
53
- logger[name] = wrapLogMethod(logger, originals[name], threshold, label);
54
- }
55
-
56
- // 拦截 child 方法
57
- const originalChild = logger.child.bind(logger);
58
- logger.child = (...args) => {
59
- const childLogger = originalChild(...args);
60
- const childOriginals = {};
61
- for (const [name] of LEVEL_CONFIG) {
62
- childOriginals[name] = childLogger[name].bind(childLogger);
63
- }
64
- for (const [name, threshold, label] of LEVEL_CONFIG) {
65
- childLogger[name] = wrapLogMethod(childLogger, childOriginals[name], threshold, label);
66
- }
67
- return childLogger;
68
- };
69
-
70
- // 暴露 setGlobalLogLevel 方法
71
- logger.setGlobalLogLevel = (level) => { Logger.globalLevel = level; };
72
-
73
- // 暴露 setSilent 方法
74
- logger.setSilent = (v) => { state.silent = v; };
75
-
76
- return logger;
77
- }
78
-
79
- // 添加框架目录的 node_modules 到全局搜索路径
80
- // 让项目插件能 require 框架内置的包(如 zod)
81
- const frameworkNodeModules = path.join(__dirname, '..', '..', 'node_modules');
82
- if (!module.paths.includes(frameworkNodeModules)) {
83
- module.paths.unshift(frameworkNodeModules);
84
- }
85
-
86
- // 现有的 AsyncLocalStorage(保持向后兼容)
87
- const asyncLocalStorage = new AsyncLocalStorage();
88
-
89
- // 新增:两层 AsyncLocalStorage
90
- const requestStorage = new AsyncLocalStorage(); // Request 级别
91
- const sessionStorage = new AsyncLocalStorage(); // Session 级别
92
-
93
- class Framework extends EventEmitter {
94
- /**
95
- * @param {Object} config - 配置
96
- * @param {boolean} [config.debug=false] - 调试模式
97
- * @param {boolean} [config.silent=false] - 是否静默(不输出日志)
98
- * @param {string} [config.cwd] - 初始工作目录(默认 process.cwd(),可后续用 setCwd 切换)
99
- * @param {string} [config.agentDir='.foliko'] - 项目级 Agent 目录名
100
- * @param {string} [config.homeAgentDir] - 用户级 Agent 目录(默认 ~/.foliko,可被 FOLIKO_HOME 环境变量覆盖)
101
- */
102
- constructor(config = {}) {
103
- super();
104
- this._silent=config.silent||false;
105
- this._debug = config.debug || false;
106
- this._ready = false;
107
- this._readyPromise = null;
108
- this._resolveReady = null;
109
-
110
- // 初始化子系统
111
- this.toolRegistry = new ToolRegistry();
112
- this.pluginManager = new PluginManager(this);
113
-
114
- // Agent 管理
115
- this._agents = []; // 所有创建的 agent
116
- this._mainAgent = null; // 主 agent
117
-
118
- // 保存 toolRegistry 监听器引用,用于清理
119
- this._toolRegistryListeners = [];
120
-
121
- // 子代理配置管理器
122
- this._subAgentConfigManager = null;
123
-
124
- // 执行上下文(工具调用时可用)
125
- this._executionContext = null;
126
-
127
- // ========== 分层上下文系统 ==========
128
- // Context 管理器
129
- this._contextManager = new ContextManager(this);
130
-
131
- // SessionContext 缓存(轻量管理,不依赖 ContextManager)
132
- this._sessionContexts = new Map(); // sessionId → SessionManager
133
-
134
- // Session 存储配置(JSONL 格式)
135
- this._sessionStorageConfig = {
136
- baseDir: '.foliko/sessions',
137
- };
138
-
139
- // 工作目录与 Agent 目录
140
- this._cwd = config.cwd || process.cwd();
141
- this._agentDir = config.agentDir || AGENT_DIR;
142
- this._homeAgentDir =
143
- config.homeAgentDir ||
144
- process.env.FOLIKO_HOME ||
145
- path.join(os.homedir(), HOME_AGENT_DIR_NAME);
146
- this._changingCwd = false;
147
-
148
- // 注册 logger 到 framework
149
- this.logger = createFrameworkLogger(this);
150
-
151
- // Coordinator管理器(用于Coordinator模式)
152
- this._coordinatorManager = null;
153
-
154
- // 事件转发 - ToolRegistry 事件
155
- const toolRegisteredHandler = (tool) => {
156
- this.emit('tool:registered', tool);
157
- };
158
- const toolCallHandler = (data) => {
159
- this.emit('tool:call', data);
160
- // 兼容连字符格式
161
- this.emit('tool-call', data);
162
- };
163
- const toolResultHandler = (data) => {
164
- this.emit('tool:result', data);
165
- };
166
- const toolErrorHandler = (data) => {
167
- this.emit('tool:error', data);
168
- };
169
- this.toolRegistry.on('tool:registered', toolRegisteredHandler);
170
- this.toolRegistry.on('tool:call', toolCallHandler);
171
- this.toolRegistry.on('tool:result', toolResultHandler);
172
- this.toolRegistry.on('tool:error', toolErrorHandler);
173
- this._toolRegistryListeners = [
174
- { event: 'tool:registered', handler: toolRegisteredHandler },
175
- { event: 'tool:call', handler: toolCallHandler },
176
- { event: 'tool:result', handler: toolResultHandler },
177
- { event: 'tool:error', handler: toolErrorHandler },
178
- ];
179
-
180
- // 事件转发 - Agent 事件
181
- this._setupAgentEventForwarding();
182
-
183
- this._registerBuiltinTools();
184
- }
185
-
186
- /**
187
- * 设置 Agent 事件转发
188
- * @private
189
- */
190
- _setupAgentEventForwarding() {
191
- // 保存 agent 创建时的转发监听器引用,用于清理
192
- this._agentForwardingListeners = new Map();
193
- // 监听 agent:created 事件,为新创建的 Agent 设置事件转发
194
- this.on('agent:created', (agent) => {
195
- // 转发 Agent 的 tool-call/result/error 事件
196
- const toolCallHandler = (data) => {
197
- this.emit('tool:call', data);
198
- };
199
- const toolResultHandler = (data) => {
200
- this.emit('tool:result', data);
201
- };
202
- const toolErrorHandler = (data) => {
203
- this.emit('tool:error', data);
204
- };
205
- const messageHandler = (msg) => {
206
- this.emit('agent:message', msg);
207
- };
208
- agent.on('tool-call', toolCallHandler);
209
- agent.on('tool-result', toolResultHandler);
210
- agent.on('tool-error', toolErrorHandler);
211
- agent.on('message', messageHandler);
212
- // 保存引用用于后续清理
213
- this._agentForwardingListeners.set(agent, {
214
- toolCallHandler,
215
- toolResultHandler,
216
- toolErrorHandler,
217
- messageHandler,
218
- });
219
- });
220
- }
221
-
222
- /**
223
- * 从 AI 插件合并配置到目标配置对象
224
- * @param {Object} target - 目标配置对象
225
- * @private
226
- */
227
- _mergeAIConfig(target) {
228
- if (target.apiKey) return target; // 已有 apiKey 不需要合并
229
-
230
- const aiPlugin = this.pluginManager.get('ai');
231
- if (!aiPlugin) return target;
232
-
233
- const defaults = {
234
- apiKey: aiPlugin.config.apiKey,
235
- provider: aiPlugin.config.provider,
236
- model: aiPlugin.config.model,
237
- baseURL: aiPlugin.config.baseURL,
238
- providerOptions: aiPlugin.config.providerOptions || {},
239
- };
240
-
241
- return {
242
- ...target,
243
- apiKey: target.apiKey || defaults.apiKey,
244
- provider: target.provider || defaults.provider,
245
- model: target.model || defaults.model,
246
- baseURL: target.baseURL || defaults.baseURL,
247
- providerOptions: target.providerOptions || defaults.providerOptions,
248
- };
249
- }
250
-
251
- /**
252
- * 注册插件(不加载)
253
- * @param {Plugin|Object} plugin - 插件
254
- */
255
- registerPlugin(plugin) {
256
- this.pluginManager.register(plugin);
257
- return this;
258
- }
259
-
260
- /**
261
- * 注册 LLM Provider
262
- * @param {string} name - Provider 名称
263
- * @param {Object} config - Provider 配置
264
- */
265
- registerProvider(name, config) {
266
- const { registerProvider } = require('./provider');
267
- registerProvider(name, config);
268
- return this;
269
- }
270
-
271
- /**
272
- * 获取 Provider Registry
273
- */
274
- getProviderRegistry() {
275
- const { getProviderRegistry } = require('./provider');
276
- return getProviderRegistry();
277
- }
278
-
279
- /**
280
- * 注册 CLI 命令
281
- * @param {string} name - 命令名称
282
- * @param {Function} handler - 命令处理函数
283
- * @param {string} description - 命令描述
284
- * @param {Function} [getArgumentCompletions] - 自动补全函数
285
- */
286
- registerCommand(name, handler, description, getArgumentCompletions) {
287
- const { getCommandRegistry } = require('./command-registry');
288
- const registry = getCommandRegistry();
289
- registry.registerCommand(name, handler, description, getArgumentCompletions);
290
- return this;
291
- }
292
-
293
- /**
294
- * 获取 CommandRegistry
295
- */
296
- getCommandRegistry() {
297
- const { getCommandRegistry } = require('./command-registry');
298
- return getCommandRegistry();
299
- }
300
-
301
- /**
302
- * 创建 UI 扩展上下文
303
- * @param {string} pluginName - 插件名称
304
- * @param {Object} chatUI - ChatUI 实例
305
- * @returns {ExtensionUIContext}
306
- */
307
- createUIContext(pluginName, chatUI) {
308
- const { ExtensionUIContext } = require('./ui-extension-context');
309
- if (!this._uiExtensions) {
310
- this._uiExtensions = new Map();
311
- }
312
- const ctx = new ExtensionUIContext(chatUI, pluginName);
313
- this._uiExtensions.set(pluginName, ctx);
314
- return ctx;
315
- }
316
-
317
- /**
318
- * 获取 UI 扩展上下文
319
- */
320
- getUIContext(pluginName) {
321
- return this._uiExtensions?.get(pluginName) || null;
322
- }
323
-
324
- /**
325
- * 加载并启动插件)
326
- * @param {Plugin|Object} plugin - 插件
327
- */
328
- async loadPlugin(plugin) {
329
- await this.pluginManager.load(plugin);
330
- return this;
331
- }
332
-
333
- /**
334
- * 卸载插件
335
- * @param {string} name - 插件名称
336
- */
337
- async unloadPlugin(name) {
338
- await this.pluginManager.unload(name);
339
- return this;
340
- }
341
-
342
- /**
343
- * 重载单个插件
344
- * @param {string} name - 插件名称
345
- */
346
- async reloadPlugin(name) {
347
- await this.pluginManager.reload(name);
348
- return this;
349
- }
350
-
351
- /**
352
- * 重载所有插件)
353
- */
354
- async reloadAllPlugins() {
355
- await this.pluginManager.reloadAll();
356
- return this;
357
- }
358
-
359
- /**
360
- * 启用插件
361
- * @param {string} name - 插件名称
362
- */
363
- async enablePlugin(name) {
364
- await this.pluginManager.enable(name);
365
- return this;
366
- }
367
-
368
- /**
369
- * 禁用插件
370
- * @param {string} name - 插件名称
371
- */
372
- async disablePlugin(name) {
373
- await this.pluginManager.disable(name);
374
- return this;
375
- }
376
-
377
- /**
378
- * 更新插件配置
379
- * @param {string} name - 插件名称
380
- * @param {Object} config - 新配置
381
- */
382
- updatePluginConfig(name, config) {
383
- return this.pluginManager.updatePluginConfig(name, config);
384
- }
385
-
386
- /**
387
- * 注册工具
388
- * @param {Object} tool - 工具定义
389
- */
390
- registerTool(tool) {
391
- this.toolRegistry.register(tool);
392
- return this;
393
- }
394
-
395
- /**
396
- * 获取所有工具)
397
- * @returns {Array<Object>}
398
- */
399
- getTools() {
400
- return this.toolRegistry.getAll();
401
- }
402
-
403
- // ============================================================================
404
- // 公开 API — 减少外部直接访问私有属性
405
- // ============================================================================
406
-
407
- /**
408
- * 获取主 Agent
409
- * @returns {Agent|null}
410
- */
411
- getMainAgent() {
412
- return this._mainAgent;
413
- }
414
-
415
- /**
416
- * 获取主 Agent 的 System Prompt
417
- * @returns {string}
418
- */
419
- getSystemPrompt() {
420
- if (this._mainAgent) {
421
- return this._mainAgent._buildSystemPrompt();
422
- }
423
- return '';
424
- }
425
-
426
- /**
427
- * 获取插件实例(安全访问)
428
- * @param {string} name - 插件名称
429
- * @returns {Object|null}
430
- */
431
- getPluginInstance(name) {
432
- const entry = this.pluginManager.get(name);
433
- return entry || null;
434
- }
435
-
436
- /**
437
- * 获取所有 Agent 列表
438
- * @returns {Array<Agent>}
439
- */
440
- getAgents() {
441
- return [...this._agents];
442
- }
443
-
444
- /**
445
- * 获取所有 SessionContext 的 ID 列表
446
- * @returns {string[]}
447
- */
448
- listSessionContexts() {
449
- return Array.from(this._sessionContexts.keys());
450
- }
451
-
452
- // ============================================================================
453
- // 事件描述注册(供 Ambient Agent 使用)
454
- // ============================================================================
455
-
456
- /**
457
- * 注册事件描述
458
- * @param {string} eventType - 事件类型,如 'tool:result'
459
- * @param {string} description - 事件描述
460
- * @param {Object} schema - 事件参数 Schema(可选)
461
- */
462
- registerEventDescription(eventType, description, schema = null) {
463
- this.pluginManager.registerEventDescription(eventType, description, schema);
464
- return this;
465
- }
466
-
467
- /**
468
- * 获取所有已注册的事件描述
469
- * @returns {Map<string, Object>} 事件描述 Map
470
- */
471
- getEventDescriptions() {
472
- return this.pluginManager.getEventDescriptions();
473
- }
474
-
475
- /**
476
- * 获取单个事件描述
477
- * @param {string} eventType - 事件类型
478
- * @returns {Object|null} 事件描述
479
- */
480
- getEventDescription(eventType) {
481
- return this.pluginManager.getEventDescription(eventType);
482
- }
483
-
484
- /**
485
- * 执行工具
486
- * @param {string} name - 工具名称
487
- * @param {Object} args - 参数
488
- */
489
- async executeTool(name, args) {
490
- return this.toolRegistry.execute(name, args, this);
491
- }
492
-
493
- /**
494
- * 在上下文中执行函数(支持并行处理,自动隔离 session)
495
- * @param {Object} context - 执行上下文 { sessionId, ... }
496
- * @param {Function} fn - 要执行的异步函数
497
- * @returns {Promise}
498
- */
499
- runWithContext(context, fn) {
500
- return asyncLocalStorage.run(context, fn);
501
- }
502
-
503
- /**
504
- * 获取当前执行上下文(在 runWithContext 内部调用)
505
- * @returns {Object|null}
506
- */
507
- getExecutionContext() {
508
- return asyncLocalStorage.getStore() || null;
509
- }
510
-
511
- /**
512
- * @deprecated 使用 runWithContext 代替
513
- */
514
- setExecutionContext() {}
515
-
516
- /**
517
- * @deprecated 使用 runWithContext 代替
518
- */
519
- clearExecutionContext() {}
520
-
521
- // ============================================================================
522
- // 分层上下文系统 - Session Context
523
- // ============================================================================
524
-
525
- /**
526
- * 获取或创建 SessionManager
527
- * @param {string} sessionId - 会话 ID
528
- * @param {Object} options - 配置选项
529
- * @returns {SessionManager}
530
- */
531
- getOrCreateSessionContext(sessionId, options = {}) {
532
- if (!sessionId) {
533
- return null;
534
- }
535
-
536
- // 先检查内存缓存
537
- let manager = this._sessionContexts.get(sessionId);
538
- if (manager) {
539
- return manager;
540
- }
541
-
542
- // 获取工作目录
543
- const cwd = options.cwd || this._cwd;
544
- const sessionDir = path.join(cwd, '.foliko', 'sessions');
545
-
546
- // 获取或创建 session file path
547
- const sessionFile = options.sessionFile || path.join(sessionDir, `${sessionId}.jsonl`);
548
-
549
- // 尝试打开已存在的 session
550
- try {
551
- if (require('fs').existsSync(sessionFile)) {
552
- manager = SessionManager.open(sessionFile, sessionDir, cwd);
553
- }
554
- } catch (err) {
555
- // 文件损坏或不存在,创建新的
556
- }
557
-
558
- if (!manager) {
559
- // 创建新的 SessionManager
560
- manager = new SessionManager(cwd, sessionDir, sessionFile, true);
561
- }
562
-
563
- this._sessionContexts.set(sessionId, manager);
564
- this.emit('session:context-created', { sessionId, manager });
565
- return manager;
566
- }
567
-
568
- /**
569
- * 异步加载 SessionManager(如果存在)
570
- * @param {string} sessionId
571
- * @param {Object} options
572
- * @returns {Promise<SessionManager|null>}
573
- */
574
- async loadSessionContext(sessionId, options = {}) {
575
- if (!sessionId) {
576
- return null;
577
- }
578
-
579
- // 先检查内存缓存
580
- let manager = this._sessionContexts.get(sessionId);
581
- if (manager) {
582
- return manager;
583
- }
584
-
585
- const cwd = options.cwd || this._cwd;
586
- const sessionDir = path.join(cwd, '.foliko', 'sessions');
587
- const sessionFile = path.join(sessionDir, `${sessionId}.jsonl`);
588
-
589
- try {
590
- if (require('fs').existsSync(sessionFile)) {
591
- manager = SessionManager.open(sessionFile, sessionDir, cwd);
592
- this._sessionContexts.set(sessionId, manager);
593
- this.emit('session:context-created', { sessionId, manager });
594
- }
595
- } catch (err) {
596
- // 文件不存在或损坏
597
- }
598
-
599
- return manager || null;
600
- }
601
-
602
- /**
603
- * 配置 Session 存储目录
604
- * @param {Object} config - 存储配置
605
- * @param {string} [config.baseDir='.foliko/sessions'] - 文件存储目录
606
- */
607
- configureSessionStorage(config) {
608
- this._sessionStorageConfig = config;
609
- }
610
-
611
- /**
612
- * 获取当前 Session 存储配置
613
- * @returns {Object}
614
- */
615
- getSessionStorage() {
616
- return this._sessionStorageConfig;
617
- }
618
-
619
- /**
620
- * 获取 SessionManager
621
- * @param {string} sessionId - 会话 ID
622
- * @returns {SessionManager|null}
623
- */
624
- getSessionContext(sessionId) {
625
- return this._sessionContexts.get(sessionId) || null;
626
- }
627
-
628
- /**
629
- * 获取当前 Session 上下文(从 AsyncLocalStorage 获取)
630
- * @returns {SessionManager|null}
631
- */
632
- getCurrentSessionContext() {
633
- const store = sessionStorage.getStore();
634
- if (!store) return null;
635
- const sessionId = store.sessionId;
636
- return sessionId ? this._sessionContexts.get(sessionId) || null : null;
637
- }
638
-
639
- /**
640
- * 获取当前 sessionId(从 AsyncLocalStorage 获取)
641
- * @returns {string|null}
642
- */
643
- getCurrentSessionId() {
644
- const store = sessionStorage.getStore();
645
- return store?.sessionId || null;
646
- }
647
-
648
- /**
649
- * 在 Session 上下文中执行函数
650
- * @param {string} sessionId - 会话 ID
651
- * @param {Object} options - Session 选项
652
- * @param {Function} fn - 异步函数
653
- * @returns {Promise}
654
- */
655
- runInSession(sessionId, options, fn) {
656
- if (!sessionId) {
657
- // 无 session,直接执行
658
- return fn();
659
- }
660
-
661
- const manager = this.getOrCreateSessionContext(sessionId, options);
662
- const context = { sessionContext: manager, sessionId };
663
- return sessionStorage.run(context, fn);
664
- }
665
-
666
- /**
667
- * 销毁 Session Manager
668
- * @param {string} sessionId
669
- */
670
- destroySessionContext(sessionId) {
671
- if (this._sessionContexts.has(sessionId)) {
672
- const manager = this._sessionContexts.get(sessionId);
673
- this._sessionContexts.delete(sessionId);
674
- this.emit('session:context-destroyed', { sessionId });
675
- }
676
- }
677
-
678
- // Session TTL 清理配置
679
- _sessionTTL = 30 * 60 * 1000; // 默认 30 分钟
680
- _sessionCleanupInterval = null;
681
-
682
- /**
683
- * 配置 Session TTL
684
- * @param {number} ttlMs - TTL 毫秒数
685
- */
686
- setSessionTTL(ttlMs) {
687
- this._sessionTTL = ttlMs;
688
- return this;
689
- }
690
-
691
- // ============================================================================
692
- // 工作目录与 Agent 目录
693
- // ============================================================================
694
-
695
- /**
696
- * 获取当前工作目录
697
- * @returns {string}
698
- */
699
- getCwd() {
700
- return this._cwd;
701
- }
702
-
703
- /**
704
- * 获取项目级 Agent 目录(绝对路径)
705
- * @returns {string}
706
- */
707
- getAgentDir() {
708
- return path.resolve(this._cwd, this._agentDir);
709
- }
710
-
711
- /**
712
- * 获取用户级 Agent 目录(绝对路径)
713
- * @returns {string}
714
- */
715
- getHomeAgentDir() {
716
- return this._homeAgentDir;
717
- }
718
-
719
- /**
720
- * 解析项目级 Agent 目录下的相对路径
721
- * @param {...string} parts
722
- * @returns {string}
723
- */
724
- resolveAgentPath(...parts) {
725
- return path.join(this.getAgentDir(), ...parts);
726
- }
727
-
728
- /**
729
- * 解析用户级 Agent 目录下的相对路径
730
- * @param {...string} parts
731
- * @returns {string}
732
- */
733
- resolveHomeAgentPath(...parts) {
734
- return path.join(this.getHomeAgentDir(), ...parts);
735
- }
736
-
737
- /**
738
- * 动态切换工作目录
739
- *
740
- * 切换后会自动:
741
- * 1. 更新 PluginManager 状态文件位置
742
- * 2. 销毁已存在的 Session 上下文(旧路径失效)
743
- * 3. 通知所有已加载插件(onCwdChanged 钩子)并触发 reload
744
- * 4. 发送 cwd:changed 事件
745
- *
746
- * @param {string} newCwd - 新工作目录(相对或绝对路径)
747
- * @param {Object} [options]
748
- * @param {boolean} [options.reload=true] - 是否自动重载已加载插件
749
- * @param {string} [options.agentDir] - 顺带覆盖项目 agent 目录名
750
- * @param {string} [options.homeAgentDir] - 顺带覆盖用户 agent 目录路径
751
- * @returns {Promise<this>}
752
- */
753
- async setCwd(newCwd, options = {}) {
754
- if (this._changingCwd) {
755
- throw new Error('setCwd re-entered during cwd change');
756
- }
757
-
758
- const oldCwd = this._cwd;
759
- const resolved = path.resolve(newCwd);
760
- if (resolved === oldCwd) {
761
- return this;
762
- }
763
-
764
- this._changingCwd = true;
765
- try {
766
- this._cwd = resolved;
767
- if (options.agentDir) this._agentDir = options.agentDir;
768
- if (options.homeAgentDir) this._homeAgentDir = options.homeAgentDir;
769
-
770
- // 1. 更新 PluginManager 状态文件位置
771
- this.pluginManager._setStateFile(resolved);
772
-
773
- // 2. 销毁已存在的 Session 上下文(旧路径失效)
774
- const count = this._sessionContexts.size;
775
- for (const sid of Array.from(this._sessionContexts.keys())) {
776
- this.destroySessionContext(sid);
777
- }
778
- this.emit('session:contexts-invalidated', { oldCwd, newCwd: resolved, count });
779
-
780
- // 3. 通知已加载插件 + 触发 reload
781
- if (options.reload !== false) {
782
- for (const entry of this.pluginManager.getAll()) {
783
- const inst = entry.instance;
784
- try {
785
- if (typeof inst?.onCwdChanged === 'function') {
786
- await inst.onCwdChanged(oldCwd, resolved, this);
787
- }
788
- await this.pluginManager.reload(entry.name);
789
- } catch (err) {
790
- this.logger.warn(`Plugin ${entry.name} failed during cwd change: ${err.message}`);
791
- }
792
- }
793
- }
794
-
795
- // 4. 发送 cwd:changed 事件
796
- this.emit('cwd:changed', { oldCwd, newCwd: resolved, framework: this });
797
- } finally {
798
- this._changingCwd = false;
799
- }
800
- return this;
801
- }
802
-
803
- // ============================================================================
804
- // 项目重扫 — 重新扫描当前 cwd 下的插件与技能
805
- // ============================================================================
806
-
807
- /**
808
- * 重新扫描当前 cwd 下的插件与技能目录
809
- *
810
- * 工作流程:
811
- * 1. 卸载 keepLoaded 列表外的所有已加载插件
812
- * 2. 重新读取 DefaultPlugins 配置(新的 skillsDirs / agentsDir / pluginLinks)
813
- * 3. 重新跑 bootstrapDefaults 加载所有默认插件(核心 + 自定义)
814
- * 4. 同步 SkillManager._skillsDirs 并触发技能重扫
815
- * 5. 发出 rescan:complete 事件
816
- *
817
- * 典型用法: 配合 setCwd 切换到新项目
818
- * await framework.setCwd('D:/new-project');
819
- * await framework.rescanProject();
820
- *
821
- * @param {Object} [options]
822
- * @param {string[]} [options.keepLoaded] - 保留的插件名列表(默认保留系统插件)
823
- * @param {boolean} [options.reloadKept=false] - 是否对 keepLoaded 中的插件也执行 reload
824
- * @param {boolean} [options.reloadSkillManager=true] - 是否重扫技能
825
- * @returns {Promise<{unloaded: string[], loaded: string[], keepLoaded: string[]}>}
826
- */
827
- async rescanProject(options = {}) {
828
- const { bootstrapDefaults, DefaultPlugins, loadAgentConfig } = require('../../plugins/default-plugins');
829
- const keepList = options.keepLoaded || Array.from(SYSTEM_PLUGINS);
830
- const keepSet = new Set(keepList);
831
- const unloadList = [];
832
- const loadList = [];
833
-
834
- // 1. 卸载 keepSet 之外的插件
835
- for (const entry of Array.from(this.pluginManager.getAll())) {
836
- if (keepSet.has(entry.name)) continue;
837
- const name = entry.name;
838
- try {
839
- await this.pluginManager.unload(name);
840
- // 同步从注册表移除,避免 bootstrapDefaults 的 has() 跳过
841
- this.pluginManager._plugins.delete(name);
842
- unloadList.push(name);
843
- } catch (err) {
844
- this.logger.warn(`rescanProject: failed to unload ${name}: ${err.message}`);
845
- }
846
- }
847
-
848
- // 2. 确保 defaults 插件已加载,重新读取配置
849
- let defaultsPlugin = this.pluginManager.get('defaults');
850
- if (!defaultsPlugin) {
851
- const dp = new DefaultPlugins({ agentDir: this._agentDir });
852
- try {
853
- await this.loadPlugin(dp);
854
- defaultsPlugin = dp;
855
- } catch (err) {
856
- this.logger.warn(`rescanProject: failed to load defaults: ${err.message}`);
857
- }
858
- } else {
859
- try {
860
- await defaultsPlugin.reload(this);
861
- } catch (err) {
862
- this.logger.warn(`rescanProject: failed to reload defaults: ${err.message}`);
863
- }
864
- }
865
- const agentConfig = defaultsPlugin
866
- ? defaultsPlugin.getConfig() || loadAgentConfig(this, this._agentDir)
867
- : loadAgentConfig(this, this._agentDir);
868
-
869
- // 3. 跑 bootstrapDefaults 加载默认插件(核心 + 自定义)
870
- const beforeNames = new Set(this.pluginManager.getAll().map((p) => p.name));
871
- try {
872
- await bootstrapDefaults(this, { _config: agentConfig, _skipConfigLoad: true });
873
- } catch (err) {
874
- this.logger.warn(`rescanProject: bootstrapDefaults failed: ${err.message}`);
875
- }
876
- for (const name of this.pluginManager.getAll().map((p) => p.name)) {
877
- if (!beforeNames.has(name)) loadList.push(name);
878
- }
879
-
880
- // 4. 同步并重扫技能
881
- if (options.reloadSkillManager !== false) {
882
- const skillManager = this.pluginManager.get('skill-manager');
883
- if (skillManager) {
884
- if (Array.isArray(agentConfig.skillsDirs)) {
885
- skillManager._skillsDirs = agentConfig.skillsDirs;
886
- }
887
- try {
888
- await skillManager.reload(this);
889
- } catch (err) {
890
- this.logger.warn(`rescanProject: skill-manager reload failed: ${err.message}`);
891
- }
892
- }
893
- }
894
-
895
- // 5. (可选) 对保留的插件也执行 reload
896
- if (options.reloadKept) {
897
- for (const name of keepList) {
898
- if (!this.pluginManager.has(name)) continue;
899
- try {
900
- await this.pluginManager.reload(name);
901
- } catch (err) {
902
- this.logger.warn(`rescanProject: reload kept ${name} failed: ${err.message}`);
903
- }
904
- }
905
- }
906
-
907
- const result = { unloaded: unloadList, loaded: loadList, keepLoaded: keepList };
908
- this.emit('rescan:complete', result);
909
- this.logger.info(
910
- `rescanProject: unloaded=${unloadList.length} loaded=${loadList.length} kept=${keepList.length}`
911
- );
912
- return result;
913
- }
914
-
915
- /**
916
- * 启动 Session 自动清理
917
- * @param {number} [intervalMs=60000] - 检查间隔
918
- */
919
- startSessionCleanup(intervalMs = 60000) {
920
- if (this._sessionCleanupInterval) {
921
- clearInterval(this._sessionCleanupInterval);
922
- }
923
- this._sessionCleanupInterval = setInterval(() => {
924
- this._cleanupStaleSessions();
925
- }, intervalMs);
926
- if (this._sessionCleanupInterval.unref) {
927
- this._sessionCleanupInterval.unref();
928
- }
929
- return this;
930
- }
931
-
932
- /**
933
- * 停止 Session 自动清理
934
- */
935
- stopSessionCleanup() {
936
- if (this._sessionCleanupInterval) {
937
- clearInterval(this._sessionCleanupInterval);
938
- this._sessionCleanupInterval = null;
939
- }
940
- return this;
941
- }
942
-
943
- /**
944
- * 清理过期的 Session
945
- * @private
946
- */
947
- _cleanupStaleSessions() {
948
- const now = Date.now();
949
- const staleIds = [];
950
- for (const [sessionId, manager] of this._sessionContexts) {
951
- const metadata = manager.getMetadata?.();
952
- if (metadata && metadata.lastActive && (now - metadata.lastActive) > this._sessionTTL) {
953
- staleIds.push(sessionId);
954
- }
955
- }
956
- for (const id of staleIds) {
957
- this.destroySessionContext(id);
958
- }
959
- if (staleIds.length > 0) {
960
- this.logger.debug(`Cleaned up ${staleIds.length} stale sessions`);
961
- }
962
- }
963
-
964
- /**
965
- * 列出所有活跃的 Session ID
966
- * @returns {string[]}
967
- */
968
- listActiveSessions() {
969
- return Array.from(this._sessionContexts.keys());
970
- }
971
-
972
- // ============================================================================
973
- // 分层上下文系统 - Request Context
974
- // ============================================================================
975
-
976
- /**
977
- * 获取当前 Request 上下文
978
- * @returns {Object|null}
979
- */
980
- getRequestContext() {
981
- return requestStorage.getStore() || null;
982
- }
983
-
984
- /**
985
- * 在 Request 上下文中执行函数
986
- * @param {Object} context - Request 上下文
987
- * @param {Function} fn - 异步函数
988
- * @returns {Promise}
989
- */
990
- runWithRequestContext(context, fn) {
991
- return requestStorage.run(context, fn);
992
- }
993
-
994
- // ============================================================================
995
- // 分层上下文系统 - 组合上下文
996
- // ============================================================================
997
-
998
- /**
999
- * 在完整上下文中执行(Request + Session)
1000
- * @param {Object} requestOptions - Request 选项
1001
- * @param {string} sessionId - Session ID
1002
- * @param {Function} fn - 异步函数
1003
- * @returns {Promise}
1004
- */
1005
- async runInContext(requestOptions, sessionId, fn) {
1006
- const sessionCtx = sessionId ? this.getOrCreateSessionContext(sessionId) : null;
1007
-
1008
- return requestStorage.run(requestOptions, () => {
1009
- if (sessionCtx) {
1010
- return sessionStorage.run({ sessionContext: sessionCtx, sessionId }, fn);
1011
- }
1012
- return fn();
1013
- });
1014
- }
1015
-
1016
- /**
1017
- * 创建 Agent
1018
- * @param {Object} config - Agent 配置
1019
- */
1020
- createAgent(config) {
1021
- // 合并 AI 插件配置
1022
- config = this._mergeAIConfig(config);
1023
-
1024
- const agent = new Agent(this, config);
1025
-
1026
- // 跟踪 agent
1027
- this._agents.push(agent);
1028
- if (!this._mainAgent) {
1029
- this._mainAgent = agent;
1030
- }
1031
-
1032
- this.emit('agent:created', agent);
1033
- return agent;
1034
- }
1035
-
1036
- /**
1037
- * 为指定 session 创建独立 Agent 实例
1038
- * @param {string} sessionId - session ID
1039
- * @param {Object} config - Agent 配置
1040
- */
1041
- createSessionAgent(sessionId, config = {}) {
1042
- const agentConfig = {
1043
- name: `session_${sessionId}`,
1044
- ...config,
1045
- };
1046
-
1047
- // 合并 AI 插件配置
1048
- const merged = this._mergeAIConfig(agentConfig);
1049
-
1050
- const agent = new Agent(this, merged);
1051
- this._agents.push(agent);
1052
-
1053
- // 如果提供了 systemPrompt,需要替换 AgentChatHandler
1054
- if (merged.systemPrompt) {
1055
- agent.setSystemPrompt(merged.systemPrompt);
1056
- }
1057
-
1058
- this.emit('agent:created', agent);
1059
- return agent;
1060
- }
1061
-
1062
- /**
1063
- * 创建子Agent(轻量级,单次任务)
1064
- * @param {Object} config - 子Agent配置
1065
- * @param {string} config.name - 子Agent名称
1066
- * @param {string} [config.role] - 角色描述
1067
- * @param {string} [config.description] - 描述
1068
- * @param {string} [config.systemPrompt] - 直接指定的系统提示词(优先使用)
1069
- * @param {Object} [config.tools] - 自定义工具 { name: toolDef }
1070
- * @param {string[]} [config.parentTools] - 从父Agent继承的工具名称列表
1071
- * @param {Object} [config.llmConfig] - 独立LLM配置
1072
- * @returns {Subagent} 创建的子Agent
1073
- */
1074
- createSubAgent(config) {
1075
- const {
1076
- name,
1077
- role,
1078
- description = '',
1079
- systemPrompt: providedPrompt,
1080
- tools = {},
1081
- parentTools, // 如果不传,默认继承主agent所有工具
1082
- llmConfig = null,
1083
- maxRetries,
1084
- disableTools,
1085
- } = config;
1086
-
1087
- // 获取 AI 配置
1088
- const aiPlugin = this.pluginManager.get('ai');
1089
- const aiConfig = aiPlugin ? aiPlugin.getConfig() : {};
1090
- const mergedAIConfig = { ...aiConfig, ...(llmConfig || {}) };
1091
-
1092
- // 构建工具列表
1093
- // const toolList = [];
1094
-
1095
- // // 添加自定义工具
1096
- // for (const [toolName, toolDef] of Object.entries(tools)) {
1097
- // if (toolDef && typeof toolDef === 'object') {
1098
- // toolList.push({
1099
- // name: toolName,
1100
- // description: toolDef.description || '',
1101
- // inputSchema: toolDef.inputSchema,
1102
- // execute: toolDef.execute,
1103
- // });
1104
- // }
1105
- // }
1106
-
1107
- // 创建 Subagent
1108
- // 如果提供了 systemPrompt 则直接使用,否则让 Subagent 动态构建
1109
- const subagent = new Subagent({
1110
- name: `subagent_${name}`,
1111
- role: role || name,
1112
- description: description,
1113
- systemPrompt: providedPrompt,
1114
- model: mergedAIConfig.model || 'deepseek-chat',
1115
- provider: mergedAIConfig.provider || 'deepseek',
1116
- apiKey: mergedAIConfig.apiKey,
1117
- baseURL: mergedAIConfig.baseURL,
1118
- providerOptions: {
1119
- maxOutputTokens: mergedAIConfig.maxOutputTokens || 8192,
1120
- temperature: mergedAIConfig.temperature || 0.3,
1121
- },
1122
- tools: tools,
1123
- parentTools: parentTools,
1124
- framework: this, // 传递 framework 引用用于动态构建系统提示词
1125
- maxRetries,
1126
- disableTools,
1127
- });
1128
-
1129
- // 注册主 Agent 的提示词部分到子 Agent(选择性注册)
1130
- if (this._mainAgent && this._mainAgent._systemPromptBuilder) {
1131
- const mainParts = this._mainAgent._systemPromptBuilder._parts;
1132
- if (mainParts) {
1133
- for (const [name, part] of mainParts) {
1134
- // 子 Agent 跳过这些不适合的部分
1135
- if (['original-prompt', 'shared-prompt', 'datetime', 'metadata', 'capabilities'].includes(name)) {
1136
- continue;
1137
- }
1138
- subagent.registerPromptPart(name, part.priority, part.provider);
1139
- }
1140
- }
1141
- }
1142
-
1143
- this._agents.push(subagent);
1144
- this.emit('agent:created', subagent);
1145
- return subagent;
1146
- }
1147
-
1148
- /**
1149
- * 同步主Agent的提示词部分到所有子Agent
1150
- * @public — 插件可能需要调用
1151
- */
1152
- syncPromptPartsToSubagents() {
1153
- if (!this._mainAgent || !this._mainAgent._systemPromptBuilder) return;
1154
- const mainParts = this._mainAgent._systemPromptBuilder._parts;
1155
- if (!mainParts) return;
1156
-
1157
- // 遍历所有 agents,排除主agent
1158
- for (const agent of this._agents) {
1159
- if (agent === this._mainAgent) continue;
1160
- // 只同步 Subagent
1161
- if (!agent._isSubagent) continue;
1162
-
1163
- // Build a Set of already registered parts for O(1) lookup
1164
- const registeredNames = new Set(agent._promptParts?.map(p => p.name) || []);
1165
-
1166
- for (const [name, part] of mainParts) {
1167
- // 子 Agent 跳过这些不适合的部分
1168
- if (['original-prompt', 'shared-prompt', 'datetime', 'metadata', 'capabilities'].includes(name)) {
1169
- continue;
1170
- }
1171
- // O(1) lookup instead of O(m)
1172
- if (!registeredNames.has(name)) {
1173
- agent.registerPromptPart(name, part.priority, part.provider);
1174
- registeredNames.add(name);
1175
- }
1176
- }
1177
- }
1178
- }
1179
-
1180
- /**
1181
- * 构建子Agent的系统提示词
1182
- * @private
1183
- */
1184
- _buildSubAgentSystemPrompt(role, description, tools) {
1185
- const lines = [];
1186
- const roleName = role || '助手';
1187
- const descText = description || '';
1188
-
1189
- // 1. 角色定义
1190
- lines.push(`你是 ${roleName}。`);
1191
- if (descText) {
1192
- lines.push(descText);
1193
- }
1194
- lines.push('');
1195
-
1196
- // 2. 主Agent的系统提示词(基础部分)
1197
- const mainAgent = this.getMainAgent();
1198
- if (mainAgent) {
1199
- const mainPrompt = mainAgent.getOriginalPrompt();
1200
- if (mainPrompt) {
1201
- lines.push('## 主Agent的系统提示词');
1202
- lines.push(mainPrompt);
1203
- lines.push('');
1204
- }
1205
- }
1206
-
1207
- // 3. 工具描述
1208
- if (tools && tools.length > 0) {
1209
- lines.push('## 可用工具');
1210
- lines.push('');
1211
- for (const tool of tools) {
1212
- const desc = tool.description || '无描述';
1213
- lines.push(`- **${tool.name}**: ${desc}`);
1214
- }
1215
- lines.push('');
1216
- }
1217
-
1218
- // 4. 子Agent核心规则
1219
- lines.push(`## ${roleName} 核心规则
1220
-
1221
- 1. **专注本职任务**:只完成与 ${roleName} 相关的任务,不要处理其他领域的请求。
1222
- 2. **必须先调用工具**:需要信息或操作时,必须调用工具获取真实结果,禁止直接编造。
1223
- 3. **结果导向**:基于工具返回结果回答,不重复工具内部实现细节。
1224
- 4. **多步骤任务**:复杂任务拆解为多个工具调用,逐步完成。
1225
- 5. **响应规范**:直接给出结论,不说"我需要..."等铺垫话术。
1226
-
1227
- ## 禁止事项
1228
-
1229
- - 不调用工具就直接回答
1230
- - 编造数据、文件、订单等信息
1231
- - 处理本职外的任务
1232
- - 回复含糊不清`);
1233
-
1234
- return lines.join('\n');
1235
- }
1236
-
1237
- /**
1238
- * 获取 AI 插件
1239
- * @returns {Object|undefined}
1240
- */
1241
- getAIPlugin() {
1242
- return this.pluginManager.get('ai');
1243
- }
1244
-
1245
- /**
1246
- * Bootstrap - 使用默认配置启动框架
1247
- * 自动加载 .foliko/ 目录下的配置和所有默认插件)
1248
- * @param {Object} config - 配置
1249
- * @param {string} [config.agentDir='.foliko'] - Agent 配置目录
1250
- * @param {Object} [config.aiConfig] - AI 配置(可选,覆盖文件配置)
1251
- * @returns {Promise<Framework>}
1252
- */
1253
- async bootstrap(config = {}) {
1254
- const {
1255
- bootstrapDefaults,
1256
- DefaultPlugins,
1257
- loadAgentConfig,
1258
- } = require('../../plugins/default-plugins');
1259
-
1260
- // 先加载默认插件配置
1261
- const defaultsPlugin = new DefaultPlugins({
1262
- agentDir: config.agentDir || '.foliko',
1263
- });
1264
-
1265
- await this.loadPlugin(defaultsPlugin);
1266
-
1267
- // 获取配置
1268
- const agentConfig = defaultsPlugin.getConfig();
1269
-
1270
- // 如果提供了 AI 配置,覆盖文件配置
1271
- if (config.aiConfig) {
1272
- agentConfig.ai = { ...agentConfig.ai, ...config.aiConfig };
1273
- }
1274
-
1275
- // 加载所有默认插件(传入已加载的配置避免重复加载)
1276
- await bootstrapDefaults(this, { _config: agentConfig, _skipConfigLoad: true });
1277
-
1278
- this._setReady();
1279
- return this;
1280
- }
1281
-
1282
- /**
1283
- * 等待框架就绪
1284
- */
1285
- async ready() {
1286
- if (this._ready) {
1287
- return this;
1288
- }
1289
-
1290
- if (!this._readyPromise) {
1291
- this._readyPromise = new Promise((resolve) => {
1292
- this._resolveReady = resolve;
1293
- });
1294
- }
1295
-
1296
- await this._readyPromise;
1297
- return this;
1298
- }
1299
-
1300
- /**
1301
- * 标记框架就绪
1302
- * @private
1303
- */
1304
- _setReady() {
1305
- this._ready = true;
1306
- if (this._resolveReady) {
1307
- this._resolveReady(this);
1308
- }
1309
- this.emit('framework:ready', this);
1310
- }
1311
-
1312
- /**
1313
- * 注册内置工具
1314
- * @private
1315
- */
1316
- _registerBuiltinTools() {
1317
- // 内置工具会在 Agent 创建时添加)
1318
- }
1319
-
1320
- // ============================================================================
1321
- // Coordinator 模式管理
1322
- // ============================================================================
1323
-
1324
- /**
1325
- * 初始化Coordinator管理器
1326
- * @param {Agent} coordinatorAgent - Coordinator Agent实例
1327
- */
1328
- initCoordinatorManager(coordinatorAgent) {
1329
- if (!this._coordinatorManager) {
1330
- this._coordinatorManager = new CoordinatorManager(this);
1331
- }
1332
- this._coordinatorManager.setCoordinatorAgent(coordinatorAgent);
1333
- return this._coordinatorManager;
1334
- }
1335
-
1336
- /**
1337
- * 获取Coordinator管理器
1338
- * @returns {CoordinatorManager|null}
1339
- */
1340
- getCoordinatorManager() {
1341
- return this._coordinatorManager;
1342
- }
1343
-
1344
- /**
1345
- * 创建Worker(由coordinator-plugin调用)
1346
- * @param {Object} config - Worker配置
1347
- * @param {string} config.name - Worker名称
1348
- * @param {string} config.task - 初始任务
1349
- * @param {string} [config.systemPrompt] - 可选系统提示
1350
- * @param {string[]} [config.tools] - 可用工具列表
1351
- * @returns {Promise<{workerId: string, status: string}>}
1352
- */
1353
- async createWorker(config) {
1354
- if (!this._coordinatorManager) {
1355
- throw new Error('CoordinatorManager not initialized. Call initCoordinatorManager first.');
1356
- }
1357
- return this._coordinatorManager.createWorker(config);
1358
- }
1359
-
1360
- /**
1361
- * 销毁框架
1362
- */
1363
- async destroy() {
1364
- // 销毁Coordinator管理器
1365
- if (this._coordinatorManager) {
1366
- this._coordinatorManager.destroy();
1367
- this._coordinatorManager = null;
1368
- }
1369
-
1370
- // 销毁所有 agents
1371
- for (const agent of this._agents) {
1372
- // 移除 agent 的事件转发监听器
1373
- const listeners = this._agentForwardingListeners?.get(agent);
1374
- if (listeners) {
1375
- agent.off('tool-call', listeners.toolCallHandler);
1376
- agent.off('tool-result', listeners.toolResultHandler);
1377
- agent.off('tool-error', listeners.toolErrorHandler);
1378
- agent.off('message', listeners.messageHandler);
1379
- this._agentForwardingListeners.delete(agent);
1380
- }
1381
- // 调用 agent.destroy() 清理自身资源
1382
- if (typeof agent.destroy === 'function') {
1383
- agent.destroy();
1384
- }
1385
- }
1386
- this._agents = [];
1387
-
1388
- // 移除 toolRegistry 的事件监听器
1389
- if (this._toolRegistryListeners) {
1390
- for (const { event, handler } of this._toolRegistryListeners) {
1391
- this.toolRegistry.off(event, handler);
1392
- }
1393
- this._toolRegistryListeners = [];
1394
- }
1395
-
1396
- // 停止 Session 清理
1397
- this.stopSessionCleanup();
1398
-
1399
- // 清空 session contexts
1400
- this._sessionContexts.clear();
1401
-
1402
- // 卸载所有插件(逐个保护,防止一个失败阻断后续)
1403
- const plugins = this.pluginManager.getAll();
1404
- for (const { name } of plugins) {
1405
- try {
1406
- await this.pluginManager.unload(name);
1407
- } catch (err) {
1408
- this.logger.warn(`Failed to unload plugin '${name}': ${err.message}`);
1409
- }
1410
- }
1411
-
1412
- // 清空工具
1413
- this.toolRegistry.clear();
1414
-
1415
- // 清空事件
1416
- this.removeAllListeners();
1417
-
1418
- this.emit('framework:destroyed');
1419
- }
1420
- }
1421
-
1422
- module.exports = { Framework };