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
@@ -3,7 +3,7 @@
3
3
  * 允许插件注册 CLI 命令,支持 Commander.js 参数解析
4
4
  */
5
5
 
6
- const { logger } = require('../utils/logger');
6
+ const { logger } = require('../common/logger');
7
7
  const { Command } = require('commander');
8
8
 
9
9
  class CommandRegistry {
@@ -0,0 +1,300 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Framework 核心容器
5
+ * 管理所有子系统,提供统一入口
6
+ * 目标 ~250 行
7
+ */
8
+
9
+ const path = require('path');
10
+ const os = require('os');
11
+ const { AsyncLocalStorage } = require('async_hooks');
12
+ const { EventEmitter } = require('../common/events');
13
+ const { PluginManager } = require('../plugin/manager');
14
+ const { ToolRegistry } = require('../tool/registry');
15
+ const { ContextManager } = require('../context/manager');
16
+ const { createFrameworkLogger } = require('../common/logger');
17
+ const { AGENT_DIR, HOME_AGENT_DIR_NAME } = require('../common/constants');
18
+ const { SessionTTL } = require('../session/ttl');
19
+
20
+ // Add framework node_modules to search path
21
+ const frameworkNodeModules = path.join(__dirname, '..', '..', 'node_modules');
22
+ if (!module.paths.includes(frameworkNodeModules)) {
23
+ module.paths.unshift(frameworkNodeModules);
24
+ }
25
+
26
+ // AsyncLocalStorage instances (backward compat)
27
+ const asyncLocalStorage = new AsyncLocalStorage();
28
+ const requestStorage = new AsyncLocalStorage();
29
+ const sessionStorage = new AsyncLocalStorage();
30
+
31
+ class Framework extends EventEmitter {
32
+ constructor(config = {}) {
33
+ super();
34
+
35
+ this._silent = config.silent || false;
36
+ this._debug = config.debug || false;
37
+ this._ready = false;
38
+ this._readyPromise = null;
39
+ this._resolveReady = null;
40
+
41
+ // Subsystems
42
+ this.toolRegistry = new ToolRegistry();
43
+ this.pluginManager = new PluginManager(this);
44
+
45
+ // Agent management
46
+ this._agents = [];
47
+ this._mainAgent = null;
48
+ this._toolRegistryListeners = [];
49
+ this._subAgentConfigManager = null;
50
+ this._executionContext = null;
51
+
52
+ // Context system
53
+ this._contextManager = new ContextManager(this);
54
+ this._sessionContexts = new Map();
55
+ this._sessionStorageConfig = { baseDir: '.foliko/sessions' };
56
+
57
+ // Working directory
58
+ this._cwd = config.cwd || process.cwd();
59
+ this._agentDir = config.agentDir || AGENT_DIR;
60
+ this._homeAgentDir = config.homeAgentDir || process.env.FOLIKO_HOME || path.join(os.homedir(), HOME_AGENT_DIR_NAME);
61
+ this._changingCwd = false;
62
+
63
+ // Logger
64
+ this.logger = createFrameworkLogger(this);
65
+
66
+ // Coordinator
67
+ this._coordinatorManager = null;
68
+
69
+ // Session TTL
70
+ this._sessionTTL = new SessionTTL({
71
+ getSessionContexts: () => this._sessionContexts,
72
+ destroySession: (sid) => this.destroySessionContext(sid),
73
+ });
74
+
75
+ // Event forwarding
76
+ this._setupToolEventForwarding();
77
+ this._setupAgentEventForwarding();
78
+ this._registerBuiltinTools();
79
+ }
80
+
81
+ _setupToolEventForwarding() {
82
+ const handlers = [
83
+ ['tool:registered', (tool) => this.emit('tool:registered', tool)],
84
+ ['tool:call', (data) => { this.emit('tool:call', data); this.emit('tool-call', data); }],
85
+ ['tool:result', (data) => { this.emit('tool:result', data); }],
86
+ ['tool:error', (data) => { this.emit('tool:error', data); }],
87
+ ];
88
+ for (const [event, handler] of handlers) {
89
+ this.toolRegistry.on(event, handler);
90
+ this._toolRegistryListeners.push({ event, handler });
91
+ }
92
+ }
93
+
94
+ _setupAgentEventForwarding() {
95
+ this._agentForwardingListeners = new Map();
96
+ this.on('agent:created', (agent) => {
97
+ const handlers = {};
98
+ handlers.toolCall = (data) => this.emit('tool:call', data);
99
+ handlers.toolResult = (data) => this.emit('tool:result', data);
100
+ handlers.toolError = (data) => this.emit('tool:error', data);
101
+ handlers.message = (msg) => this.emit('agent:message', msg);
102
+ agent.on('tool-call', handlers.toolCall);
103
+ agent.on('tool-result', handlers.toolResult);
104
+ agent.on('tool-error', handlers.toolError);
105
+ agent.on('message', handlers.message);
106
+ this._agentForwardingListeners.set(agent, handlers);
107
+ });
108
+ }
109
+
110
+ registerPlugin(plugin) { this.pluginManager.register(plugin); return this; }
111
+ registerProvider(name, config) { const { registerProvider } = require('../llm/provider'); registerProvider(name, config); return this; }
112
+ getProviderRegistry() { const { getProviderRegistry } = require('../llm/provider'); return getProviderRegistry(); }
113
+ registerCommand(name, handler, description, getArgumentCompletions) {
114
+ const { getCommandRegistry } = require('./command-registry');
115
+ getCommandRegistry().registerCommand(name, handler, description, getArgumentCompletions);
116
+ return this;
117
+ }
118
+ getCommandRegistry() { const { getCommandRegistry } = require('./command-registry'); return getCommandRegistry(); }
119
+ createUIContext(pluginName, chatUI) {
120
+ const { ExtensionUIContext } = require('./ui-extension');
121
+ if (!this._uiExtensions) this._uiExtensions = new Map();
122
+ const ctx = new ExtensionUIContext(chatUI, pluginName);
123
+ this._uiExtensions.set(pluginName, ctx);
124
+ return ctx;
125
+ }
126
+ getUIContext(pluginName) { return this._uiExtensions?.get(pluginName) || null; }
127
+
128
+ async loadPlugin(plugin) { await this.pluginManager.load(plugin); return this; }
129
+ async unloadPlugin(name) { await this.pluginManager.unload(name); return this; }
130
+ async reloadPlugin(name) { await this.pluginManager.reload(name); return this; }
131
+ async reloadAllPlugins() { await this.pluginManager.reloadAll(); return this; }
132
+ async enablePlugin(name) { await this.pluginManager.enable(name); return this; }
133
+ async disablePlugin(name) { await this.pluginManager.disable(name); return this; }
134
+ updatePluginConfig(name, config) { return this.pluginManager.updatePluginConfig(name, config); }
135
+
136
+ registerTool(tool) { this.toolRegistry.register(tool); return this; }
137
+ tool(toolDef) {
138
+ const tool = { ...toolDef };
139
+ if (tool.input && !tool.inputSchema) tool.inputSchema = tool.input;
140
+ delete tool.input;
141
+ this.toolRegistry.register(tool);
142
+ return this;
143
+ }
144
+ getTools() { return this.toolRegistry.getAll(); }
145
+
146
+ getMainAgent() { return this._mainAgent; }
147
+ getSystemPrompt() { return this._mainAgent?._buildSystemPrompt?.() || ''; }
148
+ getPluginInstance(name) { return this.pluginManager.get(name) || null; }
149
+ getAgents() { return [...this._agents]; }
150
+ listSessionContexts() { return Array.from(this._sessionContexts.keys()); }
151
+
152
+ getCwd() { return this._cwd; }
153
+
154
+ registerEventDescription(eventType, description, schema = null) { this.pluginManager.registerEventDescription(eventType, description, schema); return this; }
155
+ getEventDescriptions() { return this.pluginManager.getEventDescriptions(); }
156
+ getEventDescription(eventType) { return this.pluginManager.getEventDescription(eventType); }
157
+
158
+ async executeTool(name, args) { return this.toolRegistry.execute(name, args, this); }
159
+
160
+ runWithContext(context, fn) { return asyncLocalStorage.run(context, fn); }
161
+ getExecutionContext() { return asyncLocalStorage.getStore() || null; }
162
+ setExecutionContext() {}
163
+ clearExecutionContext() {}
164
+
165
+ // Session context
166
+ getOrCreateSessionContext(sessionId, options = {}) {
167
+ if (!sessionId) return null;
168
+ let manager = this._sessionContexts.get(sessionId);
169
+ if (manager) return manager;
170
+ const cwd = options.cwd || this._cwd;
171
+ const sessionDir = path.join(cwd, '.foliko', 'sessions');
172
+ const sessionFile = options.sessionFile || path.join(sessionDir, `${sessionId}.jsonl`);
173
+ try {
174
+ if (require('fs').existsSync(sessionFile)) {
175
+ const { SessionManager } = require('../session/session');
176
+ manager = SessionManager.open(sessionFile, sessionDir, cwd);
177
+ }
178
+ } catch { /* ignore */ }
179
+ if (!manager) {
180
+ const { SessionManager } = require('../session/session');
181
+ manager = new SessionManager(cwd, sessionDir, sessionFile, true);
182
+ }
183
+ this._sessionContexts.set(sessionId, manager);
184
+ this.touchSession(sessionId);
185
+ this.emit('session:context-created', { sessionId, manager });
186
+ return manager;
187
+ }
188
+
189
+ async loadSessionContext(sessionId, options = {}) {
190
+ if (!sessionId) return null;
191
+ let manager = this._sessionContexts.get(sessionId);
192
+ if (manager) return manager;
193
+ const cwd = options.cwd || this._cwd;
194
+ const sessionDir = path.join(cwd, '.foliko', 'sessions');
195
+ const sessionFile = path.join(sessionDir, `${sessionId}.jsonl`);
196
+ try {
197
+ if (require('fs').existsSync(sessionFile)) {
198
+ const { SessionManager } = require('../session/session');
199
+ manager = SessionManager.open(sessionFile, sessionDir, cwd);
200
+ this._sessionContexts.set(sessionId, manager);
201
+ this.emit('session:context-created', { sessionId, manager });
202
+ }
203
+ } catch { /* ignore */ }
204
+ return manager || null;
205
+ }
206
+
207
+ configureSessionStorage(config) { this._sessionStorageConfig = config; }
208
+ getSessionStorage() { return this._sessionStorageConfig; }
209
+ getSessionContext(sessionId) {
210
+ const ctx = this._sessionContexts.get(sessionId) || null;
211
+ if (ctx) this.touchSession(sessionId);
212
+ return ctx;
213
+ }
214
+ getCurrentSessionContext() {
215
+ const store = sessionStorage.getStore();
216
+ if (!store) return null;
217
+ return store.sessionId ? this._sessionContexts.get(store.sessionId) || null : null;
218
+ }
219
+ getCurrentSessionId() { return sessionStorage.getStore()?.sessionId || null; }
220
+
221
+ runInSession(sessionId, options, fn) {
222
+ if (!sessionId) return fn();
223
+ const manager = this.getOrCreateSessionContext(sessionId, options);
224
+ return sessionStorage.run({ sessionContext: manager, sessionId }, fn);
225
+ }
226
+
227
+ destroySessionContext(sessionId) {
228
+ if (this._sessionContexts.has(sessionId)) {
229
+ this._sessionContexts.delete(sessionId);
230
+ this.emit('session:context-destroyed', { sessionId });
231
+ }
232
+ }
233
+
234
+ setSessionTTL(ttlMs) { this._sessionTTL.setTTL(ttlMs); return this; }
235
+ startSessionCleanup() { this._sessionTTL.start(); return this; }
236
+ stopSessionCleanup() { this._sessionTTL.stop(); return this; }
237
+ touchSession(sessionId) { this._sessionTTL.touch(sessionId); return this; }
238
+
239
+ // Request context
240
+ getRequestContext() { return requestStorage.getStore() || null; }
241
+ runWithRequestContext(context, fn) { return requestStorage.run(context, fn); }
242
+
243
+ async runInContext(requestOptions, sessionId, fn) {
244
+ const { SessionManager } = require('../session/session');
245
+ const sessionCtx = sessionId ? this.getOrCreateSessionContext(sessionId) : null;
246
+ return requestStorage.run(requestOptions, () => {
247
+ if (sessionCtx) return sessionStorage.run({ sessionContext: sessionCtx, sessionId }, fn);
248
+ return fn();
249
+ });
250
+ }
251
+
252
+ // Agent creation
253
+ createAgent(config) {
254
+ const { createAgent } = require('../agent');
255
+ return createAgent(this, config);
256
+ }
257
+
258
+ createSessionAgent(sessionId, config = {}) {
259
+ const { createSessionAgent } = require('../agent');
260
+ return createSessionAgent(this, sessionId, config);
261
+ }
262
+
263
+ createSubAgent(config) {
264
+ const { createSubAgent } = require('../agent');
265
+ return createSubAgent(this, config);
266
+ }
267
+
268
+ syncPromptPartsToSubagents() {
269
+ const { _syncPromptParts } = require('../agent');
270
+ for (const agent of this._agents) {
271
+ if (agent === this._mainAgent || !agent._isSubagent) continue;
272
+ _syncPromptParts(this, agent);
273
+ }
274
+ }
275
+
276
+ getAIPlugin() { return this.pluginManager.get('ai'); }
277
+
278
+ // Coordinator
279
+ initCoordinatorManager(coordinatorAgent) {
280
+ const { CoordinatorManager } = require('../agent/coordinator');
281
+ if (!this._coordinatorManager) this._coordinatorManager = new CoordinatorManager(this);
282
+ this._coordinatorManager.setCoordinatorAgent(coordinatorAgent);
283
+ return this._coordinatorManager;
284
+ }
285
+ getCoordinatorManager() { return this._coordinatorManager; }
286
+ async createWorker(config) {
287
+ if (!this._coordinatorManager) throw new Error('CoordinatorManager not initialized');
288
+ return this._coordinatorManager.createWorker(config);
289
+ }
290
+
291
+ _registerBuiltinTools() {}
292
+
293
+ _setReady() {
294
+ this._ready = true;
295
+ if (this._resolveReady) this._resolveReady(this);
296
+ this.emit('framework:ready');
297
+ }
298
+ }
299
+
300
+ module.exports = { Framework, asyncLocalStorage, requestStorage, sessionStorage };
@@ -0,0 +1,18 @@
1
+ 'use strict';
2
+
3
+ const { Framework } = require('./framework');
4
+ const { bootstrap, ready, destroy, setCwd, rescanProject } = require('./lifecycle');
5
+ const { FrameworkRegistry } = require('./registry');
6
+ const { loadAgentConfig, loadDefaultPlugins } = require('./loader');
7
+
8
+ module.exports = {
9
+ Framework,
10
+ FrameworkRegistry,
11
+ bootstrap,
12
+ ready,
13
+ destroy,
14
+ setCwd,
15
+ rescanProject,
16
+ loadAgentConfig,
17
+ loadDefaultPlugins,
18
+ };
@@ -0,0 +1,203 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Framework Lifecycle - 生命周期管理
5
+ * bootstrap / ready / destroy / setCwd / rescanProject
6
+ * 目标 ~150 行
7
+ */
8
+
9
+ const path = require('path');
10
+
11
+ /**
12
+ * Bootstrap - 使用默认配置启动框架
13
+ * @param {Framework} framework
14
+ * @param {Object} config
15
+ * @returns {Promise<Framework>}
16
+ */
17
+ async function bootstrap(framework, config = {}) {
18
+ const { bootstrapDefaults, DefaultPlugins, loadAgentConfig } = require('../../plugins/core/default');
19
+ const defaultsPlugin = new DefaultPlugins({ agentDir: config.agentDir || '.foliko' });
20
+ await framework.loadPlugin(defaultsPlugin);
21
+ const agentConfig = defaultsPlugin.getConfig();
22
+ if (config.aiConfig) {
23
+ agentConfig.ai = { ...agentConfig.ai, ...config.aiConfig };
24
+ }
25
+ // 合并 _config.ai(新门面 API 的格式)
26
+ if (config._config?.ai) {
27
+ agentConfig.ai = { ...agentConfig.ai, ...config._config.ai };
28
+ }
29
+ await bootstrapDefaults(framework, { _config: agentConfig, _skipConfigLoad: true });
30
+ framework._setReady();
31
+ framework.startSessionCleanup();
32
+ return framework;
33
+ }
34
+
35
+ /**
36
+ * Wait for framework ready
37
+ * @param {Framework} framework
38
+ * @returns {Promise<Framework>}
39
+ */
40
+ async function ready(framework) {
41
+ if (framework._ready) return framework;
42
+ if (!framework._readyPromise) {
43
+ framework._readyPromise = new Promise(resolve => { framework._resolveReady = resolve; });
44
+ }
45
+ await framework._readyPromise;
46
+ return framework;
47
+ }
48
+
49
+ /**
50
+ * Destroy framework
51
+ * @param {Framework} framework
52
+ */
53
+ async function destroy(framework) {
54
+ // Destroy coordinator
55
+ if (framework._coordinatorManager) {
56
+ framework._coordinatorManager.destroy();
57
+ framework._coordinatorManager = null;
58
+ }
59
+
60
+ // Destroy all agents
61
+ for (const agent of framework._agents) {
62
+ const listeners = framework._agentForwardingListeners?.get(agent);
63
+ if (listeners) {
64
+ agent.off('tool-call', listeners.toolCall);
65
+ agent.off('tool-result', listeners.toolResult);
66
+ agent.off('tool-error', listeners.toolError);
67
+ agent.off('message', listeners.message);
68
+ framework._agentForwardingListeners.delete(agent);
69
+ }
70
+ if (typeof agent.destroy === 'function') agent.destroy();
71
+ }
72
+ framework._agents = [];
73
+
74
+ // Remove toolRegistry listeners
75
+ if (framework._toolRegistryListeners) {
76
+ for (const { event, handler } of framework._toolRegistryListeners) {
77
+ framework.toolRegistry.off(event, handler);
78
+ }
79
+ framework._toolRegistryListeners = [];
80
+ }
81
+
82
+ framework.stopSessionCleanup();
83
+ framework._sessionContexts.clear();
84
+
85
+ // Unload all plugins
86
+ const plugins = framework.pluginManager.getAll();
87
+ for (const { name } of plugins) {
88
+ try { await framework.pluginManager.unload(name); }
89
+ catch (err) { framework.logger.warn(`Failed to unload '${name}': ${err.message}`); }
90
+ }
91
+
92
+ framework.toolRegistry.clear();
93
+ framework.removeAllListeners();
94
+ framework.emit('framework:destroyed');
95
+ }
96
+
97
+ /**
98
+ * Set working directory
99
+ * @param {Framework} framework
100
+ * @param {string} newCwd
101
+ * @param {Object} options
102
+ */
103
+ async function setCwd(framework, newCwd, options = {}) {
104
+ if (framework._changingCwd) throw new Error('setCwd re-entered');
105
+ const oldCwd = framework._cwd;
106
+ const resolved = path.resolve(newCwd);
107
+ if (resolved === oldCwd) return framework;
108
+
109
+ framework._changingCwd = true;
110
+ try {
111
+ framework._cwd = resolved;
112
+ if (options.agentDir) framework._agentDir = options.agentDir;
113
+ if (options.homeAgentDir) framework._homeAgentDir = options.homeAgentDir;
114
+
115
+ framework.pluginManager._setStateFile(resolved);
116
+
117
+ const count = framework._sessionContexts.size;
118
+ for (const sid of Array.from(framework._sessionContexts.keys())) {
119
+ framework.destroySessionContext(sid);
120
+ }
121
+ framework.emit('session:contexts-invalidated', { oldCwd, newCwd: resolved, count });
122
+
123
+ if (options.reload !== false) {
124
+ for (const entry of framework.pluginManager.getAll()) {
125
+ const inst = entry.instance;
126
+ try {
127
+ if (typeof inst?.onCwdChanged === 'function') await inst.onCwdChanged(oldCwd, resolved, framework);
128
+ await framework.pluginManager.reload(entry.name);
129
+ } catch (err) {
130
+ framework.logger.warn(`Plugin ${entry.name} failed during cwd change: ${err.message}`);
131
+ }
132
+ }
133
+ }
134
+ framework.emit('cwd:changed', { oldCwd, newCwd: resolved, framework });
135
+ } finally {
136
+ framework._changingCwd = false;
137
+ }
138
+ return framework;
139
+ }
140
+
141
+ /**
142
+ * Rescan project - reload plugins and skills
143
+ * @param {Framework} framework
144
+ * @param {Object} options
145
+ */
146
+ async function rescanProject(framework, options = {}) {
147
+ const { bootstrapDefaults, DefaultPlugins, loadAgentConfig } = require('../../plugins/core/default');
148
+ const { SYSTEM_PLUGINS } = require('../common/constants');
149
+ const keepSet = new Set(options.keepLoaded || Array.from(SYSTEM_PLUGINS));
150
+ const loadList = [];
151
+
152
+ // Unload non-kept plugins using PluginManager.unloadExcept
153
+ const unloadList = await framework.pluginManager.unloadExcept(keepSet);
154
+
155
+ // Reload defaults
156
+ let defaultsPlugin = framework.pluginManager.get('defaults');
157
+ if (!defaultsPlugin) {
158
+ const dp = new DefaultPlugins({ agentDir: framework._agentDir });
159
+ try { await framework.loadPlugin(dp); defaultsPlugin = dp; }
160
+ catch (err) { framework.logger.warn(`rescanProject: load defaults failed: ${err.message}`); }
161
+ } else {
162
+ try { await defaultsPlugin.reload(framework); }
163
+ catch (err) { framework.logger.warn(`rescanProject: reload defaults failed: ${err.message}`); }
164
+ }
165
+
166
+ const agentConfig = defaultsPlugin
167
+ ? (defaultsPlugin.getConfig() || loadAgentConfig(framework, framework._agentDir))
168
+ : loadAgentConfig(framework, framework._agentDir);
169
+
170
+ // Bootstrap defaults
171
+ const beforeNames = new Set(framework.pluginManager.getAll().map(p => p.name));
172
+ try { await bootstrapDefaults(framework, { _config: agentConfig, _skipConfigLoad: true }); }
173
+ catch (err) { framework.logger.warn(`rescanProject: bootstrapDefaults failed: ${err.message}`); }
174
+ for (const name of framework.pluginManager.getAll().map(p => p.name)) {
175
+ if (!beforeNames.has(name)) loadList.push(name);
176
+ }
177
+
178
+ // Sync skills using setSearchDirs
179
+ if (options.reloadSkillManager !== false) {
180
+ const skillManager = framework.pluginManager.get('skill-manager');
181
+ if (skillManager) {
182
+ if (Array.isArray(agentConfig.skillsDirs)) skillManager.setSearchDirs(agentConfig.skillsDirs);
183
+ try { await skillManager.reload(framework); }
184
+ catch (err) { framework.logger.warn(`rescanProject: skill-manager reload failed: ${err.message}`); }
185
+ }
186
+ }
187
+
188
+ // Reload kept plugins (fix: use keepSet, not undefined keepList)
189
+ if (options.reloadKept) {
190
+ for (const name of keepSet) {
191
+ if (!framework.pluginManager.has(name)) continue;
192
+ try { await framework.pluginManager.reload(name); }
193
+ catch (err) { framework.logger.warn(`rescanProject: reload kept ${name} failed: ${err.message}`); }
194
+ }
195
+ }
196
+
197
+ const result = { unloaded: unloadList, loaded: loadList, keepLoaded: [...keepSet] };
198
+ framework.emit('rescan:complete', result);
199
+ framework.logger.info(`rescanProject: unloaded=${unloadList.length} loaded=${loadList.length} kept=${keepSet.size}`);
200
+ return result;
201
+ }
202
+
203
+ module.exports = { bootstrap, ready, destroy, setCwd, rescanProject };
@@ -0,0 +1,78 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Framework Loader - 加载 .foliko/ 配置
5
+ * 目标 ~150 行
6
+ */
7
+
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
+
64
+ /**
65
+ * Load default plugins based on config
66
+ * @param {Framework} framework
67
+ * @param {Object} config
68
+ * @returns {Promise<string[]>} loaded plugin names
69
+ */
70
+ async function loadDefaultPlugins(framework, config = {}) {
71
+ const { bootstrapDefaults, DefaultPlugins } = require('../../plugins/core/default');
72
+ const defaultsPlugin = new DefaultPlugins({ agentDir: config.agentDir || '.foliko' });
73
+ await framework.loadPlugin(defaultsPlugin);
74
+ await bootstrapDefaults(framework, { _config: config, _skipConfigLoad: true });
75
+ return framework.pluginManager.getAll().map(p => p.name);
76
+ }
77
+
78
+ module.exports = { loadAgentConfig, loadDefaultPlugins };
@@ -0,0 +1,86 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Framework Registry - 统一注册表
5
+ * 聚合 plugin/tool/command 注册表
6
+ * 目标 ~200 行
7
+ */
8
+
9
+ class FrameworkRegistry {
10
+ constructor(framework) {
11
+ this.framework = framework;
12
+ this._plugins = [];
13
+ this._tools = [];
14
+ this._commands = new Map();
15
+ }
16
+
17
+ // === Plugins ===
18
+ registerPlugin(plugin) {
19
+ this._plugins.push(plugin);
20
+ return this;
21
+ }
22
+
23
+ getPlugin(name) {
24
+ return this._plugins.find(p => p.name === name || p.constructor?.name === name) || null;
25
+ }
26
+
27
+ getAllPlugins() {
28
+ return [...this._plugins];
29
+ }
30
+
31
+ unregisterPlugin(name) {
32
+ const idx = this._plugins.findIndex(p => p.name === name);
33
+ if (idx !== -1) this._plugins.splice(idx, 1);
34
+ return this;
35
+ }
36
+
37
+ hasPlugin(name) {
38
+ return this._plugins.some(p => p.name === name);
39
+ }
40
+
41
+ // === Tools ===
42
+ registerTool(tool) {
43
+ this._tools.push(tool);
44
+ return this;
45
+ }
46
+
47
+ getTool(name) {
48
+ return this._tools.find(t => t.name === name) || null;
49
+ }
50
+
51
+ getAllTools() {
52
+ return [...this._tools];
53
+ }
54
+
55
+ unregisterTool(name) {
56
+ const idx = this._tools.findIndex(t => t.name === name);
57
+ if (idx !== -1) this._tools.splice(idx, 1);
58
+ return this;
59
+ }
60
+
61
+ clearTools() {
62
+ this._tools = [];
63
+ return this;
64
+ }
65
+
66
+ // === Commands ===
67
+ registerCommand(name, handler, description) {
68
+ this._commands.set(name, { handler, description });
69
+ return this;
70
+ }
71
+
72
+ getCommand(name) {
73
+ return this._commands.get(name) || null;
74
+ }
75
+
76
+ getAllCommands() {
77
+ return Array.from(this._commands.entries()).map(([name, cmd]) => ({ name, ...cmd }));
78
+ }
79
+
80
+ unregisterCommand(name) {
81
+ this._commands.delete(name);
82
+ return this;
83
+ }
84
+ }
85
+
86
+ module.exports = { FrameworkRegistry };