foliko 1.1.93 → 2.0.1

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 +224 -0
  27. package/plugins/core/default/config.js +222 -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 +3 -3
  52. package/plugins/{feishu-plugin.js → messaging/feishu/index.js} +4 -4
  53. package/plugins/{qq-plugin.js → messaging/qq/index.js} +6 -17
  54. package/plugins/{telegram-plugin.js → messaging/telegram/index.js} +4 -4
  55. package/plugins/{weixin-plugin.js → messaging/weixin/index.js} +16 -16
  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/common/errors.js +402 -0
  80. package/src/{utils → common}/logger.js +33 -0
  81. package/src/{utils/chat-queue.js → common/queue.js} +2 -2
  82. package/src/config/plugin-config.js +50 -0
  83. package/src/context/agent.js +32 -0
  84. package/src/context/compaction-prompts.js +170 -0
  85. package/src/context/compaction-utils.js +191 -0
  86. package/src/context/compressor.js +413 -0
  87. package/src/context/index.js +9 -0
  88. package/src/{core/context-manager.js → context/manager.js} +1 -1
  89. package/src/context/request.js +50 -0
  90. package/src/context/session.js +33 -0
  91. package/src/context/storage.js +30 -0
  92. package/src/executors/mcp-client.js +153 -0
  93. package/src/executors/mcp-desc.js +236 -0
  94. package/src/executors/mcp-executor.js +91 -956
  95. package/src/{core → framework}/command-registry.js +1 -1
  96. package/src/framework/framework.js +300 -0
  97. package/src/framework/index.js +18 -0
  98. package/src/framework/lifecycle.js +203 -0
  99. package/src/framework/loader.js +78 -0
  100. package/src/framework/registry.js +86 -0
  101. package/src/{core/ui-extension-context.js → framework/ui-extension.js} +1 -1
  102. package/src/index.js +130 -15
  103. package/src/llm/index.js +26 -0
  104. package/src/llm/provider.js +212 -0
  105. package/src/llm/registry.js +11 -0
  106. package/src/{core/token-counter.js → llm/tokens.js} +4 -37
  107. package/src/{core/plugin-base.js → plugin/base.js} +10 -136
  108. package/src/plugin/index.js +14 -0
  109. package/src/plugin/loader.js +101 -0
  110. package/src/plugin/manager.js +484 -0
  111. package/src/{core → session}/branch-summary-auto.js +2 -2
  112. package/src/{core/chat-session.js → session/chat.js} +2 -2
  113. package/src/session/index.js +7 -0
  114. package/src/{core/session-manager.js → session/session.js} +2 -2
  115. package/src/session/ttl.js +92 -0
  116. package/src/{core/jsonl-storage.js → storage/jsonl.js} +1 -1
  117. package/src/tool/executor.js +85 -0
  118. package/src/tool/index.js +15 -0
  119. package/src/tool/registry.js +143 -0
  120. package/src/{core/tool-router.js → tool/router.js} +17 -124
  121. package/src/tool/schema.js +108 -0
  122. package/src/utils/data-splitter.js +1 -1
  123. package/src/utils/download.js +1 -1
  124. package/src/utils/index.js +6 -6
  125. package/src/utils/message-validator.js +1 -1
  126. package/tests/core/context-storage.test.js +46 -0
  127. package/tests/core/llm.test.js +54 -0
  128. package/tests/core/plugin.test.js +42 -0
  129. package/tests/core/tool.test.js +60 -0
  130. package/tests/setup.js +10 -0
  131. package/tests/smoke.test.js +58 -0
  132. package/vitest.config.js +9 -0
  133. package/cli/src/daemon.js +0 -149
  134. package/docs/CONTEXT_DESIGN.md +0 -1596
  135. package/docs/ai-sdk-optimization.md +0 -655
  136. package/docs/features.md +0 -120
  137. package/docs/qq-bot.md +0 -976
  138. package/docs/quick-reference.md +0 -160
  139. package/docs/user-manual.md +0 -1391
  140. package/images/geometric_shapes.jpg +0 -0
  141. package/images/sunset_mountain_lake.jpg +0 -0
  142. package/skills/poster-guide/SKILL.md +0 -792
  143. package/src/capabilities/index.js +0 -11
  144. package/src/core/agent.js +0 -808
  145. package/src/core/context-compressor.js +0 -959
  146. package/src/core/enhanced-context-compressor.js +0 -210
  147. package/src/core/framework.js +0 -1422
  148. package/src/core/index.js +0 -30
  149. package/src/core/plugin-manager.js +0 -961
  150. package/src/core/provider-registry.js +0 -159
  151. package/src/core/provider.js +0 -156
  152. package/src/core/request-context.js +0 -98
  153. package/src/core/subagent.js +0 -442
  154. package/src/core/system-prompt-builder.js +0 -120
  155. package/src/core/tool-executor.js +0 -202
  156. package/src/core/tool-registry.js +0 -517
  157. package/src/core/worker-agent.js +0 -192
  158. package/src/executors/executor-base.js +0 -58
  159. package/src/utils/error-boundary.js +0 -363
  160. package/src/utils/error.js +0 -374
  161. package/system.md +0 -1645
  162. package/website_v2/README.md +0 -57
  163. package/website_v2/SPEC.md +0 -1
  164. package/website_v2/docs/api.html +0 -128
  165. package/website_v2/docs/configuration.html +0 -147
  166. package/website_v2/docs/plugin-development.html +0 -129
  167. package/website_v2/docs/project-structure.html +0 -89
  168. package/website_v2/docs/skill-development.html +0 -85
  169. package/website_v2/index.html +0 -489
  170. package/website_v2/scripts/main.js +0 -93
  171. package/website_v2/styles/animations.css +0 -8
  172. package/website_v2/styles/docs.css +0 -83
  173. package/website_v2/styles/main.css +0 -417
  174. package/xhs_auth.json +0 -268
  175. package//346/265/267/346/212/245/346/217/222/344/273/266.md +0 -621
  176. /package/plugins/{ambient-agent → ambient}/constants.js +0 -0
  177. /package/plugins/{email → messaging/email}/constants.js +0 -0
  178. /package/plugins/{email → messaging/email}/handlers.js +0 -0
  179. /package/plugins/{email → messaging/email}/monitor.js +0 -0
  180. /package/plugins/{email → messaging/email}/parser.js +0 -0
  181. /package/plugins/{email → messaging/email}/reply.js +0 -0
  182. /package/plugins/{email → messaging/email}/utils.js +0 -0
  183. /package/{examples → sandbox}/test-chat.js +0 -0
  184. /package/{examples → sandbox}/test-mcp.js +0 -0
  185. /package/{examples → sandbox}/test-reload.js +0 -0
  186. /package/{examples → sandbox}/test-telegram.js +0 -0
  187. /package/{examples → sandbox}/test-tg-bot.js +0 -0
  188. /package/{examples → sandbox}/test-tg-simple.js +0 -0
  189. /package/{examples → sandbox}/test-tg.js +0 -0
  190. /package/{examples → sandbox}/test-think.js +0 -0
  191. /package/src/{core/sub-agent-config.js → agent/sub-config.js} +0 -0
  192. /package/{cli/src → src/cli}/commands/daemon.js +0 -0
  193. /package/{cli/src → src/cli}/commands/list.js +0 -0
  194. /package/{cli/src → src/cli}/commands/plugin.js +0 -0
  195. /package/{cli/src → src/cli}/ui/components/agent-mention-provider.js +0 -0
  196. /package/{cli/src → src/cli}/ui/components/chained-autocomplete-provider.js +0 -0
  197. /package/{cli/src → src/cli}/ui/components/message-bubble.js +0 -0
  198. /package/{cli/src → src/cli}/ui/components/status-bar.js +0 -0
  199. /package/{cli/src → src/cli}/utils/ansi.js +0 -0
  200. /package/{cli/src → src/cli}/utils/config.js +0 -0
  201. /package/{cli/src → src/cli}/utils/markdown.js +0 -0
  202. /package/{cli/src → src/cli}/utils/plugin-config.js +0 -0
  203. /package/{cli/src → src/cli}/utils/render-diff.js +0 -0
  204. /package/src/{utils/circuit-breaker.js → common/circuit.js} +0 -0
  205. /package/src/{core → common}/constants.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
