foliko 1.1.68 → 1.1.69

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 (142) hide show
  1. package/.claude/settings.local.json +19 -10
  2. package/.dockerignore +45 -45
  3. package/.env.example +56 -56
  4. package/CLAUDE.md +2 -2
  5. package/README.md +13 -13
  6. package/SPEC.md +3 -3
  7. package/cli/src/commands/chat.js +2 -20
  8. package/cli/src/commands/list.js +7 -6
  9. package/cli/src/commands/plugin.js +3 -2
  10. package/cli/src/daemon.js +2 -2
  11. package/cli/src/ui/chat-ui-old.js +15 -4
  12. package/cli/src/ui/chat-ui.js +152 -38
  13. package/cli/src/ui/footer-bar.js +13 -14
  14. package/cli/src/utils/config.js +29 -0
  15. package/cli/src/utils/plugin-config.js +1 -1
  16. package/docker-compose.yml +33 -33
  17. package/docs/features.md +120 -120
  18. package/docs/quick-reference.md +160 -160
  19. package/docs/user-manual.md +1391 -1391
  20. package/examples/ambient-example.js +2 -2
  21. package/examples/bootstrap.js +3 -3
  22. package/examples/test-chat.js +1 -1
  23. package/examples/test-reload.js +1 -1
  24. package/examples/test-telegram.js +1 -1
  25. package/examples/test-tg-bot.js +1 -1
  26. package/examples/test-tg-simple.js +2 -2
  27. package/examples/test-tg.js +1 -1
  28. package/examples/test-think.js +1 -1
  29. package/examples/test-weixin-feishu.js +3 -3
  30. package/package.json +3 -1
  31. package/plugins/ambient-agent/index.js +1 -1
  32. package/plugins/audit-plugin.js +75 -24
  33. package/plugins/default-plugins.js +21 -11
  34. package/plugins/email/index.js +1 -1
  35. package/plugins/extension-executor-plugin.js +85 -7
  36. package/plugins/feishu-plugin.js +118 -16
  37. package/plugins/install-plugin.js +4 -4
  38. package/plugins/memory-plugin.js +1 -1
  39. package/plugins/plugin-manager-plugin.js +3 -3
  40. package/plugins/python-executor-plugin.js +1 -1
  41. package/plugins/python-plugin-loader.js +1 -1
  42. package/plugins/qq-plugin.js +143 -16
  43. package/plugins/rules-plugin.js +1 -1
  44. package/plugins/scheduler-plugin.js +2 -2
  45. package/plugins/session-plugin.js +309 -393
  46. package/plugins/storage-plugin.js +235 -176
  47. package/plugins/subagent-plugin.js +4 -4
  48. package/plugins/telegram-plugin.js +116 -17
  49. package/plugins/think-plugin.js +1 -1
  50. package/plugins/web-plugin.js +2 -2
  51. package/plugins/weixin-plugin.js +102 -19
  52. package/skills/find-skills/AGENTS.md +2 -2
  53. package/skills/find-skills/SKILL.md +133 -133
  54. package/skills/foliko-dev/AGENTS.md +236 -236
  55. package/skills/foliko-dev/SKILL.md +19 -19
  56. package/skills/mcp-usage/SKILL.md +200 -200
  57. package/skills/plugin-guide/SKILL.md +4 -4
  58. package/skills/python-plugin-dev/SKILL.md +5 -5
  59. package/skills/skill-guide/SKILL.md +104 -6
  60. package/skills/subagent-guide/SKILL.md +237 -237
  61. package/skills/workflow-guide/SKILL.md +646 -646
  62. package/src/capabilities/skill-manager.js +101 -2
  63. package/src/capabilities/workflow-engine.js +1 -1
  64. package/src/core/agent-chat.js +17 -14
  65. package/src/core/branch-summary-auto.js +206 -0
  66. package/src/core/chat-session.js +38 -8
  67. package/src/core/command-registry.js +200 -0
  68. package/src/core/constants.js +11 -11
  69. package/src/core/context-compressor.js +700 -149
  70. package/src/core/context-manager.js +0 -1
  71. package/src/core/enhanced-context-compressor.js +210 -0
  72. package/src/core/framework.js +140 -83
  73. package/src/core/jsonl-storage.js +253 -0
  74. package/src/core/plugin-manager.js +15 -10
  75. package/src/core/provider-registry.js +159 -0
  76. package/src/core/provider.js +2 -0
  77. package/src/core/session-entry.js +225 -0
  78. package/src/core/session-manager.js +701 -0
  79. package/src/core/storage-manager.js +494 -0
  80. package/src/core/sub-agent-config.js +1 -1
  81. package/src/core/token-counter.js +177 -58
  82. package/src/core/ui-extension-context.js +174 -0
  83. package/src/executors/mcp-executor.js +15 -6
  84. package/src/utils/logger.js +152 -180
  85. package/src/utils/plugin-helpers.js +2 -2
  86. package/website_v2/docs/api.html +1 -1
  87. package/website_v2/docs/configuration.html +2 -2
  88. package/website_v2/docs/plugin-development.html +4 -4
  89. package/website_v2/docs/project-structure.html +2 -2
  90. package/website_v2/docs/skill-development.html +2 -2
  91. package/website_v2/index.html +1 -1
  92. package/website_v2/styles/animations.css +7 -7
  93. package/.agent/agents/backend-dev.md +0 -102
  94. package/.agent/agents/data-analyst.md +0 -117
  95. package/.agent/agents/devops.md +0 -115
  96. package/.agent/agents/frontend-dev.md +0 -94
  97. package/.agent/agents/network-requester.md +0 -44
  98. package/.agent/agents/poster-designer.md +0 -52
  99. package/.agent/agents/product-manager.md +0 -85
  100. package/.agent/agents/qa-engineer.md +0 -100
  101. package/.agent/agents/security-engineer.md +0 -99
  102. package/.agent/agents/team-lead.md +0 -137
  103. package/.agent/agents/ui-designer.md +0 -116
  104. package/.agent/data/default.json +0 -58
  105. package/.agent/data/email/processed-emails.json +0 -1
  106. package/.agent/data/plugins-state.json +0 -207
  107. package/.agent/data/scheduler/tasks.json +0 -1
  108. package/.agent/data/web/web-config.json +0 -5
  109. package/.agent/data/weixin/images/file_1776188148383jpg +0 -0
  110. package/.agent/data/weixin/images/file_1776188458326.jpg +0 -0
  111. package/.agent/data/weixin/images/file_1776188689423.jpg +0 -0
  112. package/.agent/data/weixin/images/file_1776188813604.jpg +0 -0
  113. package/.agent/data/weixin/images/file_1776189097450.jpg +0 -0
  114. package/.agent/data/weixin/videos/file_1776188318431.mp4 +0 -0
  115. package/.agent/data/weixin.json +0 -6
  116. package/.agent/mcp_config.json +0 -14
  117. package/.agent/memory/user/mof6gk94-kneeuh.md +0 -9
  118. package/.agent/package.json +0 -8
  119. package/.agent/plugins/marknative/README.md +0 -134
  120. package/.agent/plugins/marknative/fonts/SegoeUI Emoji.ttf +0 -0
  121. package/.agent/plugins/marknative/fonts.zip +0 -0
  122. package/.agent/plugins/marknative/index.js +0 -256
  123. package/.agent/plugins/marknative/package.json +0 -12
  124. package/.agent/plugins/test-plugin.py +0 -99
  125. package/.agent/plugins.json +0 -14
  126. package/.agent/python-scripts/test_sample.py +0 -24
  127. package/.agent/sessions/cli_default.json +0 -4121
  128. package/.agent/skills/agent-browser/SKILL.md +0 -311
  129. package/.agent/skills/agent-browser/TEST_PLAN.md +0 -200
  130. package/.agent/skills/sysinfo/SKILL.md +0 -38
  131. package/.agent/skills/sysinfo/system-info.sh +0 -130
  132. package/.agent/skills/workflow/SKILL.md +0 -324
  133. package/.agent/test-agent.js +0 -35
  134. package/.agent/weixin.json +0 -6
  135. package/.agent/workflows/email-digest.json +0 -50
  136. package/.agent/workflows/file-backup.json +0 -21
  137. package/.agent/workflows/get-ip-notify.json +0 -32
  138. package/.agent/workflows/news-aggregator.json +0 -93
  139. package/.agent/workflows/news-dashboard-v2.json +0 -94
  140. package/.agent/workflows/notification-batch.json +0 -32
  141. package/src/core/session-context.js +0 -346
  142. package/src/core/session-storage.js +0 -295
