foliko 1.1.1 → 1.1.2

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 (57) hide show
  1. package/.agent/data/weixin-media/2026-04-08/img_1775618677512.jpg +0 -0
  2. package/.agent/data/weixin-media/2026-04-08/img_1775619073340.jpg +0 -0
  3. package/.agent/data/weixin-media/2026-04-08/img_1775619097536.jpg +0 -0
  4. package/.agent/data/weixin-media/2026-04-08/img_1775619209388.jpg +0 -0
  5. package/.agent/plugins/poster-plugin/package.json +2 -1
  6. package/.agent/plugins/poster-plugin/src/canvas.js +70 -7
  7. package/.agent/plugins/poster-plugin/src/components/barcode.js +120 -0
  8. package/.agent/plugins/poster-plugin/src/components/bubble.js +153 -0
  9. package/.agent/plugins/poster-plugin/src/components/button.js +124 -0
  10. package/.agent/plugins/poster-plugin/src/components/cta.js +26 -24
  11. package/.agent/plugins/poster-plugin/src/components/featureGrid.js +22 -17
  12. package/.agent/plugins/poster-plugin/src/components/frame.js +230 -0
  13. package/.agent/plugins/poster-plugin/src/components/highlightText.js +144 -0
  14. package/.agent/plugins/poster-plugin/src/components/icon.js +94 -0
  15. package/.agent/plugins/poster-plugin/src/components/index.js +19 -0
  16. package/.agent/plugins/poster-plugin/src/components/listItem.js +6 -5
  17. package/.agent/plugins/poster-plugin/src/components/qrcode.js +74 -0
  18. package/.agent/plugins/poster-plugin/src/components/ribbon.js +193 -0
  19. package/.agent/plugins/poster-plugin/src/components/seal.js +146 -0
  20. package/.agent/plugins/poster-plugin/src/components/table.js +17 -9
  21. package/.agent/plugins/poster-plugin/src/components/tagCloud.js +24 -17
  22. package/.agent/plugins/poster-plugin/src/components/timeline.js +24 -12
  23. package/.agent/plugins/poster-plugin/src/composer.js +392 -150
  24. package/.agent/plugins/poster-plugin/src/elements/background.js +36 -4
  25. package/.agent/plugins/poster-plugin/src/elements/image.js +4 -47
  26. package/.agent/plugins/poster-plugin/src/elements/index.js +2 -0
  27. package/.agent/plugins/poster-plugin/src/elements/richText.js +230 -0
  28. package/.agent/plugins/poster-plugin/src/elements/svg.js +35 -19
  29. package/.agent/plugins/poster-plugin/src/index.js +430 -7
  30. package/.agent/plugins/poster-plugin/src/utils/imageLoader.js +84 -0
  31. package/.agent/plugins/poster-plugin/test-background.svg +1 -0
  32. package/.agent/plugins/poster-plugin/test-full-poster.svg +2 -0
  33. package/.agent/plugins/poster-plugin/test-image.png +0 -0
  34. package/.agent/sessions/cli_default.json +1089 -145
  35. package/.agent/sessions/weixin_o9cq80zgZqKPA2-s59PN43GdDy1w@im.wechat.json +8902 -0
  36. package/.claude/settings.local.json +6 -1
  37. package/output/beef-love-poster.png +0 -0
  38. package/output/international-news-daily.png +0 -0
  39. package/package.json +2 -1
  40. package/plugins/extension-executor-plugin.js +33 -32
  41. package/plugins/file-system-plugin.js +4 -19
  42. package/plugins/subagent-plugin.js +37 -14
  43. package/plugins/weixin-plugin.js +167 -47
  44. package/poster-test-2.png +0 -0
  45. package/skills/poster-guide/SKILL.md +497 -5
  46. package/src/core/agent-chat.js +141 -8
  47. package/src/core/agent.js +6 -3
  48. package/calc_tokens_weixin.js +0 -81
  49. package/foliko-creative-3.png +0 -0
  50. package/foliko-creative-4.png +0 -0
  51. package/foliko-creative-5.png +0 -0
  52. package/story-cover-book-v2.png +0 -0
  53. package/story-cover-japanese-1.png +0 -0
  54. package/story-cover-japanese-2.png +0 -0
  55. package/story-cover-japanese-3.png +0 -0
  56. package/story-cover-moran.png +0 -0
  57. package/undefined.png +0 -0
