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.
- package/.agent/data/weixin-media/2026-04-08/img_1775618677512.jpg +0 -0
- package/.agent/data/weixin-media/2026-04-08/img_1775619073340.jpg +0 -0
- package/.agent/data/weixin-media/2026-04-08/img_1775619097536.jpg +0 -0
- package/.agent/data/weixin-media/2026-04-08/img_1775619209388.jpg +0 -0
- package/.agent/plugins/poster-plugin/package.json +2 -1
- package/.agent/plugins/poster-plugin/src/canvas.js +70 -7
- package/.agent/plugins/poster-plugin/src/components/barcode.js +120 -0
- package/.agent/plugins/poster-plugin/src/components/bubble.js +153 -0
- package/.agent/plugins/poster-plugin/src/components/button.js +124 -0
- package/.agent/plugins/poster-plugin/src/components/cta.js +26 -24
- package/.agent/plugins/poster-plugin/src/components/featureGrid.js +22 -17
- package/.agent/plugins/poster-plugin/src/components/frame.js +230 -0
- package/.agent/plugins/poster-plugin/src/components/highlightText.js +144 -0
- package/.agent/plugins/poster-plugin/src/components/icon.js +94 -0
- package/.agent/plugins/poster-plugin/src/components/index.js +19 -0
- package/.agent/plugins/poster-plugin/src/components/listItem.js +6 -5
- package/.agent/plugins/poster-plugin/src/components/qrcode.js +74 -0
- package/.agent/plugins/poster-plugin/src/components/ribbon.js +193 -0
- package/.agent/plugins/poster-plugin/src/components/seal.js +146 -0
- package/.agent/plugins/poster-plugin/src/components/table.js +17 -9
- package/.agent/plugins/poster-plugin/src/components/tagCloud.js +24 -17
- package/.agent/plugins/poster-plugin/src/components/timeline.js +24 -12
- package/.agent/plugins/poster-plugin/src/composer.js +392 -150
- package/.agent/plugins/poster-plugin/src/elements/background.js +36 -4
- package/.agent/plugins/poster-plugin/src/elements/image.js +4 -47
- package/.agent/plugins/poster-plugin/src/elements/index.js +2 -0
- package/.agent/plugins/poster-plugin/src/elements/richText.js +230 -0
- package/.agent/plugins/poster-plugin/src/elements/svg.js +35 -19
- package/.agent/plugins/poster-plugin/src/index.js +430 -7
- package/.agent/plugins/poster-plugin/src/utils/imageLoader.js +84 -0
- package/.agent/plugins/poster-plugin/test-background.svg +1 -0
- package/.agent/plugins/poster-plugin/test-full-poster.svg +2 -0
- package/.agent/plugins/poster-plugin/test-image.png +0 -0
- package/.agent/sessions/cli_default.json +1089 -145
- package/.agent/sessions/weixin_o9cq80zgZqKPA2-s59PN43GdDy1w@im.wechat.json +8902 -0
- package/.claude/settings.local.json +6 -1
- package/output/beef-love-poster.png +0 -0
- package/output/international-news-daily.png +0 -0
- package/package.json +2 -1
- package/plugins/extension-executor-plugin.js +33 -32
- package/plugins/file-system-plugin.js +4 -19
- package/plugins/subagent-plugin.js +37 -14
- package/plugins/weixin-plugin.js +167 -47
- package/poster-test-2.png +0 -0
- package/skills/poster-guide/SKILL.md +497 -5
- package/src/core/agent-chat.js +141 -8
- package/src/core/agent.js +6 -3
- package/calc_tokens_weixin.js +0 -81
- package/foliko-creative-3.png +0 -0
- package/foliko-creative-4.png +0 -0
- package/foliko-creative-5.png +0 -0
- package/story-cover-book-v2.png +0 -0
- package/story-cover-japanese-1.png +0 -0
- package/story-cover-japanese-2.png +0 -0
- package/story-cover-japanese-3.png +0 -0
- package/story-cover-moran.png +0 -0
- 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
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "foliko",
|
|
3
|
-
"version": "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
|
-
|
|
182
|
-
|
|
181
|
+
try {
|
|
182
|
+
const { plugin, tool, args: toolArgs = {} } = args;
|
|
183
|
+
log.info(` ext_call: plugin=${plugin}, tool=${tool}`);
|
|
183
184
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
185
|
+
const ext = this._extensions.get(plugin);
|
|
186
|
+
if (!ext) {
|
|
187
|
+
return { success: false, error: `扩展插件 '${plugin}' 不存在` };
|
|
188
|
+
}
|
|
188
189
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
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
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
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 {
|
|
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
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
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 {
|
|
559
|
+
return { error: err.message }
|
|
537
560
|
}
|
|
538
561
|
}
|
|
539
562
|
})
|
package/plugins/weixin-plugin.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
424
|
-
|
|
425
|
-
|
|
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
|
-
|
|
429
|
-
|
|
430
|
-
|
|
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
|
-
|
|
434
|
-
|
|
435
|
-
|
|
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
|
-
|
|
439
|
-
|
|
440
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|