foliko 1.1.7 → 1.1.9

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 (113) hide show
  1. package/.agent/agents/poster-expert.md +228 -0
  2. package/.agent/data/default.json +10 -0
  3. package/.agent/data/plugins-state.json +1 -1
  4. package/.agent/mcp_config.json +0 -6
  5. package/.agent/plugins/test-plugin.py +15 -0
  6. package/.agent/plugins/test_nested_plugin.py +85 -0
  7. package/.agent/sessions/cli_default.json +83 -803
  8. package/.agent/sessions/weixin_o9cq80zgZqKPA2-s59PN43GdDy1w@im.wechat.json +189 -0
  9. package/.claude/settings.local.json +8 -1
  10. package/cli/src/ui/chat-ui.js +32 -21
  11. package/foliko_poster.png +0 -0
  12. package/output/business_poster_final.png +0 -0
  13. package/output/emoji_test_v2.png +0 -0
  14. package/output/foliko-ai-launch-poster.png +0 -0
  15. package/output/foliko-poster.png +0 -0
  16. package/output/muji_style_poster.png +0 -0
  17. package/output/new_product_launch.png +0 -0
  18. package/output/news_poster_625yi_subsidy.png +0 -0
  19. package/output/tech_future_2026.png +0 -0
  20. package/output/tech_future_poster.png +0 -0
  21. package/package.json +2 -2
  22. package/plugins/default-plugins.js +60 -7
  23. package/plugins/extension-executor-plugin.js +32 -22
  24. package/plugins/feishu-plugin.js +2 -2
  25. package/plugins/python-plugin-loader.js +461 -40
  26. package/plugins/python-plugin-loader.js.bak +856 -0
  27. package/plugins/subagent-plugin.js +0 -121
  28. package/plugins/telegram-plugin.js +2 -2
  29. package/plugins/weixin-plugin.js +40 -31
  30. package/poster.png +0 -0
  31. package/skills/foliko-dev/SKILL.md +6 -0
  32. package/skills/python-plugin-dev/SKILL.md +124 -2
  33. package/src/core/agent-chat.js +220 -281
  34. package/src/core/agent.js +127 -29
  35. package/src/executors/mcp-executor.js +282 -26
  36. package/src/utils/plugin-helpers.js +22 -4
  37. package/system.md +362 -109
  38. package/test-scan.js +18 -0
  39. package/.agent/memory/feedback/mnt7jrlt-d67qs7.md +0 -15
  40. package/.agent/memory/feedback/mnt88ja3-al4fuy.md +0 -9
  41. package/.agent/plugins/poster-plugin/README.md +0 -304
  42. package/.agent/plugins/poster-plugin/emojis/rocket.png +0 -1
  43. package/.agent/plugins/poster-plugin/fonts/PatuaOne-Regular.ttf +0 -0
  44. package/.agent/plugins/poster-plugin/fonts/SegoeUI Emoji.ttf +0 -0
  45. package/.agent/plugins/poster-plugin/fonts//345/276/256/350/275/257/351/233/205/351/273/221.ttf +0 -0
  46. package/.agent/plugins/poster-plugin/fonts//345/276/256/350/275/257/351/233/205/351/273/221/347/262/227/344/275/223.ttf +0 -0
  47. package/.agent/plugins/poster-plugin/index.js +0 -13
  48. package/.agent/plugins/poster-plugin/package.json +0 -29
  49. package/.agent/plugins/poster-plugin/src/canvas.js +0 -224
  50. package/.agent/plugins/poster-plugin/src/components/arrow.js +0 -84
  51. package/.agent/plugins/poster-plugin/src/components/avatar.js +0 -71
  52. package/.agent/plugins/poster-plugin/src/components/badge.js +0 -85
  53. package/.agent/plugins/poster-plugin/src/components/barcode.js +0 -120
  54. package/.agent/plugins/poster-plugin/src/components/bubble.js +0 -153
  55. package/.agent/plugins/poster-plugin/src/components/button.js +0 -124
  56. package/.agent/plugins/poster-plugin/src/components/card.js +0 -88
  57. package/.agent/plugins/poster-plugin/src/components/chart.js +0 -127
  58. package/.agent/plugins/poster-plugin/src/components/chip.js +0 -88
  59. package/.agent/plugins/poster-plugin/src/components/columns.js +0 -107
  60. package/.agent/plugins/poster-plugin/src/components/cta.js +0 -87
  61. package/.agent/plugins/poster-plugin/src/components/divider.js +0 -55
  62. package/.agent/plugins/poster-plugin/src/components/feature.js +0 -85
  63. package/.agent/plugins/poster-plugin/src/components/featureGrid.js +0 -117
  64. package/.agent/plugins/poster-plugin/src/components/frame.js +0 -230
  65. package/.agent/plugins/poster-plugin/src/components/grid.js +0 -118
  66. package/.agent/plugins/poster-plugin/src/components/highlightText.js +0 -144
  67. package/.agent/plugins/poster-plugin/src/components/icon.js +0 -94
  68. package/.agent/plugins/poster-plugin/src/components/imageFrame.js +0 -155
  69. package/.agent/plugins/poster-plugin/src/components/index.js +0 -81
  70. package/.agent/plugins/poster-plugin/src/components/listItem.js +0 -147
  71. package/.agent/plugins/poster-plugin/src/components/notification.js +0 -123
  72. package/.agent/plugins/poster-plugin/src/components/progress.js +0 -79
  73. package/.agent/plugins/poster-plugin/src/components/progressCircle.js +0 -117
  74. package/.agent/plugins/poster-plugin/src/components/qrcode.js +0 -74
  75. package/.agent/plugins/poster-plugin/src/components/quote.js +0 -97
  76. package/.agent/plugins/poster-plugin/src/components/rating.js +0 -85
  77. package/.agent/plugins/poster-plugin/src/components/ribbon.js +0 -193
  78. package/.agent/plugins/poster-plugin/src/components/seal.js +0 -146
  79. package/.agent/plugins/poster-plugin/src/components/star.js +0 -70
  80. package/.agent/plugins/poster-plugin/src/components/statCard.js +0 -105
  81. package/.agent/plugins/poster-plugin/src/components/stepper.js +0 -118
  82. package/.agent/plugins/poster-plugin/src/components/table.js +0 -167
  83. package/.agent/plugins/poster-plugin/src/components/tagCloud.js +0 -85
  84. package/.agent/plugins/poster-plugin/src/components/timeline.js +0 -117
  85. package/.agent/plugins/poster-plugin/src/components/watermark.js +0 -52
  86. package/.agent/plugins/poster-plugin/src/composer.js +0 -2146
  87. package/.agent/plugins/poster-plugin/src/elements/artText.js +0 -60
  88. package/.agent/plugins/poster-plugin/src/elements/background.js +0 -84
  89. package/.agent/plugins/poster-plugin/src/elements/circle.js +0 -31
  90. package/.agent/plugins/poster-plugin/src/elements/image.js +0 -28
  91. package/.agent/plugins/poster-plugin/src/elements/index.js +0 -28
  92. package/.agent/plugins/poster-plugin/src/elements/line.js +0 -23
  93. package/.agent/plugins/poster-plugin/src/elements/polygon.js +0 -63
  94. package/.agent/plugins/poster-plugin/src/elements/rectangle.js +0 -32
  95. package/.agent/plugins/poster-plugin/src/elements/richText.js +0 -230
  96. package/.agent/plugins/poster-plugin/src/elements/svg.js +0 -108
  97. package/.agent/plugins/poster-plugin/src/elements/text.js +0 -109
  98. package/.agent/plugins/poster-plugin/src/fonts.js +0 -243
  99. package/.agent/plugins/poster-plugin/src/index.js +0 -2081
  100. package/.agent/plugins/poster-plugin/src/presets.js +0 -36
  101. package/.agent/plugins/poster-plugin/src/templates/business.js +0 -60
  102. package/.agent/plugins/poster-plugin/src/templates/gradient.js +0 -64
  103. package/.agent/plugins/poster-plugin/src/templates/index.js +0 -43
  104. package/.agent/plugins/poster-plugin/src/templates/modern.js +0 -69
  105. package/.agent/plugins/poster-plugin/src/templates/simple.js +0 -58
  106. package/.agent/plugins/poster-plugin/src/templates/social.js +0 -62
  107. package/.agent/plugins/poster-plugin/src/templates/tech.js +0 -84
  108. package/.agent/plugins/poster-plugin/src/utils/imageLoader.js +0 -84
  109. package/.agent/plugins/poster-plugin/test-background.svg +0 -1
  110. package/.agent/plugins/poster-plugin/test-full-poster.svg +0 -2
  111. package/.agent/plugins/poster-plugin/test-image.png +0 -0
  112. package/.agent/plugins/poster-plugin/yarn.lock +0 -1007
  113. package/output/zen_silence.png +0 -0