@@ -1,10 +1,6 @@
1
1
  /**
2
2
  * TokenCounter - Token 计算工具
3
- *
4
- * 职责:
5
- * 1. 计算文本的 token 数量
6
- * 2. 计算消息数组的 token 总数
7
- * 3. 计算工具定义的 token 总数
3
+ * Ported from pi's compaction/compaction.ts with enhancements
8
4
  */
9
5
 
10
6
  /**
@@ -22,56 +18,191 @@ function encode(text, bytesPerToken = 4) {
22
18
 
23
19
  /**
24
20
  * 计算 JSON 字符串的 token 数量
25
- * @param {string} text - JSON 字符串
26
- * @returns {number} token 数量
27
21
  */
28
22
  function encodeForJSON(text) {
29
23
  if (!text) return 0;
30
- // JSON 字符串需要额外计算引号和转义
31
24
  const encoded = encode(text);
32
25
  return encoded + Math.ceil(Buffer.byteLength(JSON.stringify(text), 'utf8') / 100);
33
26
  }
34
27
 
28
+ /**
29
+ * Safe JSON stringify that handles unserializable values
30
+ */
31
+ function safeJsonStringify(value) {
32
+ try {
33
+ return JSON.stringify(value) ?? 'undefined';
34
+ } catch {
35
+ return '[unserializable]';
36
+ }
37
+ }
38
+
39
+ /**
40
+ * Estimate token count for one message using a conservative character heuristic
41
+ * Ported from pi
42
+ */
43
+ function estimateTokens(message) {
44
+ let chars = 0;
45
+
46
+ switch (message.role) {
47
+ case 'user': {
48
+ const content = message.content;
49
+ if (typeof content === 'string') {
50
+ chars = content.length;
51
+ } else if (Array.isArray(content)) {
52
+ for (const block of content) {
53
+ if (block.type === 'text' && block.text) {
54
+ chars += block.text.length;
55
+ }
56
+ }
57
+ }
58
+ return Math.ceil(chars / 4);
59
+ }
60
+ case 'assistant': {
61
+ const assistant = message;
62
+ for (const block of assistant.content || []) {
63
+ if (block.type === 'text') {
64
+ chars += block.text.length;
65
+ } else if (block.type === 'thinking') {
66
+ chars += block.thinking.length;
67
+ } else if (block.type === 'toolCall') {
68
+ chars += block.name.length + safeJsonStringify(block.arguments).length;
69
+ }
70
+ }
71
+ return Math.ceil(chars / 4);
72
+ }
73
+ case 'custom':
74
+ case 'toolResult': {
75
+ if (typeof message.content === 'string') {
76
+ chars = message.content.length;
77
+ } else {
78
+ for (const block of message.content || []) {
79
+ if (block.type === 'text' && block.text) {
80
+ chars += block.text.length;
81
+ }
82
+ if (block.type === 'image') {
83
+ chars += 4800;
84
+ }
85
+ }
86
+ }
87
+ return Math.ceil(chars / 4);
88
+ }
89
+ case 'bashExecution': {
90
+ chars = message.command.length + (message.output?.length || 0);
91
+ return Math.ceil(chars / 4);
92
+ }
93
+ case 'branchSummary':
94
+ case 'compactionSummary': {
95
+ chars = message.summary.length;
96
+ return Math.ceil(chars / 4);
97
+ }
98
+ }
99
+
100
+ return 0;
101
+ }
102
+
103
+ /**
104
+ * Calculate total context tokens from provider usage
105
+ */
106
+ function calculateContextTokens(usage) {
107
+ return usage.totalTokens || (usage.input || 0) + (usage.output || 0) + (usage.cacheRead || 0) + (usage.cacheWrite || 0);
108
+ }
109
+
110
+ /**
111
+ * Get assistant usage from message
112
+ */
113
+ function getAssistantUsage(msg) {
114
+ if (msg.role === 'assistant' && msg.usage) {
115
+ if (msg.stopReason !== 'aborted' && msg.stopReason !== 'error' && msg.usage) {
116
+ return msg.usage;
117
+ }
118
+ }
119
+ return undefined;
120
+ }
121
+
122
+ /**
123
+ * Return usage from the last successful assistant message in session entries
124
+ */
125
+ function getLastAssistantUsage(entries) {
126
+ for (let i = entries.length - 1; i >= 0; i--) {
127
+ const entry = entries[i];
128
+ if (entry.type === 'message') {
129
+ const usage = getAssistantUsage(entry.message);
130
+ if (usage) return usage;
131
+ }
132
+ }
133
+ return undefined;
134
+ }
135
+
136
+ /**
137
+ * Get last assistant usage info from messages
138
+ */
139
+ function getLastAssistantUsageInfo(messages) {
140
+ for (let i = messages.length - 1; i >= 0; i--) {
141
+ const usage = getAssistantUsage(messages[i]);
142
+ if (usage) return { usage, index: i };
143
+ }
144
+ return undefined;
145
+ }
146
+
147
+ /**
148
+ * Estimate context tokens for messages using provider usage when available
149
+ */
150
+ function estimateContextTokens(messages) {
151
+ const usageInfo = getLastAssistantUsageInfo(messages);
152
+
153
+ if (!usageInfo) {
154
+ let estimated = 0;
155
+ for (const message of messages) {
156
+ estimated += estimateTokens(message);
157
+ }
158
+ return {
159
+ tokens: estimated,
160
+ usageTokens: 0,
161
+ trailingTokens: estimated,
162
+ lastUsageIndex: null
163
+ };
164
+ }
165
+
166
+ const usageTokens = calculateContextTokens(usageInfo.usage);
167
+ let trailingTokens = 0;
168
+ for (let i = usageInfo.index + 1; i < messages.length; i++) {
169
+ trailingTokens += estimateTokens(messages[i]);
170
+ }
171
+
172
+ return {
173
+ tokens: usageTokens + trailingTokens,
174
+ usageTokens,
175
+ trailingTokens,
176
+ lastUsageIndex: usageInfo.index
177
+ };
178
+ }
179
+
180
+ /**
181
+ * Return whether context usage exceeds the configured compaction threshold
182
+ */
183
+ function shouldCompact(contextTokens, contextWindow, settings) {
184
+ if (!settings.enabled) return false;
185
+ return contextTokens > contextWindow - settings.reserveTokens;
186
+ }
187
+
35
188
  class TokenCounter {
36
- /**
37
- * @param {Object} config - 配置
38
- * @param {Object} config.toolSchema - 工具 schema(用于工具 token 计算)
39
- */
40
189
  constructor(config = {}) {
41
190
  this.toolSchema = config.toolSchema || null;
42
191
  }
43
192
 
44
- /**
45
- * 计算文本 token
46
- * @param {string} text - 文本
47
- * @param {number} bytesPerToken - 每 token 字节数
48
- * @returns {number} token 数量
49
- */
50
193
  countText(text, bytesPerToken = 4) {
51
194
  return encode(text, bytesPerToken);
52
195
  }
53
196
 
54
- /**
55
- * 计算消息数组的 token 总数
56
- * @param {Array} messages - 消息数组
57
- * @returns {number} token 总数
58
- */
59
197
  countMessages(messages) {
60
198
  if (!Array.isArray(messages)) return 0;
61
199
  return messages.reduce((sum, msg) => sum + this.countMessage(msg), 0);
62
200
  }
63
201
 
64
- /**
65
- * 计算单条消息的 token
66
- * @param {Object} msg - 消息
67
- * @returns {number} token 数量
68
- */
69
202
  countMessage(msg) {
70
203
  if (!msg) return 0;
71
204
 
72
205
  let total = 0;
73
-
74
- // 角色和格式开销
75
206
  total += 4;
76
207
 
77
208
  if (typeof msg.content === 'string') {
@@ -82,9 +213,8 @@ class TokenCounter {
82
213
  }
83
214
  }
84
215
 
85
- // tool_calls 开销
86
216
  if (msg.tool_calls && Array.isArray(msg.tool_calls)) {
87
- total += 15; // overhead per tool_calls block
217
+ total += 15;
88
218
  for (const tc of msg.tool_calls) {
89
219
  if (tc.function) {
90
220
  total += this.countText(tc.function.name) + this.countText(tc.function.arguments);
@@ -92,7 +222,6 @@ class TokenCounter {
92
222
  }
93
223
  }
94
224
 
95
- // tool_call_id 开销
96
225
  if (msg.tool_call_id) {
97
226
  total += 15;
98
227
  }
@@ -100,11 +229,6 @@ class TokenCounter {
100
229
  return total;
101
230
  }
102
231
 
103
- /**
104
- * 计算 content block 的 token
105
- * @param {Object} block - content block
106
- * @returns {number} token 数量
107
- */
108
232
  countContentBlock(block) {
109
233
  if (!block) return 0;
110
234
 
@@ -126,34 +250,23 @@ class TokenCounter {
126
250
  }
127
251
  return 0;
128
252
  case 'image':
129
- // 图片按 token 估算
130
253
  return 85;
131
254
  default:
132
255
  return this.countText(JSON.stringify(block));
133
256
  }
134
257
  }
135
258
 
136
- /**
137
- * 计算工具定义的 token 总数
138
- * @param {Array} tools - 工具数组
139
- * @returns {number} token 总数
140
- */
141
259
  countTools(tools) {
142
260
  if (!tools || !Array.isArray(tools)) return 0;
143
261
 
144
262
  let total = 0;
145
263
  for (const tool of tools) {
146
- // 工具名和描述
147
- total += 20; // overhead
148
-
264
+ total += 20;
149
265
  if (tool.description) {
150
266
  total += this.countText(tool.description);
151
267
  }
152
-
153
- // 参数
154
268
  if (tool.inputSchema) {
155
- const schema =
156
- tool.inputSchema.jsonSchema || tool.inputSchema.inputSchema || tool.inputSchema;
269
+ const schema = tool.inputSchema.jsonSchema || tool.inputSchema.inputSchema || tool.inputSchema;
157
270
  if (schema.properties) {
158
271
  for (const [name, prop] of Object.entries(schema.properties)) {
159
272
  total += this.countText(name) + 10;
@@ -168,11 +281,6 @@ class TokenCounter {
168
281
  return total;
169
282
  }
170
283
 
171
- /**
172
- * 估算完整请求的 token(消息 + 工具 + 系统提示)
173
- * @param {Object} params - 请求参数
174
- * @returns {Object} - { messagesTokens, toolsTokens, systemPromptTokens, total }
175
- */
176
284
  estimateRequest({ messages, tools, systemPrompt }) {
177
285
  const messagesTokens = this.countMessages(messages);
178
286
  const toolsTokens = this.countTools(tools);
@@ -182,9 +290,20 @@ class TokenCounter {
182
290
  messagesTokens,
183
291
  toolsTokens,
184
292
  systemPromptTokens,
185
- total: messagesTokens + toolsTokens + systemPromptTokens,
293
+ total: messagesTokens + toolsTokens + systemPromptTokens
186
294
  };
187
295
  }
188
296
  }
189
297
 
190
- module.exports = { TokenCounter, encode, encodeForJSON };
298
+ module.exports = {
299
+ TokenCounter,
300
+ encode,
301
+ encodeForJSON,
302
+ estimateTokens,
303
+ calculateContextTokens,
304
+ getAssistantUsage,
305
+ getLastAssistantUsage,
306
+ estimateContextTokens,
307
+ shouldCompact,
308
+ safeJsonStringify
309
+ };
@@ -0,0 +1,174 @@
1
+ /**
2
+ * ExtensionUIContext - UI Extension Context for Plugins
3
+ * 允许插件扩展 TUI 界面
4
+ */
5
+
6
+ const { logger } = require('../utils/logger');
7
+
8
+ class ExtensionUIContext {
9
+ /**
10
+ * @param {Object} chatUI - ChatUI 实例
11
+ * @param {string} pluginName - 插件名称
12
+ */
13
+ constructor(chatUI, pluginName) {
14
+ this._ui = chatUI;
15
+ this._pluginName = pluginName;
16
+ this._components = new Map();
17
+ }
18
+
19
+ /**
20
+ * 显示选择对话框
21
+ * @param {string} title - 对话框标题
22
+ * @param {Array<{label: string, value: any}>} options - 选项
23
+ * @returns {Promise<any>} 选中的值
24
+ */
25
+ async select(title, options) {
26
+ return new Promise((resolve, reject) => {
27
+ // 简化实现:使用 TUI 的 prompt 机制
28
+ if (!this._ui || !this._ui.tui) {
29
+ reject(new Error('ChatUI not available'));
30
+ return;
31
+ }
32
+
33
+ // 构建选项文本
34
+ let prompt = `${title}\n`;
35
+ options.forEach((opt, i) => {
36
+ prompt += `${i + 1}. ${opt.label}\n`;
37
+ });
38
+ prompt += '请选择: ';
39
+
40
+ // 创建一个简单的选择机制
41
+ this._ui.editor.setPrompt(prompt);
42
+ this._ui.editor.once('submit', (value) => {
43
+ const idx = parseInt(value, 10) - 1;
44
+ if (idx >= 0 && idx < options.length) {
45
+ resolve(options[idx].value);
46
+ } else {
47
+ resolve(options[0]?.value);
48
+ }
49
+ });
50
+
51
+ this._ui.tui.setFocus(this._ui.editor);
52
+ });
53
+ }
54
+
55
+ /**
56
+ * 显示确认对话框
57
+ * @param {string} message - 确认消息
58
+ * @returns {Promise<boolean>} 是否确认
59
+ */
60
+ async confirm(message) {
61
+ const result = await this.select(message, [
62
+ { label: '是', value: true },
63
+ { label: '否', value: false },
64
+ ]);
65
+ return result;
66
+ }
67
+
68
+ /**
69
+ * 获取文本输入
70
+ * @param {string} prompt - 输入提示
71
+ * @returns {Promise<string>} 用户输入
72
+ */
73
+ async input(prompt) {
74
+ return new Promise((resolve, reject) => {
75
+ if (!this._ui || !this._ui.editor) {
76
+ reject(new Error('ChatUI not available'));
77
+ return;
78
+ }
79
+
80
+ const originalPrompt = this._ui.editor.getPrompt?.() || '';
81
+ this._ui.editor.setPrompt(prompt);
82
+
83
+ const handler = (value) => {
84
+ this._ui.editor.off('submit', handler);
85
+ if (originalPrompt) {
86
+ this._ui.editor.setPrompt(originalPrompt);
87
+ }
88
+ resolve(value);
89
+ };
90
+
91
+ this._ui.editor.once('submit', handler);
92
+ this._ui.tui.setFocus(this._ui.editor);
93
+ });
94
+ }
95
+
96
+ /**
97
+ * 显示通知
98
+ * @param {string} title - 通知标题
99
+ * @param {string} message - 通知消息
100
+ * @param {number} [duration=3000] - 显示时长(ms)
101
+ */
102
+ notify(title, message, duration = 3000) {
103
+ if (this._ui && this._ui.statusBar) {
104
+ this._ui.statusBar.tooler?.show(`🔔 ${title}: ${message}`);
105
+ setTimeout(() => {
106
+ this._ui.statusBar.tooler?.hide();
107
+ }, duration);
108
+ } else {
109
+ logger.info(`[Notification] ${title}: ${message}`);
110
+ }
111
+ }
112
+
113
+ /**
114
+ * 设置状态栏
115
+ * @param {string} status - 状态文本
116
+ */
117
+ setStatus(status) {
118
+ if (this._ui && this._ui.statusBar) {
119
+ this._ui.statusBar.tooler?.show(status);
120
+ } else {
121
+ logger.debug(`[Status] ${status}`);
122
+ }
123
+ }
124
+
125
+ /**
126
+ * 添加自定义组件
127
+ * @param {string} id - 组件标识
128
+ * @param {Object} component - TUI 组件
129
+ */
130
+ addComponent(id, component) {
131
+ if (this._ui && this._ui.tui) {
132
+ this._ui.tui.addChild(component);
133
+ this._components.set(id, component);
134
+ }
135
+ return this;
136
+ }
137
+
138
+ /**
139
+ * 移除自定义组件
140
+ */
141
+ removeComponent(id) {
142
+ const comp = this._components.get(id);
143
+ if (comp && this._ui && this._ui.tui) {
144
+ this._ui.tui.removeChild(comp);
145
+ this._components.delete(id);
146
+ }
147
+ return this;
148
+ }
149
+
150
+ /**
151
+ * 获取 TUI 实例(用于更高级的定制)
152
+ */
153
+ getTUI() {
154
+ return this._ui?.tui || null;
155
+ }
156
+
157
+ /**
158
+ * 在消息区域显示消息
159
+ */
160
+ showMessage(message, type = 'info') {
161
+ if (this._ui) {
162
+ const colors = {
163
+ info: 'cyan',
164
+ success: 'green',
165
+ warning: 'yellow',
166
+ error: 'red',
167
+ };
168
+ const color = colors[type] || 'cyan';
169
+ this._ui.create_message(`[${this._pluginName}] ${message}\n`, chalk?.[color] || ((t) => t));
170
+ }
171
+ }
172
+ }
173
+
174
+ module.exports = { ExtensionUIContext };
@@ -229,7 +229,7 @@ class MCPExecutorPlugin extends Plugin {
229
229
  }
230
230
 
231
231
  async start(framework) {
232
- // 先连接所有 MCP 服务器
232
+ // 异步加载所有 MCP 服务器,不阻塞框架启动
233
233
  if (this._config.servers) {
234
234
  for (const serverConfig of this._config.servers) {
235
235
  if (serverConfig.enabled === false) {
@@ -238,7 +238,10 @@ class MCPExecutorPlugin extends Plugin {
238
238
  this._serverConfigs.set(serverConfig.name, { ...serverConfig });
239
239
  continue;
240
240
  }
241
- await this.addServer(serverConfig);
241
+ // 异步连接,不 await
242
+ this.addServer(serverConfig).catch(err => {
243
+ log.error(`[MCP] Async server connection failed for ${serverConfig.name}:`, err.message);
244
+ });
242
245
  }
243
246
  }
244
247
 
@@ -458,14 +461,14 @@ class MCPExecutorPlugin extends Plugin {
458
461
  framework.registerTool({
459
462
  name: 'mcp_reload',
460
463
  description:
461
- '重新加载 MCP 服务器配置。当配置文件 .agent/mcp_config.json 修改后使用此工具重载配置',
464
+ '重新加载 MCP 服务器配置。当配置文件 .foliko/mcp_config.json 修改后使用此工具重载配置',
462
465
  inputSchema: z.object({}),
463
466
  execute: async () => {
464
467
  log.info(' mcp_reload called');
465
468
  try {
466
469
  const fs = require('fs');
467
470
  const path = require('path');
468
- const configPath = path.resolve('.agent/mcp_config.json');
471
+ const configPath = path.resolve('.foliko/mcp_config.json');
469
472
 
470
473
  log.info(' Reading config from:', configPath);
471
474
  if (!fs.existsSync(configPath)) {
@@ -1265,7 +1268,7 @@ class MCPExecutorPlugin extends Plugin {
1265
1268
  async _saveMCPServerEnabled(serverName, enabled) {
1266
1269
  const fs = require('fs');
1267
1270
  const path = require('path');
1268
- const configPath = path.resolve('.agent/mcp_config.json');
1271
+ const configPath = path.resolve('.foliko/mcp_config.json');
1269
1272
 
1270
1273
  try {
1271
1274
  if (!fs.existsSync(configPath)) {
@@ -1295,7 +1298,13 @@ class MCPExecutorPlugin extends Plugin {
1295
1298
  // 断开所有 MCP 连接
1296
1299
  for (const [name, clientInfo] of this._clients) {
1297
1300
  try {
1298
- (await clientInfo.client.close?.()) || clientInfo.client.destroy?.();
1301
+ const closePromise = clientInfo.client.close?.() || clientInfo.client.destroy?.();
1302
+ if (closePromise && typeof closePromise.then === 'function') {
1303
+ await Promise.race([
1304
+ closePromise,
1305
+ new Promise((resolve) => setTimeout(() => resolve(), 3000)),
1306
+ ]);
1307
+ }
1299
1308
  } catch (e) {
1300
1309
  // ignore
1301
1310
  }