@@ -0,0 +1,484 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * PluginManager 插件管理器
5
+ * 负责插件的加载、卸载、重载、启用/禁用
6
+ */
7
+
8
+ const { Plugin } = require('./base');
9
+ const { logger } = require('../common/logger');
10
+ const { PluginError, PluginNotFoundError } = require('../common/errors');
11
+ const { safeJsonParse } = require('../utils');
12
+ const { resolvePluginPath, scanPluginNames } = require('../utils/plugin-helpers');
13
+ const fs = require('fs');
14
+ const path = require('path');
15
+
16
+ class PluginManager {
17
+ constructor(framework) {
18
+ this.framework = framework;
19
+ this._plugins = new Map();
20
+ this._loading = false;
21
+ this._bootstrapping = false;
22
+ this._stateFile = path.join(
23
+ framework?.getCwd?.() ?? process.cwd(),
24
+ '.foliko',
25
+ 'data',
26
+ 'plugins-state.json'
27
+ );
28
+ this._knownPlugins = new Set();
29
+
30
+ this._log = logger.child('plugin-manager');
31
+ this._eventDescriptions = new Map();
32
+ this._stateCache = null;
33
+
34
+ this._registerDefaultEventDescriptions();
35
+ }
36
+
37
+ _registerDefaultEventDescriptions() {
38
+ // placeholder - plugins can register event descriptions
39
+ }
40
+
41
+ /**
42
+ * 注册插件(不加载,只记录状态)
43
+ */
44
+ register(plugin, options = {}) {
45
+ if (!(plugin instanceof Plugin)) {
46
+ throw new PluginError('Plugin must be an instance of Plugin');
47
+ }
48
+
49
+ const savedState = this._loadState();
50
+ const savedEnabled = savedState[plugin.name]?.enabled;
51
+ const savedConfig = savedState[plugin.name]?.config;
52
+
53
+ // 恢复保存的配置
54
+ if (savedConfig && plugin.config && plugin.name !== 'ai') {
55
+ plugin.config = { ...plugin.config, ...savedConfig };
56
+ }
57
+
58
+ // 确定启用状态:系统插件强制启用 → state 文件记录 → 插件默认
59
+ let enabled;
60
+ if (plugin.system) {
61
+ enabled = true;
62
+ } else if (savedEnabled !== undefined) {
63
+ enabled = savedEnabled;
64
+ } else {
65
+ enabled = plugin.enabled !== false;
66
+ }
67
+
68
+ this._plugins.set(plugin.name, {
69
+ instance: plugin,
70
+ status: 'registered',
71
+ enabled,
72
+ sourcePath: plugin.sourcePath || null,
73
+ });
74
+ this._knownPlugins.add(plugin.name);
75
+ this._log.debug(`Plugin registered: ${plugin.name} (enabled: ${enabled})`);
76
+ return this;
77
+ }
78
+
79
+ has(name) {
80
+ return this._plugins.has(name);
81
+ }
82
+
83
+ get(name) {
84
+ const entry = this._plugins.get(name);
85
+ return entry ? entry.instance : null;
86
+ }
87
+
88
+ isEnabled(name) {
89
+ const entry = this._plugins.get(name);
90
+ return entry ? entry.enabled !== false : false;
91
+ }
92
+
93
+ getAll() {
94
+ return Array.from(this._plugins.values());
95
+ }
96
+
97
+ getNames() {
98
+ return Array.from(this._plugins.keys());
99
+ }
100
+
101
+ /**
102
+ * 获取所有已知插件信息(包括已加载和未加载的)
103
+ */
104
+ getAllKnown() {
105
+ const result = [];
106
+ for (const name of this._knownPlugins) {
107
+ const entry = this._plugins.get(name);
108
+ if (entry) {
109
+ const inst = entry.instance;
110
+ result.push({
111
+ name,
112
+ status: entry.status || 'registered',
113
+ enabled: entry.enabled !== false,
114
+ version: inst.version,
115
+ system: inst.system,
116
+ });
117
+ } else {
118
+ result.push({ name, status: 'known', enabled: false });
119
+ }
120
+ }
121
+ // 加入已加载但不在 knownPlugins 中的
122
+ for (const [name, entry] of this._plugins) {
123
+ if (!result.find(p => p.name === name)) {
124
+ result.push({
125
+ name,
126
+ status: entry.status || 'registered',
127
+ enabled: entry.enabled !== false,
128
+ version: entry.instance?.version,
129
+ system: entry.instance?.system || false,
130
+ });
131
+ }
132
+ }
133
+ return result;
134
+ }
135
+
136
+ count() {
137
+ return this._plugins.size;
138
+ }
139
+
140
+ /**
141
+ * 加载插件
142
+ * @param {Plugin|Object} plugin - 插件实例或类
143
+ * @param {Object} [options] - 加载选项
144
+ * @param {boolean} [options.forceEnabled] - 强制启用,忽略 state 文件的 disabled
145
+ */
146
+ async load(plugin, options = {}) {
147
+ let pluginInstance = plugin;
148
+
149
+ // 如果是类(Plugin 子类),实例化
150
+ if (typeof plugin === 'function' && plugin.prototype instanceof Plugin) {
151
+ pluginInstance = new plugin();
152
+ }
153
+
154
+ const name = pluginInstance.name;
155
+
156
+ // 已存在且已加载 → 跳过
157
+ const existing = this._plugins.get(name);
158
+ if (existing && existing.status === 'loaded' && pluginInstance._started) {
159
+ this._log.debug(`Plugin '${name}' already loaded, skipping`);
160
+ return;
161
+ }
162
+
163
+ // 已注册但未加载 → 检查 enabled 状态
164
+ if (existing) {
165
+ if (!existing.enabled && !options.forceEnabled) {
166
+ this._log.info(`Plugin '${name}' is disabled, skipping load`);
167
+ return;
168
+ }
169
+ pluginInstance = existing.instance;
170
+ } else {
171
+ // 未注册 → 先注册
172
+ this.register(pluginInstance, options);
173
+ }
174
+
175
+ const entry = this._plugins.get(name);
176
+ if (!entry.enabled && !options.forceEnabled) {
177
+ this._log.info(`Plugin '${name}' is disabled, skipping load`);
178
+ return;
179
+ }
180
+
181
+ // 如果 forceEnabled,覆盖 enabled 状态
182
+ if (options.forceEnabled) {
183
+ entry.enabled = true;
184
+ pluginInstance.enabled = true;
185
+ }
186
+
187
+ this._log.info(`Loading plugin: ${name}`);
188
+
189
+ if (typeof pluginInstance.install === 'function') {
190
+ pluginInstance.install(this.framework);
191
+ }
192
+
193
+ entry.status = 'loaded';
194
+
195
+ // bootstrapping 模式下不 start,由 startAll() 统一启动
196
+ if (!this._bootstrapping) {
197
+ if (typeof pluginInstance.start === 'function') {
198
+ await pluginInstance.start(this.framework);
199
+ pluginInstance._started = true;
200
+ }
201
+ }
202
+
203
+ this.framework?.emit('plugin:loaded', pluginInstance);
204
+ this._saveState();
205
+ this._log.info(`Plugin loaded: ${name}`);
206
+ }
207
+
208
+ async unload(name) {
209
+ const entry = this._plugins.get(name);
210
+ if (!entry) {
211
+ throw new PluginNotFoundError(`Plugin '${name}' not found`);
212
+ }
213
+
214
+ const plugin = entry.instance;
215
+ this._log.info(`Unloading plugin: ${name}`);
216
+
217
+ if (typeof plugin.uninstall === 'function') {
218
+ plugin.uninstall(this.framework);
219
+ }
220
+
221
+ this._plugins.delete(name);
222
+ return true;
223
+ }
224
+
225
+ async unloadExcept(keepSet) {
226
+ const unloaded = [];
227
+ for (const [name, entry] of this._plugins) {
228
+ if (keepSet.has(name) || (entry.instance && entry.instance.system)) continue;
229
+ try {
230
+ await this.unload(name);
231
+ this._plugins.delete(name);
232
+ unloaded.push(name);
233
+ } catch (err) {
234
+ this._log.warn(`unloadExcept: ${name} failed: ${err.message}`);
235
+ }
236
+ }
237
+ return unloaded;
238
+ }
239
+
240
+ async reload(name) {
241
+ const entry = this._plugins.get(name);
242
+ if (!entry) {
243
+ throw new PluginNotFoundError(`Plugin '${name}' not found`);
244
+ }
245
+
246
+ this._log.info(`Reloading plugin: ${name}`);
247
+ const plugin = entry.instance;
248
+
249
+ if (typeof plugin.reload === 'function') {
250
+ await plugin.reload(this.framework);
251
+ } else {
252
+ if (typeof plugin.uninstall === 'function') {
253
+ plugin.uninstall(this.framework);
254
+ }
255
+ if (typeof plugin.install === 'function') {
256
+ plugin.install(this.framework);
257
+ }
258
+ if (typeof plugin.start === 'function') {
259
+ await plugin.start(this.framework);
260
+ }
261
+ }
262
+
263
+ this.framework?.emit('plugin:reloaded', plugin);
264
+ this._log.info(`Plugin reloaded: ${name}`);
265
+ }
266
+
267
+ /**
268
+ * 启动所有已加载、已启用、未启动的插件(按优先级排序)
269
+ */
270
+ async startAll() {
271
+ const entries = Array.from(this._plugins.values())
272
+ .filter(e => e.status === 'loaded' && e.enabled)
273
+ .sort((a, b) => (a.instance.priority || 100) - (b.instance.priority || 100));
274
+
275
+ for (const entry of entries) {
276
+ const plugin = entry.instance;
277
+ if (plugin._started) continue;
278
+ if (typeof plugin.start === 'function') {
279
+ try {
280
+ await plugin.start(this.framework);
281
+ plugin._started = true;
282
+ } catch (err) {
283
+ this._log.warn(`startAll: ${plugin.name} failed: ${err.message}`);
284
+ }
285
+ }
286
+ }
287
+ }
288
+
289
+ /**
290
+ * 启用插件
291
+ */
292
+ async enable(name) {
293
+ const entry = this._plugins.get(name);
294
+ if (!entry) {
295
+ throw new PluginNotFoundError(name);
296
+ }
297
+ if (entry.instance?.system) {
298
+ throw new PluginError(`Plugin '${name}' is a system plugin, cannot be disabled/enabled`);
299
+ }
300
+ if (entry.enabled) {
301
+ this._log.info(`Plugin '${name}' already enabled`);
302
+ return;
303
+ }
304
+
305
+ entry.enabled = true;
306
+ if (entry.instance) {
307
+ entry.instance.enabled = true;
308
+ }
309
+
310
+ // 如果已加载,重新启动
311
+ if (entry.status === 'loaded') {
312
+ try {
313
+ // 停止旧实例
314
+ if (entry.instance._started) {
315
+ if (typeof entry.instance.stopBot === 'function') {
316
+ await entry.instance.stopBot();
317
+ } else if (typeof entry.instance.stop === 'function') {
318
+ await entry.instance.stop();
319
+ }
320
+ }
321
+
322
+ // 如果记录过 sourcePath,重新 require 最新代码
323
+ if (entry.sourcePath) {
324
+ delete require.cache[require.resolve(entry.sourcePath)];
325
+ const mod = require(entry.sourcePath);
326
+ let NewClass = mod.default || mod;
327
+ if (typeof NewClass === 'function' && NewClass.prototype instanceof Plugin) {
328
+ const newInstance = new NewClass();
329
+ newInstance._sourcePath = entry.sourcePath;
330
+ if (entry.instance.config) newInstance.config = { ...entry.instance.config };
331
+ entry.instance = newInstance;
332
+ await newInstance.install(this.framework);
333
+ await newInstance.start(this.framework);
334
+ newInstance._started = true;
335
+ this.framework?.emit('plugin:reloaded', newInstance);
336
+ }
337
+ } else {
338
+ // 没有 sourcePath,调用 reload
339
+ if (typeof entry.instance.reload === 'function') {
340
+ await entry.instance.reload(this.framework);
341
+ }
342
+ if (typeof entry.instance.start === 'function') {
343
+ await entry.instance.start(this.framework);
344
+ }
345
+ entry.instance._started = true;
346
+ }
347
+ } catch (err) {
348
+ this._log.error(`Enable/start failed for '${name}':`, err.message);
349
+ }
350
+ } else if (entry.status === 'registered') {
351
+ // 只注册过但未加载,现在加载
352
+ try {
353
+ await this.load(entry.instance);
354
+ } catch (err) {
355
+ this._log.error(`Enable/load failed for '${name}':`, err.message);
356
+ }
357
+ }
358
+
359
+ this.framework?.emit('plugin:enabled', entry.instance);
360
+ this._saveState();
361
+ this._log.info(`Plugin '${name}' enabled`);
362
+ }
363
+
364
+ /**
365
+ * 禁用插件
366
+ */
367
+ async disable(name) {
368
+ const entry = this._plugins.get(name);
369
+ if (!entry) {
370
+ throw new PluginNotFoundError(name);
371
+ }
372
+ if (entry.instance?.system) {
373
+ throw new PluginError(`Plugin '${name}' is a system plugin, cannot be disabled`);
374
+ }
375
+ if (!entry.enabled) {
376
+ this._log.info(`Plugin '${name}' already disabled`);
377
+ return;
378
+ }
379
+
380
+ entry.enabled = false;
381
+ if (entry.instance) {
382
+ entry.instance.enabled = false;
383
+ }
384
+
385
+ // 如果正在运行,停止
386
+ if (entry.instance._started) {
387
+ try {
388
+ if (typeof entry.instance.stopBot === 'function') {
389
+ await entry.instance.stopBot();
390
+ } else if (typeof entry.instance.stop === 'function') {
391
+ await entry.instance.stop();
392
+ }
393
+ entry.instance._started = false;
394
+ } catch (err) {
395
+ this._log.error(`Stop failed for '${name}':`, err.message);
396
+ }
397
+ }
398
+
399
+ this.framework?.emit('plugin:disabled', entry.instance);
400
+ this._saveState();
401
+ this._log.info(`Plugin '${name}' disabled`);
402
+ }
403
+
404
+ updatePluginConfig(name, config) {
405
+ const entry = this._plugins.get(name);
406
+ if (!entry) {
407
+ throw new PluginNotFoundError(`Plugin '${name}' not found`);
408
+ }
409
+ if (entry.instance.config) {
410
+ Object.assign(entry.instance.config, config);
411
+ }
412
+ this._saveState();
413
+ }
414
+
415
+ setBootstrapping(val) {
416
+ this._bootstrapping = val !== false;
417
+ }
418
+
419
+ // ========== 状态持久化 ==========
420
+
421
+ _getStateFile() {
422
+ const dir = path.dirname(this._stateFile);
423
+ if (!fs.existsSync(dir)) {
424
+ fs.mkdirSync(dir, { recursive: true });
425
+ }
426
+ return this._stateFile;
427
+ }
428
+
429
+ _saveState() {
430
+ try {
431
+ const stateFile = this._getStateFile();
432
+ let state = safeJsonParse(
433
+ fs.existsSync(stateFile) ? fs.readFileSync(stateFile, 'utf-8') : '{}',
434
+ {}
435
+ );
436
+ for (const [name, entry] of this._plugins) {
437
+ state[name] = {
438
+ enabled: entry.enabled,
439
+ config: entry.instance?.config || {},
440
+ };
441
+ }
442
+ fs.writeFileSync(stateFile, JSON.stringify(state, null, 2));
443
+ this._stateCache = state;
444
+ } catch (err) {
445
+ this._log.error('Failed to save state:', err.message);
446
+ }
447
+ }
448
+
449
+ _loadState() {
450
+ if (this._stateCache !== null) return this._stateCache;
451
+ try {
452
+ const stateFile = this._getStateFile();
453
+ if (fs.existsSync(stateFile)) {
454
+ this._stateCache = safeJsonParse(fs.readFileSync(stateFile, 'utf-8'), {});
455
+ return this._stateCache;
456
+ }
457
+ } catch (err) {
458
+ this._log.error('Failed to load state:', err.message);
459
+ }
460
+ this._stateCache = {};
461
+ return this._stateCache;
462
+ }
463
+
464
+ _setStateFile(cwd) {
465
+ this._stateFile = path.join(cwd, '.foliko', 'data', 'plugins-state.json');
466
+ this._stateCache = null;
467
+ }
468
+
469
+ // ========== 事件描述 ==========
470
+
471
+ registerEventDescription(eventType, description, schema = null) {
472
+ this._eventDescriptions.set(eventType, { description, schema });
473
+ }
474
+
475
+ getEventDescriptions() {
476
+ return new Map(this._eventDescriptions);
477
+ }
478
+
479
+ getEventDescription(eventType) {
480
+ return this._eventDescriptions.get(eventType) || null;
481
+ }
482
+ }
483
+
484
+ module.exports = { PluginManager };
@@ -9,9 +9,9 @@ const {
9
9
  BRANCH_SUMMARY_PREAMBLE,
10
10
  BRANCH_SUMMARY_PROMPT,
11
11
  serializeConversation
12
- } = require('./context-compressor');
12
+ } = require('../context/compressor');
13
13
 