@@ -96,6 +96,87 @@ const _globalTokenizer = new SimpleTokenizer();
96
96
  // 压缩超时时间(毫秒)
97
97
  const COMPRESSION_TIMEOUT = 30000;
98
98
 
99
+ /**
100
+ * ChatStreamQueue - 流式消息队列
101
+ * 按顺序处理消息,每条消息使用 chatStream 流式输出
102
+ */
103
+ class ChatStreamQueue {
104
+ constructor(chatHandler) {
105
+ this.chatHandler = chatHandler;
106
+ this.queue = []; // 消息队列
107
+ this.isProcessing = false;
108
+ }
109
+
110
+ // 添加消息到队列,返回 Promise
111
+ enqueue(message, options = {}) {
112
+ const { onChunk, onComplete, onError, sessionId } = options;
113
+
114
+ return new Promise((resolve, reject) => {
115
+ this.queue.push({
116
+ message,
117
+ sessionId,
118
+ onChunk,
119
+ onComplete,
120
+ onError,
121
+ resolve,
122
+ reject,
123
+ });
124
+ this.process();
125
+ });
126
+ }
127
+
128
+ // 推送消息到队列(供回调中使用)
129
+ push(message, options = {}) {
130
+ return this.enqueue(message, options);
131
+ }
132
+
133
+ // 按顺序处理
134
+ async process() {
135
+ if (this.isProcessing || this.queue.length === 0) return;
136
+
137
+ this.isProcessing = true;
138
+ const task = this.queue.shift();
139
+
140
+ // 创建 push 上下文,传递给回调
141
+ const ctx = {
142
+ push: (msg, opts = {}) => {
143
+ this.push(msg, { ...task.options, ...opts });
144
+ },
145
+ };
146
+
147
+ try {
148
+ const fullText = await this.sendMessage(task);
149
+ task.resolve(fullText);
150
+ if (task.onComplete) {
151
+ await task.onComplete(fullText, ctx);
152
+ }
153
+ } catch (error) {
154
+ task.reject(error);
155
+ if (task.onError) {
156
+ await task.onError(error, ctx);
157
+ }
158
+ } finally {
159
+ this.isProcessing = false;
160
+ this.process(); // 处理下一个
161
+ }
162
+ }
163
+
164
+ // 发送消息,使用 chatStream 流式输出
165
+ async sendMessage(task) {
166
+ const { message, sessionId, onChunk } = task;
167
+ let fullText = '';
168
+
169
+ for await (const chunk of this.chatHandler.chatStream(message, { sessionId })) {
170
+ fullText += chunk.text || '';
171
+ if (onChunk) {
172
+ onChunk(chunk);
173
+ }
174
+ }
175
+
176
+ return fullText;
177
+ }
178
+ }
179
+
99
180
  class AgentChatHandler extends EventEmitter {
100
181
  /**
101
182
  * @param {Agent} agent - Agent 实例
@@ -147,6 +228,9 @@ class AgentChatHandler extends EventEmitter {
147
228
  this._sessionQueues = new Map(); // sessionId -> Queue of {message, options, resolve, reject}
148
229
  this._processingSessions = new Set(); // sessionId -> processing flag
149
230
 
231
+ // 流式消息队列(用于 continue)
232
+ this._chatStreamQueue = new ChatStreamQueue(this);
233
+
150
234
  // Session Memory prepareStep(从 memory 插件获取)
151
235
  this._sessionMemoryPrepareStep = null;
152
236
  if (agent?.framework) {
@@ -1176,191 +1260,184 @@ ${truncatedContent}${truncatedNote}
1176
1260
  }
1177
1261
 
1178
1262
  /**
1179
- * 执行实际聊天逻辑
1263
+ * 继续完成之前的响应
1264
+ * 当流式响应提前结束时,调用此方法设置 continue 标志
1265
+ * @param {string} prompt - 继续时的提示文本
1266
+ * @param {string} sessionId - 会话 ID
1267
+ * @param {Object} options - 选项 { onChunk, onComplete, onError }
1268
+ */
1269
+ continue(prompt, sessionId = null, options = {}) {
1270
+ const queue = this._chatStreamQueue;
1271
+ const finalOptions = { sessionId, ...options };
1272
+
1273
+ // enqueue 返回 Promise
1274
+ const promise = queue.enqueue(
1275
+ prompt || '请继续完成你的回答,输出之前的完整内容。',
1276
+ finalOptions
1277
+ );
1278
+
1279
+ // 返回包含 push 方法的上下文
1280
+ return {
1281
+ push: (msg, opts = {}) => {
1282
+ queue.push(msg, { sessionId, ...opts });
1283
+ },
1284
+ then: promise.then.bind(promise),
1285
+ catch: promise.catch.bind(promise),
1286
+ };
1287
+ }
1288
+
1289
+ /**
1290
+ * 准备聊天会话:刷新系统提示词、添加用户消息、压缩上下文
1180
1291
  * @param {string|Object} message - 消息
1181
- * @param {Object} options - 选项
1292
+ * @param {string} sessionId - 会话 ID
1293
+ * @returns {Object} - { messageStore, messages, tools, maxSteps }
1182
1294
  * @private
1183
1295
  */
1184
- async _doChat(message, options = {}) {
1185
- const sessionId = options.sessionId || null;
1186
- const framework = this.agent.framework;
1187
-
1188
- // 获取 Per-Session 的消息存储(自动从 session 加载)
1296
+ async _prepareSession(message, sessionId) {
1189
1297
  const messageStore = this._getSessionMessageStore(sessionId);
1190
1298
  const messages = messageStore.messages;
1191
1299
 
1192
- try {
1193
- // 关键:每次 chat 调用时刷新系统提示词,确保包含最新的工具/技能描述
1194
- // 解决上下文过长时 LLM 不调用工具的问题
1195
- this._systemPrompt = this.agent._buildSystemPrompt();
1196
- // 动态导入 AI SDK
1197
- const { tool, ToolLoopAgent } = await this._importAI();
1300
+ // 刷新系统提示词
1301
+ this._systemPrompt = this.agent._buildSystemPrompt();
1198
1302
 
1199
- const userMessage =
1200
- typeof message === 'string' ? { role: 'user', content: message } : message;
1201
- messages.push(userMessage);
1202
-
1203
- // 检查是否需要压缩上下文(包括工具定义)
1204
- const messagesTokens = this._countMessagesTokens(messages);
1205
- const toolsTokens = this._countToolsTokens();
1206
- const systemPromptTokens = this._countTokens(this._systemPrompt);
1207
- const totalTokens = messagesTokens + toolsTokens + systemPromptTokens;
1208
- const limit = this._maxContextTokens * 0.5; // 降低到 50%,更早压缩,防止 context window 超限
1209
-
1210
- if (totalTokens > limit) {
1211
- logger.info(
1212
- `Context large (${totalTokens}/${this._maxContextTokens} tokens = msgs:${messagesTokens} + tools:${toolsTokens} + sys:${systemPromptTokens}), compressing...`
1213
- );
1214
- // 使用带超时控制的压缩(传入 messages 引用)
1215
- await this._compressContext(sessionId, messages, messageStore);
1216
- // 压缩后验证消息配对(防止 orphaned tool result)
1217
- const validatedMessages = this._validateMessagesPairing(messages);
1218
- if (validatedMessages.length !== messages.length) {
1219
- messages.length = 0;
1220
- messages.push(...validatedMessages);
1221
- }
1222
- }
1303
+ // 添加用户消息
1304
+ const userMessage = typeof message === 'string' ? { role: 'user', content: message } : message;
1305
+ messages.push(userMessage);
1223
1306
 
1224
- const maxSteps = options.maxSteps || this._maxSteps;
1225
- const tools = this._getAITools(tool);
1307
+ // 检查上下文大小,必要时压缩
1308
+ const messagesTokens = this._countMessagesTokens(messages);
1309
+ const toolsTokens = this._countToolsTokens();
1310
+ const systemPromptTokens = this._countTokens(this._systemPrompt);
1311
+ const totalTokens = messagesTokens + toolsTokens + systemPromptTokens;
1312
+ const limit = this._maxContextTokens * 0.5;
1226
1313
 
1227
- // 验证消息配对(防止 API 调用时报 orphaned tool result 错误)
1228
- const validatedMessages = this._validateMessagesPairing(messages);
1229
- if (validatedMessages.length !== messages.length) {
1314
+ if (totalTokens > limit) {
1315
+ logger.info(
1316
+ `Context large (${totalTokens}/${this._maxContextTokens} tokens), compressing...`
1317
+ );
1318
+ await this._compressContext(sessionId, messages, messageStore);
1319
+ const validated = this._validateMessagesPairing(messages);
1320
+ if (validated.length !== messages.length) {
1230
1321
  messages.length = 0;
1231
- messages.push(...validatedMessages);
1322
+ messages.push(...validated);
1232
1323
  }
1324
+ }
1233
1325
 
1234
- // 验证并修复消息中的不完整工具调用(在 API 调用前执行,防止开始聊天的时候就报错)
1235
- this._validateToolCalls(messages);
1236
-
1237
- // 最终检查:在 API 调用前再次验证 token 数
1238
- const finalMessagesTokens = this._countMessagesTokens(messages);
1239
- const finalToolsTokens = this._countToolsTokens();
1240
- const finalSystemPromptTokens = this._countTokens(this._systemPrompt);
1241
- const finalTotalTokens = finalMessagesTokens + finalToolsTokens + finalSystemPromptTokens;
1242
- const finalLimit = this._maxContextTokens * 0.5;
1243
-
1244
- // logger.info(
1245
- // `[API Call Check] messages=${messages.length}, tokens=(${finalMessagesTokens}+${finalToolsTokens}+${finalSystemPromptTokens}=${finalTotalTokens}) vs limit=${finalLimit}`
1246
- // );
1247
-
1248
- // 如果仍然超过限制,强制压缩
1249
- if (finalTotalTokens > finalLimit) {
1250
- logger.warn(`[API Call Check] Still over limit after validation, forcing compression`);
1251
- await this._compressContext(sessionId, messages, messageStore);
1252
- const compressedMessages = this._validateMessagesPairing(messages);
1253
- messages.length = 0;
1254
- messages.push(...compressedMessages);
1255
- const afterTokens = this._countMessagesTokens(messages);
1256
- logger.info(
1257
- `[After Forced Compression] messages=${messages.length}, tokens=${afterTokens}`
1258
- );
1259
- }
1326
+ // 验证消息配对
1327
+ const validatedMessages = this._validateMessagesPairing(messages);
1328
+ if (validatedMessages.length !== messages.length) {
1329
+ messages.length = 0;
1330
+ messages.push(...validatedMessages);
1331
+ }
1260
1332
 
1261
- // 准备传给 agent 的消息
1262
- const prepareStepChainStream = async ({ stepNumber, messages: inputMessages }) => {
1263
- try {
1264
- // 1. 验证消息格式
1265
- if (!Array.isArray(inputMessages)) {
1266
- logger.warn('prepareStep received non-array messages');
1267
- return { messages: inputMessages };
1268
- }
1333
+ // 验证工具调用
1334
+ this._validateToolCalls(messages);
1269
1335
 
1270
- // 2. 修复不完整的工具调用(input 只有 { 或其他无效值)
1271
- this._validateToolCalls(inputMessages);
1336
+ // 最终 token 检查
1337
+ const finalTokens =
1338
+ this._countMessagesTokens(messages) +
1339
+ this._countToolsTokens() +
1340
+ this._countTokens(this._systemPrompt);
1341
+ if (finalTokens > this._maxContextTokens * 0.5) {
1342
+ logger.warn('Still over limit after validation, forcing compression');
1343
+ await this._compressContext(sessionId, messages, messageStore);
1344
+ const compressed = this._validateMessagesPairing(messages);
1345
+ messages.length = 0;
1346
+ messages.push(...compressed);
1347
+ }
1272
1348
 
1273
- // 3. 验证消息配对并更新 inputMessages(防止 API 报 orphaned tool result 错误)
1274
- const pairedMessages = this._validateMessagesPairing(inputMessages);
1275
- if (pairedMessages.length !== inputMessages.length) {
1276
- logger.debug(
1277
- `prepareStep pairing: ${inputMessages.length} -> ${pairedMessages.length}`
1278
- );
1279
- }
1280
- // 重要:用配对验证后的结果替换 inputMessages
1281
- inputMessages.length = 0;
1282
- inputMessages.push(...pairedMessages);
1349
+ const maxSteps = this._maxSteps;
1283
1350
 
1284
- // 4. Token 数检查:超过限制才修剪
1285
- const tokenCount = this._countMessagesTokens(inputMessages);
1286
- const tokenLimit = this._maxContextTokens * 0.75; // 75% 阈值,保留缓冲空间
1287
- if (inputMessages.length <= 30 && tokenCount <= tokenLimit) {
1288
- return { messages: inputMessages }; // 消息少且 token 未超限,不修剪
1289
- }
1351
+ return { messageStore, messages, maxSteps };
1352
+ }
1290
1353
 
1291
- // 5. 保留配对完整的消息(使用 AI SDK 标准格式)
1292
- const pruned = this._prepareMessagesForAI(inputMessages);
1293
- logger.debug(`prepareStep pruned: ${inputMessages.length} -> ${pruned.length} messages`);
1354
+ /**
1355
+ * 创建 prepareStep 函数(供 AI SDK 调用)
1356
+ * @returns {Function}
1357
+ * @private
1358
+ */
1359
+ _createPrepareStep() {
1360
+ return async ({ stepNumber, messages: inputMessages }) => {
1361
+ try {
1362
+ if (!Array.isArray(inputMessages)) {
1363
+ return { messages: inputMessages };
1364
+ }
1294
1365
 
1295
- // 6. 返回 AI SDK 6.x 要求的格式
1296
- return { messages: pruned };
1297
- } catch (err) {
1298
- logger.error('prepareStep error:', err.message);
1366
+ this._validateToolCalls(inputMessages);
1367
+ const pairedMessages = this._validateMessagesPairing(inputMessages);
1368
+ if (pairedMessages.length !== inputMessages.length) {
1369
+ inputMessages.length = 0;
1370
+ inputMessages.push(...pairedMessages);
1371
+ }
1372
+
1373
+ const tokenCount = this._countMessagesTokens(inputMessages);
1374
+ const tokenLimit = this._maxContextTokens * 0.75;
1375
+ if (inputMessages.length <= 30 && tokenCount <= tokenLimit) {
1299
1376
  return { messages: inputMessages };
1300
1377
  }
1301
- };
1378
+
1379
+ const pruned = this._prepareMessagesForAI(inputMessages);
1380
+ return { messages: pruned };
1381
+ } catch (err) {
1382
+ logger.error('prepareStep error:', err.message);
1383
+ return { messages: inputMessages };
1384
+ }
1385
+ };
1386
+ }
1387
+
1388
+ /**
1389
+ * 执行实际聊天逻辑
1390
+ * @param {string|Object} message - 消息
1391
+ * @param {Object} options - 选项
1392
+ * @private
1393
+ */
1394
+ async _doChat(message, options = {}) {
1395
+ const sessionId = options.sessionId || null;
1396
+ const framework = this.agent.framework;
1397
+
1398
+ try {
1399
+ const { tool, ToolLoopAgent } = await this._importAI();
1400
+ const { messageStore, messages, maxSteps } = await this._prepareSession(message, sessionId);
1302
1401
 
1303
1402
  if (!this._aiClient) {
1304
1403
  throw new Error('AI client not configured.');
1305
1404
  }
1306
1405
 
1307
- // 不使用 prepareStep,消息格式已在 _saveHistoryToSession 中处理
1406
+ const tools = this._getAITools(tool);
1407
+
1308
1408
  const agent = new ToolLoopAgent({
1309
1409
  model: this._aiClient,
1310
1410
  instructions: this._systemPrompt,
1311
1411
  tools: tools,
1312
- prepareStep: prepareStepChainStream,
1412
+ prepareStep: this._createPrepareStep(),
1313
1413
  });
1314
1414
 
1315
- // 使用 runInSession 让工具执行时能获取 sessionId(Per-Session 隔离)
1316
1415
  const result = await framework.runInSession(sessionId, {}, async () => {
1317
1416
  return agent.generate({ messages, ...this.providerOptions });
1318
1417
  });
1319
1418
 
1320
- // 捕获 API 返回的 usage 用于更准确的 token 估算
1321
1419
  if (result.usage) {
1322
1420
  this._updateMessageStoreUsage(messageStore, result.usage);
1323
- logger.debug(
1324
- `API usage: input=${result.usage.inputTokens}, output=${result.usage.outputTokens}`
1325
- );
1326
1421
  }
1327
1422
 
1328
1423
  messages.push(...result.response.messages);
1329
-
1330
- // 触发 agent:message 事件,让 memory 插件可以自动提取记忆
1331
1424
  const userMsg = messages[messages.length - result.response.messages.length - 1];
1332
1425
  this.emit('message', { content: result.text, sessionId: sessionId, userMessage: userMsg });
1333
1426
 
1334
- // 生成后检查:如果消息太长,下次需要压缩
1335
- const afterTokens = this._countMessagesTokens(messages);
1336
- if (afterTokens > this._maxContextTokens * 0.5) {
1337
- logger.info(`After generation: ${afterTokens} tokens, will compress on next turn`);
1338
- }
1339
-
1340
1427
  return {
1341
1428
  success: true,
1342
1429
  message: result.text || '',
1343
1430
  stepCount: result.stepCount || 1,
1344
1431
  };
1345
1432
  } catch (err) {
1346
- // 捕获 AI API 瞬时错误(如 Invalid JSON response、服务负载过高等)
1347
1433
  const errorMsg = err.message || String(err);
1348
- const causeMsg = err.cause?.message || String(err.cause) || '';
1349
- const fullMsg = errorMsg + causeMsg;
1350
-
1351
1434
  const isTransientError =
1352
- fullMsg.includes('Invalid JSON response') ||
1353
- fullMsg.includes('负载较高') ||
1354
- fullMsg.includes('Failed after') ||
1355
- fullMsg.includes('timeout') ||
1356
- fullMsg.includes('retry') ||
1435
+ errorMsg.includes('Invalid JSON response') ||
1436
+ errorMsg.includes('负载较高') ||
1437
+ errorMsg.includes('Failed after') ||
1357
1438
  err.name === 'AI_RetryError' ||
1358
1439
  err.name === 'AI_APICallError';
1359
1440
 
1360
- logger.warn(
1361
- `[AgentChat] Error caught in _doChat: msg="${errorMsg}", name=${err.name}, isTransient=${isTransientError}`
1362
- );
1363
-
1364
1441
  if (isTransientError) {
1365
1442
  return {
1366
1443
  success: false,
@@ -1370,15 +1447,9 @@ ${truncatedContent}${truncatedNote}
1370
1447
  };
1371
1448
  }
1372
1449
 
1373
- // 未知错误,打印调试信息后继续抛出
1374
- logger.error(
1375
- `[AgentChat] Unknown error in _doChat: ${err.message}, name: ${err.name}, cause: ${err.cause?.message || err.cause}`
1376
- );
1377
1450
  throw err;
1378
1451
  } finally {
1379
- // 校验并修复消息中的不完整工具调用
1380
- this._validateToolCalls(messages);
1381
- // 确保保存聊天历史到 session(无论成功还是失败)
1452
+ const messageStore = this._getSessionMessageStore(sessionId);
1382
1453
  messageStore.save();
1383
1454
  }
1384
1455
  }
@@ -1392,144 +1463,30 @@ ${truncatedContent}${truncatedNote}
1392
1463
  const sessionId = options.sessionId || null;
1393
1464
  const framework = this.agent.framework;
1394
1465
 
1395
- // 获取 Per-Session 的消息存储
1396
- const messageStore = this._getSessionMessageStore(sessionId);
1397
- const messages = messageStore.messages;
1398
-
1399
1466
  try {
1400
- // 关键:每次 chat 调用时刷新系统提示词,确保包含最新的工具/技能描述
1401
- // 解决上下文过长时 LLM 不调用工具的问题
1402
- this._systemPrompt = this.agent._buildSystemPrompt();
1403
- // 动态导入 AI SDK
1404
1467
  const { tool, ToolLoopAgent } = await this._importAI();
1405
- // await fs.writeFile('system.md', this._systemPrompt);
1406
- const userMessage =
1407
- typeof message === 'string' ? { role: 'user', content: message } : message;
1408
- messages.push(userMessage);
1409
-
1410
- // 检查是否需要压缩上下文(包括工具定义)
1411
- const messagesTokens = this._countMessagesTokens(messages);
1412
- const toolsTokens = this._countToolsTokens();
1413
- const systemPromptTokens = this._countTokens(this._systemPrompt);
1414
- const totalTokens = messagesTokens + toolsTokens + systemPromptTokens;
1415
- const limit = this._maxContextTokens * 0.5; // 降低到 50%,更早压缩,防止 context window 超限
1416
- // 对于流式调用,如果上下文太大,先压缩再开始
1417
- if (totalTokens > limit) {
1418
- logger.info(
1419
- `Context large (${totalTokens}/${this._maxContextTokens} tokens), compressing...`
1420
- );
1421
- // 流式调用时等待压缩完成(使用带超时控制的压缩)
1422
- await this._compressContext(sessionId, messages, messageStore);
1423
- // 压缩后验证消息配对(防止 orphaned tool result)
1424
- const validatedMessages = this._validateMessagesPairing(messages);
1425
- if (validatedMessages.length !== messages.length) {
1426
- messages.length = 0;
1427
- messages.push(...validatedMessages);
1428
- }
1429
- }
1468
+ const { messageStore, messages } = await this._prepareSession(message, sessionId);
1430
1469
 
1431
- const maxSteps = options.maxSteps || this._maxSteps;
1432
- const tools = this._getAITools(tool);
1433
1470
  if (!this._aiClient) {
1434
1471
  throw new Error('AI client not configured.');
1435
1472
  }
1436
1473
 
1437
- // 验证消息配对(防止 API 调用时报 orphaned tool result 错误)
1438
- const validatedMessages = this._validateMessagesPairing(messages);
1439
- if (validatedMessages.length !== messages.length) {
1440
- messages.length = 0;
1441
- messages.push(...validatedMessages);
1442
- }
1443
-
1444
- // 验证并修复消息中的不完整工具调用(在 API 调用前执行,防止开始聊天的时候就报错)
1445
- this._validateToolCalls(messages);
1446
-
1447
- // 最终检查:在 API 调用前再次验证 token 数
1448
- const finalMessagesTokens = this._countMessagesTokens(messages);
1449
- const finalToolsTokens = this._countToolsTokens();
1450
- const finalSystemPromptTokens = this._countTokens(this._systemPrompt);
1451
- const finalTotalTokens = finalMessagesTokens + finalToolsTokens + finalSystemPromptTokens;
1452
- const finalLimit = this._maxContextTokens * 0.5;
1453
-
1454
- logger.info(
1455
- `[API Call Check (stream)] messages=${messages.length}, tokens=(${finalMessagesTokens}+${finalToolsTokens}+${finalSystemPromptTokens}=${finalTotalTokens}) vs limit=${finalLimit}`
1456
- );
1457
-
1458
- // 如果仍然超过限制,强制压缩
1459
- if (finalTotalTokens > finalLimit) {
1460
- logger.warn(`[API Call Check (stream)] Still over limit, forcing compression`);
1461
- await this._compressContext(sessionId, messages, messageStore);
1462
- const compressedMessages = this._validateMessagesPairing(messages);
1463
- messages.length = 0;
1464
- messages.push(...compressedMessages);
1465
- const afterTokens = this._countMessagesTokens(messages);
1466
- logger.info(
1467
- `[After Forced Compression (stream)] messages=${messages.length}, tokens=${afterTokens}`
1468
- );
1469
- }
1470
-
1471
- // 准备传给 agent 的消息 (流式版本)
1472
- const prepareStepChainStream = async ({ stepNumber, messages: inputMessages }) => {
1473
- try {
1474
- // 1. 验证消息格式
1475
- if (!Array.isArray(inputMessages)) {
1476
- logger.warn('prepareStep received non-array messages');
1477
- return { messages: inputMessages };
1478
- }
1479
-
1480
- // 2. 修复不完整的工具调用(input 只有 { 或其他无效值)
1481
- this._validateToolCalls(inputMessages);
1482
-
1483
- // 3. 验证消息配对并更新 inputMessages(防止 API 报 orphaned tool result 错误)
1484
- const pairedMessages = this._validateMessagesPairing(inputMessages);
1485
- if (pairedMessages.length !== inputMessages.length) {
1486
- logger.debug(
1487
- `prepareStep pairing: ${inputMessages.length} -> ${pairedMessages.length}`
1488
- );
1489
- }
1490
- // 重要:用配对验证后的结果替换 inputMessages
1491
- inputMessages.length = 0;
1492
- inputMessages.push(...pairedMessages);
1493
-
1494
- // 4. Token 数检查:超过限制才修剪
1495
- const tokenCount = this._countMessagesTokens(inputMessages);
1496
- const tokenLimit = this._maxContextTokens * 0.75; // 75% 阈值,保留缓冲空间
1497
- if (inputMessages.length <= 30 && tokenCount <= tokenLimit) {
1498
- return { messages: inputMessages }; // 消息少且 token 未超限,不修剪
1499
- }
1500
-
1501
- // 5. 保留配对完整的消息(使用 AI SDK 标准格式)
1502
- const pruned = this._prepareMessagesForAI(inputMessages);
1503
- logger.debug(`prepareStep pruned: ${inputMessages.length} -> ${pruned.length} messages`);
1504
-
1505
- // 6. 返回 AI SDK 6.x 要求的格式
1506
- return { messages: pruned };
1507
- } catch (err) {
1508
- logger.error('prepareStep error:', err.message);
1509
- return { messages: inputMessages };
1510
- }
1511
- };
1474
+ const tools = this._getAITools(tool);
1512
1475
 
1513
1476
  const agent = new ToolLoopAgent({
1514
1477
  model: this._aiClient,
1515
1478
  instructions: this._systemPrompt,
1516
1479
  tools: tools,
1517
1480
  stopWhen: isLoopFinished(),
1518
- prepareStep: prepareStepChainStream,
1481
+ prepareStep: this._createPrepareStep(),
1519
1482
  });
1520
1483
 
1521
- // 使用 runInSession 让工具执行时能获取 sessionId(Per-Session 隔离)
1522
1484
  const result = await framework.runInSession(sessionId, {}, async () => {
1523
- return agent.stream({
1524
- messages,
1525
- ...this.providerOptions,
1526
- });
1485
+ return agent.stream({ messages, ...this.providerOptions });
1527
1486
  });
1528
1487
 
1529
1488
  const stream = result.fullStream;
1530
1489
  let fullText = '';
1531
-
1532
- // 流式迭代器
1533
1490
  const iterator = stream[Symbol.asyncIterator] ? stream : stream.fullStream;
1534
1491
 
1535
1492
  for await (const part of iterator || stream) {
@@ -1538,7 +1495,8 @@ ${truncatedContent}${truncatedNote}
1538
1495
  fullText += text;
1539
1496
  yield { type: 'text', text };
1540
1497
  } else if (part.type === 'reasoning') {
1541
- this.emit('thinking', { content: part.text });
1498
+ // this.emit('thinking', { content: part.text });
1499
+ yield { type: 'thinking', text: part.text };
1542
1500
  } else if (part.type === 'tool-call') {
1543
1501
  yield { type: 'tool-call', toolName: part.toolName, input: part.input };
1544
1502
  } else if (part.type === 'tool-result') {
@@ -1550,49 +1508,30 @@ ${truncatedContent}${truncatedNote}
1550
1508
 
1551
1509
  const finishMessages = (await result.response).messages;
1552
1510
  messages.push(...finishMessages);
1553
- // 捕获 API 返回的 usage 用于更准确的 token 估算
1554
1511
  const usage = await result.totalUsage;
1555
1512
  if (usage) {
1556
- try {
1557
- this._updateMessageStoreUsage(messageStore, usage);
1558
- logger.debug(
1559
- `API usage (stream): input=${usage.inputTokens}, output=${usage.outputTokens}`
1560
- );
1561
- } catch (e) {
1562
- logger.debug('Failed to capture stream usage:', e.message);
1563
- }
1513
+ this._updateMessageStoreUsage(messageStore, usage);
1564
1514
  }
1565
1515
 
1566
- // 触发 agent:message 事件,让 memory 插件可以自动提取记忆
1567
1516
  const userMsg = messages[messages.length - finishMessages.length - 1];
1568
1517
  this.emit('message', { content: fullText, sessionId: sessionId, userMessage: userMsg });
1569
1518
  } catch (err) {
1570
- // 捕获 AI API 瞬时错误
1571
1519
  const errorMsg = err.message || String(err);
1572
- const causeMsg = err.cause?.message || String(err.cause) || '';
1573
- const fullMsg = errorMsg + causeMsg;
1574
-
1575
1520
  const isTransientError =
1576
- fullMsg.includes('Invalid JSON response') ||
1577
- fullMsg.includes('负载较高') ||
1578
- fullMsg.includes('Failed after') ||
1579
- fullMsg.includes('timeout') ||
1580
- fullMsg.includes('retry') ||
1521
+ errorMsg.includes('Invalid JSON response') ||
1522
+ errorMsg.includes('负载较高') ||
1581
1523
  err.name === 'AI_RetryError' ||
1582
1524
  err.name === 'AI_APICallError';
1583
1525
 
1584
1526
  if (isTransientError) {
1585
- logger.warn(`[AgentChat] Transient AI error in stream: ${errorMsg}`);
1586
1527
  yield { type: 'error', error: 'AI 服务暂时不可用,请稍后重试。' };
1587
1528
  } else {
1588
- logger.error(`[AgentChat] Stream error: ${err.message}, name: ${err.name}`);
1529
+ logger.error(`[AgentChat] Stream error: ${err.message}`);
1589
1530
  this.emit('error', { error: err.message });
1590
1531
  yield { type: 'error', error: err.message };
1591
1532
  }
1592
1533
  } finally {
1593
- // 校验并修复消息中的不完整工具调用
1594
- this._validateToolCalls(messages);
1595
- // 确保保存聊天历史到 session(无论成功还是失败)
1534
+ const messageStore = this._getSessionMessageStore(sessionId);
1596
1535
  messageStore.save();
1597
1536
  }
1598
1537
  }