foliko 2.0.2 → 2.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/README.md +6 -6
  2. package/docs/public-api.md +91 -0
  3. package/docs/system-prompt.md +219 -0
  4. package/docs/usage.md +6 -6
  5. package/package.json +1 -1
  6. package/plugins/ambient/ExplorerLoop.js +1 -0
  7. package/plugins/ambient/index.js +1 -0
  8. package/plugins/core/coordinator/index.js +10 -5
  9. package/plugins/core/default/bootstrap.js +21 -3
  10. package/plugins/core/default/config.js +12 -3
  11. package/plugins/core/python-loader/index.js +3 -3
  12. package/plugins/core/scheduler/index.js +26 -2
  13. package/plugins/core/skill-manager/index.js +198 -151
  14. package/plugins/core/sub-agent/index.js +34 -15
  15. package/plugins/core/think/index.js +1 -0
  16. package/plugins/core/workflow/index.js +5 -4
  17. package/plugins/executors/data-splitter/index.js +14 -1
  18. package/plugins/executors/extension/index.js +52 -38
  19. package/plugins/executors/python/index.js +3 -3
  20. package/plugins/executors/shell/index.js +6 -4
  21. package/plugins/io/web/index.js +2 -1
  22. package/plugins/memory/index.js +29 -74
  23. package/plugins/messaging/email/handlers.js +1 -19
  24. package/plugins/messaging/email/monitor.js +2 -17
  25. package/plugins/messaging/email/reply.js +2 -1
  26. package/plugins/messaging/email/utils.js +20 -1
  27. package/plugins/messaging/feishu/index.js +1 -1
  28. package/plugins/messaging/qq/index.js +1 -1
  29. package/plugins/messaging/telegram/index.js +1 -1
  30. package/plugins/messaging/weixin/index.js +1 -1
  31. package/plugins/plugin-manager/index.js +1 -33
  32. package/plugins/tools/index.js +14 -1
  33. package/skills/plugins/SKILL.md +316 -0
  34. package/skills/skill-guide/SKILL.md +5 -5
  35. package/skills/{subagent-guide → subagents}/SKILL.md +1 -1
  36. package/skills/{workflow-guide → workflows}/SKILL.md +3 -3
  37. package/skills/{workflow-troubleshooting → workflows/workflow-troubleshooting}/DEBUGGING.md +197 -197
  38. package/skills/{workflow-troubleshooting → workflows/workflow-troubleshooting}/SKILL.md +391 -391
  39. package/src/agent/base.js +5 -20
  40. package/src/agent/chat.js +67 -5
  41. package/src/agent/index.js +20 -8
  42. package/src/agent/main.js +100 -23
  43. package/src/agent/prompt-registry.js +296 -0
  44. package/src/agent/sub-config.js +1 -78
  45. package/src/agent/sub.js +20 -25
  46. package/src/cli/commands/plugin.js +1 -60
  47. package/src/cli/ui/chat-ui-old.js +1 -1
  48. package/src/cli/ui/chat-ui.js +21 -17
  49. package/src/cli/ui/components/agent-mention-provider.js +1 -1
  50. package/src/common/constants.js +42 -0
  51. package/src/common/id.js +13 -0
  52. package/src/common/queue.js +3 -3
  53. package/src/context/compressor.js +17 -9
  54. package/src/framework/framework.js +218 -0
  55. package/src/framework/index.js +1 -2
  56. package/src/framework/lifecycle.js +102 -1
  57. package/src/framework/loader.js +1 -55
  58. package/src/index.js +11 -2
  59. package/src/plugin/base.js +69 -55
  60. package/src/plugin/loader.js +1 -57
  61. package/src/session/chat.js +1 -1
  62. package/src/session/entry.js +1 -11
  63. package/src/storage/manager.js +1 -12
  64. package/src/tool/executor.js +3 -2
  65. package/src/tool/router.js +5 -5
  66. package/src/utils/data-splitter.js +2 -1
  67. package/src/utils/index.js +150 -0
  68. package/src/utils/message-validator.js +21 -17
  69. package/src/utils/plugin-helpers.js +19 -5
  70. package/subagent.md +2 -2
  71. package/tests/core/plugin-prompts.test.js +219 -0
  72. package/tests/core/prompt-registry.test.js +209 -0
  73. package/src/cli/utils/plugin-config.js +0 -50
  74. package/src/config/plugin-config.js +0 -50
  75. /package/skills/{ambient-agent → ambient}/SKILL.md +0 -0
  76. /package/skills/{foliko-dev → foliko}/AGENTS.md +0 -0
  77. /package/skills/{foliko-dev → foliko}/SKILL.md +0 -0
  78. /package/skills/{mcp-usage → mcp}/SKILL.md +0 -0
  79. /package/skills/{plugin-guide → plugins-guide}/SKILL.md +0 -0
  80. /package/skills/{python-plugin-dev → python}/SKILL.md +0 -0