@@ -185,7 +185,12 @@
185
185
  "Bash(node -e \"\nrequire\\('dotenv'\\).config\\(\\);\nconsole.log\\('FOLIKO_BASE_URL:', process.env.FOLIKO_BASE_URL\\);\nconsole.log\\('FOLIKO_PROVIDER:', process.env.FOLIKO_PROVIDER\\);\nconsole.log\\('FOLIKO_MODEL:', process.env.FOLIKO_MODEL\\);\n\")",
186
186
  "Bash(node -e \"\nrequire\\('dotenv'\\).config\\(\\);\nconsole.log\\('FOLIKO_BASE_URL:', process.env.FOLIKO_BASE_URL\\);\nconsole.log\\('FOLIKO_MODEL:', process.env.FOLIKO_MODEL\\);\n\")",
187
187
  "Bash(node -e \"\nconst fs = require\\('fs'\\);\nconst data = JSON.parse\\(fs.readFileSync\\('D:/code/vb-agent/.agent/sessions/cli_default.json', 'utf8'\\)\\);\nconsole.log\\('Messages:', data.messages?.length || 0\\);\nconsole.log\\('File size:', fs.statSync\\('D:/code/vb-agent/.agent/sessions/cli_default.json'\\).size, 'bytes'\\);\n\")",
188
- "Bash(node D:/code/vb-agent/calc_tokens_weixin.js)"
188
+ "Bash(node D:/code/vb-agent/calc_tokens_weixin.js)",
189
+ "Bash(node -e \"const {WeixinBot} = require\\('@chnak/weixin-bot'\\); console.log\\(Object.keys\\(WeixinBot.prototype || {}\\)\\)\" 2>/dev/null || echo \"Cannot access prototype\")",
190
+ "Bash(find D:/code/vb-agent -name \"*poster*\" -type f 2>/dev/null | head -20)",
191
+ "Bash(cd \"D:/code/vb-agent/.agent/plugins/poster-plugin\" && node test-components.js 2>&1)",
192
+ "Bash(cd \"D:/code/vb-agent/.agent/plugins/poster-plugin\" && node test-image.js 2>&1)",
193
+ "Bash(cd \"D:/code/vb-agent/.agent/plugins/poster-plugin\" && node save-test.js 2>&1)"
189
194
  ]
190
195
  }
191
196
  }
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "foliko",
3
- "version": "1.1.1",
3
+ "version": "1.1.2",
4
4
  "description": "简约的插件化 Agent 框架",
5
5
  "main": "src/index.js",
6
6
  "type": "commonjs",
@@ -59,6 +59,7 @@
59
59
  "@larksuiteoapi/node-sdk": "^1.59.0",
60
60
  "@modelcontextprotocol/sdk": "^1.27.1",
61
61
  "ai": "^6.0.146",
62
+ "dayjs": "^1.11.20",
62
63
  "dotenv": "^17.3.1",
63
64
  "gate-api": "^7.2.57",
64
65
  "hono": "^4.12.9",
@@ -178,48 +178,49 @@ class ExtensionExecutorPlugin extends Plugin {
178
178
  args: z.record(z.any()).optional().describe('工具参数'),
179
179
  }),
