foliko 2.0.5 → 2.0.7

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 (93) hide show
  1. package/.claude/settings.local.json +4 -1
  2. package/.cli_default_systemPrompt.md +291 -0
  3. package/CLAUDE.md +3 -0
  4. package/README.md +20 -3
  5. package/docs/architecture.md +34 -2
  6. package/docs/extensions.md +199 -0
  7. package/docs/migration.md +100 -0
  8. package/docs/public-api.md +280 -24
  9. package/docs/usage.md +122 -30
  10. package/package.json +1 -1
  11. package/plugins/core/audit/index.js +1 -1
  12. package/plugins/core/default/bootstrap.js +44 -19
  13. package/plugins/core/python-loader/index.js +43 -25
  14. package/plugins/core/scheduler/index.js +1 -0
  15. package/plugins/core/skill-manager/PROMPT.md +6 -0
  16. package/plugins/core/skill-manager/index.js +402 -115
  17. package/plugins/core/sub-agent/PROMPT.md +10 -0
  18. package/plugins/core/sub-agent/index.js +36 -3
  19. package/plugins/core/think/index.js +1 -0
  20. package/plugins/core/workflow/context.js +941 -0
  21. package/plugins/core/workflow/engine.js +66 -0
  22. package/plugins/core/workflow/examples/01-basic.js +42 -0
  23. package/plugins/core/workflow/examples/01-basic.json +30 -0
  24. package/plugins/core/workflow/examples/02-choice.js +75 -0
  25. package/plugins/core/workflow/examples/02-choice.json +59 -0
  26. package/plugins/core/workflow/examples/03-chain-style.js +114 -0
  27. package/plugins/core/workflow/examples/03-each.json +41 -0
  28. package/plugins/core/workflow/examples/04-parallel.js +52 -0
  29. package/plugins/core/workflow/examples/04-parallel.json +51 -0
  30. package/plugins/core/workflow/examples/05-chain-style.json +68 -0
  31. package/plugins/core/workflow/examples/05-each.js +65 -0
  32. package/plugins/core/workflow/examples/06-script.js +82 -0
  33. package/plugins/core/workflow/examples/07-module-export.js +43 -0
  34. package/plugins/core/workflow/examples/08-default-export.js +29 -0
  35. package/plugins/core/workflow/examples/09-next-with-args.js +50 -0
  36. package/plugins/core/workflow/examples/10-logger.js +34 -0
  37. package/plugins/core/workflow/examples/11-storage.js +68 -0
  38. package/plugins/core/workflow/examples/simple.js +77 -0
  39. package/plugins/core/workflow/examples/simple.json +75 -0
  40. package/plugins/core/workflow/index.js +204 -78
  41. package/plugins/core/workflow/js-runner.js +318 -0
  42. package/plugins/core/workflow/json-runner.js +323 -0
  43. package/plugins/core/workflow/stages/action.js +211 -0
  44. package/plugins/core/workflow/stages/choice.js +74 -0
  45. package/plugins/core/workflow/stages/delay.js +73 -0
  46. package/plugins/core/workflow/stages/each.js +123 -0
  47. package/plugins/core/workflow/stages/parallel.js +69 -0
  48. package/plugins/core/workflow/stages/try.js +142 -0
  49. package/plugins/executors/data-splitter/PROMPT.md +13 -0
  50. package/plugins/executors/data-splitter/index.js +8 -6
  51. package/plugins/executors/extension/extension-registry.js +145 -0
  52. package/plugins/executors/extension/index.js +405 -437
  53. package/plugins/executors/extension/prompt-builder.js +359 -0
  54. package/plugins/executors/extension/skill-helper.js +143 -0
  55. package/plugins/messaging/feishu/index.js +5 -3
  56. package/plugins/messaging/qq/index.js +6 -4
  57. package/plugins/messaging/telegram/index.js +6 -3
  58. package/plugins/messaging/weixin/index.js +5 -3
  59. package/plugins/tools/PROMPT.md +26 -0
  60. package/plugins/tools/index.js +6 -5
  61. package/sandbox/check-context.js +5 -0
  62. package/sandbox/test-context.js +27 -0
  63. package/sandbox/test-fixes.js +40 -0
  64. package/sandbox/test-hello-js.js +46 -0
  65. package/skills/foliko/AGENTS.md +196 -43
  66. package/skills/foliko/SKILL.md +157 -28
  67. package/skills/mcp/SKILL.md +77 -118
  68. package/skills/plugins/SKILL.md +89 -3
  69. package/skills/python/SKILL.md +57 -39
  70. package/skills/skill-guide/SKILL.md +42 -34
  71. package/skills/workflows/SKILL.md +753 -436
  72. package/src/agent/chat.js +56 -28
  73. package/src/agent/main.js +39 -14
  74. package/src/agent/prompt-registry.js +56 -16
  75. package/src/agent/prompts/PROMPT.md +3 -0
  76. package/src/agent/sub.js +1 -1
  77. package/src/cli/ui/chat-ui-old.js +5 -2
  78. package/src/cli/ui/chat-ui.js +9 -5
  79. package/src/common/constants.js +12 -0
  80. package/src/common/error-capture.js +91 -0
  81. package/src/common/json-safe.js +20 -0
  82. package/src/common/logger.js +2 -2
  83. package/src/context/compressor.js +6 -2
  84. package/src/executors/mcp-executor.js +105 -125
  85. package/src/framework/framework.js +78 -12
  86. package/src/index.js +4 -0
  87. package/src/plugin/base.js +908 -5
  88. package/src/plugin/manager.js +124 -9
  89. package/src/tool/schema.js +32 -9
  90. package/src/utils/sandbox.js +1 -1
  91. package/website/index.html +821 -0
  92. package/skills/workflows/workflow-troubleshooting/DEBUGGING.md +0 -197
  93. package/skills/workflows/workflow-troubleshooting/SKILL.md +0 -391