@@ -79,6 +79,7 @@ const DEFAULT_RETRY_DELAY_MS = 2000;
79
79
  // System Prompt 优先级
80
80
  // ============================================================================
81
81
 
82
+ // 旧的数字优先级(保留以兼容旧代码)
82
83
  const PROMPT_PRIORITY = {
83
84
  DATETIME: 50,
84
85
  ORIGINAL_PROMPT: 80,
@@ -93,6 +94,45 @@ const PROMPT_PRIORITY = {
93
94
  TOOL_CORE_RULES: 150,
94
95
  };
95
96
 
97
+ /**
98
+ * 新的语义槽位(推荐使用)
99
+ * order 越小越靠前;同 slot 内可用 PromptPart.order 进一步排序
100
+ */
101
+ const PROMPT_SLOT = {
102
+ /** 角色身份:用户/系统设定的 systemPrompt */
103
+ IDENTITY: { order: 10, label: '角色身份' },
104
+ /** 运行环境:时间、cwd、平台等 */
105
+ ENVIRONMENT: { order: 20, label: '运行环境' },
106
+ /** 用户指令:sharedPrompt、运行时 metadata */
107
+ USER: { order: 30, label: '用户指令' },
108
+ /** 可用能力:tools、skills、subagents、extensions */
109
+ CAPABILITY: { order: 40, label: '可用能力' },
110
+ /** 行为规则:tool-core-rules、data-splitter、memory-context */
111
+ BEHAVIOR: { order: 50, label: '行为规则' },
112
+ /** 上下文摘要:自动压缩、消息历史 */
113
+ CONTEXT: { order: 60, label: '上下文摘要' },
114
+ /** 自定义兜底 */
115
+ CUSTOM: { order: 999, label: '自定义' },
116
+ };
117
+
118
+ /**
119
+ * 旧 PROMPT_PRIORITY 名字 → 新 PROMPT_SLOT 的映射
120
+ * 仅用于过渡期,旧代码迁移时按此映射
121
+ */
122
+ const PROMPT_PRIORITY_TO_SLOT = {
123
+ DATETIME: 'ENVIRONMENT',
124
+ ORIGINAL_PROMPT: 'IDENTITY',
125
+ SHARED_PROMPT: 'USER',
126
+ METADATA: 'USER',
127
+ CAPABILITIES: 'CAPABILITY',
128
+ TOOLS: 'CAPABILITY',
129
+ SKILLS: 'CAPABILITY',
130
+ SUB_AGENTS: 'CAPABILITY',
131
+ MCP_TOOLS: 'CAPABILITY',
132
+ EXTENSION_TOOLS: 'CAPABILITY',
133
+ TOOL_CORE_RULES: 'BEHAVIOR',
134
+ };
135
+
96
136
  // ============================================================================
97
137
  // 熔断器
98
138
  // ============================================================================
@@ -182,6 +222,8 @@ module.exports = {
182
222
 
183
223
  // Priority
184
224
  PROMPT_PRIORITY,
225
+ PROMPT_SLOT,
226
+ PROMPT_PRIORITY_TO_SLOT,
185
227
 
186
228
  // Circuit Breaker
187
229
  DEFAULT_CIRCUIT_FAILURE_THRESHOLD,
package/src/common/id.js CHANGED
@@ -135,4 +135,17 @@ module.exports = {
135
135
  const random = crypto.randomBytes(8).toString('base64url');
136
136
  return `id_${Date.now()}_${random}`;
137
137
  },
138
+ /**
139
+ * 生成唯一的 entry ID (8位 UUID 片段,避免冲突)
140
+ * @param {Set|string[]} existingIds - 已存在的 ID 集合
141
+ * @returns {string}
142
+ */
143
+ generateEntryId: (existingIds) => {
144
+ const byId = existingIds instanceof Set ? existingIds : new Set(existingIds);
145
+ for (let i = 0; i < 100; i++) {
146
+ const id = uuid().slice(0, 8);
147
+ if (!byId.has(id)) return id;
148
+ }
149
+ return uuid();
150
+ },
138
151
  };
@@ -122,12 +122,12 @@ class ChatQueueManager extends EventEmitter {
122
122
  await this.sleep(calculateDelay(attempt, { baseDelay: this.retryDelay }));
123
123
  continue;
124
124
  }
125
- throw lastError;
125
+ // 不 throw — 直接跳出循环,由后面的 friendlyError 统一处理
126
+ break;
126
127
  }