180
180
  execute: async (args) => {
181
- const { plugin, tool, args: toolArgs = {} } = args;
182
- log.info(` ext_call: plugin=${plugin}, tool=${tool}`);
181
+ try {
182
+ const { plugin, tool, args: toolArgs = {} } = args;
183
+ log.info(` ext_call: plugin=${plugin}, tool=${tool}`);
183
184
 
184
- const ext = this._extensions.get(plugin);
185
- if (!ext) {
186
- return { success: false, error: `扩展插件 '${plugin}' 不存在` };
187
- }
185
+ const ext = this._extensions.get(plugin);
186
+ if (!ext) {
187
+ return { success: false, error: `扩展插件 '${plugin}' 不存在` };
188
+ }
188
189
 
189
- const toolDef = ext.tools.find((t) => t.name === tool);
190
- if (!toolDef) {
191
- const availableTools = ext.tools.map((t) => t.name).join(', ');
192
- return {
193
- success: false,
194
- error: `扩展工具 '${tool}' 不存在。可用工具: ${availableTools || '无'}`
195
- };
196
- }
190
+ const toolDef = ext.tools.find((t) => t.name === tool);
191
+ if (!toolDef) {
192
+ const availableTools = ext.tools.map((t) => t.name).join(', ');
193
+ return {
194
+ success: false,
195
+ error: `扩展工具 '${tool}' 不存在。可用工具: ${availableTools || '无'}`
196
+ };
197
+ }
197
198
 
198
- // 验证参数
199
- if (toolDef.inputSchema) {
200
- try {
201
- const schema = toolDef.inputSchema instanceof z.ZodType
202
- ? toolDef.inputSchema
203
- : this._buildZodSchema(toolDef.inputSchema);
204
- schema.parse(toolArgs);
205
- } catch (err) {
206
- if (err instanceof z.ZodError) {
207
- const fieldErrors = err.errors.map((e) => `${e.path.join('.')}: ${e.message}`).join('; ');
199
+ // 验证参数
200
+ if (toolDef.inputSchema) {
201
+ try {
202
+ const schema = toolDef.inputSchema instanceof z.ZodType
203
+ ? toolDef.inputSchema
204
+ : this._buildZodSchema(toolDef.inputSchema);
205
+ schema.parse(toolArgs);
206
+ } catch (err) {
207
+ if (err instanceof z.ZodError) {
208
+ const fieldErrors = err.errors.map((e) => `${e.path.join('.')}: ${e.message}`).join('; ');
209
+ return {
210
+ success: false,
211
+ error: `参数错误: ${fieldErrors}`,
212
+ inputSchema: toolDef.inputSchema,
213
+ };
214
+ }
208
215
  return {
209
216
  success: false,
210
- error: `参数错误: ${fieldErrors}`,
217
+ error: `参数验证失败: ${err.message}`,
211
218
  inputSchema: toolDef.inputSchema,
212
219
  };
213
220
  }
214
- return {
215
- success: false,
216
- error: `参数验证失败: ${err.message}`,
217
- inputSchema: toolDef.inputSchema,
218
- };
219
221
  }
220
- }
221
222
 
222
- try {
223
+
223
224
  // 触发扩展工具开始事件
224
225
  framework.emit('tool:call', {
225
226
  name: `${plugin}:${tool}`,
@@ -827,10 +827,9 @@ class FileSystemPlugin extends Plugin {
827
827
  timeout: z.number().optional().describe('超时时间(ms),默认 30000'),
828
828
  proxy: z.boolean().optional().describe('是否使用代理,默认 false。访问失败时可自动设为 true 重试'),
829
829
  toMarkdown: z.boolean().default(true).describe('是否将 HTML 响应转换为 Markdown 格式,默认 true'),
830
- maxLength: z.number().default(50000).describe('最大返回长度(字符数),超过则截断,默认 50000')
831
830
  }),
832
831
  execute: async (args, framework) => {
833
- const { url, method = 'GET', headers = {}, body, timeout = 30000, proxy = false, toMarkdown = true, maxLength = 50000 } = args
832
+ const { url, method = 'GET', headers = {}, body, timeout = 30000, proxy = false, toMarkdown = true } = args
834
833
  try {
835
834
  const controller = new AbortController()
836
835
  const timeoutId = setTimeout(() => controller.abort(), timeout)
@@ -859,27 +858,18 @@ class FileSystemPlugin extends Plugin {
859
858
  const isHtml = /<[a-z][\s\S]*>/i.test(data) || data.trim().startsWith('<')
860
859
  if (isHtml) {
861
860
  try {
862
-
863
861
  const markdown = NodeHtmlMarkdown.translate(data)
864
- // 限制返回长度
865
- const truncatedMarkdown = maxLength && markdown.length > maxLength
866
- ? markdown.substring(0, maxLength) + '\n\n...(truncated)'
867
- : markdown
868
862
  return {
869
863
  success: true,
870
864
  status: response.status,
871
865
  statusText: response.statusText,
872
866
  headers: Object.fromEntries(response.headers.entries()),
873
867
  usedProxy: proxy,
874
- markdown: truncatedMarkdown,
868
+ markdown: markdown,
875
869
  originalLength: markdown.length,
876
- truncated: maxLength && markdown.length > maxLength
877
870
  }
878
871
  } catch (e) {
879
872
  // 转换失败时返回原始 body
880
- const truncatedBody = maxLength && data.length > maxLength
881
- ? data.substring(0, maxLength) + '\n\n...(truncated)'
882
- : data
883
873
  return {
884
874
  success: true,
885
875
  status: response.status,
@@ -887,9 +877,8 @@ class FileSystemPlugin extends Plugin {
887
877
  headers: Object.fromEntries(response.headers.entries()),
888
878
  usedProxy: proxy,
889
879
  markdown: null,
890
- body: truncatedBody,
880
+ body: data,
891
881
  originalLength: data.length,
892
- truncated: maxLength && data.length > maxLength,
893
882
  markdownError: e.message
894
883
  }
895
884
  }
@@ -897,18 +886,14 @@ class FileSystemPlugin extends Plugin {
897
886
  }
898
887
 
899
888
  // toMarkdown 为 false 或非 HTML 内容时,返回原始内容
900
- const truncatedData = maxLength && typeof data === 'string' && data.length > maxLength
901
- ? data.substring(0, maxLength) + '\n\n...(truncated)'
902
- : data
903
889
  return {
904
890
  success: true,
905
891
  status: response.status,
906
892
  statusText: response.statusText,
907
893
  headers: Object.fromEntries(response.headers.entries()),
908
894
  usedProxy: proxy,
909
- body: truncatedData,
895
+ body: data,
910
896
  originalLength: typeof data === 'string' ? data.length : null,
911
- truncated: maxLength && typeof data === 'string' && data.length > maxLength
912
897
  }
913
898
  } catch (error) {
914
899
  return {
@@ -9,6 +9,7 @@ const { Plugin } = require('../src/core/plugin-base')
9
9
  const { logger } = require('../src/utils/logger')
10
10
  const log = logger.child('SubAgent')
11
11
  const { z } = require('zod')
12
+ const { cleanResponse } = require('../src/utils')
12
13
  const { Agent } = require('../src/core/agent')
13
14
 
14
15
  class SubAgentPlugin extends Plugin {
@@ -460,17 +461,28 @@ class SubAgentManagerPlugin extends Plugin {
460
461
  if (!plugin) {
461
462
  return { success: false, error: `SubAgent ${args.agentName} not found` }
462
463
  }
463
-
464
464
  try {
465
465
  const result = await plugin.chat(args.task)
466
- return {
467
- success: true,
468
- agent: args.agentName,
469
- result: result.message || result,
470
- success: result.success !== false
466
+ // 提取返回内容
467
+ let output = result.message || result
468
+ // 处理 { type: "text", value: "..." } 格式
469
+ if (output && output.type === 'text' && output.value) {
470
+ output = output.value
471
471
  }
472
+ // 清理 thinking 标签
473
+ if (typeof output === 'string') {
474
+ output = cleanResponse(output)
475
+ }
476
+ if (result.success !== false) {
477
+ // 确保返回的是字符串
478
+ if (typeof output === 'string') {
479
+ return output
480
+ }
481
+ return JSON.stringify(output, null, 2)
482
+ }
483
+ return { error: typeof output === 'string' ? output : JSON.stringify(output) }
472
484
  } catch (err) {
473
- return { success: false, error: err.message }
485
+ return { error: err.message }
474
486
  }
475
487
  }
476
488
  })
@@ -523,17 +535,28 @@ class SubAgentManagerPlugin extends Plugin {
523
535
  if (!plugin) {
524
536
  return { success: false, error: `SubAgent ${args.agentName} not found` }
525
537
  }
526
-
527
538
  try {
528
539
  const result = await plugin.chat(args.task)
529
- return {
530
- success: true,
531
- agent: args.agentName,
532
- result: result.message || result,
533
- success: result.success !== false
540
+ // 提取返回内容
541
+ let output = result.message || result
542
+ // 处理 { type: "text", value: "..." } 格式
543
+ if (output && output.type === 'text' && output.value) {
544
+ output = output.value
534
545
  }
546
+ // 清理 thinking 标签
547
+ if (typeof output === 'string') {
548
+ output = cleanResponse(output)
549
+ }
550
+ if (result.success !== false) {
551
+ // 确保返回的是字符串
552
+ if (typeof output === 'string') {
553
+ return output
554
+ }
555
+ return JSON.stringify(output, null, 2)
556
+ }
557
+ return { error: typeof output === 'string' ? output : JSON.stringify(output) }
535
558
  } catch (err) {
536
- return { success: false, error: err.message }
559
+ return { error: err.message }
537
560
  }
538
561
  }
539
562
  })
@@ -10,12 +10,14 @@ const { CLEAR_LINE, CYAN, DIM, GREEN, RED, YELLOW, colored } = require('../cli/s
10
10
  const { Plugin } = require('../src/core/plugin-base')
11
11
  const { logger } = require('../src/utils/logger')
12
12
  const log = logger.child('WeChat')
13
- const {cleanResponse} =require('../src/utils')
13
+ const { cleanResponse } = require('../src/utils')
14
+ const { renderLine } = require('../cli/src/utils/markdown');
14
15
  const removeMarkdown = require('remove-markdown');
15
16
  const { z } = require('zod')
16
17
  const { WeixinBot } = require('@chnak/weixin-bot')
17
18
  const fs = require('fs')
18
19
  const path = require('path')
20
+ const dayjs = require('dayjs')
19
21
 
20
22
  class WeixinPlugin extends Plugin {
21
23
  constructor(config = {}) {
@@ -26,7 +28,7 @@ class WeixinPlugin extends Plugin {
26
28
  this.priority = 80
27
29
  // 默认不启用,需要在 plugins.json 中设置 enabled: true
28
30
  this.enabled = false
29
- this.path=`.agent/data`
31
+ this.path = path.resolve(process.cwd(), '.agent/data')
30
32
  this.systemPrompt=`你是一个微信助手。
31
33
 
32
34
  **重要:** 子Agent 匹配规则必须遵守:
@@ -56,6 +58,80 @@ class WeixinPlugin extends Plugin {
56
58
  this._initialized = false
57
59
  }
58
60
 
61
+ /**
62
+ * 保存媒体文件到本地
63
+ * @param {Object} msg - 消息对象,包含 raw.item_list
64
+ * @param {string} type - 媒体类型: image/video/file/voice
65
+ * @returns {Promise<string|null>} 保存后的文件路径,失败返回 null
66
+ */
67
+ async _saveMediaFile(msg, type) {
68
+ try {
69
+ const itemList = msg.raw?.item_list;
70
+ if (!itemList || !itemList[0]) {
71
+ return null;
72
+ }
73
+
74
+ const item = itemList[0];
75
+ const baseDir = path.join(this.path, 'weixin-media', dayjs().format('YYYY-MM-DD'));
76
+
77
+ // 确保目录存在
78
+ if (!fs.existsSync(baseDir)) {
79
+ fs.mkdirSync(baseDir, { recursive: true });
80
+ }
81
+
82
+ const fileTimestamp = Date.now();
83
+ let fileName = '';
84
+ let downloadUrl = '';
85
+
86
+ switch (type) {
87
+ case 'image': {
88
+ fileName = `img_${fileTimestamp}.jpg`;
89
+ downloadUrl = item.image_item?.media?.full_url;
90
+ break;
91
+ }
92
+ case 'video': {
93
+ fileName = `video_${fileTimestamp}.mp4`;
94
+ downloadUrl = item.video_item?.media?.full_url || item.video_item?.aeskey;
95
+ break;
96
+ }
97
+ case 'file': {
98
+ fileName = item.file_item?.file_name || `file_${fileTimestamp}`;
99
+ downloadUrl = item.file_item?.media?.full_url;
100
+ break;
101
+ }
102
+ case 'voice': {
103
+ fileName = `voice_${fileTimestamp}.silk`;
104
+ downloadUrl = item.voice_item?.media?.full_url;
105
+ break;
106
+ }
107
+ default:
108
+ return null;
109
+ }
110
+
111
+ if (!downloadUrl) {
112
+ log.warn(`No download URL for ${type}`);
113
+ return null;
114
+ }
115
+
116
+ const filePath = path.join(baseDir, fileName);
117
+
118
+ // 使用 fetch 下载文件
119
+ const response = await fetch(downloadUrl);
120
+ if (!response.ok) {
121
+ throw new Error(`Download failed: ${response.status}`);
122
+ }
123
+
124
+ const buffer = Buffer.from(await response.arrayBuffer());
125
+ fs.writeFileSync(filePath, buffer);
126
+
127
+ log.info(`Media saved: ${filePath}`);
128
+ return filePath;
129
+ } catch (err) {
130
+ log.error(`Failed to save ${type}:`, err.message);
131
+ return null;
132
+ }
133
+ }
134
+
59
135
  install(framework) {
60
136
  this._framework = framework
61
137
  // 注册微信发送工具
@@ -311,6 +387,7 @@ class WeixinPlugin extends Plugin {
311
387
  // log.info('', this.config.forceLogin ? '强制重新扫码登录...' : '正在登录(已有凭证则自动跳过扫码)...')
312
388
 
313
389
  const creds = await this._bot.login(loginOptions)
390
+ console.log(creds)
314
391
  // log.info(' 登录成功 — Bot ID:', creds.accountId)
315
392
  // log.info(' 关联用户:', creds.userId)
316
393
  // log.info(' API 地址:', creds.baseUrl)
@@ -419,25 +496,34 @@ class WeixinPlugin extends Plugin {
419
496
  }
420
497
  break
421
498
 
422
- case 'image':
423
- // log.info(` 收到图片消息`)
424
- await this._processMediaChat(userId, '[用户发送了图片]', msg)
425
- break
499
+ case 'image': {
500
+ const imgPath = await this._saveMediaFile(msg, 'image');
501
+ const imgText = imgPath ? `[用户发送了图片,保存于: ${imgPath}]` : '[用户发送了图片]';
502
+ await this._processMediaChat(userId, imgText, msg);
503
+ break;
504
+ }
426
505
 
427
- case 'file':
428
- // log.info(` 收到文件消息: ${msg.fileName || 'unknown'}`)
429
- await this._processMediaChat(userId, `[用户发送了文件: ${msg.fileName || 'unknown'}]`, msg)
430
- break
506
+ case 'file': {
507
+ const filePath = await this._saveMediaFile(msg, 'file');
508
+ const fileName = msg.raw?.item_list?.[0]?.file_item?.file_name || 'unknown';
509
+ const fileText = filePath ? `[用户发送了文件: ${fileName},保存于: ${filePath}]` : `[用户发送了文件: ${fileName}]`;
510
+ await this._processMediaChat(userId, fileText, msg);
511
+ break;
512
+ }
431
513
 
432
- case 'video':
433
- // log.info(` 收到视频消息`)
434
- await this._processMediaChat(userId, '[用户发送了视频]', msg)
435
- break
514
+ case 'video': {
515
+ const videoPath = await this._saveMediaFile(msg, 'video');
516
+ const videoText = videoPath ? `[用户发送了视频,保存于: ${videoPath}]` : '[用户发送了视频]';
517
+ await this._processMediaChat(userId, videoText, msg);
518
+ break;
519
+ }
436
520
 
437
- case 'voice':
438
- // log.info(` 收到语音消息`)
439
- await this._processMediaChat(userId, '[用户发送了语音消息]', msg)
440
- break
521
+ case 'voice': {
522
+ const voicePath = await this._saveMediaFile(msg, 'voice');
523
+ const voiceText = voicePath ? `[用户发送了语音消息,保存于: ${voicePath}]` : '[用户发送了语音消息]';
524
+ await this._processMediaChat(userId, voiceText, msg);
525
+ break;
526
+ }
441
527
 
442
528
  default:
443
529
  // log.info(` 不支持的消息类型: ${msg.type}`)
@@ -447,6 +533,58 @@ class WeixinPlugin extends Plugin {
447
533
  }
448
534
  }
449
535
 
536
+ /**
537
+ * 流式处理聊天,返回完整响应
538
+ * @param {Object} agent - Agent 实例
539
+ * @param {string} text - 用户消息
540
+ * @param {string} sessionId - 会话 ID
541
+ * @returns {Promise<string>} 完整响应文本
542
+ */
543
+ async _streamChat(agent, text, sessionId) {
544
+ const runWithContext = agent.framework?.runWithContext.bind(agent.framework);
545
+ let fullResponse = '';
546
+ let lineBuffer = '';
547
+ const renderState = { inThink: false, inCodeBlock: false };
548
+
549
+ const processChunk = (chunk) => {
550
+ if (chunk.type === 'text') {
551
+ fullResponse += chunk.text;
552
+ lineBuffer += chunk.text;
553
+
554
+ while (lineBuffer.includes('\n')) {
555
+ const nlIndex = lineBuffer.indexOf('\n');
556
+ const line = lineBuffer.substring(0, nlIndex);
557
+ lineBuffer = lineBuffer.substring(nlIndex + 1);
558
+ if (line.trim()) {
559
+ log.info(renderLine(line, renderState));
560
+ }
561
+ }
562
+ } else if (chunk.type === 'tool-call') {
563
+ log.info(`[工具调用] ${chunk.toolName}`);
564
+ } else if (chunk.type === 'error') {
565
+ log.warn(' Chat stream error:', chunk.error);
566
+ }
567
+ };
568
+
569
+ if (runWithContext) {
570
+ await runWithContext({ sessionId }, async () => {
571
+ for await (const chunk of agent.chatStream(text, { sessionId })) {
572
+ processChunk(chunk);
573
+ }
574
+ });
575
+ } else {
576
+ for await (const chunk of agent.chatStream(text, { sessionId })) {
577
+ processChunk(chunk);
578
+ }
579
+ }
580
+
581
+ if (lineBuffer.trim()) {
582
+ log.info(renderLine(lineBuffer, renderState));
583
+ }
584
+
585
+ return cleanResponse(fullResponse);
586
+ }
587
+
450
588
  /**
451
589
  * 处理媒体消息(图片/文件/视频/语音)
452
590
  */
@@ -456,7 +594,7 @@ class WeixinPlugin extends Plugin {
456
594
  log.error(' No session agent available')
457
595
  return
458
596
  }
459
-
597
+ const full_text=`${text} 不需要发送文件给用户,告知路径即可`
460
598
  const { agent, sessionId } = sessionInfo
461
599
 
462
600
  // 发送正在输入状态
@@ -467,19 +605,7 @@ class WeixinPlugin extends Plugin {
467
605
  await new Promise((resolve) => setTimeout(resolve, 1000))
468
606
 
469
607
  try {
470
- // 使用 chatStream 保持和 CLI 一致的行为
471
- let fullResponse = '';
472
- for await (const chunk of agent.chatStream(text, { sessionId })) {
473
- if (chunk.type === 'text') {
474
- fullResponse += chunk.text;
475
- } else if (chunk.type === 'tool-call') {
476
- log.info(`[工具调用] ${chunk.toolName}`);
477
- } else if (chunk.type === 'error') {
478
- log.warn(' Chat stream error:', chunk.error);
479
- }
480
- }
481
- const response = cleanResponse(fullResponse);
482
-
608
+ const response = await this._streamChat(agent, full_text, sessionId);
483
609
  if (response) {
484
610
  await this._sendMessageBatch(originalMsg, userId, response, true)
485
611
  }
@@ -514,19 +640,7 @@ class WeixinPlugin extends Plugin {
514
640
  await new Promise((resolve) => setTimeout(resolve, 1000))
515
641
 
516
642
  try {
517
- // 使用 chatStream 保持和 CLI 一致的行为
518
- let fullResponse = '';
519
- for await (const chunk of agent.chatStream(text, { sessionId })) {
520
- if (chunk.type === 'text') {
521
- fullResponse += chunk.text;
522
- } else if (chunk.type === 'tool-call') {
523
- // 发送工具调用提示
524
- log.info(`[工具调用] ${chunk.toolName}`);
525
- } else if (chunk.type === 'error') {
526
- log.warn(' Chat stream error:', chunk.error);
527
- }
528
- }
529
- const response = cleanResponse(fullResponse);
643
+ const response = await this._streamChat(agent, text, sessionId);
530
644
 
531
645
  // 发送回复(超过500字自动分批)
532
646
  if (response) {
@@ -560,8 +674,14 @@ class WeixinPlugin extends Plugin {
560
674
  */
561
675
  async _sendMessageBatch(originalMsg, userId, text, useReply = true) {
562
676
  const MAX_LEN = 500
563
- text=removeMarkdown(text)
564
- if (!text || text.length <= MAX_LEN) {
677
+ text = removeMarkdown(text)
678
+
679
+ // 检查是否为空
680
+ if (!text || text.trim().length === 0) {
681
+ text = '[消息内容为空]'
682
+ }
683
+
684
+ if (text.length <= MAX_LEN) {
565
685
  if (useReply && originalMsg) {
566
686
  await this._bot.reply(originalMsg, text)
567
687
  } else {
Binary file