14
- const { logger } = require('../utils/logger');
14
+ const { logger } = require('../common/logger');
15
15
 
16
16
  class BranchSummaryAuto {
17
17
  /**
@@ -8,8 +8,8 @@
8
8
  * 4. SessionScope 事件作用域
9
9
  */
10
10
 
11
- const { EventEmitter } = require('../utils/event-emitter');
12
- const { logger } = require('../utils/logger');
11
+ const { EventEmitter } = require('../common/events');
12
+ const { logger } = require('../common/logger');
13
13
 
14
14
  /**
15
15
  * Session 作用域的事件监听器
@@ -0,0 +1,7 @@
1
+ 'use strict';
2
+
3
+ module.exports = {
4
+ SessionManager: require('./session').SessionManager,
5
+ SessionEntry: require('./entry'),
6
+ ChatSession: require('./chat').ChatSession,
7
+ };
@@ -6,7 +6,7 @@
6
6
 
7
7
  const fs = require('fs');
8
8
  const path = require('path');
9
- const { JsonlSessionStorage } = require('./jsonl-storage');
9
+ const { JsonlSessionStorage } = require('../storage/jsonl');
10
10
  const {
11
11
  EntryTypes,
12
12
  generateEntryId,
@@ -15,7 +15,7 @@ const {
15
15
  createBranchSummaryMessage,
16
16
  createCompactionSummaryMessage,
17
17
  createCustomMessage
18
- } = require('./session-entry');
18
+ } = require('./entry');
19
19
 
20
20
  const CURRENT_SESSION_VERSION = 3;
21
21
 
@@ -0,0 +1,92 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * SessionTTL - Session 过期清理管理
5
+ * 从 framework.js 中提取的 TTL 清理逻辑
6
+ */
7
+
8
+ const { DEFAULT_SESSION_CLEANUP_INTERVAL_MS } = require('../common/constants');
9
+
10
+ class SessionTTL {
11
+ /**
12
+ * @param {Object} options
13
+ * @param {Function} options.getSessionContexts - 返回 session 上下文 Map
14
+ * @param {Function} options.destroySession - 销毁 session 的函数
15
+ * @param {number} [options.ttlMs=1800000] - TTL 毫秒数,默认 30 分钟
16
+ * @param {number} [options.checkIntervalMs=60000] - 检查间隔,默认 60 秒
17
+ */
18
+ constructor(options = {}) {
19
+ this._getSessionContexts = options.getSessionContexts;
20
+ this._destroySession = options.destroySession;
21
+ this._ttlMs = options.ttlMs || 30 * 60 * 1000;
22
+ this._checkIntervalMs = options.checkIntervalMs || DEFAULT_SESSION_CLEANUP_INTERVAL_MS;
23
+ this._intervalId = null;
24
+ this._lastAccessMap = new Map();
25
+ }
26
+
27
+ /**
28
+ * 设置 TTL
29
+ */
30
+ setTTL(ttlMs) {
31
+ this._ttlMs = ttlMs;
32
+ return this;
33
+ }
34
+
35
+ /**
36
+ * 启动定期清理
37
+ */
38
+ start() {
39
+ if (this._intervalId) return;
40
+ this._intervalId = setInterval(() => this._cleanup(), this._checkIntervalMs);
41
+ this._intervalId.unref();
42
+ return this;
43
+ }
44
+
45
+ /**
46
+ * 停止定期清理
47
+ */
48
+ stop() {
49
+ if (this._intervalId) {
50
+ clearInterval(this._intervalId);
51
+ this._intervalId = null;
52
+ }
53
+ return this;
54
+ }
55
+
56
+ /**
57
+ * 标记 session 活跃(更新最后访问时间)
58
+ */
59
+ touch(sessionId) {
60
+ this._lastAccessMap.set(sessionId, Date.now());
61
+ }
62
+
63
+ /**
64
+ * 清理过期 session
65
+ */
66
+ _cleanup() {
67
+ if (!this._getSessionContexts) return;
68
+ const contexts = this._getSessionContexts();
69
+ if (!contexts || contexts.size === 0) return;
70
+
71
+ const now = Date.now();
72
+ for (const [sessionId] of contexts) {
73
+ const lastAccess = this._lastAccessMap.get(sessionId) || now;
74
+ if (now - lastAccess > this._ttlMs) {
75
+ this._lastAccessMap.delete(sessionId);
76
+ if (this._destroySession) {
77
+ this._destroySession(sessionId);
78
+ }
79
+ }
80
+ }
81
+ }
82
+
83
+ /**
84
+ * 销毁
85
+ */
86
+ destroy() {
87
+ this.stop();
88
+ this._lastAccessMap.clear();
89
+ }
90
+ }
91
+
92
+ module.exports = { SessionTTL };
@@ -6,7 +6,7 @@
6
6
 
7
7
  const fs = require('fs');
8
8
  const path = require('path');
9
- const { generateEntryId, EntryTypes } = require('./session-entry');
9
+ const { generateEntryId, EntryTypes } = require('../session/entry');
10
10
 
11
11
  const CURRENT_SESSION_VERSION = 3;
12
12