127
128
 
128
129
  return result;
129
130
  } catch (error) {
130
-
131
131
  lastError = error;
132
132
  if (attempt < this.retryAttempts && this.isRetryableError(error)) {
133
133
  await this.sleep(calculateDelay(attempt, { baseDelay: this.retryDelay }));
@@ -260,7 +260,7 @@ class ChatQueueManager extends EventEmitter {
260
260
  this.activeCount = 0;
261
261
 
262
262
  pendingItems.forEach((item) => {
263
- item.reject(new Error('Queue cleared by user'));
263
+ item.reject(new Error('队列已清空'));
264
264
  });
265
265
 
266
266
  this.emit('queue:cleared', { count: pendingItems.length });
@@ -326,15 +326,23 @@ class ContextCompressor {
326
326
  let removedItems = 0;
327
327
  let removedMsgs = 0;
328
328
  for (const msg of messages) {
329
- if (msg.role !== 'tool' || !Array.isArray(msg.content)) continue;
330
- const originalLength = msg.content.length;
331
- msg.content = msg.content.filter((item) => {
332
- if (item && (item.type === 'tool-result' || item.type === 'tool_result') && item.toolCallId && !validToolCallIds.has(item.toolCallId)) {
333
- removedItems++; return false;
334
- }
335
- return true;
336
- });
337
- if (msg.content.length === 0 && originalLength > 0) msg._orphaned = true;
329
+ if (msg.role !== 'tool') continue;
330
+
331
+ if (Array.isArray(msg.content)) {
332
+ // AI SDK 数组格式:按 toolCallId 过滤
333
+ const originalLength = msg.content.length;
334
+ msg.content = msg.content.filter((item) => {
335
+ if (item && (item.type === 'tool-result' || item.type === 'tool_result') && item.toolCallId && !validToolCallIds.has(item.toolCallId)) {
336
+ removedItems++; return false;
337
+ }
338
+ return true;
339
+ });
340
+ if (msg.content.length === 0 && originalLength > 0) msg._orphaned = true;
341
+ } else if (msg.tool_call_id && !validToolCallIds.has(msg.tool_call_id)) {
342
+ // OpenAI 兼容格式:整条消息是 tool result,无对应 tool_calls 时标记为 orphaned
343
+ msg._orphaned = true;
344
+ removedMsgs++;
345
+ }
338
346
  }
339
347
 
340
348
  for (let i = messages.length - 1; i >= 0; i--) {
@@ -12,10 +12,13 @@ const { AsyncLocalStorage } = require('async_hooks');
12
12
  const { EventEmitter } = require('../common/events');
13
13
  const { PluginManager } = require('../plugin/manager');
14
14
  const { ToolRegistry } = require('../tool/registry');
15
+ const { PromptRegistry } = require('../agent/prompt-registry');
15
16
  const { ContextManager } = require('../context/manager');
16
17
  const { createFrameworkLogger } = require('../common/logger');
17
18
  const { AGENT_DIR, HOME_AGENT_DIR_NAME } = require('../common/constants');
18
19
  const { SessionTTL } = require('../session/ttl');
20
+ const { Plugin } = require('../plugin/base');
21
+ const { z } = require('zod');
19
22
 
20
23
  // Add framework node_modules to search path
21
24
  const frameworkNodeModules = path.join(__dirname, '..', '..', 'node_modules');
@@ -28,6 +31,53 @@ const asyncLocalStorage = new AsyncLocalStorage();
28
31
  const requestStorage = new AsyncLocalStorage();
29
32
  const sessionStorage = new AsyncLocalStorage();
30
33
 
34
+ /**
35
+ * 将各种形式的插件定义转换为 Plugin 实例
36
+ * 支持:
37
+ * - Plugin 实例:原样返回
38
+ * - Plugin 子类(class extends Plugin):实例化
39
+ * - function(foliko) 风格:包装为 install
40
+ * - 普通对象 { name, install, start, ... }:拷贝字段并绑定生命周期方法
41
+ * @param {Plugin|Function|Object} pluginDef
42
+ * @returns {Plugin}
43
+ */
44
+ function createPluginInstance(pluginDef) {
45
+ if (pluginDef instanceof Plugin) return pluginDef;
46
+
47
+ if (typeof pluginDef === 'function') {
48
+ if (pluginDef.prototype instanceof Plugin) {
49
+ return new pluginDef();
50
+ }
51
+ // function(foliko) 风格
52
+ const instance = new Plugin();
53
+ instance.name = pluginDef.name || 'function-plugin';
54
+ const fn = pluginDef;
55
+ instance.install = function (framework) {
56
+ this._framework = framework;
57
+ return fn(framework);
58
+ };
59
+ return instance;
60
+ }
61
+
62
+ if (!pluginDef || typeof pluginDef !== 'object') {
63
+ throw new Error('Plugin definition must be a Plugin instance, subclass, function, or plain object');
64
+ }
65
+
66
+ const instance = new Plugin();
67
+ // 显式拷贝已知字段,避免复制任意属性
68
+ const fields = ['name', 'version', 'description', 'priority', 'enabled', 'system', 'tools', 'config', 'agents'];
69
+ for (const field of fields) {
70
+ if (pluginDef[field] !== undefined) instance[field] = pluginDef[field];
71
+ }
72
+ // 绑定生命周期方法(覆盖原型默认值)
73
+ for (const method of ['install', 'start', 'reload', 'uninstall']) {
74
+ if (typeof pluginDef[method] === 'function') {
75
+ instance[method] = pluginDef[method];
76
+ }
77
+ }
78
+ return instance;
79
+ }
80
+
31
81
  class Framework extends EventEmitter {
32
82
  constructor(config = {}) {
33
83
  super();
@@ -41,6 +91,7 @@ class Framework extends EventEmitter {
41
91
  // Subsystems
42
92
  this.toolRegistry = new ToolRegistry();
43
93
  this.pluginManager = new PluginManager(this);
94
+ this.promptRegistry = new PromptRegistry();
44
95
 
45
96
  // Agent management
46
97
  this._agents = [];
@@ -66,6 +117,9 @@ class Framework extends EventEmitter {
66
117
  // Coordinator
67
118
  this._coordinatorManager = null;
68
119
 
120
+ // Global abort controller for framework.stop()
121
+ this._abortController = null;
122
+
69
123
  // Session TTL
70
124
  this._sessionTTL = new SessionTTL({
71
125
  getSessionContexts: () => this._sessionContexts,
@@ -75,6 +129,7 @@ class Framework extends EventEmitter {
75
129
  // Event forwarding
76
130
  this._setupToolEventForwarding();
77
131
  this._setupAgentEventForwarding();
132
+ this._setupPromptRegistryAutoRefresh();
78
133
  this._registerBuiltinTools();
79
134
  }
80
135
 
@@ -107,6 +162,24 @@ class Framework extends EventEmitter {
107
162
  });
108
163
  }
109
164
 
165
+ /**
166
+ * promptRegistry 变更时自动失效所有 Agent 的提示词缓存
167
+ * 插件重载、skill 重载、扩展工具变更等都会触发刷新
168
+ * 系统提示词在下次 chat 时自动重建(懒刷新,不浪费性能)
169
+ */
170
+ _setupPromptRegistryAutoRefresh() {
171
+ // 仅标记脏缓存(不调 invalidateAll,避免与 promptRegistry 事件循环)
172
+ const invalidate = () => {
173
+ for (const agent of this._agents) {
174
+ agent._systemPromptDirty = true;
175
+ }
176
+ };
177
+ const REFRESH_EVENTS = ['register', 'unregister', 'clear-owner'];
178
+ for (const event of REFRESH_EVENTS) {
179
+ this.promptRegistry.on(event, invalidate);
180
+ }
181
+ }
182
+
110
183
  registerPlugin(plugin) { this.pluginManager.register(plugin); return this; }
111
184
  registerProvider(name, config) { const { registerProvider } = require('../llm/provider'); registerProvider(name, config); return this; }
112
185
  getProviderRegistry() { const { getProviderRegistry } = require('../llm/provider'); return getProviderRegistry(); }
@@ -143,6 +216,121 @@ class Framework extends EventEmitter {
143
216
  }
144
217
  getTools() { return this.toolRegistry.getAll(); }
145
218
 
219
+ // tools 对象别名
220
+ get tools() {
221
+ const self = this;
222
+ return {
223
+ register: (toolDef) => self.registerTool(toolDef),
224
+ tool: (toolDef) => self.tool(toolDef),
225
+ execute: (name, args) => self.executeTool(name, args),
226
+ list: () => self.toolRegistry.getNames(),
227
+ get: (name) => self.toolRegistry.get(name),
228
+ has: (name) => self.toolRegistry.has(name),
229
+ };
230
+ }
231
+
232
+ // Extension 工具注册
233
+ registerExtensionTools(pluginName, tools) {
234
+ const extExecutor = this.pluginManager.get('extension-executor');
235
+ if (!extExecutor) {
236
+ this.logger.warn(`[Framework] extension-executor not found, cannot register extension tools for "${pluginName}"`);
237
+ return this;
238
+ }
239
+ for (const [toolName, toolDef] of Object.entries(tools)) {
240
+ if (!toolDef || typeof toolDef !== 'object') continue;
241
+ extExecutor.registerTool(pluginName, {
242
+ name: pluginName,
243
+ description: toolDef.description || '',
244
+ version: toolDef.version || '1.0.0',
245
+ }, {
246
+ name: toolName,
247
+ description: toolDef.description || '',
248
+ inputSchema: toolDef.inputSchema,
249
+ execute: toolDef.execute,
250
+ });
251
+ }
252
+ return this;
253
+ }
254
+
255
+ // 常用类型和工具(方便 function(foliko) 插件使用)
256
+ get Plugin() { return Plugin; }
257
+ get z() { return z; }
258
+
259
+ // prompts 对象别名(系统提示词注册表)
260
+ get prompts() {
261
+ return this.promptRegistry;
262
+ }
263
+
264
+ // plugins 对象别名
265
+ get plugins() {
266
+ const self = this;
267
+ return {
268
+ get: (name) => self.pluginManager.get(name) || null,
269
+ list: () => self.pluginManager.getAll().map(p => p.instance?.name || p.name),
270
+ has: (name) => self.pluginManager.has(name),
271
+ /**
272
+ * 从插件定义创建 Plugin 实例
273
+ * 支持 Plugin 实例 / class / function(foliko) / 普通对象
274
+ */
275
+ create: (pluginDef) => createPluginInstance(pluginDef),
276
+ /**
277
+ * 注册新插件(默认会自动安装 + 启动)
278
+ * @param {Plugin|Function|Object} pluginDef 插件定义
279
+ * @param {Object} [options] 传给 PluginManager
280
+ * @param {boolean} [options.autoLoad=true] 是否自动调用 install + start
281
+ * @returns {Promise<Plugin>}
282
+ */
283
+ register: async (pluginDef, options = {}) => {
284
+ const autoLoad = options.autoLoad !== false;
285
+ const plugin = createPluginInstance(pluginDef);
286
+ self.pluginManager.register(plugin, options);
287
+ if (autoLoad) {
288
+ await self.pluginManager.load(plugin, options);
289
+ }
290
+ return plugin;
291
+ },
292
+ };
293
+ }
294
+
295
+ // 快捷获取插件实例
296
+ plugin(name) {
297
+ return this.pluginManager.get(name) || null;
298
+ }
299
+
300
+ // Extension 对象别名
301
+ get extension() {
302
+ const self = this;
303
+ return {
304
+ tool: (pluginName, tools) => self.registerExtensionTools(pluginName, tools),
305
+ execute: (plugin, tool, args) => self.executeExtension(plugin, tool, args),
306
+ list: () => {
307
+ const extExecutor = self.pluginManager.get('extension-executor');
308
+ if (!extExecutor) return [];
309
+ return extExecutor.getExtensions?.() || [];
310
+ },
311
+ listPlugins: () => {
312
+ const extExecutor = self.pluginManager.get('extension-executor');
313
+ if (!extExecutor) return [];
314
+ return extExecutor.getExtensionNames?.() || [];
315
+ },
316
+ listTools: (pluginName) => {
317
+ const extExecutor = self.pluginManager.get('extension-executor');
318
+ if (!extExecutor) return [];
319
+ return extExecutor.getExtensionTools?.(pluginName) || [];
320
+ },
321
+ getTool: (pluginName, toolName) => {
322
+ const extExecutor = self.pluginManager.get('extension-executor');
323
+ if (!extExecutor) return null;
324
+ return extExecutor.getExtensionTool?.(pluginName, toolName) || null;
325
+ },
326
+ };
327
+ }
328
+
329
+ // 执行 extension 工具
330
+ async executeExtension(plugin, tool, args = {}) {
331
+ return this.executeTool('ext_call', { plugin, tool, args });
332
+ }
333
+
146
334
  getMainAgent() { return this._mainAgent; }
147
335
  getSystemPrompt() { return this._mainAgent?._buildSystemPrompt?.() || ''; }
148
336
  getPluginInstance(name) { return this.pluginManager.get(name) || null; }
@@ -151,6 +339,36 @@ class Framework extends EventEmitter {
151
339
 
152
340
  getCwd() { return this._cwd; }
153
341
 
342
+ /**
343
+ * 获取全局 abort signal,用于中断正在运行的对话
344
+ */
345
+ getAbortSignal() {
346
+ if (!this._abortController || this._abortController.signal.aborted) {
347
+ this._abortController = new AbortController();
348
+ }
349
+ return this._abortController.signal;
350
+ }
351
+
352
+ /**
353
+ * 立即中断所有正在运行的 Agent 对话
354
+ */
355
+ stop() {
356
+ if (this._abortController) {
357
+ this._abortController.abort(new Error('用户中断'));
358
+ this._abortController = null;
359
+ }
360
+ // 清空消息队列
361
+ for (const agent of this._agents) {
362
+ if (agent._chatHandler?.queueManager) {
363
+ agent._chatHandler.queueManager.clear();
364
+ }
365
+ if (typeof agent.cancelSession === 'function') {
366
+ agent.cancelSession('default');
367
+ }
368
+ }
369
+ this.emit('framework:stopped');
370
+ }
371
+
154
372
  registerEventDescription(eventType, description, schema = null) { this.pluginManager.registerEventDescription(eventType, description, schema); return this; }
155
373
  getEventDescriptions() { return this.pluginManager.getEventDescriptions(); }
156
374
  getEventDescription(eventType) { return this.pluginManager.getEventDescription(eventType); }
@@ -3,7 +3,7 @@
3
3
  const { Framework } = require('./framework');
4
4
  const { bootstrap, ready, destroy, setCwd, rescanProject } = require('./lifecycle');
5
5
  const { FrameworkRegistry } = require('./registry');
6
- const { loadAgentConfig, loadDefaultPlugins } = require('./loader');
6
+ const { loadDefaultPlugins } = require('./loader');
7
7
 
8
8
  module.exports = {
9
9
  Framework,
@@ -13,6 +13,5 @@ module.exports = {
13
13
  destroy,
14
14
  setCwd,
15
15
  rescanProject,
16
- loadAgentConfig,
17
16
  loadDefaultPlugins,
18
17
  };
@@ -114,6 +114,15 @@ async function setCwd(framework, newCwd, options = {}) {
114
114
 
115
115
  framework.pluginManager._setStateFile(resolved);
116
116
 
117
+ // 加载新目录配置,更新 skillsDirs 确保 skill-manager 能扫描到新项目技能
118
+ const { loadAgentConfig } = require('../../plugins/core/default');
119
+ const agentConfig = loadAgentConfig(framework, framework._agentDir || '.foliko');
120
+ const skillManager = framework.pluginManager.get('skill-manager');
121
+ if (skillManager && Array.isArray(agentConfig.skillsDirs)) {
122
+ skillManager.setSearchDirs(agentConfig.skillsDirs);
123
+ } else {
124
+ }
125
+
117
126
  const count = framework._sessionContexts.size;
118
127
  for (const sid of Array.from(framework._sessionContexts.keys())) {
119
128
  framework.destroySessionContext(sid);
@@ -200,4 +209,96 @@ async function rescanProject(framework, options = {}) {
200
209
  return result;
201
210
  }
202
211
 
203
- module.exports = { bootstrap, ready, destroy, setCwd, rescanProject };
212
+ /**
213
+ * Reload all - 完全重新加载所有插件和 skill
214
+ *
215
+ * 与 rescanProject 的区别:rescanProject 保留 SYSTEM_PLUGINS(ext-exec、skill-manager)
216
+ * 的旧实例,可能残留旧 state。reloadAll 显式按顺序:
217
+ * 1. 重载 ext-exec(先清空 _extensions)
218
+ * 2. 卸载非系统插件
219
+ * 3. 重载 defaults plugin
220
+ * 4. bootstrapDefaults(加载所有 default 插件,包括新的 ext-exec 和 skill-manager)
221
+ * 5. 重载 skill-manager(最后一步,让新 skill 的 commands 注册到新 ext-exec)
222
+ *
223
+ * 推荐用法:切换 WORK_DIR 后调用
224
+ * await foliko.setCwd(fw, newCwd);
225
+ * await foliko.reloadAll(fw);
226
+ *
227
+ * @param {Framework} framework
228
+ * @param {Object} options
229
+ * @param {boolean} [options.reloadExtExec=true] 是否重载 ext-exec(默认 true,彻底清空旧 state)
230
+ * @param {boolean} [options.reloadSkillManager=true] 是否重载 skill-manager(默认 true,扫描新 skill)
231
+ * @param {Set<string>} [options.keepLoaded] 额外保留的插件名集合
232
+ */
233
+ async function reloadAll(framework, options = {}) {
234
+ const { bootstrapDefaults, DefaultPlugins, loadAgentConfig } = require('../../plugins/core/default');
235
+ const { SYSTEM_PLUGINS } = require('../common/constants');
236
+ const keepSet = new Set(options.keepLoaded || Array.from(SYSTEM_PLUGINS));
237
+
238
+ console.log('[foliko] reloadAll: starting full reload');
239
+
240
+ // 1. 重载 ext-exec(先清空 _extensions)
241
+ if (options.reloadExtExec !== false && framework.pluginManager.has('extension-executor')) {
242
+ try {
243
+ await framework.pluginManager.reload('extension-executor');
244
+ console.log('[foliko] reloadAll: ext-executor reloaded (state cleared)');
245
+ } catch (err) {
246
+ console.log('[foliko] reloadAll: ext-executor reload failed:', err.message);
247
+ }
248
+ }
249
+
250
+ // 2. 卸载非系统插件
251
+ try {
252
+ const unloadList = await framework.pluginManager.unloadExcept(keepSet);
253
+ console.log(`[foliko] reloadAll: unloaded ${unloadList.length} non-system plugins`);
254
+ } catch (err) {
255
+ console.log('[foliko] reloadAll: unloadExcept failed:', err.message);
256
+ }
257
+
258
+ // 3. 重载 defaults plugin
259
+ let defaultsPlugin = framework.pluginManager.get('defaults');
260
+ if (!defaultsPlugin) {
261
+ const dp = new DefaultPlugins({ agentDir: framework._agentDir });
262
+ try { await framework.loadPlugin(dp); defaultsPlugin = dp; }
263
+ catch (err) { console.log('[foliko] reloadAll: load defaults failed:', err.message); }
264
+ } else {
265
+ try { await defaultsPlugin.reload(framework); }
266
+ catch (err) { console.log('[foliko] reloadAll: reload defaults failed:', err.message); }
267
+ }
268
+
269
+ const agentConfig = defaultsPlugin
270
+ ? (defaultsPlugin.getConfig() || loadAgentConfig(framework, framework._agentDir))
271
+ : loadAgentConfig(framework, framework._agentDir);
272
+
273
+ // 4. bootstrapDefaults(加载所有 default 插件)
274
+ try {
275
+ await bootstrapDefaults(framework, { _config: agentConfig, _skipConfigLoad: true });
276
+ console.log('[foliko] reloadAll: bootstrapDefaults done');
277
+ } catch (err) {
278
+ console.log('[foliko] reloadAll: bootstrapDefaults failed:', err.message);
279
+ }
280
+
281
+ // 5. 重载 skill-manager(最后一步,让新 skill 的 commands 注册到新 ext-exec)
282
+ if (options.reloadSkillManager !== false) {
283
+ const skillManager = framework.pluginManager.get('skill-manager');
284
+ if (skillManager) {
285
+ if (Array.isArray(agentConfig.skillsDirs)) skillManager.setSearchDirs(agentConfig.skillsDirs);
286
+ try {
287
+ await skillManager.reload(framework);
288
+ const sm = skillManager;
289
+ const extExec = framework.pluginManager.get('extension-executor');
290
+ const skillTools = extExec?._extensions?.get('skill')?.tools || [];
291
+ console.log(
292
+ `[foliko] reloadAll: skill-manager reloaded, ${sm._skills.size} skills, ${Object.keys(sm.tools).length} tools in sm, ${skillTools.length} tools in ext-exec`
293
+ );
294
+ } catch (err) {
295
+ console.log('[foliko] reloadAll: skill-manager reload failed:', err.message);
296
+ }
297
+ }
298
+ }
299
+
300
+ framework.emit('reload:complete', { framework });
301
+ return framework;
302
+ }
303
+
304
+ module.exports = { bootstrap, ready, destroy, setCwd, rescanProject, reloadAll };
@@ -6,60 +6,6 @@
6
6
  */
7
7
 
8
8
  const path = require('path');
9
- const fs = require('fs');
10
-
11
- /**
12
- * Load agent config from .foliko/ directory
13
- * @param {Framework} framework
14
- * @param {string} agentDir
15
- * @returns {Object}
16
- */
17
- function loadAgentConfig(framework, agentDir = '.foliko') {
18
- const cwd = framework.getCwd ? framework.getCwd() : process.cwd();
19
- const folikoDir = path.resolve(cwd, agentDir);
20
- const config = {
21
- plugins: [],
22
- skills: [],
23
- ai: {},
24
- };
25
-
26
- // Load plugins.json
27
- const pluginsPath = path.join(folikoDir, 'plugins.json');
28
- if (fs.existsSync(pluginsPath)) {
29
- try {
30
- const pluginsConfig = JSON.parse(fs.readFileSync(pluginsPath, 'utf-8'));
31
- if (Array.isArray(pluginsConfig)) {
32
- config.plugins = pluginsConfig;
33
- } else if (pluginsConfig.plugins) {
34
- config.plugins = pluginsConfig.plugins;
35
- }
36
- if (pluginsConfig.ai) config.ai = { ...config.ai, ...pluginsConfig.ai };
37
- if (pluginsConfig.skillsDirs) config.skillsDirs = pluginsConfig.skillsDirs;
38
- } catch (err) {
39
- framework.logger?.warn?.('Failed to load plugins.json:', err.message);
40
- }
41
- }
42
-
43
- // Load agent configs from agents/ directory
44
- const agentsDir = path.join(folikoDir, 'agents');
45
- if (fs.existsSync(agentsDir)) {
46
- try {
47
- const files = fs.readdirSync(agentsDir).filter(f => f.endsWith('.json'));
48
- config.agentFiles = files.map(f => path.join(agentsDir, f));
49
- } catch { /* ignore */ }
50
- }
51
-
52
- // Load skills config
53
- const skillsDir = path.join(folikoDir, 'skills');
54
- if (fs.existsSync(skillsDir)) {
55
- try {
56
- config.skillsDirs = config.skillsDirs || [];
57
- config.skillsDirs.push(skillsDir);
58
- } catch { /* ignore */ }
59
- }
60
-
61
- return config;
62
- }
63
9
 
64
10
  /**
65
11
  * Load default plugins based on config
@@ -75,4 +21,4 @@ async function loadDefaultPlugins(framework, config = {}) {
75
21
  return framework.pluginManager.getAll().map(p => p.name);
76
22
  }
77
23
 
78
- module.exports = { loadAgentConfig, loadDefaultPlugins };
24
+ module.exports = { loadDefaultPlugins };
package/src/index.js CHANGED
@@ -15,12 +15,12 @@ require('dotenv').config({ override: true });
15
15
  // Framework
16
16
  // ============================================================
17
17
  const { Framework } = require('./framework/framework');
18
- const { bootstrap, ready, destroy } = require('./framework/lifecycle');
18
+ const { bootstrap, ready, destroy, setCwd, rescanProject, reloadAll } = require('./framework/lifecycle');
19
19
 
20
20
  // ============================================================
21
21
  // Agent
22
22
  // ============================================================
23
- const { createAgent, MainAgent, SubAgent, WorkerAgent } = require('./agent');
23
+ const { createAgent, MainAgent, SubAgent, WorkerAgent, PromptRegistry } = require('./agent');
24
24
 
25
25
  // ============================================================
26
26
  // Plugin
@@ -70,6 +70,9 @@ exports.createAgent = createAgent;
70
70
 
71
71
  exports.ready = ready;
72
72
  exports.destroy = destroy;
73
+ exports.setCwd = setCwd;
74
+ exports.rescanProject = rescanProject;
75
+ exports.reloadAll = reloadAll;
73
76
 
74
77
  // --- 上下文管理 ---
75
78
 
@@ -113,6 +116,11 @@ exports.withPython = (config = {}) => {
113
116
  exports.z = require('zod');
114
117
  exports.LLM = llm;
115
118
 
119
+ // --- 常量 ---
120
+ exports.PROMPT_SLOT = require('./common/constants').PROMPT_SLOT;
121
+ exports.PROMPT_PRIORITY = require('./common/constants').PROMPT_PRIORITY;
122
+ exports.PROMPT_PRIORITY_TO_SLOT = require('./common/constants').PROMPT_PRIORITY_TO_SLOT;
123
+
116
124
  // --- 框架核心类(用于扩展) ---
117
125
 
118
126
  exports.Framework = Framework;
@@ -123,6 +131,7 @@ exports.ToolRegistry = ToolRegistry;
123
131
  exports.ToolExecutor = ToolExecutor;
124
132
  exports.Plugin = Plugin;
125
133
  exports.PluginManager = PluginManager;
134
+ exports.PromptRegistry = PromptRegistry;
126
135
  exports.EventEmitter = EventEmitter;
127
136
  exports.Logger = Logger;
128
137