@@ -14,7 +14,7 @@ const { StdioClientTransport } = require('@modelcontextprotocol/sdk/client/stdio
14
14
  const { zodSchemaToMarkdown } = require('@chnak/zod-to-markdown');
15
15
  const { logger } = require('../common/logger');
16
16
  const { MCPClientWrapper } = require('./mcp-client');
17
- const { extractSchema, buildMCPServersDescription, bindMcpParamsDesc } = require('./mcp-desc');
17
+ const { extractSchema } = require('./mcp-desc');
18
18
  const { jsonSchemaToZod } = require('../tool/schema');
19
19
 
20
20
  const log = logger.child('MCPExecutor');
@@ -53,120 +53,6 @@ class MCPExecutorPlugin extends Plugin {
53
53
  }
54
54
  }
55
55
 
56
- framework.registerTool({
57
- name: 'mcp_tool_schema',
58
- description: '查询工具的调用示例,直接复制 example 或 fullExample 字段使用',
59
- inputSchema: z.object({
60
- server: z.string().describe('MCP 服务器名称'),
61
- tool: z.string().describe('工具名称'),
62
- }),
63
- execute: async (args) => {
64
- const { server, tool } = args;
65
- const clientInfo = this._clients.get(server);
66
- if (!clientInfo) return { success: false, error: `MCP server '${server}' not found` };
67
-
68
- const toolInfo = clientInfo.tools.find((t) => t.name === tool);
69
- if (!toolInfo) return { success: false, error: `Tool '${tool}' not found` };
70
-
71
- const schema = extractSchema(toolInfo.inputSchema);
72
- const props = schema.properties || {};
73
- const required = schema.required || [];
74
-
75
- const generateExample = (prop, depth = 0) => {
76
- if (!prop) return null;
77
- const type = prop.type || (prop.properties ? 'object' : 'string');
78
- if (type === 'string') return prop.description?.split('.')[0] || `${prop.title || '值'}`;
79
- if (type === 'number' || type === 'integer') return prop.default || 0;
80
- if (type === 'boolean') return false;
81
- if (type === 'array') {
82
- if (prop.items) {
83
- const itemExample = generateExample(prop.items, depth + 1);
84
- return depth === 0 ? [itemExample] : itemExample;
85
- }
86
- return ['示例'];
87
- }
88
- if (type === 'object' || prop.properties) {
89
- const obj = {};
90
- for (const [key, val] of Object.entries(prop.properties || {})) {
91
- obj[key] = generateExample(val, depth + 1);
92
- }
93
- return obj;
94
- }
95
- return null;
96
- };
97
-
98
- const exampleArgs = {};
99
- for (const key of required) {
100
- const prop = props[key];
101
- if (prop) exampleArgs[key] = generateExample(prop);
102
- }
103
-
104
- return {
105
- success: true,
106
- data: {
107
- name: toolInfo.name, description: toolInfo.description, required,
108
- parameters: Object.fromEntries(
109
- required.map((key) => [key, {
110
- type: props[key]?.type || 'string',
111
- description: props[key]?.description || '',
112
- example: generateExample(props[key]),
113
- }])
114
- ),
115
- example: JSON.stringify(exampleArgs, null, 2),
116
- fullExample: `mcp_call({ server: "${server}", tool: "${tool}", args_json: ${JSON.stringify(JSON.stringify(exampleArgs))} })`,
117
- },
118
- };
119
- },
120
- });
121
-
122
- framework.registerTool({
123
- name: 'mcp_call',
124
- description: '调用 MCP 服务器工具。args_json 是包含实际参数的 JSON 字符串',
125
- inputSchema: z.object({
126
- server: z.string().describe('MCP 服务器名称'),
127
- tool: z.string().describe('工具名称'),
128
- args_json: z.string().describe('参数 JSON 字符串'),
129
- }),
130
- execute: async (args) => {
131
- const { server, tool, args_json } = args;
132
- const clientInfo = this._clients.get(server);
133
- if (!clientInfo) return { success: false, error: `MCP server '${server}' not found` };
134
-
135
- let finalArgs = {};
136
- if (args_json) {
137
- try { finalArgs = JSON.parse(args_json); }
138
- catch (e) { return { success: false, error: `args_json 格式错误: ${args_json}` }; }
139
- }
140
-
141
- if (!finalArgs || Object.keys(finalArgs).length === 0) {
142
- const toolInfo = clientInfo.tools.find((t) => t.name === tool);
143
- if (toolInfo) {
144
- const schema = extractSchema(toolInfo.inputSchema);
145
- const required = schema.required || [];
146
- return { success: false, error: `参数为空!必须提供: ${required.join(', ')}`, hint: `正确格式: {"${required[0]}": "具体值"}` };
147
- }
148
- }
149
-
150
- try {
151
- const mcpTool = clientInfo.toolObjects?.[tool];
152
- if (!mcpTool) return { success: false, error: `Tool '${tool}' not found on server '${server}'` };
153
-
154
- const mcpToolName = `${server}:${tool}`;
155
- framework.emit('tool:call', { name: mcpToolName, args: finalArgs, source: 'mcp' });
156
- framework.emit('tool-call', { name: mcpToolName, args: finalArgs, source: 'mcp' });
157
-
158
- const execResult = await mcpTool.execute(finalArgs);
159
-
160
- framework.emit('tool:result', { name: mcpToolName, args: finalArgs, result: execResult, source: 'mcp' });
161
- return { success: true, data: execResult };
162
- } catch (err) {
163
- log.error(` Tool '${tool}' failed:`, err.message);
164
- framework.emit('tool:error', { name: `${server}:${tool}`, args: finalArgs, error: err.message, source: 'mcp' });
165
- return { success: false, error: err.message };
166
- }
167
- },
168
- });
169
-
170
56
  framework.registerTool({
171
57
  name: 'mcp_list_servers',
172
58
  description: '列出已连接的 MCP 服务器及其工具',
@@ -235,9 +121,12 @@ class MCPExecutorPlugin extends Plugin {
235
121
  if (!clientInfo) return { success: false, error: `MCP server '${server}' not found` };
236
122
  try { clientInfo.client.close?.(); } catch (e) { /* ignore */ }
237
123
  this._clients.delete(server);
238
- for (const key of Object.keys(this.tools)) {
239
- if (key.startsWith(`${server}_`)) delete this.tools[key];
124
+ // 注销该服务器的所有工具(this.tools 中的 key 就是 MCP 原始工具名)
125
+ for (const t of clientInfo.tools) {
126
+ if (this.tools[t.name]) delete this.tools[t.name];
240
127
  }
128
+ // 同时注销对应的扩展(mcp:<server>)
129
+ this._unregisterExtension(server);
241
130
  await this._saveMCPServerEnabled(server, false);
242
131
  this._refreshAllAgentsMCPPrompt(this._framework);
243
132
  return { success: true, data: `MCP 服务器 '${server}' 已关闭` };
@@ -343,15 +232,23 @@ class MCPExecutorPlugin extends Plugin {
343
232
  toolObjects[toolName] = tool;
344
233
  toolsInfo.push({ name: toolName, description: tool.description || '', inputSchema: tool.inputSchema || {} });
345
234
 
346
- const registeredName = `${name}_${toolName}`;
347
- this.tools[registeredName] = {
348
- name: registeredName,
349
- description: `[${name}] ${tool.description || ''}`,
235
+ // 直接调用底层 MCP 工具(不再走 mcp_call 中转)
236
+ this.tools[toolName] = {
237
+ name: toolName,
238
+ description: tool.description || '',
350
239
  inputSchema: tool.inputSchema || {},
351
240
  execute: async (args) => {
352
- return await this._framework.executeTool('mcp_call', {
353
- server: name, tool: toolName, args_json: JSON.stringify(args || {}),
354
- });
241
+ try {
242
+ const mcpToolName = `${name}:${toolName}`;
243
+ framework?.emit?.('tool:call', { name: mcpToolName, args: args || {}, source: 'mcp' });
244
+ const execResult = await tool.execute(args || {});
245
+ framework?.emit?.('tool:result', { name: mcpToolName, args: args || {}, result: execResult, source: 'mcp' });
246
+ return { success: true, data: execResult };
247
+ } catch (err) {
248
+ log.error(` Tool '${toolName}' failed:`, err.message);
249
+ framework?.emit?.('tool:error', { name: `${name}:${toolName}`, args: args || {}, error: err.message, source: 'mcp' });
250
+ return { success: false, error: err.message };
251
+ }
355
252
  },
356
253
  };
357
254
  }
@@ -359,10 +256,89 @@ class MCPExecutorPlugin extends Plugin {
359
256
 
360
257
  this._clients.set(name, { client, tools: toolsInfo, toolObjects, enabled: config.enabled !== false });
361
258
 
259
+ // 把整个 MCP 服务器注册为扩展(mcp:<servername>),让 ext_call / ext_skill 统一调度
260
+ this._registerAsExtension(name, toolsInfo, toolObjects);
261
+
262
+ // 触发 mcp 工具变化事件,通知 extension-executor 刷新
263
+ this._framework?.emit?.('mcp:tools-changed', { name, toolsCount: toolsInfo.length });
264
+
362
265
  log.debug(` Added server '${name}' with ${toolsInfo.length} tools`);
363
266
  return { success: true, name, toolsCount: toolsInfo.length };
364
267
  }
365
268
 
269
+ /**
270
+ * 把 MCP 服务器注册为扩展插件(mcp:<servername>)
271
+ * 使其能通过 ext_call({plugin:'mcp:<server>', tool:'xxx', args:{...}}) 调用
272
+ * 通过 ext_skill({plugin:'mcp:<server>'}) 获取参数说明
273
+ */
274
+ _registerAsExtension(serverName, toolsInfo, toolObjects) {
275
+ const extExecutor = this._framework?.pluginManager?.get('extension-executor');
276
+ if (!extExecutor || typeof extExecutor.registerTool !== 'function') return;
277
+
278
+ const extName = `mcp:${serverName}`;
279
+ // 优先从 server config 取 description,否则用 server name
280
+ const serverConfig = this._serverConfigs.get(serverName) || {};
281
+ const extDescription = serverConfig.description
282
+ ? serverConfig.description
283
+ : `MCP 服务器 ${serverName}`;
284
+ const pluginInfo = {
285
+ name: extName,
286
+ description: extDescription,
287
+ version: '1.0.0',
288
+ };
289
+
290
+ for (const toolInfo of toolsInfo) {
291
+ const mcpTool = toolObjects[toolInfo.name];
292
+ if (!mcpTool) continue;
293
+
294
+ // 把 JSON schema 转换为 Zod schema(用于 AI SDK 的工具参数定义)
295
+ // 同时保留原始 JSON schema 作为 _rawSchema,让 ext_skill 能渲染出清晰的参数说明
296
+ const rawSchema = extractSchema(toolInfo.inputSchema);
297
+ let inputSchema = rawSchema;
298
+ try {
299
+ if (rawSchema && rawSchema.properties) {
300
+ const zodSchema = this._jsonSchemaToZod(rawSchema);
301
+ if (zodSchema) inputSchema = zodSchema;
302
+ }
303
+ } catch (e) { /* fallback: 保留原始 schema */ }
304
+
305
+ extExecutor.registerTool(extName, pluginInfo, {
306
+ name: toolInfo.name,
307
+ description: toolInfo.description || '',
308
+ inputSchema,
309
+ _rawSchema: rawSchema, // 原始 JSON schema,用于 ext_skill 渲染
310
+ execute: async (args, framework) => {
311
+ try {
312
+ const finalArgs = args || {};
313
+ const mcpToolName = `${serverName}:${toolInfo.name}`;
314
+ framework?.emit?.('tool:call', { name: mcpToolName, args: finalArgs, source: 'mcp' });
315
+ const result = await mcpTool.execute(finalArgs);
316
+ framework?.emit?.('tool:result', { name: mcpToolName, args: finalArgs, result, source: 'mcp' });
317
+ return { success: true, data: result };
318
+ } catch (err) {
319
+ log.error(` Tool '${toolInfo.name}' failed:`, err.message);
320
+ return { success: false, error: err.message };
321
+ }
322
+ },
323
+ });
324
+ }
325
+ }
326
+
327
+ /**
328
+ * 注销 MCP 服务器对应的扩展
329
+ */
330
+ _unregisterExtension(serverName) {
331
+ const extExecutor = this._framework?.pluginManager?.get('extension-executor');
332
+ if (!extExecutor || typeof extExecutor._unregisterTool !== 'function') return;
333
+
334
+ const extName = `mcp:${serverName}`;
335
+ const clientInfo = this._clients.get(serverName);
336
+ const toolNames = clientInfo?.tools?.map((t) => t.name) || [];
337
+ for (const toolName of toolNames) {
338
+ extExecutor._unregisterTool(extName, toolName);
339
+ }
340
+ }
341
+
366
342
  async removeServer(name) {
367
343
  const clientInfo = this._clients.get(name);
368
344
  if (clientInfo) {
@@ -373,9 +349,13 @@ class MCPExecutorPlugin extends Plugin {
373
349
  }
374
350
  } catch (e) { /* ignore */ }
375
351
  for (const toolName of Object.keys(this.tools)) {
376
- if (toolName.startsWith(name + '_')) delete this.tools[toolName];
352
+ if (this.tools[toolName]?.description?.includes?.(`[${name}]`)) {
353
+ delete this.tools[toolName];
354
+ }
377
355
  }
378
356
  this._clients.delete(name);
357
+ this._unregisterExtension(name);
358
+ this._framework?.emit?.('mcp:tools-changed', { name, removed: true });
379
359
  }
380
360
  }
381
361
 
@@ -91,7 +91,7 @@ class Framework extends EventEmitter {
91
91
  // Subsystems
92
92
  this.toolRegistry = new ToolRegistry();
93
93
  this.pluginManager = new PluginManager(this);
94
- this.promptRegistry = new PromptRegistry();
94
+ this.promptRegistry = new PromptRegistry(this);
95
95
 
96
96
  // Agent management
97
97
  this._agents = [];
@@ -206,16 +206,70 @@ class Framework extends EventEmitter {
206
206
  async disablePlugin(name) { await this.pluginManager.disable(name); return this; }
207
207
  updatePluginConfig(name, config) { return this.pluginManager.updatePluginConfig(name, config); }
208
208
 
209
- registerTool(tool) { this.toolRegistry.register(tool); return this; }
209
+ registerTool(tool) {
210
+ this.toolRegistry.register(tool);
211
+ // 通知所有 Agent 使 ChatHandler._aiToolsCache 失效。
212
+ // 🔴 修复:热重载插件时,插件调 framework.registerTool 注册新工具,
213
+ // 但 ChatHandler 的 _aiToolsCache 只在 agent.registerTool 被调用时清,
214
+ // 所以 reload 后 LLM 仍使用旧的 execute 闭包。
215
+ if (Array.isArray(this._agents)) {
216
+ for (const agent of this._agents) {
217
+ const ch = agent && agent._chatHandler;
218
+ if (ch) {
219
+ ch._aiToolsCache = null;
220
+ ch._aiToolsCacheKey = '';
221
+ ch._toolsTokensCacheVersion = 0;
222
+ if (ch._toolExecutor && ch._toolExecutor._tools) {
223
+ ch._toolExecutor._tools.set(tool.name, tool);
224
+ }
225
+ }
226
+ }
227
+ }
228
+ this.emit('tool:registered-changed', tool);
229
+ return this;
230
+ }
210
231
  tool(toolDef) {
211
232
  const tool = { ...toolDef };
212
233
  if (tool.input && !tool.inputSchema) tool.inputSchema = tool.input;
213
234
  delete tool.input;
214
235
  this.toolRegistry.register(tool);
236
+ // 同步使所有 Agent 的 ChatHandler 缓存失效并同步工具。
237
+ if (Array.isArray(this._agents)) {
238
+ for (const agent of this._agents) {
239
+ const ch = agent && agent._chatHandler;
240
+ if (ch) {
241
+ ch._aiToolsCache = null;
242
+ ch._aiToolsCacheKey = '';
243
+ ch._toolsTokensCacheVersion = 0;
244
+ if (ch._toolExecutor && ch._toolExecutor._tools) {
245
+ ch._toolExecutor._tools.set(tool.name, tool);
246
+ }
247
+ }
248
+ }
249
+ }
215
250
  return this;
216
251
  }
217
252
  getTools() { return this.toolRegistry.getAll(); }
218
253
 
254
+ unregisterTool(name) {
255
+ if (this.toolRegistry.unregister) this.toolRegistry.unregister(name);
256
+ if (Array.isArray(this._agents)) {
257
+ for (const agent of this._agents) {
258
+ const ch = agent && agent._chatHandler;
259
+ if (ch) {
260
+ ch._aiToolsCache = null;
261
+ ch._aiToolsCacheKey = '';
262
+ ch._toolsTokensCacheVersion = 0;
263
+ if (ch._toolExecutor && ch._toolExecutor._tools) {
264
+ ch._toolExecutor._tools.delete(name);
265
+ }
266
+ }
267
+ }
268
+ }
269
+ this.emit('tool:unregistered-changed', { name });
270
+ return this;
271
+ }
272
+
219
273
  // tools 对象别名
220
274
  get tools() {
221
275
  const self = this;
@@ -912,23 +966,23 @@ class Framework extends EventEmitter {
912
966
  return true;
913
967
  },
914
968
  /**
915
- * 调用 MCP 工具
969
+ * 调用 MCP 工具(通过 ext_call 统一调度)
916
970
  * @param {string} server 服务器名
917
971
  * @param {string} tool 工具名
918
972
  * @param {Object} args 参数
919
973
  */
920
974
  call: async (server, tool, args) => {
921
- return await self.executeTool('mcp_call', {
922
- server,
975
+ return await self.executeTool('ext_call', {
976
+ plugin: `mcp:${server}`,
923
977
  tool,
924
- args_json: JSON.stringify(args || {}),
978
+ args: args || {},
925
979
  });
926
980
  },
927
981
  /**
928
- * 获取工具调用示例
982
+ * 获取工具调用参数说明(通过 ext_skill)
929
983
  */
930
- getToolSchema: async (server, tool) => {
931
- return await self.executeTool('mcp_tool_schema', { server, tool });
984
+ getToolSchema: async (server, _tool) => {
985
+ return await self.executeTool('ext_skill', { plugin: `mcp:${server}` });
932
986
  },
933
987
  /**
934
988
  * 动态开启/关闭 MCP 服务器
@@ -946,7 +1000,14 @@ class Framework extends EventEmitter {
946
1000
  }
947
1001
 
948
1002
  getMainAgent() { return this._mainAgent; }
949
- getSystemPrompt() { return this._mainAgent?._buildSystemPrompt?.() || ''; }
1003
+ /**
1004
+ * 获取 main agent 的完整系统提示词
1005
+ * 统一走 framework.prompts.build()(含 _originalPrompt + 注册表所有 parts)
1006
+ * 这是 LLM 实际看到的完整 system prompt
1007
+ */
1008
+ getSystemPrompt() {
1009
+ return this.prompts?.build?.() || '';
1010
+ }
950
1011
  getPluginInstance(name) { return this.pluginManager.get(name) || null; }
951
1012
  getAgents() { return [...this._agents]; }
952
1013
  listSessionContexts() { return Array.from(this._sessionContexts.keys()); }
@@ -1007,7 +1068,10 @@ class Framework extends EventEmitter {
1007
1068
  const { SessionManager } = require('../session/session');
1008
1069
  manager = SessionManager.open(sessionFile, sessionDir, cwd);
1009
1070
  }
1010
- } catch { /* ignore */ }
1071
+ } catch (err) {
1072
+ // Log error but continue - will create new session if load failed
1073
+ this.logger.warn(`[Framework] Failed to load session '${sessionId}': ${err.message}`);
1074
+ }
1011
1075
  if (!manager) {
1012
1076
  const { SessionManager } = require('../session/session');
1013
1077
  manager = new SessionManager(cwd, sessionDir, sessionFile, true);
@@ -1032,7 +1096,9 @@ class Framework extends EventEmitter {
1032
1096
  this._sessionContexts.set(sessionId, manager);
1033
1097
  this.emit('session:context-created', { sessionId, manager });
1034
1098
  }
1035
- } catch { /* ignore */ }
1099
+ } catch (err) {
1100
+ this.logger.warn(`[Framework] Failed to load session context '${sessionId}': ${err.message}`);
1101
+ }
1036
1102
  return manager || null;
1037
1103
  }
1038
1104
 
package/src/index.js CHANGED
@@ -11,6 +11,10 @@
11
11
  // 加载工作目录 .env 配置(优先于系统环境变量)
12
12
  require('dotenv').config({ override: true });
13
13
 
14
+ // 安装全局错误捕获:把 unhandledRejection / uncaughtException 的完整堆栈
15
+ // 落盘到 .foliko/error.log。需要堆栈时直接 cat 这个文件即可。
16
+ require('./common/error-capture').install();
17
+
14
18
  // ============================================================
15
19
  // Framework
16
20
  // ============================================================